ソースを参照

Canvas2D, Maths and bug fixes

Canvas2D
Lines2D: done. Except intersection with Cap still missing.

Maths:
Vector2: adding PointInTriangle and DistanceOfPointFromSegment

BugFix: DynamicFloatArray.pack() hoping it's the last one!
nockawa 9 年 前
コミット
71b2e22deb

+ 105 - 53
src/Canvas2d/babylon.lines2d.ts

@@ -174,7 +174,6 @@
         public set points(value: Vector2[]) {
             this._points = value;
             this._levelBoundingInfoDirty = true;
-            this._sizeDirty = true;
         }
 
         @modelLevelProperty(Shape2D.SHAPE2D_PROPCOUNT + 2, pi => Lines2D.fillThicknessProperty = pi)
@@ -214,29 +213,40 @@
         }
 
         protected levelIntersect(intersectInfo: IntersectInfo2D): boolean {
+            let pl = this.points.length;
+            let l = this.closed ? pl + 1 : pl;
 
-            // TODO
+            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 {
-            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;
         }
 
@@ -263,6 +273,8 @@
             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 {
@@ -282,6 +294,10 @@
             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;
@@ -318,24 +334,37 @@
                 return true;
             }
 
-            let startN: Vector2;
-            let endN: Vector2;
+            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);
 
-            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 >= max) {
+                if (off >= array.length) {
                     return;
                 }
 
                 // Store start/end normal, we need it for the cap construction
                 if (index === 0) {
-                    startN = n.clone();
+                    perp(n, startDir);
                 } else if (index === max - 1) {
-                    endN = n.clone();
+                    perp(n, endDir);
+                    endDir.x *= -1;
+                    endDir.y *= -1;
                 }
 
-                let borderMode = borderThickness != null && !isNaN(borderThickness);
-                let off = index * (borderMode ? 8 : 4);
                 let swap = false;
 
                 array[off + 0] = p.x + n.x * halfThickness;
@@ -343,6 +372,9 @@
                 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) {
@@ -350,13 +382,13 @@
                     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];
+                        let tps = array[off + 0];
                         array[off + 0] = array[off + 2];
-                        array[off + 2] = n;
+                        array[off + 2] = tps;
 
-                        n = array[off + 1];
+                        tps = array[off + 1];
                         array[off + 1] = array[off + 3];
-                        array[off + 3] = n;
+                        array[off + 3] = tps;
                     }
                 }
 
@@ -366,8 +398,18 @@
                     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
@@ -448,9 +490,11 @@
 
                 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;
+                let offset = baseOffset + (index*2);
+                vb[offset + 0] = v.x;
+                vb[offset + 1] = v.y;
 
+                updateMinMax(vb, offset);
                 return (baseOffset + index*2) / 2;
             }
 
@@ -458,10 +502,12 @@
                 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 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;
@@ -692,7 +738,7 @@
                 return null;
             }
 
-            let buildLine = (vb: Float32Array, ht: number, bt?: number) => {
+            let buildLine = (vb: Float32Array, contour: Vector2[], ht: number, bt?: number) => {
                 let lineA = Vector2.Zero();
                 let lineB = Vector2.Zero();
                 let tangent = Vector2.Zero();
@@ -716,17 +762,17 @@
                     }
 
                     if (i === 1) {
-                        store(vb, 0, total, this.points[0], curNormal, ht, bt);
+                        store(vb, contour, 0, total, this.points[0], curNormal, ht, bt);
                     }
 
                     if (!next) {
                         perp(lineA, curNormal);
-                        store(vb, i, total, this.points[i], curNormal, ht, bt, i - 1);
+                        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, i, total, this.points[i], miter, miterLen*ht, miterLen*bt, i - 1);
+                        store(vb, contour, i, total, this.points[i], miter, miterLen*ht, miterLen*bt, i - 1);
                     }
                 }
 
@@ -740,7 +786,16 @@
                     perp(lineA, curNormal);
 
                     var miterLen2 = computeMiter(tangent, miter, lineA, lineB);
-                    store(vb, 0, total, this.points[0], miter, miterLen2 * ht, miterLen2 *bt, 1);
+                    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
@@ -749,6 +804,8 @@
                 }
             }
 
+            let contour = new Array<Vector2>();
+
             // Need to create WebGL resources for fill part?
             if (this.fill) {
                 let startCapInfo = getCapSize(this.startCap);
@@ -759,9 +816,9 @@
                 let ht = this.fillThickness / 2;
                 let total = this.points.length;
 
-                buildLine(vb, ht);
+                buildLine(vb, this.border ? null : contour, ht);
 
-                let max = (total - (this.closed ? 1 : 0)) * 2;
+                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) {
@@ -774,8 +831,8 @@
                     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);
+                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);
@@ -796,9 +853,9 @@
                 let bt = this.borderThickness;
                 let total = this.points.length;
 
-                buildLine(vb, ht, bt);
+                buildLine(vb, contour, ht, bt);
 
-                let max = (total - (this.closed ? 1 : 0)) * 2 * 2;
+                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) {
@@ -819,8 +876,8 @@
                     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, 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);
+                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);
@@ -829,7 +886,11 @@
                 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;
         }
 
@@ -862,15 +923,6 @@
             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;
-        }
-
         private static _noCap            = 0;
         private static _roundCap         = 1;
         private static _triangleCap      = 2;
@@ -884,7 +936,7 @@
         private _boundingMin: Vector2;
         private _boundingMax: Vector2;
         private _size: Size;
-        private _sizeDirty: boolean;
+        private _contour: Vector2[];
 
         private _closed: boolean;
         private _startCap: number;

+ 19 - 0
src/Canvas2d/babylon.renderablePrim2d.ts

@@ -527,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) {

+ 20 - 0
src/Math/babylon.math.ts

@@ -665,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));
         }
@@ -675,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 {

+ 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