diff --git a/BGL/include/CGAL/boost/graph/helpers.h b/BGL/include/CGAL/boost/graph/helpers.h index d1c6d3e78f0..b1e9068afea 100644 --- a/BGL/include/CGAL/boost/graph/helpers.h +++ b/BGL/include/CGAL/boost/graph/helpers.h @@ -27,6 +27,7 @@ #include #include #include +#include namespace CGAL { @@ -1174,7 +1175,6 @@ bool is_empty(const FaceGraph& g) return boost::empty(vertices(g)); } - } // namespace CGAL // Include "Euler_operations.h" at the end, because its implementation diff --git a/BGL/include/CGAL/boost/graph/selection.h b/BGL/include/CGAL/boost/graph/selection.h index ff32f390abf..89945046ac4 100644 --- a/BGL/include/CGAL/boost/graph/selection.h +++ b/BGL/include/CGAL/boost/graph/selection.h @@ -23,6 +23,7 @@ #include #include #include +#include namespace CGAL { @@ -521,6 +522,102 @@ reduce_vertex_selection( return out; } +/** + * \ingroup PkgBGLSelectionFct + * + * Expands a selection of faces so that their removal does not create any non manifold vertex. + * For each vertex that is incident to a selected face, we turn around that vertex and check + * if there is exactly one set of consecutive selected faces. If not, additional faces around + * that vertex are selected to match this condition. + * + * @tparam TriangleMesh a model of `FaceGraph` that is triangulated. + * @tparam FaceRange a range of `boost::graph_traits::%face_descriptor`, + * with an iterator type model of `ForwardIterator`. + * @tparam IsSelectedMap a model of `ReadWritePropertyMap` with + * `boost::graph_traits::%face_descriptor` as key and `bool` as value. + + * @param tm the triangle mesh. + * @param faces_to_be_deleted the range of selected faces. + * @param is_selected a property map containing the selected-or-not status of each face of `tm`. + * It must associate `true` to each face of `faces_to_be_deleted` and `false` to any other face of `tm`. + * It will be modified if the face selection must be expanded. + * + **/ +template +void expand_face_selection_for_removal(TriangleMesh& tm, + const FaceRange& faces_to_be_deleted, + IsSelectedMap is_selected) +{ + typedef typename boost::graph_traits::vertex_descriptor vertex_descriptor; + typedef typename boost::graph_traits::face_descriptor face_descriptor; + typedef typename boost::graph_traits::halfedge_descriptor halfedge_descriptor; + + boost::unordered_set vertices_queue; + + // collect vertices belonging to at least a triangle that will be removed + BOOST_FOREACH(face_descriptor fd, faces_to_be_deleted) + { + halfedge_descriptor h = halfedge(fd, tm); + vertices_queue.insert( target(h, tm) ); + vertices_queue.insert( target(next(h, tm), tm) ); + vertices_queue.insert( target(prev(h, tm), tm) ); + } + + while (!vertices_queue.empty()) + { + vertex_descriptor vd = *vertices_queue.begin(); + vertices_queue.erase( vertices_queue.begin() ); + + // set hd to the last selected face of a connected component + // of selected faces around a vertex + halfedge_descriptor hd = halfedge(vd, tm); + while( !get(is_selected, face(hd, tm)) ) + { + hd = opposite( next(hd, tm), tm); + CGAL_assertion( hd != halfedge(vd, tm) ); + } + halfedge_descriptor start = hd; + halfedge_descriptor next_around_vertex = opposite( next(hd, tm), tm); + while( get(is_selected, face(next_around_vertex, tm) ) ) + { + hd = next_around_vertex; + next_around_vertex = opposite( next(hd, tm), tm); + if (hd==start) break; + } + if ( get(is_selected, face(next_around_vertex, tm) ) ) continue; //all incident faces will be removed + + while( true ) + { + // collect non-selected faces + std::vector faces_traversed; + do + { + faces_traversed.push_back(next_around_vertex); + next_around_vertex = opposite( next(next_around_vertex, tm), tm); + } + while( !get(is_selected, face(next_around_vertex, tm) ) ); + + // go over the connected components of faces to remove + do{ + if (next_around_vertex==start) + break; + next_around_vertex = opposite( next(next_around_vertex, tm), tm); + } + while( get(is_selected, face(next_around_vertex, tm) ) ); + + if (next_around_vertex==start) + break; + + BOOST_FOREACH(halfedge_descriptor f_hd, faces_traversed) + { + assert(target(f_hd, tm) == vd); + put(is_selected, face(f_hd, tm), true); + vertices_queue.insert( target( next(f_hd, tm), tm) ); + vertices_queue.insert( source(f_hd, tm) ); + } + } + } +} } //end of namespace CGAL #endif //CGAL_BOOST_GRAPH_KTH_SIMPLICIAL_NEIGHBORHOOD_H diff --git a/BGL/test/BGL/CMakeLists.txt b/BGL/test/BGL/CMakeLists.txt index e15afcf3ac8..6f81059c200 100644 --- a/BGL/test/BGL/CMakeLists.txt +++ b/BGL/test/BGL/CMakeLists.txt @@ -95,6 +95,8 @@ create_single_source_cgal_program( "test_bgl_read_write.cpp" ) create_single_source_cgal_program( "graph_concept_Face_filtered_graph.cpp" ) +create_single_source_cgal_program( "test_Manifold_face_removal.cpp") + create_single_source_cgal_program( "test_Face_filtered_graph.cpp" ) create_single_source_cgal_program( "test_Euler_operations.cpp" ) diff --git a/BGL/test/BGL/test_Manifold_face_removal.cpp b/BGL/test/BGL/test_Manifold_face_removal.cpp new file mode 100644 index 00000000000..42b1e98fe63 --- /dev/null +++ b/BGL/test/BGL/test_Manifold_face_removal.cpp @@ -0,0 +1,62 @@ +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +typedef CGAL::Exact_predicates_inexact_constructions_kernel Kernel; +typedef CGAL::Surface_mesh SM; +typedef boost::graph_traits::face_descriptor face_descriptor; + +int main() +{ + SM sm; + std::ifstream input("data/head.off"); + input >> sm; + +// define my selection of faces to remove + boost::unordered_map is_selected_map; + + const int selection_indices[] = {501, 652, 646, 322, 328, 212, 347, 345, 352, 353, 696, 697, 698, 706, 714, 2892}; + std::set index_set(&selection_indices[0], &selection_indices[0]+16); + + std::vector faces_to_remove; + int index = 0; + BOOST_FOREACH(face_descriptor fh, faces(sm)) + { + if(index_set.count(index)==0) + is_selected_map[fh]=false; + else + { + faces_to_remove.push_back(fh); + is_selected_map[fh]=true; + } + ++index; + } + + expand_face_selection_for_removal(sm, + faces_to_remove, + boost::make_assoc_property_map(is_selected_map)); + + index=0; + BOOST_FOREACH(face_descriptor fh, faces(sm)) + { + if (is_selected_map[fh]) + { + CGAL::Euler::remove_face(halfedge(fh, sm), sm); + ++index; + } + } + + assert(index == 25); + assert(is_valid(sm)); + return 0; +} + diff --git a/Installation/changes.html b/Installation/changes.html index e172acc896b..bd49e80a74a 100644 --- a/Installation/changes.html +++ b/Installation/changes.html @@ -155,8 +155,15 @@ and src/ directories). - - + +

