From ec40acf2c3d89199fd4843fa5a88aeefcb560c87 Mon Sep 17 00:00:00 2001 From: Maxime Gimeno Date: Tue, 9 Jan 2018 12:31:32 +0100 Subject: [PATCH 001/116] Add a script that bundles the demo with AppImage for all Linux distribs. --- .../Bundle_polyhedron_demo_with_appimage.sh | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 Scripts/developer_scripts/Bundle_polyhedron_demo_with_appimage.sh diff --git a/Scripts/developer_scripts/Bundle_polyhedron_demo_with_appimage.sh b/Scripts/developer_scripts/Bundle_polyhedron_demo_with_appimage.sh new file mode 100644 index 00000000000..ba4e879cd2b --- /dev/null +++ b/Scripts/developer_scripts/Bundle_polyhedron_demo_with_appimage.sh @@ -0,0 +1,8 @@ +#!/bin/bash +if [ "$1" == '--help' ]; then + echo "Usage: $0 " + echo "Builds and packages the Polyhedron demo form the CGAL dir." + exit 0 +fi +docker run --rm -v "$2":/results:Z -v "$1":/cgal:ro docker.io/cgal/bundle-3d-demo '/scripts/build.sh -j6 && /scripts/deploy.sh' + From ab8719458374af079ae0a1ba34f174180617f925 Mon Sep 17 00:00:00 2001 From: Maxime Gimeno Date: Thu, 15 Mar 2018 14:15:13 +0100 Subject: [PATCH 002/116] Makes the number of cores for make a variable. --- .../developer_scripts/Bundle_polyhedron_demo_with_appimage.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Scripts/developer_scripts/Bundle_polyhedron_demo_with_appimage.sh b/Scripts/developer_scripts/Bundle_polyhedron_demo_with_appimage.sh index ba4e879cd2b..dbfbcb8f200 100644 --- a/Scripts/developer_scripts/Bundle_polyhedron_demo_with_appimage.sh +++ b/Scripts/developer_scripts/Bundle_polyhedron_demo_with_appimage.sh @@ -1,8 +1,8 @@ #!/bin/bash if [ "$1" == '--help' ]; then - echo "Usage: $0 " + echo "Usage: $0 " echo "Builds and packages the Polyhedron demo form the CGAL dir." exit 0 fi -docker run --rm -v "$2":/results:Z -v "$1":/cgal:ro docker.io/cgal/bundle-3d-demo '/scripts/build.sh -j6 && /scripts/deploy.sh' +docker run --rm -v "$2":/results:Z -v "$1":/cgal:ro docker.io/cgal/bundle-3d-demo "/scripts/build.sh -j$3 && /scripts/deploy.sh" From 5010283dfe62fd1b236b4eb4b262abedc7455b32 Mon Sep 17 00:00:00 2001 From: Maxime Gimeno Date: Fri, 25 May 2018 16:15:35 +0200 Subject: [PATCH 003/116] Change help for script. Specify the path should lead to a release (not a git repo). --- .../developer_scripts/Bundle_polyhedron_demo_with_appimage.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Scripts/developer_scripts/Bundle_polyhedron_demo_with_appimage.sh b/Scripts/developer_scripts/Bundle_polyhedron_demo_with_appimage.sh index dbfbcb8f200..e2911620838 100644 --- a/Scripts/developer_scripts/Bundle_polyhedron_demo_with_appimage.sh +++ b/Scripts/developer_scripts/Bundle_polyhedron_demo_with_appimage.sh @@ -1,6 +1,6 @@ #!/bin/bash if [ "$1" == '--help' ]; then - echo "Usage: $0 " + echo "Usage: $0 " echo "Builds and packages the Polyhedron demo form the CGAL dir." exit 0 fi From 0db8941e0a276f7eebd3472886aed18cfa1f7250 Mon Sep 17 00:00:00 2001 From: Konstantinos Katrioplas Date: Tue, 10 Apr 2018 15:26:21 +0200 Subject: [PATCH 004/116] is_degenerate_edge function --- .../CGAL/Polygon_mesh_processing/repair.h | 48 +++++++++++++++++++ .../remove_degeneracies_test.cpp | 16 +++++++ 2 files changed, 64 insertions(+) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/repair.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/repair.h index 6fcb1e929dd..02d71ec97a8 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/repair.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/repair.h @@ -186,6 +186,54 @@ degenerate_faces(const TriangleMesh& tm, OutputIterator out) return degenerate_faces(tm, get(vertex_point, tm), Kernel(), out); } +/// \ingroup PMP_repairing_grp +/// checks whether an edge is degenerate. +/// An edge is considered degenerate if two of its vertices share the same location. +/// +/// @tparam PolygonMesh a model of `FaceListGraph` and `MutableFaceGraph` +/// @tparam NamedParameters a sequence of \ref pmp_namedparameters "Named Parameters" +/// +/// @param pm the triangulated surface mesh to be repaired +/// @param np optional \ref pmp_namedparameters "Named Parameters" described below +/// +/// \cgalNamedParamsBegin +/// \cgalParamBegin{vertex_point_map} the property map with the points associated to the vertices of `pmesh`. The type of this map is model of `ReadWritePropertyMap`. +/// If this parameter is omitted, an internal property map for +/// `CGAL::vertex_point_t` should be available in `PolygonMesh` +/// \cgalParamEnd +/// \cgalParamBegin{geom_traits} a geometric traits class instance. +/// The traits class must provide the nested type `Point_3`, +/// and the nested functor : +/// - `Equal_3` to check whether 2 points are identical +/// \cgalParamEnd +/// \cgalNamedParamsEnd +/// +/// \return true if the edge is degenerate +template +bool is_degenerate_edge(typename boost::graph_traits::edge_descriptor e, + PolygonMesh& pm, + const NamedParameters& np) +{ + using boost::get_param; + using boost::choose_param; + + typedef typename GetVertexPointMap::type VertexPointMap; + VertexPointMap vpmap = choose_param(get_param(np, internal_np::vertex_point), + get_property_map(vertex_point, pm)); + typedef typename GetGeomTraits::type Traits; + Traits traits = choose_param(get_param(np, internal_np::geom_traits), Traits()); + + if ( traits.equal_3_object()(get(vpmap, target(e, pm)), get(vpmap, source(e, pm))) ) + return true; +} + +template +bool is_degenerate_edge(typename boost::graph_traits::edge_descriptor e, + PolygonMesh& pm) +{ + return is_degenerate_edge(e, pm, parameters::all_default()); +} + // this function remove a border edge even if it does not satisfy the link condition. // The only limitation is that the length connected component of the boundary this edge // is strictly greater than 3 diff --git a/Polygon_mesh_processing/test/Polygon_mesh_processing/remove_degeneracies_test.cpp b/Polygon_mesh_processing/test/Polygon_mesh_processing/remove_degeneracies_test.cpp index 386e27723a1..3e069bf79be 100644 --- a/Polygon_mesh_processing/test/Polygon_mesh_processing/remove_degeneracies_test.cpp +++ b/Polygon_mesh_processing/test/Polygon_mesh_processing/remove_degeneracies_test.cpp @@ -2,6 +2,7 @@ #include #include +#include #include #include @@ -29,6 +30,20 @@ void fix(const char* fname) assert( CGAL::is_valid_polygon_mesh(mesh) ); } +void check_edge_degeneracy(const char* fname) +{ + std::ifstream input(fname); + + Surface_mesh mesh; + if (!input || !(input >> mesh) || mesh.is_empty()) { + std::cerr << fname << " is not a valid off file.\n"; + exit(1); + } + + BOOST_FOREACH(typename boost::graph_traits::edge_descriptor e, edges(mesh)) + CGAL::Polygon_mesh_processing::is_degenerate_edge(e, mesh); +} + int main() { fix("data_degeneracies/degtri_2dt_1edge_split_twice.off"); @@ -38,6 +53,7 @@ int main() fix("data_degeneracies/degtri_three.off"); fix("data_degeneracies/degtri_single.off"); fix("data_degeneracies/trihole.off"); + check_edge_degeneracy("data_degeneracies/degtri_edge.off"); return 0; } From 8e285cb1a778c22979a64d43b0bd0026c3b80e3b Mon Sep 17 00:00:00 2001 From: Konstantinos Katrioplas Date: Tue, 10 Apr 2018 16:58:13 +0200 Subject: [PATCH 005/116] is_degenerate_triangle_face function --- .../CGAL/Polygon_mesh_processing/repair.h | 56 ++++++++++++++++++- .../remove_degeneracies_test.cpp | 15 +++++ 2 files changed, 69 insertions(+), 2 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/repair.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/repair.h index 02d71ec97a8..d12e1321a95 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/repair.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/repair.h @@ -193,6 +193,7 @@ degenerate_faces(const TriangleMesh& tm, OutputIterator out) /// @tparam PolygonMesh a model of `FaceListGraph` and `MutableFaceGraph` /// @tparam NamedParameters a sequence of \ref pmp_namedparameters "Named Parameters" /// +/// @param e the edge to check whether is degenerate /// @param pm the triangulated surface mesh to be repaired /// @param np optional \ref pmp_namedparameters "Named Parameters" described below /// @@ -234,6 +235,57 @@ bool is_degenerate_edge(typename boost::graph_traits::edge_descript return is_degenerate_edge(e, pm, parameters::all_default()); } +/// \ingroup PMP_repairing_grp +/// checks whether a triangle face is degenerate. +/// A triangle face is considered degenerate if all three points of the face are collinear. +/// +/// @tparam TriangleMesh a model of `FaceListGraph` and `MutableFaceGraph` +/// @tparam NamedParameters a sequence of \ref pmp_namedparameters "Named Parameters" +/// +/// @param f the face to check whether is degenerate +/// @param tm the triangulated surface mesh to be repaired +/// @param np optional \ref pmp_namedparameters "Named Parameters" described below +/// +/// \cgalNamedParamsBegin +/// \cgalParamBegin{vertex_point_map} the property map with the points associated to the vertices of `pmesh`. The type of this map is model of `ReadWritePropertyMap`. +/// If this parameter is omitted, an internal property map for +/// `CGAL::vertex_point_t` should be available in `PolygonMesh` +/// \cgalParamEnd +/// \cgalParamBegin{geom_traits} a geometric traits class instance. +/// The traits class must provide the nested type `Point_3`, +/// and the nested functor : +/// - `Collinear_3` to check whether 3 points are collinear +/// \cgalParamEnd +/// \cgalNamedParamsEnd +/// +/// \return true if the triangle face is degenerate +template +bool is_degenerate_triangle_face(typename boost::graph_traits::face_descriptor f, + TriangleMesh& tm, + const NamedParameters& np) +{ + CGAL_assertion(CGAL::is_triangle_mesh(tm)); + + using boost::get_param; + using boost::choose_param; + + typedef typename GetVertexPointMap::type VertexPointMap; + VertexPointMap vpmap = choose_param(get_param(np, internal_np::vertex_point), + get_property_map(vertex_point, tm)); + typedef typename GetGeomTraits::type Traits; + Traits traits = choose_param(get_param(np, internal_np::geom_traits), Traits()); + + // call from BGL helpers + return is_degenerate_triangle_face(f, tm, vpmap, traits); +} + +template +bool is_degenerate_triangle_face(typename boost::graph_traits::face_descriptor f, + TriangleMesh& tm) +{ + return CGAL::Polygon_mesh_processing::is_degenerate_triangle_face(f, tm, parameters::all_default()); +} + // this function remove a border edge even if it does not satisfy the link condition. // The only limitation is that the length connected component of the boundary this edge // is strictly greater than 3 @@ -780,7 +832,7 @@ std::size_t remove_degenerate_faces(TriangleMesh& tmesh, // Then, remove triangles made of 3 collinear points std::set degenerate_face_set; BOOST_FOREACH(face_descriptor fd, faces(tmesh)) - if ( is_degenerate_triangle_face(fd, tmesh, vpmap, traits) ) + if ( is_degenerate_triangle_face(fd, tmesh) ) degenerate_face_set.insert(fd); nb_deg_faces+=degenerate_face_set.size(); @@ -811,7 +863,7 @@ std::size_t remove_degenerate_faces(TriangleMesh& tmesh, degenerate_face_set.erase( face(hd2, tmesh) ); // remove the central vertex and check if the new face is degenerated hd=CGAL::Euler::remove_center_vertex(hd, tmesh); - if (is_degenerate_triangle_face(face(hd, tmesh), tmesh, vpmap, traits)) + if (is_degenerate_triangle_face(face(hd, tmesh), tmesh)) { degenerate_face_set.insert( face(hd, tmesh) ); } diff --git a/Polygon_mesh_processing/test/Polygon_mesh_processing/remove_degeneracies_test.cpp b/Polygon_mesh_processing/test/Polygon_mesh_processing/remove_degeneracies_test.cpp index 3e069bf79be..e87bd9b2b83 100644 --- a/Polygon_mesh_processing/test/Polygon_mesh_processing/remove_degeneracies_test.cpp +++ b/Polygon_mesh_processing/test/Polygon_mesh_processing/remove_degeneracies_test.cpp @@ -44,6 +44,20 @@ void check_edge_degeneracy(const char* fname) CGAL::Polygon_mesh_processing::is_degenerate_edge(e, mesh); } +void check_triangle_face_degeneracy(const char* fname) +{ + std::ifstream input(fname); + + Surface_mesh mesh; + if (!input || !(input >> mesh) || mesh.is_empty()) { + std::cerr << fname << " is not a valid off file.\n"; + exit(1); + } + + BOOST_FOREACH(typename boost::graph_traits::face_descriptor f, faces(mesh)) + CGAL::Polygon_mesh_processing::is_degenerate_triangle_face(f, mesh); +} + int main() { fix("data_degeneracies/degtri_2dt_1edge_split_twice.off"); @@ -54,6 +68,7 @@ int main() fix("data_degeneracies/degtri_single.off"); fix("data_degeneracies/trihole.off"); check_edge_degeneracy("data_degeneracies/degtri_edge.off"); + check_triangle_face_degeneracy("data_degeneracies/degtri_four.off"); return 0; } From 9f315abad6d417634a83fce864164c2cceda654f Mon Sep 17 00:00:00 2001 From: Konstantinos Katrioplas Date: Tue, 10 Apr 2018 17:40:30 +0200 Subject: [PATCH 006/116] duplicate_vertices function doc --- .../CGAL/Polygon_mesh_processing/repair.h | 40 +++++++++++++++---- .../remove_degeneracies_test.cpp | 14 +++++++ 2 files changed, 46 insertions(+), 8 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/repair.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/repair.h index d12e1321a95..9b7fcfa4c78 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/repair.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/repair.h @@ -157,8 +157,6 @@ struct Less_vertex_point{ } }; -///\cond SKIP_IN_MANUAL - template OutputIterator degenerate_faces(const TriangleMesh& tm, @@ -1381,7 +1379,6 @@ std::size_t remove_degenerate_faces(TriangleMesh& tmesh, return nb_deg_faces; } - template std::size_t remove_degenerate_faces(TriangleMesh& tmesh) { @@ -1389,9 +1386,38 @@ std::size_t remove_degenerate_faces(TriangleMesh& tmesh) CGAL::Polygon_mesh_processing::parameters::all_default()); } -template -std::size_t duplicate_non_manifold_vertices(TriangleMesh& tm, Vpm vpm) +/// \ingroup PMP_repairing_grp +/// duplicates all non-manifold vertices of the input mesh. +/// +/// @tparam TriangleMesh a model of `FaceListGraph` and `MutableFaceGraph` +/// @tparam NamedParameters a sequence of \ref pmp_namedparameters "Named Parameters" +/// +/// @param tm the triangulated surface mesh to be repaired +/// @param np optional \ref pmp_namedparameters "Named Parameters" described below +/// +/// \cgalNamedParamsBegin +/// \cgalParamBegin{vertex_point_map} the property map with the points associated to the vertices of `pmesh`. The type of this map is model of `ReadWritePropertyMap`. +/// If this parameter is omitted, an internal property map for +/// `CGAL::vertex_point_t` should be available in `PolygonMesh` +/// \cgalParamEnd +/// \cgalParamBegin{geom_traits} a geometric traits class instance. +/// \cgalParamEnd +/// \cgalNamedParamsEnd +/// +/// \return true if the triangle face is degenerate +template +std::size_t duplicate_non_manifold_vertices(TriangleMesh& tm, + const NamedParameters& np) { + CGAL_assertion(CGAL::is_triangle_mesh(tm)); + + using boost::get_param; + using boost::choose_param; + + typedef typename GetVertexPointMap::type VertexPointMap; + VertexPointMap vpm = choose_param(get_param(np, internal_np::vertex_point), + get_property_map(vertex_point, tm)); + typedef boost::graph_traits GT; typedef typename GT::vertex_descriptor vertex_descriptor; typedef typename GT::halfedge_descriptor halfedge_descriptor; @@ -1443,11 +1469,9 @@ std::size_t duplicate_non_manifold_vertices(TriangleMesh& tm, Vpm vpm) template std::size_t duplicate_non_manifold_vertices(TriangleMesh& tm) { - return duplicate_non_manifold_vertices(tm, get(vertex_point, tm)); + return duplicate_non_manifold_vertices(tm, parameters::all_default()); } -/// \endcond - /// \ingroup PMP_repairing_grp /// removes the isolated vertices from any polygon mesh. diff --git a/Polygon_mesh_processing/test/Polygon_mesh_processing/remove_degeneracies_test.cpp b/Polygon_mesh_processing/test/Polygon_mesh_processing/remove_degeneracies_test.cpp index e87bd9b2b83..8f89842f922 100644 --- a/Polygon_mesh_processing/test/Polygon_mesh_processing/remove_degeneracies_test.cpp +++ b/Polygon_mesh_processing/test/Polygon_mesh_processing/remove_degeneracies_test.cpp @@ -58,6 +58,19 @@ void check_triangle_face_degeneracy(const char* fname) CGAL::Polygon_mesh_processing::is_degenerate_triangle_face(f, mesh); } +void test_vetices_duplication(const char* fname) +{ + std::ifstream input(fname); + + Surface_mesh mesh; + if (!input || !(input >> mesh) || mesh.is_empty()) { + std::cerr << fname << " is not a valid off file.\n"; + exit(1); + } + + CGAL::Polygon_mesh_processing::duplicate_non_manifold_vertices(mesh); +} + int main() { fix("data_degeneracies/degtri_2dt_1edge_split_twice.off"); @@ -69,6 +82,7 @@ int main() fix("data_degeneracies/trihole.off"); check_edge_degeneracy("data_degeneracies/degtri_edge.off"); check_triangle_face_degeneracy("data_degeneracies/degtri_four.off"); + test_vetices_duplication("data_degeneracies/degtri_four.off"); return 0; } From c3e7f6d94b09b71bf5df281c7fba50628d9436d0 Mon Sep 17 00:00:00 2001 From: Konstantinos Katrioplas Date: Wed, 11 Apr 2018 12:04:21 +0200 Subject: [PATCH 007/116] is_non_manifold_vertex function --- .../CGAL/Polygon_mesh_processing/repair.h | 54 +++++++++++++++---- .../remove_degeneracies_test.cpp | 15 ++++++ 2 files changed, 59 insertions(+), 10 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/repair.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/repair.h index 9b7fcfa4c78..aef378dee83 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/repair.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/repair.h @@ -184,6 +184,40 @@ degenerate_faces(const TriangleMesh& tm, OutputIterator out) return degenerate_faces(tm, get(vertex_point, tm), Kernel(), out); } +/// \ingroup PMP_repairing_grp +/// checks whether a vertex is non-manifold. +/// +/// @tparam TriangleMesh a model of `FaceListGraph` and `MutableFaceGraph` +/// +/// @param v the vertex to check whether is degenerate +/// @param tm the triangulated surface mesh upon evaluation +/// +/// \return true if the vertrex is non-manifold +template +bool is_non_manifold_vertex(typename boost::graph_traits::vertex_descriptor v, + const TriangleMesh& tm) +{ + CGAL_assertion(CGAL::is_triangle_mesh(tm)); + + typedef boost::graph_traits GT; + typedef typename GT::halfedge_descriptor halfedge_descriptor; + + boost::unordered_set halfedges_handled; + halfedge_descriptor start = halfedge(v, tm); + halfedge_descriptor h=start; + do{ + halfedges_handled.insert(h); + h=opposite(next(h, tm), tm); + }while(h != start); + + BOOST_FOREACH(halfedge_descriptor h, halfedges_around_target(v, tm)) + { + if(!halfedges_handled.count(h)) + return true; + } + return false; +} + /// \ingroup PMP_repairing_grp /// checks whether an edge is degenerate. /// An edge is considered degenerate if two of its vertices share the same location. @@ -192,7 +226,7 @@ degenerate_faces(const TriangleMesh& tm, OutputIterator out) /// @tparam NamedParameters a sequence of \ref pmp_namedparameters "Named Parameters" /// /// @param e the edge to check whether is degenerate -/// @param pm the triangulated surface mesh to be repaired +/// @param pm the triangulated surface mesh upon evaluation /// @param np optional \ref pmp_namedparameters "Named Parameters" described below /// /// \cgalNamedParamsBegin @@ -210,15 +244,15 @@ degenerate_faces(const TriangleMesh& tm, OutputIterator out) /// \return true if the edge is degenerate template bool is_degenerate_edge(typename boost::graph_traits::edge_descriptor e, - PolygonMesh& pm, + const PolygonMesh& pm, const NamedParameters& np) { using boost::get_param; using boost::choose_param; - typedef typename GetVertexPointMap::type VertexPointMap; + typedef typename GetVertexPointMap::const_type VertexPointMap; VertexPointMap vpmap = choose_param(get_param(np, internal_np::vertex_point), - get_property_map(vertex_point, pm)); + get_const_property_map(vertex_point, pm)); typedef typename GetGeomTraits::type Traits; Traits traits = choose_param(get_param(np, internal_np::geom_traits), Traits()); @@ -228,7 +262,7 @@ bool is_degenerate_edge(typename boost::graph_traits::edge_descript template bool is_degenerate_edge(typename boost::graph_traits::edge_descriptor e, - PolygonMesh& pm) + const PolygonMesh& pm) { return is_degenerate_edge(e, pm, parameters::all_default()); } @@ -241,7 +275,7 @@ bool is_degenerate_edge(typename boost::graph_traits::edge_descript /// @tparam NamedParameters a sequence of \ref pmp_namedparameters "Named Parameters" /// /// @param f the face to check whether is degenerate -/// @param tm the triangulated surface mesh to be repaired +/// @param tm the triangulated surface mesh upon evaluation /// @param np optional \ref pmp_namedparameters "Named Parameters" described below /// /// \cgalNamedParamsBegin @@ -259,7 +293,7 @@ bool is_degenerate_edge(typename boost::graph_traits::edge_descript /// \return true if the triangle face is degenerate template bool is_degenerate_triangle_face(typename boost::graph_traits::face_descriptor f, - TriangleMesh& tm, + const TriangleMesh& tm, const NamedParameters& np) { CGAL_assertion(CGAL::is_triangle_mesh(tm)); @@ -267,9 +301,9 @@ bool is_degenerate_triangle_face(typename boost::graph_traits::fac using boost::get_param; using boost::choose_param; - typedef typename GetVertexPointMap::type VertexPointMap; + typedef typename GetVertexPointMap::const_type VertexPointMap; VertexPointMap vpmap = choose_param(get_param(np, internal_np::vertex_point), - get_property_map(vertex_point, tm)); + get_const_property_map(vertex_point, tm)); typedef typename GetGeomTraits::type Traits; Traits traits = choose_param(get_param(np, internal_np::geom_traits), Traits()); @@ -279,7 +313,7 @@ bool is_degenerate_triangle_face(typename boost::graph_traits::fac template bool is_degenerate_triangle_face(typename boost::graph_traits::face_descriptor f, - TriangleMesh& tm) + const TriangleMesh& tm) { return CGAL::Polygon_mesh_processing::is_degenerate_triangle_face(f, tm, parameters::all_default()); } diff --git a/Polygon_mesh_processing/test/Polygon_mesh_processing/remove_degeneracies_test.cpp b/Polygon_mesh_processing/test/Polygon_mesh_processing/remove_degeneracies_test.cpp index 8f89842f922..6f2c49341d9 100644 --- a/Polygon_mesh_processing/test/Polygon_mesh_processing/remove_degeneracies_test.cpp +++ b/Polygon_mesh_processing/test/Polygon_mesh_processing/remove_degeneracies_test.cpp @@ -71,6 +71,20 @@ void test_vetices_duplication(const char* fname) CGAL::Polygon_mesh_processing::duplicate_non_manifold_vertices(mesh); } +void test_vertex_non_manifoldness(const char* fname) +{ + std::ifstream input(fname); + + Surface_mesh mesh; + if (!input || !(input >> mesh) || mesh.is_empty()) { + std::cerr << fname << " is not a valid off file.\n"; + exit(1); + } + + BOOST_FOREACH(typename boost::graph_traits::vertex_descriptor v, vertices(mesh)) + CGAL::Polygon_mesh_processing::is_non_manifold_vertex(v, mesh); +} + int main() { fix("data_degeneracies/degtri_2dt_1edge_split_twice.off"); @@ -83,6 +97,7 @@ int main() check_edge_degeneracy("data_degeneracies/degtri_edge.off"); check_triangle_face_degeneracy("data_degeneracies/degtri_four.off"); test_vetices_duplication("data_degeneracies/degtri_four.off"); + test_vertex_non_manifoldness("data/non_manifold_vertex.off");; return 0; } From 1f0628fad247d3f7b7c199964b0b4a71f18b5176 Mon Sep 17 00:00:00 2001 From: Konstantinos Katrioplas Date: Thu, 12 Apr 2018 10:24:48 +0200 Subject: [PATCH 008/116] is needle andcap functions --- .../CGAL/Polygon_mesh_processing/repair.h | 149 ++++++++++++++++++ .../remove_degeneracies_test.cpp | 49 +++++- 2 files changed, 196 insertions(+), 2 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/repair.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/repair.h index aef378dee83..d13eff11f9f 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/repair.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/repair.h @@ -318,6 +318,155 @@ bool is_degenerate_triangle_face(typename boost::graph_traits::fac return CGAL::Polygon_mesh_processing::is_degenerate_triangle_face(f, tm, parameters::all_default()); } +/// \ingroup PMP_repairing_grp +/// checks whether a triangle face is needle-like. +/// In a needle-like triangle its longest edge is much longer than the shortest one. +/// +/// @tparam TriangleMesh a model of `FaceListGraph` and `MutableFaceGraph` +/// @tparam NamedParameters a sequence of \ref pmp_namedparameters "Named Parameters" +/// +/// @param f the face to check whether is almost degenerate +/// @param tm the triangulated surface mesh upon evaluation +/// @param threshold a number in the range [0, 1] to indicate the tolerance +/// upon which to characterize the degeneracy. 1 means that needle triangles +/// are those that have a infinitely small edge, while 0 means that needle triangles +/// are those that would have an infinitely long edge +/// @param np optional \ref pmp_namedparameters "Named Parameters" described below +/// +/// \cgalNamedParamsBegin +/// \cgalParamBegin{vertex_point_map} the property map with the points associated to the vertices of `pmesh`. The type of this map is model of `ReadWritePropertyMap`. +/// If this parameter is omitted, an internal property map for +/// `CGAL::vertex_point_t` should be available in `PolygonMesh` +/// \cgalParamEnd +/// \cgalParamBegin{geom_traits} a geometric traits class instance. +/// \cgalParamEnd +/// \cgalNamedParamsEnd +/// +/// \return true if the triangle face is almost degenerate +template +bool is_needle_triangle_face(typename boost::graph_traits::face_descriptor f, + const TriangleMesh& tm, + const double threshold, + const NamedParameters& np) +{ + CGAL_assertion(CGAL::is_triangle_mesh(tm)); + + using boost::get_param; + using boost::choose_param; + + typedef typename GetVertexPointMap::const_type VertexPointMap; + VertexPointMap vpmap = choose_param(get_param(np, internal_np::vertex_point), + get_const_property_map(vertex_point, tm)); + typedef typename GetGeomTraits::type FT; + typedef boost::graph_traits GT; + typedef typename GT::halfedge_descriptor halfedge_descriptor; + typedef typename GT::vertex_descriptor vertex_descriptor; + typedef typename boost::property_traits::value_type Point; + typedef typename Kernel_traits::Kernel::Vector_3 Vector; + + BOOST_FOREACH(halfedge_descriptor h, halfedges_around_face(halfedge(f, tm), tm)) + { + vertex_descriptor v0 = source(h, tm); + vertex_descriptor v1 = target(h, tm); + vertex_descriptor v2 = target(next(h, tm), tm); + + Vector a = get(vpmap, v0) - get(vpmap, v1); + Vector b = get(vpmap, v2) - get(vpmap, v1); + double ab = a*b; + double aa = a.squared_length(); + double bb = b.squared_length(); + double dot_ab = a*b / (CGAL::sqrt(aa) * CGAL::sqrt(bb)); + + // threshold = 1 means no tolerance, totally degenerate + if(dot_ab > threshold) + return true; + } + return false; +} + +template +bool is_needle_triangle_face(typename boost::graph_traits::face_descriptor f, + const TriangleMesh& tm, + const double threshold) +{ + is_needle_triangle_face(f, tm, threshold, parameters::all_default()); +} + +/// \ingroup PMP_repairing_grp +/// checks whether a triangle face is cap-like. +/// A cap-like triangle has an angle very close to 180 degrees. +/// +/// @tparam TriangleMesh a model of `FaceListGraph` and `MutableFaceGraph` +/// @tparam NamedParameters a sequence of \ref pmp_namedparameters "Named Parameters" +/// +/// @param f the face to check whether is almost degenerate +/// @param tm the triangulated surface mesh upon evaluation +/// @param threshold a number in the range [0, 1] to indicate the tolerance +/// upon which to characterize the degeneracy. 1 means that cap triangles +/// are considered those whose vertices for an angle of 180 degrees, while 0 means that +/// all triangles are considered caps. +/// @param np optional \ref pmp_namedparameters "Named Parameters" described below +/// +/// \cgalNamedParamsBegin +/// \cgalParamBegin{vertex_point_map} the property map with the points associated to the vertices of `pmesh`. The type of this map is model of `ReadWritePropertyMap`. +/// If this parameter is omitted, an internal property map for +/// `CGAL::vertex_point_t` should be available in `PolygonMesh` +/// \cgalParamEnd +/// \cgalParamBegin{geom_traits} a geometric traits class instance. +/// \cgalParamEnd +/// \cgalNamedParamsEnd +/// +/// \return true if the triangle face is almost degenerate +template +bool is_cap_triangle_face(typename boost::graph_traits::face_descriptor f, + const TriangleMesh& tm, + const double threshold, + const NamedParameters& np) +{ + CGAL_assertion(CGAL::is_triangle_mesh(tm)); + + using boost::get_param; + using boost::choose_param; + + typedef typename GetVertexPointMap::const_type VertexPointMap; + VertexPointMap vpmap = choose_param(get_param(np, internal_np::vertex_point), + get_const_property_map(vertex_point, tm)); + typedef typename GetGeomTraits::type FT; + typedef boost::graph_traits GT; + typedef typename GT::halfedge_descriptor halfedge_descriptor; + typedef typename GT::vertex_descriptor vertex_descriptor; + typedef typename boost::property_traits::value_type Point; + typedef typename Kernel_traits::Kernel::Vector_3 Vector; + + BOOST_FOREACH(halfedge_descriptor h, halfedges_around_face(halfedge(f, tm), tm)) + { + vertex_descriptor v0 = source(h, tm); + vertex_descriptor v1 = target(h, tm); + vertex_descriptor v2 = target(next(h, tm), tm); + + Vector a = get(vpmap, v0) - get(vpmap, v1); + Vector b = get(vpmap, v2) - get(vpmap, v1); + double ab = a*b; + double aa = a.squared_length(); + double bb = b.squared_length(); + double dot_ab = a*b / (CGAL::sqrt(aa) * CGAL::sqrt(bb)); + + // threshold = 1 means no tolerance, totally degenerate + // take the opposite, because cos it -1 at 180 degrees + if(dot_ab < -threshold) + return true; + } + return false; +} + +template +bool is_cap_triangle_face(typename boost::graph_traits::face_descriptor f, + const TriangleMesh& tm, + const double threshold) +{ + is_cap_triangle_face(f, tm, threshold, parameters::all_default()); +} + // this function remove a border edge even if it does not satisfy the link condition. // The only limitation is that the length connected component of the boundary this edge // is strictly greater than 3 diff --git a/Polygon_mesh_processing/test/Polygon_mesh_processing/remove_degeneracies_test.cpp b/Polygon_mesh_processing/test/Polygon_mesh_processing/remove_degeneracies_test.cpp index 6f2c49341d9..82c275c9702 100644 --- a/Polygon_mesh_processing/test/Polygon_mesh_processing/remove_degeneracies_test.cpp +++ b/Polygon_mesh_processing/test/Polygon_mesh_processing/remove_degeneracies_test.cpp @@ -82,9 +82,52 @@ void test_vertex_non_manifoldness(const char* fname) } BOOST_FOREACH(typename boost::graph_traits::vertex_descriptor v, vertices(mesh)) - CGAL::Polygon_mesh_processing::is_non_manifold_vertex(v, mesh); + { + if(CGAL::Polygon_mesh_processing::is_non_manifold_vertex(v, mesh)) + std::cout << "true\n"; + + } } +void test_needle(const char* fname) +{ + std::ifstream input(fname); + + Surface_mesh mesh; + if (!input || !(input >> mesh) || mesh.is_empty()) { + std::cerr << fname << " is not a valid off file.\n"; + exit(1); + } + + double threshold = 0.8; + + BOOST_FOREACH(typename boost::graph_traits::face_descriptor f, faces(mesh)) + { + if(CGAL::Polygon_mesh_processing::is_needle_triangle_face(f, mesh, threshold)) + std::cout << "needle\n"; + } +} + +void test_cap(const char* fname) +{ + std::ifstream input(fname); + + Surface_mesh mesh; + if (!input || !(input >> mesh) || mesh.is_empty()) { + std::cerr << fname << " is not a valid off file.\n"; + exit(1); + } + + double threshold = 0.8; + + BOOST_FOREACH(typename boost::graph_traits::face_descriptor f, faces(mesh)) + { + if(CGAL::Polygon_mesh_processing::is_cap_triangle_face(f, mesh, threshold)) + std::cout << "cap\n"; + } +} + + int main() { fix("data_degeneracies/degtri_2dt_1edge_split_twice.off"); @@ -97,7 +140,9 @@ int main() check_edge_degeneracy("data_degeneracies/degtri_edge.off"); check_triangle_face_degeneracy("data_degeneracies/degtri_four.off"); test_vetices_duplication("data_degeneracies/degtri_four.off"); - test_vertex_non_manifoldness("data/non_manifold_vertex.off");; + test_vertex_non_manifoldness("data/non_manifold_vertex.off"); + test_needle("data_degeneracies/needle.off"); + test_cap("data_degeneracies/cap.off"); return 0; } From b4da4a21540f38a388f7560e74d00e0c4a263363 Mon Sep 17 00:00:00 2001 From: Konstantinos Katrioplas Date: Thu, 12 Apr 2018 17:27:19 +0200 Subject: [PATCH 009/116] add a couple of tests to cmakelists --- .../test/Polygon_mesh_processing/CMakeLists.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Polygon_mesh_processing/test/Polygon_mesh_processing/CMakeLists.txt b/Polygon_mesh_processing/test/Polygon_mesh_processing/CMakeLists.txt index f7af9794708..5ab90c954a9 100644 --- a/Polygon_mesh_processing/test/Polygon_mesh_processing/CMakeLists.txt +++ b/Polygon_mesh_processing/test/Polygon_mesh_processing/CMakeLists.txt @@ -100,6 +100,8 @@ endif() create_single_source_cgal_program("surface_intersection_sm_poly.cpp" ) create_single_source_cgal_program("test_orient_cc.cpp") create_single_source_cgal_program("test_pmp_transform.cpp") + create_single_source_cgal_program("remove_degeneracies_test.cpp") + create_single_source_cgal_program("remove_identical_test.cpp") if( TBB_FOUND ) CGAL_target_use_TBB(test_pmp_distance) From c79add2c6a65b1ba5b54312ad3359653d383fc1a Mon Sep 17 00:00:00 2001 From: Konstantinos Katrioplas Date: Thu, 12 Apr 2018 17:31:42 +0200 Subject: [PATCH 010/116] merge vertices, tests & data --- .../Polygon_mesh_processing/stitch_holes.h | 104 ++++++++++++++++++ .../data/merge_points.off | 10 ++ .../data_degeneracies/degtri_edge.off | 6 + .../remove_identical_test.cpp | 75 +++++++++++++ 4 files changed, 195 insertions(+) create mode 100644 Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/stitch_holes.h create mode 100644 Polygon_mesh_processing/test/Polygon_mesh_processing/data/merge_points.off create mode 100644 Polygon_mesh_processing/test/Polygon_mesh_processing/data_degeneracies/degtri_edge.off create mode 100644 Polygon_mesh_processing/test/Polygon_mesh_processing/remove_identical_test.cpp diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/stitch_holes.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/stitch_holes.h new file mode 100644 index 00000000000..af71d0bf408 --- /dev/null +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/stitch_holes.h @@ -0,0 +1,104 @@ +#ifndef CGAL_STITCH_HOLES_H +#define CGAL_STITCH_HOLES _H + + +#include +#include +#include +#include + + +namespace CGAL{ + +namespace Polygon_mesh_processing{ + + +template +void extract_connected_components(PolygonMesh& mesh, + OutputIterator out) +{ + typedef typename boost::graph_traits::halfedge_descriptor halfedge_descriptor; + + std::set border_halfedges; + + BOOST_FOREACH(halfedge_descriptor h, halfedges(mesh)) + { + if(is_border(h, mesh)) + border_halfedges.insert(h); + } + + std::set connected_component; + BOOST_FOREACH(halfedge_descriptor h, border_halfedges) + { + if(connected_component.insert(h).second) + { + halfedge_descriptor start = h; + do{ + h = next(h, mesh); + connected_component.insert(h); + } while(h != start); + + *out++=connected_component; + } + } +} + + +template +std::size_t count_identical_points(PolygonMesh& mesh, + std::vector cc_list) +{ + // cc is a std::vector > + + typedef typename boost::graph_traits::halfedge_descriptor halfedge_descriptor; + typedef typename boost::graph_traits::vertex_descriptor vertex_descriptor; + + typedef typename boost::property_map::const_type Vpm; + Vpm vpm = get(boost::vertex_point, mesh); + + for(std::set i_cc : cc_list) + { + // for each cc + BOOST_FOREACH(halfedge_descriptor h, i_cc) + { + vertex_descriptor vs = source(h, mesh); + vertex_descriptor vt = target(h, mesh); + + // find identicals + } + } + return 0; // how many found +} + +/// \ingroup PMP_repairing_grp +/// merges two vertices into one +/// +/// @tparam TriangleMesh a model of `FaceListGraph` +/// +/// @param mesh the input triangle mesh +/// @param v_keep the vertex to be kept +/// @param v_rm the vertex to be removed +template +void merge_identical_points(PolygonMesh& mesh, + typename boost::graph_traits::vertex_descriptor v_keep, + typename boost::graph_traits::vertex_descriptor v_rm) +{ + typedef typename boost::graph_traits::halfedge_descriptor halfedge_descriptor; + halfedge_descriptor h = halfedge(v_rm, mesh); + halfedge_descriptor start = h; + + do{ + set_target(h, v_keep, mesh); + h = opposite(next(h, mesh), mesh); + } while( h != start ); + + remove_vertex(v_rm, mesh); +} + + + + +} +} + +#endif //CGAL_STITCH_HOLES_H diff --git a/Polygon_mesh_processing/test/Polygon_mesh_processing/data/merge_points.off b/Polygon_mesh_processing/test/Polygon_mesh_processing/data/merge_points.off new file mode 100644 index 00000000000..d520c25cdf2 --- /dev/null +++ b/Polygon_mesh_processing/test/Polygon_mesh_processing/data/merge_points.off @@ -0,0 +1,10 @@ +OFF +6 2 0 +0 0 0 +1 0 0 +1 0 0 +2 0 0 +0.5 1 0 +1.5 1 0 +3 0 1 4 +3 2 3 5 diff --git a/Polygon_mesh_processing/test/Polygon_mesh_processing/data_degeneracies/degtri_edge.off b/Polygon_mesh_processing/test/Polygon_mesh_processing/data_degeneracies/degtri_edge.off new file mode 100644 index 00000000000..afd48de232f --- /dev/null +++ b/Polygon_mesh_processing/test/Polygon_mesh_processing/data_degeneracies/degtri_edge.off @@ -0,0 +1,6 @@ +OFF +3 1 0 +0 0 0 +1 0 0 +0 0 0 +3 0 1 2 diff --git a/Polygon_mesh_processing/test/Polygon_mesh_processing/remove_identical_test.cpp b/Polygon_mesh_processing/test/Polygon_mesh_processing/remove_identical_test.cpp new file mode 100644 index 00000000000..ae31ad7f8c8 --- /dev/null +++ b/Polygon_mesh_processing/test/Polygon_mesh_processing/remove_identical_test.cpp @@ -0,0 +1,75 @@ +#include +#include + +#include +#include + +#include + + +typedef CGAL::Exact_predicates_inexact_constructions_kernel K; +typedef CGAL::Surface_mesh Surface_mesh; + + +void test_connected_components(const char* fname) +{ + std::ifstream input(fname); + + Surface_mesh mesh; + if (!input || !(input >> mesh) || mesh.is_empty()) { + std::cerr << fname << " is not a valid off file.\n"; + exit(1); + } + + typedef typename boost::graph_traits::halfedge_descriptor halfedge_descriptor; + + + std::vector > connected_components; + + CGAL::Polygon_mesh_processing::extract_connected_components(mesh, + std::back_inserter(connected_components)); + + std::cout << "# cc = " << connected_components.size(); + + for(auto set : connected_components) + { + std::cout << "of size= " << set.size() << std::endl; + } +} + + +void test_merge_points(const char* fname) +{ + std::ifstream input(fname); + + Surface_mesh mesh; + if (!input || !(input >> mesh) || mesh.is_empty()) { + std::cerr << fname << " is not a valid off file.\n"; + exit(1); + } + + typedef typename boost::graph_traits::vertex_descriptor vertex_descriptor; + std::vector verts(vertices(mesh).begin(), vertices(mesh).end()); + + vertex_descriptor v_rm = verts[1]; + vertex_descriptor v_keep = verts[2]; + + CGAL::Polygon_mesh_processing::merge_identical_points(mesh, v_keep, v_rm); + + std::ofstream out("/tmp/result.off"); + out << mesh; + out.close(); +} + + +int main() +{ + + + test_connected_components("data/small_ex.off"); + test_merge_points("data/merge_points.off"); + + + + return 0; +} From af6576047505933b2f8d9dc6e5c5c702e4a5814e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Loriot?= Date: Fri, 13 Apr 2018 13:44:53 +0200 Subject: [PATCH 011/116] rewrite boundary cycle merging --- .../Polygon_mesh_processing/stitch_holes.h | 203 ++++++++++++------ .../data/merge_points.off | 103 ++++++++- .../remove_identical_test.cpp | 60 ++---- 3 files changed, 243 insertions(+), 123 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/stitch_holes.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/stitch_holes.h index af71d0bf408..eb2010ed104 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/stitch_holes.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/stitch_holes.h @@ -1,104 +1,171 @@ -#ifndef CGAL_STITCH_HOLES_H -#define CGAL_STITCH_HOLES _H +// Copyright (c) 2018 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_STITCH_HOLES_H +#define CGAL_STITCH_HOLES_H #include #include #include #include +#include +#include +#include namespace CGAL{ namespace Polygon_mesh_processing{ - +/// \todo document me +/// It should probably go into BGL package template -void extract_connected_components(PolygonMesh& mesh, - OutputIterator out) +OutputIterator +extract_boundary_cycles(PolygonMesh& pm, + OutputIterator out) { typedef typename boost::graph_traits::halfedge_descriptor halfedge_descriptor; - std::set border_halfedges; - - BOOST_FOREACH(halfedge_descriptor h, halfedges(mesh)) + boost::unordered_set hedge_handled; + BOOST_FOREACH(halfedge_descriptor h, halfedges(pm)) { - if(is_border(h, mesh)) - border_halfedges.insert(h); - } - - std::set connected_component; - BOOST_FOREACH(halfedge_descriptor h, border_halfedges) - { - if(connected_component.insert(h).second) + if(is_border(h, pm) && hedge_handled.insert(h).second) { - halfedge_descriptor start = h; - do{ - h = next(h, mesh); - connected_component.insert(h); - } while(h != start); - - *out++=connected_component; + *out++=h; + BOOST_FOREACH(halfedge_descriptor h2, halfedges_around_face(h, pm)) + hedge_handled.insert(h2); } } -} - - -template -std::size_t count_identical_points(PolygonMesh& mesh, - std::vector cc_list) -{ - // cc is a std::vector > - - typedef typename boost::graph_traits::halfedge_descriptor halfedge_descriptor; - typedef typename boost::graph_traits::vertex_descriptor vertex_descriptor; - - typedef typename boost::property_map::const_type Vpm; - Vpm vpm = get(boost::vertex_point, mesh); - - for(std::set i_cc : cc_list) - { - // for each cc - BOOST_FOREACH(halfedge_descriptor h, i_cc) - { - vertex_descriptor vs = source(h, mesh); - vertex_descriptor vt = target(h, mesh); - - // find identicals - } - } - return 0; // how many found + return out; } /// \ingroup PMP_repairing_grp -/// merges two vertices into one -/// -/// @tparam TriangleMesh a model of `FaceListGraph` -/// -/// @param mesh the input triangle mesh -/// @param v_keep the vertex to be kept -/// @param v_rm the vertex to be removed -template -void merge_identical_points(PolygonMesh& mesh, - typename boost::graph_traits::vertex_descriptor v_keep, - typename boost::graph_traits::vertex_descriptor v_rm) +/// \todo document me +template +void merge_vertices(const VertexRange& vertices, + PolygonMesh& pm) { typedef typename boost::graph_traits::halfedge_descriptor halfedge_descriptor; - halfedge_descriptor h = halfedge(v_rm, mesh); - halfedge_descriptor start = h; + typedef typename boost::graph_traits::vertex_descriptor vertex_descriptor; + vertex_descriptor v_kept=*boost::begin(vertices); + std::vector vertices_to_rm; + + BOOST_FOREACH(vertex_descriptor vd, vertices) + { + if (vd==v_kept) continue; // skip identical vertices + if (edge(vd, v_kept, pm).second) continue; // skip null edges + bool shall_continue=false; + BOOST_FOREACH(halfedge_descriptor hd, halfedges_around_target(v_kept, pm)) + { + if (edge(vd, source(hd, pm), pm).second) + { + shall_continue=true; + break; + } + } + if (shall_continue) continue; // skip vertices already incident to the same vertex + + internal::update_target_vertex(halfedge(vd, pm), v_kept, pm); + vertices_to_rm.push_back(vd); + } + + BOOST_FOREACH(vertex_descriptor vd, vertices_to_rm) + remove_vertex(vd, pm); +} + +/// \ingroup PMP_repairing_grp +/// \todo document me +template +void merge_duplicated_vertices_in_boundary_cycle(typename boost::graph_traits::halfedge_descriptor h, + PolygonMesh& pm, + const NamedParameter& np) +{ + typedef typename boost::graph_traits::halfedge_descriptor halfedge_descriptor; + typedef typename boost::graph_traits::vertex_descriptor vertex_descriptor; + typedef typename GetVertexPointMap::const_type Vpm; + typedef typename boost::property_traits::value_type Point_3; + Vpm vpm = choose_param(get_param(np, internal_np::vertex_point), + get_const_property_map(vertex_point, pm)); + + // collect all the vertices of the cycle + std::vector vertices; + halfedge_descriptor start=h; do{ - set_target(h, v_keep, mesh); - h = opposite(next(h, mesh), mesh); - } while( h != start ); + vertices.push_back(target(h,pm)); + h=next(h, pm); + }while(start!=h); - remove_vertex(v_rm, mesh); + // sort vertices using their point to ease the detection + // of vertices with identical points + CGAL::Property_map_to_unary_function Get_point(vpm); + std::sort( vertices.begin(), vertices.end(), + boost::bind(std::less(), boost::bind(Get_point,_1), + boost::bind(Get_point, _2)) ); + std::size_t nbv=vertices.size(); + std::size_t i=1; + + std::vector< std::vector > identical_vertices; + while(i!=nbv) + { + if (get(vpm, vertices[i]) == get(vpm, vertices[i-1])) + { + identical_vertices.push_back( std::vector() ); + identical_vertices.back().push_back(vertices[i-1]); + identical_vertices.back().push_back(vertices[i]); + while(++i!=nbv) + { + if (get(vpm, vertices[i]) == get(vpm, vertices[i-1])) + identical_vertices.back().push_back(vertices[i]); + else + break; + } + } + ++i; + } + BOOST_FOREACH(const std::vector& vrtcs, identical_vertices) + merge_vertices(vrtcs, pm); } +/// \ingroup PMP_repairing_grp +/// \todo document me +template +void merge_duplicated_vertices_in_boundary_cycles( PolygonMesh& pm, + const NamedParameter& np) +{ + typedef typename boost::graph_traits::halfedge_descriptor halfedge_descriptor; + std::vector cycles; + extract_boundary_cycles(pm, std::back_inserter(cycles)); - + BOOST_FOREACH(halfedge_descriptor h, cycles) + merge_duplicated_vertices_in_boundary_cycle(h, pm, np); } + +template +void merge_duplicated_vertices_in_boundary_cycles(PolygonMesh& pm) +{ + merge_duplicated_vertices_in_boundary_cycles(pm, parameters::all_default()); } +} } // end of CGAL::Polygon_mesh_processing + #endif //CGAL_STITCH_HOLES_H diff --git a/Polygon_mesh_processing/test/Polygon_mesh_processing/data/merge_points.off b/Polygon_mesh_processing/test/Polygon_mesh_processing/data/merge_points.off index d520c25cdf2..7a83712189f 100644 --- a/Polygon_mesh_processing/test/Polygon_mesh_processing/data/merge_points.off +++ b/Polygon_mesh_processing/test/Polygon_mesh_processing/data/merge_points.off @@ -1,10 +1,95 @@ OFF -6 2 0 -0 0 0 -1 0 0 -1 0 0 -2 0 0 -0.5 1 0 -1.5 1 0 -3 0 1 4 -3 2 3 5 +47 45 0 + +-0.026804100000000001 -0.30492799999999998 0.142542 +-0.018234400000000001 -0.28125499999999998 0.15192700000000001 +-0.074329900000000004 -0.27701599999999998 0.17852999999999999 +-0.075232300000000002 -0.238787 0.19677600000000001 +-0.092496499999999995 -0.26196000000000003 0.18681900000000001 +-0.107975 -0.220055 0.202264 +-0.098042799999999999 -0.24015500000000001 0.197738 +-0.076830499999999996 -0.20389099999999999 0.20217599999999999 +-0.10664999999999999 -0.18507100000000001 0.204627 +-0.048507700000000001 -0.30671799999999999 0.14971999999999999 +-0.10985300000000001 -0.27012399999999998 0.16877500000000001 +0.020159400000000001 -0.26358599999999999 0.11985899999999999 +-0.0820192 -0.303394 0.145977 +-0.11151800000000001 -0.28692499999999999 0.148566 +0.033791700000000001 -0.209923 0.115845 +0.019949499999999998 -0.19298699999999999 0.125832 +0.00080011299999999997 -0.19967099999999999 0.13874900000000001 +0.0102764 -0.16375000000000001 0.13344700000000001 +0.015325999999999999 -0.12934699999999999 0.14213100000000001 +0.043303899999999999 -0.239647 0.099889199999999997 +-0.010328800000000001 -0.10643 0.14183100000000001 +-0.032521000000000001 -0.10838299999999999 0.13683899999999999 +-0.085985000000000006 -0.101282 0.15809300000000001 +-0.108849 -0.11043600000000001 0.16941700000000001 +-0.052558500000000001 -0.097836599999999996 0.13975499999999999 +-0.10316599999999999 -0.15746599999999999 0.19631499999999999 +-0.112763 -0.133107 0.183753 +-0.063495300000000005 -0.15489 0.17952499999999999 +-0.064211199999999996 -0.12604799999999999 0.16286 +-0.086169300000000004 -0.13996600000000001 0.183699 +-0.063495300000000005 -0.15489 0.17952499999999999 +-0.023309900000000001 -0.17152600000000001 0.15354100000000001 +-0.0254547 -0.133829 0.14063300000000001 +-0.079254099999999994 -0.17391000000000001 0.197354 +-0.0458796 -0.210094 0.18626999999999999 +-0.063495300000000005 -0.15489 0.17952499999999999 +-0.0097084900000000002 -0.22793099999999999 0.15407299999999999 +-0.0458796 -0.210094 0.18626999999999999 +-0.0458796 -0.210094 0.18626999999999999 +-0.075232300000000002 -0.238787 0.19677600000000001 +-0.049308200000000003 -0.25528899999999999 0.18343899999999999 +-0.0218198 -0.25775500000000001 0.16445000000000001 +-0.049308200000000003 -0.25528899999999999 0.18343899999999999 +-0.041782300000000001 -0.28198800000000002 0.16825000000000001 +0.00026201499999999999 -0.25824399999999997 0.14286599999999999 +0.015174200000000001 -0.229959 0.128466 +-0.0097084900000000002 -0.22793099999999999 0.15407299999999999 +3 43 0 1 +3 2 3 4 +3 5 6 3 +3 7 5 3 +3 5 7 8 +3 0 43 9 +3 10 2 4 +3 1 44 41 +3 11 45 44 +3 12 9 2 +3 13 12 2 +3 14 15 45 +3 16 45 15 +3 43 1 41 +3 17 16 15 +3 16 31 36 +3 31 16 17 +3 17 18 32 +3 11 19 45 +3 43 2 9 +3 18 20 32 +3 20 21 32 +3 13 2 10 +3 29 28 22 +3 45 19 14 +3 23 29 22 +3 24 22 28 +3 21 24 28 +3 32 21 28 +3 33 29 25 +3 8 33 25 +3 8 7 33 +3 26 29 23 +3 34 33 7 +3 25 29 26 +3 4 3 6 +3 31 17 32 +3 29 27 28 +3 32 30 31 +3 34 35 33 +3 31 37 36 +3 40 38 39 +3 41 42 43 +3 45 46 44 +3 44 46 41 diff --git a/Polygon_mesh_processing/test/Polygon_mesh_processing/remove_identical_test.cpp b/Polygon_mesh_processing/test/Polygon_mesh_processing/remove_identical_test.cpp index ae31ad7f8c8..5e65212e9b4 100644 --- a/Polygon_mesh_processing/test/Polygon_mesh_processing/remove_identical_test.cpp +++ b/Polygon_mesh_processing/test/Polygon_mesh_processing/remove_identical_test.cpp @@ -11,7 +11,8 @@ typedef CGAL::Exact_predicates_inexact_constructions_kernel K; typedef CGAL::Surface_mesh Surface_mesh; -void test_connected_components(const char* fname) +void test_merge_duplicated_vertices_in_boundary_cycles(const char* fname, + std::size_t expected_nb_vertices) { std::ifstream input(fname); @@ -21,55 +22,22 @@ void test_connected_components(const char* fname) exit(1); } - typedef typename boost::graph_traits::halfedge_descriptor halfedge_descriptor; + std::cout << "Testing " << fname << "\n"; + std::cout << " input mesh has " << vertices(mesh).size() << " vertices.\n"; + CGAL::Polygon_mesh_processing::merge_duplicated_vertices_in_boundary_cycles(mesh); + std::cout << " output mesh has " << vertices(mesh).size() << " vertices.\n"; - - std::vector > connected_components; - - CGAL::Polygon_mesh_processing::extract_connected_components(mesh, - std::back_inserter(connected_components)); - - std::cout << "# cc = " << connected_components.size(); - - for(auto set : connected_components) - { - std::cout << "of size= " << set.size() << std::endl; - } + assert(expected_nb_vertices==0 || + expected_nb_vertices == vertices(mesh).size()); } -void test_merge_points(const char* fname) +int main(int argc, char** argv) { - std::ifstream input(fname); - - Surface_mesh mesh; - if (!input || !(input >> mesh) || mesh.is_empty()) { - std::cerr << fname << " is not a valid off file.\n"; - exit(1); - } - - typedef typename boost::graph_traits::vertex_descriptor vertex_descriptor; - std::vector verts(vertices(mesh).begin(), vertices(mesh).end()); - - vertex_descriptor v_rm = verts[1]; - vertex_descriptor v_keep = verts[2]; - - CGAL::Polygon_mesh_processing::merge_identical_points(mesh, v_keep, v_rm); - - std::ofstream out("/tmp/result.off"); - out << mesh; - out.close(); -} - - -int main() -{ - - - test_connected_components("data/small_ex.off"); - test_merge_points("data/merge_points.off"); - - - + if (argc==1) + test_merge_duplicated_vertices_in_boundary_cycles("data/merge_points.off", 43); + else + for (int i=1; i< argc; ++i) + test_merge_duplicated_vertices_in_boundary_cycles(argv[i], 0); return 0; } From e1f0740b533159e4510aaa1e8a8c76bd1e4d0dd5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Loriot?= Date: Fri, 13 Apr 2018 14:00:55 +0200 Subject: [PATCH 012/116] rename header and test file --- .../{stitch_holes.h => merge_border_vertices.h} | 6 +++--- .../test/Polygon_mesh_processing/CMakeLists.txt | 2 +- ..._identical_test.cpp => test_merging_border_vertices.cpp} | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) rename Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/{stitch_holes.h => merge_border_vertices.h} (96%) rename Polygon_mesh_processing/test/Polygon_mesh_processing/{remove_identical_test.cpp => test_merging_border_vertices.cpp} (95%) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/stitch_holes.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/merge_border_vertices.h similarity index 96% rename from Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/stitch_holes.h rename to Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/merge_border_vertices.h index eb2010ed104..7ff0c1ce6e1 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/stitch_holes.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/merge_border_vertices.h @@ -19,8 +19,8 @@ // // Author(s) : Sebastien Loriot -#ifndef CGAL_STITCH_HOLES_H -#define CGAL_STITCH_HOLES_H +#ifndef CGAL_POLYGON_MESH_PROCESSING_MERGE_BORDER_VERTICES_H +#define CGAL_POLYGON_MESH_PROCESSING_MERGE_BORDER_VERTICES_H #include #include @@ -168,4 +168,4 @@ void merge_duplicated_vertices_in_boundary_cycles(PolygonMesh& pm) } } // end of CGAL::Polygon_mesh_processing -#endif //CGAL_STITCH_HOLES_H +#endif //CGAL_POLYGON_MESH_PROCESSING_MERGE_BORDER_VERTICES_H diff --git a/Polygon_mesh_processing/test/Polygon_mesh_processing/CMakeLists.txt b/Polygon_mesh_processing/test/Polygon_mesh_processing/CMakeLists.txt index 5ab90c954a9..ee3d82c4fe8 100644 --- a/Polygon_mesh_processing/test/Polygon_mesh_processing/CMakeLists.txt +++ b/Polygon_mesh_processing/test/Polygon_mesh_processing/CMakeLists.txt @@ -101,7 +101,7 @@ endif() create_single_source_cgal_program("test_orient_cc.cpp") create_single_source_cgal_program("test_pmp_transform.cpp") create_single_source_cgal_program("remove_degeneracies_test.cpp") - create_single_source_cgal_program("remove_identical_test.cpp") + create_single_source_cgal_program("test_merging_border_vertices.cpp") if( TBB_FOUND ) CGAL_target_use_TBB(test_pmp_distance) diff --git a/Polygon_mesh_processing/test/Polygon_mesh_processing/remove_identical_test.cpp b/Polygon_mesh_processing/test/Polygon_mesh_processing/test_merging_border_vertices.cpp similarity index 95% rename from Polygon_mesh_processing/test/Polygon_mesh_processing/remove_identical_test.cpp rename to Polygon_mesh_processing/test/Polygon_mesh_processing/test_merging_border_vertices.cpp index 5e65212e9b4..fc749d88373 100644 --- a/Polygon_mesh_processing/test/Polygon_mesh_processing/remove_identical_test.cpp +++ b/Polygon_mesh_processing/test/Polygon_mesh_processing/test_merging_border_vertices.cpp @@ -1,7 +1,7 @@ #include #include -#include +#include #include #include From 0830c7a112f2af0a622014aa7073ebfdfd42e1f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Loriot?= Date: Fri, 13 Apr 2018 14:09:50 +0200 Subject: [PATCH 013/116] add missing overload --- .../CGAL/Polygon_mesh_processing/merge_border_vertices.h | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/merge_border_vertices.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/merge_border_vertices.h index 7ff0c1ce6e1..8b78ff256e5 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/merge_border_vertices.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/merge_border_vertices.h @@ -166,6 +166,14 @@ void merge_duplicated_vertices_in_boundary_cycles(PolygonMesh& pm) merge_duplicated_vertices_in_boundary_cycles(pm, parameters::all_default()); } +template +void merge_duplicated_vertices_in_boundary_cycle( + typename boost::graph_traits::halfedge_descriptor h, + PolygonMesh& pm) +{ + merge_duplicated_vertices_in_boundary_cycles(h, pm, parameters::all_default()); +} + } } // end of CGAL::Polygon_mesh_processing #endif //CGAL_POLYGON_MESH_PROCESSING_MERGE_BORDER_VERTICES_H From fe407a701fa6d149abaf90b8c9b16a26f9abca4c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Loriot?= Date: Fri, 13 Apr 2018 14:54:29 +0200 Subject: [PATCH 014/116] add a function to merge vertices globally --- .../merge_border_vertices.h | 113 +++++++++++++----- .../test_merging_border_vertices.cpp | 44 ++++++- 2 files changed, 126 insertions(+), 31 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/merge_border_vertices.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/merge_border_vertices.h index 8b78ff256e5..76a0d132cca 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/merge_border_vertices.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/merge_border_vertices.h @@ -35,6 +35,46 @@ namespace CGAL{ namespace Polygon_mesh_processing{ +namespace internal { + +// warning: vertices will be altered (sorted) +template +void detect_identical_vertices(std::vector& vertices, + std::vector< std::vector >& identical_vertices, + Vpm vpm) +{ + typedef typename boost::property_traits::value_type Point_3; + + // sort vertices using their point to ease the detection + // of vertices with identical points + CGAL::Property_map_to_unary_function Get_point(vpm); + std::sort( vertices.begin(), vertices.end(), + boost::bind(std::less(), boost::bind(Get_point,_1), + boost::bind(Get_point, _2)) ); + std::size_t nbv=vertices.size(); + std::size_t i=1; + + while(i!=nbv) + { + if (get(vpm, vertices[i]) == get(vpm, vertices[i-1])) + { + identical_vertices.push_back( std::vector() ); + identical_vertices.back().push_back(vertices[i-1]); + identical_vertices.back().push_back(vertices[i]); + while(++i!=nbv) + { + if (get(vpm, vertices[i]) == get(vpm, vertices[i-1])) + identical_vertices.back().push_back(vertices[i]); + else + break; + } + } + ++i; + } +} + +} // end of internal + /// \todo document me /// It should probably go into BGL package template @@ -60,8 +100,8 @@ extract_boundary_cycles(PolygonMesh& pm, /// \ingroup PMP_repairing_grp /// \todo document me template -void merge_vertices(const VertexRange& vertices, - PolygonMesh& pm) +void merge_boundary_vertices(const VertexRange& vertices, + PolygonMesh& pm) { typedef typename boost::graph_traits::halfedge_descriptor halfedge_descriptor; typedef typename boost::graph_traits::vertex_descriptor vertex_descriptor; @@ -102,7 +142,7 @@ void merge_duplicated_vertices_in_boundary_cycle(typename boost::graph_traits::halfedge_descriptor halfedge_descriptor; typedef typename boost::graph_traits::vertex_descriptor vertex_descriptor; typedef typename GetVertexPointMap::const_type Vpm; - typedef typename boost::property_traits::value_type Point_3; + Vpm vpm = choose_param(get_param(np, internal_np::vertex_point), get_const_property_map(vertex_point, pm)); @@ -114,35 +154,11 @@ void merge_duplicated_vertices_in_boundary_cycle(typename boost::graph_traits Get_point(vpm); - std::sort( vertices.begin(), vertices.end(), - boost::bind(std::less(), boost::bind(Get_point,_1), - boost::bind(Get_point, _2)) ); - std::size_t nbv=vertices.size(); - std::size_t i=1; - std::vector< std::vector > identical_vertices; - while(i!=nbv) - { - if (get(vpm, vertices[i]) == get(vpm, vertices[i-1])) - { - identical_vertices.push_back( std::vector() ); - identical_vertices.back().push_back(vertices[i-1]); - identical_vertices.back().push_back(vertices[i]); - while(++i!=nbv) - { - if (get(vpm, vertices[i]) == get(vpm, vertices[i-1])) - identical_vertices.back().push_back(vertices[i]); - else - break; - } - } - ++i; - } + internal::detect_identical_vertices(vertices, identical_vertices, vpm); + BOOST_FOREACH(const std::vector& vrtcs, identical_vertices) - merge_vertices(vrtcs, pm); + merge_boundary_vertices(vrtcs, pm); } /// \ingroup PMP_repairing_grp @@ -160,6 +176,36 @@ void merge_duplicated_vertices_in_boundary_cycles( PolygonMesh& pm, merge_duplicated_vertices_in_boundary_cycle(h, pm, np); } + +/// \ingroup PMP_repairing_grp +/// \todo document me +template +void merge_duplicated_boundary_vertices( PolygonMesh& pm, + const NamedParameter& np) +{ + typedef typename boost::graph_traits::halfedge_descriptor halfedge_descriptor; + typedef typename boost::graph_traits::vertex_descriptor vertex_descriptor; + typedef typename GetVertexPointMap::const_type Vpm; + + Vpm vpm = choose_param(get_param(np, internal_np::vertex_point), + get_const_property_map(vertex_point, pm)); + + std::vector border_vertices; + BOOST_FOREACH(halfedge_descriptor h, halfedges(pm)) + { + if(is_border(h, pm)) + border_vertices.push_back(target(h, pm)); + } + + std::vector< std::vector > identical_vertices; + internal::detect_identical_vertices(border_vertices, identical_vertices, vpm); + + BOOST_FOREACH(const std::vector& vrtcs, identical_vertices) + merge_boundary_vertices(vrtcs, pm); +} + + + template void merge_duplicated_vertices_in_boundary_cycles(PolygonMesh& pm) { @@ -174,6 +220,13 @@ void merge_duplicated_vertices_in_boundary_cycle( merge_duplicated_vertices_in_boundary_cycles(h, pm, parameters::all_default()); } +template +void merge_duplicated_boundary_vertices(PolygonMesh& pm) +{ + merge_duplicated_boundary_vertices(pm, parameters::all_default()); +} + + } } // end of CGAL::Polygon_mesh_processing #endif //CGAL_POLYGON_MESH_PROCESSING_MERGE_BORDER_VERTICES_H diff --git a/Polygon_mesh_processing/test/Polygon_mesh_processing/test_merging_border_vertices.cpp b/Polygon_mesh_processing/test/Polygon_mesh_processing/test_merging_border_vertices.cpp index fc749d88373..1abc453207a 100644 --- a/Polygon_mesh_processing/test/Polygon_mesh_processing/test_merging_border_vertices.cpp +++ b/Polygon_mesh_processing/test/Polygon_mesh_processing/test_merging_border_vertices.cpp @@ -22,22 +22,64 @@ void test_merge_duplicated_vertices_in_boundary_cycles(const char* fname, exit(1); } - std::cout << "Testing " << fname << "\n"; + std::cout << "Testing merging in cycles " << fname << "\n"; std::cout << " input mesh has " << vertices(mesh).size() << " vertices.\n"; CGAL::Polygon_mesh_processing::merge_duplicated_vertices_in_boundary_cycles(mesh); std::cout << " output mesh has " << vertices(mesh).size() << " vertices.\n"; assert(expected_nb_vertices==0 || expected_nb_vertices == vertices(mesh).size()); + if (expected_nb_vertices==0) + { + std::cout << "writting output to out1.off\n"; + std::ofstream output("out1.off"); + output << std::setprecision(17); + output << mesh; + } +} + +void test_merge_duplicated_boundary_vertices(const char* fname, + std::size_t expected_nb_vertices) +{ + std::ifstream input(fname); + + Surface_mesh mesh; + if (!input || !(input >> mesh) || mesh.is_empty()) { + std::cerr << fname << " is not a valid off file.\n"; + exit(1); + } + + std::cout << "Testing merging globally " << fname << "\n"; + std::cout << " input mesh has " << vertices(mesh).size() << " vertices.\n"; + CGAL::Polygon_mesh_processing::merge_duplicated_boundary_vertices(mesh); + std::cout << " output mesh has " << vertices(mesh).size() << " vertices.\n"; + + assert(expected_nb_vertices == 0 || + expected_nb_vertices == vertices(mesh).size()); + if (expected_nb_vertices==0) + { + std::cout << "writting output to out2.off\n"; + std::ofstream output("out2.off"); + output << std::setprecision(17); + output << mesh; + } } int main(int argc, char** argv) { if (argc==1) + { test_merge_duplicated_vertices_in_boundary_cycles("data/merge_points.off", 43); + test_merge_duplicated_boundary_vertices("data/merge_points.off", 40); + } else + { for (int i=1; i< argc; ++i) + { test_merge_duplicated_vertices_in_boundary_cycles(argv[i], 0); + test_merge_duplicated_boundary_vertices(argv[i], 0); + } + } return 0; } From e6ffc5f505ea2d58e5eef4a972e2523b6c85c519 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Loriot?= Date: Fri, 13 Apr 2018 16:25:09 +0200 Subject: [PATCH 015/116] remove incorrect optimisation --- .../CGAL/Polygon_mesh_processing/merge_border_vertices.h | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/merge_border_vertices.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/merge_border_vertices.h index 76a0d132cca..3435e5fd7bd 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/merge_border_vertices.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/merge_border_vertices.h @@ -66,10 +66,14 @@ void detect_identical_vertices(std::vector& vertices, if (get(vpm, vertices[i]) == get(vpm, vertices[i-1])) identical_vertices.back().push_back(vertices[i]); else + { + ++i; break; + } } } - ++i; + else + ++i; } } From 903df8106a9f7e3f3bbf24be8d371366fed43223 Mon Sep 17 00:00:00 2001 From: Konstantinos Katrioplas Date: Mon, 16 Apr 2018 12:05:03 +0200 Subject: [PATCH 016/116] corrections after the review --- BGL/include/CGAL/boost/graph/helpers.h | 27 --- .../CGAL/Polygon_mesh_processing/repair.h | 195 +++++++++++------- .../data/non_manifold_vertex_duplicated.off | 20 ++ .../remove_degeneracies_test.cpp | 69 ++++--- 4 files changed, 190 insertions(+), 121 deletions(-) create mode 100644 Polygon_mesh_processing/test/Polygon_mesh_processing/data/non_manifold_vertex_duplicated.off diff --git a/BGL/include/CGAL/boost/graph/helpers.h b/BGL/include/CGAL/boost/graph/helpers.h index f34e24b7687..d84e1b5c6c3 100644 --- a/BGL/include/CGAL/boost/graph/helpers.h +++ b/BGL/include/CGAL/boost/graph/helpers.h @@ -973,33 +973,6 @@ make_tetrahedron(const P& p0, const P& p1, const P& p2, const P& p3, Graph& g) return opposite(h2,g); } -/// \cond SKIP_IN_DOC -template -bool is_degenerate_triangle_face( - typename boost::graph_traits::halfedge_descriptor hd, - TriangleMesh& tmesh, - const VertexPointMap& vpmap, - const Traits& traits) -{ - CGAL_assertion(!is_border(hd, tmesh)); - - const typename Traits::Point_3& p1 = get(vpmap, target( hd, tmesh) ); - const typename Traits::Point_3& p2 = get(vpmap, target(next(hd, tmesh), tmesh) ); - const typename Traits::Point_3& p3 = get(vpmap, source( hd, tmesh) ); - return traits.collinear_3_object()(p1, p2, p3); -} - -template -bool is_degenerate_triangle_face( - typename boost::graph_traits::face_descriptor fd, - TriangleMesh& tmesh, - const VertexPointMap& vpmap, - const Traits& traits) -{ - return is_degenerate_triangle_face(halfedge(fd,tmesh), tmesh, vpmap, traits); -} -/// \endcond - /** * \ingroup PkgBGLHelperFct * \brief Creates a triangulated regular prism, outward oriented, diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/repair.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/repair.h index d13eff11f9f..27ea6f5d191 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/repair.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/repair.h @@ -187,46 +187,46 @@ degenerate_faces(const TriangleMesh& tm, OutputIterator out) /// \ingroup PMP_repairing_grp /// checks whether a vertex is non-manifold. /// -/// @tparam TriangleMesh a model of `FaceListGraph` and `MutableFaceGraph` +/// @tparam PolygonMesh a model of `FaceListGraph` and `MutableFaceGraph` /// /// @param v the vertex to check whether is degenerate -/// @param tm the triangulated surface mesh upon evaluation +/// @param tm triangle mesh containing v /// /// \return true if the vertrex is non-manifold -template -bool is_non_manifold_vertex(typename boost::graph_traits::vertex_descriptor v, - const TriangleMesh& tm) +template +bool is_non_manifold_vertex(typename boost::graph_traits::vertex_descriptor v, + const PolygonMesh& tm) { CGAL_assertion(CGAL::is_triangle_mesh(tm)); - typedef boost::graph_traits GT; + typedef boost::graph_traits GT; typedef typename GT::halfedge_descriptor halfedge_descriptor; boost::unordered_set halfedges_handled; - halfedge_descriptor start = halfedge(v, tm); - halfedge_descriptor h=start; - do{ - halfedges_handled.insert(h); - h=opposite(next(h, tm), tm); - }while(h != start); BOOST_FOREACH(halfedge_descriptor h, halfedges_around_target(v, tm)) + halfedges_handled.insert(h); + + BOOST_FOREACH(halfedge_descriptor h, halfedges(tm)) { - if(!halfedges_handled.count(h)) - return true; + if(v == target(h, tm)) + { + if(halfedges_handled.count(h) == 0) + return true; + } } return false; } /// \ingroup PMP_repairing_grp /// checks whether an edge is degenerate. -/// An edge is considered degenerate if two of its vertices share the same location. +/// An edge is considered degenerate if the points of its vertices are identical. /// -/// @tparam PolygonMesh a model of `FaceListGraph` and `MutableFaceGraph` +/// @tparam PolygonMesh a model of `HalfedgeGraph` /// @tparam NamedParameters a sequence of \ref pmp_namedparameters "Named Parameters" /// /// @param e the edge to check whether is degenerate -/// @param pm the triangulated surface mesh upon evaluation +/// @param pm polygon mesh containing e /// @param np optional \ref pmp_namedparameters "Named Parameters" described below /// /// \cgalNamedParamsBegin @@ -258,6 +258,7 @@ bool is_degenerate_edge(typename boost::graph_traits::edge_descript if ( traits.equal_3_object()(get(vpmap, target(e, pm)), get(vpmap, source(e, pm))) ) return true; + return false; } template @@ -267,21 +268,48 @@ bool is_degenerate_edge(typename boost::graph_traits::edge_descript return is_degenerate_edge(e, pm, parameters::all_default()); } +/// \cond SKIP_IN_DOC +template +bool is_degenerate_triangle_face( + typename boost::graph_traits::halfedge_descriptor hd, + TriangleMesh& tmesh, + const VertexPointMap& vpmap, + const Traits& traits) +{ + CGAL_assertion(!is_border(hd, tmesh)); + + const typename Traits::Point_3& p1 = get(vpmap, target( hd, tmesh) ); + const typename Traits::Point_3& p2 = get(vpmap, target(next(hd, tmesh), tmesh) ); + const typename Traits::Point_3& p3 = get(vpmap, source( hd, tmesh) ); + return traits.collinear_3_object()(p1, p2, p3); +} + +template +bool is_degenerate_triangle_face( + typename boost::graph_traits::face_descriptor fd, + TriangleMesh& tmesh, + const VertexPointMap& vpmap, + const Traits& traits) +{ + return is_degenerate_triangle_face(halfedge(fd,tmesh), tmesh, vpmap, traits); +} +/// \endcond + /// \ingroup PMP_repairing_grp /// checks whether a triangle face is degenerate. -/// A triangle face is considered degenerate if all three points of the face are collinear. +/// A triangle face is degenerate if its points are collinear. /// -/// @tparam TriangleMesh a model of `FaceListGraph` and `MutableFaceGraph` +/// @tparam TriangleMesh a model of `FaceGraph` /// @tparam NamedParameters a sequence of \ref pmp_namedparameters "Named Parameters" /// /// @param f the face to check whether is degenerate -/// @param tm the triangulated surface mesh upon evaluation +/// @param tm triangle mesh containing f /// @param np optional \ref pmp_namedparameters "Named Parameters" described below /// /// \cgalNamedParamsBegin /// \cgalParamBegin{vertex_point_map} the property map with the points associated to the vertices of `pmesh`. The type of this map is model of `ReadWritePropertyMap`. /// If this parameter is omitted, an internal property map for -/// `CGAL::vertex_point_t` should be available in `PolygonMesh` +/// `CGAL::vertex_point_t` should be available in `TriangleMesh` /// \cgalParamEnd /// \cgalParamBegin{geom_traits} a geometric traits class instance. /// The traits class must provide the nested type `Point_3`, @@ -319,26 +347,27 @@ bool is_degenerate_triangle_face(typename boost::graph_traits::fac } /// \ingroup PMP_repairing_grp -/// checks whether a triangle face is needle-like. -/// In a needle-like triangle its longest edge is much longer than the shortest one. +/// checks whether a triangle face is needle. +/// A triangle is needle if its longest edge is much longer than the shortest one. /// -/// @tparam TriangleMesh a model of `FaceListGraph` and `MutableFaceGraph` +/// @tparam TriangleMesh a model of `FaceGraph` /// @tparam NamedParameters a sequence of \ref pmp_namedparameters "Named Parameters" /// -/// @param f the face to check whether is almost degenerate -/// @param tm the triangulated surface mesh upon evaluation +/// @param f a face to check whether is almost degenerate +/// @param tm triangle mesh containing f /// @param threshold a number in the range [0, 1] to indicate the tolerance /// upon which to characterize the degeneracy. 1 means that needle triangles -/// are those that have a infinitely small edge, while 0 means that needle triangles -/// are those that would have an infinitely long edge +/// are those that have a infinitely small edge, while 0 means that all +/// triangles are considered needles. /// @param np optional \ref pmp_namedparameters "Named Parameters" described below /// /// \cgalNamedParamsBegin /// \cgalParamBegin{vertex_point_map} the property map with the points associated to the vertices of `pmesh`. The type of this map is model of `ReadWritePropertyMap`. /// If this parameter is omitted, an internal property map for -/// `CGAL::vertex_point_t` should be available in `PolygonMesh` +/// `CGAL::vertex_point_t` should be available in `TriangleMesh` /// \cgalParamEnd /// \cgalParamBegin{geom_traits} a geometric traits class instance. +/// The traits class must provide the nested type `Point_3`. /// \cgalParamEnd /// \cgalNamedParamsEnd /// @@ -357,30 +386,58 @@ bool is_needle_triangle_face(typename boost::graph_traits::face_de typedef typename GetVertexPointMap::const_type VertexPointMap; VertexPointMap vpmap = choose_param(get_param(np, internal_np::vertex_point), get_const_property_map(vertex_point, tm)); - typedef typename GetGeomTraits::type FT; + typedef typename GetGeomTraits::type Traits; + typedef typename Traits::FT FT; typedef boost::graph_traits GT; - typedef typename GT::halfedge_descriptor halfedge_descriptor; typedef typename GT::vertex_descriptor vertex_descriptor; - typedef typename boost::property_traits::value_type Point; - typedef typename Kernel_traits::Kernel::Vector_3 Vector; + typedef typename boost::property_traits::reference Point_ref; - BOOST_FOREACH(halfedge_descriptor h, halfedges_around_face(halfedge(f, tm), tm)) + vertex_descriptor v0 = target(halfedge(f, tm), tm); + vertex_descriptor v1 = target(next(halfedge(f, tm), tm), tm); + vertex_descriptor v2 = target(next(next(halfedge(f, tm), tm), tm), tm); + Point_ref p0 = get(vpmap, v0); + Point_ref p1 = get(vpmap, v1); + Point_ref p2 = get(vpmap, v2); + + // e1 = p0p1 e2 = p1p2 e3 = p2p3 + FT e1 = CGAL::squared_distance(p0,p1); + FT e2 = CGAL::squared_distance(p1,p2); + FT e3 = CGAL::squared_distance(p2,p0); + + FT smallest, largest; + if(e1 < e2) { - vertex_descriptor v0 = source(h, tm); - vertex_descriptor v1 = target(h, tm); - vertex_descriptor v2 = target(next(h, tm), tm); - - Vector a = get(vpmap, v0) - get(vpmap, v1); - Vector b = get(vpmap, v2) - get(vpmap, v1); - double ab = a*b; - double aa = a.squared_length(); - double bb = b.squared_length(); - double dot_ab = a*b / (CGAL::sqrt(aa) * CGAL::sqrt(bb)); - - // threshold = 1 means no tolerance, totally degenerate - if(dot_ab > threshold) - return true; + if(e1 < e3) + smallest = e1; + else + smallest = e3; } + else + { + if(e2 < e3) + smallest = e2; + else + smallest = e3; + } + if(e1 > e2) + { + if(e1 > e3) + largest = e1; + else + largest = e3; + } + else + { + if(e2 > e3) + largest = e2; + else + largest = e3; + } + + const double ratio = smallest / largest; + // threshold is opposite + if(ratio < (1 - threshold)) + return true; return false; } @@ -389,30 +446,31 @@ bool is_needle_triangle_face(typename boost::graph_traits::face_de const TriangleMesh& tm, const double threshold) { - is_needle_triangle_face(f, tm, threshold, parameters::all_default()); + return is_needle_triangle_face(f, tm, threshold, parameters::all_default()); } /// \ingroup PMP_repairing_grp -/// checks whether a triangle face is cap-like. -/// A cap-like triangle has an angle very close to 180 degrees. +/// checks whether a triangle face is a cap. +/// A triangle is a cap if it has an angle very close to 180 degrees. /// -/// @tparam TriangleMesh a model of `FaceListGraph` and `MutableFaceGraph` +/// @tparam TriangleMesh a model of `FaceGraph` /// @tparam NamedParameters a sequence of \ref pmp_namedparameters "Named Parameters" /// /// @param f the face to check whether is almost degenerate -/// @param tm the triangulated surface mesh upon evaluation +/// @param tm triangle mesh containing f /// @param threshold a number in the range [0, 1] to indicate the tolerance /// upon which to characterize the degeneracy. 1 means that cap triangles -/// are considered those whose vertices for an angle of 180 degrees, while 0 means that +/// are considered those whose vertices form an angle of 180 degrees, while 0 means that /// all triangles are considered caps. /// @param np optional \ref pmp_namedparameters "Named Parameters" described below /// /// \cgalNamedParamsBegin /// \cgalParamBegin{vertex_point_map} the property map with the points associated to the vertices of `pmesh`. The type of this map is model of `ReadWritePropertyMap`. /// If this parameter is omitted, an internal property map for -/// `CGAL::vertex_point_t` should be available in `PolygonMesh` +/// `CGAL::vertex_point_t` should be available in `TriangleMesh` /// \cgalParamEnd /// \cgalParamBegin{geom_traits} a geometric traits class instance. +/// The traits class must provide the nested type `Point_3` /// \cgalParamEnd /// \cgalNamedParamsEnd /// @@ -431,28 +489,27 @@ bool is_cap_triangle_face(typename boost::graph_traits::face_descr typedef typename GetVertexPointMap::const_type VertexPointMap; VertexPointMap vpmap = choose_param(get_param(np, internal_np::vertex_point), get_const_property_map(vertex_point, tm)); - typedef typename GetGeomTraits::type FT; + typedef typename GetGeomTraits::type Traits; + typedef typename Traits::FT FT; typedef boost::graph_traits GT; typedef typename GT::halfedge_descriptor halfedge_descriptor; typedef typename GT::vertex_descriptor vertex_descriptor; - typedef typename boost::property_traits::value_type Point; - typedef typename Kernel_traits::Kernel::Vector_3 Vector; + typedef typename boost::property_traits::value_type Point_type; + typedef typename Kernel_traits::Kernel::Vector_3 Vector; BOOST_FOREACH(halfedge_descriptor h, halfedges_around_face(halfedge(f, tm), tm)) { vertex_descriptor v0 = source(h, tm); vertex_descriptor v1 = target(h, tm); vertex_descriptor v2 = target(next(h, tm), tm); - - Vector a = get(vpmap, v0) - get(vpmap, v1); + Vector a = get(vpmap, v0) - get (vpmap, v1); Vector b = get(vpmap, v2) - get(vpmap, v1); - double ab = a*b; - double aa = a.squared_length(); - double bb = b.squared_length(); - double dot_ab = a*b / (CGAL::sqrt(aa) * CGAL::sqrt(bb)); + FT aa = a.squared_length(); + FT bb = b.squared_length(); + FT dot_ab = (a*b) / (aa * bb); // threshold = 1 means no tolerance, totally degenerate - // take the opposite, because cos it -1 at 180 degrees + // take the opposite, because cos is -1 at 180 degrees if(dot_ab < -threshold) return true; } @@ -464,7 +521,7 @@ bool is_cap_triangle_face(typename boost::graph_traits::face_descr const TriangleMesh& tm, const double threshold) { - is_cap_triangle_face(f, tm, threshold, parameters::all_default()); + return is_cap_triangle_face(f, tm, threshold, parameters::all_default()); } // this function remove a border edge even if it does not satisfy the link condition. @@ -1013,7 +1070,7 @@ std::size_t remove_degenerate_faces(TriangleMesh& tmesh, // Then, remove triangles made of 3 collinear points std::set degenerate_face_set; BOOST_FOREACH(face_descriptor fd, faces(tmesh)) - if ( is_degenerate_triangle_face(fd, tmesh) ) + if ( is_degenerate_triangle_face(fd, tmesh, np)) degenerate_face_set.insert(fd); nb_deg_faces+=degenerate_face_set.size(); @@ -1044,7 +1101,7 @@ std::size_t remove_degenerate_faces(TriangleMesh& tmesh, degenerate_face_set.erase( face(hd2, tmesh) ); // remove the central vertex and check if the new face is degenerated hd=CGAL::Euler::remove_center_vertex(hd, tmesh); - if (is_degenerate_triangle_face(face(hd, tmesh), tmesh)) + if (is_degenerate_triangle_face(face(hd, tmesh), tmesh, np)) { degenerate_face_set.insert( face(hd, tmesh) ); } @@ -1572,7 +1629,7 @@ std::size_t remove_degenerate_faces(TriangleMesh& tmesh) /// \ingroup PMP_repairing_grp /// duplicates all non-manifold vertices of the input mesh. /// -/// @tparam TriangleMesh a model of `FaceListGraph` and `MutableFaceGraph` +/// @tparam TriangleMesh a model of `HalfedgeListGraph` and `MutableHalfedgeGraph` /// @tparam NamedParameters a sequence of \ref pmp_namedparameters "Named Parameters" /// /// @param tm the triangulated surface mesh to be repaired diff --git a/Polygon_mesh_processing/test/Polygon_mesh_processing/data/non_manifold_vertex_duplicated.off b/Polygon_mesh_processing/test/Polygon_mesh_processing/data/non_manifold_vertex_duplicated.off new file mode 100644 index 00000000000..5733b99f480 --- /dev/null +++ b/Polygon_mesh_processing/test/Polygon_mesh_processing/data/non_manifold_vertex_duplicated.off @@ -0,0 +1,20 @@ +OFF +8 8 0 +0 1 0 +1 0 0 +0 0 0 +0 0 1 +2 1 0 +2 0 0 +2 0 -1 +1 0 0 +3 0 1 2 +3 2 3 0 +3 1 3 2 +3 0 3 1 +3 7 5 4 +3 7 6 5 +3 4 6 7 +3 5 6 4 + + diff --git a/Polygon_mesh_processing/test/Polygon_mesh_processing/remove_degeneracies_test.cpp b/Polygon_mesh_processing/test/Polygon_mesh_processing/remove_degeneracies_test.cpp index 82c275c9702..e3e737e1a63 100644 --- a/Polygon_mesh_processing/test/Polygon_mesh_processing/remove_degeneracies_test.cpp +++ b/Polygon_mesh_processing/test/Polygon_mesh_processing/remove_degeneracies_test.cpp @@ -2,7 +2,7 @@ #include #include -#include +#include #include #include @@ -39,9 +39,12 @@ void check_edge_degeneracy(const char* fname) std::cerr << fname << " is not a valid off file.\n"; exit(1); } + typedef typename boost::graph_traits::edge_descriptor edge_descriptor; + std::vector all_edges(edges(mesh).begin(), edges(mesh).end()); - BOOST_FOREACH(typename boost::graph_traits::edge_descriptor e, edges(mesh)) - CGAL::Polygon_mesh_processing::is_degenerate_edge(e, mesh); + CGAL_assertion(!CGAL::Polygon_mesh_processing::is_degenerate_edge(all_edges[0], mesh)); + CGAL_assertion(!CGAL::Polygon_mesh_processing::is_degenerate_edge(all_edges[1], mesh)); + CGAL_assertion(CGAL::Polygon_mesh_processing::is_degenerate_edge(all_edges[2], mesh)); } void check_triangle_face_degeneracy(const char* fname) @@ -54,80 +57,96 @@ void check_triangle_face_degeneracy(const char* fname) exit(1); } - BOOST_FOREACH(typename boost::graph_traits::face_descriptor f, faces(mesh)) - CGAL::Polygon_mesh_processing::is_degenerate_triangle_face(f, mesh); + typedef typename boost::graph_traits::face_descriptor face_descriptor; + std::vector all_faces(faces(mesh).begin(), faces(mesh).end()); + CGAL_assertion(CGAL::Polygon_mesh_processing::is_degenerate_triangle_face(all_faces[0], mesh)); + CGAL_assertion(!CGAL::Polygon_mesh_processing::is_degenerate_triangle_face(all_faces[1], mesh)); + CGAL_assertion(!CGAL::Polygon_mesh_processing::is_degenerate_triangle_face(all_faces[2], mesh)); + CGAL_assertion(!CGAL::Polygon_mesh_processing::is_degenerate_triangle_face(all_faces[3], mesh)); } -void test_vetices_duplication(const char* fname) +void test_vertices_merge_and_duplication(const char* fname) { std::ifstream input(fname); - Surface_mesh mesh; if (!input || !(input >> mesh) || mesh.is_empty()) { std::cerr << fname << " is not a valid off file.\n"; exit(1); } + const std::size_t initial_vertices = vertices(mesh).size(); + + // create non-manifold vertex + typedef typename boost::graph_traits::vertex_descriptor vertex_descriptor; + std::vector all_vertices(vertices(mesh).begin(), vertices(mesh).end()); + CGAL::Polygon_mesh_processing::merge_identical_points(mesh, all_vertices[1], all_vertices[7]); + + const std::size_t vertices_after_merge = vertices(mesh).size(); + CGAL_assertion(vertices_after_merge == initial_vertices - 1); CGAL::Polygon_mesh_processing::duplicate_non_manifold_vertices(mesh); + const std::size_t final_vertices = vertices(mesh).size(); + CGAL_assertion(final_vertices == vertices_after_merge + 1); + CGAL_assertion(final_vertices == initial_vertices); } void test_vertex_non_manifoldness(const char* fname) { std::ifstream input(fname); - Surface_mesh mesh; if (!input || !(input >> mesh) || mesh.is_empty()) { std::cerr << fname << " is not a valid off file.\n"; exit(1); } - BOOST_FOREACH(typename boost::graph_traits::vertex_descriptor v, vertices(mesh)) - { - if(CGAL::Polygon_mesh_processing::is_non_manifold_vertex(v, mesh)) - std::cout << "true\n"; + // create non-manifold vertex + typedef typename boost::graph_traits::vertex_descriptor vertex_descriptor; + std::vector all_vertices(vertices(mesh).begin(), vertices(mesh).end()); + CGAL::Polygon_mesh_processing::merge_identical_points(mesh, all_vertices[1], all_vertices[7]); + std::vector vertices_with_non_manifold(vertices(mesh).begin(), vertices(mesh).end()); + CGAL_assertion(vertices_with_non_manifold.size() == all_vertices.size() - 1); + BOOST_FOREACH(std::size_t iv, vertices(mesh)) + { + vertex_descriptor v = vertices_with_non_manifold[iv]; + if(iv == 1) + CGAL_assertion(CGAL::Polygon_mesh_processing::is_non_manifold_vertex(v, mesh)); + else + CGAL_assertion(!CGAL::Polygon_mesh_processing::is_non_manifold_vertex(v, mesh)); } } void test_needle(const char* fname) { std::ifstream input(fname); - Surface_mesh mesh; if (!input || !(input >> mesh) || mesh.is_empty()) { std::cerr << fname << " is not a valid off file.\n"; exit(1); } - double threshold = 0.8; - + const double threshold = 0.8; BOOST_FOREACH(typename boost::graph_traits::face_descriptor f, faces(mesh)) { - if(CGAL::Polygon_mesh_processing::is_needle_triangle_face(f, mesh, threshold)) - std::cout << "needle\n"; + CGAL_assertion(CGAL::Polygon_mesh_processing::is_needle_triangle_face(f, mesh, threshold)); } } void test_cap(const char* fname) { std::ifstream input(fname); - Surface_mesh mesh; if (!input || !(input >> mesh) || mesh.is_empty()) { std::cerr << fname << " is not a valid off file.\n"; exit(1); } - double threshold = 0.8; - + const double threshold = 0.8; BOOST_FOREACH(typename boost::graph_traits::face_descriptor f, faces(mesh)) { - if(CGAL::Polygon_mesh_processing::is_cap_triangle_face(f, mesh, threshold)) - std::cout << "cap\n"; + CGAL_assertion(CGAL::Polygon_mesh_processing::is_cap_triangle_face(f, mesh, threshold)); } } - int main() { fix("data_degeneracies/degtri_2dt_1edge_split_twice.off"); @@ -139,8 +158,8 @@ int main() fix("data_degeneracies/trihole.off"); check_edge_degeneracy("data_degeneracies/degtri_edge.off"); check_triangle_face_degeneracy("data_degeneracies/degtri_four.off"); - test_vetices_duplication("data_degeneracies/degtri_four.off"); - test_vertex_non_manifoldness("data/non_manifold_vertex.off"); + test_vertices_merge_and_duplication("data_degeneracies/non_manifold_vertex_duplicated.off"); + test_vertex_non_manifoldness("data_degeneracies/non_manifold_vertex_duplicated.off"); test_needle("data_degeneracies/needle.off"); test_cap("data_degeneracies/cap.off"); From 63f49b7fcc45456c222e0ba6944b265dd2e0228c Mon Sep 17 00:00:00 2001 From: Konstantinos Katrioplas Date: Tue, 17 Apr 2018 11:14:41 +0200 Subject: [PATCH 017/116] move predicates to helper.h and seperate test file --- .../CGAL/Polygon_mesh_processing/helpers.h | 407 ++++++++++++++++++ .../CGAL/Polygon_mesh_processing/repair.h | 344 +-------------- .../Polygon_mesh_processing/CMakeLists.txt | 1 + .../remove_degeneracies_test.cpp | 123 ------ .../test_predicates.cpp | 139 ++++++ 5 files changed, 550 insertions(+), 464 deletions(-) create mode 100644 Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/helpers.h create mode 100644 Polygon_mesh_processing/test/Polygon_mesh_processing/test_predicates.cpp diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/helpers.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/helpers.h new file mode 100644 index 00000000000..20c35e54d13 --- /dev/null +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/helpers.h @@ -0,0 +1,407 @@ +// Copyright (c) 2015 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) : Konstantinos Katrioplas + +#ifndef CGAL_POLYGON_MESH_PROCESSING_HELPERS_H +#define CGAL_POLYGON_MESH_PROCESSING_HELPERS_H + +#include +#include + + +namespace CGAL { + +namespace Polygon_mesh_processing { + +namespace internal { + +template +void merge_identical_points(PolygonMesh& mesh, + typename boost::graph_traits::vertex_descriptor v_keep, + typename boost::graph_traits::vertex_descriptor v_rm) +{ + typedef typename boost::graph_traits::halfedge_descriptor halfedge_descriptor; + halfedge_descriptor h = halfedge(v_rm, mesh); + halfedge_descriptor start = h; + + do{ + set_target(h, v_keep, mesh); + h = opposite(next(h, mesh), mesh); + } while( h != start ); + + remove_vertex(v_rm, mesh); +} +} // end internal + + + +/// \ingroup PMP_repairing_grp +/// checks whether a vertex is non-manifold. +/// +/// @tparam PolygonMesh a model of `FaceListGraph` and `MutableFaceGraph` +/// +/// @param v the vertex to check whether is degenerate +/// @param tm triangle mesh containing v +/// +/// \return true if the vertrex is non-manifold +template +bool is_non_manifold_vertex(typename boost::graph_traits::vertex_descriptor v, + const PolygonMesh& tm) +{ + CGAL_assertion(CGAL::is_triangle_mesh(tm)); + + typedef boost::graph_traits GT; + typedef typename GT::halfedge_descriptor halfedge_descriptor; + + boost::unordered_set halfedges_handled; + + BOOST_FOREACH(halfedge_descriptor h, halfedges_around_target(v, tm)) + halfedges_handled.insert(h); + + BOOST_FOREACH(halfedge_descriptor h, halfedges(tm)) + { + if(v == target(h, tm)) + { + if(halfedges_handled.count(h) == 0) + return true; + } + } + return false; +} + +/// \ingroup PMP_repairing_grp +/// checks whether an edge is degenerate. +/// An edge is considered degenerate if the points of its vertices are identical. +/// +/// @tparam PolygonMesh a model of `HalfedgeGraph` +/// @tparam NamedParameters a sequence of \ref pmp_namedparameters "Named Parameters" +/// +/// @param e the edge to check whether is degenerate +/// @param pm polygon mesh containing e +/// @param np optional \ref pmp_namedparameters "Named Parameters" described below +/// +/// \cgalNamedParamsBegin +/// \cgalParamBegin{vertex_point_map} the property map with the points associated to the vertices of `pmesh`. The type of this map is model of `ReadWritePropertyMap`. +/// If this parameter is omitted, an internal property map for +/// `CGAL::vertex_point_t` should be available in `PolygonMesh` +/// \cgalParamEnd +/// \cgalParamBegin{geom_traits} a geometric traits class instance. +/// The traits class must provide the nested type `Point_3`, +/// and the nested functor : +/// - `Equal_3` to check whether 2 points are identical +/// \cgalParamEnd +/// \cgalNamedParamsEnd +/// +/// \return true if the edge is degenerate +template +bool is_degenerate_edge(typename boost::graph_traits::edge_descriptor e, + const PolygonMesh& pm, + const NamedParameters& np) +{ + using boost::get_param; + using boost::choose_param; + + typedef typename GetVertexPointMap::const_type VertexPointMap; + VertexPointMap vpmap = choose_param(get_param(np, internal_np::vertex_point), + get_const_property_map(vertex_point, pm)); + typedef typename GetGeomTraits::type Traits; + Traits traits = choose_param(get_param(np, internal_np::geom_traits), Traits()); + + if ( traits.equal_3_object()(get(vpmap, target(e, pm)), get(vpmap, source(e, pm))) ) + return true; + return false; +} + +template +bool is_degenerate_edge(typename boost::graph_traits::edge_descriptor e, + const PolygonMesh& pm) +{ + return is_degenerate_edge(e, pm, parameters::all_default()); +} + +/// \cond SKIP_IN_DOC +template +bool is_degenerate_triangle_face( + typename boost::graph_traits::halfedge_descriptor hd, + TriangleMesh& tmesh, + const VertexPointMap& vpmap, + const Traits& traits) +{ + CGAL_assertion(!is_border(hd, tmesh)); + + const typename Traits::Point_3& p1 = get(vpmap, target( hd, tmesh) ); + const typename Traits::Point_3& p2 = get(vpmap, target(next(hd, tmesh), tmesh) ); + const typename Traits::Point_3& p3 = get(vpmap, source( hd, tmesh) ); + return traits.collinear_3_object()(p1, p2, p3); +} + +template +bool is_degenerate_triangle_face( + typename boost::graph_traits::face_descriptor fd, + TriangleMesh& tmesh, + const VertexPointMap& vpmap, + const Traits& traits) +{ + return is_degenerate_triangle_face(halfedge(fd,tmesh), tmesh, vpmap, traits); +} +/// \endcond + +/// \ingroup PMP_repairing_grp +/// checks whether a triangle face is degenerate. +/// A triangle face is degenerate if its points are collinear. +/// +/// @tparam TriangleMesh a model of `FaceGraph` +/// @tparam NamedParameters a sequence of \ref pmp_namedparameters "Named Parameters" +/// +/// @param f the face to check whether is degenerate +/// @param tm triangle mesh containing f +/// @param np optional \ref pmp_namedparameters "Named Parameters" described below +/// +/// \cgalNamedParamsBegin +/// \cgalParamBegin{vertex_point_map} the property map with the points associated to the vertices of `pmesh`. The type of this map is model of `ReadWritePropertyMap`. +/// If this parameter is omitted, an internal property map for +/// `CGAL::vertex_point_t` should be available in `TriangleMesh` +/// \cgalParamEnd +/// \cgalParamBegin{geom_traits} a geometric traits class instance. +/// The traits class must provide the nested type `Point_3`, +/// and the nested functor : +/// - `Collinear_3` to check whether 3 points are collinear +/// \cgalParamEnd +/// \cgalNamedParamsEnd +/// +/// \return true if the triangle face is degenerate +template +bool is_degenerate_triangle_face(typename boost::graph_traits::face_descriptor f, + const TriangleMesh& tm, + const NamedParameters& np) +{ + CGAL_assertion(CGAL::is_triangle_mesh(tm)); + + using boost::get_param; + using boost::choose_param; + + typedef typename GetVertexPointMap::const_type VertexPointMap; + VertexPointMap vpmap = choose_param(get_param(np, internal_np::vertex_point), + get_const_property_map(vertex_point, tm)); + typedef typename GetGeomTraits::type Traits; + Traits traits = choose_param(get_param(np, internal_np::geom_traits), Traits()); + + typename boost::graph_traits::halfedge_descriptor hd = halfedge(f,tm); + const typename Traits::Point_3& p1 = get(vpmap, target( hd, tm) ); + const typename Traits::Point_3& p2 = get(vpmap, target(next(hd, tm), tm) ); + const typename Traits::Point_3& p3 = get(vpmap, source( hd, tm) ); + return traits.collinear_3_object()(p1, p2, p3); + +} + +template +bool is_degenerate_triangle_face(typename boost::graph_traits::face_descriptor f, + const TriangleMesh& tm) +{ + return CGAL::Polygon_mesh_processing::is_degenerate_triangle_face(f, tm, parameters::all_default()); +} + +/// \ingroup PMP_repairing_grp +/// checks whether a triangle face is needle. +/// A triangle is needle if its longest edge is much longer than the shortest one. +/// +/// @tparam TriangleMesh a model of `FaceGraph` +/// @tparam NamedParameters a sequence of \ref pmp_namedparameters "Named Parameters" +/// +/// @param f a face to check whether is almost degenerate +/// @param tm triangle mesh containing f +/// @param threshold a number in the range [0, 1] to indicate the tolerance +/// upon which to characterize the degeneracy. 1 means that needle triangles +/// are those that have a infinitely small edge, while 0 means that all +/// triangles are considered needles. +/// @param np optional \ref pmp_namedparameters "Named Parameters" described below +/// +/// \cgalNamedParamsBegin +/// \cgalParamBegin{vertex_point_map} the property map with the points associated to the vertices of `pmesh`. The type of this map is model of `ReadWritePropertyMap`. +/// If this parameter is omitted, an internal property map for +/// `CGAL::vertex_point_t` should be available in `TriangleMesh` +/// \cgalParamEnd +/// \cgalParamBegin{geom_traits} a geometric traits class instance. +/// The traits class must provide the nested type `Point_3`. +/// \cgalParamEnd +/// \cgalNamedParamsEnd +/// +/// \return true if the triangle face is almost degenerate +template +bool is_needle_triangle_face(typename boost::graph_traits::face_descriptor f, + const TriangleMesh& tm, + const double threshold, + const NamedParameters& np) +{ + CGAL_assertion(CGAL::is_triangle_mesh(tm)); + + using boost::get_param; + using boost::choose_param; + + typedef typename GetVertexPointMap::const_type VertexPointMap; + VertexPointMap vpmap = choose_param(get_param(np, internal_np::vertex_point), + get_const_property_map(vertex_point, tm)); + typedef typename GetGeomTraits::type Traits; + typedef typename Traits::FT FT; + typedef boost::graph_traits GT; + typedef typename GT::vertex_descriptor vertex_descriptor; + typedef typename boost::property_traits::reference Point_ref; + + vertex_descriptor v0 = target(halfedge(f, tm), tm); + vertex_descriptor v1 = target(next(halfedge(f, tm), tm), tm); + vertex_descriptor v2 = target(next(next(halfedge(f, tm), tm), tm), tm); + Point_ref p0 = get(vpmap, v0); + Point_ref p1 = get(vpmap, v1); + Point_ref p2 = get(vpmap, v2); + + // e1 = p0p1 e2 = p1p2 e3 = p2p3 + FT e1 = CGAL::squared_distance(p0,p1); + FT e2 = CGAL::squared_distance(p1,p2); + FT e3 = CGAL::squared_distance(p2,p0); + + FT smallest, largest; + if(e1 < e2) + { + if(e1 < e3) + smallest = e1; + else + smallest = e3; + } + else + { + if(e2 < e3) + smallest = e2; + else + smallest = e3; + } + if(e1 > e2) + { + if(e1 > e3) + largest = e1; + else + largest = e3; + } + else + { + if(e2 > e3) + largest = e2; + else + largest = e3; + } + + const double ratio = smallest / largest; + // threshold is opposite + if(ratio < (1 - threshold)) + return true; + return false; +} + +template +bool is_needle_triangle_face(typename boost::graph_traits::face_descriptor f, + const TriangleMesh& tm, + const double threshold) +{ + return is_needle_triangle_face(f, tm, threshold, parameters::all_default()); +} + +/// \ingroup PMP_repairing_grp +/// checks whether a triangle face is a cap. +/// A triangle is a cap if it has an angle very close to 180 degrees. +/// +/// @tparam TriangleMesh a model of `FaceGraph` +/// @tparam NamedParameters a sequence of \ref pmp_namedparameters "Named Parameters" +/// +/// @param f the face to check whether is almost degenerate +/// @param tm triangle mesh containing f +/// @param threshold a number in the range [0, 1] to indicate the tolerance +/// upon which to characterize the degeneracy. 1 means that cap triangles +/// are considered those whose vertices form an angle of 180 degrees, while 0 means that +/// all triangles are considered caps. +/// @param np optional \ref pmp_namedparameters "Named Parameters" described below +/// +/// \cgalNamedParamsBegin +/// \cgalParamBegin{vertex_point_map} the property map with the points associated to the vertices of `pmesh`. The type of this map is model of `ReadWritePropertyMap`. +/// If this parameter is omitted, an internal property map for +/// `CGAL::vertex_point_t` should be available in `TriangleMesh` +/// \cgalParamEnd +/// \cgalParamBegin{geom_traits} a geometric traits class instance. +/// The traits class must provide the nested type `Point_3` +/// \cgalParamEnd +/// \cgalNamedParamsEnd +/// +/// \return true if the triangle face is almost degenerate +template +bool is_cap_triangle_face(typename boost::graph_traits::face_descriptor f, + const TriangleMesh& tm, + const double threshold, + const NamedParameters& np) +{ + CGAL_assertion(CGAL::is_triangle_mesh(tm)); + + using boost::get_param; + using boost::choose_param; + + typedef typename GetVertexPointMap::const_type VertexPointMap; + VertexPointMap vpmap = choose_param(get_param(np, internal_np::vertex_point), + get_const_property_map(vertex_point, tm)); + typedef typename GetGeomTraits::type Traits; + typedef typename Traits::FT FT; + typedef boost::graph_traits GT; + typedef typename GT::halfedge_descriptor halfedge_descriptor; + typedef typename GT::vertex_descriptor vertex_descriptor; + typedef typename boost::property_traits::value_type Point_type; + typedef typename Kernel_traits::Kernel::Vector_3 Vector; + + BOOST_FOREACH(halfedge_descriptor h, halfedges_around_face(halfedge(f, tm), tm)) + { + vertex_descriptor v0 = source(h, tm); + vertex_descriptor v1 = target(h, tm); + vertex_descriptor v2 = target(next(h, tm), tm); + Vector a = get(vpmap, v0) - get (vpmap, v1); + Vector b = get(vpmap, v2) - get(vpmap, v1); + FT aa = a.squared_length(); + FT bb = b.squared_length(); + FT dot_ab = (a*b) / (aa * bb); + + // threshold = 1 means no tolerance, totally degenerate + // take the opposite, because cos is -1 at 180 degrees + if(dot_ab < -threshold) + return true; + } + return false; +} + +template +bool is_cap_triangle_face(typename boost::graph_traits::face_descriptor f, + const TriangleMesh& tm, + const double threshold) +{ + return is_cap_triangle_face(f, tm, threshold, parameters::all_default()); +} + + + + +} } // end namespaces CGAL and PMP + + + +#endif // CGAL_POLYGON_MESH_PROCESSING_HELPERS_H + diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/repair.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/repair.h index 27ea6f5d191..8352c191f47 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/repair.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/repair.h @@ -40,6 +40,7 @@ #include #include +#include #include #include @@ -157,6 +158,7 @@ struct Less_vertex_point{ } }; +// to be removed template OutputIterator degenerate_faces(const TriangleMesh& tm, @@ -167,7 +169,7 @@ degenerate_faces(const TriangleMesh& tm, typedef typename boost::graph_traits::face_descriptor face_descriptor; BOOST_FOREACH(face_descriptor fd, faces(tm)) { - if ( is_degenerate_triangle_face(fd, tm, vpmap, traits) ) + if ( is_degenerate_triangle_face(fd, tm) ) *out++=fd; } return out; @@ -184,346 +186,6 @@ degenerate_faces(const TriangleMesh& tm, OutputIterator out) return degenerate_faces(tm, get(vertex_point, tm), Kernel(), out); } -/// \ingroup PMP_repairing_grp -/// checks whether a vertex is non-manifold. -/// -/// @tparam PolygonMesh a model of `FaceListGraph` and `MutableFaceGraph` -/// -/// @param v the vertex to check whether is degenerate -/// @param tm triangle mesh containing v -/// -/// \return true if the vertrex is non-manifold -template -bool is_non_manifold_vertex(typename boost::graph_traits::vertex_descriptor v, - const PolygonMesh& tm) -{ - CGAL_assertion(CGAL::is_triangle_mesh(tm)); - - typedef boost::graph_traits GT; - typedef typename GT::halfedge_descriptor halfedge_descriptor; - - boost::unordered_set halfedges_handled; - - BOOST_FOREACH(halfedge_descriptor h, halfedges_around_target(v, tm)) - halfedges_handled.insert(h); - - BOOST_FOREACH(halfedge_descriptor h, halfedges(tm)) - { - if(v == target(h, tm)) - { - if(halfedges_handled.count(h) == 0) - return true; - } - } - return false; -} - -/// \ingroup PMP_repairing_grp -/// checks whether an edge is degenerate. -/// An edge is considered degenerate if the points of its vertices are identical. -/// -/// @tparam PolygonMesh a model of `HalfedgeGraph` -/// @tparam NamedParameters a sequence of \ref pmp_namedparameters "Named Parameters" -/// -/// @param e the edge to check whether is degenerate -/// @param pm polygon mesh containing e -/// @param np optional \ref pmp_namedparameters "Named Parameters" described below -/// -/// \cgalNamedParamsBegin -/// \cgalParamBegin{vertex_point_map} the property map with the points associated to the vertices of `pmesh`. The type of this map is model of `ReadWritePropertyMap`. -/// If this parameter is omitted, an internal property map for -/// `CGAL::vertex_point_t` should be available in `PolygonMesh` -/// \cgalParamEnd -/// \cgalParamBegin{geom_traits} a geometric traits class instance. -/// The traits class must provide the nested type `Point_3`, -/// and the nested functor : -/// - `Equal_3` to check whether 2 points are identical -/// \cgalParamEnd -/// \cgalNamedParamsEnd -/// -/// \return true if the edge is degenerate -template -bool is_degenerate_edge(typename boost::graph_traits::edge_descriptor e, - const PolygonMesh& pm, - const NamedParameters& np) -{ - using boost::get_param; - using boost::choose_param; - - typedef typename GetVertexPointMap::const_type VertexPointMap; - VertexPointMap vpmap = choose_param(get_param(np, internal_np::vertex_point), - get_const_property_map(vertex_point, pm)); - typedef typename GetGeomTraits::type Traits; - Traits traits = choose_param(get_param(np, internal_np::geom_traits), Traits()); - - if ( traits.equal_3_object()(get(vpmap, target(e, pm)), get(vpmap, source(e, pm))) ) - return true; - return false; -} - -template -bool is_degenerate_edge(typename boost::graph_traits::edge_descriptor e, - const PolygonMesh& pm) -{ - return is_degenerate_edge(e, pm, parameters::all_default()); -} - -/// \cond SKIP_IN_DOC -template -bool is_degenerate_triangle_face( - typename boost::graph_traits::halfedge_descriptor hd, - TriangleMesh& tmesh, - const VertexPointMap& vpmap, - const Traits& traits) -{ - CGAL_assertion(!is_border(hd, tmesh)); - - const typename Traits::Point_3& p1 = get(vpmap, target( hd, tmesh) ); - const typename Traits::Point_3& p2 = get(vpmap, target(next(hd, tmesh), tmesh) ); - const typename Traits::Point_3& p3 = get(vpmap, source( hd, tmesh) ); - return traits.collinear_3_object()(p1, p2, p3); -} - -template -bool is_degenerate_triangle_face( - typename boost::graph_traits::face_descriptor fd, - TriangleMesh& tmesh, - const VertexPointMap& vpmap, - const Traits& traits) -{ - return is_degenerate_triangle_face(halfedge(fd,tmesh), tmesh, vpmap, traits); -} -/// \endcond - -/// \ingroup PMP_repairing_grp -/// checks whether a triangle face is degenerate. -/// A triangle face is degenerate if its points are collinear. -/// -/// @tparam TriangleMesh a model of `FaceGraph` -/// @tparam NamedParameters a sequence of \ref pmp_namedparameters "Named Parameters" -/// -/// @param f the face to check whether is degenerate -/// @param tm triangle mesh containing f -/// @param np optional \ref pmp_namedparameters "Named Parameters" described below -/// -/// \cgalNamedParamsBegin -/// \cgalParamBegin{vertex_point_map} the property map with the points associated to the vertices of `pmesh`. The type of this map is model of `ReadWritePropertyMap`. -/// If this parameter is omitted, an internal property map for -/// `CGAL::vertex_point_t` should be available in `TriangleMesh` -/// \cgalParamEnd -/// \cgalParamBegin{geom_traits} a geometric traits class instance. -/// The traits class must provide the nested type `Point_3`, -/// and the nested functor : -/// - `Collinear_3` to check whether 3 points are collinear -/// \cgalParamEnd -/// \cgalNamedParamsEnd -/// -/// \return true if the triangle face is degenerate -template -bool is_degenerate_triangle_face(typename boost::graph_traits::face_descriptor f, - const TriangleMesh& tm, - const NamedParameters& np) -{ - CGAL_assertion(CGAL::is_triangle_mesh(tm)); - - using boost::get_param; - using boost::choose_param; - - typedef typename GetVertexPointMap::const_type VertexPointMap; - VertexPointMap vpmap = choose_param(get_param(np, internal_np::vertex_point), - get_const_property_map(vertex_point, tm)); - typedef typename GetGeomTraits::type Traits; - Traits traits = choose_param(get_param(np, internal_np::geom_traits), Traits()); - - // call from BGL helpers - return is_degenerate_triangle_face(f, tm, vpmap, traits); -} - -template -bool is_degenerate_triangle_face(typename boost::graph_traits::face_descriptor f, - const TriangleMesh& tm) -{ - return CGAL::Polygon_mesh_processing::is_degenerate_triangle_face(f, tm, parameters::all_default()); -} - -/// \ingroup PMP_repairing_grp -/// checks whether a triangle face is needle. -/// A triangle is needle if its longest edge is much longer than the shortest one. -/// -/// @tparam TriangleMesh a model of `FaceGraph` -/// @tparam NamedParameters a sequence of \ref pmp_namedparameters "Named Parameters" -/// -/// @param f a face to check whether is almost degenerate -/// @param tm triangle mesh containing f -/// @param threshold a number in the range [0, 1] to indicate the tolerance -/// upon which to characterize the degeneracy. 1 means that needle triangles -/// are those that have a infinitely small edge, while 0 means that all -/// triangles are considered needles. -/// @param np optional \ref pmp_namedparameters "Named Parameters" described below -/// -/// \cgalNamedParamsBegin -/// \cgalParamBegin{vertex_point_map} the property map with the points associated to the vertices of `pmesh`. The type of this map is model of `ReadWritePropertyMap`. -/// If this parameter is omitted, an internal property map for -/// `CGAL::vertex_point_t` should be available in `TriangleMesh` -/// \cgalParamEnd -/// \cgalParamBegin{geom_traits} a geometric traits class instance. -/// The traits class must provide the nested type `Point_3`. -/// \cgalParamEnd -/// \cgalNamedParamsEnd -/// -/// \return true if the triangle face is almost degenerate -template -bool is_needle_triangle_face(typename boost::graph_traits::face_descriptor f, - const TriangleMesh& tm, - const double threshold, - const NamedParameters& np) -{ - CGAL_assertion(CGAL::is_triangle_mesh(tm)); - - using boost::get_param; - using boost::choose_param; - - typedef typename GetVertexPointMap::const_type VertexPointMap; - VertexPointMap vpmap = choose_param(get_param(np, internal_np::vertex_point), - get_const_property_map(vertex_point, tm)); - typedef typename GetGeomTraits::type Traits; - typedef typename Traits::FT FT; - typedef boost::graph_traits GT; - typedef typename GT::vertex_descriptor vertex_descriptor; - typedef typename boost::property_traits::reference Point_ref; - - vertex_descriptor v0 = target(halfedge(f, tm), tm); - vertex_descriptor v1 = target(next(halfedge(f, tm), tm), tm); - vertex_descriptor v2 = target(next(next(halfedge(f, tm), tm), tm), tm); - Point_ref p0 = get(vpmap, v0); - Point_ref p1 = get(vpmap, v1); - Point_ref p2 = get(vpmap, v2); - - // e1 = p0p1 e2 = p1p2 e3 = p2p3 - FT e1 = CGAL::squared_distance(p0,p1); - FT e2 = CGAL::squared_distance(p1,p2); - FT e3 = CGAL::squared_distance(p2,p0); - - FT smallest, largest; - if(e1 < e2) - { - if(e1 < e3) - smallest = e1; - else - smallest = e3; - } - else - { - if(e2 < e3) - smallest = e2; - else - smallest = e3; - } - if(e1 > e2) - { - if(e1 > e3) - largest = e1; - else - largest = e3; - } - else - { - if(e2 > e3) - largest = e2; - else - largest = e3; - } - - const double ratio = smallest / largest; - // threshold is opposite - if(ratio < (1 - threshold)) - return true; - return false; -} - -template -bool is_needle_triangle_face(typename boost::graph_traits::face_descriptor f, - const TriangleMesh& tm, - const double threshold) -{ - return is_needle_triangle_face(f, tm, threshold, parameters::all_default()); -} - -/// \ingroup PMP_repairing_grp -/// checks whether a triangle face is a cap. -/// A triangle is a cap if it has an angle very close to 180 degrees. -/// -/// @tparam TriangleMesh a model of `FaceGraph` -/// @tparam NamedParameters a sequence of \ref pmp_namedparameters "Named Parameters" -/// -/// @param f the face to check whether is almost degenerate -/// @param tm triangle mesh containing f -/// @param threshold a number in the range [0, 1] to indicate the tolerance -/// upon which to characterize the degeneracy. 1 means that cap triangles -/// are considered those whose vertices form an angle of 180 degrees, while 0 means that -/// all triangles are considered caps. -/// @param np optional \ref pmp_namedparameters "Named Parameters" described below -/// -/// \cgalNamedParamsBegin -/// \cgalParamBegin{vertex_point_map} the property map with the points associated to the vertices of `pmesh`. The type of this map is model of `ReadWritePropertyMap`. -/// If this parameter is omitted, an internal property map for -/// `CGAL::vertex_point_t` should be available in `TriangleMesh` -/// \cgalParamEnd -/// \cgalParamBegin{geom_traits} a geometric traits class instance. -/// The traits class must provide the nested type `Point_3` -/// \cgalParamEnd -/// \cgalNamedParamsEnd -/// -/// \return true if the triangle face is almost degenerate -template -bool is_cap_triangle_face(typename boost::graph_traits::face_descriptor f, - const TriangleMesh& tm, - const double threshold, - const NamedParameters& np) -{ - CGAL_assertion(CGAL::is_triangle_mesh(tm)); - - using boost::get_param; - using boost::choose_param; - - typedef typename GetVertexPointMap::const_type VertexPointMap; - VertexPointMap vpmap = choose_param(get_param(np, internal_np::vertex_point), - get_const_property_map(vertex_point, tm)); - typedef typename GetGeomTraits::type Traits; - typedef typename Traits::FT FT; - typedef boost::graph_traits GT; - typedef typename GT::halfedge_descriptor halfedge_descriptor; - typedef typename GT::vertex_descriptor vertex_descriptor; - typedef typename boost::property_traits::value_type Point_type; - typedef typename Kernel_traits::Kernel::Vector_3 Vector; - - BOOST_FOREACH(halfedge_descriptor h, halfedges_around_face(halfedge(f, tm), tm)) - { - vertex_descriptor v0 = source(h, tm); - vertex_descriptor v1 = target(h, tm); - vertex_descriptor v2 = target(next(h, tm), tm); - Vector a = get(vpmap, v0) - get (vpmap, v1); - Vector b = get(vpmap, v2) - get(vpmap, v1); - FT aa = a.squared_length(); - FT bb = b.squared_length(); - FT dot_ab = (a*b) / (aa * bb); - - // threshold = 1 means no tolerance, totally degenerate - // take the opposite, because cos is -1 at 180 degrees - if(dot_ab < -threshold) - return true; - } - return false; -} - -template -bool is_cap_triangle_face(typename boost::graph_traits::face_descriptor f, - const TriangleMesh& tm, - const double threshold) -{ - return is_cap_triangle_face(f, tm, threshold, parameters::all_default()); -} - // this function remove a border edge even if it does not satisfy the link condition. // The only limitation is that the length connected component of the boundary this edge // is strictly greater than 3 diff --git a/Polygon_mesh_processing/test/Polygon_mesh_processing/CMakeLists.txt b/Polygon_mesh_processing/test/Polygon_mesh_processing/CMakeLists.txt index ee3d82c4fe8..0c26363fca7 100644 --- a/Polygon_mesh_processing/test/Polygon_mesh_processing/CMakeLists.txt +++ b/Polygon_mesh_processing/test/Polygon_mesh_processing/CMakeLists.txt @@ -102,6 +102,7 @@ endif() create_single_source_cgal_program("test_pmp_transform.cpp") create_single_source_cgal_program("remove_degeneracies_test.cpp") create_single_source_cgal_program("test_merging_border_vertices.cpp") + create_single_source_cgal_program("test_predicates.cpp") if( TBB_FOUND ) CGAL_target_use_TBB(test_pmp_distance) diff --git a/Polygon_mesh_processing/test/Polygon_mesh_processing/remove_degeneracies_test.cpp b/Polygon_mesh_processing/test/Polygon_mesh_processing/remove_degeneracies_test.cpp index e3e737e1a63..8b6488320e1 100644 --- a/Polygon_mesh_processing/test/Polygon_mesh_processing/remove_degeneracies_test.cpp +++ b/Polygon_mesh_processing/test/Polygon_mesh_processing/remove_degeneracies_test.cpp @@ -2,7 +2,6 @@ #include #include -#include #include #include @@ -30,122 +29,6 @@ void fix(const char* fname) assert( CGAL::is_valid_polygon_mesh(mesh) ); } -void check_edge_degeneracy(const char* fname) -{ - std::ifstream input(fname); - - Surface_mesh mesh; - if (!input || !(input >> mesh) || mesh.is_empty()) { - std::cerr << fname << " is not a valid off file.\n"; - exit(1); - } - typedef typename boost::graph_traits::edge_descriptor edge_descriptor; - std::vector all_edges(edges(mesh).begin(), edges(mesh).end()); - - CGAL_assertion(!CGAL::Polygon_mesh_processing::is_degenerate_edge(all_edges[0], mesh)); - CGAL_assertion(!CGAL::Polygon_mesh_processing::is_degenerate_edge(all_edges[1], mesh)); - CGAL_assertion(CGAL::Polygon_mesh_processing::is_degenerate_edge(all_edges[2], mesh)); -} - -void check_triangle_face_degeneracy(const char* fname) -{ - std::ifstream input(fname); - - Surface_mesh mesh; - if (!input || !(input >> mesh) || mesh.is_empty()) { - std::cerr << fname << " is not a valid off file.\n"; - exit(1); - } - - typedef typename boost::graph_traits::face_descriptor face_descriptor; - std::vector all_faces(faces(mesh).begin(), faces(mesh).end()); - CGAL_assertion(CGAL::Polygon_mesh_processing::is_degenerate_triangle_face(all_faces[0], mesh)); - CGAL_assertion(!CGAL::Polygon_mesh_processing::is_degenerate_triangle_face(all_faces[1], mesh)); - CGAL_assertion(!CGAL::Polygon_mesh_processing::is_degenerate_triangle_face(all_faces[2], mesh)); - CGAL_assertion(!CGAL::Polygon_mesh_processing::is_degenerate_triangle_face(all_faces[3], mesh)); -} - -void test_vertices_merge_and_duplication(const char* fname) -{ - std::ifstream input(fname); - Surface_mesh mesh; - if (!input || !(input >> mesh) || mesh.is_empty()) { - std::cerr << fname << " is not a valid off file.\n"; - exit(1); - } - const std::size_t initial_vertices = vertices(mesh).size(); - - // create non-manifold vertex - typedef typename boost::graph_traits::vertex_descriptor vertex_descriptor; - std::vector all_vertices(vertices(mesh).begin(), vertices(mesh).end()); - CGAL::Polygon_mesh_processing::merge_identical_points(mesh, all_vertices[1], all_vertices[7]); - - const std::size_t vertices_after_merge = vertices(mesh).size(); - CGAL_assertion(vertices_after_merge == initial_vertices - 1); - - CGAL::Polygon_mesh_processing::duplicate_non_manifold_vertices(mesh); - const std::size_t final_vertices = vertices(mesh).size(); - CGAL_assertion(final_vertices == vertices_after_merge + 1); - CGAL_assertion(final_vertices == initial_vertices); -} - -void test_vertex_non_manifoldness(const char* fname) -{ - std::ifstream input(fname); - Surface_mesh mesh; - if (!input || !(input >> mesh) || mesh.is_empty()) { - std::cerr << fname << " is not a valid off file.\n"; - exit(1); - } - - // create non-manifold vertex - typedef typename boost::graph_traits::vertex_descriptor vertex_descriptor; - std::vector all_vertices(vertices(mesh).begin(), vertices(mesh).end()); - CGAL::Polygon_mesh_processing::merge_identical_points(mesh, all_vertices[1], all_vertices[7]); - std::vector vertices_with_non_manifold(vertices(mesh).begin(), vertices(mesh).end()); - CGAL_assertion(vertices_with_non_manifold.size() == all_vertices.size() - 1); - - BOOST_FOREACH(std::size_t iv, vertices(mesh)) - { - vertex_descriptor v = vertices_with_non_manifold[iv]; - if(iv == 1) - CGAL_assertion(CGAL::Polygon_mesh_processing::is_non_manifold_vertex(v, mesh)); - else - CGAL_assertion(!CGAL::Polygon_mesh_processing::is_non_manifold_vertex(v, mesh)); - } -} - -void test_needle(const char* fname) -{ - std::ifstream input(fname); - Surface_mesh mesh; - if (!input || !(input >> mesh) || mesh.is_empty()) { - std::cerr << fname << " is not a valid off file.\n"; - exit(1); - } - - const double threshold = 0.8; - BOOST_FOREACH(typename boost::graph_traits::face_descriptor f, faces(mesh)) - { - CGAL_assertion(CGAL::Polygon_mesh_processing::is_needle_triangle_face(f, mesh, threshold)); - } -} - -void test_cap(const char* fname) -{ - std::ifstream input(fname); - Surface_mesh mesh; - if (!input || !(input >> mesh) || mesh.is_empty()) { - std::cerr << fname << " is not a valid off file.\n"; - exit(1); - } - - const double threshold = 0.8; - BOOST_FOREACH(typename boost::graph_traits::face_descriptor f, faces(mesh)) - { - CGAL_assertion(CGAL::Polygon_mesh_processing::is_cap_triangle_face(f, mesh, threshold)); - } -} int main() { @@ -156,12 +39,6 @@ int main() fix("data_degeneracies/degtri_three.off"); fix("data_degeneracies/degtri_single.off"); fix("data_degeneracies/trihole.off"); - check_edge_degeneracy("data_degeneracies/degtri_edge.off"); - check_triangle_face_degeneracy("data_degeneracies/degtri_four.off"); - test_vertices_merge_and_duplication("data_degeneracies/non_manifold_vertex_duplicated.off"); - test_vertex_non_manifoldness("data_degeneracies/non_manifold_vertex_duplicated.off"); - test_needle("data_degeneracies/needle.off"); - test_cap("data_degeneracies/cap.off"); return 0; } diff --git a/Polygon_mesh_processing/test/Polygon_mesh_processing/test_predicates.cpp b/Polygon_mesh_processing/test/Polygon_mesh_processing/test_predicates.cpp new file mode 100644 index 00000000000..9d1ed0dea26 --- /dev/null +++ b/Polygon_mesh_processing/test/Polygon_mesh_processing/test_predicates.cpp @@ -0,0 +1,139 @@ +#include +#include +#include +#include +#include +#include + +typedef CGAL::Exact_predicates_inexact_constructions_kernel K; +typedef CGAL::Surface_mesh Surface_mesh; + +void check_edge_degeneracy(const char* fname) +{ + std::ifstream input(fname); + + Surface_mesh mesh; + if (!input || !(input >> mesh) || mesh.is_empty()) { + std::cerr << fname << " is not a valid off file.\n"; + exit(1); + } + typedef typename boost::graph_traits::edge_descriptor edge_descriptor; + std::vector all_edges(edges(mesh).begin(), edges(mesh).end()); + + CGAL_assertion(!CGAL::Polygon_mesh_processing::is_degenerate_edge(all_edges[0], mesh)); + CGAL_assertion(!CGAL::Polygon_mesh_processing::is_degenerate_edge(all_edges[1], mesh)); + CGAL_assertion(CGAL::Polygon_mesh_processing::is_degenerate_edge(all_edges[2], mesh)); +} + +void check_triangle_face_degeneracy(const char* fname) +{ + std::ifstream input(fname); + + Surface_mesh mesh; + if (!input || !(input >> mesh) || mesh.is_empty()) { + std::cerr << fname << " is not a valid off file.\n"; + exit(1); + } + + typedef typename boost::graph_traits::face_descriptor face_descriptor; + std::vector all_faces(faces(mesh).begin(), faces(mesh).end()); + CGAL_assertion(CGAL::Polygon_mesh_processing::is_degenerate_triangle_face(all_faces[0], mesh)); + CGAL_assertion(!CGAL::Polygon_mesh_processing::is_degenerate_triangle_face(all_faces[1], mesh)); + CGAL_assertion(!CGAL::Polygon_mesh_processing::is_degenerate_triangle_face(all_faces[2], mesh)); + CGAL_assertion(!CGAL::Polygon_mesh_processing::is_degenerate_triangle_face(all_faces[3], mesh)); +} + +// temp left here: tests repair.h +void test_vertices_merge_and_duplication(const char* fname) +{ + std::ifstream input(fname); + Surface_mesh mesh; + if (!input || !(input >> mesh) || mesh.is_empty()) { + std::cerr << fname << " is not a valid off file.\n"; + exit(1); + } + const std::size_t initial_vertices = vertices(mesh).size(); + + // create non-manifold vertex + typedef typename boost::graph_traits::vertex_descriptor vertex_descriptor; + std::vector all_vertices(vertices(mesh).begin(), vertices(mesh).end()); + CGAL::Polygon_mesh_processing::internal::merge_identical_points(mesh, all_vertices[1], all_vertices[7]); + + const std::size_t vertices_after_merge = vertices(mesh).size(); + CGAL_assertion(vertices_after_merge == initial_vertices - 1); + + CGAL::Polygon_mesh_processing::duplicate_non_manifold_vertices(mesh); + const std::size_t final_vertices = vertices(mesh).size(); + CGAL_assertion(final_vertices == vertices_after_merge + 1); + CGAL_assertion(final_vertices == initial_vertices); +} + +void test_vertex_non_manifoldness(const char* fname) +{ + std::ifstream input(fname); + Surface_mesh mesh; + if (!input || !(input >> mesh) || mesh.is_empty()) { + std::cerr << fname << " is not a valid off file.\n"; + exit(1); + } + + // create non-manifold vertex + typedef typename boost::graph_traits::vertex_descriptor vertex_descriptor; + std::vector all_vertices(vertices(mesh).begin(), vertices(mesh).end()); + CGAL::Polygon_mesh_processing::internal::merge_identical_points(mesh, all_vertices[1], all_vertices[7]); + std::vector vertices_with_non_manifold(vertices(mesh).begin(), vertices(mesh).end()); + CGAL_assertion(vertices_with_non_manifold.size() == all_vertices.size() - 1); + + BOOST_FOREACH(std::size_t iv, vertices(mesh)) + { + vertex_descriptor v = vertices_with_non_manifold[iv]; + if(iv == 1) + CGAL_assertion(CGAL::Polygon_mesh_processing::is_non_manifold_vertex(v, mesh)); + else + CGAL_assertion(!CGAL::Polygon_mesh_processing::is_non_manifold_vertex(v, mesh)); + } +} + +void test_needle(const char* fname) +{ + std::ifstream input(fname); + Surface_mesh mesh; + if (!input || !(input >> mesh) || mesh.is_empty()) { + std::cerr << fname << " is not a valid off file.\n"; + exit(1); + } + + const double threshold = 0.8; + BOOST_FOREACH(typename boost::graph_traits::face_descriptor f, faces(mesh)) + { + CGAL_assertion(CGAL::Polygon_mesh_processing::is_needle_triangle_face(f, mesh, threshold)); + } +} + +void test_cap(const char* fname) +{ + std::ifstream input(fname); + Surface_mesh mesh; + if (!input || !(input >> mesh) || mesh.is_empty()) { + std::cerr << fname << " is not a valid off file.\n"; + exit(1); + } + + const double threshold = 0.8; + BOOST_FOREACH(typename boost::graph_traits::face_descriptor f, faces(mesh)) + { + CGAL_assertion(CGAL::Polygon_mesh_processing::is_cap_triangle_face(f, mesh, threshold)); + } +} + +int main() +{ + check_edge_degeneracy("data_degeneracies/degtri_edge.off"); + check_triangle_face_degeneracy("data_degeneracies/degtri_four.off"); + test_vertices_merge_and_duplication("data_degeneracies/non_manifold_vertex_duplicated.off"); + test_vertex_non_manifoldness("data_degeneracies/non_manifold_vertex_duplicated.off"); + test_needle("data_degeneracies/needle.off"); + test_cap("data_degeneracies/cap.off"); + + return 0; +} From 71041e03769ae7dc749488a5d8f0778877b33469 Mon Sep 17 00:00:00 2001 From: Konstantinos Katrioplas Date: Tue, 17 Apr 2018 12:41:17 +0200 Subject: [PATCH 018/116] replace is_degenerate_triangle_face predicate with new version from PMP helpers --- .../CGAL/Polygon_mesh_processing/helpers.h | 27 ------------------- .../Isotropic_remeshing/remesh_impl.h | 13 ++++----- .../CGAL/Polygon_mesh_processing/repair.h | 19 ++----------- .../Plugins/PMP/Degenerated_faces_plugin.cpp | 3 ++- .../Plugins/PMP/Selection_plugin.cpp | 7 +++-- .../Edit_polyhedron_plugin.cpp | 6 ++--- .../demo/Polyhedron/Scene_polyhedron_item.cpp | 3 ++- .../Scene_polyhedron_selection_item.cpp | 3 ++- .../Polyhedron/Scene_surface_mesh_item.cpp | 3 ++- .../include/CGAL/statistics_helpers.h | 3 ++- 10 files changed, 24 insertions(+), 63 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/helpers.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/helpers.h index 20c35e54d13..24174b778d9 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/helpers.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/helpers.h @@ -136,33 +136,6 @@ bool is_degenerate_edge(typename boost::graph_traits::edge_descript return is_degenerate_edge(e, pm, parameters::all_default()); } -/// \cond SKIP_IN_DOC -template -bool is_degenerate_triangle_face( - typename boost::graph_traits::halfedge_descriptor hd, - TriangleMesh& tmesh, - const VertexPointMap& vpmap, - const Traits& traits) -{ - CGAL_assertion(!is_border(hd, tmesh)); - - const typename Traits::Point_3& p1 = get(vpmap, target( hd, tmesh) ); - const typename Traits::Point_3& p2 = get(vpmap, target(next(hd, tmesh), tmesh) ); - const typename Traits::Point_3& p3 = get(vpmap, source( hd, tmesh) ); - return traits.collinear_3_object()(p1, p2, p3); -} - -template -bool is_degenerate_triangle_face( - typename boost::graph_traits::face_descriptor fd, - TriangleMesh& tmesh, - const VertexPointMap& vpmap, - const Traits& traits) -{ - return is_degenerate_triangle_face(halfedge(fd,tmesh), tmesh, vpmap, traits); -} -/// \endcond - /// \ingroup PMP_repairing_grp /// checks whether a triangle face is degenerate. /// A triangle face is degenerate if its points are collinear. diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Isotropic_remeshing/remesh_impl.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Isotropic_remeshing/remesh_impl.h index bfbe181b40e..9686f4c271b 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Isotropic_remeshing/remesh_impl.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Isotropic_remeshing/remesh_impl.h @@ -30,6 +30,7 @@ #include #include #include +#include #include #include @@ -364,7 +365,7 @@ namespace internal { BOOST_FOREACH(face_descriptor f, face_range) { - if (is_degenerate_triangle_face(halfedge(f,mesh_),mesh_,vpmap_,GeomTraits())){ + if (is_degenerate_triangle_face(f, mesh_,)){ continue; } Patch_id pid = get_patch_id(f); @@ -1593,7 +1594,7 @@ private: { if (is_border(h, mesh_)) continue; - if (is_degenerate_triangle_face(h, mesh_, vpmap_, GeomTraits())) + if (is_degenerate_triangle_face(face(h), mesh_)) degenerate_faces.insert(h); } while(!degenerate_faces.empty()) @@ -1601,7 +1602,7 @@ private: halfedge_descriptor h = *(degenerate_faces.begin()); degenerate_faces.erase(degenerate_faces.begin()); - if (!is_degenerate_triangle_face(h, mesh_, vpmap_, GeomTraits())) + if (!is_degenerate_triangle_face(face(h), mesh_)) //this can happen when flipping h has consequences further in the mesh continue; @@ -1654,10 +1655,10 @@ private: } if (!is_border(hf, mesh_) - && is_degenerate_triangle_face(hf, mesh_, vpmap_, GeomTraits())) + && is_degenerate_triangle_face(face(h), mesh_)) degenerate_faces.insert(hf); if (!is_border(hfo, mesh_) - && is_degenerate_triangle_face(hfo, mesh_, vpmap_, GeomTraits())) + && is_degenerate_triangle_face(face(h), mesh_)) degenerate_faces.insert(hfo); break; @@ -1676,7 +1677,7 @@ private: { if (is_border(h, mesh_)) continue; - if (is_degenerate_triangle_face(h, mesh_, vpmap_, GeomTraits())) + if (is_degenerate_triangle_face(face(h), mesh_)) return true; } return false; diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/repair.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/repair.h index 8352c191f47..cced686125d 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/repair.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/repair.h @@ -158,13 +158,9 @@ struct Less_vertex_point{ } }; -// to be removed -template +template OutputIterator -degenerate_faces(const TriangleMesh& tm, - const VertexPointMap& vpmap, - const Traits& traits, - OutputIterator out) +degenerate_faces(const TriangleMesh& tm, OutputIterator out) { typedef typename boost::graph_traits::face_descriptor face_descriptor; BOOST_FOREACH(face_descriptor fd, faces(tm)) @@ -175,17 +171,6 @@ degenerate_faces(const TriangleMesh& tm, return out; } -template -OutputIterator -degenerate_faces(const TriangleMesh& tm, OutputIterator out) -{ - typedef typename boost::property_map::type Vpm; - typedef typename boost::property_traits::value_type Point; - typedef typename Kernel_traits::Kernel Kernel; - - return degenerate_faces(tm, get(vertex_point, tm), Kernel(), out); -} - // this function remove a border edge even if it does not satisfy the link condition. // The only limitation is that the length connected component of the boundary this edge // is strictly greater than 3 diff --git a/Polyhedron/demo/Polyhedron/Plugins/PMP/Degenerated_faces_plugin.cpp b/Polyhedron/demo/Polyhedron/Plugins/PMP/Degenerated_faces_plugin.cpp index 8fd94b78149..b7c359e9ab1 100644 --- a/Polyhedron/demo/Polyhedron/Plugins/PMP/Degenerated_faces_plugin.cpp +++ b/Polyhedron/demo/Polyhedron/Plugins/PMP/Degenerated_faces_plugin.cpp @@ -19,6 +19,7 @@ #include #include #include +#include #ifdef USE_SURFACE_MESH typedef Scene_surface_mesh_item Scene_facegraph_item; #else @@ -87,7 +88,7 @@ bool isDegen(Mesh* mesh, std::vector::face_de BOOST_FOREACH(FaceDescriptor f, faces(*mesh)) { if(is_triangle(halfedge(f, *mesh), *mesh) - && is_degenerate_triangle_face(f, *mesh, get(boost::vertex_point, *mesh), Kernel()) ) + && CGAL::Polygon_mesh_processing::is_degenerate_triangle_face(f, *mesh) ) out_faces.push_back(f); } return !out_faces.empty(); diff --git a/Polyhedron/demo/Polyhedron/Plugins/PMP/Selection_plugin.cpp b/Polyhedron/demo/Polyhedron/Plugins/PMP/Selection_plugin.cpp index a6cf3a63299..ac1bfd837d4 100644 --- a/Polyhedron/demo/Polyhedron/Plugins/PMP/Selection_plugin.cpp +++ b/Polyhedron/demo/Polyhedron/Plugins/PMP/Selection_plugin.cpp @@ -27,6 +27,7 @@ #include #include #include +#include #include #ifdef USE_SURFACE_MESH @@ -745,10 +746,8 @@ public Q_SLOTS: bool is_valid = true; BOOST_FOREACH(boost::graph_traits::face_descriptor fd, faces(*selection_item->polyhedron())) { - if (CGAL::is_degenerate_triangle_face(fd, - *selection_item->polyhedron(), - vpmap, - CGAL::Kernel_traits< boost::property_traits::value_type >::Kernel())) + if (CGAL::Polygon_mesh_processing::is_degenerate_triangle_face(fd, + *selection_item->polyhedron())) { is_valid = false; break; diff --git a/Polyhedron/demo/Polyhedron/Plugins/Surface_mesh_deformation/Edit_polyhedron_plugin.cpp b/Polyhedron/demo/Polyhedron/Plugins/Surface_mesh_deformation/Edit_polyhedron_plugin.cpp index 730d6447313..18a5d1bb8e0 100644 --- a/Polyhedron/demo/Polyhedron/Plugins/Surface_mesh_deformation/Edit_polyhedron_plugin.cpp +++ b/Polyhedron/demo/Polyhedron/Plugins/Surface_mesh_deformation/Edit_polyhedron_plugin.cpp @@ -9,6 +9,7 @@ #include "Scene_edit_polyhedron_item.h" #include "Scene_polyhedron_selection_item.h" #include +#include #include #include #include @@ -421,10 +422,7 @@ void Polyhedron_demo_edit_polyhedron_plugin::dock_widget_visibility_changed(bool bool is_valid = true; BOOST_FOREACH(boost::graph_traits::face_descriptor fd, faces(*poly_item->face_graph())) { - if (CGAL::is_degenerate_triangle_face(fd, - *poly_item->face_graph(), - get(boost::vertex_point, - *poly_item->face_graph()), Kernel())) + if (CGAL::Polygon_mesh_processing::is_degenerate_triangle_face(fd, *poly_item->face_graph())) { is_valid = false; break; diff --git a/Polyhedron/demo/Polyhedron/Scene_polyhedron_item.cpp b/Polyhedron/demo/Polyhedron/Scene_polyhedron_item.cpp index 58a466c7ad1..266f5331b94 100644 --- a/Polyhedron/demo/Polyhedron/Scene_polyhedron_item.cpp +++ b/Polyhedron/demo/Polyhedron/Scene_polyhedron_item.cpp @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -276,7 +277,7 @@ void* Scene_polyhedron_item_priv::get_aabb_tree() int index =0; BOOST_FOREACH( Polyhedron::Facet_iterator f, faces(*poly)) { - if (CGAL::is_degenerate_triangle_face(f, *poly, get(CGAL::vertex_point, *poly), Kernel())) + if (CGAL::Polygon_mesh_processing::is_degenerate_triangle_face(f, *poly)) continue; if(!f->is_triangle()) { diff --git a/Polyhedron/demo/Polyhedron/Scene_polyhedron_selection_item.cpp b/Polyhedron/demo/Polyhedron/Scene_polyhedron_selection_item.cpp index 6df22ab25e7..488e874d416 100644 --- a/Polyhedron/demo/Polyhedron/Scene_polyhedron_selection_item.cpp +++ b/Polyhedron/demo/Polyhedron/Scene_polyhedron_selection_item.cpp @@ -2,6 +2,7 @@ #include "Scene_polyhedron_selection_item.h" #include #include +#include #include #include #include @@ -2032,7 +2033,7 @@ bool Scene_polyhedron_selection_item_priv::canAddFace(fg_halfedge_descriptor hc, fg_halfedge_descriptor res = CGAL::Euler::add_face_to_border(t,hc, *item->polyhedron()); - if(CGAL::is_degenerate_triangle_face(res, *item->polyhedron(), get(CGAL::vertex_point, *item->polyhedron()), Kernel())) + if(CGAL::Polygon_mesh_processing::is_degenerate_triangle_face(res, *item->polyhedron())) { CGAL::Euler::remove_face(res, *item->polyhedron()); tempInstructions("Edge not selected : resulting facet is degenerated.", diff --git a/Polyhedron/demo/Polyhedron/Scene_surface_mesh_item.cpp b/Polyhedron/demo/Polyhedron/Scene_surface_mesh_item.cpp index 29c31b34c70..75e60907f61 100644 --- a/Polyhedron/demo/Polyhedron/Scene_surface_mesh_item.cpp +++ b/Polyhedron/demo/Polyhedron/Scene_surface_mesh_item.cpp @@ -25,6 +25,7 @@ #include #include #include +#include #include #include @@ -1039,7 +1040,7 @@ void* Scene_surface_mesh_item_priv::get_aabb_tree() BOOST_FOREACH( face_descriptor f, faces(*sm)) { //if face is degenerate, skip it - if (CGAL::is_degenerate_triangle_face(f, *sm, get(CGAL::vertex_point, *sm), EPICK())) + if (CGAL::Polygon_mesh_processing::is_degenerate_triangle_face(f, *sm)) continue; //if face not triangle, triangulate corresponding primitive before adding it to the tree if(!CGAL::is_triangle(halfedge(f, *sm), *sm)) diff --git a/Polyhedron/demo/Polyhedron/include/CGAL/statistics_helpers.h b/Polyhedron/demo/Polyhedron/include/CGAL/statistics_helpers.h index 28799d40fd9..711a641dcc3 100644 --- a/Polyhedron/demo/Polyhedron/include/CGAL/statistics_helpers.h +++ b/Polyhedron/demo/Polyhedron/include/CGAL/statistics_helpers.h @@ -14,6 +14,7 @@ #include #include +#include template @@ -92,7 +93,7 @@ unsigned int nb_degenerate_faces(Mesh* poly, VPmap vpmap) unsigned int nb = 0; BOOST_FOREACH(face_descriptor f, faces(*poly)) { - if (CGAL::is_degenerate_triangle_face(f, *poly, vpmap, Traits())) + if (CGAL::Polygon_mesh_processing::is_degenerate_triangle_face(f, *poly)) ++nb; } return nb; From c6afed86a3db493a1bef08a65e193b8f9a9f229c Mon Sep 17 00:00:00 2001 From: Konstantinos Katrioplas Date: Tue, 17 Apr 2018 13:31:51 +0200 Subject: [PATCH 019/116] use cosine for threshold on needles and caps --- .../CGAL/Polygon_mesh_processing/helpers.h | 205 ++++++++---------- .../test_predicates.cpp | 2 +- 2 files changed, 88 insertions(+), 119 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/helpers.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/helpers.h index 24174b778d9..dad4a29831a 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/helpers.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/helpers.h @@ -50,14 +50,12 @@ void merge_identical_points(PolygonMesh& mesh, } } // end internal - - /// \ingroup PMP_repairing_grp /// checks whether a vertex is non-manifold. /// /// @tparam PolygonMesh a model of `FaceListGraph` and `MutableFaceGraph` /// -/// @param v the vertex to check whether is degenerate +/// @param v the vertex /// @param tm triangle mesh containing v /// /// \return true if the vertrex is non-manifold @@ -93,7 +91,7 @@ bool is_non_manifold_vertex(typename boost::graph_traits::vertex_de /// @tparam PolygonMesh a model of `HalfedgeGraph` /// @tparam NamedParameters a sequence of \ref pmp_namedparameters "Named Parameters" /// -/// @param e the edge to check whether is degenerate +/// @param e the edge /// @param pm polygon mesh containing e /// @param np optional \ref pmp_namedparameters "Named Parameters" described below /// @@ -143,7 +141,7 @@ bool is_degenerate_edge(typename boost::graph_traits::edge_descript /// @tparam TriangleMesh a model of `FaceGraph` /// @tparam NamedParameters a sequence of \ref pmp_namedparameters "Named Parameters" /// -/// @param f the face to check whether is degenerate +/// @param f the triangle face /// @param tm triangle mesh containing f /// @param np optional \ref pmp_namedparameters "Named Parameters" described below /// @@ -198,12 +196,11 @@ bool is_degenerate_triangle_face(typename boost::graph_traits::fac /// @tparam TriangleMesh a model of `FaceGraph` /// @tparam NamedParameters a sequence of \ref pmp_namedparameters "Named Parameters" /// -/// @param f a face to check whether is almost degenerate +/// @param f the triangle face /// @param tm triangle mesh containing f -/// @param threshold a number in the range [0, 1] to indicate the tolerance -/// upon which to characterize the degeneracy. 1 means that needle triangles -/// are those that have a infinitely small edge, while 0 means that all -/// triangles are considered needles. +/// @param threshold the cosine of an angle of f. +/// The threshold is in range [0 1] and corresponds to +/// angles between 0 and 90 degrees. /// @param np optional \ref pmp_namedparameters "Named Parameters" described below /// /// \cgalNamedParamsBegin @@ -216,7 +213,7 @@ bool is_degenerate_triangle_face(typename boost::graph_traits::fac /// \cgalParamEnd /// \cgalNamedParamsEnd /// -/// \return true if the triangle face is almost degenerate +/// \return true if the triangle face is a needle template bool is_needle_triangle_face(typename boost::graph_traits::face_descriptor f, const TriangleMesh& tm, @@ -224,109 +221,8 @@ bool is_needle_triangle_face(typename boost::graph_traits::face_de const NamedParameters& np) { CGAL_assertion(CGAL::is_triangle_mesh(tm)); - - using boost::get_param; - using boost::choose_param; - - typedef typename GetVertexPointMap::const_type VertexPointMap; - VertexPointMap vpmap = choose_param(get_param(np, internal_np::vertex_point), - get_const_property_map(vertex_point, tm)); - typedef typename GetGeomTraits::type Traits; - typedef typename Traits::FT FT; - typedef boost::graph_traits GT; - typedef typename GT::vertex_descriptor vertex_descriptor; - typedef typename boost::property_traits::reference Point_ref; - - vertex_descriptor v0 = target(halfedge(f, tm), tm); - vertex_descriptor v1 = target(next(halfedge(f, tm), tm), tm); - vertex_descriptor v2 = target(next(next(halfedge(f, tm), tm), tm), tm); - Point_ref p0 = get(vpmap, v0); - Point_ref p1 = get(vpmap, v1); - Point_ref p2 = get(vpmap, v2); - - // e1 = p0p1 e2 = p1p2 e3 = p2p3 - FT e1 = CGAL::squared_distance(p0,p1); - FT e2 = CGAL::squared_distance(p1,p2); - FT e3 = CGAL::squared_distance(p2,p0); - - FT smallest, largest; - if(e1 < e2) - { - if(e1 < e3) - smallest = e1; - else - smallest = e3; - } - else - { - if(e2 < e3) - smallest = e2; - else - smallest = e3; - } - if(e1 > e2) - { - if(e1 > e3) - largest = e1; - else - largest = e3; - } - else - { - if(e2 > e3) - largest = e2; - else - largest = e3; - } - - const double ratio = smallest / largest; - // threshold is opposite - if(ratio < (1 - threshold)) - return true; - return false; -} - -template -bool is_needle_triangle_face(typename boost::graph_traits::face_descriptor f, - const TriangleMesh& tm, - const double threshold) -{ - return is_needle_triangle_face(f, tm, threshold, parameters::all_default()); -} - -/// \ingroup PMP_repairing_grp -/// checks whether a triangle face is a cap. -/// A triangle is a cap if it has an angle very close to 180 degrees. -/// -/// @tparam TriangleMesh a model of `FaceGraph` -/// @tparam NamedParameters a sequence of \ref pmp_namedparameters "Named Parameters" -/// -/// @param f the face to check whether is almost degenerate -/// @param tm triangle mesh containing f -/// @param threshold a number in the range [0, 1] to indicate the tolerance -/// upon which to characterize the degeneracy. 1 means that cap triangles -/// are considered those whose vertices form an angle of 180 degrees, while 0 means that -/// all triangles are considered caps. -/// @param np optional \ref pmp_namedparameters "Named Parameters" described below -/// -/// \cgalNamedParamsBegin -/// \cgalParamBegin{vertex_point_map} the property map with the points associated to the vertices of `pmesh`. The type of this map is model of `ReadWritePropertyMap`. -/// If this parameter is omitted, an internal property map for -/// `CGAL::vertex_point_t` should be available in `TriangleMesh` -/// \cgalParamEnd -/// \cgalParamBegin{geom_traits} a geometric traits class instance. -/// The traits class must provide the nested type `Point_3` -/// \cgalParamEnd -/// \cgalNamedParamsEnd -/// -/// \return true if the triangle face is almost degenerate -template -bool is_cap_triangle_face(typename boost::graph_traits::face_descriptor f, - const TriangleMesh& tm, - const double threshold, - const NamedParameters& np) -{ - CGAL_assertion(CGAL::is_triangle_mesh(tm)); + CGAL_assertion(threshold >= 0); + CGAL_assertion(threshold <= 1); using boost::get_param; using boost::choose_param; @@ -351,11 +247,84 @@ bool is_cap_triangle_face(typename boost::graph_traits::face_descr Vector b = get(vpmap, v2) - get(vpmap, v1); FT aa = a.squared_length(); FT bb = b.squared_length(); - FT dot_ab = (a*b) / (aa * bb); + FT squared_dot_ab = ((a*b)*(a*b)) / (aa * bb); - // threshold = 1 means no tolerance, totally degenerate - // take the opposite, because cos is -1 at 180 degrees - if(dot_ab < -threshold) + if(squared_dot_ab > threshold * threshold) + return true; + } + return false; + +} + +template +bool is_needle_triangle_face(typename boost::graph_traits::face_descriptor f, + const TriangleMesh& tm, + const double threshold) +{ + return is_needle_triangle_face(f, tm, threshold, parameters::all_default()); +} + +/// \ingroup PMP_repairing_grp +/// checks whether a triangle face is a cap. +/// A triangle is a cap if it has an angle very close to 180 degrees. +/// +/// @tparam TriangleMesh a model of `FaceGraph` +/// @tparam NamedParameters a sequence of \ref pmp_namedparameters "Named Parameters" +/// +/// @param f the triangle face +/// @param tm triangle mesh containing f +/// @param threshold the cosine of an angle of f. +/// The threshold is in range [-1 0] and corresponds to +/// angles between 90 and 180 degrees. +/// @param np optional \ref pmp_namedparameters "Named Parameters" described below +/// +/// \cgalNamedParamsBegin +/// \cgalParamBegin{vertex_point_map} the property map with the points associated to the vertices of `pmesh`. The type of this map is model of `ReadWritePropertyMap`. +/// If this parameter is omitted, an internal property map for +/// `CGAL::vertex_point_t` should be available in `TriangleMesh` +/// \cgalParamEnd +/// \cgalParamBegin{geom_traits} a geometric traits class instance. +/// The traits class must provide the nested type `Point_3` +/// \cgalParamEnd +/// \cgalNamedParamsEnd +/// +/// \return true if the triangle face is a cap +template +bool is_cap_triangle_face(typename boost::graph_traits::face_descriptor f, + const TriangleMesh& tm, + const double threshold, + const NamedParameters& np) +{ + CGAL_assertion(CGAL::is_triangle_mesh(tm)); + CGAL_assertion(threshold >= -1); + CGAL_assertion(threshold <= 0); + + using boost::get_param; + using boost::choose_param; + + typedef typename GetVertexPointMap::const_type VertexPointMap; + VertexPointMap vpmap = choose_param(get_param(np, internal_np::vertex_point), + get_const_property_map(vertex_point, tm)); + typedef typename GetGeomTraits::type Traits; + typedef typename Traits::FT FT; + typedef boost::graph_traits GT; + typedef typename GT::halfedge_descriptor halfedge_descriptor; + typedef typename GT::vertex_descriptor vertex_descriptor; + typedef typename boost::property_traits::value_type Point_type; + typedef typename Kernel_traits::Kernel::Vector_3 Vector; + + BOOST_FOREACH(halfedge_descriptor h, halfedges_around_face(halfedge(f, tm), tm)) + { + vertex_descriptor v0 = source(h, tm); + vertex_descriptor v1 = target(h, tm); + vertex_descriptor v2 = target(next(h, tm), tm); + Vector a = get(vpmap, v0) - get (vpmap, v1); + Vector b = get(vpmap, v2) - get(vpmap, v1); + FT aa = a.squared_length(); + FT bb = b.squared_length(); + FT squared_dot_ab = ((a*b)*(a*b)) / (aa * bb); + + if(squared_dot_ab > threshold * threshold) return true; } return false; diff --git a/Polygon_mesh_processing/test/Polygon_mesh_processing/test_predicates.cpp b/Polygon_mesh_processing/test/Polygon_mesh_processing/test_predicates.cpp index 9d1ed0dea26..679f96762d3 100644 --- a/Polygon_mesh_processing/test/Polygon_mesh_processing/test_predicates.cpp +++ b/Polygon_mesh_processing/test/Polygon_mesh_processing/test_predicates.cpp @@ -119,7 +119,7 @@ void test_cap(const char* fname) exit(1); } - const double threshold = 0.8; + const double threshold = -0.8; BOOST_FOREACH(typename boost::graph_traits::face_descriptor f, faces(mesh)) { CGAL_assertion(CGAL::Polygon_mesh_processing::is_cap_triangle_face(f, mesh, threshold)); From 032ee2828a29dbc3fa7f4f427e2f35ae10266724 Mon Sep 17 00:00:00 2001 From: Konstantinos Katrioplas Date: Wed, 18 Apr 2018 18:11:47 +0200 Subject: [PATCH 020/116] named parameters for duplicate non-manifold vertices --- .../CGAL/boost/graph/parameters_interface.h | 1 + BGL/test/BGL/test_cgal_bgl_named_params.cpp | 3 ++ .../CGAL/Polygon_mesh_processing/helpers.h | 51 +++++++++++++++++++ .../CGAL/Polygon_mesh_processing/repair.h | 49 ++++++++++++++---- .../test_predicates.cpp | 13 +++-- 5 files changed, 102 insertions(+), 15 deletions(-) diff --git a/BGL/include/CGAL/boost/graph/parameters_interface.h b/BGL/include/CGAL/boost/graph/parameters_interface.h index 16357cb39dd..657972a74a8 100644 --- a/BGL/include/CGAL/boost/graph/parameters_interface.h +++ b/BGL/include/CGAL/boost/graph/parameters_interface.h @@ -70,6 +70,7 @@ CGAL_add_named_parameter(projection_functor_t, projection_functor, projection_fu CGAL_add_named_parameter(throw_on_self_intersection_t, throw_on_self_intersection, throw_on_self_intersection) 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) +CGAL_add_named_parameter(output_iterator_t, output_iterator, output_iterator) // 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 4bb47789fde..c0081450d1e 100644 --- a/BGL/test/BGL/test_cgal_bgl_named_params.cpp +++ b/BGL/test/BGL/test_cgal_bgl_named_params.cpp @@ -92,6 +92,7 @@ void test(const NamedParameters& np) assert(get_param(np, CGAL::internal_np::verbosity_level).v == 41); assert(get_param(np, CGAL::internal_np::projection_functor).v == 42); assert(get_param(np, CGAL::internal_np::apply_per_connected_component).v == 46); + assert(get_param(np, CGAL::internal_np::output_iterator).v == 47); // Test types @@ -162,6 +163,7 @@ void test(const NamedParameters& np) check_same_type<41>(get_param(np, CGAL::internal_np::verbosity_level)); check_same_type<42>(get_param(np, CGAL::internal_np::projection_functor)); check_same_type<46>(get_param(np, CGAL::internal_np::apply_per_connected_component)); + check_same_type472>(get_param(np, CGAL::internal_np::output_iterator)); } int main() @@ -217,6 +219,7 @@ int main() .clip_volume(A<44>(44)) .use_compact_clipper(A<45>(45)) .apply_per_connected_component(A<46>(46)) + .output_iterator(A<47>(47)) ); return EXIT_SUCCESS; diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/helpers.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/helpers.h index dad4a29831a..ea38a91b835 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/helpers.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/helpers.h @@ -32,6 +32,57 @@ namespace Polygon_mesh_processing { namespace internal { +template +struct No_constraint_pmap +{ +public: + typedef Descriptor key_type; + typedef bool value_type; + typedef value_type& reference; + typedef boost::read_write_property_map_tag category; + + friend bool get(const No_constraint_pmap& , const key_type& ) { + return false; + } + friend void put(No_constraint_pmap& , const key_type& , const bool ) {} +}; + +template +struct Vertex_collector +{ + typedef typename boost::graph_traits::vertex_descriptor vertex_descriptor; + void collect_vertices(vertex_descriptor v1, vertex_descriptor v2) + { + std::vector& verts = collections[v1]; + if (verts.empty()) + verts.push_back(v1); + verts.push_back(v2); + } + + void dump(OutputIterator out) + { + typedef std::pair > Pair_type; + BOOST_FOREACH(const Pair_type& p, collections) + { + *out++=p.second; + } + } + + std::map > collections; +}; + +template +struct Vertex_collector +{ + typedef typename boost::graph_traits::vertex_descriptor vertex_descriptor; + void collect_vertices(vertex_descriptor, vertex_descriptor) + {} + + void dump(Emptyset_iterator) + {} +}; + +// used only for testing template void merge_identical_points(PolygonMesh& mesh, typename boost::graph_traits::vertex_descriptor v_keep, diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/repair.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/repair.h index cced686125d..b5579b5204b 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/repair.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/repair.h @@ -1284,14 +1284,21 @@ std::size_t remove_degenerate_faces(TriangleMesh& tmesh) /// /// \cgalNamedParamsBegin /// \cgalParamBegin{vertex_point_map} the property map with the points associated to the vertices of `pmesh`. The type of this map is model of `ReadWritePropertyMap`. -/// If this parameter is omitted, an internal property map for -/// `CGAL::vertex_point_t` should be available in `PolygonMesh` -/// \cgalParamEnd -/// \cgalParamBegin{geom_traits} a geometric traits class instance. +/// If this parameter is omitted, an internal property map for +/// `CGAL::vertex_point_t` should be available in `PolygonMesh` /// \cgalParamEnd +/// \cgalParamBegin{vertex_is_constrained_map} a writable property map with `vertex_descriptor` +/// as key and `bool` as `value_type`. `put(pmap, v, true)` will be called for each duplicated +/// vertices and the input one. +/// \cgalParamEnd +/// \cgalParamBegin{output_iterator} an output iterator where `std::vector` can be put. +/// The first vertex of the vector is an input vertex that was non-manifold, +/// the other vertices in the vertex are the new vertices created to fix +/// the non-manifoldness. +/// \cgalParamEnd /// \cgalNamedParamsEnd /// -/// \return true if the triangle face is degenerate +/// \return the number of vertices created template std::size_t duplicate_non_manifold_vertices(TriangleMesh& tm, const NamedParameters& np) @@ -1301,14 +1308,33 @@ std::size_t duplicate_non_manifold_vertices(TriangleMesh& tm, using boost::get_param; using boost::choose_param; - typedef typename GetVertexPointMap::type VertexPointMap; - VertexPointMap vpm = choose_param(get_param(np, internal_np::vertex_point), - get_property_map(vertex_point, tm)); - typedef boost::graph_traits GT; typedef typename GT::vertex_descriptor vertex_descriptor; typedef typename GT::halfedge_descriptor halfedge_descriptor; + typedef typename GetVertexPointMap::type VertexPointMap; + VertexPointMap vpm = choose_param(get_param(np, internal_np::vertex_point), + get_property_map(vertex_point, tm)); + + typedef typename boost::lookup_named_param_def < + internal_np::vertex_is_constrained_t, + NamedParameters, + internal::No_constraint_pmap//default + > ::type VerticesMap; + VerticesMap cmap + = choose_param(get_param(np, internal_np::vertex_is_constrained), + internal::No_constraint_pmap()); + + typedef typename boost::lookup_named_param_def < + internal_np::output_iterator_t, + NamedParameters, + Emptyset_iterator + > ::type Output_iterator; + Output_iterator out + = choose_param(get_param(np, internal_np::output_iterator), + Emptyset_iterator()); + + internal::Vertex_collector dmap; boost::unordered_set vertices_handled; boost::unordered_set halfedges_handled; @@ -1322,6 +1348,7 @@ std::size_t duplicate_non_manifold_vertices(TriangleMesh& tm, vertex_descriptor vd = target(h, tm); if ( !vertices_handled.insert(vd).second ) { + put(cmap, vd, true); // store the originals non_manifold_cones.push_back(h); } else @@ -1341,6 +1368,8 @@ std::size_t duplicate_non_manifold_vertices(TriangleMesh& tm, halfedge_descriptor start = h; vertex_descriptor new_vd = add_vertex(tm); ++nb_new_vertices; + put(cmap, new_vd, true); // store the duplicates + dmap.collect_vertices(target(h, tm), new_vd); put(vpm, new_vd, get(vpm, target(h, tm))); set_halfedge(new_vd, h, tm); do{ @@ -1348,8 +1377,8 @@ std::size_t duplicate_non_manifold_vertices(TriangleMesh& tm, h=opposite(next(h, tm), tm); } while(h!=start); } + dmap.dump(out); } - return nb_new_vertices; } diff --git a/Polygon_mesh_processing/test/Polygon_mesh_processing/test_predicates.cpp b/Polygon_mesh_processing/test/Polygon_mesh_processing/test_predicates.cpp index 679f96762d3..3bf2ffcd494 100644 --- a/Polygon_mesh_processing/test/Polygon_mesh_processing/test_predicates.cpp +++ b/Polygon_mesh_processing/test/Polygon_mesh_processing/test_predicates.cpp @@ -43,7 +43,7 @@ void check_triangle_face_degeneracy(const char* fname) CGAL_assertion(!CGAL::Polygon_mesh_processing::is_degenerate_triangle_face(all_faces[3], mesh)); } -// temp left here: tests repair.h +// tests repair.h void test_vertices_merge_and_duplication(const char* fname) { std::ifstream input(fname); @@ -62,10 +62,13 @@ void test_vertices_merge_and_duplication(const char* fname) const std::size_t vertices_after_merge = vertices(mesh).size(); CGAL_assertion(vertices_after_merge == initial_vertices - 1); - CGAL::Polygon_mesh_processing::duplicate_non_manifold_vertices(mesh); - const std::size_t final_vertices = vertices(mesh).size(); - CGAL_assertion(final_vertices == vertices_after_merge + 1); - CGAL_assertion(final_vertices == initial_vertices); + std::vector< std::vector > duplicated_vertices; + CGAL::Polygon_mesh_processing::duplicate_non_manifold_vertices(mesh, + CGAL::parameters::output_iterator(std::back_inserter(duplicated_vertices))); + const std::size_t final_vertices_size = vertices(mesh).size(); + CGAL_assertion(final_vertices_size == vertices_after_merge + 1); + CGAL_assertion(final_vertices_size == initial_vertices); + CGAL_assertion(duplicated_vertices.size() == 2); } void test_vertex_non_manifoldness(const char* fname) From 99db9a0aaf22e5b6774ed7e1c9d617a013cea5b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Loriot?= Date: Fri, 20 Apr 2018 11:50:54 +0200 Subject: [PATCH 021/116] WIP correctly linking halfedges around merged vertices ... also disable the merge between cycles as it is not straight forward it will be always possible --- .../merge_border_vertices.h | 154 +++++++++++++++--- .../test_merging_border_vertices.cpp | 7 +- 2 files changed, 132 insertions(+), 29 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/merge_border_vertices.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/merge_border_vertices.h index 3435e5fd7bd..0dc8560cad5 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/merge_border_vertices.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/merge_border_vertices.h @@ -37,6 +37,7 @@ namespace Polygon_mesh_processing{ namespace internal { +#if 0 // warning: vertices will be altered (sorted) template void detect_identical_vertices(std::vector& vertices, @@ -76,11 +77,77 @@ void detect_identical_vertices(std::vector& vertices, ++i; } } +#endif + +template +struct Less_on_point_of_target +{ + typedef typename boost::graph_traits::halfedge_descriptor + halfedge_descriptor; + typedef typename boost::property_traits::reference Point; + + Less_on_point_of_target(const PM& pm, + const VertexPointMap& vpm) + : pm(pm), + vpm(vpm) + {} + + bool operator()(halfedge_descriptor h1, + halfedge_descriptor h2) const + { + return get(vpm, target(h1, pm)) < get(vpm, target(h2, pm)); + } + + const PM& pm; + const VertexPointMap& vpm; +}; + + +// warning: cycle_hedges will be altered (sorted) +template +void detect_identical_vertices(std::vector& cycle_hedges, + std::vector< std::vector >& hedges_with_identical_point_target, + const PolygonMesh& pm, + Vpm vpm) +{ + // sort vertices using their point to ease the detection + // of vertices with identical points + Less_on_point_of_target less(pm, vpm); + std::sort( cycle_hedges.begin(), cycle_hedges.end(), less); + + std::size_t nbv=cycle_hedges.size(); + std::size_t i=1; + + while(i!=nbv) + { + if ( get(vpm, target(cycle_hedges[i], pm)) == + get(vpm, target(cycle_hedges[i-1], pm)) ) + { + hedges_with_identical_point_target.push_back( std::vector() ); + hedges_with_identical_point_target.back().push_back(cycle_hedges[i-1]); + hedges_with_identical_point_target.back().push_back(cycle_hedges[i]); + while(++i!=nbv) + { + if ( get(vpm, target(cycle_hedges[i], pm)) == + get(vpm, target(cycle_hedges[i-1], pm)) ) + hedges_with_identical_point_target.back().push_back(cycle_hedges[i]); + else + { + ++i; + break; + } + } + } + else + ++i; + } +} } // end of internal /// \todo document me /// It should probably go into BGL package +/// It should make sense to also return the length of each cycle template OutputIterator extract_boundary_cycles(PolygonMesh& pm, @@ -103,33 +170,44 @@ extract_boundary_cycles(PolygonMesh& pm, /// \ingroup PMP_repairing_grp /// \todo document me -template -void merge_boundary_vertices(const VertexRange& vertices, - PolygonMesh& pm) +/// we merge the all the target of the halfedges in `hedges` +/// hedges must be sorted along the cycle +template +void merge_boundary_vertices_in_cycle(const HalfedgeRange& sorted_hedges, + PolygonMesh& pm) { typedef typename boost::graph_traits::halfedge_descriptor halfedge_descriptor; typedef typename boost::graph_traits::vertex_descriptor vertex_descriptor; - vertex_descriptor v_kept=*boost::begin(vertices); + halfedge_descriptor in_h_kept = *boost::begin(sorted_hedges); + halfedge_descriptor out_h_kept = next(in_h_kept, pm); + vertex_descriptor v_kept=target(in_h_kept, pm); + std::vector vertices_to_rm; - BOOST_FOREACH(vertex_descriptor vd, vertices) + BOOST_FOREACH(halfedge_descriptor in_h_rm, sorted_hedges) { - if (vd==v_kept) continue; // skip identical vertices + vertex_descriptor vd = target(in_h_rm, pm); + if (vd==v_kept) continue; // skip identical vertices (in particular this skips the first halfedge) if (edge(vd, v_kept, pm).second) continue; // skip null edges bool shall_continue=false; - BOOST_FOREACH(halfedge_descriptor hd, halfedges_around_target(v_kept, pm)) + BOOST_FOREACH(halfedge_descriptor h, halfedges_around_target(v_kept, pm)) { - if (edge(vd, source(hd, pm), pm).second) + if (edge(vd, source(h, pm), pm).second) { shall_continue=true; break; } } if (shall_continue) continue; // skip vertices already incident to the same vertex - - internal::update_target_vertex(halfedge(vd, pm), v_kept, pm); + // update the vertex of the halfedges incident to the vertex to remove + internal::update_target_vertex(in_h_rm, v_kept, pm); + // update next/prev pointers around the 2 vertices to be merged + halfedge_descriptor out_h_rm = next(in_h_rm, pm); + set_next(in_h_kept, out_h_rm, pm); + set_next(in_h_rm, out_h_kept, pm); vertices_to_rm.push_back(vd); + out_h_kept=out_h_rm; } BOOST_FOREACH(vertex_descriptor vd, vertices_to_rm) @@ -139,30 +217,54 @@ void merge_boundary_vertices(const VertexRange& vertices, /// \ingroup PMP_repairing_grp /// \todo document me template -void merge_duplicated_vertices_in_boundary_cycle(typename boost::graph_traits::halfedge_descriptor h, - PolygonMesh& pm, - const NamedParameter& np) +void merge_duplicated_vertices_in_boundary_cycle( + typename boost::graph_traits::halfedge_descriptor h, + PolygonMesh& pm, + const NamedParameter& np) { typedef typename boost::graph_traits::halfedge_descriptor halfedge_descriptor; - typedef typename boost::graph_traits::vertex_descriptor vertex_descriptor; typedef typename GetVertexPointMap::const_type Vpm; Vpm vpm = choose_param(get_param(np, internal_np::vertex_point), get_const_property_map(vertex_point, pm)); - // collect all the vertices of the cycle - std::vector vertices; + // collect all the halfedges of the cycle + std::vector cycle_hedges; halfedge_descriptor start=h; do{ - vertices.push_back(target(h,pm)); + cycle_hedges.push_back(h); h=next(h, pm); }while(start!=h); - std::vector< std::vector > identical_vertices; - internal::detect_identical_vertices(vertices, identical_vertices, vpm); + std::vector< std::vector > hedges_with_identical_point_target; + internal::detect_identical_vertices(cycle_hedges, hedges_with_identical_point_target, pm, vpm); - BOOST_FOREACH(const std::vector& vrtcs, identical_vertices) - merge_boundary_vertices(vrtcs, pm); + BOOST_FOREACH(const std::vector& hedges, + hedges_with_identical_point_target) + { + start=hedges.front(); + // collect all halfedges in the cycle + std::vector sorted_hedges; + h=start; + do{ + sorted_hedges.push_back(h); + do + { + h=next(h, pm); + } + while( get(vpm, target(h, pm)) != get(vpm, target(start, pm)) ); + } + while(h!=start); + + if (sorted_hedges.size() != hedges.size()) + { + std::cerr << "WARNING: cycle broken at " << get(vpm, target(start, pm)) << ". Skipped\n"; + std::cout << sorted_hedges.size() << " vs " << hedges.size() << "\n"; + CGAL_assertion(sorted_hedges.size() == hedges.size()); + continue; + } + merge_boundary_vertices_in_cycle(sorted_hedges, pm); + } } /// \ingroup PMP_repairing_grp @@ -180,7 +282,7 @@ void merge_duplicated_vertices_in_boundary_cycles( PolygonMesh& pm, merge_duplicated_vertices_in_boundary_cycle(h, pm, np); } - +#if 0 /// \ingroup PMP_repairing_grp /// \todo document me template @@ -207,8 +309,7 @@ void merge_duplicated_boundary_vertices( PolygonMesh& pm, BOOST_FOREACH(const std::vector& vrtcs, identical_vertices) merge_boundary_vertices(vrtcs, pm); } - - +#endif template void merge_duplicated_vertices_in_boundary_cycles(PolygonMesh& pm) @@ -221,15 +322,16 @@ void merge_duplicated_vertices_in_boundary_cycle( typename boost::graph_traits::halfedge_descriptor h, PolygonMesh& pm) { - merge_duplicated_vertices_in_boundary_cycles(h, pm, parameters::all_default()); + merge_duplicated_vertices_in_boundary_cycle(h, pm, parameters::all_default()); } +#if 0 template void merge_duplicated_boundary_vertices(PolygonMesh& pm) { merge_duplicated_boundary_vertices(pm, parameters::all_default()); } - +#endif } } // end of CGAL::Polygon_mesh_processing diff --git a/Polygon_mesh_processing/test/Polygon_mesh_processing/test_merging_border_vertices.cpp b/Polygon_mesh_processing/test/Polygon_mesh_processing/test_merging_border_vertices.cpp index 1abc453207a..a3052bbd2fd 100644 --- a/Polygon_mesh_processing/test/Polygon_mesh_processing/test_merging_border_vertices.cpp +++ b/Polygon_mesh_processing/test/Polygon_mesh_processing/test_merging_border_vertices.cpp @@ -38,6 +38,7 @@ void test_merge_duplicated_vertices_in_boundary_cycles(const char* fname, } } +#if 0 void test_merge_duplicated_boundary_vertices(const char* fname, std::size_t expected_nb_vertices) { @@ -64,21 +65,21 @@ void test_merge_duplicated_boundary_vertices(const char* fname, output << mesh; } } - +#endif int main(int argc, char** argv) { if (argc==1) { test_merge_duplicated_vertices_in_boundary_cycles("data/merge_points.off", 43); - test_merge_duplicated_boundary_vertices("data/merge_points.off", 40); + // test_merge_duplicated_boundary_vertices("data/merge_points.off", 40); } else { for (int i=1; i< argc; ++i) { test_merge_duplicated_vertices_in_boundary_cycles(argv[i], 0); - test_merge_duplicated_boundary_vertices(argv[i], 0); + // test_merge_duplicated_boundary_vertices(argv[i], 0); } } return 0; From ee3636d57eb86c59b3ad1c5267576dbeed5f86e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Loriot?= Date: Fri, 20 Apr 2018 14:01:24 +0200 Subject: [PATCH 022/116] directly sort halfedges and use the ordering to detect illegal merges a merge is considered as illegal if it makes to vertices to be merged unreachable. For now if a cycle contain an illegal merge, all merges of the cycle are ignored. --- .../merge_border_vertices.h | 93 +++++++++++-------- 1 file changed, 55 insertions(+), 38 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/merge_border_vertices.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/merge_border_vertices.h index 0dc8560cad5..4a3ce2f8406 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/merge_border_vertices.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/merge_border_vertices.h @@ -92,10 +92,14 @@ struct Less_on_point_of_target vpm(vpm) {} - bool operator()(halfedge_descriptor h1, - halfedge_descriptor h2) const + bool operator()(const std::pair& h1, + const std::pair& h2) const { - return get(vpm, target(h1, pm)) < get(vpm, target(h2, pm)); + if ( get(vpm, target(h1.first, pm)) < get(vpm, target(h2.first, pm)) ) + return true; + if ( get(vpm, target(h1.first, pm)) > get(vpm, target(h2.first, pm)) ) + return false; + return h1.second < h2.second; } const PM& pm; @@ -105,10 +109,11 @@ struct Less_on_point_of_target // warning: cycle_hedges will be altered (sorted) template -void detect_identical_vertices(std::vector& cycle_hedges, - std::vector< std::vector >& hedges_with_identical_point_target, - const PolygonMesh& pm, - Vpm vpm) +void detect_identical_mergeable_vertices( + std::vector< std::pair >& cycle_hedges, + std::vector< std::vector >& hedges_with_identical_point_target, + const PolygonMesh& pm, + Vpm vpm) { // sort vertices using their point to ease the detection // of vertices with identical points @@ -118,19 +123,27 @@ void detect_identical_vertices(std::vector& cycle_hedges, std::size_t nbv=cycle_hedges.size(); std::size_t i=1; + std::set< std::pair > intervals; + while(i!=nbv) { - if ( get(vpm, target(cycle_hedges[i], pm)) == - get(vpm, target(cycle_hedges[i-1], pm)) ) + if ( get(vpm, target(cycle_hedges[i].first, pm)) == + get(vpm, target(cycle_hedges[i-1].first, pm)) ) { hedges_with_identical_point_target.push_back( std::vector() ); - hedges_with_identical_point_target.back().push_back(cycle_hedges[i-1]); - hedges_with_identical_point_target.back().push_back(cycle_hedges[i]); + hedges_with_identical_point_target.back().push_back(cycle_hedges[i-1].first); + hedges_with_identical_point_target.back().push_back(cycle_hedges[i].first); + intervals.insert( std::make_pair(cycle_hedges[i-1].second, cycle_hedges[i].second) ); + std::size_t previous = cycle_hedges[i].second; while(++i!=nbv) { - if ( get(vpm, target(cycle_hedges[i], pm)) == - get(vpm, target(cycle_hedges[i-1], pm)) ) - hedges_with_identical_point_target.back().push_back(cycle_hedges[i]); + if ( get(vpm, target(cycle_hedges[i].first, pm)) == + get(vpm, target(cycle_hedges[i-1].first, pm)) ) + { + hedges_with_identical_point_target.back().push_back(cycle_hedges[i].first); + intervals.insert( std::make_pair(previous, cycle_hedges[i].second) ); + previous = cycle_hedges[i].second; + } else { ++i; @@ -141,6 +154,27 @@ void detect_identical_vertices(std::vector& cycle_hedges, else ++i; } + + // check that intervals are disjoint or strictly nested + // if there is only one issue we drop the whole cycle. + /// \todo shall we try to be more conservative? + if (hedges_with_identical_point_target.empty()) return; + std::set< std::pair >::iterator it1 = intervals.begin(), + end2 = intervals.end(), + end1 = cpp11::prev(end2), + it2; + for (; it1!=end1; ++it1) + for(it2=cpp11::next(it1); it2!= end2; ++it2 ) + { + CGAL_assertion(it1->firstfirst); + CGAL_assertion(it1->first < it1->second && it2->first < it2->second); + if (it1->second > it2->first && it2->second > it1->second) + { + std::cerr << "Merging is skipt to avoid bad cycle connections\n"; + hedges_with_identical_point_target.clear(); + return; + } + } } } // end of internal @@ -229,41 +263,24 @@ void merge_duplicated_vertices_in_boundary_cycle( get_const_property_map(vertex_point, pm)); // collect all the halfedges of the cycle - std::vector cycle_hedges; + std::vector< std::pair > cycle_hedges; halfedge_descriptor start=h; + std::size_t index=0; do{ - cycle_hedges.push_back(h); + cycle_hedges.push_back( std::make_pair(h, index) ); h=next(h, pm); + ++index; }while(start!=h); std::vector< std::vector > hedges_with_identical_point_target; - internal::detect_identical_vertices(cycle_hedges, hedges_with_identical_point_target, pm, vpm); + internal::detect_identical_mergeable_vertices(cycle_hedges, hedges_with_identical_point_target, pm, vpm); BOOST_FOREACH(const std::vector& hedges, hedges_with_identical_point_target) { start=hedges.front(); - // collect all halfedges in the cycle - std::vector sorted_hedges; - h=start; - do{ - sorted_hedges.push_back(h); - do - { - h=next(h, pm); - } - while( get(vpm, target(h, pm)) != get(vpm, target(start, pm)) ); - } - while(h!=start); - - if (sorted_hedges.size() != hedges.size()) - { - std::cerr << "WARNING: cycle broken at " << get(vpm, target(start, pm)) << ". Skipped\n"; - std::cout << sorted_hedges.size() << " vs " << hedges.size() << "\n"; - CGAL_assertion(sorted_hedges.size() == hedges.size()); - continue; - } - merge_boundary_vertices_in_cycle(sorted_hedges, pm); + // hedges are sorted along the cycle + merge_boundary_vertices_in_cycle(hedges, pm); } } From e299309a22a9598a6052fec3a75542113cdf2a63 Mon Sep 17 00:00:00 2001 From: Konstantinos Katrioplas Date: Tue, 22 May 2018 13:14:12 +0200 Subject: [PATCH 023/116] add missing named parameter documentation --- .../doc/Polygon_mesh_processing/NamedParameters.txt | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/Polygon_mesh_processing/doc/Polygon_mesh_processing/NamedParameters.txt b/Polygon_mesh_processing/doc/Polygon_mesh_processing/NamedParameters.txt index 839bd7b52ff..002f2a837bb 100644 --- a/Polygon_mesh_processing/doc/Polygon_mesh_processing/NamedParameters.txt +++ b/Polygon_mesh_processing/doc/Polygon_mesh_processing/NamedParameters.txt @@ -365,6 +365,17 @@ should be considered as part of the clipping volume or not. \cgalNPEnd +\cgalNPBegin{output_iterator} \anchor PMP_output_iterator +Iterator where `std::vector` can be put. +The first vertex of the vector is an input vertex that was non-manifold, +the other vertices in the vertex are the new vertices created to fix +the non-manifoldness. +\n +\b Type : `iterator` \n +\b Default `Emptyset_iterator` +\cgalNPEnd + + \cgalNPTableEnd */ From b51fa000a47e41e078f259d07795393c33141e31 Mon Sep 17 00:00:00 2001 From: Konstantinos Katrioplas Date: Wed, 23 May 2018 12:17:46 +0200 Subject: [PATCH 024/116] documentation on merge border vertices functions --- .../merge_border_vertices.h | 62 ++++++++++++++++--- 1 file changed, 54 insertions(+), 8 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/merge_border_vertices.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/merge_border_vertices.h index 4a3ce2f8406..2406c6d72de 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/merge_border_vertices.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/merge_border_vertices.h @@ -179,9 +179,18 @@ void detect_identical_mergeable_vertices( } // end of internal -/// \todo document me -/// It should probably go into BGL package -/// It should make sense to also return the length of each cycle +/// \ingroup PMP_repairing_grp +/// extracts boundary cycles as a list of halfedges. +/// @tparam PolygonMesh a model of `FaceListGraph` and `MutableFaceGraph`. +/// @tparam OutputIterator a model of `OutputIterator` holding objects of type +/// `boost::graph_traits::%halfedge_descriptor` +/// +/// @param pm the polygon mesh. +/// @param out an output iterator where the list of halfedges will be put. +/// +/// @todo Maybe move to BGL +/// @todo It should make sense to also return the length of each cycle. +/// @todo It should probably go into BGL package. template OutputIterator extract_boundary_cycles(PolygonMesh& pm, @@ -203,9 +212,16 @@ extract_boundary_cycles(PolygonMesh& pm, } /// \ingroup PMP_repairing_grp -/// \todo document me -/// we merge the all the target of the halfedges in `hedges` -/// hedges must be sorted along the cycle +/// merges target vertices of a list of halfedges. +/// Halfedges must be sorted in the list. +/// +/// @tparam PolygonMesh a model of `FaceListGraph` and `MutableFaceGraph`. +/// @tparam HalfedgeRange a range of halfedge descriptors of `PolygonMesh`, model of `Range`. +/// +/// @param sorted_hedges a sorted list of halfedges. +/// @param pm the polygon mesh which contains the list of halfedges. +/// +/// @todo rename me to `merge_vertices_in_range` because I merge any king of vertices in the list. template void merge_boundary_vertices_in_cycle(const HalfedgeRange& sorted_hedges, PolygonMesh& pm) @@ -249,7 +265,22 @@ void merge_boundary_vertices_in_cycle(const HalfedgeRange& sorted_hedges, } /// \ingroup PMP_repairing_grp -/// \todo document me +/// merges identical vertices around a cycle of connected edges. +/// +/// @tparam PolygonMesh a model of `FaceListGraph` and `MutableFaceGraph`. +/// @tparam NamedParameter a sequence of \ref pmp_namedparameters "Named Parameters". +/// +/// @param h a halfedge that belongs to the cycle. +/// @param pm the polygon mesh which containts the cycle. +/// @param np optional parameter of \ref pmp_namedparameters "Named Parameters" listed below. +/// +/// \cgalNamedParamsBegin +/// \cgalParamBegin{vertex_point_map} +/// the property map with the points associated to the vertices of `pm`. +/// If this parameter is omitted, an internal property map for +/// `CGAL::vertex_point_t` should be available in `PolygonMesh` +/// \cgalParamEnd +/// \cgalNamedParamsEnd template void merge_duplicated_vertices_in_boundary_cycle( typename boost::graph_traits::halfedge_descriptor h, @@ -285,7 +316,22 @@ void merge_duplicated_vertices_in_boundary_cycle( } /// \ingroup PMP_repairing_grp -/// \todo document me +/// extracts boundary cycles and merges the duplicated +/// vertices of each cycle. +/// +/// @tparam PolygonMesh a model of `FaceListGraph` and `MutableFaceGraph`. +/// @tparam NamedParameter a sequence of \ref pmp_namedparameters "Named Parameters". +/// +/// @param pm the polygon mesh which containts the cycle. +/// @param np optional parameter of \ref pmp_namedparameters "Named Parameters" listed below. +/// +/// \cgalNamedParamsBegin +/// \cgalParamBegin{vertex_point_map} +/// the property map with the points associated to the vertices of `pm`. +/// If this parameter is omitted, an internal property map for +/// `CGAL::vertex_point_t` should be available in `PolygonMesh` +/// \cgalParamEnd +/// \cgalNamedParamsEnd template void merge_duplicated_vertices_in_boundary_cycles( PolygonMesh& pm, const NamedParameter& np) From aed0cb1834060135476ffc921525e237502e7a89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Loriot?= Date: Tue, 3 Jul 2018 15:47:35 +0200 Subject: [PATCH 025/116] remove extra comma --- .../internal/Isotropic_remeshing/remesh_impl.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Isotropic_remeshing/remesh_impl.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Isotropic_remeshing/remesh_impl.h index 9686f4c271b..8826e7be23c 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Isotropic_remeshing/remesh_impl.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Isotropic_remeshing/remesh_impl.h @@ -365,7 +365,7 @@ namespace internal { BOOST_FOREACH(face_descriptor f, face_range) { - if (is_degenerate_triangle_face(f, mesh_,)){ + if (is_degenerate_triangle_face(f, mesh_)){ continue; } Patch_id pid = get_patch_id(f); From 0c9fea5d28f0dd5b543d1d073178fca81ec1a134 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mael=20Rouxel-Labb=C3=A9?= Date: Thu, 19 Jul 2018 17:03:30 +0200 Subject: [PATCH 026/116] Fixed new named parameter test --- BGL/test/BGL/test_cgal_bgl_named_params.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BGL/test/BGL/test_cgal_bgl_named_params.cpp b/BGL/test/BGL/test_cgal_bgl_named_params.cpp index c0081450d1e..6bd99f2bdc0 100644 --- a/BGL/test/BGL/test_cgal_bgl_named_params.cpp +++ b/BGL/test/BGL/test_cgal_bgl_named_params.cpp @@ -163,7 +163,7 @@ void test(const NamedParameters& np) check_same_type<41>(get_param(np, CGAL::internal_np::verbosity_level)); check_same_type<42>(get_param(np, CGAL::internal_np::projection_functor)); check_same_type<46>(get_param(np, CGAL::internal_np::apply_per_connected_component)); - check_same_type472>(get_param(np, CGAL::internal_np::output_iterator)); + check_same_type<47>(get_param(np, CGAL::internal_np::output_iterator)); } int main() From 3b9464f54974b53bb230ee9c63861dffd06214e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mael=20Rouxel-Labb=C3=A9?= Date: Thu, 19 Jul 2018 17:05:07 +0200 Subject: [PATCH 027/116] Replaced No_constraint_pmap with Constant_property_map --- .../CGAL/Polygon_mesh_processing/helpers.h | 16 ----------- .../Isotropic_remeshing/remesh_impl.h | 17 +----------- .../random_perturbation.h | 4 +-- .../CGAL/Polygon_mesh_processing/remesh.h | 27 +++++++++---------- .../CGAL/Polygon_mesh_processing/repair.h | 7 ++--- Property_map/include/CGAL/property_map.h | 3 ++- 6 files changed, 22 insertions(+), 52 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/helpers.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/helpers.h index ea38a91b835..b41c37e1c80 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/helpers.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/helpers.h @@ -25,28 +25,12 @@ #include #include - namespace CGAL { namespace Polygon_mesh_processing { namespace internal { -template -struct No_constraint_pmap -{ -public: - typedef Descriptor key_type; - typedef bool value_type; - typedef value_type& reference; - typedef boost::read_write_property_map_tag category; - - friend bool get(const No_constraint_pmap& , const key_type& ) { - return false; - } - friend void put(No_constraint_pmap& , const key_type& , const bool ) {} -}; - template struct Vertex_collector { diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Isotropic_remeshing/remesh_impl.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Isotropic_remeshing/remesh_impl.h index 8826e7be23c..428d49a6309 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Isotropic_remeshing/remesh_impl.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Isotropic_remeshing/remesh_impl.h @@ -91,21 +91,6 @@ namespace internal { }; // A property map - template - struct No_constraint_pmap - { - public: - typedef Descriptor key_type; - typedef bool value_type; - typedef value_type& reference; - typedef boost::read_write_property_map_tag category; - - friend bool get(const No_constraint_pmap& , const key_type& ) { - return false; - } - friend void put(No_constraint_pmap& , const key_type& , const bool ) {} - }; - template struct Border_constraint_pmap { @@ -1514,7 +1499,7 @@ private: // update status using constrained edge map if (!boost::is_same >::value) + Constant_property_map >::value) { BOOST_FOREACH(edge_descriptor e, edges(mesh_)) { diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/random_perturbation.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/random_perturbation.h index ea7362e1a95..8a210843b1e 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/random_perturbation.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/random_perturbation.h @@ -174,10 +174,10 @@ void random_perturbation(VertexRange vertices typedef typename boost::lookup_named_param_def < internal_np::vertex_is_constrained_t, NamedParameters, - internal::No_constraint_pmap//default + Constant_property_map // default > ::type VCMap; VCMap vcmap = choose_param(get_param(np, internal_np::vertex_is_constrained), - internal::No_constraint_pmap()); + Constant_property_map(false)); unsigned int seed = choose_param(get_param(np, internal_np::random_seed), -1); bool do_project = choose_param(get_param(np, internal_np::do_project), true); diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/remesh.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/remesh.h index d87cd651b6a..40994d53a87 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/remesh.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/remesh.h @@ -175,18 +175,18 @@ void isotropic_remeshing(const FaceRange& faces typedef typename boost::lookup_named_param_def < internal_np::edge_is_constrained_t, NamedParameters, - internal::No_constraint_pmap//default + Constant_property_map // default (no constraint pmap) > ::type ECMap; - ECMap ecmap = choose_param(get_param(np, internal_np::edge_is_constrained) - , internal::No_constraint_pmap()); + ECMap ecmap = choose_param(get_param(np, internal_np::edge_is_constrained), + Constant_property_map(false)); typedef typename boost::lookup_named_param_def < internal_np::vertex_is_constrained_t, NamedParameters, - internal::No_constraint_pmap//default + Constant_property_map // default (no constraint pmap) > ::type VCMap; VCMap vcmap = choose_param(get_param(np, internal_np::vertex_is_constrained), - internal::No_constraint_pmap()); + Constant_property_map(false)); bool protect = choose_param(get_param(np, internal_np::protect_constraints), false); typedef typename boost::lookup_named_param_def < @@ -351,22 +351,21 @@ void split_long_edges(const EdgeRange& edges typedef typename boost::lookup_named_param_def < internal_np::edge_is_constrained_t, NamedParameters, - internal::No_constraint_pmap//default + Constant_property_map // default (no constraint pmap) > ::type ECMap; ECMap ecmap = choose_param(get_param(np, internal_np::edge_is_constrained), - internal::No_constraint_pmap()); + Constant_property_map(false)); typename internal::Incremental_remesher, + Constant_property_map, // no constraint pmap internal::Connected_components_pmap, FIMap > - remesher(pmesh, vpmap, false/*protect constraints*/ - , ecmap - , internal::No_constraint_pmap() - , internal::Connected_components_pmap(faces(pmesh), pmesh, ecmap, fimap, false) - , fimap - , false/*need aabb_tree*/); + remesher(pmesh, vpmap, false/*protect constraints*/, ecmap, + Constant_property_map(false), + internal::Connected_components_pmap(faces(pmesh), pmesh, ecmap, fimap, false), + fimap, + false/*need aabb_tree*/); remesher.split_long_edges(edges, max_length); } diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/repair.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/repair.h index b5579b5204b..e03c785e990 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/repair.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/repair.h @@ -24,12 +24,13 @@ #include - #include #include + #include #include #include +#include #include #include @@ -1319,11 +1320,11 @@ std::size_t duplicate_non_manifold_vertices(TriangleMesh& tm, typedef typename boost::lookup_named_param_def < internal_np::vertex_is_constrained_t, NamedParameters, - internal::No_constraint_pmap//default + Constant_property_map // default (no constraint pmap) > ::type VerticesMap; VerticesMap cmap = choose_param(get_param(np, internal_np::vertex_is_constrained), - internal::No_constraint_pmap()); + Constant_property_map(false)); typedef typename boost::lookup_named_param_def < internal_np::output_iterator_t, diff --git a/Property_map/include/CGAL/property_map.h b/Property_map/include/CGAL/property_map.h index f589496dcba..737c82320c3 100644 --- a/Property_map/include/CGAL/property_map.h +++ b/Property_map/include/CGAL/property_map.h @@ -462,10 +462,11 @@ make_property_map(const std::vector& v) template struct Constant_property_map { - const ValueType default_value; + ValueType default_value; typedef KeyType key_type; typedef ValueType value_type; + typedef value_type& reference; typedef boost::read_write_property_map_tag category; Constant_property_map(const value_type& default_value = value_type()) : default_value (default_value) { } From fc41d58bfdc150d9cffeca0e0f804445d0eac0ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mael=20Rouxel-Labb=C3=A9?= Date: Fri, 20 Jul 2018 12:16:13 +0200 Subject: [PATCH 028/116] Added some missing \sa for Vector_23 --- Kernel_23/doc/Kernel_23/Concepts/GeomObjects.h | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Kernel_23/doc/Kernel_23/Concepts/GeomObjects.h b/Kernel_23/doc/Kernel_23/Concepts/GeomObjects.h index b120d75b38c..034890bb56c 100644 --- a/Kernel_23/doc/Kernel_23/Concepts/GeomObjects.h +++ b/Kernel_23/doc/Kernel_23/Concepts/GeomObjects.h @@ -721,6 +721,8 @@ public: \cgalHasModel `CGAL::Vector_2` \sa `Kernel::ComputeDeterminant_2` + \sa `Kernel::ComputeScalarProduct_2` + \sa `Kernel::ComputeSquaredLength_2` \sa `Kernel::ComputeX_2` \sa `Kernel::ComputeY_2` \sa `Kernel::ComputeHx_2` @@ -753,7 +755,10 @@ A type representing vectors in three dimensions. \cgalHasModel `CGAL::Vector_3` -\sa `Kernel::ComputeDeterminant_3` +\sa `Kernel::CompareDihedralAngle_3` +\sa `Kernel::ComputeDeterminant_3` +\sa `Kernel::ComputeScalarProduct_3` +\sa `Kernel::ComputeSquaredLength_3` \sa `Kernel::ComputeX_3` \sa `Kernel::ComputeY_3` \sa `Kernel::ComputeZ_3` From 49a971e9c2ae57864bbafccfc0b65c3a535c8a2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mael=20Rouxel-Labb=C3=A9?= Date: Fri, 20 Jul 2018 17:30:40 +0200 Subject: [PATCH 029/116] Various improvements/fixes to degenerate/needle/cap functions --- .../NamedParameters.txt | 13 +- .../CGAL/Polygon_mesh_processing/helpers.h | 352 +++++++++--------- .../Isotropic_remeshing/remesh_impl.h | 88 ++--- .../CGAL/Polygon_mesh_processing/remesh.h | 7 +- .../CGAL/Polygon_mesh_processing/repair.h | 122 ++++-- .../data_degeneracies/caps_and_needles.off | 17 + .../test_predicates.cpp | 244 ++++++++---- .../Plugins/PMP/Selection_plugin.cpp | 4 +- .../demo/Polyhedron/Scene_polyhedron_item.cpp | 2 +- .../Scene_polyhedron_selection_item.cpp | 3 +- .../Polyhedron/Scene_surface_mesh_item.cpp | 2 +- .../include/CGAL/statistics_helpers.h | 25 +- 12 files changed, 519 insertions(+), 360 deletions(-) create mode 100644 Polygon_mesh_processing/test/Polygon_mesh_processing/data_degeneracies/caps_and_needles.off diff --git a/Polygon_mesh_processing/doc/Polygon_mesh_processing/NamedParameters.txt b/Polygon_mesh_processing/doc/Polygon_mesh_processing/NamedParameters.txt index 002f2a837bb..a2c5456db34 100644 --- a/Polygon_mesh_processing/doc/Polygon_mesh_processing/NamedParameters.txt +++ b/Polygon_mesh_processing/doc/Polygon_mesh_processing/NamedParameters.txt @@ -337,7 +337,7 @@ of a mesh independently.\n 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 Specific to the function visited +\b Default : Specific to the function visited \cgalNPEnd \cgalNPBegin{throw_on_self_intersection} \anchor PMP_throw_on_self_intersection @@ -364,18 +364,13 @@ should be considered as part of the clipping volume or not. \b Default value is `true` \cgalNPEnd - \cgalNPBegin{output_iterator} \anchor PMP_output_iterator -Iterator where `std::vector` can be put. -The first vertex of the vector is an input vertex that was non-manifold, -the other vertices in the vertex are the new vertices created to fix -the non-manifoldness. +Parameter to pass an output iterator. \n -\b Type : `iterator` \n -\b Default `Emptyset_iterator` +\b Type : a model of `OutputIterator` \n +\b Default : `Emptyset_iterator` \cgalNPEnd - \cgalNPTableEnd */ diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/helpers.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/helpers.h index b41c37e1c80..8e9cfeef182 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/helpers.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/helpers.h @@ -1,4 +1,4 @@ -// Copyright (c) 2015 GeometryFactory (France). +// Copyright (c) 2015, 2018 GeometryFactory (France). // All rights reserved. // // This file is part of CGAL (www.cgal.org). @@ -17,7 +17,8 @@ // SPDX-License-Identifier: GPL-3.0+ // // -// Author(s) : Konstantinos Katrioplas +// Author(s) : Konstantinos Katrioplas, +// Mael Rouxel-Labbé #ifndef CGAL_POLYGON_MESH_PROCESSING_HELPERS_H #define CGAL_POLYGON_MESH_PROCESSING_HELPERS_H @@ -25,86 +26,40 @@ #include #include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include + namespace CGAL { namespace Polygon_mesh_processing { -namespace internal { - -template -struct Vertex_collector -{ - typedef typename boost::graph_traits::vertex_descriptor vertex_descriptor; - void collect_vertices(vertex_descriptor v1, vertex_descriptor v2) - { - std::vector& verts = collections[v1]; - if (verts.empty()) - verts.push_back(v1); - verts.push_back(v2); - } - - void dump(OutputIterator out) - { - typedef std::pair > Pair_type; - BOOST_FOREACH(const Pair_type& p, collections) - { - *out++=p.second; - } - } - - std::map > collections; -}; - -template -struct Vertex_collector -{ - typedef typename boost::graph_traits::vertex_descriptor vertex_descriptor; - void collect_vertices(vertex_descriptor, vertex_descriptor) - {} - - void dump(Emptyset_iterator) - {} -}; - -// used only for testing -template -void merge_identical_points(PolygonMesh& mesh, - typename boost::graph_traits::vertex_descriptor v_keep, - typename boost::graph_traits::vertex_descriptor v_rm) -{ - typedef typename boost::graph_traits::halfedge_descriptor halfedge_descriptor; - halfedge_descriptor h = halfedge(v_rm, mesh); - halfedge_descriptor start = h; - - do{ - set_target(h, v_keep, mesh); - h = opposite(next(h, mesh), mesh); - } while( h != start ); - - remove_vertex(v_rm, mesh); -} -} // end internal - /// \ingroup PMP_repairing_grp -/// checks whether a vertex is non-manifold. +/// checks whether a vertex of a triangle mesh is non-manifold. /// -/// @tparam PolygonMesh a model of `FaceListGraph` and `MutableFaceGraph` +/// @tparam TriangleMesh a model of `HalfedgeListGraph` /// -/// @param v the vertex -/// @param tm triangle mesh containing v +/// @param v a vertex of `tm` +/// @param tm a triangle mesh containing `v` /// -/// \return true if the vertrex is non-manifold -template -bool is_non_manifold_vertex(typename boost::graph_traits::vertex_descriptor v, - const PolygonMesh& tm) +/// \return `true` if the vertrex is non-manifold, `false` otherwise. +template +bool is_non_manifold_vertex(typename boost::graph_traits::vertex_descriptor v, + const TriangleMesh& tm) { CGAL_assertion(CGAL::is_triangle_mesh(tm)); - typedef boost::graph_traits GT; - typedef typename GT::halfedge_descriptor halfedge_descriptor; + typedef typename boost::graph_traits::halfedge_descriptor halfedge_descriptor; boost::unordered_set halfedges_handled; - BOOST_FOREACH(halfedge_descriptor h, halfedges_around_target(v, tm)) halfedges_handled.insert(h); @@ -121,28 +76,28 @@ bool is_non_manifold_vertex(typename boost::graph_traits::vertex_de /// \ingroup PMP_repairing_grp /// checks whether an edge is degenerate. -/// An edge is considered degenerate if the points of its vertices are identical. +/// An edge is considered degenerate if the geometric positions of its two extremities are identical. /// /// @tparam PolygonMesh a model of `HalfedgeGraph` /// @tparam NamedParameters a sequence of \ref pmp_namedparameters "Named Parameters" /// -/// @param e the edge -/// @param pm polygon mesh containing e +/// @param e an edge of `pm` +/// @param pm polygon mesh containing `e` /// @param np optional \ref pmp_namedparameters "Named Parameters" described below /// /// \cgalNamedParamsBegin -/// \cgalParamBegin{vertex_point_map} the property map with the points associated to the vertices of `pmesh`. The type of this map is model of `ReadWritePropertyMap`. -/// If this parameter is omitted, an internal property map for -/// `CGAL::vertex_point_t` should be available in `PolygonMesh` +/// \cgalParamBegin{vertex_point_map} the property map with the points associated to the vertices of `pm`. +/// The type of this map is model of `ReadWritePropertyMap`. +/// If this parameter is omitted, an internal property map for +/// `CGAL::vertex_point_t` should be available in `PolygonMesh` /// \cgalParamEnd -/// \cgalParamBegin{geom_traits} a geometric traits class instance. -/// The traits class must provide the nested type `Point_3`, -/// and the nested functor : -/// - `Equal_3` to check whether 2 points are identical +/// \cgalParamBegin{geom_traits} a geometric traits class instance. +/// The traits class must provide the nested type `Point_3`, +/// and the nested functor `Equal_3` to check whether two points are identical. /// \cgalParamEnd /// \cgalNamedParamsEnd /// -/// \return true if the edge is degenerate +/// \return `true` if the edge `e` is degenerate, `false` otherwise. template bool is_degenerate_edge(typename boost::graph_traits::edge_descriptor e, const PolygonMesh& pm, @@ -154,12 +109,11 @@ bool is_degenerate_edge(typename boost::graph_traits::edge_descript typedef typename GetVertexPointMap::const_type VertexPointMap; VertexPointMap vpmap = choose_param(get_param(np, internal_np::vertex_point), get_const_property_map(vertex_point, pm)); + typedef typename GetGeomTraits::type Traits; Traits traits = choose_param(get_param(np, internal_np::geom_traits), Traits()); - if ( traits.equal_3_object()(get(vpmap, target(e, pm)), get(vpmap, source(e, pm))) ) - return true; - return false; + return traits.equal_3_object()(get(vpmap, source(e, pm)), get(vpmap, target(e, pm))); } template @@ -171,34 +125,34 @@ bool is_degenerate_edge(typename boost::graph_traits::edge_descript /// \ingroup PMP_repairing_grp /// checks whether a triangle face is degenerate. -/// A triangle face is degenerate if its points are collinear. +/// A triangle face is considered degenerate if the geometric positions of its vertices are collinear. /// /// @tparam TriangleMesh a model of `FaceGraph` /// @tparam NamedParameters a sequence of \ref pmp_namedparameters "Named Parameters" /// -/// @param f the triangle face -/// @param tm triangle mesh containing f +/// @param f a triangle face of `tm` +/// @param tm a triangle mesh containing `f` /// @param np optional \ref pmp_namedparameters "Named Parameters" described below /// /// \cgalNamedParamsBegin -/// \cgalParamBegin{vertex_point_map} the property map with the points associated to the vertices of `pmesh`. The type of this map is model of `ReadWritePropertyMap`. -/// If this parameter is omitted, an internal property map for -/// `CGAL::vertex_point_t` should be available in `TriangleMesh` +/// \cgalParamBegin{vertex_point_map} the property map with the points associated to the vertices of `tm`. +/// The type of this map is model of `ReadWritePropertyMap`. +/// If this parameter is omitted, an internal property map for +/// `CGAL::vertex_point_t` should be available in `TriangleMesh` /// \cgalParamEnd /// \cgalParamBegin{geom_traits} a geometric traits class instance. -/// The traits class must provide the nested type `Point_3`, -/// and the nested functor : -/// - `Collinear_3` to check whether 3 points are collinear +/// The traits class must provide the nested functor `Collinear_3` +/// to check whether three points are collinear. /// \cgalParamEnd /// \cgalNamedParamsEnd /// -/// \return true if the triangle face is degenerate +/// \return `true` if the face `f` is degenerate, `false` otherwise. template bool is_degenerate_triangle_face(typename boost::graph_traits::face_descriptor f, const TriangleMesh& tm, const NamedParameters& np) { - CGAL_assertion(CGAL::is_triangle_mesh(tm)); + CGAL_precondition(CGAL::is_triangle_mesh(tm)); using boost::get_param; using boost::choose_param; @@ -206,15 +160,15 @@ bool is_degenerate_triangle_face(typename boost::graph_traits::fac typedef typename GetVertexPointMap::const_type VertexPointMap; VertexPointMap vpmap = choose_param(get_param(np, internal_np::vertex_point), get_const_property_map(vertex_point, tm)); + typedef typename GetGeomTraits::type Traits; Traits traits = choose_param(get_param(np, internal_np::geom_traits), Traits()); - typename boost::graph_traits::halfedge_descriptor hd = halfedge(f,tm); - const typename Traits::Point_3& p1 = get(vpmap, target( hd, tm) ); - const typename Traits::Point_3& p2 = get(vpmap, target(next(hd, tm), tm) ); - const typename Traits::Point_3& p3 = get(vpmap, source( hd, tm) ); - return traits.collinear_3_object()(p1, p2, p3); + typename boost::graph_traits::halfedge_descriptor h = halfedge(f, tm); + return traits.collinear_3_object()(get(vpmap, source(h, tm)), + get(vpmap, target(h, tm)), + get(vpmap, target(next(h, tm), tm))); } template @@ -226,159 +180,185 @@ bool is_degenerate_triangle_face(typename boost::graph_traits::fac /// \ingroup PMP_repairing_grp /// checks whether a triangle face is needle. -/// A triangle is needle if its longest edge is much longer than the shortest one. +/// A triangle is said to be a needle if its longest edge is much longer than its shortest edge. /// /// @tparam TriangleMesh a model of `FaceGraph` /// @tparam NamedParameters a sequence of \ref pmp_namedparameters "Named Parameters" /// -/// @param f the triangle face -/// @param tm triangle mesh containing f -/// @param threshold the cosine of an angle of f. -/// The threshold is in range [0 1] and corresponds to -/// angles between 0 and 90 degrees. +/// @param f a triangle face of `tm` +/// @param tm triangle mesh containing `f` +/// @param threshold a bound on the ratio of the longest edge length and the shortest edge length /// @param np optional \ref pmp_namedparameters "Named Parameters" described below /// /// \cgalNamedParamsBegin -/// \cgalParamBegin{vertex_point_map} the property map with the points associated to the vertices of `pmesh`. The type of this map is model of `ReadWritePropertyMap`. -/// If this parameter is omitted, an internal property map for -/// `CGAL::vertex_point_t` should be available in `TriangleMesh` +/// \cgalParamBegin{vertex_point_map} the property map with the points associated to the vertices of `tm`. +/// The type of this map is model of `ReadWritePropertyMap`. +/// If this parameter is omitted, an internal property map for +/// `CGAL::vertex_point_t` should be available in `TriangleMesh` /// \cgalParamEnd /// \cgalParamBegin{geom_traits} a geometric traits class instance. -/// The traits class must provide the nested type `Point_3`. +/// The traits class must provide the nested type `FT` and +/// the nested functor `Compute_squared_distance_3`. /// \cgalParamEnd /// \cgalNamedParamsEnd /// -/// \return true if the triangle face is a needle +/// \return the smallest halfedge if the triangle face is a needle, and a null halfedge otherwise. template -bool is_needle_triangle_face(typename boost::graph_traits::face_descriptor f, - const TriangleMesh& tm, - const double threshold, - const NamedParameters& np) +typename boost::graph_traits::halfedge_descriptor +is_needle_triangle_face(typename boost::graph_traits::face_descriptor f, + const TriangleMesh& tm, + const double threshold, + const NamedParameters& np) { - CGAL_assertion(CGAL::is_triangle_mesh(tm)); - CGAL_assertion(threshold >= 0); - CGAL_assertion(threshold <= 1); + CGAL_precondition(CGAL::is_triangle_mesh(tm)); + CGAL_precondition(threshold >= 1.); using boost::get_param; using boost::choose_param; + typedef typename boost::graph_traits::halfedge_descriptor halfedge_descriptor; + typedef typename GetVertexPointMap::const_type VertexPointMap; VertexPointMap vpmap = choose_param(get_param(np, internal_np::vertex_point), get_const_property_map(vertex_point, tm)); - typedef typename GetGeomTraits::type Traits; - typedef typename Traits::FT FT; - typedef boost::graph_traits GT; - typedef typename GT::halfedge_descriptor halfedge_descriptor; - typedef typename GT::vertex_descriptor vertex_descriptor; - typedef typename boost::property_traits::value_type Point_type; - typedef typename Kernel_traits::Kernel::Vector_3 Vector; - BOOST_FOREACH(halfedge_descriptor h, halfedges_around_face(halfedge(f, tm), tm)) + typedef typename GetGeomTraits::type Traits; + Traits traits = choose_param(get_param(np, internal_np::geom_traits), Traits()); + + typedef typename Traits::FT FT; + + const halfedge_descriptor h0 = halfedge(f, tm); + FT max_sq_length = - std::numeric_limits::max(), + min_sq_length = std::numeric_limits::max(); + halfedge_descriptor min_h = boost::graph_traits::null_halfedge(); + + BOOST_FOREACH(halfedge_descriptor h, halfedges_around_face(h0, tm)) { - vertex_descriptor v0 = source(h, tm); - vertex_descriptor v1 = target(h, tm); - vertex_descriptor v2 = target(next(h, tm), tm); - Vector a = get(vpmap, v0) - get (vpmap, v1); - Vector b = get(vpmap, v2) - get(vpmap, v1); - FT aa = a.squared_length(); - FT bb = b.squared_length(); - FT squared_dot_ab = ((a*b)*(a*b)) / (aa * bb); + const FT sq_length = traits.compute_squared_distance_3_object()(get(vpmap, source(h, tm)), + get(vpmap, target(h, tm))); - if(squared_dot_ab > threshold * threshold) - return true; + if(max_sq_length < sq_length) + max_sq_length = sq_length; + + if(min_sq_length > sq_length) + { + min_h = h; + min_sq_length = sq_length; + } } - return false; + const FT sq_threshold = threshold * threshold; + if(max_sq_length / min_sq_length >= sq_threshold) + { + CGAL_assertion(min_h != boost::graph_traits::null_halfedge()); + return min_h; + } + else + return boost::graph_traits::null_halfedge(); } template -bool is_needle_triangle_face(typename boost::graph_traits::face_descriptor f, - const TriangleMesh& tm, - const double threshold) +typename boost::graph_traits::halfedge_descriptor +is_needle_triangle_face(typename boost::graph_traits::face_descriptor f, + const TriangleMesh& tm, + const double threshold) { return is_needle_triangle_face(f, tm, threshold, parameters::all_default()); } /// \ingroup PMP_repairing_grp /// checks whether a triangle face is a cap. -/// A triangle is a cap if it has an angle very close to 180 degrees. +/// A triangle is said to be a cap if one of the its angles is close to `180` degrees. /// /// @tparam TriangleMesh a model of `FaceGraph` /// @tparam NamedParameters a sequence of \ref pmp_namedparameters "Named Parameters" /// -/// @param f the triangle face -/// @param tm triangle mesh containing f -/// @param threshold the cosine of an angle of f. -/// The threshold is in range [-1 0] and corresponds to -/// angles between 90 and 180 degrees. +/// @param f a triangle face of `tm` +/// @param tm triangle mesh containing `f` +/// @param threshold the cosine of a minimum angle such that if `f` has an angle greater than this bound, +/// it is a cap. The threshold is in range `[-1 0]` and corresponds to an angle +/// between `90` and `180` degrees. /// @param np optional \ref pmp_namedparameters "Named Parameters" described below /// /// \cgalNamedParamsBegin -/// \cgalParamBegin{vertex_point_map} the property map with the points associated to the vertices of `pmesh`. The type of this map is model of `ReadWritePropertyMap`. -/// If this parameter is omitted, an internal property map for -/// `CGAL::vertex_point_t` should be available in `TriangleMesh` +/// \cgalParamBegin{vertex_point_map} the property map with the points associated to the vertices of `tm`. +/// The type of this map is model of `ReadWritePropertyMap`. +/// If this parameter is omitted, an internal property map for +/// `CGAL::vertex_point_t` should be available in `TriangleMesh` /// \cgalParamEnd /// \cgalParamBegin{geom_traits} a geometric traits class instance. -/// The traits class must provide the nested type `Point_3` +/// The traits class must provide the nested type `Point_3` /// \cgalParamEnd /// \cgalNamedParamsEnd /// -/// \return true if the triangle face is a cap +/// \return `true` if the triangle face is a cap template -bool is_cap_triangle_face(typename boost::graph_traits::face_descriptor f, - const TriangleMesh& tm, - const double threshold, - const NamedParameters& np) +typename boost::graph_traits::halfedge_descriptor +is_cap_triangle_face(typename boost::graph_traits::face_descriptor f, + const TriangleMesh& tm, + const double threshold, + const NamedParameters& np) { - CGAL_assertion(CGAL::is_triangle_mesh(tm)); - CGAL_assertion(threshold >= -1); - CGAL_assertion(threshold <= 0); + CGAL_precondition(CGAL::is_triangle_mesh(tm)); + CGAL_precondition(threshold >= -1.); + CGAL_precondition(threshold <= 0.); using boost::get_param; using boost::choose_param; + typedef typename boost::graph_traits::vertex_descriptor vertex_descriptor; + typedef typename boost::graph_traits::halfedge_descriptor halfedge_descriptor; + typedef typename GetVertexPointMap::const_type VertexPointMap; VertexPointMap vpmap = choose_param(get_param(np, internal_np::vertex_point), get_const_property_map(vertex_point, tm)); - typedef typename GetGeomTraits::type Traits; - typedef typename Traits::FT FT; - typedef boost::graph_traits GT; - typedef typename GT::halfedge_descriptor halfedge_descriptor; - typedef typename GT::vertex_descriptor vertex_descriptor; - typedef typename boost::property_traits::value_type Point_type; - typedef typename Kernel_traits::Kernel::Vector_3 Vector; - BOOST_FOREACH(halfedge_descriptor h, halfedges_around_face(halfedge(f, tm), tm)) + typedef typename GetGeomTraits::type Traits; + Traits traits = choose_param(get_param(np, internal_np::geom_traits), Traits()); + + typedef typename Traits::FT FT; + typedef typename Traits::Vector_3 Vector_3; + + const FT sq_threshold = threshold * threshold; + const halfedge_descriptor h0 = halfedge(f, tm); + + cpp11::array sq_lengths; + int pos = 0; + BOOST_FOREACH(halfedge_descriptor h, halfedges_around_face(h0, tm)) { - vertex_descriptor v0 = source(h, tm); - vertex_descriptor v1 = target(h, tm); - vertex_descriptor v2 = target(next(h, tm), tm); - Vector a = get(vpmap, v0) - get (vpmap, v1); - Vector b = get(vpmap, v2) - get(vpmap, v1); - FT aa = a.squared_length(); - FT bb = b.squared_length(); - FT squared_dot_ab = ((a*b)*(a*b)) / (aa * bb); - - if(squared_dot_ab > threshold * threshold) - return true; + sq_lengths[pos++] = traits.compute_squared_distance_3_object()(get(vpmap, source(h, tm)), + get(vpmap, target(h, tm))); } - return false; + + pos = 0; + BOOST_FOREACH(halfedge_descriptor h, halfedges_around_face(h0, tm)) + { + const vertex_descriptor v0 = source(h, tm); + const vertex_descriptor v1 = target(h, tm); + const vertex_descriptor v2 = target(next(h, tm), tm); + const Vector_3 a = traits.construct_vector_3_object()(get(vpmap, v1), get(vpmap, v2)); + const Vector_3 b = traits.construct_vector_3_object()(get(vpmap, v1), get(vpmap, v0)); + const FT dot_ab = traits.compute_scalar_product_3_object()(a, b); + const bool neg_sp = (dot_ab <= 0); + const FT sq_a = sq_lengths[(pos+1)%3]; + const FT sq_b = sq_lengths[pos]; + const FT sq_cos = dot_ab * dot_ab / (sq_a * sq_b); + + if(neg_sp && sq_cos >= sq_threshold) + return prev(h, tm); + } + return boost::graph_traits::null_halfedge(); } template -bool is_cap_triangle_face(typename boost::graph_traits::face_descriptor f, - const TriangleMesh& tm, - const double threshold) +typename boost::graph_traits::halfedge_descriptor +is_cap_triangle_face(typename boost::graph_traits::face_descriptor f, + const TriangleMesh& tm, + const double threshold) { return is_cap_triangle_face(f, tm, threshold, parameters::all_default()); } - - - } } // end namespaces CGAL and PMP - - #endif // CGAL_POLYGON_MESH_PROCESSING_HELPERS_H - diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Isotropic_remeshing/remesh_impl.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Isotropic_remeshing/remesh_impl.h index 428d49a6309..e5e914402db 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Isotropic_remeshing/remesh_impl.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Isotropic_remeshing/remesh_impl.h @@ -311,6 +311,7 @@ namespace internal { public: Incremental_remesher(PolygonMesh& pmesh , VertexPointMap& vpmap + , const GeomTraits& gt , const bool protect_constraints , EdgeIsConstrainedMap ecmap , VertexIsConstrainedMap vcmap @@ -319,6 +320,7 @@ namespace internal { , const bool build_tree = true)//built by the remesher : mesh_(pmesh) , vpmap_(vpmap) + , gt_(gt) , build_tree_(build_tree) , has_border_(false) , input_triangles_() @@ -350,9 +352,10 @@ namespace internal { BOOST_FOREACH(face_descriptor f, face_range) { - if (is_degenerate_triangle_face(f, mesh_)){ + if(is_degenerate_triangle_face(f, mesh_, parameters::vertex_point_map(vpmap_) + .geom_traits(gt_))) continue; - } + Patch_id pid = get_patch_id(f); input_triangles_.push_back(triangle(f)); input_patch_ids_.push_back(pid); @@ -803,7 +806,8 @@ namespace internal { debug_status_map(); debug_self_intersections(); CGAL_assertion(0 == PMP::remove_degenerate_faces(mesh_, - PMP::parameters::vertex_point_map(vpmap_).geom_traits(GeomTraits()))); + parameters::vertex_point_map(vpmap_) + .geom_traits(gt_))); #endif } @@ -908,7 +912,7 @@ namespace internal { debug_status_map(); CGAL_assertion(0 == PMP::remove_degenerate_faces(mesh_ , PMP::parameters::vertex_point_map(vpmap_) - .geom_traits(GeomTraits()))); + .geom_traits(gt_))); debug_self_intersections(); #endif @@ -950,9 +954,9 @@ namespace internal { else if (is_on_patch(v)) { - Vector_3 vn = PMP::compute_vertex_normal(v, mesh_ - , PMP::parameters::vertex_point_map(vpmap_) - .geom_traits(GeomTraits())); + Vector_3 vn = PMP::compute_vertex_normal(v, mesh_, + parameters::vertex_point_map(vpmap_) + .geom_traits(gt_)); put(propmap_normals, v, vn); Vector_3 move = CGAL::NULL_VECTOR; @@ -1444,20 +1448,8 @@ private: if (f == boost::graph_traits::null_face()) return CGAL::NULL_VECTOR; - halfedge_descriptor hd = halfedge(f, mesh_); - typename boost::property_traits::reference - p = get(vpmap_, target(hd, mesh_)); - hd = next(hd,mesh_); - typename boost::property_traits::reference - q = get(vpmap_, target(hd, mesh_)); - hd = next(hd,mesh_); - typename boost::property_traits::reference - r =get(vpmap_, target(hd, mesh_)); - - if (GeomTraits().collinear_3_object()(p,q,r)) - return CGAL::NULL_VECTOR; - else - return PMP::compute_face_normal(f, mesh_, parameters::vertex_point_map(vpmap_)); + return PMP::compute_face_normal(f, mesh_, parameters::vertex_point_map(vpmap_) + .geom_traits(gt_)); } template @@ -1573,27 +1565,31 @@ private: const bool collapse_constraints) { CGAL_assertion_code(std::size_t nb_done = 0); + boost::unordered_set degenerate_faces; BOOST_FOREACH(halfedge_descriptor h, halfedges_around_target(halfedge(v, mesh_), mesh_)) { - if (is_border(h, mesh_)) - continue; - if (is_degenerate_triangle_face(face(h), mesh_)) + if(!is_border(h, mesh_) && + is_degenerate_triangle_face(face(h, mesh_), mesh_, + parameters::vertex_point_map(vpmap_) + .geom_traits(gt_))) degenerate_faces.insert(h); } + while(!degenerate_faces.empty()) { halfedge_descriptor h = *(degenerate_faces.begin()); degenerate_faces.erase(degenerate_faces.begin()); - if (!is_degenerate_triangle_face(face(h), mesh_)) + if (!is_degenerate_triangle_face(face(h, mesh_), mesh_, + parameters::vertex_point_map(vpmap_) + .geom_traits(gt_))) //this can happen when flipping h has consequences further in the mesh continue; //check that opposite is not also degenerate - if (degenerate_faces.find(opposite(h, mesh_)) != degenerate_faces.end()) - degenerate_faces.erase(opposite(h, mesh_)); + degenerate_faces.erase(opposite(h, mesh_)); if(is_border(h, mesh_)) continue; @@ -1639,11 +1635,15 @@ private: short_edges.insert(typename Bimap::value_type(hf, sqlen)); } - if (!is_border(hf, mesh_) - && is_degenerate_triangle_face(face(h), mesh_)) + if(!is_border(hf, mesh_) && + is_degenerate_triangle_face(face(hf, mesh_), mesh_, + parameters::vertex_point_map(vpmap_) + .geom_traits(gt_))) degenerate_faces.insert(hf); - if (!is_border(hfo, mesh_) - && is_degenerate_triangle_face(face(h), mesh_)) + if(!is_border(hfo, mesh_) && + is_degenerate_triangle_face(face(hfo, mesh_), mesh_, + parameters::vertex_point_map(vpmap_) + .geom_traits(gt_))) degenerate_faces.insert(hfo); break; @@ -1660,9 +1660,10 @@ private: BOOST_FOREACH(halfedge_descriptor h, halfedges_around_target(he, mesh_)) { - if (is_border(h, mesh_)) - continue; - if (is_degenerate_triangle_face(face(h), mesh_)) + if(!is_border(h, mesh_) && + is_degenerate_triangle_face(face(h, mesh_), mesh_, + parameters::vertex_point_map(vpmap_) + .geom_traits(gt_))) return true; } return false; @@ -1803,10 +1804,10 @@ private: { std::cout << "Test self intersections..."; std::vector > facets; - PMP::self_intersections( - mesh_, - std::back_inserter(facets), - PMP::parameters::vertex_point_map(vpmap_)); + PMP::self_intersections(mesh_, + std::back_inserter(facets), + PMP::parameters::vertex_point_map(vpmap_) + .geom_traits(gt_)); //CGAL_assertion(facets.empty()); std::cout << "done ("<< facets.size() <<" facets)." << std::endl; } @@ -1815,11 +1816,11 @@ private: { std::cout << "Test self intersections..."; std::vector > facets; - PMP::self_intersections( - faces_around_target(halfedge(v, mesh_), mesh_), - mesh_, - std::back_inserter(facets), - PMP::parameters::vertex_point_map(vpmap_)); + PMP::self_intersections(faces_around_target(halfedge(v, mesh_), mesh_), + mesh_, + std::back_inserter(facets), + PMP::parameters::vertex_point_map(vpmap_) + .geom_traits(gt_)); //CGAL_assertion(facets.empty()); std::cout << "done ("<< facets.size() <<" facets)." << std::endl; } @@ -1902,6 +1903,7 @@ private: private: PolygonMesh& mesh_; VertexPointMap& vpmap_; + const GeomTraits& gt_; bool build_tree_; bool has_border_; std::vector trees; diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/remesh.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/remesh.h index 40994d53a87..b50060998f0 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/remesh.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/remesh.h @@ -163,6 +163,7 @@ void isotropic_remeshing(const FaceRange& faces boost::is_default_param(get_param(np, internal_np::projection_functor)); typedef typename GetGeomTraits::type GT; + GT gt = choose_param(get_param(np, internal_np::geom_traits), GT()); typedef typename GetVertexPointMap::type VPMap; VPMap vpmap = choose_param(get_param(np, internal_np::vertex_point), @@ -227,7 +228,7 @@ void isotropic_remeshing(const FaceRange& faces #endif typename internal::Incremental_remesher - remesher(pmesh, vpmap, protect, ecmap, vcmap, fpmap, fimap, need_aabb_tree); + remesher(pmesh, vpmap, gt, protect, ecmap, vcmap, fpmap, fimap, need_aabb_tree); remesher.init_remeshing(faces); #ifdef CGAL_PMP_REMESHING_VERBOSE @@ -340,6 +341,8 @@ void split_long_edges(const EdgeRange& edges using boost::get_param; typedef typename GetGeomTraits::type GT; + GT gt = choose_param(get_param(np, internal_np::geom_traits), GT()); + typedef typename GetVertexPointMap::type VPMap; VPMap vpmap = choose_param(get_param(np, internal_np::vertex_point), get_property_map(vertex_point, pmesh)); @@ -361,7 +364,7 @@ void split_long_edges(const EdgeRange& edges internal::Connected_components_pmap, FIMap > - remesher(pmesh, vpmap, false/*protect constraints*/, ecmap, + remesher(pmesh, vpmap, gt, false/*protect constraints*/, ecmap, Constant_property_map(false), internal::Connected_components_pmap(faces(pmesh), pmesh, ecmap, fimap, false), fimap, diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/repair.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/repair.h index e03c785e990..15c77eeb5c1 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/repair.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/repair.h @@ -159,17 +159,27 @@ struct Less_vertex_point{ } }; +template +OutputIterator +degenerate_faces(const TriangleMesh& tm, + OutputIterator out, + const NamedParameters& np) +{ + typedef typename boost::graph_traits::face_descriptor face_descriptor; + + BOOST_FOREACH(face_descriptor fd, faces(tm)) + { + if(is_degenerate_triangle_face(fd, tm, np)) + *out++ = fd; + } + return out; +} + template OutputIterator degenerate_faces(const TriangleMesh& tm, OutputIterator out) { - typedef typename boost::graph_traits::face_descriptor face_descriptor; - BOOST_FOREACH(face_descriptor fd, faces(tm)) - { - if ( is_degenerate_triangle_face(fd, tm) ) - *out++=fd; - } - return out; + return degenerate_faces(tm, out, CGAL::parameters::all_default()); } // this function remove a border edge even if it does not satisfy the link condition. @@ -717,9 +727,8 @@ std::size_t remove_degenerate_faces(TriangleMesh& tmesh, // Then, remove triangles made of 3 collinear points std::set degenerate_face_set; - BOOST_FOREACH(face_descriptor fd, faces(tmesh)) - if ( is_degenerate_triangle_face(fd, tmesh, np)) - degenerate_face_set.insert(fd); + degenerate_faces(tmesh, std::inserter(degenerate_face_set, degenerate_face_set.begin()), np); + nb_deg_faces+=degenerate_face_set.size(); // first remove degree 3 vertices that are part of a cap @@ -1274,6 +1283,45 @@ std::size_t remove_degenerate_faces(TriangleMesh& tmesh) CGAL::Polygon_mesh_processing::parameters::all_default()); } +namespace internal { + +template +struct Vertex_collector +{ + typedef typename boost::graph_traits::vertex_descriptor vertex_descriptor; + + void collect_vertices(vertex_descriptor v1, vertex_descriptor v2) + { + std::vector& verts = collections[v1]; + if(verts.empty()) + verts.push_back(v1); + verts.push_back(v2); + } + + void dump(OutputIterator out) + { + typedef std::pair > Pair_type; + BOOST_FOREACH(const Pair_type& p, collections) { + *out++ = p.second; + } + } + + std::map > collections; +}; + +template +struct Vertex_collector +{ + typedef typename boost::graph_traits::vertex_descriptor vertex_descriptor; + void collect_vertices(vertex_descriptor, vertex_descriptor) + {} + + void dump(Emptyset_iterator) + {} +}; + +} // end namespace internal + /// \ingroup PMP_repairing_grp /// duplicates all non-manifold vertices of the input mesh. /// @@ -1284,22 +1332,22 @@ std::size_t remove_degenerate_faces(TriangleMesh& tmesh) /// @param np optional \ref pmp_namedparameters "Named Parameters" described below /// /// \cgalNamedParamsBegin -/// \cgalParamBegin{vertex_point_map} the property map with the points associated to the vertices of `pmesh`. The type of this map is model of `ReadWritePropertyMap`. +/// \cgalParamBegin{vertex_point_map} the property map with the points associated to the vertices of `pmesh`. +/// The type of this map is model of `ReadWritePropertyMap`. /// If this parameter is omitted, an internal property map for -/// `CGAL::vertex_point_t` should be available in `PolygonMesh` +/// `CGAL::vertex_point_t` should be available in `TriangleMesh` /// \cgalParamEnd /// \cgalParamBegin{vertex_is_constrained_map} a writable property map with `vertex_descriptor` /// as key and `bool` as `value_type`. `put(pmap, v, true)` will be called for each duplicated /// vertices and the input one. /// \cgalParamEnd -/// \cgalParamBegin{output_iterator} an output iterator where `std::vector` can be put. -/// The first vertex of the vector is an input vertex that was non-manifold, -/// the other vertices in the vertex are the new vertices created to fix -/// the non-manifoldness. +/// \cgalParamBegin{output_iterator} a model of `OutputIterator` with value type +/// `std::vector`. The first vertex of the vector is a non-manifold vertex +/// of the input mesh, followed by the new vertices that were created to fix the non-manifoldness. /// \cgalParamEnd /// \cgalNamedParamsEnd /// -/// \return the number of vertices created +/// \return the number of vertices created. template std::size_t duplicate_non_manifold_vertices(TriangleMesh& tm, const NamedParameters& np) @@ -1315,7 +1363,7 @@ std::size_t duplicate_non_manifold_vertices(TriangleMesh& tm, typedef typename GetVertexPointMap::type VertexPointMap; VertexPointMap vpm = choose_param(get_param(np, internal_np::vertex_point), - get_property_map(vertex_point, tm)); + get_property_map(vertex_point, tm)); typedef typename boost::lookup_named_param_def < internal_np::vertex_is_constrained_t, @@ -1333,37 +1381,46 @@ std::size_t duplicate_non_manifold_vertices(TriangleMesh& tm, > ::type Output_iterator; Output_iterator out = choose_param(get_param(np, internal_np::output_iterator), - Emptyset_iterator()); + Emptyset_iterator()); internal::Vertex_collector dmap; boost::unordered_set vertices_handled; boost::unordered_set halfedges_handled; - std::size_t nb_new_vertices=0; + std::size_t nb_new_vertices = 0; std::vector non_manifold_cones; BOOST_FOREACH(halfedge_descriptor h, halfedges(tm)) { - if (halfedges_handled.insert(h).second) + // If 'h' is not visited yet, we walk around the target of 'h' and mark these + // halfedges as visited. Thus, if we are here and the target is already marked as visited, + // it means that the vertex is non manifold. + if(halfedges_handled.insert(h).second) { vertex_descriptor vd = target(h, tm); - if ( !vertices_handled.insert(vd).second ) + if(!vertices_handled.insert(vd).second) { put(cmap, vd, true); // store the originals non_manifold_cones.push_back(h); } else + { set_halfedge(vd, h, tm); - halfedge_descriptor start=opposite(next(h, tm), tm); - h=start; - do{ + } + + halfedge_descriptor start = opposite(next(h, tm), tm); + h = start; + do + { halfedges_handled.insert(h); - h=opposite(next(h, tm), tm); - }while(h!=start); + h = opposite(next(h, tm), tm); + } + while(h != start); } } - if (!non_manifold_cones.empty()) { + if(!non_manifold_cones.empty()) + { BOOST_FOREACH(halfedge_descriptor h, non_manifold_cones) { halfedge_descriptor start = h; @@ -1373,13 +1430,16 @@ std::size_t duplicate_non_manifold_vertices(TriangleMesh& tm, dmap.collect_vertices(target(h, tm), new_vd); put(vpm, new_vd, get(vpm, target(h, tm))); set_halfedge(new_vd, h, tm); - do{ + do + { set_target(h, new_vd, tm); - h=opposite(next(h, tm), tm); - } while(h!=start); + h = opposite(next(h, tm), tm); + } + while(h != start); } dmap.dump(out); } + return nb_new_vertices; } diff --git a/Polygon_mesh_processing/test/Polygon_mesh_processing/data_degeneracies/caps_and_needles.off b/Polygon_mesh_processing/test/Polygon_mesh_processing/data_degeneracies/caps_and_needles.off new file mode 100644 index 00000000000..4e206746788 --- /dev/null +++ b/Polygon_mesh_processing/test/Polygon_mesh_processing/data_degeneracies/caps_and_needles.off @@ -0,0 +1,17 @@ +OFF +9 3 0 +0 0 0 +1 0 0 +1 1 0 +0 0 1 +1 0 1 +10 10 1 +0 0 2 +1 0 2 +-0.99619469809 0.08715574274 2 +3 0 1 2 +3 3 4 5 +3 6 7 8 + + + diff --git a/Polygon_mesh_processing/test/Polygon_mesh_processing/test_predicates.cpp b/Polygon_mesh_processing/test/Polygon_mesh_processing/test_predicates.cpp index 3bf2ffcd494..4dd421fe533 100644 --- a/Polygon_mesh_processing/test/Polygon_mesh_processing/test_predicates.cpp +++ b/Polygon_mesh_processing/test/Polygon_mesh_processing/test_predicates.cpp @@ -1,142 +1,248 @@ #include + #include -#include -#include + #include #include -typedef CGAL::Exact_predicates_inexact_constructions_kernel K; -typedef CGAL::Surface_mesh Surface_mesh; +#include + +#include +#include + +typedef CGAL::Exact_predicates_inexact_constructions_kernel K; +typedef K::FT FT; +typedef K::Point_3 Point_3; +typedef CGAL::Surface_mesh Surface_mesh; void check_edge_degeneracy(const char* fname) { - std::ifstream input(fname); + std::cout << "test edge degeneracy..."; + typedef typename boost::graph_traits::edge_descriptor edge_descriptor; + + std::ifstream input(fname); Surface_mesh mesh; if (!input || !(input >> mesh) || mesh.is_empty()) { std::cerr << fname << " is not a valid off file.\n"; - exit(1); + std::exit(1); } - typedef typename boost::graph_traits::edge_descriptor edge_descriptor; std::vector all_edges(edges(mesh).begin(), edges(mesh).end()); - CGAL_assertion(!CGAL::Polygon_mesh_processing::is_degenerate_edge(all_edges[0], mesh)); - CGAL_assertion(!CGAL::Polygon_mesh_processing::is_degenerate_edge(all_edges[1], mesh)); - CGAL_assertion(CGAL::Polygon_mesh_processing::is_degenerate_edge(all_edges[2], mesh)); + assert(!CGAL::Polygon_mesh_processing::is_degenerate_edge(all_edges[0], mesh)); + assert(!CGAL::Polygon_mesh_processing::is_degenerate_edge(all_edges[1], mesh)); + assert(CGAL::Polygon_mesh_processing::is_degenerate_edge(all_edges[2], mesh)); + std::cout << "done" << std::endl; } void check_triangle_face_degeneracy(const char* fname) { - std::ifstream input(fname); + std::cout << "test face degeneracy..."; + typedef typename boost::graph_traits::face_descriptor face_descriptor; + + std::ifstream input(fname); Surface_mesh mesh; if (!input || !(input >> mesh) || mesh.is_empty()) { std::cerr << fname << " is not a valid off file.\n"; - exit(1); + std::exit(1); } - typedef typename boost::graph_traits::face_descriptor face_descriptor; std::vector all_faces(faces(mesh).begin(), faces(mesh).end()); - CGAL_assertion(CGAL::Polygon_mesh_processing::is_degenerate_triangle_face(all_faces[0], mesh)); - CGAL_assertion(!CGAL::Polygon_mesh_processing::is_degenerate_triangle_face(all_faces[1], mesh)); - CGAL_assertion(!CGAL::Polygon_mesh_processing::is_degenerate_triangle_face(all_faces[2], mesh)); - CGAL_assertion(!CGAL::Polygon_mesh_processing::is_degenerate_triangle_face(all_faces[3], mesh)); + assert(CGAL::Polygon_mesh_processing::is_degenerate_triangle_face(all_faces[0], mesh)); + assert(!CGAL::Polygon_mesh_processing::is_degenerate_triangle_face(all_faces[1], mesh)); + assert(!CGAL::Polygon_mesh_processing::is_degenerate_triangle_face(all_faces[2], mesh)); + assert(!CGAL::Polygon_mesh_processing::is_degenerate_triangle_face(all_faces[3], mesh)); + + std::cout << "done" << std::endl; } -// tests repair.h -void test_vertices_merge_and_duplication(const char* fname) +// tests merge_and_duplication +template +void merge_identical_points(typename boost::graph_traits::vertex_descriptor v_keep, + typename boost::graph_traits::vertex_descriptor v_rm, + PolygonMesh& mesh) { - std::ifstream input(fname); - Surface_mesh mesh; - if (!input || !(input >> mesh) || mesh.is_empty()) { - std::cerr << fname << " is not a valid off file.\n"; - exit(1); + typedef typename boost::graph_traits::halfedge_descriptor halfedge_descriptor; + + halfedge_descriptor h = halfedge(v_rm, mesh); + halfedge_descriptor start = h; + + do + { + set_target(h, v_keep, mesh); + h = opposite(next(h, mesh), mesh); } - const std::size_t initial_vertices = vertices(mesh).size(); + while( h != start ); - // create non-manifold vertex - typedef typename boost::graph_traits::vertex_descriptor vertex_descriptor; - std::vector all_vertices(vertices(mesh).begin(), vertices(mesh).end()); - CGAL::Polygon_mesh_processing::internal::merge_identical_points(mesh, all_vertices[1], all_vertices[7]); - - const std::size_t vertices_after_merge = vertices(mesh).size(); - CGAL_assertion(vertices_after_merge == initial_vertices - 1); - - std::vector< std::vector > duplicated_vertices; - CGAL::Polygon_mesh_processing::duplicate_non_manifold_vertices(mesh, - CGAL::parameters::output_iterator(std::back_inserter(duplicated_vertices))); - const std::size_t final_vertices_size = vertices(mesh).size(); - CGAL_assertion(final_vertices_size == vertices_after_merge + 1); - CGAL_assertion(final_vertices_size == initial_vertices); - CGAL_assertion(duplicated_vertices.size() == 2); + remove_vertex(v_rm, mesh); } void test_vertex_non_manifoldness(const char* fname) { + std::cout << "test vertex non manifoldness..."; + + typedef typename boost::graph_traits::vertex_descriptor vertex_descriptor; + typedef typename boost::graph_traits::vertices_size_type size_type; + std::ifstream input(fname); Surface_mesh mesh; if (!input || !(input >> mesh) || mesh.is_empty()) { std::cerr << fname << " is not a valid off file.\n"; - exit(1); + std::exit(1); } + size_type ini_nv = num_vertices(mesh); + // create non-manifold vertex - typedef typename boost::graph_traits::vertex_descriptor vertex_descriptor; - std::vector all_vertices(vertices(mesh).begin(), vertices(mesh).end()); - CGAL::Polygon_mesh_processing::internal::merge_identical_points(mesh, all_vertices[1], all_vertices[7]); - std::vector vertices_with_non_manifold(vertices(mesh).begin(), vertices(mesh).end()); - CGAL_assertion(vertices_with_non_manifold.size() == all_vertices.size() - 1); + Surface_mesh::Vertex_index vertex_to_merge_onto(1); + Surface_mesh::Vertex_index vertex_to_merge(7); + merge_identical_points(vertex_to_merge_onto, vertex_to_merge, mesh); + mesh.collect_garbage(); - BOOST_FOREACH(std::size_t iv, vertices(mesh)) + assert(num_vertices(mesh) == ini_nv - 1); + + BOOST_FOREACH(vertex_descriptor v, vertices(mesh)) { - vertex_descriptor v = vertices_with_non_manifold[iv]; - if(iv == 1) - CGAL_assertion(CGAL::Polygon_mesh_processing::is_non_manifold_vertex(v, mesh)); + if(v == vertex_to_merge_onto) + assert(CGAL::Polygon_mesh_processing::is_non_manifold_vertex(v, mesh)); else - CGAL_assertion(!CGAL::Polygon_mesh_processing::is_non_manifold_vertex(v, mesh)); + assert(!CGAL::Polygon_mesh_processing::is_non_manifold_vertex(v, mesh)); } + + std::cout << "done" << std::endl; } -void test_needle(const char* fname) +void test_vertices_merge_and_duplication(const char* fname) { + std::cout << "test non manifold vertex duplication..."; + + typedef typename boost::graph_traits::vertex_descriptor vertex_descriptor; + std::ifstream input(fname); Surface_mesh mesh; if (!input || !(input >> mesh) || mesh.is_empty()) { std::cerr << fname << " is not a valid off file.\n"; - exit(1); + std::exit(1); } + const std::size_t initial_vertices = num_vertices(mesh); - const double threshold = 0.8; - BOOST_FOREACH(typename boost::graph_traits::face_descriptor f, faces(mesh)) - { - CGAL_assertion(CGAL::Polygon_mesh_processing::is_needle_triangle_face(f, mesh, threshold)); - } + // create non-manifold vertex + Surface_mesh::Vertex_index vertex_to_merge_onto(1); + Surface_mesh::Vertex_index vertex_to_merge(7); + Surface_mesh::Vertex_index vertex_to_merge_2(14); + Surface_mesh::Vertex_index vertex_to_merge_3(21); + + Surface_mesh::Vertex_index vertex_to_merge_onto_2(2); + Surface_mesh::Vertex_index vertex_to_merge_4(8); + + merge_identical_points(vertex_to_merge_onto, vertex_to_merge, mesh); + merge_identical_points(vertex_to_merge_onto, vertex_to_merge_2, mesh); + merge_identical_points(vertex_to_merge_onto, vertex_to_merge_3, mesh); + merge_identical_points(vertex_to_merge_onto_2, vertex_to_merge_4, mesh); + mesh.collect_garbage(); + + const std::size_t vertices_after_merge = num_vertices(mesh); + assert(vertices_after_merge == initial_vertices - 4); + + std::vector > duplicated_vertices; + CGAL::Polygon_mesh_processing::duplicate_non_manifold_vertices(mesh, + CGAL::parameters::output_iterator(std::back_inserter(duplicated_vertices))); + + const std::size_t final_vertices_size = vertices(mesh).size(); + assert(final_vertices_size == initial_vertices); + assert(duplicated_vertices.size() == 2); // two non-manifold vertex + assert(duplicated_vertices.front().size() == 4); + assert(duplicated_vertices.back().size() == 2); + + std::cout << "done" << std::endl; } -void test_cap(const char* fname) +void test_needles_and_caps(const char* fname) { + std::cout << "test needles&caps..."; + + namespace PMP = CGAL::Polygon_mesh_processing; + + typedef typename boost::graph_traits::halfedge_descriptor halfedge_descriptor; + typedef typename boost::graph_traits::face_iterator face_iterator; + typedef typename boost::graph_traits::face_descriptor face_descriptor; + std::ifstream input(fname); Surface_mesh mesh; if (!input || !(input >> mesh) || mesh.is_empty()) { std::cerr << fname << " is not a valid off file.\n"; - exit(1); + std::exit(1); } - const double threshold = -0.8; - BOOST_FOREACH(typename boost::graph_traits::face_descriptor f, faces(mesh)) - { - CGAL_assertion(CGAL::Polygon_mesh_processing::is_cap_triangle_face(f, mesh, threshold)); - } + const FT eps = std::numeric_limits::epsilon(); + + face_iterator fit, fend; + boost::tie(fit, fend) = faces(mesh); + + // (0 0 0) -- (1 0 0) -- (1 1 0) (90° cap angle) + face_descriptor f = *fit; + halfedge_descriptor res = PMP::is_needle_triangle_face(f, mesh, 2/*needle_threshold*/); + assert(res == boost::graph_traits::null_halfedge()); // not a needle + res = PMP::is_needle_triangle_face(f, mesh, CGAL::sqrt(FT(2) - eps)/*needle_threshold*/); + assert(res != boost::graph_traits::null_halfedge()); // is a needle + + res = PMP::is_cap_triangle_face(f, mesh, 0./*cos(pi/2)*/); + assert(mesh.point(target(res, mesh)) == CGAL::ORIGIN); + res = PMP::is_cap_triangle_face(f, mesh, std::cos(91 * CGAL_PI / 180)); + assert(res == boost::graph_traits::null_halfedge()); res = PMP::is_cap_triangle_face(f, mesh, std::cos(boost::math::constants::two_thirds_pi())); + assert(res == boost::graph_traits::null_halfedge()); + ++ fit; + + // (0 0 1) -- (1 0 1) -- (10 10 1) + f = *fit; + res = PMP::is_needle_triangle_face(f, mesh, 20); + assert(res == boost::graph_traits::null_halfedge()); + res = PMP::is_needle_triangle_face(f, mesh, 10 * CGAL::sqrt(FT(2) - eps)); + assert(mesh.point(target(res, mesh)) == Point_3(1,0,1)); + res = PMP::is_needle_triangle_face(f, mesh, 1); + assert(mesh.point(target(res, mesh)) == Point_3(1,0,1)); + + res = PMP::is_cap_triangle_face(f, mesh, 0./*cos(pi/2)*/); + assert(mesh.point(target(res, mesh)) == Point_3(0,0,1)); + res = PMP::is_cap_triangle_face(f, mesh, std::cos(boost::math::constants::two_thirds_pi())); + assert(mesh.point(target(res, mesh)) == Point_3(0,0,1)); + res = PMP::is_cap_triangle_face(f, mesh, std::cos(boost::math::constants::three_quarters_pi())); + assert(res == boost::graph_traits::null_halfedge()); + ++ fit; + + // (0 0 2) -- (1 0 2) -- (-0.99619469809 0.08715574274 2) (175° cap angle) + f = *fit; + res = PMP::is_needle_triangle_face(f, mesh, 2); + assert(res == boost::graph_traits::null_halfedge()); + res = PMP::is_needle_triangle_face(f, mesh, 1.9); + assert(mesh.point(target(res, mesh)) == Point_3(0,0,2) || + mesh.point(target(res, mesh)) == Point_3(1,0,2)); + res = PMP::is_needle_triangle_face(f, mesh, 1); + assert(mesh.point(target(res, mesh)) == Point_3(0,0,2) || + mesh.point(target(res, mesh)) == Point_3(1,0,2)); + + res = PMP::is_cap_triangle_face(f, mesh, 0./*cos(pi/2)*/); + assert(res != boost::graph_traits::null_halfedge() && + mesh.point(target(res, mesh)) != Point_3(0,0,2) && + mesh.point(target(res, mesh)) != Point_3(1,0,2)); + res = PMP::is_cap_triangle_face(f, mesh, std::cos(boost::math::constants::two_thirds_pi())); + assert(res != boost::graph_traits::null_halfedge()); + res = PMP::is_cap_triangle_face(f, mesh, std::cos(175 * CGAL_PI / 180)); + assert(res != boost::graph_traits::null_halfedge()); + res = PMP::is_cap_triangle_face(f, mesh, std::cos(176 * CGAL_PI / 180)); + assert(res == boost::graph_traits::null_halfedge()); + + std::cout << "done" << std::endl; } int main() { check_edge_degeneracy("data_degeneracies/degtri_edge.off"); check_triangle_face_degeneracy("data_degeneracies/degtri_four.off"); - test_vertices_merge_and_duplication("data_degeneracies/non_manifold_vertex_duplicated.off"); - test_vertex_non_manifoldness("data_degeneracies/non_manifold_vertex_duplicated.off"); - test_needle("data_degeneracies/needle.off"); - test_cap("data_degeneracies/cap.off"); + test_vertex_non_manifoldness("data/blobby.off"); + test_vertices_merge_and_duplication("data/blobby.off"); + test_needles_and_caps("data_degeneracies/caps_and_needles.off"); return 0; } diff --git a/Polyhedron/demo/Polyhedron/Plugins/PMP/Selection_plugin.cpp b/Polyhedron/demo/Polyhedron/Plugins/PMP/Selection_plugin.cpp index ac1bfd837d4..28d0d5f779a 100644 --- a/Polyhedron/demo/Polyhedron/Plugins/PMP/Selection_plugin.cpp +++ b/Polyhedron/demo/Polyhedron/Plugins/PMP/Selection_plugin.cpp @@ -742,12 +742,10 @@ public Q_SLOTS: //Edition mode case 1: { - VPmap vpmap = get(CGAL::vertex_point, *selection_item->polyhedron()); bool is_valid = true; BOOST_FOREACH(boost::graph_traits::face_descriptor fd, faces(*selection_item->polyhedron())) { - if (CGAL::Polygon_mesh_processing::is_degenerate_triangle_face(fd, - *selection_item->polyhedron())) + if (CGAL::Polygon_mesh_processing::is_degenerate_triangle_face(fd, *selection_item->polyhedron())) { is_valid = false; break; diff --git a/Polyhedron/demo/Polyhedron/Scene_polyhedron_item.cpp b/Polyhedron/demo/Polyhedron/Scene_polyhedron_item.cpp index 266f5331b94..a71037f40f7 100644 --- a/Polyhedron/demo/Polyhedron/Scene_polyhedron_item.cpp +++ b/Polyhedron/demo/Polyhedron/Scene_polyhedron_item.cpp @@ -1682,7 +1682,7 @@ QString Scene_polyhedron_item::computeStats(int type) if (d->poly->is_pure_triangle()) { if (d->number_of_degenerated_faces == (unsigned int)(-1)) - d->number_of_degenerated_faces = nb_degenerate_faces(d->poly, get(CGAL::vertex_point, *(d->poly))); + d->number_of_degenerated_faces = nb_degenerate_faces(d->poly); return QString::number(d->number_of_degenerated_faces); } else diff --git a/Polyhedron/demo/Polyhedron/Scene_polyhedron_selection_item.cpp b/Polyhedron/demo/Polyhedron/Scene_polyhedron_selection_item.cpp index 488e874d416..6696a5ac9bc 100644 --- a/Polyhedron/demo/Polyhedron/Scene_polyhedron_selection_item.cpp +++ b/Polyhedron/demo/Polyhedron/Scene_polyhedron_selection_item.cpp @@ -2032,8 +2032,9 @@ bool Scene_polyhedron_selection_item_priv::canAddFace(fg_halfedge_descriptor hc, found = true; fg_halfedge_descriptor res = CGAL::Euler::add_face_to_border(t,hc, *item->polyhedron()); + fg_face_descriptor resf = face(res, *item->polyhedron()); - if(CGAL::Polygon_mesh_processing::is_degenerate_triangle_face(res, *item->polyhedron())) + if(CGAL::Polygon_mesh_processing::is_degenerate_triangle_face(resf, *item->polyhedron())) { CGAL::Euler::remove_face(res, *item->polyhedron()); tempInstructions("Edge not selected : resulting facet is degenerated.", diff --git a/Polyhedron/demo/Polyhedron/Scene_surface_mesh_item.cpp b/Polyhedron/demo/Polyhedron/Scene_surface_mesh_item.cpp index 75e60907f61..b2b56ff87d9 100644 --- a/Polyhedron/demo/Polyhedron/Scene_surface_mesh_item.cpp +++ b/Polyhedron/demo/Polyhedron/Scene_surface_mesh_item.cpp @@ -1502,7 +1502,7 @@ QString Scene_surface_mesh_item::computeStats(int type) if(is_triangle_mesh(*d->smesh_)) { if (d->number_of_degenerated_faces == (unsigned int)(-1)) - d->number_of_degenerated_faces = nb_degenerate_faces(d->smesh_, get(CGAL::vertex_point, *(d->smesh_))); + d->number_of_degenerated_faces = nb_degenerate_faces(d->smesh_); return QString::number(d->number_of_degenerated_faces); } else diff --git a/Polyhedron/demo/Polyhedron/include/CGAL/statistics_helpers.h b/Polyhedron/demo/Polyhedron/include/CGAL/statistics_helpers.h index 711a641dcc3..e65bdf7329b 100644 --- a/Polyhedron/demo/Polyhedron/include/CGAL/statistics_helpers.h +++ b/Polyhedron/demo/Polyhedron/include/CGAL/statistics_helpers.h @@ -1,8 +1,6 @@ #ifndef POLYHEDRON_DEMO_STATISTICS_HELPERS_H #define POLYHEDRON_DEMO_STATISTICS_HELPERS_H -#include - #include #include #include @@ -10,12 +8,15 @@ #include #include #include -#include #include #include -#include +#include +#include +#include +#include +#include template void angles(Mesh* poly, double& mini, double& maxi, double& ave) @@ -84,19 +85,15 @@ void edges_length(Mesh* poly, mid = extract_result< tag::median >(acc); } -template -unsigned int nb_degenerate_faces(Mesh* poly, VPmap vpmap) +template +unsigned int nb_degenerate_faces(Mesh* poly) { typedef typename boost::graph_traits::face_descriptor face_descriptor; - typedef typename CGAL::Kernel_traits< typename boost::property_traits::value_type >::Kernel Traits; - unsigned int nb = 0; - BOOST_FOREACH(face_descriptor f, faces(*poly)) - { - if (CGAL::Polygon_mesh_processing::is_degenerate_triangle_face(f, *poly)) - ++nb; - } - return nb; + std::vector degenerate_faces; + CGAL::Polygon_mesh_processing::degenerate_faces(*poly, std::back_inserter(degenerate_faces)); + + return static_cast(degenerate_faces.size()); } template From 614f80694c6c95218560cce0717f2324fba669c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mael=20Rouxel-Labb=C3=A9?= Date: Mon, 23 Jul 2018 11:36:15 +0200 Subject: [PATCH 030/116] Removed obsolete code about merging duplicated boundary vertices --- .../merge_border_vertices.h | 37 ------------------- .../test_merging_border_vertices.cpp | 36 +----------------- .../test_predicates.cpp | 2 +- 3 files changed, 3 insertions(+), 72 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/merge_border_vertices.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/merge_border_vertices.h index 2406c6d72de..c530a82b51d 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/merge_border_vertices.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/merge_border_vertices.h @@ -345,35 +345,6 @@ void merge_duplicated_vertices_in_boundary_cycles( PolygonMesh& pm, merge_duplicated_vertices_in_boundary_cycle(h, pm, np); } -#if 0 -/// \ingroup PMP_repairing_grp -/// \todo document me -template -void merge_duplicated_boundary_vertices( PolygonMesh& pm, - const NamedParameter& np) -{ - typedef typename boost::graph_traits::halfedge_descriptor halfedge_descriptor; - typedef typename boost::graph_traits::vertex_descriptor vertex_descriptor; - typedef typename GetVertexPointMap::const_type Vpm; - - Vpm vpm = choose_param(get_param(np, internal_np::vertex_point), - get_const_property_map(vertex_point, pm)); - - std::vector border_vertices; - BOOST_FOREACH(halfedge_descriptor h, halfedges(pm)) - { - if(is_border(h, pm)) - border_vertices.push_back(target(h, pm)); - } - - std::vector< std::vector > identical_vertices; - internal::detect_identical_vertices(border_vertices, identical_vertices, vpm); - - BOOST_FOREACH(const std::vector& vrtcs, identical_vertices) - merge_boundary_vertices(vrtcs, pm); -} -#endif - template void merge_duplicated_vertices_in_boundary_cycles(PolygonMesh& pm) { @@ -388,14 +359,6 @@ void merge_duplicated_vertices_in_boundary_cycle( merge_duplicated_vertices_in_boundary_cycle(h, pm, parameters::all_default()); } -#if 0 -template -void merge_duplicated_boundary_vertices(PolygonMesh& pm) -{ - merge_duplicated_boundary_vertices(pm, parameters::all_default()); -} -#endif - } } // end of CGAL::Polygon_mesh_processing #endif //CGAL_POLYGON_MESH_PROCESSING_MERGE_BORDER_VERTICES_H diff --git a/Polygon_mesh_processing/test/Polygon_mesh_processing/test_merging_border_vertices.cpp b/Polygon_mesh_processing/test/Polygon_mesh_processing/test_merging_border_vertices.cpp index a3052bbd2fd..ed21427f4d1 100644 --- a/Polygon_mesh_processing/test/Polygon_mesh_processing/test_merging_border_vertices.cpp +++ b/Polygon_mesh_processing/test/Polygon_mesh_processing/test_merging_border_vertices.cpp @@ -38,49 +38,17 @@ void test_merge_duplicated_vertices_in_boundary_cycles(const char* fname, } } -#if 0 -void test_merge_duplicated_boundary_vertices(const char* fname, - std::size_t expected_nb_vertices) -{ - std::ifstream input(fname); - - Surface_mesh mesh; - if (!input || !(input >> mesh) || mesh.is_empty()) { - std::cerr << fname << " is not a valid off file.\n"; - exit(1); - } - - std::cout << "Testing merging globally " << fname << "\n"; - std::cout << " input mesh has " << vertices(mesh).size() << " vertices.\n"; - CGAL::Polygon_mesh_processing::merge_duplicated_boundary_vertices(mesh); - std::cout << " output mesh has " << vertices(mesh).size() << " vertices.\n"; - - assert(expected_nb_vertices == 0 || - expected_nb_vertices == vertices(mesh).size()); - if (expected_nb_vertices==0) - { - std::cout << "writting output to out2.off\n"; - std::ofstream output("out2.off"); - output << std::setprecision(17); - output << mesh; - } -} -#endif - int main(int argc, char** argv) { if (argc==1) { test_merge_duplicated_vertices_in_boundary_cycles("data/merge_points.off", 43); - // test_merge_duplicated_boundary_vertices("data/merge_points.off", 40); } else { for (int i=1; i< argc; ++i) - { test_merge_duplicated_vertices_in_boundary_cycles(argv[i], 0); - // test_merge_duplicated_boundary_vertices(argv[i], 0); - } } - return 0; + + return EXIT_SUCCESS; } diff --git a/Polygon_mesh_processing/test/Polygon_mesh_processing/test_predicates.cpp b/Polygon_mesh_processing/test/Polygon_mesh_processing/test_predicates.cpp index 4dd421fe533..2124d1382ef 100644 --- a/Polygon_mesh_processing/test/Polygon_mesh_processing/test_predicates.cpp +++ b/Polygon_mesh_processing/test/Polygon_mesh_processing/test_predicates.cpp @@ -244,5 +244,5 @@ int main() test_vertices_merge_and_duplication("data/blobby.off"); test_needles_and_caps("data_degeneracies/caps_and_needles.off"); - return 0; + return EXIT_SUCCESS; } From a9897111c46092a5b820b6a90dc4fd3a3b935ceb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mael=20Rouxel-Labb=C3=A9?= Date: Mon, 23 Jul 2018 12:14:11 +0200 Subject: [PATCH 031/116] Reorganized the new functions --- .../Isotropic_remeshing/remesh_impl.h | 2 +- .../CGAL/Polygon_mesh_processing/repair.h | 41 +++++++++++++++-- .../{helpers.h => shape_predicates.h} | 46 ++++--------------- .../Polygon_mesh_processing/CMakeLists.txt | 2 +- ...edicates.cpp => test_shape_predicates.cpp} | 2 +- .../Plugins/PMP/Degenerated_faces_plugin.cpp | 4 +- .../Plugins/PMP/Selection_plugin.cpp | 2 +- .../Edit_polyhedron_plugin.cpp | 2 +- .../demo/Polyhedron/Scene_polyhedron_item.cpp | 2 +- .../Scene_polyhedron_selection_item.cpp | 2 +- .../Polyhedron/Scene_surface_mesh_item.cpp | 2 +- 11 files changed, 57 insertions(+), 50 deletions(-) rename Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/{helpers.h => shape_predicates.h} (91%) rename Polygon_mesh_processing/test/Polygon_mesh_processing/{test_predicates.cpp => test_shape_predicates.cpp} (99%) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Isotropic_remeshing/remesh_impl.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Isotropic_remeshing/remesh_impl.h index e5e914402db..5ffc9952be8 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Isotropic_remeshing/remesh_impl.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Isotropic_remeshing/remesh_impl.h @@ -30,7 +30,7 @@ #include #include #include -#include +#include #include #include diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/repair.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/repair.h index 15c77eeb5c1..26986cb8153 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/repair.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/repair.h @@ -41,7 +41,7 @@ #include #include -#include +#include #include #include @@ -146,6 +146,8 @@ namespace debug{ } } //end of namespace debug +namespace internal { + template struct Less_vertex_point{ typedef typename boost::graph_traits::vertex_descriptor vertex_descriptor; @@ -159,6 +161,8 @@ struct Less_vertex_point{ } }; +} // end namespace internal + template OutputIterator degenerate_faces(const TriangleMesh& tm, @@ -959,7 +963,7 @@ std::size_t remove_degenerate_faces(TriangleMesh& tmesh, // preliminary step to check if the operation is possible // sort the boundary points along the common supporting line // we first need a reference point - typedef Less_vertex_point Less_vertex; + typedef internal::Less_vertex_point Less_vertex; std::pair< typename std::set::iterator, typename std::set::iterator > ref_vertices = @@ -1322,6 +1326,38 @@ struct Vertex_collector } // end namespace internal +/// \ingroup PMP_repairing_grp +/// checks whether a vertex of a triangle mesh is non-manifold. +/// +/// @tparam TriangleMesh a model of `HalfedgeListGraph` +/// +/// @param v a vertex of `tm` +/// @param tm a triangle mesh containing `v` +/// +/// \return `true` if the vertex is non-manifold, `false` otherwise. +template +bool is_non_manifold_vertex(typename boost::graph_traits::vertex_descriptor v, + const TriangleMesh& tm) +{ + CGAL_assertion(CGAL::is_triangle_mesh(tm)); + + typedef typename boost::graph_traits::halfedge_descriptor halfedge_descriptor; + + boost::unordered_set halfedges_handled; + BOOST_FOREACH(halfedge_descriptor h, halfedges_around_target(v, tm)) + halfedges_handled.insert(h); + + BOOST_FOREACH(halfedge_descriptor h, halfedges(tm)) + { + if(v == target(h, tm)) + { + if(halfedges_handled.count(h) == 0) + return true; + } + } + return false; +} + /// \ingroup PMP_repairing_grp /// duplicates all non-manifold vertices of the input mesh. /// @@ -1449,7 +1485,6 @@ std::size_t duplicate_non_manifold_vertices(TriangleMesh& tm) return duplicate_non_manifold_vertices(tm, parameters::all_default()); } - /// \ingroup PMP_repairing_grp /// removes the isolated vertices from any polygon mesh. /// A vertex is considered isolated if it is not incident to any simplex diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/helpers.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/shape_predicates.h similarity index 91% rename from Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/helpers.h rename to Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/shape_predicates.h index 8e9cfeef182..dc66aecacf9 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/helpers.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/shape_predicates.h @@ -20,8 +20,8 @@ // Author(s) : Konstantinos Katrioplas, // Mael Rouxel-Labbé -#ifndef CGAL_POLYGON_MESH_PROCESSING_HELPERS_H -#define CGAL_POLYGON_MESH_PROCESSING_HELPERS_H +#ifndef CGAL_POLYGON_MESH_PROCESSING_SHAPE_PREDICATES_H +#define CGAL_POLYGON_MESH_PROCESSING_SHAPE_PREDICATES_H #include #include @@ -42,38 +42,6 @@ namespace CGAL { namespace Polygon_mesh_processing { -/// \ingroup PMP_repairing_grp -/// checks whether a vertex of a triangle mesh is non-manifold. -/// -/// @tparam TriangleMesh a model of `HalfedgeListGraph` -/// -/// @param v a vertex of `tm` -/// @param tm a triangle mesh containing `v` -/// -/// \return `true` if the vertrex is non-manifold, `false` otherwise. -template -bool is_non_manifold_vertex(typename boost::graph_traits::vertex_descriptor v, - const TriangleMesh& tm) -{ - CGAL_assertion(CGAL::is_triangle_mesh(tm)); - - typedef typename boost::graph_traits::halfedge_descriptor halfedge_descriptor; - - boost::unordered_set halfedges_handled; - BOOST_FOREACH(halfedge_descriptor h, halfedges_around_target(v, tm)) - halfedges_handled.insert(h); - - BOOST_FOREACH(halfedge_descriptor h, halfedges(tm)) - { - if(v == target(h, tm)) - { - if(halfedges_handled.count(h) == 0) - return true; - } - } - return false; -} - /// \ingroup PMP_repairing_grp /// checks whether an edge is degenerate. /// An edge is considered degenerate if the geometric positions of its two extremities are identical. @@ -202,7 +170,7 @@ bool is_degenerate_triangle_face(typename boost::graph_traits::fac /// \cgalParamEnd /// \cgalNamedParamsEnd /// -/// \return the smallest halfedge if the triangle face is a needle, and a null halfedge otherwise. +/// \return the shortest halfedge if the triangle face is a needle, and a null halfedge otherwise. template typename boost::graph_traits::halfedge_descriptor is_needle_triangle_face(typename boost::graph_traits::face_descriptor f, @@ -287,11 +255,13 @@ is_needle_triangle_face(typename boost::graph_traits::face_descrip /// `CGAL::vertex_point_t` should be available in `TriangleMesh` /// \cgalParamEnd /// \cgalParamBegin{geom_traits} a geometric traits class instance. -/// The traits class must provide the nested type `Point_3` +/// The traits class must provide the nested type `Point_3` and +/// the nested functors `Compute_squared_distance_3`, `Construct_vector_3`, +/// and `Compute_scalar_product_3`. /// \cgalParamEnd /// \cgalNamedParamsEnd /// -/// \return `true` if the triangle face is a cap +/// \return the halfedge opposite of the largest angle if the face is a cap, and a null halfedge otherwise. template typename boost::graph_traits::halfedge_descriptor is_cap_triangle_face(typename boost::graph_traits::face_descriptor f, @@ -361,4 +331,4 @@ is_cap_triangle_face(typename boost::graph_traits::face_descriptor } } // end namespaces CGAL and PMP -#endif // CGAL_POLYGON_MESH_PROCESSING_HELPERS_H +#endif // CGAL_POLYGON_MESH_PROCESSING_SHAPE_PREDICATES_H diff --git a/Polygon_mesh_processing/test/Polygon_mesh_processing/CMakeLists.txt b/Polygon_mesh_processing/test/Polygon_mesh_processing/CMakeLists.txt index 0c26363fca7..d8436cb8f30 100644 --- a/Polygon_mesh_processing/test/Polygon_mesh_processing/CMakeLists.txt +++ b/Polygon_mesh_processing/test/Polygon_mesh_processing/CMakeLists.txt @@ -102,7 +102,7 @@ endif() create_single_source_cgal_program("test_pmp_transform.cpp") create_single_source_cgal_program("remove_degeneracies_test.cpp") create_single_source_cgal_program("test_merging_border_vertices.cpp") - create_single_source_cgal_program("test_predicates.cpp") + create_single_source_cgal_program("test_shape_predicates.cpp") if( TBB_FOUND ) CGAL_target_use_TBB(test_pmp_distance) diff --git a/Polygon_mesh_processing/test/Polygon_mesh_processing/test_predicates.cpp b/Polygon_mesh_processing/test/Polygon_mesh_processing/test_shape_predicates.cpp similarity index 99% rename from Polygon_mesh_processing/test/Polygon_mesh_processing/test_predicates.cpp rename to Polygon_mesh_processing/test/Polygon_mesh_processing/test_shape_predicates.cpp index 2124d1382ef..7526ac80bec 100644 --- a/Polygon_mesh_processing/test/Polygon_mesh_processing/test_predicates.cpp +++ b/Polygon_mesh_processing/test/Polygon_mesh_processing/test_shape_predicates.cpp @@ -2,8 +2,8 @@ #include -#include #include +#include #include diff --git a/Polyhedron/demo/Polyhedron/Plugins/PMP/Degenerated_faces_plugin.cpp b/Polyhedron/demo/Polyhedron/Plugins/PMP/Degenerated_faces_plugin.cpp index b7c359e9ab1..9df12173d1f 100644 --- a/Polyhedron/demo/Polyhedron/Plugins/PMP/Degenerated_faces_plugin.cpp +++ b/Polyhedron/demo/Polyhedron/Plugins/PMP/Degenerated_faces_plugin.cpp @@ -19,7 +19,9 @@ #include #include #include -#include + +#include + #ifdef USE_SURFACE_MESH typedef Scene_surface_mesh_item Scene_facegraph_item; #else diff --git a/Polyhedron/demo/Polyhedron/Plugins/PMP/Selection_plugin.cpp b/Polyhedron/demo/Polyhedron/Plugins/PMP/Selection_plugin.cpp index 28d0d5f779a..0b471fddc9b 100644 --- a/Polyhedron/demo/Polyhedron/Plugins/PMP/Selection_plugin.cpp +++ b/Polyhedron/demo/Polyhedron/Plugins/PMP/Selection_plugin.cpp @@ -27,7 +27,7 @@ #include #include #include -#include +#include #include #ifdef USE_SURFACE_MESH diff --git a/Polyhedron/demo/Polyhedron/Plugins/Surface_mesh_deformation/Edit_polyhedron_plugin.cpp b/Polyhedron/demo/Polyhedron/Plugins/Surface_mesh_deformation/Edit_polyhedron_plugin.cpp index 18a5d1bb8e0..d0dab116457 100644 --- a/Polyhedron/demo/Polyhedron/Plugins/Surface_mesh_deformation/Edit_polyhedron_plugin.cpp +++ b/Polyhedron/demo/Polyhedron/Plugins/Surface_mesh_deformation/Edit_polyhedron_plugin.cpp @@ -9,7 +9,7 @@ #include "Scene_edit_polyhedron_item.h" #include "Scene_polyhedron_selection_item.h" #include -#include +#include #include #include #include diff --git a/Polyhedron/demo/Polyhedron/Scene_polyhedron_item.cpp b/Polyhedron/demo/Polyhedron/Scene_polyhedron_item.cpp index a71037f40f7..df752a67272 100644 --- a/Polyhedron/demo/Polyhedron/Scene_polyhedron_item.cpp +++ b/Polyhedron/demo/Polyhedron/Scene_polyhedron_item.cpp @@ -15,7 +15,7 @@ #include #include #include -#include +#include #include #include #include diff --git a/Polyhedron/demo/Polyhedron/Scene_polyhedron_selection_item.cpp b/Polyhedron/demo/Polyhedron/Scene_polyhedron_selection_item.cpp index 6696a5ac9bc..d3d65dcd44c 100644 --- a/Polyhedron/demo/Polyhedron/Scene_polyhedron_selection_item.cpp +++ b/Polyhedron/demo/Polyhedron/Scene_polyhedron_selection_item.cpp @@ -2,7 +2,7 @@ #include "Scene_polyhedron_selection_item.h" #include #include -#include +#include #include #include #include diff --git a/Polyhedron/demo/Polyhedron/Scene_surface_mesh_item.cpp b/Polyhedron/demo/Polyhedron/Scene_surface_mesh_item.cpp index b2b56ff87d9..5b790daed6f 100644 --- a/Polyhedron/demo/Polyhedron/Scene_surface_mesh_item.cpp +++ b/Polyhedron/demo/Polyhedron/Scene_surface_mesh_item.cpp @@ -25,7 +25,7 @@ #include #include #include -#include +#include #include #include From 3934ad351e3de218049b7887ff6f0f9aa7bd6be9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mael=20Rouxel-Labb=C3=A9?= Date: Mon, 23 Jul 2018 16:03:05 +0200 Subject: [PATCH 032/116] Fixed BGL concepts --- BGL/doc/BGL/Concepts/EdgeListGraph.h | 8 ++++---- BGL/doc/BGL/PackageDescription.txt | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/BGL/doc/BGL/Concepts/EdgeListGraph.h b/BGL/doc/BGL/Concepts/EdgeListGraph.h index 7d7980aa172..d763d3a01fe 100644 --- a/BGL/doc/BGL/Concepts/EdgeListGraph.h +++ b/BGL/doc/BGL/Concepts/EdgeListGraph.h @@ -38,16 +38,16 @@ num_edges(const EdgeListGraph& g); /*! \relates EdgeListGraph -returns the source vertex of `h`. +returns the source vertex of `e`. */ template boost::graph_traits::vertex_descriptor -source(boost::graph_traits::halfedge_descriptor h, const EdgeListGraph& g); +source(boost::graph_traits::edge_descriptor e, const EdgeListGraph& g); /*! \relates EdgeListGraph -returns the target vertex of `h`. +returns the target vertex of `e`. */ template boost::graph_traits::vertex_descriptor -target(boost::graph_traits::halfedge_descriptor h, const EdgeListGraph& g); +target(boost::graph_traits::edge_descriptor e, const EdgeListGraph& g); diff --git a/BGL/doc/BGL/PackageDescription.txt b/BGL/doc/BGL/PackageDescription.txt index d40b1ec6ecf..86efff4c7a8 100644 --- a/BGL/doc/BGL/PackageDescription.txt +++ b/BGL/doc/BGL/PackageDescription.txt @@ -122,12 +122,12 @@ and adds the requirement for traversal of all edges in a graph. An upper bound of the number of edges of the graph - `source(g)` + `source(e, g)` `vertex_descriptor` The source vertex of `e` - `target(g)` + `target(e, g)` `vertex_descriptor` The target vertex of `e` From e3da86cff373a63ae8d840b22a79e346cc37c13d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mael=20Rouxel-Labb=C3=A9?= Date: Mon, 23 Jul 2018 16:07:33 +0200 Subject: [PATCH 033/116] Renamed removed_(null-->degenerate)_edges() for consistency --- .../CGAL/Polygon_mesh_processing/repair.h | 46 +++++++++---------- 1 file changed, 22 insertions(+), 24 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/repair.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/repair.h index 26986cb8153..7323c9b73db 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/repair.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/repair.h @@ -365,10 +365,9 @@ remove_a_border_edge(typename boost::graph_traits::edge_descriptor } template -std::size_t remove_null_edges( - const EdgeRange& edge_range, - TriangleMesh& tmesh, - const NamedParameters& np) +std::size_t remove_degenerate_edges(const EdgeRange& edge_range, + TriangleMesh& tmesh, + const NamedParameters& np) { CGAL_assertion(CGAL::is_triangle_mesh(tmesh)); @@ -385,27 +384,27 @@ std::size_t remove_null_edges( typedef typename GetVertexPointMap::type VertexPointMap; VertexPointMap vpmap = choose_param(get_param(np, internal_np::vertex_point), get_property_map(vertex_point, tmesh)); + typedef typename GetGeomTraits::type Traits; - Traits traits = choose_param(get_param(np, internal_np::geom_traits), Traits()); std::size_t nb_deg_faces = 0; // collect edges of length 0 - std::set null_edges_to_remove; + std::set degenerate_edges_to_remove; BOOST_FOREACH(edge_descriptor ed, edge_range) { - if ( traits.equal_3_object()(get(vpmap, target(ed, tmesh)), get(vpmap, source(ed, tmesh))) ) - null_edges_to_remove.insert(ed); + if(is_degenerate_edge(ed, tmesh, np)) + degenerate_edges_to_remove.insert(ed); } #ifdef CGAL_PMP_REMOVE_DEGENERATE_FACES_DEBUG - std::cout << "Found " << null_edges_to_remove.size() << " null edges.\n"; + std::cout << "Found " << degenerate_edges_to_remove.size() << " null edges.\n"; #endif - while (!null_edges_to_remove.empty()) + while (!degenerate_edges_to_remove.empty()) { - edge_descriptor ed = *null_edges_to_remove.begin(); - null_edges_to_remove.erase(null_edges_to_remove.begin()); + edge_descriptor ed = *degenerate_edges_to_remove.begin(); + degenerate_edges_to_remove.erase(degenerate_edges_to_remove.begin()); halfedge_descriptor h = halfedge(ed, tmesh); @@ -415,12 +414,12 @@ std::size_t remove_null_edges( if ( face(h, tmesh)!=GT::null_face() ) { ++nb_deg_faces; - null_edges_to_remove.erase(edge(prev(h, tmesh), tmesh)); + degenerate_edges_to_remove.erase(edge(prev(h, tmesh), tmesh)); } if (face(opposite(h, tmesh), tmesh)!=GT::null_face()) { ++nb_deg_faces; - null_edges_to_remove.erase(edge(prev(opposite(h, tmesh), tmesh), tmesh)); + degenerate_edges_to_remove.erase(edge(prev(opposite(h, tmesh), tmesh), tmesh)); } //now remove the edge CGAL::Euler::collapse_edge(ed, tmesh); @@ -435,7 +434,7 @@ std::size_t remove_null_edges( if (is_triangle(hd, tmesh)) { Euler::fill_hole(hd, tmesh); - null_edges_to_remove.insert(ed); + degenerate_edges_to_remove.insert(ed); continue; } } @@ -621,7 +620,7 @@ std::size_t remove_null_edges( // remove edges BOOST_FOREACH(edge_descriptor ed, edges_to_remove) { - null_edges_to_remove.erase(ed); + degenerate_edges_to_remove.erase(ed); remove_edge(ed, tmesh); } @@ -641,8 +640,8 @@ std::size_t remove_null_edges( put(vpmap, target(new_hd, tmesh), pt); BOOST_FOREACH(halfedge_descriptor hd, halfedges_around_target(new_hd, tmesh)) - if ( traits.equal_3_object()(get(vpmap, target(hd, tmesh)), get(vpmap, source(hd, tmesh))) ) - null_edges_to_remove.insert(edge(hd, tmesh)); + if(is_degenerate_edge(edge(hd, tmesh), tmesh, np)) + degenerate_edges_to_remove.insert(edge(hd, tmesh)); CGAL_assertion( is_valid_polygon_mesh(tmesh) ); } @@ -652,12 +651,10 @@ std::size_t remove_null_edges( } template -std::size_t remove_null_edges( - const EdgeRange& edge_range, - TriangleMesh& tmesh) +std::size_t remove_degenerate_edges(const EdgeRange& edge_range, + TriangleMesh& tmesh) { - return remove_null_edges(edge_range, tmesh, - parameters::all_default()); + return remove_degenerate_edges(edge_range, tmesh, parameters::all_default()); } /// \ingroup PMP_repairing_grp @@ -717,8 +714,9 @@ std::size_t remove_degenerate_faces(TriangleMesh& tmesh, typedef typename boost::property_traits::value_type Point_3; typedef typename boost::property_traits::reference Point_ref; + // First remove edges of length 0 - std::size_t nb_deg_faces = remove_null_edges(edges(tmesh), tmesh, np); + std::size_t nb_deg_faces = remove_degenerate_edges(edges(tmesh), tmesh, np); #ifdef CGAL_PMP_REMOVE_DEGENERATE_FACES_DEBUG { From 31609f2002f379a49291e8d1e498eec8e7b0771d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mael=20Rouxel-Labb=C3=A9?= Date: Mon, 23 Jul 2018 16:31:51 +0200 Subject: [PATCH 034/116] Renamed function and removed obsolete code --- .../merge_border_vertices.h | 52 ++----------------- 1 file changed, 4 insertions(+), 48 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/merge_border_vertices.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/merge_border_vertices.h index c530a82b51d..764022e0a6e 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/merge_border_vertices.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/merge_border_vertices.h @@ -37,48 +37,6 @@ namespace Polygon_mesh_processing{ namespace internal { -#if 0 -// warning: vertices will be altered (sorted) -template -void detect_identical_vertices(std::vector& vertices, - std::vector< std::vector >& identical_vertices, - Vpm vpm) -{ - typedef typename boost::property_traits::value_type Point_3; - - // sort vertices using their point to ease the detection - // of vertices with identical points - CGAL::Property_map_to_unary_function Get_point(vpm); - std::sort( vertices.begin(), vertices.end(), - boost::bind(std::less(), boost::bind(Get_point,_1), - boost::bind(Get_point, _2)) ); - std::size_t nbv=vertices.size(); - std::size_t i=1; - - while(i!=nbv) - { - if (get(vpm, vertices[i]) == get(vpm, vertices[i-1])) - { - identical_vertices.push_back( std::vector() ); - identical_vertices.back().push_back(vertices[i-1]); - identical_vertices.back().push_back(vertices[i]); - while(++i!=nbv) - { - if (get(vpm, vertices[i]) == get(vpm, vertices[i-1])) - identical_vertices.back().push_back(vertices[i]); - else - { - ++i; - break; - } - } - } - else - ++i; - } -} -#endif - template struct Less_on_point_of_target { @@ -188,7 +146,6 @@ void detect_identical_mergeable_vertices( /// @param pm the polygon mesh. /// @param out an output iterator where the list of halfedges will be put. /// -/// @todo Maybe move to BGL /// @todo It should make sense to also return the length of each cycle. /// @todo It should probably go into BGL package. template @@ -221,17 +178,16 @@ extract_boundary_cycles(PolygonMesh& pm, /// @param sorted_hedges a sorted list of halfedges. /// @param pm the polygon mesh which contains the list of halfedges. /// -/// @todo rename me to `merge_vertices_in_range` because I merge any king of vertices in the list. template -void merge_boundary_vertices_in_cycle(const HalfedgeRange& sorted_hedges, - PolygonMesh& pm) +void merge_vertices_in_range(const HalfedgeRange& sorted_hedges, + PolygonMesh& pm) { typedef typename boost::graph_traits::halfedge_descriptor halfedge_descriptor; typedef typename boost::graph_traits::vertex_descriptor vertex_descriptor; halfedge_descriptor in_h_kept = *boost::begin(sorted_hedges); halfedge_descriptor out_h_kept = next(in_h_kept, pm); - vertex_descriptor v_kept=target(in_h_kept, pm); + vertex_descriptor v_kept = target(in_h_kept, pm); std::vector vertices_to_rm; @@ -311,7 +267,7 @@ void merge_duplicated_vertices_in_boundary_cycle( { start=hedges.front(); // hedges are sorted along the cycle - merge_boundary_vertices_in_cycle(hedges, pm); + merge_vertices_in_range(hedges, pm); } } From 018195d15df358c59a4ac9a613ce00c83c0fab20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mael=20Rouxel-Labb=C3=A9?= Date: Mon, 23 Jul 2018 17:09:56 +0200 Subject: [PATCH 035/116] Documented 'degenerate_faces' and add 'degenerate_edges' --- .../CGAL/Polygon_mesh_processing/repair.h | 159 +++++++++++++++--- 1 file changed, 137 insertions(+), 22 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/repair.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/repair.h index 7323c9b73db..f41ce6aa276 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/repair.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/repair.h @@ -24,10 +24,6 @@ #include -#include -#include - -#include #include #include #include @@ -46,17 +42,28 @@ #include #include +#include + #ifdef CGAL_PMP_REMOVE_DEGENERATE_FACES_DEBUG #include #include -#include -#include #endif +#include +#include +#include + +#include +#include +#include +#include +#include +#include + namespace CGAL{ namespace Polygon_mesh_processing { - namespace debug{ + template std::ostream& dump_edge_neighborhood( typename boost::graph_traits::edge_descriptor ed, @@ -144,6 +151,7 @@ namespace debug{ << vids[ target(next(next(halfedge(f, tm), tm), tm), tm) ] << "\n"; } } + } //end of namespace debug namespace internal { @@ -163,15 +171,104 @@ struct Less_vertex_point{ } // end namespace internal +/// \ingroup PMP_repairing_grp +/// collects the degenerate edges within a given range of edges. +/// +/// @tparam EdgeRange a model of `Range` with value type `boost::graph_traits::edge_descriptor` +/// @tparam TriangleMesh a model of `EdgeListGraph` +/// @tparam NamedParameters a sequence of \ref pmp_namedparameters "Named Parameters" +/// +/// @param edges a subset of edges of `tm` +/// @param tm a triangle mesh +/// @param np optional \ref pmp_namedparameters "Named Parameters" described below +/// +/// \cgalNamedParamsBegin +/// \cgalParamBegin{vertex_point_map} the property map with the points associated to the vertices of `tm`. +/// The type of this map is model of `ReadWritePropertyMap`. +/// If this parameter is omitted, an internal property map for +/// `CGAL::vertex_point_t` should be available in `TriangleMesh` +/// \cgalParamEnd +/// \cgalParamBegin{geom_traits} a geometric traits class instance. +/// The traits class must provide the nested type `Point_3`, +/// and the nested functor `Equal_3` to check whether two points are identical. +/// \cgalParamEnd +/// \cgalNamedParamsEnd +template +OutputIterator degenerate_edges(const EdgeRange& edges, + const TriangleMesh& tm, + OutputIterator out, + const NamedParameters& np) +{ + typedef typename boost::graph_traits::edge_descriptor edge_descriptor; + + BOOST_FOREACH(edge_descriptor ed, edges) + { + if(is_degenerate_edge(ed, tm, np)) + *out++ = ed; + } + return out; +} + +template +OutputIterator degenerate_edges(const EdgeRange& edges, + const TriangleMesh& tm, + OutputIterator out, + typename boost::disable_if_c< + CGAL::is_iterator::value + >::type* = 0) +{ + return degenerate_edges(edges, tm, out, CGAL::parameters::all_default()); +} + template +OutputIterator degenerate_edges(const TriangleMesh& tm, + OutputIterator out, + const NamedParameters& np, + typename boost::enable_if_c< + CGAL::is_iterator::value + >::type* = 0) +{ + return degenerate_edges(edges(tm), tm, out, np); +} + +template OutputIterator -degenerate_faces(const TriangleMesh& tm, - OutputIterator out, - const NamedParameters& np) +degenerate_edges(const TriangleMesh& tm, OutputIterator out) +{ + return degenerate_edges(edges(tm), tm, out, CGAL::parameters::all_default()); +} + +/// \ingroup PMP_repairing_grp +/// collects the degenerate faces within a given range of faces. +/// +/// @tparam FaceRange a model of `Range` with value type `boost::graph_traits::face_descriptor` +/// @tparam TriangleMesh a model of `FaceGraph` +/// @tparam NamedParameters a sequence of \ref pmp_namedparameters "Named Parameters" +/// +/// @param faces a subset of faces of `tm` +/// @param tm a triangle mesh +/// @param np optional \ref pmp_namedparameters "Named Parameters" described below +/// +/// \cgalNamedParamsBegin +/// \cgalParamBegin{vertex_point_map} the property map with the points associated to the vertices of `tm`. +/// The type of this map is model of `ReadWritePropertyMap`. +/// If this parameter is omitted, an internal property map for +/// `CGAL::vertex_point_t` should be available in `TriangleMesh` +/// \cgalParamEnd +/// \cgalParamBegin{geom_traits} a geometric traits class instance. +/// The traits class must provide the nested functor `Collinear_3` +/// to check whether three points are collinear. +/// \cgalParamEnd +/// \cgalNamedParamsEnd +template +OutputIterator degenerate_faces(const FaceRange& faces, + const TriangleMesh& tm, + OutputIterator out, + const NamedParameters& np) { typedef typename boost::graph_traits::face_descriptor face_descriptor; - BOOST_FOREACH(face_descriptor fd, faces(tm)) + BOOST_FOREACH(face_descriptor fd, faces) { if(is_degenerate_triangle_face(fd, tm, np)) *out++ = fd; @@ -179,11 +276,32 @@ degenerate_faces(const TriangleMesh& tm, return out; } -template -OutputIterator -degenerate_faces(const TriangleMesh& tm, OutputIterator out) +template +OutputIterator degenerate_faces(const FaceRange& faces, + const TriangleMesh& tm, + OutputIterator out, + typename boost::disable_if_c< + CGAL::is_iterator::value + >::type* = 0) { - return degenerate_faces(tm, out, CGAL::parameters::all_default()); + return degenerate_faces(faces, tm, out, CGAL::parameters::all_default()); +} + +template +OutputIterator degenerate_faces(const TriangleMesh& tm, + OutputIterator out, + const NamedParameters& np, + typename boost::enable_if_c< + CGAL::is_iterator::value + >::type* = 0) +{ + return degenerate_faces(faces(tm), tm, out, np); +} + +template +OutputIterator degenerate_faces(const TriangleMesh& tm, OutputIterator out) +{ + return degenerate_faces(faces(tm), tm, out, CGAL::parameters::all_default()); } // this function remove a border edge even if it does not satisfy the link condition. @@ -391,15 +509,12 @@ std::size_t remove_degenerate_edges(const EdgeRange& edge_range, // collect edges of length 0 std::set degenerate_edges_to_remove; - BOOST_FOREACH(edge_descriptor ed, edge_range) - { - if(is_degenerate_edge(ed, tmesh, np)) - degenerate_edges_to_remove.insert(ed); - } + degenerate_edges(edge_range, tmesh, std::inserter(degenerate_edges_to_remove, + degenerate_edges_to_remove.end())); - #ifdef CGAL_PMP_REMOVE_DEGENERATE_FACES_DEBUG +#ifdef CGAL_PMP_REMOVE_DEGENERATE_FACES_DEBUG std::cout << "Found " << degenerate_edges_to_remove.size() << " null edges.\n"; - #endif +#endif while (!degenerate_edges_to_remove.empty()) { From 3d0c0d48d4f74c6a5758e68c1c8e8e6b9c72bde9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mael=20Rouxel-Labb=C3=A9?= Date: Mon, 23 Jul 2018 17:28:36 +0200 Subject: [PATCH 036/116] Minor doc changes --- .../include/CGAL/Polygon_mesh_processing/repair.h | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/repair.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/repair.h index f41ce6aa276..4e1fe05fa1e 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/repair.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/repair.h @@ -786,15 +786,15 @@ std::size_t remove_degenerate_edges(const EdgeRange& edge_range, /// @param np optional \ref pmp_namedparameters "Named Parameters" described below /// /// \cgalNamedParamsBegin -/// \cgalParamBegin{vertex_point_map} the property map with the points associated to the vertices of `pmesh`. The type of this map is model of `ReadWritePropertyMap`. -/// If this parameter is omitted, an internal property map for -/// `CGAL::vertex_point_t` must be available in `TriangleMesh` -/// \cgalParamEnd +/// \cgalParamBegin{vertex_point_map} the property map with the points associated to the vertices of `pmesh`. +/// The type of this map is model of `ReadWritePropertyMap`. +/// If this parameter is omitted, an internal property map for +/// `CGAL::vertex_point_t` must be available in `TriangleMesh` +/// \cgalParamEnd /// \cgalParamBegin{geom_traits} a geometric traits class instance. /// The traits class must provide the nested type `Point_3`, /// and the nested functors : /// - `Compare_distance_3` to compute the distance between 2 points -/// - `Collinear_are_ordered_along_line_3` to check whether 3 collinear points are ordered /// - `Collinear_3` to check whether 3 points are collinear /// - `Less_xyz_3` to compare lexicographically two points /// - `Equal_3` to check whether 2 points are identical @@ -804,6 +804,7 @@ std::size_t remove_degenerate_edges(const EdgeRange& edge_range, /// /// \todo the function might not be able to remove all degenerate faces. /// We should probably do something with the return type. +/// /// \return number of removed degenerate faces template std::size_t remove_degenerate_faces(TriangleMesh& tmesh, @@ -1472,7 +1473,7 @@ bool is_non_manifold_vertex(typename boost::graph_traits::vertex_d } /// \ingroup PMP_repairing_grp -/// duplicates all non-manifold vertices of the input mesh. +/// duplicates all the non-manifold vertices of the input mesh. /// /// @tparam TriangleMesh a model of `HalfedgeListGraph` and `MutableHalfedgeGraph` /// @tparam NamedParameters a sequence of \ref pmp_namedparameters "Named Parameters" From 6c0d6a79eb9372f7b1378cfe302e96f8055bc0ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mael=20Rouxel-Labb=C3=A9?= Date: Mon, 23 Jul 2018 17:28:44 +0200 Subject: [PATCH 037/116] Test degenerate_edges/faces --- .../remove_degeneracies_test.cpp | 48 ++++++++++++++----- 1 file changed, 36 insertions(+), 12 deletions(-) diff --git a/Polygon_mesh_processing/test/Polygon_mesh_processing/remove_degeneracies_test.cpp b/Polygon_mesh_processing/test/Polygon_mesh_processing/remove_degeneracies_test.cpp index 8b6488320e1..b37cc61eb92 100644 --- a/Polygon_mesh_processing/test/Polygon_mesh_processing/remove_degeneracies_test.cpp +++ b/Polygon_mesh_processing/test/Polygon_mesh_processing/remove_degeneracies_test.cpp @@ -11,11 +11,34 @@ //the last test (on trihole.off) does not terminate // -typedef CGAL::Exact_predicates_inexact_constructions_kernel K; +namespace PMP = CGAL::Polygon_mesh_processing; -typedef CGAL::Surface_mesh Surface_mesh; +typedef CGAL::Exact_predicates_inexact_constructions_kernel K; -void fix(const char* fname) +typedef CGAL::Surface_mesh Surface_mesh; + +typedef boost::graph_traits::edge_descriptor edge_descriptor; +typedef boost::graph_traits::face_descriptor face_descriptor; + +void detect_degeneracies(const Surface_mesh& mesh) +{ + std::vector dfaces; + + PMP::degenerate_faces(mesh, std::back_inserter(dfaces)); + PMP::degenerate_faces(faces(mesh), mesh, std::back_inserter(dfaces)); + PMP::degenerate_faces(mesh, std::back_inserter(dfaces), CGAL::parameters::all_default()); + PMP::degenerate_faces(faces(mesh), mesh, std::back_inserter(dfaces), CGAL::parameters::all_default()); + assert(!dfaces.empty()); + + std::set dedges; + PMP::degenerate_edges(mesh, std::inserter(dedges, dedges.end())); + PMP::degenerate_edges(edges(mesh), mesh, std::inserter(dedges, dedges.begin())); + PMP::degenerate_edges(mesh, std::inserter(dedges, dedges.end()), CGAL::parameters::all_default()); + PMP::degenerate_edges(edges(mesh), mesh, std::inserter(dedges, dedges.begin()), CGAL::parameters::all_default()); + assert(dedges.empty()); +} + +void fix_degeneracies(const char* fname) { std::ifstream input(fname); @@ -24,21 +47,22 @@ void fix(const char* fname) std::cerr << fname << " is not a valid off file.\n"; exit(1); } - CGAL::Polygon_mesh_processing::remove_degenerate_faces(mesh); + detect_degeneracies(mesh); + + CGAL::Polygon_mesh_processing::remove_degenerate_faces(mesh); assert( CGAL::is_valid_polygon_mesh(mesh) ); } - int main() { - fix("data_degeneracies/degtri_2dt_1edge_split_twice.off"); - fix("data_degeneracies/degtri_four-2.off"); - fix("data_degeneracies/degtri_four.off"); - fix("data_degeneracies/degtri_on_border.off"); - fix("data_degeneracies/degtri_three.off"); - fix("data_degeneracies/degtri_single.off"); - fix("data_degeneracies/trihole.off"); + fix_degeneracies("data_degeneracies/degtri_2dt_1edge_split_twice.off"); + fix_degeneracies("data_degeneracies/degtri_four-2.off"); + fix_degeneracies("data_degeneracies/degtri_four.off"); + fix_degeneracies("data_degeneracies/degtri_on_border.off"); + fix_degeneracies("data_degeneracies/degtri_three.off"); + fix_degeneracies("data_degeneracies/degtri_single.off"); + fix_degeneracies("data_degeneracies/trihole.off"); return 0; } From 9bf6c331b946a2ab877b2614408ff16101329888 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mael=20Rouxel-Labb=C3=A9?= Date: Mon, 23 Jul 2018 17:42:11 +0200 Subject: [PATCH 038/116] Moved extract_boundary_cycles to border.h --- .../CGAL/Polygon_mesh_processing/border.h | 40 +++++++++++++++++-- .../merge_border_vertices.h | 36 ++--------------- 2 files changed, 39 insertions(+), 37 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/border.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/border.h index 6c30cb6d077..dbe1f4945a7 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/border.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/border.h @@ -24,16 +24,16 @@ #include +#include +#include +#include +#include #include #include #include #include -#include -#include -#include - #include namespace CGAL{ @@ -257,6 +257,38 @@ namespace Polygon_mesh_processing { return border_counter; } + + /// @ingroup PkgPolygonMeshProcessing + /// extracts boundary cycles as a list of halfedges, with one halfedge per border. + /// + /// @tparam PolygonMesh a model of `HalfedgeListGraph` + /// @tparam OutputIterator a model of `OutputIterator` holding objects of type + /// `boost::graph_traits::%halfedge_descriptor` + /// + /// @param pm a polygon mesh + /// @param out an output iterator where the border halfedges will be put + /// + /// @todo It could make sense to also return the length of each cycle. + /// @todo It should probably go into BGL package (like the rest of this file). + template + OutputIterator extract_boundary_cycles(PolygonMesh& pm, + OutputIterator out) + { + typedef typename boost::graph_traits::halfedge_descriptor halfedge_descriptor; + + boost::unordered_set hedge_handled; + BOOST_FOREACH(halfedge_descriptor h, halfedges(pm)) + { + if(is_border(h, pm) && hedge_handled.insert(h).second) + { + *out++ = h; + BOOST_FOREACH(halfedge_descriptor h2, halfedges_around_face(h, pm)) + hedge_handled.insert(h2); + } + } + return out; + } + } // end of namespace Polygon_mesh_processing } // end of namespace CGAL diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/merge_border_vertices.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/merge_border_vertices.h index 764022e0a6e..e58eae9d3da 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/merge_border_vertices.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/merge_border_vertices.h @@ -24,6 +24,7 @@ #include #include +#include #include #include #include @@ -31,9 +32,9 @@ #include #include -namespace CGAL{ +namespace CGAL { -namespace Polygon_mesh_processing{ +namespace Polygon_mesh_processing { namespace internal { @@ -137,37 +138,6 @@ void detect_identical_mergeable_vertices( } // end of internal -/// \ingroup PMP_repairing_grp -/// extracts boundary cycles as a list of halfedges. -/// @tparam PolygonMesh a model of `FaceListGraph` and `MutableFaceGraph`. -/// @tparam OutputIterator a model of `OutputIterator` holding objects of type -/// `boost::graph_traits::%halfedge_descriptor` -/// -/// @param pm the polygon mesh. -/// @param out an output iterator where the list of halfedges will be put. -/// -/// @todo It should make sense to also return the length of each cycle. -/// @todo It should probably go into BGL package. -template -OutputIterator -extract_boundary_cycles(PolygonMesh& pm, - OutputIterator out) -{ - typedef typename boost::graph_traits::halfedge_descriptor halfedge_descriptor; - - boost::unordered_set hedge_handled; - BOOST_FOREACH(halfedge_descriptor h, halfedges(pm)) - { - if(is_border(h, pm) && hedge_handled.insert(h).second) - { - *out++=h; - BOOST_FOREACH(halfedge_descriptor h2, halfedges_around_face(h, pm)) - hedge_handled.insert(h2); - } - } - return out; -} - /// \ingroup PMP_repairing_grp /// merges target vertices of a list of halfedges. /// Halfedges must be sorted in the list. From 5db403ae343aabf2e227d30d50f18ac7196faeb5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mael=20Rouxel-Labb=C3=A9?= Date: Mon, 23 Jul 2018 17:44:04 +0200 Subject: [PATCH 039/116] Fixed indentation --- .../CGAL/Polygon_mesh_processing/merge_border_vertices.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/merge_border_vertices.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/merge_border_vertices.h index e58eae9d3da..6f0185533ef 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/merge_border_vertices.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/merge_border_vertices.h @@ -71,8 +71,8 @@ template void detect_identical_mergeable_vertices( std::vector< std::pair >& cycle_hedges, std::vector< std::vector >& hedges_with_identical_point_target, - const PolygonMesh& pm, - Vpm vpm) + const PolygonMesh& pm, + Vpm vpm) { // sort vertices using their point to ease the detection // of vertices with identical points @@ -211,7 +211,7 @@ template void merge_duplicated_vertices_in_boundary_cycle( typename boost::graph_traits::halfedge_descriptor h, PolygonMesh& pm, - const NamedParameter& np) + const NamedParameter& np) { typedef typename boost::graph_traits::halfedge_descriptor halfedge_descriptor; typedef typename GetVertexPointMap::const_type Vpm; From d56c12c738b78f600bf9b7c595a9b037503f8b36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mael=20Rouxel-Labb=C3=A9?= Date: Tue, 24 Jul 2018 11:14:47 +0200 Subject: [PATCH 040/116] Handle degenerate edges in 'is_needle_triangle_face' --- .../include/CGAL/Polygon_mesh_processing/shape_predicates.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/shape_predicates.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/shape_predicates.h index dc66aecacf9..7136db82cbd 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/shape_predicates.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/shape_predicates.h @@ -171,6 +171,7 @@ bool is_degenerate_triangle_face(typename boost::graph_traits::fac /// \cgalNamedParamsEnd /// /// \return the shortest halfedge if the triangle face is a needle, and a null halfedge otherwise. +/// If the face contains degenerate edges, a halfedge corresponding to one of these edges is returned. template typename boost::graph_traits::halfedge_descriptor is_needle_triangle_face(typename boost::graph_traits::face_descriptor f, @@ -215,6 +216,9 @@ is_needle_triangle_face(typename boost::graph_traits::face_descrip } } + if(min_sq_length == 0) + return min_h; + const FT sq_threshold = threshold * threshold; if(max_sq_length / min_sq_length >= sq_threshold) { From 16e64caf65ededeae63383c1227411f86a34d6ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mael=20Rouxel-Labb=C3=A9?= Date: Tue, 24 Jul 2018 11:20:04 +0200 Subject: [PATCH 041/116] Handle degenerate edges in 'is_cap_triangle_face' --- .../CGAL/Polygon_mesh_processing/shape_predicates.h | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/shape_predicates.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/shape_predicates.h index 7136db82cbd..9673922342d 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/shape_predicates.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/shape_predicates.h @@ -300,8 +300,14 @@ is_cap_triangle_face(typename boost::graph_traits::face_descriptor int pos = 0; BOOST_FOREACH(halfedge_descriptor h, halfedges_around_face(h0, tm)) { - sq_lengths[pos++] = traits.compute_squared_distance_3_object()(get(vpmap, source(h, tm)), - get(vpmap, target(h, tm))); + const FT sq_d = traits.compute_squared_distance_3_object()(get(vpmap, source(h, tm)), + get(vpmap, target(h, tm))); + + // If even one edge is degenerate, it cannot be a cap + if(sq_d == 0) + return boost::graph_traits::null_halfedge(); + + sq_lengths[pos++] = sq_d; } pos = 0; @@ -316,7 +322,7 @@ is_cap_triangle_face(typename boost::graph_traits::face_descriptor const bool neg_sp = (dot_ab <= 0); const FT sq_a = sq_lengths[(pos+1)%3]; const FT sq_b = sq_lengths[pos]; - const FT sq_cos = dot_ab * dot_ab / (sq_a * sq_b); + const FT sq_cos = dot_ab * dot_ab / (sq_a * sq_b); if(neg_sp && sq_cos >= sq_threshold) return prev(h, tm); From e24b6c4dbfc7bd5f6f4a26c59fc94f626975ee57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mael=20Rouxel-Labb=C3=A9?= Date: Tue, 24 Jul 2018 14:20:01 +0200 Subject: [PATCH 042/116] Revert "remove examples using a non documented function" This reverts commit f2882073bb4feb7250e190807cda836a09a84071. + updates --- .../doc/Polygon_mesh_processing/examples.txt | 2 + Polygon_mesh_processing/dont_submit | 2 + .../Polygon_mesh_processing/CMakeLists.txt | 6 +-- .../remove_degeneracies_example.cpp | 34 +++++++++++++++++ .../remove_degeneracies_example_OM.cpp | 38 +++++++++++++++++++ 5 files changed, 79 insertions(+), 3 deletions(-) create mode 100644 Polygon_mesh_processing/dont_submit create mode 100644 Polygon_mesh_processing/examples/Polygon_mesh_processing/remove_degeneracies_example.cpp create mode 100644 Polygon_mesh_processing/examples/Polygon_mesh_processing/remove_degeneracies_example_OM.cpp diff --git a/Polygon_mesh_processing/doc/Polygon_mesh_processing/examples.txt b/Polygon_mesh_processing/doc/Polygon_mesh_processing/examples.txt index 2811a37ecef..643c76c820b 100644 --- a/Polygon_mesh_processing/doc/Polygon_mesh_processing/examples.txt +++ b/Polygon_mesh_processing/doc/Polygon_mesh_processing/examples.txt @@ -21,4 +21,6 @@ \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 +\example Polygon_mesh_processing/remove_degeneracies_example.cpp +\example Polygon_mesh_processing/remove_degeneracies_example_OM.cpp */ diff --git a/Polygon_mesh_processing/dont_submit b/Polygon_mesh_processing/dont_submit new file mode 100644 index 00000000000..9ee49f9ce2d --- /dev/null +++ b/Polygon_mesh_processing/dont_submit @@ -0,0 +1,2 @@ +examples/Polygon_mesh_processing/remove_degeneracies_example.cpp +examples/Polygon_mesh_processing/remove_degeneracies_example_OM.cpp diff --git a/Polygon_mesh_processing/examples/Polygon_mesh_processing/CMakeLists.txt b/Polygon_mesh_processing/examples/Polygon_mesh_processing/CMakeLists.txt index ed6a9a6e8f9..fac29293adf 100644 --- a/Polygon_mesh_processing/examples/Polygon_mesh_processing/CMakeLists.txt +++ b/Polygon_mesh_processing/examples/Polygon_mesh_processing/CMakeLists.txt @@ -89,7 +89,7 @@ create_single_source_cgal_program( "face_filtered_graph_example.cpp") create_single_source_cgal_program( "polygon_soup_example.cpp") create_single_source_cgal_program( "triangulate_polyline_example.cpp") create_single_source_cgal_program( "mesh_slicer_example.cpp") -#create_single_source_cgal_program( "remove_degeneracies_example.cpp") +create_single_source_cgal_program( "remove_degeneracies_example.cpp") create_single_source_cgal_program( "isotropic_remeshing_example.cpp") create_single_source_cgal_program( "isotropic_remeshing_of_patch_example.cpp") create_single_source_cgal_program( "surface_mesh_intersection.cpp") @@ -118,8 +118,8 @@ target_link_libraries( point_inside_example_OM PRIVATE ${OPENMESH_LIBRARIES} ) create_single_source_cgal_program( "stitch_borders_example_OM.cpp" ) target_link_libraries( stitch_borders_example_OM PRIVATE ${OPENMESH_LIBRARIES} ) -#create_single_source_cgal_program( "remove_degeneracies_example_OM.cpp") -#target_link_libraries( remove_degeneracies_example_OM PRIVATE ${OPENMESH_LIBRARIES} ) +create_single_source_cgal_program( "remove_degeneracies_example_OM.cpp") +target_link_libraries( remove_degeneracies_example_OM PRIVATE ${OPENMESH_LIBRARIES} ) create_single_source_cgal_program( "triangulate_faces_example_OM.cpp") target_link_libraries( triangulate_faces_example_OM PRIVATE ${OPENMESH_LIBRARIES} ) diff --git a/Polygon_mesh_processing/examples/Polygon_mesh_processing/remove_degeneracies_example.cpp b/Polygon_mesh_processing/examples/Polygon_mesh_processing/remove_degeneracies_example.cpp new file mode 100644 index 00000000000..c79f9a5337f --- /dev/null +++ b/Polygon_mesh_processing/examples/Polygon_mesh_processing/remove_degeneracies_example.cpp @@ -0,0 +1,34 @@ +#include + +#include + +#include + +#include +#include + +typedef CGAL::Exact_predicates_inexact_constructions_kernel K; + +typedef CGAL::Surface_mesh Surface_mesh; + +int main(int argc, char* argv[]) +{ + const char* filename = (argc > 1) ? argv[1] : "data/degtri_sliding.off"; + std::ifstream input(filename); + + Surface_mesh mesh; + if (!input || !(input >> mesh) || mesh.is_empty()) { + std::cerr << "Not a valid .off file." << std::endl; + return EXIT_FAILURE; + } + + std::size_t nb = CGAL::Polygon_mesh_processing::remove_degenerate_faces(mesh); + + std::cout << "There were " << nb << " degenerate faces in this mesh" << std::endl; + + mesh.collect_garbage(); + std::ofstream out("repaired.off"); + out << mesh; + + return EXIT_SUCCESS; +} diff --git a/Polygon_mesh_processing/examples/Polygon_mesh_processing/remove_degeneracies_example_OM.cpp b/Polygon_mesh_processing/examples/Polygon_mesh_processing/remove_degeneracies_example_OM.cpp new file mode 100644 index 00000000000..5fc249aec73 --- /dev/null +++ b/Polygon_mesh_processing/examples/Polygon_mesh_processing/remove_degeneracies_example_OM.cpp @@ -0,0 +1,38 @@ +#include + +#include +#include + +#include +#include + +#include +#include + +typedef CGAL::Exact_predicates_inexact_constructions_kernel K; + +typedef OpenMesh::PolyMesh_ArrayKernelT< > Mesh; + +namespace PMP = CGAL::Polygon_mesh_processing; + +int main(int argc, char* argv[]) +{ + const char* filename = (argc > 1) ? argv[1] : "data/degtri_sliding.off"; + + Mesh mesh; + if (!input || !(OpenMesh::IO::read_mesh(mesh, filename)) || mesh.is_empty()) { + std::cerr << "Not a valid .off file." << std::endl; + return EXIT_FAILURE; + } + + std::size_t nb = PMP::remove_degenerate_faces(mesh, + CGAL::parameters::vertex_point_map(get(CGAL::vertex_point, mesh)) + .geom_traits(K())); + + std::cout << "There were " << nb << " degenerate faces in this mesh" << std::endl; + + mesh.garbage_collection(); + OpenMesh::IO::write_mesh(mesh, "repaired.off"); + + return EXIT_SUCCESS; +} From 64245daa4f3a445c43fba57d6fabc891341e103e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mael=20Rouxel-Labb=C3=A9?= Date: Tue, 24 Jul 2018 14:55:20 +0200 Subject: [PATCH 043/116] Renamed PMP example to clarify use of orient functions --- .../doc/Polygon_mesh_processing/examples.txt | 2 +- .../examples/Polygon_mesh_processing/CMakeLists.txt | 2 +- ...polygon_soup_example.cpp => orient_polygon_soup_example.cpp} | 0 3 files changed, 2 insertions(+), 2 deletions(-) rename Polygon_mesh_processing/examples/Polygon_mesh_processing/{polygon_soup_example.cpp => orient_polygon_soup_example.cpp} (100%) diff --git a/Polygon_mesh_processing/doc/Polygon_mesh_processing/examples.txt b/Polygon_mesh_processing/doc/Polygon_mesh_processing/examples.txt index 643c76c820b..cb4ddd2ec62 100644 --- a/Polygon_mesh_processing/doc/Polygon_mesh_processing/examples.txt +++ b/Polygon_mesh_processing/doc/Polygon_mesh_processing/examples.txt @@ -8,7 +8,7 @@ \example Polygon_mesh_processing/triangulate_faces_example.cpp \example Polygon_mesh_processing/connected_components_example.cpp \example Polygon_mesh_processing/face_filtered_graph_example.cpp -\example Polygon_mesh_processing/polygon_soup_example.cpp +\example Polygon_mesh_processing/orient_polygon_soup_example.cpp \example Polygon_mesh_processing/triangulate_polyline_example.cpp \example Polygon_mesh_processing/refine_fair_example.cpp \example Polygon_mesh_processing/mesh_slicer_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 fac29293adf..6f083879d36 100644 --- a/Polygon_mesh_processing/examples/Polygon_mesh_processing/CMakeLists.txt +++ b/Polygon_mesh_processing/examples/Polygon_mesh_processing/CMakeLists.txt @@ -86,7 +86,7 @@ create_single_source_cgal_program( "point_inside_example.cpp") create_single_source_cgal_program( "triangulate_faces_example.cpp") create_single_source_cgal_program( "connected_components_example.cpp") create_single_source_cgal_program( "face_filtered_graph_example.cpp") -create_single_source_cgal_program( "polygon_soup_example.cpp") +create_single_source_cgal_program( "orient_polygon_soup_example.cpp") create_single_source_cgal_program( "triangulate_polyline_example.cpp") create_single_source_cgal_program( "mesh_slicer_example.cpp") create_single_source_cgal_program( "remove_degeneracies_example.cpp") diff --git a/Polygon_mesh_processing/examples/Polygon_mesh_processing/polygon_soup_example.cpp b/Polygon_mesh_processing/examples/Polygon_mesh_processing/orient_polygon_soup_example.cpp similarity index 100% rename from Polygon_mesh_processing/examples/Polygon_mesh_processing/polygon_soup_example.cpp rename to Polygon_mesh_processing/examples/Polygon_mesh_processing/orient_polygon_soup_example.cpp From 3866e720399218c340cc770d2e1f91ecae4a66d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mael=20Rouxel-Labb=C3=A9?= Date: Tue, 24 Jul 2018 15:15:01 +0200 Subject: [PATCH 044/116] Updated orient_polygon_soup example to also showcase orient_to_bound_a_volume --- .../orient_polygon_soup_example.cpp | 36 ++++++++++--------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/Polygon_mesh_processing/examples/Polygon_mesh_processing/orient_polygon_soup_example.cpp b/Polygon_mesh_processing/examples/Polygon_mesh_processing/orient_polygon_soup_example.cpp index 0c3a31a5344..3c073c82c8b 100644 --- a/Polygon_mesh_processing/examples/Polygon_mesh_processing/orient_polygon_soup_example.cpp +++ b/Polygon_mesh_processing/examples/Polygon_mesh_processing/orient_polygon_soup_example.cpp @@ -1,34 +1,33 @@ #include + #include -#include +#include + #include #include #include +#include + #include #include #include -typedef CGAL::Exact_predicates_inexact_constructions_kernel K; -typedef CGAL::Polyhedron_3 Polyhedron; +typedef CGAL::Exact_predicates_inexact_constructions_kernel K; +typedef CGAL::Polyhedron_3 Polyhedron; int main(int argc, char* argv[]) { const char* filename = (argc > 1) ? argv[1] : "data/tet-shuffled.off"; std::ifstream input(filename); - if (!input) + std::vector points; + std::vector > polygons; + + if(!input || !CGAL::read_OFF(input, points, polygons) || points.empty()) { std::cerr << "Cannot open file " << std::endl; - return 1; - } - - std::vector points; - std::vector< std::vector > polygons; - if (!CGAL::read_OFF(input, points, polygons)) - { - std::cerr << "Error parsing the OFF file " << std::endl; - return 1; + return EXIT_FAILURE; } CGAL::Polygon_mesh_processing::orient_polygon_soup(points, polygons); @@ -36,8 +35,13 @@ int main(int argc, char* argv[]) Polyhedron mesh; CGAL::Polygon_mesh_processing::polygon_soup_to_polygon_mesh(points, polygons, mesh); - if (CGAL::is_closed(mesh) && (!CGAL::Polygon_mesh_processing::is_outward_oriented(mesh))) - CGAL::Polygon_mesh_processing::reverse_face_orientations(mesh); + // Number the faces because 'orient_to_bound_a_volume' needs a face <--> index map + int index = 0; + for(Polyhedron::Face_iterator fb=mesh.facets_begin(), fe=mesh.facets_end(); fb!=fe; ++fb) + fb->id() = index++; + + if(CGAL::is_closed(mesh)) + CGAL::Polygon_mesh_processing::orient_to_bound_a_volume(mesh); std::ofstream out("tet-oriented1.off"); out << mesh; @@ -48,5 +52,5 @@ int main(int argc, char* argv[]) out2 << mesh; out2.close(); - return 0; + return EXIT_SUCCESS; } From e6d1977f7329603038bdd170cd83a0abfbae089c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mael=20Rouxel-Labb=C3=A9?= Date: Tue, 24 Jul 2018 18:07:49 +0200 Subject: [PATCH 045/116] Updated documentation --- .../PackageDescription.txt | 17 +- .../Polygon_mesh_processing.txt | 225 ++++++++++-------- .../merge_border_vertices.h | 5 +- .../CGAL/Polygon_mesh_processing/repair.h | 42 +++- .../shape_predicates.h | 5 + 5 files changed, 183 insertions(+), 111 deletions(-) diff --git a/Polygon_mesh_processing/doc/Polygon_mesh_processing/PackageDescription.txt b/Polygon_mesh_processing/doc/Polygon_mesh_processing/PackageDescription.txt index 3baa705dad3..181eb3ddcd7 100644 --- a/Polygon_mesh_processing/doc/Polygon_mesh_processing/PackageDescription.txt +++ b/Polygon_mesh_processing/doc/Polygon_mesh_processing/PackageDescription.txt @@ -113,19 +113,31 @@ and provides a list of the parameters that are used in this package. - `CGAL::Polygon_mesh_processing::self_intersections()` - \link PMP_predicates_grp `CGAL::Polygon_mesh_processing::do_intersect()` \endlink - `CGAL::Polygon_mesh_processing::intersecting_meshes()` +- `CGAL::Polygon_mesh_processing::is_degenerate_edge()` +- `CGAL::Polygon_mesh_processing::degenerate_edges()` +- `CGAL::Polygon_mesh_processing::is_degenerate_triangle_face()` +- `CGAL::Polygon_mesh_processing::degenerate_faces()` +- `CGAL::Polygon_mesh_processing::is_needle_triangle_face()` +- `CGAL::Polygon_mesh_processing::is_cap_triangle_face()` ## Orientation Functions ## -- `CGAL::Polygon_mesh_processing::is_outward_oriented()` -- `CGAL::Polygon_mesh_processing::reverse_face_orientations()` - `CGAL::Polygon_mesh_processing::orient_polygon_soup()` - `CGAL::Polygon_mesh_processing::orient()` - `CGAL::Polygon_mesh_processing::orient_to_bound_a_volume()` +- `CGAL::Polygon_mesh_processing::is_outward_oriented()` +- `CGAL::Polygon_mesh_processing::reverse_face_orientations()` ## Combinatorial Repairing Functions ## - \link PMP_repairing_grp `CGAL::Polygon_mesh_processing::stitch_borders()` \endlink - `CGAL::Polygon_mesh_processing::is_polygon_soup_a_polygon_mesh()` - `CGAL::Polygon_mesh_processing::polygon_soup_to_polygon_mesh()` - `CGAL::Polygon_mesh_processing::remove_isolated_vertices()` +- `CGAL::Polygon_mesh_processing::is_non_manifold_vertex()` +- `CGAL::Polygon_mesh_processing::duplicate_non_manifold_vertices()` +- `CGAL::Polygon_mesh_processing::remove_degenerate_faces()` +- `CGAL::Polygon_mesh_processing::merge_vertices_in_range()` +- `CGAL::Polygon_mesh_processing::merge_duplicated_vertices_in_boundary_cycle()` +- `CGAL::Polygon_mesh_processing::merge_duplicated_vertices_in_boundary_cycles()` ## Normal Computation Functions ## - `CGAL::Polygon_mesh_processing::compute_face_normal()` @@ -179,6 +191,7 @@ and provides a list of the parameters that are used in this package. - `CGAL::Polygon_mesh_processing::edge_bbox()` - `CGAL::Polygon_mesh_processing::face_bbox()` - `CGAL::Polygon_mesh_processing::border_halfedges()` +- `CGAL::Polygon_mesh_processing::extract_boundary_cycles()` - `CGAL::Polygon_mesh_processing::transform()` */ 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 e6f3c49f3b9..a9ea6529f7d 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 @@ -43,14 +43,14 @@ meshes, refinement, optimization by fairing, and isotropic remeshing of triangul - \ref Coref_section : methods to corefine triangle meshes and to compute boolean operations out of corefined closed triangle meshes. - \ref PMPHoleFilling : available hole filling algorithms, which can possibly be combined with refinement and fairing. -- \ref PMPPredicates : predicates that can be evaluated on the processed polygon +- \ref PMPPredicates : predicates that can be evaluated on the processed polygon. mesh, which includes point location and self intersection tests. -- \ref PMPOrientation : checking or fixing the \ref PMPOrientation of a polygon soup. +- \ref PMPOrientation : checking or fixing the orientation of a polygon soup. - \ref PMPRepairing : reparation of polygon meshes and polygon soups. - \ref PMPNormalComp : normal computation at vertices and on faces of a polygon mesh. - \ref PMPSlicer : functor able to compute the intersections of a polygon mesh with arbitrary planes (slicer). - \ref PMPConnectedComponents : methods to deal with connected - components of a polygon mesh (extraction, marks, removal, ...) + components of a polygon mesh (extraction, marks, removal, ...). **************************************** \section PMPMeshing Meshing @@ -309,7 +309,7 @@ This package provides an algorithm for filling one closed hole that is either in or defined by a sequence of points that describe a polyline. The main steps of the algorithm are described in \cgalCite{liepa2003filling} and can be summarized as follows. -First, the largest patch triangulating the boundary of the hole is generated without introducing any new vertex. +First, the largest patch triangulating the boundary of the hole is generated without introducing any new vertex. The patch is selected so as to minimize a quality function evaluated for all possible triangular patches. The quality function first minimizes the worst dihedral angle between patch triangles, then the total surface area of the patch as a tiebreaker. @@ -335,10 +335,10 @@ From left to right: (a) the hole, \subsection HoleFillingAPI API This package provides four functions for hole filling: - - `triangulate_hole_polyline()` : given a sequence of points defining the hole, triangulates the hole. - - `triangulate_hole()` : given a border halfedge on the boundary of the hole on a mesh, triangulates the hole. - - `triangulate_and_refine_hole()` : in addition to `triangulate_hole()` the generated patch is refined. - - `triangulate_refine_and_fair_hole()` : in addition to `triangulate_and_refine_hole()` the generated patch is also faired. + - `triangulate_hole_polyline()` : given a sequence of points defining the hole, triangulates the hole. + - `triangulate_hole()` : given a border halfedge on the boundary of the hole on a mesh, triangulates the hole. + - `triangulate_and_refine_hole()` : in addition to `triangulate_hole()` the generated patch is refined. + - `triangulate_refine_and_fair_hole()` : in addition to `triangulate_and_refine_hole()` the generated patch is also faired. \subsection HFExamples Examples @@ -372,51 +372,65 @@ iteratively filled, refined and faired to get a faired mesh with no hole. The hole filling algorithm has a complexity which depends on the number of vertices. While \cgalCite{liepa2003filling} has a running time of \f$ O(n^3)\f$ , \cgalCite{zou2013algorithm} in most cases has -running time of \f$ O(n \log n)\f$. We were running -`triangulate_refine_and_fair_hole()` for the below meshes (and two -more meshes with smaller holes). The machine used is a PC running -Windows 10 with an Intel Core i7 CPU clocked at 2.70 GHz. -The program has been compiled with Visual C++ 2013 compiler with the O2 -option which maximizes speed. +running time of \f$ O(n \log n)\f$. We benchmarked the function +`triangulate_refine_and_fair_hole()` for the two meshes below (as well as two +more meshes with smaller holes). The machine used was a PC running +Windows 10 with an Intel Core i7 CPU clocked at 2.70 GHz. +The program was compiled with the Visual C++ 2013 compiler with the O2 +option, which maximizes speed. \cgalFigureBegin{Elephants, elephants-with-holes.png} The elephant on the left/right has a hole with 963/7657 vertices. \cgalFigureEnd -This takes time +The following running times were observed: +
| # vertices | without Delaunay (sec.) | with Delaunay (sec.)| -| ----: | ----: | ----: | +| ----: | ----: | ----: | 565 | 8.5 | 0.03 | 774 | 21 | 0.035 | 967 | 43 | 0.06 | 7657 | na | 0.4 | - +
*************************************** \section PMPPredicates Predicates This packages provides several predicates to be evaluated with respect to a triangle mesh. -\subsection PMPSelIntersections Self Intersections +\subsection PMPDoIntersect Intersections Detection +Intersection tests between triangle meshes and/or polylines can be done using +\link PMP_predicates_grp `CGAL::Polygon_mesh_processing::do_intersect()` \endlink. +Additionally, the function `CGAL::Polygon_mesh_processing::intersecting_meshes()` +records all pairs of intersecting meshes in a range. -Self intersections can be detected from a triangle mesh, by calling the predicate +\subsubsection PMPSelIntersections Self Intersections + +Self intersections within a triangle mesh can be detected by calling the function `CGAL::Polygon_mesh_processing::does_self_intersect()`. Additionally, the function `CGAL::Polygon_mesh_processing::self_intersections()` reports all pairs of intersecting triangles. -\cgalFigureBegin{SelfIntersections, selfintersections.jpg} -Detecting self-intersections on a triangle mesh. -The intersecting triangles are displayed in dark grey on the right image. -\cgalFigureEnd - \subsubsection SIExample Self Intersections Example + +The following example illustrates the detection of self intersection in the `pig.off` mesh. +The detected self-intersection is illustrated on Figure \cgalFigureRef{SelfIntersections}. + \cgalExample{Polygon_mesh_processing/self_intersections_example.cpp} +\cgalFigureAnchor{SelfIntersections} +
+ +
+\cgalFigureCaptionBegin{SelfIntersections} +Detecting self-intersections on a triangle mesh. +The intersecting triangles are displayed in dark grey and red on the right image. +\cgalFigureCaptionEnd \subsection PMPInsideTest Side of Triangle Mesh -The class `CGAL::Side_of_triangle_mesh` provides a functor that tests whether a query point is +The class `CGAL::Side_of_triangle_mesh` provides a functor that tests whether a query point is inside, outside, or on the boundary of the domain bounded by a given closed triangle mesh. A point is said to be on the bounded side of the domain bounded by the input triangle mesh @@ -435,39 +449,90 @@ input triangle mesh. \subsubsection InsideExample Inside Test Example \cgalExample{Polygon_mesh_processing/point_inside_example.cpp} -\subsection PMPDoIntersect Intersections Detection -Intersection tests between triangle meshes and/or polylines can be done using -\link PMP_predicates_grp `CGAL::Polygon_mesh_processing::do_intersect()` \endlink. -Additionally, the function `CGAL::Polygon_mesh_processing::intersecting_meshes()` -records all pairs of intersecting meshes in a range. +\subsection PMPShapePredicates Shape Predicates + +Badly shaped or, even worse, completely degenerate elements of a polygon mesh are problematic +in many algorithms which one might want to use on the mesh. +This package offers a toolkit of functions to detect such undesirable elements. +- `CGAL::Polygon_mesh_processing::is_degenerate_edge()`, to detect if an edge is degenerate + (that is, if its two vertices share the same geometric location). +- `CGAL::Polygon_mesh_processing::is_degenerate_triangle_face()`, to detect if a face is + degenerate (that is, if its three vertices are collinear). +- `CGAL::Polygon_mesh_processing::degenerate_edges()`, to collect degenerate edges within a range of edges. +- `CGAL::Polygon_mesh_processing::degenerate_faces()`, to collect degenerate faces within a range of faces. +- `CGAL::Polygon_mesh_processing::is_cap_triangle_face()` +- `CGAL::Polygon_mesh_processing::is_needle_triangle_face()` + +This package offers functions to remove such undesirable elements, see Section \ref PMPRepairing. **************************************** \section PMPOrientation Orientation +This package offers multiple functions to compute consistent face orientations for set of faces +(Section \ref PolygonSoups) and polygon meshes (Section \ref OrientingPolygonMeshes). +Section \ref PolygonSoupExample offers an example of combination of these functions. + +\subsection PolygonSoups Polygon Soups + +When the faces of a polygon mesh are given but the connectivity is unknown, +this set of faces is called a \e polygon \e soup. + +Before running any of the algorithms on a polygon soup, +one should ensure that the polygons are consistently oriented. +To do so, this package provides the function +`CGAL::Polygon_mesh_processing::orient_polygon_soup()`, +described in \cgalCite{gueziec2001cutting}. + +To deal with polygon soups that cannot be converted to a +combinatorially manifold surface, some points must be duplicated. +Because a polygon soup does not have any connectivity (each point +has as many occurences as the number of polygons it belongs to), +duplicating one point (or a pair of points) +amounts to duplicating the polygon to which it belongs. +The duplicated points are either an endpoint of an edge incident to more +than two polygons, an endpoint of an edge between +two polygons with incompatible orientations (during the re-orientation process), +or more generally a point \a p at which the intersection +of an infinitesimally small ball centered at \a p +with the polygons incident to it is not a topological disk. + +Once the polygon soup is consistently oriented, +with possibly duplicated (or more) points, +the connectivity can be recovered and made consistent +to build a valid polygon mesh. +The function `CGAL::Polygon_mesh_processing::polygon_soup_to_polygon_mesh()` +performs this mesh construction step. + +\subsection OrientingPolygonMeshes Polygon Meshes + This package provides functions dealing with the orientation of faces in a closed polygon mesh. -The function `CGAL::Polygon_mesh_processing::is_outward_oriented()` checks whether +- The function `CGAL::Polygon_mesh_processing::orient()` makes each connected component +of a closed polygon mesh outward- or inward-oriented. +- The function `CGAL::Polygon_mesh_processing::orient_to_bound_a_volume()` orients +the connected components of a closed polygon mesh so that it bounds a volume +(see \ref coref_def_subsec for the precise definition). +- The function `CGAL::Polygon_mesh_processing::is_outward_oriented()` checks whether an oriented polygon mesh is oriented such that the normals to all faces are oriented towards the outside of the domain bounded by the input polygon mesh. - -The function -`CGAL::Polygon_mesh_processing::reverse_face_orientations()` reverses the orientation +- The function `CGAL::Polygon_mesh_processing::reverse_face_orientations()` reverses the orientation of halfedges around faces. As a consequence, the normal computed for each face (see Section \ref PMPNormalComp) is also reversed. -The \ref PolygonSoupExample puts these functions at work on a polygon soup. +\subsection PolygonSoupExample Orientation Example -The function `CGAL::Polygon_mesh_processing::orient()` makes each connected component -of a closed polygon mesh outward or inward oriented. - -The function `CGAL::Polygon_mesh_processing::orient_to_bound_a_volume()` orients -the connected components of a closed polygon mesh so that it bounds a volume -(see \ref coref_def_subsec for the precise definition). +This example shows how to generate a mesh from a polygon soup. +The first step is to get a soup of consistently oriented faces, before +rebuilding the connectivity. +In this example, some orientation tests are performed on the output +polygon mesh to illustrate +Section \ref PMPOrientation. +\cgalExample{Polygon_mesh_processing/orient_polygon_soup_example.cpp} **************************************** -\section PMPRepairing Combinatorial Repairing +\section PMPRepairing Combinatorial Repairing ******************* \subsection Stitching @@ -490,16 +555,12 @@ with duplicated border edges. \cgalExample{Polygon_mesh_processing/stitch_borders_example.cpp} -******************* -\cond \subsection DegenerateFaces Removing Degenerate Faces Some degenerate faces may be part of a given triangle mesh. A face is considered \e degenerate if two of its vertices -share the same location, -or in general if its three vertices are collinear. -The function -`CGAL::Polygon_mesh_processing::remove_degenerate_faces()` +share the same location, or more generally if its three vertices are collinear. +The function `CGAL::Polygon_mesh_processing::remove_degenerate_faces()` removes those faces and fixes the connectivity of the newly cleaned up mesh. It is also possible to remove isolated vertices from any polygon mesh, using the function `CGAL::Polygon_mesh_processing::remove_isolated_vertices()`. @@ -511,54 +572,26 @@ are removed, the connectivity is fixed, and the number of removed faces is output. \cgalExample{Polygon_mesh_processing/remove_degeneracies_example.cpp} -\endcond -******************* -\subsection PolygonSoups Polygon Soups +\subsection PMPManifoldness Polygon Mesh Manifoldness -When the faces of a polygon mesh are given but the connectivity is unknown, -we must deal with of a \e polygon \e soup. - -Before running any of the algorithms on the so-called -polygon soup, one should ensure that the polygons are consistently oriented. -To do so, this package provides the function -`CGAL::Polygon_mesh_processing::orient_polygon_soup()`, -described in \cgalCite{gueziec2001cutting}. - -To deal with polygon soups that cannot be converted to a -combinatorial manifold surface, some points are duplicated. -Because a polygon soup does not have any connectivity (each point -has as many occurences as the number of polygons it belongs to), -duplicating one point (or a pair of points) -amounts to duplicate the polygon to which it belongs. - -The duplicated points are either an endpoint of an edge incident to more -than two polygons, an endpoint of an edge between -two polygons with incompatible orientations (during the re-orientation process), -or more generally a point \a p at which the intersection -of an infinitesimally small ball centered at \a p -with the polygons incident to it is not a topological disk. - -Once the polygon soup is consistently oriented, -with possibly duplicated (or more) points, -the connectivity can be recovered and made consistent -to build a valid polygon mesh. -The function `CGAL::Polygon_mesh_processing::polygon_soup_to_polygon_mesh()` -performs this mesh construction step. - - -\subsubsection PolygonSoupExample Polygon Soup Example - -This example shows how to generate a mesh from a polygon soup. -The first step is to get a soup of consistently oriented faces, before -rebuilding the connectivity. -In this example, some orientation tests are performed on the output -polygon mesh to illustrate -Section \ref PMPOrientation. - -\cgalExample{Polygon_mesh_processing/polygon_soup_example.cpp} +Non-manifold vertices can be detected using the function `CGAL::Polygon_mesh_processing::is_non_manifold_vertex()`. +The function `CGAL::Polygon_mesh_processing::duplicate_non_manifold_vertices()` can be used +to attempt to create a combinatorially manifold surface mesh by splitting any non-manifold vertex +into as many vertices as there are manifold sheets at this geometric position. +Note however that the mesh will still not be manifold from a geometric +point of view, as the positions of the new vertices introduced at a non-manifold vertex are identical +to the input non-manifold vertex. +\subsection PMPDuplicateVertexBoundaryCycle Duplicated Vertices in Boundary Cycles +Similarly to the problematic configuration of the previous section, another issue that can appear +in a polygon mesh is the occurrence of a "pinched" hole, that is the configuration where, when +starting from a border halfedge and walking the halfedges of this border, a geometric position appears +more than once (although, with different vertices) before reaching the initial border halfedge again. The functions +`CGAL::Polygon_mesh_processing::merge_duplicated_vertices_in_boundary_cycle()` and +`CGAL::Polygon_mesh_processing::merge_duplicated_vertices_in_boundary_cycle()`, which merge +vertices at identical positions, can be used to repair this configuration. **************************************** \section PMPNormalComp Computing Normals @@ -570,7 +603,7 @@ These computations are performed with : - `CGAL::Polygon_mesh_processing::compute_face_normal()` - `CGAL::Polygon_mesh_processing::compute_vertex_normal()` -We further provide functions to compute all the normals to faces, +Furthermore, we provide functions to compute all the normals to faces, or to vertices, or to both : - `CGAL::Polygon_mesh_processing::compute_face_normals()` - `CGAL::Polygon_mesh_processing::compute_vertex_normals()` @@ -578,14 +611,12 @@ or to vertices, or to both : Property maps are used to record the computed normals. - \subsection NormalsExample Normals Computation Examples -Property maps are an API introduced in the boost library, that allows to +Property maps are an API introduced in the boost library that allows to associate values to keys. In the following examples we associate a normal vector to each vertex and to each face. - \subsubsection NormalsExampleSM Normals Computation for a Surface Mesh The following example illustrates how to @@ -729,7 +760,7 @@ that respectively detect the sharp edges, compute the patch indices, and give ea \subsection DetectFeaturesExample Feature Detection Example In the following example, we count how many edges of `pmesh` are incident to two faces -which normals form an angle smaller than 90 degrees, +whose normals form an angle smaller than 90 degrees, and the number of surface patches that are separated by these edges. \cgalExample{Polygon_mesh_processing/detect_features_example.cpp} diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/merge_border_vertices.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/merge_border_vertices.h index 6f0185533ef..07fe1728d46 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/merge_border_vertices.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/merge_border_vertices.h @@ -242,8 +242,7 @@ void merge_duplicated_vertices_in_boundary_cycle( } /// \ingroup PMP_repairing_grp -/// extracts boundary cycles and merges the duplicated -/// vertices of each cycle. +/// extracts boundary cycles and merges the duplicated vertices of each cycle. /// /// @tparam PolygonMesh a model of `FaceListGraph` and `MutableFaceGraph`. /// @tparam NamedParameter a sequence of \ref pmp_namedparameters "Named Parameters". @@ -258,6 +257,8 @@ void merge_duplicated_vertices_in_boundary_cycle( /// `CGAL::vertex_point_t` should be available in `PolygonMesh` /// \cgalParamEnd /// \cgalNamedParamsEnd +/// +/// \sa `merge_duplicated_vertices_in_boundary_cycle()` template void merge_duplicated_vertices_in_boundary_cycles( PolygonMesh& pm, const NamedParameter& np) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/repair.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/repair.h index 9f9a1d7a4e2..88a12acec66 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/repair.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/repair.h @@ -174,12 +174,13 @@ struct Less_vertex_point{ /// \ingroup PMP_repairing_grp /// collects the degenerate edges within a given range of edges. /// -/// @tparam EdgeRange a model of `Range` with value type `boost::graph_traits::edge_descriptor` +/// @tparam EdgeRange a model of `Range` with value type `boost::graph_traits::%edge_descriptor` /// @tparam TriangleMesh a model of `EdgeListGraph` /// @tparam NamedParameters a sequence of \ref pmp_namedparameters "Named Parameters" /// /// @param edges a subset of edges of `tm` /// @param tm a triangle mesh +/// @param out an output iterator in which the degenerate edges are written /// @param np optional \ref pmp_namedparameters "Named Parameters" described below /// /// \cgalNamedParamsBegin @@ -220,13 +221,21 @@ OutputIterator degenerate_edges(const EdgeRange& edges, return degenerate_edges(edges, tm, out, CGAL::parameters::all_default()); } +/// \ingroup PMP_repairing_grp +/// calls the function `degenerate_edges()` with the range: `edges(tm)`. +/// +/// See above for the comprehensive description of the parameters. +/// template OutputIterator degenerate_edges(const TriangleMesh& tm, OutputIterator out, - const NamedParameters& np, - typename boost::enable_if_c< + const NamedParameters& np +#ifndef DOXYGEN_RUNNING + , typename boost::enable_if_c< CGAL::is_iterator::value - >::type* = 0) + >::type* = 0 +#endif + ) { return degenerate_edges(edges(tm), tm, out, np); } @@ -241,12 +250,13 @@ degenerate_edges(const TriangleMesh& tm, OutputIterator out) /// \ingroup PMP_repairing_grp /// collects the degenerate faces within a given range of faces. /// -/// @tparam FaceRange a model of `Range` with value type `boost::graph_traits::face_descriptor` +/// @tparam FaceRange a model of `Range` with value type `boost::graph_traits::%face_descriptor` /// @tparam TriangleMesh a model of `FaceGraph` /// @tparam NamedParameters a sequence of \ref pmp_namedparameters "Named Parameters" /// /// @param faces a subset of faces of `tm` /// @param tm a triangle mesh +/// @param out an output iterator in which the degenerate faces are put /// @param np optional \ref pmp_namedparameters "Named Parameters" described below /// /// \cgalNamedParamsBegin @@ -260,6 +270,8 @@ degenerate_edges(const TriangleMesh& tm, OutputIterator out) /// to check whether three points are collinear. /// \cgalParamEnd /// \cgalNamedParamsEnd +/// +/// \sa remove_degenerate_faces() template OutputIterator degenerate_faces(const FaceRange& faces, const TriangleMesh& tm, @@ -287,13 +299,21 @@ OutputIterator degenerate_faces(const FaceRange& faces, return degenerate_faces(faces, tm, out, CGAL::parameters::all_default()); } +/// \ingroup PMP_repairing_grp +/// calls the function `degenerate_faces()` with the range: `faces(tm)`. +/// +/// See above for the comprehensive description of the parameters. +/// template OutputIterator degenerate_faces(const TriangleMesh& tm, OutputIterator out, - const NamedParameters& np, - typename boost::enable_if_c< + const NamedParameters& np +#ifndef DOXYGEN_RUNNING + , typename boost::enable_if_c< CGAL::is_iterator::value - >::type* = 0) + >::type* = 0 +#endif + ) { return degenerate_faces(faces(tm), tm, out, np); } @@ -798,7 +818,7 @@ std::size_t remove_degenerate_edges(const EdgeRange& edge_range, /// - `Collinear_3` to check whether 3 points are collinear /// - `Less_xyz_3` to compare lexicographically two points /// - `Equal_3` to check whether 2 points are identical -/// - for each functor Foo, a function `Foo foo_object()` +/// For each functor `Foo`, a function `Foo foo_object()` /// \cgalParamEnd /// \cgalNamedParamsEnd /// @@ -1448,6 +1468,8 @@ struct Vertex_collector /// @param v a vertex of `tm` /// @param tm a triangle mesh containing `v` /// +/// \sa duplicate_non_manifold_vertices() +/// /// \return `true` if the vertex is non-manifold, `false` otherwise. template bool is_non_manifold_vertex(typename boost::graph_traits::vertex_descriptor v, @@ -1489,7 +1511,7 @@ bool is_non_manifold_vertex(typename boost::graph_traits::vertex_d /// \cgalParamEnd /// \cgalParamBegin{vertex_is_constrained_map} a writable property map with `vertex_descriptor` /// as key and `bool` as `value_type`. `put(pmap, v, true)` will be called for each duplicated -/// vertices and the input one. +/// vertices, as well as the original non-manifold vertex in the input mehs. /// \cgalParamEnd /// \cgalParamBegin{output_iterator} a model of `OutputIterator` with value type /// `std::vector`. The first vertex of each vector is a non-manifold vertex diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/shape_predicates.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/shape_predicates.h index 9673922342d..4cc98184b7f 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/shape_predicates.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/shape_predicates.h @@ -65,6 +65,8 @@ namespace Polygon_mesh_processing { /// \cgalParamEnd /// \cgalNamedParamsEnd /// +/// \sa `degenerate_edges()` +/// /// \return `true` if the edge `e` is degenerate, `false` otherwise. template bool is_degenerate_edge(typename boost::graph_traits::edge_descriptor e, @@ -114,6 +116,9 @@ bool is_degenerate_edge(typename boost::graph_traits::edge_descript /// \cgalParamEnd /// \cgalNamedParamsEnd /// +/// \sa `degenerate_faces()` +/// \sa `remove_degenerate_faces()` +/// /// \return `true` if the face `f` is degenerate, `false` otherwise. template bool is_degenerate_triangle_face(typename boost::graph_traits::face_descriptor f, From 15b791901b692c1c4a71458639f95f26cad73e7d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mael=20Rouxel-Labb=C3=A9?= Date: Wed, 25 Jul 2018 08:36:23 +0200 Subject: [PATCH 046/116] Fixed compilation error --- .../Polygon_mesh_processing/remove_degeneracies_example_OM.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Polygon_mesh_processing/examples/Polygon_mesh_processing/remove_degeneracies_example_OM.cpp b/Polygon_mesh_processing/examples/Polygon_mesh_processing/remove_degeneracies_example_OM.cpp index 5fc249aec73..408990850de 100644 --- a/Polygon_mesh_processing/examples/Polygon_mesh_processing/remove_degeneracies_example_OM.cpp +++ b/Polygon_mesh_processing/examples/Polygon_mesh_processing/remove_degeneracies_example_OM.cpp @@ -20,7 +20,7 @@ int main(int argc, char* argv[]) const char* filename = (argc > 1) ? argv[1] : "data/degtri_sliding.off"; Mesh mesh; - if (!input || !(OpenMesh::IO::read_mesh(mesh, filename)) || mesh.is_empty()) { + if (!OpenMesh::IO::read_mesh(mesh, filename) || mesh.is_empty()) { std::cerr << "Not a valid .off file." << std::endl; return EXIT_FAILURE; } From 81d76c2e6903bfdac1835432a6ea9c09780fca50 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mael=20Rouxel-Labb=C3=A9?= Date: Wed, 25 Jul 2018 09:37:27 +0200 Subject: [PATCH 047/116] Added example about non-manifold vertex repair --- .../Polygon_mesh_processing.txt | 11 ++- .../doc/Polygon_mesh_processing/examples.txt | 1 + .../Polygon_mesh_processing/CMakeLists.txt | 1 + .../manifoldness_repair_example.cpp | 82 +++++++++++++++++++ 4 files changed, 93 insertions(+), 2 deletions(-) create mode 100644 Polygon_mesh_processing/examples/Polygon_mesh_processing/manifoldness_repair_example.cpp 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 a9ea6529f7d..b7ca98e8808 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 @@ -46,7 +46,7 @@ meshes, refinement, optimization by fairing, and isotropic remeshing of triangul - \ref PMPPredicates : predicates that can be evaluated on the processed polygon. mesh, which includes point location and self intersection tests. - \ref PMPOrientation : checking or fixing the orientation of a polygon soup. -- \ref PMPRepairing : reparation of polygon meshes and polygon soups. +- \ref PMPRepairing : repair of polygon meshes and polygon soups. - \ref PMPNormalComp : normal computation at vertices and on faces of a polygon mesh. - \ref PMPSlicer : functor able to compute the intersections of a polygon mesh with arbitrary planes (slicer). - \ref PMPConnectedComponents : methods to deal with connected @@ -583,9 +583,16 @@ Note however that the mesh will still not be manifold from a geometric point of view, as the positions of the new vertices introduced at a non-manifold vertex are identical to the input non-manifold vertex. +\subsubsection FixNMVerticeExample Manifoldness Repair Example + +In the following example, a non-manifold configuration is artifically created and +fixed with the help of the functions described above. + +\cgalExample{Polygon_mesh_processing/manifoldness_repair_example.cpp} + \subsection PMPDuplicateVertexBoundaryCycle Duplicated Vertices in Boundary Cycles -Similarly to the problematic configuration of the previous section, another issue that can appear +Similarly to the problematic configuration described in the previous section, another issue that can be present in a polygon mesh is the occurrence of a "pinched" hole, that is the configuration where, when starting from a border halfedge and walking the halfedges of this border, a geometric position appears more than once (although, with different vertices) before reaching the initial border halfedge again. The functions diff --git a/Polygon_mesh_processing/doc/Polygon_mesh_processing/examples.txt b/Polygon_mesh_processing/doc/Polygon_mesh_processing/examples.txt index cb4ddd2ec62..03ee471a773 100644 --- a/Polygon_mesh_processing/doc/Polygon_mesh_processing/examples.txt +++ b/Polygon_mesh_processing/doc/Polygon_mesh_processing/examples.txt @@ -23,4 +23,5 @@ \example Polygon_mesh_processing/detect_features_example.cpp \example Polygon_mesh_processing/remove_degeneracies_example.cpp \example Polygon_mesh_processing/remove_degeneracies_example_OM.cpp +\example Polygon_mesh_processing/manifoldness_repair_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 6f083879d36..15092e7a30f 100644 --- a/Polygon_mesh_processing/examples/Polygon_mesh_processing/CMakeLists.txt +++ b/Polygon_mesh_processing/examples/Polygon_mesh_processing/CMakeLists.txt @@ -104,6 +104,7 @@ create_single_source_cgal_program( "random_perturbation_SM_example.cpp" ) create_single_source_cgal_program( "corefinement_LCC.cpp") create_single_source_cgal_program( "hole_filling_example_LCC.cpp" ) create_single_source_cgal_program( "detect_features_example.cpp" ) +create_single_source_cgal_program( "manifoldness_repair_example.cpp" ) if(OpenMesh_FOUND) create_single_source_cgal_program( "compute_normals_example_OM.cpp" ) diff --git a/Polygon_mesh_processing/examples/Polygon_mesh_processing/manifoldness_repair_example.cpp b/Polygon_mesh_processing/examples/Polygon_mesh_processing/manifoldness_repair_example.cpp new file mode 100644 index 00000000000..64c3a91cab6 --- /dev/null +++ b/Polygon_mesh_processing/examples/Polygon_mesh_processing/manifoldness_repair_example.cpp @@ -0,0 +1,82 @@ +#include + +#include +#include +#include +#include + +#include + +#include +#include +#include +#include + +namespace PMP = CGAL::Polygon_mesh_processing; +namespace NP = CGAL::parameters; + +typedef CGAL::Exact_predicates_inexact_constructions_kernel K; +typedef CGAL::Surface_mesh Mesh; + +typedef boost::graph_traits::vertex_descriptor vertex_descriptor; +typedef boost::graph_traits::halfedge_descriptor halfedge_descriptor; + +void merge_vertices(vertex_descriptor v_keep, vertex_descriptor v_rm, Mesh& mesh) +{ + std::cout << "merging vertices " << v_keep << " and " << v_rm << std::endl; + + BOOST_FOREACH(halfedge_descriptor h, CGAL::halfedges_around_target(v_rm, mesh)){ + set_target(h, v_keep, mesh); // to ensure that no halfedge points at the deleted vertex + } + + remove_vertex(v_rm, mesh); +} + +int main(int argc, char* argv[]) +{ + const char* filename = (argc > 1) ? argv[1] : "data/blobby.off"; + std::ifstream input(filename); + + Mesh mesh; + if(!input || !(input >> mesh) || num_vertices(mesh) == 0) + { + std::cerr << filename << " is not a valid off file.\n"; + return EXIT_FAILURE; + } + + // Artificially create non-manifoldness for the sake of the example by merging some vertices + vertex_descriptor v0 = *(vertices(mesh).begin()); + vertex_descriptor v1 = *(--(vertices(mesh).end())); + merge_vertices(v0, v1, mesh); + + // Count non manifold vertices + int counter = 0; + BOOST_FOREACH(vertex_descriptor v, vertices(mesh)) + { + if(PMP::is_non_manifold_vertex(v, mesh)) + { + std::cout << "vertex " << v << " is non-manifold" << std::endl; + ++counter; + } + } + + std::cout << counter << " non-manifold occurrence(s)" << std::endl; + + // Fix manifoldness by splitting non-manifold vertices + std::vector > duplicated_vertices; + std::size_t new_vertices_nb = PMP::duplicate_non_manifold_vertices(mesh, + NP::output_iterator( + std::back_inserter(duplicated_vertices))); + + std::cout << new_vertices_nb << " vertices have been added to fix mesh manifoldness" << std::endl; + + for(std::size_t i=0; i Date: Wed, 25 Jul 2018 09:40:03 +0200 Subject: [PATCH 048/116] Minor test improvement --- .../test/Polygon_mesh_processing/test_shape_predicates.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Polygon_mesh_processing/test/Polygon_mesh_processing/test_shape_predicates.cpp b/Polygon_mesh_processing/test/Polygon_mesh_processing/test_shape_predicates.cpp index 7526ac80bec..e68e8a7d09f 100644 --- a/Polygon_mesh_processing/test/Polygon_mesh_processing/test_shape_predicates.cpp +++ b/Polygon_mesh_processing/test/Polygon_mesh_processing/test_shape_predicates.cpp @@ -146,11 +146,13 @@ void test_vertices_merge_and_duplication(const char* fname) assert(vertices_after_merge == initial_vertices - 4); std::vector > duplicated_vertices; - CGAL::Polygon_mesh_processing::duplicate_non_manifold_vertices(mesh, - CGAL::parameters::output_iterator(std::back_inserter(duplicated_vertices))); + std::size_t new_vertices_nb = + CGAL::Polygon_mesh_processing::duplicate_non_manifold_vertices(mesh, + CGAL::parameters::output_iterator(std::back_inserter(duplicated_vertices))); const std::size_t final_vertices_size = vertices(mesh).size(); assert(final_vertices_size == initial_vertices); + assert(new_vertices_nb == 4); assert(duplicated_vertices.size() == 2); // two non-manifold vertex assert(duplicated_vertices.front().size() == 4); assert(duplicated_vertices.back().size() == 2); From 99864af9f5223a394d57126d49d7a0304e6119c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mael=20Rouxel-Labb=C3=A9?= Date: Wed, 25 Jul 2018 09:40:17 +0200 Subject: [PATCH 049/116] Moved Degenerate Faces Removal and NM Vertex Repair from demo/experimental --- .../demo/Polyhedron/Plugins/PMP/Repair_polyhedron_plugin.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Polyhedron/demo/Polyhedron/Plugins/PMP/Repair_polyhedron_plugin.cpp b/Polyhedron/demo/Polyhedron/Plugins/PMP/Repair_polyhedron_plugin.cpp index f2f7ddf66b7..c8b2349fe1c 100644 --- a/Polyhedron/demo/Polyhedron/Plugins/PMP/Repair_polyhedron_plugin.cpp +++ b/Polyhedron/demo/Polyhedron/Plugins/PMP/Repair_polyhedron_plugin.cpp @@ -54,11 +54,11 @@ public: actionAutorefine->setObjectName("actionAutorefine"); actionAutorefineAndRMSelfIntersections->setObjectName("actionAutorefineAndRMSelfIntersections"); - actionRemoveDegenerateFaces->setProperty("subMenuName", "Polygon Mesh Processing/Repair/Experimental"); + actionRemoveDegenerateFaces->setProperty("subMenuName", "Polygon Mesh Processing/Repair"); actionStitchCloseBorderHalfedges->setProperty("subMenuName", "Polygon Mesh Processing/Repair/Experimental"); actionRemoveSelfIntersections->setProperty("subMenuName", "Polygon Mesh Processing/Repair/Experimental"); actionRemoveIsolatedVertices->setProperty("subMenuName", "Polygon Mesh Processing/Repair"); - actionDuplicateNMVertices->setProperty("subMenuName", "Polygon Mesh Processing/Repair/Experimental"); + actionDuplicateNMVertices->setProperty("subMenuName", "Polygon Mesh Processing/Repair"); actionAutorefine->setProperty("subMenuName", "Polygon Mesh Processing/Repair/Experimental"); actionAutorefineAndRMSelfIntersections->setProperty("subMenuName", "Polygon Mesh Processing/Repair/Experimental"); From 5b22f7213e141ee71c004fab58a3f747b47a601b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mael=20Rouxel-Labb=C3=A9?= Date: Wed, 25 Jul 2018 09:46:55 +0200 Subject: [PATCH 050/116] Fixed compilation error --- .../Polygon_mesh_processing/remove_degeneracies_example_OM.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Polygon_mesh_processing/examples/Polygon_mesh_processing/remove_degeneracies_example_OM.cpp b/Polygon_mesh_processing/examples/Polygon_mesh_processing/remove_degeneracies_example_OM.cpp index 408990850de..46763164848 100644 --- a/Polygon_mesh_processing/examples/Polygon_mesh_processing/remove_degeneracies_example_OM.cpp +++ b/Polygon_mesh_processing/examples/Polygon_mesh_processing/remove_degeneracies_example_OM.cpp @@ -20,7 +20,7 @@ int main(int argc, char* argv[]) const char* filename = (argc > 1) ? argv[1] : "data/degtri_sliding.off"; Mesh mesh; - if (!OpenMesh::IO::read_mesh(mesh, filename) || mesh.is_empty()) { + if (!OpenMesh::IO::read_mesh(mesh, filename) || num_vertices(mesh)) { std::cerr << "Not a valid .off file." << std::endl; return EXIT_FAILURE; } From 0417bb88d77c652fd301787eb0b110ea062dd6b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mael=20Rouxel-Labb=C3=A9?= Date: Wed, 25 Jul 2018 10:51:53 +0200 Subject: [PATCH 051/116] Hide "remove_degenerate_faces" --- .../PackageDescription.txt | 1 - .../Polygon_mesh_processing.txt | 6 +- .../doc/Polygon_mesh_processing/examples.txt | 2 - .../Polygon_mesh_processing/CMakeLists.txt | 6 +- .../remove_degeneracies_example.cpp | 34 --------- .../remove_degeneracies_example_OM.cpp | 38 ---------- .../CGAL/Polygon_mesh_processing/repair.h | 69 +++++++++---------- .../shape_predicates.h | 1 - .../Plugins/PMP/Repair_polyhedron_plugin.cpp | 2 +- 9 files changed, 42 insertions(+), 117 deletions(-) delete mode 100644 Polygon_mesh_processing/examples/Polygon_mesh_processing/remove_degeneracies_example.cpp delete mode 100644 Polygon_mesh_processing/examples/Polygon_mesh_processing/remove_degeneracies_example_OM.cpp diff --git a/Polygon_mesh_processing/doc/Polygon_mesh_processing/PackageDescription.txt b/Polygon_mesh_processing/doc/Polygon_mesh_processing/PackageDescription.txt index 181eb3ddcd7..d937e76949b 100644 --- a/Polygon_mesh_processing/doc/Polygon_mesh_processing/PackageDescription.txt +++ b/Polygon_mesh_processing/doc/Polygon_mesh_processing/PackageDescription.txt @@ -134,7 +134,6 @@ and provides a list of the parameters that are used in this package. - `CGAL::Polygon_mesh_processing::remove_isolated_vertices()` - `CGAL::Polygon_mesh_processing::is_non_manifold_vertex()` - `CGAL::Polygon_mesh_processing::duplicate_non_manifold_vertices()` -- `CGAL::Polygon_mesh_processing::remove_degenerate_faces()` - `CGAL::Polygon_mesh_processing::merge_vertices_in_range()` - `CGAL::Polygon_mesh_processing::merge_duplicated_vertices_in_boundary_cycle()` - `CGAL::Polygon_mesh_processing::merge_duplicated_vertices_in_boundary_cycles()` 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 b7ca98e8808..514d3b55397 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 @@ -463,8 +463,6 @@ This package offers a toolkit of functions to detect such undesirable elements. - `CGAL::Polygon_mesh_processing::is_cap_triangle_face()` - `CGAL::Polygon_mesh_processing::is_needle_triangle_face()` -This package offers functions to remove such undesirable elements, see Section \ref PMPRepairing. - **************************************** \section PMPOrientation Orientation @@ -555,6 +553,8 @@ with duplicated border edges. \cgalExample{Polygon_mesh_processing/stitch_borders_example.cpp} +\cond + \subsection DegenerateFaces Removing Degenerate Faces Some degenerate faces may be part of a given triangle mesh. @@ -573,6 +573,8 @@ is output. \cgalExample{Polygon_mesh_processing/remove_degeneracies_example.cpp} +\endcond + \subsection PMPManifoldness Polygon Mesh Manifoldness Non-manifold vertices can be detected using the function `CGAL::Polygon_mesh_processing::is_non_manifold_vertex()`. diff --git a/Polygon_mesh_processing/doc/Polygon_mesh_processing/examples.txt b/Polygon_mesh_processing/doc/Polygon_mesh_processing/examples.txt index 03ee471a773..cb09da7d83f 100644 --- a/Polygon_mesh_processing/doc/Polygon_mesh_processing/examples.txt +++ b/Polygon_mesh_processing/doc/Polygon_mesh_processing/examples.txt @@ -21,7 +21,5 @@ \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 -\example Polygon_mesh_processing/remove_degeneracies_example.cpp -\example Polygon_mesh_processing/remove_degeneracies_example_OM.cpp \example Polygon_mesh_processing/manifoldness_repair_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 15092e7a30f..2a3bc719826 100644 --- a/Polygon_mesh_processing/examples/Polygon_mesh_processing/CMakeLists.txt +++ b/Polygon_mesh_processing/examples/Polygon_mesh_processing/CMakeLists.txt @@ -89,7 +89,7 @@ create_single_source_cgal_program( "face_filtered_graph_example.cpp") create_single_source_cgal_program( "orient_polygon_soup_example.cpp") create_single_source_cgal_program( "triangulate_polyline_example.cpp") create_single_source_cgal_program( "mesh_slicer_example.cpp") -create_single_source_cgal_program( "remove_degeneracies_example.cpp") +#create_single_source_cgal_program( "remove_degeneracies_example.cpp") create_single_source_cgal_program( "isotropic_remeshing_example.cpp") create_single_source_cgal_program( "isotropic_remeshing_of_patch_example.cpp") create_single_source_cgal_program( "surface_mesh_intersection.cpp") @@ -119,8 +119,8 @@ target_link_libraries( point_inside_example_OM PRIVATE ${OPENMESH_LIBRARIES} ) create_single_source_cgal_program( "stitch_borders_example_OM.cpp" ) target_link_libraries( stitch_borders_example_OM PRIVATE ${OPENMESH_LIBRARIES} ) -create_single_source_cgal_program( "remove_degeneracies_example_OM.cpp") -target_link_libraries( remove_degeneracies_example_OM PRIVATE ${OPENMESH_LIBRARIES} ) +#create_single_source_cgal_program( "remove_degeneracies_example_OM.cpp") +#target_link_libraries( remove_degeneracies_example_OM PRIVATE ${OPENMESH_LIBRARIES} ) create_single_source_cgal_program( "triangulate_faces_example_OM.cpp") target_link_libraries( triangulate_faces_example_OM PRIVATE ${OPENMESH_LIBRARIES} ) diff --git a/Polygon_mesh_processing/examples/Polygon_mesh_processing/remove_degeneracies_example.cpp b/Polygon_mesh_processing/examples/Polygon_mesh_processing/remove_degeneracies_example.cpp deleted file mode 100644 index c79f9a5337f..00000000000 --- a/Polygon_mesh_processing/examples/Polygon_mesh_processing/remove_degeneracies_example.cpp +++ /dev/null @@ -1,34 +0,0 @@ -#include - -#include - -#include - -#include -#include - -typedef CGAL::Exact_predicates_inexact_constructions_kernel K; - -typedef CGAL::Surface_mesh Surface_mesh; - -int main(int argc, char* argv[]) -{ - const char* filename = (argc > 1) ? argv[1] : "data/degtri_sliding.off"; - std::ifstream input(filename); - - Surface_mesh mesh; - if (!input || !(input >> mesh) || mesh.is_empty()) { - std::cerr << "Not a valid .off file." << std::endl; - return EXIT_FAILURE; - } - - std::size_t nb = CGAL::Polygon_mesh_processing::remove_degenerate_faces(mesh); - - std::cout << "There were " << nb << " degenerate faces in this mesh" << std::endl; - - mesh.collect_garbage(); - std::ofstream out("repaired.off"); - out << mesh; - - return EXIT_SUCCESS; -} diff --git a/Polygon_mesh_processing/examples/Polygon_mesh_processing/remove_degeneracies_example_OM.cpp b/Polygon_mesh_processing/examples/Polygon_mesh_processing/remove_degeneracies_example_OM.cpp deleted file mode 100644 index 46763164848..00000000000 --- a/Polygon_mesh_processing/examples/Polygon_mesh_processing/remove_degeneracies_example_OM.cpp +++ /dev/null @@ -1,38 +0,0 @@ -#include - -#include -#include - -#include -#include - -#include -#include - -typedef CGAL::Exact_predicates_inexact_constructions_kernel K; - -typedef OpenMesh::PolyMesh_ArrayKernelT< > Mesh; - -namespace PMP = CGAL::Polygon_mesh_processing; - -int main(int argc, char* argv[]) -{ - const char* filename = (argc > 1) ? argv[1] : "data/degtri_sliding.off"; - - Mesh mesh; - if (!OpenMesh::IO::read_mesh(mesh, filename) || num_vertices(mesh)) { - std::cerr << "Not a valid .off file." << std::endl; - return EXIT_FAILURE; - } - - std::size_t nb = PMP::remove_degenerate_faces(mesh, - CGAL::parameters::vertex_point_map(get(CGAL::vertex_point, mesh)) - .geom_traits(K())); - - std::cout << "There were " << nb << " degenerate faces in this mesh" << std::endl; - - mesh.garbage_collection(); - OpenMesh::IO::write_mesh(mesh, "repaired.off"); - - return EXIT_SUCCESS; -} diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/repair.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/repair.h index 88a12acec66..a5af3737c07 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/repair.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/repair.h @@ -271,7 +271,6 @@ degenerate_edges(const TriangleMesh& tm, OutputIterator out) /// \cgalParamEnd /// \cgalNamedParamsEnd /// -/// \sa remove_degenerate_faces() template OutputIterator degenerate_faces(const FaceRange& faces, const TriangleMesh& tm, @@ -792,40 +791,40 @@ std::size_t remove_degenerate_edges(const EdgeRange& edge_range, return remove_degenerate_edges(edge_range, tmesh, parameters::all_default()); } -/// \ingroup PMP_repairing_grp -/// removes the degenerate faces from a triangulated surface mesh. -/// A face is considered degenerate if two of its vertices share the same location, -/// or more generally if all its vertices are collinear. -/// -/// @pre `CGAL::is_triangle_mesh(tmesh)` -/// -/// @tparam TriangleMesh a model of `FaceListGraph` and `MutableFaceGraph` -/// @tparam NamedParameters a sequence of \ref pmp_namedparameters "Named Parameters" -/// -/// @param tmesh the triangulated surface mesh to be repaired -/// @param np optional \ref pmp_namedparameters "Named Parameters" described below -/// -/// \cgalNamedParamsBegin -/// \cgalParamBegin{vertex_point_map} the property map with the points associated to the vertices of `pmesh`. -/// The type of this map is model of `ReadWritePropertyMap`. -/// If this parameter is omitted, an internal property map for -/// `CGAL::vertex_point_t` must be available in `TriangleMesh` -/// \cgalParamEnd -/// \cgalParamBegin{geom_traits} a geometric traits class instance. -/// The traits class must provide the nested type `Point_3`, -/// and the nested functors : -/// - `Compare_distance_3` to compute the distance between 2 points -/// - `Collinear_3` to check whether 3 points are collinear -/// - `Less_xyz_3` to compare lexicographically two points -/// - `Equal_3` to check whether 2 points are identical -/// For each functor `Foo`, a function `Foo foo_object()` -/// \cgalParamEnd -/// \cgalNamedParamsEnd -/// -/// \todo the function might not be able to remove all degenerate faces. -/// We should probably do something with the return type. -/// -/// \return number of removed degenerate faces +// \ingroup PMP_repairing_grp +// removes the degenerate faces from a triangulated surface mesh. +// A face is considered degenerate if two of its vertices share the same location, +// or more generally if all its vertices are collinear. +// +// @pre `CGAL::is_triangle_mesh(tmesh)` +// +// @tparam TriangleMesh a model of `FaceListGraph` and `MutableFaceGraph` +// @tparam NamedParameters a sequence of \ref pmp_namedparameters "Named Parameters" +// +// @param tmesh the triangulated surface mesh to be repaired +// @param np optional \ref pmp_namedparameters "Named Parameters" described below +// +// \cgalNamedParamsBegin +// \cgalParamBegin{vertex_point_map} the property map with the points associated to the vertices of `pmesh`. +// The type of this map is model of `ReadWritePropertyMap`. +// If this parameter is omitted, an internal property map for +// `CGAL::vertex_point_t` must be available in `TriangleMesh` +// \cgalParamEnd +// \cgalParamBegin{geom_traits} a geometric traits class instance. +// The traits class must provide the nested type `Point_3`, +// and the nested functors : +// - `Compare_distance_3` to compute the distance between 2 points +// - `Collinear_3` to check whether 3 points are collinear +// - `Less_xyz_3` to compare lexicographically two points +// - `Equal_3` to check whether 2 points are identical +// For each functor `Foo`, a function `Foo foo_object()` +// \cgalParamEnd +// \cgalNamedParamsEnd +// +// \todo the function might not be able to remove all degenerate faces. +// We should probably do something with the return type. +// +// \return number of removed degenerate faces template std::size_t remove_degenerate_faces(TriangleMesh& tmesh, const NamedParameters& np) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/shape_predicates.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/shape_predicates.h index 4cc98184b7f..35e2a7a0920 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/shape_predicates.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/shape_predicates.h @@ -117,7 +117,6 @@ bool is_degenerate_edge(typename boost::graph_traits::edge_descript /// \cgalNamedParamsEnd /// /// \sa `degenerate_faces()` -/// \sa `remove_degenerate_faces()` /// /// \return `true` if the face `f` is degenerate, `false` otherwise. template diff --git a/Polyhedron/demo/Polyhedron/Plugins/PMP/Repair_polyhedron_plugin.cpp b/Polyhedron/demo/Polyhedron/Plugins/PMP/Repair_polyhedron_plugin.cpp index c8b2349fe1c..d286ef3f50f 100644 --- a/Polyhedron/demo/Polyhedron/Plugins/PMP/Repair_polyhedron_plugin.cpp +++ b/Polyhedron/demo/Polyhedron/Plugins/PMP/Repair_polyhedron_plugin.cpp @@ -54,7 +54,7 @@ public: actionAutorefine->setObjectName("actionAutorefine"); actionAutorefineAndRMSelfIntersections->setObjectName("actionAutorefineAndRMSelfIntersections"); - actionRemoveDegenerateFaces->setProperty("subMenuName", "Polygon Mesh Processing/Repair"); + actionRemoveDegenerateFaces->setProperty("subMenuName", "Polygon Mesh Processing/Repair/Experimental"); actionStitchCloseBorderHalfedges->setProperty("subMenuName", "Polygon Mesh Processing/Repair/Experimental"); actionRemoveSelfIntersections->setProperty("subMenuName", "Polygon Mesh Processing/Repair/Experimental"); actionRemoveIsolatedVertices->setProperty("subMenuName", "Polygon Mesh Processing/Repair"); From a4d825f144a505af3daac842e3b67b8920235c1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mael=20Rouxel-Labb=C3=A9?= Date: Wed, 25 Jul 2018 11:29:25 +0200 Subject: [PATCH 052/116] Misc minor changes --- Polygon_mesh_processing/dont_submit | 2 -- .../CGAL/Polygon_mesh_processing/merge_border_vertices.h | 2 ++ .../include/CGAL/Polygon_mesh_processing/repair.h | 2 +- .../include/CGAL/Polygon_mesh_processing/shape_predicates.h | 2 ++ 4 files changed, 5 insertions(+), 3 deletions(-) delete mode 100644 Polygon_mesh_processing/dont_submit diff --git a/Polygon_mesh_processing/dont_submit b/Polygon_mesh_processing/dont_submit deleted file mode 100644 index 9ee49f9ce2d..00000000000 --- a/Polygon_mesh_processing/dont_submit +++ /dev/null @@ -1,2 +0,0 @@ -examples/Polygon_mesh_processing/remove_degeneracies_example.cpp -examples/Polygon_mesh_processing/remove_degeneracies_example_OM.cpp diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/merge_border_vertices.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/merge_border_vertices.h index 07fe1728d46..ad0622a1b62 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/merge_border_vertices.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/merge_border_vertices.h @@ -22,6 +22,8 @@ #ifndef CGAL_POLYGON_MESH_PROCESSING_MERGE_BORDER_VERTICES_H #define CGAL_POLYGON_MESH_PROCESSING_MERGE_BORDER_VERTICES_H +#include + #include #include #include diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/repair.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/repair.h index a5af3737c07..1927ee1b57c 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/repair.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/repair.h @@ -175,7 +175,7 @@ struct Less_vertex_point{ /// collects the degenerate edges within a given range of edges. /// /// @tparam EdgeRange a model of `Range` with value type `boost::graph_traits::%edge_descriptor` -/// @tparam TriangleMesh a model of `EdgeListGraph` +/// @tparam TriangleMesh a model of `HalfedgeGraph` /// @tparam NamedParameters a sequence of \ref pmp_namedparameters "Named Parameters" /// /// @param edges a subset of edges of `tm` diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/shape_predicates.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/shape_predicates.h index 35e2a7a0920..0057c32e0da 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/shape_predicates.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/shape_predicates.h @@ -23,6 +23,8 @@ #ifndef CGAL_POLYGON_MESH_PROCESSING_SHAPE_PREDICATES_H #define CGAL_POLYGON_MESH_PROCESSING_SHAPE_PREDICATES_H +#include + #include #include From 789d416f2142b42ff8774c2f04c72648ac8371f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mael=20Rouxel-Labb=C3=A9?= Date: Wed, 25 Jul 2018 11:33:44 +0200 Subject: [PATCH 053/116] Moved 'merge_vertices_in_range' to internal namespace and undocumented it --- .../PackageDescription.txt | 1 - .../merge_border_vertices.h | 26 +++++++++---------- 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/Polygon_mesh_processing/doc/Polygon_mesh_processing/PackageDescription.txt b/Polygon_mesh_processing/doc/Polygon_mesh_processing/PackageDescription.txt index d937e76949b..e2a59d63cf9 100644 --- a/Polygon_mesh_processing/doc/Polygon_mesh_processing/PackageDescription.txt +++ b/Polygon_mesh_processing/doc/Polygon_mesh_processing/PackageDescription.txt @@ -134,7 +134,6 @@ and provides a list of the parameters that are used in this package. - `CGAL::Polygon_mesh_processing::remove_isolated_vertices()` - `CGAL::Polygon_mesh_processing::is_non_manifold_vertex()` - `CGAL::Polygon_mesh_processing::duplicate_non_manifold_vertices()` -- `CGAL::Polygon_mesh_processing::merge_vertices_in_range()` - `CGAL::Polygon_mesh_processing::merge_duplicated_vertices_in_boundary_cycle()` - `CGAL::Polygon_mesh_processing::merge_duplicated_vertices_in_boundary_cycles()` diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/merge_border_vertices.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/merge_border_vertices.h index ad0622a1b62..da456fbf214 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/merge_border_vertices.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/merge_border_vertices.h @@ -138,18 +138,16 @@ void detect_identical_mergeable_vertices( } } -} // end of internal - -/// \ingroup PMP_repairing_grp -/// merges target vertices of a list of halfedges. -/// Halfedges must be sorted in the list. -/// -/// @tparam PolygonMesh a model of `FaceListGraph` and `MutableFaceGraph`. -/// @tparam HalfedgeRange a range of halfedge descriptors of `PolygonMesh`, model of `Range`. -/// -/// @param sorted_hedges a sorted list of halfedges. -/// @param pm the polygon mesh which contains the list of halfedges. -/// +// \ingroup PMP_repairing_grp +// merges target vertices of a list of halfedges. +// Halfedges must be sorted in the list. +// +// @tparam PolygonMesh a model of `FaceListGraph` and `MutableFaceGraph`. +// @tparam HalfedgeRange a range of halfedge descriptors of `PolygonMesh`, model of `Range`. +// +// @param sorted_hedges a sorted list of halfedges. +// @param pm the polygon mesh which contains the list of halfedges. +// template void merge_vertices_in_range(const HalfedgeRange& sorted_hedges, PolygonMesh& pm) @@ -192,6 +190,8 @@ void merge_vertices_in_range(const HalfedgeRange& sorted_hedges, remove_vertex(vd, pm); } +} // end of internal + /// \ingroup PMP_repairing_grp /// merges identical vertices around a cycle of connected edges. /// @@ -239,7 +239,7 @@ void merge_duplicated_vertices_in_boundary_cycle( { start=hedges.front(); // hedges are sorted along the cycle - merge_vertices_in_range(hedges, pm); + internal::merge_vertices_in_range(hedges, pm); } } From 1765ae106bee9243f05dafbbd940002b61ae74e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mael=20Rouxel-Labb=C3=A9?= Date: Thu, 26 Jul 2018 15:49:48 +0200 Subject: [PATCH 054/116] Added new headers to pmp.h --- Polygon_mesh_processing/include/CGAL/polygon_mesh_processing.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Polygon_mesh_processing/include/CGAL/polygon_mesh_processing.h b/Polygon_mesh_processing/include/CGAL/polygon_mesh_processing.h index 7d587ad4b84..164b29b25b7 100644 --- a/Polygon_mesh_processing/include/CGAL/polygon_mesh_processing.h +++ b/Polygon_mesh_processing/include/CGAL/polygon_mesh_processing.h @@ -49,6 +49,8 @@ #include #include #include +#include +#include // the named parameter header being not documented the doc is put here for now #ifdef DOXYGEN_RUNNING From fe10859e655a3ba0cd68467ee2a033d3d60b3a6b Mon Sep 17 00:00:00 2001 From: Maxime Gimeno Date: Fri, 27 Jul 2018 16:05:41 +0200 Subject: [PATCH 055/116] Add an action to Affine_transformation_plugin that duplicates a selected item on a grid --- .../Plugins/PCA/Affine_transform_plugin.cpp | 103 +++++++- .../Polyhedron/Plugins/PCA/CMakeLists.txt | 2 +- .../Plugins/PCA/MeshOnGrid_dialog.ui | 229 ++++++++++++++++++ 3 files changed, 326 insertions(+), 8 deletions(-) create mode 100644 Polyhedron/demo/Polyhedron/Plugins/PCA/MeshOnGrid_dialog.ui diff --git a/Polyhedron/demo/Polyhedron/Plugins/PCA/Affine_transform_plugin.cpp b/Polyhedron/demo/Polyhedron/Plugins/PCA/Affine_transform_plugin.cpp index 0d7445f7796..37c5e692017 100644 --- a/Polyhedron/demo/Polyhedron/Plugins/PCA/Affine_transform_plugin.cpp +++ b/Polyhedron/demo/Polyhedron/Plugins/PCA/Affine_transform_plugin.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #ifdef CGAL_USE_SURFACE_MESH #include "Scene_surface_mesh_item.h" #else @@ -19,11 +20,13 @@ #include #include #include +#include #include #include #include #include "ui_Transformation_widget.h" +#include "ui_MeshOnGrid_dialog.h" #ifdef CGAL_USE_SURFACE_MESH typedef Scene_surface_mesh_item Facegraph_item; #else @@ -173,15 +176,21 @@ public: Polyhedron_demo_affine_transform_plugin():started(false){} QList actions() const { - return QList() << actionTransformPolyhedron; + return QList() << actionTransformPolyhedron + << actionMeshOnGrid; } - bool applicable(QAction*) const { - return qobject_cast(scene->item(scene->mainSelectionIndex())) - || qobject_cast(scene->item(scene->mainSelectionIndex())) + bool applicable(QAction* a) const { + if(a == actionMeshOnGrid) + { + return qobject_cast(scene->item(scene->mainSelectionIndex())); + } + else + return qobject_cast(scene->item(scene->mainSelectionIndex())) + || qobject_cast(scene->item(scene->mainSelectionIndex())) #ifdef CGAL_USE_SURFACE_MESH - || qobject_cast(scene->item(scene->mainSelectionIndex())) - || qobject_cast(scene->item(scene->mainSelectionIndex())) + || qobject_cast(scene->item(scene->mainSelectionIndex())) + || qobject_cast(scene->item(scene->mainSelectionIndex())) #endif ; } @@ -196,6 +205,16 @@ public: mw = _mw; this->scene = scene_interface; + actionMeshOnGrid = new QAction( + #ifdef CGAL_USE_SURFACE_MESH + tr("Create a Grid of Surface Meshes") + #else + tr("Create a Grid of Polyhedra") + #endif + , mw); + if(actionMeshOnGrid) { + connect(actionMeshOnGrid, SIGNAL(triggered()),this, SLOT(grid())); + } actionTransformPolyhedron = new QAction( #ifdef CGAL_USE_SURFACE_MESH @@ -259,10 +278,10 @@ public: } private: - QDockWidget* dock_widget; Ui::TransformationWidget ui; QAction* actionTransformPolyhedron; + QAction* actionMeshOnGrid; Scene_facegraph_transform_item* transform_item; Scene_transform_point_set_item* transform_points_item; CGAL::Three::Scene_interface::Item_id tr_item_index; @@ -306,6 +325,7 @@ private: template void normalize(Item*); public Q_SLOTS: + void grid(); void go(); void transformed_killed(); @@ -388,6 +408,75 @@ public Q_SLOTS: }; // end class Polyhedron_demo_affine_transform_plugin +class GridDialog : + public QDialog, + public Ui::GridDialog +{ + Q_OBJECT +public: + GridDialog(QWidget* =0) + { + setupUi(this); + } +}; + +void Polyhedron_demo_affine_transform_plugin::grid() +{ + Facegraph_item* item = + qobject_cast(scene->item(scene->mainSelectionIndex())); + if(!item) + return; + + + FaceGraph m = *item->face_graph(); + + Scene_item::Bbox b = item->bbox(); + + + double x_t(CGAL::sqrt(CGAL::squared_distance(Kernel::Point_3(b.min(0), b.min(1), b.min(2)), + Kernel::Point_3(b.max(0), b.min(1), b.min(2))))), + y_t(CGAL::sqrt(CGAL::squared_distance(Kernel::Point_3(b.min(0), b.min(1), b.min(2)), + Kernel::Point_3(b.min(0), b.max(1), b.min(2))))), + z_t(CGAL::sqrt(CGAL::squared_distance(Kernel::Point_3(b.min(0), b.min(1), b.min(2)), + Kernel::Point_3(b.min(0), b.min(1), b.max(2))))); + + GridDialog dialog(mw); + dialog.x_space_doubleSpinBox->setValue(x_t); + dialog.y_space_doubleSpinBox->setValue(y_t); + dialog.z_space_doubleSpinBox->setValue(z_t); + if(!dialog.exec()) + return; + QApplication::setOverrideCursor(Qt::WaitCursor); + int i_max=dialog.x_spinBox->value(), + j_max=dialog.y_spinBox->value(), + k_max=dialog.z_spinBox->value(); + x_t = dialog.x_space_doubleSpinBox->value(); + y_t = dialog.y_space_doubleSpinBox->value(); + z_t = dialog.z_space_doubleSpinBox->value(); + + for(int i = 0; i < i_max; ++i) + { + for(int j = 0; j< j_max; ++j) + { + for(int k = 0; k< k_max; ++k) + { + FaceGraph e; + CGAL::copy_face_graph(m,e); + + Kernel::Aff_transformation_3 trans(CGAL::TRANSLATION, Kernel::Vector_3(i*x_t,j*y_t,k*z_t)); + CGAL::Polygon_mesh_processing::transform(trans, e); + Facegraph_item* t_item = new Facegraph_item(e); + t_item->setName(tr("%1 %2%3%4") + .arg(item->name()) + .arg(i) + .arg(j) + .arg(k)); + scene->addItem(t_item); + } + } + } + QApplication::restoreOverrideCursor(); +} void Polyhedron_demo_affine_transform_plugin::go(){ if (!started){ Scene_item* item = scene->item(scene->mainSelectionIndex()); diff --git a/Polyhedron/demo/Polyhedron/Plugins/PCA/CMakeLists.txt b/Polyhedron/demo/Polyhedron/Plugins/PCA/CMakeLists.txt index 978cbfa2c0b..43974e0709e 100644 --- a/Polyhedron/demo/Polyhedron/Plugins/PCA/CMakeLists.txt +++ b/Polyhedron/demo/Polyhedron/Plugins/PCA/CMakeLists.txt @@ -2,7 +2,7 @@ include( polyhedron_demo_macros ) polyhedron_demo_plugin(pca_plugin Pca_plugin) target_link_libraries(pca_plugin PUBLIC scene_polyhedron_item scene_surface_mesh_item scene_points_with_normal_item scene_basic_objects) -qt5_wrap_ui( transformUI_FILES Transformation_widget.ui ) +qt5_wrap_ui( transformUI_FILES Transformation_widget.ui MeshOnGrid_dialog.ui) polyhedron_demo_plugin(affine_transform_plugin Affine_transform_plugin ${transformUI_FILES}) target_link_libraries(affine_transform_plugin PUBLIC scene_polyhedron_item scene_polyhedron_transform_item scene_points_with_normal_item) diff --git a/Polyhedron/demo/Polyhedron/Plugins/PCA/MeshOnGrid_dialog.ui b/Polyhedron/demo/Polyhedron/Plugins/PCA/MeshOnGrid_dialog.ui new file mode 100644 index 00000000000..3613737b207 --- /dev/null +++ b/Polyhedron/demo/Polyhedron/Plugins/PCA/MeshOnGrid_dialog.ui @@ -0,0 +1,229 @@ + + + GridDialog + + + + 0 + 0 + 227 + 486 + + + + + 0 + 0 + + + + Dialog + + + + + + Along X + + + + + + 99999999999999999475366575191804932315794610450682175621941694731908308538307845136842752.000000000000000 + + + + + + + Spacing: + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Number of items : + + + + + + + 999 + + + 2 + + + + + + + + + + Along Y + + + + + + 99999999999999999475366575191804932315794610450682175621941694731908308538307845136842752.000000000000000 + + + + + + + Spacing: + + + + + + + 999 + + + 2 + + + + + + + Number of items : + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + Along Z + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + 99999999999999999475366575191804932315794610450682175621941694731908308538307845136842752.000000000000000 + + + + + + + Number of items : + + + + + + + Spacing: + + + + + + + 999 + + + 2 + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + GridDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + GridDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + From f58247d8dfe2c311ab91a5068c33e1077fdb7911 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mael=20Rouxel-Labb=C3=A9?= Date: Mon, 30 Jul 2018 15:48:20 +0200 Subject: [PATCH 056/116] Added missing quotes --- .../include/CGAL/Polygon_mesh_processing/repair.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/repair.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/repair.h index 1927ee1b57c..328f4bb3d15 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/repair.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/repair.h @@ -1467,7 +1467,7 @@ struct Vertex_collector /// @param v a vertex of `tm` /// @param tm a triangle mesh containing `v` /// -/// \sa duplicate_non_manifold_vertices() +/// \sa `duplicate_non_manifold_vertices()` /// /// \return `true` if the vertex is non-manifold, `false` otherwise. template From 9752621e7a9cac826a7cd9a835e24b53d932502f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mael=20Rouxel-Labb=C3=A9?= Date: Mon, 30 Jul 2018 15:50:23 +0200 Subject: [PATCH 057/116] Updated a figure --- .../fig/selfintersections.jpg | Bin 44618 -> 190744 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/Polygon_mesh_processing/doc/Polygon_mesh_processing/fig/selfintersections.jpg b/Polygon_mesh_processing/doc/Polygon_mesh_processing/fig/selfintersections.jpg index 7504ff7b8650e0a1299c505998d1fad58cdb8e90..ffc0bc0cfc24e8b0e5b9fdfd08c721a597e97eb2 100644 GIT binary patch literal 190744 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 rni>TunJ literal 44618 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

h_M#x(;X*xtYkz->T963CZAgKK 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 Date: Thu, 9 Aug 2018 08:52:13 +0200 Subject: [PATCH 058/116] Removed useless 'typename' --- .../test_shape_predicates.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Polygon_mesh_processing/test/Polygon_mesh_processing/test_shape_predicates.cpp b/Polygon_mesh_processing/test/Polygon_mesh_processing/test_shape_predicates.cpp index e68e8a7d09f..8f76f46fae5 100644 --- a/Polygon_mesh_processing/test/Polygon_mesh_processing/test_shape_predicates.cpp +++ b/Polygon_mesh_processing/test/Polygon_mesh_processing/test_shape_predicates.cpp @@ -19,7 +19,7 @@ void check_edge_degeneracy(const char* fname) { std::cout << "test edge degeneracy..."; - typedef typename boost::graph_traits::edge_descriptor edge_descriptor; + typedef boost::graph_traits::edge_descriptor edge_descriptor; std::ifstream input(fname); Surface_mesh mesh; @@ -39,7 +39,7 @@ void check_triangle_face_degeneracy(const char* fname) { std::cout << "test face degeneracy..."; - typedef typename boost::graph_traits::face_descriptor face_descriptor; + typedef boost::graph_traits::face_descriptor face_descriptor; std::ifstream input(fname); Surface_mesh mesh; @@ -82,8 +82,8 @@ void test_vertex_non_manifoldness(const char* fname) { std::cout << "test vertex non manifoldness..."; - typedef typename boost::graph_traits::vertex_descriptor vertex_descriptor; - typedef typename boost::graph_traits::vertices_size_type size_type; + typedef boost::graph_traits::vertex_descriptor vertex_descriptor; + typedef boost::graph_traits::vertices_size_type size_type; std::ifstream input(fname); Surface_mesh mesh; @@ -117,7 +117,7 @@ void test_vertices_merge_and_duplication(const char* fname) { std::cout << "test non manifold vertex duplication..."; - typedef typename boost::graph_traits::vertex_descriptor vertex_descriptor; + typedef boost::graph_traits::vertex_descriptor vertex_descriptor; std::ifstream input(fname); Surface_mesh mesh; @@ -166,9 +166,9 @@ void test_needles_and_caps(const char* fname) namespace PMP = CGAL::Polygon_mesh_processing; - typedef typename boost::graph_traits::halfedge_descriptor halfedge_descriptor; - typedef typename boost::graph_traits::face_iterator face_iterator; - typedef typename boost::graph_traits::face_descriptor face_descriptor; + typedef boost::graph_traits::halfedge_descriptor halfedge_descriptor; + typedef boost::graph_traits::face_iterator face_iterator; + typedef boost::graph_traits::face_descriptor face_descriptor; std::ifstream input(fname); Surface_mesh mesh; From 86174e394f387f2e839fe93ccc0d27522b738e3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mael=20Rouxel-Labb=C3=A9?= Date: Thu, 9 Aug 2018 08:52:23 +0200 Subject: [PATCH 059/116] Added missing header --- Polyhedron/demo/Polyhedron/include/CGAL/statistics_helpers.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Polyhedron/demo/Polyhedron/include/CGAL/statistics_helpers.h b/Polyhedron/demo/Polyhedron/include/CGAL/statistics_helpers.h index 155313f10a0..d0449e819b5 100644 --- a/Polyhedron/demo/Polyhedron/include/CGAL/statistics_helpers.h +++ b/Polyhedron/demo/Polyhedron/include/CGAL/statistics_helpers.h @@ -1,13 +1,15 @@ #ifndef POLYHEDRON_DEMO_STATISTICS_HELPERS_H #define POLYHEDRON_DEMO_STATISTICS_HELPERS_H +#include +#include + #include #include #include #include #include #include -#include #include #include From f5025a697f4d8c0652e0dc762049dd104784b7b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mael=20Rouxel-Labb=C3=A9?= Date: Fri, 10 Aug 2018 08:55:29 +0200 Subject: [PATCH 060/116] Removed useless typedefs --- .../demo/Polyhedron/Plugins/PMP/Degenerated_faces_plugin.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Polyhedron/demo/Polyhedron/Plugins/PMP/Degenerated_faces_plugin.cpp b/Polyhedron/demo/Polyhedron/Plugins/PMP/Degenerated_faces_plugin.cpp index 929890a4d8b..ddbdfc8c751 100644 --- a/Polyhedron/demo/Polyhedron/Plugins/PMP/Degenerated_faces_plugin.cpp +++ b/Polyhedron/demo/Polyhedron/Plugins/PMP/Degenerated_faces_plugin.cpp @@ -83,9 +83,7 @@ template bool isDegen(Mesh* mesh, std::vector::face_descriptor> &out_faces) { typedef typename boost::graph_traits::face_descriptor FaceDescriptor; - typedef typename boost::property_map::type Vpm; - typedef typename boost::property_traits::value_type Point; - typedef typename CGAL::Kernel_traits::Kernel Kernel; + //filter non-triangle_faces BOOST_FOREACH(FaceDescriptor f, faces(*mesh)) { From 89df0d977f252be3e3179d1e59ac3b76efffeed9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Loriot?= Date: Thu, 16 Aug 2018 16:55:49 +0200 Subject: [PATCH 061/116] typo and indicate that the cycle is a boundary cycle --- .../CGAL/Polygon_mesh_processing/merge_border_vertices.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/merge_border_vertices.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/merge_border_vertices.h index da456fbf214..d4600321959 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/merge_border_vertices.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/merge_border_vertices.h @@ -193,13 +193,13 @@ void merge_vertices_in_range(const HalfedgeRange& sorted_hedges, } // end of internal /// \ingroup PMP_repairing_grp -/// merges identical vertices around a cycle of connected edges. +/// merges identical vertices around a cycle of boundary edges. /// /// @tparam PolygonMesh a model of `FaceListGraph` and `MutableFaceGraph`. /// @tparam NamedParameter a sequence of \ref pmp_namedparameters "Named Parameters". /// -/// @param h a halfedge that belongs to the cycle. -/// @param pm the polygon mesh which containts the cycle. +/// @param h a halfedge that belongs to a boundary cycle. +/// @param pm the polygon mesh which contains the boundary cycle. /// @param np optional parameter of \ref pmp_namedparameters "Named Parameters" listed below. /// /// \cgalNamedParamsBegin @@ -249,7 +249,7 @@ void merge_duplicated_vertices_in_boundary_cycle( /// @tparam PolygonMesh a model of `FaceListGraph` and `MutableFaceGraph`. /// @tparam NamedParameter a sequence of \ref pmp_namedparameters "Named Parameters". /// -/// @param pm the polygon mesh which containts the cycle. +/// @param pm the polygon mesh which contains the cycles. /// @param np optional parameter of \ref pmp_namedparameters "Named Parameters" listed below. /// /// \cgalNamedParamsBegin From 7b740e9561567b5e91605a30c70fddf8c5505856 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mael=20Rouxel-Labb=C3=A9?= Date: Fri, 17 Aug 2018 10:44:35 +0200 Subject: [PATCH 062/116] Fixed 'is_non_manifold_vertex' A pinched vertex is not manifold --- .../CGAL/Polygon_mesh_processing/repair.h | 41 ++++++++++++------- 1 file changed, 26 insertions(+), 15 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/repair.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/repair.h index 328f4bb3d15..f54546f528a 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/repair.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/repair.h @@ -1460,36 +1460,47 @@ struct Vertex_collector } // end namespace internal /// \ingroup PMP_repairing_grp -/// checks whether a vertex of a triangle mesh is non-manifold. +/// checks whether a vertex of a polygon mesh is non-manifold. /// -/// @tparam TriangleMesh a model of `HalfedgeListGraph` +/// @tparam PolygonMesh a model of `HalfedgeListGraph` /// -/// @param v a vertex of `tm` -/// @param tm a triangle mesh containing `v` +/// @param v a vertex of `pm` +/// @param pm a triangle mesh containing `v` /// /// \sa `duplicate_non_manifold_vertices()` /// /// \return `true` if the vertex is non-manifold, `false` otherwise. -template -bool is_non_manifold_vertex(typename boost::graph_traits::vertex_descriptor v, - const TriangleMesh& tm) +template +bool is_non_manifold_vertex(typename boost::graph_traits::vertex_descriptor v, + const PolygonMesh& pm) { - CGAL_assertion(CGAL::is_triangle_mesh(tm)); - - typedef typename boost::graph_traits::halfedge_descriptor halfedge_descriptor; - + typedef typename boost::graph_traits::halfedge_descriptor halfedge_descriptor; boost::unordered_set halfedges_handled; - BOOST_FOREACH(halfedge_descriptor h, halfedges_around_target(v, tm)) - halfedges_handled.insert(h); - BOOST_FOREACH(halfedge_descriptor h, halfedges(tm)) + std::size_t incident_null_faces_counter = 0; + BOOST_FOREACH(halfedge_descriptor h, halfedges_around_target(v, pm)) { - if(v == target(h, tm)) + halfedges_handled.insert(h); + if(CGAL::is_border(h, pm)) + ++incident_null_faces_counter; + } + + if(incident_null_faces_counter > 1) + { + // The vertex is the sole connection between two connected components --> non-manifold + return true; + } + + BOOST_FOREACH(halfedge_descriptor h, halfedges(pm)) + { + if(v == target(h, pm)) { + // More than one umbrella incident to 'v' --> non-manifold if(halfedges_handled.count(h) == 0) return true; } } + return false; } From 979456be47e364ca2c7400619e58d8e64f25b4fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mael=20Rouxel-Labb=C3=A9?= Date: Fri, 17 Aug 2018 16:32:55 +0200 Subject: [PATCH 063/116] Fixed typo --- .../include/CGAL/Polygon_mesh_processing/repair.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/repair.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/repair.h index f54546f528a..780e983b508 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/repair.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/repair.h @@ -1521,7 +1521,7 @@ bool is_non_manifold_vertex(typename boost::graph_traits::vertex_de /// \cgalParamEnd /// \cgalParamBegin{vertex_is_constrained_map} a writable property map with `vertex_descriptor` /// as key and `bool` as `value_type`. `put(pmap, v, true)` will be called for each duplicated -/// vertices, as well as the original non-manifold vertex in the input mehs. +/// vertices, as well as the original non-manifold vertex in the input mesh. /// \cgalParamEnd /// \cgalParamBegin{output_iterator} a model of `OutputIterator` with value type /// `std::vector`. The first vertex of each vector is a non-manifold vertex From fca4e686abd65cd5619309b422e58ea33ac80074 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Loriot?= Date: Wed, 22 Aug 2018 09:32:16 +0200 Subject: [PATCH 064/116] remove initial version of corefinement now officially documented in PMP --- .../CGAL/Triangle_accessor_with_ppmap_3.h | 72 - .../include/CGAL/corefinement_operations.h | 484 ---- .../Combinatorial_map_for_corefinement.h | 115 - .../Combinatorial_map_output_builder.h | 1378 --------- .../corefinement/Polyhedra_output_builder.h | 2516 ----------------- .../corefinement/Polyhedron_constness_types.h | 83 - .../corefinement/connected_components.h | 344 --- .../intersection_coplanar_triangles_3.h | 334 --- .../intersection_triangle_segment_3.h | 172 -- ...intersection_triangle_segment_3_coplanar.h | 400 --- .../CGAL/internal/corefinement/predicates.h | 131 - .../CGAL/internal/corefinement/utils.h | 133 - .../CGAL/intersection_of_Polyhedra_3.h | 2187 -------------- ...ection_of_Polyhedra_3_refinement_visitor.h | 1163 -------- .../Operations_on_polyhedra/copyright | 2 - .../Operations_on_polyhedra/dependencies | 0 .../Operations_on_polyhedra/description.txt | 3 - .../Operations_on_polyhedra/license.txt | 1 - .../Operations_on_polyhedra/maintainer | 1 - .../run_test_corefinement_bool_op_full.sh | 18 +- .../test_corefinement_bool_op.cmd | 2 +- .../test_corefinement_bool_op.cpp | 149 +- .../test_corefinement_bool_op_full.cmd | 98 +- 23 files changed, 64 insertions(+), 9722 deletions(-) delete mode 100644 Operations_on_polyhedra/include/CGAL/Triangle_accessor_with_ppmap_3.h delete mode 100644 Operations_on_polyhedra/include/CGAL/corefinement_operations.h delete mode 100644 Operations_on_polyhedra/include/CGAL/internal/corefinement/Combinatorial_map_for_corefinement.h delete mode 100644 Operations_on_polyhedra/include/CGAL/internal/corefinement/Combinatorial_map_output_builder.h delete mode 100644 Operations_on_polyhedra/include/CGAL/internal/corefinement/Polyhedra_output_builder.h delete mode 100644 Operations_on_polyhedra/include/CGAL/internal/corefinement/Polyhedron_constness_types.h delete mode 100644 Operations_on_polyhedra/include/CGAL/internal/corefinement/connected_components.h delete mode 100644 Operations_on_polyhedra/include/CGAL/internal/corefinement/intersection_coplanar_triangles_3.h delete mode 100644 Operations_on_polyhedra/include/CGAL/internal/corefinement/intersection_triangle_segment_3.h delete mode 100644 Operations_on_polyhedra/include/CGAL/internal/corefinement/intersection_triangle_segment_3_coplanar.h delete mode 100644 Operations_on_polyhedra/include/CGAL/internal/corefinement/predicates.h delete mode 100644 Operations_on_polyhedra/include/CGAL/internal/corefinement/utils.h delete mode 100644 Operations_on_polyhedra/include/CGAL/intersection_of_Polyhedra_3.h delete mode 100644 Operations_on_polyhedra/include/CGAL/intersection_of_Polyhedra_3_refinement_visitor.h delete mode 100644 Operations_on_polyhedra/package_info/Operations_on_polyhedra/copyright delete mode 100644 Operations_on_polyhedra/package_info/Operations_on_polyhedra/dependencies delete mode 100644 Operations_on_polyhedra/package_info/Operations_on_polyhedra/description.txt delete mode 100644 Operations_on_polyhedra/package_info/Operations_on_polyhedra/license.txt delete mode 100644 Operations_on_polyhedra/package_info/Operations_on_polyhedra/maintainer diff --git a/Operations_on_polyhedra/include/CGAL/Triangle_accessor_with_ppmap_3.h b/Operations_on_polyhedra/include/CGAL/Triangle_accessor_with_ppmap_3.h deleted file mode 100644 index 0b4fd781f9e..00000000000 --- a/Operations_on_polyhedra/include/CGAL/Triangle_accessor_with_ppmap_3.h +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright (c) 2011 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_TRIANGLE_ACCESSOR_WITH_PPMAP_3_H -#define CGAL_TRIANGLE_ACCESSOR_WITH_PPMAP_3_H - -#include - - -#include -#include - -namespace CGAL{ - -template < class Polyhedron, class PolyhedronPointPMap> -struct Triangle_accessor_with_ppmap_3 -{ - typedef PolyhedronPointPMap Ppmap; - typedef typename boost::property_traits< Ppmap >::value_type Point_3; - typedef typename CGAL::Kernel_traits::Kernel Kernel; - typedef typename Kernel::Triangle_3 Triangle_3; - typedef typename Polyhedron::Facet_const_iterator Triangle_iterator; - typedef typename Polyhedron::Facet_const_handle Triangle_handle; - - PolyhedronPointPMap ppmap; - - Triangle_accessor_with_ppmap_3(){} - - Triangle_accessor_with_ppmap_3(PolyhedronPointPMap ppmap):ppmap(ppmap) {} - - Triangle_iterator triangles_begin(const Polyhedron& p) const - { - return p.facets_begin(); - } - - Triangle_iterator triangles_end(const Polyhedron& p) const - { - return p.facets_end(); - } - - Triangle_3 triangle(const Triangle_handle& handle) const - { - typedef typename Kernel::Point_3 Point; - const Point& a = get(ppmap, handle->halfedge()->vertex()); - const Point& b = get(ppmap, handle->halfedge()->next()->vertex()); - const Point& c = get(ppmap, handle->halfedge()->next()->next()->vertex()); - return Triangle_3(a,b,c); - } -}; - -} // end of namespace CGAL - -#endif // CGAL_TRIANGLE_ACCESSOR_WITH_PPMAP_3_H diff --git a/Operations_on_polyhedra/include/CGAL/corefinement_operations.h b/Operations_on_polyhedra/include/CGAL/corefinement_operations.h deleted file mode 100644 index 80d1c7d7bbe..00000000000 --- a/Operations_on_polyhedra/include/CGAL/corefinement_operations.h +++ /dev/null @@ -1,484 +0,0 @@ -// Copyright (c) 2011 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_COREFINEMENT_OPERATIONS_H -#define CGAL_COREFINEMENT_OPERATIONS_H - -#include - - -#include -#include -#include - -namespace CGAL{ -/** \cond */ -namespace internal{ - -template -class Import_volume_as_polyhedron : public CGAL::Modifier_base { - typedef typename HDS::Halfedge_handle Halfedge_handle; - typedef typename HDS::Vertex_handle Vertex_handle; - typedef typename HDS::Face_handle Face_handle; - typedef typename HDS::Vertex Vertex; - typedef typename HDS::Halfedge Halfedge; - typedef typename HDS::Face Face; - - //data members - Face_handle current_face; - std::vector< typename Vertex::Point > points; - std::size_t nb_edges; - std::vector > faces; - -public: - - //to import each piece individually - template - Import_volume_as_polyhedron(const Combinatorial_map_3& map,typename Combinatorial_map_3::Dart_const_handle dart):nb_edges(0) - { - typedef Combinatorial_map_3 CMap; - typedef std::map Vertex_map; - Vertex_map vertex_map; - unsigned int index=0; - //recover all the vertices in the current volumeiterator over all the point - //map the vertex to the index of the point in the vector - for (typename CMap::template One_dart_per_incident_cell_const_range<0,3>::const_iterator - it=map.template one_dart_per_incident_cell<0,3>(dart).begin(), - itend=map.template one_dart_per_incident_cell<0,3>(dart).end(); - it!=itend; ++it) - { - points.push_back(map.template attribute<0>(it)->point()); - vertex_map.insert(std::make_pair(&map.template attribute<0>(it)->point(),index++)); - } - - //count the number of edges - nb_edges+=map.template one_dart_per_incident_cell<1,3>(dart).size(); - - //recover one dart per face - for (typename CMap::template One_dart_per_incident_cell_const_range<2,3>::const_iterator - it=map.template one_dart_per_incident_cell<2,3>(dart).begin(), - itend=map.template one_dart_per_incident_cell<2,3>(dart).end(); - it!=itend; ++it) - { - //warning: the convention used into a polyhedron is that the normal - // of a triangle indicates the outside of the object; thus - // we need to reverse the orientation of the faces of the - // combinatorial map. - unsigned int i=vertex_map[&map.template attribute<0>(it)->point()]; - unsigned int j=vertex_map[&map.template attribute<0>(map.beta(it, 0))->point()]; - unsigned int k=vertex_map[&map.template attribute<0>(map.beta(it, 1))->point()]; - faces.push_back(CGAL::cpp11::make_tuple(i,j,k)); - } - } - - //for intersection and symetric difference - template - Import_volume_as_polyhedron(const Combinatorial_map_3& map, - Iterator dart_begin, - Iterator dart_end):nb_edges(0) - { - typedef Combinatorial_map_3 CMap; - typedef std::map Vertex_map; - Vertex_map vertex_map; - unsigned int index=0; - - for (Iterator it=dart_begin;it!=dart_end;++it) - { - typename Combinatorial_map_3::Dart_const_handle dart=*it; - //recover all the vertices in the current volumeiterator over all the point - //map the vertex to the index of the point in the vector - for (typename CMap::template One_dart_per_incident_cell_const_range<0,3>::const_iterator - it=map.template one_dart_per_incident_cell<0,3>(dart).begin(), - itend=map.template one_dart_per_incident_cell<0,3>(dart).end(); - it!=itend; ++it) - { - if ( vertex_map.insert(std::make_pair(&map.template attribute<0>(it)->point(),index)).second ) - { - points.push_back(map.template attribute<0>(it)->point()); - ++index; - } - } - - //count the number of edges - nb_edges+=map.template one_dart_per_incident_cell<1,3>(dart).size(); - - //recover one dart per face - for (typename CMap::template One_dart_per_incident_cell_const_range<2,3>::const_iterator - it=map.template one_dart_per_incident_cell<2,3>(dart).begin(), - itend=map.template one_dart_per_incident_cell<2,3>(dart).end(); - it!=itend; ++it) - { - //warning: the convention used into a polyhedron is that the normal - // of a triangle indicates the outside of the object; thus - // we need to reverse the orientation of the faces of the - // combinatorial map. - unsigned int i=vertex_map[&map.template attribute<0>(it)->point()]; - unsigned int j=vertex_map[&map.template attribute<0>(map.beta(it, 0))->point()]; - unsigned int k=vertex_map[&map.template attribute<0>(map.beta(it, 1))->point()]; - faces.push_back(CGAL::cpp11::make_tuple(i,j,k)); - } - } - } - - //for union : use the inverse of the complementary - template - Import_volume_as_polyhedron(const Combinatorial_map_3& map, - Iterator dart_begin, - Iterator dart_end,bool):nb_edges(0) - { - typedef Combinatorial_map_3 CMap; - typedef std::map Vertex_map; - Vertex_map vertex_map; - unsigned int index=0; - - for (Iterator it=dart_begin;it!=dart_end;++it) - { - typename Combinatorial_map_3::Dart_const_handle dart=*it; - //recover all the vertices in the current volumeiterator over all the point - //map the vertex to the index of the point in the vector - for (typename CMap::template One_dart_per_incident_cell_const_range<0,3>::const_iterator - it=map.template one_dart_per_incident_cell<0,3>(dart).begin(), - itend=map.template one_dart_per_incident_cell<0,3>(dart).end(); - it!=itend; ++it) - { - if (vertex_map.insert(std::make_pair(&map.template attribute<0>(it)->point(),index)).second ) - { - points.push_back(map.template attribute<0>(it)->point()); - ++index; - } - } - - //count the number of edges - nb_edges+=map.template one_dart_per_incident_cell<1,3>(dart).size(); - - //recover one dart per face - for (typename CMap::template One_dart_per_incident_cell_const_range<2,3>::const_iterator - it=map.template one_dart_per_incident_cell<2,3>(dart).begin(), - itend=map.template one_dart_per_incident_cell<2,3>(dart).end(); - it!=itend; ++it) - { - //warning: the convention used into a polyhedron is that the normal - // of a triangle indicates the outside of the object; thus - // we need to reverse the orientation of the faces of the - // combinatorial map. Since to get the complementary we - // also need to reverse the orientation, we finally do - // not change it. - unsigned int i=vertex_map[&map.template attribute<0>(it)->point()]; - unsigned int j=vertex_map[&map.template attribute<0>(map.beta(it, 1))->point()]; - unsigned int k=vertex_map[&map.template attribute<0>(map.beta(it, 0))->point()]; - faces.push_back(CGAL::cpp11::make_tuple(i,j,k)); - } - } - } - - - void operator()( HDS& hds) - { - CGAL::Polyhedron_incremental_builder_3 B(hds, true); - B.begin_surface( points.size(), faces.size(),2*nb_edges); - - //insert vertices - for (typename std::vector::iterator it=points.begin();it!=points.end();++it) - B.add_vertex(*it); - - //create faces - for (std::vector >::iterator it=faces.begin();it!=faces.end();++it) - { - B.begin_facet(); - B.add_vertex_to_facet(CGAL::cpp11::get<0>(*it)); - B.add_vertex_to_facet(CGAL::cpp11::get<1>(*it)); - B.add_vertex_to_facet(CGAL::cpp11::get<2>(*it)); - B.end_facet(); - } - B.end_surface(); - } -}; - -} -/** \endcond */ - -/*! \class Polyhedron_corefinement corefinement_operations.h CGAL/corefinement_operations.h - * Function object to compute the decomposition of the space induced by two polyhedra. - * @tparam Polyhedron must be an instantiation of CGAL::Polyhedron_3. - * @tparam Kernel must be a CGAL Kernel compatible with the underlying kernel of Polyhedron. - * @tparam Output_polyhedron is a polyhedron type used as output. `Kernel` must be compatible with the underlying kernel of `Output_polyhedron`. - */ -template -class Polyhedron_corefinement -{ - typedef internal::Import_volume_as_polyhedron Volume_import_modifier; - -public: - /** Enumeration of the different feature tags, listing all kind of space decomposition the functor can compute given two input polyhedra P and Q.*/ - enum Boolean_operation_tag - { - Join_tag=1, /*!< the union of P and Q. */ - Intersection_tag=2, /*!< the intersection of P and Q. */ - P_minus_Q_tag=4, /*!< P minus Q. */ - Q_minus_P_tag=8, /*!< Q minus P. */ - Parts_of_P_tag=32, /*!< decomposition of the volume bounded by P induced by Q. */ - Parts_of_Q_tag=64, /*!< decomposition of the volume bounded by Q induced by P. */ - Decomposition_tag=16 /*!< Both decompositions of P and Q. */ - }; - - /** - * This computes different polyhedra according to the value of features. - * Each input polyhedron is expected to bound a volume: the volume is bounded by the surface polyhedron with facet - * normals pointing outside the volume. Each facet is supposed to be counterclockwise oriented, that is its vertex - * sequence (induced by halfedges) is seen counterclockwise from the side of the facet pointed by its normal. If one or - * both polyhedra are not closed, the algorithm will end up correctly if each intersection polyline separates each surface - * in two components. In that case for each triangle of each surface path boundary, the interior of the volume is considered - * to be on the side opposite of that pointed by it normals (but the orientation must be consistent on each patch). - * the surface the volume bounded by an open surface is considered to be an infinite volume above - * or below the surface (the side not pointed by normal vectors). - * @tparam Polyline_output_iterator must be an output iterator of std::vector. - * @tparam Polyhedron_ptr_and_type_output_iterator an output iterator of std::pair. - * @param P is the first input triangulated polyhedron. Note that a reference is taken and P will be updated to contain the 1D intersection between the two surfaces P and Q. - * @param Q is the second input triangulated polyhedron. Note that a reference is taken and Q will be updated to contain the 1D intersection between the two surfaces P and Q. - * @param polyline_output is an output iterator that collects intersection polylines between P and Q. - * @param poly_output is an output iterator that collects output polyhedra. Note that each polyhedron is allocated within this function (thus must be explicitly deleted when no longer used). The integer is a feature tag corresponding to the volume represented by the polyhedron of the pair. - * @param features is an integer indicating what polyhedra the function must compute. If several kind of polyhedra are expected, feature tags must be combined by |. For example if features = Polyhedron_corefinement::Join_tag | Polyhedron_corefinement::Intersection_tag, then poly_output will collect two polyhedra, the union and the intersection of P and Q. - */ - template - void operator()( Polyhedron& P, Polyhedron& Q, - Polyline_output_iterator polyline_output, - Polyhedron_ptr_and_type_output_iterator poly_output, - int features) const - { - typedef CGAL::Corefinement::Combinatorial_map_output_builder Output_builder; - Output_builder output_builder; - typedef CGAL::Node_visitor_refine_polyhedra Split_visitor; - Split_visitor visitor(output_builder); - CGAL::Intersection_of_Polyhedra_3 polyline_intersections(visitor); - - polyline_intersections(P, Q, polyline_output); - - typedef typename Output_builder::Combinatorial_map_3 Combinatorial_map_3; - typedef typename Output_builder::Volume_info Volume_info; - typedef typename Combinatorial_map_3::Dart_const_handle Dart_const_handle; - - const Combinatorial_map_3& final_map=output_builder.combinatorial_map(); - - typename Combinatorial_map_3::template One_dart_per_cell_const_range<3> cell_range=final_map.template one_dart_per_cell<3>(); - - std::list intersection; - std::list union_; - std::list P_minus_Q; - std::list Q_minus_P; - - for (typename Combinatorial_map_3::template One_dart_per_cell_const_range<3>::const_iterator - it = cell_range.begin(), it_end= cell_range.end(); - it!= it_end; - ++it ) - { - - const Volume_info& info=final_map.template attribute<3>(it)->info(); - std::size_t inside_size=info.inside.size(); - std::size_t outside_size=info.outside.size(); - - if ( inside_size + outside_size != 2){ - std::cerr << "Error: One volume cannot be represented using a polyhedron. Aborted.\n"; - break; - } - - switch (outside_size) - { - case 2: - if (features & Join_tag) union_.push_back(it); - break; - case 0: - if (features & Intersection_tag) intersection.push_back(it); - break; - default: - if ( *info.inside.begin() == &P ) - { if (features & P_minus_Q_tag) P_minus_Q.push_back(it); } - else - { if (features & Q_minus_P_tag) Q_minus_P.push_back(it); } - } - - if ( features&Decomposition_tag ) - { - Volume_import_modifier modifier(final_map,it); - Output_polyhedron* new_poly=new Output_polyhedron(); - new_poly->delegate(modifier); - *poly_output++ = std::make_pair( new_poly,static_cast(Decomposition_tag) ); - } - - if ( features&Parts_of_P_tag && info.inside.find(&P)!=info.inside.end() ) - { - Volume_import_modifier modifier(final_map,it); - Output_polyhedron* new_poly=new Output_polyhedron(); - new_poly->delegate(modifier); - *poly_output++ = std::make_pair( new_poly,static_cast(Parts_of_P_tag) ); - } - - if ( features&Parts_of_Q_tag && info.inside.find(&Q)!=info.inside.end() ) - { - Volume_import_modifier modifier(final_map,it); - Output_polyhedron* new_poly=new Output_polyhedron(); - new_poly->delegate(modifier); - *poly_output++ = std::make_pair( new_poly,static_cast(Parts_of_Q_tag) ); - } - } - - if (!intersection.empty()) - { - Volume_import_modifier modifier(final_map,intersection.begin(),intersection.end()); - Output_polyhedron* new_poly=new Output_polyhedron(); - new_poly->delegate(modifier); - *poly_output++=std::make_pair( new_poly,static_cast(Intersection_tag) ); - } - - if (!P_minus_Q.empty()) - { - Volume_import_modifier modifier(final_map,P_minus_Q.begin(),P_minus_Q.end()); - Output_polyhedron* new_poly=new Output_polyhedron(); - new_poly->delegate(modifier); - *poly_output++=std::make_pair( new_poly,static_cast(P_minus_Q_tag) ); - } - - if (!Q_minus_P.empty()) - { - Volume_import_modifier modifier(final_map,Q_minus_P.begin(),Q_minus_P.end()); - Output_polyhedron* new_poly=new Output_polyhedron(); - new_poly->delegate(modifier); - *poly_output++=std::make_pair( new_poly,static_cast(Q_minus_P_tag) ); - } - - if (!union_.empty()) - { - Volume_import_modifier modifier(final_map,union_.begin(),union_.end(),true); - Output_polyhedron* new_poly=new Output_polyhedron(); - new_poly->delegate(modifier); - *poly_output++=std::make_pair( new_poly,static_cast(Join_tag) ); - } - } - /** - * This updates P according to the value of features. - * Each input polyhedron is expected to bound a volume: the volume is bounded by the surface polyhedron with facet - * normals pointing outside the volume. Each facet is supposed to be counterclockwise oriented, that is its vertex - * sequence (induced by halfedges) is seen counterclockwise from the side of the facet pointed by its normal. If one or - * both polyhedra are not closed, the algorithm will end up correctly if each intersection polyline separates each surface - * in two components. In that case for each triangle of each surface path boundary, the interior of the volume is considered - * to be on the side opposite of that pointed by it normals (but the orientation must be consistent on each patch). - * the surface the volume bounded by an open surface is considered to be an infinite volume above - * or below the surface (the side not pointed by normal vectors). - * @tparam Polyline_output_iterator must be an output iterator of std::vector. - * @param P is the first input triangulated polyhedron. Note that a reference is taken and P will be updated to contain the 1D intersection between the two surfaces P and Q. - * @param Q is the first input triangulated polyhedron. Note that a reference is taken and Q will be updated to contain the 1D intersection between the two surfaces P and Q. - * @param polyline_output is an output iterator that collects intersection polylines between P and Q. - * @param features is either Join_tag, Intersection_tag, P_minus_Q_tag or Q_minus_P_tag - */ - template - void operator()( Polyhedron& P, Polyhedron& Q, - Polyline_output_iterator polyline_output, - Boolean_operation_tag features) const - { - typedef CGAL::Node_visitor_refine_polyhedra Split_visitor; - Split_visitor visitor; - CGAL::Intersection_of_Polyhedra_3 polyline_intersections(visitor); - - polyline_intersections(P, Q, polyline_output); - - typedef typename Split_visitor::Combinatorial_map_3 Combinatorial_map_3; - typedef typename Split_visitor::Volume_info Volume_info; - typedef typename Combinatorial_map_3::Dart_const_handle Dart_const_handle; - - const typename Split_visitor::Combinatorial_map_3& final_map=visitor.combinatorial_map(); - - typename Combinatorial_map_3::template One_dart_per_cell_const_range<3> cell_range=final_map.template one_dart_per_cell<3>(); - - std::list darts; - - for (typename Combinatorial_map_3::template One_dart_per_cell_const_range<3>::const_iterator - it = cell_range.begin(), it_end= cell_range.end(); - it!= it_end; - ++it ) - { - - const Volume_info& info=it->template attribute<3>()->info(); - std::size_t inside_size=info.inside.size(); - std::size_t outside_size=info.outside.size(); - - if ( inside_size + outside_size != 2){ - std::cerr << "Error: One volume cannot be represented using a polyhedron. Aborted.\n"; - break; - } - - switch (outside_size) - { - case 2: - if ( features==Join_tag ) darts.push_back(it); - break; - case 0: - if ( features==Intersection_tag ) darts.push_back(it); - break; - default: - if ( *info.inside.begin() == &P ) - { if (features == P_minus_Q_tag) darts.push_back(it); } - else - { if (features == Q_minus_P_tag) darts.push_back(it); } - } - } - - P.clear(); - - if (!darts.empty()) - { - Volume_import_modifier modifier= - features==Join_tag? - Volume_import_modifier(final_map,darts.begin(),darts.end(),true) - : Volume_import_modifier(final_map,darts.begin(),darts.end()); - P.delegate(modifier); - } - } - - /** \cond */ - static std::string get_type_str(const std::string& Pname,const std::string& Qname,int i) - { - switch (i) - { - case Join_tag: - return Pname+std::string("_union_")+Qname; - case P_minus_Q_tag: - return Pname+std::string("_minus_")+Qname; - case Q_minus_P_tag: - return Qname+std::string("_minus_")+Pname; - case Intersection_tag: - return Pname+std::string("_inter_")+Qname; - case Decomposition_tag: - return std::string("Decomposition"); - } - return std::string("Unknow"); - } - /** \endcond */ -}; - - - - -} - -#endif // CGAL_COREFINEMENT_OPERATIONS_H diff --git a/Operations_on_polyhedra/include/CGAL/internal/corefinement/Combinatorial_map_for_corefinement.h b/Operations_on_polyhedra/include/CGAL/internal/corefinement/Combinatorial_map_for_corefinement.h deleted file mode 100644 index 396da390ead..00000000000 --- a/Operations_on_polyhedra/include/CGAL/internal/corefinement/Combinatorial_map_for_corefinement.h +++ /dev/null @@ -1,115 +0,0 @@ -// Copyright (c) 2012 GeometryFactory Sarl (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_INTERNAL_COMBINATORIAL_MAP_FOR_COREFINEMENT_H -#define CGAL_INTERNAL_COMBINATORIAL_MAP_FOR_COREFINEMENT_H - -#include - - -#include -#include -#include -#include - -namespace CGAL{ - namespace internal_IOP{ - -template -struct Volume_info{ - std::set outside; - std::set inside; - bool is_empty; - Volume_info():is_empty(false){} -}; - -struct Volume_on_merge -{ - template - void operator() (Attribute& a1,const Attribute& a2) const - { - CGAL_assertion(!a1.info().is_empty && !a2.info().is_empty); - std::copy(a2.info().outside.begin(),a2.info().outside.end(),std::inserter(a1.info().outside,a1.info().outside.begin())); - std::copy(a2.info().inside.begin(),a2.info().inside.end(),std::inserter(a1.info().inside,a1.info().inside.begin())); - } -}; - -#ifndef NDEBUG -struct Point_on_merge -{ - template - void operator() (Attribute& a1,const Attribute& a2) const - { - CGAL_assertion(a1.point()==a2.point() ); - CGAL_USE(a1); CGAL_USE(a2); - } -}; -#endif - - -template < class Refs, class T, class Point_, - class Functor_on_merge_=CGAL::Null_functor, - class Functor_on_split_=CGAL::Null_functor > -class My_cell_attribute_with_point : - public CGAL::Cell_attribute_without_info -{ - Point_ mpoint; -public: - typedef Point_ Point; - typedef Functor_on_merge_ Functor_on_merge; - typedef Functor_on_split_ Functor_on_split; - - My_cell_attribute_with_point(){} - My_cell_attribute_with_point(const Point& apoint) : mpoint(apoint) {} - Point& point() { return mpoint; } - const Point& point() const { return mpoint; } - -}; - -template -struct Item_with_points_and_volume_info -{ - static const unsigned int dimension = 3; - static const unsigned int NB_MARKS = 32; - - template - struct Dart_wrapper - { - typedef Traits_ Traits; - typedef typename Traits::FT FT; - typedef typename Traits::Point_3 Point; - typedef typename Traits::Vector_3 Vector; - #ifndef NDEBUG - typedef My_cell_attribute_with_point Vertex_attribute; - #else - typedef My_cell_attribute_with_point Vertex_attribute; - #endif - typedef CGAL::Cell_attribute,CGAL::Tag_true,Volume_on_merge > Volume_attribute; - typedef CGAL::cpp11::tuple< Vertex_attribute, - void, - void, - Volume_attribute> Attributes; - }; -}; - -} } //namespace CGAL::internal_IOP - -#endif //CGAL_INTERNAL_COMBINATORIAL_MAP_FOR_COREFINEMENT_H diff --git a/Operations_on_polyhedra/include/CGAL/internal/corefinement/Combinatorial_map_output_builder.h b/Operations_on_polyhedra/include/CGAL/internal/corefinement/Combinatorial_map_output_builder.h deleted file mode 100644 index 944236e98c2..00000000000 --- a/Operations_on_polyhedra/include/CGAL/internal/corefinement/Combinatorial_map_output_builder.h +++ /dev/null @@ -1,1378 +0,0 @@ -// Copyright (c) 2011 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_INTERNAL_COREFINEMENT_COMBINATORIAL_MAP_OUTPUT_BUILDER_H -#define CGAL_INTERNAL_COREFINEMENT_COMBINATORIAL_MAP_OUTPUT_BUILDER_H - -#include - - -#include -#include -#include -#include -#include -#include -#include - -/// \todo right now patches made of coplanar patches are handled by considering -/// each coplanar triangle while it should be sufficient to have one -/// procedure for all coplanar triangles of the patch together with -/// a sew-handling of the boundary of the patch. - -namespace CGAL { - -namespace internal_IOP { -//turn around the target vertex of dart to find a marked dart -template -boost::optional -next_marked_dart_around_target_vertex( - Combinatorial_map_3& final_map, - typename Combinatorial_map_3::Dart_handle dart, - std::size_t mark_index) -{ - CGAL_precondition(final_map.is_marked(dart,mark_index)); - typename Combinatorial_map_3::Dart_handle next=final_map.beta(dart,1); - while ( ! final_map.is_marked(next,mark_index) ) { - if (final_map.is_free(next,2) )//we reach a boundary - return boost::optional(); - next=final_map.beta(next,2,1); - } - if (next == dart) //no new dart have been found - return boost::optional(); - CGAL_precondition(&final_map.template attribute<0>(final_map.beta(dart,1))->point() == - &final_map.template attribute<0>(next)->point()); - return boost::optional (next); -} - -//turn around the target vertex of dart to find a marked dart -//with expected_target as target vertex -template -typename Combinatorial_map_3::Dart_handle -get_next_marked_dart_around_target_vertex( - Combinatorial_map_3& final_map, - typename Combinatorial_map_3::Dart_handle dart, - std::size_t mark_index) -{ - CGAL_precondition(final_map.is_marked(dart,mark_index)); - typename Combinatorial_map_3::Dart_handle next=final_map.beta(dart,1); - while ( !final_map.is_marked(next,mark_index) ) { - CGAL_assertion( !final_map.is_free(next,2) ); - next=final_map.beta(next,2,1); - CGAL_assertion(next != dart); - } - CGAL_precondition(&final_map.template attribute<0>(final_map.beta(dart,1))->point() == - &final_map.template attribute<0>(next)->point()); - return next; -} - -//turn around the source vertex of dart to find a marked dart -//with expected_source as source vertex -template -typename Combinatorial_map_3::Dart_handle -get_next_marked_dart_around_source_vertex( - Combinatorial_map_3& final_map, - typename Combinatorial_map_3::Dart_handle dart, - std::size_t mark_index) -{ - CGAL_precondition(final_map.is_marked(dart,mark_index)); - typename Combinatorial_map_3::Dart_handle next=final_map.beta(dart,0); - while ( ! final_map.is_marked(next,mark_index) ) { - CGAL_assertion( !final_map.is_free(next,2) ); - next=final_map.beta(next,2,0); - CGAL_assertion(next != dart); - } - CGAL_precondition(&final_map.template attribute<0>(dart)->point() == - &final_map.template attribute<0>(final_map.beta(next,1))->point()); - return next; -} - -//given two marked darts, this function links these two darts with beta<2> -//but in addition it follows the marked darts connected to the same vertex -//(there should be only one) to connect them all together -//( this function is a kind of zipper ;) ) -template -void sew_2_marked_darts( Combinatorial_map_3& final_map, - typename Combinatorial_map_3::Dart_handle dart_1, - typename Combinatorial_map_3::Dart_handle dart_2, - std::size_t mark_index, - const Nodes_vector& nodes, - const std::pair& indices, - const std::pair& polyline_info) -{ - CGAL_precondition( final_map.is_free(dart_1, 2) ); - CGAL_precondition( final_map.is_free(dart_2, 2) ); - CGAL_precondition( final_map.is_marked(dart_1,mark_index) ); - CGAL_precondition( final_map.is_marked(dart_2,mark_index) ); - CGAL_precondition( final_map.template attribute<0>(dart_1)->point() == - final_map.template attribute<0>(final_map.beta(dart_2,1))->point() ); - CGAL_precondition( final_map.template attribute<0>(final_map.beta(dart_1,1))->point() == - final_map.template attribute<0>(dart_2)->point() ); - - int src_index = ( ( indices.first < indices.second) == polyline_info.first ) - ? indices.second:indices.first; - - if ( final_map.template attribute<0>(dart_1)->point() != nodes[ src_index ] ) - std::swap(dart_1,dart_2); - - int nb_segs=polyline_info.second+1,k=1; - - do { - CGAL_precondition( final_map.template is_sewable<2>(dart_1,dart_2) ); - final_map.template sew<2>(dart_1,dart_2); - - if (k==nb_segs) break; - - dart_1 = get_next_marked_dart_around_target_vertex( - final_map, dart_1, mark_index); - dart_2 = get_next_marked_dart_around_source_vertex( - final_map, dart_2, mark_index); - } while(++k); -} - -//not_top and not_down are two darts from volumes that get merged with an -//existing other one because of a set of identical coplanar triangles. -//top and down is the dart of the volumes "replacing" that of not_top and -//not down respectively, The function is considering all triangles that -//are bounded by a cycle of marked edges. The volume not_top and not_down -//are part of are those that will disappear at the end of the main algorithm. -template -void sew_3_marked_darts( Combinatorial_map_3& final_map, - typename Combinatorial_map_3::Dart_handle not_top, - typename Combinatorial_map_3::Dart_handle not_down, - typename Combinatorial_map_3::Dart_handle top, - typename Combinatorial_map_3::Dart_handle down, - std::size_t mark_index, - std::set& - darts_to_remove -){ - typedef boost::optional - O_Dart_handle; - - if ( final_map.template attribute<3>(not_top)->info().is_empty ) { - CGAL_assertion(final_map.template attribute<3>(not_down)->info().is_empty); - return; - } - - CGAL_assertion(!final_map.template attribute<3>(not_down)->info().is_empty); - - //merge attribute of the two volumes: - internal_IOP::Volume_on_merge merge_attributes; - merge_attributes(*final_map.template attribute<3>(top), - *final_map.template attribute<3>(not_top)); - merge_attributes(*final_map.template attribute<3>(down), - *final_map.template attribute<3>(not_down)); - - //set volume attributes as empty to avoid double sew_3 - //of the same topological disk of triangles - final_map.template attribute<3>(not_top)->info().is_empty=true; - final_map.template attribute<3>(not_down)->info().is_empty=true; - - CGAL_precondition( final_map.is_marked(not_top,mark_index) - && final_map.is_marked(top,mark_index) ); - CGAL_precondition( final_map.is_marked(not_down,mark_index) - && final_map.is_marked(down,mark_index) ); - CGAL_precondition( final_map.template attribute<0>(not_top)->point() == - final_map.template attribute<0>(final_map.beta(not_down,1))->point() ); - CGAL_precondition( final_map.template attribute<0>(final_map.beta(not_top,1))->point() == - final_map.template attribute<0>(not_down)->point() ); - CGAL_precondition( final_map.template attribute<0>(not_top)->point() == - final_map.template attribute<0>(top)->point() ); - CGAL_precondition( final_map.template attribute<0>(not_down)->point() == - final_map.template attribute<0>(down)->point() ); - - CGAL_assertion( final_map.beta(top,3)==down ); - - //set to be removed the darts of the two no longer used volumes - typename Combinatorial_map_3::Dart_handle start=not_top; - do { - CGAL_assertion(!final_map.is_free(not_top, 3)); - darts_to_remove.insert(not_top); - darts_to_remove.insert(final_map.beta(not_top,1)); - darts_to_remove.insert(final_map.beta(not_top,1,1)); - darts_to_remove.insert(final_map.beta(not_top,3)); - darts_to_remove.insert(final_map.beta(not_top,3,1)); - darts_to_remove.insert(final_map.beta(not_top,3,1,1)); - O_Dart_handle current_1 = - next_marked_dart_around_target_vertex(final_map, not_top, mark_index); - CGAL_precondition(bool(current_1)); - not_top=*current_1; - } while(not_top!=start); -} - -} //end of internal_IOP namespace - -namespace Corefinement -{ - -//import into the combinatorial map facets in the given range. -//they are supposed to be in the same connected component. -//two volume are created (each facets gives two opposite orientation 2-cell in the map) -template -typename Map::Dart_handle import_from_polyhedron_subset( - Map& amap, - Face_iterator faces_begin, - Face_iterator faces_end, - const Non_special_edge_predicate& is_non_special_edge, - Halfedge_to_dart_map_& selected_hedge_to_dart, - std::size_t mark_index, - PolyhedronPointPMap ppmap - ) -{ - typedef typename Polyhedron::Halfedge_const_handle Halfedge_const_handle; - typedef std::map< Halfedge_const_handle, - typename Map::Dart_handle, - internal_IOP::Compare_address > - Halfedge_to_dart_map; - - Halfedge_to_dart_map hedge_to_dart; - typename Map::Dart_handle first_dart = NULL; - // First traversal to build the darts and link them. - for (Face_iterator it_face = faces_begin; it_face != faces_end; ++it_face) { - Halfedge_const_handle start=(*it_face)->halfedge(); - - CGAL_precondition(start->next()!=start); - - Halfedge_const_handle current=start; - typename Map::Dart_handle prev = NULL; - typename Map::Dart_handle first_dart_of_face = NULL; - do { - typename Map::Dart_handle d = amap.create_dart(); - amap.template link_beta<3>(d,amap.create_dart()); //for opposite volume - hedge_to_dart[current] = d; - - if (prev != NULL) { - amap.template link_beta<1>(prev, d); - amap.template link_beta<1>(amap.beta(d,3),amap.beta(prev,3));//for opposite volume - } else { - first_dart_of_face = d; - if (first_dart==NULL) first_dart=d; - } - - if ( is_non_special_edge (current) ) { - if ( !current->is_border_edge() ) { - CGAL_assertion(current != current->opposite()); - typename Halfedge_to_dart_map::iterator it = - hedge_to_dart.find(current->opposite()); - if (it != hedge_to_dart.end()) { - //link the opposites halfedges only when both - //corresponding darts have been created - amap.template link_beta<2>(d, it->second); - amap.template link_beta<2>(amap.beta(d,3), - amap.beta(it->second,3));//for opposite volume - } - } - } else { - typename Halfedge_to_dart_map_::iterator it_hedge_map= - selected_hedge_to_dart.find(current); - //all marked hedges are not the selected one for its polyline - if ( it_hedge_map!=selected_hedge_to_dart.end() ) it_hedge_map->second=d; - //darts d and d->beta(3) are special edges - amap.mark(d,mark_index); - amap.mark(amap.beta(d,3),mark_index); - } - prev = d; - current=current->next(); - } while (current != start); - amap.template link_beta<1>(prev, first_dart_of_face); - amap.template link_beta<1>(amap.beta(first_dart_of_face,3), - amap.beta(prev,3));//for opposite volume - } - - // Second traversal to update the geometry. - // We run one again through the facets of the HDS. - for (Face_iterator it_face = faces_begin; it_face != faces_end; ++it_face) { - Halfedge_const_handle start=(*it_face)->halfedge(); - Halfedge_const_handle current=start; - do { - typename Map::Dart_handle d = - hedge_to_dart[current]; // Get the dart associated to the Halfedge - if (amap.template attribute<0>(d) == NULL) { - amap.template set_attribute<0>(d, - amap.template create_attribute<0>( - get(ppmap, current->opposite()->vertex())) - ); - } - current=current->next(); - } while (current != start); - } - - return first_dart; -} - -template -class Combinatorial_map_output_builder -{ -//Default typedefs - typedef typename Default::Get< - PolyhedronPointPMap_, - Default_polyhedron_ppmap >::type PolyhedronPointPMap; - typedef typename Default::Get< - Kernel_, - typename Kernel_traits< - typename boost::property_traits::value_type >::Kernel - >::type Kernel; -//Polyhedron typedefs - typedef typename Polyhedron::Halfedge_handle Halfedge_handle; - typedef typename Polyhedron::Halfedge_const_handle Halfedge_const_handle; - typedef typename Polyhedron::Vertex_handle Vertex_handle; -//Other typedefs - typedef internal_IOP::Compare_unik_address Cmp_unik_ad; - typedef std::map< std::pair, - std::pair< std::map, - std::pair > > An_edge_per_polyline_map; - typedef std::map Node_to_polyhedron_vertex_map; - typedef std::map Poly_to_map_node; -public: -//Combinatorial map typedefs - typedef internal_IOP::Item_with_points_and_volume_info Items; - typedef CGAL::Combinatorial_map<3,Items> Combinatorial_map_3; - typedef typename Combinatorial_map_3::Dart_handle Dart_handle; - typedef internal_IOP::Volume_info Volume_info; -private: -//Data members - PolyhedronPointPMap ppmap; - boost::shared_ptr final_map_ptr; -private: - template - inline Dart_handle get_associated_dart(Halfedge_handle hedge, - Halfedge_to_dart_map& selected_hedge_to_dart) { - typename Halfedge_to_dart_map::iterator it_saved_dart= - selected_hedge_to_dart.find(hedge); - CGAL_assertion(it_saved_dart!=selected_hedge_to_dart.end()); - return it_saved_dart->second; - } - - //first_hedge defines four volumes, second_hedge only two - //first_poly is not needed as inside/outside volume is update during the merge - //of the sew. Only second_poly is needed - template - void sew_2_three_volumes_case( Halfedge_handle first_hedge, - Halfedge_handle second_hedge, - const std::pair& indices, - const Nodes_vector& nodes, - Border_halfedges_map& border_halfedges, - Halfedge_to_dart_map& selected_hedge_to_dart, - Polyhedron* /*first_poly*/, - Polyhedron* second_poly, - std::size_t mark_index, - std::set& darts_to_remove, - const std::pair& polyline_info) { - bool took_opposite=second_hedge->is_border(); - if (took_opposite) second_hedge=second_hedge->opposite(); - - Vertex_handle P1=first_hedge->opposite()->next()->vertex(); - Vertex_handle P2=first_hedge->next()->vertex(); - // when looking from the side of indices.second, the interior of the first polyhedron is described - // by turning counterclockwise from P1 to P2 - - Vertex_handle Q = second_hedge->next()->vertex(); - - //check if the third point of each triangular face is an original point (stay -1) - //or a intersection point (in that case we need the index of the corresponding node to - //have the exact value of the point) - int index_p1=node_index_of_incident_vertex(first_hedge->opposite()->next(), - border_halfedges); - int index_p2=node_index_of_incident_vertex(first_hedge->next(), - border_halfedges); - int index_q =node_index_of_incident_vertex(second_hedge->next(), - border_halfedges); - - //Recover the dart that will be the start point of the different sewing - // dof_X_outside = dart of face of, meaning the triangle containing the - // point X and part of the volume outside of the corresponding polyhedron - //-----first polyhedron - Dart_handle dof_P1_outside = get_associated_dart(first_hedge->opposite(), - selected_hedge_to_dart); - Dart_handle dof_P2_outside = get_associated_dart(first_hedge, - selected_hedge_to_dart); - //-----second polyhedron - Dart_handle dof_Q_outside = get_associated_dart(second_hedge, - selected_hedge_to_dart); - - if (index_p1!=-1 && index_p1==index_q) { - Dart_handle top=final_map().beta(dof_P1_outside,3), - not_top=took_opposite?final_map().beta(dof_Q_outside,3):dof_Q_outside; - Dart_handle down=dof_P1_outside, - not_down=took_opposite?dof_Q_outside:final_map().beta(dof_Q_outside,3); - - if ( final_map().template attribute<3>(top)->info().is_empty ) - std::swap(not_top,top); - if ( final_map().template attribute<3>(down)->info().is_empty ) - std::swap(not_down,down); - CGAL_assertion( !final_map().template attribute<3>(top)->info().is_empty ); - CGAL_assertion( !final_map().template attribute<3>(down)->info().is_empty ); - - //P1P2 or QP2 - sew_2_marked_darts( final_map(), top, final_map().beta(dof_P2_outside,3), - mark_index, nodes, indices, polyline_info); - //P2Q or P2P1 - sew_2_marked_darts( final_map(),dof_P2_outside, down, - mark_index, nodes, indices, polyline_info); - sew_3_marked_darts( final_map(),not_top, not_down, top, down, mark_index, - darts_to_remove); - - return; - } - - if (index_p2!=-1 && index_p2==index_q) { - Dart_handle top=final_map().beta(dof_P2_outside,3), - not_top=took_opposite?dof_Q_outside:final_map().beta(dof_Q_outside,3); - Dart_handle down=dof_P2_outside, - not_down=took_opposite?final_map().beta(dof_Q_outside,3):dof_Q_outside; - - if ( final_map().template attribute<3>(top)->info().is_empty ) - std::swap(not_top,top); - if ( final_map().template attribute<3>(down)->info().is_empty ) - std::swap(not_down,down); - CGAL_assertion( !final_map().template attribute<3>(top)->info().is_empty ); - CGAL_assertion( !final_map().template attribute<3>(down)->info().is_empty ); - - //P1Q or P1P2 - sew_2_marked_darts( final_map(), final_map().beta(dof_P1_outside,3), top, - mark_index, nodes, indices, polyline_info); - //QP1 or P2P1 - sew_2_marked_darts( final_map(), down, dof_P1_outside, - mark_index, nodes, indices, polyline_info); - sew_3_marked_darts( final_map(), not_top, not_down, top, down, mark_index, - darts_to_remove); - - return; - } - - bool Q_is_between_P1P2 = OOP::sorted_around_edge_filtered(indices.first, - indices.second, - index_p1,index_p2, - index_q,P1,P2,Q, - nodes,ppmap); - - if (Q_is_between_P1P2) { - // poly_first - poly_second = took_opposite?P1Q:QP2 - // poly_second - poly_first = {0} - // poly_first \cap poly_second = took_opposite?QP2:P1Q - // opposite( poly_first U poly_second ) = P2P1 - sew_2_marked_darts( final_map(), - final_map().beta(dof_P1_outside,3), - took_opposite?dof_Q_outside:final_map().beta(dof_Q_outside,3), - mark_index, nodes, indices, polyline_info); //P1Q - sew_2_marked_darts( final_map(), - took_opposite?final_map().beta(dof_Q_outside,3):dof_Q_outside, - final_map().beta(dof_P2_outside,3),mark_index, nodes, indices, - polyline_info); //QP2 - sew_2_marked_darts( final_map(), - dof_P2_outside, dof_P1_outside, mark_index, - nodes, indices, polyline_info); //P2P1 - final_map().template attribute<3>(dof_P1_outside)->info().outside - .insert(second_poly); //update P2P1 outside poly - } else { - // poly_first - poly_second = P1P2 - // poly_second - poly_first = took_opposite?QP1:P2Q - // poly_first \cap poly_second = {0} - // opposite( poly_first U poly_second ) = took_opposite?P2Q:QP1 - sew_2_marked_darts( final_map(), - dof_P2_outside, - took_opposite?dof_Q_outside:final_map().beta(dof_Q_outside,3), - mark_index, nodes, indices, polyline_info); //P2Q - sew_2_marked_darts( final_map(), - took_opposite?final_map().beta(dof_Q_outside,3):dof_Q_outside, - dof_P1_outside,mark_index, nodes, - indices, polyline_info); //QP1 - sew_2_marked_darts( final_map(), - final_map().beta(dof_P1_outside,3), final_map().beta(dof_P2_outside,3), - mark_index, nodes, indices, polyline_info); //P1P2 - final_map().template attribute<3>(final_map().beta(dof_P1_outside,3))->info().outside - .insert(second_poly); //update P1P2 outside poly - } - } - -//first_hedge defines two volumes, second_hedge only two - template - void sew_2_two_volumes_case( Halfedge_handle first_hedge, - Halfedge_handle second_hedge, - Border_halfedges_map& border_halfedges, - Halfedge_to_dart_map& selected_hedge_to_dart, - std::size_t mark_index, - std::set& darts_to_remove, - const Nodes_vector& nodes, - const std::pair& indices, - const std::pair& polyline_info) - { - bool first_took_opposite=first_hedge->is_border(); - if (first_took_opposite) first_hedge=first_hedge->opposite(); - bool second_took_opposite=second_hedge->is_border(); - if (second_took_opposite) second_hedge=second_hedge->opposite(); - - //-----first polyhedron - Dart_handle dof_P_outside = get_associated_dart(first_hedge, - selected_hedge_to_dart); - //-----second polyhedron - Dart_handle dof_Q_outside = get_associated_dart(second_hedge, - selected_hedge_to_dart); - - - - - int index_p =node_index_of_incident_vertex(first_hedge->next(), - border_halfedges); - int index_q =node_index_of_incident_vertex(second_hedge->next(), - border_halfedges); - - if (index_p!=-1 && index_q!=-1 && index_p==index_q) { - Dart_handle top=dof_P_outside, not_top=final_map().beta(dof_Q_outside,3); - Dart_handle down=final_map().beta(dof_P_outside,3), not_down=dof_Q_outside; - - if (first_took_opposite==second_took_opposite) { - top=final_map().beta(dof_P_outside,3); - not_top=final_map().beta(dof_Q_outside,3); - down=dof_P_outside; - not_down=dof_Q_outside; - } - - if ( final_map().template attribute<3>(top)->info().is_empty ) - std::swap(not_top,top); - if ( final_map().template attribute<3>(down)->info().is_empty ) - std::swap(not_down,down); - CGAL_assertion( !final_map().template attribute<3>(top)->info().is_empty ); - CGAL_assertion( !final_map().template attribute<3>(down)->info().is_empty ); - - sew_3_marked_darts( final_map(),not_top,not_down,top,down,mark_index, - darts_to_remove); - - return; - } - - - - //since the edge is shared, the inside of each polyhedron must be on opposite orientation halfedges - if (first_took_opposite==second_took_opposite) { - //sew out with in - sew_2_marked_darts( final_map(),final_map().beta(dof_P_outside,3), dof_Q_outside, - mark_index, nodes, indices, polyline_info); //PQ - sew_2_marked_darts( final_map(),final_map().beta(dof_Q_outside,3), dof_P_outside, - mark_index, nodes, indices, polyline_info); //QP - } else { - //sew in with in - sew_2_marked_darts( final_map(), dof_P_outside, dof_Q_outside, - mark_index, nodes, indices, polyline_info); //PQ - sew_2_marked_darts( final_map(),final_map().beta(dof_Q_outside,3), - final_map().beta(dof_P_outside,3), mark_index, nodes, indices, - polyline_info); //QP - } - } - -//4 volume case with 2 identical volume -//Q2 is supposed to be identical to P2 - template - void sew_2_four_volumes_case_1( Halfedge_handle first_hedge, - Halfedge_handle second_hedge, - const std::pair& indices, - const Nodes_vector& nodes, - int index_p1, int index_p2, int index_q1, - Halfedge_to_dart_map& selected_hedge_to_dart, - std::size_t mark_index, - std::set& darts_to_remove, - const std::pair& polyline_info, - bool swap_in_out_Q=false) { - Vertex_handle P1=first_hedge->opposite()->next()->vertex(); - Vertex_handle P2=first_hedge->next()->vertex(); - // when looking from the side of indices.second, - // the interior of the first polyhedron is described - // by turning counterclockwise from P1 to P2 - Vertex_handle Q1=second_hedge->opposite()->next()->vertex(); - // Vertex_handle Q2=second_hedge->next()->vertex(); - bool Q1_is_between_P1P2 = - OOP::sorted_around_edge_filtered( indices.first, indices.second, index_p1, - index_p2, index_q1, P1, P2, Q1, nodes, ppmap); - - - //Recover the dart that will be the start point of the different sewing - // dof_X_outside = dart of face of, meaning the triangle containing the - // point X and part of the volume outside of the corresponding polyhedron - //-----first polyhedron - Dart_handle dof_P1_outside = get_associated_dart(first_hedge->opposite(), - selected_hedge_to_dart); - Dart_handle dof_P2_outside = get_associated_dart(first_hedge, - selected_hedge_to_dart); - //-----second polyhedron - Dart_handle dof_Q1_outside = get_associated_dart(second_hedge->opposite(), - selected_hedge_to_dart); - Dart_handle dof_Q2_outside = get_associated_dart(second_hedge, - selected_hedge_to_dart); - - if( swap_in_out_Q ) { - dof_Q1_outside=final_map().beta(dof_Q1_outside,3); - dof_Q2_outside=final_map().beta(dof_Q2_outside,3); - } - - if (Q1_is_between_P1P2) { - Dart_handle top=final_map().beta(dof_Q2_outside,3), not_top=final_map().beta(dof_P2_outside,3); - Dart_handle down=dof_Q2_outside, not_down=dof_P2_outside; - if ( final_map().template attribute<3>(top)->info().is_empty ) - std::swap(not_top,top); - if ( final_map().template attribute<3>(down)->info().is_empty ) - std::swap(not_down,down); - CGAL_assertion( !final_map().template attribute<3>(top)->info().is_empty ); - CGAL_assertion( !final_map().template attribute<3>(down)->info().is_empty ); - - // poly_first - poly_second = P1Q1 - // poly_second - poly_first = {0} - // poly_first \cap poly_second = Q1P2 or Q1Q2 - // opposite( poly_first U poly_second ) = Q2P1 or P2P1 - sew_2_marked_darts( final_map(), final_map().beta(dof_P1_outside,3), dof_Q1_outside, - mark_index, nodes, indices, polyline_info); //P1Q1 - sew_2_marked_darts( final_map(),final_map().beta(dof_Q1_outside,3), - top, mark_index, nodes, indices, - polyline_info); //Q1P2 or Q1Q2 - sew_2_marked_darts( final_map(),down, - dof_P1_outside, mark_index, nodes, indices, - polyline_info); //Q2P1 or P2P1 - sew_3_marked_darts( final_map(),not_top,not_down,top,down,mark_index, - darts_to_remove); - - } else { - Dart_handle top=final_map().beta(dof_Q2_outside,3), not_top=final_map().beta(dof_P2_outside,3); - Dart_handle down=dof_Q2_outside, not_down=dof_P2_outside; - if ( final_map().template attribute<3>(top)->info().is_empty ) - std::swap(not_top,top); - if ( final_map().template attribute<3>(down)->info().is_empty ) - std::swap(not_down,down); - CGAL_assertion( !final_map().template attribute<3>(top)->info().is_empty ); - CGAL_assertion( !final_map().template attribute<3>(down)->info().is_empty ); - - // poly_first - poly_second = {0} - // poly_second - poly_first = Q1P1 - // poly_first \cap poly_second = P1P2 or P1Q2 - // opposite( poly_first U poly_second ) = Q2Q1 or P2Q1 - sew_2_marked_darts( final_map(),final_map().beta(dof_Q1_outside,3), dof_P1_outside, - mark_index, nodes, indices, polyline_info); //Q1P1 - sew_2_marked_darts( final_map(),final_map().beta(dof_P1_outside,3), - top, mark_index, nodes, indices, - polyline_info); //P1P2 or P1Q2 - sew_2_marked_darts( final_map(),down, - dof_Q1_outside, mark_index, nodes, indices, - polyline_info); //Q2Q1 or P2Q1 - sew_3_marked_darts( final_map(),not_top,not_down,top,down,mark_index, - darts_to_remove); - } - } - - template - bool coplanar_triangles_case_handled( - Halfedge_handle first_hedge, Halfedge_handle second_hedge, - const std::pair& indices, - const Nodes_vector& nodes, - int index_p1, int index_p2, - int index_q1,int index_q2, - Halfedge_to_dart_map& selected_hedge_to_dart, - std::size_t mark_index, - std::set& darts_to_remove, - const std::pair& polyline_info - ){ - if( index_p1!=-1 ) { - if (index_p1==index_q1) { - if(index_p2!=-1) { - CGAL_assertion(index_p2!=index_q1); - if(index_p2==index_q2) { - //-----first polyhedron - Dart_handle dof_P1_outside = get_associated_dart(first_hedge->opposite(), - selected_hedge_to_dart); - Dart_handle dof_P2_outside = get_associated_dart(first_hedge, - selected_hedge_to_dart); - //-----second polyhedron - Dart_handle dof_Q1_outside = get_associated_dart(second_hedge->opposite(), - selected_hedge_to_dart); - Dart_handle dof_Q2_outside = get_associated_dart(second_hedge, - selected_hedge_to_dart); - - Dart_handle top_1=final_map().beta(dof_P1_outside,3), - not_top_1=final_map().beta(dof_Q1_outside,3); - Dart_handle top_2=final_map().beta(dof_P2_outside,3), - not_top_2=final_map().beta(dof_Q2_outside,3); - Dart_handle down_1=dof_P1_outside, not_down_1=dof_Q1_outside; - Dart_handle down_2=dof_P2_outside, not_down_2=dof_Q2_outside; - if ( final_map().template attribute<3>(top_1)->info().is_empty ) - std::swap(top_1,not_top_1); - if ( final_map().template attribute<3>(top_2)->info().is_empty ) - std::swap(top_2,not_top_2); - if ( final_map().template attribute<3>(down_1)->info().is_empty ) - std::swap(down_1,not_down_1); - if ( final_map().template attribute<3>(down_2)->info().is_empty ) - std::swap(down_2,not_down_2); - CGAL_assertion( !final_map().template attribute<3>(top_1)->info().is_empty ); - CGAL_assertion( !final_map().template attribute<3>(top_2)->info().is_empty ); - CGAL_assertion( !final_map().template attribute<3>(down_1)->info().is_empty ); - CGAL_assertion( !final_map().template attribute<3>(down_2)->info().is_empty ); - - // poly_first - poly_second = {0} - // poly_second - poly_first = {0} - // poly_first \cap poly_second = P1P2 or Q1Q2 or P1Q1 or Q1P2 - // opposite( poly_first U poly_second ) = P2P1 or Q2Q1 or P2Q1 or Q2P1 - sew_2_marked_darts( final_map(),top_1, top_2,mark_index, nodes, - indices, polyline_info); //P1P2 or Q1Q2 or P1Q1 or Q1P2 - sew_2_marked_darts( final_map(),down_2, down_1, mark_index, nodes, - indices, polyline_info); //P2P1 or Q2Q1 or P2Q1 or Q2P1 - sew_3_marked_darts( final_map(),not_top_1, not_down_1, top_1, - down_1,mark_index, darts_to_remove); - sew_3_marked_darts( final_map(),not_top_2, not_down_2, top_2, - down_2,mark_index, darts_to_remove); - return true; - } - } - sew_2_four_volumes_case_1(first_hedge->opposite(),second_hedge->opposite(), - std::make_pair(indices.second,indices.first), - nodes,index_p2,index_p1,index_q2, - selected_hedge_to_dart,mark_index, - darts_to_remove,polyline_info); - return true; - } - if (index_p1==index_q2) { - if(index_p2!=-1) { - CGAL_assertion(index_p2!=index_q2); - if(index_p2==index_q1) { - //-----first polyhedron - Dart_handle dof_P1_outside = get_associated_dart(first_hedge->opposite(), - selected_hedge_to_dart); - Dart_handle dof_P2_outside = get_associated_dart(first_hedge, - selected_hedge_to_dart); - //-----second polyhedron - Dart_handle dof_Q1_outside = get_associated_dart(second_hedge->opposite(), - selected_hedge_to_dart); - Dart_handle dof_Q2_outside = get_associated_dart(second_hedge, - selected_hedge_to_dart); - - Dart_handle top_1=final_map().beta(dof_P1_outside,3), not_top_1=dof_Q2_outside; - Dart_handle top_2=final_map().beta(dof_P2_outside,3), not_top_2=dof_Q1_outside; - Dart_handle down_1=dof_P1_outside, not_down_1=final_map().beta(dof_Q2_outside,3); - Dart_handle down_2=dof_P2_outside, not_down_2=final_map().beta(dof_Q1_outside,3); - if ( final_map().template attribute<3>(top_1)->info().is_empty ) - std::swap(top_1,not_top_1); - if ( final_map().template attribute<3>(top_2)->info().is_empty ) - std::swap(top_2,not_top_2); - if ( final_map().template attribute<3>(down_1)->info().is_empty ) - std::swap(down_1,not_down_1); - if ( final_map().template attribute<3>(down_2)->info().is_empty ) - std::swap(down_2,not_down_2); - CGAL_assertion( !final_map().template attribute<3>(top_1)->info().is_empty ); - CGAL_assertion( !final_map().template attribute<3>(top_2)->info().is_empty ); - CGAL_assertion( !final_map().template attribute<3>(down_1)->info().is_empty ); - CGAL_assertion( !final_map().template attribute<3>(down_2)->info().is_empty ); - - // poly_first - poly_second = P1P2 or P1Q1 or Q2P2 or Q2Q1 - // poly_second - poly_first = Q1Q2 or Q1P1 or P2P1 or P2Q2 - // poly_first \cap poly_second = {0} - // opposite( poly_first U poly_second ) = all space - sew_2_marked_darts( final_map(),top_1, top_2, mark_index, nodes, - indices, polyline_info); //P1P2 or Q1Q2 or P1Q1 or Q1P2 - sew_2_marked_darts( final_map(),down_2, down_1, mark_index, nodes, - indices, polyline_info); //P2P1 or Q2Q1 or P2Q1 or Q2P1 - sew_3_marked_darts( final_map(),not_top_1, not_down_1, top_1,down_1,mark_index, - darts_to_remove); - sew_3_marked_darts( final_map(),not_top_2, not_down_2, top_2,down_2,mark_index, - darts_to_remove); - return true; - } - } - sew_2_four_volumes_case_1(first_hedge->opposite(), second_hedge, - std::make_pair(indices.second,indices.first), - nodes,index_p2, index_p1, index_q1, - selected_hedge_to_dart, mark_index, - darts_to_remove, polyline_info, true); - return true; - } - } - - if(index_p2!=-1) { - if (index_p2==index_q1) { - sew_2_four_volumes_case_1(first_hedge,second_hedge->opposite(), - indices,nodes, index_p1,index_p2,index_q2, - selected_hedge_to_dart,mark_index, - darts_to_remove, polyline_info,true); - return true; - } - if(index_p2==index_q2) { - sew_2_four_volumes_case_1(first_hedge, second_hedge, indices, nodes, - index_p1, index_p2, index_q1, - selected_hedge_to_dart, mark_index, - darts_to_remove, polyline_info); - return true; - } - } - - - return false; - } - -public: - - Combinatorial_map_3& final_map() { - return *final_map_ptr; - } - Combinatorial_map_3& combinatorial_map() { - return *final_map_ptr; - } - boost::shared_ptr combinatorial_map_shared_ptr() { - return final_map_ptr; - } - - Combinatorial_map_output_builder(boost::shared_ptr map_ptr) - : final_map_ptr(map_ptr) {} - - Combinatorial_map_output_builder( - PolyhedronPointPMap point_pmap = PolyhedronPointPMap() - ) : ppmap(point_pmap) - , final_map_ptr(new Combinatorial_map_3()) - {} - - void input_have_coplanar_facets(){} - - template - void operator()( - const std::map,Cmp_unik_ad >& border_halfedges, - const Nodes_vector& nodes, - const An_edge_per_polyline_map& an_edge_per_polyline, - const Bitset& /*is_node_of_degree_one*/, - const Poly_to_map_node& polyhedron_to_map_node_to_polyhedron_vertex) { - //4) create one output polyhedron per connected component of polyhedron, - // connected by an edge which is not an intersection edge - //5) import into a Combinatorial map -#ifdef CGAL_COREFINEMENT_DEBUG - std::cout << "Nb marked edges " << border_halfedges.size() << std::endl; -// for (typename Border_halfedges_map::iterator it=border_halfedges.begin();it!=border_halfedges.end();++it) -// std::cout << it->first->get(ppmap,opposite()->vertex()) << " " << get(ppmap,it->first->vertex()) << " is constrained " << std::endl; - std::cout << "Nb polylines " << an_edge_per_polyline.size() << std::endl; -#endif - - internal_IOP::Non_intersection_halfedge criterium(border_halfedges); - - std::size_t mark_index= - final_map().get_new_mark(); //mark used to tag dart that are on an intersection - - //define a map that will contain the correspondance between selected halfedges of the boundary and - //their corresponding Dart_handle in the cmap. - typedef std::map< Halfedge_const_handle, - Dart_handle, - internal_IOP::Compare_address > - Halfedge_to_dart_map; - Halfedge_to_dart_map selected_hedge_to_dart; - for (typename An_edge_per_polyline_map::const_iterator it= - an_edge_per_polyline.begin(); it!=an_edge_per_polyline.end(); ++it) { - CGAL_assertion(it->second.first.size()==2); - //orientation of faces around the edge (to be sure we can do it) - Halfedge_handle first_hedge=it->second.first.begin()->second; - Halfedge_handle second_hedge=boost::next(it->second.first.begin())->second; - - if (!first_hedge->is_border()) - selected_hedge_to_dart - .insert(std::make_pair(first_hedge,Dart_handle(NULL))); - if (!first_hedge->opposite()->is_border()) - selected_hedge_to_dart - .insert(std::make_pair(first_hedge->opposite(),Dart_handle(NULL))); - if (!second_hedge->is_border()) - selected_hedge_to_dart - .insert(std::make_pair(second_hedge,Dart_handle(NULL))); - if (!second_hedge->opposite()->is_border()) - selected_hedge_to_dart - .insert(std::make_pair(second_hedge->opposite(),Dart_handle(NULL))); - } - -#ifdef CGAL_COREFINEMENT_DEBUG - int polynb=0; -#endif - for (typename Poly_to_map_node::const_iterator - it=polyhedron_to_map_node_to_polyhedron_vertex.begin(); - it!=polyhedron_to_map_node_to_polyhedron_vertex.end(); - ++it - ) { - typedef typename Polyhedron::Facet_const_handle Facet_const_handle; - typedef ::CGAL::Union_find UF; - typedef typename UF::handle UF_handle; - typedef std::map< Facet_const_handle, - std::list, - internal::corefinement::Compare_handle_ptr > Result; - typedef std::map< Facet_const_handle, - UF_handle, - internal::corefinement::Compare_handle_ptr > - Facet_to_handle_map; - - UF uf; - Facet_to_handle_map map_f2h; - Result result; - Polyhedron* current_poly=it->first; - -#ifdef CGAL_COREFINEMENT_DEBUG - std::cout << "writing poly debug"<< std::endl; - std::stringstream ss; - ss << "output_debug-" << ++polynb << ".off"; - std::ofstream output_debug(ss.str().c_str()); - output_debug << *current_poly; -#endif - - internal::corefinement::extract_connected_components(*(static_cast (current_poly) ), - criterium,uf,map_f2h,result); - - //add each connected component in the map with 2 volumes per component. - for (typename Result::iterator it_res=result.begin(); it_res!=result.end(); - ++it_res) { - //create in the final Cmap a 2D component containing faces of a connected component - //(twice: one with same orientation and one with the opposite orientation to model the other volume) - Dart_handle d = - import_from_polyhedron_subset( - final_map(), it_res->second.begin(), it_res->second.end(), - criterium, selected_hedge_to_dart, mark_index,ppmap); - //set an attribute to one volume represented by this component - //to indicate a part outside of the polyhedron current_poly - typename Combinatorial_map_3::template Attribute_range<3>::type::iterator - attrib=final_map().template create_attribute<3>(); - attrib->info().outside.insert(current_poly); - final_map().template set_attribute<3>(d,attrib); - //set the attribute for the opposite volume: represent a part inside current_poly - attrib=final_map().template create_attribute<3>(); - attrib->info().inside.insert(current_poly); - final_map().template set_attribute<3>(final_map().beta(d, 3),attrib); - -#ifdef CGAL_COREFINEMENT_DEBUG - final_map().display_characteristics(std::cout); - std::cout << std::endl; -#endif - } - } -#ifndef NDEBUG - for(typename Halfedge_to_dart_map::iterator it=selected_hedge_to_dart.begin(); - it!=selected_hedge_to_dart.end(); ++it) - CGAL_assertion(it->second!=Dart_handle(NULL)); -#endif - - CGAL_assertion(final_map().is_valid()); - - std::set darts_to_remove; - - //6) Glue pieces together - // using one edge per intersection polyline, we merge the different volumes - for (typename An_edge_per_polyline_map::const_iterator it= - an_edge_per_polyline.begin(); it!=an_edge_per_polyline.end(); ++it) { - CGAL_assertion(it->second.first.size()==2); - //orientation of faces around the edge (to be sure we can do it) - std::pair indices = it->first; - const std::pair& polyline_info=it->second.second; - - //get the two halfedges incident to the edge [indices.first,indices.second] - Halfedge_handle first_hedge=it->second.first.begin()->second; - Halfedge_handle second_hedge=boost::next(it->second.first.begin())->second; - - CGAL_assertion(nodes[indices.second]==get(ppmap,first_hedge->vertex())); - CGAL_assertion(nodes[indices.first]==get(ppmap, - first_hedge->opposite()->vertex())); - CGAL_assertion(nodes[indices.second]==get(ppmap,second_hedge->vertex())); - CGAL_assertion(nodes[indices.first]==get(ppmap, - second_hedge->opposite()->vertex())); - - Polyhedron* first_poly = it->second.first.begin()->first; - Polyhedron* second_poly = boost::next(it->second.first.begin())->first; - - //different handling depending on the number of incident triangles to the edge. - //After sewing there are two,three or four volumes if there are two,three or four incident triangles respectively - if ( first_hedge->is_border() || first_hedge->opposite()->is_border() ) { - if (second_hedge->is_border() || second_hedge->opposite()->is_border()) - sew_2_two_volumes_case(first_hedge, second_hedge, border_halfedges, - selected_hedge_to_dart, mark_index, - darts_to_remove, nodes, indices, polyline_info); - else - sew_2_three_volumes_case(second_hedge, first_hedge,indices,nodes, - border_halfedges,selected_hedge_to_dart, - second_poly,first_poly,mark_index, - darts_to_remove,polyline_info); - } else - if (second_hedge->is_border() || second_hedge->opposite()->is_border()) - sew_2_three_volumes_case(first_hedge, second_hedge, indices, nodes, - border_halfedges, selected_hedge_to_dart, - first_poly,second_poly,mark_index, - darts_to_remove,polyline_info); - else { - //Sort the four triangle facets around their common edge - // we suppose that the exterior of the polyhedron is indicated by - // counterclockwise oriented facets. - Vertex_handle P1=first_hedge->opposite()->next()->vertex(); - Vertex_handle P2=first_hedge->next()->vertex(); - // when looking from the side of indices.second, the interior of the first polyhedron is described - // by turning counterclockwise from P1 to P2 - Vertex_handle Q1=second_hedge->opposite()->next()->vertex(); - Vertex_handle Q2=second_hedge->next()->vertex(); - // when looking from the side of indices.second, the interior of the second polyhedron is described - // by turning from Q1 to Q2 - - //check if the third point of each triangular face is an original point (stay -1) - //or a intersection point (in that case we need the index of the corresponding node to - //have the exact value of the point) - int index_p1 = node_index_of_incident_vertex( - first_hedge->opposite()->next(),border_halfedges); - int index_p2=node_index_of_incident_vertex( - first_hedge->next(), border_halfedges); - int index_q1=node_index_of_incident_vertex( - second_hedge->opposite()->next(), border_halfedges); - int index_q2=node_index_of_incident_vertex( - second_hedge->next(), border_halfedges); - -#ifdef CGAL_COREFINEMENT_DEBUG - std::cout << index_p1 << " " << index_p2 << " " << index_q1 << " " <opposite(), selected_hedge_to_dart); - Dart_handle dof_P2_outside = - get_associated_dart(first_hedge, selected_hedge_to_dart); - //-----second polyhedron - Dart_handle dof_Q1_outside = - get_associated_dart(second_hedge->opposite(),selected_hedge_to_dart); - Dart_handle dof_Q2_outside = - get_associated_dart(second_hedge, selected_hedge_to_dart); - - if ( Q1_is_between_P1P2 ) { - if( Q2_is_between_P1P2 ) { - bool P1_is_between_Q1Q2 = - OOP::sorted_around_edge_filtered(indices.first, indices.second, - index_q1, index_q2, index_p1, - Q1, Q2, P1, nodes, ppmap); - if (!P1_is_between_Q1Q2) { - // poly_first - poly_second = P1Q1 U Q2P2 - // poly_second - poly_first = {0} - // poly_first \cap poly_second = Q1Q2 - // opposite( poly_first U poly_second ) = P2P1 - sew_2_marked_darts( final_map(),final_map().beta(dof_P1_outside, 3), - dof_Q1_outside, mark_index, nodes, - indices, polyline_info); //P1Q1 - sew_2_marked_darts( final_map(),dof_Q2_outside, - final_map().beta(dof_P2_outside,3),mark_index, nodes, - indices, polyline_info); //Q2P2 - sew_2_marked_darts( final_map(),final_map().beta(dof_Q1_outside,3), - final_map().beta(dof_Q2_outside,3), mark_index, nodes, - indices, polyline_info); //Q1Q2 - sew_2_marked_darts( final_map(),dof_P2_outside, - dof_P1_outside, mark_index, - nodes, indices, polyline_info); //P2P1 - //update inside outside info (because darts from the same volume have been merged) - final_map().template attribute<3>(final_map().beta(dof_Q1_outside,3))->info().inside - .insert(first_poly); //update Q1Q2 inside poly - final_map().template attribute<3>(dof_P2_outside)->info().outside - .insert(second_poly);//update P2P1 outside poly - } else { - // poly_first - poly_second = Q2Q1 - // poly_second - poly_first = P2P1 - // poly_first \cap poly_second = P1Q2 U Q1P2 - // opposite( poly_first U poly_second ) = {O} - sew_2_marked_darts( final_map(),dof_Q2_outside, - dof_Q1_outside, mark_index, nodes, - indices, polyline_info); //Q2Q1 - sew_2_marked_darts( final_map(),dof_P2_outside, - dof_P1_outside, mark_index, nodes, indices, - polyline_info); //P2P1 - sew_2_marked_darts( final_map(),final_map().beta(dof_Q1_outside,3), - final_map().beta(dof_P2_outside,3), mark_index, nodes, - indices, polyline_info); //Q1P2 - sew_2_marked_darts( final_map(),final_map().beta(dof_P1_outside,3), - final_map().beta(dof_Q2_outside,3),mark_index, nodes, - indices, polyline_info); //P1Q2 - //update inside outside info (because darts from the same volume have been merged) - final_map().template attribute<3>(dof_Q2_outside)->info().inside.insert( - first_poly); //update Q2Q1 inside poly - final_map().template attribute<3>(dof_P2_outside)->info().inside.insert( - second_poly);//update P2P1 inside poly - } - } else { - // poly_first - poly_second = P1Q1 - // poly_second - poly_first = P2Q2 - // poly_first \cap poly_second = Q1P2 - // opposite( poly_first U poly_second ) = Q2P1 - sew_2_marked_darts( final_map(),final_map().beta(dof_P1_outside,3), - dof_Q1_outside, mark_index, nodes, - indices, polyline_info); //P1Q1 - sew_2_marked_darts( final_map(),dof_P2_outside, - final_map().beta(dof_Q2_outside,3), mark_index, nodes, - indices, polyline_info); //P2Q2 - sew_2_marked_darts( final_map(),final_map().beta(dof_Q1_outside,3), - final_map().beta(dof_P2_outside,3), mark_index, nodes, - indices, polyline_info); //Q1P2 - sew_2_marked_darts( final_map(),dof_Q2_outside, - dof_P1_outside, mark_index, nodes, - indices, polyline_info); //Q2P1 - } - } else { - if( Q2_is_between_P1P2 ) { - // poly_first - poly_second = Q2P2 - // poly_second - poly_first = Q1P1 - // poly_first \cap poly_second = P1Q2 - // opposite( poly_first U poly_second ) = P2Q1 - sew_2_marked_darts( final_map(),dof_Q2_outside, - final_map().beta(dof_P2_outside,3), mark_index, nodes, - indices, polyline_info); //Q2P2 - sew_2_marked_darts( final_map(),final_map().beta(dof_Q1_outside,3), - dof_P1_outside, mark_index, nodes, - indices, polyline_info); //Q1P1 - sew_2_marked_darts( final_map(),final_map().beta(dof_P1_outside,3), - final_map().beta(dof_Q2_outside,3),mark_index, nodes, - indices, polyline_info); //P1Q2 - sew_2_marked_darts( final_map(),dof_P2_outside, - dof_Q1_outside, mark_index, nodes, - indices, polyline_info); //P2Q1 - } else { - bool P1_is_between_Q1Q2 = - OOP::sorted_around_edge_filtered(indices.first, indices.second, - index_q1,index_q2,index_p1,Q1,Q2,P1, - nodes,ppmap); - if (!P1_is_between_Q1Q2) { - // poly_first - poly_second = P1P2 - // poly_second - poly_first = Q1Q2 - // poly_first \cap poly_second = {0} - // opposite( poly_first U poly_second ) = P2Q1 U Q2P1 - sew_2_marked_darts( final_map(),final_map().beta(dof_P1_outside,3), - final_map().beta(dof_P2_outside,3), mark_index, nodes, - indices, polyline_info); //P1P2 - sew_2_marked_darts( final_map(),final_map().beta(dof_Q1_outside,3), - final_map().beta(dof_Q2_outside,3), mark_index, nodes, - indices, polyline_info); //Q1Q2 - sew_2_marked_darts( final_map(),dof_P2_outside, - dof_Q1_outside, mark_index, nodes, - indices, polyline_info); //P2Q1 - sew_2_marked_darts( final_map(),dof_Q2_outside, - dof_P1_outside, mark_index, nodes, - indices, polyline_info); //Q2P1 - //update inside outside info (because darts from the same volume have been merged) - final_map().template attribute<3>(final_map().beta(dof_Q1_outside,3))->info().outside - .insert(first_poly); //update Q1Q2 outside poly - final_map().template attribute<3>(final_map().beta(dof_P1_outside,3))->info().outside - .insert(second_poly);//update P2P1 outside poly - } else { - // poly_first - poly_second = {0} - // poly_second - poly_first = Q1P1 U P2Q2 - // poly_first \cap poly_second = P1P2 - // opposite( poly_first U poly_second ) = Q2Q1 - sew_2_marked_darts( final_map(),final_map().beta(dof_Q1_outside,3), - dof_P1_outside, mark_index, nodes, - indices, polyline_info); //Q1P1 - sew_2_marked_darts( final_map(),dof_P2_outside, - final_map().beta(dof_Q2_outside,3), mark_index, nodes, - indices, polyline_info); //P2Q2 - sew_2_marked_darts( final_map(),final_map().beta(dof_P1_outside,3), - final_map().beta(dof_P2_outside,3), mark_index, nodes, - indices, polyline_info); //P1P2 - sew_2_marked_darts( final_map(),dof_Q2_outside, - dof_Q1_outside, mark_index, nodes, indices, - polyline_info); //Q2Q1 - //update inside outside info (because darts from the same volume have been merged) - final_map().template attribute<3>(final_map().beta(dof_P1_outside,3))->info().inside - .insert(second_poly); //update P1P2 inside poly - final_map().template attribute<3>(dof_Q2_outside)->info().outside - .insert(first_poly);//update Q2Q1 outside poly - } - } - } - } - } - -#ifdef CGAL_COREFINEMENT_DEBUG - std::cout << "number of darts to remove: " << darts_to_remove.size() - <::iterator itdart=darts_to_remove.begin(), - end=darts_to_remove.end(); itdart!=end; ++itdart) { - final_map().erase_dart(*itdart); - } - - //remove empty volumes - typedef typename Combinatorial_map_3::template Attribute_range<3>::type - Volume_attribute_range; - Volume_attribute_range& ratrib=final_map().template attributes<3>(); - typename Volume_attribute_range::iterator curr=ratrib.begin(),end=ratrib.end(); - do { - if (curr->info().is_empty) - final_map().template erase_attribute<3>(curr++); - else - ++curr; - } while(curr!=end); - - CGAL_assertion(final_map().is_valid()); - - //update the info of each volume knowing about only one polyhedron: - //this happens when one polyhedron has a connected component - //that do not intersect the other polyhedron - - typedef Side_of_triangle_mesh - Inside_poly_test; - - CGAL_precondition(polyhedron_to_map_node_to_polyhedron_vertex.size()==2); - Polyhedron* Poly_A = - polyhedron_to_map_node_to_polyhedron_vertex.begin()->first; - Polyhedron* Poly_B = - boost::next(polyhedron_to_map_node_to_polyhedron_vertex.begin())->first; - Inside_poly_test* inside_A_test_ptr=NULL; - Inside_poly_test* inside_B_test_ptr=NULL; - bool Poly_A_is_closed = Poly_A->is_closed(); - bool Poly_B_is_closed = Poly_B->is_closed(); - -#ifdef CGAL_COREFINEMENT_DEBUG - final_map().display_characteristics(std::cout); - std::cout << "\n"; -#endif - - typename Combinatorial_map_3::template One_dart_per_cell_range<3> cell_range= - final_map().template one_dart_per_cell<3>(); - for (typename Combinatorial_map_3::template One_dart_per_cell_range<3> - ::iterator it = cell_range.begin(), it_end=cell_range.end(); - it_end!= it; ++it ) - { - internal_IOP::Volume_info& info = - final_map().template attribute<3>(it)->info(); - std::size_t inside_size = info.inside.size(); - std::size_t outside_size = info.outside.size(); - - // if a volume is not classified wrt the two polyhedra, it means the component we look at does not - // is a disjoint (but maybe at a vertex TAG SL001) - if ( inside_size + outside_size == 1) { - bool is_inside = (inside_size==1); - Polyhedron* current_poly= is_inside ? (*info.inside.begin()) - : (*info.outside.begin()); - Polyhedron* test_poly; - Inside_poly_test* inside_test_ptr; - if ( current_poly==Poly_A) { - // if the polyhedron is not closed, we set Poly_A to be outside by default - if (!Poly_B_is_closed){ - info.outside.insert(Poly_B); - continue; - } - test_poly=Poly_B; - if (inside_B_test_ptr == NULL) - inside_B_test_ptr = new Inside_poly_test(*Poly_B, ppmap); - inside_test_ptr=inside_B_test_ptr; - } else { - // if the polyhedron is not closed, we set Poly_B to be outside by default - if (!Poly_A_is_closed){ - info.outside.insert(Poly_A); - continue; - } - test_poly=Poly_A; - if (inside_A_test_ptr == NULL) - inside_A_test_ptr = new Inside_poly_test(*Poly_A, ppmap); - inside_test_ptr=inside_A_test_ptr; - } - - // We need to find a point of the volume that is not on the boundary of the other volume. - // Then the position of this point give the position of the volume. If all the points are on - // the bounday, we take the mid-point of an edge (which must not be on the boundary otherwise - // it contradicts the fact that volumes are disjoint - // We first use the dart we have since one_dart_per_incident_cell has a non-negligeable cost. - typename Kernel::Point_3 query=final_map().template attribute<0>(it)->point(); - CGAL::Bounded_side res = (*inside_test_ptr)(query); - if (res==ON_BOUNDARY) { - typedef typename Combinatorial_map_3:: - template One_dart_per_incident_cell_range<0,3> Vertex_range; - - Vertex_range vertex_range = - final_map().template one_dart_per_incident_cell<0,3>(it); - typename Vertex_range::iterator vit = vertex_range.begin(); - - CGAL_assertion( typename Combinatorial_map_3::Dart_handle(vit) == - typename Combinatorial_map_3::Dart_handle(it) ); - ++vit; - for ( ; vit!=vertex_range.end(); ++vit) { - query=final_map().template attribute<0>(vit)->point(); - res = (*inside_test_ptr)(query); - if ( res != ON_BOUNDARY ) break; - } - - //take edge midpoint - if (res == ON_BOUNDARY) { - /// \todo see do a better test here. At least the construction cannot fail - /// but the mid-point can fall outside of the volume... -#ifdef CGAL_COREFINEMENT_DEBUG -#warning this is not exact!!! -#endif - typename Kernel::Point_3 p1 = final_map().template attribute<0>(it)->point(); - typename Kernel::Point_3 p2 = - final_map().template attribute<0>(final_map().beta(it,1))->point(); - query=midpoint(p1,p2); - res = (*inside_test_ptr)(query); - } - - CGAL_assertion( res!= ON_BOUNDARY ); - } - - if (res == ON_BOUNDED_SIDE ) - info.inside.insert(test_poly); - else - info.outside.insert(test_poly); - } - -#ifdef CGAL_COREFINEMENT_DEBUG - std::cout << "This volume has inside: "; - for (typename std::set::iterator itpoly=info.inside.begin(); - itpoly!=info.inside.end(); ++itpoly) - std::cout << " " << *itpoly; - std::cout << " and outside: "; - for (typename std::set::iterator itpoly=info.outside.begin(); - itpoly!=info.outside.end(); ++itpoly) - std::cout << " " << *itpoly; - std::cout << std::endl; -#endif - } - if (inside_A_test_ptr!=NULL) delete inside_A_test_ptr; - if (inside_B_test_ptr!=NULL) delete inside_B_test_ptr; - } - -}; - -} -} //end of namespace CGAL::Corefinement - -#endif //CGAL_INTERNAL_COREFINEMENT_COMBINATORIAL_MAP_OUTPUT_BUILDER_H diff --git a/Operations_on_polyhedra/include/CGAL/internal/corefinement/Polyhedra_output_builder.h b/Operations_on_polyhedra/include/CGAL/internal/corefinement/Polyhedra_output_builder.h deleted file mode 100644 index 33cf1a4df8c..00000000000 --- a/Operations_on_polyhedra/include/CGAL/internal/corefinement/Polyhedra_output_builder.h +++ /dev/null @@ -1,2516 +0,0 @@ -// Copyright (c) 2014 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_INTERNAL_COREFINEMENT_POLYHEDRA_OUTPUT_BUILDER_H -#define CGAL_INTERNAL_COREFINEMENT_POLYHEDRA_OUTPUT_BUILDER_H - -#include - -#include - -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -#include - -#include -#include - -namespace CGAL{ - -namespace internal_IOP{ - -#ifdef DEFINE_UNUSED_CLASSES -template -struct Import_patch_helper; - -template -struct Import_patch_helper -{ - typedef typename Polyhedron::HalfedgeDS HDS; - typedef typename HDS::Vertex_handle Vertex_handle; - typedef typename HDS::Face_handle Face_handle; - - Vertex_handle first_vertex(Face_handle f){ - return f->halfedge()->vertex(); - } - Vertex_handle second_hedge(Face_handle f){ - return f->halfedge()->next()->vertex(); - } - Vertex_handle third_hedge(Face_handle f){ - return f->halfedge()->prev()->vertex(); - } -}; - -template -struct Import_patch_helper -{ - typedef typename Polyhedron::HalfedgeDS HDS; - typedef typename HDS::Vertex_handle Vertex_handle; - typedef typename HDS::Face_handle Face_handle; - - Vertex_handle first_vertex(Face_handle f){ - return f->halfedge()->vertex(); - } - Vertex_handle second_vertex(Face_handle f){ - return f->halfedge()->prev()->vertex(); - } - Vertex_handle third_vertex(Face_handle f){ - return f->halfedge()->next()->vertex(); - } -}; - -template -class Import_patch - : public CGAL::Modifier_base - , public Import_patch_helper -{ - typedef typename Polyhedron::HalfedgeDS HDS; - typedef typename HDS::Halfedge_handle Halfedge_handle; - typedef typename HDS::Vertex_handle Vertex_handle; - typedef typename HDS::Face_handle Face_handle; - typedef typename HDS::Vertex Vertex; - typedef typename HDS::Halfedge Halfedge; - typedef typename HDS::Face Face; - typedef typename HDS::Halfedge::Base HBase; - - const std::vector& facets; - const std::set& interior_vertices; - const std::vector& interior_halfedges; - const std::vector& patch_border_halfedges; - -public: - - Import_patch( - const std::vector& facets_, - const std::set& interior_vertices_, - const std::vector& interior_halfedges_, - const std::vector& patch_border_halfedges_ - ) : facets(facets_) - , interior_vertices(interior_vertices_) - ,interior_halfedges(interior_halfedges_) - ,patch_border_halfedges(patch_border_halfedges_) - {} - - void operator()(HDS& hds) - { - Polyhedron_incremental_builder_3 B( hds); - B.begin_surface( interior_vertices.size(), - facets.size(), - interior_halfedges.size() * 2); - - std::size_t index=0; - std::map vertex_indices; - - //insert interior vertices - BOOST_FOREACH( Vertex_handle vh, interior_vertices){ - B.add_vertex( vh->point() ); - vertex_indices.insert( std::make_pair(vh, index++) ); - } - - //insert border vertices : TODO WE SHOULD GET THEM FROM hds - BOOST_FOREACH(Halfedge_handle h, patch_border_halfedges) - { - if( vertex_indices.insert( std::make_pair(h->vertex(),index) ).second ){ - B.add_vertex( h->vertex()->point() ); - ++index; - } - if( vertex_indices.insert( std::make_pair(h->opposite()->vertex(),index) ).second ){ - B.add_vertex( h->opposite()->vertex()->point() ); - ++index; - } - } - - BOOST_FOREACH(Face_handle fh, facets) - { - B.begin_facet(); - B.add_vertex_to_facet( vertex_indices[ this->first_vertex(fh) ] ); - B.add_vertex_to_facet( vertex_indices[ this->second_vertex(fh) ] ); - B.add_vertex_to_facet( vertex_indices[ this->third_vertex(fh) ] ); - B.end_facet(); - } - - B.end_surface(); - } -}; -#endif //DEFINE_UNUSED_CLASSES - -template < class Polyhedron, class PolyhedronPointPMap, class HalfedgeOutputIterator > -struct Import_polyline - : public CGAL::Modifier_base -{ - typedef typename Polyhedron::HalfedgeDS HDS; - typedef typename HDS::Halfedge_handle Halfedge_handle; - typedef typename HDS::Halfedge_const_handle Halfedge_const_handle; - typedef typename HDS::Vertex_handle Vertex_handle; - typedef typename HDS::Face_handle Face_handle; - typedef typename HDS::Vertex Vertex; - typedef typename HDS::Halfedge Halfedge; - typedef typename HDS::Face Face; - typedef typename HDS::Halfedge::Base HBase; - typedef std::map< Halfedge_handle, - Halfedge_handle, - Compare_unik_address > Hedge_map; - typedef std::map< Vertex_handle, - Vertex_handle > Vertex_map; - typedef internal_IOP::Compare_unik_address Cmp_unik_ad; - - Vertex_map& vertex_map; - Hedge_map& P_to_O_hedge; - Hedge_map& Q_to_O_hedge; - int nb_segments; - Halfedge_handle P_first_halfedge; - Halfedge_handle Q_first_halfedge; - Halfedge_handle O_first_halfedge; - const std::map< Halfedge_const_handle, - std::pair,Cmp_unik_ad >& border_halfedges; - PolyhedronPointPMap ppmap; - HalfedgeOutputIterator output; - - Import_polyline( - Vertex_map& vertex_map_, - Hedge_map& P_to_O_hedge_, - Hedge_map& Q_to_O_hedge_, - int nb_segments_, - Halfedge_handle P_first_halfedge_, - Halfedge_handle Q_first_halfedge_, - const std::map< Halfedge_const_handle, - std::pair,Cmp_unik_ad >& border_halfedges_, - PolyhedronPointPMap ppmap_, - HalfedgeOutputIterator output_) - : vertex_map( vertex_map_ ) - , P_to_O_hedge( P_to_O_hedge_ ) - , Q_to_O_hedge( Q_to_O_hedge_ ) - , nb_segments( nb_segments_ ) - , P_first_halfedge( P_first_halfedge_ ) - , Q_first_halfedge( Q_first_halfedge_ ) - , border_halfedges( border_halfedges_ ) - , ppmap( ppmap_ ) - , output(output_) - {} - - void operator()(HDS& hds) - { - HalfedgeDS_decorator decorator(hds); - - typename HDS::Halfedge dummy_hedge; //dummy default constructed halfedge - O_first_halfedge=hds.edges_push_back(dummy_hedge, dummy_hedge); - *output++=O_first_halfedge; - - //make sure the first vertex does not already exist - Vertex_handle source; - std::pair< typename std::map< Vertex_handle, Vertex_handle >::iterator, bool > insert_res= - vertex_map.insert( std::make_pair( P_first_halfedge->opposite()->vertex(), source ) ); - - - if( insert_res.second ) - { - source = decorator.vertices_push_back( *(P_first_halfedge->opposite()->vertex()) ); - decorator.set_vertex_halfedge(source, O_first_halfedge->opposite()); - put(ppmap, source, get(ppmap, P_first_halfedge->opposite()->vertex() ) ); - insert_res.first->second = source; - } - else - source = insert_res.first->second; - - //make sure the target vertex does not already exist if it is a polyline endpoint - Vertex_handle target; - if ( nb_segments==1 ) - { - insert_res = - vertex_map.insert( std::make_pair( P_first_halfedge->vertex(), target ) ); - if (insert_res.second) - { - target=decorator.vertices_push_back( *(P_first_halfedge->vertex()) ); - decorator.set_vertex_halfedge(target, O_first_halfedge); - put(ppmap, target, get(ppmap, P_first_halfedge->vertex() ) ); - insert_res.first->second = target; - } - else - target = insert_res.first->second; - } - else{ - target=decorator.vertices_push_back( *(P_first_halfedge->vertex()) ); - decorator.set_vertex_halfedge(target, O_first_halfedge); - put(ppmap, target, get(ppmap, P_first_halfedge->vertex() ) ); - } - - - //update source and target vertex of the edge created - decorator.set_vertex(O_first_halfedge, target); - decorator.set_vertex(O_first_halfedge->opposite(), source); - - - Halfedge_handle O_previous=O_first_halfedge; - Halfedge_handle P_previous=P_first_halfedge; - Halfedge_handle Q_previous=Q_first_halfedge; - - //set the correspondance - P_to_O_hedge.insert( std::make_pair(P_previous, O_previous) ); - Q_to_O_hedge.insert( std::make_pair(Q_previous, O_previous) ); - - source=target; - for (int i=1; ivertex()) ); - decorator.set_vertex_halfedge(target, O_hedge); - put(ppmap, target, get(ppmap, P_hedge->vertex() ) ); - } - else{ - std::pair< typename std::map< Vertex_handle, Vertex_handle >::iterator, bool > insert_res= - vertex_map.insert( std::make_pair(P_hedge->vertex(), Vertex_handle()) ); - if (insert_res.second) - { - target=decorator.vertices_push_back( *(P_hedge->vertex()) ); - decorator.set_vertex_halfedge(target, O_hedge); - put(ppmap, target, get(ppmap, P_hedge->vertex() ) ); - insert_res.first->second = target; - } - else - target = insert_res.first->second; - } - - decorator.set_vertex(O_hedge, target); - decorator.set_vertex(O_hedge->opposite(), source); - - O_previous=O_hedge; - P_previous = P_hedge; - Q_previous = Q_hedge; - source = target; - - P_to_O_hedge.insert( std::make_pair(P_previous, O_previous) ); - Q_to_O_hedge.insert( std::make_pair(Q_previous, O_previous) ); - } - } -}; - -template -struct Surface_extension_helper; - -template -struct Surface_extension_helper -{ - typedef typename Polyhedron::HalfedgeDS HDS; - typedef typename HDS::Halfedge_handle Halfedge_handle; - typedef typename HDS::Face_handle Face_handle; - typedef std::map< Halfedge_handle, - Halfedge_handle, - Compare_unik_address > Hedge_map; - Hedge_map& Qhedge_to_Phedge; - - Surface_extension_helper(Hedge_map& qh_to_ph):Qhedge_to_Phedge(qh_to_ph){} - - Halfedge_handle get_hedge(Halfedge_handle qhedge) - { - CGAL_assertion( Qhedge_to_Phedge.find(qhedge)!=Qhedge_to_Phedge.end() ); - std::pair key_and_value = - *Qhedge_to_Phedge.find(qhedge); - return key_and_value.first != qhedge - ? key_and_value.second - : key_and_value.second->opposite(); - } - - Halfedge_handle first_hedge(Face_handle f){ - return get_hedge( f->halfedge() ); - } - Halfedge_handle second_hedge(Face_handle f){ - return get_hedge( f->halfedge()->prev() ); - } - Halfedge_handle third_hedge(Face_handle f){ - return get_hedge( f->halfedge()->next() ); - } -}; - -template -struct Surface_extension_helper -{ - typedef typename Polyhedron::HalfedgeDS HDS; - typedef typename HDS::Halfedge_handle Halfedge_handle; - typedef typename HDS::Face_handle Face_handle; - typedef std::map< Halfedge_handle, - Halfedge_handle, - Compare_unik_address > Hedge_map; - Hedge_map& Qhedge_to_Phedge; - - Surface_extension_helper(Hedge_map& qh_to_ph):Qhedge_to_Phedge(qh_to_ph){} - - Halfedge_handle get_hedge(Halfedge_handle qhedge) - { - CGAL_assertion( Qhedge_to_Phedge.count(qhedge)==1 ); - std::pair key_and_value = - *Qhedge_to_Phedge.find(qhedge); - return key_and_value.first == qhedge - ? key_and_value.second - : key_and_value.second->opposite(); - } - - Halfedge_handle first_hedge(Face_handle f){ - return get_hedge( f->halfedge() ); - } - Halfedge_handle second_hedge(Face_handle f){ - return get_hedge( f->halfedge()->next() ); - } - Halfedge_handle third_hedge(Face_handle f){ - return get_hedge( f->halfedge()->prev() ); - } -}; - - - -template -class Surface_extension_by_patch_appending - : public CGAL::Modifier_base - , public Surface_extension_helper -{ - typedef typename Polyhedron::HalfedgeDS HDS; - typedef typename HDS::Halfedge_handle Halfedge_handle; - typedef typename HDS::Vertex_handle Vertex_handle; - typedef typename HDS::Face_handle Face_handle; - typedef typename HDS::Vertex Vertex; - typedef typename HDS::Halfedge Halfedge; - typedef typename HDS::Face Face; - typedef typename HDS::Halfedge::Base HBase; - - const std::vector& facets; - const std::vector& interior_halfedges; - const std::vector& patch_border_halfedges; - const std::set& interior_vertices; - PolyhedronPointPMap ppmap; - - typedef Surface_extension_helper Base; - using Base::first_hedge; - using Base::second_hedge; - using Base::third_hedge; - using Base::get_hedge; - using Base::Qhedge_to_Phedge; - -public: - - Surface_extension_by_patch_appending( - const std::vector& facets_, - const std::vector& interior_halfedges_, - const std::vector& patch_border_halfedges_, - const std::set& interior_vertices_, - typename Base::Hedge_map& Qhedge_to_Phedge_, - PolyhedronPointPMap ppmap_ - ) : Base( Qhedge_to_Phedge_ ) - ,facets(facets_) - ,interior_halfedges(interior_halfedges_) - ,patch_border_halfedges(patch_border_halfedges_) - ,interior_vertices(interior_vertices_) - ,ppmap(ppmap_) - {} - - void operator()(HDS& hds) - { - HalfedgeDS_decorator decorator(hds); - std::vector interior_vertex_halfedges; - - //insert interior halfedges and create interior vertices - BOOST_FOREACH(Halfedge_handle h, interior_halfedges){ - Halfedge_handle new_h = hds.edges_push_back( *h ); - Qhedge_to_Phedge[ h ] = new_h; - // put new halfedges on the border of the mesh - decorator.set_face(new_h, Face_handle()); - decorator.set_face(new_h->opposite(), Face_handle()); - - //create a copy of interior vertices only once - if ( h->vertex()->halfedge()==h && interior_vertices.count(h->vertex()) ) - { - Vertex_handle v = decorator.vertices_push_back( *(h->vertex()) ); - decorator.set_vertex_halfedge(v, new_h); - decorator.set_vertex(new_h, v); - put(ppmap, v, get(ppmap, h->vertex() ) ); - interior_vertex_halfedges.push_back( new_h ); - } - if ( h->opposite()->vertex()->halfedge()==h->opposite() && - interior_vertices.count(h->opposite()->vertex()) ) - { - Vertex_handle v = decorator.vertices_push_back( *(h->opposite()->vertex()) ); - decorator.set_vertex_halfedge(v, new_h->opposite()); - decorator.set_vertex(new_h->opposite(), v); - put(ppmap, v, get(ppmap, h->opposite()->vertex() ) ); - interior_vertex_halfedges.push_back( new_h->opposite() ); - } - } - - //create facets and connect halfedges - BOOST_FOREACH(Face_handle f, facets) - { - Halfedge_handle hedges[]={ - first_hedge(f), second_hedge(f), third_hedge(f) - }; - - Face_handle new_f = decorator.faces_push_back( *f ); - decorator.set_face_halfedge(new_f, hedges[0]); - - for (int i=0;i<3;++i) - { - hedges[i]->HBase::set_next(hedges[(i+1)%3]); - decorator.set_prev(hedges[(i+1)%3], hedges[i]); - decorator.set_face(hedges[i], new_f); - } - } - - // handle interior edges that are on the border of the mesh: - // they do not have a prev/next pointer set since only the pointers - // of patch interior halfedges part a face have been. In the following - // (i) we set the next/prev pointer around interior vertices on the mesh - // boundary and (ii) we collect interior mesh border halfedges incident to - // a patch border vertex and set their next/prev pointer (possibly of - // another patch) - - // Containers used for step (ii) for collecting mesh border halfedges - // with source/target on an intersection polyline that needs it prev/next - // pointer to be set - std::vector border_halfedges_source_to_link; - std::vector border_halfedges_target_to_link; - BOOST_FOREACH(Halfedge_handle h, interior_halfedges) - if (h->is_border_edge()) - { - if (!h->is_border()) h=h->opposite(); - - Vertex_handle src = h->opposite()->vertex(); - Vertex_handle tgt = h->vertex(); - if (reverse_patch_orientation) std::swap(src, tgt); - - if ( !interior_vertices.count(src) ) - border_halfedges_source_to_link.push_back(get_hedge(h)); - if ( !interior_vertices.count(tgt) ){ - border_halfedges_target_to_link.push_back(get_hedge(h)); - continue; // since the next halfedge should not be in the same patch - } - CGAL_assertion( h->is_border() && - h->prev()->is_border() && h->next()->is_border() ); - // step (i) - Halfedge_handle qhedge=get_hedge(h); - Halfedge_handle qnext = reverse_patch_orientation ? - get_hedge(h->prev()) : get_hedge(h->next()); - CGAL_assertion( qhedge->is_border() && qnext->is_border() ); - qhedge->HBase::set_next(qnext); - decorator.set_prev(qnext, qhedge); - } - // now the step (ii) we look for the candidate halfedge by turning around - // the vertex in the direction of the interior of the patch - BOOST_FOREACH(Halfedge_handle h, border_halfedges_target_to_link) - { - Halfedge_handle candidate=h->opposite()->prev()->opposite(); - CGAL_assertion_code(Halfedge_handle start=candidate); - while (!candidate->is_border()){ - candidate=candidate->prev()->opposite(); - CGAL_assertion(candidate!=start); - } - h->HBase::set_next(candidate); - decorator.set_prev(candidate,h); - } - BOOST_FOREACH(Halfedge_handle h, border_halfedges_source_to_link) - { - Halfedge_handle candidate=h->opposite()->next()->opposite(); - while (!candidate->is_border()) - candidate=candidate->next()->opposite(); - candidate->HBase::set_next(h); - decorator.set_prev(h,candidate); - } - - // For all interior vertices, update the vertex pointer - // of all but the vertex halfedge - BOOST_FOREACH(Halfedge_handle h, interior_vertex_halfedges) - { - Vertex_handle v=h->vertex(); - Halfedge_handle next_around_vertex=h; - do{ - CGAL_assertion (next_around_vertex->next() != Halfedge_handle()); - next_around_vertex=next_around_vertex->next()->opposite(); - decorator.set_vertex(next_around_vertex, v); - }while(h != next_around_vertex); - } - - // For all patch boundary vertices, update the vertex pointer - // of all but the vertex halfedge - BOOST_FOREACH(Halfedge_handle qhedge, patch_border_halfedges) - { - //check for a halfedge pointing inside an already imported patch - Halfedge_handle h = get_hedge(qhedge); - CGAL_assertion( h->next()!=Halfedge_handle() ); - // update the pointers on the target - Halfedge_handle next_around_target=h; - Vertex_handle v=h->vertex(); - do{ - next_around_target = next_around_target->next()->opposite(); - decorator.set_vertex(next_around_target, v); - }while( next_around_target->next()!=Halfedge_handle() && - next_around_target!=h && !next_around_target->is_border()); - // update the pointers on the source - Halfedge_handle next_around_source=h->prev(); - CGAL_assertion(next_around_source!=Halfedge_handle()); - v = h->opposite()->vertex(); - do{ - decorator.set_vertex(next_around_source, v); - next_around_source = next_around_source->opposite()->prev(); - }while( next_around_source!=Halfedge_handle() && - next_around_source!=h->opposite() && - !next_around_source->is_border()); - } - } -}; - -template -struct Intersection_polylines{ - typedef typename Polyhedron::Halfedge_handle Halfedge_handle; - const std::vector& P; - const std::vector& Q; - const std::vector& lengths; - boost::dynamic_bitset<> to_skip; - boost::dynamic_bitset<> to_skip_in_P; - boost::dynamic_bitset<> to_skip_in_Q; - Intersection_polylines( - const std::vector& P_polylines, - const std::vector& Q_polylines, - const std::vector& lengths_ - ) : P( P_polylines ) - , Q( Q_polylines ) - , lengths( lengths_ ) - , to_skip(P.size(),false) - , to_skip_in_P(P.size(),false) - , to_skip_in_Q(P.size(),false) - {} -}; - -template -class Remove_patch_simplices : public CGAL::Modifier_base { - typedef typename Polyhedron::HalfedgeDS HDS; - typedef typename HDS::Halfedge_handle Halfedge_handle; - typedef typename HDS::Vertex_handle Vertex_handle; - typedef typename HDS::Face_handle Face_handle; - typedef typename HDS::Vertex Vertex; - typedef typename HDS::Halfedge Halfedge; - typedef typename HDS::Face Face; - typedef typename HDS::Halfedge::Base HBase; - - const std::vector& facets; - const std::set& interior_vertices; - const std::vector& interior_halfedges; - const std::vector& patch_border_halfedges; - -public: - - Remove_patch_simplices( - const std::vector& facets_, - const std::set& interior_vertices_, - const std::vector& interior_halfedges_, - const std::vector& patch_border_halfedges_ - ) : facets(facets_) - , interior_vertices(interior_vertices_) - ,interior_halfedges(interior_halfedges_) - ,patch_border_halfedges(patch_border_halfedges_) - {} - - void operator()(HDS& hds) - { - CGAL::HalfedgeDS_decorator decorator(hds); - - // put the halfedges on the boundary of the patch on the boundary of the polyhedron - Face_handle border; - BOOST_FOREACH(Halfedge_handle h, patch_border_halfedges) - decorator.set_face(h, border); - - // set next/prev relationship of border halfedges - BOOST_FOREACH(Halfedge_handle h, patch_border_halfedges) - { - Halfedge_handle next=h->next(); - while(!next->is_border()) - next=next->opposite()->next(); - h->HBase::set_next(next); - decorator.set_prev(next,h); - decorator.set_vertex_halfedge(h->vertex(),h); - } - - // In case a ccb of the patch is not a cycle (the source and target vertices - // are border vertices), the first halfedge of that ccb will not have its - // prev pointer set correctly. To fix that, we consider all interior edges - // and check for one that is on the border of the patch and that is incident - // to a border vertex and use it to get the missing prev pointer. - BOOST_FOREACH(Halfedge_handle h, interior_halfedges) - if(h->is_border_edge()) - { - if (h->is_border()) h=h->opposite(); - if ( !interior_vertices.count(h->vertex()) ) - { - // look for the halfedge belonging to patch_border_halfedge - // having the prev pointer not correctly set - Halfedge_handle next=h->next(); - while(!next->is_border()) - next=next->opposite()->next(); - CGAL_assertion( next->is_border() );//we marked it above! - // now update the prev pointer - Halfedge_handle prev=h->opposite()->prev(); - prev->HBase::set_next(h->next()); - decorator.set_prev(h->next(),prev); - decorator.set_vertex_halfedge(prev->vertex(),prev); - } - } - - //now remove the simplices - BOOST_FOREACH(Halfedge_handle h, interior_halfedges) - hds.edges_erase(h); - BOOST_FOREACH(Face_handle f, facets) - hds.faces_erase(f); - BOOST_FOREACH(Vertex_handle v, interior_vertices) - hds.vertices_erase(v); - } -}; - -template -class Remove_isolated_patch_simplices - : public CGAL::Modifier_base -{ - typedef typename Polyhedron::HalfedgeDS HDS; - typedef typename HDS::Halfedge_handle Halfedge_handle; - typedef typename HDS::Vertex_handle Vertex_handle; - typedef typename HDS::Face_handle Face_handle; - typedef typename HDS::Vertex Vertex; - typedef typename HDS::Halfedge Halfedge; - typedef typename HDS::Face Face; - typedef typename HDS::Halfedge::Base HBase; - - const std::vector& facets; - const std::set& interior_vertices; - const std::vector& interior_halfedges; - const std::vector& border_halfedges; - -public: - - Remove_isolated_patch_simplices( - const std::vector& facets_, - const std::set& interior_vertices_, - const std::vector& interior_halfedges_, - const std::vector& border_halfedges_ - ) : facets(facets_) - , interior_vertices(interior_vertices_) - , interior_halfedges(interior_halfedges_) - , border_halfedges(border_halfedges_) - {} - - void operator()(HDS& hds) - { - CGAL::HalfedgeDS_decorator decorator(hds); - //remove the simplices - BOOST_FOREACH(Halfedge_handle h, interior_halfedges) - hds.edges_erase(h); - // There is no shared halfedge between duplicated patches even - // if they were before the duplication. Thus the erase that follows is safe. - // However remember that vertices were not duplicated which is why their - // removal is not handled here (still in use or to be removed in - // remove_unused_polylines()) - BOOST_FOREACH(Halfedge_handle h, border_halfedges) - hds.edges_erase(h); - BOOST_FOREACH(Face_handle f, facets) - hds.faces_erase(f); - BOOST_FOREACH(Vertex_handle v, interior_vertices) - hds.vertices_erase(v); - } -}; - -template -class Disconnect_patches : public CGAL::Modifier_base { - typedef typename Polyhedron::HalfedgeDS HDS; - typedef typename HDS::Halfedge_handle Halfedge_handle; - typedef typename HDS::Vertex_handle Vertex_handle; - typedef typename HDS::Face_handle Face_handle; - typedef typename HDS::Vertex Vertex; - typedef typename HDS::Halfedge Halfedge; - typedef typename HDS::Face Face; - typedef typename HDS::Halfedge::Base HBase; - - const std::vector& facets; - const std::set& interior_vertices; - const std::vector& interior_halfedges; - const std::vector& patch_border_halfedges; - std::vector& new_patch_border; - -public: - - Disconnect_patches( - const std::vector& facets_, - const std::set& interior_vertices_, - const std::vector& interior_halfedges_, - const std::vector& patch_border_halfedges_, - std::vector& new_patch_border_ - ) : facets(facets_) - , interior_vertices(interior_vertices_) - , interior_halfedges(interior_halfedges_) - , patch_border_halfedges(patch_border_halfedges_) - , new_patch_border(new_patch_border_) - {} - - void operator()(HDS& hds) - { - CGAL::HalfedgeDS_decorator decorator(hds); - - new_patch_border.reserve( patch_border_halfedges.size() ); - - std::map old_to_new; - - // put the halfedges on the boundary of the patch on the boundary of the polyhedron - Face_handle border; - BOOST_FOREACH(Halfedge_handle h, patch_border_halfedges) - { - Halfedge_handle new_hedge = hds.edges_push_back(*h); - new_patch_border.push_back(new_hedge); - decorator.set_face(h, border); - decorator.set_face(new_hedge->opposite(), border); - old_to_new.insert( std::make_pair(h, new_hedge) ); - } - - // update next/prev pointer of new hedges in case it is one of the new hedges - BOOST_FOREACH(Halfedge_handle h, new_patch_border) - { - if ( h->next()->is_border() ){ - h->HBase::set_next( old_to_new[h->next()] ); - decorator.set_prev( h->next(), h); - } - } - - // set next/prev pointers on the border of the neighbor patch - BOOST_FOREACH(Halfedge_handle h, patch_border_halfedges) - { - Halfedge_handle next=h->next(); - // check if not already done - if ( !next->is_border() ){ - do{ - next=next->opposite()->next(); - } while(!next->is_border()); - h->HBase::set_next(next); - decorator.set_prev(next,h); - } - - // setting prev is only needed in case the polyhedron has a boundary - // and the intersection polyline intersects its boundary - if ( !h->prev()->is_border() ){ - Halfedge_handle prev=h->prev(); - do{ - prev=prev->opposite()->prev(); - } while( !prev->is_border() ); - prev->HBase::set_next(h); - decorator.set_prev(h, prev); - } - - CGAL_assertion( h->prev()->is_border() ); - - decorator.set_vertex_halfedge(h->vertex(),h); - decorator.set_vertex_halfedge(h->opposite()->vertex(),h->opposite()); // only needed if the polyhedra is open - } - - //update next/prev relationship inside the patch - //to have a correct connectivity, and update facet halfedge pointer - BOOST_FOREACH(Halfedge_handle h, new_patch_border) - { - if ( h->prev()->next() != h ) - h->prev()->HBase::set_next( h ); - if ( h->next()->prev() != h ) - decorator.set_prev(h->next(), h); - decorator.set_face_halfedge(h->facet(), h); - } - - // update next/prev pointers on the border of the patch - BOOST_FOREACH(Halfedge_handle h, new_patch_border) - { - h=h->opposite(); - //set next pointer if not already set - if ( h->next()->prev()!=h ) - { - // we visit facets inside the patch we consider - Halfedge_handle candidate = h->opposite()->prev()->opposite(); - while ( !candidate->is_border() ) - candidate = candidate->prev()->opposite(); - h->HBase::set_next(candidate); - decorator.set_prev(candidate,h); - } - CGAL_assertion( h->next()->prev()== h ); - - // set prev pointer if not already set - if ( h->prev()->next() != h ) - { - Halfedge_handle candidate = h->opposite()->next()->opposite(); - while ( !candidate->is_border() ) - candidate = candidate->next()->opposite(); - decorator.set_prev(h,candidate); - candidate->HBase::set_next(h); - } - - CGAL_assertion( h->next()->prev()== h ); - CGAL_assertion( h->prev()->is_border() ); - CGAL_assertion( h->next()->is_border() ); - } - } -}; - - -template -struct Patch_description{ - typedef typename Polyhedron::Facet_handle Facet_handle; - typedef typename Polyhedron::Vertex_handle Vertex_handle; - typedef typename Polyhedron::Halfedge_handle Halfedge_handle; - - std::vector facets; - std::set interior_vertices; - std::vector interior_halfedges; - std::vector patch_border_halfedges; - bool is_initialized; - - Patch_description(): is_initialized(false) {}; -}; - -template -void extract_patch_simplices( - std::size_t patch_id, - Polyhedron& P, - Facet_index_pmap facet_indices, - const std::vector& patch_ids, - std::vector& facets, - std::set& interior_vertices, - std::vector& interior_halfedges, - std::vector& patch_border_halfedges, - const Is_marked_edge_map& is_marked_edge -) -{ - typedef typename Polyhedron::Halfedge_handle Halfedge_handle; - typedef typename Polyhedron::Vertex_handle Vertex_handle; - - for (typename Polyhedron::Facet_iterator fit=P.facets_begin(), - fit_end=P.facets_end(); - fit!=fit_end; ++fit) - { - if ( patch_ids[ get(facet_indices, fit) ]==patch_id ) - { - facets.push_back( fit ); - Halfedge_handle hedges[]={fit->halfedge(), fit->halfedge()->next(), fit->halfedge()->prev()}; - for (int i=0;i<3;++i) - { - if ( !is_marked_edge.count(hedges[i]) ) - { - if ( hedges[i] < hedges[i]->opposite() - || hedges[i]->opposite()->is_border()) - interior_halfedges.push_back( hedges[i] ); - } - else - patch_border_halfedges.push_back(hedges[i]); - } - } - } - - std::set border_vertices; - for (std::size_t k=0, end=patch_border_halfedges.size(); k!=end; ++k) - { - border_vertices.insert( patch_border_halfedges[k]->vertex() ); - // if the model is not closed i.e. patch_border_halfedge is not cycle only - border_vertices.insert( patch_border_halfedges[k]->opposite()->vertex() ); - } - - for (std::size_t k=0, end=interior_halfedges.size(); k!=end; ++k) - { - if ( border_vertices.find( interior_halfedges[k]->vertex() ) - == border_vertices.end() ) - { - interior_vertices.insert( interior_halfedges[k]->vertex() ); - } - if ( border_vertices.find( interior_halfedges[k]->opposite()->vertex() ) - == border_vertices.end() ) - { - interior_vertices.insert( interior_halfedges[k]->opposite()->vertex() ); - } - } -} - -template -struct Patch_container{ -//typedefs - typedef typename Polyhedron::Halfedge_const_handle Halfedge_const_handle; - typedef internal_IOP::Compare_unik_address Cmp_unik_ad; -// data members - std::vector< Patch_description > patches; -// external data members - Polyhedron* poly_ptr; - const std::vector& patch_ids; - Facet_index_pmap facet_id_pmap; - const std::map< Halfedge_const_handle, - std::pair,Cmp_unik_ad >& border_halfedges; -// constructor - Patch_container( - Polyhedron* poly_ptr_, - const std::vector& patch_ids_, - Facet_index_pmap facet_id_pmap_, - const std::map< Halfedge_const_handle, - std::pair,Cmp_unik_ad >& border_halfedges_, - std::size_t nb_patches - ) : patches(nb_patches) - , poly_ptr(poly_ptr_) - , patch_ids(patch_ids_) - , facet_id_pmap(facet_id_pmap_) - , border_halfedges(border_halfedges_) - {} - - Patch_description& operator[](std::size_t i) { - if ( !patches[i].is_initialized ) - { - extract_patch_simplices( - i, *poly_ptr, - facet_id_pmap, patch_ids, - patches[i].facets, patches[i].interior_vertices, - patches[i].interior_halfedges, patches[i].patch_border_halfedges, - border_halfedges - ); - - patches[i].is_initialized=true; - } - return patches[i]; - } - - /// debug - std::ostream& dump_patch(std::size_t i, std::ostream& out) - { - Patch_description& patch=this->operator[](i); - out << "OFF\n" << patch.interior_vertices.size()+patch.patch_border_halfedges.size(); - out << " " << patch.facets.size() << " 0\n"; - std::map vertexid; - int id=0; - BOOST_FOREACH(typename Polyhedron::Vertex_handle vh, patch.interior_vertices) - { - vertexid[vh]=id++; - out << vh->point() << "\n"; - } - - BOOST_FOREACH(typename Polyhedron::Halfedge_handle hh, patch.patch_border_halfedges) - { - vertexid[hh->vertex()]=id++; - out << hh->vertex()->point() << "\n"; - } - - BOOST_FOREACH(typename Polyhedron::Facet_handle fh, patch.facets) - { - out << "3 " << vertexid[fh->halfedge()->vertex()] << - " " << vertexid[fh->halfedge()->next()->vertex()] << - " " << vertexid[fh->halfedge()->next()->next()->vertex()] << "\n"; - } - - return out; - } - - void dump_patches(const boost::dynamic_bitset<>& selection, std::string prefix) - { - for (std::size_t i=selection.find_first(); - i < selection.npos; i = selection.find_next(i)) - { - std::stringstream ss; - ss << prefix << "-" << i << ".off"; - std::ofstream output(ss.str().c_str()); - dump_patch(i, output); - } - } - -}; - -} //end of namespace internal_IOP - -template -struct Default_facet_id_pmap -{ - typedef boost::read_write_property_map_tag category; - typedef std::size_t value_type; - typedef std::size_t& reference; - typedef typename Polyhedron::Facet_const_handle key_type; - - friend std::size_t get(Default_facet_id_pmap, key_type f) { return f->id(); } - friend void put(Default_facet_id_pmap, key_type f, std::size_t i) { const_cast(*f).id()=i; } -}; - -namespace Corefinement -{ - -template -class Polyhedra_output_builder -{ -//Default typedefs - typedef typename Default::Get< - PolyhedronPointPMap_, - Default_polyhedron_ppmap >::type PolyhedronPointPMap; - typedef typename Default::Get< - Kernel_, - typename Kernel_traits< - typename boost::property_traits::value_type - >::Kernel >::type Kernel; - typedef typename Default::Get< - Facet_id_pmap_, - Default_facet_id_pmap >::type Facet_id_pmap; - typedef typename Default::Get >::type - EdgeMarkPropertyMap; - - -public: -//Boolean operation indices - enum Boolean_operation {P_UNION_Q = 0, P_INTER_Q, P_MINUS_Q, Q_MINUS_P, NONE }; -private: -//Data members - Polyhedron *P_ptr, *Q_ptr; - cpp11::array, 4 > desired_output; - Facet_id_pmap P_facet_id_pmap, Q_facet_id_pmap; - PolyhedronPointPMap ppmap; - EdgeMarkPropertyMap edge_mark_pmap; - bool input_with_coplanar_facets; - // bitset containing information about operations that cannot be - // performed because of non-manifoldness or that is ambiguous - // 0 = P+Q - // 1 = P inter Q - // 2 = P - Q - // 3 = Q - P - std::bitset<4> impossible_operation; - - //Orientation of polyhedra - bool is_P_inside_out; - bool is_Q_inside_out; - -//Polyhedron typedefs - typedef typename Polyhedron::Halfedge_const_handle Halfedge_const_handle; - typedef typename Polyhedron::Halfedge_handle Halfedge_handle; - typedef typename Polyhedron::Vertex_handle Vertex_handle; - typedef typename Polyhedron::Vertex_const_handle Vertex_const_handle; - typedef typename Polyhedron::Facet_handle Facet_handle; -//Other typedefs - typedef internal_IOP::Compare_unik_address Cmp_unik_ad; - typedef std::map< std::pair, - std::pair< std::map, - std::pair > > An_edge_per_polyline_map; - typedef std::map Node_to_polyhedron_vertex_map; - typedef std::map Poly_to_map_node; - typedef internal_IOP::Intersection_polylines Intersection_polylines; - typedef internal_IOP::Patch_description Patch_description; - typedef internal_IOP::Patch_container Patch_container; - typedef std::map< Halfedge_handle, Halfedge_handle, Cmp_unik_ad> Edge_map; -#ifdef CGAL_COREFINEMENT_POLYHEDRA_DEBUG - #warning move these to predicates.h -#endif - template - bool are_triangles_coplanar_same_side( - const typename Kernel::Point_3& O_prime,const typename Kernel::Point_3& O, - const typename Kernel::Point_3& P,const typename Kernel::Point_3& Q) - { - if ( CGAL::orientation(O_prime, O, P ,Q) != CGAL::COPLANAR ) return false; - CGAL::Orientation cpl_orient = CGAL::coplanar_orientation(O_prime, O, P, Q); - CGAL_assertion( cpl_orient != CGAL::COLLINEAR ); - return cpl_orient == CGAL::POSITIVE; - } - - - template - bool are_triangles_coplanar_same_side_filtered( int O_prime_index, - int O_index, - int P_index, - int Q_index, - Vertex_handle P, - Vertex_handle Q, - const Nodes_vector& nodes) - { - typename Nodes_vector::Protector p; - try{ - CGAL_USE(p); - return are_triangles_coplanar_same_side( - nodes.interval_node(O_prime_index), - nodes.interval_node(O_index), - P_index == -1 ? nodes.to_interval(get(ppmap,P)): nodes.interval_node(P_index), - Q_index == -1 ? nodes.to_interval(get(ppmap,Q)) : nodes.interval_node(Q_index ) - ); - } - catch(Uncertain_conversion_exception&){ - return are_triangles_coplanar_same_side( - nodes.exact_node(O_prime_index), - nodes.exact_node(O_index), - P_index == -1 ? nodes.to_exact(get(ppmap,P)): nodes.exact_node(P_index), - Q_index == -1 ? nodes.to_exact(get(ppmap,Q)) : nodes.exact_node(Q_index ) - ); - } - } - - void remove_patches_from_polyhedra( - Polyhedron* P_ptr, - const boost::dynamic_bitset<>& patches_to_remove, - Patch_container& patches_of_P - ) - { - for (std::size_t i=patches_to_remove.find_first(); - i < patches_to_remove.npos; i = patches_to_remove.find_next(i)) - { - Patch_description& patch=patches_of_P[i]; - internal_IOP::Remove_patch_simplices - modifier( patch.facets, - patch.interior_vertices, - patch.interior_halfedges, - patch.patch_border_halfedges); - P_ptr->delegate(modifier); - } - } - - // function used to remove polylines imported or kept that are incident only - // to patches not kept for the operation P_ptr is used for storing - // the result. We look for edges with halfedges both on the border of - // the mesh. The vertices incident only to such edges should be removed. - // Here to detect vertices that should be kept, we abuse the fact that - // the halfedge to be removed and incident to a vertex that should not be - // removed will still have its next pointer set to a halfedge part of - // the result. - void remove_unused_polylines( - Polyhedron* P_ptr, - const boost::dynamic_bitset<>& patches_to_remove, - Patch_container& patches_of_P) - { - std::set vertices_to_remove; - std::set edges_to_remove; - for (std::size_t i = patches_to_remove.find_first(); - i < patches_to_remove.npos; - i = patches_to_remove.find_next(i)) - { - Patch_description& patch=patches_of_P[i]; - BOOST_FOREACH(Halfedge_handle h, patch.patch_border_halfedges) - { - if (h->is_border() && h->opposite()->is_border()){ - vertices_to_remove.insert(h->vertex()); - vertices_to_remove.insert(h->opposite()->vertex()); - edges_to_remove.insert( hopposite()?h:h->opposite()); - } - } - } - - BOOST_FOREACH(Vertex_handle vh, vertices_to_remove) - { - bool to_remove=true; - BOOST_FOREACH(Halfedge_handle h, halfedges_around_target(vh,*P_ptr)) - if (!h->is_border() || !h->opposite()->is_border()) - { - to_remove=false; - // in case the vertex halfedge was one that is going to remove, - // update it - set_halfedge(vh, h, *P_ptr); - break; - } - if (to_remove) - remove_vertex(vh,*P_ptr); - } - BOOST_FOREACH(Halfedge_handle hh, edges_to_remove) - remove_edge(edge(hh,*P_ptr),*P_ptr); - } - - void disconnect_patches_from_polyhedra( - Polyhedron* P_ptr, - const boost::dynamic_bitset<>& patches_to_remove, - Patch_container& patches_of_P, - const Edge_map& Phedge_to_Qhedge, //map former patch border halfedge to the equivalent in the other polyhedron - Edge_map& new_Phedge_to_Qhedge //map new patch border halfedge to the equivalent in the other polyhedron - ) - { - for (std::size_t i=patches_to_remove.find_first(); - i < patches_to_remove.npos; i = patches_to_remove.find_next(i)) - { - Patch_description& patch=patches_of_P[i]; - std::vector new_patch_border; - internal_IOP::Disconnect_patches - modifier( patch.facets, - patch.interior_vertices, - patch.interior_halfedges, - patch.patch_border_halfedges, - new_patch_border ); - P_ptr->delegate(modifier); - - - CGAL_assertion( new_patch_border.size() == - patch.patch_border_halfedges.size() ); - - std::size_t nb_hedges=new_patch_border.size(); - for (std::size_t k=0; k < nb_hedges; ++k){ - - CGAL_assertion( patch.patch_border_halfedges[k]->vertex() == new_patch_border[k]->vertex() ); - CGAL_assertion( patch.patch_border_halfedges[k]->opposite()->vertex() == new_patch_border[k]->opposite()->vertex() ); - CGAL_assertion( new_patch_border[k]->is_border_edge() ); - CGAL_assertion( !new_patch_border[k]->is_border() ); - CGAL_assertion( new_patch_border[k]->opposite()->next()->is_border() ); - CGAL_assertion( new_patch_border[k]->opposite()->prev()->is_border() ); - - typename Edge_map::const_iterator it_res = - Phedge_to_Qhedge.find(patch.patch_border_halfedges[k]); - CGAL_assertion( it_res != Phedge_to_Qhedge.end() ); - CGAL_assertion( it_res->first->vertex()->point() == it_res->second->vertex()->point() ); - CGAL_assertion( it_res->first->opposite()->vertex()->point() == it_res->second->opposite()->vertex()->point() ); - new_Phedge_to_Qhedge[ - patch.patch_border_halfedges[k]==it_res->first - ? new_patch_border[k] - : new_patch_border[k]->opposite() ] = it_res->second; - } - - patch.patch_border_halfedges.swap(new_patch_border); - } - } - - template - void append_Q_patches_to_P( - Polyhedron* P_ptr, - const boost::dynamic_bitset<>& patches_to_append, - Patch_container& patches, - std::map< Halfedge_handle, - Halfedge_handle, - internal_IOP::Compare_unik_address - >& Qhedge_to_Phedge - ){ - #ifdef CGAL_COREFINEMENT_POLYHEDRA_DEBUG - #warning the size of Qhedge_to_Phedge will increase when adding new patches by the size of internal edges. Maybe the use of a copy would be better? - #endif - for (std::size_t i=patches_to_append.find_first(); - i < patches_to_append.npos; i = patches_to_append.find_next(i)) - { - Patch_description& patch = patches[i]; - - internal_IOP::Surface_extension_by_patch_appending - modifier(patch.facets, patch.interior_halfedges, patch.patch_border_halfedges, patch.interior_vertices, Qhedge_to_Phedge, ppmap); - P_ptr->delegate(modifier); - } - - //CGAL_assertion( Qhedge_to_Phedge.size()==patch_border_halfedges.size() ); // this is not true in case of coplanar patches - //CGAL_assertion( P_ptr->is_valid() ); - } - - template < class HalfedgeOutputIterator > - void - import_polyline( - Polyhedron& O, - Halfedge_handle P_first_polyline_hedge, - Halfedge_handle Q_first_polyline_hedge, - int nb_segments, - std::map< Halfedge_handle, - Halfedge_handle, - internal_IOP::Compare_unik_address - > & P_to_O_hedge, - std::map< Halfedge_handle, - Halfedge_handle, - internal_IOP::Compare_unik_address - > & Q_to_O_hedge, - std::map& vertex_map, - const std::map< Halfedge_const_handle, - std::pair,Cmp_unik_ad >& border_halfedges, - HalfedgeOutputIterator output) - { - internal_IOP::Import_polyline - modifier( vertex_map, P_to_O_hedge, Q_to_O_hedge, nb_segments, - P_first_polyline_hedge, Q_first_polyline_hedge, - border_halfedges, ppmap, output); - - O.delegate( modifier ); - } - - void compute_border_edge_map( - const Intersection_polylines& polylines, - Patch_container& patches_of_P, - Patch_container& patches_of_Q, - Edge_map& Qhedge_to_Phedge - ){ - std::size_t nb_polylines = polylines.lengths.size(); - for( std::size_t i=0; i& patches_of_P_to_keep, - const boost::dynamic_bitset<>& patches_of_Q_to_import, - Patch_container& patches_of_P, - Patch_container& patches_of_Q, - bool reverse_patch_orientation_P, - bool reverse_patch_orientation_Q, - Edge_map& Qhedge_to_Phedge - ){ - //clean up patches not kept - remove_patches_from_polyhedra(P_ptr, ~patches_of_P_to_keep, patches_of_P); - - if (reverse_patch_orientation_P){ - Polygon_mesh_processing::reverse_face_orientations_of_mesh_with_polylines(*P_ptr); - // here we need to update the mapping to use the correct border - // halfedges while appending the patches from Q - BOOST_FOREACH(typename Edge_map::value_type& v, Qhedge_to_Phedge) - v.second=v.second->opposite(); - } - - //we import patches from Q - if ( reverse_patch_orientation_Q ) - append_Q_patches_to_P(P_ptr, patches_of_Q_to_import, patches_of_Q, Qhedge_to_Phedge); - else - append_Q_patches_to_P(P_ptr, patches_of_Q_to_import, patches_of_Q, Qhedge_to_Phedge); - } - - void compute_inplace_operation( - Polyhedron* P_ptr, - const boost::dynamic_bitset<>& patches_of_P_to_keep, - const boost::dynamic_bitset<>& patches_of_Q_to_import, - Patch_container& patches_of_P, - Patch_container& patches_of_Q, - bool reverse_patch_orientation_P, - bool reverse_patch_orientation_Q, - const Intersection_polylines& polylines - ){ - Edge_map Qhedge_to_Phedge; - //maps patch border halfedge from Q to halfedge from P - compute_border_edge_map(polylines, patches_of_P, - patches_of_Q, Qhedge_to_Phedge); - - compute_inplace_operation( P_ptr, - patches_of_P_to_keep, patches_of_Q_to_import, - patches_of_P, patches_of_Q, - reverse_patch_orientation_P, reverse_patch_orientation_Q, - Qhedge_to_Phedge ); - } - - -#ifdef CGAL_COREFINEMENT_POLYHEDRA_DEBUG - #warning factorize with the code of compute_difference_inplace -#endif - void compute_inplace_operation_delay_removal_and_insideout( - Polyhedron* P_ptr, - const boost::dynamic_bitset<>& patches_of_P_to_keep, - const boost::dynamic_bitset<>& patches_of_Q_to_import, - Patch_container& patches_of_P, - Patch_container& patches_of_Q, - bool reverse_patch_orientation_Q, - const Intersection_polylines& polylines, - Edge_map& disconnected_patches_hedge_to_Qhedge - ){ - Edge_map Qhedge_to_Phedge, Phedge_to_Qhedge; - //maps patch border halfedge from Q to halfedge from P - std::size_t nb_polylines = polylines.lengths.size(); - for( std::size_t i=0; iis_border() -#endif - // disconnect patches inside Q - // For the patches scheduled to be removed, their patch descriptions - // in patches_of_P will be updated so that patch_border_halfedges are - // the newly created halfedges within disconnect_patches_from_polyhedra. - // Note that disconnected_patches_hedge_to_Qhedge also refers to those halfedges - //init the map with the previously filled one (needed when reusing patches in two operations) - disconnected_patches_hedge_to_Qhedge=Phedge_to_Qhedge; - disconnect_patches_from_polyhedra(P_ptr, ~patches_of_P_to_keep, patches_of_P, - Phedge_to_Qhedge, disconnected_patches_hedge_to_Qhedge); - - //we import patches from Q - if (reverse_patch_orientation_Q) - append_Q_patches_to_P(P_ptr, patches_of_Q_to_import, patches_of_Q, Qhedge_to_Phedge); - else - append_Q_patches_to_P(P_ptr, patches_of_Q_to_import, patches_of_Q, Qhedge_to_Phedge); - } - - void remove_disconnected_patches( - Polyhedron& P, - Patch_container& patches, - const boost::dynamic_bitset<>& patches_to_remove) - { - for (std::size_t i=patches_to_remove.find_first(); - i < patches_to_remove.npos; - i = patches_to_remove.find_next(i)) - { - Patch_description& patch=patches[i]; - internal_IOP::Remove_isolated_patch_simplices - modifier( patch.facets, patch.interior_vertices, - patch.interior_halfedges, patch.patch_border_halfedges - ); - P.delegate( modifier ); - } - } - - template < class HalfedgeOutputIterator > - void fill_new_polyhedron( - Polyhedron& O, // output - const boost::dynamic_bitset<>& patches_of_P_to_import, - const boost::dynamic_bitset<>& patches_of_Q_to_import, - Patch_container& patches_of_P, - Patch_container& patches_of_Q, - bool reverse_orientation_of_patches_from_P, - bool reverse_orientation_of_patches_from_Q, - const Intersection_polylines& polylines, - const std::map< Halfedge_const_handle, - std::pair,Cmp_unik_ad >& border_halfedges, - HalfedgeOutputIterator shared_halfedge_output // new shared halfedges - ) - { - //add a polyline inside O for each intersection polyline - std::size_t nb_polylines = polylines.lengths.size(); - std::map P_to_O_vertex; - std::map< Halfedge_handle, - Halfedge_handle, - internal_IOP::Compare_unik_address - > P_to_O_hedge, Q_to_O_hedge; - for (std::size_t i=0; i < nb_polylines; ++i) - if (!polylines.to_skip.test(i)) - import_polyline(O, polylines.P[i], polylines.Q[i], - polylines.lengths[i], - P_to_O_hedge, Q_to_O_hedge, - P_to_O_vertex, border_halfedges, shared_halfedge_output); - - //import patches of P - if (reverse_orientation_of_patches_from_P) - append_Q_patches_to_P(&O, patches_of_P_to_import, patches_of_P, P_to_O_hedge); - else - append_Q_patches_to_P(&O, patches_of_P_to_import, patches_of_P, P_to_O_hedge); - - //import patches from Q - if (reverse_orientation_of_patches_from_Q) - append_Q_patches_to_P(&O, patches_of_Q_to_import, patches_of_Q, Q_to_O_hedge); - else - append_Q_patches_to_P(&O, patches_of_Q_to_import, patches_of_Q, Q_to_O_hedge); - } - - // detect if a polyline is incident to two patches that won't be imported - // for the current operation (polylines skipt are always incident to a - // coplanar patch) - template - static - void fill_polylines_to_skip( - Intersection_polylines& polylines, - const std::vector& P_patch_ids, - const std::vector& Q_patch_ids, - const boost::dynamic_bitset<>& patches_of_P_used, - const boost::dynamic_bitset<>& patches_of_Q_used, - const FacetIdPmap& P_face_id_pmap, - const FacetIdPmap& Q_face_id_pmap) - { - for (std::size_t i=0;iis_border()){ - std::size_t patch_id = P_patch_ids[ get( P_face_id_pmap, h_P->facet() ) ]; - if (patches_of_P_used.test(patch_id)) - skip_polyline_in_P=false; - } - if (skip_polyline_in_P && !h_P->opposite()->is_border()){ - std::size_t patch_id = P_patch_ids[ get( P_face_id_pmap, h_P->opposite()->facet() ) ]; - if (patches_of_P_used.test(patch_id)) - skip_polyline_in_P=false; - } - bool skip_polyline_in_Q=true; - if (!h_Q->is_border()){ - std::size_t patch_id = Q_patch_ids[ get( Q_face_id_pmap, h_Q->facet() ) ]; - if (patches_of_Q_used.test(patch_id)) - skip_polyline_in_Q=false; - } - if (skip_polyline_in_Q && !h_Q->opposite()->is_border()){ - std::size_t patch_id = Q_patch_ids[ get( Q_face_id_pmap, h_Q->opposite()->facet() ) ]; - if (patches_of_Q_used.test(patch_id)) - skip_polyline_in_Q=false; - } - - if (skip_polyline_in_P) polylines.to_skip_in_P.set(i); - if (skip_polyline_in_Q) polylines.to_skip_in_Q.set(i); - if (skip_polyline_in_P && skip_polyline_in_Q) - polylines.to_skip.set(i); - } - } - - bool is_dangling_edge(int src_id, int tgt_id, - Halfedge_const_handle hedge, - const boost::dynamic_bitset<>& is_node_of_degree_one) const - { - if ( is_node_of_degree_one.test(src_id) ) - { - bool res=true; - Halfedge_const_handle h = hedge->opposite(), start=h; - do{ - if (h->is_border()) - { - res = false; - break; - } - h=h->next()->opposite(); - } while(h!=start); - if (res) return true; - } - if ( is_node_of_degree_one.test(tgt_id) ) - { - Halfedge_const_handle h = hedge, start=h; - do{ - if (h->is_border()) - return false; - h=h->next()->opposite(); - } while(h!=start); - return true; - } - return false; - } - -public: - - Polyhedra_output_builder( - Polyhedron& P, - Polyhedron& Q, - cpp11::array, 4 > desired_output_, - Facet_id_pmap P_facet_id_pmap_, - Facet_id_pmap Q_facet_id_pmap_, - PolyhedronPointPMap point_pmap = PolyhedronPointPMap(), - EdgeMarkPropertyMap edge_pmap = EdgeMarkPropertyMap() - ) : P_ptr(&P) - , Q_ptr(&Q) - , desired_output( desired_output_ ) - , P_facet_id_pmap(P_facet_id_pmap_) - , Q_facet_id_pmap(Q_facet_id_pmap_) - , ppmap(point_pmap) - , edge_mark_pmap(edge_pmap) - , input_with_coplanar_facets(false) - , is_P_inside_out( !Polygon_mesh_processing::is_outward_oriented(*P_ptr) ) - , is_Q_inside_out( !Polygon_mesh_processing::is_outward_oriented(*Q_ptr) ) - {} - - Polyhedra_output_builder( - Polyhedron& P, - Polyhedron& Q, - cpp11::array, 4 > desired_output_, - PolyhedronPointPMap point_pmap = PolyhedronPointPMap(), - EdgeMarkPropertyMap edge_pmap = EdgeMarkPropertyMap() - ) : P_ptr(&P) - , Q_ptr(&Q) - , desired_output( desired_output_ ) - , ppmap(point_pmap) - , edge_mark_pmap(edge_pmap) - , input_with_coplanar_facets(false) - , is_P_inside_out( !Polygon_mesh_processing::is_outward_oriented(*P_ptr) ) - , is_Q_inside_out( !Polygon_mesh_processing::is_outward_oriented(*Q_ptr) ) - {} - - bool union_valid() const { return !impossible_operation[P_UNION_Q]; } - bool intersection_valid() const { return !impossible_operation[P_INTER_Q]; } - bool P_minus_Q_valid() const { return !impossible_operation[P_MINUS_Q]; } - bool Q_minus_P_valid() const { return !impossible_operation[Q_MINUS_P]; } - - void P_is_inside_out() { is_P_inside_out = true; } - void Q_is_inside_out() { is_Q_inside_out = true; } - - void input_have_coplanar_facets() - { - input_with_coplanar_facets=true; - } - - template - void operator()( - std::map,Cmp_unik_ad >& border_halfedges, - const Nodes_vector& nodes, - An_edge_per_polyline_map& an_edge_per_polyline, - const boost::dynamic_bitset<>& is_node_of_degree_one, - const Poly_to_map_node& /* polyhedron_to_map_node_to_polyhedron_vertex */) - { - // first create and fill a map from vertex to node_id - boost::unordered_map vertex_to_node_id; - for(typename std::map,Cmp_unik_ad >::iterator it=border_halfedges.begin(), - it_end=border_halfedges.end(); - it!=it_end;++it) - { - vertex_to_node_id[it->first->vertex()]=it->second.second; - vertex_to_node_id[it->first->opposite()->vertex()]=it->second.first; - } - - std::size_t num_facets_P = internal::corefinement::init_facet_indices(*P_ptr, P_facet_id_pmap); - std::size_t num_facets_Q = internal::corefinement::init_facet_indices(*Q_ptr, Q_facet_id_pmap); - boost::dynamic_bitset<> coplanar_facets_P(num_facets_P, 0); - boost::dynamic_bitset<> coplanar_facets_Q(num_facets_Q, 0); - - // In the following loop we filter intersection edge that are strictly inside a patch - // of coplanar facets so that we keep only the edges on the border of the patch. - // This is not optimal and in an ideal world being able to find the outside edges - // directly would avoid to compute the intersection of edge/facets inside the patch - // This loop is done only if the input have some coplanar facets - typename An_edge_per_polyline_map::iterator epp_it=input_with_coplanar_facets - ?an_edge_per_polyline.begin() - :an_edge_per_polyline.end(), - epp_it_end=an_edge_per_polyline.end(); - std::set border_edges_to_remove; - for (;epp_it!=epp_it_end;) - { - Halfedge_handle first_hedge = epp_it->second.first[P_ptr]; - Halfedge_handle first_hedge_opp = first_hedge->opposite(); - Halfedge_handle second_hedge = epp_it->second.first[Q_ptr]; - Halfedge_handle second_hedge_opp = second_hedge->opposite(); - - //vertices from P - // Vertex_handle P1=first_hedge_opp->next()->vertex(); - // Vertex_handle P2=first_hedge->next()->vertex(); - //vertices from Q - // Vertex_handle Q1=second_hedge_opp->next()->vertex(); - // Vertex_handle Q2=second_hedge->next()->vertex(); - - int index_p1=get_node_id(first_hedge_opp->next()->vertex(),vertex_to_node_id); - int index_p2=get_node_id(first_hedge->next()->vertex(),vertex_to_node_id); - int index_q1=get_node_id(second_hedge_opp->next()->vertex(),vertex_to_node_id); - int index_q2=get_node_id(second_hedge->next()->vertex(),vertex_to_node_id); - - // set boolean for the position of P1 wrt to Q1 and Q2 - bool P1_eq_Q1=false, P1_eq_Q2=false; - if (!first_hedge_opp->is_border() && index_p1!=-1) - { - if (!second_hedge_opp->is_border()) - P1_eq_Q1 = index_p1 == index_q1; - if (!second_hedge->is_border()) - P1_eq_Q2 = index_p1 == index_q2; - } - - // set boolean for the position of P2 wrt to Q1 and Q2 - bool P2_eq_Q1=false, P2_eq_Q2=false; - if (!first_hedge->is_border() && index_p2!=-1) - { - if (!second_hedge_opp->is_border()) - P2_eq_Q1 = index_p2 == index_q1; - if (!P1_eq_Q2 && !second_hedge->is_border()) - P2_eq_Q2 = index_p2 == index_q2; - } - - //mark coplanar facets if any - if (P1_eq_Q1){ - coplanar_facets_P.set(get(P_facet_id_pmap, first_hedge_opp->facet())); - coplanar_facets_Q.set(get(Q_facet_id_pmap, second_hedge_opp->facet())); - } - if (P1_eq_Q2){ - coplanar_facets_P.set(get(P_facet_id_pmap, first_hedge_opp->facet())); - coplanar_facets_Q.set(get(Q_facet_id_pmap, second_hedge->facet())); - } - if (P2_eq_Q1){ - coplanar_facets_P.set(get(P_facet_id_pmap, first_hedge->facet())); - coplanar_facets_Q.set(get(Q_facet_id_pmap, second_hedge_opp->facet())); - } - if (P2_eq_Q2){ - coplanar_facets_P.set(get(P_facet_id_pmap, first_hedge->facet())); - coplanar_facets_Q.set(get(Q_facet_id_pmap, second_hedge->facet())); - } - - // remove the edge if it is in the middle of a coplanar patch - if ( (P1_eq_Q1 || P1_eq_Q2) && (P2_eq_Q1 || P2_eq_Q2) ) - { - typename An_edge_per_polyline_map::iterator it_to_rm=epp_it; - ++epp_it; - an_edge_per_polyline.erase(it_to_rm); - border_edges_to_remove.insert(first_hedge); - border_edges_to_remove.insert(second_hedge); - #ifdef CGAL_COREFINEMENT_DEBUG - #warning we need to have the EdgeMarkPropertyMap to unmark intersection hedge - #endif - } - else - ++epp_it; - } - - BOOST_FOREACH(Halfedge_const_handle h, border_edges_to_remove) - border_halfedges.erase(h); - - // (1) Assign a patch id to each facet indicating in which connected - // component limited by intersection edges of the surface they are. - internal_IOP::Non_intersection_halfedge is_not_marked(border_halfedges); - - CGAL_assertion (P_ptr!=Q_ptr); - - std::vector P_patch_ids, Q_patch_ids; - std::vector P_patch_sizes, Q_patch_sizes; -#ifdef CGAL_COREFINEMENT_POLYHEDRA_DEBUG - #warning: the extraction of patches could be done by using a seed provided. That is we first make the \ - classification of patches using a representative facet (incident to a marked edge) and once \ - we really need a patch we extract it using this seed. Note that this requires a post-processing \ - if the input polyhedra have several connected component free from intersection \ - this would require to guarantee that there is no connected component not involved in the \ - intersection -#endif //CGAL_COREFINEMENT_POLYHEDRA_DEBUG - - /// \todo I need a property map indicating if an edge is an intersection edge - std::size_t P_nb_patches = internal::corefinement:: - mark_connected_components_v2(*P_ptr, - is_not_marked, - P_facet_id_pmap, - P_patch_ids, - P_patch_sizes); - std::size_t Q_nb_patches = internal::corefinement:: - mark_connected_components_v2(*Q_ptr, - is_not_marked, - Q_facet_id_pmap, - Q_patch_ids, - Q_patch_sizes); - - CGAL_assertion ( P_nb_patches==P_patch_sizes.size() ); - CGAL_assertion ( Q_nb_patches==Q_patch_sizes.size() ); - - // (2-a) Use the orientation around an edge to classify a patch - boost::dynamic_bitset<> is_patch_inside_Q(P_nb_patches, false); - boost::dynamic_bitset<> is_patch_inside_P(Q_nb_patches, false); - boost::dynamic_bitset<> patch_status_not_set_P(P_nb_patches,true); - boost::dynamic_bitset<> patch_status_not_set_Q(Q_nb_patches,true); - boost::dynamic_bitset<> coplanar_patches_of_P(P_nb_patches,false); - boost::dynamic_bitset<> coplanar_patches_of_Q(Q_nb_patches,false); - boost::dynamic_bitset<> coplanar_patches_of_P_for_union_and_intersection(P_nb_patches,false); - boost::dynamic_bitset<> coplanar_patches_of_Q_for_union_and_intersection(Q_nb_patches,false); - - for (typename An_edge_per_polyline_map::const_iterator it=an_edge_per_polyline.begin();it!=an_edge_per_polyline.end();++it) - { - CGAL_assertion(it->second.first.size()==2); - //orientation of faces around the edge (to be sure we can do it) - std::pair indices = it->first; - //const std::pair& polyline_info=it->second.second; - - //get the two halfedges incident to the edge [indices.first,indices.second] - Halfedge_handle first_hedge = it->second.first.find(P_ptr)->second; - Halfedge_handle second_hedge = it->second.first.find(Q_ptr)->second; - - CGAL_assertion(nodes[indices.second]==get(ppmap,first_hedge->vertex())); - CGAL_assertion(nodes[indices.first]==get(ppmap,first_hedge->opposite()->vertex())); - CGAL_assertion(nodes[indices.second]==get(ppmap,second_hedge->vertex())); - CGAL_assertion(nodes[indices.first]==get(ppmap,second_hedge->opposite()->vertex())); - - //different handling depending on the number of incident triangles to the edge. - //After sewing there are two,three or four volumes if there are two,three or four incident triangles respectively - if ( first_hedge->is_border_edge() ){ - if (second_hedge->is_border_edge() ) - { - if ( first_hedge->is_border() != second_hedge->is_border() ) - { - //No restriction at this level - std::size_t patch_id_P = - P_patch_ids[ get( P_facet_id_pmap, first_hedge->is_border() - ? first_hedge->opposite()->facet() - : first_hedge->facet() ) ]; - std::size_t patch_id_Q = - Q_patch_ids[ get( Q_facet_id_pmap, second_hedge->is_border() - ? second_hedge->opposite()->facet() - : second_hedge->facet() ) ]; - patch_status_not_set_P.reset(patch_id_P); - patch_status_not_set_Q.reset(patch_id_Q); - } - else - { - //Nothing allowed - impossible_operation.set(); - return; - } - } - else - { - //Ambiguous, we can do nothing - impossible_operation.set(); - return; - } - } - else - if ( second_hedge->is_border_edge() ) - { - //Ambiguous, we do nothing - impossible_operation.set(); - return; - } - else - { - //Sort the four triangle facets around their common edge - // we suppose that the exterior of the polyhedron is indicated by - // counterclockwise oriented facets. - Vertex_handle P1=first_hedge->opposite()->next()->vertex(); - Vertex_handle P2=first_hedge->next()->vertex(); - // when looking from the side of indices.second, the interior of the first polyhedron is described - // by turning counterclockwise from P1 to P2 - Vertex_handle Q1=second_hedge->opposite()->next()->vertex(); - Vertex_handle Q2=second_hedge->next()->vertex(); - // when looking from the side of indices.second, the interior of the second polyhedron is described - // by turning from Q1 to Q2 - - //check if the third point of each triangular face is an original point (stay -1) - //or a intersection point (in that case we need the index of the corresponding node to - //have the exact value of the point) - int index_p1=get_node_id(first_hedge->opposite()->next()->vertex(),vertex_to_node_id); - int index_p2=get_node_id(first_hedge->next()->vertex(),vertex_to_node_id); - int index_q1=get_node_id(second_hedge->opposite()->next()->vertex(),vertex_to_node_id); - int index_q2=get_node_id(second_hedge->next()->vertex(),vertex_to_node_id); - - std::size_t patch_id_p1=P_patch_ids[ get(P_facet_id_pmap, first_hedge->opposite()->facet()) ]; - std::size_t patch_id_p2=P_patch_ids[ get(P_facet_id_pmap, first_hedge->facet()) ]; - std::size_t patch_id_q1=Q_patch_ids[ get(Q_facet_id_pmap, second_hedge->opposite()->facet()) ]; - std::size_t patch_id_q2=Q_patch_ids[ get(Q_facet_id_pmap, second_hedge->facet()) ]; - - //indicates that patch status will be updated - patch_status_not_set_P.reset(patch_id_p1); - patch_status_not_set_P.reset(patch_id_p2); - patch_status_not_set_Q.reset(patch_id_q1); - patch_status_not_set_Q.reset(patch_id_q2); - -#ifdef CGAL_COREFINEMENT_POLYHEDRA_DEBUG - #warning: Factorize the orientation predicates. -#endif //CGAL_COREFINEMENT_POLYHEDRA_DEBUG - // handle case of coplanar facets - // We choose that a coplanar patch is classified like the other incident patch since they bound the same volume. - if ( are_triangles_coplanar_same_side_filtered(indices.first,indices.second,index_p1,index_q1,P1,Q1,nodes) ) //P1==Q1 - { - coplanar_patches_of_P.set(patch_id_p1); - coplanar_patches_of_Q.set(patch_id_q1); - coplanar_patches_of_P_for_union_and_intersection.set(patch_id_p1); - coplanar_patches_of_Q_for_union_and_intersection.set(patch_id_q1); - - CGAL_assertion( !are_triangles_coplanar_same_side_filtered(indices.first,indices.second,index_p2,index_q2,P2,Q2,nodes) ); - - bool Q2_is_between_P1P2 = OOP::sorted_around_edge_filtered(indices.first,indices.second,index_p1,index_p2,index_q2,P1,P2,Q2,nodes,ppmap); - if ( Q2_is_between_P1P2 ) is_patch_inside_P.set(patch_id_q2); //case 1 - else is_patch_inside_Q.set(patch_id_p2); //case 2 - continue; - } - else{ - if ( are_triangles_coplanar_same_side_filtered(indices.first,indices.second,index_p1,index_q2,P1,Q2,nodes) ) //P1==Q2 - { - CGAL_assertion( index_p1!=index_p2 || index_p1==-1 ); - coplanar_patches_of_P.set(patch_id_p1); - coplanar_patches_of_Q.set(patch_id_q2); - bool Q1_is_between_P1P2 = OOP::sorted_around_edge_filtered(indices.first,indices.second,index_p1,index_p2,index_q1,P1,P2,Q1,nodes,ppmap); - if ( Q1_is_between_P1P2 ) - { // case 3 - is_patch_inside_P.set(patch_id_q1); - is_patch_inside_Q.set(patch_id_p2); - } //else case 4 - continue; - } - else - { - if ( are_triangles_coplanar_same_side_filtered(indices.first,indices.second,index_p2,index_q1,P2,Q1,nodes) ) //P2==Q1 - { - coplanar_patches_of_P.set(patch_id_p2); - coplanar_patches_of_Q.set(patch_id_q1); - bool Q2_is_between_P1P2 = OOP::sorted_around_edge_filtered(indices.first,indices.second,index_p1,index_p2,index_q2,P1,P2,Q2,nodes,ppmap); - if ( Q2_is_between_P1P2 ) - { //case 5 - is_patch_inside_P.set(patch_id_q2); - is_patch_inside_Q.set(patch_id_p1); - } // else case 6 - continue; - } - else{ - if ( are_triangles_coplanar_same_side_filtered(indices.first,indices.second,index_p2,index_q2,P2,Q2,nodes) ) //P2==Q2 - { - coplanar_patches_of_P.set(patch_id_p2); - coplanar_patches_of_Q.set(patch_id_q2); - coplanar_patches_of_P_for_union_and_intersection.set(patch_id_p2); - coplanar_patches_of_Q_for_union_and_intersection.set(patch_id_q2); - bool Q1_is_between_P1P2 = OOP::sorted_around_edge_filtered(indices.first,indices.second,index_p1,index_p2,index_q1,P1,P2,Q1,nodes,ppmap); - if ( Q1_is_between_P1P2 ) is_patch_inside_P.set(patch_id_q1); //case 7 - else is_patch_inside_Q.set(patch_id_p1); //case 8 - continue; - } - } - } - } -#ifdef CGAL_COREFINEMENT_POLYHEDRA_DEBUG - #warning At some point we should have a check if a patch status is already set, what we do is consistant otherwise --> ambiguous -#endif //CGAL_COREFINEMENT_POLYHEDRA_DEBUG - - CGAL_assertion( - ( index_p1 == -1 ? nodes.to_exact(get(ppmap,P1)): nodes.exact_node(index_p1) ) != - ( index_q1 == -1 ? nodes.to_exact(get(ppmap,Q1)): nodes.exact_node(index_q1) ) - && - ( index_p2 == -1 ? nodes.to_exact(get(ppmap,P2)): nodes.exact_node(index_p2) ) != - ( index_q1 == -1 ? nodes.to_exact(get(ppmap,Q1)): nodes.exact_node(index_q1) ) - && - ( index_p1 == -1 ? nodes.to_exact(get(ppmap,P1)): nodes.exact_node(index_p1) ) != - ( index_q2 == -1 ? nodes.to_exact(get(ppmap,Q2)): nodes.exact_node(index_q2) ) - && - ( index_p2 == -1 ? nodes.to_exact(get(ppmap,P2)): nodes.exact_node(index_p2) ) != - ( index_q2 == -1 ? nodes.to_exact(get(ppmap,Q2)): nodes.exact_node(index_q2) ) - ); - - bool Q1_is_between_P1P2 = OOP::sorted_around_edge_filtered(indices.first,indices.second,index_p1,index_p2,index_q1,P1,P2,Q1,nodes,ppmap); - bool Q2_is_between_P1P2 = OOP::sorted_around_edge_filtered(indices.first,indices.second,index_p1,index_p2,index_q2,P1,P2,Q2,nodes,ppmap); - - if ( Q1_is_between_P1P2 ){ - is_patch_inside_P.set(patch_id_q1); - if( Q2_is_between_P1P2 ) - { - is_patch_inside_P.set(patch_id_q2); - bool P1_is_between_Q1Q2 = OOP::sorted_around_edge_filtered(indices.first,indices.second,index_q1,index_q2,index_p1,Q1,Q2,P1,nodes,ppmap); - if (!P1_is_between_Q1Q2){ - // case (a4) - // poly_first - poly_second = P1Q1 U Q2P2 - // poly_second - poly_first = {0} - // poly_first \cap poly_second = Q1Q2 - // opposite( poly_first U poly_second ) = P2P1 - impossible_operation.set(P_MINUS_Q); // P-Q is non-manifold - } - else{ - // case (b4) - // poly_first - poly_second = Q2Q1 - // poly_second - poly_first = P2P1 - // poly_first \cap poly_second = P1Q2 U Q1P2 - // opposite( poly_first U poly_second ) = {O} - is_patch_inside_Q.set(patch_id_p1); - is_patch_inside_Q.set(patch_id_p2); - impossible_operation.set(P_INTER_Q); // P \cap Q is non-manifold - } - } - else - { - //case (c4) - // poly_first - poly_second = P1Q1 - // poly_second - poly_first = P2Q2 - // poly_first \cap poly_second = Q1P2 - // opposite( poly_first U poly_second ) = Q2P1 - if ( is_dangling_edge(indices.first, indices.second, first_hedge, is_node_of_degree_one) || - is_dangling_edge(indices.first, indices.second, second_hedge, is_node_of_degree_one) ) - { - impossible_operation.set(); - return; - } - is_patch_inside_Q.set(patch_id_p2); - } - } - else - { - if( Q2_is_between_P1P2 ) - { - //case (d4) - // poly_first - poly_second = Q2P2 - // poly_second - poly_first = Q1P1 - // poly_first \cap poly_second = P1Q2 - // opposite( poly_first U poly_second ) = P2Q1 - if ( is_dangling_edge(indices.first, indices.second, first_hedge, is_node_of_degree_one) || - is_dangling_edge(indices.first, indices.second, second_hedge, is_node_of_degree_one) ) - { - impossible_operation.set(); - return; - } - is_patch_inside_P.set(patch_id_q2); - is_patch_inside_Q.set(patch_id_p1); - } - else - { - bool P1_is_between_Q1Q2 = OOP::sorted_around_edge_filtered(indices.first,indices.second,index_q1,index_q2,index_p1,Q1,Q2,P1,nodes,ppmap); - if (!P1_is_between_Q1Q2){ - //case (e4) - // poly_first - poly_second = P1P2 - // poly_second - poly_first = Q1Q2 - // poly_first \cap poly_second = {0} - // opposite( poly_first U poly_second ) = P2Q1 U Q2P1 - impossible_operation.set(P_UNION_Q); // P U Q is non-manifold - } - else{ - //case (f4) - is_patch_inside_Q.set(patch_id_p1); - is_patch_inside_Q.set(patch_id_p2); - // poly_first - poly_second = {0} - // poly_second - poly_first = Q1P1 U P2Q2 - // poly_first \cap poly_second = P1P2 - // opposite( poly_first U poly_second ) = Q2Q1 - impossible_operation.set(Q_MINUS_P); // Q - P is non-manifold - } - } - } - } - } - - -/////////////////////////////////////////////// -//////////////////////END////////////////////// -/////////////////////////////////////////////// - - // (2-b) Classify isolated surface patches wrt the other support polyhedron -#ifdef CGAL_COREFINEMENT_POLYHEDRA_DEBUG - #warning this does not work with open polyhedra - #warning this should not be done if we have surfaces with boundaries!!! ask the user a flag? -#endif //CGAL_COREFINEMENT_POLYHEDRA_DEBUG - typedef Side_of_triangle_mesh Inside_poly_test; - -#ifdef CGAL_COREFINEMENT_POLYHEDRA_DEBUG - #warning stop using next_marked_halfedge_around_target_vertex and create lists of halfedges instead? -#endif - - if ( patch_status_not_set_P.any() ) - { - CGAL::Bounded_side in_Q = is_Q_inside_out ? ON_UNBOUNDED_SIDE : ON_BOUNDED_SIDE; - - Inside_poly_test inside_Q(*Q_ptr, ppmap); - for (typename Polyhedron::Face_iterator fit=P_ptr->facets_begin(), - fit_end=P_ptr->facets_end(); - fit!=fit_end; ++fit) - { - std::size_t patch_id=P_patch_ids[ get(P_facet_id_pmap, fit) ]; - if ( patch_status_not_set_P.test( patch_id ) ) - { - patch_status_not_set_P.reset( patch_id ); - CGAL::Bounded_side position = inside_Q( get(ppmap,fit->halfedge()->vertex()) ); - if ( position == in_Q ) - is_patch_inside_Q.set(patch_id); - else - if ( position == ON_BOUNDARY ) - { - if (coplanar_facets_P.test(get(P_facet_id_pmap, fit))) - { - coplanar_patches_of_P.set(patch_id); - coplanar_patches_of_P_for_union_and_intersection.set(patch_id); - } - else - { - Vertex_handle vn = fit->halfedge()->opposite()->vertex(); - Bounded_side other_position = inside_Q( get(ppmap, vn) ); - if (other_position==ON_BOUNDARY) - { - // \todo improve this part which is not robust with a kernel with inexact constructions. - other_position = inside_Q(midpoint(get(ppmap, vn), - get(ppmap, fit->halfedge()->vertex()) )); - } - if ( other_position == in_Q ) - is_patch_inside_Q.set(patch_id); - } - } - - if ( patch_status_not_set_P.none() ) break; - } - } - } - - if ( patch_status_not_set_Q.any() ) - { - CGAL::Bounded_side in_P = is_P_inside_out ? ON_UNBOUNDED_SIDE : ON_BOUNDED_SIDE; - - Inside_poly_test inside_P(*P_ptr, ppmap); - for (typename Polyhedron::Face_iterator fit=Q_ptr->facets_begin(), - fit_end=Q_ptr->facets_end(); - fit!=fit_end; ++fit) - { - std::size_t patch_id=Q_patch_ids[ get(Q_facet_id_pmap, fit) ]; - if ( patch_status_not_set_Q.test( patch_id ) ) - { - patch_status_not_set_Q.reset( patch_id ); - Bounded_side position = inside_P( get(ppmap,fit->halfedge()->vertex()) ); - if ( position == in_P ) - is_patch_inside_P.set(patch_id); - else - if ( position == ON_BOUNDARY ) - { - if (coplanar_facets_Q.test(get(Q_facet_id_pmap, fit))) - { - coplanar_patches_of_Q.set(patch_id); - coplanar_patches_of_Q_for_union_and_intersection.set(patch_id); - } - else - { - Vertex_handle vn = fit->halfedge()->opposite()->vertex(); - Bounded_side other_position = inside_P( get(ppmap, vn) ); - if (other_position==ON_BOUNDARY) - { - // \todo improve this part which is not robust with a kernel with inexact constructions. - other_position = inside_P(midpoint(get(ppmap, vn), - get(ppmap, fit->halfedge()->vertex()) )); - } - if ( other_position == in_P ) - is_patch_inside_P.set(patch_id); - } - } - if ( patch_status_not_set_Q.none() ) break; - } - } - } - - /// \todo There might be some patches for which the status is unknown, we need to use - /// for example the centroid of the facet to decide, but this would require an exact - /// inside-polyhedron object or to work with points at endpoints of the intervals. - /// See SL_TMP_ASSERT - //to maintain a polyhedron halfedge on each polyline + pair - //with first = "is the key (pair) was reversed?" and second is the number of edges +1 in the polyline - //typedef std::map< std::pair, std::pair< std::map,std::pair > > An_edge_per_polyline_map; - -#ifdef CGAL_COREFINEMENT_POLYHEDRA_DEBUG - #warning add a mechanism to handle the patches independantly (for example calculating the volume without building the polyhedron) \ - This can be done by using a functor to which we give the bitset, the polyhedra and one facet per patch? -#endif // CGAL_COREFINEMENT_POLYHEDRA_DEBUG - - #ifdef CGAL_COREFINEMENT_DEBUG - std::cout << "is_patch_inside_Q " << is_patch_inside_Q << "\n"; - std::cout << "is_patch_inside_P " << is_patch_inside_P << "\n"; - std::cout << "coplanar_patches_of_P " << coplanar_patches_of_P << "\n"; - std::cout << "coplanar_patches_of_Q " << coplanar_patches_of_Q << "\n"; - std::cout << "coplanar_patches_of_P_for_union_and_intersection " << coplanar_patches_of_P_for_union_and_intersection << "\n"; - std::cout << "coplanar_patches_of_Q_for_union_and_intersection " << coplanar_patches_of_Q_for_union_and_intersection << "\n"; - std::cout << "Size of patches of P: "; - std::copy(P_patch_sizes.rbegin(), P_patch_sizes.rend(), std::ostream_iterator(std::cout," ") ); - std::cout << "\n"; - std::cout << "Size of patches of Q: "; - std::copy(Q_patch_sizes.rbegin(), Q_patch_sizes.rend(), std::ostream_iterator(std::cout," ") ); - std::cout << "\n"; - #endif - - //backup an halfedge per polyline - std::vector P_polylines, Q_polylines; - std::vector polyline_lengths; - - for (typename An_edge_per_polyline_map::const_iterator - it=an_edge_per_polyline.begin(), - it_end=an_edge_per_polyline.end(); - it!=it_end;++it) - { - const std::pair& polyline_info=it->second.second; - - Halfedge_handle qhedge = it->second.first.find( Q_ptr )->second; - Halfedge_handle phedge = it->second.first.find( P_ptr )->second; - - if( polyline_info.first ){ - phedge=phedge->opposite(); - qhedge=qhedge->opposite(); - } - - P_polylines.push_back(phedge); - Q_polylines.push_back(qhedge); - polyline_lengths.push_back(polyline_info.second+1); - } - - //store the patch description in a container to avoid recomputing it several times - Patch_container patches_of_P( P_ptr, P_patch_ids, P_facet_id_pmap, border_halfedges, P_nb_patches), - patches_of_Q( Q_ptr, Q_patch_ids, Q_facet_id_pmap, border_halfedges, Q_nb_patches); - - // for each boolean operation, define two bitsets of patches contributing - // to the result - std::vector< boost::dynamic_bitset<> > patches_of_P_used(4); - std::vector< boost::dynamic_bitset<> > patches_of_Q_used(4); - - /// handle the bitset for the union - if ( !impossible_operation.test(P_UNION_Q) && desired_output[P_UNION_Q] ) - { - //define patches to import from P - patches_of_P_used[P_UNION_Q] = ~is_patch_inside_Q - coplanar_patches_of_P; - //define patches to import from Q - patches_of_Q_used[P_UNION_Q] = ~is_patch_inside_P - coplanar_patches_of_Q; - //handle coplanar patches - if (coplanar_patches_of_P.any()) - { - if (desired_output[P_UNION_Q]==Q_ptr) - patches_of_Q_used[P_UNION_Q] |= coplanar_patches_of_Q_for_union_and_intersection; - else - patches_of_P_used[P_UNION_Q] |= coplanar_patches_of_P_for_union_and_intersection; - } - } - - /// handle the bitset for the intersection - if ( !impossible_operation.test(P_INTER_Q) && desired_output[P_INTER_Q] ) - { - //define patches to import from P - patches_of_P_used[P_INTER_Q] = is_patch_inside_Q; - //define patches to import from Q - patches_of_Q_used[P_INTER_Q] = is_patch_inside_P; - //handle coplanar patches - if (coplanar_patches_of_P.any()) - { - if (desired_output[P_INTER_Q]==Q_ptr) - patches_of_Q_used[P_INTER_Q] |= coplanar_patches_of_Q_for_union_and_intersection; - else - patches_of_P_used[P_INTER_Q] |= coplanar_patches_of_P_for_union_and_intersection; - } - } - - /// handle the bitset for P-Q - if ( !impossible_operation.test(P_MINUS_Q) && desired_output[P_MINUS_Q] ) - { - //define patches to import from P - patches_of_P_used[P_MINUS_Q] = (~is_patch_inside_Q - coplanar_patches_of_P); - //define patches to import from Q - patches_of_Q_used[P_MINUS_Q] = is_patch_inside_P; - //handle coplanar patches - if (coplanar_patches_of_P.any()) - { - if (desired_output[P_MINUS_Q]==Q_ptr) - patches_of_Q_used[P_MINUS_Q] |= ~coplanar_patches_of_Q_for_union_and_intersection & coplanar_patches_of_Q; - else - patches_of_P_used[P_MINUS_Q] |= ~coplanar_patches_of_P_for_union_and_intersection & coplanar_patches_of_P; - } - } - - /// handle the bitset for Q-P - if ( !impossible_operation.test(Q_MINUS_P) && desired_output[Q_MINUS_P] ) - { - //define patches to import from P - patches_of_P_used[Q_MINUS_P] = is_patch_inside_Q; - //define patches to import from Q - patches_of_Q_used[Q_MINUS_P] = ~is_patch_inside_P - coplanar_patches_of_Q; - //handle coplanar patches - if (coplanar_patches_of_P.any()) - { - if (desired_output[Q_MINUS_P]==Q_ptr) - patches_of_Q_used[Q_MINUS_P] |= ~coplanar_patches_of_Q_for_union_and_intersection & coplanar_patches_of_Q; - else - patches_of_P_used[Q_MINUS_P] |= ~coplanar_patches_of_P_for_union_and_intersection & coplanar_patches_of_P; - } - } - - #ifdef CGAL_COREFINEMENT_DEBUG - std::cout << "patches_of_P_used[P_UNION_Q] " << patches_of_P_used[P_UNION_Q] << "\n"; - std::cout << "patches_of_Q_used[P_UNION_Q] " << patches_of_Q_used[P_UNION_Q] << "\n"; - std::cout << "patches_of_P_used[P_INTER_Q] " << patches_of_P_used[P_INTER_Q] << "\n"; - std::cout << "patches_of_Q_used[P_INTER_Q] " << patches_of_Q_used[P_INTER_Q] << "\n"; - std::cout << "patches_of_P_used[P_MINUS_Q] " << patches_of_P_used[P_MINUS_Q] << "\n"; - std::cout << "patches_of_Q_used[P_MINUS_Q] " << patches_of_Q_used[P_MINUS_Q] << "\n"; - std::cout << "patches_of_P_used[Q_MINUS_P] " << patches_of_P_used[Q_MINUS_P] << "\n"; - std::cout << "patches_of_Q_used[Q_MINUS_P] " << patches_of_Q_used[Q_MINUS_P] << "\n"; - #endif // CGAL_COREFINEMENT_DEBUG - // Schedule the order in which the different boolean operations should be - // done. First operations are those filling polyhedra different - // from P and Q, then the one modifying P and finally the one - // modifying Q. - std::vector out_of_place_operations; - Boolean_operation inplace_operation_P=NONE, inplace_operation_Q=NONE; - for (int i=0;i<4;++i) - { - Boolean_operation operation=enum_cast(i); - - if (!desired_output[operation] || impossible_operation.test(operation)) - continue; - - if (desired_output[operation]==P_ptr) - inplace_operation_P=operation; - else - if (desired_output[operation]==Q_ptr) - inplace_operation_Q=operation; - else - out_of_place_operations.push_back(operation); - } - - /// first handle operations in a polyhedron that is neither P nor Q - BOOST_FOREACH(Boolean_operation operation, out_of_place_operations) - { - Polyhedron* ouput_ptr = *desired_output[operation]; - CGAL_assertion(P_ptr!=ouput_ptr && Q_ptr!=ouput_ptr); - - Intersection_polylines polylines(P_polylines, Q_polylines, polyline_lengths); - // skip the import of polylines only incident to patch(es) - // not used by the current operation - fill_polylines_to_skip( - polylines, P_patch_ids, Q_patch_ids, - patches_of_P_used[operation], patches_of_Q_used[operation], - P_facet_id_pmap, Q_facet_id_pmap - ); - - std::vector shared_halfedges; - fill_new_polyhedron( - *ouput_ptr, - patches_of_P_used[operation], patches_of_Q_used[operation], - patches_of_P, patches_of_Q, - operation == Q_MINUS_P, operation == P_MINUS_Q, - polylines, - border_halfedges, - std::back_inserter(shared_halfedges) - ); - BOOST_FOREACH(Halfedge_handle h, shared_halfedges){ - put(edge_mark_pmap, std::make_pair(h,ouput_ptr),true); - put(edge_mark_pmap, std::make_pair(h->opposite(),ouput_ptr),true); - } - } - - Edge_map disconnected_patches_hedge_to_Qhedge; - - /// handle the operations updating P and/or Q - if ( inplace_operation_P!=NONE ) - { - CGAL_assertion( *desired_output[inplace_operation_P] == P_ptr ); - - if ( inplace_operation_Q!=NONE) - { - // operation in P with removal (and optinally inside-out) delayed - // First backup the border edges of patches to be used - Patch_container tmp_patches_of_P(P_ptr, - patches_of_P.patch_ids, - patches_of_P.facet_id_pmap, - patches_of_P.border_halfedges, - patches_of_P.patches.size()); - boost::dynamic_bitset<> patches_of_P_removed = ~patches_of_P_used[inplace_operation_P]; - for (std::size_t i = patches_of_P_removed.find_first(); - i < patches_of_P_removed.npos; - i = patches_of_P_removed.find_next(i)) - { - // we are only interested by patch border halfedges so - // squeeze the auto-filling mechanism - tmp_patches_of_P.patches[i].is_initialized=true; - tmp_patches_of_P.patches[i].patch_border_halfedges= - patches_of_P[i].patch_border_halfedges; - } - - Intersection_polylines polylines_in_P( - P_polylines, Q_polylines, polyline_lengths); - Intersection_polylines polylines_in_Q=polylines_in_P; - fill_polylines_to_skip( - polylines_in_P, P_patch_ids, Q_patch_ids, - patches_of_P_used[inplace_operation_P], - patches_of_Q_used[inplace_operation_P], - P_facet_id_pmap, Q_facet_id_pmap); - fill_polylines_to_skip( - polylines_in_Q, P_patch_ids, Q_patch_ids, - patches_of_P_used[inplace_operation_Q], - patches_of_Q_used[inplace_operation_Q], - P_facet_id_pmap, Q_facet_id_pmap); - // force the initialization of the patches of P used - // for the operation in Q before P is modified - for (std::size_t i=patches_of_P_used[inplace_operation_Q].find_first(); - i < patches_of_P_used[inplace_operation_Q].npos; - i = patches_of_P_used[inplace_operation_Q].find_next(i)) - { - patches_of_P[i]; - } - // Operation in P: disconnect patches not use and append the one from Q - compute_inplace_operation_delay_removal_and_insideout( - P_ptr, - patches_of_P_used[inplace_operation_P], patches_of_Q_used[inplace_operation_P], - patches_of_P, patches_of_Q, - inplace_operation_P == P_MINUS_Q || inplace_operation_P == Q_MINUS_P, - polylines_in_P, disconnected_patches_hedge_to_Qhedge); - // Operation in Q: discard patches and append the one from Q - CGAL_assertion( *desired_output[inplace_operation_Q] == Q_ptr ); - compute_inplace_operation( Q_ptr, - patches_of_Q_used[inplace_operation_Q], - patches_of_P_used[inplace_operation_Q], - patches_of_Q, patches_of_P, - inplace_operation_Q==P_MINUS_Q, - inplace_operation_Q==Q_MINUS_P, - disconnected_patches_hedge_to_Qhedge); - // remove polylines only on the border of patches not kept in Q - if (polylines_in_Q.to_skip.any()) - remove_unused_polylines(Q_ptr, - ~patches_of_Q_used[inplace_operation_Q], - patches_of_Q); - // now remove patches temporarily kept in P - remove_disconnected_patches(*P_ptr, patches_of_P, patches_of_P_removed); - // remove polylines only on the border of patches not kept in P - if (polylines_in_P.to_skip.any()) - remove_unused_polylines(P_ptr, - ~patches_of_P_used[inplace_operation_P], - tmp_patches_of_P); - // finally reverse orientation of P if needed - if (inplace_operation_P == Q_MINUS_P) - CGAL::Polygon_mesh_processing::reverse_face_orientations(*P_ptr); - } - else{ - /// handle the operation updating only P - CGAL_assertion( *desired_output[inplace_operation_P] == P_ptr ); - Intersection_polylines polylines( - P_polylines, Q_polylines, polyline_lengths); - fill_polylines_to_skip( - polylines, P_patch_ids, Q_patch_ids, - patches_of_P_used[inplace_operation_P], - patches_of_Q_used[inplace_operation_P], - P_facet_id_pmap, Q_facet_id_pmap - ); - - compute_inplace_operation( - P_ptr, - patches_of_P_used[inplace_operation_P], - patches_of_Q_used[inplace_operation_P], - patches_of_P, patches_of_Q, - inplace_operation_P == Q_MINUS_P, - inplace_operation_P == P_MINUS_Q, - polylines - ); - // remove polylines only on the border of patches not kept - if (polylines.to_skip.any()) - remove_unused_polylines(P_ptr, - ~patches_of_P_used[inplace_operation_P], - patches_of_P); - } - } - else - if ( inplace_operation_Q!=NONE ) - { - /// handle the operation updating only Q - CGAL_assertion( *desired_output[inplace_operation_Q] == Q_ptr ); - Intersection_polylines polylines( - Q_polylines, P_polylines, polyline_lengths); - fill_polylines_to_skip( - polylines, Q_patch_ids, P_patch_ids, - patches_of_Q_used[inplace_operation_Q], - patches_of_P_used[inplace_operation_Q], - Q_facet_id_pmap, P_facet_id_pmap - ); - - compute_inplace_operation( Q_ptr, - patches_of_Q_used[inplace_operation_Q], - patches_of_P_used[inplace_operation_Q], - patches_of_Q, patches_of_P, - inplace_operation_Q==P_MINUS_Q, - inplace_operation_Q==Q_MINUS_P, - polylines); - - // remove polylines only on the border of patches not kept - if (polylines.to_skip.any()) - remove_unused_polylines(Q_ptr, - ~patches_of_Q_used[inplace_operation_Q], - patches_of_Q); - } - } -}; - -} } // end of namespace CGAL::Corefinement - -#include - -#endif // CGAL_INTERNAL_COREFINEMENT_POLYHEDRA_OUTPUT_BUILDER_H diff --git a/Operations_on_polyhedra/include/CGAL/internal/corefinement/Polyhedron_constness_types.h b/Operations_on_polyhedra/include/CGAL/internal/corefinement/Polyhedron_constness_types.h deleted file mode 100644 index 9c76535af3a..00000000000 --- a/Operations_on_polyhedra/include/CGAL/internal/corefinement/Polyhedron_constness_types.h +++ /dev/null @@ -1,83 +0,0 @@ -// Copyright (c) 2011 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_INTERNAL_POLYHEDRON_CONSTNESS_TYPES_H -#define CGAL_INTERNAL_POLYHEDRON_CONSTNESS_TYPES_H - -#include - - -namespace CGAL { -namespace internal_IOP{ - -template -struct Polyhedron_types; - -template -struct Polyhedron_types{ - typedef Polyhedron& Polyhedron_ref; - typedef typename Polyhedron::Halfedge_handle Halfedge_handle; - typedef typename Polyhedron::Halfedge_iterator Halfedge_iterator; - typedef typename Polyhedron::Facet_iterator Facet_iterator; - typedef typename Polyhedron::Facet_handle Facet_handle; - typedef typename Polyhedron::Vertex_handle Vertex_handle; - typedef typename Polyhedron::Vertex Vertex; - typedef typename Polyhedron::Halfedge Halfedge; - typedef typename Polyhedron::Facet Facet; -}; - -template -struct Polyhedron_types{ - typedef const Polyhedron& Polyhedron_ref; - typedef typename Polyhedron::Halfedge_const_handle Halfedge_handle; - typedef typename Polyhedron::Halfedge_const_iterator Halfedge_iterator; - typedef typename Polyhedron::Facet_const_iterator Facet_iterator; - typedef typename Polyhedron::Facet_const_handle Facet_handle; - typedef typename Polyhedron::Vertex_const_handle Vertex_handle; - typedef const typename Polyhedron::Vertex Vertex; - typedef const typename Polyhedron::Halfedge Halfedge; - typedef const typename Polyhedron::Facet Facet; -}; - -#include -#include -#include - -template -struct Polyhedron_types_with_mpl -{ - typedef typename boost::remove_const::type Polyhedron; - typedef typename boost::mpl::if_< boost::is_const, - typename Polyhedron::Face_const_handle, - typename Polyhedron::Face_handle>::type Face_handle; - typedef typename boost::mpl::if_< boost::is_const, - typename Polyhedron::Face_const_iterator, - typename Polyhedron::Face_iterator>::type Face_iterator; - typedef typename boost::mpl::if_< boost::is_const, - typename Polyhedron::Halfedge_const_handle, - typename Polyhedron::Halfedge_handle>::type Halfedge_handle; - typedef Face_handle Facet_handle; - typedef Face_iterator Facet_iterator; -}; - -} } //namespace CGAL::internal_IOP - -#endif //CGAL_INTERNAL_POLYHEDRON_CONSTNESS_TYPES_H diff --git a/Operations_on_polyhedra/include/CGAL/internal/corefinement/connected_components.h b/Operations_on_polyhedra/include/CGAL/internal/corefinement/connected_components.h deleted file mode 100644 index 324e6e027e8..00000000000 --- a/Operations_on_polyhedra/include/CGAL/internal/corefinement/connected_components.h +++ /dev/null @@ -1,344 +0,0 @@ -// Copyright (c) 2011, 2015 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 and Andreas Fabri - -#ifndef CGAL_INTERNAL_POLYHEDRON_SUBSET_EXTRACTION_H -#define CGAL_INTERNAL_POLYHEDRON_SUBSET_EXTRACTION_H - -#include - - -#include -#include - -#include - -#include -#include - -#include -#include -#include -#include -#include - -namespace CGAL { - namespace internal{ - namespace corefinement{ - -template -struct Compare_handle_ptr{ - typedef typename Polyhedron::Facet_const_handle Facet_const_handle; - typedef typename Polyhedron::Vertex_const_handle Vertex_const_handle; - - bool operator()(Facet_const_handle f1,Facet_const_handle f2) const { - return &(*f1) < &(*f2); - } - - bool operator()(Vertex_const_handle v1,Vertex_const_handle v2) const { - return &(*v1) < &(*v2); - } -}; - -struct Dummy_true{ - template - bool operator()(T) const {return true;} -}; - -template -class Build_polyhedron_subset : public ::CGAL::Modifier_base { - typedef typename Polyhedron::Facet_const_handle Facet_const_handle; - typedef typename Polyhedron::Vertex_const_handle Vertex_const_handle; - typedef typename Polyhedron::Halfedge_const_handle Halfedge_const_handle; - - typedef typename HDS::Vertex::Point Point; - std::list points; - std::list< std::vector > facets; - - template - typename Polyhedron::Halfedge_const_handle get_facet_halfedge(Facet_iterator facet_it) const - { - return (*facet_it)->halfedge(); - } - - typename Polyhedron::Halfedge_const_handle get_facet_halfedge(typename Polyhedron::Facet_const_handle facet) const - { - return facet->halfedge(); - } - -public: - template - Build_polyhedron_subset(const Polyhedron&,Facets_const_iterator begin,Facets_const_iterator end) - { - typedef std::map > Vertices; - Vertices vertices; - unsigned int index=0; - //get vertices and get face description relatively to the restricted set of vertices - for (Facets_const_iterator it=begin;it!=end;++it) - { - Halfedge_const_handle start=get_facet_halfedge(it); - Halfedge_const_handle curr=start; - facets.push_back(std::vector()); - std::vector& indices = facets.back(); - do{ - bool is_new_vertex; - typename Vertices::iterator it_vertex; - ::CGAL::cpp11::tie(it_vertex,is_new_vertex)=vertices.insert(std::make_pair(curr->vertex(),index)); - if (is_new_vertex) { - ++index; - points.push_back(curr->vertex()); - } - indices.push_back(it_vertex->second); - curr=curr->next(); - }while(curr!=start); - } - } - - void operator()( HDS& hds) { - ::CGAL::Polyhedron_incremental_builder_3 B( hds, true); - B.begin_surface( points.size(), facets.size() ); - for (typename std::list::iterator it=points.begin();it!=points.end();++it) - B.add_vertex((*it)->point()); - for (typename std::list< std::vector >::iterator - it=facets.begin();it!=facets.end();++it) - { - B.begin_facet(); - for (std::vector::iterator it_i=it->begin();it_i!=it->end();++it_i) - B.add_vertex_to_facet(*it_i); - B.end_facet(); - } - B.end_surface(); - } -}; - - - -template -void extract_connected_components( - Polyhedron& P, - const Adjacency_criterium& adjacent, - CGAL::Union_find::Facet_handle>& uf, - Face_to_UF_handle_map& map_f2h, - Result& result - ) -{ - typedef typename internal_IOP::Polyhedron_types_with_mpl::Facet_handle Facet_handle; - typedef typename internal_IOP::Polyhedron_types_with_mpl::Facet_iterator Facet_iterator; - typedef typename internal_IOP::Polyhedron_types_with_mpl::Halfedge_handle Halfedge_handle; - typedef ::CGAL::Union_find UF; - typedef typename UF::handle UF_handle; - typedef typename UF::iterator UF_iterator; - -//init union-find: each facet is in its own set - for (Facet_iterator it=P.facets_begin();it!=P.facets_end();++it){ - map_f2h.insert(std::make_pair(it,uf.make_set(it))); - } -//merge 2 facets if they share a common edge - for (Facet_iterator it=P.facets_begin();it!=P.facets_end();++it){ - Facet_handle facet=it; - - UF_handle current=map_f2h.find(it)->second; - std::vector neighbors; - Halfedge_handle hedge=facet->halfedge(); - do - { - neighbors.push_back( hedge->opposite() ); - hedge=hedge->next(); - } - while(hedge!=facet->halfedge()); - - std::size_t nb_edges=neighbors.size(); - for (std::size_t i=0;iis_border() ) continue; - UF_handle neigh=map_f2h.find(neighbors[i]->facet())->second; - if ( adjacent(neighbors[i]) && !uf.same_set(current,neigh) ){ - uf.unify_sets(current,neigh); - } - } - } - -//recover merged sets - for (UF_iterator it=uf.begin();it!=uf.end();++it){ - UF_handle master=uf.find(it); - result[*master].push_back(*it); - } -} - -template -void extract_connected_components(const Polyhedron& P,const Adjacency_criterium& adjacent,Output_iterator out) -{ - typedef typename Polyhedron::Facet_const_handle Facet_const_handle; - typedef ::CGAL::Union_find UF; - typedef typename UF::handle UF_handle; - typedef std::map,Compare_handle_ptr > Result; - typedef std::map > Facet_to_handle_map; - - UF uf; - Facet_to_handle_map map_f2h; - Result result; - - extract_connected_components(P,adjacent,uf,map_f2h,result); - - for (typename Result::iterator it=result.begin();it!=result.end();++it) - { - typedef std::list Facets; - const Facets& facets=it->second; - Polyhedron new_poly; - Build_polyhedron_subset modifier(new_poly,facets.begin(),facets.end()); - new_poly.delegate(modifier); - *out++=new_poly; - } -} - -template -void mark_connected_components(Polyhedron& P, const Adjacency_criterium& adjacent, Face_marker& face_marker) -{ - typedef typename Polyhedron::Facet_handle Facet_handle; - typedef ::CGAL::Union_find UF; - typedef typename UF::handle UF_handle; - typedef std::map,Compare_handle_ptr > Result; - typedef std::map > Facet_to_handle_map; - - UF uf; - Facet_to_handle_map map_f2h; - Result result; - - extract_connected_components(P,adjacent,uf,map_f2h,result); - - for (typename Result::iterator it=result.begin();it!=result.end();++it) - { - face_marker.start_new_connected_component(); - typedef std::list Facets; - const Facets& facets=it->second; - face_marker.mark(facets.begin(),facets.end()); - } -} - -template -OutputIterator -mark_connected_components(Polyhedron& P, const Adjacency_criterium& adjacent, Face_marker& face_marker, OutputIterator out) -{ - typedef typename Polyhedron::Facet_handle Facet_handle; - typedef ::CGAL::Union_find UF; - typedef typename UF::handle UF_handle; - typedef std::map,Compare_handle_ptr > Result; - typedef std::map > Facet_to_handle_map; - - UF uf; - Facet_to_handle_map map_f2h; - Result result; - - extract_connected_components(P,adjacent,uf,map_f2h,result); - - for (typename Result::iterator it=result.begin();it!=result.end();++it) - { - face_marker.start_new_connected_component(); - typedef std::list Facets; - const Facets& facets=it->second; - face_marker.mark(facets.begin(),facets.end()); - *out++=*facets.begin(); - } - return out; -} - -template -void extract_connected_components(const Polyhedron& P,Output_iterator out) -{ - extract_connected_components(P,Dummy_true(),out); -} - -template -std::size_t -init_facet_indices( - const Polyhedron& P, - Polyhedron_facet_index_map facet_index_map) -{ - //init facet indices - std::size_t index=0; - for (typename Polyhedron::Facet_const_iterator fit=P.facets_begin(), - fit_end=P.facets_end(); - fit!=fit_end; ++fit) - { - put(facet_index_map, fit, index++); - } - return index; -} - -// alternative method by propagation -template -std::size_t -mark_connected_components_v2( - const Polyhedron& P, - const Adjacency_criterium& adjacent, - Polyhedron_facet_index_map facet_index_map, - std::vector& patch_ids, - std::vector& patch_sizes) -{ - typedef typename Polyhedron::Halfedge_const_handle Halfedge_handle; - - std::size_t max_id=(std::numeric_limits::max)(); - patch_ids.clear(); - patch_ids.resize(P.size_of_facets(), max_id); - - //traversal of the facets to discover connected components - std::size_t patch_id=0; - for (typename Polyhedron::Facet_const_iterator fit=P.facets_begin(), - fit_end=P.facets_end(); - fit!=fit_end; ++fit) - { - std::size_t index=get(facet_index_map, fit); - if ( patch_ids[index]==max_id ) - { - patch_sizes.push_back(0); - patch_ids[index]=patch_id;// set patch id - ++(patch_sizes.back()); - std::vector queue; - if ( adjacent(fit->halfedge()) ) - queue.push_back( fit->halfedge()->opposite() ); - if ( adjacent(fit->halfedge()->next()) ) - queue.push_back( fit->halfedge()->next()->opposite() ); - if ( adjacent(fit->halfedge()->next()->next()) ) - queue.push_back( fit->halfedge()->next()->next()->opposite() ); - while (!queue.empty()) - { - Halfedge_handle h=queue.back(); - queue.pop_back(); - index=get(facet_index_map, h->facet()); - if ( patch_ids[index]!=max_id ) continue; - patch_ids[index]=patch_id; - ++(patch_sizes.back()); - if ( adjacent(h->next()) ) - queue.push_back( h->next()->opposite() ); - if ( adjacent(h->next()->next()) ) - queue.push_back( h->next()->next()->opposite() ); - } - ++patch_id; - } - } - - return patch_id; -} - -} } // end of namespace internal::corefinement - - -} // namespace CGAL - -#endif //CGAL_INTERNAL_POLYHEDRON_SUBSET_EXTRACTION_H diff --git a/Operations_on_polyhedra/include/CGAL/internal/corefinement/intersection_coplanar_triangles_3.h b/Operations_on_polyhedra/include/CGAL/internal/corefinement/intersection_coplanar_triangles_3.h deleted file mode 100644 index eb4136ef265..00000000000 --- a/Operations_on_polyhedra/include/CGAL/internal/corefinement/intersection_coplanar_triangles_3.h +++ /dev/null @@ -1,334 +0,0 @@ -// Copyright (c) 2011 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_INTERNAL_INTERSECTION_COPLANAR_TRIANGLES_3_H -#define CGAL_INTERNAL_INTERSECTION_COPLANAR_TRIANGLES_3_H - -#include - - -#include //for Intersection_type -#include -#include -#include - -//TODO rename this file when doing proper integration -namespace CGAL{ -namespace internal_IOP{ - - -//intersection point of two coplanar triangles that keeps track of -//the location of that point onto the triangles. -template -struct Intersection_point_with_info -{ - typedef CGAL::Exact_predicates_exact_constructions_kernel Exact_kernel; - typedef IK Input_kernel; - typedef CGAL::Cartesian_converter Converter; - - Intersection_type type_1,type_2; //intersection type for 1st and 2nd facets - Halfedge_handle info_1,info_2; //halfedge providing primitive indicated by type_1 and type_2 - typename Exact_kernel::Point_3 point; //the geometric embedding of the intersection - PolyhedronPointPMap ppmap; // extract the point from a vertex. Only needed to be stored in the class for is_valid - - //constructor from a vertex of first triangle initialized inside the second triangle - Intersection_point_with_info(Halfedge_handle info1,Halfedge_handle info2, PolyhedronPointPMap ppmap): - type_1(VERTEX),type_2(FACET),info_1(info1),info_2(info2),ppmap(ppmap) - { - Converter converter; - point=converter(get(ppmap,info_1->vertex())); - } - - //constructor for intersection of edges. prev and curr are two points on an edge of the first facet (preserving the - //orientation of the facet). This edge is intersected by info2 from the second facet. - // - //The rational is the following: we first check whether curr and prev are on the same edge. I so we create - //an intersection point between two edges. Otherwise, the point is a vertex of the second facet included into - //the first facet. - // - //(V,F) : point initialy constructed - //(V,E) : (V,F) updated by get_orientation_and_update_info_2 (i.e lies on one edge) - //(V,V) : (V,E) updated by get_orientation_and_update_info_2 (i.e lies on two edges) - //(E,E) : created in the following function when prev and curr lie on the same edge - //(E,V) : (E,E) updated by get_orientation_and_update_info_2 (always done as lies on two edges) - //(E,F) : impossible - //(F,V) : detected when curr and prev and not on the same edge - //(F,E) : impossible - //(F,F) : impossible - // - Intersection_point_with_info(Intersection_point_with_info prev,Intersection_point_with_info curr, - Halfedge_handle info1,Halfedge_handle info2, PolyhedronPointPMap ppmap): - type_2(EDGE),info_2(info2),ppmap(ppmap) - { - #ifdef CGAL_DEBUG_COPLANAR_TRIANGLE_INTERSECTION - std::cout << "prev: "; prev.print_debug(); - std::cout << "curr: "; curr.print_debug(); std::cout << std::endl; - #endif //CGAL_DEBUG_COPLANAR_TRIANGLE_INTERSECTION - Converter converter; - if (prev.type_1==VERTEX && prev.info_1->next() == curr.info_1){ - CGAL_assertion(curr.type_1!=FACET); - type_1=EDGE; - info_1=curr.info_1; - } - else{ - if(curr.type_1==VERTEX && prev.info_1 == curr.info_1){ - CGAL_assertion(prev.type_1!=FACET); - type_1=EDGE; - info_1=curr.info_1; - } - else{ - if (curr.type_1==EDGE && prev.type_1==EDGE && curr.info_1==prev.info_1){ - type_1=EDGE; - info_1=curr.info_1; - } - else{ - //curr and prev are not on the same edge of the first facet. - //The intersection point to be computed is a VERTEX of the second facet - type_1=FACET; - info_1=info1; - type_2=VERTEX; - - //this is used to select the correct endpoint of the edge of the second facet - typename Exact_kernel::Collinear_3 is_collinear = Exact_kernel().collinear_3_object(); - if ( !is_collinear(prev.point,curr.point,converter(get(ppmap,info_2->vertex()) ) ) ){ - info_2=info_2->next()->next(); - CGAL_assertion( is_collinear(prev.point,curr.point,converter(get(ppmap,info_2->vertex())) ) ); - } - point = converter( get(ppmap, info_2->vertex()) ); - return; - } - } - } - - //handle degenerate case when two edges overlap - //at least one of the two vertex has already been found as a vertex of a facet. Here we set it for the second point - if(prev.type_2!=FACET && curr.type_2!=FACET && (prev.type_1==VERTEX || prev.type_2==VERTEX) && (curr.type_1==VERTEX || curr.type_2==VERTEX)){ - typename Exact_kernel::Collinear_3 is_collinear = Exact_kernel().collinear_3_object(); - if ( is_collinear(prev.point,curr.point,converter(get(ppmap, info_2->opposite()->vertex())) ) ){ - info_2=info_2->next()->next(); - type_2=VERTEX; - point = converter( get(ppmap, info_2->vertex()) ); - return; - } - if ( is_collinear(prev.point,curr.point,converter(get(ppmap, info_2->vertex())) ) ){ - type_2=VERTEX; - point = converter( get(ppmap, info_2->vertex()) ); - return; - } - } - - //handle regular intersection of two edges - typename Exact_kernel::Construct_line_3 line_3=Exact_kernel().construct_line_3_object(); - typename Exact_kernel::Line_3 l1= - line_3(converter(get(ppmap, info_2->vertex())),converter(get(ppmap, info_2->opposite()->vertex()))); - typename Exact_kernel::Line_3 l2=line_3(prev.point,curr.point); - CGAL::Object res=Exact_kernel().intersect_3_object()(l1,l2); - const typename Exact_kernel::Point_3* ptptr=CGAL::object_cast(&res); - CGAL_assertion(ptptr!=NULL); - point=*ptptr; - } - - void print_debug() const{ - std::cout << " ("; - if (type_1==VERTEX) std::cout << "V"; - if (type_1==EDGE) std::cout << "E"; - if (type_1==FACET) std::cout << "F"; - if (type_1==EMPTY) std::cout << "?"; - std::cout << "-" << &(*info_1); - std::cout << ";"; - if (type_2==VERTEX) std::cout << "V"; - if (type_2==EDGE) std::cout << "E"; - if (type_2==FACET) std::cout << "F"; - if (type_2==EMPTY) std::cout << "?"; - std::cout << ")" << "[" << CGAL::to_double(point.x()) << "," << CGAL::to_double(point.y()) << "," << CGAL::to_double(point.z()) << "]"; - } - - int debug_unique_type_int() const{ - int res=0; - switch (type_1){ - case VERTEX: res+=1; break; - case EDGE: res+=3; break; - case FACET: res+=7; break; - default: break; - } - switch (type_2){ - case VERTEX: res+=1; break; - case EDGE: res+=3; break; - case FACET: res+=7; break; - default: break; - } - return res; - } - - bool is_valid(Intersection_type type,Halfedge_handle info){ - bool valid=true; - Converter converter; - switch (type){ - case VERTEX: valid&= converter(get(ppmap, info->vertex()))==point; break; - case EDGE:{ - typename Exact_kernel::Segment_3 seg= - Exact_kernel().construct_segment_3_object()( converter(get(ppmap,info->vertex())), - converter(get(ppmap,info->opposite()->vertex())) ); - valid&= Exact_kernel().has_on_3_object()(seg,point); - } - break; - case FACET:{ - typename Exact_kernel::Coplanar_orientation_3 orient=Exact_kernel().coplanar_orientation_3_object(); - typename Exact_kernel::Point_3 p=converter(get(ppmap,info->vertex())); - typename Exact_kernel::Point_3 q=converter(get(ppmap,info->next()->vertex())); - typename Exact_kernel::Point_3 r=converter(get(ppmap,info->opposite()->vertex())); - valid &= orient(p,q,r,point)==POSITIVE; - valid &= orient(q,r,p,point)==POSITIVE; - valid &= orient(r,p,q,point)==POSITIVE; - } - break; - default: valid=false; - } - return valid; - } - - bool is_valid(){ - return is_valid(type_1,info_1) && is_valid(type_2,info_2); - } - -}; - -template -CGAL::Orientation get_orientation_and_update_info_2(Halfedge_handle h,Inter_pt& p, PolyhedronPointPMap ppmap) -{ - typename Inter_pt::Exact_kernel::Coplanar_orientation_3 orient= - typename Inter_pt::Exact_kernel().coplanar_orientation_3_object(); - typename Inter_pt::Converter converter; - - CGAL::Orientation res = orient(converter(get(ppmap,h->opposite()->vertex())), - converter(get(ppmap,h->vertex())), - converter(get(ppmap,h->next()->vertex())), - p.point); - - if ( (p.type_1==VERTEX || p.type_1==EDGE) && res==COLLINEAR){ - if (p.type_2==FACET){ //detect a case (VERTEX,EDGE) - p.type_2=EDGE; - p.info_2=h; - } - else{ - //detect a case (VERTEX,VERTEX) or (EDGE,VERTEX) - CGAL_assertion(p.type_2==EDGE); - p.type_2=VERTEX; - if (p.info_2->next()!=h){ - CGAL_assertion(h->next()==p.info_2); - p.info_2=h; - } - } - } - - return res; -} - -template -void intersection_coplanar_facets_cutoff(Facet_handle f,std::list& inter_pts,Facet_handle other,PolyhedronPointPMap ppmap) -{ - #ifdef CGAL_DEBUG_COPLANAR_TRIANGLE_INTERSECTION - std::cout << "cutoff: " << f->opposite()->vertex()->point() << " " << f->vertex()->point() << std::endl; - #endif //CGAL_DEBUG_COPLANAR_TRIANGLE_INTERSECTION - if ( inter_pts.empty() ) return; - typedef typename std::list::iterator Iterator; - - std::map orientations; - for (Iterator it=inter_pts.begin();it!=inter_pts.end();++it){ - #ifdef CGAL_DEBUG_COPLANAR_TRIANGLE_INTERSECTION - it->print_debug(); - #endif //CGAL_DEBUG_COPLANAR_TRIANGLE_INTERSECTION - orientations[ &(*it) ]=get_orientation_and_update_info_2(f,*it,ppmap); - } - #ifdef CGAL_DEBUG_COPLANAR_TRIANGLE_INTERSECTION - std::cout << std::endl; - #endif //CGAL_DEBUG_COPLANAR_TRIANGLE_INTERSECTION - - int pt_added=0; - - Inter_pt* prev = &(*boost::prior(inter_pts.end())); - bool inter_pts_size_g_2 = inter_pts.size() > 2; - Iterator stop = inter_pts_size_g_2 ? inter_pts.end() : boost::prior(inter_pts.end()); - for (Iterator it=inter_pts.begin();it!=stop;++it) - { - Inter_pt* curr=&(*it); - if (!inter_pts_size_g_2) std::swap(prev,curr); - Orientation or_prev=orientations[prev],or_curr=orientations[curr]; - if ( (or_prev==POSITIVE && or_curr==NEGATIVE) || (or_prev==NEGATIVE && or_curr==POSITIVE) ) - { - Iterator it_curr = inter_pts_size_g_2 ? it:boost::next(it); - prev=&(* inter_pts.insert( it_curr,Inter_pt(*prev,*curr,other,f,ppmap) ) ); - orientations[prev]=COLLINEAR; - ++pt_added; - } - prev=&(*it); - } - - CGAL_kernel_assertion(pt_added<3); - Iterator it=inter_pts.begin(); - std::size_t nb_interpt=inter_pts.size(); - //this boolean allows to reverse order of intersection points in case there were 3 remaining intersection points - //and the point in the middle was removed. In that case the order must be reversed to preserve the orientations - //of the last edge: - // A---X---B --> AB to be consistent with the other cases this should be BA! - // X---B---A --> BA - // B---A---X --> BA - // - bool should_revert_list=false; - - while(it!=inter_pts.end()) - { - if (orientations[&(*it)]==NEGATIVE){ - inter_pts.erase(it++); - if (--nb_interpt == 2 && it!=inter_pts.end() && boost::next(it)==inter_pts.end()) should_revert_list=true; - } - else - ++it; - } - if (should_revert_list && nb_interpt==2) inter_pts.reverse(); -} - -template -void -intersection_coplanar_facets( - Halfedge_handle f1, - Halfedge_handle f2, - PolyhedronPointPMap ppmap, - std::list >& output ) -{ - typedef Intersection_point_with_info Inter_pt; - output.push_back( Inter_pt(f1,f2,ppmap) ); - output.push_back( Inter_pt(f1->next(),f2,ppmap) ); - output.push_back( Inter_pt(f1->next()->next(),f2,ppmap) ); - - //intersect f2 with the three half planes which intersection defines f1 - intersection_coplanar_facets_cutoff(f2,output,f1,ppmap); - intersection_coplanar_facets_cutoff(f2->next(),output,f1,ppmap); - intersection_coplanar_facets_cutoff(f2->next()->next(),output,f1,ppmap); -} - - - -} } //namespace CGAL::internal_IOP - - -#endif //CGAL_INTERNAL_INTERSECTION_COPLANAR_TRIANGLES_3_H - diff --git a/Operations_on_polyhedra/include/CGAL/internal/corefinement/intersection_triangle_segment_3.h b/Operations_on_polyhedra/include/CGAL/internal/corefinement/intersection_triangle_segment_3.h deleted file mode 100644 index 1a892a9fab0..00000000000 --- a/Operations_on_polyhedra/include/CGAL/internal/corefinement/intersection_triangle_segment_3.h +++ /dev/null @@ -1,172 +0,0 @@ -// Copyright (c) 2011 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_INTERNAL_INTERSECTION_TRIANGLE_SEGMENT_3_H -#define CGAL_INTERNAL_INTERSECTION_TRIANGLE_SEGMENT_3_H - -#include - - -//TODO rename this file when doing proper integration -#include -#include -namespace CGAL{ -namespace internal_IOP{ - -enum Intersection_type {FACET,EDGE,VERTEX,EMPTY,COPLNR}; - - -//define shortcut for intersection result types -template -struct Intersection_types{ - typedef typename Polyhedron_types::Halfedge_handle Intersection_info; - typedef cpp11::tuple Intersection_result; -}; - - -template -typename Intersection_types::Intersection_result -find_intersection(const typename Kernel::Point_3& p, const typename Kernel::Point_3& q, - const typename Kernel::Point_3& a, const typename Kernel::Point_3& b, const typename Kernel::Point_3& c, - typename Polyhedron_types::Halfedge_handle /*hh*/, - typename Polyhedron_types::Facet_handle fh, - bool is_vertex_coplanar=false,bool is_vertex_opposite_coplanar=false) -{ - typedef typename Intersection_types::Intersection_info Intersection_info; - typedef typename Intersection_types::Intersection_result Intersection_result; - - Orientation ab=orientation(p,q,a,b); - Orientation bc=orientation(p,q,b,c); - Orientation ca=orientation(p,q,c,a); - - if ( ab==POSITIVE || bc==POSITIVE || ca==POSITIVE ) - return Intersection_result(EMPTY,Intersection_info(),false,false); - - int nb_coplanar=(ab==COPLANAR?1:0) + (bc==COPLANAR?1:0) + (ca==COPLANAR?1:0); - - if ( nb_coplanar==0 ) - return cpp11::make_tuple(FACET,Intersection_info(fh->halfedge()),is_vertex_coplanar,is_vertex_opposite_coplanar); - - if (nb_coplanar==1){ - if (ab==COPLANAR) - return cpp11::make_tuple(EDGE,Intersection_info(fh->halfedge()->next()),is_vertex_coplanar,is_vertex_opposite_coplanar); - if (bc==COPLANAR) - return cpp11::make_tuple(EDGE,Intersection_info(fh->halfedge()->next()->next()),is_vertex_coplanar,is_vertex_opposite_coplanar); - CGAL_assertion(ca==COPLANAR); - return cpp11::make_tuple(EDGE,Intersection_info(fh->halfedge()),is_vertex_coplanar,is_vertex_opposite_coplanar); - } - - CGAL_assertion(nb_coplanar==2); - - if (ab!=COPLANAR) - return cpp11::make_tuple(VERTEX,Intersection_info(fh->halfedge()->next()->next()),is_vertex_coplanar,is_vertex_opposite_coplanar); - if (bc!=COPLANAR) - return cpp11::make_tuple(VERTEX,Intersection_info(fh->halfedge()),is_vertex_coplanar,is_vertex_opposite_coplanar); - CGAL_assertion(ca!=COPLANAR); - return cpp11::make_tuple(VERTEX,Intersection_info(fh->halfedge()->next()),is_vertex_coplanar,is_vertex_opposite_coplanar); -} - - -template -typename Intersection_types::Intersection_result -do_intersect(typename Polyhedron_types::Halfedge_handle hh, - typename Polyhedron_types::Facet_handle fh, - PolyhedronPointPmap ppmap) -{ - typedef typename Intersection_types::Intersection_info Intersection_info; - typedef typename Intersection_types::Intersection_result Intersection_result; - - const typename Kernel::Point_3 & a = get(ppmap, fh->halfedge()->vertex() ); - const typename Kernel::Point_3 & b = get(ppmap, fh->halfedge()->next()->vertex() ); - const typename Kernel::Point_3 & c = get(ppmap, fh->halfedge()->next()->next()->vertex() ); - const typename Kernel::Point_3 & p = get(ppmap, hh->vertex() ); - const typename Kernel::Point_3 & q = get(ppmap, hh->opposite()->vertex() ); - - - const Orientation abcp = orientation(a,b,c,p); - const Orientation abcq = orientation(a,b,c,q); - - - switch ( abcp ) { - case POSITIVE: - switch ( abcq ) { - case POSITIVE: - // the segment lies in the positive open halfspaces defined by the - // triangle's supporting plane - return Intersection_result(EMPTY,Intersection_info(),false,false); - case NEGATIVE: - // p sees the triangle in counterclockwise order - return find_intersection(p,q,a,b,c,hh,fh); - case COPLANAR: - // q belongs to the triangle's supporting plane - // p sees the triangle in counterclockwise order - return find_intersection(p,q,a,b,c,hh,fh,false,true); - - default: // should not happen. - CGAL_assertion(false); - return Intersection_result(EMPTY,Intersection_info(),false,false); - } - case NEGATIVE: - switch ( abcq ) { - case POSITIVE: - // q sees the triangle in counterclockwise order - return find_intersection(q,p,a,b,c,hh,fh); - case NEGATIVE: - // the segment lies in the negative open halfspaces defined by the - // triangle's supporting plane - return Intersection_result(EMPTY,Intersection_info(),false,false); - case COPLANAR: - // q belongs to the triangle's supporting plane - // p sees the triangle in clockwise order - return find_intersection(q,p,a,b,c,hh,fh,false,true); - default: // should not happen. - CGAL_assertion(false); - return Intersection_result(EMPTY,Intersection_info(),false,false); - } - case COPLANAR: // p belongs to the triangle's supporting plane - switch ( abcq ) { - case POSITIVE: - // q sees the triangle in counterclockwise order - return find_intersection(q,p,a,b,c,hh,fh,true,false); - case NEGATIVE: - // q sees the triangle in clockwise order - return find_intersection(p,q,a,b,c,hh,fh,true,false); - case COPLANAR: - // the segment is coplanar with the triangle's supporting plane - // we test whether the segment intersects the triangle in the common - // supporting plane - if ( ::CGAL::internal::do_intersect_coplanar(a,b,c,p,q,Kernel()) ) - return Intersection_result(COPLNR,Intersection_info(),true,true); - return Intersection_result(EMPTY,Intersection_info(),true,true); - - default: // should not happen. - CGAL_assertion(false); - return Intersection_result(EMPTY,Intersection_info(),false,false); - } - default: // should not happen. - CGAL_assertion(false); - return Intersection_result(EMPTY,Intersection_info(),false,false); - } -} - -}} //namespace CGAL::internal_IOP - -#endif //CGAL_INTERNAL_INTERSECTION_TRIANGLE_SEGMENT_3_H diff --git a/Operations_on_polyhedra/include/CGAL/internal/corefinement/intersection_triangle_segment_3_coplanar.h b/Operations_on_polyhedra/include/CGAL/internal/corefinement/intersection_triangle_segment_3_coplanar.h deleted file mode 100644 index a41fc3c5324..00000000000 --- a/Operations_on_polyhedra/include/CGAL/internal/corefinement/intersection_triangle_segment_3_coplanar.h +++ /dev/null @@ -1,400 +0,0 @@ -// Copyright (c) 2011 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_INTERNAL_INTERSECTION_TRIANGLE_SEGMENT_3_COPLANAR_H -#define CGAL_INTERNAL_INTERSECTION_TRIANGLE_SEGMENT_3_COPLANAR_H - -#include - - - -namespace CGAL{ - namespace internal_IOP{ - -//enum Intersection_type {FACET,EDGE,VERTEX,EMPTY,COPLNR}; - -template -struct Intersection_point_coplanar{ - typedef typename Polyhedron_types::Halfedge_handle Halfedge_handle; - - Intersection_type type; - Halfedge_handle info; - Halfedge_handle segment_vertex; - - Intersection_point_coplanar(){}; - - Intersection_point_coplanar(Intersection_type type_, - Halfedge_handle info_, - Halfedge_handle segment_vertex_ - ) :type(type_),info(info_),segment_vertex(segment_vertex_){} -}; - -//test q vs segment bc -template -void point_vs_segment(Inter_pt_coplanar& pt, - Halfedge_handle bc, Halfedge_handle ab, - const Orientation& pqc, const Orientation& pqb) -{ - if (pqb==COLLINEAR){ - pt.type=VERTEX; - pt.info=ab; - } - else{ - if (pqc==COLLINEAR){ - pt.type=VERTEX; - pt.info=bc; - } - else{ - pt.type=EDGE; - pt.info=bc; - } - } -} - - -/* -// c -// / \ -// ___/___\____ with q-------p -// / \ -// a_______b -//supporting line of segment pq intersects ac and bc -*/ - -template -std::pair -decision_tree(const Point_3* a,const Point_3* b,const Point_3* c, - const Point_3* p,const Point_3* q, - const Orientation& pqa,const Orientation& pqb,const Orientation& pqc, - Halfedge_handle pq, - Halfedge_handle ca,Halfedge_handle ab,Halfedge_handle bc) -{ - CGAL_precondition(pqa!=NEGATIVE); - CGAL_precondition(pqb!=NEGATIVE); - CGAL_precondition(pqc!=POSITIVE); - - Inter_pt_coplanar pt1(EMPTY,NULL,NULL),pt2(EMPTY,NULL,NULL); - - //the segment supporting line intersects ac and bc - const Orientation bcq = coplanar_orientation(*b,*c,*q); - switch(bcq){ - case NEGATIVE: //segment does not intersect the triangle - return std::make_pair(pt1,pt2); - case COLLINEAR: //q \in [bc] - point_vs_segment(pt1,bc,ab,pqc,pqb); - pt1.segment_vertex=pq; - return std::make_pair(pt1,pt2); - default: - break; - } - - //===> q is to the left of bc - - const Orientation cap = coplanar_orientation(*c,*a,*p); - switch(cap){ - case NEGATIVE: //segment does not intersect the triangle - return false; - case COLLINEAR: //p \in [ca] - point_vs_segment(pt1,ca,bc,pqa,pqc); - pt1.segment_vertex=pq->opposite(); - return std::make_pair(pt1,pt2); - default: - break; - } - //===> p is to the right of ac - - - const Orientation cqa = coplanar_orientation(*c,*q,*a); - const Orientation cbp = coplanar_orientation(*c,*b,*p); - - if (pqc==COLLINEAR && cqa==POSITIVE && cbp==POSITIVE){ - //special case when c is inside the segment pq - pt1.type=VERTEX; - pt1.info=bc; - return std::make_pair(pt1,pt2); - } - - //where is located q? - switch (cqa){ - case POSITIVE: //q is outside the triangle - point_vs_segment(pt1,ca,bc,pqa,pqc); - break; - case NEGATIVE: //q is inside the triangle - pt1.type=FACET; - pt1.info=pq; - break; - case COLLINEAR: //q \in [ca] - point_vs_segment(pt1,ca,bc,pqa,pqc); - pt1.segment_vertex=pq; - break; - } - - //where is located p? - switch (cbp){ - case POSITIVE: //p is outside the triangle - point_vs_segment(pt2,bc,ab,pqc,pqb); - break; - case NEGATIVE: //p is inside the triangle - pt2.type=FACET; - pt2.info=pq->opposite(); - break; - case COLLINEAR: //p \in [bc] - point_vs_segment(pt2,bc,ab,pqc,pqb); - pt2.segment_vertex=pq->opposite(); - break; - } - return std::make_pair(pt1,pt2); -} - - -/* -// c -// / \ -// / \ -// / \ -// ____a_______b______ with q-------p -//supporting lines of segments pq and ab are the same. -*/ - -template -std::pair -collinear_decision_tree(const Point_3* a,const Point_3* b,const Point_3* c, - const Point_3* p,const Point_3* q, - const Orientation& pqa,const Orientation& pqb,const Orientation& pqc, - Halfedge_handle pq, - Halfedge_handle ca,Halfedge_handle ab,Halfedge_handle bc) -{ - CGAL_precondition(pqa==COLLINEAR); - CGAL_precondition(pqb==COLLINEAR); - CGAL_precondition(pqc==NEGATIVE); - - Inter_pt_coplanar pt1(EMPTY,NULL,NULL),pt2(EMPTY,NULL,NULL); - - //the segment supporting line intersects ac and bc - const Orientation bcq = coplanar_orientation(*b,*c,*q); - switch(bcq){ - case NEGATIVE: //segment does not intersect the triangle - return std::make_pair(pt1,pt2); - case COLLINEAR: // q = b - pt1.type=VERTEX; - pt1.info=ab; - pt1.segment_vertex=pq; - return std::make_pair(pt1,pt2); - default: - break; - } - - //===> q is to the left of b - - const Orientation cap = coplanar_orientation(*c,*a,*p); - switch(cap){ - case NEGATIVE: //segment does not intersect the triangle - return false; - case COLLINEAR: //p = a - pt1.type=VERTEX; - pt1.info=ca; - pt1.segment_vertex=pq->opposite(); - return std::make_pair(pt1,pt2); - default: - break; - } - - //===> p is to the right of a - - - const Orientation cqa = coplanar_orientation(*c,*q,*a); - const Orientation cbp = coplanar_orientation(*c,*b,*p); - - //where is located q? - switch (cqa){ - case POSITIVE: //q is to the left of a - pt1.type=VERTEX; - pt1.info=ca; - break; - case NEGATIVE: //q is to the right of a - pt1.type=EDGE; - pt1.info=ab; - pt1.segment_vertex=pq; - break; - case COLLINEAR: //q = a - pt1.type=VERTEX; - pt1.info=ca; - pt1.segment_vertex=pq; - break; - } - - //where is located p? - switch (cbp){ - case POSITIVE: //p is to the right of b - { - pt2.type=VERTEX; - pt2.info=ab; - } - break; - case NEGATIVE: //p is to the left of b - { - pt2.type=EDGE; - pt2.info=ab; - pt2.segment_vertex=pq->opposite(); - } - break; - case COLLINEAR: //p = b - pt2.type=VERTEX; - pt2.info=ab; - pt2.segment_vertex=pq->opposite(); - break; - } - - return std::make_pair(pt1,pt2); -} - -//std::pair,Intersection_point_coplanar > -template -std::pair,Intersection_point_coplanar > -do_intersect_coplanar(typename Polyhedron_types::Halfedge_handle pq, - typename Polyhedron_types::Face_handle fh) -{ - typedef Intersection_point_coplanar Inter_pt_coplanar; - typedef std::pair Return_type; - - - typedef typename Polyhedron_types::Halfedge_handle Halfedge_handle; - - const typename Kernel::Point_3 & A = fh->halfedge()->vertex()->point(); - const typename Kernel::Point_3 & B = fh->halfedge()->next()->vertex()->point(); - const typename Kernel::Point_3 & C = fh->halfedge()->next()->next()->vertex()->point(); - const typename Kernel::Point_3 & P = pq->vertex()->point(); - const typename Kernel::Point_3 & Q = pq->opposite()->vertex()->point(); - - const typename Kernel::Point_3 * a = &A; - const typename Kernel::Point_3 * b = &B; - const typename Kernel::Point_3 * c = &C; - - const typename Kernel::Point_3* p = &P; - const typename Kernel::Point_3* q = &Q; - - Halfedge_handle ca=fh->halfedge(); - Halfedge_handle ab=fh->halfedge()->next(); - Halfedge_handle bc=fh->halfedge()->next()->next(); - - - // Determine the orientation of the triangle in the common plane - - if (coplanar_orientation(A,B,C) != POSITIVE){ - // The triangle is not counterclockwise oriented swap two vertices. - b = &C; - c = &B; - std::swap(bc,ab); - } - - // Test whether the segment's supporting line intersects the - // triangle in the common plane - - Orientation pqa = coplanar_orientation(*p,*q,*a); - - //ensure pqa >= 0 - if (pqa == NEGATIVE){ - std::swap(p,q); - pqa=POSITIVE; - pq=pq->opposite(); - } - - const Orientation pqb = coplanar_orientation(*p,*q,*b); - const Orientation pqc = coplanar_orientation(*p,*q,*c); - - - - //Handle first case pq collinear with a triangle edge - if (pqa == COLLINEAR){ - if (pqb == COLLINEAR){ - //ab and pq are on the same line - if (pqc == NEGATIVE) - return collinear_decision_tree - (a,b,c,p,q,pqa,pqb,pqc,pq,ca,ab,bc); - else - return collinear_decision_tree - (a,b,c,q,p,pqa,pqb,NEGATIVE,pq->opposite(),ca,ab,bc); - } - if (pqc == COLLINEAR){ - //ac and pq are on the same line - if (pqb == NEGATIVE) - return collinear_decision_tree - (c,a,b,p,q,pqc,pqa,pqb,pq,bc,ca,ab); - else - return collinear_decision_tree - (c,a,b,q,p,pqc,pqa,NEGATIVE,pq->opposite(),bc,ca,ab); - } - } - else - if(pqb ==COLLINEAR && pqc == COLLINEAR){ - //bc and pq are on the same line - return collinear_decision_tree - (b,c,a,q,p,pqb,pqc,NEGATIVE,pq->opposite(),ab,bc,ca); - } - - - CGAL_assertion(pqa!=NEGATIVE); - - switch ( pqa ) { - case POSITIVE: - switch ( pqb ) { - case POSITIVE: - if (pqc == POSITIVE) - return std::make_pair(Inter_pt_coplanar(EMPTY,NULL,NULL),Inter_pt_coplanar(EMPTY,NULL,NULL)); - return decision_tree(a,b,c,p,q,pqa,pqb,pqc,pq,ca,ab,bc); - - case COLLINEAR: - case NEGATIVE: - if (pqc == POSITIVE) // b is isolated on the negative side - return decision_tree(c,a,b,p,q,pqc,pqa,pqb,pq,bc,ca,ab); - return decision_tree - (b,c,a,q,p,POSITIVE,opposite(pqc),NEGATIVE,pq->opposite(),ab,bc,ca); - default:// should not happen. - CGAL_assertion(false); - return std::make_pair(Inter_pt_coplanar(EMPTY,NULL,NULL),Inter_pt_coplanar(EMPTY,NULL,NULL)); - } - case COLLINEAR: - switch ( pqb ) { - case POSITIVE: - if (pqc == POSITIVE) // a is isolated on the negative side - return decision_tree(b,c,a,p,q,pqb,pqc,pqa,pq,ab,bc,ca); - return decision_tree(a,b,c,p,q,pqa,pqb,pqc,pq,ca,ab,bc); - - case NEGATIVE: - if (pqc == NEGATIVE) // a is isolated on the positive side - return decision_tree - (b,c,a,q,p,POSITIVE,POSITIVE,pqa,pq->opposite(),ab,bc,ca); - return decision_tree(c,a,b,p,q,pqc,pqa,pqb,pq,bc,ca,ab); - - default:// should not happen. - CGAL_assertion(false); - return std::make_pair(Inter_pt_coplanar(EMPTY,NULL,NULL),Inter_pt_coplanar(EMPTY,NULL,NULL)); - } - default:// should not happen. - CGAL_assertion(false); - return std::make_pair(Inter_pt_coplanar(EMPTY,NULL,NULL),Inter_pt_coplanar(EMPTY,NULL,NULL)); - - } -} - -}}//namespace CGAL::internal_IOP - -#endif //CGAL_INTERNAL_INTERSECTION_TRIANGLE_SEGMENT_3_COPLANAR_H diff --git a/Operations_on_polyhedra/include/CGAL/internal/corefinement/predicates.h b/Operations_on_polyhedra/include/CGAL/internal/corefinement/predicates.h deleted file mode 100644 index edb8a3f534a..00000000000 --- a/Operations_on_polyhedra/include/CGAL/internal/corefinement/predicates.h +++ /dev/null @@ -1,131 +0,0 @@ -// Copyright (c) 2011 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_INTERNAL_COREFINEMENT_PREDICATES_H -#define CGAL_INTERNAL_COREFINEMENT_PREDICATES_H - -#include - - -namespace CGAL{ - -namespace Corefinement{ - -namespace OOP{ -//Considering the plane with normal vector [O_prime,O] and containing O. -//We define the counterclockwise order around O when looking from -//the side of the plane into which the vector [O_prime,O] is pointing. -//We consider the portion of the plane defined by rotating a ray starting at O -//from the planar projection of P1 to the planar projection of P2 in -//counterclockwise order. -//The predicates indicates whether the planar projection of point Q lies in this -//portion of the plane. -//Preconditions: -// O_prime,O,P1 are not collinear -// O_prime,O,P2 are not collinear -// O_prime,O,Q are not collinear -// O_prime,O,P1,Q are not coplanar or coplanar_orientation(O,O_prime,P1,Q)==NEGATIVE -// O_prime,O,P2,Q are not coplanar or coplanar_orientation(O,O_prime,P2,Q)==NEGATIVE -template -bool sorted_around_edge( - const typename Kernel::Point_3& O_prime,const typename Kernel::Point_3& O, - const typename Kernel::Point_3& P1,const typename Kernel::Point_3& P2, - const typename Kernel::Point_3& Q) -{ - //guarantee to have non-flat triangles - CGAL_precondition( !collinear(O_prime,O,P1) ); - CGAL_precondition( !collinear(O_prime,O,P2) ); - CGAL_precondition( !collinear(O_prime,O,Q) ); - - //no two triangles are coplanar and on the same side of their common edge - CGAL_precondition( !coplanar(O_prime,O,P1,Q) - || coplanar_orientation(O,O_prime,P1,Q)==NEGATIVE ); - CGAL_precondition( !coplanar(O_prime,O,P2,Q) - || coplanar_orientation(O,O_prime,P2,Q)==NEGATIVE ); - - Sign s0 = CGAL::sign( determinant(O-O_prime,P1-O,P2-O) ); - - if ( s0==ZERO ) { - //O, O_prime, P1 and P2 are coplanar - Orientation o=orientation(O_prime,O,P1,Q); - CGAL_precondition(o!=COPLANAR); - return o==POSITIVE; - } - - //O, O_prime, P1 and P2 are not coplanar - Sign s1 = CGAL::sign( determinant(O-O_prime,P1-O,Q -O) ); - Sign s2 = CGAL::sign( determinant(O-O_prime,Q -O,P2-O) ); - - if (s0 == POSITIVE) // the angle P1,O,P2 is smaller that Pi. - return ( s1 == POSITIVE ) - && ( s2 ==POSITIVE ); //true if the angles P1,O,Q and Q,O,P2 are smaller than Pi - else - return ( s1 != NEGATIVE ) - || ( s2 != - NEGATIVE ); //true if the angle P1,O,Q or the angle Q,O,P2 is smaller than or equal to Pi -} - -template -bool sorted_around_edge_filtered( int O_prime_index, - int O_index, - int P1_index, - int P2_index, - int Q_index, - Vertex_handle P1, - Vertex_handle P2, - Vertex_handle Q, - const Nodes_vector& nodes, - PolyhedronPointPMap ppmap) -{ - typename Nodes_vector::Protector p; - try { - CGAL_USE(p); - return sorted_around_edge( - nodes.interval_node(O_prime_index), - nodes.interval_node(O_index), - P1_index == -1 ? nodes.to_interval(get(ppmap,P1)) - : nodes.interval_node(P1_index), - P2_index == -1 ? nodes.to_interval(get(ppmap,P2)) - : nodes.interval_node(P2_index), - Q_index == -1 ? nodes.to_interval(get(ppmap,Q)) - : nodes.interval_node(Q_index ) - ); - } catch(Uncertain_conversion_exception&) { - return sorted_around_edge( - nodes.exact_node(O_prime_index), - nodes.exact_node(O_index), - P1_index == -1 ? nodes.to_exact(get(ppmap,P1)) - : nodes.exact_node(P1_index), - P2_index == -1 ? nodes.to_exact(get(ppmap,P2)) - : nodes.exact_node(P2_index), - Q_index == -1 ? nodes.to_exact(get(ppmap,Q)) - : nodes.exact_node(Q_index ) - ); - } -} - -} - -} - -} // end of namespace CGAL::Corefinement::OOP - -#endif //CGAL_INTERNAL_COREFINEMENT_PREDICATES_H diff --git a/Operations_on_polyhedra/include/CGAL/internal/corefinement/utils.h b/Operations_on_polyhedra/include/CGAL/internal/corefinement/utils.h deleted file mode 100644 index f8574d1888a..00000000000 --- a/Operations_on_polyhedra/include/CGAL/internal/corefinement/utils.h +++ /dev/null @@ -1,133 +0,0 @@ -// Copyright (c) 2011 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_INTERNAL_COREFINEMENT_UTILS_H -#define CGAL_INTERNAL_COREFINEMENT_UTILS_H - -#include - - -namespace CGAL{ - -namespace internal_IOP { - -template -struct Compare_unik_address{ - typedef typename Polyhedron::Halfedge_handle Halfedge_handle; - typedef typename Polyhedron::Halfedge_const_handle Halfedge_const_handle; - typedef typename Polyhedron::Halfedge Halfedge; - - bool operator()(Halfedge_handle h1,Halfedge_handle h2) const { - Halfedge* ph1=&(*h1) < &(*h1->opposite()) ? &(*h1) : &(*h1->opposite()); - Halfedge* ph2=&(*h2) < &(*h2->opposite()) ? &(*h2) : &(*h2->opposite()); - return ph1 < ph2; - } - - bool operator()(Halfedge_const_handle h1,Halfedge_const_handle h2) const { - const Halfedge* ph1=&(*h1) < &(*h1->opposite()) ? &(*h1) : &(*h1->opposite()); - const Halfedge* ph2=&(*h2) < &(*h2->opposite()) ? &(*h2) : &(*h2->opposite()); - return ph1 < ph2; - } -}; - -template -struct Compare_address{ - typedef typename Polyhedron::Halfedge_handle Halfedge_handle; - typedef typename Polyhedron::Halfedge_const_handle Halfedge_const_handle; - typedef typename Polyhedron::Halfedge Halfedge; - - bool operator()(Halfedge_handle h1,Halfedge_handle h2) const { - return &(*h1) < &(*h2); - } - - bool operator()(Halfedge_const_handle h1,Halfedge_const_handle h2) const { - return &(*h1) < &(*h2); - } -}; - -template -class Non_intersection_halfedge{ - typedef std::map< typename Polyhedron::Halfedge_const_handle, - std::pair, - Compare_unik_address - > Intersection_hedges_set; - Intersection_hedges_set intersection_hedges_; -public: - Non_intersection_halfedge(const Intersection_hedges_set& the_set) : intersection_hedges_(the_set){} - - - bool operator()(typename Polyhedron::Halfedge_const_handle h) const - { - if (h->is_border_edge()) return false; - return intersection_hedges_.find(h)==intersection_hedges_.end(); - } -}; - -} //end of namespace internal_IOP - -namespace Corefinement{ - -template -struct Dummy_edge_mark_property_map{ - typedef bool value_type; - typedef value_type reference; - typedef std::pair key_type; - typedef boost::read_write_property_map_tag category; - - Dummy_edge_mark_property_map(){} - - friend reference get(Dummy_edge_mark_property_map,key_type) {return false;} - friend void put(Dummy_edge_mark_property_map,key_type,value_type) {} -}; - - -template -int node_index_of_incident_vertex(Halfedge_const_handle h, - const Border_halfedges_map& border_halfedges) -{ - //WARNING this may be expensive - Halfedge_const_handle start=h; - Halfedge_const_handle curr=start; - do { - typename Border_halfedges_map::const_iterator it_border = - border_halfedges.find(curr ); - if (it_border!=border_halfedges.end()) - return it_border->first==curr?it_border->second.second - :it_border->second.first; - curr=curr->next()->opposite(); - } while(curr!=start); - - return -1; -} - -template -int get_node_id(Vertex_handle vh, - const Vertex_to_node_id& vertex_to_node_id) -{ - typename Vertex_to_node_id::const_iterator it=vertex_to_node_id.find(vh); - if (it==vertex_to_node_id.end()) - return -1; - return it->second; -} - -} } //end of namespace CGAL::Corefinement - -#endif // CGAL_INTERNAL_COREFINEMENT_UTILS_H diff --git a/Operations_on_polyhedra/include/CGAL/intersection_of_Polyhedra_3.h b/Operations_on_polyhedra/include/CGAL/intersection_of_Polyhedra_3.h deleted file mode 100644 index 9d7bb7ec679..00000000000 --- a/Operations_on_polyhedra/include/CGAL/intersection_of_Polyhedra_3.h +++ /dev/null @@ -1,2187 +0,0 @@ -// Copyright (c) 2010-2012 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_INTERSECTION_OF_POLYHEDRA_3_H -#define CGAL_INTERSECTION_OF_POLYHEDRA_3_H - -#include - -#include - -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include - -#include -#include - -#include -#include - -#include - -#ifdef CGAL_COREFINEMENT_DEBUG -#warning look at CGAL/Mesh_3/Robust_intersection_traits.h and the statically filtered decision tree -#endif - -namespace CGAL{ - -//This functor computes the pairwise intersection of polyhedral surfaces. -//Intersection are given as a set of polylines -//The algorithm works as follow: -//From each polyhedral surface we can get it as a set of segments or as a set of triangles. -//We first use Box_intersection_d to filter intersection between all polyhedral -//surface segments and polyhedral triangles. -//From this filtered set, for each pair (segment,triangle), we look at the -//intersection type. If not empty, we can have three different cases -// 1)the segment intersect the interior of the triangle: -// We compute the intersection point and for each triangle incident -// to the segment, we write the fact that the point belong to the intersection -// of these two triangles. -// 2)the segment intersect the triangle on an edge -// We do the same thing as described above but -// for all triangle incident to the edge intersected -// 3)the segment intersect the triangle at a vertex -// for each edge incident to the vertex, we do -// the same operations as in 2) -// -//In case the segment intersect the triangle at one of the segment endpoint, -//we repeat the same procedure for each segment incident to this -//endpoint. -// -//Note that given a pair (segment,triangle)=(S,T), if S belongs -//to the plane of T, we have nothing to do in the following cases: -// -- no triangle T' contains S such that T and T' are coplanar -// -- at least one triangle contains S -// Indeed, the intersection points of S and T will be found using segments -// of T or segments adjacent to S. -// -// -- Sebastien Loriot, 2010/04/07 - -template -struct Default_polyhedron_ppmap{ - typedef typename Polyhedron::Point_3 value_type; - typedef const value_type& reference; - typedef typename Polyhedron::Vertex_handle key_type; - typedef boost::read_write_property_map_tag category; - - friend reference get(Default_polyhedron_ppmap, key_type vh) {return vh->point();} - friend reference get(Default_polyhedron_ppmap, typename Polyhedron::Vertex_const_handle vh) {return vh->point();} - friend void put(Default_polyhedron_ppmap,key_type vh, const value_type& v) {vh->point()=v;} -}; - - -namespace internal_IOP { - //an enum do decide which kind of intersection points are needed - struct No_predicates_on_constructions{}; - struct Predicates_on_constructions{}; -} // namespace internal_IOP - -template -struct Empty_node_visitor{ - typedef typename Polyhedron::Halfedge_const_handle Halfedge_handle; - void new_node_added(int,internal_IOP::Intersection_type,Halfedge_handle,Halfedge_handle,bool,bool){} - template - void annotate_graph(Iterator,Iterator){} - void update_terminal_nodes(std::vector&){} - void set_number_of_intersection_points_from_coplanar_facets(int){} - void input_have_coplanar_facets(){} - void add_filtered_intersection(Halfedge_handle,Halfedge_handle,const Polyhedron&,const Polyhedron&){} - void start_new_polyline(int,int){} - void add_node_to_polyline(int){} - void new_input_polyhedron(const Polyhedron&){} - template - void finalize(T&){} - typedef internal_IOP::No_predicates_on_constructions Node_storage_type; - typedef Tag_true Is_polyhedron_const; - static const bool do_need_vertex_graph = false; -}; - -namespace internal_IOP{ - -template -struct Compare_handles{ - typedef typename Polyhedron_types::Halfedge_handle Halfedge_handle; - typedef typename Polyhedron_types::Halfedge Halfedge; - typedef typename Polyhedron_types::Vertex Vertex; - typedef typename Polyhedron_types::Facet Facet; - typedef typename Polyhedron_types::Facet_handle Facet_handle; - typedef std::pair Vertex_handle_pair; - - static inline Vertex_handle_pair - make_sorted_pair_of_vertices(Halfedge_handle h) { - const Vertex* v1=&(* h->vertex() ); - const Vertex* v2=&(* h->opposite()->vertex() ); - if ( v1 < v2 ) - return Vertex_handle_pair(v1,v2); - return Vertex_handle_pair(v2,v1); - } - - bool operator()(Halfedge_handle h1,Halfedge_handle h2) const { - Vertex_handle_pair p1=make_sorted_pair_of_vertices(h1); - Vertex_handle_pair p2=make_sorted_pair_of_vertices(h2); - return p1 < p2; - } - - bool operator()(Facet_handle f1,Facet_handle f2) const { - return &(*f1) < &(*f2); - } - - bool operator()(const std::pair& p1, const std::pair& p2) const{ - Halfedge* h1= (std::min) ( &(*(p1.first)), &(*(p1.first->opposite())) ); - Halfedge* h2= (std::min) ( &(*(p2.first)), &(*(p2.first->opposite())) ); - return h1 -struct Compare_handle_pairs{ - typedef typename Polyhedron_types::Facet Facet; - typedef typename Polyhedron_types::Facet_handle Facet_handle; - typedef std::pair Facet_pair; - typedef std::pair Facet_pair_and_int; - - bool operator()(const Facet_pair& p1, const Facet_pair& p2) const{ - Facet* f1=&(*p1.first); - Facet* f2=&(*p2.first); - if (f1==f2){ - f1=&(*p1.second); - f2=&(*p2.second); - } - return f1f2) return false; - return p1.second -struct Order_along_a_halfedge{ - typedef typename Polyhedron_types::Halfedge_handle Halfedge_handle; - const Nodes_vector& nodes; - Halfedge_handle hedge; - PolyhedronPointPMap ppmap; - - Order_along_a_halfedge(Halfedge_handle hedge_,const Nodes_vector& nodes_, PolyhedronPointPMap ppmap):nodes(nodes_),hedge(hedge_), ppmap(ppmap){} - bool operator()(int i,int j) const { - //returns true, iff q lies strictly between p and r. - typename Nodes_vector::Protector p; - try{ - CGAL::internal::use(p); - - return CGAL::collinear_are_strictly_ordered_along_line(nodes.to_interval(get(ppmap, hedge->vertex())), - nodes.interval_node(j), - nodes.interval_node(i)); - } - catch(CGAL::Uncertain_conversion_exception&){ - return CGAL::collinear_are_strictly_ordered_along_line(nodes.to_exact(get(ppmap, hedge->vertex())), - nodes.exact_node(j), - nodes.exact_node(i)); - } - } -}; - - -template -class Split_halfedge : public CGAL::Modifier_base { - typedef typename HDS::Halfedge_handle Halfedge_handle; - typedef typename HDS::Vertex_handle Vertex_handle; - typedef typename HDS::Vertex Vertex; - Halfedge_handle hedge; - - typename HDS::Halfedge::Base* - unlock_halfedge(Halfedge_handle h){ - return static_cast(&(*h)); - } - -public: - - Split_halfedge(Halfedge_handle h) : hedge(h){} - - // new_hedge hedge - // -----------> -----------> - // v - // <----------- <----------- - // new_opposite opposite - // - void operator()( HDS& hds) { - - Vertex_handle v=hds.vertices_push_back(Vertex()); - - Halfedge_handle opposite=hedge->opposite(); - - Halfedge_handle new_hedge=hds.edges_push_back(*hedge); - Halfedge_handle new_opposite=new_hedge->opposite(); - - //update next relations - unlock_halfedge(new_hedge)->set_next(hedge); - unlock_halfedge(new_hedge->prev())->set_next(new_hedge); - unlock_halfedge(hedge)->set_prev(new_hedge); - - unlock_halfedge(opposite)->set_next(new_opposite); - unlock_halfedge(new_opposite)->set_prev(opposite); - unlock_halfedge(new_opposite->next())->set_prev(new_opposite); - - unlock_halfedge(opposite)->set_vertex(v); - unlock_halfedge(new_hedge)->set_vertex(v); - - v->set_halfedge(new_hedge); - new_opposite->vertex()->set_halfedge(new_opposite); - } -}; - - - -} //namespace internal_IOP - - - -//WARNING THIS IS DONE ONLY FOR POLYHEDRON -// Warning this will split only existing edges, newly created edge intersected -// by the intersection polyline won't be split -template, - class Kernel=typename Kernel_traits< typename boost::property_traits::value_type>::Kernel > -class Node_visitor_for_polyline_split{ -//typedefs - typedef typename Polyhedron::Halfedge_handle Halfedge_handle; - typedef typename Polyhedron::Halfedge Halfedge; - typedef typename Polyhedron::Vertex_handle Vertex_handle; - //Info stores information about a particular intersection on an - //edge or a vertex of a polyhedron. The two first elements in - //the template describe the intersected simplex of the considered - //polyhedron; the two last elements describe the element of the - //second polyhedron (can be either a vertex, an edge of a facet) - //involved in the intersection - typedef CGAL::cpp11::tuple Info; - typedef std::map Hedge_to_polyhedron_map; - typedef std::vector Infos; - typedef std::map Node_to_infos_map; -//data members - Node_to_infos_map node_infos; - Hedge_to_polyhedron_map hedge_to_polyhedron; - Halfedge_predicate is_on_polyline; - Set_vertex_corner set_as_corner; - PolyhedronPointPMap ppmap; -//functions - void handle_principal_edge(int node_id, - internal_IOP::Intersection_type type, - Halfedge_handle principal_edge, - Halfedge_handle additional_edge, - bool is_vertex_coplanar, - bool is_vertex_opposite_coplanar) - { - bool coplanar_v=false; - if (is_vertex_coplanar) coplanar_v=true; - else if (is_vertex_opposite_coplanar){ - principal_edge=principal_edge->opposite(); - coplanar_v=true; - } - - if (coplanar_v) - handle_on_vertex(node_id,principal_edge,type,additional_edge); - else{ - if ( is_on_polyline(principal_edge) ){ - typename Node_to_infos_map::iterator it_res= - node_infos.insert(std::make_pair(node_id,Infos())).first; - it_res->second.push_back( Info(internal_IOP::EDGE,principal_edge,type,additional_edge) ); - } - } - } - - void handle_on_vertex(int node_id,Halfedge_handle edge,internal_IOP::Intersection_type type,Halfedge_handle additional_edge){ - Halfedge_handle current=edge; - do{ - if (is_on_polyline(current)){ - typename Node_to_infos_map::iterator it_res= - node_infos.insert(std::make_pair(node_id,Infos())).first; - it_res->second.push_back( Info(internal_IOP::VERTEX,current,type,additional_edge) ); - break; - } - current=current->next()->opposite(); - } - while(current!=edge); - } - - Halfedge* make_unique_key(Halfedge_handle h){ - if (&(*h) < &(*h->opposite())) - return &(*h); - else - return &(*h->opposite()); - } - - // new_hedge hedge - // -----------> -----------> - // v - // <----------- <----------- - // new_opposite opposite - // - void split_edge_and_retriangulate(Halfedge_handle hedge,const typename Kernel::Point_3& point,Polyhedron& P){ - internal_IOP::Split_halfedge delegated(hedge); - P.delegate( delegated ); - - Vertex_handle vh=boost::prior(P.vertices_end()); - put(ppmap, vh, point); - CGAL_assertion(get(ppmap,vh)==point); - - CGAL_assertion(P.is_valid()); - //triangulate the two adjacent facets - if (!hedge->is_border()) - P.split_facet(hedge->prev(),hedge->next()); - if (!hedge->opposite()->is_border()) - P.split_facet(hedge->opposite(),hedge->opposite()->next()->next()); - CGAL_assertion(P.is_valid()); - } - - //sort node ids so that we can split the hedge - //consecutively - template - void sort_vertices_along_hedge(std::vector& node_ids,Halfedge_handle hedge,const Nodes_vector& nodes) - { - std::sort(node_ids.begin(), - node_ids.end(), - internal_IOP::Order_along_a_halfedge(hedge,nodes,ppmap) - ); - } - -public: - static const bool do_need_vertex_graph = false; - typedef internal_IOP::Predicates_on_constructions Node_storage_type; - typedef Tag_false Is_polyhedron_const; - - Node_visitor_for_polyline_split(){} - Node_visitor_for_polyline_split(const Halfedge_predicate& getting, - const Set_vertex_corner& setting, - PolyhedronPointPMap ppmap) - :is_on_polyline(getting),set_as_corner(setting),ppmap(ppmap){} - - void new_node_added(int node_id, - internal_IOP::Intersection_type type, - Halfedge_handle principal_edge, - Halfedge_handle additional_edge, - bool is_vertex_coplanar, - bool is_vertex_opposite_coplanar) - { - switch(type) - { - case internal_IOP::FACET: //Facet intersected by an edge - handle_principal_edge(node_id,type,principal_edge,additional_edge,is_vertex_coplanar,is_vertex_opposite_coplanar); - break; - case internal_IOP::EDGE: //Edge intersected by an edge - handle_principal_edge(node_id,type,principal_edge,additional_edge,is_vertex_coplanar,is_vertex_opposite_coplanar); - if ( is_on_polyline(additional_edge) ){ - typename Node_to_infos_map::iterator it_res= - node_infos.insert(std::make_pair(node_id,Infos())).first; - it_res->second.push_back( Info(type,additional_edge, - ( is_vertex_coplanar||is_vertex_opposite_coplanar ) ? internal_IOP::VERTEX:internal_IOP::EDGE, - is_vertex_opposite_coplanar?principal_edge->opposite():principal_edge) ); - } - break; - case internal_IOP::VERTEX://Vertex intersected by an edge - handle_principal_edge(node_id,type,principal_edge,additional_edge,is_vertex_coplanar,is_vertex_opposite_coplanar); - handle_on_vertex( node_id,additional_edge, - ( is_vertex_coplanar||is_vertex_opposite_coplanar ) ? internal_IOP::VERTEX:internal_IOP::EDGE, - is_vertex_opposite_coplanar?principal_edge->opposite():principal_edge); - break; - default: - break; - } - } - - template - void annotate_graph(Iterator begin,Iterator end){ - for(Iterator it=begin;it!=end;++it){ - typename Node_to_infos_map::iterator it_res=node_infos.find(static_cast(std::distance(begin, it))); - if (it_res!=node_infos.end()) - it->make_terminal(); - } - } - - void update_terminal_nodes(std::vector& terminal_bools){ - for (typename Node_to_infos_map::iterator it=node_infos.begin();it!=node_infos.end();++it){ - terminal_bools[it->first]=true; - } - } - - void new_input_polyhedron(const Polyhedron&){} - void start_new_polyline(int,int){} - void add_node_to_polyline(int){} - void set_number_of_intersection_points_from_coplanar_facets(int){} - - void add_filtered_intersection(Halfedge_handle eh,Halfedge_handle fh,Polyhedron& Pe,Polyhedron& Pf){ - hedge_to_polyhedron.insert(std::make_pair(make_unique_key(eh),&Pe)); - hedge_to_polyhedron.insert(std::make_pair(make_unique_key(fh),&Pf)); - hedge_to_polyhedron.insert(std::make_pair(make_unique_key(fh->next()),&Pf)); - hedge_to_polyhedron.insert(std::make_pair(make_unique_key(fh->next()->next()),&Pf)); - } - - //split_halfedges - template - void finalize(const Nodes_vector& nodes){ - typedef std::map, - std::vector,internal_IOP::Compare_handles > Halfedges_to_split; - - Halfedges_to_split halfedges_to_split; - - for (typename Node_to_infos_map::iterator it=node_infos.begin();it!=node_infos.end();++it){ - int node_id=it->first; - const Infos& infos=it->second; - std::map > hedges_to_split; - - //collect information about halfedge to split - typename Infos::const_iterator it_info=infos.begin(); - for (;it_info!=infos.end();++it_info) - { - typename Hedge_to_polyhedron_map::iterator it_poly= - hedge_to_polyhedron.find(make_unique_key(CGAL::cpp11::get<1>(*it_info))); - CGAL_assertion(it_poly!=hedge_to_polyhedron.end()); - //associate information to an intersection point: - //we give which simplex of the other polyhedron intersect the simplex considered - set_as_corner.add_info_to_node(node_id,it_poly->second,*it_info); - switch(CGAL::cpp11::get<0>(*it_info)) - { - case internal_IOP::EDGE: - { - halfedges_to_split.insert( - std::make_pair( std::make_pair(CGAL::cpp11::get<1>(*it_info),&(*(it_poly->second))),std::vector() ) - ).first->second.push_back(node_id); - break; - } - case internal_IOP::VERTEX: - set_as_corner(CGAL::cpp11::get<1>(*it_info)->vertex(),node_id,it_poly->second); - break; - default: - CGAL_assertion(false); - //should never be here - } - } - } - - - //do the split - for(typename Halfedges_to_split::iterator it=halfedges_to_split.begin();it!=halfedges_to_split.end();++it){ - Halfedge_handle hedge=it->first.first; - Polyhedron* P=it->first.second; - std::vector& node_ids=it->second; - - sort_vertices_along_hedge(node_ids,hedge,nodes); - for (std::vector::iterator it_id=node_ids.begin();it_id!=node_ids.end();++it_id){ - split_edge_and_retriangulate(hedge,nodes[*it_id],*P); - set_as_corner(hedge->opposite()->vertex(),*it_id,(Polyhedron*)(0)); - } - } - } - void input_have_coplanar_facets() {} -}; - - -namespace internal_IOP{ - - template - typename Exact_kernel::Point_3 - compute_triangle_segment_intersection_point( - typename Polyhedron::Vertex_const_handle vh1,typename Polyhedron::Vertex_const_handle vh2, - typename Polyhedron::Vertex_const_handle vf1,typename Polyhedron::Vertex_const_handle vf2,typename Polyhedron::Vertex_const_handle vf3, - const Exact_kernel& ek, - PolyhedronPointPMap ppmap) - { - CGAL::Cartesian_converter to_exact; - typename Exact_kernel::Triangle_3 t(to_exact( get(ppmap, vf1) ), - to_exact( get(ppmap, vf2) ), - to_exact( get(ppmap, vf3) ) - ); - - typename Exact_kernel::Segment_3 s (to_exact( get(ppmap, vh1) ), - to_exact( get(ppmap, vh2) ) - ); - - typename Exact_kernel::Intersect_3 exact_intersect=ek.intersect_3_object(); - CGAL::Object inter=exact_intersect(t,s); - CGAL_assertion(CGAL::do_intersect(t,s)); - const typename Exact_kernel::Point_3* e_pt=CGAL::object_cast(&inter); - CGAL_assertion(e_pt!=NULL); - return *e_pt; - } - - template - typename Exact_kernel::Point_3 - compute_triangle_segment_intersection_point( - typename Polyhedron::Halfedge_const_handle edge, - typename Polyhedron::Facet_const_handle facet, - const Exact_kernel& ek, - PolyhedronPointPMap pmap) - { - return compute_triangle_segment_intersection_point( - edge->vertex(),edge->opposite()->vertex(), - facet->halfedge()->vertex(),facet->halfedge()->next()->vertex(),facet->halfedge()->opposite()->vertex(), - ek, - pmap); - - } - - - //A class containing a vector of the intersection points. - //The third template parameter indicates whether an - //exact representation is required - template ::value> - class Triangle_segment_intersection_points; - - - //Store only the double version of the intersection points. - template - class Triangle_segment_intersection_points - { - //typedefs - typedef std::vector Nodes_vector; - typedef typename Polyhedron::Halfedge_const_handle Halfedge_handle; - typedef typename Polyhedron::Facet_const_handle Facet_handle; - typedef CGAL::Exact_predicates_exact_constructions_kernel Exact_kernel; - typedef CGAL::Cartesian_converter Exact_to_double; - //members - Nodes_vector nodes; - Exact_kernel ek; - Exact_to_double exact_to_double; - PolyhedronPointPMap ppmap; - public: - - Triangle_segment_intersection_points(PolyhedronPointPMap ppmap): - ppmap(ppmap){} - - typedef CGAL::Interval_nt::Protector Protector; - - const typename Kernel::Point_3& - operator[](int i) const { - return nodes[i]; - } - - const typename Kernel::Point_3& exact_node(int i) const {return nodes[i];} - const typename Kernel::Point_3& interval_node(int i) const {return nodes[i];} - const typename Kernel::Point_3& to_exact(const typename Kernel::Point_3& p) const {return p;} - const typename Kernel::Point_3& to_interval(const typename Kernel::Point_3& p) const {return p;} - - size_t size() const {return nodes.size();} - - void add_new_node(const typename Exact_kernel::Point_3& p) - { - nodes.push_back( exact_to_double(p) ); - } - - //add a new node in the final graph. - //it is the intersection of the triangle with the segment - void add_new_node(Halfedge_handle edge,Facet_handle facet) - { - add_new_node( - compute_triangle_segment_intersection_point(edge,facet,ek, ppmap) - ); - } - - void add_new_node(const typename Kernel::Point_3& p) - { - nodes.push_back(p); - } - }; // end specialization - // Triangle_segment_intersection_points - - - //second specializations: store an exact copy of the points so that we can answer exactly predicates - //FYI, it used to have two specializations (one in the case the polyhedron - //can be edited and on if it cannot) building exact representation on demand. - //In the former case, we were using facet and halfedge while in the latter - //triple of vertex_handle and pair of vertex_handle - template - class Triangle_segment_intersection_points - { - //typedefs - public: - typedef CGAL::Simple_cartesian > Ikernel; - typedef CGAL::Exact_predicates_exact_constructions_kernel Exact_kernel; - private: - typedef CGAL::Cartesian_converter Interval_to_double; - typedef CGAL::Cartesian_converter Double_to_interval; - typedef CGAL::Cartesian_converter Exact_to_interval; - typedef CGAL::Cartesian_converter Double_to_exact; - - typedef typename Polyhedron::Vertex_const_handle Vertex_handle; - typedef typename Polyhedron::Halfedge_const_handle Halfedge_handle; - typedef typename Polyhedron::Facet_const_handle Facet_handle; - - typedef std::vector Interval_nodes; - typedef std::vector Exact_nodes; - - - //members - Interval_nodes inodes; - Exact_nodes enodes; - - Interval_to_double interval_to_double; - Exact_to_interval exact_to_interval; - Double_to_interval double_to_interval; - Double_to_exact double_to_exact; - Exact_kernel ek; - PolyhedronPointPMap ppmap; - - public: - - Triangle_segment_intersection_points(PolyhedronPointPMap ppmap): - ppmap(ppmap){} - - typedef CGAL::Interval_nt::Protector Protector; - - typename Kernel::Point_3 - operator[](int i) const { - return interval_to_double(inodes[i]); - } - - const typename Ikernel::Point_3& - interval_node(int i) const { - return inodes[i]; - } - - typename Ikernel::Point_3 - to_interval(const typename Kernel::Point_3& p) const { - return double_to_interval(p); - } - - const Exact_kernel::Point_3 - exact_node(int i) const { - return enodes[i]; - } - - typename Exact_kernel::Point_3 - to_exact(const typename Kernel::Point_3& p) const { - return double_to_exact(p); - } - - - size_t size() const {return enodes.size();} - - void add_new_node(const Exact_kernel::Point_3& p){ - const Ikernel::Point_3& p_approx=p.approx(); - if ( !has_smaller_relative_precision(p_approx.x(),Lazy_exact_nt::get_relative_precision_of_to_double()) || - !has_smaller_relative_precision(p_approx.y(),Lazy_exact_nt::get_relative_precision_of_to_double()) || - !has_smaller_relative_precision(p_approx.z(),Lazy_exact_nt::get_relative_precision_of_to_double()) ) p.exact(); - enodes.push_back(p); - inodes.push_back( exact_to_interval(p) ); - } - - void add_new_node(Halfedge_handle edge,Facet_handle facet) - { - add_new_node( compute_triangle_segment_intersection_point(edge,facet,ek,ppmap) ); - } - - //the point is an input - void add_new_node(const typename Kernel::Point_3& p){ - enodes.push_back(to_exact(p)); - inodes.push_back( double_to_interval(p) ); - } - }; // end specialization - // Triangle_segment_intersection_points - - //Third specialization: The kernel already has exact constructions. - template - class Triangle_segment_intersection_points - { - //typedefs - typedef std::vector Nodes_vector; - typedef typename Polyhedron::Halfedge_const_handle Halfedge_handle; - typedef typename Polyhedron::Facet_const_handle Facet_handle; - //members - Nodes_vector nodes; - Kernel k; - PolyhedronPointPMap ppmap; - public: - typedef Kernel Ikernel; - typedef Kernel Exact_kernel; - typedef void* Protector; - - Triangle_segment_intersection_points(PolyhedronPointPMap ppmap): - ppmap(ppmap){} - - const typename Kernel::Point_3& - operator[](int i) const { - return nodes[i]; - } - - size_t size() const {return nodes.size();} - const typename Kernel::Point_3& exact_node(int i) const {return nodes[i];} - const typename Kernel::Point_3& interval_node(int i) const {return nodes[i];} - - //add a new node in the final graph. - //it is the intersection of the triangle with the segment - void add_new_node(Halfedge_handle edge,Facet_handle facet) - { - nodes.push_back ( - compute_triangle_segment_intersection_point(edge,facet,k,ppmap) - ); - } - - void add_new_node(const typename Kernel::Point_3& p) - { - nodes.push_back(p); - } - - const typename Kernel::Point_3& to_interval(const typename Kernel::Point_3& p) const { return p; } - const typename Kernel::Point_3& to_exact(const typename Kernel::Point_3& p) const { return p; } - - }; // end specialization - // Triangle_segment_intersection_points - -} - -#ifdef CGAL_COREFINEMENT_DO_REPORT_SELF_INTERSECTIONS -struct Intersection_of_Polyhedra_3_self_intersection_exception - : public std::logic_error - { - Intersection_of_Polyhedra_3_self_intersection_exception() - : std::logic_error("Self-intersection found in the facets involved in the intersection") - {} - }; -#endif - -//TODO an important requirement is that the Polyhedron should be based on a list-based -//HDS. We use a lot of maps that use the address of Facet,Halfedge and a reallocation would -//be dramatic. - -template< class Polyhedron, - class Kernel_=Default, - class Node_visitor_=Default, - class Node_storage_type_=Default, - class Use_const_polyhedron_=Default, - class PolyhedronPointPMap_=Default - > -class Intersection_of_Polyhedra_3{ -//Default template parameters - typedef typename Default::Get >::type Node_visitor; - typedef typename Default::Get::type Node_storage_type; - typedef typename Default::Get > ::type PolyhedronPointPMap; - typedef typename Default::Get::type Use_const_polyhedron; - typedef typename Default::Get::value_type >::Kernel >::type Kernel; - -//typedefs - typedef typename Kernel::Triangle_3 Triangle; - typedef typename Kernel::Segment_3 Segment; - typedef internal_IOP:: - Polyhedron_types Polyhedron_types; - - typedef typename Polyhedron_types::Polyhedron_ref Polyhedron_ref; - typedef typename Polyhedron_types::Halfedge_handle Halfedge_handle; - typedef typename Polyhedron_types::Halfedge_iterator Halfedge_iterator; - typedef typename Polyhedron_types::Facet_iterator Facet_iterator; - typedef typename Polyhedron_types::Facet_handle Facet_handle; - typedef typename Polyhedron_types::Vertex_handle Vertex_handle; - typedef typename Polyhedron_types::Vertex Vertex; - typedef typename Polyhedron_types::Facet Facet; - typedef CGAL::Box_intersection_d::Box_with_handle_d< - double, 3, Halfedge_handle> Box; - typedef std::pair Facet_pair; - typedef std::pair Facet_pair_and_int; - - typedef internal_IOP:: - Compare_handles Compare_handles; - - typedef internal_IOP:: - Compare_handle_pairs Compare_handle_pairs; - - - typedef std::map,Compare_handle_pairs> Facets_to_nodes_map;//Indeed the boundary of the intersection of two coplanar triangles may contain several segments. - typedef std::set Coplanar_facets_set;//any insertion should be done with make_sorted_pair_of_facets - typedef typename Kernel::Point_3 Node; - typedef internal_IOP::Triangle_segment_intersection_points - Nodes_vector; - - typedef typename internal_IOP:: - Intersection_types - ::Intersection_result Intersection_result; - - typedef std::set Facet_set; - - typedef std::map - Edge_to_intersected_facets; - #ifdef USE_DETECTION_MULTIPLE_DEFINED_EDGES - typedef std::set Coplanar_duplicated_intersection_set; - #endif -//member data - PolyhedronPointPMap ppmap; -//helper functions - static inline Facet_pair - make_sorted_pair_of_facets(Facet_handle fh1,Facet_handle fh2) { - const Facet* f1=&(*fh1); - const Facet* f2=&(*fh2); - if (f1 < f2) - return Facet_pair(fh1,fh2); - return Facet_pair(fh2,fh1); - } - - static inline Facet_pair_and_int - make_sorted_pair_of_facets_with_int(Facet_handle fh1,Facet_handle fh2,int i) { - return std::make_pair(make_sorted_pair_of_facets(fh1,fh2),i); - } - - static inline std::pair make_sorted_void_pair(void* v1,void* v2){ - if (v1opposite()) ) return h; - return h->opposite(); - } - -//member variables - Edge_to_intersected_facets edge_to_sfacet; //Associate a segment to a filtered set of facets that may be intersected - Facets_to_nodes_map f_to_node; //Associate a pair of triangle to their intersection points - Coplanar_facets_set coplanar_facets;//Contains all pairs of triangular facets intersecting that are coplanar - Nodes_vector nodes; //Contains intersection points of polyhedra - Node_visitor* visitor; - bool is_default_visitor; //indicates whether the visitor need to be deleted - #ifdef USE_DETECTION_MULTIPLE_DEFINED_EDGES - //this does not occur only when one extremity of an edge is inside a face. - // The problem occur every time an edge or a part of an edge with two incident triangles - // is on the intersection polyline, I choose to direcly filter the output by removing duplicated edges - Coplanar_duplicated_intersection_set coplanar_duplicated_intersection;//Set containing edges that are duplicated because of edges (partially) included in a triangle - #endif - -//functions that should come from a traits class - bool has_at_least_two_incident_faces(Halfedge_handle edge) - { - return !edge->is_border_edge(); - } - - template - void get_incident_facets(Halfedge_handle edge,Output_iterator out){ - if (!edge->is_border()) *out++=edge->facet(); - if (!edge->opposite()->is_border()) *out++=edge->opposite()->facet(); - } - - template - void get_incident_edges_to_vertex(Halfedge_handle edge,Output_iterator out){ - Halfedge_handle current=edge; - do{ - *out++=current; - current=current->next()->opposite(); - } - while(current!=edge); - } - -//internal functions - - class Map_edge_facet_bbox_intersection { - Edge_to_intersected_facets& edge_to_sfacet; - Polyhedron_ref polyhedron_triangle; - Polyhedron_ref polyhedron_edge; - Node_visitor& visitor; - public: - Map_edge_facet_bbox_intersection(Edge_to_intersected_facets& map_, - Polyhedron_ref P, - Polyhedron_ref Q, - Node_visitor& visitor_) - :edge_to_sfacet(map_),polyhedron_triangle(P),polyhedron_edge(Q),visitor(visitor_){} - - void operator()( const Box* fb, const Box* eb) const { - Halfedge_handle fh = fb->handle(); - Halfedge_handle eh = eb->handle(); - - // The following call to map::insert() attempts an insertion of a pair - // into 'edge_to_sfacet'. If 'eh' is already inserted in the map, - // then the result 'res' is the current entry in the map for 'eh'. - typename Edge_to_intersected_facets::iterator res= - edge_to_sfacet.insert(std::make_pair(eh,Facet_set())).first; - - res->second.insert(fh->facet()); - // That could have been shortened to: - // - // edge_to_sfacet[eh].insert(fh->facet()) - // - // -- Laurent Rineau, 2012/11/01 - - visitor.add_filtered_intersection(eh,fh,polyhedron_edge,polyhedron_triangle); - } - }; - - class Map_edge_facet_bbox_intersection_extract_coplanar { - Edge_to_intersected_facets& edge_to_sfacet; - Coplanar_facets_set& coplanar_facets; - Polyhedron_ref polyhedron_triangle; - Polyhedron_ref polyhedron_edge; - Node_visitor& visitor; - PolyhedronPointPMap ppmap; - public: - Map_edge_facet_bbox_intersection_extract_coplanar( - Edge_to_intersected_facets& map_, - Coplanar_facets_set& coplanar_facets_, - Polyhedron_ref P, - Polyhedron_ref Q, - Node_visitor& visitor_, - PolyhedronPointPMap ppmap) - :edge_to_sfacet(map_),coplanar_facets(coplanar_facets_),polyhedron_triangle(P),polyhedron_edge(Q),visitor(visitor_),ppmap(ppmap) - {} - - void operator()( const Box* fb, const Box* eb) const { - Halfedge_handle fh = fb->handle(); //handle for the face - Halfedge_handle eh = eb->handle(); //handle for the edge - if(eh->is_border()) eh = eh->opposite(); - CGAL_assertion(!eh->is_border()); - - //check if the segment intersects the plane of the facet or if it is included in the plane - const typename Kernel::Point_3 & a = get(ppmap, fh->vertex()); - const typename Kernel::Point_3 & b = get(ppmap, fh->next()->vertex()); - const typename Kernel::Point_3 & c = get(ppmap, fh->next()->next()->vertex()); - const Orientation abcp = orientation(a,b,c, get(ppmap, eh->vertex())); - const Orientation abcq = orientation(a,b,c, get(ppmap, eh->opposite()->vertex())); - if (abcp==abcq){ - if (abcp!=COPLANAR){ -// std::cout << "rejected " << &(*fh->facet()) << "{" << &(*eh->facet()) << " " <<&(*eh->opposite()->facet()) << " "<< get(ppmap, eh->vertex()) << " " << get(eh->opposite()->vertex()) << "}" <is_border() && */ orientation(a,b,c,get(ppmap, eh->next()->vertex()))==COPLANAR){ - coplanar_facets.insert(make_sorted_pair_of_facets(eh->facet(),fh->facet())); - } - if (!eh->opposite()->is_border() && orientation(a,b,c,get(ppmap, eh->opposite()->next()->vertex()))==COPLANAR){ - coplanar_facets.insert(make_sorted_pair_of_facets(eh->opposite()->facet(),fh->facet())); - } - visitor.add_filtered_intersection(eh,fh,polyhedron_edge,polyhedron_triangle); - //in case only the edge is coplanar, the intersection points will be detected using an incident facet - //(see remark at the beginning of the file) - return; - } - - typename Edge_to_intersected_facets::iterator res= - edge_to_sfacet.insert(std::make_pair(eh,Facet_set())).first; - res->second.insert(fh->facet()); - visitor.add_filtered_intersection(eh,fh,polyhedron_edge,polyhedron_triangle); - } - }; - - class Map_edge_facet_bbox_intersection_extract_coplanar_filter_self_intersections { - Polyhedron_ref polyhedron_triangle; - Polyhedron_ref polyhedron_edge; - - std::set& m_reported_facets; - std::vector >& m_intersecting_bboxes; - PolyhedronPointPMap ppmap; - public: - Map_edge_facet_bbox_intersection_extract_coplanar_filter_self_intersections( - Polyhedron_ref P, - Polyhedron_ref Q, - std::set& reported_facets, - std::vector >& intersecting_bboxes, - PolyhedronPointPMap ppmap - ) - : polyhedron_triangle(P) - , polyhedron_edge(Q) - , m_reported_facets(reported_facets) - , m_intersecting_bboxes(intersecting_bboxes) - , ppmap(ppmap) - {} - - void operator()( const Box* fb, const Box* eb) { - m_reported_facets.insert( fb->handle()->facet() ); - m_intersecting_bboxes.push_back( std::make_pair(fb, eb) ); - } - - bool self_intersections_found() - { - try{ - typedef typename CGAL::Box_intersection_d::Box_with_info_d Box; - - // make one box per facet - std::vector boxes; - boxes.reserve(m_reported_facets.size()); - - BOOST_FOREACH(Facet_handle fh, m_reported_facets) - { - boxes.push_back( Box( fh->halfedge()->vertex()->point().bbox() + - fh->halfedge()->next()->vertex()->point().bbox() + - fh->halfedge()->opposite()->vertex()->point().bbox(), fh ) - ); - } - - // generate box pointers - std::vector box_ptr; - box_ptr.reserve(boxes.size()); - typename std::vector::iterator b; - for(b = boxes.begin(); - b != boxes.end(); - b++) - box_ptr.push_back(&*b); - - // compute self-intersections filtered out by boxes - typedef boost::function_output_iterator OutputIterator; - OutputIterator out; - internal::Intersect_facets - intersect_facets(polyhedron_triangle, out, ppmap, Kernel()); - std::ptrdiff_t cutoff = 2000; - CGAL::box_self_intersection_d(box_ptr.begin(), box_ptr.end(),intersect_facets,cutoff); - return false; - } - catch( internal::Throw_at_output::Throw_at_output_exception& ) - { - return true; - } - } - }; - - // This function tests the intersection of the faces of P with the edges of Q - void filter_intersections( Polyhedron_ref P, Polyhedron_ref Q) { - std::vector facet_boxes, edge_boxes; - facet_boxes.reserve( P.size_of_facets()); - for ( Facet_iterator i = P.facets_begin(); i != P.facets_end(); ++i){ - facet_boxes.push_back( - Box( get(ppmap, i->halfedge()->vertex()).bbox() - + get(ppmap, i->halfedge()->next()->vertex()).bbox() - + get(ppmap, i->halfedge()->next()->next()->vertex()).bbox(), - i->halfedge())); - } - std::vector facet_box_ptr; - facet_box_ptr.reserve( P.size_of_facets()); - for ( typename std::vector::iterator j = facet_boxes.begin(); j != facet_boxes.end(); ++j){ - facet_box_ptr.push_back( &*j); - } - - for ( Halfedge_iterator i = Q.halfedges_begin(); i != Q.halfedges_end(); ++i){ - if(&*i < &*(i->opposite())){ - edge_boxes.push_back( - Box( get(ppmap, i->vertex()).bbox() - + get(ppmap, i->opposite()->vertex()).bbox(), - i)); - } - } - - std::vector edge_box_ptr; - edge_box_ptr.reserve( Q.size_of_halfedges()/2); - for ( typename std::vector::iterator j = edge_boxes.begin(); j != edge_boxes.end(); ++j){ - edge_box_ptr.push_back( &*j); - } - - #ifdef CGAL_COREFINEMENT_DO_REPORT_SELF_INTERSECTIONS - // this version first collect faces involved in the intersection and first - // check if they are involved in a self-intersection. - std::set reported_facets; - std::vector > intersecting_bboxes; - Map_edge_facet_bbox_intersection_extract_coplanar_filter_self_intersections - inter_functor4selfi(P, Q, reported_facets, intersecting_bboxes, ppmap); - CGAL::box_intersection_d( facet_box_ptr.begin(), facet_box_ptr.end(), - edge_box_ptr.begin(), edge_box_ptr.end(), - inter_functor4selfi, std::ptrdiff_t(2000) ); - - if (inter_functor4selfi.self_intersections_found()) throw Intersection_of_Polyhedra_3_self_intersection_exception(); - - #ifdef DO_NOT_HANDLE_COPLANAR_FACETS - Map_edge_facet_bbox_intersection inter_functor(edge_to_sfacet,P,Q,*visitor); - #else // not DO_NOT_HANDLE_COPLANAR_FACETS - Map_edge_facet_bbox_intersection_extract_coplanar inter_functor(edge_to_sfacet,coplanar_facets,P,Q,*visitor,ppmap); - #endif // not DO_NOT_HANDLE_COPLANAR_FACETS - - typedef std::pair Type_pair; - BOOST_FOREACH(const Type_pair& p, intersecting_bboxes) - inter_functor(p.first, p.second); - #else - CGAL::box_intersection_d( facet_box_ptr.begin(), facet_box_ptr.end(), - edge_box_ptr.begin(), edge_box_ptr.end(), - #ifdef DO_NOT_HANDLE_COPLANAR_FACETS - // Note that 'edge_to_sfacet' is passed by - // non-const reference, here, to be filled. - Map_edge_facet_bbox_intersection(edge_to_sfacet,P,Q,*visitor), - #else // not DO_NOT_HANDLE_COPLANAR_FACETS - Map_edge_facet_bbox_intersection_extract_coplanar(edge_to_sfacet,coplanar_facets,P,Q,*visitor,ppmap), - #endif // not DO_NOT_HANDLE_COPLANAR_FACETS - std::ptrdiff_t(2000) - ); - #endif - } - - - void add_intersection_point_to_facet_and_all_edge_incident_facets(Facet_handle facet, - Halfedge_handle edge, - int node_id) - { - std::vector incident_facets; - get_incident_facets(edge,std::back_inserter(incident_facets)); - for (typename std::vector::iterator it=incident_facets.begin(); - it!=incident_facets.end();++it) - { - CGAL_assertion(cgal_do_intersect_debug(facet,*it)); - - Facet_pair facet_pair = make_sorted_pair_of_facets(facet,*it); - if ( !coplanar_facets.empty() && coplanar_facets.find(facet_pair)!=coplanar_facets.end() ) continue; - typename Facets_to_nodes_map::iterator it_list= - f_to_node.insert( std::make_pair( Facet_pair_and_int(facet_pair,0),std::set()) ).first; - it_list->second.insert(node_id); - } - } - - void cip_handle_case_edge(int node_id, - Facet_set* fset, - Halfedge_handle edge, - Halfedge_handle edge_intersected) - { - //associate the intersection point to all facets incident to the intersected edge using edge - std::vector incident_facets; - get_incident_facets(edge_intersected,std::back_inserter(incident_facets)); - for (typename std::vector::iterator it=incident_facets.begin(); - it!=incident_facets.end();++it) - { - add_intersection_point_to_facet_and_all_edge_incident_facets(*it,edge,node_id); - if (fset!=NULL) fset->erase(*it); - } - incident_facets.clear(); - - //associate the intersection point to all facets incident to edge using the intersected edge - //at least one pair of facets is already handle above - - typename Edge_to_intersected_facets::iterator it_fset=edge_to_sfacet.find(edge_intersected); - if (it_fset==edge_to_sfacet.end()) return; - Facet_set& fset_bis=it_fset->second; - get_incident_facets(edge,std::back_inserter(incident_facets)); - for (typename std::vector::iterator it=incident_facets.begin(); - it!=incident_facets.end();++it) - { -// add_intersection_point_to_facet_and_all_edge_incident_facets(*it,edge_intersected,node_id); //this call is not needed, already done in the first loop - fset_bis.erase(*it); - } - } - - void cip_handle_case_vertex(int node_id, - Facet_set* fset, - Halfedge_handle edge, - Halfedge_handle vertex_intersected) - { - std::vector incident_halfedges; - get_incident_edges_to_vertex(vertex_intersected,std::back_inserter(incident_halfedges)); - for (typename std::vector::iterator - it=incident_halfedges.begin();it!=incident_halfedges.end();++it) - { - cip_handle_case_edge(node_id,fset,edge,*it); - } - } - - //add a new node in the final graph. - //it is the intersection of the triangle with the segment - void add_new_node(Halfedge_handle edge, - Facet_handle facet, - const Intersection_result& inter_res, - Nodes_vector& nodes) - { - bool is_vertex_coplanar = CGAL::cpp11::get<2>(inter_res); - if (is_vertex_coplanar) - nodes.add_new_node(get(ppmap, edge->vertex())); - else{ - bool is_opposite_vertex_coplanar = CGAL::cpp11::get<3>(inter_res); - if (is_opposite_vertex_coplanar) - nodes.add_new_node(get(ppmap, edge->opposite()->vertex())); - else - nodes.add_new_node(edge,facet); - } - } - - //either the exact or input point can be used to create a node - //with this function - template - void add_new_node(const Point& pt) - { - nodes.add_new_node(pt); - } - - - #ifdef USE_DETECTION_MULTIPLE_DEFINED_EDGES - void check_coplanar_edge(Halfedge_handle hedge,Facet_handle facet) - { - const typename Kernel::Point_3& p0=get(ppmap, facet->halfedge()->vertex()); - const typename Kernel::Point_3& p1=get(ppmap, facet->halfedge()->next()->vertex()); - const typename Kernel::Point_3& p2=get(ppmap, facet->halfedge()->opposite()->vertex()); - CGAL_precondition( orientation( p0,p1,p2,get(ppmap, hedge->vertex()) ) == COPLANAR ); - - if ( has_at_least_two_incident_faces(hedge) && orientation( p0,p1,p2,get(ppmap, hedge->opposite()->vertex()) ) == COPLANAR ) - { - //In case two facets are incident along such this edge, the intersection - //will be reported twice. We keep track of this so that at the end, we can remove one intersecting edge out of the two - //choose the smaller of the two faces (only one need to de deleted) - Facet_handle smaller=make_sorted_pair_of_facets(hedge->face(),hedge->opposite()->face()).first; - coplanar_duplicated_intersection.insert(make_sorted_pair_of_facets(smaller,facet)); - } - } - - bool are_incident_facets_coplanar(Halfedge_handle hedge){ - const typename Kernel::Point_3& p0=get(ppmap, hedge->vertex()); - const typename Kernel::Point_3& p1=get(ppmap, hedge->next()->vertex()); - const typename Kernel::Point_3& p2=get(ppmap, hedge->opposite()->vertex()); - const typename Kernel::Point_3& p3=get(ppmap, hedge->opposite()->next()->vertex()); - return orientation( p0,p1,p2,p3 ) == COPLANAR; - } - - void check_coplanar_edge(Halfedge_handle hedge,Halfedge_handle additional_edge,internal_IOP::Intersection_type type) - { - switch(type){ - case internal_IOP::FACET: - check_coplanar_edge(hedge,additional_edge->face()); - break; - - case internal_IOP::EDGE: - if ( !additional_edge->is_border() ){ - check_coplanar_edge(hedge,additional_edge->face()); - } - if (!additional_edge->opposite()->is_border()) - check_coplanar_edge(hedge,additional_edge->opposite()->face()); - break; - case internal_IOP::VERTEX: - { - //consider all incident faces - Halfedge_handle current=additional_edge; - do{ - if( !current->is_border() ) - check_coplanar_edge(hedge,current->face()); - current=current->next()->opposite(); - } - while(current!=additional_edge); - } - break; - - default: - CGAL_assertion(type==internal_IOP::COPLNR); - break; - } - } - - void check_coplanar_edge_old(Halfedge_handle hedge,Halfedge_handle additional_edge,internal_IOP::Intersection_type type) - { - switch(type){ - case internal_IOP::FACET: - check_coplanar_edge(hedge,additional_edge->face()); - break; - - case internal_IOP::EDGE: - { - if ( !additional_edge->is_border() ){ - if (!additional_edge->opposite()->is_border()){ - if ( are_incident_facets_coplanar(additional_edge) ) - { - Facet_handle facet=additional_edge->face(); - const typename Kernel::Point_3& p0=get(ppmap, facet->halfedge()->vertex()); - const typename Kernel::Point_3& p1=get(ppmap, facet->halfedge()->next()->vertex()); - const typename Kernel::Point_3& p2=get(ppmap, facet->halfedge()->opposite()->vertex()); - CGAL_precondition( orientation( p0,p1,p2, get(ppmap, hedge->vertex()) ) == COPLANAR ); - - if ( has_at_least_two_incident_faces(hedge) && orientation( p0,p1,p2, get(ppmap, hedge->opposite()->vertex()) ) == COPLANAR ) - { - //In case two facets are incident along a common edge of two coplanar triangles. - //We need to remove three out of the four reported pair - Facet_handle smaller=make_sorted_pair_of_facets(hedge->face(),hedge->opposite()->face()).first; - coplanar_duplicated_intersection.insert(make_sorted_pair_of_facets(hedge->face(),facet)); - coplanar_duplicated_intersection.insert(make_sorted_pair_of_facets(hedge->opposite()->face(),facet)); - coplanar_duplicated_intersection.insert(make_sorted_pair_of_facets(hedge->opposite()->face(),additional_edge->opposite()->face())); - } - } - else - { - check_coplanar_edge(hedge,additional_edge->face()); - check_coplanar_edge(hedge,additional_edge->opposite()->face()); - } - } - else - check_coplanar_edge(hedge,additional_edge->face()); - } - else{ - CGAL_assertion(!additional_edge->opposite()->is_border()); - check_coplanar_edge(hedge,additional_edge->opposite()->face()); - } - } - break; - case internal_IOP::VERTEX: - - break; - - default: - CGAL_assertion(type==internal_IOP::COPLNR); - break; - } - } - - template - void check_coplanar_edges(Hedge_iterator begin,Hedge_iterator end,Halfedge_handle additional_edge,internal_IOP::Intersection_type type) - { - for (Hedge_iterator it=begin;it!=end;++it) - check_coplanar_edge(*it,additional_edge,type); - } - #endif // USE_DETECTION_MULTIPLE_DEFINED_EDGES - - void print_type_debug(internal_IOP::Intersection_type type,bool cpl,bool opp_cpl) - { - switch(type){ - case internal_IOP::COPLNR: - std::cout << "COPLNR " << cpl << " " << opp_cpl << std::endl; - break; - case internal_IOP::EMPTY: - std::cout << "EMPTY " << cpl << " " << opp_cpl << std::endl; - break; - - case internal_IOP::FACET: - std::cout << "FACET " << cpl << " " << opp_cpl << std::endl; - break; - - case internal_IOP::EDGE: - std::cout << "EDGE " << cpl << " " << opp_cpl << std::endl; - break; - case internal_IOP::VERTEX: - std::cout << "VERTEX " << cpl << " " << opp_cpl << std::endl; - break; - } - } - - - void handle_coplanar_case_VERTEX_FACET(Halfedge_handle vertex,Halfedge_handle facet,int node_id, bool is_new_node){ - if (is_new_node) - visitor->new_node_added(node_id,internal_IOP::FACET,vertex,facet,true,false); - std::vector all_edges; - get_incident_edges_to_vertex(vertex,std::back_inserter(all_edges)); - typename std::vector::iterator it_edge=all_edges.begin(); - for (;it_edge!=all_edges.end();++it_edge){ - add_intersection_point_to_facet_and_all_edge_incident_facets(facet->facet(),*it_edge,node_id); - typename Edge_to_intersected_facets::iterator it_ets=edge_to_sfacet.find(*it_edge); - if (it_ets!=edge_to_sfacet.end()) it_ets->second.erase(facet->facet()); - } - } - - void handle_coplanar_case_VERTEX_EDGE(Halfedge_handle vertex,Halfedge_handle edge,int node_id, bool is_new_node){ - if(is_new_node) - visitor->new_node_added(node_id,internal_IOP::VERTEX,edge,vertex,false,false); - std::vector all_edges; - get_incident_edges_to_vertex(vertex,std::back_inserter(all_edges)); - typename std::vector::iterator it_edge=all_edges.begin(); - for (;it_edge!=all_edges.end();++it_edge){ - typename Edge_to_intersected_facets::iterator it_ets=edge_to_sfacet.find(*it_edge); - Facet_set* fset = (it_ets!=edge_to_sfacet.end())?&(it_ets->second):NULL; - cip_handle_case_edge(node_id,fset,*it_edge,edge); - } - } - - void handle_coplanar_case_VERTEX_VERTEX(Halfedge_handle vertex1,Halfedge_handle vertex2,int node_id, bool is_new_node){ - if (is_new_node) - visitor->new_node_added(node_id,internal_IOP::VERTEX,vertex2,vertex1,true,false); - std::vector all_edges; - get_incident_edges_to_vertex(vertex1,std::back_inserter(all_edges)); - typename std::vector::iterator it_edge=all_edges.begin(); - for (;it_edge!=all_edges.end();++it_edge){ - typename Edge_to_intersected_facets::iterator it_ets=edge_to_sfacet.find(*it_edge); - Facet_set* fset = (it_ets!=edge_to_sfacet.end())?&(it_ets->second):NULL; - cip_handle_case_vertex(node_id,fset,*it_edge,vertex2); - } - } - - - template - std::pair get_or_create_node(Cpl_inter_pt& ipt,int& current_node,Coplanar_node_map& coplanar_node_map){ - void *v1, *v2; - switch(ipt.type_1){ - case internal_IOP::VERTEX: v1=(void*)( &(*(ipt.info_1->vertex())) ); break; - case internal_IOP::EDGE : v1=(void*)( &(*smaller_handle(ipt.info_1)) ); break; - case internal_IOP::FACET : v1=(void*)( &(*(ipt.info_1->facet())) ); break; - default: CGAL_error_msg("Should not get there!"); - } - - switch(ipt.type_2){ - case internal_IOP::VERTEX: v2=(void*)( &(*(ipt.info_2->vertex())) ); break; - case internal_IOP::EDGE : v2=(void*)( &(*smaller_handle(ipt.info_2)) ); break; - case internal_IOP::FACET : v2=(void*)( &(*(ipt.info_2->facet())) ); break; - default: CGAL_error_msg("Should not get there!"); - } - - std::pair key=make_sorted_void_pair(v1,v2); - - std::pair res=coplanar_node_map.insert(std::make_pair(key,current_node+1)); - if (res.second){ //insert a new node - - if (ipt.type_1==internal_IOP::VERTEX) - add_new_node(get(ppmap, ipt.info_1->vertex())); - else{ - if(ipt.type_2==internal_IOP::VERTEX) - add_new_node(get(ppmap, ipt.info_2->vertex())); - else - add_new_node(ipt.point); - } - return std::pair(++current_node, true); - } - return std::pair(res.first->second, false); - } - - void compute_intersection_of_coplanar_facets(int& current_node){ - typedef std::map,int> Coplanar_node_map; - Coplanar_node_map coplanar_node_map; - - for (typename Coplanar_facets_set::iterator it=coplanar_facets.begin();it!=coplanar_facets.end();++it){ - Facet_handle f1=it->first; - Facet_handle f2=it->second; - typedef internal_IOP::Intersection_point_with_info Cpl_inter_pt; - std::list inter_pts; - internal_IOP::intersection_coplanar_facets(f1->halfedge(),f2->halfedge(),ppmap,inter_pts); -// std::cout << "found " << inter_pts.size() << " inter pts: "; - std::size_t nb_pts=inter_pts.size(); - if (inter_pts.empty()) continue; - std::vector cpln_nodes; cpln_nodes.reserve(nb_pts); - for (typename std::list::iterator iti=inter_pts.begin();iti!=inter_pts.end();++iti){ - #ifdef CGAL_COREFINEMENT_DEBUG - //iti->print_debug(); - #endif - CGAL_assertion(iti->is_valid()); - int node_id; - bool is_new_node; - cpp11::tie(node_id, is_new_node)=get_or_create_node(*iti,current_node,coplanar_node_map); - cpln_nodes.push_back(node_id); - - switch(iti->type_1){ - case internal_IOP::VERTEX: - { - switch(iti->type_2){ - case internal_IOP::VERTEX: handle_coplanar_case_VERTEX_VERTEX(iti->info_1,iti->info_2,node_id, is_new_node); break; - case internal_IOP::EDGE : handle_coplanar_case_VERTEX_EDGE(iti->info_1,iti->info_2,node_id, is_new_node); break; - case internal_IOP::FACET : handle_coplanar_case_VERTEX_FACET(iti->info_1,iti->info_2,node_id, is_new_node); break; - default: CGAL_error_msg("Should not get there!"); - } - } - break; - case internal_IOP::EDGE:{ - switch(iti->type_2){ - case internal_IOP::VERTEX:handle_coplanar_case_VERTEX_EDGE(iti->info_2,iti->info_1,node_id, is_new_node);break; - case internal_IOP::EDGE: - { - if (is_new_node) - visitor->new_node_added(node_id,internal_IOP::EDGE,iti->info_1,iti->info_2,false,false); - typename Edge_to_intersected_facets::iterator it_ets=edge_to_sfacet.find(iti->info_1); - Facet_set* fset = (it_ets!=edge_to_sfacet.end())?&(it_ets->second):NULL; - cip_handle_case_edge(node_id,fset,iti->info_1,iti->info_2); - } - break; - default: CGAL_error_msg("Should not get there!"); - } - } - break; - - case internal_IOP::FACET: - { - CGAL_assertion(iti->type_2==internal_IOP::VERTEX); - handle_coplanar_case_VERTEX_FACET(iti->info_2,iti->info_1,node_id, is_new_node); - } - break; - - default: CGAL_error_msg("Should not get there!"); - } - } - switch (nb_pts){ - case 0: break; - case 1: - { - typename Facets_to_nodes_map::iterator it_list= - f_to_node.insert( std::make_pair( Facet_pair_and_int(*it,1),std::set()) ).first; - it_list->second.insert(cpln_nodes[0]); - } - break; - default: - { - int i=0; - std::size_t stop=nb_pts + (nb_pts<3?-1:0); - for (std::size_t k=0;k()) ).first; - it_list->second.insert( cpln_nodes[k] ); - it_list->second.insert( cpln_nodes[(k+1)%nb_pts] ); - } - } - } -// std::cout << std::endl; - } - } - - void compute_intersection_points(int& current_node){ - for(typename Edge_to_intersected_facets::iterator it=edge_to_sfacet.begin();it!=edge_to_sfacet.end();++it){ - Halfedge_handle edge=it->first; - Facet_set& fset=it->second; - while (!fset.empty()){ - Facet_handle facet=*fset.begin(); - - Intersection_result res=internal_IOP::do_intersect(edge,facet,ppmap); - internal_IOP::Intersection_type type=CGAL::cpp11::get<0>(res); - - //handle degenerate case: one extremity of edge below to facet - std::vector all_edges; - if ( CGAL::cpp11::get<2>(res) ) - get_incident_edges_to_vertex(edge,std::back_inserter(all_edges)); - else{ - if ( CGAL::cpp11::get<3>(res) ) - get_incident_edges_to_vertex(edge->opposite(),std::back_inserter(all_edges)); - else - all_edges.push_back(edge); - } - - CGAL_precondition(*all_edges.begin()==edge || *all_edges.begin()==edge->opposite()); -// print_type_debug(type,CGAL::cpp11::get<2>(res),CGAL::cpp11::get<3>(res)); - - #ifdef USE_DETECTION_MULTIPLE_DEFINED_EDGES - check_coplanar_edges(boost::next(all_edges.begin()),all_edges.end(),CGAL::cpp11::get<1>(res),type); - #endif - - typename std::vector::iterator it_edge=all_edges.begin(); - switch(type){ - case internal_IOP::COPLNR: - #ifndef DO_NOT_HANDLE_COPLANAR_FACETS - assert(!"COPLNR : this point should never be reached!"); - #else - //nothing need to be done, cf. comments at the beginning of the file - #endif - break; - case internal_IOP::EMPTY: - fset.erase(fset.begin()); - CGAL_assertion(!cgal_do_intersect_debug(edge,facet)); - break; - - // Case when the edge pierces the facet in its interior. - case internal_IOP::FACET: - { - CGAL_assertion(cgal_do_intersect_debug(edge,facet)); - CGAL_assertion(facet==CGAL::cpp11::get<1>(res)->face()); - - int node_id=++current_node; - add_new_node(edge,facet,res,nodes); - visitor->new_node_added(node_id,internal_IOP::FACET,edge,facet->halfedge(),CGAL::cpp11::get<2>(res),CGAL::cpp11::get<3>(res)); - for (;it_edge!=all_edges.end();++it_edge){ - add_intersection_point_to_facet_and_all_edge_incident_facets(facet,*it_edge,node_id); - //erase facet from the list to test intersection with it_edge - if ( it_edge==all_edges.begin() ) - fset.erase(fset.begin()); - else - { - typename Edge_to_intersected_facets::iterator it_ets=edge_to_sfacet.find(*it_edge); - if(it_ets!=edge_to_sfacet.end()) it_ets->second.erase(facet); - } - } - } // end case FACET - break; - - // Case when the edge intersect one edge of the facet. - case internal_IOP::EDGE: - { - CGAL_assertion(cgal_do_intersect_debug(edge,facet)); - int node_id=++current_node; - add_new_node(edge,facet,res,nodes); - Halfedge_handle edge_intersected=CGAL::cpp11::get<1>(res); - visitor->new_node_added(node_id,internal_IOP::EDGE,edge,edge_intersected,CGAL::cpp11::get<2>(res),CGAL::cpp11::get<3>(res)); - for (;it_edge!=all_edges.end();++it_edge){ - if ( it_edge!=all_edges.begin() ){ - typename Edge_to_intersected_facets::iterator it_ets=edge_to_sfacet.find(*it_edge); - Facet_set* fset_bis = (it_ets!=edge_to_sfacet.end())?&(it_ets->second):NULL; - cip_handle_case_edge(node_id,fset_bis,*it_edge,edge_intersected); - } - else - cip_handle_case_edge(node_id,&fset,*it_edge,edge_intersected); - } - } // end case EDGE - break; - - case internal_IOP::VERTEX: - { - CGAL_assertion(cgal_do_intersect_debug(edge,facet)); - int node_id=++current_node; - Halfedge_handle vertex_intersected=CGAL::cpp11::get<1>(res); - add_new_node(get(ppmap, vertex_intersected->vertex())); //we use the original vertex to create the node - //before it was internal_IOP::FACET but do not remember why, probably a bug... - visitor->new_node_added(node_id,internal_IOP::VERTEX,edge,vertex_intersected,CGAL::cpp11::get<2>(res),CGAL::cpp11::get<3>(res)); - for (;it_edge!=all_edges.end();++it_edge){ - if ( it_edge!=all_edges.begin() ){ - typename Edge_to_intersected_facets::iterator it_ets=edge_to_sfacet.find(*it_edge); - Facet_set* fset_bis = (it_ets!=edge_to_sfacet.end())?&(it_ets->second):NULL; - cip_handle_case_vertex(node_id,fset_bis,*it_edge,vertex_intersected); - } - else - cip_handle_case_vertex(node_id,&fset,*it_edge,vertex_intersected); - } - } // end case VERTEX - break; - - } // end switch on the type of the intersection - } // end loop on all facets that intersect the edge - } // end loop on all entries (edges) in 'edge_to_sfacet' - CGAL_assertion(nodes.size()==unsigned(current_node+1)); - } - - #ifdef USE_DETECTION_MULTIPLE_DEFINED_EDGES - void remove_duplicated_intersecting_edges() - { - for (typename Coplanar_duplicated_intersection_set::iterator - it=coplanar_duplicated_intersection.begin(); - it!=coplanar_duplicated_intersection.end(); - ++it) - { - typename Facets_to_nodes_map::iterator res=f_to_node.find(*it); - //CGAL_assertion(res!=f_to_node.end()); - //we cannot use a precondition here: in some cases the coplanarity test is not sufficient. - //if on surface1 we have to coplanar triangle incident along edge e. Then in surface2, take a triangle - //with one edge inside one triangle of surface1 such that one vertex in on e. Then the collinearity test - //return true for both faces but only one implies a duplicated edge - if(res!=f_to_node.end()) - f_to_node.erase(res); - } - } - #else // not USE_DETECTION_MULTIPLE_DEFINED_EDGES - void remove_duplicated_intersecting_edges() - { - std::set< std::pair > already_seen; - std::list to_erase; - for (typename Facets_to_nodes_map::iterator - it=f_to_node.begin(); - it!=f_to_node.end(); - ++it - ) - { - if (it->second.size()==1) continue; - CGAL_precondition(it->second.size()==2); - //it->second is a set so int are already sorted - bool is_new=already_seen.insert( std::make_pair( - *(it->second.begin()), - *boost::next(it->second.begin()) ) - ).second; - - if (!is_new) - to_erase.push_back(it); - } - - for ( typename std::list::iterator - it=to_erase.begin();it!=to_erase.end();++it - ) - f_to_node.erase(*it); - } - #endif // not USE_DETECTION_MULTIPLE_DEFINED_EDGES - - - struct Graph_node{ - std::set neighbors; - unsigned degree; - - Graph_node():degree(0){} - - void insert(int i){ - ++degree; - CGAL_assertion(neighbors.find(i)==neighbors.end()); - neighbors.insert(i); - } - - void erase(int i){ - CGAL_assertion(neighbors.find(i)!=neighbors.end()); - neighbors.erase(i); - } - void make_terminal() {if (degree==2) degree=45;} - bool is_terminal()const {return degree!=2;} - bool empty() const {return neighbors.empty();} - int top() const {return *neighbors.begin();} - void pop() { - CGAL_assertion(!neighbors.empty()); - neighbors.erase(neighbors.begin()); - } - }; - - - template - void construct_polylines(Nodes_vector& nodes,Output_iterator out){ - std::size_t nb_nodes=nodes.size(); - std::vector graph(nb_nodes); - //counts the number of time each node has been seen - bool isolated_point_seen=false; - for (typename Facets_to_nodes_map::iterator it=f_to_node.begin();it!=f_to_node.end();++it){ - const std::set& segment=it->second; - CGAL_assertion(segment.size()==2 || segment.size()==1); - if (segment.size()==2){ - int i=*segment.begin(); - int j=*boost::next(segment.begin()); - graph[i].insert(j); - graph[j].insert(i); - } - else{ - CGAL_assertion(segment.size()==1); - isolated_point_seen=true; - } - } - - //visitor call - visitor->annotate_graph(graph.begin(),graph.end()); - - //collect terminal and interior nodes - boost::dynamic_bitset<> terminal_nodes(nb_nodes), interior_nodes(nb_nodes); - for (std::size_t i=0;i(1,nodes[static_cast(i)]); - visitor->start_new_polyline(static_cast(i),static_cast(i)); - terminal_nodes.reset(i); - } - } - - //handle polylines - while(terminal_nodes.any()) - { - int i = static_cast(terminal_nodes.find_first()); - Graph_node& node_i = graph[i]; - std::vector polyline; - - int j=node_i.top(); - visitor->start_new_polyline(i,j); - CGAL_assertion(i!=j); - node_i.pop(); - if (node_i.empty()) - terminal_nodes.reset(i); - polyline.push_back(nodes[i]); - while(true){ - Graph_node& node_j=graph[j]; - CGAL_assertion(!node_j.empty()); - node_j.erase(i); - i=j; - polyline.push_back(nodes[i]); - if (node_j.is_terminal()) - { - if (node_j.empty()) - terminal_nodes.reset(j); - break; - } - else{ - j=node_j.top(); - visitor->add_node_to_polyline(j); - node_j.pop(); - CGAL_assertion(node_j.empty()); - interior_nodes.reset(i); - } - } - *out++=polyline; - } - - //handle cycles - while(interior_nodes.any()) - { - int i=static_cast(interior_nodes.find_first()); - Graph_node& node_i=graph[i]; - std::vector polyline; - - int j=node_i.top(); - visitor->start_new_polyline(i,j); - interior_nodes.reset(i); - polyline.push_back(nodes[i]); - int first=i; - do{ - Graph_node& node_j=graph[j]; - interior_nodes.reset(j); - node_j.erase(i); - i=j; - polyline.push_back(nodes[i]); - j=node_j.top(); - visitor->add_node_to_polyline(j); - }while(j!=first); - polyline.push_back(nodes[j]);// we duplicate first point for cycles - *out++=polyline; - } - } - - int get_other_int(const std::set& s,int i) const { - if (*s.begin()!=i) return *s.begin(); - return *boost::next(s.begin()); - } - - template - bool is_grabbed(const Dispatch_out_it&) const{ - return CGAL::Is_in_tuple::value; - } - - - template - struct Is_dispatch_based_ouput_iterator{ - typedef boost::false_type type; - }; - - template class Dispatch_based_output_it,class V,class O> - struct Is_dispatch_based_ouput_iterator >{ - typedef typename boost::is_base_of< Dispatch_output_iterator, - Dispatch_based_output_it >::type type; - }; - - template - inline void construct_polylines_with_info(Nodes_vector& nodes,Output_iterator out){ - return construct_polylines_with_info(nodes,out,typename Is_dispatch_based_ouput_iterator::type()); - } - - template - void construct_polylines_with_info(Nodes_vector& nodes,Output_iterator out,boost::false_type){ - construct_polylines_with_info(nodes, - dispatch_or_drop_output >(out),boost::true_type()); - } - - template class Dispatch_based_output_it,class V,class O> - void construct_polylines_with_info(Nodes_vector& nodes, - Dispatch_based_output_it out,boost::true_type) - { - typedef typename Facets_to_nodes_map::value_type Edge; - typedef std::list Polyline_info; - - - std::size_t nb_nodes=nodes.size(); - std::vector node_mult(nb_nodes,0); - std::vector terminal_bools(nb_nodes,false); - std::vector< std::set > connections(nb_nodes); - // --counts the number of time each node has been seen - // --associate to each node its incident edges. An edge = a pair of Facet_handle+2 indices of intersection points - for (typename Facets_to_nodes_map::iterator it=f_to_node.begin();it!=f_to_node.end();++it){ - const std::set& segment=it->second; - CGAL_assertion(segment.size()==2 || segment.size()==1); - if (segment.size()==2){ - int i=*segment.begin(); - int j=*boost::next(segment.begin()); - connections[i].insert(&(*it)); - connections[j].insert(&(*it)); - ++(node_mult[i]); - ++(node_mult[j]); - } - } - - //detect terminal nodes and isolated nodes - for (unsigned k=0;k(1,nodes[k]); - if ( is_grabbed >(out)) - *out++=std::vector(); - } - } - - //visitor call - visitor->update_terminal_nodes(terminal_bools); - - //We start from a node N and recursively walk one edge to find other - // node. If this is a cycle we stop when we are back at the node N; - //otherwise we stop at a terminal node and restart a walk from N - //following another edge (if N is not terminal) until finding a terminal - //node. With this we can associate to each edge the pair of facet_handle - //intersecting that define this edge. - unsigned current_node=0; - while (current_node!=nb_nodes){ - if (connections[current_node].empty()){ - ++current_node; - continue; - } - - Edge* edge=*connections[current_node].begin(); - connections[current_node].erase(connections[current_node].begin()); - - Polyline_info polyline_info; - std::list polyline_embedding; - - if ( is_grabbed >(out)) - polyline_info.push_back(edge->first.first); - polyline_embedding.push_back(nodes[current_node]); - - unsigned i=current_node; - unsigned start_node=current_node; - bool reverse=false; - while (true){ - i=get_other_int(edge->second,i); - connections[i].erase(edge); - - if (reverse) polyline_embedding.push_front(nodes[i]); - else polyline_embedding.push_back(nodes[i]); - - if (i==start_node) break; - if (terminal_bools[i]){ - if (reverse || terminal_bools[current_node]) break; - reverse=true; - i=current_node; - if ( connections[i].empty() ) break; - } - - edge=*connections[i].begin(); - connections[i].erase(connections[i].begin()); - - if ( is_grabbed >(out)){ - if (reverse) polyline_info.push_front(edge->first.first); - else polyline_info.push_back(edge->first.first); - } - - } - - *out++=std::vector(polyline_embedding.begin(),polyline_embedding.end()); - if ( is_grabbed >(out)){ - CGAL_assertion(polyline_embedding.size()==polyline_info.size()+1); - *out++=std::vector(polyline_info.begin(),polyline_info.end()); - } - } - } - -//debug functions - - bool cgal_do_intersect_debug(Halfedge_handle eh,Facet_handle fh){ - Triangle t( get(ppmap, fh->halfedge()->vertex()), - get(ppmap, fh->halfedge()->next()->vertex()), - get(ppmap, fh->halfedge()->next()->next()->vertex())); - - Segment s( get(ppmap, eh->vertex()), - get(ppmap, eh->opposite()->vertex())); - - return CGAL::do_intersect( s, t); - } - - bool cgal_do_intersect_debug(Facet_handle fh1,Facet_handle fh2){ - Triangle t1( get(ppmap, fh1->halfedge()->vertex()), - get(ppmap, fh1->halfedge()->next()->vertex()), - get(ppmap, fh1->halfedge()->next()->next()->vertex())); - Triangle t2( get(ppmap, fh2->halfedge()->vertex()), - get(ppmap, fh2->halfedge()->next()->vertex()), - get(ppmap, fh2->halfedge()->next()->next()->vertex())); - - - return CGAL::do_intersect( t1, t2); - } - - void print_f_to_node_debug(){ - std::cout << "print_f_to_node_debug " << &f_to_node << std::endl; - for (typename Facets_to_nodes_map::iterator it=f_to_node.begin();it!=f_to_node.end();++it){ - std::cout << &(*(it->first.first.first)) << " " << &(*(it->first.first.second)) << " " << it->first.second << " -> {"; - std::copy(it->second.begin(),it->second.end(),std::ostream_iterator(std::cout,",")); - std::cout << "}" <& graph){ - for (typename std::map::const_iterator it=graph.begin();it!=graph.end();++it){ - std::cout << it->first << " -> {"; - std::copy(it->second.neighbors.begin(),it->second.neighbors.end(),std::ostream_iterator(std::cout,",")); - std::cout << "}" <second; - std::cout << &fset << " fset size " << fset.size() << std::endl; - } - } - - - template - OutputIterator main_run(OutputIterator out,bool build_polylines=true){ - // std::cout << edge_to_sfacet.size() << std::endl; - int current_node=-1; - - //print_edge_to_sfacet_debug(); - #ifndef DO_NOT_HANDLE_COPLANAR_FACETS - //first handle coplanar triangles - compute_intersection_of_coplanar_facets(current_node); - visitor->set_number_of_intersection_points_from_coplanar_facets(current_node+1); - if (!coplanar_facets.empty()) - visitor->input_have_coplanar_facets(); - #endif // not DO_NOT_HANDLE_COPLANAR_FACETS - //print_edge_to_sfacet_debug(); - //compute intersection points of segments and triangles. - //build node of the graph - //build connectivity info - compute_intersection_points(current_node); // 'current_node' is passed by - // non-const reference - - if (!build_polylines){ - visitor->finalize(nodes); - return out; - } - //remove duplicated intersecting edges: - // In case two facets are incident along such an edge coplanar in a facet of another polyhedron (and one extremity inside the facet), the intersection - // will be reported twice. We kept track (check_coplanar_edge(s)) of this so that, we can remove one intersecting edge out of the two - //print_f_to_node_debug(); - remove_duplicated_intersecting_edges(); - - //std::cout << "f_to_node "<< f_to_node.size() << std::endl; - //print_f_to_node_debug(); - //collect connectivity infos and create polylines - if ( Node_visitor::do_need_vertex_graph ) - construct_polylines(nodes,out); //using the graph approach (at some point we know all connections between intersection points) - else - construct_polylines_with_info(nodes,out); //direct construction by propagation - - visitor->finalize(nodes); - - return out; - } - -public: - Intersection_of_Polyhedra_3(PolyhedronPointPMap ppmap=PolyhedronPointPMap()) - :ppmap(ppmap),nodes(ppmap), - visitor(new Node_visitor()), - is_default_visitor(true){} - Intersection_of_Polyhedra_3(Node_visitor& v, - PolyhedronPointPMap ppmap=PolyhedronPointPMap()) - :ppmap(ppmap),nodes(ppmap),visitor(&v),is_default_visitor(false){} - ~Intersection_of_Polyhedra_3(){if (is_default_visitor) delete visitor;} - //pairwise intersection between all elements in the range - template - OutputIterator - operator()(InputIterator begin, InputIterator end, OutputIterator out) { - for(InputIterator it1=begin;it1!=end;++it1){ - CGAL_precondition( it1->is_pure_triangle() ); - Polyhedron_ref P=*it1; - visitor->new_input_polyhedron(P); - for(InputIterator it2=boost::next(it1);it2!=end;++it2){ - CGAL_precondition( it2->is_pure_triangle() ); - Polyhedron_ref Q=*it2; - filter_intersections(P, Q); - filter_intersections(Q, P); - } - } - - return main_run(out); - } - - //pairwise intersection between all elements in the range - //(pointers version) - template - OutputIterator - operator()(InputIterator begin, InputIterator end, OutputIterator out, int) { - for(InputIterator it1=begin;it1!=end;++it1){ - CGAL_precondition( (*it1)->is_pure_triangle() ); - Polyhedron_ref P=**it1; - visitor->new_input_polyhedron(P); - for(InputIterator it2=boost::next(it1);it2!=end;++it2){ - CGAL_precondition( (*it2)->is_pure_triangle() ); - Polyhedron_ref Q=**it2; - filter_intersections(P, Q); - filter_intersections(Q, P); - } - } - - return main_run(out); - } - - //intersection between P and each element in the range - template - OutputIterator - operator()( Polyhedron_ref P, InputIterator begin, InputIterator end, OutputIterator out) { - CGAL_precondition( P.is_pure_triangle() ); - visitor->new_input_polyhedron(P); - for(InputIterator it=begin;it!=end;++it){ - CGAL_precondition( it->is_pure_triangle() ); - Polyhedron_ref Q=*it; - visitor->new_input_polyhedron(Q); - filter_intersections(P, Q); - filter_intersections(Q, P); - } - return main_run(out); - } - - //intersection between P and each element in the range - //(pointers version) - template - OutputIterator - operator()(Polyhedron_ref P, InputIterator begin, InputIterator end, OutputIterator out, int) { - CGAL_precondition( P.is_pure_triangle() ); - visitor->new_input_polyhedron(P); - for(InputIterator it=begin;it!=end;++it){ - CGAL_precondition( (*it)->is_pure_triangle() ); - Polyhedron_ref Q=**it; - visitor->new_input_polyhedron(Q); - filter_intersections(P, Q); - filter_intersections(Q, P); - } - return main_run(out); - } - - //intersection between P and Q - template - OutputIterator - operator()(Polyhedron_ref P, Polyhedron_ref Q, OutputIterator out) { - visitor->new_input_polyhedron(P); - visitor->new_input_polyhedron(Q); - filter_intersections(P, Q); - filter_intersections(Q, P); - return main_run(out); - } - - //intersection between P and Q, only visitor called not polyline is constructed - void operator()(Polyhedron_ref P, Polyhedron_ref Q) { - visitor->new_input_polyhedron(P); - visitor->new_input_polyhedron(Q); - filter_intersections(P, Q); - filter_intersections(Q, P); - main_run(Emptyset_iterator(),false); - } -}; - -template -OutputIterator -intersection_Polyhedron_3_Polyhedron_3(const Polyhedron& P, const Polyhedron& Q, OutputIterator out) -{ - return Intersection_of_Polyhedra_3()(P,Q,out); -} - -}// namespace CGAL - -#include - -#endif //CGAL_INTERSECTION_OF_POLYHEDRA_3_H - -/* - // Local variables for Emacs: - // - set special indentation of case labels, compared to usual C/C++ - // indentation styles. - Local Variables: - c-file-offsets:((case-label . +)) - End: -*/ diff --git a/Operations_on_polyhedra/include/CGAL/intersection_of_Polyhedra_3_refinement_visitor.h b/Operations_on_polyhedra/include/CGAL/intersection_of_Polyhedra_3_refinement_visitor.h deleted file mode 100644 index 71533b11402..00000000000 --- a/Operations_on_polyhedra/include/CGAL/intersection_of_Polyhedra_3_refinement_visitor.h +++ /dev/null @@ -1,1163 +0,0 @@ -// Copyright (c) 2011 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_INTERSECTION_OF_POLYHEDRA_3_REFINEMENT_VISITOR_H -#define CGAL_INTERSECTION_OF_POLYHEDRA_3_REFINEMENT_VISITOR_H - -#include - -#include - -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -#include -#include -#include - -// TODOCUMENT -// --We suppose that the two input polyhedra are triangulated orientable surfaces. -// --Any polyhedron defines two bounding volumes: one inside and one outside. -// The convention used is the following: the normal of a triangle always indicates -// the outside of the object. -// --The input polyhedra should not touch at only one point locally. If so, the current -// implementation just ignore it (TAG SL001) -// --Polyhedron type should be list-based or should guarantee no reallocation. We use maps -// on pointer of halfedges,facets and vertices -// --Polyhedral_mesh_domain requires the domain tp be closed: do not provided as input -// an open polyhedral surface and a polyhedron with a connected component free from intersection -//OPTIMIZATIONS -// --cdt: try using intervals? in that case, only points inside the face should be considered -// and points on edge should be handled by hand (simply start using the point opposite to the edge) -// --sorted_around_edge_filtered: can be done using the original supporting planes -// --in intersection_of_Polyhedra_3: upon call to Triangle_segment_intersection_point::add_new_node, interval and exact nodes are -// inserted into a vector. Since we do not know the final size of vector this lead to reallocation of data. -// --in Triangle_segment_intersection_point, try using EPEC instead of Interval_nt+SC -// --use a sorted pair of indices in edge_to_hedge+simplify the code TAG_SLXX1 -// --in sew_2_marked_darts arrange how darts are passed to avoid comparing to a Point_3 -//TODO: -// --validity of the embedding: points inserted in polyhedron are approximation of the real -// intersection points. It may happen that because of the approximation, the embedding gets -// wrong. To avoid this, for each new triangle created, we should make an orientation test -// with the approximated point to check if this is correct. If not, points must be moved -// within their double interval so that all triangles incident to each of these points are correctly -// oriented. This is probably an expensive test that can be activated only with a template parameter -// of something similar. -// -- We should have an option to report intersection at a point and refinement of polyhedra -// so that the point is included into the other polyhedra. Please be careful if the point -// already exists bool report_isolated_point or a template parameter (polyline with a unique point) -namespace CGAL -{ - - namespace internal_IOP - { - - template - class Triangulate_a_face : public CGAL::Modifier_base { - typedef typename HDS::Halfedge_handle Halfedge_handle; - typedef typename HDS::Vertex_handle Vertex_handle; - typedef typename HDS::Face_handle Face_handle; - typedef typename HDS::Vertex Vertex; - typedef typename HDS::Halfedge Halfedge; - typedef typename HDS::Face Face; - typedef typename boost::property_traits::value_type Point; - - //data members - Face_handle current_face; - std::map nodes_; - std::map& node_to_polyhedron_vertex_; - std::map,Halfedge_handle>& edge_to_hedge_; - std::vector > edges_to_create_; - std::vector > faces_to_create_; - NestedFacetConstruct facet_construct; - NewNodeVertexVisitor& node_vertex_visitor; - PolyhedronPointPMap ppmap; - - typename HDS::Halfedge::Base* - unlock_halfedge(Halfedge_handle h){ - return static_cast(&(*h)); - } - - typename HDS::Face::Base* - unlock_face(Face_handle f){ - return static_cast(&(*f)); - } - - public: - - template - Triangulate_a_face( Face_handle face, - const Nodes_vector& nodes, - const std::vector& node_ids, - std::map& node_to_polyhedron_vertex, - std::map,Halfedge_handle>& edge_to_hedge, - const Triangulation& triangulation, - const NestedFacetConstruct& fc, - NewNodeVertexVisitor& nv, - PolyhedronPointPMap ppmap) - :current_face(face), node_to_polyhedron_vertex_(node_to_polyhedron_vertex), edge_to_hedge_(edge_to_hedge), facet_construct(fc), node_vertex_visitor(nv), ppmap(ppmap) - { - //grab vertices to be inserted to copy them from the vector - for (std::vector::const_iterator it=node_ids.begin();it!=node_ids.end();++it) - { - nodes_.insert(std::make_pair(*it,nodes[*it])); - } - //grab edges that are not on the convex hull (these have already been created) - for (typename Triangulation::Finite_edges_iterator - it=triangulation.finite_edges_begin(); - it!=triangulation.finite_edges_end(); - ++it) - { - typename Triangulation::Vertex_handle v0=it->first->vertex((it->second+1)%3); - typename Triangulation::Vertex_handle v1=it->first->vertex((it->second+2)%3); - //warning in degenerate cases you can insert outsite expected convex hull edges: need exact here. - //an alternative is to test if one the incident faces are infinite (cf assertion below) - if ( edge_to_hedge_.find(std::make_pair(v0->info(),v1->info()))==edge_to_hedge_.end() && - edge_to_hedge_.find(std::make_pair(v1->info(),v0->info()))==edge_to_hedge_.end() ) - { - edges_to_create_.push_back( std::make_pair(v0->info(),v1->info()) ); - } - else - CGAL_assertion( triangulation.is_infinite(it->first->vertex(it->second)) || triangulation.is_infinite( triangulation.mirror_vertex(it->first,it->second)) ); - } - //grab triangles. - for (typename Triangulation::Finite_faces_iterator - it=triangulation.finite_faces_begin(); - it!=triangulation.finite_faces_end(); - ++it) - { - typename Triangulation::Vertex_handle v0=it->vertex(0); - typename Triangulation::Vertex_handle v1=it->vertex(1); - typename Triangulation::Vertex_handle v2=it->vertex(2); - //warning in degenerate case we can have non wanted triangles: need exact here - faces_to_create_.push_back( CGAL::cpp11::make_tuple( v0->info(),v1->info(),v2->info() ) ); - } - } - - - - void operator()( HDS& hds) { -// std::cerr << "node_to_polyhedron_vertex_"<< std::endl; -// for (typename std::map::iterator it=node_to_polyhedron_vertex_.begin();it!=node_to_polyhedron_vertex_.end();++it) -// std::cerr << it->first << " " << &(*(it->second)) << std::endl; - - //insert the intersection point interior to the face inside the polyhedron and - //save their Polyhedron::vertex_handle - for (typename std::map::iterator it=nodes_.begin();it!=nodes_.end();++it) - { - Vertex_handle v=hds.vertices_push_back(Vertex()); - node_vertex_visitor.new_vertex_added(it->first, v); - put(ppmap, v, it->second); - CGAL_assertion( node_to_polyhedron_vertex_.find( it->first ) == node_to_polyhedron_vertex_.end()); - node_to_polyhedron_vertex_.insert( std::make_pair(it->first,v) ); -// std::cerr << "vertices " << it->first << " " << &(*v) << std::endl; - } - - //insert the new halfedge and set their incident vertex - for (typename std::vector >::iterator - it=edges_to_create_.begin();it!=edges_to_create_.end();++it) - { - Halfedge_handle he=hds.edges_push_back(Halfedge(),Halfedge()); - - //associate edge to halfedge going from i to j with j as incident vertex - CGAL_assertion(node_to_polyhedron_vertex_.find(it->second)!= node_to_polyhedron_vertex_.end()); - Vertex_handle v=node_to_polyhedron_vertex_.find(it->second)->second; - unlock_halfedge(he)->set_vertex( v ); - v->set_halfedge(he); -// std::cerr << " --in edge " << &(*v) << std::endl; - edge_to_hedge_.insert( std::make_pair(*it,he) ); - v=node_to_polyhedron_vertex_.find(it->first)->second; -// std::cerr << " --in edge " << &(*v) << std::endl; - unlock_halfedge( he->opposite() )->set_vertex( v ); - v->set_halfedge(he->opposite()); - edge_to_hedge_.insert( std::make_pair(std::make_pair(it->second,it->first),he->opposite()) ); -// std::cerr << "edges " << it->first << " " << it->second << std::endl; - } - - std::vector >::iterator it=faces_to_create_.begin(); - Face_handle face_triangulated = current_face; - //create the new faces and update adjacencies - while (true) - { - int i=cpp11::get<0>(*it),j=cpp11::get<1>(*it),k=cpp11::get<2>(*it); -// std::cerr << "faces " << i << " " << j << " " << k<< std::endl; - Halfedge_handle current = edge_to_hedge_.find(std::make_pair(i,j))->second; - Halfedge_handle next = edge_to_hedge_.find(std::make_pair(j,k))->second; - Halfedge_handle previous = edge_to_hedge_.find(std::make_pair(k,i))->second; - - - CGAL_assertion (edge_to_hedge_.find(std::make_pair(i,j))!=edge_to_hedge_.end()); - CGAL_assertion (edge_to_hedge_.find(std::make_pair(j,k))!=edge_to_hedge_.end()); - CGAL_assertion (edge_to_hedge_.find(std::make_pair(k,i))!=edge_to_hedge_.end()); - - CGAL_assertion(current->vertex()==node_to_polyhedron_vertex_.find(j)->second); - CGAL_assertion(next->vertex()==node_to_polyhedron_vertex_.find(k)->second); - CGAL_assertion(previous->vertex()==node_to_polyhedron_vertex_.find(i)->second); - - unlock_halfedge(current)->set_next(next); - unlock_halfedge(next)->set_next(previous); - unlock_halfedge(previous)->set_next(current); - - unlock_halfedge(current)->set_prev(previous); - unlock_halfedge(next)->set_prev(current); - unlock_halfedge(previous)->set_prev(next); - - //update face halfedge - unlock_face(current_face)->set_halfedge(current); - - //update face of halfedges - unlock_halfedge(current) ->set_face(current_face); - unlock_halfedge(next) ->set_face(current_face); - unlock_halfedge(previous) ->set_face(current_face); - - if ( ++it!=faces_to_create_.end() ) - current_face=hds.faces_push_back( facet_construct(*face_triangulated) ); - else - break; - } - } - }; - - template - Halfedge_handle - next_marked_halfedge_around_target_vertex(Halfedge_handle h, const Marked_set& is_marked) - { - CGAL_assertion( is_marked.find(h)!=is_marked.end() ); - Halfedge_handle next=h->next(); - while( is_marked.find(next)==is_marked.end() ) - { - next=next->opposite()->next(); - } - CGAL_assertion(next!=h); - return next; - } - - template - Halfedge_handle - next_marked_halfedge_around_source_vertex(Halfedge_handle h, const Marked_set& is_marked) - { - CGAL_assertion( is_marked.find(h)!=is_marked.end() ); - Halfedge_handle prev=h->prev(); - while(is_marked.find(prev)==is_marked.end()) - { - prev=prev->opposite()->prev(); - } - CGAL_assertion(prev!=h); - return prev; - } - - } //namespace internal_IOP - -template -struct Default_facet_construct{ - typename Polyhedron::Facet operator()( const typename Polyhedron::Facet& f) - { return f; } -}; - -template -struct Default_node_vertex_visitor{ - void new_node_added( int /* node_id */, - internal_IOP::Intersection_type /* type */, - typename Polyhedron::Halfedge_handle /* principal_edge */, - typename Polyhedron::Halfedge_handle /* additional_edge */, - bool /* is_vertex_coplanar */, - bool /* is_vertex_opposite_coplanar */ ) - {} - - void new_vertex_added(int /* node_id */, typename Polyhedron::Vertex_handle /* vh */){} -}; - -struct Default_output_builder{ - template - void operator()(const T1&, const T2&, const T3&, const T4&, const T5&){} - void input_have_coplanar_facets(){} -}; - -template< class Polyhedron, - class OutputBuilder_=Default, - class Kernel_=Default, - class EdgeMarkPropertyMap_=Default, - class NestedFacetConstruct_=Default, - class NewNodeVertexVisitor_=Default, - class PolyhedronPointPMap_=Default - > -class Node_visitor_refine_polyhedra{ -//Default typedefs - typedef typename Default::Get::type OutputBuilder; - typedef typename Default::Get >::type EdgeMarkPropertyMap; - typedef typename Default::Get >::type NestedFacetConstruct; - typedef typename Default::Get >::type NewNodeVertexVisitor; - typedef typename Default::Get >::type PolyhedronPointPMap; - typedef typename Default::Get::value_type >::Kernel >::type Kernel; -//typedefs - typedef typename Polyhedron::Halfedge_handle Halfedge_handle; - typedef typename Polyhedron::Halfedge_const_handle Halfedge_const_handle; - typedef typename Polyhedron::Face_handle Face_handle; - typedef typename Polyhedron::Halfedge Halfedge; - typedef typename Polyhedron::Vertex_handle Vertex_handle; - typedef internal_IOP::Compare_handles Cmp_handle; //This ensures uniqueness of edges when comparing halfedges - typedef internal_IOP::Compare_unik_address Cmp_unik_ad; //This ensures uniqueness of edges when comparing halfedges - - //constrained triangulation used for triangulation interior of faces - #ifdef DO_NO_USE_EXACT_CDT - typedef CGAL::Triangulation_vertex_base_with_info_2 Vbi; - typedef CGAL::Constrained_triangulation_face_base_2 Fb; - typedef CGAL::Triangulation_data_structure_2 TDS_2; - typedef CGAL::Constrained_Delaunay_triangulation_2 CDT; //DO WE NEED DELAUNAY???? - #else - /// \todo change this, use it only if not already exact - typedef CGAL::Exact_predicates_exact_constructions_kernel Exact_kernel; - typedef CGAL::Triangulation_vertex_base_with_info_2 Vbi; - typedef CGAL::Constrained_triangulation_face_base_2 Fb; - typedef CGAL::Triangulation_data_structure_2 TDS_2; - typedef CGAL::Constrained_Delaunay_triangulation_2 CDT; //DO WE NEED DELAUNAY???? - #endif - - typedef std::map Hedge_to_polyhedron_map; - - typedef std::vector Node_ids; - typedef std::map< Face_handle,Node_ids,Cmp_handle > In_face_map; - typedef std::map< Halfedge_handle,Node_ids,Cmp_unik_ad > In_halfedge_map; - //to keep the correspondance between node_id and vertex_handle in each polyhedron - typedef std::map Node_to_polyhedron_vertex_map; - typedef std::map Poly_to_map_node; - //to maintain an polyhedron halfedge on each polyline + pair - //with first = "is the key (pair) was reversed?" and second is the number of edges +1 in the polyline - typedef std::map< std::pair, std::pair< std::map,std::pair > > An_edge_per_polyline_map; - //to handle coplanar halfedge of polyhedra that are full in the intersection - typedef std::map< int,Halfedge_handle > Node_to_target_of_hedge_map; - typedef std::map< Polyhedron*,Node_to_target_of_hedge_map> Poly_to_vertices_on_intersection_map; - -//data members - Hedge_to_polyhedron_map hedge_to_polyhedron; - In_face_map in_face; - In_halfedge_map in_hedge; - std::map< int,std::set > graph_of_constraints; - boost::dynamic_bitset<> is_node_of_degree_one; - std::map< int,std::set > coplanar_constraints; - An_edge_per_polyline_map an_edge_per_polyline; - typename An_edge_per_polyline_map::iterator last_polyline; - Poly_to_vertices_on_intersection_map poly_to_vertices_on_inter; - Poly_to_map_node polyhedron_to_map_node_to_polyhedron_vertex; - std::set non_manifold_nodes; //contain nodes that are original vertices of input polyhedron and that neighborhood is not a topological disk - std::map nodes_that_are_original_vertices;//to keep the correspondance between original polyhedron vertices that are also nodes - - OutputBuilder output_builder; - - // new_hedge hedge - // -----------> -----------> - // v - // <----------- <----------- - // new_opposite opposite - // - template - Vertex_handle split_edge( Halfedge_handle hedge, - int node_id, - const Nodes_vector& nodes, - Polyhedron& P) - { - internal_IOP::Split_halfedge delegated(hedge); - P.delegate( delegated ); - CGAL_assertion(P.is_valid()); - - Vertex_handle vh=boost::prior(P.vertices_end()); - node_vertex_visitor.new_vertex_added(node_id, vh); - put(ppmap, vh, nodes[node_id]); - CGAL_assertion(get(ppmap,vh)==nodes[node_id]); - - //update marker tags. If the edge was marked, then the resulting edges in the split must be marked - if ( get(m_edge_mark_pmap,std::make_pair(hedge,&P)) ) - { - CGAL_assertion( get(m_edge_mark_pmap,std::make_pair(hedge->opposite(),&P)) ); - put(m_edge_mark_pmap,std::make_pair(hedge->prev(),&P),true); - put(m_edge_mark_pmap,std::make_pair(hedge->prev()->opposite(),&P),true); - } - - return vh; - } - - //sort node ids so that we can split the hedge - //consecutively - template - void sort_vertices_along_hedge(std::vector& node_ids,Halfedge_handle hedge,const Nodes_vector& nodes) - { - std::sort(node_ids.begin(), - node_ids.end(), - internal_IOP::Order_along_a_halfedge(hedge,nodes, ppmap) - ); - } - - //insert intersection as constrained edges in a CDT triangulation - template - void insert_constrained_edges_coplanar_case(int node_id, - CDT& triangulation, - std::map& id_to_CDT_vh) - { - if (node_id < number_coplanar_vertices){ - //XSL_TAG_CPL_VERT - //Insert constrained edges from coplanar facets that have been retriangulated. This ensure that triangulations are compatible - std::map< int,std::set >::iterator it_neighbors=coplanar_constraints.find(node_id); - if (it_neighbors!=coplanar_constraints.end()) - { - typename CDT::Vertex_handle vh=id_to_CDT_vh.find(node_id)->second; - for (std::set::iterator it_n=it_neighbors->second.begin();it_n!=it_neighbors->second.end();++it_n){ - typename std::map::iterator it_vh=id_to_CDT_vh.find(*it_n); - // this condition ensures to consider only graph edges that are in the same triangle (not in a neighbor one when involving node on a triangle edge) - // here we can't make the difference between a point on the interior or the boundary, so points_on_triangle is not used. - if ( it_vh!=id_to_CDT_vh.end() ){ - triangulation.insert_constraint(vh,id_to_CDT_vh.find(*it_n)->second); - } - } - } - } - } - //insert intersection as constrained edges in a CDT triangulation - template - void insert_constrained_edges(Node_ids& node_ids, //index of vertices we are interested in - CDT& triangulation, - std::map& id_to_CDT_vh, - Constrained_edges_map& constrained_edges, //list of pair of int to indicate edges that are constrained - bool points_on_triangle=false) - { - for (Node_ids::iterator it_node_id=node_ids.begin();it_node_id!=node_ids.end();++it_node_id){ - std::map< int,std::set >::iterator it_neighbors=graph_of_constraints.find(*it_node_id); - if (it_neighbors!=graph_of_constraints.end()) - { - typename CDT::Vertex_handle vh=id_to_CDT_vh.find(*it_node_id)->second; - for (std::set::iterator it_n=it_neighbors->second.begin();it_n!=it_neighbors->second.end();++it_n){ - typename std::map::iterator it_vh=id_to_CDT_vh.find(*it_n); - // this condition ensures to consider only graph edges that are in the same triangle (not in a neighbor one when involving node on a triangle edge) - if ( !points_on_triangle || it_vh!=id_to_CDT_vh.end() ){ - CGAL_assertion(it_vh!=id_to_CDT_vh.end()); - triangulation.insert_constraint(vh,id_to_CDT_vh.find(*it_n)->second); - constrained_edges.push_back(std::make_pair(*it_node_id,*it_n)); - } - } - } - #ifdef CGAL_COREFINEMENT_DEBUG - else - { - std::cout << "X0: Found an isolated point" << std::endl; - } - #endif - - insert_constrained_edges_coplanar_case(*it_node_id,triangulation,id_to_CDT_vh); - } - } - - std::pair make_sorted_pair(int i,int j) const {return i indices,typename Polyhedron::Halfedge_handle hedge) - { - std::pair sorted_pair=make_sorted_pair(indices.first,indices.second); - typename An_edge_per_polyline_map::iterator it=an_edge_per_polyline.find(sorted_pair); - if (it!=an_edge_per_polyline.end()){ - it->second.first.insert(std::make_pair( P,sorted_pair.first==indices.first?hedge:hedge->opposite() )); - } - } - - //keep track of the fact that a polyhedron original vertex is a node - void all_incident_faces_got_a_node_as_vertex(Halfedge_handle incident_to_vertex_edge,int node_id) - { - nodes_that_are_original_vertices.insert(std::make_pair(incident_to_vertex_edge->vertex(),node_id)); - } - - //if an original polyhedron vertex is also a node, do no use a fake id - void set_triangle_boundary_indices( - Vertex_handle* triangle_boundary, - int* triangle_boundary_indices) - { - triangle_boundary_indices[0]=-1; - triangle_boundary_indices[1]=-2; - triangle_boundary_indices[2]=-3; - - for (int k=0;k<3;++k){ - typename std::map::iterator it=nodes_that_are_original_vertices.find(triangle_boundary[k]); - if (it!=nodes_that_are_original_vertices.end()) - triangle_boundary_indices[k]=it->second; - } - } - - int number_coplanar_vertices; //number of intersection points between coplanar facets, see fixes XSL_TAG_CPL_VERT - EdgeMarkPropertyMap m_edge_mark_pmap; //property map to mark halfedge of the original polyhedra that are on the intersection - NestedFacetConstruct facet_construct; // functor called to create new triangular faces inside a given face - NewNodeVertexVisitor node_vertex_visitor; // functor called when a new node is created and when a new vertex is added - PolyhedronPointPMap ppmap; -public: - Node_visitor_refine_polyhedra ( - OutputBuilder output_builder_=OutputBuilder(), - PolyhedronPointPMap ppmap = PolyhedronPointPMap(), - EdgeMarkPropertyMap pmap=EdgeMarkPropertyMap(), - const NestedFacetConstruct& fc = NestedFacetConstruct(), - const NewNodeVertexVisitor& nv = NewNodeVertexVisitor() ) - : output_builder(output_builder_) - , m_edge_mark_pmap(pmap) - , facet_construct(fc) - , node_vertex_visitor(nv) - , ppmap(ppmap) - {} - - typedef internal_IOP::Predicates_on_constructions Node_storage_type; - typedef Tag_false Is_polyhedron_const; - static const bool do_need_vertex_graph = true; //because we need to know which edges are constrained - - void set_number_of_intersection_points_from_coplanar_facets(int n){ - number_coplanar_vertices=n; - } - - void input_have_coplanar_facets() - { - output_builder.input_have_coplanar_facets(); - } - - void check_node_on_non_manifold_vertex(int node_id,Halfedge_handle hedge){ - //we turn around the hedge and check no halfedge is a border halfedge - Halfedge_handle curr=hedge; - do{ - if ( curr->is_border_edge() ){ - non_manifold_nodes.insert(node_id); - return; - } - curr=curr->next()->opposite(); - } - while(curr!=hedge); - } - - void check_node_on_non_manifold_edge(int node_id,Halfedge_handle hedge){ - if ( hedge->is_border_edge() ) non_manifold_nodes.insert(node_id); - } - - void new_node_added(int node_id, - internal_IOP::Intersection_type type, - Halfedge_handle principal_edge, - Halfedge_handle additional_edge, - bool is_vertex_coplanar, - bool is_vertex_opposite_coplanar) - { - //forward to the visitor - node_vertex_visitor.new_node_added(node_id, type, principal_edge, additional_edge, is_vertex_coplanar, is_vertex_opposite_coplanar); - switch(type) - { - case internal_IOP::FACET: //Facet intersected by an edge - { - typename In_face_map::iterator it_fmap=in_face.insert(std::make_pair(additional_edge->face(), Node_ids())).first; - it_fmap->second.push_back(node_id); - } - break; - case internal_IOP::EDGE: //Edge intersected by an edge - { - typename In_halfedge_map::iterator it_hedge_map=in_hedge.insert(std::make_pair(additional_edge,Node_ids())).first; - it_hedge_map->second.push_back(node_id); - check_node_on_non_manifold_edge(node_id,additional_edge); - } - break; - case internal_IOP::VERTEX: - { - //grab original vertex that is on commom intersection - typename Hedge_to_polyhedron_map::iterator it=hedge_to_polyhedron.find(additional_edge->facet()->halfedge()); - CGAL_assertion(it!=hedge_to_polyhedron.end()); - poly_to_vertices_on_inter[it->second].insert(std::make_pair(node_id,additional_edge)); - polyhedron_to_map_node_to_polyhedron_vertex[it->second].insert(std::make_pair(node_id,additional_edge->vertex())); - all_incident_faces_got_a_node_as_vertex(additional_edge,node_id); - check_node_on_non_manifold_vertex(node_id,additional_edge); - } - break; - default: - return; - } - - CGAL_assertion(!is_vertex_coplanar || !is_vertex_opposite_coplanar); //coplanar edge are not forwarded - - - if ( is_vertex_coplanar ) - { - //grab original vertex that is on commom intersection - typename Hedge_to_polyhedron_map::iterator it=hedge_to_polyhedron.find(principal_edge->facet()->halfedge()); - poly_to_vertices_on_inter[it->second].insert(std::make_pair(node_id,principal_edge)); - polyhedron_to_map_node_to_polyhedron_vertex[it->second].insert(std::make_pair(node_id,principal_edge->vertex())); - all_incident_faces_got_a_node_as_vertex(principal_edge,node_id); - check_node_on_non_manifold_vertex(node_id,principal_edge); - } - else{ - if ( is_vertex_opposite_coplanar ){ - //grab original vertex that is on commom intersection - typename Hedge_to_polyhedron_map::iterator it=hedge_to_polyhedron.find(principal_edge->facet()->halfedge()); - poly_to_vertices_on_inter[it->second].insert(std::make_pair(node_id,principal_edge->opposite())); - polyhedron_to_map_node_to_polyhedron_vertex[it->second].insert(std::make_pair(node_id,principal_edge->opposite()->vertex())); - all_incident_faces_got_a_node_as_vertex(principal_edge->opposite(),node_id); - check_node_on_non_manifold_vertex(node_id,principal_edge->opposite()); - } - else{ - //handle intersection on principal edge - typename In_halfedge_map::iterator it_hedge_map=in_hedge.insert(std::make_pair(principal_edge,Node_ids())).first; - it_hedge_map->second.push_back(node_id); - check_node_on_non_manifold_edge(node_id,principal_edge); - } - } - } - - template - void annotate_graph(Iterator begin,Iterator end) - { -// std::cout << "Annotation graph..." << std::endl; - int node_id = 0; - is_node_of_degree_one.resize(std::distance(begin, end)); - for (Iterator it=begin;it!=end;++it, ++node_id) - { - if (non_manifold_nodes.count(node_id)) it->make_terminal(); - const std::set& neighbors = it->neighbors; - graph_of_constraints.insert(std::make_pair(node_id,neighbors)); - if (neighbors.size()==1) - is_node_of_degree_one.set(node_id); - } - } - - void update_terminal_nodes(std::vector&) - { - CGAL_assertion(!"Must not call this function"); - } - - void add_filtered_intersection(Halfedge_handle eh,Halfedge_handle fh,Polyhedron& Pe,Polyhedron& Pf){ - //use the representant halfedge of the facet as key - //--set polyhedron for the two facets incident to the edge - CGAL_assertion(!eh->is_border()); - hedge_to_polyhedron.insert(std::make_pair(eh->facet()->halfedge(),&Pe)); - if ( !eh->opposite()->is_border() ) - hedge_to_polyhedron.insert(std::make_pair(eh->opposite()->facet()->halfedge(),&Pe)); - //--set polyhedron for the facet intersected by the edge - hedge_to_polyhedron.insert(std::make_pair(fh->facet()->halfedge(),&Pf)); - } - - - struct Polyhedron_face_boundary{ - std::vector node_ids_array[3]; // the node_ids on each halfedges - std::map hedges_ids; - Halfedge_handle halfedges[3]; //the three halfedges of the original face - Vertex_handle vertices[3]; //the three vertices of the original face - //node_ids_array[0] corresponds to the original edge vertices[0],vertices[1] = halfedges[0] - //node_ids_array[1] corresponds to the original edge vertices[1],vertices[2] = halfedges[1] - //node_ids_array[2] corresponds to the original edge vertices[2],vertices[0] = halfedges[2] - Polyhedron_face_boundary(Halfedge_handle first) - { - CGAL_assertion(first->next()->next()->next()==first); //the face is a triangle - hedges_ids.insert(std::make_pair(first,0)); - hedges_ids.insert(std::make_pair(first->next(),1)); - hedges_ids.insert(std::make_pair(first->next()->next(),2)); - halfedges[0]=first; - halfedges[1]=first->next(); - halfedges[2]=first->next()->next(); - - vertices[0]=halfedges[0]->opposite()->vertex(); - vertices[1]=halfedges[1]->opposite()->vertex(); - vertices[2]=halfedges[2]->opposite()->vertex(); - } - - //used when object was created with hedge but opposite was used to split the original face - void update_original_halfedge(Halfedge_handle original,Halfedge_handle new_hedge) - { - typename std::map::iterator it_id=hedges_ids.find(original); - CGAL_assertion(it_id!=hedges_ids.end()); - int index=it_id->second; - CGAL_assertion(halfedges[index]==original); - hedges_ids.erase(it_id); - hedges_ids.insert(std::make_pair(new_hedge,index)); - halfedges[index]=new_hedge; - } - - template - void copy_node_ids(Halfedge_handle hedge,Iterator begin,Iterator end) - { - typename std::map::iterator it_id=hedges_ids.find(hedge); - CGAL_assertion(it_id!=hedges_ids.end()); - std::copy(begin,end,std::back_inserter(node_ids_array[it_id->second])); - } - }; - - - void start_new_polyline(int i, int j) - { - if ( i==j ) //case of a single point - { - //TAG SL001 - //nothing is done - return; - } - std::pair res= - an_edge_per_polyline.insert( - std::make_pair( make_sorted_pair(i,j), - std::make_pair( std::map(),std::make_pair(false,0)) ) - ); - CGAL_assertion(res.second); - last_polyline=res.first; - if ( i !=last_polyline->first.first ) - last_polyline->second.second.first=true; - } - - void add_node_to_polyline(int){ - ++(last_polyline->second.second.second); - } - - void new_input_polyhedron(Polyhedron& P) - { - typedef std::pair Res; - CGAL_USE_TYPE(Res); - CGAL_assertion_code(Res res = ) - polyhedron_to_map_node_to_polyhedron_vertex.insert(std::make_pair( &P,Node_to_polyhedron_vertex_map() )); - CGAL_assertion(res.second == true); - } - - //1) split_halfedges and retriangulate faces with no intersection point interior to the facet - //2) retriangulate using a constrained Delaunay triangulation each triangle in each Polyhedron that contains at least - // one intersection point inside the facet - //3) mark polyhedron edges that are on the intersection - //4) create one output polyhedron per connected component of polyhedron, connected by an edge which is not an intersection edge - //5) import each piece into a common combinatorial map - //6) glue all the pieces together - template - void finalize(const Nodes_vector& nodes){ - //mark halfedge that are on the intersection - //SL: I needed to use a map because to get the orientation around the edge, - // I need to know in the case the third vertex is a node its index (for exact construction) - typedef std::map,Cmp_unik_ad > Border_halfedges_map; - Border_halfedges_map border_halfedges; - - //store for each triangle facet which boundary is intersected by the other surface, - //original vertices (and halfedges in the refined mesh pointing on these vertices) - typedef std::map Faces_boundary; - Faces_boundary faces_boundary; - - //0) For each polyhedron, collect original vertices that belongs to the intersection. - // From the graph of constraints, extract intersection edges that are incident to such vertices. In case - // there exists another original vertex adjacent to the first one found, this halfedge must be - // marked on the boundary (and possibly update an_edge_per_polyline). - // This is done first to avoid halfedges stored to be modified in the steps following. - for (typename Poly_to_vertices_on_intersection_map::iterator - it=poly_to_vertices_on_inter.begin(); - it!=poly_to_vertices_on_inter.end(); - ++it) - { - Polyhedron* poly=it->first; - std::set > already_done; - Node_to_target_of_hedge_map& nodes_to_hedge=it->second; - for(typename Node_to_target_of_hedge_map::iterator - it_node_2_hedge=nodes_to_hedge.begin(); - it_node_2_hedge!=nodes_to_hedge.end(); - ++it_node_2_hedge) - { - int node_id_of_first=it_node_2_hedge->first; - std::map< int,std::set >::iterator it_neighbors=graph_of_constraints.find(node_id_of_first); - if ( it_neighbors!=graph_of_constraints.end() ) - { - std::set& neighbors=it_neighbors->second; - for (std::set::iterator it_id=neighbors.begin();it_id!=neighbors.end();++it_id){ - if ( already_done.find(std::make_pair(*it_id,node_id_of_first))!=already_done.end() ) continue;//already done for the opposite - typename Node_to_target_of_hedge_map::iterator it_node_2_hedge_two=nodes_to_hedge.find(*it_id); - if ( it_node_2_hedge_two!=nodes_to_hedge.end() ) //a full edge is on intersection - { - //get the corresponding halfedge with vertex corresponding to node_id_of_first - Halfedge_handle hedge=it_node_2_hedge->second; - CGAL_assertion_code(Halfedge_handle start=hedge;) - while ( hedge->opposite()->vertex()!=it_node_2_hedge_two->second->vertex() ){ - hedge=hedge->next()->opposite(); - CGAL_assertion(hedge!=start); - } - std::pair edge_pair(*it_id,node_id_of_first); - if ( border_halfedges.insert( std::make_pair(hedge,edge_pair) ).second) - { - put(m_edge_mark_pmap,std::make_pair(hedge,poly),true); - put(m_edge_mark_pmap,std::make_pair(hedge->opposite(),poly),true); - } - update_edge_per_polyline(poly,edge_pair,hedge); - //save the fact that we already handle this edge - already_done.insert(std::make_pair(node_id_of_first,*it_id)); - } - } - } - #ifdef CGAL_COREFINEMENT_DEBUG - else - { - std::cout << "X1: Found an isolated point" << std::endl; - } - #endif - } - } - - //1) First split halfedges cut by the intersection polyline(s) - for (typename In_halfedge_map::iterator it=in_hedge.begin();it!=in_hedge.end();++it) - { - Halfedge_handle hedge=it->first; //the halfedge to be split (and its opposite too) - Node_ids& node_ids = it->second; //indices of the intersection points to be inserted - CGAL_assertion( std::set(node_ids.begin(), node_ids.end()).size()==node_ids.size() ); - typename Hedge_to_polyhedron_map::iterator it_poly=hedge_to_polyhedron.find( hedge->facet()->halfedge() ); - CGAL_assertion(it_poly!=hedge_to_polyhedron.end()); - Polyhedron* P=it_poly->second; //the polyhedron in which vertices should be added - - sort_vertices_along_hedge(node_ids,hedge,nodes); - - //save original face and nodes for face of hedge (1) - if ( !hedge->is_border() ){ - typename Faces_boundary::iterator it_face=faces_boundary.find(hedge->face()); - if (it_face==faces_boundary.end()) - it_face=faces_boundary.insert(std::make_pair(hedge->face(),Polyhedron_face_boundary(hedge))).first; - it_face->second.copy_node_ids(hedge,node_ids.begin(),node_ids.end()); - } - - //save original face and nodes for face of hedge->opposite (2) - typename Faces_boundary::iterator opposite_original_info=faces_boundary.end(); - if ( !hedge->opposite()->is_border() ){ - opposite_original_info=faces_boundary.find(hedge->opposite()->face()); - if (opposite_original_info==faces_boundary.end()) - opposite_original_info=faces_boundary.insert(std::make_pair(hedge->opposite()->face(),Polyhedron_face_boundary(hedge->opposite()))).first; - opposite_original_info->second.copy_node_ids(hedge->opposite(),node_ids.rbegin(),node_ids.rend()); - } - - typename Poly_to_map_node::iterator it_map=polyhedron_to_map_node_to_polyhedron_vertex.find(P); - CGAL_assertion(it_map!=polyhedron_to_map_node_to_polyhedron_vertex.end()); - //a map to identify the vertex in the polyhedron corresponding to an intersection point - Node_to_polyhedron_vertex_map& node_to_polyhedron_vertex=it_map->second; - - CGAL_assertion_code(Vertex_handle original_vertex=hedge->opposite()->vertex();) - - //We need an edge incident to the source vertex of hedge. This is the first opposite edge created. - bool first=true; Halfedge_handle hedge_incident_to_src; - //do split the edges - for (std::vector::const_iterator it_id=node_ids.begin();it_id!=node_ids.end();++it_id){ - Vertex_handle v=split_edge(hedge, *it_id, nodes, *P); - node_to_polyhedron_vertex.insert(std::make_pair(*it_id,v)); - if (first){ - first=false; - hedge_incident_to_src=hedge->opposite()->next(); - } - } - - CGAL_assertion(hedge_incident_to_src->vertex()==original_vertex); - CGAL_assertion(hedge_incident_to_src->face()==hedge->opposite()->face()); - - //save original face and nodes for face of hedge->opposite (2) - if ( !hedge->opposite()->is_border() ){ - CGAL_assertion(opposite_original_info!=faces_boundary.end()); - opposite_original_info->second.update_original_halfedge(hedge->opposite(),hedge_incident_to_src); - } - - //insert the two incident faces in in_face map so that they will be triangulated. - if (!hedge->is_border()) in_face.insert(std::make_pair(hedge->face(),Node_ids())); - if (!hedge->opposite()->is_border()) in_face.insert(std::make_pair(hedge->opposite()->face(),Node_ids())); - } - - //2)triangulation of the triangle faces containing intersection point in their interior - // and also those with intersection points only on the boundary. - for (typename In_face_map::iterator it=in_face.begin();it!=in_face.end();++it) - { - Face_handle f = it->first; //the face to be retriangulated - Node_ids& node_ids = it->second; //the index of the intersection point that are interior to the face - CGAL_assertion(std::set(node_ids.begin(), node_ids.end()).size()==node_ids.size()); - typename Faces_boundary::iterator it_fb=faces_boundary.find(f); - - - typename Hedge_to_polyhedron_map::iterator it_polyhedron = hedge_to_polyhedron.find (f->halfedge()); //we can do this because the halfedge is still the same (at least its address)+no Face::set_halfedge called - CGAL_assertion(it_polyhedron != hedge_to_polyhedron.end()); - Polyhedron* P=it_polyhedron->second; - typename Poly_to_map_node::iterator it_map=polyhedron_to_map_node_to_polyhedron_vertex.find(P); - CGAL_assertion(it_map!=polyhedron_to_map_node_to_polyhedron_vertex.end()); - //a map to identify the vertex in the polyhedron corresponding to an intersection point - Node_to_polyhedron_vertex_map& node_to_polyhedron_vertex=it_map->second; - - std::map id_to_CDT_vh; - - //associate an edge of the triangulation to a halfedge in a given polyhedron - std::map,Halfedge_handle> edge_to_hedge; - - Vertex_handle triangle_boundary[3]; - int triangle_boundary_indices[3]; //the node_id of the triangle original vertex or a fake id - if (it_fb!=faces_boundary.end()){ //the boundary of the triangle face was refined - triangle_boundary[0]=it_fb->second.vertices[0]; - triangle_boundary[1]=it_fb->second.vertices[1]; - triangle_boundary[2]=it_fb->second.vertices[2]; - set_triangle_boundary_indices(triangle_boundary,triangle_boundary_indices); - } - else{ - triangle_boundary[0]=f->halfedge()->vertex(); //-1 - triangle_boundary[1]=f->halfedge()->next()->vertex(); //-2 - triangle_boundary[2]=f->halfedge()->next()->next()->vertex(); //-3 - CGAL_assertion(f->halfedge()->next()->next()->next()==f->halfedge());//check this is a triangle - set_triangle_boundary_indices(triangle_boundary,triangle_boundary_indices); - edge_to_hedge.insert (std::make_pair( std::make_pair( triangle_boundary_indices[2],triangle_boundary_indices[0] ) , f->halfedge() ) ); - edge_to_hedge.insert (std::make_pair( std::make_pair( triangle_boundary_indices[0],triangle_boundary_indices[1] ) , f->halfedge()->next() ) ); - edge_to_hedge.insert (std::make_pair( std::make_pair( triangle_boundary_indices[1],triangle_boundary_indices[2] ) , f->halfedge()->next()->next() ) ); - } - - - #ifdef DO_NO_USE_EXACT_CDT - typename Kernel::Plane_3 plane( get(ppmap,triangle_boundary[0]),get(ppmap,triangle_boundary[1]),get(ppmap,triangle_boundary[2])); - #else - CGAL::Cartesian_converter convert; - typename Exact_kernel::Plane_3 plane(convert(get(ppmap,triangle_boundary[0])),convert(get(ppmap,triangle_boundary[1])),convert(get(ppmap,triangle_boundary[2]))); - #endif - CDT triangulation; - //insert point inside face - for (std::vector::iterator it_node_id=node_ids.begin();it_node_id!=node_ids.end();++it_node_id){ - #ifdef DO_NO_USE_EXACT_CDT - typename CDT::Vertex_handle vh=triangulation.insert(plane.to_2d(nodes[*it_node_id])); - #else - typename CDT::Vertex_handle vh=triangulation.insert(plane.to_2d(nodes.exact_node(*it_node_id))); - #endif - vh->info()=*it_node_id; - id_to_CDT_vh.insert(std::make_pair(*it_node_id,vh)); - } - - - typename CDT::Vertex_handle triangle_vertices[3]; - #ifdef DO_NO_USE_EXACT_CDT - triangle_vertices[0]=triangulation.insert(plane.to_2d(get(ppmap,triangle_boundary[0]))); - triangle_vertices[1]=triangulation.insert(plane.to_2d(get(ppmap,triangle_boundary[1]))); - triangle_vertices[2]=triangulation.insert(plane.to_2d(get(ppmap,triangle_boundary[2]))); - #else - //we can do this because these are input points. - triangle_vertices[0]=triangulation.insert(plane.to_2d(convert(get(ppmap,triangle_boundary[0])))); - triangle_vertices[1]=triangulation.insert(plane.to_2d(convert(get(ppmap,triangle_boundary[1])))); - triangle_vertices[2]=triangulation.insert(plane.to_2d(convert(get(ppmap,triangle_boundary[2])))); - #endif - - triangle_vertices[0]->info()=triangle_boundary_indices[0]; - triangle_vertices[1]->info()=triangle_boundary_indices[1]; - triangle_vertices[2]->info()=triangle_boundary_indices[2]; - //insert face_extremities: we use operator[] because indice -1,-2,-3 are used in each loop and are specific to the current face - node_to_polyhedron_vertex[-1]=triangle_boundary[0]; - node_to_polyhedron_vertex[-2]=triangle_boundary[1]; - node_to_polyhedron_vertex[-3]=triangle_boundary[2]; - - //if one of the triangle original vertex is also a node - for (int ik=0;ik<3;++ik){ - if ( triangle_boundary_indices[ik]>=0 ) - id_to_CDT_vh.insert(std::make_pair(triangle_boundary_indices[ik],triangle_vertices[ik])); - } - //insert points on edges - #ifdef DO_NO_USE_EXACT_CDT - //and constrains these edges - #endif - if (it_fb!=faces_boundary.end()) //is there at least one intersection point on the boundary of the face? - { - //in the following loop, for each original edge of the triangle, we insert the constrained edges - // and we recover the halfedge_handle corresponding to these constrained (they are already in the polyhedron) - for (int i=0;i<3;++i){ -// std::cerr << "Boundary edges" << std::endl; -// std::cerr << " " << -1-i <second.node_ids_array[i]; - typename CDT::Vertex_handle previous=triangle_vertices[i]; - int previous_index=triangle_boundary_indices[i]; //index of original Polyhedron vertex - Halfedge_handle hedge = it_fb->second.halfedges[ (i+2) % 3]->next(); - CGAL_assertion( hedge->opposite()->vertex()==it_fb->second.vertices[i] ); - if (!bounding_ids.empty()){ //is there al least one intersection point on this edge? - for (Node_ids::iterator it_id=bounding_ids.begin();it_id!=bounding_ids.end();++it_id){ -// std::cerr << " "<< *it_id << std::endl; - #ifdef DO_NO_USE_EXACT_CDT - typename CDT::Vertex_handle vh=triangulation.insert(plane.to_2d(nodes[*it_id])); - #else - typename CDT::Vertex_handle vh=triangulation.insert(plane.to_2d(nodes.exact_node(*it_id))); - #endif - vh->info()=*it_id; - id_to_CDT_vh.insert(std::make_pair(*it_id,vh)); - #ifdef DO_NO_USE_EXACT_CDT - triangulation.insert_constraint(previous,vh); - #endif - edge_to_hedge.insert (std::make_pair( std::make_pair(previous_index,*it_id),hedge) ); - previous=vh; - hedge=hedge->next(); - previous_index=*it_id; - } - } - else{ - CGAL_assertion( it_fb->second.halfedges[i]->vertex() == it_fb->second.vertices[ (i+1) % 3 ] ); - CGAL_assertion( it_fb->second.halfedges[i]->opposite()->vertex() == it_fb->second.vertices[ i ] ); - } - CGAL_assertion(hedge==it_fb->second.halfedges[i]); - edge_to_hedge.insert (std::make_pair( std::make_pair(previous_index,triangle_boundary_indices[(i+1) % 3]) , it_fb->second.halfedges[i] ) ); -// std::cerr << " " << -1 - ( (i+1) % 3 ) < > constrained_edges; - - //insert constraints that are interior to the triangle (in the case no edges are collinear in the meshes) - insert_constrained_edges(node_ids,triangulation,id_to_CDT_vh,constrained_edges); - - //insert constraints between points that are on the boundary (not a contrained on the triangle boundary) - if (it_fb!=faces_boundary.end()) //is there at least one intersection point on the boundary of the face? - { - for (int i=0;i<3;++i){ - Node_ids& bounding_ids=it_fb->second.node_ids_array[i]; - insert_constrained_edges(bounding_ids,triangulation,id_to_CDT_vh,constrained_edges,true); - } - } - - //insert coplanar edges for endpoints of triangles - for (int i=0;i<3;++i){ - int nindex=triangle_vertices[i]->info(); - if ( nindex >=0 ) - insert_constrained_edges_coplanar_case(nindex,triangulation,id_to_CDT_vh); - } - - //XSL_TAG_CPL_VERT - //collect edges incident to a point that is the intersection of two coplanar faces. - //This ensure that triangulations are compatible. - if (it_fb!=faces_boundary.end()) //is there at least one intersection point on the boundary of the face? - { - for (typename CDT::Finite_vertices_iterator vit=triangulation.finite_vertices_begin(), - vit_end=triangulation.finite_vertices_end();vit_end!=vit;++vit) - { - //skip original vertices (that are not nodes) and non-coplanar facet issued vertices - //(this is working because intersection points between coplanar facets are the first inserted) - if ( vit->info() < 0 || vit->info() >= number_coplanar_vertices) continue; - std::map< int,std::set >::iterator res=coplanar_constraints.insert(std::make_pair(vit->info(),std::set())).first; - //turn around the vertex and get incident edge - typename CDT::Edge_circulator start=triangulation.incident_edges(vit); - typename CDT::Edge_circulator curr=start; - do{ - if (triangulation.is_infinite(*curr) ) continue; - typename CDT::Edge mirror_edge=triangulation.mirror_edge(*curr); - if ( triangulation.is_infinite( curr->first->vertex(curr->second) ) || - triangulation.is_infinite( mirror_edge.first->vertex(mirror_edge.second) ) ) - continue; //skip edges that are on the boundary of the triangle (these are already constrained) - //insert edges in the set of constraints - int nindex = - curr->first->vertex( (curr->second+1)%3 )==static_cast(vit)? - (curr->second+2)%3:(curr->second+1)%3; - typename CDT::Vertex_handle vn=curr->first->vertex(nindex); - if ( vit->info() > vn->info() ) continue; //take only one out of the two edges + skip negative vn->info() - CGAL_assertion(vn->info()>=0); - res->second.insert( vn->info() ); - }while(start!=++curr); - } - -// this is a working alternative that should be slower -// for (typename CDT::Finite_edges_iterator eit=triangulation.finite_edges_begin(), -// eit_end=triangulation.finite_edges_end();eit_end!=eit;++eit) -// { -// typename CDT::Edge mirror_edge=triangulation.mirror_edge(*eit); -// if ( triangulation.is_infinite( eit->first->vertex(eit->second) ) || -// triangulation.is_infinite( mirror_edge.first->vertex(mirror_edge.second) ) ) -// continue; //skip edges that are on the boundary of the triangle (these are already constrained) -// typename CDT::Vertex_handle v1=eit->first->vertex( (eit->second+1)%3 ), -// v2=eit->first->vertex( (eit->second+2)%3 ); -// if (v1->info()<0 || v2->info()<0) continue; -// if ( v1->info() > v2->info() ) std::swap(v1,v2); -// coplanar_constraints.insert(std::make_pair(v1->info(),std::set())).first->second.insert(v2->info()); -// } - } - - - //create a modifier to insert nodes and copy the triangulation of the face - //inside the polyhedron - internal_IOP::Triangulate_a_face modifier( - f, nodes, node_ids, node_to_polyhedron_vertex, edge_to_hedge, triangulation, facet_construct, node_vertex_visitor, ppmap); - - CGAL_assertion(P->is_valid()); - P->delegate(modifier); - CGAL_assertion(P->is_valid()); - - //3) mark halfedges that are common to two polyhedral surfaces - //recover halfedges inserted that are on the intersection - for (std::list >::iterator it_cst=constrained_edges.begin();it_cst!=constrained_edges.end();++it_cst) - { - typename std::map,Halfedge_handle>::iterator it_poly_hedge=edge_to_hedge.find(*it_cst); - //we cannot have an assertion here in the case an edge or part of an edge is a constraints. - //Indeed, the graph_of_constraints report an edge 0,1 and 1,0 for example while only one of the two - //is defined as one of them defines an adjacent face - //CGAL_assertion(it_poly_hedge!=edge_to_hedge.end()); - if( it_poly_hedge!=edge_to_hedge.end() ){ - if ( border_halfedges.insert( std::make_pair(Halfedge_const_handle(it_poly_hedge->second),*it_cst) ).second) - { - put(m_edge_mark_pmap,std::make_pair(it_poly_hedge->second,P),true); - put(m_edge_mark_pmap,std::make_pair(it_poly_hedge->second->opposite(),P),true); //setting the opposite is only needed for border edges (done in adjacent triangle otherwise) - } - update_edge_per_polyline(P,it_poly_hedge->first,it_poly_hedge->second); - } - else{ - //WARNING: in few case this is needed if the marked edge is on the border - //to optimize it might be better to only use sorted pair. TAG_SLXX1 - std::pair opposite_pair(it_cst->second,it_cst->first); - it_poly_hedge=edge_to_hedge.find(opposite_pair); - CGAL_assertion( it_poly_hedge!=edge_to_hedge.end() ); - - if ( border_halfedges.insert( std::make_pair(Halfedge_const_handle(it_poly_hedge->second),opposite_pair) ).second ) - { - put(m_edge_mark_pmap,std::make_pair(it_poly_hedge->second,P),true); - put(m_edge_mark_pmap,std::make_pair(it_poly_hedge->second->opposite(),P),true); //setting the opposite is only needed for border edges (done in adjacent triangle otherwise) - } - update_edge_per_polyline(P,it_poly_hedge->first,it_poly_hedge->second); - } - } - } - output_builder(border_halfedges, nodes, an_edge_per_polyline, is_node_of_degree_one, polyhedron_to_map_node_to_polyhedron_vertex); - } - - template - PolylineOfHalfedgeOutputIterator - explicitly_compute_polylines( - Polyhedron* P, - const Marked_set& is_marked, - PolylineOfHalfedgeOutputIterator out) - { - typedef std::pair< const std::pair, - std::pair< std::map, - std::pair > > Complicated_pair; - BOOST_FOREACH( - Complicated_pair& p, - an_edge_per_polyline) - { - const std::pair& reversed_and_nbpts = p.second.second; - Halfedge_handle hedge = p.second.first[P]; - std::vector polyline; - int nbsegments=reversed_and_nbpts.second-1; - polyline.reserve( nbsegments ); - polyline.push_back( reversed_and_nbpts.first?hedge->opposite():hedge ); - for (int i=1; i - -#endif //CGAL_INTERSECTION_OF_POLYHEDRA_3_REFINEMENT_VISITOR_H diff --git a/Operations_on_polyhedra/package_info/Operations_on_polyhedra/copyright b/Operations_on_polyhedra/package_info/Operations_on_polyhedra/copyright deleted file mode 100644 index d6bcad90777..00000000000 --- a/Operations_on_polyhedra/package_info/Operations_on_polyhedra/copyright +++ /dev/null @@ -1,2 +0,0 @@ -GeometryFactory -INRIA Sophia-Antipolis diff --git a/Operations_on_polyhedra/package_info/Operations_on_polyhedra/dependencies b/Operations_on_polyhedra/package_info/Operations_on_polyhedra/dependencies deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/Operations_on_polyhedra/package_info/Operations_on_polyhedra/description.txt b/Operations_on_polyhedra/package_info/Operations_on_polyhedra/description.txt deleted file mode 100644 index fb0b8c5d6c6..00000000000 --- a/Operations_on_polyhedra/package_info/Operations_on_polyhedra/description.txt +++ /dev/null @@ -1,3 +0,0 @@ -Package gathering non-documented functionalities used in a polyhedron demo plugin -and some other packages. At some point, we might decide to document them properly. - diff --git a/Operations_on_polyhedra/package_info/Operations_on_polyhedra/license.txt b/Operations_on_polyhedra/package_info/Operations_on_polyhedra/license.txt deleted file mode 100644 index 8bb8efcb72b..00000000000 --- a/Operations_on_polyhedra/package_info/Operations_on_polyhedra/license.txt +++ /dev/null @@ -1 +0,0 @@ -GPL (v3 or later) diff --git a/Operations_on_polyhedra/package_info/Operations_on_polyhedra/maintainer b/Operations_on_polyhedra/package_info/Operations_on_polyhedra/maintainer deleted file mode 100644 index 5b055eb65c1..00000000000 --- a/Operations_on_polyhedra/package_info/Operations_on_polyhedra/maintainer +++ /dev/null @@ -1 +0,0 @@ -Sébastien Loriot diff --git a/Polygon_mesh_processing/test/Polygon_mesh_processing/run_test_corefinement_bool_op_full.sh b/Polygon_mesh_processing/test/Polygon_mesh_processing/run_test_corefinement_bool_op_full.sh index 3d54c2f0f47..2cea97cfb6b 100644 --- a/Polygon_mesh_processing/test/Polygon_mesh_processing/run_test_corefinement_bool_op_full.sh +++ b/Polygon_mesh_processing/test/Polygon_mesh_processing/run_test_corefinement_bool_op_full.sh @@ -4,27 +4,19 @@ GREEN="\\033[1;32m" NORMAL="\\033[0;39m" RED="\\033[1;31m" -METHOD="SM" - -if [ ! -e $1 ""]; then - METHOD=$1 -fi - -echo "Testing with $METHOD" - k=`wc -l test_corefinement_bool_op_full.cmd | awk '{print $1}'` for i in `seq 1 $k`; do files=`head -n $i test_corefinement_bool_op_full.cmd | tail -n 1` f1=`echo $files | awk '{print $1}'` f2=`echo $files | awk '{print $2}'` - ru=`echo $files | awk '{print $5}'` - ri=`echo $files | awk '{print $6}'` - rm=`echo $files | awk '{print $7}'` - rmr=`echo $files | awk '{print $8}'` + ru=`echo $files | awk '{print $4}'` + ri=`echo $files | awk '{print $5}'` + rm=`echo $files | awk '{print $6}'` + rmr=`echo $files | awk '{print $7}'` echo -n "==== " $f1 $f2 " " - if (./test_corefinement_bool_op $f1 $f2 $METHOD ALL $ru $ri $rm $rmr|| false ) > /dev/null 2>&1; then + if (./test_corefinement_bool_op $f1 $f2 ALL $ru $ri $rm $rmr|| false ) > /dev/null 2>&1; then echo -e " ==> $GREEN SUCCEED" echo -e -n "$NORMAL" else diff --git a/Polygon_mesh_processing/test/Polygon_mesh_processing/test_corefinement_bool_op.cmd b/Polygon_mesh_processing/test/Polygon_mesh_processing/test_corefinement_bool_op.cmd index 15f6bd5c23d..3740f7caf89 100644 --- a/Polygon_mesh_processing/test/Polygon_mesh_processing/test_corefinement_bool_op.cmd +++ b/Polygon_mesh_processing/test/Polygon_mesh_processing/test_corefinement_bool_op.cmd @@ -1 +1 @@ -data-coref/elephant.off data-coref/sphere.off SM ALL 1 1 1 1 +data-coref/elephant.off data-coref/sphere.off ALL 1 1 1 1 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 97e23b20cf2..ec1664aef97 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 @@ -1,23 +1,15 @@ // #define CGAL_COREFINEMENT_POLYHEDRA_DEBUG // #define CGAL_COREFINEMENT_DEBUG -// #define CGAL_TODO_WARNINGS #include #include #include -#include - #include #include #include #include -// includes for Operations on polyhedra -#include -#include -#include - #include #include @@ -28,16 +20,6 @@ typedef CGAL::Surface_mesh Surface_mesh; namespace PMP = CGAL::Polygon_mesh_processing; namespace CFR = PMP::Corefinement; -//includes typedefs for Operations on polyhedra -typedef CGAL::Polyhedron_3 Polyhedron; -typedef std::map Facet_id_map; -typedef boost::associative_property_map Facet_id_pmap; -typedef CGAL::Corefinement - ::Polyhedra_output_builder< Polyhedron, - Facet_id_pmap> Output_builder; -typedef CGAL::Node_visitor_refine_polyhedra Split_visitor; - struct Result_checking { bool check; @@ -49,111 +31,6 @@ struct Result_checking Result_checking() : check(false) {} }; -void run_boolean_operations( - Polyhedron& P, - Polyhedron& Q, - Polyhedron& union_, - Polyhedron& inter, - Polyhedron& P_minus_Q, - Polyhedron& Q_minus_P, - std::string scenario, - std::size_t id, - const Result_checking& rc) -{ - std::cout << "Scenario #" << id << " - " << scenario << "\n"; - - CGAL::Emptyset_iterator output_it; - Facet_id_map P_facet_id_map, Q_facet_id_map; - - CGAL::cpp11::array, 4 > output; - - 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, - output, - Facet_id_pmap(P_facet_id_map), - Facet_id_pmap(Q_facet_id_map) ); - Split_visitor visitor(output_builder); - - CGAL::Intersection_of_Polyhedra_3 polyline_intersections(visitor); - std::cout << " Vertices before " << P.size_of_vertices() - << " " << Q.size_of_vertices() << std::endl; - polyline_intersections(P, Q, output_it); - std::cout << " Vertices after " << P.size_of_vertices() - << " " << Q.size_of_vertices() << std::endl; - - - if ( output_builder.union_valid() ){ - std::cout << " Union is a valid operation\n"; - assert(union_.is_valid()); - #ifdef CGAL_COREFINEMENT_DEBUG - std::stringstream fname; - fname << scenario << "_P_union_Q.off"; - std::ofstream output(fname.str().c_str()); - output << std::setprecision(17) << union_; - #endif - } - else - std::cout << " Union is invalid\n"; - - if ( output_builder.intersection_valid() ){ - std::cout << " Intersection is a valid operation\n"; - assert(inter.is_valid()); - #ifdef CGAL_COREFINEMENT_DEBUG - std::stringstream fname; - fname << scenario << "_P_inter_Q.off"; - std::ofstream output(fname.str().c_str()); - output << std::setprecision(17) << inter; - #endif - } - else - std::cout << " Intersection is invalid\n"; - - if ( output_builder.P_minus_Q_valid() ){ - std::cout << " P-Q is a valid operation\n"; - assert(P_minus_Q.is_valid()); - #ifdef CGAL_COREFINEMENT_DEBUG - std::stringstream fname; - fname << scenario << "_P_minus_Q.off"; - std::ofstream output(fname.str().c_str()); - output << std::setprecision(17) << P_minus_Q; - #endif - } - else - std::cout << " P-Q is invalid\n"; - - if ( output_builder.Q_minus_P_valid() ){ - std::cout << " Q-P is a valid operation\n"; - assert(Q_minus_P.is_valid()); - #ifdef CGAL_COREFINEMENT_DEBUG - std::stringstream fname; - fname << scenario << "_Q_minus_P.off"; - std::ofstream output(fname.str().c_str()); - output << std::setprecision(17) << Q_minus_P; - #endif - } - else - std::cout << " Q-P is invalid\n"; - - if (rc.check) - { - if(output_builder.union_valid()!=rc.union_res || - output_builder.intersection_valid()!=rc.inter_res || - output_builder.P_minus_Q_valid()!=rc.P_minus_Q_res || - output_builder.Q_minus_P_valid()!=rc.Q_minus_P_res) - { - std::cerr << " ERROR: at least one operation is not as expected!\n"; - exit(EXIT_FAILURE); - } - else - std::cout << " All operations are as expected.\n"; - } -} - - void run_boolean_operations( Surface_mesh& tm1, Surface_mesh& tm2, @@ -344,34 +221,26 @@ void run(char* P_fname, char* Q_fname, int k, int main(int argc,char** argv) { if (argc<3){ - std::cerr << "Usage "<< argv[0] << " file1.off file2.off [SM/POLY] [scenario_id/ALL] [0/1 0/1 0/1 0/1 (expected valid operations U I P-Q Q-P)]\n"; + std::cerr << "Usage "<< argv[0] << " file1.off file2.off [scenario_id/ALL] [0/1 0/1 0/1 0/1 (expected valid operations U I P-Q Q-P)]\n"; return 1; } Result_checking rc; - if (argc==9) + if (argc==8) { rc.check=true; - rc.union_res = atoi(argv[5])!=0; - rc.inter_res = atoi(argv[6])!=0; - rc.P_minus_Q_res = atoi(argv[7])!=0; - rc.Q_minus_P_res = atoi(argv[8])!=0; + rc.union_res = atoi(argv[4])!=0; + rc.inter_res = atoi(argv[5])!=0; + rc.P_minus_Q_res = atoi(argv[6])!=0; + rc.Q_minus_P_res = atoi(argv[7])!=0; } int scenario = -1; - if (argc>=5 && std::string(argv[4])!=std::string("ALL")) + if (argc>=5 && std::string(argv[3])!=std::string("ALL")) scenario = atoi(argv[4]); - if ( argc==3 || std::string(argv[3])==std::string("SM") ) - run(argv[1], argv[2], scenario, rc); - else - if ( std::string(argv[3])==std::string("POLY") ) - run(argv[1], argv[2], scenario, rc); - else - { - std::cout <<"Invalid value, only SM or POLY can be given\n"; - return 1; - } + run(argv[1], argv[2], scenario, rc); + return 0; } diff --git a/Polygon_mesh_processing/test/Polygon_mesh_processing/test_corefinement_bool_op_full.cmd b/Polygon_mesh_processing/test/Polygon_mesh_processing/test_corefinement_bool_op_full.cmd index 8b49a99ecf9..1c03b1ed689 100644 --- a/Polygon_mesh_processing/test/Polygon_mesh_processing/test_corefinement_bool_op_full.cmd +++ b/Polygon_mesh_processing/test/Polygon_mesh_processing/test_corefinement_bool_op_full.cmd @@ -1,49 +1,49 @@ -data-coref/sphere.off data-coref/sphere-2.off SM ALL 1 1 1 1 -data-coref/cube.off data-coref/coplanar_with_cube.off SM ALL 1 1 1 1 -data-coref/cube.off data-coref/coplanar_with_cube1.off SM ALL 1 1 1 1 -data-coref/cube.off data-coref/coplanar_with_cube2.off SM ALL 1 1 1 1 -data-coref/cube.off data-coref/with_cube.off SM ALL 1 1 1 0 -data-coref/open_large_cube.off data-coref/open_small_cube.off SM ALL 1 1 1 1 -data-coref/open_large_cube.off data-coref/open_small_cube_inout.off SM ALL 0 0 0 0 -data-coref/elephant.off data-coref/elephant_plane/plane_1.off SM ALL 1 1 1 1 -data-coref/elephant.off data-coref/elephant_plane/plane_2.off SM ALL 1 1 1 1 -data-coref/elephant.off data-coref/elephant_plane/plane_3.off SM ALL 1 1 1 1 -data-coref/open_tet_1.off data-coref/open_tet_2.off SM ALL 1 1 1 1 -data-coref/small_cube_coplanar_inside.off data-coref/small_cube_coplanar_outside.off SM ALL 1 1 1 1 -data-coref/large_cube_coplanar.off data-coref/small_cube_coplanar_inside.off SM ALL 1 1 1 1 -data-coref/large_cube_coplanar.off data-coref/small_cube_coplanar_outside.off SM ALL 1 1 1 1 -data-coref/P7.off data-coref/Q7.off SM ALL 1 1 1 1 -data-coref/square1.off data-coref/square2.off SM ALL 1 1 1 1 -data-coref/square_pair.off data-coref/square2.off SM ALL 1 1 1 1 -data-coref/elephant.off data-coref/sphere.off SM ALL 1 1 1 1 -data-coref/elephant_split_1.off data-coref/elephant_split_2.off SM ALL 1 1 1 1 -data-coref/cube.off data-coref/cube_on_cube_edge.off SM ALL 0 1 1 1 -data-coref/cube.off data-coref/cube_on_cube_corner.off SM ALL 1 1 1 1 -data-coref/cow.off data-coref/cross.off SM ALL 1 1 1 1 -data-coref/sphere.off data-coref/sphere_trans_cut.off SM ALL 0 0 0 0 -data-coref/open_t11.off data-coref/open_t12.off SM ALL 1 1 1 1 -data-coref/cube.off data-coref/coplanar_with_cube3.off SM ALL 1 1 1 1 -data-coref/cube.off data-coref/coplanar_with_cube4.off SM ALL 1 1 1 1 -data-coref/square1.off data-coref/square_pair.off SM ALL 1 1 1 1 -data-coref/cube_meshed.off data-coref/cube.off SM ALL 1 1 1 1 -data-coref/cube.off data-coref/cube_interior_tgt.off SM ALL 1 1 1 1 -data-coref/cube.off data-coref/edge_tangent_to_cube.off SM ALL 1 1 0 1 -data-coref/cube_dig.off data-coref/wedge.off SM ALL 1 1 1 1 -data-coref/star_tgt1-0.off data-coref/star_tgt2-0.off SM ALL 1 1 1 0 -data-coref/star_tgt1-0.off data-coref/star_tgt2-1.off SM ALL 1 1 1 0 -data-coref/star_tgt1-0.off data-coref/star_tgt2-2.off SM ALL 0 1 1 1 -data-coref/star_tgt1-0.off data-coref/star_tgt2-3.off SM ALL 1 1 1 0 -data-coref/star_tgt1-0.off data-coref/star_tgt2-4.off SM ALL 0 1 1 1 -data-coref/star_tgt1-0.off data-coref/star_tgt2-5.off SM ALL 1 1 1 0 -data-coref/star_tgt1-1.off data-coref/star_tgt2-0.off SM ALL 1 1 1 0 -data-coref/star_tgt1-1.off data-coref/star_tgt2-1.off SM ALL 1 1 1 0 -data-coref/star_tgt1-1.off data-coref/star_tgt2-2.off SM ALL 0 1 1 1 -data-coref/star_tgt1-1.off data-coref/star_tgt2-3.off SM ALL 1 1 1 0 -data-coref/star_tgt1-1.off data-coref/star_tgt2-4.off SM ALL 0 1 1 1 -data-coref/star_tgt1-1.off data-coref/star_tgt2-5.off SM ALL 1 1 1 0 -data-coref/star_tgt1-2.off data-coref/star_tgt2-0.off SM ALL 1 1 1 0 -data-coref/star_tgt1-2.off data-coref/star_tgt2-1.off SM ALL 1 1 1 0 -data-coref/star_tgt1-2.off data-coref/star_tgt2-2.off SM ALL 0 1 1 1 -data-coref/star_tgt1-2.off data-coref/star_tgt2-3.off SM ALL 1 1 1 1 -data-coref/star_tgt1-2.off data-coref/star_tgt2-4.off SM ALL 0 1 1 1 -data-coref/star_tgt1-2.off data-coref/star_tgt2-5.off SM ALL 1 1 1 1 +data-coref/sphere.off data-coref/sphere-2.off ALL 1 1 1 1 +data-coref/cube.off data-coref/coplanar_with_cube.off ALL 1 1 1 1 +data-coref/cube.off data-coref/coplanar_with_cube1.off ALL 1 1 1 1 +data-coref/cube.off data-coref/coplanar_with_cube2.off ALL 1 1 1 1 +data-coref/cube.off data-coref/with_cube.off ALL 1 1 1 0 +data-coref/open_large_cube.off data-coref/open_small_cube.off ALL 1 1 1 1 +data-coref/open_large_cube.off data-coref/open_small_cube_inout.off ALL 0 0 0 0 +data-coref/elephant.off data-coref/elephant_plane/plane_1.off ALL 1 1 1 1 +data-coref/elephant.off data-coref/elephant_plane/plane_2.off ALL 1 1 1 1 +data-coref/elephant.off data-coref/elephant_plane/plane_3.off ALL 1 1 1 1 +data-coref/open_tet_1.off data-coref/open_tet_2.off ALL 1 1 1 1 +data-coref/small_cube_coplanar_inside.off data-coref/small_cube_coplanar_outside.off ALL 1 1 1 1 +data-coref/large_cube_coplanar.off data-coref/small_cube_coplanar_inside.off ALL 1 1 1 1 +data-coref/large_cube_coplanar.off data-coref/small_cube_coplanar_outside.off ALL 1 1 1 1 +data-coref/P7.off data-coref/Q7.off ALL 1 1 1 1 +data-coref/square1.off data-coref/square2.off ALL 1 1 1 1 +data-coref/square_pair.off data-coref/square2.off ALL 1 1 1 1 +data-coref/elephant.off data-coref/sphere.off ALL 1 1 1 1 +data-coref/elephant_split_1.off data-coref/elephant_split_2.off ALL 1 1 1 1 +data-coref/cube.off data-coref/cube_on_cube_edge.off ALL 0 1 1 1 +data-coref/cube.off data-coref/cube_on_cube_corner.off ALL 1 1 1 1 +data-coref/cow.off data-coref/cross.off ALL 1 1 1 1 +data-coref/sphere.off data-coref/sphere_trans_cut.off ALL 0 0 0 0 +data-coref/open_t11.off data-coref/open_t12.off ALL 1 1 1 1 +data-coref/cube.off data-coref/coplanar_with_cube3.off ALL 1 1 1 1 +data-coref/cube.off data-coref/coplanar_with_cube4.off ALL 1 1 1 1 +data-coref/square1.off data-coref/square_pair.off ALL 1 1 1 1 +data-coref/cube_meshed.off data-coref/cube.off ALL 1 1 1 1 +data-coref/cube.off data-coref/cube_interior_tgt.off ALL 1 1 1 1 +data-coref/cube.off data-coref/edge_tangent_to_cube.off ALL 1 1 0 1 +data-coref/cube_dig.off data-coref/wedge.off ALL 1 1 1 1 +data-coref/star_tgt1-0.off data-coref/star_tgt2-0.off ALL 1 1 1 0 +data-coref/star_tgt1-0.off data-coref/star_tgt2-1.off ALL 1 1 1 0 +data-coref/star_tgt1-0.off data-coref/star_tgt2-2.off ALL 0 1 1 1 +data-coref/star_tgt1-0.off data-coref/star_tgt2-3.off ALL 1 1 1 0 +data-coref/star_tgt1-0.off data-coref/star_tgt2-4.off ALL 0 1 1 1 +data-coref/star_tgt1-0.off data-coref/star_tgt2-5.off ALL 1 1 1 0 +data-coref/star_tgt1-1.off data-coref/star_tgt2-0.off ALL 1 1 1 0 +data-coref/star_tgt1-1.off data-coref/star_tgt2-1.off ALL 1 1 1 0 +data-coref/star_tgt1-1.off data-coref/star_tgt2-2.off ALL 0 1 1 1 +data-coref/star_tgt1-1.off data-coref/star_tgt2-3.off ALL 1 1 1 0 +data-coref/star_tgt1-1.off data-coref/star_tgt2-4.off ALL 0 1 1 1 +data-coref/star_tgt1-1.off data-coref/star_tgt2-5.off ALL 1 1 1 0 +data-coref/star_tgt1-2.off data-coref/star_tgt2-0.off ALL 1 1 1 0 +data-coref/star_tgt1-2.off data-coref/star_tgt2-1.off ALL 1 1 1 0 +data-coref/star_tgt1-2.off data-coref/star_tgt2-2.off ALL 0 1 1 1 +data-coref/star_tgt1-2.off data-coref/star_tgt2-3.off ALL 1 1 1 1 +data-coref/star_tgt1-2.off data-coref/star_tgt2-4.off ALL 0 1 1 1 +data-coref/star_tgt1-2.off data-coref/star_tgt2-5.off ALL 1 1 1 1 From 0f001bda3e4e48e29ba11c1daf06ce20409b42d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Loriot?= Date: Wed, 22 Aug 2018 10:57:58 +0200 Subject: [PATCH 065/116] update test to use PMP function --- .../MCF_Skeleton_test.cpp | 14 +++++++++----- .../skeleton_connectivity_test.cpp | 12 +++++++----- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/Surface_mesh_skeletonization/test/Surface_mesh_skeletonization/MCF_Skeleton_test.cpp b/Surface_mesh_skeletonization/test/Surface_mesh_skeletonization/MCF_Skeleton_test.cpp index 63deba74b69..4d710853a78 100644 --- a/Surface_mesh_skeletonization/test/Surface_mesh_skeletonization/MCF_Skeleton_test.cpp +++ b/Surface_mesh_skeletonization/test/Surface_mesh_skeletonization/MCF_Skeleton_test.cpp @@ -2,7 +2,7 @@ #include #include #include -#include +#include #include @@ -34,12 +34,16 @@ bool is_mesh_valid(Polyhedron& pMesh) return false; } + std::size_t i = 0; + BOOST_FOREACH(Polyhedron::Face_handle fh, faces(pMesh)) + fh->id()=i++; + // the algorithm is only applicable on a mesh // that has only one connected component - std::size_t num_component; - CGAL::Counting_output_iterator output_it(&num_component); - CGAL::internal::corefinement::extract_connected_components(pMesh, output_it); - ++output_it; + std::size_t num_component = + CGAL::Polygon_mesh_processing::connected_components( + pMesh, get(CGAL::dynamic_face_property_t(), pMesh)); + if (num_component != 1) { std::cerr << "The mesh is not a single closed mesh. It has " diff --git a/Surface_mesh_skeletonization/test/Surface_mesh_skeletonization/skeleton_connectivity_test.cpp b/Surface_mesh_skeletonization/test/Surface_mesh_skeletonization/skeleton_connectivity_test.cpp index 4409100ab7e..49da555bf98 100644 --- a/Surface_mesh_skeletonization/test/Surface_mesh_skeletonization/skeleton_connectivity_test.cpp +++ b/Surface_mesh_skeletonization/test/Surface_mesh_skeletonization/skeleton_connectivity_test.cpp @@ -2,7 +2,7 @@ #include #include #include -#include +#include #include @@ -35,13 +35,15 @@ bool is_mesh_valid(Polyhedron& pMesh) std::cerr << "The mesh is not a pure triangle mesh."; return false; } + std::size_t i=0; + BOOST_FOREACH(Polyhedron::Face_handle fh, faces(pMesh)) + fh->id()=i++; // the algorithm is only applicable on a mesh // that has only one connected component - std::size_t num_component; - CGAL::Counting_output_iterator output_it(&num_component); - CGAL::internal::corefinement::extract_connected_components(pMesh, output_it); - ++output_it; + std::size_t num_component = + CGAL::Polygon_mesh_processing::connected_components( + pMesh, get(CGAL::dynamic_face_property_t(), pMesh)); if (num_component != 1) { std::cerr << "The mesh is not a single closed mesh. It has " From 5efd5b9931c340b1919e2a0dcfbe9ee938099008 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Loriot?= Date: Thu, 23 Aug 2018 13:04:31 +0200 Subject: [PATCH 066/116] remove mentions to Operation_on_polyhedra package --- .gitignore | 2 -- .travis.yml | 2 +- .travis/packages.txt | 3 +-- 3 files changed, 2 insertions(+), 5 deletions(-) diff --git a/.gitignore b/.gitignore index c1553f03af9..44a3f3a6bd0 100644 --- a/.gitignore +++ b/.gitignore @@ -1206,5 +1206,3 @@ gmon.* /Stream_support/test/Stream_support/cgal_test_with_cmake /*.html /Snap_rounding_2/test/Snap_rounding_2/data/out -/Operations_on_polyhedra/examples/Operations_on_polyhedra/cgal_test_with_cmake -/Operations_on_polyhedra/test/Operations_on_polyhedra/cgal_test_with_cmake diff --git a/.travis.yml b/.travis.yml index d5461dd8ece..c35e8444e8e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -29,7 +29,7 @@ env: - PACKAGE='Mesher_level Minkowski_sum_2 Minkowski_sum_3 ' - PACKAGE='Modifier Modular_arithmetic Nef_2 ' - PACKAGE='Nef_3 Nef_S2 NewKernel_d ' - - PACKAGE='Number_types OpenNL Operations_on_polyhedra ' + - PACKAGE='Number_types OpenNL ' - PACKAGE='Optimal_transportation_reconstruction_2 Optimisation_basic Partition_2 ' - PACKAGE='Periodic_2_triangulation_2 Periodic_3_mesh_3 Periodic_3_triangulation_3 ' - PACKAGE='Point_set_2 Point_set_3 Point_set_processing_3 ' diff --git a/.travis/packages.txt b/.travis/packages.txt index 4c872da0115..572bb16b53c 100644 --- a/.travis/packages.txt +++ b/.travis/packages.txt @@ -68,8 +68,7 @@ Nef_3 Nef_S2 NewKernel_d Number_types -OpenNL -Operations_on_polyhedra +OpenNL Optimal_transportation_reconstruction_2 Optimisation_basic Partition_2 From fe655ec7b4c04d6ae960941fd9e7213f33f9263c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Loriot?= Date: Thu, 23 Aug 2018 13:27:18 +0200 Subject: [PATCH 067/116] regenerate travis files --- .travis.yml | 43 +++++++++++++++++++++---------------------- .travis/packages.txt | 2 +- 2 files changed, 22 insertions(+), 23 deletions(-) diff --git a/.travis.yml b/.travis.yml index c35e8444e8e..b7c16dd2e1b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -29,28 +29,27 @@ env: - PACKAGE='Mesher_level Minkowski_sum_2 Minkowski_sum_3 ' - PACKAGE='Modifier Modular_arithmetic Nef_2 ' - PACKAGE='Nef_3 Nef_S2 NewKernel_d ' - - PACKAGE='Number_types OpenNL ' - - PACKAGE='Optimal_transportation_reconstruction_2 Optimisation_basic Partition_2 ' - - PACKAGE='Periodic_2_triangulation_2 Periodic_3_mesh_3 Periodic_3_triangulation_3 ' - - PACKAGE='Point_set_2 Point_set_3 Point_set_processing_3 ' - - PACKAGE='Point_set_shape_detection_3 Poisson_surface_reconstruction_3 Polygon ' - - PACKAGE='Polygon_mesh_processing Polyhedron Polyhedron_IO ' - - PACKAGE='Polyline_simplification_2 Polynomial Polytope_distance_d ' - - PACKAGE='Principal_component_analysis Principal_component_analysis_LGPL Profiling_tools ' - - PACKAGE='Property_map QP_solver Random_numbers ' - - PACKAGE='Ridges_3 Scale_space_reconstruction_3 Scripts ' - - PACKAGE='SearchStructures Segment_Delaunay_graph_2 Segment_Delaunay_graph_Linf_2 ' - - PACKAGE='Set_movable_separability_2 Skin_surface_3 Snap_rounding_2 ' - - PACKAGE='Solver_interface Spatial_searching Spatial_sorting ' - - PACKAGE='STL_Extension Straight_skeleton_2 Stream_lines_2 ' - - PACKAGE='Stream_support Subdivision_method_3 Surface_mesh ' - - PACKAGE='Surface_mesh_deformation Surface_mesher Surface_mesh_parameterization ' - - PACKAGE='Surface_mesh_segmentation Surface_mesh_shortest_path Surface_mesh_simplification ' - - PACKAGE='Surface_mesh_skeletonization Surface_sweep_2 TDS_2 ' - - PACKAGE='TDS_3 Testsuite Three ' - - PACKAGE='Triangulation Triangulation_2 Triangulation_3 ' - - PACKAGE='Union_find Visibility_2 Voronoi_diagram_2 ' - - PACKAGE='wininst ' + - PACKAGE='Number_types OpenNL Optimal_transportation_reconstruction_2 ' + - PACKAGE='Optimisation_basic Partition_2 Periodic_2_triangulation_2 ' + - PACKAGE='Periodic_3_mesh_3 Periodic_3_triangulation_3 Point_set_2 ' + - PACKAGE='Point_set_3 Point_set_processing_3 Point_set_shape_detection_3 ' + - PACKAGE='Poisson_surface_reconstruction_3 Polygon Polygon_mesh_processing ' + - PACKAGE='Polyhedron Polyhedron_IO Polyline_simplification_2 ' + - PACKAGE='Polynomial Polytope_distance_d Principal_component_analysis ' + - PACKAGE='Principal_component_analysis_LGPL Profiling_tools Property_map ' + - PACKAGE='QP_solver Random_numbers Ridges_3 ' + - PACKAGE='Scale_space_reconstruction_3 Scripts SearchStructures ' + - PACKAGE='Segment_Delaunay_graph_2 Segment_Delaunay_graph_Linf_2 Set_movable_separability_2 ' + - PACKAGE='Skin_surface_3 Snap_rounding_2 Solver_interface ' + - PACKAGE='Spatial_searching Spatial_sorting STL_Extension ' + - PACKAGE='Straight_skeleton_2 Stream_lines_2 Stream_support ' + - PACKAGE='Subdivision_method_3 Surface_mesh Surface_mesh_deformation ' + - PACKAGE='Surface_mesher Surface_mesh_parameterization Surface_mesh_segmentation ' + - PACKAGE='Surface_mesh_shortest_path Surface_mesh_simplification Surface_mesh_skeletonization ' + - PACKAGE='Surface_sweep_2 TDS_2 TDS_3 ' + - PACKAGE='Testsuite Three Triangulation ' + - PACKAGE='Triangulation_2 Triangulation_3 Union_find ' + - PACKAGE='Visibility_2 Voronoi_diagram_2 wininst ' compiler: clang-3.6 install: - echo "$PWD" diff --git a/.travis/packages.txt b/.travis/packages.txt index 572bb16b53c..53b128c1c85 100644 --- a/.travis/packages.txt +++ b/.travis/packages.txt @@ -68,7 +68,7 @@ Nef_3 Nef_S2 NewKernel_d Number_types -OpenNL +OpenNL Optimal_transportation_reconstruction_2 Optimisation_basic Partition_2 From bb14479266c0e48ccf3a3056b9e076978aa2f1ac Mon Sep 17 00:00:00 2001 From: Maxime Gimeno Date: Fri, 24 Aug 2018 14:48:55 +0200 Subject: [PATCH 068/116] Add -C option to all `cmake` calls in the testsuite mechanism --- .../Arrangement_on_surface_2/cgal_test_base | 1759 ----------------- .../cgal_test_with_cmake | 1759 ++++++++++++++++- .../demo/CGAL_ipelets/cgal_test_with_cmake | 3 +- .../test/Minkowski_sum_2/cgal_test_with_cmake | 3 +- .../cgal_test_with_cmake | 3 +- .../cgal_test_with_cmake | 3 +- .../demo/Polyhedron/cgal_test_with_cmake | 3 +- Scripts/developer_scripts/autotest_cgal | 4 +- Scripts/developer_scripts/create_cgal_test | 3 +- .../cgal_test_with_cmake | 3 +- .../test/Surface_sweep_2/cgal_test_base | 3 +- 11 files changed, 1776 insertions(+), 1770 deletions(-) delete mode 100755 Arrangement_on_surface_2/test/Arrangement_on_surface_2/cgal_test_base diff --git a/Arrangement_on_surface_2/test/Arrangement_on_surface_2/cgal_test_base b/Arrangement_on_surface_2/test/Arrangement_on_surface_2/cgal_test_base deleted file mode 100755 index bfd9c7c30b6..00000000000 --- a/Arrangement_on_surface_2/test/Arrangement_on_surface_2/cgal_test_base +++ /dev/null @@ -1,1759 +0,0 @@ -#! /bin/bash - -# This is a script for the CGAL test suite. Such a script must obey -# the following rules: -# -# - the name of the script is cgal_test -# - for every target two one line messages are written to the file 'error.txt' -# the first one indicates if the compilation was successful -# the second one indicates if the execution was successful -# if one of the two was not successful, the line should start with 'ERROR:' -# - running the script should not require any user interaction -# - the script should clean up object files and executables - -# SET PARAMETERS FOR cgal_test - - -ERRORFILE=error.txt -DO_RUN=y -if [ -z "${MAKE_CMD}" ]; then - MAKE_CMD=make -fi - - -FULL_ERROR_DESCRIPTION_FILE=ProgramOutput.error.txt - -#---------------------------------------------------------------------# -# compile_and_run -#---------------------------------------------------------------------# - -# note that these values shloud match to the values in test_configuration.h file - -CARTESIAN_KERNEL=0 -SIMPLE_CARTESIAN_KERNEL=1 -UNIVARIATE_ALGEBRAIC_KERNEL=2 - -SEGMENT_GEOM_TRAITS=0 -NON_CACHING_SEGMENT_GEOM_TRAITS=1 -POLYLINE_GEOM_TRAITS=2 -NON_CACHING_POLYLINE_GEOM_TRAITS=3 -POLYCURVE_CONIC_GEOM_TRAITS=14 -POLYCURVE_CIRCULAR_ARC_GEOM_TRAITS=15 -POLYCURVE_BEZIER_GEOM_TRAITS=16 -LINEAR_GEOM_TRAITS=4 -CORE_CONIC_GEOM_TRAITS=5 -LINE_ARC_GEOM_TRAITS=6 -CIRCULAR_ARC_GEOM_TRAITS=7 -CIRCULAR_LINE_ARC_GEOM_TRAITS=8 -CIRCLE_SEGMENT_GEOM_TRAITS=9 -BEZIER_GEOM_TRAITS=10 -GEODESIC_ARC_ON_SPHERE_GEOM_TRAITS=11 -RATIONAL_ARC_GEOM_TRAITS=12 -ALGEBRAIC_GEOM_TRAITS=13 -POLYCURVE_CONIC_GEOM_TRAITS=14 -POLYCURVE_CIRCULAR_ARC_GEOM_TRAITS=15 -POLYCURVE_BEZIER_GEOM_TRAITS=16 -FLAT_TORUS_GEOM_TRAITS=17 - -PLANAR_BOUNDED_TOPOL_TRAITS=0 -PLANAR_UNBOUNDED_TOPOL_TRAITS=1 -SPHERICAL_TOPOL_TRAITS=2 - -DOUBLE_NT=0 -MP_FLOAT_NT=1 -GMPZ_NT=2 -LEDA_RAT_NT=3 -QUOTIENT_MP_FLOAT_NT=4 -QUOTIENT_CGAL_GMPZ_NT=5 -CGAL_GMPQ_NT=6 -LAZY_LEDA_RAT_NT=7 -LAZY_CGAL_GMPQ_NT=8 -LAZY_QUOTIENT_MP_FLOAT_NT=9 -LEDA_REAL_NT=10 -CORE_EXPR_NT=11 -LAZY_GMPZ_NT=12 -LEDA_INT_NT=13 -CGAL_GMPZ_NT=14 -CORE_INT_NT=15 -CORE_RAT_NT=16 - -if [ -n "${CGAL_DISABLE_GMP}" ]; then - echo GMP is disable. Try to use LEDA instead. - GMPZ_NT=$LEDA_INT_NT - QUOTIENT_CGAL_GMPZ_NT=$LEDA_RAT_NT - CGAL_GMPQ_NT=$LEDA_RAT_NT - LAZY_CGAL_GMPQ_NT=$LAZY_LEDA_RAT_NT - LAZY_GMPZ_NT=$LAZY_LEDA_RAT_NT - CGAL_GMPZ_NT=$LEDA_INT_NT -fi - -COMPARE=1 -VERTEX=2 -IS_VERTICAL=3 -COMPARE_Y_AT_X=4 -COMPARE_Y_AT_X_LEFT=5 -COMPARE_Y_AT_X_RIGHT=6 -MAKE_X_MONOTONE=7 -INTERSECT=8 -SPLIT=9 -ARE_MERGEABLE=10 -MERGE=11 -ASSERTIONS=12 -CONSTRUCTOR=13 -COMPARE_X_AT_LIMIT=14 -COMPARE_X_NEAR_LIMIT=15 -COMPARE_X_ON_BOUNDARY=16 -COMPARE_X_NEAR_BOUNDARY=17 -COMPARE_Y_NEAR_BOUNDARY=18 -PARAMETER_SPACE_X=19 -PARAMETER_SPACE_Y=20 -X_ON_IDENTIFICATION=21 -Y_ON_IDENTIFICATION=22 -IS_BOUNDED=23 -IS_IN_X_RANGE=24 -COMPARE_Y_POSITION=25 -IS_BETWEEN_CW=26 -COMPARE_CW_AROUND_POINT=27 -PUSH_BACK=28 -PUSH_FRONT=29 -NUMBER_OF_POINTS=32 -COMPARE_ENDPOINTS_XY=33 -CONSTRUCT_OPPOSITE=34 -TRIM=35 - -#---------------------------------------------------------------------# -# configure -#---------------------------------------------------------------------# - -configure() -{ - echo "Configuring... " - rm -rf CMakeCache.txt CMakeFiles/ - echo "cmake --no-warn-unused-cli "$CMAKE_GENERATOR" -DRUNNING_CGAL_AUTO_TEST=TRUE \ - -DCGAL_DIR=\"$CGAL_DIR\" \ - -DCGAL_CXX_FLAGS:STRING=\"$TESTSUITE_CXXFLAGS -I../../include\" \ - -DCGAL_EXE_LINKER_FLAGS=\"$TESTSUITE_LDFLAGS\" \ - -DCMAKE_BUILD_TYPE=NOTFOUND \ - ." - if eval 'cmake --no-warn-unused-cli "$CMAKE_GENERATOR" -DRUNNING_CGAL_AUTO_TEST=TRUE \ - -DCGAL_DIR="$CGAL_DIR" \ - -DCGAL_CXX_FLAGS:STRING="$TESTSUITE_CXXFLAGS -I../../include" \ - -DCGAL_EXE_LINKER_FLAGS="$TESTSUITE_LDFLAGS" \ - -DCMAKE_BUILD_TYPE=NOTFOUND \ - .' ; then - - echo " successful configuration" >> $ERRORFILE - else - echo " ERROR: configuration" >> $ERRORFILE - fi -} - -compile_test_with_flags() -{ - local name=$1; - local type=$2; - local flags=$3; - - echo "Compiling $name $type ... " - if [ "${TEST_WITH_CMAKE}" != "FALSE" ]; then - export TESTSUITE_CXXFLAGS="$flags" - configure - if eval '${MAKE_CMD} VERBOSE=1 -fMakefile \ - $name' ; then - echo " successful compilation of $name $type" >> $ERRORFILE; - SUCCESS="y" - else - echo " ERROR: compilation of $name $type" >> $ERRORFILE; - SUCCESS="" - fi - else - if eval 'make CGAL_MAKEFILE=$CGAL_MAKEFILE \ - TESTSUITE_CXXFLAGS="$TESTSUITE_CXXFLAGS" \ - TESTSUITE_LDFLAGS="$TESTSUITE_LDFLAGS" $name'; then - echo " successful compilation of $name $type" >> $ERRORFILE; - SUCCESS="y" - else - echo " ERROR: compilation of $name $type" >> $ERRORFILE; - SUCCESS="" - fi - fi -} - -run_test() -{ - # $1 - executable name - - basedata=`basename "$5"` - OUTPUTFILE="ProgramOutput.$1" - rm -f $OUTPUTFILE - COMMAND="./$1" - if [ -f $1.cmd ] ; then - COMMAND="$COMMAND `cat $1.cmd`" - fi - if [ -f $1.cin ] ; then - COMMAND="cat $1.cin | $COMMAND" - fi - OUTPUTFILE="$OUTPUTFILE.$PLATFORM" - echo "Executing $1 ($2) ... " - ulimit -t 3600 2> /dev/null - if eval $COMMAND > $OUTPUTFILE 2>&1 ; then - echo " successful execution of $1" >> $ERRORFILE - else - echo " ERROR: execution of $1" >> $ERRORFILE - cat $OUTPUTFILE >> $FULL_ERROR_DESCRIPTION_FILE - fi -} - -run_test_with_flags() -{ - # $1 - executable name - # $2 - test substring name - - basedata=`basename "$5"` - OUTPUTFILE="ProgramOutput.$1" - rm -f $OUTPUTFILE - COMMAND="./$1" - if [ -f $1.cmd ] ; then - COMMAND="$COMMAND `cat $1.cmd`" - elif [ -f $1.$2.cmd ] ; then - COMMAND="$COMMAND `cat $1.$2.cmd`" - OUTPUTFILE=$OUTPUTFILE.`echo $2 | tr '/' '.'` - fi - if [ -f $1.cin ] ; then - COMMAND="cat $1.cin | $COMMAND" - elif [ -f $1.$2.cin ] ; then - COMMAND="cat $1.$2.cin | $COMMAND" - OUTPUTFILE=$OUTPUTFILE.`echo $2 | tr '/' '.'` - fi - OUTPUTFILE="$OUTPUTFILE.$PLATFORM" - echo "Executing $1 ($2) ... " - ulimit -t 3600 2> /dev/null - if eval $COMMAND > $OUTPUTFILE 2>&1 ; then - echo " successful execution of $1 ($2)" >> $ERRORFILE - else - echo " ERROR: execution of $1 ($2)" >> $ERRORFILE - cat $OUTPUTFILE >> $FULL_ERROR_DESCRIPTION_FILE - fi -} - -run_test_alt() -{ - basedata=`basename "$5"` - OUTPUTFILE=ProgramOutput.$1.`echo $5 | tr '/' '.'`.$6 - #echo ****generating file $OUTPUTFILE - # dirdata=`dirname "$datafile"` - rm -f $OUTPUTFILE - COMMAND="./$1" - echo "Executing $1 $5 $6 ... " - if eval $COMMAND $2 $3 $4 $5 $6 > $OUTPUTFILE 2>&1 ; then - echo " successful execution of $5 $6" >> $ERRORFILE - else - echo " ERROR: execution of $5 $6" >> $ERRORFILE - cat $OUTPUTFILE >> $FULL_ERROR_DESCRIPTION_FILE - fi -} - -run_trapped_test() -{ - #local name=$1; - #local datafile=$2; - - if [ "${OSTYPE}" != "cygwin" ]; then - ulimit -t 1200 - run_test_alt $1 $2 $3 $4 $5 $6 - else - run_test_alt $1 $2 $3 $4 $5 $6 & - WPID=$! - trap "kill -9 $WPID" INT - (sleep 1200; kill -9 $WPID) > /dev/null 2>&1 & - SPID=$! - wait $WPID > /dev/null 2>&1 - # RES=$? - kill -9 $SPID > /dev/null 2>&1 - # return $RES - fi -} - -clean_tests() -{ - if [ "${TEST_WITH_CMAKE}" != "FALSE" ]; then - # - # The clean target generated by CMake under cygwin - # always fails for some reason - # - if ! ( uname | grep -q "CYGWIN" ) ; then - make -fMakefile clean - fi - fi - eval "make clean > /dev/null 2>&1" -} - -compile_and_run() -{ - local name=$1; - - echo "Compiling $name ... " - if [ "${TEST_WITH_CMAKE}" != "FALSE" ]; then - if eval '${MAKE_CMD} VERBOSE=1 -fMakefile $1' ; then - echo " successful compilation of $1" >> $ERRORFILE - SUCCESS="y" - else - echo " ERROR: compilation of $1" >> $ERRORFILE - SUCCESS="" - fi - else - SUCCESS="y" - #TESTSUITE_CXXFLAGS="$TESTSUITE_CXXFLAGS" - TESTSUITE_CXXFLAGS="" - TESTSUITE_LDFLAGS="$TESTSUITE_LDFLAGS" - if eval 'make CGAL_MAKEFILE=$CGAL_MAKEFILE \ - TESTSUITE_CXXFLAGS="$TESTSUITE_CXXFLAGS" \ - TESTSUITE_LDFLAGS="$TESTSUITE_LDFLAGS" $name' ; then - echo " successful compilation of $name" >> $ERRORFILE - else - echo " ERROR: compilation of $name" >> $ERRORFILE - SUCCESS="" - fi - fi - - if [ "${TEST_WITH_CMAKE}" != "FALSE" ]; then - if [ -n "$DO_RUN" ] ; then - if [ -n "${SUCCESS}" ] ; then - run_test $1 - else - echo " ERROR: not executed $1" >> $ERRORFILE - fi - fi - else - if [ -n "${SUCCESS}" ] ; then - OUTPUTFILE=ProgramOutput.$name.$PLATFORM - rm -f $OUTPUTFILE - COMMAND="./$name" - if [ -f $name.cmd ] ; then - COMMAND="$COMMAND `cat $name.cmd`" - fi - if [ -f $name.cin ] ; then - COMMAND="cat $name.cin | $COMMAND" - fi - echo "Executing $name ... " - echo " " - if eval $COMMAND > $OUTPUTFILE 2>&1 ; then - echo " successful execution of $name" >> $ERRORFILE - else - echo " ERROR: execution of $name" >> $ERRORFILE - cat $OUTPUTFILE >> $FULL_ERROR_DESCRIPTION_FILE - fi - else - echo " ERROR: not executed $name" >> $ERRORFILE - fi - fi - clean_tests -} - -compile_and_run_trapped_test() -{ - local name=$1; - - if [ "${OSTYPE}" != "cygwin" ]; then - ulimit -t 1200 - compile_and_run $1 - else - compile_and_run $1 & - WPID=$! - trap "kill -9 $WPID" INT - (sleep 1200; kill -9 $WPID) > /dev/null 2>&1 & - SPID=$! - wait $WPID > /dev/null 2>&1 - # RES=$? - kill -9 $SPID > /dev/null 2>&1 - # return $RES - fi -} - -execute_commands_old_structure() -{ - -# at first the tests where designed in such way that all the test input was -# in one file, the points, the xcurves, the curves and the execution block -# this function is used to execute the old tests, one may use it when needed -# but you should remember that separating the input into smaller files creates -# much more modular and comfortable test suite - -# the old structure is default, so this function executes all commands -# except the commands that are given as arguments - - - commands_indicator[COMPARE]=1 - commands_indicator[VERTEX]=1 - commands_indicator[IS_VERTICAL]=1 - commands_indicator[COMPARE_Y_AT_X]=1 - commands_indicator[COMPARE_Y_AT_X_LEFT]=1 - commands_indicator[COMPARE_Y_AT_X_RIGHT]=1 - commands_indicator[MAKE_X_MONOTONE]=1 - commands_indicator[INTERSECT]=1 - commands_indicator[SPLIT]=1 - commands_indicator[ARE_MERGEABLE]=1 - commands_indicator[MERGE]=1 - commands_indicator[ASSERTIONS]=1 - commands_indicator[CONSTRUCTOR]=1 - i=1 - if [ $# -gt 2 ] ; then - for arg in $* ; do - if [ $i -gt 2 ] ; then - commands_indicator[$arg]=0 - fi - let "i+=1" - done - fi - if [ ${commands_indicator[$COMPARE]} -ne 0 ] ; then - run_trapped_test test_traits \ - data/compare.pt data/empty.zero \ - data/empty.zero data/compare $2 - fi - if [ ${commands_indicator[$VERTEX]} -ne 0 ] ; then - run_trapped_test test_traits \ - data/$1/vertex.pt data/$1/vertex.xcv \ - data/empty.zero data/$1/vertex $2 - fi - if [ ${commands_indicator[$IS_VERTICAL]} -ne 0 ] ; then - run_trapped_test test_traits \ - data/empty.zero data/$1/is_vertical.xcv data/empty.zero \ - data/$1/is_vertical $2 - fi - if [ ${commands_indicator[$COMPARE_Y_AT_X]} -ne 0 ] ; then - run_trapped_test test_traits \ - data/$1/compare_y_at_x.pt data/$1/compare_y_at_x.xcv \ - data/empty.zero data/$1/compare_y_at_x $2 - fi - if [ ${commands_indicator[$COMPARE_Y_AT_X_LEFT]} -ne 0 ] ; then - run_trapped_test test_traits \ - data/$1/compare_y_at_x_left.pt data/$1/compare_y_at_x_left.xcv \ - data/empty.zero data/$1/compare_y_at_x_left $2 - fi - if [ ${commands_indicator[$COMPARE_Y_AT_X_RIGHT]} -ne 0 ] ; then - run_trapped_test test_traits \ - data/$1/compare_y_at_x_right.pt data/$1/compare_y_at_x_right.xcv \ - data/empty.zero data/$1/compare_y_at_x_right $2 - fi - if [ ${commands_indicator[$MAKE_X_MONOTONE]} -ne 0 ] ; then - run_trapped_test test_traits \ - data/empty.zero data/$1/make_x_monotone.xcv \ - data/$1/make_x_monotone.cv data/$1/make_x_monotone $2 - fi - if [ ${commands_indicator[$INTERSECT]} -ne 0 ] ; then - run_trapped_test test_traits \ - data/$1/intersect.pt data/$1/intersect.xcv \ - data/empty.zero data/$1/intersect $2 - fi - if [ ${commands_indicator[$SPLIT]} -ne 0 ] ; then - run_trapped_test test_traits \ - data/$1/split.pt data/$1/split.xcv \ - data/empty.zero data/$1/split $2 - fi - if [ ${commands_indicator[$ARE_MERGEABLE]} -ne 0 ] ; then - run_trapped_test test_traits \ - data/empty.zero data/$1/are_mergeable.xcv \ - data/empty.zero data/$1/are_mergeable $2 - fi - if [ ${commands_indicator[$MERGE]} -ne 0 ] ; then - run_trapped_test test_traits \ - data/empty.zero data/$1/merge.xcv \ - data/empty.zero data/$1/merge $2 - fi - if [ ${commands_indicator[$ASSERTIONS]} -ne 0 ] ; then - run_trapped_test test_traits \ - data/$1/assertions.pt data/$1/assertions.xcv \ - data/empty.zero data/$1/assertions $2 - fi - if [ ${commands_indicator[$CONSTRUCTOR]} -ne 0 ] ; then - run_trapped_test test_traits \ - data/empty.zero data/$1/constructor.xcv \ - data/$1/constructor.cv data/$1/constructor $2 - fi -} - -execute_commands_new_structure() -{ - -# the new design for the tests includes separation of the test input into 4 -# parts: points file, xcurves file, curves file and execution block file. -# one may reuse the input files for the various tests - -# the new structure is not default, so this function executes only -# commands that are given as arguments - - commands_indicator[COMPARE]=0 - commands_indicator[VERTEX]=0 - commands_indicator[IS_VERTICAL]=0 - commands_indicator[COMPARE_X_AT_LIMIT]=0 - commands_indicator[COMPARE_X_NEAR_LIMIT]=0 - commands_indicator[COMPARE_X_ON_BOUNDARY]=0 - commands_indicator[COMPARE_X_NEAR_BOUNDARY]=0 - commands_indicator[COMPARE_Y_NEAR_BOUNDARY]=0 - commands_indicator[PARAMETER_SPACE_X]=0 - commands_indicator[PARAMETER_SPACE_Y]=0 - commands_indicator[COMPARE_Y_AT_X]=0 - commands_indicator[COMPARE_Y_AT_X_LEFT]=0 - commands_indicator[COMPARE_Y_AT_X_RIGHT]=0 - commands_indicator[MAKE_X_MONOTONE]=0 - commands_indicator[INTERSECT]=0 - commands_indicator[SPLIT]=0 - commands_indicator[ARE_MERGEABLE]=0 - commands_indicator[MERGE]=0 - commands_indicator[ASSERTIONS]=0 - commands_indicator[CONSTRUCTOR]=0 - commands_indicator[EQUAL]=0 - commands_indicator[PUSH_BACK]=0 - commands_indicator[PUSH_FRONT]=0 - commands_indicator[NUMBER_OF_POINTS]=0 - commands_indicator[COMPARE_ENDPOINTS_XY]=0 - commands_indicator[CONSTRUCT_OPPOSITE]=0 - commands_indicator[TRIM]=0 - i=1 - if [ $# -gt 2 ] ; then - for arg in $* ; do - if [ $i -gt 2 ] ; then - commands_indicator[$arg]=1 - fi - let "i+=1" - done - fi - if [ ${commands_indicator[$COMPARE]} -ne 0 ] ; then - run_trapped_test test_traits data/$1/points \ - data/$1/xcurves data/$1/curves data/$1/compare $2 - fi - if [ ${commands_indicator[$VERTEX]} -ne 0 ] ; then - run_trapped_test test_traits data/$1/points \ - data/$1/xcurves data/$1/curves data/$1/vertex $2 - fi - if [ ${commands_indicator[$IS_VERTICAL]} -ne 0 ] ; then - run_trapped_test test_traits data/$1/points \ - data/$1/xcurves data/$1/curves data/$1/is_vertical $2 - fi - if [ ${commands_indicator[$COMPARE_X_AT_LIMIT]} -ne 0 ] ; then - run_trapped_test test_traits data/$1/points \ - data/$1/xcurves data/$1/curves data/$1/compare_x_at_limit $2 - fi - if [ ${commands_indicator[$COMPARE_X_NEAR_LIMIT]} -ne 0 ] ; then - run_trapped_test test_traits data/$1/points \ - data/$1/xcurves data/$1/curves data/$1/compare_x_near_limit $2 - fi - if [ ${commands_indicator[$COMPARE_X_ON_BOUNDARY]} -ne 0 ] ; then - run_trapped_test test_traits data/$1/points \ - data/$1/xcurves data/$1/curves data/$1/compare_x_on_boundary $2 - fi - if [ ${commands_indicator[$COMPARE_X_NEAR_BOUNDARY]} -ne 0 ] ; then - run_trapped_test test_traits data/$1/points \ - data/$1/xcurves data/$1/curves data/$1/compare_x_near_boundary $2 - fi - if [ ${commands_indicator[$COMPARE_Y_NEAR_BOUNDARY]} -ne 0 ] ; then - run_trapped_test test_traits data/$1/points \ - data/$1/xcurves data/$1/curves data/$1/compare_y_near_boundary $2 - fi - if [ ${commands_indicator[$PARAMETER_SPACE_X]} -ne 0 ] ; then - run_trapped_test test_traits data/$1/points \ - data/$1/xcurves data/$1/curves data/$1/parameter_space_x $2 - fi - if [ ${commands_indicator[$PARAMETER_SPACE_Y]} -ne 0 ] ; then - run_trapped_test test_traits data/$1/points \ - data/$1/xcurves data/$1/curves data/$1/parameter_space_y $2 - fi - if [ ${commands_indicator[$COMPARE_Y_AT_X]} -ne 0 ] ; then - run_trapped_test test_traits data/$1/points \ - data/$1/xcurves data/$1/curves data/$1/compare_y_at_x $2 - fi - if [ ${commands_indicator[$COMPARE_Y_AT_X_LEFT]} -ne 0 ] ; then - run_trapped_test test_traits data/$1/points \ - data/$1/xcurves data/$1/curves data/$1/compare_y_at_x_left $2 - fi - if [ ${commands_indicator[$COMPARE_Y_AT_X_RIGHT]} -ne 0 ] ; then - run_trapped_test test_traits data/$1/points \ - data/$1/xcurves data/$1/curves data/$1/compare_y_at_x_right $2 - fi - if [ ${commands_indicator[$MAKE_X_MONOTONE]} -ne 0 ] ; then - run_trapped_test test_traits data/$1/points \ - data/$1/xcurves data/$1/curves data/$1/make_x_monotone $2 - fi - if [ ${commands_indicator[$INTERSECT]} -ne 0 ] ; then - run_trapped_test test_traits data/$1/points \ - data/$1/xcurves data/$1/curves data/$1/intersect $2 - fi - if [ ${commands_indicator[$SPLIT]} -ne 0 ] ; then - run_trapped_test test_traits data/$1/points \ - data/$1/xcurves data/$1/curves data/$1/split $2 - fi - if [ ${commands_indicator[$ARE_MERGEABLE]} -ne 0 ] ; then - run_trapped_test test_traits data/$1/points \ - data/$1/xcurves data/$1/curves data/$1/are_mergeable $2 - fi - if [ ${commands_indicator[$MERGE]} -ne 0 ] ; then - run_trapped_test test_traits data/$1/points \ - data/$1/xcurves data/$1/curves data/$1/merge $2 - fi - if [ ${commands_indicator[$ASSERTIONS]} -ne 0 ] ; then - run_trapped_test test_traits data/$1/points \ - data/$1/xcurves data/$1/curves data/$1/assertions $2 - fi - if [ ${commands_indicator[$CONSTRUCTOR]} -ne 0 ] ; then - run_trapped_test test_traits data/$1/points \ - data/$1/xcurves data/$1/curves data/$1/constructor $2 - fi - if [ ${commands_indicator[$EQUAL]} -ne 0 ] ; then - run_trapped_test test_traits data/$1/points \ - data/$1/xcurves data/$1/curves data/$1/equal $2 - fi - if [ ${commands_indicator[$PUSH_BACK]} -ne 0 ] ; then - run_trapped_test test_traits data/$1/points \ - data/$1/xcurves data/$1/curves data/$1/push_back $2 - fi - if [ ${commands_indicator[$PUSH_FRONT]} -ne 0 ] ; then - run_trapped_test test_traits data/$1/points \ - data/$1/xcurves data/$1/curves data/$1/push_front $2 - fi - if [ ${commands_indicator[$NUMBER_OF_POINTS]} -ne 0 ] ; then - run_trapped_test test_traits data/$1/points \ - data/$1/xcurves data/$1/curves data/$1/number_of_points $2 - fi - if [ ${commands_indicator[$COMPARE_ENDPOINTS_XY]} -ne 0 ] ; then - run_trapped_test test_traits data/$1/points \ - data/$1/xcurves data/$1/curves data/$1/compare_endpoints_xy $2 - fi - if [ ${commands_indicator[$CONSTRUCT_OPPOSITE]} -ne 0 ] ; then - run_trapped_test test_traits data/$1/points \ - data/$1/xcurves data/$1/curves data/$1/construct_opposite $2 - fi - if [ ${commands_indicator[$TRIM]} -ne 0 ] ; then - run_trapped_test test_traits data/$1/points \ - data/$1/xcurves data/$1/curves data/$1/trim $2 - fi -} - -execute_commands_traits_adaptor() -{ - -# the new structure is not default, so this function executes only -# commands that are given as arguments - - commands_indicator[PARAMETER_SPACE_X]=0 - commands_indicator[PARAMETER_SPACE_Y]=0 - commands_indicator[COMPARE_X_AT_LIMIT]=0 - commands_indicator[COMPARE_X_NEAR_LIMIT]=0 - commands_indicator[COMPARE_X_ON_BOUNDARY]=0 - commands_indicator[COMPARE_X_NEAR_BOUNDARY]=0 - commands_indicator[COMPARE_Y_NEAR_BOUNDARY]=0 - commands_indicator[COMPARE_Y_AT_X_LEFT]=0 - commands_indicator[ARE_MERGEABLE]=0 - commands_indicator[MERGE]=0 - commands_indicator[X_ON_IDENTIFICATION]=0 - commands_indicator[Y_ON_IDENTIFICATION]=0 - commands_indicator[IS_BOUNDED]=0 - commands_indicator[IS_IN_X_RANGE]=0 - commands_indicator[COMPARE_Y_POSITION]=0 - commands_indicator[IS_BETWEEN_CW]=0 - commands_indicator[COMPARE_CW_AROUND_POINT]=0 - - i=1 - if [ $# -gt 2 ] ; then - for arg in $* ; do - if [ $i -gt 2 ] ; then - commands_indicator[$arg]=1 - fi - let "i+=1" - done - fi - - if [ ${commands_indicator[$PARAMETER_SPACE_X]} -ne 0 ] ; then - run_trapped_test test_traits_adaptor data/test_adaptor/$1/points \ - data/test_adaptor/$1/xcurves data/test_adaptor/$1/curves \ - data/test_adaptor/$1/parameter_space_x $2 - fi - if [ ${commands_indicator[$PARAMETER_SPACE_Y]} -ne 0 ] ; then - run_trapped_test test_traits_adaptor data/test_adaptor/$1/points \ - data/test_adaptor/$1/xcurves data/test_adaptor/$1/curves \ - data/test_adaptor/$1/parameter_space_y $2 - fi - if [ ${commands_indicator[$COMPARE_X_AT_LIMIT]} -ne 0 ] ; then - run_trapped_test test_traits_adaptor data/test_adaptor/$1/points \ - data/test_adaptor/$1/xcurves data/test_adaptor/$1/curves \ - data/test_adaptor/$1/compare_x_at_limit $2 - fi - if [ ${commands_indicator[$COMPARE_X_NEAR_LIMIT]} -ne 0 ] ; then - run_trapped_test test_traits_adaptor data/test_adaptor/$1/points \ - data/test_adaptor/$1/xcurves data/test_adaptor/$1/curves \ - data/test_adaptor/$1/compare_x_near_limit $2 - fi - - if [ ${commands_indicator[$COMPARE_X_ON_BOUNDARY]} -ne 0 ] ; then - run_trapped_test test_traits_adaptor data/test_adaptor/$1/points \ - data/test_adaptor/$1/xcurves data/test_adaptor/$1/curves \ - data/test_adaptor/$1/compare_x_on_boundary $2 - fi - if [ ${commands_indicator[$COMPARE_X_NEAR_BOUNDARY]} -ne 0 ] ; then - run_trapped_test test_traits_adaptor data/test_adaptor/$1/points \ - data/test_adaptor/$1/xcurves data/test_adaptor/$1/curves \ - data/test_adaptor/$1/compare_x_near_boundary $2 - fi - - if [ ${commands_indicator[$COMPARE_Y_NEAR_BOUNDARY]} -ne 0 ] ; then - run_trapped_test test_traits_adaptor data/test_adaptor/$1/points \ - data/test_adaptor/$1/xcurves data/test_adaptor/$1/curves \ - data/test_adaptor/$1/compare_y_near_boundary $2 - fi - if [ ${commands_indicator[$COMPARE_Y_AT_X_LEFT]} -ne 0 ] ; then - run_trapped_test test_traits_adaptor data/test_adaptor/$1/points \ - data/test_adaptor/$1/xcurves data/test_adaptor/$1/curves \ - data/test_adaptor/$1/compare_y_at_x_left $2 - fi - if [ ${commands_indicator[$ARE_MERGEABLE]} -ne 0 ] ; then - run_trapped_test test_traits_adaptor data/test_adaptor/$1/points \ - data/test_adaptor/$1/xcurves data/test_adaptor/$1/curves \ - data/test_adaptor/$1/are_mergeable $2 - fi - if [ ${commands_indicator[$MERGE]} -ne 0 ] ; then - run_trapped_test test_traits_adaptor data/test_adaptor/$1/points \ - data/test_adaptor/$1/xcurves data/test_adaptor/$1/curves \ - data/test_adaptor/$1/merge $2 - fi - if [ ${commands_indicator[X_ON_IDENTIFICATION]} -ne 0 ] ; then - run_trapped_test test_traits_adaptor data/test_adaptor/$1/points \ - data/test_adaptor/$1/xcurves data/test_adaptor/$1/curves \ - data/test_adaptor/$1/x_on_idintification $2 - fi - if [ ${commands_indicator[Y_ON_IDENTIFICATION]} -ne 0 ] ; then - run_trapped_test test_traits_adaptor data/test_adaptor/$1/points \ - data/test_adaptor/$1/xcurves data/test_adaptor/$1/curves \ - data/test_adaptor/$1/x_on_idintification $2 - fi - if [ ${commands_indicator[IS_BOUNDED]} -ne 0 ] ; then - run_trapped_test test_traits_adaptor data/test_adaptor/$1/points \ - data/test_adaptor/$1/xcurves data/test_adaptor/$1/curves \ - data/test_adaptor/$1/is_bounded $2 - fi - if [ ${commands_indicator[IS_IN_X_RANGE]} -ne 0 ] ; then - run_trapped_test test_traits_adaptor data/test_adaptor/$1/points \ - data/test_adaptor/$1/xcurves data/test_adaptor/$1/curves \ - data/test_adaptor/$1/is_in_x_range $2 - fi - if [ ${commands_indicator[COMPARE_Y_POSITION]} -ne 0 ] ; then - run_trapped_test test_traits_adaptor data/test_adaptor/$1/points \ - data/test_adaptor/$1/xcurves data/test_adaptor/$1/curves \ - data/test_adaptor/$1/compare_y_position $2 - fi - if [ ${commands_indicator[IS_BETWEEN_CW]} -ne 0 ] ; then - run_trapped_test test_traits_adaptor data/test_adaptor/$1/points \ - data/test_adaptor/$1/xcurves data/test_adaptor/$1/curves \ - data/test_adaptor/$1/is_between_cw $2 - fi - if [ ${commands_indicator[COMPARE_CW_AROUND_POINT]} -ne 0 ] ; then - run_trapped_test test_traits_adaptor data/test_adaptor/$1/points \ - data/test_adaptor/$1/xcurves data/test_adaptor/$1/curves \ - data/test_adaptor/$1/compare_cw_around_point $2 - fi -} - -#---------------------------------------------------------------------# -# traits adaptor (segments traits) -#---------------------------------------------------------------------# -test_segment_traits_adaptor() -{ - local nt=$QUOTIENT_MP_FLOAT_NT; - local kernel=$CARTESIAN_KERNEL; - local geom_traits=$SEGMENT_GEOM_TRAITS; - local flags="-DTEST_NT=$nt -DTEST_KERNEL=$kernel -DTEST_GEOM_TRAITS=$geom_traits"; - - compile_test_with_flags test_traits_adaptor segments "$flags" - if [ -n "${SUCCESS}" ] ; then - execute_commands_traits_adaptor segments segments_traits_adaptor \ - COMPARE_Y_POSITION COMPARE_CW_AROUND_POINT COMPARE_Y_AT_X_LEFT \ - ARE_MERGEABLE MERGE IS_IN_X_RANGE IS_BETWEEN_CW - else - echo " ERROR: not executed test_traits_adaptor segment_traits" >> $ERRORFILE - fi - clean_tests -} - -#---------------------------------------------------------------------# -# traits adaptor (linear traits) -#---------------------------------------------------------------------# -test_linear_traits_adaptor() -{ - local nt=$QUOTIENT_MP_FLOAT_NT; - local kernel=$CARTESIAN_KERNEL; - local geom_traits=$LINEAR_GEOM_TRAITS; - local flags="-DTEST_NT=$nt -DTEST_KERNEL=$kernel -DTEST_GEOM_TRAITS=$geom_traits"; - - compile_test_with_flags test_traits_adaptor linear "$flags" - if [ -n "${SUCCESS}" ] ; then - execute_commands_traits_adaptor linear linear_traits_adaptor \ - COMPARE_Y_AT_X_LEFT ARE_MERGEABLE MERGE IS_IN_X_RANGE \ - COMPARE_Y_POSITION IS_BETWEEN_CW COMPARE_CW_AROUND_POINT - else - echo " ERROR: not executed test_traits_adaptor linear_traits" >> $ERRORFILE - fi - clean_tests -} - -#---------------------------------------------------------------------# -# traits adaptor (spherical arcs traits) -#---------------------------------------------------------------------# -test_spherical_arcs_traits_adaptor() -{ - local nt=$CGAL_GMPQ_NT; - local kernel=$CARTESIAN_KERNEL; - local geom_traits=$GEODESIC_ARC_ON_SPHERE_GEOM_TRAITS; - local topol_traits=$SPHERICAL_TOPOL_TRAITS - local flags="-DTEST_NT=$nt -DTEST_KERNEL=$kernel -DTEST_GEOM_TRAITS=$geom_traits -DTEST_TOPOL_TRAITS=$topol_traits"; - - compile_test_with_flags test_traits_adaptor geodesic_arcs_on_sphere "$flags" - if [ -n "${SUCCESS}" ] ; then - execute_commands_traits_adaptor spherical_arcs spherical_arcs_traits_adaptor \ - COMPARE_Y_AT_X_LEFT ARE_MERGEABLE MERGE IS_IN_X_RANGE \ - COMPARE_Y_POSITION IS_BETWEEN_CW COMPARE_CW_AROUND_POINT - else - echo " ERROR: not executed test_traits_adaptor spherical_arcs_traits" >> $ERRORFILE - fi - clean_tests -} - -#---------------------------------------------------------------------# -# compile and run test with traits -#---------------------------------------------------------------------# -compile_and_run_with_flags() -{ - local name=$1; - local type=$2; - local flags=$3; - - compile_test_with_flags $name $type "$flags" - if [ -n "${SUCCESS}" ] ; then - if [ -n "$DO_RUN" ] ; then - run_test_with_flags $name $type - fi - else - echo " ERROR: not executed construction of segments" >> $ERRORFILE - fi - clean_tests -} - -#---------------------------------------------------------------------# -# construction with segments -#---------------------------------------------------------------------# -test_construction_segments() -{ - local nt=$CGAL_GMPQ_NT; - local kernel=$CARTESIAN_KERNEL; - local geom_traits=$SEGMENT_GEOM_TRAITS; - local flags="-DTEST_NT=$nt -DTEST_KERNEL=$kernel -DTEST_GEOM_TRAITS=$geom_traits"; - compile_and_run_with_flags test_construction segments "$flags" -} - -#---------------------------------------------------------------------# -# construction with linear curves -#---------------------------------------------------------------------# -test_construction_linear_curves() -{ - local nt=$CGAL_GMPQ_NT; - local kernel=$CARTESIAN_KERNEL; - local geom_traits=$LINEAR_GEOM_TRAITS; - local topol_traits=$PLANAR_UNBOUNDED_TOPOL_TRAITS; - local flags="-DTEST_NT=$nt -DTEST_KERNEL=$kernel -DTEST_GEOM_TRAITS=$geom_traits -DTEST_TOPOL_TRAITS=$topol_traits"; - compile_and_run_with_flags test_construction linear "$flags" -} - -#---------------------------------------------------------------------# -# construction with geodesic arcs on the sphere -#---------------------------------------------------------------------# -test_construction_spherical_arcs() -{ - local nt=$CGAL_GMPQ_NT; - local kernel=$CARTESIAN_KERNEL; - local geom_traits=$GEODESIC_ARC_ON_SPHERE_GEOM_TRAITS; - local topol_traits=$SPHERICAL_TOPOL_TRAITS; - local flags="-DTEST_NT=$nt -DTEST_KERNEL=$kernel -DTEST_GEOM_TRAITS=$geom_traits -DTEST_TOPOL_TRAITS=$topol_traits"; - compile_and_run_with_flags test_construction geodesic_arcs_on_sphere "$flags" -} - -#---------------------------------------------------------------------# -# construction with polylines -#---------------------------------------------------------------------# -test_construction_polylines() -{ - local nt=$CGAL_GMPQ_NT; - local kernel=$CARTESIAN_KERNEL; - local geom_traits=$POLYLINE_GEOM_TRAITS; - local flags="-DTEST_NT=$nt -DTEST_KERNEL=$kernel -DTEST_GEOM_TRAITS=$geom_traits"; - compile_and_run_with_flags test_construction polylines "$flags" -} - -#---------------------------------------------------------------------# -# overlay with segments -#---------------------------------------------------------------------# -test_overlay_segments() -{ - local nt=$CGAL_GMPQ_NT; - local kernel=$CARTESIAN_KERNEL; - local geom_traits=$SEGMENT_GEOM_TRAITS; - local flags="-DTEST_NT=$nt -DTEST_KERNEL=$kernel -DTEST_GEOM_TRAITS=$geom_traits"; - compile_and_run_with_flags test_overlay segments "$flags" -} - -#---------------------------------------------------------------------# -# overlay with geodesic arcs on the sphere -#---------------------------------------------------------------------# -test_overlay_spherical_arcs() -{ - local nt=$CGAL_GMPQ_NT; - local kernel=$CARTESIAN_KERNEL; - local geom_traits=$GEODESIC_ARC_ON_SPHERE_GEOM_TRAITS; - local topol_traits=$SPHERICAL_TOPOL_TRAITS; - local flags="-DTEST_NT=$nt -DTEST_KERNEL=$kernel -DTEST_GEOM_TRAITS=$geom_traits -DTEST_TOPOL_TRAITS=$topol_traits"; - compile_and_run_with_flags test_overlay geodesic_arcs_on_sphere "$flags" -} - -#---------------------------------------------------------------------# -# point location with segments -#---------------------------------------------------------------------# -test_point_location_segments() -{ - local nt=$CGAL_GMPQ_NT; - local kernel=$CARTESIAN_KERNEL; - local geom_traits=$SEGMENT_GEOM_TRAITS; - local flags="-DTEST_NT=$nt -DTEST_KERNEL=$kernel -DTEST_GEOM_TRAITS=$geom_traits"; - compile_and_run_with_flags test_point_location segments "$flags" -} - -# For backward compatibility -test_point_location_segments_version() -{ - local nt=$CGAL_GMPQ_NT; - local kernel=$CARTESIAN_KERNEL; - local geom_traits=$SEGMENT_GEOM_TRAITS; - local flags="-DTEST_NT=$nt -DTEST_KERNEL=$kernel -DTEST_GEOM_TRAITS=$geom_traits -DCGAL_ARR_POINT_LOCATION_VERSION=1"; - compile_and_run_with_flags test_point_location segments "$flags" -} - -# For backward compatibility -test_point_location_segments_conversion() -{ - local nt=$CGAL_GMPQ_NT; - local kernel=$CARTESIAN_KERNEL; - local geom_traits=$SEGMENT_GEOM_TRAITS; - local flags="-DTEST_NT=$nt -DTEST_KERNEL=$kernel -DTEST_GEOM_TRAITS=$geom_traits -DCGAL_ARR_POINT_LOCATION_CONVERSION"; - compile_and_run_with_flags test_point_location segments "$flags" -} - -#---------------------------------------------------------------------# -# point location dynamic with segments -#---------------------------------------------------------------------# -test_point_location_dynamic_segments() -{ - local nt=$CGAL_GMPQ_NT; - local kernel=$CARTESIAN_KERNEL; - local geom_traits=$SEGMENT_GEOM_TRAITS; - local flags="-DTEST_NT=$nt -DTEST_KERNEL=$kernel -DTEST_GEOM_TRAITS=$geom_traits"; - compile_and_run_with_flags test_point_location_dynamic segments "$flags" -} - -#---------------------------------------------------------------------# -# point location with circle segments -#---------------------------------------------------------------------# -test_point_location_circle_segments() -{ - local nt=$CGAL_GMPQ_NT; - local kernel=$CARTESIAN_KERNEL; - local geom_traits=$CIRCLE_SEGMENT_GEOM_TRAITS; - local flags="-DTEST_NT=$nt -DTEST_KERNEL=$kernel -DTEST_GEOM_TRAITS=$geom_traits"; - compile_and_run_with_flags test_point_location circle_segments "$flags" -} - -#---------------------------------------------------------------------# -# point location with linear objects -#---------------------------------------------------------------------# -test_point_location_linear() -{ - local nt=$CGAL_GMPQ_NT; - local kernel=$CARTESIAN_KERNEL; - local geom_traits=$LINEAR_GEOM_TRAITS; - local flags="-DTEST_NT=$nt -DTEST_KERNEL=$kernel -DTEST_GEOM_TRAITS=$geom_traits"; - compile_and_run_with_flags test_point_location linear "$flags" -} - -#---------------------------------------------------------------------# -# batchecd point location with segments -#---------------------------------------------------------------------# -test_batched_point_location_segments() -{ - local nt=$CGAL_GMPQ_NT; - local kernel=$CARTESIAN_KERNEL; - local geom_traits=$SEGMENT_GEOM_TRAITS; - local flags="-DTEST_NT=$nt -DTEST_KERNEL=$kernel -DTEST_GEOM_TRAITS=$geom_traits"; - compile_and_run_with_flags test_batched_point_location segments "$flags" -} - -#---------------------------------------------------------------------# -# batchecd point location with linear objects -#---------------------------------------------------------------------# -test_batched_point_location_linear() -{ - local nt=$CGAL_GMPQ_NT; - local kernel=$CARTESIAN_KERNEL; - local geom_traits=$LINEAR_GEOM_TRAITS; - local flags="-DTEST_NT=$nt -DTEST_KERNEL=$kernel -DTEST_GEOM_TRAITS=$geom_traits"; - compile_and_run_with_flags test_batched_point_location linear "$flags" -} - -#---------------------------------------------------------------------# -# batchecd point location with geodesic arcs on the sphere -#---------------------------------------------------------------------# -test_batched_point_location_spherical_arcs() -{ - local nt=$CGAL_GMPQ_NT; - local kernel=$CARTESIAN_KERNEL; - local geom_traits=$GEODESIC_ARC_ON_SPHERE_GEOM_TRAITS; - local topol_traits=$SPHERICAL_TOPOL_TRAITS; - local flags="-DTEST_NT=$nt -DTEST_KERNEL=$kernel -DTEST_GEOM_TRAITS=$geom_traits -DTEST_TOPOL_TRAITS=$topol_traits"; - compile_and_run_with_flags test_batched_point_location geodesic_arcs_on_sphere "$flags" -} - -#---------------------------------------------------------------------# -# vertical decomposition with segments -#---------------------------------------------------------------------# -test_vertical_decomposition_segments() -{ - local nt=$CGAL_GMPQ_NT; - local kernel=$CARTESIAN_KERNEL; - local geom_traits=$SEGMENT_GEOM_TRAITS; - local flags="-DTEST_NT=$nt -DTEST_KERNEL=$kernel -DTEST_GEOM_TRAITS=$geom_traits"; - compile_and_run_with_flags test_vertical_decomposition segments "$flags" -} - -#---------------------------------------------------------------------# -# vertical decomposition with linear objects -#---------------------------------------------------------------------# -test_vertical_decomposition_linear() -{ - local nt=$CGAL_GMPQ_NT; - local kernel=$CARTESIAN_KERNEL; - local geom_traits=$LINEAR_GEOM_TRAITS; - local flags="-DTEST_NT=$nt -DTEST_KERNEL=$kernel -DTEST_GEOM_TRAITS=$geom_traits"; - compile_and_run_with_flags test_vertical_decomposition linear "$flags" -} - -#---------------------------------------------------------------------# -# vertical decomposition with geodesic arcs on the sphere -#---------------------------------------------------------------------# -test_vertical_decomposition_spherical_arcs() -{ - local nt=$CGAL_GMPQ_NT; - local kernel=$CARTESIAN_KERNEL; - local geom_traits=$LINEAR_GEOM_TRAITS; - local topol_traits=$SPHERICAL_TOPOL_TRAITS; - local flags="-DTEST_NT=$nt -DTEST_KERNEL=$kernel -DTEST_GEOM_TRAITS=$geom_traits -DTEST_TOPOL_TRAITS=$topol_traits"; - compile_and_run_with_flags test_vertical_decomposition geodesic_arcs_on_sphere "$flags" -} - -#---------------------------------------------------------------------# -# segment traits -#---------------------------------------------------------------------# -test_segment_traits() -{ - local nt=$QUOTIENT_MP_FLOAT_NT; - local kernel=$CARTESIAN_KERNEL; - local geom_traits=$SEGMENT_GEOM_TRAITS; - local flags="-DTEST_NT=$nt -DTEST_KERNEL=$kernel -DTEST_GEOM_TRAITS=$geom_traits"; - - compile_test_with_flags test_traits segments "$flags" - if [ -n "${SUCCESS}" ] ; then - execute_commands_old_structure segments segment_traits \ - VERTEX IS_VERTICAL COMPARE_Y_AT_X COMPARE_Y_AT_X_LEFT CONSTRUCTOR \ - COMPARE_Y_AT_X_RIGHT ARE_MERGEABLE - - execute_commands_new_structure segments segment_traits \ - IS_VERTICAL COMPARE_Y_AT_X COMPARE_Y_AT_X_LEFT ARE_MERGEABLE - - run_trapped_test test_traits \ - data/segments/vertex.pt data/segments/xcurves \ - data/empty.zero data/segments/vertex segment_traits - else - echo " ERROR: not executed test_traits segment_traits" >> $ERRORFILE - fi - clean_tests -} - -#---------------------------------------------------------------------# -# non-caching segment traits -#---------------------------------------------------------------------# -test_non_caching_segment_traits() -{ - local nt=$CGAL_GMPQ_NT; - local kernel=$CARTESIAN_KERNEL; - local geom_traits=$NON_CACHING_SEGMENT_GEOM_TRAITS; - local flags="-DTEST_NT=$nt -DTEST_KERNEL=$kernel -DTEST_GEOM_TRAITS=$geom_traits"; - - compile_test_with_flags test_traits non_caching_segments "$flags" - if [ -n "${SUCCESS}" ] ; then - execute_commands_old_structure segments non_caching_segment_traits \ - VERTEX IS_VERTICAL COMPARE_Y_AT_X COMPARE_Y_AT_X_LEFT CONSTRUCTOR \ - COMPARE_Y_AT_X_RIGHT ARE_MERGEABLE ASSERTIONS - - execute_commands_new_structure segments segment_traits \ - IS_VERTICAL COMPARE_Y_AT_X COMPARE_Y_AT_X_LEFT - - run_trapped_test test_traits \ - data/segments/vertex.pt data/segments/xcurves \ - data/empty.zero data/segments/vertex non_caching_segment_traits - else - echo " ERROR: not executed test_traits non_caching_segment_traits" >> $ERRORFILE - fi - clean_tests -} - -#---------------------------------------------------------------------# -# polycurve conic traits -#---------------------------------------------------------------------# -test_polycurve_conic_traits() -{ - if [ -n "${CGAL_DISABLE_GMP}" ]; then - echo "CORE is not available, test_polycurve_conic_traits not ran" - return - fi - echo polycurve test starting - local nt=$CORE_EXPR_NT; - local kernel=$CARTESIAN_KERNEL; - local geom_traits=$POLYCURVE_CONIC_GEOM_TRAITS; - local flags="-DTEST_NT=$nt -DTEST_KERNEL=$kernel -DTEST_GEOM_TRAITS=$geom_traits"; - - compile_test_with_flags test_traits conic_polycurve "$flags" - if [ -n "${SUCCESS}" ] ; then - - # The input arguments for the execute_commands_new_structure, - # 1. Polycurve_conics is the directory name in "data" - # 2. polycurve_conic_traits is a string - # Execute_command_new_structure will only run the test on functors provided as the third, fourth and so on arguments. - # To see how the input data directory should be structured for each functor, check the execute_commands_new_structure function in this file. - execute_commands_new_structure polycurves_conics polycurve_conic_traits \ - COMPARE_Y_AT_X \ - INTERSECT \ - EQUAL \ - IS_VERTICAL \ - SPLIT \ - ARE_MERGEABLE \ - COMPARE_Y_AT_X_LEFT \ - COMPARE_Y_AT_X_RIGHT \ - MAKE_X_MONOTONE \ - PUSH_BACK \ - PUSH_FRONT \ - NUMBER_OF_POINTS \ - VERTEX \ - CONSTRUCT_OPPOSITE \ - MERGE \ - COMPARE_ENDPOINTS_XY \ - TRIM - - else - echo " ERROR: not executed test_traits polyline_traits" >> $ERRORFILE - fi - clean_tests -} - -#---------------------------------------------------------------------# -# polycurve arc traits -#---------------------------------------------------------------------# -test_polycurve_circular_arc_traits() -{ - local nt=$QUOTIENT_MP_FLOAT_NT; - local kernel=$CARTESIAN_KERNEL; - local geom_traits=$POLYCURVE_CIRCULAR_ARC_GEOM_TRAITS; - local flags="-DTEST_NT=$nt -DTEST_KERNEL=$kernel -DTEST_GEOM_TRAITS=$geom_traits"; - - compile_test_with_flags test_traits circular_arc_polycurve "$flags" - if [ -n "${SUCCESS}" ] ; then - execute_commands_new_structure Polycurves_circular_arcs polycurve_circular_arc_traits \ - COMPARE_Y_AT_X \ - EQUAL \ - IS_VERTICAL \ - SPLIT \ - ARE_MERGEABLE \ - COMPARE_Y_AT_X_LEFT \ - COMPARE_Y_AT_X_RIGHT \ - MAKE_X_MONOTONE \ - PUSH_BACK \ - PUSH_FRONT \ - NUMBER_OF_POINTS \ - VERTEX \ - CONSTRUCT_OPPOSITE \ - MERGE \ - COMPARE_ENDPOINTS_XY \ - INTERSECT - - else - echo " ERROR: not executed test_traits polyline_traits" >> $ERRORFILE - fi - clean_tests -} - -#---------------------------------------------------------------------# -# polycurve bezier traits -#---------------------------------------------------------------------# -test_polycurve_bezier_traits() -{ - if [ -n "${CGAL_DISABLE_GMP}" ]; then - echo "CORE is not available, test_polycurve_bezier_traits not ran" - return - fi - local nt=$CORE_EXPR_NT; - local kernel=$CARTESIAN_KERNEL; - local geom_traits=$POLYCURVE_BEZIER_GEOM_TRAITS; - local flags="-DTEST_NT=$nt -DTEST_KERNEL=$kernel -DTEST_GEOM_TRAITS=$geom_traits"; - - compile_test_with_flags test_traits bezier_polycurve "$flags" - if [ -n "${SUCCESS}" ] ; then - execute_commands_new_structure Polycurves_bezier test_polycurve_bezier_traits \ - MERGE \ - EQUAL \ - IS_VERTICAL \ - NUMBER_OF_POINTS \ - PUSH_BACK \ - PUSH_FRONT \ - VERTEX \ - ARE_MERGEABLE \ - COMPARE_ENDPOINTS_XY - # TODO (add data for these tests) - # COMPARE_Y_AT_X \ - # SPLIT \ - # COMPARE_Y_AT_X_LEFT \ - # COMPARE_Y_AT_X_RIGHT \ - # MAKE_X_MONOTONE \ - # CONSTRUCT_OPPOSITE \ - - # INTERSECT - - else - echo " ERROR: not executed test_traits polyline_traits" >> $ERRORFILE - fi - clean_tests -} - -#---------------------------------------------------------------------# -# polyline traits -#---------------------------------------------------------------------# -test_polyline_traits() -{ - local nt=$CGAL_GMPQ_NT; - local kernel=$CARTESIAN_KERNEL; - local geom_traits=$POLYLINE_GEOM_TRAITS; - local flags="-DTEST_NT=$nt -DTEST_KERNEL=$kernel -DTEST_GEOM_TRAITS=$geom_traits"; - - compile_test_with_flags test_traits test_polylines "$flags" - if [ -n "${SUCCESS}" ] ; then - execute_commands_old_structure polylines polyline_traits \ - CONSTRUCTOR COMPARE_Y_AT_X_LEFT \ - COMPARE_Y_AT_X_RIGHT ARE_MERGEABLE - else - echo " ERROR: not executed test_traits polyline_traits" >> $ERRORFILE - fi - clean_tests -} - -#---------------------------------------------------------------------# -# non-caching polyline traits -#---------------------------------------------------------------------# -test_non_caching_polyline_traits() -{ - local nt=$CGAL_GMPQ_NT; - local kernel=$CARTESIAN_KERNEL; - local geom_traits=$NON_CACHING_POLYLINE_GEOM_TRAITS; - local flags="-DTEST_NT=$nt -DTEST_KERNEL=$kernel -DTEST_GEOM_TRAITS=$geom_traits"; - - compile_test_with_flags test_traits non_caching_polylines "$flags" - if [ -n "${SUCCESS}" ] ; then - execute_commands_old_structure polylines non_caching_polyline_traits \ - CONSTRUCTOR COMPARE_Y_AT_X_LEFT \ - COMPARE_Y_AT_X_RIGHT ARE_MERGEABLE - else - echo " ERROR: not executed test_traits non_caching_polyline_traits" >> $ERRORFILE - fi - clean_tests -} - -#---------------------------------------------------------------------# -# linear traits -#---------------------------------------------------------------------# -test_linear_traits() -{ - local nt=$QUOTIENT_MP_FLOAT_NT; - local kernel=$CARTESIAN_KERNEL; - local geom_traits=$LINEAR_GEOM_TRAITS; - local flags="-DTEST_NT=$nt -DTEST_KERNEL=$kernel -DTEST_GEOM_TRAITS=$geom_traits"; - - compile_test_with_flags test_traits linear "$flags" - if [ -n "${SUCCESS}" ] ; then - execute_commands_old_structure linear/segments linear_traits.segments \ - VERTEX IS_VERTICAL COMPARE_Y_AT_X COMPARE_Y_AT_X_LEFT \ - COMPARE_Y_AT_X_RIGHT CONSTRUCTOR ARE_MERGEABLE - - execute_commands_new_structure linear/segments linear_traits.segments \ - IS_VERTICAL COMPARE_Y_AT_X COMPARE_Y_AT_X_LEFT - - run_trapped_test test_traits \ - data/linear/segments/vertex.pt data/linear/segments/xcurves \ - data/empty.zero data/linear/segments/vertex linear_traits.segments - - execute_commands_old_structure linear/rays linear_traits.rays \ - VERTEX IS_VERTICAL COMPARE_Y_AT_X COMPARE_Y_AT_X_LEFT \ - COMPARE_Y_AT_X_RIGHT CONSTRUCTOR ARE_MERGEABLE - - execute_commands_new_structure linear/rays linear_traits.rays \ - IS_VERTICAL COMPARE_Y_AT_X COMPARE_Y_AT_X_LEFT - - run_trapped_test test_traits \ - data/linear/rays/vertex.pt data/linear/rays/xcurves \ - data/empty.zero data/linear/rays/vertex linear_traits.rays - - execute_commands_new_structure linear/lines linear_traits.lines \ - IS_VERTICAL COMPARE_Y_AT_X COMPARE_Y_AT_X_LEFT INTERSECT \ - SPLIT MERGE \ - PARAMETER_SPACE_X PARAMETER_SPACE_Y \ - COMPARE_X_AT_LIMIT COMPARE_X_NEAR_LIMIT COMPARE_Y_NEAR_BOUNDARY - else - echo " ERROR: not executed test_traits linear_traits" >> $ERRORFILE - fi - clean_tests -} - -#---------------------------------------------------------------------# -# conic traits -#---------------------------------------------------------------------# -test_conic_traits() -{ - if [ -n "${CGAL_DISABLE_GMP}" ]; then - echo "CORE is not available, test_conic_traits not ran" - return - fi - local nt=$CORE_EXPR_NT; - local kernel=$CARTESIAN_KERNEL; - local geom_traits=$CORE_CONIC_GEOM_TRAITS; - local flags="-DTEST_NT=$nt -DTEST_KERNEL=$kernel -DTEST_GEOM_TRAITS=$geom_traits"; - - compile_test_with_flags test_traits conics "$flags" - if [ -n "${SUCCESS}" ] ; then - execute_commands_old_structure conics conic_traits \ - INTERSECT SPLIT MERGE COMPARE_Y_AT_X_LEFT \ - COMPARE_Y_AT_X_RIGHT ARE_MERGEABLE - - execute_commands_new_structure conics conic_traits \ - INTERSECT SPLIT MERGE - - run_trapped_test test_traits \ - data/conics/compare.pt data/empty.zero \ - data/empty.zero data/conics/compare conic_traits - else - echo " ERROR: not executed test_traits conic_traits" >> $ERRORFILE - fi - clean_tests -} - -#---------------------------------------------------------------------# -# "line arcs" (segments) only -#---------------------------------------------------------------------# -test_line_arc_traits() -{ - local nt=$QUOTIENT_MP_FLOAT_NT; - local kernel=$CARTESIAN_KERNEL; - local geom_traits=$LINE_ARC_GEOM_TRAITS; - local flags="-DTEST_NT=$nt -DTEST_KERNEL=$kernel -DTEST_GEOM_TRAITS=$geom_traits"; - - compile_test_with_flags test_traits line_arcs "$flags" - if [ -n "${SUCCESS}" ] ; then - execute_commands_old_structure circular_lines line_arc_traits \ - VERTEX IS_VERTICAL COMPARE_Y_AT_X COMPARE_Y_AT_X_LEFT \ - ASSERTIONS COMPARE_Y_AT_X_RIGHT MERGE ARE_MERGEABLE - - execute_commands_new_structure circular_lines line_arc_traits \ - IS_VERTICAL COMPARE_Y_AT_X - - run_trapped_test test_traits \ - data/circular_lines/compare.pt data/empty.zero \ - data/empty.zero data/circular_lines/compare line_arc_traits - - run_trapped_test test_traits \ - data/circular_lines/vertex.pt data/circular_lines/xcurves \ - data/empty.zero data/circular_lines/vertex line_arc_traits - else - echo " ERROR: not executed test_traits" >> $ERRORFILE - fi - clean_tests -} - -#---------------------------------------------------------------------# -# circular arcs only -#---------------------------------------------------------------------# -test_circular_arc_traits() -{ - local nt=$QUOTIENT_MP_FLOAT_NT; - local kernel=$CARTESIAN_KERNEL; - local geom_traits=$CIRCULAR_ARC_GEOM_TRAITS; - local flags="-DTEST_NT=$nt -DTEST_KERNEL=$kernel -DTEST_GEOM_TRAITS=$geom_traits"; - - compile_test_with_flags test_traits circular_arcs "$flags" - if [ -n "${SUCCESS}" ] ; then - execute_commands_old_structure circular_arcs circular_arc_traits \ - VERTEX IS_VERTICAL COMPARE_Y_AT_X COMPARE_Y_AT_X_LEFT \ - ASSERTIONS COMPARE_Y_AT_X_RIGHT MERGE ARE_MERGEABLE - - execute_commands_new_structure circular_arcs circular_arc_traits \ - VERTEX IS_VERTICAL COMPARE_Y_AT_X - else - echo " ERROR: not executed test_traits" >> $ERRORFILE - fi - clean_tests -} - -#---------------------------------------------------------------------# -# circular and line arcs -#---------------------------------------------------------------------# -test_circular_line_arc_traits() -{ - local nt=$QUOTIENT_MP_FLOAT_NT; - local kernel=$CARTESIAN_KERNEL; - local geom_traits=$CIRCULAR_LINE_ARC_GEOM_TRAITS; - local flags="-DTEST_NT=$nt -DTEST_KERNEL=$kernel -DTEST_GEOM_TRAITS=$geom_traits"; - - compile_test_with_flags test_traits circular_line_arcs "$flags" - if [ -n "${SUCCESS}" ] ; then - execute_commands_old_structure circular_line_arcs circular_line_arc_traits \ - VERTEX IS_VERTICAL CONSTRUCTOR COMPARE_Y_AT_X COMPARE_Y_AT_X_LEFT \ - ASSERTIONS COMPARE_Y_AT_X_RIGHT MERGE ARE_MERGEABLE - - execute_commands_new_structure circular_line_arcs circular_line_arc_traits \ - IS_VERTICAL COMPARE_Y_AT_X - - run_trapped_test test_traits \ - data/circular_line_arcs/vertex.pt data/circular_line_arcs/xcurves \ - data/empty.zero data/circular_line_arcs/vertex circular_line_arc_traits - else - echo " ERROR: not executed test_traits circular_line_arc_traits" >> $ERRORFILE - fi - clean_tests -} - -#---------------------------------------------------------------------# -# circle segment traits -#---------------------------------------------------------------------# -test_circle_segments_traits() -{ - local nt=$QUOTIENT_MP_FLOAT_NT; - local kernel=$CARTESIAN_KERNEL; - local geom_traits=$CIRCLE_SEGMENT_GEOM_TRAITS; - local flags="-DTEST_NT=$nt -DTEST_KERNEL=$kernel -DTEST_GEOM_TRAITS=$geom_traits"; - - compile_test_with_flags test_traits circle_segments "$flags" - if [ -n "${SUCCESS}" ] ; then - execute_commands_old_structure circle_segments circle_segments_traits \ - VERTEX IS_VERTICAL COMPARE_Y_AT_X COMPARE_Y_AT_X_LEFT \ - COMPARE_Y_AT_X_RIGHT CONSTRUCTOR ARE_MERGEABLE - - run_trapped_test test_traits \ - data/circle_segments/points data/circle_segments/xcurves.8 \ - data/empty.zero data/circle_segments/vertex circle_segments_traits - run_trapped_test test_traits \ - data/empty.zero data/circle_segments/xcurves.8 \ - data/empty.zero data/circle_segments/is_vertical circle_segments_traits - run_trapped_test test_traits \ - data/circle_segments/points data/circle_segments/xcurves.8 \ - data/empty.zero data/circle_segments/compare_y_at_x circle_segments_traits - run_trapped_test test_traits \ - data/circle_segments/points data/circle_segments/xcurves.16 \ - data/empty.zero data/circle_segments/compare_y_at_x_left circle_segments_traits - run_trapped_test test_traits \ - data/circle_segments/points data/circle_segments/xcurves.16 \ - data/empty.zero data/circle_segments/compare_y_at_x_right circle_segments_traits - run_trapped_test test_traits \ - data/empty.zero data/circle_segments/constructor.xcv \ - data/empty.zero data/circle_segments/constructor circle_segments_traits - else - echo " ERROR: not executed test_traits circle_segments_traits" >> $ERRORFILE - fi - clean_tests -} - -#---------------------------------------------------------------------# -# bezier traits -#---------------------------------------------------------------------# -test_bezier_traits() -{ - if [ -n "${CGAL_DISABLE_GMP}" ]; then - echo "CORE is not available, test_bezier_traits not ran" - return - fi - local nt=$CORE_EXPR_NT; - local kernel=$CARTESIAN_KERNEL; - local geom_traits=$BEZIER_GEOM_TRAITS; - local flags="-DTEST_NT=$nt -DTEST_KERNEL=$kernel -DTEST_GEOM_TRAITS=$geom_traits"; - - compile_test_with_flags test_traits Bezier "$flags" - if [ -n "${SUCCESS}" ] ; then - execute_commands_old_structure bezier bezier_traits \ - COMPARE_Y_AT_X_LEFT COMPARE_Y_AT_X_RIGHT SPLIT \ - CONSTRUCTOR ASSERTIONS ARE_MERGEABLE - else - echo " ERROR: not executed test_traits bezier_traits" >> $ERRORFILE - fi - clean_tests -} - -#---------------------------------------------------------------------# -# spherical arc traits -#---------------------------------------------------------------------# -test_spherical_arc_traits() -{ - local nt=$CGAL_GMPQ_NT; - local kernel=$CARTESIAN_KERNEL; - local geom_traits=$GEODESIC_ARC_ON_SPHERE_GEOM_TRAITS; - local topol_traits=$SPHERICAL_TOPOL_TRAITS - local flags="-DTEST_NT=$nt -DTEST_KERNEL=$kernel -DTEST_GEOM_TRAITS=$geom_traits -DTEST_TOPOL_TRAITS=$topol_traits"; - - compile_test_with_flags test_traits geodesic_arcs_on_sphere "$flags" - if [ -n "${SUCCESS}" ] ; then - execute_commands_old_structure spherical_arcs spherical_arc_traits \ - COMPARE_Y_AT_X_LEFT COMPARE_Y_AT_X_RIGHT INTERSECT \ - CONSTRUCTOR \ - COMPARE MAKE_X_MONOTONE SPLIT MERGE ASSERTIONS ARE_MERGEABLE - - execute_commands_new_structure spherical_arcs spherical_arc_traits \ - INTERSECT \ - COMPARE_X_ON_BOUNDARY COMPARE_X_NEAR_BOUNDARY \ - COMPARE_Y_NEAR_BOUNDARY - - run_trapped_test test_traits \ - data/spherical_arcs/compare.pt data/spherical_arcs/compare.xcv \ - data/empty.zero data/spherical_arcs/compare spherical_arc_traits - else - echo " ERROR: not executed test_traits spherical_arc_traits" >> $ERRORFILE - fi - clean_tests -} - -#---------------------------------------------------------------------# -# rational arc traits -#---------------------------------------------------------------------# -test_rational_arc_traits() -{ - if [ -n "${CGAL_DISABLE_GMP}" ]; then - echo "CORE is not available, test_rational_arc_traits not ran" - return - fi - local nt=$CORE_INT_NT; - local kernel=$UNIVARIATE_ALGEBRAIC_KERNEL; - local geom_traits=$RATIONAL_ARC_GEOM_TRAITS; - local flags="-DTEST_NT=$nt -DTEST_KERNEL=$kernel -DTEST_GEOM_TRAITS=$geom_traits"; - - compile_test_with_flags test_traits rational_arcs "$flags" - if [ -n "${SUCCESS}" ] ; then - run_trapped_test test_traits \ - data/compare.pt data/empty.zero \ - data/empty.zero data/compare rational_arc_traits - - execute_commands_new_structure rational_arcs rational_arc_traits \ - VERTEX IS_VERTICAL COMPARE_Y_AT_X COMPARE_Y_AT_X_LEFT SPLIT MERGE \ - COMPARE_X_AT_LIMIT COMPARE_X_NEAR_LIMIT COMPARE_Y_NEAR_BOUNDARY - else - echo " ERROR: not executed test_traits rational_arc_traits" >> $ERRORFILE - fi - clean_tests -} - -#---------------------------------------------------------------------# -# algebraic traits with GMP/MPFI -#---------------------------------------------------------------------# -test_algebraic_traits_gmp() -{ - #TODO: Adapt - - local nt=$CGAL_GMPZ_NT; - local kernel=$UNIVARIATE_ALGEBRAIC_KERNEL; - local geom_traits=$ALGEBRAIC_GEOM_TRAITS; - local flags="-DTEST_NT=$nt -DTEST_KERNEL=$kernel -DTEST_GEOM_TRAITS=$geom_traits"; - - compile_test_with_flags test_traits algebraic "$flags" - if [ -n "${SUCCESS}" ] ; then - execute_commands_new_structure algebraic algebraic_traits_gmp \ - COMPARE COMPARE_Y_AT_X COMPARE_Y_AT_X_RIGHT COMPARE_Y_AT_X_LEFT \ - MAKE_X_MONOTONE IS_VERTICAL VERTEX SPLIT MERGE INTERSECT \ - PARAMETER_SPACE_X PARAMETER_SPACE_Y - else - echo " ERROR: not executed test_traits algebraic_traits_gmp" >> $ERRORFILE - fi - clean_tests -} - -#---------------------------------------------------------------------# -# algebraic traits with LEDA -#---------------------------------------------------------------------# -test_algebraic_traits_leda() -{ - #TODO: Adapt - - local nt=$LEDA_INT_NT; - local kernel=$UNIVARIATE_ALGEBRAIC_KERNEL; - local geom_traits=$ALGEBRAIC_GEOM_TRAITS; - local flags="-DTEST_NT=$nt -DTEST_KERNEL=$kernel -DTEST_GEOM_TRAITS=$geom_traits"; - - compile_test_with_flags test_traits algebraic "$flags" - if [ -n "${SUCCESS}" ] ; then - execute_commands_new_structure algebraic algebraic_traits_leda \ - COMPARE COMPARE_Y_AT_X COMPARE_Y_AT_X_RIGHT COMPARE_Y_AT_X_LEFT \ - MAKE_X_MONOTONE IS_VERTICAL VERTEX SPLIT MERGE INTERSECT \ - PARAMETER_SPACE_X PARAMETER_SPACE_Y - else - echo " ERROR: not executed test_traits algebraic_traits_leda" >> $ERRORFILE - fi - clean_tests -} - - -#---------------------------------------------------------------------# -# algebraic traits with CORE -#---------------------------------------------------------------------# -test_algebraic_traits_core() -{ - #TODO: Adapt - if [ -n "${CGAL_DISABLE_GMP}" ]; then - echo "CORE is not available, test_algebraic_traits_core not ran" - return - fi - local nt=$CORE_INT_NT; - local kernel=$UNIVARIATE_ALGEBRAIC_KERNEL; - local geom_traits=$ALGEBRAIC_GEOM_TRAITS; - local flags="-DTEST_NT=$nt -DTEST_KERNEL=$kernel -DTEST_GEOM_TRAITS=$geom_traits"; - - compile_test_with_flags test_traits algebraic "$flags" - if [ -n "${SUCCESS}" ] ; then - execute_commands_new_structure algebraic algebraic_traits_core \ - COMPARE COMPARE_Y_AT_X COMPARE_Y_AT_X_RIGHT COMPARE_Y_AT_X_LEFT \ - MAKE_X_MONOTONE IS_VERTICAL VERTEX SPLIT MERGE INTERSECT \ - PARAMETER_SPACE_X PARAMETER_SPACE_Y - else - echo " ERROR: not executed test_traits algebraic_traits_core" >> $ERRORFILE - fi - clean_tests -} - -#---------------------------------------------------------------------# -# remove the previous error file -#---------------------------------------------------------------------# - -rm -f $ERRORFILE -rm -f $FULL_ERROR_DESCRIPTION_FILE -rm -f ProgramOutput.test_* -touch $ERRORFILE - -#---------------------------------------------------------------------# -# compile and run the tests -#---------------------------------------------------------------------# - - - -if [ $# -ne 0 ] ; then - case $1 in - -cmake) TEST_WITH_CMAKE="TRUE" ;; - *)TEST_WITH_CMAKE="FALSE" ;; - esac -else - TEST_WITH_CMAKE="FALSE" -fi - -echo "Run all tests." - -if [ "${TEST_WITH_CMAKE}" != "FALSE" ]; then - configure -fi - -if [ "${TEST_WITH_CMAKE}" != "FALSE" ]; then - compile_and_run construction_test_suite_generator -fi - -test_segment_traits -test_non_caching_segment_traits -test_polyline_traits -test_polycurve_conic_traits -test_polycurve_circular_arc_traits -test_polycurve_bezier_traits -test_non_caching_polyline_traits -test_linear_traits -test_conic_traits - -test_line_arc_traits # "line arcs" (segments) only -test_circular_arc_traits # circular arcs only -test_circular_line_arc_traits # for both - -test_circle_segments_traits -test_bezier_traits - -test_spherical_arc_traits - -test_rational_arc_traits - -test_algebraic_traits_core -test_algebraic_traits_gmp -test_algebraic_traits_leda - -compile_and_run test_data_traits - -compile_and_run test_insertion -compile_and_run test_unbounded_rational_insertion -compile_and_run test_unbounded_rational_direct_insertion -compile_and_run test_rational_function_traits_2 -compile_and_run test_iso_verts - -compile_and_run test_vert_ray_shoot_vert_segments - -test_construction_segments -test_construction_linear_curves -test_construction_spherical_arcs -test_construction_polylines - -test_overlay_segments -test_overlay_spherical_arcs - -test_point_location_segments -test_point_location_segments_version -test_point_location_segments_conversion -test_point_location_circle_segments -test_point_location_linear - -test_point_location_dynamic_segments - -test_batched_point_location_segments -test_batched_point_location_linear -test_batched_point_location_spherical_arcs - -test_vertical_decomposition_segments -test_vertical_decomposition_linear -# test_vertical_decomposition_spherical_arcs - -compile_and_run test_dual -compile_and_run test_do_intersect -compile_and_run test_zone - -compile_and_run test_observer -compile_and_run test_do_equal - -test_segment_traits_adaptor -test_linear_traits_adaptor -test_spherical_arcs_traits_adaptor - -compile_and_run test_removal -compile_and_run test_unbounded_removal -compile_and_run test_spherical_removal - -compile_and_run test_io - -compile_and_run test_sgm - -# if any error occured then append the full error description file to error file - -if [ -f $FULL_ERROR_DESCRIPTION_FILE ] ; then - echo "******************** appending all error outputs ********************" >> $ERRORFILE - cat $FULL_ERROR_DESCRIPTION_FILE >> $ERRORFILE -fi diff --git a/Arrangement_on_surface_2/test/Arrangement_on_surface_2/cgal_test_with_cmake b/Arrangement_on_surface_2/test/Arrangement_on_surface_2/cgal_test_with_cmake index 8c72a52e332..0bc56422a00 100755 --- a/Arrangement_on_surface_2/test/Arrangement_on_surface_2/cgal_test_with_cmake +++ b/Arrangement_on_surface_2/test/Arrangement_on_surface_2/cgal_test_with_cmake @@ -1,4 +1,1761 @@ #! /bin/bash -./cgal_test_base -cmake +# This is a script for the CGAL test suite. Such a script must obey +# the following rules: +# +# - the name of the script is cgal_test +# - for every target two one line messages are written to the file 'error.txt' +# the first one indicates if the compilation was successful +# the second one indicates if the execution was successful +# if one of the two was not successful, the line should start with 'ERROR:' +# - running the script should not require any user interaction +# - the script should clean up object files and executables +# SET PARAMETERS FOR cgal_test + + +ERRORFILE=error.txt +DO_RUN=y +if [ -z "${MAKE_CMD}" ]; then + MAKE_CMD=make +fi + + +FULL_ERROR_DESCRIPTION_FILE=ProgramOutput.error.txt + +#---------------------------------------------------------------------# +# compile_and_run +#---------------------------------------------------------------------# + +# note that these values shloud match to the values in test_configuration.h file + +CARTESIAN_KERNEL=0 +SIMPLE_CARTESIAN_KERNEL=1 +UNIVARIATE_ALGEBRAIC_KERNEL=2 + +SEGMENT_GEOM_TRAITS=0 +NON_CACHING_SEGMENT_GEOM_TRAITS=1 +POLYLINE_GEOM_TRAITS=2 +NON_CACHING_POLYLINE_GEOM_TRAITS=3 +POLYCURVE_CONIC_GEOM_TRAITS=14 +POLYCURVE_CIRCULAR_ARC_GEOM_TRAITS=15 +POLYCURVE_BEZIER_GEOM_TRAITS=16 +LINEAR_GEOM_TRAITS=4 +CORE_CONIC_GEOM_TRAITS=5 +LINE_ARC_GEOM_TRAITS=6 +CIRCULAR_ARC_GEOM_TRAITS=7 +CIRCULAR_LINE_ARC_GEOM_TRAITS=8 +CIRCLE_SEGMENT_GEOM_TRAITS=9 +BEZIER_GEOM_TRAITS=10 +GEODESIC_ARC_ON_SPHERE_GEOM_TRAITS=11 +RATIONAL_ARC_GEOM_TRAITS=12 +ALGEBRAIC_GEOM_TRAITS=13 +POLYCURVE_CONIC_GEOM_TRAITS=14 +POLYCURVE_CIRCULAR_ARC_GEOM_TRAITS=15 +POLYCURVE_BEZIER_GEOM_TRAITS=16 +FLAT_TORUS_GEOM_TRAITS=17 + +PLANAR_BOUNDED_TOPOL_TRAITS=0 +PLANAR_UNBOUNDED_TOPOL_TRAITS=1 +SPHERICAL_TOPOL_TRAITS=2 + +DOUBLE_NT=0 +MP_FLOAT_NT=1 +GMPZ_NT=2 +LEDA_RAT_NT=3 +QUOTIENT_MP_FLOAT_NT=4 +QUOTIENT_CGAL_GMPZ_NT=5 +CGAL_GMPQ_NT=6 +LAZY_LEDA_RAT_NT=7 +LAZY_CGAL_GMPQ_NT=8 +LAZY_QUOTIENT_MP_FLOAT_NT=9 +LEDA_REAL_NT=10 +CORE_EXPR_NT=11 +LAZY_GMPZ_NT=12 +LEDA_INT_NT=13 +CGAL_GMPZ_NT=14 +CORE_INT_NT=15 +CORE_RAT_NT=16 + +if [ -n "${CGAL_DISABLE_GMP}" ]; then + echo GMP is disable. Try to use LEDA instead. + GMPZ_NT=$LEDA_INT_NT + QUOTIENT_CGAL_GMPZ_NT=$LEDA_RAT_NT + CGAL_GMPQ_NT=$LEDA_RAT_NT + LAZY_CGAL_GMPQ_NT=$LAZY_LEDA_RAT_NT + LAZY_GMPZ_NT=$LAZY_LEDA_RAT_NT + CGAL_GMPZ_NT=$LEDA_INT_NT +fi + +COMPARE=1 +VERTEX=2 +IS_VERTICAL=3 +COMPARE_Y_AT_X=4 +COMPARE_Y_AT_X_LEFT=5 +COMPARE_Y_AT_X_RIGHT=6 +MAKE_X_MONOTONE=7 +INTERSECT=8 +SPLIT=9 +ARE_MERGEABLE=10 +MERGE=11 +ASSERTIONS=12 +CONSTRUCTOR=13 +COMPARE_X_AT_LIMIT=14 +COMPARE_X_NEAR_LIMIT=15 +COMPARE_X_ON_BOUNDARY=16 +COMPARE_X_NEAR_BOUNDARY=17 +COMPARE_Y_NEAR_BOUNDARY=18 +PARAMETER_SPACE_X=19 +PARAMETER_SPACE_Y=20 +X_ON_IDENTIFICATION=21 +Y_ON_IDENTIFICATION=22 +IS_BOUNDED=23 +IS_IN_X_RANGE=24 +COMPARE_Y_POSITION=25 +IS_BETWEEN_CW=26 +COMPARE_CW_AROUND_POINT=27 +PUSH_BACK=28 +PUSH_FRONT=29 +NUMBER_OF_POINTS=32 +COMPARE_ENDPOINTS_XY=33 +CONSTRUCT_OPPOSITE=34 +TRIM=35 + +#---------------------------------------------------------------------# +# configure +#---------------------------------------------------------------------# + +configure() +{ + echo "Configuring... " + rm -rf CMakeCache.txt CMakeFiles/ + echo "cmake --no-warn-unused-cli -C"$INIT_FILE" "$CMAKE_GENERATOR" -DRUNNING_CGAL_AUTO_TEST=TRUE \ + -DCGAL_DIR=\"$CGAL_DIR\" \ + -DINIT_FILE=\"$INIT_FILE" \ + -DCGAL_CXX_FLAGS:STRING=\"$TESTSUITE_CXXFLAGS -I../../include\" \ + -DCGAL_EXE_LINKER_FLAGS=\"$TESTSUITE_LDFLAGS\" \ + -DCMAKE_BUILD_TYPE=NOTFOUND \ + ." + if eval 'cmake --no-warn-unused-cli -C"$INIT_FILE" "$CMAKE_GENERATOR" -DRUNNING_CGAL_AUTO_TEST=TRUE \ + -DCGAL_DIR="$CGAL_DIR" \ + -DINIT_FILE="$INIT_FILE" \ + -DCGAL_CXX_FLAGS:STRING="$TESTSUITE_CXXFLAGS -I../../include" \ + -DCGAL_EXE_LINKER_FLAGS="$TESTSUITE_LDFLAGS" \ + -DCMAKE_BUILD_TYPE=NOTFOUND \ + .' ; then + + echo " successful configuration" >> $ERRORFILE + else + echo " ERROR: configuration" >> $ERRORFILE + fi +} + +compile_test_with_flags() +{ + local name=$1; + local type=$2; + local flags=$3; + + echo "Compiling $name $type ... " + if [ "${TEST_WITH_CMAKE}" != "FALSE" ]; then + export TESTSUITE_CXXFLAGS="$flags" + configure + if eval '${MAKE_CMD} VERBOSE=1 -fMakefile \ + $name' ; then + echo " successful compilation of $name $type" >> $ERRORFILE; + SUCCESS="y" + else + echo " ERROR: compilation of $name $type" >> $ERRORFILE; + SUCCESS="" + fi + else + if eval 'make CGAL_MAKEFILE=$CGAL_MAKEFILE \ + TESTSUITE_CXXFLAGS="$TESTSUITE_CXXFLAGS" \ + TESTSUITE_LDFLAGS="$TESTSUITE_LDFLAGS" $name'; then + echo " successful compilation of $name $type" >> $ERRORFILE; + SUCCESS="y" + else + echo " ERROR: compilation of $name $type" >> $ERRORFILE; + SUCCESS="" + fi + fi +} + +run_test() +{ + # $1 - executable name + + basedata=`basename "$5"` + OUTPUTFILE="ProgramOutput.$1" + rm -f $OUTPUTFILE + COMMAND="./$1" + if [ -f $1.cmd ] ; then + COMMAND="$COMMAND `cat $1.cmd`" + fi + if [ -f $1.cin ] ; then + COMMAND="cat $1.cin | $COMMAND" + fi + OUTPUTFILE="$OUTPUTFILE.$PLATFORM" + echo "Executing $1 ($2) ... " + ulimit -t 3600 2> /dev/null + if eval $COMMAND > $OUTPUTFILE 2>&1 ; then + echo " successful execution of $1" >> $ERRORFILE + else + echo " ERROR: execution of $1" >> $ERRORFILE + cat $OUTPUTFILE >> $FULL_ERROR_DESCRIPTION_FILE + fi +} + +run_test_with_flags() +{ + # $1 - executable name + # $2 - test substring name + + basedata=`basename "$5"` + OUTPUTFILE="ProgramOutput.$1" + rm -f $OUTPUTFILE + COMMAND="./$1" + if [ -f $1.cmd ] ; then + COMMAND="$COMMAND `cat $1.cmd`" + elif [ -f $1.$2.cmd ] ; then + COMMAND="$COMMAND `cat $1.$2.cmd`" + OUTPUTFILE=$OUTPUTFILE.`echo $2 | tr '/' '.'` + fi + if [ -f $1.cin ] ; then + COMMAND="cat $1.cin | $COMMAND" + elif [ -f $1.$2.cin ] ; then + COMMAND="cat $1.$2.cin | $COMMAND" + OUTPUTFILE=$OUTPUTFILE.`echo $2 | tr '/' '.'` + fi + OUTPUTFILE="$OUTPUTFILE.$PLATFORM" + echo "Executing $1 ($2) ... " + ulimit -t 3600 2> /dev/null + if eval $COMMAND > $OUTPUTFILE 2>&1 ; then + echo " successful execution of $1 ($2)" >> $ERRORFILE + else + echo " ERROR: execution of $1 ($2)" >> $ERRORFILE + cat $OUTPUTFILE >> $FULL_ERROR_DESCRIPTION_FILE + fi +} + +run_test_alt() +{ + basedata=`basename "$5"` + OUTPUTFILE=ProgramOutput.$1.`echo $5 | tr '/' '.'`.$6 + #echo ****generating file $OUTPUTFILE + # dirdata=`dirname "$datafile"` + rm -f $OUTPUTFILE + COMMAND="./$1" + echo "Executing $1 $5 $6 ... " + if eval $COMMAND $2 $3 $4 $5 $6 > $OUTPUTFILE 2>&1 ; then + echo " successful execution of $5 $6" >> $ERRORFILE + else + echo " ERROR: execution of $5 $6" >> $ERRORFILE + cat $OUTPUTFILE >> $FULL_ERROR_DESCRIPTION_FILE + fi +} + +run_trapped_test() +{ + #local name=$1; + #local datafile=$2; + + if [ "${OSTYPE}" != "cygwin" ]; then + ulimit -t 1200 + run_test_alt $1 $2 $3 $4 $5 $6 + else + run_test_alt $1 $2 $3 $4 $5 $6 & + WPID=$! + trap "kill -9 $WPID" INT + (sleep 1200; kill -9 $WPID) > /dev/null 2>&1 & + SPID=$! + wait $WPID > /dev/null 2>&1 + # RES=$? + kill -9 $SPID > /dev/null 2>&1 + # return $RES + fi +} + +clean_tests() +{ + if [ "${TEST_WITH_CMAKE}" != "FALSE" ]; then + # + # The clean target generated by CMake under cygwin + # always fails for some reason + # + if ! ( uname | grep -q "CYGWIN" ) ; then + make -fMakefile clean + fi + fi + eval "make clean > /dev/null 2>&1" +} + +compile_and_run() +{ + local name=$1; + + echo "Compiling $name ... " + if [ "${TEST_WITH_CMAKE}" != "FALSE" ]; then + if eval '${MAKE_CMD} VERBOSE=1 -fMakefile $1' ; then + echo " successful compilation of $1" >> $ERRORFILE + SUCCESS="y" + else + echo " ERROR: compilation of $1" >> $ERRORFILE + SUCCESS="" + fi + else + SUCCESS="y" + #TESTSUITE_CXXFLAGS="$TESTSUITE_CXXFLAGS" + TESTSUITE_CXXFLAGS="" + TESTSUITE_LDFLAGS="$TESTSUITE_LDFLAGS" + if eval 'make CGAL_MAKEFILE=$CGAL_MAKEFILE \ + TESTSUITE_CXXFLAGS="$TESTSUITE_CXXFLAGS" \ + TESTSUITE_LDFLAGS="$TESTSUITE_LDFLAGS" $name' ; then + echo " successful compilation of $name" >> $ERRORFILE + else + echo " ERROR: compilation of $name" >> $ERRORFILE + SUCCESS="" + fi + fi + + if [ "${TEST_WITH_CMAKE}" != "FALSE" ]; then + if [ -n "$DO_RUN" ] ; then + if [ -n "${SUCCESS}" ] ; then + run_test $1 + else + echo " ERROR: not executed $1" >> $ERRORFILE + fi + fi + else + if [ -n "${SUCCESS}" ] ; then + OUTPUTFILE=ProgramOutput.$name.$PLATFORM + rm -f $OUTPUTFILE + COMMAND="./$name" + if [ -f $name.cmd ] ; then + COMMAND="$COMMAND `cat $name.cmd`" + fi + if [ -f $name.cin ] ; then + COMMAND="cat $name.cin | $COMMAND" + fi + echo "Executing $name ... " + echo " " + if eval $COMMAND > $OUTPUTFILE 2>&1 ; then + echo " successful execution of $name" >> $ERRORFILE + else + echo " ERROR: execution of $name" >> $ERRORFILE + cat $OUTPUTFILE >> $FULL_ERROR_DESCRIPTION_FILE + fi + else + echo " ERROR: not executed $name" >> $ERRORFILE + fi + fi + clean_tests +} + +compile_and_run_trapped_test() +{ + local name=$1; + + if [ "${OSTYPE}" != "cygwin" ]; then + ulimit -t 1200 + compile_and_run $1 + else + compile_and_run $1 & + WPID=$! + trap "kill -9 $WPID" INT + (sleep 1200; kill -9 $WPID) > /dev/null 2>&1 & + SPID=$! + wait $WPID > /dev/null 2>&1 + # RES=$? + kill -9 $SPID > /dev/null 2>&1 + # return $RES + fi +} + +execute_commands_old_structure() +{ + +# at first the tests where designed in such way that all the test input was +# in one file, the points, the xcurves, the curves and the execution block +# this function is used to execute the old tests, one may use it when needed +# but you should remember that separating the input into smaller files creates +# much more modular and comfortable test suite + +# the old structure is default, so this function executes all commands +# except the commands that are given as arguments + + + commands_indicator[COMPARE]=1 + commands_indicator[VERTEX]=1 + commands_indicator[IS_VERTICAL]=1 + commands_indicator[COMPARE_Y_AT_X]=1 + commands_indicator[COMPARE_Y_AT_X_LEFT]=1 + commands_indicator[COMPARE_Y_AT_X_RIGHT]=1 + commands_indicator[MAKE_X_MONOTONE]=1 + commands_indicator[INTERSECT]=1 + commands_indicator[SPLIT]=1 + commands_indicator[ARE_MERGEABLE]=1 + commands_indicator[MERGE]=1 + commands_indicator[ASSERTIONS]=1 + commands_indicator[CONSTRUCTOR]=1 + i=1 + if [ $# -gt 2 ] ; then + for arg in $* ; do + if [ $i -gt 2 ] ; then + commands_indicator[$arg]=0 + fi + let "i+=1" + done + fi + if [ ${commands_indicator[$COMPARE]} -ne 0 ] ; then + run_trapped_test test_traits \ + data/compare.pt data/empty.zero \ + data/empty.zero data/compare $2 + fi + if [ ${commands_indicator[$VERTEX]} -ne 0 ] ; then + run_trapped_test test_traits \ + data/$1/vertex.pt data/$1/vertex.xcv \ + data/empty.zero data/$1/vertex $2 + fi + if [ ${commands_indicator[$IS_VERTICAL]} -ne 0 ] ; then + run_trapped_test test_traits \ + data/empty.zero data/$1/is_vertical.xcv data/empty.zero \ + data/$1/is_vertical $2 + fi + if [ ${commands_indicator[$COMPARE_Y_AT_X]} -ne 0 ] ; then + run_trapped_test test_traits \ + data/$1/compare_y_at_x.pt data/$1/compare_y_at_x.xcv \ + data/empty.zero data/$1/compare_y_at_x $2 + fi + if [ ${commands_indicator[$COMPARE_Y_AT_X_LEFT]} -ne 0 ] ; then + run_trapped_test test_traits \ + data/$1/compare_y_at_x_left.pt data/$1/compare_y_at_x_left.xcv \ + data/empty.zero data/$1/compare_y_at_x_left $2 + fi + if [ ${commands_indicator[$COMPARE_Y_AT_X_RIGHT]} -ne 0 ] ; then + run_trapped_test test_traits \ + data/$1/compare_y_at_x_right.pt data/$1/compare_y_at_x_right.xcv \ + data/empty.zero data/$1/compare_y_at_x_right $2 + fi + if [ ${commands_indicator[$MAKE_X_MONOTONE]} -ne 0 ] ; then + run_trapped_test test_traits \ + data/empty.zero data/$1/make_x_monotone.xcv \ + data/$1/make_x_monotone.cv data/$1/make_x_monotone $2 + fi + if [ ${commands_indicator[$INTERSECT]} -ne 0 ] ; then + run_trapped_test test_traits \ + data/$1/intersect.pt data/$1/intersect.xcv \ + data/empty.zero data/$1/intersect $2 + fi + if [ ${commands_indicator[$SPLIT]} -ne 0 ] ; then + run_trapped_test test_traits \ + data/$1/split.pt data/$1/split.xcv \ + data/empty.zero data/$1/split $2 + fi + if [ ${commands_indicator[$ARE_MERGEABLE]} -ne 0 ] ; then + run_trapped_test test_traits \ + data/empty.zero data/$1/are_mergeable.xcv \ + data/empty.zero data/$1/are_mergeable $2 + fi + if [ ${commands_indicator[$MERGE]} -ne 0 ] ; then + run_trapped_test test_traits \ + data/empty.zero data/$1/merge.xcv \ + data/empty.zero data/$1/merge $2 + fi + if [ ${commands_indicator[$ASSERTIONS]} -ne 0 ] ; then + run_trapped_test test_traits \ + data/$1/assertions.pt data/$1/assertions.xcv \ + data/empty.zero data/$1/assertions $2 + fi + if [ ${commands_indicator[$CONSTRUCTOR]} -ne 0 ] ; then + run_trapped_test test_traits \ + data/empty.zero data/$1/constructor.xcv \ + data/$1/constructor.cv data/$1/constructor $2 + fi +} + +execute_commands_new_structure() +{ + +# the new design for the tests includes separation of the test input into 4 +# parts: points file, xcurves file, curves file and execution block file. +# one may reuse the input files for the various tests + +# the new structure is not default, so this function executes only +# commands that are given as arguments + + commands_indicator[COMPARE]=0 + commands_indicator[VERTEX]=0 + commands_indicator[IS_VERTICAL]=0 + commands_indicator[COMPARE_X_AT_LIMIT]=0 + commands_indicator[COMPARE_X_NEAR_LIMIT]=0 + commands_indicator[COMPARE_X_ON_BOUNDARY]=0 + commands_indicator[COMPARE_X_NEAR_BOUNDARY]=0 + commands_indicator[COMPARE_Y_NEAR_BOUNDARY]=0 + commands_indicator[PARAMETER_SPACE_X]=0 + commands_indicator[PARAMETER_SPACE_Y]=0 + commands_indicator[COMPARE_Y_AT_X]=0 + commands_indicator[COMPARE_Y_AT_X_LEFT]=0 + commands_indicator[COMPARE_Y_AT_X_RIGHT]=0 + commands_indicator[MAKE_X_MONOTONE]=0 + commands_indicator[INTERSECT]=0 + commands_indicator[SPLIT]=0 + commands_indicator[ARE_MERGEABLE]=0 + commands_indicator[MERGE]=0 + commands_indicator[ASSERTIONS]=0 + commands_indicator[CONSTRUCTOR]=0 + commands_indicator[EQUAL]=0 + commands_indicator[PUSH_BACK]=0 + commands_indicator[PUSH_FRONT]=0 + commands_indicator[NUMBER_OF_POINTS]=0 + commands_indicator[COMPARE_ENDPOINTS_XY]=0 + commands_indicator[CONSTRUCT_OPPOSITE]=0 + commands_indicator[TRIM]=0 + i=1 + if [ $# -gt 2 ] ; then + for arg in $* ; do + if [ $i -gt 2 ] ; then + commands_indicator[$arg]=1 + fi + let "i+=1" + done + fi + if [ ${commands_indicator[$COMPARE]} -ne 0 ] ; then + run_trapped_test test_traits data/$1/points \ + data/$1/xcurves data/$1/curves data/$1/compare $2 + fi + if [ ${commands_indicator[$VERTEX]} -ne 0 ] ; then + run_trapped_test test_traits data/$1/points \ + data/$1/xcurves data/$1/curves data/$1/vertex $2 + fi + if [ ${commands_indicator[$IS_VERTICAL]} -ne 0 ] ; then + run_trapped_test test_traits data/$1/points \ + data/$1/xcurves data/$1/curves data/$1/is_vertical $2 + fi + if [ ${commands_indicator[$COMPARE_X_AT_LIMIT]} -ne 0 ] ; then + run_trapped_test test_traits data/$1/points \ + data/$1/xcurves data/$1/curves data/$1/compare_x_at_limit $2 + fi + if [ ${commands_indicator[$COMPARE_X_NEAR_LIMIT]} -ne 0 ] ; then + run_trapped_test test_traits data/$1/points \ + data/$1/xcurves data/$1/curves data/$1/compare_x_near_limit $2 + fi + if [ ${commands_indicator[$COMPARE_X_ON_BOUNDARY]} -ne 0 ] ; then + run_trapped_test test_traits data/$1/points \ + data/$1/xcurves data/$1/curves data/$1/compare_x_on_boundary $2 + fi + if [ ${commands_indicator[$COMPARE_X_NEAR_BOUNDARY]} -ne 0 ] ; then + run_trapped_test test_traits data/$1/points \ + data/$1/xcurves data/$1/curves data/$1/compare_x_near_boundary $2 + fi + if [ ${commands_indicator[$COMPARE_Y_NEAR_BOUNDARY]} -ne 0 ] ; then + run_trapped_test test_traits data/$1/points \ + data/$1/xcurves data/$1/curves data/$1/compare_y_near_boundary $2 + fi + if [ ${commands_indicator[$PARAMETER_SPACE_X]} -ne 0 ] ; then + run_trapped_test test_traits data/$1/points \ + data/$1/xcurves data/$1/curves data/$1/parameter_space_x $2 + fi + if [ ${commands_indicator[$PARAMETER_SPACE_Y]} -ne 0 ] ; then + run_trapped_test test_traits data/$1/points \ + data/$1/xcurves data/$1/curves data/$1/parameter_space_y $2 + fi + if [ ${commands_indicator[$COMPARE_Y_AT_X]} -ne 0 ] ; then + run_trapped_test test_traits data/$1/points \ + data/$1/xcurves data/$1/curves data/$1/compare_y_at_x $2 + fi + if [ ${commands_indicator[$COMPARE_Y_AT_X_LEFT]} -ne 0 ] ; then + run_trapped_test test_traits data/$1/points \ + data/$1/xcurves data/$1/curves data/$1/compare_y_at_x_left $2 + fi + if [ ${commands_indicator[$COMPARE_Y_AT_X_RIGHT]} -ne 0 ] ; then + run_trapped_test test_traits data/$1/points \ + data/$1/xcurves data/$1/curves data/$1/compare_y_at_x_right $2 + fi + if [ ${commands_indicator[$MAKE_X_MONOTONE]} -ne 0 ] ; then + run_trapped_test test_traits data/$1/points \ + data/$1/xcurves data/$1/curves data/$1/make_x_monotone $2 + fi + if [ ${commands_indicator[$INTERSECT]} -ne 0 ] ; then + run_trapped_test test_traits data/$1/points \ + data/$1/xcurves data/$1/curves data/$1/intersect $2 + fi + if [ ${commands_indicator[$SPLIT]} -ne 0 ] ; then + run_trapped_test test_traits data/$1/points \ + data/$1/xcurves data/$1/curves data/$1/split $2 + fi + if [ ${commands_indicator[$ARE_MERGEABLE]} -ne 0 ] ; then + run_trapped_test test_traits data/$1/points \ + data/$1/xcurves data/$1/curves data/$1/are_mergeable $2 + fi + if [ ${commands_indicator[$MERGE]} -ne 0 ] ; then + run_trapped_test test_traits data/$1/points \ + data/$1/xcurves data/$1/curves data/$1/merge $2 + fi + if [ ${commands_indicator[$ASSERTIONS]} -ne 0 ] ; then + run_trapped_test test_traits data/$1/points \ + data/$1/xcurves data/$1/curves data/$1/assertions $2 + fi + if [ ${commands_indicator[$CONSTRUCTOR]} -ne 0 ] ; then + run_trapped_test test_traits data/$1/points \ + data/$1/xcurves data/$1/curves data/$1/constructor $2 + fi + if [ ${commands_indicator[$EQUAL]} -ne 0 ] ; then + run_trapped_test test_traits data/$1/points \ + data/$1/xcurves data/$1/curves data/$1/equal $2 + fi + if [ ${commands_indicator[$PUSH_BACK]} -ne 0 ] ; then + run_trapped_test test_traits data/$1/points \ + data/$1/xcurves data/$1/curves data/$1/push_back $2 + fi + if [ ${commands_indicator[$PUSH_FRONT]} -ne 0 ] ; then + run_trapped_test test_traits data/$1/points \ + data/$1/xcurves data/$1/curves data/$1/push_front $2 + fi + if [ ${commands_indicator[$NUMBER_OF_POINTS]} -ne 0 ] ; then + run_trapped_test test_traits data/$1/points \ + data/$1/xcurves data/$1/curves data/$1/number_of_points $2 + fi + if [ ${commands_indicator[$COMPARE_ENDPOINTS_XY]} -ne 0 ] ; then + run_trapped_test test_traits data/$1/points \ + data/$1/xcurves data/$1/curves data/$1/compare_endpoints_xy $2 + fi + if [ ${commands_indicator[$CONSTRUCT_OPPOSITE]} -ne 0 ] ; then + run_trapped_test test_traits data/$1/points \ + data/$1/xcurves data/$1/curves data/$1/construct_opposite $2 + fi + if [ ${commands_indicator[$TRIM]} -ne 0 ] ; then + run_trapped_test test_traits data/$1/points \ + data/$1/xcurves data/$1/curves data/$1/trim $2 + fi +} + +execute_commands_traits_adaptor() +{ + +# the new structure is not default, so this function executes only +# commands that are given as arguments + + commands_indicator[PARAMETER_SPACE_X]=0 + commands_indicator[PARAMETER_SPACE_Y]=0 + commands_indicator[COMPARE_X_AT_LIMIT]=0 + commands_indicator[COMPARE_X_NEAR_LIMIT]=0 + commands_indicator[COMPARE_X_ON_BOUNDARY]=0 + commands_indicator[COMPARE_X_NEAR_BOUNDARY]=0 + commands_indicator[COMPARE_Y_NEAR_BOUNDARY]=0 + commands_indicator[COMPARE_Y_AT_X_LEFT]=0 + commands_indicator[ARE_MERGEABLE]=0 + commands_indicator[MERGE]=0 + commands_indicator[X_ON_IDENTIFICATION]=0 + commands_indicator[Y_ON_IDENTIFICATION]=0 + commands_indicator[IS_BOUNDED]=0 + commands_indicator[IS_IN_X_RANGE]=0 + commands_indicator[COMPARE_Y_POSITION]=0 + commands_indicator[IS_BETWEEN_CW]=0 + commands_indicator[COMPARE_CW_AROUND_POINT]=0 + + i=1 + if [ $# -gt 2 ] ; then + for arg in $* ; do + if [ $i -gt 2 ] ; then + commands_indicator[$arg]=1 + fi + let "i+=1" + done + fi + + if [ ${commands_indicator[$PARAMETER_SPACE_X]} -ne 0 ] ; then + run_trapped_test test_traits_adaptor data/test_adaptor/$1/points \ + data/test_adaptor/$1/xcurves data/test_adaptor/$1/curves \ + data/test_adaptor/$1/parameter_space_x $2 + fi + if [ ${commands_indicator[$PARAMETER_SPACE_Y]} -ne 0 ] ; then + run_trapped_test test_traits_adaptor data/test_adaptor/$1/points \ + data/test_adaptor/$1/xcurves data/test_adaptor/$1/curves \ + data/test_adaptor/$1/parameter_space_y $2 + fi + if [ ${commands_indicator[$COMPARE_X_AT_LIMIT]} -ne 0 ] ; then + run_trapped_test test_traits_adaptor data/test_adaptor/$1/points \ + data/test_adaptor/$1/xcurves data/test_adaptor/$1/curves \ + data/test_adaptor/$1/compare_x_at_limit $2 + fi + if [ ${commands_indicator[$COMPARE_X_NEAR_LIMIT]} -ne 0 ] ; then + run_trapped_test test_traits_adaptor data/test_adaptor/$1/points \ + data/test_adaptor/$1/xcurves data/test_adaptor/$1/curves \ + data/test_adaptor/$1/compare_x_near_limit $2 + fi + + if [ ${commands_indicator[$COMPARE_X_ON_BOUNDARY]} -ne 0 ] ; then + run_trapped_test test_traits_adaptor data/test_adaptor/$1/points \ + data/test_adaptor/$1/xcurves data/test_adaptor/$1/curves \ + data/test_adaptor/$1/compare_x_on_boundary $2 + fi + if [ ${commands_indicator[$COMPARE_X_NEAR_BOUNDARY]} -ne 0 ] ; then + run_trapped_test test_traits_adaptor data/test_adaptor/$1/points \ + data/test_adaptor/$1/xcurves data/test_adaptor/$1/curves \ + data/test_adaptor/$1/compare_x_near_boundary $2 + fi + + if [ ${commands_indicator[$COMPARE_Y_NEAR_BOUNDARY]} -ne 0 ] ; then + run_trapped_test test_traits_adaptor data/test_adaptor/$1/points \ + data/test_adaptor/$1/xcurves data/test_adaptor/$1/curves \ + data/test_adaptor/$1/compare_y_near_boundary $2 + fi + if [ ${commands_indicator[$COMPARE_Y_AT_X_LEFT]} -ne 0 ] ; then + run_trapped_test test_traits_adaptor data/test_adaptor/$1/points \ + data/test_adaptor/$1/xcurves data/test_adaptor/$1/curves \ + data/test_adaptor/$1/compare_y_at_x_left $2 + fi + if [ ${commands_indicator[$ARE_MERGEABLE]} -ne 0 ] ; then + run_trapped_test test_traits_adaptor data/test_adaptor/$1/points \ + data/test_adaptor/$1/xcurves data/test_adaptor/$1/curves \ + data/test_adaptor/$1/are_mergeable $2 + fi + if [ ${commands_indicator[$MERGE]} -ne 0 ] ; then + run_trapped_test test_traits_adaptor data/test_adaptor/$1/points \ + data/test_adaptor/$1/xcurves data/test_adaptor/$1/curves \ + data/test_adaptor/$1/merge $2 + fi + if [ ${commands_indicator[X_ON_IDENTIFICATION]} -ne 0 ] ; then + run_trapped_test test_traits_adaptor data/test_adaptor/$1/points \ + data/test_adaptor/$1/xcurves data/test_adaptor/$1/curves \ + data/test_adaptor/$1/x_on_idintification $2 + fi + if [ ${commands_indicator[Y_ON_IDENTIFICATION]} -ne 0 ] ; then + run_trapped_test test_traits_adaptor data/test_adaptor/$1/points \ + data/test_adaptor/$1/xcurves data/test_adaptor/$1/curves \ + data/test_adaptor/$1/x_on_idintification $2 + fi + if [ ${commands_indicator[IS_BOUNDED]} -ne 0 ] ; then + run_trapped_test test_traits_adaptor data/test_adaptor/$1/points \ + data/test_adaptor/$1/xcurves data/test_adaptor/$1/curves \ + data/test_adaptor/$1/is_bounded $2 + fi + if [ ${commands_indicator[IS_IN_X_RANGE]} -ne 0 ] ; then + run_trapped_test test_traits_adaptor data/test_adaptor/$1/points \ + data/test_adaptor/$1/xcurves data/test_adaptor/$1/curves \ + data/test_adaptor/$1/is_in_x_range $2 + fi + if [ ${commands_indicator[COMPARE_Y_POSITION]} -ne 0 ] ; then + run_trapped_test test_traits_adaptor data/test_adaptor/$1/points \ + data/test_adaptor/$1/xcurves data/test_adaptor/$1/curves \ + data/test_adaptor/$1/compare_y_position $2 + fi + if [ ${commands_indicator[IS_BETWEEN_CW]} -ne 0 ] ; then + run_trapped_test test_traits_adaptor data/test_adaptor/$1/points \ + data/test_adaptor/$1/xcurves data/test_adaptor/$1/curves \ + data/test_adaptor/$1/is_between_cw $2 + fi + if [ ${commands_indicator[COMPARE_CW_AROUND_POINT]} -ne 0 ] ; then + run_trapped_test test_traits_adaptor data/test_adaptor/$1/points \ + data/test_adaptor/$1/xcurves data/test_adaptor/$1/curves \ + data/test_adaptor/$1/compare_cw_around_point $2 + fi +} + +#---------------------------------------------------------------------# +# traits adaptor (segments traits) +#---------------------------------------------------------------------# +test_segment_traits_adaptor() +{ + local nt=$QUOTIENT_MP_FLOAT_NT; + local kernel=$CARTESIAN_KERNEL; + local geom_traits=$SEGMENT_GEOM_TRAITS; + local flags="-DTEST_NT=$nt -DTEST_KERNEL=$kernel -DTEST_GEOM_TRAITS=$geom_traits"; + + compile_test_with_flags test_traits_adaptor segments "$flags" + if [ -n "${SUCCESS}" ] ; then + execute_commands_traits_adaptor segments segments_traits_adaptor \ + COMPARE_Y_POSITION COMPARE_CW_AROUND_POINT COMPARE_Y_AT_X_LEFT \ + ARE_MERGEABLE MERGE IS_IN_X_RANGE IS_BETWEEN_CW + else + echo " ERROR: not executed test_traits_adaptor segment_traits" >> $ERRORFILE + fi + clean_tests +} + +#---------------------------------------------------------------------# +# traits adaptor (linear traits) +#---------------------------------------------------------------------# +test_linear_traits_adaptor() +{ + local nt=$QUOTIENT_MP_FLOAT_NT; + local kernel=$CARTESIAN_KERNEL; + local geom_traits=$LINEAR_GEOM_TRAITS; + local flags="-DTEST_NT=$nt -DTEST_KERNEL=$kernel -DTEST_GEOM_TRAITS=$geom_traits"; + + compile_test_with_flags test_traits_adaptor linear "$flags" + if [ -n "${SUCCESS}" ] ; then + execute_commands_traits_adaptor linear linear_traits_adaptor \ + COMPARE_Y_AT_X_LEFT ARE_MERGEABLE MERGE IS_IN_X_RANGE \ + COMPARE_Y_POSITION IS_BETWEEN_CW COMPARE_CW_AROUND_POINT + else + echo " ERROR: not executed test_traits_adaptor linear_traits" >> $ERRORFILE + fi + clean_tests +} + +#---------------------------------------------------------------------# +# traits adaptor (spherical arcs traits) +#---------------------------------------------------------------------# +test_spherical_arcs_traits_adaptor() +{ + local nt=$CGAL_GMPQ_NT; + local kernel=$CARTESIAN_KERNEL; + local geom_traits=$GEODESIC_ARC_ON_SPHERE_GEOM_TRAITS; + local topol_traits=$SPHERICAL_TOPOL_TRAITS + local flags="-DTEST_NT=$nt -DTEST_KERNEL=$kernel -DTEST_GEOM_TRAITS=$geom_traits -DTEST_TOPOL_TRAITS=$topol_traits"; + + compile_test_with_flags test_traits_adaptor geodesic_arcs_on_sphere "$flags" + if [ -n "${SUCCESS}" ] ; then + execute_commands_traits_adaptor spherical_arcs spherical_arcs_traits_adaptor \ + COMPARE_Y_AT_X_LEFT ARE_MERGEABLE MERGE IS_IN_X_RANGE \ + COMPARE_Y_POSITION IS_BETWEEN_CW COMPARE_CW_AROUND_POINT + else + echo " ERROR: not executed test_traits_adaptor spherical_arcs_traits" >> $ERRORFILE + fi + clean_tests +} + +#---------------------------------------------------------------------# +# compile and run test with traits +#---------------------------------------------------------------------# +compile_and_run_with_flags() +{ + local name=$1; + local type=$2; + local flags=$3; + + compile_test_with_flags $name $type "$flags" + if [ -n "${SUCCESS}" ] ; then + if [ -n "$DO_RUN" ] ; then + run_test_with_flags $name $type + fi + else + echo " ERROR: not executed construction of segments" >> $ERRORFILE + fi + clean_tests +} + +#---------------------------------------------------------------------# +# construction with segments +#---------------------------------------------------------------------# +test_construction_segments() +{ + local nt=$CGAL_GMPQ_NT; + local kernel=$CARTESIAN_KERNEL; + local geom_traits=$SEGMENT_GEOM_TRAITS; + local flags="-DTEST_NT=$nt -DTEST_KERNEL=$kernel -DTEST_GEOM_TRAITS=$geom_traits"; + compile_and_run_with_flags test_construction segments "$flags" +} + +#---------------------------------------------------------------------# +# construction with linear curves +#---------------------------------------------------------------------# +test_construction_linear_curves() +{ + local nt=$CGAL_GMPQ_NT; + local kernel=$CARTESIAN_KERNEL; + local geom_traits=$LINEAR_GEOM_TRAITS; + local topol_traits=$PLANAR_UNBOUNDED_TOPOL_TRAITS; + local flags="-DTEST_NT=$nt -DTEST_KERNEL=$kernel -DTEST_GEOM_TRAITS=$geom_traits -DTEST_TOPOL_TRAITS=$topol_traits"; + compile_and_run_with_flags test_construction linear "$flags" +} + +#---------------------------------------------------------------------# +# construction with geodesic arcs on the sphere +#---------------------------------------------------------------------# +test_construction_spherical_arcs() +{ + local nt=$CGAL_GMPQ_NT; + local kernel=$CARTESIAN_KERNEL; + local geom_traits=$GEODESIC_ARC_ON_SPHERE_GEOM_TRAITS; + local topol_traits=$SPHERICAL_TOPOL_TRAITS; + local flags="-DTEST_NT=$nt -DTEST_KERNEL=$kernel -DTEST_GEOM_TRAITS=$geom_traits -DTEST_TOPOL_TRAITS=$topol_traits"; + compile_and_run_with_flags test_construction geodesic_arcs_on_sphere "$flags" +} + +#---------------------------------------------------------------------# +# construction with polylines +#---------------------------------------------------------------------# +test_construction_polylines() +{ + local nt=$CGAL_GMPQ_NT; + local kernel=$CARTESIAN_KERNEL; + local geom_traits=$POLYLINE_GEOM_TRAITS; + local flags="-DTEST_NT=$nt -DTEST_KERNEL=$kernel -DTEST_GEOM_TRAITS=$geom_traits"; + compile_and_run_with_flags test_construction polylines "$flags" +} + +#---------------------------------------------------------------------# +# overlay with segments +#---------------------------------------------------------------------# +test_overlay_segments() +{ + local nt=$CGAL_GMPQ_NT; + local kernel=$CARTESIAN_KERNEL; + local geom_traits=$SEGMENT_GEOM_TRAITS; + local flags="-DTEST_NT=$nt -DTEST_KERNEL=$kernel -DTEST_GEOM_TRAITS=$geom_traits"; + compile_and_run_with_flags test_overlay segments "$flags" +} + +#---------------------------------------------------------------------# +# overlay with geodesic arcs on the sphere +#---------------------------------------------------------------------# +test_overlay_spherical_arcs() +{ + local nt=$CGAL_GMPQ_NT; + local kernel=$CARTESIAN_KERNEL; + local geom_traits=$GEODESIC_ARC_ON_SPHERE_GEOM_TRAITS; + local topol_traits=$SPHERICAL_TOPOL_TRAITS; + local flags="-DTEST_NT=$nt -DTEST_KERNEL=$kernel -DTEST_GEOM_TRAITS=$geom_traits -DTEST_TOPOL_TRAITS=$topol_traits"; + compile_and_run_with_flags test_overlay geodesic_arcs_on_sphere "$flags" +} + +#---------------------------------------------------------------------# +# point location with segments +#---------------------------------------------------------------------# +test_point_location_segments() +{ + local nt=$CGAL_GMPQ_NT; + local kernel=$CARTESIAN_KERNEL; + local geom_traits=$SEGMENT_GEOM_TRAITS; + local flags="-DTEST_NT=$nt -DTEST_KERNEL=$kernel -DTEST_GEOM_TRAITS=$geom_traits"; + compile_and_run_with_flags test_point_location segments "$flags" +} + +# For backward compatibility +test_point_location_segments_version() +{ + local nt=$CGAL_GMPQ_NT; + local kernel=$CARTESIAN_KERNEL; + local geom_traits=$SEGMENT_GEOM_TRAITS; + local flags="-DTEST_NT=$nt -DTEST_KERNEL=$kernel -DTEST_GEOM_TRAITS=$geom_traits -DCGAL_ARR_POINT_LOCATION_VERSION=1"; + compile_and_run_with_flags test_point_location segments "$flags" +} + +# For backward compatibility +test_point_location_segments_conversion() +{ + local nt=$CGAL_GMPQ_NT; + local kernel=$CARTESIAN_KERNEL; + local geom_traits=$SEGMENT_GEOM_TRAITS; + local flags="-DTEST_NT=$nt -DTEST_KERNEL=$kernel -DTEST_GEOM_TRAITS=$geom_traits -DCGAL_ARR_POINT_LOCATION_CONVERSION"; + compile_and_run_with_flags test_point_location segments "$flags" +} + +#---------------------------------------------------------------------# +# point location dynamic with segments +#---------------------------------------------------------------------# +test_point_location_dynamic_segments() +{ + local nt=$CGAL_GMPQ_NT; + local kernel=$CARTESIAN_KERNEL; + local geom_traits=$SEGMENT_GEOM_TRAITS; + local flags="-DTEST_NT=$nt -DTEST_KERNEL=$kernel -DTEST_GEOM_TRAITS=$geom_traits"; + compile_and_run_with_flags test_point_location_dynamic segments "$flags" +} + +#---------------------------------------------------------------------# +# point location with circle segments +#---------------------------------------------------------------------# +test_point_location_circle_segments() +{ + local nt=$CGAL_GMPQ_NT; + local kernel=$CARTESIAN_KERNEL; + local geom_traits=$CIRCLE_SEGMENT_GEOM_TRAITS; + local flags="-DTEST_NT=$nt -DTEST_KERNEL=$kernel -DTEST_GEOM_TRAITS=$geom_traits"; + compile_and_run_with_flags test_point_location circle_segments "$flags" +} + +#---------------------------------------------------------------------# +# point location with linear objects +#---------------------------------------------------------------------# +test_point_location_linear() +{ + local nt=$CGAL_GMPQ_NT; + local kernel=$CARTESIAN_KERNEL; + local geom_traits=$LINEAR_GEOM_TRAITS; + local flags="-DTEST_NT=$nt -DTEST_KERNEL=$kernel -DTEST_GEOM_TRAITS=$geom_traits"; + compile_and_run_with_flags test_point_location linear "$flags" +} + +#---------------------------------------------------------------------# +# batchecd point location with segments +#---------------------------------------------------------------------# +test_batched_point_location_segments() +{ + local nt=$CGAL_GMPQ_NT; + local kernel=$CARTESIAN_KERNEL; + local geom_traits=$SEGMENT_GEOM_TRAITS; + local flags="-DTEST_NT=$nt -DTEST_KERNEL=$kernel -DTEST_GEOM_TRAITS=$geom_traits"; + compile_and_run_with_flags test_batched_point_location segments "$flags" +} + +#---------------------------------------------------------------------# +# batchecd point location with linear objects +#---------------------------------------------------------------------# +test_batched_point_location_linear() +{ + local nt=$CGAL_GMPQ_NT; + local kernel=$CARTESIAN_KERNEL; + local geom_traits=$LINEAR_GEOM_TRAITS; + local flags="-DTEST_NT=$nt -DTEST_KERNEL=$kernel -DTEST_GEOM_TRAITS=$geom_traits"; + compile_and_run_with_flags test_batched_point_location linear "$flags" +} + +#---------------------------------------------------------------------# +# batchecd point location with geodesic arcs on the sphere +#---------------------------------------------------------------------# +test_batched_point_location_spherical_arcs() +{ + local nt=$CGAL_GMPQ_NT; + local kernel=$CARTESIAN_KERNEL; + local geom_traits=$GEODESIC_ARC_ON_SPHERE_GEOM_TRAITS; + local topol_traits=$SPHERICAL_TOPOL_TRAITS; + local flags="-DTEST_NT=$nt -DTEST_KERNEL=$kernel -DTEST_GEOM_TRAITS=$geom_traits -DTEST_TOPOL_TRAITS=$topol_traits"; + compile_and_run_with_flags test_batched_point_location geodesic_arcs_on_sphere "$flags" +} + +#---------------------------------------------------------------------# +# vertical decomposition with segments +#---------------------------------------------------------------------# +test_vertical_decomposition_segments() +{ + local nt=$CGAL_GMPQ_NT; + local kernel=$CARTESIAN_KERNEL; + local geom_traits=$SEGMENT_GEOM_TRAITS; + local flags="-DTEST_NT=$nt -DTEST_KERNEL=$kernel -DTEST_GEOM_TRAITS=$geom_traits"; + compile_and_run_with_flags test_vertical_decomposition segments "$flags" +} + +#---------------------------------------------------------------------# +# vertical decomposition with linear objects +#---------------------------------------------------------------------# +test_vertical_decomposition_linear() +{ + local nt=$CGAL_GMPQ_NT; + local kernel=$CARTESIAN_KERNEL; + local geom_traits=$LINEAR_GEOM_TRAITS; + local flags="-DTEST_NT=$nt -DTEST_KERNEL=$kernel -DTEST_GEOM_TRAITS=$geom_traits"; + compile_and_run_with_flags test_vertical_decomposition linear "$flags" +} + +#---------------------------------------------------------------------# +# vertical decomposition with geodesic arcs on the sphere +#---------------------------------------------------------------------# +test_vertical_decomposition_spherical_arcs() +{ + local nt=$CGAL_GMPQ_NT; + local kernel=$CARTESIAN_KERNEL; + local geom_traits=$LINEAR_GEOM_TRAITS; + local topol_traits=$SPHERICAL_TOPOL_TRAITS; + local flags="-DTEST_NT=$nt -DTEST_KERNEL=$kernel -DTEST_GEOM_TRAITS=$geom_traits -DTEST_TOPOL_TRAITS=$topol_traits"; + compile_and_run_with_flags test_vertical_decomposition geodesic_arcs_on_sphere "$flags" +} + +#---------------------------------------------------------------------# +# segment traits +#---------------------------------------------------------------------# +test_segment_traits() +{ + local nt=$QUOTIENT_MP_FLOAT_NT; + local kernel=$CARTESIAN_KERNEL; + local geom_traits=$SEGMENT_GEOM_TRAITS; + local flags="-DTEST_NT=$nt -DTEST_KERNEL=$kernel -DTEST_GEOM_TRAITS=$geom_traits"; + + compile_test_with_flags test_traits segments "$flags" + if [ -n "${SUCCESS}" ] ; then + execute_commands_old_structure segments segment_traits \ + VERTEX IS_VERTICAL COMPARE_Y_AT_X COMPARE_Y_AT_X_LEFT CONSTRUCTOR \ + COMPARE_Y_AT_X_RIGHT ARE_MERGEABLE + + execute_commands_new_structure segments segment_traits \ + IS_VERTICAL COMPARE_Y_AT_X COMPARE_Y_AT_X_LEFT ARE_MERGEABLE + + run_trapped_test test_traits \ + data/segments/vertex.pt data/segments/xcurves \ + data/empty.zero data/segments/vertex segment_traits + else + echo " ERROR: not executed test_traits segment_traits" >> $ERRORFILE + fi + clean_tests +} + +#---------------------------------------------------------------------# +# non-caching segment traits +#---------------------------------------------------------------------# +test_non_caching_segment_traits() +{ + local nt=$CGAL_GMPQ_NT; + local kernel=$CARTESIAN_KERNEL; + local geom_traits=$NON_CACHING_SEGMENT_GEOM_TRAITS; + local flags="-DTEST_NT=$nt -DTEST_KERNEL=$kernel -DTEST_GEOM_TRAITS=$geom_traits"; + + compile_test_with_flags test_traits non_caching_segments "$flags" + if [ -n "${SUCCESS}" ] ; then + execute_commands_old_structure segments non_caching_segment_traits \ + VERTEX IS_VERTICAL COMPARE_Y_AT_X COMPARE_Y_AT_X_LEFT CONSTRUCTOR \ + COMPARE_Y_AT_X_RIGHT ARE_MERGEABLE ASSERTIONS + + execute_commands_new_structure segments segment_traits \ + IS_VERTICAL COMPARE_Y_AT_X COMPARE_Y_AT_X_LEFT + + run_trapped_test test_traits \ + data/segments/vertex.pt data/segments/xcurves \ + data/empty.zero data/segments/vertex non_caching_segment_traits + else + echo " ERROR: not executed test_traits non_caching_segment_traits" >> $ERRORFILE + fi + clean_tests +} + +#---------------------------------------------------------------------# +# polycurve conic traits +#---------------------------------------------------------------------# +test_polycurve_conic_traits() +{ + if [ -n "${CGAL_DISABLE_GMP}" ]; then + echo "CORE is not available, test_polycurve_conic_traits not ran" + return + fi + echo polycurve test starting + local nt=$CORE_EXPR_NT; + local kernel=$CARTESIAN_KERNEL; + local geom_traits=$POLYCURVE_CONIC_GEOM_TRAITS; + local flags="-DTEST_NT=$nt -DTEST_KERNEL=$kernel -DTEST_GEOM_TRAITS=$geom_traits"; + + compile_test_with_flags test_traits conic_polycurve "$flags" + if [ -n "${SUCCESS}" ] ; then + + # The input arguments for the execute_commands_new_structure, + # 1. Polycurve_conics is the directory name in "data" + # 2. polycurve_conic_traits is a string + # Execute_command_new_structure will only run the test on functors provided as the third, fourth and so on arguments. + # To see how the input data directory should be structured for each functor, check the execute_commands_new_structure function in this file. + execute_commands_new_structure polycurves_conics polycurve_conic_traits \ + COMPARE_Y_AT_X \ + INTERSECT \ + EQUAL \ + IS_VERTICAL \ + SPLIT \ + ARE_MERGEABLE \ + COMPARE_Y_AT_X_LEFT \ + COMPARE_Y_AT_X_RIGHT \ + MAKE_X_MONOTONE \ + PUSH_BACK \ + PUSH_FRONT \ + NUMBER_OF_POINTS \ + VERTEX \ + CONSTRUCT_OPPOSITE \ + MERGE \ + COMPARE_ENDPOINTS_XY \ + TRIM + + else + echo " ERROR: not executed test_traits polyline_traits" >> $ERRORFILE + fi + clean_tests +} + +#---------------------------------------------------------------------# +# polycurve arc traits +#---------------------------------------------------------------------# +test_polycurve_circular_arc_traits() +{ + local nt=$QUOTIENT_MP_FLOAT_NT; + local kernel=$CARTESIAN_KERNEL; + local geom_traits=$POLYCURVE_CIRCULAR_ARC_GEOM_TRAITS; + local flags="-DTEST_NT=$nt -DTEST_KERNEL=$kernel -DTEST_GEOM_TRAITS=$geom_traits"; + + compile_test_with_flags test_traits circular_arc_polycurve "$flags" + if [ -n "${SUCCESS}" ] ; then + execute_commands_new_structure Polycurves_circular_arcs polycurve_circular_arc_traits \ + COMPARE_Y_AT_X \ + EQUAL \ + IS_VERTICAL \ + SPLIT \ + ARE_MERGEABLE \ + COMPARE_Y_AT_X_LEFT \ + COMPARE_Y_AT_X_RIGHT \ + MAKE_X_MONOTONE \ + PUSH_BACK \ + PUSH_FRONT \ + NUMBER_OF_POINTS \ + VERTEX \ + CONSTRUCT_OPPOSITE \ + MERGE \ + COMPARE_ENDPOINTS_XY \ + INTERSECT + + else + echo " ERROR: not executed test_traits polyline_traits" >> $ERRORFILE + fi + clean_tests +} + +#---------------------------------------------------------------------# +# polycurve bezier traits +#---------------------------------------------------------------------# +test_polycurve_bezier_traits() +{ + if [ -n "${CGAL_DISABLE_GMP}" ]; then + echo "CORE is not available, test_polycurve_bezier_traits not ran" + return + fi + local nt=$CORE_EXPR_NT; + local kernel=$CARTESIAN_KERNEL; + local geom_traits=$POLYCURVE_BEZIER_GEOM_TRAITS; + local flags="-DTEST_NT=$nt -DTEST_KERNEL=$kernel -DTEST_GEOM_TRAITS=$geom_traits"; + + compile_test_with_flags test_traits bezier_polycurve "$flags" + if [ -n "${SUCCESS}" ] ; then + execute_commands_new_structure Polycurves_bezier test_polycurve_bezier_traits \ + MERGE \ + EQUAL \ + IS_VERTICAL \ + NUMBER_OF_POINTS \ + PUSH_BACK \ + PUSH_FRONT \ + VERTEX \ + ARE_MERGEABLE \ + COMPARE_ENDPOINTS_XY + # TODO (add data for these tests) + # COMPARE_Y_AT_X \ + # SPLIT \ + # COMPARE_Y_AT_X_LEFT \ + # COMPARE_Y_AT_X_RIGHT \ + # MAKE_X_MONOTONE \ + # CONSTRUCT_OPPOSITE \ + + # INTERSECT + + else + echo " ERROR: not executed test_traits polyline_traits" >> $ERRORFILE + fi + clean_tests +} + +#---------------------------------------------------------------------# +# polyline traits +#---------------------------------------------------------------------# +test_polyline_traits() +{ + local nt=$CGAL_GMPQ_NT; + local kernel=$CARTESIAN_KERNEL; + local geom_traits=$POLYLINE_GEOM_TRAITS; + local flags="-DTEST_NT=$nt -DTEST_KERNEL=$kernel -DTEST_GEOM_TRAITS=$geom_traits"; + + compile_test_with_flags test_traits test_polylines "$flags" + if [ -n "${SUCCESS}" ] ; then + execute_commands_old_structure polylines polyline_traits \ + CONSTRUCTOR COMPARE_Y_AT_X_LEFT \ + COMPARE_Y_AT_X_RIGHT ARE_MERGEABLE + else + echo " ERROR: not executed test_traits polyline_traits" >> $ERRORFILE + fi + clean_tests +} + +#---------------------------------------------------------------------# +# non-caching polyline traits +#---------------------------------------------------------------------# +test_non_caching_polyline_traits() +{ + local nt=$CGAL_GMPQ_NT; + local kernel=$CARTESIAN_KERNEL; + local geom_traits=$NON_CACHING_POLYLINE_GEOM_TRAITS; + local flags="-DTEST_NT=$nt -DTEST_KERNEL=$kernel -DTEST_GEOM_TRAITS=$geom_traits"; + + compile_test_with_flags test_traits non_caching_polylines "$flags" + if [ -n "${SUCCESS}" ] ; then + execute_commands_old_structure polylines non_caching_polyline_traits \ + CONSTRUCTOR COMPARE_Y_AT_X_LEFT \ + COMPARE_Y_AT_X_RIGHT ARE_MERGEABLE + else + echo " ERROR: not executed test_traits non_caching_polyline_traits" >> $ERRORFILE + fi + clean_tests +} + +#---------------------------------------------------------------------# +# linear traits +#---------------------------------------------------------------------# +test_linear_traits() +{ + local nt=$QUOTIENT_MP_FLOAT_NT; + local kernel=$CARTESIAN_KERNEL; + local geom_traits=$LINEAR_GEOM_TRAITS; + local flags="-DTEST_NT=$nt -DTEST_KERNEL=$kernel -DTEST_GEOM_TRAITS=$geom_traits"; + + compile_test_with_flags test_traits linear "$flags" + if [ -n "${SUCCESS}" ] ; then + execute_commands_old_structure linear/segments linear_traits.segments \ + VERTEX IS_VERTICAL COMPARE_Y_AT_X COMPARE_Y_AT_X_LEFT \ + COMPARE_Y_AT_X_RIGHT CONSTRUCTOR ARE_MERGEABLE + + execute_commands_new_structure linear/segments linear_traits.segments \ + IS_VERTICAL COMPARE_Y_AT_X COMPARE_Y_AT_X_LEFT + + run_trapped_test test_traits \ + data/linear/segments/vertex.pt data/linear/segments/xcurves \ + data/empty.zero data/linear/segments/vertex linear_traits.segments + + execute_commands_old_structure linear/rays linear_traits.rays \ + VERTEX IS_VERTICAL COMPARE_Y_AT_X COMPARE_Y_AT_X_LEFT \ + COMPARE_Y_AT_X_RIGHT CONSTRUCTOR ARE_MERGEABLE + + execute_commands_new_structure linear/rays linear_traits.rays \ + IS_VERTICAL COMPARE_Y_AT_X COMPARE_Y_AT_X_LEFT + + run_trapped_test test_traits \ + data/linear/rays/vertex.pt data/linear/rays/xcurves \ + data/empty.zero data/linear/rays/vertex linear_traits.rays + + execute_commands_new_structure linear/lines linear_traits.lines \ + IS_VERTICAL COMPARE_Y_AT_X COMPARE_Y_AT_X_LEFT INTERSECT \ + SPLIT MERGE \ + PARAMETER_SPACE_X PARAMETER_SPACE_Y \ + COMPARE_X_AT_LIMIT COMPARE_X_NEAR_LIMIT COMPARE_Y_NEAR_BOUNDARY + else + echo " ERROR: not executed test_traits linear_traits" >> $ERRORFILE + fi + clean_tests +} + +#---------------------------------------------------------------------# +# conic traits +#---------------------------------------------------------------------# +test_conic_traits() +{ + if [ -n "${CGAL_DISABLE_GMP}" ]; then + echo "CORE is not available, test_conic_traits not ran" + return + fi + local nt=$CORE_EXPR_NT; + local kernel=$CARTESIAN_KERNEL; + local geom_traits=$CORE_CONIC_GEOM_TRAITS; + local flags="-DTEST_NT=$nt -DTEST_KERNEL=$kernel -DTEST_GEOM_TRAITS=$geom_traits"; + + compile_test_with_flags test_traits conics "$flags" + if [ -n "${SUCCESS}" ] ; then + execute_commands_old_structure conics conic_traits \ + INTERSECT SPLIT MERGE COMPARE_Y_AT_X_LEFT \ + COMPARE_Y_AT_X_RIGHT ARE_MERGEABLE + + execute_commands_new_structure conics conic_traits \ + INTERSECT SPLIT MERGE + + run_trapped_test test_traits \ + data/conics/compare.pt data/empty.zero \ + data/empty.zero data/conics/compare conic_traits + else + echo " ERROR: not executed test_traits conic_traits" >> $ERRORFILE + fi + clean_tests +} + +#---------------------------------------------------------------------# +# "line arcs" (segments) only +#---------------------------------------------------------------------# +test_line_arc_traits() +{ + local nt=$QUOTIENT_MP_FLOAT_NT; + local kernel=$CARTESIAN_KERNEL; + local geom_traits=$LINE_ARC_GEOM_TRAITS; + local flags="-DTEST_NT=$nt -DTEST_KERNEL=$kernel -DTEST_GEOM_TRAITS=$geom_traits"; + + compile_test_with_flags test_traits line_arcs "$flags" + if [ -n "${SUCCESS}" ] ; then + execute_commands_old_structure circular_lines line_arc_traits \ + VERTEX IS_VERTICAL COMPARE_Y_AT_X COMPARE_Y_AT_X_LEFT \ + ASSERTIONS COMPARE_Y_AT_X_RIGHT MERGE ARE_MERGEABLE + + execute_commands_new_structure circular_lines line_arc_traits \ + IS_VERTICAL COMPARE_Y_AT_X + + run_trapped_test test_traits \ + data/circular_lines/compare.pt data/empty.zero \ + data/empty.zero data/circular_lines/compare line_arc_traits + + run_trapped_test test_traits \ + data/circular_lines/vertex.pt data/circular_lines/xcurves \ + data/empty.zero data/circular_lines/vertex line_arc_traits + else + echo " ERROR: not executed test_traits" >> $ERRORFILE + fi + clean_tests +} + +#---------------------------------------------------------------------# +# circular arcs only +#---------------------------------------------------------------------# +test_circular_arc_traits() +{ + local nt=$QUOTIENT_MP_FLOAT_NT; + local kernel=$CARTESIAN_KERNEL; + local geom_traits=$CIRCULAR_ARC_GEOM_TRAITS; + local flags="-DTEST_NT=$nt -DTEST_KERNEL=$kernel -DTEST_GEOM_TRAITS=$geom_traits"; + + compile_test_with_flags test_traits circular_arcs "$flags" + if [ -n "${SUCCESS}" ] ; then + execute_commands_old_structure circular_arcs circular_arc_traits \ + VERTEX IS_VERTICAL COMPARE_Y_AT_X COMPARE_Y_AT_X_LEFT \ + ASSERTIONS COMPARE_Y_AT_X_RIGHT MERGE ARE_MERGEABLE + + execute_commands_new_structure circular_arcs circular_arc_traits \ + VERTEX IS_VERTICAL COMPARE_Y_AT_X + else + echo " ERROR: not executed test_traits" >> $ERRORFILE + fi + clean_tests +} + +#---------------------------------------------------------------------# +# circular and line arcs +#---------------------------------------------------------------------# +test_circular_line_arc_traits() +{ + local nt=$QUOTIENT_MP_FLOAT_NT; + local kernel=$CARTESIAN_KERNEL; + local geom_traits=$CIRCULAR_LINE_ARC_GEOM_TRAITS; + local flags="-DTEST_NT=$nt -DTEST_KERNEL=$kernel -DTEST_GEOM_TRAITS=$geom_traits"; + + compile_test_with_flags test_traits circular_line_arcs "$flags" + if [ -n "${SUCCESS}" ] ; then + execute_commands_old_structure circular_line_arcs circular_line_arc_traits \ + VERTEX IS_VERTICAL CONSTRUCTOR COMPARE_Y_AT_X COMPARE_Y_AT_X_LEFT \ + ASSERTIONS COMPARE_Y_AT_X_RIGHT MERGE ARE_MERGEABLE + + execute_commands_new_structure circular_line_arcs circular_line_arc_traits \ + IS_VERTICAL COMPARE_Y_AT_X + + run_trapped_test test_traits \ + data/circular_line_arcs/vertex.pt data/circular_line_arcs/xcurves \ + data/empty.zero data/circular_line_arcs/vertex circular_line_arc_traits + else + echo " ERROR: not executed test_traits circular_line_arc_traits" >> $ERRORFILE + fi + clean_tests +} + +#---------------------------------------------------------------------# +# circle segment traits +#---------------------------------------------------------------------# +test_circle_segments_traits() +{ + local nt=$QUOTIENT_MP_FLOAT_NT; + local kernel=$CARTESIAN_KERNEL; + local geom_traits=$CIRCLE_SEGMENT_GEOM_TRAITS; + local flags="-DTEST_NT=$nt -DTEST_KERNEL=$kernel -DTEST_GEOM_TRAITS=$geom_traits"; + + compile_test_with_flags test_traits circle_segments "$flags" + if [ -n "${SUCCESS}" ] ; then + execute_commands_old_structure circle_segments circle_segments_traits \ + VERTEX IS_VERTICAL COMPARE_Y_AT_X COMPARE_Y_AT_X_LEFT \ + COMPARE_Y_AT_X_RIGHT CONSTRUCTOR ARE_MERGEABLE + + run_trapped_test test_traits \ + data/circle_segments/points data/circle_segments/xcurves.8 \ + data/empty.zero data/circle_segments/vertex circle_segments_traits + run_trapped_test test_traits \ + data/empty.zero data/circle_segments/xcurves.8 \ + data/empty.zero data/circle_segments/is_vertical circle_segments_traits + run_trapped_test test_traits \ + data/circle_segments/points data/circle_segments/xcurves.8 \ + data/empty.zero data/circle_segments/compare_y_at_x circle_segments_traits + run_trapped_test test_traits \ + data/circle_segments/points data/circle_segments/xcurves.16 \ + data/empty.zero data/circle_segments/compare_y_at_x_left circle_segments_traits + run_trapped_test test_traits \ + data/circle_segments/points data/circle_segments/xcurves.16 \ + data/empty.zero data/circle_segments/compare_y_at_x_right circle_segments_traits + run_trapped_test test_traits \ + data/empty.zero data/circle_segments/constructor.xcv \ + data/empty.zero data/circle_segments/constructor circle_segments_traits + else + echo " ERROR: not executed test_traits circle_segments_traits" >> $ERRORFILE + fi + clean_tests +} + +#---------------------------------------------------------------------# +# bezier traits +#---------------------------------------------------------------------# +test_bezier_traits() +{ + if [ -n "${CGAL_DISABLE_GMP}" ]; then + echo "CORE is not available, test_bezier_traits not ran" + return + fi + local nt=$CORE_EXPR_NT; + local kernel=$CARTESIAN_KERNEL; + local geom_traits=$BEZIER_GEOM_TRAITS; + local flags="-DTEST_NT=$nt -DTEST_KERNEL=$kernel -DTEST_GEOM_TRAITS=$geom_traits"; + + compile_test_with_flags test_traits Bezier "$flags" + if [ -n "${SUCCESS}" ] ; then + execute_commands_old_structure bezier bezier_traits \ + COMPARE_Y_AT_X_LEFT COMPARE_Y_AT_X_RIGHT SPLIT \ + CONSTRUCTOR ASSERTIONS ARE_MERGEABLE + else + echo " ERROR: not executed test_traits bezier_traits" >> $ERRORFILE + fi + clean_tests +} + +#---------------------------------------------------------------------# +# spherical arc traits +#---------------------------------------------------------------------# +test_spherical_arc_traits() +{ + local nt=$CGAL_GMPQ_NT; + local kernel=$CARTESIAN_KERNEL; + local geom_traits=$GEODESIC_ARC_ON_SPHERE_GEOM_TRAITS; + local topol_traits=$SPHERICAL_TOPOL_TRAITS + local flags="-DTEST_NT=$nt -DTEST_KERNEL=$kernel -DTEST_GEOM_TRAITS=$geom_traits -DTEST_TOPOL_TRAITS=$topol_traits"; + + compile_test_with_flags test_traits geodesic_arcs_on_sphere "$flags" + if [ -n "${SUCCESS}" ] ; then + execute_commands_old_structure spherical_arcs spherical_arc_traits \ + COMPARE_Y_AT_X_LEFT COMPARE_Y_AT_X_RIGHT INTERSECT \ + CONSTRUCTOR \ + COMPARE MAKE_X_MONOTONE SPLIT MERGE ASSERTIONS ARE_MERGEABLE + + execute_commands_new_structure spherical_arcs spherical_arc_traits \ + INTERSECT \ + COMPARE_X_ON_BOUNDARY COMPARE_X_NEAR_BOUNDARY \ + COMPARE_Y_NEAR_BOUNDARY + + run_trapped_test test_traits \ + data/spherical_arcs/compare.pt data/spherical_arcs/compare.xcv \ + data/empty.zero data/spherical_arcs/compare spherical_arc_traits + else + echo " ERROR: not executed test_traits spherical_arc_traits" >> $ERRORFILE + fi + clean_tests +} + +#---------------------------------------------------------------------# +# rational arc traits +#---------------------------------------------------------------------# +test_rational_arc_traits() +{ + if [ -n "${CGAL_DISABLE_GMP}" ]; then + echo "CORE is not available, test_rational_arc_traits not ran" + return + fi + local nt=$CORE_INT_NT; + local kernel=$UNIVARIATE_ALGEBRAIC_KERNEL; + local geom_traits=$RATIONAL_ARC_GEOM_TRAITS; + local flags="-DTEST_NT=$nt -DTEST_KERNEL=$kernel -DTEST_GEOM_TRAITS=$geom_traits"; + + compile_test_with_flags test_traits rational_arcs "$flags" + if [ -n "${SUCCESS}" ] ; then + run_trapped_test test_traits \ + data/compare.pt data/empty.zero \ + data/empty.zero data/compare rational_arc_traits + + execute_commands_new_structure rational_arcs rational_arc_traits \ + VERTEX IS_VERTICAL COMPARE_Y_AT_X COMPARE_Y_AT_X_LEFT SPLIT MERGE \ + COMPARE_X_AT_LIMIT COMPARE_X_NEAR_LIMIT COMPARE_Y_NEAR_BOUNDARY + else + echo " ERROR: not executed test_traits rational_arc_traits" >> $ERRORFILE + fi + clean_tests +} + +#---------------------------------------------------------------------# +# algebraic traits with GMP/MPFI +#---------------------------------------------------------------------# +test_algebraic_traits_gmp() +{ + #TODO: Adapt + + local nt=$CGAL_GMPZ_NT; + local kernel=$UNIVARIATE_ALGEBRAIC_KERNEL; + local geom_traits=$ALGEBRAIC_GEOM_TRAITS; + local flags="-DTEST_NT=$nt -DTEST_KERNEL=$kernel -DTEST_GEOM_TRAITS=$geom_traits"; + + compile_test_with_flags test_traits algebraic "$flags" + if [ -n "${SUCCESS}" ] ; then + execute_commands_new_structure algebraic algebraic_traits_gmp \ + COMPARE COMPARE_Y_AT_X COMPARE_Y_AT_X_RIGHT COMPARE_Y_AT_X_LEFT \ + MAKE_X_MONOTONE IS_VERTICAL VERTEX SPLIT MERGE INTERSECT \ + PARAMETER_SPACE_X PARAMETER_SPACE_Y + else + echo " ERROR: not executed test_traits algebraic_traits_gmp" >> $ERRORFILE + fi + clean_tests +} + +#---------------------------------------------------------------------# +# algebraic traits with LEDA +#---------------------------------------------------------------------# +test_algebraic_traits_leda() +{ + #TODO: Adapt + + local nt=$LEDA_INT_NT; + local kernel=$UNIVARIATE_ALGEBRAIC_KERNEL; + local geom_traits=$ALGEBRAIC_GEOM_TRAITS; + local flags="-DTEST_NT=$nt -DTEST_KERNEL=$kernel -DTEST_GEOM_TRAITS=$geom_traits"; + + compile_test_with_flags test_traits algebraic "$flags" + if [ -n "${SUCCESS}" ] ; then + execute_commands_new_structure algebraic algebraic_traits_leda \ + COMPARE COMPARE_Y_AT_X COMPARE_Y_AT_X_RIGHT COMPARE_Y_AT_X_LEFT \ + MAKE_X_MONOTONE IS_VERTICAL VERTEX SPLIT MERGE INTERSECT \ + PARAMETER_SPACE_X PARAMETER_SPACE_Y + else + echo " ERROR: not executed test_traits algebraic_traits_leda" >> $ERRORFILE + fi + clean_tests +} + + +#---------------------------------------------------------------------# +# algebraic traits with CORE +#---------------------------------------------------------------------# +test_algebraic_traits_core() +{ + #TODO: Adapt + if [ -n "${CGAL_DISABLE_GMP}" ]; then + echo "CORE is not available, test_algebraic_traits_core not ran" + return + fi + local nt=$CORE_INT_NT; + local kernel=$UNIVARIATE_ALGEBRAIC_KERNEL; + local geom_traits=$ALGEBRAIC_GEOM_TRAITS; + local flags="-DTEST_NT=$nt -DTEST_KERNEL=$kernel -DTEST_GEOM_TRAITS=$geom_traits"; + + compile_test_with_flags test_traits algebraic "$flags" + if [ -n "${SUCCESS}" ] ; then + execute_commands_new_structure algebraic algebraic_traits_core \ + COMPARE COMPARE_Y_AT_X COMPARE_Y_AT_X_RIGHT COMPARE_Y_AT_X_LEFT \ + MAKE_X_MONOTONE IS_VERTICAL VERTEX SPLIT MERGE INTERSECT \ + PARAMETER_SPACE_X PARAMETER_SPACE_Y + else + echo " ERROR: not executed test_traits algebraic_traits_core" >> $ERRORFILE + fi + clean_tests +} + +#---------------------------------------------------------------------# +# remove the previous error file +#---------------------------------------------------------------------# + +rm -f $ERRORFILE +rm -f $FULL_ERROR_DESCRIPTION_FILE +rm -f ProgramOutput.test_* +touch $ERRORFILE + +#---------------------------------------------------------------------# +# compile and run the tests +#---------------------------------------------------------------------# + + + +if [ $# -ne 0 ] ; then + case $1 in + -cmake) TEST_WITH_CMAKE="TRUE" ;; + *)TEST_WITH_CMAKE="FALSE" ;; + esac +else + TEST_WITH_CMAKE="FALSE" +fi + +echo "Run all tests." + +if [ "${TEST_WITH_CMAKE}" != "FALSE" ]; then + configure +fi + +if [ "${TEST_WITH_CMAKE}" != "FALSE" ]; then + compile_and_run construction_test_suite_generator +fi + +test_segment_traits +test_non_caching_segment_traits +test_polyline_traits +test_polycurve_conic_traits +test_polycurve_circular_arc_traits +test_polycurve_bezier_traits +test_non_caching_polyline_traits +test_linear_traits +test_conic_traits + +test_line_arc_traits # "line arcs" (segments) only +test_circular_arc_traits # circular arcs only +test_circular_line_arc_traits # for both + +test_circle_segments_traits +test_bezier_traits + +test_spherical_arc_traits + +test_rational_arc_traits + +test_algebraic_traits_core +test_algebraic_traits_gmp +test_algebraic_traits_leda + +compile_and_run test_data_traits + +compile_and_run test_insertion +compile_and_run test_unbounded_rational_insertion +compile_and_run test_unbounded_rational_direct_insertion +compile_and_run test_rational_function_traits_2 +compile_and_run test_iso_verts + +compile_and_run test_vert_ray_shoot_vert_segments + +test_construction_segments +test_construction_linear_curves +test_construction_spherical_arcs +test_construction_polylines + +test_overlay_segments +test_overlay_spherical_arcs + +test_point_location_segments +test_point_location_segments_version +test_point_location_segments_conversion +test_point_location_circle_segments +test_point_location_linear + +test_point_location_dynamic_segments + +test_batched_point_location_segments +test_batched_point_location_linear +test_batched_point_location_spherical_arcs + +test_vertical_decomposition_segments +test_vertical_decomposition_linear +# test_vertical_decomposition_spherical_arcs + +compile_and_run test_dual +compile_and_run test_do_intersect +compile_and_run test_zone + +compile_and_run test_observer +compile_and_run test_do_equal + +test_segment_traits_adaptor +test_linear_traits_adaptor +test_spherical_arcs_traits_adaptor + +compile_and_run test_removal +compile_and_run test_unbounded_removal +compile_and_run test_spherical_removal + +compile_and_run test_io + +compile_and_run test_sgm + +# if any error occured then append the full error description file to error file + +if [ -f $FULL_ERROR_DESCRIPTION_FILE ] ; then + echo "******************** appending all error outputs ********************" >> $ERRORFILE + cat $FULL_ERROR_DESCRIPTION_FILE >> $ERRORFILE +fi diff --git a/CGAL_ipelets/demo/CGAL_ipelets/cgal_test_with_cmake b/CGAL_ipelets/demo/CGAL_ipelets/cgal_test_with_cmake index 1893347ec5b..fb097574953 100755 --- a/CGAL_ipelets/demo/CGAL_ipelets/cgal_test_with_cmake +++ b/CGAL_ipelets/demo/CGAL_ipelets/cgal_test_with_cmake @@ -26,8 +26,9 @@ configure() { echo "Configuring... " - if eval 'cmake --no-warn-unused-cli "$CMAKE_GENERATOR" -DRUNNING_CGAL_AUTO_TEST=TRUE \ + if eval 'cmake --no-warn-unused-cli -C"$INIT_FILE" "$CMAKE_GENERATOR" -DRUNNING_CGAL_AUTO_TEST=TRUE \ -DCGAL_DIR="$CGAL_DIR" \ + -DINIT_FILE="$INIT_FILE" \ .' ; then echo " successful configuration" >> $ERRORFILE diff --git a/Minkowski_sum_2/test/Minkowski_sum_2/cgal_test_with_cmake b/Minkowski_sum_2/test/Minkowski_sum_2/cgal_test_with_cmake index 5206be1c0cb..625bdffabbd 100755 --- a/Minkowski_sum_2/test/Minkowski_sum_2/cgal_test_with_cmake +++ b/Minkowski_sum_2/test/Minkowski_sum_2/cgal_test_with_cmake @@ -26,8 +26,9 @@ configure() { echo "Configuring... " - if eval 'cmake --no-warn-unused-cli "$CMAKE_GENERATOR" -DRUNNING_CGAL_AUTO_TEST=TRUE \ + if eval 'cmake --no-warn-unused-cli -C"$INIT_FILE" "$CMAKE_GENERATOR" -DRUNNING_CGAL_AUTO_TEST=TRUE \ -DCGAL_DIR="$CGAL_DIR" \ + -DINIT_FILE="$INIT_FILE" \ .' ; then echo " successful configuration" >> $ERRORFILE diff --git a/Poisson_surface_reconstruction_3/examples/Poisson_surface_reconstruction_3/cgal_test_with_cmake b/Poisson_surface_reconstruction_3/examples/Poisson_surface_reconstruction_3/cgal_test_with_cmake index a40dfb9baac..8db1c729e60 100755 --- a/Poisson_surface_reconstruction_3/examples/Poisson_surface_reconstruction_3/cgal_test_with_cmake +++ b/Poisson_surface_reconstruction_3/examples/Poisson_surface_reconstruction_3/cgal_test_with_cmake @@ -49,8 +49,9 @@ configure() { echo "Configuring... " - if eval 'cmake --no-warn-unused-cli "$CMAKE_GENERATOR" -DRUNNING_CGAL_AUTO_TEST=TRUE \ + if eval 'cmake --no-warn-unused-cli -C"$INIT_FILE" "$CMAKE_GENERATOR" -DRUNNING_CGAL_AUTO_TEST=TRUE \ -DCGAL_DIR="$CGAL_DIR" \ + -DINIT_FILE="$INIT_FILE" \ .' ; then echo " successful configuration" >> $ERRORFILE diff --git a/Poisson_surface_reconstruction_3/test/Poisson_surface_reconstruction_3/cgal_test_with_cmake b/Poisson_surface_reconstruction_3/test/Poisson_surface_reconstruction_3/cgal_test_with_cmake index 80ba7fc5820..ad134d433a7 100755 --- a/Poisson_surface_reconstruction_3/test/Poisson_surface_reconstruction_3/cgal_test_with_cmake +++ b/Poisson_surface_reconstruction_3/test/Poisson_surface_reconstruction_3/cgal_test_with_cmake @@ -49,8 +49,9 @@ configure() { echo "Configuring... " - if eval 'cmake --no-warn-unused-cli "$CMAKE_GENERATOR" -DRUNNING_CGAL_AUTO_TEST=TRUE \ + if eval 'cmake --no-warn-unused-cli -C"$INIT_FILE" "$CMAKE_GENERATOR" -DRUNNING_CGAL_AUTO_TEST=TRUE \ -DCGAL_DIR="$CGAL_DIR" \ + -DINIT_FILE="$INIT_FILE" \ .' ; then echo " successful configuration" >> $ERRORFILE diff --git a/Polyhedron/demo/Polyhedron/cgal_test_with_cmake b/Polyhedron/demo/Polyhedron/cgal_test_with_cmake index 740946b6ccc..5b177843da1 100755 --- a/Polyhedron/demo/Polyhedron/cgal_test_with_cmake +++ b/Polyhedron/demo/Polyhedron/cgal_test_with_cmake @@ -26,8 +26,9 @@ configure() { echo "Configuring... " - if eval 'cmake --no-warn-unused-cli "$CMAKE_GENERATOR" -DRUNNING_CGAL_AUTO_TEST=TRUE \ + if eval 'cmake --no-warn-unused-cli -C"$INIT_FILE" "$CMAKE_GENERATOR" -DRUNNING_CGAL_AUTO_TEST=TRUE \ -DCGAL_DIR="$CGAL_DIR" \ + -DINIT_FILE="$INIT_FILE" \ .' ; then echo " successful configuration" >> $ERRORFILE diff --git a/Scripts/developer_scripts/autotest_cgal b/Scripts/developer_scripts/autotest_cgal index 27ae791e4ce..462815bb483 100755 --- a/Scripts/developer_scripts/autotest_cgal +++ b/Scripts/developer_scripts/autotest_cgal @@ -1,4 +1,4 @@ -#!/bin/sh +#!/bin/bash # ---------------------------------------------------- # ---------------------------------------------------- # autotest_cgal: a script to automagically install and @@ -525,7 +525,7 @@ export MAKE_CMD; export CGAL_BINARY_DIR; export CGAL_REFERENCE_CACHE_DIR; cd '${CGAL_BINARY_DIR}'; -cmake '${CMAKE_GENERATOR}' -DRUNNING_CGAL_AUTO_TEST=TRUE \\ +cmake -C"${CGAL_REFERENCE_CACHE_DIR}/init.cmake" '${CMAKE_GENERATOR}' -DRUNNING_CGAL_AUTO_TEST=TRUE \\ -DCGAL_REFERENCE_CACHE_DIR="\$CGAL_REFERENCE_CACHE_DIR" \\ VERBOSE=1 \\ ../../..; diff --git a/Scripts/developer_scripts/create_cgal_test b/Scripts/developer_scripts/create_cgal_test index b65d4b9c3fc..9141a0d52c5 100755 --- a/Scripts/developer_scripts/create_cgal_test +++ b/Scripts/developer_scripts/create_cgal_test @@ -80,8 +80,9 @@ configure() { echo "Configuring... " - if eval 'cmake "\$CMAKE_GENERATOR" -DRUNNING_CGAL_AUTO_TEST=TRUE \\ + if eval 'cmake -C"\$INIT_FILE" "\$CMAKE_GENERATOR" -DRUNNING_CGAL_AUTO_TEST=TRUE \\ -DCGAL_DIR="\$CGAL_DIR" \\ + -DINIT_FILE="\$INIT_FILE" \\ --no-warn-unused-cli \\ .' ; then diff --git a/Set_movable_separability_2/test/Set_movable_separability_2/cgal_test_with_cmake b/Set_movable_separability_2/test/Set_movable_separability_2/cgal_test_with_cmake index 6ecbcff56f4..79534ffd81c 100755 --- a/Set_movable_separability_2/test/Set_movable_separability_2/cgal_test_with_cmake +++ b/Set_movable_separability_2/test/Set_movable_separability_2/cgal_test_with_cmake @@ -26,8 +26,9 @@ configure() { echo "Configuring... " - if eval 'cmake "$CMAKE_GENERATOR" -DRUNNING_CGAL_AUTO_TEST=TRUE \ + if eval 'cmake -C"$INIT_FILE" "$CMAKE_GENERATOR" -DRUNNING_CGAL_AUTO_TEST=TRUE \ -DCGAL_DIR="$CGAL_DIR" \ + -DINIT_FILE="$INIT_FILE" \ .' ; then echo " successful configuration" >> $ERRORFILE diff --git a/Surface_sweep_2/test/Surface_sweep_2/cgal_test_base b/Surface_sweep_2/test/Surface_sweep_2/cgal_test_base index 7152f915d58..5671aa7a0c5 100755 --- a/Surface_sweep_2/test/Surface_sweep_2/cgal_test_base +++ b/Surface_sweep_2/test/Surface_sweep_2/cgal_test_base @@ -32,8 +32,9 @@ configure() rm CMakeCache.txt - if eval 'cmake --no-warn-unused-cli "$CMAKE_GENERATOR" -DRUNNING_CGAL_AUTO_TEST=TRUE \ + if eval 'cmake --no-warn-unused-cli -C"$INIT_FILE" "$CMAKE_GENERATOR" -DRUNNING_CGAL_AUTO_TEST=TRUE \ -DCGAL_DIR="$CGAL_DIR" \ + -DINIT_FILE="$INIT_FILE" \ -DCGAL_CXX_FLAGS:STRING="$TESTSUITE_CXXFLAGS" \ -DCGAL_EXE_LINKER_FLAGS="$TESTSUITE_LDFLAGS" \ -DCMAKE_BUILD_TYPE=NOTFOUND \ From 56a6dbaee73ffa436611fae0f80fb797d8f8d361 Mon Sep 17 00:00:00 2001 From: Maxime Gimeno Date: Mon, 27 Aug 2018 16:40:53 +0200 Subject: [PATCH 069/116] Make the dialog compact. --- .../Plugins/PCA/MeshOnGrid_dialog.ui | 78 +++++++------------ 1 file changed, 26 insertions(+), 52 deletions(-) diff --git a/Polyhedron/demo/Polyhedron/Plugins/PCA/MeshOnGrid_dialog.ui b/Polyhedron/demo/Polyhedron/Plugins/PCA/MeshOnGrid_dialog.ui index 3613737b207..f4ae9f081f0 100644 --- a/Polyhedron/demo/Polyhedron/Plugins/PCA/MeshOnGrid_dialog.ui +++ b/Polyhedron/demo/Polyhedron/Plugins/PCA/MeshOnGrid_dialog.ui @@ -6,8 +6,8 @@ 0 0 - 227 - 486 + 330 + 394 @@ -26,14 +26,14 @@ Along X - + 99999999999999999475366575191804932315794610450682175621941694731908308538307845136842752.000000000000000 - + Spacing: @@ -41,26 +41,13 @@ - - - Qt::Vertical - - - - 20 - 40 - - - - - Number of items : - + 999 @@ -79,21 +66,21 @@ Along Y - + 99999999999999999475366575191804932315794610450682175621941694731908308538307845136842752.000000000000000 - + Spacing: - + 999 @@ -103,26 +90,13 @@ - + Number of items : - - - - Qt::Vertical - - - - 20 - 40 - - - - @@ -132,41 +106,28 @@ Along Z - - - - Qt::Vertical - - - - 20 - 40 - - - - - + 99999999999999999475366575191804932315794610450682175621941694731908308538307845136842752.000000000000000 - + Number of items : - + Spacing: - + 999 @@ -179,6 +140,19 @@ + + + + Qt::Vertical + + + + 20 + 40 + + + + From d1f969ec86ec28b0caa9ada6f4d1d3c5b6430448 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mael=20Rouxel-Labb=C3=A9?= Date: Tue, 28 Aug 2018 11:46:20 +0200 Subject: [PATCH 070/116] Changed CHANGES.md --- Installation/CHANGES.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/Installation/CHANGES.md b/Installation/CHANGES.md index 36ec3ab0f90..98c17855417 100644 --- a/Installation/CHANGES.md +++ b/Installation/CHANGES.md @@ -1,6 +1,25 @@ Release History =============== +Release 4.14 +------------ + +Release date: March 2019 + +### Polygon Mesh Processing package +- Added the following new functions to detect and repair mesh degeneracies: + - `CGAL::Polygon_mesh_processing::degenerate_edges()` + - `CGAL::Polygon_mesh_processing::degenerate_faces()` + - `CGAL::Polygon_mesh_processing::is_non_manifold_vertex()` + - `CGAL::Polygon_mesh_processing::is_degenerate_triangle_face()` + - `CGAL::Polygon_mesh_processing::is_degenerate_edge()` + - `CGAL::Polygon_mesh_processing::is_needle_triangle_face()` + - `CGAL::Polygon_mesh_processing::is_cap_triangle_face()` + - `CGAL::Polygon_mesh_processing::duplicate_non_manifold_vertices()` + - `CGAL::Polygon_mesh_processing::extract_boundary_cycles()` + - `CGAL::Polygon_mesh_processing::merge_duplicated_vertices_in_boundary_cycle()` + - `CGAL::Polygon_mesh_processing::merge_duplicated_vertices_in_boundary_cycles()` + Release 4.13 ------------ From 72422ca498798ec8cfccfe2064088e8b5173d5d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mael=20Rouxel-Labb=C3=A9?= Date: Tue, 28 Aug 2018 11:46:34 +0200 Subject: [PATCH 071/116] Replaced ::max() (to avoid issues with NTs that do not have a max value) --- .../shape_predicates.h | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/shape_predicates.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/shape_predicates.h index 0057c32e0da..7b4a84e36cf 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/shape_predicates.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/shape_predicates.h @@ -185,7 +185,6 @@ is_needle_triangle_face(typename boost::graph_traits::face_descrip const double threshold, const NamedParameters& np) { - CGAL_precondition(CGAL::is_triangle_mesh(tm)); CGAL_precondition(threshold >= 1.); using boost::get_param; @@ -202,15 +201,22 @@ is_needle_triangle_face(typename boost::graph_traits::face_descrip typedef typename Traits::FT FT; - const halfedge_descriptor h0 = halfedge(f, tm); - FT max_sq_length = - std::numeric_limits::max(), - min_sq_length = std::numeric_limits::max(); - halfedge_descriptor min_h = boost::graph_traits::null_halfedge(); + CGAL::Halfedge_around_face_iterator hit, hend; + boost::tie(hit, hend) = CGAL::halfedges_around_face(halfedge(f, tm), tm); + CGAL_precondition(std::distance(hit, hend) == 3); - BOOST_FOREACH(halfedge_descriptor h, halfedges_around_face(h0, tm)) + const halfedge_descriptor h0 = *hit++; + FT sq_length = traits.compute_squared_distance_3_object()(get(vpmap, source(h0, tm)), + get(vpmap, target(h0, tm))); + + FT min_sq_length = sq_length, max_sq_length = sq_length; + halfedge_descriptor min_h = h0; + + for(; hit!=hend; ++hit) { - const FT sq_length = traits.compute_squared_distance_3_object()(get(vpmap, source(h, tm)), - get(vpmap, target(h, tm))); + const halfedge_descriptor h = *hit; + sq_length = traits.compute_squared_distance_3_object()(get(vpmap, source(h, tm)), + get(vpmap, target(h, tm))); if(max_sq_length < sq_length) max_sq_length = sq_length; From da0ca06329fde1ff7495768d0dea80c7a1be6b46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Loriot?= Date: Mon, 3 Sep 2018 15:36:26 +0200 Subject: [PATCH 072/116] walk used is the stochastic walk and not the line walk in locate --- Triangulation_2/doc/Triangulation_2/Triangulation_2.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Triangulation_2/doc/Triangulation_2/Triangulation_2.txt b/Triangulation_2/doc/Triangulation_2/Triangulation_2.txt index 65688e950b0..e9bcdeccf88 100644 --- a/Triangulation_2/doc/Triangulation_2/Triangulation_2.txt +++ b/Triangulation_2/doc/Triangulation_2/Triangulation_2.txt @@ -379,12 +379,12 @@ Flip. \subsection Triangulation_2Implementation Implementation -Locate is implemented by a line walk. The walk -begins at a vertex of the face which +Locate is implemented by a stochastic walk \cgalCite{cgal:dpt-wt-02}. +The walk begins at a vertex of the face which is given as an optional argument or at an arbitrary vertex of the triangulation if no optional argument is given. It takes -time \f$ O(n)\f$ in the worst case, but only \f$ O(\sqrt{n})\f$ +time \f$ O(n)\f$ in the worst case for Delaunay Triangulations, but only \f$ O(\sqrt{n})\f$ on average if the vertices are distributed uniformly at random. The class `Triangulation_hierarchy_2`, described in section \ref Section_2D_Triangulations_Hierarchy, From b5474fd02e20550f26115ab48b9515ebe123d88f Mon Sep 17 00:00:00 2001 From: Maxime Gimeno Date: Mon, 3 Sep 2018 15:30:53 +0200 Subject: [PATCH 073/116] Update create_new_release to use the cmake script. --- Scripts/developer_scripts/create_new_release | 108 +++++++------------ 1 file changed, 37 insertions(+), 71 deletions(-) diff --git a/Scripts/developer_scripts/create_new_release b/Scripts/developer_scripts/create_new_release index 055ac8c41fa..00b2c708a75 100755 --- a/Scripts/developer_scripts/create_new_release +++ b/Scripts/developer_scripts/create_new_release @@ -5,7 +5,6 @@ # Radu Ursu, Sylvain Pion, 2004-2006. # TODO : -# - Use svn revision instead of separate internal release number ? # - Cleanup the public/internal separation: # - have CGAL_VERSION_NR be not affected by the internal version # - have CGAL_REVISION be the revision (replacing the internal number) @@ -13,45 +12,44 @@ # [new] : create_internal_release should not know about internal/public mode. # - Merge [some parts] into ./create_internal_release ? -DO_RPM="" # Also build RPMs (no RPMs by default) DO_PUBLIC="" # Also build the public versions -DO_IT="" # Real mode (svn tag, copy to HTTP server), versus local testing -DO_NOT_TAG="" # If set, do not call svn tag -DO_TAG="" # If set, svn tag is called anyway +DO_IT="" # Real mode (copy to HTTP server), versus local testing +DO_NOT_TAG="" # If set, do not call tag +DO_TAG="" # If set, tag is called anyway NO_TESTUITE="" # If set, the LATEST file is not updated NO_SCM="" # If set, git pull is not called. -SOURCES_DIR="$PWD/trunk" # SVN working copy directory, default is "$PWD/trunk" +SOURCES_DIR="$PWD" # Directory containing the sources, default is "$PWD" VERBOSE="" # Verbose mode (displays log to standard err) BETA="" #If set, will change the release number and version number accordingly. -SCM=svn +DESTINATION="/tmp" +IS_MASTER="" SOURCES_DIR_HAS_BEEN_SET= -CANDIDATES_DIR_HAS_BEEN_SET= printerr() { echo "$@" >&2; } usage() { - printerr "Usage : $0 [--help] [--rpm] [--public] [--do-it] [--beta ] []" + printerr "Usage : $0 [--help] [--public] [--do-it] [--beta ] " printerr printerr ' --help : prints this usage help' - printerr ' --rpm : also build the corresponding SRPMs' printerr ' --public : also build the corresponding public versions' printerr ' --do-it : the real thing (NOT for local tests! Only for the release' printerr ' master! Does write operations (updating the version_number,' - printerr ' svn tag, copying on the web server...)' + printerr ' tag, copying on the web server...)' printerr ' --do-not-tag : when used with --do-it, the tag is not created.' printerr ' --tag : when used without --do-it, the tag is created, but files' printerr ' are not published' - printerr ' --no-scm-update : do not "svn update" or "git pull"' + printerr ' --no-scm-update : do not "git pull"' printerr ' --no-testsuite : when used with --do-it, the tag is made, files are published,' printerr ' but the LATEST file is not updated.' printerr ' --verbose : print log to standard output, instead of the log file' printerr ' --beta : followed by a number. When used with --public, will modify the release number and the release version name to include beta' + printerr ' --dest : followed by the path to where the release should be created. Default is /tmp.' + printerr ' --is_master : replace the Ic in the name by I.' printerr ' : the directory containing the packages [default is trunk]' - printerr ' : the directory containing the candidate packages [no default]' } @@ -62,12 +60,8 @@ while [ $1 ]; do usage; exit; ;; - --rpm) - DO_RPM="y" - shift; continue - ;; --public) - DO_PUBLIC="y" + DO_PUBLIC="ON" shift; continue ;; --do-it) @@ -91,7 +85,7 @@ while [ $1 ]; do shift; continue ;; --verbose) - VERBOSE="y" + VERBOSE="ON" shift; continue ;; --beta) @@ -104,6 +98,15 @@ while [ $1 ]; do shift; continue ;; + --dest) + shift + DESTINATION=$1 + shift;continue + ;; + --is_master) + IS_MASTER="y" + shift;continue + ;; -*) printerr "Unrecognized option : $1" exit @@ -132,8 +135,6 @@ VERSION_FILE="version_number" HTML_DIR="/u/agrion/geometrica/CGAL/Members/Releases" URL="http://cgal.inria.fr/CGAL/Members/Releases" -# SVN repository -SVNCGAL="svn+ssh://scm.gforge.inria.fr/svn/cgal" PATH=$PATH:/usr/local/bin:/usr/bin/gnu @@ -172,18 +173,11 @@ if [ -d "${SOURCES_DIR}/.git" ]; then pushd "${SOURCES_DIR}" [ -z "$NO_SCM" ] && git pull CGAL_GIT_HASH=`git rev-parse HEAD` - CGAL_SVN_REVISION=99999 popd - SCM=git else - [ -z "$NO_SCM" ] && svn update ${SOURCES_DIR} - CGAL_SVN_REVISION=`svn info ${SOURCES_DIR} | grep Revision | cut -d' ' -f2` - CGAL_GIT_HASH=n/a + echo "Not a git repository" + exit 1 fi -if [ "$SCM" = "svn" -a -n "${CANDIDATES_DIR_HAS_BEEN_SET}" ]; then - [ -z "$NO_SCM" ] && svn update ${CANDIDATES_DIR} -fi - # Set the major/minor/bugfix release numbers NUMBERS_DIR=${SOURCES_DIR}/Maintenance/release_building MAJOR_NUMBER=`cat ${NUMBERS_DIR}/MAJOR_NUMBER` # 2 digits max @@ -202,13 +196,13 @@ if [ -r $VERSION_FILE ]; then INTERNAL_NUMBER=$(( `cat $VERSION_FILE` + 1 )) [ -n "$DO_TAG" ] && printf "%d\n" "${INTERNAL_NUMBER}" > $VERSION_FILE else - INTERNAL_NUMBER=$((`svn ls $SVNCGAL/tags/internal-releases | awk "/${MAJOR_NUMBER}\\.${MINOR_NUMBER}${BUGFIX_STRING}/ "'{FS="-|/"; print $4}' | sort -n | tail -1` + 1 )) + echo "Need a \"version_number\" file." fi if [ -z "$INTERNAL_NUMBER" ]; then INTERNAL_NUMBER=1 fi -if [ -n "${CANDIDATES_DIR_HAS_BEEN_SET}" ]; then +if [ -z "${IS_MASTER}" ]; then INTERNAL_STRING="-Ic-${INTERNAL_NUMBER}" else INTERNAL_STRING="-I-${INTERNAL_NUMBER}" @@ -217,10 +211,11 @@ fi internal_nr=`printf "%4s" "${INTERNAL_NUMBER}" | sed "y/ /0/"` if [ -r "${NUMBERS_DIR}/release_name" ]; then - release_name=`cat "${NUMBERS_DIR}/release_name"`${INTERNAL_STRING} + release_version=`cat "${NUMBERS_DIR}/release_name"`${INTERNAL_STRING} else - release_name="CGAL-${MAJOR_NUMBER}.${MINOR_NUMBER}${BUGFIX_STRING}${INTERNAL_STRING}" + release_version="${MAJOR_NUMBER}.${MINOR_NUMBER}${BUGFIX_STRING}${INTERNAL_STRING}" fi +release_name="CGAL-${release_version}" echo "${release_name}" major_nr=`printf "%2s" "${MAJOR_NUMBER}" | sed "y/ /0/"` minor_nr=`printf "%2s" "${MINOR_NUMBER}" | sed "y/ /0/"` @@ -235,8 +230,12 @@ fi function cleanup() { # Remove local directory and tarball - rm -rf ./"${release_name}" - rm ${release_name}.tar.gz + if [ -d ${release_name} ]; then + rm -rf ./"${release_name}" + fi + if [ -f ${release_name}.tar.gz ]; then + rm ${release_name}.tar.gz + fi if [ -n "$DO_PUBLIC" ]; then [ -d "${public_release_name}" ] && rm -rf ./"${public_release_name}" rm -rf doc @@ -249,14 +248,8 @@ function cleanup() { trap cleanup EXIT # Create the release -if [ -n "$CANDIDATES_DIR_HAS_BEEN_SET" ]; then - ${SOURCES_DIR}/Scripts/developer_scripts/create_internal_release -a ${SOURCES_DIR} -c ${CANDIDATES_DIR} -r ${release_name} -n ${release_number} -else - ${SOURCES_DIR}/Scripts/developer_scripts/create_internal_release -a ${SOURCES_DIR} -r ${release_name} -n ${release_number} -fi -# Add the SVN revision to version.h -cd "${release_name}" -sed -i -e "s/define CGAL_SVN_REVISION .*/define CGAL_SVN_REVISION $CGAL_SVN_REVISION/" include/CGAL/version.h +cmake -DPUBLIC="${DO_PUBLIC}" -DDESTINATION="${DESTINATION}" -DCGAL_VERSION="${release_version}" -DCGAL_VERSION_NR="${release_number}" -DVERBOSE="${VERBOSE}" -P ${SOURCES_DIR}/Scripts/developer_scripts/cgal_create_release_with_cmake.cmake +cd "${DESTINATION}/${release_name}" sed -i -e "s/define CGAL_GIT_HASH .*/define CGAL_GIT_HASH $CGAL_GIT_HASH/" include/CGAL/version.h cd .. # Make the release tarball @@ -284,20 +277,8 @@ if [ -n "$DO_TAG" ]; then if [ -n "$NO_TESTSUITE" ]; then TAG_MSG_EXTRA=" (no testsuite)" fi - [ "$SCM" = "svn" ] && svn cp -m "Internal release tag for $release_name$TAG_MSG_EXTRA (automated commit)" ${SOURCES_DIR} $SVNCGAL/tags/internal-releases/$release_name fi -# Build the SRPM -if [ "$DO_RPM" ]; then - echo "Making the SRPM file" - rm -rf rpm - cp -r ${SOURCES_DIR}/Maintenance/rpm . - cp ${release_name}.tar.gz rpm/SOURCES/ - make -C rpm CGAL.src CGAL_INTERNAL_RELEASE=${INTERNAL_NUMBER} - echo "`basename rpm/SRPMS/*.src.rpm`" > "${HTML_DIR}/LATEST_SRPM" - mv rpm/SRPMS/*.src.rpm "${HTML_DIR}" - rm -rf rpm -fi # Build public release version if [ -n "$DO_PUBLIC" ]; then @@ -318,10 +299,6 @@ if [ -n "$DO_PUBLIC" ]; then mv -T ${release_name} $public_release_name cd ${public_release_name} - rm -rf bench* Bench* test package_info developer_scripts doc winutils include/CGAL/Test include/CGAL/Testsuite/ - rm -f examples/*/cgal_test* demo/*/cgal_test* - find . -name .scm-urls -exec rm '{}' '+' - rm -f .scm-branch # Modify the version numbers in sed -i -e "s/define CGAL_VERSION .*/define CGAL_VERSION $public_release_version/" -e "s/define CGAL_VERSION_NR .*/define CGAL_VERSION_NR $public_release_number/" include/CGAL/version.h @@ -345,17 +322,6 @@ if [ -n "$DO_PUBLIC" ]; then ln -s "${release_name}-public" "$HTML_DIR/CGAL-last-public" fi -# Build the SRPM of the public version -if [ -n "$DO_RPM" -a -n "$DO_PUBLIC" ]; then - echo "Making the SRPM public file" - rm -rf rpm - cp -r ${SOURCES_DIR}/Maintenance/rpm . - cp ${public_release_name}.tar.gz rpm/SOURCES/ - make -C rpm CGAL.src - # echo "`basename rpm/SRPMS/*.src.rpm`" > "${HTML_DIR}/LATEST_SRPM" - mv rpm/SRPMS/*.src.rpm "${HTML_DIR}/${release_name}-public/" - rm -rf rpm -fi if [ -n "$DO_PUBLIC" ]; then # Build the Windows installer From 6870ddd8ef05a9339dfb9c4a30cc07e7954a50ae Mon Sep 17 00:00:00 2001 From: Laurent Rineau Date: Mon, 3 Sep 2018 16:29:29 +0200 Subject: [PATCH 074/116] work also in a git-worktree --- Scripts/developer_scripts/create_new_release | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Scripts/developer_scripts/create_new_release b/Scripts/developer_scripts/create_new_release index 00b2c708a75..49cffa4fcaf 100755 --- a/Scripts/developer_scripts/create_new_release +++ b/Scripts/developer_scripts/create_new_release @@ -169,7 +169,7 @@ set -e cd ${TMPDIR} || return # Update the working copy -if [ -d "${SOURCES_DIR}/.git" ]; then +if [ -e "${SOURCES_DIR}/.git" ]; then pushd "${SOURCES_DIR}" [ -z "$NO_SCM" ] && git pull CGAL_GIT_HASH=`git rev-parse HEAD` From f09c4ffe4ef09a1111040dc761c2884feab77c96 Mon Sep 17 00:00:00 2001 From: Laurent Rineau Date: Mon, 3 Sep 2018 16:29:43 +0200 Subject: [PATCH 075/116] Untabiffy --- Scripts/developer_scripts/create_new_release | 36 ++++++++++---------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/Scripts/developer_scripts/create_new_release b/Scripts/developer_scripts/create_new_release index 49cffa4fcaf..4c348309920 100755 --- a/Scripts/developer_scripts/create_new_release +++ b/Scripts/developer_scripts/create_new_release @@ -47,8 +47,8 @@ usage() { printerr ' --verbose : print log to standard output, instead of the log file' printerr ' --beta : followed by a number. When used with --public, will modify the release number and the release version name to include beta' - printerr ' --dest : followed by the path to where the release should be created. Default is /tmp.' - printerr ' --is_master : replace the Ic in the name by I.' + printerr ' --dest : followed by the path to where the release should be created. Default is /tmp.' + printerr ' --is_master : replace the Ic in the name by I.' printerr ' : the directory containing the packages [default is trunk]' } @@ -98,15 +98,15 @@ while [ $1 ]; do shift; continue ;; - --dest) - shift - DESTINATION=$1 - shift;continue - ;; - --is_master) - IS_MASTER="y" - shift;continue - ;; + --dest) + shift + DESTINATION=$1 + shift;continue + ;; + --is_master) + IS_MASTER="y" + shift;continue + ;; -*) printerr "Unrecognized option : $1" exit @@ -230,12 +230,12 @@ fi function cleanup() { # Remove local directory and tarball - if [ -d ${release_name} ]; then + if [ -d ${release_name} ]; then rm -rf ./"${release_name}" - fi - if [ -f ${release_name}.tar.gz ]; then + fi + if [ -f ${release_name}.tar.gz ]; then rm ${release_name}.tar.gz - fi + fi if [ -n "$DO_PUBLIC" ]; then [ -d "${public_release_name}" ] && rm -rf ./"${public_release_name}" rm -rf doc @@ -290,11 +290,11 @@ if [ -n "$DO_PUBLIC" ]; then fi public_release_version="${MAJOR_NUMBER}.${MINOR_NUMBER}${BUGFIX_STRING}" if [ -n "$BETA" ]; then - public_release_name="CGAL-${public_release_version}-beta${BETA}" + public_release_name="CGAL-${public_release_version}-beta${BETA}" elif [ -r "${NUMBERS_DIR}/public_release_name" ]; then - public_release_name=`cat "${NUMBERS_DIR}/public_release_name"` + public_release_name=`cat "${NUMBERS_DIR}/public_release_name"` else - public_release_name="CGAL-${public_release_version}" + public_release_name="CGAL-${public_release_version}" fi mv -T ${release_name} $public_release_name From 8cb8102cfc3349c03284df640bfb0df869ce516a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mael=20Rouxel-Labb=C3=A9?= Date: Mon, 3 Sep 2018 16:35:02 +0200 Subject: [PATCH 076/116] Fixed not incrementing index --- .../include/CGAL/Polygon_mesh_processing/shape_predicates.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/shape_predicates.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/shape_predicates.h index 7b4a84e36cf..a84f99aae69 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/shape_predicates.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/shape_predicates.h @@ -338,6 +338,8 @@ is_cap_triangle_face(typename boost::graph_traits::face_descriptor if(neg_sp && sq_cos >= sq_threshold) return prev(h, tm); + + ++pos; } return boost::graph_traits::null_halfedge(); } From f4f3953cc03053b72047e7d92acf71bbafb347ff Mon Sep 17 00:00:00 2001 From: Laurent Rineau Date: Mon, 3 Sep 2018 17:12:56 +0200 Subject: [PATCH 077/116] Fix the script --- Scripts/developer_scripts/create_new_release | 43 +++++++++++--------- 1 file changed, 24 insertions(+), 19 deletions(-) diff --git a/Scripts/developer_scripts/create_new_release b/Scripts/developer_scripts/create_new_release index 4c348309920..636f3bb1449 100755 --- a/Scripts/developer_scripts/create_new_release +++ b/Scripts/developer_scripts/create_new_release @@ -233,6 +233,9 @@ function cleanup() { if [ -d ${release_name} ]; then rm -rf ./"${release_name}" fi + if [ -d "${DESTINATION}/${release_name}" ]; then + rm -rf "${DESTINATION}/${release_name}" + fi if [ -f ${release_name}.tar.gz ]; then rm ${release_name}.tar.gz fi @@ -248,8 +251,8 @@ function cleanup() { trap cleanup EXIT # Create the release -cmake -DPUBLIC="${DO_PUBLIC}" -DDESTINATION="${DESTINATION}" -DCGAL_VERSION="${release_version}" -DCGAL_VERSION_NR="${release_number}" -DVERBOSE="${VERBOSE}" -P ${SOURCES_DIR}/Scripts/developer_scripts/cgal_create_release_with_cmake.cmake -cd "${DESTINATION}/${release_name}" +cmake -DPUBLIC=NO -DDESTINATION="${DESTINATION}" -DCGAL_VERSION="${release_version}" -DCGAL_VERSION_NR="${release_number}" -DVERBOSE="${VERBOSE}" -P ${SOURCES_DIR}/Scripts/developer_scripts/cgal_create_release_with_cmake.cmake +pushd "${DESTINATION}/${release_name}" sed -i -e "s/define CGAL_GIT_HASH .*/define CGAL_GIT_HASH $CGAL_GIT_HASH/" include/CGAL/version.h cd .. # Make the release tarball @@ -262,6 +265,7 @@ ln -s "${release_name}.tar.gz" "$HTML_DIR/CGAL-last.tar.gz" if [ -z "${NO_TESTSUITE}" ]; then echo "${release_name}.tar.gz" > "${HTML_DIR}/LATEST" fi +popd # Tag if [ -n "$DO_TAG" ]; then @@ -290,15 +294,12 @@ if [ -n "$DO_PUBLIC" ]; then fi public_release_version="${MAJOR_NUMBER}.${MINOR_NUMBER}${BUGFIX_STRING}" if [ -n "$BETA" ]; then - public_release_name="CGAL-${public_release_version}-beta${BETA}" - elif [ -r "${NUMBERS_DIR}/public_release_name" ]; then - public_release_name=`cat "${NUMBERS_DIR}/public_release_name"` - else - public_release_name="CGAL-${public_release_version}" + public_release_version="${public_release_version}-beta${BETA}" fi - mv -T ${release_name} $public_release_name + public_release_name="CGAL-${public_release_version}" - cd ${public_release_name} + cmake -DPUBLIC="ON" -DDESTINATION="${DESTINATION}" -DCGAL_VERSION="${public_release_version}" -DCGAL_VERSION_NR="${release_number}" -DVERBOSE="${VERBOSE}" -P ${SOURCES_DIR}/Scripts/developer_scripts/cgal_create_release_with_cmake.cmake + pushd "${DESTINATION}/${public_release_name}" # Modify the version numbers in sed -i -e "s/define CGAL_VERSION .*/define CGAL_VERSION $public_release_version/" -e "s/define CGAL_VERSION_NR .*/define CGAL_VERSION_NR $public_release_number/" include/CGAL/version.h @@ -320,17 +321,21 @@ if [ -n "$DO_PUBLIC" ]; then cp "${public_release_name}.zip" "${HTML_DIR}/${release_name}-public/" rm -f "$HTML_DIR/CGAL-last-public" ln -s "${release_name}-public" "$HTML_DIR/CGAL-last-public" + popd fi - if [ -n "$DO_PUBLIC" ]; then - # Build the Windows installer - docker pull cgal/cgal-nsis-dockerfile - docker create -v `realpath ${public_release_name}`:/mnt/cgal_release:ro,z \ - -v ${SOURCES_DIR}:/mnt/cgal_sources:ro,z \ - cgal/cgal-nsis-dockerfile - container_id=`docker ps -q -l` - docker start -a ${container_id} - docker cp ${container_id}:/nsis_release/${public_release_name}-Setup.exe "${HTML_DIR}/${release_name}-public/" - docker rm ${container_id} + if docker version > /dev/null; then + # Build the Windows installer + docker pull cgal/cgal-nsis-dockerfile + docker create -v `realpath ${public_release_name}`:/mnt/cgal_release:ro,z \ + -v ${SOURCES_DIR}:/mnt/cgal_sources:ro,z \ + cgal/cgal-nsis-dockerfile + container_id=`docker ps -q -l` + docker start -a ${container_id} + docker cp ${container_id}:/nsis_release/${public_release_name}-Setup.exe "${HTML_DIR}/${release_name}-public/" + docker rm ${container_id} + else + echo "Cannot use Docker, the Windows installer will not be created" >&2 + fi fi From 0034efa67a437941e5029507bdf4a1a9846edb22 Mon Sep 17 00:00:00 2001 From: Laurent Rineau Date: Tue, 4 Sep 2018 11:09:22 +0200 Subject: [PATCH 078/116] IS_MASTER=y by default, and compatibility with old syntax --- Scripts/developer_scripts/create_new_release | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Scripts/developer_scripts/create_new_release b/Scripts/developer_scripts/create_new_release index 636f3bb1449..d6f52ce39ad 100755 --- a/Scripts/developer_scripts/create_new_release +++ b/Scripts/developer_scripts/create_new_release @@ -22,7 +22,7 @@ SOURCES_DIR="$PWD" # Directory containing the sources, default is "$PWD" VERBOSE="" # Verbose mode (displays log to standard err) BETA="" #If set, will change the release number and version number accordingly. DESTINATION="/tmp" -IS_MASTER="" +IS_MASTER="y" SOURCES_DIR_HAS_BEEN_SET= @@ -116,9 +116,9 @@ while [ $1 ]; do SOURCES_DIR="$1" SOURCES_DIR_HAS_BEEN_SET=y shift; continue - elif [ -z "$CANDIDATES_DIR_HAS_BEEN_SET" ]; then - CANDIDATES_DIR="$1" - CANDIDATES_DIR_HAS_BEEN_SET=y + elif [ -n "$IS_MASTER" ]; then + # Compatibility with the old syntax with candidates + IS_MASTER="" shift; continue else printerr "Unrecognized option : $1" From 8c744204de34e6bfa98da3806e58f7ec04513d16 Mon Sep 17 00:00:00 2001 From: Laurent Rineau Date: Tue, 4 Sep 2018 11:26:33 +0200 Subject: [PATCH 079/116] Compatibility: no -C option if INIT_FILE is unset or null --- .../test/Arrangement_on_surface_2/cgal_test_with_cmake | 6 ++---- CGAL_ipelets/demo/CGAL_ipelets/cgal_test_with_cmake | 3 +-- Minkowski_sum_2/test/Minkowski_sum_2/cgal_test_with_cmake | 3 +-- .../Poisson_surface_reconstruction_3/cgal_test_with_cmake | 3 +-- .../Poisson_surface_reconstruction_3/cgal_test_with_cmake | 3 +-- Polyhedron/demo/Polyhedron/cgal_test_with_cmake | 3 +-- Scripts/developer_scripts/create_cgal_test | 1 - .../test/Set_movable_separability_2/cgal_test_with_cmake | 3 +-- Surface_sweep_2/test/Surface_sweep_2/cgal_test_base | 3 +-- 9 files changed, 9 insertions(+), 19 deletions(-) diff --git a/Arrangement_on_surface_2/test/Arrangement_on_surface_2/cgal_test_with_cmake b/Arrangement_on_surface_2/test/Arrangement_on_surface_2/cgal_test_with_cmake index 0bc56422a00..7e1bec84175 100755 --- a/Arrangement_on_surface_2/test/Arrangement_on_surface_2/cgal_test_with_cmake +++ b/Arrangement_on_surface_2/test/Arrangement_on_surface_2/cgal_test_with_cmake @@ -129,16 +129,14 @@ configure() { echo "Configuring... " rm -rf CMakeCache.txt CMakeFiles/ - echo "cmake --no-warn-unused-cli -C"$INIT_FILE" "$CMAKE_GENERATOR" -DRUNNING_CGAL_AUTO_TEST=TRUE \ + echo "cmake --no-warn-unused-cli ${INIT_FILE:+"-C${INIT_FILE}"} "$CMAKE_GENERATOR" -DRUNNING_CGAL_AUTO_TEST=TRUE \ -DCGAL_DIR=\"$CGAL_DIR\" \ - -DINIT_FILE=\"$INIT_FILE" \ -DCGAL_CXX_FLAGS:STRING=\"$TESTSUITE_CXXFLAGS -I../../include\" \ -DCGAL_EXE_LINKER_FLAGS=\"$TESTSUITE_LDFLAGS\" \ -DCMAKE_BUILD_TYPE=NOTFOUND \ ." - if eval 'cmake --no-warn-unused-cli -C"$INIT_FILE" "$CMAKE_GENERATOR" -DRUNNING_CGAL_AUTO_TEST=TRUE \ + if eval 'cmake --no-warn-unused-cli ${INIT_FILE:+"-C${INIT_FILE}"} "$CMAKE_GENERATOR" -DRUNNING_CGAL_AUTO_TEST=TRUE \ -DCGAL_DIR="$CGAL_DIR" \ - -DINIT_FILE="$INIT_FILE" \ -DCGAL_CXX_FLAGS:STRING="$TESTSUITE_CXXFLAGS -I../../include" \ -DCGAL_EXE_LINKER_FLAGS="$TESTSUITE_LDFLAGS" \ -DCMAKE_BUILD_TYPE=NOTFOUND \ diff --git a/CGAL_ipelets/demo/CGAL_ipelets/cgal_test_with_cmake b/CGAL_ipelets/demo/CGAL_ipelets/cgal_test_with_cmake index fb097574953..7113a52ffb8 100755 --- a/CGAL_ipelets/demo/CGAL_ipelets/cgal_test_with_cmake +++ b/CGAL_ipelets/demo/CGAL_ipelets/cgal_test_with_cmake @@ -26,9 +26,8 @@ configure() { echo "Configuring... " - if eval 'cmake --no-warn-unused-cli -C"$INIT_FILE" "$CMAKE_GENERATOR" -DRUNNING_CGAL_AUTO_TEST=TRUE \ + if eval 'cmake --no-warn-unused-cli ${INIT_FILE:+"-C$INIT_FILE"} "$CMAKE_GENERATOR" -DRUNNING_CGAL_AUTO_TEST=TRUE \ -DCGAL_DIR="$CGAL_DIR" \ - -DINIT_FILE="$INIT_FILE" \ .' ; then echo " successful configuration" >> $ERRORFILE diff --git a/Minkowski_sum_2/test/Minkowski_sum_2/cgal_test_with_cmake b/Minkowski_sum_2/test/Minkowski_sum_2/cgal_test_with_cmake index 625bdffabbd..a69891eaba2 100755 --- a/Minkowski_sum_2/test/Minkowski_sum_2/cgal_test_with_cmake +++ b/Minkowski_sum_2/test/Minkowski_sum_2/cgal_test_with_cmake @@ -26,9 +26,8 @@ configure() { echo "Configuring... " - if eval 'cmake --no-warn-unused-cli -C"$INIT_FILE" "$CMAKE_GENERATOR" -DRUNNING_CGAL_AUTO_TEST=TRUE \ + if eval 'cmake --no-warn-unused-cli ${INIT_FILE:+"-C${INIT_FILE}"} "$CMAKE_GENERATOR" -DRUNNING_CGAL_AUTO_TEST=TRUE \ -DCGAL_DIR="$CGAL_DIR" \ - -DINIT_FILE="$INIT_FILE" \ .' ; then echo " successful configuration" >> $ERRORFILE diff --git a/Poisson_surface_reconstruction_3/examples/Poisson_surface_reconstruction_3/cgal_test_with_cmake b/Poisson_surface_reconstruction_3/examples/Poisson_surface_reconstruction_3/cgal_test_with_cmake index 8db1c729e60..f95394fe687 100755 --- a/Poisson_surface_reconstruction_3/examples/Poisson_surface_reconstruction_3/cgal_test_with_cmake +++ b/Poisson_surface_reconstruction_3/examples/Poisson_surface_reconstruction_3/cgal_test_with_cmake @@ -49,9 +49,8 @@ configure() { echo "Configuring... " - if eval 'cmake --no-warn-unused-cli -C"$INIT_FILE" "$CMAKE_GENERATOR" -DRUNNING_CGAL_AUTO_TEST=TRUE \ + if eval 'cmake --no-warn-unused-cli ${INIT_FILE:+"-C${INIT_FILE}"} "$CMAKE_GENERATOR" -DRUNNING_CGAL_AUTO_TEST=TRUE \ -DCGAL_DIR="$CGAL_DIR" \ - -DINIT_FILE="$INIT_FILE" \ .' ; then echo " successful configuration" >> $ERRORFILE diff --git a/Poisson_surface_reconstruction_3/test/Poisson_surface_reconstruction_3/cgal_test_with_cmake b/Poisson_surface_reconstruction_3/test/Poisson_surface_reconstruction_3/cgal_test_with_cmake index ad134d433a7..61ad14fd1d6 100755 --- a/Poisson_surface_reconstruction_3/test/Poisson_surface_reconstruction_3/cgal_test_with_cmake +++ b/Poisson_surface_reconstruction_3/test/Poisson_surface_reconstruction_3/cgal_test_with_cmake @@ -49,9 +49,8 @@ configure() { echo "Configuring... " - if eval 'cmake --no-warn-unused-cli -C"$INIT_FILE" "$CMAKE_GENERATOR" -DRUNNING_CGAL_AUTO_TEST=TRUE \ + if eval 'cmake --no-warn-unused-cli ${INIT_FILE:+"-C${INIT_FILE}"} "$CMAKE_GENERATOR" -DRUNNING_CGAL_AUTO_TEST=TRUE \ -DCGAL_DIR="$CGAL_DIR" \ - -DINIT_FILE="$INIT_FILE" \ .' ; then echo " successful configuration" >> $ERRORFILE diff --git a/Polyhedron/demo/Polyhedron/cgal_test_with_cmake b/Polyhedron/demo/Polyhedron/cgal_test_with_cmake index 5b177843da1..0b7ad915e26 100755 --- a/Polyhedron/demo/Polyhedron/cgal_test_with_cmake +++ b/Polyhedron/demo/Polyhedron/cgal_test_with_cmake @@ -26,9 +26,8 @@ configure() { echo "Configuring... " - if eval 'cmake --no-warn-unused-cli -C"$INIT_FILE" "$CMAKE_GENERATOR" -DRUNNING_CGAL_AUTO_TEST=TRUE \ + if eval 'cmake --no-warn-unused-cli ${INIT_FILE:+"-C${INIT_FILE}"} "$CMAKE_GENERATOR" -DRUNNING_CGAL_AUTO_TEST=TRUE \ -DCGAL_DIR="$CGAL_DIR" \ - -DINIT_FILE="$INIT_FILE" \ .' ; then echo " successful configuration" >> $ERRORFILE diff --git a/Scripts/developer_scripts/create_cgal_test b/Scripts/developer_scripts/create_cgal_test index 9141a0d52c5..9143b7fc295 100755 --- a/Scripts/developer_scripts/create_cgal_test +++ b/Scripts/developer_scripts/create_cgal_test @@ -82,7 +82,6 @@ configure() if eval 'cmake -C"\$INIT_FILE" "\$CMAKE_GENERATOR" -DRUNNING_CGAL_AUTO_TEST=TRUE \\ -DCGAL_DIR="\$CGAL_DIR" \\ - -DINIT_FILE="\$INIT_FILE" \\ --no-warn-unused-cli \\ .' ; then diff --git a/Set_movable_separability_2/test/Set_movable_separability_2/cgal_test_with_cmake b/Set_movable_separability_2/test/Set_movable_separability_2/cgal_test_with_cmake index 79534ffd81c..fc1dcde3929 100755 --- a/Set_movable_separability_2/test/Set_movable_separability_2/cgal_test_with_cmake +++ b/Set_movable_separability_2/test/Set_movable_separability_2/cgal_test_with_cmake @@ -26,9 +26,8 @@ configure() { echo "Configuring... " - if eval 'cmake -C"$INIT_FILE" "$CMAKE_GENERATOR" -DRUNNING_CGAL_AUTO_TEST=TRUE \ + if eval 'cmake ${INIT_FILE:+"-C${INIT_FILE}"} "$CMAKE_GENERATOR" -DRUNNING_CGAL_AUTO_TEST=TRUE \ -DCGAL_DIR="$CGAL_DIR" \ - -DINIT_FILE="$INIT_FILE" \ .' ; then echo " successful configuration" >> $ERRORFILE diff --git a/Surface_sweep_2/test/Surface_sweep_2/cgal_test_base b/Surface_sweep_2/test/Surface_sweep_2/cgal_test_base index 5671aa7a0c5..59f380da802 100755 --- a/Surface_sweep_2/test/Surface_sweep_2/cgal_test_base +++ b/Surface_sweep_2/test/Surface_sweep_2/cgal_test_base @@ -32,9 +32,8 @@ configure() rm CMakeCache.txt - if eval 'cmake --no-warn-unused-cli -C"$INIT_FILE" "$CMAKE_GENERATOR" -DRUNNING_CGAL_AUTO_TEST=TRUE \ + if eval 'cmake --no-warn-unused-cli ${INIT_FILE:+"-C${INIT_FILE}"} "$CMAKE_GENERATOR" -DRUNNING_CGAL_AUTO_TEST=TRUE \ -DCGAL_DIR="$CGAL_DIR" \ - -DINIT_FILE="$INIT_FILE" \ -DCGAL_CXX_FLAGS:STRING="$TESTSUITE_CXXFLAGS" \ -DCGAL_EXE_LINKER_FLAGS="$TESTSUITE_LDFLAGS" \ -DCMAKE_BUILD_TYPE=NOTFOUND \ From 3fc43f9e9f85b60f215795f27f76d66e78b652a1 Mon Sep 17 00:00:00 2001 From: Laurent Rineau Date: Tue, 4 Sep 2018 12:01:25 +0200 Subject: [PATCH 080/116] Fix for those scripts Use the `${INIT_FILE:+..}` syntax, for compatibility when `INIT_FILE` is unset or empty. --- Scripts/developer_scripts/autotest_cgal | 2 +- Scripts/developer_scripts/create_cgal_test | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Scripts/developer_scripts/autotest_cgal b/Scripts/developer_scripts/autotest_cgal index 462815bb483..848566200f7 100755 --- a/Scripts/developer_scripts/autotest_cgal +++ b/Scripts/developer_scripts/autotest_cgal @@ -525,7 +525,7 @@ export MAKE_CMD; export CGAL_BINARY_DIR; export CGAL_REFERENCE_CACHE_DIR; cd '${CGAL_BINARY_DIR}'; -cmake -C"${CGAL_REFERENCE_CACHE_DIR}/init.cmake" '${CMAKE_GENERATOR}' -DRUNNING_CGAL_AUTO_TEST=TRUE \\ +cmake \${INIT_FILE:+"-C\${INIT_FILE}"} '${CMAKE_GENERATOR}' -DRUNNING_CGAL_AUTO_TEST=TRUE \\ -DCGAL_REFERENCE_CACHE_DIR="\$CGAL_REFERENCE_CACHE_DIR" \\ VERBOSE=1 \\ ../../..; diff --git a/Scripts/developer_scripts/create_cgal_test b/Scripts/developer_scripts/create_cgal_test index 9143b7fc295..8f16fec5d70 100755 --- a/Scripts/developer_scripts/create_cgal_test +++ b/Scripts/developer_scripts/create_cgal_test @@ -74,20 +74,20 @@ cat << EOF EOF header "configure" -cat << EOF +cat << 'EOF' configure() { echo "Configuring... " - if eval 'cmake -C"\$INIT_FILE" "\$CMAKE_GENERATOR" -DRUNNING_CGAL_AUTO_TEST=TRUE \\ - -DCGAL_DIR="\$CGAL_DIR" \\ + if eval 'cmake ${INIT_FILE:+"-C${INIT_FILE}"} "$CMAKE_GENERATOR" -DRUNNING_CGAL_AUTO_TEST=TRUE \\ + -DCGAL_DIR="$CGAL_DIR" \\ --no-warn-unused-cli \\ .' ; then - echo " successful configuration" >> \$ERRORFILE + echo " successful configuration" >> $ERRORFILE else - echo " ERROR: configuration" >> \$ERRORFILE + echo " ERROR: configuration" >> $ERRORFILE fi } From 6a885796bc793360e2ce16d45cb6622a7bc9a59f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Loriot?= Date: Thu, 6 Sep 2018 16:44:53 +0200 Subject: [PATCH 081/116] do not test the whole mesh, only the current face --- .../include/CGAL/Polygon_mesh_processing/shape_predicates.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/shape_predicates.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/shape_predicates.h index a84f99aae69..c1f0b7c040b 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/shape_predicates.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/shape_predicates.h @@ -126,7 +126,7 @@ bool is_degenerate_triangle_face(typename boost::graph_traits::fac const TriangleMesh& tm, const NamedParameters& np) { - CGAL_precondition(CGAL::is_triangle_mesh(tm)); + CGAL_precondition(CGAL::is_triangle(halfedge(f, tm), tm)); using boost::get_param; using boost::choose_param; From e4ad5d96a7afd1f2cca3fbc7cb2661a1c1be7659 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Loriot?= Date: Thu, 6 Sep 2018 16:46:01 +0200 Subject: [PATCH 082/116] start adding support for open meshes --- .../CGAL/Polygon_mesh_processing/repair.h | 71 +++++++++++++++---- 1 file changed, 58 insertions(+), 13 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/repair.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/repair.h index 780e983b508..58c064e1bb3 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/repair.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/repair.h @@ -502,7 +502,7 @@ remove_a_border_edge(typename boost::graph_traits::edge_descriptor } template -std::size_t remove_degenerate_edges(const EdgeRange& edge_range, +bool remove_degenerate_edges(const EdgeRange& edge_range, TriangleMesh& tmesh, const NamedParameters& np) { @@ -525,6 +525,7 @@ std::size_t remove_degenerate_edges(const EdgeRange& edge_range, typedef typename GetGeomTraits::type Traits; std::size_t nb_deg_faces = 0; + bool all_removed=true; // collect edges of length 0 std::set degenerate_edges_to_remove; @@ -572,6 +573,36 @@ std::size_t remove_degenerate_edges(const EdgeRange& edge_range, continue; } } + else + { + halfedge_descriptor hd = halfedge(ed,tmesh); + // if both vertices are boundary vertices we can't do anything + bool impossible = false; + BOOST_FOREACH(halfedge_descriptor h, halfedges_around_target(hd, tmesh)) + { + if (is_border(h, tmesh)) + { + impossible = true; + break; + } + } + if (impossible) + { + BOOST_FOREACH(halfedge_descriptor h, halfedges_around_source(hd, tmesh)) + { + if (is_border(h, tmesh)) + { + impossible = true; + break; + } + } + if (impossible) + { + all_removed=false; + continue; + } + } + } // When the edge does not satisfy the link condition, it means that it cannot be // collapsed as is. In the following we assume that there is no topological issue @@ -781,11 +812,11 @@ std::size_t remove_degenerate_edges(const EdgeRange& edge_range, } } - return nb_deg_faces; + return all_removed; } template -std::size_t remove_degenerate_edges(const EdgeRange& edge_range, +bool remove_degenerate_edges(const EdgeRange& edge_range, TriangleMesh& tmesh) { return remove_degenerate_edges(edge_range, tmesh, parameters::all_default()); @@ -824,10 +855,10 @@ std::size_t remove_degenerate_edges(const EdgeRange& edge_range, // \todo the function might not be able to remove all degenerate faces. // We should probably do something with the return type. // -// \return number of removed degenerate faces +/// \return `true` if all degenerate faces were successfully removed, and `false` otherwise. template -std::size_t remove_degenerate_faces(TriangleMesh& tmesh, - const NamedParameters& np) +bool remove_degenerate_faces( TriangleMesh& tmesh, + const NamedParameters& np) { CGAL_assertion(CGAL::is_triangle_mesh(tmesh)); @@ -851,7 +882,7 @@ std::size_t remove_degenerate_faces(TriangleMesh& tmesh, typedef typename boost::property_traits::reference Point_ref; // First remove edges of length 0 - std::size_t nb_deg_faces = remove_degenerate_edges(edges(tmesh), tmesh, np); + bool all_removed = remove_degenerate_edges(edges(tmesh), tmesh, np); #ifdef CGAL_PMP_REMOVE_DEGENERATE_FACES_DEBUG { @@ -865,8 +896,20 @@ std::size_t remove_degenerate_faces(TriangleMesh& tmesh, // Then, remove triangles made of 3 collinear points std::set degenerate_face_set; degenerate_faces(tmesh, std::inserter(degenerate_face_set, degenerate_face_set.begin()), np); - - nb_deg_faces+=degenerate_face_set.size(); +// Ignore faces with null edges + if (!all_removed) + { + BOOST_FOREACH(edge_descriptor ed, edges(tmesh)) + { + if ( traits.equal_3_object()(get(vpmap, target(ed, tmesh)), get(vpmap, source(ed, tmesh))) ) + { + halfedge_descriptor h = halfedge(ed, tmesh); + if (!is_border(h, tmesh)) degenerate_face_set.erase(face(h, tmesh)); + h=opposite(h, tmesh); + if (!is_border(h, tmesh)) degenerate_face_set.erase(face(h, tmesh)); + } + } + } // first remove degree 3 vertices that are part of a cap // (only the vertex in the middle of the opposite edge) @@ -879,7 +922,7 @@ std::size_t remove_degenerate_faces(TriangleMesh& tmesh, BOOST_FOREACH(halfedge_descriptor hd, halfedges_around_face(halfedge(fd, tmesh), tmesh)) { vertex_descriptor vd = target(hd, tmesh); - if (degree(vd, tmesh) == 3) + if (degree(vd, tmesh) == 3 && is_border(vd, tmesh)==GT::null_halfedge()) { vertices_to_remove.insert(vd); break; @@ -987,15 +1030,17 @@ std::size_t remove_degenerate_faces(TriangleMesh& tmesh, { Euler::flip_edge(edge_to_flip, tmesh); } + else + { + all_removed=false; #ifdef CGAL_PMP_REMOVE_DEGENERATE_FACES_DEBUG - else{ std::cout << " WARNING: flip is not possible\n"; // \todo Let p and q be the vertices opposite to `edge_to_flip`, and let // r be the vertex of `edge_to_flip` that is the furthest away from // the edge `pq`. In that case I think we should remove all the triangles // so that the triangle pqr is in the mesh. - } #endif + } } } else @@ -1410,7 +1455,7 @@ std::size_t remove_degenerate_faces(TriangleMesh& tmesh, } } - return nb_deg_faces; + return all_removed; } template From 7017a26d35a9194b33affc5ce259310519c63ef1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Loriot?= Date: Thu, 6 Sep 2018 16:48:25 +0200 Subject: [PATCH 083/116] update conditions --- .../CGAL/Polygon_mesh_processing/repair.h | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/repair.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/repair.h index 58c064e1bb3..917eac4b22e 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/repair.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/repair.h @@ -42,7 +42,7 @@ #include #include -#include +#include #ifdef CGAL_PMP_REMOVE_DEGENERATE_FACES_DEBUG #include @@ -214,8 +214,8 @@ template OutputIterator degenerate_edges(const EdgeRange& edges, const TriangleMesh& tm, OutputIterator out, - typename boost::disable_if_c< - CGAL::is_iterator::value + typename boost::enable_if< + typename boost::has_range_iterator >::type* = 0) { return degenerate_edges(edges, tm, out, CGAL::parameters::all_default()); @@ -231,9 +231,9 @@ OutputIterator degenerate_edges(const TriangleMesh& tm, OutputIterator out, const NamedParameters& np #ifndef DOXYGEN_RUNNING - , typename boost::enable_if_c< - CGAL::is_iterator::value - >::type* = 0 + , typename boost::disable_if< + boost::has_range_iterator + >::type* = 0 #endif ) { @@ -291,9 +291,9 @@ template OutputIterator degenerate_faces(const FaceRange& faces, const TriangleMesh& tm, OutputIterator out, - typename boost::disable_if_c< - CGAL::is_iterator::value - >::type* = 0) + typename boost::enable_if< + boost::has_range_iterator + >::type* = 0) { return degenerate_faces(faces, tm, out, CGAL::parameters::all_default()); } @@ -308,9 +308,9 @@ OutputIterator degenerate_faces(const TriangleMesh& tm, OutputIterator out, const NamedParameters& np #ifndef DOXYGEN_RUNNING - , typename boost::enable_if_c< - CGAL::is_iterator::value - >::type* = 0 + , typename boost::disable_if< + boost::has_range_iterator + >::type* = 0 #endif ) { From 359efe2c96827362eda0351867f5e6cc99bf57b8 Mon Sep 17 00:00:00 2001 From: Maxime Gimeno Date: Thu, 13 Sep 2018 11:33:40 +0200 Subject: [PATCH 084/116] Add -DTESTSUITE=ON to the no-public config --- Scripts/developer_scripts/create_new_release | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Scripts/developer_scripts/create_new_release b/Scripts/developer_scripts/create_new_release index d6f52ce39ad..7c6f04e6031 100755 --- a/Scripts/developer_scripts/create_new_release +++ b/Scripts/developer_scripts/create_new_release @@ -251,7 +251,7 @@ function cleanup() { trap cleanup EXIT # Create the release -cmake -DPUBLIC=NO -DDESTINATION="${DESTINATION}" -DCGAL_VERSION="${release_version}" -DCGAL_VERSION_NR="${release_number}" -DVERBOSE="${VERBOSE}" -P ${SOURCES_DIR}/Scripts/developer_scripts/cgal_create_release_with_cmake.cmake +cmake -DPUBLIC=NO -DTESTSUITE=ON -DDESTINATION="${DESTINATION}" -DCGAL_VERSION="${release_version}" -DCGAL_VERSION_NR="${release_number}" -DVERBOSE="${VERBOSE}" -P ${SOURCES_DIR}/Scripts/developer_scripts/cgal_create_release_with_cmake.cmake pushd "${DESTINATION}/${release_name}" sed -i -e "s/define CGAL_GIT_HASH .*/define CGAL_GIT_HASH $CGAL_GIT_HASH/" include/CGAL/version.h cd .. From 63cb481dd2e2f1cb01c95424fed6dfd5d40750f6 Mon Sep 17 00:00:00 2001 From: Maxime Gimeno Date: Mon, 17 Sep 2018 12:14:45 +0200 Subject: [PATCH 085/116] Change header guard to something not forbidden. --- .../demo/Optimal_transportation_reconstruction_2/random.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Optimal_transportation_reconstruction_2/demo/Optimal_transportation_reconstruction_2/random.h b/Optimal_transportation_reconstruction_2/demo/Optimal_transportation_reconstruction_2/random.h index b8886e54b7c..2b228c5d12e 100644 --- a/Optimal_transportation_reconstruction_2/demo/Optimal_transportation_reconstruction_2/random.h +++ b/Optimal_transportation_reconstruction_2/demo/Optimal_transportation_reconstruction_2/random.h @@ -1,5 +1,5 @@ -#ifndef _RANDOM_ -#define _RANDOM_ 1 +#ifndef CGAL_OTR_RANDOM_H +#define CGAL_OTR_RANDOM_H 1 inline double random_double(const double min, const double max) From 29a086855ab2773aa541a6c38c119e931a87729f Mon Sep 17 00:00:00 2001 From: Maxime Gimeno Date: Mon, 17 Sep 2018 14:21:04 +0200 Subject: [PATCH 086/116] Don't use \\ after 'EOF' --- Scripts/developer_scripts/create_cgal_test | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Scripts/developer_scripts/create_cgal_test b/Scripts/developer_scripts/create_cgal_test index 8f16fec5d70..48296e17658 100755 --- a/Scripts/developer_scripts/create_cgal_test +++ b/Scripts/developer_scripts/create_cgal_test @@ -80,9 +80,9 @@ configure() { echo "Configuring... " - if eval 'cmake ${INIT_FILE:+"-C${INIT_FILE}"} "$CMAKE_GENERATOR" -DRUNNING_CGAL_AUTO_TEST=TRUE \\ - -DCGAL_DIR="$CGAL_DIR" \\ - --no-warn-unused-cli \\ + if eval 'cmake ${INIT_FILE:+"-C${INIT_FILE}"} "$CMAKE_GENERATOR" -DRUNNING_CGAL_AUTO_TEST=TRUE \ + -DCGAL_DIR="$CGAL_DIR" \ + --no-warn-unused-cli \ .' ; then echo " successful configuration" >> $ERRORFILE From 4d1c7ea7b099272ac05a08ed4c8bb64905763b10 Mon Sep 17 00:00:00 2001 From: Maxime Gimeno Date: Mon, 17 Sep 2018 14:22:19 +0200 Subject: [PATCH 087/116] set -- -camke to replace argument in test for Arrangement_on_surface_2 --- .../test/Arrangement_on_surface_2/cgal_test_with_cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Arrangement_on_surface_2/test/Arrangement_on_surface_2/cgal_test_with_cmake b/Arrangement_on_surface_2/test/Arrangement_on_surface_2/cgal_test_with_cmake index 7e1bec84175..33cd53adeea 100755 --- a/Arrangement_on_surface_2/test/Arrangement_on_surface_2/cgal_test_with_cmake +++ b/Arrangement_on_surface_2/test/Arrangement_on_surface_2/cgal_test_with_cmake @@ -13,7 +13,7 @@ # SET PARAMETERS FOR cgal_test - +set -- -cmake ERRORFILE=error.txt DO_RUN=y if [ -z "${MAKE_CMD}" ]; then From fcb37f1856b6d54c50d7ed2bf6d46318fde8a507 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mael=20Rouxel-Labb=C3=A9?= Date: Mon, 17 Sep 2018 15:14:18 +0200 Subject: [PATCH 088/116] Removed stowaway macro --- .../doc/Polygon_mesh_processing/Polygon_mesh_processing.txt | 2 -- 1 file changed, 2 deletions(-) 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 261016aacd6..e09b5620d85 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 @@ -574,8 +574,6 @@ is output. \cgalExample{Polygon_mesh_processing/remove_degeneracies_example.cpp} \endif -\endcond - \subsection PMPManifoldness Polygon Mesh Manifoldness Non-manifold vertices can be detected using the function `CGAL::Polygon_mesh_processing::is_non_manifold_vertex()`. From f9c9cfa40bdad5c5f250e4d600c43aa2f0d50021 Mon Sep 17 00:00:00 2001 From: Laurent Rineau Date: Mon, 17 Sep 2018 15:44:01 +0200 Subject: [PATCH 089/116] `&*handle`, if handle is null, is undefined-behavior This patch fixes an undefined behavior: when one want to convert a handle to a point, we used `&*handle` a lot. But dereferencing the default-constructed (null) handle, is undefined-behavior. The patch calls `operator->` of the handle class, instead. --- TDS_3/include/CGAL/Triangulation_ds_cell_base_3.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TDS_3/include/CGAL/Triangulation_ds_cell_base_3.h b/TDS_3/include/CGAL/Triangulation_ds_cell_base_3.h index b8046fa77af..38726c9fa30 100644 --- a/TDS_3/include/CGAL/Triangulation_ds_cell_base_3.h +++ b/TDS_3/include/CGAL/Triangulation_ds_cell_base_3.h @@ -164,7 +164,7 @@ public: void set_neighbor(int i, Cell_handle n) { CGAL_triangulation_precondition( i >= 0 && i <= 3); - CGAL_triangulation_precondition( this != &*n ); + CGAL_triangulation_precondition( this != n.operator->() ); N[i] = n; } From d5dbdcba25fb58202273575c157260c25cfea0bf Mon Sep 17 00:00:00 2001 From: Maxime Gimeno Date: Tue, 18 Sep 2018 10:20:57 +0200 Subject: [PATCH 090/116] Replace random.h by CGAL::Random --- .../random.h | 26 ------------------- .../scene.h | 18 ++++++++++--- 2 files changed, 14 insertions(+), 30 deletions(-) delete mode 100644 Optimal_transportation_reconstruction_2/demo/Optimal_transportation_reconstruction_2/random.h diff --git a/Optimal_transportation_reconstruction_2/demo/Optimal_transportation_reconstruction_2/random.h b/Optimal_transportation_reconstruction_2/demo/Optimal_transportation_reconstruction_2/random.h deleted file mode 100644 index 2b228c5d12e..00000000000 --- a/Optimal_transportation_reconstruction_2/demo/Optimal_transportation_reconstruction_2/random.h +++ /dev/null @@ -1,26 +0,0 @@ -#ifndef CGAL_OTR_RANDOM_H -#define CGAL_OTR_RANDOM_H 1 - -inline -double random_double(const double min, const double max) -{ - double range = max - min; - return min + (double(rand()) / double(RAND_MAX)) * range; -} - -inline -int random_int(const int min, const int max) -{ - int range = max - min; - return min + int((double(rand())/double(RAND_MAX)) * range); -} - -template -Vector random_vec(const double scale) -{ - double dx = random_double(-scale, scale); - double dy = random_double(-scale, scale); - return Vector(dx, dy); -} - -#endif diff --git a/Optimal_transportation_reconstruction_2/demo/Optimal_transportation_reconstruction_2/scene.h b/Optimal_transportation_reconstruction_2/demo/Optimal_transportation_reconstruction_2/scene.h index 4a9d9c8f73b..5c2444aefc9 100644 --- a/Optimal_transportation_reconstruction_2/demo/Optimal_transportation_reconstruction_2/scene.h +++ b/Optimal_transportation_reconstruction_2/demo/Optimal_transportation_reconstruction_2/scene.h @@ -18,7 +18,7 @@ #define cimg_display 0 // To avoid X11 or Windows-GDI dependency #include #endif -#include "random.h" +#include #include // std::pair #include #include @@ -104,7 +104,17 @@ private: double m_bbox_x; double m_bbox_y; double m_bbox_size; - + + //Random + CGAL::Random random; + + template + Vector random_vec(const double scale) + { + double dx = random.get_double(-scale, scale); + double dy = random.get_double(-scale, scale); + return Vector(dx, dy); + } public: Scene() { @@ -523,7 +533,7 @@ public: std::vector::iterator it; for (it = m_samples.begin(); it != m_samples.end(); it++) { - const double rd = random_double(0.0, 1.0); + const double rd = random.get_double(0.0, 1.0); if (rd >= percentage) selected.push_back(*it); } @@ -542,7 +552,7 @@ public: Sample_& s = *it; samples.push_back(&s); - FT rv = random_double(0.0, 1.0); + FT rv = random.get_double(0.0, 1.0); if (rv <= percentage) vertices.push_back(&s); } From 70f3f79c2e60133328b3baae53a78034359ddd02 Mon Sep 17 00:00:00 2001 From: Andreas Fabri Date: Thu, 20 Sep 2018 16:52:14 +0200 Subject: [PATCH 091/116] Workaround: Do not write typename for VC++ --- .../include/CGAL/regularize_planes.h | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/Point_set_shape_detection_3/include/CGAL/regularize_planes.h b/Point_set_shape_detection_3/include/CGAL/regularize_planes.h index 14cb4907b22..ec26cf59017 100644 --- a/Point_set_shape_detection_3/include/CGAL/regularize_planes.h +++ b/Point_set_shape_detection_3/include/CGAL/regularize_planes.h @@ -781,6 +781,11 @@ void regularize_planes (const PointRange& points, /// \cond SKIP_IN_MANUAL +#ifdef _MSC_VER +#define CGAL_TYPENAME_FOR_MSC +#else +#define CGAL_TYPENAME_FOR_MSC typename +#endif // This variant deduces the kernel from the point property map. template ::value_type>::Kernel::Vector_3 symmetry_direction - = typename Kernel_traits + = CGAL_TYPENAME_FOR_MSC Kernel_traits ::value_type>::Kernel::Vector_3 - (typename Kernel_traits + ( + CGAL_TYPENAME_FOR_MSC Kernel_traits ::value_type>::Kernel::FT(0.), - typename Kernel_traits + CGAL_TYPENAME_FOR_MSC Kernel_traits ::value_type>::Kernel::FT(0.), - typename Kernel_traits + CGAL_TYPENAME_FOR_MSC Kernel_traits ::value_type>::Kernel::FT(1.))) { @@ -824,6 +830,8 @@ void regularize_planes (const PointRange& points, tolerance_angle, tolerance_coplanarity, symmetry_direction); } +#undef CGAL_TYPENAME_FOR_MSC + /// \endcond From 16a8c93db01551dc29644d42699c42d0e90acf86 Mon Sep 17 00:00:00 2001 From: Iordan Iordanov Date: Mon, 24 Sep 2018 14:33:07 +0200 Subject: [PATCH 092/116] Small bugfix (citation error in Design and Implementation History) --- Circular_kernel_2/doc/Circular_kernel_2/Circular_kernel_2.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Circular_kernel_2/doc/Circular_kernel_2/Circular_kernel_2.txt b/Circular_kernel_2/doc/Circular_kernel_2/Circular_kernel_2.txt index 288a9337069..bc2674a7f71 100644 --- a/Circular_kernel_2/doc/Circular_kernel_2/Circular_kernel_2.txt +++ b/Circular_kernel_2/doc/Circular_kernel_2/Circular_kernel_2.txt @@ -73,7 +73,7 @@ The following example shows how to use a functor of the kernel. The first pieces of prototype code were comparisons of algebraic numbers of degree 2, written by Olivier Devillers -\cgalCite{cgal:dfmt-amafe-00},cgal:dfmt-amafe-02. +\cgalCite{cgal:dfmt-amafe-00},\cgalCite{cgal:dfmt-amafe-02}. Some work was then done in the direction of a "kernel" for \cgal.\cgalFootnote{Monique Teillaud, First Prototype of a \cgal Geometric Kernel with Circular Arcs, Technical Report ECG-TR-182203-01, 2002 From 0e415ac4781e0d1bc49c6a1714be8b79e5f4963d Mon Sep 17 00:00:00 2001 From: Simon Giraudot Date: Tue, 25 Sep 2018 12:27:34 +0200 Subject: [PATCH 093/116] Bugfix: don't use temp refs in parallel code --- .../Plugins/Classification/Cluster_classification.cpp | 7 ++++++- .../Classification/Point_set_item_classification.cpp | 7 ++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/Polyhedron/demo/Polyhedron/Plugins/Classification/Cluster_classification.cpp b/Polyhedron/demo/Polyhedron/Plugins/Classification/Cluster_classification.cpp index 5aca25e2ed1..d8e20fd778f 100644 --- a/Polyhedron/demo/Polyhedron/Plugins/Classification/Cluster_classification.cpp +++ b/Polyhedron/demo/Polyhedron/Plugins/Classification/Cluster_classification.cpp @@ -541,8 +541,13 @@ void Cluster_classification::compute_features (std::size_t nb_scales) std::cerr << "Computing pointwise features with " << nb_scales << " scale(s)" << std::endl; m_features.clear(); + Point_set::Vector_map normal_map; bool normals = m_points->point_set()->has_normal_map(); + if (normals) + normal_map = m_points->point_set()->normal_map(); + bool colors = (m_color != Point_set::Property_map()); + Point_set::Property_map echo_map; bool echo; boost::tie (echo_map, echo) = m_points->point_set()->template property_map("echo"); @@ -562,7 +567,7 @@ void Cluster_classification::compute_features (std::size_t nb_scales) generator.generate_point_based_features(pointwise_features); if (normals) - generator.generate_normal_based_features (pointwise_features, m_points->point_set()->normal_map()); + generator.generate_normal_based_features (pointwise_features, normal_map); if (colors) generator.generate_color_based_features (pointwise_features, m_color); if (echo) diff --git a/Polyhedron/demo/Polyhedron/Plugins/Classification/Point_set_item_classification.cpp b/Polyhedron/demo/Polyhedron/Plugins/Classification/Point_set_item_classification.cpp index 69e5451bbbd..1096fe5d067 100644 --- a/Polyhedron/demo/Polyhedron/Plugins/Classification/Point_set_item_classification.cpp +++ b/Polyhedron/demo/Polyhedron/Plugins/Classification/Point_set_item_classification.cpp @@ -442,8 +442,13 @@ void Point_set_item_classification::compute_features (std::size_t nb_scales) std::cerr << "Computing features with " << nb_scales << " scale(s)" << std::endl; m_features.clear(); + Point_set::Vector_map normal_map; bool normals = m_points->point_set()->has_normal_map(); + if (normals) + normal_map = m_points->point_set()->normal_map(); + bool colors = (m_color != Point_set::Property_map()); + Point_set::Property_map echo_map; bool echo; boost::tie (echo_map, echo) = m_points->point_set()->template property_map("echo"); @@ -461,7 +466,7 @@ void Point_set_item_classification::compute_features (std::size_t nb_scales) m_generator->generate_point_based_features(m_features); if (normals) - m_generator->generate_normal_based_features (m_features, m_points->point_set()->normal_map()); + m_generator->generate_normal_based_features (m_features, normal_map); if (colors) m_generator->generate_color_based_features (m_features, m_color); if (echo) From f1821f2101f81b15e8cd41441c0ffabcd4f9d148 Mon Sep 17 00:00:00 2001 From: Laurent Rineau Date: Tue, 25 Sep 2018 16:21:58 +0200 Subject: [PATCH 094/116] Workaround a misscompilation bug with Intel Compiler 2019 --- Number_types/include/CGAL/Mpzf.h | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Number_types/include/CGAL/Mpzf.h b/Number_types/include/CGAL/Mpzf.h index 348cb8c81ba..bcf2c480c87 100644 --- a/Number_types/include/CGAL/Mpzf.h +++ b/Number_types/include/CGAL/Mpzf.h @@ -302,7 +302,13 @@ struct Mpzf { data()[-1] = mini; } void clear(){ - while(*--data()==0); // in case we skipped final zeroes + // while(*--data()==0); + // This line gave a misscompilation by Intel Compiler 2019 + // (19.0.0.117). I replaced it by the following two lines: + // -- Laurent Rineau, sept. 2018 + --data(); + while(*data()==0) { --data(); } // in case we skipped final zeroes + #ifdef CGAL_MPZF_USE_CACHE if (data() == cache) return; #endif From fc957453100abffb0eb96cf74346cab9a4ea1a57 Mon Sep 17 00:00:00 2001 From: Maxime Gimeno Date: Fri, 28 Sep 2018 16:14:52 +0200 Subject: [PATCH 095/116] fix group bboxes --- Polyhedron/demo/Polyhedron/Scene_group_item.cpp | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/Polyhedron/demo/Polyhedron/Scene_group_item.cpp b/Polyhedron/demo/Polyhedron/Scene_group_item.cpp index 0a3c31f6653..ce41b1615b4 100644 --- a/Polyhedron/demo/Polyhedron/Scene_group_item.cpp +++ b/Polyhedron/demo/Polyhedron/Scene_group_item.cpp @@ -31,7 +31,21 @@ bool Scene_group_item::isEmpty() const { Scene_group_item::Bbox Scene_group_item::bbox() const { - return Bbox(0, 0, 0, 0, 0,0); + Scene_item* first_non_empty = nullptr; + Q_FOREACH(Scene_interface::Item_id id, children) + if(!getChild(id)->isEmpty()) + { + first_non_empty = getChild(id); + } + + if(first_non_empty) + { + Bbox b =first_non_empty->bbox(); + Q_FOREACH(Scene_interface::Item_id id, children) + b+=getChild(id)->bbox(); + return b; + } + return Bbox(0,0,0,0,0,0); } From bfb82e1e696b7de484a72446a13740599271548e Mon Sep 17 00:00:00 2001 From: Maxime Gimeno Date: Fri, 28 Sep 2018 16:31:48 +0200 Subject: [PATCH 096/116] Fix PS selection --- .../Polyhedron/Plugins/Point_set/Point_set_selection_plugin.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Polyhedron/demo/Polyhedron/Plugins/Point_set/Point_set_selection_plugin.cpp b/Polyhedron/demo/Polyhedron/Plugins/Point_set/Point_set_selection_plugin.cpp index 1d8dd937ef2..f405bc5f106 100644 --- a/Polyhedron/demo/Polyhedron/Plugins/Point_set/Point_set_selection_plugin.cpp +++ b/Polyhedron/demo/Polyhedron/Plugins/Point_set/Point_set_selection_plugin.cpp @@ -652,12 +652,12 @@ protected: shift_pressing = modifiers.testFlag(Qt::ShiftModifier); ctrl_pressing = modifiers.testFlag(Qt::ControlModifier); - background = static_cast(*CGAL::QGLViewer::QGLViewerPool().begin())->grabFramebuffer(); } // mouse events if(shift_pressing && event->type() == QEvent::MouseButtonPress) { + background = static_cast(*CGAL::QGLViewer::QGLViewerPool().begin())->grabFramebuffer(); QMouseEvent *mouseEvent = static_cast(event); // Start selection if (mouseEvent->button() == Qt::LeftButton) From ad1a1b3321133a0a630b7c86714f2340cdae6131 Mon Sep 17 00:00:00 2001 From: Maxime Gimeno Date: Fri, 28 Sep 2018 16:39:53 +0200 Subject: [PATCH 097/116] fix contextmenu after estimate normals --- .../Plugins/Point_set/Point_set_normal_estimation_plugin.cpp | 2 +- Polyhedron/demo/Polyhedron/Scene_item.cpp | 5 +++++ Three/include/CGAL/Three/Scene_item.h | 3 +++ 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/Polyhedron/demo/Polyhedron/Plugins/Point_set/Point_set_normal_estimation_plugin.cpp b/Polyhedron/demo/Polyhedron/Plugins/Point_set/Point_set_normal_estimation_plugin.cpp index 398f08f707d..09f30aa91a0 100644 --- a/Polyhedron/demo/Polyhedron/Plugins/Point_set/Point_set_normal_estimation_plugin.cpp +++ b/Polyhedron/demo/Polyhedron/Plugins/Point_set/Point_set_normal_estimation_plugin.cpp @@ -275,7 +275,7 @@ void Polyhedron_demo_point_set_normal_estimation_plugin::on_actionNormalEstimati << (memory>>20) << " Mb allocated" << std::endl; } - + item->resetMenu(); item->setRenderingMode(PointsPlusNormals); //*************************************** diff --git a/Polyhedron/demo/Polyhedron/Scene_item.cpp b/Polyhedron/demo/Polyhedron/Scene_item.cpp index fb69699861a..99e3ec5e3ec 100644 --- a/Polyhedron/demo/Polyhedron/Scene_item.cpp +++ b/Polyhedron/demo/Polyhedron/Scene_item.cpp @@ -135,6 +135,11 @@ QMenu* CGAL::Three::Scene_item::contextMenu() return defaultContextMenu; } +void CGAL::Three::Scene_item::resetMenu() +{ + delete defaultContextMenu; + defaultContextMenu = nullptr; +} CGAL::Three::Scene_group_item* CGAL::Three::Scene_item::parentGroup() const { return parent_group; } diff --git a/Three/include/CGAL/Three/Scene_item.h b/Three/include/CGAL/Three/Scene_item.h index 07470eee936..1b00fea5fc4 100644 --- a/Three/include/CGAL/Three/Scene_item.h +++ b/Three/include/CGAL/Three/Scene_item.h @@ -246,6 +246,9 @@ public: //! int getId()const; + //! invalidates the context menu. Call it when supportsRenderingMode() changes, + //! for example. + void resetMenu(); //!Handles key press events. virtual bool keyPressEvent(QKeyEvent*){return false;} From f460c872944024c75d12535477286d565642803f Mon Sep 17 00:00:00 2001 From: Maxime Gimeno Date: Fri, 28 Sep 2018 16:44:09 +0200 Subject: [PATCH 098/116] Makes ShadedPoints the default RM for points with normals. --- .../Point_set/Point_set_normal_estimation_plugin.cpp | 2 +- .../demo/Polyhedron/Scene_points_with_normal_item.cpp | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Polyhedron/demo/Polyhedron/Plugins/Point_set/Point_set_normal_estimation_plugin.cpp b/Polyhedron/demo/Polyhedron/Plugins/Point_set/Point_set_normal_estimation_plugin.cpp index 09f30aa91a0..ab53c6c887b 100644 --- a/Polyhedron/demo/Polyhedron/Plugins/Point_set/Point_set_normal_estimation_plugin.cpp +++ b/Polyhedron/demo/Polyhedron/Plugins/Point_set/Point_set_normal_estimation_plugin.cpp @@ -276,7 +276,7 @@ void Polyhedron_demo_point_set_normal_estimation_plugin::on_actionNormalEstimati << std::endl; } item->resetMenu(); - item->setRenderingMode(PointsPlusNormals); + item->setRenderingMode(ShadedPoints); //*************************************** // normal orientation diff --git a/Polyhedron/demo/Polyhedron/Scene_points_with_normal_item.cpp b/Polyhedron/demo/Polyhedron/Scene_points_with_normal_item.cpp index cf53bbbb9eb..b2f92df5055 100644 --- a/Polyhedron/demo/Polyhedron/Scene_points_with_normal_item.cpp +++ b/Polyhedron/demo/Polyhedron/Scene_points_with_normal_item.cpp @@ -231,7 +231,7 @@ Scene_points_with_normal_item::Scene_points_with_normal_item(const Scene_points_ d = new Scene_points_with_normal_item_priv(toCopy, this); if (has_normals()) { - setRenderingMode(PointsPlusNormals); + setRenderingMode(ShadedPoints); is_selected = true; } else @@ -254,7 +254,7 @@ Scene_points_with_normal_item::Scene_points_with_normal_item(const SMesh& input_ // Converts Polyhedron vertices to point set. // Computes vertices normal from connectivity. d = new Scene_points_with_normal_item_priv(input_mesh, this); - setRenderingMode(PointsPlusNormals); + setRenderingMode(ShadedPoints); is_selected = true; if(d->m_points->number_of_points() < 30 ) d->point_Slider->setValue(5); @@ -269,7 +269,7 @@ Scene_points_with_normal_item::Scene_points_with_normal_item(const Polyhedron& i // Converts Polyhedron vertices to point set. // Computes vertices normal from connectivity. d = new Scene_points_with_normal_item_priv(input_mesh, this); - setRenderingMode(PointsPlusNormals); + setRenderingMode(ShadedPoints); is_selected = true; if(d->m_points->number_of_points() < 30 ) d->point_Slider->setValue(5); @@ -616,7 +616,7 @@ bool Scene_points_with_normal_item::read_las_point_set(std::istream& stream) std::cerr << d->m_points->info(); if (d->m_points->has_normal_map()) - setRenderingMode(PointsPlusNormals); + setRenderingMode(ShadedPoints); if (d->m_points->check_colors()) std::cerr << "-> Point set has colors" << std::endl; @@ -654,7 +654,7 @@ bool Scene_points_with_normal_item::read_ply_point_set(std::istream& stream) std::cerr << d->m_points->info(); if (d->m_points->has_normal_map()) - setRenderingMode(PointsPlusNormals); + setRenderingMode(ShadedPoints); if (d->m_points->check_colors()) std::cerr << "-> Point set has colors" << std::endl; From 9b48c082ea23ea6ce4e2cc93daa4e725a04b1d22 Mon Sep 17 00:00:00 2001 From: Maxime Gimeno Date: Fri, 28 Sep 2018 16:59:37 +0200 Subject: [PATCH 099/116] Fix advance_reconstruction polyhedorn mode --- .../Plugins/Point_set/Surface_reconstruction_plugin.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Polyhedron/demo/Polyhedron/Plugins/Point_set/Surface_reconstruction_plugin.cpp b/Polyhedron/demo/Polyhedron/Plugins/Point_set/Surface_reconstruction_plugin.cpp index 8b62dbf4cc1..78639ba1c8c 100644 --- a/Polyhedron/demo/Polyhedron/Plugins/Point_set/Surface_reconstruction_plugin.cpp +++ b/Polyhedron/demo/Polyhedron/Plugins/Point_set/Surface_reconstruction_plugin.cpp @@ -893,6 +893,7 @@ private: reco_item->setName(tr("%1 (RANSAC-based reconstruction)").arg(scene->item(index)->name())); reco_item->setColor(Qt::magenta); reco_item->setRenderingMode(FlatPlusEdges); + reco_item->invalidateOpenGLBuffers(); scene->addItem(reco_item); } else @@ -1090,6 +1091,7 @@ void Polyhedron_demo_surface_reconstruction_plugin::automatic_reconstruction reco_item->setName(tr("%1 (advancing front)").arg(scene->item(index)->name())); reco_item->setColor(Qt::lightGray); reco_item->setRenderingMode(FlatPlusEdges); + reco_item->invalidateOpenGLBuffers(); scene->addItem(reco_item); } else @@ -1122,6 +1124,7 @@ void Polyhedron_demo_surface_reconstruction_plugin::automatic_reconstruction reco_item->setColor(Qt::lightGray); reco_item->setRenderingMode(FlatPlusEdges); scene->addItem(reco_item); + reco_item->invalidateOpenGLBuffers(); } else { @@ -1173,6 +1176,7 @@ void Polyhedron_demo_surface_reconstruction_plugin::automatic_reconstruction Scene_polyhedron_item* reco_item = new Scene_polyhedron_item(pRemesh); reco_item->setName(tr("%1 (poisson)").arg(pts_item->name())); reco_item->setColor(Qt::lightGray); + reco_item->invalidateOpenGLBuffers(); scene->addItem(reco_item); } else if(smRemesh) @@ -1232,6 +1236,7 @@ void Polyhedron_demo_surface_reconstruction_plugin::advancing_front_reconstructi reco_item->setName(tr("%1 (advancing front)").arg(scene->item(index)->name())); reco_item->setColor(Qt::lightGray); reco_item->setRenderingMode(FlatPlusEdges); + reco_item->invalidateOpenGLBuffers(); scene->addItem(reco_item); } else From a88438e07da6629897b49100d5a3a2bdae046fb9 Mon Sep 17 00:00:00 2001 From: Andreas Fabri Date: Sat, 29 Sep 2018 08:22:42 +0200 Subject: [PATCH 100/116] Fix so that static filters are used --- .../include/CGAL/Convex_hull_traits_3.h | 47 ++++--------------- .../CGAL/Extreme_points_traits_adapter_3.h | 4 +- 2 files changed, 10 insertions(+), 41 deletions(-) diff --git a/Convex_hull_3/include/CGAL/Convex_hull_traits_3.h b/Convex_hull_3/include/CGAL/Convex_hull_traits_3.h index 0a008157691..699155f983d 100644 --- a/Convex_hull_3/include/CGAL/Convex_hull_traits_3.h +++ b/Convex_hull_3/include/CGAL/Convex_hull_traits_3.h @@ -147,15 +147,8 @@ public: const Point_3& hp = h.p(); const Point_3& hq = h.q(); const Point_3& hr = h.r(); - //typename OldK::Less_signed_distance_to_plane_3 - // less_signed_distance_to_plane_3; - // return less_signed_distance_to_plane_3(hp, hq, hr, p, q); - return has_smaller_signed_dist_to_planeC3(hp.x(), hp.y(), hp.z(), - hq.x(), hq.y(), hq.z(), - hr.x(), hr.y(), hr.z(), - p.x(), p.y(), p.z(), - q.x(), q.y(), q.z()); - + typename K::Less_signed_distance_to_plane_3 less_signed_distance_to_plane_3; + return less_signed_distance_to_plane_3(hp, hq, hr, p, q); } }; @@ -164,35 +157,10 @@ struct GT3_for_CH3 { typedef typename GT::Point_3 Point_2; }; -template -struct Convex_hull_traits_base_3 { - typedef Point_triple_has_on_positive_side_3 Has_on_positive_side_3; - - typedef Point_triple_less_signed_distance_to_plane_3 - Less_signed_distance_to_plane_3; -}; - -template -struct Convex_hull_traits_base_3{ - typedef Filtered_predicate< - Point_triple_has_on_positive_side_3< typename R_::Exact_kernel_rt >, - Point_triple_has_on_positive_side_3< typename R_::Approximate_kernel >, - Point_triple_converter, - Point_triple_converter - > Has_on_positive_side_3; - - typedef Filtered_predicate< - Point_triple_less_signed_distance_to_plane_3< typename R_::Exact_kernel_rt >, - Point_triple_less_signed_distance_to_plane_3< typename R_::Approximate_kernel >, - Point_triple_converter, - Point_triple_converter - > Less_signed_distance_to_plane_3; -}; template -class Convex_hull_traits_3 : - public Convex_hull_traits_base_3 +class Convex_hull_traits_3 { public: typedef R_ R; @@ -228,11 +196,12 @@ class Convex_hull_traits_3 : typedef typename R::Coplanar_3 Coplanar_3; typedef typename R::Less_distance_to_point_3 Less_distance_to_point_3; - typedef typename Convex_hull_traits_base_3 - ::Has_on_positive_side_3 Has_on_positive_side_3; + typedef Point_triple_has_on_positive_side_3 Has_on_positive_side_3; - typedef typename Convex_hull_traits_base_3 - ::Less_signed_distance_to_plane_3 Less_signed_distance_to_plane_3; + typedef Point_triple_less_signed_distance_to_plane_3 + Less_signed_distance_to_plane_3; + + // required for degenerate case of all points coplanar typedef CGAL::Projection_traits_xy_3 Traits_xy_3; diff --git a/Convex_hull_3/include/CGAL/Extreme_points_traits_adapter_3.h b/Convex_hull_3/include/CGAL/Extreme_points_traits_adapter_3.h index 599f3f948c6..8991774adc3 100644 --- a/Convex_hull_3/include/CGAL/Extreme_points_traits_adapter_3.h +++ b/Convex_hull_3/include/CGAL/Extreme_points_traits_adapter_3.h @@ -138,7 +138,7 @@ public: public: Construct_plane_3(const PointPropertyMap& map, const typename Base_traits::Construct_plane_3& base): Base_traits::Construct_plane_3(base),vpm_(map), base(base){} - typename Base_traits::Plane_3 operator()(const Point_3& p, const Point_3& q, const Point_3& r)const + typename Base_traits::Plane_3 operator()(const Vertex& p, const Vertex& q, const Vertex& r)const { return base(get(vpm_,p),get(vpm_,q),get(vpm_,r)); } @@ -159,7 +159,7 @@ public: typedef bool result_type; result_type - operator()( const Plane_3& pl, const Point_3& p) const + operator()( const Plane_3& pl, const Vertex& p) const { return base(pl, get(vpm_, p)); } From c4b5b728f350dce15ac2fc7cba2090b23ce87df5 Mon Sep 17 00:00:00 2001 From: Maxime Gimeno Date: Mon, 1 Oct 2018 09:44:08 +0200 Subject: [PATCH 101/116] fix colored point-set in shape detection plugin --- .../Plugins/Point_set/Point_set_shape_detection_plugin.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Polyhedron/demo/Polyhedron/Plugins/Point_set/Point_set_shape_detection_plugin.cpp b/Polyhedron/demo/Polyhedron/Plugins/Point_set/Point_set_shape_detection_plugin.cpp index eef452a345c..aa620cd84b3 100644 --- a/Polyhedron/demo/Polyhedron/Plugins/Point_set/Point_set_shape_detection_plugin.cpp +++ b/Polyhedron/demo/Polyhedron/Plugins/Point_set/Point_set_shape_detection_plugin.cpp @@ -358,6 +358,7 @@ private: ++ nb_colored_pts; colored_item->point_set()->set_color(*it, r, g, b); } + colored_item->invalidateOpenGLBuffers(); } // Providing a useful name consisting of the order of detection, name of type and number of inliers @@ -418,7 +419,7 @@ private: poly_item->setName(QString("%1%2_alpha_shape").arg(QString::fromStdString(ss.str())) .arg (QString::number (shape->indices_of_assigned_points().size()))); poly_item->setRenderingMode (Flat); - + poly_item->invalidateOpenGLBuffers(); scene->addItem(poly_item); if(scene->item_id(groups[0]) == -1) scene->addItem(groups[0]); @@ -452,6 +453,7 @@ private: point_item->setRenderingMode(item->renderingMode()); if (dialog.generate_subset()){ + point_item->invalidateOpenGLBuffers(); scene->addItem(point_item); if (dynamic_cast *>(shape.get())) { @@ -469,6 +471,7 @@ private: if(scene->item_id(groups[0]) == -1) scene->addItem(groups[0]); + point_item->invalidateOpenGLBuffers(); scene->changeGroup(point_item, groups[0]); } else if (dynamic_cast *>(shape.get())) @@ -523,6 +526,7 @@ private: pts_full->setName(tr("%1 (structured)").arg(item->name())); pts_full->setRenderingMode(PointsPlusNormals); pts_full->setColor(Qt::blue); + pts_full->invalidateOpenGLBuffers(); scene->addItem (pts_full); } std::cerr << "done" << std::endl; From 885cc02c623df4ba68a933f4768102b5e8de6708 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Loriot?= Date: Mon, 1 Oct 2018 10:31:21 +0200 Subject: [PATCH 102/116] fix activation of local static filters --- Convex_hull_3/include/CGAL/convex_hull_3.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Convex_hull_3/include/CGAL/convex_hull_3.h b/Convex_hull_3/include/CGAL/convex_hull_3.h index 333334b3f42..531bfd3d943 100644 --- a/Convex_hull_3/include/CGAL/convex_hull_3.h +++ b/Convex_hull_3/include/CGAL/convex_hull_3.h @@ -182,13 +182,13 @@ struct Is_cartesian_kernel< Convex_hull_traits_3 { // Rational here is that Tag_true can only be passed by us since it is not documented // so we can assume that Kernel is a CGAL Kernel - typedef boost::is_same type; + typedef typename boost::is_same::type type; }; // Predicate internally used as a wrapper around has_on_positive_side // We provide a partial specialization restricted to the case of CGAL Cartesian Kernels with inexact constructions below //template ::type > -template > +template ::type > class Is_on_positive_side_of_plane_3{ typedef typename Traits::Point_3 Point_3; typename Traits::Plane_3 plane; From 5a47381813c93c179a588299204bfa79306505f1 Mon Sep 17 00:00:00 2001 From: Maxime Gimeno Date: Mon, 1 Oct 2018 15:12:22 +0200 Subject: [PATCH 103/116] Fix offset usage in convex non-quad faces of surface_mesh --- Polyhedron/demo/Polyhedron/Scene_surface_mesh_item.cpp | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/Polyhedron/demo/Polyhedron/Scene_surface_mesh_item.cpp b/Polyhedron/demo/Polyhedron/Scene_surface_mesh_item.cpp index 98d944a91d0..8c8bf3bc5e3 100644 --- a/Polyhedron/demo/Polyhedron/Scene_surface_mesh_item.cpp +++ b/Polyhedron/demo/Polyhedron/Scene_surface_mesh_item.cpp @@ -831,9 +831,6 @@ void Scene_surface_mesh_item_priv::triangulate_convex_facet(face_descriptor fd, Scene_item_rendering_helper::Gl_data_names name, bool index) const { - const CGAL::qglviewer::Vec v_offset = static_cast(CGAL::QGLViewer::QGLViewerPool().first())->offset(); - EPICK::Vector_3 offset = EPICK::Vector_3(v_offset.x, v_offset.y, v_offset.z); - Point p0,p1,p2; SMesh::Halfedge_around_face_circulator he(halfedge(fd, *smesh_), *smesh_); SMesh::Halfedge_around_face_circulator he_end = he; @@ -844,9 +841,9 @@ void Scene_surface_mesh_item_priv::triangulate_convex_facet(face_descriptor fd, vertex_descriptor v0(target(*he_end, *smesh_)), v1(target(*he, *smesh_)), v2(target(next(*he, *smesh_), *smesh_)); - p0 = smesh_->point(v0) + offset; - p1 = smesh_->point(v1) + offset; - p2 = smesh_->point(v2) + offset; + p0 = smesh_->point(v0); + p1 = smesh_->point(v1); + p2 = smesh_->point(v2); if(!index) { CGAL::Color* color; From 743047d4cffea6b5d9b7cd7eb54b15401ad69d1a Mon Sep 17 00:00:00 2001 From: Andreas Fabri Date: Tue, 2 Oct 2018 09:47:35 +0200 Subject: [PATCH 104/116] Workaround for just the one version of VC++ which has a problem --- Point_set_shape_detection_3/include/CGAL/regularize_planes.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Point_set_shape_detection_3/include/CGAL/regularize_planes.h b/Point_set_shape_detection_3/include/CGAL/regularize_planes.h index ec26cf59017..e58561c26a3 100644 --- a/Point_set_shape_detection_3/include/CGAL/regularize_planes.h +++ b/Point_set_shape_detection_3/include/CGAL/regularize_planes.h @@ -781,7 +781,7 @@ void regularize_planes (const PointRange& points, /// \cond SKIP_IN_MANUAL -#ifdef _MSC_VER +#if _MSC_VER == 1915 #define CGAL_TYPENAME_FOR_MSC #else #define CGAL_TYPENAME_FOR_MSC typename @@ -830,7 +830,9 @@ void regularize_planes (const PointRange& points, tolerance_angle, tolerance_coplanarity, symmetry_direction); } +#ifdef CGAL_TYPENAME_FOR_MSC #undef CGAL_TYPENAME_FOR_MSC +#endif /// \endcond From 1c308aa9713fe839ed6d20d3a29769e0b8382082 Mon Sep 17 00:00:00 2001 From: Andreas Fabri Date: Tue, 2 Oct 2018 09:51:21 +0200 Subject: [PATCH 105/116] Workaround for just the one version of VC++ which has a problem --- Point_set_shape_detection_3/include/CGAL/regularize_planes.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Point_set_shape_detection_3/include/CGAL/regularize_planes.h b/Point_set_shape_detection_3/include/CGAL/regularize_planes.h index e58561c26a3..6f2067a456d 100644 --- a/Point_set_shape_detection_3/include/CGAL/regularize_planes.h +++ b/Point_set_shape_detection_3/include/CGAL/regularize_planes.h @@ -781,6 +781,8 @@ void regularize_planes (const PointRange& points, /// \cond SKIP_IN_MANUAL +// Workaround for bug reported here: +// https://developercommunity.visualstudio.com/content/problem/340310/unaccepted-typename-that-other-compilers-require.html #if _MSC_VER == 1915 #define CGAL_TYPENAME_FOR_MSC #else From e041253f4b143cf78c2ee0f58156f06514d92b7c Mon Sep 17 00:00:00 2001 From: Maxime Gimeno Date: Tue, 2 Oct 2018 11:15:21 +0200 Subject: [PATCH 106/116] Add cd SOURCES_DIR before call to script. --- Scripts/developer_scripts/create_new_release | 1 + 1 file changed, 1 insertion(+) diff --git a/Scripts/developer_scripts/create_new_release b/Scripts/developer_scripts/create_new_release index 7c6f04e6031..1d4421fee36 100755 --- a/Scripts/developer_scripts/create_new_release +++ b/Scripts/developer_scripts/create_new_release @@ -251,6 +251,7 @@ function cleanup() { trap cleanup EXIT # Create the release +cd ${SOURCES_DIR} cmake -DPUBLIC=NO -DTESTSUITE=ON -DDESTINATION="${DESTINATION}" -DCGAL_VERSION="${release_version}" -DCGAL_VERSION_NR="${release_number}" -DVERBOSE="${VERBOSE}" -P ${SOURCES_DIR}/Scripts/developer_scripts/cgal_create_release_with_cmake.cmake pushd "${DESTINATION}/${release_name}" sed -i -e "s/define CGAL_GIT_HASH .*/define CGAL_GIT_HASH $CGAL_GIT_HASH/" include/CGAL/version.h From 4002d3d04ba1c81f0be694647530ecc4cd06226d Mon Sep 17 00:00:00 2001 From: Laurent Rineau Date: Tue, 2 Oct 2018 12:55:48 +0200 Subject: [PATCH 107/116] Use the file public_release_name --- Scripts/developer_scripts/create_new_release | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Scripts/developer_scripts/create_new_release b/Scripts/developer_scripts/create_new_release index 1d4421fee36..de45c991c7e 100755 --- a/Scripts/developer_scripts/create_new_release +++ b/Scripts/developer_scripts/create_new_release @@ -297,7 +297,11 @@ if [ -n "$DO_PUBLIC" ]; then if [ -n "$BETA" ]; then public_release_version="${public_release_version}-beta${BETA}" fi - public_release_name="CGAL-${public_release_version}" + if [ -r "${NUMBERS_DIR}/public_release_name" ]; then + public_release_name=`cat "${NUMBERS_DIR}/public_release_name"` + else + public_release_name="CGAL-${public_release_version}" + fi cmake -DPUBLIC="ON" -DDESTINATION="${DESTINATION}" -DCGAL_VERSION="${public_release_version}" -DCGAL_VERSION_NR="${release_number}" -DVERBOSE="${VERBOSE}" -P ${SOURCES_DIR}/Scripts/developer_scripts/cgal_create_release_with_cmake.cmake pushd "${DESTINATION}/${public_release_name}" From 2dcebb5761ddbf3751a2f89cea78411c287ce0fc Mon Sep 17 00:00:00 2001 From: Marc Glisse Date: Tue, 2 Oct 2018 13:38:55 +0200 Subject: [PATCH 108/116] Kernel_traits uses R, not Kernel --- Kernel_23/doc/Kernel_23/CGAL/Kernel_traits.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Kernel_23/doc/Kernel_23/CGAL/Kernel_traits.h b/Kernel_23/doc/Kernel_23/CGAL/Kernel_traits.h index 3f650d01399..7cf0de10c85 100644 --- a/Kernel_23/doc/Kernel_23/CGAL/Kernel_traits.h +++ b/Kernel_23/doc/Kernel_23/CGAL/Kernel_traits.h @@ -7,7 +7,7 @@ namespace CGAL { The class `Kernel_traits` provides access to the kernel model to which the argument type `T` belongs. (Provided `T` belongs to some kernel model.) The default implementation assumes there is a -local type `T::Kernel` referring to the kernel model of `T`. +local type `T::R` referring to the kernel model of `T`. If this type does not exist, a specialization of `Kernel_traits` can be used to provide the desired information. From 8caeb6e63a1986fd24564d6c1d73756464391850 Mon Sep 17 00:00:00 2001 From: Marc Glisse Date: Tue, 2 Oct 2018 13:50:42 +0200 Subject: [PATCH 109/116] Akl/Toussaint algorithm for convex hull uses Orientation_2 --- Convex_hull_2/doc/Convex_hull_2/CGAL/ch_akl_toussaint.h | 1 + 1 file changed, 1 insertion(+) diff --git a/Convex_hull_2/doc/Convex_hull_2/CGAL/ch_akl_toussaint.h b/Convex_hull_2/doc/Convex_hull_2/CGAL/ch_akl_toussaint.h index 50631e75cb1..0f1a616be0e 100644 --- a/Convex_hull_2/doc/Convex_hull_2/CGAL/ch_akl_toussaint.h +++ b/Convex_hull_2/doc/Convex_hull_2/CGAL/ch_akl_toussaint.h @@ -26,6 +26,7 @@ functions that return instances of these types:

  • `Traits::Less_xy_2`,
  • `Traits::Less_yx_2`,
  • `Traits::Left_turn_2`, +
  • `Traits::Orientation_2`,
  • `Traits::Equal_2`. From cc291cda326f4107a08bc557e13c27aae8af9058 Mon Sep 17 00:00:00 2001 From: Marc Glisse Date: Tue, 2 Oct 2018 13:54:39 +0200 Subject: [PATCH 110/116] Akl/Toussaint algorithm for convex hull uses Orientation_2 --- Convex_hull_2/doc/Convex_hull_2/CGAL/convex_hull_2.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Convex_hull_2/doc/Convex_hull_2/CGAL/convex_hull_2.h b/Convex_hull_2/doc/Convex_hull_2/CGAL/convex_hull_2.h index 1793bd3d593..7c8edc910bb 100644 --- a/Convex_hull_2/doc/Convex_hull_2/CGAL/convex_hull_2.h +++ b/Convex_hull_2/doc/Convex_hull_2/CGAL/convex_hull_2.h @@ -31,7 +31,8 @@ functions that return instances of these types:
  • `Traits::Equal_2`,
  • `Traits::Less_xy_2`,
  • `Traits::Less_yx_2`, -
  • `Traits::Left_turn_2`. +
  • `Traits::Left_turn_2`, +
  • `Traits::Orientation_2`. From 0c1b4f3ab73a0af6b1948a57e282f721c326ec01 Mon Sep 17 00:00:00 2001 From: Laurent Rineau Date: Tue, 2 Oct 2018 14:21:31 +0200 Subject: [PATCH 111/116] finally it works - Do not use the current working directory, in cgal_create_release_with_cmake.cmake - Fix the use of public_release_name --- .../cgal_create_release_with_cmake.cmake | 27 +++++++++++-------- Scripts/developer_scripts/create_new_release | 8 +++--- 2 files changed, 20 insertions(+), 15 deletions(-) diff --git a/Scripts/developer_scripts/cgal_create_release_with_cmake.cmake b/Scripts/developer_scripts/cgal_create_release_with_cmake.cmake index db6119a6bf5..91c725da55d 100644 --- a/Scripts/developer_scripts/cgal_create_release_with_cmake.cmake +++ b/Scripts/developer_scripts/cgal_create_release_with_cmake.cmake @@ -1,4 +1,5 @@ #option : +# GIT_REPO the path to the Git repository, default is the current working directory # DESTINATION the path where the release is created, default is /tmp # PUBLIC=[ON/OFF] indicates if a public release should be built, default is OFF # VERBOSE=[ON/OFF] makes the script more verbose, default is OFF @@ -7,11 +8,15 @@ # CGAL_VERSION_NR=release string used to update version.h. Must be something like 1041200033 , or 104120090 # TESTSUITE=indicate if the release is meant to be used by the testsuite, default if OFF -if (NOT EXISTS ${CMAKE_BINARY_DIR}/Installation/include/CGAL/version.h) +if (NOT GIT_REPO) + set(GIT_REPO ${CMAKE_BINARY_DIR}) +endif() + +if (NOT EXISTS ${GIT_REPO}/Installation/include/CGAL/version.h) message(FATAL_ERROR "Cannot find Installation/include/CGAL/version.h. Make sure you are at the root of a CGAL branch") endif() -file(READ "${CMAKE_BINARY_DIR}/Installation/include/CGAL/version.h" version_file_content) +file(READ "${GIT_REPO}/Installation/include/CGAL/version.h" version_file_content) string(REGEX MATCH "define CGAL_VERSION (.*)\n#define CGAL_VERSION_NR" CGAL_VERSION_FOUND "${version_file_content}") if (CGAL_VERSION_FOUND) @@ -41,10 +46,10 @@ else() endif() file(MAKE_DIRECTORY "${release_dir}") -file(GLOB files RELATIVE ${CMAKE_BINARY_DIR} ${CMAKE_BINARY_DIR}/*) +file(GLOB files RELATIVE ${GIT_REPO} ${GIT_REPO}/*) foreach(pkg ${files}) - set(pkg_dir ${CMAKE_BINARY_DIR}/${pkg}) # use absolute path + set(pkg_dir ${GIT_REPO}/${pkg}) # use absolute path if(IS_DIRECTORY ${pkg_dir} AND (NOT "${pkg}" STREQUAL "Maintenance") AND (EXISTS ${pkg_dir}/package_info OR "${pkg}" STREQUAL "Documentation" @@ -73,9 +78,9 @@ foreach(pkg ${files}) if ("${fext}" STREQUAL ".h" OR "${fext}" STREQUAL ".hpp") file(READ "${pkg_dir}/${f}" file_content) string(REPLACE "$URL$" "$URL: ${GITHUB_PREFIX}/${pkg}/${f} $" file_content "${file_content}") - if(EXISTS ${CMAKE_BINARY_DIR}/.git) + if(EXISTS ${GIT_REPO}/.git) execute_process( - COMMAND git --git-dir=${CMAKE_BINARY_DIR}/.git --work-tree=${CMAKE_BINARY_DIR} log -n1 "--format=format:%h %aI %an" -- "${pkg}/${f}" + COMMAND git --git-dir=${GIT_REPO}/.git --work-tree=${GIT_REPO} log -n1 "--format=format:%h %aI %an" -- "${pkg}/${f}" RESULT_VARIABLE RESULT_VAR OUTPUT_VARIABLE OUT_VAR ) @@ -91,7 +96,7 @@ foreach(pkg ${files}) endforeach() if (EXISTS "${release_dir}/doc/${pkg}") #generate filelist.txt used by doxygen ran on a release - file(GLOB_RECURSE includes LIST_DIRECTORIES false RELATIVE "${CMAKE_BINARY_DIR}/${pkg}/include" "${CMAKE_BINARY_DIR}/${pkg}/include/CGAL/*.h") + file(GLOB_RECURSE includes LIST_DIRECTORIES false RELATIVE "${GIT_REPO}/${pkg}/include" "${GIT_REPO}/${pkg}/include/CGAL/*.h") foreach(f ${includes}) file(APPEND "${release_dir}/doc/${pkg}/filelist.txt" "${f}\n") endforeach() @@ -109,7 +114,7 @@ file(WRITE ${release_dir}/VERSION "${CGAL_VERSION}") #edit include/CGAL/version.h file(READ "${release_dir}/include/CGAL/version.h" file_content) # update CGAL_GIT_HASH -if(EXISTS ${CMAKE_BINARY_DIR}/.git) +if(EXISTS ${GIT_REPO}/.git) execute_process( COMMAND git rev-parse HEAD RESULT_VARIABLE RESULT_VAR @@ -136,7 +141,7 @@ if (TESTSUITE) if(IS_DIRECTORY "${release_dir}/test/${d}") if(NOT EXISTS "${release_dir}/test/${d}/cgal_test_with_cmake") execute_process( - COMMAND ${CMAKE_BINARY_DIR}/Scripts/developer_scripts/create_cgal_test_with_cmake + COMMAND ${GIT_REPO}/Scripts/developer_scripts/create_cgal_test_with_cmake WORKING_DIRECTORY "${release_dir}/test/${d}" RESULT_VARIABLE RESULT_VAR OUTPUT_VARIABLE OUT_VAR @@ -162,7 +167,7 @@ if (TESTSUITE) file(RENAME "${release_dir}/tmp/${d}" "${release_dir}/test/${d}_Demo") if(NOT EXISTS "${release_dir}/test/${d}_Demo/cgal_test_with_cmake") execute_process( - COMMAND ${CMAKE_BINARY_DIR}/Scripts/developer_scripts/create_cgal_test_with_cmake --no-run + COMMAND ${GIT_REPO}/Scripts/developer_scripts/create_cgal_test_with_cmake --no-run WORKING_DIRECTORY "${release_dir}/test/${d}_Demo" RESULT_VARIABLE RESULT_VAR OUTPUT_VARIABLE OUT_VAR @@ -183,7 +188,7 @@ if (TESTSUITE) file(RENAME "${release_dir}/tmp/${d}" "${release_dir}/test/${d}_Examples") if(NOT EXISTS "${release_dir}/test/${d}_Examples/cgal_test_with_cmake") execute_process( - COMMAND ${CMAKE_BINARY_DIR}/Scripts/developer_scripts/create_cgal_test_with_cmake + COMMAND ${GIT_REPO}/Scripts/developer_scripts/create_cgal_test_with_cmake WORKING_DIRECTORY "${release_dir}/test/${d}_Examples" RESULT_VARIABLE RESULT_VAR OUTPUT_VARIABLE OUT_VAR diff --git a/Scripts/developer_scripts/create_new_release b/Scripts/developer_scripts/create_new_release index de45c991c7e..9f4ddc266df 100755 --- a/Scripts/developer_scripts/create_new_release +++ b/Scripts/developer_scripts/create_new_release @@ -251,8 +251,7 @@ function cleanup() { trap cleanup EXIT # Create the release -cd ${SOURCES_DIR} -cmake -DPUBLIC=NO -DTESTSUITE=ON -DDESTINATION="${DESTINATION}" -DCGAL_VERSION="${release_version}" -DCGAL_VERSION_NR="${release_number}" -DVERBOSE="${VERBOSE}" -P ${SOURCES_DIR}/Scripts/developer_scripts/cgal_create_release_with_cmake.cmake +cmake -DGIT_REPO=${SOURCES_DIR} -DPUBLIC=NO -DTESTSUITE=ON -DDESTINATION="${DESTINATION}" -DCGAL_VERSION="${release_version}" -DCGAL_VERSION_NR="${release_number}" -DVERBOSE="${VERBOSE}" -P ${SOURCES_DIR}/Scripts/developer_scripts/cgal_create_release_with_cmake.cmake pushd "${DESTINATION}/${release_name}" sed -i -e "s/define CGAL_GIT_HASH .*/define CGAL_GIT_HASH $CGAL_GIT_HASH/" include/CGAL/version.h cd .. @@ -299,11 +298,12 @@ if [ -n "$DO_PUBLIC" ]; then fi if [ -r "${NUMBERS_DIR}/public_release_name" ]; then public_release_name=`cat "${NUMBERS_DIR}/public_release_name"` + public_release_version=${public_release_name#CGAL-} else public_release_name="CGAL-${public_release_version}" fi - cmake -DPUBLIC="ON" -DDESTINATION="${DESTINATION}" -DCGAL_VERSION="${public_release_version}" -DCGAL_VERSION_NR="${release_number}" -DVERBOSE="${VERBOSE}" -P ${SOURCES_DIR}/Scripts/developer_scripts/cgal_create_release_with_cmake.cmake + cmake -DGIT_REPO=${SOURCES_DIR} -DPUBLIC="ON" -DDESTINATION="${DESTINATION}" -DCGAL_VERSION="${public_release_version}" -DCGAL_VERSION_NR="${release_number}" -DVERBOSE="${VERBOSE}" -P ${SOURCES_DIR}/Scripts/developer_scripts/cgal_create_release_with_cmake.cmake pushd "${DESTINATION}/${public_release_name}" # Modify the version numbers in @@ -333,7 +333,7 @@ if [ -n "$DO_PUBLIC" ]; then if docker version > /dev/null; then # Build the Windows installer docker pull cgal/cgal-nsis-dockerfile - docker create -v `realpath ${public_release_name}`:/mnt/cgal_release:ro,z \ + docker create -v `realpath ${DESTINATION}/${public_release_name}`:/mnt/cgal_release:ro,z \ -v ${SOURCES_DIR}:/mnt/cgal_sources:ro,z \ cgal/cgal-nsis-dockerfile container_id=`docker ps -q -l` From a3e9b9e2a8d4ea08e5053b6ce18fdc82a2090afd Mon Sep 17 00:00:00 2001 From: Mael Date: Wed, 3 Oct 2018 10:50:48 +0200 Subject: [PATCH 112/116] Disabled testing of `remove_degeneracies_test.cpp` --- .../test/Polygon_mesh_processing/CMakeLists.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/Polygon_mesh_processing/test/Polygon_mesh_processing/CMakeLists.txt b/Polygon_mesh_processing/test/Polygon_mesh_processing/CMakeLists.txt index 2becf2af309..124a9379880 100644 --- a/Polygon_mesh_processing/test/Polygon_mesh_processing/CMakeLists.txt +++ b/Polygon_mesh_processing/test/Polygon_mesh_processing/CMakeLists.txt @@ -101,7 +101,6 @@ endif() create_single_source_cgal_program("test_orient_cc.cpp") create_single_source_cgal_program("test_pmp_transform.cpp") create_single_source_cgal_program("extrude_test.cpp") - create_single_source_cgal_program("remove_degeneracies_test.cpp") create_single_source_cgal_program("test_merging_border_vertices.cpp") create_single_source_cgal_program("test_shape_predicates.cpp") From 3222ff8572b1117528e842eb67940c65575a0c64 Mon Sep 17 00:00:00 2001 From: Maxime Gimeno Date: Wed, 3 Oct 2018 13:57:50 +0200 Subject: [PATCH 113/116] Create .scm-branch if TESTSUITE --- .../cgal_create_release_with_cmake.cmake | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/Scripts/developer_scripts/cgal_create_release_with_cmake.cmake b/Scripts/developer_scripts/cgal_create_release_with_cmake.cmake index 91c725da55d..3caacef0d3f 100644 --- a/Scripts/developer_scripts/cgal_create_release_with_cmake.cmake +++ b/Scripts/developer_scripts/cgal_create_release_with_cmake.cmake @@ -136,6 +136,23 @@ file(WRITE ${release_dir}/include/CGAL/version.h "${file_content}") # make an extra copy of examples and demos for the testsuite and generate # create_cgal_test_with_cmake for tests, demos, and examples if (TESTSUITE) + SET(FMT_ARG "format:SCM branch:%n%H %d%n%nShort log from master:%n") + execute_process( + COMMAND git --git-dir=${GIT_REPO}/.git --work-tree=${GIT_REPO} log -n1 --format=${FMT_ARG} + WORKING_DIRECTORY "${release_dir}" + OUTPUT_VARIABLE OUT_VAR + ) +#write result in .scm-branch + file(WRITE ${release_dir}/.scm-branch "${OUT_VAR}") + SET(FMT_ARG "%h %s%n parents: %p%n") + execute_process( + COMMAND git --git-dir=${GIT_REPO}/.git --work-tree=${GIT_REPO} log --first-parent --format=${FMT_ARG} cgal/master.. + WORKING_DIRECTORY "${release_dir}" + OUTPUT_VARIABLE OUT_VAR + ) +#append result in .scm-branch + file(APPEND ${release_dir}/.scm-branch "${OUT_VAR}") + file(GLOB tests RELATIVE "${release_dir}/test" "${release_dir}/test/*") foreach(d ${tests}) if(IS_DIRECTORY "${release_dir}/test/${d}") @@ -200,7 +217,7 @@ if (TESTSUITE) endif() endforeach() file(REMOVE_RECURSE "${release_dir}/tmp") -endif() +endif() //TESTSUITE # removal of extra directories and files file(REMOVE_RECURSE ${release_dir}/benchmark) From 39ff5772ba851445651b264fea1b40653916d5ac Mon Sep 17 00:00:00 2001 From: Maxime Gimeno Date: Wed, 3 Oct 2018 15:38:58 +0200 Subject: [PATCH 114/116] Fix commentary and missing --git-dir --- .../developer_scripts/cgal_create_release_with_cmake.cmake | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Scripts/developer_scripts/cgal_create_release_with_cmake.cmake b/Scripts/developer_scripts/cgal_create_release_with_cmake.cmake index 3caacef0d3f..b9c40f0ebc6 100644 --- a/Scripts/developer_scripts/cgal_create_release_with_cmake.cmake +++ b/Scripts/developer_scripts/cgal_create_release_with_cmake.cmake @@ -116,7 +116,7 @@ file(READ "${release_dir}/include/CGAL/version.h" file_content) # update CGAL_GIT_HASH if(EXISTS ${GIT_REPO}/.git) execute_process( - COMMAND git rev-parse HEAD + COMMAND git --git-dir=${GIT_REPO}/.git rev-parse HEAD RESULT_VARIABLE RESULT_VAR OUTPUT_VARIABLE OUT_VAR ) @@ -217,7 +217,7 @@ if (TESTSUITE) endif() endforeach() file(REMOVE_RECURSE "${release_dir}/tmp") -endif() //TESTSUITE +endif() #TESTSUITE # removal of extra directories and files file(REMOVE_RECURSE ${release_dir}/benchmark) From 61017e51abb3ed290a4196e01ccbd92d261fd45d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mael=20Rouxel-Labb=C3=A9?= Date: Thu, 4 Oct 2018 13:17:44 +0200 Subject: [PATCH 115/116] Replaced boost's math constants with CGAL --- .../Polygon_mesh_processing/test_shape_predicates.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/Polygon_mesh_processing/test/Polygon_mesh_processing/test_shape_predicates.cpp b/Polygon_mesh_processing/test/Polygon_mesh_processing/test_shape_predicates.cpp index 8f76f46fae5..378c438102b 100644 --- a/Polygon_mesh_processing/test/Polygon_mesh_processing/test_shape_predicates.cpp +++ b/Polygon_mesh_processing/test/Polygon_mesh_processing/test_shape_predicates.cpp @@ -5,7 +5,7 @@ #include #include -#include +#include #include #include @@ -192,7 +192,8 @@ void test_needles_and_caps(const char* fname) res = PMP::is_cap_triangle_face(f, mesh, 0./*cos(pi/2)*/); assert(mesh.point(target(res, mesh)) == CGAL::ORIGIN); res = PMP::is_cap_triangle_face(f, mesh, std::cos(91 * CGAL_PI / 180)); - assert(res == boost::graph_traits::null_halfedge()); res = PMP::is_cap_triangle_face(f, mesh, std::cos(boost::math::constants::two_thirds_pi())); + assert(res == boost::graph_traits::null_halfedge()); + res = PMP::is_cap_triangle_face(f, mesh, std::cos(2 * CGAL_PI / 3)); assert(res == boost::graph_traits::null_halfedge()); ++ fit; @@ -207,9 +208,9 @@ void test_needles_and_caps(const char* fname) res = PMP::is_cap_triangle_face(f, mesh, 0./*cos(pi/2)*/); assert(mesh.point(target(res, mesh)) == Point_3(0,0,1)); - res = PMP::is_cap_triangle_face(f, mesh, std::cos(boost::math::constants::two_thirds_pi())); + res = PMP::is_cap_triangle_face(f, mesh, std::cos(2 * CGAL_PI / 3)); assert(mesh.point(target(res, mesh)) == Point_3(0,0,1)); - res = PMP::is_cap_triangle_face(f, mesh, std::cos(boost::math::constants::three_quarters_pi())); + res = PMP::is_cap_triangle_face(f, mesh, std::cos(0.75 * CGAL_PI)); assert(res == boost::graph_traits::null_halfedge()); ++ fit; @@ -228,7 +229,7 @@ void test_needles_and_caps(const char* fname) assert(res != boost::graph_traits::null_halfedge() && mesh.point(target(res, mesh)) != Point_3(0,0,2) && mesh.point(target(res, mesh)) != Point_3(1,0,2)); - res = PMP::is_cap_triangle_face(f, mesh, std::cos(boost::math::constants::two_thirds_pi())); + res = PMP::is_cap_triangle_face(f, mesh, std::cos(2 * CGAL_PI / 3)); assert(res != boost::graph_traits::null_halfedge()); res = PMP::is_cap_triangle_face(f, mesh, std::cos(175 * CGAL_PI / 180)); assert(res != boost::graph_traits::null_halfedge()); From ce0e1748194218a3ee00c5eca706b3acb0d71b3e Mon Sep 17 00:00:00 2001 From: Maxime Gimeno Date: Thu, 4 Oct 2018 16:06:46 +0200 Subject: [PATCH 116/116] Add tests, examples and demos to travis's call to cmake for CGAL --- .travis.yml | 2 +- .travis/template.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index d5461dd8ece..61b7507491c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -63,7 +63,7 @@ before_script: - sudo chmod +x /usr/bin/doxygen - mkdir -p build - cd build -- cmake -DCMAKE_CXX_FLAGS="-std=c++11" -DCGAL_HEADER_ONLY=ON -DQt5_DIR="/opt/qt55/lib/cmake/Qt5" -DQt5Svg_DIR="/opt/qt55/lib/cmake/Qt5Svg" -DQt5OpenGL_DIR="/opt/qt55/lib/cmake/Qt5OpenGL" -DCMAKE_CXX_FLAGS_RELEASE=-DCGAL_NDEBUG .. +- cmake -DCMAKE_CXX_FLAGS="-std=c++11" -DCGAL_HEADER_ONLY=ON -DQt5_DIR="/opt/qt55/lib/cmake/Qt5" -DQt5Svg_DIR="/opt/qt55/lib/cmake/Qt5Svg" -DQt5OpenGL_DIR="/opt/qt55/lib/cmake/Qt5OpenGL" -DCMAKE_CXX_FLAGS_RELEASE=-DCGAL_NDEBUG -DWITH_examples=ON -DWITH_demos=ON -DWITH_tests=ON .. - make - sudo make install &>/dev/null - cd .. diff --git a/.travis/template.txt b/.travis/template.txt index c023fe68f3e..1632aa16cbf 100644 --- a/.travis/template.txt +++ b/.travis/template.txt @@ -19,7 +19,7 @@ before_script: - sudo chmod +x /usr/bin/doxygen - mkdir -p build - cd build -- cmake -DCMAKE_CXX_FLAGS="-std=c++11" -DCGAL_HEADER_ONLY=ON -DQt5_DIR="/opt/qt55/lib/cmake/Qt5" -DQt5Svg_DIR="/opt/qt55/lib/cmake/Qt5Svg" -DQt5OpenGL_DIR="/opt/qt55/lib/cmake/Qt5OpenGL" -DCMAKE_CXX_FLAGS_RELEASE=-DCGAL_NDEBUG .. +- cmake -DCMAKE_CXX_FLAGS="-std=c++11" -DCGAL_HEADER_ONLY=ON -DQt5_DIR="/opt/qt55/lib/cmake/Qt5" -DQt5Svg_DIR="/opt/qt55/lib/cmake/Qt5Svg" -DQt5OpenGL_DIR="/opt/qt55/lib/cmake/Qt5OpenGL" -DCMAKE_CXX_FLAGS_RELEASE=-DCGAL_NDEBUG -DWITH_examples=ON -DWITH_demos=ON -DWITH_tests=ON .. - make - sudo make install &>/dev/null - cd ..