瀏覽代碼

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

noalak 8 年之前
父節點
當前提交
f180cee0fb
共有 61 個文件被更改,包括 486015 次插入21597 次删除
  1. 3 1
      Tools/Gulp/config.json
  2. 2 0
      assets/meshes/controllers/_headers
  3. 33778 0
      assets/meshes/controllers/generic/generic.babylon
  4. 二進制
      assets/meshes/controllers/generic/vr_controller_01_mrhat.png
  5. 二進制
      assets/meshes/controllers/generic/vr_controller_01_mrhat_MetallicGlossMap.png
  6. 21 0
      assets/meshes/controllers/microsoft/045E-065B/LICENSE
  7. 二進制
      assets/meshes/controllers/microsoft/045E-065B/left.glb
  8. 二進制
      assets/meshes/controllers/microsoft/045E-065B/right.glb
  9. 21 0
      assets/meshes/controllers/microsoft/default/LICENSE
  10. 二進制
      assets/meshes/controllers/microsoft/default/left.glb
  11. 二進制
      assets/meshes/controllers/microsoft/default/right.glb
  12. 二進制
      assets/meshes/controllers/oculus/external_controller01_col.png
  13. 二進制
      assets/meshes/controllers/oculus/external_controller01_col_MetallicGlossMap.png
  14. 128945 0
      assets/meshes/controllers/oculus/left.babylon
  15. 128945 0
      assets/meshes/controllers/oculus/right.babylon
  16. 二進制
      assets/meshes/controllers/vive/onepointfive_texture.png
  17. 二進制
      assets/meshes/controllers/vive/onepointfive_texture_MetallicGlossMap.png
  18. 170697 0
      assets/meshes/controllers/vive/wand.babylon
  19. 6775 6687
      dist/preview release/babylon.d.ts
  20. 40 39
      dist/preview release/babylon.js
  21. 923 400
      dist/preview release/babylon.max.js
  22. 6775 6687
      dist/preview release/babylon.module.d.ts
  23. 44 43
      dist/preview release/babylon.worker.js
  24. 3476 3388
      dist/preview release/customConfigurations/minimalGLTFViewer/babylon.d.ts
  25. 47 46
      dist/preview release/customConfigurations/minimalGLTFViewer/babylon.js
  26. 932 405
      dist/preview release/customConfigurations/minimalGLTFViewer/babylon.max.js
  27. 3476 3388
      dist/preview release/customConfigurations/minimalGLTFViewer/babylon.module.d.ts
  28. 2 2
      dist/preview release/inspector/babylon.inspector.bundle.js
  29. 1 0
      dist/preview release/loaders/babylon.glTF2FileLoader.d.ts
  30. 9 5
      dist/preview release/loaders/babylon.glTF2FileLoader.js
  31. 1 1
      dist/preview release/loaders/babylon.glTF2FileLoader.min.js
  32. 1 0
      dist/preview release/loaders/babylon.glTFFileLoader.d.ts
  33. 9 5
      dist/preview release/loaders/babylon.glTFFileLoader.js
  34. 3 3
      dist/preview release/loaders/babylon.glTFFileLoader.min.js
  35. 1 1
      dist/preview release/loaders/babylon.objFileLoader.min.js
  36. 1 0
      dist/preview release/what's new.md
  37. 10 4
      loaders/src/glTF/2.0/babylon.glTFLoader.ts
  38. 1 1
      loaders/src/glTF/2.0/babylon.glTFLoaderUtils.ts
  39. 17 6
      sandbox/index.js
  40. 54 38
      src/Animations/babylon.animatable.ts
  41. 25 356
      src/Animations/babylon.animation.ts
  42. 390 0
      src/Animations/babylon.runtimeAnimation.ts
  43. 1 1
      src/Bones/babylon.bone.ts
  44. 5 1
      src/Cameras/Inputs/babylon.arcRotateCameraPointersInput.ts
  45. 8 0
      src/Cameras/Inputs/babylon.freeCameraMouseInput.ts
  46. 25 23
      src/Cameras/VR/babylon.webVRCamera.ts
  47. 4 1
      src/Cameras/babylon.arcRotateCamera.ts
  48. 19 21
      src/Cameras/babylon.camera.ts
  49. 3 2
      src/Gamepad/Controllers/babylon.genericController.ts
  50. 14 3
      src/Gamepad/Controllers/babylon.oculusTouchController.ts
  51. 9 8
      src/Gamepad/Controllers/babylon.poseEnabledController.ts
  52. 3 2
      src/Gamepad/Controllers/babylon.viveController.ts
  53. 19 5
      src/Gamepad/Controllers/babylon.webVRController.ts
  54. 384 0
      src/Gamepad/Controllers/babylon.windowsMotionController.ts
  55. 6 0
      src/Gamepad/babylon.gamepad.ts
  56. 31 11
      src/Gamepad/babylon.gamepadManager.ts
  57. 27 0
      src/Mesh/babylon.abstractMesh.ts
  58. 1 1
      src/Mesh/babylon.meshBuilder.ts
  59. 10 2
      src/Tools/babylon.filesInput.ts
  60. 11 2
      src/babylon.engine.ts
  61. 10 8
      src/babylon.scene.ts

+ 3 - 1
Tools/Gulp/config.json

