sebastien 6 jaren geleden
bovenliggende
commit
647775717b
71 gewijzigde bestanden met toevoegingen van 16778 en 14160 verwijderingen
  1. 6764 6481
      Playground/babylon.d.txt
  2. 3 1
      Tools/Gulp/config.json
  3. 6713 6493
      dist/preview release/babylon.d.ts
  4. 1 1
      dist/preview release/babylon.js
  5. 690 219
      dist/preview release/babylon.max.js
  6. 690 219
      dist/preview release/babylon.no-module.max.js
  7. 1 1
      dist/preview release/babylon.worker.js
  8. 692 221
      dist/preview release/es6.js
  9. 1 4
      dist/preview release/glTF2Interface/package.json
  10. 18 2
      dist/preview release/gui/babylon.gui.d.ts
  11. 47 5
      dist/preview release/gui/babylon.gui.js
  12. 1 1
      dist/preview release/gui/babylon.gui.js.map
  13. 1 1
      dist/preview release/gui/babylon.gui.min.js
  14. 1 1
      dist/preview release/gui/babylon.gui.min.js.map
  15. 39 5
      dist/preview release/gui/babylon.gui.module.d.ts
  16. 2 2
      dist/preview release/gui/package.json
  17. 1 1
      dist/preview release/inspector/babylon.inspector.bundle.js.map
  18. 5 5
      dist/preview release/inspector/package.json
  19. 3 5
      dist/preview release/loaders/package.json
  20. 2 2
      dist/preview release/materialsLibrary/package.json
  21. 3 9
      dist/preview release/postProcessesLibrary/package.json
  22. 2 2
      dist/preview release/proceduralTexturesLibrary/package.json
  23. 3 3
      dist/preview release/serializers/package.json
  24. 14 14
      dist/preview release/viewer/babylon.viewer.js
  25. 2 2
      dist/preview release/viewer/babylon.viewer.max.js
  26. 26 5
      dist/preview release/what's new.md
  27. 31 1
      gui/src/2D/controls/button.ts
  28. 3 1
      gui/src/2D/controls/container.ts
  29. 7 2
      gui/src/2D/controls/control.ts
  30. 8 2
      gui/src/2D/controls/stackPanel.ts
  31. 1 1
      inspector/src/tools/FullscreenTool.ts
  32. 1 1
      package.json
  33. 4 3
      src/Cameras/Inputs/babylon.arcRotateCameraMouseWheelInput.ts
  34. 1 1
      src/Cameras/Inputs/babylon.freeCameraDeviceOrientationInput.ts
  35. 4 4
      src/Cameras/VR/babylon.vrExperienceHelper.ts
  36. 100 0
      src/Cameras/XR/babylon.webXRCamera.ts
  37. 143 0
      src/Cameras/XR/babylon.webXRSessionManager.ts
  38. 12 3
      src/Cameras/babylon.camera.ts
  39. 4 3
      src/Culling/babylon.boundingBox.ts
  40. 14 20
      src/Culling/babylon.boundingSphere.ts
  41. 29 8
      src/Engine/babylon.engine.ts
  42. 1 1
      src/Engine/babylon.nullEngine.ts
  43. 18 11
      src/Materials/PBR/babylon.pbrBaseMaterial.ts
  44. 5 2
      src/Materials/Textures/babylon.internalTexture.ts
  45. 11 9
      src/Materials/Textures/babylon.renderTargetTexture.ts
  46. 71 45
      src/Materials/babylon.material.ts
  47. 2 2
      src/Materials/babylon.multiMaterial.ts
  48. 14 7
      src/Materials/babylon.pushMaterial.ts
  49. 3 2
      src/Materials/babylon.shaderMaterial.ts
  50. 15 4
      src/Materials/babylon.standardMaterial.ts
  51. 4 2
      src/Materials/babylon.uniformBuffer.ts
  52. 21 4
      src/Math/babylon.math.ts
  53. 13 4
      src/Mesh/babylon.abstractMesh.ts
  54. 5 5
      src/Mesh/babylon.instancedMesh.ts
  55. 1 2
      src/Mesh/babylon.linesMesh.ts
  56. 61 11
      src/Mesh/babylon.mesh.ts
  57. 58 65
      src/Mesh/babylon.transformNode.ts
  58. 19 20
      src/Particles/babylon.gpuParticleSystem.ts
  59. 149 149
      src/Particles/babylon.particleSystem.ts
  60. 14 4
      src/Particles/babylon.solidParticleSystem.ts
  61. 2 2
      src/PostProcess/babylon.motionBlurPostProcess.ts
  62. 20 0
      src/Tools/babylon.tools.ts
  63. 6 0
      src/babylon.abstractScene.ts
  64. 7 2
      src/babylon.assetContainer.ts
  65. 41 10
      src/babylon.mixins.ts
  66. 119 37
      src/babylon.scene.ts
  67. 1 1
      src/babylon.sceneComponent.ts
  68. BIN
      tests/validation/ReferenceImages/cameraRig.png
  69. BIN
      tests/validation/ReferenceImages/sps.png
  70. 7 1
      tests/validation/config.json
  71. 3 3
      tests/validation/validation.js

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


+ 3 - 1
Tools/Gulp/config.json

@@ -1300,7 +1300,9 @@
                 "../../src/Cameras/VR/babylon.vrDeviceOrientationFreeCamera.js",
                 "../../src/Cameras/VR/babylon.vrDeviceOrientationArcRotateCamera.js",
                 "../../src/Cameras/VR/babylon.vrDeviceOrientationGamepadCamera.js",
