瀏覽代碼

Merge branch 'master' of https://github.com/BabylonJS/Babylon.js into drigax/6103-NormalFlippedDuringGLBExport

Nicholas Barlow 6 年之前
父節點
當前提交
85c5786832
共有 100 個文件被更改,包括 10499 次插入7483 次删除
  1. 741 500
      Playground/babylon.d.txt
  2. 0 3
      Tools/Gulp/package.json
  3. 1 1
      Viewer/src/model/viewerModel.ts
  4. 726 496
      dist/preview release/babylon.d.ts
  5. 2 2
      dist/preview release/babylon.js
  6. 2975 2108
      dist/preview release/babylon.max.js
  7. 1 1
      dist/preview release/babylon.max.js.map
  8. 1508 1007
      dist/preview release/babylon.module.d.ts
  9. 1 1
      dist/preview release/glTF2Interface/package.json
  10. 59 47
      dist/preview release/gui/babylon.gui.js
  11. 1 1
      dist/preview release/gui/babylon.gui.js.map
  12. 1 1
      dist/preview release/gui/babylon.gui.min.js
  13. 2 2
      dist/preview release/gui/package.json
  14. 6 6
      dist/preview release/inspector/babylon.inspector.bundle.js
  15. 58 130
      dist/preview release/inspector/babylon.inspector.bundle.max.js
  16. 1 1
      dist/preview release/inspector/babylon.inspector.bundle.max.js.map
  17. 10 16
      dist/preview release/inspector/babylon.inspector.d.ts
  18. 20 35
      dist/preview release/inspector/babylon.inspector.module.d.ts
  19. 6 6
      dist/preview release/inspector/package.json
  20. 57 9
      dist/preview release/loaders/babylon.glTF2FileLoader.js
  21. 1 1
      dist/preview release/loaders/babylon.glTF2FileLoader.js.map
  22. 1 1
      dist/preview release/loaders/babylon.glTF2FileLoader.min.js
  23. 57 9
      dist/preview release/loaders/babylon.glTFFileLoader.js
  24. 1 1
      dist/preview release/loaders/babylon.glTFFileLoader.js.map
  25. 2 2
      dist/preview release/loaders/babylon.glTFFileLoader.min.js
  26. 47 4
      dist/preview release/loaders/babylonjs.loaders.d.ts
  27. 57 9
      dist/preview release/loaders/babylonjs.loaders.js
  28. 1 1
      dist/preview release/loaders/babylonjs.loaders.js.map
  29. 2 2
      dist/preview release/loaders/babylonjs.loaders.min.js
  30. 99 10
      dist/preview release/loaders/babylonjs.loaders.module.d.ts
  31. 3 3
      dist/preview release/loaders/package.json
  32. 2 2
      dist/preview release/materialsLibrary/package.json
  33. 1 1
      dist/preview release/package.json
  34. 1 1
      dist/preview release/packagesSizeBaseLine.json
  35. 2 2
      dist/preview release/postProcessesLibrary/package.json
  36. 2 2
      dist/preview release/proceduralTexturesLibrary/package.json
  37. 1 1
      dist/preview release/readme.md
  38. 6 1
      dist/preview release/serializers/babylon.glTF2Serializer.js
  39. 1 1
      dist/preview release/serializers/babylon.glTF2Serializer.js.map
  40. 1 1
      dist/preview release/serializers/babylon.glTF2Serializer.min.js
  41. 6 1
      dist/preview release/serializers/babylonjs.serializers.js
  42. 1 1
      dist/preview release/serializers/babylonjs.serializers.js.map
  43. 1 1
      dist/preview release/serializers/babylonjs.serializers.min.js
  44. 3 3
      dist/preview release/serializers/package.json
  45. 1508 1007
      dist/preview release/viewer/babylon.module.d.ts
  46. 5 19
      dist/preview release/viewer/babylon.viewer.d.ts
  47. 341 325
      dist/preview release/viewer/babylon.viewer.js
  48. 4 4
      dist/preview release/viewer/babylon.viewer.max.js
  49. 5 22
      dist/preview release/viewer/babylon.viewer.module.d.ts
  50. 99 10
      dist/preview release/viewer/babylonjs.loaders.module.d.ts
  51. 18 6
      dist/preview release/what's new.md
  52. 2 2
      gui/src/2D/controls/inputText.ts
  53. 20 7
      gui/src/3D/controls/control3D.ts
  54. 2 2
      inspector/src/components/actionTabs/lines/booleanLineComponent.tsx
  55. 2 2
      inspector/src/components/actionTabs/lines/buttonLineComponent.tsx
  56. 2 2
      inspector/src/components/actionTabs/lines/color3LineComponent.tsx
  57. 3 3
      inspector/src/components/actionTabs/lines/fileButtonLineComponent.tsx
  58. 3 3
      inspector/src/components/actionTabs/lines/messageLineComponent.tsx
  59. 11 5
      inspector/src/components/actionTabs/lines/numericInputComponent.tsx
  60. 4 4
      inspector/src/components/actionTabs/lines/quaternionLineComponent.tsx
  61. 4 4
      inspector/src/components/actionTabs/lines/radioLineComponent.tsx
  62. 11 11
      inspector/src/components/actionTabs/lines/sliderLineComponent.tsx
  63. 7 7
      inspector/src/components/actionTabs/lines/textInputLineComponent.tsx
  64. 5 5
      inspector/src/components/actionTabs/lines/textLineComponent.tsx
  65. 4 4
      inspector/src/components/actionTabs/lines/textureLineComponent.tsx
  66. 5 5
      inspector/src/components/actionTabs/lines/valueLineComponent.tsx
  67. 5 5
      inspector/src/components/actionTabs/lines/vector2LineComponent.tsx
  68. 27 23
      inspector/src/components/actionTabs/lines/vector3LineComponent.tsx
  69. 3 1
      inspector/src/components/actionTabs/tabs/propertyGrids/animationPropertyGridComponent.tsx
  70. 0 80
      inspector/src/components/actionTabs/tabs/propertyGrids/meshes/axesViewerComponent.tsx
  71. 1 1
      inspector/src/components/actionTabs/tabs/propertyGrids/meshes/bonePropertyGridComponent.tsx
  72. 1 3
      inspector/src/components/actionTabs/tabs/propertyGrids/meshes/meshPropertyGridComponent.tsx
  73. 1 5
      inspector/src/components/actionTabs/tabs/propertyGrids/meshes/transformNodePropertyGridComponent.tsx
  74. 0 1
      inspector/src/components/actionTabs/tabs/statisticsTabComponent.tsx
  75. 4 1
      inspector/src/components/sceneExplorer/entities/sceneTreeItemComponent.tsx
  76. 22 16
      inspector/src/components/sceneExplorer/sceneExplorerComponent.tsx
  77. 11 7
      loaders/src/glTF/2.0/Extensions/KHR_draco_mesh_compression.ts
  78. 56 6
      loaders/src/glTF/2.0/glTFLoader.ts
  79. 24 2
      loaders/src/glTF/2.0/glTFLoaderExtension.ts
  80. 6 5
      package.json
  81. 91 0
      sandbox/debug.html
  82. 2 1
      sandbox/index-local.html
  83. 1 1
      sandbox/index.js
  84. 6 1
      serializers/src/glTF/2.0/glTFAnimation.ts
  85. 27 17
      src/Animations/animatable.ts
  86. 32 20
      src/Animations/animation.ts
  87. 8 2
      src/Animations/animationGroup.ts
  88. 198 165
      src/Animations/runtimeAnimation.ts
  89. 2 1
      src/Bones/skeleton.ts
  90. 52 19
      src/Cameras/arcRotateCamera.ts
  91. 3 2
      src/Cameras/deviceOrientationCamera.ts
  92. 3 1
      src/Culling/boundingBox.ts
  93. 23 0
      src/Debug/physicsViewer.ts
  94. 325 0
      src/Engines/Extensions/engine.cubeTexture.ts
  95. 327 0
      src/Engines/Extensions/engine.multiRender.ts
  96. 524 0
      src/Engines/Extensions/engine.rawTexture.ts
  97. 91 0
      src/Engines/Extensions/engine.renderTarget.ts
  98. 5 1
      src/Engines/Extensions/index.ts
  99. 78 1203
      src/Engines/engine.ts
  100. 0 0
      src/Gizmos/axisScaleGizmo.ts

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


+ 0 - 3
Tools/Gulp/package.json

@@ -12,8 +12,5 @@
         "install": "cd ../../ && npm install && cd Playground/ && npm install && cd ../Viewer && npm install && cd ../Tools/Gulp/",
         "build": "gulp --max-old-space-size=8192 --tsLintFix",
         "start": "gulp run --max-old-space-size=8192"
-    },
-    "dependencies": {
-        "typescript": "^3.3.3333"
     }
 }

+ 1 - 1
Viewer/src/model/viewerModel.ts

@@ -264,7 +264,7 @@ export class ViewerModel implements IDisposable {
                 let ag = new AnimationGroup("animation-" + idx, this._configurationContainer && this._configurationContainer.scene);
                 let add = false;
                 skeleton.getAnimatables().forEach((a) => {
-                    if (a.animations[0]) {
+                    if (a.animations && a.animations[0]) {
                         ag.addTargetedAnimation(a.animations[0], a);
                         add = true;
                     }

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


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


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


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


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


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

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

+ 59 - 47
dist/preview release/gui/babylon.gui.js

@@ -7,7 +7,7 @@
 		exports["babylonjs-gui"] = factory(require("babylonjs"));
 	else
 		root["BABYLON"] = root["BABYLON"] || {}, root["BABYLON"]["GUI"] = factory(root["BABYLON"]);
-})((typeof self !== "undefined" ? self : typeof global !== "undefined" ? global : this), function(__WEBPACK_EXTERNAL_MODULE_babylonjs_Misc_observable__) {
+})((typeof self !== "undefined" ? self : typeof global !== "undefined" ? global : this), function(__WEBPACK_EXTERNAL_MODULE_babylonjs_Maths_math__) {
 return /******/ (function(modules) { // webpackBootstrap
 /******/ 	// The module cache
 /******/ 	var installedModules = {};
@@ -355,7 +355,7 @@ module.exports = g;
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "AdvancedDynamicTextureInstrumentation", function() { return AdvancedDynamicTextureInstrumentation; });
-/* harmony import */ var babylonjs_Misc_tools__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Misc/tools */ "babylonjs/Misc/observable");
+/* harmony import */ var babylonjs_Misc_tools__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Misc/tools */ "babylonjs/Maths/math");
 /* harmony import */ var babylonjs_Misc_tools__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Misc_tools__WEBPACK_IMPORTED_MODULE_0__);
 
 /**
@@ -498,7 +498,7 @@ var AdvancedDynamicTextureInstrumentation = /** @class */ (function () {
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "AdvancedDynamicTexture", function() { return AdvancedDynamicTexture; });
 /* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! tslib */ "../../node_modules/tslib/tslib.es6.js");
-/* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Misc/observable */ "babylonjs/Misc/observable");
+/* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Misc/observable */ "babylonjs/Maths/math");
 /* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_1__);
 /* harmony import */ var _controls_container__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./controls/container */ "./2D/controls/container.ts");
 /* harmony import */ var _style__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./style */ "./2D/style.ts");
@@ -1619,7 +1619,7 @@ var Button = /** @class */ (function (_super) {
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Checkbox", function() { return Checkbox; });
 /* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! tslib */ "../../node_modules/tslib/tslib.es6.js");
-/* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Misc/observable */ "babylonjs/Misc/observable");
+/* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Misc/observable */ "babylonjs/Maths/math");
 /* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_1__);
 /* harmony import */ var _control__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./control */ "./2D/controls/control.ts");
 /* harmony import */ var _stackPanel__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./stackPanel */ "./2D/controls/stackPanel.ts");
@@ -1800,7 +1800,7 @@ var Checkbox = /** @class */ (function (_super) {
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "ColorPicker", function() { return ColorPicker; });
 /* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! tslib */ "../../node_modules/tslib/tslib.es6.js");
-/* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Misc/observable */ "babylonjs/Misc/observable");
+/* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Misc/observable */ "babylonjs/Maths/math");
 /* 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");
@@ -3247,7 +3247,7 @@ var ColorPicker = /** @class */ (function (_super) {
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Container", function() { return Container; });
 /* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! tslib */ "../../node_modules/tslib/tslib.es6.js");
-/* harmony import */ var babylonjs_Misc_logger__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Misc/logger */ "babylonjs/Misc/observable");
+/* harmony import */ var babylonjs_Misc_logger__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Misc/logger */ "babylonjs/Maths/math");
 /* 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");
@@ -3652,7 +3652,7 @@ var Container = /** @class */ (function (_super) {
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Control", function() { return Control; });
-/* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Misc/observable */ "babylonjs/Misc/observable");
+/* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Misc/observable */ "babylonjs/Maths/math");
 /* 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");
@@ -6212,7 +6212,7 @@ var Grid = /** @class */ (function (_super) {
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Image", function() { return Image; });
 /* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! tslib */ "../../node_modules/tslib/tslib.es6.js");
-/* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Misc/observable */ "babylonjs/Misc/observable");
+/* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Misc/observable */ "babylonjs/Maths/math");
 /* 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");
 
@@ -6987,7 +6987,7 @@ var InputPassword = /** @class */ (function (_super) {
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "InputText", function() { return InputText; });
 /* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! tslib */ "../../node_modules/tslib/tslib.es6.js");
-/* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Misc/observable */ "babylonjs/Misc/observable");
+/* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Misc/observable */ "babylonjs/Maths/math");
 /* 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");
@@ -7745,7 +7745,7 @@ var InputText = /** @class */ (function (_super) {
         this._isTextHighlightOn = false;
         //when write permission to clipbaord data is denied
         try {
-            ev.clipboardData.setData("text/plain", this._highlightedText);
+            ev.clipboardData && ev.clipboardData.setData("text/plain", this._highlightedText);
         }
         catch (_a) { } //pass
         this._host.clipboardData = this._highlightedText;
@@ -7760,7 +7760,7 @@ var InputText = /** @class */ (function (_super) {
         this._cursorOffset = this.text.length - this._startHighlightIndex;
         //when write permission to clipbaord data is denied
         try {
-            ev.clipboardData.setData("text/plain", this._highlightedText);
+            ev.clipboardData && ev.clipboardData.setData("text/plain", this._highlightedText);
         }
         catch (_a) { } //pass
         this._host.clipboardData = this._highlightedText;
@@ -7996,7 +7996,7 @@ var InputText = /** @class */ (function (_super) {
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Line", function() { return Line; });
 /* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! tslib */ "../../node_modules/tslib/tslib.es6.js");
-/* harmony import */ var babylonjs_Maths_math__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Maths/math */ "babylonjs/Misc/observable");
+/* harmony import */ var babylonjs_Maths_math__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Maths/math */ "babylonjs/Maths/math");
 /* harmony import */ var babylonjs_Maths_math__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Maths_math__WEBPACK_IMPORTED_MODULE_1__);
 /* harmony import */ var _control__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./control */ "./2D/controls/control.ts");
 /* harmony import */ var _valueAndUnit__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ../valueAndUnit */ "./2D/valueAndUnit.ts");
@@ -8264,7 +8264,7 @@ var Line = /** @class */ (function (_super) {
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "MultiLine", function() { return MultiLine; });
 /* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! tslib */ "../../node_modules/tslib/tslib.es6.js");
-/* harmony import */ var babylonjs_Meshes_abstractMesh__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Meshes/abstractMesh */ "babylonjs/Misc/observable");
+/* harmony import */ var babylonjs_Meshes_abstractMesh__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Meshes/abstractMesh */ "babylonjs/Maths/math");
 /* 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");
@@ -8531,7 +8531,7 @@ var MultiLine = /** @class */ (function (_super) {
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "RadioButton", function() { return RadioButton; });
 /* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! tslib */ "../../node_modules/tslib/tslib.es6.js");
-/* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Misc/observable */ "babylonjs/Misc/observable");
+/* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Misc/observable */ "babylonjs/Maths/math");
 /* 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");
@@ -8876,7 +8876,7 @@ var Rectangle = /** @class */ (function (_super) {
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "ScrollViewer", function() { return ScrollViewer; });
 /* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! tslib */ "../../node_modules/tslib/tslib.es6.js");
-/* harmony import */ var babylonjs_Events_pointerEvents__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Events/pointerEvents */ "babylonjs/Misc/observable");
+/* harmony import */ var babylonjs_Events_pointerEvents__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Events/pointerEvents */ "babylonjs/Maths/math");
 /* harmony import */ var babylonjs_Events_pointerEvents__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Events_pointerEvents__WEBPACK_IMPORTED_MODULE_1__);
 /* harmony import */ var _rectangle__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../rectangle */ "./2D/controls/rectangle.ts");
 /* harmony import */ var _grid__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ../grid */ "./2D/controls/grid.ts");
@@ -9967,7 +9967,7 @@ var SelectionPanel = /** @class */ (function (_super) {
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "BaseSlider", function() { return BaseSlider; });
 /* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! tslib */ "../../node_modules/tslib/tslib.es6.js");
-/* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Misc/observable */ "babylonjs/Misc/observable");
+/* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Misc/observable */ "babylonjs/Maths/math");
 /* 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");
@@ -10862,7 +10862,7 @@ var Slider = /** @class */ (function (_super) {
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "StackPanel", function() { return StackPanel; });
 /* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! tslib */ "../../node_modules/tslib/tslib.es6.js");
-/* harmony import */ var babylonjs_Misc_tools__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Misc/tools */ "babylonjs/Misc/observable");
+/* harmony import */ var babylonjs_Misc_tools__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Misc/tools */ "babylonjs/Maths/math");
 /* 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");
@@ -11120,7 +11120,7 @@ __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "TextWrapping", function() { return TextWrapping; });
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "TextBlock", function() { return TextBlock; });
 /* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! tslib */ "../../node_modules/tslib/tslib.es6.js");
-/* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Misc/observable */ "babylonjs/Misc/observable");
+/* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Misc/observable */ "babylonjs/Maths/math");
 /* 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");
@@ -11560,7 +11560,7 @@ __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "KeyPropertySet", function() { return KeyPropertySet; });
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "VirtualKeyboard", function() { return VirtualKeyboard; });
 /* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! tslib */ "../../node_modules/tslib/tslib.es6.js");
-/* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Misc/observable */ "babylonjs/Misc/observable");
+/* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Misc/observable */ "babylonjs/Maths/math");
 /* 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");
@@ -11935,7 +11935,7 @@ __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Vector2WithInfo", function() { return Vector2WithInfo; });
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Matrix2D", function() { return Matrix2D; });
 /* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! tslib */ "../../node_modules/tslib/tslib.es6.js");
-/* harmony import */ var babylonjs_Maths_math__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Maths/math */ "babylonjs/Misc/observable");
+/* harmony import */ var babylonjs_Maths_math__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Maths/math */ "babylonjs/Maths/math");
 /* harmony import */ var babylonjs_Maths_math__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Maths_math__WEBPACK_IMPORTED_MODULE_1__);
 
 
@@ -12159,7 +12159,7 @@ var Matrix2D = /** @class */ (function () {
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Measure", function() { return Measure; });
-/* harmony import */ var babylonjs_Maths_math__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Maths/math */ "babylonjs/Misc/observable");
+/* harmony import */ var babylonjs_Maths_math__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Maths/math */ "babylonjs/Maths/math");
 /* harmony import */ var babylonjs_Maths_math__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Maths_math__WEBPACK_IMPORTED_MODULE_0__);
 
 
@@ -12292,7 +12292,7 @@ var Measure = /** @class */ (function () {
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "MultiLinePoint", function() { return MultiLinePoint; });
-/* harmony import */ var babylonjs_Maths_math__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Maths/math */ "babylonjs/Misc/observable");
+/* harmony import */ var babylonjs_Maths_math__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Maths/math */ "babylonjs/Maths/math");
 /* harmony import */ var babylonjs_Maths_math__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Maths_math__WEBPACK_IMPORTED_MODULE_0__);
 /* harmony import */ var _valueAndUnit__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./valueAndUnit */ "./2D/valueAndUnit.ts");
 
@@ -12435,7 +12435,7 @@ var MultiLinePoint = /** @class */ (function () {
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Style", function() { return Style; });
-/* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Misc/observable */ "babylonjs/Misc/observable");
+/* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Misc/observable */ "babylonjs/Maths/math");
 /* 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");
 
@@ -12742,7 +12742,7 @@ var ValueAndUnit = /** @class */ (function () {
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "AbstractButton3D", function() { return AbstractButton3D; });
 /* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! tslib */ "../../node_modules/tslib/tslib.es6.js");
-/* harmony import */ var babylonjs_Meshes_transformNode__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Meshes/transformNode */ "babylonjs/Misc/observable");
+/* harmony import */ var babylonjs_Meshes_transformNode__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Meshes/transformNode */ "babylonjs/Maths/math");
 /* 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");
 
@@ -12785,7 +12785,7 @@ var AbstractButton3D = /** @class */ (function (_super) {
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Button3D", function() { return Button3D; });
 /* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! tslib */ "../../node_modules/tslib/tslib.es6.js");
-/* harmony import */ var babylonjs_Maths_math__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Maths/math */ "babylonjs/Misc/observable");
+/* harmony import */ var babylonjs_Maths_math__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Maths/math */ "babylonjs/Maths/math");
 /* harmony import */ var babylonjs_Maths_math__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Maths_math__WEBPACK_IMPORTED_MODULE_1__);
 /* harmony import */ var _abstractButton3D__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./abstractButton3D */ "./3D/controls/abstractButton3D.ts");
 /* harmony import */ var _2D_advancedDynamicTexture__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ../../2D/advancedDynamicTexture */ "./2D/advancedDynamicTexture.ts");
@@ -12962,7 +12962,7 @@ var Button3D = /** @class */ (function (_super) {
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Container3D", function() { return Container3D; });
 /* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! tslib */ "../../node_modules/tslib/tslib.es6.js");
-/* harmony import */ var babylonjs_Meshes_transformNode__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Meshes/transformNode */ "babylonjs/Misc/observable");
+/* harmony import */ var babylonjs_Meshes_transformNode__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Meshes/transformNode */ "babylonjs/Maths/math");
 /* 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");
 
@@ -13119,7 +13119,7 @@ var Container3D = /** @class */ (function (_super) {
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Control3D", function() { return Control3D; });
-/* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Misc/observable */ "babylonjs/Misc/observable");
+/* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Misc/observable */ "babylonjs/Maths/math");
 /* 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");
 
@@ -13408,6 +13408,7 @@ var Control3D = /** @class */ (function () {
     /** @hidden */
     Control3D.prototype._onPointerDown = function (target, coordinates, pointerId, buttonIndex) {
         if (this._downCount !== 0) {
+            this._downCount++;
             return false;
         }
         this._downCount++;
@@ -13420,14 +13421,21 @@ var Control3D = /** @class */ (function () {
     };
     /** @hidden */
     Control3D.prototype._onPointerUp = function (target, coordinates, pointerId, buttonIndex, notifyClick) {
-        this._downCount = 0;
+        this._downCount--;
         delete this._downPointerIds[pointerId];
-        if (notifyClick && (this._enterCount > 0 || this._enterCount === -1)) {
-            this.onPointerClickObservable.notifyObservers(new _vector3WithInfo__WEBPACK_IMPORTED_MODULE_1__["Vector3WithInfo"](coordinates, buttonIndex), -1, target, this);
+        if (this._downCount < 0) {
+            // Handle if forcePointerUp was called prior to this
+            this._downCount = 0;
+            return;
         }
-        this.onPointerUpObservable.notifyObservers(new _vector3WithInfo__WEBPACK_IMPORTED_MODULE_1__["Vector3WithInfo"](coordinates, buttonIndex), -1, target, this);
-        if (this.pointerUpAnimation) {
-            this.pointerUpAnimation();
+        if (this._downCount == 0) {
+            if (notifyClick && (this._enterCount > 0 || this._enterCount === -1)) {
+                this.onPointerClickObservable.notifyObservers(new _vector3WithInfo__WEBPACK_IMPORTED_MODULE_1__["Vector3WithInfo"](coordinates, buttonIndex), -1, target, this);
+            }
+            this.onPointerUpObservable.notifyObservers(new _vector3WithInfo__WEBPACK_IMPORTED_MODULE_1__["Vector3WithInfo"](coordinates, buttonIndex), -1, target, this);
+            if (this.pointerUpAnimation) {
+                this.pointerUpAnimation();
+            }
         }
     };
     /** @hidden */
@@ -13440,6 +13448,10 @@ var Control3D = /** @class */ (function () {
             for (var key in this._downPointerIds) {
                 this._onPointerUp(this, babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_0__["Vector3"].Zero(), +key, 0, true);
             }
+            if (this._downCount > 0) {
+                this._downCount = 1;
+                this._onPointerUp(this, babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_0__["Vector3"].Zero(), 0, 0, true);
+            }
         }
     };
     /** @hidden */
@@ -13513,7 +13525,7 @@ var Control3D = /** @class */ (function () {
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "CylinderPanel", function() { return CylinderPanel; });
 /* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! tslib */ "../../node_modules/tslib/tslib.es6.js");
-/* harmony import */ var babylonjs_Misc_tools__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Misc/tools */ "babylonjs/Misc/observable");
+/* harmony import */ var babylonjs_Misc_tools__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Misc/tools */ "babylonjs/Maths/math");
 /* 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");
@@ -13598,7 +13610,7 @@ __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "HolographicButton", function() { return HolographicButton; });
 /* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! tslib */ "../../node_modules/tslib/tslib.es6.js");
 /* harmony import */ var _button3D__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./button3D */ "./3D/controls/button3D.ts");
-/* harmony import */ var babylonjs_Maths_math__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! babylonjs/Maths/math */ "babylonjs/Misc/observable");
+/* harmony import */ var babylonjs_Maths_math__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! babylonjs/Maths/math */ "babylonjs/Maths/math");
 /* harmony import */ var babylonjs_Maths_math__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Maths_math__WEBPACK_IMPORTED_MODULE_2__);
 /* harmony import */ var _materials_fluentMaterial__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ../materials/fluentMaterial */ "./3D/materials/fluentMaterial.ts");
 /* harmony import */ var _2D_controls_stackPanel__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ../../2D/controls/stackPanel */ "./2D/controls/stackPanel.ts");
@@ -14074,7 +14086,7 @@ var MeshButton3D = /** @class */ (function (_super) {
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "PlanePanel", function() { return PlanePanel; });
 /* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! tslib */ "../../node_modules/tslib/tslib.es6.js");
-/* harmony import */ var babylonjs_Maths_math__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Maths/math */ "babylonjs/Misc/observable");
+/* harmony import */ var babylonjs_Maths_math__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Maths/math */ "babylonjs/Maths/math");
 /* harmony import */ var babylonjs_Maths_math__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Maths_math__WEBPACK_IMPORTED_MODULE_1__);
 /* harmony import */ var _container3D__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./container3D */ "./3D/controls/container3D.ts");
 /* harmony import */ var _volumeBasedPanel__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./volumeBasedPanel */ "./3D/controls/volumeBasedPanel.ts");
@@ -14129,7 +14141,7 @@ var PlanePanel = /** @class */ (function (_super) {
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "ScatterPanel", function() { return ScatterPanel; });
 /* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! tslib */ "../../node_modules/tslib/tslib.es6.js");
-/* harmony import */ var babylonjs_Misc_tools__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Misc/tools */ "babylonjs/Misc/observable");
+/* harmony import */ var babylonjs_Misc_tools__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Misc/tools */ "babylonjs/Maths/math");
 /* 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");
@@ -14256,7 +14268,7 @@ var ScatterPanel = /** @class */ (function (_super) {
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "SpherePanel", function() { return SpherePanel; });
 /* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! tslib */ "../../node_modules/tslib/tslib.es6.js");
-/* harmony import */ var babylonjs_Misc_tools__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Misc/tools */ "babylonjs/Misc/observable");
+/* harmony import */ var babylonjs_Misc_tools__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Misc/tools */ "babylonjs/Maths/math");
 /* 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");
@@ -14341,7 +14353,7 @@ var SpherePanel = /** @class */ (function (_super) {
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "StackPanel3D", function() { return StackPanel3D; });
 /* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! tslib */ "../../node_modules/tslib/tslib.es6.js");
-/* harmony import */ var babylonjs_Misc_tools__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Misc/tools */ "babylonjs/Misc/observable");
+/* harmony import */ var babylonjs_Misc_tools__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Misc/tools */ "babylonjs/Maths/math");
 /* 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");
 
@@ -14466,7 +14478,7 @@ var StackPanel3D = /** @class */ (function (_super) {
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "VolumeBasedPanel", function() { return VolumeBasedPanel; });
 /* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! tslib */ "../../node_modules/tslib/tslib.es6.js");
-/* harmony import */ var babylonjs_Misc_tools__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Misc/tools */ "babylonjs/Misc/observable");
+/* harmony import */ var babylonjs_Misc_tools__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Misc/tools */ "babylonjs/Maths/math");
 /* 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");
 
@@ -14657,7 +14669,7 @@ var VolumeBasedPanel = /** @class */ (function (_super) {
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "GUI3DManager", function() { return GUI3DManager; });
-/* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Misc/observable */ "babylonjs/Misc/observable");
+/* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Misc/observable */ "babylonjs/Maths/math");
 /* 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");
 
@@ -14924,7 +14936,7 @@ __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "FluentMaterialDefines", function() { return FluentMaterialDefines; });
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "FluentMaterial", function() { return FluentMaterial; });
 /* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! tslib */ "../../node_modules/tslib/tslib.es6.js");
-/* harmony import */ var babylonjs_Misc_decorators__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Misc/decorators */ "babylonjs/Misc/observable");
+/* harmony import */ var babylonjs_Misc_decorators__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Misc/decorators */ "babylonjs/Maths/math");
 /* 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");
@@ -15246,7 +15258,7 @@ __webpack_require__.r(__webpack_exports__);
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "fluentPixelShader", function() { return fluentPixelShader; });
-/* harmony import */ var babylonjs_Materials_effect__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Materials/effect */ "babylonjs/Misc/observable");
+/* harmony import */ var babylonjs_Materials_effect__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Materials/effect */ "babylonjs/Maths/math");
 /* 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';
@@ -15268,7 +15280,7 @@ var fluentPixelShader = { name: name, shader: shader };
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "fluentVertexShader", function() { return fluentVertexShader; });
-/* harmony import */ var babylonjs_Materials_effect__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Materials/effect */ "babylonjs/Misc/observable");
+/* harmony import */ var babylonjs_Materials_effect__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Materials/effect */ "babylonjs/Maths/math");
 /* 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';
@@ -15291,7 +15303,7 @@ var fluentVertexShader = { name: name, shader: shader };
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Vector3WithInfo", function() { return Vector3WithInfo; });
 /* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! tslib */ "../../node_modules/tslib/tslib.es6.js");
-/* harmony import */ var babylonjs_Maths_math__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Maths/math */ "babylonjs/Misc/observable");
+/* harmony import */ var babylonjs_Maths_math__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Maths/math */ "babylonjs/Maths/math");
 /* harmony import */ var babylonjs_Maths_math__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Maths_math__WEBPACK_IMPORTED_MODULE_1__);
 
 
@@ -15585,14 +15597,14 @@ if (typeof globalObject !== "undefined") {
 
 /***/ }),
 
-/***/ "babylonjs/Misc/observable":
+/***/ "babylonjs/Maths/math":
 /*!****************************************************************************************************!*\
   !*** 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_Maths_math__;
 
 /***/ })
 

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


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


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

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


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

@@ -33887,7 +33887,10 @@ var NumericInputComponent = /** @class */ (function (_super) {
         return (react__WEBPACK_IMPORTED_MODULE_1__["createElement"]("div", { className: "numeric" },
             this.props.label &&
                 react__WEBPACK_IMPORTED_MODULE_1__["createElement"]("div", { className: "numeric-label" }, this.props.label + ": "),
-            react__WEBPACK_IMPORTED_MODULE_1__["createElement"]("input", { type: "number", step: "1", className: "numeric-input", value: this.state.value, onChange: function (evt) { return _this.updateValue(evt); } })));
+            react__WEBPACK_IMPORTED_MODULE_1__["createElement"]("input", { type: "number", step: this.props.step, className: "numeric-input", value: this.state.value, onChange: function (evt) { return _this.updateValue(evt); } })));
+    };
+    NumericInputComponent.defaultProps = {
+        step: 1,
     };
     return NumericInputComponent;
 }(react__WEBPACK_IMPORTED_MODULE_1__["Component"]));
@@ -34818,29 +34821,26 @@ var Vector3LineComponent = /** @class */ (function (_super) {
             initialValue: previousValue
         });
     };
+    Vector3LineComponent.prototype.updateVector3 = function () {
+        var store = this.props.target[this.props.propertyName].clone();
+        this.props.target[this.props.propertyName] = this.state.value;
+        this.setState({ value: store });
+        this.raiseOnPropertyChanged(store);
+    };
     Vector3LineComponent.prototype.updateStateX = function (value) {
         this._localChange = true;
-        var store = this.state.value.clone();
-        this.props.target[this.props.propertyName].x = value;
         this.state.value.x = value;
-        this.setState({ value: this.state.value });
-        this.raiseOnPropertyChanged(store);
+        this.updateVector3();
     };
     Vector3LineComponent.prototype.updateStateY = function (value) {
         this._localChange = true;
-        var store = this.state.value.clone();
-        this.props.target[this.props.propertyName].y = value;
         this.state.value.y = value;
-        this.setState({ value: this.state.value });
-        this.raiseOnPropertyChanged(store);
+        this.updateVector3();
     };
     Vector3LineComponent.prototype.updateStateZ = function (value) {
         this._localChange = true;
-        var store = this.state.value.clone();
-        this.props.target[this.props.propertyName].z = value;
         this.state.value.z = value;
-        this.setState({ value: this.state.value });
-        this.raiseOnPropertyChanged(store);
+        this.updateVector3();
     };
     Vector3LineComponent.prototype.render = function () {
         var _this = this;
@@ -34852,9 +34852,12 @@ var Vector3LineComponent = /** @class */ (function (_super) {
                 react__WEBPACK_IMPORTED_MODULE_1__["createElement"]("div", { className: "expand hoverIcon", onClick: function () { return _this.switchExpandState(); }, title: "Expand" }, chevron)),
             this.state.isExpanded &&
                 react__WEBPACK_IMPORTED_MODULE_1__["createElement"]("div", { className: "secondLine" },
-                    react__WEBPACK_IMPORTED_MODULE_1__["createElement"](_numericInputComponent__WEBPACK_IMPORTED_MODULE_2__["NumericInputComponent"], { label: "x", value: this.state.value.x, onChange: function (value) { return _this.updateStateX(value); } }),
-                    react__WEBPACK_IMPORTED_MODULE_1__["createElement"](_numericInputComponent__WEBPACK_IMPORTED_MODULE_2__["NumericInputComponent"], { label: "y", value: this.state.value.y, onChange: function (value) { return _this.updateStateY(value); } }),
-                    react__WEBPACK_IMPORTED_MODULE_1__["createElement"](_numericInputComponent__WEBPACK_IMPORTED_MODULE_2__["NumericInputComponent"], { label: "z", value: this.state.value.z, onChange: function (value) { return _this.updateStateZ(value); } }))));
+                    react__WEBPACK_IMPORTED_MODULE_1__["createElement"](_numericInputComponent__WEBPACK_IMPORTED_MODULE_2__["NumericInputComponent"], { label: "x", step: this.props.step, value: this.state.value.x, onChange: function (value) { return _this.updateStateX(value); } }),
+                    react__WEBPACK_IMPORTED_MODULE_1__["createElement"](_numericInputComponent__WEBPACK_IMPORTED_MODULE_2__["NumericInputComponent"], { label: "y", step: this.props.step, value: this.state.value.y, onChange: function (value) { return _this.updateStateY(value); } }),
+                    react__WEBPACK_IMPORTED_MODULE_1__["createElement"](_numericInputComponent__WEBPACK_IMPORTED_MODULE_2__["NumericInputComponent"], { label: "z", step: this.props.step, value: this.state.value.z, onChange: function (value) { return _this.updateStateZ(value); } }))));
+    };
+    Vector3LineComponent.defaultProps = {
+        step: 1,
     };
     return Vector3LineComponent;
 }(react__WEBPACK_IMPORTED_MODULE_1__["Component"]));
