/** * * @param _project A functor which converts vertices to a 2d * projection. * @param _loop The polygon loop which indices address. * @param _vert The vertex from which distance is measured. * */ public heap_ordering(Project <T> _project, FList <T> _loop, T _vert, int _axis) { project = _project; loop = _loop; p = _project(_vert); axis = _axis; }
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); }
/// <summary> /// コンストラクタ、比較インターフェースと初期項目を指定して初期化する /// </summary> /// <param name="comparer">比較インターフェース</param> /// <param name="capacity">初期キャパシティ</param> /// <param name="initialValues">初期項目</param> public PriorityQueue(IComparer <T> comparer, int capacity, IEnumerable <T> initialValues) { _List = capacity != 0 ? new FList <T>(capacity) : new FList <T>(); _Comparer = comparer; if (initialValues != null) { _List.AddRange(initialValues); } UpdateHeap(); }
/// <summary> /// 指定されたバイナリヒープリストに値を追加する /// </summary> /// <param name="list">追加先リスト、バイナリヒープになっている必要がある</param> /// <param name="hole">追加先リスト内でのバイナリヒープを満たしていない可能性がある項目インデックス</param> /// <param name="top">追加先リスト内での順序並び替え開始インデックス</param> /// <param name="val">追加値</param> /// <param name="comparer">比較インターフェース</param> static void PushHeapByIndex(FList <T> list, int hole, int top, T val, IComparer <T> comparer) { // hole を親へ移動していく for (var idx = (hole - 1) / 2; top < hole && comparer.Compare(list[idx], val) < 0; idx = (hole - 1) / 2) { list[hole] = list[idx]; hole = idx; } // 最後の hole に追加値を設定 list[hole] = val; }
/// <summary> /// 指定されたバイナリヒープリストへ値を追加する /// </summary> /// <param name="list">追加先リスト、バイナリヒープになっている必要がある</param> /// <param name="value">追加値</param> /// <param name="comparer">比較インターフェース</param> public static void PushHeap(FList <T> list, T value, IComparer <T> comparer) { list.Add(value); var last = list.Count; if (2 <= last) { --last; PushHeapByIndex(list, last, 0, list[last], comparer); } }
public static FList <T> incorporateHolesIntoPolygon <T>(Project <T> project, FList <FList <T> > loops) { if (loops.Count <= 1) { return(loops[0]); } var holes = new FList <FList <T> >(loops); holes.RemoveAt(0); return(incorporateHolesIntoPolygon(project, loops[0], holes)); }
/// <summary> /// 指定されたバイナリヒープリストから優先順位が最大の値を取り出す /// </summary> /// <param name="list">削除元リスト、バイナリヒープになっている必要がある</param> /// <param name="comparer">比較インターフェース</param> /// <returns>取り出された値</returns> public static T PopHeap(FList <T> list, IComparer <T> comparer) // pop *_First to *(_Last - 1) and reheap, using _Pred { var top = list[0]; var last = list.Count - 1; if (1 <= last) { PopHeapHoleByIndex(list, 0, last, list[last], comparer); } list.RemoveAt(last); return(top); }
/// <summary> /// 指定されたリスト内要素をバイナリヒープになるよう並び替える /// </summary> /// <param name="list">並び替えられるリスト</param> /// <param name="comparer">比較インターフェース</param> public static void MakeHeap(FList <T> list, IComparer <T> comparer) { var last = list.Count; if (2 <= last) { for (var hole = last / 2; 0 < hole;) { --hole; PopHeapHoleByIndex(list, hole, last, list[hole], comparer); } } }
/** * \brief Given a polygon loop and a hole loop, and attachment * points, insert the hole loop vertices into the polygon loop. * * @param[in,out] f_loop The polygon loop to incorporate the * hole into. * @param f_loop_attach[in] The index of the vertex of the * polygon loop that the hole is to be * attached to. * @param hole_attach[in] A pair consisting of a pointer to a * hole container and an iterator into * that container reflecting the point of * attachment of the hole. */ static void patchHoleIntoPolygon <T>(FList <T> f_loop, int f_loop_attach, Iterator <T> hole_attach) { // join the vertex curr of the polygon loop to the hole at // h_loop_connect var hole_temp = new T[hole_attach.list.Count + 2]; for (int i = 0, n = hole_attach.list.Count; i <= n; i++) { hole_temp[i] = hole_attach.list[(hole_attach.index + i) % n]; } hole_temp[hole_temp.Length - 1] = f_loop[f_loop_attach]; f_loop.InsertRange(f_loop_attach + 1, hole_temp); }
public FList(FList <T> list) { if (list._Items == EmptyArray) { _Items = EmptyArray; } else { var count = list._Count; var items = new T[count]; Array.Copy(list._Items, items, count); _Items = items; _Count = count; } }
static int MinElementIndex <T>(FList <T> list, IComparer <T> comparer) { T min = list[0]; int index = 0; for (int i = list.Count - 1; i != 0; i--) { var t = list[i]; if (comparer.Compare(t, min) < 0) { min = t; index = i; } } return(index); }
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); }
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); }
public Iterator(FList <T> list, int index) { this.list = list; this.index = index; }
/** * \brief Merge a set of holes into a polygon. (templated) * * Take a polygon loop and a collection of hole loops, and patch * the hole loops into the polygon loop, returning a vector of * vertices from the polygon and holes, which describes a new * polygon boundary with no holes. The new polygon boundary is * constructed via the addition of edges * joining the polygon * loop to the holes. * * This may be applied to arbitrary vertex data (generally * carve::geom3d::Vertex pointers), but a projection function must * be supplied to convert vertices to coordinates in 2-space, in * which the work is performed. * * @tparam project_t A functor which converts vertices to a 2d * projection. * @tparam vert_t The vertex type. * @param project The projection functor. * @param f_loop The polygon loop into which holes are to be * incorporated. * @param h_loops The set of hole loops to be incorporated. * * @return A vector of vertex pointers. */ public static FList <T> incorporateHolesIntoPolygon <T>(Project <T> project, FList <T> f_loop, FList <FList <T> > h_loops) { var N = f_loop.Count; // work out how much space to reserve for the patched in holes. for (int i = h_loops.Count - 1; i != -1; i--) { N += 2 + h_loops[i].Count; } // this is the vector that we will build the result in. var current_f_loop = new FList <T>(N); current_f_loop.AddRange(f_loop); var h_loop_min_vertex = new FList <Iterator <T> >(h_loops.Count); // find the major axis for the holes - this is the axis that we // will sort on for finding vertices on the polygon to join // holes up to. // // it might also be nice to also look for whether it is better // to sort ascending or descending. // // another trick that could be used is to modify the projection // by 90 degree rotations or flipping about an axis. just as // long as we keep the carve::geom3d::Vector pointers for the // real data in sync, everything should be ok. then we wouldn't // need to accomodate axes or sort order in the main loop. // find the bounding box of all the holes. bool first = true; element min_x = element.MaxValue, min_y = element.MaxValue, max_x = element.MinValue, max_y = element.MinValue; for (int i = h_loops.Count - 1; i != -1; i--) { var hole = h_loops[i]; for (int j = hole.Count - 1; j != -1; j--) { var curr = project(hole[j]); if (first) { min_x = max_x = curr.X; min_y = max_y = curr.Y; first = false; } else { if (curr.X < min_x) { min_x = curr.X; } if (curr.Y < min_y) { min_y = curr.Y; } if (max_x < curr.X) { max_x = curr.X; } if (max_y < curr.Y) { max_y = curr.Y; } } } } // choose the axis for which the bbox is largest. int axis = (max_x - min_x) > (max_y - min_y) ? 0 : 1; // for each hole, find the minimum vertex in the chosen axis. for (int i = 0, n = h_loops.Count; i < n; i++) { var hole = h_loops[i]; var best_i = MinElementIndex(hole, new order_h_loops <T>(project, axis)); h_loop_min_vertex.Add(new Iterator <T>(hole, best_i)); } // sort the holes by the minimum vertex. h_loop_min_vertex.Sort(new order_h_loops_iterator <T>(project, axis)); // now, for each hole, find a vertex in the current polygon loop that it can be joined to. for (int i = 0; i < h_loop_min_vertex.Count; ++i) { var N_f_loop = current_f_loop.Count; // the index of the vertex in the hole to connect. var h_loop_connect = h_loop_min_vertex[i].value; var hole_min = project(h_loop_connect); // we order polygon loop vertices that may be able to be connected // to the hole vertex by their distance to the hole vertex var f_loop_heap = new PriorityQueue <int>(new heap_ordering <T>(project, current_f_loop, h_loop_connect, axis), N); for (int j = 0; j < N_f_loop; ++j) { // it is guaranteed that there exists a polygon vertex with // coord < the min hole coord chosen, which can be joined to // the min hole coord without crossing the polygon // boundary. also, because we merge holes in ascending // order, it is also true that this join can never cross // another hole (and that doesn't need to be tested for). if (project(current_f_loop[j])[axis] <= hole_min[axis]) { f_loop_heap.Push(j); } } // we are going to test each potential (according to the // previous test) polygon vertex as a candidate join. we order // by closeness to the hole vertex, so that the join we make // is as small as possible. to test, we need to check the // joining line segment does not cross any other line segment // in the current polygon loop (excluding those that have the // vertex that we are attempting to join with as an endpoint). var attachment_point = current_f_loop.Count; while (f_loop_heap.Count != 0) { var curr = f_loop_heap.Pop(); // test the candidate join from current_f_loop[curr] to hole_min if (!testCandidateAttachment(project, current_f_loop, curr, hole_min)) { continue; } attachment_point = curr; break; } if (attachment_point == current_f_loop.Count) { throw new ApplicationException("didn't manage to link up hole!"); } patchHoleIntoPolygon(current_f_loop, attachment_point, h_loop_min_vertex[i]); } return(current_f_loop); }
static bool testCandidateAttachment <T>(Project <T> project, FList <T> current_f_loop, int curr, vector hole_min) { var SZ = current_f_loop.Count; int prev, next; if (curr == 0) { prev = SZ - 1; next = 1; } else if (curr == SZ - 1) { prev = curr - 1; next = 0; } else { prev = curr - 1; next = curr + 1; } if (!internalToAngle(project(current_f_loop[next]), project(current_f_loop[curr]), project(current_f_loop[prev]), hole_min)) { return(false); } if (hole_min == project(current_f_loop[curr])) { return(true); } var test = new LineSegment2(hole_min, project(current_f_loop[curr])); var v1 = current_f_loop.Count - 1; int v2 = 0; var v1_side = orient2d(test.v1, test.v2, project(current_f_loop[v1])); element v2_side = 0; while (v2 != current_f_loop.Count) { v2_side = orient2d(test.v1, test.v2, project(current_f_loop[v2])); if (v1_side != v2_side) { // XXX: need to test vertices, not indices, because they may // be duplicated. if (project(current_f_loop[v1]) != project(current_f_loop[curr]) && project(current_f_loop[v2]) != project(current_f_loop[curr])) { var test2 = new LineSegment2(project(current_f_loop[v1]), project(current_f_loop[v2])); if (lineSegmentIntersection_simple(test, test2)) { // intersection; failed. return(false); } } } v1 = v2; v1_side = v2_side; ++v2; } return(true); }
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 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); }