فهرست منبع

Merge branch 'master' into FollowCameraInput_fixPinch

dunk 6 سال پیش
والد
کامیت
23c66f1958
94فایلهای تغییر یافته به همراه8410 افزوده شده و 3524 حذف شده
  1. 3 1
      .vscode/settings.json
  2. 786 397
      Playground/babylon.d.txt
  3. 2 1
      Playground/full.html
  4. 1 1
      Tools/Publisher/tasks/processUMDViewer.js
  5. 727 396
      dist/preview release/babylon.d.ts
  6. 2 2
      dist/preview release/babylon.js
  7. 1083 427
      dist/preview release/babylon.max.js
  8. 1 1
      dist/preview release/babylon.max.js.map
  9. 1539 864
      dist/preview release/babylon.module.d.ts
  10. 1 1
      dist/preview release/glTF2Interface/package.json
  11. 2 1
      dist/preview release/gui/babylon.gui.d.ts
  12. 45 42
      dist/preview release/gui/babylon.gui.js
  13. 1 1
      dist/preview release/gui/babylon.gui.js.map
  14. 1 1
      dist/preview release/gui/babylon.gui.min.js
  15. 4 2
      dist/preview release/gui/babylon.gui.module.d.ts
  16. 2 2
      dist/preview release/gui/package.json
  17. 6 6
      dist/preview release/inspector/babylon.inspector.bundle.js
  18. 519 104
      dist/preview release/inspector/babylon.inspector.bundle.max.js
  19. 1 1
      dist/preview release/inspector/babylon.inspector.bundle.max.js.map
  20. 76 3
      dist/preview release/inspector/babylon.inspector.d.ts
  21. 177 6
      dist/preview release/inspector/babylon.inspector.module.d.ts
  22. 6 6
      dist/preview release/inspector/package.json
  23. 81 19
      dist/preview release/loaders/babylon.objFileLoader.js
  24. 1 1
      dist/preview release/loaders/babylon.objFileLoader.js.map
  25. 1 1
      dist/preview release/loaders/babylon.objFileLoader.min.js
  26. 61 2
      dist/preview release/loaders/babylonjs.loaders.d.ts
  27. 81 19
      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. 123 5
      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. 3 3
      dist/preview release/serializers/package.json
  38. 1539 864
      dist/preview release/viewer/babylon.module.d.ts
  39. 78 70
      dist/preview release/viewer/babylon.viewer.js
  40. 2 2
      dist/preview release/viewer/babylon.viewer.max.js
  41. 9 2
      dist/preview release/what's new.md
  42. 7 3
      gui/src/2D/valueAndUnit.ts
  43. 32 11
      inspector/src/components/actionTabs/actionTabs.scss
  44. 10 2
      inspector/src/components/actionTabs/lineContainerComponent.tsx
  45. 6 0
      inspector/src/components/actionTabs/lines/checkBoxLineComponent.tsx
  46. 1 1
      inspector/src/components/actionTabs/lines/optionsLineComponent.tsx
  47. 2 1
      inspector/src/components/actionTabs/lines/sliderLineComponent.tsx
  48. 1 1
      inspector/src/components/actionTabs/lines/textureLineComponent.tsx
  49. 0 47
      inspector/src/components/actionTabs/tabs/debugTabComponent.tsx
  50. 20 0
      inspector/src/components/actionTabs/tabs/propertyGridTabComponent.tsx
  51. 194 0
      inspector/src/components/actionTabs/tabs/propertyGrids/animationPropertyGridComponent.tsx
  52. 2 2
      inspector/src/components/actionTabs/tabs/propertyGrids/gui/gridPropertyGridComponent.tsx
  53. 52 12
      inspector/src/components/actionTabs/tabs/propertyGrids/materials/pbrMaterialPropertyGridComponent.tsx
  54. 50 0
      inspector/src/components/actionTabs/tabs/propertyGrids/meshes/bonePropertyGridComponent.tsx
  55. 101 0
      inspector/src/components/actionTabs/tabs/propertyGrids/meshes/skeletonPropertyGridComponent.tsx
  56. 1 1
      inspector/src/components/actionTabs/tabs/propertyGrids/postProcesses/commonPostProcessPropertyGridComponent.tsx
  57. 6 0
      inspector/src/components/actionTabs/tabs/propertyGrids/postProcesses/commonRenderingPipelinePropertyGridComponent.tsx
  58. 59 22
      inspector/src/components/actionTabs/tabs/propertyGrids/postProcesses/defaultRenderingPipelinePropertyGridComponent.tsx
  59. 31 0
      inspector/src/components/sceneExplorer/entities/boneTreeItemComponent.tsx
  60. 31 0
      inspector/src/components/sceneExplorer/entities/skeletonTreeItemComponent.tsx
  61. 12 0
      inspector/src/components/sceneExplorer/sceneExplorer.scss
  62. 5 1
      inspector/src/components/sceneExplorer/sceneExplorerComponent.tsx
  63. 12 0
      inspector/src/components/sceneExplorer/treeItemSpecializedComponent.tsx
  64. 121 24
      loaders/src/OBJ/objFileLoader.ts
  65. 2 2
      package.json
  66. 16 0
      src/Bones/bone.ts
  67. 17 3
      src/Bones/skeleton.ts
  68. 1 0
      src/Cameras/Inputs/freeCameraDeviceOrientationInput.ts
  69. 22 1
      src/Cameras/VR/vrExperienceHelper.ts
  70. 21 10
      src/Cameras/arcRotateCamera.ts
  71. 35 2
      src/Cameras/camera.ts
  72. 1 1
      src/Engines/engine.ts
  73. 27 10
      src/Gizmos/boundingBoxGizmo.ts
  74. 68 0
      src/Helpers/videoDome.ts
  75. 4 0
      src/Materials/PBR/pbrBaseMaterial.ts
  76. 141 5
      src/Materials/PBR/pbrClearCoatConfiguration.ts
  77. 7 5
      src/Materials/Textures/baseTexture.ts
  78. 2 2
      src/Materials/Textures/texture.ts
  79. 81 44
      src/Materials/Textures/videoTexture.ts
  80. 16 0
      src/Materials/materialFlags.ts
  81. 5 8
      src/Misc/screenshotTools.ts
  82. 12 10
      src/Misc/tools.ts
  83. 8 0
      src/Misc/videoRecorder.ts
  84. 13 0
      src/PostProcesses/imageProcessingPostProcess.ts
  85. 9 0
      src/Shaders/ShadersInclude/lightFragment.fx
  86. 11 0
      src/Shaders/ShadersInclude/pbrFragmentDeclaration.fx
  87. 53 21
      src/Shaders/ShadersInclude/pbrFunctions.fx
  88. 10 0
      src/Shaders/ShadersInclude/pbrLightingFunctions.fx
  89. 5 0
      src/Shaders/ShadersInclude/pbrUboDeclaration.fx
  90. 5 0
      src/Shaders/ShadersInclude/pbrVertexDeclaration.fx
  91. 49 3
      src/Shaders/pbr.fragment.fx
  92. 15 0
      src/Shaders/pbr.vertex.fx
  93. 13 0
      src/node.ts
  94. 7 1
      src/scene.ts

+ 3 - 1
.vscode/settings.json

@@ -11,14 +11,16 @@
         "**/node_modules": true,
         "**/temp": true,
         "**/.temp": true,
+        "src/**/*.d.ts": true,
         "gui/**/*.d.ts": true,
         "inspector/**/*.d.ts": true,
         "loaders/**/*.d.ts": true,
         "materialsLibrary/**/*.d.ts": true,
         "postProcessesLibrary/**/*.d.ts": true,
         "proceduralTexturesLibrary/**/*.d.ts": true,
-        "serielazers/**/*.d.ts": true,
+        "serializers/**/*.d.ts": true,
         "Viewer/**/*.d.ts": true,
+        "src/**/*.js.map": true,
         "gui/**/*.js.map": true,
         "inspector/**/*.js.map": true,
         "loaders/**/*.js.map": true,

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 786 - 397
Playground/babylon.d.txt


+ 2 - 1
Playground/full.html

@@ -50,7 +50,8 @@
     </head>
 
     <body>
-        <canvas touch-action="none" id="renderCanvas" tabindex="1"></canvas>
+        <!-- Override frame style -->
+        <canvas touch-action="none" id="renderCanvas" tabindex="1" style="height: 100%"></canvas>
         <script src="https://code.jquery.com/jquery.js"></script>
         <script src="js/frame.js"></script>
 

+ 1 - 1
Tools/Publisher/tasks/processUMDViewer.js

@@ -49,7 +49,7 @@ function processUMDViewer(module, version) {
     packageJson.main = "babylon.viewer.js";
     packageJson.typings = "index.d.ts";
 
-    fs.writeFileSync(buildPath + 'package.json', JSON.stringify(packageJson, null, 4));
+    fs.writeFileSync(path.join(buildPath, 'package.json'), JSON.stringify(packageJson, null, 4));
 
     publish(version, "viewer", buildPath);
     colorConsole.emptyLine();

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 727 - 396
dist/preview release/babylon.d.ts


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 2 - 2
dist/preview release/babylon.js


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 1083 - 427
dist/preview release/babylon.max.js


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 1 - 1
dist/preview release/babylon.max.js.map


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 1539 - 864
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-alpha.22",
+    "version": "4.0.0-alpha.25",
     "repository": {
         "type": "git",
         "url": "https://github.com/BabylonJS/Babylon.js.git"

+ 2 - 1
dist/preview release/gui/babylon.gui.d.ts

@@ -54,9 +54,10 @@ declare module BABYLON.GUI {
         /**
          * Gets a string representation of the value
          * @param host defines the root host
+         * @param decimals defines an optional number of decimals to display
          * @returns a string
          */
-        toString(host: AdvancedDynamicTexture): string;
+        toString(host: AdvancedDynamicTexture, decimals?: number): string;
         /**
          * Store a value parsed from a string
          * @param source defines the source string

+ 45 - 42
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"]);
-})(window, function(__WEBPACK_EXTERNAL_MODULE_babylonjs_Misc_tools__) {
+})(window, function(__WEBPACK_EXTERNAL_MODULE_babylonjs_Misc_observable__) {
 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/tools");
+/* 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___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/tools");
+/* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Misc/observable */ "babylonjs/Misc/observable");
 /* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_1__);
 /* harmony import */ var _controls_container__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./controls/container */ "./2D/controls/container.ts");
 /* harmony import */ var _style__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./style */ "./2D/style.ts");
@@ -1613,7 +1613,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/tools");
+/* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Misc/observable */ "babylonjs/Misc/observable");
 /* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_1__);
 /* harmony import */ var _control__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./control */ "./2D/controls/control.ts");
 /* harmony import */ var _stackPanel__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./stackPanel */ "./2D/controls/stackPanel.ts");
@@ -1794,7 +1794,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/tools");
+/* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Misc/observable */ "babylonjs/Misc/observable");
 /* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_1__);
 /* harmony import */ var _control__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./control */ "./2D/controls/control.ts");
 /* harmony import */ var _inputText__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./inputText */ "./2D/controls/inputText.ts");
@@ -3241,7 +3241,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/tools");
+/* harmony import */ var babylonjs_Misc_logger__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Misc/logger */ "babylonjs/Misc/observable");
 /* harmony import */ var babylonjs_Misc_logger__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Misc_logger__WEBPACK_IMPORTED_MODULE_1__);
 /* harmony import */ var _control__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./control */ "./2D/controls/control.ts");
 /* harmony import */ var _measure__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ../measure */ "./2D/measure.ts");
@@ -3646,7 +3646,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/tools");
+/* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Misc/observable */ "babylonjs/Misc/observable");
 /* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_0__);
 /* harmony import */ var _valueAndUnit__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../valueAndUnit */ "./2D/valueAndUnit.ts");
 /* harmony import */ var _measure__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../measure */ "./2D/measure.ts");
@@ -6206,7 +6206,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/tools");
+/* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Misc/observable */ "babylonjs/Misc/observable");
 /* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_1__);
 /* harmony import */ var _control__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./control */ "./2D/controls/control.ts");
 
@@ -6981,7 +6981,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/tools");
+/* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Misc/observable */ "babylonjs/Misc/observable");
 /* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_1__);
 /* harmony import */ var _control__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./control */ "./2D/controls/control.ts");
 /* harmony import */ var _valueAndUnit__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ../valueAndUnit */ "./2D/valueAndUnit.ts");
@@ -7990,7 +7990,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/tools");
+/* 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___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");
@@ -8258,7 +8258,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/tools");
+/* harmony import */ var babylonjs_Meshes_abstractMesh__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Meshes/abstractMesh */ "babylonjs/Misc/observable");
 /* harmony import */ var babylonjs_Meshes_abstractMesh__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Meshes_abstractMesh__WEBPACK_IMPORTED_MODULE_1__);
 /* harmony import */ var _control__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./control */ "./2D/controls/control.ts");
 /* harmony import */ var _multiLinePoint__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ../multiLinePoint */ "./2D/multiLinePoint.ts");
@@ -8525,7 +8525,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/tools");
+/* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Misc/observable */ "babylonjs/Misc/observable");
 /* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_1__);
 /* harmony import */ var _control__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./control */ "./2D/controls/control.ts");
 /* harmony import */ var _stackPanel__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./stackPanel */ "./2D/controls/stackPanel.ts");
@@ -8870,7 +8870,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/tools");
+/* 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___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");
@@ -9961,7 +9961,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/tools");
+/* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Misc/observable */ "babylonjs/Misc/observable");
 /* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_1__);
 /* harmony import */ var _control__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../control */ "./2D/controls/control.ts");
 /* harmony import */ var _valueAndUnit__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ../../valueAndUnit */ "./2D/valueAndUnit.ts");
@@ -10856,7 +10856,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/tools");
+/* harmony import */ var babylonjs_Misc_tools__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Misc/tools */ "babylonjs/Misc/observable");
 /* harmony import */ var babylonjs_Misc_tools__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Misc_tools__WEBPACK_IMPORTED_MODULE_1__);
 /* harmony import */ var _container__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./container */ "./2D/controls/container.ts");
 /* harmony import */ var _control__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./control */ "./2D/controls/control.ts");
@@ -11114,7 +11114,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/tools");
+/* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Misc/observable */ "babylonjs/Misc/observable");
 /* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_1__);
 /* harmony import */ var _valueAndUnit__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../valueAndUnit */ "./2D/valueAndUnit.ts");
 /* harmony import */ var _control__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./control */ "./2D/controls/control.ts");
@@ -11554,7 +11554,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/tools");
+/* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Misc/observable */ "babylonjs/Misc/observable");
 /* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_1__);
 /* harmony import */ var _stackPanel__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./stackPanel */ "./2D/controls/stackPanel.ts");
 /* harmony import */ var _button__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./button */ "./2D/controls/button.ts");
@@ -11929,7 +11929,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/tools");
+/* 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___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Maths_math__WEBPACK_IMPORTED_MODULE_1__);
 
 
@@ -12153,7 +12153,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/tools");
+/* 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___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Maths_math__WEBPACK_IMPORTED_MODULE_0__);
 
 
@@ -12286,7 +12286,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/tools");
+/* 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___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");
 
@@ -12429,7 +12429,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/tools");
+/* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Misc/observable */ "babylonjs/Misc/observable");
 /* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_0__);
 /* harmony import */ var _valueAndUnit__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./valueAndUnit */ "./2D/valueAndUnit.ts");
 
@@ -12649,14 +12649,17 @@ var ValueAndUnit = /** @class */ (function () {
     /**
      * Gets a string representation of the value
      * @param host defines the root host
+     * @param decimals defines an optional number of decimals to display
      * @returns a string
      */
-    ValueAndUnit.prototype.toString = function (host) {
+    ValueAndUnit.prototype.toString = function (host, decimals) {
         switch (this.unit) {
             case ValueAndUnit.UNITMODE_PERCENTAGE:
-                return (this.getValue(host) * 100) + "%";
+                var percentage = this.getValue(host) * 100;
+                return (decimals ? percentage.toFixed(decimals) : percentage) + "%";
             case ValueAndUnit.UNITMODE_PIXEL:
-                return this.getValue(host) + "px";
+                var pixels = this.getValue(host);
+                return (decimals ? pixels.toFixed(decimals) : pixels) + "px";
         }
         return this.unit.toString();
     };
@@ -12733,7 +12736,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/tools");
+/* harmony import */ var babylonjs_Meshes_transformNode__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Meshes/transformNode */ "babylonjs/Misc/observable");
 /* harmony import */ var babylonjs_Meshes_transformNode__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Meshes_transformNode__WEBPACK_IMPORTED_MODULE_1__);
 /* harmony import */ var _control3D__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./control3D */ "./3D/controls/control3D.ts");
 
@@ -12776,7 +12779,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/tools");
+/* 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___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");
@@ -12953,7 +12956,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/tools");
+/* harmony import */ var babylonjs_Meshes_transformNode__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Meshes/transformNode */ "babylonjs/Misc/observable");
 /* harmony import */ var babylonjs_Meshes_transformNode__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Meshes_transformNode__WEBPACK_IMPORTED_MODULE_1__);
 /* harmony import */ var _control3D__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./control3D */ "./3D/controls/control3D.ts");
 
@@ -13110,7 +13113,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/tools");
+/* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Misc/observable */ "babylonjs/Misc/observable");
 /* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_0__);
 /* harmony import */ var _vector3WithInfo__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../vector3WithInfo */ "./3D/vector3WithInfo.ts");
 
@@ -13504,7 +13507,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/tools");
+/* harmony import */ var babylonjs_Misc_tools__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Misc/tools */ "babylonjs/Misc/observable");
 /* harmony import */ var babylonjs_Misc_tools__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Misc_tools__WEBPACK_IMPORTED_MODULE_1__);
 /* harmony import */ var _volumeBasedPanel__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./volumeBasedPanel */ "./3D/controls/volumeBasedPanel.ts");
 /* harmony import */ var _container3D__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./container3D */ "./3D/controls/container3D.ts");
@@ -13589,7 +13592,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/tools");
+/* 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 _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");
@@ -14065,7 +14068,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/tools");
+/* 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___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");
@@ -14120,7 +14123,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/tools");
+/* harmony import */ var babylonjs_Misc_tools__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Misc/tools */ "babylonjs/Misc/observable");
 /* harmony import */ var babylonjs_Misc_tools__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Misc_tools__WEBPACK_IMPORTED_MODULE_1__);
 /* harmony import */ var _volumeBasedPanel__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./volumeBasedPanel */ "./3D/controls/volumeBasedPanel.ts");
 /* harmony import */ var _container3D__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./container3D */ "./3D/controls/container3D.ts");
@@ -14247,7 +14250,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/tools");
+/* harmony import */ var babylonjs_Misc_tools__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Misc/tools */ "babylonjs/Misc/observable");
 /* harmony import */ var babylonjs_Misc_tools__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Misc_tools__WEBPACK_IMPORTED_MODULE_1__);
 /* harmony import */ var _volumeBasedPanel__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./volumeBasedPanel */ "./3D/controls/volumeBasedPanel.ts");
 /* harmony import */ var _container3D__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./container3D */ "./3D/controls/container3D.ts");
@@ -14332,7 +14335,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/tools");
+/* harmony import */ var babylonjs_Misc_tools__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Misc/tools */ "babylonjs/Misc/observable");
 /* harmony import */ var babylonjs_Misc_tools__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Misc_tools__WEBPACK_IMPORTED_MODULE_1__);
 /* harmony import */ var _container3D__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./container3D */ "./3D/controls/container3D.ts");
 
@@ -14457,7 +14460,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/tools");
+/* harmony import */ var babylonjs_Misc_tools__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Misc/tools */ "babylonjs/Misc/observable");
 /* harmony import */ var babylonjs_Misc_tools__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Misc_tools__WEBPACK_IMPORTED_MODULE_1__);
 /* harmony import */ var _container3D__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./container3D */ "./3D/controls/container3D.ts");
 
@@ -14648,7 +14651,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/tools");
+/* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Misc/observable */ "babylonjs/Misc/observable");
 /* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_0__);
 /* harmony import */ var _controls_container3D__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./controls/container3D */ "./3D/controls/container3D.ts");
 
@@ -14915,7 +14918,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/tools");
+/* harmony import */ var babylonjs_Misc_decorators__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Misc/decorators */ "babylonjs/Misc/observable");
 /* harmony import */ var babylonjs_Misc_decorators__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Misc_decorators__WEBPACK_IMPORTED_MODULE_1__);
 /* harmony import */ var _shaders_fluent_vertex__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./shaders/fluent.vertex */ "./3D/materials/shaders/fluent.vertex.ts");
 /* harmony import */ var _shaders_fluent_fragment__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./shaders/fluent.fragment */ "./3D/materials/shaders/fluent.fragment.ts");
@@ -15237,7 +15240,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/tools");
+/* harmony import */ var babylonjs_Materials_effect__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Materials/effect */ "babylonjs/Misc/observable");
 /* harmony import */ var babylonjs_Materials_effect__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Materials_effect__WEBPACK_IMPORTED_MODULE_0__);
 
 var name = 'fluentPixelShader';
@@ -15259,7 +15262,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/tools");
+/* harmony import */ var babylonjs_Materials_effect__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Materials/effect */ "babylonjs/Misc/observable");
 /* harmony import */ var babylonjs_Materials_effect__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Materials_effect__WEBPACK_IMPORTED_MODULE_0__);
 
 var name = 'fluentVertexShader';
@@ -15282,7 +15285,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/tools");
+/* 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___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Maths_math__WEBPACK_IMPORTED_MODULE_1__);
 
 
@@ -15576,14 +15579,14 @@ if (typeof globalObject !== "undefined") {
 
 /***/ }),
 
-/***/ "babylonjs/Misc/tools":
+/***/ "babylonjs/Misc/observable":
 /*!****************************************************************************************************!*\
   !*** external {"root":"BABYLON","commonjs":"babylonjs","commonjs2":"babylonjs","amd":"babylonjs"} ***!
   \****************************************************************************************************/
 /*! no static exports found */
 /***/ (function(module, exports) {
 
-module.exports = __WEBPACK_EXTERNAL_MODULE_babylonjs_Misc_tools__;
+module.exports = __WEBPACK_EXTERNAL_MODULE_babylonjs_Misc_observable__;
 
 /***/ })
 

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 1 - 1
dist/preview release/gui/babylon.gui.js.map


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 1 - 1
dist/preview release/gui/babylon.gui.min.js


+ 4 - 2
dist/preview release/gui/babylon.gui.module.d.ts

