From a207317cf51162ce4c5be0322c8f885de922c96a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Loriot?= Date: Wed, 23 May 2018 17:14:58 +0200 Subject: [PATCH 01/43] add a parameter in corefinement functions to track the creation of new faces --- .../CGAL/boost/graph/parameters_interface.h | 1 + BGL/test/BGL/test_cgal_bgl_named_params.cpp | 3 + .../Concepts/PMPCorefinementNewFaceVisitor.h | 44 ++++++++ .../PackageDescription.txt | 1 + .../Polygon_mesh_processing/CMakeLists.txt | 1 + ...orefinement_mesh_union_with_attributes.cpp | 106 ++++++++++++++++++ .../Polygon_mesh_processing/corefinement.h | 66 ++++++++--- .../Corefinement/Face_graph_output_builder.h | 29 +++-- .../internal/Corefinement/Visitor.h | 9 -- .../internal/Corefinement/face_graph_utils.h | 77 +++++++++---- 10 files changed, 281 insertions(+), 56 deletions(-) create mode 100644 Polygon_mesh_processing/doc/Polygon_mesh_processing/Concepts/PMPCorefinementNewFaceVisitor.h create mode 100644 Polygon_mesh_processing/examples/Polygon_mesh_processing/corefinement_mesh_union_with_attributes.cpp diff --git a/BGL/include/CGAL/boost/graph/parameters_interface.h b/BGL/include/CGAL/boost/graph/parameters_interface.h index fd75acf47d3..b3d44074380 100644 --- a/BGL/include/CGAL/boost/graph/parameters_interface.h +++ b/BGL/include/CGAL/boost/graph/parameters_interface.h @@ -64,6 +64,7 @@ CGAL_add_named_parameter(nb_points_per_distance_unit_t, nb_points_per_distance_u CGAL_add_named_parameter(outward_orientation_t, outward_orientation, outward_orientation) CGAL_add_named_parameter(overlap_test_t, overlap_test, do_overlap_test_of_bounded_sides) CGAL_add_named_parameter(preserve_genus_t, preserve_genus, preserve_genus) +CGAL_add_named_parameter(new_face_visitor_t, new_face_visitor, new_face_visitor) // List of named parameters that we use in the package 'Surface Mesh Simplification' CGAL_add_named_parameter(get_cost_policy_t, get_cost_policy, get_cost) diff --git a/BGL/test/BGL/test_cgal_bgl_named_params.cpp b/BGL/test/BGL/test_cgal_bgl_named_params.cpp index 91cd3c5ff4a..013931e028e 100644 --- a/BGL/test/BGL/test_cgal_bgl_named_params.cpp +++ b/BGL/test/BGL/test_cgal_bgl_named_params.cpp @@ -72,6 +72,7 @@ void test(const NamedParameters& np) assert(get_param(np, CGAL::internal_np::number_of_points_on_edges).v == 31); assert(get_param(np, CGAL::internal_np::nb_points_per_area_unit).v == 32); assert(get_param(np, CGAL::internal_np::nb_points_per_distance_unit).v == 33); + assert(get_param(np, CGAL::internal_np::new_face_visitor).v == 42); // Named parameters that we use in the package 'Surface Mesh Simplification' assert(get_param(np, CGAL::internal_np::get_cost_policy).v == 34); @@ -136,6 +137,7 @@ void test(const NamedParameters& np) check_same_type<31>(get_param(np, CGAL::internal_np::number_of_points_on_edges)); check_same_type<32>(get_param(np, CGAL::internal_np::nb_points_per_area_unit)); check_same_type<33>(get_param(np, CGAL::internal_np::nb_points_per_distance_unit)); + check_same_type<42>(get_param(np, CGAL::internal_np::new_face_visitor)); // Named parameters that we use in the package 'Surface Mesh Simplification' check_same_type<34>(get_param(np, CGAL::internal_np::get_cost_policy)); @@ -199,6 +201,7 @@ int main() .weight_calculator(A<39>(39)) .preserve_genus(A<40>(40)) .verbosity_level(A<41>(41)) + .new_face_visitor(A<42>(42)) ); return EXIT_SUCCESS; diff --git a/Polygon_mesh_processing/doc/Polygon_mesh_processing/Concepts/PMPCorefinementNewFaceVisitor.h b/Polygon_mesh_processing/doc/Polygon_mesh_processing/Concepts/PMPCorefinementNewFaceVisitor.h new file mode 100644 index 00000000000..04e77634c40 --- /dev/null +++ b/Polygon_mesh_processing/doc/Polygon_mesh_processing/Concepts/PMPCorefinementNewFaceVisitor.h @@ -0,0 +1,44 @@ +/// \ingroup PkgPolygonMeshProcessingConcepts +/// \cgalConcept +/// +/// The concept `PMPCorefinementNewFaceVisitor` defines the requirements for the visitor +/// used in \link PMP_corefinement_grp corefinement related functions \endlink to track +/// the creation of new faces. +/// +/// \cgalRefines `CopyConstructible` + + +class PMPCorefinementNewFaceVisitor{ +public: +/// Mesh type +typedef unspecified_type Triangle_mesh; +/// Face decriptor type +typedef unspecified_type face_descriptor; + +/// @name Functions used by corefine() +/// @{ + /// called before the triangulation of `f_split` in `tm`. Note that `f_split` + /// will be one of the faces of the triangulation. Each subsequent call to + /// `before_subface_created()`/`after_subface_created()` will correspond to + /// the creation of a new face of triangulating `f_split`. + void before_subface_creations(face_descriptor f_split, Triangle_mesh& tm); + /// called when the triangulation of a face in `tm` is finished + void after_subface_creations(Mesh& tm); + /// called before creating a new triangle face in `tm` to triangulate the face passed to `before_subface_creations()` + void before_subface_created(Mesh& tm); + /// called after creating a new triangle face `f_new` in `tm` to triangulate the face passed to `before_subface_creations()`. + /// Note that the call is placed just after a call to `add_face()` so the halfedge pointer is set yet. + void after_subface_created(face_descriptor f_new, Triangle_mesh& tm); +/// @} + +/// @name Functions used by functions computing Boolean operations using corefinement +/// These functions are not needed is you only call `corefine()`. +/// @{ + /// called before importing face `f_src` of `tm_src` in `tm_tgt` + void before_face_copy(face_descriptor f_src, Triangle_mesh& tm_src, Triangle_mesh& tm_tgt); + /// called after importing face `f_src` of `tm_src` in `tm_tgt`. The new face if `f_tgt`. + /// Note that the call is placed just after a call to `add_face()` so the halfedge pointer is set yet. + void after_face_copy(face_descriptor f_src, Triangle_mesh& tm_src, + face_descriptor f_tgt, Triangle_mesh& tm_tgt); +/// @} +}; diff --git a/Polygon_mesh_processing/doc/Polygon_mesh_processing/PackageDescription.txt b/Polygon_mesh_processing/doc/Polygon_mesh_processing/PackageDescription.txt index 80e65018910..d9118621645 100644 --- a/Polygon_mesh_processing/doc/Polygon_mesh_processing/PackageDescription.txt +++ b/Polygon_mesh_processing/doc/Polygon_mesh_processing/PackageDescription.txt @@ -148,6 +148,7 @@ and provides a list of the parameters that are used in this package. - `CGAL::Polygon_mesh_processing::corefine()` - `CGAL::Polygon_mesh_processing::surface_intersection()` - `CGAL::Polygon_mesh_processing::does_bound_a_volume()` +- `PMPCorefinementNewFaceVisitor` ## Geometric Measure Functions ## - \link measure_grp `CGAL::Polygon_mesh_processing::face_area()` \endlink diff --git a/Polygon_mesh_processing/examples/Polygon_mesh_processing/CMakeLists.txt b/Polygon_mesh_processing/examples/Polygon_mesh_processing/CMakeLists.txt index 2cffcc102aa..a83d693ab22 100644 --- a/Polygon_mesh_processing/examples/Polygon_mesh_processing/CMakeLists.txt +++ b/Polygon_mesh_processing/examples/Polygon_mesh_processing/CMakeLists.txt @@ -97,6 +97,7 @@ create_single_source_cgal_program( "corefinement_SM.cpp") create_single_source_cgal_program( "corefinement_consecutive_bool_op.cpp" ) create_single_source_cgal_program( "corefinement_difference_remeshed.cpp" ) create_single_source_cgal_program( "corefinement_mesh_union.cpp" ) +create_single_source_cgal_program( "corefinement_mesh_union_with_attributes.cpp" ) create_single_source_cgal_program( "corefinement_polyhedron_union.cpp" ) create_single_source_cgal_program( "random_perturbation_SM_example.cpp" ) create_single_source_cgal_program( "corefinement_LCC.cpp") diff --git a/Polygon_mesh_processing/examples/Polygon_mesh_processing/corefinement_mesh_union_with_attributes.cpp b/Polygon_mesh_processing/examples/Polygon_mesh_processing/corefinement_mesh_union_with_attributes.cpp new file mode 100644 index 00000000000..6dcbd51e301 --- /dev/null +++ b/Polygon_mesh_processing/examples/Polygon_mesh_processing/corefinement_mesh_union_with_attributes.cpp @@ -0,0 +1,106 @@ +#include +#include +#include +#include + +#include + +typedef CGAL::Exact_predicates_inexact_constructions_kernel K; +typedef CGAL::Surface_mesh Mesh; + +namespace PMP = CGAL::Polygon_mesh_processing; + +struct New_face_visitor +{ + typedef typename Mesh::Face_index face_descriptor; + + boost::container::flat_map > properties; + int face_id; + + New_face_visitor() + { + properties.reserve(3); + face_id=-1; + } + +// required visitor API + void after_subface_creations(Mesh&){} + void before_subface_created(Mesh&){} + void before_face_copy(face_descriptor, Mesh&, Mesh&){} + + void before_subface_creations(face_descriptor f_split,Mesh& tm) + { + face_id = properties[&tm][f_split]; + } + + void after_subface_created(face_descriptor f_new,Mesh& tm) + { + properties[&tm][f_new] = face_id; + } + + void after_face_copy(face_descriptor f_src, Mesh& tm_src, + face_descriptor f_tgt, Mesh& tm_tgt) + { + properties[&tm_tgt][f_tgt] = properties[&tm_src][f_src]; + } +}; + +int main(int argc, char* argv[]) +{ + const char* filename1 = (argc > 1) ? argv[1] : "data/blobby.off"; + const char* filename2 = (argc > 2) ? argv[2] : "data/eight.off"; + std::ifstream input(filename1); + + Mesh mesh1, mesh2; + if (!input || !(input >> mesh1)) + { + std::cerr << "First mesh is not a valid off file." << std::endl; + return 1; + } + input.close(); + input.open(filename2); + if (!input || !(input >> mesh2)) + { + std::cerr << "Second mesh is not a valid off file." << std::endl; + return 1; + } + + Mesh out; + + // add a property in each mesh to track the parent mesh for each face of the output + Mesh::Property_map + mesh1_id = mesh1.add_property_map("f:id", -1).first, + mesh2_id = mesh2.add_property_map("f:id", -1).first, + out_id = out.add_property_map("f:id", -1).first; + + // init the face ids (for the purpose of the example but choosing 1 (2) as default value of the map would avoid the loop) + BOOST_FOREACH(Mesh::Face_index f, faces(mesh1)) + mesh1_id[f] = 1; + BOOST_FOREACH(Mesh::Face_index f, faces(mesh2)) + mesh2_id[f] = 2; + + New_face_visitor nfv; + nfv.properties[&mesh1] = mesh1_id; + nfv.properties[&mesh2] = mesh2_id; + nfv.properties[&out] = out_id; + + bool valid_union = PMP::corefine_and_compute_union(mesh1, mesh2, out, PMP::parameters::new_face_visitor(nfv)); + + BOOST_FOREACH(Mesh::Face_index f, faces(mesh1)) + assert( mesh1_id[f] == 1 ); + BOOST_FOREACH(Mesh::Face_index f, faces(mesh2)) + assert( mesh2_id[f] == 2 ); + BOOST_FOREACH(Mesh::Face_index f, faces(out)) + assert( out_id[f]==1 || out_id[f]==2); + + if (valid_union) + { + std::cout << "Union was successfully computed\n"; + std::ofstream output("union.off"); + output << out; + return 0; + } + + std::cout << "Union could not be computed\n"; + return 1; +} diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/corefinement.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/corefinement.h index c6f4c83e0fd..5c6a1e7a767 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/corefinement.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/corefinement.h @@ -374,26 +374,35 @@ boolean_operation( TriangleMesh& tm1, get_property_map(boost::face_index, tm1)); Fid_map fid_map2 = boost::choose_param(get_param(np2, internal_np::face_index), get_property_map(boost::face_index, tm2)); +// New face visitor + typedef typename boost::lookup_named_param_def < + internal_np::new_face_visitor_t, + NamedParameters1, + Corefinement::Default_face_visitor//default + > ::type Nfv; + Nfv nfv( boost::choose_param( get_param(np1, internal_np::new_face_visitor), + Corefinement::Default_face_visitor() ) ); + // surface intersection algorithm call typedef Corefinement::Default_node_visitor Dnv; - typedef Corefinement::Default_face_visitor Dfv; + typedef Corefinement::Face_graph_output_builder Ob; + Edge_mark_map_tuple, + Nfv> Ob; - typedef Corefinement::Visitor Visitor; + typedef Corefinement::Visitor Visitor; Dnv dnv; - Dfv dfv; Ecm_in ecm_in(tm1,tm2,ecm1,ecm2); Edge_mark_map_tuple ecms_out(ecm_out_0, ecm_out_1, ecm_out_2, ecm_out_3); Ob ob(tm1, tm2, vpm1, vpm2, fid_map1, fid_map2, ecm_in, - output_vpms, ecms_out, desired_output); + output_vpms, ecms_out, nfv, desired_output); Corefinement::Intersection_of_triangle_meshes - functor(tm1, tm2, vpm1, vpm2, Visitor(dnv,dfv,ob,ecm_in)); + functor(tm1, tm2, vpm1, vpm2, Visitor(dnv,nfv,ob,ecm_in)); functor(CGAL::Emptyset_iterator(), throw_on_self_intersection, true); @@ -658,17 +667,24 @@ corefine_and_compute_difference( TriangleMesh& tm1, return; } + // New face visitor + typedef typename boost::lookup_named_param_def < + internal_np::new_face_visitor_t, + NamedParameters1, + Corefinement::Default_face_visitor//default + > ::type Nfv; + Nfv nfv( boost::choose_param( get_param(np1, internal_np::new_face_visitor), + Corefinement::Default_face_visitor() ) ); + // surface intersection algorithm call typedef Corefinement::Default_node_visitor Dnv; - typedef Corefinement::Default_face_visitor Dfv; typedef Corefinement::No_extra_output_from_corefinement Ob; - typedef Corefinement::Visitor Visitor; + typedef Corefinement::Visitor Visitor; Dnv dnv; - Dfv dfv; Ob ob; Ecm ecm(tm1,tm2,ecm1,ecm2); Corefinement::Intersection_of_triangle_meshes - functor(tm1, tm2, vpm1, vpm2, Visitor(dnv,dfv,ob,ecm)); + functor(tm1, tm2, vpm1, vpm2, Visitor(dnv,nfv,ob,ecm)); functor(CGAL::Emptyset_iterator(), throw_on_self_intersection, true); } @@ -722,18 +738,26 @@ namespace experimental { Ecm ecm = boost::choose_param( get_param(np, internal_np::edge_is_constrained), Corefinement::No_mark() ); +// New face visitor + typedef typename boost::lookup_named_param_def < + internal_np::new_face_visitor_t, + NamedParameters, + Corefinement::Default_face_visitor//default + > ::type Nfv; + Nfv nfv( boost::choose_param( get_param(np, internal_np::new_face_visitor), + Corefinement::Default_face_visitor() ) ); + + // surface intersection algorithm call typedef Corefinement::Default_node_visitor Dnv; - typedef Corefinement::Default_face_visitor Dfv; typedef Corefinement::No_extra_output_from_corefinement Ob; typedef Default D; - typedef Corefinement::Visitor Visitor; + typedef Corefinement::Visitor Visitor; Dnv dnv; - Dfv dfv; Ob ob; Corefinement::Intersection_of_triangle_meshes - functor(tm, vpm, Visitor(dnv,dfv,ob,ecm) ); + functor(tm, vpm, Visitor(dnv,nfv,ob,ecm) ); functor(CGAL::Emptyset_iterator(), true); } @@ -789,10 +813,17 @@ namespace experimental { > ::type Ecm; Ecm ecm = boost::choose_param( get_param(np, internal_np::edge_is_constrained), Corefinement::No_mark() ); +// New face visitor + typedef typename boost::lookup_named_param_def < + internal_np::new_face_visitor_t, + NamedParameters, + Corefinement::Default_face_visitor//default + > ::type Nfv; + Nfv nfv( boost::choose_param( get_param(np, internal_np::new_face_visitor), + Corefinement::Default_face_visitor() ) ); // surface intersection algorithm call typedef Corefinement::Default_node_visitor Dnv; - typedef Corefinement::Default_face_visitor Dfv; typedef Corefinement::Output_builder_for_autorefinement Ob; typedef Default D; - typedef Corefinement::Visitor Visitor; + typedef Corefinement::Visitor Visitor; Dnv dnv; - Dfv dfv; Ob ob(tm, vpm, fid_map, ecm); Corefinement::Intersection_of_triangle_meshes - functor(tm, vpm, Visitor(dnv,dfv,ob,ecm) ); + functor(tm, vpm, Visitor(dnv,nfv,ob,ecm) ); functor(CGAL::Emptyset_iterator(), true); diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Corefinement/Face_graph_output_builder.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Corefinement/Face_graph_output_builder.h index b0c37510978..41d8192b7c1 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Corefinement/Face_graph_output_builder.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Corefinement/Face_graph_output_builder.h @@ -57,8 +57,9 @@ template + class EdgeMarkMapBind_ = Default, + class EdgeMarkMapTuple_ = Default, + class NewFaceVisitor_ = Default> class Face_graph_output_builder { //Default typedefs @@ -75,6 +76,8 @@ class Face_graph_output_builder No_mark, No_mark, No_mark > >::type EdgeMarkMapTuple; + typedef typename Default::Get< + NewFaceVisitor_, Default_face_visitor >::type NewFaceVisitor; // graph_traits typedefs typedef TriangleMesh TM; @@ -109,6 +112,7 @@ class Face_graph_output_builder // property maps of output meshes const cpp11::array& output_vpms; EdgeMarkMapTuple& out_edge_mark_maps; + NewFaceVisitor& new_face_visitor; // output meshes const cpp11::array, 4>& desired_output; // input meshes closed ? @@ -251,14 +255,14 @@ class Face_graph_output_builder template void mark_edges(const EdgeMarkMap& edge_mark_map, - const std::vector& edges) + const std::vector& edges) { BOOST_FOREACH(edge_descriptor ed, edges) put(edge_mark_map, ed, true); } void mark_edges(const No_mark&, - const std::vector&) + const std::vector&) {} //nothing to do template @@ -338,6 +342,7 @@ public: EdgeMarkMapBind& marks_on_input_edges, const cpp11::array& output_vpms, EdgeMarkMapTuple& out_edge_mark_maps, + NewFaceVisitor& new_face_visitor, const cpp11::array< boost::optional, 4 >& desired_output) : tm1(tm1), tm2(tm2) @@ -346,6 +351,7 @@ public: , marks_on_input_edges(marks_on_input_edges) , output_vpms(output_vpms) , out_edge_mark_maps(out_edge_mark_maps) + , new_face_visitor(new_face_visitor) , desired_output(desired_output) , is_tm1_closed( is_closed(tm1)) , is_tm2_closed( is_closed(tm2)) @@ -1233,7 +1239,8 @@ public: marks_on_input_edges.ecm1, marks_on_input_edges.ecm2, CGAL_COREF_SELECT_OUT_ECM(operation), - shared_edges + shared_edges, + new_face_visitor ); mark_edges(out_edge_mark_maps, shared_edges, operation); } @@ -1312,7 +1319,8 @@ public: marks_on_input_edges.ecm1, marks_on_input_edges.ecm2, CGAL_COREF_SELECT_OUT_ECM(inplace_operation_tm1), - disconnected_patches_edge_to_tm2_edge); + disconnected_patches_edge_to_tm2_edge, + new_face_visitor); // Operation in tm2: discard patches and append the one from tm2 CGAL_assertion( *desired_output[inplace_operation_tm2] == &tm2 ); compute_inplace_operation( tm2, tm1, @@ -1326,7 +1334,8 @@ public: marks_on_input_edges.ecm2, marks_on_input_edges.ecm1, CGAL_COREF_SELECT_OUT_ECM(inplace_operation_tm2), - disconnected_patches_edge_to_tm2_edge); + disconnected_patches_edge_to_tm2_edge, + new_face_visitor); // remove polylines only on the border of patches not kept in tm2 if (polylines_in_tm2.to_skip.any()) remove_unused_polylines(tm2, @@ -1374,7 +1383,8 @@ public: marks_on_input_edges.ecm1, marks_on_input_edges.ecm2, CGAL_COREF_SELECT_OUT_ECM(inplace_operation_tm1), - polylines + polylines, + new_face_visitor ); // remove polylines only on the border of patches not kept if (polylines.to_skip.any()) @@ -1413,7 +1423,8 @@ public: marks_on_input_edges.ecm2, marks_on_input_edges.ecm1, CGAL_COREF_SELECT_OUT_ECM(inplace_operation_tm2), - polylines); + polylines, + new_face_visitor); // remove polylines only on the border of patches not kept if (polylines.to_skip.any()) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Corefinement/Visitor.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Corefinement/Visitor.h index f6f4ebd128c..e54ad1c8f86 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Corefinement/Visitor.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Corefinement/Visitor.h @@ -67,15 +67,6 @@ struct Default_node_visitor{ TriangleMesh& /*tm*/){} }; -template -struct Default_face_visitor{ - typedef boost::graph_traits GT; - typedef typename GT::face_descriptor face_descriptor; - - void before_subface_creations(face_descriptor /*f_old*/,TriangleMesh&){} - void after_subface_created(face_descriptor /*f_new*/,TriangleMesh&){} -}; - //binds two edge constrained pmaps template struct Ecm_bind{ diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Corefinement/face_graph_utils.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Corefinement/face_graph_utils.h index b5bb6eaff81..e8fc486ca2c 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Corefinement/face_graph_utils.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Corefinement/face_graph_utils.h @@ -152,6 +152,20 @@ void copy_edge_mark(G&, No_mark&) {} // nothing to do +template +struct Default_face_visitor{ + typedef boost::graph_traits GT; + typedef typename GT::face_descriptor face_descriptor; + + void before_subface_creations(face_descriptor /*f_old*/,TriangleMesh&){} + void after_subface_creations(TriangleMesh&){} + void before_subface_created(TriangleMesh&){} + void after_subface_created(face_descriptor /*f_new*/,TriangleMesh&){} + void before_face_copy(face_descriptor /*f_old*/, TriangleMesh&, TriangleMesh&){} + void after_face_copy(face_descriptor /*f_old*/, TriangleMesh&, + face_descriptor /* f_new */, TriangleMesh&){} +}; + template < class TriangleMesh, class VertexPointMap, class Node_id, @@ -262,12 +276,14 @@ triangulate_a_face( if ( ++it!=it_end ) { + new_face_visitor.before_subface_created(tm); current_face=add_face(tm); - new_face_visitor.before_subface_creations(current_face,tm); + new_face_visitor.after_subface_created(current_face,tm); } else break; } + new_face_visitor.after_subface_creations(tm); } template @@ -697,7 +713,8 @@ template < bool reverse_patch_orientation, class PatchContainer, class VertexPointMap, class EdgeMarkMapOut, - class EdgeMarkMapIn > + class EdgeMarkMapIn , + class NewFaceVisitor> void append_patches_to_triangle_mesh( TriangleMesh& output, const boost::dynamic_bitset<>& patches_to_append, @@ -709,7 +726,8 @@ void append_patches_to_triangle_mesh( boost::unordered_map< typename boost::graph_traits::edge_descriptor, typename boost::graph_traits::edge_descriptor - >& tm_to_output_edges) + >& tm_to_output_edges, + NewFaceVisitor& new_face_visitor) { typedef boost::graph_traits GT; typedef typename GT::halfedge_descriptor halfedge_descriptor; @@ -781,7 +799,9 @@ void append_patches_to_triangle_mesh( { cpp11::array hedges = helper.halfedges(f); + new_face_visitor.before_face_copy(f, patches.pm, output); face_descriptor new_f = add_face(output); + new_face_visitor.after_face_copy(f, patches.pm, new_f, output); set_halfedge(new_f, hedges[0], output); for (int i=0;i<3;++i) @@ -903,7 +923,8 @@ template < class TriangleMesh, class EdgeMarkMap2, class EdgeMarkMapOut, class IntersectionPolylines, - class PatchContainer> + class PatchContainer, + class NewFaceVisitor> void fill_new_triangle_mesh( TriangleMesh& output, const boost::dynamic_bitset<>& patches_of_tm1_to_import, @@ -922,7 +943,8 @@ void fill_new_triangle_mesh( const EdgeMarkMap2& edge_mark_map2, EdgeMarkMapOut& edge_mark_map_out, std::vector< typename boost::graph_traits::edge_descriptor>& - output_shared_edges) + output_shared_edges, + NewFaceVisitor& new_face_visitor) { typedef boost::graph_traits GT; typedef typename GT::vertex_descriptor vertex_descriptor; @@ -961,7 +983,8 @@ void fill_new_triangle_mesh( vpm1, edge_mark_map_out, edge_mark_map1, - tm1_to_output_edges); + tm1_to_output_edges, + new_face_visitor); else append_patches_to_triangle_mesh(output, patches_of_tm1_to_import, @@ -970,7 +993,8 @@ void fill_new_triangle_mesh( vpm1, edge_mark_map_out, edge_mark_map1, - tm1_to_output_edges); + tm1_to_output_edges, + new_face_visitor); //import patches from tm2 if (reverse_orientation_of_patches_from_tm2) @@ -981,7 +1005,8 @@ void fill_new_triangle_mesh( vpm2, edge_mark_map_out, edge_mark_map2, - tm2_to_output_edges); + tm2_to_output_edges, + new_face_visitor); else append_patches_to_triangle_mesh(output, patches_of_tm2_to_import, @@ -990,7 +1015,8 @@ void fill_new_triangle_mesh( vpm2, edge_mark_map_out, edge_mark_map2, - tm2_to_output_edges); + tm2_to_output_edges, + new_face_visitor); } template + class EdgeMarkMapOut, + class NewFaceVisitor> void compute_inplace_operation_delay_removal_and_insideout( TriangleMesh& tm1, TriangleMesh& tm2, @@ -1164,7 +1191,8 @@ void compute_inplace_operation_delay_removal_and_insideout( EdgeMarkMapIn1&, const EdgeMarkMapIn2& edge_mark_map2, const EdgeMarkMapOut& edge_mark_map_out1, - EdgeMap& disconnected_patches_edge_to_tm2_edge) + EdgeMap& disconnected_patches_edge_to_tm2_edge, + NewFaceVisitor& new_face_visitor) { typedef boost::graph_traits GT; typedef typename GT::edge_descriptor edge_descriptor; @@ -1214,7 +1242,8 @@ void compute_inplace_operation_delay_removal_and_insideout( vpm2, edge_mark_map_out1, edge_mark_map2, - tm2_edge_to_tm1_edge); + tm2_edge_to_tm1_edge, + new_face_visitor); else append_patches_to_triangle_mesh(tm1, patches_of_tm2_to_import, @@ -1223,7 +1252,8 @@ void compute_inplace_operation_delay_removal_and_insideout( vpm2, edge_mark_map_out1, edge_mark_map2, - tm2_edge_to_tm1_edge); + tm2_edge_to_tm1_edge, + new_face_visitor); } template + class EdgeMarkMapOut1, + class NewFaceVisitor> void compute_inplace_operation( TriangleMesh& tm1, const TriangleMesh& /*tm2*/, @@ -1321,7 +1352,8 @@ void compute_inplace_operation( boost::unordered_map< typename boost::graph_traits::edge_descriptor, typename boost::graph_traits::edge_descriptor - >& tm2_edge_to_tm1_edge) + >& tm2_edge_to_tm1_edge, + NewFaceVisitor& new_face_visitor) { typedef boost::unordered_map< typename boost::graph_traits::edge_descriptor, @@ -1350,7 +1382,8 @@ void compute_inplace_operation( vpm2, edge_mark_map_out1, edge_mark_map_in2, - tm2_edge_to_tm1_edge); + tm2_edge_to_tm1_edge, + new_face_visitor); else append_patches_to_triangle_mesh(tm1, patches_of_tm2_to_import, @@ -1359,7 +1392,8 @@ void compute_inplace_operation( vpm2, edge_mark_map_out1, edge_mark_map_in2, - tm2_edge_to_tm1_edge); + tm2_edge_to_tm1_edge, + new_face_visitor); } template + class EdgeMarkMapOut1, + class NewFaceVisitor> void compute_inplace_operation( TriangleMesh& tm1, const TriangleMesh& tm2, @@ -1419,7 +1454,8 @@ void compute_inplace_operation( const EdgeMarkMapIn1& edge_mark_map_in1, const EdgeMarkMapIn2& edge_mark_map_in2, const EdgeMarkMapOut1& edge_mark_map_out1, - const IntersectionPolylines& polylines) + const IntersectionPolylines& polylines, + NewFaceVisitor& new_face_visitor) { typedef boost::graph_traits GT; typedef typename GT::edge_descriptor edge_descriptor; @@ -1442,7 +1478,8 @@ void compute_inplace_operation( edge_mark_map_in1, edge_mark_map_in2, edge_mark_map_out1, - tm2_edge_to_tm1_edge); + tm2_edge_to_tm1_edge, + new_face_visitor); } // function used to remove polylines imported or kept that are incident only From 699a43e47f0a605718df9ad9dd8e36d2ffb19de9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Loriot?= Date: Thu, 24 May 2018 10:24:14 +0200 Subject: [PATCH 02/43] document the new named parameter --- .../Polygon_mesh_processing/NamedParameters.txt | 9 +++++++++ .../CGAL/Polygon_mesh_processing/corefinement.h | 14 +++++++++++++- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/Polygon_mesh_processing/doc/Polygon_mesh_processing/NamedParameters.txt b/Polygon_mesh_processing/doc/Polygon_mesh_processing/NamedParameters.txt index d9b8e95c9ac..34f1acc6670 100644 --- a/Polygon_mesh_processing/doc/Polygon_mesh_processing/NamedParameters.txt +++ b/Polygon_mesh_processing/doc/Polygon_mesh_processing/NamedParameters.txt @@ -310,6 +310,15 @@ of close meshes should be done in addition to surface intersection tests. \b Default value is `false` \cgalNPEnd +\cgalNPBegin{new_face_visitor} \anchor PMP_new_face_visitor +A class model of `PMPCorefinementNewFaceVisitor` that is used in corefinement related functions +to track the creation of new faces. +\n +\b Type : `A class` \n +\b Default value is a model of `PMPCorefinementNewFaceVisitor` that is doing nothing +\cgalNPEnd + + \cgalNPTableEnd */ diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/corefinement.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/corefinement.h index 5c6a1e7a767..18207a2c6b3 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/corefinement.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/corefinement.h @@ -471,6 +471,9 @@ boolean_operation( TriangleMesh& tm1, * Note that if the property map is writable, the indices of the faces * of `tm1` and `tm2` will be set after the corefinement is done. * \cgalParamEnd + * \cgalParamBegin{new_face_visitor} a class model of `PMPCorefinementNewFaceVisitor` + * that is used to track the creation of new faces (`np1` only) + * \cgalParamEnd * \cgalNamedParamsEnd * * @param np_out optional sequence of \ref pmp_namedparameters "Named Parameters" among the ones listed below @@ -611,6 +614,9 @@ corefine_and_compute_difference( TriangleMesh& tm1, * \cgalParamBegin{edge_is_constrained_map} a property map containing the * constrained-or-not status of each edge of `tm1` (`tm2`) * \cgalParamEnd + * \cgalParamBegin{new_face_visitor} a class model of `PMPCorefinementNewFaceVisitor` + * that is used to track the creation of new faces (`np1` only) + * \cgalParamEnd * \cgalNamedParamsEnd * */ @@ -711,6 +717,9 @@ namespace experimental { * \cgalParamBegin{edge_is_constrained_map} a property map containing the * constrained-or-not status of each edge of `tm` * \cgalParamEnd + * \cgalParamBegin{new_face_visitor} a class model of `PMPCorefinementNewFaceVisitor` + * that is used to track the creation of new faces + * \cgalParamEnd * \cgalNamedParamsEnd * */ @@ -785,7 +794,10 @@ namespace experimental { * \cgalParamBegin{edge_is_constrained_map} a property map containing the * constrained-or-not status of each edge of `tm` * \cgalParamEnd -* \cgalParamBegin{face_index_map} a property map containing the index of each face of `tm` \cgalParamEnd + * \cgalParamBegin{face_index_map} a property map containing the index of each face of `tm` \cgalParamEnd + * \cgalParamBegin{new_face_visitor} a class model of `PMPCorefinementNewFaceVisitor` + * that is used to track the creation of new faces + * \cgalParamEnd * \cgalNamedParamsEnd * */ From 20939d8a9ab1dc29d0b1d51da1e506ed73be4be9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Loriot?= Date: Thu, 24 May 2018 12:01:38 +0200 Subject: [PATCH 03/43] test new face visitor in corefine and autorefine --- .../test_autorefinement.cpp | 23 ++++++++++++++- .../Polygon_mesh_processing/test_corefine.cpp | 28 +++++++++++++++++-- 2 files changed, 48 insertions(+), 3 deletions(-) diff --git a/Polygon_mesh_processing/test/Polygon_mesh_processing/test_autorefinement.cpp b/Polygon_mesh_processing/test/Polygon_mesh_processing/test_autorefinement.cpp index 155226be9e1..d67ddfcb71a 100644 --- a/Polygon_mesh_processing/test/Polygon_mesh_processing/test_autorefinement.cpp +++ b/Polygon_mesh_processing/test/Polygon_mesh_processing/test_autorefinement.cpp @@ -12,6 +12,24 @@ typedef CGAL::Surface_mesh Mesh; namespace PMP = CGAL::Polygon_mesh_processing; +template +struct My_new_face_visitor +{ + typedef boost::graph_traits GT; + typedef typename GT::face_descriptor face_descriptor; + + void before_subface_creations(face_descriptor /*f_old*/,TriangleMesh&){} + void after_subface_creations(TriangleMesh&){++(*i);} + void before_subface_created(TriangleMesh&){} + void after_subface_created(face_descriptor /*f_new*/,TriangleMesh&){} + + My_new_face_visitor() + : i (new int(0) ) + {} + + boost::shared_ptr i; +}; + void test(const char* fname, std::size_t nb_polylines, std::size_t total_nb_points, std::size_t nb_vertices_after_autorefine, bool all_fixed, std::size_t nb_vertices_after_fix) { @@ -36,8 +54,11 @@ void test(const char* fname, std::size_t nb_polylines, std::size_t total_nb_poin assert(total_nb_points == total_nb_pt); // Testing autorefine() - PMP::experimental::autorefine(mesh); + My_new_face_visitor visitor; + PMP::experimental::autorefine(mesh, + PMP::parameters::new_face_visitor(visitor)); assert( nb_vertices_after_autorefine==num_vertices(mesh)); + assert( *(visitor.i) != 0); // Testing autorefine_and_remove_self_intersections() input.open(fname); diff --git a/Polygon_mesh_processing/test/Polygon_mesh_processing/test_corefine.cpp b/Polygon_mesh_processing/test/Polygon_mesh_processing/test_corefine.cpp index 4aee863f3a6..19c635c38e5 100644 --- a/Polygon_mesh_processing/test/Polygon_mesh_processing/test_corefine.cpp +++ b/Polygon_mesh_processing/test/Polygon_mesh_processing/test_corefine.cpp @@ -10,6 +10,23 @@ typedef CGAL::Exact_predicates_inexact_constructions_kernel K; typedef CGAL::Surface_mesh Surface_mesh; typedef CGAL::Polyhedron_3 Polyhedron_3; +template +struct My_new_face_visitor +{ + typedef boost::graph_traits GT; + typedef typename GT::face_descriptor face_descriptor; + + void before_subface_creations(face_descriptor /*f_old*/,TriangleMesh&){} + void after_subface_creations(TriangleMesh&){++(*i);} + void before_subface_created(TriangleMesh&){} + void after_subface_created(face_descriptor /*f_new*/,TriangleMesh&){} + + My_new_face_visitor() + : i (new int(0) ) + {} + + boost::shared_ptr i; +}; void test(const char* f1, const char* f2) { @@ -26,11 +43,14 @@ void test(const char* f1, const char* f2) assert(input); input >> sm2; input.close(); + My_new_face_visitor sm_v; - CGAL::Polygon_mesh_processing::corefine(sm1, sm2); + CGAL::Polygon_mesh_processing::corefine(sm1, sm2, + CGAL::Polygon_mesh_processing::parameters::new_face_visitor(sm_v)); assert(sm1.is_valid()); assert(sm2.is_valid()); + assert(*(sm_v.i) != 0); std::cout << " with Polyhedron_3\n"; Polyhedron_3 P, Q; @@ -41,9 +61,13 @@ void test(const char* f1, const char* f2) input.open(f2); assert(input); input >> Q; + My_new_face_visitor sm_p; - CGAL::Polygon_mesh_processing::corefine(P, Q); + CGAL::Polygon_mesh_processing::corefine(P, Q, + CGAL::Polygon_mesh_processing::parameters::new_face_visitor(sm_p)); + assert(*(sm_p.i) != 0); + assert(*(sm_v.i) == *(sm_p.i)); assert(P.is_valid()); assert(Q.is_valid()); } From da92f566dee3c1440c50db7903465677cd6639ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Loriot?= Date: Thu, 24 May 2018 12:09:39 +0200 Subject: [PATCH 04/43] update changes --- Installation/CHANGES.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Installation/CHANGES.md b/Installation/CHANGES.md index 78fc891eea6..aa819b19c1a 100644 --- a/Installation/CHANGES.md +++ b/Installation/CHANGES.md @@ -35,6 +35,9 @@ Release date: September 2018 ### Polygon Mesh Processing - Added a function to apply a transformation to a mesh : - `CGAL::Polygon_mesh_processing::transform()` +- Add in corefinement related functions a new named parameter `new_face_visitor` + that make it possible to pass a visitor to the function in order to track + the creation of new faces. Release 4.12 ------------ From c554738ea191d90ddab3c5263823b3ef6b47bed7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Loriot?= Date: Thu, 24 May 2018 13:37:30 +0200 Subject: [PATCH 05/43] update after Mael's review --- Installation/CHANGES.md | 2 +- .../Concepts/PMPCorefinementNewFaceVisitor.h | 18 +++++++++--------- .../NamedParameters.txt | 2 +- .../PackageDescription.txt | 1 - .../Polygon_mesh_processing/corefinement.h | 1 + 5 files changed, 12 insertions(+), 12 deletions(-) diff --git a/Installation/CHANGES.md b/Installation/CHANGES.md index aa819b19c1a..f6608194fe1 100644 --- a/Installation/CHANGES.md +++ b/Installation/CHANGES.md @@ -35,7 +35,7 @@ Release date: September 2018 ### Polygon Mesh Processing - Added a function to apply a transformation to a mesh : - `CGAL::Polygon_mesh_processing::transform()` -- Add in corefinement related functions a new named parameter `new_face_visitor` +- Add in corefinement-related functions a new named parameter `new_face_visitor` that make it possible to pass a visitor to the function in order to track the creation of new faces. diff --git a/Polygon_mesh_processing/doc/Polygon_mesh_processing/Concepts/PMPCorefinementNewFaceVisitor.h b/Polygon_mesh_processing/doc/Polygon_mesh_processing/Concepts/PMPCorefinementNewFaceVisitor.h index 04e77634c40..47febe3c1ca 100644 --- a/Polygon_mesh_processing/doc/Polygon_mesh_processing/Concepts/PMPCorefinementNewFaceVisitor.h +++ b/Polygon_mesh_processing/doc/Polygon_mesh_processing/Concepts/PMPCorefinementNewFaceVisitor.h @@ -2,7 +2,7 @@ /// \cgalConcept /// /// The concept `PMPCorefinementNewFaceVisitor` defines the requirements for the visitor -/// used in \link PMP_corefinement_grp corefinement related functions \endlink to track +/// used in \link PMP_corefinement_grp corefinement-related functions \endlink to track /// the creation of new faces. /// /// \cgalRefines `CopyConstructible` @@ -23,21 +23,21 @@ typedef unspecified_type face_descriptor; /// the creation of a new face of triangulating `f_split`. void before_subface_creations(face_descriptor f_split, Triangle_mesh& tm); /// called when the triangulation of a face in `tm` is finished - void after_subface_creations(Mesh& tm); + void after_subface_creations(Triangle_mesh& tm); /// called before creating a new triangle face in `tm` to triangulate the face passed to `before_subface_creations()` - void before_subface_created(Mesh& tm); + void before_subface_created(Triangle_mesh& tm); /// called after creating a new triangle face `f_new` in `tm` to triangulate the face passed to `before_subface_creations()`. - /// Note that the call is placed just after a call to `add_face()` so the halfedge pointer is set yet. + /// Note that the call is placed just after a call to `add_face()` so the halfedge pointer is not set yet. void after_subface_created(face_descriptor f_new, Triangle_mesh& tm); /// @} -/// @name Functions used by functions computing Boolean operations using corefinement -/// These functions are not needed is you only call `corefine()`. +/// @name Functions used by functions computing Boolean operations using corefinement. +/// These functions are not needed if you only call `corefine()`. /// @{ - /// called before importing face `f_src` of `tm_src` in `tm_tgt` + /// called before importing the face `f_src` of `tm_src` in `tm_tgt` void before_face_copy(face_descriptor f_src, Triangle_mesh& tm_src, Triangle_mesh& tm_tgt); - /// called after importing face `f_src` of `tm_src` in `tm_tgt`. The new face if `f_tgt`. - /// Note that the call is placed just after a call to `add_face()` so the halfedge pointer is set yet. + /// called after importing the face `f_src` of `tm_src` in `tm_tgt`. The new face is `f_tgt`. + /// Note that the call is placed just after a call to `add_face()` so the halfedge pointer is not set yet. void after_face_copy(face_descriptor f_src, Triangle_mesh& tm_src, face_descriptor f_tgt, Triangle_mesh& tm_tgt); /// @} diff --git a/Polygon_mesh_processing/doc/Polygon_mesh_processing/NamedParameters.txt b/Polygon_mesh_processing/doc/Polygon_mesh_processing/NamedParameters.txt index 34f1acc6670..55485dfdd4f 100644 --- a/Polygon_mesh_processing/doc/Polygon_mesh_processing/NamedParameters.txt +++ b/Polygon_mesh_processing/doc/Polygon_mesh_processing/NamedParameters.txt @@ -311,7 +311,7 @@ of close meshes should be done in addition to surface intersection tests. \cgalNPEnd \cgalNPBegin{new_face_visitor} \anchor PMP_new_face_visitor -A class model of `PMPCorefinementNewFaceVisitor` that is used in corefinement related functions +A class model of `PMPCorefinementNewFaceVisitor` that is used in corefinement-related functions to track the creation of new faces. \n \b Type : `A class` \n diff --git a/Polygon_mesh_processing/doc/Polygon_mesh_processing/PackageDescription.txt b/Polygon_mesh_processing/doc/Polygon_mesh_processing/PackageDescription.txt index d9118621645..80e65018910 100644 --- a/Polygon_mesh_processing/doc/Polygon_mesh_processing/PackageDescription.txt +++ b/Polygon_mesh_processing/doc/Polygon_mesh_processing/PackageDescription.txt @@ -148,7 +148,6 @@ and provides a list of the parameters that are used in this package. - `CGAL::Polygon_mesh_processing::corefine()` - `CGAL::Polygon_mesh_processing::surface_intersection()` - `CGAL::Polygon_mesh_processing::does_bound_a_volume()` -- `PMPCorefinementNewFaceVisitor` ## Geometric Measure Functions ## - \link measure_grp `CGAL::Polygon_mesh_processing::face_area()` \endlink diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/corefinement.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/corefinement.h index 18207a2c6b3..c10cc3a1a12 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/corefinement.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/corefinement.h @@ -605,6 +605,7 @@ corefine_and_compute_difference( TriangleMesh& tm1, * the set of triangles closed to the intersection of `tm1` and `tm2` will be * checked for self-intersection and `CGAL::Corefinement::Self_intersection_exception` * will be thrown if at least one is found. + * * \cgalNamedParamsBegin * \cgalParamBegin{vertex_point_map} * the property map with the points associated to the vertices of `tm1` (`tm2`). From 3c382acccaef27d99136b3df3c6c7ac445754d44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Loriot?= Date: Thu, 24 May 2018 14:39:04 +0200 Subject: [PATCH 06/43] document the default new face visitor I have to move everything in the CGAL::PMP::Corefinement because CGAL::Corefinement was strange for a publicly documented type --- .../Concepts/PMPCorefinementNewFaceVisitor.h | 1 + .../Polygon_mesh_processing/corefinement.h | 40 ++++++++++++++----- .../Corefinement/Face_graph_output_builder.h | 5 ++- .../internal/Corefinement/Intersection_type.h | 3 +- .../Output_builder_for_autorefinement.h | 3 +- .../internal/Corefinement/Visitor.h | 5 ++- .../internal/Corefinement/face_graph_utils.h | 5 ++- .../intersect_triangle_and_segment_3.h | 3 +- .../Corefinement/intersection_callbacks.h | 3 +- .../internal/Corefinement/intersection_impl.h | 3 +- .../Corefinement/intersection_nodes.h | 3 +- .../intersection_of_coplanar_triangles_3.h | 3 +- .../internal/Corefinement/predicates.h | 3 +- .../Polygon_mesh_processing/intersection.h | 2 +- .../test_corefinement_bool_op.cpp | 2 +- .../PMP/Surface_intersection_plugin.cpp | 2 +- 16 files changed, 59 insertions(+), 27 deletions(-) diff --git a/Polygon_mesh_processing/doc/Polygon_mesh_processing/Concepts/PMPCorefinementNewFaceVisitor.h b/Polygon_mesh_processing/doc/Polygon_mesh_processing/Concepts/PMPCorefinementNewFaceVisitor.h index 47febe3c1ca..36205aa9e44 100644 --- a/Polygon_mesh_processing/doc/Polygon_mesh_processing/Concepts/PMPCorefinementNewFaceVisitor.h +++ b/Polygon_mesh_processing/doc/Polygon_mesh_processing/Concepts/PMPCorefinementNewFaceVisitor.h @@ -6,6 +6,7 @@ /// the creation of new faces. /// /// \cgalRefines `CopyConstructible` +/// \cgalHasModel `CGAL::Polygon_mesh_processing::Corefinement::Default_new_face_visitor`. class PMPCorefinementNewFaceVisitor{ diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/corefinement.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/corefinement.h index c10cc3a1a12..24abae07b8f 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/corefinement.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/corefinement.h @@ -34,6 +34,13 @@ #include namespace CGAL { + +#if !defined(CGAL_NO_DEPRECATED_CODE) && !defined(DOXYGEN_RUNNING) +namespace Corefinement { +using Polygon_mesh_processing::Corefinement::Self_intersection_exception; +} +#endif + namespace Polygon_mesh_processing { namespace internal { @@ -125,6 +132,19 @@ bool recursive_does_bound_a_volume(const TriangleMesh& tm, } //end of namespace internal +namespace Corefinement +{ +/** \ingroup PMP_corefinement_grp + * Default new-face visitor model of `PMPCorefinementNewFaceVisitor`. + * All of its functions have an empty body. This class can be used as a + * base class if only some of the functions of the concept require to be + * overridden. + */ +template +struct Default_new_face_visitor; +} + + /** \ingroup PMP_corefinement_grp * * indicates if `tm` bounds a volume. @@ -378,10 +398,10 @@ boolean_operation( TriangleMesh& tm1, typedef typename boost::lookup_named_param_def < internal_np::new_face_visitor_t, NamedParameters1, - Corefinement::Default_face_visitor//default + Corefinement::Default_new_face_visitor//default > ::type Nfv; Nfv nfv( boost::choose_param( get_param(np1, internal_np::new_face_visitor), - Corefinement::Default_face_visitor() ) ); + Corefinement::Default_new_face_visitor() ) ); // surface intersection algorithm call typedef Corefinement::Default_node_visitor Dnv; @@ -571,7 +591,7 @@ corefine_and_compute_difference( TriangleMesh& tm1, const NamedParametersOut& np_out) { using namespace CGAL::Polygon_mesh_processing::parameters; - using namespace CGAL::Corefinement; + using namespace CGAL::Polygon_mesh_processing::Corefinement; cpp11::array< boost::optional,4> desired_output; desired_output[TM1_MINUS_TM2]=&tm_out; @@ -603,7 +623,7 @@ corefine_and_compute_difference( TriangleMesh& tm1, * @param np2 optional sequence of \ref pmp_namedparameters "Named Parameters" among the ones listed below * @param throw_on_self_intersection if `true`, for each input triangle mesh, * the set of triangles closed to the intersection of `tm1` and `tm2` will be - * checked for self-intersection and `CGAL::Corefinement::Self_intersection_exception` + * checked for self-intersection and `CGAL::Polygon_mesh_processing::Corefinement::Self_intersection_exception` * will be thrown if at least one is found. * * \cgalNamedParamsBegin @@ -678,10 +698,10 @@ corefine_and_compute_difference( TriangleMesh& tm1, typedef typename boost::lookup_named_param_def < internal_np::new_face_visitor_t, NamedParameters1, - Corefinement::Default_face_visitor//default + Corefinement::Default_new_face_visitor//default > ::type Nfv; Nfv nfv( boost::choose_param( get_param(np1, internal_np::new_face_visitor), - Corefinement::Default_face_visitor() ) ); + Corefinement::Default_new_face_visitor() ) ); // surface intersection algorithm call typedef Corefinement::Default_node_visitor Dnv; @@ -752,10 +772,10 @@ namespace experimental { typedef typename boost::lookup_named_param_def < internal_np::new_face_visitor_t, NamedParameters, - Corefinement::Default_face_visitor//default + Corefinement::Default_new_face_visitor//default > ::type Nfv; Nfv nfv( boost::choose_param( get_param(np, internal_np::new_face_visitor), - Corefinement::Default_face_visitor() ) ); + Corefinement::Default_new_face_visitor() ) ); // surface intersection algorithm call @@ -830,10 +850,10 @@ namespace experimental { typedef typename boost::lookup_named_param_def < internal_np::new_face_visitor_t, NamedParameters, - Corefinement::Default_face_visitor//default + Corefinement::Default_new_face_visitor//default > ::type Nfv; Nfv nfv( boost::choose_param( get_param(np, internal_np::new_face_visitor), - Corefinement::Default_face_visitor() ) ); + Corefinement::Default_new_face_visitor() ) ); // surface intersection algorithm call typedef Corefinement::Default_node_visitor Dnv; diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Corefinement/Face_graph_output_builder.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Corefinement/Face_graph_output_builder.h index 41d8192b7c1..448a147cabb 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Corefinement/Face_graph_output_builder.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Corefinement/Face_graph_output_builder.h @@ -44,6 +44,7 @@ : cpp11::get<3>(out_edge_mark_maps)) namespace CGAL { +namespace Polygon_mesh_processing { namespace Corefinement { enum Boolean_operation {UNION = 0, INTER, @@ -77,7 +78,7 @@ class Face_graph_output_builder No_mark, No_mark > >::type EdgeMarkMapTuple; typedef typename Default::Get< - NewFaceVisitor_, Default_face_visitor >::type NewFaceVisitor; + NewFaceVisitor_, Default_new_face_visitor >::type NewFaceVisitor; // graph_traits typedefs typedef TriangleMesh TM; @@ -1436,7 +1437,7 @@ public: }; -} } // CGAL::Corefinement +} } } // CGAL::Polygon_mesh_processing::Corefinement #undef CGAL_COREF_SELECT_OUT_ECM diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Corefinement/Intersection_type.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Corefinement/Intersection_type.h index 280aadefbb4..79beefbdaa5 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Corefinement/Intersection_type.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Corefinement/Intersection_type.h @@ -29,6 +29,7 @@ #include namespace CGAL{ +namespace Polygon_mesh_processing { namespace Corefinement{ enum Intersection_type {ON_FACE,ON_EDGE,ON_VERTEX,EMPTY,COPLANAR_TRIANGLES}; @@ -50,6 +51,6 @@ struct Coplanar_intersection{ }; -} } // CGAL::Corefinement +} } } // CGAL::Polygon_mesh_processing::Corefinement #endif // CGAL_PMP_INTERNAL_COREFINEMENT_INTERSECTION_TYPE_H diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Corefinement/Output_builder_for_autorefinement.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Corefinement/Output_builder_for_autorefinement.h index 5c72f1c5fbe..ba32b8948f2 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Corefinement/Output_builder_for_autorefinement.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Corefinement/Output_builder_for_autorefinement.h @@ -40,6 +40,7 @@ #include namespace CGAL { +namespace Polygon_mesh_processing { namespace Corefinement { namespace PMP=Polygon_mesh_processing; @@ -1012,7 +1013,7 @@ public: }; -} } // CGAL::Corefinement +} } } // CGAL::Corefinement #include diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Corefinement/Visitor.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Corefinement/Visitor.h index e54ad1c8f86..44c765fe5ba 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Corefinement/Visitor.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Corefinement/Visitor.h @@ -36,6 +36,7 @@ #include namespace CGAL{ +namespace Polygon_mesh_processing { namespace Corefinement{ // TODO option to ignore internal edges for patches of coplanar faces @@ -150,7 +151,7 @@ class Visitor{ typedef typename Default::Get< NewNodeVisitor_, Default_node_visitor >::type NewNodeVisitor; typedef typename Default::Get< - NewFaceVisitor_, Default_face_visitor >::type NewFaceVisitor; + NewFaceVisitor_, Default_new_face_visitor >::type NewFaceVisitor; // config flags public: @@ -1071,7 +1072,7 @@ public: } }; -} } //end of namespace CGAL::Corefinement +} } } // CGAL::Polygon_mesh_processing::Corefinement #include diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Corefinement/face_graph_utils.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Corefinement/face_graph_utils.h index e8fc486ca2c..7c500fca27c 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Corefinement/face_graph_utils.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Corefinement/face_graph_utils.h @@ -33,6 +33,7 @@ #include #include namespace CGAL { +namespace Polygon_mesh_processing { namespace Corefinement { template @@ -153,7 +154,7 @@ void copy_edge_mark(G&, {} // nothing to do template -struct Default_face_visitor{ +struct Default_new_face_visitor{ typedef boost::graph_traits GT; typedef typename GT::face_descriptor face_descriptor; @@ -1576,6 +1577,6 @@ void remove_disconnected_patches( } } -} } // end of namespace CGAL::Corefinement +} } } // CGAL::Polygon_mesh_processing::Corefinement #endif // CGAL_POLYGON_MESH_PROCESSING_INTERNAL_FACE_GRAPH_UTILS_H diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Corefinement/intersect_triangle_and_segment_3.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Corefinement/intersect_triangle_and_segment_3.h index 9aba776fc47..0bde009ee57 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Corefinement/intersect_triangle_and_segment_3.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Corefinement/intersect_triangle_and_segment_3.h @@ -31,6 +31,7 @@ #include namespace CGAL{ +namespace Polygon_mesh_processing { namespace Corefinement{ @@ -178,6 +179,6 @@ intersection_type( } } -}} //namespace CGAL::internal_IOP +} } } // CGAL::Polygon_mesh_processing::Corefinement #endif //CGAL_INTERNAL_PMP_INTERSECT_TRIANGLE_AND_SEGMENT_3_H diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Corefinement/intersection_callbacks.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Corefinement/intersection_callbacks.h index ffc4e12b274..87453fe3da3 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Corefinement/intersection_callbacks.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Corefinement/intersection_callbacks.h @@ -34,6 +34,7 @@ #include namespace CGAL { +namespace Polygon_mesh_processing { namespace Corefinement { template @@ -344,6 +345,6 @@ public: } }; -} } // end of namespace CGAL::Corefinement +} } } // CGAL::Polygon_mesh_processing::Corefinement #endif // CGAL_POLYGON_MESH_PROCESSING_INTERNAL_COREFINEMENT_INTERSECTION_CALLBACK_H diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Corefinement/intersection_impl.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Corefinement/intersection_impl.h index 741ffc2ae00..d66665c4a1b 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Corefinement/intersection_impl.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Corefinement/intersection_impl.h @@ -41,6 +41,7 @@ #include namespace CGAL{ +namespace Polygon_mesh_processing { namespace Corefinement { struct Self_intersection_exception{}; @@ -1364,6 +1365,6 @@ public: }; -} } // end of namespace CGAL::Corefinement +} } } // CGAL::Polygon_mesh_processing::Corefinement #endif //CGAL_POLYGON_MESH_PROCESSING_INTERNAL_COREFINEMENT_INTERSECTION_IMPL_H diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Corefinement/intersection_nodes.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Corefinement/intersection_nodes.h index 85285e265b9..dae07420937 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Corefinement/intersection_nodes.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Corefinement/intersection_nodes.h @@ -30,6 +30,7 @@ #include namespace CGAL { +namespace Polygon_mesh_processing { namespace Corefinement { // A class responsible for storing the intersection nodes of the intersection @@ -418,6 +419,6 @@ public: }; // end specialization -} } // end of namespace CGAL::Corefinement +} } } // CGAL::Polygon_mesh_processing::Corefinement #endif // CGAL_POLYGON_MESH_PROCESSING_INTERNAL_COREFINEMENT_INTERSECTION_NODES_H diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Corefinement/intersection_of_coplanar_triangles_3.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Corefinement/intersection_of_coplanar_triangles_3.h index 9c4fe4d3eb3..560a5b0cff4 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Corefinement/intersection_of_coplanar_triangles_3.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Corefinement/intersection_of_coplanar_triangles_3.h @@ -33,6 +33,7 @@ #include namespace CGAL{ +namespace Polygon_mesh_processing { namespace Corefinement{ template @@ -320,7 +321,7 @@ void intersection_coplanar_faces( intersect_cpln.cutoff_face(next(next(h2,tm2),tm2),inter_pts,h1); } -} } //namespace CGAL::Corefinement +} } } // CGAL::Polygon_mesh_processing::Corefinement #endif //CGAL_PMP_INTERNAL_COREFINEMENT_INTERSECTION_OF_COPLANAR_TRIANGLES_3_H diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Corefinement/predicates.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Corefinement/predicates.h index d427dbcbf4a..94d83d56857 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Corefinement/predicates.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Corefinement/predicates.h @@ -29,6 +29,7 @@ #include namespace CGAL { +namespace Polygon_mesh_processing { namespace Corefinement { @@ -176,6 +177,6 @@ bool sorted_around_edge( Node_id o_prime_index, } -} } // end of namespace CGAL::Corefinement +} } } // CGAL::Polygon_mesh_processing::Corefinement #endif // CGAL_POLYGON_MESH_PROCESSING_INTERNAL_COREFINEMENT_PREDICATES_H diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/intersection.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/intersection.h index 4a841e38e2c..b2c380e23d0 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/intersection.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/intersection.h @@ -1642,7 +1642,7 @@ OutputIterator intersecting_meshes(const TriangleMeshRange& range, * given as a vector of points * @param throw_on_self_intersection if `true`, for each input triangle mesh, * the set of triangles closed to the intersection of `tm1` and `tm2` will be - * checked for self-intersection and `CGAL::Corefinement::Self_intersection_exception` + * checked for self-intersection and `CGAL::Polygon_mesh_processing::Corefinement::Self_intersection_exception` * will be thrown if at least one is found. * @param np1 optional sequence of \ref pmp_namedparameters "Named Parameters" among the ones listed below * @param np2 optional sequence of \ref pmp_namedparameters "Named Parameters" among the ones listed below diff --git a/Polygon_mesh_processing/test/Polygon_mesh_processing/test_corefinement_bool_op.cpp b/Polygon_mesh_processing/test/Polygon_mesh_processing/test_corefinement_bool_op.cpp index 29501f793ea..037a180ad1a 100644 --- a/Polygon_mesh_processing/test/Polygon_mesh_processing/test_corefinement_bool_op.cpp +++ b/Polygon_mesh_processing/test/Polygon_mesh_processing/test_corefinement_bool_op.cpp @@ -26,7 +26,7 @@ typedef CGAL::Exact_predicates_inexact_constructions_kernel Kernel; typedef CGAL::Surface_mesh Surface_mesh; namespace PMP = CGAL::Polygon_mesh_processing; -namespace CFR = CGAL::Corefinement; +namespace CFR = PMP::Corefinement; //includes typedefs for Operations on polyhedra typedef CGAL::Polyhedron_3 Polyhedron; diff --git a/Polyhedron/demo/Polyhedron/Plugins/PMP/Surface_intersection_plugin.cpp b/Polyhedron/demo/Polyhedron/Plugins/PMP/Surface_intersection_plugin.cpp index 9f9402a2030..4b6903fa7eb 100644 --- a/Polyhedron/demo/Polyhedron/Plugins/PMP/Surface_intersection_plugin.cpp +++ b/Polyhedron/demo/Polyhedron/Plugins/PMP/Surface_intersection_plugin.cpp @@ -150,7 +150,7 @@ void Polyhedron_demo_intersection_plugin::intersectionSurfaces() std::back_inserter(new_item->polylines), true); } - catch(CGAL::Corefinement::Self_intersection_exception) + catch(CGAL::Polygon_mesh_processing::Corefinement::Self_intersection_exception) { QMessageBox::warning((QWidget*)NULL, tr("Self-intersections Found"), From a097455ab30b0714bbfe05aaa169e4f2ab280104 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Loriot?= Date: Thu, 24 May 2018 18:21:21 +0200 Subject: [PATCH 07/43] doc update --- .../Concepts/PMPCorefinementNewFaceVisitor.h | 2 +- .../doc/Polygon_mesh_processing/NamedParameters.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Polygon_mesh_processing/doc/Polygon_mesh_processing/Concepts/PMPCorefinementNewFaceVisitor.h b/Polygon_mesh_processing/doc/Polygon_mesh_processing/Concepts/PMPCorefinementNewFaceVisitor.h index 36205aa9e44..123fc000b21 100644 --- a/Polygon_mesh_processing/doc/Polygon_mesh_processing/Concepts/PMPCorefinementNewFaceVisitor.h +++ b/Polygon_mesh_processing/doc/Polygon_mesh_processing/Concepts/PMPCorefinementNewFaceVisitor.h @@ -32,7 +32,7 @@ typedef unspecified_type face_descriptor; void after_subface_created(face_descriptor f_new, Triangle_mesh& tm); /// @} -/// @name Functions used by functions computing Boolean operations using corefinement. +/// @name Functions used by Boolean operations functions using corefinement. /// These functions are not needed if you only call `corefine()`. /// @{ /// called before importing the face `f_src` of `tm_src` in `tm_tgt` diff --git a/Polygon_mesh_processing/doc/Polygon_mesh_processing/NamedParameters.txt b/Polygon_mesh_processing/doc/Polygon_mesh_processing/NamedParameters.txt index 55485dfdd4f..16eecfbadd8 100644 --- a/Polygon_mesh_processing/doc/Polygon_mesh_processing/NamedParameters.txt +++ b/Polygon_mesh_processing/doc/Polygon_mesh_processing/NamedParameters.txt @@ -315,7 +315,7 @@ A class model of `PMPCorefinementNewFaceVisitor` that is used in corefinement-re to track the creation of new faces. \n \b Type : `A class` \n -\b Default value is a model of `PMPCorefinementNewFaceVisitor` that is doing nothing +\b Default `CGAL::Polygon_mesh_processing::Corefinement::Default_new_face_visitor` \cgalNPEnd From eea53718bbac7773dfd4f26dd64d67fd15315293 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Loriot?= Date: Thu, 24 May 2018 18:23:12 +0200 Subject: [PATCH 08/43] renaming --- .../Polygon_mesh_processing/corefinement.h | 116 +++++++++--------- .../Corefinement/Face_graph_output_builder.h | 59 +++++---- .../test_corefinement_and_constraints.cpp | 28 ++--- .../test_corefinement_bool_op.cpp | 28 ++--- 4 files changed, 116 insertions(+), 115 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/corefinement.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/corefinement.h index 24abae07b8f..baacb794d71 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/corefinement.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/corefinement.h @@ -243,11 +243,11 @@ bool does_bound_a_volume(const TriangleMesh& tm) } #define CGAL_COREF_SET_OUTPUT_VERTEX_POINT_MAP(i) \ - if (desired_output[i]!=boost::none) \ + if (output[i]!=boost::none) \ { \ vpm_out.push_back( \ boost::choose_param(get_param(cpp11::get(nps_out), internal_np::vertex_point), \ - get_property_map(boost::vertex_point, *(*desired_output[i])))); \ + get_property_map(boost::vertex_point, *(*output[i])))); \ output_vpms[i]=&vpm_out.back(); \ } \ else \ @@ -275,18 +275,18 @@ template cpp11::array -boolean_operation( TriangleMesh& tm1, - TriangleMesh& tm2, - const cpp11::array< boost::optional,4>& desired_output, - const NamedParameters1& np1, - const NamedParameters2& np2, - const cpp11::tuple& nps_out, - const bool throw_on_self_intersection = false ) +corefine_and_compute_boolean_operations( + TriangleMesh& tm1, + TriangleMesh& tm2, + const cpp11::array< boost::optional,4>& output, + const NamedParameters1& np1, + const NamedParameters2& np2, + const cpp11::tuple& nps_out, + const bool throw_on_self_intersection = false ) { - // Vertex point maps //for input meshes typedef typename GetVertexPointMap functor(tm1, tm2, vpm1, vpm2, Visitor(dnv,nfv,ob,ecm_in)); @@ -434,17 +434,18 @@ boolean_operation( TriangleMesh& tm1, template cpp11::array -boolean_operation( TriangleMesh& tm1, - TriangleMesh& tm2, - const cpp11::array< boost::optional,4>& desired_output, - const bool throw_on_self_intersection = false ) +corefine_and_compute_boolean_operations( + TriangleMesh& tm1, + TriangleMesh& tm2, + const cpp11::array< boost::optional,4>& output, + const bool throw_on_self_intersection = false ) { using namespace CGAL::Polygon_mesh_processing::parameters; - return boolean_operation(tm1, tm2, desired_output, - all_default(), all_default(), - cpp11::make_tuple(all_default(), all_default(), - all_default(), all_default()), - throw_on_self_intersection); + return corefine_and_compute_boolean_operations(tm1, tm2, output, + all_default(), all_default(), + cpp11::make_tuple(all_default(), all_default(), + all_default(), all_default()), + throw_on_self_intersection); } #undef CGAL_COREF_SET_OUTPUT_VERTEX_POINT_MAP @@ -527,16 +528,16 @@ corefine_and_compute_union( TriangleMesh& tm1, const NamedParametersOut& np_out) { using namespace CGAL::Polygon_mesh_processing::parameters; - cpp11::array< boost::optional,4> desired_output; - desired_output[Corefinement::UNION]=&tm_out; + cpp11::array< boost::optional,4> output; + output[Corefinement::UNION]=&tm_out; return - boolean_operation(tm1, tm2, desired_output, np1, np2, - cpp11::make_tuple(np_out, - no_parameters(np_out), - no_parameters(np_out), - no_parameters(np_out))) - [Corefinement::UNION]; + corefine_and_compute_boolean_operations(tm1, tm2, output, np1, np2, + cpp11::make_tuple(np_out, + no_parameters(np_out), + no_parameters(np_out), + no_parameters(np_out))) + [Corefinement::UNION]; } /** @@ -559,16 +560,16 @@ corefine_and_compute_intersection( TriangleMesh& tm1, const NamedParametersOut& np_out) { using namespace CGAL::Polygon_mesh_processing::parameters; - cpp11::array< boost::optional,4> desired_output; - desired_output[Corefinement::INTER]=&tm_out; + cpp11::array< boost::optional,4> output; + output[Corefinement::INTERSECTION]=&tm_out; return - boolean_operation(tm1, tm2, desired_output, np1, np2, - cpp11::make_tuple(no_parameters(np_out), - np_out, - no_parameters(np_out), - no_parameters(np_out))) - [Corefinement::INTER]; + corefine_and_compute_boolean_operations(tm1, tm2, output, np1, np2, + cpp11::make_tuple(no_parameters(np_out), + np_out, + no_parameters(np_out), + no_parameters(np_out))) + [Corefinement::INTERSECTION]; } /** @@ -592,15 +593,16 @@ corefine_and_compute_difference( TriangleMesh& tm1, { using namespace CGAL::Polygon_mesh_processing::parameters; using namespace CGAL::Polygon_mesh_processing::Corefinement; - cpp11::array< boost::optional,4> desired_output; - desired_output[TM1_MINUS_TM2]=&tm_out; + cpp11::array< boost::optional,4> output; + output[TM1_MINUS_TM2]=&tm_out; return - boolean_operation(tm1, tm2, desired_output, np1, np2, - cpp11::make_tuple(no_parameters(np_out), - no_parameters(np_out), - np_out, - no_parameters(np_out)))[TM1_MINUS_TM2]; + corefine_and_compute_boolean_operations(tm1, tm2, output, np1, np2, + cpp11::make_tuple(no_parameters(np_out), + no_parameters(np_out), + np_out, + no_parameters(np_out))) + [TM1_MINUS_TM2]; } /** diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Corefinement/Face_graph_output_builder.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Corefinement/Face_graph_output_builder.h index 448a147cabb..e4fc15244ff 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Corefinement/Face_graph_output_builder.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Corefinement/Face_graph_output_builder.h @@ -47,9 +47,8 @@ namespace CGAL { namespace Polygon_mesh_processing { namespace Corefinement { -enum Boolean_operation {UNION = 0, INTER, - TM1_MINUS_TM2, TM2_MINUS_TM1, - NONE }; +enum Boolean_operation {UNION = 0, INTERSECTION, + TM1_MINUS_TM2, TM2_MINUS_TM1, NONE }; namespace PMP=Polygon_mesh_processing; namespace params=PMP::parameters; @@ -115,7 +114,7 @@ class Face_graph_output_builder EdgeMarkMapTuple& out_edge_mark_maps; NewFaceVisitor& new_face_visitor; // output meshes - const cpp11::array, 4>& desired_output; + const cpp11::array, 4>& requested_output; // input meshes closed ? /// \todo do we really need this? bool is_tm1_closed; @@ -345,7 +344,7 @@ public: EdgeMarkMapTuple& out_edge_mark_maps, NewFaceVisitor& new_face_visitor, const cpp11::array< - boost::optional, 4 >& desired_output) + boost::optional, 4 >& requested_output) : tm1(tm1), tm2(tm2) , vpm1(vpm1), vpm2(vpm2) , fids1(fids1), fids2(fids2) @@ -353,7 +352,7 @@ public: , output_vpms(output_vpms) , out_edge_mark_maps(out_edge_mark_maps) , new_face_visitor(new_face_visitor) - , desired_output(desired_output) + , requested_output(requested_output) , is_tm1_closed( is_closed(tm1)) , is_tm2_closed( is_closed(tm2)) , is_tm1_inside_out( is_tm1_closed && !PMP::is_outward_oriented(tm1) ) @@ -368,7 +367,7 @@ public: } bool intersection_is_valid() const { - return !impossible_operation[INTER]; + return !impossible_operation[INTERSECTION]; } bool tm1_minus_tm2_is_valid() const { @@ -859,7 +858,7 @@ public: // opposite( poly_first U poly_second ) = {O} is_patch_inside_tm2.set(patch_id_p1); is_patch_inside_tm2.set(patch_id_p2); - impossible_operation.set(INTER); // tm1 n tm2 is non-manifold + impossible_operation.set(INTERSECTION); // tm1 n tm2 is non-manifold } } else @@ -1110,7 +1109,7 @@ public: std::vector< boost::dynamic_bitset<> > patches_of_tm2_used(4); /// handle the bitset for the union - if ( !impossible_operation.test(UNION) && desired_output[UNION] ) + if ( !impossible_operation.test(UNION) && requested_output[UNION] ) { //define patches to import from P patches_of_tm1_used[UNION] = ~is_patch_inside_tm2 - coplanar_patches_of_tm1; @@ -1119,7 +1118,7 @@ public: //handle coplanar patches if (coplanar_patches_of_tm1.any()) { - if (desired_output[UNION]==&tm2) + if (requested_output[UNION]==&tm2) patches_of_tm2_used[UNION] |= coplanar_patches_of_tm2_for_union_and_intersection; else patches_of_tm1_used[UNION] |= coplanar_patches_of_tm1_for_union_and_intersection; @@ -1127,24 +1126,24 @@ public: } /// handle the bitset for the intersection - if ( !impossible_operation.test(INTER) && desired_output[INTER] ) + if ( !impossible_operation.test(INTERSECTION) && requested_output[INTERSECTION] ) { //define patches to import from P - patches_of_tm1_used[INTER] = is_patch_inside_tm2; + patches_of_tm1_used[INTERSECTION] = is_patch_inside_tm2; //define patches to import from Q - patches_of_tm2_used[INTER] = is_patch_inside_tm1; + patches_of_tm2_used[INTERSECTION] = is_patch_inside_tm1; //handle coplanar patches if (coplanar_patches_of_tm1.any()) { - if (desired_output[INTER]==&tm2) - patches_of_tm2_used[INTER] |= coplanar_patches_of_tm2_for_union_and_intersection; + if (requested_output[INTERSECTION]==&tm2) + patches_of_tm2_used[INTERSECTION] |= coplanar_patches_of_tm2_for_union_and_intersection; else - patches_of_tm1_used[INTER] |= coplanar_patches_of_tm1_for_union_and_intersection; + patches_of_tm1_used[INTERSECTION] |= coplanar_patches_of_tm1_for_union_and_intersection; } } /// handle the bitset for P-Q - if ( !impossible_operation.test(TM1_MINUS_TM2) && desired_output[TM1_MINUS_TM2] ) + if ( !impossible_operation.test(TM1_MINUS_TM2) && requested_output[TM1_MINUS_TM2] ) { //define patches to import from P patches_of_tm1_used[TM1_MINUS_TM2] = (~is_patch_inside_tm2 - coplanar_patches_of_tm1); @@ -1153,7 +1152,7 @@ public: //handle coplanar patches if (coplanar_patches_of_tm1.any()) { - if (desired_output[TM1_MINUS_TM2]==&tm2) + if (requested_output[TM1_MINUS_TM2]==&tm2) patches_of_tm2_used[TM1_MINUS_TM2] |= ~coplanar_patches_of_tm2_for_union_and_intersection & coplanar_patches_of_tm2; else patches_of_tm1_used[TM1_MINUS_TM2] |= ~coplanar_patches_of_tm1_for_union_and_intersection & coplanar_patches_of_tm1; @@ -1161,7 +1160,7 @@ public: } /// handle the bitset for Q-P - if ( !impossible_operation.test(TM2_MINUS_TM1) && desired_output[TM2_MINUS_TM1] ) + if ( !impossible_operation.test(TM2_MINUS_TM1) && requested_output[TM2_MINUS_TM1] ) { //define patches to import from P patches_of_tm1_used[TM2_MINUS_TM1] = is_patch_inside_tm2; @@ -1170,7 +1169,7 @@ public: //handle coplanar patches if (coplanar_patches_of_tm1.any()) { - if (desired_output[TM2_MINUS_TM1]==&tm2) + if (requested_output[TM2_MINUS_TM1]==&tm2) patches_of_tm2_used[TM2_MINUS_TM1] |= ~coplanar_patches_of_tm2_for_union_and_intersection & coplanar_patches_of_tm2; else patches_of_tm1_used[TM2_MINUS_TM1] |= ~coplanar_patches_of_tm1_for_union_and_intersection & coplanar_patches_of_tm1; @@ -1181,8 +1180,8 @@ public: #ifdef CGAL_COREFINEMENT_DEBUG std::cout << "patches_of_tm1_used[UNION] " << patches_of_tm1_used[UNION] << "\n"; std::cout << "patches_of_tm2_used[UNION] " << patches_of_tm2_used[UNION] << "\n"; - std::cout << "patches_of_tm1_used[INTER] " << patches_of_tm1_used[INTER] << "\n"; - std::cout << "patches_of_tm2_used[INTER] " << patches_of_tm2_used[INTER] << "\n"; + std::cout << "patches_of_tm1_used[INTERSECTION] " << patches_of_tm1_used[INTERSECTION] << "\n"; + std::cout << "patches_of_tm2_used[INTERSECTION] " << patches_of_tm2_used[INTERSECTION] << "\n"; std::cout << "patches_of_tm1_used[TM1_MINUS_TM2] " << patches_of_tm1_used[TM1_MINUS_TM2] << "\n"; std::cout << "patches_of_tm2_used[TM1_MINUS_TM2] " << patches_of_tm2_used[TM1_MINUS_TM2] << "\n"; std::cout << "patches_of_tm1_used[TM2_MINUS_TM1] " << patches_of_tm1_used[TM2_MINUS_TM1] << "\n"; @@ -1199,13 +1198,13 @@ public: { Boolean_operation operation=enum_cast(i); - if (!desired_output[operation] || impossible_operation.test(operation)) + if (!requested_output[operation] || impossible_operation.test(operation)) continue; - if (desired_output[operation]==&tm1) + if (requested_output[operation]==&tm1) inplace_operation_tm1=operation; else - if (desired_output[operation]==&tm2) + if (requested_output[operation]==&tm2) inplace_operation_tm2=operation; else out_of_place_operations.push_back(operation); @@ -1214,7 +1213,7 @@ public: /// first handle operations in a mesh that is neither tm1 nor tm2 BOOST_FOREACH(Boolean_operation operation, out_of_place_operations) { - TriangleMesh& output = *(*desired_output[operation]); + TriangleMesh& output = *(*requested_output[operation]); CGAL_assertion(&tm1!=&output && &tm2!=&output); Intersection_polylines polylines(tm1_polylines, @@ -1256,7 +1255,7 @@ public: mesh_to_intersection_edges[&tm1], inplace_operation_tm1); - CGAL_assertion( *desired_output[inplace_operation_tm1] == &tm1 ); + CGAL_assertion( *requested_output[inplace_operation_tm1] == &tm1 ); if ( inplace_operation_tm2!=NONE) { @@ -1323,7 +1322,7 @@ public: disconnected_patches_edge_to_tm2_edge, new_face_visitor); // Operation in tm2: discard patches and append the one from tm2 - CGAL_assertion( *desired_output[inplace_operation_tm2] == &tm2 ); + CGAL_assertion( *requested_output[inplace_operation_tm2] == &tm2 ); compute_inplace_operation( tm2, tm1, patches_of_tm2_used[inplace_operation_tm2], patches_of_tm1_used[inplace_operation_tm2], @@ -1363,7 +1362,7 @@ public: } else{ /// handle the operation updating only tm1 - CGAL_assertion( *desired_output[inplace_operation_tm1] == &tm1 ); + CGAL_assertion( *requested_output[inplace_operation_tm1] == &tm1 ); Intersection_polylines polylines( tm1_polylines, tm2_polylines, polyline_lengths); fill_polylines_to_skip( @@ -1403,7 +1402,7 @@ public: inplace_operation_tm2); /// handle the operation updating only tm2 - CGAL_assertion( *desired_output[inplace_operation_tm2] == &tm2 ); + CGAL_assertion( *requested_output[inplace_operation_tm2] == &tm2 ); Intersection_polylines polylines( tm2_polylines, tm1_polylines, polyline_lengths); fill_polylines_to_skip( diff --git a/Polygon_mesh_processing/test/Polygon_mesh_processing/test_corefinement_and_constraints.cpp b/Polygon_mesh_processing/test/Polygon_mesh_processing/test_corefinement_and_constraints.cpp index 2cd8a9c2a1a..d79e6f06b10 100644 --- a/Polygon_mesh_processing/test/Polygon_mesh_processing/test_corefinement_and_constraints.cpp +++ b/Polygon_mesh_processing/test/Polygon_mesh_processing/test_corefinement_and_constraints.cpp @@ -166,24 +166,24 @@ void test_bool_op_no_copy( typedef boost::optional OTM; OTM none; - const CGAL::cpp11::array desired_output = + const CGAL::cpp11::array output = reverse ? CGAL::make_array(OTM(&tm2), OTM(&tm1), none, none) : CGAL::make_array(OTM(&tm1), OTM(&tm2), none, none); - PMP::boolean_operation(tm1, - tm2, - desired_output, - params::edge_is_constrained_map(ecm1), - params::edge_is_constrained_map(ecm2), - CGAL::cpp11::make_tuple(params::edge_is_constrained_map(ecm_out_union), - params::edge_is_constrained_map(ecm_out_inter), - params::no_parameters(params::edge_is_constrained_map(ecm_out_union)), - params::no_parameters(params::edge_is_constrained_map(ecm_out_union)))); + PMP::corefine_and_compute_boolean_operations(tm1, + tm2, + output, + params::edge_is_constrained_map(ecm1), + params::edge_is_constrained_map(ecm2), + CGAL::cpp11::make_tuple(params::edge_is_constrained_map(ecm_out_union), + params::edge_is_constrained_map(ecm_out_inter), + params::no_parameters(params::edge_is_constrained_map(ecm_out_union)), + params::no_parameters(params::edge_is_constrained_map(ecm_out_union)))); - // dump_constrained_edges(*(*desired_output[0]), ecm_out_union, "out_cst_union.cgal"); - // dump_constrained_edges(*(*desired_output[1]), ecm_out_inter, "out_cst_inter.cgal"); + // dump_constrained_edges(*(*output[0]), ecm_out_union, "out_cst_union.cgal"); + // dump_constrained_edges(*(*output[1]), ecm_out_inter, "out_cst_inter.cgal"); - assert( count_constrained_edges(*(*desired_output[0]), ecm_out_union)==838 ); - assert( count_constrained_edges(*(*desired_output[1]), ecm_out_inter)==475 ); + assert( count_constrained_edges(*(*output[0]), ecm_out_union)==838 ); + assert( count_constrained_edges(*(*output[1]), ecm_out_inter)==475 ); } void test_bool_op(Triangle_mesh tm1, Triangle_mesh tm2, bool reverse, const char* outname) diff --git a/Polygon_mesh_processing/test/Polygon_mesh_processing/test_corefinement_bool_op.cpp b/Polygon_mesh_processing/test/Polygon_mesh_processing/test_corefinement_bool_op.cpp index 037a180ad1a..97e23b20cf2 100644 --- a/Polygon_mesh_processing/test/Polygon_mesh_processing/test_corefinement_bool_op.cpp +++ b/Polygon_mesh_processing/test/Polygon_mesh_processing/test_corefinement_bool_op.cpp @@ -65,15 +65,15 @@ void run_boolean_operations( CGAL::Emptyset_iterator output_it; Facet_id_map P_facet_id_map, Q_facet_id_map; - CGAL::cpp11::array, 4 > desired_output; + CGAL::cpp11::array, 4 > output; - desired_output[Output_builder::P_UNION_Q]=boost::make_optional( &union_ ); - desired_output[Output_builder::P_INTER_Q]=boost::make_optional( &inter ); - desired_output[Output_builder::P_MINUS_Q]=boost::make_optional( &P_minus_Q ); - desired_output[Output_builder::Q_MINUS_P]=boost::make_optional( &Q_minus_P ); + output[Output_builder::P_UNION_Q]=boost::make_optional( &union_ ); + output[Output_builder::P_INTER_Q]=boost::make_optional( &inter ); + output[Output_builder::P_MINUS_Q]=boost::make_optional( &P_minus_Q ); + output[Output_builder::Q_MINUS_P]=boost::make_optional( &Q_minus_P ); Output_builder output_builder(P, Q, - desired_output, + output, Facet_id_pmap(P_facet_id_map), Facet_id_pmap(Q_facet_id_map) ); Split_visitor visitor(output_builder); @@ -169,17 +169,17 @@ void run_boolean_operations( typedef boost::optional OSM; - CGAL::cpp11::array desired_output; + CGAL::cpp11::array output; - desired_output[CFR::UNION]=OSM( &union_ ); - desired_output[CFR::INTER]=OSM( &inter ); - desired_output[CFR::TM1_MINUS_TM2]=OSM( &tm1_minus_tm2 ); - desired_output[CFR::TM2_MINUS_TM1]=OSM( &tm2_minus_tm1 ); + output[CFR::UNION]=OSM( &union_ ); + output[CFR::INTERSECTION]=OSM( &inter ); + output[CFR::TM1_MINUS_TM2]=OSM( &tm1_minus_tm2 ); + output[CFR::TM2_MINUS_TM1]=OSM( &tm2_minus_tm1 ); std::cout << " Vertices before " << tm1.number_of_vertices() << " " << tm2.number_of_vertices() << std::endl; - CGAL::cpp11::array res = PMP::boolean_operation(tm1, tm2, desired_output); + CGAL::cpp11::array res = PMP::corefine_and_compute_boolean_operations(tm1, tm2, output); std::cout << " Vertices after " << tm1.number_of_vertices() << " " << tm2.number_of_vertices() << std::endl; @@ -197,7 +197,7 @@ void run_boolean_operations( else std::cout << " Union is invalid\n"; - if ( res[CFR::INTER] ){ + if ( res[CFR::INTERSECTION] ){ std::cout << " Intersection is a valid operation\n"; assert(inter.is_valid()); #ifdef CGAL_COREFINEMENT_DEBUG @@ -239,7 +239,7 @@ void run_boolean_operations( if ( rc.check ) { if (res[CFR::UNION]!=rc.union_res || - res[CFR::INTER]!=rc.inter_res || + res[CFR::INTERSECTION]!=rc.inter_res || res[CFR::TM1_MINUS_TM2]!=rc.P_minus_Q_res || res[CFR::TM2_MINUS_TM1]!=rc.Q_minus_P_res) { From d3a9f655afe73e1262f56ccddce3436a6970e4b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Loriot?= Date: Fri, 25 May 2018 09:46:31 +0200 Subject: [PATCH 09/43] replace bool parameter throw_on_self_intersection with a named parameter used it at the same time in Boolean operation functions --- .../CGAL/boost/graph/parameters_interface.h | 1 + BGL/test/BGL/test_cgal_bgl_named_params.cpp | 3 + Installation/CHANGES.md | 4 + .../NamedParameters.txt | 9 ++ .../Polygon_mesh_processing/corefinement.h | 82 +++++++++++++++---- .../Polygon_mesh_processing/intersection.h | 53 +++++++++--- .../Plugins/PMP/Corefinement_plugin.cpp | 10 ++- 7 files changed, 129 insertions(+), 33 deletions(-) diff --git a/BGL/include/CGAL/boost/graph/parameters_interface.h b/BGL/include/CGAL/boost/graph/parameters_interface.h index b3d44074380..ca7a1feed3e 100644 --- a/BGL/include/CGAL/boost/graph/parameters_interface.h +++ b/BGL/include/CGAL/boost/graph/parameters_interface.h @@ -65,6 +65,7 @@ CGAL_add_named_parameter(outward_orientation_t, outward_orientation, outward_ori CGAL_add_named_parameter(overlap_test_t, overlap_test, do_overlap_test_of_bounded_sides) CGAL_add_named_parameter(preserve_genus_t, preserve_genus, preserve_genus) CGAL_add_named_parameter(new_face_visitor_t, new_face_visitor, new_face_visitor) +CGAL_add_named_parameter(throw_on_self_intersection_t, throw_on_self_intersection, throw_on_self_intersection) // List of named parameters that we use in the package 'Surface Mesh Simplification' CGAL_add_named_parameter(get_cost_policy_t, get_cost_policy, get_cost) diff --git a/BGL/test/BGL/test_cgal_bgl_named_params.cpp b/BGL/test/BGL/test_cgal_bgl_named_params.cpp index 013931e028e..1efa27797da 100644 --- a/BGL/test/BGL/test_cgal_bgl_named_params.cpp +++ b/BGL/test/BGL/test_cgal_bgl_named_params.cpp @@ -73,6 +73,7 @@ void test(const NamedParameters& np) assert(get_param(np, CGAL::internal_np::nb_points_per_area_unit).v == 32); assert(get_param(np, CGAL::internal_np::nb_points_per_distance_unit).v == 33); assert(get_param(np, CGAL::internal_np::new_face_visitor).v == 42); + assert(get_param(np, CGAL::internal_np::throw_on_self_intersection).v == 43); // Named parameters that we use in the package 'Surface Mesh Simplification' assert(get_param(np, CGAL::internal_np::get_cost_policy).v == 34); @@ -138,6 +139,7 @@ void test(const NamedParameters& np) check_same_type<32>(get_param(np, CGAL::internal_np::nb_points_per_area_unit)); check_same_type<33>(get_param(np, CGAL::internal_np::nb_points_per_distance_unit)); check_same_type<42>(get_param(np, CGAL::internal_np::new_face_visitor)); + check_same_type<43>(get_param(np, CGAL::internal_np::throw_on_self_intersection)); // Named parameters that we use in the package 'Surface Mesh Simplification' check_same_type<34>(get_param(np, CGAL::internal_np::get_cost_policy)); @@ -202,6 +204,7 @@ int main() .preserve_genus(A<40>(40)) .verbosity_level(A<41>(41)) .new_face_visitor(A<42>(42)) + .throw_on_self_intersection(A<43>(43)) ); return EXIT_SUCCESS; diff --git a/Installation/CHANGES.md b/Installation/CHANGES.md index f6608194fe1..6840262b9d8 100644 --- a/Installation/CHANGES.md +++ b/Installation/CHANGES.md @@ -38,6 +38,10 @@ Release date: September 2018 - Add in corefinement-related functions a new named parameter `new_face_visitor` that make it possible to pass a visitor to the function in order to track the creation of new faces. +- Add in all corefinement-related functions a named parameter `throw_on_self_intersection` + (that replace the `bool` parameter in `corefine()`) that enables to check for + self-intersections faces involved in the intersection before trying to corefine the + input meshes. Release 4.12 ------------ diff --git a/Polygon_mesh_processing/doc/Polygon_mesh_processing/NamedParameters.txt b/Polygon_mesh_processing/doc/Polygon_mesh_processing/NamedParameters.txt index 16eecfbadd8..41be61c81be 100644 --- a/Polygon_mesh_processing/doc/Polygon_mesh_processing/NamedParameters.txt +++ b/Polygon_mesh_processing/doc/Polygon_mesh_processing/NamedParameters.txt @@ -318,6 +318,15 @@ to track the creation of new faces. \b Default `CGAL::Polygon_mesh_processing::Corefinement::Default_new_face_visitor` \cgalNPEnd +\cgalNPBegin{throw_on_self_intersection} \anchor PMP_throw_on_self_intersection +Parameter used in corefinement-related functions to make the functions throw an exception in +case some faces involved in the intersection of the input are self-intersecting +and make the operation impossible with the current version of the code. +\n +\b Type : `bool` \n +\b Default value is `false` +\cgalNPEnd + \cgalNPTableEnd diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/corefinement.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/corefinement.h index baacb794d71..e029ba98837 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/corefinement.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/corefinement.h @@ -284,9 +284,11 @@ corefine_and_compute_boolean_operations( const cpp11::tuple& nps_out, - const bool throw_on_self_intersection = false ) + NamedParametersOut3>& nps_out) { + const bool throw_on_self_intersection = + boost::choose_param(get_param(np1, internal_np::throw_on_self_intersection), false); + // Vertex point maps //for input meshes typedef typename GetVertexPointMap corefine_and_compute_boolean_operations( TriangleMesh& tm1, TriangleMesh& tm2, - const cpp11::array< boost::optional,4>& output, - const bool throw_on_self_intersection = false ) + const cpp11::array< boost::optional,4>& output) { using namespace CGAL::Polygon_mesh_processing::parameters; return corefine_and_compute_boolean_operations(tm1, tm2, output, all_default(), all_default(), cpp11::make_tuple(all_default(), all_default(), - all_default(), all_default()), - throw_on_self_intersection); + all_default(), all_default())); } #undef CGAL_COREF_SET_OUTPUT_VERTEX_POINT_MAP @@ -495,6 +495,11 @@ corefine_and_compute_boolean_operations( * \cgalParamBegin{new_face_visitor} a class model of `PMPCorefinementNewFaceVisitor` * that is used to track the creation of new faces (`np1` only) * \cgalParamEnd + * \cgalParamBegin{throw_on_self_intersection} if `true`, for each input triangle mesh, + * the set of triangles closed to the intersection of `tm1` and `tm2` will be + * checked for self-intersection and `CGAL::Polygon_mesh_processing::Corefinement::Self_intersection_exception` + * will be thrown if at least one is found (`np1` only). + * \cgalParamEnd * \cgalNamedParamsEnd * * @param np_out optional sequence of \ref pmp_namedparameters "Named Parameters" among the ones listed below @@ -623,10 +628,6 @@ corefine_and_compute_difference( TriangleMesh& tm1, * @param tm2 second input triangulated surface mesh * @param np1 optional sequence of \ref pmp_namedparameters "Named Parameters" among the ones listed below * @param np2 optional sequence of \ref pmp_namedparameters "Named Parameters" among the ones listed below - * @param throw_on_self_intersection if `true`, for each input triangle mesh, - * the set of triangles closed to the intersection of `tm1` and `tm2` will be - * checked for self-intersection and `CGAL::Polygon_mesh_processing::Corefinement::Self_intersection_exception` - * will be thrown if at least one is found. * * \cgalNamedParamsBegin * \cgalParamBegin{vertex_point_map} @@ -640,6 +641,11 @@ corefine_and_compute_difference( TriangleMesh& tm1, * \cgalParamBegin{new_face_visitor} a class model of `PMPCorefinementNewFaceVisitor` * that is used to track the creation of new faces (`np1` only) * \cgalParamEnd + * \cgalParamBegin{throw_on_self_intersection} if `true`, for each input triangle mesh, + * the set of triangles closed to the intersection of `tm1` and `tm2` will be + * checked for self-intersection and `CGAL::Polygon_mesh_processing::Corefinement::Self_intersection_exception` + * will be thrown if at least one is found (`np1` only). + * \cgalParamEnd * \cgalNamedParamsEnd * */ @@ -650,9 +656,11 @@ corefine_and_compute_difference( TriangleMesh& tm1, corefine( TriangleMesh& tm1, TriangleMesh& tm2, const NamedParameters1& np1, - const NamedParameters2& np2, - const bool throw_on_self_intersection = false) + const NamedParameters2& np2) { + const bool throw_on_self_intersection = + boost::choose_param(get_param(np1, internal_np::throw_on_self_intersection), false); + // Vertex point maps typedef typename GetVertexPointMap::type Vpm; @@ -1006,22 +1014,60 @@ template void corefine( TriangleMesh& tm1, TriangleMesh& tm2, - const NamedParameters1& np1, - const bool throw_on_self_intersection = false) + const NamedParameters1& np1) { using namespace CGAL::Polygon_mesh_processing::parameters; - corefine(tm1, tm2, np1, all_default(), throw_on_self_intersection); + corefine(tm1, tm2, np1, all_default()); +} + +template +void +corefine( TriangleMesh& tm1, + TriangleMesh& tm2) +{ + using namespace CGAL::Polygon_mesh_processing::parameters; + corefine(tm1, tm2, all_default(), all_default()); +} + +#ifndef CGAL_NO_DEPRECATED_CODE + template + void + corefine( TriangleMesh& tm1, + TriangleMesh& tm2, + const NamedParameters1& np1, + const NamedParameters2& np2, + const bool throw_on_self_intersection) +{ + corefine(tm1, tm2, np1.throw_on_self_intersection(throw_on_self_intersection), np2); +} + +template +void +corefine( TriangleMesh& tm1, + TriangleMesh& tm2, + const NamedParameters1& np1, + const bool throw_on_self_intersection) +{ + namespace params = CGAL::Polygon_mesh_processing::parameters; + corefine(tm1, tm2, + np1.throw_on_self_intersection(throw_on_self_intersection), + params::all_default()); } template void corefine( TriangleMesh& tm1, TriangleMesh& tm2, - const bool throw_on_self_intersection = false) + const bool throw_on_self_intersection) { - using namespace CGAL::Polygon_mesh_processing::parameters; - corefine(tm1, tm2, all_default(), all_default(), throw_on_self_intersection); + namespace params = CGAL::Polygon_mesh_processing::parameters; + corefine(tm1, tm2, + params::throw_on_self_intersection(throw_on_self_intersection), + params::all_default()); } +#endif ///// autorefine ///// namespace experimental { diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/intersection.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/intersection.h index b2c380e23d0..0d866d51544 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/intersection.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/intersection.h @@ -1640,10 +1640,6 @@ OutputIterator intersecting_meshes(const TriangleMeshRange& range, * @param tm2 second input triangulated surface mesh * @param polyline_output output iterator of polylines. Each polyline will be * given as a vector of points - * @param throw_on_self_intersection if `true`, for each input triangle mesh, - * the set of triangles closed to the intersection of `tm1` and `tm2` will be - * checked for self-intersection and `CGAL::Polygon_mesh_processing::Corefinement::Self_intersection_exception` - * will be thrown if at least one is found. * @param np1 optional sequence of \ref pmp_namedparameters "Named Parameters" among the ones listed below * @param np2 optional sequence of \ref pmp_namedparameters "Named Parameters" among the ones listed below * @@ -1652,6 +1648,11 @@ OutputIterator intersecting_meshes(const TriangleMeshRange& range, * a property map with the points associated to the vertices of `tm1` * (`tm2`). The two property map types must be the same. * \cgalParamEnd + * \cgalParamBegin{throw_on_self_intersection} if `true`, for each input triangle mesh, + * the set of triangles closed to the intersection of `tm1` and `tm2` will be + * checked for self-intersection and `CGAL::Polygon_mesh_processing::Corefinement::Self_intersection_exception` + * will be thrown if at least one is found (`np1` only). + * \cgalParamEnd * \cgalNamedParamsEnd * */ @@ -1664,9 +1665,11 @@ surface_intersection(const TriangleMesh& tm1, const TriangleMesh& tm2, OutputIterator polyline_output, const NamedParameters1& np1, - const NamedParameters2& np2, - const bool throw_on_self_intersection=false) + const NamedParameters2& np2) { + const bool throw_on_self_intersection = + boost::choose_param(get_param(np1, internal_np::throw_on_self_intersection), false); + typedef typename GetVertexPointMap::const_type Vpm; typedef typename GetVertexPointMap +OutputIterator +surface_intersection(const TriangleMesh& tm1, + const TriangleMesh& tm2, + OutputIterator polyline_output, + const NamedParameters1& np1, + const NamedParameters2& np2, + const bool throw_on_self_intersection) +{ + return surface_intersection(tm1, tm2, polyline_output, + np1.throw_on_self_intersection(throw_on_self_intersection), np2); +} + +template +OutputIterator +surface_intersection(const TriangleMesh& tm1, + const TriangleMesh& tm2, + OutputIterator polyline_output, + const bool throw_on_self_intersection) +{ + return surface_intersection(tm1, tm2, polyline_output, + CGAL::Polygon_mesh_processing::parameters::throw_on_self_intersection(throw_on_self_intersection), + CGAL::Polygon_mesh_processing::parameters::all_default()); +} +#endif + namespace experimental { template diff --git a/Polyhedron/demo/Polyhedron/Plugins/PMP/Corefinement_plugin.cpp b/Polyhedron/demo/Polyhedron/Plugins/PMP/Corefinement_plugin.cpp index 68c90b62a24..b4f592f8681 100644 --- a/Polyhedron/demo/Polyhedron/Plugins/PMP/Corefinement_plugin.cpp +++ b/Polyhedron/demo/Polyhedron/Plugins/PMP/Corefinement_plugin.cpp @@ -12,6 +12,8 @@ using namespace CGAL::Three; namespace PMP = CGAL::Polygon_mesh_processing; +namespace params = PMP::parameters; + class Polyhedron_demo_corefinement_sm_plugin : public QObject, public Polyhedron_demo_plugin_helper @@ -165,7 +167,7 @@ private: } QApplication::setOverrideCursor(Qt::WaitCursor); - PMP::corefine(*item1->face_graph(), *item2->face_graph()); + PMP::corefine(*item1->face_graph(), *item2->face_graph(), params::throw_on_self_intersection(true)); item1->invalidateOpenGLBuffers(); item2->invalidateOpenGLBuffers(); scene->itemChanged(item2); @@ -197,7 +199,7 @@ private: { case CRF_UNION: P = *first_item->face_graph(), Q = *item->face_graph(); - if (! PMP::corefine_and_compute_union(P, Q, *new_poly) ) + if (! PMP::corefine_and_compute_union(P, Q, *new_poly, params::throw_on_self_intersection(true)) ) { delete new_poly; messages->warning(tr("The result of the requested operation is not manifold and has not been computed.")); @@ -209,7 +211,7 @@ private: break; case CRF_INTER: P = *first_item->polyhedron(), Q = *item->polyhedron(); - if (! PMP::corefine_and_compute_intersection(P, Q, *new_poly) ) + if (! PMP::corefine_and_compute_intersection(P, Q, *new_poly, params::throw_on_self_intersection(true)) ) { delete new_poly; messages->warning(tr("The result of the requested operation is not manifold and has not been computed.")); @@ -224,7 +226,7 @@ private: CGAL_FALLTHROUGH; case CRF_MINUS: P = *first_item->polyhedron(), Q = *item->polyhedron(); - if (! PMP::corefine_and_compute_difference(P, Q, *new_poly) ) + if (! PMP::corefine_and_compute_difference(P, Q, *new_poly, params::throw_on_self_intersection(true)) ) { delete new_poly; messages->warning(tr("The result of the requested operation is not manifold and has not been computed.")); From 67ec602795e259af2414426f023bf19cceca73b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Loriot?= Date: Mon, 28 May 2018 10:29:16 +0200 Subject: [PATCH 10/43] remove a restriction on the output vpm they no longer requires to be of the same type. This is particularly important especially since we might ignore some of them. --- .../Polygon_mesh_processing/corefinement.h | 74 +++--- .../Corefinement/Face_graph_output_builder.h | 216 ++++++++++-------- .../internal/Corefinement/face_graph_utils.h | 76 +++++- 3 files changed, 240 insertions(+), 126 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/corefinement.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/corefinement.h index e029ba98837..8e44c7a54e7 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/corefinement.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/corefinement.h @@ -242,17 +242,6 @@ bool does_bound_a_volume(const TriangleMesh& tm) return does_bound_a_volume(tm, parameters::all_default()); } -#define CGAL_COREF_SET_OUTPUT_VERTEX_POINT_MAP(i) \ - if (output[i]!=boost::none) \ - { \ - vpm_out.push_back( \ - boost::choose_param(get_param(cpp11::get(nps_out), internal_np::vertex_point), \ - get_property_map(boost::vertex_point, *(*output[i])))); \ - output_vpms[i]=&vpm_out.back(); \ - } \ - else \ - output_vpms[i]=NULL; - #define CGAL_COREF_SET_OUTPUT_EDGE_MARK_MAP(I) \ typedef typename boost::lookup_named_param_def < \ internal_np::edge_is_constrained_t, \ @@ -306,14 +295,36 @@ corefine_and_compute_boolean_operations( Vpm vpm2 = boost::choose_param(get_param(np2, internal_np::vertex_point), get_property_map(boost::vertex_point, tm2)); - //for output meshes - cpp11::array output_vpms; - std::vector vpm_out; - vpm_out.reserve(4); - CGAL_COREF_SET_OUTPUT_VERTEX_POINT_MAP(0) - CGAL_COREF_SET_OUTPUT_VERTEX_POINT_MAP(1) - CGAL_COREF_SET_OUTPUT_VERTEX_POINT_MAP(2) - CGAL_COREF_SET_OUTPUT_VERTEX_POINT_MAP(3) + typedef typename boost::property_traits::value_type Point_3; + + // for output meshes: here we have to use a trick so that if for a specific output + // that is not requested, the default vpm does not have the same value type as the + // input map, a dummy default vpm is used so that calls to get/put can be compiled + // (even if not used). + typedef cpp11::tuple< + Corefinement::TweakedGetVertexPointMap, + Corefinement::TweakedGetVertexPointMap, + Corefinement::TweakedGetVertexPointMap, + Corefinement::TweakedGetVertexPointMap + > Vpm_out_tuple_helper; + + typedef cpp11::tuple< + boost::optional< typename cpp11::tuple_element<0, Vpm_out_tuple_helper>::type::type >, + boost::optional< typename cpp11::tuple_element<1, Vpm_out_tuple_helper>::type::type >, + boost::optional< typename cpp11::tuple_element<2, Vpm_out_tuple_helper>::type::type >, + boost::optional< typename cpp11::tuple_element<3, Vpm_out_tuple_helper>::type::type > + > Vpm_out_tuple; + + Vpm_out_tuple vpm_out_tuple( + Corefinement::get_vpm(cpp11::get<0>(nps_out), output[0], + typename cpp11::tuple_element<0, Vpm_out_tuple_helper>::type::Use_default_tag()), + Corefinement::get_vpm(cpp11::get<1>(nps_out), output[1], + typename cpp11::tuple_element<1, Vpm_out_tuple_helper>::type::Use_default_tag()), + Corefinement::get_vpm(cpp11::get<2>(nps_out), output[2], + typename cpp11::tuple_element<2, Vpm_out_tuple_helper>::type::Use_default_tag()), + Corefinement::get_vpm(cpp11::get<3>(nps_out), output[3], + typename cpp11::tuple_element<3, Vpm_out_tuple_helper>::type::Use_default_tag()) + ); if (&tm1==&tm2) { @@ -328,7 +339,7 @@ corefine_and_compute_boolean_operations( Emptyset_iterator(), Emptyset_iterator(), vpm1, - vpm_out[Corefinement::UNION]); + *cpp11::get(vpm_out_tuple)); if (output[Corefinement::INTERSECTION] != boost::none) if (&tm1 != *output[Corefinement::INTERSECTION]) @@ -338,7 +349,7 @@ corefine_and_compute_boolean_operations( Emptyset_iterator(), Emptyset_iterator(), vpm1, - vpm_out[Corefinement::INTERSECTION]); + *cpp11::get(vpm_out_tuple)); if (output[Corefinement::TM1_MINUS_TM2] != boost::none) if (&tm1 == *output[Corefinement::TM1_MINUS_TM2]) @@ -410,6 +421,7 @@ corefine_and_compute_boolean_operations( typedef Corefinement::Face_graph_output_builder functor(tm1, tm2, vpm1, vpm2, Visitor(dnv,nfv,ob,ecm_in)); @@ -539,9 +551,9 @@ corefine_and_compute_union( TriangleMesh& tm1, return corefine_and_compute_boolean_operations(tm1, tm2, output, np1, np2, cpp11::make_tuple(np_out, - no_parameters(np_out), - no_parameters(np_out), - no_parameters(np_out))) + all_default(), + all_default(), + all_default())) [Corefinement::UNION]; } @@ -570,10 +582,10 @@ corefine_and_compute_intersection( TriangleMesh& tm1, return corefine_and_compute_boolean_operations(tm1, tm2, output, np1, np2, - cpp11::make_tuple(no_parameters(np_out), + cpp11::make_tuple(all_default(), np_out, - no_parameters(np_out), - no_parameters(np_out))) + all_default(), + all_default())) [Corefinement::INTERSECTION]; } @@ -603,10 +615,10 @@ corefine_and_compute_difference( TriangleMesh& tm1, return corefine_and_compute_boolean_operations(tm1, tm2, output, np1, np2, - cpp11::make_tuple(no_parameters(np_out), - no_parameters(np_out), + cpp11::make_tuple(all_default(), + all_default(), np_out, - no_parameters(np_out))) + all_default())) [TM1_MINUS_TM2]; } diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Corefinement/Face_graph_output_builder.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Corefinement/Face_graph_output_builder.h index e4fc15244ff..9db160185cc 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Corefinement/Face_graph_output_builder.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Corefinement/Face_graph_output_builder.h @@ -36,25 +36,39 @@ #include - -#define CGAL_COREF_SELECT_OUT_ECM(I) \ - (I == 0 ? cpp11::get<0>(out_edge_mark_maps) \ - : I == 1 ? cpp11::get<1>(out_edge_mark_maps) \ - : I == 2 ? cpp11::get<2>(out_edge_mark_maps) \ - : cpp11::get<3>(out_edge_mark_maps)) +// required to handle the multiple types of edge constrained maps +// for the different output types. CGAL_COREF_FUNCTION_CALL_DEF +// must be defined prior to using this macro. +#define CGAL_COREF_FUNCTION_CALL(BO_type) \ + switch(BO_type) \ + { \ + case UNION: \ + CGAL_COREF_FUNCTION_CALL_DEF(UNION); \ + break; \ + case INTERSECTION: \ + CGAL_COREF_FUNCTION_CALL_DEF(INTERSECTION); \ + break; \ + case TM1_MINUS_TM2: \ + CGAL_COREF_FUNCTION_CALL_DEF(TM1_MINUS_TM2); \ + break; \ + default: \ + CGAL_assertion( BO_type == TM2_MINUS_TM1 ); \ + CGAL_COREF_FUNCTION_CALL_DEF(TM2_MINUS_TM1); \ + } namespace CGAL { namespace Polygon_mesh_processing { namespace Corefinement { -enum Boolean_operation {UNION = 0, INTERSECTION, - TM1_MINUS_TM2, TM2_MINUS_TM1, NONE }; +enum Boolean_operation_type {UNION = 0, INTERSECTION, + TM1_MINUS_TM2, TM2_MINUS_TM1, NONE }; namespace PMP=Polygon_mesh_processing; namespace params=PMP::parameters; template & output_vpms; + const VpmOutTuple& output_vpms; EdgeMarkMapTuple& out_edge_mark_maps; NewFaceVisitor& new_face_visitor; // output meshes @@ -340,7 +354,7 @@ public: const FaceIdMap& fids1, const FaceIdMap& fids2, EdgeMarkMapBind& marks_on_input_edges, - const cpp11::array& output_vpms, + const VpmOutTuple& output_vpms, EdgeMarkMapTuple& out_edge_mark_maps, NewFaceVisitor& new_face_visitor, const cpp11::array< @@ -1191,12 +1205,12 @@ public: // should be done. First operations are those filling meshes // different from tm1 and tm2, then the one modifying tm1 and // finally the one modifying tm2. - std::vector out_of_place_operations; - Boolean_operation inplace_operation_tm1=NONE, - inplace_operation_tm2=NONE; + std::vector out_of_place_operations; + Boolean_operation_type inplace_operation_tm1=NONE, + inplace_operation_tm2=NONE; for (int i=0;i<4;++i) { - Boolean_operation operation=enum_cast(i); + Boolean_operation_type operation=enum_cast(i); if (!requested_output[operation] || impossible_operation.test(operation)) continue; @@ -1211,7 +1225,7 @@ public: } /// first handle operations in a mesh that is neither tm1 nor tm2 - BOOST_FOREACH(Boolean_operation operation, out_of_place_operations) + BOOST_FOREACH(Boolean_operation_type operation, out_of_place_operations) { TriangleMesh& output = *(*requested_output[operation]); CGAL_assertion(&tm1!=&output && &tm2!=&output); @@ -1228,20 +1242,24 @@ public: ); std::vector shared_edges; - fill_new_triangle_mesh( - output, - patches_of_tm1_used[operation], patches_of_tm2_used[operation], - patches_of_tm1, patches_of_tm2, - operation == TM2_MINUS_TM1, operation == TM1_MINUS_TM2, - polylines, - intersection_edges1, intersection_edges2, - vpm1, vpm2, *output_vpms[operation], - marks_on_input_edges.ecm1, - marks_on_input_edges.ecm2, - CGAL_COREF_SELECT_OUT_ECM(operation), - shared_edges, - new_face_visitor - ); + + #define CGAL_COREF_FUNCTION_CALL_DEF(BO_type) \ + fill_new_triangle_mesh( \ + output, \ + patches_of_tm1_used[BO_type], patches_of_tm2_used[BO_type], \ + patches_of_tm1, patches_of_tm2, \ + BO_type == TM2_MINUS_TM1, BO_type == TM1_MINUS_TM2, \ + polylines, \ + intersection_edges1, intersection_edges2, \ + vpm1, vpm2, *cpp11::get(output_vpms), \ + marks_on_input_edges.ecm1, \ + marks_on_input_edges.ecm2, \ + cpp11::get(out_edge_mark_maps), \ + shared_edges, \ + new_face_visitor \ + ) + CGAL_COREF_FUNCTION_CALL(operation) + #undef CGAL_COREF_FUNCTION_CALL_DEF mark_edges(out_edge_mark_maps, shared_edges, operation); } @@ -1306,36 +1324,44 @@ public: patches_of_tm1[i]; } // Operation in tm1: disconnect patches not use and append the one from tm2 - compute_inplace_operation_delay_removal_and_insideout( - tm1, - tm2, - patches_of_tm1_used[inplace_operation_tm1], - patches_of_tm2_used[inplace_operation_tm1], - patches_of_tm1, patches_of_tm2, - inplace_operation_tm1 == TM1_MINUS_TM2 || - inplace_operation_tm1 == TM2_MINUS_TM1, - polylines_in_tm1, - vpm1, vpm2, - marks_on_input_edges.ecm1, - marks_on_input_edges.ecm2, - CGAL_COREF_SELECT_OUT_ECM(inplace_operation_tm1), - disconnected_patches_edge_to_tm2_edge, - new_face_visitor); + #define CGAL_COREF_FUNCTION_CALL_DEF(BO_type)\ + compute_inplace_operation_delay_removal_and_insideout( \ + tm1, \ + tm2, \ + patches_of_tm1_used[BO_type], \ + patches_of_tm2_used[BO_type], \ + patches_of_tm1, patches_of_tm2, \ + BO_type == TM1_MINUS_TM2 || \ + BO_type == TM2_MINUS_TM1, \ + polylines_in_tm1, \ + vpm1, vpm2, \ + marks_on_input_edges.ecm1, \ + marks_on_input_edges.ecm2, \ + cpp11::get(out_edge_mark_maps), \ + disconnected_patches_edge_to_tm2_edge, \ + new_face_visitor) + CGAL_COREF_FUNCTION_CALL(inplace_operation_tm1) + #undef CGAL_COREF_FUNCTION_CALL_DEF // Operation in tm2: discard patches and append the one from tm2 CGAL_assertion( *requested_output[inplace_operation_tm2] == &tm2 ); - compute_inplace_operation( tm2, tm1, - patches_of_tm2_used[inplace_operation_tm2], - patches_of_tm1_used[inplace_operation_tm2], - patches_of_tm2, patches_of_tm1, - inplace_operation_tm2==TM1_MINUS_TM2, - inplace_operation_tm2==TM2_MINUS_TM1, - vpm2, - vpm1, - marks_on_input_edges.ecm2, - marks_on_input_edges.ecm1, - CGAL_COREF_SELECT_OUT_ECM(inplace_operation_tm2), - disconnected_patches_edge_to_tm2_edge, - new_face_visitor); + + #define CGAL_COREF_FUNCTION_CALL_DEF(BO_type)\ + compute_inplace_operation( tm2, tm1, \ + patches_of_tm2_used[BO_type], \ + patches_of_tm1_used[BO_type], \ + patches_of_tm2, patches_of_tm1, \ + BO_type==TM1_MINUS_TM2, \ + BO_type==TM2_MINUS_TM1, \ + vpm2, \ + vpm1, \ + marks_on_input_edges.ecm2, \ + marks_on_input_edges.ecm1, \ + cpp11::get(out_edge_mark_maps), \ + disconnected_patches_edge_to_tm2_edge, \ + new_face_visitor) + CGAL_COREF_FUNCTION_CALL(inplace_operation_tm2) + #undef CGAL_COREF_FUNCTION_CALL_DEF + // remove polylines only on the border of patches not kept in tm2 if (polylines_in_tm2.to_skip.any()) remove_unused_polylines(tm2, @@ -1348,8 +1374,12 @@ public: marks_on_input_edges.ecm1); // transfer marks of edges of patches kept to the output edge mark property - copy_edge_mark( - tm1, marks_on_input_edges.ecm1, CGAL_COREF_SELECT_OUT_ECM(inplace_operation_tm1)); + #define CGAL_COREF_FUNCTION_CALL_DEF(BO_type) \ + copy_edge_mark( \ + tm1, marks_on_input_edges.ecm1, \ + cpp11::get(out_edge_mark_maps)) + CGAL_COREF_FUNCTION_CALL(inplace_operation_tm1) + #undef CGAL_COREF_FUNCTION_CALL_DEF // remove polylines only on the border of patches not kept in tm1 if (polylines_in_tm1.to_skip.any()) @@ -1370,22 +1400,24 @@ public: patches_of_tm1_used[inplace_operation_tm1], patches_of_tm2_used[inplace_operation_tm1], fids1, fids2, tm1, tm2); - - compute_inplace_operation( - tm1, tm2, - patches_of_tm1_used[inplace_operation_tm1], - patches_of_tm2_used[inplace_operation_tm1], - patches_of_tm1, patches_of_tm2, - inplace_operation_tm1 == TM2_MINUS_TM1, - inplace_operation_tm1 == TM1_MINUS_TM2, - vpm1, - vpm2, - marks_on_input_edges.ecm1, - marks_on_input_edges.ecm2, - CGAL_COREF_SELECT_OUT_ECM(inplace_operation_tm1), - polylines, - new_face_visitor - ); + #define CGAL_COREF_FUNCTION_CALL_DEF(BO_type) \ + compute_inplace_operation( \ + tm1, tm2, \ + patches_of_tm1_used[BO_type], \ + patches_of_tm2_used[BO_type], \ + patches_of_tm1, patches_of_tm2, \ + BO_type == TM2_MINUS_TM1, \ + BO_type == TM1_MINUS_TM2, \ + vpm1, \ + vpm2, \ + marks_on_input_edges.ecm1, \ + marks_on_input_edges.ecm2, \ + cpp11::get(out_edge_mark_maps), \ + polylines, \ + new_face_visitor \ + ) + CGAL_COREF_FUNCTION_CALL(inplace_operation_tm1) + #undef CGAL_COREF_FUNCTION_CALL_DEF // remove polylines only on the border of patches not kept if (polylines.to_skip.any()) remove_unused_polylines(tm1, @@ -1411,21 +1443,22 @@ public: patches_of_tm1_used[inplace_operation_tm2], fids2, fids1, tm2, tm1 ); - - compute_inplace_operation( tm2, tm1, - patches_of_tm2_used[inplace_operation_tm2], - patches_of_tm1_used[inplace_operation_tm2], - patches_of_tm2, patches_of_tm1, - inplace_operation_tm2==TM1_MINUS_TM2, - inplace_operation_tm2==TM2_MINUS_TM1, - vpm2, - vpm1, - marks_on_input_edges.ecm2, - marks_on_input_edges.ecm1, - CGAL_COREF_SELECT_OUT_ECM(inplace_operation_tm2), - polylines, - new_face_visitor); - + #define CGAL_COREF_FUNCTION_CALL_DEF(BO_type) \ + compute_inplace_operation( tm2, tm1, \ + patches_of_tm2_used[BO_type], \ + patches_of_tm1_used[BO_type], \ + patches_of_tm2, patches_of_tm1, \ + BO_type==TM1_MINUS_TM2, \ + BO_type==TM2_MINUS_TM1, \ + vpm2, \ + vpm1, \ + marks_on_input_edges.ecm2, \ + marks_on_input_edges.ecm1, \ + cpp11::get(out_edge_mark_maps), \ + polylines, \ + new_face_visitor); + CGAL_COREF_FUNCTION_CALL(inplace_operation_tm2) + #undef CGAL_COREF_FUNCTION_CALL_DEF // remove polylines only on the border of patches not kept if (polylines.to_skip.any()) remove_unused_polylines(tm2, @@ -1438,6 +1471,5 @@ public: } } } // CGAL::Polygon_mesh_processing::Corefinement -#undef CGAL_COREF_SELECT_OUT_ECM - +#undef CGAL_COREF_FUNCTION_CALL #endif // CGAL_POLYGON_MESH_PROCESSING_INTERNAL_FACE_GRAPH_OUTPUT_BUILDER_H diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Corefinement/face_graph_utils.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Corefinement/face_graph_utils.h index 7c500fca27c..088aab857ed 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Corefinement/face_graph_utils.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Corefinement/face_graph_utils.h @@ -28,7 +28,9 @@ #include #include #include +#include #include +#include #include #include #include @@ -153,6 +155,71 @@ void copy_edge_mark(G&, No_mark&) {} // nothing to do +// Parts to get default property maps for output meshes based on the value type +// of input vertex point maps. +template +struct Dummy_default_vertex_point_map +{ + typedef vertex_descriptor key_type; + typedef Point_3 value_type; + typedef Point_3 reference; + typedef boost::read_write_property_map_tag category; + + inline friend + value_type + get(Dummy_default_vertex_point_map, key_type) + { + CGAL_assertion(false || + !"This property map should not be used." + "Check the value type of the output vpm vs that of input"); + return Point_3(); + } + + inline friend + void + put(Dummy_default_vertex_point_map, key_type, value_type) + { + CGAL_assertion(false || + !"This property map should not be used." + "Check the value type of the output vpm vs that of input"); + } +}; + +template +struct TweakedGetVertexPointMap +{ + typedef typename GetVertexPointMap::type Default_map; + typedef typename boost::is_same::value_type>::type Use_default_tag; + + typedef typename boost::mpl::if_< + Use_default_tag, + Default_map, + Dummy_default_vertex_point_map::vertex_descriptor > + >::type type; +}; + +template +boost::optional< typename TweakedGetVertexPointMap::type > +get_vpm(const NP& np, boost::optional opm, boost::true_type) +{ + if (boost::none == opm) return boost::none; + return boost::choose_param( + boost::get_param(np, internal_np::vertex_point), + get_property_map(boost::vertex_point, *(*opm)) ); +} + +template +boost::optional< typename TweakedGetVertexPointMap::type > +get_vpm(const NP&, boost::optional opm, boost::false_type) +{ + if (boost::none == opm) return boost::none; + return typename TweakedGetVertexPointMap::type(); +} +// + template struct Default_new_face_visitor{ typedef boost::graph_traits GT; @@ -501,6 +568,7 @@ template void import_polyline( PolygonMesh& output, @@ -516,7 +584,7 @@ void import_polyline( const IntersectionEdgeMap& intersection_edges2, const VertexPointMap& vpm1, const VertexPointMap& /*vpm2*/, - const VertexPointMap& vpm_out, + const VertexPointMapOut& vpm_out, std::vector ::edge_descriptor>& output_shared_edges) { @@ -713,6 +781,7 @@ template < bool reverse_patch_orientation, class TriangleMesh, class PatchContainer, class VertexPointMap, + class VertexPointMapOut, class EdgeMarkMapOut, class EdgeMarkMapIn , class NewFaceVisitor> @@ -720,7 +789,7 @@ void append_patches_to_triangle_mesh( TriangleMesh& output, const boost::dynamic_bitset<>& patches_to_append, PatchContainer& patches, - const VertexPointMap& vpm_out, + const VertexPointMapOut& vpm_out, const VertexPointMap& vpm_tm, EdgeMarkMapOut& edge_mark_map_out, const EdgeMarkMapIn& edge_mark_map_in, @@ -920,6 +989,7 @@ void append_patches_to_triangle_mesh( template < class TriangleMesh, class IntersectionEdgeMap, class VertexPointMap, + class VertexPointMapOut, class EdgeMarkMap1, class EdgeMarkMap2, class EdgeMarkMapOut, @@ -939,7 +1009,7 @@ void fill_new_triangle_mesh( const IntersectionEdgeMap& intersection_edges2, const VertexPointMap& vpm1, const VertexPointMap& vpm2, - const VertexPointMap& vpm_out, + const VertexPointMapOut& vpm_out, const EdgeMarkMap1& edge_mark_map1, const EdgeMarkMap2& edge_mark_map2, EdgeMarkMapOut& edge_mark_map_out, From f29d8339fafaa6a0704f07639a9e1c777b11b5e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Loriot?= Date: Mon, 28 May 2018 17:07:05 +0200 Subject: [PATCH 11/43] document corefine_and_compute_boolean_operations() --- Installation/CHANGES.md | 2 + .../PackageDescription.txt | 3 +- .../Polygon_mesh_processing.txt | 5 +- .../doc/Polygon_mesh_processing/examples.txt | 1 + .../Polygon_mesh_processing/CMakeLists.txt | 1 + ...refinement_mesh_union_and_intersection.cpp | 73 ++++++++++++++ .../Polygon_mesh_processing/corefinement.h | 98 ++++++++++++++++++- 7 files changed, 175 insertions(+), 8 deletions(-) create mode 100644 Polygon_mesh_processing/examples/Polygon_mesh_processing/corefinement_mesh_union_and_intersection.cpp diff --git a/Installation/CHANGES.md b/Installation/CHANGES.md index 6840262b9d8..a23a2bf0435 100644 --- a/Installation/CHANGES.md +++ b/Installation/CHANGES.md @@ -42,6 +42,8 @@ Release date: September 2018 (that replace the `bool` parameter in `corefine()`) that enables to check for self-intersections faces involved in the intersection before trying to corefine the input meshes. +- Document the function `corefine_and_compute_boolean_operations()` that can be used to + compute the result of several Boolean operations between 2 volumes at the same time. Release 4.12 ------------ diff --git a/Polygon_mesh_processing/doc/Polygon_mesh_processing/PackageDescription.txt b/Polygon_mesh_processing/doc/Polygon_mesh_processing/PackageDescription.txt index 80e65018910..9b648e4428c 100644 --- a/Polygon_mesh_processing/doc/Polygon_mesh_processing/PackageDescription.txt +++ b/Polygon_mesh_processing/doc/Polygon_mesh_processing/PackageDescription.txt @@ -145,6 +145,7 @@ and provides a list of the parameters that are used in this package. - `CGAL::Polygon_mesh_processing::corefine_and_compute_union()` - `CGAL::Polygon_mesh_processing::corefine_and_compute_difference()` - `CGAL::Polygon_mesh_processing::corefine_and_compute_intersection()` +- `CGAL::Polygon_mesh_processing::corefine_and_compute_boolean_operations()` - `CGAL::Polygon_mesh_processing::corefine()` - `CGAL::Polygon_mesh_processing::surface_intersection()` - `CGAL::Polygon_mesh_processing::does_bound_a_volume()` @@ -178,6 +179,4 @@ and provides a list of the parameters that are used in this package. - `CGAL::Polygon_mesh_processing::border_halfedges()` - `CGAL::Polygon_mesh_processing::transform()` -\todo add `clip_triangle_mesh()` using the code of `Polyhedron_place_clipping.h` and `PMP::corefine()` -\todo fix and restore remove_degenerate_faces in the user and the reference manual */ 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 de632eeadf7..d56fdd94238 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 @@ -215,9 +215,10 @@ Considering two triangulated surface meshes, each bounding a volume, the functions `CGAL::Polygon_mesh_processing::corefine_and_compute_union()`, `CGAL::Polygon_mesh_processing::corefine_and_compute_intersection()` and `CGAL::Polygon_mesh_processing::corefine_and_compute_difference()` respectively compute the union, -the intersection and the difference of the two volumes. -Note that there is no restriction on the topology of the input volumes. +the intersection and the difference of the two volumes. If several Boolean operations must be +computed at the same type, the function `corefine_and_compute_boolean_operations()` should be used. +There is no restriction on the topology of the input volumes. However, there are some requirements on the input to guarantee that the operation is possible. First, the input meshes must not self-intersect. Second, the operation is possible only if the output diff --git a/Polygon_mesh_processing/doc/Polygon_mesh_processing/examples.txt b/Polygon_mesh_processing/doc/Polygon_mesh_processing/examples.txt index c7a96cf3ec8..2811a37ecef 100644 --- a/Polygon_mesh_processing/doc/Polygon_mesh_processing/examples.txt +++ b/Polygon_mesh_processing/doc/Polygon_mesh_processing/examples.txt @@ -18,6 +18,7 @@ \example Poisson_surface_reconstruction_3/poisson_reconstruction_example.cpp \example Polygon_mesh_processing/corefinement_difference_remeshed.cpp \example Polygon_mesh_processing/corefinement_mesh_union.cpp +\example Polygon_mesh_processing/corefinement_mesh_union_and_intersection.cpp \example Polygon_mesh_processing/corefinement_consecutive_bool_op.cpp \example Polygon_mesh_processing/detect_features_example.cpp */ diff --git a/Polygon_mesh_processing/examples/Polygon_mesh_processing/CMakeLists.txt b/Polygon_mesh_processing/examples/Polygon_mesh_processing/CMakeLists.txt index a83d693ab22..ed6a9a6e8f9 100644 --- a/Polygon_mesh_processing/examples/Polygon_mesh_processing/CMakeLists.txt +++ b/Polygon_mesh_processing/examples/Polygon_mesh_processing/CMakeLists.txt @@ -97,6 +97,7 @@ create_single_source_cgal_program( "corefinement_SM.cpp") create_single_source_cgal_program( "corefinement_consecutive_bool_op.cpp" ) create_single_source_cgal_program( "corefinement_difference_remeshed.cpp" ) create_single_source_cgal_program( "corefinement_mesh_union.cpp" ) +create_single_source_cgal_program( "corefinement_mesh_union_and_intersection.cpp" ) create_single_source_cgal_program( "corefinement_mesh_union_with_attributes.cpp" ) create_single_source_cgal_program( "corefinement_polyhedron_union.cpp" ) create_single_source_cgal_program( "random_perturbation_SM_example.cpp" ) diff --git a/Polygon_mesh_processing/examples/Polygon_mesh_processing/corefinement_mesh_union_and_intersection.cpp b/Polygon_mesh_processing/examples/Polygon_mesh_processing/corefinement_mesh_union_and_intersection.cpp new file mode 100644 index 00000000000..1ad2772bb91 --- /dev/null +++ b/Polygon_mesh_processing/examples/Polygon_mesh_processing/corefinement_mesh_union_and_intersection.cpp @@ -0,0 +1,73 @@ +#include +#include + +#include + +#include + +typedef CGAL::Exact_predicates_inexact_constructions_kernel K; +typedef CGAL::Surface_mesh Mesh; + +namespace PMP = CGAL::Polygon_mesh_processing; +namespace params = CGAL::Polygon_mesh_processing::parameters; + +int main(int argc, char* argv[]) +{ + const char* filename1 = (argc > 1) ? argv[1] : "data/blobby.off"; + const char* filename2 = (argc > 2) ? argv[2] : "data/eight.off"; + std::ifstream input(filename1); + + Mesh mesh1, mesh2; + if (!input || !(input >> mesh1)) + { + std::cerr << "First mesh is not a valid off file." << std::endl; + return 1; + } + input.close(); + input.open(filename2); + if (!input || !(input >> mesh2)) + { + std::cerr << "Second mesh is not a valid off file." << std::endl; + return 1; + } + + Mesh out_union, out_intersection; + CGAL::cpp11::array, 4> output; + output[PMP::Corefinement::UNION] = &out_union; + output[PMP::Corefinement::INTERSECTION] = &out_intersection; + + // for the example, we explicit the named parameters, this is identical to + // PMP::corefine_and_compute_boolean_operations(mesh1, mesh2, output) + CGAL::cpp11::array res = + PMP::corefine_and_compute_boolean_operations( + mesh1, mesh2, + output, + params::all_default(), // mesh1 named parameters + params::all_default(), // mesh2 named parameters + CGAL::cpp11::make_tuple( + params::vertex_point_map(get(boost::vertex_point, out_union)), // named parameters for out_union + params::vertex_point_map(get(boost::vertex_point, out_intersection)), // named parameters for out_intersection + params::all_default(), // named parameters for mesh1-mesh2 not used + params::all_default() )// named parameters for mesh2-mesh1 not used) + ); + + if (res[PMP::Corefinement::UNION]) + { + std::cout << "Union was successfully computed\n"; + std::ofstream output("union.off"); + output << out_union; + } + else + std::cout << "Union could not be computed\n"; + + if (res[PMP::Corefinement::INTERSECTION]) + { + std::cout << "Intersection was successfully computed\n"; + std::ofstream output("intersection.off"); + output << out_intersection; + } + else + std::cout << "Intersection could not be computed\n"; + + return 1; +} diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/corefinement.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/corefinement.h index 8e44c7a54e7..06a79da2258 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/corefinement.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/corefinement.h @@ -142,8 +142,15 @@ namespace Corefinement */ template struct Default_new_face_visitor; -} +#ifdef DOXYGEN_RUNNING +/** \ingroup PMP_corefinement_grp + * Integer identifiers to refer to a particular Boolean operation in the function `corefine_and_compute_boolean_operations()`. + */ +enum Boolean_operation_type {UNION = 0, INTERSECTION=1, + TM1_MINUS_TM2=2, TM2_MINUS_TM1=3, NONE }; +#endif +} /** \ingroup PMP_corefinement_grp * @@ -241,6 +248,7 @@ bool does_bound_a_volume(const TriangleMesh& tm) { return does_bound_a_volume(tm, parameters::all_default()); } +/// \endcond #define CGAL_COREF_SET_OUTPUT_EDGE_MARK_MAP(I) \ typedef typename boost::lookup_named_param_def < \ @@ -254,8 +262,90 @@ bool does_bound_a_volume(const TriangleMesh& tm) /** - \todo document me - */ + * \ingroup PMP_corefinement_grp + * \link coref_def_subsec corefines \endlink `tm1` and `tm2` and for each triangle mesh `tm_out` passed + * as an optional in `output` different from `boost::none`, the triangulated surface mesh + * \link coref_def_subsec bounding \endlink the result of a particular Boolean operation + * between the volumes bounded by `tm1` and `tm2` will be put in the corresponding triangle mesh. + * The position of the meshes in the array `output` are specific to the Boolean operation to compute + * and `Corefinement::Boolean_operation_type` encodes and describes the ordering. Constructing the default array + * means that no Boolean operation will be done. Overwritting a default value will trigger the corresponding + * operation. In such a case, the address to a valid surface mesh must be provided. + * The optional named parameters for all output meshes are provided as a `tuple` and follow the same + * order as the array `output`. A call to `corefine_and_compute_boolean_operations()` with optional + * named parameters passed for output meshes should be done using `make_tuple()` as the types of + * named parameters is unspecified. + * + * If `tm1` and/or `tm2` are one of the output surface meshes, they will be updated to + * contain the output (in-place operation), in any other case, the corresponding result will + * be inserted into the mesh without clearing it first. + * \pre \link CGAL::Polygon_mesh_processing::does_self_intersect() `!CGAL::Polygon_mesh_processing::does_self_intersect(tm1)` \endlink + * \pre \link CGAL::Polygon_mesh_processing::does_self_intersect() `!CGAL::Polygon_mesh_processing::does_self_intersect(tm2)` \endlink + * \pre \link CGAL::Polygon_mesh_processing::does_bound_a_volume() `CGAL::Polygon_mesh_processing::does_bound_a_volume(tm1)` \endlink + * \pre \link CGAL::Polygon_mesh_processing::does_bound_a_volume() `CGAL::Polygon_mesh_processing::does_bound_a_volume(tm2)` \endlink + * + * @tparam TriangleMesh a model of `MutableFaceGraph`, `HalfedgeListGraph` and `FaceListGraph`. + * If `TriangleMesh` has an internal property map for `CGAL::face_index_t`, + * as a named parameter, then it must be initialized. + * + * @tparam NamedParameters1 a sequence of \ref pmp_namedparameters "Named Parameters" + * @tparam NamedParameters2 a sequence of \ref pmp_namedparameters "Named Parameters" + * @tparam NamedParametersOut0 a sequence of \ref pmp_namedparameters "Named Parameters" for computing the union of the volumes bounded by `tm1` and `tm2` + * @tparam NamedParametersOut1 a sequence of \ref pmp_namedparameters "Named Parameters" for computing the intersection of the volumes bounded by `tm1` and `tm2` + * @tparam NamedParametersOut2 a sequence of \ref pmp_namedparameters "Named Parameters" for computing the difference of the volumes bounded by `tm1` and `tm2` + * @tparam NamedParametersOut3 a sequence of \ref pmp_namedparameters "Named Parameters" for computing the difference of the volumes bounded by `tm2` and `tm1` + * + * @param tm1 first input triangulated surface mesh + * @param tm2 second input triangulated surface mesh + * @param output an array of output surface meshes + * @param np1 optional sequence of \ref pmp_namedparameters "Named Parameters" among the ones listed below + * @param np2 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 `tm1` (`tm2`). + * If this parameter is omitted, an internal property map for + * `CGAL::vertex_point_t` should be available in `TriangleMesh` + * \cgalParamEnd + * \cgalParamBegin{edge_is_constrained_map} a property map containing the + * constrained-or-not status of each edge of `tm1` (`tm2`). + * \cgalParamEnd + * \cgalParamBegin{face_index_map} a property map containing the index of each face of `tm1` (`tm2`). + * Note that if the property map is writable, the indices of the faces + * of `tm1` and `tm2` will be set after the corefinement is done. + * \cgalParamEnd + * \cgalParamBegin{new_face_visitor} a class model of `PMPCorefinementNewFaceVisitor` + * that is used to track the creation of new faces (`np1` only) + * \cgalParamEnd + * \cgalParamBegin{throw_on_self_intersection} if `true`, for each input triangle mesh, + * the set of triangles closed to the intersection of `tm1` and `tm2` will be + * checked for self-intersection and `CGAL::Polygon_mesh_processing::Corefinement::Self_intersection_exception` + * will be thrown if at least one is found (`np1` only). + * \cgalParamEnd + * \cgalNamedParamsEnd + * + * @param nps_out tuple of optional sequences of \ref pmp_namedparameters "Named Parameters" each among the ones listed below + * (`tm_out` being use to refer the output surface mesh in `output` corresponding to a given named parameter sequence) + * + * \cgalNamedParamsBegin + * \cgalParamBegin{vertex_point_map} + * the property map with the points associated to the vertices of `tm_out`. + * If this parameter is omitted, an internal property map for + * `CGAL::vertex_point_t` should be available in `TriangleMesh` + * \cgalParamEnd + * \cgalParamBegin{edge_is_constrained_map} a property map containing the + * constrained-or-not status of each edge of `tm_out`. An edge of `tm_out` is constrained + * if it is on the intersection of `tm1` and `tm2`, or if the edge corresponds to a + * constrained edge in `tm1` or `tm2`. + * \cgalParamEnd + * \cgalNamedParamsEnd + * + * @return an array filled as follow: for each operation computed, the position in the array + * will contains `true` iff the output surface mesh is manifold, and it is put in the surface mesh + * at the same position in `output`. Note that if a output surface mesh was also + * an input mesh but the output operation was generating a non-manifold mesh, the surface mesh + * will only be corefined. + */ template Date: Mon, 28 May 2018 17:33:02 +0200 Subject: [PATCH 12/43] fix typos --- .../CGAL/Polygon_mesh_processing/corefinement.h | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/corefinement.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/corefinement.h index 06a79da2258..7b7064e90b5 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/corefinement.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/corefinement.h @@ -269,12 +269,12 @@ bool does_bound_a_volume(const TriangleMesh& tm) * between the volumes bounded by `tm1` and `tm2` will be put in the corresponding triangle mesh. * The position of the meshes in the array `output` are specific to the Boolean operation to compute * and `Corefinement::Boolean_operation_type` encodes and describes the ordering. Constructing the default array - * means that no Boolean operation will be done. Overwritting a default value will trigger the corresponding + * means that no Boolean operation will be done. Overwriting a default value will trigger the corresponding * operation. In such a case, the address to a valid surface mesh must be provided. * The optional named parameters for all output meshes are provided as a `tuple` and follow the same * order as the array `output`. A call to `corefine_and_compute_boolean_operations()` with optional * named parameters passed for output meshes should be done using `make_tuple()` as the types of - * named parameters is unspecified. + * named parameters are unspecified. * * If `tm1` and/or `tm2` are one of the output surface meshes, they will be updated to * contain the output (in-place operation), in any other case, the corresponding result will @@ -340,9 +340,9 @@ bool does_bound_a_volume(const TriangleMesh& tm) * \cgalParamEnd * \cgalNamedParamsEnd * - * @return an array filled as follow: for each operation computed, the position in the array - * will contains `true` iff the output surface mesh is manifold, and it is put in the surface mesh - * at the same position in `output`. Note that if a output surface mesh was also + * @return an array filled as follows: for each operation computed, the position in the array + * will contain `true` iff the output surface mesh is manifold, and it is put in the surface mesh + * at the same position in `output`. Note that if an output surface mesh was also * an input mesh but the output operation was generating a non-manifold mesh, the surface mesh * will only be corefined. */ From ac566df9ba31b313e5b45d6cb4b747ee59506774 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Loriot?= Date: Wed, 30 May 2018 08:34:08 +0200 Subject: [PATCH 13/43] use Boolean_property_map --- .../Polygon_mesh_processing/internal/clip.h | 36 ++----------------- 1 file changed, 2 insertions(+), 34 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/clip.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/clip.h index f8613723d5d..5376bd43ff5 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/clip.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/clip.h @@ -128,39 +128,6 @@ clip_open_impl( TriangleMesh& tm, return true; } -/// \TODO move this to property_map.h? -template -struct Constrained_edge_map -{ - typedef boost::read_write_property_map_tag category; - typedef bool value_type; - typedef bool reference; - typedef typename Set::value_type key_type; - - Constrained_edge_map() - : edge_set(NULL) - {} - - Constrained_edge_map(Set& set) - : edge_set(&set) - {} - - friend bool get(const Constrained_edge_map& map, key_type k) - { - return map.edge_set->count(k)!=0; - } - - friend void put(Constrained_edge_map& map, key_type k, bool b) - { - if (b) - map.edge_set->insert(k); - else - map.edge_set->erase(k); - } -private: - Set* edge_set; -}; - template @@ -174,7 +141,7 @@ clip_open_impl( TriangleMesh& tm, typedef typename boost::graph_traits ::edge_descriptor edge_descriptor; boost::unordered_set constrained_edges; - Constrained_edge_map > + Boolean_property_map > cst_map(constrained_edges); return clip_open_impl(tm, clipper, @@ -210,6 +177,7 @@ clip( TriangleMesh& tm, } /// \todo document me +/// \todo remove convex_hull_3 template From cd1e5126e621396bd5687c02fc49c9c4787bc6c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Loriot?= Date: Wed, 30 May 2018 10:50:52 +0200 Subject: [PATCH 14/43] add missing convenience overloads --- .../Polygon_mesh_processing/corefinement.h | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/corefinement.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/corefinement.h index 7b7064e90b5..3795803f98b 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/corefinement.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/corefinement.h @@ -550,6 +550,37 @@ corefine_and_compute_boolean_operations( all_default(), all_default())); } +template +cpp11::array +corefine_and_compute_boolean_operations( + TriangleMesh& tm1, + TriangleMesh& tm2, + const cpp11::array< boost::optional,4>& output, + const NamedParameters1& np1) +{ + using namespace CGAL::Polygon_mesh_processing::parameters; + return corefine_and_compute_boolean_operations(tm1, tm2, output, + np1, all_default(), + cpp11::make_tuple(all_default(), all_default(), + all_default(), all_default())); +} + +template +cpp11::array +corefine_and_compute_boolean_operations( + TriangleMesh& tm1, + TriangleMesh& tm2, + const cpp11::array< boost::optional,4>& output, + const NamedParameters1& np1, + const NamedParameters2& np2) +{ + using namespace CGAL::Polygon_mesh_processing::parameters; + return corefine_and_compute_boolean_operations(tm1, tm2, output, + np1, np2, + cpp11::make_tuple(all_default(), all_default(), + all_default(), all_default())); +} + #undef CGAL_COREF_SET_OUTPUT_VERTEX_POINT_MAP #undef CGAL_COREF_SET_OUTPUT_EDGE_MARK_MAP From 1a5b2c78a52c83a7d218114d242dd96ef168a57c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Loriot?= Date: Fri, 1 Jun 2018 11:00:13 +0200 Subject: [PATCH 15/43] update doc of face index map and qualify boost np functions --- .../Polygon_mesh_processing/corefinement.h | 56 ++++++++----------- 1 file changed, 24 insertions(+), 32 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/corefinement.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/corefinement.h index 3795803f98b..d3619fa91e5 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/corefinement.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/corefinement.h @@ -158,8 +158,6 @@ enum Boolean_operation_type {UNION = 0, INTERSECTION=1, * See \ref coref_def_subsec for details. * * @tparam TriangleMesh a model of `MutableFaceGraph`, `HalfedgeListGraph` and `FaceListGraph`. - * If `TriangleMesh` has an internal property map for `CGAL::face_index_t`, - * as a named parameter, then it must be initialized. * @tparam NamedParameters a sequence of \ref pmp_namedparameters "Named Parameters" * * @param tm a closed triangulated surface mesh @@ -195,10 +193,10 @@ bool does_bound_a_volume(const TriangleMesh& tm, const NamedParameters& np) if (!is_closed(tm)) return false; if (!is_triangle_mesh(tm)) return false; - Vpm vpm = boost::choose_param(get_param(np, internal_np::vertex_point), + Vpm vpm = boost::choose_param(boost::get_param(np, internal_np::vertex_point), get_const_property_map(boost::vertex_point, tm)); - Fid_map fid_map = boost::choose_param(get_param(np, internal_np::face_index), + Fid_map fid_map = boost::choose_param(boost::get_param(np, internal_np::face_index), get_const_property_map(boost::face_index, tm)); std::vector face_cc(num_faces(tm), std::size_t(-1)); @@ -257,7 +255,7 @@ bool does_bound_a_volume(const TriangleMesh& tm) Corefinement::No_mark \ > ::type Ecm_out_##I; \ Ecm_out_##I ecm_out_##I = \ - boost::choose_param( get_param(cpp11::get(nps_out), internal_np::edge_is_constrained), \ + boost::choose_param( boost::get_param(cpp11::get(nps_out), internal_np::edge_is_constrained), \ Corefinement::No_mark() ); @@ -284,10 +282,7 @@ bool does_bound_a_volume(const TriangleMesh& tm) * \pre \link CGAL::Polygon_mesh_processing::does_bound_a_volume() `CGAL::Polygon_mesh_processing::does_bound_a_volume(tm1)` \endlink * \pre \link CGAL::Polygon_mesh_processing::does_bound_a_volume() `CGAL::Polygon_mesh_processing::does_bound_a_volume(tm2)` \endlink * - * @tparam TriangleMesh a model of `MutableFaceGraph`, `HalfedgeListGraph` and `FaceListGraph`. - * If `TriangleMesh` has an internal property map for `CGAL::face_index_t`, - * as a named parameter, then it must be initialized. - * + * @tparam TriangleMesh a model of `MutableFaceGraph`, `HalfedgeListGraph` and `FaceListGraph` * @tparam NamedParameters1 a sequence of \ref pmp_namedparameters "Named Parameters" * @tparam NamedParameters2 a sequence of \ref pmp_namedparameters "Named Parameters" * @tparam NamedParametersOut0 a sequence of \ref pmp_namedparameters "Named Parameters" for computing the union of the volumes bounded by `tm1` and `tm2` @@ -366,7 +361,7 @@ corefine_and_compute_boolean_operations( NamedParametersOut3>& nps_out) { const bool throw_on_self_intersection = - boost::choose_param(get_param(np1, internal_np::throw_on_self_intersection), false); + boost::choose_param(boost::get_param(np1, internal_np::throw_on_self_intersection), false); // Vertex point maps //for input meshes @@ -379,10 +374,10 @@ corefine_and_compute_boolean_operations( static const bool same_vpm = (boost::is_same::value); ) CGAL_static_assertion(same_vpm); - Vpm vpm1 = boost::choose_param(get_param(np1, internal_np::vertex_point), + Vpm vpm1 = boost::choose_param(boost::get_param(np1, internal_np::vertex_point), get_property_map(boost::vertex_point, tm1)); - Vpm vpm2 = boost::choose_param(get_param(np2, internal_np::vertex_point), + Vpm vpm2 = boost::choose_param(boost::get_param(np2, internal_np::vertex_point), get_property_map(boost::vertex_point, tm2)); typedef typename boost::property_traits::value_type Point_3; @@ -493,9 +488,9 @@ corefine_and_compute_boolean_operations( static const bool same_fidmap = (boost::is_same::value);) CGAL_static_assertion(same_fidmap); - Fid_map fid_map1 = boost::choose_param(get_param(np1, internal_np::face_index), + Fid_map fid_map1 = boost::choose_param(boost::get_param(np1, internal_np::face_index), get_property_map(boost::face_index, tm1)); - Fid_map fid_map2 = boost::choose_param(get_param(np2, internal_np::face_index), + Fid_map fid_map2 = boost::choose_param(boost::get_param(np2, internal_np::face_index), get_property_map(boost::face_index, tm2)); // New face visitor typedef typename boost::lookup_named_param_def < @@ -503,7 +498,7 @@ corefine_and_compute_boolean_operations( NamedParameters1, Corefinement::Default_new_face_visitor//default > ::type Nfv; - Nfv nfv( boost::choose_param( get_param(np1, internal_np::new_face_visitor), + Nfv nfv( boost::choose_param( boost::get_param(np1, internal_np::new_face_visitor), Corefinement::Default_new_face_visitor() ) ); // surface intersection algorithm call @@ -598,10 +593,7 @@ corefine_and_compute_boolean_operations( * \pre \link CGAL::Polygon_mesh_processing::does_bound_a_volume() `CGAL::Polygon_mesh_processing::does_bound_a_volume(tm1)` \endlink * \pre \link CGAL::Polygon_mesh_processing::does_bound_a_volume() `CGAL::Polygon_mesh_processing::does_bound_a_volume(tm2)` \endlink * - * @tparam TriangleMesh a model of `MutableFaceGraph`, `HalfedgeListGraph` and `FaceListGraph`. - * If `TriangleMesh` has an internal property map for `CGAL::face_index_t`, - * as a named parameter, then it must be initialized. - * + * @tparam TriangleMesh a model of `MutableFaceGraph`, `HalfedgeListGraph` and `FaceListGraph` * @tparam NamedParameters1 a sequence of \ref pmp_namedparameters "Named Parameters" * @tparam NamedParameters2 a sequence of \ref pmp_namedparameters "Named Parameters" * @tparam NamedParametersOut a sequence of \ref pmp_namedparameters "Named Parameters" @@ -792,7 +784,7 @@ corefine_and_compute_difference( TriangleMesh& tm1, const NamedParameters2& np2) { const bool throw_on_self_intersection = - boost::choose_param(get_param(np1, internal_np::throw_on_self_intersection), false); + boost::choose_param(boost::get_param(np1, internal_np::throw_on_self_intersection), false); // Vertex point maps typedef typename GetVertexPointMap::value);) CGAL_static_assertion(same_vpm); - Vpm vpm1 = boost::choose_param(get_param(np1, internal_np::vertex_point), + Vpm vpm1 = boost::choose_param(boost::get_param(np1, internal_np::vertex_point), get_property_map(boost::vertex_point, tm1)); - Vpm vpm2 = boost::choose_param(get_param(np2, internal_np::vertex_point), + Vpm vpm2 = boost::choose_param(boost::get_param(np2, internal_np::vertex_point), get_property_map(boost::vertex_point, tm2)); // Edge is-constrained maps @@ -823,9 +815,9 @@ corefine_and_compute_difference( TriangleMesh& tm1, Corefinement::No_mark//default > ::type Ecm2; - Ecm1 ecm1 = boost::choose_param( get_param(np1, internal_np::edge_is_constrained), + Ecm1 ecm1 = boost::choose_param( boost::get_param(np1, internal_np::edge_is_constrained), Corefinement::No_mark() ); - Ecm2 ecm2 = boost::choose_param( get_param(np2, internal_np::edge_is_constrained), + Ecm2 ecm2 = boost::choose_param( boost::get_param(np2, internal_np::edge_is_constrained), Corefinement::No_mark() ); typedef Corefinement::Ecm_bind Ecm; @@ -843,7 +835,7 @@ corefine_and_compute_difference( TriangleMesh& tm1, NamedParameters1, Corefinement::Default_new_face_visitor//default > ::type Nfv; - Nfv nfv( boost::choose_param( get_param(np1, internal_np::new_face_visitor), + Nfv nfv( boost::choose_param( boost::get_param(np1, internal_np::new_face_visitor), Corefinement::Default_new_face_visitor() ) ); // surface intersection algorithm call @@ -897,7 +889,7 @@ namespace experimental { typedef typename GetVertexPointMap::type Vpm; - Vpm vpm = boost::choose_param(get_param(np, internal_np::vertex_point), + Vpm vpm = boost::choose_param(boost::get_param(np, internal_np::vertex_point), get_property_map(boost::vertex_point, tm)); // Edge is-constrained maps @@ -908,7 +900,7 @@ namespace experimental { > ::type Ecm; - Ecm ecm = boost::choose_param( get_param(np, internal_np::edge_is_constrained), + Ecm ecm = boost::choose_param( boost::get_param(np, internal_np::edge_is_constrained), Corefinement::No_mark() ); // New face visitor @@ -917,7 +909,7 @@ namespace experimental { NamedParameters, Corefinement::Default_new_face_visitor//default > ::type Nfv; - Nfv nfv( boost::choose_param( get_param(np, internal_np::new_face_visitor), + Nfv nfv( boost::choose_param( boost::get_param(np, internal_np::new_face_visitor), Corefinement::Default_new_face_visitor() ) ); @@ -974,12 +966,12 @@ namespace experimental { // Vertex point maps typedef typename GetVertexPointMap::type Vpm; - Vpm vpm = boost::choose_param(get_param(np, internal_np::vertex_point), + Vpm vpm = boost::choose_param(boost::get_param(np, internal_np::vertex_point), get_property_map(boost::vertex_point, tm)); // Face index map typedef typename GetFaceIndexMap::type Fid_map; - Fid_map fid_map = boost::choose_param(get_param(np, internal_np::face_index), + Fid_map fid_map = boost::choose_param(boost::get_param(np, internal_np::face_index), get_property_map(boost::face_index, tm)); // Edge is-constrained maps typedef typename boost::lookup_named_param_def < @@ -987,7 +979,7 @@ namespace experimental { NamedParameters, Corefinement::No_mark//default > ::type Ecm; - Ecm ecm = boost::choose_param( get_param(np, internal_np::edge_is_constrained), + Ecm ecm = boost::choose_param( boost::get_param(np, internal_np::edge_is_constrained), Corefinement::No_mark() ); // New face visitor typedef typename boost::lookup_named_param_def < @@ -995,7 +987,7 @@ namespace experimental { NamedParameters, Corefinement::Default_new_face_visitor//default > ::type Nfv; - Nfv nfv( boost::choose_param( get_param(np, internal_np::new_face_visitor), + Nfv nfv( boost::choose_param( boost::get_param(np, internal_np::new_face_visitor), Corefinement::Default_new_face_visitor() ) ); // surface intersection algorithm call From 94a73232d5febf05326698de4bc00816c9e8db08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Loriot?= Date: Fri, 1 Jun 2018 11:06:29 +0200 Subject: [PATCH 16/43] document clip functions --- .../CGAL/boost/graph/parameters_interface.h | 2 + BGL/test/BGL/test_cgal_bgl_named_params.cpp | 6 + Installation/CHANGES.md | 2 + .../NamedParameters.txt | 15 + .../PackageDescription.txt | 1 + .../CGAL/Polygon_mesh_processing/clip.h | 454 ++++++++++++++++++ .../Polygon_mesh_processing/internal/clip.h | 339 ------------- .../Polygon_mesh_processing/test_pmp_clip.cpp | 14 +- .../Clip_polyhedron_plugin.cpp | 16 +- 9 files changed, 498 insertions(+), 351 deletions(-) create mode 100644 Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/clip.h delete mode 100644 Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/clip.h diff --git a/BGL/include/CGAL/boost/graph/parameters_interface.h b/BGL/include/CGAL/boost/graph/parameters_interface.h index ca7a1feed3e..5279c3be58b 100644 --- a/BGL/include/CGAL/boost/graph/parameters_interface.h +++ b/BGL/include/CGAL/boost/graph/parameters_interface.h @@ -66,6 +66,8 @@ CGAL_add_named_parameter(overlap_test_t, overlap_test, do_overlap_test_of_bounde CGAL_add_named_parameter(preserve_genus_t, preserve_genus, preserve_genus) CGAL_add_named_parameter(new_face_visitor_t, new_face_visitor, new_face_visitor) CGAL_add_named_parameter(throw_on_self_intersection_t, throw_on_self_intersection, throw_on_self_intersection) +CGAL_add_named_parameter(close_volumes_t, close_volumes, close_volumes) +CGAL_add_named_parameter(include_clipper_boundary_t, include_clipper_boundary, include_clipper_boundary) // List of named parameters that we use in the package 'Surface Mesh Simplification' CGAL_add_named_parameter(get_cost_policy_t, get_cost_policy, get_cost) diff --git a/BGL/test/BGL/test_cgal_bgl_named_params.cpp b/BGL/test/BGL/test_cgal_bgl_named_params.cpp index 1efa27797da..df1eaab6034 100644 --- a/BGL/test/BGL/test_cgal_bgl_named_params.cpp +++ b/BGL/test/BGL/test_cgal_bgl_named_params.cpp @@ -74,6 +74,8 @@ void test(const NamedParameters& np) assert(get_param(np, CGAL::internal_np::nb_points_per_distance_unit).v == 33); assert(get_param(np, CGAL::internal_np::new_face_visitor).v == 42); assert(get_param(np, CGAL::internal_np::throw_on_self_intersection).v == 43); + assert(get_param(np, CGAL::internal_np::close_volumes).v == 44); + assert(get_param(np, CGAL::internal_np::include_clipper_boundary).v == 45); // Named parameters that we use in the package 'Surface Mesh Simplification' assert(get_param(np, CGAL::internal_np::get_cost_policy).v == 34); @@ -140,6 +142,8 @@ void test(const NamedParameters& np) check_same_type<33>(get_param(np, CGAL::internal_np::nb_points_per_distance_unit)); check_same_type<42>(get_param(np, CGAL::internal_np::new_face_visitor)); check_same_type<43>(get_param(np, CGAL::internal_np::throw_on_self_intersection)); + check_same_type<44>(get_param(np, CGAL::internal_np::close_volumes)); + check_same_type<45>(get_param(np, CGAL::internal_np::include_clipper_boundary)); // Named parameters that we use in the package 'Surface Mesh Simplification' check_same_type<34>(get_param(np, CGAL::internal_np::get_cost_policy)); @@ -205,6 +209,8 @@ int main() .verbosity_level(A<41>(41)) .new_face_visitor(A<42>(42)) .throw_on_self_intersection(A<43>(43)) + .close_volumes(A<44>(44)) + .include_clipper_boundary(A<45>(45)) ); return EXIT_SUCCESS; diff --git a/Installation/CHANGES.md b/Installation/CHANGES.md index a23a2bf0435..0acd7bfa72f 100644 --- a/Installation/CHANGES.md +++ b/Installation/CHANGES.md @@ -44,6 +44,8 @@ Release date: September 2018 input meshes. - Document the function `corefine_and_compute_boolean_operations()` that can be used to compute the result of several Boolean operations between 2 volumes at the same time. +- Document the function `clip()` that can be used to clip a triangulated surface mesh + by a plane or a clipping volume. Release 4.12 ------------ diff --git a/Polygon_mesh_processing/doc/Polygon_mesh_processing/NamedParameters.txt b/Polygon_mesh_processing/doc/Polygon_mesh_processing/NamedParameters.txt index 41be61c81be..fe9d89f2a81 100644 --- a/Polygon_mesh_processing/doc/Polygon_mesh_processing/NamedParameters.txt +++ b/Polygon_mesh_processing/doc/Polygon_mesh_processing/NamedParameters.txt @@ -327,6 +327,21 @@ and make the operation impossible with the current version of the code. \b Default value is `false` \cgalNPEnd +\cgalNPBegin{close_volumes} \anchor PMP_close_volumes +Parameter used in `clip()` functions to clip a volume rather than a surface. +\n +\b Type : `bool` \n +\b Default value is `false` +\cgalNPEnd + +\cgalNPBegin{include_clipper_boundary} \anchor PMP_include_clipper_boundary +Parameter used in `clip()` functions to indicate whether the boundary of the clipper +should be considered as part of the volume or not. +\n +\b Type : `bool` \n +\b Default value is `true` +\cgalNPEnd + \cgalNPTableEnd diff --git a/Polygon_mesh_processing/doc/Polygon_mesh_processing/PackageDescription.txt b/Polygon_mesh_processing/doc/Polygon_mesh_processing/PackageDescription.txt index 9b648e4428c..92f4874bca2 100644 --- a/Polygon_mesh_processing/doc/Polygon_mesh_processing/PackageDescription.txt +++ b/Polygon_mesh_processing/doc/Polygon_mesh_processing/PackageDescription.txt @@ -148,6 +148,7 @@ and provides a list of the parameters that are used in this package. - `CGAL::Polygon_mesh_processing::corefine_and_compute_boolean_operations()` - `CGAL::Polygon_mesh_processing::corefine()` - `CGAL::Polygon_mesh_processing::surface_intersection()` +- `CGAL::Polygon_mesh_processing::clip()` - `CGAL::Polygon_mesh_processing::does_bound_a_volume()` ## Geometric Measure Functions ## diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/clip.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/clip.h new file mode 100644 index 00000000000..423140e5d12 --- /dev/null +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/clip.h @@ -0,0 +1,454 @@ +// Copyright (c) 2016 GeometryFactory (France). +// All rights reserved. +// +// This file is part of CGAL (www.cgal.org). +// You can redistribute it and/or modify it under the terms of the GNU +// General Public License as published by the Free Software Foundation, +// either version 3 of the License, or (at your option) any later version. +// +// Licensees holding a valid commercial license may use this file in +// accordance with the commercial license agreement provided with the software. +// +// This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE +// WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. +// +// $URL$ +// $Id$ +// SPDX-License-Identifier: GPL-3.0+ +// +// +// Author(s) : Sebastien Loriot + +#ifndef CGAL_POLYGON_MESH_PROCESSING_CLIP_H +#define CGAL_POLYGON_MESH_PROCESSING_CLIP_H + +#include + + +#include +#include +#include + +#include + +#include + +namespace CGAL{ +namespace Polygon_mesh_processing { + +namespace internal +{ +template +bool +clip_open_impl( TriangleMesh& tm, + TriangleMesh& clipper, + const NamedParameters1& np_tm, + const NamedParameters2& np_c) +{ + typedef typename GetVertexPointMap::type Vpm; + typedef typename GetGeomTraits::type GeomTraits; + typedef boost::graph_traits GT; + typedef typename GT::halfedge_descriptor halfedge_descriptor; + typedef typename GT::face_descriptor face_descriptor; + +// First build an AABB-tree of the clipper triangles as it will be modified + typedef std::vector Clipper_triangles; + typedef typename Clipper_triangles::iterator Tr_iterator; + typedef CGAL::AABB_triangle_primitive Primitive; + typedef CGAL::AABB_traits AABB_triangle_traits; + typedef CGAL::AABB_tree Clipper_tree; + + // vector of clipper triangles + Clipper_triangles clipper_triangles; + clipper_triangles.reserve( num_faces(clipper) ); + Vpm vpm_c = boost::choose_param(get_param(np_c, internal_np::vertex_point), + get_property_map(vertex_point, clipper)); + BOOST_FOREACH(face_descriptor f, faces(clipper)) + { + halfedge_descriptor h = halfedge(f, clipper); + clipper_triangles.push_back( typename GeomTraits::Triangle_3( + get(vpm_c, source(h, clipper)), + get(vpm_c, target(h, clipper)), + get(vpm_c, target(next(h, clipper), clipper)) ) ); + } + // tree + Clipper_tree clipper_tree(clipper_triangles.begin(), clipper_triangles.end()); + // predicate functor + Side_of_triangle_mesh side_of(clipper_tree); + +// Second corefine the meshes + typedef CGAL::dynamic_edge_property_t Ecm_tag; + typedef typename boost::property_map::type Ecm; + Ecm ecm = get(Ecm_tag(), tm); + + corefine(tm, clipper, np_tm.edge_is_constrained_map(ecm), np_c); + +// Extract connected components + typedef typename GetFaceIndexMap::type Fid_map; + + Fid_map fid_map = boost::choose_param(get_param(np_tm, internal_np::face_index), + get_property_map(boost::face_index, tm)); + Vpm vpm1 = boost::choose_param(get_param(np_tm, internal_np::vertex_point), + get_property_map(vertex_point, tm)); + + typedef CGAL::dynamic_vertex_property_t Vid_tag; + typedef typename boost::property_map::type Vid_map; + Vid_map vid_map = get(Vid_tag(), tm); + + // init indices if needed + helpers::init_face_indices(tm, fid_map); + helpers::init_vertex_indices(tm, vid_map); + + // set the connected component id of each face + std::vector face_cc(num_faces(tm), std::size_t(-1)); + std::size_t nb_cc = + connected_components(tm, + bind_property_maps(fid_map, make_property_map(face_cc)), + parameters::face_index_map(fid_map). + edge_is_constrained_map(ecm)); + + + boost::dynamic_bitset<> cc_not_handled(nb_cc); + cc_not_handled.set(); + std::vector ccs_to_remove; + + BOOST_FOREACH(face_descriptor f, faces(tm)) + { + std::size_t cc_id = face_cc[ get(fid_map, f) ]; + if ( !cc_not_handled.test(cc_id) ) continue; + + halfedge_descriptor h=halfedge(f, tm); + for(int i=0;i<3;++i) + { + // look for a vertex not on a constrained edge + bool no_marked_edge=true; + BOOST_FOREACH(halfedge_descriptor h2, halfedges_around_target(h, tm)) + if ( get(ecm, edge(h2, tm)) ){ + no_marked_edge=false; + break; + } + if (no_marked_edge){ + if ( side_of( get(vpm1, target(h, tm) ) ) == ON_UNBOUNDED_SIDE ) + ccs_to_remove.push_back(cc_id); + cc_not_handled.reset(cc_id); + break; + } + h=next(h, tm); + } + if (!cc_not_handled.any()) break; + } + + if (cc_not_handled.any()) + { + // A patch without no vertex incident to a non-constrained edges + // is a coplanar patch: drop it or keep it! + if (!boost::choose_param(boost::get_param(np_tm, internal_np::include_clipper_boundary), true)) + { + for (std::size_t cc_id = cc_not_handled.find_first(); + cc_id < cc_not_handled.npos; + cc_id = cc_not_handled.find_next(cc_id)) + { + ccs_to_remove.push_back(cc_id); + } + } + } +// Filter out the cc + remove_connected_components(tm, + ccs_to_remove, + bind_property_maps(fid_map, make_property_map(face_cc)), + parameters::vertex_index_map(vid_map)); + + return true; +} + +/// \todo remove convex_hull_3 +template +Oriented_side +clip_to_bbox(const Plane_3& plane, + const Bbox_3& bbox, + TriangleMesh& tm_out, + const NamedParameters& np ) +{ + typedef typename GetGeomTraits::type Geom_traits; + typedef typename Geom_traits::Point_3 Point_3; + typedef typename Geom_traits::Segment_3 Segment_3; + typedef typename GetVertexPointMap::type Vpm; + + Vpm vpm_out = boost::choose_param(get_param(np, internal_np::vertex_point), + get_property_map(boost::vertex_point, tm_out)); + + + cpp11::array corners= {{ + Point_3(bbox.xmin(),bbox.ymin(),bbox.zmin()), + Point_3(bbox.xmin(),bbox.ymax(),bbox.zmin()), + Point_3(bbox.xmax(),bbox.ymax(),bbox.zmin()), + Point_3(bbox.xmax(),bbox.ymin(),bbox.zmin()), + Point_3(bbox.xmin(),bbox.ymin(),bbox.zmax()), + Point_3(bbox.xmin(),bbox.ymax(),bbox.zmax()), + Point_3(bbox.xmax(),bbox.ymax(),bbox.zmax()), + Point_3(bbox.xmax(),bbox.ymin(),bbox.zmax()) + }}; + + cpp11::array orientations = {{ + plane.oriented_side(corners[0]), + plane.oriented_side(corners[1]), + plane.oriented_side(corners[2]), + plane.oriented_side(corners[3]), + plane.oriented_side(corners[4]), + plane.oriented_side(corners[5]), + plane.oriented_side(corners[6]), + plane.oriented_side(corners[7]) + }}; + + std::vector points; + + // look for intersections on edges + cpp11::array edge_indices = {{ // 2 *12 edges + 0,1, 1,2, 2,3, 3,0, // bottom face edges + 4,5, 5,6, 6,7, 7,4, // top face edges + 0,4, 1,5, 2,6, 3,7 + }}; + + for (int i=0; i<12; ++i) + { + int i1=edge_indices[2*i], i2=edge_indices[2*i+1]; + if (orientations[i1]==ON_ORIENTED_BOUNDARY) continue; + if (orientations[i2]==ON_ORIENTED_BOUNDARY) continue; + if (orientations[i1]!=orientations[i2]) + points.push_back( + boost::get( + *intersection(plane, Segment_3(corners[i1], corners[i2]) ) + ) + ); + } + + + Oriented_side last_os = ON_ORIENTED_BOUNDARY; + for (int i=0; i<8; ++i) + if (orientations[i]!=ON_ORIENTED_BOUNDARY) + { + if (last_os==ON_ORIENTED_BOUNDARY) + last_os=orientations[i]; + else + { + if(last_os!=orientations[i]) + { + last_os=ON_ORIENTED_BOUNDARY; + break; + } + } + } + + // the intersection is the full bbox + if (last_os!=ON_ORIENTED_BOUNDARY) + return last_os; + + //add points on negative side and on the plane + for (int i=0; i<8; ++i) + if (orientations[i]!=ON_POSITIVE_SIDE) + points.push_back(corners[i]); + + // take the convex hull of the points on the negative side+intersection points + // overkill... + Polyhedron_3 P; + CGAL::convex_hull_3(points.begin(), points.end(), P); + copy_face_graph(P, tm_out, + Emptyset_iterator(), Emptyset_iterator(), Emptyset_iterator(), + get(vertex_point, P), vpm_out); + return ON_ORIENTED_BOUNDARY; +} + +} // end of internal namespace + +/** + * \ingroup PMP_corefinement_grp + * clips `tm` by keeping the part that is inside the volume \link coref_def_subsec bounded \endlink + * by `clipper`. + * If `tm` is closed, the clipped part can be closed too if the named parameter `close_volumes` is set to `true`. + * \attention With the current implementation, `clipper` will be modified (refined with the intersection with `tm`). + * + * \pre \link CGAL::Polygon_mesh_processing::does_self_intersect() `!CGAL::Polygon_mesh_processing::does_self_intersect(tm1)` \endlink + * \pre \link CGAL::Polygon_mesh_processing::does_self_intersect() `!CGAL::Polygon_mesh_processing::does_self_intersect(clipper)` \endlink + * \pre \link CGAL::Polygon_mesh_processing::does_bound_a_volume() `CGAL::Polygon_mesh_processing::does_bound_a_volume(clipper)` \endlink + * + * @tparam TriangleMesh a model of `MutableFaceGraph`, `HalfedgeListGraph` and `FaceListGraph`. + * If `TriangleMesh` has an internal property map for `CGAL::face_index_t`, + * as a named parameter, then it must be initialized. + * An internal property map for `CGAL::vertex_point_t` should be available. + * + * @tparam NamedParameters1 a sequence of \ref pmp_namedparameters "Named Parameters" + * @tparam NamedParameters2 a sequence of \ref pmp_namedparameters "Named Parameters" + * + * @param tm input triangulated surface mesh + * @param clipper triangulated surface mesh used to clip `tm` + * @param np_tm optional sequence of \ref pmp_namedparameters "Named Parameters" among the ones listed below + * @param np_c 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 `tm` (`clipper`). + * If this parameter is omitted, an internal property map for + * `CGAL::vertex_point_t` should be available in `TriangleMesh` + * \cgalParamEnd + * \cgalParamBegin{face_index_map} a property map containing the index of each face of `tm` (`clipper`). + * Note that if the property map is writable, the indices of the faces + * of `tm` and `clipper` will be set after the refining `tm` with the intersection with `plane`. + * \cgalParamEnd + * \cgalParamBegin{new_face_visitor} a class model of `PMPCorefinementNewFaceVisitor` + * that is used to track the creation of new faces. + * \cgalParamEnd + * \cgalParamBegin{throw_on_self_intersection} if `true`, + * the set of triangles closed to the intersection of `tm` and `clipper` will be + * checked for self-intersection and `CGAL::Polygon_mesh_processing::Corefinement::Self_intersection_exception` + * will be thrown if at least one is found. + * \cgalParamEnd + * \cgalParamBegin{close_volumes} if `true` and `tm` is closed, the clipping considered will be + * done on the volume \link coref_def_subsec bounded \endlink by `tm` rather than on its surface (`tm` will be kept closed). + * \cgalParamEnd + * \cgalParamBegin{include_clipper_boundary} if `false` and `close_volumes` is `false` and `tm` is open, the parts of `tm` coplanar with `clipper` + * will not be part of the output. + * \cgalParamEnd + * \cgalNamedParamsEnd + * + * @return `true` if the output surface mesh is manifold. + * If `false` is returned `tm` and `clipper` will be corefined. + */ +template +bool +clip( TriangleMesh& tm, + TriangleMesh& clipper, + const NamedParameters1& np_tm, + const NamedParameters2& np_c) +{ + const bool close = + boost::choose_param(boost::get_param(np_tm, internal_np::close_volumes), false); + + if (close && is_closed(tm)) + return corefine_and_compute_intersection(tm, clipper, tm, np_tm, np_c); + + return internal::clip_open_impl(tm, clipper, np_tm, np_c); +} + +/** + * \ingroup PMP_corefinement_grp + * clips `tm` by keeping the part that is on the negative side of `plane` (side opposite to its normal vector). + * If `tm` is closed, the clipped part can be closed too if the named parameter `close_volumes` is set to `true`. + * + * \note In the current implementation it is not possible to set the vertex point map and the default will be used. + * \pre \link CGAL::Polygon_mesh_processing::does_self_intersect() `!CGAL::Polygon_mesh_processing::does_self_intersect(tm)` \endlink + * + * @tparam TriangleMesh a model of `MutableFaceGraph`, `HalfedgeListGraph` and `FaceListGraph`. + * If `TriangleMesh` has an internal property map for `CGAL::face_index_t`, + * as a named parameter, then it must be initialized. + * An internal property map for `CGAL::vertex_point_t` should be available. + * + * @tparam NamedParameters a sequence of \ref pmp_namedparameters "Named Parameters" + * + * @param tm input triangulated surface mesh + * @param plane plane which negative side defines the half-space to intersect `tm` with. + * `Plane_3` is the plane type for the same CGAL kernel as the point of the vertex point map of `tm`. + * @param np optional sequence of \ref pmp_namedparameters "Named Parameters" among the ones listed below + * + * \cgalNamedParamsBegin + * \cgalParamBegin{face_index_map} a property map containing the index of each face of `tm`. + * Note that if the property map is writable, the indices of the faces + * of `tm` will be set after the refining of `tm` with the intersection with `plane`. + * \cgalParamEnd + * \cgalParamBegin{new_face_visitor} a class model of `PMPCorefinementNewFaceVisitor` + * that is used to track the creation of new faces. + * \cgalParamEnd + * \cgalParamBegin{throw_on_self_intersection} if `true`, + * the set of triangles closed to the intersection of `tm` and `plane` will be + * checked for self-intersection and `CGAL::Polygon_mesh_processing::Corefinement::Self_intersection_exception` + * will be thrown if at least one is found. + * \cgalParamEnd + * \cgalParamBegin{close_volumes} if `true` and `tm` is closed, the clipping considered will be + * done on the volume \link coref_def_subsec bounded \endlink by `tm` rather than on its surface (`tm` will be kept closed). + * \cgalParamEnd + * \cgalParamBegin{include_clipper_boundary} if `false` and `close_volumes` is `false` and `tm` is open, the parts of `tm` coplanar with `plane` + * will not be part of the output. + * \cgalNamedParamsEnd + * + * @return `true` if the output surface mesh is manifold. + * If `false` is returned `tm` will be refined by the intersection with `plane`. + */ +template +bool clip( TriangleMesh& tm, + #ifdef DOXYGEN_RUNNING + const Plane_3& plane, + #else + const typename GetGeomTraits::type::Plane_3& plane, + #endif + const NamedParameters& np) +{ + if( boost::begin(faces(tm))==boost::end(faces(tm)) ) return true; + + CGAL::Bbox_3 bbox = ::CGAL::Polygon_mesh_processing::bbox(tm); + + //extend the bbox a bit to avoid border cases + double xd=(bbox.xmax()-bbox.xmin())/100; + double yd=(bbox.ymax()-bbox.ymin())/100; + double zd=(bbox.zmax()-bbox.zmin())/100; + bbox=CGAL::Bbox_3(bbox.xmin()-xd, bbox.ymin()-yd, bbox.zmin()-zd, + bbox.xmax()+xd, bbox.ymax()+yd, bbox.zmax()+zd); + TriangleMesh clipper; + Oriented_side os = internal::clip_to_bbox(plane, bbox, clipper, parameters::all_default()); + + switch(os) + { + case ON_NEGATIVE_SIDE: + return true; // nothing to clip, the full mesh is on the negative side + case ON_POSITIVE_SIDE: + clear(tm); // clear the mesh that is fully on the positive side + return true; + default: + break; + } + return clip(tm, clipper, np, parameters::all_default()); +} + +/// \cond SKIP_IN_MANUAL +// convenience overloads +template +bool clip( TriangleMesh& tm, + const typename GetGeomTraits::type::Plane_3& plane) +{ + return clip(tm, plane, parameters::all_default()); +} + +// convenience overload +template +bool +clip( TriangleMesh& tm, + TriangleMesh& clipper, + const NamedParameters1& np_tm) +{ + return clip(tm, clipper, np_tm, parameters::all_default()); +} + +// convenience overload +template +bool +clip( TriangleMesh& tm, + TriangleMesh& clipper) +{ + return clip(tm, clipper, parameters::all_default()); +} +/// \endcond + +} } //end of namespace CGAL::Polygon_mesh_processing + +#endif // CGAL_POLYGON_MESH_PROCESSING_CLIP_H diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/clip.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/clip.h deleted file mode 100644 index 5376bd43ff5..00000000000 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/clip.h +++ /dev/null @@ -1,339 +0,0 @@ -// Copyright (c) 2016 GeometryFactory (France). -// All rights reserved. -// -// This file is part of CGAL (www.cgal.org). -// You can redistribute it and/or modify it under the terms of the GNU -// General Public License as published by the Free Software Foundation, -// either version 3 of the License, or (at your option) any later version. -// -// Licensees holding a valid commercial license may use this file in -// accordance with the commercial license agreement provided with the software. -// -// This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE -// WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. -// -// $URL$ -// $Id$ -// SPDX-License-Identifier: GPL-3.0+ -// -// -// Author(s) : Sebastien Loriot - -#ifndef CGAL_POLYGON_MESH_PROCESSING_CLIP_H -#define CGAL_POLYGON_MESH_PROCESSING_CLIP_H - -#include - - -#include -#include -#include - -#include - -namespace CGAL{ -namespace Polygon_mesh_processing { - -namespace internal -{ -template -bool -clip_open_impl( TriangleMesh& tm, - TriangleMesh& clipper, - Ecm ecm, - const NamedParameters1& np_tm, - const NamedParameters2& np_c) -{ - // first corefine the meshes - corefine(tm, clipper, np_tm, np_c); - - typedef typename GetVertexPointMap::type Vpm; - typedef typename GetFaceIndexMap::type Fid_map; - typedef typename GetVertexIndexMap::type Vid_map; - - typedef boost::graph_traits GT; - typedef typename GetGeomTraits::type GeomTraits; - typedef typename GT::halfedge_descriptor halfedge_descriptor; - typedef typename GT::face_descriptor face_descriptor; - - Fid_map fid_map = boost::choose_param(get_param(np_tm, internal_np::face_index), - get_property_map(boost::face_index, tm)); - Vid_map vid_map = boost::choose_param(get_param(np_tm, internal_np::vertex_index), - get_property_map(boost::vertex_index, tm)); - Vpm vpm1 = boost::choose_param(get_param(np_tm, internal_np::vertex_point), - get_property_map(vertex_point, tm)); - Vpm vpm_c = boost::choose_param(get_param(np_c, internal_np::vertex_point), - get_property_map(vertex_point, clipper)); - - // init indices if needed - helpers::init_face_indices(tm, fid_map); - helpers::init_vertex_indices(tm, vid_map); - - // set the connected component id of each face - std::vector face_cc(num_faces(tm), std::size_t(-1)); - std::size_t nb_cc = - connected_components(tm, - bind_property_maps(fid_map, make_property_map(face_cc)), - parameters::face_index_map(fid_map). - edge_is_constrained_map(ecm)); - - - boost::dynamic_bitset<> cc_not_handled(nb_cc); - cc_not_handled.set(); - std::vector ccs_to_remove; - /// \todo clipper has been modified, this is not robust if inexact constructions are used - CGAL::Side_of_triangle_mesh - side_of(clipper, vpm_c); - BOOST_FOREACH(face_descriptor f, faces(tm)) - { - std::size_t cc_id = face_cc[ get(fid_map, f) ]; - if ( !cc_not_handled.test(cc_id) ) continue; - - halfedge_descriptor h=halfedge(f, tm); - for(int i=0;i<3;++i) - { - bool no_marked_edge=true; - BOOST_FOREACH(halfedge_descriptor h2, halfedges_around_target(h, tm)) - if ( get(ecm, edge(h2, tm)) ){ - no_marked_edge=false; - break; - } - if (no_marked_edge){ - if ( side_of( get(vpm1, target(h, tm) ) ) == ON_UNBOUNDED_SIDE ) - ccs_to_remove.push_back(cc_id); - cc_not_handled.reset(cc_id); - break; - } - h=next(h, tm); - } - if (!cc_not_handled.any()) break; - } - - if (cc_not_handled.any()) - { - ///\todo handle using barycenters? won't work for coplanar faces - } - //now remove the cc - remove_connected_components(tm, - ccs_to_remove, - bind_property_maps(fid_map, make_property_map(face_cc)), - np_tm); - - return true; -} - -template -bool -clip_open_impl( TriangleMesh& tm, - TriangleMesh& clipper, - boost::param_not_found, - const NamedParameters1& np_tm, - const NamedParameters2& np_c) -{ - typedef typename boost::graph_traits - ::edge_descriptor edge_descriptor; - boost::unordered_set constrained_edges; - Boolean_property_map > - cst_map(constrained_edges); - - return clip_open_impl(tm, clipper, - cst_map, - np_tm.edge_is_constrained_map(cst_map), - np_c); -} - -} // end of internal namespace - -#ifndef DOXYGEN_RUNNING - -///\todo clipper const! -/// requires face_index_map, vertex_index_map for np_tm -/// requires face_index_map for np_c -/// if edge_is_constrained_map is not provided in np_tm a default one is -/// provided using boost::unordered_set -template -bool -clip( TriangleMesh& tm, - /*const*/ TriangleMesh& clipper, - bool close, - const NamedParameters1& np_tm, - const NamedParameters2& np_c) -{ - if (close && is_closed(tm)) - return corefine_and_compute_intersection(tm, clipper, tm, np_tm, np_c); - - return internal::clip_open_impl(tm, clipper, - get_param(np_tm, internal_np::edge_is_constrained), np_tm, np_c); -} - -/// \todo document me -/// \todo remove convex_hull_3 -template -Oriented_side -clip_to_bbox(const Plane_3& plane, - const Bbox_3& bbox, - TriangleMesh& tm_out, - const NamedParameters& np ) -{ - typedef typename GetGeomTraits::type Geom_traits; - typedef typename Geom_traits::Point_3 Point_3; - typedef typename Geom_traits::Segment_3 Segment_3; - typedef typename GetVertexPointMap::type Vpm; - - Vpm vpm_out = boost::choose_param(get_param(np, internal_np::vertex_point), - get_property_map(boost::vertex_point, tm_out)); - - - cpp11::array corners= {{ - Point_3(bbox.xmin(),bbox.ymin(),bbox.zmin()), - Point_3(bbox.xmin(),bbox.ymax(),bbox.zmin()), - Point_3(bbox.xmax(),bbox.ymax(),bbox.zmin()), - Point_3(bbox.xmax(),bbox.ymin(),bbox.zmin()), - Point_3(bbox.xmin(),bbox.ymin(),bbox.zmax()), - Point_3(bbox.xmin(),bbox.ymax(),bbox.zmax()), - Point_3(bbox.xmax(),bbox.ymax(),bbox.zmax()), - Point_3(bbox.xmax(),bbox.ymin(),bbox.zmax()) - }}; - - cpp11::array orientations = {{ - plane.oriented_side(corners[0]), - plane.oriented_side(corners[1]), - plane.oriented_side(corners[2]), - plane.oriented_side(corners[3]), - plane.oriented_side(corners[4]), - plane.oriented_side(corners[5]), - plane.oriented_side(corners[6]), - plane.oriented_side(corners[7]) - }}; - - std::vector points; - - // look for intersections on edges - cpp11::array edge_indices = {{ // 2 *12 edges - 0,1, 1,2, 2,3, 3,0, // bottom face edges - 4,5, 5,6, 6,7, 7,4, // top face edges - 0,4, 1,5, 2,6, 3,7 - }}; - - for (int i=0; i<12; ++i) - { - int i1=edge_indices[2*i], i2=edge_indices[2*i+1]; - if (orientations[i1]==ON_ORIENTED_BOUNDARY) continue; - if (orientations[i2]==ON_ORIENTED_BOUNDARY) continue; - if (orientations[i1]!=orientations[i2]) - points.push_back( - boost::get( - *intersection(plane, Segment_3(corners[i1], corners[i2]) ) - ) - ); - } - - - Oriented_side last_os = ON_ORIENTED_BOUNDARY; - for (int i=0; i<8; ++i) - if (orientations[i]!=ON_ORIENTED_BOUNDARY) - { - if (last_os==ON_ORIENTED_BOUNDARY) - last_os=orientations[i]; - else - { - if(last_os!=orientations[i]) - { - last_os=ON_ORIENTED_BOUNDARY; - break; - } - } - } - - // the intersection is the full bbox - if (last_os!=ON_ORIENTED_BOUNDARY) - return last_os; - - //add points on negative side and on the plane - for (int i=0; i<8; ++i) - if (orientations[i]!=ON_POSITIVE_SIDE) - points.push_back(corners[i]); - - // take the convex hull of the points on the negative side+intersection points - // overkill... - Polyhedron_3 P; - CGAL::convex_hull_3(points.begin(), points.end(), P); - copy_face_graph(P, tm_out, - Emptyset_iterator(), Emptyset_iterator(), Emptyset_iterator(), - get(vertex_point, P), vpm_out); - return ON_ORIENTED_BOUNDARY; -} - - -// convenience overload -template -bool -clip( TriangleMesh& tm, - /*const*/ TriangleMesh& clipper, - bool close, - const NamedParameters1& np_tm) -{ - return clip(tm, clipper, close, np_tm, parameters::all_default()); -} - -// convenience overload -template -bool -clip( TriangleMesh& tm, - /*const*/ TriangleMesh& clipper, - bool close) -{ - return clip(tm, clipper, close, parameters::all_default()); -} - -// works only with the default point map, for more complex use cases, use -// clip_to_bbox first and the other overload of clip with two meshes -/// \todo document me -template -void clip( TriangleMesh& tm, - const Plane_3& plane, - bool close) -{ - if( boost::begin(faces(tm))==boost::end(faces(tm)) ) return; - CGAL::Bbox_3 bbox = ::CGAL::Polygon_mesh_processing::bbox(tm); - //extend the bbox a bit to avoid border cases - double xd=(bbox.xmax()-bbox.xmin())/100; - double yd=(bbox.ymax()-bbox.ymin())/100; - double zd=(bbox.zmax()-bbox.zmin())/100; - bbox=CGAL::Bbox_3(bbox.xmin()-xd, bbox.ymin()-yd, bbox.zmin()-zd, - bbox.xmax()+xd, bbox.ymax()+yd, bbox.zmax()+zd); - TriangleMesh clipper; - Oriented_side os = clip_to_bbox(plane, bbox, clipper, parameters::all_default()); - - switch(os) - { - case ON_NEGATIVE_SIDE: - return; // nothing to clip, the full mesh is on the negative side - case ON_POSITIVE_SIDE: - clear(tm); // clear the mesh that is fully on the positive side - return; - default: - clip(tm, clipper, close); - } -} - -#endif // !DOXYGEN_RUNNING - -} } //end of namespace CGAL::Polygon_mesh_processing - -#endif // CGAL_POLYGON_MESH_PROCESSING_CLIP_H diff --git a/Polygon_mesh_processing/test/Polygon_mesh_processing/test_pmp_clip.cpp b/Polygon_mesh_processing/test/Polygon_mesh_processing/test_pmp_clip.cpp index 9a9d24e4ee7..ca35d7462db 100644 --- a/Polygon_mesh_processing/test/Polygon_mesh_processing/test_pmp_clip.cpp +++ b/Polygon_mesh_processing/test/Polygon_mesh_processing/test_pmp_clip.cpp @@ -1,5 +1,5 @@ #include -#include +#include #include #include @@ -30,7 +30,7 @@ int main() Constrained_edge_map ecm1 = tm1.add_property_map("e:cst", false).first; - PMP::clip(tm1, tm2, false, params::edge_is_constrained_map(ecm1)); + PMP::clip(tm1, tm2, params::close_volumes(false).edge_is_constrained_map(ecm1)); std::ofstream output("clipped_opened.off"); output << tm1; @@ -42,10 +42,10 @@ int main() input.open("data-coref/sphere.off"); input >> Q; - PMP::clip(P, Q, false, - params::face_index_map(get(CGAL::face_external_index, P)). - vertex_index_map(get(CGAL::vertex_external_index, P)), - params::face_index_map(get(CGAL::face_external_index, Q))); + PMP::clip(P, Q, + params::close_volumes(false) + .face_index_map(get(CGAL::face_external_index, P)) + ,params::face_index_map(get(CGAL::face_external_index, Q))); assert(P.size_of_vertices() == tm1.number_of_vertices()); } { @@ -56,7 +56,7 @@ int main() input.open("data-coref/sphere.off"); input >> tm2; - PMP::clip(tm1, tm2, true); + PMP::clip(tm1, tm2, PMP::parameters::close_volumes(true)); std::ofstream output("clipped_closed.off"); output << tm1; } diff --git a/Polyhedron/demo/Polyhedron/Plugins/Operations_on_polyhedra/Clip_polyhedron_plugin.cpp b/Polyhedron/demo/Polyhedron/Plugins/Operations_on_polyhedra/Clip_polyhedron_plugin.cpp index ba3ef64caec..13311957b49 100644 --- a/Polyhedron/demo/Polyhedron/Plugins/Operations_on_polyhedra/Clip_polyhedron_plugin.cpp +++ b/Polyhedron/demo/Polyhedron/Plugins/Operations_on_polyhedra/Clip_polyhedron_plugin.cpp @@ -8,7 +8,7 @@ #include "Scene_plane_item.h" #include #include -#include +#include #include "ui_Clip_polyhedron_plugin.h" #include "Viewer.h" @@ -141,7 +141,8 @@ public : CGAL::Polygon_mesh_processing::clip(*neg_side, plane->plane(), - ui_widget.close_checkBox->isChecked()); + CGAL::Polygon_mesh_processing::parameters::close_volumes( + ui_widget.close_checkBox->isChecked())); Item* new_item = new Item(neg_side); new_item->setName(QString("%1 on %2").arg(item->name()).arg("negative side")); new_item->setColor(item->color()); @@ -153,7 +154,9 @@ public : Mesh* pos_side = new Mesh(*item->face_graph()); CGAL::Polygon_mesh_processing::clip(*pos_side, plane->plane().opposite(), - ui_widget.close_checkBox->isChecked()); + CGAL::Polygon_mesh_processing::parameters::close_volumes( + ui_widget.close_checkBox->isChecked())); + new_item = new Item(pos_side); new_item->setName(QString("%1 on %2").arg(item->name()).arg("positive side")); new_item->setColor(item->color()); @@ -234,13 +237,16 @@ public Q_SLOTS: { CGAL::Polygon_mesh_processing::clip(*(sm_item->face_graph()), plane->plane(), - ui_widget.close_checkBox->isChecked()); + CGAL::Polygon_mesh_processing::parameters::close_volumes( + ui_widget.close_checkBox->isChecked())); } else { CGAL::Polygon_mesh_processing::clip(*(poly_item->face_graph()), plane->plane(), - ui_widget.close_checkBox->isChecked()); + CGAL::Polygon_mesh_processing::parameters::close_volumes( + ui_widget.close_checkBox->isChecked())); + } item->invalidateOpenGLBuffers(); viewer->update(); From b8f058e0d114da33f01d0bf153e383e83511cca2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Loriot?= Date: Fri, 1 Jun 2018 16:09:22 +0200 Subject: [PATCH 17/43] implement @MaelRL review --- Installation/CHANGES.md | 8 +++--- .../CGAL/Polygon_mesh_processing/clip.h | 26 ++++++++++--------- 2 files changed, 18 insertions(+), 16 deletions(-) diff --git a/Installation/CHANGES.md b/Installation/CHANGES.md index 0acd7bfa72f..69a90cf1d32 100644 --- a/Installation/CHANGES.md +++ b/Installation/CHANGES.md @@ -35,16 +35,16 @@ Release date: September 2018 ### Polygon Mesh Processing - Added a function to apply a transformation to a mesh : - `CGAL::Polygon_mesh_processing::transform()` -- Add in corefinement-related functions a new named parameter `new_face_visitor` +- Added in corefinement-related functions a new named parameter `new_face_visitor` that make it possible to pass a visitor to the function in order to track the creation of new faces. -- Add in all corefinement-related functions a named parameter `throw_on_self_intersection` +- Added in all corefinement-related functions a named parameter `throw_on_self_intersection` (that replace the `bool` parameter in `corefine()`) that enables to check for self-intersections faces involved in the intersection before trying to corefine the input meshes. -- Document the function `corefine_and_compute_boolean_operations()` that can be used to +- Added the function `corefine_and_compute_boolean_operations()` that can be used to compute the result of several Boolean operations between 2 volumes at the same time. -- Document the function `clip()` that can be used to clip a triangulated surface mesh +- Added the function `clip()` that can be used to clip a triangulated surface mesh by a plane or a clipping volume. Release 4.12 diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/clip.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/clip.h index 423140e5d12..156f47044f7 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/clip.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/clip.h @@ -64,8 +64,8 @@ clip_open_impl( TriangleMesh& tm, // vector of clipper triangles Clipper_triangles clipper_triangles; clipper_triangles.reserve( num_faces(clipper) ); - Vpm vpm_c = boost::choose_param(get_param(np_c, internal_np::vertex_point), - get_property_map(vertex_point, clipper)); + Vpm vpm_c = boost::choose_param(boost::get_param(np_c, internal_np::vertex_point), + get_property_map(vertex_point, clipper)); BOOST_FOREACH(face_descriptor f, faces(clipper)) { halfedge_descriptor h = halfedge(f, clipper); @@ -90,9 +90,9 @@ clip_open_impl( TriangleMesh& tm, typedef typename GetFaceIndexMap::type Fid_map; - Fid_map fid_map = boost::choose_param(get_param(np_tm, internal_np::face_index), + Fid_map fid_map = boost::choose_param(boost::get_param(np_tm, internal_np::face_index), get_property_map(boost::face_index, tm)); - Vpm vpm1 = boost::choose_param(get_param(np_tm, internal_np::vertex_point), + Vpm vpm1 = boost::choose_param(boost::get_param(np_tm, internal_np::vertex_point), get_property_map(vertex_point, tm)); typedef CGAL::dynamic_vertex_property_t Vid_tag; @@ -181,7 +181,7 @@ clip_to_bbox(const Plane_3& plane, typedef typename GetVertexPointMap::type Vpm; - Vpm vpm_out = boost::choose_param(get_param(np, internal_np::vertex_point), + Vpm vpm_out = boost::choose_param(boost::get_param(np, internal_np::vertex_point), get_property_map(boost::vertex_point, tm_out)); @@ -306,11 +306,12 @@ clip_to_bbox(const Plane_3& plane, * \cgalParamEnd * \cgalParamBegin{throw_on_self_intersection} if `true`, * the set of triangles closed to the intersection of `tm` and `clipper` will be - * checked for self-intersection and `CGAL::Polygon_mesh_processing::Corefinement::Self_intersection_exception` + * checked for self-intersections and `CGAL::Polygon_mesh_processing::Corefinement::Self_intersection_exception` * will be thrown if at least one is found. * \cgalParamEnd - * \cgalParamBegin{close_volumes} if `true` and `tm` is closed, the clipping considered will be - * done on the volume \link coref_def_subsec bounded \endlink by `tm` rather than on its surface (`tm` will be kept closed). + * \cgalParamBegin{close_volumes} if `true` and `tm` is closed, the clipping will be done on + * the volume \link coref_def_subsec bounded \endlink by `tm` rather than on its surface + * (i.e. `tm` will be kept closed). * \cgalParamEnd * \cgalParamBegin{include_clipper_boundary} if `false` and `close_volumes` is `false` and `tm` is open, the parts of `tm` coplanar with `clipper` * will not be part of the output. @@ -354,7 +355,7 @@ clip( TriangleMesh& tm, * @tparam NamedParameters a sequence of \ref pmp_namedparameters "Named Parameters" * * @param tm input triangulated surface mesh - * @param plane plane which negative side defines the half-space to intersect `tm` with. + * @param plane plane whose negative side defines the half-space to intersect `tm` with. * `Plane_3` is the plane type for the same CGAL kernel as the point of the vertex point map of `tm`. * @param np optional sequence of \ref pmp_namedparameters "Named Parameters" among the ones listed below * @@ -368,11 +369,12 @@ clip( TriangleMesh& tm, * \cgalParamEnd * \cgalParamBegin{throw_on_self_intersection} if `true`, * the set of triangles closed to the intersection of `tm` and `plane` will be - * checked for self-intersection and `CGAL::Polygon_mesh_processing::Corefinement::Self_intersection_exception` + * checked for self-intersections and `CGAL::Polygon_mesh_processing::Corefinement::Self_intersection_exception` * will be thrown if at least one is found. * \cgalParamEnd - * \cgalParamBegin{close_volumes} if `true` and `tm` is closed, the clipping considered will be - * done on the volume \link coref_def_subsec bounded \endlink by `tm` rather than on its surface (`tm` will be kept closed). + * \cgalParamBegin{close_volumes} if `true` and `tm` is closed, the clipping will be done on + * the volume \link coref_def_subsec bounded \endlink by `tm` rather than on its surface + * (i.e. `tm` will be kept closed). * \cgalParamEnd * \cgalParamBegin{include_clipper_boundary} if `false` and `close_volumes` is `false` and `tm` is open, the parts of `tm` coplanar with `plane` * will not be part of the output. From 2569c271e5c6f598b627c4a93e4eecb313bb78c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Loriot?= Date: Fri, 1 Jun 2018 16:50:39 +0200 Subject: [PATCH 18/43] improve doc and plugin --- .../CGAL/boost/graph/parameters_interface.h | 2 +- BGL/test/BGL/test_cgal_bgl_named_params.cpp | 6 +++--- .../NamedParameters.txt | 2 +- .../CGAL/Polygon_mesh_processing/clip.h | 18 ++++++++--------- .../Polygon_mesh_processing/test_pmp_clip.cpp | 6 +++--- .../Clip_polyhedron_plugin.cpp | 20 +++++++++++-------- 6 files changed, 29 insertions(+), 25 deletions(-) diff --git a/BGL/include/CGAL/boost/graph/parameters_interface.h b/BGL/include/CGAL/boost/graph/parameters_interface.h index 5279c3be58b..8e030bde218 100644 --- a/BGL/include/CGAL/boost/graph/parameters_interface.h +++ b/BGL/include/CGAL/boost/graph/parameters_interface.h @@ -66,7 +66,7 @@ CGAL_add_named_parameter(overlap_test_t, overlap_test, do_overlap_test_of_bounde CGAL_add_named_parameter(preserve_genus_t, preserve_genus, preserve_genus) CGAL_add_named_parameter(new_face_visitor_t, new_face_visitor, new_face_visitor) CGAL_add_named_parameter(throw_on_self_intersection_t, throw_on_self_intersection, throw_on_self_intersection) -CGAL_add_named_parameter(close_volumes_t, close_volumes, close_volumes) +CGAL_add_named_parameter(clip_volumes_t, clip_volumes, clip_volumes) CGAL_add_named_parameter(include_clipper_boundary_t, include_clipper_boundary, include_clipper_boundary) // List of named parameters that we use in the package 'Surface Mesh Simplification' diff --git a/BGL/test/BGL/test_cgal_bgl_named_params.cpp b/BGL/test/BGL/test_cgal_bgl_named_params.cpp index df1eaab6034..73ac8b7331b 100644 --- a/BGL/test/BGL/test_cgal_bgl_named_params.cpp +++ b/BGL/test/BGL/test_cgal_bgl_named_params.cpp @@ -74,7 +74,7 @@ void test(const NamedParameters& np) assert(get_param(np, CGAL::internal_np::nb_points_per_distance_unit).v == 33); assert(get_param(np, CGAL::internal_np::new_face_visitor).v == 42); assert(get_param(np, CGAL::internal_np::throw_on_self_intersection).v == 43); - assert(get_param(np, CGAL::internal_np::close_volumes).v == 44); + assert(get_param(np, CGAL::internal_np::clip_volumes).v == 44); assert(get_param(np, CGAL::internal_np::include_clipper_boundary).v == 45); // Named parameters that we use in the package 'Surface Mesh Simplification' @@ -142,7 +142,7 @@ void test(const NamedParameters& np) check_same_type<33>(get_param(np, CGAL::internal_np::nb_points_per_distance_unit)); check_same_type<42>(get_param(np, CGAL::internal_np::new_face_visitor)); check_same_type<43>(get_param(np, CGAL::internal_np::throw_on_self_intersection)); - check_same_type<44>(get_param(np, CGAL::internal_np::close_volumes)); + check_same_type<44>(get_param(np, CGAL::internal_np::clip_volumes)); check_same_type<45>(get_param(np, CGAL::internal_np::include_clipper_boundary)); // Named parameters that we use in the package 'Surface Mesh Simplification' @@ -209,7 +209,7 @@ int main() .verbosity_level(A<41>(41)) .new_face_visitor(A<42>(42)) .throw_on_self_intersection(A<43>(43)) - .close_volumes(A<44>(44)) + .clip_volumes(A<44>(44)) .include_clipper_boundary(A<45>(45)) ); diff --git a/Polygon_mesh_processing/doc/Polygon_mesh_processing/NamedParameters.txt b/Polygon_mesh_processing/doc/Polygon_mesh_processing/NamedParameters.txt index fe9d89f2a81..3f4f0f8c94b 100644 --- a/Polygon_mesh_processing/doc/Polygon_mesh_processing/NamedParameters.txt +++ b/Polygon_mesh_processing/doc/Polygon_mesh_processing/NamedParameters.txt @@ -327,7 +327,7 @@ and make the operation impossible with the current version of the code. \b Default value is `false` \cgalNPEnd -\cgalNPBegin{close_volumes} \anchor PMP_close_volumes +\cgalNPBegin{clip_volumes} \anchor PMP_clip_volumes Parameter used in `clip()` functions to clip a volume rather than a surface. \n \b Type : `bool` \n diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/clip.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/clip.h index 156f47044f7..b833b5bdaee 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/clip.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/clip.h @@ -271,7 +271,7 @@ clip_to_bbox(const Plane_3& plane, * \ingroup PMP_corefinement_grp * clips `tm` by keeping the part that is inside the volume \link coref_def_subsec bounded \endlink * by `clipper`. - * If `tm` is closed, the clipped part can be closed too if the named parameter `close_volumes` is set to `true`. + * If `tm` is closed, the clipped part can be closed too if the named parameter `clip_volumes` is set to `true`. * \attention With the current implementation, `clipper` will be modified (refined with the intersection with `tm`). * * \pre \link CGAL::Polygon_mesh_processing::does_self_intersect() `!CGAL::Polygon_mesh_processing::does_self_intersect(tm1)` \endlink @@ -309,17 +309,17 @@ clip_to_bbox(const Plane_3& plane, * checked for self-intersections and `CGAL::Polygon_mesh_processing::Corefinement::Self_intersection_exception` * will be thrown if at least one is found. * \cgalParamEnd - * \cgalParamBegin{close_volumes} if `true` and `tm` is closed, the clipping will be done on + * \cgalParamBegin{clip_volumes} if `true` and `tm` is closed, the clipping will be done on * the volume \link coref_def_subsec bounded \endlink by `tm` rather than on its surface * (i.e. `tm` will be kept closed). * \cgalParamEnd - * \cgalParamBegin{include_clipper_boundary} if `false` and `close_volumes` is `false` and `tm` is open, the parts of `tm` coplanar with `clipper` + * \cgalParamBegin{include_clipper_boundary} if `false` and `clip_volumes` is `false` and `tm` is open, the parts of `tm` coplanar with `clipper` * will not be part of the output. * \cgalParamEnd * \cgalNamedParamsEnd * * @return `true` if the output surface mesh is manifold. - * If `false` is returned `tm` and `clipper` will be corefined. + * If `false` is returned `tm` and `clipper` are only corefined. */ template diff --git a/Polygon_mesh_processing/test/Polygon_mesh_processing/test_pmp_clip.cpp b/Polygon_mesh_processing/test/Polygon_mesh_processing/test_pmp_clip.cpp index ca35d7462db..c317f339c85 100644 --- a/Polygon_mesh_processing/test/Polygon_mesh_processing/test_pmp_clip.cpp +++ b/Polygon_mesh_processing/test/Polygon_mesh_processing/test_pmp_clip.cpp @@ -30,7 +30,7 @@ int main() Constrained_edge_map ecm1 = tm1.add_property_map("e:cst", false).first; - PMP::clip(tm1, tm2, params::close_volumes(false).edge_is_constrained_map(ecm1)); + PMP::clip(tm1, tm2, params::clip_volumes(false).edge_is_constrained_map(ecm1)); std::ofstream output("clipped_opened.off"); output << tm1; @@ -43,7 +43,7 @@ int main() input >> Q; PMP::clip(P, Q, - params::close_volumes(false) + params::clip_volumes(false) .face_index_map(get(CGAL::face_external_index, P)) ,params::face_index_map(get(CGAL::face_external_index, Q))); assert(P.size_of_vertices() == tm1.number_of_vertices()); @@ -56,7 +56,7 @@ int main() input.open("data-coref/sphere.off"); input >> tm2; - PMP::clip(tm1, tm2, PMP::parameters::close_volumes(true)); + PMP::clip(tm1, tm2, PMP::parameters::clip_volumes(true)); std::ofstream output("clipped_closed.off"); output << tm1; } diff --git a/Polyhedron/demo/Polyhedron/Plugins/Operations_on_polyhedra/Clip_polyhedron_plugin.cpp b/Polyhedron/demo/Polyhedron/Plugins/Operations_on_polyhedra/Clip_polyhedron_plugin.cpp index 13311957b49..6298fe708df 100644 --- a/Polyhedron/demo/Polyhedron/Plugins/Operations_on_polyhedra/Clip_polyhedron_plugin.cpp +++ b/Polyhedron/demo/Polyhedron/Plugins/Operations_on_polyhedra/Clip_polyhedron_plugin.cpp @@ -141,8 +141,9 @@ public : CGAL::Polygon_mesh_processing::clip(*neg_side, plane->plane(), - CGAL::Polygon_mesh_processing::parameters::close_volumes( - ui_widget.close_checkBox->isChecked())); + CGAL::Polygon_mesh_processing::parameters::clip_volumes( + ui_widget.close_checkBox->isChecked()). + throw_on_self_intersection(true)); Item* new_item = new Item(neg_side); new_item->setName(QString("%1 on %2").arg(item->name()).arg("negative side")); new_item->setColor(item->color()); @@ -154,8 +155,9 @@ public : Mesh* pos_side = new Mesh(*item->face_graph()); CGAL::Polygon_mesh_processing::clip(*pos_side, plane->plane().opposite(), - CGAL::Polygon_mesh_processing::parameters::close_volumes( - ui_widget.close_checkBox->isChecked())); + CGAL::Polygon_mesh_processing::parameters::clip_volumes( + ui_widget.close_checkBox->isChecked()). + throw_on_self_intersection(true)); new_item = new Item(pos_side); new_item->setName(QString("%1 on %2").arg(item->name()).arg("positive side")); @@ -237,15 +239,17 @@ public Q_SLOTS: { CGAL::Polygon_mesh_processing::clip(*(sm_item->face_graph()), plane->plane(), - CGAL::Polygon_mesh_processing::parameters::close_volumes( - ui_widget.close_checkBox->isChecked())); + CGAL::Polygon_mesh_processing::parameters::clip_volumes( + ui_widget.close_checkBox->isChecked()). + throw_on_self_intersection(true)); } else { CGAL::Polygon_mesh_processing::clip(*(poly_item->face_graph()), plane->plane(), - CGAL::Polygon_mesh_processing::parameters::close_volumes( - ui_widget.close_checkBox->isChecked())); + CGAL::Polygon_mesh_processing::parameters::clip_volumes( + ui_widget.close_checkBox->isChecked()). + throw_on_self_intersection(true)); } item->invalidateOpenGLBuffers(); From b2fcdf2c9dd2cea653951dc61160107872b4dd86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Loriot?= Date: Mon, 4 Jun 2018 10:29:20 +0200 Subject: [PATCH 19/43] remove polyhedron dependency --- .../include/CGAL/Polygon_mesh_processing/clip.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/clip.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/clip.h index b833b5bdaee..b03153d0979 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/clip.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/clip.h @@ -257,11 +257,11 @@ clip_to_bbox(const Plane_3& plane, // take the convex hull of the points on the negative side+intersection points // overkill... - Polyhedron_3 P; - CGAL::convex_hull_3(points.begin(), points.end(), P); - copy_face_graph(P, tm_out, + TriangleMesh ch_tm; + CGAL::convex_hull_3(points.begin(), points.end(), ch_tm); + copy_face_graph(ch_tm, tm_out, Emptyset_iterator(), Emptyset_iterator(), Emptyset_iterator(), - get(vertex_point, P), vpm_out); + get(vertex_point, ch_tm), vpm_out); return ON_ORIENTED_BOUNDARY; } From 40269797ab77184a62842a54a04ebf071aa370b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Loriot?= Date: Mon, 4 Jun 2018 10:54:32 +0200 Subject: [PATCH 20/43] should -> must --- .../include/CGAL/Polygon_mesh_processing/clip.h | 5 ++--- .../include/CGAL/Polygon_mesh_processing/corefinement.h | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/clip.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/clip.h index b03153d0979..0f645e0290d 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/clip.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/clip.h @@ -281,7 +281,6 @@ clip_to_bbox(const Plane_3& plane, * @tparam TriangleMesh a model of `MutableFaceGraph`, `HalfedgeListGraph` and `FaceListGraph`. * If `TriangleMesh` has an internal property map for `CGAL::face_index_t`, * as a named parameter, then it must be initialized. - * An internal property map for `CGAL::vertex_point_t` should be available. * * @tparam NamedParameters1 a sequence of \ref pmp_namedparameters "Named Parameters" * @tparam NamedParameters2 a sequence of \ref pmp_namedparameters "Named Parameters" @@ -295,7 +294,7 @@ clip_to_bbox(const Plane_3& plane, * \cgalParamBegin{vertex_point_map} * the property map with the points associated to the vertices of `tm` (`clipper`). * If this parameter is omitted, an internal property map for - * `CGAL::vertex_point_t` should be available in `TriangleMesh` + * `CGAL::vertex_point_t` must be available in `TriangleMesh` * \cgalParamEnd * \cgalParamBegin{face_index_map} a property map containing the index of each face of `tm` (`clipper`). * Note that if the property map is writable, the indices of the faces @@ -350,7 +349,7 @@ clip( TriangleMesh& tm, * @tparam TriangleMesh a model of `MutableFaceGraph`, `HalfedgeListGraph` and `FaceListGraph`. * If `TriangleMesh` has an internal property map for `CGAL::face_index_t`, * as a named parameter, then it must be initialized. - * An internal property map for `CGAL::vertex_point_t` should be available. + * An internal property map for `CGAL::vertex_point_t` must be available. * * @tparam NamedParameters a sequence of \ref pmp_namedparameters "Named Parameters" * diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/corefinement.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/corefinement.h index d3619fa91e5..3eb14fae156 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/corefinement.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/corefinement.h @@ -326,7 +326,7 @@ bool does_bound_a_volume(const TriangleMesh& tm) * \cgalParamBegin{vertex_point_map} * the property map with the points associated to the vertices of `tm_out`. * If this parameter is omitted, an internal property map for - * `CGAL::vertex_point_t` should be available in `TriangleMesh` + * `CGAL::vertex_point_t` must be available in `TriangleMesh` * \cgalParamEnd * \cgalParamBegin{edge_is_constrained_map} a property map containing the * constrained-or-not status of each edge of `tm_out`. An edge of `tm_out` is constrained From 987d59ed6728cca8383eec390c3bb35958823266 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Loriot?= Date: Mon, 4 Jun 2018 14:05:12 +0200 Subject: [PATCH 21/43] fix doc typos --- Installation/CHANGES.md | 6 +++--- .../Concepts/PMPCorefinementNewFaceVisitor.h | 2 +- .../Polygon_mesh_processing.txt | 2 +- .../CGAL/Polygon_mesh_processing/corefinement.h | 10 +++++----- .../CGAL/Polygon_mesh_processing/intersection.h | 2 +- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/Installation/CHANGES.md b/Installation/CHANGES.md index 69a90cf1d32..09e3fd51d48 100644 --- a/Installation/CHANGES.md +++ b/Installation/CHANGES.md @@ -36,11 +36,11 @@ Release date: September 2018 - Added a function to apply a transformation to a mesh : - `CGAL::Polygon_mesh_processing::transform()` - Added in corefinement-related functions a new named parameter `new_face_visitor` - that make it possible to pass a visitor to the function in order to track + that makes it possible to pass a visitor to the function in order to track the creation of new faces. - Added in all corefinement-related functions a named parameter `throw_on_self_intersection` - (that replace the `bool` parameter in `corefine()`) that enables to check for - self-intersections faces involved in the intersection before trying to corefine the + (that replaces the `bool` parameter in `corefine()`) that enables to check for + self-intersecting faces involved in the intersection before trying to corefine the input meshes. - Added the function `corefine_and_compute_boolean_operations()` that can be used to compute the result of several Boolean operations between 2 volumes at the same time. diff --git a/Polygon_mesh_processing/doc/Polygon_mesh_processing/Concepts/PMPCorefinementNewFaceVisitor.h b/Polygon_mesh_processing/doc/Polygon_mesh_processing/Concepts/PMPCorefinementNewFaceVisitor.h index 123fc000b21..709a87afccf 100644 --- a/Polygon_mesh_processing/doc/Polygon_mesh_processing/Concepts/PMPCorefinementNewFaceVisitor.h +++ b/Polygon_mesh_processing/doc/Polygon_mesh_processing/Concepts/PMPCorefinementNewFaceVisitor.h @@ -21,7 +21,7 @@ typedef unspecified_type face_descriptor; /// called before the triangulation of `f_split` in `tm`. Note that `f_split` /// will be one of the faces of the triangulation. Each subsequent call to /// `before_subface_created()`/`after_subface_created()` will correspond to - /// the creation of a new face of triangulating `f_split`. + /// the creation of a new face of the triangulation of `f_split`. void before_subface_creations(face_descriptor f_split, Triangle_mesh& tm); /// called when the triangulation of a face in `tm` is finished void after_subface_creations(Triangle_mesh& tm); 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 d56fdd94238..f63a14b103a 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 @@ -216,7 +216,7 @@ a volume, the functions `CGAL::Polygon_mesh_processing::corefine_and_compute_uni `CGAL::Polygon_mesh_processing::corefine_and_compute_intersection()` and `CGAL::Polygon_mesh_processing::corefine_and_compute_difference()` respectively compute the union, the intersection and the difference of the two volumes. If several Boolean operations must be -computed at the same type, the function `corefine_and_compute_boolean_operations()` should be used. +computed at the same time, the function `corefine_and_compute_boolean_operations()` should be used. There is no restriction on the topology of the input volumes. However, there are some requirements on the input to guarantee that diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/corefinement.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/corefinement.h index 3eb14fae156..d53f30fc77c 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/corefinement.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/corefinement.h @@ -313,14 +313,14 @@ bool does_bound_a_volume(const TriangleMesh& tm) * that is used to track the creation of new faces (`np1` only) * \cgalParamEnd * \cgalParamBegin{throw_on_self_intersection} if `true`, for each input triangle mesh, - * the set of triangles closed to the intersection of `tm1` and `tm2` will be + * the set of triangles close to the intersection of `tm1` and `tm2` will be * checked for self-intersection and `CGAL::Polygon_mesh_processing::Corefinement::Self_intersection_exception` * will be thrown if at least one is found (`np1` only). * \cgalParamEnd * \cgalNamedParamsEnd * * @param nps_out tuple of optional sequences of \ref pmp_namedparameters "Named Parameters" each among the ones listed below - * (`tm_out` being use to refer the output surface mesh in `output` corresponding to a given named parameter sequence) + * (`tm_out` being used to refer to the output surface mesh in `output` corresponding to a given named parameter sequence) * * \cgalNamedParamsBegin * \cgalParamBegin{vertex_point_map} @@ -337,7 +337,7 @@ bool does_bound_a_volume(const TriangleMesh& tm) * * @return an array filled as follows: for each operation computed, the position in the array * will contain `true` iff the output surface mesh is manifold, and it is put in the surface mesh - * at the same position in `output`. Note that if an output surface mesh was also + * at the same position as in `output`. Note that if an output surface mesh also was * an input mesh but the output operation was generating a non-manifold mesh, the surface mesh * will only be corefined. */ @@ -621,7 +621,7 @@ corefine_and_compute_boolean_operations( * that is used to track the creation of new faces (`np1` only) * \cgalParamEnd * \cgalParamBegin{throw_on_self_intersection} if `true`, for each input triangle mesh, - * the set of triangles closed to the intersection of `tm1` and `tm2` will be + * the set of triangles close to the intersection of `tm1` and `tm2` will be * checked for self-intersection and `CGAL::Polygon_mesh_processing::Corefinement::Self_intersection_exception` * will be thrown if at least one is found (`np1` only). * \cgalParamEnd @@ -767,7 +767,7 @@ corefine_and_compute_difference( TriangleMesh& tm1, * that is used to track the creation of new faces (`np1` only) * \cgalParamEnd * \cgalParamBegin{throw_on_self_intersection} if `true`, for each input triangle mesh, - * the set of triangles closed to the intersection of `tm1` and `tm2` will be + * the set of triangles close to the intersection of `tm1` and `tm2` will be * checked for self-intersection and `CGAL::Polygon_mesh_processing::Corefinement::Self_intersection_exception` * will be thrown if at least one is found (`np1` only). * \cgalParamEnd diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/intersection.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/intersection.h index 0d866d51544..94ab21cad11 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/intersection.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/intersection.h @@ -1649,7 +1649,7 @@ OutputIterator intersecting_meshes(const TriangleMeshRange& range, * (`tm2`). The two property map types must be the same. * \cgalParamEnd * \cgalParamBegin{throw_on_self_intersection} if `true`, for each input triangle mesh, - * the set of triangles closed to the intersection of `tm1` and `tm2` will be + * the set of triangles close to the intersection of `tm1` and `tm2` will be * checked for self-intersection and `CGAL::Polygon_mesh_processing::Corefinement::Self_intersection_exception` * will be thrown if at least one is found (`np1` only). * \cgalParamEnd From aecefd2e6d0279431e9e65394573dc51a6a75afb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Loriot?= Date: Mon, 4 Jun 2018 15:20:31 +0200 Subject: [PATCH 22/43] include_clipper_boundary -> use_compact_clipper --- BGL/include/CGAL/boost/graph/parameters_interface.h | 2 +- BGL/test/BGL/test_cgal_bgl_named_params.cpp | 6 +++--- .../doc/Polygon_mesh_processing/NamedParameters.txt | 4 ++-- .../include/CGAL/Polygon_mesh_processing/clip.h | 10 +++++----- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/BGL/include/CGAL/boost/graph/parameters_interface.h b/BGL/include/CGAL/boost/graph/parameters_interface.h index 8e030bde218..988528cbd4e 100644 --- a/BGL/include/CGAL/boost/graph/parameters_interface.h +++ b/BGL/include/CGAL/boost/graph/parameters_interface.h @@ -67,7 +67,7 @@ CGAL_add_named_parameter(preserve_genus_t, preserve_genus, preserve_genus) CGAL_add_named_parameter(new_face_visitor_t, new_face_visitor, new_face_visitor) CGAL_add_named_parameter(throw_on_self_intersection_t, throw_on_self_intersection, throw_on_self_intersection) CGAL_add_named_parameter(clip_volumes_t, clip_volumes, clip_volumes) -CGAL_add_named_parameter(include_clipper_boundary_t, include_clipper_boundary, include_clipper_boundary) +CGAL_add_named_parameter(use_compact_clipper_t, use_compact_clipper, use_compact_clipper) // List of named parameters that we use in the package 'Surface Mesh Simplification' CGAL_add_named_parameter(get_cost_policy_t, get_cost_policy, get_cost) diff --git a/BGL/test/BGL/test_cgal_bgl_named_params.cpp b/BGL/test/BGL/test_cgal_bgl_named_params.cpp index 73ac8b7331b..ada231d4e3b 100644 --- a/BGL/test/BGL/test_cgal_bgl_named_params.cpp +++ b/BGL/test/BGL/test_cgal_bgl_named_params.cpp @@ -75,7 +75,7 @@ void test(const NamedParameters& np) assert(get_param(np, CGAL::internal_np::new_face_visitor).v == 42); assert(get_param(np, CGAL::internal_np::throw_on_self_intersection).v == 43); assert(get_param(np, CGAL::internal_np::clip_volumes).v == 44); - assert(get_param(np, CGAL::internal_np::include_clipper_boundary).v == 45); + assert(get_param(np, CGAL::internal_np::use_compact_clipper).v == 45); // Named parameters that we use in the package 'Surface Mesh Simplification' assert(get_param(np, CGAL::internal_np::get_cost_policy).v == 34); @@ -143,7 +143,7 @@ void test(const NamedParameters& np) check_same_type<42>(get_param(np, CGAL::internal_np::new_face_visitor)); check_same_type<43>(get_param(np, CGAL::internal_np::throw_on_self_intersection)); check_same_type<44>(get_param(np, CGAL::internal_np::clip_volumes)); - check_same_type<45>(get_param(np, CGAL::internal_np::include_clipper_boundary)); + check_same_type<45>(get_param(np, CGAL::internal_np::use_compact_clipper)); // Named parameters that we use in the package 'Surface Mesh Simplification' check_same_type<34>(get_param(np, CGAL::internal_np::get_cost_policy)); @@ -210,7 +210,7 @@ int main() .new_face_visitor(A<42>(42)) .throw_on_self_intersection(A<43>(43)) .clip_volumes(A<44>(44)) - .include_clipper_boundary(A<45>(45)) + .use_compact_clipper(A<45>(45)) ); return EXIT_SUCCESS; diff --git a/Polygon_mesh_processing/doc/Polygon_mesh_processing/NamedParameters.txt b/Polygon_mesh_processing/doc/Polygon_mesh_processing/NamedParameters.txt index 3f4f0f8c94b..f5d90b55417 100644 --- a/Polygon_mesh_processing/doc/Polygon_mesh_processing/NamedParameters.txt +++ b/Polygon_mesh_processing/doc/Polygon_mesh_processing/NamedParameters.txt @@ -334,9 +334,9 @@ Parameter used in `clip()` functions to clip a volume rather than a surface. \b Default value is `false` \cgalNPEnd -\cgalNPBegin{include_clipper_boundary} \anchor PMP_include_clipper_boundary +\cgalNPBegin{use_compact_clipper} \anchor PMP_use_compact_clipper Parameter used in `clip()` functions to indicate whether the boundary of the clipper -should be considered as part of the volume or not. +should be considered as part of the clipping volume or not. \n \b Type : `bool` \n \b Default value is `true` diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/clip.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/clip.h index 0f645e0290d..b1ca57869b0 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/clip.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/clip.h @@ -146,7 +146,7 @@ clip_open_impl( TriangleMesh& tm, { // A patch without no vertex incident to a non-constrained edges // is a coplanar patch: drop it or keep it! - if (!boost::choose_param(boost::get_param(np_tm, internal_np::include_clipper_boundary), true)) + if (!boost::choose_param(boost::get_param(np_tm, internal_np::use_compact_clipper), true)) { for (std::size_t cc_id = cc_not_handled.find_first(); cc_id < cc_not_handled.npos; @@ -312,8 +312,8 @@ clip_to_bbox(const Plane_3& plane, * the volume \link coref_def_subsec bounded \endlink by `tm` rather than on its surface * (i.e. `tm` will be kept closed). * \cgalParamEnd - * \cgalParamBegin{include_clipper_boundary} if `false` and `clip_volumes` is `false` and `tm` is open, the parts of `tm` coplanar with `clipper` - * will not be part of the output. + * \cgalParamBegin{use_compact_clipper} if `false` and `clip_volumes` is `false` and `tm` is open, the parts of `tm` coplanar with `clipper` + * will not be part of the output. * \cgalParamEnd * \cgalNamedParamsEnd * @@ -375,8 +375,8 @@ clip( TriangleMesh& tm, * the volume \link coref_def_subsec bounded \endlink by `tm` rather than on its surface * (i.e. `tm` will be kept closed). * \cgalParamEnd - * \cgalParamBegin{include_clipper_boundary} if `false` and `clip_volumes` is `false` and `tm` is open, the parts of `tm` coplanar with `plane` - * will not be part of the output. + * \cgalParamBegin{use_compact_clipper} if `false` and `clip_volumes` is `false` and `tm` is open, the parts of `tm` coplanar with `plane` + * will not be part of the output. * \cgalNamedParamsEnd * * @return `true` if the output surface mesh is manifold. From 6a6172eb0a50f539e7023dd285d5d291242897ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Loriot?= Date: Mon, 4 Jun 2018 15:45:59 +0200 Subject: [PATCH 23/43] add a section in the user manual for clipping --- .../Polygon_mesh_processing.txt | 22 ++++++++++++++++++ .../fig/clip_compact.png | Bin 0 -> 56673 bytes .../fig/clip_open_close.png | Bin 0 -> 57813 bytes .../CGAL/Polygon_mesh_processing/clip.h | 2 ++ 4 files changed, 24 insertions(+) create mode 100644 Polygon_mesh_processing/doc/Polygon_mesh_processing/fig/clip_compact.png create mode 100644 Polygon_mesh_processing/doc/Polygon_mesh_processing/fig/clip_open_close.png 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 f63a14b103a..9fb17926313 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 @@ -253,6 +253,28 @@ edges will be split at each intersection with a triangle but the position of the intersection point might create self-intersections due to the limited precision of floating point numbers. +\subsection coref_clip Clipping + +As a natural extension, some clipping functionalities with a volume bounded by a closed mesh +and a halfspace (defined by the negative side of a plane to be consistent with +the outward normal convention) are offered. The functions `CGAL::Polygon_mesh_processing::clip()` +have some options to select whether the clipping should be done at the volume or surface +level, and also if the clipper should be considered as compact or not. This is illustrated on +\cgalFigureRef{coref_clip_close_open} and \cgalFigureRef{coref_clip_compact}. + +\cgalFigureBegin{coref_clip_close_open, clip_open_close.png} +Clipping a cube with a halfspace. From left to right: (i) initial cube and the plane +defining the clipping halfspace; (ii) `close_volumes=false`: clipping of the surface mesh (boundary edges +depicted in red); (iii) `close_volumes=true`: clipping of the volume bounded by the surface mesh. +\cgalFigureEnd + +\cgalFigureBegin{coref_clip_compact, clip_compact.png} +Clipping a cube with a halfspace: compacity of the clipper (`close_volumes=false` in both cases). +From left to right: (i) initial cube and the plane defining the clipping halfspace, +note that a whole face of the cube (2 triangles) is exactly contained in the plane; +(ii) `use_compact_clipper=true`: clipping of the surface mesh with a compact halfspace: coplanar faces are part of the output; +(iii) `use_compact_clipper=false`: clipping of the surface mesh with a non-compact halfspace: coplanar faces are not part of the output. +\cgalFigureEnd \subsection coref_ex_subsec Examples diff --git a/Polygon_mesh_processing/doc/Polygon_mesh_processing/fig/clip_compact.png b/Polygon_mesh_processing/doc/Polygon_mesh_processing/fig/clip_compact.png new file mode 100644 index 0000000000000000000000000000000000000000..6640346dd576c72c003b4a50c60aace42b80af30 GIT binary patch literal 56673 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%0DxNTc3L})k?yhuB3j6|B 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 literal 0 HcmV?d00001 diff --git a/Polygon_mesh_processing/doc/Polygon_mesh_processing/fig/clip_open_close.png b/Polygon_mesh_processing/doc/Polygon_mesh_processing/fig/clip_open_close.png new file mode 100644 index 0000000000000000000000000000000000000000..caf998a2d7b6f47d0e208fc31e78f81deacc7a33 GIT binary patch literal 57813 zcmY&f1z1#D*B-iCLRv*S1*Abjlt!exR6>TK8w5#_77%F=5n%`ghVD?1ZWy{-Qks8{ z-tYVFKlgg?W5e;u$x@JYwcK{7 z;ReqBN>RC=8Smwt-3=dSqs=sI{6eY1T_pksikSm)ElJV#2nPC>r6&CDP0cHoTd{G% z8L7_|Cj|%B{KW#+?!_x6^Vq6P8lCx%?tfLBob<6*@v+}6$yhxtuq@LjR8VlsIom~I zD=1jDHMBcCP*70#$YKXYzx{<88>*nNzz@4~`}S=u#!#q&HZ>tK{LkleoPg_{oDWuW ziV7@@M%85C!$Bc00)BWq#S&Zu&oCyO4*&dVyelgFF+J)*3JP5v{ZZj|p@DR$pOcXZ z6QCYRfqG<{71y78m#x+m6?8hAz&(dv?^Q&rnOQ&G^@jchTTV-d}G4ZV-q15 zP!6Uo{r5I)LV1xK*8dq^gSzJ?>bpdXzdl9&8r(x+fx7329b9Tg?a&v(e=GqzmxJB$-vyIKjm!RL+<$zT6YN3` zHp;Tk&i{-;4o0CxJs_v+?{4QPgJXo680r&GgSw#wwc?5YJR=?*b?J|#|2*R{C+ZoH zzea$Yye!rfKf+MY|JeL@uUPJeLKW~)mOZBZJMLpUaL)~tKg0?B_3kIaU_<{5`p>wx zu`E%mWs90M_~;PGi`))Dtr-i^KkKZJeOEN;Je-l|OhZEE0SGzl)0jXz2gQ&k%`wKlMtQo9h({l<2m?2j;9Q zXm^sRxa}%_Z5+|q+Xl{>zcMYsfvg>Qmm?6Z*7wjLhDJsMlau%m8%IZt%gwbl2!tj| zktV8RczfwUTtdR)ekvNoeyn&o^&vPT?R^el6FAHtJww?I_WB-01he@)+1;ZBKY1qU z!4wNZAB#8GrQ%D09JE1VR2=rB_Y<*$l!L#5Jj0o>-X?ASh7dh3RSd%$(#!OovLIZk0OPJHtm>JJMm`n{UoOM{mqNc&2F zylEqr?$cw)CmT}vE3x;t{b}y_7ZH2)-z3Nuq~*0Y=`ft+FBxry546Gs%m@2lrzE*o z346cYRMqdNDb0$WRi`FCCyl@)i^4{OWXDQum$jEqnL-{2(JBhj%870@x3oBBYZYW` z*_!ja%IDfXWFZIN{XCJ8c&pMVB`O-8oqa3R4J@K_ZN`f=MV5o~ci=R~S<}?~Q2C{C zg;}$j8D=u{21oj+5$v4O{oTXZVnN!rFLUUs1I%YFrbUER=5J5H>XxzA>AOD`DH*lo zChMvehDXE}6G5YHkqxewW!*QI%?&fkeUzQ#?0-#2A1$bFOfPRXd;jow=G~qrGk=VY zP%K&7UB|Fi(_p!sGz$iZtD9S%T=iBd@8#})4Ae616Lh^3zen7K@^El|tyij7UtOM~ zL0U&Ge682u=bUij>c~*nPmf8H!b^%y`wA=cHSL{r?1PPl@I6$x6p89#_6yC(s@~v~ zSe(jJl~O$Ffhixsv$IOs+=0l;cYL*M6Zm{L48PwTy}R2@yba;DH%TVk`Py1@*ctKI zfYJZNY_C_Ma(V35m!<13Z88sXpUA^??>$(DtHQ}92~^U5uE|5 z1Dh57n~T#MMn)Aj4kOtWI+2x?CyNdaXT#;&+ur3B?J+(K4Bj+R*pQ~VtQ1EYj@=I# z*>rKL9_pyW7jH4gD%cr~)_5QRd3Alilmh4H80Y>onme*Z#5`Gow5o1C+XOxXWXBdd znx92KQfS*%Sw4ANQQn#_VkJ9-Z)7hc$+KNB>Y`VXZp*X&k%HKV!sIzdw_NpMDX*8= zz7xc&wgoy_9&sRfTie#ACUu3}Y=too$4Li`-t$13q@3WxslCR< zYwdMC5fwyygz4xaC#AEx*j#g*&?jGiCd8Gu;9U>a-wYUPtXJwa;cby|Lm#U+&Na&G&>SPuXK-$v^Jcmf7yMaAw$q2rHQ=EPq!nLhBYzBrTIX$-S9i>pm0UO-7?^-m} z_+F$@*vVLKO
  • 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 Z78Fq8xO5ZtWQzoljg_5ciMjjbe*t?iZZ-e_ literal 0 HcmV?d00001 diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/clip.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/clip.h index b1ca57869b0..cc1832e207f 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/clip.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/clip.h @@ -272,6 +272,7 @@ clip_to_bbox(const Plane_3& plane, * clips `tm` by keeping the part that is inside the volume \link coref_def_subsec bounded \endlink * by `clipper`. * If `tm` is closed, the clipped part can be closed too if the named parameter `clip_volumes` is set to `true`. + * See subsection \ref coref_clip for more details. * \attention With the current implementation, `clipper` will be modified (refined with the intersection with `tm`). * * \pre \link CGAL::Polygon_mesh_processing::does_self_intersect() `!CGAL::Polygon_mesh_processing::does_self_intersect(tm1)` \endlink @@ -342,6 +343,7 @@ clip( TriangleMesh& tm, * \ingroup PMP_corefinement_grp * clips `tm` by keeping the part that is on the negative side of `plane` (side opposite to its normal vector). * If `tm` is closed, the clipped part can be closed too if the named parameter `clip_volumes` is set to `true`. + * See subsection \ref coref_clip for more details. * * \note In the current implementation it is not possible to set the vertex point map and the default will be used. * \pre \link CGAL::Polygon_mesh_processing::does_self_intersect() `!CGAL::Polygon_mesh_processing::does_self_intersect(tm)` \endlink From 0202bc879ecdb3a6e69793f91d99fd4ed10b397a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Loriot?= Date: Mon, 4 Jun 2018 17:21:01 +0200 Subject: [PATCH 24/43] add an extra operator for plane-line(pt,pt) --- Kernel_23/include/CGAL/Kernel/function_objects.h | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/Kernel_23/include/CGAL/Kernel/function_objects.h b/Kernel_23/include/CGAL/Kernel/function_objects.h index ec0681875e5..ef3224824ba 100644 --- a/Kernel_23/include/CGAL/Kernel/function_objects.h +++ b/Kernel_23/include/CGAL/Kernel/function_objects.h @@ -2049,6 +2049,20 @@ namespace CommonKernelFunctors { CGAL_assertion(e_pt!=NULL); return *e_pt; } + + Point + operator()(const Plane& plane, + const Point& l1, const Point& l2) const + { + Line line = construct_line( l1, l2 ); + + typename cpp11::result_of::type + res = typename K::Intersect_3()(plane,line); + CGAL_assertion(res!=boost::none); + const Point* e_pt = boost::get(&(*res)); + CGAL_assertion(e_pt!=NULL); + return *e_pt; + } }; template From 3a625d1a282b063aa847acd5a4051fc8e71fc3ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Loriot?= Date: Mon, 4 Jun 2018 17:21:28 +0200 Subject: [PATCH 25/43] more robust construction --- .../include/CGAL/Polygon_mesh_processing/clip.h | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/clip.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/clip.h index cc1832e207f..5f091d64c4f 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/clip.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/clip.h @@ -223,10 +223,8 @@ clip_to_bbox(const Plane_3& plane, if (orientations[i2]==ON_ORIENTED_BOUNDARY) continue; if (orientations[i1]!=orientations[i2]) points.push_back( - boost::get( - *intersection(plane, Segment_3(corners[i1], corners[i2]) ) - ) - ); + Geom_traits::Construct_plane_line_intersection_point_3() + (plane, Segment_3(corners[i1], corners[i2])) ); } From 8d6a99203545efead31ea8068a80a971bdaaf29d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Loriot?= Date: Tue, 5 Jun 2018 12:33:13 +0200 Subject: [PATCH 26/43] remove the use of the convex hull --- .../CGAL/Polygon_mesh_processing/clip.h | 223 ++++++++++++++---- 1 file changed, 171 insertions(+), 52 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/clip.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/clip.h index 5f091d64c4f..c2b26c662e3 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/clip.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/clip.h @@ -28,11 +28,10 @@ #include #include #include +#include #include -#include - namespace CGAL{ namespace Polygon_mesh_processing { @@ -165,7 +164,24 @@ clip_open_impl( TriangleMesh& tm, return true; } -/// \todo remove convex_hull_3 +template +int +inter_pt_index(int i, int j, + const Plane_3& plane, + std::vector& points, + std::map, int>& id_map) +{ + std::pair, int>::iterator, bool> res = + id_map.insert(std::make_pair(make_sorted_pair(i,j), + static_cast (points.size()))); + if (res.second) + points.push_back( + typename Geom_traits::Construct_plane_line_intersection_point_3() + (plane, points[i], points[j]) ); + + return res.first->second; +} + template @@ -177,7 +193,6 @@ clip_to_bbox(const Plane_3& plane, { typedef typename GetGeomTraits::type Geom_traits; typedef typename Geom_traits::Point_3 Point_3; - typedef typename Geom_traits::Segment_3 Segment_3; typedef typename GetVertexPointMap::type Vpm; @@ -185,16 +200,15 @@ clip_to_bbox(const Plane_3& plane, get_property_map(boost::vertex_point, tm_out)); - cpp11::array corners= {{ - Point_3(bbox.xmin(),bbox.ymin(),bbox.zmin()), - Point_3(bbox.xmin(),bbox.ymax(),bbox.zmin()), - Point_3(bbox.xmax(),bbox.ymax(),bbox.zmin()), - Point_3(bbox.xmax(),bbox.ymin(),bbox.zmin()), - Point_3(bbox.xmin(),bbox.ymin(),bbox.zmax()), - Point_3(bbox.xmin(),bbox.ymax(),bbox.zmax()), - Point_3(bbox.xmax(),bbox.ymax(),bbox.zmax()), - Point_3(bbox.xmax(),bbox.ymin(),bbox.zmax()) - }}; + std::vector corners(8); + corners[0] = Point_3(bbox.xmin(),bbox.ymin(),bbox.zmin()); + corners[1] = Point_3(bbox.xmin(),bbox.ymax(),bbox.zmin()); + corners[2] = Point_3(bbox.xmax(),bbox.ymax(),bbox.zmin()); + corners[3] = Point_3(bbox.xmax(),bbox.ymin(),bbox.zmin()); + corners[4] = Point_3(bbox.xmin(),bbox.ymin(),bbox.zmax()); + corners[5] = Point_3(bbox.xmin(),bbox.ymax(),bbox.zmax()); + corners[6] = Point_3(bbox.xmax(),bbox.ymax(),bbox.zmax()); + corners[7] = Point_3(bbox.xmax(),bbox.ymin(),bbox.zmax()); cpp11::array orientations = {{ plane.oriented_side(corners[0]), @@ -209,57 +223,162 @@ clip_to_bbox(const Plane_3& plane, std::vector points; - // look for intersections on edges - cpp11::array edge_indices = {{ // 2 *12 edges - 0,1, 1,2, 2,3, 3,0, // bottom face edges - 4,5, 5,6, 6,7, 7,4, // top face edges - 0,4, 1,5, 2,6, 3,7 - }}; + // description of faces + cpp11::array face_indices = + {{ 0, 1, 2, 3, + 2, 1, 5, 6, + 3, 2, 6, 7, + 1, 0, 4, 5, + 4, 0, 3, 7, + 6, 5, 4, 7 }}; - for (int i=0; i<12; ++i) + std::map, int> id_map; + std::vector< std::vector > output_faces(6); + bool all_in = true; + bool all_out = true; + std::set in_point_ids; // to collect the set of points in the clipped bbox + + // for each face of the bbox, we look for intersection of the plane with its edges + for (int i=0; i<6; ++i) { - int i1=edge_indices[2*i], i2=edge_indices[2*i+1]; - if (orientations[i1]==ON_ORIENTED_BOUNDARY) continue; - if (orientations[i2]==ON_ORIENTED_BOUNDARY) continue; - if (orientations[i1]!=orientations[i2]) - points.push_back( - Geom_traits::Construct_plane_line_intersection_point_3() - (plane, Segment_3(corners[i1], corners[i2])) ); - } - - - Oriented_side last_os = ON_ORIENTED_BOUNDARY; - for (int i=0; i<8; ++i) - if (orientations[i]!=ON_ORIENTED_BOUNDARY) + for (int k=0; k< 4; ++k) { - if (last_os==ON_ORIENTED_BOUNDARY) - last_os=orientations[i]; + int current_id = face_indices[4*i + k]; + int next_id = face_indices[4*i + (k+1)%4]; + + if ( orientations[ current_id ] != ON_POSITIVE_SIDE ) + { + all_out=false; + // point on or on the negative side + output_faces[i].push_back( current_id ); + in_point_ids.insert( output_faces[i].back() ); + // check for intersection of the edge + if (orientations[ current_id ] == ON_NEGATIVE_SIDE && + orientations[ next_id ] == ON_POSITIVE_SIDE) + { + output_faces[i].push_back( + inter_pt_index(current_id, next_id, plane, corners, id_map) ); + in_point_ids.insert( output_faces[i].back() ); + } + } else { - if(last_os!=orientations[i]) + all_in = false; + // check for intersection of the edge + if ( orientations[ next_id ] == ON_NEGATIVE_SIDE ) { - last_os=ON_ORIENTED_BOUNDARY; - break; + output_faces[i].push_back( + inter_pt_index(current_id, next_id, plane, corners, id_map) ); + in_point_ids.insert( output_faces[i].back() ); } } } + CGAL_assertion( output_faces[i].empty() || output_faces[i].size() >= 3 ); + } // the intersection is the full bbox - if (last_os!=ON_ORIENTED_BOUNDARY) - return last_os; + if (all_in) return ON_NEGATIVE_SIDE; + if (all_out) return ON_POSITIVE_SIDE; - //add points on negative side and on the plane - for (int i=0; i<8; ++i) - if (orientations[i]!=ON_POSITIVE_SIDE) - points.push_back(corners[i]); + // build the clipped bbox + typedef boost::graph_traits graph_traits; + typedef typename graph_traits::vertex_descriptor vertex_descriptor; + typedef typename graph_traits::halfedge_descriptor halfedge_descriptor; + typedef typename graph_traits::face_descriptor face_descriptor; + + std::map out_vertices; + BOOST_FOREACH(int i, in_point_ids) + { + vertex_descriptor v = add_vertex(tm_out); + out_vertices.insert( std::make_pair(i, v ) ); + put(vpm_out, v, corners[i]); + } + + std::map< std::pair, halfedge_descriptor> hedge_map; + const halfedge_descriptor null_hedge = graph_traits::null_halfedge(); + const face_descriptor null_fd = graph_traits::null_face(); + BOOST_FOREACH( const std::vector& findices, output_faces) + { + if (findices.empty()) continue; + const face_descriptor fd=add_face(tm_out); + int prev_id = findices.back(); + + // create of recover face boundary halfedges + std::vector hedges; + hedges.reserve(findices.size()); + BOOST_FOREACH( int current_id, findices) + { + vertex_descriptor src = out_vertices[prev_id], tgt = out_vertices[current_id]; + + std::pair, + halfedge_descriptor>::iterator, bool> res = + hedge_map.insert( std::make_pair(std::make_pair(prev_id, current_id), null_hedge) ); + if (res.second) + { + res.first->second = halfedge( add_edge(tm_out), tm_out); + hedge_map.insert( std::make_pair(std::make_pair(current_id, prev_id), + opposite(res.first->second, tm_out) ) ); + set_face(opposite(res.first->second, tm_out), null_fd, tm_out); + + } + hedges.push_back(res.first->second); + // set edge source and target + set_target(hedges.back(), tgt, tm_out); + set_target(opposite(hedges.back(), tm_out), src, tm_out); + // set face pointer of halfedges + set_face(hedges.back(), fd, tm_out); + // set vertex halfedge + set_halfedge(src, opposite(hedges.back(), tm_out), tm_out); + set_halfedge(tgt, hedges.back(), tm_out); + + if (current_id==findices.front()) + set_halfedge(fd, hedges.back(), tm_out); + + prev_id = current_id; + } + CGAL_assertion(hedges.size() == findices.size()); + + // set next/prev relationship + halfedge_descriptor prev_h=hedges.back(); + BOOST_FOREACH(halfedge_descriptor h, hedges) + { + set_next(prev_h, h, tm_out); + prev_h = h; + } + } + + // handle the face of the plane: + // look for a border halfedge and reconstruct the face of the plane + // by turning around vertices inside the mesh constructed above + // until we reach another border halfedge + BOOST_FOREACH(halfedge_descriptor h, halfedges(tm_out)) + { + if (face(h, tm_out) == null_fd) + { + face_descriptor fd = add_face(tm_out); + set_halfedge(fd, h, tm_out); + + halfedge_descriptor h_prev=h; + halfedge_descriptor h_curr=h; + do{ + h_curr=opposite(h_curr, tm_out); + do{ + h_curr=opposite(prev(h_curr, tm_out), tm_out); + } while(face(h_curr, tm_out) != null_fd && h_curr!=h); + set_face(h_prev, fd, tm_out); + set_next(h_prev, h_curr, tm_out); + if (h_curr==h) + break; + h_prev=h_curr; + } while(true); + break; + } + } + CGAL_assertion(is_valid_polygon_mesh(tm_out)); + + // triangulate the faces + CGAL::Polygon_mesh_processing::triangulate_faces(tm_out, np); - // take the convex hull of the points on the negative side+intersection points - // overkill... - TriangleMesh ch_tm; - CGAL::convex_hull_3(points.begin(), points.end(), ch_tm); - copy_face_graph(ch_tm, tm_out, - Emptyset_iterator(), Emptyset_iterator(), Emptyset_iterator(), - get(vertex_point, ch_tm), vpm_out); return ON_ORIENTED_BOUNDARY; } From 26015810961215ace7d0404e85c7aaaf7f1a2d93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Loriot?= Date: Tue, 5 Jun 2018 12:54:46 +0200 Subject: [PATCH 27/43] catches exceptions thrown by (co)refinement related plugins --- .../CGAL/Polygon_mesh_processing/clip.h | 2 +- .../Clip_polyhedron_plugin.cpp | 94 +++++++++------- .../Plugins/PMP/Corefinement_plugin.cpp | 101 ++++++++++-------- .../Plugins/PMP/Repair_polyhedron_plugin.cpp | 24 +++-- 4 files changed, 129 insertions(+), 92 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/clip.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/clip.h index c2b26c662e3..66c45b11b77 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/clip.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/clip.h @@ -223,7 +223,7 @@ clip_to_bbox(const Plane_3& plane, std::vector points; - // description of faces + // description of faces of the bbox cpp11::array face_indices = {{ 0, 1, 2, 3, 2, 1, 5, 6, diff --git a/Polyhedron/demo/Polyhedron/Plugins/Operations_on_polyhedra/Clip_polyhedron_plugin.cpp b/Polyhedron/demo/Polyhedron/Plugins/Operations_on_polyhedra/Clip_polyhedron_plugin.cpp index 6298fe708df..0b3b62dcf5d 100644 --- a/Polyhedron/demo/Polyhedron/Plugins/Operations_on_polyhedra/Clip_polyhedron_plugin.cpp +++ b/Polyhedron/demo/Polyhedron/Plugins/Operations_on_polyhedra/Clip_polyhedron_plugin.cpp @@ -139,33 +139,39 @@ public : { Mesh* neg_side = new Mesh(*item->face_graph()); - CGAL::Polygon_mesh_processing::clip(*neg_side, - plane->plane(), - CGAL::Polygon_mesh_processing::parameters::clip_volumes( - ui_widget.close_checkBox->isChecked()). - throw_on_self_intersection(true)); - Item* new_item = new Item(neg_side); - new_item->setName(QString("%1 on %2").arg(item->name()).arg("negative side")); - new_item->setColor(item->color()); - new_item->setRenderingMode(item->renderingMode()); - new_item->setVisible(item->visible()); - scene->addItem(new_item); - new_item->invalidateOpenGLBuffers(); - // part on the positive side - Mesh* pos_side = new Mesh(*item->face_graph()); - CGAL::Polygon_mesh_processing::clip(*pos_side, - plane->plane().opposite(), - CGAL::Polygon_mesh_processing::parameters::clip_volumes( - ui_widget.close_checkBox->isChecked()). - throw_on_self_intersection(true)); + try { + CGAL::Polygon_mesh_processing::clip(*neg_side, + plane->plane(), + CGAL::Polygon_mesh_processing::parameters::clip_volumes( + ui_widget.close_checkBox->isChecked()). + throw_on_self_intersection(true)); + Item* new_item = new Item(neg_side); + new_item->setName(QString("%1 on %2").arg(item->name()).arg("negative side")); + new_item->setColor(item->color()); + new_item->setRenderingMode(item->renderingMode()); + new_item->setVisible(item->visible()); + scene->addItem(new_item); + new_item->invalidateOpenGLBuffers(); + // part on the positive side + Mesh* pos_side = new Mesh(*item->face_graph()); + CGAL::Polygon_mesh_processing::clip(*pos_side, + plane->plane().opposite(), + CGAL::Polygon_mesh_processing::parameters::clip_volumes( + ui_widget.close_checkBox->isChecked()). + throw_on_self_intersection(true)); - new_item = new Item(pos_side); - new_item->setName(QString("%1 on %2").arg(item->name()).arg("positive side")); - new_item->setColor(item->color()); - new_item->setRenderingMode(item->renderingMode()); - new_item->setVisible(item->visible()); - scene->addItem(new_item); - new_item->invalidateOpenGLBuffers(); + new_item = new Item(pos_side); + new_item->setName(QString("%1 on %2").arg(item->name()).arg("positive side")); + new_item->setColor(item->color()); + new_item->setRenderingMode(item->renderingMode()); + new_item->setVisible(item->visible()); + scene->addItem(new_item); + new_item->invalidateOpenGLBuffers(); + } + catch(CGAL::Polygon_mesh_processing::Corefinement::Self_intersection_exception) + { + messages->warning(tr("The requested operation is not possible due to the presence of self-intersections in the region handled.")); + } } public Q_SLOTS: void on_plane_destroyed() @@ -235,22 +241,28 @@ public Q_SLOTS: if (ui_widget.clip_radioButton->isChecked()) { - if(sm_item) - { - CGAL::Polygon_mesh_processing::clip(*(sm_item->face_graph()), - plane->plane(), - CGAL::Polygon_mesh_processing::parameters::clip_volumes( - ui_widget.close_checkBox->isChecked()). - throw_on_self_intersection(true)); - } - else - { - CGAL::Polygon_mesh_processing::clip(*(poly_item->face_graph()), - plane->plane(), - CGAL::Polygon_mesh_processing::parameters::clip_volumes( - ui_widget.close_checkBox->isChecked()). - throw_on_self_intersection(true)); + try{ + if(sm_item) + { + CGAL::Polygon_mesh_processing::clip(*(sm_item->face_graph()), + plane->plane(), + CGAL::Polygon_mesh_processing::parameters::clip_volumes( + ui_widget.close_checkBox->isChecked()). + throw_on_self_intersection(true)); + } + else + { + CGAL::Polygon_mesh_processing::clip(*(poly_item->face_graph()), + plane->plane(), + CGAL::Polygon_mesh_processing::parameters::clip_volumes( + ui_widget.close_checkBox->isChecked()). + throw_on_self_intersection(true)); + } + } + catch(CGAL::Polygon_mesh_processing::Corefinement::Self_intersection_exception) + { + messages->warning(tr("The requested operation is not possible due to the presence of self-intersections in the region handled.")); } item->invalidateOpenGLBuffers(); viewer->update(); diff --git a/Polyhedron/demo/Polyhedron/Plugins/PMP/Corefinement_plugin.cpp b/Polyhedron/demo/Polyhedron/Plugins/PMP/Corefinement_plugin.cpp index b4f592f8681..813adb803e6 100644 --- a/Polyhedron/demo/Polyhedron/Plugins/PMP/Corefinement_plugin.cpp +++ b/Polyhedron/demo/Polyhedron/Plugins/PMP/Corefinement_plugin.cpp @@ -167,11 +167,17 @@ private: } QApplication::setOverrideCursor(Qt::WaitCursor); - PMP::corefine(*item1->face_graph(), *item2->face_graph(), params::throw_on_self_intersection(true)); - item1->invalidateOpenGLBuffers(); - item2->invalidateOpenGLBuffers(); - scene->itemChanged(item2); - scene->itemChanged(item1); + try{ + PMP::corefine(*item1->face_graph(), *item2->face_graph(), params::throw_on_self_intersection(true)); + item1->invalidateOpenGLBuffers(); + item2->invalidateOpenGLBuffers(); + scene->itemChanged(item2); + scene->itemChanged(item1); + } + catch(CGAL::Polygon_mesh_processing::Corefinement::Self_intersection_exception) + { + messages->warning(tr("The requested operation is not possible due to the presence of self-intersections in the neighborhood of the intersection.")); + } // default cursor QApplication::restoreOverrideCursor(); } @@ -195,46 +201,53 @@ private: FaceGraph* new_poly = new FaceGraph(); QString str_op; FaceGraph P, Q; - switch(op) + try{ + switch(op) + { + case CRF_UNION: + P = *first_item->face_graph(), Q = *item->face_graph(); + if (! PMP::corefine_and_compute_union(P, Q, *new_poly, params::throw_on_self_intersection(true)) ) + { + delete new_poly; + messages->warning(tr("The result of the requested operation is not manifold and has not been computed.")); + // default cursor + QApplication::restoreOverrideCursor(); + return; + } + str_op = "Union"; + break; + case CRF_INTER: + P = *first_item->polyhedron(), Q = *item->polyhedron(); + if (! PMP::corefine_and_compute_intersection(P, Q, *new_poly, params::throw_on_self_intersection(true)) ) + { + delete new_poly; + messages->warning(tr("The result of the requested operation is not manifold and has not been computed.")); + // default cursor + QApplication::restoreOverrideCursor(); + return; + } + str_op = "Intersection"; + break; + case CRF_MINUS_OP: + std::swap(first_item, item); + CGAL_FALLTHROUGH; + case CRF_MINUS: + P = *first_item->polyhedron(), Q = *item->polyhedron(); + if (! PMP::corefine_and_compute_difference(P, Q, *new_poly, params::throw_on_self_intersection(true)) ) + { + delete new_poly; + messages->warning(tr("The result of the requested operation is not manifold and has not been computed.")); + // default cursor + QApplication::restoreOverrideCursor(); + return; + } + str_op = "Difference"; + } + } + catch(CGAL::Polygon_mesh_processing::Corefinement::Self_intersection_exception) { - case CRF_UNION: - P = *first_item->face_graph(), Q = *item->face_graph(); - if (! PMP::corefine_and_compute_union(P, Q, *new_poly, params::throw_on_self_intersection(true)) ) - { - delete new_poly; - messages->warning(tr("The result of the requested operation is not manifold and has not been computed.")); - // default cursor - QApplication::restoreOverrideCursor(); - return; - } - str_op = "Union"; - break; - case CRF_INTER: - P = *first_item->polyhedron(), Q = *item->polyhedron(); - if (! PMP::corefine_and_compute_intersection(P, Q, *new_poly, params::throw_on_self_intersection(true)) ) - { - delete new_poly; - messages->warning(tr("The result of the requested operation is not manifold and has not been computed.")); - // default cursor - QApplication::restoreOverrideCursor(); - return; - } - str_op = "Intersection"; - break; - case CRF_MINUS_OP: - std::swap(first_item, item); - CGAL_FALLTHROUGH; - case CRF_MINUS: - P = *first_item->polyhedron(), Q = *item->polyhedron(); - if (! PMP::corefine_and_compute_difference(P, Q, *new_poly, params::throw_on_self_intersection(true)) ) - { - delete new_poly; - messages->warning(tr("The result of the requested operation is not manifold and has not been computed.")); - // default cursor - QApplication::restoreOverrideCursor(); - return; - } - str_op = "Difference"; + messages->warning(tr("The requested operation is not possible due to the presence of self-intersections in the neighborhood of the intersection.")); + QApplication::restoreOverrideCursor(); } first_item->invalidateOpenGLBuffers(); diff --git a/Polyhedron/demo/Polyhedron/Plugins/PMP/Repair_polyhedron_plugin.cpp b/Polyhedron/demo/Polyhedron/Plugins/PMP/Repair_polyhedron_plugin.cpp index 7bab9f586a7..f2f7ddf66b7 100644 --- a/Polyhedron/demo/Polyhedron/Plugins/PMP/Repair_polyhedron_plugin.cpp +++ b/Polyhedron/demo/Polyhedron/Plugins/PMP/Repair_polyhedron_plugin.cpp @@ -203,7 +203,13 @@ void Polyhedron_demo_repair_polyhedron_plugin::on_actionAutorefine_triggered(Sce qobject_cast(scene->item(index)); if (poly_item) { - CGAL::Polygon_mesh_processing::experimental::autorefine(*poly_item->polyhedron()); + try{ + CGAL::Polygon_mesh_processing::experimental::autorefine(*poly_item->polyhedron()); + } + catch(CGAL::Polygon_mesh_processing::Corefinement::Triple_intersection_exception) + { + messages->warning(tr("The result of the requested operation is not handled (triple intersection).")); + } poly_item->invalidateOpenGLBuffers(); Q_EMIT poly_item->itemChanged(); } @@ -225,11 +231,17 @@ void Polyhedron_demo_repair_polyhedron_plugin::on_actionAutorefineAndRMSelfInter qobject_cast(scene->item(index)); if (poly_item) { - bool solved = - CGAL::Polygon_mesh_processing::experimental:: - autorefine_and_remove_self_intersections(*poly_item->polyhedron()); - if (!solved) - messages->information(tr("Self-intersection could not be removed due to non-manifold edges in the output")); + try{ + bool solved = + CGAL::Polygon_mesh_processing::experimental:: + autorefine_and_remove_self_intersections(*poly_item->polyhedron()); + if (!solved) + messages->information(tr("Self-intersection could not be removed due to non-manifold edges in the output")); + } + catch(CGAL::Polygon_mesh_processing::Corefinement::Triple_intersection_exception) + { + messages->warning(tr("The result of the requested operation is not handled (triple intersection).")); + } poly_item->invalidateOpenGLBuffers(); Q_EMIT poly_item->itemChanged(); } From 38c44cf11bd15f8907c8f985ec3f73699e8124c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Loriot?= Date: Tue, 5 Jun 2018 13:45:23 +0200 Subject: [PATCH 28/43] make sure face index maps are identical when clipping with a plane --- .../CGAL/Polygon_mesh_processing/clip.h | 29 +++++++++++++++---- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/clip.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/clip.h index 66c45b11b77..ff8fb03e910 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/clip.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/clip.h @@ -456,6 +456,26 @@ clip( TriangleMesh& tm, return internal::clip_open_impl(tm, clipper, np_tm, np_c); } +namespace internal{ +template +bool dispatch_clip_call(TriangleMesh& tm, TriangleMesh& clipper, + const NamedParameters& np, Tag_false) +{ + return clip(tm, clipper, + np.face_index_map(get(CGAL::dynamic_face_property_t(), tm)), + parameters::face_index_map(get(CGAL::dynamic_face_property_t(), clipper))); +} + +template +bool dispatch_clip_call(TriangleMesh& tm, TriangleMesh& clipper, + const NamedParameters& np, Tag_true) +{ + return clip(tm, clipper, + np.face_index_map(get(face_index, tm)), + parameters::face_index_map(get(face_index, clipper))); +} +} + /** * \ingroup PMP_corefinement_grp * clips `tm` by keeping the part that is on the negative side of `plane` (side opposite to its normal vector). @@ -478,10 +498,6 @@ clip( TriangleMesh& tm, * @param np optional sequence of \ref pmp_namedparameters "Named Parameters" among the ones listed below * * \cgalNamedParamsBegin - * \cgalParamBegin{face_index_map} a property map containing the index of each face of `tm`. - * Note that if the property map is writable, the indices of the faces - * of `tm` will be set after the refining of `tm` with the intersection with `plane`. - * \cgalParamEnd * \cgalParamBegin{new_face_visitor} a class model of `PMPCorefinementNewFaceVisitor` * that is used to track the creation of new faces. * \cgalParamEnd @@ -534,10 +550,13 @@ bool clip( TriangleMesh& tm, default: break; } - return clip(tm, clipper, np, parameters::all_default()); + // dispatch is needed because face index map for tm and clipper have to be of the same time + return internal::dispatch_clip_call(tm, clipper, + np, CGAL::graph_has_property()); } /// \cond SKIP_IN_MANUAL + // convenience overloads template From ed43102a033671919f2e2f5836cab75f28511d59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Loriot?= Date: Tue, 5 Jun 2018 13:45:48 +0200 Subject: [PATCH 29/43] improve clip test --- .../Polygon_mesh_processing/test_pmp_clip.cpp | 117 +++++++++++------- 1 file changed, 72 insertions(+), 45 deletions(-) diff --git a/Polygon_mesh_processing/test/Polygon_mesh_processing/test_pmp_clip.cpp b/Polygon_mesh_processing/test/Polygon_mesh_processing/test_pmp_clip.cpp index c317f339c85..2aee94a327d 100644 --- a/Polygon_mesh_processing/test/Polygon_mesh_processing/test_pmp_clip.cpp +++ b/Polygon_mesh_processing/test/Polygon_mesh_processing/test_pmp_clip.cpp @@ -10,56 +10,83 @@ namespace PMP = CGAL::Polygon_mesh_processing; namespace params = PMP::parameters; typedef CGAL::Exact_predicates_inexact_constructions_kernel K; -typedef CGAL::Surface_mesh Triangle_mesh; +typedef CGAL::Surface_mesh Surface_mesh; typedef CGAL::Polyhedron_3 Polyhedron; -typedef Triangle_mesh::Property_map Constrained_edge_map; +template +void test() +{ + // test with a clipper mesh + TriangleMesh tm1, tm2; + std::ifstream input("data-coref/elephant.off"); + input >> tm1; + input.close(); + input.open("data-coref/sphere.off"); + input >> tm2; + input.close(); + + PMP::clip(tm1, tm2, + params::clip_volumes(false) + .face_index_map(get(CGAL::dynamic_face_property_t(), tm1)), + params::face_index_map(get(CGAL::dynamic_face_property_t(), tm2)) + ); + assert(!CGAL::is_closed(tm1)); + + CGAL::clear(tm1); + CGAL::clear(tm2); + + input.open("data-coref/elephant.off"); + input >> tm1; + input.close(); + input.open("data-coref/sphere.off"); + input >> tm2; + input.close(); + + PMP::clip(tm1, tm2, params::clip_volumes(true) + .face_index_map(get(CGAL::dynamic_face_property_t(), tm1)), + params::face_index_map(get(CGAL::dynamic_face_property_t(), tm2))); + assert(CGAL::is_closed(tm1)); + + // test with a plane + CGAL::clear(tm1); + input.open("data-coref/cube.off"); + input >> tm1; + input.close(); + + K::Plane_3 plane(0, 0, 1, -1); + + PMP::clip(tm1, plane, params::clip_volumes(true)); + assert(CGAL::is_closed(tm1)); + CGAL::clear(tm1); + + input.open("data-coref/cube.off"); + input >> tm1; + input.close(); + PMP::clip(tm1, plane, params::clip_volumes(false) + .use_compact_clipper(false)); + assert(!CGAL::is_closed(tm1)); + CGAL::clear(tm1); + + input.open("data-coref/cube.off"); + input >> tm1; + input.close(); + PMP::clip(tm1, plane, params::clip_volumes(false) + .use_compact_clipper(true)); + assert(CGAL::is_closed(tm1)); + CGAL::clear(tm1); + + input.open("data-coref/cube.off"); + input >> tm1; + input.close(); + PMP::clip(tm1, K::Plane_3(-0.236474, 0.437732, 0.867451, -0.838791), params::clip_volumes(true)); + assert(CGAL::is_closed(tm1)); + assert(!CGAL::is_empty(tm1)); +} int main() { - { - // test open clipping with Surface_mesh - Triangle_mesh tm1, tm2; - std::ifstream input("data-coref/elephant.off"); - input >> tm1; - input.close(); - input.open("data-coref/sphere.off"); - input >> tm2; - input.close(); - - Constrained_edge_map ecm1 = - tm1.add_property_map("e:cst", false).first; - - PMP::clip(tm1, tm2, params::clip_volumes(false).edge_is_constrained_map(ecm1)); - std::ofstream output("clipped_opened.off"); - output << tm1; - - // test open clipping with Polyhedron - Polyhedron P, Q; - input.open("data-coref/elephant.off"); - input >> P; - input.close(); - input.open("data-coref/sphere.off"); - input >> Q; - - PMP::clip(P, Q, - params::clip_volumes(false) - .face_index_map(get(CGAL::face_external_index, P)) - ,params::face_index_map(get(CGAL::face_external_index, Q))); - assert(P.size_of_vertices() == tm1.number_of_vertices()); - } - { - Triangle_mesh tm1, tm2; - std::ifstream input("data-coref/elephant.off"); - input >> tm1; - input.close(); - input.open("data-coref/sphere.off"); - input >> tm2; - - PMP::clip(tm1, tm2, PMP::parameters::clip_volumes(true)); - std::ofstream output("clipped_closed.off"); - output << tm1; - } + test(); + test(); return 0; } From c177cf884b4caefb4089f3bead96d7ca46103f0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Loriot?= Date: Tue, 5 Jun 2018 14:09:32 +0200 Subject: [PATCH 30/43] fix clip convenience overload --- .../include/CGAL/Polygon_mesh_processing/clip.h | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/clip.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/clip.h index ff8fb03e910..3fe6d6f58f2 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/clip.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/clip.h @@ -558,11 +558,9 @@ bool clip( TriangleMesh& tm, /// \cond SKIP_IN_MANUAL // convenience overloads -template +template bool clip( TriangleMesh& tm, - const typename GetGeomTraits::type::Plane_3& plane) + const typename GetGeomTraits::type::Plane_3& plane) { return clip(tm, plane, parameters::all_default()); } From d9166188425a3e00dbd06cf7e215cbdbd287212f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Loriot?= Date: Tue, 5 Jun 2018 14:09:45 +0200 Subject: [PATCH 31/43] one more test --- .../Polygon_mesh_processing/test_pmp_clip.cpp | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/Polygon_mesh_processing/test/Polygon_mesh_processing/test_pmp_clip.cpp b/Polygon_mesh_processing/test/Polygon_mesh_processing/test_pmp_clip.cpp index 2aee94a327d..650cd269b63 100644 --- a/Polygon_mesh_processing/test/Polygon_mesh_processing/test_pmp_clip.cpp +++ b/Polygon_mesh_processing/test/Polygon_mesh_processing/test_pmp_clip.cpp @@ -81,6 +81,21 @@ void test() PMP::clip(tm1, K::Plane_3(-0.236474, 0.437732, 0.867451, -0.838791), params::clip_volumes(true)); assert(CGAL::is_closed(tm1)); assert(!CGAL::is_empty(tm1)); + CGAL::clear(tm1); + + input.open("data-coref/cube.off"); + input >> tm1; + input.close(); + PMP::clip(tm1, K::Plane_3(0, 0, 1, 2)); + assert(CGAL::is_empty(tm1)); + CGAL::clear(tm1); + + input.open("data-coref/cube.off"); + input >> tm1; + input.close(); + PMP::clip(tm1, K::Plane_3(0, 0, 1, -2)); + assert(!CGAL::is_empty(tm1)); + CGAL::clear(tm1); } int main() From aed6f5805c58c219b9ba0c2b481f2570a4ea5d73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Loriot?= Date: Tue, 5 Jun 2018 14:15:22 +0200 Subject: [PATCH 32/43] remove unused variable --- .../include/CGAL/Polygon_mesh_processing/clip.h | 2 -- 1 file changed, 2 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/clip.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/clip.h index 3fe6d6f58f2..ab9766bd4e2 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/clip.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/clip.h @@ -221,8 +221,6 @@ clip_to_bbox(const Plane_3& plane, plane.oriented_side(corners[7]) }}; - std::vector points; - // description of faces of the bbox cpp11::array face_indices = {{ 0, 1, 2, 3, From 45433848dba24a7b6f66941b53aacaad17fa7202 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Loriot?= Date: Tue, 5 Jun 2018 14:24:52 +0200 Subject: [PATCH 33/43] move clipping to PMP/Corefinement --- .../Plugins/Operations_on_polyhedra/Clip_polyhedron_plugin.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Polyhedron/demo/Polyhedron/Plugins/Operations_on_polyhedra/Clip_polyhedron_plugin.cpp b/Polyhedron/demo/Polyhedron/Plugins/Operations_on_polyhedra/Clip_polyhedron_plugin.cpp index 0b3b62dcf5d..e1d95fd5fe4 100644 --- a/Polyhedron/demo/Polyhedron/Plugins/Operations_on_polyhedra/Clip_polyhedron_plugin.cpp +++ b/Polyhedron/demo/Polyhedron/Plugins/Operations_on_polyhedra/Clip_polyhedron_plugin.cpp @@ -105,7 +105,7 @@ public : plane = NULL; //creates and link the actions actionClipPolyhedra = new QAction("Clip Polyhedra", mw); - actionClipPolyhedra->setProperty("subMenuName","Operations on Polyhedra"); + actionClipPolyhedra->setProperty("subMenuName","Polygon Mesh Processing/Corefinement"); dock_widget = new QDockWidget("Polyhedra Clipping", mw); dock_widget->setVisible(false); // do not show at the beginning ui_widget.setupUi(dock_widget); From 7d69c199af290f06d7eb2471a12ca38cb7e90ce2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Loriot?= Date: Wed, 6 Jun 2018 09:36:06 +0200 Subject: [PATCH 34/43] the variable is name graph_visitor and not visitor (visitor is the function) --- BGL/include/CGAL/boost/graph/boost_parameters_interface.h | 2 +- BGL/include/CGAL/boost/graph/named_function_params.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/BGL/include/CGAL/boost/graph/boost_parameters_interface.h b/BGL/include/CGAL/boost/graph/boost_parameters_interface.h index f2e1939871c..8af0898910d 100644 --- a/BGL/include/CGAL/boost/graph/boost_parameters_interface.h +++ b/BGL/include/CGAL/boost/graph/boost_parameters_interface.h @@ -20,4 +20,4 @@ // List the named parameters imported from boost we are using in CGAL CGAL_add_named_parameter(vertex_index_t, vertex_index, vertex_index_map) -CGAL_add_named_parameter(graph_visitor_t, visitor, visitor) +CGAL_add_named_parameter(graph_visitor_t, graph_visitor, visitor) diff --git a/BGL/include/CGAL/boost/graph/named_function_params.h b/BGL/include/CGAL/boost/graph/named_function_params.h index 095b122381a..205269a856d 100644 --- a/BGL/include/CGAL/boost/graph/named_function_params.h +++ b/BGL/include/CGAL/boost/graph/named_function_params.h @@ -88,7 +88,7 @@ enum all_default_t { all_default }; //cannot use macro because it takes no argum using boost::vertex_index_t; using boost::vertex_index; using boost::graph_visitor_t; -using boost::visitor; +using boost::graph_visitor; // define enum types and values for new named parameters #define CGAL_add_named_parameter(X, Y, Z) \ From e26bee1a619186852e5316326f5bd1511e425855 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Loriot?= Date: Wed, 6 Jun 2018 09:43:25 +0200 Subject: [PATCH 35/43] rename new_face_visitor -> visitor --- .../CGAL/boost/graph/parameters_interface.h | 1 - BGL/test/BGL/test_cgal_bgl_named_params.cpp | 3 - Installation/CHANGES.md | 2 +- ...FaceVisitor.h => PMPCorefinementVisitor.h} | 6 +- .../NamedParameters.txt | 7 +-- ...orefinement_mesh_union_with_attributes.cpp | 21 +++---- .../CGAL/Polygon_mesh_processing/clip.h | 8 +-- .../Polygon_mesh_processing/corefinement.h | 58 +++++++++---------- .../Corefinement/Face_graph_output_builder.h | 2 +- .../internal/Corefinement/Visitor.h | 2 +- .../internal/Corefinement/face_graph_utils.h | 2 +- .../test_autorefinement.cpp | 15 ++--- .../Polygon_mesh_processing/test_corefine.cpp | 19 +++--- 13 files changed, 64 insertions(+), 82 deletions(-) rename Polygon_mesh_processing/doc/Polygon_mesh_processing/Concepts/{PMPCorefinementNewFaceVisitor.h => PMPCorefinementVisitor.h} (93%) diff --git a/BGL/include/CGAL/boost/graph/parameters_interface.h b/BGL/include/CGAL/boost/graph/parameters_interface.h index 988528cbd4e..50241bba81b 100644 --- a/BGL/include/CGAL/boost/graph/parameters_interface.h +++ b/BGL/include/CGAL/boost/graph/parameters_interface.h @@ -64,7 +64,6 @@ CGAL_add_named_parameter(nb_points_per_distance_unit_t, nb_points_per_distance_u CGAL_add_named_parameter(outward_orientation_t, outward_orientation, outward_orientation) CGAL_add_named_parameter(overlap_test_t, overlap_test, do_overlap_test_of_bounded_sides) CGAL_add_named_parameter(preserve_genus_t, preserve_genus, preserve_genus) -CGAL_add_named_parameter(new_face_visitor_t, new_face_visitor, new_face_visitor) CGAL_add_named_parameter(throw_on_self_intersection_t, throw_on_self_intersection, throw_on_self_intersection) CGAL_add_named_parameter(clip_volumes_t, clip_volumes, clip_volumes) CGAL_add_named_parameter(use_compact_clipper_t, use_compact_clipper, use_compact_clipper) diff --git a/BGL/test/BGL/test_cgal_bgl_named_params.cpp b/BGL/test/BGL/test_cgal_bgl_named_params.cpp index ada231d4e3b..16f03530a2a 100644 --- a/BGL/test/BGL/test_cgal_bgl_named_params.cpp +++ b/BGL/test/BGL/test_cgal_bgl_named_params.cpp @@ -72,7 +72,6 @@ void test(const NamedParameters& np) assert(get_param(np, CGAL::internal_np::number_of_points_on_edges).v == 31); assert(get_param(np, CGAL::internal_np::nb_points_per_area_unit).v == 32); assert(get_param(np, CGAL::internal_np::nb_points_per_distance_unit).v == 33); - assert(get_param(np, CGAL::internal_np::new_face_visitor).v == 42); assert(get_param(np, CGAL::internal_np::throw_on_self_intersection).v == 43); assert(get_param(np, CGAL::internal_np::clip_volumes).v == 44); assert(get_param(np, CGAL::internal_np::use_compact_clipper).v == 45); @@ -140,7 +139,6 @@ void test(const NamedParameters& np) check_same_type<31>(get_param(np, CGAL::internal_np::number_of_points_on_edges)); check_same_type<32>(get_param(np, CGAL::internal_np::nb_points_per_area_unit)); check_same_type<33>(get_param(np, CGAL::internal_np::nb_points_per_distance_unit)); - check_same_type<42>(get_param(np, CGAL::internal_np::new_face_visitor)); check_same_type<43>(get_param(np, CGAL::internal_np::throw_on_self_intersection)); check_same_type<44>(get_param(np, CGAL::internal_np::clip_volumes)); check_same_type<45>(get_param(np, CGAL::internal_np::use_compact_clipper)); @@ -207,7 +205,6 @@ int main() .weight_calculator(A<39>(39)) .preserve_genus(A<40>(40)) .verbosity_level(A<41>(41)) - .new_face_visitor(A<42>(42)) .throw_on_self_intersection(A<43>(43)) .clip_volumes(A<44>(44)) .use_compact_clipper(A<45>(45)) diff --git a/Installation/CHANGES.md b/Installation/CHANGES.md index 09e3fd51d48..6632f65dc82 100644 --- a/Installation/CHANGES.md +++ b/Installation/CHANGES.md @@ -35,7 +35,7 @@ Release date: September 2018 ### Polygon Mesh Processing - Added a function to apply a transformation to a mesh : - `CGAL::Polygon_mesh_processing::transform()` -- Added in corefinement-related functions a new named parameter `new_face_visitor` +- Added in corefinement-related functions a new named parameter `visitor` that makes it possible to pass a visitor to the function in order to track the creation of new faces. - Added in all corefinement-related functions a named parameter `throw_on_self_intersection` diff --git a/Polygon_mesh_processing/doc/Polygon_mesh_processing/Concepts/PMPCorefinementNewFaceVisitor.h b/Polygon_mesh_processing/doc/Polygon_mesh_processing/Concepts/PMPCorefinementVisitor.h similarity index 93% rename from Polygon_mesh_processing/doc/Polygon_mesh_processing/Concepts/PMPCorefinementNewFaceVisitor.h rename to Polygon_mesh_processing/doc/Polygon_mesh_processing/Concepts/PMPCorefinementVisitor.h index 709a87afccf..7d95997ff80 100644 --- a/Polygon_mesh_processing/doc/Polygon_mesh_processing/Concepts/PMPCorefinementNewFaceVisitor.h +++ b/Polygon_mesh_processing/doc/Polygon_mesh_processing/Concepts/PMPCorefinementVisitor.h @@ -1,15 +1,15 @@ /// \ingroup PkgPolygonMeshProcessingConcepts /// \cgalConcept /// -/// The concept `PMPCorefinementNewFaceVisitor` defines the requirements for the visitor +/// The concept `PMPCorefinementVisitor` defines the requirements for the visitor /// used in \link PMP_corefinement_grp corefinement-related functions \endlink to track /// the creation of new faces. /// /// \cgalRefines `CopyConstructible` -/// \cgalHasModel `CGAL::Polygon_mesh_processing::Corefinement::Default_new_face_visitor`. +/// \cgalHasModel `CGAL::Polygon_mesh_processing::Corefinement::Default_visitor`. -class PMPCorefinementNewFaceVisitor{ +class PMPCorefinementVisitor{ public: /// Mesh type typedef unspecified_type Triangle_mesh; diff --git a/Polygon_mesh_processing/doc/Polygon_mesh_processing/NamedParameters.txt b/Polygon_mesh_processing/doc/Polygon_mesh_processing/NamedParameters.txt index f5d90b55417..fa2884c841d 100644 --- a/Polygon_mesh_processing/doc/Polygon_mesh_processing/NamedParameters.txt +++ b/Polygon_mesh_processing/doc/Polygon_mesh_processing/NamedParameters.txt @@ -310,12 +310,11 @@ of close meshes should be done in addition to surface intersection tests. \b Default value is `false` \cgalNPEnd -\cgalNPBegin{new_face_visitor} \anchor PMP_new_face_visitor -A class model of `PMPCorefinementNewFaceVisitor` that is used in corefinement-related functions -to track the creation of new faces. +\cgalNPBegin{visitor} \anchor PMP_visitor +Parameter used to pass a visitor class to a function. Its type and behavior depend on the visited function. \n \b Type : `A class` \n -\b Default `CGAL::Polygon_mesh_processing::Corefinement::Default_new_face_visitor` +\b Default Specific to the function visited \cgalNPEnd \cgalNPBegin{throw_on_self_intersection} \anchor PMP_throw_on_self_intersection diff --git a/Polygon_mesh_processing/examples/Polygon_mesh_processing/corefinement_mesh_union_with_attributes.cpp b/Polygon_mesh_processing/examples/Polygon_mesh_processing/corefinement_mesh_union_with_attributes.cpp index 6dcbd51e301..37cba10db44 100644 --- a/Polygon_mesh_processing/examples/Polygon_mesh_processing/corefinement_mesh_union_with_attributes.cpp +++ b/Polygon_mesh_processing/examples/Polygon_mesh_processing/corefinement_mesh_union_with_attributes.cpp @@ -10,24 +10,21 @@ typedef CGAL::Surface_mesh Mesh; namespace PMP = CGAL::Polygon_mesh_processing; -struct New_face_visitor +struct Visitor : + public PMP::Corefinement::Default_visitor { typedef typename Mesh::Face_index face_descriptor; boost::container::flat_map > properties; int face_id; - New_face_visitor() + Visitor() { properties.reserve(3); face_id=-1; } -// required visitor API - void after_subface_creations(Mesh&){} - void before_subface_created(Mesh&){} - void before_face_copy(face_descriptor, Mesh&, Mesh&){} - +// visitor API overloaded void before_subface_creations(face_descriptor f_split,Mesh& tm) { face_id = properties[&tm][f_split]; @@ -79,12 +76,12 @@ int main(int argc, char* argv[]) BOOST_FOREACH(Mesh::Face_index f, faces(mesh2)) mesh2_id[f] = 2; - New_face_visitor nfv; - nfv.properties[&mesh1] = mesh1_id; - nfv.properties[&mesh2] = mesh2_id; - nfv.properties[&out] = out_id; + Visitor visitor; + visitor.properties[&mesh1] = mesh1_id; + visitor.properties[&mesh2] = mesh2_id; + visitor.properties[&out] = out_id; - bool valid_union = PMP::corefine_and_compute_union(mesh1, mesh2, out, PMP::parameters::new_face_visitor(nfv)); + bool valid_union = PMP::corefine_and_compute_union(mesh1, mesh2, out, PMP::parameters::visitor(visitor)); BOOST_FOREACH(Mesh::Face_index f, faces(mesh1)) assert( mesh1_id[f] == 1 ); diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/clip.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/clip.h index ab9766bd4e2..68c80d439c5 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/clip.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/clip.h @@ -416,8 +416,8 @@ clip_to_bbox(const Plane_3& plane, * Note that if the property map is writable, the indices of the faces * of `tm` and `clipper` will be set after the refining `tm` with the intersection with `plane`. * \cgalParamEnd - * \cgalParamBegin{new_face_visitor} a class model of `PMPCorefinementNewFaceVisitor` - * that is used to track the creation of new faces. + * \cgalParamBegin{visitor} a class model of `PMPCorefinementVisitor` + * that is used to track the creation of new faces. * \cgalParamEnd * \cgalParamBegin{throw_on_self_intersection} if `true`, * the set of triangles closed to the intersection of `tm` and `clipper` will be @@ -496,8 +496,8 @@ bool dispatch_clip_call(TriangleMesh& tm, TriangleMesh& clipper, * @param np optional sequence of \ref pmp_namedparameters "Named Parameters" among the ones listed below * * \cgalNamedParamsBegin - * \cgalParamBegin{new_face_visitor} a class model of `PMPCorefinementNewFaceVisitor` - * that is used to track the creation of new faces. + * \cgalParamBegin{visitor} a class model of `PMPCorefinementVisitor` + * that is used to track the creation of new faces. * \cgalParamEnd * \cgalParamBegin{throw_on_self_intersection} if `true`, * the set of triangles closed to the intersection of `tm` and `plane` will be diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/corefinement.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/corefinement.h index d53f30fc77c..57726453e5d 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/corefinement.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/corefinement.h @@ -135,13 +135,13 @@ bool recursive_does_bound_a_volume(const TriangleMesh& tm, namespace Corefinement { /** \ingroup PMP_corefinement_grp - * Default new-face visitor model of `PMPCorefinementNewFaceVisitor`. + * Default new-face visitor model of `PMPCorefinementVisitor`. * All of its functions have an empty body. This class can be used as a * base class if only some of the functions of the concept require to be * overridden. */ template -struct Default_new_face_visitor; +struct Default_visitor; #ifdef DOXYGEN_RUNNING /** \ingroup PMP_corefinement_grp @@ -309,8 +309,8 @@ bool does_bound_a_volume(const TriangleMesh& tm) * Note that if the property map is writable, the indices of the faces * of `tm1` and `tm2` will be set after the corefinement is done. * \cgalParamEnd - * \cgalParamBegin{new_face_visitor} a class model of `PMPCorefinementNewFaceVisitor` - * that is used to track the creation of new faces (`np1` only) + * \cgalParamBegin{visitor} a class model of `PMPCorefinementVisitor` + * that is used to track the creation of new faces (`np1` only) * \cgalParamEnd * \cgalParamBegin{throw_on_self_intersection} if `true`, for each input triangle mesh, * the set of triangles close to the intersection of `tm1` and `tm2` will be @@ -494,12 +494,12 @@ corefine_and_compute_boolean_operations( get_property_map(boost::face_index, tm2)); // New face visitor typedef typename boost::lookup_named_param_def < - internal_np::new_face_visitor_t, + internal_np::graph_visitor_t, NamedParameters1, - Corefinement::Default_new_face_visitor//default + Corefinement::Default_visitor//default > ::type Nfv; - Nfv nfv( boost::choose_param( boost::get_param(np1, internal_np::new_face_visitor), - Corefinement::Default_new_face_visitor() ) ); + Nfv nfv( boost::choose_param( boost::get_param(np1, internal_np::graph_visitor), + Corefinement::Default_visitor() ) ); // surface intersection algorithm call typedef Corefinement::Default_node_visitor Dnv; @@ -617,8 +617,8 @@ corefine_and_compute_boolean_operations( * Note that if the property map is writable, the indices of the faces * of `tm1` and `tm2` will be set after the corefinement is done. * \cgalParamEnd - * \cgalParamBegin{new_face_visitor} a class model of `PMPCorefinementNewFaceVisitor` - * that is used to track the creation of new faces (`np1` only) + * \cgalParamBegin{visitor} a class model of `PMPCorefinementVisitor` + * that is used to track the creation of new faces (`np1` only) * \cgalParamEnd * \cgalParamBegin{throw_on_self_intersection} if `true`, for each input triangle mesh, * the set of triangles close to the intersection of `tm1` and `tm2` will be @@ -763,8 +763,8 @@ corefine_and_compute_difference( TriangleMesh& tm1, * \cgalParamBegin{edge_is_constrained_map} a property map containing the * constrained-or-not status of each edge of `tm1` (`tm2`) * \cgalParamEnd - * \cgalParamBegin{new_face_visitor} a class model of `PMPCorefinementNewFaceVisitor` - * that is used to track the creation of new faces (`np1` only) + * \cgalParamBegin{visitor} a class model of `PMPCorefinementVisitor` + * that is used to track the creation of new faces (`np1` only) * \cgalParamEnd * \cgalParamBegin{throw_on_self_intersection} if `true`, for each input triangle mesh, * the set of triangles close to the intersection of `tm1` and `tm2` will be @@ -829,14 +829,14 @@ corefine_and_compute_difference( TriangleMesh& tm1, return; } - // New face visitor + // User visitor typedef typename boost::lookup_named_param_def < - internal_np::new_face_visitor_t, + internal_np::graph_visitor_t, NamedParameters1, - Corefinement::Default_new_face_visitor//default + Corefinement::Default_visitor//default > ::type Nfv; - Nfv nfv( boost::choose_param( boost::get_param(np1, internal_np::new_face_visitor), - Corefinement::Default_new_face_visitor() ) ); + Nfv nfv( boost::choose_param( boost::get_param(np1, internal_np::graph_visitor), + Corefinement::Default_visitor() ) ); // surface intersection algorithm call typedef Corefinement::Default_node_visitor Dnv; @@ -873,8 +873,8 @@ namespace experimental { * \cgalParamBegin{edge_is_constrained_map} a property map containing the * constrained-or-not status of each edge of `tm` * \cgalParamEnd - * \cgalParamBegin{new_face_visitor} a class model of `PMPCorefinementNewFaceVisitor` - * that is used to track the creation of new faces + * \cgalParamBegin{visitor} a class model of `PMPCorefinementVisitor` + * that is used to track the creation of new faces * \cgalParamEnd * \cgalNamedParamsEnd * @@ -905,12 +905,12 @@ namespace experimental { // New face visitor typedef typename boost::lookup_named_param_def < - internal_np::new_face_visitor_t, + internal_np::graph_visitor_t, NamedParameters, - Corefinement::Default_new_face_visitor//default + Corefinement::Default_visitor//default > ::type Nfv; - Nfv nfv( boost::choose_param( boost::get_param(np, internal_np::new_face_visitor), - Corefinement::Default_new_face_visitor() ) ); + Nfv nfv( boost::choose_param( boost::get_param(np, internal_np::graph_visitor), + Corefinement::Default_visitor() ) ); // surface intersection algorithm call @@ -951,8 +951,8 @@ namespace experimental { * constrained-or-not status of each edge of `tm` * \cgalParamEnd * \cgalParamBegin{face_index_map} a property map containing the index of each face of `tm` \cgalParamEnd - * \cgalParamBegin{new_face_visitor} a class model of `PMPCorefinementNewFaceVisitor` - * that is used to track the creation of new faces + * \cgalParamBegin{visitor} a class model of `PMPCorefinementVisitor` + * that is used to track the creation of new faces * \cgalParamEnd * \cgalNamedParamsEnd * @@ -983,12 +983,12 @@ namespace experimental { Corefinement::No_mark() ); // New face visitor typedef typename boost::lookup_named_param_def < - internal_np::new_face_visitor_t, + internal_np::graph_visitor_t, NamedParameters, - Corefinement::Default_new_face_visitor//default + Corefinement::Default_visitor//default > ::type Nfv; - Nfv nfv( boost::choose_param( boost::get_param(np, internal_np::new_face_visitor), - Corefinement::Default_new_face_visitor() ) ); + Nfv nfv( boost::choose_param( boost::get_param(np, internal_np::graph_visitor), + Corefinement::Default_visitor() ) ); // surface intersection algorithm call typedef Corefinement::Default_node_visitor Dnv; diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Corefinement/Face_graph_output_builder.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Corefinement/Face_graph_output_builder.h index 9db160185cc..eb0e35b98d6 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Corefinement/Face_graph_output_builder.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Corefinement/Face_graph_output_builder.h @@ -91,7 +91,7 @@ class Face_graph_output_builder No_mark, No_mark > >::type EdgeMarkMapTuple; typedef typename Default::Get< - NewFaceVisitor_, Default_new_face_visitor >::type NewFaceVisitor; + NewFaceVisitor_, Default_visitor >::type NewFaceVisitor; // graph_traits typedefs typedef TriangleMesh TM; diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Corefinement/Visitor.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Corefinement/Visitor.h index 44c765fe5ba..eca64d2d439 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Corefinement/Visitor.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Corefinement/Visitor.h @@ -151,7 +151,7 @@ class Visitor{ typedef typename Default::Get< NewNodeVisitor_, Default_node_visitor >::type NewNodeVisitor; typedef typename Default::Get< - NewFaceVisitor_, Default_new_face_visitor >::type NewFaceVisitor; + NewFaceVisitor_, Default_visitor >::type NewFaceVisitor; // config flags public: diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Corefinement/face_graph_utils.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Corefinement/face_graph_utils.h index 088aab857ed..a26d49a1d2b 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Corefinement/face_graph_utils.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Corefinement/face_graph_utils.h @@ -221,7 +221,7 @@ get_vpm(const NP&, boost::optional opm, boost::false_type) // template -struct Default_new_face_visitor{ +struct Default_visitor{ typedef boost::graph_traits GT; typedef typename GT::face_descriptor face_descriptor; diff --git a/Polygon_mesh_processing/test/Polygon_mesh_processing/test_autorefinement.cpp b/Polygon_mesh_processing/test/Polygon_mesh_processing/test_autorefinement.cpp index d67ddfcb71a..a29f0b8697a 100644 --- a/Polygon_mesh_processing/test/Polygon_mesh_processing/test_autorefinement.cpp +++ b/Polygon_mesh_processing/test/Polygon_mesh_processing/test_autorefinement.cpp @@ -13,17 +13,12 @@ typedef CGAL::Surface_mesh Mesh; namespace PMP = CGAL::Polygon_mesh_processing; template -struct My_new_face_visitor +struct My_visitor : + public CGAL::Polygon_mesh_processing::Corefinement::Default_visitor { - typedef boost::graph_traits GT; - typedef typename GT::face_descriptor face_descriptor; - - void before_subface_creations(face_descriptor /*f_old*/,TriangleMesh&){} void after_subface_creations(TriangleMesh&){++(*i);} - void before_subface_created(TriangleMesh&){} - void after_subface_created(face_descriptor /*f_new*/,TriangleMesh&){} - My_new_face_visitor() + My_visitor() : i (new int(0) ) {} @@ -54,9 +49,9 @@ void test(const char* fname, std::size_t nb_polylines, std::size_t total_nb_poin assert(total_nb_points == total_nb_pt); // Testing autorefine() - My_new_face_visitor visitor; + My_visitor visitor; PMP::experimental::autorefine(mesh, - PMP::parameters::new_face_visitor(visitor)); + PMP::parameters::visitor(visitor)); assert( nb_vertices_after_autorefine==num_vertices(mesh)); assert( *(visitor.i) != 0); diff --git a/Polygon_mesh_processing/test/Polygon_mesh_processing/test_corefine.cpp b/Polygon_mesh_processing/test/Polygon_mesh_processing/test_corefine.cpp index 19c635c38e5..1dc6cb02ffd 100644 --- a/Polygon_mesh_processing/test/Polygon_mesh_processing/test_corefine.cpp +++ b/Polygon_mesh_processing/test/Polygon_mesh_processing/test_corefine.cpp @@ -11,17 +11,12 @@ typedef CGAL::Surface_mesh Surface_mesh; typedef CGAL::Polyhedron_3 Polyhedron_3; template -struct My_new_face_visitor +struct My_visitor : + public CGAL::Polygon_mesh_processing::Corefinement::Default_visitor { - typedef boost::graph_traits GT; - typedef typename GT::face_descriptor face_descriptor; - - void before_subface_creations(face_descriptor /*f_old*/,TriangleMesh&){} void after_subface_creations(TriangleMesh&){++(*i);} - void before_subface_created(TriangleMesh&){} - void after_subface_created(face_descriptor /*f_new*/,TriangleMesh&){} - My_new_face_visitor() + My_visitor() : i (new int(0) ) {} @@ -43,10 +38,10 @@ void test(const char* f1, const char* f2) assert(input); input >> sm2; input.close(); - My_new_face_visitor sm_v; + My_visitor sm_v; CGAL::Polygon_mesh_processing::corefine(sm1, sm2, - CGAL::Polygon_mesh_processing::parameters::new_face_visitor(sm_v)); + CGAL::Polygon_mesh_processing::parameters::visitor(sm_v)); assert(sm1.is_valid()); assert(sm2.is_valid()); @@ -61,10 +56,10 @@ void test(const char* f1, const char* f2) input.open(f2); assert(input); input >> Q; - My_new_face_visitor sm_p; + My_visitor sm_p; CGAL::Polygon_mesh_processing::corefine(P, Q, - CGAL::Polygon_mesh_processing::parameters::new_face_visitor(sm_p)); + CGAL::Polygon_mesh_processing::parameters::visitor(sm_p)); assert(*(sm_p.i) != 0); assert(*(sm_v.i) == *(sm_p.i)); From 87b45e8afe3d42949c24397271e198adff6d9566 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Loriot?= Date: Wed, 6 Jun 2018 10:27:52 +0200 Subject: [PATCH 36/43] rename algorithm visitor --- .../Polygon_mesh_processing/corefinement.h | 28 +++++++++++-------- .../internal/Corefinement/Visitor.h | 6 ++-- 2 files changed, 19 insertions(+), 15 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/corefinement.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/corefinement.h index 57726453e5d..274f95c14c3 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/corefinement.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/corefinement.h @@ -513,15 +513,16 @@ corefine_and_compute_boolean_operations( Edge_mark_map_tuple, Nfv> Ob; - typedef Corefinement::Visitor Visitor; + typedef Corefinement::Surface_intersection_visitor_for_corefinement< + TriangleMesh, Vpm, Ob, Ecm_in, Default, Nfv> Algo_visitor; Dnv dnv; Ecm_in ecm_in(tm1,tm2,ecm1,ecm2); Edge_mark_map_tuple ecms_out(ecm_out_0, ecm_out_1, ecm_out_2, ecm_out_3); Ob ob(tm1, tm2, vpm1, vpm2, fid_map1, fid_map2, ecm_in, vpm_out_tuple, ecms_out, nfv, output); - Corefinement::Intersection_of_triangle_meshes - functor(tm1, tm2, vpm1, vpm2, Visitor(dnv,nfv,ob,ecm_in)); + Corefinement::Intersection_of_triangle_meshes + functor(tm1, tm2, vpm1, vpm2, Algo_visitor(dnv,nfv,ob,ecm_in)); functor(CGAL::Emptyset_iterator(), throw_on_self_intersection, true); @@ -841,12 +842,13 @@ corefine_and_compute_difference( TriangleMesh& tm1, // surface intersection algorithm call typedef Corefinement::Default_node_visitor Dnv; typedef Corefinement::No_extra_output_from_corefinement Ob; - typedef Corefinement::Visitor Visitor; + typedef Corefinement::Surface_intersection_visitor_for_corefinement< + TriangleMesh, Vpm, Ob, Ecm, Default, Nfv> Algo_visitor; Dnv dnv; Ob ob; Ecm ecm(tm1,tm2,ecm1,ecm2); - Corefinement::Intersection_of_triangle_meshes - functor(tm1, tm2, vpm1, vpm2, Visitor(dnv,nfv,ob,ecm)); + Corefinement::Intersection_of_triangle_meshes + functor(tm1, tm2, vpm1, vpm2, Algo_visitor(dnv,nfv,ob,ecm)); functor(CGAL::Emptyset_iterator(), throw_on_self_intersection, true); } @@ -917,12 +919,13 @@ namespace experimental { typedef Corefinement::Default_node_visitor Dnv; typedef Corefinement::No_extra_output_from_corefinement Ob; typedef Default D; - typedef Corefinement::Visitor Visitor; + typedef Corefinement::Surface_intersection_visitor_for_corefinement< + TriangleMesh, Vpm, Ob, Ecm, D, Nfv, true> Algo_visitor; Dnv dnv; Ob ob; - Corefinement::Intersection_of_triangle_meshes - functor(tm, vpm, Visitor(dnv,nfv,ob,ecm) ); + Corefinement::Intersection_of_triangle_meshes + functor(tm, vpm, Algo_visitor(dnv,nfv,ob,ecm) ); functor(CGAL::Emptyset_iterator(), true); } @@ -999,12 +1002,13 @@ namespace experimental { Default > Ob; typedef Default D; - typedef Corefinement::Visitor Visitor; + typedef Corefinement::Surface_intersection_visitor_for_corefinement< + TriangleMesh, Vpm, Ob, Ecm, D, Nfv, true> Algo_visitor; Dnv dnv; Ob ob(tm, vpm, fid_map, ecm); - Corefinement::Intersection_of_triangle_meshes - functor(tm, vpm, Visitor(dnv,nfv,ob,ecm) ); + Corefinement::Intersection_of_triangle_meshes + functor(tm, vpm, Algo_visitor(dnv,nfv,ob,ecm) ); functor(CGAL::Emptyset_iterator(), true); diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Corefinement/Visitor.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Corefinement/Visitor.h index eca64d2d439..066fb540bd7 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Corefinement/Visitor.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Corefinement/Visitor.h @@ -142,7 +142,7 @@ template< class TriangleMesh, class NewNodeVisitor_ = Default, class NewFaceVisitor_ = Default, bool doing_autorefinement = false > -class Visitor{ +class Surface_intersection_visitor_for_corefinement{ //default template parameters typedef typename Default::Get > >::type EdgeMarkMapBind; @@ -237,8 +237,8 @@ private: // visitor public functions public: - Visitor(NewNodeVisitor& v, NewFaceVisitor& f, - OutputBuilder& o, const EdgeMarkMapBind& emm) + Surface_intersection_visitor_for_corefinement( + NewNodeVisitor& v, NewFaceVisitor& f, OutputBuilder& o, const EdgeMarkMapBind& emm) : new_node_visitor(v) , new_face_visitor(f) , output_builder(o) From 02bcd2a61828b6057a2701764a5554a5d9230303 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Loriot?= Date: Wed, 6 Jun 2018 12:04:30 +0200 Subject: [PATCH 37/43] merge vertex and face visitor and disable vertex for now --- .../Polygon_mesh_processing/corefinement.h | 57 ++++++------- .../Corefinement/Face_graph_output_builder.h | 20 ++--- .../internal/Corefinement/Visitor.h | 50 ++--------- .../internal/Corefinement/face_graph_utils.h | 82 ++++++++++++------- 4 files changed, 94 insertions(+), 115 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/corefinement.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/corefinement.h index 274f95c14c3..5ccf4ca5c6b 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/corefinement.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/corefinement.h @@ -492,18 +492,16 @@ corefine_and_compute_boolean_operations( get_property_map(boost::face_index, tm1)); Fid_map fid_map2 = boost::choose_param(boost::get_param(np2, internal_np::face_index), get_property_map(boost::face_index, tm2)); -// New face visitor +// User visitor typedef typename boost::lookup_named_param_def < internal_np::graph_visitor_t, NamedParameters1, Corefinement::Default_visitor//default - > ::type Nfv; - Nfv nfv( boost::choose_param( boost::get_param(np1, internal_np::graph_visitor), - Corefinement::Default_visitor() ) ); + > ::type User_visitor; + User_visitor uv( boost::choose_param( boost::get_param(np1, internal_np::graph_visitor), + Corefinement::Default_visitor() ) ); // surface intersection algorithm call - typedef Corefinement::Default_node_visitor Dnv; - typedef Corefinement::Face_graph_output_builder Ob; + User_visitor> Ob; typedef Corefinement::Surface_intersection_visitor_for_corefinement< - TriangleMesh, Vpm, Ob, Ecm_in, Default, Nfv> Algo_visitor; - Dnv dnv; + TriangleMesh, Vpm, Ob, Ecm_in, User_visitor> Algo_visitor; Ecm_in ecm_in(tm1,tm2,ecm1,ecm2); Edge_mark_map_tuple ecms_out(ecm_out_0, ecm_out_1, ecm_out_2, ecm_out_3); Ob ob(tm1, tm2, vpm1, vpm2, fid_map1, fid_map2, ecm_in, - vpm_out_tuple, ecms_out, nfv, output); + vpm_out_tuple, ecms_out, uv, output); Corefinement::Intersection_of_triangle_meshes - functor(tm1, tm2, vpm1, vpm2, Algo_visitor(dnv,nfv,ob,ecm_in)); + functor(tm1, tm2, vpm1, vpm2, Algo_visitor(uv,ob,ecm_in)); functor(CGAL::Emptyset_iterator(), throw_on_self_intersection, true); @@ -835,20 +832,18 @@ corefine_and_compute_difference( TriangleMesh& tm1, internal_np::graph_visitor_t, NamedParameters1, Corefinement::Default_visitor//default - > ::type Nfv; - Nfv nfv( boost::choose_param( boost::get_param(np1, internal_np::graph_visitor), - Corefinement::Default_visitor() ) ); + > ::type User_visitor; + User_visitor uv( boost::choose_param( boost::get_param(np1, internal_np::graph_visitor), + Corefinement::Default_visitor() ) ); // surface intersection algorithm call - typedef Corefinement::Default_node_visitor Dnv; typedef Corefinement::No_extra_output_from_corefinement Ob; typedef Corefinement::Surface_intersection_visitor_for_corefinement< - TriangleMesh, Vpm, Ob, Ecm, Default, Nfv> Algo_visitor; - Dnv dnv; + TriangleMesh, Vpm, Ob, Ecm, User_visitor> Algo_visitor; Ob ob; Ecm ecm(tm1,tm2,ecm1,ecm2); Corefinement::Intersection_of_triangle_meshes - functor(tm1, tm2, vpm1, vpm2, Algo_visitor(dnv,nfv,ob,ecm)); + functor(tm1, tm2, vpm1, vpm2, Algo_visitor(uv,ob,ecm)); functor(CGAL::Emptyset_iterator(), throw_on_self_intersection, true); } @@ -910,22 +905,19 @@ namespace experimental { internal_np::graph_visitor_t, NamedParameters, Corefinement::Default_visitor//default - > ::type Nfv; - Nfv nfv( boost::choose_param( boost::get_param(np, internal_np::graph_visitor), - Corefinement::Default_visitor() ) ); + > ::type User_visitor; + User_visitor uv( boost::choose_param( boost::get_param(np, internal_np::graph_visitor), + Corefinement::Default_visitor() ) ); // surface intersection algorithm call - typedef Corefinement::Default_node_visitor Dnv; typedef Corefinement::No_extra_output_from_corefinement Ob; - typedef Default D; typedef Corefinement::Surface_intersection_visitor_for_corefinement< - TriangleMesh, Vpm, Ob, Ecm, D, Nfv, true> Algo_visitor; - Dnv dnv; + TriangleMesh, Vpm, Ob, Ecm, User_visitor,true> Algo_visitor; Ob ob; Corefinement::Intersection_of_triangle_meshes - functor(tm, vpm, Algo_visitor(dnv,nfv,ob,ecm) ); + functor(tm, vpm, Algo_visitor(uv,ob,ecm) ); functor(CGAL::Emptyset_iterator(), true); } @@ -989,26 +981,23 @@ namespace experimental { internal_np::graph_visitor_t, NamedParameters, Corefinement::Default_visitor//default - > ::type Nfv; - Nfv nfv( boost::choose_param( boost::get_param(np, internal_np::graph_visitor), - Corefinement::Default_visitor() ) ); + > ::type User_visitor; + User_visitor uv( boost::choose_param( boost::get_param(np, internal_np::graph_visitor), + Corefinement::Default_visitor() ) ); // surface intersection algorithm call - typedef Corefinement::Default_node_visitor Dnv; typedef Corefinement::Output_builder_for_autorefinement Ob; - typedef Default D; typedef Corefinement::Surface_intersection_visitor_for_corefinement< - TriangleMesh, Vpm, Ob, Ecm, D, Nfv, true> Algo_visitor; - Dnv dnv; + TriangleMesh, Vpm, Ob, Ecm, User_visitor,true> Algo_visitor; Ob ob(tm, vpm, fid_map, ecm); Corefinement::Intersection_of_triangle_meshes - functor(tm, vpm, Algo_visitor(dnv,nfv,ob,ecm) ); + functor(tm, vpm, Algo_visitor(uv,ob,ecm) ); functor(CGAL::Emptyset_iterator(), true); diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Corefinement/Face_graph_output_builder.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Corefinement/Face_graph_output_builder.h index eb0e35b98d6..6fe903aa52b 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Corefinement/Face_graph_output_builder.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Corefinement/Face_graph_output_builder.h @@ -73,7 +73,7 @@ template + class UserVisitor_ = Default> class Face_graph_output_builder { //Default typedefs @@ -91,7 +91,7 @@ class Face_graph_output_builder No_mark, No_mark > >::type EdgeMarkMapTuple; typedef typename Default::Get< - NewFaceVisitor_, Default_visitor >::type NewFaceVisitor; + UserVisitor_, Default_visitor >::type UserVisitor; // graph_traits typedefs typedef TriangleMesh TM; @@ -126,7 +126,7 @@ class Face_graph_output_builder // property maps of output meshes const VpmOutTuple& output_vpms; EdgeMarkMapTuple& out_edge_mark_maps; - NewFaceVisitor& new_face_visitor; + UserVisitor& user_visitor; // output meshes const cpp11::array, 4>& requested_output; // input meshes closed ? @@ -356,7 +356,7 @@ public: EdgeMarkMapBind& marks_on_input_edges, const VpmOutTuple& output_vpms, EdgeMarkMapTuple& out_edge_mark_maps, - NewFaceVisitor& new_face_visitor, + UserVisitor& user_visitor, const cpp11::array< boost::optional, 4 >& requested_output) : tm1(tm1), tm2(tm2) @@ -365,7 +365,7 @@ public: , marks_on_input_edges(marks_on_input_edges) , output_vpms(output_vpms) , out_edge_mark_maps(out_edge_mark_maps) - , new_face_visitor(new_face_visitor) + , user_visitor(user_visitor) , requested_output(requested_output) , is_tm1_closed( is_closed(tm1)) , is_tm2_closed( is_closed(tm2)) @@ -1256,7 +1256,7 @@ public: marks_on_input_edges.ecm2, \ cpp11::get(out_edge_mark_maps), \ shared_edges, \ - new_face_visitor \ + user_visitor \ ) CGAL_COREF_FUNCTION_CALL(operation) #undef CGAL_COREF_FUNCTION_CALL_DEF @@ -1339,7 +1339,7 @@ public: marks_on_input_edges.ecm2, \ cpp11::get(out_edge_mark_maps), \ disconnected_patches_edge_to_tm2_edge, \ - new_face_visitor) + user_visitor) CGAL_COREF_FUNCTION_CALL(inplace_operation_tm1) #undef CGAL_COREF_FUNCTION_CALL_DEF // Operation in tm2: discard patches and append the one from tm2 @@ -1358,7 +1358,7 @@ public: marks_on_input_edges.ecm1, \ cpp11::get(out_edge_mark_maps), \ disconnected_patches_edge_to_tm2_edge, \ - new_face_visitor) + user_visitor) CGAL_COREF_FUNCTION_CALL(inplace_operation_tm2) #undef CGAL_COREF_FUNCTION_CALL_DEF @@ -1414,7 +1414,7 @@ public: marks_on_input_edges.ecm2, \ cpp11::get(out_edge_mark_maps), \ polylines, \ - new_face_visitor \ + user_visitor \ ) CGAL_COREF_FUNCTION_CALL(inplace_operation_tm1) #undef CGAL_COREF_FUNCTION_CALL_DEF @@ -1456,7 +1456,7 @@ public: marks_on_input_edges.ecm1, \ cpp11::get(out_edge_mark_maps), \ polylines, \ - new_face_visitor); + user_visitor); CGAL_COREF_FUNCTION_CALL(inplace_operation_tm2) #undef CGAL_COREF_FUNCTION_CALL_DEF // remove polylines only on the border of patches not kept diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Corefinement/Visitor.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Corefinement/Visitor.h index 066fb540bd7..7b73309e1c9 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Corefinement/Visitor.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Corefinement/Visitor.h @@ -41,33 +41,6 @@ namespace Corefinement{ // TODO option to ignore internal edges for patches of coplanar faces -template -struct Default_node_visitor{ - typedef boost::graph_traits GT; - typedef typename GT::halfedge_descriptor halfedge_descriptor; - typedef typename GT::face_descriptor face_descriptor; - typedef typename GT::vertex_descriptor vertex_descriptor; - - void new_node_added( std::size_t /* node_id */, - Intersection_type /* type */, - halfedge_descriptor /* principal_edge */, - halfedge_descriptor /* additional_edge */, - bool /* is_target_coplanar */, - bool /* is_source_coplanar */ ) - {} - - void new_node_added_triple_face(std::size_t /* node_id */, - face_descriptor /* f1 */, - face_descriptor /* f2 */, - face_descriptor /* f3 */, - const TriangleMesh& /* tm */) - {} - - void new_vertex_added(std::size_t /* node_id */, - vertex_descriptor /* vh */, - TriangleMesh& /*tm*/){} -}; - //binds two edge constrained pmaps template struct Ecm_bind{ @@ -139,8 +112,7 @@ template< class TriangleMesh, class VertexPointMap, class OutputBuilder_ = Default, class EdgeMarkMapBind_ = Default, - class NewNodeVisitor_ = Default, - class NewFaceVisitor_ = Default, + class UserVisitor_ = Default, bool doing_autorefinement = false > class Surface_intersection_visitor_for_corefinement{ //default template parameters @@ -149,9 +121,7 @@ class Surface_intersection_visitor_for_corefinement{ typedef typename Default::Get >::type OutputBuilder; typedef typename Default::Get< - NewNodeVisitor_, Default_node_visitor >::type NewNodeVisitor; - typedef typename Default::Get< - NewFaceVisitor_, Default_visitor >::type NewFaceVisitor; + UserVisitor_, Default_visitor >::type UserVisitor; // config flags public: @@ -203,8 +173,7 @@ private: std::map< Node_id,std::set > coplanar_constraints; //data members that require initialization in the constructor - NewNodeVisitor& new_node_visitor; - NewFaceVisitor& new_face_visitor; + UserVisitor& user_visitor; OutputBuilder& output_builder; EdgeMarkMapBind marks_on_edges; bool input_with_coplanar_faces; @@ -238,9 +207,8 @@ private: // visitor public functions public: Surface_intersection_visitor_for_corefinement( - NewNodeVisitor& v, NewFaceVisitor& f, OutputBuilder& o, const EdgeMarkMapBind& emm) - : new_node_visitor(v) - , new_face_visitor(f) + UserVisitor& uv, OutputBuilder& o, const EdgeMarkMapBind& emm) + : user_visitor(uv) , output_builder(o) , marks_on_edges(emm) , input_with_coplanar_faces(false) @@ -345,7 +313,7 @@ public: { CGAL_assertion(f1!=f2 && f1!=f3 && f2!=f3); TriangleMesh* tm_ptr = const_cast(&tm); - new_node_visitor.new_node_added_triple_face(node_id, f1, f2, f3, tm); +// user_visitor.new_node_added_triple_face(node_id, f1, f2, f3, tm); // NODE_VISITOR_TAG #ifdef CGAL_DEBUG_AUTOREFINEMENT std::cout << "adding node " << node_id << " " << f1 << " " << f2 << " " << f3 << "\n"; #endif @@ -369,7 +337,7 @@ public: TriangleMesh* tm2_ptr = const_cast(&tm2); //forward to the visitor - new_node_visitor.new_node_added(node_id, type, h_1, h_2, is_target_coplanar, is_source_coplanar); +// user_visitor.new_node_added(node_id, type, h_1, h_2, is_target_coplanar, is_source_coplanar); // NODE_VISITOR_TAG switch(type) { case ON_FACE: //Face intersected by an edge @@ -782,7 +750,7 @@ public: halfedge_descriptor hnew = Euler::split_edge(hedge, tm); CGAL_assertion(expected_src==source(hnew,tm)); vertex_descriptor vnew=target(hnew,tm); - new_node_visitor.new_vertex_added(node_id, vnew, tm); +// user_visitor.new_vertex_added(node_id, vnew, tm); // NODE_VISITOR_TAG nodes.call_put(vpm, vnew, node_id, tm); node_id_to_vertex[node_id]=vnew; @@ -1028,7 +996,7 @@ public: // import the triangle in `cdt` in the face `f` of `tm` triangulate_a_face(f, tm, nodes, node_ids, node_id_to_vertex, - edge_to_hedge, cdt, vpm, new_node_visitor, new_face_visitor); + edge_to_hedge, cdt, vpm, user_visitor); // TODO Here we do the update only for internal edges. // Update for border halfedges could be done during the split diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Corefinement/face_graph_utils.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Corefinement/face_graph_utils.h index a26d49a1d2b..be442040d31 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Corefinement/face_graph_utils.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Corefinement/face_graph_utils.h @@ -232,6 +232,30 @@ struct Default_visitor{ void before_face_copy(face_descriptor /*f_old*/, TriangleMesh&, TriangleMesh&){} void after_face_copy(face_descriptor /*f_old*/, TriangleMesh&, face_descriptor /* f_new */, TriangleMesh&){} + +// calls commented in the code and probably incomplete due to the migration +// see NODE_VISITOR_TAG +/* + void new_node_added( std::size_t node_id, + Intersection_type type, + halfedge_descriptor principal_edge, + halfedge_descriptor additional_edge, + bool is_target_coplanar, + bool is_source_coplanar) + {} + + // autorefinement only + void new_node_added_triple_face(std::size_t node_id, + face_descriptor f1, + face_descriptor f2, + face_descriptor f3, + const TriangleMesh& tm) + {} + + void new_vertex_added(std::size_tnode_id, + vertex_descriptor vh, + TriangleMesh& tm){} +*/ }; template < class TriangleMesh, @@ -239,8 +263,7 @@ template < class TriangleMesh, class Node_id, class Node_vector, class CDT, - class NewNodeVisitor, - class NewFaceVisitor > + class UserVisitor> void triangulate_a_face( typename boost::graph_traits::face_descriptor current_face, @@ -254,8 +277,7 @@ triangulate_a_face( ::halfedge_descriptor>& edge_to_hedge, const CDT& cdt, const VertexPointMap& vpm, - NewNodeVisitor& new_node_visitor, - NewFaceVisitor& new_face_visitor) + UserVisitor& user_visitor) { typedef boost::graph_traits GT; typedef typename GT::vertex_descriptor vertex_descriptor; @@ -268,7 +290,7 @@ triangulate_a_face( BOOST_FOREACH(Node_id node_id, node_ids) { vertex_descriptor v=add_vertex(tm); - new_node_visitor.new_vertex_added(node_id, v, tm); +// user_visitor.new_vertex_added(node_id, v, tm); // NODE_VISITOR_TAG nodes.call_put(vpm, v, node_id, tm); CGAL_assertion(node_id_to_vertex.size()>node_id); node_id_to_vertex[node_id]=v; @@ -308,7 +330,7 @@ triangulate_a_face( } //grab triangles. - new_face_visitor.before_subface_creations(current_face,tm); + user_visitor.before_subface_creations(current_face,tm); for (typename CDT::Finite_faces_iterator it=cdt.finite_faces_begin(), it_end=cdt.finite_faces_end();;) { @@ -344,14 +366,14 @@ triangulate_a_face( if ( ++it!=it_end ) { - new_face_visitor.before_subface_created(tm); + user_visitor.before_subface_created(tm); current_face=add_face(tm); - new_face_visitor.after_subface_created(current_face,tm); + user_visitor.after_subface_created(current_face,tm); } else break; } - new_face_visitor.after_subface_creations(tm); + user_visitor.after_subface_creations(tm); } template @@ -784,7 +806,7 @@ template < bool reverse_patch_orientation, class VertexPointMapOut, class EdgeMarkMapOut, class EdgeMarkMapIn , - class NewFaceVisitor> + class UserVisitor> void append_patches_to_triangle_mesh( TriangleMesh& output, const boost::dynamic_bitset<>& patches_to_append, @@ -797,7 +819,7 @@ void append_patches_to_triangle_mesh( typename boost::graph_traits::edge_descriptor, typename boost::graph_traits::edge_descriptor >& tm_to_output_edges, - NewFaceVisitor& new_face_visitor) + UserVisitor& user_visitor) { typedef boost::graph_traits GT; typedef typename GT::halfedge_descriptor halfedge_descriptor; @@ -869,9 +891,9 @@ void append_patches_to_triangle_mesh( { cpp11::array hedges = helper.halfedges(f); - new_face_visitor.before_face_copy(f, patches.pm, output); + user_visitor.before_face_copy(f, patches.pm, output); face_descriptor new_f = add_face(output); - new_face_visitor.after_face_copy(f, patches.pm, new_f, output); + user_visitor.after_face_copy(f, patches.pm, new_f, output); set_halfedge(new_f, hedges[0], output); for (int i=0;i<3;++i) @@ -995,7 +1017,7 @@ template < class TriangleMesh, class EdgeMarkMapOut, class IntersectionPolylines, class PatchContainer, - class NewFaceVisitor> + class UserVisitor> void fill_new_triangle_mesh( TriangleMesh& output, const boost::dynamic_bitset<>& patches_of_tm1_to_import, @@ -1015,7 +1037,7 @@ void fill_new_triangle_mesh( EdgeMarkMapOut& edge_mark_map_out, std::vector< typename boost::graph_traits::edge_descriptor>& output_shared_edges, - NewFaceVisitor& new_face_visitor) + UserVisitor& user_visitor) { typedef boost::graph_traits GT; typedef typename GT::vertex_descriptor vertex_descriptor; @@ -1055,7 +1077,7 @@ void fill_new_triangle_mesh( edge_mark_map_out, edge_mark_map1, tm1_to_output_edges, - new_face_visitor); + user_visitor); else append_patches_to_triangle_mesh(output, patches_of_tm1_to_import, @@ -1065,7 +1087,7 @@ void fill_new_triangle_mesh( edge_mark_map_out, edge_mark_map1, tm1_to_output_edges, - new_face_visitor); + user_visitor); //import patches from tm2 if (reverse_orientation_of_patches_from_tm2) @@ -1077,7 +1099,7 @@ void fill_new_triangle_mesh( edge_mark_map_out, edge_mark_map2, tm2_to_output_edges, - new_face_visitor); + user_visitor); else append_patches_to_triangle_mesh(output, patches_of_tm2_to_import, @@ -1087,7 +1109,7 @@ void fill_new_triangle_mesh( edge_mark_map_out, edge_mark_map2, tm2_to_output_edges, - new_face_visitor); + user_visitor); } template + class UserVisitor> void compute_inplace_operation_delay_removal_and_insideout( TriangleMesh& tm1, TriangleMesh& tm2, @@ -1263,7 +1285,7 @@ void compute_inplace_operation_delay_removal_and_insideout( const EdgeMarkMapIn2& edge_mark_map2, const EdgeMarkMapOut& edge_mark_map_out1, EdgeMap& disconnected_patches_edge_to_tm2_edge, - NewFaceVisitor& new_face_visitor) + UserVisitor& user_visitor) { typedef boost::graph_traits GT; typedef typename GT::edge_descriptor edge_descriptor; @@ -1314,7 +1336,7 @@ void compute_inplace_operation_delay_removal_and_insideout( edge_mark_map_out1, edge_mark_map2, tm2_edge_to_tm1_edge, - new_face_visitor); + user_visitor); else append_patches_to_triangle_mesh(tm1, patches_of_tm2_to_import, @@ -1324,7 +1346,7 @@ void compute_inplace_operation_delay_removal_and_insideout( edge_mark_map_out1, edge_mark_map2, tm2_edge_to_tm1_edge, - new_face_visitor); + user_visitor); } template + class UserVisitor> void compute_inplace_operation( TriangleMesh& tm1, const TriangleMesh& /*tm2*/, @@ -1424,7 +1446,7 @@ void compute_inplace_operation( typename boost::graph_traits::edge_descriptor, typename boost::graph_traits::edge_descriptor >& tm2_edge_to_tm1_edge, - NewFaceVisitor& new_face_visitor) + UserVisitor& user_visitor) { typedef boost::unordered_map< typename boost::graph_traits::edge_descriptor, @@ -1454,7 +1476,7 @@ void compute_inplace_operation( edge_mark_map_out1, edge_mark_map_in2, tm2_edge_to_tm1_edge, - new_face_visitor); + user_visitor); else append_patches_to_triangle_mesh(tm1, patches_of_tm2_to_import, @@ -1464,7 +1486,7 @@ void compute_inplace_operation( edge_mark_map_out1, edge_mark_map_in2, tm2_edge_to_tm1_edge, - new_face_visitor); + user_visitor); } template + class UserVisitor> void compute_inplace_operation( TriangleMesh& tm1, const TriangleMesh& tm2, @@ -1526,7 +1548,7 @@ void compute_inplace_operation( const EdgeMarkMapIn2& edge_mark_map_in2, const EdgeMarkMapOut1& edge_mark_map_out1, const IntersectionPolylines& polylines, - NewFaceVisitor& new_face_visitor) + UserVisitor& user_visitor) { typedef boost::graph_traits GT; typedef typename GT::edge_descriptor edge_descriptor; @@ -1550,7 +1572,7 @@ void compute_inplace_operation( edge_mark_map_in2, edge_mark_map_out1, tm2_edge_to_tm1_edge, - new_face_visitor); + user_visitor); } // function used to remove polylines imported or kept that are incident only From 23a9ae929792176fde2f923bc9b4c503a5f9b8ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Loriot?= Date: Wed, 6 Jun 2018 13:23:16 +0200 Subject: [PATCH 38/43] doc typos --- .../include/CGAL/Polygon_mesh_processing/corefinement.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/corefinement.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/corefinement.h index 5ccf4ca5c6b..0cd01d6c94e 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/corefinement.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/corefinement.h @@ -265,7 +265,7 @@ bool does_bound_a_volume(const TriangleMesh& tm) * as an optional in `output` different from `boost::none`, the triangulated surface mesh * \link coref_def_subsec bounding \endlink the result of a particular Boolean operation * between the volumes bounded by `tm1` and `tm2` will be put in the corresponding triangle mesh. - * The position of the meshes in the array `output` are specific to the Boolean operation to compute + * The positions of the meshes in the array `output` are specific to the Boolean operation to compute * and `Corefinement::Boolean_operation_type` encodes and describes the ordering. Constructing the default array * means that no Boolean operation will be done. Overwriting a default value will trigger the corresponding * operation. In such a case, the address to a valid surface mesh must be provided. @@ -274,7 +274,7 @@ bool does_bound_a_volume(const TriangleMesh& tm) * named parameters passed for output meshes should be done using `make_tuple()` as the types of * named parameters are unspecified. * - * If `tm1` and/or `tm2` are one of the output surface meshes, they will be updated to + * If `tm1` and/or `tm2` are part of the output surface meshes, they will be updated to * contain the output (in-place operation), in any other case, the corresponding result will * be inserted into the mesh without clearing it first. * \pre \link CGAL::Polygon_mesh_processing::does_self_intersect() `!CGAL::Polygon_mesh_processing::does_self_intersect(tm1)` \endlink @@ -900,7 +900,7 @@ namespace experimental { Ecm ecm = boost::choose_param( boost::get_param(np, internal_np::edge_is_constrained), Corefinement::No_mark() ); -// New face visitor +// User visitor typedef typename boost::lookup_named_param_def < internal_np::graph_visitor_t, NamedParameters, @@ -976,7 +976,7 @@ namespace experimental { > ::type Ecm; Ecm ecm = boost::choose_param( boost::get_param(np, internal_np::edge_is_constrained), Corefinement::No_mark() ); -// New face visitor +// User visitor typedef typename boost::lookup_named_param_def < internal_np::graph_visitor_t, NamedParameters, From 6e1d9b2a3e3907ecc3d9b66d62b4e951525c7693 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Loriot?= Date: Wed, 6 Jun 2018 13:30:36 +0200 Subject: [PATCH 39/43] clip_volumes -> clip_volume --- .../CGAL/boost/graph/parameters_interface.h | 2 +- BGL/test/BGL/test_cgal_bgl_named_params.cpp | 6 +++--- .../Polygon_mesh_processing/NamedParameters.txt | 2 +- .../corefinement_mesh_union_and_intersection.cpp | 2 +- .../include/CGAL/Polygon_mesh_processing/clip.h | 14 +++++++------- .../test/Polygon_mesh_processing/test_pmp_clip.cpp | 12 ++++++------ .../Clip_polyhedron_plugin.cpp | 8 ++++---- 7 files changed, 23 insertions(+), 23 deletions(-) diff --git a/BGL/include/CGAL/boost/graph/parameters_interface.h b/BGL/include/CGAL/boost/graph/parameters_interface.h index 50241bba81b..0a66ee3dd8f 100644 --- a/BGL/include/CGAL/boost/graph/parameters_interface.h +++ b/BGL/include/CGAL/boost/graph/parameters_interface.h @@ -65,7 +65,7 @@ CGAL_add_named_parameter(outward_orientation_t, outward_orientation, outward_ori CGAL_add_named_parameter(overlap_test_t, overlap_test, do_overlap_test_of_bounded_sides) CGAL_add_named_parameter(preserve_genus_t, preserve_genus, preserve_genus) CGAL_add_named_parameter(throw_on_self_intersection_t, throw_on_self_intersection, throw_on_self_intersection) -CGAL_add_named_parameter(clip_volumes_t, clip_volumes, clip_volumes) +CGAL_add_named_parameter(clip_volume_t, clip_volume, clip_volume) CGAL_add_named_parameter(use_compact_clipper_t, use_compact_clipper, use_compact_clipper) // List of named parameters that we use in the package 'Surface Mesh Simplification' diff --git a/BGL/test/BGL/test_cgal_bgl_named_params.cpp b/BGL/test/BGL/test_cgal_bgl_named_params.cpp index 16f03530a2a..3ec87c34f60 100644 --- a/BGL/test/BGL/test_cgal_bgl_named_params.cpp +++ b/BGL/test/BGL/test_cgal_bgl_named_params.cpp @@ -73,7 +73,7 @@ void test(const NamedParameters& np) assert(get_param(np, CGAL::internal_np::nb_points_per_area_unit).v == 32); assert(get_param(np, CGAL::internal_np::nb_points_per_distance_unit).v == 33); assert(get_param(np, CGAL::internal_np::throw_on_self_intersection).v == 43); - assert(get_param(np, CGAL::internal_np::clip_volumes).v == 44); + assert(get_param(np, CGAL::internal_np::clip_volume).v == 44); assert(get_param(np, CGAL::internal_np::use_compact_clipper).v == 45); // Named parameters that we use in the package 'Surface Mesh Simplification' @@ -140,7 +140,7 @@ void test(const NamedParameters& np) check_same_type<32>(get_param(np, CGAL::internal_np::nb_points_per_area_unit)); check_same_type<33>(get_param(np, CGAL::internal_np::nb_points_per_distance_unit)); check_same_type<43>(get_param(np, CGAL::internal_np::throw_on_self_intersection)); - check_same_type<44>(get_param(np, CGAL::internal_np::clip_volumes)); + check_same_type<44>(get_param(np, CGAL::internal_np::clip_volume)); check_same_type<45>(get_param(np, CGAL::internal_np::use_compact_clipper)); // Named parameters that we use in the package 'Surface Mesh Simplification' @@ -206,7 +206,7 @@ int main() .preserve_genus(A<40>(40)) .verbosity_level(A<41>(41)) .throw_on_self_intersection(A<43>(43)) - .clip_volumes(A<44>(44)) + .clip_volume(A<44>(44)) .use_compact_clipper(A<45>(45)) ); diff --git a/Polygon_mesh_processing/doc/Polygon_mesh_processing/NamedParameters.txt b/Polygon_mesh_processing/doc/Polygon_mesh_processing/NamedParameters.txt index fa2884c841d..a5a3bc83c67 100644 --- a/Polygon_mesh_processing/doc/Polygon_mesh_processing/NamedParameters.txt +++ b/Polygon_mesh_processing/doc/Polygon_mesh_processing/NamedParameters.txt @@ -326,7 +326,7 @@ and make the operation impossible with the current version of the code. \b Default value is `false` \cgalNPEnd -\cgalNPBegin{clip_volumes} \anchor PMP_clip_volumes +\cgalNPBegin{clip_volume} \anchor PMP_clip_volume Parameter used in `clip()` functions to clip a volume rather than a surface. \n \b Type : `bool` \n diff --git a/Polygon_mesh_processing/examples/Polygon_mesh_processing/corefinement_mesh_union_and_intersection.cpp b/Polygon_mesh_processing/examples/Polygon_mesh_processing/corefinement_mesh_union_and_intersection.cpp index 1ad2772bb91..1f2756a0da5 100644 --- a/Polygon_mesh_processing/examples/Polygon_mesh_processing/corefinement_mesh_union_and_intersection.cpp +++ b/Polygon_mesh_processing/examples/Polygon_mesh_processing/corefinement_mesh_union_and_intersection.cpp @@ -69,5 +69,5 @@ int main(int argc, char* argv[]) else std::cout << "Intersection could not be computed\n"; - return 1; + return 0; } diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/clip.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/clip.h index 68c80d439c5..d33dde326a2 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/clip.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/clip.h @@ -386,7 +386,7 @@ clip_to_bbox(const Plane_3& plane, * \ingroup PMP_corefinement_grp * clips `tm` by keeping the part that is inside the volume \link coref_def_subsec bounded \endlink * by `clipper`. - * If `tm` is closed, the clipped part can be closed too if the named parameter `clip_volumes` is set to `true`. + * If `tm` is closed, the clipped part can be closed too if the named parameter `clip_volume` is set to `true`. * See subsection \ref coref_clip for more details. * \attention With the current implementation, `clipper` will be modified (refined with the intersection with `tm`). * @@ -424,11 +424,11 @@ clip_to_bbox(const Plane_3& plane, * checked for self-intersections and `CGAL::Polygon_mesh_processing::Corefinement::Self_intersection_exception` * will be thrown if at least one is found. * \cgalParamEnd - * \cgalParamBegin{clip_volumes} if `true` and `tm` is closed, the clipping will be done on + * \cgalParamBegin{clip_volume} if `true` and `tm` is closed, the clipping will be done on * the volume \link coref_def_subsec bounded \endlink by `tm` rather than on its surface * (i.e. `tm` will be kept closed). * \cgalParamEnd - * \cgalParamBegin{use_compact_clipper} if `false` and `clip_volumes` is `false` and `tm` is open, the parts of `tm` coplanar with `clipper` + * \cgalParamBegin{use_compact_clipper} if `false` and `clip_volume` is `false` and `tm` is open, the parts of `tm` coplanar with `clipper` * will not be part of the output. * \cgalParamEnd * \cgalNamedParamsEnd @@ -446,7 +446,7 @@ clip( TriangleMesh& tm, const NamedParameters2& np_c) { const bool close = - boost::choose_param(boost::get_param(np_tm, internal_np::clip_volumes), false); + boost::choose_param(boost::get_param(np_tm, internal_np::clip_volume), false); if (close && is_closed(tm)) return corefine_and_compute_intersection(tm, clipper, tm, np_tm, np_c); @@ -477,7 +477,7 @@ bool dispatch_clip_call(TriangleMesh& tm, TriangleMesh& clipper, /** * \ingroup PMP_corefinement_grp * clips `tm` by keeping the part that is on the negative side of `plane` (side opposite to its normal vector). - * If `tm` is closed, the clipped part can be closed too if the named parameter `clip_volumes` is set to `true`. + * If `tm` is closed, the clipped part can be closed too if the named parameter `clip_volume` is set to `true`. * See subsection \ref coref_clip for more details. * * \note In the current implementation it is not possible to set the vertex point map and the default will be used. @@ -504,11 +504,11 @@ bool dispatch_clip_call(TriangleMesh& tm, TriangleMesh& clipper, * checked for self-intersections and `CGAL::Polygon_mesh_processing::Corefinement::Self_intersection_exception` * will be thrown if at least one is found. * \cgalParamEnd - * \cgalParamBegin{clip_volumes} if `true` and `tm` is closed, the clipping will be done on + * \cgalParamBegin{clip_volume} if `true` and `tm` is closed, the clipping will be done on * the volume \link coref_def_subsec bounded \endlink by `tm` rather than on its surface * (i.e. `tm` will be kept closed). * \cgalParamEnd - * \cgalParamBegin{use_compact_clipper} if `false` and `clip_volumes` is `false` and `tm` is open, the parts of `tm` coplanar with `plane` + * \cgalParamBegin{use_compact_clipper} if `false` and `clip_volume` is `false` and `tm` is open, the parts of `tm` coplanar with `plane` * will not be part of the output. * \cgalNamedParamsEnd * diff --git a/Polygon_mesh_processing/test/Polygon_mesh_processing/test_pmp_clip.cpp b/Polygon_mesh_processing/test/Polygon_mesh_processing/test_pmp_clip.cpp index 650cd269b63..ea5e22a7795 100644 --- a/Polygon_mesh_processing/test/Polygon_mesh_processing/test_pmp_clip.cpp +++ b/Polygon_mesh_processing/test/Polygon_mesh_processing/test_pmp_clip.cpp @@ -26,7 +26,7 @@ void test() input.close(); PMP::clip(tm1, tm2, - params::clip_volumes(false) + params::clip_volume(false) .face_index_map(get(CGAL::dynamic_face_property_t(), tm1)), params::face_index_map(get(CGAL::dynamic_face_property_t(), tm2)) ); @@ -42,7 +42,7 @@ void test() input >> tm2; input.close(); - PMP::clip(tm1, tm2, params::clip_volumes(true) + PMP::clip(tm1, tm2, params::clip_volume(true) .face_index_map(get(CGAL::dynamic_face_property_t(), tm1)), params::face_index_map(get(CGAL::dynamic_face_property_t(), tm2))); assert(CGAL::is_closed(tm1)); @@ -55,14 +55,14 @@ void test() K::Plane_3 plane(0, 0, 1, -1); - PMP::clip(tm1, plane, params::clip_volumes(true)); + PMP::clip(tm1, plane, params::clip_volume(true)); assert(CGAL::is_closed(tm1)); CGAL::clear(tm1); input.open("data-coref/cube.off"); input >> tm1; input.close(); - PMP::clip(tm1, plane, params::clip_volumes(false) + PMP::clip(tm1, plane, params::clip_volume(false) .use_compact_clipper(false)); assert(!CGAL::is_closed(tm1)); CGAL::clear(tm1); @@ -70,7 +70,7 @@ void test() input.open("data-coref/cube.off"); input >> tm1; input.close(); - PMP::clip(tm1, plane, params::clip_volumes(false) + PMP::clip(tm1, plane, params::clip_volume(false) .use_compact_clipper(true)); assert(CGAL::is_closed(tm1)); CGAL::clear(tm1); @@ -78,7 +78,7 @@ void test() input.open("data-coref/cube.off"); input >> tm1; input.close(); - PMP::clip(tm1, K::Plane_3(-0.236474, 0.437732, 0.867451, -0.838791), params::clip_volumes(true)); + PMP::clip(tm1, K::Plane_3(-0.236474, 0.437732, 0.867451, -0.838791), params::clip_volume(true)); assert(CGAL::is_closed(tm1)); assert(!CGAL::is_empty(tm1)); CGAL::clear(tm1); diff --git a/Polyhedron/demo/Polyhedron/Plugins/Operations_on_polyhedra/Clip_polyhedron_plugin.cpp b/Polyhedron/demo/Polyhedron/Plugins/Operations_on_polyhedra/Clip_polyhedron_plugin.cpp index e1d95fd5fe4..3cfa72e2748 100644 --- a/Polyhedron/demo/Polyhedron/Plugins/Operations_on_polyhedra/Clip_polyhedron_plugin.cpp +++ b/Polyhedron/demo/Polyhedron/Plugins/Operations_on_polyhedra/Clip_polyhedron_plugin.cpp @@ -142,7 +142,7 @@ public : try { CGAL::Polygon_mesh_processing::clip(*neg_side, plane->plane(), - CGAL::Polygon_mesh_processing::parameters::clip_volumes( + CGAL::Polygon_mesh_processing::parameters::clip_volume( ui_widget.close_checkBox->isChecked()). throw_on_self_intersection(true)); Item* new_item = new Item(neg_side); @@ -156,7 +156,7 @@ public : Mesh* pos_side = new Mesh(*item->face_graph()); CGAL::Polygon_mesh_processing::clip(*pos_side, plane->plane().opposite(), - CGAL::Polygon_mesh_processing::parameters::clip_volumes( + CGAL::Polygon_mesh_processing::parameters::clip_volume( ui_widget.close_checkBox->isChecked()). throw_on_self_intersection(true)); @@ -246,7 +246,7 @@ public Q_SLOTS: { CGAL::Polygon_mesh_processing::clip(*(sm_item->face_graph()), plane->plane(), - CGAL::Polygon_mesh_processing::parameters::clip_volumes( + CGAL::Polygon_mesh_processing::parameters::clip_volume( ui_widget.close_checkBox->isChecked()). throw_on_self_intersection(true)); } @@ -254,7 +254,7 @@ public Q_SLOTS: { CGAL::Polygon_mesh_processing::clip(*(poly_item->face_graph()), plane->plane(), - CGAL::Polygon_mesh_processing::parameters::clip_volumes( + CGAL::Polygon_mesh_processing::parameters::clip_volume( ui_widget.close_checkBox->isChecked()). throw_on_self_intersection(true)); From 5ebdb7c87229a461e0c2b8cb94301d05e6a2d510 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Loriot?= Date: Wed, 13 Jun 2018 09:47:05 +0200 Subject: [PATCH 40/43] fix checking condition in autorefine test --- .../test/Polygon_mesh_processing/test_autorefinement.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Polygon_mesh_processing/test/Polygon_mesh_processing/test_autorefinement.cpp b/Polygon_mesh_processing/test/Polygon_mesh_processing/test_autorefinement.cpp index a29f0b8697a..9f603bd22fa 100644 --- a/Polygon_mesh_processing/test/Polygon_mesh_processing/test_autorefinement.cpp +++ b/Polygon_mesh_processing/test/Polygon_mesh_processing/test_autorefinement.cpp @@ -38,6 +38,7 @@ void test(const char* fname, std::size_t nb_polylines, std::size_t total_nb_poin exit(EXIT_FAILURE); } input.close(); + std::size_t nb_vertices_before_autorefine = num_vertices(mesh); // Testing surface_self_intersection() std::vector< std::vector >polylines; @@ -53,7 +54,7 @@ void test(const char* fname, std::size_t nb_polylines, std::size_t total_nb_poin PMP::experimental::autorefine(mesh, PMP::parameters::visitor(visitor)); assert( nb_vertices_after_autorefine==num_vertices(mesh)); - assert( *(visitor.i) != 0); + assert( (nb_vertices_before_autorefine!=nb_vertices_after_autorefine)== (*(visitor.i) != 0) ); // Testing autorefine_and_remove_self_intersections() input.open(fname); From 7580d0124de6d7f3071b8c15ce65803a17da287d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Loriot?= Date: Wed, 13 Jun 2018 09:54:00 +0200 Subject: [PATCH 41/43] fix corefine test --- .../test/Polygon_mesh_processing/test_corefine.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/Polygon_mesh_processing/test/Polygon_mesh_processing/test_corefine.cpp b/Polygon_mesh_processing/test/Polygon_mesh_processing/test_corefine.cpp index 1dc6cb02ffd..5804b67a88f 100644 --- a/Polygon_mesh_processing/test/Polygon_mesh_processing/test_corefine.cpp +++ b/Polygon_mesh_processing/test/Polygon_mesh_processing/test_corefine.cpp @@ -40,12 +40,14 @@ void test(const char* f1, const char* f2) input.close(); My_visitor sm_v; + std::size_t nb_v_before = num_vertices(sm1) + num_vertices(sm2); CGAL::Polygon_mesh_processing::corefine(sm1, sm2, CGAL::Polygon_mesh_processing::parameters::visitor(sm_v)); + std::size_t nb_v_after = num_vertices(sm1) + num_vertices(sm2); assert(sm1.is_valid()); assert(sm2.is_valid()); - assert(*(sm_v.i) != 0); + assert((*(sm_v.i) != 0) == (nb_v_before!=nb_v_after)); std::cout << " with Polyhedron_3\n"; Polyhedron_3 P, Q; @@ -58,9 +60,12 @@ void test(const char* f1, const char* f2) input >> Q; My_visitor sm_p; + nb_v_before = num_vertices(P) + num_vertices(Q); CGAL::Polygon_mesh_processing::corefine(P, Q, CGAL::Polygon_mesh_processing::parameters::visitor(sm_p)); - assert(*(sm_p.i) != 0); + nb_v_after = num_vertices(P) + num_vertices(Q); + + assert((*(sm_p.i) != 0) == (nb_v_before!=nb_v_after)); assert(*(sm_v.i) == *(sm_p.i)); assert(P.is_valid()); From 700979b10f5b72edd078702cd07630d92e95ced6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Loriot?= Date: Mon, 18 Jun 2018 12:49:56 +0200 Subject: [PATCH 42/43] remove extra typename --- .../corefinement_mesh_union_with_attributes.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Polygon_mesh_processing/examples/Polygon_mesh_processing/corefinement_mesh_union_with_attributes.cpp b/Polygon_mesh_processing/examples/Polygon_mesh_processing/corefinement_mesh_union_with_attributes.cpp index 37cba10db44..bc6488d2b12 100644 --- a/Polygon_mesh_processing/examples/Polygon_mesh_processing/corefinement_mesh_union_with_attributes.cpp +++ b/Polygon_mesh_processing/examples/Polygon_mesh_processing/corefinement_mesh_union_with_attributes.cpp @@ -13,7 +13,7 @@ namespace PMP = CGAL::Polygon_mesh_processing; struct Visitor : public PMP::Corefinement::Default_visitor { - typedef typename Mesh::Face_index face_descriptor; + typedef Mesh::Face_index face_descriptor; boost::container::flat_map > properties; int face_id; From 6d89662e2a7a2f65e0d02109b7a849e76cf89588 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Loriot?= Date: Tue, 26 Jun 2018 00:16:57 +0200 Subject: [PATCH 43/43] use need API + add missing overload --- .../CGAL/Polygon_mesh_processing/intersection.h | 13 +++++++++++++ .../Plugins/PMP/Surface_intersection_plugin.cpp | 2 +- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/intersection.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/intersection.h index 540202f47ba..9dc79169d25 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/intersection.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/intersection.h @@ -1757,6 +1757,19 @@ surface_intersection(const TriangleMesh& tm1, CGAL::Polygon_mesh_processing::parameters::all_default()); } +template +OutputIterator +surface_intersection(const TriangleMesh& tm1, + const TriangleMesh& tm2, + OutputIterator polyline_output, + const NamedParameters1& np) +{ + return surface_intersection(tm1, tm2, polyline_output, np, + CGAL::Polygon_mesh_processing::parameters::all_default()); +} + #ifndef CGAL_NO_DEPRECATED_CODE template polyhedron(), *itemB->polyhedron(), std::back_inserter(new_item->polylines), - true); + PMP::parameters::throw_on_self_intersection(true)); } catch(CGAL::Polygon_mesh_processing::Corefinement::Self_intersection_exception) {