Browse Source

update from upstream

nockawa 9 năm trước cách đây
mục cha
commit
5467eb3226
100 tập tin đã thay đổi với 10282 bổ sung6486 xóa
  1. 6 4
      Exporters/Unity 5/Unity3D2Babylon/ExporterWindow.cs
  2. 12 1
      Exporters/Unity 5/Unity3D2Babylon/SceneBuilder.cs
  3. 12 12
      Tools/Gulp/config.json
  4. 25 26
      dist/preview release/babylon.core.js
  5. 4054 3607
      dist/preview release/babylon.d.ts
  6. 37 36
      dist/preview release/babylon.js
  7. 3314 2170
      dist/preview release/babylon.max.js
  8. 37 36
      dist/preview release/babylon.noworker.js
  9. 10 4
      dist/preview release/what's new.md
  10. 1 1
      src/Actions/babylon.action.js
  11. 5 2
      src/Actions/babylon.actionManager.js
  12. 4 0
      src/Actions/babylon.actionManager.ts
  13. 4 4
      src/Actions/babylon.condition.js
  14. 18 12
      src/Actions/babylon.directActions.js
  15. 8 0
      src/Actions/babylon.directActions.ts
  16. 1 1
      src/Actions/babylon.interpolateValueAction.js
  17. 1 1
      src/Animations/babylon.animatable.js
  18. 30 0
      src/Animations/babylon.animation.js
  19. 27 0
      src/Animations/babylon.animation.ts
  20. 13 13
      src/Animations/babylon.easing.js
  21. 1 1
      src/Audio/babylon.analyser.js
  22. 1 1
      src/Audio/babylon.audioEngine.js
  23. 1 1
      src/Audio/babylon.sound.js
  24. 1 1
      src/Audio/babylon.soundtrack.js
  25. 1 1
      src/Bones/babylon.bone.js
  26. 1 1
      src/Bones/babylon.skeleton.js
  27. 1 1
      src/Cameras/Inputs/babylon.arcrotatecamera.input.gamepad.js
  28. 1 1
      src/Cameras/Inputs/babylon.arcrotatecamera.input.keyboard.js
  29. 1 1
      src/Cameras/Inputs/babylon.arcrotatecamera.input.mousewheel.js
  30. 1 1
      src/Cameras/Inputs/babylon.arcrotatecamera.input.pointers.js
  31. 1 1
      src/Cameras/Inputs/babylon.arcrotatecamera.input.vrdeviceorientation.js
  32. 41 55
      src/Cameras/Inputs/babylon.freecamera.input.deviceorientation.js
  33. 39 54
      src/Cameras/Inputs/babylon.freecamera.input.deviceorientation.ts
  34. 1 1
      src/Cameras/Inputs/babylon.freecamera.input.gamepad.js
  35. 1 1
      src/Cameras/Inputs/babylon.freecamera.input.keyboard.js
  36. 1 1
      src/Cameras/Inputs/babylon.freecamera.input.mouse.js
  37. 1 1
      src/Cameras/Inputs/babylon.freecamera.input.touch.js
  38. 1 1
      src/Cameras/Inputs/babylon.freecamera.input.virtualjoystick.js
  39. 1 1
      src/Cameras/Inputs/babylon.freecamera.input.vrdeviceorientation.js
  40. 1 1
      src/Cameras/VR/babylon.vrCameraMetrics.js
  41. 4 3
      src/Cameras/VR/babylon.vrDeviceOrientationCamera.js
  42. 3 1
      src/Cameras/VR/babylon.vrDeviceOrientationCamera.ts
  43. 1 1
      src/Cameras/VR/babylon.webVRCamera.js
  44. 1 1
      src/Cameras/babylon.arcRotateCamera.js
  45. 1 1
      src/Cameras/babylon.arcRotateCameraInputsManager.js
  46. 1 1
      src/Cameras/babylon.camera.js
  47. 1 1
      src/Cameras/babylon.cameraInputsManager.js
  48. 3 13
      src/Cameras/babylon.deviceOrientationCamera.js
  49. 2 12
      src/Cameras/babylon.deviceOrientationCamera.ts
  50. 2 2
      src/Cameras/babylon.followCamera.js
  51. 1 1
      src/Cameras/babylon.freeCamera.js
  52. 1 1
      src/Cameras/babylon.freeCameraInputsManager.js
  53. 1 1
      src/Cameras/babylon.gamepadCamera.js
  54. 8 8
      src/Cameras/babylon.stereoscopicCameras.js
  55. 30 19
      src/Cameras/babylon.targetCamera.js
  56. 43 26
      src/Cameras/babylon.targetCamera.ts
  57. 1 1
      src/Cameras/babylon.touchCamera.js
  58. 1 1
      src/Cameras/babylon.universalCamera.js
  59. 1 1
      src/Cameras/babylon.virtualJoysticksCamera.js
  60. 43 28
      src/Canvas2d/babylon.bounding2d.js
  61. 40 26
      src/Canvas2d/babylon.bounding2d.ts
  62. 5 5
      src/Canvas2d/babylon.brushes2d.js
  63. 3 3
      src/Canvas2d/babylon.brushes2d.ts
  64. 409 43
      src/Canvas2d/babylon.canvas2d.js
  65. 487 36
      src/Canvas2d/babylon.canvas2d.ts
  66. 13 5
      src/Canvas2d/babylon.group2d.js
  67. 9 2
      src/Canvas2d/babylon.group2d.ts
  68. 2 2
      src/Canvas2d/babylon.modelRenderCache.js
  69. 429 23
      src/Canvas2d/babylon.prim2dBase.js
  70. 563 25
      src/Canvas2d/babylon.prim2dBase.ts
  71. 20 4
      src/Canvas2d/babylon.rectangle2d.js
  72. 16 1
      src/Canvas2d/babylon.rectangle2d.ts
  73. 46 11
      src/Canvas2d/babylon.renderablePrim2d.js
  74. 44 6
      src/Canvas2d/babylon.renderablePrim2d.ts
  75. 2 2
      src/Canvas2d/babylon.shape2d.js
  76. 90 27
      src/Canvas2d/babylon.smartPropertyPrim.js
  77. 102 25
      src/Canvas2d/babylon.smartPropertyPrim.ts
  78. 22 4
      src/Canvas2d/babylon.sprite2d.js
  79. 19 1
      src/Canvas2d/babylon.sprite2d.ts
  80. 36 17
      src/Canvas2d/babylon.text2d.js
  81. 37 16
      src/Canvas2d/babylon.text2d.ts
  82. 1 1
      src/Canvas2d/babylon.worldSpaceCanvas2d.js
  83. 1 1
      src/Collisions/babylon.collider.js
  84. 2 2
      src/Collisions/babylon.collisionCoordinator.js
  85. 3 3
      src/Collisions/babylon.collisionWorker.js
  86. 2 2
      src/Collisions/babylon.pickingInfo.js
  87. 1 1
      src/Culling/Octrees/babylon.octree.js
  88. 1 1
      src/Culling/Octrees/babylon.octreeBlock.js
  89. 1 1
      src/Culling/babylon.boundingBox.js
  90. 1 1
      src/Culling/babylon.boundingInfo.js
  91. 1 1
      src/Culling/babylon.boundingSphere.js
  92. 1 1
      src/Culling/babylon.ray.js
  93. 1 1
      src/Debug/babylon.debugLayer.js
  94. 1 1
      src/Debug/babylon.skeletonViewer.js
  95. 1 1
      src/Layer/babylon.layer.js
  96. 1 1
      src/LensFlare/babylon.lensFlare.js
  97. 1 1
      src/LensFlare/babylon.lensFlareSystem.js
  98. 1 1
      src/Lights/Shadows/babylon.shadowGenerator.js
  99. 1 1
      src/Lights/babylon.directionalLight.js
  100. 0 0
      src/Lights/babylon.hemisphericLight.js

+ 6 - 4
Exporters/Unity 5/Unity3D2Babylon/ExporterWindow.cs

@@ -2,11 +2,10 @@
 using System.Collections.Generic;
 using System.Diagnostics;
 using System.IO;
-using JsonFx;
 using UnityEditor;
 using UnityEngine;
 using JsonFx.Json;