CGAL and the Boost Graph Library (BGL)

+
    +
  • + Add helper function CGAL::expand_face_selection_for_removal that + expands a face selection to avoid creating a non manifold mesh when removing the selected faces. +
  • +
+ diff --git a/Polyhedron/demo/Polyhedron/Plugins/PMP/Selection_plugin.cpp b/Polyhedron/demo/Polyhedron/Plugins/PMP/Selection_plugin.cpp index 1bc73047db5..d8782d4f8d9 100644 --- a/Polyhedron/demo/Polyhedron/Plugins/PMP/Selection_plugin.cpp +++ b/Polyhedron/demo/Polyhedron/Plugins/PMP/Selection_plugin.cpp @@ -491,8 +491,44 @@ public Q_SLOTS: selection_item->keep_connected_components(); break; } - //Convert from Edge Selection to Facet Selection + //Expand face selection case 5: + { + Scene_polyhedron_selection_item* selection_item = getSelectedItem(); + if (!selection_item || + selection_item->selected_facets.empty()) + { + print_message("Error: Please select a selection item with a selection of faces."); + return; + } + boost::unordered_map is_selected_map; + int index = 0; + BOOST_FOREACH(fg_face_descriptor fh, faces(*selection_item->polyhedron())) + { + if(selection_item->selected_facets.find(fh) + == selection_item->selected_facets.end()) + is_selected_map[fh]=false; + else + { + is_selected_map[fh]=true; + } + ++index; + } + CGAL::expand_face_selection_for_removal(*selection_item->polyhedron(), + selection_item->selected_facets, + boost::make_assoc_property_map(is_selected_map)); + + BOOST_FOREACH(fg_face_descriptor fh, faces(*selection_item->polyhedron())) + { + if (is_selected_map[fh]) + selection_item->selected_facets.insert(fh); + } + selection_item->invalidateOpenGLBuffers(); + selection_item->itemChanged(); + break; + } + //Convert from Edge Selection to Facet Selection + case 6: { Scene_polyhedron_selection_item* selection_item = getSelectedItem(); if(!selection_item) { @@ -514,7 +550,7 @@ public Q_SLOTS: break; } //Convert from Edge Selection to Point Selection - case 6: + case 7: { Scene_polyhedron_selection_item* selection_item = getSelectedItem(); if(!selection_item) { @@ -537,7 +573,7 @@ public Q_SLOTS: break; } //Convert from Facet Selection to Bounding Edge Selection - case 7: + case 8: { Scene_polyhedron_selection_item* selection_item = getSelectedItem(); if(!selection_item) { @@ -560,7 +596,7 @@ public Q_SLOTS: break; } //Convert from Facet Selection to Points Selection - case 8: + case 9: { Scene_polyhedron_selection_item* selection_item = getSelectedItem(); if(!selection_item) { diff --git a/Polyhedron/demo/Polyhedron/Plugins/PMP/Selection_widget.ui b/Polyhedron/demo/Polyhedron/Plugins/PMP/Selection_widget.ui index 2cc148be005..c9dd4fec2b2 100644 --- a/Polyhedron/demo/Polyhedron/Plugins/PMP/Selection_widget.ui +++ b/Polyhedron/demo/Polyhedron/Plugins/PMP/Selection_widget.ui @@ -6,7 +6,7 @@ 0 0 - 597 + 613 334 @@ -429,6 +429,13 @@ + + + + PushButton + + + @@ -475,6 +482,11 @@ Keep Connected Components of Selected Facets + + + Expand Face Selection to Stay Manifold After Removal + + Convert from Edge Selection to Facets Selection