Procházet zdrojové kódy

Merge remote-tracking branch 'upstream/master'

sebastien před 7 roky
rodič
revize
dbf80505c6
79 změnil soubory, kde provedl 21048 přidání a 19307 odebrání
  1. 8743 8733
      Playground/babylon.d.txt
  2. 2 1
      Tools/Gulp/config.json
  3. binární
      Viewer/assets/babylon.woff
  4. 20 26
      Viewer/assets/templates/default/navbar.html
  5. 3 1
      Viewer/src/configuration/configuration.ts
  6. 2 1
      Viewer/src/configuration/interfaces/index.ts
  7. 10 0
      Viewer/src/configuration/interfaces/vrConfiguration.ts
  8. 4 2
      Viewer/src/configuration/types/default.ts
  9. 86 5
      Viewer/src/managers/sceneManager.ts
  10. 45 19
      Viewer/src/viewer/defaultViewer.ts
  11. 82 1
      Viewer/src/viewer/viewer.ts
  12. 0 25
      assets/particles/systems/fire.json
  13. 0 25
      assets/particles/systems/smoke.json
  14. 339 0
      assets/particles/systems/sun.json
  15. binární
      assets/particles/textures/flare.png
  16. binární
      assets/particles/textures/sun/T_Star.png
  17. binární
      assets/particles/textures/sun/T_SunFlare.png
  18. binární
      assets/particles/textures/sun/T_SunSurface.png
  19. 8302 8294
      dist/preview release/babylon.d.ts
  20. 40 40
      dist/preview release/babylon.js
  21. 556 328
      dist/preview release/babylon.max.js
  22. 556 328
      dist/preview release/babylon.no-module.max.js
  23. 40 40
      dist/preview release/babylon.worker.js
  24. 558 330
      dist/preview release/es6.js
  25. 1 1
      dist/preview release/glTF2Interface/package.json
  26. 1 1
      dist/preview release/gui/package.json
  27. 1 1
      dist/preview release/inspector/package.json
  28. 2 2
      dist/preview release/loaders/package.json
  29. 1 1
      dist/preview release/materialsLibrary/package.json
  30. 1 1
      dist/preview release/postProcessesLibrary/package.json
  31. 1 1
      dist/preview release/proceduralTexturesLibrary/package.json
  32. 1 1
      dist/preview release/serializers/babylon.glTF2Serializer.js
  33. 1 1
      dist/preview release/serializers/babylon.glTF2Serializer.min.js
  34. 1 1
      dist/preview release/serializers/babylonjs.serializers.js
  35. 1 1
      dist/preview release/serializers/babylonjs.serializers.min.js
  36. 2 2
      dist/preview release/serializers/package.json
  37. 30 0
      dist/preview release/viewer/babylon.viewer.d.ts
  38. 69 68
      dist/preview release/viewer/babylon.viewer.js
  39. 754 356
      dist/preview release/viewer/babylon.viewer.max.js
  40. 33 3
      dist/preview release/viewer/babylon.viewer.module.d.ts
  41. 15 10
      dist/preview release/what's new.md
  42. 1 1
      package.json
  43. 17 17
      sandbox/index.html
  44. 1 1
      serializers/src/glTF/2.0/babylon.glTFMaterialExporter.ts
  45. 1 1
      src/Engine/babylon.engine.ts
  46. 4 0
      src/Engine/babylon.nullEngine.ts
  47. 10 2
      src/Gamepad/Controllers/babylon.poseEnabledController.ts
  48. 1 1
      src/Gizmos/babylon.axisDragGizmo.ts
  49. 1 1
      src/Gizmos/babylon.axisScaleGizmo.ts
  50. 55 4
      src/Gizmos/babylon.boundingBoxGizmo.ts
  51. 1 1
      src/Gizmos/babylon.gizmo.ts
  52. 1 1
      src/Gizmos/babylon.planeRotationGizmo.ts
  53. 4 4
      src/Gizmos/babylon.positionGizmo.ts
  54. 4 4
      src/Gizmos/babylon.rotationGizmo.ts
  55. 4 4
      src/Gizmos/babylon.scaleGizmo.ts
  56. 0 349
      src/Helpers/babylon.particleHelper.ts
  57. 2 2
      src/Particles/EmitterTypes/babylon.boxParticleEmitter.ts
  58. 2 2
      src/Particles/EmitterTypes/babylon.coneParticleEmitter.ts
  59. 2 0
      src/Particles/EmitterTypes/babylon.sphereParticleEmitter.ts
  60. 29 3
      src/Particles/babylon.IParticleSystem.ts
  61. 88 31
      src/Particles/babylon.gpuParticleSystem.ts
  62. 23 43
      src/Particles/babylon.particle.ts
  63. 55 0
      src/Particles/babylon.particleHelper.ts
  64. 166 67
      src/Particles/babylon.particleSystem.ts
  65. 137 0
      src/Particles/babylon.particleSystemSet.ts
  66. 6 6
      src/PostProcess/RenderPipeline/Pipelines/babylon.defaultRenderingPipeline.ts
  67. 13 2
      src/Rendering/babylon.utilityLayerRenderer.ts
  68. 18 1
      src/Shaders/gpuRenderParticles.vertex.fx
  69. 26 11
      src/Shaders/gpuUpdateParticles.vertex.fx
  70. 1 1
      src/Tools/babylon.environmentTextureTools.ts
  71. 58 23
      src/Tools/babylon.tools.ts
  72. 2 2
      src/babylon.scene.ts
  73. 5 0
      tests/nullEngine/app.js
  74. 0 70
      tests/unit/babylon/src/Helpers/babylon.particleHelper.tests.ts
  75. 0 1
      tests/unit/karma.conf.js
  76. binární
      tests/validation/ReferenceImages/GUI.png
  77. binární
      tests/validation/ReferenceImages/particle_helper.png
  78. binární
      tests/validation/ReferenceImages/yeti.png
  79. 7 1
      tests/validation/config.json

Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 8743 - 8733
Playground/babylon.d.txt


+ 2 - 1
Tools/Gulp/config.json