-using UnityEditor.SceneManagement;
+using UnityEngine.SceneManagement;
 
 namespace Unity3D2Babylon
 {
@@ -78,6 +77,9 @@ namespace Unity3D2Babylon
             EditorGUILayout.Space();
             GUILayout.Label("Physics options", EditorStyles.boldLabel);
             exportationOptions.ExportPhysics = EditorGUILayout.Toggle("Physics", exportationOptions.ExportPhysics);
+            EditorGUILayout.Space();
+            GUILayout.Label("Shadows options", EditorStyles.boldLabel);
+            exportationOptions.ExportShadows = EditorGUILayout.Toggle("Shadows", exportationOptions.ExportShadows);
 
             EditorGUILayout.Space();
             EditorGUILayout.Space();
@@ -114,7 +116,7 @@ namespace Unity3D2Babylon
         {
             try
             {
-                string sceneName = EditorSceneManager.GetActiveScene().name;
+                string sceneName = SceneManager.GetActiveScene().name;
 
                 exportationOptions.DefaultFolder = EditorUtility.SaveFolderPanel("Please select a folder", exportationOptions.DefaultFolder, "");
 
@@ -141,7 +143,7 @@ namespace Unity3D2Babylon
                 var outputFile = sceneBuilder.WriteToBabylonFile();
 
                 watch.Stop();
-                ReportProgress(1, string.Format("Exportation done in {0:0.00}s", watch.Elapsed.TotalSeconds));
+                ReportProgress(1, $"Exportation done in {watch.Elapsed.TotalSeconds:0.00}s");
                 EditorUtility.ClearProgressBar();
 
                 sceneBuilder.GenerateStatus(logs);

+ 12 - 1
Exporters/Unity 5/Unity3D2Babylon/SceneBuilder.cs

@@ -38,6 +38,14 @@ namespace Unity3D2Babylon
 
             babylonScene = new BabylonScene(OutputPath);
 
+            babylonScene.producer = new BabylonProducer
+            {
+                file = Path.GetFileName(outputPath),
+                version = "Unity3D",
+                name = SceneName,
+                exporter_version = "0.8"
+            };
+
             this.exportationOptions = exportationOptions;
         }
 
@@ -47,7 +55,10 @@ namespace Unity3D2Babylon
 
             var outputFile = Path.Combine(OutputPath, SceneName + ".babylon");
 
-            var jsWriter = new JsonWriter(new DataWriterSettings(new DataContractResolverStrategy()));
+            var settings = new DataWriterSettings(new DataContractResolverStrategy()) {PrettyPrint = true};
+
+            var jsWriter = new JsonWriter(settings);
+
             string babylonJSformat = jsWriter.Write(babylonScene);
             using (var sw = new StreamWriter(outputFile))
             {

+ 12 - 12
Tools/Gulp/config.json

@@ -43,18 +43,18 @@
       "../../src/Collisions/babylon.collider.js",
       "../../src/Collisions/babylon.collisionCoordinator.js",
       "../../src/Cameras/babylon.camera.js",
-      "../../src/cameras/babylon.camerainputsmanager.js",
-      "../../src/cameras/inputs/babylon.freecamera.input.mouse.js",
-      "../../src/cameras/inputs/babylon.freecamera.input.keyboard.js",
-      "../../src/cameras/inputs/babylon.freecamera.input.touch.js",
-      "../../src/cameras/inputs/babylon.freecamera.input.deviceorientation.js",
-      "../../src/cameras/inputs/babylon.freecamera.input.vrdeviceorientation.js",
-      "../../src/cameras/inputs/babylon.freecamera.input.gamepad.js",
-      "../../src/cameras/inputs/babylon.arcrotatecamera.input.keyboard.js",
-      "../../src/cameras/inputs/babylon.arcrotatecamera.input.mousewheel.js",
-      "../../src/cameras/inputs/babylon.arcrotatecamera.input.pointers.js",
-      "../../src/cameras/inputs/babylon.arcrotatecamera.input.gamepad.js",
-      "../../src/cameras/inputs/babylon.arcrotatecamera.input.vrdeviceorientation.js",
+      "../../src/Cameras/babylon.camerainputsmanager.js",
+      "../../src/Cameras/inputs/babylon.freecamera.input.mouse.js",
+      "../../src/Cameras/inputs/babylon.freecamera.input.keyboard.js",
+      "../../src/Cameras/inputs/babylon.freecamera.input.touch.js",
+      "../../src/Cameras/inputs/babylon.freecamera.input.deviceorientation.js",
+      "../../src/Cameras/inputs/babylon.freecamera.input.vrdeviceorientation.js",
+      "../../src/Cameras/inputs/babylon.freecamera.input.gamepad.js",
+      "../../src/Cameras/inputs/babylon.arcrotatecamera.input.keyboard.js",
+      "../../src/Cameras/inputs/babylon.arcrotatecamera.input.mousewheel.js",
+      "../../src/Cameras/inputs/babylon.arcrotatecamera.input.pointers.js",
+      "../../src/Cameras/inputs/babylon.arcrotatecamera.input.gamepad.js",
+      "../../src/Cameras/inputs/babylon.arcrotatecamera.input.vrdeviceorientation.js",
       "../../src/Cameras/babylon.targetCamera.js",
       "../../src/Cameras/babylon.freeCamera.js",
       "../../src/Cameras/babylon.freeCameraInputsManager.js",

Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 25 - 26
dist/preview release/babylon.core.js


Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 4054 - 3607
dist/preview release/babylon.d.ts


Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 37 - 36
dist/preview release/babylon.js


Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 3314 - 2170
dist/preview release/babylon.max.js


Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 37 - 36
dist/preview release/babylon.noworker.js


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

@@ -5,7 +5,7 @@
     - Support for shaders includes ([deltakosh](https://github.com/deltakosh))
     - New mesh type : `LineSystem` ([jerome](https://github.com/jbousquie))
     - SerializationHelper for complex classes using TypeScript decorators ([deltakosh](https://github.com/deltakosh))
-    - StandardMaterial now supports Parallax and Parallax Occlusion Mapping ([nockawa](https://github.com/nockawa))
+    - StandardMaterial now supports Parallax and Parallax Occlusion Mapping ([tutorial](http://doc.babylonjs.com/tutorials/Using_parallax_mapping)) ([nockawa](https://github.com/nockawa))
     - Animations blending. See [demo here](http://www.babylonjs-playground.com/#2BLI9T#3). More [info here](http://doc.babylonjs.com/tutorials/Animations#animation-blending) ([deltakosh](https://github.com/deltakosh))
     - New debuger tool: SkeletonViewer. See [demo here](http://www.babylonjs-playground.com/#1BZJVJ#8) (Adam & [deltakosh](https://github.com/deltakosh))
     - Added Camera Inputs Manager to manage camera inputs (mouse, touch, keyboard, gamepad, ...) in a composable way, without relying on class inheritance ([gleborgne](https://github.com/gleborgne))
@@ -15,8 +15,13 @@
     - Unity3D exporter: Added support for export and run (local webserver) ([davrous](https://github.com/davrous), [deltakosh](https://github.com/deltakosh))
     - Moved PBR Material to core ([deltakosh](https://github.com/deltakosh))
     - StandardMaterial.maxSimultaneousLights can define how many dynamic lights the material can handle ([deltakosh](https://github.com/deltakosh))
+	  - Introduced Canvas2D feature: a 2D engine to render primitives, sprites in 2D, text. Canvas2D can be displayed in Screen Space (above the 3D scene) or in World Space to be a part of the Scene. [overview](http://doc.babylonjs.com/overviews/Using_The_Canvas2D), [tutorial](http://doc.babylonjs.com/tutorials/Using_the_Canvas2D) ([nockawa](https://github.com/nockawa))
+	  - Added two new types of Texture: FontTexture and MapTexture ([quick doc](http://www.html5gamedevs.com/topic/22565-two-new-texture-types-fonttexture-and-maptexture/)) ([nockawa](https://github.com/nockawa))
+	  - Added a dynamic [2D Bin Packing Algorithm](http://stackoverflow.com/questions/8762569/how-is-2d-bin-packing-achieved-programmatically), ([more info here](http://www.html5gamedevs.com/topic/22565-two-new-texture-types-fonttexture-and-maptexture/)) ([nockawa](https://github.com/nockawa))
 	  - Introduced Canvas2D feature: a 2D engine to render primitives, sprites in 2D, text. Canvas2D can be displayed in Screen Space (above the 3D scene) or in World Space to be a part of the Scene. [overview](http://doc.babylonjs.com/overviews/Using_The_Canvas2D), [tutorial](http://doc.babylonjs.com/tutorials/Using_the_Canvas2D) ([nockawa](https://github.com/nockawa))	
   - **Updates**
+    - Renderlists can now also be defined using predicates ([deltakosh](https://github.com/deltakosh))
+    - Added support for various normal maps conventions ([deltakosh](https://github.com/deltakosh))
     - Added postprocess.enablePixelPerfectMode to avoid texture scaling/stretching when dealing with non-power of 2 resolutions. cannot be used on post-processes chain ([deltakosh](https://github.com/deltakosh))
     - Enabled other post processes to be used when also using a 3D Rig ([jcpalmer](https://github.com/Palmer-JC))
     - Added skeleton.getBoneIndexByName(boneName: string) ([dad72](https://github.com/dad72))
@@ -27,6 +32,7 @@
     - New OnPickTrigger support for spritesManager ([deltakosh](https://github.com/deltakosh))
     - New SPS method `digest()` ([jerome](https://github.com/jbousquie))    
     - New SPS property `computeBoundingBox` ([jerome](https://github.com/jbousquie))  
+    - New SPS particle property `isVisible` ([jerome](https://github.com/jbousquie)) 
     - Added a new OnPickOut trigger fired when you release the pointer button outside of a mesh or sprite. ([deltakosh](https://github.com/deltakosh))
     - Added support for OnPointerOver and OnPointerOut for sprites. ([deltakosh](https://github.com/deltakosh))
     - Added an optional predicate on Node.getDescendants, Node.getChildren to filter out Nodes based on a callback execution. ([nockawa](https://github.com/nockawa))
@@ -34,9 +40,9 @@
     - LinesMesh class now supports Intersection. Added the intersectionThreshold property to set a tolerance margin during intersection with wire lines. ([nockawa](https://github.com/nockawa))
     - Geometry.boundingBias property to enlarge the boundingInfo objects ([nockawa](https://github.com/nockawa))
     - Tools.ExtractMinAndMax & ExtractMinAndMaxIndexed now supports an optional Bias for Extent computation.
-	  - Added StringDictionary<T> class to implement an efficient generic typed string dictionary based on Javascript associative array. ([nockawa](https://github.com/nockawa))
-	  - Added RectanglePackingMap class to fix several rectangles in a big map in the most optimal way. ([nockawa](https://github.com/nockawa))
-	  - Added DynamicFloatArray class to store float32 based elements of a given size (stride) into one big Float32Array, with allocation/free/pack operations to then access an optimal buffer that can be used to update a WebGLBuffer dynamically. ([nockawa](https://github.com/nockawa))
+	  - Added StringDictionary<T> class to implement an efficient generic typed string dictionary based on Javascript associative array. ([quick dock](http://www.html5gamedevs.com/topic/22566-be-efficient-my-friend-use-stringdictionary/)) ([nockawa](https://github.com/nockawa))
+	  - Added RectanglePackingMap class to fit several rectangles in a big map in the most optimal way, dynamically. ([nockawa](https://github.com/nockawa))
+	  - Added DynamicFloatArray class to store float32 based elements of a given size (stride) into one big Float32Array, with allocation/free/pack operations to then access an optimal buffer that can be used to update a WebGLBuffer dynamically.([quick doc](http://www.html5gamedevs.com/topic/22567-dynamicfloatarray-to-the-rescue-for-efficient-instanced-array/)) ([nockawa](https://github.com/nockawa))
 	  - Scene.onPointerObservable property added to enable a unique Observable event for user input (see ArcRotateCamera inputs for examples) ([nockawa](https://github.com/nockawa))
   - **Exporters**
     - Unity exporter now support skeletons ([sebavan](https://github.com/sebavan))

+ 1 - 1
src/Actions/babylon.action.js

@@ -126,6 +126,6 @@ var BABYLON;
             };
         };
         return Action;
-    }());
+    })();
     BABYLON.Action = Action;
 })(BABYLON || (BABYLON = {}));

+ 5 - 2
src/Actions/babylon.actionManager.js

@@ -46,8 +46,11 @@ var BABYLON;
         ActionEvent.CreateNewFromScene = function (scene, evt) {
             return new ActionEvent(null, scene.pointerX, scene.pointerY, scene.meshUnderPointer, evt);
         };
+        ActionEvent.CreateNewFromPrimitive = function (prim, pointerPos, evt, additionalData) {
+            return new ActionEvent(prim, pointerPos.x, pointerPos.y, null, evt, additionalData);
+        };
         return ActionEvent;
-    }());
+    })();
     BABYLON.ActionEvent = ActionEvent;
     /**
      * Action Manager manages all events to be triggered on a given mesh or the global scene.
@@ -519,6 +522,6 @@ var BABYLON;
         ActionManager.DragMovementThreshold = 10; // in pixels
         ActionManager.LongPressDelay = 500; // in milliseconds
         return ActionManager;
-    }());
+    })();
     BABYLON.ActionManager = ActionManager;
 })(BABYLON || (BABYLON = {}));

+ 4 - 0
src/Actions/babylon.actionManager.ts

@@ -44,6 +44,10 @@
         public static CreateNewFromScene(scene: Scene, evt: Event): ActionEvent {
             return new ActionEvent(null, scene.pointerX, scene.pointerY, scene.meshUnderPointer, evt);
         }
+
+        public static CreateNewFromPrimitive(prim: any, pointerPos: Vector2, evt?: Event, additionalData?: any): ActionEvent {
+            return new ActionEvent(prim, pointerPos.x, pointerPos.y, null, evt, additionalData);
+        }
     }
 
     /**

+ 4 - 4
src/Actions/babylon.condition.js

@@ -29,7 +29,7 @@ var BABYLON;
             };
         };
         return Condition;
-    }());
+    })();
     BABYLON.Condition = Condition;
     var ValueCondition = (function (_super) {
         __extends(ValueCondition, _super);
@@ -117,7 +117,7 @@ var BABYLON;
         ValueCondition._IsGreater = 2;
         ValueCondition._IsLesser = 3;
         return ValueCondition;
-    }(Condition));
+    })(Condition);
     BABYLON.ValueCondition = ValueCondition;
     var PredicateCondition = (function (_super) {
         __extends(PredicateCondition, _super);
@@ -129,7 +129,7 @@ var BABYLON;
             return this.predicate();
         };
         return PredicateCondition;
-    }(Condition));
+    })(Condition);
     BABYLON.PredicateCondition = PredicateCondition;
     var StateCondition = (function (_super) {
         __extends(StateCondition, _super);
@@ -152,6 +152,6 @@ var BABYLON;
             });
         };
         return StateCondition;
-    }(Condition));
+    })(Condition);
     BABYLON.StateCondition = StateCondition;
 })(BABYLON || (BABYLON = {}));

+ 18 - 12
src/Actions/babylon.directActions.js

@@ -29,7 +29,7 @@ var BABYLON;
             }, parent);
         };
         return SwitchBooleanAction;
-    }(BABYLON.Action));
+    })(BABYLON.Action);
     BABYLON.SwitchBooleanAction = SwitchBooleanAction;
     var SetStateAction = (function (_super) {
         __extends(SetStateAction, _super);
@@ -51,7 +51,7 @@ var BABYLON;
             }, parent);
         };
         return SetStateAction;
-    }(BABYLON.Action));
+    })(BABYLON.Action);
     BABYLON.SetStateAction = SetStateAction;
     var SetValueAction = (function (_super) {
         __extends(SetValueAction, _super);
@@ -67,6 +67,9 @@ var BABYLON;
         };
         SetValueAction.prototype.execute = function () {
             this._effectiveTarget[this._property] = this.value;
+            if (this._target.markAsDirty) {
+                this._target.markAsDirty(this._property);
+            }
         };
         SetValueAction.prototype.serialize = function (parent) {
             return _super.prototype._serialize.call(this, {
@@ -79,7 +82,7 @@ var BABYLON;
             }, parent);
         };
         return SetValueAction;
-    }(BABYLON.Action));
+    })(BABYLON.Action);
     BABYLON.SetValueAction = SetValueAction;
     var IncrementValueAction = (function (_super) {
         __extends(IncrementValueAction, _super);
@@ -98,6 +101,9 @@ var BABYLON;
         };
         IncrementValueAction.prototype.execute = function () {
             this._effectiveTarget[this._property] += this.value;
+            if (this._target.markAsDirty) {
+                this._target.markAsDirty(this._property);
+            }
         };
         IncrementValueAction.prototype.serialize = function (parent) {
             return _super.prototype._serialize.call(this, {
@@ -110,7 +116,7 @@ var BABYLON;
             }, parent);
         };
         return IncrementValueAction;
-    }(BABYLON.Action));
+    })(BABYLON.Action);
     BABYLON.IncrementValueAction = IncrementValueAction;
     var PlayAnimationAction = (function (_super) {
         __extends(PlayAnimationAction, _super);
@@ -139,7 +145,7 @@ var BABYLON;
             }, parent);
         };
         return PlayAnimationAction;
-    }(BABYLON.Action));
+    })(BABYLON.Action);
     BABYLON.PlayAnimationAction = PlayAnimationAction;
     var StopAnimationAction = (function (_super) {
         __extends(StopAnimationAction, _super);
@@ -160,7 +166,7 @@ var BABYLON;
             }, parent);
         };
         return StopAnimationAction;
-    }(BABYLON.Action));
+    })(BABYLON.Action);
     BABYLON.StopAnimationAction = StopAnimationAction;
     var DoNothingAction = (function (_super) {
         __extends(DoNothingAction, _super);
@@ -177,7 +183,7 @@ var BABYLON;
             }, parent);
         };
         return DoNothingAction;
-    }(BABYLON.Action));
+    })(BABYLON.Action);
     BABYLON.DoNothingAction = DoNothingAction;
     var CombineAction = (function (_super) {
         __extends(CombineAction, _super);
@@ -208,7 +214,7 @@ var BABYLON;
             return serializationObject;
         };
         return CombineAction;
-    }(BABYLON.Action));
+    })(BABYLON.Action);
     BABYLON.CombineAction = CombineAction;
     var ExecuteCodeAction = (function (_super) {
         __extends(ExecuteCodeAction, _super);
@@ -220,7 +226,7 @@ var BABYLON;
             this.func(evt);
         };
         return ExecuteCodeAction;
-    }(BABYLON.Action));
+    })(BABYLON.Action);
     BABYLON.ExecuteCodeAction = ExecuteCodeAction;
     var SetParentAction = (function (_super) {
         __extends(SetParentAction, _super);
@@ -250,7 +256,7 @@ var BABYLON;
             }, parent);
         };
         return SetParentAction;
-    }(BABYLON.Action));
+    })(BABYLON.Action);
     BABYLON.SetParentAction = SetParentAction;
     var PlaySoundAction = (function (_super) {
         __extends(PlaySoundAction, _super);
@@ -271,7 +277,7 @@ var BABYLON;
             }, parent);
         };
         return PlaySoundAction;
-    }(BABYLON.Action));
+    })(BABYLON.Action);
     BABYLON.PlaySoundAction = PlaySoundAction;
     var StopSoundAction = (function (_super) {
         __extends(StopSoundAction, _super);
@@ -292,6 +298,6 @@ var BABYLON;
             }, parent);
         };
         return StopSoundAction;
-    }(BABYLON.Action));
+    })(BABYLON.Action);
     BABYLON.StopSoundAction = StopSoundAction;
 })(BABYLON || (BABYLON = {}));

+ 8 - 0
src/Actions/babylon.directActions.ts

@@ -69,6 +69,10 @@
 
         public execute(): void {
             this._effectiveTarget[this._property] = this.value;
+
+            if (this._target.markAsDirty) {
+                this._target.markAsDirty(this._property);
+            }
         }
         
         public serialize(parent: any): any {
@@ -104,6 +108,10 @@
 
         public execute(): void {
             this._effectiveTarget[this._property] += this.value;
+
+            if (this._target.markAsDirty) {
+                this._target.markAsDirty(this._property);
+            }
         }
         
         public serialize(parent: any): any {

+ 1 - 1
src/Actions/babylon.interpolateValueAction.js

@@ -72,6 +72,6 @@ var BABYLON;
             }, parent);
         };
         return InterpolateValueAction;
-    }(BABYLON.Action));
+    })(BABYLON.Action);
     BABYLON.InterpolateValueAction = InterpolateValueAction;
 })(BABYLON || (BABYLON = {}));

+ 1 - 1
src/Animations/babylon.animatable.js

@@ -127,6 +127,6 @@ var BABYLON;
             return running;
         };
         return Animatable;
-    }());
+    })();
     BABYLON.Animatable = Animatable;
 })(BABYLON || (BABYLON = {}));

+ 30 - 0
src/Animations/babylon.animation.js

@@ -120,6 +120,9 @@ var BABYLON;
             else if (from instanceof BABYLON.Color3) {
                 dataType = Animation.ANIMATIONTYPE_COLOR3;
             }
+            else if (from instanceof BABYLON.Size) {
+                dataType = Animation.ANIMATIONTYPE_SIZE;
+            }
             if (dataType == undefined) {
                 return null;
             }
@@ -248,6 +251,9 @@ var BABYLON;
         Animation.prototype.vector2InterpolateFunction = function (startValue, endValue, gradient) {
             return BABYLON.Vector2.Lerp(startValue, endValue, gradient);
         };
+        Animation.prototype.sizeInterpolateFunction = function (startValue, endValue, gradient) {
+            return BABYLON.Size.Lerp(startValue, endValue, gradient);
+        };
         Animation.prototype.color3InterpolateFunction = function (startValue, endValue, gradient) {
             return BABYLON.Color3.Lerp(startValue, endValue, gradient);
         };
@@ -342,6 +348,15 @@ var BABYLON;
                                 case Animation.ANIMATIONLOOPMODE_RELATIVE:
                                     return this.vector2InterpolateFunction(startValue, endValue, gradient).add(offsetValue.scale(repeatCount));
                             }
+                        // Size
+                        case Animation.ANIMATIONTYPE_SIZE:
+                            switch (loopMode) {
+                                case Animation.ANIMATIONLOOPMODE_CYCLE:
+                                case Animation.ANIMATIONLOOPMODE_CONSTANT:
+                                    return this.sizeInterpolateFunction(startValue, endValue, gradient);
+                                case Animation.ANIMATIONLOOPMODE_RELATIVE:
+                                    return this.sizeInterpolateFunction(startValue, endValue, gradient).add(offsetValue.scale(repeatCount));
+                            }
                         // Color3
                         case Animation.ANIMATIONTYPE_COLOR3:
                             switch (loopMode) {
@@ -481,6 +496,9 @@ var BABYLON;
                             // Vector2
                             case Animation.ANIMATIONTYPE_VECTOR2:
                                 this._offsetsCache[keyOffset] = toValue.subtract(fromValue);
+                            // Size
+                            case Animation.ANIMATIONTYPE_SIZE:
+                                this._offsetsCache[keyOffset] = toValue.subtract(fromValue);
                             // Color3
                             case Animation.ANIMATIONTYPE_COLOR3:
                                 this._offsetsCache[keyOffset] = toValue.subtract(fromValue);
@@ -511,6 +529,10 @@ var BABYLON;
                     case Animation.ANIMATIONTYPE_VECTOR2:
                         offsetValue = BABYLON.Vector2.Zero();
                         break;
+                    // Size
+                    case Animation.ANIMATIONTYPE_SIZE:
+                        offsetValue = BABYLON.Size.Zero();
+                        break;
                     // Color3
                     case Animation.ANIMATIONTYPE_COLOR3:
                         offsetValue = BABYLON.Color3.Black();
@@ -604,6 +626,13 @@ var BABYLON;
             enumerable: true,
             configurable: true
         });
+        Object.defineProperty(Animation, "ANIMATIONTYPE_SIZE", {
+            get: function () {
+                return Animation._ANIMATIONTYPE_SIZE;
+            },
+            enumerable: true,
+            configurable: true
+        });
         Object.defineProperty(Animation, "ANIMATIONTYPE_QUATERNION", {
             get: function () {
                 return Animation._ANIMATIONTYPE_QUATERNION;
@@ -702,6 +731,7 @@ var BABYLON;
         Animation._ANIMATIONTYPE_MATRIX = 3;
         Animation._ANIMATIONTYPE_COLOR3 = 4;
         Animation._ANIMATIONTYPE_VECTOR2 = 5;
+        Animation._ANIMATIONTYPE_SIZE = 6;
         Animation._ANIMATIONLOOPMODE_RELATIVE = 0;
         Animation._ANIMATIONLOOPMODE_CYCLE = 1;
         Animation._ANIMATIONLOOPMODE_CONSTANT = 2;

+ 27 - 0
src/Animations/babylon.animation.ts

@@ -124,6 +124,8 @@
                 dataType = Animation.ANIMATIONTYPE_VECTOR2;
             } else if (from instanceof Color3) {
                 dataType = Animation.ANIMATIONTYPE_COLOR3;
+            } else if (from instanceof Size) {
+                dataType = Animation.ANIMATIONTYPE_SIZE;
             }
 
             if (dataType == undefined) {
@@ -293,6 +295,10 @@
             return Vector2.Lerp(startValue, endValue, gradient);
         }
 
+        public sizeInterpolateFunction(startValue: Size, endValue: Size, gradient: number): Size {
+            return Size.Lerp(startValue, endValue, gradient);
+        }
+
         public color3InterpolateFunction(startValue: Color3, endValue: Color3, gradient: number): Color3 {
             return Color3.Lerp(startValue, endValue, gradient);
         }
@@ -405,6 +411,15 @@
                                 case Animation.ANIMATIONLOOPMODE_RELATIVE:
                                     return this.vector2InterpolateFunction(startValue, endValue, gradient).add(offsetValue.scale(repeatCount));
                             }
+                        // Size
+                        case Animation.ANIMATIONTYPE_SIZE:
+                            switch (loopMode) {
+                                case Animation.ANIMATIONLOOPMODE_CYCLE:
+                                case Animation.ANIMATIONLOOPMODE_CONSTANT:
+                                    return this.sizeInterpolateFunction(startValue, endValue, gradient);
+                                case Animation.ANIMATIONLOOPMODE_RELATIVE:
+                                    return this.sizeInterpolateFunction(startValue, endValue, gradient).add(offsetValue.scale(repeatCount));
+                            }
                         // Color3
                         case Animation.ANIMATIONTYPE_COLOR3:
                             switch (loopMode) {
@@ -553,6 +568,9 @@
                             // Vector2
                             case Animation.ANIMATIONTYPE_VECTOR2:
                                 this._offsetsCache[keyOffset] = toValue.subtract(fromValue);
+                            // Size
+                            case Animation.ANIMATIONTYPE_SIZE:
+                                this._offsetsCache[keyOffset] = toValue.subtract(fromValue);
                             // Color3
                             case Animation.ANIMATIONTYPE_COLOR3:
                                 this._offsetsCache[keyOffset] = toValue.subtract(fromValue);
@@ -586,6 +604,10 @@
                     case Animation.ANIMATIONTYPE_VECTOR2:
                         offsetValue = Vector2.Zero();
                         break;
+                    // Size
+                    case Animation.ANIMATIONTYPE_SIZE:
+                        offsetValue = Size.Zero();
+                        break;
                     // Color3
                     case Animation.ANIMATIONTYPE_COLOR3:
                         offsetValue = Color3.Black();
@@ -676,6 +698,7 @@
         private static _ANIMATIONTYPE_MATRIX = 3;
         private static _ANIMATIONTYPE_COLOR3 = 4;
         private static _ANIMATIONTYPE_VECTOR2 = 5;
+        private static _ANIMATIONTYPE_SIZE = 6;
         private static _ANIMATIONLOOPMODE_RELATIVE = 0;
         private static _ANIMATIONLOOPMODE_CYCLE = 1;
         private static _ANIMATIONLOOPMODE_CONSTANT = 2;
@@ -692,6 +715,10 @@
             return Animation._ANIMATIONTYPE_VECTOR2;
         }
 
+        public static get ANIMATIONTYPE_SIZE(): number {
+            return Animation._ANIMATIONTYPE_SIZE;
+        }
+
         public static get ANIMATIONTYPE_QUATERNION(): number {
             return Animation._ANIMATIONTYPE_QUATERNION;
         }

+ 13 - 13
src/Animations/babylon.easing.js

@@ -58,7 +58,7 @@ var BABYLON;
         EasingFunction._EASINGMODE_EASEOUT = 1;
         EasingFunction._EASINGMODE_EASEINOUT = 2;
         return EasingFunction;
-    }());
+    })();
     BABYLON.EasingFunction = EasingFunction;
     var CircleEase = (function (_super) {
         __extends(CircleEase, _super);
@@ -70,7 +70,7 @@ var BABYLON;
             return (1.0 - Math.sqrt(1.0 - (gradient * gradient)));
         };
         return CircleEase;
-    }(EasingFunction));
+    })(EasingFunction);
     BABYLON.CircleEase = CircleEase;
     var BackEase = (function (_super) {
         __extends(BackEase, _super);
@@ -84,7 +84,7 @@ var BABYLON;
             return (Math.pow(gradient, 3.0) - ((gradient * num) * Math.sin(3.1415926535897931 * gradient)));
         };
         return BackEase;
-    }(EasingFunction));
+    })(EasingFunction);
     BABYLON.BackEase = BackEase;
     var BounceEase = (function (_super) {
         __extends(BounceEase, _super);
@@ -116,7 +116,7 @@ var BABYLON;
             return (((-Math.pow(1.0 / bounciness, y - num3) / (num2 * num2)) * (num6 - num2)) * (num6 + num2));
         };
         return BounceEase;
-    }(EasingFunction));
+    })(EasingFunction);
     BABYLON.BounceEase = BounceEase;
     var CubicEase = (function (_super) {
         __extends(CubicEase, _super);
@@ -127,7 +127,7 @@ var BABYLON;
             return (gradient * gradient * gradient);
         };
         return CubicEase;
-    }(EasingFunction));
+    })(EasingFunction);
     BABYLON.CubicEase = CubicEase;
     var ElasticEase = (function (_super) {
         __extends(ElasticEase, _super);
@@ -151,7 +151,7 @@ var BABYLON;
             return (num2 * Math.sin(((6.2831853071795862 * num3) + 1.5707963267948966) * gradient));
         };
         return ElasticEase;
-    }(EasingFunction));
+    })(EasingFunction);
     BABYLON.ElasticEase = ElasticEase;
     var ExponentialEase = (function (_super) {
         __extends(ExponentialEase, _super);
@@ -167,7 +167,7 @@ var BABYLON;
             return ((Math.exp(this.exponent * gradient) - 1.0) / (Math.exp(this.exponent) - 1.0));
         };
         return ExponentialEase;
-    }(EasingFunction));
+    })(EasingFunction);
     BABYLON.ExponentialEase = ExponentialEase;
     var PowerEase = (function (_super) {
         __extends(PowerEase, _super);
@@ -181,7 +181,7 @@ var BABYLON;
             return Math.pow(gradient, y);
         };
         return PowerEase;
-    }(EasingFunction));
+    })(EasingFunction);
     BABYLON.PowerEase = PowerEase;
     var QuadraticEase = (function (_super) {
         __extends(QuadraticEase, _super);
@@ -192,7 +192,7 @@ var BABYLON;
             return (gradient * gradient);
         };
         return QuadraticEase;
-    }(EasingFunction));
+    })(EasingFunction);
     BABYLON.QuadraticEase = QuadraticEase;
     var QuarticEase = (function (_super) {
         __extends(QuarticEase, _super);
@@ -203,7 +203,7 @@ var BABYLON;
             return (gradient * gradient * gradient * gradient);
         };
         return QuarticEase;
-    }(EasingFunction));
+    })(EasingFunction);
     BABYLON.QuarticEase = QuarticEase;
     var QuinticEase = (function (_super) {
         __extends(QuinticEase, _super);
@@ -214,7 +214,7 @@ var BABYLON;
             return (gradient * gradient * gradient * gradient * gradient);
         };
         return QuinticEase;
-    }(EasingFunction));
+    })(EasingFunction);
     BABYLON.QuinticEase = QuinticEase;
     var SineEase = (function (_super) {
         __extends(SineEase, _super);
@@ -225,7 +225,7 @@ var BABYLON;
             return (1.0 - Math.sin(1.5707963267948966 * (1.0 - gradient)));
         };
         return SineEase;
-    }(EasingFunction));
+    })(EasingFunction);
     BABYLON.SineEase = SineEase;
     var BezierCurveEase = (function (_super) {
         __extends(BezierCurveEase, _super);
@@ -244,6 +244,6 @@ var BABYLON;
             return BABYLON.BezierCurve.interpolate(gradient, this.x1, this.y1, this.x2, this.y2);
         };
         return BezierCurveEase;
-    }(EasingFunction));
+    })(EasingFunction);
     BABYLON.BezierCurveEase = BezierCurveEase;
 })(BABYLON || (BABYLON = {}));

+ 1 - 1
src/Audio/babylon.analyser.js

@@ -106,6 +106,6 @@ var BABYLON;
             }
         };
         return Analyser;
-    }());
+    })();
     BABYLON.Analyser = Analyser;
 })(BABYLON || (BABYLON = {}));

+ 1 - 1
src/Audio/babylon.audioEngine.js

@@ -101,6 +101,6 @@ var BABYLON;
             }
         };
         return AudioEngine;
-    }());
+    })();
     BABYLON.AudioEngine = AudioEngine;
 })(BABYLON || (BABYLON = {}));

+ 1 - 1
src/Audio/babylon.sound.js

@@ -547,6 +547,6 @@ var BABYLON;
             return newSound;
         };
         return Sound;
-    }());
+    })();
     BABYLON.Sound = Sound;
 })(BABYLON || (BABYLON = {}));

+ 1 - 1
src/Audio/babylon.soundtrack.js

@@ -96,6 +96,6 @@ var BABYLON;
             }
         };
         return SoundTrack;
-    }());
+    })();
     BABYLON.SoundTrack = SoundTrack;
 })(BABYLON || (BABYLON = {}));

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

@@ -124,6 +124,6 @@ var BABYLON;
             return true;
         };
         return Bone;
-    }(BABYLON.Node));
+    })(BABYLON.Node);
     BABYLON.Bone = Bone;
 })(BABYLON || (BABYLON = {}));

+ 1 - 1
src/Bones/babylon.skeleton.js

@@ -329,6 +329,6 @@ var BABYLON;
             return skeleton;
         };
         return Skeleton;
-    }());
+    })();
     BABYLON.Skeleton = Skeleton;
 })(BABYLON || (BABYLON = {}));

+ 1 - 1
src/Cameras/Inputs/babylon.arcrotatecamera.input.gamepad.js

@@ -65,7 +65,7 @@ var BABYLON;
             BABYLON.serialize()
         ], ArcRotateCameraGamepadInput.prototype, "gamepadMoveSensibility", void 0);
         return ArcRotateCameraGamepadInput;
-    }());
+    })();
     BABYLON.ArcRotateCameraGamepadInput = ArcRotateCameraGamepadInput;
     BABYLON.CameraInputTypes["ArcRotateCameraGamepadInput"] = ArcRotateCameraGamepadInput;
 })(BABYLON || (BABYLON = {}));

+ 1 - 1
src/Cameras/Inputs/babylon.arcrotatecamera.input.keyboard.js

@@ -107,7 +107,7 @@ var BABYLON;
             BABYLON.serialize()
         ], ArcRotateCameraKeyboardMoveInput.prototype, "keysRight", void 0);
         return ArcRotateCameraKeyboardMoveInput;
-    }());
+    })();
     BABYLON.ArcRotateCameraKeyboardMoveInput = ArcRotateCameraKeyboardMoveInput;
     BABYLON.CameraInputTypes["ArcRotateCameraKeyboardMoveInput"] = ArcRotateCameraKeyboardMoveInput;
 })(BABYLON || (BABYLON = {}));

+ 1 - 1
src/Cameras/Inputs/babylon.arcrotatecamera.input.mousewheel.js

@@ -51,7 +51,7 @@ var BABYLON;
             BABYLON.serialize()
         ], ArcRotateCameraMouseWheelInput.prototype, "wheelPrecision", void 0);
         return ArcRotateCameraMouseWheelInput;
-    }());
+    })();
     BABYLON.ArcRotateCameraMouseWheelInput = ArcRotateCameraMouseWheelInput;
     BABYLON.CameraInputTypes["ArcRotateCameraMouseWheelInput"] = ArcRotateCameraMouseWheelInput;
 })(BABYLON || (BABYLON = {}));

+ 1 - 1
src/Cameras/Inputs/babylon.arcrotatecamera.input.pointers.js

@@ -213,7 +213,7 @@ var BABYLON;
             BABYLON.serialize()
         ], ArcRotateCameraPointersInput.prototype, "panningSensibility", void 0);
         return ArcRotateCameraPointersInput;
-    }());
+    })();
     BABYLON.ArcRotateCameraPointersInput = ArcRotateCameraPointersInput;
     BABYLON.CameraInputTypes["ArcRotateCameraPointersInput"] = ArcRotateCameraPointersInput;
 })(BABYLON || (BABYLON = {}));

+ 1 - 1
src/Cameras/Inputs/babylon.arcrotatecamera.input.vrdeviceorientation.js

@@ -42,7 +42,7 @@ var BABYLON;
             return "VRDeviceOrientation";
         };
         return ArcRotateCameraVRDeviceOrientationInput;
-    }());
+    })();
     BABYLON.ArcRotateCameraVRDeviceOrientationInput = ArcRotateCameraVRDeviceOrientationInput;
     BABYLON.CameraInputTypes["ArcRotateCameraVRDeviceOrientationInput"] = ArcRotateCameraVRDeviceOrientationInput;
 })(BABYLON || (BABYLON = {}));

+ 41 - 55
src/Cameras/Inputs/babylon.freecamera.input.deviceorientation.js

@@ -1,61 +1,53 @@
-var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
-    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
-    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
-    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
-    return c > 3 && r && Object.defineProperty(target, key, r), r;
-};
 var BABYLON;
 (function (BABYLON) {
     var FreeCameraDeviceOrientationInput = (function () {
         function FreeCameraDeviceOrientationInput() {
-            this._offsetX = null;
-            this._offsetY = null;
-            this._orientationGamma = 0;
-            this._orientationBeta = 0;
-            this._initialOrientationGamma = 0;
-            this._initialOrientationBeta = 0;
-            this.angularSensibility = 10000.0;
-            this.moveSensibility = 50.0;
-            this._resetOrientationGamma = this.resetOrientationGamma.bind(this);
-            this._orientationChanged = this.orientationChanged.bind(this);
+            var _this = this;
+            this._screenOrientationAngle = 0;
+            this._screenQuaternion = new BABYLON.Quaternion();
+            this._alpha = 0;
+            this._beta = 0;
+            this._gamma = 0;
+            this._orientationChanged = function () {
+                _this._screenOrientationAngle = (window.orientation !== undefined ? +window.orientation : (window.screen.orientation && window.screen.orientation['angle'] ? window.screen.orientation.angle : 0));
+                _this._screenOrientationAngle = -BABYLON.Tools.ToRadians(_this._screenOrientationAngle / 2);
+                _this._screenQuaternion.copyFromFloats(0, Math.sin(_this._screenOrientationAngle), 0, Math.cos(_this._screenOrientationAngle));
+            };
+            this._deviceOrientation = function (evt) {
+                _this._alpha = evt.alpha;
+                _this._beta = evt.beta;
+                _this._gamma = evt.gamma;
+            };
+            this._constantTranform = new BABYLON.Quaternion(-Math.sqrt(0.5), 0, 0, Math.sqrt(0.5));
+            this._orientationChanged();
         }
+        Object.defineProperty(FreeCameraDeviceOrientationInput.prototype, "camera", {
+            get: function () {
+                return this._camera;
+            },
+            set: function (camera) {
+                this._camera = camera;
+                if (!this._camera.rotationQuaternion)
+                    this._camera.rotationQuaternion = new BABYLON.Quaternion();
+            },
+            enumerable: true,
+            configurable: true
+        });
         FreeCameraDeviceOrientationInput.prototype.attachControl = function (element, noPreventDefault) {
-            window.addEventListener("resize", this._resetOrientationGamma, false);
-            window.addEventListener("deviceorientation", this._orientationChanged);
-        };
-        FreeCameraDeviceOrientationInput.prototype.resetOrientationGamma = function () {
-            this._initialOrientationGamma = null;
-        };
-        FreeCameraDeviceOrientationInput.prototype.orientationChanged = function (evt) {
-            if (!this._initialOrientationGamma) {
-                this._initialOrientationGamma = evt.gamma;
-                this._initialOrientationBeta = evt.beta;
-            }
-            this._orientationGamma = evt.gamma;
-            this._orientationBeta = evt.beta;
-            this._offsetY = (this._initialOrientationBeta - this._orientationBeta);
-            this._offsetX = (this._initialOrientationGamma - this._orientationGamma);
+            window.addEventListener("orientationchange", this._orientationChanged);
+            window.addEventListener("deviceorientation", this._deviceOrientation);
         };
         FreeCameraDeviceOrientationInput.prototype.detachControl = function (element) {
-            window.removeEventListener("resize", this._resetOrientationGamma);
-            window.removeEventListener("deviceorientation", this._orientationChanged);
-            this._orientationGamma = 0;
-            this._orientationBeta = 0;
-            this._initialOrientationGamma = 0;
-            this._initialOrientationBeta = 0;
-            this._offsetX = null;
-            this._offsetY = null;
+            window.removeEventListener("orientationchange", this._orientationChanged);
+            window.removeEventListener("deviceorientation", this._deviceOrientation);
         };
         FreeCameraDeviceOrientationInput.prototype.checkInputs = function () {
-            if (!this._offsetX) {
-                return;
-            }
-            var camera = this.camera;
-            camera.cameraRotation.y -= this._offsetX / this.angularSensibility;
-            var speed = camera._computeLocalCameraSpeed();
-            var direction = new BABYLON.Vector3(0, 0, speed * this._offsetY / this.moveSensibility);
-            BABYLON.Matrix.RotationYawPitchRollToRef(camera.rotation.y, camera.rotation.x, 0, camera._cameraRotationMatrix);
-            camera.cameraDirection.addInPlace(BABYLON.Vector3.TransformCoordinates(direction, camera._cameraRotationMatrix));
+            BABYLON.Quaternion.RotationYawPitchRollToRef(BABYLON.Tools.ToRadians(this._alpha), BABYLON.Tools.ToRadians(this._beta), -BABYLON.Tools.ToRadians(this._gamma), this.camera.rotationQuaternion);
+            this._camera.rotationQuaternion.multiplyInPlace(this._screenQuaternion);
+            this._camera.rotationQuaternion.multiplyInPlace(this._constantTranform);
+            //Mirror on XY Plane
+            this._camera.rotationQuaternion.z *= -1;
+            this._camera.rotationQuaternion.w *= -1;
         };
         FreeCameraDeviceOrientationInput.prototype.getTypeName = function () {
             return "FreeCameraDeviceOrientationInput";
@@ -63,14 +55,8 @@ var BABYLON;
         FreeCameraDeviceOrientationInput.prototype.getSimpleName = function () {
             return "deviceOrientation";
         };
-        __decorate([
-            BABYLON.serialize()
-        ], FreeCameraDeviceOrientationInput.prototype, "angularSensibility", void 0);
-        __decorate([
-            BABYLON.serialize()
-        ], FreeCameraDeviceOrientationInput.prototype, "moveSensibility", void 0);
         return FreeCameraDeviceOrientationInput;
-    }());
+    })();
     BABYLON.FreeCameraDeviceOrientationInput = FreeCameraDeviceOrientationInput;
     BABYLON.CameraInputTypes["FreeCameraDeviceOrientationInput"] = FreeCameraDeviceOrientationInput;
 })(BABYLON || (BABYLON = {}));

+ 39 - 54
src/Cameras/Inputs/babylon.freecamera.input.deviceorientation.ts

@@ -1,84 +1,69 @@
 module BABYLON {
     export class FreeCameraDeviceOrientationInput implements ICameraInput<FreeCamera> {
-        camera: FreeCamera;
+        private _camera: FreeCamera;
 
-        private _offsetX: number = null;
-        private _offsetY: number = null;
-        private _orientationGamma: number = 0;
-        private _orientationBeta: number = 0;
-        private _initialOrientationGamma: number = 0;
-        private _initialOrientationBeta: number = 0;
-        private _orientationChanged: (e: DeviceOrientationEvent) => any;
-        private _resetOrientationGamma: () => any;
+        private _screenOrientationAngle: number = 0;
 
-        @serialize()
-        public angularSensibility: number = 10000.0;
+        private _constantTranform: Quaternion;
+        private _screenQuaternion: Quaternion = new Quaternion();
 
-        @serialize()
-        public moveSensibility: number = 50.0;
+        private _alpha: number = 0;
+        private _beta: number = 0;
+        private _gamma: number = 0;
 
         constructor() {
-            this._resetOrientationGamma = this.resetOrientationGamma.bind(this);
-            this._orientationChanged = this.orientationChanged.bind(this);
+            this._constantTranform = new Quaternion(- Math.sqrt(0.5), 0, 0, Math.sqrt(0.5));
+            this._orientationChanged();
         }
 
-        attachControl(element : HTMLElement, noPreventDefault?: boolean) {
-            window.addEventListener("resize", this._resetOrientationGamma, false);
-            window.addEventListener("deviceorientation", this._orientationChanged);
+        public get camera(): FreeCamera {
+            return this._camera;
         }
 
-        resetOrientationGamma() {
-            this._initialOrientationGamma = null;
+        public set camera(camera: FreeCamera) {
+            this._camera = camera;
+            if (!this._camera.rotationQuaternion) this._camera.rotationQuaternion = new Quaternion();
         }
 
-        orientationChanged(evt) {
-            if (!this._initialOrientationGamma) {
-                this._initialOrientationGamma = evt.gamma;
-                this._initialOrientationBeta = evt.beta;
-            }
+        attachControl(element: HTMLElement, noPreventDefault?: boolean) {
+            window.addEventListener("orientationchange", this._orientationChanged);
+            window.addEventListener("deviceorientation", this._deviceOrientation);
+        }
 
-            this._orientationGamma = evt.gamma;
-            this._orientationBeta = evt.beta;
+        private _orientationChanged = () => {
+            this._screenOrientationAngle = (window.orientation !== undefined ? +window.orientation : (window.screen.orientation && window.screen.orientation['angle'] ? (<any>window.screen.orientation).angle : 0));
+            this._screenOrientationAngle = -Tools.ToRadians(this._screenOrientationAngle / 2);
+            this._screenQuaternion.copyFromFloats(0, Math.sin(this._screenOrientationAngle), 0, Math.cos(this._screenOrientationAngle));
+        }
 
-            this._offsetY = (this._initialOrientationBeta - this._orientationBeta);
-            this._offsetX = (this._initialOrientationGamma - this._orientationGamma);
+        private _deviceOrientation = (evt: DeviceOrientationEvent) => {
+            this._alpha = evt.alpha;
+            this._beta = evt.beta;
+            this._gamma = evt.gamma;
         }
 
-        detachControl(element : HTMLElement) {
-            window.removeEventListener("resize", this._resetOrientationGamma);
-            window.removeEventListener("deviceorientation", this._orientationChanged);
-            
-            this._orientationGamma = 0;
-            this._orientationBeta = 0;
-            this._initialOrientationGamma = 0;
-            this._initialOrientationBeta = 0;
-            this._offsetX = null;
-            this._offsetY = null;
+        detachControl(element: HTMLElement) {
+            window.removeEventListener("orientationchange", this._orientationChanged);
+            window.removeEventListener("deviceorientation", this._deviceOrientation);
         }
 
         public checkInputs() {
-            if (!this._offsetX) {
-                return;
-            }
-            
-            var camera = this.camera;
-            camera.cameraRotation.y -= this._offsetX / this.angularSensibility;
-
-            var speed = camera._computeLocalCameraSpeed();
-            var direction = new Vector3(0, 0, speed * this._offsetY / this.moveSensibility);
-
-            Matrix.RotationYawPitchRollToRef(camera.rotation.y, camera.rotation.x, 0, camera._cameraRotationMatrix);
-            camera.cameraDirection.addInPlace(Vector3.TransformCoordinates(direction, camera._cameraRotationMatrix));
+            Quaternion.RotationYawPitchRollToRef(BABYLON.Tools.ToRadians(this._alpha), BABYLON.Tools.ToRadians(this._beta), -BABYLON.Tools.ToRadians(this._gamma), this.camera.rotationQuaternion)
+            this._camera.rotationQuaternion.multiplyInPlace(this._screenQuaternion);
+            this._camera.rotationQuaternion.multiplyInPlace(this._constantTranform);
+            //Mirror on XY Plane
+            this._camera.rotationQuaternion.z *= -1;
+            this._camera.rotationQuaternion.w *= -1;
         }
 
         getTypeName(): string {
             return "FreeCameraDeviceOrientationInput";
         }
-        
-        getSimpleName(){
+
+        getSimpleName() {
             return "deviceOrientation";
         }
     }
-    
+
     CameraInputTypes["FreeCameraDeviceOrientationInput"] = FreeCameraDeviceOrientationInput;
 }

+ 1 - 1
src/Cameras/Inputs/babylon.freecamera.input.gamepad.js

@@ -60,7 +60,7 @@ var BABYLON;
             BABYLON.serialize()
         ], FreeCameraGamepadInput.prototype, "gamepadMoveSensibility", void 0);
         return FreeCameraGamepadInput;
-    }());
+    })();
     BABYLON.FreeCameraGamepadInput = FreeCameraGamepadInput;
     BABYLON.CameraInputTypes["FreeCameraGamepadInput"] = FreeCameraGamepadInput;
 })(BABYLON || (BABYLON = {}));

+ 1 - 1
src/Cameras/Inputs/babylon.freecamera.input.keyboard.js

@@ -111,7 +111,7 @@ var BABYLON;
             BABYLON.serialize()
         ], FreeCameraKeyboardMoveInput.prototype, "keysRight", void 0);
         return FreeCameraKeyboardMoveInput;
-    }());
+    })();
     BABYLON.FreeCameraKeyboardMoveInput = FreeCameraKeyboardMoveInput;
     BABYLON.CameraInputTypes["FreeCameraKeyboardMoveInput"] = FreeCameraKeyboardMoveInput;
 })(BABYLON || (BABYLON = {}));

+ 1 - 1
src/Cameras/Inputs/babylon.freecamera.input.mouse.js

@@ -92,7 +92,7 @@ var BABYLON;
             BABYLON.serialize()
         ], FreeCameraMouseInput.prototype, "angularSensibility", void 0);
         return FreeCameraMouseInput;
-    }());
+    })();
     BABYLON.FreeCameraMouseInput = FreeCameraMouseInput;
     BABYLON.CameraInputTypes["FreeCameraMouseInput"] = FreeCameraMouseInput;
 })(BABYLON || (BABYLON = {}));

+ 1 - 1
src/Cameras/Inputs/babylon.freecamera.input.touch.js

@@ -116,7 +116,7 @@ var BABYLON;
             BABYLON.serialize()
         ], FreeCameraTouchInput.prototype, "touchMoveSensibility", void 0);
         return FreeCameraTouchInput;
-    }());
+    })();
     BABYLON.FreeCameraTouchInput = FreeCameraTouchInput;
     BABYLON.CameraInputTypes["FreeCameraTouchInput"] = FreeCameraTouchInput;
 })(BABYLON || (BABYLON = {}));

+ 1 - 1
src/Cameras/Inputs/babylon.freecamera.input.virtualjoystick.js

@@ -48,7 +48,7 @@ var BABYLON;
             return "virtualJoystick";
         };
         return FreeCameraVirtualJoystickInput;
-    }());
+    })();
     BABYLON.FreeCameraVirtualJoystickInput = FreeCameraVirtualJoystickInput;
     BABYLON.CameraInputTypes["FreeCameraVirtualJoystickInput"] = FreeCameraVirtualJoystickInput;
 })(BABYLON || (BABYLON = {}));

+ 1 - 1
src/Cameras/Inputs/babylon.freecamera.input.vrdeviceorientation.js

@@ -47,7 +47,7 @@ var BABYLON;
             return "VRDeviceOrientation";
         };
         return FreeCameraVRDeviceOrientationInput;
-    }());
+    })();
     BABYLON.FreeCameraVRDeviceOrientationInput = FreeCameraVRDeviceOrientationInput;
     BABYLON.CameraInputTypes["FreeCameraVRDeviceOrientationInput"] = FreeCameraVRDeviceOrientationInput;
 })(BABYLON || (BABYLON = {}));

+ 1 - 1
src/Cameras/VR/babylon.vrCameraMetrics.js

@@ -67,6 +67,6 @@ var BABYLON;
             return result;
         };
         return VRCameraMetrics;
-    }());
+    })();
     BABYLON.VRCameraMetrics = VRCameraMetrics;
 })(BABYLON || (BABYLON = {}));

+ 4 - 3
src/Cameras/VR/babylon.vrDeviceOrientationCamera.js

@@ -10,16 +10,17 @@ var BABYLON;
         function VRDeviceOrientationFreeCamera(name, position, scene, compensateDistortion) {
             if (compensateDistortion === void 0) { compensateDistortion = true; }
             _super.call(this, name, position, scene);
+            this.rotationQuaternion = new BABYLON.Quaternion();
             var metrics = BABYLON.VRCameraMetrics.GetDefault();
             metrics.compensateDistortion = compensateDistortion;
             this.setCameraRigMode(BABYLON.Camera.RIG_MODE_VR, { vrCameraMetrics: metrics });
-            this.inputs.addVRDeviceOrientation();
+            this.inputs.addDeviceOrientation();
         }
         VRDeviceOrientationFreeCamera.prototype.getTypeName = function () {
             return "VRDeviceOrientationFreeCamera";
         };
         return VRDeviceOrientationFreeCamera;
-    }(BABYLON.FreeCamera));
+    })(BABYLON.FreeCamera);
     BABYLON.VRDeviceOrientationFreeCamera = VRDeviceOrientationFreeCamera;
     var VRDeviceOrientationArcRotateCamera = (function (_super) {
         __extends(VRDeviceOrientationArcRotateCamera, _super);
@@ -35,6 +36,6 @@ var BABYLON;
             return "VRDeviceOrientationArcRotateCamera";
         };
         return VRDeviceOrientationArcRotateCamera;
-    }(BABYLON.ArcRotateCamera));
+    })(BABYLON.ArcRotateCamera);
     BABYLON.VRDeviceOrientationArcRotateCamera = VRDeviceOrientationArcRotateCamera;
 })(BABYLON || (BABYLON = {}));

+ 3 - 1
src/Cameras/VR/babylon.vrDeviceOrientationCamera.ts

@@ -4,11 +4,13 @@ module BABYLON {
         constructor(name: string, position: Vector3, scene: Scene, compensateDistortion = true) {
             super(name, position, scene);
 
+            this.rotationQuaternion = new Quaternion();
+
             var metrics = VRCameraMetrics.GetDefault();
             metrics.compensateDistortion = compensateDistortion;
             this.setCameraRigMode(Camera.RIG_MODE_VR, { vrCameraMetrics: metrics });
 
-            this.inputs.addVRDeviceOrientation();
+            this.inputs.addDeviceOrientation();
         }
 
 

+ 1 - 1
src/Cameras/VR/babylon.webVRCamera.js

@@ -72,6 +72,6 @@ var BABYLON;
             return "WebVRFreeCamera";
         };
         return WebVRFreeCamera;
-    }(BABYLON.FreeCamera));
+    })(BABYLON.FreeCamera);
     BABYLON.WebVRFreeCamera = WebVRFreeCamera;
 })(BABYLON || (BABYLON = {}));

+ 1 - 1
src/Cameras/babylon.arcRotateCamera.js

@@ -541,6 +541,6 @@ var BABYLON;
             BABYLON.serialize()
         ], ArcRotateCamera.prototype, "allowUpsideDown", void 0);
         return ArcRotateCamera;
-    }(BABYLON.TargetCamera));
+    })(BABYLON.TargetCamera);
     BABYLON.ArcRotateCamera = ArcRotateCamera;
 })(BABYLON || (BABYLON = {}));

+ 1 - 1
src/Cameras/babylon.arcRotateCameraInputsManager.js

@@ -31,6 +31,6 @@ var BABYLON;
             return this;
         };
         return ArcRotateCameraInputsManager;
-    }(BABYLON.CameraInputsManager));
+    })(BABYLON.CameraInputsManager);
     BABYLON.ArcRotateCameraInputsManager = ArcRotateCameraInputsManager;
 })(BABYLON || (BABYLON = {}));

+ 1 - 1
src/Cameras/babylon.camera.js

@@ -612,6 +612,6 @@ var BABYLON;
             BABYLON.serialize()
         ], Camera.prototype, "isStereoscopicSideBySide", void 0);
         return Camera;
-    }(BABYLON.Node));
+    })(BABYLON.Node);
     BABYLON.Camera = Camera;
 })(BABYLON || (BABYLON = {}));

+ 1 - 1
src/Cameras/babylon.cameraInputsManager.js

@@ -128,6 +128,6 @@ var BABYLON;
             }
         };
         return CameraInputsManager;
-    }());
+    })();
     BABYLON.CameraInputsManager = CameraInputsManager;
 })(BABYLON || (BABYLON = {}));

+ 3 - 13
src/Cameras/babylon.deviceOrientationCamera.js

@@ -16,28 +16,18 @@ var BABYLON;
         Object.defineProperty(DeviceOrientationCamera.prototype, "angularSensibility", {
             //-- Begin properties for backward compatibility for inputs
             get: function () {
-                var deviceOrientation = this.inputs.attached["deviceOrientation"];
-                if (deviceOrientation)
-                    return deviceOrientation.angularSensibility;
+                return 0;
             },
             set: function (value) {
-                var deviceOrientation = this.inputs.attached["deviceOrientation"];
-                if (deviceOrientation)
-                    deviceOrientation.angularSensibility = value;
             },
             enumerable: true,
             configurable: true
         });
         Object.defineProperty(DeviceOrientationCamera.prototype, "moveSensibility", {
             get: function () {
-                var deviceOrientation = this.inputs.attached["deviceOrientation"];
-                if (deviceOrientation)
-                    return deviceOrientation.moveSensibility;
+                return 0;
             },
             set: function (value) {
-                var deviceOrientation = this.inputs.attached["deviceOrientation"];
-                if (deviceOrientation)
-                    deviceOrientation.moveSensibility = value;
             },
             enumerable: true,
             configurable: true
@@ -46,6 +36,6 @@ var BABYLON;
             return "DeviceOrientationCamera";
         };
         return DeviceOrientationCamera;
-    }(BABYLON.FreeCamera));
+    })(BABYLON.FreeCamera);
     BABYLON.DeviceOrientationCamera = DeviceOrientationCamera;
 })(BABYLON || (BABYLON = {}));

+ 2 - 12
src/Cameras/babylon.deviceOrientationCamera.ts

@@ -3,27 +3,17 @@ module BABYLON {
     export class DeviceOrientationCamera extends FreeCamera {
         //-- Begin properties for backward compatibility for inputs
         public get angularSensibility() {
-            var deviceOrientation = <FreeCameraDeviceOrientationInput>this.inputs.attached["deviceOrientation"];
-            if (deviceOrientation)
-                return deviceOrientation.angularSensibility;
+            return 0;
         }
         
         public set angularSensibility(value) {
-            var deviceOrientation = <FreeCameraDeviceOrientationInput>this.inputs.attached["deviceOrientation"];
-            if (deviceOrientation)
-                deviceOrientation.angularSensibility = value;
         }
         
         public get moveSensibility() {
-            var deviceOrientation = <FreeCameraDeviceOrientationInput>this.inputs.attached["deviceOrientation"];
-            if (deviceOrientation)
-                return deviceOrientation.moveSensibility;
+                return 0;
         }
         
         public set moveSensibility(value) {
-            var deviceOrientation = <FreeCameraDeviceOrientationInput>this.inputs.attached["deviceOrientation"];
-            if (deviceOrientation)
-                deviceOrientation.moveSensibility = value;
         }
         //-- end properties for backward compatibility for inputs
         

+ 2 - 2
src/Cameras/babylon.followCamera.js

@@ -84,7 +84,7 @@ var BABYLON;
             BABYLON.serializeAsMeshReference("lockedTargetId")
         ], FollowCamera.prototype, "target", void 0);
         return FollowCamera;
-    }(BABYLON.TargetCamera));
+    })(BABYLON.TargetCamera);
     BABYLON.FollowCamera = FollowCamera;
     var ArcFollowCamera = (function (_super) {
         __extends(ArcFollowCamera, _super);
@@ -112,6 +112,6 @@ var BABYLON;
             return "ArcFollowCamera";
         };
         return ArcFollowCamera;
-    }(BABYLON.TargetCamera));
+    })(BABYLON.TargetCamera);
     BABYLON.ArcFollowCamera = ArcFollowCamera;
 })(BABYLON || (BABYLON = {}));

+ 1 - 1
src/Cameras/babylon.freeCamera.js

@@ -180,6 +180,6 @@ var BABYLON;
             BABYLON.serialize()
         ], FreeCamera.prototype, "applyGravity", void 0);
         return FreeCamera;
-    }(BABYLON.TargetCamera));
+    })(BABYLON.TargetCamera);
     BABYLON.FreeCamera = FreeCamera;
 })(BABYLON || (BABYLON = {}));

+ 1 - 1
src/Cameras/babylon.freeCameraInputsManager.js

@@ -40,6 +40,6 @@ var BABYLON;
             return this;
         };
         return FreeCameraInputsManager;
-    }(BABYLON.CameraInputsManager));
+    })(BABYLON.CameraInputsManager);
     BABYLON.FreeCameraInputsManager = FreeCameraInputsManager;
 })(BABYLON || (BABYLON = {}));

+ 1 - 1
src/Cameras/babylon.gamepadCamera.js

@@ -46,6 +46,6 @@ var BABYLON;
             return "GamepadCamera";
         };
         return GamepadCamera;
-    }(BABYLON.UniversalCamera));
+    })(BABYLON.UniversalCamera);
     BABYLON.GamepadCamera = GamepadCamera;
 })(BABYLON || (BABYLON = {}));

+ 8 - 8
src/Cameras/babylon.stereoscopicCameras.js

@@ -16,7 +16,7 @@ var BABYLON;
             return "AnaglyphFreeCamera";
         };
         return AnaglyphFreeCamera;
-    }(BABYLON.FreeCamera));
+    })(BABYLON.FreeCamera);
     BABYLON.AnaglyphFreeCamera = AnaglyphFreeCamera;
     var AnaglyphArcRotateCamera = (function (_super) {
         __extends(AnaglyphArcRotateCamera, _super);
@@ -29,7 +29,7 @@ var BABYLON;
             return "AnaglyphArcRotateCamera";
         };
         return AnaglyphArcRotateCamera;
-    }(BABYLON.ArcRotateCamera));
+    })(BABYLON.ArcRotateCamera);
     BABYLON.AnaglyphArcRotateCamera = AnaglyphArcRotateCamera;
     var AnaglyphGamepadCamera = (function (_super) {
         __extends(AnaglyphGamepadCamera, _super);
@@ -42,7 +42,7 @@ var BABYLON;
             return "AnaglyphGamepadCamera";
         };
         return AnaglyphGamepadCamera;
-    }(BABYLON.GamepadCamera));
+    })(BABYLON.GamepadCamera);
     BABYLON.AnaglyphGamepadCamera = AnaglyphGamepadCamera;
     var AnaglyphUniversalCamera = (function (_super) {
         __extends(AnaglyphUniversalCamera, _super);
@@ -55,7 +55,7 @@ var BABYLON;
             return "AnaglyphUniversalCamera";
         };
         return AnaglyphUniversalCamera;
-    }(BABYLON.UniversalCamera));
+    })(BABYLON.UniversalCamera);
     BABYLON.AnaglyphUniversalCamera = AnaglyphUniversalCamera;
     var StereoscopicFreeCamera = (function (_super) {
         __extends(StereoscopicFreeCamera, _super);
@@ -69,7 +69,7 @@ var BABYLON;
             return "StereoscopicFreeCamera";
         };
         return StereoscopicFreeCamera;
-    }(BABYLON.FreeCamera));
+    })(BABYLON.FreeCamera);
     BABYLON.StereoscopicFreeCamera = StereoscopicFreeCamera;
     var StereoscopicArcRotateCamera = (function (_super) {
         __extends(StereoscopicArcRotateCamera, _super);
@@ -83,7 +83,7 @@ var BABYLON;
             return "StereoscopicArcRotateCamera";
         };
         return StereoscopicArcRotateCamera;
-    }(BABYLON.ArcRotateCamera));
+    })(BABYLON.ArcRotateCamera);
     BABYLON.StereoscopicArcRotateCamera = StereoscopicArcRotateCamera;
     var StereoscopicGamepadCamera = (function (_super) {
         __extends(StereoscopicGamepadCamera, _super);
@@ -97,7 +97,7 @@ var BABYLON;
             return "StereoscopicGamepadCamera";
         };
         return StereoscopicGamepadCamera;
-    }(BABYLON.GamepadCamera));
+    })(BABYLON.GamepadCamera);
     BABYLON.StereoscopicGamepadCamera = StereoscopicGamepadCamera;
     var StereoscopicUniversalCamera = (function (_super) {
         __extends(StereoscopicUniversalCamera, _super);
@@ -111,6 +111,6 @@ var BABYLON;
             return "StereoscopicUniversalCamera";
         };
         return StereoscopicUniversalCamera;
-    }(BABYLON.UniversalCamera));
+    })(BABYLON.UniversalCamera);
     BABYLON.StereoscopicUniversalCamera = StereoscopicUniversalCamera;
 })(BABYLON || (BABYLON = {}));

+ 30 - 19
src/Cameras/babylon.targetCamera.js

@@ -27,6 +27,7 @@ var BABYLON;
             this._cameraTransformMatrix = BABYLON.Matrix.Zero();
             this._cameraRotationMatrix = BABYLON.Matrix.Zero();
             this._referencePoint = new BABYLON.Vector3(0, 0, 1);
+            this._defaultUpVector = new BABYLON.Vector3(0, 1, 0);
             this._transformedReferencePoint = BABYLON.Vector3.Zero();
             this._lookAtTemp = BABYLON.Matrix.Zero();
             this._tempMatrix = BABYLON.Matrix.Zero();
@@ -48,6 +49,7 @@ var BABYLON;
             _super.prototype._initCache.call(this);
             this._cache.lockedTarget = new BABYLON.Vector3(Number.MAX_VALUE, Number.MAX_VALUE, Number.MAX_VALUE);
             this._cache.rotation = new BABYLON.Vector3(Number.MAX_VALUE, Number.MAX_VALUE, Number.MAX_VALUE);
+            this._cache.rotationQuaternion = new BABYLON.Quaternion(Number.MAX_VALUE, Number.MAX_VALUE, Number.MAX_VALUE, Number.MAX_VALUE);
         };
         TargetCamera.prototype._updateCache = function (ignoreParentClass) {
             if (!ignoreParentClass) {
@@ -66,6 +68,8 @@ var BABYLON;
                 }
             }
             this._cache.rotation.copyFrom(this.rotation);
+            if (this.rotationQuaternion)
+                this._cache.rotationQuaternion.copyFrom(this.rotationQuaternion);
         };
         // Synchronized
         TargetCamera.prototype._isSynchronizedViewMatrix = function () {
@@ -74,7 +78,7 @@ var BABYLON;
             }
             var lockedTargetPosition = this._getLockedTargetPosition();
             return (this._cache.lockedTarget ? this._cache.lockedTarget.equals(lockedTargetPosition) : !lockedTargetPosition)
-                && this._cache.rotation.equals(this.rotation);
+                && (this.rotationQuaternion ? this.rotationQuaternion.equals(this._cache.rotationQuaternion) : this._cache.rotation.equals(this.rotation));
         };
         // Methods
         TargetCamera.prototype._computeLocalCameraSpeed = function () {
@@ -84,7 +88,7 @@ var BABYLON;
         // Target
         TargetCamera.prototype.setTarget = function (target) {
             this.upVector.normalize();
-            BABYLON.Matrix.LookAtLHToRef(this.position, target, this.upVector, this._camMatrix);
+            BABYLON.Matrix.LookAtLHToRef(this.position, target, this._defaultUpVector, this._camMatrix);
             this._camMatrix.invert();
             this.rotation.x = Math.atan(this._camMatrix.m[6] / this._camMatrix.m[10]);
             var vDir = target.subtract(this.position);
@@ -94,7 +98,7 @@ var BABYLON;
             else {
                 this.rotation.y = (-Math.atan(vDir.z / vDir.x) - Math.PI / 2.0);
             }
-            this.rotation.z = -Math.acos(BABYLON.Vector3.Dot(new BABYLON.Vector3(0, 1.0, 0), this.upVector));
+            this.rotation.z = 0;
             if (isNaN(this.rotation.x)) {
                 this.rotation.x = 0;
             }
@@ -104,6 +108,9 @@ var BABYLON;
             if (isNaN(this.rotation.z)) {
                 this.rotation.z = 0;
             }
+            if (this.rotationQuaternion) {
+                BABYLON.Quaternion.RotationYawPitchRollToRef(this.rotation.y, this.rotation.x, this.rotation.z, this.rotationQuaternion);
+            }
         };
         TargetCamera.prototype.getTarget = function () {
             return this._currentTarget;
@@ -157,19 +164,20 @@ var BABYLON;
             }
             _super.prototype._checkInputs.call(this);
         };
+        TargetCamera.prototype._updateCameraRotationMatrix = function () {
+            if (this.rotationQuaternion) {
+                this.rotationQuaternion.toRotationMatrix(this._cameraRotationMatrix);
+                //update the up vector!
+                BABYLON.Vector3.TransformNormalToRef(this._defaultUpVector, this._cameraRotationMatrix, this.upVector);
+            }
+            else {
+                BABYLON.Matrix.RotationYawPitchRollToRef(this.rotation.y, this.rotation.x, this.rotation.z, this._cameraRotationMatrix);
+            }
+        };
         TargetCamera.prototype._getViewMatrix = function () {
             if (!this.lockedTarget) {
                 // Compute
-                if (this.upVector.x !== 0 || this.upVector.y !== 1.0 || this.upVector.z !== 0) {
-                    BABYLON.Matrix.LookAtLHToRef(BABYLON.Vector3.Zero(), this._referencePoint, this.upVector, this._lookAtTemp);
-                    BABYLON.Matrix.RotationYawPitchRollToRef(this.rotation.y, this.rotation.x, this.rotation.z, this._cameraRotationMatrix);
-                    this._lookAtTemp.multiplyToRef(this._cameraRotationMatrix, this._tempMatrix);
-                    this._lookAtTemp.invert();
-                    this._tempMatrix.multiplyToRef(this._lookAtTemp, this._cameraRotationMatrix);
-                }
-                else {
-                    BABYLON.Matrix.RotationYawPitchRollToRef(this.rotation.y, this.rotation.x, this.rotation.z, this._cameraRotationMatrix);
-                }
+                this._updateCameraRotationMatrix();
                 BABYLON.Vector3.TransformCoordinatesToRef(this._referencePoint, this._cameraRotationMatrix, this._transformedReferencePoint);
                 // Computing target and final matrix
                 this.position.addToRef(this._transformedReferencePoint, this._currentTarget);
@@ -181,9 +189,9 @@ var BABYLON;
             return this._viewMatrix;
         };
         TargetCamera.prototype._getVRViewMatrix = function () {
-            BABYLON.Matrix.RotationYawPitchRollToRef(this.rotation.y, this.rotation.x, this.rotation.z, this._cameraRotationMatrix);
+            this._updateCameraRotationMatrix();
             BABYLON.Vector3.TransformCoordinatesToRef(this._referencePoint, this._cameraRotationMatrix, this._transformedReferencePoint);
-            BABYLON.Vector3.TransformNormalToRef(this.upVector, this._cameraRotationMatrix, this._cameraRigParams.vrActualUp);
+            BABYLON.Vector3.TransformNormalToRef(this._defaultUpVector, this._cameraRotationMatrix, this._cameraRigParams.vrActualUp);
             // Computing target and final matrix
             this.position.addToRef(this._transformedReferencePoint, this._currentTarget);
             BABYLON.Matrix.LookAtLHToRef(this.position, this._currentTarget, this._cameraRigParams.vrActualUp, this._cameraRigParams.vrWorkMatrix);
@@ -198,9 +206,13 @@ var BABYLON;
             if (this.cameraRigMode !== BABYLON.Camera.RIG_MODE_NONE) {
                 var rigCamera = new TargetCamera(name, this.position.clone(), this.getScene());
                 if (this.cameraRigMode === BABYLON.Camera.RIG_MODE_VR) {
+                    if (!this.rotationQuaternion) {
+                        this.rotationQuaternion = new BABYLON.Quaternion();
+                    }
                     rigCamera._cameraRigParams = {};
                     rigCamera._cameraRigParams.vrActualUp = new BABYLON.Vector3(0, 0, 0);
                     rigCamera._getViewMatrix = rigCamera._getVRViewMatrix;
+                    rigCamera.rotationQuaternion = new BABYLON.Quaternion();
                 }
                 return rigCamera;
             }
@@ -227,9 +239,8 @@ var BABYLON;
                     camRight.setTarget(this.getTarget());
                     break;
                 case BABYLON.Camera.RIG_MODE_VR:
-                    camLeft.rotation.x = camRight.rotation.x = this.rotation.x;
-                    camLeft.rotation.y = camRight.rotation.y = this.rotation.y;
-                    camLeft.rotation.z = camRight.rotation.z = this.rotation.z;
+                    camLeft.rotationQuaternion.copyFrom(this.rotationQuaternion);
+                    camRight.rotationQuaternion.copyFrom(this.rotationQuaternion);
                     camLeft.position.copyFrom(this.position);
                     camRight.position.copyFrom(this.position);
                     break;
@@ -258,6 +269,6 @@ var BABYLON;
             BABYLON.serializeAsMeshReference("lockedTargetId")
         ], TargetCamera.prototype, "lockedTarget", void 0);
         return TargetCamera;
-    }(BABYLON.Camera));
+    })(BABYLON.Camera);
     BABYLON.TargetCamera = TargetCamera;
 })(BABYLON || (BABYLON = {}));

+ 43 - 26
src/Cameras/babylon.targetCamera.ts

@@ -7,6 +7,8 @@
         @serializeAsVector3()
         public rotation = new Vector3(0, 0, 0);
 
+        public rotationQuaternion: Quaternion;
+
         @serialize()
         public speed = 2.0;
 
@@ -23,6 +25,7 @@
         private _rigCamTransformMatrix: Matrix;
 
         public _referencePoint = new Vector3(0, 0, 1);
+        private _defaultUpVector = new Vector3(0, 1, 0);
         public _transformedReferencePoint = Vector3.Zero();
         public _lookAtTemp = Matrix.Zero();
         public _tempMatrix = Matrix.Zero();
@@ -53,6 +56,7 @@
             super._initCache();
             this._cache.lockedTarget = new Vector3(Number.MAX_VALUE, Number.MAX_VALUE, Number.MAX_VALUE);
             this._cache.rotation = new Vector3(Number.MAX_VALUE, Number.MAX_VALUE, Number.MAX_VALUE);
+            this._cache.rotationQuaternion = new Quaternion(Number.MAX_VALUE, Number.MAX_VALUE, Number.MAX_VALUE, Number.MAX_VALUE);
         }
 
         public _updateCache(ignoreParentClass?: boolean): void {
@@ -74,6 +78,8 @@
             }
 
             this._cache.rotation.copyFrom(this.rotation);
+            if (this.rotationQuaternion)
+                this._cache.rotationQuaternion.copyFrom(this.rotationQuaternion);
         }
 
         // Synchronized
@@ -85,7 +91,7 @@
             var lockedTargetPosition = this._getLockedTargetPosition();
 
             return (this._cache.lockedTarget ? this._cache.lockedTarget.equals(lockedTargetPosition) : !lockedTargetPosition)
-                && this._cache.rotation.equals(this.rotation);
+                && (this.rotationQuaternion ? this.rotationQuaternion.equals(this._cache.rotationQuaternion) : this._cache.rotation.equals(this.rotation));
         }
 
         // Methods
@@ -98,7 +104,7 @@
         public setTarget(target: Vector3): void {
             this.upVector.normalize();
 
-            Matrix.LookAtLHToRef(this.position, target, this.upVector, this._camMatrix);
+            Matrix.LookAtLHToRef(this.position, target, this._defaultUpVector, this._camMatrix);
             this._camMatrix.invert();
 
             this.rotation.x = Math.atan(this._camMatrix.m[6] / this._camMatrix.m[10]);
@@ -111,7 +117,7 @@
                 this.rotation.y = (-Math.atan(vDir.z / vDir.x) - Math.PI / 2.0);
             }
 
-            this.rotation.z = -Math.acos(Vector3.Dot(new Vector3(0, 1.0, 0), this.upVector));
+            this.rotation.z = 0;
 
             if (isNaN(this.rotation.x)) {
                 this.rotation.x = 0;
@@ -124,6 +130,10 @@
             if (isNaN(this.rotation.z)) {
                 this.rotation.z = 0;
             }
+
+            if (this.rotationQuaternion) {
+                Quaternion.RotationYawPitchRollToRef(this.rotation.y, this.rotation.x, this.rotation.z, this.rotationQuaternion);
+            }
         }
 
         public getTarget(): Vector3 {
@@ -194,21 +204,26 @@
             super._checkInputs();
         }
 
+        private _updateCameraRotationMatrix() {
+            if (this.rotationQuaternion) {
+                this.rotationQuaternion.toRotationMatrix(this._cameraRotationMatrix);
+                //update the up vector!
+                BABYLON.Vector3.TransformNormalToRef(this._defaultUpVector, this._cameraRotationMatrix, this.upVector);
+            } else {
+                Matrix.RotationYawPitchRollToRef(this.rotation.y, this.rotation.x, this.rotation.z, this._cameraRotationMatrix);
+                //if (this.upVector.x !== 0 || this.upVector.y !== 1.0 || this.upVector.z !== 0) {
+                //    Matrix.LookAtLHToRef(Vector3.Zero(), this._referencePoint, this.upVector, this._lookAtTemp);
+                //    this._lookAtTemp.multiplyToRef(this._cameraRotationMatrix, this._tempMatrix);
+                //    this._lookAtTemp.invert();
+                //    this._tempMatrix.multiplyToRef(this._lookAtTemp, this._cameraRotationMatrix);
+                //}
+            }
+        }
 
         public _getViewMatrix(): Matrix {
             if (!this.lockedTarget) {
                 // Compute
-                if (this.upVector.x !== 0 || this.upVector.y !== 1.0 || this.upVector.z !== 0) {
-                    Matrix.LookAtLHToRef(Vector3.Zero(), this._referencePoint, this.upVector, this._lookAtTemp);
-                    Matrix.RotationYawPitchRollToRef(this.rotation.y, this.rotation.x, this.rotation.z, this._cameraRotationMatrix);
-
-
-                    this._lookAtTemp.multiplyToRef(this._cameraRotationMatrix, this._tempMatrix);
-                    this._lookAtTemp.invert();
-                    this._tempMatrix.multiplyToRef(this._lookAtTemp, this._cameraRotationMatrix);
-                } else {
-                    Matrix.RotationYawPitchRollToRef(this.rotation.y, this.rotation.x, this.rotation.z, this._cameraRotationMatrix);
-                }
+                this._updateCameraRotationMatrix();
 
                 Vector3.TransformCoordinatesToRef(this._referencePoint, this._cameraRotationMatrix, this._transformedReferencePoint);
 
@@ -223,10 +238,10 @@
         }
 
         public _getVRViewMatrix(): Matrix {
-            Matrix.RotationYawPitchRollToRef(this.rotation.y, this.rotation.x, this.rotation.z, this._cameraRotationMatrix);
+            this._updateCameraRotationMatrix();
 
             Vector3.TransformCoordinatesToRef(this._referencePoint, this._cameraRotationMatrix, this._transformedReferencePoint);
-            Vector3.TransformNormalToRef(this.upVector, this._cameraRotationMatrix, this._cameraRigParams.vrActualUp);
+            Vector3.TransformNormalToRef(this._defaultUpVector, this._cameraRotationMatrix, this._cameraRigParams.vrActualUp);
 
             // Computing target and final matrix
             this.position.addToRef(this._transformedReferencePoint, this._currentTarget);
@@ -236,7 +251,7 @@
             this._cameraRigParams.vrWorkMatrix.multiplyToRef(this._cameraRigParams.vrPreViewMatrix, this._viewMatrix);
             return this._viewMatrix;
         }
-        
+
         /**
          * @override
          * Override Camera.createRigCamera
@@ -245,15 +260,19 @@
             if (this.cameraRigMode !== Camera.RIG_MODE_NONE) {
                 var rigCamera = new TargetCamera(name, this.position.clone(), this.getScene());
                 if (this.cameraRigMode === Camera.RIG_MODE_VR) {
+                    if (!this.rotationQuaternion) {
+                        this.rotationQuaternion = new Quaternion();
+                    }
                     rigCamera._cameraRigParams = {};
                     rigCamera._cameraRigParams.vrActualUp = new Vector3(0, 0, 0);
                     rigCamera._getViewMatrix = rigCamera._getVRViewMatrix;
+                    rigCamera.rotationQuaternion = new Quaternion();
                 }
                 return rigCamera;
             }
             return null;
         }
-        
+
         /**
          * @override
          * Override Camera._updateRigCameras
@@ -268,20 +287,18 @@
                 case Camera.RIG_MODE_STEREOSCOPIC_SIDEBYSIDE_CROSSEYED:
                 case Camera.RIG_MODE_STEREOSCOPIC_OVERUNDER:
                     //provisionnaly using _cameraRigParams.stereoHalfAngle instead of calculations based on _cameraRigParams.interaxialDistance:
-                    var leftSign  = (this.cameraRigMode === Camera.RIG_MODE_STEREOSCOPIC_SIDEBYSIDE_CROSSEYED) ?  1 : -1;
-                    var rightSign = (this.cameraRigMode === Camera.RIG_MODE_STEREOSCOPIC_SIDEBYSIDE_CROSSEYED) ? -1 :  1;
-                    this._getRigCamPosition(this._cameraRigParams.stereoHalfAngle * leftSign , camLeft.position);
+                    var leftSign = (this.cameraRigMode === Camera.RIG_MODE_STEREOSCOPIC_SIDEBYSIDE_CROSSEYED) ? 1 : -1;
+                    var rightSign = (this.cameraRigMode === Camera.RIG_MODE_STEREOSCOPIC_SIDEBYSIDE_CROSSEYED) ? -1 : 1;
+                    this._getRigCamPosition(this._cameraRigParams.stereoHalfAngle * leftSign, camLeft.position);
                     this._getRigCamPosition(this._cameraRigParams.stereoHalfAngle * rightSign, camRight.position);
 
                     camLeft.setTarget(this.getTarget());
                     camRight.setTarget(this.getTarget());
                     break;
-                    
-                case Camera.RIG_MODE_VR:
-                    camLeft.rotation.x = camRight.rotation.x = this.rotation.x;
-                    camLeft.rotation.y = camRight.rotation.y = this.rotation.y;
-                    camLeft.rotation.z = camRight.rotation.z = this.rotation.z;
 
+                case Camera.RIG_MODE_VR:
+                    camLeft.rotationQuaternion.copyFrom(this.rotationQuaternion);
+                    camRight.rotationQuaternion.copyFrom(this.rotationQuaternion);
                     camLeft.position.copyFrom(this.position);
                     camRight.position.copyFrom(this.position);
 

+ 1 - 1
src/Cameras/babylon.touchCamera.js

@@ -53,6 +53,6 @@ var BABYLON;
             }
         };
         return TouchCamera;
-    }(BABYLON.FreeCamera));
+    })(BABYLON.FreeCamera);
     BABYLON.TouchCamera = TouchCamera;
 })(BABYLON || (BABYLON = {}));

+ 1 - 1
src/Cameras/babylon.universalCamera.js

@@ -46,6 +46,6 @@ var BABYLON;
             return "UniversalCamera";
         };
         return UniversalCamera;
-    }(BABYLON.TouchCamera));
+    })(BABYLON.TouchCamera);
     BABYLON.UniversalCamera = UniversalCamera;
 })(BABYLON || (BABYLON = {}));

+ 1 - 1
src/Cameras/babylon.virtualJoysticksCamera.js

@@ -13,6 +13,6 @@ var BABYLON;
             this.inputs.addVirtualJoystick();
         }
         return VirtualJoysticksCamera;
-    }(BABYLON.FreeCamera));
+    })(BABYLON.FreeCamera);
     BABYLON.VirtualJoysticksCamera = VirtualJoysticksCamera;
 })(BABYLON || (BABYLON = {}));

+ 43 - 28
src/Canvas2d/babylon.bounding2d.js

@@ -10,44 +10,58 @@ var BABYLON;
             this.center = BABYLON.Vector2.Zero();
             this.extent = BABYLON.Vector2.Zero();
         }
-        BoundingInfo2D.CreateFromSize = function (size) {
+        BoundingInfo2D.CreateFromSize = function (size, origin) {
             var r = new BoundingInfo2D();
-            BoundingInfo2D.CreateFromSizeToRef(size, r);
+            BoundingInfo2D.CreateFromSizeToRef(size, r, origin);
             return r;
         };
-        BoundingInfo2D.CreateFromRadius = function (radius) {
+        BoundingInfo2D.CreateFromRadius = function (radius, origin) {
             var r = new BoundingInfo2D();
-            BoundingInfo2D.CreateFromRadiusToRef(radius, r);
+            BoundingInfo2D.CreateFromRadiusToRef(radius, r, origin);
             return r;
         };
-        BoundingInfo2D.CreateFromPoints = function (points) {
+        BoundingInfo2D.CreateFromPoints = function (points, origin) {
             var r = new BoundingInfo2D();
-            BoundingInfo2D.CreateFromPointsToRef(points, r);
+            BoundingInfo2D.CreateFromPointsToRef(points, r, origin);
             return r;
         };
-        BoundingInfo2D.CreateFromSizeToRef = function (size, b) {
+        BoundingInfo2D.CreateFromSizeToRef = function (size, b, origin) {
             b.center = new BABYLON.Vector2(size.width / 2, size.height / 2);
             b.extent = b.center.clone();
+            if (origin) {
+                b.center.x -= size.width * origin.x;
+                b.center.y -= size.height * origin.y;
+            }
             b.radius = b.extent.length();
         };
-        BoundingInfo2D.CreateFromRadiusToRef = function (radius, b) {
+        BoundingInfo2D.CreateFromRadiusToRef = function (radius, b, origin) {
             b.center = BABYLON.Vector2.Zero();
+            if (origin) {
+                b.center.x -= radius * origin.x;
+                b.center.y -= radius * origin.y;
+            }
             b.extent = new BABYLON.Vector2(radius, radius);
             b.radius = radius;
         };
-        BoundingInfo2D.CreateFromPointsToRef = function (points, b) {
+        BoundingInfo2D.CreateFromPointsToRef = function (points, b, origin) {
             var xmin = Number.MAX_VALUE, ymin = Number.MAX_VALUE, xmax = Number.MIN_VALUE, ymax = Number.MIN_VALUE;
-            for (var _i = 0, points_1 = points; _i < points_1.length; _i++) {
-                var p = points_1[_i];
+            for (var _i = 0; _i < points.length; _i++) {
+                var p = points[_i];
                 xmin = Math.min(p.x, xmin);
                 xmax = Math.max(p.x, xmax);
                 ymin = Math.min(p.y, ymin);
                 ymax = Math.max(p.y, ymax);
             }
-            BoundingInfo2D.CreateFromMinMaxToRef(xmin, xmax, ymin, ymax, b);
+            BoundingInfo2D.CreateFromMinMaxToRef(xmin, xmax, ymin, ymax, b, origin);
         };
-        BoundingInfo2D.CreateFromMinMaxToRef = function (xmin, xmax, ymin, ymax, b) {
-            b.center = new BABYLON.Vector2(xmin + (xmax - xmin) / 2, ymin + (ymax - ymin) / 2);
+        BoundingInfo2D.CreateFromMinMaxToRef = function (xmin, xmax, ymin, ymax, b, origin) {
+            var w = xmax - xmin;
+            var h = ymax - ymin;
+            b.center = new BABYLON.Vector2(xmin + w / 2, ymin + h / 2);
+            if (origin) {
+                b.center.x -= w * origin.x;
+                b.center.y -= h * origin.y;
+            }
             b.extent = new BABYLON.Vector2(xmax - b.center.x, ymax - b.center.y);
             b.radius = b.extent.length();
         };
@@ -76,10 +90,9 @@ var BABYLON;
          * @param matrix the transformation matrix to apply
          * @return the new instance containing the result of the transformation applied on this BoundingInfo2D
          */
-        BoundingInfo2D.prototype.transform = function (matrix, origin) {
-            if (origin === void 0) { origin = null; }
+        BoundingInfo2D.prototype.transform = function (matrix) {
             var r = new BoundingInfo2D();
-            this.transformToRef(matrix, origin, r);
+            this.transformToRef(matrix, r);
             return r;
         };
         /**
@@ -94,24 +107,17 @@ var BABYLON;
         };
         /**
          * Transform this BoundingInfo2D with a given matrix and store the result in an existing BoundingInfo2D instance.
-         * This is a GC friendly version, try to use it as much as possible, specially if your transformation is inside a loop, allocate the result object once for good outside of the loop and use it everytime.
-         * @param origin An optional normalized origin to apply before the transformation. 0;0 is top/left, 0.5;0.5 is center, etc.
+         * This is a GC friendly version, try to use it as much as possible, specially if your transformation is inside a loop, allocate the result object once for good outside of the loop and use it every time.
          * @param matrix The matrix to use to compute the transformation
          * @param result A VALID (i.e. allocated) BoundingInfo2D object where the result will be stored
          */
-        BoundingInfo2D.prototype.transformToRef = function (matrix, origin, result) {
+        BoundingInfo2D.prototype.transformToRef = function (matrix, result) {
             // Construct a bounding box based on the extent values
             var p = new Array(4);
             p[0] = new BABYLON.Vector2(this.center.x + this.extent.x, this.center.y + this.extent.y);
             p[1] = new BABYLON.Vector2(this.center.x + this.extent.x, this.center.y - this.extent.y);
             p[2] = new BABYLON.Vector2(this.center.x - this.extent.x, this.center.y - this.extent.y);
             p[3] = new BABYLON.Vector2(this.center.x - this.extent.x, this.center.y + this.extent.y);
-            //if (origin) {
-            //    let off = new Vector2((p[0].x - p[2].x) * origin.x, (p[0].y - p[2].y) * origin.y);
-            //    for (let j = 0; j < 4; j++) {
-            //        p[j].subtractInPlace(off);
-            //    }
-            //}
             // Transform the four points of the bounding box with the matrix
             for (var i = 0; i < 4; i++) {
                 BABYLON.Vector2.TransformToRef(p[i], matrix, p[i]);
@@ -120,7 +126,7 @@ var BABYLON;
         };
         /**
          * Compute the union of this BoundingInfo2D with another one and store the result in a third valid BoundingInfo2D object
-         * This is a GC friendly version, try to use it as much as possible, specially if your transformation is inside a loop, allocate the result object once for good outside of the loop and use it everytime.
+         * This is a GC friendly version, try to use it as much as possible, specially if your transformation is inside a loop, allocate the result object once for good outside of the loop and use it every time.
          * @param other the second object used to compute the union
          * @param result a VALID BoundingInfo2D instance (i.e. allocated) where the result will be stored
          */
@@ -131,7 +137,16 @@ var BABYLON;
             var ymin = Math.min(this.center.y - this.extent.y, other.center.y - other.extent.y);
             BoundingInfo2D.CreateFromMinMaxToRef(xmin, xmax, ymin, ymax, result);
         };
+        BoundingInfo2D.prototype.doesIntersect = function (pickPosition) {
+            // is it inside the radius?
+            var pickLocal = pickPosition.subtract(this.center);
+            if (pickLocal.lengthSquared() <= (this.radius * this.radius)) {
+                // is it inside the rectangle?
+                return ((Math.abs(pickLocal.x) <= this.extent.x) && (Math.abs(pickLocal.y) <= this.extent.y));
+            }
+            return false;
+        };
         return BoundingInfo2D;
-    }());
+    })();
     BABYLON.BoundingInfo2D = BoundingInfo2D;
 })(BABYLON || (BABYLON = {}));

+ 40 - 26
src/Canvas2d/babylon.bounding2d.ts

@@ -28,38 +28,46 @@
             this.extent = Vector2.Zero();
         }
 
-        public static CreateFromSize(size: Size): BoundingInfo2D {
+        public static CreateFromSize(size: Size, origin?: Vector2): BoundingInfo2D {
             let r = new BoundingInfo2D();
-            BoundingInfo2D.CreateFromSizeToRef(size, r);
+            BoundingInfo2D.CreateFromSizeToRef(size, r, origin);
             return r;
         }
 
-        public static CreateFromRadius(radius: number): BoundingInfo2D {
+        public static CreateFromRadius(radius: number, origin?: Vector2): BoundingInfo2D {
             let r = new BoundingInfo2D();
-            BoundingInfo2D.CreateFromRadiusToRef(radius, r);
+            BoundingInfo2D.CreateFromRadiusToRef(radius, r, origin);
             return r;
         }
 
-        public static CreateFromPoints(points: Vector2[]): BoundingInfo2D {
+        public static CreateFromPoints(points: Vector2[], origin?: Vector2): BoundingInfo2D {
             let r = new BoundingInfo2D();
-            BoundingInfo2D.CreateFromPointsToRef(points, r);
+            BoundingInfo2D.CreateFromPointsToRef(points, r, origin);
 
             return r;
         }
 
-        public static CreateFromSizeToRef(size: Size, b: BoundingInfo2D) {
+        public static CreateFromSizeToRef(size: Size, b: BoundingInfo2D, origin?: Vector2) {
             b.center = new Vector2(size.width / 2, size.height / 2);
             b.extent = b.center.clone();
+            if (origin) {
+                b.center.x -= size.width * origin.x;
+                b.center.y -= size.height * origin.y;
+            }
             b.radius = b.extent.length();
         }
 
-        public static CreateFromRadiusToRef(radius: number, b: BoundingInfo2D) {
+        public static CreateFromRadiusToRef(radius: number, b: BoundingInfo2D, origin?: Vector2) {
             b.center = Vector2.Zero();
+            if (origin) {
+                b.center.x -= radius * origin.x;
+                b.center.y -= radius * origin.y;
+            }
             b.extent = new Vector2(radius, radius);
             b.radius = radius;
         }
 
-        public static CreateFromPointsToRef(points: Vector2[], b: BoundingInfo2D) {
+        public static CreateFromPointsToRef(points: Vector2[], b: BoundingInfo2D, origin?: Vector2) {
             let xmin = Number.MAX_VALUE, ymin = Number.MAX_VALUE, xmax = Number.MIN_VALUE, ymax = Number.MIN_VALUE;
             for (let p of points) {
                 xmin = Math.min(p.x, xmin);
@@ -67,12 +75,17 @@
                 ymin = Math.min(p.y, ymin);
                 ymax = Math.max(p.y, ymax);
             }
-            BoundingInfo2D.CreateFromMinMaxToRef(xmin, xmax, ymin, ymax, b);
+            BoundingInfo2D.CreateFromMinMaxToRef(xmin, xmax, ymin, ymax, b, origin);
         }
 
-
-        public static CreateFromMinMaxToRef(xmin: number, xmax: number, ymin: number, ymax: number, b: BoundingInfo2D) {
-            b.center = new Vector2(xmin + (xmax - xmin) / 2, ymin + (ymax - ymin) / 2);
+        public static CreateFromMinMaxToRef(xmin: number, xmax: number, ymin: number, ymax: number, b: BoundingInfo2D, origin?: Vector2) {
+            let w = xmax - xmin;
+            let h = ymax - ymin;
+            b.center = new Vector2(xmin + w / 2, ymin + h / 2);
+            if (origin) {
+                b.center.x -= w * origin.x;
+                b.center.y -= h * origin.y;
+            }
             b.extent = new Vector2(xmax - b.center.x, ymax - b.center.y);
             b.radius = b.extent.length();
         }
@@ -105,9 +118,9 @@
          * @param matrix the transformation matrix to apply
          * @return the new instance containing the result of the transformation applied on this BoundingInfo2D
          */
-        public transform(matrix: Matrix, origin: Vector2=null): BoundingInfo2D {
+        public transform(matrix: Matrix): BoundingInfo2D {
             var r = new BoundingInfo2D();
-            this.transformToRef(matrix, origin, r);
+            this.transformToRef(matrix, r);
             return r;
         }
 
@@ -124,12 +137,11 @@
 
         /**
          * Transform this BoundingInfo2D with a given matrix and store the result in an existing BoundingInfo2D instance.
-         * This is a GC friendly version, try to use it as much as possible, specially if your transformation is inside a loop, allocate the result object once for good outside of the loop and use it everytime.
-         * @param origin An optional normalized origin to apply before the transformation. 0;0 is top/left, 0.5;0.5 is center, etc.
+         * This is a GC friendly version, try to use it as much as possible, specially if your transformation is inside a loop, allocate the result object once for good outside of the loop and use it every time.
          * @param matrix The matrix to use to compute the transformation
          * @param result A VALID (i.e. allocated) BoundingInfo2D object where the result will be stored
          */
-        public transformToRef(matrix: Matrix, origin: Vector2, result: BoundingInfo2D) {
+        public transformToRef(matrix: Matrix, result: BoundingInfo2D) {
             // Construct a bounding box based on the extent values
             let p = new Array<Vector2>(4);
             p[0] = new Vector2(this.center.x + this.extent.x, this.center.y + this.extent.y);
@@ -137,13 +149,6 @@
             p[2] = new Vector2(this.center.x - this.extent.x, this.center.y - this.extent.y);
             p[3] = new Vector2(this.center.x - this.extent.x, this.center.y + this.extent.y);
 
-            //if (origin) {
-            //    let off = new Vector2((p[0].x - p[2].x) * origin.x, (p[0].y - p[2].y) * origin.y);
-            //    for (let j = 0; j < 4; j++) {
-            //        p[j].subtractInPlace(off);
-            //    }
-            //}
-
             // Transform the four points of the bounding box with the matrix
             for (let i = 0; i < 4; i++) {
                 Vector2.TransformToRef(p[i], matrix, p[i]);
@@ -153,7 +158,7 @@
 
         /**
          * Compute the union of this BoundingInfo2D with another one and store the result in a third valid BoundingInfo2D object
-         * This is a GC friendly version, try to use it as much as possible, specially if your transformation is inside a loop, allocate the result object once for good outside of the loop and use it everytime.
+         * This is a GC friendly version, try to use it as much as possible, specially if your transformation is inside a loop, allocate the result object once for good outside of the loop and use it every time.
          * @param other the second object used to compute the union
          * @param result a VALID BoundingInfo2D instance (i.e. allocated) where the result will be stored
          */
@@ -165,5 +170,14 @@
             BoundingInfo2D.CreateFromMinMaxToRef(xmin, xmax, ymin, ymax, result);
         }
 
+        doesIntersect(pickPosition: Vector2): boolean {
+            // is it inside the radius?
+            let pickLocal = pickPosition.subtract(this.center);
+            if (pickLocal.lengthSquared() <= (this.radius * this.radius)) {
+                // is it inside the rectangle?
+                return ((Math.abs(pickLocal.x) <= this.extent.x) && (Math.abs(pickLocal.y) <= this.extent.y));
+            }
+            return false;
+        }
     }
 }

+ 5 - 5
src/Canvas2d/babylon.brushes2d.js

@@ -12,7 +12,7 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
 var BABYLON;
 (function (BABYLON) {
     /**
-     * Base class implemting the ILocable interface.
+     * Base class implementing the ILocable interface.
      * The particularity of this class is to call the protected onLock() method when the instance is about to be locked for good.
      */
     var LockableBase = (function () {
@@ -35,10 +35,10 @@ var BABYLON;
         LockableBase.prototype.onLock = function () {
         };
         return LockableBase;
-    }());
+    })();
     BABYLON.LockableBase = LockableBase;
     /**
-     * This classs implements a Brush that will be drawn with a uniform solid color (i.e. the same color everywhere in the content where the brush is assigned to).
+     * This class implements a Brush that will be drawn with a uniform solid color (i.e. the same color everywhere in the content where the brush is assigned to).
      */
     var SolidColorBrush2D = (function (_super) {
         __extends(SolidColorBrush2D, _super);
@@ -82,7 +82,7 @@ var BABYLON;
             BABYLON.className("SolidColorBrush2D")
         ], SolidColorBrush2D);
         return SolidColorBrush2D;
-    }(LockableBase));
+    })(LockableBase);
     BABYLON.SolidColorBrush2D = SolidColorBrush2D;
     var GradientColorBrush2D = (function (_super) {
         __extends(GradientColorBrush2D, _super);
@@ -179,6 +179,6 @@ var BABYLON;
             BABYLON.className("GradientColorBrush2D")
         ], GradientColorBrush2D);
         return GradientColorBrush2D;
-    }(LockableBase));
+    })(LockableBase);
     BABYLON.GradientColorBrush2D = GradientColorBrush2D;
 })(BABYLON || (BABYLON = {}));

+ 3 - 3
src/Canvas2d/babylon.brushes2d.ts

@@ -25,7 +25,7 @@
      */
     export interface IBrush2D extends ILockable {
         /**
-         * Define if the brush will use transparency/alphablending
+         * Define if the brush will use transparency / alpha blending
          * @returns true if the brush use transparency
          */
         isTransparent(): boolean;
@@ -38,7 +38,7 @@
     }
 
     /**
-     * Base class implemting the ILocable interface.
+     * Base class implementing the ILocable interface.
      * The particularity of this class is to call the protected onLock() method when the instance is about to be locked for good.
      */
     export class LockableBase implements ILockable {
@@ -67,7 +67,7 @@
     }
 
     /**
-     * This classs implements a Brush that will be drawn with a uniform solid color (i.e. the same color everywhere in the content where the brush is assigned to).
+     * This class implements a Brush that will be drawn with a uniform solid color (i.e. the same color everywhere in the content where the brush is assigned to).
      */
     @className("SolidColorBrush2D")
     export class SolidColorBrush2D extends LockableBase implements IBrush2D {

+ 409 - 43
src/Canvas2d/babylon.canvas2d.js

@@ -26,29 +26,33 @@ var BABYLON;
             return true;
         };
         return Canvas2DEngineBoundData;
-    }());
+    })();
     BABYLON.Canvas2DEngineBoundData = Canvas2DEngineBoundData;
     var Canvas2D = (function (_super) {
         __extends(Canvas2D, _super);
         function Canvas2D() {
             _super.apply(this, arguments);
+            this._notifDebugMode = false;
             this._mapCounter = 0;
         }
         /**
          * Create a new 2D ScreenSpace Rendering Canvas, it is a 2D rectangle that has a size (width/height) and a position relative to the top/left corner of the screen.
          * ScreenSpace Canvas will be drawn in the Viewport as a 2D Layer lying to the top of the 3D Scene. Typically used for traditional UI.
          * All caching strategies will be available.
+         * PLEASE NOTE: the origin of a Screen Space Canvas is set to [0;0] (bottom/left) which is different than the default origin of a Primitive which is centered [0.5;0.5]
          * @param scene the Scene that owns the Canvas
          * @param name the name of the Canvas, for information purpose only
          * @param pos the position of the canvas, relative from the bottom/left of the scene's viewport
          * @param size the Size of the canvas. If null two behaviors depend on the cachingStrategy: if it's CACHESTRATEGY_CACHECANVAS then it will always auto-fit the rendering device, in all the other modes it will fit the content of the Canvas
          * @param cachingStrategy either CACHESTRATEGY_TOPLEVELGROUPS, CACHESTRATEGY_ALLGROUPS, CACHESTRATEGY_CANVAS, CACHESTRATEGY_DONTCACHE. Please refer to their respective documentation for more information.
          */
-        Canvas2D.CreateScreenSpace = function (scene, name, pos, size, cachingStrategy) {
+        Canvas2D.CreateScreenSpace = function (scene, name, pos, size, cachingStrategy, enableInteraction) {
             if (cachingStrategy === void 0) { cachingStrategy = Canvas2D.CACHESTRATEGY_TOPLEVELGROUPS; }
+            if (enableInteraction === void 0) { enableInteraction = true; }
             var c = new Canvas2D();
-            c.setupCanvas(scene, name, size, true, cachingStrategy);
+            c.setupCanvas(scene, name, size, true, cachingStrategy, enableInteraction);
             c.position = pos;
+            c.origin = BABYLON.Vector2.Zero();
             return c;
         };
         /**
@@ -65,9 +69,10 @@ var BABYLON;
          * @param sideOrientation Unexpected behavior occur if the value is different from Mesh.DEFAULTSIDE right now, so please use this one.
          * @param cachingStrategy Must be CACHESTRATEGY_CANVAS for now
          */
-        Canvas2D.CreateWorldSpace = function (scene, name, position, rotation, size, renderScaleFactor, sideOrientation, cachingStrategy) {
+        Canvas2D.CreateWorldSpace = function (scene, name, position, rotation, size, renderScaleFactor, sideOrientation, cachingStrategy, enableInteraction) {
             if (renderScaleFactor === void 0) { renderScaleFactor = 1; }
             if (cachingStrategy === void 0) { cachingStrategy = Canvas2D.CACHESTRATEGY_TOPLEVELGROUPS; }
+            if (enableInteraction === void 0) { enableInteraction = true; }
             if (cachingStrategy !== Canvas2D.CACHESTRATEGY_CANVAS) {
                 throw new Error("Right now only the CACHESTRATEGY_CANVAS cache Strategy is supported for WorldSpace Canvas. More will come soon!");
             }
@@ -78,7 +83,7 @@ var BABYLON;
                 sideOrientation = BABYLON.Mesh.DEFAULTSIDE;
             }
             var c = new Canvas2D();
-            c.setupCanvas(scene, name, new BABYLON.Size(size.width * renderScaleFactor, size.height * renderScaleFactor), false, cachingStrategy);
+            c.setupCanvas(scene, name, new BABYLON.Size(size.width * renderScaleFactor, size.height * renderScaleFactor), false, cachingStrategy, enableInteraction);
             var plane = new BABYLON.WorldSpaceCanvas2d(name, scene, c);
             var vertexData = BABYLON.VertexData.CreatePlane({ width: size.width / 2, height: size.height / 2, sideOrientation: sideOrientation });
             var mtl = new BABYLON.StandardMaterial(name + "_Material", scene);
@@ -93,10 +98,8 @@ var BABYLON;
             c._worldSpaceNode = plane;
             return c;
         };
-        Canvas2D.prototype.setupCanvas = function (scene, name, size, isScreenSpace, cachingstrategy) {
+        Canvas2D.prototype.setupCanvas = function (scene, name, size, isScreenSpace, cachingstrategy, enableInteraction) {
             var _this = this;
-            if (isScreenSpace === void 0) { isScreenSpace = true; }
-            if (cachingstrategy === void 0) { cachingstrategy = Canvas2D.CACHESTRATEGY_TOPLEVELGROUPS; }
             var engine = scene.getEngine();
             this._fitRenderingDevice = !size;
             if (!size) {
@@ -104,12 +107,13 @@ var BABYLON;
             }
             this.__engineData = engine.getOrAddExternalDataWithFactory("__BJSCANVAS2D__", function (k) { return new Canvas2DEngineBoundData(); });
             this._cachingStrategy = cachingstrategy;
-            this._depthLevel = 0;
-            this._hierarchyMaxDepth = 100;
-            this._hierarchyLevelZFactor = 1 / this._hierarchyMaxDepth;
-            this._hierarchyLevelMaxSiblingCount = 1000;
-            this._hierarchySiblingZDelta = this._hierarchyLevelZFactor / this._hierarchyLevelMaxSiblingCount;
+            this._primPointerInfo = new BABYLON.PrimitivePointerInfo();
+            this._capturedPointers = new BABYLON.StringDictionary();
+            this._pickStartingPosition = BABYLON.Vector2.Zero();
             this.setupGroup2D(this, null, name, BABYLON.Vector2.Zero(), size, this._cachingStrategy === Canvas2D.CACHESTRATEGY_ALLGROUPS ? BABYLON.Group2D.GROUPCACHEBEHAVIOR_DONTCACHEOVERRIDE : BABYLON.Group2D.GROUPCACHEBEHAVIOR_FOLLOWCACHESTRATEGY);
+            this._hierarchyLevelMaxSiblingCount = 100;
+            this._hierarchyDepthOffset = 0;
+            this._siblingDepthOffset = 1 / this._hierarchyLevelMaxSiblingCount;
             this._scene = scene;
             this._engine = engine;
             this._renderingSize = new BABYLON.Size(0, 0);
@@ -119,6 +123,7 @@ var BABYLON;
             });
             if (cachingstrategy !== Canvas2D.CACHESTRATEGY_TOPLEVELGROUPS) {
                 this._background = BABYLON.Rectangle2D.Create(this, "###CANVAS BACKGROUND###", 0, 0, size.width, size.height);
+                this._background.isPickable = false;
                 this._background.origin = BABYLON.Vector2.Zero();
                 this._background.levelVisible = false;
             }
@@ -136,6 +141,360 @@ var BABYLON;
             }
             this._supprtInstancedArray = this._engine.getCaps().instancedArrays !== null;
             //            this._supprtInstancedArray = false; // TODO REMOVE!!!
+            this._setupInteraction(enableInteraction);
+        };
+        Object.defineProperty(Canvas2D.prototype, "hierarchyLevelMaxSiblingCount", {
+            get: function () {
+                return this._hierarchyLevelMaxSiblingCount;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Canvas2D.prototype._setupInteraction = function (enable) {
+            var _this = this;
+            // No change detection
+            if (enable === this._interactionEnabled) {
+                return;
+            }
+            // Set the new state
+            this._interactionEnabled = enable;
+            // Disable interaction
+            if (!enable) {
+                if (this._scenePrePointerObserver) {
+                    this.scene.onPrePointerObservable.remove(this._scenePrePointerObserver);
+                    this._scenePrePointerObserver = null;
+                }
+                return;
+            }
+            // Enable Interaction
+            // Register the observable
+            this.scene.onPrePointerObservable.add(function (e, s) { return _this._handlePointerEventForInteraction(e, s); });
+        };
+        /**
+         * Internal method, you should use the Prim2DBase version instead
+         */
+        Canvas2D.prototype._setPointerCapture = function (pointerId, primitive) {
+            if (this.isPointerCaptured(pointerId)) {
+                return false;
+            }
+            // Try to capture the pointer on the HTML side
+            try {
+                this.engine.getRenderingCanvas().setPointerCapture(pointerId);
+            }
+            catch (e) {
+            }
+            this._primPointerInfo.updateRelatedTarget(primitive, BABYLON.Vector2.Zero());
+            this._bubbleNotifyPrimPointerObserver(primitive, BABYLON.PrimitivePointerInfo.PointerGotCapture, null);
+            this._capturedPointers.add(pointerId.toString(), primitive);
+            return true;
+        };
+        /**
+         * Internal method, you should use the Prim2DBase version instead
+         */
+        Canvas2D.prototype._releasePointerCapture = function (pointerId, primitive) {
+            if (this._capturedPointers.get(pointerId.toString()) !== primitive) {
+                return false;
+            }
+            // Try to release the pointer on the HTML side
+            try {
+                this.engine.getRenderingCanvas().releasePointerCapture(pointerId);
+            }
+            catch (e) {
+            }
+            this._primPointerInfo.updateRelatedTarget(primitive, BABYLON.Vector2.Zero());
+            this._bubbleNotifyPrimPointerObserver(primitive, BABYLON.PrimitivePointerInfo.PointerLostCapture, null);
+            this._capturedPointers.remove(pointerId.toString());
+            return true;
+        };
+        /**
+         * Determine if the given pointer is captured or not
+         * @param pointerId the Id of the pointer
+         * @return true if it's captured, false otherwise
+         */
+        Canvas2D.prototype.isPointerCaptured = function (pointerId) {
+            return this._capturedPointers.contains(pointerId.toString());
+        };
+        Canvas2D.prototype.getCapturedPrimitive = function (pointerId) {
+            // Avoid unnecessary lookup
+            if (this._capturedPointers.count === 0) {
+                return null;
+            }
+            return this._capturedPointers.get(pointerId.toString());
+        };
+        Canvas2D.prototype._handlePointerEventForInteraction = function (eventData, eventState) {
+            // Update the this._primPointerInfo structure we'll send to observers using the PointerEvent data
+            this._updatePointerInfo(eventData);
+            var capturedPrim = this.getCapturedPrimitive(this._primPointerInfo.pointerId);
+            // Make sure the intersection list is up to date, we maintain this list either in response of a mouse event (here) or before rendering the canvas.
+            // Why before rendering the canvas? because some primitives may move and get away/under the mouse cursor (which is not moving). So we need to update at both location in order to always have an accurate list, which is needed for the hover state change.
+            this._updateIntersectionList(this._primPointerInfo.canvasPointerPos, capturedPrim !== null);
+            // Update the over status, same as above, it's could be done here or during rendering, but will be performed only once per render frame
+            this._updateOverStatus();
+            // Check if we have nothing to raise
+            if (!this._actualOverPrimitive && !capturedPrim) {
+                return;
+            }
+            // Update the relatedTarget info with the over primitive or the captured one (if any)
+            var targetPrim = capturedPrim || this._actualOverPrimitive.prim;
+            var targetPointerPos = capturedPrim ? this._primPointerInfo.canvasPointerPos.subtract(new BABYLON.Vector2(targetPrim.globalTransform.m[12], targetPrim.globalTransform.m[13])) : this._actualOverPrimitive.intersectionLocation;
+            this._primPointerInfo.updateRelatedTarget(targetPrim, targetPointerPos);
+            // Analyze the pointer event type and fire proper events on the primitive
+            if (eventData.type === BABYLON.PointerEventTypes.POINTERWHEEL) {
+                this._bubbleNotifyPrimPointerObserver(targetPrim, BABYLON.PrimitivePointerInfo.PointerMouseWheel, eventData.event);
+            }
+            else if (eventData.type === BABYLON.PointerEventTypes.POINTERMOVE) {
+                this._bubbleNotifyPrimPointerObserver(targetPrim, BABYLON.PrimitivePointerInfo.PointerMove, eventData.event);
+            }
+            else if (eventData.type === BABYLON.PointerEventTypes.POINTERDOWN) {
+                this._bubbleNotifyPrimPointerObserver(targetPrim, BABYLON.PrimitivePointerInfo.PointerDown, eventData.event);
+            }
+            else if (eventData.type === BABYLON.PointerEventTypes.POINTERUP) {
+                this._bubbleNotifyPrimPointerObserver(targetPrim, BABYLON.PrimitivePointerInfo.PointerUp, eventData.event);
+            }
+        };
+        Canvas2D.prototype._updatePointerInfo = function (eventData) {
+            var pii = this._primPointerInfo;
+            if (!pii.canvasPointerPos) {
+                pii.canvasPointerPos = BABYLON.Vector2.Zero();
+            }
+            pii.canvasPointerPos.x = eventData.localPosition.x - this.position.x;
+            pii.canvasPointerPos.y = (this.engine.getRenderHeight() - eventData.localPosition.y) - this.position.y;
+            pii.mouseWheelDelta = 0;
+            if (eventData.type === BABYLON.PointerEventTypes.POINTERWHEEL) {
+                var event = eventData.event;
+                if (event.wheelDelta) {
+                    pii.mouseWheelDelta = event.wheelDelta / (BABYLON.PrimitivePointerInfo.MouseWheelPrecision * 40);
+                }
+                else if (event.detail) {
+                    pii.mouseWheelDelta = -event.detail / BABYLON.PrimitivePointerInfo.MouseWheelPrecision;
+                }
+            }
+            else {
+                var pe = eventData.event;
+                pii.ctrlKey = pe.ctrlKey;
+                pii.altKey = pe.altKey;
+                pii.shiftKey = pe.shiftKey;
+                pii.metaKey = pe.metaKey;
+                pii.button = pe.button;
+                pii.buttons = pe.buttons;
+                pii.pointerId = pe.pointerId;
+                pii.width = pe.width;
+                pii.height = pe.height;
+                pii.presssure = pe.pressure;
+                pii.tilt.x = pe.tiltX;
+                pii.tilt.y = pe.tiltY;
+                pii.isCaptured = this.getCapturedPrimitive(pe.pointerId) !== null;
+            }
+        };
+        Canvas2D.prototype._updateIntersectionList = function (mouseLocalPos, isCapture) {
+            if (this.scene.getRenderId() === this._intersectionRenderId) {
+                return;
+            }
+            var ii = Canvas2D._interInfo;
+            ii.pickPosition.x = mouseLocalPos.x;
+            ii.pickPosition.y = mouseLocalPos.y;
+            ii.findFirstOnly = false;
+            // Fast rejection: test if the mouse pointer is outside the canvas's bounding Info
+            if (!isCapture && !this.boundingInfo.doesIntersect(ii.pickPosition)) {
+                this._previousIntersectionList = this._actualIntersectionList;
+                this._actualIntersectionList = null;
+                this._previousOverPrimitive = this._actualOverPrimitive;
+                this._actualOverPrimitive = null;
+                return;
+            }
+            this._updateCanvasState();
+            this.intersect(ii);
+            this._previousIntersectionList = this._actualIntersectionList;
+            this._actualIntersectionList = ii.intersectedPrimitives;
+            this._previousOverPrimitive = this._actualOverPrimitive;
+            this._actualOverPrimitive = ii.topMostIntersectedPrimitive;
+            this._intersectionRenderId = this.scene.getRenderId();
+        };
+        // Based on the previousIntersectionList and the actualInstersectionList we can determined which primitives are being hover state or loosing it
+        Canvas2D.prototype._updateOverStatus = function () {
+            if ((this.scene.getRenderId() === this._hoverStatusRenderId) || !this._previousIntersectionList || !this._actualIntersectionList) {
+                return;
+            }
+            // Detect a change of over
+            var prevPrim = this._previousOverPrimitive ? this._previousOverPrimitive.prim : null;
+            var actualPrim = this._actualOverPrimitive ? this._actualOverPrimitive.prim : null;
+            if (prevPrim !== actualPrim) {
+                // Detect if the current pointer is captured, only fire event if they belong to the capture primitive
+                var capturedPrim = this.getCapturedPrimitive(this._primPointerInfo.pointerId);
+                // Notify the previous "over" prim that the pointer is no longer over it
+                if ((capturedPrim && capturedPrim === prevPrim) || (!capturedPrim && prevPrim)) {
+                    this._primPointerInfo.updateRelatedTarget(prevPrim, this._previousOverPrimitive.intersectionLocation);
+                    this._bubbleNotifyPrimPointerObserver(prevPrim, BABYLON.PrimitivePointerInfo.PointerOut, null);
+                }
+                // Notify the new "over" prim that the pointer is over it
+                if ((capturedPrim && capturedPrim === actualPrim) || (!capturedPrim && actualPrim)) {
+                    this._primPointerInfo.updateRelatedTarget(actualPrim, this._actualOverPrimitive.intersectionLocation);
+                    this._bubbleNotifyPrimPointerObserver(actualPrim, BABYLON.PrimitivePointerInfo.PointerOver, null);
+                }
+            }
+            this._hoverStatusRenderId = this.scene.getRenderId();
+        };
+        Canvas2D.prototype._updatePrimPointerPos = function (prim) {
+            if (this._primPointerInfo.isCaptured) {
+                this._primPointerInfo.primitivePointerPos = this._primPointerInfo.relatedTargetPointerPos;
+            }
+            else {
+                for (var _i = 0, _a = this._actualIntersectionList; _i < _a.length; _i++) {
+                    var pii = _a[_i];
+                    if (pii.prim === prim) {
+                        this._primPointerInfo.primitivePointerPos = pii.intersectionLocation;
+                        return;
+                    }
+                }
+            }
+        };
+        Canvas2D.prototype._debugExecObserver = function (prim, mask) {
+            if (!this._notifDebugMode) {
+                return;
+            }
+            var debug = "";
+            for (var i = 0; i < prim.hierarchyDepth; i++) {
+                debug += "  ";
+            }
+            var pii = this._primPointerInfo;
+            debug += "[RID:" + this.scene.getRenderId() + "] [" + prim.hierarchyDepth + "] event:" + BABYLON.PrimitivePointerInfo.getEventTypeName(mask) + ", id: " + prim.id + " (" + BABYLON.Tools.getClassName(prim) + "), primPos: " + pii.primitivePointerPos.toString() + ", canvasPos: " + pii.canvasPointerPos.toString();
+            console.log(debug);
+        };
+        Canvas2D.prototype._bubbleNotifyPrimPointerObserver = function (prim, mask, eventData) {
+            var ppi = this._primPointerInfo;
+            // In case of PointerOver/Out we will first notify the children (but the deepest to the closest) with PointerEnter/Leave
+            if ((mask & (BABYLON.PrimitivePointerInfo.PointerOver | BABYLON.PrimitivePointerInfo.PointerOut)) !== 0) {
+                this._notifChildren(prim, mask);
+            }
+            var bubbleCancelled = false;
+            var cur = prim;
+            while (cur) {
+                // Only trigger the observers if the primitive is intersected (except for out)
+                if (!bubbleCancelled) {
+                    this._updatePrimPointerPos(cur);
+                    // Exec the observers
+                    this._debugExecObserver(cur, mask);
+                    cur._pointerEventObservable.notifyObservers(ppi, mask);
+                    this._triggerActionManager(cur, ppi, mask, eventData);
+                    // Bubble canceled? If we're not executing PointerOver or PointerOut, quit immediately
+                    // If it's PointerOver/Out we have to trigger PointerEnter/Leave no matter what
+                    if (ppi.cancelBubble) {
+                        if ((mask & (BABYLON.PrimitivePointerInfo.PointerOver | BABYLON.PrimitivePointerInfo.PointerOut)) === 0) {
+                            return;
+                        }
+                        // We're dealing with PointerOver/Out, let's keep looping to fire PointerEnter/Leave, but not Over/Out anymore
+                        bubbleCancelled = true;
+                    }
+                }
+                // If bubble is cancel we didn't update the Primitive Pointer Pos yet, let's do it
+                if (bubbleCancelled) {
+                    this._updatePrimPointerPos(cur);
+                }
+                // Trigger a PointerEnter corresponding to the PointerOver
+                if (mask === BABYLON.PrimitivePointerInfo.PointerOver) {
+                    this._debugExecObserver(cur, BABYLON.PrimitivePointerInfo.PointerEnter);
+                    cur._pointerEventObservable.notifyObservers(ppi, BABYLON.PrimitivePointerInfo.PointerEnter);
+                }
+                else if (mask === BABYLON.PrimitivePointerInfo.PointerOut) {
+                    this._debugExecObserver(cur, BABYLON.PrimitivePointerInfo.PointerLeave);
+                    cur._pointerEventObservable.notifyObservers(ppi, BABYLON.PrimitivePointerInfo.PointerLeave);
+                }
+                // Loop to the parent
+                cur = cur.parent;
+            }
+        };
+        Canvas2D.prototype._triggerActionManager = function (prim, ppi, mask, eventData) {
+            var _this = this;
+            // Process Trigger related to PointerDown
+            if ((mask & BABYLON.PrimitivePointerInfo.PointerDown) !== 0) {
+                // On pointer down, record the current position and time to be able to trick PickTrigger and LongPressTrigger
+                this._pickStartingPosition = ppi.primitivePointerPos.clone();
+                this._pickStartingTime = new Date().getTime();
+                this._pickedDownPrim = null;
+                if (prim.actionManager) {
+                    this._pickedDownPrim = prim;
+                    if (prim.actionManager.hasPickTriggers) {
+                        var actionEvent = BABYLON.ActionEvent.CreateNewFromPrimitive(prim, ppi.primitivePointerPos, eventData);
+                        switch (eventData.button) {
+                            case 0:
+                                prim.actionManager.processTrigger(BABYLON.ActionManager.OnLeftPickTrigger, actionEvent);
+                                break;
+                            case 1:
+                                prim.actionManager.processTrigger(BABYLON.ActionManager.OnCenterPickTrigger, actionEvent);
+                                break;
+                            case 2:
+                                prim.actionManager.processTrigger(BABYLON.ActionManager.OnRightPickTrigger, actionEvent);
+                                break;
+                        }
+                        prim.actionManager.processTrigger(BABYLON.ActionManager.OnPickDownTrigger, actionEvent);
+                    }
+                    if (prim.actionManager.hasSpecificTrigger(BABYLON.ActionManager.OnLongPressTrigger)) {
+                        window.setTimeout(function () {
+                            var ppi = _this._primPointerInfo;
+                            var capturedPrim = _this.getCapturedPrimitive(ppi.pointerId);
+                            _this._updateIntersectionList(ppi.canvasPointerPos, capturedPrim !== null);
+                            var ii = new BABYLON.IntersectInfo2D();
+                            ii.pickPosition = ppi.canvasPointerPos.clone();
+                            ii.findFirstOnly = false;
+                            _this.intersect(ii);
+                            if (ii.isPrimIntersected(prim) !== null) {
+                                if (prim.actionManager) {
+                                    if (_this._pickStartingTime !== 0 && ((new Date().getTime() - _this._pickStartingTime) > BABYLON.ActionManager.LongPressDelay) && (Math.abs(_this._pickStartingPosition.x - ii.pickPosition.x) < BABYLON.ActionManager.DragMovementThreshold && Math.abs(_this._pickStartingPosition.y - ii.pickPosition.y) < BABYLON.ActionManager.DragMovementThreshold)) {
+                                        _this._pickStartingTime = 0;
+                                        prim.actionManager.processTrigger(BABYLON.ActionManager.OnLongPressTrigger, BABYLON.ActionEvent.CreateNewFromPrimitive(prim, ppi.primitivePointerPos, eventData));
+                                    }
+                                }
+                            }
+                        }, BABYLON.ActionManager.LongPressDelay);
+                    }
+                }
+            }
+            else if ((mask & BABYLON.PrimitivePointerInfo.PointerUp) !== 0) {
+                this._pickStartingTime = 0;
+                var actionEvent = BABYLON.ActionEvent.CreateNewFromPrimitive(prim, ppi.primitivePointerPos, eventData);
+                if (prim.actionManager) {
+                    // OnPickUpTrigger
+                    prim.actionManager.processTrigger(BABYLON.ActionManager.OnPickUpTrigger, actionEvent);
+                    // OnPickTrigger
+                    if (Math.abs(this._pickStartingPosition.x - ppi.canvasPointerPos.x) < BABYLON.ActionManager.DragMovementThreshold && Math.abs(this._pickStartingPosition.y - ppi.canvasPointerPos.y) < BABYLON.ActionManager.DragMovementThreshold) {
+                        prim.actionManager.processTrigger(BABYLON.ActionManager.OnPickTrigger, actionEvent);
+                    }
+                }
+                // OnPickOutTrigger
+                if (this._pickedDownPrim && this._pickedDownPrim.actionManager && (this._pickedDownPrim !== prim)) {
+                    this._pickedDownPrim.actionManager.processTrigger(BABYLON.ActionManager.OnPickOutTrigger, actionEvent);
+                }
+            }
+            else if ((mask & BABYLON.PrimitivePointerInfo.PointerOver) !== 0) {
+                if (prim.actionManager) {
+                    var actionEvent = BABYLON.ActionEvent.CreateNewFromPrimitive(prim, ppi.primitivePointerPos, eventData);
+                    prim.actionManager.processTrigger(BABYLON.ActionManager.OnPointerOverTrigger, actionEvent);
+                }
+            }
+            else if ((mask & BABYLON.PrimitivePointerInfo.PointerOut) !== 0) {
+                if (prim.actionManager) {
+                    var actionEvent = BABYLON.ActionEvent.CreateNewFromPrimitive(prim, ppi.primitivePointerPos, eventData);
+                    prim.actionManager.processTrigger(BABYLON.ActionManager.OnPointerOutTrigger, actionEvent);
+                }
+            }
+        };
+        Canvas2D.prototype._notifChildren = function (prim, mask) {
+            var _this = this;
+            var pii = this._primPointerInfo;
+            prim.children.forEach(function (curChild) {
+                // Recurse first, we want the deepest to be notified first
+                _this._notifChildren(curChild, mask);
+                _this._updatePrimPointerPos(curChild);
+                // Fire the proper notification
+                if (mask === BABYLON.PrimitivePointerInfo.PointerOver) {
+                    _this._debugExecObserver(curChild, BABYLON.PrimitivePointerInfo.PointerEnter);
+                    curChild._pointerEventObservable.notifyObservers(pii, BABYLON.PrimitivePointerInfo.PointerEnter);
+                }
+                else if (mask === BABYLON.PrimitivePointerInfo.PointerOut) {
+                    _this._debugExecObserver(curChild, BABYLON.PrimitivePointerInfo.PointerLeave);
+                    curChild._pointerEventObservable.notifyObservers(pii, BABYLON.PrimitivePointerInfo.PointerLeave);
+                }
+            });
         };
         /**
          * Don't forget to call the dispose method when you're done with the Canvas instance.
@@ -145,6 +504,9 @@ var BABYLON;
             if (!_super.prototype.dispose.call(this)) {
                 return false;
             }
+            if (this.interactionEnabled) {
+                this._setupInteraction(false);
+            }
             if (this._beforeRenderObserver) {
                 this._scene.onBeforeRenderObservable.remove(this._beforeRenderObserver);
                 this._beforeRenderObserver = null;
@@ -280,46 +642,37 @@ var BABYLON;
             enumerable: true,
             configurable: true
         });
-        Object.defineProperty(Canvas2D.prototype, "_engineData", {
-            get: function () {
-                return this.__engineData;
-            },
-            enumerable: true,
-            configurable: true
-        });
-        Canvas2D.prototype.checkBackgroundAvailability = function () {
-            if (this._cachingStrategy === Canvas2D.CACHESTRATEGY_TOPLEVELGROUPS) {
-                throw Error("Can't use Canvas Background with the caching strategy TOPLEVELGROUPS");
-            }
-        };
-        Object.defineProperty(Canvas2D.prototype, "hierarchySiblingZDelta", {
+        Object.defineProperty(Canvas2D.prototype, "interactionEnabled", {
             /**
-             * Read-only property that return the Z delta to apply for each sibling primitives inside of a given one.
-             * Sibling Primitives are defined in a specific order, the first ones will be draw below the next ones.
-             * This property define the Z value to apply between each sibling Primitive. Current implementation allows 1000 Siblings Primitives per level.
-             * @returns The Z Delta
+             * Enable/Disable interaction for this Canvas
+             * When enabled the Prim2DBase.pointerEventObservable property will notified when appropriate events occur
              */
             get: function () {
-                return this._hierarchySiblingZDelta;
+                return this._interactionEnabled;
+            },
+            set: function (enable) {
+                this._setupInteraction(enable);
             },
             enumerable: true,
             configurable: true
         });
-        Object.defineProperty(Canvas2D.prototype, "hierarchyLevelZFactor", {
-            /**
-             * Return the Z Factor that will be applied for each new hierarchy level.
-             * @returns The Z Factor
-             */
+        Object.defineProperty(Canvas2D.prototype, "_engineData", {
             get: function () {
-                return this._hierarchyLevelZFactor;
+                return this.__engineData;
             },
             enumerable: true,
             configurable: true
         });
-        /**
-         * Method that renders the Canvas, you should not invoke
-         */
-        Canvas2D.prototype._render = function () {
+        Canvas2D.prototype.checkBackgroundAvailability = function () {
+            if (this._cachingStrategy === Canvas2D.CACHESTRATEGY_TOPLEVELGROUPS) {
+                throw Error("Can't use Canvas Background with the caching strategy TOPLEVELGROUPS");
+            }
+        };
+        Canvas2D.prototype._updateCanvasState = function () {
+            // Check if the update has already been made for this render Frame
+            if (this.scene.getRenderId() === this._updateRenderId) {
+                return;
+            }
             this._renderingSize.width = this.engine.getRenderWidth();
             this._renderingSize.height = this.engine.getRenderHeight();
             if (this._fitRenderingDevice) {
@@ -333,6 +686,18 @@ var BABYLON;
             ++this._globalTransformProcessStep;
             this.updateGlobalTransVis(false);
             this._prepareGroupRender(context);
+            this._updateRenderId = this.scene.getRenderId();
+        };
+        /**
+         * Method that renders the Canvas, you should not invoke
+         */
+        Canvas2D.prototype._render = function () {
+            this._updateCanvasState();
+            if (this._primPointerInfo.canvasPointerPos) {
+                this._updateIntersectionList(this._primPointerInfo.canvasPointerPos, false);
+                this._updateOverStatus(); // TODO this._primPointerInfo may not be up to date!
+            }
+            var context = new BABYLON.Render2DContext();
             this._groupRender(context);
             // If the canvas is cached at canvas level, we must manually render the sprite that will display its content
             if (this._cachingStrategy === Canvas2D.CACHESTRATEGY_CANVAS && this._cachedCanvasGroup) {
@@ -393,7 +758,7 @@ var BABYLON;
                 }
                 else {
                     var sprite = BABYLON.Sprite2D.Create(parent, "__cachedSpriteOfGroup__" + group.id, group.position.x, group.position.y, map, node.contentSize, node.pos, false);
-                    sprite.origin = BABYLON.Vector2.Zero();
+                    sprite.origin = group.origin.clone();
                     res.sprite = sprite;
                 }
             }
@@ -444,6 +809,7 @@ var BABYLON;
          * Note that you can't use this strategy for WorldSpace Canvas, they need at least a top level group caching.
          */
         Canvas2D.CACHESTRATEGY_DONTCACHE = 4;
+        Canvas2D._interInfo = new BABYLON.IntersectInfo2D();
         /**
          * Define the default size used for both the width and height of a MapTexture to allocate.
          * Note that some MapTexture might be bigger than this size if the first node to allocate is bigger in width or height
@@ -455,6 +821,6 @@ var BABYLON;
             BABYLON.className("Canvas2D")
         ], Canvas2D);
         return Canvas2D;
-    }(BABYLON.Group2D));
+    })(BABYLON.Group2D);
     BABYLON.Canvas2D = Canvas2D;
 })(BABYLON || (BABYLON = {}));

+ 487 - 36
src/Canvas2d/babylon.canvas2d.ts

@@ -51,21 +51,22 @@
          * Create a new 2D ScreenSpace Rendering Canvas, it is a 2D rectangle that has a size (width/height) and a position relative to the top/left corner of the screen.
          * ScreenSpace Canvas will be drawn in the Viewport as a 2D Layer lying to the top of the 3D Scene. Typically used for traditional UI.
          * All caching strategies will be available.
+         * PLEASE NOTE: the origin of a Screen Space Canvas is set to [0;0] (bottom/left) which is different than the default origin of a Primitive which is centered [0.5;0.5]
          * @param scene the Scene that owns the Canvas
          * @param name the name of the Canvas, for information purpose only
          * @param pos the position of the canvas, relative from the bottom/left of the scene's viewport
          * @param size the Size of the canvas. If null two behaviors depend on the cachingStrategy: if it's CACHESTRATEGY_CACHECANVAS then it will always auto-fit the rendering device, in all the other modes it will fit the content of the Canvas
          * @param cachingStrategy either CACHESTRATEGY_TOPLEVELGROUPS, CACHESTRATEGY_ALLGROUPS, CACHESTRATEGY_CANVAS, CACHESTRATEGY_DONTCACHE. Please refer to their respective documentation for more information.
          */
-        static CreateScreenSpace(scene: Scene, name: string, pos: Vector2, size: Size, cachingStrategy: number = Canvas2D.CACHESTRATEGY_TOPLEVELGROUPS): Canvas2D {
+        static CreateScreenSpace(scene: Scene, name: string, pos: Vector2, size: Size, cachingStrategy: number = Canvas2D.CACHESTRATEGY_TOPLEVELGROUPS, enableInteraction: boolean = true): Canvas2D {
             let c = new Canvas2D();
-            c.setupCanvas(scene, name, size, true, cachingStrategy);
+            c.setupCanvas(scene, name, size, true, cachingStrategy, enableInteraction);
             c.position = pos;
+            c.origin = Vector2.Zero();
 
             return c;
         }
 
-
         /**
          * Create a new 2D WorldSpace Rendering Canvas, it is a 2D rectangle that has a size (width/height) and a world transformation information to place it in the world space.
          * This kind of canvas can't have its Primitives directly drawn in the Viewport, they need to be cached in a bitmap at some point, as a consequence the DONT_CACHE strategy is unavailable. For now only CACHESTRATEGY_CANVAS is supported, but the remaining strategies will be soon.
@@ -80,7 +81,7 @@
          * @param sideOrientation Unexpected behavior occur if the value is different from Mesh.DEFAULTSIDE right now, so please use this one.
          * @param cachingStrategy Must be CACHESTRATEGY_CANVAS for now
          */
-        static CreateWorldSpace(scene: Scene, name: string, position: Vector3, rotation: Quaternion, size: Size, renderScaleFactor: number=1, sideOrientation?: number, cachingStrategy: number = Canvas2D.CACHESTRATEGY_TOPLEVELGROUPS): Canvas2D {
+        static CreateWorldSpace(scene: Scene, name: string, position: Vector3, rotation: Quaternion, size: Size, renderScaleFactor: number = 1, sideOrientation?: number, cachingStrategy: number = Canvas2D.CACHESTRATEGY_TOPLEVELGROUPS, enableInteraction: boolean = true): Canvas2D {
             if (cachingStrategy !== Canvas2D.CACHESTRATEGY_CANVAS) {
                 throw new Error("Right now only the CACHESTRATEGY_CANVAS cache Strategy is supported for WorldSpace Canvas. More will come soon!");
             }
@@ -94,7 +95,7 @@
             }
 
             let c = new Canvas2D();
-            c.setupCanvas(scene, name, new Size(size.width*renderScaleFactor, size.height*renderScaleFactor), false, cachingStrategy);
+            c.setupCanvas(scene, name, new Size(size.width*renderScaleFactor, size.height*renderScaleFactor), false, cachingStrategy, enableInteraction);
 
             let plane = new WorldSpaceCanvas2d(name, scene, c);
             let vertexData = VertexData.CreatePlane({ width: size.width/2, height: size.height/2, sideOrientation: sideOrientation });
@@ -114,7 +115,7 @@
             return c;
         }
 
-        protected setupCanvas(scene: Scene, name: string, size: Size, isScreenSpace: boolean = true, cachingstrategy: number = Canvas2D.CACHESTRATEGY_TOPLEVELGROUPS) {
+        protected setupCanvas(scene: Scene, name: string, size: Size, isScreenSpace: boolean, cachingstrategy: number, enableInteraction: boolean) {
             let engine = scene.getEngine();
             this._fitRenderingDevice = !size;
             if (!size) {
@@ -122,14 +123,15 @@
             }
             this.__engineData = engine.getOrAddExternalDataWithFactory("__BJSCANVAS2D__", k => new Canvas2DEngineBoundData());
             this._cachingStrategy = cachingstrategy;
-            this._depthLevel = 0;
-            this._hierarchyMaxDepth = 100;
-            this._hierarchyLevelZFactor = 1 / this._hierarchyMaxDepth;
-            this._hierarchyLevelMaxSiblingCount = 1000;
-            this._hierarchySiblingZDelta = this._hierarchyLevelZFactor / this._hierarchyLevelMaxSiblingCount;
+            this._primPointerInfo = new PrimitivePointerInfo();
+            this._capturedPointers = new StringDictionary<Prim2DBase>();
+            this._pickStartingPosition = Vector2.Zero();
 
             this.setupGroup2D(this, null, name, Vector2.Zero(), size, this._cachingStrategy===Canvas2D.CACHESTRATEGY_ALLGROUPS ? Group2D.GROUPCACHEBEHAVIOR_DONTCACHEOVERRIDE : Group2D.GROUPCACHEBEHAVIOR_FOLLOWCACHESTRATEGY);
 
+            this._hierarchyLevelMaxSiblingCount = 100;
+            this._hierarchyDepthOffset = 0;
+            this._siblingDepthOffset = 1 / this._hierarchyLevelMaxSiblingCount;
             this._scene = scene;
             this._engine = engine;
             this._renderingSize = new Size(0, 0);
@@ -141,6 +143,7 @@
 
             if (cachingstrategy !== Canvas2D.CACHESTRATEGY_TOPLEVELGROUPS) {
                 this._background = Rectangle2D.Create(this, "###CANVAS BACKGROUND###", 0, 0, size.width, size.height);
+                this._background.isPickable = false;
                 this._background.origin = Vector2.Zero();
                 this._background.levelVisible = false;
             }
@@ -159,6 +162,425 @@
 
             this._supprtInstancedArray = this._engine.getCaps().instancedArrays !== null;
 //            this._supprtInstancedArray = false; // TODO REMOVE!!!
+
+            this._setupInteraction(enableInteraction);
+        }
+
+        public get hierarchyLevelMaxSiblingCount(): number {
+            return this._hierarchyLevelMaxSiblingCount;
+        }
+
+        private _setupInteraction(enable: boolean) {
+            // No change detection
+            if (enable === this._interactionEnabled) {
+                return;
+            }
+
+            // Set the new state
+            this._interactionEnabled = enable;
+
+            // Disable interaction
+            if (!enable) {
+                if (this._scenePrePointerObserver) {
+                    this.scene.onPrePointerObservable.remove(this._scenePrePointerObserver);
+                    this._scenePrePointerObserver = null;
+                }
+
+                return;
+            }
+
+            // Enable Interaction
+
+            // Register the observable
+            this.scene.onPrePointerObservable.add((e, s) => this._handlePointerEventForInteraction(e, s));
+        }
+
+        /**
+         * Internal method, you should use the Prim2DBase version instead
+         */
+        public _setPointerCapture(pointerId: number, primitive: Prim2DBase): boolean {
+            if (this.isPointerCaptured(pointerId)) {
+                return false;
+            }
+
+            // Try to capture the pointer on the HTML side
+            try {
+                this.engine.getRenderingCanvas().setPointerCapture(pointerId);
+            } catch (e) {
+                //Nothing to do with the error. Execution will continue.
+            }
+
+            this._primPointerInfo.updateRelatedTarget(primitive, Vector2.Zero());
+            this._bubbleNotifyPrimPointerObserver(primitive, PrimitivePointerInfo.PointerGotCapture, null);
+
+            this._capturedPointers.add(pointerId.toString(), primitive);
+            return true;
+        }
+
+        /**
+         * Internal method, you should use the Prim2DBase version instead
+         */
+        public _releasePointerCapture(pointerId: number, primitive: Prim2DBase): boolean {
+            if (this._capturedPointers.get(pointerId.toString()) !== primitive) {
+                return false;
+            }
+
+            // Try to release the pointer on the HTML side
+            try {
+                this.engine.getRenderingCanvas().releasePointerCapture(pointerId);
+            } catch (e) {
+                //Nothing to do with the error. Execution will continue.
+            }
+
+            this._primPointerInfo.updateRelatedTarget(primitive, Vector2.Zero());
+            this._bubbleNotifyPrimPointerObserver(primitive, PrimitivePointerInfo.PointerLostCapture, null);
+            this._capturedPointers.remove(pointerId.toString());
+            return true;
+        }
+
+        /**
+         * Determine if the given pointer is captured or not
+         * @param pointerId the Id of the pointer
+         * @return true if it's captured, false otherwise
+         */
+        public isPointerCaptured(pointerId: number): boolean {
+            return this._capturedPointers.contains(pointerId.toString());
+        }
+
+        private getCapturedPrimitive(pointerId: number): Prim2DBase {
+            // Avoid unnecessary lookup
+            if (this._capturedPointers.count === 0) {
+                return null;
+            }
+            return this._capturedPointers.get(pointerId.toString());
+        }
+           
+        private static _interInfo = new IntersectInfo2D();
+        private _handlePointerEventForInteraction(eventData: PointerInfoPre, eventState: EventState) {
+            // Update the this._primPointerInfo structure we'll send to observers using the PointerEvent data
+            this._updatePointerInfo(eventData);
+
+            let capturedPrim = this.getCapturedPrimitive(this._primPointerInfo.pointerId);
+
+            // Make sure the intersection list is up to date, we maintain this list either in response of a mouse event (here) or before rendering the canvas.
+            // Why before rendering the canvas? because some primitives may move and get away/under the mouse cursor (which is not moving). So we need to update at both location in order to always have an accurate list, which is needed for the hover state change.
+            this._updateIntersectionList(this._primPointerInfo.canvasPointerPos, capturedPrim!==null);
+
+            // Update the over status, same as above, it's could be done here or during rendering, but will be performed only once per render frame
+            this._updateOverStatus();
+
+            // Check if we have nothing to raise
+            if (!this._actualOverPrimitive && !capturedPrim) {
+                return;
+            }
+
+            // Update the relatedTarget info with the over primitive or the captured one (if any)
+            let targetPrim = capturedPrim || this._actualOverPrimitive.prim;
+
+            let targetPointerPos = capturedPrim ? this._primPointerInfo.canvasPointerPos.subtract(new Vector2(targetPrim.globalTransform.m[12], targetPrim.globalTransform.m[13])) : this._actualOverPrimitive.intersectionLocation;
+
+            this._primPointerInfo.updateRelatedTarget(targetPrim, targetPointerPos);
+
+            // Analyze the pointer event type and fire proper events on the primitive
+
+            if (eventData.type === PointerEventTypes.POINTERWHEEL) {
+                this._bubbleNotifyPrimPointerObserver(targetPrim, PrimitivePointerInfo.PointerMouseWheel, <MouseWheelEvent>eventData.event);
+            } else if (eventData.type === PointerEventTypes.POINTERMOVE) {
+                this._bubbleNotifyPrimPointerObserver(targetPrim, PrimitivePointerInfo.PointerMove, <PointerEvent>eventData.event);
+            } else if (eventData.type === PointerEventTypes.POINTERDOWN) {
+                this._bubbleNotifyPrimPointerObserver(targetPrim, PrimitivePointerInfo.PointerDown, <PointerEvent>eventData.event);
+            } else if (eventData.type === PointerEventTypes.POINTERUP) {
+                this._bubbleNotifyPrimPointerObserver(targetPrim, PrimitivePointerInfo.PointerUp, <PointerEvent>eventData.event);
+            }
+        }
+
+        private _updatePointerInfo(eventData: PointerInfoPre) {
+            let pii = this._primPointerInfo;
+            if (!pii.canvasPointerPos) {
+                pii.canvasPointerPos = Vector2.Zero();
+            }
+            pii.canvasPointerPos.x = eventData.localPosition.x - this.position.x;
+            pii.canvasPointerPos.y = (this.engine.getRenderHeight() - eventData.localPosition.y) - this.position.y;
+            pii.mouseWheelDelta = 0;
+
+            if (eventData.type === PointerEventTypes.POINTERWHEEL) {
+                var event = <MouseWheelEvent>eventData.event;
+                if (event.wheelDelta) {
+                    pii.mouseWheelDelta = event.wheelDelta / (PrimitivePointerInfo.MouseWheelPrecision * 40);
+                } else if (event.detail) {
+                    pii.mouseWheelDelta = -event.detail / PrimitivePointerInfo.MouseWheelPrecision;
+                }
+            } else {
+                var pe         = <PointerEvent>eventData.event;
+                pii.ctrlKey    = pe.ctrlKey;
+                pii.altKey     = pe.altKey;
+                pii.shiftKey   = pe.shiftKey;
+                pii.metaKey    = pe.metaKey;
+                pii.button     = pe.button;
+                pii.buttons    = pe.buttons;
+                pii.pointerId  = pe.pointerId;
+                pii.width      = pe.width;
+                pii.height     = pe.height;
+                pii.presssure  = pe.pressure;
+                pii.tilt.x     = pe.tiltX;
+                pii.tilt.y     = pe.tiltY;
+                pii.isCaptured = this.getCapturedPrimitive(pe.pointerId)!==null;
+            }
+        }
+
+        private _updateIntersectionList(mouseLocalPos: Vector2, isCapture: boolean) {
+            if (this.scene.getRenderId() === this._intersectionRenderId) {
+                return;
+            }
+
+            let ii = Canvas2D._interInfo;
+            ii.pickPosition.x = mouseLocalPos.x;
+            ii.pickPosition.y = mouseLocalPos.y;
+            ii.findFirstOnly = false;
+
+            // Fast rejection: test if the mouse pointer is outside the canvas's bounding Info
+            if (!isCapture && !this.boundingInfo.doesIntersect(ii.pickPosition)) {
+                this._previousIntersectionList = this._actualIntersectionList;
+                this._actualIntersectionList   = null;
+                this._previousOverPrimitive    = this._actualOverPrimitive;
+                this._actualOverPrimitive      = null;
+                return;
+            }
+
+            this._updateCanvasState();
+
+            this.intersect(ii);
+
+            this._previousIntersectionList = this._actualIntersectionList;
+            this._actualIntersectionList   = ii.intersectedPrimitives;
+            this._previousOverPrimitive    = this._actualOverPrimitive;
+            this._actualOverPrimitive      = ii.topMostIntersectedPrimitive;
+
+            this._intersectionRenderId = this.scene.getRenderId();
+        }
+
+        // Based on the previousIntersectionList and the actualInstersectionList we can determined which primitives are being hover state or loosing it
+        private _updateOverStatus() {
+            if ((this.scene.getRenderId() === this._hoverStatusRenderId) || !this._previousIntersectionList || !this._actualIntersectionList) {
+                return;
+            }
+
+            // Detect a change of over
+            let prevPrim = this._previousOverPrimitive ? this._previousOverPrimitive.prim : null;
+            let actualPrim = this._actualOverPrimitive ? this._actualOverPrimitive.prim   : null;
+
+            if (prevPrim !== actualPrim) {
+                // Detect if the current pointer is captured, only fire event if they belong to the capture primitive
+                let capturedPrim = this.getCapturedPrimitive(this._primPointerInfo.pointerId);
+
+                // Notify the previous "over" prim that the pointer is no longer over it
+                if ((capturedPrim && capturedPrim===prevPrim) || (!capturedPrim && prevPrim)) {
+                    this._primPointerInfo.updateRelatedTarget(prevPrim, this._previousOverPrimitive.intersectionLocation);
+                    this._bubbleNotifyPrimPointerObserver(prevPrim, PrimitivePointerInfo.PointerOut, null);
+                }
+
+                // Notify the new "over" prim that the pointer is over it
+                if ((capturedPrim && capturedPrim === actualPrim) || (!capturedPrim && actualPrim)) {
+                    this._primPointerInfo.updateRelatedTarget(actualPrim, this._actualOverPrimitive.intersectionLocation);
+                    this._bubbleNotifyPrimPointerObserver(actualPrim, PrimitivePointerInfo.PointerOver, null);
+                }
+            }
+
+            this._hoverStatusRenderId = this.scene.getRenderId();
+        }
+
+        private _updatePrimPointerPos(prim: Prim2DBase) {
+            if (this._primPointerInfo.isCaptured) {
+                this._primPointerInfo.primitivePointerPos = this._primPointerInfo.relatedTargetPointerPos;
+            } else {
+                for (let pii of this._actualIntersectionList) {
+                    if (pii.prim === prim) {
+                        this._primPointerInfo.primitivePointerPos = pii.intersectionLocation;
+                        return;
+                    }
+                }
+            }
+        }
+
+        private _notifDebugMode = false;
+        private _debugExecObserver(prim: Prim2DBase, mask: number) {
+            if (!this._notifDebugMode) {
+                return;
+            }
+
+            let debug = "";
+            for (let i = 0; i < prim.hierarchyDepth; i++) {
+                debug += "  ";
+            }
+
+            let pii = this._primPointerInfo;
+            debug += `[RID:${this.scene.getRenderId()}] [${prim.hierarchyDepth}] event:${PrimitivePointerInfo.getEventTypeName(mask)}, id: ${prim.id} (${Tools.getClassName(prim)}), primPos: ${pii.primitivePointerPos.toString()}, canvasPos: ${pii.canvasPointerPos.toString()}`;
+            console.log(debug);
+        }
+
+        private _bubbleNotifyPrimPointerObserver(prim: Prim2DBase, mask: number, eventData: any) {
+            let ppi = this._primPointerInfo;
+
+            // In case of PointerOver/Out we will first notify the children (but the deepest to the closest) with PointerEnter/Leave
+            if ((mask & (PrimitivePointerInfo.PointerOver | PrimitivePointerInfo.PointerOut)) !== 0) {
+                this._notifChildren(prim, mask);
+            }
+
+            let bubbleCancelled = false;
+            let cur = prim;
+            while (cur) {
+                // Only trigger the observers if the primitive is intersected (except for out)
+                if (!bubbleCancelled) {
+                    this._updatePrimPointerPos(cur);
+
+                    // Exec the observers
+                    this._debugExecObserver(cur, mask);
+                    cur._pointerEventObservable.notifyObservers(ppi, mask);
+                    this._triggerActionManager(cur, ppi, mask, eventData);
+
+                    // Bubble canceled? If we're not executing PointerOver or PointerOut, quit immediately
+                    // If it's PointerOver/Out we have to trigger PointerEnter/Leave no matter what
+                    if (ppi.cancelBubble) {
+                        if ((mask & (PrimitivePointerInfo.PointerOver | PrimitivePointerInfo.PointerOut)) === 0) {
+                            return;
+                        }
+
+                        // We're dealing with PointerOver/Out, let's keep looping to fire PointerEnter/Leave, but not Over/Out anymore
+                        bubbleCancelled = true;
+                    }
+                }
+
+                // If bubble is cancel we didn't update the Primitive Pointer Pos yet, let's do it
+                if (bubbleCancelled) {
+                    this._updatePrimPointerPos(cur);
+                }
+
+                // Trigger a PointerEnter corresponding to the PointerOver
+                if (mask === PrimitivePointerInfo.PointerOver) {
+                    this._debugExecObserver(cur, PrimitivePointerInfo.PointerEnter);
+                    cur._pointerEventObservable.notifyObservers(ppi, PrimitivePointerInfo.PointerEnter);
+                }
+
+                // Trigger a PointerLeave corresponding to the PointerOut
+                else if (mask === PrimitivePointerInfo.PointerOut) {
+                    this._debugExecObserver(cur, PrimitivePointerInfo.PointerLeave);
+                    cur._pointerEventObservable.notifyObservers(ppi, PrimitivePointerInfo.PointerLeave);
+                }
+
+                // Loop to the parent
+                cur = cur.parent;
+            }
+        }
+
+        private _triggerActionManager(prim: Prim2DBase, ppi: PrimitivePointerInfo, mask: number, eventData) {
+
+            // Process Trigger related to PointerDown
+            if ((mask & PrimitivePointerInfo.PointerDown) !== 0) {
+                // On pointer down, record the current position and time to be able to trick PickTrigger and LongPressTrigger
+                this._pickStartingPosition = ppi.primitivePointerPos.clone();
+                this._pickStartingTime = new Date().getTime();
+                this._pickedDownPrim = null;
+
+                if (prim.actionManager) {
+                    this._pickedDownPrim = prim;
+                    if (prim.actionManager.hasPickTriggers) {
+                        let actionEvent = ActionEvent.CreateNewFromPrimitive(prim, ppi.primitivePointerPos, eventData);
+
+                        switch (eventData.button) {
+                        case 0:
+                            prim.actionManager.processTrigger(ActionManager.OnLeftPickTrigger, actionEvent);
+                            break;
+                        case 1:
+                            prim.actionManager.processTrigger(ActionManager.OnCenterPickTrigger, actionEvent);
+                            break;
+                        case 2:
+                            prim.actionManager.processTrigger(ActionManager.OnRightPickTrigger, actionEvent);
+                            break;
+                        }
+                        prim.actionManager.processTrigger(ActionManager.OnPickDownTrigger, actionEvent);
+                    }
+
+                    if (prim.actionManager.hasSpecificTrigger(ActionManager.OnLongPressTrigger)) {
+                        window.setTimeout(() => {
+                            let ppi = this._primPointerInfo;
+                            let capturedPrim = this.getCapturedPrimitive(ppi.pointerId);
+                            this._updateIntersectionList(ppi.canvasPointerPos, capturedPrim !== null);
+
+                            let ii = new IntersectInfo2D();
+                            ii.pickPosition = ppi.canvasPointerPos.clone();
+                            ii.findFirstOnly = false;
+                            this.intersect(ii);
+
+                            if (ii.isPrimIntersected(prim) !== null) {
+                                if (prim.actionManager) {
+                                    if (this._pickStartingTime !== 0 && ((new Date().getTime() - this._pickStartingTime) > ActionManager.LongPressDelay) && (Math.abs(this._pickStartingPosition.x - ii.pickPosition.x) < ActionManager.DragMovementThreshold && Math.abs(this._pickStartingPosition.y - ii.pickPosition.y) < ActionManager.DragMovementThreshold)) {
+                                        this._pickStartingTime = 0;
+                                        prim.actionManager.processTrigger(ActionManager.OnLongPressTrigger, ActionEvent.CreateNewFromPrimitive(prim, ppi.primitivePointerPos, eventData));
+                                    }
+                                }
+                            }
+                        }, ActionManager.LongPressDelay);
+                    }
+                }
+            }
+
+            // Process Triggers related to Pointer Up
+            else if ((mask & PrimitivePointerInfo.PointerUp) !== 0) {
+                this._pickStartingTime = 0;
+
+                let actionEvent = ActionEvent.CreateNewFromPrimitive(prim, ppi.primitivePointerPos, eventData);
+                if (prim.actionManager) {
+                    // OnPickUpTrigger
+                    prim.actionManager.processTrigger(ActionManager.OnPickUpTrigger, actionEvent);
+
+                    // OnPickTrigger
+                    if (Math.abs(this._pickStartingPosition.x - ppi.canvasPointerPos.x) < ActionManager.DragMovementThreshold && Math.abs(this._pickStartingPosition.y - ppi.canvasPointerPos.y) < ActionManager.DragMovementThreshold) {
+                        prim.actionManager.processTrigger(ActionManager.OnPickTrigger, actionEvent);
+                    }
+                }
+
+                // OnPickOutTrigger
+                if (this._pickedDownPrim && this._pickedDownPrim.actionManager && (this._pickedDownPrim !== prim)) {
+                    this._pickedDownPrim.actionManager.processTrigger(ActionManager.OnPickOutTrigger, actionEvent);
+                }
+            }
+
+            else if ((mask & PrimitivePointerInfo.PointerOver) !== 0) {
+                if (prim.actionManager) {
+                    let actionEvent = ActionEvent.CreateNewFromPrimitive(prim, ppi.primitivePointerPos, eventData);
+                    prim.actionManager.processTrigger(ActionManager.OnPointerOverTrigger, actionEvent);
+                }
+            }
+
+            else if ((mask & PrimitivePointerInfo.PointerOut) !== 0) {
+                if (prim.actionManager) {
+                    let actionEvent = ActionEvent.CreateNewFromPrimitive(prim, ppi.primitivePointerPos, eventData);
+                    prim.actionManager.processTrigger(ActionManager.OnPointerOutTrigger, actionEvent);
+                }
+            }
+        }
+
+        _notifChildren(prim: Prim2DBase, mask: number) {
+            let pii = this._primPointerInfo;
+
+            prim.children.forEach(curChild => {
+                // Recurse first, we want the deepest to be notified first
+                this._notifChildren(curChild, mask);
+
+                this._updatePrimPointerPos(curChild);
+
+                // Fire the proper notification
+                if (mask === PrimitivePointerInfo.PointerOver) {
+                    this._debugExecObserver(curChild, PrimitivePointerInfo.PointerEnter);
+                    curChild._pointerEventObservable.notifyObservers(pii, PrimitivePointerInfo.PointerEnter);
+                }
+
+                // Trigger a PointerLeave corresponding to the PointerOut
+                else if (mask === PrimitivePointerInfo.PointerOut) {
+                    this._debugExecObserver(curChild, PrimitivePointerInfo.PointerLeave);
+                    curChild._pointerEventObservable.notifyObservers(pii, PrimitivePointerInfo.PointerLeave);
+                }
+            });
         }
 
         /**
@@ -170,6 +592,10 @@
                 return false;
             }
 
+            if (this.interactionEnabled) {
+                this._setupInteraction(false);
+            }
+
             if (this._beforeRenderObserver) {
                 this._scene.onBeforeRenderObservable.remove(this._beforeRenderObserver);
                 this._beforeRenderObserver = null;
@@ -293,6 +719,18 @@
             this._background.levelVisible = true;
         }
 
+        /**
+         * Enable/Disable interaction for this Canvas
+         * When enabled the Prim2DBase.pointerEventObservable property will notified when appropriate events occur
+         */
+        public get interactionEnabled(): boolean {
+            return this._interactionEnabled;
+        }
+
+        public set interactionEnabled(enable: boolean) {
+            this._setupInteraction(enable);
+        }
+
         public get _engineData(): Canvas2DEngineBoundData {
             return this.__engineData;
         }
@@ -303,25 +741,22 @@
             }
         }
 
-        /**
-         * Read-only property that return the Z delta to apply for each sibling primitives inside of a given one.
-         * Sibling Primitives are defined in a specific order, the first ones will be draw below the next ones.
-         * This property define the Z value to apply between each sibling Primitive. Current implementation allows 1000 Siblings Primitives per level.
-         * @returns The Z Delta
-         */
-        public get hierarchySiblingZDelta(): number {
-            return this._hierarchySiblingZDelta;
-        }
-
-        /**
-         * Return the Z Factor that will be applied for each new hierarchy level.
-         * @returns The Z Factor
-         */
-        public get hierarchyLevelZFactor(): number {
-            return this._hierarchyLevelZFactor;
-        }
 
         private __engineData: Canvas2DEngineBoundData;
+        private _interactionEnabled: boolean;
+        private _primPointerInfo: PrimitivePointerInfo;
+        private _updateRenderId: number;
+        private _intersectionRenderId: number;
+        private _hoverStatusRenderId: number;
+        private _pickStartingPosition: Vector2;
+        private _pickedDownPrim: Prim2DBase;
+        private _pickStartingTime: number;
+        private _previousIntersectionList: Array<PrimitiveIntersectedInfo>;
+        private _actualIntersectionList: Array<PrimitiveIntersectedInfo>;
+        private _previousOverPrimitive: PrimitiveIntersectedInfo;
+        private _actualOverPrimitive: PrimitiveIntersectedInfo;
+        private _capturedPointers: StringDictionary<Prim2DBase>;
+        private _scenePrePointerObserver: Observer<PointerInfoPre>;
         private _worldSpaceNode: WorldSpaceCanvas2d;
         private _mapCounter = 0;
         private _background: Rectangle2D;
@@ -331,10 +766,7 @@
         private _isScreeSpace: boolean;
         private _cachedCanvasGroup: Group2D;
         private _cachingStrategy: number;
-        private _hierarchyMaxDepth: number;
-        private _hierarchyLevelZFactor: number;
         private _hierarchyLevelMaxSiblingCount: number;
-        private _hierarchySiblingZDelta: number;
         private _groupCacheMaps: MapTexture[];
         private _beforeRenderObserver: Observer<Scene>;
         private _afterRenderObserver: Observer<Scene>;
@@ -342,10 +774,12 @@
 
         public _renderingSize: Size;
 
-        /**
-         * Method that renders the Canvas, you should not invoke
-         */
-        private _render() {
+        private _updateCanvasState() {
+            // Check if the update has already been made for this render Frame
+            if (this.scene.getRenderId() === this._updateRenderId) {
+                return;
+            }
+
             this._renderingSize.width = this.engine.getRenderWidth();
             this._renderingSize.height = this.engine.getRenderHeight();
 
@@ -363,6 +797,23 @@
             this.updateGlobalTransVis(false);
 
             this._prepareGroupRender(context);
+
+            this._updateRenderId = this.scene.getRenderId();
+        }
+
+        /**
+         * Method that renders the Canvas, you should not invoke
+         */
+        private _render() {
+
+            this._updateCanvasState();
+
+            if (this._primPointerInfo.canvasPointerPos) {
+                this._updateIntersectionList(this._primPointerInfo.canvasPointerPos, false);
+                this._updateOverStatus();   // TODO this._primPointerInfo may not be up to date!
+            }
+
+            var context = new Render2DContext();
             this._groupRender(context);
 
             // If the canvas is cached at canvas level, we must manually render the sprite that will display its content
@@ -434,7 +885,7 @@
                 // Create a Sprite that will be used to render this cache, the "__cachedSpriteOfGroup__" starting id is a hack to bypass exception throwing in case of the Canvas doesn't normally allows direct primitives
                 else {
                     let sprite = Sprite2D.Create(parent, `__cachedSpriteOfGroup__${group.id}`, group.position.x, group.position.y, map, node.contentSize, node.pos, false);
-                    sprite.origin = Vector2.Zero();
+                    sprite.origin = group.origin.clone();
                     res.sprite = sprite;
                 }
             }

+ 13 - 5
src/Canvas2d/babylon.group2d.js

@@ -32,6 +32,7 @@ var BABYLON;
         Group2D._createCachedCanvasGroup = function (owner) {
             var g = new Group2D();
             g.setupGroup2D(owner, null, "__cachedCanvasGroup__", BABYLON.Vector2.Zero());
+            g.origin = BABYLON.Vector2.Zero();
             return g;
         };
         Group2D.prototype.applyCachedTexture = function (vertexData, material) {
@@ -165,6 +166,10 @@ var BABYLON;
             this._prepareGroupRender(context);
             this._groupRender(context);
         };
+        Group2D.prototype.levelIntersect = function (intersectInfo) {
+            // If we've made it so far it means the boundingInfo intersection test succeed, the Group2D is shaped the same, so we always return true
+            return true;
+        };
         Group2D.prototype.updateLevelBoundingInfo = function () {
             var size;
             // If the size is set by the user, the boundingInfo is computed from this value
@@ -237,7 +242,7 @@ var BABYLON;
                     sortedDirtyList.forEach(function (p) {
                         // We need to check if prepare is needed because even if the primitive is in the dirtyList, its parent primitive may also have been modified, then prepared, then recurse on its children primitives (this one for instance) if the changes where impacting them.
                         // For instance: a Rect's position change, the position of its children primitives will also change so a prepare will be call on them. If a child was in the dirtyList we will avoid a second prepare by making this check.
-                        if (!p.isDisposed && p.needPrepare()) {
+                        if (!p.isDisposed && p._needPrepare()) {
                             p._prepareRender(context);
                         }
                     });
@@ -268,7 +273,7 @@ var BABYLON;
                     var curVP = engine.setDirectViewport(this._viewportPosition.x, this._viewportPosition.y, this._viewportSize.width, this._viewportSize.height);
                 }
                 // For each different model of primitive to render
-                var totalRenderCount_1 = 0;
+                var totalRenderCount = 0;
                 this._renderGroupInstancesInfo.forEach(function (k, v) {
                     // This part will pack the dynamicfloatarray and update the instanced array WebGLBufffer
                     // Skip it if instanced arrays are not supported
@@ -277,7 +282,7 @@ var BABYLON;
                             // If the instances of the model was changed, pack the data
                             var array = v._instancesPartsData[i];
                             var instanceData_1 = array.pack();
-                            totalRenderCount_1 += array.usedElementCount;
+                            totalRenderCount += array.usedElementCount;
                             // Compute the size the instance buffer should have
                             var neededSize = array.usedElementCount * array.stride * 4;
                             // Check if we have to (re)create the instancesBuffer because there's none or the size is too small
@@ -300,7 +305,7 @@ var BABYLON;
                         }
                     }
                     // Submit render only if we have something to render (everything may be hidden and the floatarray empty)
-                    if (!_this.owner.supportInstancedArray || totalRenderCount_1 > 0) {
+                    if (!_this.owner.supportInstancedArray || totalRenderCount > 0) {
                         // render all the instances of this model, if the render method returns true then our instances are no longer dirty
                         var renderFailed = !v._modelCache.render(v, context);
                         // Update dirty flag/related
@@ -367,6 +372,9 @@ var BABYLON;
             else if (prop.id === BABYLON.Prim2DBase.scaleProperty.id) {
                 this._cacheRenderSprite.scale = this.scale;
             }
+            else if (prop.id === BABYLON.Prim2DBase.originProperty.id) {
+                this._cacheRenderSprite.origin = this.origin.clone();
+            }
             else if (prop.id === Group2D.actualSizeProperty.id) {
                 this._cacheRenderSprite.spriteSize = this.actualSize.clone();
             }
@@ -452,6 +460,6 @@ var BABYLON;
             BABYLON.className("Group2D")
         ], Group2D);
         return Group2D;
-    }(BABYLON.Prim2DBase));
+    })(BABYLON.Prim2DBase);
     BABYLON.Group2D = Group2D;
 })(BABYLON || (BABYLON = {}));

+ 9 - 2
src/Canvas2d/babylon.group2d.ts

@@ -43,7 +43,7 @@
         static _createCachedCanvasGroup(owner: Canvas2D): Group2D {
             var g = new Group2D();
             g.setupGroup2D(owner, null, "__cachedCanvasGroup__", Vector2.Zero());
-
+            g.origin = Vector2.Zero();
             return g;
             
         }
@@ -184,6 +184,11 @@
             this._groupRender(context);
         }
 
+        protected levelIntersect(intersectInfo: IntersectInfo2D): boolean {
+            // If we've made it so far it means the boundingInfo intersection test succeed, the Group2D is shaped the same, so we always return true
+            return true;
+        }
+
         protected updateLevelBoundingInfo() {
             let size: Size;
 
@@ -267,7 +272,7 @@
 
                         // We need to check if prepare is needed because even if the primitive is in the dirtyList, its parent primitive may also have been modified, then prepared, then recurse on its children primitives (this one for instance) if the changes where impacting them.
                         // For instance: a Rect's position change, the position of its children primitives will also change so a prepare will be call on them. If a child was in the dirtyList we will avoid a second prepare by making this check.
-                        if (!p.isDisposed && p.needPrepare()) {
+                        if (!p.isDisposed && p._needPrepare()) {
                             p._prepareRender(context);
                         }
                     });
@@ -412,6 +417,8 @@
                 this._cacheRenderSprite.rotation = this.rotation;
             } else if (prop.id === Prim2DBase.scaleProperty.id) {
                 this._cacheRenderSprite.scale = this.scale;
+            } else if (prop.id === Prim2DBase.originProperty.id) {
+                this._cacheRenderSprite.origin = this.origin.clone();
             } else if (prop.id === Group2D.actualSizeProperty.id) {
                 this._cacheRenderSprite.spriteSize = this.actualSize.clone();
                 //console.log(`[${this._globalTransformProcessStep}] Sync Sprite ${this.id}, width: ${this.actualSize.width}, height: ${this.actualSize.height}`);

+ 2 - 2
src/Canvas2d/babylon.modelRenderCache.js

@@ -31,7 +31,7 @@ var BABYLON;
             return true;
         };
         return GroupInstanceInfo;
-    }());
+    })();
     BABYLON.GroupInstanceInfo = GroupInstanceInfo;
     var ModelRenderCache = (function () {
         function ModelRenderCache(engine, modelKey, isTransparent) {
@@ -178,6 +178,6 @@ var BABYLON;
         ModelRenderCache.v3 = BABYLON.Vector3.Zero();
         ModelRenderCache.v4 = BABYLON.Vector4.Zero();
         return ModelRenderCache;
-    }());
+    })();
     BABYLON.ModelRenderCache = ModelRenderCache;
 })(BABYLON || (BABYLON = {}));

+ 429 - 23
src/Canvas2d/babylon.prim2dBase.js

@@ -15,8 +15,216 @@ var BABYLON;
         function Render2DContext() {
         }
         return Render2DContext;
-    }());
+    })();
     BABYLON.Render2DContext = Render2DContext;
+    /**
+     * This class store information for the pointerEventObservable Observable.
+     * The Observable is divided into many sub events (using the Mask feature of the Observable pattern): PointerOver, PointerEnter, PointerDown, PointerMouseWheel, PointerMove, PointerUp, PointerDown, PointerLeave, PointerGotCapture and PointerLostCapture.
+     */
+    var PrimitivePointerInfo = (function () {
+        function PrimitivePointerInfo() {
+            this.primitivePointerPos = BABYLON.Vector2.Zero();
+            this.tilt = BABYLON.Vector2.Zero();
+            this.cancelBubble = false;
+        }
+        Object.defineProperty(PrimitivePointerInfo, "PointerOver", {
+            // The behavior is based on the HTML specifications of the Pointer Events (https://www.w3.org/TR/pointerevents/#list-of-pointer-events). This is not 100% compliant and not meant to be, but still, it's based on these specs for most use cases to be programmed the same way (as closest as possible) as it would have been in HTML.
+            /**
+             * This event type is raised when a pointing device is moved into the hit test boundaries of a primitive.
+             * Bubbles: yes
+             */
+            get: function () {
+                return PrimitivePointerInfo._pointerOver;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(PrimitivePointerInfo, "PointerEnter", {
+            /**
+             * This event type is raised when a pointing device is moved into the hit test boundaries of a primitive or one of its descendants.
+             * Bubbles: no
+             */
+            get: function () {
+                return PrimitivePointerInfo._pointerEnter;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(PrimitivePointerInfo, "PointerDown", {
+            /**
+             * This event type is raised when a pointer enters the active button state (non-zero value in the buttons property). For mouse it's when the device transitions from no buttons depressed to at least one button depressed. For touch/pen this is when a physical contact is made.
+             * Bubbles: yes
+             */
+            get: function () {
+                return PrimitivePointerInfo._pointerDown;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(PrimitivePointerInfo, "PointerMouseWheel", {
+            /**
+             * This event type is raised when the pointer is a mouse and it's wheel is rolling
+             * Bubbles: yes
+             */
+            get: function () {
+                return PrimitivePointerInfo._pointerMouseWheel;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(PrimitivePointerInfo, "PointerMove", {
+            /**
+             * This event type is raised when a pointer change coordinates or when a pointer changes button state, pressure, tilt, or contact geometry and the circumstances produce no other pointers events.
+             * Bubbles: yes
+             */
+            get: function () {
+                return PrimitivePointerInfo._pointerMove;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(PrimitivePointerInfo, "PointerUp", {
+            /**
+             * This event type is raised when the pointer leaves the active buttons states (zero value in the buttons property). For mouse, this is when the device transitions from at least one button depressed to no buttons depressed. For touch/pen, this is when physical contact is removed.
+             * Bubbles: yes
+             */
+            get: function () {
+                return PrimitivePointerInfo._pointerUp;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(PrimitivePointerInfo, "PointerOut", {
+            /**
+             * This event type is raised when a pointing device is moved out of the hit test the boundaries of a primitive.
+             * Bubbles: yes
+             */
+            get: function () {
+                return PrimitivePointerInfo._pointerOut;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(PrimitivePointerInfo, "PointerLeave", {
+            /**
+             * This event type is raised when a pointing device is moved out of the hit test boundaries of a primitive and all its descendants.
+             * Bubbles: no
+             */
+            get: function () {
+                return PrimitivePointerInfo._pointerLeave;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(PrimitivePointerInfo, "PointerGotCapture", {
+            /**
+             * This event type is raised when a primitive receives the pointer capture. This event is fired at the element that is receiving pointer capture. Subsequent events for that pointer will be fired at this element.
+             * Bubbles: yes
+             */
+            get: function () {
+                return PrimitivePointerInfo._pointerGotCapture;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(PrimitivePointerInfo, "PointerLostCapture", {
+            /**
+             * This event type is raised after pointer capture is released for a pointer.
+             * Bubbles: yes
+             */
+            get: function () {
+                return PrimitivePointerInfo._pointerLostCapture;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(PrimitivePointerInfo, "MouseWheelPrecision", {
+            get: function () {
+                return PrimitivePointerInfo._mouseWheelPrecision;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        PrimitivePointerInfo.prototype.updateRelatedTarget = function (prim, primPointerPos) {
+            this.relatedTarget = prim;
+            this.relatedTargetPointerPos = primPointerPos;
+        };
+        PrimitivePointerInfo.getEventTypeName = function (mask) {
+            switch (mask) {
+                case PrimitivePointerInfo.PointerOver: return "PointerOver";
+                case PrimitivePointerInfo.PointerEnter: return "PointerEnter";
+                case PrimitivePointerInfo.PointerDown: return "PointerDown";
+                case PrimitivePointerInfo.PointerMouseWheel: return "PointerMouseWheel";
+                case PrimitivePointerInfo.PointerMove: return "PointerMove";
+                case PrimitivePointerInfo.PointerUp: return "PointerUp";
+                case PrimitivePointerInfo.PointerOut: return "PointerOut";
+                case PrimitivePointerInfo.PointerLeave: return "PointerLeave";
+                case PrimitivePointerInfo.PointerGotCapture: return "PointerGotCapture";
+                case PrimitivePointerInfo.PointerLostCapture: return "PointerLostCapture";
+            }
+        };
+        PrimitivePointerInfo._pointerOver = 0x0001;
+        PrimitivePointerInfo._pointerEnter = 0x0002;
+        PrimitivePointerInfo._pointerDown = 0x0004;
+        PrimitivePointerInfo._pointerMouseWheel = 0x0008;
+        PrimitivePointerInfo._pointerMove = 0x0010;
+        PrimitivePointerInfo._pointerUp = 0x0020;
+        PrimitivePointerInfo._pointerOut = 0x0040;
+        PrimitivePointerInfo._pointerLeave = 0x0080;
+        PrimitivePointerInfo._pointerGotCapture = 0x0100;
+        PrimitivePointerInfo._pointerLostCapture = 0x0200;
+        PrimitivePointerInfo._mouseWheelPrecision = 3.0;
+        return PrimitivePointerInfo;
+    })();
+    BABYLON.PrimitivePointerInfo = PrimitivePointerInfo;
+    /**
+     * Stores information about a Primitive that was intersected
+     */
+    var PrimitiveIntersectedInfo = (function () {
+        function PrimitiveIntersectedInfo(prim, intersectionLocation) {
+            this.prim = prim;
+            this.intersectionLocation = intersectionLocation;
+        }
+        return PrimitiveIntersectedInfo;
+    })();
+    BABYLON.PrimitiveIntersectedInfo = PrimitiveIntersectedInfo;
+    /**
+     * Main class used for the Primitive Intersection API
+     */
+    var IntersectInfo2D = (function () {
+        function IntersectInfo2D() {
+            this.findFirstOnly = false;
+            this.intersectHidden = false;
+            this.pickPosition = BABYLON.Vector2.Zero();
+        }
+        Object.defineProperty(IntersectInfo2D.prototype, "isIntersected", {
+            /**
+             * true if at least one primitive intersected during the test
+             */
+            get: function () {
+                return this.intersectedPrimitives && this.intersectedPrimitives.length > 0;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        IntersectInfo2D.prototype.isPrimIntersected = function (prim) {
+            for (var _i = 0, _a = this.intersectedPrimitives; _i < _a.length; _i++) {
+                var cur = _a[_i];
+                if (cur.prim === prim) {
+                    return cur.intersectionLocation;
+                }
+            }
+            return null;
+        };
+        // Internals, don't use
+        IntersectInfo2D.prototype._exit = function (firstLevel) {
+            if (firstLevel) {
+                this._globalPickPosition = null;
+            }
+        };
+        return IntersectInfo2D;
+    })();
+    BABYLON.IntersectInfo2D = IntersectInfo2D;
     var Prim2DBase = (function (_super) {
         __extends(Prim2DBase, _super);
         function Prim2DBase() {
@@ -28,6 +236,9 @@ var BABYLON;
                 throw new Error("Can't create a primitive with the canvas as direct parent when the caching strategy is TOPLEVELGROUPS. You need to create a Group below the canvas and use it as the parent for the primitive");
             }
             this.setupSmartPropertyPrim();
+            this._pointerEventObservable = new BABYLON.Observable();
+            this._isPickable = true;
+            this._siblingDepthOffset = this._hierarchyDepthOffset = 0;
             this._boundingInfoDirty = true;
             this._boundingInfo = new BABYLON.BoundingInfo2D();
             this._owner = owner;
@@ -56,6 +267,21 @@ var BABYLON;
             this.levelVisible = isVisible;
             this.origin = new BABYLON.Vector2(0.5, 0.5);
         };
+        Object.defineProperty(Prim2DBase.prototype, "actionManager", {
+            get: function () {
+                if (!this._actionManager) {
+                    this._actionManager = new BABYLON.ActionManager(this.owner.scene);
+                }
+                return this._actionManager;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        /**
+         * From 'this' primitive, traverse up (from parent to parent) until the given predicate is true
+         * @param predicate the predicate to test on each parent
+         * @return the first primitive where the predicate was successful
+         */
         Prim2DBase.prototype.traverseUp = function (predicate) {
             var p = this;
             while (p != null) {
@@ -67,6 +293,9 @@ var BABYLON;
             return null;
         };
         Object.defineProperty(Prim2DBase.prototype, "owner", {
+            /**
+             * Retrieve the owner Canvas2D
+             */
             get: function () {
                 return this._owner;
             },
@@ -74,13 +303,29 @@ var BABYLON;
             configurable: true
         });
         Object.defineProperty(Prim2DBase.prototype, "parent", {
+            /**
+             * Get the parent primitive (can be the Canvas, only the Canvas has no parent)
+             */
             get: function () {
                 return this._parent;
             },
             enumerable: true,
             configurable: true
         });
+        Object.defineProperty(Prim2DBase.prototype, "children", {
+            /**
+             * The array of direct children primitives
+             */
+            get: function () {
+                return this._children;
+            },
+            enumerable: true,
+            configurable: true
+        });
         Object.defineProperty(Prim2DBase.prototype, "id", {
+            /**
+             * The identifier of this primitive, may not be unique, it's for information purpose only
+             */
             get: function () {
                 return this._id;
             },
@@ -117,14 +362,25 @@ var BABYLON;
             enumerable: true,
             configurable: true
         });
+        Object.defineProperty(Prim2DBase.prototype, "actualSize", {
+            /**
+             * this method must be implemented by the primitive type to return its size
+             * @returns The size of the primitive
+             */
+            get: function () {
+                return undefined;
+            },
+            enumerable: true,
+            configurable: true
+        });
         Object.defineProperty(Prim2DBase.prototype, "origin", {
             /**
              * The origin defines the normalized coordinate of the center of the primitive, from the top/left corner.
              * The origin is used only to compute transformation of the primitive, it has no meaning in the primitive local frame of reference
              * For instance:
-             * 0,0 means the center is top/left
-             * 0.5,0.5 means the center is at the center of the primitive
-             * 0,1 means the center is bottom/left
+             * 0,0 means the center is bottom/left. Which is the default for Canvas2D instances
+             * 0.5,0.5 means the center is at the center of the primitive, which is default of all types of Primitives
+             * 0,1 means the center is top/left
              * @returns The normalized center.
              */
             get: function () {
@@ -166,7 +422,24 @@ var BABYLON;
             enumerable: true,
             configurable: true
         });
+        Object.defineProperty(Prim2DBase.prototype, "isPickable", {
+            /**
+             * Define if the Primitive can be subject to intersection test or not (default is true)
+             */
+            get: function () {
+                return this._isPickable;
+            },
+            set: function (value) {
+                this._isPickable = value;
+            },
+            enumerable: true,
+            configurable: true
+        });
         Object.defineProperty(Prim2DBase.prototype, "hierarchyDepth", {
+            /**
+             * Return the depth level of the Primitive into the Canvas' Graph. A Canvas will be 0, its direct children 1, and so on.
+             * @returns {}
+             */
             get: function () {
                 return this._hierarchyDepth;
             },
@@ -174,6 +447,10 @@ var BABYLON;
             configurable: true
         });
         Object.defineProperty(Prim2DBase.prototype, "renderGroup", {
+            /**
+             * Retrieve the Group that is responsible to render this primitive
+             * @returns {}
+             */
             get: function () {
                 return this._renderGroup;
             },
@@ -181,6 +458,9 @@ var BABYLON;
             configurable: true
         });
         Object.defineProperty(Prim2DBase.prototype, "globalTransform", {
+            /**
+             * Get the global transformation matrix of the primitive
+             */
             get: function () {
                 return this._globalTransform;
             },
@@ -188,13 +468,32 @@ var BABYLON;
             configurable: true
         });
         Object.defineProperty(Prim2DBase.prototype, "invGlobalTransform", {
+            /**
+             * Get invert of the global transformation matrix of the primitive
+             * @returns {}
+             */
             get: function () {
                 return this._invGlobalTransform;
             },
             enumerable: true,
             configurable: true
         });
+        Object.defineProperty(Prim2DBase.prototype, "localTransform", {
+            /**
+             * Get the local transformation of the primitive
+             */
+            get: function () {
+                this._updateLocalTransform();
+                return this._localTransform;
+            },
+            enumerable: true,
+            configurable: true
+        });
         Object.defineProperty(Prim2DBase.prototype, "boundingInfo", {
+            /**
+             * Get the boundingInfo associated to the primitive.
+             * The value is supposed to be always up to date
+             */
             get: function () {
                 if (this._boundingInfoDirty) {
                     this._boundingInfo = this.levelBoundingInfo.clone();
@@ -202,8 +501,7 @@ var BABYLON;
                     var tps = new BABYLON.BoundingInfo2D();
                     for (var _i = 0, _a = this._children; _i < _a.length; _i++) {
                         var curChild = _a[_i];
-                        var t = curChild.globalTransform.multiply(this.invGlobalTransform);
-                        curChild.boundingInfo.transformToRef(t, curChild.origin, tps);
+                        curChild.boundingInfo.transformToRef(curChild.localTransform, tps);
                         bi.unionToRef(tps, bi);
                     }
                     this._boundingInfoDirty = false;
@@ -213,6 +511,95 @@ var BABYLON;
             enumerable: true,
             configurable: true
         });
+        Object.defineProperty(Prim2DBase.prototype, "pointerEventObservable", {
+            /**
+             * Interaction with the primitive can be create using this Observable. See the PrimitivePointerInfo class for more information
+             */
+            get: function () {
+                return this._pointerEventObservable;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Prim2DBase.prototype.levelIntersect = function (intersectInfo) {
+            return false;
+        };
+        /**
+         * Capture all the Events of the given PointerId for this primitive.
+         * Don't forget to call releasePointerEventsCapture when done.
+         * @param pointerId the Id of the pointer to capture the events from.
+         */
+        Prim2DBase.prototype.setPointerEventCapture = function (pointerId) {
+            return this.owner._setPointerCapture(pointerId, this);
+        };
+        /**
+         * Release a captured pointer made with setPointerEventCapture.
+         * @param pointerId the Id of the pointer to release the capture from.
+         */
+        Prim2DBase.prototype.releasePointerEventsCapture = function (pointerId) {
+            return this.owner._releasePointerCapture(pointerId, this);
+        };
+        /**
+         * Make an intersection test with the primitive, all inputs/outputs are stored in the IntersectInfo2D class, see its documentation for more information.
+         * @param intersectInfo contains the settings of the intersection to perform, to setup before calling this method as well as the result, available after a call to this method.
+         */
+        Prim2DBase.prototype.intersect = function (intersectInfo) {
+            if (!intersectInfo) {
+                return false;
+            }
+            // If this is null it means this method is call for the first level, initialize stuffs
+            var firstLevel = !intersectInfo._globalPickPosition;
+            if (firstLevel) {
+                // Compute the pickPosition in global space and use it to find the local position for each level down, always relative from the world to get the maximum accuracy (and speed). The other way would have been to compute in local every level down relative to its parent's local, which wouldn't be as accurate (even if javascript number is 80bits accurate).
+                intersectInfo._globalPickPosition = BABYLON.Vector2.Zero();
+                BABYLON.Vector2.TransformToRef(intersectInfo.pickPosition, this.globalTransform, intersectInfo._globalPickPosition);
+                intersectInfo._localPickPosition = intersectInfo.pickPosition.clone();
+                intersectInfo.intersectedPrimitives = new Array();
+                intersectInfo.topMostIntersectedPrimitive = null;
+            }
+            if (!intersectInfo.intersectHidden && !this.isVisible) {
+                return false;
+            }
+            // Fast rejection test with boundingInfo
+            if (!this.boundingInfo.doesIntersect(intersectInfo._localPickPosition)) {
+                // Important to call this before each return to allow a good recursion next time this intersectInfo is reused
+                intersectInfo._exit(firstLevel);
+                return false;
+            }
+            // We hit the boundingInfo that bounds this primitive and its children, now we have to test on the primitive of this level
+            var levelIntersectRes = this.levelIntersect(intersectInfo);
+            if (levelIntersectRes) {
+                var pii = new PrimitiveIntersectedInfo(this, intersectInfo._localPickPosition.clone());
+                intersectInfo.intersectedPrimitives.push(pii);
+                if (!intersectInfo.topMostIntersectedPrimitive || (intersectInfo.topMostIntersectedPrimitive.prim.getActualZOffset() > pii.prim.getActualZOffset())) {
+                    intersectInfo.topMostIntersectedPrimitive = pii;
+                }
+                // If we must stop at the first intersection, we're done, quit!
+                if (intersectInfo.findFirstOnly) {
+                    intersectInfo._exit(firstLevel);
+                    return true;
+                }
+            }
+            // Recurse to children if needed
+            if (!levelIntersectRes || !intersectInfo.findFirstOnly) {
+                for (var _i = 0, _a = this._children; _i < _a.length; _i++) {
+                    var curChild = _a[_i];
+                    // Don't test primitive not pick able or if it's hidden and we don't test hidden ones
+                    if (!curChild.isPickable || (!intersectInfo.intersectHidden && !curChild.isVisible)) {
+                        continue;
+                    }
+                    // Must compute the localPickLocation for the children level
+                    BABYLON.Vector2.TransformToRef(intersectInfo._globalPickPosition, curChild.invGlobalTransform, intersectInfo._localPickPosition);
+                    // If we got an intersection with the child and we only need to find the first one, quit!
+                    if (curChild.intersect(intersectInfo) && intersectInfo.findFirstOnly) {
+                        intersectInfo._exit(firstLevel);
+                        return true;
+                    }
+                }
+            }
+            intersectInfo._exit(firstLevel);
+            return intersectInfo.isIntersected;
+        };
         Prim2DBase.prototype.moveChild = function (child, previous) {
             if (child.parent !== this) {
                 return false;
@@ -233,15 +620,18 @@ var BABYLON;
             this._children.splice(prevIndex + 1, 0, this._children.splice(childIndex, 1)[0]);
         };
         Prim2DBase.prototype.addChild = function (child) {
-            child._siblingDepthOffset = (this._children.length + 1) * this.owner.hierarchySiblingZDelta;
-            child._depthLevel = this._depthLevel + 1;
-            child._hierarchyDepthOffset = child._depthLevel * this.owner.hierarchyLevelZFactor;
+            child._hierarchyDepthOffset = this._hierarchyDepthOffset + ((this._children.length + 1) * this._siblingDepthOffset);
+            child._siblingDepthOffset = this._siblingDepthOffset / this.owner.hierarchyLevelMaxSiblingCount;
             this._children.push(child);
         };
         Prim2DBase.prototype.dispose = function () {
             if (!_super.prototype.dispose.call(this)) {
                 return false;
             }
+            if (this._actionManager) {
+                this._actionManager.dispose();
+                this._actionManager = null;
+            }
             // If there's a parent, remove this object from its parent list
             if (this._parent) {
                 var i = this._parent._children.indexOf(this);
@@ -259,15 +649,15 @@ var BABYLON;
             return true;
         };
         Prim2DBase.prototype.getActualZOffset = function () {
-            return this._zOrder || 1 - (this._siblingDepthOffset + this._hierarchyDepthOffset);
+            return this._zOrder || (1 - this._hierarchyDepthOffset);
         };
         Prim2DBase.prototype.onPrimBecomesDirty = function () {
             if (this._renderGroup) {
                 this._renderGroup._addPrimToDirtyList(this);
             }
         };
-        Prim2DBase.prototype.needPrepare = function () {
-            return (this.isVisible || this._visibilityChanged) && (this._modelDirty || (this._instanceDirtyFlags !== 0) || (this._globalTransformProcessStep !== this._globalTransformStep));
+        Prim2DBase.prototype._needPrepare = function () {
+            return this._visibilityChanged || this._modelDirty || (this._instanceDirtyFlags !== 0) || (this._globalTransformProcessStep !== this._globalTransformStep);
         };
         Prim2DBase.prototype._prepareRender = function (context) {
             this._prepareRenderPre(context);
@@ -306,11 +696,29 @@ var BABYLON;
             }
         };
         Prim2DBase.prototype.updateGlobalTransVisOf = function (list, recurse) {
-            for (var _i = 0, list_1 = list; _i < list_1.length; _i++) {
-                var cur = list_1[_i];
+            for (var _i = 0; _i < list.length; _i++) {
+                var cur = list[_i];
                 cur.updateGlobalTransVis(recurse);
             }
         };
+        Prim2DBase.prototype._updateLocalTransform = function () {
+            var tflags = Prim2DBase.positionProperty.flagId | Prim2DBase.rotationProperty.flagId | Prim2DBase.scaleProperty.flagId;
+            if (this.checkPropertiesDirty(tflags)) {
+                var rot = BABYLON.Quaternion.RotationAxis(new BABYLON.Vector3(0, 0, 1), this._rotation);
+                var local = BABYLON.Matrix.Compose(new BABYLON.Vector3(this._scale, this._scale, this._scale), rot, new BABYLON.Vector3(this._position.x, this._position.y, 0));
+                this._localTransform = local;
+                this.clearPropertiesDirty(tflags);
+                // this is important to access actualSize AFTER fetching a first version of the local transform and reset the dirty flag, because accessing actualSize on a Group2D which actualSize is built from its content will trigger a call to this very method on this very object. We won't mind about the origin offset not being computed, as long as we return a local transform based on the position/rotation/scale
+                //var actualSize = this.actualSize;
+                //if (!actualSize) {
+                //    throw new Error(`The primitive type: ${Tools.getClassName(this)} must implement the actualSize get property!`);
+                //}
+                //local.m[12] -= (actualSize.width * this.origin.x) * local.m[0] + (actualSize.height * this.origin.y) * local.m[4];
+                //local.m[13] -= (actualSize.width * this.origin.x) * local.m[1] + (actualSize.height * this.origin.y) * local.m[5];
+                return true;
+            }
+            return false;
+        };
         Prim2DBase.prototype.updateGlobalTransVis = function (recurse) {
             if (this.isDisposed) {
                 return;
@@ -324,17 +732,15 @@ var BABYLON;
                 var curVisibleState = this.isVisible;
                 this.isVisible = (!this._parent || this._parent.isVisible) && this.levelVisible;
                 // Detect a change of visibility
-                this._visibilityChanged = (curVisibleState !== undefined) && curVisibleState !== this.isVisible;
-                // Detect if either the parent or this node changed
-                var tflags = Prim2DBase.positionProperty.flagId | Prim2DBase.rotationProperty.flagId | Prim2DBase.scaleProperty.flagId;
-                if (this.isVisible && (this._parent && this._parent._globalTransformStep !== this._parentTransformStep) || this.checkPropertiesDirty(tflags)) {
-                    var rot = BABYLON.Quaternion.RotationAxis(new BABYLON.Vector3(0, 0, 1), this._rotation);
-                    var local = BABYLON.Matrix.Compose(new BABYLON.Vector3(this._scale, this._scale, this._scale), rot, new BABYLON.Vector3(this._position.x, this._position.y, 0));
-                    this._globalTransform = this._parent ? local.multiply(this._parent._globalTransform) : local;
+                this._visibilityChanged = curVisibleState !== this.isVisible;
+                // Get/compute the localTransform
+                var localDirty = this._updateLocalTransform();
+                // Check if we have to update the globalTransform
+                if (!this._globalTransform || localDirty || (this._parent && this._parent._globalTransformStep !== this._parentTransformStep)) {
+                    this._globalTransform = this._parent ? this._localTransform.multiply(this._parent._globalTransform) : this._localTransform;
                     this._invGlobalTransform = BABYLON.Matrix.Invert(this._globalTransform);
                     this._globalTransformStep = this.owner._globalTransformProcessStep + 1;
                     this._parentTransformStep = this._parent ? this._parent._globalTransformStep : 0;
-                    this.clearPropertiesDirty(tflags);
                 }
                 this._globalTransformProcessStep = this.owner._globalTransformProcessStep;
             }
@@ -372,6 +778,6 @@ var BABYLON;
             BABYLON.className("Prim2DBase")
         ], Prim2DBase);
         return Prim2DBase;
-    }(BABYLON.SmartPropertyPrim));
+    })(BABYLON.SmartPropertyPrim);
     BABYLON.Prim2DBase = Prim2DBase;
 })(BABYLON || (BABYLON = {}));

+ 563 - 25
src/Canvas2d/babylon.prim2dBase.ts

@@ -4,7 +4,295 @@
         forceRefreshPrimitive: boolean;
     }
 
+    /**
+     * This class store information for the pointerEventObservable Observable.
+     * The Observable is divided into many sub events (using the Mask feature of the Observable pattern): PointerOver, PointerEnter, PointerDown, PointerMouseWheel, PointerMove, PointerUp, PointerDown, PointerLeave, PointerGotCapture and PointerLostCapture.
+     */
+    export class PrimitivePointerInfo {
+        private static _pointerOver        = 0x0001;
+        private static _pointerEnter       = 0x0002;
+        private static _pointerDown        = 0x0004;
+        private static _pointerMouseWheel  = 0x0008;
+        private static _pointerMove        = 0x0010;
+        private static _pointerUp          = 0x0020;
+        private static _pointerOut         = 0x0040;
+        private static _pointerLeave       = 0x0080;
+        private static _pointerGotCapture  = 0x0100;
+        private static _pointerLostCapture = 0x0200;
+
+        private static _mouseWheelPrecision = 3.0;
+
+        // The behavior is based on the HTML specifications of the Pointer Events (https://www.w3.org/TR/pointerevents/#list-of-pointer-events). This is not 100% compliant and not meant to be, but still, it's based on these specs for most use cases to be programmed the same way (as closest as possible) as it would have been in HTML.
+
+        /**
+         * This event type is raised when a pointing device is moved into the hit test boundaries of a primitive.
+         * Bubbles: yes
+         */
+        public static get PointerOver(): number {
+            return PrimitivePointerInfo._pointerOver;
+        }
+
+        /**
+         * This event type is raised when a pointing device is moved into the hit test boundaries of a primitive or one of its descendants.
+         * Bubbles: no
+         */
+        public static get PointerEnter(): number {
+            return PrimitivePointerInfo._pointerEnter;
+        }
+
+        /**
+         * This event type is raised when a pointer enters the active button state (non-zero value in the buttons property). For mouse it's when the device transitions from no buttons depressed to at least one button depressed. For touch/pen this is when a physical contact is made.
+         * Bubbles: yes
+         */
+        public static get PointerDown(): number {
+            return PrimitivePointerInfo._pointerDown;
+        }
+
+        /**
+         * This event type is raised when the pointer is a mouse and it's wheel is rolling
+         * Bubbles: yes
+         */
+        public static get PointerMouseWheel(): number {
+            return PrimitivePointerInfo._pointerMouseWheel;
+        }
+
+        /**
+         * This event type is raised when a pointer change coordinates or when a pointer changes button state, pressure, tilt, or contact geometry and the circumstances produce no other pointers events.
+         * Bubbles: yes
+         */
+        public static get PointerMove(): number {
+            return PrimitivePointerInfo._pointerMove;
+        }
+
+        /**
+         * This event type is raised when the pointer leaves the active buttons states (zero value in the buttons property). For mouse, this is when the device transitions from at least one button depressed to no buttons depressed. For touch/pen, this is when physical contact is removed.
+         * Bubbles: yes
+         */
+        public static get PointerUp(): number {
+            return PrimitivePointerInfo._pointerUp;
+        }
+
+        /**
+         * This event type is raised when a pointing device is moved out of the hit test the boundaries of a primitive.
+         * Bubbles: yes
+         */
+        public static get PointerOut(): number {
+            return PrimitivePointerInfo._pointerOut;
+        }
+
+        /**
+         * This event type is raised when a pointing device is moved out of the hit test boundaries of a primitive and all its descendants.
+         * Bubbles: no
+         */
+        public static get PointerLeave(): number {
+            return PrimitivePointerInfo._pointerLeave;
+        }
+
+        /**
+         * This event type is raised when a primitive receives the pointer capture. This event is fired at the element that is receiving pointer capture. Subsequent events for that pointer will be fired at this element.
+         * Bubbles: yes
+         */
+        public static get PointerGotCapture(): number {
+            return PrimitivePointerInfo._pointerGotCapture;
+        }
+
+        /**
+         * This event type is raised after pointer capture is released for a pointer.
+         * Bubbles: yes
+         */
+        public static get PointerLostCapture(): number {
+            return PrimitivePointerInfo._pointerLostCapture;
+        }
+
+        public static get MouseWheelPrecision(): number {
+            return PrimitivePointerInfo._mouseWheelPrecision;
+        }
+
+        /**
+         * Event Type, one of the static PointerXXXX property defined above (PrimitivePointerInfo.PointerOver to PrimitivePointerInfo.PointerLostCapture)
+         */
+        eventType: number;
+
+        /**
+         * Position of the pointer relative to the bottom/left of the Canvas
+         */
+        canvasPointerPos: Vector2;
+
+        /**
+         * Position of the pointer relative to the bottom/left of the primitive that registered the Observer
+         */
+        primitivePointerPos: Vector2;
+
+        /**
+         * The primitive where the event was initiated first (in case of bubbling)
+         */
+        relatedTarget: Prim2DBase;
+
+        /**
+         * Position of the pointer relative to the bottom/left of the relatedTarget
+         */
+        relatedTargetPointerPos: Vector2;
+
+        /**
+         * An observable can set this property to true to stop bubbling on the upper levels
+         */
+        cancelBubble: boolean;
+
+        /**
+         * True if the Control keyboard key is down
+         */
+        ctrlKey: boolean;
+
+        /**
+         * true if the Shift keyboard key is down
+         */
+        shiftKey: boolean;
+
+        /**
+         * true if the Alt keyboard key is down
+         */
+        altKey: boolean;
+
+        /**
+         * true if the Meta keyboard key is down
+         */
+        metaKey: boolean;
+
+        /**
+         * For button, buttons, refer to https://www.w3.org/TR/pointerevents/#button-states
+         */
+        button: number;
+        /**
+         * For button, buttons, refer to https://www.w3.org/TR/pointerevents/#button-states
+         */
+        buttons: number;
+
+        /**
+         * The amount of mouse wheel rolled
+         */
+        mouseWheelDelta: number;
+
+        /**
+         * Id of the Pointer involved in the event
+         */
+        pointerId: number;
+        width: number;
+        height: number;
+        presssure: number;
+        tilt: Vector2;
+
+        /**
+         * true if the involved pointer is captured for a particular primitive, false otherwise.
+         */
+        isCaptured: boolean;
+
+        constructor() {
+            this.primitivePointerPos = Vector2.Zero();
+            this.tilt = Vector2.Zero();
+            this.cancelBubble = false;
+        }
+
+        updateRelatedTarget(prim: Prim2DBase, primPointerPos: Vector2) {
+            this.relatedTarget = prim;
+            this.relatedTargetPointerPos = primPointerPos;
+        }
+
+        public static getEventTypeName(mask: number): string {
+            switch (mask) {
+                case PrimitivePointerInfo.PointerOver:        return "PointerOver";
+                case PrimitivePointerInfo.PointerEnter:       return "PointerEnter";
+                case PrimitivePointerInfo.PointerDown:        return "PointerDown";
+                case PrimitivePointerInfo.PointerMouseWheel:  return "PointerMouseWheel";
+                case PrimitivePointerInfo.PointerMove:        return "PointerMove";
+                case PrimitivePointerInfo.PointerUp:          return "PointerUp";
+                case PrimitivePointerInfo.PointerOut:         return "PointerOut";
+                case PrimitivePointerInfo.PointerLeave:       return "PointerLeave";
+                case PrimitivePointerInfo.PointerGotCapture:  return "PointerGotCapture";
+                case PrimitivePointerInfo.PointerLostCapture: return "PointerLostCapture";
+            }
+        }
+    }
+
+    /**
+     * Stores information about a Primitive that was intersected
+     */
+    export class PrimitiveIntersectedInfo {
+        constructor(public prim: Prim2DBase, public intersectionLocation: Vector2) {
+            
+        }
+    }
+
+    /**
+     * Main class used for the Primitive Intersection API
+     */
+    export class IntersectInfo2D {
+        constructor() {
+            this.findFirstOnly = false;
+            this.intersectHidden = false;
+            this.pickPosition = Vector2.Zero();
+        }
+
+        // Input settings, to setup before calling an intersection related method
+
+        /**
+         * Set the pick position, relative to the primitive where the intersection test is made
+         */
+        public pickPosition: Vector2;
+
+        /**
+         * If true the intersection will stop at the first hit, if false all primitives will be tested and the intersectedPrimitives array will be filled accordingly (false default)
+         */
+        public findFirstOnly: boolean;
+
+        /**
+         * If true the intersection test will also be made on hidden primitive (false default)
+         */
+        public intersectHidden: boolean;
+
+        // Intermediate data, don't use!
+        public _globalPickPosition: Vector2;
+        public _localPickPosition: Vector2;
+
+        // Output settings, up to date in return of a call to an intersection related method
+
+        /**
+         * The topmost intersected primitive
+         */
+        public topMostIntersectedPrimitive: PrimitiveIntersectedInfo;
+
+        /**
+         * The array containing all intersected primitive, in no particular order.
+         */
+        public intersectedPrimitives: Array<PrimitiveIntersectedInfo>;
+
+        /**
+         * true if at least one primitive intersected during the test
+         */
+        public get isIntersected(): boolean {
+            return this.intersectedPrimitives && this.intersectedPrimitives.length > 0;
+        }
+
+        public isPrimIntersected(prim: Prim2DBase): Vector2 {
+            for (let cur of this.intersectedPrimitives) {
+                if (cur.prim === prim) {
+                    return cur.intersectionLocation;
+                }
+            }
+            return null;
+        }
+
+        // Internals, don't use
+        public _exit(firstLevel: boolean) {
+            if (firstLevel) {
+                this._globalPickPosition = null;
+            }
+        }
+    }
+
     @className("Prim2DBase")
+    /**
+     * Base class for a Primitive of the Canvas2D feature
+     */
     export class Prim2DBase extends SmartPropertyPrim {
         static PRIM2DBASE_PROPCOUNT: number = 10;
 
@@ -14,6 +302,9 @@
             }
 
             this.setupSmartPropertyPrim();
+            this._pointerEventObservable = new Observable<PrimitivePointerInfo>();
+            this._isPickable = true;
+            this._siblingDepthOffset = this._hierarchyDepthOffset = 0;
             this._boundingInfoDirty = true;
             this._boundingInfo = new BoundingInfo2D();
             this._owner = owner;
@@ -45,6 +336,19 @@
             this.origin = new Vector2(0.5, 0.5);
         }
 
+
+        public get actionManager(): ActionManager {
+            if (!this._actionManager) {
+                this._actionManager = new ActionManager(this.owner.scene);
+            }
+            return this._actionManager;
+        }
+
+        /**
+         * From 'this' primitive, traverse up (from parent to parent) until the given predicate is true
+         * @param predicate the predicate to test on each parent
+         * @return the first primitive where the predicate was successful
+         */
         public traverseUp(predicate: (p: Prim2DBase) => boolean): Prim2DBase {
             let p: Prim2DBase = this;
             while (p != null) {
@@ -56,27 +360,73 @@
             return null;
         }
 
+        /**
+         * Retrieve the owner Canvas2D
+         */
         public get owner(): Canvas2D {
             return this._owner;
         }
 
+        /**
+         * Get the parent primitive (can be the Canvas, only the Canvas has no parent)
+         */
         public get parent(): Prim2DBase {
             return this._parent;
         }
 
+        /**
+         * The array of direct children primitives
+         */
+        public get children(): Prim2DBase[] {
+            return this._children;
+        }
+
+        /**
+         * The identifier of this primitive, may not be unique, it's for information purpose only
+         */
         public get id(): string {
             return this._id;
         }
 
+        /**
+         * Metadata of the position property
+         */
         public static positionProperty: Prim2DPropInfo;
+
+        /**
+         * Metadata of the rotation property
+         */
         public static rotationProperty: Prim2DPropInfo;
+
+        /**
+         * Metadata of the scale property
+         */
         public static scaleProperty: Prim2DPropInfo;
+
+        /**
+         * Metadata of the origin property
+         */
         public static originProperty: Prim2DPropInfo;
+
+        /**
+         * Metadata of the levelVisible property
+         */
         public static levelVisibleProperty: Prim2DPropInfo;
+
+        /**
+         * Metadata of the isVisible property
+         */
         public static isVisibleProperty: Prim2DPropInfo;
+
+        /**
+         * Metadata of the zOrder property
+         */
         public static zOrderProperty: Prim2DPropInfo;
 
         @instanceLevelProperty(1, pi => Prim2DBase.positionProperty = pi, false, true)
+        /**
+         * Position of the primitive, relative to its parent.
+         */
         public get position(): Vector2 {
             return this._position;
         }
@@ -86,6 +436,10 @@
         }
 
         @instanceLevelProperty(2, pi => Prim2DBase.rotationProperty = pi, false, true)
+        /**
+         * Rotation of the primitive, in radian, along the Z axis
+         * @returns {} 
+         */
         public get rotation(): number {
             return this._rotation;
         }
@@ -95,6 +449,9 @@
         }
 
         @instanceLevelProperty(3, pi => Prim2DBase.scaleProperty = pi, false, true)
+        /**
+         * Uniform scale applied on the primitive
+         */
         public set scale(value: number) {
             this._scale = value;
         }
@@ -103,6 +460,14 @@
             return this._scale;
         }
 
+        /**
+         * this method must be implemented by the primitive type to return its size
+         * @returns The size of the primitive
+         */
+        public get actualSize(): Size {
+            return undefined;
+        }
+
         @instanceLevelProperty(4, pi => Prim2DBase.originProperty = pi, false, true)
         public set origin(value: Vector2) {
             this._origin = value;
@@ -112,9 +477,9 @@
          * The origin defines the normalized coordinate of the center of the primitive, from the top/left corner.
          * The origin is used only to compute transformation of the primitive, it has no meaning in the primitive local frame of reference
          * For instance:
-         * 0,0 means the center is top/left
-         * 0.5,0.5 means the center is at the center of the primitive
-         * 0,1 means the center is bottom/left
+         * 0,0 means the center is bottom/left. Which is the default for Canvas2D instances
+         * 0.5,0.5 means the center is at the center of the primitive, which is default of all types of Primitives
+         * 0,1 means the center is top/left
          * @returns The normalized center.
          */
         public get origin(): Vector2 {
@@ -122,6 +487,10 @@
         }
 
         @dynamicLevelProperty(5, pi => Prim2DBase.levelVisibleProperty = pi)
+        /**
+         * Let the user defines if the Primitive is hidden or not at its level. As Primitives inherit the hidden status from their parent, only the isVisible property give properly the real visible state.
+         * Default is true, setting to false will hide this primitive and its children.
+         */
         public get levelVisible(): boolean {
             return this._levelVisible;
         }
@@ -131,6 +500,10 @@
         }
 
         @instanceLevelProperty(6, pi => Prim2DBase.isVisibleProperty = pi)
+        /**
+         * Use ONLY THE GETTER to determine if the primitive is visible or not.
+         * The Setter is for internal purpose only!
+         */
         public get isVisible(): boolean {
             return this._isVisible;
         }
@@ -140,6 +513,10 @@
         }
 
         @instanceLevelProperty(7, pi => Prim2DBase.zOrderProperty = pi)
+        /**
+         * You can override the default Z Order through this property, but most of the time the default behavior is acceptable
+         * @returns {} 
+         */
         public get zOrder(): number {
             return this._zOrder;
         }
@@ -148,22 +525,60 @@
             this._zOrder = value;
         }
 
+        /**
+         * Define if the Primitive can be subject to intersection test or not (default is true)
+         */
+        public get isPickable(): boolean {
+            return this._isPickable;
+        }
+
+        public set isPickable(value: boolean) {
+            this._isPickable = value;
+        }
+
+        /**
+         * Return the depth level of the Primitive into the Canvas' Graph. A Canvas will be 0, its direct children 1, and so on.
+         * @returns {} 
+         */
         public get hierarchyDepth(): number {
             return this._hierarchyDepth;
         }
 
+        /**
+         * Retrieve the Group that is responsible to render this primitive
+         * @returns {} 
+         */
         public get renderGroup(): Group2D {
             return this._renderGroup;
         }
 
+        /**
+         * Get the global transformation matrix of the primitive
+         */
         public get globalTransform(): Matrix {
             return this._globalTransform;
         }
 
+        /**
+         * Get invert of the global transformation matrix of the primitive
+         * @returns {} 
+         */
         public get invGlobalTransform(): Matrix {
             return this._invGlobalTransform;
         }
 
+        /**
+         * Get the local transformation of the primitive
+         */
+        public get localTransform(): Matrix {
+            this._updateLocalTransform();
+            return this._localTransform;
+        }
+
+        /**
+         * Get the boundingInfo associated to the primitive.
+         * The value is supposed to be always up to date
+         */
         public get boundingInfo(): BoundingInfo2D {
             if (this._boundingInfoDirty) {
                 this._boundingInfo = this.levelBoundingInfo.clone();
@@ -171,8 +586,7 @@
 
                 var tps = new BoundingInfo2D();
                 for (let curChild of this._children) {
-                    let t = curChild.globalTransform.multiply(this.invGlobalTransform);
-                    curChild.boundingInfo.transformToRef(t, curChild.origin, tps);
+                    curChild.boundingInfo.transformToRef(curChild.localTransform, tps);
                     bi.unionToRef(tps, bi);
                 }
 
@@ -181,6 +595,105 @@
             return this._boundingInfo;
         }
 
+        /**
+         * Interaction with the primitive can be create using this Observable. See the PrimitivePointerInfo class for more information
+         */
+        public get pointerEventObservable(): Observable<PrimitivePointerInfo> {
+            return this._pointerEventObservable;
+        }
+
+        protected levelIntersect(intersectInfo: IntersectInfo2D): boolean {
+
+            return false;
+        }
+
+        /**
+         * Capture all the Events of the given PointerId for this primitive.
+         * Don't forget to call releasePointerEventsCapture when done.
+         * @param pointerId the Id of the pointer to capture the events from.
+         */
+        public setPointerEventCapture(pointerId: number): boolean {
+            return this.owner._setPointerCapture(pointerId, this);
+        }
+
+        /**
+         * Release a captured pointer made with setPointerEventCapture.
+         * @param pointerId the Id of the pointer to release the capture from.
+         */
+        public releasePointerEventsCapture(pointerId: number): boolean {
+            return this.owner._releasePointerCapture(pointerId, this);
+        }
+
+        /**
+         * Make an intersection test with the primitive, all inputs/outputs are stored in the IntersectInfo2D class, see its documentation for more information.
+         * @param intersectInfo contains the settings of the intersection to perform, to setup before calling this method as well as the result, available after a call to this method.
+         */
+        public intersect(intersectInfo: IntersectInfo2D): boolean {
+            if (!intersectInfo) {
+                return false;
+            }
+
+            // If this is null it means this method is call for the first level, initialize stuffs
+            let firstLevel = !intersectInfo._globalPickPosition;
+            if (firstLevel) {
+                // Compute the pickPosition in global space and use it to find the local position for each level down, always relative from the world to get the maximum accuracy (and speed). The other way would have been to compute in local every level down relative to its parent's local, which wouldn't be as accurate (even if javascript number is 80bits accurate).
+                intersectInfo._globalPickPosition = Vector2.Zero();
+                Vector2.TransformToRef(intersectInfo.pickPosition, this.globalTransform, intersectInfo._globalPickPosition);
+                intersectInfo._localPickPosition = intersectInfo.pickPosition.clone();
+                intersectInfo.intersectedPrimitives = new Array<PrimitiveIntersectedInfo>();
+                intersectInfo.topMostIntersectedPrimitive = null;
+            }
+
+            if (!intersectInfo.intersectHidden && !this.isVisible) {
+                return false;
+            }
+
+            // Fast rejection test with boundingInfo
+            if (!this.boundingInfo.doesIntersect(intersectInfo._localPickPosition)) {
+                // Important to call this before each return to allow a good recursion next time this intersectInfo is reused
+                intersectInfo._exit(firstLevel);
+                return false;
+            }
+
+            // We hit the boundingInfo that bounds this primitive and its children, now we have to test on the primitive of this level
+            let levelIntersectRes = this.levelIntersect(intersectInfo);
+            if (levelIntersectRes) {
+                let pii = new PrimitiveIntersectedInfo(this, intersectInfo._localPickPosition.clone());
+                intersectInfo.intersectedPrimitives.push(pii);
+                if (!intersectInfo.topMostIntersectedPrimitive || (intersectInfo.topMostIntersectedPrimitive.prim.getActualZOffset() > pii.prim.getActualZOffset())) {
+                    intersectInfo.topMostIntersectedPrimitive = pii;
+                }
+
+                // If we must stop at the first intersection, we're done, quit!
+                if (intersectInfo.findFirstOnly) {
+                    intersectInfo._exit(firstLevel);
+                    return true;
+                }
+            }
+
+            // Recurse to children if needed
+            if (!levelIntersectRes || !intersectInfo.findFirstOnly) {
+                for (let curChild of this._children) {
+                    // Don't test primitive not pick able or if it's hidden and we don't test hidden ones
+                    if (!curChild.isPickable || (!intersectInfo.intersectHidden && !curChild.isVisible)) {
+                        continue;
+                    }
+
+                    // Must compute the localPickLocation for the children level
+                    Vector2.TransformToRef(intersectInfo._globalPickPosition, curChild.invGlobalTransform, intersectInfo._localPickPosition);
+
+                    // If we got an intersection with the child and we only need to find the first one, quit!
+                    if (curChild.intersect(intersectInfo) && intersectInfo.findFirstOnly) {
+                        intersectInfo._exit(firstLevel);
+                        return true;
+                    }
+                }
+            }
+
+            intersectInfo._exit(firstLevel);
+            return intersectInfo.isIntersected;
+        }
+
         public moveChild(child: Prim2DBase, previous: Prim2DBase): boolean {
             if (child.parent !== this) {
                 return false;
@@ -205,11 +718,9 @@
         }
 
         private addChild(child: Prim2DBase) {
-            child._siblingDepthOffset = (this._children.length + 1) * this.owner.hierarchySiblingZDelta;
-            child._depthLevel = this._depthLevel + 1;
-            child._hierarchyDepthOffset = child._depthLevel * this.owner.hierarchyLevelZFactor;
+            child._hierarchyDepthOffset = this._hierarchyDepthOffset + ((this._children.length + 1) * this._siblingDepthOffset);
+            child._siblingDepthOffset = this._siblingDepthOffset / this.owner.hierarchyLevelMaxSiblingCount;
             this._children.push(child);
-
         }
 
         public dispose(): boolean {
@@ -217,6 +728,11 @@
                 return false;
             }
 
+            if (this._actionManager) {
+                this._actionManager.dispose();
+                this._actionManager = null;
+            }
+
             // If there's a parent, remove this object from its parent list
             if (this._parent) {
                 let i = this._parent._children.indexOf(this);
@@ -236,8 +752,8 @@
             return true;
         }
 
-        protected getActualZOffset(): number {
-            return this._zOrder || 1 - (this._siblingDepthOffset + this._hierarchyDepthOffset);
+        public getActualZOffset(): number {
+            return this._zOrder || (1 - this._hierarchyDepthOffset);
         }
 
         protected onPrimBecomesDirty() {
@@ -246,8 +762,8 @@
             }
         }
 
-        public needPrepare(): boolean {
-            return (this.isVisible || this._visibilityChanged) && (this._modelDirty || (this._instanceDirtyFlags !== 0) || (this._globalTransformProcessStep !== this._globalTransformStep));
+        public _needPrepare(): boolean {
+            return this._visibilityChanged || this._modelDirty || (this._instanceDirtyFlags !== 0) || (this._globalTransformProcessStep !== this._globalTransformStep);
         }
 
         public _prepareRender(context: Render2DContext) {
@@ -299,6 +815,28 @@
             }
         }
 
+        private _updateLocalTransform(): boolean {
+            let tflags = Prim2DBase.positionProperty.flagId | Prim2DBase.rotationProperty.flagId | Prim2DBase.scaleProperty.flagId;
+            if (this.checkPropertiesDirty(tflags)) {
+                var rot = Quaternion.RotationAxis(new Vector3(0, 0, 1), this._rotation);
+                var local = Matrix.Compose(new Vector3(this._scale, this._scale, this._scale), rot, new Vector3(this._position.x, this._position.y, 0));
+
+                this._localTransform = local;
+                this.clearPropertiesDirty(tflags);
+
+                // this is important to access actualSize AFTER fetching a first version of the local transform and reset the dirty flag, because accessing actualSize on a Group2D which actualSize is built from its content will trigger a call to this very method on this very object. We won't mind about the origin offset not being computed, as long as we return a local transform based on the position/rotation/scale
+                //var actualSize = this.actualSize;
+                //if (!actualSize) {
+                //    throw new Error(`The primitive type: ${Tools.getClassName(this)} must implement the actualSize get property!`);
+                //}
+
+                //local.m[12] -= (actualSize.width * this.origin.x) * local.m[0] + (actualSize.height * this.origin.y) * local.m[4];
+                //local.m[13] -= (actualSize.width * this.origin.x) * local.m[1] + (actualSize.height * this.origin.y) * local.m[5];
+                return true;
+            }
+            return false;
+        }
+
         protected updateGlobalTransVis(recurse: boolean) {
             if (this.isDisposed) {
                 return;
@@ -315,21 +853,18 @@
                 this.isVisible = (!this._parent || this._parent.isVisible) && this.levelVisible;
 
                 // Detect a change of visibility
-                this._visibilityChanged = (curVisibleState !== undefined) && curVisibleState !== this.isVisible;
+                this._visibilityChanged = curVisibleState !== this.isVisible;
 
-                // Detect if either the parent or this node changed
-                let tflags = Prim2DBase.positionProperty.flagId | Prim2DBase.rotationProperty.flagId | Prim2DBase.scaleProperty.flagId;
-                if (this.isVisible && (this._parent && this._parent._globalTransformStep !== this._parentTransformStep) || this.checkPropertiesDirty(tflags)) {
-                    var rot = Quaternion.RotationAxis(new Vector3(0, 0, 1), this._rotation);
-                    var local = Matrix.Compose(new Vector3(this._scale, this._scale, this._scale), rot, new Vector3(this._position.x, this._position.y, 0));
+                // Get/compute the localTransform
+                let localDirty = this._updateLocalTransform();
 
-                    this._globalTransform = this._parent ? local.multiply(this._parent._globalTransform) : local;
+                // Check if we have to update the globalTransform
+                if (!this._globalTransform || localDirty || (this._parent && this._parent._globalTransformStep !== this._parentTransformStep)) {
+                    this._globalTransform = this._parent ? this._localTransform.multiply(this._parent._globalTransform) : this._localTransform;
                     this._invGlobalTransform = Matrix.Invert(this._globalTransform);
 
                     this._globalTransformStep = this.owner._globalTransformProcessStep + 1;
                     this._parentTransformStep = this._parent ? this._parent._globalTransformStep : 0;
-
-                    this.clearPropertiesDirty(tflags);
                 }
                 this._globalTransformProcessStep = this.owner._globalTransformProcessStep;
             }
@@ -343,16 +878,18 @@
 
         private _owner: Canvas2D;
         private _parent: Prim2DBase;
+        private _actionManager: ActionManager;
         protected _children: Array<Prim2DBase>;
         private _renderGroup: Group2D;
         private _hierarchyDepth: number;
-        protected _depthLevel: number;
-        private _hierarchyDepthOffset: number;
-        private _siblingDepthOffset: number;
+        protected _hierarchyDepthOffset: number;
+        protected _siblingDepthOffset: number;
         private _zOrder: number;
         private _levelVisible: boolean;
+        public _pointerEventObservable: Observable<PrimitivePointerInfo>;
         public _boundingInfoDirty: boolean;
         protected _visibilityChanged;
+        private _isPickable;
         private _isVisible: boolean;
         private _id: string;
         private _position: Vector2;
@@ -370,6 +907,7 @@
 
         // Stores the previous 
         protected _globalTransformProcessStep: number;
+        protected _localTransform: Matrix;
         protected _globalTransform: Matrix;
         protected _invGlobalTransform: Matrix;
     }

+ 20 - 4
src/Canvas2d/babylon.rectangle2d.js

@@ -112,7 +112,7 @@ var BABYLON;
             return true;
         };
         return Rectangle2DRenderCache;
-    }(BABYLON.ModelRenderCache));
+    })(BABYLON.ModelRenderCache);
     BABYLON.Rectangle2DRenderCache = Rectangle2DRenderCache;
     var Rectangle2DInstanceData = (function (_super) {
         __extends(Rectangle2DInstanceData, _super);
@@ -130,13 +130,20 @@ var BABYLON;
             BABYLON.instanceData()
         ], Rectangle2DInstanceData.prototype, "properties", null);
         return Rectangle2DInstanceData;
-    }(BABYLON.Shape2DInstanceData));
+    })(BABYLON.Shape2DInstanceData);
     BABYLON.Rectangle2DInstanceData = Rectangle2DInstanceData;
     var Rectangle2D = (function (_super) {
         __extends(Rectangle2D, _super);
         function Rectangle2D() {
             _super.apply(this, arguments);
         }
+        Object.defineProperty(Rectangle2D.prototype, "actualSize", {
+            get: function () {
+                return this.size;
+            },
+            enumerable: true,
+            configurable: true
+        });
         Object.defineProperty(Rectangle2D.prototype, "size", {
             get: function () {
                 return this._size;
@@ -168,8 +175,17 @@ var BABYLON;
             enumerable: true,
             configurable: true
         });
+        Rectangle2D.prototype.levelIntersect = function (intersectInfo) {
+            // If we got there it mean the boundingInfo intersection succeed, if the rectangle has not roundRadius, it means it succeed!
+            if (this.notRounded) {
+                return true;
+            }
+            // Well, for now we neglect the area where the pickPosition could be outside due to the roundRadius...
+            // TODO make REAL intersection test here!
+            return true;
+        };
         Rectangle2D.prototype.updateLevelBoundingInfo = function () {
-            BABYLON.BoundingInfo2D.CreateFromSizeToRef(this.size, this._levelBoundingInfo);
+            BABYLON.BoundingInfo2D.CreateFromSizeToRef(this.size, this._levelBoundingInfo, this.origin);
         };
         Rectangle2D.prototype.setupRectangle2D = function (owner, parent, id, position, size, roundRadius, fill, border, borderThickness) {
             if (roundRadius === void 0) { roundRadius = 0; }
@@ -296,6 +312,6 @@ var BABYLON;
             BABYLON.className("Rectangle2D")
         ], Rectangle2D);
         return Rectangle2D;
-    }(BABYLON.Shape2D));
+    })(BABYLON.Shape2D);
     BABYLON.Rectangle2D = Rectangle2D;
 })(BABYLON || (BABYLON = {}));

+ 16 - 1
src/Canvas2d/babylon.rectangle2d.ts

@@ -148,6 +148,10 @@
         public static notRoundedProperty: Prim2DPropInfo;
         public static roundRadiusProperty: Prim2DPropInfo;
 
+        public get actualSize(): Size {
+            return this.size;
+        }
+
         @instanceLevelProperty(Shape2D.SHAPE2D_PROPCOUNT + 1, pi => Rectangle2D.sizeProperty = pi, false, true)
         public get size(): Size {
             return this._size;
@@ -176,8 +180,19 @@
             this.notRounded = value === 0;
         }
 
+        protected levelIntersect(intersectInfo: IntersectInfo2D): boolean {
+            // If we got there it mean the boundingInfo intersection succeed, if the rectangle has not roundRadius, it means it succeed!
+            if (this.notRounded) {
+                return true;
+            }
+
+            // Well, for now we neglect the area where the pickPosition could be outside due to the roundRadius...
+            // TODO make REAL intersection test here!
+            return true;
+        }
+
         protected updateLevelBoundingInfo() {
-            BoundingInfo2D.CreateFromSizeToRef(this.size, this._levelBoundingInfo);
+            BoundingInfo2D.CreateFromSizeToRef(this.size, this._levelBoundingInfo, this.origin);
         }
 
         protected setupRectangle2D(owner: Canvas2D, parent: Prim2DBase, id: string, position: Vector2, size: Size, roundRadius = 0, fill?: IBrush2D, border?: IBrush2D, borderThickness: number = 1) {

+ 46 - 11
src/Canvas2d/babylon.renderablePrim2d.js

@@ -73,7 +73,7 @@ var BABYLON;
             return curOffset;
         };
         return InstanceClassInfo;
-    }());
+    })();
     BABYLON.InstanceClassInfo = InstanceClassInfo;
     var InstancePropInfo = (function () {
         //uniformLocation: WebGLUniformLocation;
@@ -176,7 +176,7 @@ var BABYLON;
             }
         };
         return InstancePropInfo;
-    }());
+    })();
     BABYLON.InstancePropInfo = InstancePropInfo;
     function instanceData(category, shaderAttributeName) {
         return function (target, propName, descriptor) {
@@ -253,6 +253,9 @@ var BABYLON;
             return this.typeInfo;
         };
         InstanceDataBase.prototype.allocElements = function () {
+            if (!this.dataBuffer) {
+                return;
+            }
             var res = new Array(this.dataElementCount);
             for (var i = 0; i < this.dataElementCount; i++) {
                 res[i] = this.dataBuffer.allocElement();
@@ -260,12 +263,30 @@ var BABYLON;
             this.dataElements = res;
         };
         InstanceDataBase.prototype.freeElements = function () {
+            if (!this.dataElements) {
+                return;
+            }
             for (var _i = 0, _a = this.dataElements; _i < _a.length; _i++) {
                 var ei = _a[_i];
                 this.dataBuffer.freeElement(ei);
             }
             this.dataElements = null;
         };
+        Object.defineProperty(InstanceDataBase.prototype, "dataElementCount", {
+            get: function () {
+                return this._dataElementCount;
+            },
+            set: function (value) {
+                if (value === this._dataElementCount) {
+                    return;
+                }
+                this.freeElements();
+                this._dataElementCount = value;
+                this.allocElements();
+            },
+            enumerable: true,
+            configurable: true
+        });
         __decorate([
             instanceData()
         ], InstanceDataBase.prototype, "zBias", null);
@@ -279,7 +300,7 @@ var BABYLON;
             instanceData()
         ], InstanceDataBase.prototype, "origin", null);
         return InstanceDataBase;
-    }());
+    })();
     BABYLON.InstanceDataBase = InstanceDataBase;
     var RenderablePrim2D = (function (_super) {
         __extends(RenderablePrim2D, _super);
@@ -361,8 +382,8 @@ var BABYLON;
                     var usedCatList = new Array();
                     var partIdList = new Array();
                     var joinedUsedCatList = new Array();
-                    for (var _i = 0, parts_1 = parts; _i < parts_1.length; _i++) {
-                        var dataPart = parts_1[_i];
+                    for (var _i = 0; _i < parts.length; _i++) {
+                        var dataPart = parts[_i];
                         var cat = this.getUsedShaderCategories(dataPart);
                         var cti = dataPart.getClassTreeInfo();
                         // Make sure the instance is visible other the properties won't be set and their size/offset wont be computed
@@ -373,7 +394,9 @@ var BABYLON;
                         var joinCat = cat.join(";");
                         joinedUsedCatList.push(joinCat);
                         InstanceClassInfo._CurCategories = joinCat;
+                        var obj = this.beforeRefreshForLayoutConstruction(dataPart);
                         this.refreshInstanceDataPart(dataPart);
+                        this.afterRefreshForLayoutConstruction(dataPart, obj);
                         this.isVisible = curVisible;
                         var size = 0;
                         cti.fullContent.forEach(function (k, v) {
@@ -488,28 +511,40 @@ var BABYLON;
         RenderablePrim2D.prototype.getUsedShaderCategories = function (dataPart) {
             return [];
         };
+        RenderablePrim2D.prototype.beforeRefreshForLayoutConstruction = function (part) {
+        };
+        RenderablePrim2D.prototype.afterRefreshForLayoutConstruction = function (part, obj) {
+        };
         RenderablePrim2D.prototype.refreshInstanceDataPart = function (part) {
             if (!this.isVisible) {
                 return false;
             }
             part.isVisible = this.isVisible;
-            // Which means, if there's only one data element, we're update it from this method, otherwise it is the responsability of the derived class to call updateInstanceDataPart as many times as needed, properly (look at Text2D's implementation for more information)
+            // Which means, if there's only one data element, we're update it from this method, otherwise it is the responsibility of the derived class to call updateInstanceDataPart as many times as needed, properly (look at Text2D's implementation for more information)
             if (part.dataElementCount === 1) {
+                part.curElement = 0;
                 this.updateInstanceDataPart(part);
             }
             return true;
         };
-        RenderablePrim2D.prototype.updateInstanceDataPart = function (part, positionOffset) {
+        /**
+         * Update the instanceDataBase level properties of a part
+         * @param part the part to update
+         * @param positionOffset to use in multi part per primitive (e.g. the Text2D has N parts for N letter to display), this give the offset to apply (e.g. the position of the letter from the bottom/left corner of the text). You MUST also set customSize.
+         * @param customSize to use in multi part per primitive, this is the size of the overall primitive to display (the bounding rect's size of the Text, for instance). This is mandatory to compute correct transformation based on the Primitive's origin property.
+         */
+        RenderablePrim2D.prototype.updateInstanceDataPart = function (part, positionOffset, customSize) {
             if (positionOffset === void 0) { positionOffset = null; }
+            if (customSize === void 0) { customSize = null; }
             var t = this._globalTransform.multiply(this.renderGroup.invGlobalTransform);
             var size = this.renderGroup.viewportSize;
             var zBias = this.getActualZOffset();
             var offX = 0;
             var offY = 0;
             // If there's an offset, apply the global transformation matrix on it to get a global offset
-            if (positionOffset) {
-                offX = positionOffset.x * t.m[0] + positionOffset.y * t.m[4];
-                offY = positionOffset.x * t.m[1] + positionOffset.y * t.m[5];
+            if (positionOffset && customSize) {
+                offX = (positionOffset.x - (customSize.width * this.origin.x)) * t.m[0] + (positionOffset.y - (customSize.height * this.origin.y)) * t.m[4];
+                offY = (positionOffset.x - (customSize.width * this.origin.x)) * t.m[1] + (positionOffset.y - (customSize.height * this.origin.y)) * t.m[5];
             }
             // Have to convert the coordinates to clip space which is ranged between [-1;1] on X and Y axis, with 0,0 being the left/bottom corner
             // Current coordinates are expressed in renderGroup coordinates ([0, renderGroup.actualSize.width|height]) with 0,0 being at the left/top corner
@@ -536,6 +571,6 @@ var BABYLON;
             BABYLON.className("RenderablePrim2D")
         ], RenderablePrim2D);
         return RenderablePrim2D;
-    }(BABYLON.Prim2DBase));
+    })(BABYLON.Prim2DBase);
     BABYLON.RenderablePrim2D = RenderablePrim2D;
 })(BABYLON || (BABYLON = {}));

+ 44 - 6
src/Canvas2d/babylon.renderablePrim2d.ts

@@ -264,6 +264,9 @@
         }
 
         allocElements() {
+            if (!this.dataBuffer) {
+                return;
+            }
             let res = new Array<DynamicFloatArrayElementInfo>(this.dataElementCount);
             for (let i = 0; i < this.dataElementCount; i++) {
                 res[i] = this.dataBuffer.allocElement();
@@ -272,18 +275,36 @@
         }
 
         freeElements() {
+            if (!this.dataElements) {
+                return;
+            }
             for (let ei of this.dataElements) {
                 this.dataBuffer.freeElement(ei);
             }
             this.dataElements = null;
         }
 
+        get dataElementCount(): number {
+            return this._dataElementCount;
+        }
+
+        set dataElementCount(value: number) {
+            if (value === this._dataElementCount) {
+                return;
+            }
+
+            this.freeElements();
+            this._dataElementCount = value;
+            this.allocElements();
+        }
+
         curElement: number;
-        dataElementCount: number;
         dataElements: DynamicFloatArrayElementInfo[];
         dataBuffer: DynamicFloatArray;
         typeInfo: ClassTreeInfo<InstanceClassInfo, InstancePropInfo>;
 
+        private _dataElementCount: number;
+
     }
 
     @className("RenderablePrim2D")
@@ -390,7 +411,9 @@
                         let joinCat = cat.join(";");
                         joinedUsedCatList.push(joinCat);
                         InstanceClassInfo._CurCategories = joinCat;
+                        let obj = this.beforeRefreshForLayoutConstruction(dataPart);
                         this.refreshInstanceDataPart(dataPart);
+                        this.afterRefreshForLayoutConstruction(dataPart, obj);
                         this.isVisible = curVisible;
 
                         var size = 0;
@@ -522,20 +545,35 @@
             return [];
         }
 
+        protected beforeRefreshForLayoutConstruction(part: InstanceDataBase): any {
+
+        }
+
+        protected afterRefreshForLayoutConstruction(part: InstanceDataBase, obj: any) {
+
+        }
+
         protected refreshInstanceDataPart(part: InstanceDataBase): boolean {
             if (!this.isVisible) {
                 return false;
             }
             part.isVisible = this.isVisible;
 
-            // Which means, if there's only one data element, we're update it from this method, otherwise it is the responsability of the derived class to call updateInstanceDataPart as many times as needed, properly (look at Text2D's implementation for more information)
+            // Which means, if there's only one data element, we're update it from this method, otherwise it is the responsibility of the derived class to call updateInstanceDataPart as many times as needed, properly (look at Text2D's implementation for more information)
             if (part.dataElementCount === 1) {
+                part.curElement = 0;
                 this.updateInstanceDataPart(part);
             }
             return true;
         }
 
-        protected updateInstanceDataPart(part: InstanceDataBase, positionOffset: Vector2 = null) {
+        /**
+         * Update the instanceDataBase level properties of a part
+         * @param part the part to update
+         * @param positionOffset to use in multi part per primitive (e.g. the Text2D has N parts for N letter to display), this give the offset to apply (e.g. the position of the letter from the bottom/left corner of the text). You MUST also set customSize.
+         * @param customSize to use in multi part per primitive, this is the size of the overall primitive to display (the bounding rect's size of the Text, for instance). This is mandatory to compute correct transformation based on the Primitive's origin property.
+         */
+        protected updateInstanceDataPart(part: InstanceDataBase, positionOffset: Vector2 = null, customSize: Size = null) {
             let t = this._globalTransform.multiply(this.renderGroup.invGlobalTransform);
             let size = (<Size>this.renderGroup.viewportSize);
             let zBias = this.getActualZOffset();
@@ -543,9 +581,9 @@
             let offX = 0;
             let offY = 0;
             // If there's an offset, apply the global transformation matrix on it to get a global offset
-            if (positionOffset) {
-                offX = positionOffset.x * t.m[0] + positionOffset.y * t.m[4];
-                offY = positionOffset.x * t.m[1] + positionOffset.y * t.m[5];
+            if (positionOffset && customSize) {
+                offX = (positionOffset.x-(customSize.width*this.origin.x)) * t.m[0] + (positionOffset.y-(customSize.height*this.origin.y)) * t.m[4];
+                offY = (positionOffset.x-(customSize.width*this.origin.x)) * t.m[1] + (positionOffset.y-(customSize.height*this.origin.y)) * t.m[5];
             }
 
             // Have to convert the coordinates to clip space which is ranged between [-1;1] on X and Y axis, with 0,0 being the left/bottom corner

+ 2 - 2
src/Canvas2d/babylon.shape2d.js

@@ -144,7 +144,7 @@ var BABYLON;
             BABYLON.className("Shape2D")
         ], Shape2D);
         return Shape2D;
-    }(BABYLON.RenderablePrim2D));
+    })(BABYLON.RenderablePrim2D);
     BABYLON.Shape2D = Shape2D;
     var Shape2DInstanceData = (function (_super) {
         __extends(Shape2DInstanceData, _super);
@@ -244,6 +244,6 @@ var BABYLON;
             BABYLON.instanceData(Shape2D.SHAPE2D_CATEGORY_BORDERGRADIENT)
         ], Shape2DInstanceData.prototype, "borderGradientTY", null);
         return Shape2DInstanceData;
-    }(BABYLON.InstanceDataBase));
+    })(BABYLON.InstanceDataBase);
     BABYLON.Shape2DInstanceData = Shape2DInstanceData;
 })(BABYLON || (BABYLON = {}));

+ 90 - 27
src/Canvas2d/babylon.smartPropertyPrim.js

@@ -10,7 +10,7 @@ var BABYLON;
         function Prim2DClassInfo() {
         }
         return Prim2DClassInfo;
-    }());
+    })();
     BABYLON.Prim2DClassInfo = Prim2DClassInfo;
     var Prim2DPropInfo = (function () {
         function Prim2DPropInfo() {
@@ -19,13 +19,13 @@ var BABYLON;
         Prim2DPropInfo.PROPKIND_INSTANCE = 2;
         Prim2DPropInfo.PROPKIND_DYNAMIC = 3;
         return Prim2DPropInfo;
-    }());
+    })();
     BABYLON.Prim2DPropInfo = Prim2DPropInfo;
     var PropertyChangedInfo = (function () {
         function PropertyChangedInfo() {
         }
         return PropertyChangedInfo;
-    }());
+    })();
     BABYLON.PropertyChangedInfo = PropertyChangedInfo;
     var ClassTreeInfo = (function () {
         function ClassTreeInfo(baseClass, type, classContentFactory) {
@@ -62,13 +62,13 @@ var BABYLON;
         Object.defineProperty(ClassTreeInfo.prototype, "fullContent", {
             get: function () {
                 if (!this._fullContent) {
-                    var dic_1 = new BABYLON.StringDictionary();
+                    var dic = new BABYLON.StringDictionary();
                     var curLevel = this;
                     while (curLevel) {
-                        curLevel.levelContent.forEach(function (k, v) { return dic_1.add(k, v); });
+                        curLevel.levelContent.forEach(function (k, v) { return dic.add(k, v); });
                         curLevel = curLevel._baseClass;
                     }
-                    this._fullContent = dic_1;
+                    this._fullContent = dic;
                 }
                 return this._fullContent;
             },
@@ -128,7 +128,7 @@ var BABYLON;
             return dic;
         };
         return ClassTreeInfo;
-    }());
+    })();
     BABYLON.ClassTreeInfo = ClassTreeInfo;
     var SmartPropertyPrim = (function () {
         function SmartPropertyPrim() {
@@ -140,22 +140,45 @@ var BABYLON;
             this._instanceDirtyFlags = 0;
             this._isDisposed = false;
             this._levelBoundingInfo = new BABYLON.BoundingInfo2D();
+            this.animations = new Array();
         };
         Object.defineProperty(SmartPropertyPrim.prototype, "isDisposed", {
+            /**
+             * Check if the object is disposed or not.
+             * @returns true if the object is dispose, false otherwise.
+             */
             get: function () {
                 return this._isDisposed;
             },
             enumerable: true,
             configurable: true
         });
+        /**
+         * Disposable pattern, this method must be overloaded by derived types in order to clean up hardware related resources.
+         * @returns false if the object is already dispose, true otherwise. Your implementation must call super.dispose() and check for a false return and return immediately if it's the case.
+         */
         SmartPropertyPrim.prototype.dispose = function () {
             if (this.isDisposed) {
                 return false;
             }
+            // Don't set to null, it may upset somebody...
+            this.animations.splice(0);
             this._isDisposed = true;
             return true;
         };
+        /**
+         * Returns as a new array populated with the Animatable used by the primitive. Must be overloaded by derived primitives.
+         * Look at Sprite2D for more information
+         */
+        SmartPropertyPrim.prototype.getAnimatables = function () {
+            return new Array();
+        };
         Object.defineProperty(SmartPropertyPrim.prototype, "modelKey", {
+            /**
+             * Property giving the Model Key associated to the property.
+             * This value is constructed from the type of the primitive and all the name/value of its properties declared with the modelLevelProperty decorator
+             * @returns the model key string.
+             */
             get: function () {
                 var _this = this;
                 // No need to compute it?
@@ -178,6 +201,10 @@ var BABYLON;
             configurable: true
         });
         Object.defineProperty(SmartPropertyPrim.prototype, "isDirty", {
+            /**
+             * States if the Primitive is dirty and should be rendered again next time.
+             * @returns true is dirty, false otherwise
+             */
             get: function () {
                 return (this._instanceDirtyFlags !== 0) || this._modelDirty;
             },
@@ -185,6 +212,10 @@ var BABYLON;
             configurable: true
         });
         Object.defineProperty(SmartPropertyPrim.prototype, "propDic", {
+            /**
+             * Access the dictionary of properties metadata. Only properties decorated with XXXXLevelProperty are concerned
+             * @returns the dictionary, the key is the property name as declared in Javascript, the value is the metadata object
+             */
             get: function () {
                 if (!this._propInfo) {
                     var cti = ClassTreeInfo.get(Object.getPrototypeOf(this));
@@ -213,7 +244,7 @@ var BABYLON;
             propInfo.name = propName;
             propInfo.dirtyBoundingInfo = dirtyBoundingInfo;
             propInfo.typeLevelCompare = typeLevelCompare;
-            node.levelContent.add(propId.toString(), propInfo);
+            node.levelContent.add(propName, propInfo);
             return propInfo;
         };
         SmartPropertyPrim._checkUnchanged = function (curValue, newValue) {
@@ -236,7 +267,36 @@ var BABYLON;
             }
             return false;
         };
+        SmartPropertyPrim.prototype.markAsDirty = function (propertyName) {
+            var i = propertyName.indexOf(".");
+            if (i !== -1) {
+                propertyName = propertyName.substr(0, i);
+            }
+            var propInfo = this.propDic.get(propertyName);
+            if (!propInfo) {
+                return;
+            }
+            var newValue = this[propertyName];
+            this._handlePropChanged(undefined, newValue, propertyName, propInfo, propInfo.typeLevelCompare);
+        };
         SmartPropertyPrim.prototype._handlePropChanged = function (curValue, newValue, propName, propInfo, typeLevelCompare) {
+            // If the property change also dirty the boundingInfo, update the boundingInfo dirty flags
+            if (propInfo.dirtyBoundingInfo) {
+                this._levelBoundingInfoDirty = true;
+                // Escalate the dirty flag in the instance hierarchy, stop when a renderable group is found or at the end
+                if (this instanceof BABYLON.Prim2DBase) {
+                    var curprim = this.parent;
+                    while (curprim) {
+                        curprim._boundingInfoDirty = true;
+                        if (curprim instanceof BABYLON.Group2D) {
+                            if (curprim.isRenderableGroup) {
+                                break;
+                            }
+                        }
+                        curprim = curprim.parent;
+                    }
+                }
+            }
             // Trigger property changed
             var info = SmartPropertyPrim.propChangedInfo;
             info.oldValue = curValue;
@@ -263,7 +323,7 @@ var BABYLON;
                     }
                     this._modelDirty = true;
                 }
-                else if (propInfo.kind === Prim2DPropInfo.PROPKIND_INSTANCE) {
+                else if ((propInfo.kind === Prim2DPropInfo.PROPKIND_INSTANCE) || (propInfo.kind === Prim2DPropInfo.PROPKIND_DYNAMIC)) {
                     if (!this.isDirty) {
                         this.onPrimBecomesDirty();
                     }
@@ -273,14 +333,28 @@ var BABYLON;
         };
         SmartPropertyPrim.prototype.handleGroupChanged = function (prop) {
         };
+        /**
+         * Check if a given set of properties are dirty or not.
+         * @param flags a ORed combination of Prim2DPropInfo.flagId values
+         * @return true if at least one property is dirty, false if none of them are.
+         */
         SmartPropertyPrim.prototype.checkPropertiesDirty = function (flags) {
             return (this._instanceDirtyFlags & flags) !== 0;
         };
+        /**
+         * Clear a given set of properties.
+         * @param flags a ORed combination of Prim2DPropInfo.flagId values
+         * @return the new set of property still marked as dirty
+         */
         SmartPropertyPrim.prototype.clearPropertiesDirty = function (flags) {
             this._instanceDirtyFlags &= ~flags;
             return this._instanceDirtyFlags;
         };
         Object.defineProperty(SmartPropertyPrim.prototype, "levelBoundingInfo", {
+            /**
+             * Retrieve the boundingInfo for this Primitive, computed based on the primitive itself and NOT its children
+             * @returns {}
+             */
             get: function () {
                 if (this._levelBoundingInfoDirty) {
                     this.updateLevelBoundingInfo();
@@ -291,8 +365,14 @@ var BABYLON;
             enumerable: true,
             configurable: true
         });
+        /**
+         * This method must be overridden by a given Primitive implementation to compute its boundingInfo
+         */
         SmartPropertyPrim.prototype.updateLevelBoundingInfo = function () {
         };
+        /**
+         * Property method called when the Primitive becomes dirty
+         */
         SmartPropertyPrim.prototype.onPrimBecomesDirty = function () {
         };
         SmartPropertyPrim._hookProperty = function (propId, piStore, typeLevelCompare, dirtyBoundingInfo, kind) {
@@ -316,23 +396,6 @@ var BABYLON;
                     var prim = this;
                     // Change the value
                     setter.call(this, val);
-                    // If the property change also dirty the boundingInfo, update the boundingInfo dirty flags
-                    if (propInfo.dirtyBoundingInfo) {
-                        prim._levelBoundingInfoDirty = true;
-                        // Escalate the dirty flag in the instance hierarchy, stop when a renderable group is found or at the end
-                        if (prim instanceof BABYLON.Prim2DBase) {
-                            var curprim = prim.parent;
-                            while (curprim) {
-                                curprim._boundingInfoDirty = true;
-                                if (curprim instanceof BABYLON.Group2D) {
-                                    if (curprim.isRenderableGroup) {
-                                        break;
-                                    }
-                                }
-                                curprim = curprim.parent;
-                            }
-                        }
-                    }
                     // Notify change, dirty flags update
                     prim._handlePropChanged(curVal, val, propName, propInfo, typeLevelCompare);
                 };
@@ -343,7 +406,7 @@ var BABYLON;
             BABYLON.className("SmartPropertyPrim")
         ], SmartPropertyPrim);
         return SmartPropertyPrim;
-    }());
+    })();
     BABYLON.SmartPropertyPrim = SmartPropertyPrim;
     function modelLevelProperty(propId, piStore, typeLevelCompare, dirtyBoundingInfo) {
         if (typeLevelCompare === void 0) { typeLevelCompare = false; }

+ 102 - 25
src/Canvas2d/babylon.smartPropertyPrim.ts

@@ -91,7 +91,7 @@
                     }
                 }
                 let node = new ClassTreeInfo<TClass, TProp>(this, type, this._classContentFactory);
-                let info = { type: type, node: node};
+                let info = { type: type, node: node };
                 this._subClasses.push(info);
                 return info.node;
             }
@@ -126,7 +126,7 @@
         private _type: Object;
         private _classContent: TClass;
         private _baseClass: ClassTreeInfo<TClass, TProp>;
-        private _subClasses: Array<{type: Object, node: ClassTreeInfo<TClass, TProp>}>;
+        private _subClasses: Array<{ type: Object, node: ClassTreeInfo<TClass, TProp> }>;
         private _levelContent: StringDictionary<TProp>;
         private _fullContent: StringDictionary<TProp>;
         private _classContentFactory: (base: TClass) => TClass;
@@ -142,23 +142,57 @@
             this._instanceDirtyFlags = 0;
             this._isDisposed = false;
             this._levelBoundingInfo = new BoundingInfo2D();
+            this.animations = new Array<Animation>();
         }
 
+        /**
+         * An observable that is triggered when a property (using of the XXXXLevelProperty decorator) has its value changing.
+         * You can add an observer that will be triggered only for a given set of Properties using the Mask feature of the Observable and the corresponding Prim2DPropInfo.flagid value (e.g. Prim2DBase.positionProperty.flagid|Prim2DBase.rotationProperty.flagid to be notified only about position or rotation change)
+         */
         public propertyChanged: Observable<PropertyChangedInfo>;
 
+        /**
+         * Check if the object is disposed or not.
+         * @returns true if the object is dispose, false otherwise.
+         */
         public get isDisposed(): boolean {
             return this._isDisposed;
         }
 
+        /**
+         * Disposable pattern, this method must be overloaded by derived types in order to clean up hardware related resources.
+         * @returns false if the object is already dispose, true otherwise. Your implementation must call super.dispose() and check for a false return and return immediately if it's the case.
+         */
         public dispose(): boolean {
             if (this.isDisposed) {
                 return false;
             }
 
+            // Don't set to null, it may upset somebody...
+            this.animations.splice(0);
+
             this._isDisposed = true;
             return true;
         }
 
+        /**
+         * Animation array, more info: http://doc.babylonjs.com/tutorials/Animations
+         */
+        public animations: Animation[];
+
+        /**
+         * Returns as a new array populated with the Animatable used by the primitive. Must be overloaded by derived primitives.
+         * Look at Sprite2D for more information
+         */
+        public getAnimatables(): IAnimatable[] {
+            return new Array<IAnimatable>();
+        }
+
+        /**
+         * Property giving the Model Key associated to the property.
+         * This value is constructed from the type of the primitive and all the name/value of its properties declared with the modelLevelProperty decorator
+         * @returns the model key string.
+         */
         public get modelKey(): string {
 
             // No need to compute it?
@@ -181,10 +215,18 @@
             return modelKey;
         }
 
+        /**
+         * States if the Primitive is dirty and should be rendered again next time.
+         * @returns true is dirty, false otherwise
+         */
         public get isDirty(): boolean {
             return (this._instanceDirtyFlags !== 0) || this._modelDirty;
         }
 
+        /**
+         * Access the dictionary of properties metadata. Only properties decorated with XXXXLevelProperty are concerned
+         * @returns the dictionary, the key is the property name as declared in Javascript, the value is the metadata object
+         */
         private get propDic(): StringDictionary<Prim2DPropInfo> {
             if (!this._propInfo) {
                 let cti = ClassTreeInfo.get<Prim2DClassInfo, Prim2DPropInfo>(Object.getPrototypeOf(this));
@@ -214,7 +256,7 @@
             propInfo.name = propName;
             propInfo.dirtyBoundingInfo = dirtyBoundingInfo;
             propInfo.typeLevelCompare = typeLevelCompare;
-            node.levelContent.add(propId.toString(), propInfo);
+            node.levelContent.add(propName, propInfo);
 
             return propInfo;
         }
@@ -243,7 +285,43 @@
 
         private static propChangedInfo = new PropertyChangedInfo();
 
+        public markAsDirty(propertyName: string) {
+            let i = propertyName.indexOf(".");
+            if (i !== -1) {
+                propertyName = propertyName.substr(0, i);
+            }
+
+            var propInfo = this.propDic.get(propertyName);
+            if (!propInfo) {
+                return;
+            }
+
+            var newValue = this[propertyName];
+            this._handlePropChanged(undefined, newValue, propertyName, propInfo, propInfo.typeLevelCompare);
+        }
+
         private _handlePropChanged<T>(curValue: T, newValue: T, propName: string, propInfo: Prim2DPropInfo, typeLevelCompare: boolean) {
+            // If the property change also dirty the boundingInfo, update the boundingInfo dirty flags
+            if (propInfo.dirtyBoundingInfo) {
+                this._levelBoundingInfoDirty = true;
+
+                // Escalate the dirty flag in the instance hierarchy, stop when a renderable group is found or at the end
+                if (this instanceof Prim2DBase) {
+                    let curprim = (<any>this).parent;
+                    while (curprim) {
+                        curprim._boundingInfoDirty = true;
+
+                        if (curprim instanceof Group2D) {
+                            if (curprim.isRenderableGroup) {
+                                break;
+                            }
+                        }
+
+                        curprim = curprim.parent;
+                    }
+                }
+            }
+
             // Trigger property changed
             let info = SmartPropertyPrim.propChangedInfo;
             info.oldValue = curValue;
@@ -273,7 +351,7 @@
                         this.onPrimBecomesDirty();
                     }
                     this._modelDirty = true;
-                } else if (propInfo.kind === Prim2DPropInfo.PROPKIND_INSTANCE) {
+                } else if ((propInfo.kind === Prim2DPropInfo.PROPKIND_INSTANCE) || (propInfo.kind === Prim2DPropInfo.PROPKIND_DYNAMIC)) {
                     if (!this.isDirty) {
                         this.onPrimBecomesDirty();
                     }
@@ -286,15 +364,29 @@
 
         }
 
+        /**
+         * Check if a given set of properties are dirty or not.
+         * @param flags a ORed combination of Prim2DPropInfo.flagId values
+         * @return true if at least one property is dirty, false if none of them are.
+         */
         public checkPropertiesDirty(flags: number): boolean {
             return (this._instanceDirtyFlags & flags) !== 0;
         }
 
+        /**
+         * Clear a given set of properties.
+         * @param flags a ORed combination of Prim2DPropInfo.flagId values
+         * @return the new set of property still marked as dirty
+         */
         protected clearPropertiesDirty(flags: number): number {
             this._instanceDirtyFlags &= ~flags;
             return this._instanceDirtyFlags;
         }
 
+        /**
+         * Retrieve the boundingInfo for this Primitive, computed based on the primitive itself and NOT its children
+         * @returns {} 
+         */
         public get levelBoundingInfo(): BoundingInfo2D {
             if (this._levelBoundingInfoDirty) {
                 this.updateLevelBoundingInfo();
@@ -303,10 +395,16 @@
             return this._levelBoundingInfo;
         }
 
+        /**
+         * This method must be overridden by a given Primitive implementation to compute its boundingInfo
+         */
         protected updateLevelBoundingInfo() {
 
         }
 
+        /**
+         * Property method called when the Primitive becomes dirty
+         */
         protected onPrimBecomesDirty() {
 
         }
@@ -339,27 +437,6 @@
                     // Change the value
                     setter.call(this, val);
 
-                    // If the property change also dirty the boundingInfo, update the boundingInfo dirty flags
-                    if (propInfo.dirtyBoundingInfo) {
-                        prim._levelBoundingInfoDirty = true;
-
-                        // Escalate the dirty flag in the instance hierarchy, stop when a renderable group is found or at the end
-                        if (prim instanceof Prim2DBase) {
-                            let curprim = prim.parent;
-                            while (curprim) {
-                                curprim._boundingInfoDirty = true;
-
-                                if (curprim instanceof Group2D) {
-                                    if (curprim.isRenderableGroup) {
-                                        break;
-                                    }
-                                }
-
-                                curprim = curprim.parent;
-                            }
-                        }
-                    }
-
                     // Notify change, dirty flags update
                     prim._handlePropChanged(curVal, val, <string>propName, propInfo, typeLevelCompare);
                 }

+ 22 - 4
src/Canvas2d/babylon.sprite2d.js

@@ -69,7 +69,7 @@ var BABYLON;
             return true;
         };
         return Sprite2DRenderCache;
-    }(BABYLON.ModelRenderCache));
+    })(BABYLON.ModelRenderCache);
     BABYLON.Sprite2DRenderCache = Sprite2DRenderCache;
     var Sprite2DInstanceData = (function (_super) {
         __extends(Sprite2DInstanceData, _super);
@@ -127,7 +127,7 @@ var BABYLON;
             BABYLON.instanceData()
         ], Sprite2DInstanceData.prototype, "invertY", null);
         return Sprite2DInstanceData;
-    }(BABYLON.InstanceDataBase));
+    })(BABYLON.InstanceDataBase);
     BABYLON.Sprite2DInstanceData = Sprite2DInstanceData;
     var Sprite2D = (function (_super) {
         __extends(Sprite2D, _super);
@@ -144,6 +144,13 @@ var BABYLON;
             enumerable: true,
             configurable: true
         });
+        Object.defineProperty(Sprite2D.prototype, "actualSize", {
+            get: function () {
+                return this.spriteSize;
+            },
+            enumerable: true,
+            configurable: true
+        });
         Object.defineProperty(Sprite2D.prototype, "spriteSize", {
             get: function () {
                 return this._size;
@@ -185,7 +192,18 @@ var BABYLON;
             configurable: true
         });
         Sprite2D.prototype.updateLevelBoundingInfo = function () {
-            BABYLON.BoundingInfo2D.CreateFromSizeToRef(this.spriteSize, this._levelBoundingInfo);
+            BABYLON.BoundingInfo2D.CreateFromSizeToRef(this.spriteSize, this._levelBoundingInfo, this.origin);
+        };
+        Sprite2D.prototype.getAnimatables = function () {
+            var res = new Array();
+            if (this.texture && this.texture.animations && this.texture.animations.length > 0) {
+                res.push(this.texture);
+            }
+            return res;
+        };
+        Sprite2D.prototype.levelIntersect = function (intersectInfo) {
+            // If we've made it so far it means the boundingInfo intersection test succeed, the Sprite2D is shaped the same, so we always return true
+            return true;
         };
         Sprite2D.prototype.setupSprite2D = function (owner, parent, id, position, texture, spriteSize, spriteLocation, invertY) {
             this.setupRenderablePrim2D(owner, parent, id, position, true);
@@ -278,6 +296,6 @@ var BABYLON;
             BABYLON.className("Sprite2D")
         ], Sprite2D);
         return Sprite2D;
-    }(BABYLON.RenderablePrim2D));
+    })(BABYLON.RenderablePrim2D);
     BABYLON.Sprite2D = Sprite2D;
 })(BABYLON || (BABYLON = {}));

+ 19 - 1
src/Canvas2d/babylon.sprite2d.ts

@@ -121,6 +121,10 @@
             this._texture = value;
         }
 
+        public get actualSize(): Size {
+            return this.spriteSize;
+        }
+
         @instanceLevelProperty(RenderablePrim2D.RENDERABLEPRIM2D_PROPCOUNT + 2, pi => Sprite2D.spriteSizeProperty = pi, false, true)
         public get spriteSize(): Size {
             return this._size;
@@ -158,7 +162,21 @@
         }
 
         protected updateLevelBoundingInfo() {
-            BoundingInfo2D.CreateFromSizeToRef(this.spriteSize, this._levelBoundingInfo);
+            BoundingInfo2D.CreateFromSizeToRef(this.spriteSize, this._levelBoundingInfo, this.origin);
+        }
+
+        public getAnimatables(): IAnimatable[] {
+            let res = new Array<IAnimatable>();
+
+            if (this.texture && this.texture.animations && this.texture.animations.length > 0) {
+                res.push(this.texture);
+            }
+            return res;
+        }
+
+        protected levelIntersect(intersectInfo: IntersectInfo2D): boolean {
+            // If we've made it so far it means the boundingInfo intersection test succeed, the Sprite2D is shaped the same, so we always return true
+            return true;
         }
 
         protected setupSprite2D(owner: Canvas2D, parent: Prim2DBase, id: string, position: Vector2, texture: Texture, spriteSize: Size, spriteLocation: Vector2, invertY: boolean) {

+ 36 - 17
src/Canvas2d/babylon.text2d.js

@@ -31,7 +31,7 @@ var BABYLON;
             this.effect.setTexture("diffuseSampler", this.fontTexture);
             engine.bindBuffers(this.vb, this.ib, [1], 4, this.effect);
             var cur = engine.getAlphaMode();
-            engine.setAlphaMode(BABYLON.Engine.ALPHA_COMBINE);
+            engine.setAlphaMode(BABYLON.Engine.ALPHA_ADD);
             var count = instanceInfo._instancesPartsData[0].usedElementCount;
             if (instanceInfo._owner.owner.supportInstancedArray) {
                 engine.updateAndBindInstancesBuffer(instanceInfo._instancesPartsBuffer[0], null, this.instancingAttributes);
@@ -70,7 +70,7 @@ var BABYLON;
             return true;
         };
         return Text2DRenderCache;
-    }(BABYLON.ModelRenderCache));
+    })(BABYLON.ModelRenderCache);
     BABYLON.Text2DRenderCache = Text2DRenderCache;
     var Text2DInstanceData = (function (_super) {
         __extends(Text2DInstanceData, _super);
@@ -118,7 +118,7 @@ var BABYLON;
             BABYLON.instanceData()
         ], Text2DInstanceData.prototype, "color", null);
         return Text2DInstanceData;
-    }(BABYLON.InstanceDataBase));
+    })(BABYLON.InstanceDataBase);
     BABYLON.Text2DInstanceData = Text2DInstanceData;
     var Text2D = (function (_super) {
         __extends(Text2D, _super);
@@ -154,7 +154,7 @@ var BABYLON;
             },
             set: function (value) {
                 this._text = value;
-                this._actualAreaSize = null; // A change of text will reset the Actual Area Size which will be recomputed next time it's used
+                this._actualSize = null; // A change of text will reset the Actual Area Size which will be recomputed next time it's used
                 this._updateCharCount();
             },
             enumerable: true,
@@ -190,16 +190,16 @@ var BABYLON;
             enumerable: true,
             configurable: true
         });
-        Object.defineProperty(Text2D.prototype, "actualAreaSize", {
+        Object.defineProperty(Text2D.prototype, "actualSize", {
             get: function () {
                 if (this.areaSize) {
                     return this.areaSize;
                 }
-                if (this._actualAreaSize) {
-                    return this._actualAreaSize;
+                if (this._actualSize) {
+                    return this._actualSize;
                 }
-                this._actualAreaSize = this.fontTexture.measureText(this._text, this._tabulationSize);
-                return this._actualAreaSize;
+                this._actualSize = this.fontTexture.measureText(this._text, this._tabulationSize);
+                return this._actualSize;
             },
             enumerable: true,
             configurable: true
@@ -226,7 +226,7 @@ var BABYLON;
             return true;
         };
         Text2D.prototype.updateLevelBoundingInfo = function () {
-            BABYLON.BoundingInfo2D.CreateFromSizeToRef(this.actualAreaSize, this._levelBoundingInfo);
+            BABYLON.BoundingInfo2D.CreateFromSizeToRef(this.actualSize, this._levelBoundingInfo, this.origin);
         };
         Text2D.prototype.setupText2D = function (owner, parent, id, position, fontName, text, areaSize, defaultFontColor, vAlign, hAlign, tabulationSize) {
             this.setupRenderablePrim2D(owner, parent, id, position, true);
@@ -238,7 +238,6 @@ var BABYLON;
             this.hAlign = hAlign;
             this._tabulationSize = tabulationSize;
             this._isTransparent = true;
-            this.origin = BABYLON.Vector2.Zero();
         };
         Text2D.Create = function (parent, id, x, y, fontName, text, defaultFontColor, areaSize, vAlign, hAlign, tabulationSize) {
             if (vAlign === void 0) { vAlign = Text2D.TEXT2D_VALIGN_TOP; }
@@ -249,6 +248,10 @@ var BABYLON;
             text2d.setupText2D(parent.owner, parent, id, new BABYLON.Vector2(x, y), fontName, text, areaSize, defaultFontColor || new BABYLON.Color4(0, 0, 0, 1), vAlign, hAlign, tabulationSize);
             return text2d;
         };
+        Text2D.prototype.levelIntersect = function (intersectInfo) {
+            // For now I can't do something better that boundingInfo is a hit, detecting an intersection on a particular letter would be possible, but do we really need it? Not for now...
+            return true;
+        };
         Text2D.prototype.createModelRenderCache = function (modelKey, isTransparent) {
             var renderCache = new Text2DRenderCache(this.owner.engine, modelKey, isTransparent);
             return renderCache;
@@ -272,14 +275,28 @@ var BABYLON;
             renderCache.ib = engine.createIndexBuffer(ib);
             // Effects
             var ei = this.getDataPartEffectInfo(Text2D.TEXT2D_MAINPARTID, ["index"]);
-            renderCache.effect = engine.createEffect("text2d", ei.attributes, ei.uniforms, ["diffuseSampler"], ei.defines, null, function (e) {
-                //                renderCache.setupUniformsLocation(e, ei.uniforms, Text2D.TEXT2D_MAINPARTID);
-            });
+            renderCache.effect = engine.createEffect("text2d", ei.attributes, ei.uniforms, ["diffuseSampler"], ei.defines, null);
             return renderCache;
         };
         Text2D.prototype.createInstanceDataParts = function () {
             return [new Text2DInstanceData(Text2D.TEXT2D_MAINPARTID, this._charCount)];
         };
+        // Looks like a hack!? Yes! Because that's what it is!
+        // For the InstanceData layer to compute correctly we need to set all the properties involved, which won't be the case if there's no text
+        // This method is called before the layout construction for us to detect this case, set some text and return the initial one to restore it after (there can be some text without char to display, say "\t\n" for instance)
+        Text2D.prototype.beforeRefreshForLayoutConstruction = function (part) {
+            if (!this._charCount) {
+                var curText = this._text;
+                this.text = "A";
+                return curText;
+            }
+        };
+        // if obj contains something, we restore the _text property
+        Text2D.prototype.afterRefreshForLayoutConstruction = function (part, obj) {
+            if (obj !== undefined) {
+                this.text = obj;
+            }
+        };
         Text2D.prototype.refreshInstanceDataPart = function (part) {
             if (!_super.prototype.refreshInstanceDataPart.call(this, part)) {
                 return false;
@@ -288,15 +305,17 @@ var BABYLON;
                 var d = part;
                 var texture = this.fontTexture;
                 var ts = texture.getSize();
+                var textSize = texture.measureText(this.text, this._tabulationSize);
                 var offset = BABYLON.Vector2.Zero();
                 var charxpos = 0;
+                d.dataElementCount = this._charCount;
                 d.curElement = 0;
                 for (var _i = 0, _a = this.text; _i < _a.length; _i++) {
                     var char = _a[_i];
                     // Line feed
                     if (char === "\n") {
                         offset.x = 0;
-                        offset.y += texture.lineHeight;
+                        offset.y -= texture.lineHeight;
                     }
                     // Tabulation ?
                     if (char === "\t") {
@@ -309,7 +328,7 @@ var BABYLON;
                     if (char < " ") {
                         continue;
                     }
-                    this.updateInstanceDataPart(d, offset);
+                    this.updateInstanceDataPart(d, offset, textSize);
                     var ci = texture.getChar(char);
                     offset.x += ci.charWidth;
                     d.topLeftUV = ci.topLeftUV;
@@ -362,6 +381,6 @@ var BABYLON;
             BABYLON.className("Text2D")
         ], Text2D);
         return Text2D;
-    }(BABYLON.RenderablePrim2D));
+    })(BABYLON.RenderablePrim2D);
     BABYLON.Text2D = Text2D;
 })(BABYLON || (BABYLON = {}));

+ 37 - 16
src/Canvas2d/babylon.text2d.ts

@@ -25,7 +25,7 @@
             engine.bindBuffers(this.vb, this.ib, [1], 4, this.effect);
 
             var cur = engine.getAlphaMode();
-            engine.setAlphaMode(Engine.ALPHA_COMBINE);
+            engine.setAlphaMode(Engine.ALPHA_ADD);
             let count = instanceInfo._instancesPartsData[0].usedElementCount;
             if (instanceInfo._owner.owner.supportInstancedArray) {
                 engine.updateAndBindInstancesBuffer(instanceInfo._instancesPartsBuffer[0], null, this.instancingAttributes);
@@ -145,7 +145,7 @@
 
         public set text(value: string) {
             this._text = value;
-            this._actualAreaSize = null;    // A change of text will reset the Actual Area Size which will be recomputed next time it's used
+            this._actualSize = null;    // A change of text will reset the Actual Area Size which will be recomputed next time it's used
             this._updateCharCount();
         }
 
@@ -176,18 +176,18 @@
             this._hAlign = value;
         }
 
-        public get actualAreaSize(): Size {
+        public get actualSize(): Size {
             if (this.areaSize) {
                 return this.areaSize;
             }
 
-            if (this._actualAreaSize) {
-                return this._actualAreaSize;
+            if (this._actualSize) {
+                return this._actualSize;
             }
 
-            this._actualAreaSize = this.fontTexture.measureText(this._text, this._tabulationSize);
+            this._actualSize = this.fontTexture.measureText(this._text, this._tabulationSize);
 
-            return this._actualAreaSize;
+            return this._actualSize;
         }
 
         protected get fontTexture(): FontTexture {
@@ -213,7 +213,7 @@
         }
 
         protected updateLevelBoundingInfo() {
-            BoundingInfo2D.CreateFromSizeToRef(this.actualAreaSize, this._levelBoundingInfo);
+            BoundingInfo2D.CreateFromSizeToRef(this.actualSize, this._levelBoundingInfo, this.origin);
         }
 
         protected setupText2D(owner: Canvas2D, parent: Prim2DBase, id: string, position: Vector2, fontName: string, text: string, areaSize: Size, defaultFontColor: Color4, vAlign, hAlign, tabulationSize: number) {
@@ -227,7 +227,6 @@
             this.hAlign = hAlign;
             this._tabulationSize = tabulationSize;
             this._isTransparent = true;
-            this.origin = Vector2.Zero();
         }
 
         public static Create(parent: Prim2DBase, id: string, x: number, y: number, fontName: string, text: string, defaultFontColor?: Color4, areaSize?: Size, vAlign = Text2D.TEXT2D_VALIGN_TOP, hAlign = Text2D.TEXT2D_HALIGN_LEFT, tabulationSize: number = 4): Text2D {
@@ -238,6 +237,11 @@
             return text2d;
         }
 
+        protected levelIntersect(intersectInfo: IntersectInfo2D): boolean {
+            // For now I can't do something better that boundingInfo is a hit, detecting an intersection on a particular letter would be possible, but do we really need it? Not for now...
+            return true;
+        }
+
         protected createModelRenderCache(modelKey: string, isTransparent: boolean): ModelRenderCache {
             let renderCache = new Text2DRenderCache(this.owner.engine, modelKey, isTransparent);
             return renderCache;
@@ -267,9 +271,7 @@
 
             // Effects
             let ei = this.getDataPartEffectInfo(Text2D.TEXT2D_MAINPARTID, ["index"]);
-            renderCache.effect = engine.createEffect("text2d", ei.attributes, ei.uniforms, ["diffuseSampler"], ei.defines, null, e => {
-//                renderCache.setupUniformsLocation(e, ei.uniforms, Text2D.TEXT2D_MAINPARTID);
-            });
+            renderCache.effect = engine.createEffect("text2d", ei.attributes, ei.uniforms, ["diffuseSampler"], ei.defines, null);
 
             return renderCache;
         }
@@ -278,6 +280,24 @@
             return [new Text2DInstanceData(Text2D.TEXT2D_MAINPARTID, this._charCount)];
         }
 
+        // Looks like a hack!? Yes! Because that's what it is!
+        // For the InstanceData layer to compute correctly we need to set all the properties involved, which won't be the case if there's no text
+        // This method is called before the layout construction for us to detect this case, set some text and return the initial one to restore it after (there can be some text without char to display, say "\t\n" for instance)
+        protected beforeRefreshForLayoutConstruction(part: InstanceDataBase): any {
+            if (!this._charCount) {
+                let curText = this._text;
+                this.text = "A";
+                return curText;
+            }
+        }
+
+        // if obj contains something, we restore the _text property
+        protected afterRefreshForLayoutConstruction(part: InstanceDataBase, obj: any) {
+            if (obj !== undefined) {
+                this.text = obj;
+            }
+        }
+
         protected refreshInstanceDataPart(part: InstanceDataBase): boolean {
             if (!super.refreshInstanceDataPart(part)) {
                 return false;
@@ -287,16 +307,17 @@
                 let d = <Text2DInstanceData>part;
                 let texture = this.fontTexture;
                 let ts = texture.getSize();
-
+                let textSize = texture.measureText(this.text, this._tabulationSize);
                 let offset = Vector2.Zero();
                 let charxpos = 0;
+                d.dataElementCount = this._charCount;
                 d.curElement = 0;
                 for (let char of this.text) {
 
                     // Line feed
                     if (char === "\n") {
                         offset.x = 0;
-                        offset.y += texture.lineHeight;
+                        offset.y -= texture.lineHeight;
                     }
 
                     // Tabulation ?
@@ -313,7 +334,7 @@
                         continue;
                     }
 
-                    this.updateInstanceDataPart(d, offset);
+                    this.updateInstanceDataPart(d, offset, textSize);
 
                     let ci = texture.getChar(char);
                     offset.x += ci.charWidth;
@@ -348,7 +369,7 @@
         private _defaultFontColor: Color4;
         private _text: string;
         private _areaSize: Size;
-        private _actualAreaSize: Size;
+        private _actualSize: Size;
         private _vAlign: number;
         private _hAlign: number;
     }

+ 1 - 1
src/Canvas2d/babylon.worldSpaceCanvas2d.js

@@ -22,6 +22,6 @@ var BABYLON;
             }
         };
         return WorldSpaceCanvas2d;
-    }(BABYLON.Mesh));
+    })(BABYLON.Mesh);
     BABYLON.WorldSpaceCanvas2d = WorldSpaceCanvas2d;
 })(BABYLON || (BABYLON = {}));

+ 1 - 1
src/Collisions/babylon.collider.js

@@ -267,6 +267,6 @@ var BABYLON;
             this._destinationPoint.subtractToRef(this.intersectionPoint, vel);
         };
         return Collider;
-    }());
+    })();
     BABYLON.Collider = Collider;
 })(BABYLON || (BABYLON = {}));

