瀏覽代碼

Canvas2D improvements & bug fixes

Lines2D: almost there!

Bug fixes:
 - MultiParts Primitive refreshed only the first part
 - Visibility toggle works from Canvas to bottom

Minors:
 - Size data type supported for Shaders InstanceData
 - InstanceData prevent data overwrite of non used properties
nockawa 9 年之前
父節點
當前提交
bfca80555d

+ 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) {

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

+ 490 - 71
src/Canvas2d/babylon.lines2d.ts

@@ -63,7 +63,7 @@
                 let partIndex = instanceInfo._partIndexFromId.get(Shape2D.SHAPE2D_BORDERPARTID.toString());
 
                 engine.enableEffect(this.effectBorder);
-                engine.bindBuffers(this.borderVB, this.borderIB, [1], 4, 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) {
@@ -134,6 +134,16 @@
         constructor(partId: number) {
             super(partId, 1);
         }
+
+        @instanceData()
+        get boundingMin(): Vector2 {
+            return null;
+        }
+
+        @instanceData()
+        get boundingMax(): Vector2 {
+            return null;
+        }
     }
 
     @className("Lines2D")
@@ -153,10 +163,7 @@
         public static endCapProperty: Prim2DPropInfo;
 
         public get actualSize(): Size {
-            if (this._sizeDirty) {
-                this._updateSize();
-            }
-            return this._size;
+            return this.size;
         }
 
         @modelLevelProperty(Shape2D.SHAPE2D_PROPCOUNT + 1, pi => Lines2D.pointsProperty = pi)
@@ -212,8 +219,40 @@
             return false;
         }
 
+        protected get size(): Size {
+            if (this._sizeDirty) {
+                this._updateSize();
+            }
+            return this._size;
+        }
+
+        protected get boundingMin(): Vector2 {
+            if (this._sizeDirty) {
+                this._updateSize();
+            }
+            return this._boundingMin;
+        }
+
+        protected get boundingMax(): Vector2 {
+            if (this._sizeDirty) {
+                this._updateSize();
+            }
+            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);
+            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) {
@@ -254,7 +293,7 @@
             }
 
             let tps = Vector2.Zero();
-            let computeMiter = (tangent: Vector2, miter: Vector2, a: Vector2, b: Vector2, halfThickness: number): number => {
+            let computeMiter = (tangent: Vector2, miter: Vector2, a: Vector2, b: Vector2): number => {
                 a.addToRef(b, tangent);
                 tangent.normalize();
 
@@ -264,7 +303,7 @@
                 tps.x = -a.y;
                 tps.y = a.x;
 
-                return halfThickness / Vector2.Dot(miter, tps);
+                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 => {
@@ -279,39 +318,381 @@
                 return true;
             }
 
-            let store = (array: Float32Array, index: number, p: Vector2, n: Vector2, halfThickness: number, detectFlip?: number) => {
+            let startN: Vector2;
+            let endN: Vector2;
+
+            let store = (array: Float32Array, index: number, max: number, p: Vector2, n: Vector2, halfThickness: number, borderThickness: 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) {
+                if (index >= max) {
                     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.
+                // Store start/end normal, we need it for the cap construction
+                if (index === 0) {
+                    startN = n.clone();
+                } else if (index === max - 1) {
+                    endN = n.clone();
+                }
+
+                let borderMode = borderThickness != null && !isNaN(borderThickness);
+                let off = index * (borderMode ? 8 : 4);
+                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;
+
+                // 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
-                    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;
+                    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 n = array[off + 0];
+                        array[off + 0] = array[off + 2];
+                        array[off + 2] = n;
+
+                        n = array[off + 1];
+                        array[off + 1] = array[off + 3];
+                        array[off + 3] = n;
                     }
                 }
+
+                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);
+                }
             }
+            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 };
+            }
 
-            // 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 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;
+                vb[baseOffset + (index*2) + 0] = v.x;
+                vb[baseOffset + (index*2) + 1] = v.y;
+
+                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, normal: Vector2): { vbsize: number; ibsize: number } => {
+                // Default orientation is toward right, horizontal (1,0), its normal being (0,1). 
+                // Compute the angle from the two vectors to get the rotation amount
+                let angle = Math.acos(Vector2.Dot(normal, new Vector2(0, 1)));
+                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, ht: number, bt?: number) => {
                 let lineA = Vector2.Zero();
                 let lineB = Vector2.Zero();
                 let tangent = Vector2.Zero();
@@ -335,17 +716,17 @@
                     }
 
                     if (i === 1) {
-                        store(vb, 0, this.points[0], curNormal, ht);
+                        store(vb, 0, total, this.points[0], curNormal, ht, bt);
                     }
 
-                    if (!next) { 
+                    if (!next) {
                         perp(lineA, curNormal);
-                        store(vb, i, this.points[i], curNormal, ht, i-1);
+                        store(vb, i, total, this.points[i], curNormal, ht, bt, 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);
+                        var miterLen = computeMiter(tangent, miter, lineA, lineB);
+                        store(vb, i, total, this.points[i], miter, miterLen*ht, miterLen*bt, i - 1);
                     }
                 }
 
