Jelajahi Sumber

Merge remote-tracking branch 'upstream/master' into EXT_lights_imageBased

Gary Hsu 7 tahun lalu
induk
melakukan
1275be0bbb
82 mengubah file dengan 19052 tambahan dan 16887 penghapusan
  1. 3177 3161
      Playground/babylon.d.txt
  2. 2 1
      Tools/Gulp/config.json
  3. TEMPAT SAMPAH
      Viewer/assets/babylon.woff
  4. 12 36
      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. 38 0
      Viewer/src/templating/plugins/hdButtonPlugin.ts
  11. 9 0
      Viewer/src/templating/templateManager.ts
  12. 70 0
      Viewer/src/templating/viewerTemplatePlugin.ts
  13. 79 48
      Viewer/src/viewer/defaultViewer.ts
  14. 83 6
      Viewer/src/viewer/viewer.ts
  15. 0 25
      assets/particles/systems/fire.json
  16. 0 25
      assets/particles/systems/smoke.json
  17. 339 0
      assets/particles/systems/sun.json
  18. TEMPAT SAMPAH
      assets/particles/textures/flare.png
  19. TEMPAT SAMPAH
      assets/particles/textures/sun/T_Star.png
  20. TEMPAT SAMPAH
      assets/particles/textures/sun/T_SunFlare.png
  21. TEMPAT SAMPAH
      assets/particles/textures/sun/T_SunSurface.png
  22. 3600 3584
      dist/preview release/babylon.d.ts
  23. 40 40
      dist/preview release/babylon.js
  24. 605 344
      dist/preview release/babylon.max.js
  25. 605 344
      dist/preview release/babylon.no-module.max.js
  26. 40 40
      dist/preview release/babylon.worker.js
  27. 607 346
      dist/preview release/es6.js
  28. 1 1
      dist/preview release/glTF2Interface/package.json
  29. 1 1
      dist/preview release/gui/package.json
  30. 1 1
      dist/preview release/inspector/package.json
  31. 2 2
      dist/preview release/loaders/package.json
  32. 1 1
      dist/preview release/materialsLibrary/package.json
  33. 1 1
      dist/preview release/postProcessesLibrary/package.json
  34. 1 1
      dist/preview release/proceduralTexturesLibrary/package.json
  35. 1 1
      dist/preview release/serializers/babylon.glTF2Serializer.js
  36. 1 1
      dist/preview release/serializers/babylon.glTF2Serializer.min.js
  37. 1 1
      dist/preview release/serializers/babylonjs.serializers.js
  38. 1 1
      dist/preview release/serializers/babylonjs.serializers.min.js
  39. 2 2
      dist/preview release/serializers/package.json
  40. 56 1
      dist/preview release/viewer/babylon.viewer.d.ts
  41. 69 69
      dist/preview release/viewer/babylon.viewer.js
  42. 8675 8134
      dist/preview release/viewer/babylon.viewer.max.js
  43. 59 4
      dist/preview release/viewer/babylon.viewer.module.d.ts
  44. 16 10
      dist/preview release/what's new.md
  45. 1 1
      package.json
  46. 1 1
      serializers/src/glTF/2.0/babylon.glTFMaterialExporter.ts
  47. 1 1
      src/Engine/babylon.engine.ts
  48. 4 0
      src/Engine/babylon.nullEngine.ts
  49. 10 2
      src/Gamepad/Controllers/babylon.poseEnabledController.ts
  50. 1 1
      src/Gizmos/babylon.axisDragGizmo.ts
  51. 1 1
      src/Gizmos/babylon.axisScaleGizmo.ts
  52. 55 4
      src/Gizmos/babylon.boundingBoxGizmo.ts
  53. 1 1
      src/Gizmos/babylon.gizmo.ts
  54. 1 1
      src/Gizmos/babylon.planeRotationGizmo.ts
  55. 4 4
      src/Gizmos/babylon.positionGizmo.ts
  56. 4 4
      src/Gizmos/babylon.rotationGizmo.ts
  57. 4 4
      src/Gizmos/babylon.scaleGizmo.ts
  58. 0 349
      src/Helpers/babylon.particleHelper.ts
  59. 2 2
      src/Particles/EmitterTypes/babylon.boxParticleEmitter.ts
  60. 4 4
      src/Particles/EmitterTypes/babylon.coneParticleEmitter.ts
  61. 2 0
      src/Particles/EmitterTypes/babylon.sphereParticleEmitter.ts
  62. 32 3
      src/Particles/babylon.IParticleSystem.ts
  63. 92 31
      src/Particles/babylon.gpuParticleSystem.ts
  64. 23 43
      src/Particles/babylon.particle.ts
  65. 55 0
      src/Particles/babylon.particleHelper.ts
  66. 171 68
      src/Particles/babylon.particleSystem.ts
  67. 139 0
      src/Particles/babylon.particleSystemSet.ts
  68. 6 6
      src/PostProcess/RenderPipeline/Pipelines/babylon.defaultRenderingPipeline.ts
  69. 13 2
      src/Rendering/babylon.utilityLayerRenderer.ts
  70. 20 3
      src/Shaders/gpuRenderParticles.vertex.fx
  71. 30 11
      src/Shaders/gpuUpdateParticles.vertex.fx
  72. 2 1
      src/Shaders/particles.vertex.fx
  73. 1 1
      src/Tools/babylon.environmentTextureTools.ts
  74. 58 23
      src/Tools/babylon.tools.ts
  75. 2 2
      src/babylon.scene.ts
  76. 5 0
      tests/nullEngine/app.js
  77. 0 70
      tests/unit/babylon/src/Helpers/babylon.particleHelper.tests.ts
  78. 0 1
      tests/unit/karma.conf.js
  79. TEMPAT SAMPAH
      tests/validation/ReferenceImages/GUI.png
  80. TEMPAT SAMPAH
      tests/validation/ReferenceImages/particle_helper.png
  81. TEMPAT SAMPAH
      tests/validation/ReferenceImages/yeti.png
  82. 7 1
      tests/validation/config.json