+ 2 - 2
src/Collisions/babylon.collisionCoordinator.js

@@ -205,7 +205,7 @@ var BABYLON;
             };
         };
         return CollisionCoordinatorWorker;
-    }());
+    })();
     BABYLON.CollisionCoordinatorWorker = CollisionCoordinatorWorker;
     var CollisionCoordinatorLegacy = (function () {
         function CollisionCoordinatorLegacy() {
@@ -268,6 +268,6 @@ var BABYLON;
             this._collideWithWorld(position, velocity, collider, maximumRetry, finalPosition, excludedMesh);
         };
         return CollisionCoordinatorLegacy;
-    }());
+    })();
     BABYLON.CollisionCoordinatorLegacy = CollisionCoordinatorLegacy;
 })(BABYLON || (BABYLON = {}));

+ 3 - 3
src/Collisions/babylon.collisionWorker.js

@@ -32,7 +32,7 @@ var BABYLON;
             delete this._geometries[id];
         };
         return CollisionCache;
-    }());
+    })();
     BABYLON.CollisionCache = CollisionCache;
     var CollideWorker = (function () {
         function CollideWorker(collider, _collisionCache, finalPosition) {
@@ -144,7 +144,7 @@ var BABYLON;
             return this.collider._canDoCollision(BABYLON.Vector3.FromArray(subMesh.sphereCenter), subMesh.sphereRadius, BABYLON.Vector3.FromArray(subMesh.boxMinimum), BABYLON.Vector3.FromArray(subMesh.boxMaximum));
         };
         return CollideWorker;
-    }());
+    })();
     BABYLON.CollideWorker = CollideWorker;
     var CollisionDetectorTransferable = (function () {
         function CollisionDetectorTransferable() {
@@ -206,7 +206,7 @@ var BABYLON;
             postMessage(reply, undefined);
         };
         return CollisionDetectorTransferable;
-    }());
+    })();
     BABYLON.CollisionDetectorTransferable = CollisionDetectorTransferable;
     //check if we are in a web worker, as this code should NOT run on the main UI thread
     try {

+ 2 - 2
src/Collisions/babylon.pickingInfo.js

@@ -9,7 +9,7 @@ var BABYLON;
             this.subMeshId = 0;
         }
         return IntersectionInfo;
