Browse Source

Merge remote-tracking branch 'refs/remotes/BabylonJS/master' into ParticleAnimation

# Conflicts:
#	dist/preview release/babylon.d.ts
#	dist/preview release/babylon.js
#	dist/preview release/babylon.module.d.ts
#	dist/preview release/babylon.worker.js
DESKTOP-QJU4N0L\mityh 8 years ago
parent
commit
cd3137bad8
49 changed files with 494091 additions and 31240 deletions
  1. 1 0
      Tools/Gulp/config.json
  2. 2 0
      assets/meshes/controllers/_headers
  3. 33778 0
      assets/meshes/controllers/generic/generic.babylon
  4. BIN
      assets/meshes/controllers/generic/vr_controller_01_mrhat.png
  5. BIN
      assets/meshes/controllers/generic/vr_controller_01_mrhat_MetallicGlossMap.png
  6. BIN
      assets/meshes/controllers/microsoft/045E-065B/left.glb
  7. BIN
      assets/meshes/controllers/microsoft/045E-065B/right.glb
  8. BIN
      assets/meshes/controllers/microsoft/default/left.glb
  9. BIN
      assets/meshes/controllers/microsoft/default/right.glb
  10. BIN
      assets/meshes/controllers/oculus/external_controller01_col.png
  11. BIN
      assets/meshes/controllers/oculus/external_controller01_col_MetallicGlossMap.png
  12. 128945 0
      assets/meshes/controllers/oculus/left.babylon
  13. 128945 0
      assets/meshes/controllers/oculus/right.babylon
  14. BIN
      assets/meshes/controllers/vive/onepointfive_texture.png
  15. BIN
      assets/meshes/controllers/vive/onepointfive_texture_MetallicGlossMap.png
  16. 170697 0
      assets/meshes/controllers/vive/wand.babylon
  17. 7867 7857
      dist/preview release/babylon.d.ts
  18. 39 39
      dist/preview release/babylon.js
  19. 492 364
      dist/preview release/babylon.max.js
  20. 7867 7857
      dist/preview release/babylon.module.d.ts
  21. 45 45
      dist/preview release/babylon.worker.js
  22. 6049 6018
      dist/preview release/customConfigurations/minimalGLTFViewer/babylon.d.ts
  23. 48 48
      dist/preview release/customConfigurations/minimalGLTFViewer/babylon.js
  24. 2408 2280
      dist/preview release/customConfigurations/minimalGLTFViewer/babylon.max.js
  25. 6049 6018
      dist/preview release/customConfigurations/minimalGLTFViewer/babylon.module.d.ts
  26. 3 3
      dist/preview release/gui/babylon.gui.min.js
  27. 263 263
      dist/preview release/inspector/babylon.inspector.bundle.js
  28. 3 3
      dist/preview release/inspector/babylon.inspector.min.js
  29. 2 2
      dist/preview release/loaders/babylon.glTF1FileLoader.min.js
  30. 1 1
      dist/preview release/loaders/babylon.glTF2FileLoader.min.js
  31. 2 2
      dist/preview release/loaders/babylon.glTFFileLoader.min.js
  32. 1 1
      dist/preview release/loaders/babylon.objFileLoader.min.js
  33. 1 1
      dist/preview release/materialsLibrary/babylon.customMaterial.min.js
  34. 1 1
      dist/preview release/materialsLibrary/babylon.shadowOnlyMaterial.min.js
  35. 1 1
      dist/preview release/materialsLibrary/babylon.waterMaterial.min.js
  36. 1 1
      dist/preview release/postProcessesLibrary/babylon.asciiArtPostProcess.min.js
  37. 1 1
      dist/preview release/postProcessesLibrary/babylon.digitalRainPostProcess.min.js
  38. 1 0
      sandbox/index.js
  39. 54 38
      src/Animations/babylon.animatable.ts
  40. 25 356
      src/Animations/babylon.animation.ts
  41. 390 0
      src/Animations/babylon.runtimeAnimation.ts
  42. 1 1
      src/Bones/babylon.bone.ts
  43. 4 1
      src/Gamepad/Controllers/babylon.genericController.ts
  44. 15 2
      src/Gamepad/Controllers/babylon.oculusTouchController.ts
  45. 3 1
      src/Gamepad/Controllers/babylon.viveController.ts
  46. 55 33
      src/Gamepad/Controllers/babylon.windowsMotionController.ts
  47. 27 0
      src/Mesh/babylon.abstractMesh.ts
  48. 0 2
      src/babylon.engine.ts
  49. 4 0
      src/babylon.scene.ts