File diff ditekan karena terlalu besar
+ 3177 - 3161
Playground/babylon.d.txt


+ 2 - 1
Tools/Gulp/config.json

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

TEMPAT SAMPAH
Viewer/assets/babylon.woff


+ 12 - 36
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";
     }
 
@@ -164,14 +152,14 @@
         content: "\EF4E";
     }
 
-    .hd-icon:after {
+    viewer.in-vr .vr-icon:after {
         font-size: 16px;
-        content: "\F765";
+        content: "\E7F4";
     }
 
-    .sd-icon:after {
+    .vr-icon:after {
         font-size: 16px;
-        content: "\F766";
+        content: "\F119";
     }
 
     .progress-control {
@@ -438,15 +426,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 +457,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,15 +493,11 @@
     </div>
     {{/unless}}
     <div class="default-control">
-        {{#unless hideHdButton}}
-        <button class="hd-button hd-button" title="{{text.hdButton}}">
-            {{#if hdEnabled}}
-            <span class="icon sd-icon"></span>
-            {{else}}
-            <span class="icon hd-icon"></span>
-            {{/if}}
+        {{#unless hideVr}}
+        <button class="vr vr-button" title="{{text.vrButton}}">
+            <span class="icon vr-icon"></span>
         </button>
-        {{/unless}} {{#unless hideHelp}}
+        {{/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: {

File diff ditekan karena terlalu besar
+ 86 - 5
Viewer/src/managers/sceneManager.ts


+ 38 - 0
Viewer/src/templating/plugins/hdButtonPlugin.ts

@@ -0,0 +1,38 @@
+import { AbstractViewerNavbarButton } from "../viewerTemplatePlugin";
+import { DefaultViewer } from "../../viewer/defaultViewer";
+import { EventCallback, Template } from "../templateManager";
+
+export class HDButtonPlugin extends AbstractViewerNavbarButton {
+
+    protected _buttonClass = "hd-button";
+
+    constructor(private _viewer: DefaultViewer) {
+        super();
+    }
+
+    onEvent(event: EventCallback): void {
+        let button = event.template.parent.querySelector(".hd-button");
+        if (button) {
+            button.classList.contains("hd-toggled") ? button.classList.remove("hd-toggled") : button.classList.add("hd-toggled");
+        }
+        this._viewer.toggleHD();
+    }
+
+    protected _htmlTemplate: string = `
+{{#unless hideHd}}
+<style>
+.hd-icon:after {
+    font-size: 16px;
+    content: "\\F765";
+}
+
+.hd-toggled span.hd-icon:after {
+    content: "\\F766";
+}
+</style>
+<button class="hd-button" title="{{text.hdButton}}">
+     <span class="icon hd-icon"></span>
+ </button>
+ {{/unless}}
+`;
+}

+ 9 - 0
Viewer/src/templating/templateManager.ts

@@ -272,6 +272,10 @@ export class Template {
      */
     public onEventTriggered: Observable<EventCallback>;
 
+    public onParamsUpdated: Observable<Template>;
+
+    public onHTMLRendered: Observable<Template>;
+
     /**
      * is the template loaded?
      */
@@ -309,6 +313,8 @@ export class Template {
         this.onAppended = new Observable<Template>();
         this.onStateChange = new Observable<Template>();
         this.onEventTriggered = new Observable<EventCallback>();
+        this.onParamsUpdated = new Observable<Template>();
+        this.onHTMLRendered = new Observable<Template>();
 
         this.loadRequests = [];
 
@@ -432,6 +438,9 @@ export class Template {
         } else {
             this.parent.insertAdjacentHTML("beforeend", this._rawHtml);
         }
+
+        this.onHTMLRendered.notifyObservers(this);
+
         // appended only one frame after.
         setTimeout(() => {
             this._registerEvents();

+ 70 - 0
Viewer/src/templating/viewerTemplatePlugin.ts

@@ -0,0 +1,70 @@
+import { EventCallback, Template } from "./templateManager";
+import * as Handlebars from 'handlebars/dist/handlebars';
+
+export interface IViewerTemplatePlugin {
+
+    readonly templateName: string;
+    readonly eventsToAttach?: Array<string>;
+
+    interactionPredicate(event: EventCallback): boolean;
+    onEvent?(event: EventCallback): void;
+    addHTMLTemplate?(template: Template): void;
+}
+
+export abstract class AbstractViewerNavbarButton implements IViewerTemplatePlugin {
+
+    public readonly templateName: string = "navBar";
+    public readonly eventsToAttach: Array<string> = ['pointerdown'];
+    protected _prepend: boolean = true;
+    protected abstract _buttonClass: string;
+    protected abstract _htmlTemplate: string;
+
+    interactionPredicate(event: EventCallback): boolean {
+        let pointerDown = <PointerEvent>event.event;
+        if (pointerDown.button !== 0) return false;
+        var element = (<HTMLElement>event.event.target);
+
+        if (!element) {
+            return false;
+        }
+
+        let elementClasses = element.classList;
+
+        for (let i = 0; i < elementClasses.length; ++i) {
+            let className = elementClasses[i];
+            if (className.indexOf(this._buttonClass) !== -1) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+    abstract onEvent(event: EventCallback): void;
+
+    public addHTMLTemplate(template: Template): void {
+        let element = this._generateHTMLElement(template);
+        let container = template.parent.querySelector("div.default-control");
+        if (container) {
+            if (this._prepend) {
+                container.insertBefore(element, container.firstChild);
+            } else {
+                container.appendChild(element);
+            }
+        }
+    }
+
+    protected _generateHTMLElement(template: Template): Element | DocumentFragment {
+        let compiledTemplate = Handlebars.compile(this._htmlTemplate, { noEscape: (template.configuration.params && !!template.configuration.params.noEscape) });
+        let config = template.configuration.params || {};
+        let rawHtml = compiledTemplate(config);
+        let fragment: Element | DocumentFragment;
+        try {
+            fragment = document.createRange().createContextualFragment(rawHtml);
+        } catch (e) {
+            let test = document.createElement(this._buttonClass);
+            test.innerHTML = rawHtml;
+            fragment = test;
+        }
+        return fragment;
+    }
+}

+ 79 - 48
Viewer/src/viewer/defaultViewer.ts

@@ -1,13 +1,13 @@
 
 
 import { ViewerConfiguration, IModelConfiguration, ILightConfiguration } from './../configuration';
-import { Template, EventCallback, TemplateManager } from '../templating/templateManager';
+import { Template, EventCallback } from '../templating/templateManager';
 import { AbstractViewer } from './viewer';
-import { SpotLight, MirrorTexture, Plane, ShadowGenerator, Texture, BackgroundMaterial, Observable, ShadowLight, CubeTexture, BouncingBehavior, FramingBehavior, Behavior, Light, Engine, Scene, AutoRotationBehavior, AbstractMesh, Quaternion, StandardMaterial, ArcRotateCamera, ImageProcessingConfiguration, Color3, Vector3, SceneLoader, Mesh, HemisphericLight, FilesInput } from 'babylonjs';
-import { CameraBehavior } from '../interfaces';
+import { SpotLight, Vector3, FilesInput } from 'babylonjs';
 import { ViewerModel } from '../model/viewerModel';
-import { extendClassWithConfig } from '../helper';
 import { IModelAnimation, AnimationState } from '../model/modelAnimation';
+import { IViewerTemplatePlugin } from '../templating/viewerTemplatePlugin';
+import { HDButtonPlugin } from '../templating/plugins/hdButtonPlugin';
 
 /**
  * The Default viewer is the default implementation of the AbstractViewer.
@@ -15,7 +15,7 @@ import { IModelAnimation, AnimationState } from '../model/modelAnimation';
  */
 export class DefaultViewer extends AbstractViewer {
 
-
+    public fullscreenElement?: HTMLElement;
 
     /**
      * Create a new default viewer
@@ -32,11 +32,40 @@ export class DefaultViewer extends AbstractViewer {
 
         this.onEngineInitObservable.add(() => {
             this.sceneManager.onLightsConfiguredObservable.add((data) => {
-                this._configureLights(data.newConfiguration, data.model!);
+                this._configureLights();
             })
         });
     }
 
+    private _registeredPlugins: Array<IViewerTemplatePlugin> = [];
+
+    public registerTemplatePlugin(plugin: IViewerTemplatePlugin) {
+        //validate
+        if (!plugin.templateName) {
+            throw new Error("No template name provided");
+        }
+        this._registeredPlugins.push(plugin);
+        let template = this.templateManager.getTemplate(plugin.templateName);
+        if (!template) {
+            throw new Error(`Template ${plugin.templateName} not found`);
+        }
+        if (plugin.addHTMLTemplate) {
+            template.onHTMLRendered.add((tmpl) => {
+                plugin.addHTMLTemplate && plugin.addHTMLTemplate(tmpl);
+            });
+        }
+
+        if (plugin.eventsToAttach) {
+            plugin.eventsToAttach.forEach(eventName => {
+                plugin.onEvent && this.templateManager.eventManager.registerCallback(plugin.templateName, (event) => {
+                    if (plugin.onEvent && plugin.interactionPredicate(event)) {
+                        plugin.onEvent(event);
+                    }
+                }, eventName);
+            });
+        }
+    }
+
     /**
      * This will be executed when the templates initialize.
      */
@@ -77,10 +106,6 @@ export class DefaultViewer extends AbstractViewer {
         return super._onTemplatesLoaded();
     }
 
-    private _dropped(evt: EventCallback) {
-
-    }
-
     private _initNavbar() {
         let navbar = this.templateManager.getTemplate('navBar');
         if (navbar) {
@@ -100,7 +125,7 @@ export class DefaultViewer extends AbstractViewer {
                 this._currentAnimation.goToFrame(gotoFrame);
             }, "input");
 
-            this.templateManager.eventManager.registerCallback("navBar", (e) => {
+            this.templateManager.eventManager.registerCallback("navBar", () => {
                 if (this._resumePlay) {
                     this._togglePlayPause(true);
                 }
@@ -112,6 +137,8 @@ export class DefaultViewer extends AbstractViewer {
                     hideHdButton: true
                 });
             }
+
+            this.registerTemplatePlugin(new HDButtonPlugin(this));
         }
     }
 
@@ -134,7 +161,7 @@ export class DefaultViewer extends AbstractViewer {
 
         let elementClasses = element.classList;
 
-        let elementName = ""; 0
+        let elementName = "";
 
         for (let i = 0; i < elementClasses.length; ++i) {
             let className = elementClasses[i];
@@ -157,9 +184,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":
@@ -179,8 +207,8 @@ export class DefaultViewer extends AbstractViewer {
             case "fullscreen-button":
                 this.toggleFullscreen();
                 break;
-            case "hd-button":
-                this.toggleHD();
+            case "vr-button":
+                this.toggleVR();
                 break;
             default:
                 return;
@@ -268,42 +296,39 @@ 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
             });
         }
 
         this._updateAnimationSpeed("1.0", paramsObject);
     }
 
-    public toggleHD() {
-        super.toggleHD();
-
-        // update UI element
-        let navbar = this.templateManager.getTemplate('navBar');
-        if (!navbar) return;
+    public toggleVR() {
+        super.toggleVR();
 
-        if (navbar.configuration.params) {
-            navbar.configuration.params.hdEnabled = this._hdToggled;
-        }
+        let viewerTemplate = this.templateManager.getTemplate('viewer');
+        let viewerElement = viewerTemplate && viewerTemplate.parent;
 
-        let span = navbar.parent.querySelector("button.hd-button span");
-        if (span) {
-            span.classList.remove(this._hdToggled ? "hd-icon" : "sd-icon");
-            span.classList.add(!this._hdToggled ? "hd-icon" : "sd-icon")
+        if (viewerElement) {
+            if (this._vrToggled) {
+                viewerElement.classList.add("in-vr");
+            } else {
+                viewerElement.classList.remove("in-vr");
+            }
         }
     }
 
@@ -313,15 +338,22 @@ export class DefaultViewer extends AbstractViewer {
     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 +383,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 +395,7 @@ export class DefaultViewer extends AbstractViewer {
                         animationIndex = 0;
                     }
                 }
-                this._updateAnimationType(animationNames[animationIndex], newParams);
+                this._updateAnimationType(newParams.animations[animationIndex], newParams);
             } else {
                 newParams.animations = null;
             }
@@ -563,10 +595,9 @@ export class DefaultViewer extends AbstractViewer {
      * @param lightsConfiguration the light configuration to use
      * @param model the model that will be used to configure the lights (if the lights are model-dependant)
      */
-    private _configureLights(lightsConfiguration: { [name: string]: ILightConfiguration | boolean | number } = {}, model?: ViewerModel) {
+    private _configureLights() {
         // labs feature - flashlight
         if (this.configuration.lab && this.configuration.lab.flashlight) {
-            let pointerPosition = Vector3.Zero();
             let lightTarget;
             let angle = 0.5;
             let exponent = Math.PI / 2;
@@ -591,7 +622,7 @@ export class DefaultViewer extends AbstractViewer {
 
             }
             this.sceneManager.scene.constantlyUpdateMeshUnderPointer = true;
-            this.sceneManager.scene.onPointerObservable.add((eventData, eventState) => {
+            this.sceneManager.scene.onPointerObservable.add((eventData) => {
                 if (eventData.type === 4 && eventData.pickInfo) {
                     lightTarget = (eventData.pickInfo.pickedPoint);
                 } else {

File diff ditekan karena terlalu besar
+ 83 - 6
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
+    }
+  ]
+}

TEMPAT SAMPAH
assets/particles/textures/flare.png


TEMPAT SAMPAH
assets/particles/textures/sun/T_Star.png


TEMPAT SAMPAH
assets/particles/textures/sun/T_SunFlare.png


TEMPAT SAMPAH
assets/particles/textures/sun/T_SunSurface.png


File diff ditekan karena terlalu besar
+ 3600 - 3584
dist/preview release/babylon.d.ts


File diff ditekan karena terlalu besar
+ 40 - 40
dist/preview release/babylon.js


File diff ditekan karena terlalu besar
+ 605 - 344
dist/preview release/babylon.max.js


File diff ditekan karena terlalu besar
+ 605 - 344
dist/preview release/babylon.no-module.max.js


File diff ditekan karena terlalu besar
+ 40 - 40
dist/preview release/babylon.worker.js


File diff ditekan karena terlalu besar
+ 607 - 346
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) {

File diff ditekan karena terlalu besar
+ 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) {

File diff ditekan karena terlalu besar
+ 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"

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

@@ -152,23 +152,26 @@ declare module BabylonViewer {
     
     
     
+    
     /**
         * The Default viewer is the default implementation of the AbstractViewer.
         * It uses the templating system to render a new canvas and controls.
         */
     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
                 * @param initialConfiguration the initial configuration. Defaults to extending the default configuration
                 */
             constructor(containerElement: HTMLElement, initialConfiguration?: ViewerConfiguration);
+            registerTemplatePlugin(plugin: IViewerTemplatePlugin): void;
             /**
                 * This will be executed when the templates initialize.
                 */
             protected _onTemplatesLoaded(): Promise<AbstractViewer>;
-            toggleHD(): void;
+            toggleVR(): void;
             /**
                 * Toggle fullscreen of the entire viewer
                 */
@@ -369,6 +372,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 +1016,7 @@ declare module BabylonViewer {
                     minecraft?: boolean;
                     [propName: string]: boolean | undefined;
             };
+            vr?: IVRConfiguration;
             lab?: {
                     flashlight?: boolean | {
                             exponent?: number;
@@ -1146,6 +1153,8 @@ declare module BabylonViewer {
                 * The event is a native browser event (like mouse or pointer events)
                 */
             onEventTriggered: BABYLON.Observable<EventCallback>;
+            onParamsUpdated: BABYLON.Observable<Template>;
+            onHTMLRendered: BABYLON.Observable<Template>;
             /**
                 * is the template loaded?
                 */
@@ -1221,6 +1230,28 @@ declare module BabylonViewer {
 
 declare module BabylonViewer {
     
+    export interface IViewerTemplatePlugin {
+        readonly templateName: string;
+        readonly eventsToAttach?: Array<string>;
+        interactionPredicate(event: EventCallback): boolean;
+        onEvent?(event: EventCallback): void;
+        addHTMLTemplate?(template: Template): void;
+    }
+    export abstract class AbstractViewerNavbarButton implements IViewerTemplatePlugin {
+        readonly templateName: string;
+        readonly eventsToAttach: Array<string>;
+        protected _prepend: boolean;
+        protected abstract _buttonClass: string;
+        protected abstract _htmlTemplate: string;
+        interactionPredicate(event: EventCallback): boolean;
+        abstract onEvent(event: EventCallback): void;
+        addHTMLTemplate(template: Template): void;
+        protected _generateHTMLElement(template: Template): Element | DocumentFragment;
+    }
+}
+
+declare module BabylonViewer {
+    
     
     
     
@@ -1272,6 +1303,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 +1339,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 +1398,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 +1630,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 +2257,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)

File diff ditekan karena terlalu besar
+ 69 - 69
dist/preview release/viewer/babylon.viewer.js


File diff ditekan karena terlalu besar
+ 8675 - 8134
dist/preview release/viewer/babylon.viewer.max.js


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

@@ -152,23 +152,26 @@ declare module 'babylonjs-viewer/viewer/defaultViewer' {
     import { Template } from 'babylonjs-viewer/templating/templateManager';
     import { AbstractViewer } from 'babylonjs-viewer/viewer/viewer';
     import { ViewerModel } from 'babylonjs-viewer/model/viewerModel';
+    import { IViewerTemplatePlugin } from 'babylonjs-viewer/templating/viewerTemplatePlugin';
     /**
         * The Default viewer is the default implementation of the AbstractViewer.
         * It uses the templating system to render a new canvas and controls.
         */
     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
                 * @param initialConfiguration the initial configuration. Defaults to extending the default configuration
                 */
             constructor(containerElement: HTMLElement, initialConfiguration?: ViewerConfiguration);
+            registerTemplatePlugin(plugin: IViewerTemplatePlugin): void;
             /**
                 * This will be executed when the templates initialize.
                 */
             protected _onTemplatesLoaded(): Promise<AbstractViewer>;
-            toggleHD(): void;
+            toggleVR(): void;
             /**
                 * Toggle fullscreen of the entire viewer
                 */
@@ -369,6 +372,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 +967,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 +1016,7 @@ declare module 'babylonjs-viewer/configuration/configuration' {
                     minecraft?: boolean;
                     [propName: string]: boolean | undefined;
             };
+            vr?: IVRConfiguration;
             lab?: {
                     flashlight?: boolean | {
                             exponent?: number;
@@ -1146,6 +1153,8 @@ declare module 'babylonjs-viewer/templating/templateManager' {
                 * The event is a native browser event (like mouse or pointer events)
                 */
             onEventTriggered: Observable<EventCallback>;
+            onParamsUpdated: Observable<Template>;
+            onHTMLRendered: Observable<Template>;
             /**
                 * is the template loaded?
                 */
@@ -1219,9 +1228,31 @@ declare module 'babylonjs-viewer/templating/templateManager' {
     }
 }
 
+declare module 'babylonjs-viewer/templating/viewerTemplatePlugin' {
+    import { EventCallback, Template } from "babylonjs-viewer/templating/templateManager";
+    export interface IViewerTemplatePlugin {
+        readonly templateName: string;
+        readonly eventsToAttach?: Array<string>;
+        interactionPredicate(event: EventCallback): boolean;
+        onEvent?(event: EventCallback): void;
+        addHTMLTemplate?(template: Template): void;
+    }
+    export abstract class AbstractViewerNavbarButton implements IViewerTemplatePlugin {
+        readonly templateName: string;
+        readonly eventsToAttach: Array<string>;
+        protected _prepend: boolean;
+        protected abstract _buttonClass: string;
+        protected abstract _htmlTemplate: string;
+        interactionPredicate(event: EventCallback): boolean;
+        abstract onEvent(event: EventCallback): void;
+        addHTMLTemplate(template: Template): void;
+        protected _generateHTMLElement(template: Template): Element | DocumentFragment;
+    }
+}
+
 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 +1303,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 +1339,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 +1398,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 +1630,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 +2255,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';

+ 16 - 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,9 @@
 - 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))
+- The default viewer has a plugin system with which new buttons can be added externally ([RaananW](https://github.com/RaananW))
 
 ### Documentation
 
@@ -88,6 +93,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"

+ 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";
         }   
         
         /**

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

@@ -62,7 +62,7 @@ module BABYLON {
          * @param particle is the particle we are computed the direction for
          */
         public startDirectionFunction(worldMatrix: Matrix, directionToUpdate: Vector3, particle: Particle): void {
-            if (this._angle === 0) {
+            if (Math.abs(Math.cos(this._angle)) === 1.0) {
                 Vector3.TransformNormalFromFloatsToRef(0, 1.0, 0, worldMatrix, directionToUpdate);
             }
             else {
@@ -88,7 +88,7 @@ module BABYLON {
          */
         startPositionFunction(worldMatrix: Matrix, positionToUpdate: Vector3, particle: Particle): void {
             var s = Scalar.RandomRange(0, Math.PI * 2);
-            var h = Scalar.RandomRange(0, 1);
+            var h = Scalar.RandomRange(0, 1.0);
             // Better distribution in a cone at normal angles.
             h = 1 - h * h;
             var radius = Scalar.RandomRange(0, this._radius);
@@ -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;
         }          
     }

+ 32 - 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,31 @@ 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 or sets a Vector2 used to move the pivot (by default (0,0)) */
+        translationPivot: Vector2;
 
         /**
          * Gets the maximum number of particles active at the same time.
@@ -232,8 +260,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

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

@@ -301,7 +301,40 @@
         /**
          * 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;
+
+        /** Gets or sets a Vector2 used to move the pivot (by default (0,0)) */
+        public translationPivot = new Vector2(0, 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 +516,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 +571,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 +602,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 +659,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 +674,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 +695,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 +708,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 +740,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 +765,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 +788,10 @@
                 // angle
                 data.push(0.0);  
                 data.push(0.0); 
+
+                if (this._isAnimationSheetEnabled) {
+                    data.push(0.0); 
+                }                
             }
 
             // Sprite data
@@ -793,7 +835,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 +847,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 +859,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 +882,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", "translationPivot"], 
                                             ["textureSampler", "colorGradientSampler"], this._scene.getEngine(), defines);
         }        
 
@@ -873,7 +923,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 +1034,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) {
@@ -1014,12 +1067,17 @@
                 this._renderEffect.setMatrix("view", viewMatrix);
                 this._renderEffect.setMatrix("projection", this._scene.getProjectionMatrix());
                 this._renderEffect.setTexture("textureSampler", this.particleTexture);
+                this._renderEffect.setVector2("translationPivot", this.translationPivot);
                 if (this._colorGradientsTexture) {
                     this._renderEffect.setTexture("colorGradientSampler", this._colorGradientsTexture);
                 } else {
                     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 +1237,7 @@
             var serializationObject: any = {};
 
             ParticleSystem._Serialize(serializationObject, this);
+            serializationObject.activeParticleCount = this.activeParticleCount;
 
             return serializationObject;            
         }
@@ -1194,7 +1253,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;
+        }
+    }
+
+}

+ 171 - 68
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;
         /**
@@ -343,6 +349,9 @@
         /** Gets or sets a value indicating the time step multiplier to use in pre-warm mode (default is 1) */
         public preWarmStepOffset = 1;
 
+        /** Gets or sets a Vector2 used to move the pivot (by default (0,0)) */
+        public translationPivot = new Vector2(0, 0);
+
         /**
         * An event triggered when the system is disposed
         */
@@ -517,36 +526,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 = [];
-            }
-
-            let sizeGradient = new FactorGradient();
-            sizeGradient.gradient = gradient;
-            sizeGradient.factor = factor;
-            this._sizeGradients.push(sizeGradient);
+        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);
 
-            this._sizeGradients.sort((a, b) => {
+            factorGradients.sort((a, b) => {
                 if (a.gradient < b.gradient) {
                     return -1;
                 } else if (a.gradient > b.gradient) {
@@ -554,28 +560,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 +946,7 @@
             subSystem.start();
         }
 
-        // end of sub system methods
+        // End of sub system methods
 
         private _update(newParticles: number): void {
             // Update current
@@ -925,6 +975,7 @@
 
                 this._particles.push(particle);
 
+                // Emitter
                 let emitPower = Scalar.RandomRange(this.minEmitPower, this.maxEmitPower);
 
                 if (this.startPositionFunction) {
@@ -953,15 +1004,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 +1059,12 @@
                         particle._currentColor2.copyFrom(particle.color);
                     }
                 }
+
+                // Sheet
+                if (this._isAnimationSheetEnabled) {
+                    particle._initialStartSpriteCellID = this.startSpriteCellID;
+                    particle._initialEndSpriteCellID = this.endSpriteCellID;
+                }
             }
         }
 
@@ -999,7 +1084,7 @@
         }
 
         public static _GetEffectCreationOptions(isAnimationSheetEnabled = false): string[] {
-            var effectCreationOption = ["invView", "view", "projection", "vClipPlane", "textureMask"];
+            var effectCreationOption = ["invView", "view", "projection", "vClipPlane", "textureMask", "translationPivot"];
 
             if (isAnimationSheetEnabled) {
                 effectCreationOption.push("particlesInfos")
@@ -1190,6 +1275,7 @@
                 effect.setFloat3("particlesInfos", this.spriteCellWidth / baseSize.width, this.spriteCellHeight / baseSize.height, baseSize.width / this.spriteCellWidth);
             }
 
+            effect.setVector2("translationPivot", this.translationPivot);
             effect.setFloat4("textureMask", this.textureMask.r, this.textureMask.g, this.textureMask.b, this.textureMask.a);
 
             if (this._scene.clipPlane) {
@@ -1375,13 +1461,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 +1495,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 +1521,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 +1548,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 +1573,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 +1648,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 +1656,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 +1679,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 +1716,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();

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

@@ -0,0 +1,139 @@
+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 _emitterNode: Nullable<TransformNode>;
+
+        /**
+         * Gets the particle system list
+         */
+        public systems = new Array<IParticleSystem>();
+
+        /**
+         * Gets the emitter node used with this set
+         */
+        public get emitterNode(): Nullable<TransformNode> {
+            return this._emitterNode;
+        }
+
+        /**
+         * 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._emitterNode) {
+                this._emitterNode.dispose();
+            }
+
+            this._emitterCreationOptions = {
+                kind: "Sphere",
+                options: options,
+                renderingGroupId: renderingGroupId
+            }
+
+            let emitterMesh = MeshBuilder.CreateSphere("emitterSphere", {diameter: options.diameter, segments: options.segments}, scene);
+            emitterMesh.renderingGroupId = renderingGroupId;
+
+            var material = new BABYLON.StandardMaterial("emitterSphereMaterial", scene)
+            material.emissiveColor = options.color;    
+            emitterMesh.material = material;     
+            
+            for (var system of this.systems) {
+                system.emitter = emitterMesh;
+            }
+
+            this._emitterNode = 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._emitterNode) {
+                this._emitterNode.dispose();
+                this._emitterNode = 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._emitterNode) {
+                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;
         }
     }
-}
+}

+ 20 - 3
src/Shaders/gpuRenderParticles.vertex.fx

@@ -3,6 +3,7 @@
 
 uniform mat4 view;
 uniform mat4 projection;
+uniform vec2 translationPivot;
 
 // Particles state
 in vec3 position;
@@ -13,6 +14,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 +36,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));
@@ -41,8 +59,7 @@ void main() {
 	vColor = color * vec4(1.0 - ratio) + colorDead * vec4(ratio);
 #endif
   
-
-  vec2 cornerPos = offset * size.yz * size.x;
+  vec2 cornerPos = (offset - translationPivot) * size.yz * size.x + translationPivot;
 
 #ifdef BILLBOARD
   // Rotate

+ 30 - 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,14 @@ void main() {
     outSeed = seed;
 
     // Size
+#ifdef SIZEGRADIENTS    
+    outSize.x = texture(sizeGradientSampler, vec2(0, 0)).r;
+#else
     outSize.x = sizeRange.x + (sizeRange.y - sizeRange.x) * randoms.g;
+#endif
     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;
@@ -184,7 +194,7 @@ void main() {
     position = vec3(randX, randY, randZ); 
 
     // Direction
-    if (coneAngle == 0.) {
+    if (abs(cos(coneAngle)) == 1.0) {
         direction = vec3(0., 1.0, 0.);
     } else {
         vec3 randoms3 = getRandomVec3(seed.z);
@@ -206,6 +216,9 @@ void main() {
 #ifndef BILLBOARD        
     outInitialDirection = initial;
 #endif
+#ifdef ANIMATESHEET      
+    outCellIndex = cellInfos.x;
+#endif
 
   } else {   
     outPosition = position + direction * timeDelta;
@@ -217,8 +230,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 +241,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
   }
 }

+ 2 - 1
src/Shaders/particles.vertex.fx

@@ -14,6 +14,7 @@ attribute vec2 offset;
 // Uniforms
 uniform mat4 view;
 uniform mat4 projection;
+uniform vec2 translationPivot;
 
 #ifdef ANIMATESHEET	
 uniform vec3 particlesInfos; // x (number of rows) y(number of columns) z(rowSize)
@@ -32,7 +33,7 @@ varying float fClipDistance;
 void main(void) {	
 	vec2 cornerPos;
 	
-	cornerPos = vec2(offset.x - 0.5, offset.y  - 0.5) * size;
+	cornerPos = (vec2(offset.x - 0.5, offset.y  - 0.5) - translationPivot) * size + translationPivot;
 
 #ifdef BILLBOARD	
 	// Rotate

+ 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

@@ -633,8 +633,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',

TEMPAT SAMPAH
tests/validation/ReferenceImages/GUI.png


TEMPAT SAMPAH
tests/validation/ReferenceImages/particle_helper.png


TEMPAT SAMPAH
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",