-    }());
+    })();
     BABYLON.IntersectionInfo = IntersectionInfo;
     var PickingInfo = (function () {
         function PickingInfo() {
@@ -71,6 +71,6 @@ var BABYLON;
             return new BABYLON.Vector2(uv0.x + uv1.x + uv2.x, uv0.y + uv1.y + uv2.y);
         };
         return PickingInfo;
-    }());
+    })();
     BABYLON.PickingInfo = PickingInfo;
 })(BABYLON || (BABYLON = {}));

+ 1 - 1
src/Culling/Octrees/babylon.octree.js

@@ -83,6 +83,6 @@ var BABYLON;
             }
         };
         return Octree;
-    }());
+    })();
     BABYLON.Octree = Octree;
 })(BABYLON || (BABYLON = {}));

+ 1 - 1
src/Culling/Octrees/babylon.octreeBlock.js

@@ -117,6 +117,6 @@ var BABYLON;
             BABYLON.Octree._CreateBlocks(this._minPoint, this._maxPoint, this.entries, this._capacity, this._depth, this._maxDepth, this, this._creationFunc);
         };
         return OctreeBlock;
-    }());
+    })();
     BABYLON.OctreeBlock = OctreeBlock;
 })(BABYLON || (BABYLON = {}));

+ 1 - 1
src/Culling/babylon.boundingBox.js

