From 713f5a2d4555a252af0043444827c1e111172f7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mael=20Rouxel-Labb=C3=A9?= Date: Tue, 7 May 2019 10:20:19 +0200 Subject: [PATCH] Resolve conflicts between master and 4.14-based branch that fixes manifoldness Conflicts resulting from the switch to C++11 (BOOST_FOREACH disappearing etc.) --- .../NamedParameters.txt | 2 +- .../CGAL/Polygon_mesh_processing/repair.h | 342 ++++++++++++------ .../Polygon_mesh_processing/CMakeLists.txt | 1 + .../data_repair/many_umbrellas.off | 26 ++ .../data_repair/nm_closed_cubes.off | 44 +++ .../three_triangles_sharing_a_vertex.off | 12 + .../two_triangles_sharing_a_vertex.off | 9 + .../test_pmp_manifoldness.cpp | 272 ++++++++++++++ .../test_shape_predicates.cpp | 106 +----- .../include/CGAL/Dynamic_property_map.h | 11 +- 10 files changed, 614 insertions(+), 211 deletions(-) create mode 100644 Polygon_mesh_processing/test/Polygon_mesh_processing/data_repair/many_umbrellas.off create mode 100644 Polygon_mesh_processing/test/Polygon_mesh_processing/data_repair/nm_closed_cubes.off create mode 100644 Polygon_mesh_processing/test/Polygon_mesh_processing/data_repair/three_triangles_sharing_a_vertex.off create mode 100644 Polygon_mesh_processing/test/Polygon_mesh_processing/data_repair/two_triangles_sharing_a_vertex.off create mode 100644 Polygon_mesh_processing/test/Polygon_mesh_processing/test_pmp_manifoldness.cpp diff --git a/Polygon_mesh_processing/doc/Polygon_mesh_processing/NamedParameters.txt b/Polygon_mesh_processing/doc/Polygon_mesh_processing/NamedParameters.txt index 6176b34f75c..4d34f30d8b3 100644 --- a/Polygon_mesh_processing/doc/Polygon_mesh_processing/NamedParameters.txt +++ b/Polygon_mesh_processing/doc/Polygon_mesh_processing/NamedParameters.txt @@ -368,7 +368,7 @@ should be considered as part of the clipping volume or not. Parameter to pass an output iterator. \n \b Type : a model of `OutputIterator` \n -\b Default : `Emptyset_iterator` +\b Default : `#CGAL::Emptyset_iterator` \cgalNPEnd \cgalNPBegin{erase_all_duplicates} \anchor PMP_erase_all_duplicates diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/repair.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/repair.h index d8642e483d6..3d635fb407d 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/repair.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/repair.h @@ -17,7 +17,8 @@ // SPDX-License-Identifier: GPL-3.0+ // // -// Author(s) : Sebastien Loriot +// Author(s) : Sebastien Loriot, +// Mael Rouxel-Labbé #ifndef CGAL_POLYGON_MESH_PROCESSING_REPAIR_H #define CGAL_POLYGON_MESH_PROCESSING_REPAIR_H @@ -25,6 +26,7 @@ #include #include +#include #include #include #include @@ -1949,45 +1951,6 @@ bool remove_degenerate_faces(TriangleMesh& tmesh) CGAL::Polygon_mesh_processing::parameters::all_default()); } -namespace internal { - -template -struct Vertex_collector -{ - typedef typename boost::graph_traits::vertex_descriptor vertex_descriptor; - - void collect_vertices(vertex_descriptor v1, vertex_descriptor v2) - { - std::vector& verts = collections[v1]; - if(verts.empty()) - verts.push_back(v1); - verts.push_back(v2); - } - - void dump(OutputIterator out) - { - typedef std::pair > Pair_type; - for(const Pair_type& p : collections) { - *out++ = p.second; - } - } - - std::map > collections; -}; - -template -struct Vertex_collector -{ - typedef typename boost::graph_traits::vertex_descriptor vertex_descriptor; - void collect_vertices(vertex_descriptor, vertex_descriptor) - {} - - void dump(Emptyset_iterator) - {} -}; - -} // end namespace internal - /// \ingroup PMP_repairing_grp /// checks whether a vertex of a polygon mesh is non-manifold. /// @@ -2004,12 +1967,19 @@ bool is_non_manifold_vertex(typename boost::graph_traits::vertex_de const PolygonMesh& pm) { typedef typename boost::graph_traits::halfedge_descriptor halfedge_descriptor; - boost::unordered_set halfedges_handled; + + typedef CGAL::dynamic_halfedge_property_t Halfedge_property_tag; + typedef typename boost::property_map::const_type Visited_halfedge_map; + + // Dynamic pmaps do not have default initialization values (yet) + Visited_halfedge_map visited_halfedges = get(Halfedge_property_tag(), pm); + for(halfedge_descriptor h : halfedges(pm)) + put(visited_halfedges, h, false); std::size_t incident_null_faces_counter = 0; for(halfedge_descriptor h : halfedges_around_target(v, pm)) { - halfedges_handled.insert(h); + put(visited_halfedges, h, true); if(CGAL::is_border(h, pm)) ++incident_null_faces_counter; } @@ -2024,8 +1994,8 @@ bool is_non_manifold_vertex(typename boost::graph_traits::vertex_de { if(v == target(h, pm)) { - // More than one umbrella incident to 'v' --> non-manifold - if(halfedges_handled.count(h) == 0) + // Haven't seen that halfedge yet ==> more than one umbrella incident to 'v' ==> non-manifold + if(!get(visited_halfedges, h)) return true; } } @@ -2033,20 +2003,193 @@ bool is_non_manifold_vertex(typename boost::graph_traits::vertex_de return false; } +namespace internal { + +template +struct Vertex_collector +{ + typedef typename boost::graph_traits::vertex_descriptor vertex_descriptor; + + bool has_old_vertex(const vertex_descriptor v) const { return collections.count(v) != 0; } + + void collect_vertices(vertex_descriptor v1, vertex_descriptor v2) + { + std::vector& verts = collections[v1]; + if(verts.empty()) + verts.push_back(v1); + verts.push_back(v2); + } + + template + void dump(OutputIterator out) + { + typedef std::pair > Pair_type; + for(const Pair_type& p : collections) + *out++ = p.second; + } + + void dump(Emptyset_iterator) { } + + std::map > collections; +}; + +} // end namespace internal + +template +typename boost::graph_traits::vertex_descriptor +create_new_vertex_for_sector(typename boost::graph_traits::halfedge_descriptor sector_begin_h, + typename boost::graph_traits::halfedge_descriptor sector_last_h, + PolygonMesh& pm, + const VPM& vpm, + const ConstraintMap& cmap) +{ + typedef typename boost::graph_traits::vertex_descriptor vertex_descriptor; + typedef typename boost::graph_traits::halfedge_descriptor halfedge_descriptor; + + vertex_descriptor old_vd = target(sector_begin_h, pm); + vertex_descriptor new_vd = add_vertex(pm); + put(vpm, new_vd, get(vpm, old_vd)); + + put(cmap, new_vd, true); + + set_halfedge(new_vd, sector_begin_h, pm); + halfedge_descriptor h = sector_begin_h; + do + { + set_target(h, new_vd, pm); + + if(h == sector_last_h) + break; + else + h = prev(opposite(h, pm), pm); + } + while(h != sector_begin_h); // for safety + CGAL_assertion(h != sector_begin_h); + + return new_vd; +} + +template +std::size_t make_umbrella_manifold(typename boost::graph_traits::halfedge_descriptor h, + PolygonMesh& pm, + internal::Vertex_collector& dmap, + const NamedParameters& np) +{ + typedef typename boost::graph_traits::vertex_descriptor vertex_descriptor; + typedef typename boost::graph_traits::halfedge_descriptor halfedge_descriptor; + + typedef typename GetVertexPointMap::type VertexPointMap; + VertexPointMap vpm = choose_param(get_param(np, internal_np::vertex_point), + get_property_map(vertex_point, pm)); + + typedef typename boost::lookup_named_param_def // default (no constraint pmap) + >::type VerticesMap; + VerticesMap cmap = choose_param(get_param(np, internal_np::vertex_is_constrained), + Constant_property_map(false)); + + std::size_t nb_new_vertices = 0; + + vertex_descriptor old_v = target(h, pm); + put(cmap, old_v, true); // store the duplicates + + // count the number of borders + int border_counter = 0; + halfedge_descriptor ih = h, done = ih, border_h = h; + do + { + if(is_border(ih, pm)) + { + border_h = ih; + ++border_counter; + } + + ih = prev(opposite(ih, pm), pm); + } + while(ih != done); + + bool is_non_manifold_within_umbrella = (border_counter > 1); + + // if there is a single sector, then simply move the full umbrella to a new vertex, and we're done + if(!is_non_manifold_within_umbrella) + { + // note that since this is marked as a non-manifold vertex, we necessarily need to create + // a new vertex for this umbrella (the main umbrella is not marked as non-manifold) + halfedge_descriptor last_h = opposite(next(h, pm), pm); + vertex_descriptor new_v = create_new_vertex_for_sector(h, last_h, pm, vpm, cmap); + dmap.collect_vertices(old_v, new_v); + nb_new_vertices = 1; + } + // if there is more than one sector, look at each sector and split them away from the main one + else + { + // the first manifold sector, described by two halfedges + halfedge_descriptor sector_start_h = border_h; + CGAL_assertion(is_border(border_h, pm)); + + bool should_stop = false; + bool is_main_sector = true; + do + { + CGAL_assertion(is_border(sector_start_h, pm)); + + // collect the sector and split it away if it must be + halfedge_descriptor sector_last_h = sector_start_h; + do + { + halfedge_descriptor next_h = prev(opposite(sector_last_h, pm), pm); + + if(is_border(next_h, pm)) + break; + + sector_last_h = next_h; + } + while(sector_last_h != sector_start_h); + CGAL_assertion(!is_border(sector_last_h, pm)); + CGAL_assertion(sector_last_h != sector_start_h); + + halfedge_descriptor next_start_h = prev(opposite(sector_last_h, pm), pm); + + // there are multiple CCs incident to this particular vertex, and we should create a new vertex + // if it's not the first umbrella around 'old_v' or not the first sector, but not if it's + // the first umbrella and first sector. + bool must_create_new_vertex = (!is_main_sector || dmap.has_old_vertex(old_v)); + + // In any case, we must set up the next pointer correctly + set_next(sector_start_h, opposite(sector_last_h, pm), pm); + + if(must_create_new_vertex) + { + vertex_descriptor new_v = create_new_vertex_for_sector(sector_start_h, sector_last_h, pm, vpm, cmap); + dmap.collect_vertices(old_v, new_v); + ++nb_new_vertices; + } + + is_main_sector = false; + sector_start_h = next_start_h; + should_stop = (sector_start_h == border_h); + } + while(!should_stop); + } + + return nb_new_vertices; +} + /// \ingroup PMP_repairing_grp /// duplicates all the non-manifold vertices of the input mesh. /// -/// @tparam TriangleMesh a model of `HalfedgeListGraph` and `MutableHalfedgeGraph` +/// @tparam PolygonMesh a model of `HalfedgeListGraph` and `MutableHalfedgeGraph` /// @tparam NamedParameters a sequence of \ref pmp_namedparameters "Named Parameters" /// -/// @param tm the triangulated surface mesh to be repaired +/// @param pm the surface mesh to be repaired /// @param np optional \ref pmp_namedparameters "Named Parameters" described below /// /// \cgalNamedParamsBegin /// \cgalParamBegin{vertex_point_map} the property map with the points associated to the vertices of `pmesh`. /// The type of this map is model of `ReadWritePropertyMap`. /// If this parameter is omitted, an internal property map for -/// `CGAL::vertex_point_t` should be available in `TriangleMesh` +/// `CGAL::vertex_point_t` should be available in `PolygonMesh` /// \cgalParamEnd /// \cgalParamBegin{vertex_is_constrained_map} a writable property map with `vertex_descriptor` /// as key and `bool` as `value_type`. `put(pmap, v, true)` will be called for each duplicated @@ -2060,32 +2203,17 @@ bool is_non_manifold_vertex(typename boost::graph_traits::vertex_de /// \cgalNamedParamsEnd /// /// \return the number of vertices created. -template -std::size_t duplicate_non_manifold_vertices(TriangleMesh& tm, +template +std::size_t duplicate_non_manifold_vertices(PolygonMesh& pm, const NamedParameters& np) { - CGAL_assertion(CGAL::is_triangle_mesh(tm)); - using boost::get_param; using boost::choose_param; - typedef boost::graph_traits GT; + typedef boost::graph_traits GT; typedef typename GT::vertex_descriptor vertex_descriptor; typedef typename GT::halfedge_descriptor halfedge_descriptor; - typedef typename GetVertexPointMap::type VertexPointMap; - VertexPointMap vpm = choose_param(get_param(np, internal_np::vertex_point), - get_property_map(vertex_point, tm)); - - typedef typename boost::lookup_named_param_def < - internal_np::vertex_is_constrained_t, - NamedParameters, - Constant_property_map // default (no constraint pmap) - > ::type VerticesMap; - VerticesMap cmap - = choose_param(get_param(np, internal_np::vertex_is_constrained), - Constant_property_map(false)); - typedef typename boost::lookup_named_param_def < internal_np::output_iterator_t, NamedParameters, @@ -2095,70 +2223,78 @@ std::size_t duplicate_non_manifold_vertices(TriangleMesh& tm, = choose_param(get_param(np, internal_np::output_iterator), Emptyset_iterator()); - internal::Vertex_collector dmap; - boost::unordered_set vertices_handled; - boost::unordered_set halfedges_handled; + internal::Vertex_collector dmap; + + typedef CGAL::dynamic_vertex_property_t Vertex_property_tag; + typedef typename boost::property_map::type Visited_vertex_map; + typedef CGAL::dynamic_halfedge_property_t Halfedge_property_tag; + typedef typename boost::property_map::type Visited_halfedge_map; + + Visited_vertex_map visited_vertices = get(Vertex_property_tag(), pm); + Visited_halfedge_map visited_halfedges = get(Halfedge_property_tag(), pm); + + // Dynamic pmaps do not have default initialization values (yet) + for(vertex_descriptor v : vertices(pm)) + put(visited_vertices, v, false); + for(halfedge_descriptor h : halfedges(pm)) + put(visited_halfedges, h, false); std::size_t nb_new_vertices = 0; std::vector non_manifold_cones; - for(halfedge_descriptor h : halfedges(tm)) + for(halfedge_descriptor h : halfedges(pm)) { // If 'h' is not visited yet, we walk around the target of 'h' and mark these // halfedges as visited. Thus, if we are here and the target is already marked as visited, // it means that the vertex is non manifold. - if(halfedges_handled.insert(h).second) + if(!get(visited_halfedges, h)) { - vertex_descriptor vd = target(h, tm); - if(!vertices_handled.insert(vd).second) - { - put(cmap, vd, true); // store the originals - non_manifold_cones.push_back(h); - } - else - { - set_halfedge(vd, h, tm); - } + put(visited_halfedges, h, true); + bool is_non_manifold = false; - halfedge_descriptor start = opposite(next(h, tm), tm); - h = start; + vertex_descriptor vd = target(h, pm); + if(get(visited_vertices, vd)) // already seen this vertex, but not from this star + is_non_manifold = true; + + put(visited_vertices, vd, true); + + // While walking the star of this halfedge, if we meet a border halfedge more than once, + // it means the mesh is pinched and we are also in the case of a non-manifold situation + halfedge_descriptor ih = h, done = ih; + int border_counter = 0; do { - halfedges_handled.insert(h); - h = opposite(next(h, tm), tm); + put(visited_halfedges, ih, true); + if(is_border(ih, pm)) + ++border_counter; + + ih = prev(opposite(ih, pm), pm); } - while(h != start); + while(ih != done); + + if(border_counter > 1) + is_non_manifold = true; + + if(is_non_manifold) + non_manifold_cones.push_back(h); } } if(!non_manifold_cones.empty()) { for(halfedge_descriptor h : non_manifold_cones) - { - halfedge_descriptor start = h; - vertex_descriptor new_vd = add_vertex(tm); - ++nb_new_vertices; - put(cmap, new_vd, true); // store the duplicates - dmap.collect_vertices(target(h, tm), new_vd); - put(vpm, new_vd, get(vpm, target(h, tm))); - set_halfedge(new_vd, h, tm); - do - { - set_target(h, new_vd, tm); - h = opposite(next(h, tm), tm); - } - while(h != start); - } + nb_new_vertices += make_umbrella_manifold(h, pm, dmap, np); + dmap.dump(out); } return nb_new_vertices; } -template -std::size_t duplicate_non_manifold_vertices(TriangleMesh& tm) +template +std::size_t duplicate_non_manifold_vertices(PolygonMesh& pm) { - return duplicate_non_manifold_vertices(tm, parameters::all_default()); + return duplicate_non_manifold_vertices(pm, parameters::all_default()); } /// \ingroup PMP_repairing_grp diff --git a/Polygon_mesh_processing/test/Polygon_mesh_processing/CMakeLists.txt b/Polygon_mesh_processing/test/Polygon_mesh_processing/CMakeLists.txt index 816d3acc787..b3779a0cf87 100644 --- a/Polygon_mesh_processing/test/Polygon_mesh_processing/CMakeLists.txt +++ b/Polygon_mesh_processing/test/Polygon_mesh_processing/CMakeLists.txt @@ -99,6 +99,7 @@ endif() create_single_source_cgal_program("test_shape_predicates.cpp") create_single_source_cgal_program("test_pmp_collision_detection.cpp") create_single_source_cgal_program("remove_degeneracies_test.cpp") + create_single_source_cgal_program("test_pmp_manifoldness.cpp") if( TBB_FOUND ) CGAL_target_use_TBB(test_pmp_distance) diff --git a/Polygon_mesh_processing/test/Polygon_mesh_processing/data_repair/many_umbrellas.off b/Polygon_mesh_processing/test/Polygon_mesh_processing/data_repair/many_umbrellas.off new file mode 100644 index 00000000000..9bcb6898d0b --- /dev/null +++ b/Polygon_mesh_processing/test/Polygon_mesh_processing/data_repair/many_umbrellas.off @@ -0,0 +1,26 @@ +OFF +16 8 0 +0 0 0 +1 0 0 +2 0 0 +2 1 0 +0 1 0 +0 -1 0 +2 -1 0 +0 1 2 +0 -1 1 +1 0 0 +1 1 -1 +2 0 -1 +1 0 1 +1 1 0 +0 1 0 +1 0 0 +3 0 1 4 +3 1 2 3 +3 5 6 1 +3 7 8 9 +3 10 9 11 +3 12 14 13 +3 13 14 15 +3 15 12 13 diff --git a/Polygon_mesh_processing/test/Polygon_mesh_processing/data_repair/nm_closed_cubes.off b/Polygon_mesh_processing/test/Polygon_mesh_processing/data_repair/nm_closed_cubes.off new file mode 100644 index 00000000000..4db8b1e9edf --- /dev/null +++ b/Polygon_mesh_processing/test/Polygon_mesh_processing/data_repair/nm_closed_cubes.off @@ -0,0 +1,44 @@ +OFF +16 24 0 +0 0 0 +1 0 0 +1 1 0 +0 1 0 +0 0 1 +1 0 1 +1 1 1 +0 1 1 +2 0 1 +2 1 1 +1 0 2 +2 0 2 +2 1 2 +1 1 2 +1 0 1 +1 1 1 +3 0 2 1 +3 0 3 2 +3 0 1 5 +3 0 5 4 +3 3 0 4 +3 3 4 7 +3 2 3 7 +3 2 7 6 +3 4 5 6 +3 4 6 7 +3 1 2 5 +3 5 2 6 +3 14 9 8 +3 15 9 14 +3 14 8 11 +3 14 11 10 +3 8 12 11 +3 8 9 12 +3 12 9 15 +3 13 12 15 +3 15 14 10 +3 15 10 13 +3 10 11 12 +3 10 12 13 + + diff --git a/Polygon_mesh_processing/test/Polygon_mesh_processing/data_repair/three_triangles_sharing_a_vertex.off b/Polygon_mesh_processing/test/Polygon_mesh_processing/data_repair/three_triangles_sharing_a_vertex.off new file mode 100644 index 00000000000..ea869fcf8d1 --- /dev/null +++ b/Polygon_mesh_processing/test/Polygon_mesh_processing/data_repair/three_triangles_sharing_a_vertex.off @@ -0,0 +1,12 @@ +OFF +7 3 0 +0 0 0 +1 0 0 +2 0 0 +2 1 0 +0 1 0 +0 -1 0 +2 -1 0 +3 0 1 4 +3 1 2 3 +3 5 6 1 diff --git a/Polygon_mesh_processing/test/Polygon_mesh_processing/data_repair/two_triangles_sharing_a_vertex.off b/Polygon_mesh_processing/test/Polygon_mesh_processing/data_repair/two_triangles_sharing_a_vertex.off new file mode 100644 index 00000000000..7efcb129e93 --- /dev/null +++ b/Polygon_mesh_processing/test/Polygon_mesh_processing/data_repair/two_triangles_sharing_a_vertex.off @@ -0,0 +1,9 @@ +OFF +5 2 0 +0 0 0 +1 0 0 +2 0 0 +2 1 0 +0 1 0 +3 0 1 4 +3 1 2 3 diff --git a/Polygon_mesh_processing/test/Polygon_mesh_processing/test_pmp_manifoldness.cpp b/Polygon_mesh_processing/test/Polygon_mesh_processing/test_pmp_manifoldness.cpp new file mode 100644 index 00000000000..b922c86716a --- /dev/null +++ b/Polygon_mesh_processing/test/Polygon_mesh_processing/test_pmp_manifoldness.cpp @@ -0,0 +1,272 @@ +#include + +#include +#include + +#include +#include + +#include + +#include +#include +#include +#include + +typedef CGAL::Exact_predicates_inexact_constructions_kernel K; + +typedef CGAL::Surface_mesh Surface_mesh; +typedef CGAL::Polyhedron_3 Polyhedron; + +typedef std::vector > Vertices_to_merge_container; + +template +void read_mesh(const char* fname, + PolygonMesh& mesh) +{ + std::ifstream input(fname); + if (!input || !(input >> mesh) || mesh.is_empty()) + { + std::cerr << fname << " is not a valid off file.\n"; + std::exit(1); + } +} + +// tests merge_and_duplication +template +void merge_vertices(typename boost::graph_traits::vertex_descriptor v_keep, + typename boost::graph_traits::vertex_descriptor v_rm, + PolygonMesh& mesh) +{ + typedef typename boost::graph_traits::halfedge_descriptor halfedge_descriptor; + + assert(v_keep != v_rm); + + std::size_t ini_nv = static_cast(vertices(mesh).size()); + + halfedge_descriptor h = halfedge(v_rm, mesh); + halfedge_descriptor start = h; + do + { + set_target(h, v_keep, mesh); + h = opposite(next(h, mesh), mesh); + } + while(h != start); + + remove_vertex(v_rm, mesh); + + assert(vertices(mesh).size() == ini_nv - 1); +} + +template +void merge_vertices(const Vertices_to_merge_container& all_vertices_to_merge, + std::map::vertex_descriptor, std::size_t>& merged_onto, + PolygonMesh& mesh) +{ + typedef typename boost::graph_traits::vertex_descriptor vertex_descriptor; + + // int to vd + std::vector vds(vertices(mesh).begin(), vertices(mesh).end()); + + for(std::size_t i=0, avtmn=all_vertices_to_merge.size(); i& vertices_to_merge = all_vertices_to_merge[i]; + if(vertices_to_merge.size() <= 1) + continue; + + vertex_descriptor vd_to_merge_onto = vds[vertices_to_merge[0]]; + + for(std::size_t j=1, vtmn=vertices_to_merge.size(); j::iterator, bool > is_insert_successful = + merged_onto.insert(std::make_pair(vd_to_merge_onto, vertices_to_merge.size() - 1)); + if(!is_insert_successful.second) + is_insert_successful.first->second += vertices_to_merge.size() - 1; + + assert(CGAL::Polygon_mesh_processing::is_non_manifold_vertex(vd_to_merge_onto, mesh)); + } +} + +template +std::size_t test_nm_vertices_duplication(const Vertices_to_merge_container& all_merges, + std::map::vertex_descriptor, std::size_t>& merged_onto, + std::vector::vertex_descriptor> >& duplicated_vertices, + PolygonMesh& mesh) +{ + merge_vertices(all_merges, merged_onto, mesh); + + std::size_t new_vertices_nb = + CGAL::Polygon_mesh_processing::duplicate_non_manifold_vertices(mesh, + CGAL::parameters::output_iterator(std::back_inserter(duplicated_vertices))); + + return new_vertices_nb; +} + +template +std::size_t test_nm_vertices_duplication(const Vertices_to_merge_container& all_merges, + std::vector::vertex_descriptor> >& duplicated_vertices, + PolygonMesh& mesh) +{ + std::map::vertex_descriptor, std::size_t> useless_map; + + return test_nm_vertices_duplication(all_merges, useless_map, duplicated_vertices, mesh); +} + + +template +void test_unpinched_mesh(const Vertices_to_merge_container& all_merges, + PolygonMesh& mesh) +{ + typedef typename boost::graph_traits::vertex_descriptor vertex_descriptor; + + const std::size_t ini_vertices_size = num_vertices(mesh); + + // this is a nice, smooth, surface initially + for(vertex_descriptor vd : vertices(mesh)) { + assert(!CGAL::Polygon_mesh_processing::is_non_manifold_vertex(vd, mesh)); + } + + std::vector > duplicated_vertices; + std::map number_of_vertices_merged_onto; + std::size_t nb = test_nm_vertices_duplication(all_merges, + number_of_vertices_merged_onto, + duplicated_vertices, + mesh); + + const std::size_t final_vertices_size = vertices(mesh).size(); + std::cout << " ini: " << ini_vertices_size << " final: " << final_vertices_size << std::endl; + assert(final_vertices_size == ini_vertices_size); + + std::size_t expected_nb = 0; + for(std::size_t i=0, n=all_merges.size(); i +void test_blobby() +{ + std::cout << " test: data/blobby.off" << std::endl; + + PolygonMesh mesh; + read_mesh("data/blobby.off", mesh); + + // non-manifold vertices + Vertices_to_merge_container all_merges; + std::vector single_merge; + single_merge.push_back(1); single_merge.push_back(7); single_merge.push_back(14); single_merge.push_back(21); + all_merges.push_back(single_merge); + + single_merge.clear(); + single_merge.push_back(2); single_merge.push_back(8); + all_merges.push_back(single_merge); + + test_unpinched_mesh(all_merges, mesh); +} + +template +void test_nm_cubes() +{ + std::cout << " test: data_repair/nm_closed_cubes.off" << std::endl; + + PolygonMesh mesh; + read_mesh("data_repair/nm_closed_cubes.off", mesh); + + // non-manifold vertices + Vertices_to_merge_container all_merges; + std::vector single_merge; + single_merge.push_back(5); single_merge.push_back(14); + all_merges.push_back(single_merge); + + single_merge.clear(); + single_merge.push_back(6); single_merge.push_back(15); + all_merges.push_back(single_merge); + + test_unpinched_mesh(all_merges, mesh); +} + +template +void test_pinched_triangles(const char* filename, + const std::size_t expected_nb) +{ + typedef typename boost::graph_traits::vertex_descriptor vertex_descriptor; + + std::cout << " test: " << filename << " expected: " << expected_nb << std::endl; + + PolygonMesh mesh; + read_mesh(filename, mesh); + + // in the triangles, the second (id==1) vertex is non-manifold because it is pinched + int id = 0; + for(vertex_descriptor vd : vertices(mesh)) { + if(id++ == 1) { + assert(CGAL::Polygon_mesh_processing::is_non_manifold_vertex(vd, mesh)); + } else { + assert(!CGAL::Polygon_mesh_processing::is_non_manifold_vertex(vd, mesh)); + } + } + + std::vector > duplicated_vertices; + std::size_t new_vertices_nb = + CGAL::Polygon_mesh_processing::duplicate_non_manifold_vertices(mesh, + CGAL::parameters::output_iterator(std::back_inserter(duplicated_vertices))); + std::cout << " new_vertices_nb: " << new_vertices_nb << " vs expected: " << expected_nb << std::endl; + assert(new_vertices_nb == expected_nb); + assert(duplicated_vertices.size() == 1); + assert(duplicated_vertices[0].size() == 1 + expected_nb); +} + +template +void test_many_umbrellas() +{ + typedef typename boost::graph_traits::vertex_descriptor vertex_descriptor; + + std::cout << " test: data_repair/many_umbrellas.off" << std::endl; + + PolygonMesh mesh; + read_mesh("data_repair/many_umbrellas.off", mesh); + + // non-manifold vertices + Vertices_to_merge_container all_merges; + std::vector single_merge; + single_merge.push_back(1); single_merge.push_back(9); single_merge.push_back(15); + all_merges.push_back(single_merge); + + std::vector > duplicated_vertices; + std::size_t nb = test_nm_vertices_duplication(all_merges, duplicated_vertices, mesh); + assert(nb == 5); + + const std::size_t final_vertices_size = vertices(mesh).size(); + + assert(duplicated_vertices.size() == 1); + assert(duplicated_vertices[0].size() == 6); + assert(final_vertices_size == 19); // 5 new ones, but we merged 2 before, so +3 from '16' at the start +} + +int main(int /*argc*/, char** /*argv*/) +{ + std::cout << "Test Vertex Manifoldness Functions (SM)" << std::endl; + + test_blobby(); // data/blobby.off + test_nm_cubes(); // data_repair/nm_closed_cubes.off + test_pinched_triangles("data_repair/two_triangles_sharing_a_vertex.off", 1); + test_pinched_triangles("data_repair/three_triangles_sharing_a_vertex.off", 2); + test_many_umbrellas(); // data_repair/many_umbrellas.off + + std::cout << "Test Vertex Manifoldness Functions (Polyhedron)" << std::endl; + + test_blobby(); // data/blobby.off + test_nm_cubes(); // data_repair/nm_closed_cubes.off + test_pinched_triangles("data_repair/two_triangles_sharing_a_vertex.off", 1); + test_pinched_triangles("data_repair/three_triangles_sharing_a_vertex.off", 2); + test_many_umbrellas(); // data_repair/many_umbrellas.off + + return EXIT_SUCCESS; +} diff --git a/Polygon_mesh_processing/test/Polygon_mesh_processing/test_shape_predicates.cpp b/Polygon_mesh_processing/test/Polygon_mesh_processing/test_shape_predicates.cpp index 6ff76236ccd..2f4c275e29d 100644 --- a/Polygon_mesh_processing/test/Polygon_mesh_processing/test_shape_predicates.cpp +++ b/Polygon_mesh_processing/test/Polygon_mesh_processing/test_shape_predicates.cpp @@ -57,109 +57,6 @@ void check_triangle_face_degeneracy(const char* fname) std::cout << "done" << std::endl; } -// tests merge_and_duplication -template -void merge_identical_points(typename boost::graph_traits::vertex_descriptor v_keep, - typename boost::graph_traits::vertex_descriptor v_rm, - PolygonMesh& mesh) -{ - typedef typename boost::graph_traits::halfedge_descriptor halfedge_descriptor; - - halfedge_descriptor h = halfedge(v_rm, mesh); - halfedge_descriptor start = h; - - do - { - set_target(h, v_keep, mesh); - h = opposite(next(h, mesh), mesh); - } - while( h != start ); - - remove_vertex(v_rm, mesh); -} - -void test_vertex_non_manifoldness(const char* fname) -{ - std::cout << "test vertex non manifoldness..."; - - typedef boost::graph_traits::vertex_descriptor vertex_descriptor; - typedef boost::graph_traits::vertices_size_type size_type; - - std::ifstream input(fname); - Surface_mesh mesh; - if (!input || !(input >> mesh) || mesh.is_empty()) { - std::cerr << fname << " is not a valid off file.\n"; - std::exit(1); - } - - size_type ini_nv = num_vertices(mesh); - - // create non-manifold vertex - Surface_mesh::Vertex_index vertex_to_merge_onto(1); - Surface_mesh::Vertex_index vertex_to_merge(7); - merge_identical_points(vertex_to_merge_onto, vertex_to_merge, mesh); - mesh.collect_garbage(); - - assert(num_vertices(mesh) == ini_nv - 1); - - for(vertex_descriptor v : vertices(mesh)) - { - if(v == vertex_to_merge_onto) - assert(CGAL::Polygon_mesh_processing::is_non_manifold_vertex(v, mesh)); - else - assert(!CGAL::Polygon_mesh_processing::is_non_manifold_vertex(v, mesh)); - } - - std::cout << "done" << std::endl; -} - -void test_vertices_merge_and_duplication(const char* fname) -{ - std::cout << "test non manifold vertex duplication..."; - - typedef boost::graph_traits::vertex_descriptor vertex_descriptor; - - std::ifstream input(fname); - Surface_mesh mesh; - if (!input || !(input >> mesh) || mesh.is_empty()) { - std::cerr << fname << " is not a valid off file.\n"; - std::exit(1); - } - const std::size_t initial_vertices = num_vertices(mesh); - - // create non-manifold vertex - Surface_mesh::Vertex_index vertex_to_merge_onto(1); - Surface_mesh::Vertex_index vertex_to_merge(7); - Surface_mesh::Vertex_index vertex_to_merge_2(14); - Surface_mesh::Vertex_index vertex_to_merge_3(21); - - Surface_mesh::Vertex_index vertex_to_merge_onto_2(2); - Surface_mesh::Vertex_index vertex_to_merge_4(8); - - merge_identical_points(vertex_to_merge_onto, vertex_to_merge, mesh); - merge_identical_points(vertex_to_merge_onto, vertex_to_merge_2, mesh); - merge_identical_points(vertex_to_merge_onto, vertex_to_merge_3, mesh); - merge_identical_points(vertex_to_merge_onto_2, vertex_to_merge_4, mesh); - mesh.collect_garbage(); - - const std::size_t vertices_after_merge = num_vertices(mesh); - assert(vertices_after_merge == initial_vertices - 4); - - std::vector > duplicated_vertices; - std::size_t new_vertices_nb = - CGAL::Polygon_mesh_processing::duplicate_non_manifold_vertices(mesh, - CGAL::parameters::output_iterator(std::back_inserter(duplicated_vertices))); - - const std::size_t final_vertices_size = vertices(mesh).size(); - assert(final_vertices_size == initial_vertices); - assert(new_vertices_nb == 4); - assert(duplicated_vertices.size() == 2); // two non-manifold vertex - assert(duplicated_vertices.front().size() == 4); - assert(duplicated_vertices.back().size() == 2); - - std::cout << "done" << std::endl; -} - void test_needles_and_caps(const char* fname) { std::cout << "test needles&caps..."; @@ -243,8 +140,7 @@ int main() { check_edge_degeneracy("data_degeneracies/degtri_edge.off"); check_triangle_face_degeneracy("data_degeneracies/degtri_four.off"); - test_vertex_non_manifoldness("data/blobby.off"); - test_vertices_merge_and_duplication("data/blobby.off"); + test_needles_and_caps("data_degeneracies/caps_and_needles.off"); return EXIT_SUCCESS; diff --git a/Property_map/include/CGAL/Dynamic_property_map.h b/Property_map/include/CGAL/Dynamic_property_map.h index 24532c41e55..4934bfe2202 100644 --- a/Property_map/include/CGAL/Dynamic_property_map.h +++ b/Property_map/include/CGAL/Dynamic_property_map.h @@ -27,6 +27,9 @@ #include #include +#include +#include + namespace CGAL { namespace internal { @@ -131,8 +134,12 @@ struct Dynamic_with_index { typedef Key key_type; typedef Value value_type; - typedef value_type& reference; - typedef boost::lvalue_property_map_tag category; + typedef typename boost::mpl::if_< boost::is_same, + value_type, + value_type&>::type reference; + typedef typename boost::mpl::if_< boost::is_same, + boost::read_write_property_map_tag, + boost::lvalue_property_map_tag>::type category; Dynamic_with_index() : m_values()