Przeglądaj źródła

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

David Rousset 7 lat temu
rodzic
commit
f794413f93
50 zmienionych plików z 18750 dodań i 18178 usunięć
  1. 6856 6835
      Playground/babylon.d.txt
  2. 1 1
      Viewer/assets/templates/default/overlay.html
  3. 7 0
      Viewer/dist/external/msft/config.json
  4. 2 1
      Viewer/src/configuration/configurationContainer.ts
  5. 2 3
      Viewer/src/loader/modelLoader.ts
  6. 15 4
      Viewer/src/managers/sceneManager.ts
  7. 5 3
      Viewer/src/model/viewerModel.ts
  8. 7 0
      Viewer/src/viewer/defaultViewer.ts
  9. 2 7
      Viewer/src/viewer/viewer.ts
  10. 10794 10779
      dist/preview release/babylon.d.ts
  11. 18 18
      dist/preview release/babylon.js
  12. 162 58
      dist/preview release/babylon.max.js
  13. 162 58
      dist/preview release/babylon.no-module.max.js
  14. 18 18
      dist/preview release/babylon.worker.js
  15. 162 58
      dist/preview release/es6.js
  16. 1 1
      dist/preview release/glTF2Interface/package.json
  17. 1 1
      dist/preview release/gui/package.json
  18. 2 2
      dist/preview release/inspector/babylon.inspector.bundle.js
  19. 1 1
      dist/preview release/inspector/package.json
  20. 2 2
      dist/preview release/loaders/package.json
  21. 1 1
      dist/preview release/materialsLibrary/package.json
  22. 1 1
      dist/preview release/postProcessesLibrary/package.json
  23. 1 1
      dist/preview release/proceduralTexturesLibrary/package.json
  24. 2 2
      dist/preview release/serializers/package.json
  25. 1 0
      dist/preview release/viewer/babylon.viewer.d.ts
  26. 34 34
      dist/preview release/viewer/babylon.viewer.js
  27. 194 81
      dist/preview release/viewer/babylon.viewer.max.js
  28. 2 1
      dist/preview release/viewer/babylon.viewer.module.d.ts
  29. 6 4
      dist/preview release/what's new.md
  30. 0 97
      localDev/indexMini.html
  31. 1 1
      package.json
  32. 3 3
      src/Animations/babylon.animationGroup.ts
  33. 26 20
      src/Behaviors/Mesh/babylon.pointerDragBehavior.ts
  34. 10 4
      src/Behaviors/Mesh/babylon.sixDofDragBehavior.ts
  35. 1 1
      src/Engine/babylon.engine.ts
  36. 2 1
      src/Gizmos/babylon.axisDragGizmo.ts
  37. 2 1
      src/Gizmos/babylon.axisScaleGizmo.ts
  38. 2 6
      src/Gizmos/babylon.boundingBoxGizmo.ts
  39. 17 1
      src/Gizmos/babylon.gizmo.ts
  40. 2 1
      src/Gizmos/babylon.planeRotationGizmo.ts
  41. 2 2
      src/Helpers/babylon.environmentHelper.ts
  42. 81 42
      src/Particles/babylon.gpuParticleSystem.ts
  43. 13 6
      src/Particles/babylon.particle.ts
  44. 25 7
      src/Particles/babylon.particleSystem.ts
  45. 5 3
      src/Shaders/gpuRenderParticles.vertex.fx
  46. 22 7
      src/Shaders/gpuUpdateParticles.vertex.fx
  47. 73 0
      tests/unit/babylon/src/Animations/babylon.animationGroup.tests.ts
  48. 1 0
      tests/unit/karma.conf.js
  49. BIN
      tests/validation/ReferenceImages/particles.png
  50. BIN
      tests/validation/ReferenceImages/yeti.png

Plik diff jest za duży
+ 6856 - 6835
Playground/babylon.d.txt


+ 1 - 1
Viewer/assets/templates/default/overlay.html

@@ -3,7 +3,7 @@
         position: absolute;
         z-index: 99;
         opacity: 0;
-        display: flex;
+        display: none;
         justify-content: center;
         align-items: center;
         -webkit-transition: opacity 1s ease;

+ 7 - 0
Viewer/dist/external/msft/config.json

@@ -21,5 +21,12 @@
             "g": 0.9607843137254902,
             "b": 0.9607843137254902
         }
+    },
+    "templates": {
+        "navBar": {
+            "params": {
+                "hideHdButton": false
+            }
+        }
     }
 }

+ 2 - 1
Viewer/src/configuration/configurationContainer.ts

@@ -1,5 +1,5 @@
 import { ViewerConfiguration } from './configuration';
