diff --git a/Polyhedron/demo/Polyhedron/Plugins/Operations_on_polyhedra/Clip_polyhedron_plugin.ui b/Polyhedron/demo/Polyhedron/Plugins/Operations_on_polyhedra/Clip_polyhedron_plugin.ui index 3f001f77f78..7c5473bb039 100644 --- a/Polyhedron/demo/Polyhedron/Plugins/Operations_on_polyhedra/Clip_polyhedron_plugin.ui +++ b/Polyhedron/demo/Polyhedron/Plugins/Operations_on_polyhedra/Clip_polyhedron_plugin.ui @@ -11,7 +11,7 @@ - Clip polyhedra + Clip Polyhedra @@ -20,7 +20,7 @@ - <html><head/><body><p>This function allows to clip all the selected polyhedra by an half-space. The blue side of the clipping plane will be clipped out, and the yellow side will be kept.<br/><br/>If the option <span style=" font-style:italic;">keep closed</span> is checked, the clipped part of each polyhedron will be closed. Otherwise, it will be left open.</p><p><br/></p></body></html> + <html><head/><body><p>This function allows to clip all the selected polyhedra against a halfspace. What is on the blue side of the clipping plane will be clipped, and what is on the yellow side will be kept.<br/><br/>If the option <span style=" font-style:italic;">keep closed</span> is checked, the clipped part of each polyhedron will be closed, if it has a closed contour on the clipping plane. Otherwise, it will be left open.</p><p><br/></p></body></html> true diff --git a/Polyhedron/demo/Polyhedron/Plugins/PMP/Isotropic_remeshing_dialog.ui b/Polyhedron/demo/Polyhedron/Plugins/PMP/Isotropic_remeshing_dialog.ui index 51a81169f3d..3ddde77db51 100644 --- a/Polyhedron/demo/Polyhedron/Plugins/PMP/Isotropic_remeshing_dialog.ui +++ b/Polyhedron/demo/Polyhedron/Plugins/PMP/Isotropic_remeshing_dialog.ui @@ -162,6 +162,23 @@ + + + + + + + + + + + Preserve duplicated edges + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + diff --git a/Polyhedron/demo/Polyhedron/Plugins/PMP/Isotropic_remeshing_plugin.cpp b/Polyhedron/demo/Polyhedron/Plugins/PMP/Isotropic_remeshing_plugin.cpp index e2549ee3fcf..8861170d0d4 100644 --- a/Polyhedron/demo/Polyhedron/Plugins/PMP/Isotropic_remeshing_plugin.cpp +++ b/Polyhedron/demo/Polyhedron/Plugins/PMP/Isotropic_remeshing_plugin.cpp @@ -14,8 +14,12 @@ #include #include #include +#include +#include +#include #include +#include #include #include @@ -39,6 +43,118 @@ #include "ui_Isotropic_remeshing_dialog.h" +// give a halfedge and a target edge length, put in `out` points +// which the edge equally spaced such that splitting the edge +// using the sequence of points make the edges shorter than +// `target_length` +template +PointOutputIterator +sample_edge( + typename boost::graph_traits::halfedge_descriptor hd, + TriangleMesh& triangle_mesh, + double target_length, + const PointPMap& pmap, + PointOutputIterator out) +{ + typedef typename boost::property_traits::value_type Point_3; + typedef typename CGAL::Kernel_traits::Kernel::Vector_3 Vector_3; + typename boost::property_traits::reference src=get(pmap, source(hd,triangle_mesh) ); + typename boost::property_traits::reference tgt=get(pmap, target(hd,triangle_mesh) ); + + double length = std::sqrt( CGAL::squared_distance(src, tgt) ); + if ( length <= target_length ) return out; + + double nb_points = std::floor( length / target_length ); + Vector_3 unit = (tgt-src) / (nb_points+1); + + for(double i=0; i +EdgeOutputIterator +split_identical_edges( + typename boost::graph_traits::halfedge_descriptor hd, + TriangleMesh& tm, + const PointPMap& pmap, + const PointRange& points, + EdgeOutputIterator out) +{ + typedef typename PointRange::value_type Point_3; + typedef boost::graph_traits GT; + typedef typename GT::halfedge_descriptor halfedge_descriptor; + + BOOST_FOREACH(const Point_3& p, points) + { + // split the edge + halfedge_descriptor new_hd=CGAL::Euler::split_edge(hd,tm); + // set the vertex point + put(pmap, target(new_hd, tm), p); + *out++=edge(new_hd, tm); + } + *out++=edge(hd, tm); + return out; +} + +// HedgeRange is expected to be a range with value type being +// std::pair +// Given a set of halfedges representing different edges +// but with identical endpoints, and a target edge length +// we split all edges identically so that subedges are +// or length <= length +template +void split_long_duplicated_edge(const HedgeRange& hedge_range, + double target_length, + Edges_to_protect& edges_to_protect) +{ + typedef typename HedgeRange::value_type Pair; + typedef typename Pair::first_type halfedge_descriptor; + typedef typename boost::remove_pointer< + typename Pair::second_type>::type TriangleMesh; + typedef typename boost::property_map::type PointPMap; + typedef typename boost::property_traits::value_type Point_3; + + if (hedge_range.empty()) return; + + const Pair& p = *hedge_range.begin(); + PointPMap pmap = get(boost::vertex_point, *p.second); + + std::vector points; + halfedge_descriptor hd = p.first; + + // collect points to be add inside the edges + sample_edge(hd, *p.second, target_length, pmap, std::back_inserter(points) ); + + CGAL_assertion_code(Point_3 src = get(pmap, source(hd, *p.second));) + CGAL_assertion_code(Point_3 tgt = get(pmap, target(hd, *p.second));) + + // split the edges and collect faces to triangulate + BOOST_FOREACH(const Pair& h_and_p, hedge_range) + { + halfedge_descriptor hc=h_and_p.first; + TriangleMesh* polyc = h_and_p.second; + + PointPMap pmap_2 = get(boost::vertex_point, *polyc); + //make sure halfedge are consistently oriented + CGAL_assertion( get(pmap_2, source(hc, *polyc)) == src ); + CGAL_assertion( get(pmap_2, target(hc, *polyc)) == tgt ); + + typedef typename Edges_to_protect::value_type::second_type Edge_set; + Edge_set& edge_set = edges_to_protect[polyc]; + + // now split the halfedge and incident faces + split_identical_edges(hc,*polyc,pmap_2, points, + std::inserter( edge_set, edge_set.begin())); + } +} + using namespace CGAL::Three; class Polyhedron_demo_isotropic_remeshing_plugin : public QObject, @@ -48,6 +164,13 @@ class Polyhedron_demo_isotropic_remeshing_plugin : Q_INTERFACES(CGAL::Three::Polyhedron_demo_plugin_interface) Q_PLUGIN_METADATA(IID "com.geometryfactory.PolyhedronDemo.PluginInterface/1.0") + typedef boost::graph_traits::edge_descriptor edge_descriptor; + typedef boost::graph_traits::halfedge_descriptor halfedge_descriptor; + typedef boost::graph_traits::face_descriptor face_descriptor; + + typedef boost::unordered_set Edge_set; + typedef Scene_polyhedron_selection_item::Is_constrained_map Edge_constrained_pmap; + public: void init(QMainWindow* mainWindow, Scene_interface* scene_interface) { @@ -83,6 +206,58 @@ public: return false; } + void detect_and_split_duplicates(std::vector& selection, + std::map& edges_to_protect, + double target_length) + { + typedef Polyhedron::Point_3 Point_3; + typedef std::pair Segment_3; + + typedef std::map< Segment_3, + std::vector< std::pair > > MapType; + MapType duplicated_edges; + + + BOOST_FOREACH(Scene_polyhedron_item* poly_item, selection){ + Polyhedron& pmesh = *poly_item->polyhedron(); + BOOST_FOREACH(edge_descriptor ed, edges(pmesh)){ + halfedge_descriptor hd = halfedge(ed,pmesh); + Point_3 p = source(hd,pmesh)->point(), q = target(hd,pmesh)->point(); + Segment_3 s = CGAL::make_sorted_pair(p,q); + if (s.first==q) hd=opposite(hd,pmesh); // make sure the halfedges are consistently oriented + + duplicated_edges[s].push_back( std::make_pair(hd,&pmesh) ); + } + } + + // consistently split duplicate edges and triangulate incident faces + typedef std::pair Face_and_poly; + std::set< Face_and_poly > faces_to_triangulate; + BOOST_FOREACH(const MapType::value_type& p, duplicated_edges) + if (p.second.size()>1){ + //collect faces to retriangulate + typedef std::pair Pair_type; + BOOST_FOREACH(const Pair_type& h_and_p, p.second) + { + halfedge_descriptor hc=h_and_p.first; + Polyhedron* polyc = h_and_p.second; + + if ( !is_border(hc, *polyc) ) + faces_to_triangulate.insert( Face_and_poly(face(hc,*polyc), polyc) ); + if ( !is_border(opposite(hc, *polyc), *polyc) ) + faces_to_triangulate.insert( + Face_and_poly(face(opposite(hc, *polyc),*polyc), polyc) ); + } + // split the edges + split_long_duplicated_edge(p.second, target_length, edges_to_protect); + } + // now retriangulate + namespace PMP=CGAL::Polygon_mesh_processing; + BOOST_FOREACH(Face_and_poly f_and_p, faces_to_triangulate) + PMP::triangulate_face(f_and_p.first, *f_and_p.second); + } + + public Q_SLOTS: void isotropic_remeshing() { @@ -114,6 +289,7 @@ public Q_SLOTS: return; } bool edges_only = ui.splitEdgesOnly_checkbox->isChecked(); + bool preserve_duplicates = ui.preserveDuplicates_checkbox->isChecked(); double target_length = ui.edgeLength_dspinbox->value(); unsigned int nb_iter = ui.nbIterations_spinbox->value(); bool protect = ui.protect_checkbox->isChecked(); @@ -132,6 +308,15 @@ public Q_SLOTS: const Polyhedron& pmesh = (poly_item != NULL) ? *poly_item->polyhedron() : *selection_item->polyhedron(); + + // tricks to use the function detect_and_split_duplicates + // that uses several poly items + std::map edges_to_protect_map; + std::vector poly_items(1,poly_item); + Edge_set& edges_to_protect=edges_to_protect_map[poly_item->polyhedron()]; + if(preserve_duplicates) + detect_and_split_duplicates(poly_items, edges_to_protect_map, target_length); + boost::property_map::type fim = get(CGAL::face_index, pmesh); unsigned int id = 0; @@ -174,13 +359,16 @@ public Q_SLOTS: } else { + Edge_constrained_pmap ecm(edges_to_protect); + CGAL::OR_property_map etp(ecm, selection_item->constrained_edges_pmap()); + CGAL::Polygon_mesh_processing::isotropic_remeshing( selection_item->selected_facets , target_length , *selection_item->polyhedron() , CGAL::Polygon_mesh_processing::parameters::number_of_iterations(nb_iter) .protect_constraints(protect) - .edge_is_constrained_map(selection_item->constrained_edges_pmap()) + .edge_is_constrained_map(etp) .smooth_along_features(smooth_features) .vertex_is_constrained_map(selection_item->constrained_vertices_pmap())); } @@ -212,12 +400,14 @@ public Q_SLOTS: } else { + Scene_polyhedron_selection_item::Is_constrained_map ecm(edges_to_protect); CGAL::Polygon_mesh_processing::isotropic_remeshing( faces(*poly_item->polyhedron()) , target_length , *poly_item->polyhedron() , CGAL::Polygon_mesh_processing::parameters::number_of_iterations(nb_iter) .protect_constraints(protect) + .edge_is_constrained_map(ecm) .smooth_along_features(smooth_features)); } poly_item->invalidateOpenGLBuffers(); @@ -236,7 +426,7 @@ public Q_SLOTS: void isotropic_remeshing_of_several_polyhedra() { // Remeshing parameters - bool edges_only = false; + bool edges_only = false, preserve_duplicates = false; double target_length = 0.; unsigned int nb_iter = 1; bool protect = false; @@ -272,6 +462,7 @@ public Q_SLOTS: } edges_only = ui.splitEdgesOnly_checkbox->isChecked(); + preserve_duplicates = ui.preserveDuplicates_checkbox->isChecked(); target_length = ui.edgeLength_dspinbox->value(); nb_iter = ui.nbIterations_spinbox->value(); protect = ui.protect_checkbox->isChecked(); @@ -290,6 +481,13 @@ public Q_SLOTS: QApplication::setOverrideCursor(Qt::WaitCursor); int total_time = 0; + + // typedef boost::graph_traits::edge_descriptor edge_descriptor; + std::map edges_to_protect; + + if(preserve_duplicates) + detect_and_split_duplicates(selection, edges_to_protect, target_length); + #ifdef CGAL_LINKED_WITH_TBB QTime time; time.start(); @@ -297,10 +495,12 @@ public Q_SLOTS: tbb::parallel_for( tbb::blocked_range(0, selection.size()), Remesh_polyhedron_item_for_parallel_for( - selection, edges_only, target_length, nb_iter, protect, smooth_features)); + selection, edges_to_protect, edges_only, target_length, nb_iter, protect, smooth_features)); total_time = time.elapsed(); + #else + Remesh_polyhedron_item remesher(edges_only, target_length, nb_iter, protect, smooth_features); BOOST_FOREACH(Scene_polyhedron_item* poly_item, selection) @@ -308,7 +508,7 @@ public Q_SLOTS: QTime time; time.start(); - remesher(poly_item); + remesher(poly_item, edges_to_protect[poly_item->polyhedron()]); total_time += time.elapsed(); std::cout << "Remeshing of " << poly_item->name().data() @@ -323,7 +523,7 @@ public Q_SLOTS: poly_item->invalidateOpenGLBuffers(); Q_EMIT poly_item->itemChanged(); } - + // default cursor QApplication::restoreOverrideCursor(); } @@ -342,7 +542,8 @@ private: bool smooth_features_; protected: - void remesh(Scene_polyhedron_item* poly_item) const + void remesh(Scene_polyhedron_item* poly_item, + Edge_set& edges_to_protect) const { //fill face_index property map boost::property_map::type fim @@ -369,12 +570,14 @@ private: } else { + Scene_polyhedron_selection_item::Is_constrained_map ecm(edges_to_protect); CGAL::Polygon_mesh_processing::isotropic_remeshing( faces(*poly_item->polyhedron()) , target_length_ , *poly_item->polyhedron() , CGAL::Polygon_mesh_processing::parameters::number_of_iterations(nb_iter_) .protect_constraints(protect_) + .edge_is_constrained_map(ecm) .smooth_along_features(smooth_features_)); } } @@ -401,9 +604,10 @@ private: , smooth_features_(remesh.smooth_features_) {} - void operator()(Scene_polyhedron_item* poly_item) const + void operator()(Scene_polyhedron_item* poly_item, + Edge_set& edges_to_protect) const { - remesh(poly_item); + remesh(poly_item, edges_to_protect); } }; @@ -413,34 +617,35 @@ private: : RemeshFunctor { const std::vector& selection_; + std::map& edges_to_protect_; public: // Constructor Remesh_polyhedron_item_for_parallel_for( const std::vector& selection, + std::map& edges_to_protect, const bool edges_only, const double target_length, const unsigned int nb_iter, const bool protect, const bool smooth_features) : RemeshFunctor(edges_only, target_length, nb_iter, protect, smooth_features) - , selection_(selection) - { - ; - } + , selection_(selection), edges_to_protect_(edges_to_protect) + {} // Constructor Remesh_polyhedron_item_for_parallel_for( const Remesh_polyhedron_item_for_parallel_for &remesh) : RemeshFunctor(remesh) , selection_(remesh.selection_) + , edges_to_protect_(remesh.edges_to_protect_) {} // operator() void operator()(const tbb::blocked_range& r) const { for (size_t i = r.begin(); i != r.end(); ++i) - RemeshFunctor::remesh(selection_[i]); + RemeshFunctor::remesh(selection_[i], edges_to_protect_[selection_[i]->polyhedron()]); } }; #endif @@ -464,6 +669,10 @@ private: ui.smooth1D_checkbox, SLOT(setDisabled(bool))); connect(ui.splitEdgesOnly_checkbox, SIGNAL(toggled(bool)), ui.smooth1D_checkbox, SLOT(setDisabled(bool))); + connect(ui.preserveDuplicates_checkbox, SIGNAL(toggled(bool)), + ui.protect_checkbox, SLOT(setChecked(bool))); + connect(ui.preserveDuplicates_checkbox, SIGNAL(toggled(bool)), + ui.protect_checkbox, SLOT(setDisabled(bool))); //Set default parameters Scene_interface::Bbox bbox = poly_item != NULL ? poly_item->bbox() diff --git a/Property_map/include/CGAL/property_map.h b/Property_map/include/CGAL/property_map.h index fa41272e98a..a4a8f871169 100644 --- a/Property_map/include/CGAL/property_map.h +++ b/Property_map/include/CGAL/property_map.h @@ -38,6 +38,40 @@ namespace CGAL { /// \cond SKIP_DOXYGEN + + +template +class OR_property_map { + typedef typename PM1::key_type key_type; + typedef typename PM1::value_type value_type; + typedef typename PM1::reference reference; + typedef boost::read_write_property_map_tag category; + + PM1 pm1; + PM2 pm2; + + public: + OR_property_map(PM1 pm1, PM2 pm2) + : pm1(pm1),pm2(pm2) + {} + + inline friend + value_type + get(const OR_property_map& pm, const key_type& k) + { + return get(pm.pm1,k) || get(pm.pm2,k); + } + + inline friend + void + put(OR_property_map& pm, const key_type& k, const value_type& v) + { + put(pm.pm1,k, v); + put(pm.pm2,k, v); + } + +}; + /// Property map that accesses a value from an iterator /// /// \cgalModels `ReadablePropertyMap`