فهرست منبع

Firs replacement push for new inspector

David Catuhe 6 سال پیش
والد
کامیت
ea39ee3f4c
100فایلهای تغییر یافته به همراه10151 افزوده شده و 9704 حذف شده
  1. 4 0
      .gitignore
  2. 3 2
      Tools/Gulp/config.json
  3. 6 3
      Tools/Gulp/gulpfile.js
  4. 4340 4336
      dist/preview release/babylon.d.ts
  5. 1 1
      dist/preview release/babylon.js
  6. 7 3
      dist/preview release/babylon.max.js
  7. 7 3
      dist/preview release/babylon.no-module.max.js
  8. 1 1
      dist/preview release/babylon.worker.js
  9. 7 3
      dist/preview release/es6.js
  10. 2 2
      dist/preview release/gui/babylon.gui.d.ts
  11. 4 4
      dist/preview release/gui/babylon.gui.module.d.ts
  12. 40 1
      dist/preview release/inspector/babylon.inspector.bundle.js
  13. 1 1
      dist/preview release/inspector/babylon.inspector.bundle.js.map
  14. 389 1045
      dist/preview release/inspector/babylon.inspector.d.ts
  15. 798 2382
      dist/preview release/inspector/babylon.inspector.module.d.ts
  16. 1 1
      dist/preview release/inspector/readme.md
  17. 27 16
      inspector/package.json
  18. 0 156
      inspector/sass/_detailPanel.scss
  19. 0 18
      inspector/sass/_resizeBar.scss
  20. 0 19
      inspector/sass/_searchbar.scss
  21. 0 74
      inspector/sass/_slider.scss
  22. 0 92
      inspector/sass/_tabPanel.scss
  23. 0 60
      inspector/sass/_tabbar.scss
  24. 0 31
      inspector/sass/_toolbar.scss
  25. 0 14
      inspector/sass/_tooltip.scss
  26. 0 73
      inspector/sass/_tree.scss
  27. 0 16
      inspector/sass/_treeTool.scss
  28. 0 28
      inspector/sass/defines.scss
  29. 0 105
      inspector/sass/main.scss
  30. 0 51
      inspector/sass/tabs/_consoleTab.scss
  31. 0 92
      inspector/sass/tabs/_gltfTab.scss
  32. 0 53
      inspector/sass/tabs/_shaderTab.scss
  33. 0 52
      inspector/sass/tabs/_statsTab.scss
  34. 0 80
      inspector/sass/tabs/_toolsTab.scss
  35. BIN
      inspector/screens/tab_mesh.jpg
  36. BIN
      inspector/screens/tools.jpg
  37. 360 375
      inspector/src/Inspector.ts
  38. 0 43
      inspector/src/adapters/Adapter.ts
  39. 0 56
      inspector/src/adapters/CameraAdapter.ts
  40. 0 48
      inspector/src/adapters/GUIAdapter.ts
  41. 0 51
      inspector/src/adapters/LightAdapter.ts
  42. 0 37
      inspector/src/adapters/MaterialAdapter.ts
  43. 0 116
      inspector/src/adapters/MeshAdapter.ts
  44. 0 59
      inspector/src/adapters/PhysicsImpostorAdapter.ts
  45. 0 52
      inspector/src/adapters/SoundAdapter.ts
  46. 0 40
      inspector/src/adapters/TextureAdapter.ts
  47. 0 9
      inspector/src/adapters/index.ts
  48. 716 0
      inspector/src/components/actionTabs/actionTabs.scss
  49. 108 0
      inspector/src/components/actionTabs/actionTabsComponent.tsx
  50. 59 0
      inspector/src/components/actionTabs/lineContainerComponent.tsx
  51. 31 0
      inspector/src/components/actionTabs/lines/booleanLineComponent.tsx
  52. 21 0
      inspector/src/components/actionTabs/lines/buttonLineComponent.tsx
  53. 77 0
      inspector/src/components/actionTabs/lines/checkBoxLineComponent.tsx
  54. 61 0
      inspector/src/components/actionTabs/lines/color3LineComponent.tsx
  55. 30 0
      inspector/src/components/actionTabs/lines/fileButtonLineComponent.tsx
  56. 73 0
      inspector/src/components/actionTabs/lines/floatLineComponent.tsx
  57. 56 0
      inspector/src/components/actionTabs/lines/numericInputComponent.tsx
  58. 93 0
      inspector/src/components/actionTabs/lines/optionsLineComponent.tsx
  59. 50 0
      inspector/src/components/actionTabs/lines/radioLineComponent.tsx
  60. 64 0
      inspector/src/components/actionTabs/lines/sliderLineComponent.tsx
  61. 48 0
      inspector/src/components/actionTabs/lines/textLineComponent.tsx
  62. 177 0
      inspector/src/components/actionTabs/lines/textureLineComponent.tsx
  63. 126 0
      inspector/src/components/actionTabs/lines/textureLinkLineComponent.tsx
  64. 31 0
      inspector/src/components/actionTabs/lines/valueLineComponent.tsx
  65. 110 0
      inspector/src/components/actionTabs/lines/vector3LineComponent.tsx
  66. 25 0
      inspector/src/components/actionTabs/paneComponent.tsx
  67. 116 0
      inspector/src/components/actionTabs/tabs/debugTabComponent.tsx
  68. 114 0
      inspector/src/components/actionTabs/tabs/propertyGridTabComponent.tsx
  69. 70 0
      inspector/src/components/actionTabs/tabs/propertyGrids/cameras/arcRotateCameraPropertyGridComponent.tsx
  70. 63 0
      inspector/src/components/actionTabs/tabs/propertyGrids/cameras/commonCameraPropertyGridComponent.tsx
  71. 52 0
      inspector/src/components/actionTabs/tabs/propertyGrids/cameras/freeCameraPropertyGridComponent.tsx
  72. 102 0
      inspector/src/components/actionTabs/tabs/propertyGrids/cameras/propertyGridTabComponent.tsx
  73. 52 0
      inspector/src/components/actionTabs/tabs/propertyGrids/fogPropertyGridComponent.tsx
  74. 84 0
      inspector/src/components/actionTabs/tabs/propertyGrids/gridPropertyGridComponent.tsx
  75. 30 0
      inspector/src/components/actionTabs/tabs/propertyGrids/lights/commonLightPropertyGridComponent.tsx
  76. 29 0
      inspector/src/components/actionTabs/tabs/propertyGrids/lights/commonShadowLightPropertyGridComponent.tsx
  77. 36 0
      inspector/src/components/actionTabs/tabs/propertyGrids/lights/directionalLightPropertyGridComponent.tsx
  78. 33 0
      inspector/src/components/actionTabs/tabs/propertyGrids/lights/hemisphericLightPropertyGridComponent.tsx
  79. 35 0
      inspector/src/components/actionTabs/tabs/propertyGrids/lights/pointLightPropertyGridComponent.tsx
  80. 39 0
      inspector/src/components/actionTabs/tabs/propertyGrids/lights/spotLightPropertyGridComponent.tsx
  81. 60 0
      inspector/src/components/actionTabs/tabs/propertyGrids/materials/backgroundMaterialPropertyGridComponent.tsx
  82. 45 0
      inspector/src/components/actionTabs/tabs/propertyGrids/materials/commonMaterialPropertyGridComponent.tsx
  83. 25 0
      inspector/src/components/actionTabs/tabs/propertyGrids/materials/materialPropertyGridComponent.tsx
  84. 89 0
      inspector/src/components/actionTabs/tabs/propertyGrids/materials/pbrMaterialPropertyGridComponent.tsx
  85. 71 0
      inspector/src/components/actionTabs/tabs/propertyGrids/materials/standardMaterialPropertyGridComponent.tsx
  86. 136 0
      inspector/src/components/actionTabs/tabs/propertyGrids/meshes/meshPropertyGridComponent.tsx
  87. 38 0
      inspector/src/components/actionTabs/tabs/propertyGrids/meshes/transformNodePropertyGridComponent.tsx
  88. 93 0
      inspector/src/components/actionTabs/tabs/propertyGrids/scenePropertyGridComponent.tsx
  89. 51 0
      inspector/src/components/actionTabs/tabs/propertyGrids/texturePropertyGridComponent.tsx
  90. 125 0
      inspector/src/components/actionTabs/tabs/statisticsTabComponent.tsx
  91. 102 0
      inspector/src/components/actionTabs/tabs/toolsTabComponent.tsx
  92. 47 0
      inspector/src/components/actionTabs/tabsComponent.tsx
  93. 117 0
      inspector/src/components/embedHost/embedHost.scss
  94. 85 0
      inspector/src/components/embedHost/embedHostComponent.tsx
  95. 101 0
      inspector/src/components/headerComponent.tsx
  96. 6 0
      inspector/src/components/propertyChangedEvent.ts
  97. 74 0
      inspector/src/components/sceneExplorer/entities/cameraTreeItemComponent.tsx
  98. 48 0
      inspector/src/components/sceneExplorer/entities/lightTreeItemComponent.tsx
  99. 29 0
      inspector/src/components/sceneExplorer/entities/materialTreeItemComponent.tsx
  100. 0 0
      inspector/src/components/sceneExplorer/entities/meshTreeItemComponent.tsx

+ 4 - 0
.gitignore

@@ -189,3 +189,7 @@ Viewer/tests/Lib/**/*.js
 Viewer/tests/commons/**/*.js
 .sass-cache/
 gui/dist/
+/Viewer/tests/tsc
+/Viewer/tests/tsc.cmd
+/Viewer/tests/tsserver
+/Viewer/tests/tsserver.cmd

+ 3 - 2
Tools/Gulp/config.json

@@ -1963,7 +1963,7 @@
                 "main": "../../dist/preview release/gui/build/index.d.ts",
                 "out": "../babylon.gui.module.d.ts",
                 "baseDir": "../../dist/preview release/gui/build/",
-                "headerText": "BabylonJS GUI"
+                "headerText": "Babylon.js GUI"
             },
             "processDeclaration": {
                 "filename": "babylon.gui.module.d.ts",
@@ -1987,6 +1987,7 @@
                 "webpack": "../../inspector/webpack.config.js",
                 "bundle": "true",
                 "extendsRoot": true,
+                "babylonIncluded": false,
                 "useOutputForDebugging": true
             }
         ],
@@ -1998,7 +1999,7 @@
                 "main": "../../dist/preview release/inspector/build/index.d.ts",
                 "out": "../babylon.inspector.module.d.ts",
                 "baseDir": "../../dist/preview release/inspector/build/",
