Procházet zdrojové kódy

Merge pull request #1151 from nockawa/engine2d

Canvas2D new features and bug fixes
David Catuhe před 9 roky
rodič
revize
601e9dfbbb

+ 2 - 0
Tools/Gulp/config.json

@@ -166,8 +166,10 @@
       "../../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.linesd.js",
       "../../src/Canvas2d/babylon.canvas2d.js",
       "../../src/Canvas2d/babylon.worldspacecanvas2d.js",
       "../../src/Materials/babylon.shaderMaterial.js",

+ 8 - 6
dist/preview release/what's new.md

@@ -9,8 +9,10 @@
     - 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))
-    - Added a new VR camera : VRDeviceOrientationArcRotateCamera ([temechon](https://github.com/Temechon)
+    - 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))
@@ -38,10 +40,10 @@
     - 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))

+ 4 - 0
src/Canvas2d/babylon.canvas2d.ts

@@ -774,6 +774,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) {

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

@@ -0,0 +1,299 @@
+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, size: Size, subdivisions: number=64, fill?: IBrush2D, border?: IBrush2D, borderThickness: number = 1) {
+            this.setupShape2D(owner, parent, id, position, true, fill, border, borderThickness);
+            this.size = size;
+            this.subdivisions = subdivisions;
+        }
+
+        public static Create(parent: Prim2DBase, id: string, x: number, y: number, width: number, height: number, subdivisions: number = 64, fill?: IBrush2D, border?: IBrush2D, borderThickness?: number): Ellipse2D {
+            Prim2DBase.CheckParent(parent);
+
+            let ellipse = new Ellipse2D();
+            ellipse.setupEllipse2D(parent.owner, parent, id, new Vector2(x, y), new Size(width, height), subdivisions, fill, border, borderThickness);
+            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;
+    }
+}

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

@@ -278,6 +278,7 @@
                     });
 
                     // Everything is updated, clear the dirty list
+                    this._primDirtyList.forEach(p => p._resetPropertiesDirty());
                     this._primDirtyList.splice(0);
                 }
             }
@@ -337,9 +338,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)

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

@@ -0,0 +1,949 @@
+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, points: Vector2[], fillThickness: number, startCap: number, endCap: number, fill?: IBrush2D, border?: IBrush2D, borderThickness: number = 1) {
+            this.setupShape2D(owner, parent, id, position, true, fill, border, borderThickness);
+            this.fillThickness = fillThickness;
+            this.startCap = startCap;
+            this.endCap = endCap;
+            this.points = points;
+            this.closed = false;
+            this._size = Size.Zero();
+            this._boundingMin = Vector2.Zero();
+            this._boundingMax = Vector2.Zero();
+        }
+
+        public static Create(parent: Prim2DBase, id: string, x: number, y: number, points: Vector2[], fillThickness: number, startCap: number = Lines2D.NoCap, endCap: number = Lines2D.NoCap, fill?: IBrush2D, border?: IBrush2D, borderThickness?: number): Lines2D {
+            Prim2DBase.CheckParent(parent);
+
+            let lines = new Lines2D();
+            lines.setupLines2D(parent.owner, parent, id, new Vector2(x, y), points, fillThickness, startCap, endCap, fill, border, borderThickness);
+            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 {

+ 44 - 4
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);
@@ -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) {

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

@@ -123,8 +123,6 @@
                         d.borderGradientTY = ty;
                     }
                 }
-
-
             }
 
             return true;

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

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

@@ -419,7 +419,7 @@
         }
 
         // 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;
 
@@ -456,10 +456,24 @@
             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 subtract(otherVector: Vector2): Vector2 {
             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;
@@ -651,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));
         }
@@ -661,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 {

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

+ 2 - 2
src/Shaders/rect2d.vertex.fx

@@ -72,13 +72,13 @@ void main(void) {
 
 		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);
 		}

+ 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

+ 46 - 1
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;
@@ -938,7 +982,8 @@
          * @param array
          */
         public static arrayOrStringFeeder(array: any): (i) => number {
-            return (index: number) => {
+            return (index: number) =>
+            {
                 if (index >= array.length) {
                     return null;
                 }