diff --git a/Installation/CHANGES.md b/Installation/CHANGES.md index a22a8f52ee6..6af7f7aa97d 100644 --- a/Installation/CHANGES.md +++ b/Installation/CHANGES.md @@ -39,8 +39,12 @@ Release date: September 2019 Parameters. ### Polygon Mesh Processing - - Added the function `CGAL::Polygon_mesh_processing::centroid()` which computes + - Added the function `CGAL::Polygon_mesh_processing::centroid()`, which computes the centroid of a closed triangle mesh. +- Added the functions `CGAL::Polygon_mesh_processing::stitch_boundary_cycle()` and + `CGAL::Polygon_mesh_processing::stitch_boundary_cycles()`, which can be used + to try and merge together geometrically compatible but combinatorially different halfedges + that belong to the same boundary cycle. ### IO Streams - **Breaking change:** The API of `CGAL::Color` has been cleaned up. diff --git a/Polygon_mesh_processing/doc/Polygon_mesh_processing/PackageDescription.txt b/Polygon_mesh_processing/doc/Polygon_mesh_processing/PackageDescription.txt index 032167d48e0..3193cbf966b 100644 --- a/Polygon_mesh_processing/doc/Polygon_mesh_processing/PackageDescription.txt +++ b/Polygon_mesh_processing/doc/Polygon_mesh_processing/PackageDescription.txt @@ -133,6 +133,8 @@ and provides a list of the parameters that are used in this package. - `CGAL::Polygon_mesh_processing::merge_duplicate_polygons_in_polygon_soup()` - `CGAL::Polygon_mesh_processing::remove_isolated_points_in_polygon_soup()` - `CGAL::Polygon_mesh_processing::repair_polygon_soup()` +- `CGAL::Polygon_mesh_processing::stitch_boundary_cycle()` +- `CGAL::Polygon_mesh_processing::stitch_boundary_cycles()` - `CGAL::Polygon_mesh_processing::stitch_borders()` - `CGAL::Polygon_mesh_processing::is_polygon_soup_a_polygon_mesh()` - `CGAL::Polygon_mesh_processing::polygon_soup_to_polygon_mesh()` diff --git a/Polygon_mesh_processing/doc/Polygon_mesh_processing/Polygon_mesh_processing.txt b/Polygon_mesh_processing/doc/Polygon_mesh_processing/Polygon_mesh_processing.txt index 7c2bf8ab4e2..273d19e04f4 100644 --- a/Polygon_mesh_processing/doc/Polygon_mesh_processing/Polygon_mesh_processing.txt +++ b/Polygon_mesh_processing/doc/Polygon_mesh_processing/Polygon_mesh_processing.txt @@ -547,17 +547,20 @@ to obtain an as-clean-as-possible polygon soup. \subsection Stitching -It happens that a polygon mesh has several edges and vertices that are duplicated. +When handling polygon meshes, it might happen that a mesh has several edges and vertices that are duplicated. For those edges and vertices, the connectivity of the mesh is incomplete, if not considered incorrect. -Stitching the borders of such a polygon mesh consists in two main steps. -First, border edges that are similar but duplicated are detected and paired. -Then, they are "stitched" together so that the edges and vertices duplicates are removed -from the mesh, and each of these remaining edges is incident to exactly two faces. +Stitching the borders of a polygon mesh can be done to fix some of the duplication. +It consists in two main steps. First, border edges that are geometrically identical but duplicated +are detected and paired. Then, they are "stitched" together so that edges and vertices duplicates +are removed from the mesh, and each of these remaining edges is incident to exactly two faces. -The function \link PMP_repairing_grp `CGAL::Polygon_mesh_processing::stitch_borders()` \endlink - performs such repairing operation. The input mesh should be manifold. -Otherwise, stitching is not guaranteed to succeed. +The functions `CGAL::Polygon_mesh_processing::stitch_boundary_cycle()`, `CGAL::Polygon_mesh_processing::stitch_boundary_cycles()`, +and `CGAL::Polygon_mesh_processing::stitch_borders()` can perform such repairing operations: the first two +functions can be used to stitch halfedges that are part of the same boundary(ies), whereas the third +function is more generic and can also stitch halfedges that live on different borders. + +The input mesh should be manifold; otherwise, stitching is not guaranteed to succeed. \subsubsection StitchingExample Stitching Example diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/stitch_borders.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/stitch_borders.h index d4201990c17..e2d79d4019f 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/stitch_borders.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/stitch_borders.h @@ -529,25 +529,37 @@ void stitch_borders_impl(PM& pmesh, run_stitch_borders(pmesh, to_stitch, uf_vertices, uf_handles); } -// \ingroup PMP_repairing_grp -// -// Stitches together, whenever possible, two halfedges of the same border. -// -// \tparam PolygonMesh a model of `FaceListGraph` and `MutableFaceGraph` -// \tparam NamedParameters a sequence of \ref pmp_namedparameters "Named Parameters" -// -// \param pm the polygon mesh to be modified by stitching -// \param np optional sequence of \ref pmp_namedparameters "Named Parameters" among the ones listed below -// -// \cgalNamedParamsBegin -// \cgalParamBegin{vertex_point_map} the property map with the points associated to the vertices of `pmesh`. -// If this parameter is omitted, an internal property map for `CGAL::vertex_point_t` must be available in `PolygonMesh`. -// \cgalParamEnd -// \cgalNamedParamsEnd -// +} //end of namespace internal + +/// \ingroup PMP_repairing_grp +/// +/// Stitches together, whenever possible, two halfedges belonging to the boundary cycle described by the halfedge `h`. +/// Two border halfedges `h1` and `h2` can be stitched +/// if the points associated to the source and target vertices of `h1` are +/// the same as those of the target and source vertices of `h2` respectively. +/// +/// \tparam PolygonMesh a model of `MutableFaceGraph` +/// \tparam NamedParameters a sequence of \ref pmp_namedparameters "Named Parameters" +/// +/// \param h a border halfedge of the polygon mesh `pm` +/// \param pm the polygon mesh to be stitched +/// \param np optional sequence of \ref pmp_namedparameters "Named Parameters" among the ones listed below +/// +/// \cgalNamedParamsBegin +/// \cgalParamBegin{vertex_point_map} the property map with the points associated to the vertices of `pmesh`. +/// If this parameter is omitted, an internal property map for `CGAL::vertex_point_t` must be available in `PolygonMesh`. +/// \cgalParamEnd +/// \cgalNamedParamsEnd +/// +/// \returns the number of pairs of halfedges that were stitched. +/// +/// \sa `stitch_boundary_cycles()` +/// \sa `stitch_borders()` +/// template -std::size_t stitch_boundary_cycles(PolygonMesh& pm, - const NamedParameters& np) +std::size_t stitch_boundary_cycle(const typename boost::graph_traits::halfedge_descriptor h, + PolygonMesh& pm, + const NamedParameters& np) { using boost::choose_param; using boost::get_param; @@ -558,9 +570,6 @@ std::size_t stitch_boundary_cycles(PolygonMesh& pm, VPMap vpm = choose_param(get_param(np, internal_np::vertex_point), get_const_property_map(vertex_point, pm)); - std::vector boundary_cycles; - extract_boundary_cycles(pm, std::back_inserter(boundary_cycles)); - std::size_t stitched_boundary_cycles_n = 0; // A boundary cycle might need to be stitched starting from different extremities @@ -573,62 +582,121 @@ std::size_t stitch_boundary_cycles(PolygonMesh& pm, // so we mark which edges have been stitched std::unordered_set stitched_hedges; - for(halfedge_descriptor h : boundary_cycles) + std::vector stitching_starting_points; + halfedge_descriptor hn = next(h, pm); + while(hn != h) { - std::vector stitching_starting_points; + if((get(vpm, source(hn, pm)) == get(vpm, target(next(hn, pm), pm))) && + (get(vpm, source(hn, pm)) != get(vpm, target(hn, pm)))) // ignore degenerate edges + stitching_starting_points.push_back(hn); + + hn = next(hn, pm); + } + + for(std::size_t i=0, end=stitching_starting_points.size(); i 0) // already treated + continue; + + std::vector > hedges_to_stitch; + halfedge_descriptor hn = next(h, pm); - while(hn != h) + bool do_stitching = true; + do { - if(get(vpm, source(hn, pm)) == get(vpm, target(next(hn, pm), pm))) - stitching_starting_points.push_back(hn); + // Don't want to create an invalid polygon mesh, even if the geometry allows it + if(face(opposite(h, pm), pm) == face(opposite(hn, pm), pm)) + break; + hedges_to_stitch.push_back(std::make_pair(h, hn)); + +#ifdef CGAL_PMP_STITCHING_DEBUG + std::cout << "Stitch halfegdes: " + << h << "(" << get(vpm, source(h, pm)) << ") - (" << get(vpm, target(h, pm)) << ") and " + << hn << "(" << get(vpm, source(hn, pm)) << ") - (" << get(vpm, target(hn, pm)) << ")" << std::endl; +#endif + + stitched_hedges.insert(h); + stitched_hedges.insert(hn); + + if(next(hn, pm) == h) + break; + + h = prev(h, pm); hn = next(hn, pm); + + if((get(vpm, source(h, pm)) != get(vpm, target(hn, pm))) || + (get(vpm, source(hn, pm)) == get(vpm, target(hn, pm)))) // ignore degenerate edges + do_stitching = false; } + while(do_stitching); - for(std::size_t i=0, end=stitching_starting_points.size(); i 0) // already treated - continue; - - std::vector > hedges_to_stitch; - - halfedge_descriptor hn = next(h, pm); - bool do_stitching = true; - do - { - hedges_to_stitch.push_back(std::make_pair(h, hn)); - stitched_hedges.insert(h); - stitched_hedges.insert(hn); - - if(next(hn, pm) == h) - break; - - h = prev(h, pm); - hn = next(hn, pm); - - if(get(vpm, source(h, pm)) != get(vpm, target(hn, pm))) - do_stitching = false; - } - while(do_stitching); - internal::stitch_borders_impl(pm, hedges_to_stitch); - ++stitched_boundary_cycles_n; + stitched_boundary_cycles_n += hedges_to_stitch.size(); } } return stitched_boundary_cycles_n; } +template +std::size_t stitch_boundary_cycle(const typename boost::graph_traits::halfedge_descriptor h, + PolygonMesh& pm) +{ + return stitch_boundary_cycle(h, pm, CGAL::parameters::all_default()); +} + +/// \ingroup PMP_repairing_grp +/// +/// Stitches together, whenever possible, two halfedges belonging to the same boundary cycle. +/// Two border halfedges `h1` and `h2` can be stitched +/// if the points associated to the source and target vertices of `h1` are +/// the same as those of the target and source vertices of `h2` respectively. +/// +/// \tparam PolygonMesh a model of `MutableFaceGraph` +/// \tparam NamedParameters a sequence of \ref pmp_namedparameters "Named Parameters" +/// +/// \param pm the polygon mesh to be stitched +/// \param np optional sequence of \ref pmp_namedparameters "Named Parameters" among the ones listed below +/// +/// \cgalNamedParamsBegin +/// \cgalParamBegin{vertex_point_map} the property map with the points associated to the vertices of `pmesh`. +/// If this parameter is omitted, an internal property map for `CGAL::vertex_point_t` must be available in `PolygonMesh`. +/// \cgalParamEnd +/// \cgalNamedParamsEnd +/// +/// \returns the number of pairs of halfedges that were stitched. +/// +/// \sa `stitch_boundary_cycle()` +/// \sa `stitch_borders()` +/// +template +std::size_t stitch_boundary_cycles(PolygonMesh& pm, + const NamedParameters& np) +{ + typedef typename boost::graph_traits::halfedge_descriptor halfedge_descriptor; + + std::vector boundary_cycles; + extract_boundary_cycles(pm, std::back_inserter(boundary_cycles)); + + std::size_t stitched_boundary_cycles_n = 0; + + for(halfedge_descriptor h : boundary_cycles) + stitched_boundary_cycles_n += stitch_boundary_cycle(h, pm, np); + + return stitched_boundary_cycles_n; +} + template std::size_t stitch_boundary_cycles(PolygonMesh& pm) { return stitch_boundary_cycles(pm, CGAL::parameters::all_default()); } -} //end of namespace internal - /*! * \ingroup PMP_repairing_grp * Stitches together border halfedges in a polygon mesh. @@ -636,7 +704,7 @@ std::size_t stitch_boundary_cycles(PolygonMesh& pm) * For each pair `p` in this vector, `p.second` and its opposite will be removed * from `pmesh`. * -* @tparam PolygonMesh a model of `FaceListGraph` and `MutableFaceGraph` +* @tparam PolygonMesh a model of `MutableFaceGraph` * @tparam HalfedgePairsRange a range of * `std::pair::%halfedge_descriptor, * boost::graph_traits::%halfedge_descriptor>`, @@ -683,7 +751,10 @@ void stitch_borders(PolygonMesh& pmesh, /// Default value is `false`.\cgalParamEnd /// \cgalParamBegin{face_index_map} a property map containing the index of each face of `pmesh` \cgalParamEnd /// \cgalNamedParamsEnd - +/// +/// @sa `stitch_boundary_cycle()` +/// @sa `stitch_boundary_cycles()` +/// template void stitch_borders(PolygonMesh& pmesh, const CGAL_PMP_NP_CLASS& np) { @@ -698,7 +769,7 @@ void stitch_borders(PolygonMesh& pmesh, const CGAL_PMP_NP_CLASS& np) VPMap vpm = choose_param(get_param(np, internal_np::vertex_point), get_const_property_map(vertex_point, pmesh)); - internal::stitch_boundary_cycles(pmesh, np); + stitch_boundary_cycles(pmesh, np); internal::collect_duplicated_stitchable_boundary_edges(pmesh, std::back_inserter(hedge_pairs_to_stitch), @@ -706,7 +777,7 @@ void stitch_borders(PolygonMesh& pmesh, const CGAL_PMP_NP_CLASS& np) vpm, np); stitch_borders(pmesh, hedge_pairs_to_stitch); - internal::stitch_boundary_cycles(pmesh, np); + stitch_boundary_cycles(pmesh, np); } diff --git a/Polygon_mesh_processing/test/Polygon_mesh_processing/test_stitching.cpp b/Polygon_mesh_processing/test/Polygon_mesh_processing/test_stitching.cpp index f2cdf958b64..cee1afbba01 100644 --- a/Polygon_mesh_processing/test/Polygon_mesh_processing/test_stitching.cpp +++ b/Polygon_mesh_processing/test/Polygon_mesh_processing/test_stitching.cpp @@ -18,7 +18,8 @@ namespace params = CGAL::parameters; typedef CGAL::Exact_predicates_inexact_constructions_kernel EPICK; typedef CGAL::Exact_predicates_exact_constructions_kernel EPECK; -void test_stitch_boundary_cycles(const char* fname) +void test_stitch_boundary_cycles(const char* fname, + const std::size_t expected_n) { typedef CGAL::Surface_mesh Mesh; @@ -29,8 +30,8 @@ void test_stitch_boundary_cycles(const char* fname) return; } - std::size_t res = PMP::internal::stitch_boundary_cycles(mesh); - assert(res > 0); + std::size_t res = PMP::stitch_boundary_cycles(mesh); + assert(res == expected_n); assert(is_valid(mesh)); } @@ -134,8 +135,8 @@ void bug_test() int main() { - test_stitch_boundary_cycles("data_stitching/boundary_cycle.off"); - test_stitch_boundary_cycles("data_stitching/boundary_cycle_2.off"); + test_stitch_boundary_cycles("data_stitching/boundary_cycle.off", 5); + test_stitch_boundary_cycles("data_stitching/boundary_cycle_2.off", 2); test_polyhedron("data_stitching/full_border.off"); test_polyhedron("data_stitching/full_border.off");