@@ -55,9 +55,10 @@ declare module "babylonjs-gui/2D/valueAndUnit" {
         /**
          * Gets a string representation of the value
          * @param host defines the root host
+         * @param decimals defines an optional number of decimals to display
          * @returns a string
          */
-        toString(host: AdvancedDynamicTexture): string;
+        toString(host: AdvancedDynamicTexture, decimals?: number): string;
         /**
          * Store a value parsed from a string
          * @param source defines the source string
@@ -3898,9 +3899,10 @@ declare module BABYLON.GUI {
         /**
          * Gets a string representation of the value
          * @param host defines the root host
+         * @param decimals defines an optional number of decimals to display
          * @returns a string
          */
-        toString(host: AdvancedDynamicTexture): string;
+        toString(host: AdvancedDynamicTexture, decimals?: number): string;
         /**
          * Store a value parsed from a string
          * @param source defines the source string

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

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 6 - 6
dist/preview release/inspector/babylon.inspector.bundle.js


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 519 - 104
dist/preview release/inspector/babylon.inspector.bundle.max.js


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 1 - 1
dist/preview release/inspector/babylon.inspector.bundle.max.js.map


+ 76 - 3
dist/preview release/inspector/babylon.inspector.d.ts

@@ -136,6 +136,7 @@ declare module INSPECTOR {
         propertyName?: string;
         isSelected?: () => boolean;
         onSelect?: (value: boolean) => void;
+        onValueChanged?: () => void;
         onPropertyChangedObservable?: BABYLON.Observable<PropertyChangedEvent>;
     }
     export class CheckBoxLineComponent extends React.Component<ICheckBoxLineComponentProps, {
@@ -169,13 +170,10 @@ declare module INSPECTOR {
 }
 declare module INSPECTOR {
     export class DebugTabComponent extends PaneComponent {
-        private _skeletonViewersEnabled;
         private _physicsViewersEnabled;
-        private _skeletonViewers;
         constructor(props: IPaneComponentProps);
         componentWillMount(): void;
         componentWillUnmount(): void;
-        switchSkeletonViewers(): void;
         switchPhysicsViewers(): void;
         render(): JSX.Element | null;
     }
@@ -1093,6 +1091,59 @@ declare module INSPECTOR {
     }
 }
 declare module INSPECTOR {
+    interface IAnimationGridComponentProps {
+        globalState: GlobalState;
+        animatable: BABYLON.IAnimatable;
+        scene: BABYLON.Scene;
+        lockObject: LockObject;
+        onPropertyChangedObservable?: BABYLON.Observable<PropertyChangedEvent>;
+    }
+    export class AnimationGridComponent extends React.Component<IAnimationGridComponentProps, {
+        currentFrame: number;
+    }> {
+        private _animations;
+        private _ranges;
+        private _animationControl;
+        private _runningAnimatable;
+        private _onBeforeRenderObserver;
+        private _isPlaying;
+        constructor(props: IAnimationGridComponentProps);
+        playOrPause(): void;
+        componentWillMount(): void;
+        componentWillUnmount(): void;
+        onCurrentFrameChange(value: number): void;
+        render(): JSX.Element;
+    }
+}
+declare module INSPECTOR {
+    interface ISkeletonPropertyGridComponentProps {
+        globalState: GlobalState;
+        skeleton: BABYLON.Skeleton;
+        lockObject: LockObject;
+        onPropertyChangedObservable?: BABYLON.Observable<PropertyChangedEvent>;
+    }
+    export class SkeletonPropertyGridComponent extends React.Component<ISkeletonPropertyGridComponentProps> {
+        private _skeletonViewersEnabled;
+        private _skeletonViewers;
+        constructor(props: ISkeletonPropertyGridComponentProps);
+        switchSkeletonViewers(): void;
+        componentWillMount(): void;
+        render(): JSX.Element;
+    }
+}
+declare module INSPECTOR {
+    interface IBonePropertyGridComponentProps {
+        globalState: GlobalState;
+        bone: BABYLON.Bone;
+        lockObject: LockObject;
+        onPropertyChangedObservable?: BABYLON.Observable<PropertyChangedEvent>;
+    }
+    export class BonePropertyGridComponent extends React.Component<IBonePropertyGridComponentProps> {
+        constructor(props: IBonePropertyGridComponentProps);
+        render(): JSX.Element;
+    }
+}
+declare module INSPECTOR {
     export class PropertyGridTabComponent extends PaneComponent {
         private _timerIntervalId;
         private _lockObject;
@@ -1372,6 +1423,28 @@ declare module INSPECTOR {
     }
 }
 declare module INSPECTOR {
+    interface ISkeletonTreeItemComponentProps {
+        skeleton: BABYLON.Skeleton;
+        extensibilityGroups?: BABYLON.IExplorerExtensibilityGroup[];
+        onClick: () => void;
+    }
+    export class SkeletonTreeItemComponent extends React.Component<ISkeletonTreeItemComponentProps> {
+        constructor(props: ISkeletonTreeItemComponentProps);
+        render(): JSX.Element;
+    }
+}
+declare module INSPECTOR {
+    interface IBoneTreeItemComponenttProps {
+        bone: BABYLON.Bone;
+        extensibilityGroups?: BABYLON.IExplorerExtensibilityGroup[];
+        onClick: () => void;
+    }
+    export class BoneTreeItemComponent extends React.Component<IBoneTreeItemComponenttProps> {
+        constructor(props: IBoneTreeItemComponenttProps);
+        render(): JSX.Element;
+    }
+}
+declare module INSPECTOR {
     interface ITreeItemSpecializedComponentProps {
         label: string;
         entity?: any;

+ 177 - 6
dist/preview release/inspector/babylon.inspector.module.d.ts

@@ -161,6 +161,7 @@ declare module "babylonjs-inspector/components/actionTabs/lines/checkBoxLineComp
         propertyName?: string;
         isSelected?: () => boolean;
         onSelect?: (value: boolean) => void;
+        onValueChanged?: () => void;
         onPropertyChangedObservable?: Observable<PropertyChangedEvent>;
     }
     export class CheckBoxLineComponent extends React.Component<ICheckBoxLineComponentProps, {
@@ -198,13 +199,10 @@ declare module "babylonjs-inspector/components/actionTabs/tabs/propertyGrids/ren
 declare module "babylonjs-inspector/components/actionTabs/tabs/debugTabComponent" {
     import { PaneComponent, IPaneComponentProps } from "babylonjs-inspector/components/actionTabs/paneComponent";
     export class DebugTabComponent extends PaneComponent {
-        private _skeletonViewersEnabled;
         private _physicsViewersEnabled;
-        private _skeletonViewers;
         constructor(props: IPaneComponentProps);
         componentWillMount(): void;
         componentWillUnmount(): void;
-        switchSkeletonViewers(): void;
         switchPhysicsViewers(): void;
         render(): JSX.Element | null;
     }
@@ -1428,6 +1426,78 @@ declare module "babylonjs-inspector/components/actionTabs/tabs/propertyGrids/pos
         render(): JSX.Element;
     }
 }
+declare module "babylonjs-inspector/components/actionTabs/tabs/propertyGrids/animationPropertyGridComponent" {
+    import * as React from "react";
+    import { Observable } from "babylonjs/Misc/observable";
+    import { Scene } from "babylonjs/scene";
+    import { PropertyChangedEvent } from "babylonjs-inspector/components/propertyChangedEvent";
+    import { LockObject } from "babylonjs-inspector/components/actionTabs/tabs/propertyGrids/lockObject";
+    import { GlobalState } from "babylonjs-inspector/components/globalState";
+    import { IAnimatable } from 'babylonjs/Misc/tools';
+    interface IAnimationGridComponentProps {
+        globalState: GlobalState;
+        animatable: IAnimatable;
+        scene: Scene;
+        lockObject: LockObject;
+        onPropertyChangedObservable?: Observable<PropertyChangedEvent>;
+    }
+    export class AnimationGridComponent extends React.Component<IAnimationGridComponentProps, {
+        currentFrame: number;
+    }> {
+        private _animations;
+        private _ranges;
+        private _animationControl;
+        private _runningAnimatable;
+        private _onBeforeRenderObserver;
+        private _isPlaying;
+        constructor(props: IAnimationGridComponentProps);
+        playOrPause(): void;
+        componentWillMount(): void;
+        componentWillUnmount(): void;
+        onCurrentFrameChange(value: number): void;
+        render(): JSX.Element;
+    }
+}
+declare module "babylonjs-inspector/components/actionTabs/tabs/propertyGrids/meshes/skeletonPropertyGridComponent" {
+    import * as React from "react";
+    import { Observable } from "babylonjs/Misc/observable";
+    import { PropertyChangedEvent } from "babylonjs-inspector/components/propertyChangedEvent";
+    import { LockObject } from "babylonjs-inspector/components/actionTabs/tabs/propertyGrids/lockObject";
+    import { GlobalState } from "babylonjs-inspector/components/globalState";
+    import { Skeleton } from 'babylonjs/Bones/skeleton';
+    interface ISkeletonPropertyGridComponentProps {
+        globalState: GlobalState;
+        skeleton: Skeleton;
+        lockObject: LockObject;
+        onPropertyChangedObservable?: Observable<PropertyChangedEvent>;
+    }
+    export class SkeletonPropertyGridComponent extends React.Component<ISkeletonPropertyGridComponentProps> {
+        private _skeletonViewersEnabled;
+        private _skeletonViewers;
+        constructor(props: ISkeletonPropertyGridComponentProps);
+        switchSkeletonViewers(): void;
+        componentWillMount(): void;
+        render(): JSX.Element;
+    }
+}
+declare module "babylonjs-inspector/components/actionTabs/tabs/propertyGrids/meshes/bonePropertyGridComponent" {
+    import * as React from "react";
+    import { Observable } from "babylonjs/Misc/observable";
+    import { PropertyChangedEvent } from "babylonjs-inspector/components/propertyChangedEvent";
+    import { LockObject } from "babylonjs-inspector/components/actionTabs/tabs/propertyGrids/lockObject";
+    import { GlobalState } from "babylonjs-inspector/components/globalState";
+    import { Bone } from 'babylonjs/Bones/bone';
+    interface IBonePropertyGridComponentProps {
+        globalState: GlobalState;
+        bone: Bone;
+        lockObject: LockObject;
+        onPropertyChangedObservable?: Observable<PropertyChangedEvent>;
+    }
+    export class BonePropertyGridComponent extends React.Component<IBonePropertyGridComponentProps> {
+        constructor(props: IBonePropertyGridComponentProps);
+        render(): JSX.Element;
+    }
+}
 declare module "babylonjs-inspector/components/actionTabs/tabs/propertyGridTabComponent" {
     import { PaneComponent, IPaneComponentProps } from "babylonjs-inspector/components/actionTabs/paneComponent";
     export class PropertyGridTabComponent extends PaneComponent {
@@ -1757,6 +1827,34 @@ declare module "babylonjs-inspector/components/sceneExplorer/entities/renderingP
         render(): JSX.Element;
     }
 }
+declare module "babylonjs-inspector/components/sceneExplorer/entities/skeletonTreeItemComponent" {
+    import { IExplorerExtensibilityGroup } from "babylonjs/Debug/debugLayer";
+    import * as React from "react";
+    import { Skeleton } from 'babylonjs/Bones/skeleton';
+    interface ISkeletonTreeItemComponentProps {
+        skeleton: Skeleton;
+        extensibilityGroups?: IExplorerExtensibilityGroup[];
+        onClick: () => void;
+    }
+    export class SkeletonTreeItemComponent extends React.Component<ISkeletonTreeItemComponentProps> {
+        constructor(props: ISkeletonTreeItemComponentProps);
+        render(): JSX.Element;
+    }
+}
+declare module "babylonjs-inspector/components/sceneExplorer/entities/boneTreeItemComponent" {
+    import { IExplorerExtensibilityGroup } from "babylonjs/Debug/debugLayer";
+    import * as React from "react";
+    import { Bone } from 'babylonjs/Bones/bone';
+    interface IBoneTreeItemComponenttProps {
+        bone: Bone;
+        extensibilityGroups?: IExplorerExtensibilityGroup[];
+        onClick: () => void;
+    }
+    export class BoneTreeItemComponent extends React.Component<IBoneTreeItemComponenttProps> {
+        constructor(props: IBoneTreeItemComponenttProps);
+        render(): JSX.Element;
+    }
+}
 declare module "babylonjs-inspector/components/sceneExplorer/treeItemSpecializedComponent" {
     import { IExplorerExtensibilityGroup } from "babylonjs/Debug/debugLayer";
     import * as React from "react";
@@ -2173,6 +2271,7 @@ declare module INSPECTOR {
         propertyName?: string;
         isSelected?: () => boolean;
         onSelect?: (value: boolean) => void;
+        onValueChanged?: () => void;
         onPropertyChangedObservable?: BABYLON.Observable<PropertyChangedEvent>;
     }
     export class CheckBoxLineComponent extends React.Component<ICheckBoxLineComponentProps, {
@@ -2206,13 +2305,10 @@ declare module INSPECTOR {
 }
 declare module INSPECTOR {
     export class DebugTabComponent extends PaneComponent {
-        private _skeletonViewersEnabled;
         private _physicsViewersEnabled;
-        private _skeletonViewers;
         constructor(props: IPaneComponentProps);
         componentWillMount(): void;
         componentWillUnmount(): void;
-        switchSkeletonViewers(): void;
         switchPhysicsViewers(): void;
         render(): JSX.Element | null;
     }
@@ -3130,6 +3226,59 @@ declare module INSPECTOR {
     }
 }
 declare module INSPECTOR {
+    interface IAnimationGridComponentProps {
+        globalState: GlobalState;
+        animatable: BABYLON.IAnimatable;
+        scene: BABYLON.Scene;
+        lockObject: LockObject;
+        onPropertyChangedObservable?: BABYLON.Observable<PropertyChangedEvent>;
+    }
+    export class AnimationGridComponent extends React.Component<IAnimationGridComponentProps, {
+        currentFrame: number;
+    }> {
+        private _animations;
+        private _ranges;
+        private _animationControl;
+        private _runningAnimatable;
+        private _onBeforeRenderObserver;
+        private _isPlaying;
+        constructor(props: IAnimationGridComponentProps);
+        playOrPause(): void;
+        componentWillMount(): void;
+        componentWillUnmount(): void;
+        onCurrentFrameChange(value: number): void;
+        render(): JSX.Element;
+    }
+}
+declare module INSPECTOR {
+    interface ISkeletonPropertyGridComponentProps {
+        globalState: GlobalState;
+        skeleton: BABYLON.Skeleton;
+        lockObject: LockObject;
+        onPropertyChangedObservable?: BABYLON.Observable<PropertyChangedEvent>;
+    }
+    export class SkeletonPropertyGridComponent extends React.Component<ISkeletonPropertyGridComponentProps> {
+        private _skeletonViewersEnabled;
+        private _skeletonViewers;
+        constructor(props: ISkeletonPropertyGridComponentProps);
+        switchSkeletonViewers(): void;
+        componentWillMount(): void;
+        render(): JSX.Element;
+    }
+}
+declare module INSPECTOR {
+    interface IBonePropertyGridComponentProps {
+        globalState: GlobalState;
+        bone: BABYLON.Bone;
+        lockObject: LockObject;
+        onPropertyChangedObservable?: BABYLON.Observable<PropertyChangedEvent>;
+    }
+    export class BonePropertyGridComponent extends React.Component<IBonePropertyGridComponentProps> {
+        constructor(props: IBonePropertyGridComponentProps);
+        render(): JSX.Element;
+    }
+}
+declare module INSPECTOR {
     export class PropertyGridTabComponent extends PaneComponent {
         private _timerIntervalId;
         private _lockObject;
@@ -3409,6 +3558,28 @@ declare module INSPECTOR {
     }
 }
 declare module INSPECTOR {
+    interface ISkeletonTreeItemComponentProps {
+        skeleton: BABYLON.Skeleton;
+        extensibilityGroups?: BABYLON.IExplorerExtensibilityGroup[];
+        onClick: () => void;
+    }
+    export class SkeletonTreeItemComponent extends React.Component<ISkeletonTreeItemComponentProps> {
+        constructor(props: ISkeletonTreeItemComponentProps);
+        render(): JSX.Element;
+    }
+}
+declare module INSPECTOR {
+    interface IBoneTreeItemComponenttProps {
+        bone: BABYLON.Bone;
+        extensibilityGroups?: BABYLON.IExplorerExtensibilityGroup[];
+        onClick: () => void;
+    }
+    export class BoneTreeItemComponent extends React.Component<IBoneTreeItemComponenttProps> {
+        constructor(props: IBoneTreeItemComponenttProps);
+        render(): JSX.Element;
+    }
+}
+declare module INSPECTOR {
     interface ITreeItemSpecializedComponentProps {
         label: string;
         entity?: any;

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

@@ -4,7 +4,7 @@
     },
     "name": "babylonjs-inspector",
     "description": "The Babylon.js inspector.",
-    "version": "4.0.0-alpha.22",
+    "version": "4.0.0-alpha.25",
     "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-alpha.22",
-        "babylonjs-gui": "4.0.0-alpha.22",
-        "babylonjs-loaders": "4.0.0-alpha.22",
-        "babylonjs-serializers": "4.0.0-alpha.22",
-        "babylonjs-gltf2interface": "4.0.0-alpha.22"
+        "babylonjs": "4.0.0-alpha.25",
+        "babylonjs-gui": "4.0.0-alpha.25",
+        "babylonjs-loaders": "4.0.0-alpha.25",
+        "babylonjs-serializers": "4.0.0-alpha.25",
+        "babylonjs-gltf2interface": "4.0.0-alpha.25"
     },
     "engines": {
         "node": "*"

+ 81 - 19
dist/preview release/loaders/babylon.objFileLoader.js

@@ -383,7 +383,12 @@ var MTLFileLoader = /** @class */ (function () {
  * This is a babylon scene loader plugin.
  */
 var OBJFileLoader = /** @class */ (function () {
-    function OBJFileLoader() {
+    /**
+     * Creates loader for .OBJ files
+     *
+     * @param meshLoadOptions options for loading and parsing OBJ/MTL files.
+     */
+    function OBJFileLoader(meshLoadOptions) {
         /**
          * Defines the name of the plugin.
          */
@@ -425,7 +430,22 @@ var OBJFileLoader = /** @class */ (function () {
         // f -vertex/-uvs/-normal -vertex/-uvs/-normal -vertex/-uvs/-normal ...
         /** @hidden */
         this.facePattern5 = /f\s+(((-[\d]{1,}\/-[\d]{1,}\/-[\d]{1,}[\s]?){3,})+)/;
+        this._meshLoadOptions = meshLoadOptions || OBJFileLoader.currentMeshLoadOptions;
     }
+    Object.defineProperty(OBJFileLoader, "currentMeshLoadOptions", {
+        get: function () {
+            return {
+                ComputeNormals: OBJFileLoader.COMPUTE_NORMALS,
+                ImportVertexColors: OBJFileLoader.IMPORT_VERTEX_COLORS,
+                InvertY: OBJFileLoader.INVERT_Y,
+                MaterialLoadingFailsSilently: OBJFileLoader.MATERIAL_LOADING_FAILS_SILENTLY,
+                OptimizeWithUV: OBJFileLoader.OPTIMIZE_WITH_UV,
+                SkipMaterials: OBJFileLoader.SKIP_MATERIALS
+            };
+        },
+        enumerable: true,
+        configurable: true
+    });
     /**
      * Calls synchronously the MTL file attached to this obj.
      * Load function or importMesh function don't enable to load 2 files in the same time asynchronously.
@@ -437,11 +457,29 @@ var OBJFileLoader = /** @class */ (function () {
      * @param onSuccess Callback function to be called when the MTL file is loaded
      * @private
      */
-    OBJFileLoader.prototype._loadMTL = function (url, rootUrl, onSuccess) {
+    OBJFileLoader.prototype._loadMTL = function (url, rootUrl, onSuccess, onFailure) {
         //The complete path to the mtl file
         var pathOfFile = babylonjs_Maths_math__WEBPACK_IMPORTED_MODULE_0__["Tools"].BaseUrl + rootUrl + url;
         // Loads through the babylon tools to allow fileInput search.
-        babylonjs_Maths_math__WEBPACK_IMPORTED_MODULE_0__["Tools"].LoadFile(pathOfFile, onSuccess, undefined, undefined, false, function () { console.warn("Error - Unable to load " + pathOfFile); });
+        babylonjs_Maths_math__WEBPACK_IMPORTED_MODULE_0__["Tools"].LoadFile(pathOfFile, onSuccess, undefined, undefined, false, function (request, exception) {
+            onFailure(pathOfFile, exception);
+        });
+    };
+    /**
+     * Instantiates a OBJ file loader plugin.
+     * @returns the created plugin
+     */
+    OBJFileLoader.prototype.createPlugin = function () {
+        return new OBJFileLoader(OBJFileLoader.currentMeshLoadOptions);
+    };
+    /**
+     * If the data string can be loaded directly.
+     *
+     * @param data string containing the file data
+     * @returns if the data can be loaded directly
+     */
+    OBJFileLoader.prototype.canDirectLoad = function (data) {
+        return false;
     };
     /**
      * Imports one or more meshes from the loaded OBJ data and adds them to the scene
@@ -578,7 +616,7 @@ var OBJFileLoader = /** @class */ (function () {
         var setData = function (indicePositionFromObj, indiceUvsFromObj, indiceNormalFromObj, positionVectorFromOBJ, textureVectorFromOBJ, normalsVectorFromOBJ, positionColorsFromOBJ) {
             //Check if this tuple already exists in the list of tuples
             var _index;
-            if (OBJFileLoader.OPTIMIZE_WITH_UV) {
+            if (_this._meshLoadOptions.OptimizeWithUV) {
                 _index = isInArrayUV(tuplePosNorm, [
                     indicePositionFromObj,
                     indiceNormalFromObj,
@@ -614,7 +652,7 @@ var OBJFileLoader = /** @class */ (function () {
                 //Add the tuple in the comparison list
                 tuplePosNorm[indicePositionFromObj].normals.push(indiceNormalFromObj);
                 tuplePosNorm[indicePositionFromObj].idx.push(curPositionInIndices++);
-                if (OBJFileLoader.OPTIMIZE_WITH_UV) {
+                if (_this._meshLoadOptions.OptimizeWithUV) {
                     tuplePosNorm[indicePositionFromObj].uv.push(indiceUvsFromObj);
                 }
             }
@@ -636,7 +674,7 @@ var OBJFileLoader = /** @class */ (function () {
                 unwrappedNormalsForBabylon.push(wrappedNormalsForBabylon[l].x, wrappedNormalsForBabylon[l].y, wrappedNormalsForBabylon[l].z);
                 unwrappedUVForBabylon.push(wrappedUvsForBabylon[l].x, wrappedUvsForBabylon[l].y); //z is an optional value not supported by BABYLON
             }
-            if (OBJFileLoader.IMPORT_VERTEX_COLORS === true) {
+            if (_this._meshLoadOptions.ImportVertexColors === true) {
                 //Push the r, g, b, a values of each element in the unwrapped array
                 unwrappedColorsForBabylon.push(wrappedColorsForBabylon[l].r, wrappedColorsForBabylon[l].g, wrappedColorsForBabylon[l].b, wrappedColorsForBabylon[l].a);
             }
@@ -696,7 +734,7 @@ var OBJFileLoader = /** @class */ (function () {
                 setData(indicePositionFromObj, 0, 0, //In the pattern 1, normals and uvs are not defined
                 positions[indicePositionFromObj], //Get the vectors data
                 babylonjs_Maths_math__WEBPACK_IMPORTED_MODULE_0__["Vector2"].Zero(), babylonjs_Maths_math__WEBPACK_IMPORTED_MODULE_0__["Vector3"].Up(), //Create default vectors
-                OBJFileLoader.IMPORT_VERTEX_COLORS === true ? colors[indicePositionFromObj] : undefined);
+                _this._meshLoadOptions.ImportVertexColors === true ? colors[indicePositionFromObj] : undefined);
             }
             //Reset variable for the next line
             triangles = [];
@@ -721,7 +759,7 @@ var OBJFileLoader = /** @class */ (function () {
                 setData(indicePositionFromObj, indiceUvsFromObj, 0, //Default value for normals
                 positions[indicePositionFromObj], //Get the values for each element
                 uvs[indiceUvsFromObj], babylonjs_Maths_math__WEBPACK_IMPORTED_MODULE_0__["Vector3"].Up(), //Default value for normals
-                OBJFileLoader.IMPORT_VERTEX_COLORS === true ? colors[indicePositionFromObj] : undefined);
+                _this._meshLoadOptions.ImportVertexColors === true ? colors[indicePositionFromObj] : undefined);
             }
             //Reset variable for the next line
             triangles = [];
@@ -768,7 +806,7 @@ var OBJFileLoader = /** @class */ (function () {
                 var indiceNormalFromObj = parseInt(point[1]) - 1;
                 setData(indicePositionFromObj, 1, //Default value for uv
                 indiceNormalFromObj, positions[indicePositionFromObj], //Get each vector of data
-                babylonjs_Maths_math__WEBPACK_IMPORTED_MODULE_0__["Vector2"].Zero(), normals[indiceNormalFromObj], OBJFileLoader.IMPORT_VERTEX_COLORS === true ? colors[indicePositionFromObj] : undefined);
+                babylonjs_Maths_math__WEBPACK_IMPORTED_MODULE_0__["Vector2"].Zero(), normals[indiceNormalFromObj], _this._meshLoadOptions.ImportVertexColors === true ? colors[indicePositionFromObj] : undefined);
             }
             //Reset variable for the next line
             triangles = [];
@@ -793,7 +831,7 @@ var OBJFileLoader = /** @class */ (function () {
                 // Set normal indice
                 var indiceNormalFromObj = normals.length + parseInt(point[2]);
                 setData(indicePositionFromObj, indiceUvsFromObj, indiceNormalFromObj, positions[indicePositionFromObj], uvs[indiceUvsFromObj], normals[indiceNormalFromObj], //Set the vector for each component
-                OBJFileLoader.IMPORT_VERTEX_COLORS === true ? colors[indicePositionFromObj] : undefined);
+                _this._meshLoadOptions.ImportVertexColors === true ? colors[indicePositionFromObj] : undefined);
             }
             //Reset variable for the next line
             triangles = [];
@@ -814,7 +852,7 @@ var OBJFileLoader = /** @class */ (function () {
                 handledMesh.positions = unwrappedPositionsForBabylon.slice();
                 handledMesh.normals = unwrappedNormalsForBabylon.slice();
                 handledMesh.uvs = unwrappedUVForBabylon.slice();
-                if (OBJFileLoader.IMPORT_VERTEX_COLORS === true) {
+                if (_this._meshLoadOptions.ImportVertexColors === true) {
                     handledMesh.colors = unwrappedColorsForBabylon.slice();
                 }
                 //Reset the array for the next mesh
@@ -843,7 +881,7 @@ var OBJFileLoader = /** @class */ (function () {
                 // ["v", "1.0", "2.0", "3.0"]
                 //Create a Vector3 with the position x, y, z
                 positions.push(new babylonjs_Maths_math__WEBPACK_IMPORTED_MODULE_0__["Vector3"](parseFloat(result[1]), parseFloat(result[2]), parseFloat(result[3])));
-                if (OBJFileLoader.IMPORT_VERTEX_COLORS === true) {
+                if (this._meshLoadOptions.ImportVertexColors === true) {
                     if (result.length >= 7) {
                         // TODO: if these numbers are > 1 we can use Color4.FromInts(r,g,b,a)
                         colors.push(new babylonjs_Maths_math__WEBPACK_IMPORTED_MODULE_0__["Color4"](parseFloat(result[4]), parseFloat(result[5]), parseFloat(result[6]), (result.length === 7 || result[7] === undefined) ? 1 : parseFloat(result[7])));
@@ -987,7 +1025,7 @@ var OBJFileLoader = /** @class */ (function () {
             handledMesh.positions = unwrappedPositionsForBabylon;
             handledMesh.normals = unwrappedNormalsForBabylon;
             handledMesh.uvs = unwrappedUVForBabylon;
-            if (OBJFileLoader.IMPORT_VERTEX_COLORS === true) {
+            if (this._meshLoadOptions.ImportVertexColors === true) {
                 handledMesh.colors = unwrappedColorsForBabylon;
             }
         }
@@ -1039,7 +1077,7 @@ var OBJFileLoader = /** @class */ (function () {
             vertexData.uvs = handledMesh.uvs;
             vertexData.indices = handledMesh.indices;
             vertexData.positions = handledMesh.positions;
-            if (OBJFileLoader.COMPUTE_NORMALS === true) {
+            if (this._meshLoadOptions.ComputeNormals === true) {
                 var normals_1 = new Array();
                 babylonjs_Maths_math__WEBPACK_IMPORTED_MODULE_0__["VertexData"].ComputeNormals(handledMesh.positions, handledMesh.indices, normals_1);
                 vertexData.normals = normals_1;
@@ -1047,12 +1085,12 @@ var OBJFileLoader = /** @class */ (function () {
             else {
                 vertexData.normals = handledMesh.normals;
             }
-            if (OBJFileLoader.IMPORT_VERTEX_COLORS === true) {
+            if (this._meshLoadOptions.ImportVertexColors === true) {
                 vertexData.colors = handledMesh.colors;
             }
             //Set the data from the VertexBuffer to the current Mesh
             vertexData.applyToMesh(babylonMesh);
-            if (OBJFileLoader.INVERT_Y) {
+            if (this._meshLoadOptions.InvertY) {
                 babylonMesh.scaling.y *= -1;
             }
             //Push the mesh into an array
@@ -1061,7 +1099,7 @@ var OBJFileLoader = /** @class */ (function () {
         var mtlPromises = [];
         //load the materials
         //Check if we have a file to load
-        if (fileToLoad !== "") {
+        if (fileToLoad !== "" && this._meshLoadOptions.SkipMaterials === false) {
             //Load the file synchronously
             mtlPromises.push(new Promise(function (resolve, reject) {
                 _this._loadMTL(fileToLoad, rootUrl, function (dataLoaded) {
@@ -1096,7 +1134,21 @@ var OBJFileLoader = /** @class */ (function () {
                         resolve();
                     }
                     catch (e) {
-                        reject(e);
+                        babylonjs_Maths_math__WEBPACK_IMPORTED_MODULE_0__["Tools"].Warn("Error processing MTL file: '" + fileToLoad + "'");
+                        if (_this._meshLoadOptions.MaterialLoadingFailsSilently) {
+                            resolve();
+                        }
+                        else {
+                            reject(e);
+                        }
+                    }
+                }, function (pathOfFile, exception) {
+                    babylonjs_Maths_math__WEBPACK_IMPORTED_MODULE_0__["Tools"].Warn("Error downloading MTL file: '" + fileToLoad + "'");
+                    if (_this._meshLoadOptions.MaterialLoadingFailsSilently) {
+                        resolve();
+                    }
+                    else {
+                        reject(exception);
                     }
                 });
             }));
@@ -1119,9 +1171,19 @@ var OBJFileLoader = /** @class */ (function () {
      */
     OBJFileLoader.IMPORT_VERTEX_COLORS = false;
     /**
-     * Compute the normals for the model, even if normals are present in the file
+     * Compute the normals for the model, even if normals are present in the file.
      */
     OBJFileLoader.COMPUTE_NORMALS = false;
+    /**
+     * Skip loading the materials even if defined in the OBJ file (materials are ignored).
+     */
+    OBJFileLoader.SKIP_MATERIALS = false;
+    /**
+     * When a material fails to load OBJ loader will silently fail and onSuccess() callback will be triggered.
+     *
+     * Defaults to true for backwards compatibility.
+     */
+    OBJFileLoader.MATERIAL_LOADING_FAILS_SILENTLY = true;
     return OBJFileLoader;
 }());
 

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 1 - 1
dist/preview release/loaders/babylon.objFileLoader.js.map


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 1 - 1
dist/preview release/loaders/babylon.objFileLoader.min.js


+ 61 - 2
dist/preview release/loaders/babylonjs.loaders.d.ts

@@ -1758,10 +1758,39 @@ declare module BABYLON {
         private static _getTexture;
     }
     /**
+     * Options for loading OBJ/MTL files
+     */
+    type MeshLoadOptions = {
+        /**
+         * Defines if UVs are optimized by default during load.
+         */
+        OptimizeWithUV: boolean;
+        /**
+         * Invert model on y-axis (does a model scaling inversion)
+         */
+        InvertY: boolean;
+        /**
+         * Include in meshes the vertex colors available in some OBJ files.  This is not part of OBJ standard.
+         */
+        ImportVertexColors: boolean;
+        /**
+         * Compute the normals for the model, even if normals are present in the file.
+         */
+        ComputeNormals: boolean;
+        /**
+         * Skip loading the materials even if defined in the OBJ file (materials are ignored).
+         */
+        SkipMaterials: boolean;
+        /**
+         * When a material fails to load OBJ loader will silently fail and onSuccess() callback will be triggered.
+         */
+        MaterialLoadingFailsSilently: boolean;
+    };
+    /**
      * OBJ file type loader.
      * This is a babylon scene loader plugin.
      */
-    export class OBJFileLoader implements ISceneLoaderPluginAsync {
+    export class OBJFileLoader implements ISceneLoaderPluginAsync, ISceneLoaderPluginFactory {
         /**
          * Defines if UVs are optimized by default during load.
          */
@@ -1775,10 +1804,20 @@ declare module BABYLON {
          */
         static IMPORT_VERTEX_COLORS: boolean;
         /**
-         * Compute the normals for the model, even if normals are present in the file
+         * Compute the normals for the model, even if normals are present in the file.
          */
         static COMPUTE_NORMALS: boolean;
         /**
+         * Skip loading the materials even if defined in the OBJ file (materials are ignored).
+         */
+        static SKIP_MATERIALS: boolean;
+        /**
+         * When a material fails to load OBJ loader will silently fail and onSuccess() callback will be triggered.
+         *
+         * Defaults to true for backwards compatibility.
+         */
+        static MATERIAL_LOADING_FAILS_SILENTLY: boolean;
+        /**
          * Defines the name of the plugin.
          */
         name: string;
@@ -1812,6 +1851,14 @@ declare module BABYLON {
         facePattern4: RegExp;
         /** @hidden */
         facePattern5: RegExp;
+        private _meshLoadOptions;
+        /**
+         * Creates loader for .OBJ files
+         *
+         * @param meshLoadOptions options for loading and parsing OBJ/MTL files.
+         */
+        constructor(meshLoadOptions?: MeshLoadOptions);
+        private static readonly currentMeshLoadOptions;
         /**
          * Calls synchronously the MTL file attached to this obj.
          * Load function or importMesh function don't enable to load 2 files in the same time asynchronously.
@@ -1825,6 +1872,18 @@ declare module BABYLON {
          */
         private _loadMTL;
         /**
+         * Instantiates a OBJ file loader plugin.
+         * @returns the created plugin
+         */
+        createPlugin(): ISceneLoaderPluginAsync | ISceneLoaderPlugin;
+        /**
+         * If the data string can be loaded directly.
+         *
+         * @param data string containing the file data
+         * @returns if the data can be loaded directly
+         */
+        canDirectLoad(data: string): boolean;
+        /**
          * Imports one or more meshes from the loaded OBJ data and adds them to the scene
          * @param meshesNames a string or array of strings of the mesh names that should be loaded from the file
          * @param scene the scene the meshes should be added to

+ 81 - 19
dist/preview release/loaders/babylonjs.loaders.js

@@ -601,7 +601,12 @@ var MTLFileLoader = /** @class */ (function () {
  * This is a babylon scene loader plugin.
  */
 var OBJFileLoader = /** @class */ (function () {
-    function OBJFileLoader() {
+    /**
+     * Creates loader for .OBJ files
+     *
+     * @param meshLoadOptions options for loading and parsing OBJ/MTL files.
+     */
+    function OBJFileLoader(meshLoadOptions) {
         /**
          * Defines the name of the plugin.
          */
@@ -643,7 +648,22 @@ var OBJFileLoader = /** @class */ (function () {
         // f -vertex/-uvs/-normal -vertex/-uvs/-normal -vertex/-uvs/-normal ...
         /** @hidden */
         this.facePattern5 = /f\s+(((-[\d]{1,}\/-[\d]{1,}\/-[\d]{1,}[\s]?){3,})+)/;
+        this._meshLoadOptions = meshLoadOptions || OBJFileLoader.currentMeshLoadOptions;
     }
+    Object.defineProperty(OBJFileLoader, "currentMeshLoadOptions", {
+        get: function () {
+            return {
+                ComputeNormals: OBJFileLoader.COMPUTE_NORMALS,
+                ImportVertexColors: OBJFileLoader.IMPORT_VERTEX_COLORS,
+                InvertY: OBJFileLoader.INVERT_Y,
+                MaterialLoadingFailsSilently: OBJFileLoader.MATERIAL_LOADING_FAILS_SILENTLY,
+                OptimizeWithUV: OBJFileLoader.OPTIMIZE_WITH_UV,
+                SkipMaterials: OBJFileLoader.SKIP_MATERIALS
+            };
+        },
+        enumerable: true,
+        configurable: true
+    });
     /**
      * Calls synchronously the MTL file attached to this obj.
      * Load function or importMesh function don't enable to load 2 files in the same time asynchronously.
@@ -655,11 +675,29 @@ var OBJFileLoader = /** @class */ (function () {
      * @param onSuccess Callback function to be called when the MTL file is loaded
      * @private
      */
-    OBJFileLoader.prototype._loadMTL = function (url, rootUrl, onSuccess) {
+    OBJFileLoader.prototype._loadMTL = function (url, rootUrl, onSuccess, onFailure) {
         //The complete path to the mtl file
         var pathOfFile = babylonjs_Maths_math__WEBPACK_IMPORTED_MODULE_0__["Tools"].BaseUrl + rootUrl + url;
         // Loads through the babylon tools to allow fileInput search.
-        babylonjs_Maths_math__WEBPACK_IMPORTED_MODULE_0__["Tools"].LoadFile(pathOfFile, onSuccess, undefined, undefined, false, function () { console.warn("Error - Unable to load " + pathOfFile); });
+        babylonjs_Maths_math__WEBPACK_IMPORTED_MODULE_0__["Tools"].LoadFile(pathOfFile, onSuccess, undefined, undefined, false, function (request, exception) {
+            onFailure(pathOfFile, exception);
+        });
+    };
+    /**
+     * Instantiates a OBJ file loader plugin.
+     * @returns the created plugin
+     */
+    OBJFileLoader.prototype.createPlugin = function () {
+        return new OBJFileLoader(OBJFileLoader.currentMeshLoadOptions);
+    };
+    /**
+     * If the data string can be loaded directly.
+     *
+     * @param data string containing the file data
+     * @returns if the data can be loaded directly
+     */
+    OBJFileLoader.prototype.canDirectLoad = function (data) {
+        return false;
     };
     /**
      * Imports one or more meshes from the loaded OBJ data and adds them to the scene
@@ -796,7 +834,7 @@ var OBJFileLoader = /** @class */ (function () {
         var setData = function (indicePositionFromObj, indiceUvsFromObj, indiceNormalFromObj, positionVectorFromOBJ, textureVectorFromOBJ, normalsVectorFromOBJ, positionColorsFromOBJ) {
             //Check if this tuple already exists in the list of tuples
             var _index;
-            if (OBJFileLoader.OPTIMIZE_WITH_UV) {
+            if (_this._meshLoadOptions.OptimizeWithUV) {
                 _index = isInArrayUV(tuplePosNorm, [
                     indicePositionFromObj,
                     indiceNormalFromObj,
@@ -832,7 +870,7 @@ var OBJFileLoader = /** @class */ (function () {
                 //Add the tuple in the comparison list
                 tuplePosNorm[indicePositionFromObj].normals.push(indiceNormalFromObj);
                 tuplePosNorm[indicePositionFromObj].idx.push(curPositionInIndices++);
-                if (OBJFileLoader.OPTIMIZE_WITH_UV) {
+                if (_this._meshLoadOptions.OptimizeWithUV) {
                     tuplePosNorm[indicePositionFromObj].uv.push(indiceUvsFromObj);
                 }
             }
@@ -854,7 +892,7 @@ var OBJFileLoader = /** @class */ (function () {
                 unwrappedNormalsForBabylon.push(wrappedNormalsForBabylon[l].x, wrappedNormalsForBabylon[l].y, wrappedNormalsForBabylon[l].z);
                 unwrappedUVForBabylon.push(wrappedUvsForBabylon[l].x, wrappedUvsForBabylon[l].y); //z is an optional value not supported by BABYLON
             }
-            if (OBJFileLoader.IMPORT_VERTEX_COLORS === true) {
+            if (_this._meshLoadOptions.ImportVertexColors === true) {
                 //Push the r, g, b, a values of each element in the unwrapped array
                 unwrappedColorsForBabylon.push(wrappedColorsForBabylon[l].r, wrappedColorsForBabylon[l].g, wrappedColorsForBabylon[l].b, wrappedColorsForBabylon[l].a);
             }
@@ -914,7 +952,7 @@ var OBJFileLoader = /** @class */ (function () {
                 setData(indicePositionFromObj, 0, 0, //In the pattern 1, normals and uvs are not defined
                 positions[indicePositionFromObj], //Get the vectors data
                 babylonjs_Maths_math__WEBPACK_IMPORTED_MODULE_0__["Vector2"].Zero(), babylonjs_Maths_math__WEBPACK_IMPORTED_MODULE_0__["Vector3"].Up(), //Create default vectors
-                OBJFileLoader.IMPORT_VERTEX_COLORS === true ? colors[indicePositionFromObj] : undefined);
+                _this._meshLoadOptions.ImportVertexColors === true ? colors[indicePositionFromObj] : undefined);
             }
             //Reset variable for the next line
             triangles = [];
@@ -939,7 +977,7 @@ var OBJFileLoader = /** @class */ (function () {
                 setData(indicePositionFromObj, indiceUvsFromObj, 0, //Default value for normals
                 positions[indicePositionFromObj], //Get the values for each element
                 uvs[indiceUvsFromObj], babylonjs_Maths_math__WEBPACK_IMPORTED_MODULE_0__["Vector3"].Up(), //Default value for normals
-                OBJFileLoader.IMPORT_VERTEX_COLORS === true ? colors[indicePositionFromObj] : undefined);
+                _this._meshLoadOptions.ImportVertexColors === true ? colors[indicePositionFromObj] : undefined);
             }
             //Reset variable for the next line
             triangles = [];
@@ -986,7 +1024,7 @@ var OBJFileLoader = /** @class */ (function () {
                 var indiceNormalFromObj = parseInt(point[1]) - 1;
                 setData(indicePositionFromObj, 1, //Default value for uv
                 indiceNormalFromObj, positions[indicePositionFromObj], //Get each vector of data
-                babylonjs_Maths_math__WEBPACK_IMPORTED_MODULE_0__["Vector2"].Zero(), normals[indiceNormalFromObj], OBJFileLoader.IMPORT_VERTEX_COLORS === true ? colors[indicePositionFromObj] : undefined);
+                babylonjs_Maths_math__WEBPACK_IMPORTED_MODULE_0__["Vector2"].Zero(), normals[indiceNormalFromObj], _this._meshLoadOptions.ImportVertexColors === true ? colors[indicePositionFromObj] : undefined);
             }
             //Reset variable for the next line
             triangles = [];
@@ -1011,7 +1049,7 @@ var OBJFileLoader = /** @class */ (function () {
                 // Set normal indice
                 var indiceNormalFromObj = normals.length + parseInt(point[2]);
                 setData(indicePositionFromObj, indiceUvsFromObj, indiceNormalFromObj, positions[indicePositionFromObj], uvs[indiceUvsFromObj], normals[indiceNormalFromObj], //Set the vector for each component
-                OBJFileLoader.IMPORT_VERTEX_COLORS === true ? colors[indicePositionFromObj] : undefined);
+                _this._meshLoadOptions.ImportVertexColors === true ? colors[indicePositionFromObj] : undefined);
             }
             //Reset variable for the next line
             triangles = [];
@@ -1032,7 +1070,7 @@ var OBJFileLoader = /** @class */ (function () {
                 handledMesh.positions = unwrappedPositionsForBabylon.slice();
                 handledMesh.normals = unwrappedNormalsForBabylon.slice();
                 handledMesh.uvs = unwrappedUVForBabylon.slice();
-                if (OBJFileLoader.IMPORT_VERTEX_COLORS === true) {
+                if (_this._meshLoadOptions.ImportVertexColors === true) {
                     handledMesh.colors = unwrappedColorsForBabylon.slice();
                 }
                 //Reset the array for the next mesh
@@ -1061,7 +1099,7 @@ var OBJFileLoader = /** @class */ (function () {
                 // ["v", "1.0", "2.0", "3.0"]
                 //Create a Vector3 with the position x, y, z
                 positions.push(new babylonjs_Maths_math__WEBPACK_IMPORTED_MODULE_0__["Vector3"](parseFloat(result[1]), parseFloat(result[2]), parseFloat(result[3])));
-                if (OBJFileLoader.IMPORT_VERTEX_COLORS === true) {
+                if (this._meshLoadOptions.ImportVertexColors === true) {
                     if (result.length >= 7) {
                         // TODO: if these numbers are > 1 we can use Color4.FromInts(r,g,b,a)
                         colors.push(new babylonjs_Maths_math__WEBPACK_IMPORTED_MODULE_0__["Color4"](parseFloat(result[4]), parseFloat(result[5]), parseFloat(result[6]), (result.length === 7 || result[7] === undefined) ? 1 : parseFloat(result[7])));
@@ -1205,7 +1243,7 @@ var OBJFileLoader = /** @class */ (function () {
             handledMesh.positions = unwrappedPositionsForBabylon;
             handledMesh.normals = unwrappedNormalsForBabylon;
             handledMesh.uvs = unwrappedUVForBabylon;
-            if (OBJFileLoader.IMPORT_VERTEX_COLORS === true) {
+            if (this._meshLoadOptions.ImportVertexColors === true) {
                 handledMesh.colors = unwrappedColorsForBabylon;
             }
         }
@@ -1257,7 +1295,7 @@ var OBJFileLoader = /** @class */ (function () {
             vertexData.uvs = handledMesh.uvs;
             vertexData.indices = handledMesh.indices;
             vertexData.positions = handledMesh.positions;
-            if (OBJFileLoader.COMPUTE_NORMALS === true) {
+            if (this._meshLoadOptions.ComputeNormals === true) {
                 var normals_1 = new Array();
                 babylonjs_Maths_math__WEBPACK_IMPORTED_MODULE_0__["VertexData"].ComputeNormals(handledMesh.positions, handledMesh.indices, normals_1);
                 vertexData.normals = normals_1;
@@ -1265,12 +1303,12 @@ var OBJFileLoader = /** @class */ (function () {
             else {
                 vertexData.normals = handledMesh.normals;
             }
-            if (OBJFileLoader.IMPORT_VERTEX_COLORS === true) {
+            if (this._meshLoadOptions.ImportVertexColors === true) {
                 vertexData.colors = handledMesh.colors;
             }
             //Set the data from the VertexBuffer to the current Mesh
             vertexData.applyToMesh(babylonMesh);
-            if (OBJFileLoader.INVERT_Y) {
+            if (this._meshLoadOptions.InvertY) {
                 babylonMesh.scaling.y *= -1;
             }
             //Push the mesh into an array
@@ -1279,7 +1317,7 @@ var OBJFileLoader = /** @class */ (function () {
         var mtlPromises = [];
         //load the materials
         //Check if we have a file to load
-        if (fileToLoad !== "") {
+        if (fileToLoad !== "" && this._meshLoadOptions.SkipMaterials === false) {
             //Load the file synchronously
             mtlPromises.push(new Promise(function (resolve, reject) {
                 _this._loadMTL(fileToLoad, rootUrl, function (dataLoaded) {
@@ -1314,7 +1352,21 @@ var OBJFileLoader = /** @class */ (function () {
                         resolve();
                     }
                     catch (e) {
-                        reject(e);
+                        babylonjs_Maths_math__WEBPACK_IMPORTED_MODULE_0__["Tools"].Warn("Error processing MTL file: '" + fileToLoad + "'");
+                        if (_this._meshLoadOptions.MaterialLoadingFailsSilently) {
+                            resolve();
+                        }
+                        else {
+                            reject(e);
+                        }
+                    }
+                }, function (pathOfFile, exception) {
+                    babylonjs_Maths_math__WEBPACK_IMPORTED_MODULE_0__["Tools"].Warn("Error downloading MTL file: '" + fileToLoad + "'");
+                    if (_this._meshLoadOptions.MaterialLoadingFailsSilently) {
+                        resolve();
+                    }
+                    else {
+                        reject(exception);
                     }
                 });
             }));
@@ -1337,9 +1389,19 @@ var OBJFileLoader = /** @class */ (function () {
      */
     OBJFileLoader.IMPORT_VERTEX_COLORS = false;
     /**
-     * Compute the normals for the model, even if normals are present in the file
+     * Compute the normals for the model, even if normals are present in the file.
      */
     OBJFileLoader.COMPUTE_NORMALS = false;
+    /**
+     * Skip loading the materials even if defined in the OBJ file (materials are ignored).
+     */
+    OBJFileLoader.SKIP_MATERIALS = false;
+    /**
+     * When a material fails to load OBJ loader will silently fail and onSuccess() callback will be triggered.
+     *
+     * Defaults to true for backwards compatibility.
+     */
+    OBJFileLoader.MATERIAL_LOADING_FAILS_SILENTLY = true;
     return OBJFileLoader;
 }());
 

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 1 - 1
dist/preview release/loaders/babylonjs.loaders.js.map


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 2 - 2
dist/preview release/loaders/babylonjs.loaders.min.js


+ 123 - 5
dist/preview release/loaders/babylonjs.loaders.module.d.ts

@@ -1893,7 +1893,7 @@ declare module "babylonjs-loaders/OBJ/objFileLoader" {
     import { IParticleSystem } from "babylonjs/Particles/IParticleSystem";
     import { StandardMaterial } from "babylonjs/Materials/standardMaterial";
     import { AbstractMesh } from "babylonjs/Meshes/abstractMesh";
-    import { ISceneLoaderPluginAsync, SceneLoaderProgressEvent } from "babylonjs/Loading/sceneLoader";
+    import { ISceneLoaderPluginAsync, SceneLoaderProgressEvent, ISceneLoaderPluginFactory, ISceneLoaderPlugin } from "babylonjs/Loading/sceneLoader";
     import { AssetContainer } from "babylonjs/assetContainer";
     import { Scene } from "babylonjs/scene";
     /**
@@ -1928,10 +1928,39 @@ declare module "babylonjs-loaders/OBJ/objFileLoader" {
         private static _getTexture;
     }
     /**
+     * Options for loading OBJ/MTL files
+     */
+    type MeshLoadOptions = {
+        /**
+         * Defines if UVs are optimized by default during load.
+         */
+        OptimizeWithUV: boolean;
+        /**
+         * Invert model on y-axis (does a model scaling inversion)
+         */
+        InvertY: boolean;
+        /**
+         * Include in meshes the vertex colors available in some OBJ files.  This is not part of OBJ standard.
+         */
+        ImportVertexColors: boolean;
+        /**
+         * Compute the normals for the model, even if normals are present in the file.
+         */
+        ComputeNormals: boolean;
+        /**
+         * Skip loading the materials even if defined in the OBJ file (materials are ignored).
+         */
+        SkipMaterials: boolean;
+        /**
+         * When a material fails to load OBJ loader will silently fail and onSuccess() callback will be triggered.
+         */
+        MaterialLoadingFailsSilently: boolean;
+    };
+    /**
      * OBJ file type loader.
      * This is a babylon scene loader plugin.
      */
-    export class OBJFileLoader implements ISceneLoaderPluginAsync {
+    export class OBJFileLoader implements ISceneLoaderPluginAsync, ISceneLoaderPluginFactory {
         /**
          * Defines if UVs are optimized by default during load.
          */
@@ -1945,10 +1974,20 @@ declare module "babylonjs-loaders/OBJ/objFileLoader" {
          */
         static IMPORT_VERTEX_COLORS: boolean;
         /**
-         * Compute the normals for the model, even if normals are present in the file
+         * Compute the normals for the model, even if normals are present in the file.
          */
         static COMPUTE_NORMALS: boolean;
         /**
+         * Skip loading the materials even if defined in the OBJ file (materials are ignored).
+         */
+        static SKIP_MATERIALS: boolean;
+        /**
+         * When a material fails to load OBJ loader will silently fail and onSuccess() callback will be triggered.
+         *
+         * Defaults to true for backwards compatibility.
+         */
+        static MATERIAL_LOADING_FAILS_SILENTLY: boolean;
+        /**
          * Defines the name of the plugin.
          */
         name: string;
@@ -1982,6 +2021,14 @@ declare module "babylonjs-loaders/OBJ/objFileLoader" {
         facePattern4: RegExp;
         /** @hidden */
         facePattern5: RegExp;
+        private _meshLoadOptions;
+        /**
+         * Creates loader for .OBJ files
+         *
+         * @param meshLoadOptions options for loading and parsing OBJ/MTL files.
+         */
+        constructor(meshLoadOptions?: MeshLoadOptions);
+        private static readonly currentMeshLoadOptions;
         /**
          * Calls synchronously the MTL file attached to this obj.
          * Load function or importMesh function don't enable to load 2 files in the same time asynchronously.
@@ -1995,6 +2042,18 @@ declare module "babylonjs-loaders/OBJ/objFileLoader" {
          */
         private _loadMTL;
         /**
+         * Instantiates a OBJ file loader plugin.
+         * @returns the created plugin
+         */
+        createPlugin(): ISceneLoaderPluginAsync | ISceneLoaderPlugin;
+        /**
+         * If the data string can be loaded directly.
+         *
+         * @param data string containing the file data
+         * @returns if the data can be loaded directly
+         */
+        canDirectLoad(data: string): boolean;
+        /**
          * Imports one or more meshes from the loaded OBJ data and adds them to the scene
          * @param meshesNames a string or array of strings of the mesh names that should be loaded from the file
          * @param scene the scene the meshes should be added to
@@ -3924,10 +3983,39 @@ declare module BABYLON {
         private static _getTexture;
     }
     /**
+     * Options for loading OBJ/MTL files
+     */
+    type MeshLoadOptions = {
+        /**
+         * Defines if UVs are optimized by default during load.
+         */
+        OptimizeWithUV: boolean;
+        /**
+         * Invert model on y-axis (does a model scaling inversion)
+         */
+        InvertY: boolean;
+        /**
+         * Include in meshes the vertex colors available in some OBJ files.  This is not part of OBJ standard.
+         */
+        ImportVertexColors: boolean;
+        /**
+         * Compute the normals for the model, even if normals are present in the file.
+         */
+        ComputeNormals: boolean;
+        /**
+         * Skip loading the materials even if defined in the OBJ file (materials are ignored).
+         */
+        SkipMaterials: boolean;
+        /**
+         * When a material fails to load OBJ loader will silently fail and onSuccess() callback will be triggered.
+         */
+        MaterialLoadingFailsSilently: boolean;
+    };
+    /**
      * OBJ file type loader.
      * This is a babylon scene loader plugin.
      */
-    export class OBJFileLoader implements ISceneLoaderPluginAsync {
+    export class OBJFileLoader implements ISceneLoaderPluginAsync, ISceneLoaderPluginFactory {
         /**
          * Defines if UVs are optimized by default during load.
          */
@@ -3941,10 +4029,20 @@ declare module BABYLON {
          */
         static IMPORT_VERTEX_COLORS: boolean;
         /**
-         * Compute the normals for the model, even if normals are present in the file
+         * Compute the normals for the model, even if normals are present in the file.
          */
         static COMPUTE_NORMALS: boolean;
         /**
+         * Skip loading the materials even if defined in the OBJ file (materials are ignored).
+         */
+        static SKIP_MATERIALS: boolean;
+        /**
+         * When a material fails to load OBJ loader will silently fail and onSuccess() callback will be triggered.
+         *
+         * Defaults to true for backwards compatibility.
+         */
+        static MATERIAL_LOADING_FAILS_SILENTLY: boolean;
+        /**
          * Defines the name of the plugin.
          */
         name: string;
@@ -3978,6 +4076,14 @@ declare module BABYLON {
         facePattern4: RegExp;
         /** @hidden */
         facePattern5: RegExp;
+        private _meshLoadOptions;
+        /**
+         * Creates loader for .OBJ files
+         *
+         * @param meshLoadOptions options for loading and parsing OBJ/MTL files.
+         */
+        constructor(meshLoadOptions?: MeshLoadOptions);
+        private static readonly currentMeshLoadOptions;
         /**
          * Calls synchronously the MTL file attached to this obj.
          * Load function or importMesh function don't enable to load 2 files in the same time asynchronously.
@@ -3991,6 +4097,18 @@ declare module BABYLON {
          */
         private _loadMTL;
         /**
+         * Instantiates a OBJ file loader plugin.
+         * @returns the created plugin
+         */
+        createPlugin(): ISceneLoaderPluginAsync | ISceneLoaderPlugin;
+        /**
+         * If the data string can be loaded directly.
+         *
+         * @param data string containing the file data
+         * @returns if the data can be loaded directly
+         */
+        canDirectLoad(data: string): boolean;
+        /**
          * Imports one or more meshes from the loaded OBJ data and adds them to the scene
          * @param meshesNames a string or array of strings of the mesh names that should be loaded from the file
          * @param scene the scene the meshes should be added to

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

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

@@ -1 +1 @@
-{"engineOnly":304108,"sceneOnly":504513,"minGridMaterial":623674,"minStandardMaterial":745822}
+{"engineOnly":304232,"sceneOnly":504997,"minGridMaterial":624489,"minStandardMaterial":746950}

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

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

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

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 1539 - 864
dist/preview release/viewer/babylon.module.d.ts


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 78 - 70
dist/preview release/viewer/babylon.viewer.js


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 2 - 2
dist/preview release/viewer/babylon.viewer.max.js


+ 9 - 2
dist/preview release/what's new.md

@@ -24,7 +24,7 @@
   - Moved to a measure / draw mechanism ([Deltakosh](https://github.com/deltakosh))
   - Added support for [nine patch stretch](https://www.babylonjs-playground.com/#G5H9IN#2) mode for images. ([Deltakosh](https://github.com/deltakosh))
   - InvalidateRect added to AdvancedDynamicTexture to improve perf for heavily populated GUIs, works with shadows ([TrevorDev](https://github.com/TrevorDev)) **** NEED DEMO or DOC LINK)
-- Migrated the code to modules and deploy ES6 npm packages ([Sebavan](https://github.com/Sebavan)) **** NEED DEMO or DOC LINK)
+- Migrated the code to modules and deploy [ES6 npm packages](https://doc.babylonjs.com/features/es6_support) ([Sebavan](https://github.com/Sebavan))
 - Added clear coat support to PBR ([Sebavan](https://github.com/Sebavan)) **** NEED DEMO or DOC LINK)
 - Added anisotropy support to PBR ([Sebavan](https://github.com/Sebavan)) **** NEED DEMO or DOC LINK)
 - Added `TrailMesh` class. Credit to furcatomasz ([danjpar](https://github.com/danjpar)) **** NEED DEMO or DOC LINK)
@@ -35,7 +35,7 @@
 
 - Added `inputText.onKeyboardEventProcessedObservable` ([Deltakosh](https://github.com/deltakosh))
 - Added `button.image` and `button.textBlock` to simplify access to button internal parts ([Deltakosh](https://github.com/deltakosh))
-- Added `sldier.displayThumb` to show/hide slider's thumb ([Deltakosh](https://github.com/deltakosh))
+- Added `slider.displayThumb` to show/hide slider's thumb ([Deltakosh](https://github.com/deltakosh))
 - Added `grid.rowCount`, `grid.columnCount` and `grid.getChildrenAt()` ([Deltakosh](https://github.com/deltakosh))
 - Added `Control.AllowAlphaInheritance` to let users control the way alpha is used (inherited or not) ([Deltakosh](https://github.com/deltakosh))
 - Added support for performing operations like select all, text highlight, delete selected in `inputText` ([Saket Saurabh](https://github.com/ssaket))
@@ -46,6 +46,7 @@
 
 ### Core Engine
 
+- Added support for `scene.customLODSelector` to let users define their own LOD rules ([Deltakosh](https://github.com/deltakosh))
 - Added `animatable.onAnimationLoopObservable` ([Deltakosh](https://github.com/deltakosh))
 - Added `animationGroup.onAnimationLoopObservable` ([Deltakosh](https://github.com/deltakosh))
 - Added FlyCamera for free navigation in 3D space, with a limited set of settings ([Phuein](https://github.com/phuein))
@@ -105,9 +106,14 @@
 - Inspector light gizmo ([TrevorDev](https://github.com/TrevorDev))
 - Added option `multiMultiMaterials` to mesh.mergeMeshes ([danjpar](https://github.com/danjpar))
 - Expose fallback camera distortion metrics option in vrExperienceHelper ([TrevorDev](https://github.com/TrevorDev))
+- Added setColor method to boundingBoxGizmo ([TrevorDev](https://github.com/TrevorDev))
+- Added OnAfterEnteringVRObservable to webVRHelper ([TrevorDev](https://github.com/TrevorDev))
+- Added Support for Side By Side and Top/Bottom VR videos in the [video dome](https://doc.babylonjs.com/how_to/360videodome#video-types) ([Sebavan](https://github.com/Sebavan))
 
 ### OBJ Loader
 - Add color vertex support (not part of standard) ([brianzinn](https://github.com/brianzinn))
+- Add option for silently failing when materials fail to load ([brianzinn](https://github.com/brianzinn))
+- Add option to skip loading materials ([brianzinn](https://github.com/brianzinn))
 
 ### glTF Loader
 
@@ -172,6 +178,7 @@
 - PointerDragBehavior should not let the drag plane get out of sync when rotating the object during dragging ([TrevorDev](https://github.com/TrevorDev))
 - Do not crash the application if webVR submitFrame fails ([TrevorDev](https://github.com/TrevorDev))
 - Fix pinch action on FollowCameraPointersInput ([mrdunk](https://github.com))
+- Tools.CreateScreenshot stopped working ([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))

+ 7 - 3
gui/src/2D/valueAndUnit.ts

@@ -105,14 +105,18 @@ export class ValueAndUnit {
     /**
      * Gets a string representation of the value
      * @param host defines the root host
+     * @param decimals defines an optional number of decimals to display
      * @returns a string
      */
-    public toString(host: AdvancedDynamicTexture): string {
+    public toString(host: AdvancedDynamicTexture, decimals?: number): string {
         switch (this.unit) {
             case ValueAndUnit.UNITMODE_PERCENTAGE:
-                return (this.getValue(host) * 100) + "%";
+                let percentage = this.getValue(host) * 100;
+
+                return (decimals ? percentage.toFixed(decimals) : percentage) + "%";
             case ValueAndUnit.UNITMODE_PIXEL:
-                return this.getValue(host) + "px";
+                let pixels = this.getValue(host);
+                return (decimals ? pixels.toFixed(decimals) : pixels) + "px";
         }
 
         return this.unit.toString();

+ 32 - 11
inspector/src/components/actionTabs/actionTabs.scss

@@ -1,3 +1,5 @@
+$line-padding-left: 2px;
+
 #inspector-host {
     position: absolute;
     right: 0px;
@@ -184,7 +186,7 @@
                 }
 
                 .iconMessageLine {
-                    padding-left: 5px;
+                    padding-left: $line-padding-left;
                     height: 30px;
                     display: grid;
                     grid-template-columns: 30px 1fr;
@@ -204,7 +206,7 @@
                 }
 
                 .textLine {
-                    padding-left: 5px;
+                    padding-left: $line-padding-left;
                     height: 30px;
                     display: grid;
                     grid-template-columns: 1fr auto;
@@ -255,7 +257,7 @@
                 }
 
                 .textInputLine {
-                    padding-left: 5px;
+                    padding-left: $line-padding-left;
                     height: 30px;
                     display: grid;
                     grid-template-columns: 1fr 120px;
@@ -331,7 +333,7 @@
                 }
 
                 .radioLine {
-                    padding-left: 5px;
+                    padding-left: $line-padding-left;
                     height: 30px;
                     display: grid;
                     grid-template-columns: 1fr 24px;
@@ -393,7 +395,7 @@
                 }
 
                 .vector3Line {
-                    padding-left: 5px;                    
+                    padding-left:$line-padding-left;                    
                     display: grid;
 
                     .firstLine {
@@ -454,7 +456,7 @@
                 }
 
                 .checkBoxLine {
-                    padding-left: 5px;
+                    padding-left: $line-padding-left;
                     height: 30px;
                     display: grid;
                     grid-template-columns: 1fr auto;
@@ -608,7 +610,7 @@
                 }
 
                 .floatLine {
-                    padding-left: 5px;
+                    padding-left: $line-padding-left;
                     height: 30px;
                     display: grid;
                     grid-template-columns: 1fr 120px;
@@ -688,7 +690,7 @@
                 }       
                 
                 .color3Line {
-                    padding-left: 5px;
+                    padding-left: $line-padding-left;
                     display: grid;
 
                     .firstLine {
@@ -774,7 +776,7 @@
                 }     
                 
                 .listLine {
-                    padding-left: 5px;
+                    padding-left: $line-padding-left;
                     height: 30px;
                     display: grid;
                     grid-template-columns: 1fr auto;
@@ -805,6 +807,24 @@
                     grid-template-rows: 100%;
                     grid-template-columns: 100%;
                     
+                    .paneList {
+                        border-left: 3px solid transparent;
+                    }
+
+                    &:hover {  
+                        .paneList {                      
+                            border-left: 3px solid rgba(51, 122, 183, 0.8);
+                        }
+
+                        .paneContainer-content {
+                            .header {
+                                .title {   
+                                    border-left: 3px solid rgb(51, 122, 183);
+                                }
+                            }
+                        }
+                    }
+                    
                     .paneContainer-highlight-border {
                         grid-row: 1;
                         grid-column: 1;
@@ -830,8 +850,9 @@
                             padding-right: 5px;                        
                             cursor: pointer;
                             
-                            .title {
-                                margin-left: 5px;
+                            .title {                                
+                                border-left: 3px solid transparent;
+                                padding-left: 5px;
                                 grid-column: 1;
                                 display: flex;
                                 align-items: center;

+ 10 - 2
inspector/src/components/actionTabs/lineContainerComponent.tsx

@@ -55,15 +55,23 @@ export class LineContainerComponent extends React.Component<ILineContainerCompon
     }
 
     componentDidMount() {
+        if (!this.props.globalState.selectedLineContainerTitle) {
+            return;
+        }
+
         if (this.props.globalState.selectedLineContainerTitle === this.props.title) {
-            this.props.globalState.selectedLineContainerTitle = "";
+            setTimeout(() => {
+                this.props.globalState.selectedLineContainerTitle = "";
+            });
 
             this.setState({ isExpanded: true, isHighlighted: true });
 
             window.setTimeout(() => {
                 this.setState({ isHighlighted: false });
             }, 5000);
-        }
+        } else {
+            this.setState({isExpanded: false});
+        }        
     }
 
     renderHeader() {

+ 6 - 0
inspector/src/components/actionTabs/lines/checkBoxLineComponent.tsx

@@ -8,6 +8,7 @@ export interface ICheckBoxLineComponentProps {
     propertyName?: string;
     isSelected?: () => boolean;
     onSelect?: (value: boolean) => void;
+    onValueChanged?: () => void;
     onPropertyChangedObservable?: Observable<PropertyChangedEvent>;
 }
 
@@ -60,6 +61,11 @@ export class CheckBoxLineComponent extends React.Component<ICheckBoxLineComponen
 
             this.props.target[this.props.propertyName!] = !this.state.isSelected;
         }
+
+        if (this.props.onValueChanged) {
+            this.props.onValueChanged();
+        }
+
         this.setState({ isSelected: !this.state.isSelected });
     }
 

+ 1 - 1
inspector/src/components/actionTabs/lines/optionsLineComponent.tsx

@@ -24,7 +24,7 @@ export class OptionsLineComponent extends React.Component<IOptionsLineComponentP
     constructor(props: IOptionsLineComponentProps) {
         super(props);
 
-        this.state = { value: 0 };
+        this.state = { value: props.target[props.propertyName] };
     }
 
     shouldComponentUpdate(nextProps: IOptionsLineComponentProps, nextState: { value: number }) {

+ 2 - 1
inspector/src/components/actionTabs/lines/sliderLineComponent.tsx

@@ -77,13 +77,14 @@ export class SliderLineComponent extends React.Component<ISliderLineComponentPro
     }
 
     render() {
+        let decimalCount = this.props.decimalCount !== undefined ? this.props.decimalCount : 2;
         return (
             <div className="sliderLine">
                 <div className="label">
                     {this.props.label}
                 </div>
                 <div className="slider">
-                    {this.state.value ? this.state.value.toFixed(this.props.decimalCount || 2) : "0"}&nbsp;<input className="range" type="range" step={this.props.step} min={this.props.minimum} max={this.props.maximum} value={this.state.value}
+                    {this.state.value ? this.state.value.toFixed(decimalCount) : "0"}&nbsp;<input className="range" type="range" step={this.props.step} min={this.props.minimum} max={this.props.maximum} value={this.state.value}
                         onInput={evt => this.onInput((evt.target as HTMLInputElement).value)}
                         onChange={evt => this.onChange(evt.target.value)} />
                 </div>

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

@@ -48,7 +48,7 @@ export class TextureLineComponent extends React.Component<ITextureLineComponentP
         var size = texture.getSize();
         var ratio = size.width / size.height;
         var width = this.props.width;
-        var height = (width / ratio) | 0;
+        var height = (width / ratio) | 1;
 
         let passPostProcess: PostProcess;
 

+ 0 - 47
inspector/src/components/actionTabs/tabs/debugTabComponent.tsx

@@ -4,14 +4,11 @@ import { LineContainerComponent } from "../lineContainerComponent";
 import { CheckBoxLineComponent } from "../lines/checkBoxLineComponent";
 import { RenderGridPropertyGridComponent } from "./propertyGrids/renderGridPropertyGridComponent";
 
-import { SkeletonViewer } from "babylonjs/Debug/skeletonViewer";
 import { PhysicsViewer } from "babylonjs/Debug/physicsViewer";
 import { StandardMaterial } from "babylonjs/Materials/standardMaterial";
 
 export class DebugTabComponent extends PaneComponent {
-    private _skeletonViewersEnabled = false;
     private _physicsViewersEnabled = false;
-    private _skeletonViewers = new Array<SkeletonViewer>();
 
     constructor(props: IPaneComponentProps) {
         super(props);
@@ -28,55 +25,12 @@ export class DebugTabComponent extends PaneComponent {
             scene.reservedDataStore = {};
         }
 
-        for (var mesh of scene.meshes) {
-            if (mesh.skeleton && mesh.reservedDataStore && mesh.reservedDataStore.skeletonViewer) {
-                this._skeletonViewers.push(mesh.reservedDataStore.skeletonViewer);
-            }
-        }
-
-        this._skeletonViewersEnabled = (this._skeletonViewers.length > 0);
         this._physicsViewersEnabled = scene.reservedDataStore.physicsViewer != null;
     }
 
     componentWillUnmount() {
     }
 
-    switchSkeletonViewers() {
-        this._skeletonViewersEnabled = !this._skeletonViewersEnabled;
-        const scene = this.props.scene;
-
-        if (this._skeletonViewersEnabled) {
-            for (var mesh of scene.meshes) {
-                if (mesh.skeleton) {
-                    var found = false;
-                    for (var sIndex = 0; sIndex < this._skeletonViewers.length; sIndex++) {
-                        if (this._skeletonViewers[sIndex].skeleton === mesh.skeleton) {
-                            found = true;
-                            break;
-                        }
-                    }
-                    if (found) {
-                        continue;
-                    }
-                    var viewer = new SkeletonViewer(mesh.skeleton, mesh, scene, true, 0);
-                    viewer.isEnabled = true;
-                    this._skeletonViewers.push(viewer);
-                    if (!mesh.reservedDataStore) {
-                        mesh.reservedDataStore = {};
-                    }
-                    mesh.reservedDataStore.skeletonViewer = viewer;
-                }
-            }
-        } else {
-            for (var index = 0; index < this._skeletonViewers.length; index++) {
-                this._skeletonViewers[index].mesh.reservedDataStore.skeletonViewer = null;
-                this._skeletonViewers[index].dispose();
-            }
-            this._skeletonViewers = [];
-
-        }
-    }
-
     switchPhysicsViewers() {
         this._physicsViewersEnabled = !this._physicsViewersEnabled;
         const scene = this.props.scene;
@@ -112,7 +66,6 @@ export class DebugTabComponent extends PaneComponent {
             <div className="pane">
                 <LineContainerComponent globalState={this.props.globalState} title="HELPERS">
                     <RenderGridPropertyGridComponent globalState={this.props.globalState} scene={scene} />
-                    <CheckBoxLineComponent label="Bones" isSelected={() => this._skeletonViewersEnabled} onSelect={() => this.switchSkeletonViewers()} />
                     <CheckBoxLineComponent label="Physics" isSelected={() => this._physicsViewersEnabled} onSelect={() => this.switchPhysicsViewers()} />
                 </LineContainerComponent>
                 <LineContainerComponent globalState={this.props.globalState} title="TEXTURE CHANNELS">

+ 20 - 0
inspector/src/components/actionTabs/tabs/propertyGridTabComponent.tsx

@@ -75,6 +75,10 @@ import { SSAORenderingPipeline } from 'babylonjs/PostProcesses/RenderPipeline/Pi
 import { SSAORenderingPipelinePropertyGridComponent } from './propertyGrids/postProcesses/ssaoRenderingPipelinePropertyGridComponent';
 import { SSAO2RenderingPipeline } from 'babylonjs/PostProcesses/RenderPipeline/Pipelines/ssao2RenderingPipeline';
 import { SSAO2RenderingPipelinePropertyGridComponent } from './propertyGrids/postProcesses/ssao2RenderingPipelinePropertyGridComponent';
+import { Skeleton } from 'babylonjs/Bones/skeleton';
+import { SkeletonPropertyGridComponent } from './propertyGrids/meshes/skeletonPropertyGridComponent';
+import { Bone } from 'babylonjs/Bones/bone';
+import { BonePropertyGridComponent } from './propertyGrids/meshes/bonePropertyGridComponent';
 
 export class PropertyGridTabComponent extends PaneComponent {
     private _timerIntervalId: number;
@@ -278,6 +282,22 @@ export class PropertyGridTabComponent extends PaneComponent {
                     onPropertyChangedObservable={this.props.onPropertyChangedObservable} />);
             }
 
+            if (className.indexOf("Skeleton") !== -1) {
+                const skeleton = entity as Skeleton;
+                return (<SkeletonPropertyGridComponent skeleton={skeleton}
+                    globalState={this.props.globalState}
+                    lockObject={this._lockObject}
+                    onPropertyChangedObservable={this.props.onPropertyChangedObservable} />);
+            }
+
+            if (className.indexOf("Bone") !== -1) {
+                const bone = entity as Bone;
+                return (<BonePropertyGridComponent bone={bone}
+                    globalState={this.props.globalState}
+                    lockObject={this._lockObject}
+                    onPropertyChangedObservable={this.props.onPropertyChangedObservable} />);
+            }
+
             if (className === "TextBlock") {
                 const textBlock = entity as TextBlock;
                 return (<TextBlockPropertyGridComponent textBlock={textBlock}

+ 194 - 0
inspector/src/components/actionTabs/tabs/propertyGrids/animationPropertyGridComponent.tsx

@@ -0,0 +1,194 @@
+import * as React from "react";
+
+import { Observable, Observer } from "babylonjs/Misc/observable";
+import { Scene } from "babylonjs/scene";
+
+import { PropertyChangedEvent } from "../../../propertyChangedEvent";
+import { ButtonLineComponent } from "../../lines/buttonLineComponent";
+import { LineContainerComponent } from "../../lineContainerComponent";
+import { SliderLineComponent } from "../../lines/sliderLineComponent";
+import { LockObject } from "./lockObject";
+import { GlobalState } from '../../../globalState';
+import { IAnimatable } from 'babylonjs/Misc/tools';
+import { Animation } from 'babylonjs/Animations/animation';
+import { Animatable } from 'babylonjs/Animations/animatable';
+import { AnimationPropertiesOverride } from 'babylonjs/Animations/animationPropertiesOverride';
+import { AnimationRange } from 'babylonjs/Animations/animationRange';
+import { CheckBoxLineComponent } from '../../lines/checkBoxLineComponent';
+import { Nullable } from 'babylonjs/types';
+import { FloatLineComponent } from '../../lines/floatLineComponent';
+import { TextLineComponent } from '../../lines/textLineComponent';
+
+interface IAnimationGridComponentProps {
+    globalState: GlobalState;
+    animatable: IAnimatable,
+    scene: Scene,
+    lockObject: LockObject,
+    onPropertyChangedObservable?: Observable<PropertyChangedEvent>
+}
+
+export class AnimationGridComponent extends React.Component<IAnimationGridComponentProps, { currentFrame: number }> {
+    private _animations: Nullable<Animation[]> = null;
+    private _ranges: AnimationRange[];
+    private _animationControl = {
+        from: 0,
+        to: 0,
+        loop: false
+    }
+    private _runningAnimatable: Nullable<Animatable>;
+    private _onBeforeRenderObserver: Nullable<Observer<Scene>>;
+    private _isPlaying = false;
+
+    constructor(props: IAnimationGridComponentProps) {
+        super(props);
+
+        this.state = { currentFrame: 0 };
+
+        const animatableAsAny = this.props.animatable as any;
+
+        this._ranges = animatableAsAny.getAnimationRanges ? animatableAsAny.getAnimationRanges() : [];
+        if (animatableAsAny.getAnimatables) {
+            const animatables = animatableAsAny.getAnimatables();
+            this._animations = new Array<Animation>();
+
+            animatables.forEach((animatable: IAnimatable) => {
+                this._animations!.push(...animatable.animations);
+            });
+
+            // Extract from and to
+            if (this._animations && this._animations.length) {
+                this._animations.forEach(animation => {
+                    let keys = animation.getKeys();
+
+                    if (keys && keys.length > 0) {
+                        if (keys[0].frame < this._animationControl.from) {
+                            this._animationControl.from = keys[0].frame;
+                        }
+                        const lastKeyIndex = keys.length - 1;
+                        if (keys[lastKeyIndex].frame > this._animationControl.to) {
+                            this._animationControl.to = keys[lastKeyIndex].frame;
+                        }
+                    }
+                });
+            }
+        }
+    }
+
+    playOrPause() {
+        const animatable = this.props.animatable;
+        this._isPlaying = this.props.scene.getAllAnimatablesByTarget(animatable).length > 0;
+
+        if (this._isPlaying) {
+            this.props.scene.stopAnimation(this.props.animatable);
+            this._runningAnimatable = null;
+        } else {
+            this._runningAnimatable = this.props.scene.beginAnimation(this.props.animatable, this._animationControl.from, this._animationControl.to, this._animationControl.loop);
+        }
+        this.forceUpdate();
+    }
+
+    componentWillMount() {
+        this._onBeforeRenderObserver = this.props.scene.onBeforeRenderObservable.add(() => {
+            if (!this._isPlaying || !this._runningAnimatable) {
+                return;
+            }
+            this.setState({ currentFrame: this._runningAnimatable.masterFrame });
+        });
+    }
+
+    componentWillUnmount() {
+        if (this._onBeforeRenderObserver) {
+            this.props.scene.onBeforeRenderObservable.remove(this._onBeforeRenderObserver);
+            this._onBeforeRenderObserver = null;
+        }
+    }
+
+    onCurrentFrameChange(value: number) {
+        if (!this._runningAnimatable) {
+            return;
+        }
+
+        this._runningAnimatable.goToFrame(value);
+        this.setState({ currentFrame: value });
+    }
+
+    render() {
+        const animatable = this.props.animatable;
+        const animatableAsAny = this.props.animatable as any;
+
+        let animatablesForTarget = this.props.scene.getAllAnimatablesByTarget(animatable);
+        this._isPlaying = animatablesForTarget.length > 0;
+
+        if (this._isPlaying && !this._runningAnimatable) {
+            this._runningAnimatable = animatablesForTarget[0];
+        }
+
+        if (this._runningAnimatable) {
+            this._animationControl.from = this._runningAnimatable.fromFrame;
+            this._animationControl.to = this._runningAnimatable.toFrame;
+            this._animationControl.loop = this._runningAnimatable.loopAnimation;
+        }
+
+        return (
+            <div>
+                {
+                    (this._ranges.length > 0 || this._animations && this._animations.length > 0) &&
+                    <LineContainerComponent globalState={this.props.globalState} title="ANIMATION OVERRIDE">
+                        <CheckBoxLineComponent label="Enable override" onSelect={value => {
+                            if (value) {
+                                animatableAsAny.animationPropertiesOverride = new AnimationPropertiesOverride();
+                                animatableAsAny.animationPropertiesOverride.blendingSpeed = 0.05;
+                            } else {
+                                animatableAsAny.animationPropertiesOverride = null;
+                            }
+                            this.forceUpdate();
+                        }} isSelected={() => animatableAsAny.animationPropertiesOverride != null}
+                            onValueChanged={() => this.forceUpdate()}
+                        />
+                        {
+                            animatableAsAny.animationPropertiesOverride != null &&
+                            <div>
+                                <CheckBoxLineComponent label="Enable blending" target={animatableAsAny.animationPropertiesOverride} propertyName="enableBlending" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                                <SliderLineComponent label="Blending speed" target={animatableAsAny.animationPropertiesOverride} propertyName="blendingSpeed" minimum={0} maximum={0.1} step={0.01} onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                            </div>
+                        }
+                    </LineContainerComponent>
+                }
+                {
+                    this._ranges.length > 0 &&
+                    <LineContainerComponent globalState={this.props.globalState} title="ANIMATION RANGES">
+                        {
+                            this._ranges.map(range => {
+                                return (
+                                    <ButtonLineComponent key={range.name} label={range.name}
+                                        onClick={() => {
+                                            this._runningAnimatable = null;
+                                            this.props.scene.beginAnimation(animatable, range.from, range.to, true)
+                                        }} />
+                                );
+                            })
+                        }
+                    </LineContainerComponent>
+                }
+                {
+                    this._animations && this._animations.length > 0 &&
+                    <LineContainerComponent globalState={this.props.globalState} title="ANIMATIONS">
+                        <TextLineComponent label="Count" value={this._animations.length.toString()} />
+                        <FloatLineComponent lockObject={this.props.lockObject} label="From" target={this._animationControl} propertyName="from" />
+                        <FloatLineComponent lockObject={this.props.lockObject} label="To" target={this._animationControl} propertyName="to" />
+                        <CheckBoxLineComponent label="Loop" onSelect={value => this._animationControl.loop = value} isSelected={() => this._animationControl.loop} />
+                        <ButtonLineComponent label={this._isPlaying ? "Stop" : "Play"} onClick={() => this.playOrPause()} />
+                        {
+                            this._isPlaying &&
+                            <SliderLineComponent ref="timeline" label="Current frame" minimum={this._animationControl.from} maximum={this._animationControl.to}
+                                step={(this._animationControl.to - this._animationControl.from) / 1000.0} directValue={this.state.currentFrame}
+                                onInput={value => this.onCurrentFrameChange(value)}
+                            />
+                        }
+                    </LineContainerComponent>
+                }
+            </div>
+        );
+    }
+
+}

+ 2 - 2
inspector/src/components/actionTabs/tabs/propertyGrids/gui/gridPropertyGridComponent.tsx

@@ -31,7 +31,7 @@ export class GridPropertyGridComponent extends React.Component<IGridPropertyGrid
         return (
             rows.map((rd, i) => {
                 return (
-                    <TextLineComponent key={`r${i}`} label={`Row ${i}`} value={rd.toString(grid.host)} underline={i === grid.rowCount - 1} />
+                    <TextLineComponent key={`r${i}`} label={`Row ${i}`} value={rd.toString(grid.host, 2)} underline={i === grid.rowCount - 1} />
                 )
             })
         );
@@ -48,7 +48,7 @@ export class GridPropertyGridComponent extends React.Component<IGridPropertyGrid
         return (
             cols.map((cd, i) => {
                 return (
-                    <TextLineComponent key={`c${i}`} label={`Column ${i}`} value={cd.toString(grid.host)} />
+                    <TextLineComponent key={`c${i}`} label={`Column ${i}`} value={cd.toString(grid.host, 2)} />
                 )
             })
         );

+ 52 - 12
inspector/src/components/actionTabs/tabs/propertyGrids/materials/pbrMaterialPropertyGridComponent.tsx

@@ -62,27 +62,67 @@ export class PBRMaterialPropertyGridComponent extends React.Component<IPBRMateri
                 <LineContainerComponent globalState={this.props.globalState} title="LIGHTING & COLORS">
                     <Color3LineComponent label="Albedo" target={material} propertyName="albedoColor" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
                     <Color3LineComponent label="Reflectivity" target={material} propertyName="reflectivityColor" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                    <SliderLineComponent label="Micro-surface" target={material} propertyName="microSurface" minimum={0} maximum={1} step={0.01} onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
                     <Color3LineComponent label="Emissive" target={material} propertyName="emissiveColor" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
                     <Color3LineComponent label="Ambient" target={material} propertyName="ambientColor" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
                 </LineContainerComponent>
+                <LineContainerComponent globalState={this.props.globalState} title="METALLIC WORKFLOW">
+                    <SliderLineComponent label="Metallic" target={material} propertyName="metallic" minimum={0} maximum={1} step={0.01} onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                    <SliderLineComponent label="Roughness" target={material} propertyName="roughness" minimum={0} maximum={1} step={0.01} onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                </LineContainerComponent>
                 <LineContainerComponent globalState={this.props.globalState} title="CLEAR COAT">
-                    <CheckBoxLineComponent label="Enabled" target={material.clearCoat} propertyName="isEnabled" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
-                    <SliderLineComponent label="Intensity" target={material.clearCoat} propertyName="intensity" minimum={0} maximum={1} step={0.01} onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
-                    <SliderLineComponent label="Roughness" target={material.clearCoat} propertyName="roughness" minimum={0} maximum={1} step={0.01} onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
-                    <TextureLinkLineComponent label="Texture" texture={material.clearCoat.texture} material={material} onSelectionChangedObservable={this.props.onSelectionChangedObservable} onDebugSelectionChangeObservable={onDebugSelectionChangeObservable} />
-                    <TextureLinkLineComponent label="Bump" texture={material.clearCoat.bumpTexture} material={material} onSelectionChangedObservable={this.props.onSelectionChangedObservable} onDebugSelectionChangeObservable={onDebugSelectionChangeObservable} />
+                    <CheckBoxLineComponent label="Enabled" target={material.clearCoat} propertyName="isEnabled" 
+                    onValueChanged={() => this.forceUpdate()}
+                    onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                    {
+                        material.clearCoat.isEnabled &&
+                        <div>
+                            <SliderLineComponent label="Intensity" target={material.clearCoat} propertyName="intensity" minimum={0} maximum={1} step={0.01} onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                            <SliderLineComponent label="Roughness" target={material.clearCoat} propertyName="roughness" minimum={0} maximum={1} step={0.01} onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                            <SliderLineComponent label="IOR" target={material.clearCoat} propertyName="indiceOfRefraction" minimum={1.0} maximum={3} step={0.01} onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                            <TextureLinkLineComponent label="Texture" texture={material.clearCoat.texture} material={material} onSelectionChangedObservable={this.props.onSelectionChangedObservable} onDebugSelectionChangeObservable={onDebugSelectionChangeObservable} />
+                            <TextureLinkLineComponent label="Bump" texture={material.clearCoat.bumpTexture} material={material} onSelectionChangedObservable={this.props.onSelectionChangedObservable} onDebugSelectionChangeObservable={onDebugSelectionChangeObservable} />
+                            {
+                                material.clearCoat.bumpTexture &&
+                                <SliderLineComponent label="Bump strength" target={material.clearCoat.bumpTexture} propertyName="level" minimum={0} maximum={2} step={0.01} onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                            }
+                            <CheckBoxLineComponent label="Tint" target={material.clearCoat} propertyName="isTintEnabled" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                            {
+                                material.clearCoat.isEnabled && material.clearCoat.isTintEnabled &&
+                                <Color3LineComponent label="Tint Color" target={material.clearCoat} propertyName="tintColor" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                            }
+                            {
+                                material.clearCoat.isEnabled && material.clearCoat.isTintEnabled &&
+                                <SliderLineComponent label="At Distance" target={material.clearCoat} propertyName="tintColorAtDistance" minimum={0} maximum={20} step={0.1} onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                            }
+                            {
+                                material.clearCoat.isEnabled && material.clearCoat.isTintEnabled &&
+                                <SliderLineComponent label="Tint Thickness" target={material.clearCoat} propertyName="tintThickness" minimum={0} maximum={20} step={0.1} onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                            }
+                            {
+                                material.clearCoat.isEnabled && material.clearCoat.isTintEnabled &&
+                                <TextureLinkLineComponent label="Tint Texture" texture={material.clearCoat.tintTexture} material={material} onSelectionChangedObservable={this.props.onSelectionChangedObservable} onDebugSelectionChangeObservable={onDebugSelectionChangeObservable} />
+                            }
+                        </div>
+                    }
                 </LineContainerComponent>
                 <LineContainerComponent globalState={this.props.globalState} title="ANISOTROPIC">
-                    <CheckBoxLineComponent label="Enabled" target={material.anisotropy} propertyName="isEnabled" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
-                    <SliderLineComponent label="Intensity" target={material.anisotropy} propertyName="intensity" minimum={0} maximum={1} step={0.01} onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
-                    <CheckBoxLineComponent label="Follow tangents" target={material.anisotropy} propertyName="followTangents" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
-                </LineContainerComponent>
+                    <CheckBoxLineComponent label="Enabled" target={material.anisotropy} propertyName="isEnabled" 
+                    onValueChanged={() => this.forceUpdate()}
+                    onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                    {
+                        material.anisotropy.isEnabled &&
+                        <div>
+                            <SliderLineComponent label="Intensity" target={material.anisotropy} propertyName="intensity" minimum={0} maximum={1} step={0.01} onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                            <CheckBoxLineComponent label="Follow tangents" target={material.anisotropy} propertyName="followTangents" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                        </div>
+                   }
+                 </LineContainerComponent>
                 <LineContainerComponent globalState={this.props.globalState} title="LEVELS" closed={true}>
                     <SliderLineComponent label="Environment" target={material} propertyName="environmentIntensity" minimum={0} maximum={1} step={0.01} onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
-                    <SliderLineComponent label="Metallic" target={material} propertyName="metallic" minimum={0} maximum={1} step={0.01} onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
-                    <SliderLineComponent label="Roughness" target={material} propertyName="roughness" minimum={0} maximum={1} step={0.01} onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
-                    <SliderLineComponent label="Micro-surface" target={material} propertyName="microSurface" minimum={0} maximum={1} step={0.01} onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
                     <SliderLineComponent label="Specular" target={material} propertyName="specularIntensity" minimum={0} maximum={1} step={0.01} onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                    <SliderLineComponent label="Emissive" target={material} propertyName="emissiveIntensity" minimum={0} maximum={1} step={0.01} onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                    <SliderLineComponent label="Direct" target={material} propertyName="directIntensity" minimum={0} maximum={1} step={0.01} onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
                     {
                         material.bumpTexture &&
                         <SliderLineComponent label="Bump strength" target={material.bumpTexture} propertyName="level" minimum={0} maximum={2} step={0.01} onPropertyChangedObservable={this.props.onPropertyChangedObservable} />

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

@@ -0,0 +1,50 @@
+import * as React from "react";
+
+import { Observable } from "babylonjs/Misc/observable";
+
+import { PropertyChangedEvent } from "../../../../propertyChangedEvent";
+import { LineContainerComponent } from "../../../lineContainerComponent";
+import { TextLineComponent } from "../../../lines/textLineComponent";
+import { LockObject } from "../lockObject";
+import { GlobalState } from '../../../../globalState';
+import { Bone } from 'babylonjs/Bones/bone';
+import { Vector3LineComponent } from '../../../lines/vector3LineComponent';
+import { QuaternionLineComponent } from '../../../lines/quaternionLineComponent';
+
+interface IBonePropertyGridComponentProps {
+    globalState: GlobalState;
+    bone: Bone,
+    lockObject: LockObject,
+    onPropertyChangedObservable?: Observable<PropertyChangedEvent>
+}
+
+export class BonePropertyGridComponent extends React.Component<IBonePropertyGridComponentProps> {
+    constructor(props: IBonePropertyGridComponentProps) {
+        super(props);
+    }
+
+    render() {
+        const bone = this.props.bone;
+
+        return (
+            <div className="pane">
+                <LineContainerComponent globalState={this.props.globalState} title="GENERAL">
+                    <TextLineComponent label="ID" value={bone.id} />
+                    <TextLineComponent label="Unique ID" value={bone.uniqueId.toString()} />
+                </LineContainerComponent>
+                <LineContainerComponent globalState={this.props.globalState} title="TRANSFORMATIONS">
+                    <Vector3LineComponent label="Position" target={bone} propertyName="position" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                    {
+                        !bone.rotationQuaternion &&
+                        <Vector3LineComponent label="Rotation" target={bone} propertyName="rotation" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                    }
+                    {
+                        bone.rotationQuaternion &&
+                        <QuaternionLineComponent label="Rotation" target={bone} propertyName="rotationQuaternion" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                    }
+                    <Vector3LineComponent label="Scaling" target={bone} propertyName="scaling" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                </LineContainerComponent>
+            </div>
+        );
+    }
+}

+ 101 - 0
inspector/src/components/actionTabs/tabs/propertyGrids/meshes/skeletonPropertyGridComponent.tsx

@@ -0,0 +1,101 @@
+import * as React from "react";
+
+import { Observable } from "babylonjs/Misc/observable";
+
+import { PropertyChangedEvent } from "../../../../propertyChangedEvent";
+import { LineContainerComponent } from "../../../lineContainerComponent";
+import { CheckBoxLineComponent } from "../../../lines/checkBoxLineComponent";
+import { TextLineComponent } from "../../../lines/textLineComponent";
+import { LockObject } from "../lockObject";
+import { GlobalState } from '../../../../globalState';
+import { Skeleton } from 'babylonjs/Bones/skeleton';
+import { AnimationGridComponent } from '../animationPropertyGridComponent';
+import { SkeletonViewer } from 'babylonjs/Debug/skeletonViewer';
+
+interface ISkeletonPropertyGridComponentProps {
+    globalState: GlobalState;
+    skeleton: Skeleton,
+    lockObject: LockObject,
+    onPropertyChangedObservable?: Observable<PropertyChangedEvent>
+}
+
+export class SkeletonPropertyGridComponent extends React.Component<ISkeletonPropertyGridComponentProps> {
+    private _skeletonViewersEnabled = false;
+    private _skeletonViewers = new Array<SkeletonViewer>();
+
+    constructor(props: ISkeletonPropertyGridComponentProps) {
+        super(props);
+    }
+
+    switchSkeletonViewers() {
+        this._skeletonViewersEnabled = !this._skeletonViewersEnabled;
+        const scene = this.props.skeleton.getScene();
+
+        if (this._skeletonViewersEnabled) {
+            for (var mesh of scene.meshes) {
+                if (mesh.skeleton === this.props.skeleton) {
+                    var found = false;
+                    for (var sIndex = 0; sIndex < this._skeletonViewers.length; sIndex++) {
+                        if (this._skeletonViewers[sIndex].skeleton === mesh.skeleton) {
+                            found = true;
+                            break;
+                        }
+                    }
+                    if (found) {
+                        continue;
+                    }
+                    var viewer = new SkeletonViewer(mesh.skeleton, mesh, scene, true, 0);
+                    viewer.isEnabled = true;
+                    this._skeletonViewers.push(viewer);
+                    if (!mesh.reservedDataStore) {
+                        mesh.reservedDataStore = {};
+                    }
+                    mesh.reservedDataStore.skeletonViewer = viewer;
+                }
+            }
+        } else {
+            for (var index = 0; index < this._skeletonViewers.length; index++) {
+                this._skeletonViewers[index].mesh.reservedDataStore.skeletonViewer = null;
+                this._skeletonViewers[index].dispose();
+            }
+            this._skeletonViewers = [];
+
+        }
+    }
+
+    componentWillMount() {
+        const scene = this.props.skeleton.getScene();
+
+        if (!scene) {
+            return;
+        }
+
+        if (!scene.reservedDataStore) {
+            scene.reservedDataStore = {};
+        }
+
+        for (var mesh of scene.meshes) {
+            if (mesh.skeleton === this.props.skeleton && mesh.reservedDataStore && mesh.reservedDataStore.skeletonViewer) {
+                this._skeletonViewers.push(mesh.reservedDataStore.skeletonViewer);
+            }
+        }
+
+        this._skeletonViewersEnabled = (this._skeletonViewers.length > 0);
+    }
+
+    render() {
+        const skeleton = this.props.skeleton;
+
+        return (
+            <div className="pane">
+                <LineContainerComponent globalState={this.props.globalState} title="GENERAL">
+                    <TextLineComponent label="ID" value={skeleton.id} />
+                    <TextLineComponent label="Bone count" value={skeleton.bones.length.toString()} />
+                    <CheckBoxLineComponent label="Use texture to store matrices" target={skeleton} propertyName="useTextureToStoreBoneMatrices" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                    <CheckBoxLineComponent label="Debug mode" isSelected={() => this._skeletonViewersEnabled} onSelect={() => this.switchSkeletonViewers()} />
+                </LineContainerComponent>
+                <AnimationGridComponent globalState={this.props.globalState} animatable={skeleton} scene={skeleton.getScene()} lockObject={this.props.lockObject} />
+            </div>
+        );
+    }
+}

+ 1 - 1
inspector/src/components/actionTabs/tabs/propertyGrids/postProcesses/commonPostProcessPropertyGridComponent.tsx

@@ -40,7 +40,7 @@ export class CommonPostProcessPropertyGridComponent extends React.Component<ICom
                     }
                     <CheckBoxLineComponent label="Pixel perfect" target={postProcess} propertyName="enablePixelPerfectMode" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
                     <CheckBoxLineComponent label="Fullscreen viewport" target={postProcess} propertyName="forceFullscreenViewport" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
-                    <SliderLineComponent label="Samples" target={postProcess} propertyName="samples" minimum={1} maximum={8} step={1} onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                    <SliderLineComponent label="Samples" target={postProcess} propertyName="samples" minimum={1} maximum={8} step={1} decimalCount={0} onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
                 </LineContainerComponent>
             </div>
         );

+ 6 - 0
inspector/src/components/actionTabs/tabs/propertyGrids/postProcesses/commonRenderingPipelinePropertyGridComponent.tsx

@@ -8,6 +8,7 @@ import { TextLineComponent } from "../../../lines/textLineComponent";
 import { LockObject } from "../lockObject";
 import { PostProcessRenderPipeline } from 'babylonjs/PostProcesses/RenderPipeline/postProcessRenderPipeline';
 import { GlobalState } from '../../../../globalState';
+import { SliderLineComponent } from '../../../lines/sliderLineComponent';
 
 interface ICommonRenderingPipelinePropertyGridComponentProps {
     globalState: GlobalState;
@@ -23,11 +24,16 @@ export class CommonRenderingPipelinePropertyGridComponent extends React.Componen
 
     render() {
         const renderPipeline = this.props.renderPipeline;
+        const renderPipelineAsAny = renderPipeline as any;
 
         return (
             <div>
                 <LineContainerComponent globalState={this.props.globalState} title="GENERAL">
                     <TextLineComponent label="Class" value={renderPipeline.getClassName()} />
+                    {
+                        renderPipelineAsAny.samples !== undefined &&
+                        <SliderLineComponent label="Samples" minimum={1} maximum={8} step={1} decimalCount={0} target={renderPipeline} propertyName="samples" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                    }
                 </LineContainerComponent>
             </div>
         );

+ 59 - 22
inspector/src/components/actionTabs/tabs/propertyGrids/postProcesses/defaultRenderingPipelinePropertyGridComponent.tsx

@@ -46,25 +46,46 @@ export class DefaultRenderingPipelinePropertyGridComponent extends React.Compone
             <div className="pane">
                 <CommonRenderingPipelinePropertyGridComponent globalState={this.props.globalState} lockObject={this.props.lockObject} renderPipeline={renderPipeline} onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
                 <LineContainerComponent globalState={this.props.globalState} title="BLOOM">
-                    <CheckBoxLineComponent label="Enabled" target={renderPipeline} propertyName="bloomEnabled" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
-                    <SliderLineComponent label="Threshold" minimum={0} maximum={1} step={0.05} target={renderPipeline} propertyName="bloomThreshold" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
-                    <SliderLineComponent label="Weight" minimum={0} maximum={1} step={0.05} target={renderPipeline} propertyName="bloomWeight" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
-                    <SliderLineComponent label="Kernel" minimum={0} maximum={128} step={1} target={renderPipeline} propertyName="bloomKernel" onPropertyChangedObservable={this.props.onPropertyChangedObservable} decimalCount={0} />
-                    <SliderLineComponent label="Scale" minimum={0} maximum={1} step={0.25} target={renderPipeline} propertyName="bloomScale" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                    <CheckBoxLineComponent label="Enabled" target={renderPipeline} 
+                    onValueChanged={() => this.forceUpdate()}
+                    propertyName="bloomEnabled" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                    {
+                        renderPipeline.bloomEnabled &&
+                        <div>
+                            <SliderLineComponent label="Threshold" minimum={0} maximum={1} step={0.05} target={renderPipeline} propertyName="bloomThreshold" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                            <SliderLineComponent label="Weight" minimum={0} maximum={1} step={0.05} target={renderPipeline} propertyName="bloomWeight" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                            <SliderLineComponent label="Kernel" minimum={0} maximum={128} step={1} target={renderPipeline} propertyName="bloomKernel" onPropertyChangedObservable={this.props.onPropertyChangedObservable} decimalCount={0} />
+                            <SliderLineComponent label="Scale" minimum={0} maximum={1} step={0.25} target={renderPipeline} propertyName="bloomScale" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                        </div>
+                    }
                 </LineContainerComponent>
                 <LineContainerComponent globalState={this.props.globalState} title="CHROMATIC ABERRATION">
-                    <CheckBoxLineComponent label="Enabled" target={renderPipeline} propertyName="chromaticAberrationEnabled" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
-                    <SliderLineComponent label="aberrationAmount" minimum={0} maximum={128} step={0.1} target={renderPipeline.chromaticAberration} propertyName="aberrationAmount" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
-                    <SliderLineComponent label="Radial intensity" minimum={0} maximum={1} step={0.01} target={renderPipeline.chromaticAberration} propertyName="radialIntensity" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
-                    <Vector2LineComponent label="Center" target={renderPipeline.chromaticAberration} propertyName="centerPosition" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
-                    <Vector2LineComponent label="Direction" target={renderPipeline.chromaticAberration} propertyName="direction" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                    <CheckBoxLineComponent label="Enabled" target={renderPipeline} 
+                    onValueChanged={() => this.forceUpdate()}
+                    propertyName="chromaticAberrationEnabled" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                    {
+                        renderPipeline.chromaticAberrationEnabled &&
+                        <div>
+                            <SliderLineComponent label="aberrationAmount" minimum={0} maximum={128} step={0.1} target={renderPipeline.chromaticAberration} propertyName="aberrationAmount" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                            <SliderLineComponent label="Radial intensity" minimum={0} maximum={1} step={0.01} target={renderPipeline.chromaticAberration} propertyName="radialIntensity" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                            <Vector2LineComponent label="Center" target={renderPipeline.chromaticAberration} propertyName="centerPosition" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                            <Vector2LineComponent label="Direction" target={renderPipeline.chromaticAberration} propertyName="direction" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                        </div>
+                    }
                 </LineContainerComponent>
                 <LineContainerComponent globalState={this.props.globalState} title="DEPTH OF FIELD">
-                    <CheckBoxLineComponent label="Enabled" target={renderPipeline} propertyName="depthOfFieldEnabled" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
-                    <SliderLineComponent label="Focal length" minimum={0} maximum={camera.maxZ} step={0.1} target={renderPipeline.depthOfField} propertyName="focalLength" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
-                    <SliderLineComponent label="fStop" minimum={0} maximum={32} step={0.1} target={renderPipeline.depthOfField} propertyName="fStop" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
-                    <SliderLineComponent label="Distance" minimum={0} maximum={camera.maxZ} step={0.1} target={renderPipeline.depthOfField} propertyName="focusDistance" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
-                    <SliderLineComponent label="Lens size" minimum={0} maximum={1000} step={1} target={renderPipeline.depthOfField} propertyName="lensSize" onPropertyChangedObservable={this.props.onPropertyChangedObservable} decimalCount={0} />
+                    <CheckBoxLineComponent label="Enabled" target={renderPipeline} 
+                    onValueChanged={() => this.forceUpdate()}
+                    propertyName="depthOfFieldEnabled" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                    {
+                        renderPipeline.depthOfFieldEnabled &&
+                        <div>
+                            <SliderLineComponent label="Focal length" minimum={0} maximum={camera.maxZ} step={0.1} target={renderPipeline.depthOfField} propertyName="focalLength" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                            <SliderLineComponent label="fStop" minimum={0} maximum={32} step={0.1} target={renderPipeline.depthOfField} propertyName="fStop" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                            <SliderLineComponent label="Distance" minimum={0} maximum={camera.maxZ} step={0.1} target={renderPipeline.depthOfField} propertyName="focusDistance" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                            <SliderLineComponent label="Lens size" minimum={0} maximum={1000} step={1} target={renderPipeline.depthOfField} propertyName="lensSize" onPropertyChangedObservable={this.props.onPropertyChangedObservable} decimalCount={0} />
+                        </div>                    
+                    }
                 </LineContainerComponent>
                 <LineContainerComponent globalState={this.props.globalState} title="FXAA">
                     <CheckBoxLineComponent label="Enabled" target={renderPipeline} propertyName="fxaaEnabled" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
@@ -73,12 +94,21 @@ export class DefaultRenderingPipelinePropertyGridComponent extends React.Compone
                     <CheckBoxLineComponent label="Enabled" target={renderPipeline} propertyName="glowLayerEnabled" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
                 </LineContainerComponent>
                 <LineContainerComponent globalState={this.props.globalState} title="GRAIN">
-                    <CheckBoxLineComponent label="Enabled" target={renderPipeline} propertyName="grainEnabled" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
-                    <CheckBoxLineComponent label="Animated" target={renderPipeline.grain} propertyName="animated" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
-                    <SliderLineComponent label="Intensity" minimum={0} maximum={50} step={0.1} target={renderPipeline.grain} propertyName="intensity" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                    <CheckBoxLineComponent label="Enabled" target={renderPipeline} 
+                    onValueChanged={() => this.forceUpdate()}
+                    propertyName="grainEnabled" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                    {
+                        renderPipeline.grainEnabled &&
+                        <div>
+                            <CheckBoxLineComponent label="Animated" target={renderPipeline.grain} propertyName="animated" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                            <SliderLineComponent label="Intensity" minimum={0} maximum={50} step={0.1} target={renderPipeline.grain} propertyName="intensity" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                        </div>                    
+                    }
                 </LineContainerComponent>
                 <LineContainerComponent globalState={this.props.globalState} title="IMAGE PROCESSING">
-                    <CheckBoxLineComponent label="Enabled" target={renderPipeline} propertyName="imageProcessingEnabled" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                    <CheckBoxLineComponent label="Enabled" target={renderPipeline} 
+                    onValueChanged={() => this.forceUpdate()}
+                    propertyName="imageProcessingEnabled" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
                     {
                         renderPipeline.imageProcessing &&
                         <div>
@@ -98,9 +128,16 @@ export class DefaultRenderingPipelinePropertyGridComponent extends React.Compone
                     }
                 </LineContainerComponent>
                 <LineContainerComponent globalState={this.props.globalState} title="SHARPEN">
-                    <CheckBoxLineComponent label="Enabled" target={renderPipeline} propertyName="sharpenEnabled" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
-                    <SliderLineComponent label="Color amount" minimum={0} maximum={1} step={0.05} target={renderPipeline.sharpen} propertyName="colorAmount" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
-                    <SliderLineComponent label="Edge amount" minimum={0} maximum={5} step={0.05} target={renderPipeline.sharpen} propertyName="edgeAmount" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                    <CheckBoxLineComponent label="Enabled" target={renderPipeline} 
+                    onValueChanged={() => this.forceUpdate()}
+                    propertyName="sharpenEnabled" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                    {
+                        renderPipeline.sharpenEnabled &&
+                        <div>
+                            <SliderLineComponent label="Color amount" minimum={0} maximum={1} step={0.05} target={renderPipeline.sharpen} propertyName="colorAmount" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                            <SliderLineComponent label="Edge amount" minimum={0} maximum={5} step={0.05} target={renderPipeline.sharpen} propertyName="edgeAmount" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                        </div>                    
+                    }
                 </LineContainerComponent>
             </div>
         );

+ 31 - 0
inspector/src/components/sceneExplorer/entities/boneTreeItemComponent.tsx

@@ -0,0 +1,31 @@
+import { IExplorerExtensibilityGroup } from "babylonjs/Debug/debugLayer";
+import { faBone } from '@fortawesome/free-solid-svg-icons';
+import { TreeItemLabelComponent } from "../treeItemLabelComponent";
+import { ExtensionsComponent } from "../extensionsComponent";
+import * as React from "react";
+import { Bone } from 'babylonjs/Bones/bone';
+
+interface IBoneTreeItemComponenttProps {
+    bone: Bone,
+    extensibilityGroups?: IExplorerExtensibilityGroup[],
+    onClick: () => void
+}
+
+export class BoneTreeItemComponent extends React.Component<IBoneTreeItemComponenttProps> {
+    constructor(props: IBoneTreeItemComponenttProps) {
+        super(props);
+    }
+
+
+    render() {
+        const bone = this.props.bone;
+        return (
+            <div className="skeletonTools">
+                <TreeItemLabelComponent label={bone.name || "no name"} onClick={() => this.props.onClick()} icon={faBone} color="lightgray" />
+                {
+                    <ExtensionsComponent target={bone} extensibilityGroups={this.props.extensibilityGroups} />
+                }
+            </div>
+        )
+    }
+}

+ 31 - 0
inspector/src/components/sceneExplorer/entities/skeletonTreeItemComponent.tsx

@@ -0,0 +1,31 @@
+import { IExplorerExtensibilityGroup } from "babylonjs/Debug/debugLayer";
+import { faSkull } from '@fortawesome/free-solid-svg-icons';
+import { TreeItemLabelComponent } from "../treeItemLabelComponent";
+import { ExtensionsComponent } from "../extensionsComponent";
+import * as React from "react";
+import { Skeleton } from 'babylonjs/Bones/skeleton';
+
+interface ISkeletonTreeItemComponentProps {
+    skeleton: Skeleton,
+    extensibilityGroups?: IExplorerExtensibilityGroup[],
+    onClick: () => void
+}
+
+export class SkeletonTreeItemComponent extends React.Component<ISkeletonTreeItemComponentProps> {
+    constructor(props: ISkeletonTreeItemComponentProps) {
+        super(props);
+    }
+
+
+    render() {
+        const skeleton = this.props.skeleton;
+        return (
+            <div className="skeletonTools">
+                <TreeItemLabelComponent label={skeleton.name || "no name"} onClick={() => this.props.onClick()} icon={faSkull} color="gray" />
+                {
+                    <ExtensionsComponent target={skeleton} extensibilityGroups={this.props.extensibilityGroups} />
+                }
+            </div>
+        )
+    }
+}

+ 12 - 0
inspector/src/components/sceneExplorer/sceneExplorer.scss

@@ -500,6 +500,18 @@
             }
         }    
 
+        .skeletonTools {
+            grid-column: 2;
+            display: grid;
+            grid-template-columns: 1fr auto 5px;
+            align-items: center;
+
+            .extensions {
+                width: 20px;
+                grid-column: 2;
+            }
+        }  
+
         .title {
             grid-column: 1;
             background: transparent;

+ 5 - 1
inspector/src/components/sceneExplorer/sceneExplorerComponent.tsx

@@ -212,7 +212,7 @@ export class SceneExplorerComponent extends React.Component<ISceneExplorerCompon
         if (scene.activeCamera) {
             if (!pipelines.some(p => p.getClassName() === "DefaultRenderingPipeline")) {
                 pipelineContextMenus.push({
-                    label: "Add new DefaultRenderingPipeline",
+                    label: "Add new Default Rendering Pipeline",
                     action: () => {
                         let newPipeline = new DefaultRenderingPipeline("Default rendering pipeline", true, scene, [scene.activeCamera!]);
                         this.props.globalState.onSelectionChangedObservable.notifyObservers(newPipeline);
@@ -226,6 +226,10 @@ export class SceneExplorerComponent extends React.Component<ISceneExplorerCompon
                 <SceneExplorerFilterComponent onFilter={(filter) => this.filterContent(filter)} />
                 <SceneTreeItemComponent globalState={this.props.globalState} extensibilityGroups={this.props.extensibilityGroups} selectedEntity={this.state.selectedEntity} scene={scene} onRefresh={() => this.forceUpdate()} onSelectionChangedObservable={this.props.globalState.onSelectionChangedObservable} />
                 <TreeItemComponent globalState={this.props.globalState} extensibilityGroups={this.props.extensibilityGroups} selectedEntity={this.state.selectedEntity} items={scene.rootNodes} label="Nodes" offset={1} filter={this.state.filter} />
+                {
+                    scene.skeletons.length > 0 &&
+                    <TreeItemComponent globalState={this.props.globalState} extensibilityGroups={this.props.extensibilityGroups} selectedEntity={this.state.selectedEntity} items={scene.skeletons} label="Skeletons" offset={1} filter={this.state.filter} />
+                }
                 <TreeItemComponent globalState={this.props.globalState} extensibilityGroups={this.props.extensibilityGroups} selectedEntity={this.state.selectedEntity} items={scene.materials} label="Materials" offset={1} filter={this.state.filter} />
                 <TreeItemComponent globalState={this.props.globalState} extensibilityGroups={this.props.extensibilityGroups} selectedEntity={this.state.selectedEntity} items={textures} label="Textures" offset={1} filter={this.state.filter} />
                 {

+ 12 - 0
inspector/src/components/sceneExplorer/treeItemSpecializedComponent.tsx

@@ -26,6 +26,10 @@ import { GlobalState } from "../globalState";
 import { PostProcessItemComponent } from './entities/postProcessTreeItemComponent';
 import { RenderingPipelineItemComponent } from './entities/renderingPipelineTreeItemComponent';
 import { PostProcessRenderPipeline } from 'babylonjs/PostProcesses/RenderPipeline/postProcessRenderPipeline';
+import { SkeletonTreeItemComponent } from './entities/skeletonTreeItemComponent';
+import { Skeleton } from 'babylonjs/Bones/skeleton';
+import { BoneTreeItemComponent } from './entities/boneTreeItemComponent';
+import { Bone } from 'babylonjs/Bones/bone';
 
 
 interface ITreeItemSpecializedComponentProps {
@@ -64,6 +68,14 @@ export class TreeItemSpecializedComponent extends React.Component<ITreeItemSpeci
                 }
             }
 
+            if (className.indexOf("Skeleton") !== -1) {
+                return (<SkeletonTreeItemComponent extensibilityGroups={this.props.extensibilityGroups} skeleton={entity as Skeleton} onClick={() => this.onClick()} />);
+            }
+
+            if (className.indexOf("Bone") !== -1) {
+                return (<BoneTreeItemComponent extensibilityGroups={this.props.extensibilityGroups} bone={entity as Bone} onClick={() => this.onClick()} />);
+            }
+
             if (className.indexOf("TransformNode") !== -1) {
                 return (<TransformNodeItemComponent extensibilityGroups={this.props.extensibilityGroups} transformNode={entity as TransformNode} onClick={() => this.onClick()} />);
             }

+ 121 - 24
loaders/src/OBJ/objFileLoader.ts

@@ -10,7 +10,8 @@ import { Texture } from "babylonjs/Materials/Textures/texture";
 import { StandardMaterial } from "babylonjs/Materials/standardMaterial";
 import { AbstractMesh } from "babylonjs/Meshes/abstractMesh";
 import { Mesh } from "babylonjs/Meshes/mesh";
-import { SceneLoader, ISceneLoaderPluginAsync, SceneLoaderProgressEvent } from "babylonjs/Loading/sceneLoader";
+import { SceneLoader, ISceneLoaderPluginAsync, SceneLoaderProgressEvent, ISceneLoaderPluginFactory, ISceneLoaderPlugin } from "babylonjs/Loading/sceneLoader";
+
 import { AssetContainer } from "babylonjs/assetContainer";
 import { Scene } from "babylonjs/scene";
 /**
@@ -225,10 +226,40 @@ type MeshObject = {
 };
 
 /**
+ * Options for loading OBJ/MTL files
+ */
+type MeshLoadOptions = {
+    /**
+     * Defines if UVs are optimized by default during load.
+     */
+    OptimizeWithUV : boolean,
+    /**
+     * Invert model on y-axis (does a model scaling inversion)
+     */
+    InvertY: boolean,
+    /**
+     * Include in meshes the vertex colors available in some OBJ files.  This is not part of OBJ standard.
+     */
+    ImportVertexColors: boolean,
+    /**
+     * Compute the normals for the model, even if normals are present in the file.
+     */
+    ComputeNormals: boolean,
+    /**
+     * Skip loading the materials even if defined in the OBJ file (materials are ignored).
+     */
+    SkipMaterials: boolean,
+    /**
+     * When a material fails to load OBJ loader will silently fail and onSuccess() callback will be triggered.
+     */
+    MaterialLoadingFailsSilently: boolean
+};
+
+/**
  * OBJ file type loader.
  * This is a babylon scene loader plugin.
  */
-export class OBJFileLoader implements ISceneLoaderPluginAsync {
+export class OBJFileLoader implements ISceneLoaderPluginAsync, ISceneLoaderPluginFactory {
 
     /**
      * Defines if UVs are optimized by default during load.
@@ -243,9 +274,21 @@ export class OBJFileLoader implements ISceneLoaderPluginAsync {
      */
     public static IMPORT_VERTEX_COLORS = false;
     /**
-     * Compute the normals for the model, even if normals are present in the file
+     * Compute the normals for the model, even if normals are present in the file.
      */
     public static COMPUTE_NORMALS = false;
+
+    /**
+     * Skip loading the materials even if defined in the OBJ file (materials are ignored).
+     */
+    public static SKIP_MATERIALS = false;
+
+    /**
+     * When a material fails to load OBJ loader will silently fail and onSuccess() callback will be triggered.
+     *
+     * Defaults to true for backwards compatibility.
+     */
+    public static MATERIAL_LOADING_FAILS_SILENTLY = true;
     /**
      * Defines the name of the plugin.
      */
@@ -288,6 +331,28 @@ export class OBJFileLoader implements ISceneLoaderPluginAsync {
     /** @hidden */
     public facePattern5 = /f\s+(((-[\d]{1,}\/-[\d]{1,}\/-[\d]{1,}[\s]?){3,})+)/;
 
+    private _meshLoadOptions: MeshLoadOptions;
+
+    /**
+     * Creates loader for .OBJ files
+     *
+     * @param meshLoadOptions options for loading and parsing OBJ/MTL files.
+     */
+    constructor(meshLoadOptions?: MeshLoadOptions) {
+        this._meshLoadOptions = meshLoadOptions || OBJFileLoader.currentMeshLoadOptions;
+    }
+
+    private static get currentMeshLoadOptions() : MeshLoadOptions {
+        return {
+            ComputeNormals: OBJFileLoader.COMPUTE_NORMALS,
+            ImportVertexColors: OBJFileLoader.IMPORT_VERTEX_COLORS,
+            InvertY: OBJFileLoader.INVERT_Y,
+            MaterialLoadingFailsSilently: OBJFileLoader.MATERIAL_LOADING_FAILS_SILENTLY,
+            OptimizeWithUV: OBJFileLoader.OPTIMIZE_WITH_UV,
+            SkipMaterials: OBJFileLoader.SKIP_MATERIALS
+        };
+    }
+
     /**
      * Calls synchronously the MTL file attached to this obj.
      * Load function or importMesh function don't enable to load 2 files in the same time asynchronously.
@@ -299,17 +364,39 @@ export class OBJFileLoader implements ISceneLoaderPluginAsync {
      * @param onSuccess Callback function to be called when the MTL file is loaded
      * @private
      */
-    private _loadMTL(url: string, rootUrl: string, onSuccess: (response: string | ArrayBuffer, responseUrl?: string) => any) {
+    private _loadMTL(url: string, rootUrl: string, onSuccess: (response: string | ArrayBuffer, responseUrl?: string) => any, onFailure: (pathOfFile: string, exception?: any) => void) {
         //The complete path to the mtl file
         var pathOfFile = Tools.BaseUrl + rootUrl + url;
 
         // Loads through the babylon tools to allow fileInput search.
-        Tools.LoadFile(pathOfFile,
+        Tools.LoadFile(
+            pathOfFile,
             onSuccess,
             undefined,
             undefined,
             false,
-            () => { console.warn("Error - Unable to load " + pathOfFile); });
+            (request?: XMLHttpRequest | undefined, exception?: any) => {
+                onFailure(pathOfFile, exception);
+            }
+        );
+    }
+
+    /**
+     * Instantiates a OBJ file loader plugin.
+     * @returns the created plugin
+     */
+    createPlugin(): ISceneLoaderPluginAsync | ISceneLoaderPlugin {
+        return new OBJFileLoader(OBJFileLoader.currentMeshLoadOptions);
+    }
+
+    /**
+     * If the data string can be loaded directly.
+     *
+     * @param data string containing the file data
+     * @returns if the data can be loaded directly
+     */
+    public canDirectLoad(data: string): boolean {
+        return false;
     }
 
     /**
@@ -381,7 +468,6 @@ export class OBJFileLoader implements ISceneLoaderPluginAsync {
      * @private
      */
     private _parseSolid(meshesNames: any, scene: Scene, data: string, rootUrl: string): Promise<Array<AbstractMesh>> {
-
         var positions: Array<Vector3> = [];      //values for the positions of vertices
         var normals: Array<Vector3> = [];      //Values for the normals
         var uvs: Array<Vector2> = [];      //Values for the textures
@@ -450,7 +536,7 @@ export class OBJFileLoader implements ISceneLoaderPluginAsync {
         var setData = (indicePositionFromObj: number, indiceUvsFromObj: number, indiceNormalFromObj: number, positionVectorFromOBJ: Vector3, textureVectorFromOBJ: Vector2, normalsVectorFromOBJ: Vector3, positionColorsFromOBJ?: Color4) => {
             //Check if this tuple already exists in the list of tuples
             var _index: number;
-            if (OBJFileLoader.OPTIMIZE_WITH_UV) {
+            if (this._meshLoadOptions.OptimizeWithUV) {
                 _index = isInArrayUV(
                     tuplePosNorm,
                     [
@@ -495,7 +581,7 @@ export class OBJFileLoader implements ISceneLoaderPluginAsync {
                 //Add the tuple in the comparison list
                 tuplePosNorm[indicePositionFromObj].normals.push(indiceNormalFromObj);
                 tuplePosNorm[indicePositionFromObj].idx.push(curPositionInIndices++);
-                if (OBJFileLoader.OPTIMIZE_WITH_UV) { tuplePosNorm[indicePositionFromObj].uv.push(indiceUvsFromObj); }
+                if (this._meshLoadOptions.OptimizeWithUV) { tuplePosNorm[indicePositionFromObj].uv.push(indiceUvsFromObj); }
             } else {
                 //The tuple already exists
                 //Add the index of the already existing tuple
@@ -515,7 +601,7 @@ export class OBJFileLoader implements ISceneLoaderPluginAsync {
                 unwrappedNormalsForBabylon.push(wrappedNormalsForBabylon[l].x, wrappedNormalsForBabylon[l].y, wrappedNormalsForBabylon[l].z);
                 unwrappedUVForBabylon.push(wrappedUvsForBabylon[l].x, wrappedUvsForBabylon[l].y); //z is an optional value not supported by BABYLON
             }
-            if (OBJFileLoader.IMPORT_VERTEX_COLORS === true) {
+            if (this._meshLoadOptions.ImportVertexColors === true) {
                 //Push the r, g, b, a values of each element in the unwrapped array
                 unwrappedColorsForBabylon.push(wrappedColorsForBabylon[l].r, wrappedColorsForBabylon[l].g, wrappedColorsForBabylon[l].b, wrappedColorsForBabylon[l].a);
             }
@@ -581,7 +667,7 @@ export class OBJFileLoader implements ISceneLoaderPluginAsync {
                     0, 0,                                           //In the pattern 1, normals and uvs are not defined
                     positions[indicePositionFromObj],               //Get the vectors data
                     Vector2.Zero(), Vector3.Up(),    //Create default vectors
-                    OBJFileLoader.IMPORT_VERTEX_COLORS === true ? colors[indicePositionFromObj] : undefined
+                    this._meshLoadOptions.ImportVertexColors === true ? colors[indicePositionFromObj] : undefined
                 );
             }
             //Reset variable for the next line
@@ -613,7 +699,7 @@ export class OBJFileLoader implements ISceneLoaderPluginAsync {
                     positions[indicePositionFromObj],   //Get the values for each element
                     uvs[indiceUvsFromObj],
                     Vector3.Up(),                //Default value for normals
-                    OBJFileLoader.IMPORT_VERTEX_COLORS === true ? colors[indicePositionFromObj] : undefined
+                    this._meshLoadOptions.ImportVertexColors === true ? colors[indicePositionFromObj] : undefined
                 );
             }
 
@@ -676,7 +762,7 @@ export class OBJFileLoader implements ISceneLoaderPluginAsync {
                     positions[indicePositionFromObj], //Get each vector of data
                     Vector2.Zero(),
                     normals[indiceNormalFromObj],
-                    OBJFileLoader.IMPORT_VERTEX_COLORS === true ? colors[indicePositionFromObj] : undefined
+                    this._meshLoadOptions.ImportVertexColors === true ? colors[indicePositionFromObj] : undefined
                 );
             }
             //Reset variable for the next line
@@ -707,7 +793,7 @@ export class OBJFileLoader implements ISceneLoaderPluginAsync {
                 setData(
                     indicePositionFromObj, indiceUvsFromObj, indiceNormalFromObj,
                     positions[indicePositionFromObj], uvs[indiceUvsFromObj], normals[indiceNormalFromObj], //Set the vector for each component
-                    OBJFileLoader.IMPORT_VERTEX_COLORS === true ? colors[indicePositionFromObj] : undefined
+                    this._meshLoadOptions.ImportVertexColors === true ? colors[indicePositionFromObj] : undefined
                 );
 
             }
@@ -735,7 +821,7 @@ export class OBJFileLoader implements ISceneLoaderPluginAsync {
                 handledMesh.normals = unwrappedNormalsForBabylon.slice();
                 handledMesh.uvs = unwrappedUVForBabylon.slice();
 
-                if (OBJFileLoader.IMPORT_VERTEX_COLORS === true) {
+                if (this._meshLoadOptions.ImportVertexColors === true) {
                     handledMesh.colors = unwrappedColorsForBabylon.slice();
                 }
 
@@ -772,7 +858,7 @@ export class OBJFileLoader implements ISceneLoaderPluginAsync {
                     parseFloat(result[3])
                 ));
 
-                if (OBJFileLoader.IMPORT_VERTEX_COLORS === true) {
+                if (this._meshLoadOptions.ImportVertexColors === true) {
                     if (result.length >= 7) {
                         // TODO: if these numbers are > 1 we can use Color4.FromInts(r,g,b,a)
                         colors.push(new Color4(
@@ -947,7 +1033,7 @@ export class OBJFileLoader implements ISceneLoaderPluginAsync {
             handledMesh.normals = unwrappedNormalsForBabylon;
             handledMesh.uvs = unwrappedUVForBabylon;
 
-            if (OBJFileLoader.IMPORT_VERTEX_COLORS === true) {
+            if (this._meshLoadOptions.ImportVertexColors === true) {
                 handledMesh.colors = unwrappedColorsForBabylon;
             }
         }
@@ -1005,19 +1091,19 @@ export class OBJFileLoader implements ISceneLoaderPluginAsync {
             vertexData.uvs = handledMesh.uvs as FloatArray;
             vertexData.indices = handledMesh.indices as FloatArray;
             vertexData.positions = handledMesh.positions as FloatArray;
-            if (OBJFileLoader.COMPUTE_NORMALS === true) {
+            if (this._meshLoadOptions.ComputeNormals === true) {
                 let normals: Array<number> = new Array<number>();
                 VertexData.ComputeNormals(handledMesh.positions, handledMesh.indices, normals);
                 vertexData.normals = normals;
             } else {
                 vertexData.normals = handledMesh.normals as FloatArray;
             }
-            if (OBJFileLoader.IMPORT_VERTEX_COLORS === true) {
+            if (this._meshLoadOptions.ImportVertexColors === true) {
                 vertexData.colors = handledMesh.colors as FloatArray;
             }
             //Set the data from the VertexBuffer to the current Mesh
             vertexData.applyToMesh(babylonMesh);
-            if (OBJFileLoader.INVERT_Y) {
+            if (this._meshLoadOptions.InvertY) {
                 babylonMesh.scaling.y *= -1;
             }
 
@@ -1028,10 +1114,10 @@ export class OBJFileLoader implements ISceneLoaderPluginAsync {
         let mtlPromises: Array<Promise<any>> = [];
         //load the materials
         //Check if we have a file to load
-        if (fileToLoad !== "") {
+        if (fileToLoad !== "" && this._meshLoadOptions.SkipMaterials === false) {
             //Load the file synchronously
             mtlPromises.push(new Promise((resolve, reject) => {
-                this._loadMTL(fileToLoad, rootUrl, function(dataLoaded) {
+                this._loadMTL(fileToLoad, rootUrl, (dataLoaded) => {
                     try {
                         //Create materials thanks MTLLoader function
                         materialsFromMTLFile.parseMTL(scene, dataLoaded, rootUrl);
@@ -1062,9 +1148,20 @@ export class OBJFileLoader implements ISceneLoaderPluginAsync {
                         }
                         resolve();
                     } catch (e) {
-                        reject(e);
+                        Tools.Warn(`Error processing MTL file: '${fileToLoad}'`);
+                        if (this._meshLoadOptions.MaterialLoadingFailsSilently) {
+                            resolve();
+                        } else {
+                            reject(e);
+                        }
+                    }
+                }, (pathOfFile: string, exception?: any) => {
+                    Tools.Warn(`Error downloading MTL file: '${fileToLoad}'`);
+                    if (this._meshLoadOptions.MaterialLoadingFailsSilently) {
+                        resolve();
+                    } else {
+                        reject(exception);
                     }
-
                 });
             }));
 

+ 2 - 2
package.json

@@ -9,7 +9,7 @@
     ],
     "name": "babylonjs",
     "description": "Babylon.js is a JavaScript 3D engine based on webgl.",
-    "version": "4.0.0-alpha.22",
+    "version": "4.0.0-alpha.25",
     "repository": {
         "type": "git",
         "url": "https://github.com/BabylonJS/Babylon.js.git"
@@ -106,4 +106,4 @@
         "webpack-stream": "5.0.0",
         "react-contextmenu": "~2.10.0"
     }
-}
+}

+ 16 - 0
src/Bones/bone.ts

@@ -103,6 +103,14 @@ export class Bone extends Node {
         }
     }
 
+    /**
+     * Gets the current object class name.
+     * @return the class name
+     */
+    public getClassName(): string {
+        return "Bone";
+    }
+
     // Members
 
     /**
@@ -122,6 +130,14 @@ export class Bone extends Node {
     }
 
     /**
+     * Returns an array containing the root bones
+     * @returns an array containing the root bones
+     */
+    public getChildren(): Array<Bone> {
+        return this.children;
+    }
+
+    /**
      * Sets the parent bone
      * @param parent defines the parent (can be null if the bone is the root)
      * @param updateDifferenceMatrix defines if the difference matrix must be updated

+ 17 - 3
src/Bones/skeleton.ts

@@ -126,6 +126,22 @@ export class Skeleton implements IAnimatable {
         this._canUseTextureForBones = engineCaps.textureFloat && engineCaps.maxVertexTextureImageUnits > 0;
     }
 
+    /**
+     * Gets the current object class name.
+     * @return the class name
+     */
+    public getClassName(): string {
+        return "Skeleton";
+    }
+
+    /**
+     * Returns an array containing the root bones
+     * @returns an array containing the root bones
+     */
+    public getChildren(): Array<Bone> {
+        return this.bones.filter((b) => !b.getParent());
+    }
+
     // Members
     /**
      * Gets the list of transform matrices to send to shaders (one matrix per bone)
@@ -247,10 +263,8 @@ export class Skeleton implements IAnimatable {
     public getAnimationRanges(): Nullable<AnimationRange>[] {
         var animationRanges: Nullable<AnimationRange>[] = [];
         var name: string;
-        var i: number = 0;
         for (name in this._ranges) {
-            animationRanges[i] = this._ranges[name];
-            i++;
+            animationRanges.push(this._ranges[name]);
         }
         return animationRanges;
     }

+ 1 - 0
src/Cameras/Inputs/freeCameraDeviceOrientationInput.ts

@@ -97,6 +97,7 @@ export class FreeCameraDeviceOrientationInput implements ICameraInput<FreeCamera
     public detachControl(element: Nullable<HTMLElement>): void {
         window.removeEventListener("orientationchange", this._orientationChanged);
         window.removeEventListener("deviceorientation", this._deviceOrientation);
+        this._alpha = 0;
     }
 
     /**

+ 22 - 1
src/Cameras/VR/vrExperienceHelper.ts

@@ -280,6 +280,16 @@ class VRExperienceHelperCameraGazer extends VRExperienceHelperGazer {
 }
 
 /**
+ * Event containing information after VR has been entered
+ */
+export class OnAfterEnteringVRObservableEvent {
+    /**
+     * If entering vr was successful
+     */
+    public success: boolean;
+}
+
+/**
  * Helps to quickly add VR support to an existing scene.
  * See http://doc.babylonjs.com/how_to/webvr_helper
  */
@@ -316,11 +326,16 @@ export class VRExperienceHelper {
     private _onVRRequestPresentComplete: (success: boolean) => void;
 
     /**
-     * Observable raised when entering VR.
+     * Observable raised right before entering VR.
      */
     public onEnteringVRObservable = new Observable<VRExperienceHelper>();
 
     /**
+     * Observable raised when entering VR has completed.
+     */
+    public onAfterEnteringVRObservable = new Observable<OnAfterEnteringVRObservableEvent>();
+
+    /**
      * Observable raised when exiting VR.
      */
     public onExitingVRObservable = new Observable<VRExperienceHelper>();
@@ -966,6 +981,9 @@ export class VRExperienceHelper {
         // If WebVR is supported and a headset is connected
         if (this._webVRready) {
             if (!this._webVRpresenting) {
+                this._scene.getEngine().onVRRequestPresentComplete.addOnce((result) => {
+                    this.onAfterEnteringVRObservable.notifyObservers({success: result});
+                });
                 this._webVRCamera.position = this._position;
                 this._scene.activeCamera = this._webVRCamera;
             }
@@ -978,6 +996,9 @@ export class VRExperienceHelper {
             this._scene.activeCamera = this._vrDeviceOrientationCamera;
             this._scene.getEngine().enterFullscreen(this.requestPointerLockOnFullScreen);
             this.updateButtonVisibility();
+            this._vrDeviceOrientationCamera.onViewMatrixChangedObservable.addOnce(() => {
+                this.onAfterEnteringVRObservable.notifyObservers({success: true});
+            });
         }
 
         if (this._scene.activeCamera && this._canvas) {

+ 21 - 10
src/Cameras/arcRotateCamera.ts

@@ -64,6 +64,17 @@ export class ArcRotateCamera extends TargetCamera {
     }
 
     /**
+     * Define the current local position of the camera in the scene
+     */
+    public get position(): Vector3 {
+        return this._position;
+    }
+
+    public set position(newPosition: Vector3) {
+        this.setPosition(newPosition);
+    }
+
+    /**
      * Current inertia value on the longitudinal axis.
      * The bigger this number the longer it will take for the camera to stop.
      */
@@ -852,7 +863,7 @@ export class ArcRotateCamera extends TargetCamera {
             this.getViewMatrix(); // Force position update
         }
 
-        this.position.subtractToRef(this._getTargetPosition(), this._computationVector);
+        this._position.subtractToRef(this._getTargetPosition(), this._computationVector);
         this.radius = this._computationVector.length();
 
         if (this.radius === 0) {
@@ -877,10 +888,10 @@ export class ArcRotateCamera extends TargetCamera {
      * @param position Defines the position to set the camera at
      */
     public setPosition(position: Vector3): void {
-        if (this.position.equals(position)) {
+        if (this._position.equals(position)) {
             return;
         }
-        this.position.copyFrom(position);
+        this._position.copyFrom(position);
 
         this.rebuildAnglesAndRadius(false);
     }
@@ -960,11 +971,11 @@ export class ArcRotateCamera extends TargetCamera {
                 this._collider = coordinator.createCollider();
             }
             this._collider._radius = this.collisionRadius;
-            this._newPosition.subtractToRef(this.position, this._collisionVelocity);
+            this._newPosition.subtractToRef(this._position, this._collisionVelocity);
             this._collisionTriggered = true;
-            coordinator.getNewPosition(this.position, this._collisionVelocity, this._collider, 3, null, this._onCollisionPositionChange, this.uniqueId);
+            coordinator.getNewPosition(this._position, this._collisionVelocity, this._collider, 3, null, this._onCollisionPositionChange, this.uniqueId);
         } else {
-            this.position.copyFrom(this._newPosition);
+            this._position.copyFrom(this._newPosition);
 
             var up = this.upVector;
             if (this.allowUpsideDown && sinb < 0) {
@@ -972,7 +983,7 @@ export class ArcRotateCamera extends TargetCamera {
                 up = up.negate();
             }
 
-            this._computeViewMatrix(this.position, target, up);
+            this._computeViewMatrix(this._position, target, up);
 
             this._viewMatrix.addAtIndex(12, this.targetScreenOffset.x);
             this._viewMatrix.addAtIndex(13, this.targetScreenOffset.y);
@@ -984,7 +995,7 @@ export class ArcRotateCamera extends TargetCamera {
     protected _onCollisionPositionChange = (collisionId: number, newPosition: Vector3, collidedMesh: Nullable<AbstractMesh> = null) => {
 
         if (!collidedMesh) {
-            this._previousPosition.copyFrom(this.position);
+            this._previousPosition.copyFrom(this._position);
         } else {
             this.setPosition(newPosition);
 
@@ -1006,7 +1017,7 @@ export class ArcRotateCamera extends TargetCamera {
         var target = this._getTargetPosition();
         this._computationVector.copyFromFloats(this.radius * cosa * sinb, this.radius * cosb, this.radius * sina * sinb);
         target.addToRef(this._computationVector, this._newPosition);
-        this.position.copyFrom(this._newPosition);
+        this._position.copyFrom(this._newPosition);
 
         var up = this.upVector;
         if (this.allowUpsideDown && this.beta < 0) {
@@ -1014,7 +1025,7 @@ export class ArcRotateCamera extends TargetCamera {
             up = up.negate();
         }
 
-        this._computeViewMatrix(this.position, target, up);
+        this._computeViewMatrix(this._position, target, up);
         this._viewMatrix.addAtIndex(12, this.targetScreenOffset.x);
         this._viewMatrix.addAtIndex(13, this.targetScreenOffset.y);
 

+ 35 - 2
src/Cameras/camera.ts

@@ -103,11 +103,20 @@ export class Camera extends Node {
      */
     public inputs: CameraInputsManager<Camera>;
 
+    /** @hidden */
+    @serializeAsVector3("position")
+    public _position = Vector3.Zero();
+
     /**
      * Define the current local position of the camera in the scene
      */
-    @serializeAsVector3()
-    public position: Vector3;
+    public get position(): Vector3 {
+        return this._position;
+    }
+
+    public set position(newPosition: Vector3) {
+        this._position = newPosition;
+    }
 
     /**
      * The vector the camera should consider as up.
@@ -876,6 +885,24 @@ export class Camera extends Node {
         super.dispose(doNotRecurse, disposeMaterialAndTextures);
     }
 
+    /** @hidden */
+    public _isLeftCamera = false;
+    /**
+     * Gets the left camera of a rig setup in case of Rigged Camera
+     */
+    public get isLeftCamera(): boolean {
+        return this._isLeftCamera;
+    }
+
+    /** @hidden */
+    public _isRightCamera = true;
+    /**
+     * Gets the right camera of a rig setup in case of Rigged Camera
+     */
+    public get isRightCamera(): boolean {
+        return this._isRightCamera;
+    }
+
     /**
      * Gets the left camera of a rig setup in case of Rigged Camera
      */
@@ -943,7 +970,13 @@ export class Camera extends Node {
         // create the rig cameras, unless none
         if (this.cameraRigMode !== Camera.RIG_MODE_NONE) {
             let leftCamera = this.createRigCamera(this.name + "_L", 0);
+            if (leftCamera) {
+                leftCamera._isLeftCamera = true;
+            }
             let rightCamera = this.createRigCamera(this.name + "_R", 1);
+            if (rightCamera) {
+                rightCamera._isRightCamera = true;
+            }
             if (leftCamera && rightCamera) {
                 this._rigCameras.push(leftCamera);
                 this._rigCameras.push(rightCamera);

+ 1 - 1
src/Engines/engine.ts

@@ -491,7 +491,7 @@ export class Engine {
      * Returns the current version of the framework
      */
     public static get Version(): string {
-        return "4.0.0-alpha.22";
+        return "4.0.0-alpha.25";
     }
 
     /**

+ 27 - 10
src/Gizmos/boundingBoxGizmo.ts

@@ -18,6 +18,7 @@ import { StandardMaterial } from "../Materials/standardMaterial";
 import { PivotTools } from "../Misc/pivotTools";
 
 import "../Meshes/Builders/boxBuilder";
+import { LinesMesh } from '../Meshes/linesMesh';
 
 /**
  * Bounding box gizmo
@@ -91,6 +92,22 @@ export class BoundingBoxGizmo extends Gizmo {
     private _dragMesh: Nullable<Mesh> = null;
     private pointerDragBehavior = new PointerDragBehavior();
 
+    private coloredMaterial: StandardMaterial;
+    private hoverColoredMaterial: StandardMaterial;
+
+    /**
+     * Sets the color of the bounding box gizmo
+     * @param color the color to set
+     */
+    public setColor(color: Color3) {
+        this.coloredMaterial.emissiveColor = color;
+        this.hoverColoredMaterial.emissiveColor = color.clone().add(new Color3(0.3, 0.3, 0.3));
+        this._lineBoundingBox.getChildren().forEach((l) => {
+            if ((l as LinesMesh).color) {
+                (l as LinesMesh).color = color;
+            }
+        });
+    }
     /**
      * Creates an BoundingBoxGizmo
      * @param gizmoLayer The utility layer the gizmo will be added to
@@ -104,12 +121,10 @@ export class BoundingBoxGizmo extends Gizmo {
 
         this._anchorMesh = new AbstractMesh("anchor", gizmoLayer.utilityLayerScene);
         // Create Materials
-        var coloredMaterial = new StandardMaterial("", gizmoLayer.utilityLayerScene);
-        coloredMaterial.disableLighting = true;
-        coloredMaterial.emissiveColor = color;
-        var hoverColoredMaterial = new StandardMaterial("", gizmoLayer.utilityLayerScene);
-        hoverColoredMaterial.disableLighting = true;
-        hoverColoredMaterial.emissiveColor = color.clone().add(new Color3(0.3, 0.3, 0.3));
+        this.coloredMaterial = new StandardMaterial("", gizmoLayer.utilityLayerScene);
+        this.coloredMaterial.disableLighting = true;
+        this.hoverColoredMaterial = new StandardMaterial("", gizmoLayer.utilityLayerScene);
+        this.hoverColoredMaterial.disableLighting = true;
 
         // Build bounding box out of lines
         this._lineBoundingBox = new AbstractMesh("", gizmoLayer.utilityLayerScene);
@@ -135,13 +150,15 @@ export class BoundingBoxGizmo extends Gizmo {
         });
         this._rootMesh.addChild(this._lineBoundingBox);
 
+        this.setColor(color);
+
         // Create rotation spheres
         this._rotateSpheresParent = new AbstractMesh("", gizmoLayer.utilityLayerScene);
         this._rotateSpheresParent.rotationQuaternion = new Quaternion();
         for (let i = 0; i < 12; i++) {
             let sphere = SphereBuilder.CreateSphere("", { diameter: 1 }, gizmoLayer.utilityLayerScene);
             sphere.rotationQuaternion = new Quaternion();
-            sphere.material = coloredMaterial;
+            sphere.material = this.coloredMaterial;
 
             // Drag behavior
             var _dragBehavior = new PointerDragBehavior({});
@@ -230,7 +247,7 @@ export class BoundingBoxGizmo extends Gizmo {
             for (var j = 0; j < 2; j++) {
                 for (var k = 0; k < 2; k++) {
                     let box = BoxBuilder.CreateBox("", { size: 1 }, gizmoLayer.utilityLayerScene);
-                    box.material = coloredMaterial;
+                    box.material = this.coloredMaterial;
 
                     // Dragging logic
                     let dragAxis = new Vector3(i == 0 ? -1 : 1, j == 0 ? -1 : 1, k == 0 ? -1 : 1);
@@ -301,12 +318,12 @@ export class BoundingBoxGizmo extends Gizmo {
                 this._rotateSpheresParent.getChildMeshes().concat(this._scaleBoxesParent.getChildMeshes()).forEach((mesh) => {
                     if (pointerInfo.pickInfo && pointerInfo.pickInfo.pickedMesh == mesh) {
                         pointerIds[(<PointerEvent>pointerInfo.event).pointerId] = mesh;
-                        mesh.material = hoverColoredMaterial;
+                        mesh.material = this.hoverColoredMaterial;
                     }
                 });
             } else {
                 if (pointerInfo.pickInfo && pointerInfo.pickInfo.pickedMesh != pointerIds[(<PointerEvent>pointerInfo.event).pointerId]) {
-                    pointerIds[(<PointerEvent>pointerInfo.event).pointerId].material = coloredMaterial;
+                    pointerIds[(<PointerEvent>pointerInfo.event).pointerId].material = this.coloredMaterial;
                     delete pointerIds[(<PointerEvent>pointerInfo.event).pointerId];
                 }
             }

+ 68 - 0
src/Helpers/videoDome.ts

@@ -7,6 +7,10 @@ import { Texture } from "../Materials/Textures/texture";
 import { VideoTexture, VideoTextureSettings } from "../Materials/Textures/videoTexture";
 import { BackgroundMaterial } from "../Materials/Background/backgroundMaterial";
 import "../Meshes/Builders/sphereBuilder";
+import { Nullable } from "../types";
+import { Observer } from "../Misc/observable";
+
+declare type Camera = import("../Cameras/camera").Camera;
 
 /**
  * Display a 360 degree video on an approximately spherical surface, useful for VR applications or skyboxes.
@@ -15,6 +19,19 @@ import "../Meshes/Builders/sphereBuilder";
  * Potential additions to this helper include zoom and and non-infinite distance rendering effects.
  */
 export class VideoDome extends TransformNode {
+    /**
+     * Define the video source as a Monoscopic panoramic 360 video.
+     */
+    public static readonly MODE_MONOSCOPIC = 0;
+    /**
+     * Define the video source as a Stereoscopic TopBottom/OverUnder panoramic 360 video.
+     */
+    public static readonly MODE_TOPBOTTOM = 1;
+    /**
+     * Define the video source as a Stereoscopic Side by Side panoramic 360 video.
+     */
+    public static readonly MODE_SIDEBYSIDE = 2;
+
     private _useDirectMapping = false;
 
     /**
@@ -50,6 +67,29 @@ export class VideoDome extends TransformNode {
         this._material.fovMultiplier = value;
     }
 
+    private _videoMode = VideoDome.MODE_MONOSCOPIC;
+    /**
+     * Gets or set the current video mode for the video. It can be:
+     * * VideoDome.MODE_MONOSCOPIC : Define the video source as a Monoscopic panoramic 360 video.
+     * * VideoDome.MODE_TOPBOTTOM  : Define the video source as a Stereoscopic TopBottom/OverUnder panoramic 360 video.
+     * * VideoDome.MODE_SIDEBYSIDE : Define the video source as a Stereoscopic Side by Side panoramic 360 video.
+     */
+    public get videoMode(): number {
+        return this._videoMode;
+    }
+    public set videoMode(value: number) {
+        if (this._videoMode === value) {
+            return;
+        }
+
+        this._changeVideoMode(value);
+    }
+
+    /**
+     * Oberserver used in Stereoscopic VR Mode.
+     */
+    private _onBeforeCameraRenderObserver: Nullable<Observer<Camera>> = null;
+
     /**
      * Create an instance of this class and pass through the parameters to the relevant classes, VideoTexture, StandardMaterial, and Mesh.
      * @param name Element's name, child elements will append suffixes for their own names.
@@ -120,6 +160,32 @@ export class VideoDome extends TransformNode {
         }
     }
 
+    private _changeVideoMode(value: number): void {
+        this._scene.onBeforeCameraRenderObservable.remove(this._onBeforeCameraRenderObserver);
+        this._videoMode = value;
+
+        // Default Setup and Reset.
+        this._videoTexture.uScale = 1;
+        this._videoTexture.vScale = 1;
+        this._videoTexture.uOffset = 0;
+        this._videoTexture.vOffset = 0;
+
+        switch (value) {
+            case VideoDome.MODE_SIDEBYSIDE:
+                this._videoTexture.uScale = 0.5;
+                this._onBeforeCameraRenderObserver = this._scene.onBeforeCameraRenderObservable.add((camera) => {
+                    this._videoTexture.uOffset = camera.isRightCamera ? 0.5 : 0.0;
+                });
+            break;
+            case VideoDome.MODE_TOPBOTTOM:
+                this._videoTexture.vScale = 0.5;
+                this._onBeforeCameraRenderObserver = this._scene.onBeforeCameraRenderObservable.add((camera) => {
+                    this._videoTexture.vOffset = camera.isRightCamera ? 0.5 : 0.0;
+                });
+                break;
+        }
+    }
+
     /**
      * Releases resources associated with this node.
      * @param doNotRecurse Set to true to not recurse into each children (recurse into each children by default)
@@ -130,6 +196,8 @@ export class VideoDome extends TransformNode {
         this._mesh.dispose();
         this._material.dispose();
 
+        this._scene.onBeforeCameraRenderObservable.remove(this._onBeforeCameraRenderObserver);
+
         super.dispose(doNotRecurse, disposeMaterialAndTextures);
     }
 }

+ 4 - 0
src/Materials/PBR/pbrBaseMaterial.ts

@@ -181,10 +181,14 @@ class PBRMaterialDefines extends MaterialDefines
     public SPECULARAA = false;
 
     public CLEARCOAT = false;
+    public CLEARCOAT_DEFAULTIOR = false;
     public CLEARCOAT_TEXTURE = false;
     public CLEARCOAT_TEXTUREDIRECTUV = 0;
     public CLEARCOAT_BUMP = false;
     public CLEARCOAT_BUMPDIRECTUV = 0;
+    public CLEARCOAT_TINT = false;
+    public CLEARCOAT_TINT_TEXTURE = false;
+    public CLEARCOAT_TINT_TEXTUREDIRECTUV = 0;
 
     public ANISOTROPIC = false;
 

+ 141 - 5
src/Materials/PBR/pbrClearCoatConfiguration.ts

@@ -1,6 +1,7 @@
 import { Nullable } from "../../types";
 import { IAnimatable } from "../../Misc/tools";
 import { SerializationHelper, serialize, serializeAsTexture, expandToProperty } from "../../Misc/decorators";
+import { Color3 } from "../../Maths/math";
 import { BaseTexture } from "../../Materials/Textures/baseTexture";
 import { EffectFallbacks } from "../../Materials/effect";
 import { MaterialFlags } from "../materialFlags";
@@ -15,11 +16,16 @@ declare type Scene = import("../../scene").Scene;
  */
 export interface IMaterialClearCoatDefines {
     CLEARCOAT: boolean;
+    CLEARCOAT_DEFAULTIOR: boolean;
     CLEARCOAT_TEXTURE: boolean;
     CLEARCOAT_TEXTUREDIRECTUV: number;
     CLEARCOAT_BUMP: boolean;
     CLEARCOAT_BUMPDIRECTUV: number;
 
+    CLEARCOAT_TINT: boolean;
+    CLEARCOAT_TINT_TEXTURE: boolean;
+    CLEARCOAT_TINT_TEXTUREDIRECTUV: number;
+
     /** @hidden */
     _areTexturesDirty: boolean;
 }
@@ -28,6 +34,11 @@ export interface IMaterialClearCoatDefines {
  * Define the code related to the clear coat parameters of the pbr material.
  */
 export class PBRClearCoatConfiguration {
+    /**
+     * This defaults to 1.5 corresponding to a 0.04 f0 or a 4% reflectance at normal incidence
+     * The default fits with a polyurethane material.
+     */
+    private static readonly _DefaultIndiceOfRefraction = 1.5;
 
     @serialize()
     private _isEnabled = false;
@@ -49,6 +60,17 @@ export class PBRClearCoatConfiguration {
     @serialize()
     public roughness: number = 0;
 
+    @serialize()
+    private _indiceOfRefraction = PBRClearCoatConfiguration._DefaultIndiceOfRefraction;
+    /**
+     * Defines the indice of refraction of the clear coat.
+     * This defaults to 1.5 corresponding to a 0.04 f0 or a 4% reflectance at normal incidence
+     * The default fits with a polyurethane material.
+     * Changing the default value is more performance intensive.
+     */
+    @expandToProperty("_markAllSubMeshesAsTexturesDirty")
+    public indiceOfRefraction = PBRClearCoatConfiguration._DefaultIndiceOfRefraction;
+
     @serializeAsTexture()
     private _texture: Nullable<BaseTexture> = null;
     /**
@@ -65,6 +87,46 @@ export class PBRClearCoatConfiguration {
     @expandToProperty("_markAllSubMeshesAsTexturesDirty")
     public bumpTexture: Nullable<BaseTexture> = null;
 
+    @serialize()
+    private _isTintEnabled = false;
+    /**
+     * Defines if the clear coat tint is enabled in the material.
+     */
+    @expandToProperty("_markAllSubMeshesAsTexturesDirty")
+    public isTintEnabled = false;
+
+    /**
+     * Defines if the clear coat tint is enabled in the material.
+     * This is only use if tint is enabled
+     */
+    @serialize()
+    public tintColor = Color3.White();
+
+    /**
+     * Defines if the distance at which the tint color should be found in the
+     * clear coat media.
+     * This is only use if tint is enabled
+     */
+    @serialize()
+    public tintColorAtDistance = 1;
+
+    /**
+     * Defines the clear coat layer thickness.
+     * This is only use if tint is enabled
+     */
+    @serialize()
+    public tintThickness: number = 1;
+
+    @serializeAsTexture()
+    private _tintTexture: Nullable<BaseTexture> = null;
+    /**
+     * Stores the clear tint values in a texture.
+     * rgb is tint
+     * a is a thickness factor
+     */
+    @expandToProperty("_markAllSubMeshesAsTexturesDirty")
+    public tintTexture: Nullable<BaseTexture> = null;
+
     /** @hidden */
     private _internalMarkAllSubMeshesAsTexturesDirty: () => void;
 
@@ -104,6 +166,12 @@ export class PBRClearCoatConfiguration {
                         return false;
                     }
                 }
+
+                if (this._isTintEnabled && this._tintTexture && MaterialFlags.ClearCoatTintTextureEnabled) {
+                    if (!this._tintTexture.isReadyOrNotBlocking()) {
+                        return false;
+                    }
+                }
             }
         }
 
@@ -132,6 +200,22 @@ export class PBRClearCoatConfiguration {
                     } else {
                         defines.CLEARCOAT_BUMP = false;
                     }
+
+                    defines.CLEARCOAT_DEFAULTIOR = this._indiceOfRefraction === PBRClearCoatConfiguration._DefaultIndiceOfRefraction;
+
+                    if (this._isTintEnabled) {
+                        defines.CLEARCOAT_TINT = true;
+                        if (this._tintTexture && MaterialFlags.ClearCoatTintTextureEnabled) {
+                            MaterialHelper.PrepareDefinesForMergedUV(this._tintTexture, defines, "CLEARCOAT_TINT_TEXTURE");
+                        }
+                        else {
+                            defines.CLEARCOAT_TINT_TEXTURE = false;
+                        }
+                    }
+                    else {
+                        defines.CLEARCOAT_TINT = false;
+                        defines.CLEARCOAT_TINT_TEXTURE = false;
+                    }
                 }
             }
         }
@@ -139,6 +223,8 @@ export class PBRClearCoatConfiguration {
             defines.CLEARCOAT = false;
             defines.CLEARCOAT_TEXTURE = false;
             defines.CLEARCOAT_BUMP = false;
+            defines.CLEARCOAT_TINT = false;
+            defines.CLEARCOAT_TINT_TEXTURE = false;
         }
     }
 
@@ -170,8 +256,29 @@ export class PBRClearCoatConfiguration {
                 }
             }
 
-            // Clear Coat
+            if (this._tintTexture && MaterialFlags.ClearCoatTintTextureEnabled) {
+                uniformBuffer.updateFloat2("vClearCoatTintInfos", this._tintTexture.coordinatesIndex, this._tintTexture.level);
+                MaterialHelper.BindTextureMatrix(this._tintTexture, uniformBuffer, "clearCoatTint");
+            }
+
+            // Clear Coat General params
             uniformBuffer.updateFloat2("vClearCoatParams", this.intensity, this.roughness);
+
+            // Clear Coat Refraction params
+            const a = 1 - this._indiceOfRefraction;
+            const b = 1 + this._indiceOfRefraction;
+            const f0 = Math.pow((-a / b), 2); // Schlicks approx: (ior1 - ior2) / (ior1 + ior2) where ior2 for air is close to vacuum = 1.
+            const eta = 1 / this._indiceOfRefraction;
+            uniformBuffer.updateFloat4("vClearCoatRefractionParams", f0, eta, a,  b);
+
+            if (this._isTintEnabled) {
+                uniformBuffer.updateFloat4("vClearCoatTintParams",
+                    this.tintColor.r,
+                    this.tintColor.g,
+                    this.tintColor.b,
+                    Math.max(0.00001, this.tintThickness));
+                uniformBuffer.updateFloat("clearCoatColorAtDistance", Math.max(0.00001, this.tintColorAtDistance));
+            }
         }
 
         // Textures
@@ -183,6 +290,10 @@ export class PBRClearCoatConfiguration {
             if (this._bumpTexture && engine.getCaps().standardDerivatives && MaterialFlags.ClearCoatBumpTextureEnabled && !disableBumpMap) {
                 uniformBuffer.setTexture("clearCoatBumpSampler", this._bumpTexture);
             }
+
+            if (this._isTintEnabled && this._tintTexture && MaterialFlags.ClearCoatTintTextureEnabled) {
+                uniformBuffer.setTexture("clearCoatTintSampler", this._tintTexture);
+            }
         }
     }
 
@@ -200,6 +311,10 @@ export class PBRClearCoatConfiguration {
             return true;
         }
 
+        if (this._tintTexture === texture) {
+            return true;
+        }
+
         return false;
     }
 
@@ -215,6 +330,10 @@ export class PBRClearCoatConfiguration {
         if (this._bumpTexture) {
             activeTextures.push(this._bumpTexture);
         }
+
+        if (this._tintTexture) {
+            activeTextures.push(this._tintTexture);
+        }
     }
 
     /**
@@ -229,6 +348,10 @@ export class PBRClearCoatConfiguration {
         if (this._bumpTexture && this._bumpTexture.animations && this._bumpTexture.animations.length > 0) {
             animatables.push(this._bumpTexture);
         }
+
+        if (this._tintTexture && this._tintTexture.animations && this._tintTexture.animations.length > 0) {
+            animatables.push(this._tintTexture);
+        }
     }
 
     /**
@@ -244,6 +367,10 @@ export class PBRClearCoatConfiguration {
             if (this._bumpTexture) {
                 this._bumpTexture.dispose();
             }
+
+            if (this._tintTexture) {
+                this._tintTexture.dispose();
+            }
         }
     }
 
@@ -290,6 +417,9 @@ export class PBRClearCoatConfiguration {
         if (defines.CLEARCOAT_BUMP) {
             fallbacks.addFallback(currentRank++, "CLEARCOAT_BUMP");
         }
+        if (defines.CLEARCOAT_TINT) {
+            fallbacks.addFallback(currentRank++, "CLEARCOAT_TINT");
+        }
         if (defines.CLEARCOAT) {
             fallbacks.addFallback(currentRank++, "CLEARCOAT");
         }
@@ -301,9 +431,10 @@ export class PBRClearCoatConfiguration {
      * @param uniforms defines the current uniform list.
      */
     public static AddUniforms(uniforms: string[]): void {
-        uniforms.push("vClearCoatTangentSpaceParams", "vClearCoatParams",
-            "clearCoatMatrix", "clearCoatBumpMatrix",
-            "vClearCoatInfos", "vClearCoatBumpInfos");
+        uniforms.push("vClearCoatTangentSpaceParams", "vClearCoatParams", "vClearCoatRefractionParams",
+            "vClearCoatTintParams", "clearCoatColorAtDistance",
+            "clearCoatMatrix", "clearCoatBumpMatrix", "clearCoatTintMatrix",
+            "vClearCoatInfos", "vClearCoatBumpInfos", "vClearCoatTintInfos");
     }
 
     /**
@@ -311,7 +442,7 @@ export class PBRClearCoatConfiguration {
      * @param samplers defines the current sampler list.
      */
     public static AddSamplers(samplers: string[]): void {
-        samplers.push("clearCoatSampler", "clearCoatBumpSampler");
+        samplers.push("clearCoatSampler", "clearCoatBumpSampler", "clearCoatTintSampler");
     }
 
     /**
@@ -320,10 +451,15 @@ export class PBRClearCoatConfiguration {
      */
     public static PrepareUniformBuffer(uniformBuffer: UniformBuffer): void {
         uniformBuffer.addUniform("vClearCoatParams", 2);
+        uniformBuffer.addUniform("vClearCoatRefractionParams", 4);
         uniformBuffer.addUniform("vClearCoatInfos", 2);
         uniformBuffer.addUniform("clearCoatMatrix", 16);
         uniformBuffer.addUniform("vClearCoatBumpInfos", 2);
         uniformBuffer.addUniform("vClearCoatTangentSpaceParams", 2);
         uniformBuffer.addUniform("clearCoatBumpMatrix", 16);
+        uniformBuffer.addUniform("vClearCoatTintParams", 4);
+        uniformBuffer.addUniform("clearCoatColorAtDistance", 1);
+        uniformBuffer.addUniform("vClearCoatTintInfos", 2);
+        uniformBuffer.addUniform("clearCoatTintMatrix", 16);
     }
 }

+ 7 - 5
src/Materials/Textures/baseTexture.ts

@@ -497,7 +497,7 @@ export class BaseTexture implements IAnimatable {
     }
 
     /** @hidden */
-    public _getFromCache(url: Nullable<string>, noMipmap: boolean, sampling?: number): Nullable<InternalTexture> {
+    public _getFromCache(url: Nullable<string>, noMipmap: boolean, sampling?: number, invertY?: boolean): Nullable<InternalTexture> {
         if (!this._scene) {
             return null;
         }
@@ -506,10 +506,12 @@ export class BaseTexture implements IAnimatable {
         for (var index = 0; index < texturesCache.length; index++) {
             var texturesCacheEntry = texturesCache[index];
 
-            if (texturesCacheEntry.url === url && texturesCacheEntry.generateMipMaps === !noMipmap) {
-                if (!sampling || sampling === texturesCacheEntry.samplingMode) {
-                    texturesCacheEntry.incrementReferences();
-                    return texturesCacheEntry;
+            if (invertY === undefined || invertY === texturesCacheEntry.invertY) {
+                if (texturesCacheEntry.url === url && texturesCacheEntry.generateMipMaps === !noMipmap) {
+                    if (!sampling || sampling === texturesCacheEntry.samplingMode) {
+                        texturesCacheEntry.incrementReferences();
+                        return texturesCacheEntry;
+                    }
                 }
             }
         }

+ 2 - 2
src/Materials/Textures/texture.ts

@@ -303,7 +303,7 @@ export class Texture extends BaseTexture {
             return;
         }
 
-        this._texture = this._getFromCache(this.url, noMipmap, samplingMode);
+        this._texture = this._getFromCache(this.url, noMipmap, samplingMode, invertY);
 
         if (!this._texture) {
             if (!scene.useDelayedTextureLoading) {
@@ -364,7 +364,7 @@ export class Texture extends BaseTexture {
         }
 
         this.delayLoadState = Constants.DELAYLOADSTATE_LOADED;
-        this._texture = this._getFromCache(this.url, this._noMipmap, this.samplingMode);
+        this._texture = this._getFromCache(this.url, this._noMipmap, this.samplingMode, this._invertY);
 
         if (!this._texture) {
             this._texture = scene.getEngine().createTexture(this.url, this._noMipmap, this._invertY, scene, this.samplingMode, this._delayedOnLoad, this._delayedOnError, this._buffer, null, this._format);

+ 81 - 44
src/Materials/Textures/videoTexture.ts

@@ -327,27 +327,59 @@ export class VideoTexture extends Texture {
     }
 
     /**
+     * Creates a video texture straight from a stream.
+     * @param scene Define the scene the texture should be created in
+     * @param stream Define the stream the texture should be created from
+     * @returns The created video texture as a promise
+     */
+    public static CreateFromStreamAsync(scene: Scene, stream: MediaStream): Promise<VideoTexture> {
+        var video = document.createElement("video");
+        video.setAttribute('autoplay', '');
+        video.setAttribute('muted', 'true');
+        video.setAttribute('playsinline', '');
+        video.muted = true;
+
+        if (video.mozSrcObject !== undefined) {
+            // hack for Firefox < 19
+            video.mozSrcObject = stream;
+        } else {
+            if (typeof video.srcObject == "object") {
+                video.srcObject = stream;
+            } else {
+                window.URL = window.URL || window.webkitURL || window.mozURL || window.msURL;
+                video.src = (window.URL && window.URL.createObjectURL(stream));
+            }
+        }
+
+        return new Promise<VideoTexture>((resolve) => {
+            let onPlaying = () => {
+                resolve(new VideoTexture("video", video, scene, true, true));
+                video.removeEventListener("playing", onPlaying);
+            };
+
+            video.addEventListener("playing", onPlaying);
+            video.play();
+        });
+    }
+
+    /**
      * Creates a video texture straight from your WebCam video feed.
      * @param scene Define the scene the texture should be created in
-     * @param onReady Define a callback to triggered once the texture will be ready
      * @param constraints Define the constraints to use to create the web cam feed from WebRTC
+     * @param audioConstaints Define the audio constraints to use to create the web cam feed from WebRTC
+     * @returns The created video texture as a promise
      */
-    public static CreateFromWebCam(
+    public static CreateFromWebCamAsync(
         scene: Scene,
-        onReady: (videoTexture: VideoTexture) => void,
         constraints: {
             minWidth: number;
             maxWidth: number;
             minHeight: number;
             maxHeight: number;
             deviceId: string;
-        }
-    ): void {
-        var video = document.createElement("video");
-        video.setAttribute('autoplay', '');
-        video.setAttribute('muted', '');
-        video.setAttribute('playsinline', '');
-
+        } & MediaTrackConstraints,
+        audioConstaints: boolean | MediaTrackConstraints = false
+    ): Promise<VideoTexture> {
         var constraintsDeviceId;
         if (constraints && constraints.deviceId) {
             constraintsDeviceId = {
@@ -355,30 +387,13 @@ export class VideoTexture extends Texture {
             };
         }
 
-        window.URL = window.URL || window.webkitURL || window.mozURL || window.msURL;
-
         if (navigator.mediaDevices) {
-            navigator.mediaDevices.getUserMedia({ video: constraints })
-                .then(function(stream) {
-                    if (video.mozSrcObject !== undefined) {
-                        // hack for Firefox < 19
-                        video.mozSrcObject = stream;
-                    } else {
-                        video.srcObject = stream;
-                    }
-
-                    let onPlaying = () => {
-                        if (onReady) {
-                            onReady(new VideoTexture("video", video, scene, true, true));
-                        }
-                        video.removeEventListener("playing", onPlaying);
-                    };
-
-                    video.addEventListener("playing", onPlaying);
-                    video.play();
+            return navigator.mediaDevices.getUserMedia({
+                    video: constraints,
+                    audio: audioConstaints
                 })
-                .catch(function(err) {
-                    Logger.Error(err.name);
+                .then((stream) => {
+                    return this.CreateFromStreamAsync(scene, stream);
                 });
         }
         else {
@@ -402,20 +417,10 @@ export class VideoTexture extends Texture {
                                 max: (constraints && constraints.maxHeight) || 480,
                             },
                         },
+                        audio: audioConstaints
                     },
                     (stream: any) => {
-                        if (video.mozSrcObject !== undefined) {
-                            // hack for Firefox < 19
-                            video.mozSrcObject = stream;
-                        } else {
-                            video.src = (window.URL && window.URL.createObjectURL(stream)) || stream;
-                        }
-
-                        video.play();
-
-                        if (onReady) {
-                            onReady(new VideoTexture("video", video, scene, true, true));
-                        }
+                        return this.CreateFromStreamAsync(scene, stream);
                     },
                     function(e: MediaStreamError) {
                         Logger.Error(e.name);
@@ -423,5 +428,37 @@ export class VideoTexture extends Texture {
                 );
             }
         }
+
+        return Promise.reject("No support for userMedia on this device");
+    }
+
+    /**
+     * Creates a video texture straight from your WebCam video feed.
+     * @param scene Define the scene the texture should be created in
+     * @param onReady Define a callback to triggered once the texture will be ready
+     * @param constraints Define the constraints to use to create the web cam feed from WebRTC
+     * @param audioConstaints Define the audio constraints to use to create the web cam feed from WebRTC
+     */
+    public static CreateFromWebCam(
+        scene: Scene,
+        onReady: (videoTexture: VideoTexture) => void,
+        constraints: {
+            minWidth: number;
+            maxWidth: number;
+            minHeight: number;
+            maxHeight: number;
+            deviceId: string;
+        } & MediaTrackConstraints,
+        audioConstaints: boolean | MediaTrackConstraints = false
+    ): void {
+        this.CreateFromWebCamAsync(scene, constraints, audioConstaints)
+            .then(function(videoTexture) {
+                if (onReady) {
+                    onReady(videoTexture);
+                }
+            })
+            .catch(function(err) {
+                Logger.Error(err.name);
+            });
     }
 }

+ 16 - 0
src/Materials/materialFlags.ts

@@ -213,4 +213,20 @@ export class MaterialFlags {
         this._ClearCoatBumpTextureEnabled = value;
         Engine.MarkAllMaterialsAsDirty(Constants.MATERIAL_TextureDirtyFlag);
     }
+
+    private static _ClearCoatTintTextureEnabled = true;
+    /**
+     * Are clear coat tint textures enabled in the application.
+     */
+    public static get ClearCoatTintTextureEnabled(): boolean {
+        return this._ClearCoatTintTextureEnabled;
+    }
+    public static set ClearCoatTintTextureEnabled(value: boolean) {
+        if (this._ClearCoatTintTextureEnabled === value) {
+            return;
+        }
+
+        this._ClearCoatTintTextureEnabled = value;
+        Engine.MarkAllMaterialsAsDirty(Constants.MATERIAL_TextureDirtyFlag);
+    }
 }

+ 5 - 8
src/Misc/screenshotTools.ts

@@ -10,9 +10,6 @@ import { Tools } from './tools';
 
 declare type Engine = import("../Engines/engine").Engine;
 
-// Screenshots
-var screenshotCanvas: HTMLCanvasElement;
-
 /**
  * Class containing a set of static utilities functions for screenshots
  */
@@ -66,14 +63,14 @@ export class ScreenshotTools {
             return;
         }
 
-        if (!screenshotCanvas) {
-            screenshotCanvas = document.createElement('canvas');
+        if (!Tools._ScreenshotCanvas) {
+            Tools._ScreenshotCanvas = document.createElement('canvas');
         }
 
-        screenshotCanvas.width = width;
-        screenshotCanvas.height = height;
+        Tools._ScreenshotCanvas.width = width;
+        Tools._ScreenshotCanvas.height = height;
 
-        var renderContext = screenshotCanvas.getContext("2d");
+        var renderContext = Tools._ScreenshotCanvas.getContext("2d");
 
         var ratio = engine.getRenderWidth() / engine.getRenderHeight();
         var newWidth = width;

+ 12 - 10
src/Misc/tools.ts

@@ -190,9 +190,6 @@ export interface IFileRequest {
     abort: () => void;
 }
 
-// Screenshots
-var screenshotCanvas: HTMLCanvasElement;
-
 /**
  * Class containing a set of static utilities functions
  */
@@ -1188,6 +1185,11 @@ export class Tools {
     }
 
     /**
+     * @ignore
+     */
+    public static _ScreenshotCanvas: HTMLCanvasElement;
+
+    /**
      * Dumps the current bound framebuffer
      * @param width defines the rendering width
      * @param height defines the rendering height
@@ -1218,12 +1220,12 @@ export class Tools {
         }
 
         // Create a 2D canvas to store the result
-        if (!screenshotCanvas) {
-            screenshotCanvas = document.createElement('canvas');
+        if (!Tools._ScreenshotCanvas) {
+            Tools._ScreenshotCanvas = document.createElement('canvas');
         }
-        screenshotCanvas.width = width;
-        screenshotCanvas.height = height;
-        var context = screenshotCanvas.getContext('2d');
+        Tools._ScreenshotCanvas.width = width;
+        Tools._ScreenshotCanvas.height = height;
+        var context = Tools._ScreenshotCanvas.getContext('2d');
 
         if (context) {
             // Copy the pixels to a 2D canvas
@@ -1273,11 +1275,11 @@ export class Tools {
      */
     static EncodeScreenshotCanvasData(successCallback?: (data: string) => void, mimeType: string = "image/png", fileName?: string): void {
         if (successCallback) {
-            var base64Image = screenshotCanvas.toDataURL(mimeType);
+            var base64Image = Tools._ScreenshotCanvas.toDataURL(mimeType);
             successCallback(base64Image);
         }
         else {
-            this.ToBlob(screenshotCanvas, function(blob) {
+            this.ToBlob(Tools._ScreenshotCanvas, function(blob) {
                 //Creating a link if the browser have the download attribute on the a tag, to automatically start download generated image.
                 if (("download" in document.createElement("a"))) {
                     if (!fileName) {

+ 8 - 0
src/Misc/videoRecorder.ts

@@ -56,6 +56,8 @@ export interface VideoRecorderOptions {
     fps: number;
     /** Defines the chunk size for the recording data */
     recordChunckSize: number;
+    /** The audio tracks to attach to the record */
+    audioTracks?: MediaStreamTrack[];
 }
 
 /**
@@ -123,6 +125,12 @@ export class VideoRecorder {
         };
 
         const stream = this._canvas.captureStream(this._options.fps);
+        if (this._options.audioTracks) {
+            for (let track of this._options.audioTracks) {
+                stream.addTrack(track);
+            }
+        }
+
         this._mediaRecorder = new MediaRecorder(stream, { mimeType: this._options.mimeType });
         this._mediaRecorder.ondataavailable = this._handleDataAvailable.bind(this);
         this._mediaRecorder.onerror = this._handleError.bind(this);

+ 13 - 0
src/PostProcesses/imageProcessingPostProcess.ts

@@ -177,6 +177,19 @@ export class ImageProcessingPostProcess extends PostProcess {
     }
 
     /**
+     * Gets the type of tone mapping effect.
+     */
+    public get toneMappingType(): number {
+        return this._imageProcessingConfiguration.toneMappingType;
+    }
+    /**
+     * Sets the type of tone mapping effect.
+     */
+    public set toneMappingType(value: number) {
+        this._imageProcessingConfiguration.toneMappingType = value;
+    }
+
+    /**
      * Gets contrast used in the effect.
      */
     public get contrast(): number {

+ 9 - 0
src/Shaders/ShadersInclude/lightFragment.fx

@@ -79,6 +79,15 @@
                 #endif
 
                 info.clearCoat = computeClearCoatLighting(preInfo, clearCoatNormalW, clearCoatAARoughnessFactors.x, clearCoatIntensity, light{X}.vLightDiffuse.rgb);
+                
+                #ifdef CLEARCOAT_TINT
+                    // Absorption
+                    absorption = computeClearCoatLightingAbsorption(clearCoatNdotVRefract, preInfo.L, clearCoatNormalW, clearCoatColor, clearCoatThickness, clearCoatIntensity);
+                    info.diffuse *= absorption;
+                    #ifdef SPECULARTERM
+                        info.specular *= absorption;
+                    #endif
+                #endif
 
                 // Apply energy conservation on diffuse and specular term.
                 info.diffuse *= info.clearCoat.w;

+ 11 - 0
src/Shaders/ShadersInclude/pbrFragmentDeclaration.fx

@@ -68,6 +68,7 @@ uniform mat4 view;
 // Clear Coat
 #ifdef CLEARCOAT
     uniform vec2 vClearCoatParams;
+    uniform vec4 vClearCoatRefractionParams;
 
     #ifdef CLEARCOAT_TEXTURE
         uniform vec2 vClearCoatInfos;
@@ -79,6 +80,16 @@ uniform mat4 view;
         uniform vec2 vClearCoatTangentSpaceParams;
         uniform mat4 clearCoatBumpMatrix;
     #endif
+
+    #ifdef CLEARCOAT_TINT
+        uniform vec4 vClearCoatTintParams;
+        uniform float clearCoatColorAtDistance;
+
+        #ifdef CLEARCOAT_TINT_TEXTURE
+            uniform vec2 vClearCoatTintInfos;
+            uniform mat4 clearCoatTintMatrix;
+        #endif
+    #endif
 #endif
 
 // Anisotropy

+ 53 - 21
src/Shaders/ShadersInclude/pbrFunctions.fx

@@ -1,11 +1,10 @@
 // Constants
 #define RECIPROCAL_PI2 0.15915494
 #define FRESNEL_MAXIMUM_ON_ROUGH 0.25
+
 // AlphaG epsilon to avoid numerical issues
 #define MINIMUMVARIANCE 0.0005
 
-// f0 = 4% based on the IOR of a air-polyurethane interface.
-#define CLEARCOATREFLECTANCE0 0.04
 #define CLEARCOATREFLECTANCE90 1.0
 
 float convertRoughnessToAverageSlope(float roughness)
@@ -50,17 +49,33 @@ vec2 getAARoughnessFactors(vec3 normalVector) {
 //     return (1.0 + s) / (1.0 - s);
 // }
 
-// // Clear coat Remapping
+// f0 Remapping due to layers
 // vec3 getR0RemappedForClearCoat(vec3 f0, vec3 clearCoatF0) {
 //     vec3 iorBase = getIORfromAirToSurfaceR0(f0);
 //     vec3 clearCoatIor = getIORfromAirToSurfaceR0(clearCoatF0);
 //     return getR0fromIOR(iorBase, clearCoatIor);
 // }
 
-vec3 getR0RemappedForPolyurethaneClearCoat(vec3 f0) {
-    vec3 s = sqrt(f0);
-    return (-1.0 + 5.0 * s) / (5.0 + s);
+#ifdef CLEARCOAT
+// Knowing ior clear coat is fix for the material
+// Solving iorbase = 1 + sqrt(fo) / (1 - sqrt(fo)) and f0base = square((iorbase - iorclearcoat) / (iorbase - iorclearcoat))
+// provide f0base = square(A + B * sqrt(fo)) / (B + A * sqrt(fo))
+// where A = 1 - iorclearcoat
+// and   B = 1 + iorclearcoat
+vec3 getR0RemappedForClearCoat(vec3 f0) {
+    #ifdef CLEARCOAT_DEFAULTIOR
+        #ifdef MOBILE
+            return clamp(f0 * (f0 * 0.526868 + 0.529324) - 0.0482256, 0., 1.);
+        #else
+            return clamp(f0 * (f0 * (0.941892 - 0.263008 * f0) + 0.346479) - 0.0285998, 0., 1.);
+        #endif
+    #else
+        vec3 s = sqrt(f0);
+        vec3 t = (vClearCoatRefractionParams.z + vClearCoatRefractionParams.w * s) / (vClearCoatRefractionParams.w + vClearCoatRefractionParams.z * s);
+        return t * t;
+    #endif
 }
+#endif
 
 // From Microfacet Models for Refraction through Rough Surfaces, Walter et al. 2007
 // Keep for references
@@ -81,18 +96,15 @@ vec3 getR0RemappedForPolyurethaneClearCoat(vec3 f0) {
 // torrance denominator :-)
 float smithVisibilityG1_TrowbridgeReitzGGXFast(float dot, float alphaG)
 {
-    float alphaSquared = alphaG * alphaG;
-    return 1.0 / (dot + sqrt(alphaSquared + (1.0 - alphaSquared) * dot * dot));
+    #ifdef MOBILE
+        // Appply simplification as all squared root terms are below 1 and squared
+        return 1.0 / (dot + alphaG + (1.0 - alphaG) * dot ));
+    #else
+        float alphaSquared = alphaG * alphaG;
+        return 1.0 / (dot + sqrt(alphaSquared + (1.0 - alphaSquared) * dot * dot));
+    #endif
 }
 
-// From smithVisibilityG1_TrowbridgeReitzGGXFast
-// Appply simplification as all squared root terms are below 1 and squared
-// Ready to be used
-// float smithVisibilityG1_TrowbridgeReitzGGXMobile(float dot, float alphaG)
-// {
-//     return 1.0 / (dot + alpha + (1.0 - alpha) * dot ));
-// }
-
 float smithVisibility_TrowbridgeReitzGGXFast(float NdotL, float NdotV, float alphaG)
 {
     float visibility = smithVisibilityG1_TrowbridgeReitzGGXFast(NdotL, alphaG) * smithVisibilityG1_TrowbridgeReitzGGXFast(NdotV, alphaG);
@@ -163,11 +175,22 @@ vec3 fresnelSchlickEnvironmentGGX(float VdotN, vec3 reflectance0, vec3 reflectan
     return reflectance0 + weight * (reflectance90 - reflectance0) * pow(clamp(1.0 - VdotN, 0., 1.), 5.0);
 }
 
+// From beer lambert law I1/I0 = e −α′lc
+// c is considered included in alpha
+// https://blog.selfshadow.com/publications/s2017-shading-course/drobot/s2017_pbs_multilayered.pdf page 47
+// where L on a thin constant size layer can be (d * ((NdotLRefract + NdotVRefract) / (NdotLRefract * NdotVRefract))
+vec3 cocaLambert(float NdotVRefract, float NdotLRefract, vec3 alpha, float thickness) {
+    return exp(alpha * -(thickness * ((NdotLRefract + NdotVRefract) / (NdotLRefract * NdotVRefract))));
+}
+// From beerLambert Solves what alpha should be for a given resutlt at a known distance.
+vec3 computeColorAtDistanceInMedia(vec3 color, float distance) {
+    return -log(color) / distance;
+}
+
 // Disney diffuse term
 // https://blog.selfshadow.com/publications/s2012-shading-course/burley/s2012_pbs_disney_brdf_notes_v3.pdf
 // Page 14
-float computeDiffuseTerm(float NdotL, float NdotV, float VdotH, float roughness)
-{
+float computeDiffuseTerm(float NdotL, float NdotV, float VdotH, float roughness) {
     // Diffuse fresnel falloff as per Disney principled BRDF, and in the spirit of
     // of general coupled diffuse/specular models e.g. Ashikhmin Shirley.
     float diffuseFresnelNV = pow(clamp(1.0 - NdotL, 0.000001, 1.), 5.0);
@@ -206,6 +229,7 @@ vec3 computeAnisotropicSpecularTerm(float NdotH, float NdotL, float NdotV, float
     return fresnel * specTerm;
 }
 
+#ifdef CLEARCOAT
 vec2 computeClearCoatTerm(float NdotH, float VdotH, float clearCoatRoughness, float geometricRoughnessFactor, float clearCoatIntensity) {
     clearCoatRoughness = max(clearCoatRoughness, geometricRoughnessFactor);
     float alphaG = convertRoughnessToAverageSlope(clearCoatRoughness);
@@ -214,12 +238,20 @@ vec2 computeClearCoatTerm(float NdotH, float VdotH, float clearCoatRoughness, fl
     float visibility = kelemenVisibility(VdotH);
     float clearCoatTerm = max(0., visibility * distribution);
 
-    float fresnel = fresnelSchlickGGX(VdotH, CLEARCOATREFLECTANCE0, CLEARCOATREFLECTANCE90);
+    float fresnel = fresnelSchlickGGX(VdotH, vClearCoatRefractionParams.x, CLEARCOATREFLECTANCE90);
     fresnel *= clearCoatIntensity;
     
     return vec2(fresnel * clearCoatTerm, 1.0 - fresnel);
 }
 
+vec3 computeClearCoatAbsorption(float NdotVRefract, float NdotLRefract, vec3 clearCoatColor, float clearCoatThickness, float clearCoatIntensity) {
+    vec3 clearCoatAbsorption = mix(vec3(1.0),
+        cocaLambert(NdotVRefract, NdotLRefract, clearCoatColor, clearCoatThickness),
+        clearCoatIntensity);
+    return clearCoatAbsorption;
+}
+#endif
+
 float adjustRoughnessFromLightProperties(float roughness, float lightRadius, float lightDistance)
 {
     #if defined(USEPHYSICALLIGHTFALLOFF) || defined(USEGLTFLIGHTFALLOFF)
@@ -247,8 +279,8 @@ float computeDefaultMicroSurface(float microSurface, vec3 reflectivityColor)
 // For typical incident reflectance range (between 4% to 100%) set the grazing reflectance to 100% for typical fresnel effect.
 // For very low reflectance range on highly diffuse objects (below 4%), incrementally reduce grazing reflecance to 0%.
 float fresnelGrazingReflectance(float reflectance0) {
-	float reflectance90 = clamp(reflectance0 * 25.0, 0.0, 1.0);
-	return reflectance90;
+    float reflectance90 = clamp(reflectance0 * 25.0, 0.0, 1.0);
+    return reflectance90;
 }
 
 // To enable 8 bit textures to be used we need to pack and unpack the LOD

+ 10 - 0
src/Shaders/ShadersInclude/pbrLightingFunctions.fx

@@ -43,6 +43,7 @@ vec3 computeAnisotropicSpecularLighting(preLightingInfo info, vec3 V, vec3 N, ve
     return specTerm * info.attenuation * info.NdotL * lightColor;
 }
 
+#ifdef CLEARCOAT
 vec4 computeClearCoatLighting(preLightingInfo info, vec3 Ncc, float geometricRoughnessFactor, float clearCoatIntensity, vec3 lightColor) {
     float NccdotL = clamp(dot(Ncc, info.L), 0.00000000001, 1.0);
     float NccdotH = clamp(dot(Ncc, info.H), 0.000000000001, 1.0);
@@ -55,6 +56,15 @@ vec4 computeClearCoatLighting(preLightingInfo info, vec3 Ncc, float geometricRou
     return result;
 }
 
+vec3 computeClearCoatLightingAbsorption(float NdotVRefract, vec3 L, vec3 Ncc, vec3 clearCoatColor, float clearCoatThickness, float clearCoatIntensity) {
+    vec3 LRefract = -refract(L, Ncc, vClearCoatRefractionParams.y);
+    float NdotLRefract = clamp(dot(Ncc, LRefract), 0.00000000001, 1.0);
+
+    vec3 absorption = computeClearCoatAbsorption(NdotVRefract, NdotLRefract, clearCoatColor, clearCoatThickness, clearCoatIntensity);
+    return absorption;
+}
+#endif
+
 vec3 computeProjectionTextureDiffuseLighting(sampler2D projectionLightSampler, mat4 textureProjectionMatrix){
 	vec4 strq = textureProjectionMatrix * vec4(vPositionW, 1.0);
 	strq /= strq.w;

+ 5 - 0
src/Shaders/ShadersInclude/pbrUboDeclaration.fx

@@ -39,11 +39,16 @@ uniform Material
     uniform float pointSize;
 
     uniform vec2 vClearCoatParams;
+    uniform vec4 vClearCoatRefractionParams;
     uniform vec2 vClearCoatInfos;
     uniform mat4 clearCoatMatrix;
     uniform vec2 vClearCoatBumpInfos;
     uniform vec2 vClearCoatTangentSpaceParams;
     uniform mat4 clearCoatBumpMatrix;
+    uniform vec4 vClearCoatTintParams;
+    uniform float clearCoatColorAtDistance;
+    uniform vec2 vClearCoatTintInfos;
+    uniform mat4 clearCoatTintMatrix;
 
     uniform float anisotropy;
 };

+ 5 - 0
src/Shaders/ShadersInclude/pbrVertexDeclaration.fx

@@ -70,4 +70,9 @@ uniform float pointSize;
         uniform vec2 vClearCoatBumpInfos;
         uniform mat4 clearCoatBumpMatrix;
     #endif
+
+    #ifdef CLEARCOAT_TINT_TEXTURE
+        uniform vec2 vClearCoatTintInfos;
+        uniform mat4 clearCoatTintMatrix;
+    #endif
 #endif

+ 49 - 3
src/Shaders/pbr.fragment.fx

@@ -143,6 +143,17 @@ varying vec4 vColor;
         #endif
         uniform sampler2D clearCoatBumpSampler;
     #endif
+
+    #ifdef CLEARCOAT_TINT_TEXTURE
+        #if CLEARCOAT_TINT_TEXTUREDIRECTUV == 1
+            #define vClearCoatTintUV vMainUV1
+        #elif CLEARCOAT_TINT_TEXTUREDIRECTUV == 2
+            #define vClearCoatTintUV vMainUV2
+        #else
+            varying vec2 vClearCoatTintUV;
+        #endif
+        uniform sampler2D clearCoatTintSampler;
+    #endif
 #endif
 
 // Refraction
@@ -690,12 +701,25 @@ void main(void) {
             clearCoatRoughness *= clearCoatMapData.y;
         #endif
 
+        #ifdef CLEARCOAT_TINT
+            vec3 clearCoatColor = vClearCoatTintParams.rgb;
+            float clearCoatThickness = vClearCoatTintParams.a;
+
+            #ifdef CLEARCOAT_TINT_TEXTURE
+                vec4 clearCoatTintMapData = texture2D(clearCoatTintSampler, vClearCoatTintUV + uvOffset);
+                clearCoatColor *= toLinearSpace(clearCoatTintMapData.rgb);
+                clearCoatThickness *= clearCoatTintMapData.a;
+            #endif
+
+            clearCoatColor = computeColorAtDistanceInMedia(clearCoatColor, clearCoatColorAtDistance);
+        #endif
+
         // remapping and linearization of clear coat roughness
         // Let s see how it ends up in gltf
         // clearCoatRoughness = mix(0.089, 0.6, clearCoatRoughness);
 
         // Remap F0 to account for the change of interface within the material.
-        vec3 specularEnvironmentR0Updated = getR0RemappedForPolyurethaneClearCoat(specularEnvironmentR0);
+        vec3 specularEnvironmentR0Updated = getR0RemappedForClearCoat(specularEnvironmentR0);
         specularEnvironmentR0 = mix(specularEnvironmentR0, specularEnvironmentR0Updated, clearCoatIntensity);
 
         #ifdef CLEARCOAT_BUMP
@@ -801,6 +825,14 @@ void main(void) {
                 environmentClearCoatRadiance.rgb = toLinearSpace(environmentClearCoatRadiance.rgb);
             #endif
 
+            #ifdef CLEARCOAT_TINT
+                vec3 clearCoatVRefract = -refract(vPositionW, clearCoatNormalW, vClearCoatRefractionParams.y);
+                float clearCoatNdotVRefract = clamp(dot(clearCoatNormalW, clearCoatVRefract), 0.00000000001, 1.0);
+
+                // Used later on in the light fragment and ibl.
+                vec3 absorption = vec3(0.);
+            #endif
+
             // _____________________________ Levels _____________________________________
             environmentClearCoatRadiance.rgb *= vReflectionInfos.x;
             environmentClearCoatRadiance.rgb *= vReflectionColor.rgb;
@@ -875,7 +907,7 @@ void main(void) {
             // We can find the scale and offset to apply to the specular value.
             vec4 environmentClearCoatBrdf = texture2D(environmentBrdfSampler, brdfClearCoatSamplerUV);
 
-            vec3 clearCoatEnvironmentReflectance = vec3(CLEARCOATREFLECTANCE0 * environmentClearCoatBrdf.x + environmentClearCoatBrdf.y);
+            vec3 clearCoatEnvironmentReflectance = vec3(vClearCoatRefractionParams.x * environmentClearCoatBrdf.x + environmentClearCoatBrdf.y);
 
             #ifdef RADIANCEOCCLUSION
                 float clearCoatSeo = environmentRadianceOcclusion(ambientMonochrome, clearCoatNdotVUnclamped);
@@ -897,11 +929,22 @@ void main(void) {
 
         clearCoatEnvironmentReflectance *= clearCoatIntensity;
 
+        #ifdef CLEARCOAT_TINT
+            // NdotL = NdotV in IBL
+            absorption = computeClearCoatAbsorption(clearCoatNdotVRefract, clearCoatNdotVRefract, clearCoatColor, clearCoatThickness, clearCoatIntensity);
+
+            #ifdef REFLECTION
+                environmentIrradiance *= absorption;
+            #endif
+            specularEnvironmentReflectance *= absorption;
+        #endif
+
         // clear coat energy conservation
-        float fresnelIBLClearCoat = fresnelSchlickGGX(clearCoatNdotV, CLEARCOATREFLECTANCE0, CLEARCOATREFLECTANCE90);
+        float fresnelIBLClearCoat = fresnelSchlickGGX(clearCoatNdotV, vClearCoatRefractionParams.x, CLEARCOATREFLECTANCE90);
         fresnelIBLClearCoat *= clearCoatIntensity;
 
         float conservationFactor = (1. - fresnelIBLClearCoat);
+
         #ifdef REFLECTION
             environmentIrradiance *= conservationFactor;
         #endif
@@ -1001,6 +1044,9 @@ void main(void) {
 
         #ifdef REFRACTION
             finalRefraction *= (conservationFactor * conservationFactor);
+            #ifdef CLEARCOAT_TINT
+                finalRefraction *= absorption;
+            #endif
         #endif
     #endif
 

+ 15 - 0
src/Shaders/pbr.vertex.fx

@@ -72,6 +72,10 @@ varying vec2 vBumpUV;
     #if defined(CLEARCOAT_BUMP) && CLEARCOAT_BUMPDIRECTUV == 0 
         varying vec2 vClearCoatBumpUV;
     #endif
+
+    #if defined(CLEARCOAT_TINT_TEXTURE) && CLEARCOAT_TINT_TEXTUREDIRECTUV == 0 
+        varying vec2 vClearCoatTintUV;
+    #endif
 #endif
 
 // Output
@@ -282,6 +286,17 @@ void main(void) {
             vClearCoatBumpUV = vec2(clearCoatBumpMatrix * vec4(uv2, 1.0, 0.0));
         }
     #endif
+
+    #if defined(CLEARCOAT_TINT_TEXTURE) && CLEARCOAT_TINT_TEXTUREDIRECTUV == 0 
+        if (vClearCoatTintInfos.x == 0.)
+        {
+            vClearCoatTintUV = vec2(clearCoatTintMatrix * vec4(uv, 1.0, 0.0));
+        }
+        else
+        {
+            vClearCoatTintUV = vec2(clearCoatTintMatrix * vec4(uv2, 1.0, 0.0));
+        }
+    #endif
 #endif
 
     // TBN

+ 13 - 0
src/node.ts

@@ -656,6 +656,19 @@ export class Node implements IBehaviorAware<Node> {
     }
 
     /**
+     * Gets the list of all animation ranges defined on this node
+     * @returns an array
+     */
+    public getAnimationRanges(): Nullable<AnimationRange>[] {
+        var animationRanges: Nullable<AnimationRange>[] = [];
+        var name: string;
+        for (name in this._ranges) {
+            animationRanges.push(this._ranges[name]);
+        }
+        return animationRanges;
+    }
+
+    /**
      * Will start the animation sequence
      * @param name defines the range frames for animation sequence
      * @param loop defines if the animation should loop (false by default)

+ 7 - 1
src/scene.ts

@@ -591,6 +591,12 @@ export class Scene extends AbstractScene implements IAnimatable {
      */
     public onMeshImportedObservable = new Observable<AbstractMesh>();
 
+    /**
+     * Gets or sets a user defined funtion to select LOD from a mesh and a camera.
+     * By default this function is undefined and Babylon.js will select LOD based on distance to camera
+     */
+    public customLODSelector: (mesh: AbstractMesh, camera: Camera) => Nullable<AbstractMesh>;
+
     // Animations
 
     /** @hidden */
@@ -3891,7 +3897,7 @@ export class Scene extends AbstractScene implements IAnimatable {
             }
 
             // Switch to current LOD
-            const meshLOD = mesh.getLOD(this.activeCamera);
+            const meshLOD = this.customLODSelector ? this.customLODSelector(mesh, this.activeCamera) : mesh.getLOD(this.activeCamera);
             if (meshLOD === undefined || meshLOD === null) {
                 continue;
             }