+ 1 - 0
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"
             ],

+ 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


BIN
assets/meshes/controllers/generic/vr_controller_01_mrhat.png


BIN
assets/meshes/controllers/generic/vr_controller_01_mrhat_MetallicGlossMap.png


BIN
assets/meshes/controllers/microsoft/045E-065B/left.glb


BIN
assets/meshes/controllers/microsoft/045E-065B/right.glb


BIN
assets/meshes/controllers/microsoft/default/left.glb


BIN
assets/meshes/controllers/microsoft/default/right.glb


BIN
assets/meshes/controllers/oculus/external_controller01_col.png


BIN
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


BIN
assets/meshes/controllers/vive/onepointfive_texture.png


BIN
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
+ 7867 - 7857
dist/preview release/babylon.d.ts


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


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


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


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


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


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


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


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


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


File diff suppressed because it is too large
+ 263 - 263
dist/preview release/inspector/babylon.inspector.bundle.js


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


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


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


File diff suppressed because it is too large
+ 2 - 2
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


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


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


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


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


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


+ 1 - 0
sandbox/index.js

@@ -73,6 +73,7 @@
 
             var framingBehavior = currentScene.activeCamera.getBehaviorByName("Framing");
             framingBehavior.framingTime = 0;
+            framingBehavior.elevationReturnTime = -1;
 
             var bouncingBehavior = currentScene.activeCamera.getBehaviorByName("Bouncing");
             bouncingBehavior.autoTransitionRange = true;        

+ 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;

+ 4 - 1
src/Gamepad/Controllers/babylon.genericController.ts