@@ -35482,7 +35485,9 @@ var AnimationGridComponent = /** @class */ (function (_super) {
             _this._animations = new Array();
             animatables.forEach(function (animatable) {
                 var _a;
-                (_a = _this._animations).push.apply(_a, animatable.animations);
+                if (animatable.animations) {
+                    (_a = _this._animations).push.apply(_a, animatable.animations);
+                }
             });
             // Extract from and to
             if (_this._animations && _this._animations.length) {
@@ -37880,85 +37885,6 @@ var TexturePropertyGridComponent = /** @class */ (function (_super) {
 
 /***/ }),
 
-/***/ "./components/actionTabs/tabs/propertyGrids/meshes/axesViewerComponent.tsx":
-/*!*********************************************************************************!*\
-  !*** ./components/actionTabs/tabs/propertyGrids/meshes/axesViewerComponent.tsx ***!
-  \*********************************************************************************/
-/*! exports provided: AxesViewerComponent */
-/***/ (function(module, __webpack_exports__, __webpack_require__) {
-
-"use strict";
-__webpack_require__.r(__webpack_exports__);
-/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "AxesViewerComponent", function() { return AxesViewerComponent; });
-/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! tslib */ "../../node_modules/tslib/tslib.es6.js");
-/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! react */ "../../node_modules/react/index.js");
-/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_1__);
-/* harmony import */ var babylonjs_Maths_math__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! babylonjs/Maths/math */ "babylonjs/Misc/observable");
-/* harmony import */ var babylonjs_Maths_math__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Maths_math__WEBPACK_IMPORTED_MODULE_2__);
-/* harmony import */ var _lines_checkBoxLineComponent__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ../../../lines/checkBoxLineComponent */ "./components/actionTabs/lines/checkBoxLineComponent.tsx");
-
-
-
-
-
-
-var AxesViewerComponent = /** @class */ (function (_super) {
-    tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](AxesViewerComponent, _super);
-    function AxesViewerComponent(props) {
-        var _this = _super.call(this, props) || this;
-        var node = _this.props.node;
-        if (!node.reservedDataStore) {
-            node.reservedDataStore = {};
-        }
-        _this.state = { displayAxis: (node.reservedDataStore && node.reservedDataStore.axisViewer) ? true : false };
-        return _this;
-    }
-    AxesViewerComponent.prototype.shouldComponentUpdate = function (nextProps, nextState) {
-        if (nextProps.node !== this.props.node) {
-            nextState.displayAxis = (nextProps.node.reservedDataStore && nextProps.node.reservedDataStore.axisViewer) ? true : false;
-        }
-        return true;
-    };
-    AxesViewerComponent.prototype.displayAxes = function () {
-        var node = this.props.node;
-        var scene = babylonjs_Maths_math__WEBPACK_IMPORTED_MODULE_2__["UtilityLayerRenderer"].DefaultUtilityLayer.utilityLayerScene;
-        if (node.reservedDataStore.axisViewer) {
-            node.reservedDataStore.axisViewer.dispose();
-            node.reservedDataStore.axisViewer = null;
-            scene.onBeforeRenderObservable.remove(node.reservedDataStore.onBeforeRenderObserver);
-            node.reservedDataStore.onBeforeRenderObserver = null;
-            this.setState({ displayAxis: false });
-            return;
-        }
-        var viewer = new babylonjs_Maths_math__WEBPACK_IMPORTED_MODULE_2__["AxesViewer"](scene);
-        node.reservedDataStore.axisViewer = viewer;
-        var x = new babylonjs_Maths_math__WEBPACK_IMPORTED_MODULE_2__["Vector3"](1, 0, 0);
-        var y = new babylonjs_Maths_math__WEBPACK_IMPORTED_MODULE_2__["Vector3"](0, 1, 0);
-        var z = new babylonjs_Maths_math__WEBPACK_IMPORTED_MODULE_2__["Vector3"](0, 0, 1);
-        viewer.xAxis.reservedDataStore = { hidden: true };
-        viewer.yAxis.reservedDataStore = { hidden: true };
-        viewer.zAxis.reservedDataStore = { hidden: true };
-        node.reservedDataStore.onBeforeRenderObserver = scene.onBeforeRenderObservable.add(function () {
-            var cameraMatrix = scene.activeCamera.getWorldMatrix();
-            var matrix = node.getWorldMatrix();
-            var extend = babylonjs_Maths_math__WEBPACK_IMPORTED_MODULE_2__["Tmp"].Vector3[0];
-            babylonjs_Maths_math__WEBPACK_IMPORTED_MODULE_2__["Vector3"].TransformCoordinatesFromFloatsToRef(0, 0, 1, cameraMatrix, extend);
-            viewer.scaleLines = extend.length() / 10;
-            viewer.update(node.getAbsolutePosition(), babylonjs_Maths_math__WEBPACK_IMPORTED_MODULE_2__["Vector3"].TransformNormal(x, matrix), babylonjs_Maths_math__WEBPACK_IMPORTED_MODULE_2__["Vector3"].TransformNormal(y, matrix), babylonjs_Maths_math__WEBPACK_IMPORTED_MODULE_2__["Vector3"].TransformNormal(z, matrix));
-        });
-        this.setState({ displayAxis: true });
-    };
-    AxesViewerComponent.prototype.render = function () {
-        var _this = this;
-        return (react__WEBPACK_IMPORTED_MODULE_1__["createElement"](_lines_checkBoxLineComponent__WEBPACK_IMPORTED_MODULE_3__["CheckBoxLineComponent"], { label: "Display axes", isSelected: function () { return _this.state.displayAxis; }, onSelect: function () { return _this.displayAxes(); } }));
-    };
-    return AxesViewerComponent;
-}(react__WEBPACK_IMPORTED_MODULE_1__["Component"]));
-
-
-
-/***/ }),
-
 /***/ "./components/actionTabs/tabs/propertyGrids/meshes/bonePropertyGridComponent.tsx":
 /*!***************************************************************************************!*\
   !*** ./components/actionTabs/tabs/propertyGrids/meshes/bonePropertyGridComponent.tsx ***!
@@ -37996,7 +37922,7 @@ var BonePropertyGridComponent = /** @class */ (function (_super) {
             react__WEBPACK_IMPORTED_MODULE_1__["createElement"](_lineContainerComponent__WEBPACK_IMPORTED_MODULE_2__["LineContainerComponent"], { globalState: this.props.globalState, title: "TRANSFORMATIONS" },
                 react__WEBPACK_IMPORTED_MODULE_1__["createElement"](_lines_vector3LineComponent__WEBPACK_IMPORTED_MODULE_4__["Vector3LineComponent"], { label: "Position", target: bone, propertyName: "position", onPropertyChangedObservable: this.props.onPropertyChangedObservable }),
                 !bone.rotationQuaternion &&
-                    react__WEBPACK_IMPORTED_MODULE_1__["createElement"](_lines_vector3LineComponent__WEBPACK_IMPORTED_MODULE_4__["Vector3LineComponent"], { label: "Rotation", target: bone, propertyName: "rotation", onPropertyChangedObservable: this.props.onPropertyChangedObservable }),
+                    react__WEBPACK_IMPORTED_MODULE_1__["createElement"](_lines_vector3LineComponent__WEBPACK_IMPORTED_MODULE_4__["Vector3LineComponent"], { label: "Rotation", target: bone, propertyName: "rotation", step: 0.01, onPropertyChangedObservable: this.props.onPropertyChangedObservable }),
                 bone.rotationQuaternion &&
                     react__WEBPACK_IMPORTED_MODULE_1__["createElement"](_lines_quaternionLineComponent__WEBPACK_IMPORTED_MODULE_5__["QuaternionLineComponent"], { label: "Rotation", target: bone, propertyName: "rotationQuaternion", onPropertyChangedObservable: this.props.onPropertyChangedObservable }),
                 react__WEBPACK_IMPORTED_MODULE_1__["createElement"](_lines_vector3LineComponent__WEBPACK_IMPORTED_MODULE_4__["Vector3LineComponent"], { label: "Scaling", target: bone, propertyName: "scaling", onPropertyChangedObservable: this.props.onPropertyChangedObservable }))));
@@ -38029,10 +37955,8 @@ __webpack_require__.r(__webpack_exports__);
 /* harmony import */ var _lines_vector3LineComponent__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! ../../../lines/vector3LineComponent */ "./components/actionTabs/lines/vector3LineComponent.tsx");
 /* harmony import */ var _lines_sliderLineComponent__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! ../../../lines/sliderLineComponent */ "./components/actionTabs/lines/sliderLineComponent.tsx");
 /* harmony import */ var _lines_quaternionLineComponent__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(/*! ../../../lines/quaternionLineComponent */ "./components/actionTabs/lines/quaternionLineComponent.tsx");
-/* harmony import */ var _axesViewerComponent__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(/*! ./axesViewerComponent */ "./components/actionTabs/tabs/propertyGrids/meshes/axesViewerComponent.tsx");
-/* harmony import */ var _lines_floatLineComponent__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(/*! ../../../lines/floatLineComponent */ "./components/actionTabs/lines/floatLineComponent.tsx");
-/* harmony import */ var _customPropertyGridComponent__WEBPACK_IMPORTED_MODULE_11__ = __webpack_require__(/*! ../customPropertyGridComponent */ "./components/actionTabs/tabs/propertyGrids/customPropertyGridComponent.tsx");
-
+/* harmony import */ var _lines_floatLineComponent__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(/*! ../../../lines/floatLineComponent */ "./components/actionTabs/lines/floatLineComponent.tsx");
+/* harmony import */ var _customPropertyGridComponent__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(/*! ../customPropertyGridComponent */ "./components/actionTabs/tabs/propertyGrids/customPropertyGridComponent.tsx");
 
 
 