@@ -175,6 +175,7 @@
         {
             "files": [
                 "../../src/Animations/babylon.animation.js",
+                "../../src/Animations/babylon.runtimeAnimation.js",
                 "../../src/Animations/babylon.animatable.js",
                 "../../src/Animations/babylon.easing.js"
             ],
@@ -544,7 +545,8 @@
                 "../../src/Gamepad/Controllers/babylon.webVRController.js",
                 "../../src/Gamepad/Controllers/babylon.oculusTouchController.js",
                 "../../src/Gamepad/Controllers/babylon.viveController.js",
-                "../../src/Gamepad/Controllers/babylon.genericController.js"
+                "../../src/Gamepad/Controllers/babylon.genericController.js",
+                "../../src/Gamepad/Controllers/babylon.windowsMotionController.js"
             ],
             "dependUpon" : [
                 "core"

+ 2 - 0
assets/meshes/controllers/_headers

@@ -0,0 +1,2 @@
+/*
+	Access-Control-Allow-Origin: *

File diff suppressed because it is too large
+ 33778 - 0
assets/meshes/controllers/generic/generic.babylon


二進制
assets/meshes/controllers/generic/vr_controller_01_mrhat.png


二進制
assets/meshes/controllers/generic/vr_controller_01_mrhat_MetallicGlossMap.png


+ 21 - 0
assets/meshes/controllers/microsoft/045E-065B/LICENSE

@@ -0,0 +1,21 @@
+The MIT License
+
+Copyright © Microsoft 2017. All rights reserved.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.

二進制
assets/meshes/controllers/microsoft/045E-065B/left.glb


二進制
assets/meshes/controllers/microsoft/045E-065B/right.glb


+ 21 - 0
assets/meshes/controllers/microsoft/default/LICENSE

@@ -0,0 +1,21 @@
+The MIT License
+
+Copyright © Microsoft 2017. All rights reserved.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.

二進制
assets/meshes/controllers/microsoft/default/left.glb


二進制
assets/meshes/controllers/microsoft/default/right.glb


二進制
assets/meshes/controllers/oculus/external_controller01_col.png


二進制
assets/meshes/controllers/oculus/external_controller01_col_MetallicGlossMap.png


File diff suppressed because it is too large
+ 128945 - 0
assets/meshes/controllers/oculus/left.babylon


File diff suppressed because it is too large
+ 128945 - 0
assets/meshes/controllers/oculus/right.babylon


二進制
assets/meshes/controllers/vive/onepointfive_texture.png


二進制
assets/meshes/controllers/vive/onepointfive_texture_MetallicGlossMap.png


File diff suppressed because it is too large
+ 170697 - 0
assets/meshes/controllers/vive/wand.babylon


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


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


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


File diff suppressed because it is too large
+ 6775 - 6687
dist/preview release/babylon.module.d.ts


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


File diff suppressed because it is too large
+ 3476 - 3388
dist/preview release/customConfigurations/minimalGLTFViewer/babylon.d.ts


File diff suppressed because it is too large
+ 47 - 46
dist/preview release/customConfigurations/minimalGLTFViewer/babylon.js


File diff suppressed because it is too large
+ 932 - 405
dist/preview release/customConfigurations/minimalGLTFViewer/babylon.max.js


File diff suppressed because it is too large
+ 3476 - 3388
dist/preview release/customConfigurations/minimalGLTFViewer/babylon.module.d.ts


+ 2 - 2
dist/preview release/inspector/babylon.inspector.bundle.js

@@ -65,8 +65,8 @@ var INSPECTOR =
 	if(false) {
 		// When the styles change, update the <style> tags
 		if(!content.locals) {
-			module.hot.accept("!!../../../tools/gulp/node_modules/css-loader/index.js!./babylon.inspector.css", function() {
-				var newContent = require("!!../../../tools/gulp/node_modules/css-loader/index.js!./babylon.inspector.css");
+			module.hot.accept("!!../../../Tools/Gulp/node_modules/css-loader/index.js!./babylon.inspector.css", function() {
+				var newContent = require("!!../../../Tools/Gulp/node_modules/css-loader/index.js!./babylon.inspector.css");
 				if(typeof newContent === 'string') newContent = [[module.id, newContent, '']];
 				update(newContent);
 			});

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

@@ -301,6 +301,7 @@ declare module BABYLON.GLTF2 {
         private _disposed;
         private _blockPendingTracking;
         private _nonBlockingData;
+        private _rootMesh;
         private _renderReadyObservable;
         private _renderPendingCount;
         private _loaderPendingCount;

+ 9 - 5
dist/preview release/loaders/babylon.glTF2FileLoader.js

@@ -462,21 +462,25 @@ var BABYLON;
                 }
             };
             GLTFLoader.prototype._addRightHandToLeftHandRootTransform = function () {
-                var rootMesh = new BABYLON.Mesh("root", this._babylonScene);
-                rootMesh.scaling = new BABYLON.Vector3(1, 1, -1);
-                rootMesh.rotation.y = Math.PI;
+                this._rootMesh = new BABYLON.Mesh("root", this._babylonScene);
+                this._rootMesh.isVisible = false;
+                this._rootMesh.scaling = new BABYLON.Vector3(1, 1, -1);
+                this._rootMesh.rotation.y = Math.PI;
                 var nodes = this._gltf.nodes;
                 if (nodes) {
                     for (var i = 0; i < nodes.length; i++) {
                         var mesh = nodes[i].babylonMesh;
                         if (mesh && !mesh.parent) {
-                            mesh.parent = rootMesh;
+                            mesh.parent = this._rootMesh;
                         }
                     }
                 }
             };
             GLTFLoader.prototype._getMeshes = function () {
                 var meshes = [];
+                if (this._rootMesh) {
+                    meshes.push(this._rootMesh);
+                }
                 var nodes = this._gltf.nodes;
                 if (nodes) {
                     nodes.forEach(function (node) {
@@ -1420,7 +1424,7 @@ var BABYLON;
             GLTFUtils.GetTextureSamplingMode = function (magFilter, minFilter) {
                 // Set defaults if undefined
                 magFilter = magFilter === undefined ? GLTF2.ETextureMagFilter.LINEAR : magFilter;
-                minFilter = minFilter === undefined ? GLTF2.ETextureMinFilter.LINEAR_MIPMAP_NEAREST : minFilter;
+                minFilter = minFilter === undefined ? GLTF2.ETextureMinFilter.LINEAR_MIPMAP_LINEAR : minFilter;
                 if (magFilter === GLTF2.ETextureMagFilter.LINEAR) {
                     switch (minFilter) {
                         case GLTF2.ETextureMinFilter.NEAREST: return BABYLON.Texture.LINEAR_NEAREST;

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


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

@@ -797,6 +797,7 @@ declare module BABYLON.GLTF2 {
         private _disposed;
         private _blockPendingTracking;
         private _nonBlockingData;
+        private _rootMesh;
         private _renderReadyObservable;
         private _renderPendingCount;
         private _loaderPendingCount;

+ 9 - 5
dist/preview release/loaders/babylon.glTFFileLoader.js

@@ -2620,21 +2620,25 @@ var BABYLON;
                 }
             };
             GLTFLoader.prototype._addRightHandToLeftHandRootTransform = function () {
-                var rootMesh = new BABYLON.Mesh("root", this._babylonScene);
-                rootMesh.scaling = new BABYLON.Vector3(1, 1, -1);
-                rootMesh.rotation.y = Math.PI;
+                this._rootMesh = new BABYLON.Mesh("root", this._babylonScene);
+                this._rootMesh.isVisible = false;
+                this._rootMesh.scaling = new BABYLON.Vector3(1, 1, -1);
+                this._rootMesh.rotation.y = Math.PI;
                 var nodes = this._gltf.nodes;
                 if (nodes) {
                     for (var i = 0; i < nodes.length; i++) {
                         var mesh = nodes[i].babylonMesh;
                         if (mesh && !mesh.parent) {
-                            mesh.parent = rootMesh;
+                            mesh.parent = this._rootMesh;
                         }
                     }
                 }
             };
             GLTFLoader.prototype._getMeshes = function () {
                 var meshes = [];
+                if (this._rootMesh) {
+                    meshes.push(this._rootMesh);
+                }
                 var nodes = this._gltf.nodes;
                 if (nodes) {
                     nodes.forEach(function (node) {
@@ -3578,7 +3582,7 @@ var BABYLON;
             GLTFUtils.GetTextureSamplingMode = function (magFilter, minFilter) {
                 // Set defaults if undefined
                 magFilter = magFilter === undefined ? GLTF2.ETextureMagFilter.LINEAR : magFilter;
-                minFilter = minFilter === undefined ? GLTF2.ETextureMinFilter.LINEAR_MIPMAP_NEAREST : minFilter;
+                minFilter = minFilter === undefined ? GLTF2.ETextureMinFilter.LINEAR_MIPMAP_LINEAR : minFilter;
                 if (magFilter === GLTF2.ETextureMagFilter.LINEAR) {
                     switch (minFilter) {
                         case GLTF2.ETextureMinFilter.NEAREST: return BABYLON.Texture.LINEAR_NEAREST;

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


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


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

@@ -13,6 +13,7 @@
 - New InputText for Babylon.GUI. [Doc here](http://doc.babylonjs.com/overviews/gui#inputtext) ([deltakosh](https://github.com/deltakosh))
 - New VirtualKeyboard for Babylon.GUI. [Doc here](http://doc.babylonjs.com/overviews/gui#virtualkeyboard) ([deltakosh](https://github.com/deltakosh) / [adam](https://github.com/abow))
 - Added support for depth pre-pass rendering. [Doc here](http://doc.babylonjs.com/tutorials/transparency_and_how_meshes_are_rendered#depth-pre-pass-meshes) ([deltakosh](https://github.com/deltakosh))
+- Added support for Windows Motion Controllers ([Lewis Weaver](https://github.com/leweaver))
 
 ## Updates
 - New `camera.storeState()` and `camera.restoreState()` functions to store / restore cameras position / rotation / fov. (Doc here)[http://doc.babylonjs.com/tutorials/cameras#state] ([deltakosh](https://github.com/deltakosh))

+ 10 - 4
loaders/src/glTF/2.0/babylon.glTFLoader.ts

@@ -14,6 +14,7 @@ module BABYLON.GLTF2 {
         private _disposed: boolean = false;
         private _blockPendingTracking: boolean = false;
         private _nonBlockingData: Array<any>;
+        private _rootMesh: Mesh;
 
         // Observable with boolean indicating success or error.
         private _renderReadyObservable = new Observable<GLTFLoader>();
@@ -181,16 +182,17 @@ module BABYLON.GLTF2 {
         }
 
         private _addRightHandToLeftHandRootTransform(): void {
-            var rootMesh = new Mesh("root", this._babylonScene);
-            rootMesh.scaling = new Vector3(1, 1, -1);
-            rootMesh.rotation.y = Math.PI;
+            this._rootMesh = new Mesh("root", this._babylonScene);
+            this._rootMesh.isVisible = false;
+            this._rootMesh.scaling = new Vector3(1, 1, -1);
+            this._rootMesh.rotation.y = Math.PI;
 
             var nodes = this._gltf.nodes;
             if (nodes) {
                 for (var i = 0; i < nodes.length; i++) {
                     var mesh = nodes[i].babylonMesh;
                     if (mesh && !mesh.parent) {
-                        mesh.parent = rootMesh;
+                        mesh.parent = this._rootMesh;
                     }
                 }
             }
@@ -199,6 +201,10 @@ module BABYLON.GLTF2 {
         private _getMeshes(): Mesh[] {
             var meshes = [];
 
+            if (this._rootMesh) {
+                meshes.push(this._rootMesh);
+            }
+
             var nodes = this._gltf.nodes;
             if (nodes) {
                 nodes.forEach(node => {

+ 1 - 1
loaders/src/glTF/2.0/babylon.glTFLoaderUtils.ts

@@ -52,7 +52,7 @@ module BABYLON.GLTF2 {
         public static GetTextureSamplingMode(magFilter: ETextureMagFilter, minFilter: ETextureMinFilter): number {
             // Set defaults if undefined
             magFilter = magFilter === undefined ? ETextureMagFilter.LINEAR : magFilter;
-            minFilter = minFilter === undefined ? ETextureMinFilter.LINEAR_MIPMAP_NEAREST : minFilter;
+            minFilter = minFilter === undefined ? ETextureMinFilter.LINEAR_MIPMAP_LINEAR : minFilter;
 
             if (magFilter === ETextureMagFilter.LINEAR) {
                 switch (minFilter) {

+ 17 - 6
sandbox/index.js

@@ -14,6 +14,7 @@
     var filesInput;
     var currentHelpCounter;
     var currentScene;
+    var currentSkybox;
     var enableDebugLayer = false;
     var currentPluginName;
 
@@ -68,20 +69,20 @@
             currentScene.createDefaultCameraOrLight(true);
             // Enable camera's behaviors
             currentScene.activeCamera.useBouncingBehavior = true;
-            currentScene.activeCamera.useAutoRotationBehavior  = true;
             currentScene.activeCamera.useFramingBehavior = true;
 
             var framingBehavior = currentScene.activeCamera.getBehaviorByName("Framing");
             framingBehavior.framingTime = 0;
+            framingBehavior.elevationReturnTime = -1;
 
             var bouncingBehavior = currentScene.activeCamera.getBehaviorByName("Bouncing");
-            bouncingBehavior.autoTransitionRange = true;                
+            bouncingBehavior.autoTransitionRange = true;        
 
             if (currentScene.meshes.length) {
                 // Let's zoom on the first object with geometry
                 for (var index = 0; index < currentScene.meshes.length; index++) {
                     var mesh = currentScene.meshes[index];
-    
+
                     if (mesh.getTotalVertices()) {
                         currentScene.activeCamera.setTarget(mesh);
                         break;
@@ -95,9 +96,7 @@
         // Environment
         if (currentPluginName === "gltf") {
             var hdrTexture = BABYLON.CubeTexture.CreateFromPrefilteredData("Assets/environment.dds", currentScene);
-            hdrTexture.gammaSpace = false;
-                        
-            currentScene.createDefaultSkybox(hdrTexture, true, (currentScene.activeCamera.maxZ - currentScene.activeCamera.minZ) / 2, 0.3);
+            currentSkybox = currentScene.createDefaultSkybox(hdrTexture, true, (currentScene.activeCamera.maxZ - currentScene.activeCamera.minZ) / 2, 0.3);
         }
 
         // In case of error during loading, meshes will be empty and clearColor is set to red
@@ -125,6 +124,18 @@
     };
 
     filesInput = new BABYLON.FilesInput(engine, null, sceneLoaded);
+    filesInput.onProcessFileCallback = (function (file, name, extension) {
+        if (extension === "dds") {
+            BABYLON.FilesInput.FilesToLoad[name] = file;
+            var newHdrTexture = BABYLON.CubeTexture.CreateFromPrefilteredData("file:" + file.correctName, currentScene);
+            if (currentSkybox) {
+                currentSkybox.dispose();
+            }
+            currentSkybox = currentScene.createDefaultSkybox(newHdrTexture, true, (currentScene.activeCamera.maxZ - currentScene.activeCamera.minZ) / 2, 0.3);
+            return false;
+        }
+        return true;
+    }).bind(this);
     filesInput.monitorElementForDragNDrop(canvas);
 
     window.addEventListener("keydown", function (evt) {

+ 54 - 38
src/Animations/babylon.animatable.ts

@@ -2,7 +2,7 @@
     export class Animatable {
         private _localDelayOffset: number = null;
         private _pausedDelay: number = null;
-        private _animations = new Array<Animation>();
+        private _runtimeAnimations = new Array<RuntimeAnimation>();
         private _paused = false;
         private _scene: Scene;
 
@@ -18,36 +18,47 @@
         }
 
         // Methods
-        public getAnimations(): Animation[] {
-            return this._animations;
+        public getAnimations(): RuntimeAnimation[] {
+            return this._runtimeAnimations;
         }
 
         public appendAnimations(target: any, animations: Animation[]): void {
             for (var index = 0; index < animations.length; index++) {
                 var animation = animations[index];
 
-                animation._target = target;
-                this._animations.push(animation);
+                this._runtimeAnimations.push(new RuntimeAnimation(target, animation));
             }
         }
 
-        public getAnimationByTargetProperty(property: string) {
-            var animations = this._animations;
+        public getAnimationByTargetProperty(property: string): Animation {
+            var runtimeAnimations = this._runtimeAnimations;
 
-            for (var index = 0; index < animations.length; index++) {
-                if (animations[index].targetProperty === property) {
-                    return animations[index];
+            for (var index = 0; index < runtimeAnimations.length; index++) {
+                if (runtimeAnimations[index].animation.targetProperty === property) {
+                    return runtimeAnimations[index].animation;
                 }
             }
 
             return null;
         }
 
+        public getRuntimeAnimationByTargetProperty(property: string): RuntimeAnimation {
+            var runtimeAnimations = this._runtimeAnimations;
+
+            for (var index = 0; index < runtimeAnimations.length; index++) {
+                if (runtimeAnimations[index].animation.targetProperty === property) {
+                    return runtimeAnimations[index];
+                }
+            }
+
+            return null;
+        }        
+
         public reset(): void {
-            var animations = this._animations;
+            var runtimeAnimations = this._runtimeAnimations;
 
-            for (var index = 0; index < animations.length; index++) {
-                animations[index].reset();
+            for (var index = 0; index < runtimeAnimations.length; index++) {
+                runtimeAnimations[index].reset();
             }
 
             this._localDelayOffset = null;
@@ -55,35 +66,35 @@
         }
 
         public enableBlending(blendingSpeed: number): void {
-            var animations = this._animations;
+            var runtimeAnimations = this._runtimeAnimations;
 
-            for (var index = 0; index < animations.length; index++) {
-                animations[index].enableBlending = true;
-                animations[index].blendingSpeed = blendingSpeed;
+            for (var index = 0; index < runtimeAnimations.length; index++) {
+                runtimeAnimations[index].animation.enableBlending = true;
+                runtimeAnimations[index].animation.blendingSpeed = blendingSpeed;
             }
         }
 
         public disableBlending(): void {
-            var animations = this._animations;
+            var runtimeAnimations = this._runtimeAnimations;
 
-            for (var index = 0; index < animations.length; index++) {
-                animations[index].enableBlending = false;
+            for (var index = 0; index < runtimeAnimations.length; index++) {
+                runtimeAnimations[index].animation.enableBlending = false;
             }
         }
 
         public goToFrame(frame: number): void {
-            var animations = this._animations;
+            var runtimeAnimations = this._runtimeAnimations;
 
-            if (animations[0]) {
-                var fps = animations[0].framePerSecond;
-                var currentFrame = animations[0].currentFrame;
+            if (runtimeAnimations[0]) {
+                var fps = runtimeAnimations[0].animation.framePerSecond;
+                var currentFrame = runtimeAnimations[0].currentFrame;
                 var adjustTime = frame - currentFrame;
                 var delay = adjustTime * 1000 / fps;
                 this._localDelayOffset -= delay;
             }
 
-            for (var index = 0; index < animations.length; index++) {
-                animations[index].goToFrame(frame);
+            for (var index = 0; index < runtimeAnimations.length; index++) {
+                runtimeAnimations[index].goToFrame(frame);
             }
         }
 
@@ -106,18 +117,18 @@
 
                 if (idx > -1) {
 
-                    var animations = this._animations;
+                    var runtimeAnimations = this._runtimeAnimations;
                     
-                    for (var index = animations.length - 1; index >= 0; index--) {
-                        if (typeof animationName === "string" && animations[index].name != animationName) {
+                    for (var index = runtimeAnimations.length - 1; index >= 0; index--) {
+                        if (typeof animationName === "string" && runtimeAnimations[index].animation.name != animationName) {
                             continue;
                         }
 
-                        animations[index].reset();
-                        animations.splice(index, 1);
+                        runtimeAnimations[index].dispose();
+                        runtimeAnimations.splice(index, 1);
                     }
 
-                    if (animations.length == 0) {
+                    if (runtimeAnimations.length == 0) {
                         this._scene._activeAnimatables.splice(idx, 1);
 
                         if (this.onAnimationEnd) {
@@ -132,10 +143,10 @@
 
                 if (index > -1) {
                     this._scene._activeAnimatables.splice(index, 1);
-                    var animations = this._animations;
+                    var runtimeAnimations = this._runtimeAnimations;
                     
-                    for (var index = 0; index < animations.length; index++) {
-                        animations[index].reset();
+                    for (var index = 0; index < runtimeAnimations.length; index++) {
+                        runtimeAnimations[index].dispose();
                     }
                     
                     if (this.onAnimationEnd) {
@@ -164,11 +175,11 @@
 
             // Animating
             var running = false;
-            var animations = this._animations;
+            var runtimeAnimations = this._runtimeAnimations;
             var index: number;
 
-            for (index = 0; index < animations.length; index++) {
-                var animation = animations[index];
+            for (index = 0; index < runtimeAnimations.length; index++) {
+                var animation = runtimeAnimations[index];
                 var isRunning = animation.animate(delay - this._localDelayOffset, this.fromFrame, this.toFrame, this.loopAnimation, this.speedRatio);
                 running = running || isRunning;
             }
@@ -179,6 +190,11 @@
                 // Remove from active animatables
                 index = this._scene._activeAnimatables.indexOf(this);
                 this._scene._activeAnimatables.splice(index, 1);
+
+                // Dispose all runtime animations
+                for (index = 0; index < runtimeAnimations.length; index++) {
+                    runtimeAnimations[index].dispose();
+                }
             }
 
             if (!running && this.onAnimationEnd) {

+ 25 - 356
src/Animations/babylon.animation.ts

@@ -92,22 +92,16 @@
         public static AllowMatricesInterpolation = false;
 
         private _keys: Array<{frame:number, value: any, inTangent?: any, outTangent?: any}>;
-        private _offsetsCache = {};
-        private _highLimitsCache = {};
-        private _stopped = false;
-        public _target;
-        private _blendingFactor = 0;
         private _easingFunction: IEasingFunction;
 
+        public _runtimeAnimations = new Array<RuntimeAnimation>();
+
         // The set of event that will be linked to this animation
         private _events = new Array<AnimationEvent>();
 
         public targetPropertyPath: string[];
-        public currentFrame: number;
-
 
         public blendingSpeed = 0.01;
-        private _originalBlendValue: any;
 
         private _ranges: { [name: string]: AnimationRange; } = {};
 
@@ -225,7 +219,24 @@
 			var animation: BABYLON.Animatable = scene.beginAnimation(host, 0, endFrame, false);
 			animation.onAnimationEnd = onAnimationEnd;
 			return animation;
-		}
+        }
+        
+        /**
+         * Return the array of runtime animations currently using this animation
+         */
+        public get runtimeAnimations(): RuntimeAnimation[] {
+            return this._runtimeAnimations;
+        }
+
+        public get hasRunningRuntimeAnimations(): boolean {
+            for (var runtimeAnimation of this._runtimeAnimations) {
+                if (!runtimeAnimation.isStopped) {
+                    return true;
+                }
+            }
+
+            return false;
+        }
 
         constructor(public name: string, public targetProperty: string, public framePerSecond: number, public dataType: number, public loopMode?: number, public enableBlending?: boolean) {
             this.targetPropertyPath = targetProperty.split(".");
@@ -277,6 +288,10 @@
             }
         }
 