@@ -1,12 +1,15 @@
 module BABYLON {
     
     export class GenericController extends WebVRController {
+        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);

+ 15 - 2
src/Gamepad/Controllers/babylon.oculusTouchController.ts

@@ -1,6 +1,10 @@
 module BABYLON {
 
     export class OculusTouchController extends WebVRController {
+        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>();
 
         public onThumbRestChangedObservable = new Observable<ExtendedGamepadButton>();
@@ -11,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

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

@@ -1,6 +1,8 @@
 module BABYLON {
 
     export class ViveController extends WebVRController {
+        private static readonly MODEL_BASE_URL:string = 'https://controllers.babylonjs.com/vive/';
+        private static readonly MODEL_FILENAME:string = 'wand.babylon';
 
         constructor(vrGamepad) {
             super(vrGamepad);
@@ -8,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

+ 55 - 33
src/Gamepad/Controllers/babylon.windowsMotionController.ts

@@ -3,7 +3,6 @@ module BABYLON {
     class LoadedMeshInfo {
         public rootNode: AbstractMesh;
         public pointingPoseNode: AbstractMesh;
-        public holdingPoseNode: AbstractMesh;
         public buttonMeshes: { [id: string] : IButtonMeshInfo; } = {};
         public axisMeshes: { [id: number] : IAxisMeshInfo; } = {};
     }
@@ -27,15 +26,10 @@ module BABYLON {
         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';
-        private static readonly MODEL_ROOT_NODE_NAME:string = 'RootNode';
-        private static readonly GLTF_ROOT_TRANSFORM_NAME:string = 'root';
 
         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]+)$/;
 
-        // Art assets is backward facing
-        private static readonly ROTATE_OFFSET:number[] = [Math.PI, 0, 0]; // x, y, z.
-
         private _loadedMeshInfo: LoadedMeshInfo;
         private readonly _mapping = {
             // Semantic button names
@@ -67,7 +61,8 @@ module BABYLON {
                 'THUMBSTICK_Y',
                 'TOUCHPAD_TOUCH_X',
                 'TOUCHPAD_TOUCH_Y'
-            ]
+            ],
+            pointingPoseMeshName: 'POINTING_POSE'
         };
 
         public onTrackpadChangedObservable = new Observable<ExtendedGamepadButton>();
@@ -181,23 +176,33 @@ module BABYLON {
          * @param meshLoaded optional callback function that will be called if the mesh loads successfully.
          */
         public initControllerMesh(scene: Scene, meshLoaded?: (mesh: AbstractMesh) => void, forceDefault = false) {
-            // 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
+            let path: string;        
             let filename: string;
-            if (this.hand === 'left') {
-                filename = WindowsMotionController.MODEL_LEFT_FILENAME;
-            }
-            else { // Right is the default if no hand is specified
-                filename = WindowsMotionController.MODEL_RIGHT_FILENAME;
+
+            // 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;
             }
 
-            let path = WindowsMotionController.MODEL_BASE_URL + device + '/';
 
             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.
@@ -216,7 +221,9 @@ module BABYLON {
             }, null, (scene: Scene, message: string) => {
                 Tools.Log(message);
                 Tools.Warn('Failed to retrieve controller model from the remote server: ' + path + filename);
-                this.initControllerMesh(scene, meshLoaded, true);
+                if (!forceDefault) {
+                    this.initControllerMesh(scene, meshLoaded, true);
+                }
             });
         }
 
@@ -238,15 +245,12 @@ module BABYLON {
             let childMesh : AbstractMesh = null;
             for (let i = 0; i < meshes.length; i++) {
                 let mesh = meshes[i];
-                if (mesh.id === WindowsMotionController.MODEL_ROOT_NODE_NAME) {
-                    // There may be a parent mesh to perform the RH to LH matrix transform.
+
+                if (!mesh.parent) {
                     // Exclude controller meshes from picking results
                     mesh.isPickable = false;
-
-                    // Handle root node, attach to the new parentMesh
-                    if (mesh.parent && mesh.parent.name === WindowsMotionController.GLTF_ROOT_TRANSFORM_NAME)
-                        mesh = <AbstractMesh>mesh.parent;
                     
+                    // Handle root node, attach to the new parentMesh
                     childMesh = mesh;
                     break;
                 }
@@ -257,12 +261,8 @@ module BABYLON {
 
                 // Create our mesh info. Note that this method will always return non-null.
                 loadedMeshInfo = this.createMeshInfo(parentMesh);
-
-                // Apply rotation offsets
-                var rotOffset = WindowsMotionController.ROTATE_OFFSET;
-                childMesh.addRotation(rotOffset[0], rotOffset[1], rotOffset[2]);
             } else {
-                Tools.Warn('No node with name ' + WindowsMotionController.MODEL_ROOT_NODE_NAME +' in model file.');
+                Tools.Warn('Could not find root node in model file.');
             }
 
             return loadedMeshInfo;
@@ -341,6 +341,12 @@ module BABYLON {
                 }
             }
 
+            // 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.
@@ -353,6 +359,22 @@ module BABYLON {
             }
         }
 
+        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();
 

+ 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.  
          */

+ 0 - 2
src/babylon.engine.ts

@@ -947,13 +947,11 @@
 
                 this._onVRDisplayPointerRestricted = () => {
                     this._vrExclusivePointerMode = true;
-                    console.log("enter");
                     canvas.requestPointerLock();
                 }
 
                 this._onVRDisplayPointerUnrestricted = () => {
                     this._vrExclusivePointerMode = false;
-                    console.log("exit");
                     document.exitPointerLock();
                 }
 

+ 4 - 0
src/babylon.scene.ts

@@ -3657,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;