-                "headerText": "BabylonJS Inspector"
+                "headerText": "Babylon.js Inspector"
             },
             "processDeclaration": {
                 "filename": "babylon.inspector.module.d.ts",

+ 6 - 3
Tools/Gulp/gulpfile.js

@@ -274,7 +274,7 @@ gulp.task("build", gulp.series("shaders", function build() {
 * TsLint all typescript files from the src directory.
 */
 gulp.task("typescript-tsLint", function() {
-    const dtsFilter = filter(['**', '!**/*.d.ts'], {restore: false});
+    const dtsFilter = filter(['**', '!**/*.d.ts'], { restore: false });
     return gulp.src(config.typescript)
         .pipe(dtsFilter)
         .pipe(gulpTslint({
@@ -348,7 +348,7 @@ gulp.task("tsLint", gulp.series("typescript-tsLint", "typescript-libraries-tsLin
 * Compiles all typescript files and creating a js and a declaration file.
 */
 gulp.task("typescript-compile", function() {
-    const dtsFilter = filter(['**', '!**/*.d.ts'], {restore: false});
+    const dtsFilter = filter(['**', '!**/*.d.ts'], { restore: false });
     var tsResult = gulp.src(config.typescript)
         .pipe(dtsFilter)
         .pipe(sourcemaps.init())
@@ -676,6 +676,9 @@ var buildExternalLibrary = function(library, settings, watch) {
 
                 let wpBuild = webpackStream(require(library.webpack), webpack);
 
+
+                console.log(outputDirectory);
+
                 let buildEvent = wpBuild
                     .pipe(gulp.dest(outputDirectory))
                     //back-compat
@@ -883,7 +886,7 @@ gulp.task("netlify-cleanup", function() {
 
 // this is needed for the modules for the declaration files.
 gulp.task("modules-compile", function() {
-    const dtsFilter = filter(['**', '!**/*.d.ts'], {restore: false});
+    const dtsFilter = filter(['**', '!**/*.d.ts'], { restore: false });
     var tsResult = gulp.src(config.typescript)
         .pipe(dtsFilter)
         .pipe(sourcemaps.init())

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


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


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


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


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


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


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

@@ -1,6 +1,6 @@
-/*BabylonJS GUI*/
+/*Babylon.js GUI*/
 // Dependencies for this module:
-//   ../../../../Tools/Gulp/babylonjs
+//   ../../../../Tools/gulp/babylonjs
 declare module BABYLON.GUI {
 }
 declare module BABYLON.GUI {

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

@@ -1,6 +1,6 @@
-/*BabylonJS GUI*/
+/*Babylon.js GUI*/
 // Dependencies for this module:
-//   ../../../../Tools/Gulp/babylonjs
+//   ../../../../Tools/gulp/babylonjs
 
 declare module 'babylonjs-gui' {
     export * from "babylonjs-gui/2D";
@@ -2983,9 +2983,9 @@ declare module 'babylonjs-gui/3D/materials/fluentMaterial' {
 }
 
 
-/*BabylonJS GUI*/
+/*Babylon.js GUI*/
 // Dependencies for this module:
-//   ../../../../Tools/Gulp/babylonjs
+//   ../../../../Tools/gulp/babylonjs
 declare module BABYLON.GUI {
 }
 declare module BABYLON.GUI {

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


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


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


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


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

@@ -1,7 +1,7 @@
 Babylon.js inspector module
 =====================
 
-For usage documentation please visit http://doc.babylonjs.com/how_to/debug_layer and search "inspector".
+For usage documentation please visit http://doc.babylonjs.com/how_to/debug_layer.
 
 # Installation instructions
 

+ 27 - 16
inspector/package.json

@@ -1,6 +1,6 @@
 {
     "name": "babylonjs-inspector",
-    "version": "1.0.0",
+    "version": "2.0.0",
     "description": "",
     "scripts": {
         "start:server": "webpack-dev-server",
@@ -24,19 +24,30 @@
     },
     "homepage": "https://github.com/BabylonJS/Babylon.js#readme",
     "devDependencies": {
-        "@types/node": "^10.5.3",
-        "clean-webpack-plugin": "^0.1.19",
-        "css-loader": "^1.0.0",
-        "dts-bundle-webpack": "^1.0.0",
-        "mini-css-extract-plugin": "^0.4.1",
-        "node-sass": "^4.9.2",
-        "sass-loader": "^7.0.3",
-        "style-loader": "^0.21.0",
-        "ts-loader": "^4.0.0",
-        "typescript": "~3.0.1",
-        "webpack": "^4.16.2",
-        "webpack-cli": "^3.1.0",
-        "webpack-dev-server": "^3.1.5"
-    },
-    "dependencies": {}
+        "@fortawesome/fontawesome-svg-core": "~1.2.8",
+        "@fortawesome/free-regular-svg-icons": "~5.4.1",
+        "@fortawesome/free-solid-svg-icons": "~5.4.1",
+        "@fortawesome/react-fontawesome": "~0.1.3",
+        "@types/react": "~16.4.18",
+        "@types/react-dom": "~16.0.9",
+        "css-loader": "~1.0.0",
+        "dts-bundle": "~0.7.3",
+        "file-loader": "~2.0.0",
+        "gulp": "~4.0.0",
+        "gulp-webserver": "~0.9.1",
+        "node-sass": "~4.9.4",
+        "re-resizable": "~4.9.1",
+        "react": "~16.5.2",
+        "react-dom": "~16.5.2",
+        "react-splitter-layout": "~3.0.1",
+        "sass-loader": "~7.1.0",
+        "style-loader": "~0.23.1",
+        "ts-loader": "~5.2.2",
+        "typescript": "~3.1.3",
+        "webpack": "~4.21.0",
+        "webpack-cli": "~3.1.2",
+        "webpack-stream": "5.0.0",
+        "mini-css-extract-plugin": "~0.4.4",
+        "clean-webpack-plugin": "~0.1.19"
+    }
 }

+ 0 - 156
inspector/sass/_detailPanel.scss

@@ -1,156 +0,0 @@
-// The main class of the detail panel
-.insp-details {
-  background-color: $background;
-  overflow-y: auto;
-  overflow-x: auto;
-  color:$color;
-  font-family: $font;
-
-  .details {
-      padding-left: 5px;
-  }
-
-  // Common defintion between header row and detail row
-  .base-row {      
-    display:flex;
-    width:100%;
-
-    .base-property {
-        padding: 2px 0 2px 0;
-        text-overflow: ellipsis;
-        white-space: nowrap;
-        overflow: hidden;
-    }
-    // Property name
-    .prop-name {
-        @extend .property-name;
-        @extend .base-property;
-        width:35%;
-    }
-    // property value
-    .prop-value {
-        @extend .base-property;
-        width:59%;
-        padding-left:5px;
-        &.clickable {
-            cursor:pointer;
-            &:hover {
-                background-color:$background-lighter2;
-            }
-            &:after {
-                font-family: $font-family-icons;
-                content: "\00a0 \00a0 \00a0 \f105"; // 4 space before + angle-right
-                font-weight: 900; // To display solid font family
-            }
-        }   
-    }
-    
-    // input {
-    //     border:0;
-    //     font-family: $font;
-    //     background-color: $background-lighter;
-    //     color:$color;
-    //     // Remove blue border on focus
-    //     &:focus {
-    //         outline:none;
-    //     }
-    // }
-  }
-  
-  // A line of details composed of name, type, value, id, flagId
-  .row {
-    @extend .base-row;
-    &:nth-child(even) {
-        background-color:$background-lighter;
-    }
-    &.unfolded {          
-        .prop-value.clickable:after {
-            font-family: $font-family-icons;
-            content: "\00a0 \00a0 \00a0 \f107"; // 4 space before + angle-down
-            font-weight: 900; // To display solid font family
-        }
-    }
-  }
-
-  
-  // Name, type, value, Id, FlagId
-  .header-row {
-    @extend .base-row;
-    background-color: $background-lighter; 
-    color           : $color;
-    width           : 100%;
-    max-width       : 100%;
-    
-    // Special definition for text color: the color is the default one
-    // All header columns
-    & > * {
-        color:$color !important;
-        padding:5px 0 5px 5px !important;
-        cursor: pointer;
-        &:hover {
-            background-color:$background-lighter2;
-        }
-    }
-
-    .header-col {
-        display:flex;
-        justify-content: space-between;
-        align-items: center;
-
-        .sort-direction {
-            margin-right:5px;
-        }
-    }
-  }
-  
-  // A div used to view a property in the property line (color, texture...)
-  .element-viewer {
-      position:relative;
-      width:10px;
-      height:10px;
-      display:inline-block;
-      margin-left:5px;
-  }
-
-  // The div displaying a color
-  .color-element {
-      @extend .element-viewer;
-      width: 20px;
-      height: 15px;
-  }
-
-  // The div displaying a texture element
-  .texture-element {
-      @extend .element-viewer;
-      color:$color-top;
-      margin-left:10px; 
-
-      .texture-viewer {
-          
-          color:$color;
-          position:absolute;
-          z-index:10;
-          bottom:0;
-          right:0;
-          display:block;
-          width:150px;
-          height:150px;
-          border: 1px solid $background-lighter3;
-          background-color: $background;
-          transform: translateX(100%) translateY(100%);
-          
-          display: none;
-          flex-direction: column;
-          justify-content: flex-start;
-          align-items: center;
-
-          .texture-viewer-img {
-              margin:10px 0 10px 0;
-              max-width:110px;
-              max-height:110px;
-          }
-      }
-  }
-   
-
-}

+ 0 - 18
inspector/sass/_resizeBar.scss

@@ -1,18 +0,0 @@
-.c2di-resize-bar-v {
-  height:100%;
-  width:$resizebar-width;
-  background-color: $background;
-  cursor: col-resize;
-  flex-shrink: 0;
-  border-left:1px solid $background-lighter;
-  border-right:1px solid $background-lighter;
-}
-.c2di-resize-bar-h {
-  width:100%;
-  height:$resizebar-width;
-  background-color: $background;
-  cursor: row-resize;
-  flex-shrink: 0;
-  border-top: 1px solid $background-lighter;
-  border-bottom: 1px solid $background-lighter;
-}

+ 0 - 19
inspector/sass/_searchbar.scss

@@ -1,19 +0,0 @@
-.searchbar {
-    
-    border       : 1px solid $background-lighter;
-    margin-bottom: 5px;
-    display      : flex;
-    align-items  : center;
-    color:darken($color, 10%);
-    
-    input {
-        background-color: $background;
-        border          : none;
-        width           : 100%;
-        outline         : none;
-        font-family     : $font;
-        color           : darken($color, 10%);
-        padding         : 3px 0 3px 10px;
-        margin          : 6px 0 6px 0;
-    }
-}

+ 0 - 74
inspector/sass/_slider.scss

@@ -1,74 +0,0 @@
-$thumb-height: 15px;
-
-input[type="range"] { 
-  margin: auto;
-  -webkit-appearance: none;
-  position: relative;
-  overflow: hidden;
-  height: $thumb-height;
-  width: 50%;
-  cursor: pointer;
-  border-radius: 0; /* iOS */
-}
-
-::-webkit-slider-runnable-track {
-  background: #ddd;
-}
-
-/*
-* 1. Set to 0 height and width, and remove border for a slider without a thumb
-*/
-::-webkit-slider-thumb {
-  -webkit-appearance: none;
-  width: 20px; /* 1 */
-  height: $thumb-height; /* 1 */
-  background: #fff;
-  box-shadow: -100vw 0 0 100vw dodgerblue;
-  border: 0px solid #999; /* 1 */
-}
-
-::-moz-range-track {
-  height: $thumb-height;
-  background: #ddd;
-}
-
-::-moz-range-thumb {
-  background: #fff;
-  height: $thumb-height;
-  width: 20px;
-  border: 0px solid #999;
-  border-radius: 0 !important;
-  box-shadow: -100vw 0 0 100vw dodgerblue;
-  box-sizing: border-box;
-}
-
-::-ms-fill-lower { 
-  background: dodgerblue;
-}
-
-::-ms-thumb { 
-  background: #fff;
-  border: 0px solid #999;
-  height: $thumb-height;
-  width: 20px;
-  box-sizing: border-box;
-}
-
-::-ms-ticks-after { 
-  display: none; 
-}
-
-::-ms-ticks-before { 
-  display: none; 
-}
-
-::-ms-track { 
-  background: #ddd;
-  color: transparent;
-  height: $thumb-height;
-  border: none;
-}
-
-::-ms-tooltip { 
-  display: none;
-}

+ 0 - 92
inspector/sass/_tabPanel.scss

@@ -1,92 +0,0 @@
-.tab-panel {
-    height:100%;
-
-    &.searchable {
-        height:calc(100% - #{$searchbar-height} - 10px);   
-    }  
-
-    .texture-image {
-        max-height:400px;
-    }
-    
-    .scene-actions {
-        overflow-y: auto;
-        padding-left: 5px;
-        
-        // Action title : Textures/Options/Viewer
-        .actions-title {
-            font-size     : 1.1em;
-            padding-bottom: 10px;
-            border-bottom : 1px solid $color-bot;
-            margin        : 10px 0 10px 0;
-        }
-
-        .defaut-action {            
-            height     : 20px;
-            line-height: 20px;
-            width      : 100%;
-            cursor     : pointer;   
-            
-            &:hover {
-                background-color:$background-lighter;
-            }
-            &:active {                
-                background-color: $background-lighter2;
-            }
-        }
-
-        // Radio button
-        .action-radio {
-            @extend .defaut-action;
-            &:before {
-                width      : 1em;
-                height     : 1em;
-                line-height: 1em;
-                display    : inline-block;
-                font-family: $font-family-icons;
-                content    : "\f111";
-                margin-right:10px;
-            }
-            // radio button active
-            &.active {
-                &:before {
-                    width      : 1em;
-                    height     : 1em;
-                    line-height: 1em;
-                    display    : inline-block;
-                    font-family: $font-family-icons;
-                    content    : "\f192";
-                    color      : $color-bot;
-                    margin-right:10px;
-                }
-            }
-        }
-
-        // Check button
-        .action {             
-            @extend .defaut-action;
-            &:before {
-                width      : 1em;
-                height     : 1em;
-                line-height: 1em;
-                display    : inline-block;
-                font-family: $font-family-icons;
-                content    : "\f0c8";
-                margin-right:10px;
-            }
-                
-            &.active {
-                &:before {
-                    width      : 1em;
-                    height     : 1em;
-                    line-height: 1em;
-                    display    : inline-block;
-                    font-family: $font-family-icons;
-                    content    : "\f14a";
-                    color      : $color-bot;
-                    margin-right:10px;
-                }
-            }
-        }
-    }
-}

+ 0 - 60
inspector/sass/_tabbar.scss

@@ -1,60 +0,0 @@
-.tabbar {
-    height:$tabbar-height;
-    
-    display:flex;
-    align-items: center;
-    border-bottom: 1px solid $background-lighter2;
-    width:100%;
-    overflow-x:auto;
-    overflow-y:hidden;
-    box-sizing: border-box;
-    
-    .tab {
-        height:calc(#{$tabbar-height} - 2px);
-        width:auto;
-        padding: 0 10px 0 10px;
-        color:$color;
-        line-height: $tabbar-height;
-        text-align: center;
-        cursor: pointer;
-        margin: 0 5px 0 5px;
-        box-sizing: border-box;
-       
-        // Hover on it
-        &:hover {
-            border-bottom: 1px solid $color-top;
-            background-color: $background-lighter;
-        }
-        // Clic on it
-        &:active {
-            background-color: $background-lighter2;
-        }
-        // tab selected
-        &.active {
-            border-bottom: 1px solid $color-top;
-        }
-    }
-
-    .more-tabs {
-        width           : $tabbar-height;
-        height          : $tabbar-height;
-        display         : flex;
-        justify-content : center;
-        align-items     : center;
-        cursor          : pointer;
-        position        : relative;
-        border-right    : 1px solid $background-lighter2;
-        &:hover {
-            background-color: $background-lighter2;
-        }
-        &:active {
-            color:$color-top;
-            background-color: $background-lighter3;
-        }
-        // This tool is activated
-        &.active {
-            color:$color-top;
-        }
-
-    }
-}

+ 0 - 31
inspector/sass/_toolbar.scss

@@ -1,31 +0,0 @@
-/**
- * The toolbar contains : 
- * - a refresh tool - refresh the whole panel
- * - a popup tool - Open the inspector in a new panel
- * ...
- */   
-.toolbar {   
-    display:flex;
-     
-    .tool {
-        width          : $tabbar-height;
-        height         : $tabbar-height;
-        display        : flex;
-        justify-content: center;
-        align-items    : center;
-        cursor         : pointer;
-        position       : relative;
-        border-right   : 1px solid $background-lighter2;
-        &:hover {
-            background-color: $background-lighter2;
-        }
-        &:active {
-            color:$color-top;
-            background-color: $background-lighter3;
-        }
-        // This tool is activated
-        &.active {
-            color:$color-top;
-        }
-    }
-}

+ 0 - 14
inspector/sass/_tooltip.scss

@@ -1,14 +0,0 @@
-// Tooltip used when hovering on divisions
-
-.tooltip {
-    position        : absolute;
-    top             : $tabbar-height;
-    right           : 0;
-    color           : $color-top;
-    display         : none;
-    z-index         : 4;
-    font-family     : $font;  
-    padding         : 2px;
-    background-color: $background;
-    border          : 1px solid $background-lighter3;
-}

+ 0 - 73
inspector/sass/_tree.scss

@@ -1,73 +0,0 @@
-
-// Color for a property type
-.property-type {
-  color:$color-bot;
-}
-
-.property-name {
-  color:$color-top;
-}
-
-.insp-tree {
-  overflow-y: auto;
-  overflow-x: hidden;
-  height    : calc(50% - #{$tabbar-height} - #{$searchbar-height});
-  
-    
-    .line {
-        padding:3px;
-        cursor:pointer;
-        // Hover
-        &:hover {
-            background-color:$background-lighter;
-        }
-        // Line active (selected)
-        &.active {
-            background-color:$background-lighter3;
-            .line-content {
-                background-color:$background;
-            }
-        }
-        // > before title names
-        &.unfolded:before{            
-            width      : 1em;
-            height     : 1em;
-            line-height: 1em;
-            display    : inline-block;
-            font-family: $font-family-icons;
-            content    : "\f107";
-            font-weight: 900;          
-        }
-        &.folded:before{            
-            width      : 1em;
-            height     : 1em;
-            line-height: 1em;
-            display    : inline-block;
-            font-family: $font-family-icons;
-            content    : "\f105";
-            font-weight: 900;
-        }
-        
-        &.unfolded.transformNode > span:first-of-type{
-            color:$color-top;
-        }
-        &.folded.transformNode > span:first-of-type{ 
-            color:$color-top;
-        }
-
-        // Sub lines
-        .line-content {
-            padding-left:15px;
-            &:hover { 
-                background-color:$background;
-            }
-            .line:hover:first-child {            
-                background-color:$background-lighter2;
-            }
-            
-        }
-    }
-    .line_invisible {
-        display: none;
-    }
-}

+ 0 - 16
inspector/sass/_treeTool.scss

@@ -1,16 +0,0 @@
-/**
- * A tool contained in the tree panel (available for each item of the tree)
- */
-.treeTool {
-    margin : 3px 8px 3px 3px;
-    cursor:pointer;
-    position:relative;
-   
-    &:hover {        
-        color:$color-bot;
-    }
-    
-    &.active {
-        color:$color-bot;
-    }
-}

+ 0 - 28
inspector/sass/defines.scss

@@ -1,28 +0,0 @@
-// @import url(https://maxcdn.bootstrapcdn.com/font-awesome/4.6.3/css/font-awesome.min.css);
-@import url(https://use.fontawesome.com/releases/v5.0.13/css/all.css);
-@import url(https://fonts.googleapis.com/css?family=Inconsolata);
-
-$font               : 'Inconsolata', sans-serif;
-
-// ------------- //
-// If you update these colors, you should also 
-// replace them in the class Inspector as well, 
-// as it's used for custom themes
-// ------------- //
-
-$color              : #ccc;
-$background         : #242424;
-$background-active  : #2c2c2c;
-$color-top          : #f29766;
-$color-bot          : #5db0d7;
-$color-error        : #fa371d;
-
-$background-lighter : lighten($color: $background, $amount         : 3%);
-$background-lighter2: lighten($color: $background-lighter, $amount : 5%);
-$background-lighter3: lighten($color: $background-lighter2, $amount: 5%);
-
-$resizebar-width    : 10px;
-$tabbar-height      : 50px;
-$searchbar-height   : 30px;
-
-$font-family-icons  : 'Font Awesome 5 Free', sans-serif;

+ 0 - 105
inspector/sass/main.scss

@@ -1,105 +0,0 @@
-
-
-.insp-wrapper {
-  // Import variables, color and font files
-  @import "defines";
-
-  // Style for resize bar
-  .gutter {
-    background-color: $background-lighter;
-    &.gutter-vertical:not(.blocked) {
-      cursor        : ns-resize;
-    }
-    &.gutter-horizontal:not(.blocked) {
-      cursor        : ew-resize;
-    }
-  }
-
-  user-select: none; 
-  display:flex;
-  font-size:0.9em;
-  font-family     : $font;
-  background-color: $background;
-
-  // The panel containing the two subpanel : tree and details
-  .insp-right-panel {
-    width: 750px;
-    overflow-y: auto;
-
-    &.popupmode {
-      width:100% !important;
-    }
-    
-    display:flex; 
-    flex-direction: column;
-    flex-shrink: 0;
-
-    // The tree panel
-    .top-panel {
-      width:100%;
-      height:100%;
-      position:relative;
-      background-color: $background;
-      color           : $color;
-      font-size       : 1em;
-      
-      // The div that will contain the tab div
-      .tab-panel-content {
-        width:100%;
-        height:calc(100% - #{$tabbar-height});   
-      }   
-
-      // The div displaying all invisible tabs (when the tabbar width is small)
-      .more-tabs-panel {
-          position:absolute;
-          z-index:10;
-          top:$tabbar-height;
-          right:0;
-          width:100px;
-          display:none;
-          flex-direction: column;
-          align-items: center;
-          justify-content: center;
-          border: 1px solid $background-lighter3;
-          background-color: $background;
-
-          .invisible-tab {
-            height     : 25px;
-            width      : 100%;
-            line-height: 25px;
-            text-align : center;
-            background-color: $background-lighter;
-            cursor:pointer;
-
-            // Hover on it
-            &:hover {
-              background-color: $background-lighter2;
-            }
-            // Clic on it
-            &:active {
-              background-color: $background-lighter3;
-            }
-          }
-
-      }
-    }
-  }
-
-  @import 'tooltip';
-
-  // All tools used on object in the tree
-  @import 'treeTool';
-  @import "tabPanel";
-  @import "tabs/shaderTab";
-  @import "tabs/consoleTab";
-  @import "tabs/statsTab";
-  @import "tabs/gltfTab";
-  @import "tabs/toolsTab";
-
-  @import "tree";
-  @import "detailPanel";
-  @import "tabbar";
-  @import "toolbar";
-  @import "searchbar";
-  @import "slider";
-}

+ 0 - 51
inspector/sass/tabs/_consoleTab.scss

@@ -1,51 +0,0 @@
-.tab-panel {
-
-    .console-panel {
-        min-height              : 100px;
-        user-select             : text;
-        box-sizing              : border-box;
-        padding                 : 0 15px;    
-        
-        .console-panel-title {
-            height              : 25px;
-            border-bottom       : 1px solid $background-lighter2;
-            text-transform      : uppercase;
-            line-height         : 25px;
-            margin-bottom       : 10px; 
-        }
-        
-        .console-panel-content {            
-            overflow-y              : auto;
-            overflow-x              : hidden;    
-            height                  : calc(100% - 30px);
-        }  
-
-        .defaut-line {
-            word-wrap: break-word;
-            padding: 3px 0 3px 5px;
-        }
-
-        .caller {
-            padding: 3px 0 3px 0;
-            color:darken($color-bot, 10%);
-        }
-
-        .log {  
-            @extend .defaut-line;
-            color:white;
-        }   
-        
-        .warn {
-            @extend .defaut-line;
-            color:orange;
-        }
-        .error {
-            @extend .defaut-line;
-            color:orangered; 
-        }
-        .object {
-            @extend .defaut-line;
-            color:$color-bot;
-        }
-    }
-}

+ 0 - 92
inspector/sass/tabs/_gltfTab.scss

@@ -1,92 +0,0 @@
-.tab-panel {
-    .gltf-actions {
-        overflow-y: auto;
-        padding-left: 5px;
-
-        .gltf-title {
-            font-size     : 1.1em;
-            padding-bottom: 10px;
-            border-bottom : 1px solid $color-bot;
-            margin        : 10px 0 10px 0;
-        }
-
-        .gltf-action {
-            height     : 20px;
-            line-height: 20px;
-            width      : 100%;
-            cursor     : pointer;
-            white-space: nowrap;
-
-            &:hover {
-                background-color: $background-lighter;
-            }
-        }
-
-        .gltf-icon {
-            width      : 1em;
-            height     : 1em;
-            line-height: 1em;
-            display    : inline-block;
-            font-family: $font-family-icons;
-            margin-right:10px;
-        }
-
-        .gltf-checkbox {
-            @extend .gltf-action;
-            &:before {
-                width      : 1em;
-                height     : 1em;
-                line-height: 1em;
-                display    : inline-block;
-                font-family: $font-family-icons;
-                content    : "\f0c8";
-                margin-right:10px;
-            }
-
-            &.active {
-                &:before {
-                    width      : 1em;
-                    height     : 1em;
-                    line-height: 1em;
-                    display    : inline-block;
-                    font-family: $font-family-icons;
-                    content    : "\f14a";
-                    color      : $color-bot;
-                    margin-right:10px;
-                }
-            }
-        }
-
-        .gltf-input {
-            background-color: $background-lighter;
-            border          : none;
-            outline         : none;
-            font-family     : $font;
-            color           : darken($color, 10%);
-            padding         : 5px;
-            margin          : 0px 6px 0px 0;
-
-            &:hover {
-                background-color:$background-lighter2;
-            }
-        }
-
-        .gltf-button {
-            background-color: $background-lighter;
-            border          : none;
-            outline         : none;
-            font-family     : $font;
-            color           : $color;
-            padding         : 5px 10px;
-            margin          : 0px 6px 0px 0;
-
-            &:hover {
-                background-color:$background-lighter2;
-            }
-
-            &:active {
-                background-color:$background-lighter3;
-            }
-        }
-    }
-}

+ 0 - 53
inspector/sass/tabs/_shaderTab.scss

@@ -1,53 +0,0 @@
-.tab-panel {
-
-    .shader-tree-panel {
-        height                  : 30px;
-        
-        // The combo box listing all shaders
-        select {
-            height              : 30px;
-            background-color    : transparent;
-            color               : #ccc;
-            height              : 30px;
-            width               : 100%;
-            max-width           : 300px;
-            padding-left        : 15px;
-            border              : 1px solid $background-lighter;
-            outline             : 1px solid $background-lighter3;
-            option {
-                padding         : 5px;
-                color           : darken($color: $color, $amount: 30%)
-            }
-        }
-        
-    }
-
-    .shader-panel {
-        min-height              : 100px;
-        user-select             : text;
-        box-sizing              : border-box;
-        padding                 : 0 15px;
-        
-        // Shader code style - the syntax highlightinh is done by highlight.js, loaded dynamically
-        pre {
-            margin              : 0;
-            white-space         : pre-wrap;
-            code {
-                // to cancel the background color from zenburn theme of highlight js
-                background-color: $background !important;
-                padding         : 0;
-                margin          : 0;                    
-            }            
-        }
-        
-        .shader-panel-title {
-            height              : 25px;
-            border-bottom       : 1px solid $background-lighter2;
-            text-transform      : uppercase;
-            line-height         : 25px;
-            margin-bottom       : 10px;
-        }
-        
-        
-    }
-}

+ 0 - 52
inspector/sass/tabs/_statsTab.scss

@@ -1,52 +0,0 @@
-.tab-panel {
-
-    &.stats-panel {
-        overflow-y      : auto;
-    }
-    
-    .stats-fps {
-        font-weight:600;
-        color:$color-top;
-    }
-
-    .stat-title1 {        
-        font-size       : 1.1em;
-        padding         : 10px;
-    }
-
-    .stat-title2 {
-        margin          : 10px 0 10px 0;
-        font-size       : 1.05em; 
-        border-bottom   : 1px solid $color-bot;
-        box-sizing      : border-box;
-    }
-
-    .stat-label {
-        display         : inline-block;
-        width           : 80%;
-        padding         : 2px;
-        background-color: $background-lighter;
-        border-bottom   : 1px solid $background;
-        border-top      : 1px solid $background;
-        height          : 30px;
-        line-height     : 30px;
-        box-sizing      : border-box;
-        
-    }
-    .stat-value {
-        display         : inline-block;
-        width           : 20%;
-        padding         : 2px;
-        background-color: $background-lighter;
-        border-top      : 1px solid $background;
-        border-bottom   : 1px solid $background;
-        height          : 30px;
-        line-height     : 30px;
-        box-sizing      : border-box;
-    }
-
-    .stat-infos {
-        width           : 100%;
-        padding         : 4px;
-    }
-}

+ 0 - 80
inspector/sass/tabs/_toolsTab.scss

@@ -1,80 +0,0 @@
-.tab-panel {
-
-    &.tools-panel {
-        overflow-y      : auto;
-    }
-
-    .tool-title1 {        
-        font-size       : 1.1em;
-        padding         : 10px;
-    }
-
-    .tool-title2 {
-        margin          : 10px 0 10px 0;
-        font-size       : 1.05em; 
-        border-bottom   : 1px solid $color-bot;
-        box-sizing      : border-box;
-    }
-
-    .tool-label {
-        background-color: $background-lighter;
-        border          : none;
-        outline         : none;
-        font-family     : $font;
-        color           : darken($color, 10%);
-        padding         : 5px;
-        margin          : 0px 6px 0px 0;
-    }
-
-    .tool-label-line {
-        @extend .tool-label;
-        width           : 100%;
-    }
-
-    .tool-label-error {
-        @extend .tool-label;
-        color           : $color-error;
-        width           : 100%;
-        background-color: none;
-    }
-
-    .tool-value {
-        display         : inline-block;
-        width           : 25%;
-        padding         : 2px;
-        background-color: $background-lighter;
-        border-top      : 1px solid $background;
-        border-bottom   : 1px solid $background;
-        height          : 30px;
-        line-height     : 30px;
-        box-sizing      : border-box;
-    }
-
-    .tool-infos {
-        width           : 100%;
-        padding         : 4px;
-    }
-
-    .tool-input {
-        background-color: $background-lighter;
-        border          : none;
-        outline         : none;
-        font-family     : $font;
-        color           : $color;
-        padding         : 5px 10px;
-        margin          : 0px 6px 0px 0;
-        width           : 100%;
-        border-top      : 1px solid $background;
-        border-bottom   : 1px solid $background;
-        text-align: left;
-
-        &:hover {
-            background-color:$background-lighter2;
-            cursor: pointer;
-        }
-
-        &:active {
-            background-color:$background-lighter3;
-        }
-    }
-}

BIN
inspector/screens/tab_mesh.jpg


BIN
inspector/screens/tools.jpg


+ 360 - 375
inspector/src/Inspector.ts

@@ -1,441 +1,426 @@
-import { AbstractMesh, Nullable, Scene, Tools, Observable } from "babylonjs";
-import "../sass/main.scss";
-import { Helpers } from "./helpers/Helpers";
-import { loadGUIProperties } from "./properties_gui";
-import { Scheduler } from "./scheduler/Scheduler";
-import { TabBar } from "./tabs/TabBar";
 
-import * as Split from "Split";
+import * as React from "react";
+import * as ReactDOM from "react-dom";
+import { ActionTabsComponent } from "./components/actionTabs/actionTabsComponent";
+import { SceneExplorerComponent } from "./components/sceneExplorer/sceneExplorerComponent";
+import { Scene, Observable, Observer, Nullable } from "babylonjs";
+import { EmbedHostComponent } from "./components/embedHost/embedHostComponent";
+import { PropertyChangedEvent } from "./components/propertyChangedEvent";
+
+export interface IExtensibilityOption {
+    label: string;
+    action: (entity: any) => void;
+}
+
+export interface IExtensibilityGroup {
+    predicate: (entity: any) => boolean;
+    entries: IExtensibilityOption[];
+}
+
+export interface IInspectorOptions {
+    overlay?: boolean;
+    sceneExplorerRoot?: HTMLElement;
+    actionTabsRoot?: HTMLElement;
+    embedHostRoot?: HTMLElement;
+    showExplorer?: boolean;
+    showInspector?: boolean;
+    explorerWidth?: string;
+    inspectorWidth?: string;
+    embedHostWidth?: string;
+    embedMode?: boolean;
+    handleResize?: boolean;
+    enablePopup?: boolean;
+    explorerExtensibility?: IExtensibilityGroup[];
+}
+
+interface IInternalInspectorOptions extends IInspectorOptions {
+    popup: boolean;
+    original: boolean;
+}
 
 export class Inspector {
+    private static _SceneExplorerHost: Nullable<HTMLElement>;
+    private static _ActionTabsHost: Nullable<HTMLElement>;
+    private static _EmbedHost: Nullable<HTMLElement>;
+    private static _NewCanvasContainer: HTMLElement;
+
+    private static _SceneExplorerWindow: Window;
+    private static _ActionTabsWindow: Window;
+    private static _EmbedHostWindow: Window;
+
+    private static _Scene: Scene;
+    private static _OpenedPane = 0;
+    private static _OnBeforeRenderObserver: Nullable<Observer<Scene>>;
+
+    public static OnSelectionChangeObservable = new BABYLON.Observable<string>();
+    public static OnPropertyChangedObservable = new BABYLON.Observable<PropertyChangedEvent>();
+
+    private static _CopyStyles(sourceDoc: HTMLDocument, targetDoc: HTMLDocument) {
+        for (var index = 0; index < sourceDoc.styleSheets.length; index++) {
+            var styleSheet: any = sourceDoc.styleSheets[index];
+            if (styleSheet.cssRules) { // for <style> elements
+                const newStyleEl = sourceDoc.createElement('style');
+
+                for (var cssRule of styleSheet.cssRules) {
+                    // write the text of each rule into the body of the style element
+                    newStyleEl.appendChild(sourceDoc.createTextNode(cssRule.cssText));
+                }
 
-    private _c2diwrapper: HTMLElement;
-    // private _detailsPanel: DetailPanel;
-    /** The panel displayed at the top of the inspector */
-    private _topPanel: HTMLElement;
-    /** The div containing the content of the active tab */
-    private _tabPanel: HTMLElement;
-    /** The panel containing the list if items */
-    // private _treePanel   : HTMLElement;
-    private _tabbar: TabBar;
-    private _scene: Scene;
-    /** The HTML document relative to this inspector (the window or the popup depending on its mode) */
-    public static DOCUMENT: HTMLDocument;
-    /** The HTML window. In popup mode, it's the popup itself. Otherwise, it's the current tab */
-    public static WINDOW: Window;
-    /** True if the inspector is built as a popup tab */
-    private _popupMode: boolean = false;
-    /** The original canvas style, before applying the inspector*/
-    private _canvasStyle: any;
-
-    private _initialTab: number | string;
-
-    private _parentElement: Nullable<HTMLElement>;
-
-    public onGUILoaded: Observable<any>;
-
-    public static GUIObject: any; // should be typeof "babylonjs-gui";
-
-    /** The inspector is created with the given engine.
-     * If the parameter 'popup' is false, the inspector is created as a right panel on the main window.
-     * If the parameter 'popup' is true, the inspector is created in another popup.
-     */
-    constructor(scene: Scene, popup?: boolean, initialTab: number | string = 0, parentElement: Nullable<HTMLElement> = null, newColors?: {
-        backgroundColor?: string,
-        backgroundColorLighter?: string,
-        backgroundColorLighter2?: string,
-        backgroundColorLighter3?: string,
-        color?: string,
-        colorTop?: string,
-        colorBot?: string
-    }) {
-
-        this.onGUILoaded = new Observable();
-
-        import("babylonjs-gui").then((GUI) => {
-            // Load GUI library if not already done
-            if (!GUI || (typeof GUI !== "undefined" && Object.keys(GUI).indexOf("default") !== -1)) {
-                Tools.LoadScript("https://preview.babylonjs.com/gui/babylon.gui.min.js", () => {
-                    Inspector.GUIObject = (<any>BABYLON).GUI;
-                    this.onGUILoaded.notifyObservers(Inspector.GUIObject);
-                    //Load properties of GUI objects now as GUI has to be declared before
-                    loadGUIProperties(Inspector.GUIObject);
-                }, () => {
-                    console.warn('Error : loading "babylon.gui.min.js". Please add script https://preview.babylonjs.com/gui/babylon.min.gui.js to the HTML file.');
-                });
-            }
-            else {
-                Inspector.GUIObject = GUI;
-                this.onGUILoaded.notifyObservers(Inspector.GUIObject);
-                //Load properties of GUI objects now as GUI has to be declared before
-                loadGUIProperties(Inspector.GUIObject);
-            }
-        });
-        //get Tabbar initialTab
-        this._initialTab = initialTab;
+                targetDoc.head!.appendChild(newStyleEl);
+            } else if (styleSheet.href) { // for <link> elements loading CSS from a URL
+                const newLinkEl = sourceDoc.createElement('link');
 
-        //get parentElement of our Inspector
-        this._parentElement = parentElement;
+                newLinkEl.rel = 'stylesheet';
+                newLinkEl.href = styleSheet.href;
+                targetDoc.head!.appendChild(newLinkEl);
+            }
+        }
+    }
 
-        // get canvas parent only if needed.
-        this._scene = scene;
+    private static _CreateSceneExplorer(scene: Scene, options: IInternalInspectorOptions, parentControlExplorer: Nullable<HTMLElement>, onSelectionChangeObservable: Observable<string>) {
+        // Duplicating the options as they can be different for each pane
+        if (options.original) {
+            options = {
+                original: false,
+                popup: options.popup,
+                overlay: options.overlay,
+                showExplorer: options.showExplorer,
+                showInspector: options.showInspector,
+                embedMode: options.embedMode,
+                handleResize: options.handleResize,
+                enablePopup: options.enablePopup,
+                explorerExtensibility: options.explorerExtensibility
+            };
+        }
 
-        // Save HTML document and window
-        Inspector.DOCUMENT = window.document;
-        Inspector.WINDOW = window;
+        if (!options.sceneExplorerRoot || options.popup) {
+            // Prepare the scene explorer host
+            if (parentControlExplorer) {
+                this._SceneExplorerHost = parentControlExplorer.ownerDocument!.createElement("div");
 
-        // POPUP MODE
-        if (popup) {
-            // Build the inspector in the given parent
-            this.openPopup(true); // set to true in order to NOT dispose the inspector (done in openPopup), as it's not existing yet
-        } else {
-            // Get canvas and its DOM parent
-            let canvas = <HTMLElement>this._scene.getEngine().getRenderingCanvas();
-            let canvasParent = canvas.parentElement;
-
-            // get canvas style
-            let canvasComputedStyle = Inspector.WINDOW.getComputedStyle(canvas);
-
-            this._canvasStyle = {
-                width: Helpers.Css(canvas, 'width'),
-                height: Helpers.Css(canvas, 'height'),
-
-                position: canvasComputedStyle.position,
-                top: canvasComputedStyle.top,
-                bottom: canvasComputedStyle.bottom,
-                left: canvasComputedStyle.left,
-                right: canvasComputedStyle.right,
-
-                padding: canvasComputedStyle.padding,
-                paddingBottom: canvasComputedStyle.paddingBottom,
-                paddingLeft: canvasComputedStyle.paddingLeft,
-                paddingTop: canvasComputedStyle.paddingTop,
-                paddingRight: canvasComputedStyle.paddingRight,
-
-                margin: canvasComputedStyle.margin,
-                marginBottom: canvasComputedStyle.marginBottom,
-                marginLeft: canvasComputedStyle.marginLeft,
-                marginTop: canvasComputedStyle.marginTop,
-                marginRight: canvasComputedStyle.marginRight
+                this._SceneExplorerHost.id = "scene-explorer-host";
+                this._SceneExplorerHost.style.width = options.explorerWidth || "300px";
 
-            };
+                parentControlExplorer.appendChild(this._SceneExplorerHost);
 
-            if (this._parentElement) {
-                // Build the inspector wrapper
-                this._c2diwrapper = Helpers.CreateDiv('insp-wrapper', this._parentElement);
-                this._c2diwrapper.style.width = '100%';
-                this._c2diwrapper.style.height = '100%';
-                this._c2diwrapper.style.paddingLeft = '5px';
-
-                // add inspector
-                let inspector = Helpers.CreateDiv('insp-right-panel', this._c2diwrapper);
-                inspector.style.width = '100%';
-                inspector.style.height = '100%';
-                // and build it in the popup
-                this._buildInspector(inspector);
-            } else {
-                // Create c2di wrapper
-                this._c2diwrapper = Helpers.CreateDiv('insp-wrapper');
-
-                // copy style from canvas to wrapper
-                for (let prop in this._canvasStyle) {
-                    (<any>this._c2diwrapper.style)[prop] = this._canvasStyle[prop];
-                }
+                if (!options.overlay) {
+                    this._SceneExplorerHost.style.gridColumn = "1";
+                    this._SceneExplorerHost.style.position = "relative";
 
-                if (!canvasComputedStyle.width || !canvasComputedStyle.height || !canvasComputedStyle.left) {
-                    return;
+                    if (!options.popup) {
+                        options.sceneExplorerRoot = this._SceneExplorerHost;
+                    }
                 }
+            }
+        } else {
+            this._SceneExplorerHost = options.sceneExplorerRoot;
+        }
 
-                // Convert wrapper size in % (because getComputedStyle returns px only)
-                let widthPx = parseFloat(canvasComputedStyle.width.substr(0, canvasComputedStyle.width.length - 2)) || 0;
-                let heightPx = parseFloat(canvasComputedStyle.height.substr(0, canvasComputedStyle.height.length - 2)) || 0;
+        // Scene
+        if (this._SceneExplorerHost) {
+            Inspector._OpenedPane++;
+            const sceneExplorerElement = React.createElement(SceneExplorerComponent, {
+                scene, onSelectionChangeObservable: onSelectionChangeObservable,
+                extensibilityGroups: options.explorerExtensibility,
+                noExpand: !options.enablePopup, popupMode: options.popup, onPopup: () => {
+                    ReactDOM.unmountComponentAtNode(this._SceneExplorerHost!);
+
+                    if (options.popup) {
+                        this._SceneExplorerWindow.close();
+                    }
 
-                // If the canvas position is absolute, restrain the wrapper width to the window width + left positionning
-                if (canvasComputedStyle.position === "absolute" || canvasComputedStyle.position === "relative") {
-                    // compute only left as it takes predominance if right is also specified (and it will be for the wrapper)
-                    let leftPx = parseFloat(canvasComputedStyle.left.substr(0, canvasComputedStyle.left.length - 2)) || 0;
-                    if (widthPx + leftPx >= Inspector.WINDOW.innerWidth) {
-                        this._c2diwrapper.style.maxWidth = `${widthPx - leftPx}px`;
+                    options.popup = !options.popup;
+                    options.showExplorer = true;
+                    options.showInspector = false;
+                    options.explorerWidth = options.popup ? "100%" : "300px";
+                    Inspector.Show(scene, options);
+                }, onClose: () => {
+                    ReactDOM.unmountComponentAtNode(this._SceneExplorerHost!);
+                    Inspector._OpenedPane--;
+                    this._Cleanup();
+
+                    if (options.popup) {
+                        this._SceneExplorerWindow.close();
+                    } else if (!options.overlay) {
+                        if (this._SceneExplorerHost) {
+                            this._SceneExplorerHost.style.width = "0";
+                        }
                     }
                 }
+            });
+            ReactDOM.render(sceneExplorerElement, this._SceneExplorerHost);
+        }
+    }
 
-                // Check if the parent of the canvas is the body page. If yes, the size ratio is computed
-                let parent = this._getRelativeParent(canvas);
+    private static _CreateActionTabs(scene: Scene, options: IInternalInspectorOptions, parentControlActions: Nullable<HTMLElement>, onSelectionChangeObservable: Observable<string>) {
 
-                let parentWidthPx = parent.clientWidth;
-                let parentHeightPx = parent.clientHeight;
+        if (!options.actionTabsRoot || options.popup) {
+            // Prepare the inspector host
+            if (parentControlActions) {
+                const host = parentControlActions.ownerDocument!.createElement("div");
 
-                let pWidth = widthPx / parentWidthPx * 100;
-                let pheight = heightPx / parentHeightPx * 100;
+                host.id = "inspector-host";
+                host.style.width = options.inspectorWidth || "300px";
 
-                this._c2diwrapper.style.width = pWidth + "%";
-                this._c2diwrapper.style.height = pheight + "%";
+                parentControlActions.appendChild(host);
 
-                // reset canvas style
-                canvas.style.position = "static";
-                canvas.style.width = "100%";
-                canvas.style.height = "100%";
-                canvas.style.paddingBottom = "0";
-                canvas.style.paddingLeft = "0";
-                canvas.style.paddingTop = "0";
-                canvas.style.paddingRight = "0";
+                this._ActionTabsHost = host;
 
-                canvas.style.margin = "0";
-                canvas.style.marginBottom = "0";
-                canvas.style.marginLeft = "0";
-                canvas.style.marginTop = "0";
-                canvas.style.marginRight = "0";
+                if (!options.overlay) {
+                    this._ActionTabsHost.style.gridColumn = "3";
+                    this._ActionTabsHost.style.position = "relative";
 
-                // Replace canvas with the wrapper...
-                if (canvasParent) {
-                    canvasParent.replaceChild(this._c2diwrapper, canvas);
-                }
-                // ... and add canvas to the wrapper
-                this._c2diwrapper.appendChild(canvas);
-
-                // add inspector
-                let inspector = Helpers.CreateDiv('insp-right-panel', this._c2diwrapper);
-
-                // Add split bar
-                if (!this._parentElement) {
-                    Split([canvas, inspector], {
-                        direction: 'horizontal',
-                        sizes: [75, 25],
-                        onDrag: () => {
-                            Helpers.SEND_EVENT('resize');
-                            if (this._tabbar) {
-                                this._tabbar.updateWidth();
-                            }
-                        }
-                    });
+                    if (!options.popup) {
+                        options.actionTabsRoot = this._ActionTabsHost;
+                    }
                 }
-
-                // Build the inspector
-                this._buildInspector(inspector);
             }
-            // Send resize event to the window
-            Helpers.SEND_EVENT('resize');
-            this._tabbar.updateWidth();
+        } else {
+            this._ActionTabsHost = options.actionTabsRoot;
         }
 
-        /*
-        * Refresh the inspector if the browser is not edge
-        *   Why not ?! Condition commented on 180525
-        *   To be tested
-        */
-        // if (!Helpers.IsBrowserEdge()) {
-        this.refresh();
-        // }
-
-        // Check custom css colors
-        if (newColors) {
-
-            let bColor = newColors.backgroundColor || '#242424';
-            let bColorl1 = newColors.backgroundColorLighter || '#2c2c2c';
-            let bColorl2 = newColors.backgroundColorLighter2 || '#383838';
-            let bColorl3 = newColors.backgroundColorLighter3 || '#454545';
-
-            let color = newColors.color || '#ccc';
-            let colorTop = newColors.colorTop || '#f29766';
-            let colorBot = newColors.colorBot || '#5db0d7';
-
-            let styles = Inspector.DOCUMENT.querySelectorAll('style');
-            for (let s = 0; s < styles.length; s++) {
-                let style = styles[s];
-
-                if (style.innerHTML.indexOf('insp-wrapper') != -1) {
-
-                    styles[s].innerHTML = styles[s].innerHTML
-                        .replace(/#242424/g, bColor) // background color
-                        .replace(/#2c2c2c/g, bColorl1) // background-lighter
-                        .replace(/#383838/g, bColorl2) // background-lighter2
-                        .replace(/#454545/g, bColorl3) // background-lighter3
-                        .replace(/#ccc/g, color) // color
-                        .replace(/#f29766/g, colorTop) // color-top
-                        .replace(/#5db0d7/g, colorBot); // color-bot
-                }
-            }
+        if (this._ActionTabsHost) {
+            Inspector._OpenedPane++;
+            const actionTabsElement = React.createElement(ActionTabsComponent, {
+                onSelectionChangeObservable: onSelectionChangeObservable, scene: scene, noExpand: !options.enablePopup, popupMode: options.popup, onPopup: () => {
+                    ReactDOM.unmountComponentAtNode(this._ActionTabsHost!);
+
+                    if (options.popup) {
+                        this._ActionTabsWindow.close();
+                    }
+
+                    options.popup = !options.popup;
+                    options.showExplorer = false;
+                    options.showInspector = true;
+                    options.inspectorWidth = options.popup ? "100%" : "300px";
+                    Inspector.Show(scene, options);
+                }, onClose: () => {
+                    ReactDOM.unmountComponentAtNode(this._ActionTabsHost!);
+                    Inspector._OpenedPane--;
+                    this._Cleanup();
+                    if (options.popup) {
+                        this._ActionTabsWindow.close();
+                    } else if (!options.overlay) {
+                        if (this._ActionTabsHost) {
+                            this._ActionTabsHost.style.width = "0";
+                        }
+                    }
+
+                }, onPropertyChangedObservable: Inspector.OnPropertyChangedObservable
+            });
+            ReactDOM.render(actionTabsElement, this._ActionTabsHost);
         }
     }
 
-    /**
-     * If the given element has a position 'asbolute' or 'relative',
-     * returns the first parent of the given element that has a position 'relative' or 'absolute'.
-     * If the given element has no position, returns the first parent
-     *
-     */
-    private _getRelativeParent(elem: HTMLElement, lookForAbsoluteOrRelative?: boolean): HTMLElement {
-        // If the elem has no parent, returns himself
-        if (!elem.parentElement) {
-            return elem;
-        }
-        let computedStyle = Inspector.WINDOW.getComputedStyle(elem);
-        // looking for the first element absolute or relative
-        if (lookForAbsoluteOrRelative) {
-            // if found, return this one
-            if (computedStyle.position === "relative" || computedStyle.position === "absolute") {
-                return elem;
-            } else {
-                // otherwise keep looking
-                return this._getRelativeParent(elem.parentElement, true);
+    private static _CreateEmbedHost(scene: Scene, options: IInternalInspectorOptions, parentControl: Nullable<HTMLElement>, onSelectionChangeObservable: Observable<string>) {
+
+
+        if (!options.embedHostRoot) {
+            // Prepare the inspector host
+            if (parentControl) {
+                const host = parentControl.ownerDocument!.createElement("div");
+
+                host.id = "embed-host";
+                host.style.width = options.embedHostWidth || "300px";
+
+                parentControl.appendChild(host);
+
+                this._EmbedHost = host;
             }
+        } else {
+            this._EmbedHost = options.embedHostRoot;
         }
-        // looking for the relative parent of the element
-        else {
-            if (computedStyle.position == "static") {
-                return elem.parentElement;
-            } else {
-                // the elem has a position relative or absolute, look for the closest relative/absolute parent
-                return this._getRelativeParent(elem.parentElement, true);
-            }
+
+        if (this._EmbedHost) {
+            const embedHostElement = React.createElement(EmbedHostComponent, {
+                onSelectionChangeObservable: onSelectionChangeObservable, scene: scene, popupMode: options.popup, onPopup: () => {
+                    ReactDOM.unmountComponentAtNode(this._EmbedHost!);
+
+                    if (options.popup) {
+                        this._EmbedHostWindow.close();
+                    }
+
+                    options.popup = !options.popup;
+                    options.embedMode = true;
+                    options.showExplorer = true;
+                    options.showInspector = true;
+                    options.embedHostWidth = options.popup ? "100%" : "auto";
+                    Inspector.Show(scene, options);
+                }, onClose: () => {
+                    ReactDOM.unmountComponentAtNode(this._EmbedHost!);
+
+                    this._OpenedPane = 0;
+                    this._Cleanup();
+                    if (options.popup) {
+                        this._EmbedHostWindow.close();
+                    }
+                }
+            });
+            ReactDOM.render(embedHostElement, this._EmbedHost);
         }
     }
+    private static _CreatePopup(title: string, windowVariableName: string) {
+        const windowCreationOptionsList = {
+            width: 300,
+            height: 800,
+            top: (window.innerHeight - 800) / 2 + window.screenY,
+            left: (window.innerWidth - 300) / 2 + window.screenX
+        };
+
+        var windowCreationOptions = Object.keys(windowCreationOptionsList)
+            .map(
+                (key) => key + '=' + (windowCreationOptionsList as any)[key]
+            )
+            .join(',');
+
+        const popupWindow = window.open("", title, windowCreationOptions);
+        if (!popupWindow) {
+            return null;
+        }
 
-    /** Build the inspector panel in the given HTML element */
-    private _buildInspector(parent: HTMLElement) {
-        // tabbar
-        this._tabbar = new TabBar(this, this._initialTab);
+        const parentDocument = popupWindow.document;
 
-        // Top panel
-        this._topPanel = Helpers.CreateDiv('top-panel', parent);
-        // Add tabbar
-        this._topPanel.appendChild(this._tabbar.toHtml());
-        this._tabbar.updateWidth();
+        parentDocument.title = title;
+        parentDocument.body.style.width = "100%";
+        parentDocument.body.style.height = "100%";
+        parentDocument.body.style.margin = "0";
+        parentDocument.body.style.padding = "0";
 
-        // Tab panel
-        this._tabPanel = Helpers.CreateDiv('tab-panel-content', this._topPanel);
+        let parentControl = parentDocument.createElement("div");
+        parentControl.style.width = "100%";
+        parentControl.style.height = "100%";
+        parentControl.style.margin = "0";
+        parentControl.style.padding = "0";
 
-    }
+        popupWindow.document.body.appendChild(parentControl);
 
-    public get scene(): Scene {
-        return this._scene;
-    }
-    public get popupMode(): boolean {
-        return this._popupMode;
-    }
+        this._CopyStyles(window.document, parentDocument);
 
-    /**
-     * Filter the list of item present in the tree.
-     * All item returned should have the given filter contained in the item id.
-    */
-    public filterItem(filter: string) {
-        let tab = this._tabbar.getActiveTab();
+        (this as any)[windowVariableName] = popupWindow;
 
-        if (tab) {
-            tab.filter(filter);
-        }
+        return parentControl;
     }
 
-    /** Display the mesh tab on the given object */
-    public displayObjectDetails(mesh: AbstractMesh) {
-        this._tabbar.switchMeshTab(mesh);
-    }
+    public static Show(scene: Scene, userOptions: Partial<IInspectorOptions>) {
+
+        const options: IInternalInspectorOptions = {
+            original: true,
+            popup: false,
+            overlay: false,
+            showExplorer: true,
+            showInspector: true,
+            embedMode: false,
+            handleResize: true,
+            enablePopup: true,
+            inspectorWidth: "auto",
+            explorerWidth: "auto",
+            embedHostWidth: "auto",
+            ...userOptions
+        };
+
+        if (!scene) {
+            scene = BABYLON.Engine.LastCreatedScene!;
+        }
 
-    /** Clean the whole tree of item and rebuilds it */
-    public refresh() {
-        // Clean top panel
-        Helpers.CleanDiv(this._tabPanel);
+        this._Scene = scene;
 
-        // Get the active tab and its items
-        let activeTab = this._tabbar.getActiveTab();
+        var canvas = scene ? scene.getEngine().getRenderingCanvas() : BABYLON.Engine.LastCreatedEngine!.getRenderingCanvas();
 
-        if (!activeTab) {
-            return;
+        if (options.embedMode && options.showExplorer && options.showInspector) {
+            if (options.popup) {
+                this._CreateEmbedHost(scene, options, this._CreatePopup("INSPECTOR", "_EmbedHostWindow"), Inspector.OnSelectionChangeObservable);
+            }
+            else {
+                let parentControl = options.embedHostRoot ? options.embedHostRoot.parentElement : canvas!.parentElement;
+                this._CreateEmbedHost(scene, options, parentControl, Inspector.OnSelectionChangeObservable);
+            }
         }
-        activeTab.update();
-        this._tabPanel.appendChild(activeTab.getPanel());
-        Helpers.SEND_EVENT('resize');
+        else if (options.popup) {
+            if (options.showExplorer) {
+                if (this._SceneExplorerHost) {
+                    this._SceneExplorerHost.style.width = "0";
+                }
+                this._CreateSceneExplorer(scene, options, this._CreatePopup("SCENE EXPLORER", "_SceneExplorerWindow"), Inspector.OnSelectionChangeObservable);
+            }
+            if (options.showInspector) {
+                if (this._ActionTabsHost) {
+                    this._ActionTabsHost.style.width = "0";
+                }
+                this._CreateActionTabs(scene, options, this._CreatePopup("INSPECTOR", "_ActionTabsWindow"), Inspector.OnSelectionChangeObservable);
+            }
+        } else {
+            let parentControl = (options.actionTabsRoot ? options.actionTabsRoot.parentElement : canvas!.parentElement) as HTMLElement;
 
-    }
+            if (!options.overlay && !this._NewCanvasContainer) {
+
+                // Create a container for previous elements
+                parentControl.style.display = "grid";
+                parentControl.style.gridTemplateColumns = "auto 1fr auto";
+                parentControl.style.gridTemplateRows = "100%";
 
-    /** Remove the inspector panel when it's built as a right panel:
-     * remove the right panel and remove the wrapper
-     */
-    public dispose() {
-        if (!this._popupMode) {
-            let activeTab = this._tabbar.getActiveTab();
-            if (activeTab) {
-                activeTab.dispose();
+                this._NewCanvasContainer = parentControl.ownerDocument!.createElement("div");
+
+                parentControl.appendChild(this._NewCanvasContainer);
+                parentControl.removeChild(canvas!);
+                this._NewCanvasContainer.appendChild(canvas!);
+
+                this._NewCanvasContainer.style.gridRow = "1";
+                this._NewCanvasContainer.style.gridColumn = "2";
+                this._NewCanvasContainer.style.width = "100%";
+                this._NewCanvasContainer.style.height = "100%";
+                this._NewCanvasContainer.style.display = "grid";
+
+                if (options.handleResize && scene) {
+                    this._OnBeforeRenderObserver = scene.onBeforeRenderObservable.add(() => {
+                        scene.getEngine().resize();
+                    });
+                }
             }
 
-            // Get canvas
-            let canvas = <HTMLElement>this._scene.getEngine().getRenderingCanvas();
+            if (options.showExplorer) {
+                if (options.sceneExplorerRoot && !options.overlay) {
+                    options.sceneExplorerRoot.style.width = "auto";
+                }
 
-            // restore canvas style
-            for (let prop in this._canvasStyle) {
-                (<any>canvas.style)[prop] = this._canvasStyle[prop];
+                this._CreateSceneExplorer(scene, options, parentControl, Inspector.OnSelectionChangeObservable);
             }
-            // Get parent of the wrapper
-            if (canvas.parentElement) {
-                let canvasParent = canvas.parentElement.parentElement;
-
-                if (canvasParent) {
-                    canvasParent.insertBefore(canvas, this._c2diwrapper);
-                    // Remove wrapper
-                    Helpers.CleanDiv(this._c2diwrapper);
-                    this._c2diwrapper.remove();
-                    // Send resize event to the window
-                    Helpers.SEND_EVENT('resize');
+
+            if (options.showInspector) {
+                if (options.actionTabsRoot && !options.overlay) {
+                    options.actionTabsRoot.style.width = "auto";
                 }
+
+                this._CreateActionTabs(scene, options, parentControl, Inspector.OnSelectionChangeObservable);
             }
         }
-        Scheduler.getInstance().dispose();
     }
 
-    /** Open the inspector in a new popup
-     * Set 'firstTime' to true if there is no inspector created beforehands
-     */
-    public openPopup(firstTime?: boolean) {
+    private static _Cleanup() {
+        if (Inspector._OpenedPane === 0 && this._OnBeforeRenderObserver && this._Scene) {
+            this._Scene.onBeforeRenderObservable.remove(this._OnBeforeRenderObserver);
+            this._OnBeforeRenderObserver = null;
 
-        // Create popup
-        let popup = window.open('', 'js INSPECTOR', 'toolbar=no,resizable=yes,menubar=no,width=750,height=1000');
-        if (!popup) {
-            alert("Please update your browser to open the js inspector in an external view.");
-            return;
+            this._Scene.getEngine().resize();
         }
-        popup.document.title = "js INSPECTOR";
-        // Get the inspector style
-        let styles = Inspector.DOCUMENT.querySelectorAll('style');
-        for (let s = 0; s < styles.length; s++) {
-            popup.document.body.appendChild(styles[s].cloneNode(true));
+    }
+
+    public static Hide() {
+        if (this._ActionTabsHost) {
+            ReactDOM.unmountComponentAtNode(this._ActionTabsHost);
+            this._ActionTabsHost = null;
         }
-        let links = document.querySelectorAll('link');
-        for (let l = 0; l < links.length; l++) {
-            let link = popup.document.createElement("link");
-            link.rel = "stylesheet";
-            link.href = (links[l] as HTMLLinkElement).href;
-
-            if (popup.document.head) {
-                popup.document.head.appendChild(link);
-            }
+
+        if (this._SceneExplorerHost) {
+            ReactDOM.unmountComponentAtNode(this._SceneExplorerHost);
+            this._SceneExplorerHost = null;
         }
-        // Dispose the right panel if existing
-        if (!firstTime) {
-            this.dispose();
+
+        if (this._EmbedHost) {
+            ReactDOM.unmountComponentAtNode(this._EmbedHost);
+            this._EmbedHost = null;
         }
-        // set the mode as popup
-        this._popupMode = true;
-        // Save the HTML document
-        Inspector.DOCUMENT = popup.document;
-        Inspector.WINDOW = popup;
-        // Build the inspector wrapper
-        this._c2diwrapper = Helpers.CreateDiv('insp-wrapper', popup.document.body);
-        // add inspector
-        let inspector = Helpers.CreateDiv('insp-right-panel', this._c2diwrapper);
-        inspector.classList.add('popupmode');
-        // and build it in the popup
-        this._buildInspector(inspector);
-        // Rebuild it
-        this.refresh();
-
-        popup.addEventListener('resize', () => {
-            if (this._tabbar) {
-                this._tabbar.updateWidth();
-            }
-        });
-    }
 
-    public getActiveTabIndex(): number {
-        return this._tabbar.getActiveTabIndex();
+        Inspector._OpenedPane = 0;
+        this._Cleanup();
     }
 }

+ 0 - 43
inspector/src/adapters/Adapter.ts

@@ -1,43 +0,0 @@
-import { Geometry } from "babylonjs";
-import { PropertyLine } from "../details/PropertyLine";
-import { AbstractTreeTool } from "../treetools/AbstractTreeTool";
-
-export abstract class Adapter {
-
-    protected _obj: any;
-    // a unique name for this adapter, to retrieve its own key in the local storage
-    private static _name: string = Geometry.RandomId();
-
-    constructor(obj: any) {
-        this._obj = obj;
-    }
-
-    /** Returns the name displayed in the tree */
-    public abstract id(): string;
-
-    /** Returns the type of this object - displayed in the tree */
-    public abstract type(): string;
-
-    /** Returns the list of properties to be displayed for this adapter */
-    public abstract getProperties(): Array<PropertyLine>;
-
-    /** Returns true if the given object correspond to this  */
-    public correspondsTo(obj: any) {
-        return obj === this._obj;
-    }
-
-    /** Returns the adapter unique name */
-    public get name(): string {
-        return Adapter._name;
-    }
-
-    /**
-     * Returns the actual object used for this adapter
-     */
-    public get object(): any {
-        return this._obj;
-    }
-
-    /** Returns the list of tools available for this adapter */
-    public abstract getTools(): Array<AbstractTreeTool>;
-}

+ 0 - 56
inspector/src/adapters/CameraAdapter.ts

@@ -1,56 +0,0 @@
-import { Camera } from "babylonjs";
-import { AbstractTreeTool } from "../treetools/AbstractTreeTool";
-import { PropertyLine } from "../details/PropertyLine";
-import { Helpers } from "../helpers/Helpers";
-import { CameraPOV, ICameraPOV } from "../treetools/CameraPOV";
-import { Adapter } from "./Adapter";
-
-export class CameraAdapter
-    extends Adapter
-    implements ICameraPOV {
-
-    constructor(obj: Camera) {
-        super(obj);
-    }
-
-    /** Returns the name displayed in the tree */
-    public id(): string {
-        let str = '';
-        if (this._obj.name) {
-            str = this._obj.name;
-        } // otherwise nothing displayed
-        return str;
-    }
-
-    /** Returns the type of this object - displayed in the tree */
-    public type(): string {
-        return Helpers.GET_TYPE(this._obj);
-    }
-
-    /** Returns the list of properties to be displayed for this adapter */
-    public getProperties(): Array<PropertyLine> {
-        return Helpers.GetAllLinesProperties(this._obj);
-    }
-
-    public getTools(): Array<AbstractTreeTool> {
-        let tools = [];
-        tools.push(new CameraPOV(this));
-        return tools;
-    }
-
-    // Set the point of view of the chosen camera
-    public setPOV() {
-        (this._obj as Camera).getScene().switchActiveCamera(this._obj);
-    }
-
-    // Return the name of the current active camera
-    public getCurrentActiveCamera() {
-        let activeCamera = (this._obj as Camera).getScene().activeCamera;
-        if (activeCamera != null) {
-            return activeCamera.name;
-        } else {
-            return "0";
-        }
-    }
-
-}

+ 0 - 48
inspector/src/adapters/GUIAdapter.ts

@@ -1,48 +0,0 @@
-
-import { PropertyLine } from "../details/PropertyLine";
-import { Helpers } from "../helpers/Helpers";
-import { AbstractTreeTool } from "../treetools/AbstractTreeTool";
-import { Checkbox, IToolVisible } from "../treetools/Checkbox";
-import { Adapter } from "./Adapter";
-
-export class GUIAdapter
-    extends Adapter
-    implements IToolVisible {
-
-    constructor(obj: any) {
-        super(obj);
-    }
-
-    /** Returns the name displayed in the tree */
-    public id(): string {
-        let str = '';
-        if (this._obj.name) {
-            str = this._obj.name;
-        } // otherwise nothing displayed
-        return str;
-    }
-
-    /** Returns the type of this object - displayed in the tree */
-    public type(): string {
-        return Helpers.GET_TYPE(this._obj);
-    }
-
-    /** Returns the list of properties to be displayed for this adapter */
-    public getProperties(): Array<PropertyLine> {
-        return Helpers.GetAllLinesProperties(this._obj);
-    }
-
-    public getTools(): Array<AbstractTreeTool> {
-        let tools = [];
-        tools.push(new Checkbox(this));
-        return tools;
-    }
-
-    public setVisible(b: boolean) {
-        (this._obj).isVisible = b;
-    }
-
-    public isVisible(): boolean {
-        return (this._obj).isVisible;
-    }
-}

+ 0 - 51
inspector/src/adapters/LightAdapter.ts

@@ -1,51 +0,0 @@
-import { PropertyLine } from "../details/PropertyLine";
-import { Helpers } from "../helpers/Helpers";
-import { AbstractTreeTool } from "../treetools/AbstractTreeTool";
-import { Checkbox, IToolVisible } from "../treetools/Checkbox";
-import { Adapter } from "./Adapter";
-
-export class LightAdapter
-    extends Adapter
-    implements IToolVisible {
-
-    constructor(obj: BABYLON.Light) {
-        super(obj);
-    }
-
-    /** Returns the name displayed in the tree */
-    public id(): string {
-        let str = '';
-        if (this._obj.name) {
-            str = this._obj.name;
-        } // otherwise nothing displayed
-        return str;
-    }
-
-    /** Returns the type of this object - displayed in the tree */
-    public type(): string {
-        return Helpers.GET_TYPE(this._obj);
-    }
-
-    /** Returns the list of properties to be displayed for this adapter */
-    public getProperties(): Array<PropertyLine> {
-        return Helpers.GetAllLinesProperties(this._obj);
-    }
-
-    public getTools(): Array<AbstractTreeTool> {
-        let tools = [];
-        tools.push(new Checkbox(this));
-        return tools;
-    }
-
-    public setVisible(b: boolean) {
-        this._obj.setEnabled(b);
-    }
-    public isVisible(): boolean {
-        return this._obj.isEnabled();
-    }
-
-    /** Returns some information about this mesh */
-    // public getInfo() : string {
-    //     return `${(this._obj as BABYLON.AbstractMesh).getTotalVertices()} vertices`;
-    // }
-}

+ 0 - 37
inspector/src/adapters/MaterialAdapter.ts

@@ -1,37 +0,0 @@
-import { Material } from "babylonjs";
-import { PropertyLine } from "../details/PropertyLine";
-import { Helpers } from "../helpers/Helpers";
-import { AbstractTreeTool } from "../treetools/AbstractTreeTool";
-import { Adapter } from "./Adapter";
-
-export class MaterialAdapter
-    extends Adapter {
-
-    constructor(obj: Material) {
-        super(obj);
-    }
-
-    /** Returns the name displayed in the tree */
-    public id(): string {
-        let str = '';
-        if (this._obj.name) {
-            str = this._obj.name;
-        } // otherwise nothing displayed
-        return str;
-    }
-
-    /** Returns the type of this object - displayed in the tree */
-    public type(): string {
-        return Helpers.GET_TYPE(this._obj);
-    }
-
-    /** Returns the list of properties to be displayed for this adapter */
-    public getProperties(): Array<PropertyLine> {
-        return Helpers.GetAllLinesProperties(this._obj);
-    }
-
-    /** No tools for a material adapter */
-    public getTools(): Array<AbstractTreeTool> {
-        return [];
-    }
-}

+ 0 - 116
inspector/src/adapters/MeshAdapter.ts

@@ -1,116 +0,0 @@
-import { AbstractMesh, Debug, Node, Nullable, Observer, Scene, TransformNode, Vector3 } from "babylonjs";
-import { PropertyLine } from "../details/PropertyLine";
-import { Helpers } from "../helpers/Helpers";
-import { AbstractTreeTool } from "../treetools/AbstractTreeTool";
-import { BoundingBox, IToolBoundingBox } from "../treetools/BoundingBox";
-import { Checkbox, IToolVisible } from "../treetools/Checkbox";
-import { DebugArea, IToolDebug } from "../treetools/DebugArea";
-import { Info, IToolInfo } from "../treetools/Info";
-import { Adapter } from "./Adapter";
-
-export class MeshAdapter
-    extends Adapter
-    implements IToolVisible, IToolDebug, IToolBoundingBox, IToolInfo {
-
-    /** Keep track of the axis of the actual object */
-    private _axesViewer: Nullable<any>;
-    private onBeforeRenderObserver: Nullable<Observer<Scene>>;
-
-    constructor(mesh: Node) {
-        super(mesh);
-    }
-
-    /** Returns the name displayed in the tree */
-    public id(): string {
-        let str = '';
-        if (this._obj.name) {
-            str = this._obj.name;
-        } // otherwise nothing displayed
-        return str;
-    }
-
-    /** Returns the type of this object - displayed in the tree */
-    public type(): string {
-        return Helpers.GET_TYPE(this._obj);
-    }
-
-    /** Returns the list of properties to be displayed for this adapter */
-    public getProperties(): Array<PropertyLine> {
-        return Helpers.GetAllLinesProperties(this._obj);
-    }
-
-    public getTools(): Array<AbstractTreeTool> {
-        let tools = [];
-        tools.push(new Checkbox(this));
-        tools.push(new DebugArea(this));
-        if (this._obj instanceof AbstractMesh) {
-            if ((this._obj as AbstractMesh).getTotalVertices() > 0) {
-                tools.push(new BoundingBox(this));
-            }
-        }
-
-        tools.push(new Info(this));
-        return tools;
-    }
-
-    public setVisible(b: boolean) {
-        this._obj.setEnabled(b);
-        this._obj.isVisible = b;
-    }
-    public isVisible(): boolean {
-        return this._obj.isEnabled() && (this._obj.isVisible === undefined || this._obj.isVisible);
-    }
-    public isBoxVisible(): boolean {
-        return (this._obj as AbstractMesh).showBoundingBox;
-    }
-    public setBoxVisible(b: boolean) {
-        return (this._obj as AbstractMesh).showBoundingBox = b;
-    }
-
-    public debug(enable: boolean) {
-        // Draw axis the first time
-        if (!this._axesViewer) {
-            this._drawAxis();
-        }
-        // Display or hide axis
-        if (!enable && this._axesViewer) {
-            let mesh = this._obj as AbstractMesh;
-            mesh.getScene().onBeforeRenderObservable.remove(this.onBeforeRenderObserver);
-            this._axesViewer.dispose();
-            this._axesViewer = null;
-        }
-    }
-
-    /** Returns some information about this mesh */
-    public getInfo(): string {
-        if (this._obj instanceof AbstractMesh) {
-            return `${(this._obj as AbstractMesh).getTotalVertices()} vertices`;
-        }
-        return '0 vertices';
-    }
-
-    /** Draw X, Y and Z axis for the actual object if this adapter.
-     * Should be called only one time as it will fill this._axis
-     */
-    private _drawAxis() {
-        this._obj.computeWorldMatrix();
-
-        // Axis
-        var x = new Vector3(1, 0, 0);
-        var y = new Vector3(0, 1, 0);
-        var z = new Vector3(0, 0, 1);
-
-        this._axesViewer = new Debug.AxesViewer(this._obj.getScene());
-
-        let mesh = this._obj as TransformNode;
-        this.onBeforeRenderObserver = mesh.getScene().onBeforeRenderObservable.add(() => {
-            let matrix = mesh.getWorldMatrix();
-            let extend = new Vector3(1, 1, 1);
-            if (mesh instanceof AbstractMesh) {
-                extend = mesh.getBoundingInfo().boundingBox.extendSizeWorld;
-            }
-            this._axesViewer!.scaleLines = Math.max(extend.x, extend.y, extend.z) * 2;
-            this._axesViewer!.update(this._obj.position, Vector3.TransformNormal(x, matrix), Vector3.TransformNormal(y, matrix), Vector3.TransformNormal(z, matrix));
-        });
-    }
-}

+ 0 - 59
inspector/src/adapters/PhysicsImpostorAdapter.ts

@@ -1,59 +0,0 @@
-import { AbstractMesh, PhysicsImpostor } from "babylonjs";
-import { PropertyLine } from "../details/PropertyLine";
-import { Helpers } from "../helpers/Helpers";
-import { AbstractTreeTool } from "../treetools/AbstractTreeTool";
-import { Checkbox, IToolVisible } from "../treetools/Checkbox";
-import { Adapter } from "./Adapter";
-
-export class PhysicsImpostorAdapter
-    extends Adapter
-    implements IToolVisible {
-
-    private _viewer: any;
-    private _isVisible = false;
-
-    constructor(obj: PhysicsImpostor, viewer: any) {
-        super(obj);
-        this._viewer = viewer;
-    }
-
-    /** Returns the name displayed in the tree */
-    public id(): string {
-        let str = '';
-        let physicsImposter = (<PhysicsImpostor>this._obj);
-        if (physicsImposter && physicsImposter.object) {
-            str = (<AbstractMesh>physicsImposter.object).name || "";
-        } // otherwise nothing displayed
-        return str;
-    }
-
-    /** Returns the type of this object - displayed in the tree */
-    public type(): string {
-        return Helpers.GET_TYPE(this._obj);
-    }
-
-    /** Returns the list of properties to be displayed for this adapter */
-    public getProperties(): Array<PropertyLine> {
-        return Helpers.GetAllLinesProperties(this._obj);
-    }
-
-    public getTools(): Array<AbstractTreeTool> {
-        let tools = [];
-        tools.push(new Checkbox(this));
-        return tools;
-    }
-
-    public setVisible(b: boolean) {
-        this._isVisible = b;
-        if (b) {
-            this._viewer.showImpostor(this._obj);
-        } else {
-            this._viewer.hideImpostor(this._obj);
-        }
-    }
-
-    public isVisible(): boolean {
-        return this._isVisible;
-    }
-
-}

+ 0 - 52
inspector/src/adapters/SoundAdapter.ts

@@ -1,52 +0,0 @@
-import { Sound } from "babylonjs";
-import { PropertyLine } from "../details/PropertyLine";
-import { Helpers } from "../helpers/Helpers";
-import { AbstractTreeTool } from "../treetools/AbstractTreeTool";
-import { ISoundInteractions, SoundInteractions } from "../treetools/SoundInteractions";
-import { Adapter } from "./Adapter";
-
-export class SoundAdapter
-    extends Adapter
-    implements ISoundInteractions {
-
-    constructor(obj: Sound) {
-        super(obj);
-    }
-
-    /** Returns the name displayed in the tree */
-    public id(): string {
-        let str = '';
-        if (this._obj.name) {
-            str = this._obj.name;
-        } // otherwise nothing displayed
-        return str;
-    }
-
-    /** Returns the type of this object - displayed in the tree */
-    public type(): string {
-        return Helpers.GET_TYPE(this._obj);
-    }
-
-    /** Returns the list of properties to be displayed for this adapter */
-    public getProperties(): Array<PropertyLine> {
-        return Helpers.GetAllLinesProperties(this._obj);
-    }
-
-    public getTools(): Array<AbstractTreeTool> {
-        let tools = [];
-        tools.push(new SoundInteractions(this));
-        return tools;
-    }
-
-    public setPlaying(callback: Function) {
-        if ((this._obj as Sound).isPlaying) {
-            (this._obj as Sound).pause();
-        }
-        else {
-            (this._obj as Sound).play();
-        }
-        (this._obj as Sound).onEndedObservable.addOnce(() => {
-            callback();
-        });
-    }
-}

+ 0 - 40
inspector/src/adapters/TextureAdapter.ts

@@ -1,40 +0,0 @@
-import { Adapter } from "./Adapter";
-import { BaseTexture } from "babylonjs";
-import { Helpers } from "../helpers/Helpers";
-import { PropertyLine } from "../details/PropertyLine";
-import { AbstractTreeTool } from "../treetools/AbstractTreeTool";
-
-export class TextureAdapter
-    extends Adapter {
-
-    constructor(obj: BaseTexture) {
-        super(obj);
-    }
-
-    /** Returns the name displayed in the tree */
-    public id(): string {
-        let str = '';
-        if (this._obj.name) {
-            str = this._obj.name;
-        } // otherwise nothing displayed
-        return str;
-    }
-
-    /** Returns the type of this object - displayed in the tree */
-    public type(): string {
-        return Helpers.GET_TYPE(this._obj);
-    }
-
-    /** Returns the list of properties to be displayed for this adapter */
-    public getProperties(): Array<PropertyLine> {
-        // Not used in this tab
-        return [];
-    }
-
-    public getTools(): Array<AbstractTreeTool> {
-        let tools = new Array<AbstractTreeTool>();
-        // tools.push(new CameraPOV(this));
-        return tools;
-    }
-
-}

+ 0 - 9
inspector/src/adapters/index.ts

@@ -1,9 +0,0 @@
-export * from './Adapter';
-export * from './CameraAdapter';
-export * from './GUIAdapter';
-export * from './LightAdapter';
-export * from './MaterialAdapter';
-export * from './MeshAdapter';
-export * from './PhysicsImpostorAdapter';
-export * from './SoundAdapter';
-export * from './TextureAdapter';

+ 716 - 0
inspector/src/components/actionTabs/actionTabs.scss

@@ -0,0 +1,716 @@
+#inspector-host {
+    position: absolute;
+    right: 0px;
+    top:0px;
+    bottom: 0px;
+}
+
+#actionTabs {
+    background: #333333;
+    height: 100%;
+    margin: 0;
+    padding: 0;
+    display: grid;
+    grid-template-rows: auto 1fr;
+    font: 14px "Arial";    
+    overflow: hidden;
+
+    #header {
+        height: 30px;
+        font-size: 16px;
+        color: white;
+        background: #222222;
+        grid-row: 1;
+        text-align: center;
+        display: grid;
+        grid-template-columns: 30px 1fr 50px;        
+        -webkit-user-select: none; 
+        -moz-user-select: none;   
+        -ms-user-select: none;    
+        user-select: none;                
+
+        #logo {
+            grid-column: 1; 
+            width: 24px;
+            height: 24px;
+            display: flex;
+            align-self: center;   
+            justify-self: center;
+        }        
+
+        #back {
+            grid-column: 1; 
+            display: grid;
+            align-self: center;   
+            justify-self: center;
+            cursor: pointer;
+        }              
+
+        #title {
+            grid-column: 2; 
+            display: grid;
+            align-items: center;   
+            text-align: center;
+        }
+
+        #commands {
+            grid-column: 3; 
+            display: grid;
+            align-items: center;  
+            grid-template-columns: 1fr 1fr;   
+            
+            .expand {
+                grid-column: 1;
+                display: grid;
+                align-items: center;   
+                justify-items: center;
+                cursor: pointer;     
+            }
+
+            .close {
+                grid-column: 2;
+                display: grid;
+                align-items: center;   
+                justify-items: center;
+                cursor: pointer;     
+            }        
+        }
+    }
+
+    .tabs {
+        display: grid;
+        grid-row: 2;
+        grid-template-rows: 40px 1fr;
+        font: 14px "Arial";
+        overflow: hidden;
+
+        .labels {
+            grid-row: 1;
+            display: flex;
+            align-items: center;
+            justify-items: center;
+            border-bottom: 1px solid #ffffff; 
+            margin: 0;
+            padding: 0;         
+
+            .label {
+                font-size: 24px;
+                color: white;
+                width: 40px;
+                display: flex;
+                align-content: center;
+                justify-content: center;
+                border: 1px solid transparent;            
+                border-bottom: none;    
+                background: #333333;
+                padding: 5px;  
+                height: 28px;
+                cursor: pointer;
+
+                &.active {
+                    border-color: #ffffff;  
+                    border-bottom: 2px solid transparent;           
+                    border-radius: 5px 5px 0 0;                        
+                    margin-bottom: -2px;
+                }
+            }
+        }
+
+        .panes {
+            grid-row: 2;
+
+            display: grid;
+            grid-template-rows: 1fr;
+
+            overflow: hidden;
+
+            .infoMessage {
+                opacity: 0.5;
+                color: white;
+                margin: 15px 5px 0px 5px;
+                                
+            }
+
+            .pane {
+                color: white;
+
+                overflow-x: hidden;
+                overflow-y: auto;
+
+                -webkit-user-select: none; 
+                -moz-user-select: none;   
+                -ms-user-select: none;    
+                user-select: none;     
+                
+                .textureLinkLine {
+                    display: grid;
+                    grid-template-columns: auto 1fr;
+
+                    .debug {
+                        grid-column: 1;
+                        margin-left: 5px;
+                        display: grid;
+                        align-items: center; 
+                        justify-items: center;                          
+                        cursor: pointer;
+                        opacity: 0.5;
+
+                        &.selected {
+                            opacity: 1.0;
+                        }
+                    }
+
+                    .textLine {
+                        grid-column: 2;
+                    }
+                }
+
+                .textLine {
+                    padding-left: 5px;
+                    height: 30px;
+                    display: grid;
+                    grid-template-columns: 1fr auto;
+
+                    .label {
+                        grid-column: 1;
+                        display: flex;
+                        align-items: center;
+                    }
+
+                    .link-value {
+                        grid-column: 2;
+                        white-space: nowrap;
+                        text-overflow: ellipsis;
+                        overflow: hidden;
+                        text-align: end;
+                        opacity: 0.8;
+                        margin:5px;
+                        margin-top: 6px;
+                        max-width: 200px;
+                        text-decoration: underline;
+                        cursor: pointer;
+                    }
+
+                    .value {
+                        grid-column: 2;
+                        white-space: nowrap;
+                        text-overflow: ellipsis;
+                        overflow: hidden;
+                        text-align: end;
+                        opacity: 0.8;
+                        margin:5px;
+                        margin-top: 6px;
+                        max-width: 200px;
+
+                        &.check {
+                            color: green;
+                        }
+
+                        &.uncheck {
+                            color: red;
+                        }  
+                    }
+                }
+
+                .buttonLine {
+                    height: 30px;
+                    display: grid;
+                    align-items: center;
+                    justify-items: stretch;
+
+                    input[type="file"] {
+                        display: none;
+                    }
+
+                    .file-upload {
+                        background: transparent;
+                        border: 1px solid rgb(51, 122, 183);
+                        margin: 0px 10px;
+                        color:white;
+                        padding: 4px 5px;
+                        opacity: 0.9;
+                        cursor: pointer;
+                        text-align: center;
+                    }
+
+                    .file-upload:hover {
+                        opacity: 1.0;
+                    }
+
+                    .file-upload:active {
+                        transform: scale(0.98);
+                        transform-origin: 0.5 0.5;
+                    }
+
+                    button {
+                        background: #222222;
+                        border: 0px;
+                        color:white;
+                        padding: 4px 5px;
+                        opacity: 0.9;
+                        width: 100%;
+                        height: 28px;
+                    }
+
+                    button:hover {
+                        opacity: 1.0;
+                    }
+
+                    button:active {
+                        background: #282828;
+                    }   
+                    
+                    button:focus {
+                        border: 0px;
+                        outline: 0px;
+                    }  
+                }
+
+                .radioLine {
+                    padding-left: 5px;
+                    height: 30px;
+                    display: grid;
+                    grid-template-columns: 1fr 30px;
+
+                    .label {
+                        grid-column: 1;
+                        display: flex;
+                        align-items: center;
+                    }
+
+                    .radioContainer {
+                        display: flex;
+                        align-items: center;
+
+                        .radio {
+                            grid-column: 2;                        
+                            display: none;
+
+                            &:checked + label:before {
+                                border-color: rgb(51, 122, 183);
+                            }
+                            &:checked + label:after {
+                                transform: scale(1);
+                            }                        
+                        }
+
+                        .labelForRadio {
+                            display: inline-block;
+                            height: 14px;
+                            position: relative;
+                            padding: 0 24px;
+                            margin-bottom: 0;
+                            cursor: pointer;
+                            vertical-align: bottom;
+                            &:before, &:after {
+                                position: absolute;            
+                                content: '';  
+                                border-radius: 50%;
+                                transition: all .3s ease;
+                                transition-property: transform, border-color;
+                            }
+                            &:before {
+                                left: 0px;
+                                top: 0;
+                                width: 16px;
+                                height: 16px;
+                                border: 2px solid white;
+                            }
+                            &:after {
+                                top: 6px;
+                                left: 6px;
+                                width: 8px;
+                                height: 8px;
+                                transform: scale(0);
+                                background:rgb(51, 122, 183);
+                            }
+                        }
+                    }
+                }
+
+                .vector3Line {
+                    padding-left: 5px;                    
+                    display: grid;
+
+                    .firstLine {
+                        display: grid;
+                        grid-template-columns: 1fr auto 20px;
+                        height: 30px;
+
+                        .label {
+                            grid-column: 1;
+                            display: flex;
+                            align-items: center;
+                        }
+
+                        .vector {
+                            grid-column: 2;
+                            display: flex;
+                            align-items: center;
+                            text-align: right;
+                            opacity: 0.8;
+                        }
+
+                        .expand {
+                            grid-column: 3;
+                            display: grid;
+                            align-items: center;
+                            justify-items: center;
+                            cursor: pointer;
+                        }
+                    }
+
+                    .secondLine {
+                        display: grid;
+                        padding-right: 5px;  
+                        border-left: 1px solid rgb(51, 122, 183);
+
+                        .numeric {
+                            display: grid;
+                            grid-template-columns: 1fr auto;
+                        }
+
+                        .numeric-label {
+                            text-align: right;
+                            grid-column: 1;
+                            display: flex;
+                            align-items: center;                            
+                            justify-self: right;
+                            margin-right: 10px;                          
+                        }
+
+                        .numeric-value {
+                            width: 120px;
+                            grid-column: 2;
+                            display: flex;
+                            align-items: center;  
+                            border: 1px solid  rgb(51, 122, 183);
+                        }                        
+                    }
+                }
+
+                .checkBoxLine {
+                    padding-left: 5px;
+                    height: 30px;
+                    display: grid;
+                    grid-template-columns: 1fr auto;
+
+
+                    .label {
+                        grid-column: 1;
+                        display: flex;
+                        align-items: center;
+                    }
+
+                    .checkBox {
+                        grid-column: 2;
+                        
+                        display: flex;
+                        align-items: center;
+
+                        .lbl {
+                            position: relative;
+                            display: block;
+                            height: 14px;
+                            width: 34px;
+                            margin-right: 10px;
+                            background: #898989;
+                            border-radius: 100px;
+                            cursor: pointer;
+                            transition: all 0.3s ease;
+                        }
+
+                        .lbl:after {
+                            position: absolute;
+                            left: -2px;
+                            top: -3px;
+                            display: block;
+                            width: 20px;
+                            height: 20px;
+                            border-radius: 100px;
+                            background: #fff;
+                            box-shadow: 0px 3px 3px rgba(0,0,0,0.05);
+                            content: '';
+                            transition: all 0.3s ease;
+                        }
+
+                        .lbl:active:after { 
+                            transform: scale(1.15, 0.85); 
+                        }
+
+                        .cbx:checked ~ label { 
+                            background: rgb(51, 122, 183);
+                        }
+
+                        .cbx:checked ~ label:after {
+                            left: 20px;
+                            background: rgb(22, 73, 117);
+                        }
+
+                        .hidden { 
+                            display: none; 
+                        }               
+                    }                    
+                }
+
+                .textureLine {                   
+                    display: grid;
+                    grid-template-rows: 30px auto;
+
+                    .control {
+                        margin-top: 2px;
+                        grid-row: 1;
+                        display: grid;
+                        grid-template-columns: 1fr 40px 40px 40px 40px 40px 1fr;
+
+                        .red {
+                            grid-column: 2;
+                        }
+
+                        .green {
+                            grid-column: 3;
+                        }
+
+                        .blue {
+                            grid-column: 4;
+                        }
+
+                        .alpha {
+                            grid-column: 5;
+                        }                        
+
+                        .all {
+                            grid-column: 6;
+                        }                        
+                    }
+
+                    .control3D {
+                        margin-top: 2px;
+                        grid-row: 1;
+                        display: grid;
+                        grid-template-columns: 1fr 40px 40px 40px 40px 40px 40px 1fr;
+
+                        .px {
+                            grid-column: 2;
+                        }
+
+                        .nx {
+                            grid-column: 3;
+                        }
+
+                        .py {
+                            grid-column: 4;
+                        }
+
+                        .ny {
+                            grid-column: 5;
+                        }   
+
+                        .pz {
+                            grid-column: 6;
+                        }
+
+                        .nz {
+                            grid-column: 7;
+                        }                     
+                    }                    
+
+                    .command {
+                        border: 0px;
+                        background:transparent;
+                        color: white;
+                    }
+
+                    .selected {
+                        border: 1px solid rgb(51, 122, 183);
+                    }
+
+                    .preview {
+                        grid-row: 2;
+                        display: grid;
+                        align-self: center;
+                        justify-self: center;
+                        height: 256px;
+                        width: 256px;
+                        margin-top: 5px;
+                        margin-bottom: 5px;
+                    }
+                }
+
+                .floatLine {
+                    padding-left: 5px;
+                    height: 30px;
+                    display: grid;
+                    grid-template-columns: 1fr 120px;
+
+
+                    .label {
+                        grid-column: 1;
+                        display: flex;
+                        align-items: center;
+                    }
+
+                    .value {
+                        grid-column: 2;
+                        
+                        display: flex;
+                        align-items: center;
+                        
+                        input {
+                            width: 110px;
+                        }
+                    }
+                }
+
+                .sliderLine {
+                    padding-left: 5px;
+                    height: 30px;
+                    display: grid;
+                    grid-template-columns: 1fr auto;
+
+
+                    .label {
+                        grid-column: 1;
+                        display: flex;
+                        align-items: center;
+                    }
+
+                    .slider {
+                        grid-column: 2;
+                        
+                        display: flex;
+                        align-items: center;
+
+                        .range {
+                            -webkit-appearance: none;
+                            width: 120px;
+                            height: 8px;
+                            background: #d3d3d3;
+                            border-radius: 5px;
+                            outline: none;
+                            opacity: 0.7;
+                            -webkit-transition: .2s;
+                            transition: opacity .2s;
+                        }
+                        
+                        .range:hover {
+                            opacity: 1;
+                        }
+                        
+                        .range::-webkit-slider-thumb {
+                            -webkit-appearance: none;
+                            appearance: none;
+                            width: 20px;
+                            height: 20px;
+                            border-radius: 50%;
+                            background: rgb(51, 122, 183);
+                            cursor: pointer;
+                        }
+                        
+                        .range::-moz-range-thumb {
+                            width: 20px;
+                            height: 20px;
+                            border-radius: 50%;
+                            background: rgb(51, 122, 183);
+                            cursor: pointer;
+                        }
+                    }                    
+                }    
+                
+                .color3Line {
+                    padding-left: 5px;
+                    height: 30px;
+                    display: grid;
+                    grid-template-columns: 1fr auto;
+
+
+                    .label {
+                        grid-column: 1;
+                        display: flex;
+                        align-items: center;
+                    }
+
+                    .color3 {
+                        grid-column: 2;
+                        
+                        display: flex;
+                        align-items: center;   
+
+                        input[type="color"] {
+                            -webkit-appearance: none;
+                            border: none;
+                            padding: 0;
+                            width: 24px;
+                            height: 24px;
+                        }
+                        input[type="color"]::-webkit-color-swatch-wrapper {
+                            padding: 0;
+                        }
+                        input[type="color"]::-webkit-color-swatch {
+                            border: none;
+                        }
+                        
+                        input {
+                            margin-right: 5px;
+                        }
+                    }                    
+                }     
+                
+                .listLine {
+                    padding-left: 5px;
+                    height: 30px;
+                    display: grid;
+                    grid-template-columns: 1fr auto;
+
+
+                    .label {
+                        grid-column: 1;
+                        display: flex;
+                        align-items: center;
+                    }
+
+                    .options {
+                        grid-column: 2;
+                        
+                        display: flex;
+                        align-items: center;   
+                        margin-right: 5px;
+                    }                    
+                }                   
+
+                .paneContainer {
+                    margin-top: 5px;
+
+                    .header {
+                        display: grid;
+                        grid-template-columns: 1fr auto;
+                        background: #555555;    
+                        height: 30px;   
+                        padding-right: 5px;
+                        
+                        .title {
+                            margin-left: 5px;
+                            grid-column: 1;
+                            display: flex;
+                            align-items: center;
+                        }
+
+                        .collapse {
+                            grid-column: 2;
+                            display: flex;
+                            align-items: center;  
+                            justify-items: center;
+                            cursor: pointer;
+                            transform-origin: center;
+                            transition: transform 100ms ease-in-out;
+
+                            &.closed {
+                                transform: rotate(180deg);
+                            }
+                        }                        
+                    }
+                }
+            }
+        }
+    }
+}

+ 108 - 0
inspector/src/components/actionTabs/actionTabsComponent.tsx

@@ -0,0 +1,108 @@
+import * as React from "react";
+import { Observable, Observer, Scene, Nullable } from "babylonjs";
+import { TabsComponent } from "./tabsComponent";
+import { faFileAlt, faWrench, faBug, faChartPie } from '@fortawesome/free-solid-svg-icons';
+import { StatisticsTabComponent } from "./tabs/statisticsTabComponent";
+import { DebugTabComponent } from "./tabs/debugTabComponent";
+import Resizable from "re-resizable";
+import { PropertyGridTabComponent } from "./tabs/propertyGridTabComponent";
+import { HeaderComponent } from "../headerComponent";
+import { PropertyChangedEvent } from "../propertyChangedEvent";
+import { ToolsTabComponent } from "./tabs/toolsTabComponent";
+
+require("./actionTabs.scss");
+
+interface IActionTabsComponentProps {
+    onSelectionChangeObservable: Observable<any>,
+    scene: Scene,
+    onPropertyChangedObservable?: Observable<PropertyChangedEvent>,
+    noCommands?: boolean,
+    noHeader?: boolean,
+    noExpand?: boolean,
+    popupMode?: boolean,
+    onPopup?: () => void,
+    onClose?: () => void
+}
+
+export class ActionTabsComponent extends React.Component<IActionTabsComponentProps, { selectedEntity: any, selectedIndex: number }> {
+    private _onSelectionChangeObserver: Nullable<Observer<any>>;
+    private _once = true;
+
+    constructor(props: IActionTabsComponentProps) {
+        super(props);
+
+        this.state = { selectedEntity: null, selectedIndex: 0 }
+    }
+
+    componentWillMount() {
+        this._onSelectionChangeObserver = this.props.onSelectionChangeObservable.add((entity) => {
+            this.setState({ selectedEntity: entity, selectedIndex: 0 });
+        });
+    }
+
+    componentWillUnmount() {
+        if (this._onSelectionChangeObserver) {
+            this.props.onSelectionChangeObservable.remove(this._onSelectionChangeObserver);
+        }
+    }
+
+    renderContent() {
+        return (
+            <TabsComponent selectedIndex={this.state.selectedIndex} onSelectedIndexChange={(value) => this.setState({ selectedIndex: value })}>
+                <PropertyGridTabComponent
+                    title="Properties" icon={faFileAlt} scene={this.props.scene} selectedEntity={this.state.selectedEntity}
+                    onSelectionChangeObservable={this.props.onSelectionChangeObservable}
+                    onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                <DebugTabComponent title="Debug" icon={faBug} scene={this.props.scene} />
+                <StatisticsTabComponent title="Statistics" icon={faChartPie} scene={this.props.scene} />
+                <ToolsTabComponent title="Tools" icon={faWrench} scene={this.props.scene} />
+            </TabsComponent>
+        )
+    }
+
+    onClose() {
+        if (!this.props.onClose) {
+            return;
+        }
+        this.props.onClose();
+    }
+
+    onPopup() {
+        if (!this.props.onPopup) {
+            return;
+        }
+        this.props.onPopup();
+    }
+
+    render() {
+        if (this.props.popupMode) {
+            return (
+                <div id="actionTabs">
+                    {
+                        !this.props.noHeader &&
+                        <HeaderComponent title="INSPECTOR" handleBack={true} noCommands={this.props.noCommands} onClose={() => this.onClose()} onPopup={() => this.onPopup()} onSelectionChangeObservable={this.props.onSelectionChangeObservable} />
+                    }
+                    {this.renderContent()}
+                </div>
+            );
+        }
+
+        if (this._once) {
+            this._once = false;
+            // A bit hacky but no other way to force the initial width to 300px and not auto
+            setTimeout(() => {
+                document.getElementById("actionTabs")!.style.width = "300px";
+            }, 150);
+        }
+
+        return (
+            <Resizable id="actionTabs" minWidth={300} maxWidth={600} size={{ height: "100%" }} minHeight="100%" enable={{ top: false, right: false, bottom: false, left: true, topRight: false, bottomRight: false, bottomLeft: false, topLeft: false }}>
+                {
+                    !this.props.noHeader &&
+                    <HeaderComponent title="INSPECTOR" handleBack={true} noExpand={this.props.noExpand} noCommands={this.props.noCommands} onClose={() => this.onClose()} onPopup={() => this.onPopup()} onSelectionChangeObservable={this.props.onSelectionChangeObservable} />
+                }
+                {this.renderContent()}
+            </Resizable>
+        );
+    }
+}

+ 59 - 0
inspector/src/components/actionTabs/lineContainerComponent.tsx

@@ -0,0 +1,59 @@
+import * as React from "react";
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { faChevronDown } from '@fortawesome/free-solid-svg-icons';
+
+interface ILineContainerComponentProps {
+    title: string,
+    children: any[] | any,
+    closed?: boolean
+}
+
+export class LineContainerComponent extends React.Component<ILineContainerComponentProps, { isExpanded: boolean }> {
+    constructor(props: ILineContainerComponentProps) {
+        super(props);
+
+        this.state = { isExpanded: !this.props.closed };
+    }
+
+    switchExpandedState(): void {
+        this.setState({ isExpanded: !this.state.isExpanded });
+    }
+
+    renderHeader() {
+        const className = this.state.isExpanded ? "collapse" : "collapse closed";
+
+        return (
+            <div className="header">
+                <div className="title">
+                    {this.props.title}
+                </div>
+                <div className={className} onClick={() => this.switchExpandedState()}>
+                    <FontAwesomeIcon icon={faChevronDown} />
+                </div>
+            </div>
+        )
+    }
+
+    render() {
+        if (!this.state.isExpanded) {
+            return (
+                <div className="paneContainer">
+                    {
+                        this.renderHeader()
+                    }
+                </div>
+            )
+        }
+
+        return (
+            <div className="paneContainer">
+                {
+                    this.renderHeader()
+                }
+                <div>
+                    {this.props.children}
+                </div >
+            </div>
+        );
+    }
+}

+ 31 - 0
inspector/src/components/actionTabs/lines/booleanLineComponent.tsx

@@ -0,0 +1,31 @@
+import * as React from "react";
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { faCheck, faTimes } from "@fortawesome/free-solid-svg-icons";
+
+export interface IBooleanLineComponentProps {
+    label: string,
+    value: boolean
+}
+
+export class BooleanLineComponent extends React.Component<IBooleanLineComponentProps> {
+    constructor(props: IBooleanLineComponentProps) {
+        super(props);
+    }
+
+    render() {
+
+        const check = this.props.value ? <FontAwesomeIcon icon={faCheck} /> : <FontAwesomeIcon icon={faTimes} />
+        const className = this.props.value ? "value check" : "value uncheck";
+
+        return (
+            <div className="textLine">
+                <div className="label">
+                    {this.props.label}
+                </div>
+                <div className={className}>
+                    {check}
+                </div>
+            </div>
+        );
+    }
+}

+ 21 - 0
inspector/src/components/actionTabs/lines/buttonLineComponent.tsx

@@ -0,0 +1,21 @@
+import * as React from "react";
+
+export interface IButtonLineComponentProps {
+    label: string,
+    onClick: () => void
+}
+
+export class ButtonLineComponent extends React.Component<IButtonLineComponentProps> {
+    constructor(props: IButtonLineComponentProps) {
+        super(props);
+    }
+
+    render() {
+
+        return (
+            <div className="buttonLine">
+                <button onClick={() => this.props.onClick()}>{this.props.label}</button>
+            </div>
+        );
+    }
+}

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

@@ -0,0 +1,77 @@
+import * as React from "react";
+import { Observable } from "babylonjs";
+import { PropertyChangedEvent } from "../../propertyChangedEvent";
+
+export interface ICheckBoxLineComponentProps {
+    label: string,
+    target?: any,
+    propertyName?: string,
+    isSelected?: () => boolean,
+    onSelect?: (value: boolean) => void,
+    onPropertyChangedObservable?: Observable<PropertyChangedEvent>
+}
+
+export class CheckBoxLineComponent extends React.Component<ICheckBoxLineComponentProps, { isSelected: boolean }> {
+    private static _UniqueIdSeed = 0;
+    private _uniqueId: number;
+    private _localChange = false;
+    constructor(props: ICheckBoxLineComponentProps) {
+        super(props);
+
+        this._uniqueId = CheckBoxLineComponent._UniqueIdSeed++;
+
+        if (this.props.isSelected) {
+            this.state = { isSelected: this.props.isSelected() };
+        } else {
+            this.state = { isSelected: this.props.target[this.props.propertyName!] };
+        }
+    }
+
+    shouldComponentUpdate(nextProps: ICheckBoxLineComponentProps, nextState: { isSelected: boolean }) {
+        var currentState: boolean;
+
+        if (this.props.isSelected) {
+            currentState = nextProps.isSelected!();
+        } else {
+            currentState = nextProps.target[nextProps.propertyName!];
+        }
+
+        if (currentState !== nextState.isSelected || this._localChange) {
+            nextState.isSelected = currentState;
+            this._localChange = false;
+            return true;
+        }
+        return false;
+    }
+
+    onChange() {
+        this._localChange = true;
+        if (this.props.onSelect) {
+            this.props.onSelect(!this.state.isSelected);
+        } else {
+            this.props.onPropertyChangedObservable!.notifyObservers({
+                object: this.props.target,
+                property: this.props.propertyName!,
+                value: !this.state.isSelected,
+                initialValue: this.state.isSelected
+            });
+
+            this.props.target[this.props.propertyName!] = !this.state.isSelected;
+        }
+        this.setState({ isSelected: !this.state.isSelected });
+    }
+
+    render() {
+        return (
+            <div className="checkBoxLine">
+                <div className="label">
+                    {this.props.label}
+                </div>
+                <div className="checkBox">
+                    <input type="checkbox" id={"checkbox" + this._uniqueId} className="cbx hidden" checked={this.state.isSelected} onChange={() => this.onChange()} />
+                    <label htmlFor={"checkbox" + this._uniqueId} className="lbl"></label>
+                </div>
+            </div>
+        );
+    }
+}

+ 61 - 0
inspector/src/components/actionTabs/lines/color3LineComponent.tsx

@@ -0,0 +1,61 @@
+import * as React from "react";
+import { Observable, Color3 } from "babylonjs";
+import { PropertyChangedEvent } from "../../propertyChangedEvent";
+
+export interface IColor3LineComponentProps {
+    label: string,
+    target: any,
+    propertyName: string,
+    onPropertyChangedObservable?: Observable<PropertyChangedEvent>
+}
+
+export class Color3LineComponent extends React.Component<IColor3LineComponentProps, { color: Color3 }> {
+    private _localChange = false;
+    constructor(props: IColor3LineComponentProps) {
+        super(props);
+
+        this.state = { color: this.props.target[this.props.propertyName] };
+    }
+
+    shouldComponentUpdate(nextProps: IColor3LineComponentProps, nextState: { color: Color3 }) {
+        const currentState = nextProps.target[nextProps.propertyName];
+
+        if (!currentState.equals(nextState.color) || this._localChange) {
+            nextState.color = currentState;
+            this._localChange = false;
+            return true;
+        }
+        return false;
+    }
+
+    onChange(newValue: string) {
+        this._localChange = true;
+        const newColor = BABYLON.Color3.FromHexString(newValue);
+
+        if (this.props.onPropertyChangedObservable) {
+            this.props.onPropertyChangedObservable.notifyObservers({
+                object: this.props.target,
+                property: this.props.propertyName,
+                value: newColor,
+                initialValue: this.state.color
+            });
+        }
+
+        this.props.target[this.props.propertyName] = newColor;
+
+        this.setState({ color: newColor });
+    }
+
+    render() {
+        return (
+            <div className="color3Line">
+                <div className="label">
+                    {this.props.label}
+                </div>
+                <div className="color3">
+                    <input type="color" value={this.state.color.toHexString()} onChange={(evt) => this.onChange(evt.target.value)} />
+                </div>
+            </div>
+        );
+    }
+}

+ 30 - 0
inspector/src/components/actionTabs/lines/fileButtonLineComponent.tsx

@@ -0,0 +1,30 @@
+import * as React from "react";
+
+interface IFileButtonLineComponentProps {
+    label: string,
+    onClick: (file: File) => void
+}
+
+export class FileButtonLineComponent extends React.Component<IFileButtonLineComponentProps> {
+    constructor(props: IFileButtonLineComponentProps) {
+        super(props);
+    }
+
+    onChange(evt: any) {
+        var files: File[] = evt.target.files;
+        if (files && files.length) {
+            this.props.onClick(files[0]);
+        }
+    }
+
+    render() {
+        return (
+            <div className="buttonLine">
+                <label htmlFor="file-upload" className="file-upload">
+                    {this.props.label}
+                </label>
+                <input id="file-upload" type="file" accept=".dds, .env" onChange={evt => this.onChange(evt)} />
+            </div>
+        );
+    }
+}

+ 73 - 0
inspector/src/components/actionTabs/lines/floatLineComponent.tsx

@@ -0,0 +1,73 @@
+import * as React from "react";
+import { Observable } from "babylonjs";
+import { PropertyChangedEvent } from "../../propertyChangedEvent";
+
+interface IFloatLineComponentProps {
+    label: string,
+    target: any,
+    propertyName: string,
+    step?: number,
+    onPropertyChangedObservable?: Observable<PropertyChangedEvent>
+}
+
+export class FloatLineComponent extends React.Component<IFloatLineComponentProps, { value: number }> {
+    private _localChange = false;
+
+    constructor(props: IFloatLineComponentProps) {
+        super(props);
+
+        this.state = { value: this.props.target[this.props.propertyName] }
+    }
+
+    shouldComponentUpdate(nextProps: IFloatLineComponentProps, nextState: { value: number }) {
+        if (this._localChange) {
+            this._localChange = false;
+            return true;
+        }
+
+        const newValue = nextProps.target[nextProps.propertyName];
+        if (newValue !== nextState.value) {
+            nextState.value = newValue;
+            return true;
+        }
+        return false;
+    }
+
+    raiseOnPropertyChanged(newValue: number, previousValue: number) {
+        if (!this.props.onPropertyChangedObservable) {
+            return;
+        }
+        this.props.onPropertyChangedObservable.notifyObservers({
+            object: this.props.target,
+            property: this.props.propertyName,
+            value: newValue,
+            initialValue: previousValue
+        });
+    }
+
+    updateValue(valueString: string) {
+        const value = parseFloat(valueString);
+        this._localChange = true;
+
+        const store = this.state.value;
+        this.props.target[this.props.propertyName] = value;
+        this.setState({ value: value });
+
+        this.raiseOnPropertyChanged(value, store);
+    }
+
+    render() {
+
+        const step = this.props.step !== undefined ? this.props.step : 0.1;
+        return (
+            <div className="floatLine">
+                <div className="label">
+                    {this.props.label}
+                </div>
+                <div className="value">
+                    <input className="numeric-input" value={this.state.value ? parseFloat(this.state.value.toFixed(3)) : 0} type="number" onChange={evt => this.updateValue(evt.target.value)} step={step} />
+                </div>
+            </div>
+        );
+    }
+}

+ 56 - 0
inspector/src/components/actionTabs/lines/numericInputComponent.tsx

@@ -0,0 +1,56 @@
+import * as React from "react";
+
+interface INumericInputComponentProps {
+    label: string,
+    value: number,
+    onChange: (value: number) => void
+}
+
+export class NumericInputComponent extends React.Component<INumericInputComponentProps, { value: number }> {
+    private _localChange = false;
+    constructor(props: INumericInputComponentProps) {
+        super(props);
+
+        this.state = { value: this.props.value }
+    }
+
+    shouldComponentUpdate(nextProps: INumericInputComponentProps, nextState: { value: number }) {
+        if (this._localChange) {
+            this._localChange = false;
+            return true;
+        }
+
+        if (nextProps.value !== nextState.value) {
+            nextState.value = nextProps.value;
+            return true;
+        }
+        return false;
+    }
+
+    updateValue(evt: any) {
+        let valueAsNumber = parseFloat(evt.target.value);
+
+        if (isNaN(valueAsNumber)) {
+            return;
+        }
+
+        this._localChange = true;
+        this.setState({ value: valueAsNumber });
+        this.props.onChange(valueAsNumber);
+    }
+
+
+    render() {
+        return (
+            <div className="numeric">
+                {
+                    this.props.label &&
+                    <div className="numeric-label">
+                        {`${this.props.label}: `}
+                    </div>
+                }
+                <input className="numeric-input" value={this.state.value} type="number" onChange={evt => this.updateValue(evt)} step="0.1" />
+            </div>
+        )
+    }
+}

+ 93 - 0
inspector/src/components/actionTabs/lines/optionsLineComponent.tsx

@@ -0,0 +1,93 @@
+import * as React from "react";
+import { Observable } from "babylonjs";
+import { PropertyChangedEvent } from "../../propertyChangedEvent";
+
+class ListLineOption {
+    public label: string;
+    public value: number;
+}
+
+interface IOptionsLineComponentProps {
+    label: string,
+    target: any,
+    propertyName: string,
+    options: ListLineOption[],
+    onSelect?: (value: number) => void,
+    onPropertyChangedObservable?: Observable<PropertyChangedEvent>
+}
+
+export class OptionsLineComponent extends React.Component<IOptionsLineComponentProps, { value: number }> {
+    private _localChange = false;
+
+    constructor(props: IOptionsLineComponentProps) {
+        super(props);
+
+        this.state = { value: 0 };
+    }
+
+    shouldComponentUpdate(nextProps: IOptionsLineComponentProps, nextState: { value: number }) {
+        if (this._localChange) {
+            this._localChange = false;
+            return true;
+        }
+
+        const newValue = nextProps.target[nextProps.propertyName];
+        if (newValue !== nextState.value) {
+            nextState.value = newValue;
+            return true;
+        }
+        return false;
+    }
+
+    raiseOnPropertyChanged(newValue: number, previousValue: number) {
+        if (!this.props.onPropertyChangedObservable) {
+            return;
+        }
+
+        this.props.onPropertyChangedObservable.notifyObservers({
+            object: this.props.target,
+            property: this.props.propertyName,
+            value: newValue,
+            initialValue: previousValue
+        });
+    }
+
+    updateValue(valueString: string) {
+        const value = parseInt(valueString);
+        this._localChange = true;
+
+        const store = this.state.value;
+        this.props.target[this.props.propertyName] = value;
+        this.setState({ value: value });
+
+        this.raiseOnPropertyChanged(value, store);
+
+        if (this.props.onSelect) {
+            this.props.onSelect(value);
+        }
+    }
+
+    render() {
+        var currentValue = this.props.target[this.props.propertyName];
+
+        return (
+            <div className="listLine">
+                <div className="label">
+                    {this.props.label}
+
+                </div>
+                <div className="options">
+                    <select onChange={evt => this.updateValue(evt.target.value)} defaultValue={currentValue}>
+                        {
+                            this.props.options.map(option => {
+                                return (
+                                    <option key={option.label} value={option.value}>{option.label}</option>
+                                )
+                            })
+                        }
+                    </select>
+                </div>
+            </div>
+        );
+    }
+}

+ 50 - 0
inspector/src/components/actionTabs/lines/radioLineComponent.tsx

@@ -0,0 +1,50 @@
+import * as React from "react";
+import { Observer, Observable, Nullable } from "babylonjs";
+
+interface IRadioButtonLineComponentProps {
+    onSelectionChangedObservable: Observable<RadioButtonLineComponent>,
+    label: string,
+    isSelected: () => boolean,
+    onSelect: () => void
+}
+
+export class RadioButtonLineComponent extends React.Component<IRadioButtonLineComponentProps, { isSelected: boolean }> {
+    private _onSelectionChangedObserver: Nullable<Observer<RadioButtonLineComponent>>;
+
+    constructor(props: IRadioButtonLineComponentProps) {
+        super(props);
+
+        this.state = { isSelected: this.props.isSelected() };
+    }
+
+    componentWillMount() {
+        this._onSelectionChangedObserver = this.props.onSelectionChangedObservable.add((value) => {
+            this.setState({ isSelected: value === this });
+        });
+    }
+
+    componentWillUnmount() {
+        if (this._onSelectionChangedObserver) {
+            this.props.onSelectionChangedObservable.remove(this._onSelectionChangedObserver);
+        }
+    }
+
+    onChange() {
+        this.props.onSelect();
+        this.props.onSelectionChangedObservable.notifyObservers(this);
+    }
+
+    render() {
+        return (
+            <div className="radioLine">
+                <div className="label">
+                    {this.props.label}
+                </div>
+                <div className="radioContainer">
+                    <input id={this.props.label} className="radio" type="radio" checked={this.state.isSelected} onChange={() => this.onChange()} />
+                    <label htmlFor={this.props.label} className="labelForRadio" />
+                </div>
+            </div>
+        );
+    }
+}

+ 64 - 0
inspector/src/components/actionTabs/lines/sliderLineComponent.tsx

@@ -0,0 +1,64 @@
+import * as React from "react";
+import { Observable } from "babylonjs";
+import { PropertyChangedEvent } from "../../propertyChangedEvent";
+
+interface ISliderLineComponentProps {
+    label: string,
+    target: any,
+    propertyName: string,
+    minimum: number,
+    maximum: number,
+    step: number,
+    onPropertyChangedObservable?: Observable<PropertyChangedEvent>
+}
+
+export class SliderLineComponent extends React.Component<ISliderLineComponentProps, { value: number }> {
+    private _localChange = false;
+    constructor(props: ISliderLineComponentProps) {
+        super(props);
+
+        this.state = { value: this.props.target[this.props.propertyName] };
+    }
+
+    shouldComponentUpdate(nextProps: ISliderLineComponentProps, nextState: { value: number }) {
+        const currentState = nextProps.target[nextProps.propertyName];
+
+        if (currentState !== nextState.value || this._localChange) {
+            nextState.value = currentState;
+            this._localChange = false;
+            return true;
+        }
+        return false;
+    }
+
+    onChange(newValueString: any) {
+        this._localChange = true;
+        const newValue = parseFloat(newValueString);
+
+        if (this.props.onPropertyChangedObservable) {
+            this.props.onPropertyChangedObservable.notifyObservers({
+                object: this.props.target,
+                property: this.props.propertyName,
+                value: newValue,
+                initialValue: this.state.value
+            });
+        }
+
+        this.props.target[this.props.propertyName] = newValue;
+
+        this.setState({ value: newValue });
+    }
+
+    render() {
+        return (
+            <div className="sliderLine">
+                <div className="label">
+                    {this.props.label}
+                </div>
+                <div className="slider">
+                    {this.state.value.toFixed(2)}&nbsp;<input className="range" type="range" step={this.props.step} min={this.props.minimum} max={this.props.maximum} value={this.state.value} onChange={evt => this.onChange(evt.target.value)} />
+                </div>
+            </div>
+        );
+    }
+}

+ 48 - 0
inspector/src/components/actionTabs/lines/textLineComponent.tsx

@@ -0,0 +1,48 @@
+import * as React from "react";
+
+interface ITextLineComponentProps {
+    label: string,
+    value: string,
+    color?: string,
+    onLink?: () => void
+}
+
+export class TextLineComponent extends React.Component<ITextLineComponentProps> {
+    constructor(props: ITextLineComponentProps) {
+        super(props);
+    }
+
+    onLink() {
+        if (!this.props.onLink) {
+            return;
+        }
+
+        this.props.onLink();
+    }
+
+    renderContent() {
+        if (this.props.onLink) {
+            return (
+                <div className="link-value" title={this.props.value} onClick={() => this.onLink()}>
+                    {this.props.value || "no name"}
+                </div>
+            )
+        }
+        return (
+            <div className="value" title={this.props.value} style={{ color: this.props.color ? this.props.color : "" }}>
+                {this.props.value || "no name"}
+            </div>
+        )
+    }
+
+    render() {
+        return (
+            <div className="textLine">
+                <div className="label">
+                    {this.props.label}
+                </div>
+                {this.renderContent()}
+            </div>
+        );
+    }
+}

+ 177 - 0
inspector/src/components/actionTabs/lines/textureLineComponent.tsx

@@ -0,0 +1,177 @@
+import * as React from "react";
+import { Texture, PostProcess } from "babylonjs";
+
+interface ITextureLineComponentProps {
+    texture: Texture,
+    width: number,
+    height: number
+}
+
+export class TextureLineComponent extends React.Component<ITextureLineComponentProps, { displayRed: boolean, displayGreen: boolean, displayBlue: boolean, displayAlpha: boolean, face: number }> {
+    constructor(props: ITextureLineComponentProps) {
+        super(props);
+
+        this.state = {
+            displayRed: true,
+            displayGreen: true,
+            displayBlue: true,
+            displayAlpha: true,
+            face: 0
+        }
+    }
+
+    componentDidMount() {
+        this.updatePreview();
+    }
+
+    componentDidUpdate() {
+        this.updatePreview();
+    }
+
+    updatePreview() {
+        var texture = this.props.texture;
+        var scene = texture.getScene()!;
+        var engine = scene.getEngine();
+        var size = texture.getSize();
+        var ratio = size.width / size.height
+        var width = this.props.width;
+        var height = (width / ratio) | 0;
+
+        let passPostProcess: PostProcess;
+
+        if (!texture.isCube) {
+            passPostProcess = new BABYLON.PassPostProcess("pass", 1, null, BABYLON.Texture.NEAREST_SAMPLINGMODE, engine, false, BABYLON.Engine.TEXTURETYPE_UNSIGNED_INT);
+        } else {
+            var passCubePostProcess = new BABYLON.PassCubePostProcess("pass", 1, null, BABYLON.Texture.NEAREST_SAMPLINGMODE, engine, false, BABYLON.Engine.TEXTURETYPE_UNSIGNED_INT);
+            passCubePostProcess.face = this.state.face;
+
+            passPostProcess = passCubePostProcess;
+        }
+
+        if (!passPostProcess.getEffect().isReady()) {
+            // Try again later
+            passPostProcess.dispose();
+
+            setTimeout(() => this.updatePreview(), 250);
+
+            return;
+        }
+
+        const previewCanvas = this.refs.canvas as HTMLCanvasElement;
+
+        let rtt = new BABYLON.RenderTargetTexture(
+            "temp",
+            { width: width, height: height },
+            scene, false);
+
+        passPostProcess.onApply = function(effect) {
+            effect.setTexture("textureSampler", texture);
+        };
+
+        let internalTexture = rtt.getInternalTexture();
+
+        if (internalTexture) {
+            scene.postProcessManager.directRender([passPostProcess], internalTexture);
+
+            // Read the contents of the framebuffer
+            var numberOfChannelsByLine = width * 4;
+            var halfHeight = height / 2;
+
+            //Reading datas from WebGL
+            var data = engine.readPixels(0, 0, width, height);
+
+            if (!texture.isCube) {
+                if (!this.state.displayRed || !this.state.displayGreen || !this.state.displayBlue) {
+                    for (var i = 0; i < width * height * 4; i += 4) {
+
+                        if (!this.state.displayRed) {
+                            data[i] = 0;
+                        }
+
+                        if (!this.state.displayGreen) {
+                            data[i + 1] = 0;
+                        }
+
+                        if (!this.state.displayBlue) {
+                            data[i + 2] = 0;
+                        }
+
+                        if (this.state.displayAlpha) {
+                            var alpha = data[i + 2];
+                            data[i] = alpha;
+                            data[i + 1] = alpha;
+                            data[i + 2] = alpha;
+                            data[i + 2] = 0;
+                        }
+                    }
+                }
+            }
+
+            //To flip image on Y axis.
+            if (texture.invertY || texture.isCube) {
+                for (var i = 0; i < halfHeight; i++) {
+                    for (var j = 0; j < numberOfChannelsByLine; j++) {
+                        var currentCell = j + i * numberOfChannelsByLine;
+                        var targetLine = height - i - 1;
+                        var targetCell = j + targetLine * numberOfChannelsByLine;
+
+                        var temp = data[currentCell];
+                        data[currentCell] = data[targetCell];
+                        data[targetCell] = temp;
+                    }
+                }
+            }
+
+            previewCanvas.width = width;
+            previewCanvas.height = height;
+            var context = previewCanvas.getContext('2d');
+
+            if (context) {
+                // Copy the pixels to the preview canvas
+                var imageData = context.createImageData(width, height);
+                var castData = imageData.data;
+                castData.set(data);
+                context.putImageData(imageData, 0, 0);
+            }
+
+            // Unbind
+            engine.unBindFramebuffer(internalTexture);
+        }
+
+        rtt.dispose();
+        passPostProcess.dispose();
+
+        previewCanvas.style.height = height + "px";
+    }
+
+    render() {
+        var texture = this.props.texture;
+
+        return (
+            <div className="textureLine">
+                {
+                    texture.isCube &&
+                    <div className="control3D">
+                        <button className={this.state.face === 0 ? "px command selected" : "px command"} onClick={() => this.setState({ face: 0 })}>PX</button>
+                        <button className={this.state.face === 1 ? "nx command selected" : "nx command"} onClick={() => this.setState({ face: 1 })}>NX</button>
+                        <button className={this.state.face === 2 ? "py command selected" : "py command"} onClick={() => this.setState({ face: 2 })}>PY</button>
+                        <button className={this.state.face === 3 ? "ny command selected" : "ny command"} onClick={() => this.setState({ face: 3 })}>NY</button>
+                        <button className={this.state.face === 4 ? "pz command selected" : "pz command"} onClick={() => this.setState({ face: 4 })}>PZ</button>
+                        <button className={this.state.face === 5 ? "nz command selected" : "nz command"} onClick={() => this.setState({ face: 5 })}>NZ</button>
+                    </div>
+                }
+                {
+                    !texture.isCube &&
+                    <div className="control">
+                        <button className={this.state.displayRed && !this.state.displayGreen ? "red command selected" : "red command"} onClick={() => this.setState({ displayRed: true, displayGreen: false, displayBlue: false, displayAlpha: false })}>R</button>
+                        <button className={this.state.displayGreen && !this.state.displayBlue ? "green command selected" : "green command"} onClick={() => this.setState({ displayRed: false, displayGreen: true, displayBlue: false, displayAlpha: false })}>G</button>
+                        <button className={this.state.displayBlue && !this.state.displayAlpha ? "blue command selected" : "blue command"} onClick={() => this.setState({ displayRed: false, displayGreen: false, displayBlue: true, displayAlpha: false })}>B</button>
+                        <button className={this.state.displayAlpha && !this.state.displayRed ? "alpha command selected" : "alpha command"} onClick={() => this.setState({ displayRed: false, displayGreen: false, displayBlue: false, displayAlpha: true })}>A</button>
+                        <button className={this.state.displayRed && this.state.displayGreen ? "all command selected" : "all command"} onClick={() => this.setState({ displayRed: true, displayGreen: true, displayBlue: true, displayAlpha: true })}>ALL</button>
+                    </div>
+                }
+                <canvas ref="canvas" className="preview" />
+            </div>
+        );
+    }
+}

+ 126 - 0
inspector/src/components/actionTabs/lines/textureLinkLineComponent.tsx

@@ -0,0 +1,126 @@
+import * as React from "react";
+import { BaseTexture, Observable, Material, Observer, Nullable } from "babylonjs";
+import { TextLineComponent } from "./textLineComponent";
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { faWrench } from '@fortawesome/free-solid-svg-icons';
+
+export interface ITextureLinkLineComponentProps {
+    label: string,
+    texture: Nullable<BaseTexture>,
+    material: Material,
+    onSelectionChangeObservable?: Observable<any>,
+    onDebugSelectionChangeObservable: Observable<BaseTexture>
+}
+
+export class TextureLinkLineComponent extends React.Component<ITextureLinkLineComponentProps, { isDebugSelected: boolean }> {
+    private _onDebugSelectionChangeObserver: Nullable<Observer<BaseTexture>>;
+
+    constructor(props: ITextureLinkLineComponentProps) {
+        super(props);
+
+        const material = this.props.material;
+        const texture = this.props.texture;
+
+        this.state = { isDebugSelected: material.metadata && material.metadata.debugTexture === texture };
+    }
+
+
+    componentWillMount() {
+        this._onDebugSelectionChangeObserver = this.props.onDebugSelectionChangeObservable.add((texture) => {
+            if (this.props.texture !== texture) {
+                this.setState({ isDebugSelected: false });
+            }
+        });
+    }
+
+    componentWillUnmount() {
+        if (this._onDebugSelectionChangeObserver) {
+            this.props.onDebugSelectionChangeObservable.remove(this._onDebugSelectionChangeObserver);
+        }
+    }
+
+    debugTexture() {
+        const texture = this.props.texture;
+        const material = this.props.material;
+        const scene = material.getScene();
+
+        if (material.metadata && material.metadata.debugTexture === texture) {
+            const debugMaterial = material.metadata.debugMaterial;
+
+            for (var mesh of scene.meshes) {
+                if (mesh.material === debugMaterial) {
+                    mesh.material = material;
+                }
+            }
+            debugMaterial.dispose();
+            material.metadata.debugTexture = null;
+            material.metadata.debugMaterial = null;
+
+            this.setState({ isDebugSelected: false });
+            return;
+        }
+
+        let checkMaterial = material;
+        let needToDisposeCheckMaterial = false;
+        if (material.metadata && material.metadata.debugTexture) {
+            checkMaterial = material.metadata.debugMaterial;
+            needToDisposeCheckMaterial = true;
+        }
+
+        var debugMaterial = new BABYLON.StandardMaterial("debugMaterial", scene);
+        debugMaterial.disableLighting = true;
+        debugMaterial.sideOrientation = material.sideOrientation;
+        debugMaterial.emissiveTexture = texture!;
+        debugMaterial.forceDepthWrite = true;
+        debugMaterial.metadata = { hidden: true };
+
+        for (var mesh of scene.meshes) {
+            if (mesh.material === checkMaterial) {
+                mesh.material = debugMaterial;
+            }
+        }
+
+        if (!material.metadata) {
+            material.metadata = {};
+        }
+
+        material.metadata.debugTexture = texture;
+        material.metadata.debugMaterial = debugMaterial;
+
+        this.props.onDebugSelectionChangeObservable.notifyObservers(texture!);
+
+        if (needToDisposeCheckMaterial) {
+            checkMaterial.dispose();
+        }
+
+        this.setState({ isDebugSelected: true });
+    }
+
+    onLink() {
+        if (!this.props.onSelectionChangeObservable) {
+            return;
+        }
+
+        const texture = this.props.texture;
+        this.props.onSelectionChangeObservable.notifyObservers(texture!);
+    }
+
+    render() {
+        const texture = this.props.texture;
+
+        if (!texture) {
+            return null;
+        }
+        return (
+            <div className="textureLinkLine">
+                {
+                    !texture.isCube &&
+                    <div className={this.state.isDebugSelected ? "debug selected" : "debug"} onClick={() => this.debugTexture()} title="Render as main texture">
+                        <FontAwesomeIcon icon={faWrench} />
+                    </div>
+                }
+                <TextLineComponent label={this.props.label} value={texture.name} onLink={() => this.onLink()} />
+            </div>
+        );
+    }
+}

+ 31 - 0
inspector/src/components/actionTabs/lines/valueLineComponent.tsx

@@ -0,0 +1,31 @@
+import * as React from "react";
+
+interface IValueLineComponentProps {
+    label: string,
+    value: number,
+    color?: string,
+    fractionDigits?: number,
+    units?: string
+}
+
+export class ValueLineComponent extends React.Component<IValueLineComponentProps> {
+    constructor(props: IValueLineComponentProps) {
+        super(props);
+    }
+
+    render() {
+        const digits = this.props.fractionDigits !== undefined ? this.props.fractionDigits : 2;
+        const value = this.props.value.toFixed(digits) + (this.props.units ? " " + this.props.units : "");
+
+        return (
+            <div className="textLine">
+                <div className="label">
+                    {this.props.label}
+                </div>
+                <div className="value" style={{ color: this.props.color ? this.props.color : "" }}>
+                    {value}
+                </div>
+            </div>
+        );
+    }
+}

+ 110 - 0
inspector/src/components/actionTabs/lines/vector3LineComponent.tsx

@@ -0,0 +1,110 @@
+import * as React from "react";
+import { Vector3, Observable } from "babylonjs";
+import { NumericInputComponent } from "./numericInputComponent";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { faMinus, faPlus } from "@fortawesome/free-solid-svg-icons";
+import { PropertyChangedEvent } from "../../propertyChangedEvent";
+
+interface IVector3LineComponentProps {
+    label: string,
+    target: any,
+    propertyName: string,
+    onPropertyChangedObservable?: Observable<PropertyChangedEvent>
+}
+
+export class Vector3LineComponent extends React.Component<IVector3LineComponentProps, { isExpanded: boolean, value: Vector3 }> {
+    private _localChange = false;
+
+    constructor(props: IVector3LineComponentProps) {
+        super(props);
+
+        this.state = { isExpanded: false, value: this.props.target[this.props.propertyName] }
+    }
+
+    shouldComponentUpdate(nextProps: IVector3LineComponentProps, nextState: { isExpanded: boolean, value: Vector3 }) {
+        const nextPropsValue = nextProps.target[nextProps.propertyName];
+
+        if (!nextPropsValue.equals(nextState.value) || this._localChange) {
+            nextState.value = nextPropsValue;
+            this._localChange = false;
+            return true;
+        }
+        return false;
+    }
+
+    switchExpandState() {
+        this._localChange = true;
+        this.setState({ isExpanded: !this.state.isExpanded });
+    }
+
+    raiseOnPropertyChanged(previousValue: Vector3) {
+        if (!this.props.onPropertyChangedObservable) {
+            return;
+        }
+        this.props.onPropertyChangedObservable.notifyObservers({
+            object: this.props.target,
+            property: this.props.propertyName,
+            value: !this.state.value,
+            initialValue: previousValue
+        });
+    }
+
+    updateStateX(value: number) {
+        this._localChange = true;
+
+        const store = this.state.value.clone();
+        this.state.value.x = value;
+        this.setState({ value: this.state.value });
+
+        this.raiseOnPropertyChanged(store);
+    }
+
+    updateStateY(value: number) {
+        this._localChange = true;
+
+        const store = this.state.value.clone();
+        this.state.value.y = value;
+        this.setState({ value: this.state.value });
+
+        this.raiseOnPropertyChanged(store);
+    }
+
+    updateStateZ(value: number) {
+        this._localChange = true;
+
+        const store = this.state.value.clone();
+        this.state.value.z = value;
+        this.setState({ value: this.state.value });
+
+        this.raiseOnPropertyChanged(store);
+    }
+
+    render() {
+        const chevron = this.state.isExpanded ? <FontAwesomeIcon icon={faMinus} /> : <FontAwesomeIcon icon={faPlus} />
+
+        return (
+            <div className="vector3Line">
+                <div className="firstLine">
+                    <div className="label">
+                        {this.props.label}
+                    </div>
+                    <div className="vector">
+                        {`X: ${this.state.value.x.toFixed(2)}, Y: ${this.state.value.y.toFixed(2)}, Z: ${this.state.value.z.toFixed(2)}`}
+
+                    </div>
+                    <div className="expand" onClick={() => this.switchExpandState()}>
+                        {chevron}
+                    </div>
+                </div>
+                {
+                    this.state.isExpanded &&
+                    <div className="secondLine">
+                        <NumericInputComponent label="x" value={this.state.value.x} onChange={value => this.updateStateX(value)} />
+                        <NumericInputComponent label="y" value={this.state.value.y} onChange={value => this.updateStateY(value)} />
+                        <NumericInputComponent label="z" value={this.state.value.z} onChange={value => this.updateStateZ(value)} />
+                    </div>
+                }
+            </div>
+        );
+    }
+}

+ 25 - 0
inspector/src/components/actionTabs/paneComponent.tsx

@@ -0,0 +1,25 @@
+import * as React from "react";
+import { IconDefinition } from '@fortawesome/free-solid-svg-icons';
+import { Scene, Observable } from "babylonjs";
+import { PropertyChangedEvent } from "../propertyChangedEvent";
+
+export interface IPaneComponentProps {
+    title: string,
+    icon: IconDefinition, scene: Scene,
+    selectedEntity?: any,
+    onSelectionChangeObservable?: Observable<any>,
+    onPropertyChangedObservable?: Observable<PropertyChangedEvent>
+}
+
+export class PaneComponent extends React.Component<IPaneComponentProps, { tag: any }> {
+    constructor(props: IPaneComponentProps) {
+        super(props);
+    }
+
+    render(): JSX.Element | null {
+        return (
+            <div className="pane">
+            </div>
+        );
+    }
+}

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

@@ -0,0 +1,116 @@
+import * as React from "react";
+import { PaneComponent, IPaneComponentProps } from "../paneComponent";
+import { LineContainerComponent } from "../lineContainerComponent";
+import { CheckBoxLineComponent } from "../lines/checkBoxLineComponent";
+import { GridPropertyGridComponent } from "./propertyGrids/gridPropertyGridComponent";
+
+export class DebugTabComponent extends PaneComponent {
+    private _skeletonViewersEnabled = false;
+    private _skeletonViewers = new Array<BABYLON.Debug.SkeletonViewer>();
+
+    constructor(props: IPaneComponentProps) {
+        super(props);
+    }
+
+
+    componentWillMount() {
+        const scene = this.props.scene;
+
+        if (!scene) {
+            return;
+        }
+
+        for (var mesh of scene.meshes) {
+            if (mesh.skeleton && mesh.metadata && mesh.metadata.skeletonViewer) {
+                this._skeletonViewers.push(mesh.metadata.skeletonViewer);
+            }
+        }
+
+        this._skeletonViewersEnabled = (this._skeletonViewers.length > 0);
+    }
+
+    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 BABYLON.Debug.SkeletonViewer(mesh.skeleton, mesh, scene, true, 0, BABYLON.UtilityLayerRenderer.DefaultUtilityLayer);
+                    viewer.isEnabled = true;
+                    this._skeletonViewers.push(viewer);
+                    if (!mesh.metadata) {
+                        mesh.metadata = {};
+                    }
+                    mesh.metadata.skeletonViewer = viewer;
+                }
+            }
+        } else {
+            for (var index = 0; index < this._skeletonViewers.length; index++) {
+                this._skeletonViewers[index].mesh.metadata.skeletonViewer = null;
+                this._skeletonViewers[index].dispose();
+            }
+            this._skeletonViewers = [];
+
+        }
+    }
+
+    render() {
+        const scene = this.props.scene;
+
+        if (!scene) {
+            return null;
+        }
+
+        return (
+            <div className="pane">
+                <LineContainerComponent title="HELPERS">
+                    <GridPropertyGridComponent scene={scene} />
+                    <CheckBoxLineComponent label="Bones" isSelected={() => this._skeletonViewersEnabled} onSelect={() => this.switchSkeletonViewers()} />
+                </LineContainerComponent>
+                <LineContainerComponent title="TEXTURE CHANNELS">
+                    <CheckBoxLineComponent label="Diffuse" isSelected={() => BABYLON.StandardMaterial.DiffuseTextureEnabled} onSelect={() => BABYLON.StandardMaterial.DiffuseTextureEnabled = !BABYLON.StandardMaterial.DiffuseTextureEnabled} />
+                    <CheckBoxLineComponent label="Ambient" isSelected={() => BABYLON.StandardMaterial.AmbientTextureEnabled} onSelect={() => BABYLON.StandardMaterial.AmbientTextureEnabled = !BABYLON.StandardMaterial.AmbientTextureEnabled} />
+                    <CheckBoxLineComponent label="Specular" isSelected={() => BABYLON.StandardMaterial.SpecularTextureEnabled} onSelect={() => BABYLON.StandardMaterial.SpecularTextureEnabled = !BABYLON.StandardMaterial.SpecularTextureEnabled} />
+                    <CheckBoxLineComponent label="Emissive" isSelected={() => BABYLON.StandardMaterial.EmissiveTextureEnabled} onSelect={() => BABYLON.StandardMaterial.EmissiveTextureEnabled = !BABYLON.StandardMaterial.EmissiveTextureEnabled} />
+                    <CheckBoxLineComponent label="Bump" isSelected={() => BABYLON.StandardMaterial.BumpTextureEnabled} onSelect={() => BABYLON.StandardMaterial.BumpTextureEnabled = !BABYLON.StandardMaterial.BumpTextureEnabled} />
+                    <CheckBoxLineComponent label="Opacity" isSelected={() => BABYLON.StandardMaterial.OpacityTextureEnabled} onSelect={() => BABYLON.StandardMaterial.OpacityTextureEnabled = !BABYLON.StandardMaterial.OpacityTextureEnabled} />
+                    <CheckBoxLineComponent label="Reflection" isSelected={() => BABYLON.StandardMaterial.ReflectionTextureEnabled} onSelect={() => BABYLON.StandardMaterial.ReflectionTextureEnabled = !BABYLON.StandardMaterial.ReflectionTextureEnabled} />
+                    <CheckBoxLineComponent label="Refraction" isSelected={() => BABYLON.StandardMaterial.RefractionTextureEnabled} onSelect={() => BABYLON.StandardMaterial.RefractionTextureEnabled = !BABYLON.StandardMaterial.RefractionTextureEnabled} />
+                    <CheckBoxLineComponent label="ColorGrading" isSelected={() => BABYLON.StandardMaterial.ColorGradingTextureEnabled} onSelect={() => BABYLON.StandardMaterial.ColorGradingTextureEnabled = !BABYLON.StandardMaterial.ColorGradingTextureEnabled} />
+                    <CheckBoxLineComponent label="Lightmap" isSelected={() => BABYLON.StandardMaterial.LightmapTextureEnabled} onSelect={() => BABYLON.StandardMaterial.LightmapTextureEnabled = !BABYLON.StandardMaterial.LightmapTextureEnabled} />
+                    <CheckBoxLineComponent label="Fresnel" isSelected={() => BABYLON.StandardMaterial.FresnelEnabled} onSelect={() => BABYLON.StandardMaterial.FresnelEnabled = !BABYLON.StandardMaterial.FresnelEnabled} />
+                </LineContainerComponent>
+                <LineContainerComponent title="FEATURES">
+                    <CheckBoxLineComponent label="Animations" isSelected={() => scene.animationsEnabled} onSelect={() => scene.animationsEnabled = !scene.animationsEnabled} />
+                    <CheckBoxLineComponent label="Collisions" isSelected={() => scene.collisionsEnabled} onSelect={() => scene.collisionsEnabled = !scene.collisionsEnabled} />
+                    <CheckBoxLineComponent label="Fog" isSelected={() => scene.fogEnabled} onSelect={() => scene.fogEnabled = !scene.fogEnabled} />
+                    <CheckBoxLineComponent label="Lens flares" isSelected={() => scene.lensFlaresEnabled} onSelect={() => scene.lensFlaresEnabled = !scene.lensFlaresEnabled} />
+                    <CheckBoxLineComponent label="Lights" isSelected={() => scene.lightsEnabled} onSelect={() => scene.lightsEnabled = !scene.lightsEnabled} />
+                    <CheckBoxLineComponent label="Particles" isSelected={() => scene.particlesEnabled} onSelect={() => scene.particlesEnabled = !scene.particlesEnabled} />
+                    <CheckBoxLineComponent label="Post-processes" isSelected={() => scene.postProcessesEnabled} onSelect={() => scene.postProcessesEnabled = !scene.postProcessesEnabled} />
+                    <CheckBoxLineComponent label="Probes" isSelected={() => scene.probesEnabled} onSelect={() => scene.probesEnabled = !scene.probesEnabled} />
+                    <CheckBoxLineComponent label="Textures" isSelected={() => scene.texturesEnabled} onSelect={() => scene.texturesEnabled = !scene.texturesEnabled} />
+                    <CheckBoxLineComponent label="Procedural textures" isSelected={() => scene.proceduralTexturesEnabled} onSelect={() => scene.proceduralTexturesEnabled = !scene.proceduralTexturesEnabled} />
+                    <CheckBoxLineComponent label="Render targets" isSelected={() => scene.renderTargetsEnabled} onSelect={() => scene.renderTargetsEnabled = !scene.renderTargetsEnabled} />
+                    <CheckBoxLineComponent label="Shadows" isSelected={() => scene.shadowsEnabled} onSelect={() => scene.shadowsEnabled = !scene.shadowsEnabled} />
+                    <CheckBoxLineComponent label="Skeletons" isSelected={() => scene.skeletonsEnabled} onSelect={() => scene.skeletonsEnabled = !scene.skeletonsEnabled} />
+                    <CheckBoxLineComponent label="Sprites" isSelected={() => scene.spritesEnabled} onSelect={() => scene.spritesEnabled = !scene.spritesEnabled} />
+                </LineContainerComponent>
+            </div>
+        );
+    }
+}

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

@@ -0,0 +1,114 @@
+import * as React from "react";
+import { PaneComponent, IPaneComponentProps } from "../paneComponent";
+import { Mesh, TransformNode, Material, StandardMaterial, Texture, PBRMaterial, Scene, FreeCamera, ArcRotateCamera, HemisphericLight, PointLight, BackgroundMaterial } from "babylonjs";
+import { MaterialPropertyGridComponent } from "./propertyGrids/materials/materialPropertyGridComponent";
+import { StandardMaterialPropertyGridComponent } from "./propertyGrids/materials/standardMaterialPropertyGridComponent";
+import { TexturePropertyGridComponent } from "./propertyGrids/texturePropertyGridComponent";
+import { PBRMaterialPropertyGridComponent } from "./propertyGrids/materials/pbrMaterialPropertyGridComponent";
+import { ScenePropertyGridComponent } from "./propertyGrids/scenePropertyGridComponent";
+import { HemisphericLightPropertyGridComponent } from "./propertyGrids/lights/hemisphericLightPropertyGridComponent";
+import { PointLightPropertyGridComponent } from "./propertyGrids/lights/pointLightPropertyGridComponent";
+import { FreeCameraPropertyGridComponent } from "./propertyGrids/cameras/freeCameraPropertyGridComponent";
+import { ArcRotateCameraPropertyGridComponent } from "./propertyGrids/cameras/arcRotateCameraPropertyGridComponent";
+import { MeshPropertyGridComponent } from "./propertyGrids/meshes/meshPropertyGridComponent";
+import { TransformNodePropertyGridComponent } from "./propertyGrids/meshes/transformNodePropertyGridComponent";
+import { BackgroundMaterialPropertyGridComponent } from "./propertyGrids/materials/backgroundMaterialPropertyGridComponent";
+
+export class PropertyGridTabComponent extends PaneComponent {
+    constructor(props: IPaneComponentProps) {
+        super(props);
+    }
+
+    render() {
+        const entity = this.props.selectedEntity;
+
+        if (!entity) {
+            return (
+                <div className="infoMessage">
+                    Please select an entity in the scene explorer.
+                </div>
+            );
+        }
+
+        if (entity.getClassName) {
+            const className = entity.getClassName();
+
+            if (className.indexOf("Mesh") !== -1) {
+                const mesh = entity as Mesh;
+                if (mesh.getTotalVertices() > 0) {
+                    return (<MeshPropertyGridComponent mesh={mesh}
+                        onSelectionChangeObservable={this.props.onSelectionChangeObservable}
+                        onPropertyChangedObservable={this.props.onPropertyChangedObservable} />);
+                }
+            }
+
+            if (className.indexOf("FreeCamera") !== -1) {
+                const freeCamera = entity as FreeCamera;
+                return (<FreeCameraPropertyGridComponent camera={freeCamera} onPropertyChangedObservable={this.props.onPropertyChangedObservable} />);
+            }
+
+            if (className.indexOf("ArcRotateCamera") !== -1) {
+                const arcRotateCamera = entity as ArcRotateCamera;
+                return (<ArcRotateCameraPropertyGridComponent camera={arcRotateCamera} onPropertyChangedObservable={this.props.onPropertyChangedObservable} />);
+            }
+
+            if (className === "HemisphericLight") {
+                const hemisphericLight = entity as HemisphericLight;
+                return (<HemisphericLightPropertyGridComponent
+                    light={hemisphericLight}
+                    onPropertyChangedObservable={this.props.onPropertyChangedObservable} />);
+            }
+
+            if (className === "PointLight") {
+                const pointLight = entity as PointLight;
+                return (<PointLightPropertyGridComponent
+                    light={pointLight}
+                    onPropertyChangedObservable={this.props.onPropertyChangedObservable} />);
+            }
+
+            if (className.indexOf("TransformNode") !== -1 || className.indexOf("Mesh") !== -1) {
+                const transformNode = entity as TransformNode;
+                return (<TransformNodePropertyGridComponent transformNode={transformNode} onPropertyChangedObservable={this.props.onPropertyChangedObservable} />);
+            }
+
+            if (className === "StandardMaterial") {
+                const material = entity as StandardMaterial;
+                return (<StandardMaterialPropertyGridComponent
+                    material={material}
+                    onSelectionChangeObservable={this.props.onSelectionChangeObservable}
+                    onPropertyChangedObservable={this.props.onPropertyChangedObservable} />);
+            }
+
+            if (className === "PBRMaterial") {
+                const material = entity as PBRMaterial;
+                return (<PBRMaterialPropertyGridComponent
+                    material={material}
+                    onSelectionChangeObservable={this.props.onSelectionChangeObservable}
+                    onPropertyChangedObservable={this.props.onPropertyChangedObservable} />);
+            }
+
+            if (className === "BackgroundMaterial") {
+                const material = entity as BackgroundMaterial;
+                return (<BackgroundMaterialPropertyGridComponent
+                    material={material}
+                    onSelectionChangeObservable={this.props.onSelectionChangeObservable}
+                    onPropertyChangedObservable={this.props.onPropertyChangedObservable} />);
+            }
+
+            if (className.indexOf("Material") !== -1) {
+                const material = entity as Material;
+                return (<MaterialPropertyGridComponent material={material} onPropertyChangedObservable={this.props.onPropertyChangedObservable} />);
+            }
+
+            if (className.indexOf("Texture") !== -1) {
+                const texture = entity as Texture;
+                return (<TexturePropertyGridComponent texture={texture} onPropertyChangedObservable={this.props.onPropertyChangedObservable} />);
+            }
+        } else if (entity.transformNodes) {
+            const scene = entity as Scene;
+            return (<ScenePropertyGridComponent scene={scene} onPropertyChangedObservable={this.props.onPropertyChangedObservable} />);
+        }
+
+        return null;
+    }
+}

+ 70 - 0
inspector/src/components/actionTabs/tabs/propertyGrids/cameras/arcRotateCameraPropertyGridComponent.tsx

@@ -0,0 +1,70 @@
+import * as React from "react";
+import { Observable, ArcRotateCamera } from "babylonjs";
+import { PropertyChangedEvent } from "../../../../propertyChangedEvent";
+import { CommonCameraPropertyGridComponent } from "./commonCameraPropertyGridComponent";
+import { LineContainerComponent } from "../../../lineContainerComponent";
+import { CheckBoxLineComponent } from "../../../lines/checkBoxLineComponent";
+import { FloatLineComponent } from "../../../lines/floatLineComponent";
+import { SliderLineComponent } from "../../../lines/sliderLineComponent";
+import { Vector3LineComponent } from "../../../lines/vector3LineComponent";
+
+interface IArcRotateCameraPropertyGridComponentProps {
+    camera: ArcRotateCamera,
+    onPropertyChangedObservable?: Observable<PropertyChangedEvent>
+}
+
+export class ArcRotateCameraPropertyGridComponent extends React.Component<IArcRotateCameraPropertyGridComponentProps> {
+    private _timerIntervalId: number;
+
+    constructor(props: IArcRotateCameraPropertyGridComponentProps) {
+        super(props);
+    }
+
+    componentWillMount() {
+        this._timerIntervalId = window.setInterval(() => this.forceUpdate(), 500);
+    }
+
+    componentWillUnmount() {
+        window.clearInterval(this._timerIntervalId);
+    }
+
+    render() {
+        const camera = this.props.camera;
+
+        return (
+            <div className="pane">
+                <CommonCameraPropertyGridComponent camera={camera} onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                <LineContainerComponent title="TRANSFORMS">
+                    <SliderLineComponent label="Alpha" target={camera} propertyName="alpha" minimum={0} maximum={2 * Math.PI} step={0.01} onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                    <SliderLineComponent label="Beta" target={camera} propertyName="beta" minimum={0} maximum={2 * Math.PI} step={0.01} onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                    <FloatLineComponent label="Radius" target={camera} propertyName="radius" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                </LineContainerComponent>
+                <LineContainerComponent title="CONTROLS" closed={true}>
+                    <FloatLineComponent label="Angular sensitivity X" target={camera} propertyName="angularSensibilityX" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                    <FloatLineComponent label="Angular sensitivity Y" target={camera} propertyName="angularSensibilityY" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                    <FloatLineComponent label="Panning sensitivity" target={camera} propertyName="panningSensibility" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                    <FloatLineComponent label="Pinch delta percentage" target={camera} propertyName="pinchDeltaPercentage" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                    <FloatLineComponent label="Wheel delta percentage" target={camera} propertyName="wheelDeltaPercentage" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                    <FloatLineComponent label="Speed" target={camera} propertyName="speed" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                </LineContainerComponent>
+                <LineContainerComponent title="COLLISIONS" closed={true}>
+                    <CheckBoxLineComponent label="Check collisions" target={camera} propertyName="checkCollisions" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                    <Vector3LineComponent label="Collision radius" target={camera} propertyName="collisionRadius" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                </LineContainerComponent>
+                <LineContainerComponent title="LIMITS" closed={true}>
+                    <FloatLineComponent label="Lower alpha limit" target={camera} propertyName="lowerAlphaLimit" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                    <FloatLineComponent label="Upper alpha limit" target={camera} propertyName="upperAlphaLimit" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                    <FloatLineComponent label="Lower beta limit" target={camera} propertyName="lowerBetaLimit" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                    <FloatLineComponent label="Upper beta limit" target={camera} propertyName="upperBetaLimit" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                    <FloatLineComponent label="Lower radius limit" target={camera} propertyName="lowerRadiusLimit" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                    <FloatLineComponent label="Upper radius limit" target={camera} propertyName="upperRadiusLimit" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                </LineContainerComponent>
+                <LineContainerComponent title="BEHAVIORS" closed={true}>
+                    <CheckBoxLineComponent label="Auto rotation" target={camera} propertyName="useAutoRotationBehavior" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                    <CheckBoxLineComponent label="Bouncing" target={camera} propertyName="useBouncingBehavior" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                    <CheckBoxLineComponent label="Framing" target={camera} propertyName="useFramingBehavior" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                </LineContainerComponent>
+            </div>
+        );
+    }
+}

+ 63 - 0
inspector/src/components/actionTabs/tabs/propertyGrids/cameras/commonCameraPropertyGridComponent.tsx

@@ -0,0 +1,63 @@
+import * as React from "react";
+import { Camera, Observable } from "babylonjs";
+import { PropertyChangedEvent } from "../../../../propertyChangedEvent";
+import { SliderLineComponent } from "../../../lines/sliderLineComponent";
+import { LineContainerComponent } from "../../../lineContainerComponent";
+import { FloatLineComponent } from "../../../lines/floatLineComponent";
+import { TextLineComponent } from "../../../lines/textLineComponent";
+import { OptionsLineComponent } from "../../../lines/optionsLineComponent";
+
+interface ICommonCameraPropertyGridComponentProps {
+    camera: Camera,
+    onPropertyChangedObservable?: Observable<PropertyChangedEvent>
+}
+
+export class CommonCameraPropertyGridComponent extends React.Component<ICommonCameraPropertyGridComponentProps, { mode: number }> {
+    constructor(props: ICommonCameraPropertyGridComponentProps) {
+        super(props);
+
+        this.state = { mode: this.props.camera.mode };
+    }
+
+    render() {
+        const camera = this.props.camera;
+
+        var modeOptions = [
+            { label: "Perspective", value: BABYLON.Camera.PERSPECTIVE_CAMERA },
+            { label: "Orthographic", value: BABYLON.Camera.ORTHOGRAPHIC_CAMERA }
+        ]
+
+        return (
+            <LineContainerComponent title="GENERAL">
+                <TextLineComponent label="ID" value={camera.id} />
+                <TextLineComponent label="Unique ID" value={camera.uniqueId.toString()} />
+                <TextLineComponent label="Class" value={camera.getClassName()} />
+                <FloatLineComponent label="Near plane" target={camera} propertyName="minZ" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                <FloatLineComponent label="Far plane" target={camera} propertyName="maxZ" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                <SliderLineComponent label="Inertia" target={camera} propertyName="inertia" minimum={0} maximum={1} step={0.01} onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                <OptionsLineComponent label="Mode" options={modeOptions} target={camera} propertyName="mode" onPropertyChangedObservable={this.props.onPropertyChangedObservable} onSelect={value => this.setState({ mode: value })} />
+                {
+                    camera.mode === BABYLON.Camera.PERSPECTIVE_CAMERA &&
+                    <SliderLineComponent label="Field of view" target={camera} propertyName="fov" minimum={0.1} maximum={Math.PI} step={0.1} onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                }
+                {
+                    camera.mode === BABYLON.Camera.ORTHOGRAPHIC_CAMERA &&
+                    <FloatLineComponent label="Left" target={camera} propertyName="orthoLeft" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                }
+                {
+                    camera.mode === BABYLON.Camera.ORTHOGRAPHIC_CAMERA &&
+                    <FloatLineComponent label="Top" target={camera} propertyName="orthoTop" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                }
+                {
+                    camera.mode === BABYLON.Camera.ORTHOGRAPHIC_CAMERA &&
+                    <FloatLineComponent label="Right" target={camera} propertyName="orthoRight" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                }
+                {
+                    camera.mode === BABYLON.Camera.ORTHOGRAPHIC_CAMERA &&
+                    <FloatLineComponent label="Bottom" target={camera} propertyName="orthoBottom" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                }
+
+            </LineContainerComponent>
+        );
+    }
+}

+ 52 - 0
inspector/src/components/actionTabs/tabs/propertyGrids/cameras/freeCameraPropertyGridComponent.tsx

@@ -0,0 +1,52 @@
+import * as React from "react";
+import { Observable, FreeCamera } from "babylonjs";
+import { CommonCameraPropertyGridComponent } from "./commonCameraPropertyGridComponent";
+import { PropertyChangedEvent } from "../../../../propertyChangedEvent";
+import { LineContainerComponent } from "../../../lineContainerComponent";
+import { Vector3LineComponent } from "../../../lines/vector3LineComponent";
+import { FloatLineComponent } from "../../../lines/floatLineComponent";
+import { CheckBoxLineComponent } from "../../../lines/checkBoxLineComponent";
+
+interface IFreeCameraPropertyGridComponentProps {
+    camera: FreeCamera,
+    onPropertyChangedObservable?: Observable<PropertyChangedEvent>
+}
+
+export class FreeCameraPropertyGridComponent extends React.Component<IFreeCameraPropertyGridComponentProps> {
+    private _timerIntervalId: number;
+
+    constructor(props: IFreeCameraPropertyGridComponentProps) {
+        super(props);
+    }
+
+    componentWillMount() {
+        this._timerIntervalId = window.setInterval(() => this.forceUpdate(), 500);
+    }
+
+    componentWillUnmount() {
+        window.clearInterval(this._timerIntervalId);
+    }
+
+    render() {
+        const camera = this.props.camera;
+
+        return (
+            <div className="pane">
+                <CommonCameraPropertyGridComponent camera={camera} onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                <LineContainerComponent title="TRANSFORMS">
+                    <Vector3LineComponent label="Position" target={camera} propertyName="position" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                </LineContainerComponent>
+                <LineContainerComponent title="CONTROLS" closed={true}>
+                    <FloatLineComponent label="Angular sensitivity" target={camera} propertyName="angularSensibility" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                    <FloatLineComponent label="Speed" target={camera} propertyName="speed" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                </LineContainerComponent>
+                <LineContainerComponent title="COLLISIONS" closed={true}>
+                    <CheckBoxLineComponent label="Check collisions" target={camera} propertyName="checkCollisions" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                    <CheckBoxLineComponent label="Apply gravity" target={camera} propertyName="applYGravity" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                    <Vector3LineComponent label="Ellipsoid" target={camera} propertyName="ellipsoid" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                    <Vector3LineComponent label="Ellipsoid offset" target={camera} propertyName="ellipsoidOffset" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                </LineContainerComponent>
+            </div>
+        );
+    }
+}

+ 102 - 0
inspector/src/components/actionTabs/tabs/propertyGrids/cameras/propertyGridTabComponent.tsx

@@ -0,0 +1,102 @@
+import * as React from "react";
+import { PaneComponent, IPaneComponentProps } from "../../../paneComponent";
+import { Mesh, TransformNode, Material, StandardMaterial, Texture, PBRMaterial, Scene, FreeCamera, ArcRotateCamera, HemisphericLight, PointLight } from "babylonjs";
+import { MaterialPropertyGridComponent } from "../materials/materialPropertyGridComponent";
+import { StandardMaterialPropertyGridComponent } from "../materials/standardMaterialPropertyGridComponent";
+import { TexturePropertyGridComponent } from "../texturePropertyGridComponent";
+import { PBRMaterialPropertyGridComponent } from "../materials/pbrMaterialPropertyGridComponent";
+import { ScenePropertyGridComponent } from "../scenePropertyGridComponent";
+import { HemisphericLightPropertyGridComponent } from "../lights/hemisphericLightPropertyGridComponent";
+import { PointLightPropertyGridComponent } from "../lights/pointLightPropertyGridComponent";
+import { FreeCameraPropertyGridComponent } from "./freeCameraPropertyGridComponent";
+import { ArcRotateCameraPropertyGridComponent } from "./arcRotateCameraPropertyGridComponent";
+import { MeshPropertyGridComponent } from "../meshes/meshPropertyGridComponent";
+import { TransformNodePropertyGridComponent } from "../meshes/transformNodePropertyGridComponent";
+
+export class PropertyGridTabComponent extends PaneComponent {
+    constructor(props: IPaneComponentProps) {
+        super(props);
+    }
+
+    render() {
+        const entity = this.props.selectedEntity;
+
+        if (!entity) {
+            return null;
+        }
+
+        if (entity.getClassName) {
+            const className = entity.getClassName();
+
+            if (className.indexOf("Mesh") !== -1) {
+                const mesh = entity as Mesh;
+                if (mesh.getTotalVertices() > 0) {
+                    return (<MeshPropertyGridComponent mesh={mesh}
+                        onSelectionChangeObservable={this.props.onSelectionChangeObservable}
+                        onPropertyChangedObservable={this.props.onPropertyChangedObservable} />);
+                }
+            }
+
+            if (className.indexOf("FreeCamera") !== -1) {
+                const freeCamera = entity as FreeCamera;
+                return (<FreeCameraPropertyGridComponent camera={freeCamera} onPropertyChangedObservable={this.props.onPropertyChangedObservable} />);
+            }
+
+            if (className.indexOf("ArcRotateCamera") !== -1) {
+                const arcRotateCamera = entity as ArcRotateCamera;
+                return (<ArcRotateCameraPropertyGridComponent camera={arcRotateCamera} onPropertyChangedObservable={this.props.onPropertyChangedObservable} />);
+            }
+
+            if (className === "HemisphericLight") {
+                const hemisphericLight = entity as HemisphericLight;
+                return (<HemisphericLightPropertyGridComponent
+                    light={hemisphericLight}
+                    onPropertyChangedObservable={this.props.onPropertyChangedObservable} />);
+            }
+
+            if (className === "PointLight") {
+                const pointLight = entity as PointLight;
+                return (<PointLightPropertyGridComponent
+                    light={pointLight}
+                    onPropertyChangedObservable={this.props.onPropertyChangedObservable} />);
+            }
+
+            if (className.indexOf("TransformNode") !== -1 || className.indexOf("Mesh") !== -1) {
+                const transformNode = entity as TransformNode;
+                return (<TransformNodePropertyGridComponent transformNode={transformNode} onPropertyChangedObservable={this.props.onPropertyChangedObservable} />);
+            }
+
+            if (className === "StandardMaterial") {
+                const material = entity as StandardMaterial;
+                return (<StandardMaterialPropertyGridComponent
+                    material={material}
+                    onSelectionChangeObservable={this.props.onSelectionChangeObservable}
+                    onPropertyChangedObservable={this.props.onPropertyChangedObservable} />);
+            }
+
+            if (className === "PBRMaterial") {
+                const material = entity as PBRMaterial;
+                return (<PBRMaterialPropertyGridComponent
+                    material={material}
+                    onSelectionChangeObservable={this.props.onSelectionChangeObservable}
+                    onPropertyChangedObservable={this.props.onPropertyChangedObservable} />);
+            }
+
+            if (className.indexOf("Material") !== -1) {
+                const material = entity as Material;
+                return (<MaterialPropertyGridComponent material={material} onPropertyChangedObservable={this.props.onPropertyChangedObservable} />);
+            }
+
+            if (className.indexOf("Texture") !== -1) {
+                const texture = entity as Texture;
+                return (<TexturePropertyGridComponent texture={texture} onPropertyChangedObservable={this.props.onPropertyChangedObservable} />);
+            }
+
+        } else if (entity.transformNodes) {
+            const scene = entity as Scene;
+            return (<ScenePropertyGridComponent scene={scene} onPropertyChangedObservable={this.props.onPropertyChangedObservable} />);
+        }
+
+        return null;
+    }
+}

+ 52 - 0
inspector/src/components/actionTabs/tabs/propertyGrids/fogPropertyGridComponent.tsx

@@ -0,0 +1,52 @@
+import * as React from "react";
+import { Observable, Scene } from "babylonjs";
+import { PropertyChangedEvent } from "../../../propertyChangedEvent";
+import { Color3LineComponent } from "../../lines/color3LineComponent";
+import { FloatLineComponent } from "../../lines/floatLineComponent";
+import { OptionsLineComponent } from "../../lines/optionsLineComponent";
+
+interface IFogPropertyGridComponentProps {
+    scene: Scene,
+    onPropertyChangedObservable?: Observable<PropertyChangedEvent>
+}
+
+export class FogPropertyGridComponent extends React.Component<IFogPropertyGridComponentProps, { mode: number }> {
+
+    constructor(props: IFogPropertyGridComponentProps) {
+        super(props);
+        this.state = { mode: 0 };
+    }
+
+    render() {
+        const scene = this.props.scene;
+
+        var fogModeOptions = [
+            { label: "None", value: BABYLON.Scene.FOGMODE_NONE },
+            { label: "Linear", value: BABYLON.Scene.FOGMODE_LINEAR },
+            { label: "Exp", value: BABYLON.Scene.FOGMODE_EXP },
+            { label: "Exp2", value: BABYLON.Scene.FOGMODE_EXP2 },
+        ]
+
+        return (
+            <div>
+                <OptionsLineComponent label="Fog mode" options={fogModeOptions} target={scene} propertyName="fogMode" onPropertyChangedObservable={this.props.onPropertyChangedObservable} onSelect={value => this.setState({ mode: value })} />
+                {
+                    this.state.mode !== BABYLON.Scene.FOGMODE_NONE &&
+                    <Color3LineComponent label="Fog color" target={scene} propertyName="fogColor" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                }
+                {
+                    (this.state.mode === BABYLON.Scene.FOGMODE_EXP || this.state.mode === BABYLON.Scene.FOGMODE_EXP2) &&
+                    <FloatLineComponent label="Fog density" target={scene} propertyName="fogDensity" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                }
+                {
+                    this.state.mode === BABYLON.Scene.FOGMODE_LINEAR &&
+                    <FloatLineComponent label="Fog start" target={scene} propertyName="fogStart" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                }
+                {
+                    this.state.mode === BABYLON.Scene.FOGMODE_LINEAR &&
+                    <FloatLineComponent label="Fog end" target={scene} propertyName="fogEnd" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                }
+            </div>
+        );
+    }
+}

+ 84 - 0
inspector/src/components/actionTabs/tabs/propertyGrids/gridPropertyGridComponent.tsx

@@ -0,0 +1,84 @@
+import * as React from "react";
+import { Scene, AbstractMesh } from "babylonjs";
+import { CheckBoxLineComponent } from "../../lines/checkBoxLineComponent";
+
+interface IGridPropertyGridComponentProps {
+    scene: Scene
+}
+
+export class GridPropertyGridComponent extends React.Component<IGridPropertyGridComponentProps, { isEnabled: boolean }> {
+    private _gridMesh: AbstractMesh;
+
+    constructor(props: IGridPropertyGridComponentProps) {
+        super(props);
+        this.state = { isEnabled: false };
+    }
+
+    componentWillMount() {
+        const scene = BABYLON.UtilityLayerRenderer.DefaultKeepDepthUtilityLayer.utilityLayerScene;
+
+        for (var mesh of scene.meshes) {
+            if (mesh.metadata && mesh.metadata.isInspectorGrid) {
+                this._gridMesh = mesh;
+                this.setState({ isEnabled: true });
+                return;
+            }
+        }
+    }
+
+    componentWillUnmount() {
+    }
+
+    addOrRemoveGrid() {
+        const scene = BABYLON.UtilityLayerRenderer.DefaultKeepDepthUtilityLayer.utilityLayerScene;
+
+        if (!(BABYLON as any).GridMaterial) {
+            this.setState({ isEnabled: true });
+            BABYLON.Tools.LoadScript("https://preview.babylonjs.com/materialsLibrary/babylonjs.materials.min.js", () => {
+                this.addOrRemoveGrid();
+            });
+            return;
+        }
+
+        if (!this._gridMesh) {
+            var extend = this.props.scene.getWorldExtends();
+            var width = extend.max.x - extend.min.x;
+            var depth = extend.max.z - extend.min.z;
+
+            this._gridMesh = BABYLON.Mesh.CreateGround("grid", width, depth, 1, scene);
+            if (!this._gridMesh.metadata) {
+                this._gridMesh.metadata = {}
+            }
+            this._gridMesh.metadata.isInspectorGrid = true;
+            this._gridMesh.isPickable = false;
+
+            var groundMaterial = new (BABYLON as any).GridMaterial("GridMaterial", scene);
+            groundMaterial.majorUnitFrequency = width / 10;
+            groundMaterial.minorUnitVisibility = 0.3;
+            groundMaterial.gridRatio = 1;
+            groundMaterial.backFaceCulling = false;
+            groundMaterial.mainColor = new BABYLON.Color3(1, 1, 1);
+            groundMaterial.lineColor = new BABYLON.Color3(1.0, 1.0, 1.0);
+            groundMaterial.opacity = 0.8;
+            groundMaterial.zOffset = 1.0;
+            groundMaterial.opacityTexture = new BABYLON.Texture("https://assets.babylonjs.com/environments/backgroundGround.png", scene);
+
+            this._gridMesh.material = groundMaterial;
+
+            this.setState({ isEnabled: true });
+            return;
+        }
+
+        this.setState({ isEnabled: !this.state.isEnabled });
+        this._gridMesh.setEnabled(!this._gridMesh.isEnabled());
+    }
+
+    render() {
+
+        return (
+            <div>
+                <CheckBoxLineComponent label="Render grid" isSelected={() => this.state.isEnabled} onSelect={() => this.addOrRemoveGrid()} />
+            </div>
+        );
+    }
+}

+ 30 - 0
inspector/src/components/actionTabs/tabs/propertyGrids/lights/commonLightPropertyGridComponent.tsx

@@ -0,0 +1,30 @@
+import * as React from "react";
+import { Light, Observable } from "babylonjs";
+import { PropertyChangedEvent } from "../../../../propertyChangedEvent";
+import { LineContainerComponent } from "../../../lineContainerComponent";
+import { FloatLineComponent } from "../../../lines/floatLineComponent";
+import { TextLineComponent } from "../../../lines/textLineComponent";
+
+interface ICommonLightPropertyGridComponentProps {
+    light: Light,
+    onPropertyChangedObservable?: Observable<PropertyChangedEvent>
+}
+
+export class CommonLightPropertyGridComponent extends React.Component<ICommonLightPropertyGridComponentProps> {
+    constructor(props: ICommonLightPropertyGridComponentProps) {
+        super(props);
+    }
+
+    render() {
+        const light = this.props.light;
+
+        return (
+            <LineContainerComponent title="GENERAL">
+                <TextLineComponent label="ID" value={light.id} />
+                <TextLineComponent label="Unique ID" value={light.uniqueId.toString()} />
+                <TextLineComponent label="Class" value={light.getClassName()} />
+                <FloatLineComponent label="Intensity" target={light} propertyName="intensity" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+            </LineContainerComponent>
+        );
+    }
+}

+ 29 - 0
inspector/src/components/actionTabs/tabs/propertyGrids/lights/commonShadowLightPropertyGridComponent.tsx

@@ -0,0 +1,29 @@
+import * as React from "react";
+import { Observable, IShadowLight } from "babylonjs";
+import { PropertyChangedEvent } from "../../../../propertyChangedEvent";
+import { LineContainerComponent } from "../../../lineContainerComponent";
+import { CheckBoxLineComponent } from "../../../lines/checkBoxLineComponent";
+import { FloatLineComponent } from "../../../lines/floatLineComponent";
+
+interface ICommonShadowLightPropertyGridComponentProps {
+    light: IShadowLight,
+    onPropertyChangedObservable?: Observable<PropertyChangedEvent>
+}
+
+export class CommonShadowLightPropertyGridComponent extends React.Component<ICommonShadowLightPropertyGridComponentProps> {
+    constructor(props: ICommonShadowLightPropertyGridComponentProps) {
+        super(props);
+    }
+
+    render() {
+        const light = this.props.light;
+
+        return (
+            <LineContainerComponent title="SHADOWS">
+                <CheckBoxLineComponent label="Shadows enabled" target={light} propertyName="shadowEnabled" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                <FloatLineComponent label="Shadows near plane" target={light} propertyName="shadowMinZ" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                <FloatLineComponent label="Shadows far plane" target={light} propertyName="shadowMaxZ" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+            </LineContainerComponent>
+        );
+    }
+}

+ 36 - 0
inspector/src/components/actionTabs/tabs/propertyGrids/lights/directionalLightPropertyGridComponent.tsx

@@ -0,0 +1,36 @@
+import * as React from "react";
+import { Observable, DirectionalLight } from "babylonjs";
+import { PropertyChangedEvent } from "../../../../propertyChangedEvent";
+import { CommonLightPropertyGridComponent } from "./commonLightPropertyGridComponent";
+import { LineContainerComponent } from "../../../lineContainerComponent";
+import { Color3LineComponent } from "../../../lines/color3LineComponent";
+import { Vector3LineComponent } from "../../../lines/vector3LineComponent";
+import { CommonShadowLightPropertyGridComponent } from "./commonShadowLightPropertyGridComponent";
+
+interface IDirectionalLightPropertyGridComponentProps {
+    light: DirectionalLight,
+    onPropertyChangedObservable?: Observable<PropertyChangedEvent>
+}
+
+export class DirectionalLightPropertyGridComponent extends React.Component<IDirectionalLightPropertyGridComponentProps> {
+    constructor(props: IDirectionalLightPropertyGridComponentProps) {
+        super(props);
+    }
+
+    render() {
+        const light = this.props.light;
+
+        return (
+            <div className="pane">
+                <CommonLightPropertyGridComponent light={light} onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                <LineContainerComponent title="SETUP">
+                    <Color3LineComponent label="Diffuse" target={light} propertyName="diffuse" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                    <Color3LineComponent label="Specular" target={light} propertyName="specular" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                    <Vector3LineComponent label="Position" target={light} propertyName="position" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                    <Vector3LineComponent label="Direction" target={light} propertyName="direction" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                </LineContainerComponent>
+                <CommonShadowLightPropertyGridComponent light={light} onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+            </div>
+        );
+    }
+}

+ 33 - 0
inspector/src/components/actionTabs/tabs/propertyGrids/lights/hemisphericLightPropertyGridComponent.tsx

@@ -0,0 +1,33 @@
+import * as React from "react";
+import { Observable, HemisphericLight } from "babylonjs";
+import { PropertyChangedEvent } from "../../../../propertyChangedEvent";
+import { CommonLightPropertyGridComponent } from "./commonLightPropertyGridComponent";
+import { LineContainerComponent } from "../../../lineContainerComponent";
+import { Color3LineComponent } from "../../../lines/color3LineComponent";
+import { Vector3LineComponent } from "../../../lines/vector3LineComponent";
+
+interface IHemisphericLightPropertyGridComponentProps {
+    light: HemisphericLight,
+    onPropertyChangedObservable?: Observable<PropertyChangedEvent>
+}
+
+export class HemisphericLightPropertyGridComponent extends React.Component<IHemisphericLightPropertyGridComponentProps> {
+    constructor(props: IHemisphericLightPropertyGridComponentProps) {
+        super(props);
+    }
+
+    render() {
+        const light = this.props.light;
+
+        return (
+            <div className="pane">
+                <CommonLightPropertyGridComponent light={light} onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                <LineContainerComponent title="SETUP">
+                    <Color3LineComponent label="Diffuse" target={light} propertyName="diffuse" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                    <Color3LineComponent label="Ground" target={light} propertyName="groundColor" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                    <Vector3LineComponent label="Direction" target={light} propertyName="direction" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                </LineContainerComponent>
+            </div>
+        );
+    }
+}

+ 35 - 0
inspector/src/components/actionTabs/tabs/propertyGrids/lights/pointLightPropertyGridComponent.tsx

@@ -0,0 +1,35 @@
+import * as React from "react";
+import { Observable, PointLight } from "babylonjs";
+import { PropertyChangedEvent } from "../../../../propertyChangedEvent";
+import { CommonLightPropertyGridComponent } from "./commonLightPropertyGridComponent";
+import { LineContainerComponent } from "../../../lineContainerComponent";
+import { Color3LineComponent } from "../../../lines/color3LineComponent";
+import { Vector3LineComponent } from "../../../lines/vector3LineComponent";
+import { CommonShadowLightPropertyGridComponent } from "./commonShadowLightPropertyGridComponent";
+
+interface IPointLightPropertyGridComponentProps {
+    light: PointLight,
+    onPropertyChangedObservable?: Observable<PropertyChangedEvent>
+}
+
+export class PointLightPropertyGridComponent extends React.Component<IPointLightPropertyGridComponentProps> {
+    constructor(props: IPointLightPropertyGridComponentProps) {
+        super(props);
+    }
+
+    render() {
+        const light = this.props.light;
+
+        return (
+            <div className="pane">
+                <CommonLightPropertyGridComponent light={light} onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                <LineContainerComponent title="SETUP">
+                    <Color3LineComponent label="Diffuse" target={light} propertyName="diffuse" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                    <Color3LineComponent label="Specular" target={light} propertyName="specular" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                    <Vector3LineComponent label="Position" target={light} propertyName="position" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                </LineContainerComponent>
+                <CommonShadowLightPropertyGridComponent light={light} onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+            </div>
+        );
+    }
+}

+ 39 - 0
inspector/src/components/actionTabs/tabs/propertyGrids/lights/spotLightPropertyGridComponent.tsx

@@ -0,0 +1,39 @@
+import * as React from "react";
+import { Observable, SpotLight } from "babylonjs";
+import { PropertyChangedEvent } from "../../../../propertyChangedEvent";
+import { CommonLightPropertyGridComponent } from "./commonLightPropertyGridComponent";
+import { LineContainerComponent } from "../../../lineContainerComponent";
+import { Color3LineComponent } from "../../../lines/color3LineComponent";
+import { Vector3LineComponent } from "../../../lines/vector3LineComponent";
+import { FloatLineComponent } from "../../../lines/floatLineComponent";
+import { CommonShadowLightPropertyGridComponent } from "./commonShadowLightPropertyGridComponent";
+
+interface ISpotLightPropertyGridComponentProps {
+    light: SpotLight,
+    onPropertyChangedObservable?: Observable<PropertyChangedEvent>
+}
+
+export class SpotLightPropertyGridComponent extends React.Component<ISpotLightPropertyGridComponentProps> {
+    constructor(props: ISpotLightPropertyGridComponentProps) {
+        super(props);
+    }
+
+    render() {
+        const light = this.props.light;
+
+        return (
+            <div className="pane">
+                <CommonLightPropertyGridComponent light={light} onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                <LineContainerComponent title="SETUP">
+                    <Color3LineComponent label="Diffuse" target={light} propertyName="diffuse" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                    <Color3LineComponent label="Specular" target={light} propertyName="specular" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                    <Vector3LineComponent label="Position" target={light} propertyName="position" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                    <Vector3LineComponent label="Direction" target={light} propertyName="direction" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                    <FloatLineComponent label="Angle" target={light} propertyName="angle" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                    <FloatLineComponent label="Exponent" target={light} propertyName="exponent" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                </LineContainerComponent>
+                <CommonShadowLightPropertyGridComponent light={light} onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+            </div>
+        );
+    }
+}

+ 60 - 0
inspector/src/components/actionTabs/tabs/propertyGrids/materials/backgroundMaterialPropertyGridComponent.tsx

@@ -0,0 +1,60 @@
+import * as React from "react";
+import { Observable, BackgroundMaterial, BaseTexture } from "babylonjs";
+import { PropertyChangedEvent } from "../../../../propertyChangedEvent";
+import { LineContainerComponent } from "../../../lineContainerComponent";
+import { Color3LineComponent } from "../../../lines/color3LineComponent";
+import { CheckBoxLineComponent } from "../../../lines/checkBoxLineComponent";
+import { SliderLineComponent } from "../../../lines/sliderLineComponent";
+import { CommonMaterialPropertyGridComponent } from "./commonMaterialPropertyGridComponent";
+import { TextureLinkLineComponent } from "../../../lines/textureLinkLineComponent";
+
+interface IBackgroundMaterialPropertyGridComponentProps {
+    material: BackgroundMaterial,
+    onSelectionChangeObservable?: Observable<any>,
+    onPropertyChangedObservable?: Observable<PropertyChangedEvent>
+}
+
+export class BackgroundMaterialPropertyGridComponent extends React.Component<IBackgroundMaterialPropertyGridComponentProps> {
+    constructor(props: IBackgroundMaterialPropertyGridComponentProps) {
+        super(props);
+    }
+
+    renderTextures() {
+        const material = this.props.material;
+
+        const onDebugSelectionChangeObservable = new BABYLON.Observable<BaseTexture>();
+
+        return (
+            <LineContainerComponent title="TEXTURES">
+                <TextureLinkLineComponent label="Diffuse" texture={material.diffuseTexture} material={material} onSelectionChangeObservable={this.props.onSelectionChangeObservable} onDebugSelectionChangeObservable={onDebugSelectionChangeObservable} />
+                <TextureLinkLineComponent label="Reflection" texture={material.reflectionTexture} material={material} onSelectionChangeObservable={this.props.onSelectionChangeObservable} onDebugSelectionChangeObservable={onDebugSelectionChangeObservable} />
+                {
+                    material.reflectionTexture &&
+                    <SliderLineComponent label="Reflection blur" target={material} propertyName="reflectionBlur" minimum={0} maximum={1} step={0.01} onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                }
+            </LineContainerComponent>
+        )
+    }
+
+    render() {
+        const material = this.props.material;
+
+        return (
+            <div className="pane">
+                <CommonMaterialPropertyGridComponent material={material} onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                <LineContainerComponent title="LIGHTING & COLORS">
+                    <Color3LineComponent label="Primary" target={material} propertyName="primaryColor" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                    <SliderLineComponent label="Shadow level" target={material} propertyName="primaryColorShadowLevel" minimum={0} maximum={1} step={0.01} onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                    <SliderLineComponent label="Highlight level" target={material} propertyName="primaryColorHighlightLevel" minimum={0} maximum={1} step={0.01} onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                </LineContainerComponent>
+                {this.renderTextures()}
+                <LineContainerComponent title="RENDERING" closed={true}>
+                    <CheckBoxLineComponent label="Enable noise" target={material} propertyName="enableNoise" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                    <CheckBoxLineComponent label="Opacity fresnel" target={material} propertyName="opacityFresnel" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                    <CheckBoxLineComponent label="Reflection fresnel" target={material} propertyName="reflectionFresnel" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                    <SliderLineComponent label="Reflection amount" target={material} propertyName="reflectionAmount" minimum={0} maximum={1} step={0.01} onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                </LineContainerComponent>
+            </div>
+        );
+    }
+}

+ 45 - 0
inspector/src/components/actionTabs/tabs/propertyGrids/materials/commonMaterialPropertyGridComponent.tsx

@@ -0,0 +1,45 @@
+import * as React from "react";
+import { Material, Observable } from "babylonjs";
+import { PropertyChangedEvent } from "../../../../propertyChangedEvent";
+import { CheckBoxLineComponent } from "../../../lines/checkBoxLineComponent";
+import { SliderLineComponent } from "../../../lines/sliderLineComponent";
+import { LineContainerComponent } from "../../../lineContainerComponent";
+import { TextLineComponent } from "../../../lines/textLineComponent";
+import { OptionsLineComponent } from "../../../lines/optionsLineComponent";
+
+interface ICommonMaterialPropertyGridComponentProps {
+    material: Material,
+    onPropertyChangedObservable?: Observable<PropertyChangedEvent>
+}
+
+export class CommonMaterialPropertyGridComponent extends React.Component<ICommonMaterialPropertyGridComponentProps> {
+    constructor(props: ICommonMaterialPropertyGridComponentProps) {
+        super(props);
+    }
+
+    render() {
+        const material = this.props.material;
+
+        var orientationOptions = [
+            { label: "Clockwise", value: BABYLON.Material.ClockWiseSideOrientation },
+            { label: "Counterclockwise", value: BABYLON.Material.CounterClockWiseSideOrientation }
+        ]
+
+        return (
+            <LineContainerComponent title="GENERAL">
+                <TextLineComponent label="ID" value={material.id} />
+                <TextLineComponent label="Unique ID" value={material.uniqueId.toString()} />
+                <TextLineComponent label="Class" value={material.getClassName()} />
+                <CheckBoxLineComponent label="Backface culling" target={material} propertyName="backFaceCulling" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                <OptionsLineComponent label="Orientation" options={orientationOptions} target={material} propertyName="sideOrientation" onPropertyChangedObservable={this.props.onPropertyChangedObservable} onSelect={value => this.setState({ mode: value })} />
+                <CheckBoxLineComponent label="Disable lighting" target={material} propertyName="disableLighting" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                <CheckBoxLineComponent label="Disable depth write" target={material} propertyName="disableDepthWrite" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                <CheckBoxLineComponent label="Need depth pre-pass" target={material} propertyName="needDepthPrePass" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                <CheckBoxLineComponent label="Wireframe" target={material} propertyName="wireframe" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                <CheckBoxLineComponent label="Point cloud" target={material} propertyName="pointsCloud" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                <SliderLineComponent label="Point size" target={material} propertyName="pointSize" minimum={0} maximum={100} step={0.1} onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                <SliderLineComponent label="Z-offset" target={material} propertyName="zOffset" minimum={-10} maximum={10} step={0.1} onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+            </LineContainerComponent>
+        );
+    }
+}

+ 25 - 0
inspector/src/components/actionTabs/tabs/propertyGrids/materials/materialPropertyGridComponent.tsx

@@ -0,0 +1,25 @@
+import * as React from "react";
+import { Material, Observable } from "babylonjs";
+import { PropertyChangedEvent } from "../../../../propertyChangedEvent";
+import { CommonMaterialPropertyGridComponent } from "./commonMaterialPropertyGridComponent";
+
+interface IMaterialPropertyGridComponentProps {
+    material: Material,
+    onPropertyChangedObservable?: Observable<PropertyChangedEvent>
+}
+
+export class MaterialPropertyGridComponent extends React.Component<IMaterialPropertyGridComponentProps> {
+    constructor(props: IMaterialPropertyGridComponentProps) {
+        super(props);
+    }
+
+    render() {
+        const material = this.props.material;
+
+        return (
+            <div className="pane">
+                <CommonMaterialPropertyGridComponent material={material} onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+            </div>
+        );
+    }
+}

+ 89 - 0
inspector/src/components/actionTabs/tabs/propertyGrids/materials/pbrMaterialPropertyGridComponent.tsx

@@ -0,0 +1,89 @@
+import * as React from "react";
+import { Observable, PBRMaterial, BaseTexture } from "babylonjs";
+import { PropertyChangedEvent } from "../../../../propertyChangedEvent";
+import { LineContainerComponent } from "../../../lineContainerComponent";
+import { Color3LineComponent } from "../../../lines/color3LineComponent";
+import { CheckBoxLineComponent } from "../../../lines/checkBoxLineComponent";
+import { SliderLineComponent } from "../../../lines/sliderLineComponent";
+import { CommonMaterialPropertyGridComponent } from "./commonMaterialPropertyGridComponent";
+import { TextureLinkLineComponent } from "../../../lines/textureLinkLineComponent";
+
+interface IPBRMaterialPropertyGridComponentProps {
+    material: PBRMaterial,
+    onSelectionChangeObservable?: Observable<any>,
+    onPropertyChangedObservable?: Observable<PropertyChangedEvent>
+}
+
+export class PBRMaterialPropertyGridComponent extends React.Component<IPBRMaterialPropertyGridComponentProps> {
+    constructor(props: IPBRMaterialPropertyGridComponentProps) {
+        super(props);
+    }
+
+    renderTextures() {
+        const material = this.props.material;
+
+        if (material.getActiveTextures().length === 0) {
+            return null;
+        }
+
+        const onDebugSelectionChangeObservable = new BABYLON.Observable<BaseTexture>();
+
+        return (
+            <LineContainerComponent title="TEXTURES">
+                <TextureLinkLineComponent label="Albedo" texture={material.albedoTexture} material={material} onSelectionChangeObservable={this.props.onSelectionChangeObservable} onDebugSelectionChangeObservable={onDebugSelectionChangeObservable} />
+                <TextureLinkLineComponent label="Metallic" texture={material.metallicTexture} material={material} onSelectionChangeObservable={this.props.onSelectionChangeObservable} onDebugSelectionChangeObservable={onDebugSelectionChangeObservable} />
+                <TextureLinkLineComponent label="Reflection" texture={material.reflectionTexture} material={material} onSelectionChangeObservable={this.props.onSelectionChangeObservable} onDebugSelectionChangeObservable={onDebugSelectionChangeObservable} />
+                <TextureLinkLineComponent label="Refraction" texture={material.refractionTexture} material={material} onSelectionChangeObservable={this.props.onSelectionChangeObservable} onDebugSelectionChangeObservable={onDebugSelectionChangeObservable} />
+                <TextureLinkLineComponent label="Micro-surface" texture={material.microSurfaceTexture} material={material} onSelectionChangeObservable={this.props.onSelectionChangeObservable} onDebugSelectionChangeObservable={onDebugSelectionChangeObservable} />
+                <TextureLinkLineComponent label="Bump" texture={material.bumpTexture} material={material} onSelectionChangeObservable={this.props.onSelectionChangeObservable} onDebugSelectionChangeObservable={onDebugSelectionChangeObservable} />
+                {
+                    material.bumpTexture &&
+                    <SliderLineComponent label="Bump strength" target={material.bumpTexture} propertyName="level" minimum={0} maximum={2} step={0.01} onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                }
+                <TextureLinkLineComponent label="Emissive" texture={material.emissiveTexture} material={material} onSelectionChangeObservable={this.props.onSelectionChangeObservable} onDebugSelectionChangeObservable={onDebugSelectionChangeObservable} />
+                <TextureLinkLineComponent label="Opacity" texture={material.opacityTexture} material={material} onSelectionChangeObservable={this.props.onSelectionChangeObservable} onDebugSelectionChangeObservable={onDebugSelectionChangeObservable} />
+                <TextureLinkLineComponent label="Ambient" texture={material.ambientTexture} material={material} onSelectionChangeObservable={this.props.onSelectionChangeObservable} onDebugSelectionChangeObservable={onDebugSelectionChangeObservable} />
+                {
+                    material.ambientTexture &&
+                    <SliderLineComponent label="Ambient strength" target={material} propertyName="ambientTextureStrength" minimum={0} maximum={1} step={0.01} onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                }
+                <TextureLinkLineComponent label="Lightmap" texture={material.lightmapTexture} material={material} onSelectionChangeObservable={this.props.onSelectionChangeObservable} onDebugSelectionChangeObservable={onDebugSelectionChangeObservable} />
+            </LineContainerComponent>
+        )
+    }
+
+    render() {
+        const material = this.props.material;
+
+        return (
+            <div className="pane">
+                <CommonMaterialPropertyGridComponent material={material} onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                <LineContainerComponent 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} />
+                    <Color3LineComponent label="Emissive" target={material} propertyName="emissiveColor" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                    <Color3LineComponent label="Ambient" target={material} propertyName="ambientColor" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                    <SliderLineComponent label="Alpha" target={material} propertyName="alpha" minimum={0} maximum={1} step={0.01} onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                </LineContainerComponent>
+                {this.renderTextures()}
+                <LineContainerComponent title="RENDERING" closed={true}>
+                    <CheckBoxLineComponent label="Alpha from albedo" target={material} propertyName="useAlphaFromAlbedoTexture" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                    <CheckBoxLineComponent label="Ambient in grayscale" target={material} propertyName="useAmbientInGrayScale" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                    <CheckBoxLineComponent label="Radiance over alpha" target={material} propertyName="useRadianceOverAlpha" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                    <CheckBoxLineComponent label="Link refraction with transparency" target={material} propertyName="linkRefractionWithTransparency" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                    <CheckBoxLineComponent label="Micro-surface from ref. map alpha" target={material} propertyName="useMicroSurfaceFromReflectivityMapAlpha" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                    <CheckBoxLineComponent label="Specular over alpha" target={material} propertyName="useSpecularOverAlpha" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                    <CheckBoxLineComponent label="Specular anti-aliasing" target={material} propertyName="enableSpecularAntiAliasing" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                </LineContainerComponent>
+                <LineContainerComponent title="ADVANCED" closed={true}>
+                    <SliderLineComponent label="Metallic" target={material} propertyName="metallic" 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 intensity" target={material} propertyName="specularIntensity" minimum={0} maximum={1} step={0.01} onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                    <CheckBoxLineComponent label="Radiance occlusion" target={material} propertyName="useRadianceOcclusion" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                    <CheckBoxLineComponent label="Horizon occlusion " target={material} propertyName="useHorizonOcclusion" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                    <CheckBoxLineComponent label="Unlit" target={material} propertyName="unlit" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                </LineContainerComponent>
+            </div>
+        );
+    }
+}

+ 71 - 0
inspector/src/components/actionTabs/tabs/propertyGrids/materials/standardMaterialPropertyGridComponent.tsx

@@ -0,0 +1,71 @@
+import * as React from "react";
+import { Observable, StandardMaterial, BaseTexture } from "babylonjs";
+import { PropertyChangedEvent } from "../../../../propertyChangedEvent";
+import { LineContainerComponent } from "../../../lineContainerComponent";
+import { Color3LineComponent } from "../../../lines/color3LineComponent";
+import { SliderLineComponent } from "../../../lines/sliderLineComponent";
+import { CommonMaterialPropertyGridComponent } from "./commonMaterialPropertyGridComponent";
+import { TextureLinkLineComponent } from "../../../lines/textureLinkLineComponent";
+
+interface IStandardMaterialPropertyGridComponentProps {
+    material: StandardMaterial,
+    onSelectionChangeObservable?: Observable<any>,
+    onPropertyChangedObservable?: Observable<PropertyChangedEvent>
+}
+
+export class StandardMaterialPropertyGridComponent extends React.Component<IStandardMaterialPropertyGridComponentProps> {
+    constructor(props: IStandardMaterialPropertyGridComponentProps) {
+        super(props);
+    }
+
+    renderTextures() {
+        const material = this.props.material;
+
+        if (material.getActiveTextures().length === 0) {
+            return null;
+        }
+
+        const onDebugSelectionChangeObservable = new BABYLON.Observable<BaseTexture>();
+
+        return (
+            <LineContainerComponent title="TEXTURES">
+                <TextureLinkLineComponent label="Diffuse" texture={material.diffuseTexture} material={material} onSelectionChangeObservable={this.props.onSelectionChangeObservable} onDebugSelectionChangeObservable={onDebugSelectionChangeObservable} />
+                {
+                    material.diffuseTexture &&
+                    <SliderLineComponent label="Diffuse level" target={material.diffuseTexture} propertyName="level" minimum={0} maximum={2} step={0.01} onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                }
+                <TextureLinkLineComponent label="Specular" texture={material.specularTexture} material={material} onSelectionChangeObservable={this.props.onSelectionChangeObservable} onDebugSelectionChangeObservable={onDebugSelectionChangeObservable} />
+                <TextureLinkLineComponent label="Reflection" texture={material.reflectionTexture} material={material} onSelectionChangeObservable={this.props.onSelectionChangeObservable} onDebugSelectionChangeObservable={onDebugSelectionChangeObservable} />
+                <TextureLinkLineComponent label="Refraction" texture={material.refractionTexture} material={material} onSelectionChangeObservable={this.props.onSelectionChangeObservable} onDebugSelectionChangeObservable={onDebugSelectionChangeObservable} />
+                <TextureLinkLineComponent label="Emissive" texture={material.emissiveTexture} material={material} onSelectionChangeObservable={this.props.onSelectionChangeObservable} onDebugSelectionChangeObservable={onDebugSelectionChangeObservable} />
+                <TextureLinkLineComponent label="Bump" texture={material.bumpTexture} material={material} onSelectionChangeObservable={this.props.onSelectionChangeObservable} onDebugSelectionChangeObservable={onDebugSelectionChangeObservable} />
+                {
+                    material.bumpTexture &&
+                    <SliderLineComponent label="Bump level" target={material.bumpTexture} propertyName="level" minimum={0} maximum={2} step={0.01} onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                }
+                <TextureLinkLineComponent label="Opacity" texture={material.opacityTexture} material={material} onSelectionChangeObservable={this.props.onSelectionChangeObservable} onDebugSelectionChangeObservable={onDebugSelectionChangeObservable} />
+                <TextureLinkLineComponent label="Ambient" texture={material.ambientTexture} material={material} onSelectionChangeObservable={this.props.onSelectionChangeObservable} onDebugSelectionChangeObservable={onDebugSelectionChangeObservable} />
+                <TextureLinkLineComponent label="Lightmap" texture={material.lightmapTexture} material={material} onSelectionChangeObservable={this.props.onSelectionChangeObservable} onDebugSelectionChangeObservable={onDebugSelectionChangeObservable} />
+            </LineContainerComponent>
+        )
+    }
+
+    render() {
+        const material = this.props.material;
+
+        return (
+            <div className="pane">
+                <CommonMaterialPropertyGridComponent material={material} onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                <LineContainerComponent title="LIGHTING & COLORS">
+                    <Color3LineComponent label="Diffuse" target={material} propertyName="diffuseColor" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                    <Color3LineComponent label="Specular" target={material} propertyName="specularColor" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                    <SliderLineComponent label="Specular power" target={material} propertyName="specularPower" minimum={0} maximum={128} step={0.1} 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} />
+                    <SliderLineComponent label="Alpha" target={material} propertyName="alpha" minimum={0} maximum={1} step={0.01} onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                </LineContainerComponent>
+                {this.renderTextures()}
+            </div>
+        );
+    }
+}

+ 136 - 0
inspector/src/components/actionTabs/tabs/propertyGrids/meshes/meshPropertyGridComponent.tsx

@@ -0,0 +1,136 @@
+import * as React from "react";
+import { Mesh, Observable } from "babylonjs";
+import { PropertyChangedEvent } from "../../../../propertyChangedEvent";
+import { LineContainerComponent } from "../../../lineContainerComponent";
+import { TextLineComponent } from "../../../lines/textLineComponent";
+import { CheckBoxLineComponent } from "../../../lines/checkBoxLineComponent";
+import { Vector3LineComponent } from "../../../lines/vector3LineComponent";
+import { SliderLineComponent } from "../../../lines/sliderLineComponent";
+
+interface IMeshPropertyGridComponentProps {
+    mesh: Mesh,
+    onSelectionChangeObservable?: Observable<any>,
+    onPropertyChangedObservable?: Observable<PropertyChangedEvent>
+}
+
+export class MeshPropertyGridComponent extends React.Component<IMeshPropertyGridComponentProps, { paintNormals: boolean }> {
+    constructor(props: IMeshPropertyGridComponentProps) {
+        super(props);
+
+        this.state = { paintNormals: false }
+    }
+
+    paintNormals() {
+        const mesh = this.props.mesh;
+        const scene = mesh.getScene();
+        if (!mesh.material) {
+            return;
+        }
+
+        if (mesh.material.getClassName() === "NormalMaterial") {
+            mesh.material.dispose();
+
+            mesh.material = mesh.metadata.originalMaterial;
+            mesh.metadata.originalMaterial = null;
+            this.setState({ paintNormals: false });
+        } else {
+
+            if (!(BABYLON as any).NormalMaterial) {
+                this.setState({ paintNormals: true });
+                BABYLON.Tools.LoadScript("https://preview.babylonjs.com/materialsLibrary/babylonjs.materials.js", () => {
+                    this.paintNormals();
+                });
+                return;
+            }
+
+            if (!mesh.metadata) {
+                mesh.metadata = {};
+            }
+
+            mesh.metadata.originalMaterial = mesh.material;
+            const normalMaterial = new (BABYLON as any).NormalMaterial("normalMaterial", scene);
+            normalMaterial.disableLighting = true;
+            normalMaterial.sideOrientation = mesh.material.sideOrientation;
+            normalMaterial.metadata = { hidden: true };
+            mesh.material = normalMaterial;
+            this.setState({ paintNormals: true });
+        }
+    }
+
+    onMaterialLink() {
+        if (!this.props.onSelectionChangeObservable) {
+            return;
+        }
+
+        const mesh = this.props.mesh;
+        this.props.onSelectionChangeObservable.notifyObservers(mesh.material)
+    }
+
+    render() {
+        const mesh = this.props.mesh;
+        const scene = mesh.getScene();
+
+        const paintNormals = mesh.material != null && mesh.material.getClassName() === "NormalMaterial";
+
+        return (
+            <div className="pane">
+                <LineContainerComponent title="GENERAL">
+                    <TextLineComponent label="ID" value={mesh.id} />
+                    <TextLineComponent label="Unique ID" value={mesh.uniqueId.toString()} />
+                    <TextLineComponent label="Class" value={mesh.getClassName()} />
+                    <TextLineComponent label="Vertices" value={mesh.getTotalVertices().toString()} />
+                    <TextLineComponent label="Faces" value={(mesh.getTotalIndices() / 3).toFixed(0)} />
+                    <TextLineComponent label="Sub-meshes" value={mesh.subMeshes ? mesh.subMeshes.length.toString() : "0"} />
+                    <TextLineComponent label="Has skeleton" value={mesh.skeleton ? "Yes" : "No"} />
+                    <CheckBoxLineComponent label="IsEnabled" isSelected={() => mesh.isEnabled()} onSelect={(value) => mesh.setEnabled(value)} />
+                    <CheckBoxLineComponent label="IsPickable" target={mesh} propertyName="isPickable" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                    {
+                        mesh.material &&
+                        <TextLineComponent label="Material" value={mesh.material.name} onLink={() => this.onMaterialLink()} />
+                    }
+                </LineContainerComponent>
+                <LineContainerComponent title="TRANSFORMS">
+                    <Vector3LineComponent label="Position" target={mesh} propertyName="position" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                    <Vector3LineComponent label="Rotation" target={mesh} propertyName="rotation" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                    <Vector3LineComponent label="Scaling" target={mesh} propertyName="scaling" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                </LineContainerComponent>
+                <LineContainerComponent title="DISPLAY" closed={true}>
+                    <SliderLineComponent label="Visibility" target={mesh} propertyName="visibility" minimum={0} maximum={1} step={0.01} onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                    <CheckBoxLineComponent label="Receive shadows" target={mesh} propertyName="receiveShadows" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                    {
+                        mesh.isVerticesDataPresent(BABYLON.VertexBuffer.ColorKind) &&
+                        <CheckBoxLineComponent label="Use vertex colors" target={mesh} propertyName="useVertexColors" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                    }
+                    {
+                        scene.fogMode !== BABYLON.Scene.FOGMODE_NONE &&
+                        <CheckBoxLineComponent label="Apply fog" target={mesh} propertyName="applyFog" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                    }
+                    {
+                        !mesh.parent &&
+                        <CheckBoxLineComponent label="Infinite distance" target={mesh} propertyName="infiniteDistance" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                    }
+                </LineContainerComponent>
+                <LineContainerComponent title="ADVANCED" closed={true}>
+                    {
+                        mesh.useBones &&
+                        <CheckBoxLineComponent label="Compute bones using shaders" target={mesh} propertyName="computeBonesUsingShaders" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                    }
+                    <CheckBoxLineComponent label="Collisions" target={mesh} propertyName="checkCollisions" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                    <TextLineComponent label="Has normals" value={mesh.isVerticesDataPresent(BABYLON.VertexBuffer.NormalKind) ? "Yes" : "No"} />
+                    <TextLineComponent label="Has vertex colors" value={mesh.isVerticesDataPresent(BABYLON.VertexBuffer.ColorKind) ? "Yes" : "No"} />
+                    <TextLineComponent label="has UV set 0" value={mesh.isVerticesDataPresent(BABYLON.VertexBuffer.UVKind) ? "Yes" : "No"} />
+                    <TextLineComponent label="has UV set 1" value={mesh.isVerticesDataPresent(BABYLON.VertexBuffer.UV2Kind) ? "Yes" : "No"} />
+                    <TextLineComponent label="has UV set 2" value={mesh.isVerticesDataPresent(BABYLON.VertexBuffer.UV3Kind) ? "Yes" : "No"} />
+                    <TextLineComponent label="has UV set 3" value={mesh.isVerticesDataPresent(BABYLON.VertexBuffer.UV4Kind) ? "Yes" : "No"} />
+                    <TextLineComponent label="has tangents" value={mesh.isVerticesDataPresent(BABYLON.VertexBuffer.TangentKind) ? "Yes" : "No"} />
+                    <TextLineComponent label="has matrix weights" value={mesh.isVerticesDataPresent(BABYLON.VertexBuffer.MatricesWeightsKind) ? "Yes" : "No"} />
+                    <TextLineComponent label="has matrix indices" value={mesh.isVerticesDataPresent(BABYLON.VertexBuffer.MatricesIndicesKind) ? "Yes" : "No"} />
+                </LineContainerComponent>
+                <LineContainerComponent title="DEBUG" closed={true}>
+                    <CheckBoxLineComponent label="Show bounding box" target={mesh} propertyName="showBoundingBox" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                    <CheckBoxLineComponent label="Paint normals" isSelected={() => paintNormals} onSelect={() => this.paintNormals()} />
+                </LineContainerComponent>
+            </div>
+        );
+    }
+}

+ 38 - 0
inspector/src/components/actionTabs/tabs/propertyGrids/meshes/transformNodePropertyGridComponent.tsx

@@ -0,0 +1,38 @@
+import * as React from "react";
+import { TransformNode, Observable } from "babylonjs";
+import { PropertyChangedEvent } from "../../../../propertyChangedEvent";
+import { LineContainerComponent } from "../../../lineContainerComponent";
+import { CheckBoxLineComponent } from "../../../lines/checkBoxLineComponent";
+import { Vector3LineComponent } from "../../../lines/vector3LineComponent";
+import { TextLineComponent } from "../../../lines/textLineComponent";
+
+interface ITransformNodePropertyGridComponentProps {
+    transformNode: TransformNode,
+    onPropertyChangedObservable?: Observable<PropertyChangedEvent>
+}
+
+export class TransformNodePropertyGridComponent extends React.Component<ITransformNodePropertyGridComponentProps> {
+    constructor(props: ITransformNodePropertyGridComponentProps) {
+        super(props);
+    }
+
+    render() {
+        const transformNode = this.props.transformNode;
+
+        return (
+            <div className="pane">
+                <LineContainerComponent title="GENERAL">
+                    <TextLineComponent label="ID" value={transformNode.id} />
+                    <TextLineComponent label="Unique ID" value={transformNode.uniqueId.toString()} />
+                    <TextLineComponent label="Class" value={transformNode.getClassName()} />
+                    <CheckBoxLineComponent label="IsEnabled" isSelected={() => transformNode.isEnabled()} onSelect={(value) => transformNode.setEnabled(value)} />
+                </LineContainerComponent>
+                <LineContainerComponent title="TRANSFORMATIONS">
+                    <Vector3LineComponent label="Position" target={transformNode} propertyName="position" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                    <Vector3LineComponent label="Rotation" target={transformNode} propertyName="rotation" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                    <Vector3LineComponent label="Scaling" target={transformNode} propertyName="scaling" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                </LineContainerComponent>
+            </div>
+        );
+    }
+}

+ 93 - 0
inspector/src/components/actionTabs/tabs/propertyGrids/scenePropertyGridComponent.tsx

@@ -0,0 +1,93 @@
+import * as React from "react";
+import { Observable, Scene, BaseTexture, Nullable } from "babylonjs";
+import { PropertyChangedEvent } from "../../../propertyChangedEvent";
+import { LineContainerComponent } from "../../lineContainerComponent";
+import { RadioButtonLineComponent } from "../../lines/radioLineComponent";
+import { Color3LineComponent } from "../../lines/color3LineComponent";
+import { CheckBoxLineComponent } from "../../lines/checkBoxLineComponent";
+import { FogPropertyGridComponent } from "./fogPropertyGridComponent";
+import { FileButtonLineComponent } from "../../lines/fileButtonLineComponent";
+
+interface IScenePropertyGridComponentProps {
+    scene: Scene,
+    onPropertyChangedObservable?: Observable<PropertyChangedEvent>
+}
+
+export class ScenePropertyGridComponent extends React.Component<IScenePropertyGridComponentProps> {
+    private _storedEnvironmentTexture: Nullable<BaseTexture>;
+
+    constructor(props: IScenePropertyGridComponentProps) {
+        super(props);
+    }
+
+    setRenderingModes(point: boolean, wireframe: boolean) {
+        const scene = this.props.scene;
+        scene.forcePointsCloud = point;
+        scene.forceWireframe = wireframe;
+    }
+
+    switchIBL() {
+        const scene = this.props.scene;
+
+        if (scene.environmentTexture) {
+            this._storedEnvironmentTexture = scene.environmentTexture;
+            scene.environmentTexture = null;
+        } else {
+            scene.environmentTexture = this._storedEnvironmentTexture;
+            this._storedEnvironmentTexture = null;
+        }
+    }
+
+    updateEnvironmentTexture(file: File) {
+        let isFileDDS = file.name.toLowerCase().indexOf(".dds") > 0;
+        let isFileEnv = file.name.toLowerCase().indexOf(".env") > 0;
+        if (!isFileDDS && !isFileEnv) {
+            console.error("Unable to update environment texture. Please select a dds or env file.");
+            return;
+        }
+
+        const scene = this.props.scene;
+        BABYLON.Tools.ReadFile(file, (data) => {
+            var blob = new Blob([data], { type: "octet/stream" });
+            var url = URL.createObjectURL(blob);
+            if (isFileDDS) {
+                scene.environmentTexture = BABYLON.CubeTexture.CreateFromPrefilteredData(url, scene, ".dds");
+            }
+            else {
+                scene.environmentTexture = new BABYLON.CubeTexture(url, scene,
+                    undefined, undefined, undefined,
+                    () => {
+                    },
+                    (message) => {
+                        if (message) {
+                            console.error(message);
+                        }
+                    },
+                    undefined, undefined,
+                    ".env");
+            }
+        }, undefined, true);
+    }
+
+    render() {
+        const scene = this.props.scene;
+
+        const renderingModeGroupObservable = new BABYLON.Observable<RadioButtonLineComponent>();
+
+        return (
+            <div className="pane">
+                <LineContainerComponent title="RENDERING MODE">
+                    <RadioButtonLineComponent onSelectionChangedObservable={renderingModeGroupObservable} label="Point" isSelected={() => scene.forcePointsCloud} onSelect={() => this.setRenderingModes(true, false)} />
+                    <RadioButtonLineComponent onSelectionChangedObservable={renderingModeGroupObservable} label="Wireframe" isSelected={() => scene.forceWireframe} onSelect={() => this.setRenderingModes(false, true)} />
+                    <RadioButtonLineComponent onSelectionChangedObservable={renderingModeGroupObservable} label="Solid" isSelected={() => !scene.forcePointsCloud && !scene.forceWireframe} onSelect={() => this.setRenderingModes(false, false)} />
+                </LineContainerComponent>
+                <LineContainerComponent title="ENVIRONMENT">
+                    <Color3LineComponent label="Ambient color" target={scene} propertyName="ambientColor" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                    <CheckBoxLineComponent label="Environment texture (IBL)" isSelected={() => scene.environmentTexture != null} onSelect={() => this.switchIBL()} />
+                    <FileButtonLineComponent label="Update environment texture" onClick={(file) => this.updateEnvironmentTexture(file)} />
+                    <FogPropertyGridComponent scene={scene} onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                </LineContainerComponent>
+            </div>
+        );
+    }
+}

+ 51 - 0
inspector/src/components/actionTabs/tabs/propertyGrids/texturePropertyGridComponent.tsx

@@ -0,0 +1,51 @@
+import * as React from "react";
+import { Texture, Observable } from "babylonjs";
+import { PropertyChangedEvent } from "../../../propertyChangedEvent";
+import { LineContainerComponent } from "../../lineContainerComponent";
+import { SliderLineComponent } from "../../lines/sliderLineComponent";
+import { TextLineComponent } from "../../lines/textLineComponent";
+import { CheckBoxLineComponent } from "../../lines/checkBoxLineComponent";
+import { TextureLineComponent } from "../../lines/textureLineComponent";
+import { FloatLineComponent } from "../../lines/floatLineComponent";
+
+interface ITexturePropertyGridComponentProps {
+    texture: Texture,
+    onPropertyChangedObservable?: Observable<PropertyChangedEvent>
+}
+
+export class TexturePropertyGridComponent extends React.Component<ITexturePropertyGridComponentProps> {
+    constructor(props: ITexturePropertyGridComponentProps) {
+        super(props);
+    }
+
+    render() {
+        const texture = this.props.texture;
+
+        return (
+            <div className="pane">
+                <LineContainerComponent title="PREVIEW">
+                    <TextureLineComponent texture={texture} width={256} height={256} />
+                </LineContainerComponent>
+                <LineContainerComponent title="GENERAL">
+                    <TextLineComponent label="Has alpha" value={texture.hasAlpha ? "Yes" : "No"} />
+                    <TextLineComponent label="Is 3D" value={texture.is3D ? "Yes" : "No"} />
+                    <TextLineComponent label="Is cube" value={texture.isCube ? "Yes" : "No"} />
+                    <TextLineComponent label="Is render target" value={texture.isRenderTarget ? "Yes" : "No"} />
+                    <TextLineComponent label="Has mipmaps" value={!texture.noMipmap ? "Yes" : "No"} />
+                    <SliderLineComponent label="UV set" target={texture} propertyName="coordinatesIndex" minimum={0} maximum={3} step={1} onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                </LineContainerComponent>
+                <LineContainerComponent title="TRANSFORM">
+                    <FloatLineComponent label="U offset" target={texture} propertyName="uOffset" step={0.1} onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                    <FloatLineComponent label="V offset" target={texture} propertyName="vOffset" step={0.1} onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                    <FloatLineComponent label="V scale" target={texture} propertyName="uScale" step={0.1} onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                    <FloatLineComponent label="V scale" target={texture} propertyName="vScale" step={0.1} onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                    <FloatLineComponent label="U angle" target={texture} propertyName="uAng" step={0.1} onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                    <FloatLineComponent label="V angle" target={texture} propertyName="vAng" step={0.1} onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                    <FloatLineComponent label="W angle" target={texture} propertyName="wAng" step={0.1} onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                    <CheckBoxLineComponent label="Clamp U" isSelected={() => texture.wrapU === BABYLON.Texture.CLAMP_ADDRESSMODE} onSelect={(value) => texture.wrapU = value ? BABYLON.Texture.CLAMP_ADDRESSMODE : BABYLON.Texture.WRAP_ADDRESSMODE} />
+                    <CheckBoxLineComponent label="Clamp V" isSelected={() => texture.wrapV === BABYLON.Texture.CLAMP_ADDRESSMODE} onSelect={(value) => texture.wrapV = value ? BABYLON.Texture.CLAMP_ADDRESSMODE : BABYLON.Texture.WRAP_ADDRESSMODE} />
+                </LineContainerComponent>
+            </div>
+        );
+    }
+}

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

@@ -0,0 +1,125 @@
+import * as React from "react";
+import { PaneComponent, IPaneComponentProps } from "../paneComponent";
+import { TextLineComponent } from "../lines/textLineComponent";
+import { LineContainerComponent } from "../lineContainerComponent";
+import { SceneInstrumentation, EngineInstrumentation, Nullable } from "babylonjs";
+import { ValueLineComponent } from "../lines/valueLineComponent";
+import { BooleanLineComponent } from "../lines/booleanLineComponent";
+
+export class StatisticsTabComponent extends PaneComponent {
+    private _sceneInstrumentation: Nullable<SceneInstrumentation>;
+    private _engineInstrumentation: Nullable<EngineInstrumentation>;
+    private _timerIntervalId: number;
+
+    constructor(props: IPaneComponentProps) {
+        super(props);
+    }
+
+    componentWillMount() {
+        const scene = this.props.scene;
+
+        if (!scene) {
+            return;
+        }
+
+        this._sceneInstrumentation = new BABYLON.SceneInstrumentation(scene);
+        this._sceneInstrumentation.captureActiveMeshesEvaluationTime = true;
+        this._sceneInstrumentation.captureRenderTargetsRenderTime = true;
+        this._sceneInstrumentation.captureFrameTime = true;
+        this._sceneInstrumentation.captureRenderTime = true;
+        this._sceneInstrumentation.captureInterFrameTime = true;
+        this._sceneInstrumentation.captureParticlesRenderTime = true;
+        this._sceneInstrumentation.captureSpritesRenderTime = true;
+        this._sceneInstrumentation.capturePhysicsTime = true;
+        this._sceneInstrumentation.captureAnimationsTime = true;
+
+        this._engineInstrumentation = new BABYLON.EngineInstrumentation(scene.getEngine());
+        this._engineInstrumentation.captureGPUFrameTime = true;
+
+        this._timerIntervalId = window.setInterval(() => this.forceUpdate(), 500);
+    }
+
+    componentWillUnmount() {
+        if (this._sceneInstrumentation) {
+            this._sceneInstrumentation.dispose();
+            this._sceneInstrumentation = null;
+        }
+
+        if (this._engineInstrumentation) {
+            this._engineInstrumentation.dispose();
+            this._engineInstrumentation = null;
+        }
+
+        window.clearInterval(this._timerIntervalId);
+    }
+
+    render() {
+        const scene = this.props.scene;
+
+        if (!scene || !this._sceneInstrumentation || !this._engineInstrumentation) {
+            return null;
+        }
+
+        const engine = scene.getEngine();
+        const sceneInstrumentation = this._sceneInstrumentation;
+        const engineInstrumentation = this._engineInstrumentation;
+        const caps = engine.getCaps();
+
+        return (
+            <div className="pane">
+                <TextLineComponent label="Version" value={BABYLON.Engine.Version} color="rgb(51, 122, 255)" />
+                <ValueLineComponent label="FPS" value={engine.getFps()} fractionDigits={0} />
+                <LineContainerComponent title="COUNT">
+                    <TextLineComponent label="Total meshes" value={scene.meshes.length.toString()} />
+                    <TextLineComponent label="Active meshes" value={scene.getActiveMeshes().length.toString()} />
+                    <TextLineComponent label="Active indices" value={scene.getActiveIndices().toString()} />
+                    <TextLineComponent label="Active faces" value={(scene.getActiveIndices() / 3).toString()} />
+                    <TextLineComponent label="Active bones" value={scene.getActiveBones().toString()} />
+                    <TextLineComponent label="Active particles" value={scene.getActiveParticles().toString()} />
+                    <TextLineComponent label="Draw calls" value={sceneInstrumentation.drawCallsCounter.current.toString()} />
+                    <TextLineComponent label="Texture collisions" value={sceneInstrumentation.textureCollisionsCounter.current.toString()} />
+                    <TextLineComponent label="Total lights" value={scene.lights.length.toString()} />
+                    <TextLineComponent label="Total vertices" value={scene.getTotalVertices().toString()} />
+                    <TextLineComponent label="Total materials" value={scene.materials.length.toString()} />
+                    <TextLineComponent label="Total textures" value={scene.textures.length.toString()} />
+                </LineContainerComponent>
+                <LineContainerComponent title="FRAME STEPS DURATION">
+                    <ValueLineComponent label="Absolute FPS" value={1000.0 / this._sceneInstrumentation!.frameTimeCounter.current} fractionDigits={0} />
+                    <ValueLineComponent label="Meshes selection" value={sceneInstrumentation.activeMeshesEvaluationTimeCounter.current} units="ms" />
+                    <ValueLineComponent label="Render targets" value={sceneInstrumentation.renderTargetsRenderTimeCounter.current} units="ms" />
+                    <ValueLineComponent label="Particles" value={sceneInstrumentation.particlesRenderTimeCounter.current} units="ms" />
+                    <ValueLineComponent label="Sprites" value={sceneInstrumentation.spritesRenderTimeCounter.current} units="ms" />
+                    <ValueLineComponent label="Animations" value={sceneInstrumentation.animationsTimeCounter.current} units="ms" />
+                    <ValueLineComponent label="Physics" value={sceneInstrumentation.physicsTimeCounter.current} units="ms" />
+                    <ValueLineComponent label="Render" value={sceneInstrumentation.renderTimeCounter.current} units="ms" />
+                    <ValueLineComponent label="Frame total" value={sceneInstrumentation.frameTimeCounter.current} units="ms" />
+                    <ValueLineComponent label="Inter-frame" value={sceneInstrumentation.interFrameTimeCounter.current} units="ms" />
+                    <ValueLineComponent label="GPU Frame time" value={engineInstrumentation.gpuFrameTimeCounter.current * 0.000001} units="ms" />
+                    <ValueLineComponent label="GPU Frame time (average)" value={engineInstrumentation.gpuFrameTimeCounter.average * 0.000001} units="ms" />
+                </LineContainerComponent>
+                <LineContainerComponent title="SYSTEM INFO">
+                    <TextLineComponent label="Resolution" value={engine.getRenderWidth() + "x" + engine.getRenderHeight()} />
+                    <TextLineComponent label="WebGL version" value={engine.webGLVersion.toString()} />
+                    <BooleanLineComponent label="Std derivatives" value={caps.standardDerivatives} />
+                    <BooleanLineComponent label="Compressed textures" value={caps.s3tc !== undefined} />
+                    <BooleanLineComponent label="Hardware instances" value={caps.instancedArrays} />
+                    <BooleanLineComponent label="Texture float" value={caps.textureFloat} />
+                    <BooleanLineComponent label="Texture half-float" value={caps.textureHalfFloat} />
+                    <BooleanLineComponent label="Render to texture float" value={caps.textureFloatRender} />
+                    <BooleanLineComponent label="Render to texture half-float" value={caps.textureHalfFloatRender} />
+                    <BooleanLineComponent label="32bits indices" value={caps.uintIndices} />
+                    <BooleanLineComponent label="Fragment depth" value={caps.fragmentDepthSupported} />
+                    <BooleanLineComponent label="High precision shaders" value={caps.highPrecisionShaderSupported} />
+                    <BooleanLineComponent label="Draw buffers" value={caps.drawBuffersExtension} />
+                    <BooleanLineComponent label="Vertex array object" value={caps.vertexArrayObject} />
+                    <BooleanLineComponent label="Timer query" value={caps.timerQuery !== undefined} />
+                    <BooleanLineComponent label="Stencil" value={engine.isStencilEnable} />
+                    <ValueLineComponent label="Max textures units" value={caps.maxTexturesImageUnits} fractionDigits={0} />
+                    <ValueLineComponent label="Max textures size" value={caps.maxTextureSize} fractionDigits={0} />
+                    <ValueLineComponent label="Max anisotropy" value={caps.maxAnisotropy} fractionDigits={0} />
+                    <TextLineComponent label="Driver" value={engine.getGlInfo().renderer} />
+                </LineContainerComponent>
+            </div>
+        );
+    }
+}

+ 102 - 0
inspector/src/components/actionTabs/tabs/toolsTabComponent.tsx

@@ -0,0 +1,102 @@
+import * as React from "react";
+import { PaneComponent, IPaneComponentProps } from "../paneComponent";
+import { LineContainerComponent } from "../lineContainerComponent";
+import { ButtonLineComponent } from "../lines/buttonLineComponent";
+import { VideoRecorder, TransformNode, PBRMaterial, StandardMaterial, BackgroundMaterial, Nullable } from "babylonjs";
+import { GLTFData } from "babylonjs-serializers";
+
+export class ToolsTabComponent extends PaneComponent {
+    private _videoRecorder: Nullable<VideoRecorder>;
+
+    constructor(props: IPaneComponentProps) {
+        super(props);
+
+        this.state = { tag: "Record video" };
+    }
+
+    componentWillMount() {
+        if (!(BABYLON as any).GLTF2Export) {
+            BABYLON.Tools.LoadScript("https://preview.babylonjs.com/serializers/babylonjs.serializers.min.js", () => {
+            });
+            return;
+        }
+    }
+
+    componentWillUnmount() {
+        if (this._videoRecorder) {
+            this._videoRecorder.stopRecording();
+            this._videoRecorder.dispose();
+            this._videoRecorder = null;
+        }
+    }
+
+    captureScreenshot() {
+        const scene = this.props.scene;
+        if (scene.activeCamera) {
+            BABYLON.Tools.CreateScreenshotUsingRenderTarget(scene.getEngine(), scene.activeCamera, { precision: 1.0 }, undefined, undefined, 4, true);
+        }
+    }
+
+    recordVideo() {
+        if (this._videoRecorder && this._videoRecorder.isRecording) {
+            this._videoRecorder.stopRecording();
+            return;
+        }
+
+        const scene = this.props.scene;
+        if (!this._videoRecorder) {
+            this._videoRecorder = new BABYLON.VideoRecorder(scene.getEngine());
+        }
+
+        this._videoRecorder.startRecording().then(() => {
+            this.setState({ tag: "Record video" })
+        });
+        this.setState({ tag: "Stop recording" })
+    }
+
+    shouldExport(transformNode: TransformNode): boolean {
+
+        // No skybox
+        if (transformNode instanceof BABYLON.Mesh) {
+            if (transformNode.material) {
+                const material = transformNode.material as PBRMaterial | StandardMaterial | BackgroundMaterial;
+                const reflectionTexture = material.reflectionTexture;
+                if (reflectionTexture && reflectionTexture.coordinatesMode === BABYLON.Texture.SKYBOX_MODE) {
+                    return false;
+                }
+            }
+        }
+
+        return true;
+    }
+
+    exportGLTF() {
+        const scene = this.props.scene;
+
+        BABYLON.GLTF2Export.GLBAsync(scene, "scene", {
+            shouldExportTransformNode: (transformNode) => this.shouldExport(transformNode)
+        }).then((glb: GLTFData) => {
+            glb.downloadFiles();
+        });
+    }
+
+    render() {
+        const scene = this.props.scene;
+
+        if (!scene) {
+            return null;
+        }
+
+        return (
+            <div className="pane">
+                <LineContainerComponent title="CAPTURE">
+                    <ButtonLineComponent label="Screenshot" onClick={() => this.captureScreenshot()} />
+                    <ButtonLineComponent label={this.state.tag} onClick={() => this.recordVideo()} />
+                </LineContainerComponent>
+                <LineContainerComponent title="GLTF">
+                    <ButtonLineComponent label="Export" onClick={() => this.exportGLTF()} />
+                </LineContainerComponent>
+            </div>
+        );
+    }
+}

+ 47 - 0
inspector/src/components/actionTabs/tabsComponent.tsx

@@ -0,0 +1,47 @@
+import * as React from "react";
+import { PaneComponent } from "./paneComponent";
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+
+interface ITabsComponentProps {
+    children: any[],
+    selectedIndex: number,
+    onSelectedIndexChange: (value: number) => void
+}
+
+export class TabsComponent extends React.Component<ITabsComponentProps> {
+    constructor(props: ITabsComponentProps) {
+        super(props);
+    }
+
+    onSelect(index: number) {
+        this.props.onSelectedIndexChange(index);
+    }
+
+    renderLabel(child: PaneComponent, index: number) {
+        const activeClass = (this.props.selectedIndex === index ? 'label active' : 'label');
+        return (
+            <div className={activeClass} key={index} onClick={() => this.onSelect(index)} title={child.props.title}>
+                <div>
+                    <FontAwesomeIcon icon={child.props.icon} />
+                </div>
+            </div>
+        )
+    }
+
+    render() {
+        return (
+            <div className="tabs">
+                <div className="labels">
+                    {
+                        this.props.children.map((child: PaneComponent, index) => {
+                            return this.renderLabel(child, index);
+                        })
+                    }
+                </div>
+                <div className="panes">
+                    {this.props.children[this.props.selectedIndex]}
+                </div>
+            </div>
+        );
+    }
+}

+ 117 - 0
inspector/src/components/embedHost/embedHost.scss

@@ -0,0 +1,117 @@
+#embed-host {
+    position: absolute;
+    right: 0px;
+    top:0px;
+    bottom: 0px;
+}
+
+#embed {
+    background: #333333;
+    height: 100%;
+    margin: 0;
+    padding: 0;
+    display: grid;
+    grid-template-rows: 30px 1fr;
+    font: 14px "Arial";
+    overflow: hidden;
+
+    #header {
+        font-size: 16px;
+        color: white;
+        background: #222222;
+        grid-row: 1;
+        text-align: center;
+        display: grid;
+        grid-template-columns: 30px 1fr 50px;        
+        -webkit-user-select: none; 
+        -moz-user-select: none;   
+        -ms-user-select: none;    
+        user-select: none;                
+
+        #logo {
+            grid-column: 1; 
+            width: 24px;
+            height: 24px;
+            display: flex;
+            align-self: center;   
+            justify-self: center;
+        }      
+        
+        #back {
+            grid-column: 1; 
+            display: grid;
+            align-self: center;   
+            justify-self: center;
+            cursor: pointer;
+        }    
+
+        #title {
+            grid-column: 2; 
+            display: grid;
+            align-items: center;   
+            text-align: center;
+        }
+
+        #commands {
+            grid-column: 3; 
+            display: grid;
+            align-items: center;  
+            grid-template-columns: 1fr 1fr;   
+            
+            .expand {
+                grid-column: 1;
+                display: grid;
+                align-items: center;   
+                justify-items: center;
+                cursor: pointer;     
+            }
+
+            .close {
+                grid-column: 2;
+                display: grid;
+                align-items: center;   
+                justify-items: center;
+                cursor: pointer;     
+            }            
+        }
+    }
+
+    #split {
+        grid-row: 2;
+        display: grid;
+        grid-template-rows: 40% 60%;
+        overflow: hidden;
+
+        #topPart {
+            grid-row: 1;
+            overflow: hidden;
+            display: grid;
+            grid-auto-rows: 100%;
+        }
+
+        #bottomPart {
+            overflow: hidden;
+            grid-row: 2;
+            height: 100%;
+        }        
+    }
+    
+    .splitter-layout.splitter-layout-vertical {
+        grid-row: 2;
+
+        .layout-pane {            
+            overflow: hidden;
+        }
+
+        #topPart {
+            overflow: hidden;
+            display: grid;
+            grid-auto-rows: 100%;
+        }
+
+        #bottomPart {
+            overflow: hidden;
+            height: 100%;
+        }
+    }
+}

+ 85 - 0
inspector/src/components/embedHost/embedHostComponent.tsx

@@ -0,0 +1,85 @@
+import * as React from "react";
+import { HeaderComponent } from "../headerComponent";
+import Resizable from "re-resizable";
+import { SceneExplorerComponent } from "../sceneExplorer/sceneExplorerComponent";
+import { ActionTabsComponent } from "../actionTabs/actionTabsComponent";
+import { Scene, Observable } from "babylonjs";
+
+let SplitterLayout = require('react-splitter-layout');
+require("./embedHost.scss");
+
+interface IEmbedHostComponentProps {
+    scene: Scene,
+    onSelectionChangeObservable: Observable<any>,
+    popupMode: boolean,
+    onClose: () => void,
+    onPopup: () => void
+}
+
+export class EmbedHostComponent extends React.Component<IEmbedHostComponentProps> {
+    private _once = true;
+
+    constructor(props: IEmbedHostComponentProps) {
+        super(props);
+    }
+
+    renderContent(splitEnabled: boolean) {
+        if (!splitEnabled) {
+            return (
+                <div id="split">
+                    <div id="topPart">
+                        <SceneExplorerComponent scene={this.props.scene}
+                            popupMode={true}
+                            onSelectionChangeObservable={this.props.onSelectionChangeObservable} noHeader={true} />
+                    </div>
+                    <div id="bottomPart" style={{ marginTop: "4px", overflow: "hidden" }}>
+                        <ActionTabsComponent scene={this.props.scene}
+                            popupMode={true}
+                            onSelectionChangeObservable={this.props.onSelectionChangeObservable} noHeader={true} />
+                    </div>
+                </div>
+            )
+        }
+
+        return (
+            <SplitterLayout vertical={true} primaryMinSize={200} secondaryMinSize={300}>
+                <div id="topPart">
+                    <SceneExplorerComponent scene={this.props.scene}
+                        popupMode={true}
+                        onSelectionChangeObservable={this.props.onSelectionChangeObservable} noHeader={true} />
+                </div>
+                <div id="bottomPart" style={{ marginTop: "4px", overflow: "hidden" }}>
+                    <ActionTabsComponent scene={this.props.scene}
+                        popupMode={true}
+                        onSelectionChangeObservable={this.props.onSelectionChangeObservable} noHeader={true} />
+                </div>
+            </SplitterLayout>
+        )
+    }
+
+    render() {
+        if (this.props.popupMode) {
+            return (
+                <div id="embed">
+                    <HeaderComponent title="INSPECTOR" handleBack={true} onClose={() => this.props.onClose()} onPopup={() => this.props.onPopup()} onSelectionChangeObservable={this.props.onSelectionChangeObservable} />
+                    {this.renderContent(false)}
+                </div>
+            );
+        }
+
+        if (this._once) {
+            this._once = false;
+            // A bit hacky but no other way to force the initial width to 300px and not auto
+            setTimeout(() => {
+                document.getElementById("embed")!.style.width = "300px";
+            }, 150);
+        }
+
+        return (
+            <Resizable id="embed" minWidth={300} maxWidth={600} size={{ height: "100%" }} minHeight="100%" enable={{ top: false, right: false, bottom: false, left: true, topRight: false, bottomRight: false, bottomLeft: false, topLeft: false }}>
+                <HeaderComponent title="INSPECTOR" handleBack={true} onClose={() => this.props.onClose()} onPopup={() => this.props.onPopup()} onSelectionChangeObservable={this.props.onSelectionChangeObservable} />
+                {this.renderContent(true)}
+            </Resizable>
+        );
+    }
+}

+ 101 - 0
inspector/src/components/headerComponent.tsx

@@ -0,0 +1,101 @@
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { faWindowRestore, faTimes, faArrowLeft } from "@fortawesome/free-solid-svg-icons";
+import { Observable, Observer, Nullable } from "babylonjs";
+import * as React from "react";
+
+export interface IHeaderComponentProps {
+    title: string,
+    handleBack?: boolean,
+    noExpand?: boolean,
+    noCommands?: boolean,
+    onPopup: () => void,
+    onClose: () => void,
+    onSelectionChangeObservable?: Observable<any>
+}
+
+export class HeaderComponent extends React.Component<IHeaderComponentProps, { isBackVisible: boolean }> {
+    private _backStack = new Array<any>();
+    private _onSelectionChangeObserver: Nullable<Observer<any>>;
+
+    constructor(props: IHeaderComponentProps) {
+        super(props);
+        this.state = { isBackVisible: false };
+    }
+
+    componentWillMount() {
+        if (!this.props.onSelectionChangeObservable) {
+            return;
+        }
+
+        this._onSelectionChangeObserver = this.props.onSelectionChangeObservable.add((entity) => {
+            if (this._backStack.length === 0 || entity !== this._backStack[this._backStack.length - 1]) {
+                this._backStack.push(entity);
+                this.setState({ isBackVisible: this._backStack.length > 1 });
+            }
+        });
+    }
+
+    componentWillUnmount() {
+        if (this._onSelectionChangeObserver) {
+            this.props.onSelectionChangeObservable!.remove(this._onSelectionChangeObserver);
+        }
+    }
+
+    goBack() {
+        this._backStack.pop(); // remove current
+        var entity = this._backStack[this._backStack.length - 1];
+
+        if (this.props.onSelectionChangeObservable) {
+            this.props.onSelectionChangeObservable.notifyObservers(entity);
+        }
+
+        this.setState({ isBackVisible: this._backStack.length > 1 });
+    }
+
+    renderLogo() {
+        if (this.props.noCommands) {
+            return null;
+        }
+
+        if (this.props.handleBack) {
+            if (!this.state.isBackVisible) {
+                return null;
+            }
+
+            return (
+                <div id="back" onClick={() => this.goBack()} >
+                    <FontAwesomeIcon icon={faArrowLeft} />
+                </div>
+            )
+        }
+
+        return (
+            <img id="logo" src="https://www.babylonjs.com/Assets/logo-babylonjs-social-twitter.png" />
+        )
+    }
+
+    render() {
+        return (
+            <div id="header">
+                {this.renderLogo()}
+                <div id="title">
+                    {this.props.title}
+                </div>
+                <div id="commands">
+                    {
+                        !this.props.noCommands && !this.props.noExpand &&
+                        <div className="expand" onClick={() => this.props.onPopup()}>
+                            <FontAwesomeIcon icon={faWindowRestore} />
+                        </div>
+                    }
+                    {
+                        !this.props.noCommands &&
+                        <div className="close" onClick={() => this.props.onClose()}>
+                            <FontAwesomeIcon icon={faTimes} />
+                        </div>
+                    }
+                </div>
+            </div>
+        )
+    }
+}

+ 6 - 0
inspector/src/components/propertyChangedEvent.ts

@@ -0,0 +1,6 @@
+export class PropertyChangedEvent {
+    public object: any;
+    public property: string;
+    public value: any;
+    public initialValue: any;
+}

+ 74 - 0
inspector/src/components/sceneExplorer/entities/cameraTreeItemComponent.tsx

@@ -0,0 +1,74 @@
+import { Camera, Observer, Scene, Nullable } from "babylonjs";
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { faVideo, faCamera } from '@fortawesome/free-solid-svg-icons';
+import { TreeItemLabelComponent } from "../treeItemLabelComponent";
+import { IExtensibilityGroup } from "../../../inspector";
+import { ExtensionsComponent } from "../extensionsComponent";
+import * as React from "react";
+
+interface ICameraTreeItemComponentProps {
+    camera: Camera,
+    extensibilityGroups?: IExtensibilityGroup[],
+    onClick: () => void
+}
+
+export class CameraTreeItemComponent extends React.Component<ICameraTreeItemComponentProps, { isActive: boolean }> {
+    private _onActiveCameraObserver: Nullable<Observer<Scene>>;
+
+    constructor(props: ICameraTreeItemComponentProps) {
+        super(props);
+
+        const camera = this.props.camera;
+        const scene = camera.getScene();
+
+        this.state = { isActive: scene.activeCamera === camera };
+    }
+
+    setActive(): void {
+        const camera = this.props.camera;
+        const scene = camera.getScene();
+
+        scene.activeCamera = camera;
+        camera.attachControl(scene.getEngine().getRenderingCanvas()!, true);
+
+
+        this.setState({ isActive: true });
+    }
+
+    componentWillMount() {
+        const camera = this.props.camera;
+        const scene = camera.getScene();
+        this._onActiveCameraObserver = scene.onActiveCameraChanged.add(() => {
+            if (this.state.isActive) {
+                camera.detachControl(scene.getEngine().getRenderingCanvas()!);
+            }
+
+            this.setState({ isActive: scene.activeCamera === camera });
+        });
+    }
+
+    componentWillUnmount() {
+        if (this._onActiveCameraObserver) {
+            const camera = this.props.camera;
+            const scene = camera.getScene();
+
+            scene.onActiveCameraChanged.remove(this._onActiveCameraObserver);
+        }
+    }
+
+    render() {
+        const isActiveElement = this.state.isActive ? <FontAwesomeIcon icon={faVideo} /> : <FontAwesomeIcon icon={faVideo} className="isNotActive" />;
+
+        return (
+            <div className="cameraTools">
+                <TreeItemLabelComponent label={this.props.camera.name} onClick={() => this.props.onClick()} icon={faCamera} color="green" />
+                <div className="activeCamera icon" onClick={() => this.setActive()} title="Set as main camera">
+                    {isActiveElement}
+                </div>
+                {
+                    <ExtensionsComponent target={this.props.camera} extensibilityGroups={this.props.extensibilityGroups} />
+                }
+            </div>
+        )
+    }
+}

+ 48 - 0
inspector/src/components/sceneExplorer/entities/lightTreeItemComponent.tsx

@@ -0,0 +1,48 @@
+import { Light } from "babylonjs";
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { faLightbulb } from '@fortawesome/free-solid-svg-icons';
+import { faLightbulb as faLightbubRegular } from '@fortawesome/free-regular-svg-icons';
+import { TreeItemLabelComponent } from "../treeItemLabelComponent";
+import { IExtensibilityGroup } from "../../../inspector";
+import { ExtensionsComponent } from "../extensionsComponent";
+import * as React from "react";
+
+interface ILightTreeItemComponentProps {
+    light: Light,
+    extensibilityGroups?: IExtensibilityGroup[]
+    onClick: () => void
+}
+
+export class LightTreeItemComponent extends React.Component<ILightTreeItemComponentProps, { isEnabled: boolean }> {
+    constructor(props: ILightTreeItemComponentProps) {
+        super(props);
+
+        const light = this.props.light;
+
+        this.state = { isEnabled: light.isEnabled() };
+    }
+
+    switchIsEnabled(): void {
+        const light = this.props.light;
+
+        light.setEnabled(!light.isEnabled());
+
+        this.setState({ isEnabled: light.isEnabled() });
+    }
+
+    render() {
+        const isEnabledElement = this.state.isEnabled ? <FontAwesomeIcon icon={faLightbubRegular} /> : <FontAwesomeIcon icon={faLightbubRegular} className="isNotActive" />;
+
+        return (
+            <div className="lightTools">
+                <TreeItemLabelComponent label={this.props.light.name} onClick={() => this.props.onClick()} icon={faLightbulb} color="yellow" />
+                <div className="enableLight icon" onClick={() => this.switchIsEnabled()} title="Turn on/off the light">
+                    {isEnabledElement}
+                </div>
+                {
+                    <ExtensionsComponent target={this.props.light} extensibilityGroups={this.props.extensibilityGroups} />
+                }
+            </div>
+        )
+    }
+}

+ 29 - 0
inspector/src/components/sceneExplorer/entities/materialTreeItemComponent.tsx

@@ -0,0 +1,29 @@
+import { faBrush } from '@fortawesome/free-solid-svg-icons';
+import { Material } from "babylonjs";
+import { TreeItemLabelComponent } from "../treeItemLabelComponent";
+import { IExtensibilityGroup } from "../../../inspector";
+import { ExtensionsComponent } from "../extensionsComponent";
+import * as React from 'react';
+
+interface IMaterialTreeItemComponentProps {
+    material: Material,
+    extensibilityGroups?: IExtensibilityGroup[],
+    onClick: () => void
+}
+
+export class MaterialTreeItemComponent extends React.Component<IMaterialTreeItemComponentProps> {
+    constructor(props: IMaterialTreeItemComponentProps) {
+        super(props);
+    }
+
+    render() {
+        return (
+            <div className="materialTools">
+                <TreeItemLabelComponent label={this.props.material.name} onClick={() => this.props.onClick()} icon={faBrush} color="orange" />
+                {
+                    <ExtensionsComponent target={this.props.material} extensibilityGroups={this.props.extensibilityGroups} />
+                }
+            </div>
+        )
+    }
+}

+ 0 - 0
inspector/src/components/sceneExplorer/entities/meshTreeItemComponent.tsx


برخی فایل ها در این مقایسه diff نمایش داده نمی شوند زیرا تعداد فایل ها بسیار زیاد است