+        public getEvents(): AnimationEvent[] {
+            return this._events;
+        }
+
         public createRange(name: string, from: number, to: number): void {
             // check name not already in use; could happen for bones after serialized
             if (!this._ranges[name]) {
@@ -305,19 +320,8 @@
             return this._ranges[name];
         }
 
-        public reset(): void {
-            this._offsetsCache = {};
-            this._highLimitsCache = {};
-            this.currentFrame = 0;
-            this._blendingFactor = 0;
-            this._originalBlendValue = null;
-        }
-
-        public isStopped(): boolean {
-            return this._stopped;
-        }
 
-        public getKeys(): Array<{ frame: number, value: any }> {
+        public getKeys(): Array<{frame:number, value: any, inTangent?: any, outTangent?: any}> {
             return this._keys;
         }
 
@@ -406,341 +410,6 @@
 
         public setKeys(values: Array<{ frame: number, value: any }>): void {
             this._keys = values.slice(0);
-            this._offsetsCache = {};
-            this._highLimitsCache = {};
-        }
-
-        private _getKeyValue(value: any): any {
-            if (typeof value === "function") {
-                return value();
-            }
-
-            return value;
-        }
-
-        private _interpolate(currentFrame: number, repeatCount: number, loopMode: number, offsetValue?, highLimitValue?) {
-            if (loopMode === Animation.ANIMATIONLOOPMODE_CONSTANT && repeatCount > 0) {
-                return highLimitValue.clone ? highLimitValue.clone() : highLimitValue;
-            }
-
-            this.currentFrame = currentFrame;
-
-            // Try to get a hash to find the right key
-            var startKeyIndex = Math.max(0, Math.min(this._keys.length - 1, Math.floor(this._keys.length * (currentFrame - this._keys[0].frame) / (this._keys[this._keys.length - 1].frame - this._keys[0].frame)) - 1));
-
-            if (this._keys[startKeyIndex].frame >= currentFrame) {
-                while (startKeyIndex - 1 >= 0 && this._keys[startKeyIndex].frame >= currentFrame) {
-                    startKeyIndex--;
-                }
-            }
-
-            for (var key = startKeyIndex; key < this._keys.length; key++) {
-                var endKey = this._keys[key + 1];
-
-                if (endKey.frame >= currentFrame) {
-
-                    var startKey = this._keys[key];
-                    var startValue = this._getKeyValue(startKey.value);
-                    var endValue = this._getKeyValue(endKey.value);
-
-                    var useTangent = startKey.outTangent !== undefined && endKey.inTangent !== undefined;
-                    var frameDelta = endKey.frame - startKey.frame;
-
-                    // gradient : percent of currentFrame between the frame inf and the frame sup
-                    var gradient = (currentFrame - startKey.frame) / frameDelta;
-
-                    // check for easingFunction and correction of gradient
-                    if (this._easingFunction != null) {
-                        gradient = this._easingFunction.ease(gradient);
-                    }
-
-                    switch (this.dataType) {
-                        // Float
-                        case Animation.ANIMATIONTYPE_FLOAT:
-                            var floatValue = useTangent ? this.floatInterpolateFunctionWithTangents(startValue, startKey.outTangent * frameDelta, endValue, endKey.inTangent * frameDelta, gradient) : this.floatInterpolateFunction(startValue, endValue, gradient);
-                            switch (loopMode) {
-                                case Animation.ANIMATIONLOOPMODE_CYCLE:
-                                case Animation.ANIMATIONLOOPMODE_CONSTANT:
-                                    return floatValue;
-                                case Animation.ANIMATIONLOOPMODE_RELATIVE:
-                                    return offsetValue * repeatCount + floatValue;
-                            }
-                            break;
-                        // Quaternion
-                        case Animation.ANIMATIONTYPE_QUATERNION:
-                            var quatValue = useTangent ? this.quaternionInterpolateFunctionWithTangents(startValue, startKey.outTangent.scale(frameDelta), endValue, endKey.inTangent.scale(frameDelta), gradient) : this.quaternionInterpolateFunction(startValue, endValue, gradient);
-                            switch (loopMode) {
-                                case Animation.ANIMATIONLOOPMODE_CYCLE:
-                                case Animation.ANIMATIONLOOPMODE_CONSTANT:
-                                    return quatValue;
-                                case Animation.ANIMATIONLOOPMODE_RELATIVE:
-                                    return quatValue.add(offsetValue.scale(repeatCount));
-                            }
-
-                            return quatValue;
-                        // Vector3
-                        case Animation.ANIMATIONTYPE_VECTOR3:
-                            var vec3Value = useTangent ? this.vector3InterpolateFunctionWithTangents(startValue, startKey.outTangent.scale(frameDelta), endValue, endKey.inTangent.scale(frameDelta), gradient) : this.vector3InterpolateFunction(startValue, endValue, gradient);
-                            switch (loopMode) {
-                                case Animation.ANIMATIONLOOPMODE_CYCLE:
-                                case Animation.ANIMATIONLOOPMODE_CONSTANT:
-                                    return vec3Value;
-                                case Animation.ANIMATIONLOOPMODE_RELATIVE:
-                                    return vec3Value.add(offsetValue.scale(repeatCount));
-                            }
-                        // Vector2
-                        case Animation.ANIMATIONTYPE_VECTOR2:
-                            var vec2Value = useTangent ? this.vector2InterpolateFunctionWithTangents(startValue, startKey.outTangent.scale(frameDelta), endValue, endKey.inTangent.scale(frameDelta), gradient) : this.vector2InterpolateFunction(startValue, endValue, gradient);
-                            switch (loopMode) {
-                                case Animation.ANIMATIONLOOPMODE_CYCLE:
-                                case Animation.ANIMATIONLOOPMODE_CONSTANT:
-                                    return vec2Value;
-                                case Animation.ANIMATIONLOOPMODE_RELATIVE:
-                                    return vec2Value.add(offsetValue.scale(repeatCount));
-                            }
-                        // Size
-                        case Animation.ANIMATIONTYPE_SIZE:
-                            switch (loopMode) {
-                                case Animation.ANIMATIONLOOPMODE_CYCLE:
-                                case Animation.ANIMATIONLOOPMODE_CONSTANT:
-                                    return this.sizeInterpolateFunction(startValue, endValue, gradient);
-                                case Animation.ANIMATIONLOOPMODE_RELATIVE:
-                                    return this.sizeInterpolateFunction(startValue, endValue, gradient).add(offsetValue.scale(repeatCount));
-                            }
-                        // Color3
-                        case Animation.ANIMATIONTYPE_COLOR3:
-                            switch (loopMode) {
-                                case Animation.ANIMATIONLOOPMODE_CYCLE:
-                                case Animation.ANIMATIONLOOPMODE_CONSTANT:
-                                    return this.color3InterpolateFunction(startValue, endValue, gradient);
-                                case Animation.ANIMATIONLOOPMODE_RELATIVE:
-                                    return this.color3InterpolateFunction(startValue, endValue, gradient).add(offsetValue.scale(repeatCount));
-                            }
-                        // Matrix
-                        case Animation.ANIMATIONTYPE_MATRIX:
-                            switch (loopMode) {
-                                case Animation.ANIMATIONLOOPMODE_CYCLE:
-                                case Animation.ANIMATIONLOOPMODE_CONSTANT:
-                                    if (Animation.AllowMatricesInterpolation) {
-                                        return this.matrixInterpolateFunction(startValue, endValue, gradient);
-                                    }
-                                case Animation.ANIMATIONLOOPMODE_RELATIVE:
-                                    return startValue;
-                            }
-                        default:
-                            break;
-                    }
-                    break;
-                }
-            }
-            return this._getKeyValue(this._keys[this._keys.length - 1].value);
-        }
-
-        public setValue(currentValue: any, blend: boolean = false): void {
-            // Set value
-            var path: any;
-            var destination: any;
-
-            if (this.targetPropertyPath.length > 1) {
-                var property = this._target[this.targetPropertyPath[0]];
-
-                for (var index = 1; index < this.targetPropertyPath.length - 1; index++) {
-                    property = property[this.targetPropertyPath[index]];
-                }
-
-                path = this.targetPropertyPath[this.targetPropertyPath.length - 1];
-                destination = property;
-            } else {
-                path = this.targetPropertyPath[0];
-                destination = this._target;
-            }
-
-            // Blending
-            if (this.enableBlending && this._blendingFactor <= 1.0) {
-                if (!this._originalBlendValue) {
-                    if (destination[path].clone) {
-                        this._originalBlendValue = destination[path].clone();
-                    } else {
-                        this._originalBlendValue = destination[path];
-                    }
-                }
-
-                if (this._originalBlendValue.prototype) { // Complex value
-                    
-                    if (this._originalBlendValue.prototype.Lerp) { // Lerp supported
-                        destination[path] = this._originalBlendValue.construtor.prototype.Lerp(currentValue, this._originalBlendValue, this._blendingFactor);
-                    } else { // Blending not supported
-                        destination[path] = currentValue;
-                    }
-
-                } else if (this._originalBlendValue.m) { // Matrix
-                    destination[path] = Matrix.Lerp(this._originalBlendValue, currentValue, this._blendingFactor);
-                } else { // Direct value
-                    destination[path] = this._originalBlendValue * (1.0 - this._blendingFactor) + this._blendingFactor * currentValue;
-                }
-                this._blendingFactor += this.blendingSpeed;
-            } else {
-                destination[path] = currentValue;
-            }
-
-            if (this._target.markAsDirty) {
-                this._target.markAsDirty(this.targetProperty);
-            }
-        }
-
-        public goToFrame(frame: number): void {
-            if (frame < this._keys[0].frame) {
-                frame = this._keys[0].frame;
-            } else if (frame > this._keys[this._keys.length - 1].frame) {
-                frame = this._keys[this._keys.length - 1].frame;
-            }
-
-            var currentValue = this._interpolate(frame, 0, this.loopMode);
-
-            this.setValue(currentValue);
-        }
-
-        public animate(delay: number, from: number, to: number, loop: boolean, speedRatio: number, blend: boolean = false): boolean {
-            if (!this.targetPropertyPath || this.targetPropertyPath.length < 1) {
-                this._stopped = true;
-                return false;
-            }
-            var returnValue = true;
-
-            // Adding a start key at frame 0 if missing
-            if (this._keys[0].frame !== 0) {
-                var newKey = { frame: 0, value: this._keys[0].value };
-                this._keys.splice(0, 0, newKey);
-            }
-
-            // Check limits
-            if (from < this._keys[0].frame || from > this._keys[this._keys.length - 1].frame) {
-                from = this._keys[0].frame;
-            }
-            if (to < this._keys[0].frame || to > this._keys[this._keys.length - 1].frame) {
-                to = this._keys[this._keys.length - 1].frame;
-            }
-
-            //to and from cannot be the same key
-            if(from === to) {
-                from++;
-            }
-            
-            // Compute ratio
-            var range = to - from;
-            var offsetValue;
-            // ratio represents the frame delta between from and to
-            var ratio = delay * (this.framePerSecond * speedRatio) / 1000.0;
-            var highLimitValue = 0;
-
-            if (((to > from && ratio > range) || (from > to && ratio < range)) && !loop) { // If we are out of range and not looping get back to caller
-                returnValue = false;
-                highLimitValue = this._getKeyValue(this._keys[this._keys.length - 1].value);
-            } else {
-                // Get max value if required
-
-                if (this.loopMode !== Animation.ANIMATIONLOOPMODE_CYCLE) {
-
-                    var keyOffset = to.toString() + from.toString();
-                    if (!this._offsetsCache[keyOffset]) {
-                        var fromValue = this._interpolate(from, 0, Animation.ANIMATIONLOOPMODE_CYCLE);
-                        var toValue = this._interpolate(to, 0, Animation.ANIMATIONLOOPMODE_CYCLE);
-                        switch (this.dataType) {
-                            // Float
-                            case Animation.ANIMATIONTYPE_FLOAT:
-                                this._offsetsCache[keyOffset] = toValue - fromValue;
-                                break;
-                            // Quaternion
-                            case Animation.ANIMATIONTYPE_QUATERNION:
-                                this._offsetsCache[keyOffset] = toValue.subtract(fromValue);
-                                break;
-                            // Vector3
-                            case Animation.ANIMATIONTYPE_VECTOR3:
-                                this._offsetsCache[keyOffset] = toValue.subtract(fromValue);
-                            // Vector2
-                            case Animation.ANIMATIONTYPE_VECTOR2:
-                                this._offsetsCache[keyOffset] = toValue.subtract(fromValue);
-                            // Size
-                            case Animation.ANIMATIONTYPE_SIZE:
-                                this._offsetsCache[keyOffset] = toValue.subtract(fromValue);
-                            // Color3
-                            case Animation.ANIMATIONTYPE_COLOR3:
-                                this._offsetsCache[keyOffset] = toValue.subtract(fromValue);
-                            default:
-                                break;
-                        }
-
-                        this._highLimitsCache[keyOffset] = toValue;
-                    }
-
-                    highLimitValue = this._highLimitsCache[keyOffset];
-                    offsetValue = this._offsetsCache[keyOffset];
-                }
-            }
-
-            if (offsetValue === undefined) {
-                switch (this.dataType) {
-                    // Float
-                    case Animation.ANIMATIONTYPE_FLOAT:
-                        offsetValue = 0;
-                        break;
-                    // Quaternion
-                    case Animation.ANIMATIONTYPE_QUATERNION:
-                        offsetValue = new Quaternion(0, 0, 0, 0);
-                        break;
-                    // Vector3
-                    case Animation.ANIMATIONTYPE_VECTOR3:
-                        offsetValue = Vector3.Zero();
-                        break;
-                    // Vector2
-                    case Animation.ANIMATIONTYPE_VECTOR2:
-                        offsetValue = Vector2.Zero();
-                        break;
-                    // Size
-                    case Animation.ANIMATIONTYPE_SIZE:
-                        offsetValue = Size.Zero();
-                        break;
-                    // Color3
-                    case Animation.ANIMATIONTYPE_COLOR3:
-                        offsetValue = Color3.Black();
-                }
-            }
-
-            // Compute value
-            var repeatCount = (ratio / range) >> 0;
-            var currentFrame = returnValue ? from + ratio % range : to;
-            var currentValue = this._interpolate(currentFrame, repeatCount, this.loopMode, offsetValue, highLimitValue);
-
-            // Set value
-            this.setValue(currentValue);
-            // Check events
-            for (var index = 0; index < this._events.length; index++) {
-                // Make sure current frame has passed event frame and that event frame is within the current range
-                // Also, handle both forward and reverse animations
-                if (
-                    (range > 0 && currentFrame >= this._events[index].frame && this._events[index].frame >= from) ||
-                    (range < 0 && currentFrame <= this._events[index].frame && this._events[index].frame <= from)
-                ){
-                    var event = this._events[index];
-                    if (!event.isDone) {
-                        // If event should be done only once, remove it.
-                        if (event.onlyOnce) {
-                            this._events.splice(index, 1);
-                            index--;
-                        }
-                        event.isDone = true;
-                        event.action();
-                    } // Don't do anything if the event has already be done.
-                } else if (this._events[index].isDone && !this._events[index].onlyOnce) {
-                    // reset event, the animation is looping
-                    this._events[index].isDone = false;
-                }
-            }
-            if (!returnValue) {
-                this._stopped = true;
-            }
-
-            return returnValue;
         }
 
         public serialize(): any {

+ 390 - 0
src/Animations/babylon.runtimeAnimation.ts

@@ -0,0 +1,390 @@
+module BABYLON {
+
+    export class RuntimeAnimation {
+        public currentFrame: number;
+        private _animation: Animation;
+        private _target: any;
+
+        private _originalBlendValue: any;
+        private _offsetsCache = {};
+        private _highLimitsCache = {};
+        private _stopped = false;
+        private _blendingFactor = 0;
+        
+        public constructor(target: any, animation: Animation) {
+            this._animation = animation;
+            this._target = target;
+
+            animation._runtimeAnimations.push(this);
+        }
+
+        public get animation(): Animation {
+            return this._animation;
+        }
+
+        public reset(): void {
+            this._offsetsCache = {};
+            this._highLimitsCache = {};
+            this.currentFrame = 0;
+            this._blendingFactor = 0;
+            this._originalBlendValue = null;
+        }
+
+        public isStopped(): boolean {
+            return this._stopped;
+        }        
+
+        public dispose(): void {
+            let index = this._animation.runtimeAnimations.indexOf(this);
+
+            if (index > -1) {
+                this._animation.runtimeAnimations.splice(index, 1);
+            }
+        }
+
+        private _getKeyValue(value: any): any {
+            if (typeof value === "function") {
+                return value();
+            }
+
+            return value;
+        }      
+        
+        private _interpolate(currentFrame: number, repeatCount: number, loopMode: number, offsetValue?, highLimitValue?) {
+            if (loopMode === Animation.ANIMATIONLOOPMODE_CONSTANT && repeatCount > 0) {
+                return highLimitValue.clone ? highLimitValue.clone() : highLimitValue;
+            }
+
+            this.currentFrame = currentFrame;
+
+            let keys = this._animation.getKeys();
+
+            // Try to get a hash to find the right key
+            var startKeyIndex = Math.max(0, Math.min(keys.length - 1, Math.floor(keys.length * (currentFrame - keys[0].frame) / (keys[keys.length - 1].frame - keys[0].frame)) - 1));
+
+            if (keys[startKeyIndex].frame >= currentFrame) {
+                while (startKeyIndex - 1 >= 0 && keys[startKeyIndex].frame >= currentFrame) {
+                    startKeyIndex--;
+                }
+            }
+
+            for (var key = startKeyIndex; key < keys.length; key++) {
+                var endKey = keys[key + 1];
+
+                if (endKey.frame >= currentFrame) {
+
+                    var startKey = keys[key];
+                    var startValue = this._getKeyValue(startKey.value);
+                    var endValue = this._getKeyValue(endKey.value);
+
+                    var useTangent = startKey.outTangent !== undefined && endKey.inTangent !== undefined;
+                    var frameDelta = endKey.frame - startKey.frame;
+
+                    // gradient : percent of currentFrame between the frame inf and the frame sup
+                    var gradient = (currentFrame - startKey.frame) / frameDelta;
+
+                    // check for easingFunction and correction of gradient
+                    let easingFunction = this._animation.getEasingFunction();
+                    if (easingFunction != null) {
+                        gradient = easingFunction.ease(gradient);
+                    }
+
+                    switch (this._animation.dataType) {
+                        // Float
+                        case Animation.ANIMATIONTYPE_FLOAT:
+                            var floatValue = useTangent ? this._animation.floatInterpolateFunctionWithTangents(startValue, startKey.outTangent * frameDelta, endValue, endKey.inTangent * frameDelta, gradient) : this._animation.floatInterpolateFunction(startValue, endValue, gradient);
+                            switch (loopMode) {
+                                case Animation.ANIMATIONLOOPMODE_CYCLE:
+                                case Animation.ANIMATIONLOOPMODE_CONSTANT:
+                                    return floatValue;
+                                case Animation.ANIMATIONLOOPMODE_RELATIVE:
+                                    return offsetValue * repeatCount + floatValue;
+                            }
+                            break;
+                        // Quaternion
+                        case Animation.ANIMATIONTYPE_QUATERNION:
+                            var quatValue = useTangent ? this._animation.quaternionInterpolateFunctionWithTangents(startValue, startKey.outTangent.scale(frameDelta), endValue, endKey.inTangent.scale(frameDelta), gradient) : this._animation.quaternionInterpolateFunction(startValue, endValue, gradient);
+                            switch (loopMode) {
+                                case Animation.ANIMATIONLOOPMODE_CYCLE:
+                                case Animation.ANIMATIONLOOPMODE_CONSTANT:
+                                    return quatValue;
+                                case Animation.ANIMATIONLOOPMODE_RELATIVE:
+                                    return quatValue.add(offsetValue.scale(repeatCount));
+                            }
+
+                            return quatValue;
+                        // Vector3
+                        case Animation.ANIMATIONTYPE_VECTOR3:
+                            var vec3Value = useTangent ? this._animation.vector3InterpolateFunctionWithTangents(startValue, startKey.outTangent.scale(frameDelta), endValue, endKey.inTangent.scale(frameDelta), gradient) : this._animation.vector3InterpolateFunction(startValue, endValue, gradient);
+                            switch (loopMode) {
+                                case Animation.ANIMATIONLOOPMODE_CYCLE:
+                                case Animation.ANIMATIONLOOPMODE_CONSTANT:
+                                    return vec3Value;
+                                case Animation.ANIMATIONLOOPMODE_RELATIVE:
+                                    return vec3Value.add(offsetValue.scale(repeatCount));
+                            }
+                        // Vector2
+                        case Animation.ANIMATIONTYPE_VECTOR2:
+                            var vec2Value = useTangent ? this._animation.vector2InterpolateFunctionWithTangents(startValue, startKey.outTangent.scale(frameDelta), endValue, endKey.inTangent.scale(frameDelta), gradient) : this._animation.vector2InterpolateFunction(startValue, endValue, gradient);
+                            switch (loopMode) {
+                                case Animation.ANIMATIONLOOPMODE_CYCLE:
+                                case Animation.ANIMATIONLOOPMODE_CONSTANT:
+                                    return vec2Value;
+                                case Animation.ANIMATIONLOOPMODE_RELATIVE:
+                                    return vec2Value.add(offsetValue.scale(repeatCount));
+                            }
+                        // Size
+                        case Animation.ANIMATIONTYPE_SIZE:
+                            switch (loopMode) {
+                                case Animation.ANIMATIONLOOPMODE_CYCLE:
+                                case Animation.ANIMATIONLOOPMODE_CONSTANT:
+                                    return this._animation.sizeInterpolateFunction(startValue, endValue, gradient);
+                                case Animation.ANIMATIONLOOPMODE_RELATIVE:
+                                    return this._animation.sizeInterpolateFunction(startValue, endValue, gradient).add(offsetValue.scale(repeatCount));
+                            }
+                        // Color3
+                        case Animation.ANIMATIONTYPE_COLOR3:
+                            switch (loopMode) {
+                                case Animation.ANIMATIONLOOPMODE_CYCLE:
+                                case Animation.ANIMATIONLOOPMODE_CONSTANT:
+                                    return this._animation.color3InterpolateFunction(startValue, endValue, gradient);
+                                case Animation.ANIMATIONLOOPMODE_RELATIVE:
+                                    return this._animation.color3InterpolateFunction(startValue, endValue, gradient).add(offsetValue.scale(repeatCount));
+                            }
+                        // Matrix
+                        case Animation.ANIMATIONTYPE_MATRIX:
+                            switch (loopMode) {
+                                case Animation.ANIMATIONLOOPMODE_CYCLE:
+                                case Animation.ANIMATIONLOOPMODE_CONSTANT:
+                                    if (Animation.AllowMatricesInterpolation) {
+                                        return this._animation.matrixInterpolateFunction(startValue, endValue, gradient);
+                                    }
+                                case Animation.ANIMATIONLOOPMODE_RELATIVE:
+                                    return startValue;
+                            }
+                        default:
+                            break;
+                    }
+                    break;
+                }
+            }
+            return this._getKeyValue(keys[keys.length - 1].value);
+        }
+
+        public setValue(currentValue: any, blend: boolean = false): void {
+            // Set value
+            var path: any;
+            var destination: any;
+
+            let targetPropertyPath = this._animation.targetPropertyPath
+
+            if (targetPropertyPath.length > 1) {
+                var property = this._target[targetPropertyPath[0]];
+
+                for (var index = 1; index < targetPropertyPath.length - 1; index++) {
+                    property = property[targetPropertyPath[index]];
+                }
+
+                path = targetPropertyPath[targetPropertyPath.length - 1];
+                destination = property;
+            } else {
+                path = targetPropertyPath[0];
+                destination = this._target;
+            }
+
+            // Blending
+            if (this._animation.enableBlending && this._blendingFactor <= 1.0) {
+                if (!this._originalBlendValue) {
+                    if (destination[path].clone) {
+                        this._originalBlendValue = destination[path].clone();
+                    } else {
+                        this._originalBlendValue = destination[path];
+                    }
+                }
+
+                if (this._originalBlendValue.prototype) { // Complex value
+                    
+                    if (this._originalBlendValue.prototype.Lerp) { // Lerp supported
+                        destination[path] = this._originalBlendValue.construtor.prototype.Lerp(currentValue, this._originalBlendValue, this._blendingFactor);
+                    } else { // Blending not supported
+                        destination[path] = currentValue;
+                    }
+
+                } else if (this._originalBlendValue.m) { // Matrix
+                    destination[path] = Matrix.Lerp(this._originalBlendValue, currentValue, this._blendingFactor);
+                } else { // Direct value
+                    destination[path] = this._originalBlendValue * (1.0 - this._blendingFactor) + this._blendingFactor * currentValue;
+                }
+                this._blendingFactor += this._animation.blendingSpeed;
+            } else {
+                destination[path] = currentValue;
+            }
+
+            if (this._target.markAsDirty) {
+                this._target.markAsDirty(this._animation.targetProperty);
+            }
+        }
+
+        public goToFrame(frame: number): void {
+            let keys = this._animation.getKeys();
+
+            if (frame < keys[0].frame) {
+                frame = keys[0].frame;
+            } else if (frame > keys[keys.length - 1].frame) {
+                frame = keys[keys.length - 1].frame;
+            }
+
+            var currentValue = this._interpolate(frame, 0, this._animation.loopMode);
+
+            this.setValue(currentValue);
+        }
+
+        public animate(delay: number, from: number, to: number, loop: boolean, speedRatio: number, blend: boolean = false): boolean {
+            let targetPropertyPath = this._animation.targetPropertyPath
+            if (!targetPropertyPath || targetPropertyPath.length < 1) {
+                this._stopped = true;
+                return false;
+            }
+            var returnValue = true;
+            let keys = this._animation.getKeys();
+
+            // Adding a start key at frame 0 if missing
+            if (keys[0].frame !== 0) {
+                var newKey = { frame: 0, value: keys[0].value };
+                keys.splice(0, 0, newKey);
+            }
+
+            // Check limits
+            if (from < keys[0].frame || from > keys[keys.length - 1].frame) {
+                from = keys[0].frame;
+            }
+            if (to < keys[0].frame || to > keys[keys.length - 1].frame) {
+                to = keys[keys.length - 1].frame;
+            }
+
+            //to and from cannot be the same key
+            if(from === to) {
+                from++;
+            }
+            
+            // Compute ratio
+            var range = to - from;
+            var offsetValue;
+            // ratio represents the frame delta between from and to
+            var ratio = delay * (this._animation.framePerSecond * speedRatio) / 1000.0;
+            var highLimitValue = 0;
+
+            if (((to > from && ratio > range) || (from > to && ratio < range)) && !loop) { // If we are out of range and not looping get back to caller
+                returnValue = false;
+                highLimitValue = this._getKeyValue(keys[keys.length - 1].value);
+            } else {
+                // Get max value if required
+
+                if (this._animation.loopMode !== Animation.ANIMATIONLOOPMODE_CYCLE) {
+
+                    var keyOffset = to.toString() + from.toString();
+                    if (!this._offsetsCache[keyOffset]) {
+                        var fromValue = this._interpolate(from, 0, Animation.ANIMATIONLOOPMODE_CYCLE);
+                        var toValue = this._interpolate(to, 0, Animation.ANIMATIONLOOPMODE_CYCLE);
+                        switch (this._animation.dataType) {
+                            // Float
+                            case Animation.ANIMATIONTYPE_FLOAT:
+                                this._offsetsCache[keyOffset] = toValue - fromValue;
+                                break;
+                            // Quaternion
+                            case Animation.ANIMATIONTYPE_QUATERNION:
+                                this._offsetsCache[keyOffset] = toValue.subtract(fromValue);
+                                break;
+                            // Vector3
+                            case Animation.ANIMATIONTYPE_VECTOR3:
+                                this._offsetsCache[keyOffset] = toValue.subtract(fromValue);
+                            // Vector2
+                            case Animation.ANIMATIONTYPE_VECTOR2:
+                                this._offsetsCache[keyOffset] = toValue.subtract(fromValue);
+                            // Size
+                            case Animation.ANIMATIONTYPE_SIZE:
+                                this._offsetsCache[keyOffset] = toValue.subtract(fromValue);
+                            // Color3
+                            case Animation.ANIMATIONTYPE_COLOR3:
+                                this._offsetsCache[keyOffset] = toValue.subtract(fromValue);
+                            default:
+                                break;
+                        }
+
+                        this._highLimitsCache[keyOffset] = toValue;
+                    }
+
+                    highLimitValue = this._highLimitsCache[keyOffset];
+                    offsetValue = this._offsetsCache[keyOffset];
+                }
+            }
+
+            if (offsetValue === undefined) {
+                switch (this._animation.dataType) {
+                    // Float
+                    case Animation.ANIMATIONTYPE_FLOAT:
+                        offsetValue = 0;
+                        break;
+                    // Quaternion
+                    case Animation.ANIMATIONTYPE_QUATERNION:
+                        offsetValue = new Quaternion(0, 0, 0, 0);
+                        break;
+                    // Vector3
+                    case Animation.ANIMATIONTYPE_VECTOR3:
+                        offsetValue = Vector3.Zero();
+                        break;
+                    // Vector2
+                    case Animation.ANIMATIONTYPE_VECTOR2:
+                        offsetValue = Vector2.Zero();
+                        break;
+                    // Size
+                    case Animation.ANIMATIONTYPE_SIZE:
+                        offsetValue = Size.Zero();
+                        break;
+                    // Color3
+                    case Animation.ANIMATIONTYPE_COLOR3:
+                        offsetValue = Color3.Black();
+                }
+            }
+
+            // Compute value
+            var repeatCount = (ratio / range) >> 0;
+            var currentFrame = returnValue ? from + ratio % range : to;
+            var currentValue = this._interpolate(currentFrame, repeatCount, this._animation.loopMode, offsetValue, highLimitValue);
+
+            // Set value
+            this.setValue(currentValue);
+            // Check events
+            let events = this._animation.getEvents();
+            for (var index = 0; index < events.length; index++) {
+                // Make sure current frame has passed event frame and that event frame is within the current range
+                // Also, handle both forward and reverse animations
+                if (
+                    (range > 0 && currentFrame >= events[index].frame && events[index].frame >= from) ||
+                    (range < 0 && currentFrame <= events[index].frame && events[index].frame <= from)
+                ){
+                    var event = events[index];
+                    if (!event.isDone) {
+                        // If event should be done only once, remove it.
+                        if (event.onlyOnce) {
+                            events.splice(index, 1);
+                            index--;
+                        }
+                        event.isDone = true;
+                        event.action();
+                    } // Don't do anything if the event has already be done.
+                } else if (events[index].isDone && !events[index].onlyOnce) {
+                    // reset event, the animation is looping
+                    events[index].isDone = false;
+                }
+            }
+            if (!returnValue) {
+                this._stopped = true;
+            }
+
+            return returnValue;
+        }
+    }
+} 
+
+

+ 1 - 1
src/Bones/babylon.bone.ts

@@ -371,7 +371,7 @@ module BABYLON {
          */
         public setScale(x: number, y: number, z: number, scaleChildren = false): void {
 
-            if (this.animations[0] && !this.animations[0].isStopped()) {
+            if (this.animations[0] && !this.animations[0].hasRunningRuntimeAnimations) {
                 if (!scaleChildren) {
                     this._negateScaleChildren.x = 1/x;
                     this._negateScaleChildren.y = 1/y;

+ 5 - 1
src/Cameras/Inputs/babylon.arcRotateCameraPointersInput.ts

@@ -45,6 +45,10 @@ module BABYLON {
             this._pointerInput = (p, s) => {
                 var evt = <PointerEvent>p.event;
 
+                if (engine.isInVRExclusivePointerMode) {
+                    return;
+                }
+
                 if (p.type !== PointerEventTypes.POINTERMOVE && this.buttons.indexOf(evt.button) === -1) {
                     return;
                 }
@@ -135,7 +139,7 @@ module BABYLON {
                             return;
                         }
 
-                        if (Math.abs(pinchDistance - previousPinchDistance) > this.camera.pinchToPanMaxDistance) {
+                        if (pinchDistance > this.camera.panMaxFingersDistance || Math.abs(pinchDistance - previousPinchDistance) > this.camera.pinchToPanMaxDistance) {
                             this.camera
                                 .inertialRadiusOffset += (pinchSquaredDistance - previousPinchSquaredDistance) /
                                 (this.pinchPrecision *

+ 8 - 0
src/Cameras/Inputs/babylon.freeCameraMouseInput.ts

@@ -24,6 +24,10 @@ module BABYLON {
                 this._pointerInput = (p, s) => {
                     var evt = <PointerEvent>p.event;
 
+                    if (engine.isInVRExclusivePointerMode) {
+                        return;
+                    }
+
                     if (!this.touchEnabled && evt.pointerType === "touch") {
                         return;
                     }
@@ -95,6 +99,10 @@ module BABYLON {
                     return;
                 }
 
+                if (engine.isInVRExclusivePointerMode) {
+                    return;
+                }
+
                 var offsetX = evt.movementX || evt.mozMovementX || evt.webkitMovementX || evt.msMovementX || 0;
                 var offsetY = evt.movementY || evt.mozMovementY || evt.webkitMovementY || evt.msMovementY || 0;
 

+ 25 - 23
src/Cameras/VR/babylon.webVRCamera.ts

@@ -386,39 +386,41 @@ module BABYLON {
             this._onGamepadDisconnectedObserver = manager.onGamepadDisconnectedObservable.add((gamepad) => {
                 if (gamepad.type === BABYLON.Gamepad.POSE_ENABLED) {
                     let webVrController: WebVRController = <WebVRController>gamepad;
-                    let index = this.controllers.indexOf(webVrController);
-
-                    if (index === -1) {
-                        // we are good
-                        return;
+                    
+                    if (webVrController.defaultModel) {
+                        webVrController.defaultModel.setEnabled(false);
                     }
-
-                    this.controllers.splice(index, 1);
                 }
             });
 
             this._onGamepadConnectedObserver = manager.onGamepadConnectedObservable.add((gamepad) => {
                 if (gamepad.type === BABYLON.Gamepad.POSE_ENABLED) {
                     let webVrController: WebVRController = <WebVRController>gamepad;
+                    
                     if (this.webVROptions.controllerMeshes) {
-                        webVrController.initControllerMesh(this.getScene(), (loadedMesh) => {
-                            if (this.webVROptions.defaultLightingOnControllers) {
-                                if (!this._lightOnControllers) {
-                                    this._lightOnControllers = new BABYLON.HemisphericLight("vrControllersLight", new BABYLON.Vector3(0, 1, 0), this.getScene());
-                                }
-                                let activateLightOnSubMeshes = function(mesh: AbstractMesh, light: HemisphericLight) {
-                                    let children = mesh.getChildren();
-                                    if (children.length !== 0) {
-                                        children.forEach((mesh) => {
-                                            light.includedOnlyMeshes.push(<AbstractMesh>mesh);
-                                            activateLightOnSubMeshes(<AbstractMesh>mesh, light);
-                                        });
+                        if (webVrController.defaultModel) {
+                            webVrController.defaultModel.setEnabled(true);
+                        } else {
+                            // Load the meshes
+                            webVrController.initControllerMesh(this.getScene(), (loadedMesh) => {
+                                if (this.webVROptions.defaultLightingOnControllers) {
+                                    if (!this._lightOnControllers) {
+                                        this._lightOnControllers = new BABYLON.HemisphericLight("vrControllersLight", new BABYLON.Vector3(0, 1, 0), this.getScene());
                                     }
+                                    let activateLightOnSubMeshes = function(mesh: AbstractMesh, light: HemisphericLight) {
+                                        let children = mesh.getChildren();
+                                        if (children.length !== 0) {
+                                            children.forEach((mesh) => {
+                                                light.includedOnlyMeshes.push(<AbstractMesh>mesh);
+                                                activateLightOnSubMeshes(<AbstractMesh>mesh, light);
+                                            });
+                                        }
+                                    }
+                                    this._lightOnControllers.includedOnlyMeshes.push(loadedMesh);
+                                    activateLightOnSubMeshes(loadedMesh, this._lightOnControllers);
                                 }
-                                this._lightOnControllers.includedOnlyMeshes.push(loadedMesh);
-                                activateLightOnSubMeshes(loadedMesh, this._lightOnControllers);
-                            }
-                        });
+                            });
+                        }
                     }
                     webVrController.attachToPoseControlledCamera(this);
 

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

@@ -57,7 +57,10 @@ module BABYLON {
         public inertialPanningY: number = 0;
 
         @serialize()
-        public pinchToPanMaxDistance: number = 2;
+        public pinchToPanMaxDistance: number = 3;
+
+        @serialize()
+        public panMaxFingersDistance: number = 100;
 
         @serialize()
         public panningDistanceLimit: number = null;

+ 19 - 21
src/Cameras/babylon.camera.ts

@@ -261,26 +261,6 @@
 
             this._cache.position.copyFrom(this.position);
             this._cache.upVector.copyFrom(this.upVector);
-
-            this._cache.mode = this.mode;
-            this._cache.minZ = this.minZ;
-            this._cache.maxZ = this.maxZ;
-
-            this._cache.fov = this.fov;
-            this._cache.fovMode = this.fovMode;
-            this._cache.aspectRatio = engine.getAspectRatio(this);
-
-            this._cache.orthoLeft = this.orthoLeft;
-            this._cache.orthoRight = this.orthoRight;
-            this._cache.orthoBottom = this.orthoBottom;
-            this._cache.orthoTop = this.orthoTop;
-            this._cache.renderWidth = engine.getRenderWidth();
-            this._cache.renderHeight = engine.getRenderHeight();
-        }
-
-        public _updateFromScene(): void {
-            this.updateCache();
-            this.update();
         }
 
         // Synchronized
@@ -333,10 +313,10 @@
         }
 
         public update(): void {
+            this._checkInputs();
             if (this.cameraRigMode !== Camera.RIG_MODE_NONE) {
                 this._updateRigCameras();
             }
-            this._checkInputs();
         }
 
         public _checkInputs(): void {
@@ -441,6 +421,7 @@
                 return this._computedViewMatrix;
             }
 
+            this.updateCache();
             this._computedViewMatrix = this._getViewMatrix();
             this._currentRenderId = this.getScene().getRenderId();
             
@@ -489,11 +470,21 @@
                 return this._projectionMatrix;
             }
 
+            // Cache
+            this._cache.mode = this.mode;
+            this._cache.minZ = this.minZ;
+            this._cache.maxZ = this.maxZ;
+        
+            // Matrix
             this._refreshFrustumPlanes = true;
 
             var engine = this.getEngine();
             var scene = this.getScene();
             if (this.mode === Camera.PERSPECTIVE_CAMERA) {
+                this._cache.fov = this.fov;
+                this._cache.fovMode = this.fovMode;
+                this._cache.aspectRatio = engine.getAspectRatio(this);
+                
                 if (this.minZ <= 0) {
                     this.minZ = 0.1;
                 }
@@ -533,6 +524,13 @@
                         this.maxZ,
                         this._projectionMatrix);
                 }
+
+                this._cache.orthoLeft = this.orthoLeft;
+                this._cache.orthoRight = this.orthoRight;
+                this._cache.orthoBottom = this.orthoBottom;
+                this._cache.orthoTop = this.orthoTop;
+                this._cache.renderWidth = engine.getRenderWidth();
+                this._cache.renderHeight = engine.getRenderHeight();                    
             }
 
             this.onProjectionMatrixChangedObservable.notifyObservers(this);

+ 3 - 2
src/Gamepad/Controllers/babylon.genericController.ts

@@ -1,14 +1,15 @@
 module BABYLON {
     
     export class GenericController extends WebVRController {
-        private _defaultModel: BABYLON.AbstractMesh;
+        public static readonly MODEL_BASE_URL:string = 'https://controllers.babylonjs.com/generic/';
+        public static readonly MODEL_FILENAME:string = 'generic.babylon';
 
         constructor(vrGamepad) {
             super(vrGamepad);
         }
 
         public initControllerMesh(scene: Scene, meshLoaded?: (mesh: AbstractMesh) => void) {
-            SceneLoader.ImportMesh("", "http://yoda.blob.core.windows.net/models/", "genericvrcontroller.babylon", scene, (newMeshes) => {
+            SceneLoader.ImportMesh("", GenericController.MODEL_BASE_URL, GenericController.MODEL_FILENAME, scene, (newMeshes) => {
                 this._defaultModel = newMeshes[1];
                 if (meshLoaded) {
                     meshLoaded(this._defaultModel);

+ 14 - 3
src/Gamepad/Controllers/babylon.oculusTouchController.ts

@@ -1,7 +1,9 @@
 module BABYLON {
 
     export class OculusTouchController extends WebVRController {
-        private _defaultModel: BABYLON.AbstractMesh;
+        private static readonly MODEL_BASE_URL:string = 'https://controllers.babylonjs.com/oculus/';
+        private static readonly MODEL_LEFT_FILENAME:string = 'left.babylon';
+        private static readonly MODEL_RIGHT_FILENAME:string = 'right.babylon';
 
         public onSecondaryTriggerStateChangedObservable = new Observable<ExtendedGamepadButton>();
 
@@ -13,8 +15,17 @@ module BABYLON {
         }
 
         public initControllerMesh(scene: Scene, meshLoaded?: (mesh: AbstractMesh) => void) {
-            let meshName = this.hand === 'right' ? 'RightTouch.babylon' : 'LeftTouch.babylon';
-            SceneLoader.ImportMesh("", "http://yoda.blob.core.windows.net/models/", meshName, scene, (newMeshes) => {
+            let meshName;
+
+            // Hand
+            if (this.hand === 'left') {
+                meshName = OculusTouchController.MODEL_LEFT_FILENAME;
+            }
+            else { // Right is the default if no hand is specified
+                meshName = OculusTouchController.MODEL_RIGHT_FILENAME;
+            }
+
+            SceneLoader.ImportMesh("", OculusTouchController.MODEL_BASE_URL, meshName, scene, (newMeshes) => {
                 /*
                 Parent Mesh name: oculus_touch_left
                 - body

+ 9 - 8
src/Gamepad/Controllers/babylon.poseEnabledController.ts

@@ -26,9 +26,9 @@ module BABYLON {
                 return new OculusTouchController(vrGamepad);
             }
             // Windows Mixed Reality controllers 
-            // else if (vrGamepad.id.indexOf('Spatial Control') === 0) {
-            //     //return new WindowsMixedRealityController(vrGamepad);
-            // }
+            else if (vrGamepad.id.indexOf(WindowsMotionController.GAMEPAD_ID_PREFIX) === 0) {
+                return new WindowsMotionController(vrGamepad);
+            }
             // HTC Vive
             else if (vrGamepad.id.toLowerCase().indexOf('openvr') !== -1) {
                 return new ViveController(vrGamepad);
@@ -58,9 +58,9 @@ module BABYLON {
         private _poseControlledCamera: TargetCamera;
 
         private _leftHandSystemQuaternion: Quaternion = new Quaternion();
-
-        constructor(public vrGamepad) {
-            super(vrGamepad.id, vrGamepad.index, vrGamepad);
+        
+        constructor(browserGamepad) {
+            super(browserGamepad.id, browserGamepad.index, browserGamepad);
             this.type = Gamepad.POSE_ENABLED;
             this.controllerType = PoseEnabledControllerType.GENERIC;
             this.position = Vector3.Zero();
@@ -75,9 +75,9 @@ module BABYLON {
 
         public update() {
             super.update();
-            var pose: GamepadPose = this.vrGamepad.pose;
+            var pose: GamepadPose = this.browserGamepad.pose;
             this.updateFromDevice(pose);
-
+            
             if (this._mesh) {
                 this._mesh.position.copyFrom(this._calculatedPosition);
                 this._mesh.rotationQuaternion.copyFrom(this._calculatedRotation);
@@ -95,6 +95,7 @@ module BABYLON {
 
                     this.devicePosition.scaleToRef(this.deviceScaleFactor, this._calculatedPosition);
                     this._calculatedPosition.addInPlace(this.position);
+
                 }
                 if (poseData.orientation) {
                     this.deviceRotationQuaternion.copyFromFloats(this.rawPose.orientation[0], this.rawPose.orientation[1], -this.rawPose.orientation[2], -this.rawPose.orientation[3]);

+ 3 - 2
src/Gamepad/Controllers/babylon.viveController.ts

@@ -1,7 +1,8 @@
 module BABYLON {
 
     export class ViveController extends WebVRController {
-        private _defaultModel: BABYLON.AbstractMesh;
+        private static readonly MODEL_BASE_URL:string = 'https://controllers.babylonjs.com/vive/';
+        private static readonly MODEL_FILENAME:string = 'wand.babylon';
 
         constructor(vrGamepad) {
             super(vrGamepad);
@@ -9,7 +10,7 @@ module BABYLON {
         }
 
         public initControllerMesh(scene: Scene, meshLoaded?: (mesh: AbstractMesh) => void) {
-            SceneLoader.ImportMesh("", "http://yoda.blob.core.windows.net/models/", "ViveWand.babylon", scene, (newMeshes) => {
+            SceneLoader.ImportMesh("", ViveController.MODEL_BASE_URL, ViveController.MODEL_FILENAME, scene, (newMeshes) => {
                 /*
                 Parent Mesh name: ViveWand
                 - body

+ 19 - 5
src/Gamepad/Controllers/babylon.webVRController.ts

@@ -2,12 +2,12 @@ module BABYLON {
 
     export abstract class WebVRController extends PoseEnabledController {
 
-        public onTriggerStateChangedObservable = new Observable<ExtendedGamepadButton>();
+        protected _defaultModel: AbstractMesh;
 
+        // Observables
+        public onTriggerStateChangedObservable = new Observable<ExtendedGamepadButton>();
         public onMainButtonStateChangedObservable = new Observable<ExtendedGamepadButton>();
-
         public onSecondaryButtonStateChangedObservable = new Observable<ExtendedGamepadButton>();
-
         public onPadStateChangedObservable = new Observable<ExtendedGamepadButton>();
         public onPadValuesChangedObservable = new Observable<StickValues>();
 
@@ -23,6 +23,10 @@ module BABYLON {
 
         public hand: string; // 'left' or 'right', see https://w3c.github.io/gamepad/extensions.html#gamepadhand-enum
 
+        public get defaultModel(): AbstractMesh {
+            return this._defaultModel;
+        }
+
         constructor(vrGamepad) {
             super(vrGamepad);
             this._buttons = new Array<ExtendedGamepadButton>(vrGamepad.buttons.length);
@@ -32,7 +36,7 @@ module BABYLON {
         public update() {
             super.update();
             for (var index = 0; index < this._buttons.length; index++) {
-                this._setButtonValue(this.vrGamepad.buttons[index], this._buttons[index], index);
+                this._setButtonValue(this.browserGamepad.buttons[index], this._buttons[index], index);
             };
             if (this.leftStick.x !== this.pad.x || this.leftStick.y !== this.pad.y) {
                 this.pad.x = this.leftStick.x;
@@ -88,6 +92,16 @@ module BABYLON {
             this._changes.changed = this._changes.pressChanged || this._changes.touchChanged || this._changes.valueChanged;
             return this._changes;
         }
+
+        public dispose(): void {
+            super.dispose();
+
+            this.onTriggerStateChangedObservable.clear();
+            this.onMainButtonStateChangedObservable.clear();
+            this.onSecondaryButtonStateChangedObservable.clear();
+            this.onPadStateChangedObservable.clear();
+            this.onPadValuesChangedObservable.clear();
+        }
     }
         
-}
+}

+ 384 - 0
src/Gamepad/Controllers/babylon.windowsMotionController.ts

@@ -0,0 +1,384 @@
+module BABYLON {
+
+    class LoadedMeshInfo {
+        public rootNode: AbstractMesh;
+        public pointingPoseNode: AbstractMesh;
+        public buttonMeshes: { [id: string] : IButtonMeshInfo; } = {};
+        public axisMeshes: { [id: number] : IAxisMeshInfo; } = {};
+    }
+
+    interface IMeshInfo {
+        index: number;
+        value: AbstractMesh;
+    }
+
+    interface IButtonMeshInfo extends IMeshInfo {
+        pressed: AbstractMesh;
+        unpressed: AbstractMesh;
+    }
+
+    interface IAxisMeshInfo extends IMeshInfo {
+        min: AbstractMesh;
+        max: AbstractMesh;
+    }
+
+    export class WindowsMotionController extends WebVRController {
+        private static readonly MODEL_BASE_URL:string = 'https://controllers.babylonjs.com/microsoft/';
+        private static readonly MODEL_LEFT_FILENAME:string = 'left.glb';
+        private static readonly MODEL_RIGHT_FILENAME:string = 'right.glb';
+
+        public static readonly GAMEPAD_ID_PREFIX:string = 'Spatial Controller (Spatial Interaction Source) ';
+        private static readonly GAMEPAD_ID_PATTERN = /([0-9a-zA-Z]+-[0-9a-zA-Z]+)$/;
+
+        private _loadedMeshInfo: LoadedMeshInfo;
+        private readonly _mapping = {
+            // Semantic button names
+            buttons: ['thumbstick', 'trigger', 'grip', 'menu', 'trackpad'],
+            
+            // A mapping of the button name to glTF model node name
+            // that should be transformed by button value.
+            buttonMeshNames: {
+                'trigger': 'SELECT',
+                'menu': 'MENU',
+                'grip': 'GRASP',
+                'thumbstick': 'THUMBSTICK_PRESS',
+                'trackpad': 'TOUCHPAD_PRESS'
+            },
+            // This mapping is used to translate from the Motion Controller to Babylon semantics
+            buttonObservableNames: {
+                'trigger': 'onTriggerStateChangedObservable',
+                'menu': 'onSecondaryButtonStateChangedObservable',
+                'grip': 'onMainButtonStateChangedObservable',
+                'thumbstick': 'onPadStateChangedObservable',
+                'trackpad': 'onTrackpadChangedObservable'
+            },
+            // A mapping of the axis name to glTF model node name
+            // that should be transformed by axis value.
+            // This array mirrors the browserGamepad.axes array, such that 
+            // the mesh corresponding to axis 0 is in this array index 0.
+            axisMeshNames: [
+                'THUMBSTICK_X',
+                'THUMBSTICK_Y',
+                'TOUCHPAD_TOUCH_X',
+                'TOUCHPAD_TOUCH_Y'
+            ],
+            pointingPoseMeshName: 'POINTING_POSE'
+        };
+
+        public onTrackpadChangedObservable = new Observable<ExtendedGamepadButton>();
+
+        constructor(vrGamepad) {
+            super(vrGamepad);
+            this.controllerType = PoseEnabledControllerType.WINDOWS;
+            this._loadedMeshInfo = null;
+        }
+        
+        public get onTriggerButtonStateChangedObservable(): Observable<ExtendedGamepadButton> {
+            return this.onTriggerStateChangedObservable;
+        }
+
+        public get onMenuButtonStateChangedObservable(): Observable<ExtendedGamepadButton> {
+            return this.onSecondaryButtonStateChangedObservable;
+        }
+
+        public get onGripButtonStateChangedObservable(): Observable<ExtendedGamepadButton> {
+            return this.onMainButtonStateChangedObservable;
+        }
+
+        public get onThumbstickButtonStateChangedObservable(): Observable<ExtendedGamepadButton> {
+            return this.onPadStateChangedObservable;
+        }    
+
+        public get onTouchpadButtonStateChangedObservable(): Observable<ExtendedGamepadButton> {
+            return this.onTrackpadChangedObservable;
+        }
+        
+        /**
+         * Called once per frame by the engine.
+         */
+        public update() {
+            super.update();
+            
+            // Only need to animate axes if there is a loaded mesh
+            if (this._loadedMeshInfo) {
+                if (this.browserGamepad.axes) {
+                    for (let axis = 0; axis < this._mapping.axisMeshNames.length; axis++) {
+                        this.lerpAxisTransform(axis, this.browserGamepad.axes[axis]);
+                    }
+                }
+            }
+        }
+        
+        /**
+         * Called once for each button that changed state since the last frame
+         * @param buttonIdx Which button index changed
+         * @param state New state of the button
+         * @param changes Which properties on the state changed since last frame
+         */
+        protected handleButtonChange(buttonIdx: number, state: ExtendedGamepadButton, changes: GamepadButtonChanges) {
+            let buttonName = this._mapping.buttons[buttonIdx];
+            if (!buttonName) {
+                return; 
+            }
+
+            // Only emit events for buttons that we know how to map from index to name
+            let observable = this[this._mapping.buttonObservableNames[buttonName]];
+            if (observable) {
+                observable.notifyObservers(state);
+            }
+
+            this.lerpButtonTransform(buttonName, state.value);
+        }
+        
+        protected lerpButtonTransform(buttonName: string, buttonValue: number) {
+            
+            // If there is no loaded mesh, there is nothing to transform.
+            if (!this._loadedMeshInfo) {
+                return;
+            }
+
+            var meshInfo = this._loadedMeshInfo.buttonMeshes[buttonName];
+            BABYLON.Quaternion.SlerpToRef(
+                meshInfo.unpressed.rotationQuaternion, 
+                meshInfo.pressed.rotationQuaternion, 
+                buttonValue,
+                meshInfo.value.rotationQuaternion);
+            BABYLON.Vector3.LerpToRef(
+                meshInfo.unpressed.position, 
+                meshInfo.pressed.position,
+                buttonValue,
+                meshInfo.value.position);
+        }
+        
+        protected lerpAxisTransform(axis:number, axisValue: number) {
+            let meshInfo = this._loadedMeshInfo.axisMeshes[axis];
+            if (!meshInfo) {
+                return;
+            }
+
+            // Convert from gamepad value range (-1 to +1) to lerp range (0 to 1)
+            let lerpValue = axisValue * 0.5 + 0.5;
+            BABYLON.Quaternion.SlerpToRef(
+                meshInfo.min.rotationQuaternion, 
+                meshInfo.max.rotationQuaternion, 
+                lerpValue,
+                meshInfo.value.rotationQuaternion);
+            BABYLON.Vector3.LerpToRef(
+                meshInfo.min.position, 
+                meshInfo.max.position,
+                lerpValue,
+                meshInfo.value.position);
+        }
+        
+        /**
+         * Implements abstract method on WebVRController class, loading controller meshes and calling this.attachToMesh if successful.
+         * @param scene scene in which to add meshes
+         * @param meshLoaded optional callback function that will be called if the mesh loads successfully.
+         */
+        public initControllerMesh(scene: Scene, meshLoaded?: (mesh: AbstractMesh) => void, forceDefault = false) {
+            let path: string;        
+            let filename: string;
+
+            // Checking if GLB loader is present
+            if (SceneLoader.GetPluginForExtension("glb")) {
+                // Determine the device specific folder based on the ID suffix
+                let device = 'default';
+                if (this.id && !forceDefault) {
+                    let match = this.id.match(WindowsMotionController.GAMEPAD_ID_PATTERN);
+                    device = ((match && match[0]) || device);
+                }
+
+                // Hand
+                if (this.hand === 'left') {
+                    filename = WindowsMotionController.MODEL_LEFT_FILENAME;
+                }
+                else { // Right is the default if no hand is specified
+                    filename = WindowsMotionController.MODEL_RIGHT_FILENAME;
+                }
+
+                path = WindowsMotionController.MODEL_BASE_URL + device + '/';
+            } else {
+                Tools.Warn("You need to reference GLTF loader to load Windows Motion Controllers model. Falling back to generic models");
+                path = GenericController.MODEL_BASE_URL;
+                filename = GenericController.MODEL_FILENAME;
+            }
+
+
+            SceneLoader.ImportMesh("", path, filename, scene, (meshes: AbstractMesh[]) => {
+                // glTF files successfully loaded from the remote server, now process them to ensure they are in the right format.
+                this._loadedMeshInfo = this.processModel(scene, meshes);
+
+                if (!this._loadedMeshInfo) {
+                    return;
+                }
+
+                this._defaultModel = this._loadedMeshInfo.rootNode;
+                this.attachToMesh(this._defaultModel);
+
+                if (meshLoaded) {
+                    meshLoaded(this._defaultModel);
+                }
+            }, null, (scene: Scene, message: string) => {
+                Tools.Log(message);
+                Tools.Warn('Failed to retrieve controller model from the remote server: ' + path + filename);
+                if (!forceDefault) {
+                    this.initControllerMesh(scene, meshLoaded, true);
+                }
+            });
+        }
+
+        /**
+         * Takes a list of meshes (as loaded from the glTF file) and finds the root node, as well as nodes that 
+         * can be transformed by button presses and axes values, based on this._mapping.
+         * 
+         * @param scene scene in which the meshes exist
+         * @param meshes list of meshes that make up the controller model to process
+         * @return structured view of the given meshes, with mapping of buttons and axes to meshes that can be transformed.
+         */
+        private processModel(scene: Scene, meshes: AbstractMesh[]) : LoadedMeshInfo {
+            let loadedMeshInfo = null;
+
+            // Create a new mesh to contain the glTF hierarchy
+            let parentMesh = new BABYLON.Mesh(this.id + " " + this.hand, scene);
+
+            // Find the root node in the loaded glTF scene, and attach it as a child of 'parentMesh'
+            let childMesh : AbstractMesh = null;
+            for (let i = 0; i < meshes.length; i++) {
+                let mesh = meshes[i];
+
+                if (!mesh.parent) {
+                    // Exclude controller meshes from picking results
+                    mesh.isPickable = false;
+                    
+                    // Handle root node, attach to the new parentMesh
+                    childMesh = mesh;
+                    break;
+                }
+            }
+
+            if (childMesh) {
+                childMesh.setParent(parentMesh);
+
+                // Create our mesh info. Note that this method will always return non-null.
+                loadedMeshInfo = this.createMeshInfo(parentMesh);
+            } else {
+                Tools.Warn('Could not find root node in model file.');
+            }
+
+            return loadedMeshInfo;
+        }
+        
+        private createMeshInfo(rootNode: AbstractMesh) : LoadedMeshInfo {
+            let loadedMeshInfo = new LoadedMeshInfo();
+            var i;
+            loadedMeshInfo.rootNode = rootNode;
+
+            // Reset the caches
+            loadedMeshInfo.buttonMeshes = {};
+            loadedMeshInfo.axisMeshes = {};
+
+            // Button Meshes
+            for (i = 0; i < this._mapping.buttons.length; i++) {
+                var buttonMeshName = this._mapping.buttonMeshNames[this._mapping.buttons[i]];
+                if (!buttonMeshName) {
+                    Tools.Log('Skipping unknown button at index: ' + i + ' with mapped name: ' + this._mapping.buttons[i]);
+                    continue;
+                }
+
+                var buttonMesh = getChildByName(rootNode, buttonMeshName);
+                if (!buttonMesh) {
+                    Tools.Warn('Missing button mesh with name: ' + buttonMeshName);
+                    continue;
+                }
+
+                var buttonMeshInfo = {
+                    index: i,
+                    value: getImmediateChildByName(buttonMesh, 'VALUE'),
+                    pressed: getImmediateChildByName(buttonMesh, 'PRESSED'),
+                    unpressed: getImmediateChildByName(buttonMesh, 'UNPRESSED')
+                };
+                if (buttonMeshInfo.value && buttonMeshInfo.pressed && buttonMeshInfo.unpressed) {
+                    loadedMeshInfo.buttonMeshes[this._mapping.buttons[i]] = buttonMeshInfo;
+                } else {
+                    // If we didn't find the mesh, it simply means this button won't have transforms applied as mapped button value changes.
+                    Tools.Warn('Missing button submesh under mesh with name: ' + buttonMeshName +
+                        '(VALUE: ' + !!buttonMeshInfo.value +
+                        ', PRESSED: ' + !!buttonMeshInfo.pressed +
+                        ', UNPRESSED:' + !!buttonMeshInfo.unpressed +
+                        ')');
+                }
+            }
+
+            // Axis Meshes
+            for (i = 0; i < this._mapping.axisMeshNames.length; i++) {
+                var axisMeshName = this._mapping.axisMeshNames[i];
+                if (!axisMeshName) {
+                    Tools.Log('Skipping unknown axis at index: ' + i);
+                    continue;
+                }
+
+                var axisMesh = getChildByName(rootNode, axisMeshName);
+                if (!axisMesh) {
+                    Tools.Warn('Missing axis mesh with name: ' + axisMeshName);
+                    continue;
+                }
+
+                var axisMeshInfo = {
+                    index: i,
+                    value: getImmediateChildByName(axisMesh, 'VALUE'),
+                    min: getImmediateChildByName(axisMesh, 'MIN'),
+                    max: getImmediateChildByName(axisMesh, 'MAX')
+                };
+                if (axisMeshInfo.value && axisMeshInfo.min && axisMeshInfo.max) {
+                    loadedMeshInfo.axisMeshes[i] = axisMeshInfo;
+                } else {
+                    // If we didn't find the mesh, it simply means thit axis won't have transforms applied as mapped axis values change.
+                    Tools.Warn('Missing axis submesh under mesh with name: ' + axisMeshName +
+                        '(VALUE: ' + !!axisMeshInfo.value +
+                        ', MIN: ' + !!axisMeshInfo.min +
+                        ', MAX:' + !!axisMeshInfo.max +
+                        ')');
+                }
+            }
+
+            // Pointing Ray
+            loadedMeshInfo.pointingPoseNode = getChildByName(rootNode, this._mapping.pointingPoseMeshName);
+            if (!loadedMeshInfo.pointingPoseNode) {                
+                Tools.Warn('Missing pointing pose mesh with name: ' + this._mapping.pointingPoseMeshName);
+            }
+
+            return loadedMeshInfo;
+            
+            // Look through all children recursively. This will return null if no mesh exists with the given name.
+            function getChildByName(node, name) {
+                return node.getChildMeshes(false, n => n.name === name)[0];
+            }
+            // Look through only immediate children. This will return null if no mesh exists with the given name.
+            function getImmediateChildByName (node, name) : AbstractMesh {
+                return node.getChildMeshes(true, n => n.name == name)[0];
+            }
+        }
+
+        public getForwardRay(length = 100): Ray {
+            if (!(this._loadedMeshInfo && this._loadedMeshInfo.pointingPoseNode)) {
+                return super.getForwardRay(length);
+            }
+
+            var m = this._loadedMeshInfo.pointingPoseNode.getWorldMatrix();
+            var origin = m.getTranslation();
+
+            var forward = new BABYLON.Vector3(0, 0, -1);
+            var forwardWorld = BABYLON.Vector3.TransformNormal(forward, m);
+
+            var direction = BABYLON.Vector3.Normalize(forwardWorld);            
+
+            return new Ray(origin, direction, length);
+        }
+
+        public dispose(): void {
+            super.dispose();
+
+            this.onTrackpadChangedObservable.clear();
+        }
+    }
+}

+ 6 - 0
src/Gamepad/babylon.gamepad.ts

@@ -18,6 +18,8 @@
         private _leftStick: StickValues;
         private _rightStick: StickValues;
 
+        public _isConnected = true;
+
         private _leftStickAxisX: number;
         private _leftStickAxisY: number;
         private _rightStickAxisX: number;
@@ -31,6 +33,10 @@
         public static XBOX = 2;
         public static POSE_ENABLED = 3;
 
+        public get isConnected(): boolean {
+            return this._isConnected;
+        }
+
         constructor(public id: string, public index: number, public browserGamepad, leftStickX: number = 0, leftStickY: number = 1, rightStickX: number = 2, rightStickY: number = 3) {
             this.type = Gamepad.GAMEPAD;
             this._leftStickAxisX = leftStickX;

+ 31 - 11
src/Gamepad/babylon.gamepadManager.ts

@@ -18,13 +18,21 @@
             this._onGamepadConnectedEvent = (evt) => {
                 let gamepad = evt.gamepad;
 
-                // Protection code for Chrome which has a very buggy gamepad implementation...
-                // And raises a connected event on disconnection for instance
                 if (gamepad.index in this._babylonGamepads) {
-                    return;
+                    if (this._babylonGamepads[gamepad.index].isConnected) {
+                        return;
+                    }
                 }
 
-                var newGamepad = this._addNewGamepad(gamepad);
+                let newGamepad: Gamepad;
+
+                if (this._babylonGamepads[gamepad.index]) {
+                    newGamepad = this._babylonGamepads[gamepad.index];
+                    newGamepad.browserGamepad = gamepad;
+                    newGamepad._isConnected = true;
+                } else {
+                    newGamepad = this._addNewGamepad(gamepad);
+                }
                 this.onGamepadConnectedObservable.notifyObservers(newGamepad);
                 this._startMonitoringGamepads();                
             };
@@ -35,12 +43,10 @@
                 // Remove the gamepad from the list of gamepads to monitor.
                 for (var i in this._babylonGamepads) {
                     if (this._babylonGamepads[i].index === gamepad.index) {
-                        let gamepadToRemove = this._babylonGamepads[i];
-                        this._babylonGamepads[i] = null;
+                        let disconnectedGamepad = this._babylonGamepads[i];
+                        disconnectedGamepad._isConnected = false;
                         
-                        this.onGamepadDisconnectedObservable.notifyObservers(gamepadToRemove);
-
-                        gamepadToRemove.dispose();
+                        this.onGamepadDisconnectedObservable.notifyObservers(disconnectedGamepad);
                         break;
                     }
                 }            
@@ -63,7 +69,11 @@
             }
         }
 
-        public getGamepadByType(type: number = Gamepad.XBOX) {
+        public get gamepads(): Gamepad[] {
+            return this._babylonGamepads;
+        }
+
+        public getGamepadByType(type: number = Gamepad.XBOX): Gamepad {
             for (var gamepad of this._babylonGamepads) {
                 if (gamepad && gamepad.type === type) {
                     return gamepad;
@@ -80,6 +90,11 @@
                 this._onGamepadConnectedEvent = null;
                 this._onGamepadDisconnectedEvent = null;
             }
+
+            for (let gamepad of this._babylonGamepads) {
+                gamepad.dispose();
+            }
+
             this._oneGamepadConnected = false;
             this._stopMonitoringGamepads();
             this._babylonGamepads = [];
@@ -123,7 +138,7 @@
 
             for (var i in this._babylonGamepads) {
                 let gamepad = this._babylonGamepads[i];
-                if (!gamepad) {
+                if (!gamepad || !gamepad.isConnected) {
                     continue;
                 }
                 gamepad.update();
@@ -147,6 +162,11 @@
                     else {
                         // Forced to copy again this object for Chrome for unknown reason
                         this._babylonGamepads[i].browserGamepad = gamepads[i];
+
+                        if (!this._babylonGamepads[i].isConnected) {
+                            this._babylonGamepads[i]._isConnected = true;                            
+                            this.onGamepadConnectedObservable.notifyObservers(this._babylonGamepads[i]);
+                        }
                     }
                 }
             }

+ 27 - 0
src/Mesh/babylon.abstractMesh.ts

@@ -1156,6 +1156,33 @@
         }
 
         /**
+         * Return the minimum and maximum world vectors of the entire hierarchy under current mesh
+         */
+        public getHierarchyBoundingVectors(): { min: Vector3, max: Vector3 }{
+            this.computeWorldMatrix();
+            let min = this.getBoundingInfo().boundingBox.minimumWorld;
+            let max = this.getBoundingInfo().boundingBox.maximumWorld;
+
+            let descendants = this.getDescendants(false, (node) => (<any>node).subMeshes);
+
+            for (var descendant of descendants) {
+                let childMesh = <AbstractMesh>descendant;
+
+                childMesh.computeWorldMatrix();
+                var minBox = childMesh.getBoundingInfo().boundingBox.minimumWorld;
+                var maxBox = childMesh.getBoundingInfo().boundingBox.maximumWorld;
+
+                Tools.CheckExtends(minBox, min, max);
+                Tools.CheckExtends(maxBox, min, max);
+            }
+
+            return {
+                min: min,
+                max: max
+            }
+        }
+
+        /**
          * Updates the mesh BoundingInfo object and all its children BoundingInfo objects also.  
          * Returns the AbstractMesh.  
          */

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

@@ -728,7 +728,7 @@
             var height = options.height || 10.0;
             var subdivisions = options.subdivisions || 1|0;
             var minHeight = options.minHeight || 0.0;
-            var maxHeight = options.maxHeight || 10.0;
+            var maxHeight = options.maxHeight || 1.0;
             var filter = options.colorFilter || new Color3(0.3, 0.59, 0.11);
             var updatable = options.updatable;
             var onReady = options.onReady;

+ 10 - 2
src/Tools/babylon.filesInput.ts

@@ -2,6 +2,8 @@
     export class FilesInput {
         public static FilesToLoad: File[] = new Array<File>();
 
+        public onProcessFileCallback: (file: File, name: string, extension: string) => true = () => { return true; };
+
         private _engine: Engine;
         private _currentScene: Scene;
         private _sceneLoadedCallback: (sceneFile: File, scene: Scene) => void;
@@ -114,10 +116,16 @@
         }
 
         private _processFiles(files: Array<any>): void {
+            var skippedFiles = 0;
             for (var i = 0; i < files.length; i++) {
                 var name = files[i].correctName.toLowerCase();
                 var extension = name.split('.').pop();
-                
+
+                if (!this.onProcessFileCallback(files[i], name, extension)) {
+                    skippedFiles++;
+                    continue;
+                }
+
                 if ((extension === "babylon" || extension === "stl" || extension === "obj" || extension === "gltf" || extension === "glb") 
                     && name.indexOf(".binary.babylon") === -1 && name.indexOf(".incremental.babylon") === -1) {
                     this._sceneFileToLoad = files[i];
@@ -130,7 +138,7 @@
             if (this._onReloadCallback) {
                 this._onReloadCallback(this._sceneFileToLoad);
             }
-            else {
+            else if (skippedFiles < files.length) {
                 this.reload();
             }
         }

+ 11 - 2
src/babylon.engine.ts

@@ -564,6 +564,11 @@
         private _vrDisplayEnabled;
         private _oldSize: BABYLON.Size;
         private _oldHardwareScaleFactor: number;
+        private _vrExclusivePointerMode = false;
+
+        public get isInVRExclusivePointerMode(): boolean {
+            return this._vrExclusivePointerMode;
+        }
 
         // Uniform buffers list
         public disableUniformBuffers = false;
@@ -941,10 +946,12 @@
                 document.addEventListener("webkitpointerlockchange", this._onPointerLockChange, false);
 
                 this._onVRDisplayPointerRestricted = () => {
+                    this._vrExclusivePointerMode = true;
                     canvas.requestPointerLock();
                 }
 
                 this._onVRDisplayPointerUnrestricted = () => {
+                    this._vrExclusivePointerMode = false;
                     document.exitPointerLock();
                 }
 
@@ -4235,8 +4242,10 @@
             var value = texture.anisotropicFilteringLevel;
 
 
-            if (internalTexture.samplingMode === Texture.NEAREST_SAMPLINGMODE) {
-                value = 1;
+            if (internalTexture.samplingMode !== Texture.LINEAR_LINEAR_MIPNEAREST 
+                && internalTexture.samplingMode !== Texture.LINEAR_LINEAR_MIPLINEAR
+                && internalTexture.samplingMode !== Texture.LINEAR_LINEAR) {
+                value = 1; // Forcing the anisotropic to 1 because else webgl will force filters to linear
             }
 
             if (anisotropicFilterExtension && internalTexture._cachedAnisotropicFilteringLevel !== value) {

+ 10 - 8
src/babylon.scene.ts

@@ -2829,13 +2829,14 @@
                 throw new Error("Active camera not set");
 
             Tools.StartPerformanceCounter("Rendering camera " + this.activeCamera.name);
-
+           
             // Viewport
             engine.setViewport(this.activeCamera.viewport);
 
             // Camera
             this.resetCachedMaterial();
             this._renderId++;
+            this.activeCamera.update();
             this.updateTransformMatrix();
 
             if (camera._alternateCamera) {
@@ -3003,10 +3004,7 @@
 
             // Finalize frame
             this.postProcessManager._finalizeFrame(camera.isIntermediate);
-
-            // Update camera
-            this.activeCamera._updateFromScene();
-
+           
             // Reset some special arrays
             this._renderTargets.reset();
 
@@ -3023,6 +3021,9 @@
                 return;
             }
 
+            // Update camera
+            this.activeCamera.update();
+            
             // rig cameras
             for (var index = 0; index < camera._rigCameras.length; index++) {
                 this._renderForCamera(camera._rigCameras[index]);
@@ -3030,9 +3031,6 @@
 
             this.activeCamera = camera;
             this.setTransformMatrix(this.activeCamera.getViewMatrix(), this.activeCamera.getProjectionMatrix());
-
-            // Update camera
-            this.activeCamera._updateFromScene();
         }
 
         private _checkIntersections(): void {
@@ -3659,6 +3657,10 @@
             for (var index = 0; index < this.meshes.length; index++) {
                 var mesh = this.meshes[index];
 
+                if (!mesh.subMeshes || mesh.subMeshes.length === 0) {
+                    continue;
+                }
+
                 mesh.computeWorldMatrix(true);
                 var minBox = mesh.getBoundingInfo().boundingBox.minimumWorld;
                 var maxBox = mesh.getBoundingInfo().boundingBox.maximumWorld;