-                "../../src/Cameras/VR/babylon.vrExperienceHelper.js"
+                "../../src/Cameras/VR/babylon.vrExperienceHelper.js",
+                "../../src/Cameras/XR/babylon.webXRCamera.js",
+                "../../src/Cameras/XR/babylon.webXRSessionManager.js"
             ],
             "dependUpon": [
                 "core",

File diff suppressed because it is too large
+ 6713 - 6493
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
+ 690 - 219
dist/preview release/babylon.max.js


File diff suppressed because it is too large
+ 690 - 219
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
+ 692 - 221
dist/preview release/es6.js


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

@@ -1,7 +1,7 @@
 {
     "name": "babylonjs-gltf2interface",
     "description": "A typescript declaration of babylon's gltf2 inteface.",
-    "version": "3.3.0",
+    "version": "4.0.0-alpha.2",
     "repository": {
         "type": "git",
         "url": "https://github.com/BabylonJS/Babylon.js.git"
@@ -19,9 +19,6 @@
         "gltf2"
     ],
     "license": "Apache-2.0",
-    "peerDependencies": {
-        "babylonjs": ">=3.3.0-rc.4"
-    },
     "engines": {
         "node": "*"
     }

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

@@ -582,6 +582,14 @@ declare module BABYLON.GUI {
                 */
             pointerUpAnimation: () => void;
             /**
+                * Returns the image part of the button (if any)
+                */
+            readonly image: BABYLON.Nullable<Image>;
+            /**
+                * Returns the image part of the button (if any)
+                */
+            readonly textBlock: BABYLON.Nullable<TextBlock>;
+            /**
                 * Creates a new Button
                 * @param name defines the name of the button
                 */
@@ -837,6 +845,8 @@ declare module BABYLON.GUI {
             isPointerBlocker: boolean;
             /** Gets or sets a boolean indicating if the control can be focusable */
             isFocusInvisible: boolean;
+            /** Gets or sets a boolean indicating if the children are clipped to the current control bounds */
+            clipChildren: boolean;
             /** Gets or sets a value indicating the offset to apply on X axis to render the shadow */
             shadowOffsetX: number;
             /** Gets or sets a value indicating the offset to apply on Y axis to render the shadow */
@@ -1603,9 +1613,15 @@ declare module BABYLON.GUI {
             name?: string | undefined;
             /** Gets or sets a boolean indicating if the stack panel is vertical or horizontal*/
             isVertical: boolean;
-            /** Gets or sets panel width */
+            /**
+                * Gets or sets panel width.
+                * This value should not be set when in horizontal mode as it will be computed automatically
+                */
             width: string | number;
-            /** Gets or sets panel height */
+            /**
+                * Gets or sets panel height.
+                * This value should not be set when in vertical mode as it will be computed automatically
+                */
             height: string | number;
             /**
                 * Creates a new StackPanel

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

@@ -902,6 +902,26 @@ var Button = /** @class */ (function (_super) {
         };
         return _this;
     }
+    Object.defineProperty(Button.prototype, "image", {
+        /**
+         * Returns the image part of the button (if any)
+         */
+        get: function () {
+            return this._image;
+        },
+        enumerable: true,
+        configurable: true
+    });
+    Object.defineProperty(Button.prototype, "textBlock", {
+        /**
+         * Returns the image part of the button (if any)
+         */
+        get: function () {
+            return this._textBlock;
+        },
+        enumerable: true,
+        configurable: true
+    });
     Button.prototype._getTypeName = function () {
         return "Button";
     };
@@ -973,6 +993,9 @@ var Button = /** @class */ (function (_super) {
         iconImage.stretch = image_1.Image.STRETCH_UNIFORM;
         iconImage.horizontalAlignment = control_1.Control.HORIZONTAL_ALIGNMENT_LEFT;
         result.addControl(iconImage);
+        // Store
+        result._image = iconImage;
+        result._textBlock = textBlock;
         return result;
     };
     /**
@@ -988,6 +1011,8 @@ var Button = /** @class */ (function (_super) {
         iconImage.stretch = image_1.Image.STRETCH_FILL;
         iconImage.horizontalAlignment = control_1.Control.HORIZONTAL_ALIGNMENT_LEFT;
         result.addControl(iconImage);
+        // Store
+        result._image = iconImage;
         return result;
     };
     /**
@@ -1003,6 +1028,8 @@ var Button = /** @class */ (function (_super) {
         textBlock.textWrapping = true;
         textBlock.textHorizontalAlignment = control_1.Control.HORIZONTAL_ALIGNMENT_CENTER;
         result.addControl(textBlock);
+        // Store
+        result._textBlock = textBlock;
         return result;
     };
     /**
@@ -1023,6 +1050,9 @@ var Button = /** @class */ (function (_super) {
         textBlock.textWrapping = true;
         textBlock.textHorizontalAlignment = control_1.Control.HORIZONTAL_ALIGNMENT_CENTER;
         result.addControl(textBlock);
+        // Store
+        result._image = iconImage;
+        result._textBlock = textBlock;
         return result;
     };
     return Button;
@@ -1869,7 +1899,9 @@ var Container = /** @class */ (function (_super) {
         this._applyStates(context);
         if (this._processMeasures(parentMeasure, context)) {
             this._localDraw(context);
-            this._clipForChildren(context);
+            if (this.clipChildren) {
+                this._clipForChildren(context);
+            }
             var computedWidth = -1;
             var computedHeight = -1;
             for (var _i = 0, _a = this._children; _i < _a.length; _i++) {
@@ -2034,6 +2066,8 @@ var Control = /** @class */ (function () {
         this.isPointerBlocker = false;
         /** Gets or sets a boolean indicating if the control can be focusable */
         this.isFocusInvisible = false;
+        /** Gets or sets a boolean indicating if the children are clipped to the current control bounds */
+        this.clipChildren = true;
         /** Gets or sets a value indicating the offset to apply on X axis to render the shadow */
         this.shadowOffsetX = 0;
         /** Gets or sets a value indicating the offset to apply on Y axis to render the shadow */
@@ -2995,8 +3029,10 @@ var Control = /** @class */ (function () {
             return false; // We do not want rendering for this frame as they are measure dependant information that need to be gathered
         }
         // Clip
-        this._clip(context);
-        context.clip();
+        if (this.clipChildren) {
+            this._clip(context);
+            context.clip();
+        }
         return true;
     };
     /** @hidden */
@@ -7187,7 +7223,10 @@ var StackPanel = /** @class */ (function (_super) {
         get: function () {
             return this._width.toString(this._host);
         },
-        /** Gets or sets panel width */
+        /**
+         * Gets or sets panel width.
+         * This value should not be set when in horizontal mode as it will be computed automatically
+         */
         set: function (value) {
             if (!this._doNotTrackManualChanges) {
                 this._manualWidth = true;
@@ -7206,7 +7245,10 @@ var StackPanel = /** @class */ (function (_super) {
         get: function () {
             return this._height.toString(this._host);
         },
-        /** Gets or sets panel height */
+        /**
+         * Gets or sets panel height.
+         * This value should not be set when in vertical mode as it will be computed automatically
+         */
         set: function (value) {
             if (!this._doNotTrackManualChanges) {
                 this._manualHeight = true;

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


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


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

@@ -652,7 +652,9 @@ declare module 'babylonjs-gui/3D/vector3WithInfo' {
 declare module 'babylonjs-gui/2D/controls/button' {
     import { Rectangle } from "babylonjs-gui/2D/controls/rectangle";
     import { Control } from "babylonjs-gui/2D/controls/control";
-    import { Vector2 } from "babylonjs";
+    import { TextBlock } from "babylonjs-gui/2D/controls/textBlock";
+    import { Image } from "babylonjs-gui/2D/controls/image";
+    import { Vector2, Nullable } from "babylonjs";
     /**
         * Class used to create 2D buttons
         */
@@ -675,6 +677,14 @@ declare module 'babylonjs-gui/2D/controls/button' {
                 */
             pointerUpAnimation: () => void;
             /**
+                * Returns the image part of the button (if any)
+                */
+            readonly image: Nullable<Image>;
+            /**
+                * Returns the image part of the button (if any)
+                */
+            readonly textBlock: Nullable<TextBlock>;
+            /**
                 * Creates a new Button
                 * @param name defines the name of the button
                 */
@@ -952,6 +962,8 @@ declare module 'babylonjs-gui/2D/controls/control' {
             isPointerBlocker: boolean;
             /** Gets or sets a boolean indicating if the control can be focusable */
             isFocusInvisible: boolean;
+            /** Gets or sets a boolean indicating if the children are clipped to the current control bounds */
+            clipChildren: boolean;
             /** Gets or sets a value indicating the offset to apply on X axis to render the shadow */
             shadowOffsetX: number;
             /** Gets or sets a value indicating the offset to apply on Y axis to render the shadow */
@@ -1754,9 +1766,15 @@ declare module 'babylonjs-gui/2D/controls/stackPanel' {
             name?: string | undefined;
             /** Gets or sets a boolean indicating if the stack panel is vertical or horizontal*/
             isVertical: boolean;
-            /** Gets or sets panel width */
+            /**
+                * Gets or sets panel width.
+                * This value should not be set when in horizontal mode as it will be computed automatically
+                */
             width: string | number;
-            /** Gets or sets panel height */
+            /**
+                * Gets or sets panel height.
+                * This value should not be set when in vertical mode as it will be computed automatically
+                */
             height: string | number;
             /**
                 * Creates a new StackPanel
@@ -3409,6 +3427,14 @@ declare module BABYLON.GUI {
                 */
             pointerUpAnimation: () => void;
             /**
+                * Returns the image part of the button (if any)
+                */
+            readonly image: BABYLON.Nullable<Image>;
+            /**
+                * Returns the image part of the button (if any)
+                */
+            readonly textBlock: BABYLON.Nullable<TextBlock>;
+            /**
                 * Creates a new Button
                 * @param name defines the name of the button
                 */
@@ -3664,6 +3690,8 @@ declare module BABYLON.GUI {
             isPointerBlocker: boolean;
             /** Gets or sets a boolean indicating if the control can be focusable */
             isFocusInvisible: boolean;
+            /** Gets or sets a boolean indicating if the children are clipped to the current control bounds */
+            clipChildren: boolean;
             /** Gets or sets a value indicating the offset to apply on X axis to render the shadow */
             shadowOffsetX: number;
             /** Gets or sets a value indicating the offset to apply on Y axis to render the shadow */
@@ -4430,9 +4458,15 @@ declare module BABYLON.GUI {
             name?: string | undefined;
             /** Gets or sets a boolean indicating if the stack panel is vertical or horizontal*/
             isVertical: boolean;
-            /** Gets or sets panel width */
+            /**
+                * Gets or sets panel width.
+                * This value should not be set when in horizontal mode as it will be computed automatically
+                */
             width: string | number;
-            /** Gets or sets panel height */
+            /**
+                * Gets or sets panel height.
+                * This value should not be set when in vertical mode as it will be computed automatically
+                */
             height: string | number;
             /**
                 * Creates a new StackPanel

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

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

+ 3 - 5
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": "3.3.0",
+    "version": "4.0.0-alpha.2",
     "repository": {
         "type": "git",
         "url": "https://github.com/BabylonJS/Babylon.js.git"
@@ -27,10 +27,8 @@
     ],
     "license": "Apache-2.0",
     "dependencies": {
-        "babylonjs-gltf2interface": "3.3.0"
-    },
-    "peerDependencies": {
-        "babylonjs": ">=3.3.0-rc.4"
+        "babylonjs-gltf2interface": "4.0.0-alpha.2",
+        "babylonjs": "4.0.0-alpha.2"
     },
     "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": "3.3.0",
+    "version": "4.0.0-alpha.2",
     "repository": {
         "type": "git",
         "url": "https://github.com/BabylonJS/Babylon.js.git"
@@ -27,7 +27,7 @@
     ],
     "license": "Apache-2.0",
     "dependencies": {
-        "babylonjs": "3.3.0"
+        "babylonjs": "4.0.0-alpha.2"
     },
     "engines": {
         "node": "*"

+ 3 - 9
dist/preview release/postProcessesLibrary/package.json

@@ -2,15 +2,9 @@
     "author": {
         "name": "David CATUHE"
     },
-<<<<<<< HEAD
-    "name": "babylonjs-post-processes",
-    "description": "The Babylon.js post process library is a collection of advanced post process to be used in a Babylon.js scene.",
-    "version": "3.3.0-rc.5",
-=======
     "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": "3.3.0",
->>>>>>> upstream/master
+    "description": "The Babylon.js post process library is a collection of advanced post process to be used in a Babylon.js scene.",
+    "version": "4.0.0-alpha.2",
     "repository": {
         "type": "git",
         "url": "https://github.com/BabylonJS/Babylon.js.git"
@@ -33,7 +27,7 @@
     ],
     "license": "Apache-2.0",
     "dependencies": {
-        "babylonjs": "3.3.0"
+        "babylonjs": "4.0.0-alpha.2"
     },
     "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": "3.3.0",
+    "version": "4.0.0-alpha.2",
     "repository": {
         "type": "git",
         "url": "https://github.com/BabylonJS/Babylon.js.git"
@@ -27,7 +27,7 @@
     ],
     "license": "Apache-2.0",
     "dependencies": {
-        "babylonjs": "3.3.0"
+        "babylonjs": "4.0.0-alpha.2"
     },
     "engines": {
         "node": "*"

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

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

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


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


+ 26 - 5
dist/preview release/what's new.md

@@ -3,18 +3,35 @@
 ## Major updates
 
 - Added support for [parallel shader compilation](https://www.khronos.org/registry/webgl/extensions/KHR_parallel_shader_compile/) ([Deltakosh](https://github.com/deltakosh))
-- Added FlyCamera for free navigation in 3D space, with a limited set of settings ([Phuein](https://github.com/phuein))
-- Added Object Based Motion Blur post-process ([julien-moreau](https://github.com/julien-moreau))
+@NEED DEMO- Added FlyCamera for free navigation in 3D space, with a limited set of settings ([Phuein](https://github.com/phuein))
+@NEED DEMO- Added Object Based Motion Blur post-process ([julien-moreau](https://github.com/julien-moreau))
+@NEED DEMO- WebXR ([TrevorDev](https://github.com/TrevorDev))
+  - Add customAnimationFrameRequester to allow sessions to hook into engine's render loop ([TrevorDev](https://github.com/TrevorDev))
+  - camera customDefaultRenderTarget to allow cameras to render to a custom render target (eg. xr framebuffer) instead of the canvas ([TrevorDev](https://github.com/TrevorDev))
+  - webXR camera which can be updated by a webXRSession ([TrevorDev](https://github.com/TrevorDev))
+  - webXRSessionManager to bridge xrSession to babylon's engine/camera ([TrevorDev](https://github.com/TrevorDev))
 
 ## Updates
 
 ### GUI
 
+- Added `button.image` and `button.textBlock` to simplify access to button internal parts ([Deltakosh](https://github.com/deltakosh))
+
 ### Core Engine
 
-- Refactor of the SolidParticleSystem code for performance and code quality improvement ([barroij](https://github.com/barroij))
+- Refactored of the SolidParticleSystem code for performance and code quality improvement ([barroij](https://github.com/barroij))
 - Added utility function `Tools.BuildArray` for array initialisation ([barroij](https://github.com/barroij))
 - Introduced a new `IOfflineSupport` interface to hide IndexedDB ([Deltakosh](https://github.com/deltakosh))
+- `PBRMaterial` and `StandardMaterial` now use hot swapping feature for shaders. This means they can keep using a previous shader while a new one is being compiled ([Deltakosh](https://github.com/deltakosh))
+- Performance oriented changes ([barroij](https://github.com/barroij))
+  - Prevented avoidable matrix inversion or square root computation
+  - Enabled a removal in O(1) from the `transformNodes` array and `materials` array of the `Scene`. As a consequence, the order of the element within these arrays might change during a removal
+  - Enabled a removal in O(1) from the `instances` array of a `Mesh`. As a consequence, the order of the element within this array might change during a removal
+  - Stopped calling `Array.splice` on the `scene.meshes` array and on the `engine._uniformBuffer` when removing an element. As a consequence, the order of the element within these arrays might change during a removal
+  - Added an option `useMaterialMeshMap` in the `Scene` constructor options. When set to true, each `Material` isntance will have and will keep up-to-date a map of its bound meshes. This is to avoid browsing all the meshes of the scene to retrieve the ones bound to the current material when disposing the Material. Disabled by default
+  - Added an option `useClonedMeshhMap` in the `Scene` constructor options. When set to true, each `Mesh` will have and will keep up-to-date a map of cloned meshes. This is to avoid browsing all the meshes of the scene to retrieve the ones that have the current mesh as source mesh. Disabled by default
+  - Added `blockfreeActiveMeshesAndRenderingGroups` property in the `Scene`, following the same model as `blockMaterialDirtyMechanism`. This is to avoid calling `Scene.freeActiveMeshes` and `Scene.freeRenderingGroups` for each disposed mesh when we dispose several meshes in a row. One have to set `blockfreeActiveMeshesAndRenderingGroups` to `true` just before disposing the meshes, and set it back to `false` just after
+  - Prevented code from doing useless and possible time consuming computation when disposing the `ShaderMaterial` of a `LinesMesh`
 
 ### glTF Loader
 
@@ -25,9 +42,12 @@
 ### Materials Library
 
 ## Bug fixes
+- Add missing effect layer to asset container ([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))
+- Fixed a bug with pointer up being fire twice ([Deltakosh](https://github.com/deltakosh))
+- Fixed a bug with particle systems being update once per camera instead of once per frame ([Deltakosh](https://github.com/deltakosh))
 
 
 ### Viewer
@@ -37,4 +57,5 @@
 ## Breaking changes
 
 - `Database.IDBStorageEnabled` is now false by default ([Deltakosh](https://github.com/deltakosh))
-- `Database.openAsync` was renamed by `Database.open`
+- `Database.openAsync` was renamed by `Database.open` ([Deltakosh](https://github.com/deltakosh))
+- `scene.database` was renamed to `scene.offlineProvider` ([Deltakosh](https://github.com/deltakosh))

+ 31 - 1
gui/src/2D/controls/button.ts

@@ -2,7 +2,7 @@ import { Rectangle } from "./rectangle";
 import { Control } from "./control";
 import { TextBlock } from "./textBlock";
 import { Image } from "./image";
-import { Vector2 } from "babylonjs";
+import { Vector2, Nullable } from "babylonjs";
 
 /**
  * Class used to create 2D buttons
@@ -25,6 +25,22 @@ export class Button extends Rectangle {
      */
     public pointerUpAnimation: () => void;
 
+    private _image: Nullable<Image>;
+    /**
+     * Returns the image part of the button (if any)
+     */
+    public get image(): Nullable<Image> {
+        return this._image;
+    }
+
+    private _textBlock: Nullable<TextBlock>;
+    /**
+     * Returns the image part of the button (if any)
+     */
+    public get textBlock(): Nullable<TextBlock> {
+        return this._textBlock;
+    }
+
     /**
      * Creates a new Button
      * @param name defines the name of the button
@@ -143,6 +159,10 @@ export class Button extends Rectangle {
         iconImage.horizontalAlignment = Control.HORIZONTAL_ALIGNMENT_LEFT;
         result.addControl(iconImage);
 
+        // Store
+        result._image = iconImage;
+        result._textBlock = textBlock;
+
         return result;
     }
 
@@ -161,6 +181,9 @@ export class Button extends Rectangle {
         iconImage.horizontalAlignment = Control.HORIZONTAL_ALIGNMENT_LEFT;
         result.addControl(iconImage);
 
+        // Store
+        result._image = iconImage;
+
         return result;
     }
 
@@ -179,6 +202,9 @@ export class Button extends Rectangle {
         textBlock.textHorizontalAlignment = Control.HORIZONTAL_ALIGNMENT_CENTER;
         result.addControl(textBlock);
 
+        // Store
+        result._textBlock = textBlock;
+
         return result;
     }
 
@@ -203,6 +229,10 @@ export class Button extends Rectangle {
         textBlock.textHorizontalAlignment = Control.HORIZONTAL_ALIGNMENT_CENTER;
         result.addControl(textBlock);
 
+        // Store
+        result._image = iconImage;
+        result._textBlock = textBlock;
+
         return result;
     }
 }

+ 3 - 1
gui/src/2D/controls/container.ts

@@ -266,7 +266,9 @@ export class Container extends Control {
         if (this._processMeasures(parentMeasure, context)) {
             this._localDraw(context);
 
-            this._clipForChildren(context);
+            if (this.clipChildren) {
+                this._clipForChildren(context);
+            }
 
             let computedWidth = -1;
             let computedHeight = -1;

+ 7 - 2
gui/src/2D/controls/control.ts

@@ -88,6 +88,9 @@ export class Control {
     /** Gets or sets a boolean indicating if the control can be focusable */
     public isFocusInvisible = false;
 
+    /** Gets or sets a boolean indicating if the children are clipped to the current control bounds */
+    public clipChildren = true;
+
     /** Gets or sets a value indicating the offset to apply on X axis to render the shadow */
     public shadowOffsetX = 0;
     /** Gets or sets a value indicating the offset to apply on Y axis to render the shadow */
@@ -1040,8 +1043,10 @@ export class Control {
         }
 
         // Clip
-        this._clip(context);
-        context.clip();
+        if (this.clipChildren) {
+            this._clip(context);
+            context.clip();
+        }
 
         return true;
     }

+ 8 - 2
gui/src/2D/controls/stackPanel.ts

@@ -26,7 +26,10 @@ export class StackPanel extends Container {
         this._markAsDirty();
     }
 
-    /** Gets or sets panel width */
+    /**
+     * Gets or sets panel width.
+     * This value should not be set when in horizontal mode as it will be computed automatically
+     */
     public set width(value: string | number) {
         if (!this._doNotTrackManualChanges) {
             this._manualWidth = true;
@@ -45,7 +48,10 @@ export class StackPanel extends Container {
         return this._width.toString(this._host);
     }
 
-    /** Gets or sets panel height */
+    /**
+     * Gets or sets panel height.
+     * This value should not be set when in vertical mode as it will be computed automatically
+     */
     public set height(value: string | number) {
         if (!this._doNotTrackManualChanges) {
             this._manualHeight = true;

+ 1 - 1
inspector/src/tools/FullscreenTool.ts

@@ -14,7 +14,7 @@ export class FullscreenTool extends AbstractTool {
 
         function requestFullScreen(element: HTMLElement) {
             // Supports most browsers and their versions.
-            var requestMethod = element.requestFullscreen || element.webkitRequestFullScreen;
+            var requestMethod = element.requestFullscreen || (<any>element).webkitRequestFullScreen;
             requestMethod.call(element);
         }
 

+ 1 - 1
package.json

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

+ 4 - 3
src/Cameras/Inputs/babylon.arcRotateCameraMouseWheelInput.ts

@@ -48,12 +48,13 @@ module BABYLON {
                     } else {
                         delta = event.wheelDelta / (this.wheelPrecision * 40);
                     }
-                } else if (event.detail) {
-                    delta = -event.detail / this.wheelPrecision;
+                } else {
+                    let deltaValue = event.deltaY || event.detail;
+                    delta = -deltaValue / this.wheelPrecision;
                 }
 
                 if (delta) {
-                    this.camera.inertialRadiusOffset += delta;
+                    this.camera.inertialRadiusOffset += delta;
                 }
 
                 if (event.preventDefault) {

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

@@ -53,7 +53,7 @@ module BABYLON {
         }
 
         private _orientationChanged = () => {
-            this._screenOrientationAngle = (window.orientation !== undefined ? +window.orientation : (window.screen.orientation && (<any>window.screen.orientation)['angle'] ? (<any>window.screen.orientation).angle : 0));
+            this._screenOrientationAngle = (<any>window.orientation !== undefined ? +<any>window.orientation : ((<any>window.screen).orientation && ((<any>window.screen).orientation)['angle'] ? ((<any>window.screen).orientation).angle : 0));
             this._screenOrientationAngle = -Tools.ToRadians(this._screenOrientationAngle / 2);
             this._screenQuaternion.copyFromFloats(0, Math.sin(this._screenOrientationAngle), 0, Math.cos(this._screenOrientationAngle));
         }

+ 4 - 4
src/Cameras/VR/babylon.vrExperienceHelper.ts

@@ -780,8 +780,8 @@ module BABYLON {
         }
 
         private _onFullscreenChange = () => {
-            if (document.fullscreen !== undefined) {
-                this._fullscreenVRpresenting = document.fullscreen;
+            if ((<any>document).fullscreen !== undefined) {
+                this._fullscreenVRpresenting = (<any>document).fullscreen;
             } else if (document.mozFullScreen !== undefined) {
                 this._fullscreenVRpresenting = document.mozFullScreen;
             } else if (document.webkitIsFullScreen !== undefined) {
@@ -814,7 +814,7 @@ module BABYLON {
                 this._webVRpresenting = vrDisplay.isPresenting;
 
                 if (wasPresenting && !this._webVRpresenting) {
-                    this.exitVR();
+                    this.exitVR();
                 }
             } else {
                 Tools.Warn('Detected VRDisplayPresentChange on an unknown VRDisplay. Did you can enterVR on the vrExperienceHelper?');
@@ -880,7 +880,7 @@ module BABYLON {
             }
 
             if (this._webVRrequesting) {
-                return;
+                return;
             }
 
             // If WebVR is supported and a headset is connected

+ 100 - 0
src/Cameras/XR/babylon.webXRCamera.ts

@@ -0,0 +1,100 @@
+module BABYLON {
+    /**
+     * WebXR Camera which holds the views for the xrSession
+     * @see https://doc.babylonjs.com/how_to/webxr
+     */
+    export class WebXRCamera extends FreeCamera {
+        private static _TmpMatrix = new BABYLON.Matrix();
+
+        /**
+         * Creates a new webXRCamera, this should only be set at the camera after it has been updated by the xrSessionManager
+         * @param name the name of the camera
+         * @param scene the scene to add the camera to
+         */
+        constructor(name: string, scene: BABYLON.Scene) {
+            super(name, BABYLON.Vector3.Zero(), scene);
+
+            // Initial camera configuration
+            this.minZ = 0;
+            this.rotationQuaternion = new BABYLON.Quaternion();
+            this.cameraRigMode = BABYLON.Camera.RIG_MODE_CUSTOM;
+            this._updateNumberOfRigCameras(1);
+        }
+
+        private _updateNumberOfRigCameras(viewCount = 1) {
+            while (this.rigCameras.length < viewCount) {
+                var newCamera = new BABYLON.TargetCamera("view: " + this.rigCameras.length, BABYLON.Vector3.Zero(), this.getScene());
+                newCamera.minZ = 0;
+                newCamera.parent = this;
+                this.rigCameras.push(newCamera);
+            }
+            while (this.rigCameras.length > viewCount) {
+                var removedCamera = this.rigCameras.pop();
+                if (removedCamera) {
+                    removedCamera.dispose();
+                }
+            }
+        }
+
+        /** @hidden */
+        public _updateForDualEyeDebugging(pupilDistance = 0.01) {
+            // Create initial camera rigs
+            this._updateNumberOfRigCameras(2);
+            this.rigCameras[0].viewport = new BABYLON.Viewport(0, 0, 0.5, 1.0);
+            this.rigCameras[0].position.x = -pupilDistance / 2;
+            this.rigCameras[0].outputRenderTarget = null;
+            this.rigCameras[1].viewport = new BABYLON.Viewport(0.5, 0, 0.5, 1.0);
+            this.rigCameras[1].position.x = pupilDistance / 2;
+            this.rigCameras[1].outputRenderTarget = null;
+        }
+
+        /**
+         * Updates the cameras position from the current pose information of the  XR session
+         * @param xrSessionManager the session containing pose information
+         */
+        public updateFromXRSessionManager(xrSessionManager: WebXRSessionManager) {
+            // Ensure all frame data is available
+            if (!xrSessionManager._currentXRFrame || !xrSessionManager._currentXRFrame.getDevicePose) {
+                return;
+            }
+            var pose = xrSessionManager._currentXRFrame.getDevicePose(xrSessionManager._frameOfReference);
+            if (!pose || !pose.poseModelMatrix) {
+                return;
+            }
+
+            // Update the parent cameras matrix
+            BABYLON.Matrix.FromFloat32ArrayToRefScaled(pose.poseModelMatrix, 0, 1, WebXRCamera._TmpMatrix);
+            if (!this._scene.useRightHandedSystem) {
+                WebXRCamera._TmpMatrix.toggleModelMatrixHandInPlace();
+            }
+            WebXRCamera._TmpMatrix.getTranslationToRef(this.position);
+            WebXRCamera._TmpMatrix.getRotationMatrixToRef(WebXRCamera._TmpMatrix);
+            BABYLON.Quaternion.FromRotationMatrixToRef(WebXRCamera._TmpMatrix, this.rotationQuaternion);
+            this.computeWorldMatrix();
+
+            // Update camera rigs
+            this._updateNumberOfRigCameras(xrSessionManager._currentXRFrame.views.length);
+            xrSessionManager._currentXRFrame.views.forEach((view, i) => {
+                // Update view/projection matrix
+                BABYLON.Matrix.FromFloat32ArrayToRefScaled(pose.getViewMatrix(view), 0, 1, this.rigCameras[i]._computedViewMatrix);
+                BABYLON.Matrix.FromFloat32ArrayToRefScaled(view.projectionMatrix, 0, 1, this.rigCameras[i]._projectionMatrix);
+                if (!this._scene.useRightHandedSystem) {
+                    this.rigCameras[i]._computedViewMatrix.toggleModelMatrixHandInPlace();
+                    this.rigCameras[i]._projectionMatrix.toggleProjectionMatrixHandInPlace();
+                }
+
+                // Update viewport
+                var viewport  = xrSessionManager._xrSession.baseLayer.getViewport(view);
+                var width = xrSessionManager._xrSession.baseLayer.framebufferWidth;
+                var height = xrSessionManager._xrSession.baseLayer.framebufferHeight;
+                this.rigCameras[i].viewport.width = viewport.width / width;
+                this.rigCameras[i].viewport.height = viewport.height / height;
+                this.rigCameras[i].viewport.x = viewport.x / width;
+                this.rigCameras[i].viewport.y = viewport.y / height;
+
+                // Set cameras to render to the session's render target
+                this.rigCameras[i].outputRenderTarget = xrSessionManager._sessionRenderTargetTexture;
+            });
+        }
+    }
+}

+ 143 - 0
src/Cameras/XR/babylon.webXRSessionManager.ts

@@ -0,0 +1,143 @@
+module BABYLON {
+    /**
+     * Manages an XRSession
+     * @see https://doc.babylonjs.com/how_to/webxr
+     */
+    export class WebXRSessionManager {
+        private _xrNavigator: any;
+        private _xrDevice: XRDevice;
+        private _tmpMatrix = new BABYLON.Matrix();
+        /** @hidden */
+        public _xrSession: XRSession;
+        /** @hidden */
+        public _frameOfReference: XRFrameOfReference;
+        /** @hidden */
+        public _sessionRenderTargetTexture: RenderTargetTexture;
+        /** @hidden */
+        public _currentXRFrame: XRFrame;
+
+        /**
+         * Constructs a WebXRSessionManager, this must be initialized within a user action before usage
+         * @param scene The scene which the session should be created for
+         */
+        constructor(private scene: BABYLON.Scene) {
+
+        }
+
+        /**
+         * Initializes the manager, this must be done with a user action (eg. button click event)
+         * After initialization enterXR can be called to start an XR session
+         * @returns Promise which resolves after it is initialized
+         */
+        public initialize(): Promise<void> {
+             // Check if the browser supports webXR
+            this._xrNavigator = navigator;
+            if (!this._xrNavigator.xr) {
+                return Promise.reject("webXR not supported by this browser");
+            }
+             // Request the webXR device
+            return this._xrNavigator.xr.requestDevice().then((device: XRDevice) => {
+                this._xrDevice = device;
+                return (<any>this.scene.getEngine()._gl).setCompatibleXRDevice(this._xrDevice);
+            });
+        }
+
+        /**
+         * Enters XR with the desired XR session options
+         * @param sessionCreationOptions xr options to create the session with
+         * @param frameOfReferenceType option to configure how the xr pose is expressed
+         * @returns Promise which resolves after it enters XR
+         */
+        public enterXR(sessionCreationOptions: XRSessionCreationOptions, frameOfReferenceType: XRFrameOfReferenceType): Promise<void> {
+            // initialize session
+            return this._xrDevice.requestSession(sessionCreationOptions).then((session: XRSession) => {
+                this._xrSession = session;
+                this._xrSession.baseLayer = new XRWebGLLayer(this._xrSession, this.scene.getEngine()._gl);
+                return this._xrSession.requestFrameOfReference(frameOfReferenceType);
+            }).then((frameOfRef: any) => {
+                this._frameOfReference = frameOfRef;
+                // Tell the engine's render loop to be driven by the xr session's refresh rate and provide xr pose information
+                this.scene.getEngine().customAnimationFrameRequester = {
+                    requestAnimationFrame: this._xrSession.requestAnimationFrame.bind(this._xrSession),
+                    renderFunction: (timestamp: number, xrFrame: XRFrame) => {
+                        // Store the XR frame in the manager to be consumed by the XR camera to update pose
+                        this._currentXRFrame = xrFrame;
+                        this.scene.getEngine()._renderLoop();
+                    }
+                };
+                // Create render target texture from xr's webgl render target
+                this._sessionRenderTargetTexture = WebXRSessionManager._CreateRenderTargetTextureFromSession(this._xrSession, this.scene);
+            });
+        }
+
+        /**
+         * Stops the xrSession and restores the renderloop
+         * @returns Promise which resolves after it exits XR
+         */
+        public exitXR() {
+            return new Promise((res) => {
+                this.scene.getEngine().customAnimationFrameRequester = null;
+                this._xrSession.end();
+                // Restore frame buffer to avoid clear on xr framebuffer after session end
+                this.scene.getEngine().restoreDefaultFramebuffer();
+                // Need to restart render loop as after calling session.end the last request for new frame will never call callback
+                this.scene.getEngine()._renderLoop();
+                res();
+            });
+        }
+
+        /**
+         * Fires a ray and returns the closest hit in the xr sessions enviornment, useful to place objects in AR
+         * @param ray ray to cast into the environment
+         * @returns Promise which resolves with a collision point in the environment if it exists
+         */
+        public environmentPointHitTest(ray: BABYLON.Ray): Promise<Nullable<Vector3>> {
+            return new Promise((res, rej) => {
+                // Compute left handed inputs to request hit test
+                var origin = new Float32Array([ray.origin.x, ray.origin.y, ray.origin.z]);
+                var direction = new Float32Array([ray.direction.x, ray.direction.y, ray.direction.z]);
+                if (!this.scene.useRightHandedSystem) {
+                    origin[2] *= -1;
+                    direction[2] *= -1;
+                }
+
+                // Fire hittest
+                this._xrSession.requestHitTest(origin, direction, this._frameOfReference)
+                .then((hits: any) => {
+                    if (hits.length > 0) {
+                        BABYLON.Matrix.FromFloat32ArrayToRefScaled(hits[0].hitMatrix, 0, 1.0, this._tmpMatrix);
+                        var hitPoint = this._tmpMatrix.getTranslation();
+                        if (!this.scene.useRightHandedSystem) {
+                            hitPoint.z *= -1;
+                        }
+                        res(hitPoint);
+                    }else {
+                        res(null);
+                    }
+                }).catch((e: Error) => {
+                    res(null);
+                });
+            });
+        }
+
+        /**
+         * @hidden
+         * Converts the render layer of xrSession to a render target
+         * @param session session to create render target for
+         * @param scene scene the new render target should be created for
+         */
+        public static _CreateRenderTargetTextureFromSession(session: XRSession, scene: BABYLON.Scene) {
+            // Create internal texture
+            var internalTexture = new BABYLON.InternalTexture(scene.getEngine(), BABYLON.InternalTexture.DATASOURCE_UNKNOWN, true);
+            internalTexture.width = session.baseLayer.framebufferWidth;
+            internalTexture.height = session.baseLayer.framebufferHeight;
+            internalTexture._framebuffer = session.baseLayer.framebuffer;
+
+             // Create render target texture from the internal texture
+            var renderTargetTexture = new BABYLON.RenderTargetTexture("XR renderTargetTexture", {width: internalTexture.width, height: internalTexture.height}, scene, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, true);
+            renderTargetTexture._texture = internalTexture;
+
+             return renderTargetTexture;
+        }
+    }
+}

+ 12 - 3
src/Cameras/babylon.camera.ts

@@ -56,6 +56,10 @@ module BABYLON {
          * Defines that both eyes of the camera should be renderered in a VR mode (webVR).
          */
         public static readonly RIG_MODE_WEBVR = 21;
+        /**
+         * Custom rig mode allowing rig cameras to be populated manually with any number of cameras
+         */
+        public static readonly RIG_MODE_CUSTOM = 22;
 
         /**
          * Defines if by default attaching controls should prevent the default javascript event to continue.
@@ -195,11 +199,15 @@ module BABYLON {
         public isStereoscopicSideBySide: boolean;
 
         /**
-         * Defines the list of custom render target the camera should render to.
+         * Defines the list of custom render target which are rendered to and then used as the input to this camera's render. Eg. display another camera view on a TV in the main scene
          * This is pretty helpfull if you wish to make a camera render to a texture you could reuse somewhere
          * else in the scene.
          */
         public customRenderTargets = new Array<RenderTargetTexture>();
+        /**
+         * When set, the camera will render to this render target instead of the default canvas
+         */
+        public outputRenderTarget: Nullable<RenderTargetTexture> = null;
 
         /**
          * Observable triggered when the camera view matrix has changed.
@@ -242,7 +250,8 @@ module BABYLON {
 
         protected _globalPosition = Vector3.Zero();
 
-        private _computedViewMatrix = Matrix.Identity();
+        /** hidden */
+        public _computedViewMatrix = Matrix.Identity();
         private _doNotComputeProjectionMatrix = false;
         private _transformMatrix = Matrix.Zero();
         private _frustumPlanes: Plane[];
@@ -415,7 +424,7 @@ module BABYLON {
         /** @hidden */
         public _isSynchronizedViewMatrix(): boolean {
             if (!super._isSynchronized()) {
-                return false;
+                return false;
             }
 
             return this._cache.position.equals(this.position)

+ 4 - 3
src/Culling/babylon.boundingBox.ts

@@ -49,6 +49,7 @@
         public maximum: Vector3 = Vector3.Zero();
 
         private _worldMatrix: Matrix;
+        private static TmpVector3 = Tools.BuildArray(3, Vector3.Zero);
 
         /**
          * @hidden
@@ -89,8 +90,8 @@
             vectors[7].copyFromFloats(maxX, minY, maxZ);
 
             // OBB
-            this.maximum.addToRef(min, this.center).scaleInPlace(0.5);
-            this.maximum.subtractToRef(max, this.extendSize).scaleInPlace(0.5);
+            max.addToRef(min, this.center).scaleInPlace(0.5);
+            max.subtractToRef(min, this.extendSize).scaleInPlace(0.5);
 
             this._update(worldMatrix || this._worldMatrix || Matrix.Identity());
         }
@@ -101,7 +102,7 @@
          * @returns the current bounding box
          */
         public scale(factor: number): BoundingBox {
-            const tmpVectors = Tmp.Vector3;
+            const tmpVectors = BoundingBox.TmpVector3;
             const diff = this.maximum.subtractToRef(this.minimum, tmpVectors[0]);
             const len = diff.length();
             diff.normalizeFromLength(len);

+ 14 - 20
src/Culling/babylon.boundingSphere.ts

@@ -31,6 +31,8 @@ module BABYLON {
          */
         public maximum = Vector3.Zero();
 
+        private static TmpVector3 = Tools.BuildArray(3, Vector3.Zero);
+
         /**
          * Creates a new bounding sphere
          * @param min defines the minimum vector (in local space)
@@ -53,7 +55,7 @@ module BABYLON {
 
             var distance = Vector3.Distance(min, max);
 
-            Vector3.LerpToRef(min, max, 0.5, this.center);
+            max.addToRef(min, this.center).scaleInPlace(0.5);
             this.radius = distance * 0.5;
 
             this._update(worldMatrix || _identityMatrix);
@@ -65,11 +67,11 @@ module BABYLON {
          * @returns the current bounding box
          */
         public scale(factor: number): BoundingSphere {
-            let newRadius = this.radius * factor;
-            const tmpVectors = Tmp.Vector3;
-            const tempRadiusVector = tmpVectors[0].copyFromFloats(newRadius, newRadius, newRadius);
-            let min = this.center.subtractToRef(tempRadiusVector, tmpVectors[1]);
-            let max = this.center.addToRef(tempRadiusVector, tmpVectors[2]);
+            const newRadius = this.radius * factor;
+            const tmpVectors = BoundingSphere.TmpVector3;
+            const tempRadiusVector = tmpVectors[0].setAll(newRadius);
+            const min = this.center.subtractToRef(tempRadiusVector, tmpVectors[1]);
+            const max = this.center.addToRef(tempRadiusVector, tmpVectors[2]);
 
             this.reConstruct(min, max);
 
@@ -80,7 +82,7 @@ module BABYLON {
         /** @hidden */
         public _update(world: Matrix): void {
             Vector3.TransformCoordinatesToRef(this.center, world, this.centerWorld);
-            const tempVector = Tmp.Vector3[0];
+            const tempVector = BoundingSphere.TmpVector3[0];
             Vector3.TransformNormalFromFloatsToRef(1.0, 1.0, 1.0, world, tempVector);
             this.radiusWorld = Math.max(Math.abs(tempVector.x), Math.abs(tempVector.y), Math.abs(tempVector.z)) * this.radius;
         }
@@ -106,13 +108,8 @@ module BABYLON {
          * @returns true if the point is inside the bounding sphere
          */
         public intersectsPoint(point: Vector3): boolean {
-            var x = this.centerWorld.x - point.x;
-            var y = this.centerWorld.y - point.y;
-            var z = this.centerWorld.z - point.z;
-
-            var distance = Math.sqrt((x * x) + (y * y) + (z * z));
-
-            if (this.radiusWorld < distance) {
+            const squareDistance = Vector3.DistanceSquared(this.centerWorld, point);
+            if (this.radiusWorld * this.radiusWorld < squareDistance) {
                 return false;
             }
 
@@ -127,13 +124,10 @@ module BABYLON {
          * @returns true if the speres intersect
          */
         public static Intersects(sphere0: BoundingSphere, sphere1: BoundingSphere): boolean {
-            var x = sphere0.centerWorld.x - sphere1.centerWorld.x;
-            var y = sphere0.centerWorld.y - sphere1.centerWorld.y;
-            var z = sphere0.centerWorld.z - sphere1.centerWorld.z;
-
-            var distance = Math.sqrt((x * x) + (y * y) + (z * z));
+            const squareDistance = Vector3.DistanceSquared(sphere0.centerWorld, sphere1.centerWorld);
+            const radiusSum = sphere0.radiusWorld + sphere1.radiusWorld;
 
-            if (sphere0.radiusWorld + sphere1.radiusWorld < distance) {
+            if (radiusSum * radiusSum < squareDistance) {
                 return false;
             }
 

+ 29 - 8
src/Engine/babylon.engine.ts

@@ -481,7 +481,20 @@ module BABYLON {
          * Returns the current version of the framework
          */
         public static get Version(): string {
-            return "4.0.0-alpha.0";
+            return "4.0.0-alpha.2";
+        }
+
+        /**
+         * Returns a string describing the current engine
+         */
+        public get description(): string {
+            let description = "WebGL" + this.webGLVersion;
+
+            if (this._caps.parallelShaderCompile) {
+                description += " - Parallel shader compilation";
+            }
+
+            return description;
         }
 
         // Updatable statics so stick with vars here
@@ -625,6 +638,11 @@ module BABYLON {
         public onBeginFrameObservable = new Observable<Engine>();
 
         /**
+         * If set, will be used to request the next animation frame for the render loop
+         */
+        public customAnimationFrameRequester: Nullable<ICustomAnimationFrameRequester> = null;
+
+        /**
          * Observable raised when the engine ends the current frame
          */
         public onEndFrameObservable = new Observable<Engine>();
@@ -1146,8 +1164,8 @@ module BABYLON {
             if (canvas) {
                 // Fullscreen
                 this._onFullscreenChange = () => {
-                    if (document.fullscreen !== undefined) {
-                        this.isFullscreen = document.fullscreen;
+                    if ((<any>document).fullscreen !== undefined) {
+                        this.isFullscreen = (<any>document).fullscreen;
                     } else if (document.mozFullScreen !== undefined) {
                         this.isFullscreen = document.mozFullScreen;
                     } else if (document.webkitIsFullScreen !== undefined) {
@@ -1225,7 +1243,7 @@ module BABYLON {
             // Detect if we are running on a faulty buggy desktop OS.
             this._badDesktopOS = /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
 
-            console.log("Babylon.js engine (v" + Engine.Version + ") launched");
+            console.log(`Babylon.js v${Engine.Version} - ${this.description}`);
 
             this.enableOfflineSupport = Engine.OfflineProviderFactory !== undefined;
         }
@@ -1894,11 +1912,14 @@ module BABYLON {
 
             if (this._activeRenderLoops.length > 0) {
                 // Register new frame
-                var requester = null;
-                if (this._vrDisplay && this._vrDisplay.isPresenting) {
-                    requester = this._vrDisplay;
+                if (this.customAnimationFrameRequester) {
+                    this.customAnimationFrameRequester.requestID = Tools.QueueNewFrame(this.customAnimationFrameRequester.renderFunction || this._bindedRenderFunction, this.customAnimationFrameRequester);
+                    this._frameHandler = this.customAnimationFrameRequester.requestID;
+                } else if (this._vrDisplay && this._vrDisplay.isPresenting) {
+                    this._frameHandler = Tools.QueueNewFrame(this._bindedRenderFunction, this._vrDisplay);
+                } else {
+                    this._frameHandler = Tools.QueueNewFrame(this._bindedRenderFunction);
                 }
-                this._frameHandler = Tools.QueueNewFrame(this._bindedRenderFunction, requester);
             } else {
                 this._renderingQueueLaunched = false;
             }

+ 1 - 1
src/Engine/babylon.nullEngine.ts

@@ -116,7 +116,7 @@ module BABYLON {
             this._caps.vertexArrayObject = false;
             this._caps.instancedArrays = false;
 
-            Tools.Log("Babylon.js null engine (v" + Engine.Version + ") launched");
+            Tools.Log(`Babylon.js v${Engine.Version} - Null engine`);
 
             // Wrappers
             if (typeof URL === "undefined") {

+ 18 - 11
src/Materials/PBR/babylon.pbrBaseMaterial.ts

@@ -861,13 +861,21 @@ module BABYLON {
             if (!engine.getCaps().standardDerivatives && !mesh.isVerticesDataPresent(VertexBuffer.NormalKind)) {
                 mesh.createNormals(true);
                 Tools.Warn("PBRMaterial: Normals have been created for the mesh: " + mesh.name);
-                }
+            }
+
+            let previousEffect = subMesh.effect;
+            let effect = this._prepareEffect(mesh, defines, this.onCompiled, this.onError, useInstances);
 
-            const effect = this._prepareEffect(mesh, defines, this.onCompiled, this.onError, useInstances);
             if (effect) {
-                scene.resetCachedMaterial();
-                subMesh.setEffect(effect, defines);
-                this.buildUniformLayout();
+                // Use previous effect while new one is compiling
+                if (this.allowShaderHotSwapping && previousEffect && !effect.isReady()) {
+                    effect = previousEffect;
+                    defines.markAsUnprocessed();
+                } else {
+                    scene.resetCachedMaterial();
+                    subMesh.setEffect(effect, defines);
+                    this.buildUniformLayout();
+                }
             }
 
             if (!subMesh.effect || !subMesh.effect.isReady()) {
@@ -1134,10 +1142,10 @@ module BABYLON {
                             case Texture.CUBIC_MODE:
                             case Texture.INVCUBIC_MODE:
                             default:
-                                    defines.REFLECTIONMAP_CUBIC = true;
-                                    defines.USE_LOCAL_REFLECTIONMAP_CUBIC = (<any>reflectionTexture).boundingBoxSize ? true : false;
-                                    break;
-                            }
+                                defines.REFLECTIONMAP_CUBIC = true;
+                                defines.USE_LOCAL_REFLECTIONMAP_CUBIC = (<any>reflectionTexture).boundingBoxSize ? true : false;
+                                break;
+                        }
 
                         if (reflectionTexture.coordinatesMode !== Texture.SKYBOX_MODE) {
                             if (reflectionTexture.sphericalPolynomial) {
@@ -1424,8 +1432,7 @@ module BABYLON {
             this.bindOnlyWorldMatrix(world);
 
             // Normal Matrix
-            if (defines.OBJECTSPACE_NORMALMAP)
-            {
+            if (defines.OBJECTSPACE_NORMALMAP) {
                 world.toNormalMatrix(this._normalMatrix);
                 this.bindOnlyNormalMatrix(this._normalMatrix);
             }

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

@@ -236,12 +236,15 @@ module BABYLON {
          * Creates a new InternalTexture
          * @param engine defines the engine to use
          * @param dataSource defines the type of data that will be used
+         * @param delayAllocation if the texture allocation should be delayed (default: false)
          */
-        constructor(engine: Engine, dataSource: number) {
+        constructor(engine: Engine, dataSource: number, delayAllocation = false) {
             this._engine = engine;
             this._dataSource = dataSource;
 
-            this._webGLTexture = engine._createTexture();
+            if (!delayAllocation) {
+                this._webGLTexture = engine._createTexture();
+            }
         }
 
         /**

+ 11 - 9
src/Materials/Textures/babylon.renderTargetTexture.ts

@@ -260,8 +260,9 @@ module BABYLON {
          * @param generateStencilBuffer True to generate a stencil buffer
          * @param isMulti True if multiple textures need to be created (Draw Buffers)
          * @param format The internal format of the buffer in the RTT (RED, RG, RGB, RGBA, ALPHA...)
+         * @param delayAllocation if the texture allocation should be delayed (default: false)
          */
-        constructor(name: string, size: number | { width: number, height: number } | { ratio: number }, scene: Nullable<Scene>, generateMipMaps?: boolean, doNotChangeAspectRatio: boolean = true, type: number = Engine.TEXTURETYPE_UNSIGNED_INT, public isCube = false, samplingMode = Texture.TRILINEAR_SAMPLINGMODE, generateDepthBuffer = true, generateStencilBuffer = false, isMulti = false, format = Engine.TEXTUREFORMAT_RGBA) {
+        constructor(name: string, size: number | { width: number, height: number } | { ratio: number }, scene: Nullable<Scene>, generateMipMaps?: boolean, doNotChangeAspectRatio: boolean = true, type: number = Engine.TEXTURETYPE_UNSIGNED_INT, public isCube = false, samplingMode = Texture.TRILINEAR_SAMPLINGMODE, generateDepthBuffer = true, generateStencilBuffer = false, isMulti = false, format = Engine.TEXTUREFORMAT_RGBA, delayAllocation = false) {
             super(null, scene, !generateMipMaps);
             scene = this.getScene();
 
@@ -305,14 +306,15 @@ module BABYLON {
                 this.wrapV = Texture.CLAMP_ADDRESSMODE;
             }
 
-            if (isCube) {
-                this._texture = scene.getEngine().createRenderTargetCubeTexture(this.getRenderSize(), this._renderTargetOptions);
-                this.coordinatesMode = Texture.INVCUBIC_MODE;
-                this._textureMatrix = Matrix.Identity();
-            } else {
-                this._texture = scene.getEngine().createRenderTargetTexture(this._size, this._renderTargetOptions);
+            if (!delayAllocation) {
+                if (isCube) {
+                    this._texture = scene.getEngine().createRenderTargetCubeTexture(this.getRenderSize(), this._renderTargetOptions);
+                    this.coordinatesMode = Texture.INVCUBIC_MODE;
+                    this._textureMatrix = Matrix.Identity();
+                } else {
+                    this._texture = scene.getEngine().createRenderTargetTexture(this._size, this._renderTargetOptions);
+                }
             }
-
         }
 
         /**
@@ -586,7 +588,7 @@ module BABYLON {
             // Is predicate defined?
             if (this.renderListPredicate) {
                 if (this.renderList) {
-                    this.renderList.splice(0); // Clear previous renderList
+                    this.renderList.length = 0; // Clear previous renderList
                 } else {
                     this.renderList = [];
                 }

+ 71 - 45
src/Materials/babylon.material.ts

@@ -467,7 +467,7 @@ module BABYLON {
          * Gets a boolean indicating that current material needs to register RTT
          */
         public get hasRenderTargetTextures(): boolean {
-          return false;
+            return false;
         }
 
         /**
@@ -476,9 +476,9 @@ module BABYLON {
         public doNotSerialize = false;
 
         /**
-         * Specifies if the effect should be stored on sub meshes
+         * @hidden
          */
-        public storeEffectOnSubMeshes = false;
+        public _storeEffectOnSubMeshes = false;
 
         /**
          * Stores the animations for the material
@@ -765,6 +765,12 @@ module BABYLON {
          */
         protected _uniformBuffer: UniformBuffer;
 
+        /** @hidden */
+        public _indexInSceneMaterialArray = -1;
+
+        /** @hidden */
+        public meshMap: Nullable<{[id: string]: AbstractMesh | undefined}>;
+
         /**
          * Creates a material instance
          * @param name defines the name of the material
@@ -788,8 +794,11 @@ module BABYLON {
             this._useUBO = this.getScene().getEngine().supportsUniformBuffers;
 
             if (!doNotAdd) {
-                this._scene.materials.push(this);
-                this._scene.onNewMaterialAddedObservable.notifyObservers(this);
+                this._scene.addMaterial(this);
+            }
+
+            if (this._scene.useMaterialMeshMap) {
+                this.meshMap = {};
             }
         }
 
@@ -1057,17 +1066,20 @@ module BABYLON {
          * @returns an array of meshes bound to the material
          */
         public getBindedMeshes(): AbstractMesh[] {
-            var result = new Array<AbstractMesh>();
-
-            for (var index = 0; index < this._scene.meshes.length; index++) {
-                var mesh = this._scene.meshes[index];
-
-                if (mesh.material === this) {
-                    result.push(mesh);
+            if (this.meshMap) {
+                var result = new Array<AbstractMesh>();
+                for (let meshId in this.meshMap) {
+                    const mesh = this.meshMap[meshId];
+                    if (mesh) {
+                        result.push(mesh);
+                    }
                 }
+                return result;
+            }
+            else {
+                const meshes = this._scene.meshes;
+                return meshes.filter((mesh) => mesh.material === this);
             }
-
-            return result;
         }
 
         /**
@@ -1100,7 +1112,7 @@ module BABYLON {
                     scene.clipPlane = new Plane(0, 0, 0, 1);
                 }
 
-                if (this.storeEffectOnSubMeshes) {
+                if (this._storeEffectOnSubMeshes) {
                     if (this.isReadyForSubMesh(mesh, subMesh)) {
                         if (onCompiled) {
                             onCompiled(this);
@@ -1259,38 +1271,34 @@ module BABYLON {
          * Disposes the material
          * @param forceDisposeEffect specifies if effects should be forcefully disposed
          * @param forceDisposeTextures specifies if textures should be forcefully disposed
+         * @param notBoundToMesh specifies if the material that is being disposed is known to be not bound to any mesh
          */
-        public dispose(forceDisposeEffect?: boolean, forceDisposeTextures?: boolean): void {
+        public dispose(forceDisposeEffect?: boolean, forceDisposeTextures?: boolean, notBoundToMesh?: boolean): void {
+            const scene = this.getScene();
             // Animations
-            this.getScene().stopAnimation(this);
-            this.getScene().freeProcessedMaterials();
+            scene.stopAnimation(this);
+            scene.freeProcessedMaterials();
 
             // Remove from scene
-            var index = this._scene.materials.indexOf(this);
-            if (index >= 0) {
-                this._scene.materials.splice(index, 1);
-            }
-            this._scene.onMaterialRemovedObservable.notifyObservers(this);
-
-            // Remove from meshes
-            for (index = 0; index < this._scene.meshes.length; index++) {
-                var mesh = this._scene.meshes[index];
-
-                if (mesh.material === this) {
-                    mesh.material = null;
-
-                    if ((<Mesh>mesh).geometry) {
-                        var geometry = <Geometry>((<Mesh>mesh).geometry);
-
-                        if (this.storeEffectOnSubMeshes) {
-                            for (var subMesh of mesh.subMeshes) {
-                                geometry._releaseVertexArrayObject(subMesh._materialEffect);
-                                if (forceDisposeEffect && subMesh._materialEffect) {
-                                    this._scene.getEngine()._releaseEffect(subMesh._materialEffect);
-                                }
-                            }
-                        } else {
-                            geometry._releaseVertexArrayObject(this._effect);
+            scene.removeMaterial(this);
+
+            if (notBoundToMesh !== true) {
+                // Remove from meshes
+                if (this.meshMap) {
+                    for (let meshId in this.meshMap) {
+                        const mesh = this.meshMap[meshId];
+                        if (mesh) {
+                            mesh.material = null; // will set the entry in the map to undefined
+                            this.releaseVertexArrayObject(mesh, forceDisposeEffect);
+                        }
+                    }
+                }
+                else {
+                    const meshes = scene.meshes;
+                    for (let mesh of meshes) {
+                        if (mesh.material === this) {
+                            mesh.material = null;
+                            this.releaseVertexArrayObject(mesh, forceDisposeEffect);
                         }
                     }
                 }
@@ -1300,8 +1308,8 @@ module BABYLON {
 
             // Shader are kept in cache for further use but we can get rid of this by using forceDisposeEffect
             if (forceDisposeEffect && this._effect) {
-                if (!this.storeEffectOnSubMeshes) {
-                    this._scene.getEngine()._releaseEffect(this._effect);
+                if (!this._storeEffectOnSubMeshes) {
+                    scene.getEngine()._releaseEffect(this._effect);
                 }
 
                 this._effect = null;
@@ -1320,6 +1328,24 @@ module BABYLON {
             }
         }
 
+        /** @hidden */
+        private  releaseVertexArrayObject(mesh: AbstractMesh, forceDisposeEffect?: boolean) {
+            if ((<Mesh>mesh).geometry) {
+                var geometry = <Geometry>((<Mesh>mesh).geometry);
+                const scene = this.getScene();
+                if (this._storeEffectOnSubMeshes) {
+                    for (var subMesh of mesh.subMeshes) {
+                        geometry._releaseVertexArrayObject(subMesh._materialEffect);
+                        if (forceDisposeEffect && subMesh._materialEffect) {
+                            scene.getEngine()._releaseEffect(subMesh._materialEffect);
+                        }
+                    }
+                } else {
+                    geometry._releaseVertexArrayObject(this._effect);
+                }
+            }
+        }
+
         /**
          * Serializes this material
          * @returns the serialized material object

+ 2 - 2
src/Materials/babylon.multiMaterial.ts

@@ -35,7 +35,7 @@ module BABYLON {
 
             this.subMaterials = new Array<Material>();
 
-            this.storeEffectOnSubMeshes = true; // multimaterial is considered like a push material
+            this._storeEffectOnSubMeshes = true; // multimaterial is considered like a push material
         }
 
         private _hookArray(array: Nullable<Material>[]): void {
@@ -105,7 +105,7 @@ module BABYLON {
             for (var index = 0; index < this.subMaterials.length; index++) {
                 var subMaterial = this.subMaterials[index];
                 if (subMaterial) {
-                    if (subMaterial.storeEffectOnSubMeshes) {
+                    if (subMaterial._storeEffectOnSubMeshes) {
                         if (!subMaterial.isReadyForSubMesh(mesh, subMesh, useInstances)) {
                             return false;
                         }

+ 14 - 7
src/Materials/babylon.pushMaterial.ts

@@ -7,11 +7,18 @@ module BABYLON {
 
         protected _activeEffect: Effect;
 
-        protected _normalMatrix : Matrix = new Matrix();
+        protected _normalMatrix: Matrix = new Matrix();
+
+        /**
+         * Gets or sets a boolean indicating that the material is allowed to do shader hot swapping.
+         * This means that the material can keep using a previous shader while a new one is being compiled.
+         * This is mostly used when shader parallel compilation is supported (true by default)
+         */
+        public allowShaderHotSwapping = true;
 
         constructor(name: string, scene: Scene) {
             super(name, scene);
-            this.storeEffectOnSubMeshes = true;
+            this._storeEffectOnSubMeshes = true;
         }
 
         public getEffect(): Effect {
@@ -30,11 +37,11 @@ module BABYLON {
             return this.isReadyForSubMesh(mesh, mesh.subMeshes[0], useInstances);
         }
 
-         /**
-         * Binds the given world matrix to the active effect
-         *
-         * @param world the matrix to bind
-         */
+        /**
+        * Binds the given world matrix to the active effect
+        *
+        * @param world the matrix to bind
+        */
         public bindOnlyWorldMatrix(world: Matrix): void {
             this._activeEffect.setMatrix("world", world);
         }

+ 3 - 2
src/Materials/babylon.shaderMaterial.ts

@@ -656,8 +656,9 @@ module BABYLON {
          * Disposes the material
          * @param forceDisposeEffect specifies if effects should be forcefully disposed
          * @param forceDisposeTextures specifies if textures should be forcefully disposed
+         * @param notBoundToMesh specifies if the material that is being disposed is known to be not bound to any mesh
          */
-        public dispose(forceDisposeEffect?: boolean, forceDisposeTextures?: boolean): void {
+        public dispose(forceDisposeEffect?: boolean, forceDisposeTextures?: boolean, notBoundToMesh?: boolean): void {
 
             if (forceDisposeTextures) {
                 var name: string;
@@ -675,7 +676,7 @@ module BABYLON {
 
             this._textures = {};
 
-            super.dispose(forceDisposeEffect, forceDisposeTextures);
+            super.dispose(forceDisposeEffect, forceDisposeTextures, notBoundToMesh);
         }
 
         /**

+ 15 - 4
src/Materials/babylon.standardMaterial.ts

@@ -983,7 +983,6 @@ module BABYLON {
             // Get correct effect
             if (defines.isDirty) {
                 defines.markAsProcessed();
-                scene.resetCachedMaterial();
 
                 // Fallbacks
                 var fallbacks = new EffectFallbacks();
@@ -1106,7 +1105,9 @@ module BABYLON {
                 }
 
                 var join = defines.toString();
-                subMesh.setEffect(scene.getEngine().createEffect(shaderName, <EffectCreationOptions>{
+
+                let previousEffect = subMesh.effect;
+                let effect = scene.getEngine().createEffect(shaderName, <EffectCreationOptions>{
                     attributes: attribs,
                     uniformsNames: uniforms,
                     uniformBuffersNames: uniformBuffers,
@@ -1116,9 +1117,19 @@ module BABYLON {
                     onCompiled: this.onCompiled,
                     onError: this.onError,
                     indexParameters: { maxSimultaneousLights: this._maxSimultaneousLights, maxSimultaneousMorphTargets: defines.NUM_MORPH_INFLUENCERS }
-                }, engine), defines);
+                }, engine);
 
-                this.buildUniformLayout();
+                if (effect) {
+                    // Use previous effect while new one is compiling
+                    if (this.allowShaderHotSwapping && previousEffect && !effect.isReady()) {
+                        effect = previousEffect;
+                        defines.markAsUnprocessed();
+                    } else {
+                        scene.resetCachedMaterial();
+                        subMesh.setEffect(effect, defines);
+                        this.buildUniformLayout();
+                    }
+                }
             }
 
             if (!subMesh.effect || !subMesh.effect.isReady()) {

+ 4 - 2
src/Materials/babylon.uniformBuffer.ts

@@ -616,10 +616,12 @@ module BABYLON {
                 return;
             }
 
-            let index = this._engine._uniformBuffers.indexOf(this);
+            const uniformBuffers = this._engine._uniformBuffers;
+            let index = uniformBuffers.indexOf(this);
 
             if (index !== -1) {
-                this._engine._uniformBuffers.splice(index, 1);
+                uniformBuffers[index] = uniformBuffers[uniformBuffers.length - 1];
+                uniformBuffers.pop();
             }
 
             if (!this._buffer) {

+ 21 - 4
src/Math/babylon.math.ts

@@ -8,7 +8,7 @@ module BABYLON {
      * Constant used to convert a value to linear space
      * @ignorenaming
      */
-     export const ToLinearSpace = 2.2;
+    export const ToLinearSpace = 2.2;
     /**
      * Constant used to define the minimal number value in Babylon.js
      * @ignorenaming
@@ -1978,7 +1978,7 @@ module BABYLON {
          * @param len the length of the vector
          * @returns the current updated Vector3
          */
-        public normalizeFromLength(len : number): Vector3 {
+        public normalizeFromLength(len: number): Vector3 {
             if (len === 0 || len === 1.0) {
                 return this;
             }
@@ -4880,9 +4880,26 @@ module BABYLON {
             return this;
         }
 
-        // Statics
+        /**
+         * Toggles model matrix from being right handed to left handed in place and vice versa
+         */
+        public toggleModelMatrixHandInPlace() {
+            [2, 6, 8, 9, 14].forEach((num) => {
+                this.m[num] *= -1;
+            });
+        }
 
         /**
+         * Toggles projection matrix from being right handed to left handed in place and vice versa
+         */
+        public toggleProjectionMatrixHandInPlace() {
+            [8, 9, 10, 11].forEach((num) => {
+                this.m[num] *= -1;
+            });
+        }
+
+        // Statics
+        /**
          * Creates a matrix from an array
          * @param array defines the source array
          * @param offset defines an offset in the source array
@@ -7175,7 +7192,7 @@ module BABYLON {
         public static Vector3: Vector3[] = Tools.BuildArray(13, Vector3.Zero); // 13 temp Vector3 at once should be enough
         public static Vector4: Vector4[] = Tools.BuildArray(3, Vector4.Zero); // 3 temp Vector4 at once should be enough
         public static Quaternion: Quaternion[] = Tools.BuildArray(2, Quaternion.Zero); // 2 temp Quaternion at once should be enough
-        public static Matrix: Matrix[] = Tools.BuildArray(6, Matrix.Identity); // 6 temp Matrices at once should be enough
+        public static Matrix: Matrix[] = Tools.BuildArray(8, Matrix.Identity); // 8 temp Matrices at once should be enough
     }
     /**
      * @hidden

+ 13 - 4
src/Mesh/babylon.abstractMesh.ts

@@ -264,8 +264,17 @@ module BABYLON {
                 return;
             }
 
+            // remove from material mesh map id needed
+            if (this._material && this._material.meshMap) {
+                this._material.meshMap[this.uniqueId] = undefined;
+            }
+
             this._material = value;
 
+            if (value && value.meshMap) {
+                value.meshMap[this.uniqueId] = this;
+            }
+
             if (this.onMaterialChangedObservable.hasObservers) {
                 this.onMaterialChangedObservable.notifyObservers(this);
             }
@@ -1196,7 +1205,7 @@ module BABYLON {
         private _onCollisionPositionChange = (collisionId: number, newPosition: Vector3, collidedMesh: Nullable<AbstractMesh> = null) => {
             //TODO move this to the collision coordinator!
             if (this.getScene().workerCollisions) {
-                newPosition.multiplyInPlace(this._collider._radius);
+                newPosition.multiplyInPlace(this._collider._radius);
             }
 
             newPosition.subtractToRef(this._oldPositionForCollisions, this._diffPositionForCollisions);
@@ -1250,7 +1259,7 @@ module BABYLON {
 
                 // Bounding test
                 if (len > 1 && !subMesh._checkCollision(collider)) {
-                    continue;
+                    continue;
                 }
 
                 this._collideForSubMesh(subMesh, transformMatrix, collider);
@@ -1262,7 +1271,7 @@ module BABYLON {
         public _checkCollision(collider: Collider): AbstractMesh {
             // Bounding box test
             if (!this._boundingInfo || !this._boundingInfo._checkCollision(collider)) {
-                return this;
+                return this;
             }
 
             // Transformation matrix
@@ -1307,7 +1316,7 @@ module BABYLON {
 
                 // Bounding test
                 if (len > 1 && !subMesh.canIntersects(ray)) {
-                    continue;
+                    continue;
                 }
 
                 var currentIntersectInfo = subMesh.intersects(ray, (<Vector3[]>this._positions), (<IndicesArray>this.getIndices()), fastCheck);

+ 5 - 5
src/Mesh/babylon.instancedMesh.ts

@@ -7,10 +7,13 @@ module BABYLON {
         private _sourceMesh: Mesh;
         private _currentLOD: Mesh;
 
+        /** @hidden */
+        public _indexInSourceMeshInstanceArray = -1;
+
         constructor(name: string, source: Mesh) {
             super(name, source.getScene());
 
-            source.instances.push(this);
+            source.addInstance(this);
 
             this._sourceMesh = source;
 
@@ -317,11 +320,8 @@ module BABYLON {
          * Returns nothing.
          */
         public dispose(doNotRecurse?: boolean, disposeMaterialAndTextures = false): void {
-
             // Remove from mesh
-            var index = this._sourceMesh.instances.indexOf(this);
-            this._sourceMesh.instances.splice(index, 1);
-
+            this._sourceMesh.removeInstance(this);
             super.dispose(doNotRecurse, disposeMaterialAndTextures);
         }
     }

+ 1 - 2
src/Mesh/babylon.linesMesh.ts

@@ -163,8 +163,7 @@ module BABYLON {
          * @param doNotRecurse If children should be disposed
          */
         public dispose(doNotRecurse?: boolean): void {
-            this._colorShader.dispose();
-
+            this._colorShader.dispose(false, false, true);
             super.dispose(doNotRecurse);
         }
 

+ 61 - 11
src/Mesh/babylon.mesh.ts

@@ -139,6 +139,8 @@ module BABYLON {
 
         /**
          * Gets the list of instances created from this mesh
+         * it is not supposed to be modified manually.
+         * Note also that the order of the InstancedMesh wihin the array is not significant and might change.
          * @see http://doc.babylonjs.com/how_to/how_to_use_instances
          */
         public instances = new Array<InstancedMesh>();
@@ -213,6 +215,8 @@ module BABYLON {
 
         // Will be used to save a source mesh reference, If any
         private _source: Nullable<Mesh> = null;
+        // Will be used to for fast cloned mesh lookup
+        private meshMap: Nullable<{[id: string]: Mesh | undefined}>;
 
         /**
          * Gets the source mesh (the one used to clone this one from)
@@ -266,6 +270,12 @@ module BABYLON {
 
                 // Source mesh
                 this._source = source;
+                if (scene.useClonedMeshhMap) {
+                    if (!source.meshMap) {
+                        source.meshMap = {};
+                    }
+                    source.meshMap[this.uniqueId] = this;
+                }
 
                 // Construction Params
                 // Clone parameters allowing mesh to be updated in case of parametric shapes.
@@ -759,11 +769,11 @@ module BABYLON {
 
             let mat = this.material || scene.defaultMaterial;
             if (mat) {
-                if (mat.storeEffectOnSubMeshes) {
+                if (mat._storeEffectOnSubMeshes) {
                     for (var subMesh of this.subMeshes) {
                         let effectiveMaterial = subMesh.getMaterial();
                         if (effectiveMaterial) {
-                            if (effectiveMaterial.storeEffectOnSubMeshes) {
+                            if (effectiveMaterial._storeEffectOnSubMeshes) {
                                 if (!effectiveMaterial.isReadyForSubMesh(this, subMesh, hardwareInstancedRendering)) {
                                     return false;
                                 }
@@ -1533,7 +1543,7 @@ module BABYLON {
 
             this._effectiveMaterial = material;
 
-            if (this._effectiveMaterial.storeEffectOnSubMeshes) {
+            if (this._effectiveMaterial._storeEffectOnSubMeshes) {
                 if (!this._effectiveMaterial.isReadyForSubMesh(this, subMesh, hardwareInstancedRendering)) {
                     return this;
                 }
@@ -1551,7 +1561,7 @@ module BABYLON {
             }
 
             var effect: Nullable<Effect>;
-            if (this._effectiveMaterial.storeEffectOnSubMeshes) {
+            if (this._effectiveMaterial._storeEffectOnSubMeshes) {
                 effect = subMesh.effect;
             } else {
                 effect = this._effectiveMaterial.getEffect();
@@ -1584,7 +1594,7 @@ module BABYLON {
 
             var world = this.getWorldMatrix();
 
-            if (this._effectiveMaterial.storeEffectOnSubMeshes) {
+            if (this._effectiveMaterial._storeEffectOnSubMeshes) {
                 this._effectiveMaterial.bindForSubMesh(world, this, subMesh);
             } else {
                 this._effectiveMaterial.bind(world, this);
@@ -2014,13 +2024,31 @@ module BABYLON {
             }
 
             // Sources
-            var meshes = this.getScene().meshes;
-            meshes.forEach((abstractMesh: AbstractMesh) => {
-                let mesh = abstractMesh as Mesh;
-                if (mesh._source && mesh._source === this) {
-                    mesh._source = null;
+            if (this._scene.useClonedMeshhMap) {
+                if (this.meshMap) {
+                    for (const uniqueId in this.meshMap) {
+                        const mesh = this.meshMap[uniqueId];
+                        if (mesh) {
+                            mesh._source = null;
+                            this.meshMap[uniqueId] = undefined;
+                        }
+                    }
                 }
-            });
+
+                if (this._source && this._source.meshMap) {
+                    this._source.meshMap[this.uniqueId] = undefined;
+                }
+            }
+            else {
+                var meshes = this.getScene().meshes;
+                for (const abstractMesh of meshes) {
+                    let mesh = abstractMesh as Mesh;
+                    if (mesh._source && mesh._source === this) {
+                        mesh._source = null;
+                    }
+                }
+            }
+
             this._source = null;
 
             // Instances
@@ -3780,5 +3808,27 @@ module BABYLON {
 
             return meshSubclass;
         }
+
+        /** @hidden */
+        public addInstance(instance: InstancedMesh) {
+            instance._indexInSourceMeshInstanceArray = this.instances.length;
+            this.instances.push(instance);
+        }
+
+        /** @hidden */
+        public removeInstance(instance: InstancedMesh) {
+            // Remove from mesh
+            const index = instance._indexInSourceMeshInstanceArray;
+            if (index != -1) {
+                if (index !== this.instances.length - 1) {
+                    const last = this.instances[this.instances.length - 1];
+                    this.instances[index] = last;
+                    last._indexInSourceMeshInstanceArray = index;
+                }
+
+                instance._indexInSourceMeshInstanceArray = -1;
+                this.instances.pop();
+            }
+        }
     }
 }

+ 58 - 65
src/Mesh/babylon.transformNode.ts

@@ -93,6 +93,9 @@ module BABYLON {
 
         protected _isWorldMatrixFrozen = false;
 
+        /** @hidden */
+        public _indexInSceneTransformNodesArray = -1;
+
         /**
         * An event triggered after the world matrix is updated
         */
@@ -162,8 +165,8 @@ module BABYLON {
         public set rotationQuaternion(quaternion: Nullable<Quaternion>) {
             this._rotationQuaternion = quaternion;
             //reset the rotation vector.
-            if (quaternion && this.rotation.length()) {
-                this.rotation.copyFromFloats(0.0, 0.0, 0.0);
+            if (quaternion) {
+                this.rotation.setAll(0.0);
             }
         }
 
@@ -222,7 +225,7 @@ module BABYLON {
             }
 
             if (this.billboardMode !== this._cache.billboardMode || this.billboardMode !== TransformNode.BILLBOARDMODE_NONE) {
-                return false;
+                return false;
             }
 
             if (this._cache.pivotMatrixUpdated) {
@@ -234,21 +237,21 @@ module BABYLON {
             }
 
             if (!this._cache.position.equals(this._position)) {
-                return false;
+                return false;
             }
 
             if (this._rotationQuaternion) {
                 if (!this._cache.rotationQuaternion.equals(this._rotationQuaternion)) {
-                    return false;
+                    return false;
                 }
             }
 
             if (!this._cache.rotation.equals(this._rotation)) {
-                return false;
+                return false;
             }
 
             if (!this._cache.scaling.equals(this._scaling)) {
-                return false;
+                return false;
             }
 
             return true;
@@ -304,7 +307,7 @@ module BABYLON {
          * @returns the current TransformNode
         */
         public setPivotMatrix(matrix: Matrix, postMultiplyPivotMatrix = true): TransformNode {
-            this._pivotMatrix = matrix.clone();
+            this._pivotMatrix.copyFrom(matrix);
             this._cache.pivotMatrixUpdated = true;
             this._postMultiplyPivotMatrix = postMultiplyPivotMatrix;
 
@@ -391,10 +394,9 @@ module BABYLON {
                 absolutePositionZ = absolutePosition.z;
             }
             if (this.parent) {
-                var invertParentWorldMatrix = this.parent.getWorldMatrix().clone();
-                invertParentWorldMatrix.invert();
-                var worldPosition = new Vector3(absolutePositionX, absolutePositionY, absolutePositionZ);
-                this.position = Vector3.TransformCoordinates(worldPosition, invertParentWorldMatrix);
+                const invertParentWorldMatrix = Tmp.Matrix[0];
+                this.parent.getWorldMatrix().invertToRef(invertParentWorldMatrix);
+                Vector3.TransformCoordinatesFromFloatsToRef(absolutePositionX, absolutePositionY, absolutePositionZ, invertParentWorldMatrix, this.position);
             } else {
                 this.position.x = absolutePositionX;
                 this.position.y = absolutePositionY;
@@ -420,9 +422,8 @@ module BABYLON {
          */
         public getPositionExpressedInLocalSpace(): Vector3 {
             this.computeWorldMatrix();
-            var invLocalWorldMatrix = this._localWorld.clone();
-            invLocalWorldMatrix.invert();
-
+            const invLocalWorldMatrix = Tmp.Matrix[0];
+            this._localWorld.invertToRef(invLocalWorldMatrix);
             return Vector3.TransformNormal(this.position, invLocalWorldMatrix);
         }
 
@@ -571,34 +572,18 @@ module BABYLON {
             if (!node && !this.parent) {
                 return this;
             }
-            if (!node) {
-                var rotation = Tmp.Quaternion[0];
-                var position = Tmp.Vector3[0];
-                var scale = Tmp.Vector3[1];
 
+            var quatRotation = Tmp.Quaternion[0];
+            var position = Tmp.Vector3[0];
+            var scale = Tmp.Vector3[1];
+
+            if (!node) {
                 if (this.parent && this.parent.computeWorldMatrix) {
                     this.parent.computeWorldMatrix(true);
                 }
                 this.computeWorldMatrix(true);
-                this.getWorldMatrix().decompose(scale, rotation, position);
-
-                if (this.rotationQuaternion) {
-                    this.rotationQuaternion.copyFrom(rotation);
-                } else {
-                    rotation.toEulerAnglesToRef(this.rotation);
-                }
-
-                this.scaling.x = scale.x;
-                this.scaling.y = scale.y;
-                this.scaling.z = scale.z;
-
-                this.position.x = position.x;
-                this.position.y = position.y;
-                this.position.z = position.z;
+                this.getWorldMatrix().decompose(scale, quatRotation, position);
             } else {
-                var rotation = Tmp.Quaternion[0];
-                var position = Tmp.Vector3[0];
-                var scale = Tmp.Vector3[1];
                 var diffMatrix = Tmp.Matrix[0];
                 var invParentMatrix = Tmp.Matrix[1];
 
@@ -607,23 +592,18 @@ module BABYLON {
 
                 node.getWorldMatrix().invertToRef(invParentMatrix);
                 this.getWorldMatrix().multiplyToRef(invParentMatrix, diffMatrix);
-                diffMatrix.decompose(scale, rotation, position);
-
-                if (this.rotationQuaternion) {
-                    this.rotationQuaternion.copyFrom(rotation);
-                } else {
-                    rotation.toEulerAnglesToRef(this.rotation);
-                }
-
-                this.position.x = position.x;
-                this.position.y = position.y;
-                this.position.z = position.z;
+                diffMatrix.decompose(scale, quatRotation, position);
+            }
 
-                this.scaling.x = scale.x;
-                this.scaling.y = scale.y;
-                this.scaling.z = scale.z;
+            if (this.rotationQuaternion) {
+                this.rotationQuaternion.copyFrom(quatRotation);
+            } else {
+                quatRotation.toEulerAnglesToRef(this.rotation);
             }
 
+            this.scaling.copyFrom(scale);
+            this.position.copyFrom(position);
+
             this.parent = node;
             return this;
         }
@@ -693,8 +673,8 @@ module BABYLON {
         public rotate(axis: Vector3, amount: number, space?: Space): TransformNode {
             axis.normalize();
             if (!this.rotationQuaternion) {
-                this.rotationQuaternion = Quaternion.RotationYawPitchRoll(this.rotation.y, this.rotation.x, this.rotation.z);
-                this.rotation = Vector3.Zero();
+                this.rotationQuaternion = this.rotation.toQuaternion();
+                this.rotation.setAll(0);
             }
             var rotationQuaternion: Quaternion;
             if (!space || (space as any) === Space.LOCAL) {
@@ -703,8 +683,8 @@ module BABYLON {
             }
             else {
                 if (this.parent) {
-                    var invertParentWorldMatrix = this.parent.getWorldMatrix().clone();
-                    invertParentWorldMatrix.invert();
+                    const invertParentWorldMatrix = Tmp.Matrix[0];
+                    this.parent.getWorldMatrix().invertToRef(invertParentWorldMatrix);
                     axis = Vector3.TransformNormal(axis, invertParentWorldMatrix);
                 }
                 rotationQuaternion = Quaternion.RotationAxisToRef(axis, amount, TransformNode._rotationAxisCache);
@@ -727,19 +707,32 @@ module BABYLON {
             axis.normalize();
             if (!this.rotationQuaternion) {
                 this.rotationQuaternion = Quaternion.RotationYawPitchRoll(this.rotation.y, this.rotation.x, this.rotation.z);
-                this.rotation.copyFromFloats(0, 0, 0);
+                this.rotation.setAll(0);
             }
-            point.subtractToRef(this.position, Tmp.Vector3[0]);
-            Matrix.TranslationToRef(Tmp.Vector3[0].x, Tmp.Vector3[0].y, Tmp.Vector3[0].z, Tmp.Matrix[0]);
-            Tmp.Matrix[0].invertToRef(Tmp.Matrix[2]);
-            Matrix.RotationAxisToRef(axis, amount, Tmp.Matrix[1]);
-            Tmp.Matrix[2].multiplyToRef(Tmp.Matrix[1], Tmp.Matrix[2]);
-            Tmp.Matrix[2].multiplyToRef(Tmp.Matrix[0], Tmp.Matrix[2]);
 
-            Tmp.Matrix[2].decompose(Tmp.Vector3[0], Tmp.Quaternion[0], Tmp.Vector3[1]);
+            const tmpVector = Tmp.Vector3[0];
+            const finalScale = Tmp.Vector3[1];
+            const finalTranslation = Tmp.Vector3[2];
+
+            const finalRotation = Tmp.Quaternion[0];
+
+            const translationMatrix = Tmp.Matrix[0]; // T
+            const translationMatrixInv = Tmp.Matrix[1]; // T'
+            const rotationMatrix = Tmp.Matrix[2]; // R
+            const finalMatrix = Tmp.Matrix[3]; // T' x R x T
+
+            point.subtractToRef(this.position, tmpVector);
+            Matrix.TranslationToRef(tmpVector.x, tmpVector.y, tmpVector.z, translationMatrix); // T
+            Matrix.TranslationToRef(-tmpVector.x, -tmpVector.y, -tmpVector.z, translationMatrixInv); // T'
+            Matrix.RotationAxisToRef(axis, amount, rotationMatrix); // R
+
+            translationMatrixInv.multiplyToRef(rotationMatrix, finalMatrix); // T' x R
+            finalMatrix.multiplyToRef(translationMatrix, finalMatrix);  // T' x R x T
+
+            finalMatrix.decompose(finalScale, finalRotation, finalTranslation);
 
-            this.position.addInPlace(Tmp.Vector3[1]);
-            Tmp.Quaternion[0].multiplyToRef(this.rotationQuaternion, this.rotationQuaternion);
+            this.position.addInPlace(finalTranslation);
+            finalRotation.multiplyToRef(this.rotationQuaternion, this.rotationQuaternion);
 
             return this;
         }

+ 19 - 20
src/Particles/babylon.gpuParticleSystem.ts

@@ -591,9 +591,9 @@ module BABYLON {
          * @param isAnimationSheetEnabled Must be true if using a spritesheet to animate the particles texture
          */
         constructor(name: string, options: Partial<{
-                        capacity: number,
-                        randomTextureSize: number
-                    }>, scene: Scene, isAnimationSheetEnabled: boolean = false) {
+            capacity: number,
+            randomTextureSize: number
+        }>, scene: Scene, isAnimationSheetEnabled: boolean = false) {
             super(name);
             this._scene = scene || Engine.LastCreatedScene;
             // Setup the default processing configuration to the scene.
@@ -626,8 +626,8 @@ module BABYLON {
             this._updateEffectOptions = {
                 attributes: ["position", "age", "life", "seed", "size", "color", "direction", "initialDirection", "angle", "cellIndex", "cellStartOffset", "noiseCoordinates1", "noiseCoordinates2"],
                 uniformsNames: ["currentCount", "timeDelta", "emitterWM", "lifeTime", "color1", "color2", "sizeRange", "scaleRange", "gravity", "emitPower",
-                                "direction1", "direction2", "minEmitBox", "maxEmitBox", "radius", "directionRandomizer", "height", "coneAngle", "stopFactor",
-                                "angleRange", "radiusRange", "cellInfos", "noiseStrength", "limitVelocityDamping"],
+                    "direction1", "direction2", "minEmitBox", "maxEmitBox", "radius", "directionRandomizer", "height", "coneAngle", "stopFactor",
+                    "angleRange", "radiusRange", "cellInfos", "noiseStrength", "limitVelocityDamping"],
                 uniformBuffersNames: [],
                 samplers: ["randomSampler", "randomSampler2", "sizeGradientSampler", "angularSpeedGradientSampler", "velocityGradientSampler", "limitVelocityGradientSampler", "noiseSampler", "dragGradientSampler"],
                 defines: "",
@@ -673,7 +673,7 @@ module BABYLON {
         }
 
         private _createUpdateVAO(source: Buffer): WebGLVertexArrayObject {
-            let updateVertexBuffers: {[key: string]: VertexBuffer} = {};
+            let updateVertexBuffers: { [key: string]: VertexBuffer } = {};
             updateVertexBuffers["position"] = source.createVertexBuffer("position", 0, 3);
             updateVertexBuffers["age"] = source.createVertexBuffer("age", 3, 1);
             updateVertexBuffers["life"] = source.createVertexBuffer("life", 4, 1);
@@ -725,7 +725,7 @@ module BABYLON {
         }
 
         private _createRenderVAO(source: Buffer, spriteSource: Buffer): WebGLVertexArrayObject {
-            let renderVertexBuffers: {[key: string]: VertexBuffer} = {};
+            let renderVertexBuffers: { [key: string]: VertexBuffer } = {};
             renderVertexBuffers["position"] = source.createVertexBuffer("position", 0, 3, this._attributesStrideSize, true);
             renderVertexBuffers["age"] = source.createVertexBuffer("age", 3, 1, this._attributesStrideSize, true);
             renderVertexBuffers["life"] = source.createVertexBuffer("life", 4, 1, this._attributesStrideSize, true);
@@ -877,10 +877,10 @@ module BABYLON {
             }
 
             // Sprite data
-            var spriteData = new Float32Array([0.5, 0.5,  1, 1,
-                                              -0.5, 0.5,  0, 1,
-                                             -0.5, -0.5,  0, 0,
-                                             0.5, -0.5,  1, 0]);
+            var spriteData = new Float32Array([0.5, 0.5, 1, 1,
+                -0.5, 0.5, 0, 1,
+                -0.5, -0.5, 0, 0,
+                0.5, -0.5, 1, 0]);
 
             // Buffers
             this._buffer0 = new Buffer(engine, data, false, this._attributesStrideSize);
@@ -1042,9 +1042,9 @@ module BABYLON {
             }
 
             this._renderEffect = new Effect("gpuRenderParticles",
-                                            ["position", "age", "life", "size", "color", "offset", "uv", "direction", "initialDirection", "angle", "cellIndex"],
-                                            uniforms,
-                                            samplers, this._scene.getEngine(), defines);
+                ["position", "age", "life", "size", "color", "offset", "uv", "direction", "initialDirection", "angle", "cellIndex"],
+                uniforms,
+                samplers, this._scene.getEngine(), defines);
         }
 
         /**
@@ -1161,11 +1161,11 @@ module BABYLON {
                     this._preWarmDone = true;
                 }
 
-                if (this._currentRenderId === this._scene.getRenderId()) {
+                if (this._currentRenderId === this._scene.getFrameId()) {
                     return 0;
                 }
 
-                this._currentRenderId = this._scene.getRenderId();
+                this._currentRenderId = this._scene.getFrameId();
             }
 
             // Get everything ready to render
@@ -1294,8 +1294,7 @@ module BABYLON {
                 }
 
                 // Draw order
-                switch (this.blendMode)
-                {
+                switch (this.blendMode) {
                     case ParticleSystem.BLENDMODE_ADD:
                         this._engine.setAlphaMode(Engine.ALPHA_ADD);
                         break;
@@ -1447,7 +1446,7 @@ module BABYLON {
          * @returns the cloned particle system
          */
         public clone(name: string, newEmitter: any): GPUParticleSystem {
-            var result = new GPUParticleSystem(name, {capacity: this._capacity, randomTextureSize: this._randomTextureSize}, this._scene);
+            var result = new GPUParticleSystem(name, { capacity: this._capacity, randomTextureSize: this._randomTextureSize }, this._scene);
 
             Tools.DeepCopy(this, result);
 
@@ -1486,7 +1485,7 @@ module BABYLON {
          */
         public static Parse(parsedParticleSystem: any, scene: Scene, rootUrl: string, doNotStart = false): GPUParticleSystem {
             var name = parsedParticleSystem.name;
-            var particleSystem = new GPUParticleSystem(name, {capacity: parsedParticleSystem.capacity, randomTextureSize: parsedParticleSystem.randomTextureSize}, scene);
+            var particleSystem = new GPUParticleSystem(name, { capacity: parsedParticleSystem.capacity, randomTextureSize: parsedParticleSystem.randomTextureSize }, scene);
 
             if (parsedParticleSystem.activeParticleCount) {
                 particleSystem.activeParticleCount = parsedParticleSystem.activeParticleCount;

+ 149 - 149
src/Particles/babylon.particleSystem.ts

@@ -206,181 +206,181 @@ module BABYLON {
                 for (var index = 0; index < particles.length; index++) {
                     var particle = particles[index];
 
-                        let scaledUpdateSpeed = this._scaledUpdateSpeed;
-                        let previousAge = particle.age;
-                        particle.age += scaledUpdateSpeed;
+                    let scaledUpdateSpeed = this._scaledUpdateSpeed;
+                    let previousAge = particle.age;
+                    particle.age += scaledUpdateSpeed;
 
-                        // Evaluate step to death
-                        if (particle.age > particle.lifeTime) {
-                            let diff = particle.age - previousAge;
-                            let oldDiff = particle.lifeTime - previousAge;
+                    // Evaluate step to death
+                    if (particle.age > particle.lifeTime) {
+                        let diff = particle.age - previousAge;
+                        let oldDiff = particle.lifeTime - previousAge;
 
-                            scaledUpdateSpeed = (oldDiff * scaledUpdateSpeed) / diff;
+                        scaledUpdateSpeed = (oldDiff * scaledUpdateSpeed) / diff;
 
-                            particle.age = particle.lifeTime;
-                        }
+                        particle.age = particle.lifeTime;
+                    }
 
-                        let ratio = particle.age / particle.lifeTime;
-
-                        // Color
-                        if (this._colorGradients && this._colorGradients.length > 0) {
-                            Tools.GetCurrentGradient(ratio, this._colorGradients, (currentGradient, nextGradient, scale) => {
-                                if (currentGradient !== particle._currentColorGradient) {
-                                    particle._currentColor1.copyFrom(particle._currentColor2);
-                                    (<ColorGradient>nextGradient).getColorToRef(particle._currentColor2);
-                                    particle._currentColorGradient = (<ColorGradient>currentGradient);
-                                }
-                                Color4.LerpToRef(particle._currentColor1, particle._currentColor2, scale, particle.color);
-                            });
-                        }
-                        else {
-                            particle.colorStep.scaleToRef(scaledUpdateSpeed, this._scaledColorStep);
-                            particle.color.addInPlace(this._scaledColorStep);
+                    let ratio = particle.age / particle.lifeTime;
 
-                            if (particle.color.a < 0) {
-                                particle.color.a = 0;
+                    // Color
+                    if (this._colorGradients && this._colorGradients.length > 0) {
+                        Tools.GetCurrentGradient(ratio, this._colorGradients, (currentGradient, nextGradient, scale) => {
+                            if (currentGradient !== particle._currentColorGradient) {
+                                particle._currentColor1.copyFrom(particle._currentColor2);
+                                (<ColorGradient>nextGradient).getColorToRef(particle._currentColor2);
+                                particle._currentColorGradient = (<ColorGradient>currentGradient);
                             }
-                        }
+                            Color4.LerpToRef(particle._currentColor1, particle._currentColor2, scale, particle.color);
+                        });
+                    }
+                    else {
+                        particle.colorStep.scaleToRef(scaledUpdateSpeed, this._scaledColorStep);
+                        particle.color.addInPlace(this._scaledColorStep);
 
-                        // Angular speed
-                        if (this._angularSpeedGradients && this._angularSpeedGradients.length > 0) {
-                            Tools.GetCurrentGradient(ratio, this._angularSpeedGradients, (currentGradient, nextGradient, scale) => {
-                                if (currentGradient !== particle._currentAngularSpeedGradient) {
-                                    particle._currentAngularSpeed1 = particle._currentAngularSpeed2;
-                                    particle._currentAngularSpeed2 = (<FactorGradient>nextGradient).getFactor();
-                                    particle._currentAngularSpeedGradient = (<FactorGradient>currentGradient);
-                                }
-                                particle.angularSpeed = Scalar.Lerp(particle._currentAngularSpeed1, particle._currentAngularSpeed2, scale);
-                            });
-                        }
-                        particle.angle += particle.angularSpeed * scaledUpdateSpeed;
-
-                        // Direction
-                        let directionScale = scaledUpdateSpeed;
-
-                        /// Velocity
-                        if (this._velocityGradients && this._velocityGradients.length > 0) {
-                            Tools.GetCurrentGradient(ratio, this._velocityGradients, (currentGradient, nextGradient, scale) => {
-                                if (currentGradient !== particle._currentVelocityGradient) {
-                                    particle._currentVelocity1 = particle._currentVelocity2;
-                                    particle._currentVelocity2 = (<FactorGradient>nextGradient).getFactor();
-                                    particle._currentVelocityGradient = (<FactorGradient>currentGradient);
-                                }
-                                directionScale *= Scalar.Lerp(particle._currentVelocity1, particle._currentVelocity2, scale);
-                            });
+                        if (particle.color.a < 0) {
+                            particle.color.a = 0;
                         }
+                    }
 
-                        particle.direction.scaleToRef(directionScale, this._scaledDirection);
-
-                        /// Limit velocity
-                        if (this._limitVelocityGradients && this._limitVelocityGradients.length > 0) {
-                            Tools.GetCurrentGradient(ratio, this._limitVelocityGradients, (currentGradient, nextGradient, scale) => {
-                                if (currentGradient !== particle._currentLimitVelocityGradient) {
-                                    particle._currentLimitVelocity1 = particle._currentLimitVelocity2;
-                                    particle._currentLimitVelocity2 = (<FactorGradient>nextGradient).getFactor();
-                                    particle._currentLimitVelocityGradient = (<FactorGradient>currentGradient);
-                                }
+                    // Angular speed
+                    if (this._angularSpeedGradients && this._angularSpeedGradients.length > 0) {
+                        Tools.GetCurrentGradient(ratio, this._angularSpeedGradients, (currentGradient, nextGradient, scale) => {
+                            if (currentGradient !== particle._currentAngularSpeedGradient) {
+                                particle._currentAngularSpeed1 = particle._currentAngularSpeed2;
+                                particle._currentAngularSpeed2 = (<FactorGradient>nextGradient).getFactor();
+                                particle._currentAngularSpeedGradient = (<FactorGradient>currentGradient);
+                            }
+                            particle.angularSpeed = Scalar.Lerp(particle._currentAngularSpeed1, particle._currentAngularSpeed2, scale);
+                        });
+                    }
+                    particle.angle += particle.angularSpeed * scaledUpdateSpeed;
+
+                    // Direction
+                    let directionScale = scaledUpdateSpeed;
+
+                    /// Velocity
+                    if (this._velocityGradients && this._velocityGradients.length > 0) {
+                        Tools.GetCurrentGradient(ratio, this._velocityGradients, (currentGradient, nextGradient, scale) => {
+                            if (currentGradient !== particle._currentVelocityGradient) {
+                                particle._currentVelocity1 = particle._currentVelocity2;
+                                particle._currentVelocity2 = (<FactorGradient>nextGradient).getFactor();
+                                particle._currentVelocityGradient = (<FactorGradient>currentGradient);
+                            }
+                            directionScale *= Scalar.Lerp(particle._currentVelocity1, particle._currentVelocity2, scale);
+                        });
+                    }
 
-                                let limitVelocity = Scalar.Lerp(particle._currentLimitVelocity1, particle._currentLimitVelocity2, scale);
-                                let currentVelocity = particle.direction.length();
+                    particle.direction.scaleToRef(directionScale, this._scaledDirection);
 
-                                if (currentVelocity > limitVelocity) {
-                                    particle.direction.scaleInPlace(this.limitVelocityDamping);
-                                }
-                            });
-                        }
+                    /// Limit velocity
+                    if (this._limitVelocityGradients && this._limitVelocityGradients.length > 0) {
+                        Tools.GetCurrentGradient(ratio, this._limitVelocityGradients, (currentGradient, nextGradient, scale) => {
+                            if (currentGradient !== particle._currentLimitVelocityGradient) {
+                                particle._currentLimitVelocity1 = particle._currentLimitVelocity2;
+                                particle._currentLimitVelocity2 = (<FactorGradient>nextGradient).getFactor();
+                                particle._currentLimitVelocityGradient = (<FactorGradient>currentGradient);
+                            }
 
-                        /// Drag
-                        if (this._dragGradients && this._dragGradients.length > 0) {
-                            Tools.GetCurrentGradient(ratio, this._dragGradients, (currentGradient, nextGradient, scale) => {
-                                if (currentGradient !== particle._currentDragGradient) {
-                                    particle._currentDrag1 = particle._currentDrag2;
-                                    particle._currentDrag2 = (<FactorGradient>nextGradient).getFactor();
-                                    particle._currentDragGradient = (<FactorGradient>currentGradient);
-                                }
+                            let limitVelocity = Scalar.Lerp(particle._currentLimitVelocity1, particle._currentLimitVelocity2, scale);
+                            let currentVelocity = particle.direction.length();
 
-                                let drag = Scalar.Lerp(particle._currentDrag1, particle._currentDrag2, scale);
+                            if (currentVelocity > limitVelocity) {
+                                particle.direction.scaleInPlace(this.limitVelocityDamping);
+                            }
+                        });
+                    }
 
-                                this._scaledDirection.scaleInPlace(1.0 - drag);
-                            });
-                        }
+                    /// Drag
+                    if (this._dragGradients && this._dragGradients.length > 0) {
+                        Tools.GetCurrentGradient(ratio, this._dragGradients, (currentGradient, nextGradient, scale) => {
+                            if (currentGradient !== particle._currentDragGradient) {
+                                particle._currentDrag1 = particle._currentDrag2;
+                                particle._currentDrag2 = (<FactorGradient>nextGradient).getFactor();
+                                particle._currentDragGradient = (<FactorGradient>currentGradient);
+                            }
 
-                        particle.position.addInPlace(this._scaledDirection);
+                            let drag = Scalar.Lerp(particle._currentDrag1, particle._currentDrag2, scale);
 
-                        // Noise
-                        if (noiseTextureData && noiseTextureSize) {
-                            let fetchedColorR = this._fetchR(particle._randomNoiseCoordinates1.x, particle._randomNoiseCoordinates1.y, noiseTextureSize.width, noiseTextureSize.height, noiseTextureData);
-                            let fetchedColorG = this._fetchR(particle._randomNoiseCoordinates1.z, particle._randomNoiseCoordinates2.x, noiseTextureSize.width, noiseTextureSize.height, noiseTextureData);
-                            let fetchedColorB = this._fetchR(particle._randomNoiseCoordinates2.y, particle._randomNoiseCoordinates2.z, noiseTextureSize.width, noiseTextureSize.height, noiseTextureData);
+                            this._scaledDirection.scaleInPlace(1.0 - drag);
+                        });
+                    }
 
-                            let force = Tmp.Vector3[0];
-                            let scaledForce = Tmp.Vector3[1];
+                    particle.position.addInPlace(this._scaledDirection);
 
-                            force.copyFromFloats((2 * fetchedColorR - 1) * this.noiseStrength.x, (2 * fetchedColorG - 1) * this.noiseStrength.y, (2 * fetchedColorB - 1) * this.noiseStrength.z);
+                    // Noise
+                    if (noiseTextureData && noiseTextureSize) {
+                        let fetchedColorR = this._fetchR(particle._randomNoiseCoordinates1.x, particle._randomNoiseCoordinates1.y, noiseTextureSize.width, noiseTextureSize.height, noiseTextureData);
+                        let fetchedColorG = this._fetchR(particle._randomNoiseCoordinates1.z, particle._randomNoiseCoordinates2.x, noiseTextureSize.width, noiseTextureSize.height, noiseTextureData);
+                        let fetchedColorB = this._fetchR(particle._randomNoiseCoordinates2.y, particle._randomNoiseCoordinates2.z, noiseTextureSize.width, noiseTextureSize.height, noiseTextureData);
 
-                            force.scaleToRef(scaledUpdateSpeed, scaledForce);
-                            particle.direction.addInPlace(scaledForce);
-                        }
+                        let force = Tmp.Vector3[0];
+                        let scaledForce = Tmp.Vector3[1];
 
-                        // Gravity
-                        this.gravity.scaleToRef(scaledUpdateSpeed, this._scaledGravity);
-                        particle.direction.addInPlace(this._scaledGravity);
-
-                        // Size
-                        if (this._sizeGradients && this._sizeGradients.length > 0) {
-                            Tools.GetCurrentGradient(ratio, this._sizeGradients, (currentGradient, nextGradient, scale) => {
-                                if (currentGradient !== particle._currentSizeGradient) {
-                                    particle._currentSize1 = particle._currentSize2;
-                                    particle._currentSize2 = (<FactorGradient>nextGradient).getFactor();
-                                    particle._currentSizeGradient = (<FactorGradient>currentGradient);
-                                }
-                                particle.size = Scalar.Lerp(particle._currentSize1, particle._currentSize2, scale);
-                            });
-                        }
+                        force.copyFromFloats((2 * fetchedColorR - 1) * this.noiseStrength.x, (2 * fetchedColorG - 1) * this.noiseStrength.y, (2 * fetchedColorB - 1) * this.noiseStrength.z);
 
-                        // Remap data
-                        if (this._useRampGradients) {
-                            if (this._colorRemapGradients && this._colorRemapGradients.length > 0) {
-                                Tools.GetCurrentGradient(ratio, this._colorRemapGradients, (currentGradient, nextGradient, scale) => {
-                                    let min = Scalar.Lerp((<FactorGradient>currentGradient).factor1, (<FactorGradient>nextGradient).factor1, scale);
-                                    let max = Scalar.Lerp((<FactorGradient>currentGradient).factor2!, (<FactorGradient>nextGradient).factor2!, scale);
+                        force.scaleToRef(scaledUpdateSpeed, scaledForce);
+                        particle.direction.addInPlace(scaledForce);
+                    }
 
-                                    particle.remapData.x = min;
-                                    particle.remapData.y = max - min;
-                                });
+                    // Gravity
+                    this.gravity.scaleToRef(scaledUpdateSpeed, this._scaledGravity);
+                    particle.direction.addInPlace(this._scaledGravity);
+
+                    // Size
+                    if (this._sizeGradients && this._sizeGradients.length > 0) {
+                        Tools.GetCurrentGradient(ratio, this._sizeGradients, (currentGradient, nextGradient, scale) => {
+                            if (currentGradient !== particle._currentSizeGradient) {
+                                particle._currentSize1 = particle._currentSize2;
+                                particle._currentSize2 = (<FactorGradient>nextGradient).getFactor();
+                                particle._currentSizeGradient = (<FactorGradient>currentGradient);
                             }
+                            particle.size = Scalar.Lerp(particle._currentSize1, particle._currentSize2, scale);
+                        });
+                    }
 
-                            if (this._alphaRemapGradients && this._alphaRemapGradients.length > 0) {
-                                Tools.GetCurrentGradient(ratio, this._alphaRemapGradients, (currentGradient, nextGradient, scale) => {
-                                    let min = Scalar.Lerp((<FactorGradient>currentGradient).factor1, (<FactorGradient>nextGradient).factor1, scale);
-                                    let max = Scalar.Lerp((<FactorGradient>currentGradient).factor2!, (<FactorGradient>nextGradient).factor2!, scale);
+                    // Remap data
+                    if (this._useRampGradients) {
+                        if (this._colorRemapGradients && this._colorRemapGradients.length > 0) {
+                            Tools.GetCurrentGradient(ratio, this._colorRemapGradients, (currentGradient, nextGradient, scale) => {
+                                let min = Scalar.Lerp((<FactorGradient>currentGradient).factor1, (<FactorGradient>nextGradient).factor1, scale);
+                                let max = Scalar.Lerp((<FactorGradient>currentGradient).factor2!, (<FactorGradient>nextGradient).factor2!, scale);
 
-                                    particle.remapData.z = min;
-                                    particle.remapData.w = max - min;
-                                });
-                            }
+                                particle.remapData.x = min;
+                                particle.remapData.y = max - min;
+                            });
                         }
 
-                        if (this._isAnimationSheetEnabled) {
-                            particle.updateCellIndex();
+                        if (this._alphaRemapGradients && this._alphaRemapGradients.length > 0) {
+                            Tools.GetCurrentGradient(ratio, this._alphaRemapGradients, (currentGradient, nextGradient, scale) => {
+                                let min = Scalar.Lerp((<FactorGradient>currentGradient).factor1, (<FactorGradient>nextGradient).factor1, scale);
+                                let max = Scalar.Lerp((<FactorGradient>currentGradient).factor2!, (<FactorGradient>nextGradient).factor2!, scale);
+
+                                particle.remapData.z = min;
+                                particle.remapData.w = max - min;
+                            });
                         }
+                    }
 
-                        // Update the position of the attached sub-emitters to match their attached particle
-                        particle._inheritParticleInfoToSubEmitters();
-
-                        if (particle.age >= particle.lifeTime) { // Recycle by swapping with last particle
-                            this._emitFromParticle(particle);
-                            if (particle._attachedSubEmitters) {
-                                particle._attachedSubEmitters.forEach((subEmitter) => {
-                                    subEmitter.particleSystem.disposeOnStop = true;
-                                    subEmitter.particleSystem.stop();
-                                });
-                                particle._attachedSubEmitters = null;
-                            }
-                            this.recycleParticle(particle);
-                            index--;
-                            continue;
+                    if (this._isAnimationSheetEnabled) {
+                        particle.updateCellIndex();
+                    }
+
+                    // Update the position of the attached sub-emitters to match their attached particle
+                    particle._inheritParticleInfoToSubEmitters();
+
+                    if (particle.age >= particle.lifeTime) { // Recycle by swapping with last particle
+                        this._emitFromParticle(particle);
+                        if (particle._attachedSubEmitters) {
+                            particle._attachedSubEmitters.forEach((subEmitter) => {
+                                subEmitter.particleSystem.disposeOnStop = true;
+                                subEmitter.particleSystem.stop();
+                            });
+                            particle._attachedSubEmitters = null;
                         }
+                        this.recycleParticle(particle);
+                        index--;
+                        continue;
+                    }
                 }
             };
         }
@@ -1138,14 +1138,14 @@ module BABYLON {
                     }
                     else if (offsetX === 1) {
                         offsetX = 1 - this._epsilon;
-                         }
+                    }
 
                     if (offsetY === 0) {
                         offsetY = this._epsilon;
                     }
                     else if (offsetY === 1) {
                         offsetY = 1 - this._epsilon;
-                         }
+                    }
                 }
 
                 this._vertexData[offset++] = offsetX;
@@ -1565,10 +1565,10 @@ module BABYLON {
                     return;
                 }
 
-                if (this._currentRenderId === this._scene.getRenderId()) {
+                if (this._currentRenderId === this._scene.getFrameId()) {
                     return;
                 }
-                this._currentRenderId = this._scene.getRenderId();
+                this._currentRenderId = this._scene.getFrameId();
             }
 
             this._scaledUpdateSpeed = this.updateSpeed * (preWarmOnly ? this.preWarmStepOffset : this._scene.getAnimationRatio());

+ 14 - 4
src/Particles/babylon.solidParticleSystem.ts

@@ -301,13 +301,23 @@ module BABYLON {
             var index = 0;
             var idx = 0;
             const tmpNormal = Tmp.Vector3[0];
-            const rotMatrix = Tmp.Matrix[0];
-            const invertedRotMatrix = Tmp.Matrix[1];
+            const quaternion = Tmp.Quaternion[0];
+            const invertedRotMatrix = Tmp.Matrix[0];
             for (var p = 0; p < this.particles.length; p++) {
                 const particle = this.particles[p];
                 const shape = particle._model._shape;
-                particle.getRotationMatrix(rotMatrix);
-                rotMatrix.invertToRef(invertedRotMatrix);
+
+                // computing the inverse of the rotation matrix from the quaternion
+                // is equivalent to computing the matrix of the inverse quaternion, i.e of the conjugate quaternion
+                if (particle.rotationQuaternion) {
+                    particle.rotationQuaternion.conjugateToRef(quaternion);
+                }
+                else {
+                    const rotation = particle.rotation;
+                    Quaternion.RotationYawPitchRollToRef(rotation.y, rotation.x, rotation.z, quaternion);
+                    quaternion.conjugateInPlace();
+                }
+                quaternion.toRotationMatrix(invertedRotMatrix);
 
                 for (var pt = 0; pt < shape.length; pt++) {
                     idx = index + pt * 3;

+ 2 - 2
src/PostProcess/babylon.motionBlurPostProcess.ts

@@ -3,7 +3,7 @@ module BABYLON {
      * The Motion Blur Post Process which blurs an image based on the objects velocity in scene.
      * Velocity can be affected by each object's rotation, position and scale depending on the transformation speed.
      * As an example, all you have to do is to create the post-process:
-     *  var mb = new BABYLON.MotionBlurProcess(
+     *  var mb = new BABYLON.MotionBlurPostProcess(
      *      'mb', // The name of the effect.
      *      scene, // The scene containing the objects to blur according to their velocity.
      *      1.0, // The required width/height ratio to downsize to before computing the render pass.
@@ -11,7 +11,7 @@ module BABYLON {
      * );
      * Then, all objects moving, rotating and/or scaling will be blurred depending on the transformation speed.
      */
-    export class MotionBlurProcess extends PostProcess {
+    export class MotionBlurPostProcess extends PostProcess {
         /**
          * Defines how much the image is blurred by the movement. Default value is equal to 1
          */

+ 20 - 0
src/Tools/babylon.tools.ts

@@ -1,5 +1,25 @@
 module BABYLON {
     /**
+     * Interface for any object that can request an animation frame
+     */
+    export interface ICustomAnimationFrameRequester {
+        /**
+         * This function will be called when the render loop is ready. If this is not populated, the engine's renderloop function will be called
+         */
+        renderFunction?: Function;
+        /**
+         * Called to request the next frame to render to
+         * @see https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame
+         */
+        requestAnimationFrame: Function;
+        /**
+         * You can pass this value to cancelAnimationFrame() to cancel the refresh callback request
+         * @see https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame#Return_value
+         */
+        requestID?: number;
+    }
+
+    /**
      * Interface containing an array of animations
      */
     export interface IAnimatable {

+ 6 - 0
src/babylon.abstractScene.ts

@@ -138,6 +138,9 @@ module BABYLON {
 
         /**
         * All of the materials added to this scene
+        * In the context of a Scene, it is not supposed to be modified manually.
+        * Any addition or removal should be done using the addMaterial and removeMAterial Scene methods.
+        * Note also that the order of the Material wihin the array is not significant and might change.
         * @see http://doc.babylonjs.com/babylon101/materials
         */
         public materials = new Array<Material>();
@@ -155,6 +158,9 @@ module BABYLON {
 
         /**
         * All of the tranform nodes added to this scene
+        * In the context of a Scene, it is not supposed to be modified manually.
+        * Any addition or removal should be done using the addTransformNode and removeTransformNode Scene methods.
+        * Note also that the order of the TransformNode wihin the array is not significant and might change.
         * @see http://doc.babylonjs.com/how_to/transformnode
         */
         public transformNodes = new Array<TransformNode>();

+ 7 - 2
src/babylon.assetContainer.ts

@@ -20,6 +20,11 @@ module BABYLON {
         constructor(scene: Scene) {
             super();
             this.scene = scene;
+            this["sounds"] = [];
+            this["effectLayers"] = [];
+            this["layers"] = [];
+            this["lensFlareSystems"] = [];
+            this["proceduralTextures"] = [];
         }
 
         /**
@@ -67,7 +72,7 @@ module BABYLON {
             });
 
             for (let component of this.scene._serializableComponents) {
-                component.addFromContainer(this.scene);
+                component.addFromContainer(this);
             }
         }
 
@@ -116,7 +121,7 @@ module BABYLON {
             });
 
             for (let component of this.scene._serializableComponents) {
-                component.removeFromContainer(this.scene);
+                component.removeFromContainer(this);
             }
         }
 

+ 41 - 10
src/babylon.mixins.ts

@@ -99,7 +99,6 @@ interface Document {
     webkitCancelFullScreen(): void;
     requestPointerLock(): void;
     exitPointerLock(): void;
-    fullscreen: boolean;
     mozFullScreen: boolean;
     msIsFullScreen: boolean;
     readonly webkitIsFullScreen: boolean;
@@ -155,15 +154,6 @@ interface HTMLVideoElement {
     mozSrcObject: any;
 }
 
-interface Element {
-    webkitRequestFullScreen: () => void;
-}
-
-interface Screen {
-    readonly orientation: string;
-    readonly mozOrientation: string;
-}
-
 interface Math {
     fround(x: number): number;
     imul(a: number, b: number): number;
@@ -187,3 +177,44 @@ interface EXT_disjoint_timer_query {
 interface WebGLUniformLocation {
     _currentState: any;
 }
+
+// WebXR
+interface XRDevice {
+    requestSession(options: XRSessionCreationOptions): Promise<XRSession>;
+}
+interface XRSession {
+    baseLayer: XRWebGLLayer;
+    requestFrameOfReference(type: XRFrameOfReferenceType): Promise<void>;
+    requestHitTest(origin: Float32Array, direction: Float32Array, frameOfReference: any): any;
+    end(): void;
+    requestAnimationFrame: Function;
+}
+interface XRSessionCreationOptions {
+}
+interface XRLayer {
+    getViewport: Function;
+    framebufferWidth: number;
+    framebufferHeight: number;
+}
+interface XRView {
+    projectionMatrix: Float32Array;
+}
+interface XRFrame {
+    getDevicePose: Function;
+    views: Array<XRView>;
+    baseLayer: XRLayer;
+}
+interface XRFrameOfReference {
+}
+interface XRWebGLLayer extends XRLayer {
+    framebuffer: WebGLFramebuffer;
+}
+declare var XRWebGLLayer: {
+    prototype: XRWebGLLayer;
+    new(session: XRSession, context?: WebGLRenderingContext): XRWebGLLayer;
+};
+enum XRFrameOfReferenceType {
+    "head-model",
+    "eye-level",
+    "stage",
+}

+ 119 - 37
src/babylon.scene.ts

@@ -70,6 +70,18 @@ module BABYLON {
          * It will improve performance when the number of geometries becomes important.
          */
         useGeometryIdsMap?: boolean;
+
+        /**
+         * Defines that each material of the scene should keep up-to-date a map of referencing meshes for fast diposing
+         * It will improve performance when the number of mesh becomes important, but might consume a bit more memory
+         */
+        useMaterialMeshMap?: boolean;
+
+        /**
+         * Defines that each mesh of the scene should keep up-to-date a map of referencing cloned meshes for fast diposing
+         * It will improve performance when the number of mesh becomes important, but might consume a bit more memory
+         */
+        useClonedMeshhMap?: boolean;
     }
 
     /**
@@ -253,6 +265,12 @@ module BABYLON {
          */
         public preventDefaultOnPointerDown = true;
 
+        /**
+         * This is used to call preventDefault() on pointer up
+         * in order to block unwanted artifacts like system double clicks
+         */
+        public preventDefaultOnPointerUp = true;
+
         // Metadata
         /**
          * Gets or sets user defined metadata
@@ -987,6 +1005,7 @@ module BABYLON {
         private _alternateTransformMatrix: Matrix;
         private _useAlternateCameraConfiguration = false;
         private _alternateRendering = false;
+        private _wheelEventName = "";
         /** @hidden */
         public _forcedViewPosition: Nullable<Vector3>;
 
@@ -1009,6 +1028,11 @@ module BABYLON {
          */
         public requireLightSorting = false;
 
+        /** @hidden */
+        public readonly useMaterialMeshMap: boolean;
+        /** @hidden */
+        public readonly useClonedMeshhMap: boolean;
+
         private _pointerOverMesh: Nullable<AbstractMesh>;
 
         private _pickedDownMesh: Nullable<AbstractMesh>;
@@ -1216,6 +1240,9 @@ module BABYLON {
             if (options && options.useGeometryIdsMap === true) {
                 this.geometriesById = {};
             }
+
+            this.useMaterialMeshMap = options && options.useGeometryIdsMap || false;
+            this.useClonedMeshhMap = options && options.useClonedMeshhMap || false;
         }
 
         private _defaultMeshCandidates: ISmartArrayLike<AbstractMesh> = {
@@ -1609,7 +1636,7 @@ module BABYLON {
             }
 
             if (pickResult) {
-                let type = evt.type === "mousewheel" || evt.type === "DOMMouseScroll" ? PointerEventTypes.POINTERWHEEL : PointerEventTypes.POINTERMOVE;
+                let type = evt.type === this._wheelEventName ? PointerEventTypes.POINTERWHEEL : PointerEventTypes.POINTERMOVE;
 
                 if (this.onPointerMove) {
                     this.onPointerMove(evt, pickResult, type);
@@ -1726,8 +1753,6 @@ module BABYLON {
         public simulatePointerUp(pickResult: PickingInfo, pointerEventInit?: PointerEventInit): Scene {
             let evt = new PointerEvent("pointerup", pointerEventInit);
             let clickInfo = new ClickInfo();
-            clickInfo.singleClick = true;
-            clickInfo.ignore = true;
 
             if (this._checkPrePointerObservable(pickResult, evt, PointerEventTypes.POINTERUP)) {
                 return this;
@@ -1779,30 +1804,20 @@ module BABYLON {
 
             let type = PointerEventTypes.POINTERUP;
             if (this.onPointerObservable.hasObservers()) {
-                if (!clickInfo.ignore) {
-                    if (!clickInfo.hasSwiped) {
-                        if (clickInfo.singleClick && this.onPointerObservable.hasSpecificMask(PointerEventTypes.POINTERTAP)) {
-                            let type = PointerEventTypes.POINTERTAP;
-                            let pi = new PointerInfo(type, evt, pickResult);
-                            this._setRayOnPointerInfo(pi);
-                            this.onPointerObservable.notifyObservers(pi, type);
-                        }
-                        if (clickInfo.doubleClick && this.onPointerObservable.hasSpecificMask(PointerEventTypes.POINTERDOUBLETAP)) {
-                            let type = PointerEventTypes.POINTERDOUBLETAP;
-                            let pi = new PointerInfo(type, evt, pickResult);
-                            this._setRayOnPointerInfo(pi);
-                            this.onPointerObservable.notifyObservers(pi, type);
-                        }
+                if (!clickInfo.ignore && !clickInfo.hasSwiped) {
+                    if (clickInfo.singleClick && this.onPointerObservable.hasSpecificMask(PointerEventTypes.POINTERTAP)) {
+                        type = PointerEventTypes.POINTERTAP;
+                    }
+                    else if (clickInfo.doubleClick && this.onPointerObservable.hasSpecificMask(PointerEventTypes.POINTERDOUBLETAP)) {
+                        type = PointerEventTypes.POINTERDOUBLETAP;
                     }
                 }
-                else {
-                    let pi = new PointerInfo(type, evt, pickResult);
-                    this._setRayOnPointerInfo(pi);
-                    this.onPointerObservable.notifyObservers(pi, type);
-                }
+                let pi = new PointerInfo(type, evt, pickResult);
+                this._setRayOnPointerInfo(pi);
+                this.onPointerObservable.notifyObservers(pi, type);
             }
 
-            if (this.onPointerUp) {
+            if (this.onPointerUp && !clickInfo.ignore) {
                 this.onPointerUp(evt, pickResult, type);
             }
 
@@ -1892,7 +1907,6 @@ module BABYLON {
                             if (Date.now() - this._previousStartingPointerTime > Scene.DoubleClickDelay ||
                                 btn !== this._previousButtonPressed) {
                                 clickInfo.singleClick = true;
-
                                 cb(clickInfo, this._currentPickResult);
                             }
                         }
@@ -1961,6 +1975,7 @@ module BABYLON {
                         }
                     }
                 }
+
                 clickInfo.ignore = true;
                 cb(clickInfo, this._currentPickResult);
             };
@@ -1970,7 +1985,7 @@ module BABYLON {
                 this._updatePointerPosition(evt);
 
                 // PreObservable support
-                if (this._checkPrePointerObservable(null, evt, evt.type === "mousewheel" || evt.type === "DOMMouseScroll" ? PointerEventTypes.POINTERWHEEL : PointerEventTypes.POINTERMOVE)) {
+                if (this._checkPrePointerObservable(null, evt, evt.type === this._wheelEventName ? PointerEventTypes.POINTERWHEEL : PointerEventTypes.POINTERMOVE)) {
                     return;
                 }
 
@@ -2037,6 +2052,12 @@ module BABYLON {
                 this._meshPickProceed = false;
 
                 this._updatePointerPosition(evt);
+
+                if (this.preventDefaultOnPointerUp && canvas) {
+                    evt.preventDefault();
+                    canvas.focus();
+                }
+
                 this._initClickEvent(this.onPrePointerObservable, this.onPointerObservable, evt, (clickInfo: ClickInfo, pickResult: Nullable<PickingInfo>) => {
                     // PreObservable support
                     if (this.onPrePointerObservable.hasObservers()) {
@@ -2153,9 +2174,13 @@ module BABYLON {
 
             if (attachMove) {
                 canvas.addEventListener(eventPrefix + "move", <any>this._onPointerMove, false);
+
                 // Wheel
-                canvas.addEventListener('mousewheel', <any>this._onPointerMove, false);
-                canvas.addEventListener('DOMMouseScroll', <any>this._onPointerMove, false);
+                this._wheelEventName = "onwheel" in document.createElement("div") ? "wheel" :       // Modern browsers support "wheel"
+                    (<any>document).onmousewheel !== undefined ? "mousewheel" :                     // Webkit and IE support at least "mousewheel"
+                        "DOMMouseScroll";                                                           // let's assume that remaining browsers are older Firefox
+
+                canvas.addEventListener(this._wheelEventName, <any>this._onPointerMove, false);
             }
 
             if (attachDown) {
@@ -2192,8 +2217,7 @@ module BABYLON {
             }
 
             // Wheel
-            canvas.removeEventListener('mousewheel', <any>this._onPointerMove);
-            canvas.removeEventListener('DOMMouseScroll', <any>this._onPointerMove);
+            canvas.removeEventListener(this._wheelEventName, <any>this._onPointerMove);
 
             // Keyboard
             canvas.removeEventListener("keydown", this._onKeyDown);
@@ -2983,7 +3007,8 @@ module BABYLON {
             var index = this.meshes.indexOf(toRemove);
             if (index !== -1) {
                 // Remove from the scene if mesh found
-                this.meshes.splice(index, 1);
+                this.meshes[index] = this.meshes[this.meshes.length - 1];
+                this.meshes.pop();
             }
 
             this.onMeshRemovedObservable.notifyObservers(toRemove);
@@ -3000,6 +3025,7 @@ module BABYLON {
          * @param newTransformNode defines the transform node to add
          */
         public addTransformNode(newTransformNode: TransformNode) {
+            newTransformNode._indexInSceneTransformNodesArray = this.transformNodes.length;
             this.transformNodes.push(newTransformNode);
 
             this.onNewTransformNodeAddedObservable.notifyObservers(newTransformNode);
@@ -3011,10 +3037,16 @@ module BABYLON {
          * @returns the index where the transform node was in the transform node list
          */
         public removeTransformNode(toRemove: TransformNode): number {
-            var index = this.transformNodes.indexOf(toRemove);
+            var index = toRemove._indexInSceneTransformNodesArray;
             if (index !== -1) {
-                // Remove from the scene if found
-                this.transformNodes.splice(index, 1);
+                if (index !== this.transformNodes.length - 1) {
+                    const lastNode = this.transformNodes[this.transformNodes.length - 1];
+                    this.transformNodes[index] = lastNode;
+                    lastNode._indexInSceneTransformNodesArray = index;
+                }
+
+                toRemove._indexInSceneTransformNodesArray = -1;
+                this.transformNodes.pop();
             }
 
             this.onTransformNodeRemovedObservable.notifyObservers(toRemove);
@@ -3160,10 +3192,18 @@ module BABYLON {
          * @returns The index of the removed material
          */
         public removeMaterial(toRemove: Material): number {
-            var index = this.materials.indexOf(toRemove);
+            var index = toRemove._indexInSceneMaterialArray;
             if (index !== -1) {
-                this.materials.splice(index, 1);
+                if (index !== this.materials.length - 1) {
+                    const lastMaterial = this.materials[this.materials.length - 1];
+                    this.materials[index] = lastMaterial;
+                    lastMaterial._indexInSceneMaterialArray = index;
+                }
+
+                toRemove._indexInSceneMaterialArray = -1;
+                this.materials.pop();
             }
+
             this.onMaterialRemovedObservable.notifyObservers(toRemove);
 
             return index;
@@ -3279,6 +3319,7 @@ module BABYLON {
          * @param newMaterial The material to add
          */
         public addMaterial(newMaterial: Material): void {
+            newMaterial._indexInSceneMaterialArray = this.materials.length;
             this.materials.push(newMaterial);
             this.onNewMaterialAddedObservable.notifyObservers(newMaterial);
         }
@@ -4016,10 +4057,38 @@ module BABYLON {
             this._processedMaterials.dispose();
         }
 
+        private _preventFreeActiveMeshesAndRenderingGroups = false;
+
+        /** Gets or sets a boolean blocking all the calls to freeActiveMeshes and freeRenderingGroups
+         * It can be used in order to prevent going through methods freeRenderingGroups and freeActiveMeshes several times to improve performance
+         * when disposing several meshes in a row or a hierarchy of meshes.
+         * When used, it is the responsability of the user to blockfreeActiveMeshesAndRenderingGroups back to false.
+         */
+        public get blockfreeActiveMeshesAndRenderingGroups(): boolean {
+            return this._preventFreeActiveMeshesAndRenderingGroups;
+        }
+
+        public set blockfreeActiveMeshesAndRenderingGroups(value: boolean) {
+            if (this._preventFreeActiveMeshesAndRenderingGroups === value) {
+                return;
+            }
+
+            if (value) {
+                this.freeActiveMeshes();
+                this.freeRenderingGroups();
+            }
+
+            this._preventFreeActiveMeshesAndRenderingGroups = value;
+        }
+
         /**
          * Clear the active meshes smart array preventing retention point in mesh dispose.
          */
         public freeActiveMeshes(): void {
+            if (this.blockfreeActiveMeshesAndRenderingGroups) {
+                return;
+            }
+
             this._activeMeshes.dispose();
             if (this.activeCamera && this.activeCamera._activeMeshes) {
                 this.activeCamera._activeMeshes.dispose();
@@ -4038,6 +4107,10 @@ module BABYLON {
          * Clear the info related to rendering groups preventing retention points during dispose.
          */
         public freeRenderingGroups(): void {
+            if (this.blockfreeActiveMeshesAndRenderingGroups) {
+                return;
+            }
+
             if (this._renderingManager) {
                 this._renderingManager.freeRenderingGroups();
             }
@@ -4159,7 +4232,7 @@ module BABYLON {
 
                 mesh._preActivate();
 
-                if (mesh.isVisible && mesh.visibility > 0 && (mesh.alwaysSelectAsActiveMesh || ((mesh.layerMask & this.activeCamera.layerMask) !== 0 && mesh.isInFrustum(this._frustumPlanes)))) {
+                if (mesh.isVisible && mesh.visibility > 0 && ((mesh.layerMask & this.activeCamera.layerMask) !== 0) && (mesh.alwaysSelectAsActiveMesh || mesh.isInFrustum(this._frustumPlanes))) {
                     this._activeMeshes.push(mesh);
                     this.activeCamera._activeMeshes.push(mesh);
 
@@ -4321,7 +4394,16 @@ module BABYLON {
 
                 this._intermediateRendering = false;
 
-                engine.restoreDefaultFramebuffer(); // Restore back buffer if needed
+                if (this.activeCamera.outputRenderTarget) {
+                    var internalTexture = this.activeCamera.outputRenderTarget.getInternalTexture();
+                    if (internalTexture) {
+                        engine.bindFramebuffer(internalTexture);
+                    } else {
+                        Tools.Error("Camera contains invalid customDefaultRenderTarget");
+                    }
+                } else {
+                    engine.restoreDefaultFramebuffer(); // Restore back buffer if needed
+                }
             }
 
             this.onAfterRenderTargetsRenderObservable.notifyObservers(this);

+ 1 - 1
src/babylon.sceneComponent.ts

@@ -138,7 +138,7 @@ module BABYLON {
     /**
      * Strong typing of a Active Mesh related stage step action
      */
-    export type ActiveMeshStageAction =  (sourceMesh: AbstractMesh, mesh: AbstractMesh) => void;
+    export type ActiveMeshStageAction = (sourceMesh: AbstractMesh, mesh: AbstractMesh) => void;
 
     /**
      * Strong typing of a Camera related stage step action

BIN
tests/validation/ReferenceImages/cameraRig.png


BIN
tests/validation/ReferenceImages/sps.png


+ 7 - 1
tests/validation/config.json

@@ -2,13 +2,19 @@
   "root": "https://rawgit.com/BabylonJS/Website/master",
   "tests": [
     {
+      "title": "Solid particle system",
+      "playgroundId": "#WCDZS#92",
+      "renderCount": 150,
+      "referenceImage": "sps.png"
+    },
+    {
       "title": "Simulate pointer",
       "playgroundId": "#8MGKWK#78",
       "referenceImage": "simulatePointer.png"
     },
     {
       "title": "Camera rig",
-      "playgroundId": "#ATL1CS#6",
+      "playgroundId": "#ATL1CS#9",
       "referenceImage": "cameraRig.png"
     },
     {

+ 3 - 3
tests/validation/validation.js

@@ -7,7 +7,7 @@ var config;
 var justOnce;
 
 var threshold = 25;
-var errorCountThreshold = 400;
+var errorRatio = 2.5;
 
 // Overload the random to make it deterministic
 var seed = 100000,
@@ -52,7 +52,7 @@ function compare(renderData, referenceCanvas) {
         console.log("Pixel difference: " + differencesCount + " pixels.");
     }
 
-    return differencesCount > errorCountThreshold;
+    return (differencesCount * 100) / (width * height) > errorRatio;
 }
 
 function getRenderData(canvas, engine) {
@@ -235,7 +235,7 @@ function runTest(index, done) {
         var snippetUrl = "//babylonjs-api2.azurewebsites.net/snippets";
         var pgRoot = "/Playground"
 
-        var retryTime = 30 * 1000;
+        var retryTime = 500;
         var maxRetry = 2;
         var retry = 0;