@@ -138,6 +138,6 @@ var BABYLON;
             return true;
         };
         return BoundingBox;
-    }());
+    })();
     BABYLON.BoundingBox = BoundingBox;
 })(BABYLON || (BABYLON = {}));

+ 1 - 1
src/Culling/babylon.boundingInfo.js

@@ -114,6 +114,6 @@ var BABYLON;
             return true;
         };
         return BoundingInfo;
-    }());
+    })();
     BABYLON.BoundingInfo = BoundingInfo;
 })(BABYLON || (BABYLON = {}));

+ 1 - 1
src/Culling/babylon.boundingSphere.js

@@ -44,6 +44,6 @@ var BABYLON;
             return true;
         };
         return BoundingSphere;
-    }());
+    })();
     BABYLON.BoundingSphere = BoundingSphere;
 })(BABYLON || (BABYLON = {}));

+ 1 - 1
src/Culling/babylon.ray.js

@@ -269,6 +269,6 @@ var BABYLON;
         Ray.smallnum = 0.00000001;
         Ray.rayl = 10e8;
         return Ray;
-    }());
+    })();
     BABYLON.Ray = Ray;
 })(BABYLON || (BABYLON = {}));

+ 1 - 1
src/Debug/babylon.debugLayer.js

@@ -674,6 +674,6 @@ var BABYLON;
             }
         };
         return DebugLayer;
