/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ #ifndef CDT_vW1vZ0lO8rS4gY4uI4fB #define CDT_vW1vZ0lO8rS4gY4uI4fB #include "mcut/internal/cdt/kdtree.h" #include "mcut/internal/cdt/utils.h" #include #include #include #include #include #include #include /// Namespace containing triangulation functionality namespace cdt { /// @addtogroup API /// @{ /** * Enum of strategies specifying order in which a range of vertices is inserted * @note vertex_insertion_order_t::RANDOM will only randomize order of * inserting in triangulation, vertex indices will be preserved as they were * specified in the final triangulation */ struct vertex_insertion_order_t { /** * The Enum itself * @note needed to pre c++11 compilers that don't support 'class enum' */ enum Enum { RANDOM, ///< vertices will be inserted in random order AS_GIVEN, ///< vertices will be inserted in the same order as provided }; }; /// Enum of what type of geometry used to embed triangulation into struct super_geometry_type_t { /** * The Enum itself * @note needed to pre c++11 compilers that don't support 'class enum' */ enum Enum { SUPER_TRIANGLE, ///< conventional super-triangle CUSTOM, ///< user-specified custom geometry (e.g., grid) }; }; /** * Enum of strategies for treating intersecting constraint edges */ struct action_on_intersecting_constraint_edges_t { /** * The Enum itself * @note needed to pre c++11 compilers that don't support 'class enum' */ enum Enum { IGNORE, ///< constraint edge intersections are not checked RESOLVE, ///< constraint edge intersections are resolved }; }; /** * Type used for storing layer depths for triangles * @note layer_depth_t should support 60K+ layers, which could be to much or * too little for some use cases. Feel free to re-define this typedef. */ typedef unsigned short layer_depth_t; typedef layer_depth_t boundary_overlap_count_t; namespace detail { /// Needed for c++03 compatibility (no uniform initialization available) template std::array arr3(const T& v0, const T& v1, const T& v2) { const std::array out = { v0, v1, v2 }; return out; } namespace defaults { const std::size_t nTargetVerts = 0; const super_geometry_type_t::Enum superGeomType = super_geometry_type_t::SUPER_TRIANGLE; const vertex_insertion_order_t::Enum vertexInsertionOrder = vertex_insertion_order_t::RANDOM; const action_on_intersecting_constraint_edges_t::Enum intersectingEdgesStrategy = action_on_intersecting_constraint_edges_t::IGNORE; const float minDistToConstraintEdge(0); } // namespace defaults // add element to 'to' if not already in 'to' template void insert_unique(std::vector& to, const T& elem) { if (std::find(to.begin(), to.end(), elem) == to.end()) { to.push_back(elem); } } // add elements of 'from' that are not present in 'to' to 'to' template void insert_unique( std::vector& to, const std::vector& from) { typedef typename std::vector::const_iterator Cit; to.reserve(to.size() + from.size()); for (Cit cit = from.begin(); cit != from.end(); ++cit) { insert_unique(to, *cit); } } } // namespace detail /** * @defgroup triangulator_t triangulator_t Class * Class performing triangulations. */ /// @{ /** * Data structure representing a 2D constrained Delaunay triangulation * * @tparam T type of vertex coordinates (e.g., float, double) * @tparam TNearPointLocator class providing locating near point for efficiently * inserting new points. Provides methods: 'add_point(vPos, iV)' and * 'nearPoint(vPos) -> iV' */ template > class triangulator_t { public: // typedef std::vector> vec2_vector_t; ///< Vertices vector std::vector> vertices; ///< triangulation's vertices std::vector triangles; ///< triangulation's triangles std::unordered_set fixedEdges; ///< triangulation's constraints (fixed edges) /** * triangles adjacent to each vertex * @note will be reset to empty when super-triangle is removed and * triangulation is finalized. To re-calculate adjacent triangles use * cdt::get_vertex_to_triangles_map helper */ std::vector> vertTris; /** Stores count of overlapping boundaries for a fixed edge. If no entry is * present for an edge: no boundaries overlap. * @note map only has entries for fixed for edges that represent overlapping * boundaries * @note needed for handling depth calculations and hole-removel in case of * overlapping boundaries */ std::unordered_map overlapCount; /** Stores list of original edges represented by a given fixed edge * @note map only has entries for edges where multiple original fixed edges * overlap or where a fixed edge is a part of original edge created by * conforming Delaunay triangulation vertex insertion */ std::unordered_map> pieceToOriginals; /*____ API _____*/ /// Default constructor triangulator_t(); /** * Constructor * @param vertexInsertionOrder strategy used for ordering vertex insertions */ triangulator_t(vertex_insertion_order_t::Enum vertexInsertionOrder); /** * Constructor * @param vertexInsertionOrder strategy used for ordering vertex insertions * @param intersectingEdgesStrategy strategy for treating intersecting * constraint edges * @param minDistToConstraintEdge distance within which point is considered * to be lying on a constraint edge. Used when adding constraints to the * triangulation. */ triangulator_t( vertex_insertion_order_t::Enum vertexInsertionOrder, action_on_intersecting_constraint_edges_t::Enum intersectingEdgesStrategy, T minDistToConstraintEdge); /** * Constructor * @param vertexInsertionOrder strategy used for ordering vertex insertions * @param nearPtLocator class providing locating near point for efficiently * inserting new points * @param intersectingEdgesStrategy strategy for treating intersecting * constraint edges * @param minDistToConstraintEdge distance within which point is considered * to be lying on a constraint edge. Used when adding constraints to the * triangulation. */ triangulator_t( vertex_insertion_order_t::Enum vertexInsertionOrder, const TNearPointLocator& nearPtLocator, action_on_intersecting_constraint_edges_t::Enum intersectingEdgesStrategy, T minDistToConstraintEdge); /** * Insert custom point-types specified by iterator range and X/Y-getters * @tparam TVertexIter iterator that dereferences to custom point type * @tparam TGetVertexCoordX function object getting x coordinate from * vertex. Getter signature: const TVertexIter::value_type& -> T * @tparam TGetVertexCoordY function object getting y coordinate from * vertex. Getter signature: const TVertexIter::value_type& -> T * @param first beginning of the range of vertices to add * @param last end of the range of vertices to add * @param get_x_coord getter of X-coordinate * @param get_y_coord getter of Y-coordinate */ template < typename TVertexIter, typename TGetVertexCoordX, typename TGetVertexCoordY> void insert_vertices( TVertexIter first, TVertexIter last, TGetVertexCoordX get_x_coord, TGetVertexCoordY get_y_coord); /** * Insert vertices into triangulation * @param vertices vector of vertices to insert */ void insert_vertices(const std::vector>& vertices); /** * Insert constraints (custom-type fixed edges) into triangulation * @note Each fixed edge is inserted by deleting the triangles it crosses, * followed by the triangulation of the polygons on each side of the edge. * No new vertices are inserted. * @note If some edge appears more than once in the input this means that * multiple boundaries overlap at the edge and impacts how hole detection * algorithm of triangulator_t::erase_outer_triangles_and_holes works. * Make sure there are no erroneous duplicates. * @tparam TEdgeIter iterator that dereferences to custom edge type * @tparam TGetEdgeVertexStart function object getting start vertex index * from an edge. * Getter signature: const TEdgeIter::value_type& -> std::uint32_t * @tparam TGetEdgeVertexEnd function object getting end vertex index from * an edge. Getter signature: const TEdgeIter::value_type& -> std::uint32_t * @param first beginning of the range of edges to add * @param last end of the range of edges to add * @param getStart getter of edge start vertex index * @param getEnd getter of edge end vertex index */ template < typename TEdgeIter, typename TGetEdgeVertexStart, typename TGetEdgeVertexEnd> void insert_edges( TEdgeIter first, TEdgeIter last, TGetEdgeVertexStart getStart, TGetEdgeVertexEnd getEnd); /** * Insert constraint edges into triangulation * @note Each fixed edge is inserted by deleting the triangles it crosses, * followed by the triangulation of the polygons on each side of the edge. * No new vertices are inserted. * @note If some edge appears more than once in the input this means that * multiple boundaries overlap at the edge and impacts how hole detection * algorithm of triangulator_t::erase_outer_triangles_and_holes works. * Make sure there are no erroneous duplicates. * @tparam edges constraint edges */ void insert_edges(const std::vector& edges); /*! * Returns: * - intersected triangle index * - index of point on the left of the line * - index of point on the right of the line * If left point is right on the line: no triangle is intersected: * - triangle index is no-neighbor (invalid) * - index of point on the line * - index of point on the right of the line */ std::tuple get_intersected_triangle( const std::uint32_t iA, const std::vector& candidates, const vec2_& a, const vec2_& b, const T orientationTolerance) const { typedef std::vector::const_iterator TriIndCit; for (TriIndCit it = candidates.begin(); it != candidates.end(); ++it) { const std::uint32_t iT = *it; const triangle_t t = triangles[iT]; const std::uint32_t i = get_vertex_index(t, iA); const std::uint32_t iP2 = t.vertices[ccw(i)]; const T orientP2 = orient2d(vertices[iP2], a, b); const point_to_line_location_t::Enum locP2 = classify_orientation(orientP2); if (locP2 == point_to_line_location_t::RIGHT_SIDE) { const std::uint32_t iP1 = t.vertices[cw(i)]; const T orientP1 = orient2d(vertices[iP1], a, b); const point_to_line_location_t::Enum locP1 = classify_orientation(orientP1); if (locP1 == point_to_line_location_t::COLLINEAR) { return std::make_tuple(null_neighbour, iP1, iP1); } if (locP1 == point_to_line_location_t::LEFT_SIDE) { if (orientationTolerance) { T closestOrient; std::uint32_t iClosestP; if (std::abs(orientP1) <= std::abs(orientP2)) { closestOrient = orientP1; iClosestP = iP1; } else { closestOrient = orientP2; iClosestP = iP2; } if (classify_orientation( closestOrient, orientationTolerance) == point_to_line_location_t::COLLINEAR) { return std::make_tuple(null_neighbour, iClosestP, iClosestP); } } return std::make_tuple(iT, iP1, iP2); } } } throw std::runtime_error("Could not find vertex triangle intersected by " "edge. Note: can be caused by duplicate points."); } /// Returns indices of four resulting triangles /* Inserting a point on the edge between two triangles * T1 (top) v1 * /|\ * n1 / | \ n4 * / | \ * / T1' | Tnew1\ * v2-------v-------v4 * \ Tnew2| T2' / * \ | / * n2 \ | / n3 * \|/ * T2 (bottom) v3 */ std::stack insert_point_on_edge( const std::uint32_t v, const std::uint32_t iT1, const std::uint32_t iT2) { const std::uint32_t iTnew1 = add_triangle(); const std::uint32_t iTnew2 = add_triangle(); triangle_t& t1 = triangles[iT1]; triangle_t& t2 = triangles[iT2]; std::uint32_t i = get_opposite_vertex_index(t1, iT2); const std::uint32_t v1 = t1.vertices[i]; const std::uint32_t v2 = t1.vertices[ccw(i)]; const std::uint32_t n1 = t1.neighbors[i]; const std::uint32_t n4 = t1.neighbors[cw(i)]; i = get_opposite_vertex_index(t2, iT1); const std::uint32_t v3 = t2.vertices[i]; const std::uint32_t v4 = t2.vertices[ccw(i)]; const std::uint32_t n3 = t2.neighbors[i]; const std::uint32_t n2 = t2.neighbors[cw(i)]; // add new triangles and change existing ones using detail::arr3; t1 = triangle_t::make(arr3(v1, v2, v), arr3(n1, iTnew2, iTnew1)); t2 = triangle_t::make(arr3(v3, v4, v), arr3(n3, iTnew1, iTnew2)); triangles[iTnew1] = triangle_t::make(arr3(v1, v, v4), arr3(iT1, iT2, n4)); triangles[iTnew2] = triangle_t::make(arr3(v3, v, v2), arr3(iT2, iT1, n2)); // make and add new vertex add_adjacent_triangles(v, iT1, iTnew2, iT2, iTnew1); // adjust neighboring triangles and vertices change_neighbour(n4, iT1, iTnew1); change_neighbour(n2, iT2, iTnew2); add_adjacent_triangle(v1, iTnew1); add_adjacent_triangle(v3, iTnew2); remove_adjacent_triangle(v2, iT2); add_adjacent_triangle(v2, iTnew2); remove_adjacent_triangle(v4, iT1); add_adjacent_triangle(v4, iTnew1); // return newly added triangles std::stack newTriangles; newTriangles.push(iT1); newTriangles.push(iTnew2); newTriangles.push(iT2); newTriangles.push(iTnew1); return newTriangles; } std::array trianglesAt(const vec2_& pos) const { std::array out = { null_neighbour, null_neighbour }; for (std::uint32_t i = std::uint32_t(0); i < std::uint32_t(triangles.size()); ++i) { const triangle_t& t = triangles[i]; const vec2_& v1 = vertices[t.vertices[0]]; const vec2_& v2 = vertices[t.vertices[1]]; const vec2_& v3 = vertices[t.vertices[2]]; const point_to_triangle_location_t::Enum loc = locate_point_wrt_triangle(pos, v1, v2, v3); if (loc == point_to_triangle_location_t::OUTSIDE) continue; out[0] = i; if (check_on_edge(loc)) out[1] = t.neighbors[edge_neighbour(loc)]; return out; } throw std::runtime_error("No triangle was found at position"); } /** * Ensure that triangulation conforms to constraints (fixed edges) * @note For each fixed edge that is not present in the triangulation its * midpoint is recursively added until the original edge is represented by a * sequence of its pieces. New vertices are inserted. * @note If some edge appears more than once the input this * means that multiple boundaries overlap at the edge and impacts how hole * detection algorithm of triangulator_t::erase_outer_triangles_and_holes works. * Make sure there are no erroneous duplicates. * @tparam TEdgeIter iterator that dereferences to custom edge type * @tparam TGetEdgeVertexStart function object getting start vertex index * from an edge. * Getter signature: const TEdgeIter::value_type& -> std::uint32_t * @tparam TGetEdgeVertexEnd function object getting end vertex index from * an edge. Getter signature: const TEdgeIter::value_type& -> std::uint32_t * @param first beginning of the range of edges to add * @param last end of the range of edges to add * @param getStart getter of edge start vertex index * @param getEnd getter of edge end vertex index */ template < typename TEdgeIter, typename TGetEdgeVertexStart, typename TGetEdgeVertexEnd> void conform_to_edges( TEdgeIter first, TEdgeIter last, TGetEdgeVertexStart getStart, TGetEdgeVertexEnd getEnd); /*! * Returns: * - intersected triangle index * - index of point on the left of the line * - index of point on the right of the line * If left point is right on the line: no triangle is intersected: * - triangle index is no-neighbor (invalid) * - index of point on the line * - index of point on the right of the line */ std::tuple TintersectedTriangle( const std::uint32_t iA, const std::vector& candidates, const vec2_& a, const vec2_& b, const T orientationTolerance = T(0)) const { typedef std::vector::const_iterator TriIndCit; for (TriIndCit it = candidates.begin(); it != candidates.end(); ++it) { const std::uint32_t iT = *it; const triangle_t t = triangles[iT]; const std::uint32_t i = get_vertex_index(t, iA); const std::uint32_t iP2 = t.vertices[ccw(i)]; const T orientP2 = orient2D(vertices[iP2], a, b); const point_to_line_location_t::Enum locP2 = classify_orientation(orientP2); if (locP2 == point_to_line_location_t::RIGHT_SIDE) { const std::uint32_t iP1 = t.vertices[cw(i)]; const T orientP1 = orient2D(vertices[iP1], a, b); const point_to_line_location_t::Enum locP1 = classify_orientation(orientP1); if (locP1 == point_to_line_location_t::COLLINEAR) { return std::make_tuple(null_neighbour, iP1, iP1); } if (locP1 == point_to_line_location_t::LEFT_SIDE) { if (orientationTolerance) { T closestOrient; std::uint32_t iClosestP; if (std::abs(orientP1) <= std::abs(orientP2)) { closestOrient = orientP1; iClosestP = iP1; } else { closestOrient = orientP2; iClosestP = iP2; } if (classify_orientation( closestOrient, orientationTolerance) == point_to_line_location_t::COLLINEAR) { return std::make_tuple(null_neighbour, iClosestP, iClosestP); } } return std::make_tuple(iT, iP1, iP2); } } } throw std::runtime_error("Could not find vertex triangle intersected by " "edge. Note: can be caused by duplicate points."); } /// Returns indices of three resulting triangles /* Insert point into triangle: split into 3 triangles: * - create 2 new triangles * - re-use old triangle for the 3rd * v3 * / | \ * / | \ <-- original triangle (t) * / | \ * n3 / | \ n2 * /newT2|newT1\ * / v \ * / __/ \__ \ * / __/ \__ \ * / _/ t' \_ \ * v1 ___________________ v2 * n1 */ std::stack insert_point_in_triangle( const std::uint32_t v, const std::uint32_t iT) { const std::uint32_t iNewT1 = add_triangle(); const std::uint32_t iNewT2 = add_triangle(); triangle_t& t = triangles[iT]; const std::array vv = t.vertices; const std::array nn = t.neighbors; const std::uint32_t v1 = vv[0], v2 = vv[1], v3 = vv[2]; const std::uint32_t n1 = nn[0], n2 = nn[1], n3 = nn[2]; // make two new triangles and convert current triangle to 3rd new // triangle using detail::arr3; triangles[iNewT1] = triangle_t::make(arr3(v2, v3, v), arr3(n2, iNewT2, iT)); triangles[iNewT2] = triangle_t::make(arr3(v3, v1, v), arr3(n3, iT, iNewT1)); t = triangle_t::make(arr3(v1, v2, v), arr3(n1, iNewT1, iNewT2)); // make and add a new vertex add_adjacent_triangles(v, iT, iNewT1, iNewT2); // adjust lists of adjacent triangles for v1, v2, v3 add_adjacent_triangle(v1, iNewT2); add_adjacent_triangle(v2, iNewT1); remove_adjacent_triangle(v3, iT); add_adjacent_triangle(v3, iNewT1); add_adjacent_triangle(v3, iNewT2); // change triangle neighbor's neighbors to new triangles change_neighbour(n2, iT, iNewT1); change_neighbour(n3, iT, iNewT2); // return newly added triangles std::stack newTriangles; newTriangles.push(iT); newTriangles.push(iNewT1); newTriangles.push(iNewT2); return newTriangles; } std::array walking_search_triangle_at( const vec2_& pos) const { std::array out = { null_neighbour, null_neighbour }; // Query for a vertex close to pos, to start the search const std::uint32_t startVertex = m_nearPtLocator.nearPoint(pos, vertices); const std::uint32_t iT = walk_triangles(startVertex, pos); // Finished walk, locate point in current triangle const triangle_t& t = triangles[iT]; const vec2_& v1 = vertices[t.vertices[0]]; const vec2_& v2 = vertices[t.vertices[1]]; const vec2_& v3 = vertices[t.vertices[2]]; const point_to_triangle_location_t::Enum loc = locate_point_wrt_triangle(pos, v1, v2, v3); if (loc == point_to_triangle_location_t::OUTSIDE) throw std::runtime_error("No triangle was found at position"); out[0] = iT; if (check_on_edge(loc)) out[1] = t.neighbors[edge_neighbour(loc)]; return out; } /** * Ensure that triangulation conforms to constraints (fixed edges) * @note For each fixed edge that is not present in the triangulation its * midpoint is recursively added until the original edge is represented by a * sequence of its pieces. New vertices are inserted. * @note If some edge appears more than once the input this * means that multiple boundaries overlap at the edge and impacts how hole * detection algorithm of triangulator_t::erase_outer_triangles_and_holes works. * Make sure there are no erroneous duplicates. * @tparam edges edges to conform to */ void conform_to_edges(const std::vector& edges); /** * Erase triangles adjacent to super triangle * * @note does nothing if custom geometry is used */ void eraseSuperTriangle(); /// Erase triangles outside of constrained boundary using growing void erase_outer_triangles(); /** * Erase triangles outside of constrained boundary and auto-detected holes * * @note detecting holes relies on layer peeling based on layer depth * @note supports overlapping or touching boundaries */ void erase_outer_triangles_and_holes(); /** * Call this method after directly setting custom super-geometry via * vertices and triangles members */ void initialise_with_custom_supergeometry(); /** * Check if the triangulation was finalized with `erase...` method and * super-triangle was removed. * @return true if triangulation is finalized, false otherwise */ bool is_finalized() const; /** * Calculate depth of each triangle in constraint triangulation. Supports * overlapping boundaries. * * Perform depth peeling from super triangle to outermost boundary, * then to next boundary and so on until all triangles are traversed.@n * For example depth is: * - 0 for triangles outside outermost boundary * - 1 for triangles inside boundary but outside hole * - 2 for triangles in hole * - 3 for triangles in island and so on... * @return vector where element at index i stores depth of i-th triangle */ std::vector calculate_triangle_depths() const { std::vector triDepths( triangles.size(), std::numeric_limits::max()); std::stack seeds(std::deque(1, vertTris[0].front())); layer_depth_t layerDepth = 0; layer_depth_t deepestSeedDepth = 0; std::unordered_map> seedsByDepth; do { const std::unordered_map& newSeeds = peel_layer(seeds, layerDepth, triDepths); seedsByDepth.erase(layerDepth); typedef std::unordered_map::const_iterator Iter; for (Iter it = newSeeds.begin(); it != newSeeds.end(); ++it) { deepestSeedDepth = std::max(deepestSeedDepth, it->second); seedsByDepth[it->second].insert(it->first); } const std::unordered_set& nextLayerSeeds = seedsByDepth[layerDepth + 1]; seeds = std::stack( std::deque(nextLayerSeeds.begin(), nextLayerSeeds.end())); ++layerDepth; } while (!seeds.empty() || deepestSeedDepth > layerDepth); return triDepths; } /** * @defgroup Advanced Advanced triangulator_t Methods * Advanced methods for manually modifying the triangulation from * outside. Please only use them when you know what you are doing. */ /// @{ /** * Flip an edge between two triangle. * @note Advanced method for manually modifying the triangulation from * outside. Please call it when you know what you are doing. * @param iT first triangle * @param iTopo second triangle */ void do_edgeflip(std::uint32_t iT, std::uint32_t iTopo); /** * Remove triangles with specified indices. * Adjust internal triangulation state accordingly. * @param removedTriangles indices of triangles to remove */ void remove_triangles(const std::unordered_set& removedTriangles); /// @} private: /*____ Detail __*/ void add_super_triangle(const box2d_t& box); void create_vertex(const vec2_& pos, const std::vector& tris); void insert_vertex(std::uint32_t iVert); void enforce_delaunay_property_using_edge_flips( const vec2_& v, std::uint32_t iVert, std::stack& triStack); /// Flip fixed edges and return a list of flipped fixed edges std::vector insert_vertex_and_flip_fixed_edges(std::uint32_t iVert); /** * Insert an edge into constraint Delaunay triangulation * @param edge edge to insert * @param originalEdge original edge inserted edge is part of */ void insert_edge(edge_t edge, edge_t originalEdge); /** * Conform Delaunay triangulation to a fixed edge by recursively inserting * mid point of the edge and then conforming to its halves * @param edge fixed edge to conform to * @param originalEdges original edges that new edge is piece of * @param overlaps count of overlapping boundaries at the edge. Only used * when re-introducing edge with overlaps > 0 * @param orientationTolerance tolerance for orient2d predicate, * values [-tolerance,+tolerance] are considered as 0. */ void conform_to_edge( edge_t edge, std::vector originalEdges, boundary_overlap_count_t overlaps); std::uint32_t walk_triangles(std::uint32_t startVertex, const vec2_& pos) const; bool check_is_edgeflip_needed( const vec2_& v, std::uint32_t iV, std::uint32_t iV1, std::uint32_t iV2, std::uint32_t iV3) const; bool check_is_edgeflip_needed(const vec2_& v, std::uint32_t iT, std::uint32_t iTopo, std::uint32_t iVert) const; void change_neighbour(std::uint32_t iT, std::uint32_t oldNeighbor, std::uint32_t newNeighbor); void change_neighbour( std::uint32_t iT, std::uint32_t iVedge1, std::uint32_t iVedge2, std::uint32_t newNeighbor); void add_adjacent_triangle(std::uint32_t iVertex, std::uint32_t iTriangle); void add_adjacent_triangles(std::uint32_t iVertex, std::uint32_t iT1, std::uint32_t iT2, std::uint32_t iT3); void add_adjacent_triangles( std::uint32_t iVertex, std::uint32_t iT1, std::uint32_t iT2, std::uint32_t iT3, std::uint32_t iT4); void remove_adjacent_triangle(std::uint32_t iVertex, std::uint32_t iTriangle); std::uint32_t triangulate_pseudo_polygon( std::uint32_t ia, std::uint32_t ib, std::vector::const_iterator pointsFirst, std::vector::const_iterator pointsLast); std::uint32_t find_delaunay_point( std::uint32_t ia, std::uint32_t ib, std::vector::const_iterator pointsFirst, std::vector::const_iterator pointsLast) const; std::uint32_t pseudo_polygon_outer_triangle(std::uint32_t ia, std::uint32_t ib) const; std::uint32_t add_triangle(const triangle_t& t); // note: invalidates iterators! std::uint32_t add_triangle(); // note: invalidates triangle iterators! /** * Remove super-triangle (if used) and triangles with specified indices. * Adjust internal triangulation state accordingly. * @removedTriangles indices of triangles to remove */ void finalise_triangulation(const std::unordered_set& removedTriangles); std::unordered_set grow_to_boundary(std::stack seeds) const; void fixEdge( const edge_t& edge, const boundary_overlap_count_t overlaps) { fixedEdges.insert(edge); overlapCount[edge] = overlaps; // override overlap counter } void fixEdge(const edge_t& edge) { if (!fixedEdges.insert(edge).second) { ++overlapCount[edge]; // if edge is already fixed increment the counter } } void fixEdge( const edge_t& edge, const edge_t& originalEdge) { fixEdge(edge); if (edge != originalEdge) detail::insert_unique(pieceToOriginals[edge], originalEdge); } /** * Flag triangle as dummy * @note Advanced method for manually modifying the triangulation from * outside. Please call it when you know what you are doing. * @param iT index of a triangle to flag */ void make_dummies(const std::uint32_t iT) { const triangle_t& t = triangles[iT]; typedef std::array::const_iterator VCit; for (VCit iV = t.vertices.begin(); iV != t.vertices.end(); ++iV) remove_adjacent_triangle(*iV, iT); typedef std::array::const_iterator NCit; for (NCit iTn = t.neighbors.begin(); iTn != t.neighbors.end(); ++iTn) change_neighbour(*iTn, iT, null_neighbour); m_dummyTris.push_back(iT); } /** * Erase all dummy triangles * @note Advanced method for manually modifying the triangulation from * outside. Please call it when you know what you are doing. */ void erase_dummies() { if (m_dummyTris.empty()) return; const std::unordered_set dummySet(m_dummyTris.begin(), m_dummyTris.end()); std::unordered_map triIndMap; triIndMap[null_neighbour] = null_neighbour; for (std::uint32_t iT(0), iTnew(0); iT < std::uint32_t(triangles.size()); ++iT) { if (dummySet.count(iT)) continue; triIndMap[iT] = iTnew; triangles[iTnew] = triangles[iT]; iTnew++; } triangles.erase(triangles.end() - dummySet.size(), triangles.end()); // remap adjacent triangle indices for vertices typedef typename std::vector>::iterator VertTrisIt; for (VertTrisIt vTris = vertTris.begin(); vTris != vertTris.end(); ++vTris) { for (std::vector::iterator iT = vTris->begin(); iT != vTris->end(); ++iT) *iT = triIndMap[*iT]; } // remap neighbor indices for triangles for (std::vector::iterator t = triangles.begin(); t != triangles.end(); ++t) { std::array& nn = t->neighbors; for (std::array::iterator iN = nn.begin(); iN != nn.end(); ++iN) *iN = triIndMap[*iN]; } // clear dummy triangles m_dummyTris = std::vector(); } /** * Depth-peel a layer in triangulation, used when calculating triangle * depths * * It takes starting seed triangles, traverses neighboring triangles, and * assigns given layer depth to the traversed triangles. Traversal is * blocked by constraint edges. Triangles behind constraint edges are * recorded as seeds of next layer and returned from the function. * * @param seeds indices of seed triangles * @param layerDepth current layer's depth to mark triangles with * @param[in, out] triDepths depths of triangles * @return triangles of the deeper layers that are adjacent to the peeled * layer. To be used as seeds when peeling deeper layers. */ std::unordered_map peel_layer( std::stack seeds, const layer_depth_t layerDepth, std::vector& triDepths) const { std::unordered_map behindBoundary; while (!seeds.empty()) { const std::uint32_t iT = seeds.top(); seeds.pop(); triDepths[iT] = layerDepth; behindBoundary.erase(iT); const triangle_t& t = triangles[iT]; for (std::uint32_t i(0); i < std::uint32_t(3); ++i) { const edge_t opEdge(t.vertices[ccw(i)], t.vertices[cw(i)]); const std::uint32_t iN = t.neighbors[get_opposite_neighbour_from_vertex(i)]; if (iN == null_neighbour || triDepths[iN] <= layerDepth) continue; if (fixedEdges.count(opEdge)) { const std::unordered_map::const_iterator cit = overlapCount.find(opEdge); const layer_depth_t triDepth = cit == overlapCount.end() ? layerDepth + 1 : layerDepth + cit->second + 1; behindBoundary[iN] = triDepth; continue; } seeds.push(iN); } } return behindBoundary; } std::vector m_dummyTris; TNearPointLocator m_nearPtLocator; std::size_t m_nTargetVerts; super_geometry_type_t::Enum m_superGeomType; vertex_insertion_order_t::Enum m_vertexInsertionOrder; action_on_intersecting_constraint_edges_t::Enum m_intersectingEdgesStrategy; T m_minDistToConstraintEdge; }; /// @} /// @} namespace detail { static std::mt19937 randGenerator(9001); template void random_shuffle(RandomIt first, RandomIt last) { typename std::iterator_traits::difference_type i, n; n = last - first; for (i = n - 1; i > 0; --i) { std::swap(first[i], first[randGenerator() % (i + 1)]); } } } // namespace detail //----------------------- // triangulator_t methods //----------------------- template template < typename TVertexIter, typename TGetVertexCoordX, typename TGetVertexCoordY> void triangulator_t::insert_vertices( const TVertexIter first, const TVertexIter last, TGetVertexCoordX get_x_coord, TGetVertexCoordY get_y_coord) { if (is_finalized()) { throw std::runtime_error( "triangulator_t was finalized with 'erase...' method. Inserting new " "vertices is not possible"); } detail::randGenerator.seed(9001); // ensure deterministic behavior if (vertices.empty()) { add_super_triangle(expand_with_points(first, last, get_x_coord, get_y_coord)); } const std::size_t nExistingVerts = vertices.size(); vertices.reserve(nExistingVerts + std::distance(first, last)); for (TVertexIter it = first; it != last; ++it) { create_vertex(vec2_::make(get_x_coord(*it), get_y_coord(*it)), std::vector()); } switch (m_vertexInsertionOrder) { case vertex_insertion_order_t::AS_GIVEN: { for (TVertexIter it = first; it != last; ++it) { insert_vertex(std::uint32_t(nExistingVerts + std::distance(first, it))); } break; } case vertex_insertion_order_t::RANDOM: { std::vector ii(std::distance(first, last)); typedef std::vector::iterator Iter; std::uint32_t value = static_cast(nExistingVerts); for (Iter it = ii.begin(); it != ii.end(); ++it, ++value) *it = value; detail::random_shuffle(ii.begin(), ii.end()); for (Iter it = ii.begin(); it != ii.end(); ++it) insert_vertex(*it); break; } } } template template < typename TEdgeIter, typename TGetEdgeVertexStart, typename TGetEdgeVertexEnd> void triangulator_t::insert_edges( TEdgeIter first, const TEdgeIter last, TGetEdgeVertexStart getStart, TGetEdgeVertexEnd getEnd) { if (is_finalized()) { throw std::runtime_error( "triangulator_t was finalized with 'erase...' method. Inserting new " "edges is not possible"); } for (; first != last; ++first) { // +3 to account for super-triangle vertices const edge_t edge( std::uint32_t(getStart(*first) + m_nTargetVerts), std::uint32_t(getEnd(*first) + m_nTargetVerts)); insert_edge(edge, edge); } erase_dummies(); } template template < typename TEdgeIter, typename TGetEdgeVertexStart, typename TGetEdgeVertexEnd> void triangulator_t::conform_to_edges( TEdgeIter first, const TEdgeIter last, TGetEdgeVertexStart getStart, TGetEdgeVertexEnd getEnd) { if (is_finalized()) { throw std::runtime_error( "triangulator_t was finalized with 'erase...' method. Conforming to " "new edges is not possible"); } for (; first != last; ++first) { // +3 to account for super-triangle vertices const edge_t e( std::uint32_t(getStart(*first) + m_nTargetVerts), std::uint32_t(getEnd(*first) + m_nTargetVerts)); conform_to_edge(e, std::vector(1, e), 0); } erase_dummies(); } } // namespace cdt #ifndef CDT_USE_AS_COMPILED_LIBRARY //#include "triangulator_t.hpp" #include #include #include #include namespace cdt { template triangulator_t::triangulator_t() : m_nTargetVerts(detail::defaults::nTargetVerts) , m_superGeomType(detail::defaults::superGeomType) , m_vertexInsertionOrder(detail::defaults::vertexInsertionOrder) , m_intersectingEdgesStrategy(detail::defaults::intersectingEdgesStrategy) , m_minDistToConstraintEdge(detail::defaults::minDistToConstraintEdge) { } template triangulator_t::triangulator_t( const vertex_insertion_order_t::Enum vertexInsertionOrder) : m_nTargetVerts(detail::defaults::nTargetVerts) , m_superGeomType(detail::defaults::superGeomType) , m_vertexInsertionOrder(vertexInsertionOrder) , m_intersectingEdgesStrategy(detail::defaults::intersectingEdgesStrategy) , m_minDistToConstraintEdge(detail::defaults::minDistToConstraintEdge) { } template triangulator_t::triangulator_t( const vertex_insertion_order_t::Enum vertexInsertionOrder, const action_on_intersecting_constraint_edges_t::Enum intersectingEdgesStrategy, const T minDistToConstraintEdge) : m_nTargetVerts(detail::defaults::nTargetVerts) , m_superGeomType(detail::defaults::superGeomType) , m_vertexInsertionOrder(vertexInsertionOrder) , m_intersectingEdgesStrategy(intersectingEdgesStrategy) , m_minDistToConstraintEdge(minDistToConstraintEdge) { } template triangulator_t::triangulator_t( const vertex_insertion_order_t::Enum vertexInsertionOrder, const TNearPointLocator& nearPtLocator, const action_on_intersecting_constraint_edges_t::Enum intersectingEdgesStrategy, const T minDistToConstraintEdge) : m_nTargetVerts(detail::defaults::nTargetVerts) , m_nearPtLocator(nearPtLocator) , m_superGeomType(detail::defaults::superGeomType) , m_vertexInsertionOrder(vertexInsertionOrder) , m_intersectingEdgesStrategy(intersectingEdgesStrategy) , m_minDistToConstraintEdge(minDistToConstraintEdge) { } template void triangulator_t::change_neighbour( const std::uint32_t iT, const std::uint32_t iVedge1, const std::uint32_t iVedge2, const std::uint32_t newNeighbor) { triangle_t& t = triangles[iT]; t.neighbors[opposite_triangle_index(t, iVedge1, iVedge2)] = newNeighbor; } template void triangulator_t::eraseSuperTriangle() { if (m_superGeomType != super_geometry_type_t::SUPER_TRIANGLE) return; // find triangles adjacent to super-triangle's vertices std::unordered_set toErase; toErase.reserve( vertTris[0].size() + vertTris[1].size() + vertTris[2].size()); for (std::uint32_t iT(0); iT < std::uint32_t(triangles.size()); ++iT) { triangle_t& t = triangles[iT]; if (t.vertices[0] < 3 || t.vertices[1] < 3 || t.vertices[2] < 3) toErase.insert(iT); } finalise_triangulation(toErase); } template void triangulator_t::erase_outer_triangles() { // make dummy triangles adjacent to super-triangle's vertices const std::stack seed(std::deque(1, vertTris[0].front())); const std::unordered_set toErase = grow_to_boundary(seed); finalise_triangulation(toErase); } template void triangulator_t::erase_outer_triangles_and_holes() { const std::vector triDepths = calculate_triangle_depths(); std::unordered_set toErase; toErase.reserve(triangles.size()); for (std::size_t iT = 0; iT != triangles.size(); ++iT) { if (triDepths[iT] % 2 == 0){ toErase.insert(static_cast(iT)); } } finalise_triangulation(toErase); } /// Remap removing super-triangle: subtract 3 from vertices inline edge_t remap_no_supertriangle(const edge_t& e) { return edge_t(e.v1() - 3, e.v2() - 3); } template void triangulator_t::remove_triangles( const std::unordered_set& removedTriangles) { if (removedTriangles.empty()) return; // remove triangles and calculate triangle index mapping std::unordered_map triIndMap; for (std::uint32_t iT(0), iTnew(0); iT < std::uint32_t(triangles.size()); ++iT) { if (removedTriangles.count(iT)) continue; triIndMap[iT] = iTnew; triangles[iTnew] = triangles[iT]; iTnew++; } triangles.erase(triangles.end() - removedTriangles.size(), triangles.end()); // adjust triangles' neighbors vertTris = std::vector>(); for (std::uint32_t iT = 0; iT < triangles.size(); ++iT) { triangle_t& t = triangles[iT]; // update neighbors to account for removed triangles std::array& nn = t.neighbors; for (std::array::iterator n = nn.begin(); n != nn.end(); ++n) { if (removedTriangles.count(*n)) { *n = null_neighbour; } else if (*n != null_neighbour) { *n = triIndMap[*n]; } } } } template void triangulator_t::finalise_triangulation( const std::unordered_set& removedTriangles) { erase_dummies(); // remove super-triangle if (m_superGeomType == super_geometry_type_t::SUPER_TRIANGLE) { vertices.erase(vertices.begin(), vertices.begin() + 3); if (removedTriangles.empty()) vertTris.erase(vertTris.begin(), vertTris.begin() + 3); // edge_t re-mapping { // fixed edges std::unordered_set updatedFixedEdges; typedef std::unordered_set::const_iterator It; for (It e = fixedEdges.begin(); e != fixedEdges.end(); ++e) { updatedFixedEdges.insert(remap_no_supertriangle(*e)); } fixedEdges = updatedFixedEdges; } { // overlap count std::unordered_map updatedOverlapCount; typedef std::unordered_map::const_iterator It; for (It it = overlapCount.begin(); it != overlapCount.end(); ++it) { updatedOverlapCount.insert(std::make_pair( remap_no_supertriangle(it->first), it->second)); } overlapCount = updatedOverlapCount; } { // split edges mapping std::unordered_map> updatedPieceToOriginals; typedef std::unordered_map>::const_iterator It; for (It it = pieceToOriginals.begin(); it != pieceToOriginals.end(); ++it) { std::vector ee = it->second; for (std::vector::iterator eeIt = ee.begin(); eeIt != ee.end(); ++eeIt) { *eeIt = remap_no_supertriangle(*eeIt); } updatedPieceToOriginals.insert( std::make_pair(remap_no_supertriangle(it->first), ee)); } pieceToOriginals = updatedPieceToOriginals; } } // remove other triangles remove_triangles(removedTriangles); // adjust triangle vertices: account for removed super-triangle if (m_superGeomType == super_geometry_type_t::SUPER_TRIANGLE) { for (std::vector::iterator t = triangles.begin(); t != triangles.end(); ++t) { std::array& vv = t->vertices; for (std::array::iterator v = vv.begin(); v != vv.end(); ++v) { *v -= 3; } } } } template void triangulator_t::initialise_with_custom_supergeometry() { m_nearPtLocator.initialize(vertices); m_nTargetVerts = vertices.size(); m_superGeomType = super_geometry_type_t::CUSTOM; } template std::unordered_set triangulator_t::grow_to_boundary( std::stack seeds) const { std::unordered_set traversed; while (!seeds.empty()) { const std::uint32_t iT = seeds.top(); seeds.pop(); traversed.insert(iT); const triangle_t& t = triangles[iT]; for (std::uint32_t i(0); i < std::uint32_t(3); ++i) { const edge_t opEdge(t.vertices[ccw(i)], t.vertices[cw(i)]); if (fixedEdges.count(opEdge)) continue; const std::uint32_t iN = t.neighbors[get_opposite_neighbour_from_vertex(i)]; if (iN != null_neighbour && traversed.count(iN) == 0) seeds.push(iN); } } return traversed; } template std::uint32_t triangulator_t::add_triangle(const triangle_t& t) { if (m_dummyTris.empty()) { triangles.push_back(t); return std::uint32_t(triangles.size() - 1); } const std::uint32_t nxtDummy = m_dummyTris.back(); m_dummyTris.pop_back(); triangles[nxtDummy] = t; return nxtDummy; } template std::uint32_t triangulator_t::add_triangle() { if (m_dummyTris.empty()) { const triangle_t dummy = { { null_vertex, null_vertex, null_vertex }, { null_neighbour, null_neighbour, null_neighbour } }; triangles.push_back(dummy); return std::uint32_t(triangles.size() - 1); } const std::uint32_t nxtDummy = m_dummyTris.back(); m_dummyTris.pop_back(); return nxtDummy; } template void triangulator_t::insert_edges( const std::vector& edges) { insert_edges(edges.begin(), edges.end(), edge_get_v1, edge_get_v2); } template void triangulator_t::conform_to_edges( const std::vector& edges) { conform_to_edges(edges.begin(), edges.end(), edge_get_v1, edge_get_v2); } namespace detail { template T lerp(const T& a, const T& b, const T t) { return (T(1) - t) * a + t * b; } // Precondition: ab and cd intersect normally template vec2_ get_intersection_point_coords( const vec2_& a, const vec2_& b, const vec2_& c, const vec2_& d) { // interpolate point on the shorter segment if (get_square_distance(a, b) < get_square_distance(c, d)) { // const T a_cd = orient2d(c.x(), c.y(), d.x(), d.y(), a.x(), a.y()); // const T b_cd = orient2d(c.x(), c.y(), d.x(), d.y(), b.x(), b.y()); const T a_cd = orient2d(c, d, a); const T b_cd = orient2d(c, d, b); const T t = a_cd / (a_cd - b_cd); return vec2_::make(lerp(a.x(), b.x(), t), lerp(a.y(), b.y(), t)); } else { // const T c_ab = orient2d(a.x(), a.y(), b.x(), b.y(), c.x(), c.y()); // const T d_ab = orient2d(a.x(), a.y(), b.x(), b.y(), d.x(), d.y()); const T c_ab = orient2d(a, b, c); const T d_ab = orient2d(a, b, d); const T t = c_ab / (c_ab - d_ab); return vec2_::make(lerp(c.x(), d.x(), t), lerp(c.y(), d.y(), t)); } } } // namespace detail template void triangulator_t::insert_edge( const edge_t edge, const edge_t originalEdge) { const std::uint32_t iA = edge.v1(); std::uint32_t iB = edge.v2(); if (iA == iB) // edge connects a vertex to itself return; const std::vector& aTris = vertTris[iA]; const std::vector& bTris = vertTris[iB]; const vec2_& a = vertices[iA]; const vec2_& b = vertices[iB]; if (check_vertices_share_edge(aTris, bTris)) { fixEdge(edge, originalEdge); return; } const T distanceTolerance = m_minDistToConstraintEdge == T(0) ? T(0) : m_minDistToConstraintEdge * distance(a, b); std::uint32_t iT; std::uint32_t iVleft, iVright; std::tie(iT, iVleft, iVright) = get_intersected_triangle(iA, aTris, a, b, distanceTolerance); // if one of the triangle vertices is on the edge, move edge start if (iT == null_neighbour) { const edge_t edgePart(iA, iVleft); fixEdge(edgePart, originalEdge); return insert_edge(edge_t(iVleft, iB), originalEdge); } std::vector intersected(1, iT); std::vector ptsLeft(1, iVleft); std::vector ptsRight(1, iVright); std::uint32_t iV = iA; triangle_t t = triangles[iT]; while (std::find(t.vertices.begin(), t.vertices.end(), iB) == t.vertices.end()) { const std::uint32_t iTopo = get_opposite_triangle_index(t, iV); const triangle_t& tOpo = triangles[iTopo]; const std::uint32_t iVopo = get_opposed_vertex_index(tOpo, iT); const vec2_ vOpo = vertices[iVopo]; // RESOLVE intersection between two constraint edges if needed if (m_intersectingEdgesStrategy == action_on_intersecting_constraint_edges_t::RESOLVE && fixedEdges.count(edge_t(iVleft, iVright))) { const std::uint32_t iNewVert = static_cast(vertices.size()); // split constraint edge that already exists in triangulation const edge_t splitEdge(iVleft, iVright); const edge_t half1(iVleft, iNewVert); const edge_t half2(iNewVert, iVright); const boundary_overlap_count_t overlaps = overlapCount[splitEdge]; // remove the edge that will be split fixedEdges.erase(splitEdge); overlapCount.erase(splitEdge); // add split edge's halves fixEdge(half1, overlaps); fixEdge(half2, overlaps); // maintain piece-to-original mapping std::vector newOriginals(1, splitEdge); const std::unordered_map>::const_iterator originalsIt = pieceToOriginals.find(splitEdge); if (originalsIt != pieceToOriginals.end()) { // edge being split was split before: pass-through originals newOriginals = originalsIt->second; pieceToOriginals.erase(originalsIt); } detail::insert_unique(pieceToOriginals[half1], newOriginals); detail::insert_unique(pieceToOriginals[half2], newOriginals); // add a new point at the intersection of two constraint edges const vec2_ newV = detail::get_intersection_point_coords( vertices[iA], vertices[iB], vertices[iVleft], vertices[iVright]); create_vertex(newV, std::vector()); std::stack triStack = insert_point_on_edge(iNewVert, iT, iTopo); enforce_delaunay_property_using_edge_flips(newV, iNewVert, triStack); // TODO: is it's possible to re-use pseudo-polygons // for inserting [iA, iNewVert] edge half? insert_edge(edge_t(iA, iNewVert), originalEdge); insert_edge(edge_t(iNewVert, iB), originalEdge); return; } intersected.push_back(iTopo); iT = iTopo; t = triangles[iT]; const point_to_line_location_t::Enum loc = locate_point_wrt_line(vOpo, a, b, distanceTolerance); if (loc == point_to_line_location_t::LEFT_SIDE) { ptsLeft.push_back(iVopo); iV = iVleft; iVleft = iVopo; } else if (loc == point_to_line_location_t::RIGHT_SIDE) { ptsRight.push_back(iVopo); iV = iVright; iVright = iVopo; } else // encountered point on the edge iB = iVopo; } // Remove intersected triangles typedef std::vector::const_iterator TriIndCit; for (TriIndCit it = intersected.begin(); it != intersected.end(); ++it) make_dummies(*it); // Triangulate pseudo-polygons on both sides const std::uint32_t iTleft = triangulate_pseudo_polygon(iA, iB, ptsLeft.begin(), ptsLeft.end()); std::reverse(ptsRight.begin(), ptsRight.end()); const std::uint32_t iTright = triangulate_pseudo_polygon(iB, iA, ptsRight.begin(), ptsRight.end()); change_neighbour(iTleft, null_neighbour, iTright); change_neighbour(iTright, null_neighbour, iTleft); if (iB != edge.v2()) // encountered point on the edge { // fix edge part const edge_t edgePart(iA, iB); fixEdge(edgePart, originalEdge); return insert_edge(edge_t(iB, edge.v2()), originalEdge); } else { fixEdge(edge, originalEdge); } } template void triangulator_t::conform_to_edge( const edge_t edge, std::vector originalEdges, const boundary_overlap_count_t overlaps) { const std::uint32_t iA = edge.v1(); std::uint32_t iB = edge.v2(); if (iA == iB) // edge connects a vertex to itself return; const std::vector& aTris = vertTris[iA]; const std::vector& bTris = vertTris[iB]; const vec2_& a = vertices[iA]; const vec2_& b = vertices[iB]; if (check_vertices_share_edge(aTris, bTris)) { overlaps > 0 ? fixEdge(edge, overlaps) : fixEdge(edge); // avoid marking edge as a part of itself if (!originalEdges.empty() && edge != originalEdges.front()) { detail::insert_unique(pieceToOriginals[edge], originalEdges); } return; } const T distanceTolerance = m_minDistToConstraintEdge == T(0) ? T(0) : m_minDistToConstraintEdge * distance(a, b); std::uint32_t iT; std::uint32_t iVleft, iVright; std::tie(iT, iVleft, iVright) = get_intersected_triangle(iA, aTris, a, b, distanceTolerance); // if one of the triangle vertices is on the edge, move edge start if (iT == null_neighbour) { const edge_t edgePart(iA, iVleft); overlaps > 0 ? fixEdge(edgePart, overlaps) : fixEdge(edgePart); detail::insert_unique(pieceToOriginals[edgePart], originalEdges); return conform_to_edge(edge_t(iVleft, iB), originalEdges, overlaps); } std::uint32_t iV = iA; triangle_t t = triangles[iT]; while (std::find(t.vertices.begin(), t.vertices.end(), iB) == t.vertices.end()) { const std::uint32_t iTopo = get_opposite_triangle_index(t, iV); const triangle_t& tOpo = triangles[iTopo]; const std::uint32_t iVopo = get_opposed_vertex_index(tOpo, iT); const vec2_ vOpo = vertices[iVopo]; // RESOLVE intersection between two constraint edges if needed if (m_intersectingEdgesStrategy == action_on_intersecting_constraint_edges_t::RESOLVE && fixedEdges.count(edge_t(iVleft, iVright))) { const std::uint32_t iNewVert = static_cast(vertices.size()); // split constraint edge that already exists in triangulation const edge_t splitEdge(iVleft, iVright); const edge_t half1(iVleft, iNewVert); const edge_t half2(iNewVert, iVright); const boundary_overlap_count_t overlaps = overlapCount[splitEdge]; // remove the edge that will be split fixedEdges.erase(splitEdge); overlapCount.erase(splitEdge); // add split edge's halves fixEdge(half1, overlaps); fixEdge(half2, overlaps); // maintain piece-to-original mapping std::vector newOriginals(1, splitEdge); const std::unordered_map>::const_iterator originalsIt = pieceToOriginals.find(splitEdge); if (originalsIt != pieceToOriginals.end()) { // edge being split was split before: pass-through originals newOriginals = originalsIt->second; pieceToOriginals.erase(originalsIt); } detail::insert_unique(pieceToOriginals[half1], newOriginals); detail::insert_unique(pieceToOriginals[half2], newOriginals); // add a new point at the intersection of two constraint edges const vec2_ newV = detail::get_intersection_point_coords( vertices[iA], vertices[iB], vertices[iVleft], vertices[iVright]); create_vertex(newV, std::vector()); std::stack triStack = insert_point_on_edge(iNewVert, iT, iTopo); enforce_delaunay_property_using_edge_flips(newV, iNewVert, triStack); conform_to_edge(edge_t(iA, iNewVert), originalEdges, overlaps); conform_to_edge(edge_t(iNewVert, iB), originalEdges, overlaps); return; } iT = iTopo; t = triangles[iT]; const point_to_line_location_t::Enum loc = locate_point_wrt_line(vOpo, a, b, distanceTolerance); if (loc == point_to_line_location_t::LEFT_SIDE) { iV = iVleft; iVleft = iVopo; } else if (loc == point_to_line_location_t::RIGHT_SIDE) { iV = iVright; iVright = iVopo; } else // encountered point on the edge iB = iVopo; } /**/ // add mid-point to triangulation const std::uint32_t iMid = static_cast(vertices.size()); const vec2_& start = vertices[iA]; const vec2_& end = vertices[iB]; create_vertex( vec2_::make((start.x() + end.x()) / T(2), (start.y() + end.y()) / T(2)), std::vector()); const std::vector flippedFixedEdges = insert_vertex_and_flip_fixed_edges(iMid); conform_to_edge(edge_t(iA, iMid), originalEdges, overlaps); conform_to_edge(edge_t(iMid, iB), originalEdges, overlaps); // re-introduce fixed edges that were flipped // and make sure overlap count is preserved for (std::vector::const_iterator it = flippedFixedEdges.begin(); it != flippedFixedEdges.end(); ++it) { fixedEdges.erase(*it); boundary_overlap_count_t prevOverlaps = 0; const std::unordered_map::const_iterator overlapsIt = overlapCount.find(*it); if (overlapsIt != overlapCount.end()) { prevOverlaps = overlapsIt->second; overlapCount.erase(overlapsIt); } // override overlapping boundaries count when re-inserting an edge std::vector prevOriginals(1, *it); const std::unordered_map>::const_iterator originalsIt = pieceToOriginals.find(*it); if (originalsIt != pieceToOriginals.end()) { prevOriginals = originalsIt->second; } conform_to_edge(*it, prevOriginals, prevOverlaps); } if (iB != edge.v2()) conform_to_edge(edge_t(iB, edge.v2()), originalEdges, overlaps); } template void triangulator_t::add_super_triangle(const box2d_t& box) { m_nTargetVerts = 3; m_superGeomType = super_geometry_type_t::SUPER_TRIANGLE; const vec2_ center = { (box.min.x() + box.max.x()) / T(2), (box.min.y() + box.max.y()) / T(2) }; const T w = box.max.x() - box.min.x(); const T h = box.max.y() - box.min.y(); T r = std::sqrt(w * w + h * h) / T(2); // incircle radius r *= T(1.1); const T R = T(2) * r; // excircle radius const T shiftX = R * std::sqrt(T(3)) / T(2); // R * cos(30 deg) const vec2_ posV1 = { center.x() - shiftX, center.y() - r }; const vec2_ posV2 = { center.x() + shiftX, center.y() - r }; const vec2_ posV3 = { center.x(), center.y() + R }; create_vertex(posV1, std::vector(1, std::uint32_t(0))); create_vertex(posV2, std::vector(1, std::uint32_t(0))); create_vertex(posV3, std::vector(1, std::uint32_t(0))); const triangle_t superTri = { { std::uint32_t(0), std::uint32_t(1), std::uint32_t(2) }, { null_neighbour, null_neighbour, null_neighbour } }; add_triangle(superTri); m_nearPtLocator.initialize(vertices); } template void triangulator_t::create_vertex( const vec2_& pos, const std::vector& tris) { vertices.push_back(pos); vertTris.push_back(tris); } template std::vector triangulator_t::insert_vertex_and_flip_fixed_edges( const std::uint32_t iVert) { std::vector flippedFixedEdges; const vec2_& v = vertices[iVert]; std::array trisAt = walking_search_triangle_at(v); std::stack triStack = trisAt[1] == null_neighbour ? insert_point_in_triangle(iVert, trisAt[0]) : insert_point_on_edge(iVert, trisAt[0], trisAt[1]); while (!triStack.empty()) { const std::uint32_t iT = triStack.top(); triStack.pop(); const triangle_t& t = triangles[iT]; const std::uint32_t iTopo = get_opposite_triangle_index(t, iVert); if (iTopo == null_neighbour) continue; /* * v3 original edge: (v1, v3) * /|\ flip-candidate edge: (v, v2) * / | \ * / | \ * / | \ * new vertex--> v | v2 * \ | / * \ | / * \ | / * \|/ * v1 */ const triangle_t& tOpo = triangles[iTopo]; const std::uint32_t i = get_opposite_vertex_index(tOpo, iT); const std::uint32_t iV2 = tOpo.vertices[i]; const std::uint32_t iV1 = tOpo.vertices[cw(i)]; const std::uint32_t iV3 = tOpo.vertices[ccw(i)]; if (check_is_edgeflip_needed(v, iVert, iV1, iV2, iV3)) { // if flipped edge is fixed, remember it const edge_t flippedEdge(iV1, iV3); if (fixedEdges.count(flippedEdge)) flippedFixedEdges.push_back(flippedEdge); do_edgeflip(iT, iTopo); triStack.push(iT); triStack.push(iTopo); } } m_nearPtLocator.add_point(iVert, vertices); return flippedFixedEdges; } template void triangulator_t::insert_vertex(const std::uint32_t iVert) { const vec2_& v = vertices[iVert]; std::array trisAt = walking_search_triangle_at(v); std::stack triStack = (trisAt[1] == null_neighbour) ? insert_point_in_triangle(iVert, trisAt[0]) : insert_point_on_edge(iVert, trisAt[0], trisAt[1]); enforce_delaunay_property_using_edge_flips(v, iVert, triStack); m_nearPtLocator.add_point(iVert, vertices); } template void triangulator_t::enforce_delaunay_property_using_edge_flips( const vec2_& v, const std::uint32_t iVert, std::stack& triStack) { while (!triStack.empty()) { const std::uint32_t iT = triStack.top(); triStack.pop(); const triangle_t& t = triangles[iT]; const std::uint32_t iTopo = get_opposite_triangle_index(t, iVert); if (iTopo == null_neighbour) { continue; } if (check_is_edgeflip_needed(v, iT, iTopo, iVert)) { do_edgeflip(iT, iTopo); triStack.push(iT); triStack.push(iTopo); } } } /*! * Handles super-triangle vertices. * Super-tri points are not infinitely far and influence the input points * Three cases are possible: * 1. If one of the opposed vertices is super-tri: no flip needed * 2. One of the shared vertices is super-tri: * check if on point is same side of line formed by non-super-tri * vertices as the non-super-tri shared vertex * 3. None of the vertices are super-tri: normal circumcircle test */ /* * v3 original edge: (v1, v3) * /|\ flip-candidate edge: (v, v2) * / | \ * / | \ * / | \ * new vertex--> v | v2 * \ | / * \ | / * \ | / * \|/ * v1 */ template bool triangulator_t::check_is_edgeflip_needed( const vec2_& v, const std::uint32_t iV, const std::uint32_t iV1, const std::uint32_t iV2, const std::uint32_t iV3) const { const vec2_& v1 = vertices[iV1]; const vec2_& v2 = vertices[iV2]; const vec2_& v3 = vertices[iV3]; if (m_superGeomType == super_geometry_type_t::SUPER_TRIANGLE) { // If flip-candidate edge touches super-triangle in-circumference // test has to be replaced with orient2d test against the line // formed by two non-artificial vertices (that don't belong to // super-triangle) if (iV < 3) // flip-candidate edge touches super-triangle { // does original edge also touch super-triangle? if (iV1 < 3) return locate_point_wrt_line(v1, v2, v3) == locate_point_wrt_line(v, v2, v3); if (iV3 < 3) return locate_point_wrt_line(v3, v1, v2) == locate_point_wrt_line(v, v1, v2); return false; // original edge does not touch super-triangle } if (iV2 < 3) // flip-candidate edge touches super-triangle { // does original edge also touch super-triangle? if (iV1 < 3) return locate_point_wrt_line(v1, v, v3) == locate_point_wrt_line(v2, v, v3); if (iV3 < 3) return locate_point_wrt_line(v3, v1, v) == locate_point_wrt_line(v2, v1, v); return false; // original edge does not touch super-triangle } // flip-candidate edge does not touch super-triangle if (iV1 < 3) return locate_point_wrt_line(v1, v2, v3) == locate_point_wrt_line(v, v2, v3); if (iV3 < 3) return locate_point_wrt_line(v3, v1, v2) == locate_point_wrt_line(v, v1, v2); } return check_is_in_circumcircle(v, v1, v2, v3); } template bool triangulator_t::check_is_edgeflip_needed( const vec2_& v, const std::uint32_t iT, const std::uint32_t iTopo, const std::uint32_t iV) const { /* * v3 original edge: (v1, v3) * /|\ flip-candidate edge: (v, v2) * / | \ * / | \ * / | \ * new vertex--> v | v2 * \ | / * \ | / * \ | / * \|/ * v1 */ const triangle_t& tOpo = triangles[iTopo]; const std::uint32_t i = get_opposite_vertex_index(tOpo, iT); const std::uint32_t iV2 = tOpo.vertices[i]; const std::uint32_t iV1 = tOpo.vertices[cw(i)]; const std::uint32_t iV3 = tOpo.vertices[ccw(i)]; // flip not needed if the original edge is fixed if (fixedEdges.count(edge_t(iV1, iV3))) return false; return check_is_edgeflip_needed(v, iV, iV1, iV2, iV3); } template std::uint32_t triangulator_t::walk_triangles( const std::uint32_t startVertex, const vec2_& pos) const { // begin walk in search of triangle at pos std::uint32_t currTri = vertTris[startVertex][0]; std::unordered_set visited; bool found = false; while (!found) { const triangle_t& t = triangles[currTri]; found = true; // stochastic offset to randomize which edge we check first const std::uint32_t offset(detail::randGenerator() % 3); for (std::uint32_t i_(0); i_ < std::uint32_t(3); ++i_) { const std::uint32_t i((i_ + offset) % 3); const vec2_& vStart = vertices[t.vertices[i]]; const vec2_& vEnd = vertices[t.vertices[ccw(i)]]; const point_to_line_location_t::Enum edgeCheck = locate_point_wrt_line(pos, vStart, vEnd); if (edgeCheck == point_to_line_location_t::RIGHT_SIDE && t.neighbors[i] != null_neighbour && visited.insert(t.neighbors[i]).second) { found = false; currTri = t.neighbors[i]; break; } } } return currTri; } /* Flip edge between T and Topo: * * v4 | - old edge * /|\ ~ - new edge * / | \ * n3 / T' \ n4 * / | \ * / | \ * T -> v1~~~~~~~~~v3 <- Topo * \ | / * \ | / * n1 \Topo'/ n2 * \ | / * \|/ * v2 */ template void triangulator_t::do_edgeflip( const std::uint32_t iT, const std::uint32_t iTopo) { triangle_t& t = triangles[iT]; triangle_t& tOpo = triangles[iTopo]; const std::array& triNs = t.neighbors; const std::array& triOpoNs = tOpo.neighbors; const std::array& triVs = t.vertices; const std::array& triOpoVs = tOpo.vertices; // find vertices and neighbors std::uint32_t i = get_opposite_vertex_index(t, iTopo); const std::uint32_t v1 = triVs[i]; const std::uint32_t v2 = triVs[ccw(i)]; const std::uint32_t n1 = triNs[i]; const std::uint32_t n3 = triNs[cw(i)]; i = get_opposite_vertex_index(tOpo, iT); const std::uint32_t v3 = triOpoVs[i]; const std::uint32_t v4 = triOpoVs[ccw(i)]; const std::uint32_t n4 = triOpoNs[i]; const std::uint32_t n2 = triOpoNs[cw(i)]; // change vertices and neighbors using detail::arr3; t = triangle_t::make(arr3(v4, v1, v3), arr3(n3, iTopo, n4)); tOpo = triangle_t::make(arr3(v2, v3, v1), arr3(n2, iT, n1)); // adjust neighboring triangles and vertices change_neighbour(n1, iT, iTopo); change_neighbour(n4, iTopo, iT); // only adjust adjacent triangles if triangulation is not finalized: // can happen when called from outside on an already finalized triangulation if (!is_finalized()) { add_adjacent_triangle(v1, iTopo); add_adjacent_triangle(v3, iT); remove_adjacent_triangle(v2, iT); remove_adjacent_triangle(v4, iTopo); } } template void triangulator_t::change_neighbour( const std::uint32_t iT, const std::uint32_t oldNeighbor, const std::uint32_t newNeighbor) { if (iT == null_neighbour) { return; } triangle_t& t = triangles[iT]; t.neighbors[get_neighbour_index(t, oldNeighbor)] = newNeighbor; } template void triangulator_t::add_adjacent_triangle( const std::uint32_t iVertex, const std::uint32_t iTriangle) { vertTris[iVertex].push_back(iTriangle); } template void triangulator_t::add_adjacent_triangles( const std::uint32_t iVertex, const std::uint32_t iT1, const std::uint32_t iT2, const std::uint32_t iT3) { std::vector& vTris = vertTris[iVertex]; vTris.reserve(vTris.size() + 3); vTris.push_back(iT1); vTris.push_back(iT2); vTris.push_back(iT3); } template void triangulator_t::add_adjacent_triangles( const std::uint32_t iVertex, const std::uint32_t iT1, const std::uint32_t iT2, const std::uint32_t iT3, const std::uint32_t iT4) { std::vector& vTris = vertTris[iVertex]; vTris.reserve(vTris.size() + 4); vTris.push_back(iT1); vTris.push_back(iT2); vTris.push_back(iT3); vTris.push_back(iT4); } template void triangulator_t::remove_adjacent_triangle( const std::uint32_t iVertex, const std::uint32_t iTriangle) { std::vector& tris = vertTris[iVertex]; tris.erase(std::find(tris.begin(), tris.end(), iTriangle)); } template std::uint32_t triangulator_t::triangulate_pseudo_polygon( const std::uint32_t ia, const std::uint32_t ib, const std::vector::const_iterator pointsFirst, const std::vector::const_iterator pointsLast) { if (pointsFirst == pointsLast) return pseudo_polygon_outer_triangle(ia, ib); // Find delaunay point const std::uint32_t ic = find_delaunay_point(ia, ib, pointsFirst, pointsLast); // Find pseudopolygons split by the delaunay point std::vector::const_iterator newLast = pointsFirst; while (*newLast != ic) { ++newLast; } const std::vector::const_iterator newFirst = newLast + 1; // triangulate splitted pseudo-polygons const std::uint32_t iT2 = triangulate_pseudo_polygon(ic, ib, newFirst, pointsLast); const std::uint32_t iT1 = triangulate_pseudo_polygon(ia, ic, pointsFirst, newLast); // add new triangle const triangle_t t = { { ia, ib, ic }, { null_neighbour, iT2, iT1 } }; const std::uint32_t iT = add_triangle(t); // adjust neighboring triangles and vertices if (iT1 != null_neighbour) { if (pointsFirst == newLast) { change_neighbour(iT1, ia, ic, iT); } else { triangles[iT1].neighbors[0] = iT; } } if (iT2 != null_neighbour) { if (newFirst == pointsLast) { change_neighbour(iT2, ic, ib, iT); } else { triangles[iT2].neighbors[0] = iT; } } add_adjacent_triangle(ia, iT); add_adjacent_triangle(ib, iT); add_adjacent_triangle(ic, iT); return iT; } template std::uint32_t triangulator_t::find_delaunay_point( const std::uint32_t ia, const std::uint32_t ib, const std::vector::const_iterator pointsFirst, const std::vector::const_iterator pointsLast) const { MCUT_ASSERT(pointsFirst != pointsLast); const vec2_& a = vertices[ia]; const vec2_& b = vertices[ib]; std::uint32_t ic = *pointsFirst; vec2_ c = vertices[ic]; for (std::vector::const_iterator it = pointsFirst + 1; it != pointsLast; ++it) { const vec2_ v = vertices[*it]; if (!check_is_in_circumcircle(v, a, b, c)) { continue; } ic = *it; c = vertices[ic]; } return ic; } template std::uint32_t triangulator_t::pseudo_polygon_outer_triangle( const std::uint32_t ia, const std::uint32_t ib) const { const std::vector& aTris = vertTris[ia]; const std::vector& bTris = vertTris[ib]; for (std::vector::const_iterator it = aTris.begin(); it != aTris.end(); ++it) { if (std::find(bTris.begin(), bTris.end(), *it) != bTris.end()) { return *it; } } return null_neighbour; } template void triangulator_t::insert_vertices( const std::vector>& newVertices) { return insert_vertices( newVertices.begin(), newVertices.end(), get_x_coord_vec2d, get_y_coord_vec2d); } template bool triangulator_t::is_finalized() const { return vertTris.empty() && !vertices.empty(); } } // namespace cdt #endif #endif // header-guard