Browse Source

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

ssaket 6 years ago
parent
commit
cb5bd11d48
90 changed files with 24761 additions and 19949 deletions
  1. 1 1
      .vscode/tasks.json
  2. 10086 9750
      Playground/babylon.d.txt
  3. 1 0
      Playground/debug.html
  4. 1 0
      Playground/frame.html
  5. 1 0
      Playground/full.html
  6. 2 0
      Playground/index-local.html
  7. 1 0
      Playground/index.html
  8. 1 0
      Playground/indexStable.html
  9. 1 0
      Playground/ts.html
  10. 1 0
      Playground/zipContent/index.html
  11. 1 0
      Tools/Gulp/config.json
  12. 27 18
      Tools/Gulp/gulpfile.js
  13. 7 7
      Tools/Gulp/package.json
  14. 2 1
      Viewer/tests/validation/validate.html
  15. 666 0
      dist/preview release/ammo.js
  16. 9855 9592
      dist/preview release/babylon.d.ts
  17. 1 1
      dist/preview release/babylon.js
  18. 782 101
      dist/preview release/babylon.max.js
  19. 782 101
      dist/preview release/babylon.no-module.max.js
  20. 1 1
      dist/preview release/babylon.worker.js
  21. 784 103
      dist/preview release/es6.js
  22. 1 1
      dist/preview release/glTF2Interface/package.json
  23. 76 0
      dist/preview release/gui/babylon.gui.d.ts
  24. 1 1
      dist/preview release/gui/babylon.gui.js
  25. 1 1
      dist/preview release/gui/babylon.gui.min.js
  26. 1 1
      dist/preview release/gui/babylon.gui.min.js.map
  27. 157 0
      dist/preview release/gui/babylon.gui.module.d.ts
  28. 2 2
      dist/preview release/gui/package.json
  29. 12 12
      dist/preview release/inspector/babylon.inspector.bundle.js
  30. 1 1
      dist/preview release/inspector/babylon.inspector.bundle.js.map
  31. 5 5
      dist/preview release/inspector/package.json
  32. 1 1
      dist/preview release/loaders/babylon.glTF1FileLoader.min.js
  33. 11 6
      dist/preview release/loaders/babylon.glTF2FileLoader.js
  34. 1 1
      dist/preview release/loaders/babylon.glTF2FileLoader.min.js
  35. 11 6
      dist/preview release/loaders/babylon.glTFFileLoader.js
  36. 1 1
      dist/preview release/loaders/babylon.glTFFileLoader.min.js
  37. 11 6
      dist/preview release/loaders/babylonjs.loaders.js
  38. 1 1
      dist/preview release/loaders/babylonjs.loaders.min.js
  39. 3 3
      dist/preview release/loaders/package.json
  40. 2 2
      dist/preview release/materialsLibrary/package.json
  41. 1 1
      dist/preview release/postProcessesLibrary/babylon.asciiArtPostProcess.min.js
  42. 1 1
      dist/preview release/postProcessesLibrary/babylon.digitalRainPostProcess.min.js
  43. 1 1
      dist/preview release/postProcessesLibrary/babylonjs.postProcess.min.js
  44. 2 2
      dist/preview release/postProcessesLibrary/package.json
  45. 2 2
      dist/preview release/proceduralTexturesLibrary/package.json
  46. 1 1
      dist/preview release/serializers/babylon.glTF2Serializer.min.js
  47. 1 1
      dist/preview release/serializers/babylonjs.serializers.min.js
  48. 3 3
      dist/preview release/serializers/package.json
  49. 1 15
      dist/preview release/viewer/babylon.viewer.d.ts
  50. 3 3
      dist/preview release/viewer/babylon.viewer.js
  51. 6 6
      dist/preview release/viewer/babylon.viewer.max.js
  52. 1 18
      dist/preview release/viewer/babylon.viewer.module.d.ts
  53. 10 0
      dist/preview release/what's new.md
  54. 1 0
      gui/src/2D/controls/index.ts
  55. 435 0
      gui/src/2D/controls/scrollViewer.ts
  56. 5 1
      inspector/src/components/actionTabs/actionTabs.scss
  57. 3 2
      inspector/src/components/actionTabs/actionTabsComponent.tsx
  58. 31 6
      inspector/src/components/actionTabs/lineContainerComponent.tsx
  59. 1 1
      inspector/src/components/actionTabs/lines/optionsLineComponent.tsx
  60. 8 10
      inspector/src/components/actionTabs/tabs/propertyGrids/materials/texturePropertyGridComponent.tsx
  61. 3 3
      inspector/src/components/actionTabs/tabs/propertyGrids/meshes/axesViewerComponent.tsx
  62. 4 0
      inspector/src/components/embedHost/embedHost.scss
  63. 4 2
      inspector/src/components/embedHost/embedHostComponent.tsx
  64. 2 1
      inspector/src/components/headerComponent.tsx
  65. 2 2
      inspector/src/components/sceneExplorer/entities/cameraTreeItemComponent.tsx
  66. 4 0
      inspector/src/components/sceneExplorer/sceneExplorer.scss
  67. 3 2
      inspector/src/components/sceneExplorer/sceneExplorerComponent.tsx
  68. 10 2
      inspector/src/inspector.ts
  69. 16 1
      inspector/src/tools.ts
  70. 11 6
      loaders/src/glTF/2.0/babylon.glTFLoader.ts
  71. 1 0
      localDev/index.html
  72. 1 1
      package.json
  73. 1 0
      sandbox/index-local.html
  74. 1 0
      sandbox/index.html
  75. 0 1
      src/Cameras/babylon.arcRotateCamera.ts
  76. 97 78
      src/Debug/babylon.axesViewer.ts
  77. 5 1
      src/Debug/babylon.debugLayer.ts
  78. 6 6
      src/Engine/babylon.engine.ts
  79. 30 21
      src/Gizmos/babylon.axisDragGizmo.ts
  80. 14 13
      src/Gizmos/babylon.boundingBoxGizmo.ts
  81. 8 0
      src/Materials/Textures/Loaders/babylon.ktxTextureLoader.ts
  82. 2 0
      src/Materials/Textures/babylon.internalTexture.ts
  83. 3 0
      src/Materials/Textures/babylon.texture.ts
  84. 1 1
      src/Mesh/babylon.transformNode.ts
  85. 1 1
      src/Particles/babylon.solidParticleSystem.ts
  86. 694 0
      src/Physics/Plugins/babylon.ammoJSPlugin.ts
  87. 5 2
      src/Physics/babylon.physicsImpostor.ts
  88. 8 2
      src/Rendering/babylon.utilityLayerRenderer.ts
  89. 3 2
      src/babylon.node.ts
  90. 1 0
      tests/validation/validate.html

+ 1 - 1
.vscode/tasks.json

@@ -36,7 +36,7 @@
                 "background": {
                     "activeOnStart": true,
                     "beginsPattern": "Starting \\'watch\\'",
-                    "endsPattern": "Finished \\'run\\'"
+                    "endsPattern": "Entrypoint babylonjs-inspector"
                 }
             }
         },

File diff suppressed because it is too large
+ 10086 - 9750
Playground/babylon.d.txt


+ 1 - 0
Playground/debug.html

@@ -35,6 +35,7 @@
         <script src="js/libs/fileSaver.js"></script>
 
         <!-- Babylon.js -->
+        <script src="https://preview.babylonjs.com/ammo.js"></script>
         <script src="https://preview.babylonjs.com/cannon.js"></script>
         <script src="https://preview.babylonjs.com/Oimo.js"></script>
         <script src="https://preview.babylonjs.com/gltf_validator.js"></script>

+ 1 - 0
Playground/frame.html

@@ -28,6 +28,7 @@
 
         <script src="https://cdnjs.cloudflare.com/ajax/libs/dat-gui/0.6.2/dat.gui.min.js"></script>
         <!-- Babylon.js -->
+        <script src="https://preview.babylonjs.com/ammo.js"></script>
         <script src="https://preview.babylonjs.com/cannon.js"></script>
         <script src="https://preview.babylonjs.com/Oimo.js"></script>
         <script src="https://preview.babylonjs.com/earcut.min.js"></script>

+ 1 - 0
Playground/full.html

@@ -26,6 +26,7 @@
 
         <script src="https://code.jquery.com/pep/0.4.2/pep.min.js"></script>
         <!-- Babylon.js -->
+        <script src="https://preview.babylonjs.com/ammo.js"></script>
         <script src="https://preview.babylonjs.com/cannon.js"></script>
         <script src="https://preview.babylonjs.com/Oimo.js"></script>
         <script src="https://preview.babylonjs.com/gltf_validator.js"></script>

+ 2 - 0
Playground/index-local.html

@@ -1,6 +1,7 @@
 <!DOCTYPE html>
 <html>
 
+
 <head>
     <title>Babylon.js Playground</title>
     <meta charset='utf-8' />
@@ -14,6 +15,7 @@
     <script src="js/libs/jszip.min.js"></script>
     <script src="js/libs/fileSaver.js"></script>
     <!-- Dependencies -->
+    <script src="../dist/preview%20release/ammo.js"></script>
     <script src="../dist/preview%20release/cannon.js"></script>
     <script src="../dist/preview%20release/Oimo.js"></script>
     <script src="../dist/preview%20release/gltf_validator.js"></script>

+ 1 - 0
Playground/index.html

@@ -34,6 +34,7 @@
         <script src="js/libs/jszip.min.js"></script>
         <script src="js/libs/fileSaver.js"></script>
         <!-- Dependencies -->
+        <script src="https://preview.babylonjs.com/ammo.js"></script>
         <script src="https://preview.babylonjs.com/cannon.js"></script>
         <script src="https://preview.babylonjs.com/Oimo.js"></script>
         <script src="https://preview.babylonjs.com/gltf_validator.js"></script>

+ 1 - 0
Playground/indexStable.html

@@ -34,6 +34,7 @@
     <script src="js/libs/jszip.min.js"></script>
     <script src="js/libs/fileSaver.js"></script>
     <!-- Physics -->
+    <script src="https://cdn.babylonjs.com/ammo.js"></script>
     <script src="https://cdn.babylonjs.com/cannon.js"></script>
     <script src="https://cdn.babylonjs.com/Oimo.js"></script>
     <script src="https://cdn.babylonjs.com/gltf_validator.js"></script>

+ 1 - 0
Playground/ts.html

@@ -34,6 +34,7 @@
         <script src="js/libs/jszip.min.js"></script>
         <script src="js/libs/fileSaver.js"></script>
         <!-- Dependencies -->
+        <script src="https://preview.babylonjs.com/ammo.js"></script>
         <script src="https://preview.babylonjs.com/cannon.js"></script>
         <script src="https://preview.babylonjs.com/Oimo.js"></script>
         <script src="https://preview.babylonjs.com/earcut.min.js"></script>

+ 1 - 0
Playground/zipContent/index.html

@@ -8,6 +8,7 @@
         <!-- Babylon.js -->
         <script src="https://code.jquery.com/pep/0.4.2/pep.min.js"></script>
         <script src="https://cdnjs.cloudflare.com/ajax/libs/dat-gui/0.6.2/dat.gui.min.js"></script>
+        <script src="https://preview.babylonjs.com/ammo.js"></script>
         <script src="https://preview.babylonjs.com/cannon.js"></script>
         <script src="https://preview.babylonjs.com/Oimo.js"></script>
         <script src="https://preview.babylonjs.com/gltf_validator.js"></script>

+ 1 - 0
Tools/Gulp/config.json

@@ -1125,6 +1125,7 @@
                 "../../src/Physics/babylon.physicsEngine.js",
                 "../../src/Physics/babylon.physicsHelper.js",
                 "../../src/Physics/Plugins/babylon.cannonJSPlugin.js",
+                "../../src/Physics/Plugins/babylon.ammoJSPlugin.js",
                 "../../src/Physics/Plugins/babylon.oimoJSPlugin.js",
                 "../../src/Physics/babylon.physicsEngineComponent.js"
             ],

+ 27 - 18
Tools/Gulp/gulpfile.js

@@ -20,7 +20,7 @@ var replace = require("gulp-replace");
 var uncommentShader = require("./gulp-removeShaderComments");
 var expect = require("gulp-expect-file");
 var optimisejs = require("gulp-optimize-js");
-var webserver = require("gulp-webserver");
+var connect = require("gulp-connect");
 var path = require("path");
 const webpack = require('webpack');
 var webpackStream = require("webpack-stream");
@@ -793,21 +793,27 @@ gulp.task("watch", gulp.series("srcTscWatch", function startWatch() {
                 //config.stats = "minimal";
                 tasks.push(webpackStream(wpconfig, webpack).pipe(gulp.dest(outputDirectory)))
             } else {
-                tasks.push(gulp.watch(library.files, { interval: interval }, function() {
-                    console.log(library.output);
-                    return buildExternalLibrary(library, config[module], true)
-                        .pipe(debug());
-                }));
-                tasks.push(gulp.watch(library.shaderFiles, { interval: interval }, function() {
-                    console.log(library.output);
-                    return buildExternalLibrary(library, config[module], true)
-                        .pipe(debug())
-                }));
-                tasks.push(gulp.watch(library.sassFiles, { interval: interval }, function() {
-                    console.log(library.output);
-                    return buildExternalLibrary(library, config[module], true)
-                        .pipe(debug())
-                }));
+                if (library.files) {
+                    tasks.push(gulp.watch(library.files, { interval: interval }, function() {
+                        console.log(library.output);
+                        return buildExternalLibrary(library, config[module], true)
+                            .pipe(debug());
+                    }));
+                }
+                if (library.shaderFiles) {
+                    tasks.push(gulp.watch(library.shaderFiles, { interval: interval }, function() {
+                        console.log(library.output);
+                        return buildExternalLibrary(library, config[module], true)
+                            .pipe(debug())
+                    }));
+                }
+                if (library.sassFiles) {
+                    tasks.push(gulp.watch(library.sassFiles, { interval: interval }, function() {
+                        console.log(library.output);
+                        return buildExternalLibrary(library, config[module], true)
+                            .pipe(debug())
+                    }));
+                }
             }
         });
     });