-    }());
+    })();
     BABYLON.DebugLayer = DebugLayer;
 })(BABYLON || (BABYLON = {}));

+ 1 - 1
src/Debug/babylon.skeletonViewer.js

@@ -131,7 +131,7 @@ var BABYLON;
                 }
             };
             return SkeletonViewer;
-        }());
+        })();
         Debug.SkeletonViewer = SkeletonViewer;
     })(Debug = BABYLON.Debug || (BABYLON.Debug = {}));
 })(BABYLON || (BABYLON = {}));

+ 1 - 1
src/Layer/babylon.layer.js

@@ -133,6 +133,6 @@ var BABYLON;
             this.onBeforeRenderObservable.clear();
         };
         return Layer;
-    }());
+    })();
     BABYLON.Layer = Layer;
 })(BABYLON || (BABYLON = {}));

+ 1 - 1
src/LensFlare/babylon.lensFlare.js

@@ -18,6 +18,6 @@ var BABYLON;
             system.lensFlares.push(this);
         }
         return LensFlare;
-    }());
+    })();
     BABYLON.LensFlare = LensFlare;
 })(BABYLON || (BABYLON = {}));

+ 1 - 1
src/LensFlare/babylon.lensFlareSystem.js

@@ -208,6 +208,6 @@ var BABYLON;
             return serializationObject;
         };
         return LensFlareSystem;
-    }());
+    })();
     BABYLON.LensFlareSystem = LensFlareSystem;
 })(BABYLON || (BABYLON = {}));

+ 1 - 1
src/Lights/Shadows/babylon.shadowGenerator.js

@@ -402,6 +402,6 @@ var BABYLON;
         ShadowGenerator._FILTER_POISSONSAMPLING = 2;
         ShadowGenerator._FILTER_BLURVARIANCESHADOWMAP = 3;
         return ShadowGenerator;
-    }());
+    })();
     BABYLON.ShadowGenerator = ShadowGenerator;
 })(BABYLON || (BABYLON = {}));

+ 1 - 1
src/Lights/babylon.directionalLight.js

@@ -124,6 +124,6 @@ var BABYLON;
             BABYLON.serialize()
         ], DirectionalLight.prototype, "autoUpdateExtends", void 0);
         return DirectionalLight;
-    }(BABYLON.Light));
+    })(BABYLON.Light);
     BABYLON.DirectionalLight = DirectionalLight;
 })(BABYLON || (BABYLON = {}));

+ 0 - 0
src/Lights/babylon.hemisphericLight.js


Một số tệp đã không được hiển thị bởi vì quá nhiều tập tin thay đổi trong này khác