Browse Source

Merge remote-tracking branch 'BabylonJS/master' into WebVR-Fixes

Raanan Weber 9 năm trước cách đây
mục cha
commit
c61e42c7fb
78 tập tin đã thay đổi với 10119 bổ sung4634 xóa
  1. 2 0
      Exporters/Unity 5/Unity3D2Babylon/ExportationOptions.cs
  2. 1 1
      Exporters/Unity 5/Unity3D2Babylon/ExporterWindow.cs
  3. 24 22
      Tools/Gulp/config.json
  4. 21 10
      Tools/Gulp/gulpfile.js
  5. 2 1
      Tools/Gulp/package.json
  6. 24 24
      dist/preview release/babylon.core.js
  7. 4199 3868
      dist/preview release/babylon.d.ts
  8. 38 37
      dist/preview release/babylon.js
  9. 1828 212
      dist/preview release/babylon.max.js
  10. 37 36
      dist/preview release/babylon.noworker.js
  11. 12 11
      dist/preview release/what's new.md
  12. 40 35
      src/Canvas2d/babylon.canvas2d.js
  13. 46 33
      src/Canvas2d/babylon.canvas2d.ts
  14. 306 0
      src/Canvas2d/babylon.ellipse2d.js
  15. 321 0
      src/Canvas2d/babylon.ellipse2d.ts
  16. 17 7
      src/Canvas2d/babylon.group2d.js
  17. 17 6
      src/Canvas2d/babylon.group2d.ts
  18. 991 0
      src/Canvas2d/babylon.lines2d.js
  19. 973 0
      src/Canvas2d/babylon.lines2d.ts
  20. 1 1
      src/Canvas2d/babylon.modelRenderCache.ts
  21. 3 3
      src/Canvas2d/babylon.prim2dBase.js
  22. 3 3
      src/Canvas2d/babylon.prim2dBase.ts
  23. 27 15
      src/Canvas2d/babylon.rectangle2d.js
  24. 26 15
      src/Canvas2d/babylon.rectangle2d.ts
  25. 42 6
      src/Canvas2d/babylon.renderablePrim2d.js
  26. 46 6
      src/Canvas2d/babylon.renderablePrim2d.ts
  27. 2 2
      src/Canvas2d/babylon.shape2d.js
  28. 2 4
      src/Canvas2d/babylon.shape2d.ts
  29. 13 0
      src/Canvas2d/babylon.smartPropertyPrim.js
  30. 16 1
      src/Canvas2d/babylon.smartPropertyPrim.ts
  31. 25 9
      src/Canvas2d/babylon.sprite2d.js
  32. 26 8
      src/Canvas2d/babylon.sprite2d.ts
  33. 20 7
      src/Canvas2d/babylon.text2d.js
  34. 20 4
      src/Canvas2d/babylon.text2d.ts
  35. 6 6
      src/Canvas2d/babylon.worldSpaceCanvas2d.js
  36. 1 1
      src/Canvas2d/babylon.worldSpaceCanvas2d.ts
  37. 1 1
      src/Materials/Textures/Procedurals/babylon.proceduralTexture.ts
  38. 112 0
      src/Math/babylon.math.js
  39. 139 1
      src/Math/babylon.math.ts
  40. 1 0
      src/Particles/babylon.solidParticle.js
  41. 1 0
      src/Particles/babylon.solidParticle.ts
  42. 102 73
      src/Particles/babylon.solidParticleSystem.js
  43. 112 79
      src/Particles/babylon.solidParticleSystem.ts
  44. 2 2
      src/PostProcess/babylon.anaglyphPostProcess.js
  45. 2 2
      src/PostProcess/babylon.anaglyphPostProcess.ts
  46. 2 2
      src/PostProcess/babylon.blackAndWhitePostProcess.js
  47. 2 2
      src/PostProcess/babylon.blackAndWhitePostProcess.ts
  48. 2 2
      src/PostProcess/babylon.blurPostProcess.js
  49. 2 2
      src/PostProcess/babylon.blurPostProcess.ts
  50. 2 2
      src/PostProcess/babylon.colorCorrectionPostProcess.js
  51. 2 2
      src/PostProcess/babylon.colorCorrectionPostProcess.ts
  52. 2 2
      src/PostProcess/babylon.convolutionPostProcess.js
  53. 2 2
      src/PostProcess/babylon.convolutionPostProcess.ts
  54. 2 2
      src/PostProcess/babylon.displayPassPostProcess.js
  55. 2 2
      src/PostProcess/babylon.displayPassPostProcess.ts
  56. 2 2
      src/PostProcess/babylon.filterPostProcess.js
  57. 2 2
      src/PostProcess/babylon.filterPostProcess.ts
  58. 2 2
      src/PostProcess/babylon.fxaaPostProcess.js
  59. 2 2
      src/PostProcess/babylon.fxaaPostProcess.ts
  60. 2 2
      src/PostProcess/babylon.passPostProcess.js
  61. 2 2
      src/PostProcess/babylon.passPostProcess.ts
  62. 8 8
      src/PostProcess/babylon.postProcess.js
  63. 13 10
      src/PostProcess/babylon.postProcess.ts
  64. 2 2
      src/PostProcess/babylon.refractionPostProcess.js
  65. 2 2
      src/PostProcess/babylon.refractionPostProcess.ts
  66. 5 0
      src/Shaders/ellipse2d.fragment.fx
  67. 108 0
      src/Shaders/ellipse2d.vertex.fx
  68. 5 0
      src/Shaders/lines2d.fragment.fx
  69. 68 0
      src/Shaders/lines2d.vertex.fx
  70. 34 5
      src/Shaders/rect2d.vertex.fx
  71. 1 1
      src/States/babylon.depthCullingState.js
  72. 1 1
      src/States/babylon.depthCullingState.ts
  73. 2 2
      src/Tools/babylon.dynamicFloatArray.js
  74. 2 2
      src/Tools/babylon.dynamicFloatArray.ts
  75. 86 7
      src/Tools/babylon.tools.js
  76. 98 10
      src/Tools/babylon.tools.ts
  77. 1 0
      src/babylon.engine.js
  78. 1 0
      src/babylon.engine.ts

+ 2 - 0
Exporters/Unity 5/Unity3D2Babylon/ExportationOptions.cs

@@ -9,6 +9,7 @@ namespace Unity3D2Babylon
         public float ReflectionDefaultLevel { get; set; }
         public bool ExportCollisions { get; set; }
         public bool ExportPhysics { get; set; }
+        public bool ExportShadows { get; set; }
         public SerializableVector3 CameraEllipsoid { get; set; }
         public SerializableVector3 Gravity { get; set; }
 
@@ -18,6 +19,7 @@ namespace Unity3D2Babylon
             ReflectionDefaultLevel = 0.3f;
             ExportCollisions = false;
             ExportPhysics = false;
+            ExportShadows = false;
             CameraEllipsoid = new Vector3(0.5f, 1.0f, 0.5f);
             Gravity = new Vector3(0, -0.9f, 0);
         }

+ 1 - 1
Exporters/Unity 5/Unity3D2Babylon/ExporterWindow.cs

@@ -143,7 +143,7 @@ namespace Unity3D2Babylon
                 var outputFile = sceneBuilder.WriteToBabylonFile();
 
                 watch.Stop();
-                ReportProgress(1, $"Exportation done in {watch.Elapsed.TotalSeconds:0.00}s");
+                ReportProgress(1, string.Format("Exportation done in {0:0.00}s", watch.Elapsed.TotalSeconds));
                 EditorUtility.ClearProgressBar();
 
                 sceneBuilder.GenerateStatus(logs);

+ 24 - 22
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",
@@ -166,10 +166,12 @@
       "../../src/Canvas2d/babylon.shape2d.js",
       "../../src/Canvas2d/babylon.group2d.js",
       "../../src/Canvas2d/babylon.rectangle2d.js",
+      "../../src/Canvas2d/babylon.ellipse2d.js",
       "../../src/Canvas2d/babylon.sprite2d.js",
       "../../src/Canvas2d/babylon.text2d.js",
+      "../../src/Canvas2d/babylon.lines2d.js",
       "../../src/Canvas2d/babylon.canvas2d.js",
-      "../../src/Canvas2d/babylon.worldspacecanvas2d.js",
+      "../../src/Canvas2d/babylon.worldSpaceCanvas2d.js",
       "../../src/Materials/babylon.shaderMaterial.js",
       "../../src/Tools/babylon.tools.dds.js",
       "../../src/Physics/Plugins/babylon.cannonJSPlugin.js",
@@ -181,11 +183,11 @@
       "../../src/PostProcess/babylon.vrDistortionCorrectionPostProcess.js",
       "../../src/Tools/babylon.virtualJoystick.js",
       "../../src/Cameras/babylon.virtualJoysticksCamera.js",      
-      "../../src/cameras/inputs/babylon.freecamera.input.virtualjoystick.js",
+      "../../src/Cameras/Inputs/babylon.freecamera.input.virtualjoystick.js",
       "../../src/PostProcess/babylon.anaglyphPostProcess.js",
       "../../src/Rendering/babylon.outlineRenderer.js",
       "../../src/Tools/babylon.assetsManager.js",
-      "../../src/cameras/vr/babylon.vrCameraMetrics.js",
+      "../../src/Cameras/VR/babylon.vrCameraMetrics.js",
       "../../src/Cameras/VR/babylon.vrDeviceOrientationCamera.js",
       "../../src/Cameras/VR/babylon.webVRCamera.js",
       "../../src/Tools/babylon.sceneOptimizer.js",
@@ -220,14 +222,14 @@
       "../../src/Probes/babylon.reflectionProbe.js",
       "../../src/Particles/babylon.solidParticle.js",
       "../../src/Particles/babylon.solidParticleSystem.js",
-      "../../src/tools/hdr/babylon.tools.cubemaptosphericalpolynomial.js",
-      "../../src/tools/hdr/babylon.tools.panoramatocubemap.js",
-      "../../src/tools/hdr/babylon.tools.hdr.js",
-      "../../src/tools/hdr/babylon.tools.pmremGenerator.js",
-      "../../src/materials/textures/babylon.hdrcubetexture.js",
-      "../../src/debug/babylon.skeletonViewer.js",
+      "../../src/Tools/HDR/babylon.tools.cubemapToSphericalPolynomial.js",
+      "../../src/Tools/HDR/babylon.tools.panoramaToCubemap.js",
+      "../../src/Tools/HDR/babylon.tools.hdr.js",
+      "../../src/Tools/HDR/babylon.tools.pmremgenerator.js",
+      "../../src/Materials/Textures/babylon.hdrCubeTexture.js",
+      "../../src/Debug/babylon.skeletonViewer.js",
       "../../src/Materials/Textures/babylon.colorGradingTexture.js",
-      "../../src/materials/babylon.pbrmaterial.js",      
+      "../../src/Materials/babylon.pbrMaterial.js",      
       "../../src/Debug/babylon.debugLayer.js"
     ]
   }

+ 21 - 10
Tools/Gulp/gulpfile.js

@@ -12,6 +12,7 @@ var changed = require('gulp-changed');
 var runSequence = require('run-sequence');
 var replace = require("gulp-replace");
 var uncommentShader = require("./gulp-removeShaderComments");
+var expect = require('gulp-expect-file');
 
 var config = require("./config.json");
 
