Browse Source

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

Julien Moreau-Mathis 8 years ago
parent
commit
247892729f
53 changed files with 22005 additions and 20654 deletions
  1. 2 0
      Playground/indexStable.html
  2. 2 0
      Tools/Gulp/config.json
  3. 6099 6050
      dist/preview release/babylon.d.ts
  4. 43 43
      dist/preview release/babylon.js
  5. 381 247
      dist/preview release/babylon.max.js
  6. 6099 6050
      dist/preview release/babylon.module.d.ts
  7. 44 44
      dist/preview release/babylon.worker.js
  8. 3746 3697
      dist/preview release/customConfigurations/minimalGLTFViewer/babylon.d.ts
  9. 33 33
      dist/preview release/customConfigurations/minimalGLTFViewer/babylon.js
  10. 417 224
      dist/preview release/customConfigurations/minimalGLTFViewer/babylon.max.js
  11. 3746 3697
      dist/preview release/customConfigurations/minimalGLTFViewer/babylon.module.d.ts
  12. 24 3
      dist/preview release/gui/babylon.gui.d.ts
  13. 210 14
      dist/preview release/gui/babylon.gui.js
  14. 3 3
      dist/preview release/gui/babylon.gui.min.js
  15. 24 3
      dist/preview release/gui/babylon.gui.module.d.ts
  16. 263 263
      dist/preview release/inspector/babylon.inspector.bundle.js
  17. 3 3
      dist/preview release/inspector/babylon.inspector.min.js
  18. 1 0
      dist/preview release/loaders/babylon.glTF1FileLoader.d.ts
  19. 2 2
      dist/preview release/loaders/babylon.glTF1FileLoader.min.js
  20. 8 1
      dist/preview release/loaders/babylon.glTF2FileLoader.d.ts
  21. 77 10
      dist/preview release/loaders/babylon.glTF2FileLoader.js
  22. 1 1
      dist/preview release/loaders/babylon.glTF2FileLoader.min.js
  23. 9 1
      dist/preview release/loaders/babylon.glTFFileLoader.d.ts
  24. 77 10
      dist/preview release/loaders/babylon.glTFFileLoader.js
  25. 3 3
      dist/preview release/loaders/babylon.glTFFileLoader.min.js
  26. 1 1
      dist/preview release/loaders/babylon.objFileLoader.min.js
  27. 1 1
      dist/preview release/materialsLibrary/babylon.customMaterial.min.js
  28. 1 1
      dist/preview release/materialsLibrary/babylon.shadowOnlyMaterial.min.js
  29. 1 1
      dist/preview release/materialsLibrary/babylon.waterMaterial.min.js
  30. 1 1
      dist/preview release/postProcessesLibrary/babylon.asciiArtPostProcess.min.js
  31. 1 1
      dist/preview release/postProcessesLibrary/babylon.digitalRainPostProcess.min.js
  32. 4 2
      dist/preview release/what's new.md
  33. 52 7
      gui/src/advancedDynamicTexture.ts
  34. 1 0
      gui/src/controls/control.ts
  35. 185 11
      gui/src/controls/inputText.ts
  36. 1 0
      loaders/src/glTF/1.0/babylon.glTFLoaderInterfaces.ts
  37. 103 10
      loaders/src/glTF/2.0/babylon.glTFLoader.ts
  38. 1 1
      localDev/index.html
  39. 3 2
      src/Animations/babylon.animation.ts
  40. 57 78
      src/Cameras/Inputs/babylon.arcRotateCameraKeyboardMoveInput.ts
  41. 28 28
      src/Cameras/Inputs/babylon.freeCameraKeyboardMoveInput.ts
  42. 11 3
      src/Cameras/VR/babylon.webVRCamera.ts
  43. 4 4
      src/Cameras/babylon.freeCamera.ts
  44. 5 5
      src/Collisions/babylon.collisionCoordinator.ts
  45. 33 0
      src/Events/babylon.keyboardEvents.ts
  46. 69 0
      src/Events/babylon.pointerEvents.ts
  47. 3 3
      src/Materials/PBR/babylon.pbrBaseMaterial.ts
  48. 3 3
      src/Materials/babylon.effect.ts
  49. 1 1
      src/Materials/babylon.material.ts
  50. 1 1
      src/Materials/babylon.uniformBuffer.ts
  51. 2 2
      src/Mesh/babylon.abstractMesh.ts
  52. 48 8
      src/babylon.engine.ts
  53. 67 77
      src/babylon.scene.ts

+ 2 - 0
Playground/indexStable.html

@@ -45,6 +45,8 @@
     <script src="https://cdn.babylonjs.com/loaders/babylon.objFileLoader.js"></script>
     <script src="https://cdn.babylonjs.com/loaders/babylon.stlFileLoader.js"></script>
 
+    <script src="https://cdn.babylonjs.com/gui/babylon.gui.min.js"></script>    
+
     <script src="https://rawgit.com/BabylonJS/Extensions/master/ClonerSystem/src/babylonx.cloner.js"></script>
     <link href="css/index.css" rel="stylesheet" />
 </head>

+ 2 - 0
Tools/Gulp/config.json

