// Begin License: // Copyright (C) 2006-2008 Tobias Sargeant (tobias.sargeant@gmail.com). // All rights reserved. // // This file is part of the Carve CSG Library (http://carve-csg.com/) // // This file may be used under the terms of the GNU General Public // License version 2.0 as published by the Free Software Foundation // and appearing in the file LICENSE.GPL2 included in the packaging of // this file. // // This file is provided "AS IS" with NO WARRANTY OF ANY KIND, // INCLUDING THE WARRANTIES OF DESIGN, MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE. // End: #if defined(HAVE_CONFIG_H) # include #endif #include #include #include #include #include namespace { // private code related to hole patching. class order_h_loops_2d { const std::vector > &poly; int axis; public: order_h_loops_2d(const std::vector > &_poly, int _axis) : poly(_poly), axis(_axis) { } bool operator()(const std::pair &a, const std::pair &b) const { return carve::triangulate::detail::axisOrdering(poly[a.first][a.second], poly[b.first][b.second], axis); } }; class heap_ordering_2d { const std::vector > &poly; const std::vector > &loop; const carve::geom2d::P2 p; int axis; public: heap_ordering_2d(const std::vector > &_poly, const std::vector > &_loop, const carve::geom2d::P2 _p, int _axis) : poly(_poly), loop(_loop), p(_p), axis(_axis) { } bool operator()(size_t a, size_t b) const { double da = carve::geom::distance2(p, poly[loop[a].first][loop[a].second]); double db = carve::geom::distance2(p, poly[loop[b].first][loop[b].second]); if (da > db) return true; if (da < db) return false; return carve::triangulate::detail::axisOrdering(poly[loop[a].first][loop[a].second], poly[loop[b].first][loop[b].second], axis); } }; static inline void patchHoleIntoPolygon_2d(std::vector > &f_loop, size_t f_loop_attach, size_t h_loop, size_t h_loop_attach, size_t h_loop_size) { f_loop.insert(f_loop.begin() + f_loop_attach + 1, h_loop_size + 2, std::make_pair(h_loop, 0)); size_t f = f_loop_attach + 1; for (size_t h = h_loop_attach; h != h_loop_size; ++h) { f_loop[f++].second = h; } for (size_t h = 0; h <= h_loop_attach; ++h) { f_loop[f++].second = h; } f_loop[f] = f_loop[f_loop_attach]; } static inline const carve::geom2d::P2 &pvert(const std::vector > &poly, const std::pair &idx) { return poly[idx.first][idx.second]; } } namespace { // private code related to triangulation. using carve::triangulate::detail::vertex_info; struct vertex_info_ordering { bool operator()(const vertex_info *a, const vertex_info *b) const { return a->score < b->score; } }; struct vertex_info_l2norm_inc_ordering { const vertex_info *v; vertex_info_l2norm_inc_ordering(const vertex_info *_v) : v(_v) { } bool operator()(const vertex_info *a, const vertex_info *b) const { return carve::geom::distance2(v->p, a->p) > carve::geom::distance2(v->p, b->p); } }; class EarQueue { std::vector queue; void checkheap() { //#ifdef __GNUC__ // CARVE_ASSERT(std::__is_heap(queue.begin(), queue.end(), vertex_info_ordering())); //#endif } public: EarQueue() { } size_t size() const { return queue.size(); } void push(vertex_info *v) { #if defined(CARVE_DEBUG) checkheap(); #endif queue.push_back(v); std::push_heap(queue.begin(), queue.end(), vertex_info_ordering()); } vertex_info *pop() { #if defined(CARVE_DEBUG) checkheap(); #endif std::pop_heap(queue.begin(), queue.end(), vertex_info_ordering()); vertex_info *v = queue.back(); queue.pop_back(); return v; } void remove(vertex_info *v) { #if defined(CARVE_DEBUG) checkheap(); #endif CARVE_ASSERT(std::find(queue.begin(), queue.end(), v) != queue.end()); double score = v->score; if (v != queue[0]) { v->score = queue[0]->score + 1; std::make_heap(queue.begin(), queue.end(), vertex_info_ordering()); } CARVE_ASSERT(v == queue[0]); std::pop_heap(queue.begin(), queue.end(), vertex_info_ordering()); CARVE_ASSERT(queue.back() == v); queue.pop_back(); v->score = score; } void changeScore(vertex_info *v, double score) { #if defined(CARVE_DEBUG) checkheap(); #endif CARVE_ASSERT(std::find(queue.begin(), queue.end(), v) != queue.end()); if (v->score != score) { v->score = score; std::make_heap(queue.begin(), queue.end(), vertex_info_ordering()); } } // 39% of execution time void updateVertex(vertex_info *v) { double spre = v->score; bool qpre = v->isCandidate(); v->recompute(); bool qpost = v->isCandidate(); double 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); } } } }; int windingNumber(vertex_info *begin, const carve::geom2d::P2 &point) { int wn = 0; vertex_info *v = begin; do { if (v->p.y <= point.y) { if (v->next->p.y > point.y && carve::geom2d::orient2d(v->p, v->next->p, point) > 0.0) { ++wn; } } else { if (v->next->p.y <= point.y && carve::geom2d::orient2d(v->p, v->next->p, point) < 0.0) { --wn; } } v = v->next; } while (v != begin); return wn; } bool internalToAngle(const vertex_info *a, const vertex_info *b, const vertex_info *c, const carve::geom2d::P2 &p) { return carve::geom2d::internalToAngle(a->p, b->p, c->p, p); } bool findDiagonal(vertex_info *begin, vertex_info *&v1, vertex_info *&v2) { vertex_info *t; std::vector heap; 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; heap.push_back(v2); std::push_heap(heap.begin(), heap.end(), vertex_info_l2norm_inc_ordering(v1)); } while (heap.size()) { std::pop_heap(heap.begin(), heap.end(), vertex_info_l2norm_inc_ordering(v1)); v2 = heap.back(); heap.pop_back(); #if defined(CARVE_DEBUG) std::cerr << "testing: " << v1 << " - " << v2 << std::endl; std::cerr << " length = " << (v2->p - v1->p).length() << std::endl; std::cerr << " pos: " << v1->p << " - " << v2->p << std::endl; #endif // test whether v1-v2 is a valid diagonal. double v_min_x = std::min(v1->p.x, v2->p.x); double v_max_x = std::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; double l1 = carve::geom2d::orient2d(v1->p, v2->p, t->p); double l2 = carve::geom2d::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; } double dx13 = v1->p.x - t->p.x; double dy13 = v1->p.y - t->p.y; double dx43 = u->p.x - t->p.x; double dy43 = u->p.y - t->p.y; double dx21 = v2->p.x - v1->p.x; double dy21 = v2->p.y - v1->p.y; double ua_n = dx43 * dy13 - dy43 * dx13; double ub_n = dx21 * dy13 - dy21 * dx13; double u_d = dy43 * dx21 - dx43 * dy21; if (carve::math::ZERO(u_d)) { // parallel if (carve::math::ZERO(ua_n)) { // colinear if (std::max(t->p.x, u->p.x) >= v_min_x && std::min(t->p.x, u->p.x) <= v_max_x) { // colinear and intersecting intersected = true; } } } else { // not parallel double ua = ua_n / u_d; double ub = ub_n / u_d; if (0.0 <= ua && ua <= 1.0 && 0.0 <= ub && ub <= 1.0) { intersected = true; } } #if defined(CARVE_DEBUG) if (intersected) { std::cerr << " failed on edge: " << t << " - " << u << std::endl; std::cerr << " pos: " << t->p << " - " << u->p << std::endl; } #endif } if (!intersected) { // test whether midpoint winding == 1 carve::geom2d::P2 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; } #if defined(CARVE_DEBUG_WRITE_PLY_DATA) void dumpPoly(const std::vector &points, const std::vector &result) { static int step = 0; std::ostringstream filename; filename << "poly_" << step++ << ".svg"; std::cerr << "dumping to " << filename.str() << std::endl; std::ofstream out(filename.str().c_str()); double minx = points[0].x, maxx = points[0].x; double miny = points[0].y, maxy = points[0].y; for (size_t i = 1; i < points.size(); ++i) { minx = std::min(points[i].x, minx); maxx = std::max(points[i].x, maxx); miny = std::min(points[i].y, miny); maxy = std::max(points[i].y, maxy); } double scale = 100 / std::max(maxx-minx, maxy-miny); maxx *= scale; minx *= scale; maxy *= scale; miny *= scale; double width = maxx - minx + 10; double height = maxy - miny + 10; out << "\ \n\ \n\ \n"; out << "" << std::endl; for (size_t i = 0; i < result.size(); ++i) { out << "" << std::endl; } out << "" << std::endl; } #endif } double carve::triangulate::detail::vertex_info::triScore(const vertex_info *p, const vertex_info *v, const vertex_info *n) { // different scoring functions. #if 0 bool convex = isLeft(p, v, n); if (!convex) return -1e-5; double a1 = carve::geom2d::atan2(p->p - v->p) - carve::geom2d::atan2(n->p - v->p); double a2 = carve::geom2d::atan2(v->p - n->p) - carve::geom2d::atan2(p->p - n->p); if (a1 < 0) a1 += M_PI * 2; if (a2 < 0) a2 += M_PI * 2; return std::min(a1, std::min(a2, M_PI - a1 - a2)) / (M_PI / 3); #endif #if 1 // range: 0 - 1 double a, b, c; bool convex = isLeft(p, v, n); if (!convex) return -1e-5; 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.0; return std::max(std::min((a+b)/c, std::min((a+c)/b, (b+c)/a)) - 1.0, 0.0); #endif } double carve::triangulate::detail::vertex_info::calcScore() const { #if 0 // examine only this triangle. double this_tri = triScore(prev, this, next); return this_tri; #endif #if 1 // attempt to look ahead in the neighbourhood to attempt to clip ears that have good neighbours. double this_tri = triScore(prev, this, next); double next_tri = triScore(prev, next, next->next); double prev_tri = triScore(prev->prev, prev, next); return this_tri + std::max(next_tri, prev_tri) * .2; #endif #if 0 // attempt to penalise ears that will require producing a sliver triangle. double score = triScore(prev, this, next); double a1, a2; a1 = carve::geom2d::atan2(prev->p - next->p); a2 = carve::geom2d::atan2(next->next->p - next->p); if (fabs(a1 - a2) < 1e-5) score -= .5; a1 = carve::geom2d::atan2(next->p - prev->p); a2 = carve::geom2d::atan2(prev->prev->p - prev->p); if (fabs(a1 - a2) < 1e-5) score -= .5; return score; #endif } bool carve::triangulate::detail::vertex_info::isClipable() const { for (const 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; } size_t carve::triangulate::detail::removeDegeneracies(vertex_info *&begin, std::vector &result) { vertex_info *v = begin; vertex_info *n; size_t count = 0; do { 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.push_back(carve::triangulate::tri_idx(v->idx, v->next->idx, v->next->next->idx)); n = v->next; if (n == begin) begin = n->next; n->remove(); count++; delete n; continue; } v = v->next; } while (v != begin); return count; } bool carve::triangulate::detail::splitAndResume(vertex_info *begin, std::vector &result) { vertex_info *v1, *v2; #if defined(CARVE_DEBUG_WRITE_PLY_DATA) { std::vector dummy; std::vector dummy_p; vertex_info *v = begin; do { dummy_p.push_back(v->p); v = v->next; } while (v != begin); std::cerr << "input to splitAndResume:" << std::endl; dumpPoly(dummy_p, dummy); } #endif if (!findDiagonal(begin, v1, v2)) return false; vertex_info *v1_copy = new vertex_info(*v1); vertex_info *v2_copy = new vertex_info(*v2); 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; } bool carve::triangulate::detail::doTriangulate(vertex_info *begin, std::vector &result) { #if defined(CARVE_DEBUG) std::cerr << "entering doTriangulate" << std::endl; #endif #if defined(CARVE_DEBUG_WRITE_PLY_DATA) { std::vector dummy; std::vector dummy_p; vertex_info *v = begin; do { dummy_p.push_back(v->p); v = v->next; } while (v != begin); dumpPoly(dummy_p, dummy); } #endif EarQueue vq; vertex_info *v = begin; size_t remain = 0; do { if (v->isCandidate()) vq.push(v); v = v->next; remain++; } while (v != begin); #if defined(CARVE_DEBUG) std::cerr << "remain = " << remain << std::endl; #endif while (vq.size()) { vertex_info *v = vq.pop(); if (!v->isClipable()) { v->failed = true; continue; } continue_clipping: vertex_info *n = v->next; vertex_info *p = v->prev; result.push_back(carve::triangulate::tri_idx(v->prev->idx, v->idx, v->next->idx)); #if defined(CARVE_DEBUG) { std::vector temp; temp.push_back(v->prev->p); temp.push_back(v->p); temp.push_back(v->next->p); std::cerr << "clip " << v << " idx = " << v->idx << " score = " << v->score << " area = " << carve::geom2d::signedArea(temp) << " " << temp[0] << " " << temp[1] << " " << temp[2] << std::endl; } #endif v->remove(); remain--; if (v == begin) begin = v->next; delete v; vq.updateVertex(n); vq.updateVertex(p); if (n->score < p->score) { std::swap(n, p); } if (n->score > 0.25 && n->isCandidate() && n->isClipable()) { vq.remove(n); v = n; #if defined(CARVE_DEBUG) std::cerr << " continue clipping (n), score = " << n->score << std::endl; #endif goto continue_clipping; } if (p->score > 0.25 && p->isCandidate() && p->isClipable()) { vq.remove(p); v = p; #if defined(CARVE_DEBUG) std::cerr << " continue clipping (p), score = " << n->score << std::endl; #endif goto continue_clipping; } #if defined(CARVE_DEBUG) std::cerr << "looking for new start point" << std::endl; std::cerr << "remain = " << remain << std::endl; #endif } #if defined(CARVE_DEBUG) std::cerr << "doTriangulate complete; remain=" << remain << std::endl; #endif bool ret; if (remain > 3) { std::vector temp; temp.reserve(remain); vertex_info *v = begin; do { temp.push_back(v->p); v = v->next; } while (v != begin); if (carve::geom2d::signedArea(temp) == 0) { // XXX: this test will fail in cases where the boundary is // twisted so that a negative area balances a positive area. #if defined(CARVE_DEBUG) std::cerr << "skeleton remains. complete." << std::endl; #endif goto done; } #if defined(CARVE_DEBUG) std::cerr << "before removeDegeneracies: remain=" << remain << std::endl; #endif remain -= removeDegeneracies(begin, result); #if defined(CARVE_DEBUG) std::cerr << "after removeDegeneracies: remain=" << remain << std::endl; #endif } if (remain > 3) { return splitAndResume(begin, result); } else if (remain == 3) { result.push_back(carve::triangulate::tri_idx(begin->idx, begin->next->idx, begin->next->next->idx)); ret = true; } else { ret = true; } done: vertex_info *d = begin; do { vertex_info *n = d->next; delete d; d = n; } while (d != begin); return ret; } bool testCandidateAttachment(const std::vector > &poly, std::vector > ¤t_f_loop, size_t curr, carve::geom2d::P2 hole_min) { const size_t SZ = current_f_loop.size(); if (!carve::geom2d::internalToAngle(pvert(poly, current_f_loop[(curr+1) % SZ]), pvert(poly, current_f_loop[curr]), pvert(poly, current_f_loop[(curr+SZ-1) % SZ]), hole_min)) { return false; } carve::geom2d::LineSegment2 test(hole_min, pvert(poly, current_f_loop[curr])); size_t v1 = current_f_loop.size() - 1; size_t v2 = 0; int v1_side = carve::geom2d::orient2d(test.v1, test.v2, pvert(poly, current_f_loop[v1])); int v2_side = 0; while (v2 != current_f_loop.size()) { v2_side = carve::geom2d::orient2d(test.v1, test.v2, pvert(poly, current_f_loop[v2])); if (v1_side != v2_side) { // XXX: need to test vertices, not indices, because they may // be duplicated. if (pvert(poly, current_f_loop[v1]) != pvert(poly, current_f_loop[curr]) && pvert(poly, current_f_loop[v2]) != pvert(poly, current_f_loop[curr])) { carve::geom2d::LineSegment2 test2(pvert(poly, current_f_loop[v1]), pvert(poly, current_f_loop[v2])); carve::LineIntersectionClass ic = carve::geom2d::lineSegmentIntersection(test, test2).iclass; if (ic > 0) { // intersection; failed. return false; } } } v1 = v2; v1_side = v2_side; ++v2; } return true; } void carve::triangulate::incorporateHolesIntoPolygon( const std::vector > &poly, std::vector > &result, size_t poly_loop, const std::vector &hole_loops) { typedef std::vector loop_t; size_t N = poly[poly_loop].size(); // work out how much space to reserve for the patched in holes. for (size_t i = 0; i < hole_loops.size(); i++) { N += 2 + poly[hole_loops[i]].size(); } // this is the vector that we will build the result in. result.clear(); result.reserve(N); // this is a heap of result indices that defines the vertex test order. std::vector f_loop_heap; f_loop_heap.reserve(N); // add the poly loop to result. for (size_t i = 0; i < poly[poly_loop].size(); ++i) { result.push_back(std::make_pair((size_t)poly_loop, i)); } if (hole_loops.size() == 0) { return; } std::vector > h_loop_min_vertex; h_loop_min_vertex.reserve(hole_loops.size()); // 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. carve::geom2d::P2 h_min, h_max; h_min = h_max = poly[hole_loops[0]][0]; for (size_t i = 0; i < hole_loops.size(); ++i) { const loop_t &hole = poly[hole_loops[i]]; for (size_t j = 0; j < hole.size(); ++j) { assign_op(h_min, h_min, hole[j], carve::util::min_functor()); assign_op(h_max, h_max, hole[j], carve::util::max_functor()); } } // choose the axis for which the bbox is largest. int axis = (h_max.x - h_min.x) > (h_max.y - h_min.y) ? 0 : 1; // for each hole, find the minimum vertex in the chosen axis. for (size_t i = 0; i < hole_loops.size(); ++i) { const loop_t &hole = poly[hole_loops[i]]; size_t best, curr; best = 0; for (curr = 1; curr != hole.size(); ++curr) { if (detail::axisOrdering(hole[curr], hole[best], axis)) { best = curr; } } h_loop_min_vertex.push_back(std::make_pair(hole_loops[i], best)); } // sort the holes by the minimum vertex. std::sort(h_loop_min_vertex.begin(), h_loop_min_vertex.end(), order_h_loops_2d(poly, axis)); // now, for each hole, find a vertex in the current polygon loop that it can be joined to. for (unsigned i = 0; i < h_loop_min_vertex.size(); ++i) { // the index of the vertex in the hole to connect. size_t hole_i = h_loop_min_vertex[i].first; size_t hole_i_connect = h_loop_min_vertex[i].second; carve::geom2d::P2 hole_min = poly[hole_i][hole_i_connect]; f_loop_heap.clear(); // we order polygon loop vertices that may be able to be connected // to the hole vertex by their distance to the hole vertex heap_ordering_2d _heap_ordering(poly, result, hole_min, axis); const size_t SZ = result.size(); for (size_t j = 0; j < SZ; ++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 (pvert(poly, result[j]).v[axis] < hole_min.v[axis]) { f_loop_heap.push_back(j); std::push_heap(f_loop_heap.begin(), f_loop_heap.end(), _heap_ordering); } } // 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). size_t attachment_point = result.size(); while (f_loop_heap.size()) { std::pop_heap(f_loop_heap.begin(), f_loop_heap.end(), _heap_ordering); size_t curr = f_loop_heap.back(); f_loop_heap.pop_back(); // test the candidate join from result[curr] to hole_min if (!testCandidateAttachment(poly, result, curr, hole_min)) { continue; } attachment_point = curr; break; } if (attachment_point == result.size()) { CARVE_FAIL("didn't manage to link up hole!"); } patchHoleIntoPolygon_2d(result, attachment_point, hole_i, hole_i_connect, poly[hole_i].size()); } } std::vector > carve::triangulate::incorporateHolesIntoPolygon(const std::vector > &poly) { #if 1 std::vector > result; std::vector hole_indices; hole_indices.reserve(poly.size() - 1); for (size_t i = 1; i < poly.size(); ++i) { hole_indices.push_back(i); } incorporateHolesIntoPolygon(poly, result, 0, hole_indices); return result; #else typedef std::vector loop_t; size_t N = poly[0].size(); // // work out how much space to reserve for the patched in holes. for (size_t i = 0; i < poly.size(); i++) { N += 2 + poly[i].size(); } // this is the vector that we will build the result in. std::vector > current_f_loop; current_f_loop.reserve(N); // this is a heap of current_f_loop indices that defines the vertex test order. std::vector f_loop_heap; f_loop_heap.reserve(N); // add the poly loop to current_f_loop. for (size_t i = 0; i < poly[0].size(); ++i) { current_f_loop.push_back(std::make_pair((size_t)0, i)); } if (poly.size() == 1) { return current_f_loop; } std::vector > h_loop_min_vertex; h_loop_min_vertex.reserve(poly.size() - 1); // 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. double min_x, min_y, max_x, max_y; min_x = max_x = poly[1][0].x; min_y = max_y = poly[1][0].y; for (size_t i = 1; i < poly.size(); ++i) { const loop_t &hole = poly[i]; for (size_t j = 0; j < hole.size(); ++j) { min_x = std::min(min_x, hole[j].x); min_y = std::min(min_y, hole[j].y); max_x = std::max(max_x, hole[j].x); max_y = std::max(max_y, hole[j].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 (size_t i = 1; i < poly.size(); ++i) { const loop_t &hole = poly[i]; size_t best, curr; best = 0; for (curr = 1; curr != hole.size(); ++curr) { if (detail::axisOrdering(hole[curr], hole[best], axis)) { best = curr; } } h_loop_min_vertex.push_back(std::make_pair(i, best)); } // sort the holes by the minimum vertex. std::sort(h_loop_min_vertex.begin(), h_loop_min_vertex.end(), order_h_loops_2d(poly, axis)); // now, for each hole, find a vertex in the current polygon loop that it can be joined to. for (unsigned i = 0; i < h_loop_min_vertex.size(); ++i) { // the index of the vertex in the hole to connect. size_t hole_i = h_loop_min_vertex[i].first; size_t hole_i_connect = h_loop_min_vertex[i].second; carve::geom2d::P2 hole_min = poly[hole_i][hole_i_connect]; f_loop_heap.clear(); // we order polygon loop vertices that may be able to be connected // to the hole vertex by their distance to the hole vertex heap_ordering_2d _heap_ordering(poly, current_f_loop, hole_min, axis); const size_t SZ = current_f_loop.size(); for (size_t j = 0; j < SZ; ++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 (pvert(poly, current_f_loop[j]).v[axis] < hole_min.v[axis]) { f_loop_heap.push_back(j); std::push_heap(f_loop_heap.begin(), f_loop_heap.end(), _heap_ordering); } } // 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). size_t attachment_point = current_f_loop.size(); while (f_loop_heap.size()) { std::pop_heap(f_loop_heap.begin(), f_loop_heap.end(), _heap_ordering); size_t curr = f_loop_heap.back(); f_loop_heap.pop_back(); // test the candidate join from current_f_loop[curr] to hole_min if (!testCandidateAttachment(poly, current_f_loop, curr, hole_min)) { continue; } attachment_point = curr; break; } if (attachment_point == current_f_loop.size()) { CARVE_FAIL("didn't manage to link up hole!"); } patchHoleIntoPolygon_2d(current_f_loop, attachment_point, hole_i, hole_i_connect, poly[hole_i].size()); } return current_f_loop; #endif } std::vector > > carve::triangulate::mergePolygonsAndHoles(const std::vector > &poly) { std::vector poly_indices, hole_indices; poly_indices.reserve(poly.size()); hole_indices.reserve(poly.size()); for (size_t i = 0; i < poly.size(); ++i) { if (carve::geom2d::signedArea(poly[i]) < 0) { poly_indices.push_back(i); } else { hole_indices.push_back(i); } } std::vector > > result; result.resize(poly_indices.size()); if (hole_indices.size() == 0) { for (size_t i = 0; i < poly.size(); ++i) { result[i].resize(poly[i].size()); for (size_t j = 0; j < poly[i].size(); ++j) { result[i].push_back(std::make_pair(i, j)); } } return result; } if (poly_indices.size() == 1) { incorporateHolesIntoPolygon(poly, result[0], poly_indices[0], hole_indices); return result; } throw carve::exception("not implemented"); } void carve::triangulate::triangulate(const std::vector &poly, std::vector &result) { std::vector vinfo; const size_t N = poly.size(); #if defined(CARVE_DEBUG) std::cerr << "TRIANGULATION BEGINS" << std::endl; #endif #if defined(CARVE_DEBUG_WRITE_PLY_DATA) dumpPoly(poly, result); #endif result.clear(); if (N < 3) { return; } result.reserve(poly.size() - 2); if (N == 3) { result.push_back(tri_idx(0, 1, 2)); return; } vinfo.resize(N); vinfo[0] = new detail::vertex_info(poly[0], 0); for (size_t i = 1; i < N-1; ++i) { vinfo[i] = new detail::vertex_info(poly[i], i); vinfo[i]->prev = vinfo[i-1]; vinfo[i-1]->next = vinfo[i]; } vinfo[N-1] = new detail::vertex_info(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 (size_t i = 0; i < N; ++i) { vinfo[i]->recompute(); } detail::vertex_info *begin = vinfo[0]; removeDegeneracies(begin, result); doTriangulate(begin, result); #if defined(CARVE_DEBUG) std::cerr << "TRIANGULATION ENDS" << std::endl; #endif #if defined(CARVE_DEBUG_WRITE_PLY_DATA) dumpPoly(poly, result); #endif } void carve::triangulate::detail::tri_pair_t::flip(vert_edge_t &old_edge, vert_edge_t &new_edge, vert_edge_t perim[4]) { unsigned ai, bi; unsigned cross_ai, cross_bi; findSharedEdge(ai, bi); old_edge = ordered_vert_edge_t(a->v[ai], b->v[bi]); cross_ai = P(ai); cross_bi = P(bi); new_edge = ordered_vert_edge_t(a->v[cross_ai], b->v[cross_bi]); score = -score; a->v[N(ai)] = b->v[cross_bi]; b->v[N(bi)] = a->v[cross_ai]; perim[0] = ordered_vert_edge_t(a->v[P(ai)], a->v[ai]); perim[1] = ordered_vert_edge_t(a->v[N(ai)], a->v[ai]); // this edge was a b-edge perim[2] = ordered_vert_edge_t(b->v[P(bi)], b->v[bi]); perim[3] = ordered_vert_edge_t(b->v[N(bi)], b->v[bi]); // this edge was an a-edge } void carve::triangulate::detail::tri_pairs_t::insert(unsigned a, unsigned b, carve::triangulate::tri_idx *t) { tri_pair_t *tp; if (a < b) { tp = storage[vert_edge_t(a,b)]; if (!tp) { tp = storage[vert_edge_t(a,b)] = new tri_pair_t; } tp->a = t; } else { tp = storage[vert_edge_t(b,a)]; if (!tp) { tp = storage[vert_edge_t(b,a)] = new tri_pair_t; } tp->b = t; } }