Bladeren bron

More Primitives on the way + Math/Tools

Math: more method on Vector2 that match their Vector3 counterparts.
Tools: adding computeMinAndMax for Vector2

Ellipse is done
Lines2D: still WIP...
nockawa 9 jaren geleden
bovenliggende
commit
dfcb5cf3ef

+ 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",

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

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

@@ -0,0 +1,478 @@
+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, [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 Lines2DInstanceData extends Shape2DInstanceData {
+        constructor(partId: number) {
+            super(partId, 1);
+        }
+    }
+
+    @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 {
+            if (this._sizeDirty) {
+                this._updateSize();
+            }
+            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;
+            this._sizeDirty = 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 {
+
+            // TODO
+            return false;
+        }
+
+        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();
+        }
+
+        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;
+
+            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, halfThickness: number): number => {
+                a.addToRef(b, tangent);
+                tangent.normalize();
+
+                miter.x = -tangent.y;
+                miter.y = tangent.x;
+
+                tps.x = -a.y;
+                tps.y = a.x;
+
+                return halfThickness / 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 store = (array: Float32Array, index: number, p: Vector2, n: Vector2, halfThickness: number, detectFlip?: number) => {
+                // 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 (index * 4 >= array.length) {
+                    return;
+                }
+                array[index * 4 + 0] = p.x + n.x * halfThickness;
+                array[index * 4 + 1] = p.y + n.y * halfThickness;
+                array[index * 4 + 2] = p.x + n.x * -halfThickness;
+                array[index * 4 + 3] = p.y + n.y * -halfThickness;
+
+                // If an index is given we check if the two segments formed between [index+0;dectectFlip+0] and [index+2;dectectFlip+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
+                    if (intersect(array[index * 4 + 0], array[index * 4 + 1], array[detectFlip * 4 + 0], array[detectFlip * 4 + 1], array[index * 4 + 2], array[index * 4 + 3], array[detectFlip * 4 + 2], array[detectFlip * 4 + 3])) {
+                        let n = array[index * 4 + 0];
+                        array[index * 4 + 0] = array[index * 4 + 2];
+                        array[index * 4 + 2] = n;
+
+                        n = array[index * 4 + 1];
+                        array[index * 4 + 1] = array[index * 4 + 3];
+                        array[index * 4 + 3] = n;
+                    }
+                }
+            }
+
+
+            // Need to create WebGL resources for fill part?
+            if (this.fill) {
+                let count = this.points.length;
+                let vbSize = count * 2 * 2;
+                let vb = new Float32Array(vbSize);
+                let ht = this.fillThickness/2;
+                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, 0, this.points[0], curNormal, ht);
+                    }
+
+                    if (!next) { 
+                        perp(lineA, curNormal);
+                        store(vb, i, this.points[i], curNormal, ht, i-1);
+                    } else {
+                        direction(next, cur, lineB);
+
+                        var miterLen = computeMiter(tangent, miter, lineA, lineB, ht);
+                        store(vb, i, this.points[i], miter, miterLen, 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, ht);
+                    store(vb, 0, this.points[0], miter, miterLen2, 1);
+                }
+
+                // Remove the point we added at the beginning
+                if (this.closed) {
+                    this.points.splice(total - 1);
+                }
+
+                renderCache.fillVB = engine.createVertexBuffer(vb);
+
+                let max = (total - (this.closed ? 1 : 0)) * 2;
+                let triCount = (count - (this.closed ? 0 : 1)) * 2;
+                let ib = new Float32Array(triCount * 3);
+                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;
+                }
+
+                renderCache.fillIB = engine.createIndexBuffer(ib);
+                renderCache.fillIndicesCount = triCount * 3;
+
+                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 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: "lines2d", fragment: "lines2d" }, ei.attributes, ei.uniforms, [], ei.defines, null);
+            //}
+
+            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;
+            //}
+            //else if (part.id === Shape2D.SHAPE2D_FILLPARTID) {
+            //    let d = <Lines2DInstanceData>part;
+            //}
+            return true;
+        }
+
+        private _updateSize() {
+            let res = Tools.ExtractMinAndMaxVector2(Tools.Vector2ArrayFeeder(this.points));
+            this._size.width = res.maximum.x - res.minimum.x;
+            this._size.height = res.maximum.y - res.minimum.y;
+            this._sizeDirty = false;
+        }
+
+        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 _size: Size;
+        private _sizeDirty: boolean;
+
+        private _closed: boolean;
+        private _startCap: number;
+        private _endCap: number;
+        private _fillThickness: number;
+        private _points: Vector2[];
+
+
+    }
+}

+ 12 - 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]") + ";";
                 }
             });
@@ -445,8 +456,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;

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

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

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

@@ -0,0 +1,70 @@
+// 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;
+
+#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
+
+#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(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 = position.xy - origin.xy;
+	pos.z = 1.0;
+	pos.w = 1.0;
+	gl_Position = vec4(dot(pos, transformX), dot(pos, transformY), zBias.x, zBias.y);
+
+}

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