diff --git a/BGL/include/CGAL/boost/graph/Seam_mesh.h b/BGL/include/CGAL/boost/graph/Seam_mesh.h index 8df36e3ff4c..429e38c98ee 100644 --- a/BGL/include/CGAL/boost/graph/Seam_mesh.h +++ b/BGL/include/CGAL/boost/graph/Seam_mesh.h @@ -1017,7 +1017,7 @@ public: /// of a vertex of the underlying mesh is given by its position /// in the container `tm_vds`. /// - /// \tparam VdContainer must be a model of SequenceContainer (that is, provide + /// \tparam VdContainer must be a model of `SequenceContainer` (that is, provide /// the functions: `operator[]` and `at()`). /// /// \returns one of the halfedges of the seam mesh that is on a seam. @@ -1062,7 +1062,7 @@ public: /// /// \returns one of the halfedges of the seam mesh that is on a seam. /// - /// \tparam VdContainer must be a model of SequenceContainer (that is, provide + /// \tparam VdContainer must be a model of `SequenceContainer` (that is, provide /// the functions: `operator[]` and `at()`). /// /// \pre filename should be the name of a CGAL selection file: edges are diff --git a/BGL/include/CGAL/boost/graph/parameters_interface.h b/BGL/include/CGAL/boost/graph/parameters_interface.h index 01c9796e813..3272be27bfe 100644 --- a/BGL/include/CGAL/boost/graph/parameters_interface.h +++ b/BGL/include/CGAL/boost/graph/parameters_interface.h @@ -79,6 +79,8 @@ CGAL_add_named_parameter(throw_on_self_intersection_t, throw_on_self_intersectio 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) +CGAL_add_named_parameter(erase_all_duplicates_t, erase_all_duplicates, erase_all_duplicates) +CGAL_add_named_parameter(require_same_orientation_t, require_same_orientation, require_same_orientation) // 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 22252abdd36..db322900b15 100644 --- a/BGL/test/BGL/test_cgal_bgl_named_params.cpp +++ b/BGL/test/BGL/test_cgal_bgl_named_params.cpp @@ -85,6 +85,8 @@ void test(const NamedParameters& np) assert(get_param(np, CGAL::internal_np::throw_on_self_intersection).v == 43); assert(get_param(np, CGAL::internal_np::clip_volume).v == 44); assert(get_param(np, CGAL::internal_np::use_compact_clipper).v == 45); + assert(get_param(np, CGAL::internal_np::erase_all_duplicates).v == 48); + assert(get_param(np, CGAL::internal_np::require_same_orientation).v == 49); // Named parameters that we use in the package 'Surface Mesh Simplification' assert(get_param(np, CGAL::internal_np::get_cost_policy).v == 34); @@ -162,6 +164,8 @@ void test(const NamedParameters& np) check_same_type<43>(get_param(np, CGAL::internal_np::throw_on_self_intersection)); check_same_type<44>(get_param(np, CGAL::internal_np::clip_volume)); check_same_type<45>(get_param(np, CGAL::internal_np::use_compact_clipper)); + check_same_type<48>(get_param(np, CGAL::internal_np::erase_all_duplicates)); + check_same_type<49>(get_param(np, CGAL::internal_np::require_same_orientation)); // Named parameters that we use in the package 'Surface Mesh Simplification' check_same_type<34>(get_param(np, CGAL::internal_np::get_cost_policy)); @@ -241,6 +245,8 @@ int main() .use_compact_clipper(A<45>(45)) .apply_per_connected_component(A<46>(46)) .output_iterator(A<47>(47)) + .erase_all_duplicates(A<48>(48)) + .require_same_orientation(A<49>(49)) ); return EXIT_SUCCESS; diff --git a/Barycentric_coordinates_2/doc/Barycentric_coordinates_2/Barycentric_coordinates_2.txt b/Barycentric_coordinates_2/doc/Barycentric_coordinates_2/Barycentric_coordinates_2.txt index da226aedf29..855db161ee9 100644 --- a/Barycentric_coordinates_2/doc/Barycentric_coordinates_2/Barycentric_coordinates_2.txt +++ b/Barycentric_coordinates_2/doc/Barycentric_coordinates_2/Barycentric_coordinates_2.txt @@ -223,7 +223,7 @@ From the figure above it is easy to see that the \f$O(n^2)\f$ algorithm is as fa The generic design of the package was developed in 2013 by Dmitry Anisimov and David Bommes with many useful comments by Kai Hormann and Pierre Alliez. The package consists of 6 classes, 2 enumerations, and one namespace. Appropriate iterators are used to provide an efficient access to data and to pass them to one of the generic algorithms for computing coordinates. Once instantiated for a polygon (triangle, segment), the coordinates can be computed multiple times for different query points with respect to all the vertices of the provided polygon (triangle, segment). All the classes are fully templated and have a simple and similar design. In particular, we follow the same naming convention for all functions. Yet, the number of functions can differ from one class to another. -The implemented algorithms for computing coordinates do not depend on a particular kernel, and all the coordinates can be computed exactly, if an exact kernel is used, apart from mean value coordinates. The latter coordinates involve a square root operation, which results in a slightly worse precision with exact data types due to temporal conversion into a floating point type. The computed coordinates can be stored in an arbitrary container if an appropriate output iterator is provided. +The implemented algorithms for computing coordinates do not depend on a particular kernel, and all the coordinates can be computed exactly, if an exact kernel is used, apart from mean value coordinates. The latter coordinates involve a square root operation, which results in a slightly worse precision with exact data types due to temporal conversion into a floating point type. The computed coordinates can be stored in an arbitrary container if an appropriate output iterator is provided. It is worth noting that the class `CGAL::Barycentric_coordinates::Segment_coordinates_2` is used to compute generalized barycentric coordinates along the polygon's boundary. Hence, one can use the trick for segment coordinates from Section \ref gbc_degeneracies if one is convinced that a point must lie exactly on the polygon's boundary but due to some numerical instabilities it does not. diff --git a/Documentation/doc/Documentation/General.txt b/Documentation/doc/Documentation/General.txt index 275c0e03963..ccb1c7e89e6 100644 --- a/Documentation/doc/Documentation/General.txt +++ b/Documentation/doc/Documentation/General.txt @@ -94,11 +94,37 @@ class RandomAccessIterator {}; /// See https://en.cppreference.com/w/cpp/named_req/BidirectionalIterator class BidirectionalIterator {}; +/// \cgalConcept +/// Concept from the \cpp standard. +/// See https://en.cppreference.com/w/cpp/named_req/Swappable +class Swappable {}; + +/// \cgalConcept +/// Concept from the \cpp standard. +/// See https://en.cppreference.com/w/cpp/named_req/Container +class Container {}; + +/// \cgalConcept +/// Concept from the \cpp standard. +/// See https://en.cppreference.com/w/cpp/named_req/ReversibleContainer +class ReversibleContainer {}; + +/// \cgalConcept +/// Concept from the \cpp standard. +/// See https://en.cppreference.com/w/cpp/named_req/AssociativeContainer +class AssociativeContainer {}; + +/// \cgalConcept +/// Concept from the \cpp standard. +/// See https://en.cppreference.com/w/cpp/named_req/SequenceContainer +class SequenceContainer {}; + /// \cgalConcept /// This container concept refines /// ReversibleContainer and its iterator type is a model of -/// RandomAccessIterator. +/// RandomAccessIterator./// \cgalConcept class RandomAccessContainer {}; + /// \cgalConcept /// This container concepts refines /// SequenceContainer and diff --git a/Mesh_3/include/CGAL/IO/facets_in_complex_3_to_triangle_mesh.h b/Mesh_3/include/CGAL/IO/facets_in_complex_3_to_triangle_mesh.h index dd4f88ab26d..7fd025f7a73 100644 --- a/Mesh_3/include/CGAL/IO/facets_in_complex_3_to_triangle_mesh.h +++ b/Mesh_3/include/CGAL/IO/facets_in_complex_3_to_triangle_mesh.h @@ -54,7 +54,7 @@ void resize(Polygon& p, std::size_t size) template void resize(CGAL::cpp11::array&, std::size_t CGAL_assertion_code(size)) { - CGAL_assertion(size >= N); + CGAL_assertion(size == N); } template diff --git a/Mesh_3/include/CGAL/Polyhedral_complex_mesh_domain_3.h b/Mesh_3/include/CGAL/Polyhedral_complex_mesh_domain_3.h index 5b8f04688b4..43f75dc2091 100644 --- a/Mesh_3/include/CGAL/Polyhedral_complex_mesh_domain_3.h +++ b/Mesh_3/include/CGAL/Polyhedral_complex_mesh_domain_3.h @@ -414,9 +414,9 @@ public: needed_vertices_on_patch[i] = (std::min)(nb_of_extra_vertices_per_patch, needed_vertices_on_patch[i]); } + // Then a second path to fill `several_vertices_on_patch`... // The algorithm is adapted from SGI `random_sample_n`: - // https://www.sgi.com/tech/stl/random_sample_n.html BOOST_FOREACH(const Polyhedron& p, this->stored_polyhedra) { for (typename Polyhedron::Vertex_const_iterator diff --git a/Polygon_mesh_processing/doc/Polygon_mesh_processing/NamedParameters.txt b/Polygon_mesh_processing/doc/Polygon_mesh_processing/NamedParameters.txt index a2c5456db34..fa8022005e1 100644 --- a/Polygon_mesh_processing/doc/Polygon_mesh_processing/NamedParameters.txt +++ b/Polygon_mesh_processing/doc/Polygon_mesh_processing/NamedParameters.txt @@ -329,15 +329,15 @@ Parameter used in `isotropic_remeshing()` to specify an alternative vertex proje \cgalNPBegin{apply_per_connected_component} \anchor PMP_apply_per_connected_component Parameter used to indicate whether an algorithm should consider each connected component of a mesh independently.\n -\b Type : `bool` \n -\b Default value is `false` +Type: `bool` \n +Default: `false` \cgalNPEnd \cgalNPBegin{visitor} \anchor PMP_visitor Parameter used to pass a visitor class to a function. Its type and behavior depend on the visited function. \n -\b Type : `A class` \n -\b Default : Specific to the function visited +Type: `A class` \n +Default: Specific to the function visited \cgalNPEnd \cgalNPBegin{throw_on_self_intersection} \anchor PMP_throw_on_self_intersection @@ -345,23 +345,23 @@ Parameter used in corefinement-related functions to make the functions throw an case some faces involved in the intersection of the input are self-intersecting and make the operation impossible with the current version of the code. \n -\b Type : `bool` \n -\b Default value is `false` +Type: `bool` \n +Default: `false` \cgalNPEnd \cgalNPBegin{clip_volume} \anchor PMP_clip_volume Parameter used in `clip()` functions to clip a volume rather than a surface. \n -\b Type : `bool` \n -\b Default value is `false` +Type: `bool` \n +Default: `false` \cgalNPEnd \cgalNPBegin{use_compact_clipper} \anchor PMP_use_compact_clipper Parameter used in `clip()` functions to indicate whether the boundary of the clipper should be considered as part of the clipping volume or not. \n -\b Type : `bool` \n -\b Default value is `true` +Type: `bool` \n +Default: `true` \cgalNPEnd \cgalNPBegin{output_iterator} \anchor PMP_output_iterator @@ -371,6 +371,23 @@ Parameter to pass an output iterator. \b Default : `Emptyset_iterator` \cgalNPEnd +\cgalNPBegin{erase_all_duplicates} \anchor PMP_erase_all_duplicates +Parameter used in the function `merge_duplicate_polygons_in_polygon_soup()` to indicate, +when multiple faces are duplicates, whether all the duplicate faces should be removed +or if one (arbitrarily chosen) face should be kept. +\n +Type: `bool` \n +Default: `false` +\cgalNPEnd + +\cgalNPBegin{require_same_orientation} \anchor PMP_require_same_orientation +Parameter used in the function `merge_duplicate_polygons_in_polygon_soup()` to indicate +if orientation should matter when determining whether two faces are duplicates. +\n +Type: `bool` \n +Default: `false` +\cgalNPEnd + \cgalNPTableEnd */ diff --git a/Polygon_mesh_processing/doc/Polygon_mesh_processing/PackageDescription.txt b/Polygon_mesh_processing/doc/Polygon_mesh_processing/PackageDescription.txt index e2a59d63cf9..f5025dcb442 100644 --- a/Polygon_mesh_processing/doc/Polygon_mesh_processing/PackageDescription.txt +++ b/Polygon_mesh_processing/doc/Polygon_mesh_processing/PackageDescription.txt @@ -36,7 +36,7 @@ /// \ingroup PkgPolygonMeshProcessing /// \defgroup PMP_repairing_grp Combinatorial Repairing -/// Functions to orient polygon soups and to stitch geometrically identical boundaries. +/// Functions to repair polygon soups and polygon meshes. /// \ingroup PkgPolygonMeshProcessing /// \defgroup PMP_distance_grp Distance Functions @@ -128,7 +128,11 @@ and provides a list of the parameters that are used in this package. - `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::merge_duplicate_points_in_polygon_soup()` +- `CGAL::Polygon_mesh_processing::merge_duplicate_polygons_in_polygon_soup()` +- `CGAL::Polygon_mesh_processing::remove_isolated_points_in_polygon_soup()` +- `CGAL::Polygon_mesh_processing::repair_polygon_soup()` +- `CGAL::Polygon_mesh_processing::stitch_borders()` - `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()` 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 e09b5620d85..d6a0b0e5e03 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 @@ -531,7 +531,20 @@ Section \ref PMPOrientation. **************************************** \section PMPRepairing Combinatorial Repairing + ******************* +\subsection PSRepairing Polygon Soup Repairing +To ensure that a polygon soup can be oriented (see Section \ref PolygonSoups) and transformed +into a workable polygon mesh, it might be necessary to preprocess the data to remove combinatorial +and geometrical errors. This package offers the following functions: +- `CGAL::Polygon_mesh_processing::merge_duplicate_points_in_polygon_soup()`, +- `CGAL::Polygon_mesh_processing::merge_duplicate_polygons_in_polygon_soup()`, +- `CGAL::Polygon_mesh_processing::remove_isolated_points_in_polygon_soup()`, + +as well as the function `CGAL::Polygon_mesh_processing::repair_polygon_soup()`, +which bundles the previous functions and an additional handful of repairing techniques +to obtain an as-clean-as-possible polygon soup. + \subsection Stitching It happens that a polygon mesh has several edges and vertices that are duplicated. @@ -575,6 +588,8 @@ is output. \endif \subsection PMPManifoldness Polygon Mesh Manifoldness +This package offers repairing methods to clean ill-formed polygon soups, +see Section \ref PMPRepairing. 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 diff --git a/Polygon_mesh_processing/doc/Polygon_mesh_processing/examples.txt b/Polygon_mesh_processing/doc/Polygon_mesh_processing/examples.txt index cb09da7d83f..f5aa23b864b 100644 --- a/Polygon_mesh_processing/doc/Polygon_mesh_processing/examples.txt +++ b/Polygon_mesh_processing/doc/Polygon_mesh_processing/examples.txt @@ -22,4 +22,5 @@ \example Polygon_mesh_processing/corefinement_consecutive_bool_op.cpp \example Polygon_mesh_processing/detect_features_example.cpp \example Polygon_mesh_processing/manifoldness_repair_example.cpp +\example Polygon_mesh_processing/repair_polygon_soup_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 2a3bc719826..31283d2708f 100644 --- a/Polygon_mesh_processing/examples/Polygon_mesh_processing/CMakeLists.txt +++ b/Polygon_mesh_processing/examples/Polygon_mesh_processing/CMakeLists.txt @@ -105,6 +105,7 @@ 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" ) +create_single_source_cgal_program( "repair_polygon_soup_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/repair_polygon_soup_example.cpp b/Polygon_mesh_processing/examples/Polygon_mesh_processing/repair_polygon_soup_example.cpp new file mode 100644 index 00000000000..e44be56106e --- /dev/null +++ b/Polygon_mesh_processing/examples/Polygon_mesh_processing/repair_polygon_soup_example.cpp @@ -0,0 +1,82 @@ +#include +#include + +#include +#include +#include + +#include +#include + +typedef CGAL::Exact_predicates_inexact_constructions_kernel K; +typedef K::Point_3 Point_3; + +typedef std::vector Polygon; +typedef CGAL::Surface_mesh Mesh; + +namespace PMP = CGAL::Polygon_mesh_processing; + +int main(int, char**) +{ + // First, construct a polygon soup with some problems + std::vector points; + std::vector polygons; + + points.push_back(Point_3(0,0,0)); + points.push_back(Point_3(1,0,0)); + points.push_back(Point_3(0,1,0)); + points.push_back(Point_3(-1,0,0)); + points.push_back(Point_3(0,-1,0)); + points.push_back(Point_3(0,1,0)); // duplicate point + points.push_back(Point_3(0,-2,0)); // unused point + + Polygon p; + p.push_back(0); p.push_back(1); p.push_back(2); + polygons.push_back(p); + + // degenerate face + p.clear(); + p.push_back(0); p.push_back(0); p.push_back(0); + polygons.push_back(p); + + p.clear(); + p.push_back(0); p.push_back(1); p.push_back(4); + polygons.push_back(p); + + // duplicate face with different orientation + p.clear(); + p.push_back(0); p.push_back(4); p.push_back(1); + polygons.push_back(p); + + p.clear(); + p.push_back(0); p.push_back(3); p.push_back(5); + polygons.push_back(p); + + // degenerate face + p.clear(); + p.push_back(0); p.push_back(3); p.push_back(0); + polygons.push_back(p); + + p.clear(); + p.push_back(0); p.push_back(3); p.push_back(4); + polygons.push_back(p); + + // pinched and degenerate face + p.clear(); + p.push_back(0); p.push_back(1); p.push_back(2); p.push_back(3); + p.push_back(4); p.push_back(3); p.push_back(2); p.push_back(1); + polygons.push_back(p); + + PMP::repair_polygon_soup(points, polygons); + PMP::orient_polygon_soup(points, polygons); + + Mesh mesh; + PMP::polygon_soup_to_polygon_mesh(points, polygons, mesh); + + std::cout << "Mesh has " << num_vertices(mesh) << " vertices and " << num_faces(mesh) << " faces" << std::endl; + + assert(num_vertices(mesh) == 5); + assert(num_faces(mesh) == 4); + + return 0; +} diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/connected_components.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/connected_components.h index c72b3d54d98..df4a7fd80ab 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/connected_components.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/connected_components.h @@ -277,7 +277,7 @@ void keep_connected_components(PolygonMesh& pmesh * * \param pmesh the polygon mesh * \param nb_components_to_keep the number of components to be kept - * \param np optional \ref pmp_namedparameters "Named Parameters" described below + * \param np optional \ref pmp_namedparameters "Named Parameters", amongst those described below * * \cgalNamedParamsBegin * \cgalParamBegin{edge_is_constrained_map} a property map containing the constrained-or-not status of each edge of `pmesh` \cgalParamEnd @@ -360,7 +360,7 @@ std::size_t keep_largest_connected_components(PolygonMesh& pmesh, * * \param pmesh the polygon mesh * \param threshold_components_to_keep the number of faces a component must have so that it is kept - * \param np optional \ref pmp_namedparameters "Named Parameters" described below + * \param np optional \ref pmp_namedparameters "Named Parameters", amongst those described below * * \cgalNamedParamsBegin * \cgalParamBegin{edge_is_constrained_map} a property map containing the constrained-or-not status of each edge of `pmesh` \cgalParamEnd @@ -664,7 +664,7 @@ void remove_connected_components(PolygonMesh& pmesh * * \param components_to_remove a face range, including one face or more on each component to be removed * \param pmesh the polygon mesh -* \param np optional \ref pmp_namedparameters "Named Parameters" described below +* \param np optional \ref pmp_namedparameters "Named Parameters", amongst those described below * * \cgalNamedParamsBegin * \cgalParamBegin{edge_is_constrained_map} a property map containing the constrained-or-not status of each edge of `pmesh` \cgalParamEnd @@ -722,7 +722,7 @@ void remove_connected_components(PolygonMesh& pmesh * * \param pmesh the polygon mesh * \param components_to_keep a face range, including one face or more on each component to be kept -* \param np optional \ref pmp_namedparameters "Named Parameters" described below +* \param np optional \ref pmp_namedparameters "Named Parameters", amongst those described below * * \cgalNamedParamsBegin * \cgalParamBegin{edge_is_constrained_map} a property map containing the constrained-or-not status of each edge of `pmesh` \cgalParamEnd diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/detect_features.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/detect_features.h index 330e348277b..787c812650c 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/detect_features.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/detect_features.h @@ -258,7 +258,7 @@ template + +#include +#include + +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#ifdef CGAL_PMP_REPAIR_POLYGON_SOUP_VERBOSE_PP + #ifndef CGAL_PMP_REPAIR_POLYGON_SOUP_VERBOSE + #define CGAL_PMP_REPAIR_POLYGON_SOUP_VERBOSE + #endif +#endif + +namespace CGAL { + +namespace Polygon_mesh_processing { + +namespace internal { + +template +struct Polygon_types +{ + typedef typename boost::range_value::type Point_3; + typedef typename boost::range_value::type Polygon_3; + + typedef typename boost::range_iterator::type V_ID_iterator; + typedef typename std::iterator_traits::value_type V_ID; + typedef typename std::vector::size_type P_ID; +}; + +template +struct GetPolygonGeomTraits +{ + typedef typename boost::lookup_named_param_def < + internal_np::geom_traits_t, + NamedParameters, + typename CGAL::Kernel_traits< + typename internal::Polygon_types< + PointRange, PolygonRange>::Point_3 >::type + > ::type type; +}; + +template +void print_polygon(Stream& out, const Polygon& polygon) +{ + const std::size_t polygon_size = polygon.size(); + out << "(" << polygon_size << ")"; + for(std::size_t i=0; i +struct Vertex_ID_comparer +{ + Vertex_ID_comparer(const PointRange& points, const Traits& traits = Traits()) + : points(points), traits(traits) + { } + + template + bool operator()(const VID id_1, const VID id_2) const { + return traits.less_xyz_3_object()(points[id_1], points[id_2]); + } + +private: + const PointRange& points; + const Traits& traits; +}; + +template +bool polygon_has_unique_vertices(const PointRange& points, + const Polygon& polygon, + const Traits& traits = Traits()) +{ + typedef Vertex_ID_comparer Comparer; + + Comparer comp(points, traits); + std::set unique_vertices(comp); + unique_vertices.insert(polygon.begin(), polygon.end()); + + return (unique_vertices.size() == polygon.size()); +} + +template +bool simplify_polygon(PointRange& points, + Polygon& polygon, + const Traits& traits = Traits()) +{ + const std::size_t ini_polygon_size = polygon.size(); + + // Start at the last since if two points are identical, the second one gets removed. + // By starting at 'last', we ensure that 'to_remove' is ordered from closest to .begin() + // to closest to .end() + std::size_t last = ini_polygon_size - 1, i = last; + bool stop = false; + std::vector to_remove; + + do + { + std::size_t next_i = (i == last) ? 0 : i+1; + stop = (next_i == last); + + while(polygon[i] == polygon[next_i] || // combinatorial equality + traits.equal_3_object()(points[polygon[i]], points[polygon[next_i]])) // geometric equality + { + to_remove.push_back(next_i); +#ifdef CGAL_PMP_REPAIR_POLYGON_SOUP_VERBOSE_PP + std::cout << "Duplicate point: polygon[" << next_i << "] = " << polygon[next_i] << std::endl; +#endif + next_i = (next_i == last) ? 0 : next_i+1; + + // Every duplicate in front of 'last' (circularly-speaking) has already been cleared + if(next_i == last) + { + stop = true; + break; + } + } + + i = next_i; + } + while(!stop); + + while(!to_remove.empty()) + { + polygon.erase(polygon.begin() + to_remove.back()); + to_remove.pop_back(); + } + + const std::size_t removed_points_n = ini_polygon_size - polygon.size(); + return (removed_points_n != 0); +} + +// \ingroup PMP_repairing_grp +// +// For each polygon of the soup, removes consecutive identical (in a geometric sense) points. +// +// \tparam PointRange a model of the concept `RandomAccessContainer` whose value type is the point type. +// \tparam PolygonRange a model of the concept `SequenceContainer` +// whose value_type is itself a model of the concept `SequenceContainer` +// whose value_type is `std::size_t`. +// \tparam NamedParameters a sequence of \ref pmp_namedparameters "Named Parameters" +// +// \param points points of the soup of polygons. +// \param polygons a vector of polygons. Each element in the vector describes a polygon +// using the indices of the points in `points`. +// \param np optional \ref pmp_namedparameters "Named Parameters" described below +// +// \cgalNamedParamsBegin +// \cgalParamBegin{geom_traits} a geometric traits class instance. +// The traits class must provide the nested functor `Equal_3` +// to compare lexicographically two points a function `Equal_3 equal_3_object()`. +// \cgalParamEnd +// \cgalNamedParamsEnd +// +template +std::size_t simplify_polygons_in_polygon_soup(PointRange& points, + PolygonRange& polygons, + const Traits& traits = Traits()) +{ + typedef typename Polygon_types::P_ID P_ID; + typedef typename Polygon_types::Polygon_3 Polygon_3; + + std::size_t simplified_polygons_n = 0; + + for(P_ID polygon_index=0, end=polygons.size(); polygon_index!=end; ++polygon_index) + { + Polygon_3& polygon = polygons[polygon_index]; + if(polygon.size() <= 1) + continue; + + if(simplify_polygon(points, polygon, traits)) + ++simplified_polygons_n; + } + +#ifdef CGAL_PMP_REPAIR_POLYGON_SOUP_VERBOSE + if(simplified_polygons_n > 0) + std::cout << "Cleaned consecutive duplicate vertices in " << simplified_polygons_n << " polygon(s)" << std::endl; +#endif + + return simplified_polygons_n; +} + +// \ingroup PMP_repairing_grp +// +// Splits "pinched" polygons, that is polygons for which a point appears more than once, +// into multiple non-pinched polygons. +// +// \tparam PointRange a model of the concept `RandomAccessContainer` whose value type is the point type. +// \tparam PolygonRange a model of the concept `SequenceContainer` +// whose value_type is itself a model of the concepts `SequenceContainer` +// and `Swappable` whose value_type is `std::size_t`. +// \tparam NamedParameters a sequence of \ref pmp_namedparameters "Named Parameters" +// +// \param points points of the soup of polygons. +// \param polygons a vector of polygons. Each element in the vector describes a polygon +// using the indices of the points in `points`. +// \param np optional \ref pmp_namedparameters "Named Parameters" described below +// +// \cgalNamedParamsBegin +// \cgalParamBegin{geom_traits} a geometric traits class instance. +// The traits class must provide the nested functor `Less_xyz_3` +// to compare lexicographically two points a function `Less_xyz_3 less_xyz_3_object()`. +// \cgalParamEnd +// \cgalNamedParamsEnd +// +template +std::size_t split_pinched_polygons_in_polygon_soup(PointRange& points, + PolygonRange& polygons, + const Traits& traits = Traits()) +{ + typedef typename Polygon_types::P_ID P_ID; + typedef typename Polygon_types::Point_3 Point_3; + typedef typename Polygon_types::Polygon_3 Polygon_3; + + typedef typename Traits::Less_xyz_3 Less_xyz_3; + + std::size_t new_polygons_n = 0; + + // It is important that polygons.size() is re-evaluated at each loop iteration + // because new polygons are intentionally added at the back of 'polygons' to also be examined + for(P_ID polygon_index=0; polygon_index < polygons.size(); ++polygon_index) + { + Polygon_3& polygon = polygons[polygon_index]; + const std::size_t ini_polygon_size = polygon.size(); + +#ifdef CGAL_PMP_REPAIR_POLYGON_SOUP_VERBOSE_PP + std::cout << "Input polygon: "; + internal::print_polygon(std::cout, polygon); +#endif + + if(ini_polygon_size <= 3) + continue; + + // 'polygon' must not have consecutive duplicates + CGAL_assertion(!simplify_polygon(points, polygon, traits)); + + typedef std::map Unique_point_container; + Unique_point_container unique_points(traits.less_xyz_3_object()); + + for(std::size_t i=0, polygon_size = polygon.size(); i is_insert_successful = + unique_points.insert(std::make_pair(p, i)); + + if(!is_insert_successful.second) + { +#ifdef CGAL_PMP_REPAIR_POLYGON_SOUP_VERBOSE + std::cout << "Pinched polygon: "; + internal::print_polygon(std::cout, polygon); +#endif + + // We have already met that point, split the polygon into two smaller polygons + std::size_t prev_id = is_insert_successful.first->second; + CGAL_assertion(prev_id < i-1); + + Polygon_3 split_polygon_1(polygon.begin() + prev_id, polygon.begin() + i); + CGAL_postcondition(internal::polygon_has_unique_vertices(points, split_polygon_1, traits)); + + Polygon_3 split_polygon_2; // might be pinched too, but it'll be checked later + split_polygon_2.insert(split_polygon_2.end(), polygon.begin(), polygon.begin() + prev_id); + split_polygon_2.insert(split_polygon_2.end(), polygon.begin() + i, polygon.end()); + +#ifdef CGAL_PMP_REPAIR_POLYGON_SOUP_VERBOSE_PP + std::cout << "New polygons:" << std::endl; + std::cout << "P1:"; + internal::print_polygon(std::cout, split_polygon_1); + + std::cout << "P2:"; + internal::print_polygon(std::cout, split_polygon_2); +#endif + + std::swap(polygon, split_polygon_1); + polygons.push_back(split_polygon_2); + + ++new_polygons_n; + break; + } + } + } + + return new_polygons_n; +} + +// \ingroup PMP_repairing_grp +// +// Removes polygons with fewer than 2 points from the soup. +// +// \tparam PointRange a model of the concept `Container` whose value type is the point type. +// \tparam PolygonRange a model of the concept `SequenceContainer` +// whose value_type is itself a model of the concept `Container` +// whose value_type is `std::size_t`. +// \tparam NamedParameters a sequence of \ref pmp_namedparameters "Named Parameters" +// +// \param points points of the soup of polygons. +// \param polygons a vector of polygons. Each element in the vector describes a polygon +// using the indices of the points in `points`. +// +template +std::size_t remove_invalid_polygons_in_polygon_soup(PointRange& /*points*/, + PolygonRange& polygons) +{ + std::vector to_remove; + const std::size_t ini_polygons_size = polygons.size(); + for(std::size_t polygon_index=0; polygon_index!=ini_polygons_size; ++polygon_index) + { + if(polygons[polygon_index].size() <= 2) + { +#ifdef CGAL_PMP_REPAIR_POLYGON_SOUP_VERBOSE_PP + std::cout << "Invalid polygon:"; + print_polygon(std::cout, polygons[polygon_index]); +#endif + to_remove.push_back(polygon_index); + } + } + + while(!to_remove.empty()) + { + polygons.erase(polygons.begin() + to_remove.back()); + to_remove.pop_back(); + } + + const std::size_t removed_polygons_n = ini_polygons_size - polygons.size(); + +#ifdef CGAL_PMP_REPAIR_POLYGON_SOUP_VERBOSE + if(removed_polygons_n > 0) + std::cout << "Removed " << removed_polygons_n << " invalid polygon(s)" << std::endl; +#endif + + return removed_polygons_n; +} + +} // end namespace internal + +template +std::size_t remove_degenerate_polygons_in_polygon_soup(PointRange& points, + PolygonRange& polygons) +{ + return remove_degenerate_polygons_in_polygon_soup(points, polygons, CGAL::parameters::all_default()); +} + +/// \ingroup PMP_repairing_grp +/// +/// Removes the isolated points from a polygon soup. +/// A point is considered isolated if it does not appear in any polygon of the soup. +/// +/// \tparam PointRange a model of the concept `SequenceContainer` whose value type is the point type. +/// \tparam PolygonRange a model of the concept `RandomAccessContainer` +/// whose value_type is itself a model of the concept `RandomAccessContainer` +/// whose value_type is `std::size_t`. +/// +/// \param points points of the soup of polygons. +/// \param polygons a vector of polygons. Each element in the vector describes a polygon +/// using the indices of the points in `points`. +/// +/// \returns the number of removed isolated points +/// +template +std::size_t remove_isolated_points_in_polygon_soup(PointRange& points, + PolygonRange& polygons) +{ + typedef typename internal::Polygon_types::P_ID P_ID; + typedef typename internal::Polygon_types::Polygon_3 Polygon_3; + + if(points.empty()) + return 0; + + // Go through all the polygons to find points that are never used. + const std::size_t ini_points_size = points.size(); + std::vector visited(ini_points_size, false); + std::vector id_remapping(ini_points_size); + for(std::size_t i=0; i 0) + std::cout << "Removed " << removed_points_n << " isolated point(s)" << std::endl; +#endif + + return removed_points_n; +} + +/// \ingroup PMP_repairing_grp +/// +/// Merges the duplicate points in a polygon soup. +/// Note that the index of a point that is merged with another point will thus change +/// in all the polygons that the point appears in. +/// +/// \tparam PointRange a model of the concepts `SequenceContainer` and `Swappable` +/// whose value type is the point type. +/// \tparam PolygonRange a model of the concept `RandomAccessContainer` +/// whose value_type is itself a model of the concept `RandomAccessContainer` +/// whose value_type is `std::size_t`. +/// \tparam NamedParameters a sequence of \ref pmp_namedparameters "Named Parameters" +/// +/// \param points points of the soup of polygons. +/// \param polygons a vector of polygons. Each element in the vector describes a polygon +/// using the indices of the points in `points`. +/// \param np optional \ref pmp_namedparameters "Named Parameters" described below +/// +/// \cgalNamedParamsBegin +/// \cgalParamBegin{geom_traits} a geometric traits class instance. +/// The traits class must provide the nested functor `Less_xyz_3` +/// to compare lexicographically two points a function `Less_xyz_3 less_xyz_3_object()`. +/// \cgalParamEnd +/// \cgalNamedParamsEnd +/// +/// \returns the number of removed points +/// +template +std::size_t merge_duplicate_points_in_polygon_soup(PointRange& points, + PolygonRange& polygons, + const NamedParameters& np) +{ + typedef typename internal::Polygon_types::P_ID P_ID; + typedef typename internal::Polygon_types::Point_3 Point_3; + typedef typename internal::Polygon_types::Polygon_3 Polygon_3; + + using boost::get_param; + using boost::choose_param; + + typedef typename internal::GetPolygonGeomTraits::type Traits; + Traits traits = choose_param(get_param(np, internal_np::geom_traits), Traits()); + + typedef typename Traits::Less_xyz_3 Less_xyz_3; + + const std::size_t ini_points_n = points.size(); + std::vector point_index(ini_points_n, 0); + + typedef std::map Unique_point_container; + Unique_point_container point_to_id(traits.less_xyz_3_object()); + + std::vector unique_points; + unique_points.reserve(ini_points_n); + + for(std::size_t i=0; i is_insert_successful = + point_to_id.insert(std::make_pair(points[i], unique_points.size())); + +#ifdef CGAL_PMP_REPAIR_POLYGON_SOUP_VERBOSE_PP + if(!is_insert_successful.second) + std::cout << "points[" <second; + + if(id == unique_points.size()) + unique_points.push_back(points[i]); + point_index[i] = id; + } + + if(unique_points.size() != ini_points_n) + { + for(P_ID polygon_index=0, end=polygons.size(); polygon_index!=end; ++polygon_index) + { + Polygon_3& polygon = polygons[polygon_index]; +#ifdef CGAL_PMP_REPAIR_POLYGON_SOUP_VERBOSE_PP + std::cout << "Input polygon: "; + internal::print_polygon(std::cout, polygon); +#endif + + for(std::size_t i=0, polygon_size = polygon.size(); i +std::size_t merge_duplicate_points_in_polygon_soup(PointRange& points, + PolygonRange& polygons) +{ + return merge_duplicate_points_in_polygon_soup(points, polygons, CGAL::parameters::all_default()); +} + +namespace internal { + +// Find the position of the (arbitrarily chose) first point of the canonical point +// and whether we should order from left to right or the opposite +template +void canonical_polygon_markers(const PointRange& points, + const Polygon& polygon, + std::size_t& first, + bool& reversed, + const Traits& traits = Traits()) +{ + CGAL_precondition(polygon.size() > 0); + CGAL_precondition(polygon_has_unique_vertices(points, polygon, traits)); + + typedef typename boost::range_iterator::type V_ID_iterator; + typedef Vertex_ID_comparer Vertex_comparer; + + // Find the bottom-left-front-most point that will be the first point of the polygon + Vertex_comparer comp(points, traits); + V_ID_iterator min_id = std::min_element(polygon.begin(), polygon.end(), comp); + first = min_id - polygon.begin(); + +#ifdef CGAL_PMP_REPAIR_POLYGON_SOUP_VERBOSE_PP + std::cout << "first: " << first + << " points[" << *min_id << "] = " << points[*min_id] << std::endl; +#endif + + // Decide arbitrarily whether we are reading from left to right or the opposite + const std::size_t last = polygon.size() - 1; + std::size_t pos_prev = (first == 0) ? last : first - 1; + std::size_t pos_next = (first == last) ? 0 : first + 1; + + reversed = traits.less_xyz_3_object()(points[polygon[pos_prev]], points[polygon[pos_next]]); + +#ifdef CGAL_PMP_REPAIR_POLYGON_SOUP_VERBOSE_PP + std::cout << "pos_prev: " << pos_prev + << " points[" << polygon[pos_prev] << "] = " << points[polygon[pos_prev]] << std::endl; + std::cout << "pos_next: " << pos_next + << " points[" << polygon[pos_next] << "] = " << points[polygon[pos_next]] << std::endl; + std::cout << "reversed: " << std::boolalpha << reversed << std::endl; +#endif +} + +template +Polygon construct_canonical_polygon_with_markers(const Polygon& polygon, + const std::size_t first, + const bool reversed) +{ + const std::size_t polygon_size = polygon.size(); + Polygon canonical_polygon; + + if(reversed) + { + std::size_t rfirst = polygon_size - 1 - first; + canonical_polygon.insert(canonical_polygon.end(), polygon.rbegin() + rfirst, polygon.rend()); + canonical_polygon.insert(canonical_polygon.end(), polygon.rbegin(), polygon.rbegin() + rfirst); + } + else + { + canonical_polygon.insert(canonical_polygon.end(), polygon.begin() + first, polygon.end()); + canonical_polygon.insert(canonical_polygon.end(), polygon.begin(), polygon.begin() + first); + } + + CGAL_postcondition(canonical_polygon[0] == polygon[first]); + CGAL_postcondition(canonical_polygon.size() == polygon_size); + return canonical_polygon; +} + +// 'reversed' indicates whether the canonical polygon has the same order as input polygon. +template +Polygon construct_canonical_polygon(const PointRange& points, + const Polygon& polygon, + bool& reversed, + const Traits& traits = Traits()) +{ + if(polygon.size() < 2) + return polygon; + +#ifdef CGAL_PMP_REPAIR_POLYGON_SOUP_VERBOSE_PP + std::cout << "Input polygon:"; + internal::print_polygon(std::cout, polygon); +#endif + + std::size_t first; + canonical_polygon_markers(points, polygon, first, reversed, traits); + Polygon canonical_polygon = construct_canonical_polygon_with_markers(polygon, first, reversed); + +#ifdef CGAL_PMP_REPAIR_POLYGON_SOUP_VERBOSE_PP + std::cout << "Canonical polygon:"; + internal::print_polygon(std::cout, canonical_polygon); +#endif + + return canonical_polygon; +} + +template +Polygon construct_canonical_polygon(const PointRange& points, + const Polygon& polygon, + const Traits& traits = Traits()) +{ + bool useless = false; + return construct_canonical_polygon(points, polygon, useless, traits); +} + +template +struct Polygon_hash +{ + typedef std::size_t result_type; + typedef typename internal::Polygon_types::Polygon_3 Polygon_3; + + Polygon_hash(const PointRange& points, const PolygonRange& canonical_polygons, const Traits& traits) + : points(points), canonical_polygons(canonical_polygons), traits(traits) + { } + + template + result_type operator() (const Polygon_ID& polygon_index) const + { + const Polygon_3& canonical_polygon = canonical_polygons[polygon_index]; + + std::size_t seed = 0; + for(std::size_t i=0, end=canonical_polygon.size(); i +struct Polygon_equality_tester +{ + typedef bool result_type; + typedef typename internal::Polygon_types::Polygon_3 Polygon_3; + + Polygon_equality_tester(const PointRange& points, + const PolygonRange& canonical_polygons, + const Reversed_markers& reversed_markers, + const Traits& traits, + const bool same_orientation = false) + : points(points), + canonical_polygons(canonical_polygons), + reversed_markers(reversed_markers), + traits(traits), + same_orientation(same_orientation) + { } + + template + bool operator()(const Polygon_ID& polygon_index_1, const Polygon_ID& polygon_index_2) const + { + const Polygon_3& canonical_polygon_1 = canonical_polygons[polygon_index_1]; + const Polygon_3& canonical_polygon_2 = canonical_polygons[polygon_index_2]; + + if(same_orientation && + reversed_markers[polygon_index_1] != reversed_markers[polygon_index_2]) + return false; + + return (canonical_polygon_1 == canonical_polygon_2); + } + +private: + const PointRange& points; + const PolygonRange& canonical_polygons; + const Reversed_markers& reversed_markers; + const Traits& traits; + const bool same_orientation; +}; + +template +struct Duplicate_collector +{ + void collect_duplicates(const ValueType& v1, const ValueType& 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; + } + + CGAL::cpp11::unordered_map > collections; +}; + +template +struct Duplicate_collector +{ + void collect_duplicates(const ValueType&, const ValueType&) { } + void dump(CGAL::Emptyset_iterator) { } +}; + +// \ingroup PMP_repairing_grp +// +// Collects duplicate polygons in a polygon soup, that is polygons that share the same vertices in the same +// order. +// +// \tparam PointRange a model of the concept `RandomAccessContainer` whose value type is the point type. +// \tparam PolygonRange a model of the concept `RandomAccessContainer` +// whose value_type is itself a model of the concepts `RandomAccessContainer` +// and `ReversibleContainer` whose value_type is `std::size_t`. +// \tparam DuplicateOutputIterator a model of `OutputIterator` with value type +// `std::vector >`. +// +// \param points points of the soup of polygons. +// \param polygons a vector of polygons. Each element in the vector describes a polygon +// using the indices of the points in `points`. +// \param out the output iterator in which duplicate polygons are put. Each entry is a vector of +// polygon ids `i0`, `i1`, etc. such that `polygons[i0] = polygons[i1] = ...` +// \param same_orientation whether two polygons should have the same orientation to be duplicates. +// +template +DuplicateOutputIterator collect_duplicate_polygons(const PointRange& points, + const PolygonRange& polygons, + DuplicateOutputIterator out, + const Traits& traits = Traits(), + const bool same_orientation = false) +{ + typedef typename internal::Polygon_types::P_ID P_ID; + + typedef internal::Polygon_hash Hasher; + typedef boost::dynamic_bitset<> Reversed_markers; + typedef internal::Polygon_equality_tester Equality; + typedef CGAL::cpp11::unordered_set Unique_polygons; + + const std::size_t polygons_n = polygons.size(); + + // We want the hash function to return the same value if the polygons are the same, + // regardless of circular permutations and different orientations. + PolygonRange canonical_polygons(polygons_n); + Reversed_markers is_reversed(polygons_n, 0); + for(P_ID polygon_index=0, end=polygons.size(); polygon_index!=end; ++polygon_index) + { + bool reversed; + canonical_polygons[polygon_index] = + internal::construct_canonical_polygon(points, polygons[polygon_index], reversed, traits); + + if(reversed) + is_reversed.set(polygon_index); + } + + Hasher hash(points, canonical_polygons, traits); + Equality equal(points, canonical_polygons, is_reversed, traits, same_orientation); + + Unique_polygons unique_polygons(polygons_n /*bucket size*/, hash, equal); + Duplicate_collector duplicates; + + for(P_ID polygon_index=0, end=polygons.size(); polygon_index!=end; ++polygon_index) + { + std::pair is_insert_successful = + unique_polygons.insert(polygon_index); + + if(!is_insert_successful.second) + { + const P_ID other_polygon_id = *(is_insert_successful.first); +#ifdef CGAL_PMP_REPAIR_POLYGON_SOUP_VERBOSE_PP + std::cout << "polygon: " << polygon_index << " is a duplicate of polygon: " << other_polygon_id << std::endl; +#endif + duplicates.collect_duplicates(other_polygon_id, polygon_index); + } + } + + duplicates.dump(out); + return out; +} + +} // end namespace internal + +/// \ingroup PMP_repairing_grp +/// +/// Merges the duplicate polygons in a polygon soup. Two polygons are duplicate if they share the same +/// vertices in the same order. Note that the first vertex of the polygon does not matter, that is +/// the triangle `0,1,2` is a duplicate of the triangle `2,0,1`. +/// +/// \tparam PointRange a model of the concept `RandomAccessContainer` whose value type is the point type. +/// \tparam PolygonRange a model of the concept `SequenceContainer` +/// whose value_type is itself a model of the concepts `RandomAccessContainer` +/// and `ReversibleContainer` whose value_type is `std::size_t`. +/// \tparam NamedParameters a sequence of \ref pmp_namedparameters "Named Parameters" +/// +/// \param points points of the soup of polygons. +/// \param polygons a vector of polygons. Each element in the vector describes a polygon +/// using the indices of the points in `points`. +/// \param np optional \ref pmp_namedparameters "Named Parameters", amongst those described below +/// +/// \cgalNamedParamsBegin +/// \cgalParamBegin{geom_traits} a geometric traits class instance. +/// The traits class must provide the nested functor `Less_xyz_3` +/// to compare lexicographically two points a function `Less_xyz_3 less_xyz_3_object()`. +/// \cgalParamEnd +/// \cgalParamBegin{erase_all_duplicates} +/// Parameter to indicate, when multiple polygons are duplicates, whether all the duplicate polygons +/// should be removed or if one (arbitrarily chosen) face should be kept. %Default is `false`. +/// \cgalParamEnd +/// \cgalParamBegin{require_same_orientation} +/// Parameter to indicate if polygon orientation should be taken into account when determining +/// whether two polygons are duplicates, that is, whether e.g. the triangles `0,1,2` and `0,2,1` +/// are duplicates. %Default is `false`. +/// \cgalParamEnd +/// \cgalNamedParamsEnd +/// +/// \returns the number of removed polygons +/// +template +std::size_t merge_duplicate_polygons_in_polygon_soup(const PointRange& points, + PolygonRange& polygons, + const NamedParameters& np) +{ + using boost::get_param; + using boost::choose_param; + + typedef typename internal::Polygon_types::P_ID P_ID; + + const bool erase_all_duplicates = choose_param(get_param(np, internal_np::erase_all_duplicates), false); + const bool same_orientation = choose_param(get_param(np, internal_np::require_same_orientation), false); + +#ifdef CGAL_PMP_REPAIR_POLYGON_SOUP_VERBOSE_PP + std::cout << "Only polygons with the same orientation are duplicates: " << std::boolalpha << same_orientation << std::endl; + std::cout << "Erase all duplicate polygons: " << std::boolalpha << erase_all_duplicates << std::endl; +#endif + + typedef typename internal::GetPolygonGeomTraits::type Traits; + Traits traits = choose_param(get_param(np, internal_np::geom_traits), Traits()); + + std::vector > all_duplicate_polygons; + internal::collect_duplicate_polygons(points, polygons, std::back_inserter(all_duplicate_polygons), traits, same_orientation); + + if(all_duplicate_polygons.empty()) + return 0; + +#ifdef CGAL_PMP_REPAIR_POLYGON_SOUP_VERBOSE_PP + std::cout << all_duplicate_polygons.size() << " duplicate(s)" << std::endl; +#endif + + // Move all polygons that will be removed to the end of container + const std::size_t init_polygons_n = polygons.size(); + std::size_t swap_position = init_polygons_n - 1; + + while(!all_duplicate_polygons.empty()) + { + const std::vector& duplicate_polygons = all_duplicate_polygons.back(); + CGAL_assertion(duplicate_polygons.size() >= 2); + + std::size_t i = erase_all_duplicates ? 0 : 1; + for(; i +std::size_t merge_duplicate_polygons_in_polygon_soup(PointRange& points, + PolygonRange& polygons) +{ + return merge_duplicate_polygons_in_polygon_soup(points, polygons, CGAL::parameters::all_default()); +} + +/// \ingroup PMP_repairing_grp +/// +/// Cleans a given polygon soup through various repairing operations. More precisely, this function +/// carries out the following tasks, in the same order as they are listed: +/// - merging of duplicate points, using the function +/// `CGAL::Polygon_mesh_processing::merge_duplicate_points_in_polygon_soup()`; +/// - simplification of polygons to remove geometrically identical consecutive vertices; +/// - splitting of "pinched" polygons, that is polygons in which a geometric position appears more than once. +/// The splitting process results in multiple non-pinched polygons; +/// - removal of invalid polygons, that is polygons with fewer than 2 vertices; +/// - removal of duplicate polygons, using the function +/// `CGAL::Polygon_mesh_processing::merge_duplicate_polygons_in_polygon_soup()`; +/// - removal of isolated points, +/// using the function `CGAL::Polygon_mesh_processing::remove_isolated_points_in_polygon_soup()`. +/// +/// Note that the point and polygon containers will be modified by the repairing operations, +/// and thus the indexation of the polygons will also be changed. +/// +/// \tparam PointRange a model of the concepts `SequenceContainer` and `Swappable` +/// and whose value type is the point type. +/// \tparam PolygonRange a model of the concept `SequenceContainer`. +/// whose value_type is itself a model of the concepts `SequenceContainer`, +/// `Swappable`, and `ReversibleContainer` whose value_type is `std::size_t`. +/// \tparam NamedParameters a sequence of \ref pmp_namedparameters "Named Parameters" +/// +/// \param points points of the soup of polygons. +/// \param polygons a vector of polygons. Each element in the vector describes a polygon +/// using the indices of the points in `points`. +/// \param np optional \ref pmp_namedparameters "Named Parameters", amongst those described below +/// +/// \cgalNamedParamsBegin +/// \cgalParamBegin{geom_traits} a geometric traits class instance. +/// The traits class must provide the nested functors : +/// - `Less_xyz_3` to compare lexicographically two points +/// - `Equal_3` to check whether 2 points are identical +/// +/// and, for each functor `Foo`, a function `Foo foo_object()`. +/// \cgalParamEnd +/// \cgalParamBegin{erase_all_duplicates} +/// Parameter forwarded to the function `merge_duplicate_polygons_in_polygon_soup()` to indicate, +/// when multiple polygons are duplicates, whether all the duplicate polygons +/// should be removed or if one (arbitrarily chosen) face should be kept. %Default is `false`. +/// \cgalParamEnd +/// \cgalParamBegin{require_same_orientation} +/// Parameter forwarded to the function `merge_duplicate_polygons_in_polygon_soup()` +/// to indicate if polygon orientation should be taken into account when determining whether +/// two polygons are duplicates, that is, whether e.g. the triangles `0,1,2` and `0,2,1` are duplicates. +/// %Default is `false`. +/// \cgalParamEnd +/// \cgalNamedParamsEnd +/// +template +void repair_polygon_soup(PointRange& points, + PolygonRange& polygons, + const NamedParameters& np) +{ + using boost::get_param; + using boost::choose_param; + + typedef typename internal::GetPolygonGeomTraits::type Traits; + Traits traits = choose_param(get_param(np, internal_np::geom_traits), Traits()); + +#ifdef CGAL_PMP_REPAIR_POLYGON_SOUP_VERBOSE + std::cout << "Repairing soup with " << points.size() << " points and " << polygons.size() << " polygons" << std::endl; +#endif + + merge_duplicate_points_in_polygon_soup(points, polygons, np); + internal::simplify_polygons_in_polygon_soup(points, polygons, traits); + internal::split_pinched_polygons_in_polygon_soup(points, polygons, traits); + internal::remove_invalid_polygons_in_polygon_soup(points, polygons); + merge_duplicate_polygons_in_polygon_soup(points, polygons, np); + remove_isolated_points_in_polygon_soup(points, polygons); +} + +template +void repair_polygon_soup(PointRange& points, + PolygonRange& polygons) +{ + return repair_polygon_soup(points, polygons, CGAL::parameters::all_default()); +} + +} // end namespace Polygon_mesh_processing + +} // end namespace CGAL + +#endif // CGAL_POLYGON_MESH_PROCESSING_REPAIR_POLYGON_SOUP diff --git a/Polygon_mesh_processing/include/CGAL/polygon_mesh_processing.h b/Polygon_mesh_processing/include/CGAL/polygon_mesh_processing.h index 164b29b25b7..3061ad2dbd8 100644 --- a/Polygon_mesh_processing/include/CGAL/polygon_mesh_processing.h +++ b/Polygon_mesh_processing/include/CGAL/polygon_mesh_processing.h @@ -50,6 +50,7 @@ #include #include #include +#include #include // the named parameter header being not documented the doc is put here for now diff --git a/Polygon_mesh_processing/test/Polygon_mesh_processing/CMakeLists.txt b/Polygon_mesh_processing/test/Polygon_mesh_processing/CMakeLists.txt index 124a9379880..9ed06e20989 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("extrude_test.cpp") create_single_source_cgal_program("test_merging_border_vertices.cpp") + create_single_source_cgal_program("test_repair_polygon_soup.cpp") create_single_source_cgal_program("test_shape_predicates.cpp") if( TBB_FOUND ) diff --git a/Polygon_mesh_processing/test/Polygon_mesh_processing/test_repair_polygon_soup.cpp b/Polygon_mesh_processing/test/Polygon_mesh_processing/test_repair_polygon_soup.cpp new file mode 100644 index 00000000000..006f4dc3f0e --- /dev/null +++ b/Polygon_mesh_processing/test/Polygon_mesh_processing/test_repair_polygon_soup.cpp @@ -0,0 +1,551 @@ +#define CGAL_PMP_REPAIR_POLYGON_SOUP_VERBOSE_PP + +#include + +#include + +#include + +#include +#include +#include +#include +#include + +namespace PMP = CGAL::Polygon_mesh_processing; +namespace params = PMP::parameters; + +typedef CGAL::Exact_predicates_inexact_constructions_kernel K; +typedef K::Point_3 Point_3; + +typedef CGAL::Surface_mesh Mesh; +typedef std::vector Polygon; + +void test_polygon_canonicalization(const bool verbose = false) +{ + std::cout << "test polygon canonicalization... " << std::endl; + + std::vector points; + points.push_back(Point_3(0,0,0)); // #0 + points.push_back(Point_3(1,0,0)); // #1 + points.push_back(Point_3(0,1,0)); // #2 + points.push_back(Point_3(1,1,0)); // #3 + points.push_back(Point_3(1,1,2)); // #4 + points.push_back(Point_3(1,1,-2)); // #5 + + // empty + Polygon polygon; + Polygon canonical_polygon = PMP::internal::construct_canonical_polygon(points, polygon, K()); + assert(canonical_polygon.empty()); + + // 1 point + polygon.push_back(5); + + canonical_polygon = PMP::internal::construct_canonical_polygon(points, polygon, K()); + assert(canonical_polygon == polygon); + + // 2 points + polygon.clear(); + polygon.push_back(4); polygon.push_back(3); + + canonical_polygon = PMP::internal::construct_canonical_polygon(points, polygon, K()); + assert(canonical_polygon[0] == 3 && canonical_polygon[1] == 4); + std::swap(polygon[0], polygon[1]); + canonical_polygon = PMP::internal::construct_canonical_polygon(points, polygon, K()); + assert(canonical_polygon[0] == 3 && canonical_polygon[1] == 4); + + // 3 points + polygon.clear(); + polygon.push_back(4); polygon.push_back(1); polygon.push_back(3); + + canonical_polygon = PMP::internal::construct_canonical_polygon(points, polygon, K()); + assert(canonical_polygon[0] == 1 && canonical_polygon[1] == 3 && canonical_polygon[2] == 4); + std::swap(polygon[0], polygon[2]); + canonical_polygon = PMP::internal::construct_canonical_polygon(points, polygon, K()); + assert(canonical_polygon[0] == 1 && canonical_polygon[1] == 3 && canonical_polygon[2] == 4); + + // Generic case + polygon.clear(); + polygon.push_back(0); polygon.push_back(2); polygon.push_back(5); polygon.push_back(4); polygon.push_back(1); + + // Whether reversed or with cyclic permutations, it should always yield the same canonical polygon + canonical_polygon = PMP::internal::construct_canonical_polygon(points, polygon, K()); + assert(canonical_polygon.size() == 5); + assert(canonical_polygon[0] == 0); + + // all cyclic permutations + for(std::size_t i=0, end=polygon.size(); i points; + std::vector polygons; + + // empty + std::size_t res = PMP::merge_duplicate_points_in_polygon_soup(points, polygons); + assert(res == 0 && points.empty() && polygons.empty()); + + points.push_back(Point_3(0,0,0)); // #0 + points.push_back(Point_3(1,2,0)); // #1 + points.push_back(Point_3(1,1,0)); // #2 + points.push_back(Point_3(1,1,0)); // #3 // identical to #2 + points.push_back(Point_3(0,1,0)); // #4 + points.push_back(Point_3(1,1,0)); // #5 // identical to #2 + points.push_back(Point_3(0,0,0)); // #6 // idental to #0 + + Polygon polygon; + polygon.push_back(0); polygon.push_back(1); polygon.push_back(2); + polygons.push_back(polygon); + + polygon.clear(); + polygon.push_back(6); polygon.push_back(3); polygon.push_back(1); polygon.push_back(0); + polygons.push_back(polygon); + + polygon.clear(); + polygon.push_back(5); + polygons.push_back(polygon); + + res = PMP::merge_duplicate_points_in_polygon_soup(points, polygons, params::geom_traits(K())); + assert(res == 3 && points.size() == 4 && polygons.size() == 3); + + assert(polygons[0][0] == 0 && polygons[0][1] == 1 && polygons[0][2] == 2); + assert(polygons[1][0] == 0 && polygons[1][1] == 2 && polygons[1][2] == 1 && polygons[1][3] == 0); + + for(std::size_t i=0, psn=polygons.size(); i= 0 && polygon[j] < points.size()); + } + } +} + +void test_merge_duplicate_polygons(const bool /*verbose*/ = false) +{ + std::cout << "test duplicate polygons merging..." << std::endl; + + std::vector points; + std::vector polygons; + + points.push_back(Point_3(0,0,0)); // #0 + points.push_back(Point_3(1,0,0)); // #1 + points.push_back(Point_3(0,1,0)); // #2 + points.push_back(Point_3(1,1,0)); // #3 + points.push_back(Point_3(1,1,2)); // #4 + + // ------------------------------------------------------- + // empty + std::vector > all_duplicate_polygons; + PMP::internal::collect_duplicate_polygons(points, polygons, + std::back_inserter(all_duplicate_polygons), + K(), true /*only equal if same orientation*/); + assert(polygons.empty() && all_duplicate_polygons.empty()); + + std::size_t res = PMP::merge_duplicate_polygons_in_polygon_soup(points, polygons, params::geom_traits(K())); + assert(res == 0 && polygons.empty()); + + // ------------------------------------------------------- + // 1 polygon + Polygon polygon; + polygon.push_back(0); polygon.push_back(1); polygon.push_back(2); + polygons.push_back(polygon); + + PMP::internal::collect_duplicate_polygons(points, polygons, + std::back_inserter(all_duplicate_polygons), + K(), false /*equal regardless of orientation*/); + assert(all_duplicate_polygons.empty()); + + PMP::internal::collect_duplicate_polygons(points, polygons, + std::back_inserter(all_duplicate_polygons), + K(), true /*only equal if same orientation*/); + assert(all_duplicate_polygons.empty()); + + res = PMP::merge_duplicate_polygons_in_polygon_soup(points, polygons, params::geom_traits(K())); + assert(res == 0 && polygons.size() == 1); + + // ------------------------------------------------------- + // 2 different polygons + polygon.clear(); + polygon.push_back(0); polygon.push_back(1); polygon.push_back(3); polygon.push_back(4); + polygons.push_back(polygon); + + PMP::internal::collect_duplicate_polygons(points, polygons, + std::back_inserter(all_duplicate_polygons), + K(), false /*equal regardless of orientation*/); + assert(all_duplicate_polygons.empty()); + + PMP::internal::collect_duplicate_polygons(points, polygons, + std::back_inserter(all_duplicate_polygons), + K(), true /*only equal if same orientation*/); + assert(all_duplicate_polygons.empty()); + + res = PMP::merge_duplicate_polygons_in_polygon_soup(points, polygons); + assert(res == 0 && polygons.size() == 2); + + // ------------------------------------------------------- + // Multiple duplicates + // duplicate, same orientations + polygon.clear(); + polygon.push_back(2); polygon.push_back(0); polygon.push_back(1); + polygons.push_back(polygon); + + // duplicate, different orientations + polygon.clear(); + polygon.push_back(2); polygon.push_back(1); polygon.push_back(0); + polygons.push_back(polygon); + + // duplicate, different orientations + polygon.clear(); + polygon.push_back(4); polygon.push_back(3); polygon.push_back(1); polygon.push_back(0); + polygons.push_back(polygon); + + // same vertices, not a duplicate + polygon.clear(); + polygon.push_back(3); polygon.push_back(4); polygon.push_back(1); polygon.push_back(0); + polygons.push_back(polygon); + + PMP::internal::collect_duplicate_polygons(points, polygons, + std::back_inserter(all_duplicate_polygons), + K(), true /*only equal if same orientation*/); + assert(all_duplicate_polygons.size() == 1); // one duplication + assert(all_duplicate_polygons[0].size() == 2); // two polygons are equal + all_duplicate_polygons.clear(); + + PMP::internal::collect_duplicate_polygons(points, polygons, + std::back_inserter(all_duplicate_polygons), + K(), false /*equal regardless of orientation*/); + assert(all_duplicate_polygons.size() == 2); + + // not sure which duplicate is output first + if(all_duplicate_polygons[0][0] == 0) + assert(all_duplicate_polygons[0].size() == 3 && all_duplicate_polygons[1].size() == 2); + else + assert(all_duplicate_polygons[0].size() == 2 && all_duplicate_polygons[1].size() == 3); + + // Keep one for each duplicate + std::vector polygons_copy(polygons); + res = PMP::merge_duplicate_polygons_in_polygon_soup(points, polygons_copy, + params::all_default()); + assert(res == 3 && polygons_copy.size() == 3); + + // Remove all duplicates + polygons_copy = polygons; + res = PMP::merge_duplicate_polygons_in_polygon_soup(points, polygons_copy, + params::erase_all_duplicates(true) + .require_same_orientation(false)); + assert(res == 5 && polygons_copy.size() == 1); + + // Remove all duplicates but different orientations are different polygons + res = PMP::merge_duplicate_polygons_in_polygon_soup(points, polygons, + params::erase_all_duplicates(true) + .require_same_orientation(true)); + assert(res == 2 && polygons.size() == 4); +} + +void test_simplify_polygons(const bool /*verbose*/ = false) +{ + std::cout << "test simplify_polygons... " << std::endl; + + std::vector points; + std::vector polygons; + + points.push_back(Point_3(0,0,0)); // #0 + points.push_back(Point_3(1,2,0)); // #1 + points.push_back(Point_3(1,0,0)); // #2 + points.push_back(Point_3(1,3,0)); // #3 + points.push_back(Point_3(0,1,0)); // #4 + points.push_back(Point_3(1,1,0)); // #5 + points.push_back(Point_3(0,0,0)); // #6 + + // ------ + Polygon polygon; + polygon.push_back(0); polygon.push_back(2); polygon.push_back(4); + polygons.push_back(polygon); + + std::size_t res = PMP::internal::simplify_polygons_in_polygon_soup(points, polygons); + assert(res == 0 && polygons.back().size() == 3); + + // ------ + polygon.clear(); + polygon.push_back(0); polygon.push_back(0); + polygons.push_back(polygon); + + res = PMP::internal::simplify_polygons_in_polygon_soup(points, polygons, K()); + assert(res == 1 && polygons.back().size() == 1); + + // ------ + polygon.clear(); + polygon.push_back(0); polygon.push_back(0); polygon.push_back(0); polygon.push_back(0); + polygons.push_back(polygon); + + res = PMP::internal::simplify_polygons_in_polygon_soup(points, polygons, K()); + assert(res == 1 && polygons.back().size() == 1); + + // ------ + polygon.clear(); + polygon.push_back(0); polygon.push_back(3); polygon.push_back(3); polygon.push_back(3); polygon.push_back(0); + polygons.push_back(polygon); + + res = PMP::internal::simplify_polygons_in_polygon_soup(points, polygons, K()); + assert(res == 1 && polygons.back().size() == 2); + + // ------ + // Now with the same geometric positions, but different combinatorial information + polygon.clear(); + polygon.push_back(0); polygon.push_back(2); polygon.push_back(1); polygon.push_back(6); + polygons.push_back(polygon); + + res = PMP::internal::simplify_polygons_in_polygon_soup(points, polygons, K()); + assert(res == 1 && polygons.back().size() == 3); + + // ------ + polygon.clear(); + polygon.push_back(0); polygon.push_back(6); polygon.push_back(0); polygon.push_back(5); + polygons.push_back(polygon); + + res = PMP::internal::simplify_polygons_in_polygon_soup(points, polygons, K()); + assert(res == 1 && polygons.back().size() == 2); + + // ------ + polygon.clear(); + polygon.push_back(0); polygon.push_back(6); polygon.push_back(5); polygon.push_back(3); polygon.push_back(3); + polygons.push_back(polygon); + + res = PMP::internal::simplify_polygons_in_polygon_soup(points, polygons, K()); + assert(res == 1 && polygons.back().size() == 3); +} + +void test_remove_invalid_polygons(const bool /*verbose*/ = false) +{ + std::cout << "test remove_invalid_polygons... " << std::endl; + + // points are not actually needed since only the size of the polygons is considered + std::vector points; + std::vector polygons; + + std::size_t res = PMP::internal::remove_invalid_polygons_in_polygon_soup(points, polygons); + assert(res == 0 && polygons.size() == 0); + + // non-trivial polygon + Polygon polygon; + polygon.push_back(0); polygon.push_back(2); polygon.push_back(4); + polygons.push_back(polygon); + + res = PMP::internal::remove_invalid_polygons_in_polygon_soup(points, polygons); + assert(res == 0 && polygons.size() == 1); + + // another non-trivial polygon + polygon.clear(); + polygon.push_back(1); polygon.push_back(3); polygon.push_back(3); polygon.push_back(7); + polygons.push_back(polygon); + + // empty polygon + polygon.clear(); + polygons.push_back(polygon); + + // 1-vertex polygon + polygon.push_back(0); + polygons.push_back(polygon); + + // 2-vertex polygon + polygon.push_back(1); + polygons.push_back(polygon); + + // another non-trivial polygon + polygon.clear(); + polygon.push_back(8); polygon.push_back(9); polygon.push_back(7); polygon.push_back(6); + polygons.push_back(polygon); + + res = PMP::internal::remove_invalid_polygons_in_polygon_soup(points, polygons); + assert(res == 3 && polygons.size() == 3); +} + +template +std::size_t test_remove_isolated_points_data_set(PointRange& points, + PolygonRange& polygons, + const bool verbose = false) +{ + if(verbose) + { + std::cout << "Input:" << std::endl; + std::cout << points.size() << " points" << std::endl; + std::cout << polygons.size() << " polygons" << std::endl; + } + + std::size_t rm_nv = PMP::remove_isolated_points_in_polygon_soup(points, polygons); + + if(verbose) + { + std::cout << "Removed " << rm_nv << " points" << std::endl; + std::cout << points.size() << " points" << std::endl; + std::cout << polygons.size() << " polygons" << std::endl; + + std::cout << "Polygons:" << std::endl; + for(std::size_t i=0; i points; + std::vector polygons; + + // everything empty + std::size_t res = test_remove_isolated_points_data_set(points, polygons, verbose); + assert(res == 0 && points.empty() && polygons.empty()); + + points.push_back(Point_3(0,0,0)); + points.push_back(Point_3(1,2,0)); + points.push_back(Point_3(1,0,0)); + points.push_back(Point_3(1,3,0)); + points.push_back(Point_3(0,1,0)); + points.push_back(Point_3(1,1,0)); + + // no polygons (all points are unused) + std::deque points_copy(points.begin(), points.end()); + res = test_remove_isolated_points_data_set(points_copy, polygons, verbose); + assert(res == 6 && points_copy.empty() && polygons.empty()); + + Polygon polygon; + polygon.push_back(0); polygon.push_back(2); polygon.push_back(4); + polygons.push_back(polygon); + + polygon.clear(); + polygon.push_back(4); polygon.push_back(0); polygon.push_back(2); polygon.push_back(5); + polygons.push_back(polygon); + + // generic test + res = test_remove_isolated_points_data_set(points, polygons, verbose); + assert(res == 2 && points.size() == 4 && polygons.size() == 2); +} + +void test_slit_pinched_polygons(const bool /*verbose*/ = false) +{ + std::cout << "test split_pinched_polygons... " << std::endl; + + std::vector points; + std::vector polygons; + + // everything empty + std::size_t res = PMP::internal::split_pinched_polygons_in_polygon_soup(points, polygons); + assert(res == 0 && points.empty() && polygons.empty()); + + points.push_back(Point_3(0,0,0)); // #0 + points.push_back(Point_3(1,0,0)); // #1 + points.push_back(Point_3(0,1,0)); // #2 + points.push_back(Point_3(1,1,0)); // #3 + points.push_back(Point_3(1,3,0)); // #4 + points.push_back(Point_3(1,1,0)); // #5 + + // no pinch + Polygon polygon; + polygon.push_back(0); polygon.push_back(2); polygon.push_back(4); + polygons.push_back(polygon); + + res = PMP::internal::split_pinched_polygons_in_polygon_soup(points, polygons, K()); + assert(res == 0 && polygons.size() == 1); + + // pinch via same ID (2) + polygon.clear(); + polygons.clear(); + polygon.push_back(0); polygon.push_back(2); polygon.push_back(4); polygon.push_back(3); polygon.push_back(2); + polygons.push_back(polygon); + + res = PMP::internal::split_pinched_polygons_in_polygon_soup(points, polygons, K()); + assert(res == 1 && polygons.size() == 2); + assert(polygons[0].size() == 3 && polygons[1].size() == 2); + + // pinch via same point (5 & 3) + polygon.clear(); + polygons.clear(); + polygon.push_back(5); polygon.push_back(1); polygon.push_back(0); + polygon.push_back(3); polygon.push_back(4); polygon.push_back(2); + polygons.push_back(polygon); + + res = PMP::internal::split_pinched_polygons_in_polygon_soup(points, polygons, K()); + assert(res == 1 && polygons.size() == 2); + assert(polygons[0].size() == 3 && polygons[1].size() == 3); + + // pinch on last point + polygon.clear(); + polygons.clear(); + polygon.push_back(1); polygon.push_back(5); polygon.push_back(0); + polygon.push_back(2); polygon.push_back(4); polygon.push_back(3); + polygons.push_back(polygon); + + res = PMP::internal::split_pinched_polygons_in_polygon_soup(points, polygons, K()); + assert(res == 1 && polygons.size() == 2); + assert(polygons[0].size() == 4 && polygons[1].size() == 2); + + // multiple pinches + polygon.clear(); + polygons.clear(); + polygon.push_back(5); polygon.push_back(1); polygon.push_back(3); // pinch 5&3, pinch 3... + polygon.push_back(2); polygon.push_back(4); polygon.push_back(0); + polygon.push_back(1); polygon.push_back(3); polygon.push_back(1); // ... and 3, pinch 1... + polygon.push_back(2); polygon.push_back(4); polygon.push_back(1); // ... and 1 + polygons.push_back(polygon); + + res = PMP::internal::split_pinched_polygons_in_polygon_soup(points, polygons, K()); + assert(res == 3 && polygons.size() == 4); + assert(polygons[0].size() == 2); // 5 1 + assert(polygons[1].size() == 5); // 3 2 4 5 1 + assert(polygons[2].size() == 3); // 1 2 4 + assert(polygons[3].size() == 2); // 1 3 +} + +int main() +{ + test_polygon_canonicalization(true); + test_merge_duplicate_points(false); + test_merge_duplicate_polygons(false); + test_simplify_polygons(false); + test_remove_invalid_polygons(false); + test_remove_isolated_points(false); + test_slit_pinched_polygons(false); + + return EXIT_SUCCESS; +} diff --git a/Polyhedron_IO/include/CGAL/IO/OFF_reader.h b/Polyhedron_IO/include/CGAL/IO/OFF_reader.h index 17741fe1933..d1d5ddf502c 100644 --- a/Polyhedron_IO/include/CGAL/IO/OFF_reader.h +++ b/Polyhedron_IO/include/CGAL/IO/OFF_reader.h @@ -21,6 +21,7 @@ #define CGAL_IO_OFF_READER_H #include +#include #include #include @@ -28,33 +29,7 @@ #include #include -namespace CGAL{ - - namespace read_OFF_internal{ - template - void fill_point(double x, double y, double z, Point_3& pt) - { - pt = Point_3(x, y, z); - } - - void fill_point(double x, double y, double z, CGAL::cpp11::array& p) - { - p = CGAL::make_array(x,y,z); - } - - template - void resize(Polygon_3& p, std::size_t size) - { - p.resize(size); - } - - template - void resize(CGAL::cpp11::array&, std::size_t size) - { - CGAL_USE(size); - CGAL_assertion( size>=N ); - } - } +namespace CGAL { template bool @@ -71,7 +46,7 @@ namespace CGAL{ double x, y, z, w; scanner.scan_vertex( x, y, z, w); CGAL_assertion(w!=0); - read_OFF_internal::fill_point( x/w, y/w, z/w, points[i] ); + IO::internal::fill_point( x/w, y/w, z/w, points[i] ); scanner.skip_to_next_vertex( i); } if(!in) @@ -81,7 +56,7 @@ namespace CGAL{ std::size_t no; scanner.scan_facet( no, i); - read_OFF_internal::resize(polygons[i], no); + IO::internal::resize(polygons[i], no); for(std::size_t j = 0; j < no; ++j) { std::size_t id; scanner.scan_facet_vertex_index(id, i); @@ -114,7 +89,7 @@ namespace CGAL{ double x, y, z, w; scanner.scan_vertex( x, y, z, w); CGAL_assertion(w!=0); - read_OFF_internal::fill_point( x/w, y/w, z/w, points[i] ); + IO::internal::fill_point( x/w, y/w, z/w, points[i] ); if(scanner.has_colors()) { unsigned char r=0, g=0, b=0; @@ -131,7 +106,7 @@ namespace CGAL{ std::size_t no; scanner.scan_facet( no, i); - read_OFF_internal::resize(polygons[i], no); + IO::internal::resize(polygons[i], no); for(std::size_t j = 0; j < no; ++j) { std::size_t id; scanner.scan_facet_vertex_index(id, i); diff --git a/Polyhedron_IO/include/CGAL/IO/STL_reader.h b/Polyhedron_IO/include/CGAL/IO/STL_reader.h index 5bf8cde0059..34f0938af36 100644 --- a/Polyhedron_IO/include/CGAL/IO/STL_reader.h +++ b/Polyhedron_IO/include/CGAL/IO/STL_reader.h @@ -21,8 +21,8 @@ #ifndef CGAL_IO_STL_READER_H #define CGAL_IO_STL_READER_H -#include #include +#include #include @@ -34,11 +34,12 @@ namespace CGAL { +template bool read_ASCII_facet(std::istream& input, - std::vector >& points, - std::vector >& facets, + std::vector& points, + std::vector& facets, int& index, - std::map, int >& index_map, + std::map& index_map, bool verbose = false) { // Here, we have already read the word 'facet' and are looking to read till 'endfacet' @@ -48,8 +49,10 @@ bool read_ASCII_facet(std::istream& input, endfacet("endfacet"); int count = 0; - cpp11::array p; - cpp11::array ijk; + double x,y,z; + Point p; + Triangle ijk; + IO::internal::resize(ijk, 3); while(input >> s) { @@ -76,7 +79,7 @@ bool read_ASCII_facet(std::istream& input, return false; } - if(!(input >> iformat(p[0]) >> iformat(p[1]) >> iformat(p[2]))) + if(!(input >> iformat(x) >> iformat(y) >> iformat(z))) { if(verbose) std::cerr << "Error while reading point coordinates (premature end of file)" << std::endl; @@ -85,8 +88,8 @@ bool read_ASCII_facet(std::istream& input, } else { - std::map, int>::iterator iti= - index_map.insert(std::make_pair(p, -1)).first; + IO::internal::fill_point(x, y, z, p); + typename std::map::iterator iti = index_map.insert(std::make_pair(p, -1)).first; if(iti->second == -1) { @@ -110,9 +113,10 @@ bool read_ASCII_facet(std::istream& input, return false; } +template bool parse_ASCII_STL(std::istream& input, - std::vector >& points, - std::vector >& facets, + std::vector& points, + std::vector& facets, bool verbose = false) { if(verbose) @@ -124,11 +128,9 @@ bool parse_ASCII_STL(std::istream& input, // Here, we have already read the word 'solid' int index = 0; - std::map, int> index_map; + std::map index_map; - std::string s; - std::string facet("facet"), - endsolid("endsolid"); + std::string s, facet("facet"), endsolid("endsolid"); while(input >> s) { @@ -149,9 +151,10 @@ bool parse_ASCII_STL(std::istream& input, return false; } +template bool parse_binary_STL(std::istream& input, - std::vector >& points, - std::vector >& facets, + std::vector& points, + std::vector& facets, bool verbose = false) { if(verbose) @@ -190,7 +193,7 @@ bool parse_binary_STL(std::istream& input, return true; // empty file int index = 0; - std::map, int> index_map; + std::map index_map; boost::uint32_t N32; if(!(input.read(reinterpret_cast(&N32), sizeof(N32)))) @@ -218,7 +221,8 @@ bool parse_binary_STL(std::istream& input, return false; } - cpp11::array ijk; + Triangle ijk; + IO::internal::resize(ijk, 3); for(int j=0; j<3; ++j) { @@ -233,11 +237,10 @@ bool parse_binary_STL(std::istream& input, return false; } - cpp11::array p; - p[0] = x; p[1] = y; p[2] = z; + Point p; + IO::internal::fill_point(x, y, z, p); - std::map, int>::iterator iti = - index_map.insert(std::make_pair(p, -1)).first; + typename std::map::iterator iti = index_map.insert(std::make_pair(p, -1)).first; if(iti->second == -1) { @@ -268,9 +271,29 @@ bool parse_binary_STL(std::istream& input, return true; } +// +// Read a file with `.stl` format. +// +// \tparam Point must be a model of the concept `RandomAccessContainer` or a %CGAL point type +// \tparam Triangle must be a model of the concept `RandomAccessContainer` +// +// \param input the input stream +// \param points a container that will contain the points used in the .stl file +// \param polygons a container that will contain the triangles used in the .stl file +// \param verbose whether to enable or not a sanity log +// +// \returns `true` if the reading process went well, `false` otherwise +// +// \warning `points` and `facets` are not cleared: new points and triangles are added to the back +// of the containers. +// +// Although the STL file format uses triangles, it is convenient to be able to use vectors +// and other models of the `SequenceContainer` (instead of arrays) for the face type, +// to avoid having to convert the to apply polygon soup reparation algorithms. +template bool read_STL(std::istream& input, - std::vector >& points, - std::vector >& facets, + std::vector& points, + std::vector& facets, bool verbose = false) { int pos = 0; diff --git a/Polyhedron_IO/include/CGAL/IO/reader_helpers.h b/Polyhedron_IO/include/CGAL/IO/reader_helpers.h new file mode 100644 index 00000000000..7e6ebeb89ad --- /dev/null +++ b/Polyhedron_IO/include/CGAL/IO/reader_helpers.h @@ -0,0 +1,90 @@ +// Copyright (c) 2015 GeometryFactory +// +// This file is part of CGAL (www.cgal.org); you can redistribute it and/or +// modify it under the terms of the GNU Lesser 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: LGPL-3.0+ +// +// Author(s) : Mael Rouxel-Labbé + +#ifndef CGAL_IO_READER_HELPERS_H +#define CGAL_IO_READER_HELPERS_H + +#include +#include +#include + +#include +#include + +namespace CGAL{ + +namespace IO { + +namespace internal { + +CGAL_GENERATE_MEMBER_DETECTOR(size); +CGAL_GENERATE_MEMBER_DETECTOR(resize); + +// Typical container +template +void resize(Container& c, std::size_t size, + typename boost::enable_if_c::value>::type* = NULL) +{ + c.resize(size); +} + +// Container without a resize() function, but with a size() function (e.g. an array) +template +void resize(Container& CGAL_assertion_code(array), std::size_t CGAL_assertion_code(size), + typename boost::enable_if< + boost::mpl::and_< + boost::mpl::not_ >, + has_size > >::type* = NULL) +{ + CGAL_assertion(array.size() == size); +} + +// A class with neither resize() nor size(), can't enforce size (it better be correct!) +template +void resize(Container&, std::size_t, + typename boost::disable_if< + boost::mpl::or_, + has_size > >::type* = NULL) +{ +} + +// Ideally this should be a std::is_constructible(double, double, double) but boost::is_constructible +// is not safe to use without CXX11 +template +void fill_point(const double x, const double y, const double z, CGAL::Point_3& pt) +{ + pt = CGAL::Point_3(x, y, z); +} + +template +void fill_point(const double x, const double y, const double z, Point_3& pt) +{ + // just in case something weirder than arrays or CGAL points are used as points... + resize(pt, 3); + + pt[0] = x; pt[1] = y; pt[2] = z; +} + +} // end namespace internal + +} // end namespace IO + +} // namespace CGAL + +#endif // CGAL_IO_READER_HELPERS_H diff --git a/Polyhedron_IO/test/Polyhedron_IO/stl2off.cpp b/Polyhedron_IO/test/Polyhedron_IO/stl2off.cpp index f12ad5fd6d6..79f1f55226e 100644 --- a/Polyhedron_IO/test/Polyhedron_IO/stl2off.cpp +++ b/Polyhedron_IO/test/Polyhedron_IO/stl2off.cpp @@ -1,53 +1,65 @@ -#include -#include -#include +#include +#include +#include + +#include #include #include #include +template void read(const char* fname, std::size_t v, std::size_t f) { std::cout << "Reading "<< fname << std::endl; std::ifstream input(fname, std::ios::in | std::ios::binary); - std::vector< CGAL::cpp11::array > points; - std::vector< CGAL::cpp11::array > faces; + std::cout << "Types: " << std::endl; + std::cout << typeid(Point_type).name() << std::endl; + std::cout << typeid(Polygon_type).name() << std::endl; - CGAL::read_STL( input, - points, - faces, - true); + std::cout << "Expecting " << v << " vertices and " << f << " triangles" << std::endl; - assert(points.size() == v); - assert(faces.size() == f); + std::vector points; + std::vector faces; + assert(CGAL::read_STL(input, points, faces, true)); std::cout << "OFF version of file " << fname << std::endl; - std::cout.precision(17); std::cout << "OFF\n" << points.size() << " " << faces.size() << " 0" << std::endl; - for(std::size_t i=0; i < points.size(); i++){ + for(std::size_t i=0; i < points.size(); i++) std::cout << points[i][0] << " " << points[i][1] << " " << points[i][2]<< std::endl; - } - for(std::size_t i=0; i < faces.size(); i++){ + for(std::size_t i=0; i < faces.size(); i++) std::cout << "3 " << faces[i][0] << " " << faces[i][1] << " " << faces[i][2] << std::endl; - } + + assert(points.size() == v); + assert(faces.size() == f); } - -int main() +int main(int, char**) { - read("data/cube.stl", 8, 12); - read("data/triangle.stl", 3, 1); + std::cout.precision(17); - read("data/ascii-tetrahedron.stl", 4, 4); - read("data/binary-tetrahedron-nice-header.stl", 4, 4); - read("data/binary-tetrahedron-non-standard-header-1.stl", 4, 4); - read("data/binary-tetrahedron-non-standard-header-2.stl", 4, 4); - read("data/binary-tetrahedron-non-standard-header-3.stl", 4, 4); - read("data/binary-tetrahedron-non-standard-header-4.stl", 4, 4); - read("data/binary-tetrahedron-non-standard-header-5.stl", 4, 4); + // bunch of types to test + typedef CGAL::cpp11::array Point_type_1; + typedef CGAL::Exact_predicates_exact_constructions_kernel::Point_3 Point_type_2; + typedef std::basic_string Point_type_3; - return 0; + typedef CGAL::cpp11::array Polygon_type_1; + typedef std::vector Polygon_type_2; + typedef std::basic_string Polygon_type_3; + + read("data/cube.stl", 8, 12); + read("data/triangle.stl", 3, 1); + + read("data/ascii-tetrahedron.stl", 4, 4); + read("data/binary-tetrahedron-nice-header.stl", 4, 4); + read("data/binary-tetrahedron-non-standard-header-1.stl", 4, 4); + read("data/binary-tetrahedron-non-standard-header-2.stl", 4, 4); + read("data/binary-tetrahedron-non-standard-header-3.stl", 4, 4); + read("data/binary-tetrahedron-non-standard-header-4.stl", 4, 4); + read("data/binary-tetrahedron-non-standard-header-5.stl", 4, 4); + + return EXIT_SUCCESS; } diff --git a/STL_Extension/include/CGAL/Has_member.h b/STL_Extension/include/CGAL/Has_member.h new file mode 100644 index 00000000000..d35faad9448 --- /dev/null +++ b/STL_Extension/include/CGAL/Has_member.h @@ -0,0 +1,45 @@ +// Copyright (c) 2017 Inria (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 Lesser 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: LGPL-3.0+ +// +// Author(s) : Clement Jamin + +#ifndef CGAL_HAS_MEMBER_H +#define CGAL_HAS_MEMBER_H + +// Macro used to check if a type T has a member named `X` +// It generates a class has_X where has_X::value is a boolean +// See example in Concurrent_compact_container.h +#define CGAL_GENERATE_MEMBER_DETECTOR(X) \ +template class has_##X { \ + struct Fallback { int X; }; \ + struct Derived : T, Fallback { }; \ + \ + template struct Check; \ + \ + typedef char ArrayOfOne[1]; \ + typedef char ArrayOfTwo[2]; \ + \ + template static ArrayOfOne & func( \ + Check *); \ + template static ArrayOfTwo & func(...); \ + public: \ + typedef has_##X type; \ + enum { value = sizeof(func(0)) == 2 }; \ +} // semicolon is after the macro call + +#endif // CGAL_HAS_MEMBER_H diff --git a/Surface_mesh/include/CGAL/Surface_mesh/Surface_mesh.h b/Surface_mesh/include/CGAL/Surface_mesh/Surface_mesh.h index 8ac55c3a311..8ab62548da3 100644 --- a/Surface_mesh/include/CGAL/Surface_mesh/Surface_mesh.h +++ b/Surface_mesh/include/CGAL/Surface_mesh/Surface_mesh.h @@ -73,7 +73,7 @@ namespace CGAL { public: typedef boost::uint32_t size_type; /// Constructor. %Default construction creates an invalid index. - /// We write -1, which is + /// We write -1, which is /// std::numeric_limits::max() /// as `size_type` is an unsigned type. explicit SM_Index(size_type _idx=(std::numeric_limits::max)()) : idx_(_idx) {} diff --git a/Surface_mesh_parameterization/include/CGAL/Surface_mesh_parameterization/Orbifold_Tutte_parameterizer_3.h b/Surface_mesh_parameterization/include/CGAL/Surface_mesh_parameterization/Orbifold_Tutte_parameterizer_3.h index fdb68bc3d09..5d52a2a2f49 100644 --- a/Surface_mesh_parameterization/include/CGAL/Surface_mesh_parameterization/Orbifold_Tutte_parameterizer_3.h +++ b/Surface_mesh_parameterization/include/CGAL/Surface_mesh_parameterization/Orbifold_Tutte_parameterizer_3.h @@ -210,7 +210,7 @@ Error_code read_cones(const TriangleMesh& tm, const char* filename, ConeOutputIt /// having to pass the multiple template parameters of the class `CGAL::Seam_mesh`. /// \tparam ConeInputBidirectionalIterator must be a model of `BidirectionalIterator` /// with value type `boost::graph_traits::%vertex_descriptor`. -/// \tparam ConeMap must be a model of AssociativeContainer +/// \tparam ConeMap must be a model of `AssociativeContainer` /// with `boost::graph_traits::%vertex_descriptor` as key type and /// \link PkgSurfaceParameterizationEnums Cone_type \endlink as value type. /// @@ -896,7 +896,7 @@ public: /// The mapping is piecewise linear (linear in each triangle). /// The result is the (u,v) pair image of each vertex of the 3D surface. /// - /// \tparam ConeMap must be a model of AssociativeContainer + /// \tparam ConeMap must be a model of `AssociativeContainer` /// with key type `boost::graph_traits::%vertex_descriptor` and /// \link PkgSurfaceParameterizationEnums Cone_type \endlink as value type. /// \tparam VertexUVmap must be a model of `ReadWritePropertyMap` with diff --git a/Surface_mesh_parameterization/include/CGAL/Surface_mesh_parameterization/orbifold_shortest_path.h b/Surface_mesh_parameterization/include/CGAL/Surface_mesh_parameterization/orbifold_shortest_path.h index f3f641c5e19..8dd9b09548c 100644 --- a/Surface_mesh_parameterization/include/CGAL/Surface_mesh_parameterization/orbifold_shortest_path.h +++ b/Surface_mesh_parameterization/include/CGAL/Surface_mesh_parameterization/orbifold_shortest_path.h @@ -175,8 +175,8 @@ void compute_shortest_paths_between_two_cones(const TriangleMesh& mesh, /// \tparam TriangleMesh A triangle mesh, model of `FaceListGraph` and `HalfedgeListGraph`. /// \tparam InputConesForwardIterator A model of `ForwardIterator` with value type /// `boost::graph_traits::%vertex_descriptor`. -/// \tparam SeamContainer A model of SequenceContainer -/// with value type `boost::graph_traits::%edge_descriptor`. +/// \tparam SeamContainer A model of `SequenceContainer` with value type +/// `boost::graph_traits::%edge_descriptor`. /// /// \param mesh the triangular mesh on which paths are computed /// \param first, beyond a range of cones