// 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: #pragma once #include #if defined(CARVE_DEBUG) # include #endif namespace carve { namespace triangulate { namespace detail { static inline bool axisOrdering(const carve::geom2d::P2 &a, const carve::geom2d::P2 &b, int axis) { return a.v[axis] < b.v[axis] || (a.v[axis] == b.v[axis] && a.v[1-axis] < b.v[1-axis]); } /** * \class order_h_loops * \brief Provides an ordering of hole loops based upon a single * projected axis. * * @tparam project_t A functor which converts vertices to a 2d * projection. * @tparam hole_t A collection of vertices. */ template class order_h_loops { const project_t &project; int axis; public: /** * * @param _project The projection functor. * @param _axis The axis of the 2d projection upon which hole * loops are ordered. */ order_h_loops(const project_t &_project, int _axis) : project(_project), axis(_axis) { } bool operator()(const vert_t &a, const vert_t &b) const { return axisOrdering(project(a), project(b), axis); } bool operator()( const std::pair *, typename std::vector::const_iterator> &a, const std::pair *, typename std::vector::const_iterator> &b) { return axisOrdering(project(*(a.second)), project(*(b.second)), axis); } }; /** * \class heap_ordering * \brief Provides an ordering of vertex indicies in a polygon * loop according to proximity to a vertex. * * @tparam project_t A functor which converts vertices to a 2d * projection. * @tparam vert_t A vertex type. */ template class heap_ordering { const project_t &project; const std::vector &loop; const carve::geom2d::P2 p; int axis; public: /** * * @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. * */ heap_ordering(const project_t &_project, const std::vector &_loop, vert_t _vert, int _axis) : project(_project), loop(_loop), p(_project(_vert)), axis(_axis) { } bool operator()(size_t a, size_t b) const { carve::geom2d::P2 pa = project(loop[a]); carve::geom2d::P2 pb = project(loop[b]); double da = carve::geom::distance2(p, pa); double db = carve::geom::distance2(p, pb); if (da > db) return true; if (da < db) return false; return axisOrdering(pa, pb, axis); } }; /** * \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. */ template void patchHoleIntoPolygon(std::vector &f_loop, unsigned f_loop_attach, const std::pair *, typename std::vector::const_iterator> &hole_attach) { // join the vertex curr of the polygon loop to the hole at // h_loop_connect f_loop.insert(f_loop.begin() + f_loop_attach + 1, hole_attach.first->size() + 2, NULL); typename std::vector::iterator f = f_loop.begin() + f_loop_attach; typename std::vector::const_iterator h = hole_attach.second; while (h != hole_attach.first->end()) { *++f = *h++; } h = hole_attach.first->begin(); typename std::vector::const_iterator he = hole_attach.second; ++he; while (h != he) { *++f = *h++; } *++f = f_loop[f_loop_attach]; } struct vertex_info; /** * \brief Determine whether c is to the left of a->b. */ static inline bool isLeft(const vertex_info *a, const vertex_info *b, const vertex_info *c); /** * \brief Determine whether d is contained in the triangle abc. */ static inline bool pointInTriangle(const vertex_info *a, const vertex_info *b, const vertex_info *c, const vertex_info *d); /** * \class vertex_info * \brief Maintains a linked list of untriangulated vertices * during a triangulation operation. */ struct vertex_info { vertex_info *prev; vertex_info *next; carve::geom2d::P2 p; size_t idx; double score; bool convex; bool failed; vertex_info(const carve::geom2d::P2 &_p, size_t _idx) : prev(NULL), next(NULL), p(_p), idx(_idx), score(0.0), convex(false) { } static double triScore(const vertex_info *p, const vertex_info *v, const vertex_info *n); double calcScore() const; void recompute() { score = calcScore(); convex = isLeft(prev, this, next); failed = false; } bool isCandidate() const { return convex && !failed; } void remove() { next->prev = prev; prev->next = next; } bool isClipable() const; }; static inline bool isLeft(const vertex_info *a, const vertex_info *b, const vertex_info *c) { if (a->idx < b->idx && b->idx < c->idx) { return carve::geom2d::orient2d(a->p, b->p, c->p) > 0.0; } else if (a->idx < c->idx && c->idx < b->idx) { return carve::geom2d::orient2d(a->p, c->p, b->p) < 0.0; } else if (b->idx < a->idx && a->idx < c->idx) { return carve::geom2d::orient2d(b->p, a->p, c->p) < 0.0; } else if (b->idx < c->idx && c->idx < a->idx) { return carve::geom2d::orient2d(b->p, c->p, a->p) > 0.0; } else if (c->idx < a->idx && a->idx < b->idx) { return carve::geom2d::orient2d(c->p, a->p, b->p) > 0.0; } else { return carve::geom2d::orient2d(c->p, b->p, a->p) < 0.0; } } static inline bool pointInTriangle(const vertex_info *a, const vertex_info *b, const vertex_info *c, const vertex_info *d) { return !isLeft(a, c, d) && !isLeft(b, a, d) && !isLeft(c, b, d); } size_t removeDegeneracies(vertex_info *&begin, std::vector &result); bool splitAndResume(vertex_info *begin, std::vector &result); bool doTriangulate(vertex_info *begin, std::vector &result); typedef std::pair vert_edge_t; struct hash_vert_edge_t { size_t operator()(const vert_edge_t &e) const { size_t r = (size_t)e.first; size_t s = (size_t)e.second; return r ^ ((s >> 16) | (s << 16)); } }; static inline vert_edge_t ordered_vert_edge_t(unsigned a, unsigned b) { return (a < b) ? vert_edge_t(a, b) : vert_edge_t(b, a); } struct tri_pair_t { carve::triangulate::tri_idx *a, *b; double score; size_t idx; tri_pair_t() : a(NULL), b(NULL), score(0.0) { } static inline unsigned N(unsigned i) { return (i+1)%3; } static inline unsigned P(unsigned i) { return (i+2)%3; } void findSharedEdge(unsigned &ai, unsigned &bi) const { if (a->v[1] == b->v[0]) { if (a->v[0] == b->v[1]) { ai = 0; bi = 0; } else { ai = 1; bi = 2; } return; } if (a->v[1] == b->v[1]) { if (a->v[0] == b->v[2]) { ai = 0; bi = 1; } else { ai = 1; bi = 0; } return; } if (a->v[1] == b->v[2]) { if (a->v[0] == b->v[0]) { ai = 0; bi = 2; } else { ai = 1; bi = 1; } return; } if (a->v[2] == b->v[0]) { ai = 2; bi = 2; return; } if (a->v[2] == b->v[1]) { ai = 2; bi = 0; return; } if (a->v[2] == b->v[2]) { ai = 2; bi = 1; return; } CARVE_FAIL("should not be reached"); } void flip(vert_edge_t &old_edge, vert_edge_t &new_edge, vert_edge_t perim[4]); template double calc(const project_t &project, const std::vector &poly) { unsigned ai, bi; unsigned cross_ai, cross_bi; unsigned ea, eb; findSharedEdge(ai, bi); #if defined(CARVE_DEBUG) if (carve::geom2d::signedArea(project(poly[a->v[0]]), project(poly[a->v[1]]), project(poly[a->v[2]])) > 0.0 || carve::geom2d::signedArea(project(poly[b->v[0]]), project(poly[b->v[1]]), project(poly[b->v[2]])) > 0.0) { std::cerr << "warning: triangle pair " << this << " contains triangles with incorrect orientation" << std::endl; } #endif cross_ai = P(ai); cross_bi = P(bi); ea = a->v[cross_ai]; eb = b->v[cross_bi]; double side_1 = carve::geom2d::orient2d(project(poly[ea]), project(poly[eb]), project(poly[a->v[ai]])); double side_2 = carve::geom2d::orient2d(project(poly[ea]), project(poly[eb]), project(poly[a->v[N(ai)]])); bool can_flip = (side_1 < 0.0 && side_2 > 0.0) || (side_1 > 0.0 && side_2 < 0.0); if (!can_flip) { score = -1; } else { score = distance(poly[a->v[ai]], poly[b->v[bi]]) - distance(poly[a->v[cross_ai]], poly[b->v[cross_bi]]); } return score; } template double edgeLen(const project_t &project, const std::vector &poly) const { unsigned ai, bi; findSharedEdge(ai, bi); return distance(poly[a->v[ai]], poly[b->v[bi]]); } }; struct max_score { bool operator()(const tri_pair_t *a, const tri_pair_t *b) const { return a->score < b->score; } }; struct tri_pairs_t { typedef std::unordered_map storage_t; storage_t storage; tri_pairs_t() : storage() { }; ~tri_pairs_t() { for (storage_t::iterator i = storage.begin(); i != storage.end(); ++i) { if ((*i).second) delete (*i).second; } } void insert(unsigned a, unsigned b, carve::triangulate::tri_idx *t); template void updateEdge(tri_pair_t *tp, const project_t &project, const std::vector &poly, std::vector &edges, size_t &n) { double old_score = tp->score; double new_score = tp->calc(project, poly); #if defined(CARVE_DEBUG) std::cerr << "tp:" << tp << " old_score: " << old_score << " new_score: " << new_score << std::endl; #endif if (new_score > 0.0 && old_score <= 0.0) { tp->idx = n; edges[n++] = tp; } else if (new_score <= 0.0 && old_score > 0.0) { std::swap(edges[tp->idx], edges[--n]); edges[tp->idx]->idx = tp->idx; } } tri_pair_t *get(vert_edge_t &e) { storage_t::iterator i; i = storage.find(e); if (i == storage.end()) return NULL; return (*i).second; } template void flip(const project_t &project, const std::vector &poly, std::vector &edges, size_t &n) { vert_edge_t old_e, new_e; vert_edge_t perim[4]; #if defined(CARVE_DEBUG) std::cerr << "improvable edges: " << n << std::endl; #endif tri_pair_t *tp = *std::max_element(edges.begin(), edges.begin() + n, max_score()); #if defined(CARVE_DEBUG) std::cerr << "improving tri-pair: " << tp << " with score: " << tp->score << std::endl; #endif tp->flip(old_e, new_e, perim); #if defined(CARVE_DEBUG) std::cerr << "old_e: " << old_e.first << "," << old_e.second << " -> new_e: " << new_e.first << "," << new_e.second << std::endl; #endif CARVE_ASSERT(storage.find(old_e) != storage.end()); storage.erase(old_e); storage[new_e] = tp; std::swap(edges[tp->idx], edges[--n]); edges[tp->idx]->idx = tp->idx; tri_pair_t *tp2; tp2 = get(perim[0]); if (tp2 != NULL) { updateEdge(tp2, project, poly, edges, n); } tp2 = get(perim[1]); if (tp2 != NULL) { CARVE_ASSERT(tp2->a == tp->b || tp2->b == tp->b); if (tp2->a == tp->b) { tp2->a = tp->a; } else { tp2->b = tp->a; } updateEdge(tp2, project, poly, edges, n); } tp2 = get(perim[2]); if (tp2 != NULL) { updateEdge(tp2, project, poly, edges, n); } tp2 = get(perim[3]); if (tp2 != NULL) { CARVE_ASSERT(tp2->a == tp->a || tp2->b == tp->a); if (tp2->a == tp->a) { tp2->a = tp->b; } else { tp2->b = tp->b; } updateEdge(tp2, project, poly, edges, n); } } template size_t getInternalEdges(const project_t &project, const std::vector &poly, std::vector &edges) { size_t count = 0; for (storage_t::iterator i = storage.begin(); i != storage.end();) { tri_pair_t *tp = (*i).second; if (tp->a && tp->b) { tp->calc(project, poly); count++; #if defined(CARVE_DEBUG) std::cerr << "internal edge: " << (*i).first.first << "," << (*i).first.second << " -> " << tp << " " << tp->score << std::endl; #endif ++i; } else { delete (*i).second; storage.erase(i++); } } edges.resize(count); size_t fwd = 0; size_t rev = count; for (storage_t::iterator i = storage.begin(); i != storage.end(); ++i) { tri_pair_t *tp = (*i).second; if (tp && tp->a && tp->b) { if (tp->score > 0.0) { edges[fwd++] = tp; } else { edges[--rev] = tp; } } } CARVE_ASSERT(fwd == rev); return fwd; } }; } template static std::vector incorporateHolesIntoPolygon(const project_t &project, const std::vector &f_loop, const std::vector > &h_loops) { typedef std::vector hole_t; typedef typename std::vector::const_iterator vert_iter; typedef typename std::vector >::const_iterator hole_iter; size_t N = f_loop.size(); // work out how much space to reserve for the patched in holes. for (hole_iter i = h_loops.begin(); i != h_loops.end(); ++i) { N += 2 + (*i).size(); } // this is the vector that we will build the result in. std::vector current_f_loop; current_f_loop.reserve(N); std::vector f_loop_heap; f_loop_heap.reserve(N); for (unsigned i = 0; i < f_loop.size(); ++i) { current_f_loop.push_back(f_loop[i]); } std::vector *, vert_iter> > h_loop_min_vertex; h_loop_min_vertex.reserve(h_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. bool first = true; double min_x, min_y, max_x, max_y; for (hole_iter i = h_loops.begin(); i != h_loops.end(); ++i) { const hole_t &hole(*i); for (vert_iter j = hole.begin(); j != hole.end(); ++j) { carve::geom2d::P2 curr = project(*j); if (first) { min_x = max_x = curr.x; min_y = max_y = curr.y; first = false; } else { min_x = std::min(min_x, curr.x); min_y = std::min(min_y, curr.y); max_x = std::max(max_x, curr.x); max_y = std::max(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 (hole_iter i = h_loops.begin(); i != h_loops.end(); ++i) { const hole_t &hole = *i; vert_iter best_i = std::min_element(hole.begin(), hole.end(), detail::order_h_loops(project, axis)); h_loop_min_vertex.push_back(std::make_pair(&hole, best_i)); } // sort the holes by the minimum vertex. std::sort(h_loop_min_vertex.begin(), h_loop_min_vertex.end(), detail::order_h_loops(project, 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. vert_iter h_loop_connect = h_loop_min_vertex[i].second; carve::geom2d::P2 hole_min = project(*h_loop_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 detail::heap_ordering _heap_ordering(project, current_f_loop, *h_loop_connect, axis); for (size_t j = 0; j < current_f_loop.size(); ++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]).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). 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 carve::geom2d::LineSegment2 test(hole_min, project(current_f_loop[curr])); size_t v1, v2; for (v1 = current_f_loop.size() - 1, v2 = 0; v2 != current_f_loop.size(); v1 = v2++) { // XXX: need to test vertices, not indices, because they may // be duplicated. if (current_f_loop[v1] == current_f_loop[curr] || current_f_loop[v2] == current_f_loop[curr]) continue; carve::geom2d::LineSegment2 test2(project(current_f_loop[v1]), project(current_f_loop[v2])); carve::LineIntersectionClass ic = carve::geom2d::lineSegmentIntersection(test, test2).iclass; if (ic > 0) { // intersection; failed. goto intersection; } } detail::patchHoleIntoPolygon(current_f_loop, curr, h_loop_min_vertex[i]); goto merged; intersection:; } CARVE_FAIL("didn't manage to link up hole!"); merged:; } return current_f_loop; } template void triangulate(const project_t &project, const std::vector &poly, std::vector &result) { std::vector vinfo; const size_t N = poly.size(); 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(project(poly[0]), 0); for (size_t i = 1; i < N-1; ++i) { vinfo[i] = new detail::vertex_info(project(poly[i]), i); vinfo[i]->prev = vinfo[i-1]; vinfo[i-1]->next = vinfo[i]; } vinfo[N-1] = new detail::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 (size_t i = 0; i < N; ++i) { vinfo[i]->recompute(); } detail::vertex_info *begin = vinfo[0]; removeDegeneracies(begin, result); doTriangulate(begin, result); } template void improve(const project_t &project, const std::vector &poly, std::vector &result) { detail::tri_pairs_t tri_pairs; #if defined(CARVE_DEBUG) bool warn = false; for (size_t i = 0; i < result.size(); ++i) { tri_idx &t = result[i]; if (carve::geom2d::signedArea(project(poly[t.a]), project(poly[t.b]), project(poly[t.c])) > 0) { warn = true; } } if (warn) { std::cerr << "carve::triangulate::improve(): Some triangles are incorrectly oriented. Results may be incorrect." << std::endl; } #endif for (size_t i = 0; i < result.size(); ++i) { tri_idx &t = result[i]; tri_pairs.insert(t.a, t.b, &t); tri_pairs.insert(t.b, t.c, &t); tri_pairs.insert(t.c, t.a, &t); } std::vector edges; size_t n = tri_pairs.getInternalEdges(project, poly, edges); for (size_t i = 0; i < n; ++i) { edges[i]->idx = i; } // procedure: // while a tri pair with a positive score exists: // p = pair with highest positive score // flip p, rewriting its two referenced triangles. // negate p's score // for each q in the up-to-four adjoining tri pairs: // update q's tri ptr, if changed, and its score. #if defined(CARVE_DEBUG) double initial_score = 0; for (size_t i = 0; i < edges.size(); ++i) { initial_score += edges[i]->edgeLen(project, poly); } std::cerr << "initial score: " << initial_score << std::endl; #endif while (n) { tri_pairs.flip(project, poly, edges, n); } #if defined(CARVE_DEBUG) double final_score = 0; for (size_t i = 0; i < edges.size(); ++i) { final_score += edges[i]->edgeLen(project, poly); } std::cerr << "final score: " << final_score << std::endl; #endif #if defined(CARVE_DEBUG) if (!warn) { for (size_t i = 0; i < result.size(); ++i) { tri_idx &t = result[i]; CARVE_ASSERT (carve::geom2d::signedArea(project(poly[t.a]), project(poly[t.b]), project(poly[t.c])) <= 0.0); } } #endif } } }