@@ -841,16 +847,19 @@ gulp.task("deployLocalDev", function() {
  */
 gulp.task("webserver", function() {
     var options = {
+        root: "../../.",
         port: 1338,
         livereload: false,
-        middleware: [cors()]
+        middleware: function() {
+            return [cors()];
+        }
     };
 
     if (commandLineOptions.public) {
         options.host = "0.0.0.0";
     }
 
-    return gulp.src("../../.").pipe(webserver(options));
+    connect.server(options);
 });
 
 /**

+ 7 - 7
Tools/Gulp/package.json

@@ -12,10 +12,14 @@
         "@types/node": "^8.10.22",
         "chai": "^4.1.2",
         "color-support": "^1.1.3",
+        "cors": "^2.8.4",
         "del": "3.0.0",
+        "dts-bundle": "^0.7.3",
         "gulp": "^4.0.0",
+        "gulp-clean": "^0.4.0",
         "gulp-clean-ts-extends": "~0.1.1",
         "gulp-concat": "~2.6.1",
+        "gulp-connect": "^5.6.1",
         "gulp-content-to-variable": "^0.1.0",
         "gulp-debug": "^4.0.0",
         "gulp-expect-file": "^1.0.0",
@@ -25,12 +29,10 @@
         "gulp-replace": "~1.0.0",
         "gulp-sourcemaps": "~2.6.4",
         "gulp-tslint": "^8.1.3",
-        "gulp-typedoc": "^2.2.0",
+        "gulp-typedoc": "^2.2.1",
         "gulp-typescript": "4.0.2",
         "gulp-uglify": "^3.0.1",
-        "gulp-webserver": "^0.9.1",
-        "cors": "^2.8.4",
-        "karma": "^2.0.5",
+        "karma": "^3.1.1",
         "karma-browserstack-launcher": "^1.3.0",
         "karma-chai": "^0.1.0",
         "karma-chrome-launcher": "^2.2.0",
@@ -49,9 +51,7 @@
         "typedoc": "^0.12.0",
         "typescript": "~3.0.1",
         "webpack": "^4.16.3",
-        "webpack-stream": "5.0.0",
-        "dts-bundle": "^0.7.3",
-        "gulp-clean": "^0.4.0"
+        "webpack-stream": "5.0.0"
     },
     "scripts": {
         "install": "cd ../../gui && npm install && cd ../Tools/Gulp/ &&  cd ../../inspector && npm install && cd ../Tools/Gulp/ && npm --prefix ../../Playground/ install ../../Playground/ && npm --prefix ../../tests/unit/ install ../../tests/unit/ && npm --prefix ../../Viewer/tests/ install ../../Viewer/tests/ && cd ../../Viewer && npm install && cd ../Tools/Gulp/ && gulp deployLocalDev"

+ 2 - 1
Viewer/tests/validation/validate.html

@@ -3,7 +3,8 @@
 <head>
 	<title>BabylonJS - Build validation page</title>
 	<link href="index.css" rel="stylesheet" />
-    <script src="https://preview.babylonjs.com/cannon.js"></script>
+	<script src="https://preview.babylonjs.com/ammo.js"></script>
+	<script src="https://preview.babylonjs.com/cannon.js"></script>
     <script src="https://preview.babylonjs.com/Oimo.js"></script>
     <script src="https://preview.babylonjs.com/gltf_validator.js"></script>
     <script src="https://preview.babylonjs.com/babylon.js"></script>

File diff suppressed because it is too large
+ 666 - 0
dist/preview release/ammo.js


File diff suppressed because it is too large
+ 9855 - 9592
dist/preview release/babylon.d.ts


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


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


File diff suppressed because it is too large
+ 782 - 101
dist/preview release/babylon.no-module.max.js


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


File diff suppressed because it is too large
+ 784 - 103
dist/preview release/es6.js


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

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

+ 76 - 0
dist/preview release/gui/babylon.gui.d.ts

@@ -1956,6 +1956,82 @@ declare module BABYLON.GUI {
 }
 declare module BABYLON.GUI {
     /**
+        * Class used to hold a viewer window and sliders in a grid
+     */
+    export class ScrollViewer extends Rectangle {
+            /** name of ScrollViewer */
+            name?: string | undefined;
+            /**
+                * Adds windowContents to the grid view window
+                * @param windowContents the contents to add the grid view window
+                */
+            addToWindow(windowContents: Control): void;
+            /**
+                * Gets or sets a value indicating the padding to use on the left of the viewer window
+                * @see http://doc.babylonjs.com/how_to/gui#position-and-size
+                */
+            paddingLeft: string | number;
+            /**
+                * Gets a value indicating the padding in pixels to use on the left of the viewer window
+                * @see http://doc.babylonjs.com/how_to/gui#position-and-size
+                */
+            readonly paddingLeftInPixels: number;
+            /**
+                * Gets or sets a value indicating the padding to use on the right of the viewer window
+                * @see http://doc.babylonjs.com/how_to/gui#position-and-size
+                */
+            paddingRight: string | number;
+            /**
+                * Gets a value indicating the padding in pixels to use on the right of the viewer window
+                * @see http://doc.babylonjs.com/how_to/gui#position-and-size
+                */
+            readonly paddingRightInPixels: number;
+            /**
+                * Gets or sets a value indicating the padding to use on the top of the viewer window
+                * @see http://doc.babylonjs.com/how_to/gui#position-and-size
+                */
+            paddingTop: string | number;
+            /**
+                * Gets a value indicating the padding in pixels to use on the top of the viewer window
+                * @see http://doc.babylonjs.com/how_to/gui#position-and-size
+                */
+            readonly paddingTopInPixels: number;
+            /**
+                * Gets or sets a value indicating the padding to use on the bottom of the viewer window
+                * @see http://doc.babylonjs.com/how_to/gui#position-and-size
+                */
+            paddingBottom: string | number;
+            /**
+                * Gets a value indicating the padding in pixels to use on the bottom of the viewer window
+                * @see http://doc.babylonjs.com/how_to/gui#position-and-size
+                */
+            readonly paddingBottomInPixels: number;
+            /**
+             * Creates a new ScrollViewer
+             * @param name of ScrollViewer
+             */
+            constructor(
+            /** name of ScrollViewer */
+            name?: string | undefined);
+            /**
+                * Gets or sets the mouse wheel precision
+                * from 0 to 1 with a default value of 0.05
+                * */
+            wheelPrecision: number;
+            /** Gets or sets the bar color */
+            barColor: string;
+            /** Gets or sets the bar color */
+            barBorderColor: string;
+            /** Gets or sets the bar background */
+            barBackground: string;
+            /** @hidden */
+            protected _additionalProcessing(parentMeasure: Measure, context: CanvasRenderingContext2D): void;
+            /** Releases associated resources */
+            dispose(): void;
+    }
+}
+declare module BABYLON.GUI {
+    /**
         * Enum that determines the text-wrapping mode to use.
         */
     export enum TextWrapping {

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


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


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


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

@@ -40,6 +40,7 @@ declare module 'babylonjs-gui/2D/controls' {
     export * from "babylonjs-gui/2D/controls/radioButton";
     export * from "babylonjs-gui/2D/controls/stackPanel";
     export * from "babylonjs-gui/2D/controls/selector";
+    export * from "babylonjs-gui/2D/controls/scrollViewer";
     export * from "babylonjs-gui/2D/controls/textBlock";
     export * from "babylonjs-gui/2D/controls/virtualKeyboard";
     export * from "babylonjs-gui/2D/controls/rectangle";
@@ -2102,6 +2103,86 @@ declare module 'babylonjs-gui/2D/controls/selector' {
     }
 }
 
+declare module 'babylonjs-gui/2D/controls/scrollViewer' {
+    import { Measure } from "babylonjs-gui/2D/measure";
+    import { Rectangle } from "babylonjs-gui/2D/controls/rectangle";
+    import { Control } from "babylonjs-gui/2D/controls/control";
+    /**
+        * Class used to hold a viewer window and sliders in a grid
+     */
+    export class ScrollViewer extends Rectangle {
+            /** name of ScrollViewer */
+            name?: string | undefined;
+            /**
+                * Adds windowContents to the grid view window
+                * @param windowContents the contents to add the grid view window
+                */
+            addToWindow(windowContents: Control): void;
+            /**
+                * Gets or sets a value indicating the padding to use on the left of the viewer window
+                * @see http://doc.babylonjs.com/how_to/gui#position-and-size
+                */
+            paddingLeft: string | number;
+            /**
+                * Gets a value indicating the padding in pixels to use on the left of the viewer window
+                * @see http://doc.babylonjs.com/how_to/gui#position-and-size
+                */
+            readonly paddingLeftInPixels: number;
+            /**
+                * Gets or sets a value indicating the padding to use on the right of the viewer window
+                * @see http://doc.babylonjs.com/how_to/gui#position-and-size
+                */
+            paddingRight: string | number;
+            /**
+                * Gets a value indicating the padding in pixels to use on the right of the viewer window
+                * @see http://doc.babylonjs.com/how_to/gui#position-and-size
+                */
+            readonly paddingRightInPixels: number;
+            /**
+                * Gets or sets a value indicating the padding to use on the top of the viewer window
+                * @see http://doc.babylonjs.com/how_to/gui#position-and-size
+                */
+            paddingTop: string | number;
+            /**
+                * Gets a value indicating the padding in pixels to use on the top of the viewer window
+                * @see http://doc.babylonjs.com/how_to/gui#position-and-size
+                */
+            readonly paddingTopInPixels: number;
+            /**
+                * Gets or sets a value indicating the padding to use on the bottom of the viewer window
+                * @see http://doc.babylonjs.com/how_to/gui#position-and-size
+                */
+            paddingBottom: string | number;
+            /**
+                * Gets a value indicating the padding in pixels to use on the bottom of the viewer window
+                * @see http://doc.babylonjs.com/how_to/gui#position-and-size
+                */
+            readonly paddingBottomInPixels: number;
+            /**
+             * Creates a new ScrollViewer
+             * @param name of ScrollViewer
+             */
+            constructor(
+            /** name of ScrollViewer */
+            name?: string | undefined);
+            /**
+                * Gets or sets the mouse wheel precision
+                * from 0 to 1 with a default value of 0.05
+                * */
+            wheelPrecision: number;
+            /** Gets or sets the bar color */
+            barColor: string;
+            /** Gets or sets the bar color */
+            barBorderColor: string;
+            /** Gets or sets the bar background */
+            barBackground: string;
+            /** @hidden */
+            protected _additionalProcessing(parentMeasure: Measure, context: CanvasRenderingContext2D): void;
+            /** Releases associated resources */
+            dispose(): void;
+    }
+}
+
 declare module 'babylonjs-gui/2D/controls/textBlock' {
     import { Observable } from "babylonjs";
     import { Measure } from "babylonjs-gui/2D/measure";
@@ -5009,6 +5090,82 @@ declare module BABYLON.GUI {
 }
 declare module BABYLON.GUI {
     /**
+        * Class used to hold a viewer window and sliders in a grid
+     */
+    export class ScrollViewer extends Rectangle {
+            /** name of ScrollViewer */
+            name?: string | undefined;
+            /**
+                * Adds windowContents to the grid view window
+                * @param windowContents the contents to add the grid view window
+                */
+            addToWindow(windowContents: Control): void;
+            /**
+                * Gets or sets a value indicating the padding to use on the left of the viewer window
+                * @see http://doc.babylonjs.com/how_to/gui#position-and-size
+                */
+            paddingLeft: string | number;
+            /**
+                * Gets a value indicating the padding in pixels to use on the left of the viewer window
+                * @see http://doc.babylonjs.com/how_to/gui#position-and-size
+                */
+            readonly paddingLeftInPixels: number;
+            /**
+                * Gets or sets a value indicating the padding to use on the right of the viewer window
+                * @see http://doc.babylonjs.com/how_to/gui#position-and-size
+                */
+            paddingRight: string | number;
+            /**
+                * Gets a value indicating the padding in pixels to use on the right of the viewer window
+                * @see http://doc.babylonjs.com/how_to/gui#position-and-size
+                */
+            readonly paddingRightInPixels: number;
+            /**
+                * Gets or sets a value indicating the padding to use on the top of the viewer window
+                * @see http://doc.babylonjs.com/how_to/gui#position-and-size
+                */
+            paddingTop: string | number;
+            /**
+                * Gets a value indicating the padding in pixels to use on the top of the viewer window
+                * @see http://doc.babylonjs.com/how_to/gui#position-and-size
+                */
+            readonly paddingTopInPixels: number;
+            /**
+                * Gets or sets a value indicating the padding to use on the bottom of the viewer window
+                * @see http://doc.babylonjs.com/how_to/gui#position-and-size
+                */
+            paddingBottom: string | number;
+            /**
+                * Gets a value indicating the padding in pixels to use on the bottom of the viewer window
+                * @see http://doc.babylonjs.com/how_to/gui#position-and-size
+                */
+            readonly paddingBottomInPixels: number;
+            /**
+             * Creates a new ScrollViewer
+             * @param name of ScrollViewer
+             */
+            constructor(
+            /** name of ScrollViewer */
+            name?: string | undefined);
+            /**
+                * Gets or sets the mouse wheel precision
+                * from 0 to 1 with a default value of 0.05
+                * */
+            wheelPrecision: number;
+            /** Gets or sets the bar color */
+            barColor: string;
+            /** Gets or sets the bar color */
+            barBorderColor: string;
+            /** Gets or sets the bar background */
+            barBackground: string;
+            /** @hidden */
+            protected _additionalProcessing(parentMeasure: Measure, context: CanvasRenderingContext2D): void;
+            /** Releases associated resources */
+            dispose(): void;
+    }
+}
+declare module BABYLON.GUI {
+    /**
         * Enum that determines the text-wrapping mode to use.
         */
     export enum TextWrapping {

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

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

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


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


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

@@ -4,7 +4,7 @@
     },
     "name": "babylonjs-inspector",
     "description": "The Babylon.js inspector.",
-    "version": "4.0.0-alpha.8",
+    "version": "4.0.0-alpha.10",
     "repository": {
         "type": "git",
         "url": "https://github.com/BabylonJS/Babylon.js.git"
@@ -28,10 +28,10 @@
     ],
     "license": "Apache-2.0",
     "dependencies": {
-        "babylonjs": "4.0.0-alpha.8",
-        "babylonjs-gui": "4.0.0-alpha.8",
-        "babylonjs-loaders": "4.0.0-alpha.8",
-        "babylonjs-serializers": "4.0.0-alpha.8"
+        "babylonjs": "4.0.0-alpha.10",
+        "babylonjs-gui": "4.0.0-alpha.10",
+        "babylonjs-loaders": "4.0.0-alpha.10",
+        "babylonjs-serializers": "4.0.0-alpha.10"
     },
     "engines": {
         "node": "*"

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


+ 11 - 6
dist/preview release/loaders/babylon.glTF2FileLoader.js

@@ -2206,13 +2206,18 @@ var BABYLON;
                 var sampler = (texture.sampler == undefined ? GLTFLoader._DefaultSampler : ArrayItem.Get(context + "/sampler", this.gltf.samplers, texture.sampler));
                 var samplerData = this._loadSampler("/samplers/" + sampler.index, sampler);
                 var image = ArrayItem.Get(context + "/source", this.gltf.images, texture.source);
-                var textureURL = null;
-                if (image.uri && !BABYLON.Tools.IsBase64(image.uri) && this.babylonScene.getEngine().textureFormatInUse) {
-                    // If an image uri and a texture format is set like (eg. KTX) load from url instead of blob to support texture format and fallback
-                    textureURL = this._uniqueRootUrl + image.uri;
+                var url = null;
+                if (image.uri) {
+                    if (BABYLON.Tools.IsBase64(image.uri)) {
+                        url = image.uri;
+                    }
+                    else if (this.babylonScene.getEngine().textureFormatInUse) {
+                        // If an image uri and a texture format is set like (eg. KTX) load from url instead of blob to support texture format and fallback
+                        url = this._rootUrl + image.uri;
+                    }
                 }
                 var deferred = new BABYLON.Deferred();
-                var babylonTexture = new BABYLON.Texture(textureURL, this.babylonScene, samplerData.noMipMaps, false, samplerData.samplingMode, function () {
+                var babylonTexture = new BABYLON.Texture(url, this.babylonScene, samplerData.noMipMaps, false, samplerData.samplingMode, function () {
                     if (!_this._disposed) {
                         deferred.resolve();
                     }
@@ -2222,7 +2227,7 @@ var BABYLON;
                     }
                 });
                 promises.push(deferred.promise);
-                if (!textureURL) {
+                if (!url) {
                     promises.push(this.loadImageAsync("/images/" + image.index, image).then(function (data) {
                         var name = image.uri || _this._fileName + "#image" + image.index;
                         var dataUrl = "data:" + _this._uniqueRootUrl + name;

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


+ 11 - 6
dist/preview release/loaders/babylon.glTFFileLoader.js

@@ -4414,13 +4414,18 @@ var BABYLON;
                 var sampler = (texture.sampler == undefined ? GLTFLoader._DefaultSampler : ArrayItem.Get(context + "/sampler", this.gltf.samplers, texture.sampler));
                 var samplerData = this._loadSampler("/samplers/" + sampler.index, sampler);
                 var image = ArrayItem.Get(context + "/source", this.gltf.images, texture.source);
-                var textureURL = null;
-                if (image.uri && !BABYLON.Tools.IsBase64(image.uri) && this.babylonScene.getEngine().textureFormatInUse) {
-                    // If an image uri and a texture format is set like (eg. KTX) load from url instead of blob to support texture format and fallback
-                    textureURL = this._uniqueRootUrl + image.uri;
+                var url = null;
+                if (image.uri) {
+                    if (BABYLON.Tools.IsBase64(image.uri)) {
+                        url = image.uri;
+                    }
+                    else if (this.babylonScene.getEngine().textureFormatInUse) {
+                        // If an image uri and a texture format is set like (eg. KTX) load from url instead of blob to support texture format and fallback
+                        url = this._rootUrl + image.uri;
+                    }
                 }
                 var deferred = new BABYLON.Deferred();
-                var babylonTexture = new BABYLON.Texture(textureURL, this.babylonScene, samplerData.noMipMaps, false, samplerData.samplingMode, function () {
+                var babylonTexture = new BABYLON.Texture(url, this.babylonScene, samplerData.noMipMaps, false, samplerData.samplingMode, function () {
                     if (!_this._disposed) {
                         deferred.resolve();
                     }
@@ -4430,7 +4435,7 @@ var BABYLON;
                     }
                 });
                 promises.push(deferred.promise);
-                if (!textureURL) {
+                if (!url) {
                     promises.push(this.loadImageAsync("/images/" + image.index, image).then(function (data) {
                         var name = image.uri || _this._fileName + "#image" + image.index;
                         var dataUrl = "data:" + _this._uniqueRootUrl + name;

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


+ 11 - 6
dist/preview release/loaders/babylonjs.loaders.js

@@ -5476,13 +5476,18 @@ var BABYLON;
                 var sampler = (texture.sampler == undefined ? GLTFLoader._DefaultSampler : ArrayItem.Get(context + "/sampler", this.gltf.samplers, texture.sampler));
                 var samplerData = this._loadSampler("/samplers/" + sampler.index, sampler);
                 var image = ArrayItem.Get(context + "/source", this.gltf.images, texture.source);
-                var textureURL = null;
-                if (image.uri && !BABYLON.Tools.IsBase64(image.uri) && this.babylonScene.getEngine().textureFormatInUse) {
-                    // If an image uri and a texture format is set like (eg. KTX) load from url instead of blob to support texture format and fallback
-                    textureURL = this._uniqueRootUrl + image.uri;
+                var url = null;
+                if (image.uri) {
+                    if (BABYLON.Tools.IsBase64(image.uri)) {
+                        url = image.uri;
+                    }
+                    else if (this.babylonScene.getEngine().textureFormatInUse) {
+                        // If an image uri and a texture format is set like (eg. KTX) load from url instead of blob to support texture format and fallback
+                        url = this._rootUrl + image.uri;
+                    }
                 }
                 var deferred = new BABYLON.Deferred();
-                var babylonTexture = new BABYLON.Texture(textureURL, this.babylonScene, samplerData.noMipMaps, false, samplerData.samplingMode, function () {
+                var babylonTexture = new BABYLON.Texture(url, this.babylonScene, samplerData.noMipMaps, false, samplerData.samplingMode, function () {
                     if (!_this._disposed) {
                         deferred.resolve();
                     }
@@ -5492,7 +5497,7 @@ var BABYLON;
                     }
                 });
                 promises.push(deferred.promise);
-                if (!textureURL) {
+                if (!url) {
                     promises.push(this.loadImageAsync("/images/" + image.index, image).then(function (data) {
                         var name = image.uri || _this._fileName + "#image" + image.index;
                         var dataUrl = "data:" + _this._uniqueRootUrl + name;

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


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

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

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

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

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


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


File diff suppressed because it is too large
+ 1 - 1
dist/preview release/postProcessesLibrary/babylonjs.postProcess.min.js


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

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

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

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

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


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


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

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

+ 1 - 15
dist/preview release/viewer/babylon.viewer.d.ts

@@ -924,7 +924,7 @@ declare module BabylonViewer {
       * @param name the name of the custom optimizer configuration
       * @param upgrade set to true if you want to upgrade optimizer and false if you want to degrade
       */
-    export function getCustomOptimizerByName(name: string, upgrade?: boolean): typeof extendedUpgrade;
+    export function getCustomOptimizerByName(name: string, upgrade?: boolean): (sceneManager: SceneManager) => boolean;
     export function registerCustomOptimizer(name: string, optimizer: (sceneManager: SceneManager) => boolean): void;
 }
 declare module BabylonViewer {
@@ -1558,20 +1558,6 @@ declare module BabylonViewer {
     export function addLoaderPlugin(name: string, plugin: ILoaderPlugin): void;
 }
 declare module BabylonViewer {
-    /**
-        * A custom upgrade-oriented function configuration for the scene optimizer.
-        *
-        * @param viewer the viewer to optimize
-        */
-    export function extendedUpgrade(sceneManager: SceneManager): boolean;
-    /**
-        * A custom degrade-oriented function configuration for the scene optimizer.
-        *
-        * @param viewer the viewer to optimize
-        */
-    export function extendedDegrade(sceneManager: SceneManager): boolean;
-}
-declare module BabylonViewer {
 }
 declare module BabylonViewer {
     export interface IEnvironmentMapConfiguration {

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


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


+ 1 - 18
dist/preview release/viewer/babylon.viewer.module.d.ts

@@ -985,14 +985,13 @@ declare module 'babylonjs-viewer/templating/viewerTemplatePlugin' {
 }
 
 declare module 'babylonjs-viewer/optimizer/custom' {
-    import { extendedUpgrade } from "babylonjs-viewer/optimizer/custom/extended";
     import { SceneManager } from "babylonjs-viewer/managers/sceneManager";
     /**
       *
       * @param name the name of the custom optimizer configuration
       * @param upgrade set to true if you want to upgrade optimizer and false if you want to degrade
       */
-    export function getCustomOptimizerByName(name: string, upgrade?: boolean): typeof extendedUpgrade;
+    export function getCustomOptimizerByName(name: string, upgrade?: boolean): (sceneManager: SceneManager) => boolean;
     export function registerCustomOptimizer(name: string, optimizer: (sceneManager: SceneManager) => boolean): void;
 }
 
@@ -1663,22 +1662,6 @@ declare module 'babylonjs-viewer/loader/plugins' {
     export function addLoaderPlugin(name: string, plugin: ILoaderPlugin): void;
 }
 
-declare module 'babylonjs-viewer/optimizer/custom/extended' {
-    import { SceneManager } from 'babylonjs-viewer/managers/sceneManager';
-    /**
-        * A custom upgrade-oriented function configuration for the scene optimizer.
-        *
-        * @param viewer the viewer to optimize
-        */
-    export function extendedUpgrade(sceneManager: SceneManager): boolean;
-    /**
-        * A custom degrade-oriented function configuration for the scene optimizer.
-        *
-        * @param viewer the viewer to optimize
-        */
-    export function extendedDegrade(sceneManager: SceneManager): boolean;
-}
-
 declare module 'babylonjs-viewer/configuration/interfaces' {
     export * from 'babylonjs-viewer/configuration/interfaces/cameraConfiguration';
     export * from 'babylonjs-viewer/configuration/interfaces/colorGradingConfiguration';

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

@@ -18,6 +18,7 @@
 - GUI:
   - Added new [ImageBasedSlider](http://doc.babylonjs.com/how_to/gui#imagebasedslider) to let users customize sliders using images ([Deltakosh](https://github.com/deltakosh))
   - Added support for clipboard events to let users perform `cut`, `copy` and `paste` events ([Saket Saurabh](https://github.com/ssaket))
+  - Added new [ScrollViewer](https://doc.babylonjs.com/how_to/scrollviewer) with mouse wheel scrolling for larger containers to be viewed using Sliders ([JohnK](https://github.com/BabylonJSGuide/))
 
 ## Updates
 
@@ -70,6 +71,9 @@
 - Added support for overriding the mesh used for the world matrix for a mesh with a skeleton ([bghgary](https://github.com/bghgary))
 - Added support for linking a bone to a transform node ([bghgary](https://github.com/bghgary))
 - Factored out `setDirection` function from `lookAt` for transform node ([bghgary](https://github.com/bghgary))
+- Added support for AmmoJS physics plugin ([TrevorDev](https://github.com/TrevorDev))
+- Add support for setting renderingGroupId and creating instances to `AxesViewer` ([bghgary](https://github.com/bghgary))
+- Invert vScale of compressed ktx textures as they are inverted in the file and UNPACK_FLIP_Y_WEBGL is not supported by ktx ([TrevorDev](https://github.com/TrevorDev))
 
 ### glTF Loader
 
@@ -102,6 +106,8 @@
 - PointerDragBahavior using Mesh as base type, causing type-checking problems with AbstractMesh ([Poolminer](https://github.com/Poolminer/))
 - TransformNode lookAt not working in world space when node's parent has rotation ([TrevorDev](https://github.com/TrevorDev))
 - MakeNotPickableAndWrapInBoundingBox had unexpected behavior when input had scaling of 0 on an axis ([TrevorDev](https://github.com/TrevorDev))
+- Fixed an issue with loading base64 encoded images in the glTF loader ([bghgary](https://github.com/bghgary))
+- In multi-camera scenes the inspector would cause the camera's interaction events to get detached ([TrevorDev](https://github.com/TrevorDev))
 
 ### Core Engine
 - Fixed a bug with `mesh.alwaysSelectAsActiveMesh` preventing layerMask to be taken in account ([Deltakosh](https://github.com/deltakosh))
@@ -115,6 +121,9 @@
 - Added a `DeepImmutable<T>` type to specifiy that a referenced object should be considered recursively immutable, meaning that all its properties are `readonly` and that if a property is a reference to an object, this object is also recursively immutable. ([barroij](https://github.com/barroij))
 - Fixed `VideoTexture` poster property when autoplay is turned off.
 - Fixed position and rotation of plane mesh created by MeshBuilder.CreatePlane when specifying a source plane ([sable](https://github.com/thscott), [bghgary](https://github.com/bghgary))
+- Fixed inspector dynamic loading ([Sebavan](https://github.com/Sebavan))
+- Fixed infiniteDistance not working anymore ([Sebavan](https://github.com/Sebavan))
+- Fixed bug in SolidParticle BoundingSphere update within the SolidParticleSystem ([barroij](https://github.com/barroij))
 
 ### Viewer
 
@@ -150,3 +159,4 @@
   - _Note: The root node is still a `Mesh` object and is still the first in the returned list of meshes_
   - `TransformNode` objects are excluded from the returned list of meshes when importing mesh
   - `TransformNode` objects do not raise `onMeshLoaded` events
+- `xAxisMesh`, `yAxisMesh`, and `zAxisMesh` of `AxesViewer` was renamed to `xAxis`, `yAxis`, and `zAxis` respectively and now return a `TransformNode` to represent the parent node of the cylinder and line of the arrow ([bghgary](https://github.com/bghgary))

+ 1 - 0
gui/src/2D/controls/index.ts

@@ -13,6 +13,7 @@ export * from "./multiLine";
 export * from "./radioButton";
 export * from "./stackPanel";
 export * from "./selector";
+export * from "./scrollViewer";
 export * from "./textBlock";
 export * from "./virtualKeyboard";
 export * from "./rectangle";

+ 435 - 0
gui/src/2D/controls/scrollViewer.ts

@@ -0,0 +1,435 @@
+import { Measure } from "../measure";
+import { Rectangle } from "./rectangle";
+import { Grid } from "./grid";
+import { Control } from "./control";
+import { Slider } from "./slider";
+import { ValueAndUnit } from "../valueAndUnit";
+import { Container } from "./container";
+import { TextBlock } from "./textBlock";
+import { PointerInfo, Observer, Nullable } from "babylonjs";
+
+/**
+ * Class used to hold a viewer window and sliders in a grid
+*/
+export class ScrollViewer extends Rectangle {
+    private _grid: Grid;
+    private _horizontalBarSpace: Rectangle;
+    private _verticalBarSpace: Rectangle;
+    private _dragSpace: Rectangle;
+    private _horizontalBar: Slider;
+    private _verticalBar: Slider;
+    private _barColor: string = "grey";
+    private _barBorderColor: string = "#444444";
+    private _barBackground: string = "white";
+    private _scrollGridWidth: number = 30;
+    private _scrollGridHeight: number = 30;
+    private _widthScale: number;
+    private _heightScale: number;
+    private _endLeft: number;
+    private _endTop: number;
+    private _window: Container;
+    private _windowContents: Control;
+    private _pointerIsOver: Boolean = false;
+    private _wheelPrecision: number = 0.05;
+    private _onPointerObserver: Nullable<Observer<PointerInfo>>;
+
+    /**
+     * Adds windowContents to the grid view window
+     * @param windowContents the contents to add the grid view window
+     */
+    public addToWindow(windowContents: Control): void {
+        this._window.removeControl(this._windowContents);
+        this._windowContents.dispose();
+        this._windowContents = windowContents;
+        if (windowContents.typeName === "TextBlock") {
+            this._updateTextBlock(windowContents);
+        }
+        else {
+            this._updateScroller(windowContents);
+        }
+        this._window.addControl(windowContents);
+    }
+
+    /**
+     * Gets or sets a value indicating the padding to use on the left of the viewer window
+     * @see http://doc.babylonjs.com/how_to/gui#position-and-size
+     */
+    public get paddingLeft(): string | number {
+        return this._windowContents.paddingLeft;
+    }
+
+    /**
+     * Gets a value indicating the padding in pixels to use on the left of the viewer window
+     * @see http://doc.babylonjs.com/how_to/gui#position-and-size
+     */
+    public get paddingLeftInPixels(): number {
+        return this._windowContents.paddingLeftInPixels;
+    }
+
+    public set paddingLeft(value: string | number) {
+        this._windowContents.paddingLeft = value;
+    }
+
+    /**
+     * Gets or sets a value indicating the padding to use on the right of the viewer window
+     * @see http://doc.babylonjs.com/how_to/gui#position-and-size
+     */
+    public get paddingRight(): string | number {
+        return this._windowContents.paddingRight;
+    }
+
+    /**
+     * Gets a value indicating the padding in pixels to use on the right of the viewer window
+     * @see http://doc.babylonjs.com/how_to/gui#position-and-size
+     */
+    public get paddingRightInPixels(): number {
+        return this._windowContents.paddingRightInPixels;
+    }
+
+    public set paddingRight(value: string | number) {
+        this._windowContents.paddingRight = value;
+    }
+
+    /**
+     * Gets or sets a value indicating the padding to use on the top of the viewer window
+     * @see http://doc.babylonjs.com/how_to/gui#position-and-size
+     */
+    public get paddingTop(): string | number {
+        return this._windowContents.paddingTop;
+    }
+
+    /**
+     * Gets a value indicating the padding in pixels to use on the top of the viewer window
+     * @see http://doc.babylonjs.com/how_to/gui#position-and-size
+     */
+    public get paddingTopInPixels(): number {
+        return this._windowContents.paddingTopInPixels;
+    }
+
+    public set paddingTop(value: string | number) {
+        this._windowContents.paddingTop = value;
+    }
+
+    /**
+     * Gets or sets a value indicating the padding to use on the bottom of the viewer window
+     * @see http://doc.babylonjs.com/how_to/gui#position-and-size
+     */
+    public get paddingBottom(): string | number {
+        return this._windowContents.paddingBottom;
+    }
+
+    /**
+     * Gets a value indicating the padding in pixels to use on the bottom of the viewer window
+     * @see http://doc.babylonjs.com/how_to/gui#position-and-size
+     */
+    public get paddingBottomInPixels(): number {
+        return this._windowContents.paddingBottomInPixels;
+    }
+
+    public set paddingBottom(value: string | number) {
+        this._windowContents.paddingBottom = value;
+    }
+
+    /**
+    * Creates a new ScrollViewer
+    * @param name of ScrollViewer
+    */
+    constructor(
+        /** name of ScrollViewer */
+        public name?: string) {
+        super(name);
+
+        this.onDirtyObservable.add(() => {
+            this._horizontalBarSpace.color = this.color;
+            this._verticalBarSpace.color = this.color;
+            this._dragSpace.color = this.color;
+            this._updateScroller(this._windowContents);
+            if (this._windowContents.typeName === "TextBlock") {
+                this._updateTextBlock(this._windowContents);
+            }
+        });
+
+        this.onPointerEnterObservable.add(() => {
+            this._pointerIsOver = true;
+        });
+
+        this.onPointerOutObservable.add(() => {
+            this._pointerIsOver = false;
+        });
+
+        this._grid = new Grid();
+        this._horizontalBar = new Slider();
+        this._verticalBar = new Slider();
+
+        this._window = new Container();
+        this._window.horizontalAlignment = Control.HORIZONTAL_ALIGNMENT_LEFT;
+        this._window.verticalAlignment = Control.VERTICAL_ALIGNMENT_TOP;
+
+        this._windowContents = new Control();
+        this._window.addControl(this._windowContents);
+
+        this._width = new ValueAndUnit(0.25, ValueAndUnit.UNITMODE_PERCENTAGE, false);
+        this._height = new ValueAndUnit(0.25, ValueAndUnit.UNITMODE_PERCENTAGE, false);
+        this._background = "black";
+
+        this.fontSize = "16px";
+
+        this._grid.addColumnDefinition(1, true);
+        this._grid.addColumnDefinition(this._scrollGridWidth, true);
+        this._grid.addRowDefinition(1, true);
+        this._grid.addRowDefinition(this._scrollGridHeight, true);
+
+        this.addControl(this._grid);
+        this._grid.addControl(this._window, 0, 0);
+
+        this._verticalBar.paddingLeft = 0;
+        this._verticalBar.width = "25px";
+        this._verticalBar.value = 0;
+        this._verticalBar.maximum = 100;
+        this._verticalBar.horizontalAlignment = Control.HORIZONTAL_ALIGNMENT_CENTER;
+        this._verticalBar.verticalAlignment = Control.VERTICAL_ALIGNMENT_CENTER;
+        this._verticalBar.left = 0.05;
+        this._verticalBar.isThumbClamped = true;
+        this._verticalBar.color = "grey";
+        this._verticalBar.borderColor = "#444444";
+        this._verticalBar.background = "white";
+        this._verticalBar.isVertical = true;
+        this._verticalBar.rotation = Math.PI;
+
+        this._verticalBarSpace = new Rectangle();
+        this._verticalBarSpace.horizontalAlignment = Control.HORIZONTAL_ALIGNMENT_LEFT;
+        this._verticalBarSpace.verticalAlignment = Control.VERTICAL_ALIGNMENT_TOP;
+        this._verticalBarSpace.color = this.color;
+        this._verticalBarSpace.thickness = 1;
+        this._grid.addControl(this._verticalBarSpace, 0, 1);
+        this._verticalBarSpace.addControl(this._verticalBar);
+
+        this._verticalBar.onValueChangedObservable.add((value) => {
+            this._window.top = value * this._endTop / 100 + "px";
+        });
+
+        this._horizontalBar.paddingLeft = 0;
+        this._horizontalBar.height = "25px";
+        this._horizontalBar.value = 0;
+        this._horizontalBar.maximum = 100;
+        this._horizontalBar.horizontalAlignment = Control.HORIZONTAL_ALIGNMENT_CENTER;
+        this._horizontalBar.verticalAlignment = Control.VERTICAL_ALIGNMENT_CENTER;
+        this._horizontalBar.left = 0.05;
+        this._horizontalBar.isThumbClamped = true;
+        this._horizontalBar.color = "grey";
+        this._horizontalBar.borderColor = "#444444";
+        this._horizontalBar.background = "white";
+
+        this._horizontalBarSpace = new Rectangle();
+        this._horizontalBarSpace.horizontalAlignment = Control.HORIZONTAL_ALIGNMENT_LEFT;
+        this._horizontalBarSpace.verticalAlignment = Control.VERTICAL_ALIGNMENT_TOP;
+        this._horizontalBarSpace.color = this.color;
+        this._horizontalBarSpace.thickness = 1;
+        this._grid.addControl(this._horizontalBarSpace, 1, 0);
+        this._horizontalBarSpace.addControl(this._horizontalBar);
+
+        this._horizontalBar.onValueChangedObservable.add((value) => {
+            this._window.left = value * this._endLeft / 100 + "px";
+        });
+
+        this._dragSpace = new Rectangle();
+        this._dragSpace.color = this.color;
+        this._dragSpace.thickness = 2;
+        this._dragSpace.background = this._barColor;
+        this._grid.addControl(this._dragSpace, 1, 1);
+    }
+
+    /**
+     * Gets or sets the mouse wheel precision
+     * from 0 to 1 with a default value of 0.05
+     * */
+    public get wheelPrecision(): number {
+        return this._wheelPrecision;
+    }
+
+    public set wheelPrecision(value: number) {
+        if (this._wheelPrecision === value) {
+            return;
+        }
+
+        if (value < 0) {
+            value = 0;
+        }
+
+        if (value > 1) {
+            value = 1;
+        }
+
+        this._wheelPrecision = value;
+    }
+
+    /** Gets or sets the bar color */
+    public get barColor(): string {
+        return this._barColor;
+    }
+
+    public set barColor(color: string) {
+        if (this._barColor === color) {
+            return;
+        }
+
+        this._barColor = color;
+        this._horizontalBar.color = color;
+        this._verticalBar.color = color;
+        this._dragSpace.background = color;
+    }
+
+    /** Gets or sets the bar color */
+    public get barBorderColor(): string {
+        return this._barBorderColor;
+    }
+
+    public set barBorderColor(color: string) {
+        if (this._barBorderColor === color) {
+            return;
+        }
+
+        this._barBorderColor = color;
+        this._horizontalBar.borderColor = color;
+        this._verticalBar.borderColor = color;
+    }
+
+    /** Gets or sets the bar background */
+    public get barBackground(): string {
+        return this._barBackground;
+    }
+
+    public set barBackground(color: string) {
+        if (this._barBackground === color) {
+            return;
+        }
+
+        this._barBackground = color;
+        this._horizontalBar.background = color;
+        this._verticalBar.background = color;
+    }
+
+    /** @hidden */
+    protected _additionalProcessing(parentMeasure: Measure, context: CanvasRenderingContext2D): void {
+        super._additionalProcessing(parentMeasure, context);
+
+        let viewerWidth = this._width.getValueInPixel(this._host, parentMeasure.width);
+        let viewerHeight = this._height.getValueInPixel(this._host, parentMeasure.height);
+
+        let innerWidth = viewerWidth - this._scrollGridWidth - 2 * this.thickness;
+        let innerHeight = viewerHeight  - this._scrollGridHeight - 2 * this.thickness;
+        this._horizontalBar.width = (innerWidth * 0.8) + "px";
+        this._verticalBar.height = (innerHeight * 0.8) + "px";
+
+        this._grid.setColumnDefinition(0, innerWidth, true);
+        this._grid.setRowDefinition(0, innerHeight, true);
+
+        this._attachWheel();
+    }
+
+    /** @hidden */
+    private _updateScroller(windowContents: Control): void {
+
+        let windowContentsWidth: number  = parseFloat(windowContents.width.toString());
+        if (windowContents._width.unit === 0) {
+            this._widthScale = windowContentsWidth / 100;
+            windowContentsWidth = this._host.getSize().width * this._widthScale;
+            windowContents.width = windowContentsWidth + "px";
+        }
+
+        let windowContentsHeight: number  = parseFloat(windowContents.height.toString());
+        if (windowContents._height.unit === 0) {
+            this._heightScale = windowContentsHeight / 100;
+            windowContentsHeight = this._host.getSize().height * this._heightScale;
+            windowContents.height = this._host.getSize().height * this._heightScale + "px";
+        }
+
+        this._window.width = windowContents.width;
+        this._window.height = windowContents.height;
+        this._windowContents.width = windowContents.width;
+        this._windowContents.height = windowContents.height;
+
+        let viewerWidth = this._width.getValueInPixel(this._host, this._host.getSize().width);
+        let viewerHeight = this._height.getValueInPixel(this._host, this._host.getSize().height);
+
+        let innerWidth = viewerWidth - this._scrollGridWidth - 2 * this.thickness;
+        let innerHeight = viewerHeight  - this._scrollGridHeight - 2 * this.thickness;
+
+        if (windowContentsWidth <= innerWidth) {
+            this._grid.setRowDefinition(0, viewerHeight - 2 * this.thickness , true);
+            this._grid.setRowDefinition(1, 0, true);
+            this._horizontalBar.isVisible = false;
+        }
+        else {
+            this._grid.setRowDefinition(0, innerHeight, true);
+            this._grid.setRowDefinition(1, this._scrollGridHeight, true);
+            this._horizontalBar.isVisible = true;
+        }
+
+        if (windowContentsHeight < innerHeight) {
+            this._grid.setColumnDefinition(0, viewerWidth - 2 * this.thickness, true);
+            this._grid.setColumnDefinition(1, 0, true);
+            this._verticalBar.isVisible = false;
+        }
+        else {
+            this._grid.setColumnDefinition(0, innerWidth, true);
+            this._grid.setColumnDefinition(1, this._scrollGridWidth, true);
+            this._verticalBar.isVisible = true;
+        }
+
+        this._endLeft = innerWidth - windowContentsWidth;
+        this._endTop = innerHeight - windowContentsHeight;
+    }
+
+    /** @hidden */
+    private _updateTextBlock(windowContents: Control): void {
+        let viewerWidth = this._width.getValueInPixel(this._host, this._host.getSize().width);
+        let innerWidth = viewerWidth - this._scrollGridWidth - 2 * this.thickness;
+
+        windowContents.width = innerWidth + "px";
+
+        this._window.width = windowContents.width;
+        this._windowContents.width = windowContents.width;
+
+        (<TextBlock>windowContents).onLinesReadyObservable.add(() => {
+            let windowContentsHeight = (this.fontOffset.height) * (<TextBlock>windowContents).lines.length + windowContents.paddingTopInPixels + windowContents.paddingBottomInPixels;
+            windowContents.height = windowContentsHeight + "px";
+            this._window.height = windowContents.height;
+            this._windowContents.height = windowContents.height;
+            this._updateScroller(windowContents);
+        });
+    }
+
+    /** @hidden */
+    private _attachWheel() {
+        let scene = this._host.getScene();
+        this._onPointerObserver = scene!.onPointerObservable.add((pi, state) => {
+            if (!this._pointerIsOver || pi.type !== BABYLON.PointerEventTypes.POINTERWHEEL) {
+                return;
+            }
+            if (this._verticalBar.isVisible == true) {
+                if ((<MouseWheelEvent>pi.event).deltaY < 0 && this._verticalBar.value > 0) {
+                    this._verticalBar.value -= this._wheelPrecision * 100;
+                } else if ((<MouseWheelEvent>pi.event).deltaY > 0 && this._verticalBar.value < this._verticalBar.maximum) {
+                    this._verticalBar.value += this._wheelPrecision * 100;
+                }
+            }
+            if (this._horizontalBar.isVisible == true) {
+                if ((<MouseWheelEvent>pi.event).deltaX < 0 && this._horizontalBar.value < this._horizontalBar.maximum) {
+                    this._horizontalBar.value += this._wheelPrecision * 100;
+                } else if ((<MouseWheelEvent>pi.event).deltaX > 0 && this._horizontalBar.value > 0) {
+                    this._horizontalBar.value -= this._wheelPrecision * 100;
+                }
+            }
+        });
+    }
+
+    /** Releases associated resources */
+    public dispose() {
+        let scene = this._host.getScene();
+        if (scene && this._onPointerObserver) {
+            scene.onPointerObservable.remove(this._onPointerObserver);
+        }
+        super.dispose();
+    }
+}

+ 5 - 1
inspector/src/components/actionTabs/actionTabs.scss

@@ -5,6 +5,10 @@
     bottom: 0px;
 }
 
+#__resizable_base__ {
+    display: none;
+}
+
 #actionTabs {
     background: #333333;
     height: 100%;
@@ -731,7 +735,7 @@
                         margin-right: 5px;
 
                         select {
-                            min-width: 115px;
+                            width: 115px;
                         }
                     }                    
                 }                   

+ 3 - 2
inspector/src/components/actionTabs/actionTabsComponent.tsx

@@ -17,6 +17,7 @@ interface IActionTabsComponentProps {
     noCommands?: boolean,
     noHeader?: boolean,
     noExpand?: boolean,
+    noClose?: boolean,
     popupMode?: boolean,
     onPopup?: () => void,
     onClose?: () => void,
@@ -101,7 +102,7 @@ export class ActionTabsComponent extends React.Component<IActionTabsComponentPro
                 <div id="actionTabs">
                     {
                         !this.props.noHeader &&
-                        <HeaderComponent title="INSPECTOR" handleBack={true} noCommands={this.props.noCommands} onClose={() => this.onClose()} onPopup={() => this.onPopup()} onSelectionChangedObservable={this.props.globalState.onSelectionChangedObservable} />
+                        <HeaderComponent title="INSPECTOR" handleBack={true} noClose={this.props.noClose} noExpand={this.props.noExpand} noCommands={this.props.noCommands} onClose={() => this.onClose()} onPopup={() => this.onPopup()} onSelectionChangedObservable={this.props.globalState.onSelectionChangedObservable} />
                     }
                     {this.renderContent()}
                 </div>
@@ -124,7 +125,7 @@ export class ActionTabsComponent extends React.Component<IActionTabsComponentPro
             <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()} onSelectionChangedObservable={this.props.globalState.onSelectionChangedObservable} />
+                    <HeaderComponent title="INSPECTOR" handleBack={true} noClose={this.props.noClose} noExpand={this.props.noExpand} noCommands={this.props.noCommands} onClose={() => this.onClose()} onPopup={() => this.onPopup()} onSelectionChangedObservable={this.props.globalState.onSelectionChangedObservable} />
                 }
                 {this.renderContent()}
             </Resizable>

+ 31 - 6
inspector/src/components/actionTabs/lineContainerComponent.tsx

@@ -9,14 +9,26 @@ interface ILineContainerComponentProps {
 }
 
 export class LineContainerComponent extends React.Component<ILineContainerComponentProps, { isExpanded: boolean }> {
+    private static _InMemoryStorage: {[key: string]: boolean};
+    
     constructor(props: ILineContainerComponentProps) {
         super(props);
 
         let initialState: boolean;
 
-        if (typeof (Storage) !== "undefined" && localStorage.getItem(this.props.title) !== null) {
-            initialState = localStorage.getItem(this.props.title) === "true";
-        } else {
+        try
+        { 
+            if (LineContainerComponent._InMemoryStorage && LineContainerComponent._InMemoryStorage[this.props.title] !== undefined) {
+                initialState = LineContainerComponent._InMemoryStorage[this.props.title];
+            } else if (typeof (Storage) !== "undefined" && localStorage.getItem(this.props.title) !== null) {
+                initialState = localStorage.getItem(this.props.title) === "true";
+            } else {
+                initialState = !this.props.closed;
+            }   
+        }
+        catch (e) {
+            LineContainerComponent._InMemoryStorage = {};
+            LineContainerComponent._InMemoryStorage[this.props.title] = !this.props.closed
             initialState = !this.props.closed;
         }
 
@@ -24,10 +36,23 @@ export class LineContainerComponent extends React.Component<ILineContainerCompon
     }
 
     switchExpandedState(): void {
-        if (typeof (Storage) !== "undefined") {
-            localStorage.setItem(this.props.title, !this.state.isExpanded ? "true" : "false");
+        const newState = !this.state.isExpanded;
+        
+        try
+        { 
+            if (LineContainerComponent._InMemoryStorage) {
+                LineContainerComponent._InMemoryStorage[this.props.title] = newState;
+            } else if (typeof (Storage) !== "undefined") {
+                localStorage.setItem(this.props.title, newState ? "true" : "false");
+            }
+        }
+        catch (e) {
+            LineContainerComponent._InMemoryStorage = {};
+            LineContainerComponent._InMemoryStorage[this.props.title] = newState;
         }
-        this.setState({ isExpanded: !this.state.isExpanded });
+        
+        this.setState({ isExpanded: newState });
+
     }
 
     renderHeader() {

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

@@ -33,7 +33,7 @@ export class OptionsLineComponent extends React.Component<IOptionsLineComponentP
         }
 
         const newValue = nextProps.target[nextProps.propertyName];
-        if (newValue !== nextState.value) {
+        if (newValue != null && newValue !== nextState.value) {
             nextState.value = newValue;
             return true;
         }

+ 8 - 10
inspector/src/components/actionTabs/tabs/propertyGrids/materials/texturePropertyGridComponent.tsx

@@ -7,7 +7,6 @@ import { TextLineComponent } from "../../../lines/textLineComponent";
 import { CheckBoxLineComponent } from "../../../lines/checkBoxLineComponent";
 import { TextureLineComponent } from "../../../lines/textureLineComponent";
 import { FloatLineComponent } from "../../../lines/floatLineComponent";
-import { AdvancedDynamicTexture } from "babylonjs-gui";
 import { OptionsLineComponent } from "../../../lines/optionsLineComponent";
 import { FileButtonLineComponent } from "../../../lines/fileButtonLineComponent";
 import { LockObject } from "../lockObject";
@@ -47,7 +46,6 @@ export class TexturePropertyGridComponent extends React.Component<ITextureProper
 
     render() {
         const texture = this.props.texture;
-        const adtTexture = texture instanceof AdvancedDynamicTexture ? texture as AdvancedDynamicTexture : null;
 
         var samplingMode = [
             { label: "Nearest", value: BABYLON.Texture.NEAREST_NEAREST },
@@ -76,14 +74,14 @@ export class TexturePropertyGridComponent extends React.Component<ITextureProper
                     }
                 </LineContainerComponent>
                 {
-                    adtTexture &&
+                    (texture as any).rootContainer &&
                     <LineContainerComponent title="ADVANCED TEXTURE PROPERTIES">
-                        <SliderLineComponent label="Render scale" minimum={0.1} maximum={5} step={0.1} target={adtTexture} propertyName="renderScale" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
-                        <CheckBoxLineComponent label="Premultiply alpha" target={adtTexture} propertyName="premulAlpha" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
-                        <FloatLineComponent lockObject={this.props.lockObject} label="Ideal width" target={adtTexture} propertyName="idealWidth" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
-                        <FloatLineComponent lockObject={this.props.lockObject} label="Ideal height" target={adtTexture} propertyName="idealHeight" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
-                        <CheckBoxLineComponent label="Use smallest ideal" target={adtTexture} propertyName="useSmallestIdeal" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
-                        <CheckBoxLineComponent label="Render at ideal size" target={adtTexture} propertyName="renderAtIdealSize" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                        <SliderLineComponent label="Render scale" minimum={0.1} maximum={5} step={0.1} target={texture} propertyName="renderScale" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                        <CheckBoxLineComponent label="Premultiply alpha" target={texture} propertyName="premulAlpha" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                        <FloatLineComponent lockObject={this.props.lockObject} label="Ideal width" target={texture} propertyName="idealWidth" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                        <FloatLineComponent lockObject={this.props.lockObject} label="Ideal height" target={texture} propertyName="idealHeight" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                        <CheckBoxLineComponent label="Use smallest ideal" target={texture} propertyName="useSmallestIdeal" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                        <CheckBoxLineComponent label="Render at ideal size" target={texture} propertyName="renderAtIdealSize" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
                     </LineContainerComponent>
                 }
                 <LineContainerComponent title="TRANSFORM">
@@ -92,7 +90,7 @@ export class TexturePropertyGridComponent extends React.Component<ITextureProper
                         <div>
                             <FloatLineComponent lockObject={this.props.lockObject} label="U offset" target={texture} propertyName="uOffset" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
                             <FloatLineComponent lockObject={this.props.lockObject} label="V offset" target={texture} propertyName="vOffset" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
-                            <FloatLineComponent lockObject={this.props.lockObject} label="V scale" target={texture} propertyName="uScale" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                            <FloatLineComponent lockObject={this.props.lockObject} label="U scale" target={texture} propertyName="uScale" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
                             <FloatLineComponent lockObject={this.props.lockObject} label="V scale" target={texture} propertyName="vScale" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
                             <FloatLineComponent lockObject={this.props.lockObject} label="U angle" target={texture} propertyName="uAng" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
                             <FloatLineComponent lockObject={this.props.lockObject} label="V angle" target={texture} propertyName="vAng" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />

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

@@ -40,9 +40,9 @@ export class AxesViewerComponent extends React.Component<IAxisViewerComponentPro
         const y = new BABYLON.Vector3(0, 1, 0);
         const z = new BABYLON.Vector3(0, 0, 1);
 
-        viewer.xAxisMesh!.reservedDataStore = { hidden: true };
-        viewer.yAxisMesh!.reservedDataStore = { hidden: true };
-        viewer.zAxisMesh!.reservedDataStore = { hidden: true };
+        viewer.xAxis.reservedDataStore = { hidden: true };
+        viewer.yAxis.reservedDataStore = { hidden: true };
+        viewer.zAxis.reservedDataStore = { hidden: true };
 
         node.reservedDataStore.onBeforeRenderObserver = scene.onBeforeRenderObservable.add(() => {
             let matrix = node.getWorldMatrix();

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

@@ -5,6 +5,10 @@
     bottom: 0px;
 }
 
+#__resizable_base__ {
+    display: none;
+}
+
 #embed {
     background: #333333;
     height: 100%;

+ 4 - 2
inspector/src/components/embedHost/embedHostComponent.tsx

@@ -14,6 +14,8 @@ interface IEmbedHostComponentProps {
     scene: Scene,
     globalState: GlobalState,
     popupMode: boolean,
+    noClose?: boolean,
+    noExpand?: boolean,
     onClose: () => void,
     onPopup: () => void
 }
@@ -80,7 +82,7 @@ export class EmbedHostComponent extends React.Component<IEmbedHostComponentProps
         if (this.props.popupMode) {
             return (
                 <div id="embed">
-                    <HeaderComponent title="INSPECTOR" handleBack={true} onClose={() => this.props.onClose()} onPopup={() => this.props.onPopup()} onSelectionChangedObservable={this.props.globalState.onSelectionChangedObservable} />
+                    <HeaderComponent title="INSPECTOR" noClose={this.props.noClose} noExpand={this.props.noExpand} handleBack={true} onClose={() => this.props.onClose()} onPopup={() => this.props.onPopup()} onSelectionChangedObservable={this.props.globalState.onSelectionChangedObservable} />
                     {this.renderContent()}
                 </div>
             );
@@ -100,7 +102,7 @@ export class EmbedHostComponent extends React.Component<IEmbedHostComponentProps
 
         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()} onSelectionChangedObservable={this.props.globalState.onSelectionChangedObservable} />
+                <HeaderComponent title="INSPECTOR" noClose={this.props.noClose} noExpand={this.props.noExpand} handleBack={true} onClose={() => this.props.onClose()} onPopup={() => this.props.onPopup()} onSelectionChangedObservable={this.props.globalState.onSelectionChangedObservable} />
                 {this.renderContent()}
             </Resizable>
         );

+ 2 - 1
inspector/src/components/headerComponent.tsx

@@ -7,6 +7,7 @@ export interface IHeaderComponentProps {
     title: string,
     handleBack?: boolean,
     noExpand?: boolean,
+    noClose?: boolean,
     noCommands?: boolean,
     onPopup: () => void,
     onClose: () => void,
@@ -89,7 +90,7 @@ export class HeaderComponent extends React.Component<IHeaderComponentProps, { is
                         </div>
                     }
                     {
-                        !this.props.noCommands &&
+                        !this.props.noCommands && !this.props.noClose &&
                         <div className="close" onClick={() => this.props.onClose()}>
                             <FontAwesomeIcon icon={faTimes} />
                         </div>

+ 2 - 2
inspector/src/components/sceneExplorer/entities/cameraTreeItemComponent.tsx

@@ -38,10 +38,10 @@ export class CameraTreeItemComponent extends React.Component<ICameraTreeItemComp
         const camera = this.props.camera;
         const scene = camera.getScene();
         this._onActiveCameraObserver = scene.onActiveCameraChanged.add(() => {
-            if (this.state.isActive) {
+            // This will deactivate the previous camera when the camera is changed. Multiple camera's cycle frequently so only do this for single cameras
+            if (this.state.isActive && scene.activeCameras.length <= 1) {
                 camera.detachControl(scene.getEngine().getRenderingCanvas()!);
             }
-
             this.setState({ isActive: scene.activeCamera === camera });
         });
     }

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

@@ -9,6 +9,10 @@
     }
 }
 
+#__resizable_base__ {
+    display: none;
+}
+
 #sceneExplorer {
     background: #333333;
     height: 100%;

+ 3 - 2
inspector/src/components/sceneExplorer/sceneExplorerComponent.tsx

@@ -33,6 +33,7 @@ interface ISceneExplorerComponentProps {
     noCommands?: boolean,
     noHeader?: boolean,
     noExpand?: boolean,
+    noClose?: boolean,
     extensibilityGroups?: IExplorerExtensibilityGroup[],
     globalState: GlobalState,
     popupMode?: boolean,
@@ -234,7 +235,7 @@ export class SceneExplorerComponent extends React.Component<ISceneExplorerCompon
                 <div id="sceneExplorer">
                     {
                         !this.props.noHeader &&
-                        <HeaderComponent title="SCENE EXPLORER" noCommands={this.props.noCommands} onClose={() => this.onClose()} onPopup={() => this.onPopup()} />
+                        <HeaderComponent title="SCENE EXPLORER" noClose={this.props.noClose} noExpand={this.props.noExpand} noCommands={this.props.noCommands} onClose={() => this.onClose()} onPopup={() => this.onPopup()} />
                     }
                     {this.renderContent()}
                 </div>
@@ -273,7 +274,7 @@ export class SceneExplorerComponent extends React.Component<ISceneExplorerCompon
             <Resizable tabIndex={-1} id="sceneExplorer" ref="sceneExplorer" size={{ height: "100%" }} minWidth={300} maxWidth={600} minHeight="100%" enable={{ top: false, right: true, bottom: false, left: false, topRight: false, bottomRight: false, bottomLeft: false, topLeft: false }} onKeyDown={keyEvent => this.processKeys(keyEvent)}>
                 {
                     !this.props.noHeader &&
-                    <HeaderComponent title="SCENE EXPLORER" noExpand={this.props.noExpand} noCommands={this.props.noCommands} onClose={() => this.onClose()} onPopup={() => this.onPopup()} />
+                    <HeaderComponent title="SCENE EXPLORER" noClose={this.props.noClose} noExpand={this.props.noExpand} noCommands={this.props.noCommands} onClose={() => this.onClose()} onPopup={() => this.onPopup()} />
                 }
                 {this.renderContent()}
             </Resizable>

+ 10 - 2
inspector/src/inspector.ts

@@ -69,6 +69,7 @@ export class Inspector {
                 embedMode: options.embedMode,
                 handleResize: options.handleResize,
                 enablePopup: options.enablePopup,
+                enableClose: options.enablePopup,
                 explorerExtensibility: options.explorerExtensibility
             };
         }
@@ -98,6 +99,7 @@ export class Inspector {
             const sceneExplorerElement = React.createElement(SceneExplorerComponent, {
                 scene, globalState: this._GlobalState,
                 extensibilityGroups: options.explorerExtensibility,
+                noClose: !options.enableClose,
                 noExpand: !options.enablePopup, popupMode: options.popup, onPopup: () => {
                     ReactDOM.unmountComponentAtNode(this._SceneExplorerHost!);
 
@@ -152,7 +154,9 @@ export class Inspector {
         if (this._ActionTabsHost) {
             this._OpenedPane++;
             const actionTabsElement = React.createElement(ActionTabsComponent, {
-                globalState: this._GlobalState, scene: scene, noExpand: !options.enablePopup, popupMode: options.popup, onPopup: () => {
+                globalState: this._GlobalState, scene: scene,
+                noClose: !options.enableClose,
+                noExpand: !options.enablePopup, popupMode: options.popup, onPopup: () => {
                     ReactDOM.unmountComponentAtNode(this._ActionTabsHost!);
 
                     this._RemoveElementFromDOM(this._ActionTabsHost);
@@ -205,7 +209,10 @@ export class Inspector {
         if (this._EmbedHost) {
             this._OpenedPane++;
             const embedHostElement = React.createElement(EmbedHostComponent, {
-                globalState: this._GlobalState, scene: scene, popupMode: options.popup, onPopup: () => {
+                globalState: this._GlobalState, scene: scene,
+                    noExpand: !options.enablePopup,
+                    noClose: !options.enableClose,
+                    popupMode: options.popup, onPopup: () => {
                     ReactDOM.unmountComponentAtNode(this._EmbedHost!);
 
                     if (options.popup) {
@@ -300,6 +307,7 @@ export class Inspector {
             showExplorer: true,
             showInspector: true,
             embedMode: false,
+            enableClose: true,
             handleResize: true,
             enablePopup: true,
             ...userOptions

+ 16 - 1
inspector/src/tools.ts

@@ -16,12 +16,27 @@ export class Tools {
         return false;
     }
 
+    private static _RecursiveRemoveHiddenMeshesAndHoistChildren(items: Array<any>) {
+        let result: Array<any> = [];
+        for (let i of items) {
+            // If the mesh is hidden, add it's children that are not hidden, this will handle the case of bounding box parenting for bounding box gizmo
+            if (i.reservedDataStore && i.reservedDataStore.hidden) {
+                Tools._RecursiveRemoveHiddenMeshesAndHoistChildren(i.getChildMeshes()).forEach((m) => {
+                    result.push(m);
+                });
+            }else {
+                result.push(i);
+            }
+        }
+        return result;
+    }
+
     public static SortAndFilter(parent: any, items: any[]): any[] {
         if (!items) {
             return [];
         }
 
-        const finalArray = items.filter((i) => !i.reservedDataStore || !i.reservedDataStore.hidden);
+        const finalArray = Tools._RecursiveRemoveHiddenMeshesAndHoistChildren(items);
 
         if (parent && parent.reservedDataStore && parent.reservedDataStore.detachedChildren) {
             finalArray.push(...parent.reservedDataStore.detachedChildren);

+ 11 - 6
loaders/src/glTF/2.0/babylon.glTFLoader.ts

@@ -1702,14 +1702,19 @@ module BABYLON.GLTF2 {
             const samplerData = this._loadSampler(`/samplers/${sampler.index}`, sampler);
 
             const image = ArrayItem.Get(`${context}/source`, this.gltf.images, texture.source);
-            let textureURL: Nullable<string> = null;
-            if (image.uri && !Tools.IsBase64(image.uri) && this.babylonScene.getEngine().textureFormatInUse) {
-                // If an image uri and a texture format is set like (eg. KTX) load from url instead of blob to support texture format and fallback
-                textureURL = this._uniqueRootUrl + image.uri;
+            let url: Nullable<string> = null;
+            if (image.uri) {
+                if (Tools.IsBase64(image.uri)) {
+                    url = image.uri;
+                }
+                else if (this.babylonScene.getEngine().textureFormatInUse) {
+                    // If an image uri and a texture format is set like (eg. KTX) load from url instead of blob to support texture format and fallback
+                    url = this._rootUrl + image.uri;
+                }
             }
 
             const deferred = new Deferred<void>();
-            const babylonTexture = new Texture(textureURL, this.babylonScene, samplerData.noMipMaps, false, samplerData.samplingMode, () => {
+            const babylonTexture = new Texture(url, this.babylonScene, samplerData.noMipMaps, false, samplerData.samplingMode, () => {
                 if (!this._disposed) {
                     deferred.resolve();
                 }
@@ -1720,7 +1725,7 @@ module BABYLON.GLTF2 {
             });
             promises.push(deferred.promise);
 
-            if (!textureURL) {
+            if (!url) {
                 promises.push(this.loadImageAsync(`/images/${image.index}`, image).then((data) => {
                     const name = image.uri || `${this._fileName}#image${image.index}`;
                     const dataUrl = `data:${this._uniqueRootUrl}${name}`;

+ 1 - 0
localDev/index.html

@@ -8,6 +8,7 @@
     <script src="https://cdnjs.cloudflare.com/ajax/libs/dat-gui/0.6.2/dat.gui.min.js"></script>
     <script src="../dist/preview%20release/cannon.js"></script>
     <script src="../dist/preview%20release/Oimo.js"></script>
+    <script src="../dist/preview%20release/ammo.js"></script>
     <script src="../dist/preview%20release/gltf_validator.js"></script>
     <script src="../Tools/DevLoader/BabylonLoader.js"></script>
     <script src="src/webgl-debug.js"></script>

+ 1 - 1
package.json

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

+ 1 - 0
sandbox/index-local.html

@@ -5,6 +5,7 @@
     <title>BabylonJS - Sandbox</title>
     <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1">
     <link href="index.css" rel="stylesheet" />
+    <script src="../dist/preview%20release/ammo.js"></script>
     <script src="../dist/preview%20release/cannon.js"></script>
     <script src="../dist/preview%20release/Oimo.js"></script>
     <script src="../dist/preview%20release/gltf_validator.js"></script>

+ 1 - 0
sandbox/index.html

@@ -31,6 +31,7 @@
     <script src="https://code.jquery.com/pep/0.4.2/pep.min.js"></script>
     <script src="https://playground.babylonjs.com/js/libs/split.js"></script>
 
+    <script src="https://preview.babylonjs.com/ammo.js"></script>
     <script src="https://preview.babylonjs.com/cannon.js"></script>
     <script src="https://preview.babylonjs.com/Oimo.js"></script>
     <script src="https://preview.babylonjs.com/gltf_validator.js"></script>

+ 0 - 1
src/Cameras/babylon.arcRotateCamera.ts

@@ -1054,7 +1054,6 @@ module BABYLON {
             var camRight = <ArcRotateCamera>this._rigCameras[1];
 
             camLeft.beta = camRight.beta = this.beta;
-            camLeft.radius = camRight.radius = this.radius;
 
             switch (this.cameraRigMode) {
                 case Camera.RIG_MODE_STEREOSCOPIC_ANAGLYPH:

+ 97 - 78
src/Debug/babylon.axesViewer.ts

@@ -7,78 +7,89 @@ module BABYLON.Debug {
      * The Axes viewer will show 3 axes in a specific point in space
      */
     export class AxesViewer {
-        private _xmesh: Nullable<AbstractMesh>;
-        private _ymesh: Nullable<AbstractMesh>;
-        private _zmesh: Nullable<AbstractMesh>;
+        private _xAxis: TransformNode;
+        private _yAxis: TransformNode;
+        private _zAxis: TransformNode;
+        private _tmpVector = new Vector3();
+        private _scaleLinesFactor = 4;
+        private _instanced = false;
 
         /**
          * Gets the hosting scene
          */
-        public scene: Nullable<Scene>;
+        public scene: Scene;
+
         /**
          * Gets or sets a number used to scale line length
          */
         public scaleLines = 1;
 
-        /** Gets the mesh used to render x-axis */
-        public get xAxisMesh(): Nullable<AbstractMesh> {
-            return this._xmesh;
-        }
-
-        /** Gets the mesh used to render x-axis */
-        public get yAxisMesh(): Nullable<AbstractMesh> {
-            return this._ymesh;
+        /** Gets the node hierarchy used to render x-axis */
+        public get xAxis(): TransformNode {
+            return this._xAxis;
         }
 
-        /** Gets the mesh used to render x-axis */
-        public get zAxisMesh(): Nullable<AbstractMesh> {
-            return this._zmesh;
+        /** Gets the node hierarchy used to render y-axis */
+        public get yAxis(): TransformNode {
+            return this._yAxis;
         }
 
-        private static _recursiveChangeRenderingGroupId(mesh: AbstractMesh, id: number) {
-            mesh.renderingGroupId = id;
-            mesh.getChildMeshes().forEach((m) => {
-                AxesViewer._recursiveChangeRenderingGroupId(m, id);
-            });
+        /** Gets the node hierarchy used to render z-axis */
+        public get zAxis(): TransformNode {
+            return this._zAxis;
         }
 
         /**
          * Creates a new AxesViewer
          * @param scene defines the hosting scene
          * @param scaleLines defines a number used to scale line length (1 by default)
+         * @param renderingGroupId defines a number used to set the renderingGroupId of the meshes (2 by default)
+         * @param xAxis defines the node hierarchy used to render the x-axis
+         * @param yAxis defines the node hierarchy used to render the y-axis
+         * @param zAxis defines the node hierarchy used to render the z-axis
          */
-        constructor(scene: Scene, scaleLines = 1) {
+        constructor(scene: Scene, scaleLines = 1, renderingGroupId: Nullable<number> = 2, xAxis?: TransformNode, yAxis?: TransformNode, zAxis?: TransformNode) {
             this.scaleLines = scaleLines;
 
-            var greenColoredMaterial = new BABYLON.StandardMaterial("", scene);
-            greenColoredMaterial.disableLighting = true;
-            greenColoredMaterial.emissiveColor = BABYLON.Color3.Green().scale(0.5);
-
-            var redColoredMaterial = new BABYLON.StandardMaterial("", scene);
-            redColoredMaterial.disableLighting = true;
-            redColoredMaterial.emissiveColor = BABYLON.Color3.Red().scale(0.5);
-
-            var blueColoredMaterial = new BABYLON.StandardMaterial("", scene);
-            blueColoredMaterial.disableLighting = true;
-            blueColoredMaterial.emissiveColor = BABYLON.Color3.Blue().scale(0.5);
+            if (!xAxis) {
+                var redColoredMaterial = new StandardMaterial("", scene);
+                redColoredMaterial.disableLighting = true;
+                redColoredMaterial.emissiveColor = Color3.Red().scale(0.5);
+                xAxis = AxisDragGizmo._CreateArrow(scene, redColoredMaterial);
+            }
 
-            this._xmesh = BABYLON.AxisDragGizmo._CreateArrow(scene, redColoredMaterial);
-            this._ymesh = BABYLON.AxisDragGizmo._CreateArrow(scene, greenColoredMaterial);
-            this._zmesh = BABYLON.AxisDragGizmo._CreateArrow(scene, blueColoredMaterial);
+            if (!yAxis) {
+                var greenColoredMaterial = new StandardMaterial("", scene);
+                greenColoredMaterial.disableLighting = true;
+                greenColoredMaterial.emissiveColor = Color3.Green().scale(0.5);
+                yAxis = AxisDragGizmo._CreateArrow(scene, greenColoredMaterial);
+            }
 
-            this._xmesh.rotationQuaternion = new BABYLON.Quaternion();
-            this._xmesh.scaling.scaleInPlace(4);
-            this._ymesh.rotationQuaternion = new BABYLON.Quaternion();
-            this._ymesh.scaling.scaleInPlace(4);
-            this._zmesh.rotationQuaternion = new BABYLON.Quaternion();
-            this._zmesh.scaling.scaleInPlace(4);
+            if (!zAxis) {
+                var blueColoredMaterial = new StandardMaterial("", scene);
+                blueColoredMaterial.disableLighting = true;
+                blueColoredMaterial.emissiveColor = Color3.Blue().scale(0.5);
+                zAxis = AxisDragGizmo._CreateArrow(scene, blueColoredMaterial);
+            }
 
-            AxesViewer._recursiveChangeRenderingGroupId(this._xmesh, 2);
-            AxesViewer._recursiveChangeRenderingGroupId(this._ymesh, 2);
-            AxesViewer._recursiveChangeRenderingGroupId(this._zmesh, 2);
+            this._xAxis = xAxis;
+            this._xAxis.rotationQuaternion = new Quaternion();
+            this._xAxis.scaling.setAll(this.scaleLines * this._scaleLinesFactor);
+            this._yAxis = yAxis;
+            this._yAxis.rotationQuaternion = new Quaternion();
+            this._yAxis.scaling.setAll(this.scaleLines * this._scaleLinesFactor);
+            this._zAxis = zAxis;
+            this._zAxis.rotationQuaternion = new Quaternion();
+            this._zAxis.scaling.setAll(this.scaleLines * this._scaleLinesFactor);
+
+            if (renderingGroupId != null) {
+                AxesViewer._SetRenderingGroupId(this._xAxis, renderingGroupId);
+                AxesViewer._SetRenderingGroupId(this._yAxis, renderingGroupId);
+                AxesViewer._SetRenderingGroupId(this._zAxis, renderingGroupId);
+            }
 
             this.scene = scene;
-            this.update(new BABYLON.Vector3(), Vector3.Right(), Vector3.Up(), Vector3.Forward());
+            this.update(new Vector3(), Vector3.Right(), Vector3.Up(), Vector3.Forward());
         }
 
         /**
@@ -89,51 +100,59 @@ module BABYLON.Debug {
          * @param zaxis defines the z axis of the viewer
          */
         public update(position: Vector3, xaxis: Vector3, yaxis: Vector3, zaxis: Vector3): void {
-            if (this._xmesh) {
-                this._xmesh.position.copyFrom(position);
-
-                var cross = Vector3.Cross(Vector3.Forward(), xaxis);
-                this._xmesh.rotationQuaternion!.set(cross.x, cross.y, cross.z, 1 + Vector3.Dot(Vector3.Forward(), xaxis));
-                this._xmesh.rotationQuaternion!.normalize();
-            }
-            if (this._ymesh) {
-                this._ymesh.position.copyFrom(position);
-
-                var cross = Vector3.Cross(Vector3.Forward(), yaxis);
-                this._ymesh.rotationQuaternion!.set(cross.x, cross.y, cross.z, 1 + Vector3.Dot(Vector3.Forward(), yaxis));
-                this._ymesh.rotationQuaternion!.normalize();
-            }
-            if (this._zmesh) {
-                this._zmesh.position.copyFrom(position);
-
-                var cross = Vector3.Cross(Vector3.Forward(), zaxis);
-                this._zmesh.rotationQuaternion!.set(cross.x, cross.y, cross.z, 1 + Vector3.Dot(Vector3.Forward(), zaxis));
-                this._zmesh.rotationQuaternion!.normalize();
-            }
+            this._xAxis.position.copyFrom(position);
+            xaxis.scaleToRef(-1, this._tmpVector);
+            this._xAxis.setDirection(this._tmpVector);
+            this._xAxis.scaling.setAll(this.scaleLines * this._scaleLinesFactor);
+
+            this._yAxis.position.copyFrom(position);
+            yaxis.scaleToRef(-1, this._tmpVector);
+            this._yAxis.setDirection(this._tmpVector);
+            this._yAxis.scaling.setAll(this.scaleLines * this._scaleLinesFactor);
+
+            this._zAxis.position.copyFrom(position);
+            zaxis.scaleToRef(-1, this._tmpVector);
+            this._zAxis.setDirection(this._tmpVector);
+            this._zAxis.scaling.setAll(this.scaleLines * this._scaleLinesFactor);
+        }
 
+        /**
+         * Creates an instance of this axes viewer.
+         * @returns a new axes viewer with instanced meshes
+         */
+        public createInstance(): AxesViewer {
+            const xAxis = AxisDragGizmo._CreateArrowInstance(this.scene, this._xAxis);
+            const yAxis = AxisDragGizmo._CreateArrowInstance(this.scene, this._yAxis);
+            const zAxis = AxisDragGizmo._CreateArrowInstance(this.scene, this._zAxis);
+            const axesViewer = new AxesViewer(this.scene, this.scaleLines, null, xAxis, yAxis, zAxis);
+            axesViewer._instanced = true;
+            return axesViewer;
         }
 
         /** Releases resources */
         public dispose() {
-
-            if (this._xmesh) {
-                this._xmesh.dispose();
+            if (this._xAxis) {
+                this._xAxis.dispose(false, !this._instanced);
+                delete this._xAxis;
             }
 
-            if (this._ymesh) {
-                this._ymesh.dispose();
+            if (this._yAxis) {
+                this._yAxis.dispose(false, !this._instanced);
+                delete this._yAxis;
             }
 
-            if (this._zmesh) {
-                this._zmesh.dispose();
+            if (this._zAxis) {
+                this._zAxis.dispose(false, !this._instanced);
+                delete this._zAxis;
             }
 
-            this._xmesh = null;
-            this._ymesh = null;
-            this._zmesh = null;
-
-            this.scene = null;
+            delete this.scene;
         }
 
+        private static _SetRenderingGroupId(node: TransformNode, id: number) {
+            node.getChildMeshes().forEach((mesh) => {
+                mesh.renderingGroupId = id;
+            });
+        }
     }
 }

+ 5 - 1
src/Debug/babylon.debugLayer.ts

@@ -65,6 +65,10 @@ module BABYLON {
          */
         enablePopup?: boolean;
         /**
+         * Allow the panes to be closed by users (default: true)
+         */
+        enableClose?: boolean;
+        /**
          * Optional list of extensibility entries
          */
         explorerExtensibility?: IExplorerExtensibilityGroup[];
@@ -160,7 +164,7 @@ module BABYLON {
          * @returns true if visible otherwise, false
          */
         public isVisible(): boolean {
-            return this.BJSINSPECTOR.Inspector.IsVisible;
+            return this.BJSINSPECTOR && this.BJSINSPECTOR.Inspector.IsVisible;
         }
 
         /**

+ 6 - 6
src/Engine/babylon.engine.ts

@@ -481,7 +481,7 @@ module BABYLON {
          * Returns the current version of the framework
          */
         public static get Version(): string {
-            return "4.0.0-alpha.8";
+            return "4.0.0-alpha.10";
         }
 
         /**
@@ -4234,7 +4234,7 @@ module BABYLON {
          * * A base64 string of in-line texture data, e.g. 'data:image/jpg;base64,/...'
          * * An indicator that data being passed using the buffer parameter, e.g. 'data:mytexture.jpg'
          * @param noMipmap defines a boolean indicating that no mipmaps shall be generated.  Ignored for compressed textures.  They must be in the file
-         * @param invertY when true, image is flipped when loaded.  You probably want true. Ignored for compressed textures.  Must be flipped in the file
+         * @param invertY when true, image is flipped when loaded.  You probably want true. Certain compressed textures may invert this if their default is inverted (eg. ktx)
          * @param scene needed for loading to the correct scene
          * @param samplingMode mode with should be used sample / access the texture (Default: BABYLON.Texture.TRILINEAR_SAMPLINGMODE)
          * @param onLoad optional callback to be called upon successful completion
@@ -4306,7 +4306,7 @@ module BABYLON {
                         customFallback = true;
                         excludeLoaders.push(loader);
                         Tools.Warn((loader.constructor as any).name + " failed when trying to load " + texture.url + ", falling back to the next supported loader");
-                        this.createTexture(urlArg, noMipmap, invertY, scene, samplingMode, null, onError, buffer, texture, undefined, undefined, excludeLoaders);
+                        this.createTexture(urlArg, noMipmap, texture.invertY, scene, samplingMode, null, onError, buffer, texture, undefined, undefined, excludeLoaders);
                         return;
                     }
                 }
@@ -4316,7 +4316,7 @@ module BABYLON {
                         texture.onLoadedObservable.remove(onLoadObserver);
                     }
                     if (Tools.UseFallbackTexture) {
-                        this.createTexture(Tools.fallbackTexture, noMipmap, invertY, scene, samplingMode, null, onError, buffer, texture);
+                        this.createTexture(Tools.fallbackTexture, noMipmap, texture.invertY, scene, samplingMode, null, onError, buffer, texture);
                         return;
                     }
                 }
@@ -4333,7 +4333,7 @@ module BABYLON {
                         if (loadFailed) {
                             onInternalError("TextureLoader failed to load data");
                         } else {
-                            this._prepareWebGLTexture(texture, scene, width, height, invertY, !loadMipmap, isCompressed, () => {
+                            this._prepareWebGLTexture(texture, scene, width, height, texture.invertY, !loadMipmap, isCompressed, () => {
                                 done();
                                 return false;
                             }, samplingMode);
@@ -4356,7 +4356,7 @@ module BABYLON {
                         texture._buffer = img;
                     }
 
-                    this._prepareWebGLTexture(texture, scene, img.width, img.height, invertY, noMipmap, false, (potWidth, potHeight, continuationCallback) => {
+                    this._prepareWebGLTexture(texture, scene, img.width, img.height, texture.invertY, noMipmap, false, (potWidth, potHeight, continuationCallback) => {
                         let gl = this._gl;
                         var isPot = (img.width === potWidth && img.height === potHeight);
                         let internalFormat = format ? this._getInternalFormat(format) : ((extension === ".jpg") ? gl.RGB : gl.RGBA);

+ 30 - 21
src/Gizmos/babylon.axisDragGizmo.ts

@@ -16,28 +16,38 @@ module BABYLON {
          * Event that fires each time the gizmo snaps to a new location.
          * * snapDistance is the the change in distance
          */
-        public onSnapObservable = new Observable<{snapDistance: number}>();
+        public onSnapObservable = new Observable<{ snapDistance: number }>();
 
         /** @hidden */
-        public static _CreateArrow(scene: Scene, material: StandardMaterial) {
-            var arrow = new BABYLON.AbstractMesh("", scene);
-            var arrowMesh = BABYLON.MeshBuilder.CreateCylinder("yPosMesh", {diameterTop: 0, height: 1.5, diameterBottom: 0.75, tessellation: 96}, scene);
-            var arrowTail = BABYLON.MeshBuilder.CreateLines("yPosMesh", {points: [new Vector3(0, 0, 0), new Vector3(0, 1.1, 0)]}, scene);
-            arrowTail.color = material.emissiveColor;
-            arrow.addChild(arrowMesh);
-            arrow.addChild(arrowTail);
+        public static _CreateArrow(scene: Scene, material: StandardMaterial): TransformNode {
+            var arrow = new BABYLON.TransformNode("arrow", scene);
+            var cylinder = BABYLON.MeshBuilder.CreateCylinder("cylinder", { diameterTop: 0, height: 1.5, diameterBottom: 0.75, tessellation: 96 }, scene);
+            var line = BABYLON.MeshBuilder.CreateLines("line", { points: [new Vector3(0, 0, 0), new Vector3(0, 1.1, 0)] }, scene);
+            line.color = material.emissiveColor;
+            cylinder.parent = arrow;
+            line.parent = arrow;
 
             // Position arrow pointing in its drag axis
-            arrowMesh.scaling.scaleInPlace(0.05);
-            arrowMesh.material = material;
-            arrowMesh.rotation.x = Math.PI / 2;
-            arrowMesh.position.z += 0.3;
-            arrowTail.scaling.scaleInPlace(0.26);
-            arrowTail.rotation.x = Math.PI / 2;
-            arrowTail.material = material;
+            cylinder.scaling.scaleInPlace(0.05);
+            cylinder.material = material;
+            cylinder.rotation.x = Math.PI / 2;
+            cylinder.position.z += 0.3;
+            line.scaling.scaleInPlace(0.26);
+            line.rotation.x = Math.PI / 2;
+            line.material = material;
             return arrow;
         }
 
+        /** @hidden */
+        public static _CreateArrowInstance(scene: Scene, arrow: TransformNode): TransformNode {
+            const instance = new BABYLON.TransformNode("arrow", scene);
+            for (const mesh of arrow.getChildMeshes()) {
+                const childInstance = (mesh as Mesh).createInstance(mesh.name);
+                childInstance.parent = instance;
+            }
+            return instance;
+        }
+
         /**
          * Creates an AxisDragGizmo
          * @param gizmoLayer The utility layer the gizmo will be added to
@@ -61,14 +71,13 @@ module BABYLON {
 
             arrow.lookAt(this._rootMesh.position.subtract(dragAxis));
             arrow.scaling.scaleInPlace(1 / 3);
-
-            this._rootMesh.addChild(arrow);
+            arrow.parent = this._rootMesh;
 
             var currentSnapDragDistance = 0;
             var tmpVector = new Vector3();
-            var tmpSnapEvent = {snapDistance: 0};
+            var tmpSnapEvent = { snapDistance: 0 };
             // Add drag behavior to handle events when the gizmo is dragged
-            this.dragBehavior = new PointerDragBehavior({dragAxis: dragAxis});
+            this.dragBehavior = new PointerDragBehavior({ dragAxis: dragAxis });
             this.dragBehavior.moveAttached = false;
             this._rootMesh.addBehavior(this.dragBehavior);
 
@@ -81,13 +90,13 @@ module BABYLON {
                         this.attachedMesh.parent.computeWorldMatrix().invertToRef(tmpMatrix);
                         tmpMatrix.setTranslationFromFloats(0, 0, 0);
                         Vector3.TransformCoordinatesToRef(event.delta, tmpMatrix, localDelta);
-                    }else {
+                    } else {
                         localDelta.copyFrom(event.delta);
                     }
                     // Snapping logic
                     if (this.snapDistance == 0) {
                         this.attachedMesh.position.addInPlace(localDelta);
-                    }else {
+                    } else {
                         currentSnapDragDistance += event.dragDistance;
                         if (Math.abs(currentSnapDragDistance) > this.snapDistance) {
                             var dragSteps = Math.floor(Math.abs(currentSnapDragDistance) / this.snapDistance);

+ 14 - 13
src/Gizmos/babylon.boundingBoxGizmo.ts

@@ -408,8 +408,8 @@ module BABYLON {
                             rotateSpheres[index].position.addInPlace(new BABYLON.Vector3(-this._boundingDimensions.x / 2, -this._boundingDimensions.y / 2, -this._boundingDimensions.z / 2));
                             rotateSpheres[index].lookAt(Vector3.Cross(Vector3.Forward(), rotateSpheres[index].position.normalizeToNew()).normalizeToNew().add(rotateSpheres[index].position));
                         }
-                        if (this.fixedDragMeshScreenSize) {
-                            rotateSpheres[index].absolutePosition.subtractToRef(this.gizmoLayer.utilityLayerScene.activeCamera!.position, this._tmpVector);
+                        if (this.fixedDragMeshScreenSize && this.gizmoLayer.utilityLayerScene.activeCamera) {
+                            rotateSpheres[index].absolutePosition.subtractToRef(this.gizmoLayer.utilityLayerScene.activeCamera.position, this._tmpVector);
                             var distanceFromCamera = this.rotationSphereSize * this._tmpVector.length() / this.fixedDragMeshScreenSizeDistanceFactor;
                             rotateSpheres[index].scaling.set(distanceFromCamera, distanceFromCamera, distanceFromCamera);
                         } else {
@@ -429,8 +429,8 @@ module BABYLON {
                         if (scaleBoxes[index]) {
                             scaleBoxes[index].position.set(this._boundingDimensions.x * i, this._boundingDimensions.y * j, this._boundingDimensions.z * k);
                             scaleBoxes[index].position.addInPlace(new BABYLON.Vector3(-this._boundingDimensions.x / 2, -this._boundingDimensions.y / 2, -this._boundingDimensions.z / 2));
-                            if (this.fixedDragMeshScreenSize) {
-                                scaleBoxes[index].absolutePosition.subtractToRef(this.gizmoLayer.utilityLayerScene.activeCamera!.position, this._tmpVector);
+                            if (this.fixedDragMeshScreenSize && this.gizmoLayer.utilityLayerScene.activeCamera) {
+                                scaleBoxes[index].absolutePosition.subtractToRef(this.gizmoLayer.utilityLayerScene.activeCamera.position, this._tmpVector);
                                 var distanceFromCamera = this.scaleBoxSize * this._tmpVector.length() / this.fixedDragMeshScreenSizeDistanceFactor;
                                 scaleBoxes[index].scaling.set(distanceFromCamera, distanceFromCamera, distanceFromCamera);
                             } else {
@@ -497,15 +497,6 @@ module BABYLON {
             var box = BABYLON.MeshBuilder.CreateBox("box", { size: 1 }, mesh.getScene());
             var boundingMinMax = mesh.getHierarchyBoundingVectors();
             boundingMinMax.max.subtractToRef(boundingMinMax.min, box.scaling);
-            box.position.set((boundingMinMax.max.x + boundingMinMax.min.x) / 2, (boundingMinMax.max.y + boundingMinMax.min.y) / 2, (boundingMinMax.max.z + boundingMinMax.min.z) / 2);
-
-            // Restore original positions
-            mesh.addChild(box);
-            mesh.rotationQuaternion.copyFrom(oldRot);
-            mesh.position.copyFrom(oldPos);
-
-            // Reverse parenting
-            mesh.removeChild(box);
 
             // Adjust scale to avoid undefined behavior when adding child
             if (box.scaling.y === 0) {
@@ -518,6 +509,16 @@ module BABYLON {
                 box.scaling.z = BABYLON.Epsilon;
             }
 
+            box.position.set((boundingMinMax.max.x + boundingMinMax.min.x) / 2, (boundingMinMax.max.y + boundingMinMax.min.y) / 2, (boundingMinMax.max.z + boundingMinMax.min.z) / 2);
+
+            // Restore original positions
+            mesh.addChild(box);
+            mesh.rotationQuaternion.copyFrom(oldRot);
+            mesh.position.copyFrom(oldPos);
+
+            // Reverse parenting
+            mesh.removeChild(box);
+
             box.addChild(mesh);
             box.visibility = 0;
             return box;

+ 8 - 0
src/Materials/Textures/Loaders/babylon.ktxTextureLoader.ts

@@ -32,6 +32,10 @@ module BABYLON {
          */
         public transformUrl(rootUrl: string, textureFormatInUse: Nullable<string>): string {
             var lastDot = rootUrl.lastIndexOf('.');
+            if (lastDot != -1 && rootUrl.substring(lastDot + 1) == "ktx") {
+                // Already transformed
+                return rootUrl;
+            }
             return (lastDot > -1 ? rootUrl.substring(0, lastDot) : rootUrl) + textureFormatInUse;
         }
 
@@ -60,6 +64,8 @@ module BABYLON {
                 return;
             }
 
+            // Need to invert vScale as invertY via UNPACK_FLIP_Y_WEBGL is not supported by compressed texture
+            texture._invertVScale = !texture.invertY;
             var engine = texture.getEngine();
             var ktx = new KhronosTextureContainer(data, 6);
 
@@ -84,6 +90,8 @@ module BABYLON {
          */
         public loadData(data: ArrayBuffer, texture: InternalTexture,
             callback: (width: number, height: number, loadMipmap: boolean, isCompressed: boolean, done: () => void, loadFailed: boolean) => void): void {
+            // Need to invert vScale as invertY via UNPACK_FLIP_Y_WEBGL is not supported by compressed texture
+            texture._invertVScale = !texture.invertY;
             var ktx = new KhronosTextureContainer(data, 1);
 
             callback(ktx.pixelWidth, ktx.pixelHeight, false, true, () => {

+ 2 - 0
src/Materials/Textures/babylon.internalTexture.ts

@@ -138,6 +138,8 @@ module BABYLON {
 
         // Private
         /** @hidden */
+        public _invertVScale = false;
+        /** @hidden */
         public _initialSlot = -1;
         /** @hidden */
         public _designatedSlot = -1;

+ 3 - 0
src/Materials/Textures/babylon.texture.ts

@@ -247,6 +247,9 @@ module BABYLON {
             scene.getEngine().onBeforeTextureInitObservable.notifyObservers(this);
 
             let load = () => {
+                if (this._texture && this._texture._invertVScale) {
+                    this.vScale *= -1;
+                }
                 if (this.onLoadObservable.hasObservers()) {
                     this.onLoadObservable.notifyObservers(this);
                 }

+ 1 - 1
src/Mesh/babylon.transformNode.ts

@@ -233,7 +233,7 @@ module BABYLON {
                 return false;
             }
 
-            if (this.infiniteDistance !== this._cache.infiniteDistance) {
+            if (this.infiniteDistance) {
                 return false;
             }
 

+ 1 - 1
src/Particles/babylon.solidParticleSystem.ts

@@ -898,7 +898,7 @@ module BABYLON {
                     const minBbox = modelBoundingInfo.minimum.multiplyToRef(particleScaling, tempVectors[1]);
                     const maxBbox = modelBoundingInfo.maximum.multiplyToRef(particleScaling, tempVectors[2]);
 
-                    const bSphereCenter = maxBbox.addToRef(minBbox, tempVectors[3]).scaleInPlace(0.5);
+                    const bSphereCenter = maxBbox.addToRef(minBbox, tempVectors[3]).scaleInPlace(0.5).addInPlace(particleGlobalPosition);
                     const halfDiag = maxBbox.subtractToRef(minBbox, tempVectors[4]).scaleInPlace(0.5 * this._bSphereRadiusFactor);
                     const bSphereMinBbox = bSphereCenter.subtractToRef(halfDiag, tempVectors[1]);
                     const bSphereMaxBbox = bSphereCenter.addToRef(halfDiag, tempVectors[2]);

+ 694 - 0
src/Physics/Plugins/babylon.ammoJSPlugin.ts

@@ -0,0 +1,694 @@
+module BABYLON {
+    declare var Ammo: any;
+
+    /**
+     * AmmoJS Physics plugin
+     * @see https://doc.babylonjs.com/how_to/using_the_physics_engine
+     * @see https://github.com/kripken/ammo.js/
+     */
+    export class AmmoJSPlugin implements IPhysicsEnginePlugin {
+        /**
+         * Reference to the Ammo library
+         */
+        public bjsAMMO: any;
+        /**
+         * Created ammoJS world which physics bodies are added to
+         */
+        public world: any;
+        /**
+         * Name of the plugin
+         */
+        public name: string = "AmmoJSPlugin";
+
+        private _timeStep: number = 1 / 60;
+        private _maxSteps = 5;
+        private _tmpQuaternion = new BABYLON.Quaternion();
+        private _tmpAmmoTransform: any;
+        private _tmpAmmoQuaternion: any;
+        private _tmpAmmoConcreteContactResultCallback: any;
+        private _collisionConfiguration: any;
+        private _dispatcher: any;
+        private _overlappingPairCache: any;
+        private _solver: any;
+        private _tmpAmmoVectorA: any;
+        private _tmpAmmoVectorB: any;
+        private _tmpAmmoVectorC: any;
+        private _tmpContactCallbackResult = false;
+
+        private static readonly KINEMATIC_FLAG = 2;
+        private static readonly DISABLE_DEACTIVATION_FLAG = 4;
+
+        /**
+         * Initializes the ammoJS plugin
+         * @param _useDeltaForWorldStep if the time between frames should be used when calculating physics steps (Default: true)
+         */
+        public constructor(private _useDeltaForWorldStep: boolean = true) {
+            if (typeof Ammo === "function") {
+                Ammo();
+            }
+            this.bjsAMMO = Ammo;
+            if (!this.isSupported()) {
+                Tools.Error("AmmoJS is not available. Please make sure you included the js file.");
+                return;
+            }
+
+            // Initialize the physics world
+            this._collisionConfiguration  = new this.bjsAMMO.btDefaultCollisionConfiguration();
+            this._dispatcher              = new this.bjsAMMO.btCollisionDispatcher(this._collisionConfiguration);
+            this._overlappingPairCache    = new this.bjsAMMO.btDbvtBroadphase();
+            this._solver                  = new this.bjsAMMO.btSequentialImpulseConstraintSolver();
+            this.world           = new this.bjsAMMO.btDiscreteDynamicsWorld(this._dispatcher, this._overlappingPairCache, this._solver, this._collisionConfiguration);
+            this._tmpAmmoConcreteContactResultCallback = new this.bjsAMMO.ConcreteContactResultCallback();
+            this._tmpAmmoConcreteContactResultCallback.addSingleResult = () => { this._tmpContactCallbackResult = true; };
+
+            // Create temp ammo variables
+            this._tmpAmmoTransform = new this.bjsAMMO.btTransform();
+            this._tmpAmmoTransform.setIdentity();
+            this._tmpAmmoQuaternion = new this.bjsAMMO.btQuaternion(0, 0, 0, 1);
+            this._tmpAmmoVectorA = new this.bjsAMMO.btVector3(0, 0, 0);
+            this._tmpAmmoVectorB = new this.bjsAMMO.btVector3(0, 0, 0);
+            this._tmpAmmoVectorC = new this.bjsAMMO.btVector3(0, 0, 0);
+        }
+
+        /**
+         * Sets the gravity of the physics world (m/(s^2))
+         * @param gravity Gravity to set
+         */
+        public setGravity(gravity: Vector3): void {
+            this._tmpAmmoVectorA.setValue(gravity.x, gravity.y, gravity.z);
+            this.world.setGravity(this._tmpAmmoVectorA);
+        }
+
+        /**
+         * Amount of time to step forward on each frame (only used if useDeltaForWorldStep is false in the constructor)
+         * @param timeStep timestep to use in seconds
+         */
+        public setTimeStep(timeStep: number) {
+            this._timeStep = timeStep;
+        }
+
+        /**
+         * Gets the current timestep (only used if useDeltaForWorldStep is false in the constructor)
+         * @returns the current timestep in seconds
+         */
+        public getTimeStep(): number {
+            return this._timeStep;
+        }
+
+        // Ammo's contactTest and contactPairTest take a callback that runs synchronously, wrap them so that they are easier to consume
+        private _isImpostorInContact(impostor: PhysicsImpostor) {
+            this._tmpContactCallbackResult = false;
+            this.world.contactTest(impostor.physicsBody, this._tmpAmmoConcreteContactResultCallback);
+            return this._tmpContactCallbackResult;
+        }
+        // Ammo's collision events have some weird quirks
+        // contactPairTest fires too many events as it fires events even when objects are close together but contactTest does not
+        // so only fire event if both contactTest and contactPairTest have a hit
+        private _isImpostorPairInContact(impostorA: PhysicsImpostor, impostorB: PhysicsImpostor) {
+            this._tmpContactCallbackResult = false;
+            this.world.contactPairTest(impostorA.physicsBody, impostorB.physicsBody, this._tmpAmmoConcreteContactResultCallback);
+            return this._tmpContactCallbackResult;
+        }
+
+        // Ammo's behavior when maxSteps > 0 does not behave as described in docs
+        // @see http://www.bulletphysics.org/mediawiki-1.5.8/index.php/Stepping_The_World
+        //
+        // When maxSteps is 0 do the entire simulation in one step
+        // When maxSteps is > 0, run up to maxStep times, if on the last step the (remaining step - fixedTimeStep) is < fixedTimeStep, the remainder will be used for the step. (eg. if remainder is 1.001 and fixedTimeStep is 1 the last step will be 1.001, if instead it did 2 steps (1, 0.001) issues occuered when having a tiny step in ammo)
+        // Note: To get deterministic physics, timeStep would always need to be divisible by fixedTimeStep
+        private _stepSimulation(timeStep: number = 1 / 60, maxSteps: number = 10, fixedTimeStep: number = 1 / 60) {
+            if (maxSteps == 0) {
+                this.world.stepSimulation(timeStep, 0);
+            }else {
+                while (maxSteps > 0 && timeStep > 0) {
+                    if (timeStep - fixedTimeStep <  fixedTimeStep) {
+                        this.world.stepSimulation(timeStep, 0);
+                        timeStep = 0;
+                    }else {
+                        timeStep -= fixedTimeStep;
+                        this.world.stepSimulation(fixedTimeStep, 0);
+                    }
+                    maxSteps--;
+                }
+            }
+        }
+
+        /**
+         * Moves the physics simulation forward delta seconds and updates the given physics imposters
+         * Prior to the step the imposters physics location is set to the position of the babylon meshes
+         * After the step the babylon meshes are set to the position of the physics imposters
+         * @param delta amount of time to step forward
+         * @param impostors array of imposters to update before/after the step
+         */
+        public executeStep(delta: number, impostors: Array<PhysicsImpostor>): void {
+            for (var impostor of impostors) {
+                // Update physics world objects to match babylon world
+                impostor.beforeStep();
+            }
+
+            this._stepSimulation(this._useDeltaForWorldStep ? delta : this._timeStep, this._maxSteps);
+
+            for (var mainImpostor of impostors) {
+                // After physics update make babylon world objects match physics world objects
+                mainImpostor.afterStep();
+
+                // Handle collision event
+                if (mainImpostor._onPhysicsCollideCallbacks.length > 0) {
+                    if (this._isImpostorInContact(mainImpostor)) {
+                        for (var collideCallback of mainImpostor._onPhysicsCollideCallbacks) {
+                            for (var otherImpostor of  collideCallback.otherImpostors) {
+                                if (mainImpostor.physicsBody.isActive() || otherImpostor.physicsBody.isActive()) {
+                                    if (this._isImpostorPairInContact(mainImpostor, otherImpostor)) {
+                                        mainImpostor.onCollide({ body: otherImpostor.physicsBody });
+                                        otherImpostor.onCollide({ body: mainImpostor.physicsBody });
+                                    }
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        }
+
+        /**
+         * Applies an implulse on the imposter
+         * @param impostor imposter to apply impulse
+         * @param force amount of force to be applied to the imposter
+         * @param contactPoint the location to apply the impulse on the imposter
+         */
+        public applyImpulse(impostor: PhysicsImpostor, force: Vector3, contactPoint: Vector3) {
+            var worldPoint = this._tmpAmmoVectorA;
+            var impulse = this._tmpAmmoVectorB;
+            worldPoint.setValue(contactPoint.x, contactPoint.y, contactPoint.z);
+            impulse.setValue(force.x, force.y, force.z);
+
+            impostor.physicsBody.applyImpulse(impulse, worldPoint);
+        }
+
+        /**
+         * Applies a force on the imposter
+         * @param impostor imposter to apply force
+         * @param force amount of force to be applied to the imposter
+         * @param contactPoint the location to apply the force on the imposter
+         */
+        public applyForce(impostor: PhysicsImpostor, force: Vector3, contactPoint: Vector3) {
+            var worldPoint = this._tmpAmmoVectorA;
+            var impulse = this._tmpAmmoVectorB;
+            worldPoint.setValue(contactPoint.x, contactPoint.y, contactPoint.z);
+            impulse.setValue(force.x, force.y, force.z);
+
+            impostor.physicsBody.applyForce(impulse, worldPoint);
+        }
+
+        /**
+         * Creates a physics body using the plugin
+         * @param impostor the imposter to create the physics body on
+         */
+        public generatePhysicsBody(impostor: PhysicsImpostor) {
+            impostor._pluginData = {toDispose: []};
+
+            //parent-child relationship
+            if (impostor.parent) {
+                if (impostor.physicsBody) {
+                    this.removePhysicsBody(impostor);
+                    impostor.forceUpdate();
+                }
+                return;
+            }
+
+            if (impostor.isBodyInitRequired()) {
+                var colShape = this._createShape(impostor);
+                var mass          = impostor.getParam("mass");
+                impostor._pluginData.mass = mass;
+                var localInertia  = new Ammo.btVector3(0, 0, 0);
+                var startTransform  = new Ammo.btTransform();
+                startTransform.setIdentity();
+                if (mass !== 0) {
+                    colShape.calculateLocalInertia(mass, localInertia);
+                }
+                this._tmpAmmoVectorA.setValue(impostor.object.position.x, impostor.object.position.y, impostor.object.position.z);
+                this._tmpAmmoQuaternion.setValue(impostor.object.rotationQuaternion!.x, impostor.object.rotationQuaternion!.y, impostor.object.rotationQuaternion!.z, impostor.object.rotationQuaternion!.w);
+                startTransform.setOrigin(this._tmpAmmoVectorA);
+                startTransform.setRotation(this._tmpAmmoQuaternion);
+                var myMotionState = new Ammo.btDefaultMotionState(startTransform);
+                var rbInfo        = new Ammo.btRigidBodyConstructionInfo(mass, myMotionState, colShape, localInertia);
+                var body          = new Ammo.btRigidBody(rbInfo);
+
+                // Make objects kinematic if it's mass is 0
+                if (mass === 0) {
+                    body.setCollisionFlags(body.getCollisionFlags() | AmmoJSPlugin.KINEMATIC_FLAG);
+                    body.setActivationState(AmmoJSPlugin.DISABLE_DEACTIVATION_FLAG);
+                }
+
+                body.setRestitution(impostor.getParam("restitution"));
+                this.world.addRigidBody(body);
+                impostor.physicsBody = body;
+
+                impostor._pluginData.toDispose.concat([body, rbInfo, myMotionState, startTransform, localInertia, colShape]);
+            }
+        }
+
+        /**
+         * Removes the physics body from the imposter and disposes of the body's memory
+         * @param impostor imposter to remove the physics body from
+         */
+        public removePhysicsBody(impostor: PhysicsImpostor) {
+            if (this.world) {
+                this.world.removeRigidBody(impostor.physicsBody);
+
+                impostor._pluginData.toDispose.forEach((d: any) => {
+                    this.bjsAMMO.destroy(d);
+                });
+            }
+        }
+
+        /**
+         * Generates a joint
+         * @param impostorJoint the imposter joint to create the joint with
+         */
+        public generateJoint(impostorJoint: PhysicsImpostorJoint) {
+            var mainBody = impostorJoint.mainImpostor.physicsBody;
+            var connectedBody = impostorJoint.connectedImpostor.physicsBody;
+            if (!mainBody || !connectedBody) {
+                return;
+            }
+
+            var jointData = impostorJoint.joint.jointData;
+            if (!jointData.mainPivot) {
+                jointData.mainPivot = new Vector3(0, 0, 0);
+            }
+            if (!jointData.connectedPivot) {
+                jointData.connectedPivot = new Vector3(0, 0, 0);
+            }
+
+            var joint: any;
+            switch (impostorJoint.joint.type) {
+                case PhysicsJoint.BallAndSocketJoint:
+                    joint = new Ammo.btPoint2PointConstraint(mainBody, connectedBody, new Ammo.btVector3(jointData.mainPivot.x, jointData.mainPivot.y, jointData.mainPivot.z), new Ammo.btVector3(jointData.connectedPivot.x, jointData.connectedPivot.y, jointData.connectedPivot.z));
+                    break;
+                default:
+                    Tools.Warn("JointType not currently supported by the Ammo plugin, falling back to PhysicsJoint.BallAndSocketJoint");
+                    joint = new Ammo.btPoint2PointConstraint(mainBody, connectedBody, new Ammo.btVector3(jointData.mainPivot.x, jointData.mainPivot.y, jointData.mainPivot.z), new Ammo.btVector3(jointData.connectedPivot.x, jointData.connectedPivot.y, jointData.connectedPivot.z));
+                    break;
+            }
+            this.world.addConstraint(joint, true);
+            impostorJoint.joint.physicsJoint = joint;
+        }
+
+        /**
+         * Removes a joint
+         * @param impostorJoint the imposter joint to remove the joint from
+         */
+        public removeJoint(impostorJoint: PhysicsImpostorJoint) {
+            this.world.removeConstraint(impostorJoint.joint.physicsJoint);
+        }
+
+        // adds all verticies (including child verticies) to the triangle mesh
+        private _addMeshVerts(btTriangleMesh: any, topLevelObject: IPhysicsEnabledObject, object: IPhysicsEnabledObject) {
+            var triangleCount = 0;
+            if (object && object.getIndices && object.getWorldMatrix && object.getChildMeshes) {
+                var indices = object.getIndices();
+                if (!indices) {
+                    indices = [];
+                }
+                var vertexPositions = object.getVerticesData(BABYLON.VertexBuffer.PositionKind);
+                if (!vertexPositions) {
+                    vertexPositions = [];
+                }
+                object.computeWorldMatrix(false);
+                var faceCount = indices.length / 3;
+                for (var i = 0; i < faceCount; i++) {
+                    var triPoints = [];
+                    for (var point = 0; point < 3; point++) {
+                        var v = new BABYLON.Vector3(vertexPositions[(indices[(i * 3) + point] * 3) + 0], vertexPositions[(indices[(i * 3) + point] * 3) + 1], vertexPositions[(indices[(i * 3) + point] * 3) + 2]);
+                        v = Vector3.TransformCoordinates(v, object.getWorldMatrix());
+                        v.subtractInPlace(topLevelObject.position);
+                        var vec: any;
+                        if (point == 0) {
+                            vec = this._tmpAmmoVectorA;
+                        }else if (point == 1) {
+                            vec = this._tmpAmmoVectorB;
+                        }else {
+                            vec = this._tmpAmmoVectorC;
+                        }
+                        vec.setValue(v.x, v.y, v.z);
+
+                        triPoints.push(vec);
+                    }
+                    btTriangleMesh.addTriangle(triPoints[0], triPoints[1], triPoints[2]);
+                    triangleCount++;
+                }
+
+                object.getChildMeshes().forEach((m) => {
+                    triangleCount += this._addMeshVerts(btTriangleMesh, topLevelObject, m);
+                });
+            }
+            return triangleCount;
+        }
+
+        private _createShape(impostor: PhysicsImpostor, ignoreChildren= false) {
+            var object = impostor.object;
+
+            var returnValue: any;
+            var extendSize = impostor.getObjectExtendSize();
+
+            if (!ignoreChildren) {
+                var meshChildren = impostor.object.getChildMeshes ? impostor.object.getChildMeshes(true) : [];
+                if (meshChildren.length > 0) {
+                    returnValue = new Ammo.btCompoundShape();
+
+                    // Add shape of all children to the compound shape
+                    meshChildren.forEach((childMesh) => {
+                        var childImpostor = childMesh.getPhysicsImpostor();
+                        if (childImpostor) {
+                            var shape = this._createShape(childImpostor);
+                            this._tmpAmmoTransform.getOrigin().setValue(childMesh.position.x, childMesh.position.y, childMesh.position.z);
+                            this._tmpAmmoQuaternion.setValue(childMesh.rotationQuaternion!.x, childMesh.rotationQuaternion!.y, childMesh.rotationQuaternion!.z, childMesh.rotationQuaternion!.w);
+                            this._tmpAmmoTransform.setRotation(this._tmpAmmoQuaternion);
+                            returnValue.addChildShape(this._tmpAmmoTransform, shape);
+                            childImpostor.dispose();
+                        }
+                    });
+
+                    // Add parents shape as a child if present
+                    var shape = this._createShape(impostor, true);
+                    if (shape) {
+                        this._tmpAmmoTransform.getOrigin().setValue(0, 0, 0);
+                        //this._tmpAmmoQuaternion = new this.BJSAMMO.btQuaternion(0,0,0,1);
+                        this._tmpAmmoQuaternion.setValue(0, 0, 0, 1);
+                        this._tmpAmmoTransform.setRotation(this._tmpAmmoQuaternion);
+
+                        returnValue.addChildShape(this._tmpAmmoTransform, shape);
+                    }
+
+                    return returnValue;
+                }
+            }
+
+            switch (impostor.type) {
+                case PhysicsImpostor.SphereImpostor:
+                    returnValue = new Ammo.btSphereShape(extendSize.x / 2);
+                    break;
+                case PhysicsImpostor.CylinderImpostor:
+                this._tmpAmmoVectorA.setValue(extendSize.x / 2, extendSize.y / 2, extendSize.z / 2);
+                    returnValue = new Ammo.btCylinderShape(this._tmpAmmoVectorA);
+                    break;
+                case PhysicsImpostor.PlaneImpostor:
+                case PhysicsImpostor.BoxImpostor:
+                    this._tmpAmmoVectorA.setValue(extendSize.x / 2, extendSize.y / 2, extendSize.z / 2);
+                    returnValue = new Ammo.btBoxShape(this._tmpAmmoVectorA);
+                    break;
+                case PhysicsImpostor.MeshImpostor:
+                    var tetraMesh = new Ammo.btTriangleMesh();
+                    impostor._pluginData.toDispose.concat([tetraMesh]);
+                    var triangeCount = this._addMeshVerts(tetraMesh, object, object);
+                    if (triangeCount == 0) {
+                        returnValue = new Ammo.btCompoundShape();
+                    }else {
+                        returnValue = new Ammo.btBvhTriangleMeshShape(tetraMesh);
+                    }
+                    break;
+            }
+
+            return returnValue;
+        }
+
+        /**
+         * Sets the physics body position/rotation from the babylon mesh's position/rotation
+         * @param impostor imposter containing the physics body and babylon object
+         */
+        public setTransformationFromPhysicsBody(impostor: PhysicsImpostor) {
+            impostor.physicsBody.getMotionState().getWorldTransform(this._tmpAmmoTransform);
+            impostor.object.position.set(this._tmpAmmoTransform.getOrigin().x(), this._tmpAmmoTransform.getOrigin().y(), this._tmpAmmoTransform.getOrigin().z());
+
+            if (!impostor.object.rotationQuaternion) {
+                if (impostor.object.rotation) {
+                    this._tmpQuaternion.set(this._tmpAmmoTransform.getRotation().x(), this._tmpAmmoTransform.getRotation().y(), this._tmpAmmoTransform.getRotation().z(), this._tmpAmmoTransform.getRotation().w());
+                    this._tmpQuaternion.toEulerAnglesToRef(impostor.object.rotation);
+                }
+            }else {
+                impostor.object.rotationQuaternion.set(this._tmpAmmoTransform.getRotation().x(), this._tmpAmmoTransform.getRotation().y(), this._tmpAmmoTransform.getRotation().z(), this._tmpAmmoTransform.getRotation().w());
+            }
+        }
+
+        /**
+         * Sets the babylon object's position/rotation from the physics body's position/rotation
+         * @param impostor imposter containing the physics body and babylon object
+         * @param newPosition new position
+         * @param newRotation new rotation
+         */
+        public setPhysicsBodyTransformation(impostor: PhysicsImpostor, newPosition: Vector3, newRotation: Quaternion) {
+            var trans = impostor.physicsBody.getWorldTransform();
+
+            // If rotation/position has changed update and activate riged body
+            if (
+                trans.getOrigin().x() != newPosition.x ||
+                trans.getOrigin().y() != newPosition.y ||
+                trans.getOrigin().z() != newPosition.z ||
+                trans.getRotation().x() != newRotation.x ||
+                trans.getRotation().y() != newRotation.y ||
+                trans.getRotation().z() != newRotation.z ||
+                trans.getRotation().w() != newRotation.w
+            ) {
+                this._tmpAmmoVectorA.setValue(newPosition.x, newPosition.y, newPosition.z);
+                trans.setOrigin(this._tmpAmmoVectorA);
+
+                this._tmpAmmoQuaternion.setValue(newRotation.x, newRotation.y, newRotation.z, newRotation.w);
+                trans.setRotation(this._tmpAmmoQuaternion);
+                impostor.physicsBody.setWorldTransform(trans);
+
+                if (impostor.mass == 0) {
+                    // Kinematic objects must be updated using motion state
+                    var motionState = impostor.physicsBody.getMotionState();
+                    if (motionState) {
+                        motionState.setWorldTransform(trans);
+                    }
+                }else {
+                    impostor.physicsBody.activate();
+                }
+            }
+        }
+
+        /**
+         * If this plugin is supported
+         * @returns true if its supported
+         */
+        public isSupported(): boolean {
+            return this.bjsAMMO !== undefined;
+        }
+
+        /**
+         * Sets the linear velocity of the physics body
+         * @param impostor imposter to set the velocity on
+         * @param velocity velocity to set
+         */
+        public setLinearVelocity(impostor: PhysicsImpostor, velocity: Vector3) {
+            this._tmpAmmoVectorA.setValue(velocity.x, velocity.y, velocity.z);
+            impostor.physicsBody.setLinearVelocity(this._tmpAmmoVectorA);
+        }
+
+        /**
+         * Sets the angular velocity of the physics body
+         * @param impostor imposter to set the velocity on
+         * @param velocity velocity to set
+         */
+        public setAngularVelocity(impostor: PhysicsImpostor, velocity: Vector3) {
+            this._tmpAmmoVectorA.setValue(velocity.x, velocity.y, velocity.z);
+            impostor.physicsBody.setAngularVelocity(this._tmpAmmoVectorA);
+        }
+
+        /**
+         * gets the linear velocity
+         * @param impostor imposter to get linear velocity from
+         * @returns linear velocity
+         */
+        public getLinearVelocity(impostor: PhysicsImpostor): Nullable<Vector3> {
+            var v = impostor.physicsBody.getLinearVelocity();
+            if (!v) {
+                return null;
+            }
+            return new Vector3(v.x(), v.y(), v.z());
+        }
+
+        /**
+         * gets the angular velocity
+         * @param impostor imposter to get angular velocity from
+         * @returns angular velocity
+         */
+        public getAngularVelocity(impostor: PhysicsImpostor): Nullable<Vector3> {
+            var v = impostor.physicsBody.getAngularVelocity();
+            if (!v) {
+                return null;
+            }
+            return new Vector3(v.x(), v.y(), v.z());
+        }
+
+        /**
+         * Sets the mass of physics body
+         * @param impostor imposter to set the mass on
+         * @param mass mass to set
+         */
+        public setBodyMass(impostor: PhysicsImpostor, mass: number) {
+            impostor.physicsBody.setMassProps(mass);
+            impostor._pluginData.mass = mass;
+        }
+
+        /**
+         * Gets the mass of the physics body
+         * @param impostor imposter to get the mass from
+         * @returns mass
+         */
+        public getBodyMass(impostor: PhysicsImpostor): number {
+            return impostor._pluginData.mass;
+        }
+
+        /**
+         * Gets friction of the impostor
+         * @param impostor impostor to get friction from
+         * @returns friction value
+         */
+        public getBodyFriction(impostor: PhysicsImpostor): number {
+            return impostor.physicsBody.getFriction();
+        }
+
+        /**
+         * Sets friction of the impostor
+         * @param impostor impostor to set friction on
+         * @param friction friction value
+         */
+        public setBodyFriction(impostor: PhysicsImpostor, friction: number) {
+            impostor.physicsBody.setFriction(friction);
+        }
+
+        /**
+         * Gets restitution of the impostor
+         * @param impostor impostor to get restitution from
+         * @returns restitution value
+         */
+        public getBodyRestitution(impostor: PhysicsImpostor): number {
+            return impostor.physicsBody.getRestitution();
+        }
+
+        /**
+         * Sets resitution of the impostor
+         * @param impostor impostor to set resitution on
+         * @param restitution resitution value
+         */
+        public setBodyRestitution(impostor: PhysicsImpostor, restitution: number) {
+            impostor.physicsBody.setRestitution(restitution);
+        }
+
+        /**
+         * Sleeps the physics body and stops it from being active
+         * @param impostor impostor to sleep
+         */
+        public sleepBody(impostor: PhysicsImpostor) {
+            Tools.Warn("sleepBody is not currently supported by the Ammo physics plugin");
+        }
+
+        /**
+         * Activates the physics body
+         * @param impostor impostor to activate
+         */
+        public wakeUpBody(impostor: PhysicsImpostor) {
+            impostor.physicsBody.activate();
+        }
+
+        /**
+         * Updates the distance parameters of the joint
+         * @param joint joint to update
+         * @param maxDistance maximum distance of the joint
+         * @param minDistance minimum distance of the joint
+         */
+        public updateDistanceJoint(joint: PhysicsJoint, maxDistance: number, minDistance?: number) {
+            Tools.Warn("updateDistanceJoint is not currently supported by the Ammo physics plugin");
+        }
+
+        /**
+         * Sets a motor on the joint
+         * @param joint joint to set motor on
+         * @param speed speed of the motor
+         * @param maxForce maximum force of the motor
+         * @param motorIndex index of the motor
+         */
+        public setMotor(joint: IMotorEnabledJoint, speed?: number, maxForce?: number, motorIndex?: number) {
+            Tools.Warn("setMotor is not currently supported by the Ammo physics plugin");
+        }
+
+        /**
+         * Sets the motors limit
+         * @param joint joint to set limit on
+         * @param upperLimit upper limit
+         * @param lowerLimit lower limit
+         */
+        public setLimit(joint: IMotorEnabledJoint, upperLimit: number, lowerLimit?: number) {
+            Tools.Warn("setLimit is not currently supported by the Ammo physics plugin");
+        }
+
+        /**
+         * Syncs the position and rotation of a mesh with the impostor
+         * @param mesh mesh to sync
+         * @param impostor impostor to update the mesh with
+         */
+        public syncMeshWithImpostor(mesh: AbstractMesh, impostor: PhysicsImpostor) {
+            var body = impostor.physicsBody;
+
+            body.getMotionState().getWorldTransform(this._tmpAmmoTransform);
+
+            mesh.position.x = this._tmpAmmoTransform.getOrigin().x();
+            mesh.position.y = this._tmpAmmoTransform.getOrigin().y();
+            mesh.position.z = this._tmpAmmoTransform.getOrigin().z();
+
+            if (mesh.rotationQuaternion) {
+                mesh.rotationQuaternion.x = this._tmpAmmoTransform.getRotation().x();
+                mesh.rotationQuaternion.y = this._tmpAmmoTransform.getRotation().y();
+                mesh.rotationQuaternion.z = this._tmpAmmoTransform.getRotation().z();
+                mesh.rotationQuaternion.w = this._tmpAmmoTransform.getRotation().w();
+            }
+        }
+
+        /**
+         * Gets the radius of the impostor
+         * @param impostor impostor to get radius from
+         * @returns the radius
+         */
+        public getRadius(impostor: PhysicsImpostor): number {
+            var exntend = impostor.getObjectExtendSize();
+            return exntend.x / 2;
+        }
+
+        /**
+         * Gets the box size of the impostor
+         * @param impostor impostor to get box size from
+         * @param result the resulting box size
+         */
+        public getBoxSizeToRef(impostor: PhysicsImpostor, result: Vector3): void {
+            var exntend = impostor.getObjectExtendSize();
+            result.x = exntend.x;
+            result.y = exntend.y;
+            result.z = exntend.z;
+        }
+
+        /**
+         * Disposes of the impostor
+         */
+        public dispose() {
+            // Dispose of world
+            Ammo.destroy(this.world);
+            Ammo.destroy(this._solver);
+            Ammo.destroy(this._overlappingPairCache);
+            Ammo.destroy(this._dispatcher);
+            Ammo.destroy(this._collisionConfiguration);
+
+            // Dispose of tmp variables
+            Ammo.destroy(this._tmpAmmoVectorA);
+            Ammo.destroy(this._tmpAmmoVectorB);
+            Ammo.destroy(this._tmpAmmoVectorC);
+            Ammo.destroy(this._tmpAmmoTransform);
+            Ammo.destroy(this._tmpAmmoQuaternion);
+            Ammo.destroy(this._tmpAmmoConcreteContactResultCallback);
+
+            this.world = null;
+        }
+    }
+}

+ 5 - 2
src/Physics/babylon.physicsImpostor.ts

@@ -149,6 +149,9 @@ module BABYLON {
          */
         public static IDENTITY_QUATERNION = Quaternion.Identity();
 
+        /** @hidden */
+        public _pluginData: any;
+
         private _physicsEngine: Nullable<IPhysicsEngine>;
         //The native cannon/oimo/energy physics body object.
         private _physicsBody: any;
@@ -156,7 +159,8 @@ module BABYLON {
 
         private _onBeforePhysicsStepCallbacks = new Array<(impostor: PhysicsImpostor) => void>();
         private _onAfterPhysicsStepCallbacks = new Array<(impostor: PhysicsImpostor) => void>();
-        private _onPhysicsCollideCallbacks: Array<{ callback: (collider: PhysicsImpostor, collidedAgainst: PhysicsImpostor) => void, otherImpostors: Array<PhysicsImpostor> }> = [];
+        /** @hidden */
+        public _onPhysicsCollideCallbacks: Array<{ callback: (collider: PhysicsImpostor, collidedAgainst: PhysicsImpostor) => void, otherImpostors: Array<PhysicsImpostor> }> = [];
 
         private _deltaPosition: Vector3 = Vector3.Zero();
         private _deltaRotation: Quaternion;
@@ -410,7 +414,6 @@ module BABYLON {
                 this.object.rotationQuaternion = q;
                 //calculate the world matrix with the new rotation
                 this.object.computeWorldMatrix && this.object.computeWorldMatrix(true);
-
                 return size;
             } else {
                 return PhysicsImpostor.DEFAULT_OBJECT_SIZE;

+ 8 - 2
src/Rendering/babylon.utilityLayerRenderer.ts

@@ -88,7 +88,9 @@ module BABYLON {
 
             if (handleEvents) {
                 this._originalPointerObserver = originalScene.onPrePointerObservable.add((prePointerInfo, eventState) => {
-
+                    if (!this.utilityLayerScene.activeCamera) {
+                        return;
+                    }
                     if (!this.processAllEvents) {
                         if (prePointerInfo.type !== BABYLON.PointerEventTypes.POINTERMOVE
                             && prePointerInfo.type !== BABYLON.PointerEventTypes.POINTERUP
@@ -254,7 +256,11 @@ module BABYLON {
         }
 
         private _updateCamera() {
-            this.utilityLayerScene.activeCamera = this.originalScene.activeCamera;
+            if (this.originalScene.activeCameras.length > 1) {
+                this.utilityLayerScene.activeCamera = this.originalScene.activeCameras[0];
+            }else {
+                this.utilityLayerScene.activeCamera = this.originalScene.activeCamera;
+            }
         }
     }
 }

+ 3 - 2
src/babylon.node.ts

@@ -143,7 +143,7 @@ module BABYLON {
                     this._parentNode._children.splice(index, 1);
                 }
 
-                if (!parent) {
+                if (!parent && !this._isDisposed) {
                     this.addToSceneRootNodes();
                 }
             }
@@ -693,6 +693,8 @@ module BABYLON {
          * @param disposeMaterialAndTextures Set to true to also dispose referenced materials and textures (false by default)
          */
         public dispose(doNotRecurse?: boolean, disposeMaterialAndTextures = false): void {
+            this._isDisposed = true;
+
             if (!doNotRecurse) {
                 const nodes = this.getDescendants(true);
                 for (const node of nodes) {
@@ -722,7 +724,6 @@ module BABYLON {
             }
 
             this._behaviors = [];
-            this._isDisposed = true;
         }
 
         /**

+ 1 - 0
tests/validation/validate.html

@@ -5,6 +5,7 @@
 	<title>BabylonJS - Build validation page</title>
 	<link href="index.css" rel="stylesheet" />
 	<script src="https://preview.babylonjs.com/draco_decoder_gltf.js"></script>
+	<script src="https://preview.babylonjs.com/ammo.js"></script>
 	<script src="https://preview.babylonjs.com/cannon.js"></script>
 	<script src="https://preview.babylonjs.com/Oimo.js"></script>
 	<script src="https://preview.babylonjs.com/gltf_validator.js"></script>