-import { Color3 } from 'babylonjs';
+import { Color3, Scene } from 'babylonjs';
 
 export class ConfigurationContainer {
 
@@ -9,4 +9,5 @@ export class ConfigurationContainer {
 
     public mainColor: Color3 = Color3.White();
     public reflectionColor: Color3 = Color3.White();
+    public scene?: Scene;
 }

+ 2 - 3
Viewer/src/loader/modelLoader.ts

@@ -1,9 +1,8 @@
 import { ISceneLoaderPlugin, ISceneLoaderPluginAsync, Tools, SceneLoader, Tags } from "babylonjs";
 import { GLTFFileLoader, GLTFLoaderAnimationStartMode } from "babylonjs-loaders";
-import { ViewerConfiguration } from "../configuration/configuration";
 import { IModelConfiguration } from "../configuration/interfaces/modelConfiguration";
 import { ViewerModel, ModelState } from "../model/viewerModel";
-import { getLoaderPluginByName, TelemetryLoaderPlugin, ILoaderPlugin } from './plugins/';
+import { getLoaderPluginByName, ILoaderPlugin } from './plugins/';
 import { ObservablesManager } from "../managers/observablesManager";
 import { ConfigurationContainer } from "../configuration/configurationContainer";
 
@@ -58,7 +57,7 @@ export class ModelLoader {
      */
     public load(modelConfiguration: IModelConfiguration): ViewerModel {
 
-        const model = new ViewerModel(this._observablesManager, modelConfiguration);
+        const model = new ViewerModel(this._observablesManager, modelConfiguration, this._configurationContainer);
 
         model.loadId = this._loadId++;
 

+ 15 - 4
Viewer/src/managers/sceneManager.ts

@@ -338,6 +338,8 @@ export class SceneManager {
         // create a new scene
         this.scene = new Scene(this._engine);
 
+        this._configurationContainer.scene = this.scene;
+
         // set a default PBR material
         if (!sceneConfiguration.defaultMaterial) {
             var defaultMaterial = new BABYLON.PBRMaterial('defaultMaterial', this.scene);
@@ -857,7 +859,7 @@ export class SceneManager {
         if (!skyboxConifguration && !groundConfiguration) {
             if (this.environmentHelper) {
                 this.environmentHelper.dispose();
-                delete this.environmentHelper;
+                this.environmentHelper = undefined;
             };
         } else {
 
@@ -963,10 +965,19 @@ export class SceneManager {
                     this.environmentHelper.dispose();
                     this.environmentHelper = this.scene.createDefaultEnvironment(options)!;
                 } else {
-                    //this.environmentHelper.updateOptions(options)!;
+                    // recreate the ground
+                    if (this.environmentHelper.ground) {
+                        this.environmentHelper.ground.dispose();
+                    }
+                    // recreate the skybox
+                    if (this.environmentHelper.skybox) {
+                        this.environmentHelper.skybox.dispose();
+                    }
+
+                    this.environmentHelper.updateOptions(options)!;
                     // update doesn't change the size of the skybox and ground, so we have to recreate!
-                    this.environmentHelper.dispose();
-                    this.environmentHelper = this.scene.createDefaultEnvironment(options)!;
+                    //this.environmentHelper.dispose();
+                    //this.environmentHelper = this.scene.createDefaultEnvironment(options)!;
                 }
             }
 

+ 5 - 3
Viewer/src/model/viewerModel.ts

@@ -114,8 +114,10 @@ export class ViewerModel implements IDisposable {
 
         this.state = ModelState.INIT;
 
-        this.rootMesh = new AbstractMesh("modelRootMesh");
-        this._pivotMesh = new AbstractMesh("pivotMesh");
+        let scene = this._configurationContainer && this._configurationContainer.scene
+
+        this.rootMesh = new AbstractMesh("modelRootMesh", scene);
+        this._pivotMesh = new AbstractMesh("pivotMesh", scene);
         this._pivotMesh.parent = this.rootMesh;
         // rotate 180, gltf fun
         this._pivotMesh.rotation.y += Math.PI;
@@ -241,7 +243,7 @@ export class ViewerModel implements IDisposable {
         // check if this is not a gltf loader and init the animations
         if (this.skeletons.length) {
             this.skeletons.forEach((skeleton, idx) => {
-                let ag = new AnimationGroup("animation-" + idx);
+                let ag = new AnimationGroup("animation-" + idx, this._configurationContainer && this._configurationContainer.scene);
                 let add = false;
                 skeleton.getAnimatables().forEach(a => {
                     if (a.animations[0]) {

+ 7 - 0
Viewer/src/viewer/defaultViewer.ts

@@ -106,6 +106,12 @@ export class DefaultViewer extends AbstractViewer {
                 }
                 this._resumePlay = false;
             }, "pointerup", ".progress-wrapper");
+
+            if (window.devicePixelRatio === 1 && navbar.configuration.params && !navbar.configuration.params.hideHdButton) {
+                navbar.updateParams({
+                    hideHdButton: true
+                });
+            }
         }
     }
 
@@ -535,6 +541,7 @@ export class DefaultViewer extends AbstractViewer {
     }
 
     protected _onConfigurationLoaded(configuration: ViewerConfiguration) {
+
         super._onConfigurationLoaded(configuration);
 
         // initialize the templates

+ 2 - 7
Viewer/src/viewer/viewer.ts

@@ -255,14 +255,9 @@ export abstract class AbstractViewer {
     public toggleHD() {
         this._hdToggled = !this._hdToggled;
 
-        let currentLevel = this.engine.getHardwareScalingLevel();
-        const scalingFactor = 2;
+        var scale = this._hdToggled ? Math.max(0.5, 1 / (window.devicePixelRatio || 2)) : 1;
 
-        if (this._hdToggled) {
-            this.engine.setHardwareScalingLevel(currentLevel / scalingFactor);
-        } else {
-            this.engine.setHardwareScalingLevel(currentLevel * scalingFactor);
-        }
+        this.engine.setHardwareScalingLevel(scale);
     }
 
     /**

Plik diff jest za duży
+ 10794 - 10779
dist/preview release/babylon.d.ts


Plik diff jest za duży
+ 18 - 18
dist/preview release/babylon.js


Plik diff jest za duży
+ 162 - 58
dist/preview release/babylon.max.js


Plik diff jest za duży
+ 162 - 58
dist/preview release/babylon.no-module.max.js


Plik diff jest za duży
+ 18 - 18
dist/preview release/babylon.worker.js


Plik diff jest za duży
+ 162 - 58
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.7",
+    "version": "3.3.0-alpha.8",
     "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.7",
+    "version": "3.3.0-alpha.8",
     "repository": {
         "type": "git",
         "url": "https://github.com/BabylonJS/Babylon.js.git"

Plik diff jest za duży
+ 2 - 2
dist/preview release/inspector/babylon.inspector.bundle.js


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

+ 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.7",
+    "version": "3.3.0-alpha.8",
     "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.7"
+        "babylonjs-gltf2interface": "3.3.0-alpha.8"
     },
     "peerDependencies": {
         "babylonjs": ">=3.2.0-alpha"

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

@@ -1482,6 +1482,7 @@ declare module BabylonViewer {
         viewerId: string;
         mainColor: BABYLON.Color3;
         reflectionColor: BABYLON.Color3;
+        scene?: BABYLON.Scene;
     }
 }
 

Plik diff jest za duży
+ 34 - 34
dist/preview release/viewer/babylon.viewer.js


Plik diff jest za duży
+ 194 - 81
dist/preview release/viewer/babylon.viewer.max.js


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

@@ -1476,12 +1476,13 @@ declare module 'babylonjs-viewer/managers/observablesManager' {
 
 declare module 'babylonjs-viewer/configuration/configurationContainer' {
     import { ViewerConfiguration } from 'babylonjs-viewer/configuration/configuration';
-    import { Color3 } from 'babylonjs';
+    import { Color3, Scene } from 'babylonjs';
     export class ConfigurationContainer {
         configuration: ViewerConfiguration;
         viewerId: string;
         mainColor: Color3;
         reflectionColor: Color3;
+        scene?: Scene;
     }
 }
 

+ 6 - 4
dist/preview release/what's new.md

@@ -14,7 +14,8 @@
   - 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-colors)
+  - 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)
 
 ## Updates
 
@@ -33,12 +34,12 @@
 - Get a root mesh from an asset container, load a mesh from a file with a single string url ([TrevorDev](https://github.com/TrevorDev))
 - UtilityLayer class to render another scene as a layer on top of an existing scene ([TrevorDev](https://github.com/TrevorDev))
 - AnimationGroup has now onAnimationGroupEnd observable ([RaananW](https://github.com/RaananW))
-- PointerDragBehavior, SixDofDragBehavior and MultiPointerScaleBehavior to enable drag and drop/scaling with mouse or 6dof controller on a mesh ([TrevorDev](https://github.com/TrevorDev))
+- PointerDragBehavior, SixDofDragBehavior and MultiPointerScaleBehavior to enable smooth drag and drop/scaling with mouse or 6dof controller on a mesh ([TrevorDev](https://github.com/TrevorDev))
 - 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))
 - Added a new `mesh.ignoreNonUniformScaling` to turn off non uniform scaling compensation ([Deltakosh](https://github.com/deltakosh))
 - AssetsManager tasks will only run when their state is INIT. It is now possible to remove a task from the assets manager ([RaananW](https://github.com/RaananW))
 - Added sprite isVisible field ([TrevorDev](https://github.com/TrevorDev))
-
+- EnvironmentHelper will recreate ground and skybox meshes if force-disposed ([RaananW](https://github.com/RaananW))
 
 ### glTF Loader
 
@@ -94,7 +95,8 @@
 - Animation navbar now updates correctly when a new model is loaded [#4441](https://github.com/BabylonJS/Babylon.js/issues/4441) ([RaananW](https://github.com/RaananW))
 - Non-normalized meshes didn't center and focus correctly ([RaananW](https://github.com/RaananW))
 - Meshes with skeletons could have incorrect animations ([RaananW](https://github.com/RaananW))
-- Removed element IDs from viewer's templates to allow more than one viewqer in a single page [#4500](https://github.com/BabylonJS/Babylon.js/issues/4500) ([RaananW](https://github.com/RaananW))
+- Removed element IDs from viewer's templates to allow muitiple viewers in a single page [#4500](https://github.com/BabylonJS/Babylon.js/issues/4500) ([RaananW](https://github.com/RaananW))
+- Viewer is not using Engine.LastCreatedScene anymore, to support multiple viewers in a single page [#4500](https://github.com/BabylonJS/Babylon.js/issues/4500) ([RaananW](https://github.com/RaananW))
 
 ### Loaders
 

+ 0 - 97
localDev/indexMini.html

@@ -1,97 +0,0 @@
-<!DOCTYPE html>
-<html xmlns="http://www.w3.org/1999/xhtml">
-
-<head>
-    <title>Local Development</title>
-
-    <script src="../dist/preview%20release/babylon.max.js"></script>
-
-    <style>
-        html,
-        body {
-            width: 100%;
-            height: 100%;
-            padding: 0;
-            margin: 0;
-            overflow: hidden;
-        }
-
-        #renderCanvas {
-            width: 100%;
-            height: 100%;
-        }
-
-        #fps {
-            position: absolute;
-            background-color: black;
-            border: 2px solid red;
-            text-align: center;
-            font-size: 16px;
-            color: white;
-            top: 15px;
-            right: 10px;
-            width: 60px;
-            height: 20px;
-        }
-    </style>
-</head>
-
-<body>
-    <div id="fps">0</div>
-    <canvas id="renderCanvas" touch-action="none"></canvas>
-
-    <script>
-        var canvas = document.getElementById("renderCanvas");
-        var divFps = document.getElementById("fps");
-
-        // Global to simulate PG.
-        var engine = new BABYLON.Engine(canvas, true, { stencil: true, disableWebGL2Support: false, preserveDrawingBuffer: true });
-
-        if (BABYLON.Engine.isSupported()) {
-
-            // This creates a basic Babylon Scene object (non-mesh)
-            var scene = new BABYLON.Scene(engine);
-
-            // This creates and positions a free camera (non-mesh)
-            var camera = new BABYLON.FreeCamera("camera1", new BABYLON.Vector3(0, 5, -10), scene);
-
-            camera.attachControl(canvas, true);
-
-            // This targets the camera to scene origin
-            camera.setTarget(BABYLON.Vector3.Zero());
-
-            // This attaches the camera to the canvas
-            camera.attachControl(canvas, true);
-
-            // This creates a light, aiming 0,1,0 - to the sky (non-mesh)
-            var light = new BABYLON.HemisphericLight("light1", new BABYLON.Vector3(0, 1, 0), scene);
-
-            // Default intensity is 1. Let's dim the light a small amount
-            light.intensity = 0.7;
-
-            // Our built-in 'sphere' shape. Params: name, subdivs, size, scene
-            var sphere = BABYLON.Mesh.CreateSphere("sphere1", 16, 2, scene);
-
-            // Move the sphere upward 1/2 its height
-            sphere.position.y = 1;
-
-            // Our built-in 'ground' shape. Params: name, width, depth, subdivs, scene
-            var ground = BABYLON.Mesh.CreateGround("ground1", 6, 6, 2, scene);            
-
-
-            engine.runRenderLoop(function () {
-                if (scene.activeCamera) {
-                    scene.render();
-                }
-                divFps.innerHTML = engine.getFps().toFixed() + " fps";
-            });
-        }
-
-        // Resize
-        window.addEventListener("resize", function () {
-            engine.resize();
-        });
-    </script>
-</body>
-
-</html>

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

+ 3 - 3
src/Animations/babylon.animationGroup.ts

@@ -270,9 +270,9 @@ module BABYLON {
                 return this;
             }
 
-            for (var index = 0; index < this._animatables.length; index++) {
-                let animatable = this._animatables[index];
-                animatable.stop();
+            var list = this._animatables.slice();
+            for (var index = 0; index < list.length; index++) {
+                list[index].stop();
             }
 
             this._isStarted = false;

+ 26 - 20
src/Behaviors/Mesh/babylon.pointerDragBehavior.ts

@@ -20,10 +20,13 @@ module BABYLON {
          * If the behavior is currently in a dragging state
          */
         public dragging = false;
+        /**
+         * The distance towards the target drag position to move each frame. This can be useful to avoid jitter. Set this to 1 for no delay. (Default: 0.2)
+         */
+        public dragDeltaRatio = 0.2;
         // Debug mode will display drag planes to help visualize behavior
         private _debugMode = false;
-        private _maxDragAngle = Math.PI/5;
-        
+        private _moving = false;
         /**
          *  Fires each time the attached mesh is dragged with the pointer
          *  * delta between last drag position and current drag position in world space
@@ -44,10 +47,6 @@ module BABYLON {
          *  If the attached mesh should be moved when dragged
          */
         public moveAttached = true;
-        /**
-         *  Mesh with the position where the drag plane should be placed
-         */
-        public _dragPlaneParent:Nullable<Mesh>=null;
 
         /**
          *  If the drag behavior will react to drag events (Default: true)
@@ -116,6 +115,7 @@ module BABYLON {
             this.lastDragPosition = new BABYLON.Vector3(0,0,0);
             var delta = new BABYLON.Vector3(0,0,0);
             var dragLength = 0;
+            var targetPosition = new BABYLON.Vector3(0,0,0);
 
             var pickPredicate = (m:AbstractMesh)=>{
                 return this._attachedNode == m || m.isDescendantOf(this._attachedNode)
@@ -128,14 +128,15 @@ module BABYLON {
                 
                 if (pointerInfo.type == BABYLON.PointerEventTypes.POINTERDOWN) {
                     
-                    if(!this.dragging && pointerInfo.pickInfo && pointerInfo.pickInfo.hit && pointerInfo.pickInfo.pickedMesh && pointerInfo.pickInfo.ray && pickPredicate(pointerInfo.pickInfo.pickedMesh)){
-                        this._updateDragPlanePosition(pointerInfo.pickInfo.ray);
+                    if(!this.dragging && pointerInfo.pickInfo && pointerInfo.pickInfo.hit && pointerInfo.pickInfo.pickedMesh && pointerInfo.pickInfo.pickedPoint && pointerInfo.pickInfo.ray && pickPredicate(pointerInfo.pickInfo.pickedMesh)){
+                        this._updateDragPlanePosition(pointerInfo.pickInfo.ray, pointerInfo.pickInfo.pickedPoint);
                         var pickedPoint = this._pickWithRayOnDragPlane(pointerInfo.pickInfo.ray);
                         if(pickedPoint){
                             this.dragging = true;
                             this.currentDraggingPointerID = (<PointerEvent>pointerInfo.event).pointerId;
                             this.lastDragPosition.copyFrom(pickedPoint);
                             this.onDragStartObservable.notifyObservers({dragPlanePoint: pickedPoint, pointerId: this.currentDraggingPointerID});
+                            targetPosition.copyFrom((<Mesh>this._attachedNode).absolutePosition)
                         }
                     }
                 }else if(pointerInfo.type == BABYLON.PointerEventTypes.POINTERUP){
@@ -144,15 +145,12 @@ module BABYLON {
                     }
                 }else if(pointerInfo.type == BABYLON.PointerEventTypes.POINTERMOVE){
                     if(this.currentDraggingPointerID == (<PointerEvent>pointerInfo.event).pointerId && this.dragging && pointerInfo.pickInfo && pointerInfo.pickInfo.ray){
+                        this._moving = true;
                         var pickedPoint = this._pickWithRayOnDragPlane(pointerInfo.pickInfo.ray);
                         
-                         // Get angle between drag plane and ray. Only update the drag plane at non steep angles to avoid jumps in delta position
-                        var angle = Math.acos(Vector3.Dot(this._dragPlane.forward, pointerInfo.pickInfo.ray.direction));
-                        if(angle < this._maxDragAngle){
-                            this._updateDragPlanePosition(pointerInfo.pickInfo.ray);
-                        }
-                        
                         if (pickedPoint) {
+                            this._updateDragPlanePosition(pointerInfo.pickInfo.ray, pickedPoint);
+
                             // depending on the drag mode option drag accordingly
                             if(this.options.dragAxis){
                                 // Convert local drag axis to world
@@ -166,22 +164,30 @@ module BABYLON {
                                 dragLength = delta.length();
                                 pickedPoint.subtractToRef(this.lastDragPosition, delta);
                             }
-                            if(this.moveAttached){
-                                (<Mesh>this._attachedNode).absolutePosition.addToRef(delta, this._tmpVector);
-                                (<Mesh>this._attachedNode).setAbsolutePosition(this._tmpVector);
-                            }
+                            targetPosition.addInPlace(delta);
                             this.onDragObservable.notifyObservers({dragDistance: dragLength, delta: delta, dragPlanePoint: pickedPoint, dragPlaneNormal: this._dragPlane.forward, pointerId: this.currentDraggingPointerID});
                             this.lastDragPosition.copyFrom(pickedPoint);
                         }
                     }
                 }
             });
+
+            this._scene.onBeforeRenderObservable.add(()=>{
+                if(this._moving && this.moveAttached){
+                    // Slowly move mesh to avoid jitter
+                    targetPosition.subtractToRef((<Mesh>this._attachedNode).absolutePosition, this._tmpVector);
+                    this._tmpVector.scaleInPlace(0.2);
+                    (<Mesh>this._attachedNode).getAbsolutePosition().addToRef(this._tmpVector, this._tmpVector);
+                    (<Mesh>this._attachedNode).setAbsolutePosition(this._tmpVector);
+                }
+            });
         }
 
         public releaseDrag(){
             this.dragging = false;
             this.onDragEndObservable.notifyObservers({dragPlanePoint: this.lastDragPosition, pointerId: this.currentDraggingPointerID});
             this.currentDraggingPointerID = -1;
+            this._moving = false;
         }
 
         private _pickWithRayOnDragPlane(ray:Nullable<Ray>){
@@ -205,8 +211,8 @@ module BABYLON {
         private _localAxis = new Vector3(0,0,0);
         private _lookAt = new Vector3(0,0,0); 
         // Position the drag plane based on the attached mesh position, for single axis rotate the plane along the axis to face the camera
-        private _updateDragPlanePosition(ray:Ray){
-            this._pointA.copyFrom(this._dragPlaneParent ? this._dragPlaneParent.absolutePosition : (<Mesh>this._attachedNode).absolutePosition);
+        private _updateDragPlanePosition(ray:Ray, dragPlanePosition:Vector3){
+            this._pointA.copyFrom(dragPlanePosition);
             if(this.options.dragAxis){
                 this.useObjectOrienationForDragging ? Vector3.TransformCoordinatesToRef(this.options.dragAxis, this._attachedNode.getWorldMatrix().getRotationMatrix(), this._localAxis) : this._localAxis.copyFrom(this.options.dragAxis);
 

+ 10 - 4
src/Behaviors/Mesh/babylon.sixDofDragBehavior.ts

@@ -11,13 +11,19 @@ module BABYLON {
         private _virtualOriginMesh:AbstractMesh;
         private _virtualDragMesh:AbstractMesh;
         private _pointerObserver:Nullable<Observer<PointerInfo>>;
-        // How much faster the object should move when its further away
-        private _sixDofZDragFactor = 5;
+        /**
+         * How much faster the object should move when the controller is moving towards it. This is useful to bring objects that are far away from the user to them faster. Set this to 0 to avoid any speed increase. (Default: 5)
+         */
+         private zDragFactor = 5;
         /**
          * If the behavior is currently in a dragging state
          */
         public dragging = false;
         /**
+         * The distance towards the target drag position to move each frame. This can be useful to avoid jitter. Set this to 1 for no delay. (Default: 0.2)
+         */
+        public dragDeltaRatio = 0.2;
+        /**
          * The id of the pointer that is currently interacting with the behavior (-1 when no pointer is active)
          */
         public currentDraggingPointerID = -1;
@@ -103,7 +109,7 @@ module BABYLON {
                         this._virtualOriginMesh.addChild(this._virtualDragMesh);
                         // Determine how much the controller moved to/away towards the dragged object and use this to move the object further when its further away
                         var zDragDistance = Vector3.Dot(localOriginDragDifference, this._virtualOriginMesh.position.normalizeToNew());
-                        this._virtualDragMesh.position.z -= this._virtualDragMesh.position.z < 1 ? zDragDistance*this._sixDofZDragFactor : zDragDistance*this._sixDofZDragFactor*this._virtualDragMesh.position.z;
+                        this._virtualDragMesh.position.z -= this._virtualDragMesh.position.z < 1 ? zDragDistance*this.zDragFactor : zDragDistance*this.zDragFactor*this._virtualDragMesh.position.z;
                         if(this._virtualDragMesh.position.z < 0){
                             this._virtualDragMesh.position.z = 0;
                         }
@@ -126,7 +132,7 @@ module BABYLON {
             this._sceneRenderObserver = ownerNode.getScene().onBeforeRenderObservable.add(()=>{
                 if(this.dragging && pickedMesh){
                     // Slowly move mesh to avoid jitter
-                    pickedMesh.position.addInPlace(this._targetPosition.subtract(pickedMesh.position).scale(0.2));
+                    pickedMesh.position.addInPlace(this._targetPosition.subtract(pickedMesh.position).scale(this.dragDeltaRatio));
                 }
             });
         }

+ 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.7";
+            return "3.3.0-alpha.8";
         }
 
         // Updatable statics so stick with vars here

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

@@ -12,7 +12,8 @@ module BABYLON {
          */
         constructor(gizmoLayer:UtilityLayerRenderer, dragAxis:Vector3, color:Color3){
             super(gizmoLayer);
-
+            this.updateGizmoRotationToMatchAttachedMesh = false;
+            
             // Create Material
             var coloredMaterial = new BABYLON.StandardMaterial("", gizmoLayer.utilityLayerScene);
             coloredMaterial.disableLighting = true;

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

@@ -12,7 +12,8 @@ module BABYLON {
          */
         constructor(gizmoLayer:UtilityLayerRenderer, dragAxis:Vector3, color:Color3){
             super(gizmoLayer);
-
+            this.updateGizmoRotationToMatchAttachedMesh=false;
+            
             // Create Material
             var coloredMaterial = new BABYLON.StandardMaterial("", gizmoLayer.utilityLayerScene);
             coloredMaterial.disableLighting = true;

+ 2 - 6
src/Gizmos/babylon.boundingBoxGizmo.ts

@@ -9,6 +9,7 @@ module BABYLON {
         private _boundingDimensions = new BABYLON.Vector3(1,1,1);
         private _renderObserver:Nullable<Observer<Scene>> = null;
         private _pointerObserver:Nullable<Observer<PointerInfo>> = null;
+        private _scaleDragSpeed = 0.2;
 
         /**
          * Creates an BoundingBoxGizmo
@@ -121,6 +122,7 @@ module BABYLON {
                                 
                                 // Get the change in bounding box size/2 and add this to the mesh's position to offset from scaling with center pivot point
                                 var deltaScale = new Vector3(event.dragDistance,event.dragDistance,event.dragDistance);
+                                deltaScale.scaleInPlace(this._scaleDragSpeed);
                                 var scaleRatio = deltaScale.divide(this.attachedMesh.scaling).scaleInPlace(0.5);
                                 var moveDirection = boundBoxDimensions.multiply(scaleRatio).multiplyInPlace(dragAxis);
                                 var worldMoveDirection = Vector3.TransformCoordinates(moveDirection, this.attachedMesh.getWorldMatrix().getRotationMatrix());
@@ -184,12 +186,6 @@ module BABYLON {
                 var boundBoxDimensions = boundingInfo.maximum.subtract(boundingInfo.minimum).multiplyInPlace(this.attachedMesh.scaling);
                 this._boundingDimensions.copyFrom(boundBoxDimensions);
                 this._lineBoundingBox.scaling.copyFrom(this._boundingDimensions);
-                if(!this.attachedMesh.rotationQuaternion){
-                    this.attachedMesh.rotationQuaternion = new BABYLON.Quaternion();
-                }
-                this._lineBoundingBox.rotationQuaternion!.copyFrom(this.attachedMesh.rotationQuaternion);
-                this._rotateSpheresParent.rotationQuaternion!.copyFrom(this.attachedMesh.rotationQuaternion);
-                this._scaleBoxesParent.rotationQuaternion!.copyFrom(this.attachedMesh.rotationQuaternion);
             }
 
             // Update rotation sphere locations

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

@@ -12,6 +12,14 @@ module BABYLON {
          */
         public attachedMesh:Nullable<AbstractMesh>;
         /**
+         * If set the gizmo's rotation will be updated to match the attached mesh each frame (Default: true)
+         */
+        public updateGizmoRotationToMatchAttachedMesh = true;
+        /**
+         * If set the gizmo's position will be updated to match the attached mesh each frame (Default: true)
+         */
+        public updateGizmoPositionToMatchAttachedMesh = true;
+        /**
          * When set, the gizmo will always appear the same size no matter where the camera is (default: false)
          */
         protected _updateScale = true;
@@ -44,7 +52,15 @@ module BABYLON {
                     this._rootMesh.scaling.set(dist, dist, dist);
                 }
                 if(this.attachedMesh){
-                    this._rootMesh.position.copyFrom(this.attachedMesh.position);
+                    if(this.updateGizmoRotationToMatchAttachedMesh){
+                        if(!this._rootMesh.rotationQuaternion){
+                            this._rootMesh.rotationQuaternion = new BABYLON.Quaternion();
+                        }
+                        Quaternion.FromRotationMatrixToRef(this.attachedMesh.getWorldMatrix().getRotationMatrix(), this._rootMesh.rotationQuaternion);
+                    }
+                    if(this.updateGizmoPositionToMatchAttachedMesh){
+                        this._rootMesh.position.copyFrom(this.attachedMesh.absolutePosition);
+                    }
                 }
             })
         }

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

@@ -12,7 +12,8 @@ module BABYLON {
          */
         constructor(gizmoLayer:UtilityLayerRenderer, planeNormal:Vector3, color:Color3){
             super(gizmoLayer);
-
+            this.updateGizmoRotationToMatchAttachedMesh=false;
+            
             // Create Material
             var coloredMaterial = new BABYLON.StandardMaterial("", gizmoLayer.utilityLayerScene);
             coloredMaterial.disableLighting = true;

+ 2 - 2
src/Helpers/babylon.environmentHelper.ts

@@ -515,7 +515,7 @@ module BABYLON {
          * Setup the ground according to the specified options.
          */
         private _setupGround(sceneSize: ISceneSize): void {
-            if (!this._ground) {
+            if (!this._ground || this._ground.isDisposed()) {
                 this._ground = Mesh.CreatePlane("BackgroundPlane", sceneSize.groundSize, this._scene);
                 this._ground.rotation.x = Math.PI / 2; // Face up by default.
                 this._ground.parent = this._rootMesh;
@@ -623,7 +623,7 @@ module BABYLON {
          * Setup the skybox according to the specified options.
          */
         private _setupSkybox(sceneSize: ISceneSize): void {
-            if (!this._skybox) {
+            if (!this._skybox || this._skybox.isDisposed()) {
                 this._skybox = Mesh.CreateBox("BackgroundSkybox", sceneSize.skyboxSize, this._scene, undefined, Mesh.BACKSIDE);
                 this._skybox.onDisposeObservable.add(() => { this._skybox = null; })
             }

+ 81 - 42
src/Particles/babylon.gpuParticleSystem.ts

@@ -57,7 +57,7 @@
 
         private _randomTexture: RawTexture;
 
-        private readonly _attributesStrideSize = 19;
+        private _attributesStrideSize = 18;
         private _updateEffectOptions: EffectCreationOptions;
 
         private _randomTextureSize: number;
@@ -359,6 +359,8 @@
             }
 
             this._isBillboardBased = value;
+
+            this._releaseBuffers();
         }          
 
         /**
@@ -390,7 +392,7 @@
             this._scene.particleSystems.push(this);
 
             this._updateEffectOptions = {
-                attributes: ["position", "age", "life", "seed", "size", "color", "direction", "angle"],
+                attributes: ["position", "age", "life", "seed", "size", "color", "direction", "initialDirection", "angle"],
                 uniformsNames: ["currentCount", "timeDelta", "generalRandoms", "emitterWM", "lifeTime", "color1", "color2", "sizeRange", "scaleRange","gravity", "emitPower",
                                 "direction1", "direction2", "minEmitBox", "maxEmitBox", "radius", "directionRandomizer", "height", "coneAngle", "stopFactor", 
                                 "angleRange", "radiusRange"],
@@ -402,7 +404,7 @@
                 onError: null,
                 indexParameters: null,
                 maxSimultaneousLights: 0,                                                      
-                transformFeedbackVaryings: ["outPosition", "outAge", "outLife", "outSeed", "outSize", "outColor", "outDirection", "outAngle"]
+                transformFeedbackVaryings: []
             };
 
             // Random data
@@ -430,8 +432,15 @@
             updateVertexBuffers["seed"] = source.createVertexBuffer("seed", 5, 1);
             updateVertexBuffers["size"] = source.createVertexBuffer("size", 6, 3);
             updateVertexBuffers["color"] = source.createVertexBuffer("color", 9, 4);
-            updateVertexBuffers["direction"] = source.createVertexBuffer("direction", 13, 4);
-            updateVertexBuffers["angle"] = source.createVertexBuffer("angle", 17, 2);
+            updateVertexBuffers["direction"] = source.createVertexBuffer("direction", 13, 3);
+
+            let offset = 16;
+            if (!this._isBillboardBased) {
+                updateVertexBuffers["initialDirection"] = source.createVertexBuffer("initialDirection", offset, 3);
+                offset += 3;
+            }
+
+            updateVertexBuffers["angle"] = source.createVertexBuffer("angle", offset, 2);
            
             let vao = this._engine.recordVertexArrayObject(updateVertexBuffers, null, this._updateEffect);
             this._engine.bindArrayBuffer(null);
@@ -446,8 +455,13 @@
             renderVertexBuffers["life"] = source.createVertexBuffer("life", 4, 1, this._attributesStrideSize, true);
             renderVertexBuffers["size"] = source.createVertexBuffer("size", 6, 3, this._attributesStrideSize, true);           
             renderVertexBuffers["color"] = source.createVertexBuffer("color", 9, 4, this._attributesStrideSize, true);
-            renderVertexBuffers["direction"] = source.createVertexBuffer("direction", 13, 4, this._attributesStrideSize, true);
-            renderVertexBuffers["angle"] = source.createVertexBuffer("angle", 17, 2, this._attributesStrideSize, true);
+
+            let offset = 16;
+            if (!this._isBillboardBased) {
+                renderVertexBuffers["initialDirection"] = source.createVertexBuffer("initialDirection", offset, 3, this._attributesStrideSize, true);
+                offset += 3;
+            }
+            renderVertexBuffers["angle"] = source.createVertexBuffer("angle", offset, 2, this._attributesStrideSize, true);
 
             renderVertexBuffers["offset"] = spriteSource.createVertexBuffer("offset", 0, 2);
             renderVertexBuffers["uv"] = spriteSource.createVertexBuffer("uv", 2, 2);
@@ -465,39 +479,50 @@
 
             let engine = this._scene.getEngine();
             var data = new Array<float>();
+
+            if (!this.isBillboardBased) {
+                this._attributesStrideSize = 21;
+            }
+
             for (var particleIndex = 0; particleIndex < this._capacity; particleIndex++) {
-              // position
-              data.push(0.0);
-              data.push(0.0);
-              data.push(0.0);
-
-              // Age and life
-              data.push(0.0); // create the particle as a dead one to create a new one at start
-              data.push(0.0);
-
-              // Seed
-              data.push(Math.random());
-
-              // Size
-              data.push(0.0);
-              data.push(0.0);
-              data.push(0.0);
-
-              // color
-              data.push(0.0);
-              data.push(0.0);
-              data.push(0.0);                     
-              data.push(0.0); 
-
-              // direction
-              data.push(0.0);
-              data.push(0.0);
-              data.push(0.0);  
-              data.push(0.0);    
-              
-              // angle
-              data.push(0.0);  
-              data.push(0.0); 
+                // position
+                data.push(0.0);
+                data.push(0.0);
+                data.push(0.0);
+
+                // Age and life
+                data.push(0.0); // create the particle as a dead one to create a new one at start
+                data.push(0.0);
+
+                // Seed
+                data.push(Math.random());
+
+                // Size
+                data.push(0.0);
+                data.push(0.0);
+                data.push(0.0);
+
+                // color
+                data.push(0.0);
+                data.push(0.0);
+                data.push(0.0);                     
+                data.push(0.0); 
+
+                // direction
+                data.push(0.0);
+                data.push(0.0);
+                data.push(0.0);  
+
+                if (!this.isBillboardBased) {
+                    // initialDirection
+                    data.push(0.0);
+                    data.push(0.0);
+                    data.push(0.0);  
+                }
+
+                // angle
+                data.push(0.0);  
+                data.push(0.0); 
             }
 
             // Sprite data
@@ -530,9 +555,23 @@
         /** @hidden */
         public _recreateUpdateEffect() {
             let defines = this.particleEmitterType ? this.particleEmitterType.getEffectDefines() : "";
+
+            if (this._isBillboardBased) {
+                defines += "\n#define BILLBOARD";
+            }   
+
             if (this._updateEffect && this._updateEffectOptions.defines === defines) {
                 return;
             }
+
+            this._updateEffectOptions.transformFeedbackVaryings = ["outPosition", "outAge", "outLife", "outSeed", "outSize", "outColor", "outDirection"];           
+
+            if (!this._isBillboardBased) {
+                this._updateEffectOptions.transformFeedbackVaryings.push("outInitialDirection");
+            }
+
+            this._updateEffectOptions.transformFeedbackVaryings.push("outAngle");
+
             this._updateEffectOptions.defines = defines;
             this._updateEffect = new Effect("gpuUpdateParticles", this._updateEffectOptions, this._scene.getEngine());   
         }
@@ -545,7 +584,7 @@
             }
 
             if (this._isBillboardBased) {
-                defines = "\n#define BILLBOARD";
+                defines += "\n#define BILLBOARD";
             }            
 
             if (this._renderEffect && this._renderEffect.defines === defines) {
@@ -553,7 +592,7 @@
             }
 
             this._renderEffect = new Effect("gpuRenderParticles", 
-                                            ["position", "age", "life", "size", "color", "offset", "uv", "direction", "angle"], 
+                                            ["position", "age", "life", "size", "color", "offset", "uv", "initialDirection", "angle"], 
                                             ["view", "projection", "colorDead", "invView", "vClipPlane"], 
                                             ["textureSampler"], this._scene.getEngine(), defines);
         }        
@@ -595,7 +634,7 @@
             this._currentRenderId = this._scene.getRenderId();      
             
             // Get everything ready to render
-            this. _initialize();
+            this._initialize();
 
             this._currentActiveCount = Math.min(this._activeCount, this._currentActiveCount + (this.emitRate * this._timeDelta) | 0);
             

+ 13 - 6
src/Particles/babylon.particle.ts

@@ -58,15 +58,13 @@
         /**
          * Defines the cell index used by the particle to be rendered from a sprite.
          */
-        public cellIndex: number = 0;
-
-        /**
-         * Defines the energy applied to the particle direction.
-         */
-        public emitPower = 0;        
+        public cellIndex: number = 0;  
 
         private _currentFrameCounter = 0;
 
+        /** @hidden */
+        public _initialDirection: Nullable<Vector3>;
+
         /**
          * Creates a new instance Particle
          * @param particleSystem the particle system the particle belongs to
@@ -138,6 +136,15 @@
          */
         public copyTo(other: Particle) {
             other.position.copyFrom(this.position);
+            if (this._initialDirection) {
+                if (other._initialDirection) {
+                    other._initialDirection.copyFrom(this._initialDirection);
+                } else {
+                    other._initialDirection = this._initialDirection.clone();
+                }
+            } else {
+                other._initialDirection = null;
+            }
             other.direction.copyFrom(this.direction);
             other.color.copyFrom(this.color);
             other.colorStep.copyFrom(this.colorStep);

+ 25 - 7
src/Particles/babylon.particleSystem.ts

@@ -459,7 +459,7 @@
                         continue;
                     }
                     else {
-                        if (this._colorGradients) {
+                        if (this._colorGradients && this._colorGradients.length > 0) {
                             let ratio = particle.age / particle.lifeTime;
 
                             for (var gradientIndex = 0; gradientIndex < this._colorGradients.length - 1; gradientIndex++) {
@@ -483,7 +483,7 @@
                         }
                         particle.angle += particle.angularSpeed * this._scaledUpdateSpeed;
 
-                        particle.direction.scaleToRef(this._scaledUpdateSpeed * particle.emitPower, this._scaledDirection);
+                        particle.direction.scaleToRef(this._scaledUpdateSpeed, this._scaledDirection);
                         particle.position.addInPlace(this._scaledDirection);
 
                         this.gravity.scaleToRef(this._scaledUpdateSpeed, this._scaledGravity);
@@ -715,9 +715,15 @@
             }
 
             if (!this._isBillboardBased) {
-                this._vertexData[offset++] = particle.direction.x;
-                this._vertexData[offset++] = particle.direction.y;
-                this._vertexData[offset++] = particle.direction.z;
+                if (particle._initialDirection) {
+                    this._vertexData[offset++] = particle._initialDirection.x;
+                    this._vertexData[offset++] = particle._initialDirection.y;
+                    this._vertexData[offset++] = particle._initialDirection.z;
+                } else {
+                    this._vertexData[offset++] = particle.direction.x;
+                    this._vertexData[offset++] = particle.direction.y;
+                    this._vertexData[offset++] = particle.direction.z;
+                }
             }
 
             if (!this._useInstancing) {
@@ -828,7 +834,7 @@
 
                 this._particles.push(particle);
 
-                particle.emitPower = Scalar.RandomRange(this.minEmitPower, this.maxEmitPower);
+                let emitPower = Scalar.RandomRange(this.minEmitPower, this.maxEmitPower);
 
                 if (this.startPositionFunction) {
                     this.startPositionFunction(worldMatrix, particle.position, particle);
@@ -844,13 +850,25 @@
                     this.particleEmitterType.startDirectionFunction(worldMatrix, particle.direction, particle);
                 }
 
+                if (emitPower === 0) {
+                    if (!particle._initialDirection) {
+                        particle._initialDirection = particle.direction.clone();
+                    } else {
+                        particle._initialDirection.copyFrom(particle.direction);
+                    }
+                } else {
+                    particle._initialDirection = null;
+                }
+
+                particle.direction.scaleInPlace(emitPower);
+
                 particle.lifeTime = Scalar.RandomRange(this.minLifeTime, this.maxLifeTime);
 
                 particle.size = Scalar.RandomRange(this.minSize, this.maxSize);
                 particle.scale.copyFromFloats(Scalar.RandomRange(this.minScaleX, this.maxScaleX), Scalar.RandomRange(this.minScaleY, this.maxScaleY));
                 particle.angularSpeed = Scalar.RandomRange(this.minAngularSpeed, this.maxAngularSpeed);
 
-                if (!this._colorGradients) {
+                if (!this._colorGradients || this._colorGradients.length === 0) {
                     var step = Scalar.RandomRange(0, 1.0);
 
                     Color4.LerpToRef(this.color1, this.color2, step, particle.color);

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

@@ -10,10 +10,12 @@ in float age;
 in float life;
 in vec3 size;
 in vec4 color;
+#ifndef BILLBOARD
+in vec3 initialDirection;
+#endif
+in vec2 angle;
 in vec2 offset;
 in vec2 uv;
-in vec4 direction;
-in vec2 angle;
 
 out vec2 vUV;
 out vec4 vColor;
@@ -50,7 +52,7 @@ void main() {
 	rotatedCorner.y = 0.;
 	rotatedCorner.z = cornerPos.x * sin(angle.x) + cornerPos.y * cos(angle.x);
 
-	vec3 yaxis = normalize(direction.xyz);
+	vec3 yaxis = normalize(initialDirection);
 	vec3 xaxis = normalize(cross(vec3(0., 1.0, 0.), yaxis));
 	vec3 zaxis = normalize(cross(yaxis, xaxis));
 

+ 22 - 7
src/Shaders/gpuUpdateParticles.vertex.fx

@@ -49,7 +49,10 @@ in float life;
 in float seed;
 in vec3 size;
 in vec4 color;
-in vec4 direction;
+in vec3 direction;
+#ifndef BILLBOARD
+in vec3 initialDirection;
+#endif
 in vec2 angle;
 
 // Output
@@ -59,7 +62,10 @@ out float outLife;
 out float outSeed;
 out vec3 outSize;
 out vec4 outColor;
-out vec4 outDirection;
+out vec3 outDirection;
+#ifndef BILLBOARD
+out vec3 outInitialDirection;
+#endif
 out vec2 outAngle;
 
 vec3 getRandomVec3(float offset) {
@@ -80,6 +86,9 @@ void main() {
       outSeed = seed;
       outColor = vec4(0.,0.,0.,0.);
       outSize = vec3(0., 0., 0.);
+#ifndef BILLBOARD        
+      outInitialDirection = initialDirection;
+#endif      
       outDirection = direction;
       outAngle = angle;
       return;
@@ -168,20 +177,26 @@ void main() {
     direction = 2.0 * (getRandomVec3(seed) - vec3(0.5, 0.5, 0.5));
 #endif
 
-    outDirection.w = emitPower.x + (emitPower.y - emitPower.x) * randoms.a;
+    float power = emitPower.x + (emitPower.y - emitPower.x) * randoms.a;
 
     outPosition = (emitterWM * vec4(position, 1.)).xyz;
-    outDirection.xyz = (emitterWM * vec4(direction, 0.)).xyz;
+    vec3 initial = (emitterWM * vec4(direction, 0.)).xyz;
+    outDirection = initial * power;
+#ifndef BILLBOARD        
+    outInitialDirection = initial;
+#endif
 
   } else {   
-    outPosition = position + direction.xyz * timeDelta * direction.w;
+    outPosition = position + direction * timeDelta;
     outAge = age + timeDelta;
     outLife = life;
     outSeed = seed;
     outColor = color;
     outSize = size;
-    outDirection.w = direction.w;
-    outDirection.xyz = direction.xyz + gravity * timeDelta;
+#ifndef BILLBOARD    
+    outInitialDirection = initialDirection;
+#endif
+    outDirection = direction + gravity * timeDelta;
     outAngle = vec2(angle.x + angle.y * timeDelta, angle.y);
   }
 }

+ 73 - 0
tests/unit/babylon/src/Animations/babylon.animationGroup.tests.ts

@@ -0,0 +1,73 @@
+/**
+ * Describes the test suite.
+ */
+describe('Babylon Animation Group', 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
+        });
+
+        // Avoid creating normals in PBR materials.
+        subject.getCaps().standardDerivatives = true;
+    });
+
+    /**
+     * Animation group tests.
+     */
+    describe('#AnimationGroup', () => {
+        it('start and stop', () => {
+            const scene = new BABYLON.Scene(subject);
+            const node = new BABYLON.TransformNode("node0", scene);
+
+            const animationGroup = new BABYLON.AnimationGroup("animationGroup0", scene);
+
+            const length = 10;
+            for (let i = 0; i < length; i++) {
+                const animation = new BABYLON.Animation(`animation${i}`, "", 1, BABYLON.Animation.ANIMATIONTYPE_VECTOR3);
+                animation.setKeys([
+                    {
+                        frame: 0,
+                        value: BABYLON.Vector3.Zero()
+                    },
+                    {
+                        frame: 1,
+                        value: BABYLON.Vector3.Zero()
+                    }
+                ]);
+
+                animationGroup.addTargetedAnimation(animation, node);
+            }
+
+            animationGroup.start();
+            expect(animationGroup.animatables.length, "animationGroup.animatables.length").to.equal(length);
+            expect(scene.animatables.length, "scene.animatables.length").to.equal(length);
+
+            animationGroup.stop();
+            expect(animationGroup.animatables.length, "animationGroup.animatables.length").to.equal(0);
+            expect(scene.animatables.length, "scene.animatables.length").to.equal(0);
+        });
+    });
+});

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

@@ -16,6 +16,7 @@ module.exports = function (config) {
             './tests/unit/babylon/babylon.example.tests.js',
             './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',

BIN
tests/validation/ReferenceImages/particles.png


BIN
tests/validation/ReferenceImages/yeti.png