@@ -1241,7 +1241,8 @@
         },
         "particleHelper": {
             "files": [
-                "../../src/Helpers/babylon.particleHelper.js"
+                "../../src/Particles/babylon.particleSystemSet.js",
+                "../../src/Particles/babylon.particleHelper.js"
             ],
             "dependUpon": [
                 "particles"

binární
Viewer/assets/babylon.woff


+ 20 - 26
Viewer/assets/templates/default/navbar.html

@@ -143,19 +143,7 @@
     A few browsers dont support combining the next rule to one. Redudant.
     */
 
-    viewer:fullscreen .fullscreen-icon:after {
-        content: "\E73F";
-    }
-
-    viewer:-webkit-full-screen .fullscreen-icon:after {
-        content: "\E73F";
-    }
-
-    viewer:-moz-full-screen .fullscreen-icon:after {
-        content: "\E73F";
-    }
-
-    viewer:-ms-full-screen .fullscreen-icon:after {
+    viewer.in-fullscreen .fullscreen-icon:after {
         content: "\E73F";
     }
 
@@ -174,6 +162,16 @@
         content: "\F766";
     }
 
+    viewer.in-vr .vr-icon:after {
+        font-size: 16px;
+        content: "\E7F4";
+    }
+
+    .vr-icon:after {
+        font-size: 16px;
+        content: "\F119";
+    }
+
     .progress-control {
         display: flex;
         flex: 1;
@@ -438,15 +436,7 @@
 </style>
 {{/if}} {{/if}} {{#if disableOnFullscreen}}
 <style>
-    viewer:fullscreen nav-bar {
-        display: none;
-    }
-
-    viewer:-moz-full-screen nav-bar {
-        display: none;
-    }
-
-    viewer:-webkit-full-screen nav-bar {
+    viewer.in-fullscreen nav-bar {
         display: none;
     }
 </style>
@@ -477,10 +467,10 @@
             </button>
             <div class="menu-options">
                 {{#each animations}} {{#unless (eq ../selectedAnimation (add @index 1))}}
-                <button class="flex-container label-option-button animation-buttons" data-value="{{this}}">
+                <button class="flex-container label-option-button animation-buttons" data-value="{{this.value}}">
                     <!-- <div> -->
                     <span class="icon types-icon"></span>
-                    <span class="control-text animation-label">{{this}}</span>
+                    <span class="control-text animation-label">{{this.label}}</span>
                     <span class="control-text animation-number">{{add @index 1}}</span>
                     <!-- </div> -->
                 </button>
@@ -513,7 +503,7 @@
     </div>
     {{/unless}}
     <div class="default-control">
-        {{#unless hideHdButton}}
+        {{#unless hideHd}}
         <button class="hd-button hd-button" title="{{text.hdButton}}">
             {{#if hdEnabled}}
             <span class="icon sd-icon"></span>
@@ -521,7 +511,11 @@
             <span class="icon hd-icon"></span>
             {{/if}}
         </button>
-        {{/unless}} {{#unless hideHelp}}
+        {{/unless}} {{#unless hideVr}}
+        <button class="vr vr-button" title="{{text.vrButton}}">
+            <span class="icon vr-icon"></span>
+        </button>
+        {{/unless}}{{#unless hideHelp}}
         <button class="help help-button" title="{{text.helpButton}}">
             <span class="icon help-icon"></span>
         </button>

+ 3 - 1
Viewer/src/configuration/configuration.ts

@@ -1,5 +1,5 @@
 import { EngineOptions, IGlowLayerOptions, DepthOfFieldEffectBlurLevel } from 'babylonjs';
-import { IObserversConfiguration, IModelConfiguration, ISceneConfiguration, ISceneOptimizerConfiguration, ICameraConfiguration, ISkyboxConfiguration, IGroundConfiguration, ILightConfiguration, IDefaultRenderingPipelineConfiguration, ITemplateConfiguration } from './interfaces';
+import { IVRConfiguration, IObserversConfiguration, IModelConfiguration, ISceneConfiguration, ISceneOptimizerConfiguration, ICameraConfiguration, ISkyboxConfiguration, IGroundConfiguration, ILightConfiguration, IDefaultRenderingPipelineConfiguration, ITemplateConfiguration } from './interfaces';
 
 export function getConfigurationKey(key: string, configObject: any) {
     let splits = key.split('.');
@@ -80,6 +80,8 @@ export interface ViewerConfiguration {
         [propName: string]: boolean | undefined;
     };
 
+    vr?: IVRConfiguration;
+
     // features that are being tested.
     // those features' syntax will change and move out! 
     // Don't use in production (or be ready to make the changes :) )

+ 2 - 1
Viewer/src/configuration/interfaces/index.ts

@@ -10,4 +10,5 @@ export * from './observersConfiguration';
 export * from './sceneConfiguration';
 export * from './sceneOptimizerConfiguration';
 export * from './skyboxConfiguration';
-export * from './templateConfiguration';
+export * from './templateConfiguration';
+export * from './vrConfiguration';

+ 10 - 0
Viewer/src/configuration/interfaces/vrConfiguration.ts

@@ -0,0 +1,10 @@
+import { VRExperienceHelperOptions } from "babylonjs";
+
+export interface IVRConfiguration {
+    disabled?: boolean;
+    objectScaleFactor?: number;
+    disableInteractions?: boolean;
+    disableTeleportation?: boolean;
+    overrideFloorMeshName?: string;
+    vrOptions?: VRExperienceHelperOptions;
+}

+ 4 - 2
Viewer/src/configuration/types/default.ts

@@ -49,12 +49,14 @@ export let defaultConfiguration: ViewerConfiguration = {
                 logoText: 'BabylonJS',
                 logoLink: 'https://babylonjs.com',
                 hideHelp: true,
-                hideHdButton: true,
+                hideHd: true,
+                hideVr: true,
                 disableOnFullscreen: false,
                 text: {
                     hdButton: "Toggle HD",
                     fullscreenButton: "Fullscreen",
-                    helpButton: "Help"
+                    helpButton: "Help",
+                    vrButton: "Toggle VR"
                 }
             },
             events: {

Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 86 - 5
Viewer/src/managers/sceneManager.ts


+ 45 - 19
Viewer/src/viewer/defaultViewer.ts

@@ -15,7 +15,7 @@ import { IModelAnimation, AnimationState } from '../model/modelAnimation';
  */
 export class DefaultViewer extends AbstractViewer {
 
-
+    public fullscreenElement?: HTMLElement;
 
     /**
      * Create a new default viewer
@@ -157,9 +157,10 @@ export class DefaultViewer extends AbstractViewer {
                 this._togglePlayPause();
                 break;
             case "label-option-button":
-                var label = element.dataset["value"];
-                if (label) {
-                    this._updateAnimationType(label);
+                var value = element.dataset["value"];
+                var label = element.querySelector("span.animation-label");
+                if (label && value) {
+                    this._updateAnimationType({ value, label: label.innerHTML });
                 }
                 break;
             case "speed-option-button":
@@ -182,6 +183,9 @@ export class DefaultViewer extends AbstractViewer {
             case "hd-button":
                 this.toggleHD();
                 break;
+            case "vr-button":
+                this.toggleVR();
+                break;
             default:
                 return;
         }
@@ -268,21 +272,21 @@ export class DefaultViewer extends AbstractViewer {
     /** 
      * Update Current Animation Type
      */
-    private _updateAnimationType = (label: string, paramsObject?: any) => {
+    private _updateAnimationType = (data: { label: string, value: string }, paramsObject?: any) => {
         let navbar = this.templateManager.getTemplate('navBar');
         if (!navbar) return;
 
-        if (label) {
-            this._currentAnimation = this.sceneManager.models[0].setCurrentAnimationByName(label);
+        if (data) {
+            this._currentAnimation = this.sceneManager.models[0].setCurrentAnimationByName(data.value);
         }
 
         if (paramsObject) {
-            paramsObject.selectedAnimation = (this._animationList.indexOf(label) + 1);
-            paramsObject.selectedAnimationName = label;
+            paramsObject.selectedAnimation = (this._animationList.indexOf(data.value) + 1);
+            paramsObject.selectedAnimationName = data.label;
         } else {
             navbar.updateParams({
-                selectedAnimation: (this._animationList.indexOf(label) + 1),
-                selectedAnimationName: label
+                selectedAnimation: (this._animationList.indexOf(data.value) + 1),
+                selectedAnimationName: data.label
             });
         }
 
@@ -307,21 +311,43 @@ export class DefaultViewer extends AbstractViewer {
         }
     }
 
+    public toggleVR() {
+        super.toggleVR();
+
+        let viewerTemplate = this.templateManager.getTemplate('viewer');
+        let viewerElement = viewerTemplate && viewerTemplate.parent;
+
+        if (viewerElement) {
+            if (this._vrToggled) {
+                viewerElement.classList.add("in-vr");
+            } else {
+                viewerElement.classList.remove("in-vr");
+            }
+        }
+    }
+
     /**
      * Toggle fullscreen of the entire viewer
      */
     public toggleFullscreen = () => {
         let viewerTemplate = this.templateManager.getTemplate('viewer');
         let viewerElement = viewerTemplate && viewerTemplate.parent;
-
-        if (viewerElement) {
-            let fullscreenElement = document.fullscreenElement || document.webkitFullscreenElement || (<any>document).mozFullScreenElement || (<any>document).msFullscreenElement;
-            if (!fullscreenElement) {
-                let requestFullScreen = viewerElement.requestFullscreen || viewerElement.webkitRequestFullscreen || (<any>viewerElement).msRequestFullscreen || (<any>viewerElement).mozRequestFullScreen;
-                requestFullScreen.call(viewerElement);
+        let fullscreenElement = this.fullscreenElement || viewerElement;
+
+        if (fullscreenElement) {
+            let currentElement = document.fullscreenElement || document.webkitFullscreenElement || (<any>document).mozFullScreenElement || (<any>document).msFullscreenElement;
+            if (!currentElement) {
+                let requestFullScreen = fullscreenElement.requestFullscreen || fullscreenElement.webkitRequestFullscreen || (<any>fullscreenElement).msRequestFullscreen || (<any>fullscreenElement).mozRequestFullScreen;
+                requestFullScreen.call(fullscreenElement);
+                if (viewerElement) {
+                    viewerElement.classList.add("in-fullscreen");
+                }
             } else {
                 let exitFullscreen = document.exitFullscreen || document.webkitExitFullscreen || (<any>document).msExitFullscreen || (<any>document).mozCancelFullScreen
                 exitFullscreen.call(document);
+                if (viewerElement) {
+                    viewerElement.classList.remove("in-fullscreen");
+                }
             }
         }
     }
@@ -351,7 +377,7 @@ export class DefaultViewer extends AbstractViewer {
         } else {
 
             let animationNames = model.getAnimationNames();
-            newParams.animations = animationNames;
+            newParams.animations = animationNames.map(a => { return { label: a, value: a } });
             if (animationNames.length) {
                 this._isAnimationPaused = (model.configuration.animation && !model.configuration.animation.autoStart) || !model.configuration.animation;
                 this._animationList = animationNames;
@@ -363,7 +389,7 @@ export class DefaultViewer extends AbstractViewer {
                         animationIndex = 0;
                     }
                 }
-                this._updateAnimationType(animationNames[animationIndex], newParams);
+                this._updateAnimationType(newParams.animations[animationIndex], newParams);
             } else {
                 newParams.animations = null;
             }

Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 82 - 1
Viewer/src/viewer/viewer.ts


+ 0 - 25
assets/particles/systems/fire.json

@@ -1,25 +0,0 @@
-{
-  "type": "fire",
-  "emitterType": "box",
-  "capacity": 2000,
-  "textureFile": "flare.png",
-  "minEmitBox": { "x": -0.5, "y": 1, "z": -0.5 },
-  "maxEmitBox": { "x": 0.5, "y": 1, "z": 0.5 },
-  "color1": { "r": 1, "g": 0.5, "b": 0, "a": 1.0 },
-  "color2": { "r": 1, "g": 0.5, "b": 0, "a": 1.0 },
-  "colorDead": { "r": 0, "g": 0, "b": 0, "a": 0.0 },
-  "minSize": 0.3,
-  "maxSize": 1,
-  "minLifeTime": 0.2,
-  "maxLifeTime": 0.4,
-  "emitRate": 600,
-  "blendMode": 0,
-  "gravity": { "x": 0, "y": 0, "z": 0 },
-  "direction1": { "x": 0, "y": 4, "z": 0 },
-  "direction2": { "x": 0, "y": 4, "z": 0 },
-  "minAngularSpeed": 0,
-  "maxAngularSpeed": 3.141592653589793,
-  "minEmitPower": 1,
-  "maxEmitPower": 3,
-  "updateSpeed": 0.007
-}

+ 0 - 25
assets/particles/systems/smoke.json

@@ -1,25 +0,0 @@
-{
-  "type": "smoke",
-  "emitterType": "box",
-  "capacity": 1000,
-  "textureFile": "flare.png",
-  "minEmitBox": { "x": -0.5, "y": 1, "z": -0.5 },
-  "maxEmitBox": { "x": 0.5, "y": 1, "z": 0.5 },
-  "color1": { "r": 0.1, "g": 0.1, "b": 0.1, "a": 1.0 },
-  "color2": { "r": 0.1, "g": 0.1, "b": 0.1, "a": 1.0 },
-  "colorDead": { "r": 0, "g": 0, "b": 0, "a": 0.0 },
-  "minSize": 0.3,
-  "maxSize": 1,
-  "minLifeTime": 0.3,
-  "maxLifeTime": 1.5,
-  "emitRate": 350,
-  "blendMode": 0,
-  "gravity": { "x": 0, "y": 0, "z": 0 },
-  "direction1": { "x": -1.5, "y": 8, "z": -1.5 },
-  "direction2": { "x": 1.5, "y": 8, "z": 1.5 },
-  "minAngularSpeed": 0,
-  "maxAngularSpeed": 3.141592653589793,
-  "minEmitPower": 0.5,
-  "maxEmitPower": 1.5,
-  "updateSpeed": 0.005
-}

+ 339 - 0
assets/particles/systems/sun.json

@@ -0,0 +1,339 @@
+{
+  "emitter" : {
+    "kind": "Sphere",
+    "options": {
+      "diameter": 2.01,
+      "segments": 32,
+      "color": [
+      0.3773, 
+      0.0930, 
+      0.0266]
+    },
+    "renderingGroupId": 3
+  },
+  "systems": [
+    {
+      "name": "surfaceParticles",
+      "id": "surfaceParticles",
+      "capacity": 1600,
+      "renderingGroupId": 3,
+      "isBillboardBased": false,
+      "particleEmitterType": {
+        "type": "SphereParticleEmitter",
+        "radius": 1,
+        "radiusRange": 0,
+        "directionRandomizer": 0
+      },
+      "textureName": "sun/T_SunSurface.png",
+      "animations": [],
+      "minAngularSpeed": -0.4,
+      "maxAngularSpeed": 0.4,
+      "minSize": 0.4,
+      "maxSize": 0.7,
+      "minScaleX": 1,
+      "maxScaleX": 1,
+      "minScaleY": 1,
+      "maxScaleY": 1,
+      "minEmitPower": 0,
+      "maxEmitPower": 0,
+      "minLifeTime": 8,
+      "maxLifeTime": 8,
+      "emitRate": 200,
+      "gravity": [
+        0,
+        0,
+        0
+      ],
+      "color1": [
+        1,
+        1,
+        1,
+        1
+      ],
+      "color2": [
+        1,
+        1,
+        1,
+        1
+      ],
+      "colorDead": [
+        0,
+        0,
+        0,
+        1
+      ],
+      "updateSpeed": 0.005,
+      "targetStopDuration": 0,
+      "blendMode": 2,
+      "preWarmCycles": 100,
+      "preWarmStepOffset": 10,
+      "minInitialRotation": -6.283185307179586,
+      "maxInitialRotation": 6.283185307179586,
+      "colorGradients": [
+        {
+          "gradient": 0,
+          "color1": [
+            0.8509,
+            0.4784,
+            0.1019,
+            0
+          ]
+        },
+        {
+          "gradient": 0.4,
+          "color1": [
+            0.6259,
+            0.3056,
+            0.0619,
+            0.5
+          ]
+        },
+        {
+          "gradient": 0.5,
+          "color1": [
+            0.6039,
+            0.2887,
+            0.0579,
+            0.5
+          ]
+        },
+        {
+          "gradient": 1,
+          "color1": [
+            0.3207,
+            0.0713,
+            0.0075,
+            0
+          ]
+        }
+      ],
+      "textureMask": [
+        1,
+        1,
+        1,
+        1
+      ],
+      "customShader": null,
+      "preventAutoStart": true,
+      "startSpriteCellID": 0,
+      "endSpriteCellID": 0,
+      "spriteCellLoop": true,
+      "spriteCellChangeSpeed": 0,
+      "spriteCellWidth": 0,
+      "spriteCellHeight": 0,
+      "isAnimationSheetEnabled": false
+    },
+    {
+      "name": "flareParticles",
+      "id": "flareParticles",
+      "capacity": 20,
+      "renderingGroupId": 2,
+      "particleEmitterType": {
+        "type": "SphereParticleEmitter",
+        "radius": 1,
+        "radiusRange": 0,
+        "directionRandomizer": 0
+      },
+      "textureName": "sun/T_SunFlare.png",
+      "animations": [],
+      "minAngularSpeed": 0,
+      "maxAngularSpeed": 0,
+      "minSize": 1,
+      "maxSize": 1,
+      "minScaleX": 0.5,
+      "maxScaleX": 1,
+      "minScaleY": 0.5,
+      "maxScaleY": 1,
+      "minEmitPower": 0.001,
+      "maxEmitPower": 0.01,
+      "minLifeTime": 10,
+      "maxLifeTime": 10,
+      "emitRate": 1,
+      "gravity": [
+        0,
+        0,
+        0
+      ],
+      "color1": [
+        1,
+        1,
+        1,
+        1
+      ],
+      "color2": [
+        1,
+        1,
+        1,
+        1
+      ],
+      "colorDead": [
+        0,
+        0,
+        0,
+        1
+      ],
+      "updateSpeed": 0.01,
+      "targetStopDuration": 0,
+      "blendMode": 2,
+      "preWarmCycles": 100,
+      "preWarmStepOffset": 10,
+      "minInitialRotation": -6.283185307179586,
+      "maxInitialRotation": 6.283185307179586,
+      "colorGradients": [
+        {
+          "gradient": 0,
+          "color1": [
+            1,
+            0.9612,
+            0.5141,
+            0
+          ]
+        },
+        {
+          "gradient": 0.25,
+          "color1": [
+            0.9058,
+            0.7152,
+            0.3825,
+            1
+          ]
+        },
+        {
+          "gradient": 1,
+          "color1": [
+            0.632,
+            0,
+            0,
+            0
+          ]
+        }
+      ],
+      "sizeGradients": [
+        {
+          "gradient": 0,
+          "factor1": 0
+        },
+        {
+          "gradient": 1,
+          "factor1": 1
+        }
+      ],
+      "textureMask": [
+        1,
+        1,
+        1,
+        1
+      ],
+      "customShader": null,
+      "preventAutoStart": true,
+      "startSpriteCellID": 0,
+      "endSpriteCellID": 0,
+      "spriteCellLoop": true,
+      "spriteCellChangeSpeed": 0,
+      "spriteCellWidth": 0,
+      "spriteCellHeight": 0,
+      "isAnimationSheetEnabled": false
+    },
+    {
+      "name": "glareParticles",
+      "id": "glareParticles",
+      "renderingGroupId": 1,
+      "capacity": 600,
+      "particleEmitterType": {
+        "type": "SphereParticleEmitter",
+        "radius": 1,
+        "radiusRange": 0,
+        "directionRandomizer": 0
+      },
+      "textureName": "sun/T_Star.png",
+      "animations": [],
+      "minAngularSpeed": 0,
+      "maxAngularSpeed": 0,
+      "minSize": 1,
+      "maxSize": 1,
+      "minScaleX": 0.5,
+      "maxScaleX": 1.2,
+      "minScaleY": 0.75,
+      "maxScaleY": 3,
+      "minEmitPower": 0,
+      "maxEmitPower": 0,
+      "minLifeTime": 2,
+      "maxLifeTime": 2,
+      "emitRate": 300,
+      "gravity": [
+        0,
+        0,
+        0
+      ],
+      "color1": [
+        1,
+        1,
+        1,
+        1
+      ],
+      "color2": [
+        1,
+        1,
+        1,
+        1
+      ],
+      "colorDead": [
+        0,
+        0,
+        0,
+        1
+      ],
+      "updateSpeed": 0.01,
+      "targetStopDuration": 0,
+      "blendMode": 2,
+      "preWarmCycles": 100,
+      "preWarmStepOffset": 10,
+      "minInitialRotation": -6.283185307179586,
+      "maxInitialRotation": 6.283185307179586,
+      "colorGradients": [
+        {
+          "gradient": 0,
+          "color1": [
+            0.8509,
+            0.4784,
+            0.1019,
+            0
+          ]
+        },
+        {
+          "gradient": 0.5,
+          "color1": [
+            0.6039,
+            0.2887,
+            0.0579,
+            0.12
+          ]
+        },
+        {
+          "gradient": 1,
+          "color1": [
+            0.3207,
+            0.0713,
+            0.0075,
+            0
+          ]
+        }
+      ],
+      "textureMask": [
+        1,
+        1,
+        1,
+        1
+      ],
+      "customShader": null,
+      "preventAutoStart": true,
+      "startSpriteCellID": 0,
+      "endSpriteCellID": 0,
+      "spriteCellLoop": true,
+      "spriteCellChangeSpeed": 0,
+      "spriteCellWidth": 0,
+      "spriteCellHeight": 0,
+      "isAnimationSheetEnabled": false
+    }
+  ]
+}

binární
assets/particles/textures/flare.png


binární
assets/particles/textures/sun/T_Star.png


binární
assets/particles/textures/sun/T_SunFlare.png


binární
assets/particles/textures/sun/T_SunSurface.png


Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 8302 - 8294
dist/preview release/babylon.d.ts


Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 40 - 40
dist/preview release/babylon.js


Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 556 - 328
dist/preview release/babylon.max.js


Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 556 - 328
dist/preview release/babylon.no-module.max.js


Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 40 - 40
dist/preview release/babylon.worker.js


Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 558 - 330
dist/preview release/es6.js


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

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

+ 1 - 1
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-alpha.8",
+    "version": "3.3.0-alpha.9",
     "repository": {
         "type": "git",
         "url": "https://github.com/BabylonJS/Babylon.js.git"

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

@@ -4,7 +4,7 @@
     },
     "name": "babylonjs-inspector",
     "description": "The Babylon.js inspector.",
-    "version": "3.3.0-alpha.8",
+    "version": "3.3.0-alpha.9",
     "repository": {
         "type": "git",
         "url": "https://github.com/BabylonJS/Babylon.js.git"

+ 2 - 2
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-alpha.8",
+    "version": "3.3.0-alpha.9",
     "repository": {
         "type": "git",
         "url": "https://github.com/BabylonJS/Babylon.js.git"
@@ -27,7 +27,7 @@
     ],
     "license": "Apache-2.0",
     "dependencies": {
-        "babylonjs-gltf2interface": "3.3.0-alpha.8"
+        "babylonjs-gltf2interface": "3.3.0-alpha.9"
     },
     "peerDependencies": {
         "babylonjs": ">=3.2.0-alpha"

+ 1 - 1
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-alpha.8",
+    "version": "3.3.0-alpha.9",
     "repository": {
         "type": "git",
         "url": "https://github.com/BabylonJS/Babylon.js.git"

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

@@ -4,7 +4,7 @@
     },
     "name": "babylonjs-post-process",
     "description": "The Babylon.js materials library is a collection of advanced materials to be used in a Babylon.js scene.",
-    "version": "3.3.0-alpha.8",
+    "version": "3.3.0-alpha.9",
     "repository": {
         "type": "git",
         "url": "https://github.com/BabylonJS/Babylon.js.git"

+ 1 - 1
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-alpha.8",
+    "version": "3.3.0-alpha.9",
     "repository": {
         "type": "git",
         "url": "https://github.com/BabylonJS/Babylon.js.git"

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

@@ -1720,7 +1720,7 @@ var BABYLON;
                         // Read data from WebGL
                         var canvas = engine.getRenderingCanvas();
                         if (canvas) {
-                            canvas.toBlob(function (blob) {
+                            BABYLON.Tools.ToBlob(canvas, function (blob) {
                                 if (blob) {
                                     var fileReader = new FileReader();
                                     fileReader.onload = function (event) {

Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 1 - 1
dist/preview release/serializers/babylon.glTF2Serializer.min.js


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

@@ -1870,7 +1870,7 @@ var BABYLON;
                         // Read data from WebGL
                         var canvas = engine.getRenderingCanvas();
                         if (canvas) {
-                            canvas.toBlob(function (blob) {
+                            BABYLON.Tools.ToBlob(canvas, function (blob) {
                                 if (blob) {
                                     var fileReader = new FileReader();
                                     fileReader.onload = function (event) {

Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 1 - 1
dist/preview release/serializers/babylonjs.serializers.min.js


+ 2 - 2
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-alpha.8",
+    "version": "3.3.0-alpha.9",
     "repository": {
         "type": "git",
         "url": "https://github.com/BabylonJS/Babylon.js.git"
@@ -27,7 +27,7 @@
     ],
     "license": "Apache-2.0",
     "dependencies": {
-        "babylonjs-gltf2interface": "3.3.0-alpha.8"
+        "babylonjs-gltf2interface": "3.3.0-alpha.9"
     },
     "peerDependencies": {
         "babylonjs": ">=3.2.0-alpha"

+ 30 - 0
dist/preview release/viewer/babylon.viewer.d.ts

@@ -158,6 +158,7 @@ declare module BabylonViewer {
         */
     export class DefaultViewer extends AbstractViewer {
             containerElement: HTMLElement;
+            fullscreenElement?: HTMLElement;
             /**
                 * Create a new default viewer
                 * @param containerElement the element in which the templates will be rendered
@@ -169,6 +170,7 @@ declare module BabylonViewer {
                 */
             protected _onTemplatesLoaded(): Promise<AbstractViewer>;
             toggleHD(): void;
+            toggleVR(): void;
             /**
                 * Toggle fullscreen of the entire viewer
                 */
@@ -369,6 +371,9 @@ declare module BabylonViewer {
             forceResize(): void;
             protected _hdToggled: boolean;
             toggleHD(): void;
+            protected _vrToggled: boolean;
+            protected _vrScale: number;
+            toggleVR(): void;
             /**
                 * The resize function that will be registered with the window object
                 */
@@ -1010,6 +1015,7 @@ declare module BabylonViewer {
                     minecraft?: boolean;
                     [propName: string]: boolean | undefined;
             };
+            vr?: IVRConfiguration;
             lab?: {
                     flashlight?: boolean | {
                             exponent?: number;
@@ -1272,6 +1278,10 @@ declare module BabylonViewer {
                     ground?: IGroundConfiguration | boolean;
             }>>;
             /**
+                * Will notify after the model(s) were configured. Can be used to further configure models
+                */
+            onVRConfiguredObservable: BABYLON.Observable<IPostConfigurationCallback<BABYLON.VRExperienceHelper, IVRConfiguration>>;
+            /**
                 * The Babylon BABYLON.Scene of this viewer
                 */
             scene: BABYLON.Scene;
@@ -1304,6 +1314,8 @@ declare module BabylonViewer {
                 */
             labs: ViewerLabs;
             readonly defaultRenderingPipeline: BABYLON.Nullable<BABYLON.DefaultRenderingPipeline>;
+            protected _vrHelper?: BABYLON.VRExperienceHelper;
+            readonly vrHelper: BABYLON.VRExperienceHelper | undefined;
             constructor(_engine: BABYLON.Engine, _configurationContainer: ConfigurationContainer, _observablesManager?: ObservablesManager | undefined);
             /**
                 * Returns a boolean representing HDR support
@@ -1361,6 +1373,11 @@ declare module BabylonViewer {
                 */
             protected _configureOptimizer(optimizerConfig: ISceneOptimizerConfiguration | boolean): void;
             /**
+                * configure all models using the configuration.
+                * @param modelConfiguration the configuration to use to reconfigure the models
+                */
+            protected _configureVR(vrConfig: IVRConfiguration): void;
+            /**
                 * (Re) configure the camera. The camera will only be created once and from this point will only be reconfigured.
                 * @param cameraConfig the new camera configuration
                 * @param model optionally use the model to configure the camera.
@@ -1588,6 +1605,7 @@ declare module BabylonViewer {
     export * from 'babylonjs-viewer/configuration/interfaces/sceneOptimizerConfiguration';
     export * from 'babylonjs-viewer/configuration/interfaces/skyboxConfiguration';
     export * from 'babylonjs-viewer/configuration/interfaces/templateConfiguration';
+    export * from 'babylonjs-viewer/configuration/interfaces/vrConfiguration';
 }
 
 declare module BabylonViewer {
@@ -2214,6 +2232,18 @@ declare module BabylonViewer {
 
 declare module BabylonViewer {
     
+    export interface IVRConfiguration {
+        disabled?: boolean;
+        objectScaleFactor?: number;
+        disableInteractions?: boolean;
+        disableTeleportation?: boolean;
+        overrideFloorMeshName?: string;
+        vrOptions?: BABYLON.VRExperienceHelperOptions;
+    }
+}
+
+declare module BabylonViewer {
+    
     
     /**
         * Spherical polynomial coefficients (counter part to spherical harmonic coefficients used in shader irradiance calculation)

Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 69 - 68
dist/preview release/viewer/babylon.viewer.js


Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 754 - 356
dist/preview release/viewer/babylon.viewer.max.js


+ 33 - 3
dist/preview release/viewer/babylon.viewer.module.d.ts

@@ -158,6 +158,7 @@ declare module 'babylonjs-viewer/viewer/defaultViewer' {
         */
     export class DefaultViewer extends AbstractViewer {
             containerElement: HTMLElement;
+            fullscreenElement?: HTMLElement;
             /**
                 * Create a new default viewer
                 * @param containerElement the element in which the templates will be rendered
@@ -169,6 +170,7 @@ declare module 'babylonjs-viewer/viewer/defaultViewer' {
                 */
             protected _onTemplatesLoaded(): Promise<AbstractViewer>;
             toggleHD(): void;
+            toggleVR(): void;
             /**
                 * Toggle fullscreen of the entire viewer
                 */
@@ -369,6 +371,9 @@ declare module 'babylonjs-viewer/viewer/viewer' {
             forceResize(): void;
             protected _hdToggled: boolean;
             toggleHD(): void;
+            protected _vrToggled: boolean;
+            protected _vrScale: number;
+            toggleVR(): void;
             /**
                 * The resize function that will be registered with the window object
                 */
@@ -961,7 +966,7 @@ declare module 'babylonjs-viewer/configuration' {
 
 declare module 'babylonjs-viewer/configuration/configuration' {
     import { EngineOptions } from 'babylonjs';
-    import { IObserversConfiguration, IModelConfiguration, ISceneConfiguration, ISceneOptimizerConfiguration, ICameraConfiguration, ISkyboxConfiguration, IGroundConfiguration, ILightConfiguration, IDefaultRenderingPipelineConfiguration, ITemplateConfiguration } from 'babylonjs-viewer/configuration/interfaces';
+    import { IVRConfiguration, IObserversConfiguration, IModelConfiguration, ISceneConfiguration, ISceneOptimizerConfiguration, ICameraConfiguration, ISkyboxConfiguration, IGroundConfiguration, ILightConfiguration, IDefaultRenderingPipelineConfiguration, ITemplateConfiguration } from 'babylonjs-viewer/configuration/interfaces';
     export function getConfigurationKey(key: string, configObject: any): any;
     export interface ViewerConfiguration {
             version?: string;
@@ -1010,6 +1015,7 @@ declare module 'babylonjs-viewer/configuration/configuration' {
                     minecraft?: boolean;
                     [propName: string]: boolean | undefined;
             };
+            vr?: IVRConfiguration;
             lab?: {
                     flashlight?: boolean | {
                             exponent?: number;
@@ -1220,8 +1226,8 @@ declare module 'babylonjs-viewer/templating/templateManager' {
 }
 
 declare module 'babylonjs-viewer/managers/sceneManager' {
-    import { Scene, ArcRotateCamera, Engine, Light, SceneOptimizer, EnvironmentHelper, Color3, Observable, DefaultRenderingPipeline, Nullable } from 'babylonjs';
-    import { ILightConfiguration, ISceneConfiguration, ISceneOptimizerConfiguration, ICameraConfiguration, ISkyboxConfiguration, ViewerConfiguration, IGroundConfiguration, IModelConfiguration } from 'babylonjs-viewer/configuration';
+    import { Scene, ArcRotateCamera, Engine, Light, SceneOptimizer, EnvironmentHelper, Color3, Observable, DefaultRenderingPipeline, Nullable, VRExperienceHelper } from 'babylonjs';
+    import { ILightConfiguration, ISceneConfiguration, ISceneOptimizerConfiguration, ICameraConfiguration, ISkyboxConfiguration, ViewerConfiguration, IGroundConfiguration, IModelConfiguration, IVRConfiguration } from 'babylonjs-viewer/configuration';
     import { ViewerModel } from 'babylonjs-viewer/model/viewerModel';
     import { ViewerLabs } from 'babylonjs-viewer/labs/viewerLabs';
     import { ObservablesManager } from 'babylonjs-viewer/managers/observablesManager';
@@ -1272,6 +1278,10 @@ declare module 'babylonjs-viewer/managers/sceneManager' {
                     ground?: IGroundConfiguration | boolean;
             }>>;
             /**
+                * Will notify after the model(s) were configured. Can be used to further configure models
+                */
+            onVRConfiguredObservable: Observable<IPostConfigurationCallback<VRExperienceHelper, IVRConfiguration>>;
+            /**
                 * The Babylon Scene of this viewer
                 */
             scene: Scene;
@@ -1304,6 +1314,8 @@ declare module 'babylonjs-viewer/managers/sceneManager' {
                 */
             labs: ViewerLabs;
             readonly defaultRenderingPipeline: Nullable<DefaultRenderingPipeline>;
+            protected _vrHelper?: VRExperienceHelper;
+            readonly vrHelper: VRExperienceHelper | undefined;
             constructor(_engine: Engine, _configurationContainer: ConfigurationContainer, _observablesManager?: ObservablesManager | undefined);
             /**
                 * Returns a boolean representing HDR support
@@ -1361,6 +1373,11 @@ declare module 'babylonjs-viewer/managers/sceneManager' {
                 */
             protected _configureOptimizer(optimizerConfig: ISceneOptimizerConfiguration | boolean): void;
             /**
+                * configure all models using the configuration.
+                * @param modelConfiguration the configuration to use to reconfigure the models
+                */
+            protected _configureVR(vrConfig: IVRConfiguration): void;
+            /**
                 * (Re) configure the camera. The camera will only be created once and from this point will only be reconfigured.
                 * @param cameraConfig the new camera configuration
                 * @param model optionally use the model to configure the camera.
@@ -1588,6 +1605,7 @@ declare module 'babylonjs-viewer/configuration/interfaces' {
     export * from 'babylonjs-viewer/configuration/interfaces/sceneOptimizerConfiguration';
     export * from 'babylonjs-viewer/configuration/interfaces/skyboxConfiguration';
     export * from 'babylonjs-viewer/configuration/interfaces/templateConfiguration';
+    export * from 'babylonjs-viewer/configuration/interfaces/vrConfiguration';
 }
 
 declare module 'babylonjs-viewer/templating/eventManager' {
@@ -2212,6 +2230,18 @@ declare module 'babylonjs-viewer/configuration/interfaces/templateConfiguration'
     }
 }
 
+declare module 'babylonjs-viewer/configuration/interfaces/vrConfiguration' {
+    import { VRExperienceHelperOptions } from "babylonjs";
+    export interface IVRConfiguration {
+        disabled?: boolean;
+        objectScaleFactor?: number;
+        disableInteractions?: boolean;
+        disableTeleportation?: boolean;
+        overrideFloorMeshName?: string;
+        vrOptions?: VRExperienceHelperOptions;
+    }
+}
+
 declare module 'babylonjs-viewer/labs/environmentSerializer' {
     import { Vector3 } from "babylonjs";
     import { TextureCube } from 'babylonjs-viewer/labs/texture';

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

@@ -2,7 +2,6 @@
 
 ## Major updates
 
-- Added a ParticleHelper class to create some pre-configured particle systems in a one-liner method style ([DevChris](https://github.com/yovanoc))
 - Added new `MixMaterial` to the Materials Library allowing to mix up to 8 textures ([julien-moreau](https://github.com/julien-moreau))
 - Added new `PhotoDome` object to display 360 photos. [Demo](https://www.babylonjs-playground.com/#14KRGG#0) ([SzeyinLee](https://github.com/SzeyinLee))
 - New GUI 3D controls toolset. [Complete doc + demos](http://doc.babylonjs.com/how_to/gui3d) ([Deltakosh](https://github.com/deltakosh))
@@ -10,15 +9,17 @@
 - New GUI control: the [Grid](http://doc.babylonjs.com/how_to/gui#grid) ([Deltakosh](https://github.com/deltakosh))
 - Gizmo and GizmoManager classes used to manipulate meshes in a scene. Gizmo types include: position, rotation, scale and bounding box ([TrevorDev](https://github.com/TrevorDev))
 - Particle system improvements ([Deltakosh](https://github.com/deltakosh))
+  - Added a ParticleHelper class to create some pre-configured particle systems in a one-liner method style. [Doc](https://doc.babylonjs.com/How_To/ParticleHelper) ([Deltakosh](https://github.com/deltakosh)) / ([DevChris](https://github.com/yovanoc))
   - Improved CPU particles rendering performance (up to x2 on low end devices)
   - Added support for `isBillboardBased`. [Doc](http://doc.babylonjs.com/babylon101/particles#alignment)
-  - Added support for `minScaleX`, `minScaleY`, `maxScaleX`, `maxScaleY`. [Doc](http://doc.babylonjs.com/babylon101/particles#size)
-  - Added support for `radiusRange` for sphere emitter. [Doc](http://doc.babylonjs.com/babylon101/particles#sphere-emitter)
-  - Added support for `ParticleSystem.BLENDMODE_ADD` alpha mode. [Doc](http://doc.babylonjs.com/babylon101/particles#particle-blending)
-  - Added support for color gradients. [Doc](http://doc.babylonjs.com/babylon101/particles#particle-colors)
-  - Added support for pre-warming. [Doc](http://doc.babylonjs.com/babylon101/particles#pre-warming)
-  - Added support for `minInitialRotation` and `maxInitialRotation`. [Doc](http://doc.babylonjs.com/babylon101/particles#rotation)
-  - Added support for size gradients. [Doc](http://doc.babylonjs.com/babylon101/particles#size)
+  - Added support for `minScaleX`, `minScaleY`, `maxScaleX`, `maxScaleY`. [Doc](https://doc.babylonjs.com/babylon101/particles#size)
+  - Added support for `radiusRange` for sphere emitter. [Doc](https://doc.babylonjs.com/babylon101/particles#sphere-emitter)
+  - Added support for `ParticleSystem.BLENDMODE_ADD` alpha mode. [Doc](https://doc.babylonjs.com/babylon101/particles#particle-blending)
+  - Added support for color gradients. [Doc](https://doc.babylonjs.com/babylon101/particles#particle-colors)
+  - Added support for pre-warming. [Doc](https://doc.babylonjs.com/babylon101/particles#pre-warming)
+  - Added support for `minInitialRotation` and `maxInitialRotation`. [Doc](https://doc.babylonjs.com/babylon101/particles#rotation)
+  - Added support for size gradients. [Doc](https://doc.babylonjs.com/babylon101/particles#size)
+  - Added support for life time gradients. [Doc](https://doc.babylonjs.com/babylon101/particles#lifetime)
 
 ## Updates
 
@@ -54,8 +55,9 @@
 - `Sound` now accepts `MediaStream` as source to enable easier WebAudio and WebRTC integrations ([menduz](https://github.com/menduz))
 - Vector x, y and z constructor parameters are now optional and default to 0 ([TrevorDev](https://github.com/TrevorDev))
 - New vertical mode for sliders in 2D GUI. [Demo](https://www.babylonjs-playground.com/#U9AC0N#53) ([Saket Saurabh](https://github.com/ssaket))
-- Add and remove camera methods in the default pipeline ([TrevorDev](https://github.com/TrevorDev))
-- Add internal texture `format` support for RenderTargetCubeTexture ([PeapBoy](https://github.com/NicolasBuecher))
+- Added and remove camera methods in the default pipeline ([TrevorDev](https://github.com/TrevorDev))
+- Added internal texture `format` support for RenderTargetCubeTexture ([PeapBoy](https://github.com/NicolasBuecher))
+- Added canvas toBlob polyfill in tools ([sebavan](http://www.github.com/sebavan))
 
 ### glTF Loader
 
@@ -74,6 +76,8 @@
 - New lab feature - global light rotation [#4347](https://github.com/BabylonJS/Babylon.js/issues/4347) ([RaananW](https://github.com/RaananW))
 - New NPM package - babylonjs-viewer-assets, to separate the binary assets and the code of the viewer ([RaananW](https://github.com/RaananW))
 - A new HD-Toggler button allows setting a better hardware scaling rate ([RaananW](https://github.com/RaananW))
+- An initial support for WebVR is implemented ([RaananW](https://github.com/RaananW))
+- It is now possible to choose the element that goes fullscreen in the default viewer ([RaananW](https://github.com/RaananW))
 
 ### Documentation
 
@@ -88,6 +92,7 @@
 - VR experience helper's controllers will not fire pointer events when laser's are disabled, instead the camera ray pointer event will be used ([TrevorDev](https://github.com/TrevorDev))
 - Node's setParent(node.parent) will no longer throw an exception when parent is undefined and will behave the same as setParent(null) ([TrevorDev](https://github.com/TrevorDev))
 - Mesh.MergeMeshes flips triangles on meshes with negative scaling ([SvenFrankson](http://svenfrankson.com))
+- Avoid firing button events multiple times when calling vrController.attachMesh() ([TrevorDev]
 
 ### Core Engine
 

+ 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-alpha.8",
+    "version": "3.3.0-alpha.9",
     "repository": {
         "type": "git",
         "url": "https://github.com/BabylonJS/Babylon.js.git"

+ 17 - 17
sandbox/index.html

@@ -6,24 +6,24 @@
     <meta name="keywords" content="Babylon.js, Babylon, BabylonJS, glTF, glb, obj, viewer, online viewer, 3D model viewer, 3D, webgl" />
     <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1">
 
-    <link rel="shortcut icon" href="http://www.babylonjs.com/img/favicon/favicon.ico">
-    <link rel="apple-touch-icon" sizes="57x57" href="http://www.babylonjs.com/img/favicon/apple-icon-57x57.png">
-    <link rel="apple-touch-icon" sizes="60x60" href="http://www.babylonjs.com/img/favicon/apple-icon-60x60.png">
-    <link rel="apple-touch-icon" sizes="72x72" href="http://www.babylonjs.com/img/favicon/apple-icon-72x72.png">
-    <link rel="apple-touch-icon" sizes="76x76" href="http://www.babylonjs.com/img/favicon/apple-icon-76x76.png">
-    <link rel="apple-touch-icon" sizes="114x114" href="http://www.babylonjs.com/img/favicon/apple-icon-114x114.png">
-    <link rel="apple-touch-icon" sizes="120x120" href="http://www.babylonjs.com/img/favicon/apple-icon-120x120.png">
-    <link rel="apple-touch-icon" sizes="144x144" href="http://www.babylonjs.com/img/favicon/apple-icon-144x144.png">
-    <link rel="apple-touch-icon" sizes="152x152" href="http://www.babylonjs.com/img/favicon/apple-icon-152x152.png">
-    <link rel="apple-touch-icon" sizes="180x180" href="http://www.babylonjs.com/img/favicon/apple-icon-180x180.png">
-    <link rel="icon" type="image/png" sizes="192x192" href="http://www.babylonjs.com/img/favicon/android-icon-192x192.png">
-    <link rel="icon" type="image/png" sizes="32x32" href="http://www.babylonjs.com/img/favicon/favicon-32x32.png">
-    <link rel="icon" type="image/png" sizes="96x96" href="http://www.babylonjs.com/img/favicon/favicon-96x96.png">
-    <link rel="icon" type="image/png" sizes="16x16" href="http://www.babylonjs.com/img/favicon/favicon-16x16.png">
-    <link rel="manifest" href="http://www.babylonjs.com/img/favicon/manifest.json">
+    <link rel="shortcut icon" href="https://www.babylonjs.com/img/favicon/favicon.ico">
+    <link rel="apple-touch-icon" sizes="57x57" href="https://www.babylonjs.com/img/favicon/apple-icon-57x57.png">
+    <link rel="apple-touch-icon" sizes="60x60" href="https://www.babylonjs.com/img/favicon/apple-icon-60x60.png">
+    <link rel="apple-touch-icon" sizes="72x72" href="https://www.babylonjs.com/img/favicon/apple-icon-72x72.png">
+    <link rel="apple-touch-icon" sizes="76x76" href="https://www.babylonjs.com/img/favicon/apple-icon-76x76.png">
+    <link rel="apple-touch-icon" sizes="114x114" href="https://www.babylonjs.com/img/favicon/apple-icon-114x114.png">
+    <link rel="apple-touch-icon" sizes="120x120" href="https://www.babylonjs.com/img/favicon/apple-icon-120x120.png">
+    <link rel="apple-touch-icon" sizes="144x144" href="https://www.babylonjs.com/img/favicon/apple-icon-144x144.png">
+    <link rel="apple-touch-icon" sizes="152x152" href="https://www.babylonjs.com/img/favicon/apple-icon-152x152.png">
+    <link rel="apple-touch-icon" sizes="180x180" href="https://www.babylonjs.com/img/favicon/apple-icon-180x180.png">
+    <link rel="icon" type="image/png" sizes="192x192" href="https://www.babylonjs.com/img/favicon/android-icon-192x192.png">
+    <link rel="icon" type="image/png" sizes="32x32" href="https://www.babylonjs.com/img/favicon/favicon-32x32.png">
+    <link rel="icon" type="image/png" sizes="96x96" href="https://www.babylonjs.com/img/favicon/favicon-96x96.png">
+    <link rel="icon" type="image/png" sizes="16x16" href="https://www.babylonjs.com/img/favicon/favicon-16x16.png">
+    <link rel="manifest" href="https://www.babylonjs.com/img/favicon/manifest.json">
     <meta name="msapplication-TileColor" content="#ffffff">
-    <meta name="msapplication-TileImage" content="http://www.babylonjs.com/img/favicon/ms-icon-144x144.png">
-    <meta name="msapplication-config" content="http://www.babylonjs.com/img/favicon/browserconfig.xml">
+    <meta name="msapplication-TileImage" content="https://www.babylonjs.com/img/favicon/ms-icon-144x144.png">
+    <meta name="msapplication-config" content="https://www.babylonjs.com/img/favicon/browserconfig.xml">
     <meta name="theme-color" content="#ffffff">
 
     <link href="index.css" rel="stylesheet" />

+ 1 - 1
serializers/src/glTF/2.0/babylon.glTFMaterialExporter.ts

@@ -501,7 +501,7 @@ module BABYLON.GLTF2 {
                     // Read data from WebGL
                     const canvas = engine.getRenderingCanvas();
                     if (canvas) {
-                        canvas.toBlob(blob => {
+                        BABYLON.Tools.ToBlob(canvas, blob => {
                             if (blob) {
                                 let fileReader = new FileReader();
                                 fileReader.onload = (event: any) => {

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

@@ -710,7 +710,7 @@
          * Returns the current version of the framework
          */
         public static get Version(): string {
-            return "3.3.0-alpha.8";
+            return "3.3.0-alpha.9";
         }
 
         // Updatable statics so stick with vars here

+ 4 - 0
src/Engine/babylon.nullEngine.ts

@@ -397,6 +397,10 @@
             return vbo;
         }
 
+        public updateDynamicTexture(texture: Nullable<InternalTexture>, canvas: HTMLCanvasElement, invertY: boolean, premulAlpha: boolean = false, format?: number): void {
+
+        }
+
         public updateDynamicIndexBuffer(indexBuffer: WebGLBuffer, indices: IndicesArray, offset: number = 0): void {
         }
 

+ 10 - 2
src/Gamepad/Controllers/babylon.poseEnabledController.ts

@@ -188,6 +188,13 @@ module BABYLON {
          */
         public update() {
             super.update();
+            this._updatePoseAndMesh();
+        }
+
+        /**
+         * Updates only the pose device and mesh without doing any button event checking
+         */
+        protected _updatePoseAndMesh(){
             var pose: GamepadPose = this.browserGamepad.pose;
             this.updateFromDevice(pose);
 
@@ -204,6 +211,7 @@ module BABYLON {
                 }
             }
         }
+
         /**
          * Updates the state of the pose enbaled controller based on the raw pose data from the device
          * @param poseData raw pose fromthe device
@@ -259,8 +267,8 @@ module BABYLON {
                 this._mesh.rotationQuaternion = new Quaternion();
             }
 
-            // Sync controller mesh and pointing pose node's state with controller
-            this.update();
+            // Sync controller mesh and pointing pose node's state with controller, this is done to avoid a frame where position is 0,0,0 when attaching mesh
+            this._updatePoseAndMesh();
             if(this._pointingPoseNode){
                 var parents = [];
                 var obj:Node = this._pointingPoseNode;

+ 1 - 1
src/Gizmos/babylon.axisDragGizmo.ts

@@ -20,7 +20,7 @@ module BABYLON {
          * @param dragAxis The axis which the gizmo will be able to drag on
          * @param color The color of the gizmo
          */
-        constructor(gizmoLayer:UtilityLayerRenderer, dragAxis:Vector3, color:Color3){
+        constructor(dragAxis:Vector3, color:Color3 = Color3.Gray(), gizmoLayer:UtilityLayerRenderer = UtilityLayerRenderer.DefaultUtilityLayer){
             super(gizmoLayer);
             
             // Create Material

+ 1 - 1
src/Gizmos/babylon.axisScaleGizmo.ts

@@ -20,7 +20,7 @@ module BABYLON {
          * @param dragAxis The axis which the gizmo will be able to scale on
          * @param color The color of the gizmo
          */
-        constructor(gizmoLayer:UtilityLayerRenderer, dragAxis:Vector3, color:Color3){
+        constructor(dragAxis:Vector3, color:Color3 = Color3.Gray(), gizmoLayer:UtilityLayerRenderer = UtilityLayerRenderer.DefaultUtilityLayer){
             super(gizmoLayer);
             
             // Create Material

+ 55 - 4
src/Gizmos/babylon.boundingBoxGizmo.ts

@@ -13,12 +13,43 @@ module BABYLON {
 
         private _tmpQuaternion = new Quaternion();
         private _tmpVector = new Vector3(0,0,0);
+
+        /**
+         * The size of the rotation spheres attached to the bounding box (Default: 0.1)
+         */
+        public rotationSphereSize = 0.1;
+        /**
+         * The size of the scale boxes attached to the bounding box (Default: 0.1)
+         */
+        public scaleBoxSize = 0.1;
+        /**
+         * If set, the rotation spheres and scale boxes will increase in size based on the distance away from the camera to have a consistent screen size (Default: false)
+         */
+        public fixedDragMeshScreenSize = false;
+
+        /**
+         * The distance away from the object which the draggable meshes should appear world sized when fixedDragMeshScreenSize is set to true (default: 10)
+         */
+        public fixedDragMeshScreenSizeDistanceFactor = 10;
+        /**
+         * Fired when a rotation sphere or scale box is dragged
+         */
+        public onDragStartObservable = new Observable<{}>();
+        /**
+         * Fired when a rotation sphere or scale box drag is started
+         */
+        public onDragObservable = new Observable<{}>();
+        /**
+         * Fired when a rotation sphere or scale box drag is needed
+         */
+        public onDragEndObservable = new Observable<{}>();
+        
         /**
          * Creates an BoundingBoxGizmo
          * @param gizmoLayer The utility layer the gizmo will be added to
          * @param color The color of the gizmo
          */
-        constructor(gizmoLayer:UtilityLayerRenderer, color:Color3){
+        constructor(color:Color3 = Color3.Gray(), gizmoLayer:UtilityLayerRenderer = UtilityLayerRenderer.DefaultUtilityLayer){
             super(gizmoLayer);
 
             // Do not update the gizmo's scale so it has a fixed size to the object its attached to
@@ -60,7 +91,7 @@ module BABYLON {
             this._rotateSpheresParent = new BABYLON.AbstractMesh("", gizmoLayer.utilityLayerScene);
             this._rotateSpheresParent.rotationQuaternion = new Quaternion();
             for(let i=0;i<12;i++){
-                let sphere = BABYLON.MeshBuilder.CreateSphere("", {diameter: 0.1}, gizmoLayer.utilityLayerScene);
+                let sphere = BABYLON.MeshBuilder.CreateSphere("", {diameter: 1}, gizmoLayer.utilityLayerScene);
                 sphere.rotationQuaternion = new Quaternion();
                 sphere.material = coloredMaterial;
 
@@ -76,6 +107,7 @@ module BABYLON {
                     totalTurnAmountOfDrag = 0;
                 })
                 _dragBehavior.onDragObservable.add((event)=>{
+                    this.onDragObservable.notifyObservers({});
                     if(this.attachedMesh){
                         var worldDragDirection = startingTurnDirection;
 
@@ -108,9 +140,11 @@ module BABYLON {
 
                 // Selection/deselection
                 _dragBehavior.onDragStartObservable.add(()=>{
+                    this.onDragStartObservable.notifyObservers({});
                     this._selectNode(sphere)
                 })
                 _dragBehavior.onDragEndObservable.add(()=>{
+                    this.onDragEndObservable.notifyObservers({});
                     this._selectNode(null)
                 })
 
@@ -124,7 +158,7 @@ module BABYLON {
             for(var i=0;i<2;i++){
                 for(var j=0;j<2;j++){
                     for(var k=0;k<2;k++){
-                        let box = BABYLON.MeshBuilder.CreateBox("", {size: 0.1}, gizmoLayer.utilityLayerScene);
+                        let box = BABYLON.MeshBuilder.CreateBox("", {size: 1}, gizmoLayer.utilityLayerScene);
                         box.material = coloredMaterial;
 
                         // Dragging logic
@@ -133,6 +167,7 @@ module BABYLON {
                         _dragBehavior.moveAttached = false;
                         box.addBehavior(_dragBehavior);
                         _dragBehavior.onDragObservable.add((event)=>{
+                            this.onDragObservable.notifyObservers({});
                             if(this.attachedMesh){
                                 // Current boudning box dimensions
                                 var boundingInfo = this.attachedMesh.getBoundingInfo().boundingBox;
@@ -154,9 +189,11 @@ module BABYLON {
 
                         // Selection/deselection
                         _dragBehavior.onDragStartObservable.add(()=>{
+                            this.onDragStartObservable.notifyObservers({});
                             this._selectNode(box)
                         })
                         _dragBehavior.onDragEndObservable.add(()=>{
+                            this.onDragEndObservable.notifyObservers({});
                             this._selectNode(null)
                         })
 
@@ -198,7 +235,7 @@ module BABYLON {
             })
         }
 
-        private _updateBoundingBox(){   
+        private _updateBoundingBox(){
             if(this.attachedMesh){
                 // Update bounding dimensions/positions
                 var boundingInfo = this.attachedMesh.getBoundingInfo().boundingBox;
@@ -228,6 +265,13 @@ module BABYLON {
                             rotateSpheres[index].position.addInPlace(new BABYLON.Vector3(-this._boundingDimensions.x/2,-this._boundingDimensions.y/2,-this._boundingDimensions.z/2));
                             rotateSpheres[index].lookAt(Vector3.Cross(Vector3.Forward(), rotateSpheres[index].position.normalizeToNew()).normalizeToNew().add(rotateSpheres[index].position));
                         }
+                        if(this.fixedDragMeshScreenSize){
+                            rotateSpheres[index].absolutePosition.subtractToRef(this.gizmoLayer.utilityLayerScene.activeCamera!.position, this._tmpVector);
+                            var distanceFromCamera = this.rotationSphereSize*this._tmpVector.length()/this.fixedDragMeshScreenSizeDistanceFactor;
+                            rotateSpheres[index].scaling.set(distanceFromCamera, distanceFromCamera, distanceFromCamera);
+                        }else{
+                            rotateSpheres[index].scaling.set(this.rotationSphereSize, this.rotationSphereSize, this.rotationSphereSize);
+                        }
                     }
                 }
             }
@@ -241,6 +285,13 @@ module BABYLON {
                         if(scaleBoxes[index]){
                             scaleBoxes[index].position.set(this._boundingDimensions.x*i,this._boundingDimensions.y*j,this._boundingDimensions.z*k);
                             scaleBoxes[index].position.addInPlace(new BABYLON.Vector3(-this._boundingDimensions.x/2,-this._boundingDimensions.y/2,-this._boundingDimensions.z/2));
+                            if(this.fixedDragMeshScreenSize){
+                                scaleBoxes[index].absolutePosition.subtractToRef(this.gizmoLayer.utilityLayerScene.activeCamera!.position, this._tmpVector);
+                                var distanceFromCamera = this.scaleBoxSize*this._tmpVector.length()/this.fixedDragMeshScreenSizeDistanceFactor;
+                                scaleBoxes[index].scaling.set(distanceFromCamera, distanceFromCamera, distanceFromCamera);
+                            }else{
+                                scaleBoxes[index].scaling.set(this.scaleBoxSize, this.scaleBoxSize, this.scaleBoxSize);
+                            }
                         }
                     }
                 }

+ 1 - 1
src/Gizmos/babylon.gizmo.ts

@@ -42,7 +42,7 @@ module BABYLON {
          * Creates a gizmo
          * @param gizmoLayer The utility layer the gizmo will be added to
          */
-        constructor(/** The utility layer the gizmo will be added to */ public gizmoLayer:UtilityLayerRenderer){
+        constructor(/** The utility layer the gizmo will be added to */ public gizmoLayer:UtilityLayerRenderer=UtilityLayerRenderer.DefaultUtilityLayer){
             this._rootMesh = new BABYLON.Mesh("gizmoRootNode",gizmoLayer.utilityLayerScene);
             this._beforeRenderObserver = this.gizmoLayer.utilityLayerScene.onBeforeRenderObservable.add(()=>{
                 if(this._updateScale && this.gizmoLayer.utilityLayerScene.activeCamera && this.attachedMesh){

+ 1 - 1
src/Gizmos/babylon.planeRotationGizmo.ts

@@ -22,7 +22,7 @@ module BABYLON {
          * @param planeNormal The normal of the plane which the gizmo will be able to rotate on
          * @param color The color of the gizmo
          */
-        constructor(gizmoLayer:UtilityLayerRenderer, planeNormal:Vector3, color:Color3){
+        constructor(planeNormal:Vector3, color:Color3 = Color3.Gray(), gizmoLayer:UtilityLayerRenderer = UtilityLayerRenderer.DefaultUtilityLayer){
             super(gizmoLayer);
             
             // Create Material

+ 4 - 4
src/Gizmos/babylon.positionGizmo.ts

@@ -18,11 +18,11 @@ module BABYLON {
          * Creates a PositionGizmo
          * @param gizmoLayer The utility layer the gizmo will be added to
          */
-        constructor(gizmoLayer:UtilityLayerRenderer){
+        constructor(gizmoLayer:UtilityLayerRenderer=UtilityLayerRenderer.DefaultUtilityLayer){
             super(gizmoLayer);
-            this._xDrag = new AxisDragGizmo(gizmoLayer, new Vector3(1,0,0), BABYLON.Color3.Green().scale(0.5));
-            this._yDrag = new AxisDragGizmo(gizmoLayer, new Vector3(0,1,0), BABYLON.Color3.Red().scale(0.5));
-            this._zDrag = new AxisDragGizmo(gizmoLayer, new Vector3(0,0,1), BABYLON.Color3.Blue().scale(0.5));
+            this._xDrag = new AxisDragGizmo(new Vector3(1,0,0), BABYLON.Color3.Green().scale(0.5), gizmoLayer);
+            this._yDrag = new AxisDragGizmo(new Vector3(0,1,0), BABYLON.Color3.Red().scale(0.5), gizmoLayer);
+            this._zDrag = new AxisDragGizmo(new Vector3(0,0,1), BABYLON.Color3.Blue().scale(0.5), gizmoLayer);
             this.attachedMesh = null;
         }
 

+ 4 - 4
src/Gizmos/babylon.rotationGizmo.ts

@@ -18,11 +18,11 @@ module BABYLON {
          * Creates a RotationGizmo
          * @param gizmoLayer The utility layer the gizmo will be added to
          */
-        constructor(gizmoLayer:UtilityLayerRenderer){
+        constructor(gizmoLayer:UtilityLayerRenderer=UtilityLayerRenderer.DefaultUtilityLayer){
             super(gizmoLayer);
-            this._xDrag = new PlaneRotationGizmo(gizmoLayer, new Vector3(1,0,0), BABYLON.Color3.Green().scale(0.5));
-            this._yDrag = new PlaneRotationGizmo(gizmoLayer, new Vector3(0,1,0), BABYLON.Color3.Red().scale(0.5));
-            this._zDrag = new PlaneRotationGizmo(gizmoLayer, new Vector3(0,0,1), BABYLON.Color3.Blue().scale(0.5));
+            this._xDrag = new PlaneRotationGizmo(new Vector3(1,0,0), BABYLON.Color3.Green().scale(0.5), gizmoLayer);
+            this._yDrag = new PlaneRotationGizmo(new Vector3(0,1,0), BABYLON.Color3.Red().scale(0.5), gizmoLayer);
+            this._zDrag = new PlaneRotationGizmo(new Vector3(0,0,1), BABYLON.Color3.Blue().scale(0.5), gizmoLayer);
             this.attachedMesh = null;
         }
 

+ 4 - 4
src/Gizmos/babylon.scaleGizmo.ts

@@ -18,11 +18,11 @@ module BABYLON {
          * Creates a ScaleGizmo
          * @param gizmoLayer The utility layer the gizmo will be added to
          */
-        constructor(gizmoLayer:UtilityLayerRenderer){
+        constructor(gizmoLayer:UtilityLayerRenderer=UtilityLayerRenderer.DefaultUtilityLayer){
             super(gizmoLayer);
-            this._xDrag = new AxisScaleGizmo(gizmoLayer, new Vector3(1,0,0), BABYLON.Color3.Green().scale(0.5));
-            this._yDrag = new AxisScaleGizmo(gizmoLayer, new Vector3(0,1,0), BABYLON.Color3.Red().scale(0.5));
-            this._zDrag = new AxisScaleGizmo(gizmoLayer, new Vector3(0,0,1), BABYLON.Color3.Blue().scale(0.5));
+            this._xDrag = new AxisScaleGizmo(new Vector3(1,0,0), BABYLON.Color3.Green().scale(0.5), gizmoLayer);
+            this._yDrag = new AxisScaleGizmo(new Vector3(0,1,0), BABYLON.Color3.Red().scale(0.5), gizmoLayer);
+            this._zDrag = new AxisScaleGizmo(new Vector3(0,0,1), BABYLON.Color3.Blue().scale(0.5), gizmoLayer);
             this.attachedMesh = null;
         }
 

+ 0 - 349
src/Helpers/babylon.particleHelper.ts

@@ -1,349 +0,0 @@
-module BABYLON {
-    /**
-     * Represents all the data needed to create a ParticleSystem.
-     */
-    export interface IParticleSystemData {
-        /**
-         * ParticleSystem type
-         */
-        type: string;
-        /**
-         * Shape of the emitter
-         */
-        emitterType: string
-        /**
-         * Maximum number of particles in the system
-         */
-        capacity: number;
-        /**
-         * Link for the texture file
-         */
-        textureFile: string;
-        /**
-         * minEmitBox Vector3
-         */
-        minEmitBox?: { x: number, y: number, z: number };
-        /**
-         * maxEmitBox Vector3
-         */
-        maxEmitBox?: { x: number, y: number, z: number };
-        /**
-         * color1 Color4
-         */
-        color1: { r: number, g: number, b: number, a: number };
-        /**
-         * color2 Color4
-         */
-        color2: { r: number, g: number, b: number, a: number };
-        /**
-         * colorDead Color4
-         */
-        colorDead: { r: number, g: number, b: number, a: number };
-        /**
-         * Minimum size of each particle
-         */
-        minSize: number;
-        /**
-         * Maximum size of each particle
-         */
-        maxSize: number;
-        /**
-         * Minimum scale of each particle on X axis
-         */
-        minScaleX: number;
-        /**
-         * Maximum scale of each particle on X axis
-         */
-        maxScaleX: number;       
-        /**
-         * Minimum scale of each particle on Y axis
-         */
-        minScaleY: number;
-        /**
-         * Maximum scale of each particle on Y axis
-         */
-        maxScaleY: number;           
-        /**
-         * Minimum lifetime for each particle
-         */
-        minLifeTime: number;
-        /**
-         * Maximum lifetime for each particle
-         */
-        maxLifeTime: number;
-        /**
-         * Emit rate
-         */
-        emitRate: number;
-        /**
-         * Blend Mode
-         */
-        blendMode: number;
-        /**
-         * gravity Vector3
-         */
-        gravity: { x: number, y: number, z: number };
-        /**
-         * direction1 Vector3
-         */
-        direction1?: { x: number, y: number, z: number };
-        /**
-         * direction2 Vector3
-         */
-        direction2?: { x: number, y: number, z: number };
-        /**
-         * Minimum Angular Speed
-         */
-        minAngularSpeed: number;
-        /**
-         * Maximum Angular Speed
-         */
-        maxAngularSpeed: number;
-        /**
-         * Minimum Emit Power
-         */
-        minEmitPower: number;
-        /**
-         * Maximum Emit Power
-         */
-        maxEmitPower: number;
-        /**
-         * Update Speed
-         */
-        updateSpeed: number;
-        /**
-         * Radius
-         */
-        radius?: number;
-        /**
-         * Angle
-         */
-        angle?: number;
-    }
-    /**
-     * This class is made for on one-liner static method to help creating particle systems.
-     */
-    export class ParticleHelper {
-        /**
-         * Base Assets URL.
-         */
-        private static _baseAssetsUrl = "https://assets.babylonjs.com/particles";
-
-        /**
-         * This is the main static method (one-liner) of this helper to create different particle systems.
-         * @param type This string represents the type to the particle system to create
-         * @param emitter The object where the particle system will start to emit from.
-         * @param scene The scene where the particle system should live.
-         * @param gpu If the system will use gpu.
-         * @returns the ParticleSystem created.
-         */
-        public static CreateAsync(type: string, emitter: AbstractMesh,
-                                   scene: Nullable<Scene> = Engine.LastCreatedScene, gpu: boolean = false): Promise<ParticleSystem> {
-            
-            return new Promise((resolve, reject) => {
-                if (!scene) {
-                    scene = Engine.LastCreatedScene;;
-                }
-
-                if (gpu && !GPUParticleSystem.IsSupported) {
-                    return reject("Particle system with GPU is not supported.");
-                }
-
-                Tools.LoadFile(`${ParticleHelper._baseAssetsUrl}/systems/${type}.json`, (data, response) => {
-                    const newData = JSON.parse(data.toString()) as IParticleSystemData;
-                    return resolve(ParticleHelper.CreateSystem(newData, scene!, emitter));
-                }, undefined, undefined, undefined, (req, exception) => {
-                    return reject(`An error occured while the creation of your particle system. Check if your type '${type}' exists.`);
-                });
-
-            });
-        }
-
-        /**
-         * Static function used to create a new particle system from a IParticleSystemData
-         * @param data defines the source data
-         * @param scene defines the hosting scene
-         * @param emitter defines the particle emitter
-         * @returns a new ParticleSystem based on referenced data
-         */
-        public static CreateSystem(data: IParticleSystemData, scene: Scene, emitter: AbstractMesh): ParticleSystem {
-            // Create a particle system
-            const system = new ParticleSystem(data.type, data.capacity, scene);
-
-            // Where the particles come from
-            system.emitter = emitter; // the starting object, the emitter            
-
-            ParticleHelper.UpdateSystem(system, data, scene);
-
-            return system;
-        }
-
-        /**
-         * Static function used to update a particle system from a IParticleSystemData
-         * @param system defines the particle system to update
-         * @param data defines the source data
-         * @param scene defines the hosting scene
-         */
-        public static UpdateSystem(system: ParticleSystem, data: IParticleSystemData, scene: Scene): void {
-            // Texture of each particle
-            if (data.textureFile) {
-                system.particleTexture = new Texture(`${ParticleHelper._baseAssetsUrl}/textures/${data.textureFile}`, scene);
-            }
-
-            // Colors of all particles
-            system.color1 = new Color4(data.color1.r, data.color1.g, data.color1.b, data.color1.a);
-            system.color2 = new Color4(data.color2.r, data.color2.g, data.color2.b, data.color2.a);
-            system.colorDead = new Color4(data.colorDead.r, data.colorDead.g, data.colorDead.b, data.colorDead.a);
-
-            // Size of each particle (random between...
-            system.minSize = data.minSize;
-            system.maxSize = data.maxSize;
-
-            system.minScaleX = data.minScaleX;
-            system.maxScaleX = data.maxScaleX;    
-            
-            system.minScaleY = data.minScaleY;
-            system.maxScaleY = data.maxScaleY;              
-
-            // Life time of each particle (random between...
-            system.minLifeTime = data.minLifeTime;
-            system.maxLifeTime = data.maxLifeTime;
-
-            // Emission rate
-            system.emitRate = data.emitRate;
-
-            // Blend mode : BLENDMODE_ONEONE, or BLENDMODE_STANDARD
-            system.blendMode = data.blendMode;
-
-            // Set the gravity of all particles
-            system.gravity = new Vector3(data.gravity.x, data.gravity.y, data.gravity.z);
-
-            // Angular speed, in radians
-            system.minAngularSpeed = data.minAngularSpeed;
-            system.maxAngularSpeed = data.maxAngularSpeed;
-
-            // Speed
-            system.minEmitPower = data.minEmitPower;
-            system.maxEmitPower = data.maxEmitPower;
-            system.updateSpeed = data.updateSpeed;
-
-            switch (data.emitterType) {
-                case "box":
-
-                    if (!data.direction1 || !data.direction2) {
-                        throw new Error("Directions are missing in this particle system.");
-                    }
-
-                    if (!data.minEmitBox || !data.maxEmitBox) {
-                        throw new Error("EmitBox is missing in this particle system.");
-                    }
-
-                    system.createBoxEmitter(
-                        new Vector3(data.direction1.x, data.direction1.y, data.direction1.z),
-                        new Vector3(data.direction2.x, data.direction2.y, data.direction2.z),
-                        new Vector3(data.minEmitBox.x, data.minEmitBox.y, data.minEmitBox.z),
-                        new Vector3(data.maxEmitBox.x, data.maxEmitBox.y, data.maxEmitBox.z)
-                    );
-                    break;
-                case "sphere":
-                    system.createSphereEmitter(data.radius);
-                    break;
-                case "directed_sphere":
-                    
-                    if (!data.direction1 || !data.direction2) {
-                        throw new Error("Directions are missing in this particle system.");
-                    }
-                    
-                    system.createDirectedSphereEmitter(
-                        data.radius,
-                        new Vector3(data.direction1.x, data.direction1.y, data.direction1.z),
-                        new Vector3(data.direction2.x, data.direction2.y, data.direction2.z)
-                    );
-                    break;
-                case "cone":
-                    system.createConeEmitter(data.radius, data.angle);
-                    break;
-                default:
-                    break;
-            }
-        }
-
-        /**
-         * Static function used to export a particle system to a IParticleSystemData variable.
-         * Please note that texture file name is not exported and must be added manually
-         * @param system defines the particle system to export
-         */
-        public static ExportSystem(system: ParticleSystem): IParticleSystemData {
-            var outData: any = {};
-
-            // Colors of all particles
-            outData.color1 = { r: system.color1.r, g: system.color1.g, b: system.color1.b, a: system.color1.a };
-            outData.color2 = { r: system.color2.r, g: system.color2.g, b: system.color2.b, a: system.color2.a };
-            outData.colorDead = { r: system.colorDead.r, g: system.colorDead.g, b: system.colorDead.b, a: system.colorDead.a };
-
-            // Size of each particle (random between...
-            outData.minSize = system.minSize;
-            outData.maxSize = system.maxSize;
-
-            outData.minScaleX = system.minScaleX;
-            outData.maxScaleX = system.maxScaleX;     
-            
-            outData.minScaleY = system.minScaleY;
-            outData.maxScaleY = system.maxScaleY;             
-
-            // Life time of each particle (random between...
-            outData.minLifeTime = system.minLifeTime;
-            outData.maxLifeTime = system.maxLifeTime;
-
-            // Emission rate
-            outData.emitRate = system.emitRate;
-
-            // Blend mode : BLENDMODE_ONEONE, or BLENDMODE_STANDARD
-            outData.blendMode = system.blendMode;
-
-            // Set the gravity of all particles
-            outData.gravity = {x: system.gravity.x, y: system.gravity.y, z: system.gravity.z};
-
-            // Angular speed, in radians
-            outData.minAngularSpeed = system.minAngularSpeed;
-            outData.maxAngularSpeed = system.maxAngularSpeed;
-
-            // Speed
-            outData.minEmitPower = system.minEmitPower;
-            outData.maxEmitPower = system.maxEmitPower;
-            outData.updateSpeed = system.updateSpeed;
-
-            
-            switch (system.particleEmitterType.getClassName()) {
-                case "BoxEmitter":
-                    outData.emitterType = "box";
-                    outData.direction1 = {x: system.direction1.x, y: system.direction1.y, z: system.direction1.z };
-                    outData.direction2 = {x: system.direction2.x, y: system.direction2.y, z: system.direction2.z };
-                    outData.minEmitBox = {x: system.minEmitBox.x, y: system.minEmitBox.y, z: system.minEmitBox.z };
-                    outData.maxEmitBox = {x: system.maxEmitBox.x, y: system.maxEmitBox.y, z: system.maxEmitBox.z };
-                    break;
-                case "SphereParticleEmitter":
-                    outData.emitterType = "sphere";
-                    outData.radius = (system.particleEmitterType as SphereParticleEmitter).radius;
-                    break;
-                case "SphereDirectedParticleEmitter":
-                    outData.emitterType = "directed_sphere";
-                    var sphereDirectedParticleEmitter = system.particleEmitterType as SphereDirectedParticleEmitter;
-                    outData.radius = sphereDirectedParticleEmitter.radius;
-                    outData.direction1 = {x: sphereDirectedParticleEmitter.direction1.x, y: sphereDirectedParticleEmitter.direction1.y, z: sphereDirectedParticleEmitter.direction1.z };
-                    outData.direction2 = {x: sphereDirectedParticleEmitter.direction2.x, y: sphereDirectedParticleEmitter.direction2.y, z: sphereDirectedParticleEmitter.direction2.z };                
-                    break;
-                case "ConeEmitter":
-                    outData.emitterType = "cone";
-                    outData.radius = (system.particleEmitterType as ConeParticleEmitter).radius;
-                    outData.angle = (system.particleEmitterType as ConeParticleEmitter).angle;
-                    break;
-                default:
-                    break;
-            }
-
-            return outData;
-        }
-    }
-
-}

+ 2 - 2
src/Particles/EmitterTypes/babylon.boxParticleEmitter.ts

@@ -91,11 +91,11 @@ module BABYLON {
         }
 
         /**
-         * Returns the string "BoxEmitter"
+         * Returns the string "BoxParticleEmitter"
          * @returns a string containing the class name
          */
         public getClassName(): string {
-            return "BoxEmitter";
+            return "BoxParticleEmitter";
         }   
         
         /**

+ 2 - 2
src/Particles/EmitterTypes/babylon.coneParticleEmitter.ts

@@ -133,11 +133,11 @@ module BABYLON {
         }     
         
         /**
-         * Returns the string "BoxEmitter"
+         * Returns the string "ConeParticleEmitter"
          * @returns a string containing the class name
          */
         public getClassName(): string {
-            return "ConeEmitter";
+            return "ConeParticleEmitter";
         }  
         
         /**

+ 2 - 0
src/Particles/EmitterTypes/babylon.sphereParticleEmitter.ts

@@ -108,6 +108,7 @@ module BABYLON {
             var serializationObject: any = {};
             serializationObject.type = this.getClassName();
             serializationObject.radius = this.radius;
+            serializationObject.radiusRange = this.radiusRange;
             serializationObject.directionRandomizer = this.directionRandomizer;
 
             return serializationObject;
@@ -119,6 +120,7 @@ module BABYLON {
          */
         public parse(serializationObject: any): void {
             this.radius = serializationObject.radius;
+            this.radiusRange = serializationObject.radiusRange;
             this.directionRandomizer = serializationObject.directionRandomizer;
         }          
     }

+ 29 - 3
src/Particles/babylon.IParticleSystem.ts

@@ -22,6 +22,10 @@ module BABYLON {
          */
         emitter: Nullable<AbstractMesh | Vector3>;
         /**
+         * Gets or sets a boolean indicating if the particles must be rendered as billboard or aligned with the direction
+         */
+        isBillboardBased: boolean;        
+        /**
          * The rendering group used by the Particle system to chose when to render.
          */
         renderingGroupId: number;
@@ -46,7 +50,7 @@ module BABYLON {
         particleTexture: Nullable<Texture>;   
         
         /**
-         * Blend mode use to render the particle, it can be either ParticleSystem.BLENDMODE_ONEONE or ParticleSystem.BLENDMODE_STANDARD.
+         * Blend mode use to render the particle, it can be either ParticleSystem.BLENDMODE_ONEONE, ParticleSystem.BLENDMODE_STANDARD or ParticleSystem.BLENDMODE_ADD.
          */
         blendMode: number;   
         
@@ -146,7 +150,28 @@ module BABYLON {
         /** 
          * Gets or sets a value indicating the time step multiplier to use in pre-warm mode (default is 1) 
          */
-        preWarmStepOffset: number;               
+        preWarmStepOffset: number;     
+        
+        /**
+         * If using a spritesheet (isAnimationSheetEnabled) defines the speed of the sprite loop (default is 1 meaning the animation will play once during the entire particle lifetime)
+         */
+        spriteCellChangeSpeed: number;
+        /**
+         * If using a spritesheet (isAnimationSheetEnabled) defines the first sprite cell to display
+         */
+        startSpriteCellID: number;
+        /**
+         * If using a spritesheet (isAnimationSheetEnabled) defines the last sprite cell to display
+         */
+        endSpriteCellID: number;
+        /**
+         * If using a spritesheet (isAnimationSheetEnabled), defines the sprite cell width to use
+         */
+        spriteCellWidth: number;
+        /**
+         * If using a spritesheet (isAnimationSheetEnabled), defines the sprite cell height to use
+         */
+        spriteCellHeight: number;        
 
         /**
          * Gets the maximum number of particles active at the same time.
@@ -232,8 +257,9 @@ module BABYLON {
          * Adds a new size gradient
          * @param gradient defines the gradient to use (between 0 and 1)
          * @param factor defines the size factor to affect to the specified gradient
+         * @param factor2 defines an additional factor used to define a range ([factor, factor2]) with main value to pick the final value from
          */
-        addSizeGradient(gradient: number, factor: number): IParticleSystem;
+        addSizeGradient(gradient: number, factor: number, factor2?: number): IParticleSystem;
         /**
          * Remove a specific size gradient
          * @param gradient defines the gradient to remove

+ 88 - 31
src/Particles/babylon.gpuParticleSystem.ts

@@ -301,7 +301,37 @@
         /**
          * Gets or sets the maximal initial rotation in radians.         
          */
-        public maxInitialRotation = 0;            
+        public maxInitialRotation = 0;   
+        
+        /**
+         * If using a spritesheet (isAnimationSheetEnabled) defines the speed of the sprite loop (default is 1 meaning the animation will play once during the entire particle lifetime)
+         */
+        public spriteCellChangeSpeed = 1;
+        /**
+         * If using a spritesheet (isAnimationSheetEnabled) defines the first sprite cell to display
+         */
+        public startSpriteCellID = 0;
+        /**
+         * If using a spritesheet (isAnimationSheetEnabled) defines the last sprite cell to display
+         */
+        public endSpriteCellID = 0;
+        /**
+         * If using a spritesheet (isAnimationSheetEnabled), defines the sprite cell width to use
+         */
+        public spriteCellWidth = 0;
+        /**
+         * If using a spritesheet (isAnimationSheetEnabled), defines the sprite cell height to use
+         */
+        public spriteCellHeight = 0;
+                
+        private _isAnimationSheetEnabled: boolean;
+
+        /**
+         * Gets whether an animation sprite sheet is enabled or not on the particle system
+         */
+        public get isAnimationSheetEnabled(): boolean {
+            return this._isAnimationSheetEnabled;
+        }        
 
         /**
          * Is this system ready to be used/rendered
@@ -483,7 +513,7 @@
 
             let sizeGradient = new FactorGradient();
             sizeGradient.gradient = gradient;
-            sizeGradient.factor = factor;
+            sizeGradient.factor1 = factor;
             this._sizeGradients.push(sizeGradient);
 
             this._sizeGradients.sort((a, b) => {
@@ -538,18 +568,23 @@
          * Instantiates a GPU particle system.
          * Particles are often small sprites used to simulate hard-to-reproduce phenomena like fire, smoke, water, or abstract visual effects like magic glitter and faery dust.
          * @param name The name of the particle system
-         * @param capacity The max number of particles alive at the same time
+         * @param options The options used to create the system
          * @param scene The scene the particle system belongs to
+         * @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) {
+                    }>, scene: Scene, isAnimationSheetEnabled: boolean = false) {
             this.id = name;
             this.name = name;
             this._scene = scene || Engine.LastCreatedScene;
             this._engine = this._scene.getEngine();
 
+            if (!options.randomTextureSize) {
+                delete options.randomTextureSize;
+            }
+
             let fullOptions = {
                 capacity: 50000,
                 randomTextureSize: this._engine.getCaps().maxTextureSize,
@@ -564,14 +599,15 @@
             this._capacity = fullOptions.capacity;
             this._activeCount = fullOptions.capacity;
             this._currentActiveCount = 0;
+            this._isAnimationSheetEnabled = isAnimationSheetEnabled;
 
             this._scene.particleSystems.push(this);
 
             this._updateEffectOptions = {
-                attributes: ["position", "age", "life", "seed", "size", "color", "direction", "initialDirection", "angle", "initialSize"],
+                attributes: ["position", "age", "life", "seed", "size", "color", "direction", "initialDirection", "angle", "cellIndex"],
                 uniformsNames: ["currentCount", "timeDelta", "emitterWM", "lifeTime", "color1", "color2", "sizeRange", "scaleRange","gravity", "emitPower",
                                 "direction1", "direction2", "minEmitBox", "maxEmitBox", "radius", "directionRandomizer", "height", "coneAngle", "stopFactor", 
-                                "angleRange", "radiusRange"],
+                                "angleRange", "radiusRange", "cellInfos"],
                 uniformBuffersNames: [],
                 samplers:["randomSampler", "randomSampler2", "sizeGradientSampler"],
                 defines: "",
@@ -620,10 +656,6 @@
             updateVertexBuffers["seed"] = source.createVertexBuffer("seed", 5, 4);
             updateVertexBuffers["size"] = source.createVertexBuffer("size", 9, 3);
             let offset = 12;
-            if (this._sizeGradientsTexture) {
-                updateVertexBuffers["initialSize"] = source.createVertexBuffer("initialSize", offset, 3);
-                offset += 3;
-            }
 
             if (!this._colorGradientsTexture) {
                 updateVertexBuffers["color"] = source.createVertexBuffer("color", offset, 4);
@@ -639,6 +671,12 @@
             }
 
             updateVertexBuffers["angle"] = source.createVertexBuffer("angle", offset, 2);
+            offset += 2;
+
+            if (this._isAnimationSheetEnabled) {
+                updateVertexBuffers["cellIndex"] = source.createVertexBuffer("cellIndex", offset, 1);
+                offset += 1;
+            }            
            
             let vao = this._engine.recordVertexArrayObject(updateVertexBuffers, null, this._updateEffect);
             this._engine.bindArrayBuffer(null);
@@ -654,9 +692,6 @@
             renderVertexBuffers["size"] = source.createVertexBuffer("size", 9, 3, this._attributesStrideSize, true);      
             
             let offset = 12;
-            if (this._sizeGradientsTexture) {
-                offset += 3;
-            }
 
             if (!this._colorGradientsTexture) {
                 renderVertexBuffers["color"] = source.createVertexBuffer("color", offset, 4, this._attributesStrideSize, true);
@@ -670,6 +705,12 @@
                 offset += 3;
             }
             renderVertexBuffers["angle"] = source.createVertexBuffer("angle", offset, 2, this._attributesStrideSize, true);
+            offset += 2;
+
+            if (this._isAnimationSheetEnabled) {
+                renderVertexBuffers["cellIndex"] = source.createVertexBuffer("cellIndex", offset, 1, this._attributesStrideSize, true);
+                offset += 1;
+            }               
 
             renderVertexBuffers["offset"] = spriteSource.createVertexBuffer("offset", 0, 2);
             renderVertexBuffers["uv"] = spriteSource.createVertexBuffer("uv", 2, 2);
@@ -696,9 +737,9 @@
                 this._attributesStrideSize -= 4;
             }
 
-            if (this._sizeGradientsTexture) {
-                this._attributesStrideSize += 3;
-            }
+            if (this._isAnimationSheetEnabled) {
+                this._attributesStrideSize += 1;
+            }            
 
             for (var particleIndex = 0; particleIndex < this._capacity; particleIndex++) {
                 // position
@@ -721,12 +762,6 @@
                 data.push(0.0);
                 data.push(0.0);
 
-                if (this._sizeGradientsTexture) {
-                    data.push(0.0);
-                    data.push(0.0);
-                    data.push(0.0);  
-                }                
-
                 if (!this._colorGradientsTexture) {
                     // color
                     data.push(0.0);
@@ -750,6 +785,10 @@
                 // angle
                 data.push(0.0);  
                 data.push(0.0); 
+
+                if (this._isAnimationSheetEnabled) {
+                    data.push(0.0); 
+                }                
             }
 
             // Sprite data
@@ -793,7 +832,11 @@
             
             if (this._sizeGradientsTexture) {
                 defines += "\n#define SIZEGRADIENTS";
-            }                 
+            }     
+            
+            if (this.isAnimationSheetEnabled) {
+                defines += "\n#define ANIMATESHEET";
+            }             
 
             if (this._updateEffect && this._updateEffectOptions.defines === defines) {
                 return;
@@ -801,10 +844,6 @@
 
             this._updateEffectOptions.transformFeedbackVaryings = ["outPosition", "outAge", "outLife", "outSeed", "outSize"];           
 
-            if (this._sizeGradientsTexture) {
-                this._updateEffectOptions.transformFeedbackVaryings.push("outInitialSize");
-            }
-
             if (!this._colorGradientsTexture) {
                 this._updateEffectOptions.transformFeedbackVaryings.push("outColor");
             }
@@ -817,6 +856,10 @@
 
             this._updateEffectOptions.transformFeedbackVaryings.push("outAngle");
 
+            if (this.isAnimationSheetEnabled) {
+                this._updateEffectOptions.transformFeedbackVaryings.push("outCellIndex");
+            }               
+
             this._updateEffectOptions.defines = defines;
             this._updateEffect = new Effect("gpuUpdateParticles", this._updateEffectOptions, this._scene.getEngine());   
         }
@@ -836,13 +879,17 @@
                 defines += "\n#define COLORGRADIENTS";
             }   
 
+            if (this.isAnimationSheetEnabled) {
+                defines += "\n#define ANIMATESHEET";
+            }                 
+
             if (this._renderEffect && this._renderEffect.defines === defines) {
                 return;
             }
 
             this._renderEffect = new Effect("gpuRenderParticles", 
-                                            ["position", "age", "life", "size", "color", "offset", "uv", "initialDirection", "angle"], 
-                                            ["view", "projection", "colorDead", "invView", "vClipPlane"], 
+                                            ["position", "age", "life", "size", "color", "offset", "uv", "initialDirection", "angle", "cellIndex"], 
+                                            ["view", "projection", "colorDead", "invView", "vClipPlane", "sheetInfos"], 
                                             ["textureSampler", "colorGradientSampler"], this._scene.getEngine(), defines);
         }        
 
@@ -873,7 +920,7 @@
                 var ratio = x / textureWidth;
 
                 Tools.GetCurrentGradient(ratio, this._sizeGradients, (currentGradient, nextGradient, scale) => {
-                    data[x] = Scalar.Lerp((<FactorGradient>currentGradient).factor, (<FactorGradient>nextGradient).factor, scale);
+                    data[x] = Scalar.Lerp((<FactorGradient>currentGradient).factor1, (<FactorGradient>nextGradient).factor1, scale);
                 });
             }
 
@@ -984,6 +1031,9 @@
             if (this.particleEmitterType) {
                 this.particleEmitterType.applyToShader(this._updateEffect);
             }
+            if (this._isAnimationSheetEnabled) {
+                this._updateEffect.setFloat3("cellInfos", this.startSpriteCellID, this.endSpriteCellID, this.spriteCellChangeSpeed);
+            }            
 
             let emitterWM: Matrix;
             if ((<AbstractMesh>this.emitter).position) {
@@ -1020,6 +1070,10 @@
                     this._renderEffect.setDirectColor4("colorDead", this.colorDead);
                 }
 
+                if (this._isAnimationSheetEnabled && this.particleTexture) {
+                    let baseSize = this.particleTexture.getBaseSize();
+                    this._renderEffect.setFloat3("sheetInfos", this.spriteCellWidth / baseSize.width, this.spriteCellHeight / baseSize.height, baseSize.width / this.spriteCellWidth);
+                }
 
                 if (this._scene.clipPlane) {
                     var clipPlane = this._scene.clipPlane;
@@ -1179,6 +1233,7 @@
             var serializationObject: any = {};
 
             ParticleSystem._Serialize(serializationObject, this);
+            serializationObject.activeParticleCount = this.activeParticleCount;
 
             return serializationObject;            
         }
@@ -1194,7 +1249,9 @@
             var name = parsedParticleSystem.name;
             var particleSystem = new GPUParticleSystem(name, {capacity: parsedParticleSystem.capacity, randomTextureSize: parsedParticleSystem.randomTextureSize}, scene);
 
-            particleSystem.activeParticleCount = parsedParticleSystem.activeParticleCount;
+            if (parsedParticleSystem.activeParticleCount) {
+                particleSystem.activeParticleCount = parsedParticleSystem.activeParticleCount;
+            }
             ParticleSystem._Parse(parsedParticleSystem, particleSystem, scene, rootUrl);
 
             return particleSystem;

+ 23 - 43
src/Particles/babylon.particle.ts

@@ -60,13 +60,12 @@
          */
         public cellIndex: number = 0;  
 
-        private _currentFrameCounter = 0;
-
         /** @hidden */
         public _initialDirection: Nullable<Vector3>;
 
         /** @hidden */
-        public _initialSize: number;
+        public _initialStartSpriteCellID: number;
+        public _initialEndSpriteCellID: number;
 
         /** @hidden */
         public _currentColorGradient: Nullable<ColorGradient>;
@@ -75,6 +74,13 @@
         /** @hidden */
         public _currentColor2 = new Color4(0, 0, 0, 0);
 
+        /** @hidden */
+        public _currentSizeGradient: Nullable<FactorGradient>;
+        /** @hidden */
+        public _currentSize1 = 0;
+        /** @hidden */
+        public _currentSize2 = 0;        
+
         /**
          * Creates a new instance Particle
          * @param particleSystem the particle system the particle belongs to
@@ -93,51 +99,16 @@
 
         private updateCellInfoFromSystem(): void {
             this.cellIndex = this.particleSystem.startSpriteCellID;
-
-            if (this.particleSystem.spriteCellChangeSpeed == 0) {
-                this.updateCellIndex = this._updateCellIndexWithSpeedCalculated;
-            }
-            else {
-                this.updateCellIndex = this._updateCellIndexWithCustomSpeed;
-            }
         }
 
         /**
-         * Defines how the sprite cell index is updated for the particle. This is 
-         * defined as a callback.
+         * Defines how the sprite cell index is updated for the particle
          */
-        public updateCellIndex: (scaledUpdateSpeed: number) => void;
-
-        private _updateCellIndexWithSpeedCalculated(scaledUpdateSpeed: number): void {
-            //   (ageOffset / scaledUpdateSpeed) / available cells
-            var numberOfScaledUpdatesPerCell = ((this.lifeTime - this.age) / scaledUpdateSpeed) / (this.particleSystem.endSpriteCellID + 1 - this.cellIndex);
-
-            this._currentFrameCounter += scaledUpdateSpeed;
-            if (this._currentFrameCounter >= numberOfScaledUpdatesPerCell * scaledUpdateSpeed) {
-                this._currentFrameCounter = 0;
-                this.cellIndex++;
-                if (this.cellIndex > this.particleSystem.endSpriteCellID) {
-                    this.cellIndex = this.particleSystem.endSpriteCellID;
-                }
-            }
-        }
+        public updateCellIndex(): void {
+            let dist = (this._initialEndSpriteCellID - this._initialStartSpriteCellID);
+            let ratio = Scalar.Clamp(((this.age * this.particleSystem.spriteCellChangeSpeed) / this.lifeTime) % this.lifeTime);
 
-        private _updateCellIndexWithCustomSpeed(): void {
-            if (this._currentFrameCounter >= this.particleSystem.spriteCellChangeSpeed) {
-                this.cellIndex++;
-                this._currentFrameCounter = 0;
-                if (this.cellIndex > this.particleSystem.endSpriteCellID) {
-                    if (this.particleSystem.spriteCellLoop) {
-                        this.cellIndex = this.particleSystem.startSpriteCellID;
-                    }
-                    else {
-                        this.cellIndex = this.particleSystem.endSpriteCellID;
-                    }
-                }
-            }
-            else {
-                this._currentFrameCounter++;
-            }
+            this.cellIndex = this._initialStartSpriteCellID + (ratio * dist) | 0;
         }
 
         /**
@@ -171,6 +142,15 @@
                 other._currentColor1.copyFrom(this._currentColor1);
                 other._currentColor2.copyFrom(this._currentColor2);
             }
+            if (this._currentSizeGradient) {
+                other._currentSizeGradient = this._currentSizeGradient;
+                other._currentSize1 = this._currentSize1;
+                other._currentSize2 = this._currentSize2;
+            }
+            if (this.particleSystem.isAnimationSheetEnabled) {
+                other._initialStartSpriteCellID = this._initialStartSpriteCellID;
+                other._initialEndSpriteCellID = this._initialEndSpriteCellID;
+            }
         }
     }
 } 

+ 55 - 0
src/Particles/babylon.particleHelper.ts

@@ -0,0 +1,55 @@
+module BABYLON {
+    /**
+     * This class is made for on one-liner static method to help creating particle system set.
+     */
+    export class ParticleHelper {
+        /**
+         * Gets or sets base Assets URL
+         */
+        public static BaseAssetsUrl = "https://assets.babylonjs.com/particles";
+
+        /**
+         * This is the main static method (one-liner) of this helper to create different particle systems
+         * @param type This string represents the type to the particle system to create
+         * @param scene The scene where the particle system should live
+         * @param gpu If the system will use gpu
+         * @returns the ParticleSystemSet created
+         */
+        public static CreateAsync(type: string, scene: Nullable<Scene> = Engine.LastCreatedScene, gpu: boolean = false): Promise<ParticleSystemSet> {
+            
+            return new Promise((resolve, reject) => {
+                if (!scene) {
+                    scene = Engine.LastCreatedScene;;
+                }
+
+                if (gpu && !GPUParticleSystem.IsSupported) {
+                    return reject("Particle system with GPU is not supported.");
+                }
+
+                Tools.LoadFile(`${ParticleHelper.BaseAssetsUrl}/systems/${type}.json`, (data, response) => {
+                    const newData = JSON.parse(data.toString());
+                    return resolve(ParticleSystemSet.Parse(newData, scene!, gpu));
+                }, undefined, undefined, undefined, (req, exception) => {
+                    return reject(`An error occured while the creation of your particle system. Check if your type '${type}' exists.`);
+                });
+
+            });
+        }
+
+        /**
+         * Static function used to export a particle system to a ParticleSystemSet variable.
+         * Please note that the emitter shape is not exported
+         * @param system defines the particle systems to export
+         */
+        public static ExportSet(systems: ParticleSystem[]): ParticleSystemSet {
+            var set = new ParticleSystemSet();
+
+            for (var system of systems) {
+                set.systems.push(system);
+            }
+
+            return set;
+        }
+    }
+
+}

+ 166 - 67
src/Particles/babylon.particleSystem.ts

@@ -186,6 +186,7 @@
 
         private _colorGradients: Nullable<Array<ColorGradient>> = null;
         private _sizeGradients: Nullable<Array<FactorGradient>> = null;
+        private _lifeTimeGradients: Nullable<Array<FactorGradient>> = null;
 
         /**
          * Gets the current list of color gradients.
@@ -206,6 +207,15 @@
         }        
 
         /**
+         * Gets the current list of life time gradients.
+         * You must use addLifeTimeGradient and removeLifeTimeGradient to udpate this list
+         * @returns the list of life time gradients
+         */
+        public getLifeTimeGradients(): Nullable<Array<FactorGradient>> {
+            return this._lifeTimeGradients;
+        }          
+
+        /**
          * Random direction of each particle after it has been emitted, between direction1 and direction2 vectors.
          * This only works when particleEmitterTyps is a BoxParticleEmitter
          */
@@ -313,19 +323,15 @@
         public startPositionFunction: (worldMatrix: Matrix, positionToUpdate: Vector3, particle: Particle) => void;
 
         /**
-         * If using a spritesheet (isAnimationSheetEnabled), defines if the sprite animation should loop between startSpriteCellID and endSpriteCellID or not
+         * If using a spritesheet (isAnimationSheetEnabled) defines the speed of the sprite loop (default is 1 meaning the animation will play once during the entire particle lifetime)
          */
-        public spriteCellLoop = true;
+        public spriteCellChangeSpeed = 1;
         /**
-         * If using a spritesheet (isAnimationSheetEnabled) and spriteCellLoop defines the speed of the sprite loop
-         */
-        public spriteCellChangeSpeed = 0;
-        /**
-         * If using a spritesheet (isAnimationSheetEnabled) and spriteCellLoop defines the first sprite cell to display
+         * If using a spritesheet (isAnimationSheetEnabled) defines the first sprite cell to display
          */
         public startSpriteCellID = 0;
         /**
-         * If using a spritesheet (isAnimationSheetEnabled) and spriteCellLoop defines the last sprite cell to display
+         * If using a spritesheet (isAnimationSheetEnabled) defines the last sprite cell to display
          */
         public endSpriteCellID = 0;
         /**
@@ -517,36 +523,33 @@
                         particle.direction.addInPlace(this._scaledGravity);
 
                         // Gradient
-                        if (this._sizeGradients && this._sizeGradients.length > 0) {
+                        if (this._sizeGradients && this._sizeGradients.length > 0) {                  
                             Tools.GetCurrentGradient(ratio, this._sizeGradients, (currentGradient, nextGradient, scale) => {
-                                particle.size = particle._initialSize * Scalar.Lerp((<FactorGradient>currentGradient).factor, (<FactorGradient>nextGradient).factor, 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._isAnimationSheetEnabled) {
-                            particle.updateCellIndex(this._scaledUpdateSpeed);
+                            particle.updateCellIndex();
                         }
                     }
                 }
             }
         }
 
-        /**
-         * Adds a new size gradient
-         * @param gradient defines the gradient to use (between 0 and 1)
-         * @param factor defines the size factor to affect to the specified gradient
-         */
-        public addSizeGradient(gradient: number, factor: number): ParticleSystem {
-            if (!this._sizeGradients) {
-                this._sizeGradients = [];
-            }
+        private _addFactorGradient(factorGradients: FactorGradient[], gradient: number, factor: number, factor2?: number) {
+            let newGradient = new FactorGradient();
+            newGradient.gradient = gradient;
+            newGradient.factor1 = factor;
+            newGradient.factor2 = factor2;
+            factorGradients.push(newGradient);
 
-            let sizeGradient = new FactorGradient();
-            sizeGradient.gradient = gradient;
-            sizeGradient.factor = factor;
-            this._sizeGradients.push(sizeGradient);
-
-            this._sizeGradients.sort((a, b) => {
+            factorGradients.sort((a, b) => {
                 if (a.gradient < b.gradient) {
                     return -1;
                 } else if (a.gradient > b.gradient) {
@@ -554,28 +557,72 @@
                 }
 
                 return 0;
-            });
+            });            
+        }
+
+        private _removeFactorGradient(factorGradients: Nullable<FactorGradient[]>, gradient: number) {
+            if (!factorGradients) {
+                return;
+            }
+
+            let index = 0;
+            for (var factorGradient of factorGradients) {
+                if (factorGradient.gradient === gradient) {
+                    factorGradients.splice(index, 1);
+                    break;
+                }
+                index++;
+            }
+        }
+
+        /**
+         * Adds a new life time gradient
+         * @param gradient defines the gradient to use (between 0 and 1)
+         * @param factor defines the life time factor to affect to the specified gradient         
+         * @param factor2 defines an additional factor used to define a range ([factor, factor2]) with main value to pick the final value from
+         */
+        public addLifeTimeGradient(gradient: number, factor: number, factor2?: number): ParticleSystem {
+            if (!this._lifeTimeGradients) {
+                this._lifeTimeGradients = [];
+            }
+
+            this._addFactorGradient(this._lifeTimeGradients, gradient, factor, factor2);
 
             return this;
         }
 
         /**
-         * Remove a specific size gradient
+         * Remove a specific life time gradient
          * @param gradient defines the gradient to remove
          */
-        public removeSizeGradient(gradient: number): ParticleSystem {
+        public removeLifeTimeGradient(gradient: number): ParticleSystem {
+            this._removeFactorGradient(this._lifeTimeGradients, gradient);
+
+            return this;
+        }       
+
+        /**
+         * Adds a new size gradient
+         * @param gradient defines the gradient to use (between 0 and 1)
+         * @param factor defines the size factor to affect to the specified gradient         
+         * @param factor2 defines an additional factor used to define a range ([factor, factor2]) with main value to pick the final value from
+         */
+        public addSizeGradient(gradient: number, factor: number, factor2?: number): ParticleSystem {
             if (!this._sizeGradients) {
-                return this;
+                this._sizeGradients = [];
             }
 
-            let index = 0;
-            for (var sizeGradient of this._sizeGradients) {
-                if (sizeGradient.gradient === gradient) {
-                    this._sizeGradients.splice(index, 1);
-                    break;
-                }
-                index++;
-            }
+            this._addFactorGradient(this._sizeGradients, gradient, factor, factor2);
+
+            return this;
+        }
+
+        /**
+         * Remove a specific size gradient
+         * @param gradient defines the gradient to remove
+         */
+        public removeSizeGradient(gradient: number): ParticleSystem {
+            this._removeFactorGradient(this._sizeGradients, gradient);
 
             return this;
         }        
@@ -896,7 +943,7 @@
             subSystem.start();
         }
 
-        // end of sub system methods
+        // End of sub system methods
 
         private _update(newParticles: number): void {
             // Update current
@@ -925,6 +972,7 @@
 
                 this._particles.push(particle);
 
+                // Emitter
                 let emitPower = Scalar.RandomRange(this.minEmitPower, this.maxEmitPower);
 
                 if (this.startPositionFunction) {
@@ -953,15 +1001,43 @@
 
                 particle.direction.scaleInPlace(emitPower);
 
-                particle.lifeTime = Scalar.RandomRange(this.minLifeTime, this.maxLifeTime);
+                // Life time
+                if (this.targetStopDuration && this._lifeTimeGradients && this._lifeTimeGradients.length > 0) {
+                    let ratio = Scalar.Clamp(this._actualFrame / this.targetStopDuration);
+                    Tools.GetCurrentGradient(ratio, this._lifeTimeGradients, (currentGradient, nextGradient, scale) => {
+                        let factorGradient1 = (<FactorGradient>currentGradient);
+                        let factorGradient2 = (<FactorGradient>nextGradient);
+                        let lifeTime1 = factorGradient1.getFactor(); 
+                        let lifeTime2 = factorGradient2.getFactor(); 
+                        let gradient = (ratio - factorGradient1.gradient) / (factorGradient2.gradient - factorGradient1.gradient);
+                        particle.lifeTime = Scalar.Lerp(lifeTime1, lifeTime2, gradient);
+                    });
+                } else {
+                    particle.lifeTime = Scalar.RandomRange(this.minLifeTime, this.maxLifeTime);
+                }
 
-                particle.size = Scalar.RandomRange(this.minSize, this.maxSize);
-                particle._initialSize = particle.size;
+                // Size
+                if (!this._sizeGradients || this._sizeGradients.length === 0) {
+                    particle.size = Scalar.RandomRange(this.minSize, this.maxSize);
+                } else {
+                    particle._currentSizeGradient = this._sizeGradients[0];
+                    particle._currentSize1 = particle._currentSizeGradient.getFactor();
+                    particle.size = particle._currentSize1;
+
+                    if (this._sizeGradients.length > 1) {
+                        particle._currentSize2 = this._sizeGradients[1].getFactor();
+                    } else {
+                        particle._currentSize2 = particle._currentSize1;
+                    }
+                }
+                // Size and scale
                 particle.scale.copyFromFloats(Scalar.RandomRange(this.minScaleX, this.maxScaleX), Scalar.RandomRange(this.minScaleY, this.maxScaleY));
-                particle.angularSpeed = Scalar.RandomRange(this.minAngularSpeed, this.maxAngularSpeed);
 
+                // Angle
+                particle.angularSpeed = Scalar.RandomRange(this.minAngularSpeed, this.maxAngularSpeed);
                 particle.angle = Scalar.RandomRange(this.minInitialRotation, this.maxInitialRotation);
 
+                // Color
                 if (!this._colorGradients || this._colorGradients.length === 0) {
                     var step = Scalar.RandomRange(0, 1.0);
 
@@ -980,6 +1056,12 @@
                         particle._currentColor2.copyFrom(particle.color);
                     }
                 }
+
+                // Sheet
+                if (this._isAnimationSheetEnabled) {
+                    particle._initialStartSpriteCellID = this.startSpriteCellID;
+                    particle._initialEndSpriteCellID = this.endSpriteCellID;
+                }
             }
         }
 
@@ -1375,13 +1457,6 @@
             serializationObject.customShader = this.customShader;
             serializationObject.preventAutoStart = this.preventAutoStart;
 
-            serializationObject.startSpriteCellID = this.startSpriteCellID;
-            serializationObject.endSpriteCellID = this.endSpriteCellID;
-            serializationObject.spriteCellLoop = this.spriteCellLoop;
-            serializationObject.spriteCellChangeSpeed = this.spriteCellChangeSpeed;
-            serializationObject.spriteCellWidth = this.spriteCellWidth;
-            serializationObject.spriteCellHeight = this.spriteCellHeight;
-
             serializationObject.isAnimationSheetEnabled = this._isAnimationSheetEnabled;
 
             return serializationObject;
@@ -1416,6 +1491,8 @@
             Animation.AppendSerializedAnimations(particleSystem, serializationObject);
 
             // Particle system
+            serializationObject.renderingGroupId = particleSystem.renderingGroupId;
+            serializationObject.isBillboardBased = particleSystem.isBillboardBased;
             serializationObject.minAngularSpeed = particleSystem.minAngularSpeed;
             serializationObject.maxAngularSpeed = particleSystem.maxAngularSpeed;
             serializationObject.minSize = particleSystem.minSize;
@@ -1440,6 +1517,11 @@
             serializationObject.preWarmStepOffset = particleSystem.preWarmStepOffset;
             serializationObject.minInitialRotation = particleSystem.minInitialRotation;
             serializationObject.maxInitialRotation = particleSystem.maxInitialRotation;
+            serializationObject.startSpriteCellID = particleSystem.startSpriteCellID;
+            serializationObject.endSpriteCellID = particleSystem.endSpriteCellID;
+            serializationObject.spriteCellChangeSpeed = particleSystem.spriteCellChangeSpeed;
+            serializationObject.spriteCellWidth = particleSystem.spriteCellWidth;
+            serializationObject.spriteCellHeight = particleSystem.spriteCellHeight;            
 
             let colorGradients = particleSystem.getColorGradients();
             if (colorGradients) {
@@ -1462,10 +1544,17 @@
             if (sizeGradients) {
                 serializationObject.sizeGradients = [];
                 for (var sizeGradient of sizeGradients) {
-                    serializationObject.sizeGradients.push({
+
+                    var serializedGradient: any = {
                         gradient: sizeGradient.gradient,
-                        factor: sizeGradient.factor
-                    })
+                        factor1: sizeGradient.factor1
+                    };
+
+                    if (sizeGradient.factor2 !== undefined) {
+                        serializedGradient.factor2 = sizeGradient.factor2;
+                    }
+
+                    serializationObject.sizeGradients.push(serializedGradient);
                 }
             }            
 
@@ -1480,12 +1569,24 @@
             }
 
             // Emitter
-            if (parsedParticleSystem.emitterId) {
+            if (parsedParticleSystem.emitterId === undefined) {
+                particleSystem.emitter = Vector3.Zero();
+            }
+             else if (parsedParticleSystem.emitterId) {
                 particleSystem.emitter = scene.getLastMeshByID(parsedParticleSystem.emitterId);
             } else {
                 particleSystem.emitter = Vector3.FromArray(parsedParticleSystem.emitter);
             }
 
+            // Misc.
+            if (parsedParticleSystem.renderingGroupId !== undefined) {
+                particleSystem.renderingGroupId = parsedParticleSystem.renderingGroupId;
+            }
+
+            if (parsedParticleSystem.isBillboardBased !== undefined) {
+                particleSystem.isBillboardBased = parsedParticleSystem.isBillboardBased;
+            }
+
             // Animations
             if (parsedParticleSystem.animations) {
                 for (var animationIndex = 0; animationIndex < parsedParticleSystem.animations.length; animationIndex++) {
@@ -1543,7 +1644,7 @@
 
             if (parsedParticleSystem.sizeGradients) {
                 for (var sizeGradient of parsedParticleSystem.sizeGradients) {
-                    particleSystem.addSizeGradient(sizeGradient.gradient, sizeGradient.factor);
+                    particleSystem.addSizeGradient(sizeGradient.gradient, sizeGradient.factor1 !== undefined ?  sizeGradient.factor1 : sizeGradient.factor, sizeGradient.factor2);
                 }
             }       
             
@@ -1551,16 +1652,18 @@
             let emitterType: IParticleEmitterType;
             if (parsedParticleSystem.particleEmitterType) {
                 switch (parsedParticleSystem.particleEmitterType.type) {
-                    case "SphereEmitter":
+                    case "SphereParticleEmitter":
                         emitterType = new SphereParticleEmitter();
                         break;
                     case "SphereDirectedParticleEmitter":
                         emitterType = new SphereDirectedParticleEmitter();
                         break;
                     case "ConeEmitter":
+                    case "ConeParticleEmitter":
                         emitterType = new ConeParticleEmitter();
                         break;
                     case "BoxEmitter":
+                    case "BoxParticleEmitter":
                     default:
                         emitterType = new BoxParticleEmitter();
                         break;                                                
@@ -1572,6 +1675,13 @@
                 emitterType.parse(parsedParticleSystem);
             }
             particleSystem.particleEmitterType = emitterType;
+
+            // Animation sheet
+            particleSystem.startSpriteCellID = parsedParticleSystem.startSpriteCellID;
+            particleSystem.endSpriteCellID = parsedParticleSystem.endSpriteCellID;
+            particleSystem.spriteCellWidth = parsedParticleSystem.spriteCellWidth;
+            particleSystem.spriteCellHeight = parsedParticleSystem.spriteCellHeight;
+            particleSystem.spriteCellChangeSpeed = parsedParticleSystem.spriteCellChangeSpeed;
         }
 
         /**
@@ -1602,20 +1712,9 @@
                 particleSystem.preventAutoStart = parsedParticleSystem.preventAutoStart;
             }
 
-            particleSystem.minEmitBox = Vector3.FromArray(parsedParticleSystem.minEmitBox);
-            particleSystem.maxEmitBox = Vector3.FromArray(parsedParticleSystem.maxEmitBox);
-            particleSystem.direction1 = Vector3.FromArray(parsedParticleSystem.direction1);
-            particleSystem.direction2 = Vector3.FromArray(parsedParticleSystem.direction2);
-
             ParticleSystem._Parse(parsedParticleSystem, particleSystem, scene, rootUrl);
 
             particleSystem.textureMask = Color4.FromArray(parsedParticleSystem.textureMask);
-            particleSystem.startSpriteCellID = parsedParticleSystem.startSpriteCellID;
-            particleSystem.endSpriteCellID = parsedParticleSystem.endSpriteCellID;
-            particleSystem.spriteCellLoop = parsedParticleSystem.spriteCellLoop;
-            particleSystem.spriteCellChangeSpeed = parsedParticleSystem.spriteCellChangeSpeed;
-            particleSystem.spriteCellWidth = parsedParticleSystem.spriteCellWidth;
-            particleSystem.spriteCellHeight = parsedParticleSystem.spriteCellHeight;
 
             if (!particleSystem.preventAutoStart) {
                 particleSystem.start();

+ 137 - 0
src/Particles/babylon.particleSystemSet.ts

@@ -0,0 +1,137 @@
+module BABYLON {
+
+    /** Internal class used to store shapes for emitters */
+    class ParticleSystemSetEmitterCreationOptions {
+        public kind: string;
+        public options: any;
+        public renderingGroupId: number;
+    }
+
+    /**
+     * Represents a set of particle systems working together to create a specific effect
+     */
+    export class ParticleSystemSet implements IDisposable {
+        private _emitterCreationOptions: ParticleSystemSetEmitterCreationOptions;
+        private _emitterMesh: Nullable<Mesh>;
+
+        /**
+         * Gets the particle system list
+         */
+        public systems = new Array<IParticleSystem>();
+
+        /**
+         * Gets the emitter mesh used with this set
+         */
+        public get emitterMesh(): Nullable<Mesh> {
+            return this._emitterMesh;
+        }
+
+        /**
+         * Creates a new emitter mesh as a sphere
+         * @param options defines the options used to create the sphere
+         * @param renderingGroupId defines the renderingGroupId to use for the sphere
+         * @param scene defines the hosting scene
+         */
+        public setEmitterAsSphere(options: {diameter: number, segments: number, color: Color3} , renderingGroupId: number, scene: Scene) {
+            if (this._emitterMesh) {
+                this._emitterMesh.dispose();
+            }
+
+            this._emitterCreationOptions = {
+                kind: "Sphere",
+                options: options,
+                renderingGroupId: renderingGroupId
+            }
+
+            this._emitterMesh = MeshBuilder.CreateSphere("emitterSphere", {diameter: options.diameter, segments: options.segments}, scene);
+            this._emitterMesh.renderingGroupId = renderingGroupId;
+
+            var material = new BABYLON.StandardMaterial("emitterSphereMaterial", scene)
+            material.emissiveColor = options.color;    
+            this._emitterMesh.material = material;     
+            
+            for (var system of this.systems) {
+                system.emitter = this._emitterMesh;
+            }
+        }
+
+        /**
+         * Starts all particle systems of the set
+         * @param emitter defines an optional mesh to use as emitter for the particle systems
+         */
+        public start(emitter?: AbstractMesh): void {
+            for (var system of this.systems) {
+                if (emitter) {
+                    system.emitter = emitter;
+                }
+                system.start();
+            }
+        }
+
+        /**
+         * Release all associated resources
+         */
+        public dispose(): void {
+            for (var system of this.systems) {
+                system.dispose();
+            }
+
+            this.systems = [];
+
+            if (this._emitterMesh) {
+                this._emitterMesh.dispose();
+                this._emitterMesh = null;
+            }
+        }
+
+        /**
+         * Serialize the set into a JSON compatible object
+         * @returns a JSON compatible representation of the set
+         */
+        public serialize(): any {
+            var result:any = {};
+
+            result.systems = [];
+            for (var system of this.systems) {
+                result.systems.push(system.serialize());
+            }
+
+            if (this._emitterMesh) {
+                result.emitter = this._emitterCreationOptions;
+            }
+
+            return result;
+        }
+
+        /** 
+         * Parse a new ParticleSystemSet from a serialized source
+         * @param data defines a JSON compatible representation of the set
+         * @param scene defines the hosting scene
+         * @param gpu defines if we want GPU particles or CPU particles
+         * @returns a new ParticleSystemSet
+         */
+        public static Parse(data: any, scene: Scene, gpu = false): ParticleSystemSet {
+            var result = new ParticleSystemSet();
+            var rootUrl = ParticleHelper.BaseAssetsUrl + "/textures/";
+
+            for (var system of data.systems) {
+                result.systems.push(gpu ? GPUParticleSystem.Parse(system, scene, rootUrl) : ParticleSystem.Parse(system, scene, rootUrl));
+            }
+
+            if (data.emitter) {
+                let options = data.emitter.options;
+                switch (data.emitter.kind) {                    
+                    case "Sphere":
+                        result.setEmitterAsSphere({
+                            diameter: options.diameter, 
+                            segments: options.segments,
+                            color: Color3.FromArray(options.color)
+                        }, data.emitter.renderingGroupId, scene);
+                        break;
+                }
+            }
+
+            return result;
+        }
+    }
+}

+ 6 - 6
src/PostProcess/RenderPipeline/Pipelines/babylon.defaultRenderingPipeline.ts

@@ -5,7 +5,7 @@
      */
     export class DefaultRenderingPipeline extends PostProcessRenderPipeline implements IDisposable, IAnimatable {
         private _scene: Scene;
-        private _originalCameras:Array<Camera> = [];
+        private _camerasToBeAttached:Array<Camera> = [];
         /**
 		 * ID of the sharpen post process,
 		 */
@@ -367,7 +367,7 @@
         constructor(name: string = "", hdr: boolean = true, scene: Scene = BABYLON.Engine.LastCreatedScene!, cameras?: Camera[], automaticBuild = true) {
             super(scene.getEngine(), name);
             this._cameras = cameras ||  scene.cameras;
-            this._originalCameras = this._cameras.slice();
+            this._camerasToBeAttached = this._cameras.slice();
 
             this._buildAllowed = automaticBuild;
 
@@ -468,7 +468,7 @@
             if (this._cameras !== null) {
                 this._scene.postProcessRenderPipelineManager.detachCamerasFromRenderPipeline(this._name, this._cameras);
                 // get back cameras to be used to reattach pipeline
-                this._cameras = this._originalCameras.slice();
+                this._cameras = this._camerasToBeAttached.slice();
             }
             this._reset();
             this._prevPostProcess = null;
@@ -603,7 +603,7 @@
          * @param camera the camera to be added
          */
         public addCamera(camera:Camera):void{
-            this._originalCameras.push(camera);
+            this._camerasToBeAttached.push(camera);
             this._buildPipeline();
         }
 
@@ -612,8 +612,8 @@
          * @param camera the camera to remove
          */
         public removeCamera(camera:Camera):void{
-            var index = this._originalCameras.indexOf(camera);
-            this._originalCameras.splice(index, 1);
+            var index = this._camerasToBeAttached.indexOf(camera);
+            this._camerasToBeAttached.splice(index, 1);
             this._buildPipeline();
         }
 

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

@@ -5,6 +5,16 @@ module BABYLON {
     export class UtilityLayerRenderer implements IDisposable {
         private _pointerCaptures: {[pointerId:number]: boolean} = {};
         private _lastPointerEvents: {[pointerId:number]: number} = {};
+        private static _DefaultUtilityLayer:Nullable<UtilityLayerRenderer> = null;
+        public static get DefaultUtilityLayer():UtilityLayerRenderer{
+            if(UtilityLayerRenderer._DefaultUtilityLayer == null){
+                UtilityLayerRenderer._DefaultUtilityLayer = new UtilityLayerRenderer(BABYLON.Engine.LastCreatedScene!);
+                UtilityLayerRenderer._DefaultUtilityLayer.originalScene.onDisposeObservable.add(()=>{
+                    UtilityLayerRenderer._DefaultUtilityLayer = null;
+                });
+            }
+            return UtilityLayerRenderer._DefaultUtilityLayer;
+        }
 
         /** 
          * The scene that is rendered on top of the original scene
@@ -59,6 +69,7 @@ module BABYLON {
 
                 let pointerEvent = <PointerEvent>(prePointerInfo.event);
                 if (originalScene!.isPointerCaptured(pointerEvent.pointerId)) {
+                    this._pointerCaptures[pointerEvent.pointerId] = false;
                     return;
                 }
 
@@ -96,7 +107,7 @@ module BABYLON {
 
                     // If the layer can be occluded by the original scene, only fire pointer events to the first layer that hit they ray
                     if (originalScenePick && utilityScenePick){
-
+                        
                         // No pick in utility scene
                         if (utilityScenePick.distance === 0 && originalScenePick.pickedMesh) {
                             if (this.mainSceneTrackerPredicate && this.mainSceneTrackerPredicate(originalScenePick.pickedMesh)) {
@@ -185,4 +196,4 @@ module BABYLON {
             this.utilityLayerScene.activeCamera = this.originalScene.activeCamera;
         }
     }
-}
+}

+ 18 - 1
src/Shaders/gpuRenderParticles.vertex.fx

@@ -13,6 +13,9 @@ in vec3 size;
 in vec3 initialDirection;
 #endif
 in vec2 angle;
+#ifdef ANIMATESHEET
+in float cellIndex;
+#endif
 in vec2 offset;
 in vec2 uv;
 
@@ -32,8 +35,22 @@ uniform vec4 colorDead;
 in vec4 color;
 #endif
 
+#ifdef ANIMATESHEET
+uniform vec3 sheetInfos;
+#endif
+
 void main() {
-  vUV = uv;
+
+	#ifdef ANIMATESHEET
+		float rowOffset = floor(cellIndex / sheetInfos.z);
+		float columnOffset = cellIndex - rowOffset * sheetInfos.z;
+
+		vec2 uvScale = sheetInfos.xy;
+		vec2 uvOffset = vec2(uv.x , 1.0 - uv.y);
+		vUV = (uvOffset + vec2(columnOffset, rowOffset)) * uvScale;
+	#else	
+   	vUV = uv;
+	#endif
   float ratio = age / life;
 #ifdef COLORGRADIENTS
 	vColor = texture(colorGradientSampler, vec2(ratio, 0));

+ 26 - 11
src/Shaders/gpuUpdateParticles.vertex.fx

@@ -58,6 +58,9 @@ in vec3 direction;
 in vec3 initialDirection;
 #endif
 in vec2 angle;
+#ifdef ANIMATESHEET
+in float cellIndex;
+#endif
 
 // Output
 out vec3 outPosition;
@@ -65,9 +68,6 @@ out float outAge;
 out float outLife;
 out vec4 outSeed;
 out vec3 outSize;
-#ifdef SIZEGRADIENTS
-out vec3 outInitialSize;
-#endif
 #ifndef COLORGRADIENTS
 out vec4 outColor;
 #endif
@@ -76,12 +76,19 @@ out vec3 outDirection;
 out vec3 outInitialDirection;
 #endif
 out vec2 outAngle;
+#ifdef ANIMATESHEET
+out float outCellIndex;
+#endif
 
 #ifdef SIZEGRADIENTS
 uniform sampler2D sizeGradientSampler;
-in vec3 initialSize;
 #endif 
 
+#ifdef ANIMATESHEET
+uniform vec3 cellInfos;
+#endif
+
+
 vec3 getRandomVec3(float offset) {
   return texture(randomSampler2, vec2(float(gl_VertexID) * offset / currentCount, 0)).rgb;
 }
@@ -106,6 +113,9 @@ void main() {
 #endif      
       outDirection = direction;
       outAngle = angle;
+#ifdef ANIMATESHEET      
+      outCellIndex = cellIndex;
+#endif
       return;
     }
     vec3 position;
@@ -122,14 +132,10 @@ void main() {
     outSeed = seed;
 
     // Size
-    outSize.x = sizeRange.x + (sizeRange.y - sizeRange.x) * randoms.g;
+    outSize.x = texture(sizeGradientSampler, vec2(0, 0)).r;
     outSize.y = scaleRange.x + (scaleRange.y - scaleRange.x) * randoms.b;
     outSize.z = scaleRange.z + (scaleRange.w - scaleRange.z) * randoms.a; 
 
-#ifdef SIZEGRADIENTS
-    outInitialSize = outSize;
-#endif    
-
 #ifndef COLORGRADIENTS
     // Color
     outColor = color1 + (color2 - color1) * randoms.b;
@@ -206,6 +212,9 @@ void main() {
 #ifndef BILLBOARD        
     outInitialDirection = initial;
 #endif
+#ifdef ANIMATESHEET      
+    outCellIndex = cellInfos.x;
+#endif
 
   } else {   
     outPosition = position + direction * timeDelta;
@@ -217,8 +226,8 @@ void main() {
 #endif
 
 #ifdef SIZEGRADIENTS
-    outInitialSize = initialSize;
-	outSize = initialSize * texture(sizeGradientSampler, vec2(age / life, 0)).r;
+	outSize.x = texture(sizeGradientSampler, vec2(age / life, 0)).r;
+    outSize.yz = size.yz;
 #else
     outSize = size;
 #endif 
@@ -228,5 +237,11 @@ void main() {
 #endif
     outDirection = direction + gravity * timeDelta;
     outAngle = vec2(angle.x + angle.y * timeDelta, angle.y);
+#ifdef ANIMATESHEET      
+    float dist = cellInfos.y - cellInfos.x;
+    float ratio = clamp(mod(((outAge * cellInfos.z) / life), life), 0., 1.0);
+
+    outCellIndex = float(int(cellInfos.x + ratio * dist));
+#endif
   }
 }

+ 1 - 1
src/Tools/babylon.environmentTextureTools.ts

@@ -196,7 +196,7 @@ module BABYLON {
                             hostingScene.postProcessManager.directRender([rgbdPostProcess], null);
 
                             // Reading datas from WebGL
-                            canvas!.toBlob((blob) => {
+                            Tools.ToBlob(canvas!, (blob) => {
                                 let fileReader = new FileReader();
                                 fileReader.onload = (event: any) => {
                                     let arrayBuffer = event.target!.result as ArrayBuffer;

+ 58 - 23
src/Tools/babylon.tools.ts

@@ -49,9 +49,26 @@
          */
         public gradient: number;
         /**
-         * Gets or sets associated factor
+         * Gets or sets first associated factor
          */        
-        public factor: number;
+        public factor1: number;
+        /**
+         * Gets or sets second associated factor
+         */        
+        public factor2?: number;    
+        
+        /** 
+         * Will get a number picked randomly between factor1 and factor2.
+         * If factor2 is undefined then factor1 will be used
+         * @returns the picked number
+         */
+        public getFactor(): number {
+            if (this.factor2 === undefined) {
+                return this.factor1;
+            }
+
+            return Scalar.Lerp(this.factor1, this.factor2, Math.random());
+        }        
     }  
 
     // See https://stackoverflow.com/questions/12915412/how-do-i-extend-a-host-object-e-g-error-in-typescript
@@ -1024,29 +1041,48 @@
             }
         }
 
-        static EncodeScreenshotCanvasData(successCallback?: (data: string) => void, mimeType: string = "image/png", fileName?: string) {
-            var base64Image = screenshotCanvas.toDataURL(mimeType);
+        /**
+         * Converts the canvas data to blob.
+         * This acts as a polyfill for browsers not supporting the to blob function.
+         * @param canvas Defines the canvas to extract the data from
+         * @param successCallback Defines the callback triggered once the data are available
+         * @param mimeType Defines the mime type of the result
+         */
+        static ToBlob(canvas: HTMLCanvasElement, successCallback: (blob: Nullable<Blob>) => void, mimeType: string = "image/png"): void {
+            // We need HTMLCanvasElement.toBlob for HD screenshots
+            if (!screenshotCanvas.toBlob) {
+                //  low performance polyfill based on toDataURL (https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toBlob)
+                screenshotCanvas.toBlob = function (callback, type, quality) {
+                    setTimeout(() => {
+                        var binStr = atob(this.toDataURL(type, quality).split(',')[1]),
+                            len = binStr.length,
+                            arr = new Uint8Array(len);
+
+                        for (var i = 0; i < len; i++) {
+                            arr[i] = binStr.charCodeAt(i);
+                        }
+                        callback(new Blob([arr]));
+                    });
+                }
+            }
+            screenshotCanvas.toBlob(function (blob) {
+                successCallback(blob);
+            }, mimeType);
+        }
+
+        /**
+         * Encodes the canvas data to base 64 or automatically download the result if filename is defined
+         * @param successCallback Defines the callback triggered once the data are available
+         * @param mimeType Defines the mime type of the result
+         * @param fileName The filename to download. If present, the result will automatically be downloaded
+         */
+        static EncodeScreenshotCanvasData(successCallback?: (data: string) => void, mimeType: string = "image/png", fileName?: string): void {
             if (successCallback) {
+                var base64Image = screenshotCanvas.toDataURL(mimeType);
                 successCallback(base64Image);
             }
             else {
-                // We need HTMLCanvasElement.toBlob for HD screenshots
-                if (!screenshotCanvas.toBlob) {
-                    //  low performance polyfill based on toDataURL (https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toBlob)
-                    screenshotCanvas.toBlob = function (callback, type, quality) {
-                        setTimeout(() => {
-                            var binStr = atob(this.toDataURL(type, quality).split(',')[1]),
-                                len = binStr.length,
-                                arr = new Uint8Array(len);
-
-                            for (var i = 0; i < len; i++) {
-                                arr[i] = binStr.charCodeAt(i);
-                            }
-                            callback(new Blob([arr], { type: type || 'image/png' }));
-                        });
-                    }
-                }
-                screenshotCanvas.toBlob(function (blob) {
+                this.ToBlob(screenshotCanvas, function (blob) {
                     //Creating a link if the browser have the download attribute on the a tag, to automatically start download generated image.
                     if (("download" in document.createElement("a"))) {
                         if (!fileName) {
@@ -1069,8 +1105,7 @@
                         img.src = url;
                         newWindow.document.body.appendChild(img);
                     }
-
-                });
+                }, mimeType);
             }
         }
 

+ 2 - 2
src/babylon.scene.ts

@@ -753,8 +753,8 @@
 
         /** Define this parameter if you are using multiple cameras and you want to specify which one should be used for pointer position */
         public cameraToUseForPointers: Nullable<Camera> = null;
-        private _pointerX: number;
-        private _pointerY: number;
+        private _pointerX: number = 0;
+        private _pointerY: number = 0;
         private _unTranslatedPointerX: number;
         private _unTranslatedPointerY: number;
         private _startingPointerPosition = new Vector2(0, 0);

+ 5 - 0
tests/nullEngine/app.js

@@ -162,6 +162,11 @@ var engine = new BABYLON.NullEngine();
 
 BABYLON.Tools.LogLevels = BABYLON.Tools.ErrorLogLevel & BABYLON.Tools.WarningLogLevel;
 const scene = new BABYLON.Scene(engine);
+BABYLON.ParticleHelper.CreateAsync("sun", scene)
+            .then((system) => {
+                console.log("ok");
+            });
+
 const camera = new BABYLON.ArcRotateCamera("camera", 0, 0, 10, BABYLON.Vector3.Zero(), scene);
 const mesh = BABYLON.MeshBuilder.CreateBox("box", {}, scene);
 mesh.position.set(0.5, 0.5, 0.5);

+ 0 - 70
tests/unit/babylon/src/Helpers/babylon.particleHelper.tests.ts

@@ -1,70 +0,0 @@
-/**
- * Describes the test suite.
- */
-describe('Babylon Particle Helper', function () {
-  let subject: BABYLON.Engine;
-
-  /**
-   * Loads the dependencies.
-   */
-  before(function (done) {
-      this.timeout(180000);
-      (BABYLONDEVTOOLS).Loader
-          .useDist()
-          .load(function () {
-              // Force apply promise polyfill for consistent behavior between PhantomJS, IE11, and other browsers.
-              BABYLON.PromisePolyfill.Apply(true);
-              done();
-          });
-  });
-
-  /**
-   * Create a new engine subject before each test.
-   */
-  beforeEach(function () {
-      subject = new BABYLON.NullEngine({
-          renderHeight: 256,
-          renderWidth: 256,
-          textureSize: 256,
-          deterministicLockstep: false,
-          lockstepMaxSteps: 1
-      });
-  });
-
-  describe('#ParticleHelper.CreateAsync', () => {
-      let scene: BABYLON.Scene;
-      let emitter: BABYLON.Mesh;
-
-      beforeEach(() => {
-        scene = new BABYLON.Scene(subject);
-        emitter = BABYLON.Mesh.CreateSphere("sphere", 10, 5, scene);
-      });
-
-      it('creates a fire particle system', (done) => {
-        BABYLON.ParticleHelper.CreateAsync("fire", emitter, scene)
-            .then((system) => {
-                expect(system).to.be.instanceof(BABYLON.ParticleSystem);
-                done();
-            })
-            .catch((error) => { throw new Error("was not supposed to fail"); });
-      });
-
-      it('creates a smoke particle system', (done) => {
-        BABYLON.ParticleHelper.CreateAsync("smoke", emitter, scene)
-            .then((system) => {
-                expect(system).to.be.instanceof(BABYLON.ParticleSystem);
-                done();
-            })
-            .catch((error) => { throw new Error("was not supposed to fail"); });
-    });
-
-      it('rejects the creation of the particle system', (done) => {
-        BABYLON.ParticleHelper.CreateAsync("test", emitter, scene)
-            .then(() => { throw new Error("was not supposed to succeed"); })
-            .catch((error) => {
-                expect(error).to.equals("An error occured while the creation of your particle system. Check if your type 'test' exists.");
-                done();
-            });
-    });
-  });
-});

+ 0 - 1
tests/unit/karma.conf.js

@@ -17,7 +17,6 @@ module.exports = function (config) {
             './tests/unit/babylon/serializers/babylon.glTFSerializer.tests.js',
             './tests/unit/babylon/src/babylon.node.tests.js',
             './tests/unit/babylon/src/Animations/babylon.animationGroup.tests.js',
-            './tests/unit/babylon/src/Helpers/babylon.particleHelper.tests.js',
             './tests/unit/babylon/src/Loading/babylon.sceneLoader.tests.js',
             './tests/unit/babylon/src/PostProcess/babylon.postProcess.tests.js',
             './tests/unit/babylon/src/Material/babylon.material.tests.js',

binární
tests/validation/ReferenceImages/GUI.png


binární
tests/validation/ReferenceImages/particle_helper.png


binární
tests/validation/ReferenceImages/yeti.png


+ 7 - 1
tests/validation/config.json

@@ -1,6 +1,12 @@
 {
   "root": "https://rawgit.com/BabylonJS/Website/master",
-  "tests": [
+  "tests": [  
+    {
+      "title": "Particle Helper",
+      "playgroundId": "#1VGT5D#2",
+      "renderCount": 50,
+      "referenceImage": "particle_helper.png"
+    },     
     {
       "title": "Chibi Rex",
       "playgroundId": "#QATUCH#4",