@@ -45,6 +45,8 @@
         "core" :
         {
             "files":[
+                "../../src/Events/babylon.keyboardEvents.js",
+                "../../src/Events/babylon.pointerEvents.js",
                 "../../src/Math/babylon.math.js",
                 "../../src/Math/babylon.math.scalar.js",
                 "../../src/babylon.mixins.js",

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


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


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


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


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


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


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


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


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


+ 24 - 3
dist/preview release/gui/babylon.gui.d.ts

@@ -1,14 +1,21 @@
 
 declare module BABYLON.GUI {
+    interface IFocusableControl {
+        onFocus(): void;
+        onBlur(): void;
+        processKeyboard(evt: KeyboardEvent): void;
+    }
     class AdvancedDynamicTexture extends DynamicTexture {
         private _isDirty;
         private _renderObserver;
         private _resizeObserver;
+        private _preKeyboardObserver;
         private _pointerMoveObserver;
         private _pointerObserver;
-        private _canvasBlurObserver;
+        private _canvasPointerOutObserver;
         private _background;
         _rootContainer: Container;
+        _lastPickedControl: Control;
         _lastControlOver: Control;
         _lastControlDown: Control;
         _capturingControl: Control;
@@ -20,12 +27,14 @@ declare module BABYLON.GUI {
         private _idealWidth;
         private _idealHeight;
         private _renderAtIdealSize;
+        private _focusedControl;
         background: string;
         idealWidth: number;
         idealHeight: number;
         renderAtIdealSize: boolean;
         readonly layer: Layer;
         readonly rootContainer: Container;
+        focusedControl: IFocusableControl;
         constructor(name: string, width: number, height: number, scene: Scene, generateMipMaps?: boolean, samplingMode?: number);
         executeOnAllControls(func: (control: Control) => void, container?: Container): void;
         markAsDirty(): void;
@@ -39,7 +48,7 @@ declare module BABYLON.GUI {
         private _doPicking(x, y, type);
         attach(): void;
         attachToMesh(mesh: AbstractMesh, supportPointerMove?: boolean): void;
-        private _attachToOnBlur(scene);
+        private _attachToOnPointerOut(scene);
         static CreateForMesh(mesh: AbstractMesh, width?: number, height?: number, supportPointerMove?: boolean): AdvancedDynamicTexture;
         static CreateFullscreenUI(name: string, foreground?: boolean, scene?: Scene): AdvancedDynamicTexture;
     }
@@ -584,22 +593,34 @@ declare module BABYLON.GUI {
 
 
 declare module BABYLON.GUI {
-    class InputText extends Control {
+    class InputText extends Control implements IFocusableControl {
         name: string;
         private _text;
         private _background;
+        private _focusedBackground;
         private _thickness;
         private _margin;
         private _autoStretchWidth;
         private _maxWidth;
+        private _isFocused;
+        private _blinkTimeout;
+        private _blinkIsEven;
+        private _cursorOffset;
+        private _scrollLeft;
         maxWidth: string | number;
         margin: string;
         autoStretchWidth: boolean;
         thickness: number;
+        focusedBackground: string;
         background: string;
         text: string;
         constructor(name?: string, text?: string);
+        onBlur(): void;
+        onFocus(): void;
         protected _getTypeName(): string;
+        processKeyboard(evt: KeyboardEvent): void;
         _draw(parentMeasure: Measure, context: CanvasRenderingContext2D): void;
+        protected _onPointerDown(coordinates: Vector2): boolean;
+        protected _onPointerUp(coordinates: Vector2): void;
     }
 }

+ 210 - 14
dist/preview release/gui/babylon.gui.js

@@ -30,6 +30,15 @@ var BABYLON;
                 _this._idealHeight = 0;
                 _this._renderAtIdealSize = false;
                 _this._renderObserver = _this.getScene().onBeforeCameraRenderObservable.add(function (camera) { return _this._checkUpdate(camera); });
+                _this._preKeyboardObserver = _this.getScene().onPreKeyboardObservable.add(function (info) {
+                    if (!_this._focusedControl) {
+                        return;
+                    }
+                    if (info.type === BABYLON.KeyboardEventTypes.KEYDOWN) {
+                        _this._focusedControl.processKeyboard(info.event);
+                    }
+                    info.skipOnPointerObservable = true;
+                });
                 _this._rootContainer._link(null, _this);
                 _this.hasAlpha = true;
                 if (!width || !height) {
@@ -111,6 +120,25 @@ var BABYLON;
                 enumerable: true,
                 configurable: true
             });
+            Object.defineProperty(AdvancedDynamicTexture.prototype, "focusedControl", {
+                get: function () {
+                    return this._focusedControl;
+                },
+                set: function (control) {
+                    if (this._focusedControl === control) {
+                        return;
+                    }
+                    if (!this._focusedControl) {
+                        control.onFocus();
+                    }
+                    else {
+                        this._focusedControl.onBlur();
+                    }
+                    this._focusedControl = control;
+                },
+                enumerable: true,
+                configurable: true
+            });
             AdvancedDynamicTexture.prototype.executeOnAllControls = function (func, container) {
                 if (!container) {
                     container = this._rootContainer;
@@ -146,8 +174,8 @@ var BABYLON;
                 if (this._pointerObserver) {
                     this.getScene().onPointerObservable.remove(this._pointerObserver);
                 }
-                if (this._canvasBlurObserver) {
-                    this.getScene().getEngine().onCanvasBlurObservable.remove(this._canvasBlurObserver);
+                if (this._canvasPointerOutObserver) {
+                    this.getScene().getEngine().onCanvasPointerOutObservable.remove(this._canvasPointerOutObserver);
                 }
                 if (this._layerToDispose) {
                     this._layerToDispose.texture = null;
@@ -262,6 +290,12 @@ var BABYLON;
                         this._lastControlOver = null;
                     }
                 }
+                // Focus management
+                if (this._focusedControl) {
+                    if (this._focusedControl !== this._lastPickedControl) {
+                        this.focusedControl = null;
+                    }
+                }
             };
             AdvancedDynamicTexture.prototype.attach = function () {
                 var _this = this;
@@ -281,7 +315,7 @@ var BABYLON;
                     _this._doPicking(x, y, pi.type);
                     pi.skipOnPointerObservable = _this._shouldBlockPointer && pi.type !== BABYLON.PointerEventTypes.POINTERUP;
                 });
-                this._attachToOnBlur(scene);
+                this._attachToOnPointerOut(scene);
             };
             AdvancedDynamicTexture.prototype.attachToMesh = function (mesh, supportPointerMove) {
                 var _this = this;
@@ -312,11 +346,11 @@ var BABYLON;
                     }
                 });
                 mesh.enablePointerMoveEvents = supportPointerMove;
-                this._attachToOnBlur(scene);
+                this._attachToOnPointerOut(scene);
             };
-            AdvancedDynamicTexture.prototype._attachToOnBlur = function (scene) {
+            AdvancedDynamicTexture.prototype._attachToOnPointerOut = function (scene) {
                 var _this = this;
-                this._canvasBlurObserver = scene.getEngine().onCanvasBlurObservable.add(function () {
+                this._canvasPointerOutObserver = scene.getEngine().onCanvasPointerOutObservable.add(function () {
                     if (_this._lastControlOver) {
                         _this._lastControlOver._onPointerOut();
                     }
@@ -1429,6 +1463,7 @@ var BABYLON;
                 if (type === BABYLON.PointerEventTypes.POINTERDOWN) {
                     this._onPointerDown(this._dummyVector2);
                     this._host._lastControlDown = this;
+                    this._host._lastPickedControl = this;
                     return true;
                 }
                 if (type === BABYLON.PointerEventTypes.POINTERUP) {
@@ -3719,10 +3754,14 @@ var BABYLON;
                 _this.name = name;
                 _this._text = "";
                 _this._background = "black";
+                _this._focusedBackground = "black";
                 _this._thickness = 1;
                 _this._margin = new GUI.ValueAndUnit(10, GUI.ValueAndUnit.UNITMODE_PIXEL);
                 _this._autoStretchWidth = true;
                 _this._maxWidth = new GUI.ValueAndUnit(1, GUI.ValueAndUnit.UNITMODE_PERCENTAGE, false);
+                _this._isFocused = false;
+                _this._blinkIsEven = false;
+                _this._cursorOffset = 0;
                 _this.text = text;
                 return _this;
             }
@@ -3784,6 +3823,20 @@ var BABYLON;
                 enumerable: true,
                 configurable: true
             });
+            Object.defineProperty(InputText.prototype, "focusedBackground", {
+                get: function () {
+                    return this._focusedBackground;
+                },
+                set: function (value) {
+                    if (this._focusedBackground === value) {
+                        return;
+                    }
+                    this._focusedBackground = value;
+                    this._markAsDirty();
+                },
+                enumerable: true,
+                configurable: true
+            });
             Object.defineProperty(InputText.prototype, "background", {
                 get: function () {
                     return this._background;
@@ -3812,29 +3865,162 @@ var BABYLON;
                 enumerable: true,
                 configurable: true
             });
+            InputText.prototype.onBlur = function () {
+                this._isFocused = false;
+                this._scrollLeft = null;
+                this._cursorOffset = 0;
+                clearTimeout(this._blinkTimeout);
+                this._markAsDirty();
+            };
+            InputText.prototype.onFocus = function () {
+                this._scrollLeft = null;
+                this._isFocused = true;
+                this._blinkIsEven = false;
+                this._cursorOffset = 0;
+                this._markAsDirty();
+            };
             InputText.prototype._getTypeName = function () {
                 return "InputText";
             };
+            InputText.prototype.processKeyboard = function (evt) {
+                // Specific cases
+                switch (evt.keyCode) {
+                    case 8:// BACKSPACE
+                        if (this._text && this._text.length > 0) {
+                            if (this._cursorOffset === 0) {
+                                this.text = this._text.substr(0, this._text.length - 1);
+                            }
+                            else {
+                                var deletePosition = this._text.length - this._cursorOffset;
+                                if (deletePosition > 0) {
+                                    this.text = this._text.slice(0, deletePosition - 1) + this._text.slice(deletePosition);
+                                }
+                            }
+                        }
+                        return;
+                    case 46:// DELETE
+                        if (this._text && this._text.length > 0) {
+                            var deletePosition = this._text.length - this._cursorOffset;
+                            this.text = this._text.slice(0, deletePosition) + this._text.slice(deletePosition + 1);
+                            this._cursorOffset--;
+                        }
+                        return;
+                    case 13:// RETURN
+                        this._host.focusedControl = null;
+                        return;
+                    case 35:// END
+                        this._cursorOffset = 0;
+                        this._blinkIsEven = false;
+                        this._markAsDirty();
+                        return;
+                    case 36:// HOME
+                        this._cursorOffset = this._text.length;
+                        this._blinkIsEven = false;
+                        this._markAsDirty();
+                        return;
+                    case 37:// LEFT
+                        this._cursorOffset++;
+                        if (this._cursorOffset > this._text.length) {
+                            this._cursorOffset = this._text.length;
+                        }
+                        this._blinkIsEven = false;
+                        this._markAsDirty();
+                        return;
+                    case 39:// RIGHT
+                        this._cursorOffset--;
+                        if (this._cursorOffset < 0) {
+                            this._cursorOffset = 0;
+                        }
+                        this._blinkIsEven = false;
+                        this._markAsDirty();
+                        return;
+                }
+                // Printable characters
+                if ((evt.keyCode === 32) ||
+                    (evt.keyCode > 47 && evt.keyCode < 58) ||
+                    (evt.keyCode > 64 && evt.keyCode < 91) ||
+                    (evt.keyCode > 185 && evt.keyCode < 193) ||
+                    (evt.keyCode > 218 && evt.keyCode < 223) ||
+                    (evt.keyCode > 95 && evt.keyCode < 112)) {
+                    if (this._cursorOffset === 0) {
+                        this.text += evt.key;
+                    }
+                    else {
+                        var insertPosition = this._text.length - this._cursorOffset;
+                        this.text = this._text.slice(0, insertPosition) + evt.key + this._text.slice(insertPosition);
+                    }
+                }
+            };
             InputText.prototype._draw = function (parentMeasure, context) {
+                var _this = this;
                 context.save();
                 this._applyStates(context);
                 if (this._processMeasures(parentMeasure, context)) {
                     // Background
-                    if (this._background) {
+                    if (this._isFocused) {
+                        if (this._focusedBackground) {
+                            context.fillStyle = this._focusedBackground;
+                            context.fillRect(this._currentMeasure.left, this._currentMeasure.top, this._currentMeasure.width, this._currentMeasure.height);
+                        }
+                    }
+                    else if (this._background) {
                         context.fillStyle = this._background;
                         context.fillRect(this._currentMeasure.left, this._currentMeasure.top, this._currentMeasure.width, this._currentMeasure.height);
                     }
+                    if (!this._fontOffset) {
+                        this._fontOffset = GUI.Control._GetFontOffset(context.font);
+                    }
                     // Text
-                    if (this._text) {
-                        if (this.color) {
-                            context.fillStyle = this.color;
+                    var clipTextLeft = this._currentMeasure.left + this._margin.getValueInPixel(this._host, parentMeasure.width);
+                    if (this.color) {
+                        context.fillStyle = this.color;
+                    }
+                    var textWidth = context.measureText(this._text).width;
+                    var marginWidth = this._margin.getValueInPixel(this._host, parentMeasure.width) * 2;
+                    if (this._autoStretchWidth) {
+                        this.width = Math.min(this._maxWidth.getValueInPixel(this._host, parentMeasure.width), textWidth + marginWidth) + "px";
+                    }
+                    var rootY = this._fontOffset.ascent + (this._currentMeasure.height - this._fontOffset.height) / 2;
+                    var availableWidth = this._width.getValueInPixel(this._host, parentMeasure.width) - marginWidth;
+                    context.save();
+                    context.beginPath();
+                    context.rect(clipTextLeft, this._currentMeasure.top + (this._currentMeasure.height - this._fontOffset.height) / 2, availableWidth + 2, this._currentMeasure.height);
+                    context.clip();
+                    if (this._isFocused && textWidth > availableWidth) {
+                        var textLeft = clipTextLeft - textWidth + availableWidth;
+                        if (!this._scrollLeft) {
+                            this._scrollLeft = textLeft;
                         }
-                        var rootY = this._fontOffset.ascent + (this._currentMeasure.height - this._fontOffset.height) / 2;
-                        context.fillText(this._text, this._currentMeasure.left + this._margin.getValueInPixel(this._host, parentMeasure.width), this._currentMeasure.top + rootY);
-                        if (this._autoStretchWidth) {
-                            this.width = Math.min(this._maxWidth.getValueInPixel(this._host, parentMeasure.width), context.measureText(this._text).width + this._margin.getValueInPixel(this._host, parentMeasure.width) * 2) + "px";
+                    }
+                    else {
+                        this._scrollLeft = clipTextLeft;
+                    }
+                    context.fillText(this._text, this._scrollLeft, this._currentMeasure.top + rootY);
+                    // Cursor
+                    if (this._isFocused) {
+                        if (!this._blinkIsEven) {
+                            var cursorOffsetText = this.text.substr(this._text.length - this._cursorOffset);
+                            var cursorOffsetWidth = context.measureText(cursorOffsetText).width;
+                            var cursorLeft = this._scrollLeft + textWidth - cursorOffsetWidth;
+                            if (cursorLeft < clipTextLeft) {
+                                this._scrollLeft += (clipTextLeft - cursorLeft);
+                                cursorLeft = clipTextLeft;
+                                this._markAsDirty();
+                            }
+                            else if (cursorLeft > clipTextLeft + availableWidth) {
+                                this._scrollLeft += (clipTextLeft + availableWidth - cursorLeft);
+                                cursorLeft = clipTextLeft + availableWidth;
+                                this._markAsDirty();
+                            }
+                            context.fillRect(cursorLeft, this._currentMeasure.top + (this._currentMeasure.height - this._fontOffset.height) / 2, 2, this._fontOffset.height);
                         }
+                        clearTimeout(this._blinkTimeout);
+                        this._blinkTimeout = setTimeout(function () {
+                            _this._blinkIsEven = !_this._blinkIsEven;
+                            _this._markAsDirty();
+                        }, 500);
                     }
+                    context.restore();
                     // Border
                     if (this._thickness) {
                         if (this.color) {
@@ -3846,6 +4032,16 @@ var BABYLON;
                 }
                 context.restore();
             };
+            InputText.prototype._onPointerDown = function (coordinates) {
+                if (!_super.prototype._onPointerDown.call(this, coordinates)) {
+                    return false;
+                }
+                this._host.focusedControl = this;
+                return true;
+            };
+            InputText.prototype._onPointerUp = function (coordinates) {
+                _super.prototype._onPointerUp.call(this, coordinates);
+            };
             return InputText;
         }(GUI.Control));
         GUI.InputText = InputText;

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


+ 24 - 3
dist/preview release/gui/babylon.gui.module.d.ts

@@ -1,14 +1,21 @@
 /// <reference path="../../dist/preview release/babylon.d.ts" />
 declare module BABYLON.GUI {
+    interface IFocusableControl {
+        onFocus(): void;
+        onBlur(): void;
+        processKeyboard(evt: KeyboardEvent): void;
+    }
     class AdvancedDynamicTexture extends DynamicTexture {
         private _isDirty;
         private _renderObserver;
         private _resizeObserver;
+        private _preKeyboardObserver;
         private _pointerMoveObserver;
         private _pointerObserver;
-        private _canvasBlurObserver;
+        private _canvasPointerOutObserver;
         private _background;
         _rootContainer: Container;
+        _lastPickedControl: Control;
         _lastControlOver: Control;
         _lastControlDown: Control;
         _capturingControl: Control;
@@ -20,12 +27,14 @@ declare module BABYLON.GUI {
         private _idealWidth;
         private _idealHeight;
         private _renderAtIdealSize;
+        private _focusedControl;
         background: string;
         idealWidth: number;
         idealHeight: number;
         renderAtIdealSize: boolean;
         readonly layer: Layer;
         readonly rootContainer: Container;
+        focusedControl: IFocusableControl;
         constructor(name: string, width: number, height: number, scene: Scene, generateMipMaps?: boolean, samplingMode?: number);
         executeOnAllControls(func: (control: Control) => void, container?: Container): void;
         markAsDirty(): void;
@@ -39,7 +48,7 @@ declare module BABYLON.GUI {
         private _doPicking(x, y, type);
         attach(): void;
         attachToMesh(mesh: AbstractMesh, supportPointerMove?: boolean): void;
-        private _attachToOnBlur(scene);
+        private _attachToOnPointerOut(scene);
         static CreateForMesh(mesh: AbstractMesh, width?: number, height?: number, supportPointerMove?: boolean): AdvancedDynamicTexture;
         static CreateFullscreenUI(name: string, foreground?: boolean, scene?: Scene): AdvancedDynamicTexture;
     }
@@ -584,23 +593,35 @@ declare module BABYLON.GUI {
 
 /// <reference path="../../../dist/preview release/babylon.d.ts" />
 declare module BABYLON.GUI {
-    class InputText extends Control {
+    class InputText extends Control implements IFocusableControl {
         name: string;
         private _text;
         private _background;
+        private _focusedBackground;
         private _thickness;
         private _margin;
         private _autoStretchWidth;
         private _maxWidth;
+        private _isFocused;
+        private _blinkTimeout;
+        private _blinkIsEven;
+        private _cursorOffset;
+        private _scrollLeft;
         maxWidth: string | number;
         margin: string;
         autoStretchWidth: boolean;
         thickness: number;
+        focusedBackground: string;
         background: string;
         text: string;
         constructor(name?: string, text?: string);
+        onBlur(): void;
+        onFocus(): void;
         protected _getTypeName(): string;
+        processKeyboard(evt: KeyboardEvent): void;
         _draw(parentMeasure: Measure, context: CanvasRenderingContext2D): void;
+        protected _onPointerDown(coordinates: Vector2): boolean;
+        protected _onPointerUp(coordinates: Vector2): void;
     }
 }
 

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


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

@@ -155,6 +155,7 @@ declare module BABYLON.GLTF1 {
         buffer: string;
         byteOffset: number;
         byteLength: number;
+        byteStride: number;
         target?: number;
     }
     interface IGLTFBuffer extends IGLTFChildRootProperty {

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


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

@@ -344,7 +344,14 @@ declare module BABYLON.GLTF2 {
         private _loadAnimations();
         private _loadAnimationChannel(animation, animationIndex, channelIndex);
         private _loadBufferAsync(index, onSuccess);
-        private _loadBufferViewAsync(bufferView, byteOffset, byteLength, componentType, onSuccess);
+        private _buildInt8ArrayBuffer(buffer, byteOffset, byteLength, byteStride, bytePerComponent);
+        private _buildUint8ArrayBuffer(buffer, byteOffset, byteLength, byteStride, bytePerComponent);
+        private _buildInt16ArrayBuffer(buffer, byteOffset, byteLength, byteStride, bytePerComponent);
+        private _buildUint16ArrayBuffer(buffer, byteOffset, byteLength, byteStride, bytePerComponent);
+        private _buildUint32ArrayBuffer(buffer, byteOffset, byteLength, byteStride, bytePerComponent);
+        private _buildFloat32ArrayBuffer(buffer, byteOffset, byteLength, byteStride, bytePerComponent);
+        private _extractInterleavedData(sourceBuffer, targetBuffer, bytePerComponent, stride, length);
+        private _loadBufferViewAsync(bufferView, byteOffset, byteLength, bytePerComponent, componentType, onSuccess);
         private _loadAccessorAsync(accessor, onSuccess);
         private _getByteStrideFromType(accessor);
         blockPendingTracking: boolean;

+ 77 - 10
dist/preview release/loaders/babylon.glTF2FileLoader.js

@@ -1012,7 +1012,73 @@ var BABYLON;
                     });
                 }
             };
-            GLTFLoader.prototype._loadBufferViewAsync = function (bufferView, byteOffset, byteLength, componentType, onSuccess) {
+            GLTFLoader.prototype._buildInt8ArrayBuffer = function (buffer, byteOffset, byteLength, byteStride, bytePerComponent) {
+                if (!byteStride) {
+                    return new Int8Array(buffer, byteOffset, byteLength);
+                }
+                var sourceBuffer = new Int8Array(buffer, byteOffset);
+                var targetBuffer = new Int8Array(byteLength);
+                this._extractInterleavedData(sourceBuffer, targetBuffer, bytePerComponent, byteStride, targetBuffer.length);
+                return targetBuffer;
+            };
+            GLTFLoader.prototype._buildUint8ArrayBuffer = function (buffer, byteOffset, byteLength, byteStride, bytePerComponent) {
+                if (!byteStride) {
+                    return new Uint8Array(buffer, byteOffset, byteLength);
+                }
+                var sourceBuffer = new Uint8Array(buffer, byteOffset);
+                var targetBuffer = new Uint8Array(byteLength);
+                this._extractInterleavedData(sourceBuffer, targetBuffer, bytePerComponent, byteStride, targetBuffer.length);
+                return targetBuffer;
+            };
+            GLTFLoader.prototype._buildInt16ArrayBuffer = function (buffer, byteOffset, byteLength, byteStride, bytePerComponent) {
+                if (!byteStride) {
+                    return new Int16Array(buffer, byteOffset, byteLength);
+                }
+                var sourceBuffer = new Int16Array(buffer, byteOffset);
+                var targetBuffer = new Int16Array(byteLength);
+                this._extractInterleavedData(sourceBuffer, targetBuffer, bytePerComponent, byteStride / 2, targetBuffer.length);
+                return targetBuffer;
+            };
+            GLTFLoader.prototype._buildUint16ArrayBuffer = function (buffer, byteOffset, byteLength, byteStride, bytePerComponent) {
+                if (!byteStride) {
+                    return new Uint16Array(buffer, byteOffset, byteLength);
+                }
+                var sourceBuffer = new Uint16Array(buffer, byteOffset);
+                var targetBuffer = new Uint16Array(byteLength);
+                this._extractInterleavedData(sourceBuffer, targetBuffer, bytePerComponent, byteStride / 2, targetBuffer.length);
+                return targetBuffer;
+            };
+            GLTFLoader.prototype._buildUint32ArrayBuffer = function (buffer, byteOffset, byteLength, byteStride, bytePerComponent) {
+                if (!byteStride) {
+                    return new Uint32Array(buffer, byteOffset, byteLength);
+                }
+                var sourceBuffer = new Uint32Array(buffer, byteOffset);
+                var targetBuffer = new Uint32Array(byteLength);
+                this._extractInterleavedData(sourceBuffer, targetBuffer, bytePerComponent, byteStride / 4, targetBuffer.length);
+                return targetBuffer;
+            };
+            GLTFLoader.prototype._buildFloat32ArrayBuffer = function (buffer, byteOffset, byteLength, byteStride, bytePerComponent) {
+                if (!byteStride) {
+                    return new Float32Array(buffer, byteOffset, byteLength);
+                }
+                var sourceBuffer = new Float32Array(buffer, byteOffset);
+                var targetBuffer = new Float32Array(byteLength);
+                this._extractInterleavedData(sourceBuffer, targetBuffer, bytePerComponent, byteStride / 4, targetBuffer.length);
+                return targetBuffer;
+            };
+            GLTFLoader.prototype._extractInterleavedData = function (sourceBuffer, targetBuffer, bytePerComponent, stride, length) {
+                var tempIndex = 0;
+                var sourceIndex = 0;
+                var storageSize = bytePerComponent;
+                while (tempIndex < length) {
+                    for (var cursor = 0; cursor < storageSize; cursor++) {
+                        targetBuffer[tempIndex] = sourceBuffer[sourceIndex + cursor];
+                        tempIndex++;
+                    }
+                    sourceIndex += stride;
+                }
+            };
+            GLTFLoader.prototype._loadBufferViewAsync = function (bufferView, byteOffset, byteLength, bytePerComponent, componentType, onSuccess) {
                 var _this = this;
                 byteOffset += (bufferView.byteOffset || 0);
                 this._loadBufferAsync(bufferView.buffer, function (bufferData) {
@@ -1025,22 +1091,22 @@ var BABYLON;
                     var bufferViewData;
                     switch (componentType) {
                         case GLTF2.EComponentType.BYTE:
-                            bufferViewData = new Int8Array(buffer, byteOffset, byteLength);
+                            bufferViewData = _this._buildInt8ArrayBuffer(buffer, byteOffset, byteLength, bufferView.byteStride, bytePerComponent);
                             break;
                         case GLTF2.EComponentType.UNSIGNED_BYTE:
-                            bufferViewData = new Uint8Array(buffer, byteOffset, byteLength);
+                            bufferViewData = _this._buildUint8ArrayBuffer(buffer, byteOffset, byteLength, bufferView.byteStride, bytePerComponent);
                             break;
                         case GLTF2.EComponentType.SHORT:
-                            bufferViewData = new Int16Array(buffer, byteOffset, byteLength);
+                            bufferViewData = _this._buildInt16ArrayBuffer(buffer, byteOffset, byteLength, bufferView.byteStride, bytePerComponent);
                             break;
                         case GLTF2.EComponentType.UNSIGNED_SHORT:
-                            bufferViewData = new Uint16Array(buffer, byteOffset, byteLength);
+                            bufferViewData = _this._buildUint16ArrayBuffer(buffer, byteOffset, byteLength, bufferView.byteStride, bytePerComponent);
                             break;
                         case GLTF2.EComponentType.UNSIGNED_INT:
-                            bufferViewData = new Uint32Array(buffer, byteOffset, byteLength);
+                            bufferViewData = _this._buildUint32ArrayBuffer(buffer, byteOffset, byteLength, bufferView.byteStride, bytePerComponent);
                             break;
                         case GLTF2.EComponentType.FLOAT:
-                            bufferViewData = new Float32Array(buffer, byteOffset, byteLength);
+                            bufferViewData = _this._buildFloat32ArrayBuffer(buffer, byteOffset, byteLength, bufferView.byteStride, bytePerComponent);
                             break;
                         default:
                             _this._onError("Invalid component type (" + componentType + ")");
@@ -1052,8 +1118,9 @@ var BABYLON;
             GLTFLoader.prototype._loadAccessorAsync = function (accessor, onSuccess) {
                 var bufferView = this._gltf.bufferViews[accessor.bufferView];
                 var byteOffset = accessor.byteOffset || 0;
-                var byteLength = accessor.count * this._getByteStrideFromType(accessor);
-                this._loadBufferViewAsync(bufferView, byteOffset, byteLength, accessor.componentType, onSuccess);
+                var bytePerComponent = this._getByteStrideFromType(accessor);
+                var byteLength = accessor.count * bytePerComponent;
+                this._loadBufferViewAsync(bufferView, byteOffset, byteLength, bytePerComponent, accessor.componentType, onSuccess);
             };
             GLTFLoader.prototype._getByteStrideFromType = function (accessor) {
                 switch (accessor.type) {
@@ -1265,7 +1332,7 @@ var BABYLON;
                 };
                 if (!source.uri) {
                     var bufferView = this._gltf.bufferViews[source.bufferView];
-                    this._loadBufferViewAsync(bufferView, 0, bufferView.byteLength, GLTF2.EComponentType.UNSIGNED_BYTE, setTextureData);
+                    this._loadBufferViewAsync(bufferView, 0, bufferView.byteLength, 1, GLTF2.EComponentType.UNSIGNED_BYTE, setTextureData);
                 }
                 else if (GLTF2.GLTFUtils.IsBase64(source.uri)) {
                     setTextureData(new Uint8Array(GLTF2.GLTFUtils.DecodeBase64(source.uri)));

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


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

@@ -155,6 +155,7 @@ declare module BABYLON.GLTF1 {
         buffer: string;
         byteOffset: number;
         byteLength: number;
+        byteStride: number;
         target?: number;
     }
     interface IGLTFBuffer extends IGLTFChildRootProperty {
@@ -839,7 +840,14 @@ declare module BABYLON.GLTF2 {
         private _loadAnimations();
         private _loadAnimationChannel(animation, animationIndex, channelIndex);
         private _loadBufferAsync(index, onSuccess);
-        private _loadBufferViewAsync(bufferView, byteOffset, byteLength, componentType, onSuccess);
+        private _buildInt8ArrayBuffer(buffer, byteOffset, byteLength, byteStride, bytePerComponent);
+        private _buildUint8ArrayBuffer(buffer, byteOffset, byteLength, byteStride, bytePerComponent);
+        private _buildInt16ArrayBuffer(buffer, byteOffset, byteLength, byteStride, bytePerComponent);
+        private _buildUint16ArrayBuffer(buffer, byteOffset, byteLength, byteStride, bytePerComponent);
+        private _buildUint32ArrayBuffer(buffer, byteOffset, byteLength, byteStride, bytePerComponent);
+        private _buildFloat32ArrayBuffer(buffer, byteOffset, byteLength, byteStride, bytePerComponent);
+        private _extractInterleavedData(sourceBuffer, targetBuffer, bytePerComponent, stride, length);
+        private _loadBufferViewAsync(bufferView, byteOffset, byteLength, bytePerComponent, componentType, onSuccess);
         private _loadAccessorAsync(accessor, onSuccess);
         private _getByteStrideFromType(accessor);
         blockPendingTracking: boolean;

+ 77 - 10
dist/preview release/loaders/babylon.glTFFileLoader.js

@@ -3170,7 +3170,73 @@ var BABYLON;
                     });
                 }
             };
-            GLTFLoader.prototype._loadBufferViewAsync = function (bufferView, byteOffset, byteLength, componentType, onSuccess) {
+            GLTFLoader.prototype._buildInt8ArrayBuffer = function (buffer, byteOffset, byteLength, byteStride, bytePerComponent) {
+                if (!byteStride) {
+                    return new Int8Array(buffer, byteOffset, byteLength);
+                }
+                var sourceBuffer = new Int8Array(buffer, byteOffset);
+                var targetBuffer = new Int8Array(byteLength);
+                this._extractInterleavedData(sourceBuffer, targetBuffer, bytePerComponent, byteStride, targetBuffer.length);
+                return targetBuffer;
+            };
+            GLTFLoader.prototype._buildUint8ArrayBuffer = function (buffer, byteOffset, byteLength, byteStride, bytePerComponent) {
+                if (!byteStride) {
+                    return new Uint8Array(buffer, byteOffset, byteLength);
+                }
+                var sourceBuffer = new Uint8Array(buffer, byteOffset);
+                var targetBuffer = new Uint8Array(byteLength);
+                this._extractInterleavedData(sourceBuffer, targetBuffer, bytePerComponent, byteStride, targetBuffer.length);
+                return targetBuffer;
+            };
+            GLTFLoader.prototype._buildInt16ArrayBuffer = function (buffer, byteOffset, byteLength, byteStride, bytePerComponent) {
+                if (!byteStride) {
+                    return new Int16Array(buffer, byteOffset, byteLength);
+                }
+                var sourceBuffer = new Int16Array(buffer, byteOffset);
+                var targetBuffer = new Int16Array(byteLength);
+                this._extractInterleavedData(sourceBuffer, targetBuffer, bytePerComponent, byteStride / 2, targetBuffer.length);
+                return targetBuffer;
+            };
+            GLTFLoader.prototype._buildUint16ArrayBuffer = function (buffer, byteOffset, byteLength, byteStride, bytePerComponent) {
+                if (!byteStride) {
+                    return new Uint16Array(buffer, byteOffset, byteLength);
+                }
+                var sourceBuffer = new Uint16Array(buffer, byteOffset);
+                var targetBuffer = new Uint16Array(byteLength);
+                this._extractInterleavedData(sourceBuffer, targetBuffer, bytePerComponent, byteStride / 2, targetBuffer.length);
+                return targetBuffer;
+            };
+            GLTFLoader.prototype._buildUint32ArrayBuffer = function (buffer, byteOffset, byteLength, byteStride, bytePerComponent) {
+                if (!byteStride) {
+                    return new Uint32Array(buffer, byteOffset, byteLength);
+                }
+                var sourceBuffer = new Uint32Array(buffer, byteOffset);
+                var targetBuffer = new Uint32Array(byteLength);
+                this._extractInterleavedData(sourceBuffer, targetBuffer, bytePerComponent, byteStride / 4, targetBuffer.length);
+                return targetBuffer;
+            };
+            GLTFLoader.prototype._buildFloat32ArrayBuffer = function (buffer, byteOffset, byteLength, byteStride, bytePerComponent) {
+                if (!byteStride) {
+                    return new Float32Array(buffer, byteOffset, byteLength);
+                }
+                var sourceBuffer = new Float32Array(buffer, byteOffset);
+                var targetBuffer = new Float32Array(byteLength);
+                this._extractInterleavedData(sourceBuffer, targetBuffer, bytePerComponent, byteStride / 4, targetBuffer.length);
+                return targetBuffer;
+            };
+            GLTFLoader.prototype._extractInterleavedData = function (sourceBuffer, targetBuffer, bytePerComponent, stride, length) {
+                var tempIndex = 0;
+                var sourceIndex = 0;
+                var storageSize = bytePerComponent;
+                while (tempIndex < length) {
+                    for (var cursor = 0; cursor < storageSize; cursor++) {
+                        targetBuffer[tempIndex] = sourceBuffer[sourceIndex + cursor];
+                        tempIndex++;
+                    }
+                    sourceIndex += stride;
+                }
+            };
+            GLTFLoader.prototype._loadBufferViewAsync = function (bufferView, byteOffset, byteLength, bytePerComponent, componentType, onSuccess) {
                 var _this = this;
                 byteOffset += (bufferView.byteOffset || 0);
                 this._loadBufferAsync(bufferView.buffer, function (bufferData) {
@@ -3183,22 +3249,22 @@ var BABYLON;
                     var bufferViewData;
                     switch (componentType) {
                         case GLTF2.EComponentType.BYTE:
-                            bufferViewData = new Int8Array(buffer, byteOffset, byteLength);
+                            bufferViewData = _this._buildInt8ArrayBuffer(buffer, byteOffset, byteLength, bufferView.byteStride, bytePerComponent);
                             break;
                         case GLTF2.EComponentType.UNSIGNED_BYTE:
-                            bufferViewData = new Uint8Array(buffer, byteOffset, byteLength);
+                            bufferViewData = _this._buildUint8ArrayBuffer(buffer, byteOffset, byteLength, bufferView.byteStride, bytePerComponent);
                             break;
                         case GLTF2.EComponentType.SHORT:
-                            bufferViewData = new Int16Array(buffer, byteOffset, byteLength);
+                            bufferViewData = _this._buildInt16ArrayBuffer(buffer, byteOffset, byteLength, bufferView.byteStride, bytePerComponent);
                             break;
                         case GLTF2.EComponentType.UNSIGNED_SHORT:
-                            bufferViewData = new Uint16Array(buffer, byteOffset, byteLength);
+                            bufferViewData = _this._buildUint16ArrayBuffer(buffer, byteOffset, byteLength, bufferView.byteStride, bytePerComponent);
                             break;
                         case GLTF2.EComponentType.UNSIGNED_INT:
-                            bufferViewData = new Uint32Array(buffer, byteOffset, byteLength);
+                            bufferViewData = _this._buildUint32ArrayBuffer(buffer, byteOffset, byteLength, bufferView.byteStride, bytePerComponent);
                             break;
                         case GLTF2.EComponentType.FLOAT:
-                            bufferViewData = new Float32Array(buffer, byteOffset, byteLength);
+                            bufferViewData = _this._buildFloat32ArrayBuffer(buffer, byteOffset, byteLength, bufferView.byteStride, bytePerComponent);
                             break;
                         default:
                             _this._onError("Invalid component type (" + componentType + ")");
@@ -3210,8 +3276,9 @@ var BABYLON;
             GLTFLoader.prototype._loadAccessorAsync = function (accessor, onSuccess) {
                 var bufferView = this._gltf.bufferViews[accessor.bufferView];
                 var byteOffset = accessor.byteOffset || 0;
-                var byteLength = accessor.count * this._getByteStrideFromType(accessor);
-                this._loadBufferViewAsync(bufferView, byteOffset, byteLength, accessor.componentType, onSuccess);
+                var bytePerComponent = this._getByteStrideFromType(accessor);
+                var byteLength = accessor.count * bytePerComponent;
+                this._loadBufferViewAsync(bufferView, byteOffset, byteLength, bytePerComponent, accessor.componentType, onSuccess);
             };
             GLTFLoader.prototype._getByteStrideFromType = function (accessor) {
                 switch (accessor.type) {
@@ -3423,7 +3490,7 @@ var BABYLON;
                 };
                 if (!source.uri) {
                     var bufferView = this._gltf.bufferViews[source.bufferView];
-                    this._loadBufferViewAsync(bufferView, 0, bufferView.byteLength, GLTF2.EComponentType.UNSIGNED_BYTE, setTextureData);
+                    this._loadBufferViewAsync(bufferView, 0, bufferView.byteLength, 1, GLTF2.EComponentType.UNSIGNED_BYTE, setTextureData);
                 }
                 else if (GLTF2.GLTFUtils.IsBase64(source.uri)) {
                     setTextureData(new Uint8Array(GLTF2.GLTFUtils.DecodeBase64(source.uri)));

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


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


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

@@ -1,14 +1,16 @@
 # 3.1.0:
 
 ## Major updates
+- Added support for webgl context lost and restored events. [Doc here](http://doc.babylonjs.com/tutorials/optimizing_your_scene#handling-webgl-context-lost) ([deltakosh](https://github.com/deltakosh))
 - Added support for non-pow2 textures when in WebGL2 mode ([deltakosh](https://github.com/deltakosh))
 - Engine can now be initialized with an existing webgl context ([deltakosh](https://github.com/deltakosh))
-- Introduced behaviors. (Doc here)[http://doc.babylonjs.com/overviews/behaviors] ([deltakosh](https://github.com/deltakosh))
-- Added support for WebGL Occlusion queries. (Doc here)[http://doc.babylonjs.com/overviews/occlusionquery] ([Ibraheem Osama](https://github.com/IbraheemOsama))
+- Introduced behaviors. [Doc here](http://doc.babylonjs.com/overviews/behaviors) ([deltakosh](https://github.com/deltakosh))
+- Added support for WebGL Occlusion queries. [Doc here](http://doc.babylonjs.com/overviews/occlusionquery) ([Ibraheem Osama](https://github.com/IbraheemOsama))
 - New behaviors for ArcRotateCamera:
  - AutoRotation ([deltakosh](https://github.com/deltakosh))
  - Framing ([deltakosh](https://github.com/deltakosh))
  - Bouncing ([deltakosh](https://github.com/deltakosh))
+- New InputText for Babylon.GUI. [Doc here](http://doc.babylonjs.com/overviews/gui#inputtext) ([deltakosh](https://github.com/deltakosh))
 
 ## 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))

+ 52 - 7
gui/src/advancedDynamicTexture.ts

@@ -1,15 +1,23 @@
 /// <reference path="../../dist/preview release/babylon.d.ts"/>
 
 module BABYLON.GUI {
+    export interface IFocusableControl {
+        onFocus(): void;
+        onBlur(): void;
+        processKeyboard(evt: KeyboardEvent): void;
+    }
+
     export class AdvancedDynamicTexture extends DynamicTexture {
         private _isDirty = false;
         private _renderObserver: Observer<Camera>;
         private _resizeObserver: Observer<Engine>;
+        private _preKeyboardObserver: Observer<KeyboardInfoPre>;
         private _pointerMoveObserver: Observer<PointerInfoPre>;
         private _pointerObserver: Observer<PointerInfo>;
-        private _canvasBlurObserver: Observer<Engine>;
+        private _canvasPointerOutObserver: Observer<Engine>;
         private _background: string;
         public _rootContainer = new Container("root");
+        public _lastPickedControl: Control;
         public _lastControlOver: Control;
         public _lastControlDown: Control;
         public _capturingControl: Control;
@@ -21,6 +29,7 @@ module BABYLON.GUI {
         private _idealWidth = 0;
         private _idealHeight = 0;
         private _renderAtIdealSize = false;
+        private _focusedControl: IFocusableControl;
 
         public get background(): string {
             return this._background;
@@ -83,11 +92,40 @@ module BABYLON.GUI {
         public get rootContainer(): Container {
             return this._rootContainer;
         }
+
+        public get focusedControl(): IFocusableControl {
+            return this._focusedControl;
+        }
+
+        public set focusedControl(control: IFocusableControl) {
+            if (this._focusedControl === control) {
+                return;
+            }
+
+            if (!this._focusedControl) {
+                control.onFocus();
+            } else {
+                this._focusedControl.onBlur();
+            }
+
+            this._focusedControl = control;
+        }
        
         constructor(name: string, width = 0, height = 0, scene: Scene, generateMipMaps = false, samplingMode = Texture.NEAREST_SAMPLINGMODE) {
             super(name, {width: width, height: height}, scene, generateMipMaps, samplingMode, Engine.TEXTUREFORMAT_RGBA);
 
             this._renderObserver = this.getScene().onBeforeCameraRenderObservable.add((camera: Camera) => this._checkUpdate(camera));
+            this._preKeyboardObserver = this.getScene().onPreKeyboardObservable.add(info => {
+                if (!this._focusedControl) {
+                    return;
+                }
+
+                if (info.type === KeyboardEventTypes.KEYDOWN) {
+                    this._focusedControl.processKeyboard(info.event);
+                }
+
+                info.skipOnPointerObservable = true;
+            });
 
             this._rootContainer._link(null, this);
 
@@ -145,8 +183,8 @@ module BABYLON.GUI {
                 this.getScene().onPointerObservable.remove(this._pointerObserver);
             }
 
-            if (this._canvasBlurObserver) {
-                this.getScene().getEngine().onCanvasBlurObservable.remove(this._canvasBlurObserver);
+            if (this._canvasPointerOutObserver) {
+                this.getScene().getEngine().onCanvasPointerOutObservable.remove(this._canvasPointerOutObserver);
             }
 
             if (this._layerToDispose) {
@@ -286,6 +324,13 @@ module BABYLON.GUI {
                     this._lastControlOver = null;
                 }
             }
+
+            // Focus management
+            if (this._focusedControl) {
+                if (this._focusedControl !== (<any>this._lastPickedControl)) {
+                    this.focusedControl = null;
+                }
+            }
         }
 
         public attach(): void {
@@ -309,7 +354,7 @@ module BABYLON.GUI {
                 pi.skipOnPointerObservable = this._shouldBlockPointer && pi.type !== BABYLON.PointerEventTypes.POINTERUP;
             });
 
-            this._attachToOnBlur(scene);
+            this._attachToOnPointerOut(scene);
         }
 
         public attachToMesh(mesh: AbstractMesh, supportPointerMove = true): void {
@@ -339,11 +384,11 @@ module BABYLON.GUI {
             });
 
             mesh.enablePointerMoveEvents = supportPointerMove;
-            this._attachToOnBlur(scene);
+            this._attachToOnPointerOut(scene);
         }
 
-        private _attachToOnBlur(scene: Scene): void {
-            this._canvasBlurObserver = scene.getEngine().onCanvasBlurObservable.add(() => {
+        private _attachToOnPointerOut(scene: Scene): void {
+            this._canvasPointerOutObserver = scene.getEngine().onCanvasPointerOutObservable.add(() => {
                 if (this._lastControlOver) {
                     this._lastControlOver._onPointerOut();
                 }            

+ 1 - 0
gui/src/controls/control.ts

@@ -834,6 +834,7 @@ module BABYLON.GUI {
             if (type === BABYLON.PointerEventTypes.POINTERDOWN) {
                 this._onPointerDown(this._dummyVector2);
                 this._host._lastControlDown = this;
+                this._host._lastPickedControl = this;
                 return true;
             }
 

+ 185 - 11
gui/src/controls/inputText.ts

@@ -1,13 +1,19 @@
 /// <reference path="../../../dist/preview release/babylon.d.ts"/>
 
 module BABYLON.GUI {
-    export class InputText extends Control {
+    export class InputText extends Control implements IFocusableControl {
         private _text = "";
         private _background = "black";   
+        private _focusedBackground = "black";   
         private _thickness = 1;
         private _margin = new ValueAndUnit(10, ValueAndUnit.UNITMODE_PIXEL);
         private _autoStretchWidth = true;        
         private _maxWidth = new ValueAndUnit(1, ValueAndUnit.UNITMODE_PERCENTAGE, false);
+        private _isFocused = false;
+        private _blinkTimeout: number;
+        private _blinkIsEven = false;
+        private _cursorOffset = 0;        
+        private _scrollLeft: number;
 
         public get maxWidth(): string | number {
             return this._maxWidth.toString(this._host);
@@ -63,6 +69,19 @@ module BABYLON.GUI {
             this._markAsDirty();
         }          
 
+        public get focusedBackground(): string {
+            return this._focusedBackground;
+        }
+
+        public set focusedBackground(value: string) {
+            if (this._focusedBackground === value) {
+                return;
+            }
+
+            this._focusedBackground = value;
+            this._markAsDirty();
+        }  
+
         public get background(): string {
             return this._background;
         }
@@ -74,7 +93,7 @@ module BABYLON.GUI {
 
             this._background = value;
             this._markAsDirty();
-        }  
+        }          
 
         public get text(): string {
             return this._text;
@@ -94,37 +113,178 @@ module BABYLON.GUI {
             this.text = text;
         }
 
+        public onBlur(): void {
+            this._isFocused = false;
+            this._scrollLeft = null;
+            this._cursorOffset = 0;
+            clearTimeout(this._blinkTimeout);
+            this._markAsDirty();
+        }
+
+        public onFocus(): void {
+            this._scrollLeft = null;
+            this._isFocused = true;
+            this._blinkIsEven = false;
+            this._cursorOffset = 0;
+            this._markAsDirty();
+        }
+
         protected _getTypeName(): string {
             return "InputText";
         }
 
-       public _draw(parentMeasure: Measure, context: CanvasRenderingContext2D): void {
+        public processKeyboard(evt: KeyboardEvent): void {
+            // Specific cases
+            switch (evt.keyCode) {
+                case 8: // BACKSPACE
+                    if (this._text && this._text.length > 0) {
+                        if (this._cursorOffset === 0) {
+                            this.text = this._text.substr(0, this._text.length - 1);
+                        } else {
+                            let deletePosition = this._text.length - this._cursorOffset;
+                            if (deletePosition > 0) {
+                                this.text = this._text.slice(0, deletePosition - 1) + this._text.slice(deletePosition);
+                            }
+                        }
+                    }
+                    return;
+                case 46: // DELETE
+                    if (this._text && this._text.length > 0) {
+                        let deletePosition = this._text.length - this._cursorOffset;
+                        this.text = this._text.slice(0, deletePosition) + this._text.slice(deletePosition + 1);
+                        this._cursorOffset--;
+                    }
+                    return;                    
+                case 13: // RETURN
+                    this._host.focusedControl = null;
+                    return;
+                case 35: // END
+                    this._cursorOffset = 0;
+                    this._blinkIsEven = false;
+                    this._markAsDirty();                
+                    return;
+                case 36: // HOME
+                    this._cursorOffset = this._text.length;
+                    this._blinkIsEven = false;
+                    this._markAsDirty();                
+                return;
+                case 37: // LEFT
+                    this._cursorOffset++;
+                    if (this._cursorOffset > this._text.length) {
+                        this._cursorOffset = this._text.length;
+                    }
+                    this._blinkIsEven = false;
+                    this._markAsDirty();
+                    return;
+                case 39: // RIGHT
+                    this._cursorOffset--;
+                    if (this._cursorOffset < 0) {
+                        this._cursorOffset = 0;
+                    }
+                    this._blinkIsEven = false;
+                    this._markAsDirty();
+                    return;
+            }
+
+            // Printable characters
+            if (
+                (evt.keyCode === 32) ||                         // Space
+                (evt.keyCode > 47 && evt.keyCode < 58) ||       // Numbers
+                (evt.keyCode > 64 && evt.keyCode < 91) ||       // Letters
+                (evt.keyCode > 185 && evt.keyCode < 193) ||     // Special characters
+                (evt.keyCode > 218  && evt.keyCode < 223) ||    // Special characters
+                (evt.keyCode > 95 && evt.keyCode < 112)) {      // Numpad
+                    if (this._cursorOffset === 0) {
+                        this.text += evt.key;
+                    } else {
+                        let insertPosition = this._text.length - this._cursorOffset;
+
+                        this.text = this._text.slice(0, insertPosition) + evt.key + this._text.slice(insertPosition);
+                    }
+                }
+        }
+
+        public _draw(parentMeasure: Measure, context: CanvasRenderingContext2D): void {
             context.save();
 
             this._applyStates(context);
             if (this._processMeasures(parentMeasure, context)) {
                 
                 // Background
-                if (this._background) {
+                if (this._isFocused) {
+                    if (this._focusedBackground) {
+                        context.fillStyle = this._focusedBackground;
+    
+                        context.fillRect(this._currentMeasure.left, this._currentMeasure.top, this._currentMeasure.width, this._currentMeasure.height);
+                    }                        
+                } else if (this._background) {
                     context.fillStyle = this._background;
 
                     context.fillRect(this._currentMeasure.left, this._currentMeasure.top, this._currentMeasure.width, this._currentMeasure.height);
                 }
 
+                if (!this._fontOffset) {
+                    this._fontOffset = Control._GetFontOffset(context.font);
+                }
+
                 // Text
-                if (this._text) {
-                    if (this.color) {
-                        context.fillStyle = this.color;
+                let clipTextLeft = this._currentMeasure.left + this._margin.getValueInPixel(this._host, parentMeasure.width);
+                if (this.color) {
+                    context.fillStyle = this.color;
+                }
+
+                let textWidth = context.measureText(this._text).width;   
+                let marginWidth = this._margin.getValueInPixel(this._host, parentMeasure.width) * 2;
+                if (this._autoStretchWidth) {
+                    this.width = Math.min(this._maxWidth.getValueInPixel(this._host, parentMeasure.width), textWidth + marginWidth) + "px";
+                }
+
+                let rootY = this._fontOffset.ascent + (this._currentMeasure.height - this._fontOffset.height) / 2;
+                let availableWidth = this._width.getValueInPixel(this._host, parentMeasure.width) - marginWidth;
+                context.save();
+                context.beginPath();
+                context.rect(clipTextLeft, this._currentMeasure.top + (this._currentMeasure.height - this._fontOffset.height) / 2, availableWidth + 2, this._currentMeasure.height);
+                context.clip();
+
+                if (this._isFocused && textWidth > availableWidth) {      
+                    let textLeft = clipTextLeft - textWidth + availableWidth;
+                    if (!this._scrollLeft) {
+                        this._scrollLeft = textLeft;
                     }
+                } else {
+                    this._scrollLeft = clipTextLeft;
+                }
 
-                    let rootY = this._fontOffset.ascent + (this._currentMeasure.height - this._fontOffset.height) / 2;
-                    context.fillText(this._text, this._currentMeasure.left + this._margin.getValueInPixel(this._host, parentMeasure.width), this._currentMeasure.top + rootY);
+                context.fillText(this._text, this._scrollLeft, this._currentMeasure.top + rootY);
 
-                    if (this._autoStretchWidth) {
-                        this.width = Math.min(this._maxWidth.getValueInPixel(this._host, parentMeasure.width), context.measureText(this._text).width + this._margin.getValueInPixel(this._host, parentMeasure.width) * 2) + "px";
+                // Cursor
+                if (this._isFocused) {         
+                    if (!this._blinkIsEven) {
+                        let cursorOffsetText = this.text.substr(this._text.length - this._cursorOffset);
+                        let cursorOffsetWidth = context.measureText(cursorOffsetText).width;   
+                        let cursorLeft = this._scrollLeft  + textWidth - cursorOffsetWidth;
+    
+                        if (cursorLeft < clipTextLeft) {
+                            this._scrollLeft += (clipTextLeft - cursorLeft);
+                            cursorLeft = clipTextLeft;
+                            this._markAsDirty();
+                        } else if (cursorLeft > clipTextLeft + availableWidth) {
+                            this._scrollLeft += (clipTextLeft  + availableWidth - cursorLeft);
+                            cursorLeft = clipTextLeft + availableWidth;
+                            this._markAsDirty();
+                        }                   
+                        context.fillRect(cursorLeft, this._currentMeasure.top + (this._currentMeasure.height - this._fontOffset.height) / 2, 2, this._fontOffset.height);
                     }
+
+                    clearTimeout(this._blinkTimeout);
+                    this._blinkTimeout = setTimeout(() => {
+                        this._blinkIsEven = !this._blinkIsEven;
+                        this._markAsDirty();
+                    }, 500);
                 }
 
+                context.restore();
+
                 // Border
                 if (this._thickness) {
                     if (this.color) {
@@ -138,5 +298,19 @@ module BABYLON.GUI {
             }
             context.restore();
         }
+
+        protected _onPointerDown(coordinates: Vector2): boolean {
+            if (!super._onPointerDown(coordinates)) {
+                return false;
+            }
+
+            this._host.focusedControl = this;
+
+            return true;
+        }
+
+        protected _onPointerUp(coordinates: Vector2): void {
+            super._onPointerUp(coordinates);
+        }  
     }
 }

+ 1 - 0
loaders/src/glTF/1.0/babylon.glTFLoaderInterfaces.ts

@@ -117,6 +117,7 @@ module BABYLON.GLTF1 {
         buffer: string;
         byteOffset: number;
         byteLength: number;
+        byteStride: number;
 
         target?: number;
     }

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

@@ -800,7 +800,99 @@ module BABYLON.GLTF2 {
             }
         }
 
-        private _loadBufferViewAsync(bufferView: IGLTFBufferView, byteOffset: number, byteLength: number, componentType: EComponentType, onSuccess: (data: ArrayBufferView) => void): void {
+        private _buildInt8ArrayBuffer(buffer: ArrayBuffer, byteOffset: number, byteLength: number, byteStride: number, bytePerComponent: number): Int8Array {
+            if (!byteStride) {
+                return new Int8Array(buffer, byteOffset, byteLength);
+            }
+
+            let sourceBuffer = new Int8Array(buffer, byteOffset);
+            let targetBuffer = new Int8Array(byteLength);
+
+            this._extractInterleavedData(sourceBuffer, targetBuffer, bytePerComponent, byteStride, targetBuffer.length);
+
+            return targetBuffer;              
+        }
+
+        private _buildUint8ArrayBuffer(buffer: ArrayBuffer, byteOffset: number, byteLength: number, byteStride: number, bytePerComponent: number): Uint8Array {
+            if (!byteStride) {
+                return new Uint8Array(buffer, byteOffset, byteLength);
+            }
+
+            let sourceBuffer = new Uint8Array(buffer, byteOffset);
+            let targetBuffer = new Uint8Array(byteLength);
+
+            this._extractInterleavedData(sourceBuffer, targetBuffer, bytePerComponent, byteStride, targetBuffer.length);
+
+            return targetBuffer;              
+        }        
+
+        private _buildInt16ArrayBuffer(buffer: ArrayBuffer, byteOffset: number, byteLength: number, byteStride: number, bytePerComponent: number): Int16Array {
+            if (!byteStride) {
+                return new Int16Array(buffer, byteOffset, byteLength);
+            }
+
+            let sourceBuffer = new Int16Array(buffer, byteOffset);
+            let targetBuffer = new Int16Array(byteLength);
+
+            this._extractInterleavedData(sourceBuffer, targetBuffer, bytePerComponent, byteStride / 2, targetBuffer.length);
+
+            return targetBuffer;             
+        }   
+
+        private _buildUint16ArrayBuffer(buffer: ArrayBuffer, byteOffset: number, byteLength: number, byteStride: number, bytePerComponent: number): Uint16Array {
+            if (!byteStride) {
+                return new Uint16Array(buffer, byteOffset, byteLength);
+            }
+
+            let sourceBuffer = new Uint16Array(buffer, byteOffset);
+            let targetBuffer = new Uint16Array(byteLength);
+
+            this._extractInterleavedData(sourceBuffer, targetBuffer, bytePerComponent, byteStride / 2, targetBuffer.length);
+
+            return targetBuffer;             
+        }          
+        
+        private _buildUint32ArrayBuffer(buffer: ArrayBuffer, byteOffset: number, byteLength: number, byteStride: number, bytePerComponent: number): Uint32Array {
+            if (!byteStride) {
+                return new Uint32Array(buffer, byteOffset, byteLength);
+            }
+
+            let sourceBuffer = new Uint32Array(buffer, byteOffset);
+            let targetBuffer = new Uint32Array(byteLength);
+
+            this._extractInterleavedData(sourceBuffer, targetBuffer, bytePerComponent, byteStride / 4, targetBuffer.length);
+
+            return targetBuffer;            
+        }     
+        
+        private _buildFloat32ArrayBuffer(buffer: ArrayBuffer, byteOffset: number, byteLength: number, byteStride: number, bytePerComponent: number): Float32Array {
+            if (!byteStride) {
+                return new Float32Array(buffer, byteOffset, byteLength);
+            }
+
+            let sourceBuffer = new Float32Array(buffer, byteOffset);
+            let targetBuffer = new Float32Array(byteLength);
+
+            this._extractInterleavedData(sourceBuffer, targetBuffer, bytePerComponent, byteStride / 4, targetBuffer.length);
+
+            return targetBuffer;
+        }    
+        
+        private _extractInterleavedData(sourceBuffer: ArrayBufferView, targetBuffer: ArrayBufferView, bytePerComponent: number, stride: number, length: number): void {
+            let tempIndex = 0;
+            let sourceIndex = 0;            
+            let storageSize = bytePerComponent;
+
+            while (tempIndex < length) {
+                for (var cursor = 0; cursor < storageSize; cursor++) {
+                    targetBuffer[tempIndex] = sourceBuffer[sourceIndex + cursor]
+                    tempIndex++;
+                }
+                sourceIndex += stride;
+            }
+        }
+
+        private _loadBufferViewAsync(bufferView: IGLTFBufferView, byteOffset: number, byteLength: number, bytePerComponent: number, componentType: EComponentType, onSuccess: (data: ArrayBufferView) => void): void {
             byteOffset += (bufferView.byteOffset || 0);
 
             this._loadBufferAsync(bufferView.buffer, bufferData => {
@@ -815,22 +907,22 @@ module BABYLON.GLTF2 {
                 var bufferViewData;
                 switch (componentType) {
                     case EComponentType.BYTE:
-                        bufferViewData = new Int8Array(buffer, byteOffset, byteLength);
+                        bufferViewData = this._buildInt8ArrayBuffer(buffer, byteOffset, byteLength, bufferView.byteStride, bytePerComponent);
                         break;
                     case EComponentType.UNSIGNED_BYTE:
-                        bufferViewData = new Uint8Array(buffer, byteOffset, byteLength);
+                        bufferViewData = this._buildUint8ArrayBuffer(buffer, byteOffset, byteLength, bufferView.byteStride, bytePerComponent);
                         break;
                     case EComponentType.SHORT:
-                        bufferViewData = new Int16Array(buffer, byteOffset, byteLength);
+                        bufferViewData = this._buildInt16ArrayBuffer(buffer, byteOffset, byteLength, bufferView.byteStride, bytePerComponent);
                         break;
                     case EComponentType.UNSIGNED_SHORT:
-                        bufferViewData = new Uint16Array(buffer, byteOffset, byteLength);
+                        bufferViewData = this._buildUint16ArrayBuffer(buffer, byteOffset, byteLength, bufferView.byteStride, bytePerComponent);
                         break;
                     case EComponentType.UNSIGNED_INT:
-                        bufferViewData = new Uint32Array(buffer, byteOffset, byteLength);
+                        bufferViewData = this._buildUint32ArrayBuffer(buffer, byteOffset, byteLength, bufferView.byteStride, bytePerComponent);
                         break;
                     case EComponentType.FLOAT:
-                        bufferViewData = new Float32Array(buffer, byteOffset, byteLength);
+                        bufferViewData = this._buildFloat32ArrayBuffer(buffer, byteOffset, byteLength, bufferView.byteStride, bytePerComponent);
                         break;
                     default:
                         this._onError("Invalid component type (" + componentType + ")");
@@ -844,8 +936,9 @@ module BABYLON.GLTF2 {
         private _loadAccessorAsync(accessor: IGLTFAccessor, onSuccess: (data: ArrayBufferView) => void): void {
             var bufferView = this._gltf.bufferViews[accessor.bufferView];
             var byteOffset = accessor.byteOffset || 0;
-            var byteLength = accessor.count * this._getByteStrideFromType(accessor);
-            this._loadBufferViewAsync(bufferView, byteOffset, byteLength, accessor.componentType, onSuccess);
+            let bytePerComponent = this._getByteStrideFromType(accessor);
+            var byteLength = accessor.count * bytePerComponent;
+            this._loadBufferViewAsync(bufferView, byteOffset, byteLength, bytePerComponent, accessor.componentType, onSuccess);
         }
 
         private _getByteStrideFromType(accessor: IGLTFAccessor): number {
@@ -1091,7 +1184,7 @@ module BABYLON.GLTF2 {
 
             if (!source.uri) {
                 var bufferView = this._gltf.bufferViews[source.bufferView];
-                this._loadBufferViewAsync(bufferView, 0, bufferView.byteLength, EComponentType.UNSIGNED_BYTE, setTextureData);
+                this._loadBufferViewAsync(bufferView, 0, bufferView.byteLength, 1, EComponentType.UNSIGNED_BYTE, setTextureData);
             }
             else if (GLTFUtils.IsBase64(source.uri)) {
                 setTextureData(new Uint8Array(GLTFUtils.DecodeBase64(source.uri)));

+ 1 - 1
localDev/index.html

@@ -43,7 +43,7 @@
 	
 	<script>
 		var canvas = document.getElementById("renderCanvas");
-		canvas = WebGLDebugUtils.makeLostContextSimulatingCanvas(canvas);
+	//	canvas = WebGLDebugUtils.makeLostContextSimulatingCanvas(canvas);
 		var divFps = document.getElementById("fps");
 
 		// Global to simulate PG.

+ 3 - 2
src/Animations/babylon.animation.ts

@@ -89,6 +89,8 @@
     }
 
     export class Animation {
+        public static AllowMatricesInterpolation = false;
+
         private _keys: Array<{frame:number, value: any, inTangent?: any, outTangent?: any}>;
         private _offsetsCache = {};
         private _highLimitsCache = {};
@@ -103,7 +105,6 @@
         public targetPropertyPath: string[];
         public currentFrame: number;
 
-        public allowMatricesInterpolation = false;
 
         public blendingSpeed = 0.01;
         private _originalBlendValue: any;
@@ -520,7 +521,7 @@
                             switch (loopMode) {
                                 case Animation.ANIMATIONLOOPMODE_CYCLE:
                                 case Animation.ANIMATIONLOOPMODE_CONSTANT:
-                                    if (this.allowMatricesInterpolation) {
+                                    if (Animation.AllowMatricesInterpolation) {
                                         return this.matrixInterpolateFunction(startValue, endValue, gradient);
                                     }
                                 case Animation.ANIMATIONLOOPMODE_RELATIVE:

+ 57 - 78
src/Cameras/Inputs/babylon.arcRotateCameraKeyboardMoveInput.ts

@@ -2,11 +2,6 @@ module BABYLON {
     export class ArcRotateCameraKeyboardMoveInput implements ICameraInput<ArcRotateCamera> {
         camera: ArcRotateCamera;
         private _keys = [];
-        private _onKeyDown: (e: KeyboardEvent) => any;
-        private _onKeyUp: (e: KeyboardEvent) => any;
-        private _onLostFocus: (e: FocusEvent) => any;
-        private _onFocus: () => void;
-        private _onBlur: () => void;
         
         @serialize()
         public keysUp = [38];
@@ -27,97 +22,81 @@ module BABYLON {
         public panningSensibility: number = 50.0;        
 
         private _ctrlPressed: boolean;
+        private _onCanvasBlurObserver: Observer<Engine>;
+        private _onKeyboardObserver: Observer<KeyboardInfo>;
+        private _engine: Engine;
+        private _scene: Scene;
 
         public attachControl(element: HTMLElement, noPreventDefault?: boolean) {
-            element.tabIndex = 1;
-
-            this._onKeyDown = evt => {
-
-                this._ctrlPressed = evt.ctrlKey;
-
-                if (this.keysUp.indexOf(evt.keyCode) !== -1 ||
-                    this.keysDown.indexOf(evt.keyCode) !== -1 ||
-                    this.keysLeft.indexOf(evt.keyCode) !== -1 ||
-                    this.keysRight.indexOf(evt.keyCode) !== -1 ||
-                    this.keysReset.indexOf(evt.keyCode) !== -1) {
-                    var index = this._keys.indexOf(evt.keyCode);
+            if (this._onCanvasBlurObserver) {
+                return;
+            }
+            
+            this._scene = this.camera.getScene();
+            this._engine = this._scene.getEngine();
 
-                    if (index === -1) {
-                        this._keys.push(evt.keyCode);
-                    }
+            this._onCanvasBlurObserver = this._engine.onCanvasBlurObservable.add(()=>{
+                this._keys = [];
+            });
+
+            this._onKeyboardObserver = this._scene.onKeyboardObservable.add(info => {
+                let evt = info.event;
+
+                if (info.type === KeyboardEventTypes.KEYDOWN) {
+                    this._ctrlPressed = evt.ctrlKey;
+                    
+                    if (this.keysUp.indexOf(evt.keyCode) !== -1 ||
+                        this.keysDown.indexOf(evt.keyCode) !== -1 ||
+                        this.keysLeft.indexOf(evt.keyCode) !== -1 ||
+                        this.keysRight.indexOf(evt.keyCode) !== -1 ||
+                        this.keysReset.indexOf(evt.keyCode) !== -1) {
+                        var index = this._keys.indexOf(evt.keyCode);
+
+                        if (index === -1) {
+                            this._keys.push(evt.keyCode);
+                        }
 
-                    if (evt.preventDefault) {
-                        if (!noPreventDefault) {
-                            evt.preventDefault();
+                        if (evt.preventDefault) {
+                            if (!noPreventDefault) {
+                                evt.preventDefault();
+                            }
                         }
                     }
-                }
-            };
-
-            this._onKeyUp = evt => {
-                
-                if (this.keysUp.indexOf(evt.keyCode) !== -1 ||
-                    this.keysDown.indexOf(evt.keyCode) !== -1 ||
-                    this.keysLeft.indexOf(evt.keyCode) !== -1 ||
-                    this.keysRight.indexOf(evt.keyCode) !== -1 ||
-                    this.keysReset.indexOf(evt.keyCode) !== -1) {
-                    var index = this._keys.indexOf(evt.keyCode);
-
-                    if (index >= 0) {
-                        this._keys.splice(index, 1);
-                    }
-
-                    if (evt.preventDefault) {
-                        if (!noPreventDefault) {
-                            evt.preventDefault();
+                } else {
+                    if (this.keysUp.indexOf(evt.keyCode) !== -1 ||
+                        this.keysDown.indexOf(evt.keyCode) !== -1 ||
+                        this.keysLeft.indexOf(evt.keyCode) !== -1 ||
+                        this.keysRight.indexOf(evt.keyCode) !== -1 ||
+                        this.keysReset.indexOf(evt.keyCode) !== -1) {
+                        var index = this._keys.indexOf(evt.keyCode);
+    
+                        if (index >= 0) {
+                            this._keys.splice(index, 1);
+                        }
+    
+                        if (evt.preventDefault) {
+                            if (!noPreventDefault) {
+                                evt.preventDefault();
+                            }
                         }
                     }
                 }
-            };
-
-            this._onLostFocus = () => {
-                this._keys = [];
-            };
-
-            this._onFocus = () => {
-                element.addEventListener("keydown", this._onKeyDown, false);
-                element.addEventListener("keyup", this._onKeyUp, false);   
-            }
-
-            this._onBlur = () => {
-                element.removeEventListener("keydown", this._onKeyDown);
-                element.removeEventListener("keyup", this._onKeyUp);
-            }
-
-            element.addEventListener("focus", this._onFocus);
-            element.addEventListener("blur", this._onBlur);
-
-            Tools.RegisterTopRootEvents([
-                { name: "blur", handler: this._onLostFocus }
-            ]);
+            });    
         }
 
         public detachControl(element: HTMLElement) {
-            if (element && this._onBlur) {
-                this._onBlur();
-                element.removeEventListener("focus", this._onFocus);
-                element.removeEventListener("blur", this._onBlur);
+            if (this._scene) {
+                this._scene.onKeyboardObservable.remove(this._onKeyboardObserver);
+                this._engine.onCanvasBlurObservable.remove(this._onCanvasBlurObserver);
+                this._onKeyboardObserver = null;
+                this._onCanvasBlurObserver = null;
             }
 
-            Tools.UnregisterTopRootEvents([
-                { name: "blur", handler: this._onLostFocus }
-            ]);
-            
             this._keys = [];
-            this._onKeyDown = null;
-            this._onKeyUp = null;
-            this._onLostFocus = null;
-            this._onBlur = null;
-            this._onFocus = null;
         }
 
         public checkInputs() {
-            if (this._onKeyDown){
+            if (this._onKeyboardObserver){
                 var camera = this.camera;
 
                 for (var index = 0; index < this._keys.length; index++) {

+ 28 - 28
src/Cameras/Inputs/babylon.freeCameraKeyboardMoveInput.ts

@@ -2,8 +2,10 @@ module BABYLON {
     export class FreeCameraKeyboardMoveInput implements ICameraInput<FreeCamera> {
         camera: FreeCamera;
         private _keys = [];
-        private _onKeyDown: (e: KeyboardEvent) => any;
-        private _onKeyUp: (e: KeyboardEvent) => any;
+        private _onCanvasBlurObserver: Observer<Engine>;
+        private _onKeyboardObserver: Observer<KeyboardInfo>;
+        private _engine: Engine;
+        private _scene: Scene;        
 
         @serialize()
         public keysUp = [38];
@@ -18,10 +20,21 @@ module BABYLON {
         public keysRight = [39];
 
         attachControl(element : HTMLElement, noPreventDefault?: boolean) {
-            if (!this._onKeyDown) {
-                element.tabIndex = 1;
+            if (this._onCanvasBlurObserver) {
+                return;
+            }
+
+            this._scene = this.camera.getScene();
+            this._engine = this._scene.getEngine();
+
+            this._onCanvasBlurObserver = this._engine.onCanvasBlurObservable.add(()=>{
+                this._keys = [];
+            });
 
-                this._onKeyDown = evt => {
+            this._onKeyboardObserver = this._scene.onKeyboardObservable.add(info => {
+                let evt = info.event;
+
+                if (info.type === KeyboardEventTypes.KEYDOWN) {
                     if (this.keysUp.indexOf(evt.keyCode) !== -1 ||
                         this.keysDown.indexOf(evt.keyCode) !== -1 ||
                         this.keysLeft.indexOf(evt.keyCode) !== -1 ||
@@ -35,9 +48,7 @@ module BABYLON {
                             evt.preventDefault();
                         }
                     }
-                };
-
-                this._onKeyUp = evt => {
+                } else {
                     if (this.keysUp.indexOf(evt.keyCode) !== -1 ||
                         this.keysDown.indexOf(evt.keyCode) !== -1 ||
                         this.keysLeft.indexOf(evt.keyCode) !== -1 ||
@@ -51,33 +62,22 @@ module BABYLON {
                             evt.preventDefault();
                         }
                     }
-                };
-
-                element.addEventListener("keydown", this._onKeyDown, false);
-                element.addEventListener("keyup", this._onKeyUp, false);
-
-                Tools.RegisterTopRootEvents([
-                    { name: "blur", handler: this._onLostFocus }
-                ]);
-            }
+                }
+            });     
         }
 
         detachControl(element : HTMLElement) {
-            if (this._onKeyDown) {
-                element.removeEventListener("keydown", this._onKeyDown);
-                element.removeEventListener("keyup", this._onKeyUp);
-
-                Tools.UnregisterTopRootEvents([
-                    { name: "blur", handler: this._onLostFocus }
-                ]);
-                this._keys = [];
-                this._onKeyDown = null;
-                this._onKeyUp = null;
+            if (this._scene) {
+                this._scene.onKeyboardObservable.remove(this._onKeyboardObserver);
+                this._engine.onCanvasBlurObservable.remove(this._onCanvasBlurObserver);
+                this._onKeyboardObserver = null;
+                this._onCanvasBlurObserver = null;
             }
+            this._keys = [];
         }
         
         public checkInputs() {
-            if (this._onKeyDown){
+            if (this._onKeyboardObserver){
                 var camera = this.camera;
                 // Keyboard
                 for (var index = 0; index < this._keys.length; index++) {

+ 11 - 3
src/Cameras/VR/babylon.webVRCamera.ts

@@ -406,9 +406,17 @@ module BABYLON {
                                 if (!this._lightOnControllers) {
                                     this._lightOnControllers = new BABYLON.HemisphericLight("vrControllersLight", new BABYLON.Vector3(0, 1, 0), this.getScene());
                                 }
-                                loadedMesh.getChildren().forEach((mesh) => {
-                                    this._lightOnControllers.includedOnlyMeshes.push(<AbstractMesh>mesh);
-                                });
+                                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);
                             }
                         });
                     }

+ 4 - 4
src/Cameras/babylon.freeCamera.ts

@@ -114,7 +114,7 @@
             this._collisionMask = !isNaN(mask) ? mask : -1;
         }
 	 
-        public _collideWithWorld(direction: Vector3): void {
+        public _collideWithWorld(displacement: Vector3): void {
             var globalPosition: Vector3;
 
             if (this.parent) {
@@ -133,15 +133,15 @@
             this._collider.collisionMask = this._collisionMask;
 		
             //no need for clone, as long as gravity is not on.
-            var actualDirection = direction;
+            var actualDisplacement = displacement;
 			
             //add gravity to the direction to prevent the dual-collision checking
             if (this.applyGravity) {
                 //this prevents mending with cameraDirection, a global variable of the free camera class.
-                actualDirection = direction.add(this.getScene().gravity);
+                actualDisplacement = displacement.add(this.getScene().gravity);
             }
 
-            this.getScene().collisionCoordinator.getNewPosition(this._oldPosition, actualDirection, this._collider, 3, null, this._onCollisionPositionChange, this.uniqueId);
+            this.getScene().collisionCoordinator.getNewPosition(this._oldPosition, actualDisplacement, this._collider, 3, null, this._onCollisionPositionChange, this.uniqueId);
 
         }
 

+ 5 - 5
src/Collisions/babylon.collisionCoordinator.ts

@@ -4,7 +4,7 @@ module BABYLON {
     export var CollisionWorker = "";
 
     export interface ICollisionCoordinator {
-        getNewPosition(position: Vector3, velocity: Vector3, collider: Collider, maximumRetry: number, excludedMesh: AbstractMesh, onNewPosition: (collisionIndex: number, newPosition: Vector3, collidedMesh?: AbstractMesh) => void, collisionIndex: number): void;
+        getNewPosition(position: Vector3, displacement: Vector3, collider: Collider, maximumRetry: number, excludedMesh: AbstractMesh, onNewPosition: (collisionIndex: number, newPosition: Vector3, collidedMesh?: AbstractMesh) => void, collisionIndex: number): void;
         init(scene: Scene): void;
         destroy(): void;
 
@@ -186,12 +186,12 @@ module BABYLON {
             }
         }
 
-        public getNewPosition(position: Vector3, velocity: Vector3, collider: Collider, maximumRetry: number, excludedMesh: AbstractMesh, onNewPosition: (collisionIndex: number, newPosition: Vector3, collidedMesh?: AbstractMesh) => void, collisionIndex: number): void {
+        public getNewPosition(position: Vector3, displacement: Vector3, collider: Collider, maximumRetry: number, excludedMesh: AbstractMesh, onNewPosition: (collisionIndex: number, newPosition: Vector3, collidedMesh?: AbstractMesh) => void, collisionIndex: number): void {
             if (!this._init) return;
             if (this._collisionsCallbackArray[collisionIndex] || this._collisionsCallbackArray[collisionIndex + 100000]) return;
 
             position.divideToRef(collider.radius, this._scaledPosition);
-            velocity.divideToRef(collider.radius, this._scaledVelocity);
+            displacement.divideToRef(collider.radius, this._scaledVelocity);
 
             this._collisionsCallbackArray[collisionIndex] = onNewPosition;
 
@@ -347,9 +347,9 @@ module BABYLON {
 
         private _finalPosition = Vector3.Zero();
 
-        public getNewPosition(position: Vector3, velocity: Vector3, collider: Collider, maximumRetry: number, excludedMesh: AbstractMesh, onNewPosition: (collisionIndex: number, newPosition: Vector3, collidedMesh?: AbstractMesh) => void, collisionIndex: number): void {
+        public getNewPosition(position: Vector3, displacement: Vector3, collider: Collider, maximumRetry: number, excludedMesh: AbstractMesh, onNewPosition: (collisionIndex: number, newPosition: Vector3, collidedMesh?: AbstractMesh) => void, collisionIndex: number): void {
             position.divideToRef(collider.radius, this._scaledPosition);
-            velocity.divideToRef(collider.radius, this._scaledVelocity);
+            displacement.divideToRef(collider.radius, this._scaledVelocity);
             collider.collidedMesh = null;
             collider.retry = 0;
             collider.initialVelocity = this._scaledVelocity;

+ 33 - 0
src/Events/babylon.keyboardEvents.ts

@@ -0,0 +1,33 @@
+module BABYLON {
+    
+    export class KeyboardEventTypes {
+        static _KEYDOWN = 0x01;
+        static _KEYUP = 0x02;
+
+        public static get KEYDOWN(): number {
+            return KeyboardEventTypes._KEYDOWN;
+        }
+
+        public static get KEYUP(): number {
+            return KeyboardEventTypes._KEYUP;
+        }
+    }
+
+    export class KeyboardInfo {
+        constructor(public type: number, public event: KeyboardEvent) {
+        }
+    }
+
+    /**
+     * This class is used to store keyboard related info for the onPreKeyboardObservable event.
+     * Set the skipOnKeyboardObservable property to true if you want the engine to stop any process after this event is triggered, even not calling onKeyboardObservable
+     */
+    export class KeyboardInfoPre extends KeyboardInfo {
+        constructor(type: number, event: KeyboardEvent) {
+            super(type, event);
+            this.skipOnPointerObservable = false;
+        }
+
+        public skipOnPointerObservable: boolean;
+    }   
+}

+ 69 - 0
src/Events/babylon.pointerEvents.ts

@@ -0,0 +1,69 @@
+module BABYLON {
+    export class PointerEventTypes {
+        static _POINTERDOWN = 0x01;
+        static _POINTERUP = 0x02;
+        static _POINTERMOVE = 0x04;
+        static _POINTERWHEEL = 0x08;
+        static _POINTERPICK = 0x10;
+        static _POINTERTAP = 0x20;
+        static _POINTERDOUBLETAP = 0x40;
+
+        public static get POINTERDOWN(): number {
+            return PointerEventTypes._POINTERDOWN;
+        }
+
+        public static get POINTERUP(): number {
+            return PointerEventTypes._POINTERUP;
+        }
+
+        public static get POINTERMOVE(): number {
+            return PointerEventTypes._POINTERMOVE;
+        }
+
+        public static get POINTERWHEEL(): number {
+            return PointerEventTypes._POINTERWHEEL;
+        }
+
+        public static get POINTERPICK(): number {
+            return PointerEventTypes._POINTERPICK;
+        }
+
+        public static get POINTERTAP(): number {
+            return PointerEventTypes._POINTERTAP;
+        }
+
+        public static get POINTERDOUBLETAP(): number {
+            return PointerEventTypes._POINTERDOUBLETAP;
+        }
+    }
+
+    export class PointerInfoBase {
+        constructor(public type: number, public event: PointerEvent | MouseWheelEvent) {
+        }
+    }
+
+    /**
+     * This class is used to store pointer related info for the onPrePointerObservable event.
+     * Set the skipOnPointerObservable property to true if you want the engine to stop any process after this event is triggered, even not calling onPointerObservable
+     */
+    export class PointerInfoPre extends PointerInfoBase {
+        constructor(type: number, event: PointerEvent | MouseWheelEvent, localX, localY) {
+            super(type, event);
+            this.skipOnPointerObservable = false;
+            this.localPosition = new Vector2(localX, localY);
+        }
+
+        public localPosition: Vector2;
+        public skipOnPointerObservable: boolean;
+    }
+
+    /**
+     * This type contains all the data related to a pointer event in Babylon.js.
+     * The event member is an instance of PointerEvent for all types except PointerWheel and is of type MouseWheelEvent when type equals PointerWheel. The different event types can be found in the PointerEventTypes class.
+     */
+    export class PointerInfo extends PointerInfoBase {
+        constructor(type: number, event: PointerEvent | MouseWheelEvent, public pickInfo: PickingInfo) {
+            super(type, event);
+        }
+    }    
+}

+ 3 - 3
src/Materials/PBR/babylon.pbrBaseMaterial.ts

@@ -1061,7 +1061,9 @@
                 this._uniformBuffer.bindToEffect(effect, "Material");
 
                 this.bindViewProjection(effect);
-
+                var reflectionTexture = this._getReflectionTexture();
+                var refractionTexture = this._getRefractionTexture();
+                               
                 if (!this._uniformBuffer.useUbo || !this.isFrozen || !this._uniformBuffer.isSync) {
 
                     // Texture uniforms
@@ -1081,7 +1083,6 @@
                             MaterialHelper.BindTextureMatrix(this._opacityTexture, this._uniformBuffer, "opacity");
                         }
 
-                        var reflectionTexture = this._getReflectionTexture();
                         if (reflectionTexture && StandardMaterial.ReflectionTextureEnabled) {
                             this._uniformBuffer.updateMatrix("reflectionMatrix", reflectionTexture.getReflectionTextureMatrix());
                             this._uniformBuffer.updateFloat2("vReflectionInfos", reflectionTexture.level, 0);
@@ -1146,7 +1147,6 @@
                             }                                                         
                         }
 
-                        var refractionTexture = this._getRefractionTexture();
                         if (refractionTexture && StandardMaterial.RefractionTextureEnabled) {
                             this._uniformBuffer.updateMatrix("refractionMatrix", refractionTexture.getReflectionTextureMatrix());
 

+ 3 - 3
src/Materials/babylon.effect.ts

@@ -419,7 +419,7 @@
                 // Uniform declaration
                 if (includeFile.indexOf("__decl__") !== -1) {
                     includeFile = includeFile.replace(/__decl__/, "");
-                    if (this._engine.webGLVersion != 1) {
+                    if (this._engine.supportsUniformBuffers) {
                         includeFile = includeFile.replace(/Vertex/, "Ubo");
                         includeFile = includeFile.replace(/Fragment/, "Ubo");
                     }
@@ -455,7 +455,7 @@
                             }
 
                             for (var i = minIndex; i < maxIndex; i++) {
-                                if (this._engine.webGLVersion === 1) {
+                                if (!this._engine.supportsUniformBuffers) {
                                     // Ubo replacement
                                     sourceIncludeContent = sourceIncludeContent.replace(/light\{X\}.(\w*)/g, (str: string, p1: string) => {
                                         return p1 + "{X}";
@@ -464,7 +464,7 @@
                                 includeContent += sourceIncludeContent.replace(/\{X\}/g, i) + "\n";
                             }
                         } else {
-                            if (this._engine.webGLVersion === 1) {
+                            if (!this._engine.supportsUniformBuffers) {
                                 // Ubo replacement
                                 includeContent = includeContent.replace(/light\{X\}.(\w*)/g, (str: string, p1: string) => {
                                     return p1 + "{X}";

+ 1 - 1
src/Materials/babylon.material.ts

@@ -360,7 +360,7 @@
             }
 
             this._uniformBuffer = new UniformBuffer(this._scene.getEngine());
-            this._useUBO = this.getScene().getEngine().webGLVersion > 1;
+            this._useUBO = this.getScene().getEngine().supportsUniformBuffers;
 
             if (!doNotAdd) {
                 this._scene.materials.push(this);

+ 1 - 1
src/Materials/babylon.uniformBuffer.ts

@@ -121,7 +121,7 @@ module BABYLON {
          */
         constructor(engine: Engine, data?: number[], dynamic?: boolean) {
             this._engine = engine;
-            this._noUBO = engine.webGLVersion === 1;
+            this._noUBO = !engine.supportsUniformBuffers;
             this._dynamic = dynamic;
 
             this._data = data || [];

+ 2 - 2
src/Mesh/babylon.abstractMesh.ts

@@ -1533,7 +1533,7 @@
             }
         }
 
-        public moveWithCollisions(direction: Vector3): AbstractMesh {
+        public moveWithCollisions(displacement: Vector3): AbstractMesh {
             var globalPosition = this.getAbsolutePosition();
 
             globalPosition.subtractFromFloatsToRef(0, this.ellipsoid.y, 0, this._oldPositionForCollisions);
@@ -1545,7 +1545,7 @@
 
             this._collider.radius = this.ellipsoid;
 
-            this.getScene().collisionCoordinator.getNewPosition(this._oldPositionForCollisions, direction, this._collider, 3, this, this._onCollisionPositionChange, this.uniqueId);
+            this.getScene().collisionCoordinator.getNewPosition(this._oldPositionForCollisions, displacement, this._collider, 3, this, this._onCollisionPositionChange, this.uniqueId);
             return this;
         }
 

+ 48 - 8
src/babylon.engine.ts

@@ -539,10 +539,20 @@
         public onResizeObservable = new Observable<Engine>();
 
         /**
-         * Observable event triggered each time the canvas lost focus
+         * Observable event triggered each time the canvas loses focus
          */
         public onCanvasBlurObservable = new Observable<Engine>();
 
+        /**
+         * Observable event triggered each time the canvas gains focus
+         */
+        public onCanvasFocusObservable = new Observable<Engine>();        
+
+        /**
+         * Observable event triggered each time the canvas receives pointerout event
+         */
+        public onCanvasPointerOutObservable = new Observable<Engine>();
+
         //WebVR
 
         //The new WebVR uses promises.
@@ -556,7 +566,11 @@
         private _vrAnimationFrameHandler: number;
 
         // Uniform buffers list
+        public disableUniformBuffers = false;
         public _uniformBuffers = new Array<UniformBuffer>();
+        public get supportsUniformBuffers(): boolean {
+            return this.webGLVersion > 1 && !this.disableUniformBuffers;
+        }
 
         // Private Members
         private _gl: WebGLRenderingContext;
@@ -580,9 +594,14 @@
 
         public static audioEngine: AudioEngine;
 
-        private _onCanvasBlur: () => void;
-        private _onBlur: () => void;
+        
+        // Focus
         private _onFocus: () => void;
+        private _onBlur: () => void;       
+        private _onCanvasPointerOut: () => void;
+        private _onCanvasBlur: () => void;
+        private _onCanvasFocus: () => void;
+        
         private _onFullscreenChange: () => void;
         private _onPointerLockChange: () => void;
 
@@ -776,7 +795,18 @@
                 if (!this._gl) {
                     throw new Error("WebGL not supported");
                 }
-
+    
+                this._onCanvasFocus = () => {
+                    this.onCanvasFocusObservable.notifyObservers(this);
+                }
+    
+                this._onCanvasBlur = () => {
+                    this.onCanvasBlurObservable.notifyObservers(this);
+                }
+    
+                canvas.addEventListener("focus", this._onCanvasFocus);
+                canvas.addEventListener("blur", this._onCanvasBlur);
+    
                 this._onBlur = () => {
                     if (this.disablePerformanceMonitorInBackground) {
                         this._performanceMonitor.disable();
@@ -791,14 +821,14 @@
                     this._windowIsBackground = false;
                 };
 
-                this._onCanvasBlur = () => {
-                    this.onCanvasBlurObservable.notifyObservers(this);
+                this._onCanvasPointerOut = () => {
+                    this.onCanvasPointerOutObservable.notifyObservers(this);
                 };
 
                 window.addEventListener("blur", this._onBlur);
                 window.addEventListener("focus", this._onFocus);
 
-                canvas.addEventListener("pointerout", this._onCanvasBlur);
+                canvas.addEventListener("pointerout", this._onCanvasPointerOut);
             } else {
                 this._gl = <WebGLRenderingContext>canvasOrContext;
                 this._renderingCanvas = this._gl.canvas
@@ -2832,12 +2862,18 @@
             if (!this._rescalePostProcess) {
                 this._rescalePostProcess = new BABYLON.PassPostProcess("rescale", 1, null, Texture.BILINEAR_SAMPLINGMODE, this, false, Engine.TEXTURETYPE_UNSIGNED_INT);
             }
+
             this._rescalePostProcess.getEffect().executeWhenCompiled(() => {
                 this._rescalePostProcess.onApply = function (effect) {
                     effect._bindTexture("textureSampler", source);
                 }
 
-                scene.postProcessManager.directRender([this._rescalePostProcess], rtt);
+                let hostingScene = scene;
+
+                if (!hostingScene) {
+                    hostingScene = this.scenes[this.scenes.length - 1];
+                }
+                hostingScene.postProcessManager.directRender([this._rescalePostProcess], rtt);
 
                 this._bindTextureDirectly(this._gl.TEXTURE_2D, destination);
                 this._gl.copyTexImage2D(this._gl.TEXTURE_2D, 0, internalFormat, 0, 0, destination.width, destination.height, 0);
@@ -4330,6 +4366,8 @@
             window.removeEventListener("focus", this._onFocus);
             window.removeEventListener('vrdisplaypointerrestricted', this._onVRDisplayPointerRestricted);
             window.removeEventListener('vrdisplaypointerunrestricted', this._onVRDisplayPointerUnrestricted);
+            this._renderingCanvas.removeEventListener("focus", this._onCanvasFocus);
+            this._renderingCanvas.removeEventListener("blur", this._onCanvasBlur);            
             this._renderingCanvas.removeEventListener("pointerout", this._onCanvasBlur);
 
             if (!this._doNotHandleContextLost) {
@@ -4360,6 +4398,8 @@
 
             this.onResizeObservable.clear();
             this.onCanvasBlurObservable.clear();
+            this.onCanvasFocusObservable.clear();
+            this.onCanvasPointerOutObservable.clear();
 
             BABYLON.Effect.ResetCache();
         }

+ 67 - 77
src/babylon.scene.ts

@@ -36,75 +36,6 @@
         }
     }
 
-    export class PointerEventTypes {
-        static _POINTERDOWN = 0x01;
-        static _POINTERUP = 0x02;
-        static _POINTERMOVE = 0x04;
-        static _POINTERWHEEL = 0x08;
-        static _POINTERPICK = 0x10;
-        static _POINTERTAP = 0x20;
-        static _POINTERDOUBLETAP = 0x40;
-
-        public static get POINTERDOWN(): number {
-            return PointerEventTypes._POINTERDOWN;
-        }
-
-        public static get POINTERUP(): number {
-            return PointerEventTypes._POINTERUP;
-        }
-
-        public static get POINTERMOVE(): number {
-            return PointerEventTypes._POINTERMOVE;
-        }
-
-        public static get POINTERWHEEL(): number {
-            return PointerEventTypes._POINTERWHEEL;
-        }
-
-        public static get POINTERPICK(): number {
-            return PointerEventTypes._POINTERPICK;
-        }
-
-        public static get POINTERTAP(): number {
-            return PointerEventTypes._POINTERTAP;
-        }
-
-        public static get POINTERDOUBLETAP(): number {
-            return PointerEventTypes._POINTERDOUBLETAP;
-        }
-    }
-
-    export class PointerInfoBase {
-        constructor(public type: number, public event: PointerEvent | MouseWheelEvent) {
-        }
-
-    }
-
-    /**
-     * This class is used to store pointer related info for the onPrePointerObservable event.
-     * Set the skipOnPointerObservable property to true if you want the engine to stop any process after this event is triggered, even not calling onPointerObservable
-     */
-    export class PointerInfoPre extends PointerInfoBase {
-        constructor(type: number, event: PointerEvent | MouseWheelEvent, localX, localY) {
-            super(type, event);
-            this.skipOnPointerObservable = false;
-            this.localPosition = new Vector2(localX, localY);
-        }
-
-        public localPosition: Vector2;
-        public skipOnPointerObservable: boolean;
-    }
-
-    /**
-     * This type contains all the data related to a pointer event in Babylon.js.
-     * The event member is an instance of PointerEvent for all types except PointerWheel and is of type MouseWheelEvent when type equals PointerWheel. The different event types can be found in the PointerEventTypes class.
-     */
-    export class PointerInfo extends PointerInfoBase {
-        constructor(type: number, event: PointerEvent | MouseWheelEvent, public pickInfo: PickingInfo) {
-            super(type, event);
-        }
-    }
-
     /**
      * This class is used by the onRenderingGroupObservable
      */
@@ -434,8 +365,8 @@
         }
 
         /**
-         * This observable event is triggered when any mouse event registered during Scene.attach() is called BEFORE the 3D engine to process anything (mesh/sprite picking for instance).
-         * You have the possibility to skip the 3D Engine process and the call to onPointerObservable by setting PointerInfoBase.skipOnPointerObservable to true
+         * This observable event is triggered when any ponter event is triggered. It is registered during Scene.attachControl() and it is called BEFORE the 3D engine process anything (mesh/sprite picking for instance).
+         * You have the possibility to skip the process and the call to onPointerObservable by setting PointerInfoPre.skipOnPointerObservable to true
          */
         public onPrePointerObservable = new Observable<PointerInfoPre>();
 
@@ -491,8 +422,21 @@
         public _mirroredCameraPosition: Vector3;
 
         // Keyboard
+
+        /**
+         * This observable event is triggered when any keyboard event si raised and registered during Scene.attachControl()
+         * You have the possibility to skip the process and the call to onKeyboardObservable by setting KeyboardInfoPre.skipOnPointerObservable to true
+         */
+        public onPreKeyboardObservable = new Observable<KeyboardInfoPre>();
+        
+        /**
+         * Observable event triggered each time an keyboard event is received from the hosting window
+         */
+        public onKeyboardObservable = new Observable<KeyboardInfo>();
         private _onKeyDown: (evt: Event) => void;
         private _onKeyUp: (evt: Event) => void;
+        private _onCanvasFocusObserver: Observer<Engine>;
+        private _onCanvasBlurObserver: Observer<Engine>;
 
         // Coordinate system
         /**
@@ -1537,18 +1481,56 @@
                 }).bind(this));
             };
 
-            this._onKeyDown = (evt: Event) => {
+            this._onKeyDown = (evt: KeyboardEvent) => {
+                let type = KeyboardEventTypes.KEYDOWN;
+                if (this.onPreKeyboardObservable.hasObservers()) {
+                    let pi = new KeyboardInfoPre(type, evt);
+                    this.onPreKeyboardObservable.notifyObservers(pi, type);
+                    if (pi.skipOnPointerObservable) {
+                        return;
+                    }
+                }
+
+                if (this.onKeyboardObservable.hasObservers()) {
+                    let pi = new KeyboardInfo(type, evt);
+                    this.onKeyboardObservable.notifyObservers(pi, type);
+                }
+
                 if (this.actionManager) {
                     this.actionManager.processTrigger(ActionManager.OnKeyDownTrigger, ActionEvent.CreateNewFromScene(this, evt));
                 }
             };
 
-            this._onKeyUp = (evt: Event) => {
+            this._onKeyUp = (evt: KeyboardEvent) => {
+                let type = KeyboardEventTypes.KEYUP;
+                if (this.onPreKeyboardObservable.hasObservers()) {
+                    let pi = new KeyboardInfoPre(type, evt);
+                    this.onPreKeyboardObservable.notifyObservers(pi, type);
+                    if (pi.skipOnPointerObservable) {
+                        return;
+                    }
+                }
+
+                if (this.onKeyboardObservable.hasObservers()) {
+                    let pi = new KeyboardInfo(type, evt);
+                    this.onKeyboardObservable.notifyObservers(pi, type);
+                }
+
                 if (this.actionManager) {
                     this.actionManager.processTrigger(ActionManager.OnKeyUpTrigger, ActionEvent.CreateNewFromScene(this, evt));
                 }
             };
 
+            let engine = this.getEngine();
+            this._onCanvasFocusObserver = engine.onCanvasFocusObservable.add(()=>{
+                canvas.addEventListener("keydown", this._onKeyDown, false);
+                canvas.addEventListener("keyup", this._onKeyUp, false);   
+            });
+
+            this._onCanvasBlurObserver = engine.onCanvasBlurObservable.add(()=>{                
+                canvas.removeEventListener("keydown", this._onKeyDown);
+                canvas.removeEventListener("keyup", this._onKeyUp);
+            });
 
             var eventPrefix = Tools.GetPointerPrefix();
             var canvas = this._engine.getRenderingCanvas();
@@ -1568,25 +1550,33 @@
             }
 
             canvas.tabIndex = 1;
-
-            canvas.addEventListener("keydown", this._onKeyDown, false);
-            canvas.addEventListener("keyup", this._onKeyUp, false);
         }
 
         public detachControl() {
+            let engine = this.getEngine();
             var eventPrefix = Tools.GetPointerPrefix();
-            var canvas = this._engine.getRenderingCanvas();
+            var canvas = engine.getRenderingCanvas();
 
             canvas.removeEventListener(eventPrefix + "move", this._onPointerMove);
             canvas.removeEventListener(eventPrefix + "down", this._onPointerDown);
             canvas.removeEventListener(eventPrefix + "up", this._onPointerUp);
 
+            engine.onCanvasBlurObservable.remove(this._onCanvasBlurObserver);
+            engine.onCanvasFocusObservable.remove(this._onCanvasFocusObserver);
+
             // Wheel
             canvas.removeEventListener('mousewheel', this._onPointerMove);
             canvas.removeEventListener('DOMMouseScroll', this._onPointerMove);
 
+            // Keyboard
             canvas.removeEventListener("keydown", this._onKeyDown);
             canvas.removeEventListener("keyup", this._onKeyUp);
+
+            // Observables
+            this.onKeyboardObservable.clear();
+            this.onPreKeyboardObservable.clear();
+            this.onPointerObservable.clear();
+            this.onPrePointerObservable.clear();     
         }
 
         // Ready