static bool splitAndResume(vertex_info begin, FList <TriIdx> result) { vertex_info v1, v2; if (!findDiagonal(begin, out v1, out v2)) { return(false); } vertex_info v1_copy = v1.Clone(); vertex_info v2_copy = v2.Clone(); v1.next = v2; v2.prev = v1; v1_copy.next.prev = v1_copy; v2_copy.prev.next = v2_copy; v1_copy.prev = v2_copy; v2_copy.next = v1_copy; bool r1 = doTriangulate(v1, result); bool r2 = doTriangulate(v1_copy, result); return(r1 && r2); }
static int windingNumber(vertex_info begin, vector point) { int wn = 0; vertex_info v = begin; do { if (v.p.Y <= point.Y) { if (v.next.p.Y > point.Y && orient2d(v.p, v.next.p, point) > 0) { ++wn; } } else { if (v.next.p.Y <= point.Y && orient2d(v.p, v.next.p, point) < 0) { --wn; } } v = v.next; } while (v != begin); return(wn); }
// 39% of execution time public void updateVertex(vertex_info v) { var spre = v.score; var qpre = v.isCandidate(); v.recompute(); var qpost = v.isCandidate(); var spost = v.score; v.score = spre; if (qpre) { if (qpost) { if (v.score != spre) { changeScore(v, spost); } } else { remove(v); } } else { if (qpost) { push(v); } } }
static bool isLeft(vertex_info a, vertex_info b, vertex_info c) { if (a.idx < b.idx && b.idx < c.idx) { return(orient2d(a.p, b.p, c.p) > 0.0); } else if (a.idx < c.idx && c.idx < b.idx) { return(orient2d(a.p, c.p, b.p) < 0.0); } else if (b.idx < a.idx && a.idx < c.idx) { return(orient2d(b.p, a.p, c.p) < 0.0); } else if (b.idx < c.idx && c.idx < a.idx) { return(orient2d(b.p, c.p, a.p) > 0.0); } else if (c.idx < a.idx && a.idx < b.idx) { return(orient2d(c.p, a.p, b.p) > 0.0); } else { return(orient2d(c.p, b.p, a.p) < 0.0); } }
public void changeScore(vertex_info v, element score) { if (v.score != score) { v.score = score; queue.UpdateHeap(); } }
public void remove(vertex_info v) { var score = v.score; if (v != queue.List[0]) { v.score = queue.List[0].score + 1; queue.UpdateHeap(); } queue.Pop(); v.score = score; }
public static void triangulate <T>(Project <T> project, FList <T> poly, FList <TriIdx> result) { var N = poly.Count; result.Clear(); if (N < 3) { return; } result.Capacity = poly.Count - 2; if (N == 3) { result.Add(new TriIdx(0, 1, 2)); return; } var vinfo = new vertex_info[N]; vinfo[0] = new vertex_info(project(poly[0]), 0); for (int i = 1; i < N - 1; ++i) { vinfo[i] = new vertex_info(project(poly[i]), i); vinfo[i].prev = vinfo[i - 1]; vinfo[i - 1].next = vinfo[i]; } vinfo[N - 1] = new vertex_info(project(poly[N - 1]), N - 1); vinfo[N - 1].prev = vinfo[N - 2]; vinfo[N - 1].next = vinfo[0]; vinfo[0].prev = vinfo[N - 1]; vinfo[N - 2].next = vinfo[N - 1]; for (int i = 0; i < N; ++i) { vinfo[i].recompute(); } var begin = vinfo[0]; removeDegeneracies(ref begin, result); doTriangulate(begin, result); }
public static element triScore(vertex_info p, vertex_info v, vertex_info n) { // range: 0 - 1 element a, b, c; bool convex = isLeft(p, v, n); if (!convex) { return(-1e-5f); } a = (n.p - v.p).Length; b = (p.p - n.p).Length; c = (v.p - p.p).Length; if (a < 1e-10 || b < 1e-10 || c < 1e-10) { return(0); } return(Math.Max(Math.Min((a + b) / c, Math.Min((a + c) / b, (b + c) / a)) - 1, 0)); }
public bool isClipable() { for (vertex_info v_test = next.next; v_test != prev; v_test = v_test.next) { if (v_test.convex) { continue; } if (v_test.p == prev.p || v_test.p == next.p) { continue; } if (v_test.p == p) { if (v_test.next.p == prev.p && v_test.prev.p == next.p) { return(false); } if (v_test.next.p == prev.p || v_test.prev.p == next.p) { continue; } } if (pointInTriangle(prev, this, next, v_test)) { return(false); } } return(true); }
static bool internalToAngle(vertex_info a, vertex_info b, vertex_info c, vector p) { return(internalToAngle(a.p, b.p, c.p, p)); }
static bool findDiagonal(vertex_info begin, out vertex_info v1, out vertex_info v2) { vertex_info t; var heap = new FList <vertex_info>(); v1 = begin; do { heap.Clear(); for (v2 = v1.next.next; v2 != v1.prev; v2 = v2.next) { if (!internalToAngle(v1.next, v1, v1.prev, v2.p) || !internalToAngle(v2.next, v2, v2.prev, v1.p)) { continue; } PriorityQueue <vertex_info> .PushHeap(heap, v2, new vertex_info_l2norm_inc_ordering(v1)); } while (heap.Count != 0) { v2 = PriorityQueue <vertex_info> .PopHeap(heap, new vertex_info_l2norm_inc_ordering(v1)); // test whether v1-v2 is a valid diagonal. var v_min_x = Math.Min(v1.p.X, v2.p.X); var v_max_x = Math.Max(v1.p.X, v2.p.X); bool intersected = false; for (t = v1.next; !intersected && t != v1.prev; t = t.next) { vertex_info u = t.next; if (t == v2 || u == v2) { continue; } var l1 = orient2d(v1.p, v2.p, t.p); var l2 = orient2d(v1.p, v2.p, u.p); if ((l1 > 0.0 && l2 > 0.0) || (l1 < 0.0 && l2 < 0.0)) { // both on the same side; no intersection continue; } var dx13 = v1.p.X - t.p.X; var dy13 = v1.p.Y - t.p.Y; var dx43 = u.p.X - t.p.X; var dy43 = u.p.Y - t.p.Y; var dx21 = v2.p.X - v1.p.X; var dy21 = v2.p.Y - v1.p.Y; var ua_n = dx43 * dy13 - dy43 * dx13; var ub_n = dx21 * dy13 - dy21 * dx13; var u_d = dy43 * dx21 - dx43 * dy21; if (Math.Abs(u_d) < element.Epsilon) { // parallel if (Math.Abs(ua_n) < element.Epsilon) { // colinear if (Math.Max(t.p.X, u.p.X) >= v_min_x && Math.Min(t.p.X, u.p.X) <= v_max_x) { // colinear and intersecting intersected = true; } } } else { // not parallel var ua = ua_n / u_d; var ub = ub_n / u_d; if (0 <= ua && ua <= 1 && 0 <= ub && ub <= 1) { intersected = true; } } } if (!intersected) { // test whether midpoint winding == 1 var mid = (v1.p + v2.p) / 2; if (windingNumber(begin, mid) == 1) { // this diagonal is ok return(true); } } } // couldn't find a diagonal from v1 that was ok. v1 = v1.next; } while (v1 != begin); return(false); }
static int removeDegeneracies(ref vertex_info begin, FList <TriIdx> result) { vertex_info v; vertex_info n; int count = 0; int remain = 0; v = begin; do { v = v.next; ++remain; } while (v != begin); v = begin; do { if (remain < 4) { break; } bool remove = false; if (v.p == v.next.p) { remove = true; } else if (v.p == v.next.next.p) { if (v.next.p == v.next.next.next.p) { // a 'z' in the loop: z (a) b a b c . remove a-b-a . z (a) a b c . remove a-a-b (next loop) . z a b c // z --(a)-- b // / // / // a -- b -- d remove = true; } else { // a 'shard' in the loop: z (a) b a c d . remove a-b-a . z (a) a b c d . remove a-a-b (next loop) . z a b c d // z --(a)-- b // / // / // a -- c -- d // n.b. can only do this if the shard is pointing out of the polygon. i.e. b is outside z-a-c remove = !internalToAngle(v.next.next.next, v, v.prev, v.next.p); } } if (remove) { result.Add(new TriIdx(v.idx, v.next.idx, v.next.next.idx)); n = v.next; if (n == begin) { begin = n.next; } n.remove(); count++; remain--; } else { v = v.next; } } while (v != begin); return(count); }
static bool pointInTriangle(vertex_info a, vertex_info b, vertex_info c, vertex_info d) { return(!isLeft(a, c, d) && !isLeft(b, a, d) && !isLeft(c, b, d)); }
public void push(vertex_info v) { queue.Push(v); }
static bool doTriangulate(vertex_info begin, FList <TriIdx> result) { var vq = new EarQueue(); var v = begin; int remain = 0; do { if (v.isCandidate()) { vq.push(v); } v = v.next; remain++; } while (v != begin); while (remain > 3 && vq.size() != 0) { var v2 = vq.pop(); if (!v2.isClipable()) { v2.failed = true; continue; } continue_clipping: var n = v2.next; var p = v2.prev; result.Add(new TriIdx(v2.prev.idx, v2.idx, v2.next.idx)); v2.remove(); if (v2 == begin) { begin = v2.next; } if (--remain == 3) { break; } vq.updateVertex(n); vq.updateVertex(p); if (n.score < p.score) { var t = n; n = p; p = t; } if (n.score > 0.25 && n.isCandidate() && n.isClipable()) { vq.remove(n); v2 = n; goto continue_clipping; } if (p.score > 0.25 && p.isCandidate() && p.isClipable()) { vq.remove(p); v2 = p; goto continue_clipping; } } if (remain > 3) { remain -= removeDegeneracies(ref begin, result); if (remain > 3) { return(splitAndResume(begin, result)); } } if (remain == 3) { result.Add(new TriIdx(begin.idx, begin.next.idx, begin.next.next.idx)); } var d = begin; do { var n = d.next; d = n; } while (d != begin); return(true); }