@@ -36,6 +37,7 @@ function includeShadersName(filename) {
 gulp.task("includeShaders", function (cb) {
     includeShadersStream = config.includeShadersDirectories.map(function (shadersDef) {
         return gulp.src(shadersDef.files).
+            pipe(expect.real({ errorOnFailure: true }, shadersDef.files)).
             pipe(uncommentShader()).
             pipe(srcToVariable({
             variableName: shadersDef.variable, asMap: true, namingCallback: includeShadersName
@@ -47,6 +49,7 @@ gulp.task("includeShaders", function (cb) {
 gulp.task("shaders", ["includeShaders"], function (cb) {
     shadersStream = config.shadersDirectories.map(function (shadersDef) {
         return gulp.src(shadersDef.files).
+            pipe(expect.real({ errorOnFailure: true }, shadersDef.files)).
             pipe(uncommentShader()).
             pipe(srcToVariable({
             variableName: shadersDef.variable, asMap: true, namingCallback: shadersName
@@ -57,9 +60,12 @@ gulp.task("shaders", ["includeShaders"], function (cb) {
 
 gulp.task("workers", function (cb) {
     workersStream = config.workers.map(function (workerDef) {
-        return gulp.src(workerDef.files).pipe(uglify()).pipe(srcToVariable({
-            variableName: workerDef.variable
-        }));
+        return gulp.src(workerDef.files).
+            pipe(expect.real({ errorOnFailure: true }, workerDef.files)).
+            pipe(uglify()).
+            pipe(srcToVariable({
+                variableName: workerDef.variable
+            }));
     });
     cb();
 });
@@ -68,8 +74,8 @@ gulp.task("workers", function (cb) {
 Compiles all typescript files and creating a declaration file.
 */
 gulp.task('typescript-compile', function () {
-    var tsResult = gulp.src(config.core.typescript)
-        .pipe(typescript({
+    var tsResult = gulp.src(config.core.typescript).
+        pipe(typescript({
             noExternalResolve: true,
             target: 'ES5',
             declarationFiles: true,
@@ -114,7 +120,8 @@ gulp.task('typescript-sourcemaps', function () {
 
 gulp.task("buildCore", ["shaders"], function () {
     return merge2(
-        gulp.src(config.core.files),
+        gulp.src(config.core.files).        
+            pipe(expect.real({ errorOnFailure: true }, config.core.files)),
         shadersStream,
         includeShadersStream
         )
@@ -129,8 +136,10 @@ gulp.task("buildCore", ["shaders"], function () {
 
 gulp.task("buildNoWorker", ["shaders"], function () {
     return merge2(
-        gulp.src(config.core.files),
-        gulp.src(config.extras.files),
+        gulp.src(config.core.files).        
+            pipe(expect.real({ errorOnFailure: true }, config.core.files)),
+        gulp.src(config.extras.files).        
+            pipe(expect.real({ errorOnFailure: true }, config.extras.files)),
         shadersStream,
         includeShadersStream
         )
@@ -145,8 +154,10 @@ gulp.task("buildNoWorker", ["shaders"], function () {
 
 gulp.task("build", ["workers", "shaders"], function () {
     return merge2(
-        gulp.src(config.core.files),
-        gulp.src(config.extras.files),
+        gulp.src(config.core.files).        
+            pipe(expect.real({ errorOnFailure: true }, config.core.files)),
+        gulp.src(config.extras.files).        
+            pipe(expect.real({ errorOnFailure: true }, config.extras.files)),   
         shadersStream,
         includeShadersStream,
         workersStream

+ 2 - 1
Tools/Gulp/package.json

@@ -21,6 +21,7 @@
     "gulp-changed": "~1.2.1",
     "run-sequence": "~1.1.0",
     "gulp-replace": "~0.5.3",
-    "gulp-content-to-variable": "^0.1.0"
+    "gulp-content-to-variable": "^0.1.0",
+    "gulp-expect-file": "^0.0.7"
   }
 }

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


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


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


Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 1828 - 212
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


+ 12 - 11
dist/preview release/what's new.md

@@ -9,16 +9,14 @@
     - 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))
-    - Introduced new observable system to handle events ([nockawa](https://github.com/nockawa), [deltakosh](https://github.com/deltakosh))
+    - Introduced new observable system to handle events ([quick doc](http://www.html5gamedevs.com/topic/22655-big-brother-is-back-observable-everywhere/)) ([nockawa](https://github.com/nockawa), [deltakosh](https://github.com/deltakosh))
     - Added a new VR camera : VRDeviceOrientationArcRotateCamera ([temechon](https://github.com/Temechon))
-    - Unity3D exporter: Added support for lightmaps ([davrous](https://github.com/davrous), [deltakosh](https://github.com/deltakosh))
-    - 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))	
+    - 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))
@@ -32,6 +30,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))
@@ -39,11 +38,13 @@
     - 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. ([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))
+    - Added StringDictionary<T> class to implement an efficient generic typed string dictionary based on Javascript associative array. ([quick doc](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**
+    - Unity3D exporter: Added support for lightmaps ([davrous](https://github.com/davrous), [deltakosh](https://github.com/deltakosh))
+    - Unity3D exporter: Added support for export and run (local webserver) ([davrous](https://github.com/davrous), [deltakosh](https://github.com/deltakosh))
     - Unity exporter now support skeletons ([sebavan](https://github.com/sebavan))
     - Support for 3dsmax 2017 ([deltakosh](https://github.com/deltakosh))
     - Added support for up to 8 bones influences per vertex for 3dsmax exporter ([deltakosh](https://github.com/deltakosh))

+ 40 - 35
src/Canvas2d/babylon.canvas2d.js

@@ -36,64 +36,62 @@ var BABYLON;
             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.
+         * Create a new 2D ScreenSpace Rendering Canvas, it is a 2D rectangle that has a size (width/height) and a position relative to the bottom/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.
+         * Options:
+         *  - id: a text identifier, for information purpose only
+         *  - pos: the position of the canvas, relative from the bottom/left of the scene's viewport
+         *  - 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
+         *  - cachingStrategy: either CACHESTRATEGY_TOPLEVELGROUPS, CACHESTRATEGY_ALLGROUPS, CACHESTRATEGY_CANVAS, CACHESTRATEGY_DONTCACHE. Please refer to their respective documentation for more information. Default is Canvas2D.CACHESTRATEGY_TOPLEVELGROUPS
+         *  - enableInteraction: if true the pointer events will be listened and rerouted to the appropriate primitives of the Canvas2D through the Prim2DBase.onPointerEventObservable observable property.
          */
-        Canvas2D.CreateScreenSpace = function (scene, name, pos, size, cachingStrategy, enableInteraction) {
-            if (cachingStrategy === void 0) { cachingStrategy = Canvas2D.CACHESTRATEGY_TOPLEVELGROUPS; }
-            if (enableInteraction === void 0) { enableInteraction = true; }
+        Canvas2D.CreateScreenSpace = function (scene, options) {
             var c = new Canvas2D();
-            c.setupCanvas(scene, name, size, true, cachingStrategy, enableInteraction);
-            c.position = pos;
-            c.origin = BABYLON.Vector2.Zero();
+            c.setupCanvas(scene, options && options.id || null, options && options.size || null, true, options && options.cachingStrategy || Canvas2D.CACHESTRATEGY_TOPLEVELGROUPS, options && options.enableInteraction || true);
+            c.position = options && options.pos || BABYLON.Vector2.Zero();
+            c.origin = options && options.origin || BABYLON.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.
          * @param scene the Scene that owns the Canvas
-         * @param name the name of the Canvas, for information purpose only
-         * @param position the position of the Canvas in World Space
-         * @param rotation the rotation of the Canvas in World Space
          * @param size the dimension of the Canvas in World Space
-         * @param renderScaleFactor A scale factor applied to create the rendering texture that will be mapped in the Scene Rectangle. If you set 2 for instance the texture will be twice large in width and height. A greater value will allow to achieve a better rendering quality.
+         * Options:
+         *  - id: a text identifier, for information purpose only, default is null.
+         *  - position the position of the Canvas in World Space, default is [0,0,0]
+         *  - rotation the rotation of the Canvas in World Space, default is Quaternion.Identity()
+         *  - renderScaleFactor A scale factor applied to create the rendering texture that will be mapped in the Scene Rectangle. If you set 2 for instance the texture will be twice large in width and height. A greater value will allow to achieve a better rendering quality. Default value is 1.
          * BE AWARE that the Canvas true dimension will be size*renderScaleFactor, then all coordinates and size will have to be express regarding this size.
          * TIPS: if you want a renderScaleFactor independent reference of frame, create a child Group2D in the Canvas with position 0,0 and size set to null, then set its scale property to the same amount than the renderScaleFactor, put all your primitive inside using coordinates regarding the size property you pick for the Canvas and you'll be fine.
-         * @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
+         * - sideOrientation: Unexpected behavior occur if the value is different from Mesh.DEFAULTSIDE right now, so please use this one, which is the default.
+         * - cachingStrategy Must be CACHESTRATEGY_CANVAS for now, which is the default.
          */
-        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) {
+        Canvas2D.CreateWorldSpace = function (scene, size, options) {
+            var cs = options && options.cachingStrategy || Canvas2D.CACHESTRATEGY_CANVAS;
+            if (cs !== Canvas2D.CACHESTRATEGY_CANVAS) {
                 throw new Error("Right now only the CACHESTRATEGY_CANVAS cache Strategy is supported for WorldSpace Canvas. More will come soon!");
             }
             //if (cachingStrategy === Canvas2D.CACHESTRATEGY_DONTCACHE) {
             //    throw new Error("CACHESTRATEGY_DONTCACHE cache Strategy can't be used for WorldSpace Canvas");
             //}
-            if (!sideOrientation) {
-                sideOrientation = BABYLON.Mesh.DEFAULTSIDE;
-            }
+            var id = options && options.id || null;
+            var rsf = options && options.renderScaleFactor || 1;
             var c = new Canvas2D();
-            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);
+            c.setupCanvas(scene, id, new BABYLON.Size(size.width * rsf, size.height * rsf), false, cs, options && options.enableInteraction || true);
+            var plane = new BABYLON.WorldSpaceCanvas2D(id, scene, c);
+            var vertexData = BABYLON.VertexData.CreatePlane({ width: size.width / 2, height: size.height / 2, sideOrientation: options && options.sideOrientation || BABYLON.Mesh.DEFAULTSIDE });
+            var mtl = new BABYLON.StandardMaterial(id + "_Material", scene);
             c.applyCachedTexture(vertexData, mtl);
             vertexData.applyToMesh(plane, false);
             mtl.specularColor = new BABYLON.Color3(0, 0, 0);
             mtl.disableLighting = true;
             mtl.useAlphaFromDiffuseTexture = true;
-            plane.position = position;
-            plane.rotationQuaternion = rotation;
+            plane.position = options && options.position || BABYLON.Vector3.Zero();
+            plane.rotationQuaternion = options && options.rotation || BABYLON.Quaternion.Identity();
             plane.material = mtl;
             c._worldSpaceNode = plane;
             return c;
@@ -110,7 +108,7 @@ var BABYLON;
             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.setupGroup2D(this, null, name, BABYLON.Vector2.Zero(), null, 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;
@@ -122,7 +120,7 @@ var BABYLON;
                 _this.dispose();
             });
             if (cachingstrategy !== Canvas2D.CACHESTRATEGY_TOPLEVELGROUPS) {
-                this._background = BABYLON.Rectangle2D.Create(this, "###CANVAS BACKGROUND###", 0, 0, size.width, size.height);
+                this._background = BABYLON.Rectangle2D.Create(this, { id: "###CANVAS BACKGROUND###", width: size.width, height: size.height });
                 this._background.isPickable = false;
                 this._background.origin = BABYLON.Vector2.Zero();
                 this._background.levelVisible = false;
@@ -222,6 +220,10 @@ var BABYLON;
             return this._capturedPointers.get(pointerId.toString());
         };
         Canvas2D.prototype._handlePointerEventForInteraction = function (eventData, eventState) {
+            // Dispose check
+            if (this.isDisposed) {
+                return;
+            }
             // Update the this._primPointerInfo structure we'll send to observers using the PointerEvent data
             this._updatePointerInfo(eventData);
             var capturedPrim = this.getCapturedPrimitive(this._primPointerInfo.pointerId);
@@ -668,6 +670,9 @@ var BABYLON;
                 throw Error("Can't use Canvas Background with the caching strategy TOPLEVELGROUPS");
             }
         };
+        Canvas2D.prototype.onPrimBecomesDirty = function () {
+            this._addPrimToDirtyList(this);
+        };
         Canvas2D.prototype._updateCanvasState = function () {
             // Check if the update has already been made for this render Frame
             if (this.scene.getRenderId() === this._updateRenderId) {
@@ -752,12 +757,12 @@ var BABYLON;
                 // Special case if the canvas is entirely cached: create a group that will have a single sprite it will be rendered specifically at the very end of the rendering process
                 if (this._cachingStrategy === Canvas2D.CACHESTRATEGY_CANVAS) {
                     this._cachedCanvasGroup = BABYLON.Group2D._createCachedCanvasGroup(this);
-                    var sprite = BABYLON.Sprite2D.Create(this._cachedCanvasGroup, "__cachedCanvasSprite__", 0, 0, map, node.contentSize, node.pos);
+                    var sprite = BABYLON.Sprite2D.Create(this._cachedCanvasGroup, map, { id: "__cachedCanvasSprite__", spriteSize: node.contentSize, spriteLocation: node.pos });
                     sprite.zOrder = 1;
                     sprite.origin = BABYLON.Vector2.Zero();
                 }
                 else {
-                    var sprite = BABYLON.Sprite2D.Create(parent, "__cachedSpriteOfGroup__" + group.id, group.position.x, group.position.y, map, node.contentSize, node.pos, false);
+                    var sprite = BABYLON.Sprite2D.Create(parent, map, { id: "__cachedSpriteOfGroup__" + group.id, x: group.position.x, y: group.position.y, spriteSize: node.contentSize, spriteLocation: node.pos });
                     sprite.origin = group.origin.clone();
                     res.sprite = sprite;
                 }

+ 46 - 33
src/Canvas2d/babylon.canvas2d.ts

@@ -48,21 +48,23 @@
         public static CACHESTRATEGY_DONTCACHE = 4;
 
         /**
-         * 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.
+         * Create a new 2D ScreenSpace Rendering Canvas, it is a 2D rectangle that has a size (width/height) and a position relative to the bottom/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.
+         * Options:
+         *  - id: a text identifier, for information purpose only
+         *  - pos: the position of the canvas, relative from the bottom/left of the scene's viewport
+         *  - 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
+         *  - cachingStrategy: either CACHESTRATEGY_TOPLEVELGROUPS, CACHESTRATEGY_ALLGROUPS, CACHESTRATEGY_CANVAS, CACHESTRATEGY_DONTCACHE. Please refer to their respective documentation for more information. Default is Canvas2D.CACHESTRATEGY_TOPLEVELGROUPS
+         *  - enableInteraction: if true the pointer events will be listened and rerouted to the appropriate primitives of the Canvas2D through the Prim2DBase.onPointerEventObservable observable property.
          */
-        static CreateScreenSpace(scene: Scene, name: string, pos: Vector2, size: Size, cachingStrategy: number = Canvas2D.CACHESTRATEGY_TOPLEVELGROUPS, enableInteraction: boolean = true): Canvas2D {
+        static CreateScreenSpace(scene: Scene, options: { id?: string, pos?: Vector2, origin?: Vector2, size?: Size, cachingStrategy?: number, enableInteraction?: boolean }): Canvas2D {
             let c = new Canvas2D();
-            c.setupCanvas(scene, name, size, true, cachingStrategy, enableInteraction);
-            c.position = pos;
-            c.origin = Vector2.Zero();
+            c.setupCanvas(scene, options && options.id || null, options && options.size || null, true, options && options.cachingStrategy || Canvas2D.CACHESTRATEGY_TOPLEVELGROUPS, options && options.enableInteraction || true);
+            c.position = options && options.pos || Vector2.Zero();
+            c.origin = options && options.origin || Vector2.Zero();
 
             return c;
         }
@@ -71,18 +73,22 @@
          * 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.
          * @param scene the Scene that owns the Canvas
-         * @param name the name of the Canvas, for information purpose only
-         * @param position the position of the Canvas in World Space
-         * @param rotation the rotation of the Canvas in World Space
          * @param size the dimension of the Canvas in World Space
-         * @param renderScaleFactor A scale factor applied to create the rendering texture that will be mapped in the Scene Rectangle. If you set 2 for instance the texture will be twice large in width and height. A greater value will allow to achieve a better rendering quality.
+         * Options:
+         *  - id: a text identifier, for information purpose only, default is null.
+         *  - position the position of the Canvas in World Space, default is [0,0,0]
+         *  - rotation the rotation of the Canvas in World Space, default is Quaternion.Identity()
+         *  - renderScaleFactor A scale factor applied to create the rendering texture that will be mapped in the Scene Rectangle. If you set 2 for instance the texture will be twice large in width and height. A greater value will allow to achieve a better rendering quality. Default value is 1.
          * BE AWARE that the Canvas true dimension will be size*renderScaleFactor, then all coordinates and size will have to be express regarding this size.
          * TIPS: if you want a renderScaleFactor independent reference of frame, create a child Group2D in the Canvas with position 0,0 and size set to null, then set its scale property to the same amount than the renderScaleFactor, put all your primitive inside using coordinates regarding the size property you pick for the Canvas and you'll be fine.
-         * @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
+         * - sideOrientation: Unexpected behavior occur if the value is different from Mesh.DEFAULTSIDE right now, so please use this one, which is the default.
+         * - cachingStrategy Must be CACHESTRATEGY_CANVAS for now, which is the default.
          */
-        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) {
+        static CreateWorldSpace(scene: Scene, size: Size, options: { id?: string, position?: Vector3, rotation?: Quaternion, renderScaleFactor?: number, sideOrientation?: number, cachingStrategy?: number, enableInteraction?: boolean}): Canvas2D {
+
+            let cs = options && options.cachingStrategy || Canvas2D.CACHESTRATEGY_CANVAS;
+
+            if (cs !== Canvas2D.CACHESTRATEGY_CANVAS) {
                 throw new Error("Right now only the CACHESTRATEGY_CANVAS cache Strategy is supported for WorldSpace Canvas. More will come soon!");
             }
 
@@ -90,16 +96,14 @@
             //    throw new Error("CACHESTRATEGY_DONTCACHE cache Strategy can't be used for WorldSpace Canvas");
             //}
 
-            if (!sideOrientation) {
-                sideOrientation = Mesh.DEFAULTSIDE;
-            }
-
+            let id = options && options.id || null;
+            let rsf = options && options.renderScaleFactor || 1;
             let c = new Canvas2D();
-            c.setupCanvas(scene, name, new Size(size.width*renderScaleFactor, size.height*renderScaleFactor), false, cachingStrategy, enableInteraction);
+            c.setupCanvas(scene, id, new Size(size.width * rsf, size.height * rsf), false, cs, options && options.enableInteraction || true);
 
-            let plane = new WorldSpaceCanvas2d(name, scene, c);
-            let vertexData = VertexData.CreatePlane({ width: size.width/2, height: size.height/2, sideOrientation: sideOrientation });
-            let mtl = new StandardMaterial(name + "_Material", scene);
+            let plane = new WorldSpaceCanvas2D(id, scene, c);
+            let vertexData = VertexData.CreatePlane({ width: size.width / 2, height: size.height / 2, sideOrientation: options && options.sideOrientation || Mesh.DEFAULTSIDE });
+            let mtl = new StandardMaterial(id + "_Material", scene);
 
             c.applyCachedTexture(vertexData, mtl);
             vertexData.applyToMesh(plane, false);
@@ -107,8 +111,8 @@
             mtl.specularColor = new Color3(0, 0, 0);
             mtl.disableLighting =true;
             mtl.useAlphaFromDiffuseTexture = true;
-            plane.position = position;
-            plane.rotationQuaternion = rotation;
+            plane.position = options && options.position || Vector3.Zero();
+            plane.rotationQuaternion = options && options.rotation || Quaternion.Identity();
             plane.material = mtl;
             c._worldSpaceNode = plane;
 
@@ -127,7 +131,7 @@
             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.setupGroup2D(this, null, name, Vector2.Zero(), null, size, this._cachingStrategy===Canvas2D.CACHESTRATEGY_ALLGROUPS ? Group2D.GROUPCACHEBEHAVIOR_DONTCACHEOVERRIDE : Group2D.GROUPCACHEBEHAVIOR_FOLLOWCACHESTRATEGY);
 
             this._hierarchyLevelMaxSiblingCount = 100;
             this._hierarchyDepthOffset = 0;
@@ -142,7 +146,7 @@
             });
 
             if (cachingstrategy !== Canvas2D.CACHESTRATEGY_TOPLEVELGROUPS) {
-                this._background = Rectangle2D.Create(this, "###CANVAS BACKGROUND###", 0, 0, size.width, size.height);
+                this._background = Rectangle2D.Create(this, { id: "###CANVAS BACKGROUND###", width: size.width, height: size.height});
                 this._background.isPickable = false;
                 this._background.origin = Vector2.Zero();
                 this._background.levelVisible = false;
@@ -257,6 +261,11 @@
            
         private static _interInfo = new IntersectInfo2D();
         private _handlePointerEventForInteraction(eventData: PointerInfoPre, eventState: EventState) {
+            // Dispose check
+            if (this.isDisposed) {
+                return;
+            }
+
             // Update the this._primPointerInfo structure we'll send to observers using the PointerEvent data
             this._updatePointerInfo(eventData);
 
@@ -640,7 +649,7 @@
         /**
          * Only valid for World Space Canvas, returns the scene node that display the canvas
          */
-        public get worldSpaceCanvasNode(): WorldSpaceCanvas2d {
+        public get worldSpaceCanvasNode(): WorldSpaceCanvas2D {
             return this._worldSpaceNode;
         }
 
@@ -757,7 +766,7 @@
         private _actualOverPrimitive: PrimitiveIntersectedInfo;
         private _capturedPointers: StringDictionary<Prim2DBase>;
         private _scenePrePointerObserver: Observer<PointerInfoPre>;
-        private _worldSpaceNode: WorldSpaceCanvas2d;
+        private _worldSpaceNode: WorldSpaceCanvas2D;
         private _mapCounter = 0;
         private _background: Rectangle2D;
         private _scene: Scene;
@@ -774,6 +783,10 @@
 
         public _renderingSize: Size;
 
+        protected onPrimBecomesDirty() {
+            this._addPrimToDirtyList(this);
+        }
+
         private _updateCanvasState() {
             // Check if the update has already been made for this render Frame
             if (this.scene.getRenderId() === this._updateRenderId) {
@@ -877,14 +890,14 @@
                 // Special case if the canvas is entirely cached: create a group that will have a single sprite it will be rendered specifically at the very end of the rendering process
                 if (this._cachingStrategy === Canvas2D.CACHESTRATEGY_CANVAS) {
                     this._cachedCanvasGroup = Group2D._createCachedCanvasGroup(this);
-                    let sprite = Sprite2D.Create(this._cachedCanvasGroup, "__cachedCanvasSprite__", 0, 0, map, node.contentSize, node.pos);
+                    let sprite = Sprite2D.Create(this._cachedCanvasGroup, map, {id: "__cachedCanvasSprite__", spriteSize:node.contentSize, spriteLocation:node.pos});
                     sprite.zOrder = 1;
                     sprite.origin = Vector2.Zero();
                 }
 
                 // 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);
+                    let sprite = Sprite2D.Create(parent, map, {id:`__cachedSpriteOfGroup__${group.id}`, x: group.position.x, y: group.position.y, spriteSize:node.contentSize, spriteLocation:node.pos});
                     sprite.origin = group.origin.clone();
                     res.sprite = sprite;
                 }

+ 306 - 0
src/Canvas2d/babylon.ellipse2d.js

@@ -0,0 +1,306 @@
+var __extends = (this && this.__extends) || function (d, b) {
+    for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];
+    function __() { this.constructor = d; }
+    d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
+};
+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 Ellipse2DRenderCache = (function (_super) {
+        __extends(Ellipse2DRenderCache, _super);
+        function Ellipse2DRenderCache(engine, modelKey, isTransparent) {
+            _super.call(this, engine, modelKey, isTransparent);
+        }
+        Ellipse2DRenderCache.prototype.render = function (instanceInfo, context) {
+            // Do nothing if the shader is still loading/preparing 
+            if ((this.effectFill && !this.effectFill.isReady()) || (this.effectBorder && !this.effectBorder.isReady())) {
+                return false;
+            }
+            var engine = instanceInfo._owner.owner.engine;
+            var depthFunction = 0;
+            if (this.effectFill && this.effectBorder) {
+                depthFunction = engine.getDepthFunction();
+                engine.setDepthFunctionToLessOrEqual();
+            }
+            var cur;
+            if (this.isTransparent) {
+                cur = engine.getAlphaMode();
+                engine.setAlphaMode(BABYLON.Engine.ALPHA_COMBINE);
+            }
+            if (this.effectFill) {
+                var partIndex = instanceInfo._partIndexFromId.get(BABYLON.Shape2D.SHAPE2D_FILLPARTID.toString());
+                engine.enableEffect(this.effectFill);
+                engine.bindBuffers(this.fillVB, this.fillIB, [1], 4, this.effectFill);
+                var count = instanceInfo._instancesPartsData[partIndex].usedElementCount;
+                if (instanceInfo._owner.owner.supportInstancedArray) {
+                    if (!this.instancingFillAttributes) {
+                        // Compute the offset locations of the attributes in the vertex shader that will be mapped to the instance buffer data
+                        this.instancingFillAttributes = this.loadInstancingAttributes(BABYLON.Shape2D.SHAPE2D_FILLPARTID, this.effectFill);
+                    }
+                    engine.updateAndBindInstancesBuffer(instanceInfo._instancesPartsBuffer[partIndex], null, this.instancingFillAttributes);
+                    engine.draw(true, 0, this.fillIndicesCount, count);
+                    engine.unBindInstancesBuffer(instanceInfo._instancesPartsBuffer[partIndex], this.instancingFillAttributes);
+                }
+                else {
+                    for (var i = 0; i < count; i++) {
+                        this.setupUniforms(this.effectFill, partIndex, instanceInfo._instancesPartsData[partIndex], i);
+                        engine.draw(true, 0, this.fillIndicesCount);
+                    }
+                }
+            }
+            if (this.effectBorder) {
+                var partIndex = instanceInfo._partIndexFromId.get(BABYLON.Shape2D.SHAPE2D_BORDERPARTID.toString());
+                engine.enableEffect(this.effectBorder);
+                engine.bindBuffers(this.borderVB, this.borderIB, [1], 4, this.effectBorder);
+                var count = instanceInfo._instancesPartsData[partIndex].usedElementCount;
+                if (instanceInfo._owner.owner.supportInstancedArray) {
+                    if (!this.instancingBorderAttributes) {
+                        this.instancingBorderAttributes = this.loadInstancingAttributes(BABYLON.Shape2D.SHAPE2D_BORDERPARTID, this.effectBorder);
+                    }
+                    engine.updateAndBindInstancesBuffer(instanceInfo._instancesPartsBuffer[partIndex], null, this.instancingBorderAttributes);
+                    engine.draw(true, 0, this.borderIndicesCount, count);
+                    engine.unBindInstancesBuffer(instanceInfo._instancesPartsBuffer[partIndex], this.instancingBorderAttributes);
+                }
+                else {
+                    for (var i = 0; i < count; i++) {
+                        this.setupUniforms(this.effectBorder, partIndex, instanceInfo._instancesPartsData[partIndex], i);
+                        engine.draw(true, 0, this.borderIndicesCount);
+                    }
+                }
+            }
+            if (this.isTransparent) {
+                engine.setAlphaMode(cur);
+            }
+            if (this.effectFill && this.effectBorder) {
+                engine.setDepthFunction(depthFunction);
+            }
+            return true;
+        };
+        Ellipse2DRenderCache.prototype.dispose = function () {
+            if (!_super.prototype.dispose.call(this)) {
+                return false;
+            }
+            if (this.fillVB) {
+                this._engine._releaseBuffer(this.fillVB);
+                this.fillVB = null;
+            }
+            if (this.fillIB) {
+                this._engine._releaseBuffer(this.fillIB);
+                this.fillIB = null;
+            }
+            if (this.effectFill) {
+                this._engine._releaseEffect(this.effectFill);
+                this.effectFill = null;
+            }
+            if (this.borderVB) {
+                this._engine._releaseBuffer(this.borderVB);
+                this.borderVB = null;
+            }
+            if (this.borderIB) {
+                this._engine._releaseBuffer(this.borderIB);
+                this.borderIB = null;
+            }
+            if (this.effectBorder) {
+                this._engine._releaseEffect(this.effectBorder);
+                this.effectBorder = null;
+            }
+            return true;
+        };
+        return Ellipse2DRenderCache;
+    })(BABYLON.ModelRenderCache);
+    BABYLON.Ellipse2DRenderCache = Ellipse2DRenderCache;
+    var Ellipse2DInstanceData = (function (_super) {
+        __extends(Ellipse2DInstanceData, _super);
+        function Ellipse2DInstanceData(partId) {
+            _super.call(this, partId, 1);
+        }
+        Object.defineProperty(Ellipse2DInstanceData.prototype, "properties", {
+            get: function () {
+                return null;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        __decorate([
+            BABYLON.instanceData()
+        ], Ellipse2DInstanceData.prototype, "properties", null);
+        return Ellipse2DInstanceData;
+    })(BABYLON.Shape2DInstanceData);
+    BABYLON.Ellipse2DInstanceData = Ellipse2DInstanceData;
+    var Ellipse2D = (function (_super) {
+        __extends(Ellipse2D, _super);
+        function Ellipse2D() {
+            _super.apply(this, arguments);
+        }
+        Object.defineProperty(Ellipse2D.prototype, "actualSize", {
+            get: function () {
+                return this.size;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(Ellipse2D.prototype, "size", {
+            get: function () {
+                return this._size;
+            },
+            set: function (value) {
+                this._size = value;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(Ellipse2D.prototype, "subdivisions", {
+            get: function () {
+                return this._subdivisions;
+            },
+            set: function (value) {
+                this._subdivisions = value;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Ellipse2D.prototype.levelIntersect = function (intersectInfo) {
+            var x = intersectInfo._localPickPosition.x;
+            var y = intersectInfo._localPickPosition.y;
+            var w = this.size.width / 2;
+            var h = this.size.height / 2;
+            return ((x * x) / (w * w) + (y * y) / (h * h)) <= 1;
+        };
+        Ellipse2D.prototype.updateLevelBoundingInfo = function () {
+            BABYLON.BoundingInfo2D.CreateFromSizeToRef(this.size, this._levelBoundingInfo, this.origin);
+        };
+        Ellipse2D.prototype.setupEllipse2D = function (owner, parent, id, position, origin, size, subdivisions, fill, border, borderThickness) {
+            if (subdivisions === void 0) { subdivisions = 64; }
+            if (borderThickness === void 0) { borderThickness = 1; }
+            this.setupShape2D(owner, parent, id, position, origin, true, fill, border, borderThickness);
+            this.size = size;
+            this.subdivisions = subdivisions;
+        };
+        /**
+         * Create an Ellipse 2D Shape primitive
+         * @param parent the parent primitive, must be a valid primitive (or the Canvas)
+         * options:
+         *  - id: a text identifier, for information purpose
+         *  - x: the X position relative to its parent, default is 0
+         *  - y: the Y position relative to its parent, default is 0
+         *  - origin: define the normalized origin point location, default [0.5;0.5]
+         *  - width: the width of the ellipse, default is 10
+         *  - height: the height of the ellipse, default is 10
+         *  - subdivision: the number of subdivision to create the ellipse perimeter, default is 64.
+         *  - fill: the brush used to draw the fill content of the ellipse, you can set null to draw nothing (but you will have to set a border brush), default is a SolidColorBrush of plain white.
+         *  - border: the brush used to draw the border of the ellipse, you can set null to draw nothing (but you will have to set a fill brush), default is null.
+         *  - borderThickness: the thickness of the drawn border, default is 1.
+         */
+        Ellipse2D.Create = function (parent, options) {
+            BABYLON.Prim2DBase.CheckParent(parent);
+            var fill;
+            if (options && options.fill !== undefined) {
+                fill = options.fill;
+            }
+            else {
+                fill = BABYLON.Canvas2D.GetSolidColorBrushFromHex("#FFFFFFFF");
+            }
+            var ellipse = new Ellipse2D();
+            ellipse.setupEllipse2D(parent.owner, parent, options && options.id || null, new BABYLON.Vector2(options && options.x || 0, options && options.y || 0), options && options.origin || null, new BABYLON.Size(options && options.width || 10, options && options.height || 10), options && options.subdivisions || 64, fill, options && options.border || null, options && options.borderThickness || 1);
+            return ellipse;
+        };
+        Ellipse2D.prototype.createModelRenderCache = function (modelKey, isTransparent) {
+            var renderCache = new Ellipse2DRenderCache(this.owner.engine, modelKey, isTransparent);
+            return renderCache;
+        };
+        Ellipse2D.prototype.setupModelRenderCache = function (modelRenderCache) {
+            var renderCache = modelRenderCache;
+            var engine = this.owner.engine;
+            // Need to create WebGL resources for fill part?
+            if (this.fill) {
+                var vbSize = this.subdivisions + 1;
+                var vb = new Float32Array(vbSize);
+                for (var i = 0; i < vbSize; i++) {
+                    vb[i] = i;
+                }
+                renderCache.fillVB = engine.createVertexBuffer(vb);
+                var triCount = vbSize - 1;
+                var ib = new Float32Array(triCount * 3);
+                for (var i = 0; i < triCount; i++) {
+                    ib[i * 3 + 0] = 0;
+                    ib[i * 3 + 2] = i + 1;
+                    ib[i * 3 + 1] = i + 2;
+                }
+                ib[triCount * 3 - 2] = 1;
+                renderCache.fillIB = engine.createIndexBuffer(ib);
+                renderCache.fillIndicesCount = triCount * 3;
+                var ei = this.getDataPartEffectInfo(BABYLON.Shape2D.SHAPE2D_FILLPARTID, ["index"]);
+                renderCache.effectFill = engine.createEffect({ vertex: "ellipse2d", fragment: "ellipse2d" }, ei.attributes, ei.uniforms, [], ei.defines, null);
+            }
+            // Need to create WebGL resource for border part?
+            if (this.border) {
+                var vbSize = this.subdivisions * 2;
+                var vb = new Float32Array(vbSize);
+                for (var i = 0; i < vbSize; i++) {
+                    vb[i] = i;
+                }
+                renderCache.borderVB = engine.createVertexBuffer(vb);
+                var triCount = vbSize;
+                var rs = triCount / 2;
+                var ib = new Float32Array(triCount * 3);
+                for (var i = 0; i < rs; i++) {
+                    var r0 = i;
+                    var r1 = (i + 1) % rs;
+                    ib[i * 6 + 0] = rs + r1;
+                    ib[i * 6 + 1] = rs + r0;
+                    ib[i * 6 + 2] = r0;
+                    ib[i * 6 + 3] = r1;
+                    ib[i * 6 + 4] = rs + r1;
+                    ib[i * 6 + 5] = r0;
+                }
+                renderCache.borderIB = engine.createIndexBuffer(ib);
+                renderCache.borderIndicesCount = (triCount * 3);
+                var ei = this.getDataPartEffectInfo(BABYLON.Shape2D.SHAPE2D_BORDERPARTID, ["index"]);
+                renderCache.effectBorder = engine.createEffect({ vertex: "ellipse2d", fragment: "ellipse2d" }, ei.attributes, ei.uniforms, [], ei.defines, null);
+            }
+            return renderCache;
+        };
+        Ellipse2D.prototype.createInstanceDataParts = function () {
+            var res = new Array();
+            if (this.border) {
+                res.push(new Ellipse2DInstanceData(BABYLON.Shape2D.SHAPE2D_BORDERPARTID));
+            }
+            if (this.fill) {
+                res.push(new Ellipse2DInstanceData(BABYLON.Shape2D.SHAPE2D_FILLPARTID));
+            }
+            return res;
+        };
+        Ellipse2D.prototype.refreshInstanceDataPart = function (part) {
+            if (!_super.prototype.refreshInstanceDataPart.call(this, part)) {
+                return false;
+            }
+            if (part.id === BABYLON.Shape2D.SHAPE2D_BORDERPARTID) {
+                var d = part;
+                var size = this.size;
+                d.properties = new BABYLON.Vector3(size.width, size.height, this.subdivisions);
+            }
+            else if (part.id === BABYLON.Shape2D.SHAPE2D_FILLPARTID) {
+                var d = part;
+                var size = this.size;
+                d.properties = new BABYLON.Vector3(size.width, size.height, this.subdivisions);
+            }
+            return true;
+        };
+        __decorate([
+            BABYLON.instanceLevelProperty(BABYLON.Shape2D.SHAPE2D_PROPCOUNT + 1, function (pi) { return Ellipse2D.sizeProperty = pi; }, false, true)
+        ], Ellipse2D.prototype, "size", null);
+        __decorate([
+            BABYLON.modelLevelProperty(BABYLON.Shape2D.SHAPE2D_PROPCOUNT + 2, function (pi) { return Ellipse2D.subdivisionsProperty = pi; })
+        ], Ellipse2D.prototype, "subdivisions", null);
+        Ellipse2D = __decorate([
+            BABYLON.className("Ellipse2D")
+        ], Ellipse2D);
+        return Ellipse2D;
+    })(BABYLON.Shape2D);
+    BABYLON.Ellipse2D = Ellipse2D;
+})(BABYLON || (BABYLON = {}));

+ 321 - 0
src/Canvas2d/babylon.ellipse2d.ts

@@ -0,0 +1,321 @@
+module BABYLON {
+    export class Ellipse2DRenderCache extends ModelRenderCache {
+        fillVB: WebGLBuffer;
+        fillIB: WebGLBuffer;
+        fillIndicesCount: number;
+        instancingFillAttributes: InstancingAttributeInfo[];
+        effectFill: Effect;
+
+        borderVB: WebGLBuffer;
+        borderIB: WebGLBuffer;
+        borderIndicesCount: number;
+        instancingBorderAttributes: InstancingAttributeInfo[];
+        effectBorder: Effect;
+
+        constructor(engine: Engine, modelKey: string, isTransparent: boolean) {
+            super(engine, modelKey, isTransparent);
+        }
+
+        render(instanceInfo: GroupInstanceInfo, context: Render2DContext): boolean {
+            // Do nothing if the shader is still loading/preparing 
+            if ((this.effectFill && !this.effectFill.isReady()) || (this.effectBorder && !this.effectBorder.isReady())) {
+                return false;
+            }
+
+            var engine = instanceInfo._owner.owner.engine;
+
+            let depthFunction = 0;
+            if (this.effectFill && this.effectBorder) {
+                depthFunction = engine.getDepthFunction();
+                engine.setDepthFunctionToLessOrEqual();
+            }
+
+            var cur: number;
+            if (this.isTransparent) {
+                cur = engine.getAlphaMode();
+                engine.setAlphaMode(Engine.ALPHA_COMBINE);
+            }
+
+            if (this.effectFill) {
+                let partIndex = instanceInfo._partIndexFromId.get(Shape2D.SHAPE2D_FILLPARTID.toString());
+
+                engine.enableEffect(this.effectFill);
+                engine.bindBuffers(this.fillVB, this.fillIB, [1], 4, this.effectFill);
+                let count = instanceInfo._instancesPartsData[partIndex].usedElementCount;
+                if (instanceInfo._owner.owner.supportInstancedArray) {
+                    if (!this.instancingFillAttributes) {
+                        // Compute the offset locations of the attributes in the vertex shader that will be mapped to the instance buffer data
+                        this.instancingFillAttributes = this.loadInstancingAttributes(Shape2D.SHAPE2D_FILLPARTID, this.effectFill);
+                    }
+
+                    engine.updateAndBindInstancesBuffer(instanceInfo._instancesPartsBuffer[partIndex], null, this.instancingFillAttributes);
+                    engine.draw(true, 0, this.fillIndicesCount, count);
+                    engine.unBindInstancesBuffer(instanceInfo._instancesPartsBuffer[partIndex], this.instancingFillAttributes);
+                } else {
+                    for (let i = 0; i < count; i++) {
+                        this.setupUniforms(this.effectFill, partIndex, instanceInfo._instancesPartsData[partIndex], i);
+                        engine.draw(true, 0, this.fillIndicesCount);                        
+                    }
+                }
+            }
+
+            if (this.effectBorder) {
+                let partIndex = instanceInfo._partIndexFromId.get(Shape2D.SHAPE2D_BORDERPARTID.toString());
+
+                engine.enableEffect(this.effectBorder);
+                engine.bindBuffers(this.borderVB, this.borderIB, [1], 4, this.effectBorder);
+                let count = instanceInfo._instancesPartsData[partIndex].usedElementCount;
+                if (instanceInfo._owner.owner.supportInstancedArray) {
+                    if (!this.instancingBorderAttributes) {
+                        this.instancingBorderAttributes = this.loadInstancingAttributes(Shape2D.SHAPE2D_BORDERPARTID, this.effectBorder);
+                    }
+
+                    engine.updateAndBindInstancesBuffer(instanceInfo._instancesPartsBuffer[partIndex], null, this.instancingBorderAttributes);
+                    engine.draw(true, 0, this.borderIndicesCount, count);
+                    engine.unBindInstancesBuffer(instanceInfo._instancesPartsBuffer[partIndex], this.instancingBorderAttributes);
+                } else {
+                    for (let i = 0; i < count; i++) {
+                        this.setupUniforms(this.effectBorder, partIndex, instanceInfo._instancesPartsData[partIndex], i);
+                        engine.draw(true, 0, this.borderIndicesCount);
+                    }
+                }
+            }
+
+            if (this.isTransparent) {
+                engine.setAlphaMode(cur);
+            }
+
+            if (this.effectFill && this.effectBorder) {
+                engine.setDepthFunction(depthFunction);
+            }
+            return true;
+        }
+
+        public dispose(): boolean {
+            if (!super.dispose()) {
+                return false;
+            }
+
+            if (this.fillVB) {
+                this._engine._releaseBuffer(this.fillVB);
+                this.fillVB = null;
+            }
+
+            if (this.fillIB) {
+                this._engine._releaseBuffer(this.fillIB);
+                this.fillIB = null;
+            }
+
+            if (this.effectFill) {
+                this._engine._releaseEffect(this.effectFill);
+                this.effectFill = null;
+            }
+
+            if (this.borderVB) {
+                this._engine._releaseBuffer(this.borderVB);
+                this.borderVB = null;
+            }
+
+            if (this.borderIB) {
+                this._engine._releaseBuffer(this.borderIB);
+                this.borderIB = null;
+            }
+
+            if (this.effectBorder) {
+                this._engine._releaseEffect(this.effectBorder);
+                this.effectBorder = null;
+            }
+
+            return true;
+        }
+    }
+
+    export class Ellipse2DInstanceData extends Shape2DInstanceData {
+        constructor(partId: number) {
+            super(partId, 1);
+        }
+
+        @instanceData()
+        get properties(): Vector3 {
+            return null;
+        }
+    }
+
+    @className("Ellipse2D")
+    export class Ellipse2D extends Shape2D {
+
+        public static sizeProperty: Prim2DPropInfo;
+        public static subdivisionsProperty: Prim2DPropInfo;
+
+        public get actualSize(): Size {
+            return this.size;
+        }
+
+        @instanceLevelProperty(Shape2D.SHAPE2D_PROPCOUNT + 1, pi => Ellipse2D.sizeProperty = pi, false, true)
+        public get size(): Size {
+            return this._size;
+        }
+
+        public set size(value: Size) {
+            this._size = value;
+        }
+
+        @modelLevelProperty(Shape2D.SHAPE2D_PROPCOUNT + 2, pi => Ellipse2D.subdivisionsProperty = pi)
+        public get subdivisions(): number {
+            return this._subdivisions;
+        }
+
+        public set subdivisions(value: number) {
+            this._subdivisions = value;
+        }
+
+        protected levelIntersect(intersectInfo: IntersectInfo2D): boolean {
+            let x = intersectInfo._localPickPosition.x;
+            let y = intersectInfo._localPickPosition.y;
+            let w = this.size.width/2;
+            let h = this.size.height/2;
+            return ((x * x) / (w * w) + (y * y) / (h * h)) <= 1;
+        }
+
+        protected updateLevelBoundingInfo() {
+            BoundingInfo2D.CreateFromSizeToRef(this.size, this._levelBoundingInfo, this.origin);
+        }
+
+        protected setupEllipse2D(owner: Canvas2D, parent: Prim2DBase, id: string, position: Vector2, origin: Vector2, size: Size, subdivisions: number=64, fill?: IBrush2D, border?: IBrush2D, borderThickness: number = 1) {
+            this.setupShape2D(owner, parent, id, position, origin, true, fill, border, borderThickness);
+            this.size = size;
+            this.subdivisions = subdivisions;
+        }
+
+        /**
+         * Create an Ellipse 2D Shape primitive
+         * @param parent the parent primitive, must be a valid primitive (or the Canvas)
+         * options:
+         *  - id: a text identifier, for information purpose
+         *  - x: the X position relative to its parent, default is 0
+         *  - y: the Y position relative to its parent, default is 0
+         *  - origin: define the normalized origin point location, default [0.5;0.5]
+         *  - width: the width of the ellipse, default is 10
+         *  - height: the height of the ellipse, default is 10
+         *  - subdivision: the number of subdivision to create the ellipse perimeter, default is 64.
+         *  - fill: the brush used to draw the fill content of the ellipse, you can set null to draw nothing (but you will have to set a border brush), default is a SolidColorBrush of plain white.
+         *  - border: the brush used to draw the border of the ellipse, you can set null to draw nothing (but you will have to set a fill brush), default is null.
+         *  - borderThickness: the thickness of the drawn border, default is 1.
+         */
+        public static Create(parent: Prim2DBase, options: { id?: string, x?: number, y?: number, origin?: Vector2, width?: number, height?: number, subdivisions?: number, fill?: IBrush2D, border?: IBrush2D, borderThickness?: number }): Ellipse2D {
+            Prim2DBase.CheckParent(parent);
+
+            let fill: IBrush2D;
+            if (options && options.fill !== undefined) {
+                fill = options.fill;
+            } else {
+                fill = Canvas2D.GetSolidColorBrushFromHex("#FFFFFFFF");
+            }
+
+            let ellipse = new Ellipse2D();
+            ellipse.setupEllipse2D(parent.owner, parent, options && options.id || null, new Vector2(options && options.x || 0, options && options.y || 0), options && options.origin || null, new Size(options && options.width || 10, options && options.height || 10), options && options.subdivisions || 64, fill, options && options.border || null, options && options.borderThickness || 1);
+            return ellipse;
+        }
+
+        protected createModelRenderCache(modelKey: string, isTransparent: boolean): ModelRenderCache {
+            let renderCache = new Ellipse2DRenderCache(this.owner.engine, modelKey, isTransparent);
+            return renderCache;
+        }
+
+        protected setupModelRenderCache(modelRenderCache: ModelRenderCache) {
+            let renderCache = <Ellipse2DRenderCache>modelRenderCache;
+            let engine = this.owner.engine;
+
+            // Need to create WebGL resources for fill part?
+            if (this.fill) {
+                let vbSize = this.subdivisions + 1;
+                let vb = new Float32Array(vbSize);
+                for (let i = 0; i < vbSize; i++) {
+                    vb[i] = i;
+                }
+                renderCache.fillVB = engine.createVertexBuffer(vb);
+
+                let triCount = vbSize - 1;
+                let ib = new Float32Array(triCount * 3);
+                for (let i = 0; i < triCount; i++) {
+                    ib[i * 3 + 0] = 0;
+                    ib[i * 3 + 2] = i + 1;
+                    ib[i * 3 + 1] = i + 2;
+                }
+                ib[triCount * 3 - 2] = 1;
+
+                renderCache.fillIB = engine.createIndexBuffer(ib);
+                renderCache.fillIndicesCount = triCount * 3;
+
+                let ei = this.getDataPartEffectInfo(Shape2D.SHAPE2D_FILLPARTID, ["index"]);
+                renderCache.effectFill = engine.createEffect({ vertex: "ellipse2d", fragment: "ellipse2d" }, ei.attributes, ei.uniforms, [], ei.defines, null);
+            }
+
+            // Need to create WebGL resource for border part?
+            if (this.border) {
+                let vbSize = this.subdivisions * 2;
+                let vb = new Float32Array(vbSize);
+                for (let i = 0; i < vbSize; i++) {
+                    vb[i] = i;
+                }
+                renderCache.borderVB = engine.createVertexBuffer(vb);
+
+                let triCount = vbSize;
+                let rs = triCount / 2;
+                let ib = new Float32Array(triCount * 3);
+                for (let i = 0; i < rs; i++) {
+                    let r0 = i;
+                    let r1 = (i + 1) % rs;
+
+                    ib[i * 6 + 0] = rs + r1;
+                    ib[i * 6 + 1] = rs + r0;
+                    ib[i * 6 + 2] = r0;
+
+                    ib[i * 6 + 3] = r1;
+                    ib[i * 6 + 4] = rs + r1;
+                    ib[i * 6 + 5] = r0;
+                }
+
+                renderCache.borderIB = engine.createIndexBuffer(ib);
+                renderCache.borderIndicesCount = (triCount* 3);
+
+                let ei = this.getDataPartEffectInfo(Shape2D.SHAPE2D_BORDERPARTID, ["index"]);
+                renderCache.effectBorder = engine.createEffect({ vertex: "ellipse2d", fragment: "ellipse2d" }, ei.attributes, ei.uniforms, [], ei.defines, null);
+            }
+
+            return renderCache;
+        }
+
+
+        protected createInstanceDataParts(): InstanceDataBase[] {
+            var res = new Array<InstanceDataBase>();
+            if (this.border) {
+                res.push(new Ellipse2DInstanceData(Shape2D.SHAPE2D_BORDERPARTID));
+            }
+            if (this.fill) {
+                res.push(new Ellipse2DInstanceData(Shape2D.SHAPE2D_FILLPARTID));
+            }
+            return res;
+        }
+
+        protected refreshInstanceDataPart(part: InstanceDataBase): boolean {
+            if (!super.refreshInstanceDataPart(part)) {
+                return false;
+            }
+            if (part.id === Shape2D.SHAPE2D_BORDERPARTID) {
+                let d = <Ellipse2DInstanceData>part;
+                let size = this.size;
+                d.properties = new Vector3(size.width, size.height, this.subdivisions);
+            }
+            else if (part.id === Shape2D.SHAPE2D_FILLPARTID) {
+                let d = <Ellipse2DInstanceData>part;
+                let size = this.size;
+                d.properties = new Vector3(size.width, size.height, this.subdivisions);
+            }
+            return true;
+        }
+
+        private _size: Size;
+        private _subdivisions: number;
+    }
+}

+ 17 - 7
src/Canvas2d/babylon.group2d.js

@@ -22,16 +22,25 @@ var BABYLON;
             this._childrenRenderableGroups = new Array();
             this._renderGroupInstancesInfo = new BABYLON.StringDictionary();
         }
-        Group2D.CreateGroup2D = function (parent, id, position, size, cacheBehabior) {
-            if (cacheBehabior === void 0) { cacheBehabior = Group2D.GROUPCACHEBEHAVIOR_FOLLOWCACHESTRATEGY; }
+        /**
+         * Create an Logical or Renderable Group.
+         * @param parent the parent primitive, must be a valid primitive (or the Canvas)
+         * options:
+         *  - id a text identifier, for information purpose
+         *  - position: the X & Y positions relative to its parent, default is [0;0]
+         *  - origin: define the normalized origin point location, default [0.5;0.5]
+         *  - size: the size of the group, if null the size will be computed from its content, default is null.
+         *  - cacheBehavior: Define how the group should behave regarding the Canvas's cache strategy, default is Group2D.GROUPCACHEBEHAVIOR_FOLLOWCACHESTRATEGY
+         */
+        Group2D.CreateGroup2D = function (parent, options) {
             BABYLON.Prim2DBase.CheckParent(parent);
             var g = new Group2D();
-            g.setupGroup2D(parent.owner, parent, id, position, size, cacheBehabior);
+            g.setupGroup2D(parent.owner, parent, options && options.id || null, options && options.position || BABYLON.Vector2.Zero(), options && options.origin || null, options && options.size || null, options && options.cacheBehavior || Group2D.GROUPCACHEBEHAVIOR_FOLLOWCACHESTRATEGY);
             return g;
         };
         Group2D._createCachedCanvasGroup = function (owner) {
             var g = new Group2D();
-            g.setupGroup2D(owner, null, "__cachedCanvasGroup__", BABYLON.Vector2.Zero());
+            g.setupGroup2D(owner, null, "__cachedCanvasGroup__", BABYLON.Vector2.Zero(), null);
             g.origin = BABYLON.Vector2.Zero();
             return g;
         };
@@ -76,10 +85,10 @@ var BABYLON;
             }
             return true;
         };
-        Group2D.prototype.setupGroup2D = function (owner, parent, id, position, size, cacheBehavior) {
+        Group2D.prototype.setupGroup2D = function (owner, parent, id, position, origin, size, cacheBehavior) {
             if (cacheBehavior === void 0) { cacheBehavior = Group2D.GROUPCACHEBEHAVIOR_FOLLOWCACHESTRATEGY; }
             this._cacheBehavior = cacheBehavior;
-            this.setupPrim2DBase(owner, parent, id, position);
+            this.setupPrim2DBase(owner, parent, id, position, origin);
             this.size = size;
             this._viewportPosition = BABYLON.Vector2.Zero();
         };
@@ -247,6 +256,7 @@ var BABYLON;
                         }
                     });
                     // Everything is updated, clear the dirty list
+                    this._primDirtyList.forEach(function (p) { return p._resetPropertiesDirty(); });
                     this._primDirtyList.splice(0);
                 }
             }
@@ -300,9 +310,9 @@ var BABYLON;
                                 // Update the WebGL buffer to match the new content of the instances data
                                 engine._gl.bindBuffer(engine._gl.ARRAY_BUFFER, v._instancesPartsBuffer[i]);
                                 engine._gl.bufferSubData(engine._gl.ARRAY_BUFFER, 0, instanceData_1);
-                                v._dirtyInstancesData = false;
                             }
                         }
+                        v._dirtyInstancesData = false;
                     }
                     // Submit render only if we have something to render (everything may be hidden and the floatarray empty)
                     if (!_this.owner.supportInstancedArray || totalRenderCount > 0) {

+ 17 - 6
src/Canvas2d/babylon.group2d.ts

@@ -32,17 +32,27 @@
             this._renderGroupInstancesInfo = new StringDictionary<GroupInstanceInfo>();
         }
 
-        static CreateGroup2D(parent: Prim2DBase, id: string, position: Vector2, size?: Size, cacheBehabior: number = Group2D.GROUPCACHEBEHAVIOR_FOLLOWCACHESTRATEGY): Group2D {
+        /**
+         * Create an Logical or Renderable Group.
+         * @param parent the parent primitive, must be a valid primitive (or the Canvas)
+         * options:
+         *  - id a text identifier, for information purpose
+         *  - position: the X & Y positions relative to its parent, default is [0;0]
+         *  - origin: define the normalized origin point location, default [0.5;0.5]
+         *  - size: the size of the group, if null the size will be computed from its content, default is null.
+         *  - cacheBehavior: Define how the group should behave regarding the Canvas's cache strategy, default is Group2D.GROUPCACHEBEHAVIOR_FOLLOWCACHESTRATEGY
+         */
+        static CreateGroup2D(parent: Prim2DBase, options: { id?: string, position?: Vector2; origin?: Vector2, size?: Size, cacheBehavior?: number}): Group2D {
             Prim2DBase.CheckParent(parent);
             var g = new Group2D();
-            g.setupGroup2D(parent.owner, parent, id, position, size, cacheBehabior);
+            g.setupGroup2D(parent.owner, parent, options && options.id || null, options && options.position || Vector2.Zero(), options && options.origin || null, options && options.size || null, options && options.cacheBehavior || Group2D.GROUPCACHEBEHAVIOR_FOLLOWCACHESTRATEGY);
 
             return g;
         }
 
         static _createCachedCanvasGroup(owner: Canvas2D): Group2D {
             var g = new Group2D();
-            g.setupGroup2D(owner, null, "__cachedCanvasGroup__", Vector2.Zero());
+            g.setupGroup2D(owner, null, "__cachedCanvasGroup__", Vector2.Zero(), null);
             g.origin = Vector2.Zero();
             return g;
             
@@ -98,9 +108,9 @@
             return true;
         }
 
-        protected setupGroup2D(owner: Canvas2D, parent: Prim2DBase, id: string, position: Vector2, size?: Size, cacheBehavior: number = Group2D.GROUPCACHEBEHAVIOR_FOLLOWCACHESTRATEGY) {
+        protected setupGroup2D(owner: Canvas2D, parent: Prim2DBase, id: string, position: Vector2, origin: Vector2, size?: Size, cacheBehavior: number = Group2D.GROUPCACHEBEHAVIOR_FOLLOWCACHESTRATEGY) {
             this._cacheBehavior = cacheBehavior;
-            this.setupPrim2DBase(owner, parent, id, position);
+            this.setupPrim2DBase(owner, parent, id, position, origin);
             this.size = size;
             this._viewportPosition = Vector2.Zero();
         }
@@ -278,6 +288,7 @@
                     });
 
                     // Everything is updated, clear the dirty list
+                    this._primDirtyList.forEach(p => p._resetPropertiesDirty());
                     this._primDirtyList.splice(0);
                 }
             }
@@ -337,9 +348,9 @@
                                 engine._gl.bindBuffer(engine._gl.ARRAY_BUFFER, v._instancesPartsBuffer[i]);
                                 engine._gl.bufferSubData(engine._gl.ARRAY_BUFFER, 0, instanceData);
 
-                                v._dirtyInstancesData = false;
                             }
                         }
+                        v._dirtyInstancesData = false;
                     }
 
                     // Submit render only if we have something to render (everything may be hidden and the floatarray empty)

+ 991 - 0
src/Canvas2d/babylon.lines2d.js

@@ -0,0 +1,991 @@
+var __extends = (this && this.__extends) || function (d, b) {
+    for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];
+    function __() { this.constructor = d; }
+    d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
+};
+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 Lines2DRenderCache = (function (_super) {
+        __extends(Lines2DRenderCache, _super);
+        function Lines2DRenderCache(engine, modelKey, isTransparent) {
+            _super.call(this, engine, modelKey, isTransparent);
+        }
+        Lines2DRenderCache.prototype.render = function (instanceInfo, context) {
+            // Do nothing if the shader is still loading/preparing 
+            if ((this.effectFill && !this.effectFill.isReady()) || (this.effectBorder && !this.effectBorder.isReady())) {
+                return false;
+            }
+            var engine = instanceInfo._owner.owner.engine;
+            var depthFunction = 0;
+            if (this.effectFill && this.effectBorder) {
+                depthFunction = engine.getDepthFunction();
+                engine.setDepthFunctionToLessOrEqual();
+            }
+            var cur;
+            if (this.isTransparent) {
+                cur = engine.getAlphaMode();
+                engine.setAlphaMode(BABYLON.Engine.ALPHA_COMBINE);
+            }
+            if (this.effectFill) {
+                var partIndex = instanceInfo._partIndexFromId.get(BABYLON.Shape2D.SHAPE2D_FILLPARTID.toString());
+                engine.enableEffect(this.effectFill);
+                engine.bindBuffers(this.fillVB, this.fillIB, [2], 2 * 4, this.effectFill);
+                var count = instanceInfo._instancesPartsData[partIndex].usedElementCount;
+                if (instanceInfo._owner.owner.supportInstancedArray) {
+                    if (!this.instancingFillAttributes) {
+                        // Compute the offset locations of the attributes in the vertex shader that will be mapped to the instance buffer data
+                        this.instancingFillAttributes = this.loadInstancingAttributes(BABYLON.Shape2D.SHAPE2D_FILLPARTID, this.effectFill);
+                    }
+                    engine.updateAndBindInstancesBuffer(instanceInfo._instancesPartsBuffer[partIndex], null, this.instancingFillAttributes);
+                    engine.draw(true, 0, this.fillIndicesCount, count);
+                    engine.unBindInstancesBuffer(instanceInfo._instancesPartsBuffer[partIndex], this.instancingFillAttributes);
+                }
+                else {
+                    for (var i = 0; i < count; i++) {
+                        this.setupUniforms(this.effectFill, partIndex, instanceInfo._instancesPartsData[partIndex], i);
+                        engine.draw(true, 0, this.fillIndicesCount);
+                    }
+                }
+            }
+            if (this.effectBorder) {
+                var partIndex = instanceInfo._partIndexFromId.get(BABYLON.Shape2D.SHAPE2D_BORDERPARTID.toString());
+                engine.enableEffect(this.effectBorder);
+                engine.bindBuffers(this.borderVB, this.borderIB, [2], 2 * 4, this.effectBorder);
+                var count = instanceInfo._instancesPartsData[partIndex].usedElementCount;
+                if (instanceInfo._owner.owner.supportInstancedArray) {
+                    if (!this.instancingBorderAttributes) {
+                        this.instancingBorderAttributes = this.loadInstancingAttributes(BABYLON.Shape2D.SHAPE2D_BORDERPARTID, this.effectBorder);
+                    }
+                    engine.updateAndBindInstancesBuffer(instanceInfo._instancesPartsBuffer[partIndex], null, this.instancingBorderAttributes);
+                    engine.draw(true, 0, this.borderIndicesCount, count);
+                    engine.unBindInstancesBuffer(instanceInfo._instancesPartsBuffer[partIndex], this.instancingBorderAttributes);
+                }
+                else {
+                    for (var i = 0; i < count; i++) {
+                        this.setupUniforms(this.effectBorder, partIndex, instanceInfo._instancesPartsData[partIndex], i);
+                        engine.draw(true, 0, this.borderIndicesCount);
+                    }
+                }
+            }
+            if (this.isTransparent) {
+                engine.setAlphaMode(cur);
+            }
+            if (this.effectFill && this.effectBorder) {
+                engine.setDepthFunction(depthFunction);
+            }
+            return true;
+        };
+        Lines2DRenderCache.prototype.dispose = function () {
+            if (!_super.prototype.dispose.call(this)) {
+                return false;
+            }
+            if (this.fillVB) {
+                this._engine._releaseBuffer(this.fillVB);
+                this.fillVB = null;
+            }
+            if (this.fillIB) {
+                this._engine._releaseBuffer(this.fillIB);
+                this.fillIB = null;
+            }
+            if (this.effectFill) {
+                this._engine._releaseEffect(this.effectFill);
+                this.effectFill = null;
+            }
+            if (this.borderVB) {
+                this._engine._releaseBuffer(this.borderVB);
+                this.borderVB = null;
+            }
+            if (this.borderIB) {
+                this._engine._releaseBuffer(this.borderIB);
+                this.borderIB = null;
+            }
+            if (this.effectBorder) {
+                this._engine._releaseEffect(this.effectBorder);
+                this.effectBorder = null;
+            }
+            return true;
+        };
+        return Lines2DRenderCache;
+    })(BABYLON.ModelRenderCache);
+    BABYLON.Lines2DRenderCache = Lines2DRenderCache;
+    var Lines2DInstanceData = (function (_super) {
+        __extends(Lines2DInstanceData, _super);
+        function Lines2DInstanceData(partId) {
+            _super.call(this, partId, 1);
+        }
+        Object.defineProperty(Lines2DInstanceData.prototype, "boundingMin", {
+            get: function () {
+                return null;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(Lines2DInstanceData.prototype, "boundingMax", {
+            get: function () {
+                return null;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        __decorate([
+            BABYLON.instanceData()
+        ], Lines2DInstanceData.prototype, "boundingMin", null);
+        __decorate([
+            BABYLON.instanceData()
+        ], Lines2DInstanceData.prototype, "boundingMax", null);
+        return Lines2DInstanceData;
+    })(BABYLON.Shape2DInstanceData);
+    BABYLON.Lines2DInstanceData = Lines2DInstanceData;
+    var Lines2D = (function (_super) {
+        __extends(Lines2D, _super);
+        function Lines2D() {
+            _super.apply(this, arguments);
+        }
+        Object.defineProperty(Lines2D, "NoCap", {
+            get: function () { return Lines2D._noCap; },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(Lines2D, "RoundCap", {
+            get: function () { return Lines2D._roundCap; },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(Lines2D, "TriangleCap", {
+            get: function () { return Lines2D._triangleCap; },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(Lines2D, "SquareAnchorCap", {
+            get: function () { return Lines2D._squareAnchorCap; },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(Lines2D, "RoundAnchorCap", {
+            get: function () { return Lines2D._roundAnchorCap; },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(Lines2D, "DiamondAnchorCap", {
+            get: function () { return Lines2D._diamondAnchorCap; },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(Lines2D, "ArrowCap", {
+            get: function () { return Lines2D._arrowCap; },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(Lines2D.prototype, "actualSize", {
+            get: function () {
+                return this.size;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(Lines2D.prototype, "points", {
+            get: function () {
+                return this._points;
+            },
+            set: function (value) {
+                this._points = value;
+                this._levelBoundingInfoDirty = true;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(Lines2D.prototype, "fillThickness", {
+            get: function () {
+                return this._fillThickness;
+            },
+            set: function (value) {
+                this._fillThickness = value;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(Lines2D.prototype, "closed", {
+            get: function () {
+                return this._closed;
+            },
+            set: function (value) {
+                this._closed = value;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(Lines2D.prototype, "startCap", {
+            get: function () {
+                return this._startCap;
+            },
+            set: function (value) {
+                this._startCap = value;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(Lines2D.prototype, "endCap", {
+            get: function () {
+                return this._endCap;
+            },
+            set: function (value) {
+                this._endCap = value;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Lines2D.prototype.levelIntersect = function (intersectInfo) {
+            var pl = this.points.length;
+            var l = this.closed ? pl + 1 : pl;
+            var originOffset = new BABYLON.Vector2(-0.5, -0.5);
+            var p = intersectInfo._localPickPosition;
+            var prevA = this.transformPointWithOrigin(this._contour[0], originOffset);
+            var prevB = this.transformPointWithOrigin(this._contour[1], originOffset);
+            for (var i = 1; i < l; i++) {
+                var curA = this.transformPointWithOrigin(this._contour[(i % pl) * 2 + 0], originOffset);
+                var curB = this.transformPointWithOrigin(this._contour[(i % pl) * 2 + 1], originOffset);
+                if (BABYLON.Vector2.PointInTriangle(p, prevA, prevB, curA)) {
+                    return true;
+                }
+                if (BABYLON.Vector2.PointInTriangle(p, curA, prevB, curB)) {
+                    return true;
+                }
+                prevA = curA;
+                prevB = curB;
+            }
+            return false;
+        };
+        Object.defineProperty(Lines2D.prototype, "size", {
+            get: function () {
+                return this._size;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(Lines2D.prototype, "boundingMin", {
+            get: function () {
+                return this._boundingMin;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(Lines2D.prototype, "boundingMax", {
+            get: function () {
+                return this._boundingMax;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Lines2D.prototype.getUsedShaderCategories = function (dataPart) {
+            var res = _super.prototype.getUsedShaderCategories.call(this, dataPart);
+            // Remove the BORDER category, we don't use it in the VertexShader
+            var i = res.indexOf(BABYLON.Shape2D.SHAPE2D_CATEGORY_BORDER);
+            if (i !== -1) {
+                res.splice(i, 1);
+            }
+            return res;
+        };
+        Lines2D.prototype.updateLevelBoundingInfo = function () {
+            BABYLON.BoundingInfo2D.CreateFromSizeToRef(this.size, this._levelBoundingInfo, this.origin);
+        };
+        Lines2D.prototype.setupLines2D = function (owner, parent, id, position, origin, points, fillThickness, startCap, endCap, fill, border, borderThickness, closed) {
+            this.setupShape2D(owner, parent, id, position, origin, true, fill, border, borderThickness);
+            this.fillThickness = fillThickness;
+            this.startCap = startCap;
+            this.endCap = endCap;
+            this.points = points;
+            this.closed = closed;
+            this._size = BABYLON.Size.Zero();
+            this._boundingMin = BABYLON.Vector2.Zero();
+            this._boundingMax = BABYLON.Vector2.Zero();
+        };
+        /**
+         * Create an 2D Lines Shape primitive. The defined lines may be opened or closed (see below)
+         * @param parent the parent primitive, must be a valid primitive (or the Canvas)
+         * @param points an array that describe the points to use to draw the line, must contain at least two entries.
+         * options:
+         *  - id a text identifier, for information purpose
+         *  - x: the X position relative to its parent, default is 0
+         *  - y: the Y position relative to its parent, default is 0
+         *  - origin: define the normalized origin point location, default [0.5;0.5]
+         *  - fillThickness: the thickness of the fill part of the line, can be null to draw nothing (but a border brush must be given), default is 1.
+         *  - closed: if false the lines are said to be opened, the first point and the latest DON'T connect. if true the lines are said to be closed, the first and last point will be connected by a line. For instance you can define the 4 points of a rectangle, if you set closed to true a 4 edges rectangle will be drawn. If you set false, only three edges will be drawn, the edge formed by the first and last point won't exist. Default is false.
+         *  - Draw a cap of the given type at the start of the first line, you can't define a Cap if the Lines2D is closed. Default is Lines2D.NoCap.
+         *  - Draw a cap of the given type at the end of the last line, you can't define a Cap if the Lines2D is closed. Default is Lines2D.NoCap.
+         *  - fill: the brush used to draw the fill content of the lines, you can set null to draw nothing (but you will have to set a border brush), default is a SolidColorBrush of plain white.
+         *  - border: the brush used to draw the border of the lines, you can set null to draw nothing (but you will have to set a fill brush), default is null.
+         *  - borderThickness: the thickness of the drawn border, default is 1.
+         */
+        Lines2D.Create = function (parent, points, options) {
+            BABYLON.Prim2DBase.CheckParent(parent);
+            var fill;
+            if (options && options.fill !== undefined) {
+                fill = options.fill;
+            }
+            else {
+                fill = BABYLON.Canvas2D.GetSolidColorBrushFromHex("#FFFFFFFF");
+            }
+            var lines = new Lines2D();
+            lines.setupLines2D(parent.owner, parent, options && options.id || null, new BABYLON.Vector2(options && options.x || 0, options && options.y || 0), options && options.origin || null, points, options && options.fillThickness || 1, options && options.startCap || 0, options && options.endCap || 0, fill, options && options.border || null, options && options.borderThickness || 0, options && options.closed || false);
+            return lines;
+        };
+        Lines2D.prototype.createModelRenderCache = function (modelKey, isTransparent) {
+            var renderCache = new Lines2DRenderCache(this.owner.engine, modelKey, isTransparent);
+            return renderCache;
+        };
+        Lines2D.prototype.setupModelRenderCache = function (modelRenderCache) {
+            var _this = this;
+            var renderCache = modelRenderCache;
+            var engine = this.owner.engine;
+            // Init min/max because their being computed here
+            this.boundingMin = new BABYLON.Vector2(Number.MAX_VALUE, Number.MAX_VALUE);
+            this.boundingMax = new BABYLON.Vector2(Number.MIN_VALUE, Number.MIN_VALUE);
+            var perp = function (v, res) {
+                res.x = v.y;
+                res.y = -v.x;
+            };
+            var direction = function (a, b, res) {
+                a.subtractToRef(b, res);
+                res.normalize();
+            };
+            var tps = BABYLON.Vector2.Zero();
+            var computeMiter = function (tangent, miter, a, b) {
+                a.addToRef(b, tangent);
+                tangent.normalize();
+                miter.x = -tangent.y;
+                miter.y = tangent.x;
+                tps.x = -a.y;
+                tps.y = a.x;
+                return 1 / BABYLON.Vector2.Dot(miter, tps);
+            };
+            var intersect = function (x1, y1, x2, y2, x3, y3, x4, y4) {
+                var d = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4);
+                if (d === 0)
+                    return false;
+                var xi = ((x3 - x4) * (x1 * y2 - y1 * x2) - (x1 - x2) * (x3 * y4 - y3 * x4)) / d; // Intersection point is xi/yi, just in case...
+                //let yi = ((y3 - y4) * (x1 * y2 - y1 * x2) - (y1 - y2) * (x3 * y4 - y3 * x4)) / d; // That's why I left it commented
+                if (xi < Math.min(x1, x2) || xi > Math.max(x1, x2))
+                    return false;
+                if (xi < Math.min(x3, x4) || xi > Math.max(x3, x4))
+                    return false;
+                return true;
+            };
+            var startDir = BABYLON.Vector2.Zero();
+            var endDir = BABYLON.Vector2.Zero();
+            var updateMinMax = function (array, offset) {
+                if (offset >= array.length) {
+                    return;
+                }
+                _this._boundingMin.x = Math.min(_this._boundingMin.x, array[offset]);
+                _this._boundingMax.x = Math.max(_this._boundingMax.x, array[offset]);
+                _this._boundingMin.y = Math.min(_this._boundingMin.y, array[offset + 1]);
+                _this._boundingMax.y = Math.max(_this._boundingMax.y, array[offset + 1]);
+            };
+            var store = function (array, contour, index, max, p, n, halfThickness, borderThickness, detectFlip) {
+                var borderMode = borderThickness != null && !isNaN(borderThickness);
+                var off = index * (borderMode ? 8 : 4);
+                // Mandatory because we'll be out of bound in case of closed line, for the very last point (which is a duplicate of the first that we don't store in the vb)
+                if (off >= array.length) {
+                    return;
+                }
+                // Store start/end normal, we need it for the cap construction
+                if (index === 0) {
+                    perp(n, startDir);
+                }
+                else if (index === max - 1) {
+                    perp(n, endDir);
+                    endDir.x *= -1;
+                    endDir.y *= -1;
+                }
+                var swap = false;
+                array[off + 0] = p.x + n.x * halfThickness;
+                array[off + 1] = p.y + n.y * halfThickness;
+                array[off + 2] = p.x + n.x * -halfThickness;
+                array[off + 3] = p.y + n.y * -halfThickness;
+                updateMinMax(array, off);
+                updateMinMax(array, off + 2);
+                // If an index is given we check if the two segments formed between [index+0;detectFlip+0] and [index+2;detectFlip+2] intersect themselves.
+                // It should not be the case, they should be parallel, so if they cross, we switch the order of storage to ensure we'll have parallel lines
+                if (detectFlip !== undefined) {
+                    // Flip if intersect
+                    var flipOff = detectFlip * (borderMode ? 8 : 4);
+                    if (intersect(array[off + 0], array[off + 1], array[flipOff + 0], array[flipOff + 1], array[off + 2], array[off + 3], array[flipOff + 2], array[flipOff + 3])) {
+                        swap = true;
+                        var tps_1 = array[off + 0];
+                        array[off + 0] = array[off + 2];
+                        array[off + 2] = tps_1;
+                        tps_1 = array[off + 1];
+                        array[off + 1] = array[off + 3];
+                        array[off + 3] = tps_1;
+                    }
+                }
+                if (borderMode) {
+                    var t = halfThickness + borderThickness;
+                    array[off + 4] = p.x + n.x * (swap ? -t : t);
+                    array[off + 5] = p.y + n.y * (swap ? -t : t);
+                    array[off + 6] = p.x + n.x * (swap ? t : -t);
+                    array[off + 7] = p.y + n.y * (swap ? t : -t);
+                    updateMinMax(array, off + 4);
+                    updateMinMax(array, off + 6);
+                }
+                if (contour) {
+                    off += borderMode ? 4 : 0;
+                    contour.push(new BABYLON.Vector2(array[off + 0], array[off + 1]));
+                    contour.push(new BABYLON.Vector2(array[off + 2], array[off + 3]));
+                }
+            };
+            var sd = Lines2D._roundCapSubDiv;
+            var getCapSize = function (type, border) {
+                if (border === void 0) { border = false; }
+                // If no array given, we call this to get the size
+                var vbsize = 0, ibsize = 0;
+                switch (type) {
+                    case Lines2D.NoCap:
+                        // If the line is not close and we're computing border, we add the size to generate the edge border
+                        if (!_this.closed && border) {
+                            vbsize = 4;
+                            ibsize = 6;
+                        }
+                        else {
+                            vbsize = ibsize = 0;
+                        }
+                        break;
+                    case Lines2D.RoundCap:
+                        if (border) {
+                            vbsize = sd;
+                            ibsize = (sd - 2) * 3;
+                        }
+                        else {
+                            vbsize = (sd / 2) + 1;
+                            ibsize = (sd / 2) * 3;
+                        }
+                        break;
+                    case Lines2D.ArrowCap:
+                        if (border) {
+                            vbsize = 12;
+                            ibsize = 24;
+                        }
+                        else {
+                            vbsize = 3;
+                            ibsize = 3;
+                        }
+                        break;
+                    case Lines2D.TriangleCap:
+                        if (border) {
+                            vbsize = 6;
+                            ibsize = 12;
+                        }
+                        else {
+                            vbsize = 3;
+                            ibsize = 3;
+                        }
+                        break;
+                    case Lines2D.DiamondAnchorCap:
+                        if (border) {
+                            vbsize = 10;
+                            ibsize = 24;
+                        }
+                        else {
+                            vbsize = 5;
+                            ibsize = 9;
+                        }
+                        break;
+                    case Lines2D.SquareAnchorCap:
+                        if (border) {
+                            vbsize = 12;
+                            ibsize = 30;
+                        }
+                        else {
+                            vbsize = 4;
+                            ibsize = 6;
+                        }
+                        break;
+                    case Lines2D.RoundAnchorCap:
+                        if (border) {
+                            vbsize = sd * 2;
+                            ibsize = (sd - 1) * 6;
+                        }
+                        else {
+                            vbsize = sd + 1;
+                            ibsize = (sd + 1) * 3;
+                        }
+                        break;
+                }
+                return { vbsize: vbsize * 2, ibsize: ibsize };
+            };
+            var v = BABYLON.Vector2.Zero();
+            var storeVertex = function (vb, baseOffset, index, basePos, rotation, vertex) {
+                var c = Math.cos(rotation);
+                var s = Math.sin(rotation);
+                v.x = (c * vertex.x) + (-s * vertex.y) + basePos.x;
+                v.y = (s * vertex.x) + (c * vertex.y) + basePos.y;
+                var offset = baseOffset + (index * 2);
+                vb[offset + 0] = v.x;
+                vb[offset + 1] = v.y;
+                updateMinMax(vb, offset);
+                return (baseOffset + index * 2) / 2;
+            };
+            var storeIndex = function (ib, baseOffset, index, vertexIndex) {
+                ib[baseOffset + index] = vertexIndex;
+            };
+            var buildCap = function (vb, vbi, ib, ibi, pos, thickness, borderThickness, type, capDir) {
+                // Compute the transformation from the direction of the cap to build relative to our default orientation [1;0] (our cap are by default pointing toward right, horizontal
+                var dir = new BABYLON.Vector2(1, 0);
+                var angle = Math.atan2(capDir.y, capDir.x) - Math.atan2(dir.y, dir.x);
+                var ht = thickness / 2;
+                var t = thickness;
+                var borderMode = borderThickness != null;
+                var bt = borderThickness;
+                switch (type) {
+                    case Lines2D.NoCap:
+                        if (borderMode && !_this.closed) {
+                            var vi = 0;
+                            var ii = 0;
+                            var v1 = storeVertex(vb, vbi, vi++, pos, angle, new BABYLON.Vector2(0, ht + bt));
+                            var v2 = storeVertex(vb, vbi, vi++, pos, angle, new BABYLON.Vector2(bt, ht + bt));
+                            var v3 = storeVertex(vb, vbi, vi++, pos, angle, new BABYLON.Vector2(bt, -(ht + bt)));
+                            var v4 = storeVertex(vb, vbi, vi++, pos, angle, new BABYLON.Vector2(0, -(ht + bt)));
+                            storeIndex(ib, ibi, ii++, v1);
+                            storeIndex(ib, ibi, ii++, v2);
+                            storeIndex(ib, ibi, ii++, v3);
+                            storeIndex(ib, ibi, ii++, v1);
+                            storeIndex(ib, ibi, ii++, v3);
+                            storeIndex(ib, ibi, ii++, v4);
+                        }
+                        break;
+                    case Lines2D.ArrowCap:
+                        ht *= 2;
+                    case Lines2D.TriangleCap:
+                        {
+                            if (borderMode) {
+                                var f = type === Lines2D.TriangleCap ? bt : Math.sqrt(bt * bt * 2);
+                                var v1 = storeVertex(vb, vbi, 0, pos, angle, new BABYLON.Vector2(0, ht));
+                                var v2 = storeVertex(vb, vbi, 1, pos, angle, new BABYLON.Vector2(ht, 0));
+                                var v3 = storeVertex(vb, vbi, 2, pos, angle, new BABYLON.Vector2(0, -ht));
+                                var v4 = storeVertex(vb, vbi, 3, pos, angle, new BABYLON.Vector2(0, ht + f));
+                                var v5 = storeVertex(vb, vbi, 4, pos, angle, new BABYLON.Vector2(ht + f, 0));
+                                var v6 = storeVertex(vb, vbi, 5, pos, angle, new BABYLON.Vector2(0, -(ht + f)));
+                                var ii = 0;
+                                storeIndex(ib, ibi, ii++, v1);
+                                storeIndex(ib, ibi, ii++, v4);
+                                storeIndex(ib, ibi, ii++, v5);
+                                storeIndex(ib, ibi, ii++, v1);
+                                storeIndex(ib, ibi, ii++, v5);
+                                storeIndex(ib, ibi, ii++, v2);
+                                storeIndex(ib, ibi, ii++, v6);
+                                storeIndex(ib, ibi, ii++, v3);
+                                storeIndex(ib, ibi, ii++, v2);
+                                storeIndex(ib, ibi, ii++, v6);
+                                storeIndex(ib, ibi, ii++, v2);
+                                storeIndex(ib, ibi, ii++, v5);
+                                if (type === Lines2D.ArrowCap) {
+                                    var rht = thickness / 2;
+                                    var v7 = storeVertex(vb, vbi, 6, pos, angle, new BABYLON.Vector2(0, rht + bt));
+                                    var v8 = storeVertex(vb, vbi, 7, pos, angle, new BABYLON.Vector2(-bt, rht + bt));
+                                    var v9 = storeVertex(vb, vbi, 8, pos, angle, new BABYLON.Vector2(-bt, ht + f));
+                                    var v10 = storeVertex(vb, vbi, 9, pos, angle, new BABYLON.Vector2(0, -(rht + bt)));
+                                    var v11 = storeVertex(vb, vbi, 10, pos, angle, new BABYLON.Vector2(-bt, -(rht + bt)));
+                                    var v12 = storeVertex(vb, vbi, 11, pos, angle, new BABYLON.Vector2(-bt, -(ht + f)));
+                                    storeIndex(ib, ibi, ii++, v7);
+                                    storeIndex(ib, ibi, ii++, v8);
+                                    storeIndex(ib, ibi, ii++, v9);
+                                    storeIndex(ib, ibi, ii++, v7);
+                                    storeIndex(ib, ibi, ii++, v9);
+                                    storeIndex(ib, ibi, ii++, v4);
+                                    storeIndex(ib, ibi, ii++, v10);
+                                    storeIndex(ib, ibi, ii++, v12);
+                                    storeIndex(ib, ibi, ii++, v11);
+                                    storeIndex(ib, ibi, ii++, v10);
+                                    storeIndex(ib, ibi, ii++, v6);
+                                    storeIndex(ib, ibi, ii++, v12);
+                                }
+                            }
+                            else {
+                                var v1 = storeVertex(vb, vbi, 0, pos, angle, new BABYLON.Vector2(0, ht));
+                                var v2 = storeVertex(vb, vbi, 1, pos, angle, new BABYLON.Vector2(ht, 0));
+                                var v3 = storeVertex(vb, vbi, 2, pos, angle, new BABYLON.Vector2(0, -ht));
+                                storeIndex(ib, ibi, 0, v1);
+                                storeIndex(ib, ibi, 1, v2);
+                                storeIndex(ib, ibi, 2, v3);
+                            }
+                            break;
+                        }
+                    case Lines2D.RoundCap:
+                        {
+                            if (borderMode) {
+                                var curA = -Math.PI / 2;
+                                var incA = Math.PI / (sd / 2 - 1);
+                                var ii = 0;
+                                for (var i = 0; i < (sd / 2); i++) {
+                                    var v1 = storeVertex(vb, vbi, i * 2 + 0, pos, angle, new BABYLON.Vector2(Math.cos(curA) * ht, Math.sin(curA) * ht));
+                                    var v2 = storeVertex(vb, vbi, i * 2 + 1, pos, angle, new BABYLON.Vector2(Math.cos(curA) * (ht + bt), Math.sin(curA) * (ht + bt)));
+                                    if (i > 0) {
+                                        storeIndex(ib, ibi, ii++, v1 - 2);
+                                        storeIndex(ib, ibi, ii++, v2 - 2);
+                                        storeIndex(ib, ibi, ii++, v2);
+                                        storeIndex(ib, ibi, ii++, v1 - 2);
+                                        storeIndex(ib, ibi, ii++, v2);
+                                        storeIndex(ib, ibi, ii++, v1);
+                                    }
+                                    curA += incA;
+                                }
+                            }
+                            else {
+                                var c = storeVertex(vb, vbi, 0, pos, angle, new BABYLON.Vector2(0, 0));
+                                var curA = -Math.PI / 2;
+                                var incA = Math.PI / (sd / 2 - 1);
+                                storeVertex(vb, vbi, 1, pos, angle, new BABYLON.Vector2(Math.cos(curA) * ht, Math.sin(curA) * ht));
+                                curA += incA;
+                                for (var i = 1; i < (sd / 2); i++) {
+                                    var v2 = storeVertex(vb, vbi, i + 1, pos, angle, new BABYLON.Vector2(Math.cos(curA) * ht, Math.sin(curA) * ht));
+                                    storeIndex(ib, ibi, i * 3 + 0, c);
+                                    storeIndex(ib, ibi, i * 3 + 1, v2 - 1);
+                                    storeIndex(ib, ibi, i * 3 + 2, v2);
+                                    curA += incA;
+                                }
+                            }
+                            break;
+                        }
+                    case Lines2D.SquareAnchorCap:
+                        {
+                            var vi = 0;
+                            var v1 = storeVertex(vb, vbi, vi++, pos, angle, new BABYLON.Vector2(0, t));
+                            var v2 = storeVertex(vb, vbi, vi++, pos, angle, new BABYLON.Vector2(t * 2, t));
+                            var v3 = storeVertex(vb, vbi, vi++, pos, angle, new BABYLON.Vector2(t * 2, -t));
+                            var v4 = storeVertex(vb, vbi, vi++, pos, angle, new BABYLON.Vector2(0, -t));
+                            if (borderMode) {
+                                var v5 = storeVertex(vb, vbi, vi++, pos, angle, new BABYLON.Vector2(0, ht + bt));
+                                var v6 = storeVertex(vb, vbi, vi++, pos, angle, new BABYLON.Vector2(-bt, ht + bt));
+                                var v7 = storeVertex(vb, vbi, vi++, pos, angle, new BABYLON.Vector2(-bt, t + bt));
+                                var v8 = storeVertex(vb, vbi, vi++, pos, angle, new BABYLON.Vector2(t * 2 + bt, t + bt));
+                                var v9 = storeVertex(vb, vbi, vi++, pos, angle, new BABYLON.Vector2(t * 2 + bt, -(t + bt)));
+                                var v10 = storeVertex(vb, vbi, vi++, pos, angle, new BABYLON.Vector2(-bt, -(t + bt)));
+                                var v11 = storeVertex(vb, vbi, vi++, pos, angle, new BABYLON.Vector2(-bt, -(ht + bt)));
+                                var v12 = storeVertex(vb, vbi, vi++, pos, angle, new BABYLON.Vector2(0, -(ht + bt)));
+                                var ii = 0;
+                                storeIndex(ib, ibi, ii++, v6);
+                                storeIndex(ib, ibi, ii++, v1);
+                                storeIndex(ib, ibi, ii++, v5);
+                                storeIndex(ib, ibi, ii++, v6);
+                                storeIndex(ib, ibi, ii++, v7);
+                                storeIndex(ib, ibi, ii++, v1);
+                                storeIndex(ib, ibi, ii++, v1);
+                                storeIndex(ib, ibi, ii++, v7);
+                                storeIndex(ib, ibi, ii++, v8);
+                                storeIndex(ib, ibi, ii++, v1);
+                                storeIndex(ib, ibi, ii++, v8);
+                                storeIndex(ib, ibi, ii++, v2);
+                                storeIndex(ib, ibi, ii++, v2);
+                                storeIndex(ib, ibi, ii++, v8);
+                                storeIndex(ib, ibi, ii++, v9);
+                                storeIndex(ib, ibi, ii++, v2);
+                                storeIndex(ib, ibi, ii++, v9);
+                                storeIndex(ib, ibi, ii++, v3);
+                                storeIndex(ib, ibi, ii++, v3);
+                                storeIndex(ib, ibi, ii++, v9);
+                                storeIndex(ib, ibi, ii++, v10);
+                                storeIndex(ib, ibi, ii++, v3);
+                                storeIndex(ib, ibi, ii++, v10);
+                                storeIndex(ib, ibi, ii++, v4);
+                                storeIndex(ib, ibi, ii++, v10);
+                                storeIndex(ib, ibi, ii++, v11);
+                                storeIndex(ib, ibi, ii++, v4);
+                                storeIndex(ib, ibi, ii++, v11);
+                                storeIndex(ib, ibi, ii++, v12);
+                                storeIndex(ib, ibi, ii++, v4);
+                            }
+                            else {
+                                storeIndex(ib, ibi, 0, v1);
+                                storeIndex(ib, ibi, 1, v2);
+                                storeIndex(ib, ibi, 2, v3);
+                                storeIndex(ib, ibi, 3, v1);
+                                storeIndex(ib, ibi, 4, v3);
+                                storeIndex(ib, ibi, 5, v4);
+                            }
+                            break;
+                        }
+                    case Lines2D.RoundAnchorCap:
+                        {
+                            var cpos = Math.sqrt(t * t - ht * ht);
+                            var center = new BABYLON.Vector2(cpos, 0);
+                            var curA = BABYLON.Tools.ToRadians(-150);
+                            var incA = BABYLON.Tools.ToRadians(300) / (sd - 1);
+                            if (borderMode) {
+                                var ii = 0;
+                                for (var i = 0; i < sd; i++) {
+                                    var v1 = storeVertex(vb, vbi, i * 2 + 0, pos, angle, new BABYLON.Vector2(cpos + Math.cos(curA) * t, Math.sin(curA) * t));
+                                    var v2 = storeVertex(vb, vbi, i * 2 + 1, pos, angle, new BABYLON.Vector2(cpos + Math.cos(curA) * (t + bt), Math.sin(curA) * (t + bt)));
+                                    if (i > 0) {
+                                        storeIndex(ib, ibi, ii++, v1 - 2);
+                                        storeIndex(ib, ibi, ii++, v2 - 2);
+                                        storeIndex(ib, ibi, ii++, v2);
+                                        storeIndex(ib, ibi, ii++, v1 - 2);
+                                        storeIndex(ib, ibi, ii++, v2);
+                                        storeIndex(ib, ibi, ii++, v1);
+                                    }
+                                    curA += incA;
+                                }
+                            }
+                            else {
+                                var c = storeVertex(vb, vbi, 0, pos, angle, center);
+                                storeVertex(vb, vbi, 1, pos, angle, new BABYLON.Vector2(cpos + Math.cos(curA) * t, Math.sin(curA) * t));
+                                curA += incA;
+                                for (var i = 1; i < sd; i++) {
+                                    var v2 = storeVertex(vb, vbi, i + 1, pos, angle, new BABYLON.Vector2(cpos + Math.cos(curA) * t, Math.sin(curA) * t));
+                                    storeIndex(ib, ibi, i * 3 + 0, c);
+                                    storeIndex(ib, ibi, i * 3 + 1, v2 - 1);
+                                    storeIndex(ib, ibi, i * 3 + 2, v2);
+                                    curA += incA;
+                                }
+                                storeIndex(ib, ibi, sd * 3 + 0, c);
+                                storeIndex(ib, ibi, sd * 3 + 1, c + 1);
+                                storeIndex(ib, ibi, sd * 3 + 2, c + sd);
+                            }
+                            break;
+                        }
+                    case Lines2D.DiamondAnchorCap:
+                        {
+                            var vi = 0;
+                            var v1 = storeVertex(vb, vbi, vi++, pos, angle, new BABYLON.Vector2(0, ht));
+                            var v2 = storeVertex(vb, vbi, vi++, pos, angle, new BABYLON.Vector2(ht, t));
+                            var v3 = storeVertex(vb, vbi, vi++, pos, angle, new BABYLON.Vector2(ht * 3, 0));
+                            var v4 = storeVertex(vb, vbi, vi++, pos, angle, new BABYLON.Vector2(ht, -t));
+                            var v5 = storeVertex(vb, vbi, vi++, pos, angle, new BABYLON.Vector2(0, -ht));
+                            if (borderMode) {
+                                var f = Math.sqrt(bt * bt * 2);
+                                var v6 = storeVertex(vb, vbi, vi++, pos, angle, new BABYLON.Vector2(-f, ht));
+                                var v7 = storeVertex(vb, vbi, vi++, pos, angle, new BABYLON.Vector2(ht, t + f));
+                                var v8 = storeVertex(vb, vbi, vi++, pos, angle, new BABYLON.Vector2(ht * 3 + f, 0));
+                                var v9 = storeVertex(vb, vbi, vi++, pos, angle, new BABYLON.Vector2(ht, -(t + f)));
+                                var v10 = storeVertex(vb, vbi, vi++, pos, angle, new BABYLON.Vector2(-f, -ht));
+                                var ii = 0;
+                                storeIndex(ib, ibi, ii++, v6);
+                                storeIndex(ib, ibi, ii++, v7);
+                                storeIndex(ib, ibi, ii++, v1);
+                                storeIndex(ib, ibi, ii++, v1);
+                                storeIndex(ib, ibi, ii++, v7);
+                                storeIndex(ib, ibi, ii++, v2);
+                                storeIndex(ib, ibi, ii++, v2);
+                                storeIndex(ib, ibi, ii++, v7);
+                                storeIndex(ib, ibi, ii++, v8);
+                                storeIndex(ib, ibi, ii++, v2);
+                                storeIndex(ib, ibi, ii++, v8);
+                                storeIndex(ib, ibi, ii++, v3);
+                                storeIndex(ib, ibi, ii++, v3);
+                                storeIndex(ib, ibi, ii++, v8);
+                                storeIndex(ib, ibi, ii++, v9);
+                                storeIndex(ib, ibi, ii++, v3);
+                                storeIndex(ib, ibi, ii++, v9);
+                                storeIndex(ib, ibi, ii++, v4);
+                                storeIndex(ib, ibi, ii++, v4);
+                                storeIndex(ib, ibi, ii++, v9);
+                                storeIndex(ib, ibi, ii++, v10);
+                                storeIndex(ib, ibi, ii++, v4);
+                                storeIndex(ib, ibi, ii++, v10);
+                                storeIndex(ib, ibi, ii++, v5);
+                            }
+                            else {
+                                storeIndex(ib, ibi, 0, v1);
+                                storeIndex(ib, ibi, 1, v2);
+                                storeIndex(ib, ibi, 2, v3);
+                                storeIndex(ib, ibi, 3, v1);
+                                storeIndex(ib, ibi, 4, v3);
+                                storeIndex(ib, ibi, 5, v5);
+                                storeIndex(ib, ibi, 6, v5);
+                                storeIndex(ib, ibi, 7, v3);
+                                storeIndex(ib, ibi, 8, v4);
+                            }
+                            break;
+                        }
+                }
+                return null;
+            };
+            var buildLine = function (vb, contour, ht, bt) {
+                var lineA = BABYLON.Vector2.Zero();
+                var lineB = BABYLON.Vector2.Zero();
+                var tangent = BABYLON.Vector2.Zero();
+                var miter = BABYLON.Vector2.Zero();
+                var curNormal = null;
+                if (_this.closed) {
+                    _this.points.push(_this.points[0]);
+                }
+                var total = _this.points.length;
+                for (var i = 1; i < total; i++) {
+                    var last = _this.points[i - 1];
+                    var cur = _this.points[i];
+                    var next = (i < (_this.points.length - 1)) ? _this.points[i + 1] : null;
+                    direction(cur, last, lineA);
+                    if (!curNormal) {
+                        curNormal = BABYLON.Vector2.Zero();
+                        perp(lineA, curNormal);
+                    }
+                    if (i === 1) {
+                        store(vb, contour, 0, total, _this.points[0], curNormal, ht, bt);
+                    }
+                    if (!next) {
+                        perp(lineA, curNormal);
+                        store(vb, contour, i, total, _this.points[i], curNormal, ht, bt, i - 1);
+                    }
+                    else {
+                        direction(next, cur, lineB);
+                        var miterLen = computeMiter(tangent, miter, lineA, lineB);
+                        store(vb, contour, i, total, _this.points[i], miter, miterLen * ht, miterLen * bt, i - 1);
+                    }
+                }
+                if (_this.points.length > 2 && _this.closed) {
+                    var last2 = _this.points[total - 2];
+                    var cur2 = _this.points[0];
+                    var next2 = _this.points[1];
+                    direction(cur2, last2, lineA);
+                    direction(next2, cur2, lineB);
+                    perp(lineA, curNormal);
+                    var miterLen2 = computeMiter(tangent, miter, lineA, lineB);
+                    store(vb, null, 0, total, _this.points[0], miter, miterLen2 * ht, miterLen2 * bt, 1);
+                    // Patch contour
+                    if (contour) {
+                        var off = (bt == null) ? 0 : 4;
+                        contour[0].x = vb[off + 0];
+                        contour[0].y = vb[off + 1];
+                        contour[1].x = vb[off + 2];
+                        contour[1].y = vb[off + 3];
+                    }
+                }
+                // Remove the point we added at the beginning
+                if (_this.closed) {
+                    _this.points.splice(total - 1);
+                }
+            };
+            var contour = new Array();
+            // Need to create WebGL resources for fill part?
+            if (this.fill) {
+                var startCapInfo = getCapSize(this.startCap);
+                var endCapInfo = getCapSize(this.endCap);
+                var count = this.points.length;
+                var vbSize = (count * 2 * 2) + startCapInfo.vbsize + endCapInfo.vbsize;
+                var vb = new Float32Array(vbSize);
+                var ht = this.fillThickness / 2;
+                var total = this.points.length;
+                buildLine(vb, this.border ? null : contour, ht);
+                var max = total * 2;
+                var triCount = (count - (this.closed ? 0 : 1)) * 2;
+                var ib = new Float32Array(triCount * 3 + startCapInfo.ibsize + endCapInfo.ibsize);
+                for (var i = 0; i < triCount; i += 2) {
+                    ib[i * 3 + 0] = i + 0;
+                    ib[i * 3 + 1] = i + 1;
+                    ib[i * 3 + 2] = (i + 2) % max;
+                    ib[i * 3 + 3] = i + 1;
+                    ib[i * 3 + 4] = (i + 3) % max;
+                    ib[i * 3 + 5] = (i + 2) % max;
+                }
+                buildCap(vb, count * 2 * 2, ib, triCount * 3, this.points[0], this.fillThickness, null, this.startCap, startDir);
+                buildCap(vb, (count * 2 * 2) + startCapInfo.vbsize, ib, (triCount * 3) + startCapInfo.ibsize, this.points[total - 1], this.fillThickness, null, this.endCap, endDir);
+                renderCache.fillVB = engine.createVertexBuffer(vb);
+                renderCache.fillIB = engine.createIndexBuffer(ib);
+                renderCache.fillIndicesCount = ib.length;
+                var ei = this.getDataPartEffectInfo(BABYLON.Shape2D.SHAPE2D_FILLPARTID, ["position"]);
+                renderCache.effectFill = engine.createEffect({ vertex: "lines2d", fragment: "lines2d" }, ei.attributes, ei.uniforms, [], ei.defines, null);
+            }
+            // Need to create WebGL resources for border part?
+            if (this.border) {
+                var startCapInfo = getCapSize(this.startCap, true);
+                var endCapInfo = getCapSize(this.endCap, true);
+                var count = this.points.length;
+                var vbSize = (count * 2 * 2 * 2) + startCapInfo.vbsize + endCapInfo.vbsize;
+                var vb = new Float32Array(vbSize);
+                var ht = this.fillThickness / 2;
+                var bt = this.borderThickness;
+                var total = this.points.length;
+                buildLine(vb, contour, ht, bt);
+                var max = total * 2 * 2;
+                var triCount = (count - (this.closed ? 0 : 1)) * 2 * 2;
+                var ib = new Float32Array(triCount * 3 + startCapInfo.ibsize + endCapInfo.ibsize);
+                for (var i = 0; i < triCount; i += 4) {
+                    ib[i * 3 + 0] = i + 0;
+                    ib[i * 3 + 1] = i + 2;
+                    ib[i * 3 + 2] = (i + 6) % max;
+                    ib[i * 3 + 3] = i + 0;
+                    ib[i * 3 + 4] = (i + 6) % max;
+                    ib[i * 3 + 5] = (i + 4) % max;
+                    ib[i * 3 + 6] = i + 3;
+                    ib[i * 3 + 7] = i + 1;
+                    ib[i * 3 + 8] = (i + 5) % max;
+                    ib[i * 3 + 9] = i + 3;
+                    ib[i * 3 + 10] = (i + 5) % max;
+                    ib[i * 3 + 11] = (i + 7) % max;
+                }
+                buildCap(vb, count * 2 * 2 * 2, ib, triCount * 3, this.points[0], this.fillThickness, this.borderThickness, this.startCap, startDir);
+                buildCap(vb, (count * 2 * 2 * 2) + startCapInfo.vbsize, ib, (triCount * 3) + startCapInfo.ibsize, this.points[total - 1], this.fillThickness, this.borderThickness, this.endCap, endDir);
+                renderCache.borderVB = engine.createVertexBuffer(vb);
+                renderCache.borderIB = engine.createIndexBuffer(ib);
+                renderCache.borderIndicesCount = ib.length;
+                var ei = this.getDataPartEffectInfo(BABYLON.Shape2D.SHAPE2D_BORDERPARTID, ["position"]);
+                renderCache.effectBorder = engine.createEffect({ vertex: "lines2d", fragment: "lines2d" }, ei.attributes, ei.uniforms, [], ei.defines, null);
+            }
+            this._contour = contour;
+            var bs = this._boundingMax.subtract(this._boundingMin);
+            this._size.width = bs.x;
+            this._size.height = bs.y;
+            return renderCache;
+        };
+        Lines2D.prototype.createInstanceDataParts = function () {
+            var res = new Array();
+            if (this.border) {
+                res.push(new Lines2DInstanceData(BABYLON.Shape2D.SHAPE2D_BORDERPARTID));
+            }
+            if (this.fill) {
+                res.push(new Lines2DInstanceData(BABYLON.Shape2D.SHAPE2D_FILLPARTID));
+            }
+            return res;
+        };
+        Lines2D.prototype.refreshInstanceDataPart = function (part) {
+            if (!_super.prototype.refreshInstanceDataPart.call(this, part)) {
+                return false;
+            }
+            if (part.id === BABYLON.Shape2D.SHAPE2D_BORDERPARTID) {
+                var d = part;
+                d.boundingMin = this.boundingMin;
+                d.boundingMax = this.boundingMax;
+            }
+            else if (part.id === BABYLON.Shape2D.SHAPE2D_FILLPARTID) {
+                var d = part;
+                d.boundingMin = this.boundingMin;
+                d.boundingMax = this.boundingMax;
+            }
+            return true;
+        };
+        Lines2D._noCap = 0;
+        Lines2D._roundCap = 1;
+        Lines2D._triangleCap = 2;
+        Lines2D._squareAnchorCap = 3;
+        Lines2D._roundAnchorCap = 4;
+        Lines2D._diamondAnchorCap = 5;
+        Lines2D._arrowCap = 6;
+        Lines2D._roundCapSubDiv = 36;
+        __decorate([
+            BABYLON.modelLevelProperty(BABYLON.Shape2D.SHAPE2D_PROPCOUNT + 1, function (pi) { return Lines2D.pointsProperty = pi; })
+        ], Lines2D.prototype, "points", null);
+        __decorate([
+            BABYLON.modelLevelProperty(BABYLON.Shape2D.SHAPE2D_PROPCOUNT + 2, function (pi) { return Lines2D.fillThicknessProperty = pi; })
+        ], Lines2D.prototype, "fillThickness", null);
+        __decorate([
+            BABYLON.modelLevelProperty(BABYLON.Shape2D.SHAPE2D_PROPCOUNT + 3, function (pi) { return Lines2D.closedProperty = pi; })
+        ], Lines2D.prototype, "closed", null);
+        __decorate([
+            BABYLON.modelLevelProperty(BABYLON.Shape2D.SHAPE2D_PROPCOUNT + 4, function (pi) { return Lines2D.startCapProperty = pi; })
+        ], Lines2D.prototype, "startCap", null);
+        __decorate([
+            BABYLON.modelLevelProperty(BABYLON.Shape2D.SHAPE2D_PROPCOUNT + 5, function (pi) { return Lines2D.endCapProperty = pi; })
+        ], Lines2D.prototype, "endCap", null);
+        Lines2D = __decorate([
+            BABYLON.className("Lines2D")
+        ], Lines2D);
+        return Lines2D;
+    })(BABYLON.Shape2D);
+    BABYLON.Lines2D = Lines2D;
+})(BABYLON || (BABYLON = {}));

+ 973 - 0
src/Canvas2d/babylon.lines2d.ts

@@ -0,0 +1,973 @@
+module BABYLON {
+    export class Lines2DRenderCache extends ModelRenderCache {
+        fillVB: WebGLBuffer;
+        fillIB: WebGLBuffer;
+        fillIndicesCount: number;
+        instancingFillAttributes: InstancingAttributeInfo[];
+        effectFill: Effect;
+
+        borderVB: WebGLBuffer;
+        borderIB: WebGLBuffer;
+        borderIndicesCount: number;
+        instancingBorderAttributes: InstancingAttributeInfo[];
+        effectBorder: Effect;
+
+        constructor(engine: Engine, modelKey: string, isTransparent: boolean) {
+            super(engine, modelKey, isTransparent);
+        }
+
+        render(instanceInfo: GroupInstanceInfo, context: Render2DContext): boolean {
+            // Do nothing if the shader is still loading/preparing 
+            if ((this.effectFill && !this.effectFill.isReady()) || (this.effectBorder && !this.effectBorder.isReady())) {
+                return false;
+            }
+
+            var engine = instanceInfo._owner.owner.engine;
+
+            let depthFunction = 0;
+            if (this.effectFill && this.effectBorder) {
+                depthFunction = engine.getDepthFunction();
+                engine.setDepthFunctionToLessOrEqual();
+            }
+
+            var cur: number;
+            if (this.isTransparent) {
+                cur = engine.getAlphaMode();
+                engine.setAlphaMode(Engine.ALPHA_COMBINE);
+            }
+
+            if (this.effectFill) {
+                let partIndex = instanceInfo._partIndexFromId.get(Shape2D.SHAPE2D_FILLPARTID.toString());
+             
+                engine.enableEffect(this.effectFill);
+                engine.bindBuffers(this.fillVB, this.fillIB, [2], 2*4, this.effectFill);
+                let count = instanceInfo._instancesPartsData[partIndex].usedElementCount;
+                if (instanceInfo._owner.owner.supportInstancedArray) {
+                    if (!this.instancingFillAttributes) {
+                        // Compute the offset locations of the attributes in the vertex shader that will be mapped to the instance buffer data
+                        this.instancingFillAttributes = this.loadInstancingAttributes(Shape2D.SHAPE2D_FILLPARTID, this.effectFill);
+                    }
+
+                    engine.updateAndBindInstancesBuffer(instanceInfo._instancesPartsBuffer[partIndex], null, this.instancingFillAttributes);
+                    engine.draw(true, 0, this.fillIndicesCount, count);
+                    engine.unBindInstancesBuffer(instanceInfo._instancesPartsBuffer[partIndex], this.instancingFillAttributes);
+                } else {
+                    for (let i = 0; i < count; i++) {
+                        this.setupUniforms(this.effectFill, partIndex, instanceInfo._instancesPartsData[partIndex], i);
+                        engine.draw(true, 0, this.fillIndicesCount);                        
+                    }
+                }
+            }
+
+            if (this.effectBorder) {
+                let partIndex = instanceInfo._partIndexFromId.get(Shape2D.SHAPE2D_BORDERPARTID.toString());
+
+                engine.enableEffect(this.effectBorder);
+                engine.bindBuffers(this.borderVB, this.borderIB, [2], 2 * 4, this.effectBorder);
+                let count = instanceInfo._instancesPartsData[partIndex].usedElementCount;
+                if (instanceInfo._owner.owner.supportInstancedArray) {
+                    if (!this.instancingBorderAttributes) {
+                        this.instancingBorderAttributes = this.loadInstancingAttributes(Shape2D.SHAPE2D_BORDERPARTID, this.effectBorder);
+                    }
+
+                    engine.updateAndBindInstancesBuffer(instanceInfo._instancesPartsBuffer[partIndex], null, this.instancingBorderAttributes);
+                    engine.draw(true, 0, this.borderIndicesCount, count);
+                    engine.unBindInstancesBuffer(instanceInfo._instancesPartsBuffer[partIndex], this.instancingBorderAttributes);
+                } else {
+                    for (let i = 0; i < count; i++) {
+                        this.setupUniforms(this.effectBorder, partIndex, instanceInfo._instancesPartsData[partIndex], i);
+                        engine.draw(true, 0, this.borderIndicesCount);
+                    }
+                }
+            }
+
+            if (this.isTransparent) {
+                engine.setAlphaMode(cur);
+            }
+
+            if (this.effectFill && this.effectBorder) {
+                engine.setDepthFunction(depthFunction);
+            }
+            return true;
+        }
+
+        public dispose(): boolean {
+            if (!super.dispose()) {
+                return false;
+            }
+
+            if (this.fillVB) {
+                this._engine._releaseBuffer(this.fillVB);
+                this.fillVB = null;
+            }
+
+            if (this.fillIB) {
+                this._engine._releaseBuffer(this.fillIB);
+                this.fillIB = null;
+            }
+
+            if (this.effectFill) {
+                this._engine._releaseEffect(this.effectFill);
+                this.effectFill = null;
+            }
+
+            if (this.borderVB) {
+                this._engine._releaseBuffer(this.borderVB);
+                this.borderVB = null;
+            }
+
+            if (this.borderIB) {
+                this._engine._releaseBuffer(this.borderIB);
+                this.borderIB = null;
+            }
+
+            if (this.effectBorder) {
+                this._engine._releaseEffect(this.effectBorder);
+                this.effectBorder = null;
+            }
+
+            return true;
+        }
+    }
+
+    export class Lines2DInstanceData extends Shape2DInstanceData {
+        constructor(partId: number) {
+            super(partId, 1);
+        }
+
+        @instanceData()
+        get boundingMin(): Vector2 {
+            return null;
+        }
+
+        @instanceData()
+        get boundingMax(): Vector2 {
+            return null;
+        }
+    }
+
+    @className("Lines2D")
+    export class Lines2D extends Shape2D {
+        static get NoCap            () { return Lines2D._noCap;            }
+        static get RoundCap         () { return Lines2D._roundCap;         }
+        static get TriangleCap      () { return Lines2D._triangleCap;      }
+        static get SquareAnchorCap  () { return Lines2D._squareAnchorCap;  }
+        static get RoundAnchorCap   () { return Lines2D._roundAnchorCap;   }
+        static get DiamondAnchorCap () { return Lines2D._diamondAnchorCap; }
+        static get ArrowCap         () { return Lines2D._arrowCap;         }
+
+        public static pointsProperty: Prim2DPropInfo;
+        public static fillThicknessProperty: Prim2DPropInfo;
+        public static closedProperty: Prim2DPropInfo;
+        public static startCapProperty: Prim2DPropInfo;
+        public static endCapProperty: Prim2DPropInfo;
+
+        public get actualSize(): Size {
+            return this.size;
+        }
+
+        @modelLevelProperty(Shape2D.SHAPE2D_PROPCOUNT + 1, pi => Lines2D.pointsProperty = pi)
+        public get points(): Vector2[] {
+            return this._points;
+        }
+
+        public set points(value: Vector2[]) {
+            this._points = value;
+            this._levelBoundingInfoDirty = true;
+        }
+
+        @modelLevelProperty(Shape2D.SHAPE2D_PROPCOUNT + 2, pi => Lines2D.fillThicknessProperty = pi)
+        public get fillThickness(): number {
+            return this._fillThickness;
+        }
+
+        public set fillThickness(value: number) {
+            this._fillThickness = value;
+        }
+
+        @modelLevelProperty(Shape2D.SHAPE2D_PROPCOUNT + 3, pi => Lines2D.closedProperty = pi)
+        public get closed(): boolean {
+            return this._closed;
+        }
+
+        public set closed(value: boolean) {
+            this._closed = value;
+        }
+
+        @modelLevelProperty(Shape2D.SHAPE2D_PROPCOUNT + 4, pi => Lines2D.startCapProperty = pi)
+        public get startCap(): number {
+            return this._startCap;
+        }
+
+        public set startCap(value: number) {
+            this._startCap = value;
+        }
+
+        @modelLevelProperty(Shape2D.SHAPE2D_PROPCOUNT + 5, pi => Lines2D.endCapProperty = pi)
+        public get endCap(): number {
+            return this._endCap;
+        }
+
+        public set endCap(value: number) {
+            this._endCap = value;
+        }
+
+        protected levelIntersect(intersectInfo: IntersectInfo2D): boolean {
+            let pl = this.points.length;
+            let l = this.closed ? pl + 1 : pl;
+
+            let originOffset = new Vector2(-0.5, -0.5);
+            let p = intersectInfo._localPickPosition;
+
+            let prevA = this.transformPointWithOrigin(this._contour[0], originOffset);
+            let prevB = this.transformPointWithOrigin(this._contour[1], originOffset);
+            for (let i = 1; i < l; i++) {
+                let curA = this.transformPointWithOrigin(this._contour[(i % pl) * 2 + 0], originOffset);
+                let curB = this.transformPointWithOrigin(this._contour[(i % pl) * 2 + 1], originOffset);
+
+                if (Vector2.PointInTriangle(p, prevA, prevB, curA)) {
+                    return true;
+                }
+                if (Vector2.PointInTriangle(p, curA, prevB, curB)) {
+                    return true;
+                }
+
+                prevA = curA;
+                prevB = curB;
+            }
+            return false;
+        }
+
+        protected get size(): Size {
+            return this._size;
+        }
+
+        protected get boundingMin(): Vector2 {
+            return this._boundingMin;
+        }
+
+        protected get boundingMax(): Vector2 {
+            return this._boundingMax;
+        }
+
+        protected getUsedShaderCategories(dataPart: InstanceDataBase): string[] {
+            let res = super.getUsedShaderCategories(dataPart);
+
+            // Remove the BORDER category, we don't use it in the VertexShader
+            let i = res.indexOf(Shape2D.SHAPE2D_CATEGORY_BORDER);
+            if (i !== -1) {
+                res.splice(i, 1);
+            }
+            return res;
+        }
+
+        protected updateLevelBoundingInfo() {
+            BoundingInfo2D.CreateFromSizeToRef(this.size, this._levelBoundingInfo, this.origin);
+        }
+
+        protected setupLines2D(owner: Canvas2D, parent: Prim2DBase, id: string, position: Vector2, origin: Vector2, points: Vector2[], fillThickness: number, startCap: number, endCap: number, fill: IBrush2D, border: IBrush2D, borderThickness: number, closed: boolean) {
+            this.setupShape2D(owner, parent, id, position, origin, true, fill, border, borderThickness);
+            this.fillThickness = fillThickness;
+            this.startCap = startCap;
+            this.endCap = endCap;
+            this.points = points;
+            this.closed = closed;
+            this._size = Size.Zero();
+            this._boundingMin = Vector2.Zero();
+            this._boundingMax = Vector2.Zero();
+        }
+
+        /**
+         * Create an 2D Lines Shape primitive. The defined lines may be opened or closed (see below)
+         * @param parent the parent primitive, must be a valid primitive (or the Canvas)
+         * @param points an array that describe the points to use to draw the line, must contain at least two entries.
+         * options:
+         *  - id a text identifier, for information purpose
+         *  - x: the X position relative to its parent, default is 0
+         *  - y: the Y position relative to its parent, default is 0
+         *  - origin: define the normalized origin point location, default [0.5;0.5]
+         *  - fillThickness: the thickness of the fill part of the line, can be null to draw nothing (but a border brush must be given), default is 1.
+         *  - closed: if false the lines are said to be opened, the first point and the latest DON'T connect. if true the lines are said to be closed, the first and last point will be connected by a line. For instance you can define the 4 points of a rectangle, if you set closed to true a 4 edges rectangle will be drawn. If you set false, only three edges will be drawn, the edge formed by the first and last point won't exist. Default is false.
+         *  - Draw a cap of the given type at the start of the first line, you can't define a Cap if the Lines2D is closed. Default is Lines2D.NoCap.
+         *  - Draw a cap of the given type at the end of the last line, you can't define a Cap if the Lines2D is closed. Default is Lines2D.NoCap.
+         *  - fill: the brush used to draw the fill content of the lines, you can set null to draw nothing (but you will have to set a border brush), default is a SolidColorBrush of plain white.
+         *  - border: the brush used to draw the border of the lines, you can set null to draw nothing (but you will have to set a fill brush), default is null.
+         *  - borderThickness: the thickness of the drawn border, default is 1.
+         */
+        public static Create(parent: Prim2DBase, points: Vector2[], options: { id?: string, x?: number, y?: number, origin?: Vector2, fillThickness?: number, closed?: boolean, startCap?: number, endCap?: number, fill?: IBrush2D, border?: IBrush2D, borderThickness?: number }): Lines2D {
+            Prim2DBase.CheckParent(parent);
+
+            let fill: IBrush2D;
+            if (options && options.fill !== undefined) {
+                fill = options.fill;
+            } else {
+                fill = Canvas2D.GetSolidColorBrushFromHex("#FFFFFFFF");
+            }
+
+            let lines = new Lines2D();
+            lines.setupLines2D(parent.owner, parent, options && options.id || null, new Vector2(options && options.x || 0, options && options.y || 0), options && options.origin || null, points, options && options.fillThickness || 1, options && options.startCap || 0, options && options.endCap || 0, fill, options && options.border || null, options && options.borderThickness || 0, options && options.closed || false);
+            return lines;
+        }
+
+        protected createModelRenderCache(modelKey: string, isTransparent: boolean): ModelRenderCache {
+            let renderCache = new Lines2DRenderCache(this.owner.engine, modelKey, isTransparent);
+            return renderCache;
+        }
+
+        protected setupModelRenderCache(modelRenderCache: ModelRenderCache) {
+            let renderCache = <Lines2DRenderCache>modelRenderCache;
+            let engine = this.owner.engine;
+
+            // Init min/max because their being computed here
+            this.boundingMin = new Vector2(Number.MAX_VALUE, Number.MAX_VALUE);
+            this.boundingMax = new Vector2(Number.MIN_VALUE, Number.MIN_VALUE);
+
+            let perp = (v: Vector2, res: Vector2) => {
+                res.x = v.y;
+                res.y = -v.x;
+            };
+
+            let direction = (a: Vector2, b: Vector2, res: Vector2) => {
+                a.subtractToRef(b, res);
+                res.normalize();
+            }
+
+            let tps = Vector2.Zero();
+            let computeMiter = (tangent: Vector2, miter: Vector2, a: Vector2, b: Vector2): number => {
+                a.addToRef(b, tangent);
+                tangent.normalize();
+
+                miter.x = -tangent.y;
+                miter.y = tangent.x;
+
+                tps.x = -a.y;
+                tps.y = a.x;
+
+                return 1 / Vector2.Dot(miter, tps);
+            }
+
+            let intersect = (x1: number, y1: number, x2: number, y2: number, x3: number, y3: number, x4: number, y4: number): boolean => {
+                let d = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4);
+                if (d === 0) return false;
+
+                let xi = ((x3 - x4) * (x1 * y2 - y1 * x2) - (x1 - x2) * (x3 * y4 - y3 * x4)) / d;   // Intersection point is xi/yi, just in case...
+                //let yi = ((y3 - y4) * (x1 * y2 - y1 * x2) - (y1 - y2) * (x3 * y4 - y3 * x4)) / d; // That's why I left it commented
+
+                if (xi < Math.min(x1, x2) || xi > Math.max(x1, x2)) return false;
+                if (xi < Math.min(x3, x4) || xi > Math.max(x3, x4)) return false;
+                return true;
+            }
+
+            let startDir: Vector2 = Vector2.Zero();
+            let endDir: Vector2 = Vector2.Zero();
+
+            let updateMinMax = (array: Float32Array, offset: number) => {
+                if (offset >= array.length) {
+                    return;
+                }
+                this._boundingMin.x = Math.min(this._boundingMin.x, array[offset]);
+                this._boundingMax.x = Math.max(this._boundingMax.x, array[offset]);
+                this._boundingMin.y = Math.min(this._boundingMin.y, array[offset+1]);
+                this._boundingMax.y = Math.max(this._boundingMax.y, array[offset+1]);
+            }
+
+            let store = (array: Float32Array, contour: Vector2[], index: number, max: number, p: Vector2, n: Vector2, halfThickness: number, borderThickness: number, detectFlip?: number) => {
+                let borderMode = borderThickness != null && !isNaN(borderThickness);
+                let off = index * (borderMode ? 8 : 4);
+
+                // Mandatory because we'll be out of bound in case of closed line, for the very last point (which is a duplicate of the first that we don't store in the vb)
+                if (off >= array.length) {
+                    return;
+                }
+
+                // Store start/end normal, we need it for the cap construction
+                if (index === 0) {
+                    perp(n, startDir);
+                } else if (index === max - 1) {
+                    perp(n, endDir);
+                    endDir.x *= -1;
+                    endDir.y *= -1;
+                }
+
+                let swap = false;
+
+                array[off + 0] = p.x + n.x * halfThickness;
+                array[off + 1] = p.y + n.y * halfThickness;
+                array[off + 2] = p.x + n.x * -halfThickness;
+                array[off + 3] = p.y + n.y * -halfThickness;
+
+                updateMinMax(array, off);
+                updateMinMax(array, off + 2);
+
+                // If an index is given we check if the two segments formed between [index+0;detectFlip+0] and [index+2;detectFlip+2] intersect themselves.
+                // It should not be the case, they should be parallel, so if they cross, we switch the order of storage to ensure we'll have parallel lines
+                if (detectFlip !== undefined) {
+                    // Flip if intersect
+                    let flipOff = detectFlip * (borderMode ? 8 : 4);
+                    if (intersect(array[off + 0], array[off + 1], array[flipOff + 0], array[flipOff + 1], array[off + 2], array[off + 3], array[flipOff + 2], array[flipOff + 3])) {
+                        swap = true;
+                        let tps = array[off + 0];
+                        array[off + 0] = array[off + 2];
+                        array[off + 2] = tps;
+
+                        tps = array[off + 1];
+                        array[off + 1] = array[off + 3];
+                        array[off + 3] = tps;
+                    }
+                }
+
+                if (borderMode) {
+                    let t = halfThickness + borderThickness;
+                    array[off + 4] = p.x + n.x * (swap ? -t : t);
+                    array[off + 5] = p.y + n.y * (swap ? -t : t);
+                    array[off + 6] = p.x + n.x * (swap ? t : -t);
+                    array[off + 7] = p.y + n.y * (swap ? t : -t);
+
+                    updateMinMax(array, off + 4);
+                    updateMinMax(array, off + 6);
+                }
+
+                if (contour) {
+                    off += borderMode ? 4 : 0;
+                    contour.push(new Vector2(array[off + 0], array[off + 1]));
+                    contour.push(new Vector2(array[off + 2], array[off + 3]));
+                }
+            }
+
+            let sd = Lines2D._roundCapSubDiv;
+            let getCapSize = (type: number, border: boolean = false): { vbsize: number; ibsize: number } => {
+                // If no array given, we call this to get the size
+                let vbsize: number = 0, ibsize: number = 0;
+                switch (type) {
+                    case Lines2D.NoCap:
+                        // If the line is not close and we're computing border, we add the size to generate the edge border
+                        if (!this.closed && border) {
+                            vbsize = 4;
+                            ibsize = 6;
+                        } else {
+                            vbsize = ibsize = 0;
+                        }
+                        break;
+                    case Lines2D.RoundCap:
+                        if (border) {
+                            vbsize = sd;
+                            ibsize = (sd-2) * 3;
+                        } else {
+                            vbsize = (sd / 2) + 1;
+                            ibsize = (sd / 2) * 3;
+                        }
+                        break;
+                    case Lines2D.ArrowCap:
+                        if (border) {
+                            vbsize = 12;
+                            ibsize = 24;
+                        } else {
+                            vbsize = 3;
+                            ibsize = 3;
+                        }
+                        break;
+                    case Lines2D.TriangleCap:
+                        if (border) {
+                            vbsize = 6;
+                            ibsize = 12;
+                        } else {
+                            vbsize = 3;
+                            ibsize = 3;
+                        }
+                        break;
+                    case Lines2D.DiamondAnchorCap:
+                        if (border) {
+                            vbsize = 10;
+                            ibsize = 24;
+                        } else {
+                            vbsize = 5;
+                            ibsize = 9;
+                        }
+                        break;
+                    case Lines2D.SquareAnchorCap:
+                        if (border) {
+                            vbsize = 12;
+                            ibsize = 30;
+                        } else {
+                            vbsize = 4;
+                            ibsize = 6;
+                        }
+                        break;
+                    case Lines2D.RoundAnchorCap:
+                        if (border) {
+                            vbsize = sd*2;
+                            ibsize = (sd - 1) * 6;
+                        } else {
+                            vbsize = sd + 1;
+                            ibsize = (sd + 1) * 3;
+                        }
+                        break;
+                }
+
+                return { vbsize: vbsize*2, ibsize: ibsize };
+            }
+
+            let v = Vector2.Zero();
+            let storeVertex = (vb: Float32Array, baseOffset: number, index: number, basePos: Vector2, rotation: number, vertex: Vector2): number => {
+                let c = Math.cos(rotation);
+                let s = Math.sin(rotation);
+
+                v.x = (c * vertex.x) + (-s * vertex.y) + basePos.x;
+                v.y = (s * vertex.x) + ( c * vertex.y) + basePos.y;
+                let offset = baseOffset + (index*2);
+                vb[offset + 0] = v.x;
+                vb[offset + 1] = v.y;
+
+                updateMinMax(vb, offset);
+                return (baseOffset + index*2) / 2;
+            }
+
+            let storeIndex = (ib: Float32Array, baseOffset: number, index: number, vertexIndex: number) => {
+                ib[baseOffset + index] = vertexIndex;
+            }
+
+            let buildCap = (vb: Float32Array, vbi: number, ib: Float32Array, ibi: number, pos: Vector2, thickness: number, borderThickness: number, type: number, capDir: Vector2): { vbsize: number; ibsize: number } => {
+
+                // Compute the transformation from the direction of the cap to build relative to our default orientation [1;0] (our cap are by default pointing toward right, horizontal
+                let dir = new Vector2(1, 0);
+                let angle = Math.atan2(capDir.y, capDir.x) - Math.atan2(dir.y, dir.x);
+
+                let ht = thickness / 2;
+                let t = thickness;
+                let borderMode = borderThickness != null;
+                let bt = borderThickness;
+                switch (type) {
+                    case Lines2D.NoCap:
+                        if (borderMode && !this.closed) {
+                            let vi = 0;
+                            let ii = 0;
+                            let v1 = storeVertex(vb, vbi, vi++, pos, angle, new Vector2(0, ht + bt));
+                            let v2 = storeVertex(vb, vbi, vi++, pos, angle, new Vector2(bt, ht + bt));
+                            let v3 = storeVertex(vb, vbi, vi++, pos, angle, new Vector2(bt, -(ht + bt)));
+                            let v4 = storeVertex(vb, vbi, vi++, pos, angle, new Vector2(0, -(ht + bt)));
+
+                            storeIndex(ib, ibi, ii++, v1); storeIndex(ib, ibi, ii++, v2); storeIndex(ib, ibi, ii++, v3);
+                            storeIndex(ib, ibi, ii++, v1); storeIndex(ib, ibi, ii++, v3); storeIndex(ib, ibi, ii++, v4);
+                        }
+                        break;
+                    case Lines2D.ArrowCap:
+                        ht *= 2;
+                    case Lines2D.TriangleCap:
+                    {
+                        if (borderMode) {
+                            let f = type===Lines2D.TriangleCap ? bt : Math.sqrt(bt * bt * 2);
+                            let v1 = storeVertex(vb, vbi, 0, pos, angle, new Vector2(0, ht));
+                            let v2 = storeVertex(vb, vbi, 1, pos, angle, new Vector2(ht, 0));
+                            let v3 = storeVertex(vb, vbi, 2, pos, angle, new Vector2(0, -ht));
+                            let v4 = storeVertex(vb, vbi, 3, pos, angle, new Vector2(0, ht+f));
+                            let v5 = storeVertex(vb, vbi, 4, pos, angle, new Vector2(ht+f, 0));
+                            let v6 = storeVertex(vb, vbi, 5, pos, angle, new Vector2(0, -(ht+f)));
+
+                            let ii = 0;
+                            storeIndex(ib, ibi, ii++, v1);  storeIndex(ib, ibi, ii++, v4);  storeIndex(ib, ibi, ii++, v5);
+                            storeIndex(ib, ibi, ii++, v1);  storeIndex(ib, ibi, ii++, v5);  storeIndex(ib, ibi, ii++, v2);
+                            storeIndex(ib, ibi, ii++, v6);  storeIndex(ib, ibi, ii++, v3);  storeIndex(ib, ibi, ii++, v2);
+                            storeIndex(ib, ibi, ii++, v6);  storeIndex(ib, ibi, ii++, v2);  storeIndex(ib, ibi, ii++, v5);
+
+                            if (type === Lines2D.ArrowCap) {
+                                let rht = thickness / 2;
+                                let v7 = storeVertex(vb, vbi, 6, pos, angle, new Vector2(0, rht+bt));
+                                let v8 = storeVertex(vb, vbi, 7, pos, angle, new Vector2(-bt, rht+bt));
+                                let v9 = storeVertex(vb, vbi, 8, pos, angle, new Vector2(-bt, ht+f));
+
+                                let v10 = storeVertex(vb, vbi, 9, pos, angle, new Vector2(0, -(rht+bt)));
+                                let v11 = storeVertex(vb, vbi, 10, pos, angle, new Vector2(-bt, -(rht+bt)));
+                                let v12 = storeVertex(vb, vbi, 11, pos, angle, new Vector2(-bt, -(ht+f)));
+
+                                storeIndex(ib, ibi, ii++, v7);  storeIndex(ib, ibi, ii++, v8);  storeIndex(ib, ibi, ii++, v9);
+                                storeIndex(ib, ibi, ii++, v7);  storeIndex(ib, ibi, ii++, v9);  storeIndex(ib, ibi, ii++, v4);
+                                storeIndex(ib, ibi, ii++, v10); storeIndex(ib, ibi, ii++, v12); storeIndex(ib, ibi, ii++, v11);
+                                storeIndex(ib, ibi, ii++, v10); storeIndex(ib, ibi, ii++, v6);  storeIndex(ib, ibi, ii++, v12);
+                            }
+                        } else {
+                            let v1 = storeVertex(vb, vbi, 0, pos, angle, new Vector2(0, ht));
+                            let v2 = storeVertex(vb, vbi, 1, pos, angle, new Vector2(ht, 0));
+                            let v3 = storeVertex(vb, vbi, 2, pos, angle, new Vector2(0, -ht));
+
+                            storeIndex(ib, ibi, 0, v1);
+                            storeIndex(ib, ibi, 1, v2);
+                            storeIndex(ib, ibi, 2, v3);
+                        }
+                        break;
+                    }
+                    case Lines2D.RoundCap:
+                    {
+                        if (borderMode) {
+                            let curA = -Math.PI / 2;
+                            let incA = Math.PI / (sd / 2 - 1);
+                            let ii = 0;
+
+                            for (let i = 0; i < (sd / 2); i++) {
+                                let v1 = storeVertex(vb, vbi, i*2 + 0, pos, angle, new Vector2(Math.cos(curA) * ht, Math.sin(curA) * ht));
+                                let v2 = storeVertex(vb, vbi, i*2 + 1, pos, angle, new Vector2(Math.cos(curA) * (ht+bt), Math.sin(curA) * (ht+bt)));
+
+                                if (i > 0) {
+                                    storeIndex(ib, ibi, ii++, v1 - 2);
+                                    storeIndex(ib, ibi, ii++, v2 - 2);
+                                    storeIndex(ib, ibi, ii++, v2);
+
+                                    storeIndex(ib, ibi, ii++, v1 - 2);
+                                    storeIndex(ib, ibi, ii++, v2);
+                                    storeIndex(ib, ibi, ii++, v1);
+                                }
+                                curA += incA;
+                            }
+                        } else {
+                            let c = storeVertex(vb, vbi, 0, pos, angle, new Vector2(0, 0));
+                            let curA = -Math.PI / 2;
+                            let incA = Math.PI / (sd / 2 - 1);
+
+                            storeVertex(vb, vbi, 1, pos, angle, new Vector2(Math.cos(curA) * ht, Math.sin(curA) * ht));
+                            curA += incA;
+                            for (let i = 1; i < (sd / 2); i++) {
+                                let v2 = storeVertex(vb, vbi, i + 1, pos, angle, new Vector2(Math.cos(curA) * ht, Math.sin(curA) * ht));
+
+                                storeIndex(ib, ibi, i * 3 + 0, c);
+                                storeIndex(ib, ibi, i * 3 + 1, v2 - 1);
+                                storeIndex(ib, ibi, i * 3 + 2, v2);
+                                curA += incA;
+                            }
+                        }
+                        break;
+                    }
+                    case Lines2D.SquareAnchorCap:
+                    {
+                        let vi = 0;
+                        let v1 = storeVertex(vb, vbi, vi++, pos, angle, new Vector2(0, t));
+                        let v2 = storeVertex(vb, vbi, vi++, pos, angle, new Vector2(t * 2, t));
+                        let v3 = storeVertex(vb, vbi, vi++, pos, angle, new Vector2(t * 2, -t));
+                        let v4 = storeVertex(vb, vbi, vi++, pos, angle, new Vector2(0, -t));
+
+                        if (borderMode) {
+                            let v5 = storeVertex(vb, vbi, vi++, pos, angle, new Vector2(0, ht+bt));
+                            let v6 = storeVertex(vb, vbi, vi++, pos, angle, new Vector2(-bt, ht+bt));
+                            let v7 = storeVertex(vb, vbi, vi++, pos, angle, new Vector2(-bt, t + bt));
+                            let v8 = storeVertex(vb, vbi, vi++, pos, angle, new Vector2(t * 2 + bt, t + bt));
+
+                            let v9 =  storeVertex(vb, vbi, vi++, pos, angle, new Vector2(t * 2 + bt, -(t+bt)));
+                            let v10 = storeVertex(vb, vbi, vi++, pos, angle, new Vector2(-bt, -(t + bt)));
+                            let v11 = storeVertex(vb, vbi, vi++, pos, angle, new Vector2(-bt, -(ht+bt)));
+                            let v12 = storeVertex(vb, vbi, vi++, pos, angle, new Vector2(0, -(ht+bt)));
+
+                            let ii = 0;
+                            storeIndex(ib, ibi, ii++, v6);  storeIndex(ib, ibi, ii++, v1);  storeIndex(ib, ibi, ii++, v5);
+                            storeIndex(ib, ibi, ii++, v6);  storeIndex(ib, ibi, ii++, v7);  storeIndex(ib, ibi, ii++, v1);
+                            storeIndex(ib, ibi, ii++, v1);  storeIndex(ib, ibi, ii++, v7);  storeIndex(ib, ibi, ii++, v8);
+                            storeIndex(ib, ibi, ii++, v1);  storeIndex(ib, ibi, ii++, v8);  storeIndex(ib, ibi, ii++, v2);
+                            storeIndex(ib, ibi, ii++, v2);  storeIndex(ib, ibi, ii++, v8);  storeIndex(ib, ibi, ii++, v9);
+                            storeIndex(ib, ibi, ii++, v2);  storeIndex(ib, ibi, ii++, v9);  storeIndex(ib, ibi, ii++, v3);
+                            storeIndex(ib, ibi, ii++, v3);  storeIndex(ib, ibi, ii++, v9);  storeIndex(ib, ibi, ii++, v10);
+                            storeIndex(ib, ibi, ii++, v3);  storeIndex(ib, ibi, ii++, v10); storeIndex(ib, ibi, ii++, v4);
+                            storeIndex(ib, ibi, ii++, v10); storeIndex(ib, ibi, ii++, v11); storeIndex(ib, ibi, ii++, v4);
+                            storeIndex(ib, ibi, ii++, v11); storeIndex(ib, ibi, ii++, v12); storeIndex(ib, ibi, ii++, v4);
+
+                        } else {
+                            storeIndex(ib, ibi, 0, v1);
+                            storeIndex(ib, ibi, 1, v2);
+                            storeIndex(ib, ibi, 2, v3);
+
+                            storeIndex(ib, ibi, 3, v1);
+                            storeIndex(ib, ibi, 4, v3);
+                            storeIndex(ib, ibi, 5, v4);
+                        }
+                        break;
+                    }
+                    case Lines2D.RoundAnchorCap:
+                    {
+                        let cpos = Math.sqrt(t * t - ht * ht);
+                        let center = new Vector2(cpos, 0);
+                        let curA = Tools.ToRadians(-150);
+                        let incA = Tools.ToRadians(300) / (sd - 1);
+
+                        if (borderMode) {
+                            let ii = 0;
+
+                            for (let i = 0; i < sd; i++) {
+                                let v1 = storeVertex(vb, vbi, i * 2 + 0, pos, angle, new Vector2(cpos + Math.cos(curA) * t, Math.sin(curA) * t));
+                                let v2 = storeVertex(vb, vbi, i * 2 + 1, pos, angle, new Vector2(cpos + Math.cos(curA) * (t + bt), Math.sin(curA) * (t + bt)));
+
+                                if (i > 0) {
+                                    storeIndex(ib, ibi, ii++, v1 - 2);
+                                    storeIndex(ib, ibi, ii++, v2 - 2);
+                                    storeIndex(ib, ibi, ii++, v2);
+
+                                    storeIndex(ib, ibi, ii++, v1 - 2);
+                                    storeIndex(ib, ibi, ii++, v2);
+                                    storeIndex(ib, ibi, ii++, v1);
+                                }
+                                curA += incA;
+                            }
+                        } else {
+                            let c = storeVertex(vb, vbi, 0, pos, angle, center);
+                            storeVertex(vb, vbi, 1, pos, angle, new Vector2(cpos + Math.cos(curA) * t, Math.sin(curA) * t));
+                            curA += incA;
+                            for (let i = 1; i < sd; i++) {
+                                let v2 = storeVertex(vb, vbi, i + 1, pos, angle, new Vector2(cpos + Math.cos(curA) * t, Math.sin(curA) * t));
+
+                                storeIndex(ib, ibi, i * 3 + 0, c);
+                                storeIndex(ib, ibi, i * 3 + 1, v2 - 1);
+                                storeIndex(ib, ibi, i * 3 + 2, v2);
+                                curA += incA;
+                            }
+                            storeIndex(ib, ibi, sd * 3 + 0, c);
+                            storeIndex(ib, ibi, sd * 3 + 1, c + 1);
+                            storeIndex(ib, ibi, sd * 3 + 2, c + sd);
+                        }
+                        break;
+                    }
+                    case Lines2D.DiamondAnchorCap:
+                    {
+                        let vi = 0;
+                        let v1 = storeVertex(vb, vbi, vi++, pos, angle, new Vector2(0, ht));
+                        let v2 = storeVertex(vb, vbi, vi++, pos, angle, new Vector2(ht, t));
+                        let v3 = storeVertex(vb, vbi, vi++, pos, angle, new Vector2(ht * 3, 0));
+                        let v4 = storeVertex(vb, vbi, vi++, pos, angle, new Vector2(ht, -t));
+                        let v5 = storeVertex(vb, vbi, vi++, pos, angle, new Vector2(0, -ht));
+
+                        if (borderMode) {
+                            let f = Math.sqrt(bt * bt * 2);
+                            let v6 = storeVertex(vb, vbi, vi++, pos, angle, new Vector2(-f,ht));
+                            let v7 = storeVertex(vb, vbi, vi++, pos, angle, new Vector2(ht,t+f));
+                            let v8 = storeVertex(vb, vbi, vi++, pos, angle, new Vector2(ht*3+f,0));
+                            let v9 = storeVertex(vb, vbi, vi++, pos, angle, new Vector2(ht,-(t+f)));
+                            let v10 = storeVertex(vb, vbi, vi++, pos, angle, new Vector2(-f, -ht));
+
+                            let ii = 0;
+                            storeIndex(ib, ibi, ii++, v6); storeIndex(ib, ibi, ii++, v7); storeIndex(ib, ibi, ii++, v1);
+                            storeIndex(ib, ibi, ii++, v1); storeIndex(ib, ibi, ii++, v7); storeIndex(ib, ibi, ii++, v2);
+
+                            storeIndex(ib, ibi, ii++, v2); storeIndex(ib, ibi, ii++, v7); storeIndex(ib, ibi, ii++, v8);
+                            storeIndex(ib, ibi, ii++, v2); storeIndex(ib, ibi, ii++, v8); storeIndex(ib, ibi, ii++, v3);
+
+                            storeIndex(ib, ibi, ii++, v3); storeIndex(ib, ibi, ii++, v8); storeIndex(ib, ibi, ii++, v9);
+                            storeIndex(ib, ibi, ii++, v3); storeIndex(ib, ibi, ii++, v9); storeIndex(ib, ibi, ii++, v4);
+
+                            storeIndex(ib, ibi, ii++, v4); storeIndex(ib, ibi, ii++, v9); storeIndex(ib, ibi, ii++, v10);
+                            storeIndex(ib, ibi, ii++, v4); storeIndex(ib, ibi, ii++, v10); storeIndex(ib, ibi, ii++, v5);
+
+                        } else {
+                            storeIndex(ib, ibi, 0, v1); storeIndex(ib, ibi, 1, v2); storeIndex(ib, ibi, 2, v3);
+                            storeIndex(ib, ibi, 3, v1); storeIndex(ib, ibi, 4, v3); storeIndex(ib, ibi, 5, v5);
+                            storeIndex(ib, ibi, 6, v5); storeIndex(ib, ibi, 7, v3); storeIndex(ib, ibi, 8, v4);
+                        }
+                        break;
+                    }
+                }
+
+                return null;
+            }
+
+            let buildLine = (vb: Float32Array, contour: Vector2[], ht: number, bt?: number) => {
+                let lineA = Vector2.Zero();
+                let lineB = Vector2.Zero();
+                let tangent = Vector2.Zero();
+                let miter = Vector2.Zero();
+                let curNormal: Vector2 = null;
+
+                if (this.closed) {
+                    this.points.push(this.points[0]);
+                }
+
+                var total = this.points.length;
+                for (let i = 1; i < total; i++) {
+                    let last = this.points[i - 1];
+                    let cur = this.points[i];
+                    let next = (i < (this.points.length - 1)) ? this.points[i + 1] : null;
+
+                    direction(cur, last, lineA);
+                    if (!curNormal) {
+                        curNormal = Vector2.Zero();
+                        perp(lineA, curNormal);
+                    }
+
+                    if (i === 1) {
+                        store(vb, contour, 0, total, this.points[0], curNormal, ht, bt);
+                    }
+
+                    if (!next) {
+                        perp(lineA, curNormal);
+                        store(vb, contour, i, total, this.points[i], curNormal, ht, bt, i - 1);
+                    } else {
+                        direction(next, cur, lineB);
+
+                        var miterLen = computeMiter(tangent, miter, lineA, lineB);
+                        store(vb, contour, i, total, this.points[i], miter, miterLen*ht, miterLen*bt, i - 1);
+                    }
+                }
+
+                if (this.points.length > 2 && this.closed) {
+                    let last2 = this.points[total - 2];
+                    let cur2 = this.points[0];
+                    let next2 = this.points[1];
+
+                    direction(cur2, last2, lineA);
+                    direction(next2, cur2, lineB);
+                    perp(lineA, curNormal);
+
+                    var miterLen2 = computeMiter(tangent, miter, lineA, lineB);
+                    store(vb, null, 0, total, this.points[0], miter, miterLen2 * ht, miterLen2 * bt, 1);
+
+                    // Patch contour
+                    if (contour) {
+                        let off = (bt == null) ? 0 : 4;
+                        contour[0].x = vb[off + 0];
+                        contour[0].y = vb[off + 1];
+                        contour[1].x = vb[off + 2];
+                        contour[1].y = vb[off + 3];
+                    }
+                }
+
+                // Remove the point we added at the beginning
+                if (this.closed) {
+                    this.points.splice(total - 1);
+                }
+            }
+
+            let contour = new Array<Vector2>();
+
+            // Need to create WebGL resources for fill part?
+            if (this.fill) {
+                let startCapInfo = getCapSize(this.startCap);
+                let endCapInfo = getCapSize(this.endCap);
+                let count = this.points.length;
+                let vbSize = (count * 2 * 2) + startCapInfo.vbsize + endCapInfo.vbsize;
+                let vb = new Float32Array(vbSize);
+                let ht = this.fillThickness / 2;
+                let total = this.points.length;
+
+                buildLine(vb, this.border ? null : contour, ht);
+
+                let max = total * 2;
+                let triCount = (count - (this.closed ? 0 : 1)) * 2;
+                let ib = new Float32Array(triCount * 3 + startCapInfo.ibsize + endCapInfo.ibsize);
+                for (let i = 0; i < triCount; i+=2) {
+                    ib[i * 3 + 0] = i + 0;
+                    ib[i * 3 + 1] = i + 1;
+                    ib[i * 3 + 2] = (i + 2) % max;
+
+                    ib[i * 3 + 3] = i + 1;
+                    ib[i * 3 + 4] = (i + 3) % max;
+                    ib[i * 3 + 5] = (i + 2) % max;
+                }
+
+                buildCap(vb, count * 2 * 2, ib, triCount * 3, this.points[0], this.fillThickness, null, this.startCap, startDir);
+                buildCap(vb, (count * 2 * 2) + startCapInfo.vbsize, ib, (triCount * 3) + startCapInfo.ibsize, this.points[total - 1], this.fillThickness, null, this.endCap, endDir);
+
+                renderCache.fillVB = engine.createVertexBuffer(vb);
+                renderCache.fillIB = engine.createIndexBuffer(ib);
+                renderCache.fillIndicesCount = ib.length;
+
+                let ei = this.getDataPartEffectInfo(Shape2D.SHAPE2D_FILLPARTID, ["position"]);
+                renderCache.effectFill = engine.createEffect({ vertex: "lines2d", fragment: "lines2d" }, ei.attributes, ei.uniforms, [], ei.defines, null);
+            }
+
+            // Need to create WebGL resources for border part?
+            if (this.border) {
+                let startCapInfo = getCapSize(this.startCap, true);
+                let endCapInfo = getCapSize(this.endCap, true);
+                let count = this.points.length;
+                let vbSize = (count * 2 * 2 * 2) + startCapInfo.vbsize + endCapInfo.vbsize;
+                let vb = new Float32Array(vbSize);
+                let ht = this.fillThickness / 2;
+                let bt = this.borderThickness;
+                let total = this.points.length;
+
+                buildLine(vb, contour, ht, bt);
+
+                let max = total * 2 * 2;
+                let triCount = (count - (this.closed ? 0 : 1)) * 2 * 2;
+                let ib = new Float32Array(triCount * 3 + startCapInfo.ibsize + endCapInfo.ibsize);
+                for (let i = 0; i < triCount; i += 4) {
+                    ib[i * 3 + 0] = i + 0;
+                    ib[i * 3 + 1] = i + 2;
+                    ib[i * 3 + 2] = (i + 6) % max;
+
+                    ib[i * 3 + 3] = i + 0;
+                    ib[i * 3 + 4] = (i + 6) % max;
+                    ib[i * 3 + 5] = (i + 4) % max;
+
+                    ib[i * 3 + 6] = i + 3;
+                    ib[i * 3 + 7] = i + 1;
+                    ib[i * 3 + 8] = (i + 5) % max;
+
+                    ib[i * 3 + 9] = i + 3;
+                    ib[i * 3 + 10] = (i + 5) % max;
+                    ib[i * 3 + 11] = (i + 7) % max;
+                }
+
+                buildCap(vb, count * 2 * 2 * 2, ib, triCount * 3, this.points[0], this.fillThickness, this.borderThickness, this.startCap, startDir);
+                buildCap(vb, (count * 2 * 2 * 2) + startCapInfo.vbsize, ib, (triCount * 3) + startCapInfo.ibsize, this.points[total - 1], this.fillThickness, this.borderThickness, this.endCap, endDir);
+
+                renderCache.borderVB = engine.createVertexBuffer(vb);
+                renderCache.borderIB = engine.createIndexBuffer(ib);
+                renderCache.borderIndicesCount = ib.length;
+
+                let ei = this.getDataPartEffectInfo(Shape2D.SHAPE2D_BORDERPARTID, ["position"]);
+                renderCache.effectBorder = engine.createEffect({ vertex: "lines2d", fragment: "lines2d" }, ei.attributes, ei.uniforms, [], ei.defines, null);
+            }
+
+            this._contour = contour;
+            let bs = this._boundingMax.subtract(this._boundingMin);
+            this._size.width = bs.x;
+            this._size.height = bs.y;
+            return renderCache;
+        }
+
+
+        protected createInstanceDataParts(): InstanceDataBase[] {
+            var res = new Array<InstanceDataBase>();
+            if (this.border) {
+                res.push(new Lines2DInstanceData(Shape2D.SHAPE2D_BORDERPARTID));
+            }
+            if (this.fill) {
+                res.push(new Lines2DInstanceData(Shape2D.SHAPE2D_FILLPARTID));
+            }
+            return res;
+        }
+
+        protected refreshInstanceDataPart(part: InstanceDataBase): boolean {
+            if (!super.refreshInstanceDataPart(part)) {
+                return false;
+            }
+            if (part.id === Shape2D.SHAPE2D_BORDERPARTID) {
+                let d = <Lines2DInstanceData>part;
+                d.boundingMin = this.boundingMin;
+                d.boundingMax = this.boundingMax;
+            }
+            else if (part.id === Shape2D.SHAPE2D_FILLPARTID) {
+                let d = <Lines2DInstanceData>part;
+                d.boundingMin = this.boundingMin;
+                d.boundingMax = this.boundingMax;
+            }
+            return true;
+        }
+
+        private static _noCap            = 0;
+        private static _roundCap         = 1;
+        private static _triangleCap      = 2;
+        private static _squareAnchorCap  = 3;
+        private static _roundAnchorCap   = 4;
+        private static _diamondAnchorCap = 5;
+        private static _arrowCap         = 6;
+
+        private static _roundCapSubDiv = 36;
+
+        private _boundingMin: Vector2;
+        private _boundingMax: Vector2;
+        private _size: Size;
+        private _contour: Vector2[];
+
+        private _closed: boolean;
+        private _startCap: number;
+        private _endCap: number;
+        private _fillThickness: number;
+        private _points: Vector2[];
+
+
+    }
+}

+ 1 - 1
src/Canvas2d/babylon.modelRenderCache.ts

@@ -1,6 +1,6 @@
 module BABYLON {
     export const enum ShaderDataType {
-        Vector2, Vector3, Vector4, Matrix, float, Color3, Color4
+        Vector2, Vector3, Vector4, Matrix, float, Color3, Color4, Size
     }
 
     export class GroupInstanceInfo {

+ 3 - 3
src/Canvas2d/babylon.prim2dBase.js

@@ -230,7 +230,7 @@ var BABYLON;
         function Prim2DBase() {
             _super.apply(this, arguments);
         }
-        Prim2DBase.prototype.setupPrim2DBase = function (owner, parent, id, position, isVisible) {
+        Prim2DBase.prototype.setupPrim2DBase = function (owner, parent, id, position, origin, isVisible) {
             if (isVisible === void 0) { isVisible = true; }
             if (!(this instanceof BABYLON.Group2D) && !(this instanceof BABYLON.Sprite2D && id !== null && id.indexOf("__cachedSpriteOfGroup__") === 0) && (owner.cachingStrategy === BABYLON.Canvas2D.CACHESTRATEGY_TOPLEVELGROUPS) && (parent === owner)) {
                 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");
@@ -265,7 +265,7 @@ var BABYLON;
             this.rotation = 0;
             this.scale = 1;
             this.levelVisible = isVisible;
-            this.origin = new BABYLON.Vector2(0.5, 0.5);
+            this.origin = origin || new BABYLON.Vector2(0.5, 0.5);
         };
         Object.defineProperty(Prim2DBase.prototype, "actionManager", {
             get: function () {
@@ -628,7 +628,7 @@ var BABYLON;
             if (!_super.prototype.dispose.call(this)) {
                 return false;
             }
-            if (!this._actionManager) {
+            if (this._actionManager) {
                 this._actionManager.dispose();
                 this._actionManager = null;
             }

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

@@ -296,7 +296,7 @@
     export class Prim2DBase extends SmartPropertyPrim {
         static PRIM2DBASE_PROPCOUNT: number = 10;
 
-        protected setupPrim2DBase(owner: Canvas2D, parent: Prim2DBase, id: string, position: Vector2, isVisible: boolean = true) {
+        protected setupPrim2DBase(owner: Canvas2D, parent: Prim2DBase, id: string, position: Vector2, origin: Vector2, isVisible: boolean = true) {
             if (!(this instanceof Group2D) && !(this instanceof Sprite2D && id !== null && id.indexOf("__cachedSpriteOfGroup__") === 0) && (owner.cachingStrategy === Canvas2D.CACHESTRATEGY_TOPLEVELGROUPS) && (parent === owner)) {
                 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");
             }
@@ -333,7 +333,7 @@
             this.rotation = 0;
             this.scale = 1;
             this.levelVisible = isVisible;
-            this.origin = new Vector2(0.5, 0.5);
+            this.origin = origin || new Vector2(0.5, 0.5);
         }
 
 
@@ -728,7 +728,7 @@
                 return false;
             }
 
-            if (!this._actionManager) {
+            if (this._actionManager) {
                 this._actionManager.dispose();
                 this._actionManager = null;
             }

+ 27 - 15
src/Canvas2d/babylon.rectangle2d.js

@@ -187,29 +187,41 @@ var BABYLON;
         Rectangle2D.prototype.updateLevelBoundingInfo = function () {
             BABYLON.BoundingInfo2D.CreateFromSizeToRef(this.size, this._levelBoundingInfo, this.origin);
         };
-        Rectangle2D.prototype.setupRectangle2D = function (owner, parent, id, position, size, roundRadius, fill, border, borderThickness) {
+        Rectangle2D.prototype.setupRectangle2D = function (owner, parent, id, position, origin, size, roundRadius, fill, border, borderThickness) {
             if (roundRadius === void 0) { roundRadius = 0; }
             if (borderThickness === void 0) { borderThickness = 1; }
-            this.setupShape2D(owner, parent, id, position, true, fill, border, borderThickness);
+            this.setupShape2D(owner, parent, id, position, origin, true, fill, border, borderThickness);
             this.size = size;
             this.notRounded = !roundRadius;
             this.roundRadius = roundRadius;
         };
-        Rectangle2D.Create = function (parent, id, x, y, width, height, fill, border) {
+        /**
+         * Create an Rectangle 2D Shape primitive. May be a sharp rectangle (with sharp corners), or a rounded one.
+         * @param parent the parent primitive, must be a valid primitive (or the Canvas)
+         * options:
+         *  - id a text identifier, for information purpose
+         *  - x: the X position relative to its parent, default is 0
+         *  - y: the Y position relative to its parent, default is 0
+         *  - origin: define the normalized origin point location, default [0.5;0.5]
+         *  - width: the width of the rectangle, default is 10
+         *  - height: the height of the rectangle, default is 10
+         *  - roundRadius: if the rectangle has rounded corner, set their radius, default is 0 (to get a sharp rectangle).
+         *  - fill: the brush used to draw the fill content of the ellipse, you can set null to draw nothing (but you will have to set a border brush), default is a SolidColorBrush of plain white.
+         *  - border: the brush used to draw the border of the ellipse, you can set null to draw nothing (but you will have to set a fill brush), default is null.
+         *  - borderThickness: the thickness of the drawn border, default is 1.
+         */
+        Rectangle2D.Create = function (parent, options) {
             BABYLON.Prim2DBase.CheckParent(parent);
             var rect = new Rectangle2D();
-            rect.setupRectangle2D(parent.owner, parent, id, new BABYLON.Vector2(x, y), new BABYLON.Size(width, height), null);
-            rect.fill = fill;
-            rect.border = border;
-            return rect;
-        };
-        Rectangle2D.CreateRounded = function (parent, id, x, y, width, height, roundRadius, fill, border) {
-            if (roundRadius === void 0) { roundRadius = 0; }
-            BABYLON.Prim2DBase.CheckParent(parent);
-            var rect = new Rectangle2D();
-            rect.setupRectangle2D(parent.owner, parent, id, new BABYLON.Vector2(x, y), new BABYLON.Size(width, height), roundRadius);
-            rect.fill = fill || BABYLON.Canvas2D.GetSolidColorBrushFromHex("#FFFFFFFF");
-            rect.border = border;
+            rect.setupRectangle2D(parent.owner, parent, options && options.id || null, new BABYLON.Vector2(options && options.x || 0, options && options.y || 0), options && options.origin || null, new BABYLON.Size(options && options.width || 10, options && options.height || 10), options && options.roundRadius || 0);
+            if (options && options.fill !== undefined) {
+                rect.fill = options.fill;
+            }
+            else {
+                rect.fill = BABYLON.Canvas2D.GetSolidColorBrushFromHex("#FFFFFFFF");
+            }
+            rect.border = options && options.border || null;
+            rect.borderThickness = options && options.borderThickness || 1;
             return rect;
         };
         Rectangle2D.prototype.createModelRenderCache = function (modelKey, isTransparent) {

+ 26 - 15
src/Canvas2d/babylon.rectangle2d.ts

@@ -195,30 +195,41 @@
             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) {
-            this.setupShape2D(owner, parent, id, position, true, fill, border, borderThickness);
+        protected setupRectangle2D(owner: Canvas2D, parent: Prim2DBase, id: string, position: Vector2, origin: Vector2, size: Size, roundRadius = 0, fill?: IBrush2D, border?: IBrush2D, borderThickness: number = 1) {
+            this.setupShape2D(owner, parent, id, position, origin, true, fill, border, borderThickness);
             this.size = size;
             this.notRounded = !roundRadius;
             this.roundRadius = roundRadius;
         }
 
-        public static Create(parent: Prim2DBase, id: string, x: number, y: number, width: number, height: number, fill?: IBrush2D, border?: IBrush2D): Rectangle2D {
+        /**
+         * Create an Rectangle 2D Shape primitive. May be a sharp rectangle (with sharp corners), or a rounded one.
+         * @param parent the parent primitive, must be a valid primitive (or the Canvas)
+         * options:
+         *  - id a text identifier, for information purpose
+         *  - x: the X position relative to its parent, default is 0
+         *  - y: the Y position relative to its parent, default is 0
+         *  - origin: define the normalized origin point location, default [0.5;0.5]
+         *  - width: the width of the rectangle, default is 10
+         *  - height: the height of the rectangle, default is 10
+         *  - roundRadius: if the rectangle has rounded corner, set their radius, default is 0 (to get a sharp rectangle).
+         *  - fill: the brush used to draw the fill content of the ellipse, you can set null to draw nothing (but you will have to set a border brush), default is a SolidColorBrush of plain white.
+         *  - border: the brush used to draw the border of the ellipse, you can set null to draw nothing (but you will have to set a fill brush), default is null.
+         *  - borderThickness: the thickness of the drawn border, default is 1.
+         */
+        public static Create(parent: Prim2DBase, options: { id?: string, x?: number, y?: number, origin?: Vector2, width?: number, height?: number, roundRadius?: number, fill?: IBrush2D, border?: IBrush2D, borderThickness?: number}): Rectangle2D {
             Prim2DBase.CheckParent(parent);
 
             let rect = new Rectangle2D();
-            rect.setupRectangle2D(parent.owner, parent, id, new Vector2(x, y), new Size(width, height), null);
-            rect.fill = fill;
-            rect.border = border;
-            return rect;
-        }
-
-        public static CreateRounded(parent: Prim2DBase, id: string, x: number, y: number, width: number, height: number, roundRadius = 0, fill?: IBrush2D, border?: IBrush2D): Rectangle2D {
-            Prim2DBase.CheckParent(parent);
+            rect.setupRectangle2D(parent.owner, parent, options && options.id || null, new Vector2(options && options.x || 0, options && options.y || 0), options && options.origin || null, new Size(options && options.width || 10, options && options.height || 10), options && options.roundRadius || 0);
 
-            let rect = new Rectangle2D();
-            rect.setupRectangle2D(parent.owner, parent, id, new Vector2(x, y), new Size(width, height), roundRadius);
-            rect.fill = fill || Canvas2D.GetSolidColorBrushFromHex("#FFFFFFFF");
-            rect.border = border;
+            if (options && options.fill !== undefined) {
+                rect.fill = options.fill;
+            } else {
+                rect.fill = Canvas2D.GetSolidColorBrushFromHex("#FFFFFFFF");                
+            }
+            rect.border = options && options.border || null;
+            rect.borderThickness = options && options.borderThickness || 1;
             return rect;
         }
 

+ 42 - 6
src/Canvas2d/babylon.renderablePrim2d.js

@@ -27,7 +27,7 @@ var BABYLON;
             }
         };
         InstanceClassInfo.prototype.getInstancingAttributeInfos = function (effect, categories) {
-            var catInline = categories.join(";");
+            var catInline = ";" + categories.join(";") + ";";
             var res = new Array();
             var curInfo = this;
             while (curInfo) {
@@ -76,7 +76,6 @@ var BABYLON;
     })();
     BABYLON.InstanceClassInfo = InstanceClassInfo;
     var InstancePropInfo = (function () {
-        //uniformLocation: WebGLUniformLocation;
         function InstancePropInfo() {
             this.instanceOffset = new BABYLON.StringDictionary();
         }
@@ -114,6 +113,11 @@ var BABYLON;
                 this.dataType = 6 /* Color4 */;
                 return;
             }
+            if (val instanceof BABYLON.Size) {
+                this.size = 8;
+                this.dataType = 7 /* Size */;
+                return;
+            }
             return;
         };
         InstancePropInfo.prototype.writeData = function (array, offset, val) {
@@ -173,6 +177,13 @@ var BABYLON;
                         }
                         break;
                     }
+                case 7 /* Size */:
+                    {
+                        var s = val;
+                        array[offset + 0] = s.width;
+                        array[offset + 1] = s.height;
+                        break;
+                    }
             }
         };
         return InstancePropInfo;
@@ -191,11 +202,19 @@ var BABYLON;
             info = new InstancePropInfo();
             info.attributeName = shaderAttributeName;
             info.category = category || null;
+            if (info.category) {
+                info.delimitedCategory = ";" + info.category + ";";
+            }
             node.levelContent.add(instanceDataName, info);
             descriptor.get = function () {
                 return null;
             };
             descriptor.set = function (val) {
+                // Check that we're not trying to set a property that belongs to a category that is not allowed (current)
+                // Quit if it's the case, otherwise we could overwrite data somewhere...
+                if (info.category && InstanceClassInfo._CurCategories.indexOf(info.delimitedCategory) === -1) {
+                    return;
+                }
                 if (!info.size) {
                     info.setSize(val);
                     node.classContent.mapProperty(info, true);
@@ -317,8 +336,8 @@ var BABYLON;
             enumerable: true,
             configurable: true
         });
-        RenderablePrim2D.prototype.setupRenderablePrim2D = function (owner, parent, id, position, isVisible) {
-            this.setupPrim2DBase(owner, parent, id, position);
+        RenderablePrim2D.prototype.setupRenderablePrim2D = function (owner, parent, id, position, origin, isVisible) {
+            this.setupPrim2DBase(owner, parent, id, position, origin);
             this._isTransparent = false;
         };
         RenderablePrim2D.prototype.dispose = function () {
@@ -391,7 +410,7 @@ var BABYLON;
                         this.isVisible = true;
                         // We manually trigger refreshInstanceData for the only sake of evaluating each instance property size and offset in the instance data, this can only be made at runtime. Once it's done we have all the information to create the instance data buffer.
                         //console.log("Build Prop Layout for " + Tools.getClassName(this._instanceDataParts[0]));
-                        var joinCat = cat.join(";");
+                        var joinCat = ";" + cat.join(";") + ";";
                         joinedUsedCatList.push(joinCat);
                         InstanceClassInfo._CurCategories = joinCat;
                         var obj = this.beforeRefreshForLayoutConstruction(dataPart);
@@ -432,7 +451,7 @@ var BABYLON;
                         gii._partIndexFromId.add(this._modelRenderCache._partIdList[j].toString(), j);
                         for (var _a = 0, _b = this._instanceDataParts; _a < _b.length; _a++) {
                             var part = _b[_a];
-                            gii._instancesPartsUsedShaderCategories[gii._partIndexFromId.get(part.id.toString())] = this.getUsedShaderCategories(part).join(";");
+                            gii._instancesPartsUsedShaderCategories[gii._partIndexFromId.get(part.id.toString())] = ";" + this.getUsedShaderCategories(part).join(";") + ";";
                         }
                     }
                 }
@@ -477,6 +496,23 @@ var BABYLON;
                 this._visibilityChanged = false; // Reset the flag as we've handled the case
             }
         };
+        /**
+         * Transform a given point using the Primitive's origin setting.
+         * This method requires the Primitive's actualSize to be accurate
+         * @param p the point to transform
+         * @param originOffset an offset applied on the current origin before performing the transformation. Depending on which frame of reference your data is expressed you may have to apply a offset. (if you data is expressed from the bottom/left, no offset is required. If it's expressed from the center the a [-0.5;-0.5] offset has to be applied.
+         * @param res an allocated Vector2 that will receive the transformed content
+         */
+        RenderablePrim2D.prototype.transformPointWithOriginByRef = function (p, originOffset, res) {
+            var actualSize = this.actualSize;
+            res.x = p.x - ((this.origin.x + (originOffset ? originOffset.x : 0)) * actualSize.width);
+            res.y = p.y - ((this.origin.y + (originOffset ? originOffset.y : 0)) * actualSize.height);
+        };
+        RenderablePrim2D.prototype.transformPointWithOrigin = function (p, originOffset) {
+            var res = new BABYLON.Vector2(0, 0);
+            this.transformPointWithOriginByRef(p, originOffset, res);
+            return res;
+        };
         RenderablePrim2D.prototype.getDataPartEffectInfo = function (dataPartId, vertexBufferAttributes) {
             var dataPart = BABYLON.Tools.first(this._instanceDataParts, function (i) { return i.id === dataPartId; });
             if (!dataPart) {

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

@@ -19,7 +19,7 @@
         }
 
         getInstancingAttributeInfos(effect: Effect, categories: string[]): InstancingAttributeInfo[] {
-            let catInline = categories.join(";");
+            let catInline = ";" + categories.join(";") + ";";
             let res = new Array<InstancingAttributeInfo>();
             let curInfo: InstanceClassInfo = this;
             while (curInfo) {
@@ -82,6 +82,8 @@
         dataType: ShaderDataType;
         //uniformLocation: WebGLUniformLocation;
 
+        delimitedCategory: string;
+
         constructor() {
             this.instanceOffset = new StringDictionary<number>();
         }
@@ -120,7 +122,11 @@
                 this.dataType = ShaderDataType.Color4;
                 return;
             }
-            return;
+            if (val instanceof Size) {
+                this.size = 8;
+                this.dataType = ShaderDataType.Size;
+                return;
+            }            return;
         }
 
         writeData(array: Float32Array, offset: number, val) {
@@ -180,6 +186,13 @@
                         }
                         break;
                     }
+                case ShaderDataType.Size:
+                    {
+                        let s = <Size>val;
+                        array[offset + 0] = s.width;
+                        array[offset + 1] = s.height;
+                        break;
+                    }
             }
         }
     }
@@ -201,6 +214,9 @@
             info = new InstancePropInfo();
             info.attributeName = shaderAttributeName;
             info.category = category || null;
+            if (info.category) {
+                info.delimitedCategory = ";" + info.category + ";";
+            }
 
             node.levelContent.add(instanceDataName, info);
 
@@ -209,6 +225,11 @@
             }
 
             descriptor.set = function (val) {
+                // Check that we're not trying to set a property that belongs to a category that is not allowed (current)
+                // Quit if it's the case, otherwise we could overwrite data somewhere...
+                if (info.category && InstanceClassInfo._CurCategories.indexOf(info.delimitedCategory) === -1) {
+                    return;
+                }
                 if (!info.size) {
                     info.setSize(val);
                     node.classContent.mapProperty(info, true);
@@ -322,8 +343,8 @@
             this._isTransparent = value;
         }
 
-        setupRenderablePrim2D(owner: Canvas2D, parent: Prim2DBase, id: string, position: Vector2, isVisible: boolean) {
-            this.setupPrim2DBase(owner, parent, id, position);
+        setupRenderablePrim2D(owner: Canvas2D, parent: Prim2DBase, id: string, position: Vector2, origin: Vector2, isVisible: boolean) {
+            this.setupPrim2DBase(owner, parent, id, position, origin);
             this._isTransparent = false;
         }
 
@@ -408,7 +429,7 @@
                         this.isVisible = true;
                         // We manually trigger refreshInstanceData for the only sake of evaluating each instance property size and offset in the instance data, this can only be made at runtime. Once it's done we have all the information to create the instance data buffer.
                         //console.log("Build Prop Layout for " + Tools.getClassName(this._instanceDataParts[0]));
-                        let joinCat = cat.join(";");
+                        let joinCat = ";" + cat.join(";") + ";";
                         joinedUsedCatList.push(joinCat);
                         InstanceClassInfo._CurCategories = joinCat;
                         let obj = this.beforeRefreshForLayoutConstruction(dataPart);
@@ -451,7 +472,7 @@
                         gii._partIndexFromId.add(this._modelRenderCache._partIdList[j].toString(), j);
 
                         for (let part of this._instanceDataParts) {
-                            gii._instancesPartsUsedShaderCategories[gii._partIndexFromId.get(part.id.toString())] = this.getUsedShaderCategories(part).join(";");
+                            gii._instancesPartsUsedShaderCategories[gii._partIndexFromId.get(part.id.toString())] = ";" + this.getUsedShaderCategories(part).join(";") + ";";
                         }
                     }
                 }
@@ -506,6 +527,25 @@
             }
         }
 
+        /**
+         * Transform a given point using the Primitive's origin setting.
+         * This method requires the Primitive's actualSize to be accurate
+         * @param p the point to transform
+         * @param originOffset an offset applied on the current origin before performing the transformation. Depending on which frame of reference your data is expressed you may have to apply a offset. (if you data is expressed from the bottom/left, no offset is required. If it's expressed from the center the a [-0.5;-0.5] offset has to be applied.
+         * @param res an allocated Vector2 that will receive the transformed content
+         */
+        protected transformPointWithOriginByRef(p: Vector2, originOffset:Vector2, res: Vector2) {
+            let actualSize = this.actualSize;
+            res.x = p.x - ((this.origin.x + (originOffset ? originOffset.x : 0)) * actualSize.width);
+            res.y = p.y - ((this.origin.y + (originOffset ? originOffset.y : 0)) * actualSize.height);
+        }
+
+        protected transformPointWithOrigin(p: Vector2, originOffset: Vector2): Vector2 {
+            let res = new Vector2(0, 0);
+            this.transformPointWithOriginByRef(p, originOffset, res);
+            return res;
+        }
+
         protected getDataPartEffectInfo(dataPartId: number, vertexBufferAttributes: string[]): { attributes: string[], uniforms: string[], defines: string } {
             let dataPart = Tools.first(this._instanceDataParts, i => i.id === dataPartId);
             if (!dataPart) {

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

@@ -48,9 +48,9 @@ var BABYLON;
             enumerable: true,
             configurable: true
         });
-        Shape2D.prototype.setupShape2D = function (owner, parent, id, position, isVisible, fill, border, borderThickness) {
+        Shape2D.prototype.setupShape2D = function (owner, parent, id, position, origin, isVisible, fill, border, borderThickness) {
             if (borderThickness === void 0) { borderThickness = 1.0; }
-            this.setupRenderablePrim2D(owner, parent, id, position, isVisible);
+            this.setupRenderablePrim2D(owner, parent, id, position, origin, isVisible);
             this.border = border;
             this.fill = fill;
             this.borderThickness = borderThickness;

+ 2 - 4
src/Canvas2d/babylon.shape2d.ts

@@ -44,8 +44,8 @@
             this._borderThickness = value;
         }
 
-        setupShape2D(owner: Canvas2D, parent: Prim2DBase, id: string, position: Vector2, isVisible: boolean, fill: IBrush2D, border: IBrush2D, borderThickness: number = 1.0) {
-            this.setupRenderablePrim2D(owner, parent, id, position, isVisible);
+        setupShape2D(owner: Canvas2D, parent: Prim2DBase, id: string, position: Vector2, origin: Vector2, isVisible: boolean, fill: IBrush2D, border: IBrush2D, borderThickness: number = 1.0) {
+            this.setupRenderablePrim2D(owner, parent, id, position, origin, isVisible);
             this.border = border;
             this.fill = fill;
             this.borderThickness = borderThickness;
@@ -123,8 +123,6 @@
                         d.borderGradientTY = ty;
                     }
                 }
-
-
             }
 
             return true;

+ 13 - 0
src/Canvas2d/babylon.smartPropertyPrim.js

@@ -190,6 +190,16 @@ var BABYLON;
                 propDic.forEach(function (k, v) {
                     if (v.kind === Prim2DPropInfo.PROPKIND_MODEL) {
                         var propVal = _this[v.name];
+                        // Special case, array, this WON'T WORK IN ALL CASES, all entries have to be of the same type and it must be a BJS well known one
+                        if (propVal && propVal.constructor === Array) {
+                            var firstVal = propVal[0];
+                            if (!firstVal) {
+                                propVal = 0;
+                            }
+                            else {
+                                propVal = BABYLON.Tools.hashCodeFromStream(BABYLON.Tools.arrayOrStringFeeder(propVal));
+                            }
+                        }
                         modelKey += v.name + ":" + ((propVal != null) ? ((v.typeLevelCompare) ? BABYLON.Tools.getClassName(propVal) : propVal.toString()) : "[null]") + ";";
                     }
                 });
@@ -350,6 +360,9 @@ var BABYLON;
             this._instanceDirtyFlags &= ~flags;
             return this._instanceDirtyFlags;
         };
+        SmartPropertyPrim.prototype._resetPropertiesDirty = function () {
+            this._instanceDirtyFlags = 0;
+        };
         Object.defineProperty(SmartPropertyPrim.prototype, "levelBoundingInfo", {
             /**
              * Retrieve the boundingInfo for this Primitive, computed based on the primitive itself and NOT its children

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

@@ -205,6 +205,17 @@
             propDic.forEach((k, v) => {
                 if (v.kind === Prim2DPropInfo.PROPKIND_MODEL) {
                     let propVal = this[v.name];
+
+                    // Special case, array, this WON'T WORK IN ALL CASES, all entries have to be of the same type and it must be a BJS well known one
+                    if (propVal && propVal.constructor === Array) {
+                        let firstVal = propVal[0];
+                        if (!firstVal) {
+                            propVal = 0;
+                        } else {
+                            propVal = Tools.hashCodeFromStream(Tools.arrayOrStringFeeder(propVal));
+                        }
+                    }
+
                     modelKey += v.name + ":" + ((propVal != null) ? ((v.typeLevelCompare) ? Tools.getClassName(propVal) : propVal.toString()) : "[null]") + ";";
                 }
             });
@@ -383,6 +394,10 @@
             return this._instanceDirtyFlags;
         }
 
+        public _resetPropertiesDirty() {
+            this._instanceDirtyFlags = 0;
+        }
+
         /**
          * Retrieve the boundingInfo for this Primitive, computed based on the primitive itself and NOT its children
          * @returns {} 
@@ -445,8 +460,8 @@
 
         private _modelKey; string;
         private _propInfo: StringDictionary<Prim2DPropInfo>;
-        private _levelBoundingInfoDirty: boolean;
         private _isDisposed: boolean;
+        protected _levelBoundingInfoDirty: boolean;
         protected _levelBoundingInfo: BoundingInfo2D;
         protected _boundingInfo: BoundingInfo2D;
         protected _modelDirty: boolean;

+ 25 - 9
src/Canvas2d/babylon.sprite2d.js

@@ -205,27 +205,43 @@ var BABYLON;
             // 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);
+        Sprite2D.prototype.setupSprite2D = function (owner, parent, id, position, origin, texture, spriteSize, spriteLocation, invertY) {
+            this.setupRenderablePrim2D(owner, parent, id, position, origin, true);
             this.texture = texture;
             this.texture.wrapU = BABYLON.Texture.CLAMP_ADDRESSMODE;
             this.texture.wrapV = BABYLON.Texture.CLAMP_ADDRESSMODE;
-            this.spriteSize = spriteSize;
-            this.spriteLocation = spriteLocation;
+            this.spriteSize = spriteSize || null;
+            this.spriteLocation = spriteLocation || new BABYLON.Vector2(0, 0);
             this.spriteFrame = 0;
             this.invertY = invertY;
             this._isTransparent = true;
+            if (!this.spriteSize) {
+                var s = texture.getSize();
+                this.spriteSize = new BABYLON.Size(s.width, s.height);
+            }
         };
-        Sprite2D.Create = function (parent, id, x, y, texture, spriteSize, spriteLocation, invertY) {
-            if (invertY === void 0) { invertY = false; }
+        /**
+         * Create an 2D Sprite primitive
+         * @param parent the parent primitive, must be a valid primitive (or the Canvas)
+         * @param texture the texture that stores the sprite to render
+         * options:
+         *  - id a text identifier, for information purpose
+         *  - x: the X position relative to its parent, default is 0
+         *  - y: the Y position relative to its parent, default is 0
+         *  - origin: define the normalized origin point location, default [0.5;0.5]
+         *  - spriteSize: the size of the sprite, if null the size of the given texture will be used, default is null.
+         *  - spriteLocation: the location in the texture of the top/left corner of the Sprite to display, default is null (0,0)
+         *  - invertY: if true the texture Y will be inverted, default is false.
+         */
+        Sprite2D.Create = function (parent, texture, options) {
             BABYLON.Prim2DBase.CheckParent(parent);
             var sprite = new Sprite2D();
-            sprite.setupSprite2D(parent.owner, parent, id, new BABYLON.Vector2(x, y), texture, spriteSize, spriteLocation, invertY);
+            sprite.setupSprite2D(parent.owner, parent, options && options.id || null, new BABYLON.Vector2(options && options.x || 0, options && options.y || 0), options && options.origin || null, texture, options && options.spriteSize || null, options && options.spriteLocation || null, options && options.invertY || false);
             return sprite;
         };
         Sprite2D._createCachedCanvasSprite = function (owner, texture, size, pos) {
             var sprite = new Sprite2D();
-            sprite.setupSprite2D(owner, null, "__cachedCanvasSprite__", new BABYLON.Vector2(0, 0), texture, size, pos, false);
+            sprite.setupSprite2D(owner, null, "__cachedCanvasSprite__", new BABYLON.Vector2(0, 0), null, texture, size, pos, false);
             return sprite;
         };
         Sprite2D.prototype.createModelRenderCache = function (modelKey, isTransparent) {
@@ -264,7 +280,7 @@ var BABYLON;
             }
             if (part.id === Sprite2D.SPRITE2D_MAINPARTID) {
                 var d = this._instanceDataParts[0];
-                var ts = this.texture.getSize();
+                var ts = this.texture.getBaseSize();
                 var sl = this.spriteLocation;
                 var ss = this.spriteSize;
                 d.topLeftUV = new BABYLON.Vector2(sl.x / ts.width, sl.y / ts.height);

+ 26 - 8
src/Canvas2d/babylon.sprite2d.ts

@@ -179,30 +179,48 @@
             return true;
         }
 
-        protected setupSprite2D(owner: Canvas2D, parent: Prim2DBase, id: string, position: Vector2, texture: Texture, spriteSize: Size, spriteLocation: Vector2, invertY: boolean) {
-            this.setupRenderablePrim2D(owner, parent, id, position, true);
+        protected setupSprite2D(owner: Canvas2D, parent: Prim2DBase, id: string, position: Vector2, origin: Vector2, texture: Texture, spriteSize: Size, spriteLocation: Vector2, invertY: boolean) {
+            this.setupRenderablePrim2D(owner, parent, id, position, origin, true);
             this.texture = texture;
             this.texture.wrapU = Texture.CLAMP_ADDRESSMODE;
             this.texture.wrapV = Texture.CLAMP_ADDRESSMODE;
-            this.spriteSize = spriteSize;
-            this.spriteLocation = spriteLocation;
+            this.spriteSize = spriteSize || null;
+            this.spriteLocation = spriteLocation || new Vector2(0,0);
             this.spriteFrame = 0;
             this.invertY = invertY;
             this._isTransparent = true;
+
+            if (!this.spriteSize) {
+                var s = texture.getSize();
+                this.spriteSize = new Size(s.width, s.height);
+            }
         }
 
-        public static Create(parent: Prim2DBase, id: string, x: number, y: number, texture: Texture, spriteSize: Size, spriteLocation: Vector2, invertY: boolean = false): Sprite2D {
+        /**
+         * Create an 2D Sprite primitive
+         * @param parent the parent primitive, must be a valid primitive (or the Canvas)
+         * @param texture the texture that stores the sprite to render
+         * options:
+         *  - id a text identifier, for information purpose
+         *  - x: the X position relative to its parent, default is 0
+         *  - y: the Y position relative to its parent, default is 0
+         *  - origin: define the normalized origin point location, default [0.5;0.5]
+         *  - spriteSize: the size of the sprite, if null the size of the given texture will be used, default is null.
+         *  - spriteLocation: the location in the texture of the top/left corner of the Sprite to display, default is null (0,0)
+         *  - invertY: if true the texture Y will be inverted, default is false.
+         */
+        public static Create(parent: Prim2DBase, texture: Texture, options: { id?: string, x?: number, y?: number, origin?: Vector2, spriteSize?: Size, spriteLocation?: Vector2, invertY?: boolean}): Sprite2D {
             Prim2DBase.CheckParent(parent);
 
             let sprite = new Sprite2D();
-            sprite.setupSprite2D(parent.owner, parent, id, new Vector2(x, y), texture, spriteSize, spriteLocation, invertY);
+            sprite.setupSprite2D(parent.owner, parent, options && options.id || null, new Vector2(options && options.x || 0, options && options.y || 0), options && options.origin || null, texture, options && options.spriteSize || null, options && options.spriteLocation || null, options && options.invertY || false);
             return sprite;
         }
 
         static _createCachedCanvasSprite(owner: Canvas2D, texture: MapTexture, size: Size, pos: Vector2): Sprite2D {
 
             let sprite = new Sprite2D();
-            sprite.setupSprite2D(owner, null, "__cachedCanvasSprite__", new Vector2(0, 0), texture, size, pos, false);
+            sprite.setupSprite2D(owner, null, "__cachedCanvasSprite__", new Vector2(0, 0), null, texture, size, pos, false);
 
             return sprite;
         }
@@ -253,7 +271,7 @@
 
             if (part.id === Sprite2D.SPRITE2D_MAINPARTID) {
                 let d = <Sprite2DInstanceData>this._instanceDataParts[0];
-                let ts = this.texture.getSize();
+                let ts = this.texture.getBaseSize();
                 let sl = this.spriteLocation;
                 let ss = this.spriteSize;
                 d.topLeftUV = new Vector2(sl.x / ts.width, sl.y / ts.height);

Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 20 - 7
src/Canvas2d/babylon.text2d.js


+ 20 - 4
src/Canvas2d/babylon.text2d.ts

@@ -216,8 +216,8 @@
             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) {
-            this.setupRenderablePrim2D(owner, parent, id, position, true);
+        protected setupText2D(owner: Canvas2D, parent: Prim2DBase, id: string, position: Vector2, origin: Vector2, fontName: string, text: string, areaSize: Size, defaultFontColor: Color4, vAlign, hAlign, tabulationSize: number) {
+            this.setupRenderablePrim2D(owner, parent, id, position, origin, true);
 
             this.fontName = fontName;
             this.defaultFontColor = defaultFontColor;
@@ -229,11 +229,27 @@
             this._isTransparent = true;
         }
 
-        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 {
+        /**
+         * Create a Text primitive
+         * @param parent the parent primitive, must be a valid primitive (or the Canvas)
+         * @param text the text to display
+         * Options:
+         *  - id a text identifier, for information purpose
+         *  - x: the X position relative to its parent, default is 0
+         *  - y: the Y position relative to its parent, default is 0
+         *  - origin: define the normalized origin point location, default [0.5;0.5]
+         *  - fontName: the name/size/style of the font to use, following the CSS notation. Default is "12pt Arial".
+         *  - defaultColor: the color by default to apply on each letter of the text to display, default is plain white.
+         *  - areaSize: the size of the area in which to display the text, default is auto-fit from text content.
+         *  - vAlign: vertical alignment (areaSize must be specified), default is Text2D.TEXT2D_VALIGN_CENTER
+         *  - hAlign: horizontal alignment (areaSize must be specified), default is Text2D.TEXT2D_HALIGN_CENTER
+         *  - tabulationSize: number of space character to insert when a tabulation is encountered, default is 4
+         */
+        public static Create(parent: Prim2DBase, text: string, options?: { id?: string, x?: number, y?: number, origin?:Vector2, fontName?: string, defaultFontColor?: Color4, areaSize?: Size, vAlign?: number, hAlign?: number, tabulationSize?: number}): Text2D {
             Prim2DBase.CheckParent(parent);
 
             let text2d = new Text2D();
-            text2d.setupText2D(parent.owner, parent, id, new Vector2(x, y), fontName, text, areaSize, defaultFontColor || new Color4(0,0,0,1), vAlign, hAlign, tabulationSize);
+            text2d.setupText2D(parent.owner, parent, options && options.id || null, new Vector2(options && options.x || 0, options && options.y || 0), options && options.origin || null, options && options.fontName || "12pt Arial", text, options && options.areaSize, options && options.defaultFontColor || new Color4(1, 1, 1, 1), options && options.vAlign || Text2D.TEXT2D_VALIGN_CENTER, options && options.hAlign || Text2D.TEXT2D_HALIGN_CENTER, options && options.tabulationSize || 4);
             return text2d;
         }
 

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

@@ -8,20 +8,20 @@ var BABYLON;
     /**
      * This is the class that is used to display a World Space Canvas into a scene
      */
-    var WorldSpaceCanvas2d = (function (_super) {
-        __extends(WorldSpaceCanvas2d, _super);
-        function WorldSpaceCanvas2d(name, scene, canvas) {
+    var WorldSpaceCanvas2D = (function (_super) {
+        __extends(WorldSpaceCanvas2D, _super);
+        function WorldSpaceCanvas2D(name, scene, canvas) {
             _super.call(this, name, scene);
             this._canvas = canvas;
         }
-        WorldSpaceCanvas2d.prototype.dispose = function () {
+        WorldSpaceCanvas2D.prototype.dispose = function () {
             _super.prototype.dispose.call(this);
             if (this._canvas) {
                 this._canvas.dispose();
                 this._canvas = null;
             }
         };
-        return WorldSpaceCanvas2d;
+        return WorldSpaceCanvas2D;
     })(BABYLON.Mesh);
-    BABYLON.WorldSpaceCanvas2d = WorldSpaceCanvas2d;
+    BABYLON.WorldSpaceCanvas2D = WorldSpaceCanvas2D;
 })(BABYLON || (BABYLON = {}));

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

@@ -2,7 +2,7 @@
     /**
      * This is the class that is used to display a World Space Canvas into a scene
      */
-    export class WorldSpaceCanvas2d extends Mesh {
+    export class WorldSpaceCanvas2D extends Mesh {
         constructor(name: string, scene: Scene, canvas: Canvas2D) {
             super(name, scene);
 

+ 1 - 1
src/Materials/Textures/Procedurals/babylon.proceduralTexture.ts

@@ -288,7 +288,7 @@
             // Matrix      
             for (name in this._matrices) {
                 this._effect.setMatrix(name, this._matrices[name]);
-            }
+            }            
 
             // VBOs
             engine.bindBuffers(this._vertexBuffer, this._indexBuffer, this._vertexDeclaration, this._vertexStrideSize, this._effect);

+ 112 - 0
src/Math/babylon.math.js

@@ -46,6 +46,15 @@ var BABYLON;
         Color3.prototype.toString = function () {
             return "{R: " + this.r + " G:" + this.g + " B:" + this.b + "}";
         };
+        Color3.prototype.getClassName = function () {
+            return "Color3";
+        };
+        Color3.prototype.getHashCode = function () {
+            var hash = this.r || 0;
+            hash = (hash * 397) ^ (this.g || 0);
+            hash = (hash * 397) ^ (this.b || 0);
+            return hash;
+        };
         // Operators
         Color3.prototype.toArray = function (array, index) {
             if (index === undefined) {
@@ -245,6 +254,16 @@ var BABYLON;
         Color4.prototype.toString = function () {
             return "{R: " + this.r + " G:" + this.g + " B:" + this.b + " A:" + this.a + "}";
         };
+        Color4.prototype.getClassName = function () {
+            return "Color4";
+        };
+        Color4.prototype.getHashCode = function () {
+            var hash = this.r || 0;
+            hash = (hash * 397) ^ (this.g || 0);
+            hash = (hash * 397) ^ (this.b || 0);
+            hash = (hash * 397) ^ (this.a || 0);
+            return hash;
+        };
         Color4.prototype.clone = function () {
             return new Color4(this.r, this.g, this.b, this.a);
         };
@@ -318,6 +337,14 @@ var BABYLON;
         Vector2.prototype.toString = function () {
             return "{X: " + this.x + " Y:" + this.y + "}";
         };
+        Vector2.prototype.getClassName = function () {
+            return "Vector2";
+        };
+        Vector2.prototype.getHashCode = function () {
+            var hash = this.x || 0;
+            hash = (hash * 397) ^ (this.y || 0);
+            return hash;
+        };
         // Operators
         Vector2.prototype.toArray = function (array, index) {
             if (index === void 0) { index = 0; }
@@ -343,12 +370,22 @@ var BABYLON;
         Vector2.prototype.add = function (otherVector) {
             return new Vector2(this.x + otherVector.x, this.y + otherVector.y);
         };
+        Vector2.prototype.addToRef = function (otherVector, result) {
+            result.x = this.x + otherVector.x;
+            result.y = this.y + otherVector.y;
+            return this;
+        };
         Vector2.prototype.addVector3 = function (otherVector) {
             return new Vector2(this.x + otherVector.x, this.y + otherVector.y);
         };
         Vector2.prototype.subtract = function (otherVector) {
             return new Vector2(this.x - otherVector.x, this.y - otherVector.y);
         };
+        Vector2.prototype.subtractToRef = function (otherVector, result) {
+            result.x = this.x - otherVector.x;
+            result.y = this.y - otherVector.y;
+            return this;
+        };
         Vector2.prototype.subtractInPlace = function (otherVector) {
             this.x -= otherVector.x;
             this.y -= otherVector.y;
@@ -493,6 +530,13 @@ var BABYLON;
             result.x = x;
             result.y = y;
         };
+        Vector2.PointInTriangle = function (p, p0, p1, p2) {
+            var a = 1 / 2 * (-p1.y * p2.x + p0.y * (-p1.x + p2.x) + p0.x * (p1.y - p2.y) + p1.x * p2.y);
+            var sign = a < 0 ? -1 : 1;
+            var s = (p0.y * p2.x - p0.x * p2.y + (p2.y - p0.y) * p.x + (p0.x - p2.x) * p.y) * sign;
+            var t = (p0.x * p1.y - p0.y * p1.x + (p0.y - p1.y) * p.x + (p1.x - p0.x) * p.y) * sign;
+            return s > 0 && t > 0 && (s + t) < 2 * a * sign;
+        };
         Vector2.Distance = function (value1, value2) {
             return Math.sqrt(Vector2.DistanceSquared(value1, value2));
         };
@@ -501,6 +545,16 @@ var BABYLON;
             var y = value1.y - value2.y;
             return (x * x) + (y * y);
         };
+        Vector2.DistanceOfPointFromSegment = function (p, segA, segB) {
+            var l2 = Vector2.DistanceSquared(segA, segB);
+            if (l2 === 0.0) {
+                return Vector2.Distance(p, segA);
+            }
+            var v = segB.subtract(segA);
+            var t = Math.max(0, Math.min(1, Vector2.Dot(p.subtract(segA), v) / l2));
+            var proj = segA.add(v.multiplyByFloats(t, t));
+            return Vector2.Distance(p, proj);
+        };
         return Vector2;
     })();
     BABYLON.Vector2 = Vector2;
@@ -513,6 +567,15 @@ var BABYLON;
         Vector3.prototype.toString = function () {
             return "{X: " + this.x + " Y:" + this.y + " Z:" + this.z + "}";
         };
+        Vector3.prototype.getClassName = function () {
+            return "Vector3";
+        };
+        Vector3.prototype.getHashCode = function () {
+            var hash = this.x || 0;
+            hash = (hash * 397) ^ (this.y || 0);
+            hash = (hash * 397) ^ (this.z || 0);
+            return hash;
+        };
         // Operators
         Vector3.prototype.asArray = function () {
             var result = [];
@@ -1014,6 +1077,16 @@ var BABYLON;
         Vector4.prototype.toString = function () {
             return "{X: " + this.x + " Y:" + this.y + " Z:" + this.z + "W:" + this.w + "}";
         };
+        Vector4.prototype.getClassName = function () {
+            return "Vector4";
+        };
+        Vector4.prototype.getHashCode = function () {
+            var hash = this.x || 0;
+            hash = (hash * 397) ^ (this.y || 0);
+            hash = (hash * 397) ^ (this.z || 0);
+            hash = (hash * 397) ^ (this.w || 0);
+            return hash;
+        };
         // Operators
         Vector4.prototype.asArray = function () {
             var result = [];
@@ -1268,6 +1341,17 @@ var BABYLON;
             this.width = width;
             this.height = height;
         }
+        Size.prototype.toString = function () {
+            return "{W: " + this.width + ", H: " + this.height + "}";
+        };
+        Size.prototype.getClassName = function () {
+            return "Size";
+        };
+        Size.prototype.getHashCode = function () {
+            var hash = this.width || 0;
+            hash = (hash * 397) ^ (this.height || 0);
+            return hash;
+        };
         Size.prototype.clone = function () {
             return new Size(this.width, this.height);
         };
@@ -1317,6 +1401,16 @@ var BABYLON;
         Quaternion.prototype.toString = function () {
             return "{X: " + this.x + " Y:" + this.y + " Z:" + this.z + " W:" + this.w + "}";
         };
+        Quaternion.prototype.getClassName = function () {
+            return "Quaternion";
+        };
+        Quaternion.prototype.getHashCode = function () {
+            var hash = this.x || 0;
+            hash = (hash * 397) ^ (this.y || 0);
+            hash = (hash * 397) ^ (this.z || 0);
+            hash = (hash * 397) ^ (this.w || 0);
+            return hash;
+        };
         Quaternion.prototype.asArray = function () {
             return [this.x, this.y, this.z, this.w];
         };
@@ -1799,6 +1893,16 @@ var BABYLON;
         Matrix.prototype.clone = function () {
             return Matrix.FromValues(this.m[0], this.m[1], this.m[2], this.m[3], this.m[4], this.m[5], this.m[6], this.m[7], this.m[8], this.m[9], this.m[10], this.m[11], this.m[12], this.m[13], this.m[14], this.m[15]);
         };
+        Matrix.prototype.getClassName = function () {
+            return "Matrix";
+        };
+        Matrix.prototype.getHashCode = function () {
+            var hash = this.m[0] || 0;
+            for (var i = 1; i < 16; i++) {
+                hash = (hash * 397) ^ (this.m[i] || 0);
+            }
+            return hash;
+        };
         Matrix.prototype.decompose = function (scale, rotation, translation) {
             translation.x = this.m[12];
             translation.y = this.m[13];
@@ -2263,6 +2367,14 @@ var BABYLON;
         Plane.prototype.clone = function () {
             return new Plane(this.normal.x, this.normal.y, this.normal.z, this.d);
         };
+        Plane.prototype.getClassName = function () {
+            return "Plane";
+        };
+        Plane.prototype.getHashCode = function () {
+            var hash = this.normal.getHashCode();
+            hash = (hash * 397) ^ (this.d || 0);
+            return hash;
+        };
         Plane.prototype.normalize = function () {
             var norm = (Math.sqrt((this.normal.x * this.normal.x) + (this.normal.y * this.normal.y) + (this.normal.z * this.normal.z)));
             var magnitude = 0;

+ 139 - 1
src/Math/babylon.math.ts

@@ -48,6 +48,17 @@
             return "{R: " + this.r + " G:" + this.g + " B:" + this.b + "}";
         }
 
+        public getClassName(): string {
+            return "Color3";
+        }
+
+        public getHashCode(): number {
+            let hash = this.r || 0;
+            hash = (hash * 397) ^ (this.g || 0);
+            hash = (hash * 397) ^ (this.b || 0);
+            return hash;
+        }
+
         // Operators
         public toArray(array: number[], index?: number): Color3 {
             if (index === undefined) {
@@ -298,6 +309,18 @@
             return "{R: " + this.r + " G:" + this.g + " B:" + this.b + " A:" + this.a + "}";
         }
 
+        public getClassName(): string {
+            return "Color4";
+        }
+
+        public getHashCode(): number {
+            let hash = this.r || 0;
+            hash = (hash * 397) ^ (this.g || 0);
+            hash = (hash * 397) ^ (this.b || 0);
+            hash = (hash * 397) ^ (this.a || 0);
+            return hash;
+        }
+
         public clone(): Color4 {
             return new Color4(this.r, this.g, this.b, this.a);
         }
@@ -385,8 +408,18 @@
             return "{X: " + this.x + " Y:" + this.y + "}";
         }
 
+        public getClassName(): string {
+            return "Vector2";
+        }
+
+        public getHashCode(): number {
+            let hash = this.x || 0;
+            hash = (hash * 397) ^ (this.y || 0);
+            return hash;
+        }
+
         // Operators
-        public toArray(array: number[], index: number = 0): Vector2 {
+        public toArray(array: number[] | Float32Array, index: number = 0): Vector2 {
             array[index] = this.x;
             array[index + 1] = this.y;
 
@@ -419,6 +452,13 @@
             return new Vector2(this.x + otherVector.x, this.y + otherVector.y);
         }
 
+        public addToRef(otherVector: Vector2, result: Vector2): Vector2 {
+            result.x = this.x + otherVector.x;
+            result.y = this.y + otherVector.y;
+
+            return this;
+        }
+
         public addVector3(otherVector: Vector3): Vector2 {
             return new Vector2(this.x + otherVector.x, this.y + otherVector.y);
         }
@@ -427,6 +467,13 @@
             return new Vector2(this.x - otherVector.x, this.y - otherVector.y);
         }
 
+        public subtractToRef(otherVector: Vector2, result: Vector2): Vector2 {
+            result.x = this.x - otherVector.x;
+            result.y = this.y - otherVector.y;
+
+            return this;
+        }
+
         public subtractInPlace(otherVector: Vector2): Vector2 {
             this.x -= otherVector.x;
             this.y -= otherVector.y;
@@ -618,6 +665,15 @@
             result.y = y;
         }
 
+        public static PointInTriangle(p: Vector2, p0: Vector2, p1: Vector2, p2: Vector2) {
+            let a = 1 / 2 * (-p1.y * p2.x + p0.y * (-p1.x + p2.x) + p0.x * (p1.y - p2.y) + p1.x * p2.y);
+            let sign = a < 0 ? -1 : 1;
+            let s = (p0.y * p2.x - p0.x * p2.y + (p2.y - p0.y) * p.x + (p0.x - p2.x) * p.y) * sign;
+            let t = (p0.x * p1.y - p0.y * p1.x + (p0.y - p1.y) * p.x + (p1.x - p0.x) * p.y) * sign;
+
+            return s > 0 && t > 0 && (s + t) < 2 * a * sign;            
+        }
+
         public static Distance(value1: Vector2, value2: Vector2): number {
             return Math.sqrt(Vector2.DistanceSquared(value1, value2));
         }
@@ -628,6 +684,17 @@
 
             return (x * x) + (y * y);
         }
+
+        public static DistanceOfPointFromSegment(p: Vector2, segA: Vector2, segB: Vector2): number {
+            let l2 = Vector2.DistanceSquared(segA, segB);
+            if (l2 === 0.0) {
+                return Vector2.Distance(p, segA);
+            }
+            let v = segB.subtract(segA);
+            let t = Math.max(0, Math.min(1, Vector2.Dot(p.subtract(segA), v) / l2));
+            let proj = segA.add(v.multiplyByFloats(t, t));
+            return Vector2.Distance(p, proj);
+        }
     }
 
     export class Vector3 {
@@ -640,6 +707,17 @@
             return "{X: " + this.x + " Y:" + this.y + " Z:" + this.z + "}";
         }
 
+        public getClassName(): string {
+            return "Vector3";
+        }
+
+        public getHashCode(): number {
+            let hash = this.x || 0;
+            hash = (hash * 397) ^ (this.y || 0);
+            hash = (hash * 397) ^ (this.z || 0);
+            return hash;
+        }
+
         // Operators
         public asArray(): number[] {
             var result = [];
@@ -1261,6 +1339,18 @@
             return "{X: " + this.x + " Y:" + this.y + " Z:" + this.z + "W:" + this.w + "}";
         }
 
+        public getClassName(): string {
+            return "Vector4";
+        }
+
+        public getHashCode(): number {
+            let hash = this.x || 0;
+            hash = (hash * 397) ^ (this.y || 0);
+            hash = (hash * 397) ^ (this.z || 0);
+            hash = (hash * 397) ^ (this.w || 0);
+            return hash;
+        }
+
         // Operators
         public asArray(): number[] {
             var result = [];
@@ -1579,6 +1669,20 @@
             this.height = height;
         }
 
+        public toString(): string {
+            return `{W: ${this.width}, H: ${this.height}}`;
+        }
+
+        public getClassName(): string {
+            return "Size";
+        }
+
+        public getHashCode(): number {
+            let hash = this.width || 0;
+            hash = (hash * 397) ^ (this.height || 0);
+            return hash;
+        }
+
         public clone(): Size {
             return new Size(this.width, this.height);
         }
@@ -1627,6 +1731,18 @@
             return "{X: " + this.x + " Y:" + this.y + " Z:" + this.z + " W:" + this.w + "}";
         }
 
+        public getClassName(): string {
+            return "Quaternion";
+        }
+
+        public getHashCode(): number {
+            let hash = this.x || 0;
+            hash = (hash * 397) ^ (this.y || 0);
+            hash = (hash * 397) ^ (this.z || 0);
+            hash = (hash * 397) ^ (this.w || 0);
+            return hash;
+        }
+
         public asArray(): number[] {
             return [this.x, this.y, this.z, this.w];
         }
@@ -2216,6 +2332,18 @@
                 this.m[12], this.m[13], this.m[14], this.m[15]);
         }
 
+        public getClassName(): string {
+            return "Matrix";
+        }
+
+        public getHashCode(): number {
+            let hash = this.m[0] || 0;
+            for (let i = 1; i < 16; i++) {
+                hash = (hash * 397) ^ (this.m[i] || 0);
+            }
+            return hash;
+        }
+
         public decompose(scale: Vector3, rotation: Quaternion, translation: Vector3): boolean {
             translation.x = this.m[12];
             translation.y = this.m[13];
@@ -2838,6 +2966,16 @@
             return new Plane(this.normal.x, this.normal.y, this.normal.z, this.d);
         }
 
+        public getClassName(): string {
+            return "Plane";
+        }
+
+        public getHashCode(): number {
+            let hash = this.normal.getHashCode();
+            hash = (hash * 397) ^ (this.d || 0);
+            return hash;
+        }
+
         public normalize(): Plane {
             var norm = (Math.sqrt((this.normal.x * this.normal.x) + (this.normal.y * this.normal.y) + (this.normal.z * this.normal.z)));
             var magnitude = 0;

+ 1 - 0
src/Particles/babylon.solidParticle.js

@@ -9,6 +9,7 @@ var BABYLON;
             this.uvs = new BABYLON.Vector4(0, 0, 1, 1); // uvs
             this.velocity = BABYLON.Vector3.Zero(); // velocity
             this.alive = true; // alive
+            this.isVisible = true; // visibility
             this.idx = particleIndex;
             this._pos = positionIndex;
             this._model = model;

+ 1 - 0
src/Particles/babylon.solidParticle.ts

@@ -10,6 +10,7 @@ module BABYLON {
         public uvs = new Vector4(0, 0, 1, 1);   // uvs
         public velocity = Vector3.Zero();       // velocity
         public alive = true;                    // alive
+        public isVisible = true;                // visibility
         public _pos: number;                    // index of this particle in the global "positions" array
         public _model: ModelShape;              // model shape reference
         public shapeId: number;                 // model shape id

+ 102 - 73
src/Particles/babylon.solidParticleSystem.js

@@ -460,6 +460,7 @@ var BABYLON;
             var colorIndex = 0;
             var uvidx = 0;
             var uvIndex = 0;
+            var pt = 0;
             if (this._computeBoundingBox) {
                 BABYLON.Vector3.FromFloatsToRef(Number.MAX_VALUE, Number.MAX_VALUE, Number.MAX_VALUE, this._minimum);
                 BABYLON.Vector3.FromFloatsToRef(-Number.MAX_VALUE, -Number.MAX_VALUE, -Number.MAX_VALUE, this._maximum);
@@ -472,92 +473,120 @@ var BABYLON;
                 this._shapeUV = this._particle._model._shapeUV;
                 // call to custom user function to update the particle properties
                 this.updateParticle(this._particle);
-                // particle rotation matrix
-                if (this.billboard) {
-                    this._particle.rotation.x = 0.0;
-                    this._particle.rotation.y = 0.0;
-                }
-                if (this._computeParticleRotation) {
-                    if (this._particle.rotationQuaternion) {
-                        this._quaternion.copyFrom(this._particle.rotationQuaternion);
-                    }
-                    else {
-                        this._yaw = this._particle.rotation.y;
-                        this._pitch = this._particle.rotation.x;
-                        this._roll = this._particle.rotation.z;
-                        this._quaternionRotationYPR();
-                    }
-                    this._quaternionToRotationMatrix();
-                }
-                for (var pt = 0; pt < this._shape.length; pt++) {
-                    idx = index + pt * 3;
-                    colidx = colorIndex + pt * 4;
-                    uvidx = uvIndex + pt * 2;
-                    this._vertex.x = this._shape[pt].x;
-                    this._vertex.y = this._shape[pt].y;
-                    this._vertex.z = this._shape[pt].z;
-                    if (this._computeParticleVertex) {
-                        this.updateParticleVertex(this._particle, this._vertex, pt);
+                if (this._particle.isVisible) {
+                    // particle rotation matrix
+                    if (this.billboard) {
+                        this._particle.rotation.x = 0.0;
+                        this._particle.rotation.y = 0.0;
                     }
-                    // positions
-                    this._vertex.x *= this._particle.scaling.x;
-                    this._vertex.y *= this._particle.scaling.y;
-                    this._vertex.z *= this._particle.scaling.z;
-                    this._w = (this._vertex.x * this._rotMatrix.m[3]) + (this._vertex.y * this._rotMatrix.m[7]) + (this._vertex.z * this._rotMatrix.m[11]) + this._rotMatrix.m[15];
-                    this._rotated.x = ((this._vertex.x * this._rotMatrix.m[0]) + (this._vertex.y * this._rotMatrix.m[4]) + (this._vertex.z * this._rotMatrix.m[8]) + this._rotMatrix.m[12]) / this._w;
-                    this._rotated.y = ((this._vertex.x * this._rotMatrix.m[1]) + (this._vertex.y * this._rotMatrix.m[5]) + (this._vertex.z * this._rotMatrix.m[9]) + this._rotMatrix.m[13]) / this._w;
-                    this._rotated.z = ((this._vertex.x * this._rotMatrix.m[2]) + (this._vertex.y * this._rotMatrix.m[6]) + (this._vertex.z * this._rotMatrix.m[10]) + this._rotMatrix.m[14]) / this._w;
-                    this._positions32[idx] = this._particle.position.x + this._cam_axisX.x * this._rotated.x + this._cam_axisY.x * this._rotated.y + this._cam_axisZ.x * this._rotated.z;
-                    this._positions32[idx + 1] = this._particle.position.y + this._cam_axisX.y * this._rotated.x + this._cam_axisY.y * this._rotated.y + this._cam_axisZ.y * this._rotated.z;
-                    this._positions32[idx + 2] = this._particle.position.z + this._cam_axisX.z * this._rotated.x + this._cam_axisY.z * this._rotated.y + this._cam_axisZ.z * this._rotated.z;
-                    if (this._computeBoundingBox) {
-                        if (this._positions32[idx] < this._minimum.x) {
-                            this._minimum.x = this._positions32[idx];
+                    if (this._computeParticleRotation) {
+                        if (this._particle.rotationQuaternion) {
+                            this._quaternion.copyFrom(this._particle.rotationQuaternion);
                         }
-                        if (this._positions32[idx] > this._maximum.x) {
-                            this._maximum.x = this._positions32[idx];
+                        else {
+                            this._yaw = this._particle.rotation.y;
+                            this._pitch = this._particle.rotation.x;
+                            this._roll = this._particle.rotation.z;
+                            this._quaternionRotationYPR();
                         }
-                        if (this._positions32[idx + 1] < this._minimum.y) {
-                            this._minimum.y = this._positions32[idx + 1];
+                        this._quaternionToRotationMatrix();
+                    }
+                    // particle vertex loop
+                    for (pt = 0; pt < this._shape.length; pt++) {
+                        idx = index + pt * 3;
+                        colidx = colorIndex + pt * 4;
+                        uvidx = uvIndex + pt * 2;
+                        this._vertex.x = this._shape[pt].x;
+                        this._vertex.y = this._shape[pt].y;
+                        this._vertex.z = this._shape[pt].z;
+                        if (this._computeParticleVertex) {
+                            this.updateParticleVertex(this._particle, this._vertex, pt);
                         }
-                        if (this._positions32[idx + 1] > this._maximum.y) {
-                            this._maximum.y = this._positions32[idx + 1];
+                        // positions
+                        this._vertex.x *= this._particle.scaling.x;
+                        this._vertex.y *= this._particle.scaling.y;
+                        this._vertex.z *= this._particle.scaling.z;
+                        this._w = (this._vertex.x * this._rotMatrix.m[3]) + (this._vertex.y * this._rotMatrix.m[7]) + (this._vertex.z * this._rotMatrix.m[11]) + this._rotMatrix.m[15];
+                        this._rotated.x = ((this._vertex.x * this._rotMatrix.m[0]) + (this._vertex.y * this._rotMatrix.m[4]) + (this._vertex.z * this._rotMatrix.m[8]) + this._rotMatrix.m[12]) / this._w;
+                        this._rotated.y = ((this._vertex.x * this._rotMatrix.m[1]) + (this._vertex.y * this._rotMatrix.m[5]) + (this._vertex.z * this._rotMatrix.m[9]) + this._rotMatrix.m[13]) / this._w;
+                        this._rotated.z = ((this._vertex.x * this._rotMatrix.m[2]) + (this._vertex.y * this._rotMatrix.m[6]) + (this._vertex.z * this._rotMatrix.m[10]) + this._rotMatrix.m[14]) / this._w;
+                        this._positions32[idx] = this._particle.position.x + this._cam_axisX.x * this._rotated.x + this._cam_axisY.x * this._rotated.y + this._cam_axisZ.x * this._rotated.z;
+                        this._positions32[idx + 1] = this._particle.position.y + this._cam_axisX.y * this._rotated.x + this._cam_axisY.y * this._rotated.y + this._cam_axisZ.y * this._rotated.z;
+                        this._positions32[idx + 2] = this._particle.position.z + this._cam_axisX.z * this._rotated.x + this._cam_axisY.z * this._rotated.y + this._cam_axisZ.z * this._rotated.z;
+                        if (this._computeBoundingBox) {
+                            if (this._positions32[idx] < this._minimum.x) {
+                                this._minimum.x = this._positions32[idx];
+                            }
+                            if (this._positions32[idx] > this._maximum.x) {
+                                this._maximum.x = this._positions32[idx];
+                            }
+                            if (this._positions32[idx + 1] < this._minimum.y) {
+                                this._minimum.y = this._positions32[idx + 1];
+                            }
+                            if (this._positions32[idx + 1] > this._maximum.y) {
+                                this._maximum.y = this._positions32[idx + 1];
+                            }
+                            if (this._positions32[idx + 2] < this._minimum.z) {
+                                this._minimum.z = this._positions32[idx + 2];
+                            }
+                            if (this._positions32[idx + 2] > this._maximum.z) {
+                                this._maximum.z = this._positions32[idx + 2];
+                            }
                         }
-                        if (this._positions32[idx + 2] < this._minimum.z) {
-                            this._minimum.z = this._positions32[idx + 2];
+                        // normals : if the particles can't be morphed then just rotate the normals, what if much more faster than ComputeNormals()
+                        if (!this._computeParticleVertex && !this.billboard) {
+                            this._normal.x = this._fixedNormal32[idx];
+                            this._normal.y = this._fixedNormal32[idx + 1];
+                            this._normal.z = this._fixedNormal32[idx + 2];
+                            this._w = (this._normal.x * this._rotMatrix.m[3]) + (this._normal.y * this._rotMatrix.m[7]) + (this._normal.z * this._rotMatrix.m[11]) + this._rotMatrix.m[15];
+                            this._rotated.x = ((this._normal.x * this._rotMatrix.m[0]) + (this._normal.y * this._rotMatrix.m[4]) + (this._normal.z * this._rotMatrix.m[8]) + this._rotMatrix.m[12]) / this._w;
+                            this._rotated.y = ((this._normal.x * this._rotMatrix.m[1]) + (this._normal.y * this._rotMatrix.m[5]) + (this._normal.z * this._rotMatrix.m[9]) + this._rotMatrix.m[13]) / this._w;
+                            this._rotated.z = ((this._normal.x * this._rotMatrix.m[2]) + (this._normal.y * this._rotMatrix.m[6]) + (this._normal.z * this._rotMatrix.m[10]) + this._rotMatrix.m[14]) / this._w;
+                            this._normals32[idx] = this._cam_axisX.x * this._rotated.x + this._cam_axisY.x * this._rotated.y + this._cam_axisZ.x * this._rotated.z;
+                            this._normals32[idx + 1] = this._cam_axisX.y * this._rotated.x + this._cam_axisY.y * this._rotated.y + this._cam_axisZ.y * this._rotated.z;
+                            this._normals32[idx + 2] = this._cam_axisX.z * this._rotated.x + this._cam_axisY.z * this._rotated.y + this._cam_axisZ.z * this._rotated.z;
                         }
-                        if (this._positions32[idx + 2] > this._maximum.z) {
-                            this._maximum.z = this._positions32[idx + 2];
+                        if (this._computeParticleColor) {
+                            this._colors32[colidx] = this._particle.color.r;
+                            this._colors32[colidx + 1] = this._particle.color.g;
+                            this._colors32[colidx + 2] = this._particle.color.b;
+                            this._colors32[colidx + 3] = this._particle.color.a;
+                        }
+                        if (this._computeParticleTexture) {
+                            this._uvs32[uvidx] = this._shapeUV[pt * 2] * (this._particle.uvs.z - this._particle.uvs.x) + this._particle.uvs.x;
+                            this._uvs32[uvidx + 1] = this._shapeUV[pt * 2 + 1] * (this._particle.uvs.w - this._particle.uvs.y) + this._particle.uvs.y;
                         }
                     }
-                    // normals : if the particles can't be morphed then just rotate the normals
-                    if (!this._computeParticleVertex && !this.billboard) {
-                        this._normal.x = this._fixedNormal32[idx];
-                        this._normal.y = this._fixedNormal32[idx + 1];
-                        this._normal.z = this._fixedNormal32[idx + 2];
-                        this._w = (this._normal.x * this._rotMatrix.m[3]) + (this._normal.y * this._rotMatrix.m[7]) + (this._normal.z * this._rotMatrix.m[11]) + this._rotMatrix.m[15];
-                        this._rotated.x = ((this._normal.x * this._rotMatrix.m[0]) + (this._normal.y * this._rotMatrix.m[4]) + (this._normal.z * this._rotMatrix.m[8]) + this._rotMatrix.m[12]) / this._w;
-                        this._rotated.y = ((this._normal.x * this._rotMatrix.m[1]) + (this._normal.y * this._rotMatrix.m[5]) + (this._normal.z * this._rotMatrix.m[9]) + this._rotMatrix.m[13]) / this._w;
-                        this._rotated.z = ((this._normal.x * this._rotMatrix.m[2]) + (this._normal.y * this._rotMatrix.m[6]) + (this._normal.z * this._rotMatrix.m[10]) + this._rotMatrix.m[14]) / this._w;
-                        this._normals32[idx] = this._cam_axisX.x * this._rotated.x + this._cam_axisY.x * this._rotated.y + this._cam_axisZ.x * this._rotated.z;
-                        this._normals32[idx + 1] = this._cam_axisX.y * this._rotated.x + this._cam_axisY.y * this._rotated.y + this._cam_axisZ.y * this._rotated.z;
-                        this._normals32[idx + 2] = this._cam_axisX.z * this._rotated.x + this._cam_axisY.z * this._rotated.y + this._cam_axisZ.z * this._rotated.z;
-                    }
-                    if (this._computeParticleColor) {
-                        this._colors32[colidx] = this._particle.color.r;
-                        this._colors32[colidx + 1] = this._particle.color.g;
-                        this._colors32[colidx + 2] = this._particle.color.b;
-                        this._colors32[colidx + 3] = this._particle.color.a;
-                    }
-                    if (this._computeParticleTexture) {
-                        this._uvs32[uvidx] = this._shapeUV[pt * 2] * (this._particle.uvs.z - this._particle.uvs.x) + this._particle.uvs.x;
-                        this._uvs32[uvidx + 1] = this._shapeUV[pt * 2 + 1] * (this._particle.uvs.w - this._particle.uvs.y) + this._particle.uvs.y;
+                }
+                else {
+                    for (pt = 0; pt < this._shape.length; pt++) {
+                        idx = index + pt * 3;
+                        colidx = colorIndex + pt * 4;
+                        uvidx = uvIndex + pt * 2;
+                        this._positions32[idx] = this._camera.position.x;
+                        this._positions32[idx + 1] = this._camera.position.y;
+                        this._positions32[idx + 2] = this._camera.position.z;
+                        this._normals32[idx] = 0.0;
+                        this._normals32[idx + 1] = 0.0;
+                        this._normals32[idx + 2] = 0.0;
+                        if (this._computeParticleColor) {
+                            this._colors32[colidx] = this._particle.color.r;
+                            this._colors32[colidx + 1] = this._particle.color.g;
+                            this._colors32[colidx + 2] = this._particle.color.b;
+                            this._colors32[colidx + 3] = this._particle.color.a;
+                        }
+                        if (this._computeParticleTexture) {
+                            this._uvs32[uvidx] = this._shapeUV[pt * 2] * (this._particle.uvs.z - this._particle.uvs.x) + this._particle.uvs.x;
+                            this._uvs32[uvidx + 1] = this._shapeUV[pt * 2 + 1] * (this._particle.uvs.w - this._particle.uvs.y) + this._particle.uvs.y;
+                        }
                     }
                 }
+                // increment indexes for the next particle
                 index = idx + 3;
                 colorIndex = colidx + 4;
                 uvIndex = uvidx + 2;
             }
+            // if the VBO must be updated
             if (update) {
                 if (this._computeParticleColor) {
                     this.mesh.updateVerticesData(BABYLON.VertexBuffer.ColorKind, this._colors32, false, false);
@@ -568,7 +597,7 @@ var BABYLON;
                 this.mesh.updateVerticesData(BABYLON.VertexBuffer.PositionKind, this._positions32, false, false);
                 if (!this.mesh.areNormalsFrozen) {
                     if (this._computeParticleVertex || this.billboard) {
-                        // recompute the normals only if the particles can be morphed, update then the normal reference array
+                        // recompute the normals only if the particles can be morphed, update then also the normal reference array _fixedNormal32[]
                         BABYLON.VertexData.ComputeNormals(this._positions32, this._indices, this._normals32);
                         for (var i = 0; i < this._normals32.length; i++) {
                             this._fixedNormal32[i] = this._normals32[i];

+ 112 - 79
src/Particles/babylon.solidParticleSystem.ts

@@ -532,6 +532,7 @@
             var colorIndex = 0;
             var uvidx = 0;
             var uvIndex = 0;
+            var pt = 0;
 
             if (this._computeBoundingBox) {
                 Vector3.FromFloatsToRef(Number.MAX_VALUE, Number.MAX_VALUE, Number.MAX_VALUE, this._minimum);
@@ -547,105 +548,137 @@
 
                 // call to custom user function to update the particle properties
                 this.updateParticle(this._particle);
+                
+                if (this._particle.isVisible) {
 
-                // particle rotation matrix
-                if (this.billboard) {
-                    this._particle.rotation.x = 0.0;
-                    this._particle.rotation.y = 0.0;
-                }
-                if (this._computeParticleRotation) {
-                    if (this._particle.rotationQuaternion) {
-                        this._quaternion.copyFrom(this._particle.rotationQuaternion);
-                    } else {
-                        this._yaw = this._particle.rotation.y;
-                        this._pitch = this._particle.rotation.x;
-                        this._roll = this._particle.rotation.z;
-                        this._quaternionRotationYPR();
+                    // particle rotation matrix
+                    if (this.billboard) {
+                        this._particle.rotation.x = 0.0;
+                        this._particle.rotation.y = 0.0;
                     }
-                    this._quaternionToRotationMatrix();
-                }
-
-                for (var pt = 0; pt < this._shape.length; pt++) {
-                    idx = index + pt * 3;
-                    colidx = colorIndex + pt * 4;
-                    uvidx = uvIndex + pt * 2;
-
-                    this._vertex.x = this._shape[pt].x;
-                    this._vertex.y = this._shape[pt].y;
-                    this._vertex.z = this._shape[pt].z;
-
-                    if (this._computeParticleVertex) {
-                        this.updateParticleVertex(this._particle, this._vertex, pt);
+                    if (this._computeParticleRotation) {
+                        if (this._particle.rotationQuaternion) {
+                            this._quaternion.copyFrom(this._particle.rotationQuaternion);
+                        } else {
+                            this._yaw = this._particle.rotation.y;
+                            this._pitch = this._particle.rotation.x;
+                            this._roll = this._particle.rotation.z;
+                            this._quaternionRotationYPR();
+                        }
+                        this._quaternionToRotationMatrix();
                     }
 
-                    // positions
-                    this._vertex.x *= this._particle.scaling.x;
-                    this._vertex.y *= this._particle.scaling.y;
-                    this._vertex.z *= this._particle.scaling.z;
-
-                    this._w = (this._vertex.x * this._rotMatrix.m[3]) + (this._vertex.y * this._rotMatrix.m[7]) + (this._vertex.z * this._rotMatrix.m[11]) + this._rotMatrix.m[15];
-                    this._rotated.x = ((this._vertex.x * this._rotMatrix.m[0]) + (this._vertex.y * this._rotMatrix.m[4]) + (this._vertex.z * this._rotMatrix.m[8]) + this._rotMatrix.m[12]) / this._w;
-                    this._rotated.y = ((this._vertex.x * this._rotMatrix.m[1]) + (this._vertex.y * this._rotMatrix.m[5]) + (this._vertex.z * this._rotMatrix.m[9]) + this._rotMatrix.m[13]) / this._w;
-                    this._rotated.z = ((this._vertex.x * this._rotMatrix.m[2]) + (this._vertex.y * this._rotMatrix.m[6]) + (this._vertex.z * this._rotMatrix.m[10]) + this._rotMatrix.m[14]) / this._w;
+                    // particle vertex loop
+                    for (pt = 0; pt < this._shape.length; pt++) {
+                        idx = index + pt * 3;
+                        colidx = colorIndex + pt * 4;
+                        uvidx = uvIndex + pt * 2;
 
-                    this._positions32[idx] = this._particle.position.x + this._cam_axisX.x * this._rotated.x + this._cam_axisY.x * this._rotated.y + this._cam_axisZ.x * this._rotated.z;
-                    this._positions32[idx + 1] = this._particle.position.y + this._cam_axisX.y * this._rotated.x + this._cam_axisY.y * this._rotated.y + this._cam_axisZ.y * this._rotated.z;
-                    this._positions32[idx + 2] = this._particle.position.z + this._cam_axisX.z * this._rotated.x + this._cam_axisY.z * this._rotated.y + this._cam_axisZ.z * this._rotated.z;
+                        this._vertex.x = this._shape[pt].x;
+                        this._vertex.y = this._shape[pt].y;
+                        this._vertex.z = this._shape[pt].z;
 
-                    if (this._computeBoundingBox) {
-                        if (this._positions32[idx] < this._minimum.x) {
-                            this._minimum.x = this._positions32[idx];
-                        }
-                        if (this._positions32[idx] > this._maximum.x) {
-                            this._maximum.x = this._positions32[idx];
-                        }
-                        if (this._positions32[idx + 1] < this._minimum.y) {
-                            this._minimum.y = this._positions32[idx + 1];
-                        }
-                        if (this._positions32[idx + 1] > this._maximum.y) {
-                            this._maximum.y = this._positions32[idx + 1];
+                        if (this._computeParticleVertex) {
+                            this.updateParticleVertex(this._particle, this._vertex, pt);
                         }
-                        if (this._positions32[idx + 2] < this._minimum.z) {
-                            this._minimum.z = this._positions32[idx + 2];
-                        }
-                        if (this._positions32[idx + 2] > this._maximum.z) {
-                            this._maximum.z = this._positions32[idx + 2];
+
+                        // positions
+                        this._vertex.x *= this._particle.scaling.x;
+                        this._vertex.y *= this._particle.scaling.y;
+                        this._vertex.z *= this._particle.scaling.z;
+
+                        this._w = (this._vertex.x * this._rotMatrix.m[3]) + (this._vertex.y * this._rotMatrix.m[7]) + (this._vertex.z * this._rotMatrix.m[11]) + this._rotMatrix.m[15];
+                        this._rotated.x = ((this._vertex.x * this._rotMatrix.m[0]) + (this._vertex.y * this._rotMatrix.m[4]) + (this._vertex.z * this._rotMatrix.m[8]) + this._rotMatrix.m[12]) / this._w;
+                        this._rotated.y = ((this._vertex.x * this._rotMatrix.m[1]) + (this._vertex.y * this._rotMatrix.m[5]) + (this._vertex.z * this._rotMatrix.m[9]) + this._rotMatrix.m[13]) / this._w;
+                        this._rotated.z = ((this._vertex.x * this._rotMatrix.m[2]) + (this._vertex.y * this._rotMatrix.m[6]) + (this._vertex.z * this._rotMatrix.m[10]) + this._rotMatrix.m[14]) / this._w;
+
+                        this._positions32[idx] = this._particle.position.x + this._cam_axisX.x * this._rotated.x + this._cam_axisY.x * this._rotated.y + this._cam_axisZ.x * this._rotated.z;
+                        this._positions32[idx + 1] = this._particle.position.y + this._cam_axisX.y * this._rotated.x + this._cam_axisY.y * this._rotated.y + this._cam_axisZ.y * this._rotated.z;
+                        this._positions32[idx + 2] = this._particle.position.z + this._cam_axisX.z * this._rotated.x + this._cam_axisY.z * this._rotated.y + this._cam_axisZ.z * this._rotated.z;
+
+                        if (this._computeBoundingBox) {
+                            if (this._positions32[idx] < this._minimum.x) {
+                                this._minimum.x = this._positions32[idx];
+                            }
+                            if (this._positions32[idx] > this._maximum.x) {
+                                this._maximum.x = this._positions32[idx];
+                            }
+                            if (this._positions32[idx + 1] < this._minimum.y) {
+                                this._minimum.y = this._positions32[idx + 1];
+                            }
+                            if (this._positions32[idx + 1] > this._maximum.y) {
+                                this._maximum.y = this._positions32[idx + 1];
+                            }
+                            if (this._positions32[idx + 2] < this._minimum.z) {
+                                this._minimum.z = this._positions32[idx + 2];
+                            }
+                            if (this._positions32[idx + 2] > this._maximum.z) {
+                                this._maximum.z = this._positions32[idx + 2];
+                            }
                         }
-                    }
 
-                    // normals : if the particles can't be morphed then just rotate the normals
-                    if (!this._computeParticleVertex && !this.billboard) {
-                        this._normal.x = this._fixedNormal32[idx];
-                        this._normal.y = this._fixedNormal32[idx + 1];
-                        this._normal.z = this._fixedNormal32[idx + 2];
+                        // normals : if the particles can't be morphed then just rotate the normals, what if much more faster than ComputeNormals()
+                        if (!this._computeParticleVertex && !this.billboard) {
+                            this._normal.x = this._fixedNormal32[idx];
+                            this._normal.y = this._fixedNormal32[idx + 1];
+                            this._normal.z = this._fixedNormal32[idx + 2];
 
-                        this._w = (this._normal.x * this._rotMatrix.m[3]) + (this._normal.y * this._rotMatrix.m[7]) + (this._normal.z * this._rotMatrix.m[11]) + this._rotMatrix.m[15];
-                        this._rotated.x = ((this._normal.x * this._rotMatrix.m[0]) + (this._normal.y * this._rotMatrix.m[4]) + (this._normal.z * this._rotMatrix.m[8]) + this._rotMatrix.m[12]) / this._w;
-                        this._rotated.y = ((this._normal.x * this._rotMatrix.m[1]) + (this._normal.y * this._rotMatrix.m[5]) + (this._normal.z * this._rotMatrix.m[9]) + this._rotMatrix.m[13]) / this._w;
-                        this._rotated.z = ((this._normal.x * this._rotMatrix.m[2]) + (this._normal.y * this._rotMatrix.m[6]) + (this._normal.z * this._rotMatrix.m[10]) + this._rotMatrix.m[14]) / this._w;
+                            this._w = (this._normal.x * this._rotMatrix.m[3]) + (this._normal.y * this._rotMatrix.m[7]) + (this._normal.z * this._rotMatrix.m[11]) + this._rotMatrix.m[15];
+                            this._rotated.x = ((this._normal.x * this._rotMatrix.m[0]) + (this._normal.y * this._rotMatrix.m[4]) + (this._normal.z * this._rotMatrix.m[8]) + this._rotMatrix.m[12]) / this._w;
+                            this._rotated.y = ((this._normal.x * this._rotMatrix.m[1]) + (this._normal.y * this._rotMatrix.m[5]) + (this._normal.z * this._rotMatrix.m[9]) + this._rotMatrix.m[13]) / this._w;
+                            this._rotated.z = ((this._normal.x * this._rotMatrix.m[2]) + (this._normal.y * this._rotMatrix.m[6]) + (this._normal.z * this._rotMatrix.m[10]) + this._rotMatrix.m[14]) / this._w;
 
-                        this._normals32[idx] = this._cam_axisX.x * this._rotated.x + this._cam_axisY.x * this._rotated.y + this._cam_axisZ.x * this._rotated.z;
-                        this._normals32[idx + 1] = this._cam_axisX.y * this._rotated.x + this._cam_axisY.y * this._rotated.y + this._cam_axisZ.y * this._rotated.z;
-                        this._normals32[idx + 2] = this._cam_axisX.z * this._rotated.x + this._cam_axisY.z * this._rotated.y + this._cam_axisZ.z * this._rotated.z;
-                    }
+                            this._normals32[idx] = this._cam_axisX.x * this._rotated.x + this._cam_axisY.x * this._rotated.y + this._cam_axisZ.x * this._rotated.z;
+                            this._normals32[idx + 1] = this._cam_axisX.y * this._rotated.x + this._cam_axisY.y * this._rotated.y + this._cam_axisZ.y * this._rotated.z;
+                            this._normals32[idx + 2] = this._cam_axisX.z * this._rotated.x + this._cam_axisY.z * this._rotated.y + this._cam_axisZ.z * this._rotated.z;
+                        }
 
-                    if (this._computeParticleColor) {
-                        this._colors32[colidx] = this._particle.color.r;
-                        this._colors32[colidx + 1] = this._particle.color.g;
-                        this._colors32[colidx + 2] = this._particle.color.b;
-                        this._colors32[colidx + 3] = this._particle.color.a;
+                        if (this._computeParticleColor) {
+                            this._colors32[colidx] = this._particle.color.r;
+                            this._colors32[colidx + 1] = this._particle.color.g;
+                            this._colors32[colidx + 2] = this._particle.color.b;
+                            this._colors32[colidx + 3] = this._particle.color.a;
+                        }
+
+                        if (this._computeParticleTexture) {
+                            this._uvs32[uvidx] = this._shapeUV[pt * 2] * (this._particle.uvs.z - this._particle.uvs.x) + this._particle.uvs.x;
+                            this._uvs32[uvidx + 1] = this._shapeUV[pt * 2 + 1] * (this._particle.uvs.w - this._particle.uvs.y) + this._particle.uvs.y;
+                        }
                     }
 
-                    if (this._computeParticleTexture) {
-                        this._uvs32[uvidx] = this._shapeUV[pt * 2] * (this._particle.uvs.z - this._particle.uvs.x) + this._particle.uvs.x;
-                        this._uvs32[uvidx + 1] = this._shapeUV[pt * 2 + 1] * (this._particle.uvs.w - this._particle.uvs.y) + this._particle.uvs.y;
+                } 
+                // particle not visible : scaled to zero and positioned to the camera position
+                else {
+                    for (pt = 0; pt < this._shape.length; pt++) {
+                        idx = index + pt * 3;
+                        colidx = colorIndex + pt * 4;
+                        uvidx = uvIndex + pt * 2;
+                        this._positions32[idx] = this._camera.position.x;
+                        this._positions32[idx + 1] = this._camera.position.y;
+                        this._positions32[idx + 2] = this._camera.position.z;
+                        this._normals32[idx] = 0.0;
+                        this._normals32[idx + 1] = 0.0;
+                        this._normals32[idx + 2] = 0.0;
+                        if (this._computeParticleColor) {
+                            this._colors32[colidx] = this._particle.color.r;
+                            this._colors32[colidx + 1] = this._particle.color.g;
+                            this._colors32[colidx + 2] = this._particle.color.b;
+                            this._colors32[colidx + 3] = this._particle.color.a;
+                        }
+                        if (this._computeParticleTexture) {
+                            this._uvs32[uvidx] = this._shapeUV[pt * 2] * (this._particle.uvs.z - this._particle.uvs.x) + this._particle.uvs.x;
+                            this._uvs32[uvidx + 1] = this._shapeUV[pt * 2 + 1] * (this._particle.uvs.w - this._particle.uvs.y) + this._particle.uvs.y;
+                        }
                     }
                 }
+                
+                // increment indexes for the next particle
                 index = idx + 3;
                 colorIndex = colidx + 4;
                 uvIndex = uvidx + 2;
             }
 
+            // if the VBO must be updated
             if (update) {
                 if (this._computeParticleColor) {
                     this.mesh.updateVerticesData(VertexBuffer.ColorKind, this._colors32, false, false);
@@ -656,7 +689,7 @@
                 this.mesh.updateVerticesData(VertexBuffer.PositionKind, this._positions32, false, false);
                 if (!this.mesh.areNormalsFrozen) {
                     if (this._computeParticleVertex || this.billboard) {
-                        // recompute the normals only if the particles can be morphed, update then the normal reference array
+                        // recompute the normals only if the particles can be morphed, update then also the normal reference array _fixedNormal32[]
                         VertexData.ComputeNormals(this._positions32, this._indices, this._normals32);
                         for (var i = 0; i < this._normals32.length; i++) {
                             this._fixedNormal32[i] = this._normals32[i];

+ 2 - 2
src/PostProcess/babylon.anaglyphPostProcess.js

@@ -7,9 +7,9 @@ var BABYLON;
 (function (BABYLON) {
     var AnaglyphPostProcess = (function (_super) {
         __extends(AnaglyphPostProcess, _super);
-        function AnaglyphPostProcess(name, ratio, rigCameras, samplingMode, engine, reusable) {
+        function AnaglyphPostProcess(name, options, rigCameras, samplingMode, engine, reusable) {
             var _this = this;
-            _super.call(this, name, "anaglyph", null, ["leftSampler"], ratio, rigCameras[1], samplingMode, engine, reusable);
+            _super.call(this, name, "anaglyph", null, ["leftSampler"], options, rigCameras[1], samplingMode, engine, reusable);
             this._passedProcess = rigCameras[0]._rigPostProcess;
             this.onApplyObservable.add(function (effect) {
                 effect.setTextureFromPostProcess("leftSampler", _this._passedProcess);

+ 2 - 2
src/PostProcess/babylon.anaglyphPostProcess.ts

@@ -2,8 +2,8 @@
     export class AnaglyphPostProcess extends PostProcess {
         private _passedProcess : PostProcess;
 
-        constructor(name: string, ratio: number,  rigCameras: Camera[], samplingMode?: number, engine?: Engine, reusable?: boolean) {
-            super(name, "anaglyph", null, ["leftSampler"], ratio, rigCameras[1], samplingMode, engine, reusable);
+        constructor(name: string, options: number | PostProcessOptions,  rigCameras: Camera[], samplingMode?: number, engine?: Engine, reusable?: boolean) {
+            super(name, "anaglyph", null, ["leftSampler"], options, rigCameras[1], samplingMode, engine, reusable);
             this._passedProcess = rigCameras[0]._rigPostProcess;
 
             this.onApplyObservable.add((effect: Effect) => {

+ 2 - 2
src/PostProcess/babylon.blackAndWhitePostProcess.js

@@ -7,8 +7,8 @@ var BABYLON;
 (function (BABYLON) {
     var BlackAndWhitePostProcess = (function (_super) {
         __extends(BlackAndWhitePostProcess, _super);
-        function BlackAndWhitePostProcess(name, ratio, camera, samplingMode, engine, reusable) {
-            _super.call(this, name, "blackAndWhite", null, null, ratio, camera, samplingMode, engine, reusable);
+        function BlackAndWhitePostProcess(name, options, camera, samplingMode, engine, reusable) {
+            _super.call(this, name, "blackAndWhite", null, null, options, camera, samplingMode, engine, reusable);
         }
         return BlackAndWhitePostProcess;
     })(BABYLON.PostProcess);

+ 2 - 2
src/PostProcess/babylon.blackAndWhitePostProcess.ts

@@ -1,7 +1,7 @@
 module BABYLON {
     export class BlackAndWhitePostProcess extends PostProcess {
-        constructor(name: string, ratio: number, camera: Camera, samplingMode?: number, engine?: Engine, reusable?: boolean) {
-            super(name, "blackAndWhite", null, null, ratio, camera, samplingMode, engine, reusable);
+        constructor(name: string, options: number | PostProcessOptions, camera: Camera, samplingMode?: number, engine?: Engine, reusable?: boolean) {
+            super(name, "blackAndWhite", null, null, options, camera, samplingMode, engine, reusable);
         }
     }
 } 

+ 2 - 2
src/PostProcess/babylon.blurPostProcess.js

@@ -7,10 +7,10 @@ var BABYLON;
 (function (BABYLON) {
     var BlurPostProcess = (function (_super) {
         __extends(BlurPostProcess, _super);
-        function BlurPostProcess(name, direction, blurWidth, ratio, camera, samplingMode, engine, reusable) {
+        function BlurPostProcess(name, direction, blurWidth, options, camera, samplingMode, engine, reusable) {
             var _this = this;
             if (samplingMode === void 0) { samplingMode = BABYLON.Texture.BILINEAR_SAMPLINGMODE; }
-            _super.call(this, name, "blur", ["screenSize", "direction", "blurWidth"], null, ratio, camera, samplingMode, engine, reusable);
+            _super.call(this, name, "blur", ["screenSize", "direction", "blurWidth"], null, options, camera, samplingMode, engine, reusable);
             this.direction = direction;
             this.blurWidth = blurWidth;
             this.onApplyObservable.add(function (effect) {

+ 2 - 2
src/PostProcess/babylon.blurPostProcess.ts

@@ -1,7 +1,7 @@
 module BABYLON {
     export class BlurPostProcess extends PostProcess {
-        constructor(name: string, public direction: Vector2, public blurWidth: number, ratio: number, camera: Camera, samplingMode: number = Texture.BILINEAR_SAMPLINGMODE, engine?: Engine, reusable?: boolean) {
-            super(name, "blur", ["screenSize", "direction", "blurWidth"], null, ratio, camera, samplingMode, engine, reusable);
+        constructor(name: string, public direction: Vector2, public blurWidth: number, options: number | PostProcessOptions, camera: Camera, samplingMode: number = Texture.BILINEAR_SAMPLINGMODE, engine?: Engine, reusable?: boolean) {
+            super(name, "blur", ["screenSize", "direction", "blurWidth"], null, options, camera, samplingMode, engine, reusable);
             this.onApplyObservable.add((effect: Effect) => {
                 effect.setFloat2("screenSize", this.width, this.height);
                 effect.setVector2("direction", this.direction);

+ 2 - 2
src/PostProcess/babylon.colorCorrectionPostProcess.js

@@ -20,9 +20,9 @@ var BABYLON;
 (function (BABYLON) {
     var ColorCorrectionPostProcess = (function (_super) {
         __extends(ColorCorrectionPostProcess, _super);
-        function ColorCorrectionPostProcess(name, colorTableUrl, ratio, camera, samplingMode, engine, reusable) {
+        function ColorCorrectionPostProcess(name, colorTableUrl, options, camera, samplingMode, engine, reusable) {
             var _this = this;
-            _super.call(this, name, 'colorCorrection', null, ['colorTable'], ratio, camera, samplingMode, engine, reusable);
+            _super.call(this, name, 'colorCorrection', null, ['colorTable'], options, camera, samplingMode, engine, reusable);
             this._colorTableTexture = new BABYLON.Texture(colorTableUrl, camera.getScene(), true, false, BABYLON.Texture.TRILINEAR_SAMPLINGMODE);
             this._colorTableTexture.anisotropicFilteringLevel = 1;
             this._colorTableTexture.wrapU = BABYLON.Texture.CLAMP_ADDRESSMODE;

+ 2 - 2
src/PostProcess/babylon.colorCorrectionPostProcess.ts

@@ -17,8 +17,8 @@ module BABYLON {
 
         private _colorTableTexture: Texture;
 
-        constructor(name: string, colorTableUrl: string, ratio: number, camera: Camera, samplingMode?: number, engine?: Engine, reusable?: boolean) {
-            super(name, 'colorCorrection', null, ['colorTable'], ratio, camera, samplingMode, engine, reusable);
+        constructor(name: string, colorTableUrl: string, options: number | PostProcessOptions, camera: Camera, samplingMode?: number, engine?: Engine, reusable?: boolean) {
+            super(name, 'colorCorrection', null, ['colorTable'], options, camera, samplingMode, engine, reusable);
 
             this._colorTableTexture = new Texture(colorTableUrl, camera.getScene(), true, false, Texture.TRILINEAR_SAMPLINGMODE);
             this._colorTableTexture.anisotropicFilteringLevel = 1;

+ 2 - 2
src/PostProcess/babylon.convolutionPostProcess.js

@@ -7,9 +7,9 @@ var BABYLON;
 (function (BABYLON) {
     var ConvolutionPostProcess = (function (_super) {
         __extends(ConvolutionPostProcess, _super);
-        function ConvolutionPostProcess(name, kernel, ratio, camera, samplingMode, engine, reusable) {
+        function ConvolutionPostProcess(name, kernel, options, camera, samplingMode, engine, reusable) {
             var _this = this;
-            _super.call(this, name, "convolution", ["kernel", "screenSize"], null, ratio, camera, samplingMode, engine, reusable);
+            _super.call(this, name, "convolution", ["kernel", "screenSize"], null, options, camera, samplingMode, engine, reusable);
             this.kernel = kernel;
             this.onApply = function (effect) {
                 effect.setFloat2("screenSize", _this.width, _this.height);

+ 2 - 2
src/PostProcess/babylon.convolutionPostProcess.ts

@@ -1,7 +1,7 @@
 module BABYLON {
     export class ConvolutionPostProcess extends PostProcess{
-        constructor(name: string, public kernel: number[], ratio: number, camera: Camera, samplingMode?: number, engine?: Engine, reusable?: boolean) {
-            super(name, "convolution", ["kernel", "screenSize"], null, ratio, camera, samplingMode, engine, reusable);
+        constructor(name: string, public kernel: number[], options: number | PostProcessOptions, camera: Camera, samplingMode?: number, engine?: Engine, reusable?: boolean) {
+            super(name, "convolution", ["kernel", "screenSize"], null, options, camera, samplingMode, engine, reusable);
 
             this.onApply = (effect: Effect) => {
                 effect.setFloat2("screenSize", this.width, this.height);

+ 2 - 2
src/PostProcess/babylon.displayPassPostProcess.js

@@ -7,8 +7,8 @@ var BABYLON;
 (function (BABYLON) {
     var DisplayPassPostProcess = (function (_super) {
         __extends(DisplayPassPostProcess, _super);
-        function DisplayPassPostProcess(name, ratio, camera, samplingMode, engine, reusable) {
-            _super.call(this, name, "displayPass", ["passSampler"], ["passSampler"], ratio, camera, samplingMode, engine, reusable);
+        function DisplayPassPostProcess(name, options, camera, samplingMode, engine, reusable) {
+            _super.call(this, name, "displayPass", ["passSampler"], ["passSampler"], options, camera, samplingMode, engine, reusable);
         }
         return DisplayPassPostProcess;
     })(BABYLON.PostProcess);

+ 2 - 2
src/PostProcess/babylon.displayPassPostProcess.ts

@@ -1,7 +1,7 @@
 module BABYLON {
     export class DisplayPassPostProcess extends PostProcess {
-        constructor(name: string, ratio: number, camera: Camera, samplingMode?: number, engine?: Engine, reusable?: boolean) {
-            super(name, "displayPass", ["passSampler"], ["passSampler"], ratio, camera, samplingMode, engine, reusable);
+        constructor(name: string, options: number | PostProcessOptions, camera: Camera, samplingMode?: number, engine?: Engine, reusable?: boolean) {
+            super(name, "displayPass", ["passSampler"], ["passSampler"], options, camera, samplingMode, engine, reusable);
         }
     }
 }

+ 2 - 2
src/PostProcess/babylon.filterPostProcess.js

@@ -7,9 +7,9 @@ var BABYLON;
 (function (BABYLON) {
     var FilterPostProcess = (function (_super) {
         __extends(FilterPostProcess, _super);
-        function FilterPostProcess(name, kernelMatrix, ratio, camera, samplingMode, engine, reusable) {
+        function FilterPostProcess(name, kernelMatrix, options, camera, samplingMode, engine, reusable) {
             var _this = this;
-            _super.call(this, name, "filter", ["kernelMatrix"], null, ratio, camera, samplingMode, engine, reusable);
+            _super.call(this, name, "filter", ["kernelMatrix"], null, options, camera, samplingMode, engine, reusable);
             this.kernelMatrix = kernelMatrix;
             this.onApply = function (effect) {
                 effect.setMatrix("kernelMatrix", _this.kernelMatrix);

+ 2 - 2
src/PostProcess/babylon.filterPostProcess.ts

@@ -1,7 +1,7 @@
 module BABYLON {
     export class FilterPostProcess extends PostProcess {
-        constructor(name: string, public kernelMatrix: Matrix, ratio: number, camera?: Camera, samplingMode?: number, engine?: Engine, reusable?: boolean) {
-            super(name, "filter", ["kernelMatrix"], null, ratio, camera, samplingMode, engine, reusable);
+        constructor(name: string, public kernelMatrix: Matrix, options: number | PostProcessOptions, camera?: Camera, samplingMode?: number, engine?: Engine, reusable?: boolean) {
+            super(name, "filter", ["kernelMatrix"], null, options, camera, samplingMode, engine, reusable);
 
             this.onApply = (effect: Effect) => {
                 effect.setMatrix("kernelMatrix", this.kernelMatrix);

+ 2 - 2
src/PostProcess/babylon.fxaaPostProcess.js

@@ -7,9 +7,9 @@ var BABYLON;
 (function (BABYLON) {
     var FxaaPostProcess = (function (_super) {
         __extends(FxaaPostProcess, _super);
-        function FxaaPostProcess(name, ratio, camera, samplingMode, engine, reusable) {
+        function FxaaPostProcess(name, options, camera, samplingMode, engine, reusable) {
             var _this = this;
-            _super.call(this, name, "fxaa", ["texelSize"], null, ratio, camera, samplingMode, engine, reusable);
+            _super.call(this, name, "fxaa", ["texelSize"], null, options, camera, samplingMode, engine, reusable);
             this.onSizeChangedObservable.add(function () {
                 _this.texelWidth = 1.0 / _this.width;
                 _this.texelHeight = 1.0 / _this.height;

+ 2 - 2
src/PostProcess/babylon.fxaaPostProcess.ts

@@ -3,8 +3,8 @@
         public texelWidth: number;
         public texelHeight: number;
 
-        constructor(name: string, ratio: number, camera: Camera, samplingMode?: number, engine?: Engine, reusable?: boolean) {
-            super(name, "fxaa", ["texelSize"], null, ratio, camera, samplingMode, engine, reusable);
+        constructor(name: string, options: number | PostProcessOptions, camera: Camera, samplingMode?: number, engine?: Engine, reusable?: boolean) {
+            super(name, "fxaa", ["texelSize"], null, options, camera, samplingMode, engine, reusable);
 
             this.onSizeChangedObservable.add(() => {
                 this.texelWidth = 1.0 / this.width;

+ 2 - 2
src/PostProcess/babylon.passPostProcess.js

@@ -7,8 +7,8 @@ var BABYLON;
 (function (BABYLON) {
     var PassPostProcess = (function (_super) {
         __extends(PassPostProcess, _super);
-        function PassPostProcess(name, ratio, camera, samplingMode, engine, reusable) {
-            _super.call(this, name, "pass", null, null, ratio, camera, samplingMode, engine, reusable);
+        function PassPostProcess(name, options, camera, samplingMode, engine, reusable) {
+            _super.call(this, name, "pass", null, null, options, camera, samplingMode, engine, reusable);
         }
         return PassPostProcess;
     })(BABYLON.PostProcess);

+ 2 - 2
src/PostProcess/babylon.passPostProcess.ts

@@ -1,7 +1,7 @@
 module BABYLON {
     export class PassPostProcess extends PostProcess {
-        constructor(name: string, ratio: number, camera: Camera, samplingMode?: number, engine?: Engine, reusable?: boolean) {
-            super(name, "pass", null, null, ratio, camera, samplingMode, engine, reusable);
+        constructor(name: string, options: number | PostProcessOptions, camera: Camera, samplingMode?: number, engine?: Engine, reusable?: boolean) {
+            super(name, "pass", null, null, options, camera, samplingMode, engine, reusable);
         }
     }
 } 

+ 8 - 8
src/PostProcess/babylon.postProcess.js

@@ -1,7 +1,7 @@
 var BABYLON;
 (function (BABYLON) {
     var PostProcess = (function () {
-        function PostProcess(name, fragmentUrl, parameters, samplers, ratio, camera, samplingMode, engine, reusable, defines, textureType) {
+        function PostProcess(name, fragmentUrl, parameters, samplers, options, camera, samplingMode, engine, reusable, defines, textureType) {
             if (samplingMode === void 0) { samplingMode = BABYLON.Texture.NEAREST_SAMPLINGMODE; }
             if (textureType === void 0) { textureType = BABYLON.Engine.TEXTURETYPE_UNSIGNED_INT; }
             this.name = name;
@@ -51,7 +51,7 @@ var BABYLON;
             else {
                 this._engine = engine;
             }
-            this._renderRatio = ratio;
+            this._options = options;
             this.renderTargetSamplingMode = samplingMode ? samplingMode : BABYLON.Texture.NEAREST_SAMPLINGMODE;
             this._reusable = reusable || false;
             this._textureType = textureType;
@@ -126,15 +126,15 @@ var BABYLON;
             camera = camera || this._camera;
             var scene = camera.getScene();
             var maxSize = camera.getEngine().getCaps().maxTextureSize;
-            var requiredWidth = ((sourceTexture ? sourceTexture._width : this._engine.getRenderingCanvas().width) * this._renderRatio) | 0;
-            var requiredHeight = ((sourceTexture ? sourceTexture._height : this._engine.getRenderingCanvas().height) * this._renderRatio) | 0;
-            var desiredWidth = this._renderRatio.width || requiredWidth;
-            var desiredHeight = this._renderRatio.height || requiredHeight;
+            var requiredWidth = ((sourceTexture ? sourceTexture._width : this._engine.getRenderingCanvas().width) * this._options) | 0;
+            var requiredHeight = ((sourceTexture ? sourceTexture._height : this._engine.getRenderingCanvas().height) * this._options) | 0;
+            var desiredWidth = this._options.width || requiredWidth;
+            var desiredHeight = this._options.height || requiredHeight;
             if (this.renderTargetSamplingMode !== BABYLON.Texture.NEAREST_SAMPLINGMODE) {
-                if (!this._renderRatio.width) {
+                if (!this._options.width) {
                     desiredWidth = BABYLON.Tools.GetExponentOfTwo(desiredWidth, maxSize);
                 }
-                if (!this._renderRatio.height) {
+                if (!this._options.height) {
                     desiredHeight = BABYLON.Tools.GetExponentOfTwo(desiredHeight, maxSize);
                 }
             }

+ 13 - 10
src/PostProcess/babylon.postProcess.ts

@@ -1,5 +1,8 @@
 module BABYLON {
-    export class PostProcess {
+    export type PostProcessOptions = { width: number, height: number };
+
+    export class PostProcess
+    {
         public width = -1;
         public height = -1;
         public renderTargetSamplingMode: number;
@@ -14,7 +17,7 @@
         private _camera: Camera;
         private _scene: Scene;
         private _engine: Engine;
-        private _renderRatio: number|any;
+        private _options: number | PostProcessOptions;
         private _reusable = false;
         private _textureType: number;
         public _textures = new SmartArray<WebGLTexture>(2);
@@ -97,7 +100,7 @@
             this._onAfterRenderObserver = this.onAfterRenderObservable.add(callback);
         }
 
-        constructor(public name: string, fragmentUrl: string, parameters: string[], samplers: string[], ratio: number|any, camera: Camera, samplingMode: number = Texture.NEAREST_SAMPLINGMODE, engine?: Engine, reusable?: boolean, defines?: string, textureType: number = Engine.TEXTURETYPE_UNSIGNED_INT) {
+        constructor(public name: string, fragmentUrl: string, parameters: string[], samplers: string[], options: number | PostProcessOptions, camera: Camera, samplingMode: number = Texture.NEAREST_SAMPLINGMODE, engine?: Engine, reusable?: boolean, defines?: string, textureType: number = Engine.TEXTURETYPE_UNSIGNED_INT) {
             if (camera != null) {
                 this._camera = camera;
                 this._scene = camera.getScene();
@@ -108,7 +111,7 @@
                 this._engine = engine;
             }
 
-            this._renderRatio = ratio;
+            this._options = options;
             this.renderTargetSamplingMode = samplingMode ? samplingMode : Texture.NEAREST_SAMPLINGMODE;
             this._reusable = reusable || false;
             this._textureType = textureType;
@@ -146,18 +149,18 @@
             var scene = camera.getScene();
             var maxSize = camera.getEngine().getCaps().maxTextureSize;
 
-            var requiredWidth = ((sourceTexture ? sourceTexture._width : this._engine.getRenderingCanvas().width) * this._renderRatio) | 0;
-            var requiredHeight = ((sourceTexture ? sourceTexture._height : this._engine.getRenderingCanvas().height) * this._renderRatio) | 0;
+            var requiredWidth = ((sourceTexture ? sourceTexture._width : this._engine.getRenderingCanvas().width) * <number>this._options) | 0;
+            var requiredHeight = ((sourceTexture ? sourceTexture._height : this._engine.getRenderingCanvas().height) * <number>this._options) | 0;
 
-            var desiredWidth = this._renderRatio.width || requiredWidth;
-            var desiredHeight = this._renderRatio.height || requiredHeight;
+            var desiredWidth = (<PostProcessOptions>this._options).width || requiredWidth;
+            var desiredHeight = (<PostProcessOptions>this._options).height || requiredHeight;
 
             if (this.renderTargetSamplingMode !== Texture.NEAREST_SAMPLINGMODE) {
-                if (!this._renderRatio.width) {
+                if (!(<PostProcessOptions>this._options).width) {
                     desiredWidth = Tools.GetExponentOfTwo(desiredWidth, maxSize);
                 }
 
-                if (!this._renderRatio.height) {
+                if (!(<PostProcessOptions>this._options).height) {
                     desiredHeight = Tools.GetExponentOfTwo(desiredHeight, maxSize);
                 }
             }

+ 2 - 2
src/PostProcess/babylon.refractionPostProcess.js

@@ -7,9 +7,9 @@ var BABYLON;
 (function (BABYLON) {
     var RefractionPostProcess = (function (_super) {
         __extends(RefractionPostProcess, _super);
-        function RefractionPostProcess(name, refractionTextureUrl, color, depth, colorLevel, ratio, camera, samplingMode, engine, reusable) {
+        function RefractionPostProcess(name, refractionTextureUrl, color, depth, colorLevel, options, camera, samplingMode, engine, reusable) {
             var _this = this;
-            _super.call(this, name, "refraction", ["baseColor", "depth", "colorLevel"], ["refractionSampler"], ratio, camera, samplingMode, engine, reusable);
+            _super.call(this, name, "refraction", ["baseColor", "depth", "colorLevel"], ["refractionSampler"], options, camera, samplingMode, engine, reusable);
             this.color = color;
             this.depth = depth;
             this.colorLevel = colorLevel;

+ 2 - 2
src/PostProcess/babylon.refractionPostProcess.ts

@@ -1,8 +1,8 @@
 module BABYLON {
     export class RefractionPostProcess extends PostProcess {
         private _refRexture: Texture;
-        constructor(name: string, refractionTextureUrl: string, public color: Color3, public depth: number, public colorLevel: number, ratio: number, camera: Camera, samplingMode?: number, engine?: Engine, reusable?: boolean) {
-            super(name, "refraction", ["baseColor", "depth", "colorLevel"], ["refractionSampler"], ratio, camera, samplingMode, engine, reusable);
+        constructor(name: string, refractionTextureUrl: string, public color: Color3, public depth: number, public colorLevel: number, options: number | PostProcessOptions, camera: Camera, samplingMode?: number, engine?: Engine, reusable?: boolean) {
+            super(name, "refraction", ["baseColor", "depth", "colorLevel"], ["refractionSampler"], options, camera, samplingMode, engine, reusable);
 
             this.onActivateObservable.add((cam: Camera) => {
                 this._refRexture = this._refRexture || new Texture(refractionTextureUrl, cam.getScene());

+ 5 - 0
src/Shaders/ellipse2d.fragment.fx

@@ -0,0 +1,5 @@
+varying vec4 vColor;
+
+void main(void) {
+	gl_FragColor = vColor;
+}

+ 108 - 0
src/Shaders/ellipse2d.vertex.fx

@@ -0,0 +1,108 @@
+// based on if Instanced Array are supported or not, declare the field either as attribute or uniform
+#ifdef Instanced
+#define att attribute
+#else
+#define att uniform
+#endif
+
+attribute float index;
+att vec2 zBias;
+att vec4 transformX;
+att vec4 transformY;
+att vec2 origin;
+
+#ifdef Border
+att float borderThickness;
+#endif
+
+#ifdef FillSolid
+att vec4 fillSolidColor;
+#endif
+
+#ifdef BorderSolid
+att vec4 borderSolidColor;
+#endif
+
+#ifdef FillGradient
+att vec4 fillGradientColor1;
+att vec4 fillGradientColor2;
+att vec4 fillGradientTY;
+#endif
+
+#ifdef BorderGradient
+att vec4 borderGradientColor1;
+att vec4 borderGradientColor2;
+att vec4 borderGradientTY;
+#endif
+
+// x, y and z are: width, height, subdivisions
+att vec3 properties;
+
+#define TWOPI 6.28318530
+
+// Output
+varying vec2 vUV;
+varying vec4 vColor;
+
+void main(void) {
+
+	vec2 pos2;
+
+#ifdef Border
+	float w = properties.x;
+	float h = properties.y;
+	float ms = properties.z;
+	vec2 borderOffset = vec2(1.0, 1.0);
+
+	float segi = index;
+	if (index < ms) {
+		borderOffset = vec2(1.0-(borderThickness*2.0 / w), 1.0-(borderThickness*2.0 / h));
+	}
+	else {
+		segi -= ms;
+	}
+
+	float angle = TWOPI * segi / ms;
+	pos2.x = (cos(angle) / 2.0) + 0.5;
+	pos2.y = (sin(angle) / 2.0) + 0.5;
+
+	pos2.x = ((pos2.x - 0.5) * borderOffset.x) + 0.5;
+	pos2.y = ((pos2.y - 0.5) * borderOffset.y) + 0.5;
+#else
+	if (index == 0.0) {
+		pos2 = vec2(0.5, 0.5);
+	}
+	else {
+		float ms = properties.z;
+
+		float angle = TWOPI * (index - 1.0) / ms;
+		pos2.x = (cos(angle) / 2.0) + 0.5;
+		pos2.y = (sin(angle) / 2.0) + 0.5;
+	}
+#endif
+
+#ifdef FillSolid
+	vColor = fillSolidColor;
+#endif
+
+#ifdef BorderSolid
+	vColor = borderSolidColor;
+#endif
+
+#ifdef FillGradient
+	float v = dot(vec4(pos2.xy, 1, 1), fillGradientTY);
+	vColor = mix(fillGradientColor2, fillGradientColor1, v);	// As Y is inverted, Color2 first, then Color1
+#endif
+
+#ifdef BorderGradient
+	float v = dot(vec4(pos2.xy, 1, 1), borderGradientTY);
+	vColor = mix(borderGradientColor2, borderGradientColor1, v);	// As Y is inverted, Color2 first, then Color1
+#endif
+
+	vec4 pos;
+	pos.xy = (pos2.xy - origin) * properties.xy;
+	pos.z = 1.0;
+	pos.w = 1.0;
+	gl_Position = vec4(dot(pos, transformX), dot(pos, transformY), zBias.x, zBias.y);
+
+}

+ 5 - 0
src/Shaders/lines2d.fragment.fx

@@ -0,0 +1,5 @@
+varying vec4 vColor;
+
+void main(void) {
+	gl_FragColor = vColor;
+}

+ 68 - 0
src/Shaders/lines2d.vertex.fx

@@ -0,0 +1,68 @@
+// based on if Instanced Array are supported or not, declare the field either as attribute or uniform
+#ifdef Instanced
+#define att attribute
+#else
+#define att uniform
+#endif
+
+attribute vec2 position;
+att vec2 zBias;
+att vec4 transformX;
+att vec4 transformY;
+att vec2 origin;
+att vec2 boundingMin;
+att vec2 boundingMax;
+
+#ifdef FillSolid
+att vec4 fillSolidColor;
+#endif
+
+#ifdef BorderSolid
+att vec4 borderSolidColor;
+#endif
+
+#ifdef FillGradient
+att vec4 fillGradientColor1;
+att vec4 fillGradientColor2;
+att vec4 fillGradientTY;
+#endif
+
+#ifdef BorderGradient
+att vec4 borderGradientColor1;
+att vec4 borderGradientColor2;
+att vec4 borderGradientTY;
+#endif
+
+#define TWOPI 6.28318530
+
+// Output
+varying vec2 vUV;
+varying vec4 vColor;
+
+void main(void) {
+
+#ifdef FillSolid
+	vColor = fillSolidColor;
+#endif
+
+#ifdef BorderSolid
+	vColor = borderSolidColor;
+#endif
+
+#ifdef FillGradient
+	float v = dot(vec4((position.xy - boundingMin) / (boundingMax - boundingMin), 1, 1), fillGradientTY);
+	vColor = mix(fillGradientColor2, fillGradientColor1, v);	// As Y is inverted, Color2 first, then Color1
+#endif
+
+#ifdef BorderGradient
+	float v = dot(vec4((position.xy - boundingMin) / (boundingMax - boundingMin), 1, 1), borderGradientTY);
+	vColor = mix(borderGradientColor2, borderGradientColor1, v);	// As Y is inverted, Color2 first, then Color1
+#endif
+
+	vec4 pos;
+	pos.xy = position.xy - ((origin.xy-vec2(0.5,0.5)) * (boundingMax - boundingMin));
+	pos.z = 1.0;
+	pos.w = 1.0;
+	gl_Position = vec4(dot(pos, transformX), dot(pos, transformY), zBias.x, zBias.y);
+
+}

+ 34 - 5
src/Shaders/rect2d.vertex.fx

@@ -57,23 +57,52 @@ void main(void) {
 
 	// notRound case, only five vertices, 0 is center, then the 4 other for perimeter
 	if (properties.z == 0.0) {
+#ifdef Border
+		float w = properties.x;
+		float h = properties.y;
+		vec2 borderOffset = vec2(1.0, 1.0);
+
+		float segi = index;
+		if (index < 4.0) {
+			borderOffset = vec2(1.0 - (borderThickness*2.0 / w), 1.0 - (borderThickness*2.0 / h));
+		}
+		else {
+			segi -= 4.0;
+		}
+
+		if (segi == 0.0) {
+			pos2 = vec2(1.0, 1.0);
+		} 
+		else if (segi == 1.0) {
+			pos2 = vec2(1.0, 0.0);
+		}
+		else if (segi == 2.0) {
+			pos2 = vec2(0.0, 0.0);
+		} 
+		else {
+			pos2 = vec2(0.0, 1.0);
+		}
+		pos2.x = ((pos2.x - 0.5) * borderOffset.x) + 0.5;
+		pos2.y = ((pos2.y - 0.5) * borderOffset.y) + 0.5;
+#else
 		if (index == 0.0) {
 			pos2 = vec2(0.5, 0.5);
-		} 
+		}
 		else if (index == 1.0) {
 			pos2 = vec2(1.0, 1.0);
-		} 
+		}
 		else if (index == 2.0) {
 			pos2 = vec2(1.0, 0.0);
 		}
 		else if (index == 3.0) {
 			pos2 = vec2(0.0, 0.0);
-		} 
+		}
 		else {
 			pos2 = vec2(0.0, 1.0);
 		}
+#endif
 	}
-	else 
+	else
 	{
 #ifdef Border
 		float w = properties.x;
@@ -85,7 +114,7 @@ void main(void) {
 
 		float segi = index;
 		if (index < rsub) {
-			borderOffset = vec2(1.0-(borderThickness*2.0 / w), 1.0-(borderThickness*2.0 / h));
+			borderOffset = vec2(1.0 - (borderThickness*2.0 / w), 1.0 - (borderThickness*2.0 / h));
 		}
 		else {
 			segi -= rsub;

+ 1 - 1
src/States/babylon.depthCullingState.js

@@ -106,8 +106,8 @@ var BABYLON;
                 this._depthMask = true;
                 this._depthTest = true;
                 this._depthFunc = null;
-                this._cull = null;
                 this._cullFace = null;
+                this._cull = null;
                 this._zOffset = 0;
                 this._isDepthTestDirty = true;
                 this._isDepthMaskDirty = true;

+ 1 - 1
src/States/babylon.depthCullingState.ts

@@ -100,8 +100,8 @@
             this._depthMask = true;
             this._depthTest = true;
             this._depthFunc = null;
-            this._cull = null;
             this._cullFace = null;
+            this._cull = null;
             this._zOffset = 0;
 
             this._isDepthTestDirty = true;

+ 2 - 2
src/Tools/babylon.dynamicFloatArray.js

@@ -87,7 +87,7 @@ var BABYLON;
             var sortedAll = this._allEntries.sort(function (a, b) { return a.offset - b.offset; });
             var firstFreeSlotOffset = sortedFree[0].offset;
             var freeZoneSize = 1;
-            var occupiedZoneSize = this.usedElementCount * s;
+            var occupiedZoneSize = (this.usedElementCount + 1) * s;
             var prevOffset = sortedFree[0].offset;
             for (var i = 1; i < sortedFree.length; i++) {
                 // If the first free (which means everything before is occupied) is greater or equal the occupied zone size, it means everything is defragmented, we can quit
@@ -129,7 +129,7 @@ var BABYLON;
                 // Free Zone is smaller or equal so it's no longer a free zone, set the new one to the current location
                 if (freeZoneSize <= usedRange) {
                     firstFreeSlotOffset = curMoveOffset + s;
-                    freeZoneSize = 1;
+                    freeZoneSize = 1 + copyCount;
                 }
                 else {
                     freeZoneSize = ((curOffset - firstFreeSlotOffset) / s) + 1;

+ 2 - 2
src/Tools/babylon.dynamicFloatArray.ts

@@ -97,7 +97,7 @@
 
             let firstFreeSlotOffset = sortedFree[0].offset;
             let freeZoneSize = 1;
-            let occupiedZoneSize = this.usedElementCount * s;
+            let occupiedZoneSize = (this.usedElementCount+1) * s;
 
             let prevOffset = sortedFree[0].offset;
             for (let i = 1; i < sortedFree.length; i++) {
@@ -153,7 +153,7 @@
                 // Free Zone is smaller or equal so it's no longer a free zone, set the new one to the current location
                 if (freeZoneSize <= usedRange) {
                     firstFreeSlotOffset = curMoveOffset + s;
-                    freeZoneSize = 1;
+                    freeZoneSize = 1+copyCount;
                 }
 
                 // Free Zone was bigger, the firstFreeSlotOffset is already up to date, but we need to update its size

+ 86 - 7
src/Tools/babylon.tools.js

@@ -146,6 +146,43 @@ var BABYLON;
                 maximum: maximum
             };
         };
+        Tools.Vector2ArrayFeeder = function (array) {
+            return function (index) {
+                var isFloatArray = (array.BYTES_PER_ELEMENT !== undefined);
+                var length = isFloatArray ? array.length / 2 : array.length;
+                if (index >= length) {
+                    return null;
+                }
+                if (isFloatArray) {
+                    var fa = array;
+                    return new BABYLON.Vector2(fa[index * 2 + 0], fa[index * 2 + 1]);
+                }
+                var a = array;
+                return a[index];
+            };
+        };
+        Tools.ExtractMinAndMaxVector2 = function (feeder, bias) {
+            if (bias === void 0) { bias = null; }
+            var minimum = new BABYLON.Vector2(Number.MAX_VALUE, Number.MAX_VALUE);
+            var maximum = new BABYLON.Vector2(-Number.MAX_VALUE, -Number.MAX_VALUE);
+            var i = 0;
+            var cur = feeder(i++);
+            while (cur) {
+                minimum = BABYLON.Vector2.Minimize(cur, minimum);
+                maximum = BABYLON.Vector2.Maximize(cur, maximum);
+                cur = feeder(i++);
+            }
+            if (bias) {
+                minimum.x -= minimum.x * bias.x + bias.y;
+                minimum.y -= minimum.y * bias.x + bias.y;
+                maximum.x += maximum.x * bias.x + bias.y;
+                maximum.y += maximum.y * bias.x + bias.y;
+            }
+            return {
+                minimum: minimum,
+                maximum: maximum
+            };
+        };
         Tools.MakeArray = function (obj, allowsNullUndefined) {
             if (allowsNullUndefined !== true && (obj === undefined || obj == null))
                 return undefined;
@@ -794,12 +831,17 @@ var BABYLON;
         Tools.getClassName = function (object, isType) {
             if (isType === void 0) { isType = false; }
             var name = null;
-            if (object instanceof Object) {
-                var classObj = isType ? object : Object.getPrototypeOf(object);
-                name = classObj.constructor["__bjsclassName__"];
+            if (!isType && object.getClassName) {
+                name = object.getClassName();
             }
-            if (!name) {
-                name = typeof object;
+            else {
+                if (object instanceof Object) {
+                    var classObj = isType ? object : Object.getPrototypeOf(object);
+                    name = classObj.constructor["__bjsclassName__"];
+                }
+                if (!name) {
+                    name = typeof object;
+                }
             }
             return name;
         };
@@ -811,6 +853,43 @@ var BABYLON;
                 }
             }
         };
+        /**
+         * This method can be used with hashCodeFromStream when your input is an array of values that are either: number, string, boolean or custom type implementing the getHashCode():number method.
+         * @param array
+         */
+        Tools.arrayOrStringFeeder = function (array) {
+            return function (index) {
+                if (index >= array.length) {
+                    return null;
+                }
+                var val = array.charCodeAt ? array.charCodeAt(index) : array[index];
+                if (val && val.getHashCode) {
+                    val = val.getHashCode();
+                }
+                if (typeof val === "string") {
+                    return Tools.hashCodeFromStream(Tools.arrayOrStringFeeder(val));
+                }
+                return val;
+            };
+        };
+        /**
+         * Compute the hashCode of a stream of number
+         * To compute the HashCode on a string or an Array of data types implementing the getHashCode() method, use the arrayOrStringFeeder method.
+         * @param feeder a callback that will be called until it returns null, each valid returned values will be used to compute the hash code.
+         * @return the hash code computed
+         */
+        Tools.hashCodeFromStream = function (feeder) {
+            // Based from here: http://stackoverflow.com/a/7616484/802124
+            var hash = 0;
+            var index = 0;
+            var chr = feeder(index++);
+            while (chr != null) {
+                hash = ((hash << 5) - hash) + chr;
+                hash |= 0; // Convert to 32bit integer
+                chr = feeder(index++);
+            }
+            return hash;
+        };
         Tools.BaseUrl = "";
         Tools.CorsBehavior = "anonymous";
         Tools.UseFallbackTexture = true;
@@ -847,8 +926,8 @@ var BABYLON;
     }
     BABYLON.className = className;
     /**
-     * An implementation of a loop for asynchronous functions.
-     */
+    * An implementation of a loop for asynchronous functions.
+    */
     var AsyncLoop = (function () {
         /**
          * Constroctor.

+ 98 - 10
src/Tools/babylon.tools.ts

@@ -184,6 +184,50 @@
             };
         }
 
+        public static Vector2ArrayFeeder(array: Array<Vector2>|Float32Array): (i) => Vector2 {
+            return (index: number) => {
+                let isFloatArray = ((<Float32Array>array).BYTES_PER_ELEMENT !== undefined);
+                let length = isFloatArray ? array.length / 2 : array.length; 
+
+                if (index >= length) {
+                    return null;
+                }
+
+                if (isFloatArray) {
+                    let fa = <Float32Array>array;
+                    return new Vector2(fa[index * 2 + 0], fa[index * 2 + 1]);
+                } 
+                let a = <Array<Vector2>>array;
+                return a[index];
+            };
+        }
+
+        public static ExtractMinAndMaxVector2(feeder: (index: number) => Vector2, bias: Vector2 = null) : { minimum: Vector2; maximum: Vector2 } {
+            var minimum = new Vector2(Number.MAX_VALUE, Number.MAX_VALUE);
+            var maximum = new Vector2(-Number.MAX_VALUE, -Number.MAX_VALUE);
+
+            let i = 0;
+            let cur = feeder(i++);
+            while (cur) {
+                minimum = Vector2.Minimize(cur, minimum);
+                maximum = Vector2.Maximize(cur, maximum);
+
+                cur = feeder(i++);
+            }
+
+            if (bias) {
+                minimum.x -= minimum.x * bias.x + bias.y;
+                minimum.y -= minimum.y * bias.x + bias.y;
+                maximum.x += maximum.x * bias.x + bias.y;
+                maximum.y += maximum.y * bias.x + bias.y;
+            }
+
+            return {
+                minimum: minimum,
+                maximum: maximum
+            };
+        }
+
         public static MakeArray(obj, allowsNullUndefined?: boolean): Array<any> {
             if (allowsNullUndefined !== true && (obj === undefined || obj == null))
                 return undefined;
@@ -905,14 +949,18 @@
          */
         public static getClassName(object, isType: boolean = false): string {
             let name = null;
-            if (object instanceof Object) {
-                let classObj = isType ? object : Object.getPrototypeOf(object);
-                name = classObj.constructor["__bjsclassName__"];
-            }
-            if (!name) {
-                name = typeof object;
-            }
 
+            if (!isType && object.getClassName) {
+                name = object.getClassName();
+            } else {
+                if (object instanceof Object) {
+                    let classObj = isType ? object : Object.getPrototypeOf(object);
+                    name = classObj.constructor["__bjsclassName__"];
+                }
+                if (!name) {
+                    name = typeof object;
+                }
+            }
             return name;
         }
 
@@ -924,6 +972,46 @@
             }
         }
 
+        /**
+         * This method can be used with hashCodeFromStream when your input is an array of values that are either: number, string, boolean or custom type implementing the getHashCode():number method.
+         * @param array
+         */
+        public static arrayOrStringFeeder(array: any): (i) => number {
+            return (index: number) =>
+            {
+                if (index >= array.length) {
+                    return null;
+                }
+
+                let val = array.charCodeAt ? array.charCodeAt(index) : array[index];
+                if (val && val.getHashCode) {
+                    val = val.getHashCode();
+                }
+                if (typeof val === "string") {
+                    return Tools.hashCodeFromStream(Tools.arrayOrStringFeeder(val));
+                }
+                return val;
+            };
+        }
+
+        /**
+         * Compute the hashCode of a stream of number
+         * To compute the HashCode on a string or an Array of data types implementing the getHashCode() method, use the arrayOrStringFeeder method.
+         * @param feeder a callback that will be called until it returns null, each valid returned values will be used to compute the hash code.
+         * @return the hash code computed
+         */
+        public static hashCodeFromStream(feeder: (index: number) => number): number {
+            // Based from here: http://stackoverflow.com/a/7616484/802124
+            let hash = 0;
+            let index = 0;
+            let chr = feeder(index++);
+            while (chr != null) {
+                hash = ((hash << 5) - hash) + chr;
+                hash |= 0;                          // Convert to 32bit integer
+                chr = feeder(index++);
+            }
+            return hash;
+        }
     }
 
     /**
@@ -939,8 +1027,8 @@
     }
 
     /**
-     * An implementation of a loop for asynchronous functions.
-     */
+    * An implementation of a loop for asynchronous functions.
+    */
     export class AsyncLoop {
         public index: number;
         private _done: boolean;
@@ -1021,4 +1109,4 @@
             }, callback);
         }
     }
-} 
+} 

+ 1 - 0
src/babylon.engine.js

@@ -1169,6 +1169,7 @@ var BABYLON;
             this.resetTextureCache();
             this._currentEffect = null;
             this._depthCullingState.reset();
+            this.setDepthFunctionToLessOrEqual();
             this._alphaState.reset();
             this._cachedVertexBuffers = null;
             this._cachedIndexBuffer = null;

+ 1 - 0
src/babylon.engine.ts

@@ -1440,6 +1440,7 @@
             this._currentEffect = null;
 
             this._depthCullingState.reset();
+            this.setDepthFunctionToLessOrEqual();
             this._alphaState.reset();
 
             this._cachedVertexBuffers = null;