@@ -358,20 +739,31 @@
                     direction(next2, cur2, lineB);
                     perp(lineA, curNormal);
 
-                    var miterLen2 = computeMiter(tangent, miter, lineA, lineB, ht);
-                    store(vb, 0, this.points[0], miter, miterLen2, 1);
+                    var miterLen2 = computeMiter(tangent, miter, lineA, lineB);
+                    store(vb, 0, total, this.points[0], miter, miterLen2 * ht, miterLen2 *bt, 1);
                 }
 
                 // Remove the point we added at the beginning
                 if (this.closed) {
                     this.points.splice(total - 1);
                 }
+            }
 
-                renderCache.fillVB = engine.createVertexBuffer(vb);
+            // 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, ht);
 
                 let max = (total - (this.closed ? 1 : 0)) * 2;
                 let triCount = (count - (this.closed ? 0 : 1)) * 2;
-                let ib = new Float32Array(triCount * 3);
+                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;
@@ -382,45 +774,62 @@
                     ib[i * 3 + 5] = (i + 2) % max;
                 }
 
+                buildCap(vb, count * 2 * 2, ib, triCount * 3, this.points[0], this.fillThickness, null, this.startCap, startN);
+                buildCap(vb, (count * 2 * 2) + startCapInfo.vbsize, ib, (triCount * 3) + startCapInfo.ibsize, this.points[total - 1], this.fillThickness, null, this.endCap, endN);
+
+                renderCache.fillVB = engine.createVertexBuffer(vb);
                 renderCache.fillIB = engine.createIndexBuffer(ib);
-                renderCache.fillIndicesCount = triCount * 3;
+                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 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);
+            // 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, ht, bt);
 
-            //    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;
+                let max = (total - (this.closed ? 1 : 0)) * 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 * 6 + 0] = rs + r1;
-            //        ib[i * 6 + 1] = rs + r0;
-            //        ib[i * 6 + 2] = r0;
+                    ib[i * 3 + 3] = i + 0;
+                    ib[i * 3 + 4] = (i + 6) % max;
+                    ib[i * 3 + 5] = (i + 4) % max;
 
-            //        ib[i * 6 + 3] = r1;
-            //        ib[i * 6 + 4] = rs + r1;
-            //        ib[i * 6 + 5] = r0;
-            //    }
+                    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;
+                }
 
-            //    renderCache.borderIB = engine.createIndexBuffer(ib);
-            //    renderCache.borderIndicesCount = (triCount* 3);
+                buildCap(vb, count * 2 * 2 * 2, ib, triCount * 3, this.points[0], this.fillThickness, this.borderThickness, this.startCap, startN);
+                buildCap(vb, (count * 2 * 2 * 2) + startCapInfo.vbsize, ib, (triCount * 3) + startCapInfo.ibsize, this.points[total - 1], this.fillThickness, this.borderThickness, this.endCap, endN);
 
-            //    let ei = this.getDataPartEffectInfo(Shape2D.SHAPE2D_BORDERPARTID, ["index"]);
-            //    renderCache.effectBorder = engine.createEffect({ vertex: "lines2d", fragment: "lines2d" }, ei.attributes, ei.uniforms, [], ei.defines, null);
-            //}
+                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);
+            }
+ 
             return renderCache;
         }
 
@@ -440,17 +849,23 @@
             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;
-            //}
+            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 _updateSize() {
             let res = Tools.ExtractMinAndMaxVector2(Tools.Vector2ArrayFeeder(this.points));
+            this._boundingMin = res.minimum;
+            this._boundingMax = res.maximum;
             this._size.width = res.maximum.x - res.minimum.x;
             this._size.height = res.maximum.y - res.minimum.y;
             this._sizeDirty = false;
@@ -464,6 +879,10 @@
         private static _diamondAnchorCap = 5;
         private static _arrowCap         = 6;
 
+        private static _roundCapSubDiv = 36;
+
+        private _boundingMin: Vector2;
+        private _boundingMax: Vector2;
         private _size: Size;
         private _sizeDirty: boolean;
 

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

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

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

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

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

@@ -394,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 {} 

+ 5 - 7
src/Shaders/lines2d.vertex.fx

@@ -10,10 +10,8 @@ att vec2 zBias;
 att vec4 transformX;
 att vec4 transformY;
 att vec2 origin;
-
-#ifdef Border
-att float borderThickness;
-#endif
+att vec2 boundingMin;
+att vec2 boundingMax;
 
 #ifdef FillSolid
 att vec4 fillSolidColor;
@@ -52,17 +50,17 @@ void main(void) {
 #endif
 
 #ifdef FillGradient
-	float v = dot(vec4(pos2.xy, 1, 1), fillGradientTY);
+	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(pos2.xy, 1, 1), borderGradientTY);
+	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;
+	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);