@@ -38190,7 +38114,7 @@ var MeshPropertyGridComponent = /** @class */ (function (_super) {
         var renderNormalVectors = (mesh.reservedDataStore && mesh.reservedDataStore.normalLines) ? true : false;
         var renderWireframeOver = (mesh.reservedDataStore && mesh.reservedDataStore.wireframeOver) ? true : false;
         return (react__WEBPACK_IMPORTED_MODULE_1__["createElement"]("div", { className: "pane" },
-            react__WEBPACK_IMPORTED_MODULE_1__["createElement"](_customPropertyGridComponent__WEBPACK_IMPORTED_MODULE_11__["CustomPropertyGridComponent"], { globalState: this.props.globalState, target: mesh, onPropertyChangedObservable: this.props.onPropertyChangedObservable }),
+            react__WEBPACK_IMPORTED_MODULE_1__["createElement"](_customPropertyGridComponent__WEBPACK_IMPORTED_MODULE_10__["CustomPropertyGridComponent"], { globalState: this.props.globalState, target: mesh, onPropertyChangedObservable: this.props.onPropertyChangedObservable }),
             react__WEBPACK_IMPORTED_MODULE_1__["createElement"](_lineContainerComponent__WEBPACK_IMPORTED_MODULE_3__["LineContainerComponent"], { globalState: this.props.globalState, title: "GENERAL" },
                 react__WEBPACK_IMPORTED_MODULE_1__["createElement"](_lines_textLineComponent__WEBPACK_IMPORTED_MODULE_4__["TextLineComponent"], { label: "ID", value: mesh.id }),
                 react__WEBPACK_IMPORTED_MODULE_1__["createElement"](_lines_textLineComponent__WEBPACK_IMPORTED_MODULE_4__["TextLineComponent"], { label: "Unique ID", value: mesh.uniqueId.toString() }),
@@ -38206,13 +38130,13 @@ var MeshPropertyGridComponent = /** @class */ (function (_super) {
             react__WEBPACK_IMPORTED_MODULE_1__["createElement"](_lineContainerComponent__WEBPACK_IMPORTED_MODULE_3__["LineContainerComponent"], { globalState: this.props.globalState, title: "TRANSFORMS" },
                 react__WEBPACK_IMPORTED_MODULE_1__["createElement"](_lines_vector3LineComponent__WEBPACK_IMPORTED_MODULE_6__["Vector3LineComponent"], { label: "Position", target: mesh, propertyName: "position", onPropertyChangedObservable: this.props.onPropertyChangedObservable }),
                 !mesh.rotationQuaternion &&
-                    react__WEBPACK_IMPORTED_MODULE_1__["createElement"](_lines_vector3LineComponent__WEBPACK_IMPORTED_MODULE_6__["Vector3LineComponent"], { label: "Rotation", target: mesh, propertyName: "rotation", onPropertyChangedObservable: this.props.onPropertyChangedObservable }),
+                    react__WEBPACK_IMPORTED_MODULE_1__["createElement"](_lines_vector3LineComponent__WEBPACK_IMPORTED_MODULE_6__["Vector3LineComponent"], { label: "Rotation", target: mesh, propertyName: "rotation", step: 0.01, onPropertyChangedObservable: this.props.onPropertyChangedObservable }),
                 mesh.rotationQuaternion &&
                     react__WEBPACK_IMPORTED_MODULE_1__["createElement"](_lines_quaternionLineComponent__WEBPACK_IMPORTED_MODULE_8__["QuaternionLineComponent"], { label: "Rotation", target: mesh, propertyName: "rotationQuaternion", onPropertyChangedObservable: this.props.onPropertyChangedObservable }),
                 react__WEBPACK_IMPORTED_MODULE_1__["createElement"](_lines_vector3LineComponent__WEBPACK_IMPORTED_MODULE_6__["Vector3LineComponent"], { label: "Scaling", target: mesh, propertyName: "scaling", onPropertyChangedObservable: this.props.onPropertyChangedObservable })),
             react__WEBPACK_IMPORTED_MODULE_1__["createElement"](_lineContainerComponent__WEBPACK_IMPORTED_MODULE_3__["LineContainerComponent"], { globalState: this.props.globalState, title: "DISPLAY", closed: true },
                 react__WEBPACK_IMPORTED_MODULE_1__["createElement"](_lines_sliderLineComponent__WEBPACK_IMPORTED_MODULE_7__["SliderLineComponent"], { label: "Visibility", target: mesh, propertyName: "visibility", minimum: 0, maximum: 1, step: 0.01, onPropertyChangedObservable: this.props.onPropertyChangedObservable }),
-                react__WEBPACK_IMPORTED_MODULE_1__["createElement"](_lines_floatLineComponent__WEBPACK_IMPORTED_MODULE_10__["FloatLineComponent"], { lockObject: this.props.lockObject, label: "Alpha index", target: mesh, propertyName: "alphaIndex", onPropertyChangedObservable: this.props.onPropertyChangedObservable }),
+                react__WEBPACK_IMPORTED_MODULE_1__["createElement"](_lines_floatLineComponent__WEBPACK_IMPORTED_MODULE_9__["FloatLineComponent"], { lockObject: this.props.lockObject, label: "Alpha index", target: mesh, propertyName: "alphaIndex", onPropertyChangedObservable: this.props.onPropertyChangedObservable }),
                 react__WEBPACK_IMPORTED_MODULE_1__["createElement"](_lines_checkBoxLineComponent__WEBPACK_IMPORTED_MODULE_5__["CheckBoxLineComponent"], { label: "Receive shadows", target: mesh, propertyName: "receiveShadows", onPropertyChangedObservable: this.props.onPropertyChangedObservable }),
                 mesh.isVerticesDataPresent(babylonjs_Misc_tools__WEBPACK_IMPORTED_MODULE_2__["VertexBuffer"].ColorKind) &&
                     react__WEBPACK_IMPORTED_MODULE_1__["createElement"](_lines_checkBoxLineComponent__WEBPACK_IMPORTED_MODULE_5__["CheckBoxLineComponent"], { label: "Use vertex colors", target: mesh, propertyName: "useVertexColors", onPropertyChangedObservable: this.props.onPropertyChangedObservable }),
@@ -38235,16 +38159,15 @@ var MeshPropertyGridComponent = /** @class */ (function (_super) {
                 react__WEBPACK_IMPORTED_MODULE_1__["createElement"](_lines_textLineComponent__WEBPACK_IMPORTED_MODULE_4__["TextLineComponent"], { label: "has matrix indices", value: mesh.isVerticesDataPresent(babylonjs_Misc_tools__WEBPACK_IMPORTED_MODULE_2__["VertexBuffer"].MatricesIndicesKind) ? "Yes" : "No" })),
             mesh.physicsImpostor != null &&
                 react__WEBPACK_IMPORTED_MODULE_1__["createElement"](_lineContainerComponent__WEBPACK_IMPORTED_MODULE_3__["LineContainerComponent"], { globalState: this.props.globalState, title: "PHYSICS", closed: true },
-                    react__WEBPACK_IMPORTED_MODULE_1__["createElement"](_lines_floatLineComponent__WEBPACK_IMPORTED_MODULE_10__["FloatLineComponent"], { lockObject: this.props.lockObject, label: "Mass", target: mesh.physicsImpostor, propertyName: "mass", onPropertyChangedObservable: this.props.onPropertyChangedObservable }),
-                    react__WEBPACK_IMPORTED_MODULE_1__["createElement"](_lines_floatLineComponent__WEBPACK_IMPORTED_MODULE_10__["FloatLineComponent"], { lockObject: this.props.lockObject, label: "Friction", target: mesh.physicsImpostor, propertyName: "friction", onPropertyChangedObservable: this.props.onPropertyChangedObservable }),
-                    react__WEBPACK_IMPORTED_MODULE_1__["createElement"](_lines_floatLineComponent__WEBPACK_IMPORTED_MODULE_10__["FloatLineComponent"], { lockObject: this.props.lockObject, label: "Restitution", target: mesh.physicsImpostor, propertyName: "restitution", onPropertyChangedObservable: this.props.onPropertyChangedObservable }),
+                    react__WEBPACK_IMPORTED_MODULE_1__["createElement"](_lines_floatLineComponent__WEBPACK_IMPORTED_MODULE_9__["FloatLineComponent"], { lockObject: this.props.lockObject, label: "Mass", target: mesh.physicsImpostor, propertyName: "mass", onPropertyChangedObservable: this.props.onPropertyChangedObservable }),
+                    react__WEBPACK_IMPORTED_MODULE_1__["createElement"](_lines_floatLineComponent__WEBPACK_IMPORTED_MODULE_9__["FloatLineComponent"], { lockObject: this.props.lockObject, label: "Friction", target: mesh.physicsImpostor, propertyName: "friction", onPropertyChangedObservable: this.props.onPropertyChangedObservable }),
+                    react__WEBPACK_IMPORTED_MODULE_1__["createElement"](_lines_floatLineComponent__WEBPACK_IMPORTED_MODULE_9__["FloatLineComponent"], { lockObject: this.props.lockObject, label: "Restitution", target: mesh.physicsImpostor, propertyName: "restitution", onPropertyChangedObservable: this.props.onPropertyChangedObservable }),
                     react__WEBPACK_IMPORTED_MODULE_1__["createElement"](_lines_textLineComponent__WEBPACK_IMPORTED_MODULE_4__["TextLineComponent"], { label: "Type", value: this.convertPhysicsTypeToString() })),
             react__WEBPACK_IMPORTED_MODULE_1__["createElement"](_lineContainerComponent__WEBPACK_IMPORTED_MODULE_3__["LineContainerComponent"], { globalState: this.props.globalState, title: "DEBUG", closed: true },
                 mesh.material &&
                     react__WEBPACK_IMPORTED_MODULE_1__["createElement"](_lines_checkBoxLineComponent__WEBPACK_IMPORTED_MODULE_5__["CheckBoxLineComponent"], { label: "Display normals", isSelected: function () { return displayNormals; }, onSelect: function () { return _this.displayNormals(); } }),
                 mesh.isVerticesDataPresent(babylonjs_Misc_tools__WEBPACK_IMPORTED_MODULE_2__["VertexBuffer"].NormalKind) &&
                     react__WEBPACK_IMPORTED_MODULE_1__["createElement"](_lines_checkBoxLineComponent__WEBPACK_IMPORTED_MODULE_5__["CheckBoxLineComponent"], { label: "Render vertex normals", isSelected: function () { return renderNormalVectors; }, onSelect: function () { return _this.renderNormalVectors(); } }),
-                react__WEBPACK_IMPORTED_MODULE_1__["createElement"](_axesViewerComponent__WEBPACK_IMPORTED_MODULE_9__["AxesViewerComponent"], { globalState: this.props.globalState, node: mesh }),
                 react__WEBPACK_IMPORTED_MODULE_1__["createElement"](_lines_checkBoxLineComponent__WEBPACK_IMPORTED_MODULE_5__["CheckBoxLineComponent"], { label: "Render wireframe over mesh", isSelected: function () { return renderWireframeOver; }, onSelect: function () { return _this.renderWireframeOver(); } }))));
     };
     return MeshPropertyGridComponent;
@@ -38385,9 +38308,7 @@ __webpack_require__.r(__webpack_exports__);
 /* harmony import */ var _lines_vector3LineComponent__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ../../../lines/vector3LineComponent */ "./components/actionTabs/lines/vector3LineComponent.tsx");
 /* harmony import */ var _lines_textLineComponent__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ../../../lines/textLineComponent */ "./components/actionTabs/lines/textLineComponent.tsx");
 /* harmony import */ var _lines_quaternionLineComponent__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! ../../../lines/quaternionLineComponent */ "./components/actionTabs/lines/quaternionLineComponent.tsx");
-/* harmony import */ var _axesViewerComponent__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! ./axesViewerComponent */ "./components/actionTabs/tabs/propertyGrids/meshes/axesViewerComponent.tsx");
-/* harmony import */ var _customPropertyGridComponent__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(/*! ../customPropertyGridComponent */ "./components/actionTabs/tabs/propertyGrids/customPropertyGridComponent.tsx");
-
+/* harmony import */ var _customPropertyGridComponent__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! ../customPropertyGridComponent */ "./components/actionTabs/tabs/propertyGrids/customPropertyGridComponent.tsx");
 
 
 
@@ -38404,7 +38325,7 @@ var TransformNodePropertyGridComponent = /** @class */ (function (_super) {
     TransformNodePropertyGridComponent.prototype.render = function () {
         var transformNode = this.props.transformNode;
         return (react__WEBPACK_IMPORTED_MODULE_1__["createElement"]("div", { className: "pane" },
-            react__WEBPACK_IMPORTED_MODULE_1__["createElement"](_customPropertyGridComponent__WEBPACK_IMPORTED_MODULE_8__["CustomPropertyGridComponent"], { globalState: this.props.globalState, target: transformNode, onPropertyChangedObservable: this.props.onPropertyChangedObservable }),
+            react__WEBPACK_IMPORTED_MODULE_1__["createElement"](_customPropertyGridComponent__WEBPACK_IMPORTED_MODULE_7__["CustomPropertyGridComponent"], { globalState: this.props.globalState, target: transformNode, onPropertyChangedObservable: this.props.onPropertyChangedObservable }),
             react__WEBPACK_IMPORTED_MODULE_1__["createElement"](_lineContainerComponent__WEBPACK_IMPORTED_MODULE_2__["LineContainerComponent"], { globalState: this.props.globalState, title: "GENERAL" },
                 react__WEBPACK_IMPORTED_MODULE_1__["createElement"](_lines_textLineComponent__WEBPACK_IMPORTED_MODULE_5__["TextLineComponent"], { label: "ID", value: transformNode.id }),
                 react__WEBPACK_IMPORTED_MODULE_1__["createElement"](_lines_textLineComponent__WEBPACK_IMPORTED_MODULE_5__["TextLineComponent"], { label: "Unique ID", value: transformNode.uniqueId.toString() }),
@@ -38413,12 +38334,10 @@ var TransformNodePropertyGridComponent = /** @class */ (function (_super) {
             react__WEBPACK_IMPORTED_MODULE_1__["createElement"](_lineContainerComponent__WEBPACK_IMPORTED_MODULE_2__["LineContainerComponent"], { globalState: this.props.globalState, title: "TRANSFORMATIONS" },
                 react__WEBPACK_IMPORTED_MODULE_1__["createElement"](_lines_vector3LineComponent__WEBPACK_IMPORTED_MODULE_4__["Vector3LineComponent"], { label: "Position", target: transformNode, propertyName: "position", onPropertyChangedObservable: this.props.onPropertyChangedObservable }),
                 !transformNode.rotationQuaternion &&
-                    react__WEBPACK_IMPORTED_MODULE_1__["createElement"](_lines_vector3LineComponent__WEBPACK_IMPORTED_MODULE_4__["Vector3LineComponent"], { label: "Rotation", target: transformNode, propertyName: "rotation", onPropertyChangedObservable: this.props.onPropertyChangedObservable }),
+                    react__WEBPACK_IMPORTED_MODULE_1__["createElement"](_lines_vector3LineComponent__WEBPACK_IMPORTED_MODULE_4__["Vector3LineComponent"], { label: "Rotation", target: transformNode, propertyName: "rotation", step: 0.01, onPropertyChangedObservable: this.props.onPropertyChangedObservable }),
                 transformNode.rotationQuaternion &&
                     react__WEBPACK_IMPORTED_MODULE_1__["createElement"](_lines_quaternionLineComponent__WEBPACK_IMPORTED_MODULE_6__["QuaternionLineComponent"], { label: "Rotation", target: transformNode, propertyName: "rotationQuaternion", onPropertyChangedObservable: this.props.onPropertyChangedObservable }),
-                react__WEBPACK_IMPORTED_MODULE_1__["createElement"](_lines_vector3LineComponent__WEBPACK_IMPORTED_MODULE_4__["Vector3LineComponent"], { label: "Scaling", target: transformNode, propertyName: "scaling", onPropertyChangedObservable: this.props.onPropertyChangedObservable })),
-            react__WEBPACK_IMPORTED_MODULE_1__["createElement"](_lineContainerComponent__WEBPACK_IMPORTED_MODULE_2__["LineContainerComponent"], { globalState: this.props.globalState, title: "DEBUG", closed: true },
-                react__WEBPACK_IMPORTED_MODULE_1__["createElement"](_axesViewerComponent__WEBPACK_IMPORTED_MODULE_7__["AxesViewerComponent"], { globalState: this.props.globalState, node: transformNode }))));
+                react__WEBPACK_IMPORTED_MODULE_1__["createElement"](_lines_vector3LineComponent__WEBPACK_IMPORTED_MODULE_4__["Vector3LineComponent"], { label: "Scaling", target: transformNode, propertyName: "scaling", onPropertyChangedObservable: this.props.onPropertyChangedObservable }))));
     };
     return TransformNodePropertyGridComponent;
 }(react__WEBPACK_IMPORTED_MODULE_1__["Component"]));
@@ -39188,7 +39107,6 @@ var StatisticsTabComponent = /** @class */ (function (_super) {
                 react__WEBPACK_IMPORTED_MODULE_1__["createElement"](_lines_textLineComponent__WEBPACK_IMPORTED_MODULE_3__["TextLineComponent"], { label: "Active bones", value: scene.getActiveBones().toString() }),
                 react__WEBPACK_IMPORTED_MODULE_1__["createElement"](_lines_textLineComponent__WEBPACK_IMPORTED_MODULE_3__["TextLineComponent"], { label: "Active particles", value: scene.getActiveParticles().toString() }),
                 react__WEBPACK_IMPORTED_MODULE_1__["createElement"](_lines_textLineComponent__WEBPACK_IMPORTED_MODULE_3__["TextLineComponent"], { label: "Draw calls", value: sceneInstrumentation.drawCallsCounter.current.toString() }),
-                react__WEBPACK_IMPORTED_MODULE_1__["createElement"](_lines_textLineComponent__WEBPACK_IMPORTED_MODULE_3__["TextLineComponent"], { label: "Texture collisions", value: sceneInstrumentation.textureCollisionsCounter.current.toString() }),
                 react__WEBPACK_IMPORTED_MODULE_1__["createElement"](_lines_textLineComponent__WEBPACK_IMPORTED_MODULE_3__["TextLineComponent"], { label: "Total lights", value: scene.lights.length.toString() }),
                 react__WEBPACK_IMPORTED_MODULE_1__["createElement"](_lines_textLineComponent__WEBPACK_IMPORTED_MODULE_3__["TextLineComponent"], { label: "Total vertices", value: scene.getTotalVertices().toString() }),
                 react__WEBPACK_IMPORTED_MODULE_1__["createElement"](_lines_textLineComponent__WEBPACK_IMPORTED_MODULE_3__["TextLineComponent"], { label: "Total materials", value: scene.materials.length.toString() }),
@@ -40673,6 +40591,9 @@ var SceneTreeItemComponent = /** @class */ (function (_super) {
                     break;
                 case 4:
                     manager.boundingBoxGizmoEnabled = true;
+                    if (manager.gizmos.boundingBoxGizmo) {
+                        manager.gizmos.boundingBoxGizmo.fixedDragMeshScreenSize = true;
+                    }
                     break;
             }
             if (this._selectedEntity && this._selectedEntity.getClassName) {
@@ -40985,6 +40906,7 @@ var SceneExplorerComponent = /** @class */ (function (_super) {
     function SceneExplorerComponent(props) {
         var _this = _super.call(this, props) || this;
         _this._once = true;
+        _this._hooked = false;
         _this.state = { filter: null, selectedEntity: null, scene: _this.props.scene };
         _this.sceneMutationFunc = _this.processMutation.bind(_this);
         return _this;
@@ -41011,12 +40933,14 @@ var SceneExplorerComponent = /** @class */ (function (_super) {
             babylonjs_Engines_engineStore__WEBPACK_IMPORTED_MODULE_2__["EngineStore"].LastCreatedEngine.onNewSceneAddedObservable.remove(this._onNewSceneAddedObserver);
         }
         var scene = this.state.scene;
+        scene.onNewSkeletonAddedObservable.removeCallback(this.sceneMutationFunc);
         scene.onNewCameraAddedObservable.removeCallback(this.sceneMutationFunc);
         scene.onNewLightAddedObservable.removeCallback(this.sceneMutationFunc);
         scene.onNewMaterialAddedObservable.removeCallback(this.sceneMutationFunc);
         scene.onNewMeshAddedObservable.removeCallback(this.sceneMutationFunc);
         scene.onNewTextureAddedObservable.removeCallback(this.sceneMutationFunc);
         scene.onNewTransformNodeAddedObservable.removeCallback(this.sceneMutationFunc);
+        scene.onSkeletonRemovedObservable.removeCallback(this.sceneMutationFunc);
         scene.onMeshRemovedObservable.removeCallback(this.sceneMutationFunc);
         scene.onCameraRemovedObservable.removeCallback(this.sceneMutationFunc);
         scene.onLightRemovedObservable.removeCallback(this.sceneMutationFunc);
@@ -41109,6 +41033,23 @@ var SceneExplorerComponent = /** @class */ (function (_super) {
             this._onNewSceneAddedObserver = babylonjs_Engines_engineStore__WEBPACK_IMPORTED_MODULE_2__["EngineStore"].LastCreatedEngine.onNewSceneAddedObservable.addOnce(function (scene) { return _this.setState({ scene: scene }); });
             return null;
         }
+        if (!this._hooked) {
+            this._hooked = true;
+            scene.onNewSkeletonAddedObservable.add(this.sceneMutationFunc);
+            scene.onNewCameraAddedObservable.add(this.sceneMutationFunc);
+            scene.onNewLightAddedObservable.add(this.sceneMutationFunc);
+            scene.onNewMaterialAddedObservable.add(this.sceneMutationFunc);
+            scene.onNewMeshAddedObservable.add(this.sceneMutationFunc);
+            scene.onNewTextureAddedObservable.add(this.sceneMutationFunc);
+            scene.onNewTransformNodeAddedObservable.add(this.sceneMutationFunc);
+            scene.onSkeletonRemovedObservable.add(this.sceneMutationFunc);
+            scene.onMeshRemovedObservable.add(this.sceneMutationFunc);
+            scene.onCameraRemovedObservable.add(this.sceneMutationFunc);
+            scene.onLightRemovedObservable.add(this.sceneMutationFunc);
+            scene.onMaterialRemovedObservable.add(this.sceneMutationFunc);
+            scene.onTransformNodeRemovedObservable.add(this.sceneMutationFunc);
+            scene.onTextureRemovedObservable.add(this.sceneMutationFunc);
+        }
         var guiElements = scene.textures.filter(function (t) { return t.getClassName() === "AdvancedDynamicTexture"; });
         var textures = scene.textures.filter(function (t) { return t.getClassName() !== "AdvancedDynamicTexture"; });
         var postProcessses = scene.postProcesses;
@@ -41163,19 +41104,6 @@ var SceneExplorerComponent = /** @class */ (function (_super) {
         }
         if (this._once) {
             this._once = false;
-            var scene = this.state.scene;
-            scene.onNewCameraAddedObservable.add(this.sceneMutationFunc);
-            scene.onNewLightAddedObservable.add(this.sceneMutationFunc);
-            scene.onNewMaterialAddedObservable.add(this.sceneMutationFunc);
-            scene.onNewMeshAddedObservable.add(this.sceneMutationFunc);
-            scene.onNewTextureAddedObservable.add(this.sceneMutationFunc);
-            scene.onNewTransformNodeAddedObservable.add(this.sceneMutationFunc);
-            scene.onMeshRemovedObservable.add(this.sceneMutationFunc);
-            scene.onCameraRemovedObservable.add(this.sceneMutationFunc);
-            scene.onLightRemovedObservable.add(this.sceneMutationFunc);
-            scene.onMaterialRemovedObservable.add(this.sceneMutationFunc);
-            scene.onTransformNodeRemovedObservable.add(this.sceneMutationFunc);
-            scene.onTextureRemovedObservable.add(this.sceneMutationFunc);
             // A bit hacky but no other way to force the initial width to 300px and not auto
             setTimeout(function () {
                 var element = document.getElementById("sceneExplorer");

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


+ 10 - 16
dist/preview release/inspector/babylon.inspector.d.ts

@@ -259,11 +259,15 @@ declare module INSPECTOR {
     interface INumericInputComponentProps {
         label: string;
         value: number;
+        step?: number;
         onChange: (value: number) => void;
     }
     export class NumericInputComponent extends React.Component<INumericInputComponentProps, {
         value: string;
     }> {
+        static defaultProps: {
+            step: number;
+        };
         private _localChange;
         constructor(props: INumericInputComponentProps);
         shouldComponentUpdate(nextProps: INumericInputComponentProps, nextState: {
@@ -304,6 +308,7 @@ declare module INSPECTOR {
         label: string;
         target: any;
         propertyName: string;
+        step?: number;
         onChange?: (newvalue: BABYLON.Vector3) => void;
         onPropertyChangedObservable?: BABYLON.Observable<PropertyChangedEvent>;
     }
@@ -311,6 +316,9 @@ declare module INSPECTOR {
         isExpanded: boolean;
         value: BABYLON.Vector3;
     }> {
+        static defaultProps: {
+            step: number;
+        };
         private _localChange;
         constructor(props: IVector3LineComponentProps);
         shouldComponentUpdate(nextProps: IVector3LineComponentProps, nextState: {
@@ -319,6 +327,7 @@ declare module INSPECTOR {
         }): boolean;
         switchExpandState(): void;
         raiseOnPropertyChanged(previousValue: BABYLON.Vector3): void;
+        updateVector3(): void;
         updateStateX(value: number): void;
         updateStateY(value: number): void;
         updateStateZ(value: number): void;
@@ -688,22 +697,6 @@ declare module INSPECTOR {
     }
 }
 declare module INSPECTOR {
-    interface IAxisViewerComponentProps {
-        node: BABYLON.TransformNode;
-        globalState: GlobalState;
-    }
-    export class AxesViewerComponent extends React.Component<IAxisViewerComponentProps, {
-        displayAxis: boolean;
-    }> {
-        constructor(props: IAxisViewerComponentProps);
-        shouldComponentUpdate(nextProps: IAxisViewerComponentProps, nextState: {
-            displayAxis: boolean;
-        }): boolean;
-        displayAxes(): void;
-        render(): JSX.Element;
-    }
-}
-declare module INSPECTOR {
     interface IMeshPropertyGridComponentProps {
         globalState: GlobalState;
         mesh: BABYLON.Mesh;
@@ -1654,6 +1647,7 @@ declare module INSPECTOR {
         private _onSelectionChangeObserver;
         private _onNewSceneAddedObserver;
         private _once;
+        private _hooked;
         private sceneMutationFunc;
         constructor(props: ISceneExplorerComponentProps);
         processMutation(): void;

+ 20 - 35
dist/preview release/inspector/babylon.inspector.module.d.ts

@@ -297,11 +297,15 @@ declare module "babylonjs-inspector/components/actionTabs/lines/numericInputComp
     interface INumericInputComponentProps {
         label: string;
         value: number;
+        step?: number;
         onChange: (value: number) => void;
     }
     export class NumericInputComponent extends React.Component<INumericInputComponentProps, {
         value: string;
     }> {
+        static defaultProps: {
+            step: number;
+        };
         private _localChange;
         constructor(props: INumericInputComponentProps);
         shouldComponentUpdate(nextProps: INumericInputComponentProps, nextState: {
@@ -350,6 +354,7 @@ declare module "babylonjs-inspector/components/actionTabs/lines/vector3LineCompo
         label: string;
         target: any;
         propertyName: string;
+        step?: number;
         onChange?: (newvalue: Vector3) => void;
         onPropertyChangedObservable?: Observable<PropertyChangedEvent>;
     }
@@ -357,6 +362,9 @@ declare module "babylonjs-inspector/components/actionTabs/lines/vector3LineCompo
         isExpanded: boolean;
         value: Vector3;
     }> {
+        static defaultProps: {
+            step: number;
+        };
         private _localChange;
         constructor(props: IVector3LineComponentProps);
         shouldComponentUpdate(nextProps: IVector3LineComponentProps, nextState: {
@@ -365,6 +373,7 @@ declare module "babylonjs-inspector/components/actionTabs/lines/vector3LineCompo
         }): boolean;
         switchExpandState(): void;
         raiseOnPropertyChanged(previousValue: Vector3): void;
+        updateVector3(): void;
         updateStateX(value: number): void;
         updateStateY(value: number): void;
         updateStateZ(value: number): void;
@@ -847,25 +856,6 @@ declare module "babylonjs-inspector/components/actionTabs/tabs/propertyGrids/cam
         render(): JSX.Element;
     }
 }
-declare module "babylonjs-inspector/components/actionTabs/tabs/propertyGrids/meshes/axesViewerComponent" {
-    import * as React from "react";
-    import { TransformNode } from "babylonjs/Meshes/transformNode";
-    import { GlobalState } from "babylonjs-inspector/components/globalState";
-    interface IAxisViewerComponentProps {
-        node: TransformNode;
-        globalState: GlobalState;
-    }
-    export class AxesViewerComponent extends React.Component<IAxisViewerComponentProps, {
-        displayAxis: boolean;
-    }> {
-        constructor(props: IAxisViewerComponentProps);
-        shouldComponentUpdate(nextProps: IAxisViewerComponentProps, nextState: {
-            displayAxis: boolean;
-        }): boolean;
-        displayAxes(): void;
-        render(): JSX.Element;
-    }
-}
 declare module "babylonjs-inspector/components/actionTabs/tabs/propertyGrids/meshes/meshPropertyGridComponent" {
     import * as React from "react";
     import { Observable } from "babylonjs/Misc/observable";
@@ -2111,6 +2101,7 @@ declare module "babylonjs-inspector/components/sceneExplorer/sceneExplorerCompon
         private _onSelectionChangeObserver;
         private _onNewSceneAddedObserver;
         private _once;
+        private _hooked;
         private sceneMutationFunc;
         constructor(props: ISceneExplorerComponentProps);
         processMutation(): void;
@@ -2454,11 +2445,15 @@ declare module INSPECTOR {
     interface INumericInputComponentProps {
         label: string;
         value: number;
+        step?: number;
         onChange: (value: number) => void;
     }
     export class NumericInputComponent extends React.Component<INumericInputComponentProps, {
         value: string;
     }> {
+        static defaultProps: {
+            step: number;
+        };
         private _localChange;
         constructor(props: INumericInputComponentProps);
         shouldComponentUpdate(nextProps: INumericInputComponentProps, nextState: {
@@ -2499,6 +2494,7 @@ declare module INSPECTOR {
         label: string;
         target: any;
         propertyName: string;
+        step?: number;
         onChange?: (newvalue: BABYLON.Vector3) => void;
         onPropertyChangedObservable?: BABYLON.Observable<PropertyChangedEvent>;
     }
@@ -2506,6 +2502,9 @@ declare module INSPECTOR {
         isExpanded: boolean;
         value: BABYLON.Vector3;
     }> {
+        static defaultProps: {
+            step: number;
+        };
         private _localChange;
         constructor(props: IVector3LineComponentProps);
         shouldComponentUpdate(nextProps: IVector3LineComponentProps, nextState: {
@@ -2514,6 +2513,7 @@ declare module INSPECTOR {
         }): boolean;
         switchExpandState(): void;
         raiseOnPropertyChanged(previousValue: BABYLON.Vector3): void;
+        updateVector3(): void;
         updateStateX(value: number): void;
         updateStateY(value: number): void;
         updateStateZ(value: number): void;
@@ -2883,22 +2883,6 @@ declare module INSPECTOR {
     }
 }
 declare module INSPECTOR {
-    interface IAxisViewerComponentProps {
-        node: BABYLON.TransformNode;
-        globalState: GlobalState;
-    }
-    export class AxesViewerComponent extends React.Component<IAxisViewerComponentProps, {
-        displayAxis: boolean;
-    }> {
-        constructor(props: IAxisViewerComponentProps);
-        shouldComponentUpdate(nextProps: IAxisViewerComponentProps, nextState: {
-            displayAxis: boolean;
-        }): boolean;
-        displayAxes(): void;
-        render(): JSX.Element;
-    }
-}
-declare module INSPECTOR {
     interface IMeshPropertyGridComponentProps {
         globalState: GlobalState;
         mesh: BABYLON.Mesh;
@@ -3849,6 +3833,7 @@ declare module INSPECTOR {
         private _onSelectionChangeObserver;
         private _onNewSceneAddedObserver;
         private _once;
+        private _hooked;
         private sceneMutationFunc;
         constructor(props: ISceneExplorerComponentProps);
         processMutation(): void;

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

@@ -4,7 +4,7 @@
     },
     "name": "babylonjs-inspector",
     "description": "The Babylon.js inspector.",
-    "version": "4.0.0-beta.3",
+    "version": "4.0.0-beta.4",
     "repository": {
         "type": "git",
         "url": "https://github.com/BabylonJS/Babylon.js.git"
@@ -31,11 +31,11 @@
     "dependencies": {
         "@types/react": "~16.7.3",
         "@types/react-dom": "~16.0.9",
-        "babylonjs": "4.0.0-beta.3",
-        "babylonjs-gui": "4.0.0-beta.3",
-        "babylonjs-loaders": "4.0.0-beta.3",
-        "babylonjs-serializers": "4.0.0-beta.3",
-        "babylonjs-gltf2interface": "4.0.0-beta.3"
+        "babylonjs": "4.0.0-beta.4",
+        "babylonjs-gui": "4.0.0-beta.4",
+        "babylonjs-loaders": "4.0.0-beta.4",
+        "babylonjs-serializers": "4.0.0-beta.4",
+        "babylonjs-gltf2interface": "4.0.0-beta.4"
     },
     "engines": {
         "node": "*"

+ 57 - 9
dist/preview release/loaders/babylon.glTF2FileLoader.js

@@ -277,13 +277,14 @@ var KHR_draco_mesh_compression = /** @class */ (function () {
         this.name = NAME;
         /** Defines whether this extension is enabled. */
         this.enabled = babylonjs_Meshes_Compression_dracoCompression__WEBPACK_IMPORTED_MODULE_0__["DracoCompression"].DecoderAvailable;
+        this._dracoCompressionOwned = false;
         this._loader = loader;
     }
     /** @hidden */
     KHR_draco_mesh_compression.prototype.dispose = function () {
-        if (this._dracoCompression) {
-            this._dracoCompression.dispose();
-            delete this._dracoCompression;
+        if (this.dracoCompression && this._dracoCompressionOwned) {
+            this.dracoCompression.dispose();
+            delete this.dracoCompression;
         }
         delete this._loader;
     };
@@ -324,10 +325,11 @@ var KHR_draco_mesh_compression = /** @class */ (function () {
             var bufferView = _glTFLoader__WEBPACK_IMPORTED_MODULE_1__["ArrayItem"].Get(extensionContext, _this._loader.gltf.bufferViews, extension.bufferView);
             if (!bufferView._dracoBabylonGeometry) {
                 bufferView._dracoBabylonGeometry = _this._loader.loadBufferViewAsync("#/bufferViews/" + bufferView.index, bufferView).then(function (data) {
-                    if (!_this._dracoCompression) {
-                        _this._dracoCompression = new babylonjs_Meshes_Compression_dracoCompression__WEBPACK_IMPORTED_MODULE_0__["DracoCompression"]();
+                    if (!_this.dracoCompression) {
+                        _this.dracoCompression = new babylonjs_Meshes_Compression_dracoCompression__WEBPACK_IMPORTED_MODULE_0__["DracoCompression"]();
+                        _this._dracoCompressionOwned = true;
                     }
-                    return _this._dracoCompression.decodeMeshAsync(data, attributes).then(function (babylonVertexData) {
+                    return _this.dracoCompression.decodeMeshAsync(data, attributes).then(function (babylonVertexData) {
                         var babylonGeometry = new babylonjs_Meshes_Compression_dracoCompression__WEBPACK_IMPORTED_MODULE_0__["Geometry"](babylonMesh.name, _this._loader.babylonScene);
                         babylonVertexData.applyToGeometry(babylonGeometry);
                         return babylonGeometry;
@@ -1570,6 +1572,9 @@ var GLTFLoader = /** @class */ (function () {
             _this._setState(_glTFFileLoader__WEBPACK_IMPORTED_MODULE_1__["GLTFLoaderState"].LOADING);
             _this._extensionsOnLoading();
             var promises = new Array();
+            // Block the marking of materials dirty until the scene is loaded.
+            var oldBlockMaterialDirtyMechanism = _this._babylonScene.blockMaterialDirtyMechanism;
+            _this._babylonScene.blockMaterialDirtyMechanism = true;
             if (nodes) {
                 promises.push(_this.loadSceneAsync("/nodes", { nodes: nodes, index: -1 }));
             }
@@ -1577,6 +1582,8 @@ var GLTFLoader = /** @class */ (function () {
                 var scene = ArrayItem.Get("/scene", _this._gltf.scenes, _this._gltf.scene || 0);
                 promises.push(_this.loadSceneAsync("/scenes/" + scene.index, scene));
             }
+            // Restore the blocking of material dirty.
+            _this._babylonScene.blockMaterialDirtyMechanism = oldBlockMaterialDirtyMechanism;
             if (_this._parent.compileMaterials) {
                 promises.push(_this._compileMaterialsAsync());
             }
@@ -1932,8 +1939,22 @@ var GLTFLoader = /** @class */ (function () {
             return node._babylonTransformNode;
         });
     };
+    /**
+     * @hidden Define this method to modify the default behavior when loading data for mesh primitives.
+     * @param context The context when loading the asset
+     * @param name The mesh name when loading the asset
+     * @param node The glTF node when loading the asset
+     * @param mesh The glTF mesh when loading the asset
+     * @param primitive The glTF mesh primitive property
+     * @param assign A function called synchronously after parsing the glTF properties
+     * @returns A promise that resolves with the loaded mesh when the load is complete or null if not handled
+     */
     GLTFLoader.prototype._loadMeshPrimitiveAsync = function (context, name, node, mesh, primitive, assign) {
         var _this = this;
+        var extensionPromise = this._extensionsLoadMeshPrimitiveAsync(context, name, node, mesh, primitive, assign);
+        if (extensionPromise) {
+            return extensionPromise;
+        }
         this.logOpen("" + context);
         var canInstance = (node.skin == undefined && !mesh.primitives[0].targets);
         var babylonAbstractMesh;
@@ -2139,6 +2160,10 @@ var GLTFLoader = /** @class */ (function () {
     };
     GLTFLoader.prototype._loadSkinAsync = function (context, node, skin) {
         var _this = this;
+        var extensionPromise = this._extensionsLoadSkinAsync(context, node, skin);
+        if (extensionPromise) {
+            return extensionPromise;
+        }
         var assignSkeleton = function (skeleton) {
             _this._forEachPrimitive(node, function (babylonMesh) {
                 babylonMesh.skeleton = skeleton;
@@ -2305,8 +2330,19 @@ var GLTFLoader = /** @class */ (function () {
             return babylonAnimationGroup;
         });
     };
-    GLTFLoader.prototype._loadAnimationChannelAsync = function (context, animationContext, animation, channel, babylonAnimationGroup) {
+    /**
+     * @hidden Loads a glTF animation channel.
+     * @param context The context when loading the asset
+     * @param animationContext The context of the animation when loading the asset
+     * @param animation The glTF animation property
+     * @param channel The glTF animation channel property
+     * @param babylonAnimationGroup The babylon animation group property
+     * @param animationTargetOverride The babylon animation channel target override property. My be null.
+     * @returns A void promise when the channel load is complete
+     */
+    GLTFLoader.prototype._loadAnimationChannelAsync = function (context, animationContext, animation, channel, babylonAnimationGroup, animationTargetOverride) {
         var _this = this;
+        if (animationTargetOverride === void 0) { animationTargetOverride = null; }
         if (channel.target.node == undefined) {
             return Promise.resolve();
         }
@@ -2440,8 +2476,14 @@ var GLTFLoader = /** @class */ (function () {
                 var animationName = babylonAnimationGroup.name + "_channel" + babylonAnimationGroup.targetedAnimations.length;
                 var babylonAnimation = new babylonjs_Misc_deferred__WEBPACK_IMPORTED_MODULE_0__["Animation"](animationName, targetPath, 1, animationType);
                 babylonAnimation.setKeys(keys);
-                targetNode._babylonTransformNode.animations.push(babylonAnimation);
-                babylonAnimationGroup.addTargetedAnimation(babylonAnimation, targetNode._babylonTransformNode);
+                if (animationTargetOverride != null && animationTargetOverride.animations != null) {
+                    animationTargetOverride.animations.push(babylonAnimation);
+                    babylonAnimationGroup.addTargetedAnimation(babylonAnimation, animationTargetOverride);
+                }
+                else {
+                    targetNode._babylonTransformNode.animations.push(babylonAnimation);
+                    babylonAnimationGroup.addTargetedAnimation(babylonAnimation, targetNode._babylonTransformNode);
+                }
             }
         });
     };
@@ -3182,6 +3224,9 @@ var GLTFLoader = /** @class */ (function () {
     GLTFLoader.prototype._extensionsLoadVertexDataAsync = function (context, primitive, babylonMesh) {
         return this._applyExtensions(primitive, "loadVertexData", function (extension) { return extension._loadVertexDataAsync && extension._loadVertexDataAsync(context, primitive, babylonMesh); });
     };
+    GLTFLoader.prototype._extensionsLoadMeshPrimitiveAsync = function (context, name, node, mesh, primitive, assign) {
+        return this._applyExtensions(primitive, "loadMeshPrimitive", function (extension) { return extension._loadMeshPrimitiveAsync && extension._loadMeshPrimitiveAsync(context, name, node, mesh, primitive, assign); });
+    };
     GLTFLoader.prototype._extensionsLoadMaterialAsync = function (context, material, babylonMesh, babylonDrawMode, assign) {
         return this._applyExtensions(material, "loadMaterial", function (extension) { return extension._loadMaterialAsync && extension._loadMaterialAsync(context, material, babylonMesh, babylonDrawMode, assign); });
     };
@@ -3197,6 +3242,9 @@ var GLTFLoader = /** @class */ (function () {
     GLTFLoader.prototype._extensionsLoadAnimationAsync = function (context, animation) {
         return this._applyExtensions(animation, "loadAnimation", function (extension) { return extension.loadAnimationAsync && extension.loadAnimationAsync(context, animation); });
     };
+    GLTFLoader.prototype._extensionsLoadSkinAsync = function (context, node, skin) {
+        return this._applyExtensions(skin, "loadSkin", function (extension) { return extension._loadSkinAsync && extension._loadSkinAsync(context, node, skin); });
+    };
     GLTFLoader.prototype._extensionsLoadUriAsync = function (context, property, uri) {
         return this._applyExtensions(property, "loadUri", function (extension) { return extension._loadUriAsync && extension._loadUriAsync(context, property, uri); });
     };

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


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


+ 57 - 9
dist/preview release/loaders/babylon.glTFFileLoader.js

@@ -2826,13 +2826,14 @@ var KHR_draco_mesh_compression = /** @class */ (function () {
         this.name = NAME;
         /** Defines whether this extension is enabled. */
         this.enabled = babylonjs_Meshes_Compression_dracoCompression__WEBPACK_IMPORTED_MODULE_0__["DracoCompression"].DecoderAvailable;
+        this._dracoCompressionOwned = false;
         this._loader = loader;
     }
     /** @hidden */
     KHR_draco_mesh_compression.prototype.dispose = function () {
-        if (this._dracoCompression) {
-            this._dracoCompression.dispose();
-            delete this._dracoCompression;
+        if (this.dracoCompression && this._dracoCompressionOwned) {
+            this.dracoCompression.dispose();
+            delete this.dracoCompression;
         }
         delete this._loader;
     };
@@ -2873,10 +2874,11 @@ var KHR_draco_mesh_compression = /** @class */ (function () {
             var bufferView = _glTFLoader__WEBPACK_IMPORTED_MODULE_1__["ArrayItem"].Get(extensionContext, _this._loader.gltf.bufferViews, extension.bufferView);
             if (!bufferView._dracoBabylonGeometry) {
                 bufferView._dracoBabylonGeometry = _this._loader.loadBufferViewAsync("#/bufferViews/" + bufferView.index, bufferView).then(function (data) {
-                    if (!_this._dracoCompression) {
-                        _this._dracoCompression = new babylonjs_Meshes_Compression_dracoCompression__WEBPACK_IMPORTED_MODULE_0__["DracoCompression"]();
+                    if (!_this.dracoCompression) {
+                        _this.dracoCompression = new babylonjs_Meshes_Compression_dracoCompression__WEBPACK_IMPORTED_MODULE_0__["DracoCompression"]();
+                        _this._dracoCompressionOwned = true;
                     }
-                    return _this._dracoCompression.decodeMeshAsync(data, attributes).then(function (babylonVertexData) {
+                    return _this.dracoCompression.decodeMeshAsync(data, attributes).then(function (babylonVertexData) {
                         var babylonGeometry = new babylonjs_Meshes_Compression_dracoCompression__WEBPACK_IMPORTED_MODULE_0__["Geometry"](babylonMesh.name, _this._loader.babylonScene);
                         babylonVertexData.applyToGeometry(babylonGeometry);
                         return babylonGeometry;
@@ -4119,6 +4121,9 @@ var GLTFLoader = /** @class */ (function () {
             _this._setState(_glTFFileLoader__WEBPACK_IMPORTED_MODULE_1__["GLTFLoaderState"].LOADING);
             _this._extensionsOnLoading();
             var promises = new Array();
+            // Block the marking of materials dirty until the scene is loaded.
+            var oldBlockMaterialDirtyMechanism = _this._babylonScene.blockMaterialDirtyMechanism;
+            _this._babylonScene.blockMaterialDirtyMechanism = true;
             if (nodes) {
                 promises.push(_this.loadSceneAsync("/nodes", { nodes: nodes, index: -1 }));
             }
@@ -4126,6 +4131,8 @@ var GLTFLoader = /** @class */ (function () {
                 var scene = ArrayItem.Get("/scene", _this._gltf.scenes, _this._gltf.scene || 0);
                 promises.push(_this.loadSceneAsync("/scenes/" + scene.index, scene));
             }
+            // Restore the blocking of material dirty.
+            _this._babylonScene.blockMaterialDirtyMechanism = oldBlockMaterialDirtyMechanism;
             if (_this._parent.compileMaterials) {
                 promises.push(_this._compileMaterialsAsync());
             }
@@ -4481,8 +4488,22 @@ var GLTFLoader = /** @class */ (function () {
             return node._babylonTransformNode;
         });
     };
+    /**
+     * @hidden Define this method to modify the default behavior when loading data for mesh primitives.
+     * @param context The context when loading the asset
+     * @param name The mesh name when loading the asset
+     * @param node The glTF node when loading the asset
+     * @param mesh The glTF mesh when loading the asset
+     * @param primitive The glTF mesh primitive property
+     * @param assign A function called synchronously after parsing the glTF properties
+     * @returns A promise that resolves with the loaded mesh when the load is complete or null if not handled
+     */
     GLTFLoader.prototype._loadMeshPrimitiveAsync = function (context, name, node, mesh, primitive, assign) {
         var _this = this;
+        var extensionPromise = this._extensionsLoadMeshPrimitiveAsync(context, name, node, mesh, primitive, assign);
+        if (extensionPromise) {
+            return extensionPromise;
+        }
         this.logOpen("" + context);
         var canInstance = (node.skin == undefined && !mesh.primitives[0].targets);
         var babylonAbstractMesh;
@@ -4688,6 +4709,10 @@ var GLTFLoader = /** @class */ (function () {
     };
     GLTFLoader.prototype._loadSkinAsync = function (context, node, skin) {
         var _this = this;
+        var extensionPromise = this._extensionsLoadSkinAsync(context, node, skin);
+        if (extensionPromise) {
+            return extensionPromise;
+        }
         var assignSkeleton = function (skeleton) {
             _this._forEachPrimitive(node, function (babylonMesh) {
                 babylonMesh.skeleton = skeleton;
@@ -4854,8 +4879,19 @@ var GLTFLoader = /** @class */ (function () {
             return babylonAnimationGroup;
         });
     };
-    GLTFLoader.prototype._loadAnimationChannelAsync = function (context, animationContext, animation, channel, babylonAnimationGroup) {
+    /**
+     * @hidden Loads a glTF animation channel.
+     * @param context The context when loading the asset
+     * @param animationContext The context of the animation when loading the asset
+     * @param animation The glTF animation property
+     * @param channel The glTF animation channel property
+     * @param babylonAnimationGroup The babylon animation group property
+     * @param animationTargetOverride The babylon animation channel target override property. My be null.
+     * @returns A void promise when the channel load is complete
+     */
+    GLTFLoader.prototype._loadAnimationChannelAsync = function (context, animationContext, animation, channel, babylonAnimationGroup, animationTargetOverride) {
         var _this = this;
+        if (animationTargetOverride === void 0) { animationTargetOverride = null; }
         if (channel.target.node == undefined) {
             return Promise.resolve();
         }
@@ -4989,8 +5025,14 @@ var GLTFLoader = /** @class */ (function () {
                 var animationName = babylonAnimationGroup.name + "_channel" + babylonAnimationGroup.targetedAnimations.length;
                 var babylonAnimation = new babylonjs_Misc_deferred__WEBPACK_IMPORTED_MODULE_0__["Animation"](animationName, targetPath, 1, animationType);
                 babylonAnimation.setKeys(keys);
-                targetNode._babylonTransformNode.animations.push(babylonAnimation);
-                babylonAnimationGroup.addTargetedAnimation(babylonAnimation, targetNode._babylonTransformNode);
+                if (animationTargetOverride != null && animationTargetOverride.animations != null) {
+                    animationTargetOverride.animations.push(babylonAnimation);
+                    babylonAnimationGroup.addTargetedAnimation(babylonAnimation, animationTargetOverride);
+                }
+                else {
+                    targetNode._babylonTransformNode.animations.push(babylonAnimation);
+                    babylonAnimationGroup.addTargetedAnimation(babylonAnimation, targetNode._babylonTransformNode);
+                }
             }
         });
     };
@@ -5731,6 +5773,9 @@ var GLTFLoader = /** @class */ (function () {
     GLTFLoader.prototype._extensionsLoadVertexDataAsync = function (context, primitive, babylonMesh) {
         return this._applyExtensions(primitive, "loadVertexData", function (extension) { return extension._loadVertexDataAsync && extension._loadVertexDataAsync(context, primitive, babylonMesh); });
     };
+    GLTFLoader.prototype._extensionsLoadMeshPrimitiveAsync = function (context, name, node, mesh, primitive, assign) {
+        return this._applyExtensions(primitive, "loadMeshPrimitive", function (extension) { return extension._loadMeshPrimitiveAsync && extension._loadMeshPrimitiveAsync(context, name, node, mesh, primitive, assign); });
+    };
     GLTFLoader.prototype._extensionsLoadMaterialAsync = function (context, material, babylonMesh, babylonDrawMode, assign) {
         return this._applyExtensions(material, "loadMaterial", function (extension) { return extension._loadMaterialAsync && extension._loadMaterialAsync(context, material, babylonMesh, babylonDrawMode, assign); });
     };
@@ -5746,6 +5791,9 @@ var GLTFLoader = /** @class */ (function () {
     GLTFLoader.prototype._extensionsLoadAnimationAsync = function (context, animation) {
         return this._applyExtensions(animation, "loadAnimation", function (extension) { return extension.loadAnimationAsync && extension.loadAnimationAsync(context, animation); });
     };
+    GLTFLoader.prototype._extensionsLoadSkinAsync = function (context, node, skin) {
+        return this._applyExtensions(skin, "loadSkin", function (extension) { return extension._loadSkinAsync && extension._loadSkinAsync(context, node, skin); });
+    };
     GLTFLoader.prototype._extensionsLoadUriAsync = function (context, property, uri) {
         return this._applyExtensions(property, "loadUri", function (extension) { return extension._loadUriAsync && extension._loadUriAsync(context, property, uri); });
     };

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


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


+ 47 - 4
dist/preview release/loaders/babylonjs.loaders.d.ts

@@ -1180,6 +1180,17 @@ declare module BABYLON.GLTF2 {
          */
         _loadVertexDataAsync?(context: string, primitive: IMeshPrimitive, babylonMesh: Mesh): Nullable<Promise<Geometry>>;
         /**
+         * @hidden Define this method to modify the default behavior when loading data for mesh primitives.
+         * @param context The context when loading the asset
+         * @param name The mesh name when loading the asset
+         * @param node The glTF node when loading the asset
+         * @param mesh The glTF mesh when loading the asset
+         * @param primitive The glTF mesh primitive property
+         * @param assign A function called synchronously after parsing the glTF properties
+         * @returns A promise that resolves with the loaded mesh when the load is complete or null if not handled
+         */
+        _loadMeshPrimitiveAsync?(context: string, name: string, node: INode, mesh: IMesh, primitive: IMeshPrimitive, assign: (babylonMesh: AbstractMesh) => void): Promise<AbstractMesh>;
+        /**
          * @hidden Define this method to modify the default behavior when loading materials. Load material creates the material and then loads material properties.
          * @param context The context when loading the asset
          * @param material The glTF material property
@@ -1219,7 +1230,15 @@ declare module BABYLON.GLTF2 {
          */
         loadAnimationAsync?(context: string, animation: IAnimation): Nullable<Promise<AnimationGroup>>;
         /**
-         * Define this method to modify the default behavior when loading uris.
+         * @hidden Define this method to modify the default behavior when loading skins.
+         * @param context The context when loading the asset
+         * @param node The glTF node property
+         * @param skin The glTF skin property
+         * @returns A promise that resolves when the load is complete or null if not handled
+         */
+        _loadSkinAsync?(context: string, node: INode, skin: ISkin): Nullable<Promise<void>>;
+        /**
+         * @hidden Define this method to modify the default behavior when loading uris.
          * @param context The context when loading the asset
          * @param property The glTF property associated with the uri
          * @param uri The uri to load
@@ -1338,7 +1357,17 @@ declare module BABYLON.GLTF2 {
          */
         loadNodeAsync(context: string, node: INode, assign?: (babylonTransformNode: TransformNode) => void): Promise<TransformNode>;
         private _loadMeshAsync;
-        private _loadMeshPrimitiveAsync;
+        /**
+         * @hidden Define this method to modify the default behavior when loading data for mesh primitives.
+         * @param context The context when loading the asset
+         * @param name The mesh name when loading the asset
+         * @param node The glTF node when loading the asset
+         * @param mesh The glTF mesh when loading the asset
+         * @param primitive The glTF mesh primitive property
+         * @param assign A function called synchronously after parsing the glTF properties
+         * @returns A promise that resolves with the loaded mesh when the load is complete or null if not handled
+         */
+        _loadMeshPrimitiveAsync(context: string, name: string, node: INode, mesh: IMesh, primitive: IMeshPrimitive, assign: (babylonMesh: AbstractMesh) => void): Promise<AbstractMesh>;
         private _loadVertexDataAsync;
         private _createMorphTargets;
         private _loadMorphTargetsAsync;
@@ -1366,7 +1395,17 @@ declare module BABYLON.GLTF2 {
          * @returns A promise that resolves with the loaded Babylon animation group when the load is complete
          */
         loadAnimationAsync(context: string, animation: IAnimation): Promise<AnimationGroup>;
-        private _loadAnimationChannelAsync;
+        /**
+         * @hidden Loads a glTF animation channel.
+         * @param context The context when loading the asset
+         * @param animationContext The context of the animation when loading the asset
+         * @param animation The glTF animation property
+         * @param channel The glTF animation channel property
+         * @param babylonAnimationGroup The babylon animation group property
+         * @param animationTargetOverride The babylon animation channel target override property. My be null.
+         * @returns A void promise when the channel load is complete
+         */
+        _loadAnimationChannelAsync(context: string, animationContext: string, animation: IAnimation, channel: IAnimationChannel, babylonAnimationGroup: AnimationGroup, animationTargetOverride?: Nullable<IAnimatable>): Promise<void>;
         private _loadAnimationSamplerAsync;
         private _loadBufferAsync;
         /**
@@ -1466,11 +1505,13 @@ declare module BABYLON.GLTF2 {
         private _extensionsLoadNodeAsync;
         private _extensionsLoadCameraAsync;
         private _extensionsLoadVertexDataAsync;
+        private _extensionsLoadMeshPrimitiveAsync;
         private _extensionsLoadMaterialAsync;
         private _extensionsCreateMaterial;
         private _extensionsLoadMaterialPropertiesAsync;
         private _extensionsLoadTextureInfoAsync;
         private _extensionsLoadAnimationAsync;
+        private _extensionsLoadSkinAsync;
         private _extensionsLoadUriAsync;
         /**
          * Helper method called by a loader extension to load an glTF extension.
@@ -1545,10 +1586,12 @@ declare module BABYLON.GLTF2.Loader.Extensions {
     export class KHR_draco_mesh_compression implements IGLTFLoaderExtension {
         /** The name of this extension. */
         readonly name: string;
+        /** The draco compression used to decode vertex data. */
+        dracoCompression?: DracoCompression;
         /** Defines whether this extension is enabled. */
         enabled: boolean;
         private _loader;
-        private _dracoCompression?;
+        private _dracoCompressionOwned;
         /** @hidden */
         constructor(loader: GLTFLoader);
         /** @hidden */

+ 57 - 9
dist/preview release/loaders/babylonjs.loaders.js

@@ -4158,13 +4158,14 @@ var KHR_draco_mesh_compression = /** @class */ (function () {
         this.name = NAME;
         /** Defines whether this extension is enabled. */
         this.enabled = babylonjs_Meshes_Compression_dracoCompression__WEBPACK_IMPORTED_MODULE_0__["DracoCompression"].DecoderAvailable;
+        this._dracoCompressionOwned = false;
         this._loader = loader;
     }
     /** @hidden */
     KHR_draco_mesh_compression.prototype.dispose = function () {
-        if (this._dracoCompression) {
-            this._dracoCompression.dispose();
-            delete this._dracoCompression;
+        if (this.dracoCompression && this._dracoCompressionOwned) {
+            this.dracoCompression.dispose();
+            delete this.dracoCompression;
         }
         delete this._loader;
     };
@@ -4205,10 +4206,11 @@ var KHR_draco_mesh_compression = /** @class */ (function () {
             var bufferView = _glTFLoader__WEBPACK_IMPORTED_MODULE_1__["ArrayItem"].Get(extensionContext, _this._loader.gltf.bufferViews, extension.bufferView);
             if (!bufferView._dracoBabylonGeometry) {
                 bufferView._dracoBabylonGeometry = _this._loader.loadBufferViewAsync("#/bufferViews/" + bufferView.index, bufferView).then(function (data) {
-                    if (!_this._dracoCompression) {
-                        _this._dracoCompression = new babylonjs_Meshes_Compression_dracoCompression__WEBPACK_IMPORTED_MODULE_0__["DracoCompression"]();
+                    if (!_this.dracoCompression) {
+                        _this.dracoCompression = new babylonjs_Meshes_Compression_dracoCompression__WEBPACK_IMPORTED_MODULE_0__["DracoCompression"]();
+                        _this._dracoCompressionOwned = true;
                     }
-                    return _this._dracoCompression.decodeMeshAsync(data, attributes).then(function (babylonVertexData) {
+                    return _this.dracoCompression.decodeMeshAsync(data, attributes).then(function (babylonVertexData) {
                         var babylonGeometry = new babylonjs_Meshes_Compression_dracoCompression__WEBPACK_IMPORTED_MODULE_0__["Geometry"](babylonMesh.name, _this._loader.babylonScene);
                         babylonVertexData.applyToGeometry(babylonGeometry);
                         return babylonGeometry;
@@ -5451,6 +5453,9 @@ var GLTFLoader = /** @class */ (function () {
             _this._setState(_glTFFileLoader__WEBPACK_IMPORTED_MODULE_1__["GLTFLoaderState"].LOADING);
             _this._extensionsOnLoading();
             var promises = new Array();
+            // Block the marking of materials dirty until the scene is loaded.
+            var oldBlockMaterialDirtyMechanism = _this._babylonScene.blockMaterialDirtyMechanism;
+            _this._babylonScene.blockMaterialDirtyMechanism = true;
             if (nodes) {
                 promises.push(_this.loadSceneAsync("/nodes", { nodes: nodes, index: -1 }));
             }
@@ -5458,6 +5463,8 @@ var GLTFLoader = /** @class */ (function () {
                 var scene = ArrayItem.Get("/scene", _this._gltf.scenes, _this._gltf.scene || 0);
                 promises.push(_this.loadSceneAsync("/scenes/" + scene.index, scene));
             }
+            // Restore the blocking of material dirty.
+            _this._babylonScene.blockMaterialDirtyMechanism = oldBlockMaterialDirtyMechanism;
             if (_this._parent.compileMaterials) {
                 promises.push(_this._compileMaterialsAsync());
             }
@@ -5813,8 +5820,22 @@ var GLTFLoader = /** @class */ (function () {
             return node._babylonTransformNode;
         });
     };
+    /**
+     * @hidden Define this method to modify the default behavior when loading data for mesh primitives.
+     * @param context The context when loading the asset
+     * @param name The mesh name when loading the asset
+     * @param node The glTF node when loading the asset
+     * @param mesh The glTF mesh when loading the asset
+     * @param primitive The glTF mesh primitive property
+     * @param assign A function called synchronously after parsing the glTF properties
+     * @returns A promise that resolves with the loaded mesh when the load is complete or null if not handled
+     */
     GLTFLoader.prototype._loadMeshPrimitiveAsync = function (context, name, node, mesh, primitive, assign) {
         var _this = this;
+        var extensionPromise = this._extensionsLoadMeshPrimitiveAsync(context, name, node, mesh, primitive, assign);
+        if (extensionPromise) {
+            return extensionPromise;
+        }
         this.logOpen("" + context);
         var canInstance = (node.skin == undefined && !mesh.primitives[0].targets);
         var babylonAbstractMesh;
@@ -6020,6 +6041,10 @@ var GLTFLoader = /** @class */ (function () {
     };
     GLTFLoader.prototype._loadSkinAsync = function (context, node, skin) {
         var _this = this;
+        var extensionPromise = this._extensionsLoadSkinAsync(context, node, skin);
+        if (extensionPromise) {
+            return extensionPromise;
+        }
         var assignSkeleton = function (skeleton) {
             _this._forEachPrimitive(node, function (babylonMesh) {
                 babylonMesh.skeleton = skeleton;
@@ -6186,8 +6211,19 @@ var GLTFLoader = /** @class */ (function () {
             return babylonAnimationGroup;
         });
     };
-    GLTFLoader.prototype._loadAnimationChannelAsync = function (context, animationContext, animation, channel, babylonAnimationGroup) {
+    /**
+     * @hidden Loads a glTF animation channel.
+     * @param context The context when loading the asset
+     * @param animationContext The context of the animation when loading the asset
+     * @param animation The glTF animation property
+     * @param channel The glTF animation channel property
+     * @param babylonAnimationGroup The babylon animation group property
+     * @param animationTargetOverride The babylon animation channel target override property. My be null.
+     * @returns A void promise when the channel load is complete
+     */
+    GLTFLoader.prototype._loadAnimationChannelAsync = function (context, animationContext, animation, channel, babylonAnimationGroup, animationTargetOverride) {
         var _this = this;
+        if (animationTargetOverride === void 0) { animationTargetOverride = null; }
         if (channel.target.node == undefined) {
             return Promise.resolve();
         }
@@ -6321,8 +6357,14 @@ var GLTFLoader = /** @class */ (function () {
                 var animationName = babylonAnimationGroup.name + "_channel" + babylonAnimationGroup.targetedAnimations.length;
                 var babylonAnimation = new babylonjs_Misc_deferred__WEBPACK_IMPORTED_MODULE_0__["Animation"](animationName, targetPath, 1, animationType);
                 babylonAnimation.setKeys(keys);
-                targetNode._babylonTransformNode.animations.push(babylonAnimation);
-                babylonAnimationGroup.addTargetedAnimation(babylonAnimation, targetNode._babylonTransformNode);
+                if (animationTargetOverride != null && animationTargetOverride.animations != null) {
+                    animationTargetOverride.animations.push(babylonAnimation);
+                    babylonAnimationGroup.addTargetedAnimation(babylonAnimation, animationTargetOverride);
+                }
+                else {
+                    targetNode._babylonTransformNode.animations.push(babylonAnimation);
+                    babylonAnimationGroup.addTargetedAnimation(babylonAnimation, targetNode._babylonTransformNode);
+                }
             }
         });
     };
@@ -7063,6 +7105,9 @@ var GLTFLoader = /** @class */ (function () {
     GLTFLoader.prototype._extensionsLoadVertexDataAsync = function (context, primitive, babylonMesh) {
         return this._applyExtensions(primitive, "loadVertexData", function (extension) { return extension._loadVertexDataAsync && extension._loadVertexDataAsync(context, primitive, babylonMesh); });
     };
+    GLTFLoader.prototype._extensionsLoadMeshPrimitiveAsync = function (context, name, node, mesh, primitive, assign) {
+        return this._applyExtensions(primitive, "loadMeshPrimitive", function (extension) { return extension._loadMeshPrimitiveAsync && extension._loadMeshPrimitiveAsync(context, name, node, mesh, primitive, assign); });
+    };
     GLTFLoader.prototype._extensionsLoadMaterialAsync = function (context, material, babylonMesh, babylonDrawMode, assign) {
         return this._applyExtensions(material, "loadMaterial", function (extension) { return extension._loadMaterialAsync && extension._loadMaterialAsync(context, material, babylonMesh, babylonDrawMode, assign); });
     };
@@ -7078,6 +7123,9 @@ var GLTFLoader = /** @class */ (function () {
     GLTFLoader.prototype._extensionsLoadAnimationAsync = function (context, animation) {
         return this._applyExtensions(animation, "loadAnimation", function (extension) { return extension.loadAnimationAsync && extension.loadAnimationAsync(context, animation); });
     };
+    GLTFLoader.prototype._extensionsLoadSkinAsync = function (context, node, skin) {
+        return this._applyExtensions(skin, "loadSkin", function (extension) { return extension._loadSkinAsync && extension._loadSkinAsync(context, node, skin); });
+    };
     GLTFLoader.prototype._extensionsLoadUriAsync = function (context, property, uri) {
         return this._applyExtensions(property, "loadUri", function (extension) { return extension._loadUriAsync && extension._loadUriAsync(context, property, uri); });
     };

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


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


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

@@ -1202,8 +1202,9 @@ declare module "babylonjs-loaders/glTF/2.0/glTFLoaderExtension" {
     import { TransformNode } from "babylonjs/Meshes/transformNode";
     import { BaseTexture } from "babylonjs/Materials/Textures/baseTexture";
     import { Mesh } from "babylonjs/Meshes/mesh";
+    import { AbstractMesh } from "babylonjs/Meshes/abstractMesh";
     import { IDisposable } from "babylonjs/scene";
-    import { IScene, INode, ICamera, IMeshPrimitive, IMaterial, ITextureInfo, IAnimation } from "babylonjs-loaders/glTF/2.0/glTFLoaderInterfaces";
+    import { IScene, INode, IMesh, ISkin, ICamera, IMeshPrimitive, IMaterial, ITextureInfo, IAnimation } from "babylonjs-loaders/glTF/2.0/glTFLoaderInterfaces";
     import { IGLTFLoaderExtension as IGLTFBaseLoaderExtension } from "babylonjs-loaders/glTF/glTFFileLoader";
     import { IProperty } from 'babylonjs-gltf2interface';
     /**
@@ -1249,6 +1250,17 @@ declare module "babylonjs-loaders/glTF/2.0/glTFLoaderExtension" {
          */
         _loadVertexDataAsync?(context: string, primitive: IMeshPrimitive, babylonMesh: Mesh): Nullable<Promise<Geometry>>;
         /**
+         * @hidden Define this method to modify the default behavior when loading data for mesh primitives.
+         * @param context The context when loading the asset
+         * @param name The mesh name when loading the asset
+         * @param node The glTF node when loading the asset
+         * @param mesh The glTF mesh when loading the asset
+         * @param primitive The glTF mesh primitive property
+         * @param assign A function called synchronously after parsing the glTF properties
+         * @returns A promise that resolves with the loaded mesh when the load is complete or null if not handled
+         */
+        _loadMeshPrimitiveAsync?(context: string, name: string, node: INode, mesh: IMesh, primitive: IMeshPrimitive, assign: (babylonMesh: AbstractMesh) => void): Promise<AbstractMesh>;
+        /**
          * @hidden Define this method to modify the default behavior when loading materials. Load material creates the material and then loads material properties.
          * @param context The context when loading the asset
          * @param material The glTF material property
@@ -1288,7 +1300,15 @@ declare module "babylonjs-loaders/glTF/2.0/glTFLoaderExtension" {
          */
         loadAnimationAsync?(context: string, animation: IAnimation): Nullable<Promise<AnimationGroup>>;
         /**
-         * Define this method to modify the default behavior when loading uris.
+         * @hidden Define this method to modify the default behavior when loading skins.
+         * @param context The context when loading the asset
+         * @param node The glTF node property
+         * @param skin The glTF skin property
+         * @returns A promise that resolves when the load is complete or null if not handled
+         */
+        _loadSkinAsync?(context: string, node: INode, skin: ISkin): Nullable<Promise<void>>;
+        /**
+         * @hidden Define this method to modify the default behavior when loading uris.
          * @param context The context when loading the asset
          * @param property The glTF property associated with the uri
          * @param uri The uri to load
@@ -1299,6 +1319,7 @@ declare module "babylonjs-loaders/glTF/2.0/glTFLoaderExtension" {
 }
 declare module "babylonjs-loaders/glTF/2.0/glTFLoader" {
     import { Nullable } from "babylonjs/types";
+    import { IAnimatable } from "babylonjs/Misc/tools";
     import { Camera } from "babylonjs/Cameras/camera";
     import { AnimationGroup } from "babylonjs/Animations/animationGroup";
     import { Skeleton } from "babylonjs/Bones/skeleton";
@@ -1311,7 +1332,7 @@ declare module "babylonjs-loaders/glTF/2.0/glTFLoader" {
     import { SceneLoaderProgressEvent } from "babylonjs/Loading/sceneLoader";
     import { Scene } from "babylonjs/scene";
     import { IProperty } from "babylonjs-gltf2interface";
-    import { IGLTF, INode, IScene, ICamera, IAnimation, IBufferView, IMaterial, ITextureInfo, IImage, IArrayItem as IArrItem } from "babylonjs-loaders/glTF/2.0/glTFLoaderInterfaces";
+    import { IGLTF, INode, IScene, IMesh, ICamera, IAnimation, IAnimationChannel, IBufferView, IMaterial, ITextureInfo, IImage, IMeshPrimitive, IArrayItem as IArrItem } from "babylonjs-loaders/glTF/2.0/glTFLoaderInterfaces";
     import { IGLTFLoaderExtension } from "babylonjs-loaders/glTF/2.0/glTFLoaderExtension";
     import { IGLTFLoader, GLTFFileLoader, GLTFLoaderState, IGLTFLoaderData } from "babylonjs-loaders/glTF/glTFFileLoader";
     /**
@@ -1423,7 +1444,17 @@ declare module "babylonjs-loaders/glTF/2.0/glTFLoader" {
          */
         loadNodeAsync(context: string, node: INode, assign?: (babylonTransformNode: TransformNode) => void): Promise<TransformNode>;
         private _loadMeshAsync;
-        private _loadMeshPrimitiveAsync;
+        /**
+         * @hidden Define this method to modify the default behavior when loading data for mesh primitives.
+         * @param context The context when loading the asset
+         * @param name The mesh name when loading the asset
+         * @param node The glTF node when loading the asset
+         * @param mesh The glTF mesh when loading the asset
+         * @param primitive The glTF mesh primitive property
+         * @param assign A function called synchronously after parsing the glTF properties
+         * @returns A promise that resolves with the loaded mesh when the load is complete or null if not handled
+         */
+        _loadMeshPrimitiveAsync(context: string, name: string, node: INode, mesh: IMesh, primitive: IMeshPrimitive, assign: (babylonMesh: AbstractMesh) => void): Promise<AbstractMesh>;
         private _loadVertexDataAsync;
         private _createMorphTargets;
         private _loadMorphTargetsAsync;
@@ -1451,7 +1482,17 @@ declare module "babylonjs-loaders/glTF/2.0/glTFLoader" {
          * @returns A promise that resolves with the loaded Babylon animation group when the load is complete
          */
         loadAnimationAsync(context: string, animation: IAnimation): Promise<AnimationGroup>;
-        private _loadAnimationChannelAsync;
+        /**
+         * @hidden Loads a glTF animation channel.
+         * @param context The context when loading the asset
+         * @param animationContext The context of the animation when loading the asset
+         * @param animation The glTF animation property
+         * @param channel The glTF animation channel property
+         * @param babylonAnimationGroup The babylon animation group property
+         * @param animationTargetOverride The babylon animation channel target override property. My be null.
+         * @returns A void promise when the channel load is complete
+         */
+        _loadAnimationChannelAsync(context: string, animationContext: string, animation: IAnimation, channel: IAnimationChannel, babylonAnimationGroup: AnimationGroup, animationTargetOverride?: Nullable<IAnimatable>): Promise<void>;
         private _loadAnimationSamplerAsync;
         private _loadBufferAsync;
         /**
@@ -1551,11 +1592,13 @@ declare module "babylonjs-loaders/glTF/2.0/glTFLoader" {
         private _extensionsLoadNodeAsync;
         private _extensionsLoadCameraAsync;
         private _extensionsLoadVertexDataAsync;
+        private _extensionsLoadMeshPrimitiveAsync;
         private _extensionsLoadMaterialAsync;
         private _extensionsCreateMaterial;
         private _extensionsLoadMaterialPropertiesAsync;
         private _extensionsLoadTextureInfoAsync;
         private _extensionsLoadAnimationAsync;
+        private _extensionsLoadSkinAsync;
         private _extensionsLoadUriAsync;
         /**
          * Helper method called by a loader extension to load an glTF extension.
@@ -1628,6 +1671,7 @@ declare module "babylonjs-loaders/glTF/2.0/Extensions/EXT_lights_image_based" {
     }
 }
 declare module "babylonjs-loaders/glTF/2.0/Extensions/KHR_draco_mesh_compression" {
+    import { DracoCompression } from "babylonjs/Meshes/Compression/dracoCompression";
     import { Nullable } from "babylonjs/types";
     import { Geometry } from "babylonjs/Meshes/geometry";
     import { Mesh } from "babylonjs/Meshes/mesh";
@@ -1640,10 +1684,12 @@ declare module "babylonjs-loaders/glTF/2.0/Extensions/KHR_draco_mesh_compression
     export class KHR_draco_mesh_compression implements IGLTFLoaderExtension {
         /** The name of this extension. */
         readonly name: string;
+        /** The draco compression used to decode vertex data. */
+        dracoCompression?: DracoCompression;
         /** Defines whether this extension is enabled. */
         enabled: boolean;
         private _loader;
-        private _dracoCompression?;
+        private _dracoCompressionOwned;
         /** @hidden */
         constructor(loader: GLTFLoader);
         /** @hidden */
@@ -3419,6 +3465,17 @@ declare module BABYLON.GLTF2 {
          */
         _loadVertexDataAsync?(context: string, primitive: IMeshPrimitive, babylonMesh: Mesh): Nullable<Promise<Geometry>>;
         /**
+         * @hidden Define this method to modify the default behavior when loading data for mesh primitives.
+         * @param context The context when loading the asset
+         * @param name The mesh name when loading the asset
+         * @param node The glTF node when loading the asset
+         * @param mesh The glTF mesh when loading the asset
+         * @param primitive The glTF mesh primitive property
+         * @param assign A function called synchronously after parsing the glTF properties
+         * @returns A promise that resolves with the loaded mesh when the load is complete or null if not handled
+         */
+        _loadMeshPrimitiveAsync?(context: string, name: string, node: INode, mesh: IMesh, primitive: IMeshPrimitive, assign: (babylonMesh: AbstractMesh) => void): Promise<AbstractMesh>;
+        /**
          * @hidden Define this method to modify the default behavior when loading materials. Load material creates the material and then loads material properties.
          * @param context The context when loading the asset
          * @param material The glTF material property
@@ -3458,7 +3515,15 @@ declare module BABYLON.GLTF2 {
          */
         loadAnimationAsync?(context: string, animation: IAnimation): Nullable<Promise<AnimationGroup>>;
         /**
-         * Define this method to modify the default behavior when loading uris.
+         * @hidden Define this method to modify the default behavior when loading skins.
+         * @param context The context when loading the asset
+         * @param node The glTF node property
+         * @param skin The glTF skin property
+         * @returns A promise that resolves when the load is complete or null if not handled
+         */
+        _loadSkinAsync?(context: string, node: INode, skin: ISkin): Nullable<Promise<void>>;
+        /**
+         * @hidden Define this method to modify the default behavior when loading uris.
          * @param context The context when loading the asset
          * @param property The glTF property associated with the uri
          * @param uri The uri to load
@@ -3577,7 +3642,17 @@ declare module BABYLON.GLTF2 {
          */
         loadNodeAsync(context: string, node: INode, assign?: (babylonTransformNode: TransformNode) => void): Promise<TransformNode>;
         private _loadMeshAsync;
-        private _loadMeshPrimitiveAsync;
+        /**
+         * @hidden Define this method to modify the default behavior when loading data for mesh primitives.
+         * @param context The context when loading the asset
+         * @param name The mesh name when loading the asset
+         * @param node The glTF node when loading the asset
+         * @param mesh The glTF mesh when loading the asset
+         * @param primitive The glTF mesh primitive property
+         * @param assign A function called synchronously after parsing the glTF properties
+         * @returns A promise that resolves with the loaded mesh when the load is complete or null if not handled
+         */
+        _loadMeshPrimitiveAsync(context: string, name: string, node: INode, mesh: IMesh, primitive: IMeshPrimitive, assign: (babylonMesh: AbstractMesh) => void): Promise<AbstractMesh>;
         private _loadVertexDataAsync;
         private _createMorphTargets;
         private _loadMorphTargetsAsync;
@@ -3605,7 +3680,17 @@ declare module BABYLON.GLTF2 {
          * @returns A promise that resolves with the loaded Babylon animation group when the load is complete
          */
         loadAnimationAsync(context: string, animation: IAnimation): Promise<AnimationGroup>;
-        private _loadAnimationChannelAsync;
+        /**
+         * @hidden Loads a glTF animation channel.
+         * @param context The context when loading the asset
+         * @param animationContext The context of the animation when loading the asset
+         * @param animation The glTF animation property
+         * @param channel The glTF animation channel property
+         * @param babylonAnimationGroup The babylon animation group property
+         * @param animationTargetOverride The babylon animation channel target override property. My be null.
+         * @returns A void promise when the channel load is complete
+         */
+        _loadAnimationChannelAsync(context: string, animationContext: string, animation: IAnimation, channel: IAnimationChannel, babylonAnimationGroup: AnimationGroup, animationTargetOverride?: Nullable<IAnimatable>): Promise<void>;
         private _loadAnimationSamplerAsync;
         private _loadBufferAsync;
         /**
@@ -3705,11 +3790,13 @@ declare module BABYLON.GLTF2 {
         private _extensionsLoadNodeAsync;
         private _extensionsLoadCameraAsync;
         private _extensionsLoadVertexDataAsync;
+        private _extensionsLoadMeshPrimitiveAsync;
         private _extensionsLoadMaterialAsync;
         private _extensionsCreateMaterial;
         private _extensionsLoadMaterialPropertiesAsync;
         private _extensionsLoadTextureInfoAsync;
         private _extensionsLoadAnimationAsync;
+        private _extensionsLoadSkinAsync;
         private _extensionsLoadUriAsync;
         /**
          * Helper method called by a loader extension to load an glTF extension.
@@ -3784,10 +3871,12 @@ declare module BABYLON.GLTF2.Loader.Extensions {
     export class KHR_draco_mesh_compression implements IGLTFLoaderExtension {
         /** The name of this extension. */
         readonly name: string;
+        /** The draco compression used to decode vertex data. */
+        dracoCompression?: DracoCompression;
         /** Defines whether this extension is enabled. */
         enabled: boolean;
         private _loader;
-        private _dracoCompression?;
+        private _dracoCompressionOwned;
         /** @hidden */
         constructor(loader: GLTFLoader);
         /** @hidden */

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

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

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

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

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

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

@@ -1 +1 @@
-{"engineOnly":308951,"sceneOnly":510921,"minGridMaterial":634690,"minStandardMaterial":758509}
+{"engineOnly":291173,"sceneOnly":495340,"minGridMaterial":620335,"minStandardMaterial":743431}

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

+ 1 - 1
dist/preview release/readme.md

@@ -3,7 +3,7 @@
 Getting started? Play directly with the Babylon.js API using our [playground](https://playground.babylonjs.com/). It also contains a lot of samples to learn how to use it.
 
 [![npm version](https://badge.fury.io/js/babylonjs.svg)](https://badge.fury.io/js/babylonjs)
-[![Build Status](https://travis-ci.com/BabylonJS/Babylon.js.svg?branch=master)](https://travis-ci.com/BabylonJS/Babylon.js)
+[![Build Status](https://dev.azure.com/babylonjs/ContinousIntegration/_apis/build/status/CI?branchName=master)](https://dev.azure.com/babylonjs/ContinousIntegration/_build/latest?definitionId=1&branchName=master)
 [![Average time to resolve an issue](http://isitmaintained.com/badge/resolution/BabylonJS/Babylon.js.svg)](http://isitmaintained.com/project/BabylonJS/Babylon.js "Average time to resolve an issue")
 [![Percentage of issues still open](https://isitmaintained.com/badge/open/babylonJS/babylon.js.svg)](https://isitmaintained.com/project/babylonJS/babylon.js "Percentage of issues still open")
 [![Build Size](https://img.badgesize.io/BabylonJS/Babylon.js/master/dist/preview%20release/babylon.js.svg?compression=gzip)](https://img.badgesize.io/BabylonJS/Babylon.js/master/dist/preview%20release/babylon.js.svg?compression=gzip)

+ 6 - 1
dist/preview release/serializers/babylon.glTF2Serializer.js

@@ -729,7 +729,12 @@ var _GLTFAnimation = /** @class */ (function () {
                     }
                     previousTime = time;
                     maxUsedFrame = time;
-                    value = animation._interpolate(f, 0, undefined, animation.loopMode);
+                    var state = {
+                        key: 0,
+                        repeatCount: 0,
+                        loopMode: animation.loopMode
+                    };
+                    value = animation._interpolate(f, state);
                     _GLTFAnimation._SetInterpolatedValue(babylonTransformNode, value, time, animation, animationChannelTargetPath, quaternionCache, inputs, outputs, convertToRightHandedSystem, useQuaternion);
                 }
             }

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


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


+ 6 - 1
dist/preview release/serializers/babylonjs.serializers.js

@@ -907,7 +907,12 @@ var _GLTFAnimation = /** @class */ (function () {
                     }
                     previousTime = time;
                     maxUsedFrame = time;
-                    value = animation._interpolate(f, 0, undefined, animation.loopMode);
+                    var state = {
+                        key: 0,
+                        repeatCount: 0,
+                        loopMode: animation.loopMode
+                    };
+                    value = animation._interpolate(f, state);
                     _GLTFAnimation._SetInterpolatedValue(babylonTransformNode, value, time, animation, animationChannelTargetPath, quaternionCache, inputs, outputs, convertToRightHandedSystem, useQuaternion);
                 }
             }

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


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


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

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


+ 5 - 19
dist/preview release/viewer/babylon.viewer.d.ts

@@ -197,11 +197,11 @@ declare module BabylonViewer {
                 * Mainly used for help and errors
                 * @param subScreen the name of the subScreen. Those can be defined in the configuration object
                 */
-            showOverlayScreen(subScreen: string): Promise<Template> | Promise<string>;
+            showOverlayScreen(subScreen: string): Promise<string> | Promise<Template>;
             /**
                 * Hide the overlay screen.
                 */
-            hideOverlayScreen(): Promise<Template> | Promise<string>;
+            hideOverlayScreen(): Promise<string> | Promise<Template>;
             /**
                 * show the viewer (in case it was hidden)
                 *
@@ -218,11 +218,11 @@ declare module BabylonViewer {
                 * Show the loading screen.
                 * The loading screen can be configured using the configuration object
                 */
-            showLoadingScreen(): Promise<Template> | Promise<string>;
+            showLoadingScreen(): Promise<string> | Promise<Template>;
             /**
                 * Hide the loading screen
                 */
-            hideLoadingScreen(): Promise<Template> | Promise<string>;
+            hideLoadingScreen(): Promise<string> | Promise<Template>;
             dispose(): void;
             protected _onConfigurationLoaded(configuration: ViewerConfiguration): void;
     }
@@ -951,7 +951,7 @@ declare module BabylonViewer {
       * @param name the name of the custom optimizer configuration
       * @param upgrade set to true if you want to upgrade optimizer and false if you want to degrade
       */
-    export function getCustomOptimizerByName(name: string, upgrade?: boolean): typeof extendedUpgrade;
+    export function getCustomOptimizerByName(name: string, upgrade?: boolean): (sceneManager: SceneManager) => boolean;
     export function registerCustomOptimizer(name: string, optimizer: (sceneManager: SceneManager) => boolean): void;
 }
 declare module BabylonViewer {
@@ -1594,20 +1594,6 @@ declare module BabylonViewer {
     export function addLoaderPlugin(name: string, plugin: ILoaderPlugin): void;
 }
 declare module BabylonViewer {
-    /**
-        * A custom upgrade-oriented function configuration for the scene optimizer.
-        *
-        * @param viewer the viewer to optimize
-        */
-    export function extendedUpgrade(sceneManager: SceneManager): boolean;
-    /**
-        * A custom degrade-oriented function configuration for the scene optimizer.
-        *
-        * @param viewer the viewer to optimize
-        */
-    export function extendedDegrade(sceneManager: SceneManager): boolean;
-}
-declare module BabylonViewer {
     export interface IEnvironmentMapConfiguration {
             /**
                 * Environment map texture path in relative to the asset folder.

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


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


+ 5 - 22
dist/preview release/viewer/babylon.viewer.module.d.ts

@@ -230,11 +230,11 @@ declare module 'babylonjs-viewer/viewer/defaultViewer' {
                 * Mainly used for help and errors
                 * @param subScreen the name of the subScreen. Those can be defined in the configuration object
                 */
-            showOverlayScreen(subScreen: string): Promise<Template> | Promise<string>;
+            showOverlayScreen(subScreen: string): Promise<string> | Promise<Template>;
             /**
                 * Hide the overlay screen.
                 */
-            hideOverlayScreen(): Promise<Template> | Promise<string>;
+            hideOverlayScreen(): Promise<string> | Promise<Template>;
             /**
                 * show the viewer (in case it was hidden)
                 *
@@ -251,11 +251,11 @@ declare module 'babylonjs-viewer/viewer/defaultViewer' {
                 * Show the loading screen.
                 * The loading screen can be configured using the configuration object
                 */
-            showLoadingScreen(): Promise<Template> | Promise<string>;
+            showLoadingScreen(): Promise<string> | Promise<Template>;
             /**
                 * Hide the loading screen
                 */
-            hideLoadingScreen(): Promise<Template> | Promise<string>;
+            hideLoadingScreen(): Promise<string> | Promise<Template>;
             dispose(): void;
             protected _onConfigurationLoaded(configuration: ViewerConfiguration): void;
     }
@@ -1031,14 +1031,13 @@ declare module 'babylonjs-viewer/templating/viewerTemplatePlugin' {
 }
 
 declare module 'babylonjs-viewer/optimizer/custom' {
-    import { extendedUpgrade } from "babylonjs-viewer/optimizer/custom/extended";
     import { SceneManager } from "babylonjs-viewer/managers/sceneManager";
     /**
       *
       * @param name the name of the custom optimizer configuration
       * @param upgrade set to true if you want to upgrade optimizer and false if you want to degrade
       */
-    export function getCustomOptimizerByName(name: string, upgrade?: boolean): typeof extendedUpgrade;
+    export function getCustomOptimizerByName(name: string, upgrade?: boolean): (sceneManager: SceneManager) => boolean;
     export function registerCustomOptimizer(name: string, optimizer: (sceneManager: SceneManager) => boolean): void;
 }
 
@@ -1738,22 +1737,6 @@ declare module 'babylonjs-viewer/loader/plugins' {
     export function addLoaderPlugin(name: string, plugin: ILoaderPlugin): void;
 }
 
-declare module 'babylonjs-viewer/optimizer/custom/extended' {
-    import { SceneManager } from 'babylonjs-viewer/managers/sceneManager';
-    /**
-        * A custom upgrade-oriented function configuration for the scene optimizer.
-        *
-        * @param viewer the viewer to optimize
-        */
-    export function extendedUpgrade(sceneManager: SceneManager): boolean;
-    /**
-        * A custom degrade-oriented function configuration for the scene optimizer.
-        *
-        * @param viewer the viewer to optimize
-        */
-    export function extendedDegrade(sceneManager: SceneManager): boolean;
-}
-
 declare module 'babylonjs-viewer/configuration/interfaces' {
     export * from 'babylonjs-viewer/configuration/interfaces/cameraConfiguration';
     export * from 'babylonjs-viewer/configuration/interfaces/colorGradingConfiguration';

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

@@ -1202,8 +1202,9 @@ declare module "babylonjs-loaders/glTF/2.0/glTFLoaderExtension" {
     import { TransformNode } from "babylonjs/Meshes/transformNode";
     import { BaseTexture } from "babylonjs/Materials/Textures/baseTexture";
     import { Mesh } from "babylonjs/Meshes/mesh";
+    import { AbstractMesh } from "babylonjs/Meshes/abstractMesh";
     import { IDisposable } from "babylonjs/scene";
-    import { IScene, INode, ICamera, IMeshPrimitive, IMaterial, ITextureInfo, IAnimation } from "babylonjs-loaders/glTF/2.0/glTFLoaderInterfaces";
+    import { IScene, INode, IMesh, ISkin, ICamera, IMeshPrimitive, IMaterial, ITextureInfo, IAnimation } from "babylonjs-loaders/glTF/2.0/glTFLoaderInterfaces";
     import { IGLTFLoaderExtension as IGLTFBaseLoaderExtension } from "babylonjs-loaders/glTF/glTFFileLoader";
     import { IProperty } from 'babylonjs-gltf2interface';
     /**
@@ -1249,6 +1250,17 @@ declare module "babylonjs-loaders/glTF/2.0/glTFLoaderExtension" {
          */
         _loadVertexDataAsync?(context: string, primitive: IMeshPrimitive, babylonMesh: Mesh): Nullable<Promise<Geometry>>;
         /**
+         * @hidden Define this method to modify the default behavior when loading data for mesh primitives.
+         * @param context The context when loading the asset
+         * @param name The mesh name when loading the asset
+         * @param node The glTF node when loading the asset
+         * @param mesh The glTF mesh when loading the asset
+         * @param primitive The glTF mesh primitive property
+         * @param assign A function called synchronously after parsing the glTF properties
+         * @returns A promise that resolves with the loaded mesh when the load is complete or null if not handled
+         */
+        _loadMeshPrimitiveAsync?(context: string, name: string, node: INode, mesh: IMesh, primitive: IMeshPrimitive, assign: (babylonMesh: AbstractMesh) => void): Promise<AbstractMesh>;
+        /**
          * @hidden Define this method to modify the default behavior when loading materials. Load material creates the material and then loads material properties.
          * @param context The context when loading the asset
          * @param material The glTF material property
@@ -1288,7 +1300,15 @@ declare module "babylonjs-loaders/glTF/2.0/glTFLoaderExtension" {
          */
         loadAnimationAsync?(context: string, animation: IAnimation): Nullable<Promise<AnimationGroup>>;
         /**
-         * Define this method to modify the default behavior when loading uris.
+         * @hidden Define this method to modify the default behavior when loading skins.
+         * @param context The context when loading the asset
+         * @param node The glTF node property
+         * @param skin The glTF skin property
+         * @returns A promise that resolves when the load is complete or null if not handled
+         */
+        _loadSkinAsync?(context: string, node: INode, skin: ISkin): Nullable<Promise<void>>;
+        /**
+         * @hidden Define this method to modify the default behavior when loading uris.
          * @param context The context when loading the asset
          * @param property The glTF property associated with the uri
          * @param uri The uri to load
@@ -1299,6 +1319,7 @@ declare module "babylonjs-loaders/glTF/2.0/glTFLoaderExtension" {
 }
 declare module "babylonjs-loaders/glTF/2.0/glTFLoader" {
     import { Nullable } from "babylonjs/types";
+    import { IAnimatable } from "babylonjs/Misc/tools";
     import { Camera } from "babylonjs/Cameras/camera";
     import { AnimationGroup } from "babylonjs/Animations/animationGroup";
     import { Skeleton } from "babylonjs/Bones/skeleton";
@@ -1311,7 +1332,7 @@ declare module "babylonjs-loaders/glTF/2.0/glTFLoader" {
     import { SceneLoaderProgressEvent } from "babylonjs/Loading/sceneLoader";
     import { Scene } from "babylonjs/scene";
     import { IProperty } from "babylonjs-gltf2interface";
-    import { IGLTF, INode, IScene, ICamera, IAnimation, IBufferView, IMaterial, ITextureInfo, IImage, IArrayItem as IArrItem } from "babylonjs-loaders/glTF/2.0/glTFLoaderInterfaces";
+    import { IGLTF, INode, IScene, IMesh, ICamera, IAnimation, IAnimationChannel, IBufferView, IMaterial, ITextureInfo, IImage, IMeshPrimitive, IArrayItem as IArrItem } from "babylonjs-loaders/glTF/2.0/glTFLoaderInterfaces";
     import { IGLTFLoaderExtension } from "babylonjs-loaders/glTF/2.0/glTFLoaderExtension";
     import { IGLTFLoader, GLTFFileLoader, GLTFLoaderState, IGLTFLoaderData } from "babylonjs-loaders/glTF/glTFFileLoader";
     /**
@@ -1423,7 +1444,17 @@ declare module "babylonjs-loaders/glTF/2.0/glTFLoader" {
          */
         loadNodeAsync(context: string, node: INode, assign?: (babylonTransformNode: TransformNode) => void): Promise<TransformNode>;
         private _loadMeshAsync;
-        private _loadMeshPrimitiveAsync;
+        /**
+         * @hidden Define this method to modify the default behavior when loading data for mesh primitives.
+         * @param context The context when loading the asset
+         * @param name The mesh name when loading the asset
+         * @param node The glTF node when loading the asset
+         * @param mesh The glTF mesh when loading the asset
+         * @param primitive The glTF mesh primitive property
+         * @param assign A function called synchronously after parsing the glTF properties
+         * @returns A promise that resolves with the loaded mesh when the load is complete or null if not handled
+         */
+        _loadMeshPrimitiveAsync(context: string, name: string, node: INode, mesh: IMesh, primitive: IMeshPrimitive, assign: (babylonMesh: AbstractMesh) => void): Promise<AbstractMesh>;
         private _loadVertexDataAsync;
         private _createMorphTargets;
         private _loadMorphTargetsAsync;
@@ -1451,7 +1482,17 @@ declare module "babylonjs-loaders/glTF/2.0/glTFLoader" {
          * @returns A promise that resolves with the loaded Babylon animation group when the load is complete
          */
         loadAnimationAsync(context: string, animation: IAnimation): Promise<AnimationGroup>;
-        private _loadAnimationChannelAsync;
+        /**
+         * @hidden Loads a glTF animation channel.
+         * @param context The context when loading the asset
+         * @param animationContext The context of the animation when loading the asset
+         * @param animation The glTF animation property
+         * @param channel The glTF animation channel property
+         * @param babylonAnimationGroup The babylon animation group property
+         * @param animationTargetOverride The babylon animation channel target override property. My be null.
+         * @returns A void promise when the channel load is complete
+         */
+        _loadAnimationChannelAsync(context: string, animationContext: string, animation: IAnimation, channel: IAnimationChannel, babylonAnimationGroup: AnimationGroup, animationTargetOverride?: Nullable<IAnimatable>): Promise<void>;
         private _loadAnimationSamplerAsync;
         private _loadBufferAsync;
         /**
@@ -1551,11 +1592,13 @@ declare module "babylonjs-loaders/glTF/2.0/glTFLoader" {
         private _extensionsLoadNodeAsync;
         private _extensionsLoadCameraAsync;
         private _extensionsLoadVertexDataAsync;
+        private _extensionsLoadMeshPrimitiveAsync;
         private _extensionsLoadMaterialAsync;
         private _extensionsCreateMaterial;
         private _extensionsLoadMaterialPropertiesAsync;
         private _extensionsLoadTextureInfoAsync;
         private _extensionsLoadAnimationAsync;
+        private _extensionsLoadSkinAsync;
         private _extensionsLoadUriAsync;
         /**
          * Helper method called by a loader extension to load an glTF extension.
@@ -1628,6 +1671,7 @@ declare module "babylonjs-loaders/glTF/2.0/Extensions/EXT_lights_image_based" {
     }
 }
 declare module "babylonjs-loaders/glTF/2.0/Extensions/KHR_draco_mesh_compression" {
+    import { DracoCompression } from "babylonjs/Meshes/Compression/dracoCompression";
     import { Nullable } from "babylonjs/types";
     import { Geometry } from "babylonjs/Meshes/geometry";
     import { Mesh } from "babylonjs/Meshes/mesh";
@@ -1640,10 +1684,12 @@ declare module "babylonjs-loaders/glTF/2.0/Extensions/KHR_draco_mesh_compression
     export class KHR_draco_mesh_compression implements IGLTFLoaderExtension {
         /** The name of this extension. */
         readonly name: string;
+        /** The draco compression used to decode vertex data. */
+        dracoCompression?: DracoCompression;
         /** Defines whether this extension is enabled. */
         enabled: boolean;
         private _loader;
-        private _dracoCompression?;
+        private _dracoCompressionOwned;
         /** @hidden */
         constructor(loader: GLTFLoader);
         /** @hidden */
@@ -3419,6 +3465,17 @@ declare module BABYLON.GLTF2 {
          */
         _loadVertexDataAsync?(context: string, primitive: IMeshPrimitive, babylonMesh: Mesh): Nullable<Promise<Geometry>>;
         /**
+         * @hidden Define this method to modify the default behavior when loading data for mesh primitives.
+         * @param context The context when loading the asset
+         * @param name The mesh name when loading the asset
+         * @param node The glTF node when loading the asset
+         * @param mesh The glTF mesh when loading the asset
+         * @param primitive The glTF mesh primitive property
+         * @param assign A function called synchronously after parsing the glTF properties
+         * @returns A promise that resolves with the loaded mesh when the load is complete or null if not handled
+         */
+        _loadMeshPrimitiveAsync?(context: string, name: string, node: INode, mesh: IMesh, primitive: IMeshPrimitive, assign: (babylonMesh: AbstractMesh) => void): Promise<AbstractMesh>;
+        /**
          * @hidden Define this method to modify the default behavior when loading materials. Load material creates the material and then loads material properties.
          * @param context The context when loading the asset
          * @param material The glTF material property
@@ -3458,7 +3515,15 @@ declare module BABYLON.GLTF2 {
          */
         loadAnimationAsync?(context: string, animation: IAnimation): Nullable<Promise<AnimationGroup>>;
         /**
-         * Define this method to modify the default behavior when loading uris.
+         * @hidden Define this method to modify the default behavior when loading skins.
+         * @param context The context when loading the asset
+         * @param node The glTF node property
+         * @param skin The glTF skin property
+         * @returns A promise that resolves when the load is complete or null if not handled
+         */
+        _loadSkinAsync?(context: string, node: INode, skin: ISkin): Nullable<Promise<void>>;
+        /**
+         * @hidden Define this method to modify the default behavior when loading uris.
          * @param context The context when loading the asset
          * @param property The glTF property associated with the uri
          * @param uri The uri to load
@@ -3577,7 +3642,17 @@ declare module BABYLON.GLTF2 {
          */
         loadNodeAsync(context: string, node: INode, assign?: (babylonTransformNode: TransformNode) => void): Promise<TransformNode>;
         private _loadMeshAsync;
-        private _loadMeshPrimitiveAsync;
+        /**
+         * @hidden Define this method to modify the default behavior when loading data for mesh primitives.
+         * @param context The context when loading the asset
+         * @param name The mesh name when loading the asset
+         * @param node The glTF node when loading the asset
+         * @param mesh The glTF mesh when loading the asset
+         * @param primitive The glTF mesh primitive property
+         * @param assign A function called synchronously after parsing the glTF properties
+         * @returns A promise that resolves with the loaded mesh when the load is complete or null if not handled
+         */
+        _loadMeshPrimitiveAsync(context: string, name: string, node: INode, mesh: IMesh, primitive: IMeshPrimitive, assign: (babylonMesh: AbstractMesh) => void): Promise<AbstractMesh>;
         private _loadVertexDataAsync;
         private _createMorphTargets;
         private _loadMorphTargetsAsync;
@@ -3605,7 +3680,17 @@ declare module BABYLON.GLTF2 {
          * @returns A promise that resolves with the loaded Babylon animation group when the load is complete
          */
         loadAnimationAsync(context: string, animation: IAnimation): Promise<AnimationGroup>;
-        private _loadAnimationChannelAsync;
+        /**
+         * @hidden Loads a glTF animation channel.
+         * @param context The context when loading the asset
+         * @param animationContext The context of the animation when loading the asset
+         * @param animation The glTF animation property
+         * @param channel The glTF animation channel property
+         * @param babylonAnimationGroup The babylon animation group property
+         * @param animationTargetOverride The babylon animation channel target override property. My be null.
+         * @returns A void promise when the channel load is complete
+         */
+        _loadAnimationChannelAsync(context: string, animationContext: string, animation: IAnimation, channel: IAnimationChannel, babylonAnimationGroup: AnimationGroup, animationTargetOverride?: Nullable<IAnimatable>): Promise<void>;
         private _loadAnimationSamplerAsync;
         private _loadBufferAsync;
         /**
@@ -3705,11 +3790,13 @@ declare module BABYLON.GLTF2 {
         private _extensionsLoadNodeAsync;
         private _extensionsLoadCameraAsync;
         private _extensionsLoadVertexDataAsync;
+        private _extensionsLoadMeshPrimitiveAsync;
         private _extensionsLoadMaterialAsync;
         private _extensionsCreateMaterial;
         private _extensionsLoadMaterialPropertiesAsync;
         private _extensionsLoadTextureInfoAsync;
         private _extensionsLoadAnimationAsync;
+        private _extensionsLoadSkinAsync;
         private _extensionsLoadUriAsync;
         /**
          * Helper method called by a loader extension to load an glTF extension.
@@ -3784,10 +3871,12 @@ declare module BABYLON.GLTF2.Loader.Extensions {
     export class KHR_draco_mesh_compression implements IGLTFLoaderExtension {
         /** The name of this extension. */
         readonly name: string;
+        /** The draco compression used to decode vertex data. */
+        dracoCompression?: DracoCompression;
         /** Defines whether this extension is enabled. */
         enabled: boolean;
         private _loader;
-        private _dracoCompression?;
+        private _dracoCompressionOwned;
         /** @hidden */
         constructor(loader: GLTFLoader);
         /** @hidden */

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

@@ -8,7 +8,7 @@
 - Added [support for AmmoJS](https://doc.babylonjs.com/how_to/using_the_physics_engine) as a physics plugin (Composite objects, motors, joints) ([TrevorDev](https://github.com/TrevorDev))
   - Added support for soft bodies, which are 3D softbody, 2D cloth and 1D rope, in Ammo physics plugin. [Doc](https://doc.babylonjs.com/how_to/soft_bodies) ([JohnK](https://github.com/BabylonJSGuide))
   - Added support for [Convex Hull Impostor][https://github.com/kripken/ammo.js/blob/master/bullet/src/BulletCollision/CollisionShapes/btConvexHullShape.h] using Ammo.js plugin ([MackeyK24](https://github.com/mackeyk24))
-  - Added AmmoJSPlugin scene file loader [MackeyK24](https://github.com/mackeyk24))  
+  - Added AmmoJSPlugin scene file loader ([MackeyK24](https://github.com/mackeyk24))
 - Added support for [WebXR](https://doc.babylonjs.com/how_to/webxr) ([TrevorDev](https://github.com/TrevorDev))
   - Add customAnimationFrameRequester to allow sessions to hook into engine's render loop ([TrevorDev](https://github.com/TrevorDev))
   - camera customDefaultRenderTarget to allow cameras to render to a custom render target (eg. xr framebuffer) instead of the canvas ([TrevorDev](https://github.com/TrevorDev))
@@ -96,6 +96,7 @@
 - Align `BoundingBox` and `BoundingSphere` API and behavior for clarity and simplicity. As a consequence, the `BoundingBox`'s method `setWorldMatrix` has been removed and the underlying world matrix cannot be modified but by calling `reConstruct` or `update`. ([barroij](https://github.com/barroij))
 - Make sure that `Material.markAsDirty` and all the `markXXXDirty` methods early out when `scene.blockMaterialDirtyMechanism` is true. ([barroij](https://github.com/barroij))
 - Add updateUpVectorFromRotation to target camera to allow the up vector to be computed from rotation ([TrevorDev](https://github.com/TrevorDev))
+- Added `wrap` boolean parameter to `CreateBox` options to orientate images vertically on box sides ([JohnK](https://github.com/BabylonJSGuide))
 - Added opacity texture support to `GridMaterial` ([Deltakosh](https://github.com/deltakosh))
 - Added support for deserializing morph target animations in animation groups
 - AssetContainer dispose method ([TrevorDev](https://github.com/TrevorDev))
@@ -130,11 +131,16 @@
 - Added `MeshExploder` class ([danjpar](https://github.com/danjpar))
 - Observables can now make observers top or bottom priority ([TrevorDev](https://github.com/TrevorDev))
 - Mesh outline no longer is shown through the mesh when it's transparent ([TrevorDev](https://github.com/TrevorDev))
-- DeviceOrientationCamera will no longer be modified by mouse input if the orientation sensor is active ([TrevorDev](https://github.com/TrevorDev))
-- Added LoadScriptAsync tools helper function [MackeyK24](https://github.com/mackeyk24))  
+- DeviceOrientationCamera will no longer be modified by mouse input when the orientation sensor is active ([TrevorDev](https://github.com/TrevorDev))
+- Added LoadScriptAsync tools helper function [MackeyK24](https://github.com/mackeyk24))
 - Added customShaderNameResolve to PBRMaterialBase to allow subclasses to specify custom shader information [MackeyK24](https://github.com/mackeyk24))
 - Added PBRCustomMaterial to material library to allow easy subclassing of PBR materials [MackeyK24](https://github.com/mackeyk24))
 - Added `auto-exposure` support in `StandardRenderingPipeline` when `HDR` is enabled ([julien-moreau](https://github.com/julien-moreau))
+- Add `EquiRectangularCubeTexture` class to enable the usage of browser-canvas supported images as `CubeTexture`'s ([Dennis Dervisis](https://github.com/ddervisis))
+- Add `EquiRectangularCubeTextureAssetTask` to be able to load `EquiRectangularCubeTextures`s via Asset Manager ([Dennis Dervisis](https://github.com/ddervisis))
+- Added `Matrix.RotationAlignToRef` method to obtain rotation matrix from one vector to another ([sable](https://github.com/thscott))
+- ArcRotateCamera will now cache the necessary matrices when modifying its upVector, instead of calculating them each time they're needed ([sable](https://github.com/thscott))
+- Update `DracoCompression` to use web workers ([bghgary](https://github.com/bghgary))
 
 ### OBJ Loader
 - Add color vertex support (not part of standard) ([brianzinn](https://github.com/brianzinn))
@@ -150,8 +156,9 @@
 - Skinned meshes now behave as intended by glTF ([bghgary](https://github.com/bghgary))
   - Skinned meshes now set an override mesh instead of reparenting to the `__root__` transform node
   - Loaded bones are linked with the transform node created for the corresponding glTF node
-- Add `EquiRectangularCubeTexture` class to enable the usage of browser-canvas supported images as `CubeTexture`'s ([Dennis Dervisis](https://github.com/ddervisis))
-- Add `EquiRectangularCubeTextureAssetTask` to be able to load `EquiRectangularCubeTextures`s via Asset Manager ([Dennis Dervisis](https://github.com/ddervisis))
+- Improve load performance by blocking material dirtying during load ([bghgary](https://github.com/bghgary))
+- Added animation group target override to support custom animation targets ([MackeyK24](https://github.com/mackeyk24))
+- Added loadMeshPrimitiveAsync extension support ([MackeyK24](https://github.com/mackeyk24))
 
 ### glTF Serializer
 
@@ -207,7 +214,7 @@
 - CannonJS ignores connectedPivot joint parameter ([TrevorDev](https://github.com/TrevorDev))
 - Fix case sensitive paths ([mrdunk](https://github.com))
 - Fix more case sensitive paths ([mrdunk](https://github.com))
-- Attaching a BoundingBoxGizmo on a child should not remove its parent ([TrevorDev](https://github.com/TrevorDev))
+- Attaching a BoundingBoxGizmo on a child node shouldn't remove its parent ([TrevorDev](https://github.com/TrevorDev))
 - AmmoJS fix include issue caused after modules update and use world contact point to be consistent with Oimo and Cannon ([TrevorDev](https://github.com/TrevorDev))
 - Warn of motor with maxForce in Oimo plugin and set default force to be consistent with others, cannonJS support no impostor, cannonJS cylinder axis, ammoJS wake up impostor when apply force/impulse ([TrevorDev](https://github.com/TrevorDev))
 - Utility layer should render on last active camera ([TrevorDev](https://github.com/TrevorDev))
@@ -221,6 +228,11 @@
 - Fix `mesh.visibility` not working properly when certain material properties are set that changes the interpretation of alpha (e.g. refraction, specular over alpha, etc.) ([bghgary](https://github.com/bghgary))
 - Fix material and texture leak when loading/removing GLTF/obj/babylon files with AssetContainer ([TrevorDev](https://github.com/TrevorDev))
 - Avoid exception when removing impostor during cannon world step ([TrevorDev](https://github.com/TrevorDev))
+- Fix ArcRotateCamera divide by zero error (when looking along up axis) in rebuildAnglesAndRadius ([sable](https://github.com/thscott))
+- Fix ArcRotateCamera rebuildAnglesAndRadius when upVector modified ([sable](https://github.com/thscott))
+- Fix code branch, that does not try to (re)load an `EquiRectangularCubeTexture`/`HDRCubeTexture` when the caching returns an empty or corrupt `InternalTexture` ([Dennis Dervisis](https://github.com/ddervisis))
+- Add error eventlistener (bubbling up the onError callback chain) in case an `EquiRectangularCubeTexture` cannot be loaded, because of a wrong path or IO problems ([Dennis Dervisis](https://github.com/ddervisis))
+- 3D GUI buttons no longer will scale up when pressing with a multitouch device ([TrevorDev](https://github.com/TrevorDev))
 
 ### Core Engine
 - Fixed a bug with `mesh.alwaysSelectAsActiveMesh` preventing layerMask to be taken in account ([Deltakosh](https://github.com/deltakosh))

+ 2 - 2
gui/src/2D/controls/inputText.ts

@@ -758,7 +758,7 @@ export class InputText extends Control implements IFocusableControl {
         this._isTextHighlightOn = false;
         //when write permission to clipbaord data is denied
         try {
-            ev.clipboardData.setData("text/plain", this._highlightedText);
+            ev.clipboardData && ev.clipboardData.setData("text/plain", this._highlightedText);
         }
         catch { } //pass
         this._host.clipboardData = this._highlightedText;
@@ -773,7 +773,7 @@ export class InputText extends Control implements IFocusableControl {
         this._cursorOffset = this.text.length - this._startHighlightIndex;
         //when write permission to clipbaord data is denied
         try {
-            ev.clipboardData.setData("text/plain", this._highlightedText);
+            ev.clipboardData && ev.clipboardData.setData("text/plain", this._highlightedText);
         }
         catch { } //pass
 

+ 20 - 7
gui/src/3D/controls/control3D.ts

@@ -333,6 +333,7 @@ export class Control3D implements IDisposable, IBehaviorAware<Control3D> {
     /** @hidden */
     public _onPointerDown(target: Control3D, coordinates: Vector3, pointerId: number, buttonIndex: number): boolean {
         if (this._downCount !== 0) {
+            this._downCount++;
             return false;
         }
 
@@ -351,17 +352,24 @@ export class Control3D implements IDisposable, IBehaviorAware<Control3D> {
 
     /** @hidden */
     public _onPointerUp(target: Control3D, coordinates: Vector3, pointerId: number, buttonIndex: number, notifyClick: boolean): void {
-        this._downCount = 0;
-
+        this._downCount--;
         delete this._downPointerIds[pointerId];
 
-        if (notifyClick && (this._enterCount > 0 || this._enterCount === -1)) {
-            this.onPointerClickObservable.notifyObservers(new Vector3WithInfo(coordinates, buttonIndex), -1, target, this);
+        if (this._downCount < 0) {
+            // Handle if forcePointerUp was called prior to this
+            this._downCount = 0;
+            return;
         }
-        this.onPointerUpObservable.notifyObservers(new Vector3WithInfo(coordinates, buttonIndex), -1, target, this);
 
-        if (this.pointerUpAnimation) {
-            this.pointerUpAnimation();
+        if (this._downCount == 0) {
+            if (notifyClick && (this._enterCount > 0 || this._enterCount === -1)) {
+                this.onPointerClickObservable.notifyObservers(new Vector3WithInfo(coordinates, buttonIndex), -1, target, this);
+            }
+            this.onPointerUpObservable.notifyObservers(new Vector3WithInfo(coordinates, buttonIndex), -1, target, this);
+
+            if (this.pointerUpAnimation) {
+                this.pointerUpAnimation();
+            }
         }
     }
 
@@ -373,6 +381,11 @@ export class Control3D implements IDisposable, IBehaviorAware<Control3D> {
             for (var key in this._downPointerIds) {
                 this._onPointerUp(this, Vector3.Zero(), +key as number, 0, true);
             }
+            if (this._downCount > 0) {
+                this._downCount = 1;
+                this._onPointerUp(this, Vector3.Zero(), 0, 0, true);
+            }
+
         }
     }
 

+ 2 - 2
inspector/src/components/actionTabs/lines/booleanLineComponent.tsx

@@ -3,8 +3,8 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { faCheck, faTimesCircle } from "@fortawesome/free-solid-svg-icons";
 
 export interface IBooleanLineComponentProps {
-    label: string,
-    value: boolean
+    label: string;
+    value: boolean;
 }
 
 export class BooleanLineComponent extends React.Component<IBooleanLineComponentProps> {

+ 2 - 2
inspector/src/components/actionTabs/lines/buttonLineComponent.tsx

@@ -1,8 +1,8 @@
 import * as React from "react";
 
 export interface IButtonLineComponentProps {
-    label: string,
-    onClick: () => void
+    label: string;
+    onClick: () => void;
 }
 
 export class ButtonLineComponent extends React.Component<IButtonLineComponentProps> {

+ 2 - 2
inspector/src/components/actionTabs/lines/color3LineComponent.tsx

@@ -112,8 +112,8 @@ export class Color3LineComponent extends React.Component<IColor3LineComponentPro
         if (window.getSelection) {
             var range = document.createRange();
             range.selectNode(element);
-            window.getSelection().removeAllRanges();
-            window.getSelection().addRange(range);
+            window.getSelection()!.removeAllRanges();
+            window.getSelection()!.addRange(range);
         }
 
         document.execCommand('copy');

+ 3 - 3
inspector/src/components/actionTabs/lines/fileButtonLineComponent.tsx

@@ -1,9 +1,9 @@
 import * as React from "react";
 
 interface IFileButtonLineComponentProps {
-    label: string,
-    onClick: (file: File) => void,
-    accept: string
+    label: string;
+    onClick: (file: File) => void;
+    accept: string;
 }
 
 export class FileButtonLineComponent extends React.Component<IFileButtonLineComponentProps> {

+ 3 - 3
inspector/src/components/actionTabs/lines/messageLineComponent.tsx

@@ -3,9 +3,9 @@ import { IconProp } from "@fortawesome/fontawesome-svg-core";
 import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
 
 interface IMessageLineComponentProps {
-    text: string,
-    color?: string,
-    icon?: IconProp
+    text: string;
+    color?: string;
+    icon?: IconProp;
 }
 
 export class MessageLineComponent extends React.Component<IMessageLineComponentProps> {

+ 11 - 5
inspector/src/components/actionTabs/lines/numericInputComponent.tsx

@@ -1,12 +1,18 @@
 import * as React from "react";
 
 interface INumericInputComponentProps {
-    label: string,
-    value: number,
-    onChange: (value: number) => void
+    label: string;
+    value: number;
+    step?: number;
+    onChange: (value: number) => void;
 }
 
 export class NumericInputComponent extends React.Component<INumericInputComponentProps, { value: string }> {
+
+    static defaultProps = {
+        step: 1,
+    };
+
     private _localChange = false;
     constructor(props: INumericInputComponentProps) {
         super(props);
@@ -56,8 +62,8 @@ export class NumericInputComponent extends React.Component<INumericInputComponen
                         {`${this.props.label}: `}
                     </div>
                 }
-                <input type="number" step="1" className="numeric-input" value={this.state.value} onChange={evt => this.updateValue(evt)} />
+                <input type="number" step={this.props.step} className="numeric-input" value={this.state.value} onChange={evt => this.updateValue(evt)} />
             </div>
         )
     }
-}
+}

+ 4 - 4
inspector/src/components/actionTabs/lines/quaternionLineComponent.tsx

@@ -7,10 +7,10 @@ import { faMinus, faPlus } from "@fortawesome/free-solid-svg-icons";
 import { PropertyChangedEvent } from "../../propertyChangedEvent";
 
 interface IQuaternionLineComponentProps {
-    label: string,
-    target: any,
-    propertyName: string,
-    onPropertyChangedObservable?: Observable<PropertyChangedEvent>
+    label: string;
+    target: any;
+    propertyName: string;
+    onPropertyChangedObservable?: Observable<PropertyChangedEvent>;
 }
 
 export class QuaternionLineComponent extends React.Component<IQuaternionLineComponentProps, { isExpanded: boolean, value: Quaternion }> {

+ 4 - 4
inspector/src/components/actionTabs/lines/radioLineComponent.tsx

@@ -3,10 +3,10 @@ import { Nullable } from "babylonjs/types";
 import { Observer, Observable } from "babylonjs/Misc/observable";
 
 interface IRadioButtonLineComponentProps {
-    onSelectionChangedObservable: Observable<RadioButtonLineComponent>,
-    label: string,
-    isSelected: () => boolean,
-    onSelect: () => void
+    onSelectionChangedObservable: Observable<RadioButtonLineComponent>;
+    label: string;
+    isSelected: () => boolean;
+    onSelect: () => void;
 }
 
 export class RadioButtonLineComponent extends React.Component<IRadioButtonLineComponentProps, { isSelected: boolean }> {

+ 11 - 11
inspector/src/components/actionTabs/lines/sliderLineComponent.tsx

@@ -3,17 +3,17 @@ import { Observable } from "babylonjs/Misc/observable";
 import { PropertyChangedEvent } from "../../propertyChangedEvent";
 
 interface ISliderLineComponentProps {
-    label: string,
-    target?: any,
-    propertyName?: string,
-    minimum: number,
-    maximum: number,
-    step: number,
-    directValue?: number,
-    onChange?: (value: number) => void,
-    onInput?: (value: number) => void,
-    onPropertyChangedObservable?: Observable<PropertyChangedEvent>,
-    decimalCount?: number
+    label: string;
+    target?: any;
+    propertyName?: string;
+    minimum: number;
+    maximum: number;
+    step: number;
+    directValue?: number;
+    onChange?: (value: number) => void;
+    onInput?: (value: number) => void;
+    onPropertyChangedObservable?: Observable<PropertyChangedEvent>;
+    decimalCount?: number;
 }
 
 export class SliderLineComponent extends React.Component<ISliderLineComponentProps, { value: number }> {

+ 7 - 7
inspector/src/components/actionTabs/lines/textInputLineComponent.tsx

@@ -4,13 +4,13 @@ import { PropertyChangedEvent } from "../../../components/propertyChangedEvent";
 import { LockObject } from "../tabs/propertyGrids/lockObject";
 
 interface ITextInputLineComponentProps {
-    label: string,
-    lockObject: LockObject,
-    target?: any,
-    propertyName?: string,
-    value?: string,
-    onChange?: (value: string) => void,
-    onPropertyChangedObservable?: Observable<PropertyChangedEvent>
+    label: string;
+    lockObject: LockObject;
+    target?: any;
+    propertyName?: string;
+    value?: string;
+    onChange?: (value: string) => void;
+    onPropertyChangedObservable?: Observable<PropertyChangedEvent>;
 }
 
 export class TextInputLineComponent extends React.Component<ITextInputLineComponentProps, { value: string }> {

+ 5 - 5
inspector/src/components/actionTabs/lines/textLineComponent.tsx

@@ -1,11 +1,11 @@
 import * as React from "react";
 
 interface ITextLineComponentProps {
-    label: string,
-    value: string,
-    color?: string,
-    underline?: boolean,
-    onLink?: () => void
+    label: string;
+    value: string;
+    color?: string;
+    underline?: boolean;
+    onLink?: () => void;
 }
 
 export class TextLineComponent extends React.Component<ITextLineComponentProps> {

+ 4 - 4
inspector/src/components/actionTabs/lines/textureLineComponent.tsx

@@ -10,10 +10,10 @@ import { PassPostProcess, PassCubePostProcess } from "babylonjs/PostProcesses/pa
 import { GlobalState } from "../../../components/globalState";
 
 interface ITextureLineComponentProps {
-    texture: BaseTexture,
-    width: number,
-    height: number,
-    globalState: GlobalState
+    texture: BaseTexture;
+    width: number;
+    height: number;
+    globalState: GlobalState;
 }
 
 export class TextureLineComponent extends React.Component<ITextureLineComponentProps, { displayRed: boolean, displayGreen: boolean, displayBlue: boolean, displayAlpha: boolean, face: number }> {

+ 5 - 5
inspector/src/components/actionTabs/lines/valueLineComponent.tsx

@@ -1,11 +1,11 @@
 import * as React from "react";
 
 interface IValueLineComponentProps {
-    label: string,
-    value: number,
-    color?: string,
-    fractionDigits?: number,
-    units?: string
+    label: string;
+    value: number;
+    color?: string;
+    fractionDigits?: number;
+    units?: string;
 }
 
 export class ValueLineComponent extends React.Component<IValueLineComponentProps> {

+ 5 - 5
inspector/src/components/actionTabs/lines/vector2LineComponent.tsx

@@ -8,11 +8,11 @@ import { faMinus, faPlus } from "@fortawesome/free-solid-svg-icons";
 import { PropertyChangedEvent } from "../../propertyChangedEvent";
 
 interface IVector2LineComponentProps {
-    label: string,
-    target: any,
-    propertyName: string,
-    onChange?: (newvalue: Vector2) => void,
-    onPropertyChangedObservable?: Observable<PropertyChangedEvent>
+    label: string;
+    target: any;
+    propertyName: string;
+    onChange?: (newvalue: Vector2) => void;
+    onPropertyChangedObservable?: Observable<PropertyChangedEvent>;
 }
 
 export class Vector2LineComponent extends React.Component<IVector2LineComponentProps, { isExpanded: boolean, value: Vector2 }> {

+ 27 - 23
inspector/src/components/actionTabs/lines/vector3LineComponent.tsx

@@ -8,14 +8,20 @@ import { faMinus, faPlus } from "@fortawesome/free-solid-svg-icons";
 import { PropertyChangedEvent } from "../../propertyChangedEvent";
 
 interface IVector3LineComponentProps {
-    label: string,
-    target: any,
-    propertyName: string,
-    onChange?: (newvalue: Vector3) => void,
-    onPropertyChangedObservable?: Observable<PropertyChangedEvent>
+    label: string;
+    target: any;
+    propertyName: string;
+    step?: number;
+    onChange?: (newvalue: Vector3) => void;
+    onPropertyChangedObservable?: Observable<PropertyChangedEvent>;
 }
 
 export class Vector3LineComponent extends React.Component<IVector3LineComponentProps, { isExpanded: boolean, value: Vector3 }> {
+
+    static defaultProps = {
+        step: 1,
+    };
+
     private _localChange = false;
 
     constructor(props: IVector3LineComponentProps) {
@@ -56,37 +62,35 @@ export class Vector3LineComponent extends React.Component<IVector3LineComponentP
         });
     }
 
+    updateVector3() {
+        const store = this.props.target[this.props.propertyName].clone();
+        this.props.target[this.props.propertyName] = this.state.value;
+
+        this.setState({ value: store });
+
+        this.raiseOnPropertyChanged(store);
+    }
+
+
     updateStateX(value: number) {
         this._localChange = true;
 
-        const store = this.state.value.clone();
-        this.props.target[this.props.propertyName].x = value;
         this.state.value.x = value;
-        this.setState({ value: this.state.value });
-
-        this.raiseOnPropertyChanged(store);
+        this.updateVector3();
     }
 
     updateStateY(value: number) {
         this._localChange = true;
 
-        const store = this.state.value.clone();
-        this.props.target[this.props.propertyName].y = value;
         this.state.value.y = value;
-        this.setState({ value: this.state.value });
-
-        this.raiseOnPropertyChanged(store);
+        this.updateVector3();
     }
 
     updateStateZ(value: number) {
         this._localChange = true;
 
-        const store = this.state.value.clone();
-        this.props.target[this.props.propertyName].z = value;
         this.state.value.z = value;
-        this.setState({ value: this.state.value });
-
-        this.raiseOnPropertyChanged(store);
+        this.updateVector3();
     }
 
     render() {
@@ -109,9 +113,9 @@ export class Vector3LineComponent extends React.Component<IVector3LineComponentP
                 {
                     this.state.isExpanded &&
                     <div className="secondLine">
-                        <NumericInputComponent label="x" value={this.state.value.x} onChange={value => this.updateStateX(value)} />
-                        <NumericInputComponent label="y" value={this.state.value.y} onChange={value => this.updateStateY(value)} />
-                        <NumericInputComponent label="z" value={this.state.value.z} onChange={value => this.updateStateZ(value)} />
+                        <NumericInputComponent label="x" step={this.props.step} value={this.state.value.x} onChange={value => this.updateStateX(value)} />
+                        <NumericInputComponent label="y" step={this.props.step} value={this.state.value.y} onChange={value => this.updateStateY(value)} />
+                        <NumericInputComponent label="z" step={this.props.step} value={this.state.value.z} onChange={value => this.updateStateZ(value)} />
                     </div>
                 }
             </div>

+ 3 - 1
inspector/src/components/actionTabs/tabs/propertyGrids/animationPropertyGridComponent.tsx

@@ -52,7 +52,9 @@ export class AnimationGridComponent extends React.Component<IAnimationGridCompon
             this._animations = new Array<Animation>();
 
             animatables.forEach((animatable: IAnimatable) => {
-                this._animations!.push(...animatable.animations);
+                if (animatable.animations) {
+                    this._animations!.push(...animatable.animations);
+                }
             });
 
             // Extract from and to

+ 0 - 80
inspector/src/components/actionTabs/tabs/propertyGrids/meshes/axesViewerComponent.tsx

@@ -1,80 +0,0 @@
-import * as React from "react";
-
-import { Vector3, Tmp } from "babylonjs/Maths/math";
-import { TransformNode } from "babylonjs/Meshes/transformNode";
-import { AxesViewer } from "babylonjs/Debug/axesViewer";
-
-import { CheckBoxLineComponent } from "../../../lines/checkBoxLineComponent";
-import { UtilityLayerRenderer } from 'babylonjs/Rendering/utilityLayerRenderer';
-import { GlobalState } from '../../../../globalState';
-
-interface IAxisViewerComponentProps {
-    node: TransformNode;
-    globalState: GlobalState;
-}
-
-export class AxesViewerComponent extends React.Component<IAxisViewerComponentProps, { displayAxis: boolean }> {
-    constructor(props: IAxisViewerComponentProps) {
-        super(props);
-        const node = this.props.node;
-
-        if (!node.reservedDataStore) {
-            node.reservedDataStore = {};
-        }
-
-        this.state = { displayAxis: (node.reservedDataStore && node.reservedDataStore.axisViewer) ? true : false };
-    }
-
-    shouldComponentUpdate(nextProps: IAxisViewerComponentProps, nextState: { displayAxis: boolean }) {
-        if (nextProps.node !== this.props.node) {
-            nextState.displayAxis = (nextProps.node.reservedDataStore && nextProps.node.reservedDataStore.axisViewer) ? true : false;
-        }
-
-        return true;
-    }
-
-    displayAxes() {
-        const node = this.props.node;
-        const scene = UtilityLayerRenderer.DefaultUtilityLayer.utilityLayerScene;
-
-        if (node.reservedDataStore.axisViewer) {
-            node.reservedDataStore.axisViewer.dispose();
-            node.reservedDataStore.axisViewer = null;
-
-            scene.onBeforeRenderObservable.remove(node.reservedDataStore.onBeforeRenderObserver);
-            node.reservedDataStore.onBeforeRenderObserver = null;
-
-            this.setState({ displayAxis: false });
-
-            return;
-        }
-
-        const viewer = new AxesViewer(scene);
-        node.reservedDataStore.axisViewer = viewer;
-        const x = new Vector3(1, 0, 0);
-        const y = new Vector3(0, 1, 0);
-        const z = new Vector3(0, 0, 1);
-
-        viewer.xAxis.reservedDataStore = { hidden: true };
-        viewer.yAxis.reservedDataStore = { hidden: true };
-        viewer.zAxis.reservedDataStore = { hidden: true };
-
-        node.reservedDataStore.onBeforeRenderObserver = scene.onBeforeRenderObservable.add(() => {
-            let cameraMatrix = scene.activeCamera!.getWorldMatrix();
-            let matrix = node.getWorldMatrix();
-            let extend = Tmp.Vector3[0];
-            Vector3.TransformCoordinatesFromFloatsToRef(0, 0, 1, cameraMatrix, extend);
-
-            viewer.scaleLines = extend.length() / 10;
-            viewer.update(node.getAbsolutePosition(), Vector3.TransformNormal(x, matrix), Vector3.TransformNormal(y, matrix), Vector3.TransformNormal(z, matrix));
-        });
-
-        this.setState({ displayAxis: true });
-    }
-
-    render() {
-        return (
-            <CheckBoxLineComponent label="Display axes" isSelected={() => this.state.displayAxis} onSelect={() => this.displayAxes()} />
-        );
-    }
-}

+ 1 - 1
inspector/src/components/actionTabs/tabs/propertyGrids/meshes/bonePropertyGridComponent.tsx

@@ -36,7 +36,7 @@ export class BonePropertyGridComponent extends React.Component<IBonePropertyGrid
                     <Vector3LineComponent label="Position" target={bone} propertyName="position" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
                     {
                         !bone.rotationQuaternion &&
-                        <Vector3LineComponent label="Rotation" target={bone} propertyName="rotation" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                        <Vector3LineComponent label="Rotation" target={bone} propertyName="rotation" step={0.01} onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
                     }
                     {
                         bone.rotationQuaternion &&

+ 1 - 3
inspector/src/components/actionTabs/tabs/propertyGrids/meshes/meshPropertyGridComponent.tsx

@@ -16,7 +16,6 @@ import { CheckBoxLineComponent } from "../../../lines/checkBoxLineComponent";
 import { Vector3LineComponent } from "../../../lines/vector3LineComponent";
 import { SliderLineComponent } from "../../../lines/sliderLineComponent";
 import { QuaternionLineComponent } from "../../../lines/quaternionLineComponent";
-import { AxesViewerComponent } from "./axesViewerComponent";
 import { FloatLineComponent } from "../../../lines/floatLineComponent";
 import { LockObject } from "../lockObject";
 import { GlobalState } from '../../../../globalState';
@@ -222,7 +221,7 @@ export class MeshPropertyGridComponent extends React.Component<IMeshPropertyGrid
                     <Vector3LineComponent label="Position" target={mesh} propertyName="position" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
                     {
                         !mesh.rotationQuaternion &&
-                        <Vector3LineComponent label="Rotation" target={mesh} propertyName="rotation" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                        <Vector3LineComponent label="Rotation" target={mesh} propertyName="rotation" step={0.01} onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
                     }
                     {
                         mesh.rotationQuaternion &&
@@ -281,7 +280,6 @@ export class MeshPropertyGridComponent extends React.Component<IMeshPropertyGrid
                         mesh.isVerticesDataPresent(VertexBuffer.NormalKind) &&
                         <CheckBoxLineComponent label="Render vertex normals" isSelected={() => renderNormalVectors} onSelect={() => this.renderNormalVectors()} />
                     }
-                    <AxesViewerComponent globalState={this.props.globalState} node={mesh} />
                     <CheckBoxLineComponent label="Render wireframe over mesh" isSelected={() => renderWireframeOver} onSelect={() => this.renderWireframeOver()} />
                 </LineContainerComponent>
             </div>

+ 1 - 5
inspector/src/components/actionTabs/tabs/propertyGrids/meshes/transformNodePropertyGridComponent.tsx

@@ -9,7 +9,6 @@ import { CheckBoxLineComponent } from "../../../lines/checkBoxLineComponent";
 import { Vector3LineComponent } from "../../../lines/vector3LineComponent";
 import { TextLineComponent } from "../../../lines/textLineComponent";
 import { QuaternionLineComponent } from "../../../lines/quaternionLineComponent";
-import { AxesViewerComponent } from "./axesViewerComponent";
 import { LockObject } from "../lockObject";
 import { GlobalState } from '../../../../globalState';
 import { CustomPropertyGridComponent } from '../customPropertyGridComponent';
@@ -43,7 +42,7 @@ export class TransformNodePropertyGridComponent extends React.Component<ITransfo
                     <Vector3LineComponent label="Position" target={transformNode} propertyName="position" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
                     {
                         !transformNode.rotationQuaternion &&
-                        <Vector3LineComponent label="Rotation" target={transformNode} propertyName="rotation" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                        <Vector3LineComponent label="Rotation" target={transformNode} propertyName="rotation" step={0.01} onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
                     }
                     {
                         transformNode.rotationQuaternion &&
@@ -51,9 +50,6 @@ export class TransformNodePropertyGridComponent extends React.Component<ITransfo
                     }
                     <Vector3LineComponent label="Scaling" target={transformNode} propertyName="scaling" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
                 </LineContainerComponent>
-                <LineContainerComponent globalState={this.props.globalState} title="DEBUG" closed={true}>
-                    <AxesViewerComponent globalState={this.props.globalState} node={transformNode} />
-                </LineContainerComponent>
             </div>
         );
     }

+ 0 - 1
inspector/src/components/actionTabs/tabs/statisticsTabComponent.tsx

@@ -82,7 +82,6 @@ export class StatisticsTabComponent extends PaneComponent {
                     <TextLineComponent label="Active bones" value={scene.getActiveBones().toString()} />
                     <TextLineComponent label="Active particles" value={scene.getActiveParticles().toString()} />
                     <TextLineComponent label="Draw calls" value={sceneInstrumentation.drawCallsCounter.current.toString()} />
-                    <TextLineComponent label="Texture collisions" value={sceneInstrumentation.textureCollisionsCounter.current.toString()} />
                     <TextLineComponent label="Total lights" value={scene.lights.length.toString()} />
                     <TextLineComponent label="Total vertices" value={scene.getTotalVertices().toString()} />
                     <TextLineComponent label="Total materials" value={scene.materials.length.toString()} />

+ 4 - 1
inspector/src/components/sceneExplorer/entities/sceneTreeItemComponent.tsx

@@ -206,6 +206,9 @@ export class SceneTreeItemComponent extends React.Component<ISceneTreeItemCompon
                     break;
                 case 4:
                     manager.boundingBoxGizmoEnabled = true;
+                    if (manager.gizmos.boundingBoxGizmo) {
+                        manager.gizmos.boundingBoxGizmo.fixedDragMeshScreenSize = true;
+                    }
                     break;
             }
 
@@ -256,4 +259,4 @@ export class SceneTreeItemComponent extends React.Component<ISceneTreeItemCompon
             </div>
         );
     }
-}
+}

+ 22 - 16
inspector/src/components/sceneExplorer/sceneExplorerComponent.tsx

@@ -53,6 +53,7 @@ export class SceneExplorerComponent extends React.Component<ISceneExplorerCompon
     private _onNewSceneAddedObserver: Nullable<Observer<Scene>>;
 
     private _once = true;
+    private _hooked = false;
 
     private sceneMutationFunc: () => void;
 
@@ -91,6 +92,7 @@ export class SceneExplorerComponent extends React.Component<ISceneExplorerCompon
 
         const scene = this.state.scene;
 
+        scene.onNewSkeletonAddedObservable.removeCallback(this.sceneMutationFunc);
         scene.onNewCameraAddedObservable.removeCallback(this.sceneMutationFunc);
         scene.onNewLightAddedObservable.removeCallback(this.sceneMutationFunc);
         scene.onNewMaterialAddedObservable.removeCallback(this.sceneMutationFunc);
@@ -98,6 +100,7 @@ export class SceneExplorerComponent extends React.Component<ISceneExplorerCompon
         scene.onNewTextureAddedObservable.removeCallback(this.sceneMutationFunc);
         scene.onNewTransformNodeAddedObservable.removeCallback(this.sceneMutationFunc);
 
+        scene.onSkeletonRemovedObservable.removeCallback(this.sceneMutationFunc);
         scene.onMeshRemovedObservable.removeCallback(this.sceneMutationFunc);
         scene.onCameraRemovedObservable.removeCallback(this.sceneMutationFunc);
         scene.onLightRemovedObservable.removeCallback(this.sceneMutationFunc);
@@ -202,6 +205,25 @@ export class SceneExplorerComponent extends React.Component<ISceneExplorerCompon
             return null;
         }
 
+        if (!this._hooked) {
+            this._hooked = true;
+            scene.onNewSkeletonAddedObservable.add(this.sceneMutationFunc);
+            scene.onNewCameraAddedObservable.add(this.sceneMutationFunc);
+            scene.onNewLightAddedObservable.add(this.sceneMutationFunc);
+            scene.onNewMaterialAddedObservable.add(this.sceneMutationFunc);
+            scene.onNewMeshAddedObservable.add(this.sceneMutationFunc);
+            scene.onNewTextureAddedObservable.add(this.sceneMutationFunc);
+            scene.onNewTransformNodeAddedObservable.add(this.sceneMutationFunc);
+
+            scene.onSkeletonRemovedObservable.add(this.sceneMutationFunc);
+            scene.onMeshRemovedObservable.add(this.sceneMutationFunc);
+            scene.onCameraRemovedObservable.add(this.sceneMutationFunc);
+            scene.onLightRemovedObservable.add(this.sceneMutationFunc);
+            scene.onMaterialRemovedObservable.add(this.sceneMutationFunc);
+            scene.onTransformNodeRemovedObservable.add(this.sceneMutationFunc);
+            scene.onTextureRemovedObservable.add(this.sceneMutationFunc);
+        }
+
         let guiElements = scene.textures.filter((t) => t.getClassName() === "AdvancedDynamicTexture");
         let textures = scene.textures.filter((t) => t.getClassName() !== "AdvancedDynamicTexture");
         let postProcessses = scene.postProcesses;
@@ -282,22 +304,6 @@ export class SceneExplorerComponent extends React.Component<ISceneExplorerCompon
 
         if (this._once) {
             this._once = false;
-            const scene = this.state.scene;
-
-            scene.onNewCameraAddedObservable.add(this.sceneMutationFunc);
-            scene.onNewLightAddedObservable.add(this.sceneMutationFunc);
-            scene.onNewMaterialAddedObservable.add(this.sceneMutationFunc);
-            scene.onNewMeshAddedObservable.add(this.sceneMutationFunc);
-            scene.onNewTextureAddedObservable.add(this.sceneMutationFunc);
-            scene.onNewTransformNodeAddedObservable.add(this.sceneMutationFunc);
-
-            scene.onMeshRemovedObservable.add(this.sceneMutationFunc);
-            scene.onCameraRemovedObservable.add(this.sceneMutationFunc);
-            scene.onLightRemovedObservable.add(this.sceneMutationFunc);
-            scene.onMaterialRemovedObservable.add(this.sceneMutationFunc);
-            scene.onTransformNodeRemovedObservable.add(this.sceneMutationFunc);
-            scene.onTextureRemovedObservable.add(this.sceneMutationFunc);
-
             // A bit hacky but no other way to force the initial width to 300px and not auto
             setTimeout(() => {
                 const element = document.getElementById("sceneExplorer");

+ 11 - 7
loaders/src/glTF/2.0/Extensions/KHR_draco_mesh_compression.ts

@@ -27,11 +27,14 @@ export class KHR_draco_mesh_compression implements IGLTFLoaderExtension {
     /** The name of this extension. */
     public readonly name = NAME;
 
+    /** The draco compression used to decode vertex data. */
+    public dracoCompression?: DracoCompression;
+
     /** Defines whether this extension is enabled. */
     public enabled = DracoCompression.DecoderAvailable;
 
     private _loader: GLTFLoader;
-    private _dracoCompression?: DracoCompression;
+    private _dracoCompressionOwned = false;
 
     /** @hidden */
     constructor(loader: GLTFLoader) {
@@ -40,9 +43,9 @@ export class KHR_draco_mesh_compression implements IGLTFLoaderExtension {
 
     /** @hidden */
     public dispose(): void {
-        if (this._dracoCompression) {
-            this._dracoCompression.dispose();
-            delete this._dracoCompression;
+        if (this.dracoCompression && this._dracoCompressionOwned) {
+            this.dracoCompression.dispose();
+            delete this.dracoCompression;
         }
 
         delete this._loader;
@@ -90,11 +93,12 @@ export class KHR_draco_mesh_compression implements IGLTFLoaderExtension {
             var bufferView = ArrayItem.Get(extensionContext, this._loader.gltf.bufferViews, extension.bufferView) as IBufferViewDraco;
             if (!bufferView._dracoBabylonGeometry) {
                 bufferView._dracoBabylonGeometry = this._loader.loadBufferViewAsync(`#/bufferViews/${bufferView.index}`, bufferView).then((data) => {
-                    if (!this._dracoCompression) {
-                        this._dracoCompression = new DracoCompression();
+                    if (!this.dracoCompression) {
+                        this.dracoCompression = new DracoCompression();
+                        this._dracoCompressionOwned = true;
                     }
 
-                    return this._dracoCompression.decodeMeshAsync(data, attributes).then((babylonVertexData) => {
+                    return this.dracoCompression.decodeMeshAsync(data, attributes).then((babylonVertexData) => {
                         const babylonGeometry = new Geometry(babylonMesh.name, this._loader.babylonScene);
                         babylonVertexData.applyToGeometry(babylonGeometry);
                         return babylonGeometry;

+ 56 - 6
loaders/src/glTF/2.0/glTFLoader.ts

@@ -1,7 +1,7 @@
 import { IndicesArray, Nullable } from "babylonjs/types";
 import { Deferred } from "babylonjs/Misc/deferred";
 import { Quaternion, Color3, Vector3, Matrix } from "babylonjs/Maths/math";
-import { LoadFileError, IFileRequest, Tools } from "babylonjs/Misc/tools";
+import { LoadFileError, IFileRequest, IAnimatable, Tools } from "babylonjs/Misc/tools";
 import { Camera } from "babylonjs/Cameras/camera";
 import { FreeCamera } from "babylonjs/Cameras/freeCamera";
 import { AnimationGroup } from "babylonjs/Animations/animationGroup";
@@ -273,6 +273,10 @@ export class GLTFLoader implements IGLTFLoader {
 
             const promises = new Array<Promise<any>>();
 
+            // Block the marking of materials dirty until the scene is loaded.
+            const oldBlockMaterialDirtyMechanism = this._babylonScene.blockMaterialDirtyMechanism;
+            this._babylonScene.blockMaterialDirtyMechanism = true;
+
             if (nodes) {
                 promises.push(this.loadSceneAsync("/nodes", { nodes: nodes, index: -1 }));
             }
@@ -281,6 +285,9 @@ export class GLTFLoader implements IGLTFLoader {
                 promises.push(this.loadSceneAsync(`/scenes/${scene.index}`, scene));
             }
 
+            // Restore the blocking of material dirty.
+            this._babylonScene.blockMaterialDirtyMechanism = oldBlockMaterialDirtyMechanism;
+
             if (this._parent.compileMaterials) {
                 promises.push(this._compileMaterialsAsync());
             }
@@ -690,7 +697,22 @@ export class GLTFLoader implements IGLTFLoader {
         });
     }
 
-    private _loadMeshPrimitiveAsync(context: string, name: string, node: INode, mesh: IMesh, primitive: IMeshPrimitive, assign: (babylonMesh: AbstractMesh) => void): Promise<AbstractMesh> {
+    /**
+     * @hidden Define this method to modify the default behavior when loading data for mesh primitives.
+     * @param context The context when loading the asset
+     * @param name The mesh name when loading the asset
+     * @param node The glTF node when loading the asset
+     * @param mesh The glTF mesh when loading the asset
+     * @param primitive The glTF mesh primitive property
+     * @param assign A function called synchronously after parsing the glTF properties
+     * @returns A promise that resolves with the loaded mesh when the load is complete or null if not handled
+     */
+    public _loadMeshPrimitiveAsync(context: string, name: string, node: INode, mesh: IMesh, primitive: IMeshPrimitive, assign: (babylonMesh: AbstractMesh) => void): Promise<AbstractMesh> {
+        const extensionPromise = this._extensionsLoadMeshPrimitiveAsync(context, name, node, mesh, primitive, assign);
+        if (extensionPromise) {
+            return extensionPromise;
+        }
+
         this.logOpen(`${context}`);
 
         const canInstance = (node.skin == undefined && !mesh.primitives[0].targets);
@@ -932,6 +954,11 @@ export class GLTFLoader implements IGLTFLoader {
     }
 
     private _loadSkinAsync(context: string, node: INode, skin: ISkin): Promise<void> {
+        const extensionPromise = this._extensionsLoadSkinAsync(context, node, skin);
+        if (extensionPromise) {
+            return extensionPromise;
+        }
+
         const assignSkeleton = (skeleton: Skeleton) => {
             this._forEachPrimitive(node, (babylonMesh) => {
                 babylonMesh.skeleton = skeleton;
@@ -1137,7 +1164,17 @@ export class GLTFLoader implements IGLTFLoader {
         });
     }
 
-    private _loadAnimationChannelAsync(context: string, animationContext: string, animation: IAnimation, channel: IAnimationChannel, babylonAnimationGroup: AnimationGroup): Promise<void> {
+    /**
+     * @hidden Loads a glTF animation channel.
+     * @param context The context when loading the asset
+     * @param animationContext The context of the animation when loading the asset
+     * @param animation The glTF animation property
+     * @param channel The glTF animation channel property
+     * @param babylonAnimationGroup The babylon animation group property
+     * @param animationTargetOverride The babylon animation channel target override property. My be null.
+     * @returns A void promise when the channel load is complete
+     */
+    public _loadAnimationChannelAsync(context: string, animationContext: string, animation: IAnimation, channel: IAnimationChannel, babylonAnimationGroup: AnimationGroup, animationTargetOverride: Nullable<IAnimatable> = null): Promise<void> {
         if (channel.target.node == undefined) {
             return Promise.resolve();
         }
@@ -1277,8 +1314,13 @@ export class GLTFLoader implements IGLTFLoader {
                 const babylonAnimation = new Animation(animationName, targetPath, 1, animationType);
                 babylonAnimation.setKeys(keys);
 
-                targetNode._babylonTransformNode!.animations.push(babylonAnimation);
-                babylonAnimationGroup.addTargetedAnimation(babylonAnimation, targetNode._babylonTransformNode!);
+                if (animationTargetOverride != null && animationTargetOverride.animations != null) {
+                    animationTargetOverride.animations.push(babylonAnimation);
+                    babylonAnimationGroup.addTargetedAnimation(babylonAnimation, animationTargetOverride);
+                } else {
+                    targetNode._babylonTransformNode!.animations.push(babylonAnimation);
+                    babylonAnimationGroup.addTargetedAnimation(babylonAnimation, targetNode._babylonTransformNode!);
+                }
             }
         });
     }
@@ -2139,6 +2181,10 @@ export class GLTFLoader implements IGLTFLoader {
         return this._applyExtensions(primitive, "loadVertexData", (extension) => extension._loadVertexDataAsync && extension._loadVertexDataAsync(context, primitive, babylonMesh));
     }
 
+    private _extensionsLoadMeshPrimitiveAsync(context: string, name: string, node: INode, mesh: IMesh, primitive: IMeshPrimitive, assign: (babylonMesh: AbstractMesh) => void): Nullable<Promise<AbstractMesh>> {
+        return this._applyExtensions(primitive, "loadMeshPrimitive", (extension) => extension._loadMeshPrimitiveAsync && extension._loadMeshPrimitiveAsync(context, name, node, mesh, primitive, assign));
+    }
+
     private _extensionsLoadMaterialAsync(context: string, material: IMaterial, babylonMesh: Mesh, babylonDrawMode: number, assign: (babylonMaterial: Material) => void): Nullable<Promise<Material>> {
         return this._applyExtensions(material, "loadMaterial", (extension) => extension._loadMaterialAsync && extension._loadMaterialAsync(context, material, babylonMesh, babylonDrawMode, assign));
     }
@@ -2159,6 +2205,10 @@ export class GLTFLoader implements IGLTFLoader {
         return this._applyExtensions(animation, "loadAnimation", (extension) => extension.loadAnimationAsync && extension.loadAnimationAsync(context, animation));
     }
 
+    private _extensionsLoadSkinAsync(context: string, node: INode, skin: ISkin): Nullable<Promise<void>> {
+        return this._applyExtensions(skin, "loadSkin", (extension) => extension._loadSkinAsync && extension._loadSkinAsync(context, node, skin));
+    }
+
     private _extensionsLoadUriAsync(context: string, property: IProperty, uri: string): Nullable<Promise<ArrayBufferView>> {
         return this._applyExtensions(property, "loadUri", (extension) => extension._loadUriAsync && extension._loadUriAsync(context, property, uri));
     }
@@ -2249,4 +2299,4 @@ export class GLTFLoader implements IGLTFLoader {
     }
 }
 
-GLTFFileLoader._CreateGLTF2Loader = (parent) => new GLTFLoader(parent);
+GLTFFileLoader._CreateGLTF2Loader = (parent) => new GLTFLoader(parent);

+ 24 - 2
loaders/src/glTF/2.0/glTFLoaderExtension.ts

@@ -6,9 +6,10 @@ import { Geometry } from "babylonjs/Meshes/geometry";
 import { TransformNode } from "babylonjs/Meshes/transformNode";
 import { BaseTexture } from "babylonjs/Materials/Textures/baseTexture";
 import { Mesh } from "babylonjs/Meshes/mesh";
+import { AbstractMesh } from "babylonjs/Meshes/abstractMesh";
 import { IDisposable } from "babylonjs/scene";
 
-import { IScene, INode, ICamera, IMeshPrimitive, IMaterial, ITextureInfo, IAnimation } from "./glTFLoaderInterfaces";
+import { IScene, INode, IMesh, ISkin, ICamera, IMeshPrimitive, IMaterial, ITextureInfo, IAnimation } from "./glTFLoaderInterfaces";
 import { IGLTFLoaderExtension as IGLTFBaseLoaderExtension } from "../glTFFileLoader";
 import { IProperty } from 'babylonjs-gltf2interface';
 
@@ -61,6 +62,18 @@ export interface IGLTFLoaderExtension extends IGLTFBaseLoaderExtension, IDisposa
     _loadVertexDataAsync?(context: string, primitive: IMeshPrimitive, babylonMesh: Mesh): Nullable<Promise<Geometry>>;
 
     /**
+     * @hidden Define this method to modify the default behavior when loading data for mesh primitives.
+     * @param context The context when loading the asset
+     * @param name The mesh name when loading the asset
+     * @param node The glTF node when loading the asset
+     * @param mesh The glTF mesh when loading the asset
+     * @param primitive The glTF mesh primitive property
+     * @param assign A function called synchronously after parsing the glTF properties
+     * @returns A promise that resolves with the loaded mesh when the load is complete or null if not handled
+     */
+    _loadMeshPrimitiveAsync?(context: string, name: string, node: INode, mesh: IMesh, primitive: IMeshPrimitive, assign: (babylonMesh: AbstractMesh) => void): Promise<AbstractMesh>;
+
+    /**
      * @hidden Define this method to modify the default behavior when loading materials. Load material creates the material and then loads material properties.
      * @param context The context when loading the asset
      * @param material The glTF material property
@@ -105,7 +118,16 @@ export interface IGLTFLoaderExtension extends IGLTFBaseLoaderExtension, IDisposa
     loadAnimationAsync?(context: string, animation: IAnimation): Nullable<Promise<AnimationGroup>>;
 
     /**
-     * Define this method to modify the default behavior when loading uris.
+     * @hidden Define this method to modify the default behavior when loading skins.
+     * @param context The context when loading the asset
+     * @param node The glTF node property
+     * @param skin The glTF skin property
+     * @returns A promise that resolves when the load is complete or null if not handled
+     */
+    _loadSkinAsync?(context: string, node: INode, skin: ISkin): Nullable<Promise<void>>;
+
+    /**
+     * @hidden Define this method to modify the default behavior when loading uris.
      * @param context The context when loading the asset
      * @param property The glTF property associated with the uri
      * @param uri The uri to load

+ 6 - 5
package.json

@@ -9,7 +9,7 @@
     ],
     "name": "babylonjs",
     "description": "Babylon.js is a JavaScript 3D engine based on webgl.",
-    "version": "4.0.0-beta.3",
+    "version": "4.0.0-beta.4",
     "repository": {
         "type": "git",
         "url": "https://github.com/BabylonJS/Babylon.js.git"
@@ -68,7 +68,7 @@
         "gulp-rename": "^1.4.0",
         "gulp-replace": "~1.0.0",
         "gulp-tslint": "^8.1.3",
-        "gulp-typedoc": "^2.2.1",
+        "gulp-typedoc": "^2.2.2",
         "gulp-typescript": "4.0.2",
         "karma": "^4.0.1",
         "karma-browserstack-launcher": "^1.4.0",
@@ -97,13 +97,14 @@
         "ts-loader": "^5.2.1",
         "tslib": "^1.9.3",
         "tslint": "^5.11.0",
-        "typedoc": "^0.12.0",
-        "typescript": "^3.3.3",
+        "typedoc": "^0.14.2",
+        "typescript": "~3.4.1",
         "webpack": "^4.29.3",
+        "webpack-bundle-analyzer": "^3.1.0",
         "webpack-cli": "^3.1.2",
         "webpack-dev-server": "^3.1.14",
         "webpack-stream": "5.0.0",
         "xhr2": "^0.1.4",
         "xmlbuilder": "8.2.2"
     }
-}
+}

+ 91 - 0
sandbox/debug.html

@@ -0,0 +1,91 @@
+<!DOCTYPE html>
+<html xmlns="http://www.w3.org/1999/xhtml">
+
+<head>
+    <title>BabylonJS Sandbox - View glTF, glb, obj and babylon files</title>
+    <meta name="description" content="Viewer for glTF, glb, obj and babylon files powered by BabylonJS" />
+    <meta name="keywords"
+        content="Babylon.js, Babylon, BabylonJS, glTF, glb, obj, viewer, online viewer, 3D model viewer, 3D, webgl" />
+    <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1">
+
+    <link rel="shortcut icon" href="https://www.babylonjs.com/img/favicon/favicon.ico">
+    <link rel="apple-touch-icon" sizes="57x57" href="https://www.babylonjs.com/img/favicon/apple-icon-57x57.png">
+    <link rel="apple-touch-icon" sizes="60x60" href="https://www.babylonjs.com/img/favicon/apple-icon-60x60.png">
+    <link rel="apple-touch-icon" sizes="72x72" href="https://www.babylonjs.com/img/favicon/apple-icon-72x72.png">
+    <link rel="apple-touch-icon" sizes="76x76" href="https://www.babylonjs.com/img/favicon/apple-icon-76x76.png">
+    <link rel="apple-touch-icon" sizes="114x114" href="https://www.babylonjs.com/img/favicon/apple-icon-114x114.png">
+    <link rel="apple-touch-icon" sizes="120x120" href="https://www.babylonjs.com/img/favicon/apple-icon-120x120.png">
+    <link rel="apple-touch-icon" sizes="144x144" href="https://www.babylonjs.com/img/favicon/apple-icon-144x144.png">
+    <link rel="apple-touch-icon" sizes="152x152" href="https://www.babylonjs.com/img/favicon/apple-icon-152x152.png">
+    <link rel="apple-touch-icon" sizes="180x180" href="https://www.babylonjs.com/img/favicon/apple-icon-180x180.png">
+    <link rel="icon" type="image/png" sizes="192x192"
+        href="https://www.babylonjs.com/img/favicon/android-icon-192x192.png">
+    <link rel="icon" type="image/png" sizes="32x32" href="https://www.babylonjs.com/img/favicon/favicon-32x32.png">
+    <link rel="icon" type="image/png" sizes="96x96" href="https://www.babylonjs.com/img/favicon/favicon-96x96.png">
+    <link rel="icon" type="image/png" sizes="16x16" href="https://www.babylonjs.com/img/favicon/favicon-16x16.png">
+    <link rel="manifest" href="https://www.babylonjs.com/img/favicon/manifest.json">
+    <meta name="msapplication-TileColor" content="#ffffff">
+    <meta name="msapplication-TileImage" content="https://www.babylonjs.com/img/favicon/ms-icon-144x144.png">
+    <meta name="msapplication-config" content="https://www.babylonjs.com/img/favicon/browserconfig.xml">
+    <meta name="theme-color" content="#ffffff">
+
+    <link href="index.css" rel="stylesheet" />
+    <script src="https://code.jquery.com/pep/0.4.2/pep.min.js"></script>
+    <script src="https://playground.babylonjs.com/js/libs/split.js"></script>
+
+    <script src="https://preview.babylonjs.com/ammo.js"></script>
+    <script src="https://preview.babylonjs.com/cannon.js"></script>
+    <script src="https://preview.babylonjs.com/Oimo.js"></script>
+    <script src="https://preview.babylonjs.com/gltf_validator.js"></script>
+    <script src="https://preview.babylonjs.com/babylon.max.js"></script>
+    <script src="https://preview.babylonjs.com/inspector/babylon.inspector.bundle.js"></script>
+
+    <script src="https://preview.babylonjs.com/loaders/babylonjs.loaders.js"></script>
+    <script src="https://preview.babylonjs.com/serializers/babylonjs.serializers.js"></script>
+    <script src="https://preview.babylonjs.com/materialsLibrary/babylonjs.materials.js"></script>
+</head>
+
+<body>
+    <div id="root">
+        <p id="droptext">Drag and drop gltf, glb, obj or babylon files to view them</p>
+        <div id="canvasZone">
+            <canvas id="renderCanvas" touch-action="none"></canvas>
+        </div>
+        <div id="footer" class="footer">
+            <div id="animationBar">
+                <div class="dropdown">
+                    <div id="dropdownBtn">
+                        <img src="Assets/Icon_Up.svg" id="chevronUp">
+                        <img src="Assets/Icon_Down.svg" id="chevronDown">
+                        <span id="dropdownLabel"></span>
+                    </div>
+                    <div id="dropdownContent">
+                    </div>
+                </div>
+                <div class="row">
+                    <button id="playBtn" class="pause">
+                        <img id="playImg" src="Assets/Icon_Play.svg">
+                        <img id="pauseImg" src="Assets/Icon_Pause.svg">
+                    </button>
+                    <input id="slider" type="range" min="0" max="100" value="0" step="any">
+                </div>
+            </div>
+            <div class="footerRight">
+                <a href="javascript:void(null);" id="btnInspector" class="hidden"><img src="./Assets/Icon_EditModel.svg"
+                        alt="Display inspector" title="Display inspector" /></a>
+                <a href="javascript:void(null);">
+                    <div class="custom-upload"
+                        title="Open your scene from your hard drive (.babylon, .gltf, .glb, .obj)">
+                        <input type="file" id="files" multiple />
+                    </div>
+                </a>
+            </div>
+        </div>
+        <div id="logo">
+        </div>
+        <div id="errorZone"></div>
+    </div>
+    <script src="index.js"></script>
+</body>
+
+</html>

+ 2 - 1
sandbox/index-local.html

@@ -41,7 +41,8 @@
                 <a href="javascript:void(null);" id="btnInspector" class="hidden"><img src="./Assets/Icon_EditModel.svg"
                         alt="Display inspector" title="Display inspector" /></a>
                 <a href="javascript:void(null);">
-                    <div class="custom-upload" title="Open your scene from your hard drive (.babylon, .gltf, .glb, .obj)">
+                    <div class="custom-upload"
+                        title="Open your scene from your hard drive (.babylon, .gltf, .glb, .obj)">
                         <input type="file" id="files" multiple />
                     </div>
                 </a>

+ 1 - 1
sandbox/index.js

@@ -112,7 +112,7 @@ if (BABYLON.Engine.isSupported()) {
             }
             currentGroup = babylonScene.animationGroups[0];
             currentGroupIndex = 0;
-            document.getElementById(formatId(currentGroup.name + "-" + currentGroupIndex)).click();
+            currentGroup.play(true);
         }
 
         // Sync the slider with the current frame

+ 6 - 1
serializers/src/glTF/2.0/glTFAnimation.ts

@@ -395,7 +395,12 @@ export class _GLTFAnimation {
                     }
                     previousTime = time;
                     maxUsedFrame = time;
-                    value = animation._interpolate(f, 0, undefined, animation.loopMode);
+                    let state = {
+                        key: 0,
+                        repeatCount: 0,
+                        loopMode: animation.loopMode
+                    };
+                    value = animation._interpolate(f, state);
 
                     _GLTFAnimation._SetInterpolatedValue(babylonTransformNode, value, time, animation, animationChannelTargetPath, quaternionCache, inputs, outputs, convertToRightHandedSystem, useQuaternion);
                 }

+ 27 - 17
src/Animations/animatable.ts

@@ -20,7 +20,7 @@ export class Animatable {
     private _scene: Scene;
     private _speedRatio = 1;
     private _weight = -1.0;
-    private _syncRoot: Animatable;
+    private _syncRoot: Nullable<Animatable> = null;
 
     /**
      * Gets or sets a boolean indicating if the animatable must be disposed and removed at the end of the animation.
@@ -46,7 +46,7 @@ export class Animatable {
     /**
      * Gets the root Animatable used to synchronize and normalize animations
      */
-    public get syncRoot(): Animatable {
+    public get syncRoot(): Nullable<Animatable> {
         return this._syncRoot;
     }
 
@@ -170,7 +170,15 @@ export class Animatable {
         for (var index = 0; index < animations.length; index++) {
             var animation = animations[index];
 
-            this._runtimeAnimations.push(new RuntimeAnimation(target, animation, this._scene, this));
+            let newRuntimeAnimation = new RuntimeAnimation(target, animation, this._scene, this);
+            newRuntimeAnimation._onLoop = () => {
+                this.onAnimationLoopObservable.notifyObservers(this);
+                if (this.onAnimationLoop) {
+                    this.onAnimationLoop();
+                }
+            };
+
+            this._runtimeAnimations.push(newRuntimeAnimation);
         }
     }
 
@@ -259,7 +267,7 @@ export class Animatable {
             var fps = runtimeAnimations[0].animation.framePerSecond;
             var currentFrame = runtimeAnimations[0].currentFrame;
             var adjustTime = frame - currentFrame;
-            var delay = adjustTime * 1000 / (fps * this.speedRatio);
+            var delay = this.speedRatio !== 0 ? adjustTime * 1000 / (fps * this.speedRatio) : 0;
             if (this._localDelayOffset === null) {
                 this._localDelayOffset = 0;
             }
@@ -387,13 +395,7 @@ export class Animatable {
         for (index = 0; index < runtimeAnimations.length; index++) {
             var animation = runtimeAnimations[index];
             var isRunning = animation.animate(delay - this._localDelayOffset, this.fromFrame,
-                this.toFrame, this.loopAnimation, this._speedRatio, this._weight,
-                () => {
-                    this.onAnimationLoopObservable.notifyObservers(this);
-                    if (this.onAnimationLoop) {
-                        this.onAnimationLoop();
-                    }
-                }
+                this.toFrame, this.loopAnimation, this._speedRatio, this._weight
             );
             running = running || isRunning;
         }
@@ -561,7 +563,12 @@ declare module "../scene" {
 }
 
 Scene.prototype._animate = function(): void {
-    if (!this.animationsEnabled || this._activeAnimatables.length === 0) {
+    if (!this.animationsEnabled) {
+        return;
+    }
+
+    const animatables = this._activeAnimatables;
+    if (animatables.length === 0) {
         return;
     }
 
@@ -573,11 +580,14 @@ Scene.prototype._animate = function(): void {
         }
         this._animationTimeLast = now;
     }
+
     var deltaTime = this.useConstantAnimationDeltaTime ? 16.0 : (now - this._animationTimeLast) * this.animationTimeScale;
     this._animationTime += deltaTime;
+    const animationTime = this._animationTime;
     this._animationTimeLast = now;
-    for (var index = 0; index < this._activeAnimatables.length; index++) {
-        this._activeAnimatables[index]._animate(this._animationTime);
+
+    for (let index = 0; index < animatables.length; index++) {
+        animatables[index]._animate(animationTime);
     }
 
     // Late animation bindings
@@ -781,9 +791,9 @@ Scene.prototype._processLateAnimationBindingsForMatrices = function(holder: {
         currentQuaternion.scaleAndAddToRef(scale, finalQuaternion);
         currentPosition.scaleAndAddToRef(scale, finalPosition);
     }
-
-    Matrix.ComposeToRef(finalScaling, finalQuaternion, finalPosition, originalAnimation._workValue);
-    return originalAnimation._workValue;
+    let workValue = originalAnimation._animationState.workValue;
+    Matrix.ComposeToRef(finalScaling, finalQuaternion, finalPosition, workValue);
+    return workValue;
 };
 
 Scene.prototype._processLateAnimationBindingsForQuaternions = function(holder: {

+ 32 - 20
src/Animations/animation.ts

@@ -16,6 +16,18 @@ declare type Animatable = import("./animatable").Animatable;
 declare type RuntimeAnimation = import("./runtimeAnimation").RuntimeAnimation;
 
 /**
+ * @hidden
+ */
+export class _IAnimationState {
+    key: number;
+    repeatCount: number;
+    workValue?: any;
+    loopMode?: number;
+    offsetValue?: any;
+    highLimitValue?: any;
+}
+
+/**
  * Class used to store any kind of animation
  */
 export class Animation {
@@ -571,18 +583,17 @@ export class Animation {
     /**
      * @hidden Internal use only
      */
-    public _interpolate(currentFrame: number, repeatCount: number, workValue?: any, loopMode?: number, offsetValue?: any, highLimitValue?: any): any {
-        if (loopMode === Animation.ANIMATIONLOOPMODE_CONSTANT && repeatCount > 0) {
-            return highLimitValue.clone ? highLimitValue.clone() : highLimitValue;
+    public _interpolate(currentFrame: number, state: _IAnimationState): any {
+        if (state.loopMode === Animation.ANIMATIONLOOPMODE_CONSTANT && state.repeatCount > 0) {
+            return state.highLimitValue.clone ? state.highLimitValue.clone() : state.highLimitValue;
         }
 
-        const keys = this.getKeys();
+        const keys = this._keys;
         if (keys.length === 1) {
             return this._getKeyValue(keys[0].value);
         }
 
-        // Try to get a hash to find the right key
-        var startKeyIndex = Math.max(0, Math.min(keys.length - 1, Math.floor(keys.length * (currentFrame - keys[0].frame) / (keys[keys.length - 1].frame - keys[0].frame)) - 1));
+        var startKeyIndex = state.key;
 
         if (keys[startKeyIndex].frame >= currentFrame) {
             while (startKeyIndex - 1 >= 0 && keys[startKeyIndex].frame >= currentFrame) {
@@ -594,6 +605,7 @@ export class Animation {
             var endKey = keys[key + 1];
 
             if (endKey.frame >= currentFrame) {
+                state.key = key;
                 var startKey = keys[key];
                 var startValue = this._getKeyValue(startKey.value);
                 if (startKey.interpolation === AnimationKeyInterpolation.STEP) {
@@ -618,71 +630,71 @@ export class Animation {
                     // Float
                     case Animation.ANIMATIONTYPE_FLOAT:
                         var floatValue = useTangent ? this.floatInterpolateFunctionWithTangents(startValue, startKey.outTangent * frameDelta, endValue, endKey.inTangent * frameDelta, gradient) : this.floatInterpolateFunction(startValue, endValue, gradient);
-                        switch (loopMode) {
+                        switch (state.loopMode) {
                             case Animation.ANIMATIONLOOPMODE_CYCLE:
                             case Animation.ANIMATIONLOOPMODE_CONSTANT:
                                 return floatValue;
                             case Animation.ANIMATIONLOOPMODE_RELATIVE:
-                                return offsetValue * repeatCount + floatValue;
+                                return state.offsetValue * state.repeatCount + floatValue;
                         }
                         break;
                     // Quaternion
                     case Animation.ANIMATIONTYPE_QUATERNION:
                         var quatValue = useTangent ? this.quaternionInterpolateFunctionWithTangents(startValue, startKey.outTangent.scale(frameDelta), endValue, endKey.inTangent.scale(frameDelta), gradient) : this.quaternionInterpolateFunction(startValue, endValue, gradient);
-                        switch (loopMode) {
+                        switch (state.loopMode) {
                             case Animation.ANIMATIONLOOPMODE_CYCLE:
                             case Animation.ANIMATIONLOOPMODE_CONSTANT:
                                 return quatValue;
                             case Animation.ANIMATIONLOOPMODE_RELATIVE:
-                                return quatValue.addInPlace(offsetValue.scale(repeatCount));
+                                return quatValue.addInPlace(state.offsetValue.scale(state.repeatCount));
                         }
 
                         return quatValue;
                     // Vector3
                     case Animation.ANIMATIONTYPE_VECTOR3:
                         var vec3Value = useTangent ? this.vector3InterpolateFunctionWithTangents(startValue, startKey.outTangent.scale(frameDelta), endValue, endKey.inTangent.scale(frameDelta), gradient) : this.vector3InterpolateFunction(startValue, endValue, gradient);
-                        switch (loopMode) {
+                        switch (state.loopMode) {
                             case Animation.ANIMATIONLOOPMODE_CYCLE:
                             case Animation.ANIMATIONLOOPMODE_CONSTANT:
                                 return vec3Value;
                             case Animation.ANIMATIONLOOPMODE_RELATIVE:
-                                return vec3Value.add(offsetValue.scale(repeatCount));
+                                return vec3Value.add(state.offsetValue.scale(state.repeatCount));
                         }
                     // Vector2
                     case Animation.ANIMATIONTYPE_VECTOR2:
                         var vec2Value = useTangent ? this.vector2InterpolateFunctionWithTangents(startValue, startKey.outTangent.scale(frameDelta), endValue, endKey.inTangent.scale(frameDelta), gradient) : this.vector2InterpolateFunction(startValue, endValue, gradient);
-                        switch (loopMode) {
+                        switch (state.loopMode) {
                             case Animation.ANIMATIONLOOPMODE_CYCLE:
                             case Animation.ANIMATIONLOOPMODE_CONSTANT:
                                 return vec2Value;
                             case Animation.ANIMATIONLOOPMODE_RELATIVE:
-                                return vec2Value.add(offsetValue.scale(repeatCount));
+                                return vec2Value.add(state.offsetValue.scale(state.repeatCount));
                         }
                     // Size
                     case Animation.ANIMATIONTYPE_SIZE:
-                        switch (loopMode) {
+                        switch (state.loopMode) {
                             case Animation.ANIMATIONLOOPMODE_CYCLE:
                             case Animation.ANIMATIONLOOPMODE_CONSTANT:
                                 return this.sizeInterpolateFunction(startValue, endValue, gradient);
                             case Animation.ANIMATIONLOOPMODE_RELATIVE:
-                                return this.sizeInterpolateFunction(startValue, endValue, gradient).add(offsetValue.scale(repeatCount));
+                                return this.sizeInterpolateFunction(startValue, endValue, gradient).add(state.offsetValue.scale(state.repeatCount));
                         }
                     // Color3
                     case Animation.ANIMATIONTYPE_COLOR3:
-                        switch (loopMode) {
+                        switch (state.loopMode) {
                             case Animation.ANIMATIONLOOPMODE_CYCLE:
                             case Animation.ANIMATIONLOOPMODE_CONSTANT:
                                 return this.color3InterpolateFunction(startValue, endValue, gradient);
                             case Animation.ANIMATIONLOOPMODE_RELATIVE:
-                                return this.color3InterpolateFunction(startValue, endValue, gradient).add(offsetValue.scale(repeatCount));
+                                return this.color3InterpolateFunction(startValue, endValue, gradient).add(state.offsetValue.scale(state.repeatCount));
                         }
                     // Matrix
                     case Animation.ANIMATIONTYPE_MATRIX:
-                        switch (loopMode) {
+                        switch (state.loopMode) {
                             case Animation.ANIMATIONLOOPMODE_CYCLE:
                             case Animation.ANIMATIONLOOPMODE_CONSTANT:
                                 if (Animation.AllowMatricesInterpolation) {
-                                    return this.matrixInterpolateFunction(startValue, endValue, gradient, workValue);
+                                    return this.matrixInterpolateFunction(startValue, endValue, gradient, state.workValue);
                                 }
                             case Animation.ANIMATIONLOOPMODE_RELATIVE:
                                 return startValue;

+ 8 - 2
src/Animations/animationGroup.ts

@@ -245,8 +245,14 @@ export class AnimationGroup implements IDisposable {
 
         this._speedRatio = speedRatio;
 
-        if (from !== undefined && to !== undefined && from > to && this._speedRatio > 0) {
-            this._speedRatio = -speedRatio;
+        if (from !== undefined && to !== undefined) {
+            if (from < to && this._speedRatio < 0) {
+                let temp = to;
+                to = from;
+                from = temp;
+            } else if (from > to && this._speedRatio > 0) {
+                this._speedRatio = -speedRatio;
+            }
         }
 
         this._isStarted = true;

+ 198 - 165
src/Animations/runtimeAnimation.ts

@@ -1,11 +1,12 @@
-import { DeepImmutable } from "../types";
+import { DeepImmutable, Nullable } from "../types";
 import { Quaternion, Vector3, Vector2, Size, Color3, Matrix } from "../Maths/math";
-import { Animation } from "./animation";
+import { Animation, _IAnimationState } from "./animation";
 import { AnimationEvent } from "./animationEvent";
 
 declare type Animatable = import("./animatable").Animatable;
 
 import { Scene } from "../scene";
+import { IAnimationKey } from './animationKey';
 
 // Static values to help the garbage collector
 
@@ -58,7 +59,7 @@ export class RuntimeAnimation {
     /**
      * The original blend value of the runtime animation
      */
-    private _originalBlendValue: any;
+    private _originalBlendValue: Nullable<any> = null;
 
     /**
      * The offsets cache of the runtime animation
@@ -88,15 +89,17 @@ export class RuntimeAnimation {
     /**
      * The current value of the runtime animation
      */
-    private _currentValue: any;
+    private _currentValue: Nullable<any> = null;
 
     /** @hidden */
-    public _workValue: any;
+    public _animationState: _IAnimationState;
 
     /**
      * The active target of the runtime animation
      */
-    private _activeTarget: any;
+    private _activeTargets: any[];
+    private _currentActiveTarget: Nullable<any> = null;
+    private _directTarget: Nullable<any> = null;
 
     /**
      * The target path of the runtime animation
@@ -123,6 +126,15 @@ export class RuntimeAnimation {
      */
     private _previousRatio: number = 0;
 
+    private _enableBlending: boolean;
+
+    private _keys: IAnimationKey[];
+    private _minFrame: number;
+    private _maxFrame: number;
+    private _minValue: any;
+    private _maxValue: any;
+    private _targetIsArray = false;
+
     /**
      * Gets the current frame of the runtime animation
      */
@@ -155,9 +167,12 @@ export class RuntimeAnimation {
      * Gets the actual target of the runtime animation
      */
     public get target(): any {
-        return this._activeTarget;
+        return this._currentActiveTarget;
     }
 
+    /** @hidden */
+    public _onLoop: () => void;
+
     /**
      * Create a new RuntimeAnimation object
      * @param target defines the target of the animation
@@ -170,9 +185,51 @@ export class RuntimeAnimation {
         this._target = target;
         this._scene = scene;
         this._host = host;
+        this._activeTargets = [];
 
         animation._runtimeAnimations.push(this);
 
+        // State
+        this._animationState = {
+            key: 0,
+            repeatCount: 0,
+            loopMode: this._getCorrectLoopMode()
+        };
+
+        if (this._animation.dataType === Animation.ANIMATIONTYPE_MATRIX) {
+            this._animationState.workValue = Matrix.Zero();
+        }
+
+        // Limits
+        this._keys = this._animation.getKeys();
+        this._minFrame = this._keys[0].frame;
+        this._maxFrame = this._keys[this._keys.length - 1].frame;
+        this._minValue = this._keys[0].value;
+        this._maxValue = this._keys[this._keys.length - 1].value;
+
+        // Add a start key at frame 0 if missing
+        if (this._minFrame !== 0) {
+            const newKey = { frame: 0, value: this._minValue };
+            this._keys.splice(0, 0, newKey);
+        }
+
+        // Check data
+        if (this._target instanceof Array) {
+            var index = 0;
+            for (const target of this._target) {
+                this._preparePath(target, index);
+                this._getOriginalValues(index);
+                index++;
+            }
+            this._targetIsArray = true;
+        }
+        else {
+            this._preparePath(this._target);
+            this._getOriginalValues();
+            this._targetIsArray = false;
+            this._directTarget = this._activeTargets[0];
+        }
+
         // Cloning events locally
         var events = animation.getEvents();
         if (events && events.length > 0) {
@@ -180,6 +237,26 @@ export class RuntimeAnimation {
                 this._events.push(e._clone());
             });
         }
+
+        this._enableBlending = target && target.animationPropertiesOverride ? target.animationPropertiesOverride.enableBlending : this._animation.enableBlending;
+    }
+
+    private _preparePath(target: any, targetIndex = 0) {
+        let targetPropertyPath = this._animation.targetPropertyPath;
+
+        if (targetPropertyPath.length > 1) {
+            var property = target[targetPropertyPath[0]];
+
+            for (var index = 1; index < targetPropertyPath.length - 1; index++) {
+                property = property[targetPropertyPath[index]];
+            }
+
+            this._targetPath = targetPropertyPath[targetPropertyPath.length - 1];
+            this._activeTargets[targetIndex] = property;
+        } else {
+            this._targetPath = targetPropertyPath[0];
+            this._activeTargets[targetIndex] = target;
+        }
     }
 
     /**
@@ -199,14 +276,14 @@ export class RuntimeAnimation {
                 var index = 0;
                 for (const target of this._target) {
                     if (this._originalValue[index] !== undefined) {
-                        this._setValue(target, this._originalValue[index], -1);
+                        this._setValue(target, this._activeTargets[index], this._originalValue[index], -1, index);
                     }
                     index++;
                 }
             }
             else {
                 if (this._originalValue[0] !== undefined) {
-                    this._setValue(this._target, this._originalValue[0], -1);
+                    this._setValue(this._target, this._directTarget, this._originalValue[0], -1, 0);
                 }
             }
         }
@@ -215,7 +292,6 @@ export class RuntimeAnimation {
         this._highLimitsCache = {};
         this._currentFrame = 0;
         this._blendingFactor = 0;
-        this._originalValue = new Array<any>();
 
         // Events
         for (var index = 0; index < this._events.length; index++) {
@@ -243,88 +319,47 @@ export class RuntimeAnimation {
     }
 
     /**
-     * Interpolates the animation from the current frame
-     * @param currentFrame The frame to interpolate the animation to
-     * @param repeatCount The number of times that the animation should loop
-     * @param loopMode The type of looping mode to use
-     * @param offsetValue Animation offset value
-     * @param highLimitValue The high limit value
-     * @returns The interpolated value
-     */
-    private _interpolate(currentFrame: number, repeatCount: number, loopMode?: number, offsetValue?: any, highLimitValue?: any): any {
-        this._currentFrame = currentFrame;
-
-        if (this._animation.dataType === Animation.ANIMATIONTYPE_MATRIX && !this._workValue) {
-            this._workValue = Matrix.Zero();
-        }
-
-        return this._animation._interpolate(currentFrame, repeatCount, this._workValue, loopMode, offsetValue, highLimitValue);
-    }
-
-    /**
      * Apply the interpolated value to the target
      * @param currentValue defines the value computed by the animation
      * @param weight defines the weight to apply to this value (Defaults to 1.0)
      */
-    public setValue(currentValue: any, weight = 1.0): void {
-        if (this._target instanceof Array) {
-            var index = 0;
-            for (const target of this._target) {
-                this._setValue(target, currentValue, weight, index);
-                index++;
+    public setValue(currentValue: any, weight: number) {
+        if (this._targetIsArray) {
+            for (var index = 0; index < this._target.length; index++) {
+                const target = this._target[index];
+                this._setValue(target, this._activeTargets[index], currentValue, weight, index);
             }
+            return;
         }
-        else {
-            this._setValue(this._target, currentValue, weight);
-        }
+        this._setValue(this._target, this._directTarget, currentValue, weight, 0);
     }
 
-    private _setValue(target: any, currentValue: any, weight: number, targetIndex = 0): void {
-        // Set value
-        var path: any;
-        var destination: any;
-
-        let targetPropertyPath = this._animation.targetPropertyPath;
-
-        if (targetPropertyPath.length > 1) {
-            var property = target[targetPropertyPath[0]];
-
-            for (var index = 1; index < targetPropertyPath.length - 1; index++) {
-                property = property[targetPropertyPath[index]];
-            }
+    private _getOriginalValues(targetIndex = 0) {
+        let originalValue: any;
+        let target = this._activeTargets[targetIndex];
 
-            path = targetPropertyPath[targetPropertyPath.length - 1];
-            destination = property;
+        if (target.getRestPose && this._targetPath === "_matrix") { // For bones
+            originalValue = target.getRestPose();
         } else {
-            path = targetPropertyPath[0];
-            destination = target;
+            originalValue = target[this._targetPath];
         }
 
-        this._targetPath = path;
-        this._activeTarget = destination;
-        this._weight = weight;
-
-        if (this._originalValue[targetIndex] === undefined) {
-            let originalValue: any;
+        if (originalValue && originalValue.clone) {
+            this._originalValue[targetIndex] = originalValue.clone();
+        } else {
+            this._originalValue[targetIndex] = originalValue;
+        }
+    }
 
-            if (destination.getRestPose && path === "_matrix") { // For bones
-                originalValue = destination.getRestPose();
-            } else {
-                originalValue = destination[path];
-            }
+    private _setValue(target: any, destination: any, currentValue: any, weight: number, targetIndex: number): void {
+        // Set value
+        this._currentActiveTarget = destination;
 
-            if (originalValue && originalValue.clone) {
-                this._originalValue[targetIndex] = originalValue.clone();
-            } else {
-                this._originalValue[targetIndex] = originalValue;
-            }
-        }
+        this._weight = weight;
 
-        // Blending
-        const enableBlending = target && target.animationPropertiesOverride ? target.animationPropertiesOverride.enableBlending : this._animation.enableBlending;
-        if (enableBlending && this._blendingFactor <= 1.0) {
+        if (this._enableBlending && this._blendingFactor <= 1.0) {
             if (!this._originalBlendValue) {
-                let originalValue = destination[path];
+                let originalValue = destination[this._targetPath];
 
                 if (originalValue.clone) {
                     this._originalBlendValue = originalValue.clone();
@@ -360,7 +395,7 @@ export class RuntimeAnimation {
         if (weight !== -1.0) {
             this._scene._registerTargetForLateAnimationBinding(this, this._originalValue[targetIndex]);
         } else {
-            destination[path] = this._currentValue;
+            destination[this._targetPath] = this._currentValue;
         }
 
         if (target.markAsDirty) {
@@ -393,7 +428,8 @@ export class RuntimeAnimation {
             frame = keys[keys.length - 1].frame;
         }
 
-        var currentValue = this._interpolate(frame, 0, this._getCorrectLoopMode());
+        this._currentFrame = frame;
+        var currentValue = this._animation._interpolate(frame, this._animationState);
 
         this.setValue(currentValue, -1);
     }
@@ -418,8 +454,9 @@ export class RuntimeAnimation {
      * @param onLoop optional callback called when animation loops
      * @returns a boolean indicating if the animation is running
      */
-    public animate(delay: number, from: number, to: number, loop: boolean, speedRatio: number, weight = -1.0, onLoop?: () => void): boolean {
-        let targetPropertyPath = this._animation.targetPropertyPath;
+    public animate(delay: number, from: number, to: number, loop: boolean, speedRatio: number, weight = -1.0): boolean {
+        let animation = this._animation;
+        let targetPropertyPath = animation.targetPropertyPath;
         if (!targetPropertyPath || targetPropertyPath.length < 1) {
             this._stopped = true;
             return false;
@@ -427,84 +464,73 @@ export class RuntimeAnimation {
 
         let returnValue = true;
 
-        let keys = this._animation.getKeys();
-        let min = keys[0].frame;
-        let max = keys[keys.length - 1].frame;
-
-        // Add a start key at frame 0 if missing
-        if (min !== 0) {
-            const newKey = { frame: 0, value: keys[0].value };
-            keys.splice(0, 0, newKey);
-        }
-
         // Check limits
-        if (from < min || from > max) {
-            from = min;
+        if (from < this._minFrame || from > this._maxFrame) {
+            from = this._minFrame;
         }
-        if (to < min || to > max) {
-            to = max;
+        if (to < this._minFrame || to > this._maxFrame) {
+            to = this._maxFrame;
         }
 
         const range = to - from;
         let offsetValue: any;
 
         // Compute ratio which represents the frame delta between from and to
-        const ratio = (delay * (this._animation.framePerSecond * speedRatio) / 1000.0) + this._ratioOffset;
+        const ratio = (delay * (animation.framePerSecond * speedRatio) / 1000.0) + this._ratioOffset;
         let highLimitValue = 0;
 
         this._previousDelay = delay;
         this._previousRatio = ratio;
 
-        if ((to > from && ratio >= range) && !loop) { // If we are out of range and not looping get back to caller
+        if (!loop && (to > from && ratio >= range)) { // If we are out of range and not looping get back to caller
             returnValue = false;
-            highLimitValue = this._animation._getKeyValue(keys[keys.length - 1].value);
-        } else if ((from > to && ratio <= range) && !loop) {
+            highLimitValue = animation._getKeyValue(this._maxValue);
+        } else if (!loop && (from > to && ratio <= range)) {
             returnValue = false;
-            highLimitValue = this._animation._getKeyValue(keys[0].value);
-        } else {
-            // Get max value if required
-
-            if (this._getCorrectLoopMode() !== Animation.ANIMATIONLOOPMODE_CYCLE) {
-
-                var keyOffset = to.toString() + from.toString();
-                if (!this._offsetsCache[keyOffset]) {
-                    var fromValue = this._interpolate(from, 0, Animation.ANIMATIONLOOPMODE_CYCLE);
-                    var toValue = this._interpolate(to, 0, Animation.ANIMATIONLOOPMODE_CYCLE);
-                    switch (this._animation.dataType) {
-                        // Float
-                        case Animation.ANIMATIONTYPE_FLOAT:
-                            this._offsetsCache[keyOffset] = toValue - fromValue;
-                            break;
-                        // Quaternion
-                        case Animation.ANIMATIONTYPE_QUATERNION:
-                            this._offsetsCache[keyOffset] = toValue.subtract(fromValue);
-                            break;
-                        // Vector3
-                        case Animation.ANIMATIONTYPE_VECTOR3:
-                            this._offsetsCache[keyOffset] = toValue.subtract(fromValue);
-                        // Vector2
-                        case Animation.ANIMATIONTYPE_VECTOR2:
-                            this._offsetsCache[keyOffset] = toValue.subtract(fromValue);
-                        // Size
-                        case Animation.ANIMATIONTYPE_SIZE:
-                            this._offsetsCache[keyOffset] = toValue.subtract(fromValue);
-                        // Color3
-                        case Animation.ANIMATIONTYPE_COLOR3:
-                            this._offsetsCache[keyOffset] = toValue.subtract(fromValue);
-                        default:
-                            break;
-                    }
-
-                    this._highLimitsCache[keyOffset] = toValue;
+            highLimitValue = animation._getKeyValue(this._minValue);
+        } else if (this._animationState.loopMode !== Animation.ANIMATIONLOOPMODE_CYCLE) {
+            var keyOffset = to.toString() + from.toString();
+            if (!this._offsetsCache[keyOffset]) {
+                this._animationState.repeatCount = 0;
+                this._animationState.loopMode = Animation.ANIMATIONLOOPMODE_CYCLE;
+                var fromValue = animation._interpolate(from, this._animationState);
+                var toValue = animation._interpolate(to, this._animationState);
+
+                this._animationState.loopMode = this._getCorrectLoopMode();
+                switch (animation.dataType) {
+                    // Float
+                    case Animation.ANIMATIONTYPE_FLOAT:
+                        this._offsetsCache[keyOffset] = toValue - fromValue;
+                        break;
+                    // Quaternion
+                    case Animation.ANIMATIONTYPE_QUATERNION:
+                        this._offsetsCache[keyOffset] = toValue.subtract(fromValue);
+                        break;
+                    // Vector3
+                    case Animation.ANIMATIONTYPE_VECTOR3:
+                        this._offsetsCache[keyOffset] = toValue.subtract(fromValue);
+                    // Vector2
+                    case Animation.ANIMATIONTYPE_VECTOR2:
+                        this._offsetsCache[keyOffset] = toValue.subtract(fromValue);
+                    // Size
+                    case Animation.ANIMATIONTYPE_SIZE:
+                        this._offsetsCache[keyOffset] = toValue.subtract(fromValue);
+                    // Color3
+                    case Animation.ANIMATIONTYPE_COLOR3:
+                        this._offsetsCache[keyOffset] = toValue.subtract(fromValue);
+                    default:
+                        break;
                 }
 
-                highLimitValue = this._highLimitsCache[keyOffset];
-                offsetValue = this._offsetsCache[keyOffset];
+                this._highLimitsCache[keyOffset] = toValue;
             }
+
+            highLimitValue = this._highLimitsCache[keyOffset];
+            offsetValue = this._offsetsCache[keyOffset];
         }
 
         if (offsetValue === undefined) {
-            switch (this._animation.dataType) {
+            switch (animation.dataType) {
                 // Float
                 case Animation.ANIMATIONTYPE_FLOAT:
                     offsetValue = 0;
@@ -532,58 +558,65 @@ export class RuntimeAnimation {
         }
 
         // Compute value
-        let currentFrame = (returnValue && range !== 0) ? from + ratio % range : to;
+        let currentFrame: number;
 
-        // Need to normalize?
         if (this._host && this._host.syncRoot) {
             const syncRoot = this._host.syncRoot;
             const hostNormalizedFrame = (syncRoot.masterFrame - syncRoot.fromFrame) / (syncRoot.toFrame - syncRoot.fromFrame);
             currentFrame = from + (to - from) * hostNormalizedFrame;
+        } else {
+            currentFrame = (returnValue && range !== 0) ? from + ratio % range : to;
         }
 
         // Reset events if looping
         const events = this._events;
         if (range > 0 && this.currentFrame > currentFrame ||
             range < 0 && this.currentFrame < currentFrame) {
-            if (onLoop) {
-                onLoop();
-            }
+            this._onLoop();
 
             // Need to reset animation events
-            for (var index = 0; index < events.length; index++) {
-                if (!events[index].onlyOnce) {
-                    // reset event, the animation is looping
-                    events[index].isDone = false;
+            if (events.length) {
+                for (var index = 0; index < events.length; index++) {
+                    if (!events[index].onlyOnce) {
+                        // reset event, the animation is looping
+                        events[index].isDone = false;
+                    }
                 }
             }
         }
+        this._currentFrame = currentFrame;
+        this._animationState.repeatCount = range === 0 ? 0 : (ratio / range) >> 0;
+        this._animationState.highLimitValue = highLimitValue;
+        this._animationState.offsetValue = offsetValue;
 
-        const repeatCount = range === 0 ? 0 : (ratio / range) >> 0;
-        const currentValue = this._interpolate(currentFrame, repeatCount, this._getCorrectLoopMode(), offsetValue, highLimitValue);
+        const currentValue = animation._interpolate(currentFrame, this._animationState);
 
         // Set value
         this.setValue(currentValue, weight);
 
         // Check events
-        for (var index = 0; index < events.length; index++) {
-            // Make sure current frame has passed event frame and that event frame is within the current range
-            // Also, handle both forward and reverse animations
-            if (
-                (range > 0 && currentFrame >= events[index].frame && events[index].frame >= from) ||
-                (range < 0 && currentFrame <= events[index].frame && events[index].frame <= from)
-            ) {
-                var event = events[index];
-                if (!event.isDone) {
-                    // If event should be done only once, remove it.
-                    if (event.onlyOnce) {
-                        events.splice(index, 1);
-                        index--;
-                    }
-                    event.isDone = true;
-                    event.action(currentFrame);
-                } // Don't do anything if the event has already be done.
+        if (events.length) {
+            for (var index = 0; index < events.length; index++) {
+                // Make sure current frame has passed event frame and that event frame is within the current range
+                // Also, handle both forward and reverse animations
+                if (
+                    (range > 0 && currentFrame >= events[index].frame && events[index].frame >= from) ||
+                    (range < 0 && currentFrame <= events[index].frame && events[index].frame <= from)
+                ) {
+                    var event = events[index];
+                    if (!event.isDone) {
+                        // If event should be done only once, remove it.
+                        if (event.onlyOnce) {
+                            events.splice(index, 1);
+                            index--;
+                        }
+                        event.isDone = true;
+                        event.action(currentFrame);
+                    } // Don't do anything if the event has already be done.
+                }
             }
         }
+
         if (!returnValue) {
             this._stopped = true;
         }

+ 2 - 1
src/Bones/skeleton.ts

@@ -407,6 +407,7 @@ export class Skeleton implements IAnimatable {
 
         for (var index = 0; index < this.bones.length; index++) {
             var bone = this.bones[index];
+            bone._childUpdateId++;
             var parentBone = bone.getParent();
 
             if (parentBone) {
@@ -519,7 +520,7 @@ export class Skeleton implements IAnimatable {
     /**
      * Clone the current skeleton
      * @param name defines the name of the new skeleton
-     * @param id defines the id of the enw skeleton
+     * @param id defines the id of the new skeleton
      * @returns the new skeleton
      */
     public clone(name: string, id: string): Skeleton {

+ 52 - 19
src/Cameras/arcRotateCamera.ts

@@ -74,6 +74,45 @@ export class ArcRotateCamera extends TargetCamera {
         this.setPosition(newPosition);
     }
 
+    @serializeAsVector3("upVector")
+    protected _upVector = Vector3.Up();
+
+    protected _upToYMatrix: Matrix;
+    protected _YToUpMatrix: Matrix;
+
+    /**
+     * The vector the camera should consider as up. (default is Vector3(0, 1, 0) as returned by Vector3.Up())
+     * Setting this will copy the given vector to the camera's upVector, and set rotation matrices to and from Y up.
+     * DO NOT set the up vector using copyFrom or copyFromFloats, as this bypasses setting the above matrices.
+     */
+    set upVector(vec: Vector3) {
+        if (!this._upToYMatrix) {
+            this._YToUpMatrix = new Matrix();
+            this._upToYMatrix = new Matrix();
+
+            this._upVector = Vector3.Zero();
+        }
+
+        vec.normalize();
+        this._upVector.copyFrom(vec);
+        this.setMatUp();
+    }
+
+    get upVector() {
+        return this._upVector;
+    }
+
+    /**
+     * Sets the Y-up to camera up-vector rotation matrix, and the up-vector to Y-up rotation matrix.
+     */
+    public setMatUp() {
+        // from y-up to custom-up (used in _getViewMatrix)
+        Matrix.RotationAlignToRef(Vector3.UpReadOnly, this._upVector, this._YToUpMatrix);
+
+        // from custom-up to y-up (used in rebuildAnglesAndRadius)
+        Matrix.RotationAlignToRef(this._upVector, Vector3.UpReadOnly, this._upToYMatrix);
+    }
+
     /**
      * Current inertia value on the longitudinal axis.
      * The bigger this number the longer it will take for the camera to stop.
@@ -574,8 +613,6 @@ export class ArcRotateCamera extends TargetCamera {
     protected _targetBoundingCenter: Nullable<Vector3>;
 
     private _computationVector: Vector3 = Vector3.Zero();
-    private _tempAxisVector: Vector3;
-    private _tempAxisRotationMatrix: Matrix;
 
     /**
      * Instantiates a new ArcRotateCamera in a given scene
@@ -859,6 +896,12 @@ export class ArcRotateCamera extends TargetCamera {
      */
     public rebuildAnglesAndRadius(): void {
         this._position.subtractToRef(this._getTargetPosition(), this._computationVector);
+
+        // need to rotate to Y up equivalent if up vector not Axis.Y
+        if (this._upVector.x !== 0 || this._upVector.y !== 1.0 || this._upVector.z !== 0) {
+            Vector3.TransformCoordinatesToRef(this._computationVector, this._upToYMatrix, this._computationVector);
+        }
+
         this.radius = this._computationVector.length();
 
         if (this.radius === 0) {
@@ -866,7 +909,11 @@ export class ArcRotateCamera extends TargetCamera {
         }
 
         // Alpha
-        this.alpha = Math.acos(this._computationVector.x / Math.sqrt(Math.pow(this._computationVector.x, 2) + Math.pow(this._computationVector.z, 2)));
+        if (this._computationVector.x === 0 && this._computationVector.z === 0) {
+            this.alpha = Math.PI / 2; // avoid division by zero when looking along up axis, and set to acos(0)
+        } else {
+            this.alpha = Math.acos(this._computationVector.x / Math.sqrt(Math.pow(this._computationVector.x, 2) + Math.pow(this._computationVector.z, 2)));
+        }
 
         if (this._computationVector.z < 0) {
             this.alpha = 2 * Math.PI - this.alpha;
@@ -942,22 +989,8 @@ export class ArcRotateCamera extends TargetCamera {
         this._computationVector.copyFromFloats(this.radius * cosa * sinb, this.radius * cosb, this.radius * sina * sinb);
 
         // Rotate according to up vector
-        if (this.upVector.x !== 0 || this.upVector.y !== 1.0 || this.upVector.z !== 0) {
-
-            if (!this._tempAxisVector) {
-                this._tempAxisVector = new Vector3();
-                this._tempAxisRotationMatrix = new Matrix();
-            }
-
-            Vector3.CrossToRef(Vector3.Up(), this.upVector, this._tempAxisVector);
-            this._tempAxisVector.normalize();
-
-            let angle = Math.acos(Vector3.Dot(Vector3.UpReadOnly, this.upVector));
-
-            Matrix.RotationAxisToRef(this._tempAxisVector, angle, this._tempAxisRotationMatrix);
-
-            this._tempAxisVector.copyFrom(this._computationVector);
-            Vector3.TransformCoordinatesToRef(this._tempAxisVector, this._tempAxisRotationMatrix, this._computationVector);
+        if (this._upVector.x !== 0 || this._upVector.y !== 1.0 || this._upVector.z !== 0) {
+            Vector3.TransformCoordinatesToRef(this._computationVector, this._YToUpMatrix, this._computationVector);
         }
 
         target.addToRef(this._computationVector, this._newPosition);

+ 3 - 2
src/Cameras/deviceOrientationCamera.ts

@@ -34,7 +34,7 @@ export class DeviceOrientationCamera extends FreeCamera {
         // When the orientation sensor fires it's first event, disable mouse input
         if (this.inputs._deviceOrientationInput) {
             this.inputs._deviceOrientationInput._onDeviceOrientationChangedObservable.addOnce(() => {
-                if (this.disablePointerInputWhenUsingDeviceOrientation) {
+                if (this._disablePointerInputWhenUsingDeviceOrientation) {
                     if (this.inputs._mouseInput) {
                         this.inputs._mouseInput._allowCameraRotation = false;
                         this.inputs._mouseInput.onPointerMovedObservable.add((e) => {
@@ -54,9 +54,10 @@ export class DeviceOrientationCamera extends FreeCamera {
     }
 
     /**
+     * @hidden
      * Disabled pointer input on first orientation sensor update (Default: true)
      */
-    public disablePointerInputWhenUsingDeviceOrientation = true;
+    public _disablePointerInputWhenUsingDeviceOrientation = true;
     private _dragFactor = 0;
     /**
      * Enabled turning on the y axis when the orientation sensor is active

+ 3 - 1
src/Culling/boundingBox.ts

@@ -99,7 +99,9 @@ export class BoundingBox implements ICullable {
         max.addToRef(min, this.center).scaleInPlace(0.5);
         max.subtractToRef(min, this.extendSize).scaleInPlace(0.5);
 
-        this._update(worldMatrix || Matrix.IdentityReadOnly);
+        this._worldMatrix = worldMatrix || Matrix.IdentityReadOnly;
+
+        this._update(this._worldMatrix);
     }
 
     /**

+ 23 - 0
src/Debug/physicsViewer.ts

@@ -11,6 +11,7 @@ import { StandardMaterial } from "../Materials/standardMaterial";
 import { IPhysicsEnginePlugin } from "../Physics/IPhysicsEngine";
 import { PhysicsImpostor } from "../Physics/physicsImpostor";
 import { UtilityLayerRenderer } from "../Rendering/utilityLayerRenderer";
+import { CylinderBuilder } from '../Meshes/Builders/cylinderBuilder';
 
 /**
      * Used to show the physics impostor around the specific mesh
@@ -32,6 +33,7 @@ export class PhysicsViewer {
 
     private _debugBoxMesh: Mesh;
     private _debugSphereMesh: Mesh;
+    private _debugCylinderMesh: Mesh;
     private _debugMaterial: StandardMaterial;
     private _debugMeshMeshes = new Array<Mesh>();
 
@@ -196,6 +198,17 @@ export class PhysicsViewer {
         return this._debugSphereMesh.createInstance('physicsBodyBoxViewInstance');
     }
 
+    private _getDebugCylinderMesh(scene: Scene): AbstractMesh {
+        if (!this._debugCylinderMesh) {
+            this._debugCylinderMesh = CylinderBuilder.CreateCylinder('physicsBodyCylinderViewMesh', { diameterTop: 1, diameterBottom: 1, height: 1 }, scene);
+            this._debugCylinderMesh.rotationQuaternion = Quaternion.Identity();
+            this._debugCylinderMesh.material = this._getDebugMaterial(scene);
+            this._debugCylinderMesh.setEnabled(false);
+        }
+
+        return this._debugCylinderMesh.createInstance('physicsBodyBoxViewInstance');
+    }
+
     private _getDebugMeshMesh(mesh: Mesh, scene: Scene): AbstractMesh {
         var wireframeOver = new Mesh(mesh.name, scene, null, mesh);
         wireframeOver.position = Vector3.Zero();
@@ -247,6 +260,13 @@ export class PhysicsViewer {
                     });
                 }
                 break;
+            case PhysicsImpostor.CylinderImpostor:
+                mesh = this._getDebugCylinderMesh(utilityLayerScene);
+                var bi = impostor.object.getBoundingInfo();
+                mesh.scaling.x = bi.boundingBox.maximum.x - bi.boundingBox.minimum.x;
+                mesh.scaling.y = bi.boundingBox.maximum.y - bi.boundingBox.minimum.y;
+                mesh.scaling.z = bi.boundingBox.maximum.z - bi.boundingBox.minimum.z;
+                break;
         }
         return mesh;
     }
@@ -264,6 +284,9 @@ export class PhysicsViewer {
         if (this._debugSphereMesh) {
             this._debugSphereMesh.dispose();
         }
+        if (this._debugCylinderMesh) {
+            this._debugCylinderMesh.dispose();
+        }
         if (this._debugMaterial) {
             this._debugMaterial.dispose();
         }

+ 325 - 0
src/Engines/Extensions/engine.cubeTexture.ts

@@ -0,0 +1,325 @@
+import { Engine, DepthTextureCreationOptions } from "../../Engines/engine";
+import { InternalTexture } from '../../Materials/Textures/internalTexture';
+import { Logger } from '../../Misc/logger';
+import { Nullable } from '../../types';
+import { Scene } from '../../scene';
+import { IInternalTextureLoader } from '../../Materials/Textures/internalTextureLoader';
+import { WebRequest } from '../../Misc/webRequest';
+import { Tools } from '../../Misc/tools';
+
+declare module "../../Engines/engine" {
+    export interface Engine {
+        /**
+         * Creates a depth stencil cube texture.
+         * This is only available in WebGL 2.
+         * @param size The size of face edge in the cube texture.
+         * @param options The options defining the cube texture.
+         * @returns The cube texture
+         */
+        _createDepthStencilCubeTexture(size: number, options: DepthTextureCreationOptions): InternalTexture;
+
+        /**
+         * Creates a cube texture
+         * @param rootUrl defines the url where the files to load is located
+         * @param scene defines the current scene
+         * @param files defines the list of files to load (1 per face)
+         * @param noMipmap defines a boolean indicating that no mipmaps shall be generated (false by default)
+         * @param onLoad defines an optional callback raised when the texture is loaded
+         * @param onError defines an optional callback raised if there is an issue to load the texture
+         * @param format defines the format of the data
+         * @param forcedExtension defines the extension to use to pick the right loader
+         * @param createPolynomials if a polynomial sphere should be created for the cube texture
+         * @param lodScale defines the scale applied to environment texture. This manages the range of LOD level used for IBL according to the roughness
+         * @param lodOffset defines the offset applied to environment texture. This manages first LOD level used for IBL according to the roughness
+         * @param fallback defines texture to use while falling back when (compressed) texture file not found.
+         * @param excludeLoaders array of texture loaders that should be excluded when picking a loader for the texture (defualt: empty array)
+         * @returns the cube texture as an InternalTexture
+         */
+        createCubeTexture(rootUrl: string, scene: Nullable<Scene>, files: Nullable<string[]>, noMipmap: boolean | undefined,
+            onLoad: Nullable<(data?: any) => void>, onError: Nullable<(message?: string, exception?: any) => void>,
+            format: number | undefined, forcedExtension: any, createPolynomials: boolean, lodScale: number, lodOffset: number, fallback: Nullable<InternalTexture>, excludeLoaders: Array<IInternalTextureLoader>): InternalTexture;
+
+        /**
+         * Creates a cube texture
+         * @param rootUrl defines the url where the files to load is located
+         * @param scene defines the current scene
+         * @param files defines the list of files to load (1 per face)
+         * @param noMipmap defines a boolean indicating that no mipmaps shall be generated (false by default)
+         * @param onLoad defines an optional callback raised when the texture is loaded
+         * @param onError defines an optional callback raised if there is an issue to load the texture
+         * @param format defines the format of the data
+         * @param forcedExtension defines the extension to use to pick the right loader
+         * @returns the cube texture as an InternalTexture
+         */
+        createCubeTexture(rootUrl: string, scene: Nullable<Scene>, files: Nullable<string[]>, noMipmap: boolean,
+            onLoad: Nullable<(data?: any) => void>, onError: Nullable<(message?: string, exception?: any) => void>,
+            format: number | undefined, forcedExtension: any): InternalTexture;
+
+        /**
+         * Creates a cube texture
+         * @param rootUrl defines the url where the files to load is located
+         * @param scene defines the current scene
+         * @param files defines the list of files to load (1 per face)
+         * @param noMipmap defines a boolean indicating that no mipmaps shall be generated (false by default)
+         * @param onLoad defines an optional callback raised when the texture is loaded
+         * @param onError defines an optional callback raised if there is an issue to load the texture
+         * @param format defines the format of the data
+         * @param forcedExtension defines the extension to use to pick the right loader
+         * @param createPolynomials if a polynomial sphere should be created for the cube texture
+         * @param lodScale defines the scale applied to environment texture. This manages the range of LOD level used for IBL according to the roughness
+         * @param lodOffset defines the offset applied to environment texture. This manages first LOD level used for IBL according to the roughness
+         * @returns the cube texture as an InternalTexture
+         */
+        createCubeTexture(rootUrl: string, scene: Nullable<Scene>, files: Nullable<string[]>, noMipmap: boolean,
+            onLoad: Nullable<(data?: any) => void>, onError: Nullable<(message?: string, exception?: any) => void>,
+            format: number | undefined, forcedExtension: any, createPolynomials: boolean, lodScale: number, lodOffset: number): InternalTexture;
+
+        /** @hidden */
+        _partialLoadFile(url: string, index: number, loadedFiles: (string | ArrayBuffer)[], onfinish: (files: (string | ArrayBuffer)[]) => void, onErrorCallBack: Nullable<(message?: string, exception?: any) => void>): void;
+
+        /** @hidden */
+        _cascadeLoadFiles(scene: Nullable<Scene>, onfinish: (images: (string | ArrayBuffer)[]) => void, files: string[], onError: Nullable<(message?: string, exception?: any) => void>): void;
+
+        /** @hidden */
+        _cascadeLoadImgs(scene: Nullable<Scene>, onfinish: (images: HTMLImageElement[]) => void, files: string[], onError: Nullable<(message?: string, exception?: any) => void>): void;
+
+        /** @hidden */
+        _partialLoadImg(url: string, index: number, loadedImages: HTMLImageElement[], scene: Nullable<Scene>, onfinish: (images: HTMLImageElement[]) => void, onErrorCallBack: Nullable<(message?: string, exception?: any) => void>): void;
+    }
+}
+
+Engine.prototype._createDepthStencilCubeTexture = function(size: number, options: DepthTextureCreationOptions): InternalTexture {
+    var internalTexture = new InternalTexture(this, InternalTexture.DATASOURCE_UNKNOWN);
+    internalTexture.isCube = true;
+
+    if (this.webGLVersion === 1) {
+        Logger.Error("Depth cube texture is not supported by WebGL 1.");
+        return internalTexture;
+    }
+
+    var internalOptions = {
+        bilinearFiltering: false,
+        comparisonFunction: 0,
+        generateStencil: false,
+        ...options
+    };
+
+    var gl = this._gl;
+    this._bindTextureDirectly(gl.TEXTURE_CUBE_MAP, internalTexture, true);
+
+    this._setupDepthStencilTexture(internalTexture, size, internalOptions.generateStencil, internalOptions.bilinearFiltering, internalOptions.comparisonFunction);
+
+    // Create the depth/stencil buffer
+    for (var face = 0; face < 6; face++) {
+        if (internalOptions.generateStencil) {
+            gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + face, 0, gl.DEPTH24_STENCIL8, size, size, 0, gl.DEPTH_STENCIL, gl.UNSIGNED_INT_24_8, null);
+        }
+        else {
+            gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + face, 0, gl.DEPTH_COMPONENT24, size, size, 0, gl.DEPTH_COMPONENT, gl.UNSIGNED_INT, null);
+        }
+    }
+
+    this._bindTextureDirectly(gl.TEXTURE_CUBE_MAP, null);
+
+    return internalTexture;
+};
+
+Engine.prototype._partialLoadFile = function(url: string, index: number, loadedFiles: (string | ArrayBuffer)[], onfinish: (files: (string | ArrayBuffer)[]) => void, onErrorCallBack: Nullable<(message?: string, exception?: any) => void> = null): void {
+    var onload = (data: string | ArrayBuffer) => {
+        loadedFiles[index] = data;
+        (<any>loadedFiles)._internalCount++;
+
+        if ((<any>loadedFiles)._internalCount === 6) {
+            onfinish(loadedFiles);
+        }
+    };
+
+    const onerror = (request?: WebRequest, exception?: any) => {
+        if (onErrorCallBack && request) {
+            onErrorCallBack(request.status + " " + request.statusText, exception);
+        }
+    };
+
+    this._loadFile(url, onload, undefined, undefined, true, onerror);
+};
+
+Engine.prototype._cascadeLoadFiles = function(scene: Nullable<Scene>, onfinish: (images: (string | ArrayBuffer)[]) => void, files: string[], onError: Nullable<(message?: string, exception?: any) => void> = null): void {
+    var loadedFiles: (string | ArrayBuffer)[] = [];
+    (<any>loadedFiles)._internalCount = 0;
+
+    for (let index = 0; index < 6; index++) {
+        this._partialLoadFile(files[index], index, loadedFiles, onfinish, onError);
+    }
+};
+
+Engine.prototype._cascadeLoadImgs = function(scene: Nullable<Scene>,
+    onfinish: (images: HTMLImageElement[]) => void, files: string[], onError: Nullable<(message?: string, exception?: any) => void> = null) {
+
+    var loadedImages: HTMLImageElement[] = [];
+    (<any>loadedImages)._internalCount = 0;
+
+    for (let index = 0; index < 6; index++) {
+        this._partialLoadImg(files[index], index, loadedImages, scene, onfinish, onError);
+    }
+};
+
+Engine.prototype._partialLoadImg = function(url: string, index: number, loadedImages: HTMLImageElement[], scene: Nullable<Scene>,
+    onfinish: (images: HTMLImageElement[]) => void, onErrorCallBack: Nullable<(message?: string, exception?: any) => void> = null) {
+
+    var img: HTMLImageElement;
+
+    var onload = () => {
+        loadedImages[index] = img;
+        (<any>loadedImages)._internalCount++;
+
+        if (scene) {
+            scene._removePendingData(img);
+        }
+
+        if ((<any>loadedImages)._internalCount === 6) {
+            onfinish(loadedImages);
+        }
+    };
+
+    var onerror = (message?: string, exception?: any) => {
+        if (scene) {
+            scene._removePendingData(img);
+        }
+
+        if (onErrorCallBack) {
+            onErrorCallBack(message, exception);
+        }
+    };
+
+    img = Tools.LoadImage(url, onload, onerror, scene ? scene.offlineProvider : null);
+    if (scene) {
+        scene._addPendingData(img);
+    }
+};
+
+Engine.prototype.createCubeTexture = function(rootUrl: string, scene: Nullable<Scene>, files: Nullable<string[]>, noMipmap?: boolean, onLoad: Nullable<(data?: any) => void> = null, onError: Nullable<(message?: string, exception?: any) => void> = null, format?: number, forcedExtension: any = null, createPolynomials: boolean = false, lodScale: number = 0, lodOffset: number = 0, fallback: Nullable<InternalTexture> = null, excludeLoaders: Array<IInternalTextureLoader> = []): InternalTexture {
+    var gl = this._gl;
+
+    var texture = fallback ? fallback : new InternalTexture(this, InternalTexture.DATASOURCE_CUBE);
+    texture.isCube = true;
+    texture.url = rootUrl;
+    texture.generateMipMaps = !noMipmap;
+    texture._lodGenerationScale = lodScale;
+    texture._lodGenerationOffset = lodOffset;
+
+    if (!this._doNotHandleContextLost) {
+        texture._extension = forcedExtension;
+        texture._files = files;
+    }
+
+    var lastDot = rootUrl.lastIndexOf('.');
+    var extension = forcedExtension ? forcedExtension : (lastDot > -1 ? rootUrl.substring(lastDot).toLowerCase() : "");
+
+    let loader: Nullable<IInternalTextureLoader> = null;
+    for (let availableLoader of Engine._TextureLoaders) {
+        if (excludeLoaders.indexOf(availableLoader) === -1 && availableLoader.canLoad(extension, this._textureFormatInUse, fallback, false, false)) {
+            loader = availableLoader;
+            break;
+        }
+    }
+
+    let onInternalError = (request?: WebRequest, exception?: any) => {
+        if (loader) {
+            const fallbackUrl = loader.getFallbackTextureUrl(texture.url, this._textureFormatInUse);
+            Logger.Warn((loader.constructor as any).name + " failed when trying to load " + texture.url + ", falling back to the next supported loader");
+            if (fallbackUrl) {
+                excludeLoaders.push(loader);
+                this.createCubeTexture(fallbackUrl, scene, files, noMipmap, onLoad, onError, format, extension, createPolynomials, lodScale, lodOffset, texture, excludeLoaders);
+                return;
+            }
+        }
+
+        if (onError && request) {
+            onError(request.status + " " + request.statusText, exception);
+        }
+    };
+
+    if (loader) {
+        rootUrl = loader.transformUrl(rootUrl, this._textureFormatInUse);
+
+        const onloaddata = (data: any) => {
+            this._bindTextureDirectly(gl.TEXTURE_CUBE_MAP, texture, true);
+            loader!.loadCubeData(data, texture, createPolynomials, onLoad, onError);
+        };
+        if (files && files.length === 6) {
+            if (loader.supportCascades) {
+                this._cascadeLoadFiles(scene, onloaddata, files, onError);
+            }
+            else {
+                if (onError) {
+                    onError("Textures type does not support cascades.");
+                } else {
+                    Logger.Warn("Texture loader does not support cascades.");
+                }
+            }
+        }
+        else {
+            this._loadFile(rootUrl, onloaddata, undefined, undefined, true, onInternalError);
+        }
+    }
+    else {
+        if (!files) {
+            throw new Error("Cannot load cubemap because files were not defined");
+        }
+
+        this._cascadeLoadImgs(scene, (imgs) => {
+            var width = this.needPOTTextures ? Tools.GetExponentOfTwo(imgs[0].width, this._caps.maxCubemapTextureSize) : imgs[0].width;
+            var height = width;
+
+            this._prepareWorkingCanvas();
+
+            if (!this._workingCanvas || !this._workingContext) {
+                return;
+            }
+            this._workingCanvas.width = width;
+            this._workingCanvas.height = height;
+
+            var faces = [
+                gl.TEXTURE_CUBE_MAP_POSITIVE_X, gl.TEXTURE_CUBE_MAP_POSITIVE_Y, gl.TEXTURE_CUBE_MAP_POSITIVE_Z,
+                gl.TEXTURE_CUBE_MAP_NEGATIVE_X, gl.TEXTURE_CUBE_MAP_NEGATIVE_Y, gl.TEXTURE_CUBE_MAP_NEGATIVE_Z
+            ];
+
+            this._bindTextureDirectly(gl.TEXTURE_CUBE_MAP, texture, true);
+            this._unpackFlipY(false);
+
+            let internalFormat = format ? this._getInternalFormat(format) : this._gl.RGBA;
+            for (var index = 0; index < faces.length; index++) {
+                if (imgs[index].width !== width || imgs[index].height !== height) {
+                    this._workingContext.drawImage(imgs[index], 0, 0, imgs[index].width, imgs[index].height, 0, 0, width, height);
+                    gl.texImage2D(faces[index], 0, internalFormat, internalFormat, gl.UNSIGNED_BYTE, this._workingCanvas);
+                } else {
+                    gl.texImage2D(faces[index], 0, internalFormat, internalFormat, gl.UNSIGNED_BYTE, imgs[index]);
+                }
+            }
+
+            if (!noMipmap) {
+                gl.generateMipmap(gl.TEXTURE_CUBE_MAP);
+            }
+
+            this._setCubeMapTextureParams(!noMipmap);
+
+            texture.width = width;
+            texture.height = height;
+            texture.isReady = true;
+            if (format) {
+                texture.format = format;
+            }
+
+            texture.onLoadedObservable.notifyObservers(texture);
+            texture.onLoadedObservable.clear();
+
+            if (onLoad) {
+                onLoad();
+            }
+        }, files, onError);
+    }
+
+    this._internalTexturesCache.push(texture);
+
+    return texture;
+};

+ 327 - 0
src/Engines/Extensions/engine.multiRender.ts

@@ -0,0 +1,327 @@
+import { Engine } from "../../Engines/engine";
+import { InternalTexture } from '../../Materials/Textures/internalTexture';
+import { IMultiRenderTargetOptions } from '../../Materials/Textures/multiRenderTarget';
+import { Logger } from '../../Misc/logger';
+import { Nullable } from '../../types';
+
+declare module "../../Engines/engine" {
+    export interface Engine {
+        /**
+         * Unbind a list of render target textures from the webGL context
+         * This is used only when drawBuffer extension or webGL2 are active
+         * @param textures defines the render target textures to unbind
+         * @param disableGenerateMipMaps defines a boolean indicating that mipmaps must not be generated
+         * @param onBeforeUnbind defines a function which will be called before the effective unbind
+         */
+        unBindMultiColorAttachmentFramebuffer(textures: InternalTexture[], disableGenerateMipMaps: boolean, onBeforeUnbind?: () => void): void;
+
+        /**
+         * Create a multi render target texture
+         * @see http://doc.babylonjs.com/features/webgl2#multiple-render-target
+         * @param size defines the size of the texture
+         * @param options defines the creation options
+         * @returns the cube texture as an InternalTexture
+         */
+        createMultipleRenderTarget(size: any, options: IMultiRenderTargetOptions): InternalTexture[];
+
+        /**
+         * Update the sample count for a given multiple render target texture
+         * @see http://doc.babylonjs.com/features/webgl2#multisample-render-targets
+         * @param textures defines the textures to update
+         * @param samples defines the sample count to set
+         * @returns the effective sample count (could be 0 if multisample render targets are not supported)
+         */
+        updateMultipleRenderTargetTextureSampleCount(textures: Nullable<InternalTexture[]>, samples: number): number;
+    }
+}
+
+Engine.prototype.unBindMultiColorAttachmentFramebuffer = function(textures: InternalTexture[], disableGenerateMipMaps: boolean = false, onBeforeUnbind?: () => void): void {
+    this._currentRenderTarget = null;
+
+    // If MSAA, we need to bitblt back to main texture
+    var gl = this._gl;
+
+    if (textures[0]._MSAAFramebuffer) {
+        gl.bindFramebuffer(gl.READ_FRAMEBUFFER, textures[0]._MSAAFramebuffer);
+        gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, textures[0]._framebuffer);
+
+        var attachments = textures[0]._attachments;
+        if (!attachments) {
+            attachments = new Array(textures.length);
+            textures[0]._attachments = attachments;
+        }
+
+        for (var i = 0; i < textures.length; i++) {
+            var texture = textures[i];
+
+            for (var j = 0; j < attachments.length; j++) {
+                attachments[j] = gl.NONE;
+            }
+
+            attachments[i] = (<any>gl)[this.webGLVersion > 1 ? "COLOR_ATTACHMENT" + i : "COLOR_ATTACHMENT" + i + "_WEBGL"];
+            gl.readBuffer(attachments[i]);
+            gl.drawBuffers(attachments);
+            gl.blitFramebuffer(0, 0, texture.width, texture.height,
+                0, 0, texture.width, texture.height,
+                gl.COLOR_BUFFER_BIT, gl.NEAREST);
+
+        }
+        for (var i = 0; i < attachments.length; i++) {
+            attachments[i] = (<any>gl)[this.webGLVersion > 1 ? "COLOR_ATTACHMENT" + i : "COLOR_ATTACHMENT" + i + "_WEBGL"];
+        }
+        gl.drawBuffers(attachments);
+    }
+
+    for (var i = 0; i < textures.length; i++) {
+        var texture = textures[i];
+        if (texture.generateMipMaps && !disableGenerateMipMaps && !texture.isCube) {
+            this._bindTextureDirectly(gl.TEXTURE_2D, texture);
+            gl.generateMipmap(gl.TEXTURE_2D);
+            this._bindTextureDirectly(gl.TEXTURE_2D, null);
+        }
+    }
+
+    if (onBeforeUnbind) {
+        if (textures[0]._MSAAFramebuffer) {
+            // Bind the correct framebuffer
+            this._bindUnboundFramebuffer(textures[0]._framebuffer);
+        }
+        onBeforeUnbind();
+    }
+
+    this._bindUnboundFramebuffer(null);
+};
+
+Engine.prototype.createMultipleRenderTarget = function(size: any, options: IMultiRenderTargetOptions): InternalTexture[] {
+    var generateMipMaps = false;
+    var generateDepthBuffer = true;
+    var generateStencilBuffer = false;
+    var generateDepthTexture = false;
+    var textureCount = 1;
+
+    var defaultType = Engine.TEXTURETYPE_UNSIGNED_INT;
+    var defaultSamplingMode = Engine.TEXTURE_TRILINEAR_SAMPLINGMODE;
+
+    var types = new Array<number>();
+    var samplingModes = new Array<number>();
+
+    if (options !== undefined) {
+        generateMipMaps = options.generateMipMaps === undefined ? false : options.generateMipMaps;
+        generateDepthBuffer = options.generateDepthBuffer === undefined ? true : options.generateDepthBuffer;
+        generateStencilBuffer = options.generateStencilBuffer === undefined ? false : options.generateStencilBuffer;
+        generateDepthTexture = options.generateDepthTexture === undefined ? false : options.generateDepthTexture;
+        textureCount = options.textureCount || 1;
+
+        if (options.types) {
+            types = options.types;
+        }
+        if (options.samplingModes) {
+            samplingModes = options.samplingModes;
+        }
+
+    }
+    var gl = this._gl;
+    // Create the framebuffer
+    var framebuffer = gl.createFramebuffer();
+    this._bindUnboundFramebuffer(framebuffer);
+
+    var width = size.width || size;
+    var height = size.height || size;
+
+    var textures = [];
+    var attachments = [];
+
+    var depthStencilBuffer = this._setupFramebufferDepthAttachments(generateStencilBuffer, generateDepthBuffer, width, height);
+
+    for (var i = 0; i < textureCount; i++) {
+        var samplingMode = samplingModes[i] || defaultSamplingMode;
+        var type = types[i] || defaultType;
+
+        if (type === Engine.TEXTURETYPE_FLOAT && !this._caps.textureFloatLinearFiltering) {
+            // if floating point linear (gl.FLOAT) then force to NEAREST_SAMPLINGMODE
+            samplingMode = Engine.TEXTURE_NEAREST_SAMPLINGMODE;
+        }
+        else if (type === Engine.TEXTURETYPE_HALF_FLOAT && !this._caps.textureHalfFloatLinearFiltering) {
+            // if floating point linear (HALF_FLOAT) then force to NEAREST_SAMPLINGMODE
+            samplingMode = Engine.TEXTURE_NEAREST_SAMPLINGMODE;
+        }
+
+        var filters = this._getSamplingParameters(samplingMode, generateMipMaps);
+        if (type === Engine.TEXTURETYPE_FLOAT && !this._caps.textureFloat) {
+            type = Engine.TEXTURETYPE_UNSIGNED_INT;
+            Logger.Warn("Float textures are not supported. Render target forced to TEXTURETYPE_UNSIGNED_BYTE type");
+        }
+
+        var texture = new InternalTexture(this, InternalTexture.DATASOURCE_MULTIRENDERTARGET);
+        var attachment = (<any>gl)[this.webGLVersion > 1 ? "COLOR_ATTACHMENT" + i : "COLOR_ATTACHMENT" + i + "_WEBGL"];
+
+        textures.push(texture);
+        attachments.push(attachment);
+
+        gl.activeTexture((<any>gl)["TEXTURE" + i]);
+        gl.bindTexture(gl.TEXTURE_2D, texture._webGLTexture);
+
+        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, filters.mag);
+        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, filters.min);
+        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
+        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
+
+        gl.texImage2D(gl.TEXTURE_2D, 0, this._getRGBABufferInternalSizedFormat(type), width, height, 0, gl.RGBA, this._getWebGLTextureType(type), null);
+
+        gl.framebufferTexture2D(gl.DRAW_FRAMEBUFFER, attachment, gl.TEXTURE_2D, texture._webGLTexture, 0);
+
+        if (generateMipMaps) {
+            this._gl.generateMipmap(this._gl.TEXTURE_2D);
+        }
+
+        // Unbind
+        this._bindTextureDirectly(gl.TEXTURE_2D, null);
+
+        texture._framebuffer = framebuffer;
+        texture._depthStencilBuffer = depthStencilBuffer;
+        texture.baseWidth = width;
+        texture.baseHeight = height;
+        texture.width = width;
+        texture.height = height;
+        texture.isReady = true;
+        texture.samples = 1;
+        texture.generateMipMaps = generateMipMaps;
+        texture.samplingMode = samplingMode;
+        texture.type = type;
+        texture._generateDepthBuffer = generateDepthBuffer;
+        texture._generateStencilBuffer = generateStencilBuffer;
+        texture._attachments = attachments;
+
+        this._internalTexturesCache.push(texture);
+    }
+
+    if (generateDepthTexture && this._caps.depthTextureExtension) {
+        // Depth texture
+        var depthTexture = new InternalTexture(this, InternalTexture.DATASOURCE_MULTIRENDERTARGET);
+
+        gl.activeTexture(gl.TEXTURE0);
+        gl.bindTexture(gl.TEXTURE_2D, depthTexture._webGLTexture);
+        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
+        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
+        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
+        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
+        gl.texImage2D(
+            gl.TEXTURE_2D,
+            0,
+            this.webGLVersion < 2 ? gl.DEPTH_COMPONENT : gl.DEPTH_COMPONENT16,
+            width,
+            height,
+            0,
+            gl.DEPTH_COMPONENT,
+            gl.UNSIGNED_SHORT,
+            null
+        );
+
+        gl.framebufferTexture2D(
+            gl.FRAMEBUFFER,
+            gl.DEPTH_ATTACHMENT,
+            gl.TEXTURE_2D,
+            depthTexture._webGLTexture,
+            0
+        );
+
+        depthTexture._framebuffer = framebuffer;
+        depthTexture.baseWidth = width;
+        depthTexture.baseHeight = height;
+        depthTexture.width = width;
+        depthTexture.height = height;
+        depthTexture.isReady = true;
+        depthTexture.samples = 1;
+        depthTexture.generateMipMaps = generateMipMaps;
+        depthTexture.samplingMode = gl.NEAREST;
+        depthTexture._generateDepthBuffer = generateDepthBuffer;
+        depthTexture._generateStencilBuffer = generateStencilBuffer;
+
+        textures.push(depthTexture);
+        this._internalTexturesCache.push(depthTexture);
+    }
+
+    gl.drawBuffers(attachments);
+    gl.bindRenderbuffer(gl.RENDERBUFFER, null);
+    this._bindUnboundFramebuffer(null);
+
+    this.resetTextureCache();
+
+    return textures;
+};
+
+Engine.prototype.updateMultipleRenderTargetTextureSampleCount = function(textures: Nullable<InternalTexture[]>, samples: number): number {
+    if (this.webGLVersion < 2 || !textures || textures.length == 0) {
+        return 1;
+    }
+
+    if (textures[0].samples === samples) {
+        return samples;
+    }
+
+    var gl = this._gl;
+
+    samples = Math.min(samples, gl.getParameter(gl.MAX_SAMPLES));
+
+    // Dispose previous render buffers
+    if (textures[0]._depthStencilBuffer) {
+        gl.deleteRenderbuffer(textures[0]._depthStencilBuffer);
+        textures[0]._depthStencilBuffer = null;
+    }
+
+    if (textures[0]._MSAAFramebuffer) {
+        gl.deleteFramebuffer(textures[0]._MSAAFramebuffer);
+        textures[0]._MSAAFramebuffer = null;
+    }
+
+    for (var i = 0; i < textures.length; i++) {
+        if (textures[i]._MSAARenderBuffer) {
+            gl.deleteRenderbuffer(textures[i]._MSAARenderBuffer);
+            textures[i]._MSAARenderBuffer = null;
+        }
+    }
+
+    if (samples > 1) {
+        let framebuffer = gl.createFramebuffer();
+
+        if (!framebuffer) {
+            throw new Error("Unable to create multi sampled framebuffer");
+        }
+
+        this._bindUnboundFramebuffer(framebuffer);
+
+        let depthStencilBuffer = this._setupFramebufferDepthAttachments(textures[0]._generateStencilBuffer, textures[0]._generateDepthBuffer, textures[0].width, textures[0].height, samples);
+
+        var attachments = [];
+
+        for (var i = 0; i < textures.length; i++) {
+            var texture = textures[i];
+            var attachment = (<any>gl)[this.webGLVersion > 1 ? "COLOR_ATTACHMENT" + i : "COLOR_ATTACHMENT" + i + "_WEBGL"];
+
+            var colorRenderbuffer = gl.createRenderbuffer();
+
+            if (!colorRenderbuffer) {
+                throw new Error("Unable to create multi sampled framebuffer");
+            }
+
+            gl.bindRenderbuffer(gl.RENDERBUFFER, colorRenderbuffer);
+            gl.renderbufferStorageMultisample(gl.RENDERBUFFER, samples, this._getRGBAMultiSampleBufferFormat(texture.type), texture.width, texture.height);
+
+            gl.framebufferRenderbuffer(gl.FRAMEBUFFER, attachment, gl.RENDERBUFFER, colorRenderbuffer);
+
+            texture._MSAAFramebuffer = framebuffer;
+            texture._MSAARenderBuffer = colorRenderbuffer;
+            texture.samples = samples;
+            texture._depthStencilBuffer = depthStencilBuffer;
+            gl.bindRenderbuffer(gl.RENDERBUFFER, null);
+            attachments.push(attachment);
+        }
+        gl.drawBuffers(attachments);
+    } else {
+        this._bindUnboundFramebuffer(textures[0]._framebuffer);
+    }
+
+    this._bindUnboundFramebuffer(null);
+
+    return samples;
+};

+ 524 - 0
src/Engines/Extensions/engine.rawTexture.ts

@@ -0,0 +1,524 @@
+import { Nullable } from "../../types";
+import { Engine } from "../../Engines/engine";
+import { _TimeToken } from "../../Instrumentation/timeToken";
+import { InternalTexture } from '../../Materials/Textures/internalTexture';
+import { Logger } from '../../Misc/logger';
+import { Tools } from '../../Misc/tools';
+import { Scene } from '../../scene';
+import { WebRequest } from '../../Misc/webRequest';
+
+declare module "../../Engines/engine" {
+    export interface Engine {
+        /**
+         * Creates a raw texture
+         * @param data defines the data to store in the texture
+         * @param width defines the width of the texture
+         * @param height defines the height of the texture
+         * @param format defines the format of the data
+         * @param generateMipMaps defines if the engine should generate the mip levels
+         * @param invertY defines if data must be stored with Y axis inverted
+         * @param samplingMode defines the required sampling mode (Texture.NEAREST_SAMPLINGMODE by default)
+         * @param compression defines the compression used (null by default)
+         * @param type defines the type fo the data (Engine.TEXTURETYPE_UNSIGNED_INT by default)
+         * @returns the raw texture inside an InternalTexture
+         */
+        createRawTexture(data: Nullable<ArrayBufferView>, width: number, height: number, format: number, generateMipMaps: boolean, invertY: boolean, samplingMode: number, compression: Nullable<string>, type: number): InternalTexture;
+
+        /**
+         * Update a raw texture
+         * @param texture defines the texture to update
+         * @param data defines the data to store in the texture
+         * @param format defines the format of the data
+         * @param invertY defines if data must be stored with Y axis inverted
+         */
+        updateRawTexture(texture: Nullable<InternalTexture>, data: Nullable<ArrayBufferView>, format: number, invertY: boolean): void;
+
+        /**
+         * Update a raw texture
+         * @param texture defines the texture to update
+         * @param data defines the data to store in the texture
+         * @param format defines the format of the data
+         * @param invertY defines if data must be stored with Y axis inverted
+         * @param compression defines the compression used (null by default)
+         * @param type defines the type fo the data (Engine.TEXTURETYPE_UNSIGNED_INT by default)
+         */
+        updateRawTexture(texture: Nullable<InternalTexture>, data: Nullable<ArrayBufferView>, format: number, invertY: boolean, compression: Nullable<string>, type: number): void;
+
+        /**
+         * Creates a new raw cube texture
+         * @param data defines the array of data to use to create each face
+         * @param size defines the size of the textures
+         * @param format defines the format of the data
+         * @param type defines the type of the data (like Engine.TEXTURETYPE_UNSIGNED_INT)
+         * @param generateMipMaps  defines if the engine should generate the mip levels
+         * @param invertY defines if data must be stored with Y axis inverted
+         * @param samplingMode defines the required sampling mode (like Texture.NEAREST_SAMPLINGMODE)
+         * @param compression defines the compression used (null by default)
+         * @returns the cube texture as an InternalTexture
+         */
+        createRawCubeTexture(data: Nullable<ArrayBufferView[]>, size: number, format: number, type: number, generateMipMaps: boolean, invertY: boolean, samplingMode: number, compression: Nullable<string>): InternalTexture;
+
+        /**
+         * Update a raw cube texture
+         * @param texture defines the texture to udpdate
+         * @param data defines the data to store
+         * @param format defines the data format
+         * @param type defines the type fo the data (Engine.TEXTURETYPE_UNSIGNED_INT by default)
+         * @param invertY defines if data must be stored with Y axis inverted
+         */
+        updateRawCubeTexture(texture: InternalTexture, data: ArrayBufferView[], format: number, type: number, invertY: boolean): void;
+
+        /**
+         * Update a raw cube texture
+         * @param texture defines the texture to udpdate
+         * @param data defines the data to store
+         * @param format defines the data format
+         * @param type defines the type fo the data (Engine.TEXTURETYPE_UNSIGNED_INT by default)
+         * @param invertY defines if data must be stored with Y axis inverted
+         * @param compression defines the compression used (null by default)
+         */
+        updateRawCubeTexture(texture: InternalTexture, data: ArrayBufferView[], format: number, type: number, invertY: boolean, compression: Nullable<string>): void;
+
+        /**
+         * Update a raw cube texture
+         * @param texture defines the texture to udpdate
+         * @param data defines the data to store
+         * @param format defines the data format
+         * @param type defines the type fo the data (Engine.TEXTURETYPE_UNSIGNED_INT by default)
+         * @param invertY defines if data must be stored with Y axis inverted
+         * @param compression defines the compression used (null by default)
+         * @param level defines which level of the texture to update
+         */
+        updateRawCubeTexture(texture: InternalTexture, data: ArrayBufferView[], format: number, type: number, invertY: boolean, compression: Nullable<string>, level: number): void;
+
+        /**
+         * Creates a new raw cube texture from a specified url
+         * @param url defines the url where the data is located
+         * @param scene defines the current scene
+         * @param size defines the size of the textures
+         * @param format defines the format of the data
+         * @param type defines the type fo the data (like Engine.TEXTURETYPE_UNSIGNED_INT)
+         * @param noMipmap defines if the engine should avoid generating the mip levels
+         * @param callback defines a callback used to extract texture data from loaded data
+         * @param mipmapGenerator defines to provide an optional tool to generate mip levels
+         * @param onLoad defines a callback called when texture is loaded
+         * @param onError defines a callback called if there is an error
+         * @returns the cube texture as an InternalTexture
+         */
+        createRawCubeTextureFromUrl(url: string, scene: Scene, size: number, format: number, type: number, noMipmap: boolean,
+            callback: (ArrayBuffer: ArrayBuffer) => Nullable<ArrayBufferView[]>,
+            mipmapGenerator: Nullable<((faces: ArrayBufferView[]) => ArrayBufferView[][])>,
+            onLoad: Nullable<() => void>,
+            onError: Nullable<(message?: string, exception?: any) => void>): InternalTexture;
+
+        /**
+         * Creates a new raw cube texture from a specified url
+         * @param url defines the url where the data is located
+         * @param scene defines the current scene
+         * @param size defines the size of the textures
+         * @param format defines the format of the data
+         * @param type defines the type fo the data (like Engine.TEXTURETYPE_UNSIGNED_INT)
+         * @param noMipmap defines if the engine should avoid generating the mip levels
+         * @param callback defines a callback used to extract texture data from loaded data
+         * @param mipmapGenerator defines to provide an optional tool to generate mip levels
+         * @param onLoad defines a callback called when texture is loaded
+         * @param onError defines a callback called if there is an error
+         * @param samplingMode defines the required sampling mode (like Texture.NEAREST_SAMPLINGMODE)
+         * @param invertY defines if data must be stored with Y axis inverted
+         * @returns the cube texture as an InternalTexture
+         */
+        createRawCubeTextureFromUrl(url: string, scene: Scene, size: number, format: number, type: number, noMipmap: boolean,
+            callback: (ArrayBuffer: ArrayBuffer) => Nullable<ArrayBufferView[]>,
+            mipmapGenerator: Nullable<((faces: ArrayBufferView[]) => ArrayBufferView[][])>,
+            onLoad: Nullable<() => void>,
+            onError: Nullable<(message?: string, exception?: any) => void>,
+            samplingMode: number,
+            invertY: boolean): InternalTexture;
+
+        /**
+         * Creates a new raw 3D texture
+         * @param data defines the data used to create the texture
+         * @param width defines the width of the texture
+         * @param height defines the height of the texture
+         * @param depth defines the depth of the texture
+         * @param format defines the format of the texture
+         * @param generateMipMaps defines if the engine must generate mip levels
+         * @param invertY defines if data must be stored with Y axis inverted
+         * @param samplingMode defines the required sampling mode (like Texture.NEAREST_SAMPLINGMODE)
+         * @param compression defines the compressed used (can be null)
+         * @param textureType defines the compressed used (can be null)
+         * @returns a new raw 3D texture (stored in an InternalTexture)
+         */
+        createRawTexture3D(data: Nullable<ArrayBufferView>, width: number, height: number, depth: number, format: number, generateMipMaps: boolean, invertY: boolean, samplingMode: number, compression: Nullable<string>, textureType: number): InternalTexture;
+
+        /**
+         * Update a raw 3D texture
+         * @param texture defines the texture to update
+         * @param data defines the data to store
+         * @param format defines the data format
+         * @param invertY defines if data must be stored with Y axis inverted
+         */
+        updateRawTexture3D(texture: InternalTexture, data: Nullable<ArrayBufferView>, format: number, invertY: boolean): void;
+
+        /**
+         * Update a raw 3D texture
+         * @param texture defines the texture to update
+         * @param data defines the data to store
+         * @param format defines the data format
+         * @param invertY defines if data must be stored with Y axis inverted
+         * @param compression defines the used compression (can be null)
+         * @param textureType defines the texture Type (Engine.TEXTURETYPE_UNSIGNED_INT, Engine.TEXTURETYPE_FLOAT...)
+         */
+        updateRawTexture3D(texture: InternalTexture, data: Nullable<ArrayBufferView>, format: number, invertY: boolean, compression: Nullable<string>, textureType: number): void;
+    }
+}
+
+Engine.prototype.updateRawTexture = function(texture: Nullable<InternalTexture>, data: Nullable<ArrayBufferView>, format: number, invertY: boolean, compression: Nullable<string> = null, type: number = Engine.TEXTURETYPE_UNSIGNED_INT): void {
+    if (!texture) {
+        return;
+    }
+    // Babylon's internalSizedFomat but gl's texImage2D internalFormat
+    var internalSizedFomat = this._getRGBABufferInternalSizedFormat(type, format);
+
+    // Babylon's internalFormat but gl's texImage2D format
+    var internalFormat = this._getInternalFormat(format);
+    var textureType = this._getWebGLTextureType(type);
+    this._bindTextureDirectly(this._gl.TEXTURE_2D, texture, true);
+    this._unpackFlipY(invertY === undefined ? true : (invertY ? true : false));
+
+    if (!this._doNotHandleContextLost) {
+        texture._bufferView = data;
+        texture.format = format;
+        texture.type = type;
+        texture.invertY = invertY;
+        texture._compression = compression;
+    }
+
+    if (texture.width % 4 !== 0) {
+        this._gl.pixelStorei(this._gl.UNPACK_ALIGNMENT, 1);
+    }
+
+    if (compression && data) {
+        this._gl.compressedTexImage2D(this._gl.TEXTURE_2D, 0, (<any>this.getCaps().s3tc)[compression], texture.width, texture.height, 0, <DataView>data);
+    } else {
+        this._gl.texImage2D(this._gl.TEXTURE_2D, 0, internalSizedFomat, texture.width, texture.height, 0, internalFormat, textureType, data);
+    }
+
+    if (texture.generateMipMaps) {
+        this._gl.generateMipmap(this._gl.TEXTURE_2D);
+    }
+    this._bindTextureDirectly(this._gl.TEXTURE_2D, null);
+    //  this.resetTextureCache();
+    texture.isReady = true;
+};
+
+Engine.prototype.createRawTexture = function(data: Nullable<ArrayBufferView>, width: number, height: number, format: number, generateMipMaps: boolean, invertY: boolean, samplingMode: number, compression: Nullable<string> = null, type: number = Engine.TEXTURETYPE_UNSIGNED_INT): InternalTexture {
+    var texture = new InternalTexture(this, InternalTexture.DATASOURCE_RAW);
+    texture.baseWidth = width;
+    texture.baseHeight = height;
+    texture.width = width;
+    texture.height = height;
+    texture.format = format;
+    texture.generateMipMaps = generateMipMaps;
+    texture.samplingMode = samplingMode;
+    texture.invertY = invertY;
+    texture._compression = compression;
+    texture.type = type;
+
+    if (!this._doNotHandleContextLost) {
+        texture._bufferView = data;
+    }
+
+    this.updateRawTexture(texture, data, format, invertY, compression, type);
+    this._bindTextureDirectly(this._gl.TEXTURE_2D, texture, true);
+
+    // Filters
+    var filters = this._getSamplingParameters(samplingMode, generateMipMaps);
+
+    this._gl.texParameteri(this._gl.TEXTURE_2D, this._gl.TEXTURE_MAG_FILTER, filters.mag);
+    this._gl.texParameteri(this._gl.TEXTURE_2D, this._gl.TEXTURE_MIN_FILTER, filters.min);
+
+    if (generateMipMaps) {
+        this._gl.generateMipmap(this._gl.TEXTURE_2D);
+    }
+
+    this._bindTextureDirectly(this._gl.TEXTURE_2D, null);
+
+    this._internalTexturesCache.push(texture);
+
+    return texture;
+};
+
+Engine.prototype.createRawCubeTexture = function(data: Nullable<ArrayBufferView[]>, size: number, format: number, type: number,
+    generateMipMaps: boolean, invertY: boolean, samplingMode: number,
+    compression: Nullable<string> = null): InternalTexture {
+    var gl = this._gl;
+    var texture = new InternalTexture(this, InternalTexture.DATASOURCE_CUBERAW);
+    texture.isCube = true;
+    texture.format = format;
+    texture.type = type;
+    if (!this._doNotHandleContextLost) {
+        texture._bufferViewArray = data;
+    }
+
+    var textureType = this._getWebGLTextureType(type);
+    var internalFormat = this._getInternalFormat(format);
+
+    if (internalFormat === gl.RGB) {
+        internalFormat = gl.RGBA;
+    }
+
+    // Mipmap generation needs a sized internal format that is both color-renderable and texture-filterable
+    if (textureType === gl.FLOAT && !this._caps.textureFloatLinearFiltering) {
+        generateMipMaps = false;
+        samplingMode = Engine.TEXTURE_NEAREST_SAMPLINGMODE;
+        Logger.Warn("Float texture filtering is not supported. Mipmap generation and sampling mode are forced to false and TEXTURE_NEAREST_SAMPLINGMODE, respectively.");
+    }
+    else if (textureType === this._gl.HALF_FLOAT_OES && !this._caps.textureHalfFloatLinearFiltering) {
+        generateMipMaps = false;
+        samplingMode = Engine.TEXTURE_NEAREST_SAMPLINGMODE;
+        Logger.Warn("Half float texture filtering is not supported. Mipmap generation and sampling mode are forced to false and TEXTURE_NEAREST_SAMPLINGMODE, respectively.");
+    }
+    else if (textureType === gl.FLOAT && !this._caps.textureFloatRender) {
+        generateMipMaps = false;
+        Logger.Warn("Render to float textures is not supported. Mipmap generation forced to false.");
+    }
+    else if (textureType === gl.HALF_FLOAT && !this._caps.colorBufferFloat) {
+        generateMipMaps = false;
+        Logger.Warn("Render to half float textures is not supported. Mipmap generation forced to false.");
+    }
+
+    var width = size;
+    var height = width;
+
+    texture.width = width;
+    texture.height = height;
+
+    // Double check on POT to generate Mips.
+    var isPot = !this.needPOTTextures || (Tools.IsExponentOfTwo(texture.width) && Tools.IsExponentOfTwo(texture.height));
+    if (!isPot) {
+        generateMipMaps = false;
+    }
+
+    // Upload data if needed. The texture won't be ready until then.
+    if (data) {
+        this.updateRawCubeTexture(texture, data, format, type, invertY, compression);
+    }
+
+    this._bindTextureDirectly(this._gl.TEXTURE_CUBE_MAP, texture, true);
+
+    // Filters
+    if (data && generateMipMaps) {
+        this._gl.generateMipmap(this._gl.TEXTURE_CUBE_MAP);
+    }
+
+    var filters = this._getSamplingParameters(samplingMode, generateMipMaps);
+    gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MAG_FILTER, filters.mag);
+    gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MIN_FILTER, filters.min);
+
+    gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
+    gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
+    this._bindTextureDirectly(gl.TEXTURE_CUBE_MAP, null);
+
+    texture.generateMipMaps = generateMipMaps;
+
+    return texture;
+};
+
+Engine.prototype.updateRawCubeTexture = function(texture: InternalTexture, data: ArrayBufferView[], format: number, type: number, invertY: boolean, compression: Nullable<string> = null, level: number = 0): void {
+    texture._bufferViewArray = data;
+    texture.format = format;
+    texture.type = type;
+    texture.invertY = invertY;
+    texture._compression = compression;
+
+    var gl = this._gl;
+    var textureType = this._getWebGLTextureType(type);
+    var internalFormat = this._getInternalFormat(format);
+    var internalSizedFomat = this._getRGBABufferInternalSizedFormat(type);
+
+    var needConversion = false;
+    if (internalFormat === gl.RGB) {
+        internalFormat = gl.RGBA;
+        needConversion = true;
+    }
+
+    this._bindTextureDirectly(gl.TEXTURE_CUBE_MAP, texture, true);
+    this._unpackFlipY(invertY === undefined ? true : (invertY ? true : false));
+
+    if (texture.width % 4 !== 0) {
+        gl.pixelStorei(gl.UNPACK_ALIGNMENT, 1);
+    }
+
+    // Data are known to be in +X +Y +Z -X -Y -Z
+    for (let faceIndex = 0; faceIndex < 6; faceIndex++) {
+        let faceData = data[faceIndex];
+
+        if (compression) {
+            gl.compressedTexImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + faceIndex, level, (<any>(this.getCaps().s3tc))[compression], texture.width, texture.height, 0, <DataView>faceData);
+        } else {
+            if (needConversion) {
+                faceData = this._convertRGBtoRGBATextureData(faceData, texture.width, texture.height, type);
+            }
+            gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + faceIndex, level, internalSizedFomat, texture.width, texture.height, 0, internalFormat, textureType, faceData);
+        }
+    }
+
+    var isPot = !this.needPOTTextures || (Tools.IsExponentOfTwo(texture.width) && Tools.IsExponentOfTwo(texture.height));
+    if (isPot && texture.generateMipMaps && level === 0) {
+        this._gl.generateMipmap(this._gl.TEXTURE_CUBE_MAP);
+    }
+    this._bindTextureDirectly(this._gl.TEXTURE_CUBE_MAP, null);
+
+    // this.resetTextureCache();
+    texture.isReady = true;
+};
+
+Engine.prototype.createRawCubeTextureFromUrl = function(url: string, scene: Scene, size: number, format: number, type: number, noMipmap: boolean,
+    callback: (ArrayBuffer: ArrayBuffer) => Nullable<ArrayBufferView[]>,
+    mipmapGenerator: Nullable<((faces: ArrayBufferView[]) => ArrayBufferView[][])>,
+    onLoad: Nullable<() => void> = null,
+    onError: Nullable<(message?: string, exception?: any) => void> = null,
+    samplingMode: number = Engine.TEXTURE_TRILINEAR_SAMPLINGMODE,
+    invertY: boolean = false): InternalTexture {
+
+    var gl = this._gl;
+    var texture = this.createRawCubeTexture(null, size, format, type, !noMipmap, invertY, samplingMode);
+    scene._addPendingData(texture);
+    texture.url = url;
+    this._internalTexturesCache.push(texture);
+
+    var onerror = (request?: WebRequest, exception?: any) => {
+        scene._removePendingData(texture);
+        if (onError && request) {
+            onError(request.status + " " + request.statusText, exception);
+        }
+    };
+
+    var internalCallback = (data: any) => {
+        var width = texture.width;
+        var faceDataArrays = callback(data);
+
+        if (!faceDataArrays) {
+            return;
+        }
+
+        if (mipmapGenerator) {
+            var textureType = this._getWebGLTextureType(type);
+            var internalFormat = this._getInternalFormat(format);
+            var internalSizedFomat = this._getRGBABufferInternalSizedFormat(type);
+
+            var needConversion = false;
+            if (internalFormat === gl.RGB) {
+                internalFormat = gl.RGBA;
+                needConversion = true;
+            }
+
+            this._bindTextureDirectly(gl.TEXTURE_CUBE_MAP, texture, true);
+            this._unpackFlipY(false);
+
+            var mipData = mipmapGenerator(faceDataArrays);
+            for (var level = 0; level < mipData.length; level++) {
+                var mipSize = width >> level;
+
+                for (var faceIndex = 0; faceIndex < 6; faceIndex++) {
+                    let mipFaceData = mipData[level][faceIndex];
+                    if (needConversion) {
+                        mipFaceData = this._convertRGBtoRGBATextureData(mipFaceData, mipSize, mipSize, type);
+                    }
+                    gl.texImage2D(faceIndex, level, internalSizedFomat, mipSize, mipSize, 0, internalFormat, textureType, mipFaceData);
+                }
+            }
+
+            this._bindTextureDirectly(gl.TEXTURE_CUBE_MAP, null);
+        }
+        else {
+            this.updateRawCubeTexture(texture, faceDataArrays, format, type, invertY);
+        }
+
+        texture.isReady = true;
+        // this.resetTextureCache();
+        scene._removePendingData(texture);
+
+        if (onLoad) {
+            onLoad();
+        }
+    };
+
+    this._loadFile(url, (data) => {
+        internalCallback(data);
+    }, undefined, scene.offlineProvider, true, onerror);
+
+    return texture;
+};
+
+Engine.prototype.createRawTexture3D = function(data: Nullable<ArrayBufferView>, width: number, height: number, depth: number, format: number, generateMipMaps: boolean, invertY: boolean, samplingMode: number, compression: Nullable<string> = null, textureType: number = Engine.TEXTURETYPE_UNSIGNED_INT): InternalTexture {
+    var texture = new InternalTexture(this, InternalTexture.DATASOURCE_RAW3D);
+    texture.baseWidth = width;
+    texture.baseHeight = height;
+    texture.baseDepth = depth;
+    texture.width = width;
+    texture.height = height;
+    texture.depth = depth;
+    texture.format = format;
+    texture.type = textureType;
+    texture.generateMipMaps = generateMipMaps;
+    texture.samplingMode = samplingMode;
+    texture.is3D = true;
+
+    if (!this._doNotHandleContextLost) {
+        texture._bufferView = data;
+    }
+
+    this.updateRawTexture3D(texture, data, format, invertY, compression, textureType);
+    this._bindTextureDirectly(this._gl.TEXTURE_3D, texture, true);
+
+    // Filters
+    var filters = this._getSamplingParameters(samplingMode, generateMipMaps);
+
+    this._gl.texParameteri(this._gl.TEXTURE_3D, this._gl.TEXTURE_MAG_FILTER, filters.mag);
+    this._gl.texParameteri(this._gl.TEXTURE_3D, this._gl.TEXTURE_MIN_FILTER, filters.min);
+
+    if (generateMipMaps) {
+        this._gl.generateMipmap(this._gl.TEXTURE_3D);
+    }
+
+    this._bindTextureDirectly(this._gl.TEXTURE_3D, null);
+
+    this._internalTexturesCache.push(texture);
+
+    return texture;
+};
+
+Engine.prototype.updateRawTexture3D = function(texture: InternalTexture, data: Nullable<ArrayBufferView>, format: number, invertY: boolean, compression: Nullable<string> = null, textureType: number = Engine.TEXTURETYPE_UNSIGNED_INT): void {
+    var internalType = this._getWebGLTextureType(textureType);
+    var internalFormat = this._getInternalFormat(format);
+    var internalSizedFomat = this._getRGBABufferInternalSizedFormat(textureType, format);
+
+    this._bindTextureDirectly(this._gl.TEXTURE_3D, texture, true);
+    this._unpackFlipY(invertY === undefined ? true : (invertY ? true : false));
+
+    if (!this._doNotHandleContextLost) {
+        texture._bufferView = data;
+        texture.format = format;
+        texture.invertY = invertY;
+        texture._compression = compression;
+    }
+
+    if (texture.width % 4 !== 0) {
+        this._gl.pixelStorei(this._gl.UNPACK_ALIGNMENT, 1);
+    }
+
+    if (compression && data) {
+        this._gl.compressedTexImage3D(this._gl.TEXTURE_3D, 0, (<any>this.getCaps().s3tc)[compression], texture.width, texture.height, texture.depth, 0, data);
+    } else {
+        this._gl.texImage3D(this._gl.TEXTURE_3D, 0, internalSizedFomat, texture.width, texture.height, texture.depth, 0, internalFormat, internalType, data);
+    }
+
+    if (texture.generateMipMaps) {
+        this._gl.generateMipmap(this._gl.TEXTURE_3D);
+    }
+    this._bindTextureDirectly(this._gl.TEXTURE_3D, null);
+    // this.resetTextureCache();
+    texture.isReady = true;
+};

+ 91 - 0
src/Engines/Extensions/engine.renderTarget.ts

@@ -0,0 +1,91 @@
+import { Engine } from "../../Engines/engine";
+import { InternalTexture } from '../../Materials/Textures/internalTexture';
+import { Logger } from '../../Misc/logger';
+import { RenderTargetCreationOptions } from '../../Materials/Textures/renderTargetCreationOptions';
+
+declare module "../../Engines/engine" {
+    export interface Engine {
+        /**
+         * Creates a new render target cube texture
+         * @param size defines the size of the texture
+         * @param options defines the options used to create the texture
+         * @returns a new render target cube texture stored in an InternalTexture
+         */
+        createRenderTargetCubeTexture(size: number, options?: Partial<RenderTargetCreationOptions>): InternalTexture;
+    }
+}
+
+Engine.prototype.createRenderTargetCubeTexture = function(size: number, options?: Partial<RenderTargetCreationOptions>): InternalTexture {
+    let fullOptions = {
+        generateMipMaps: true,
+        generateDepthBuffer: true,
+        generateStencilBuffer: false,
+        type: Engine.TEXTURETYPE_UNSIGNED_INT,
+        samplingMode: Engine.TEXTURE_TRILINEAR_SAMPLINGMODE,
+        format: Engine.TEXTUREFORMAT_RGBA,
+        ...options
+    };
+    fullOptions.generateStencilBuffer = fullOptions.generateDepthBuffer && fullOptions.generateStencilBuffer;
+
+    if (fullOptions.type === Engine.TEXTURETYPE_FLOAT && !this._caps.textureFloatLinearFiltering) {
+        // if floating point linear (gl.FLOAT) then force to NEAREST_SAMPLINGMODE
+        fullOptions.samplingMode = Engine.TEXTURE_NEAREST_SAMPLINGMODE;
+    }
+    else if (fullOptions.type === Engine.TEXTURETYPE_HALF_FLOAT && !this._caps.textureHalfFloatLinearFiltering) {
+        // if floating point linear (HALF_FLOAT) then force to NEAREST_SAMPLINGMODE
+        fullOptions.samplingMode = Engine.TEXTURE_NEAREST_SAMPLINGMODE;
+    }
+    var gl = this._gl;
+
+    var texture = new InternalTexture(this, InternalTexture.DATASOURCE_RENDERTARGET);
+    this._bindTextureDirectly(gl.TEXTURE_CUBE_MAP, texture, true);
+
+    var filters = this._getSamplingParameters(fullOptions.samplingMode, fullOptions.generateMipMaps);
+
+    if (fullOptions.type === Engine.TEXTURETYPE_FLOAT && !this._caps.textureFloat) {
+        fullOptions.type = Engine.TEXTURETYPE_UNSIGNED_INT;
+        Logger.Warn("Float textures are not supported. Cube render target forced to TEXTURETYPE_UNESIGNED_BYTE type");
+    }
+
+    gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MAG_FILTER, filters.mag);
+    gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MIN_FILTER, filters.min);
+    gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
+    gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
+
+    for (var face = 0; face < 6; face++) {
+        gl.texImage2D((gl.TEXTURE_CUBE_MAP_POSITIVE_X + face), 0, this._getRGBABufferInternalSizedFormat(fullOptions.type, fullOptions.format), size, size, 0, this._getInternalFormat(fullOptions.format), this._getWebGLTextureType(fullOptions.type), null);
+    }
+
+    // Create the framebuffer
+    var framebuffer = gl.createFramebuffer();
+    this._bindUnboundFramebuffer(framebuffer);
+
+    texture._depthStencilBuffer = this._setupFramebufferDepthAttachments(fullOptions.generateStencilBuffer, fullOptions.generateDepthBuffer, size, size);
+
+    // MipMaps
+    if (fullOptions.generateMipMaps) {
+        gl.generateMipmap(gl.TEXTURE_CUBE_MAP);
+    }
+
+    // Unbind
+    this._bindTextureDirectly(gl.TEXTURE_CUBE_MAP, null);
+    gl.bindRenderbuffer(gl.RENDERBUFFER, null);
+    this._bindUnboundFramebuffer(null);
+
+    texture._framebuffer = framebuffer;
+    texture.width = size;
+    texture.height = size;
+    texture.isReady = true;
+    texture.isCube = true;
+    texture.samples = 1;
+    texture.generateMipMaps = fullOptions.generateMipMaps;
+    texture.samplingMode = fullOptions.samplingMode;
+    texture.type = fullOptions.type;
+    texture.format = fullOptions.format;
+    texture._generateDepthBuffer = fullOptions.generateDepthBuffer;
+    texture._generateStencilBuffer = fullOptions.generateStencilBuffer;
+
+    this._internalTexturesCache.push(texture);
+
+    return texture;
+};

+ 5 - 1
src/Engines/Extensions/index.ts

@@ -1,3 +1,7 @@
 export * from "./engine.occlusionQuery";
 export * from "./engine.transformFeedback";
-export * from "./engine.multiview";
+export * from "./engine.multiview";
+export * from "./engine.rawTexture";
+export * from "./engine.multiRender";
+export * from "./engine.cubeTexture";
+export * from "./engine.renderTarget";

文件差異過大導致無法顯示
+ 78 - 1203
src/Engines/engine.ts


+ 0 - 0
src/Gizmos/axisScaleGizmo.ts


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