From 16a9bc4d7c795dc584c391a803f472b6ccdea022 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mael=20Rouxel-Labb=C3=A9?= Date: Thu, 26 Jul 2018 16:27:58 +0200 Subject: [PATCH 01/23] Fixed some links / concepts related to sgi.com and cppreference --- BGL/include/CGAL/boost/graph/Seam_mesh.h | 4 +- .../Barycentric_coordinates_2.txt | 2 +- Documentation/doc/Documentation/General.txt | 54 +++++++++++++------ .../CGAL/Polyhedral_complex_mesh_domain_3.h | 2 +- .../include/CGAL/Surface_mesh/Surface_mesh.h | 2 +- .../Orbifold_Tutte_parameterizer_3.h | 4 +- .../orbifold_shortest_path.h | 4 +- 7 files changed, 47 insertions(+), 25 deletions(-) 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/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 0941991e41c..cf46e5e08a8 100644 --- a/Documentation/doc/Documentation/General.txt +++ b/Documentation/doc/Documentation/General.txt @@ -1,33 +1,33 @@ ///This concept refers to the one described at http://www.sgi.com/tech/stl/Assignable.html. /// ///Using the latest concepts of the \cpp standard, a type that is a model of this concept -///is both CopyAssignable -///and CopyConstructible. +///is both CopyAssignable +///and CopyConstructible. class Assignable {}; /// \cgalConcept /// Concept from the \cpp standard. -/// See http://en.cppreference.com/w/cpp/concept/DefaultConstructible +/// See https://en.cppreference.com/w/cpp/named_req/DefaultConstructible class DefaultConstructible {}; /// \cgalConcept /// Concept from the \cpp standard. -/// See http://en.cppreference.com/w/cpp/concept/CopyConstructible +/// See https://en.cppreference.com/w/cpp/named_req/CopyConstructible class CopyConstructible {}; /// \cgalConcept /// Concept from the \cpp standard. -/// See http://en.cppreference.com/w/cpp/concept/Callable +/// See https://en.cppreference.com/w/cpp/named_req/Callable class Callable {}; /// \cgalConcept /// Concept from the \cpp standard. -/// See http://en.cppreference.com/w/cpp/concept/EqualityComparable +/// See https://en.cppreference.com/w/cpp/named_req/EqualityComparable class EqualityComparable {}; /// \cgalConcept /// Concept from the \cpp standard. -/// See http://en.cppreference.com/w/cpp/concept/LessThanComparable +/// See https://en.cppreference.com/w/cpp/named_req/LessThanComparable class LessThanComparable {}; /// \cgalConcept @@ -38,52 +38,74 @@ class AdaptableFunctor {}; /// \cgalConcept ///This concept refers to the one described at http://www.sgi.com/tech/stl/AdaptableUnaryFunction.html. class AdaptableUnaryFunction {}; + /// \cgalConcept ///This concept refers to the one described at http://www.sgi.com/tech/stl/AdaptableBinaryFunction.html. class AdaptableBinaryFunction {}; /// \cgalConcept /// Concept from the \cpp standard. -/// See http://en.cppreference.com/w/cpp/concept/Iterator +/// See https://en.cppreference.com/w/cpp/named_req/Iterator class Iterator {}; /// \cgalConcept /// Concept from the \cpp standard. -/// See http://en.cppreference.com/w/cpp/concept/OutputIterator +/// See https://en.cppreference.com/w/cpp/named_req/OutputIterator class OutputIterator {}; /// \cgalConcept /// Concept from the \cpp standard. -/// See http://en.cppreference.com/w/cpp/concept/InputIterator +/// See https://en.cppreference.com/w/cpp/named_req/InputIterator class InputIterator {}; /// \cgalConcept /// Concept from the \cpp standard. -/// See http://en.cppreference.com/w/cpp/concept/ForwardIterator +/// See https://en.cppreference.com/w/cpp/named_req/ForwardIterator class ForwardIterator {}; /// \cgalConcept /// Concept from the \cpp standard. -/// See http://en.cppreference.com/w/cpp/concept/RandomAccessIterator +/// See https://en.cppreference.com/w/cpp/named_req/RandomAccessIterator class RandomAccessIterator {}; /// \cgalConcept /// Concept from the \cpp standard. -/// See http://en.cppreference.com/w/cpp/concept/BidirectionalIterator +/// 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/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 concept refers to the one described at http://www.sgi.com/tech/stl/RandomAccessContainer.html. /// /// It is a container that is a model of -/// ReversibleContainer and whose iterator type is a model of -/// RandomAccessIterator. +/// ReversibleContainer and whose iterator type is a model of +/// RandomAccessIterator. class RandomAccessContainer {}; + /// \cgalConcept /// This concepts refers to the one described at http://www.sgi.com/tech/stl/BackInsertionSequence.html. /// /// It is a container that is a model of -/// SequenceContainer +/// Container /// that has the ability to append elements at the end of the sequence and to access the last element, both in amortized constant time. class BackInsertionSequence {}; 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 600de3aeb0c..726068f1f76 100644 --- a/Mesh_3/include/CGAL/Polyhedral_complex_mesh_domain_3.h +++ b/Mesh_3/include/CGAL/Polyhedral_complex_mesh_domain_3.h @@ -404,9 +404,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/Surface_mesh/include/CGAL/Surface_mesh/Surface_mesh.h b/Surface_mesh/include/CGAL/Surface_mesh/Surface_mesh.h index b28d643f531..6adea720fb7 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 3a21e30b78d..bfa4d723e52 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 From ed6ebae2deac247bb038763754738d5b96fbeaaa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mael=20Rouxel-Labb=C3=A9?= Date: Thu, 26 Jul 2018 16:30:28 +0200 Subject: [PATCH 02/23] Added repair_polygon_soup.h --- .../repair_polygon_soup.h | 439 ++++++++++++++++++ .../include/CGAL/polygon_mesh_processing.h | 1 + 2 files changed, 440 insertions(+) create mode 100644 Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/repair_polygon_soup.h diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/repair_polygon_soup.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/repair_polygon_soup.h new file mode 100644 index 00000000000..c389c16dcaf --- /dev/null +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/repair_polygon_soup.h @@ -0,0 +1,439 @@ +// 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) : Mael Rouxel-Labbé + +#ifndef CGAL_POLYGON_MESH_PROCESSING_REPAIR_POLYGON_SOUP +#define CGAL_POLYGON_MESH_PROCESSING_REPAIR_POLYGON_SOUP + +#include + +#include +#include + +#include + +#include +#include +#include +#include +#include + +namespace CGAL { + +namespace Polygon_mesh_processing { + +namespace internal { + +template +struct Polygon_types +{ + typedef typename PointRange::value_type Point_3; + typedef typename PolygonRange::value_type Polygon_3; + + typedef typename std::iterator_traits::value_type V_ID; + typedef typename std::vector::size_type P_ID; +}; + +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 +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_DEBUG + std::cout << "removing 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` +// \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; + } + + return simplified_polygons_n; +} + +// \ingroup PMP_repairing_grp +// +// Splits "pinched" poygons, that is polygons for which a point appears more than once, +// into multiple non-pinched polygons. +// +// \tparam PointRange a model of the concept `RandomAccessContainer` +// \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 `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_DEBUG + std::cout << "Input polygon: "; + print_polygon(std::cout, polygon); +#endif + + if(ini_polygon_size <= 3) + continue; + + CGAL_assertion(!simplify_polygon(points, polygon, traits)); // 'polygon' must not have duplicates + + 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_succesful = + unique_points.insert(std::make_pair(p, i)); + + if(!is_insert_succesful.second) + { + // We have already met that point, split the polygon into two smaller polygons + std::size_t prev_id = is_insert_succesful.first->second; + + CGAL_assertion(prev_id < i-1); + + Polygon_3 split_polygon_1(polygon.begin() + prev_id, polygon.begin() + i); + 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_DEBUG + std::cout << "New polygons:" << std::endl; + std::cout << "P1:"; + print_polygon(std::cout, split_polygon_1); + + std::cout << "P2:"; + 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` +// \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) + to_remove.push_back(polygon_index); + } + + while(!to_remove.empty()) + { + polygons.erase(polygons.begin() + to_remove.back()); + to_remove.pop_back(); + } + + return ini_polygons_size - polygons.size(); +} + +} // 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` +/// \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 +void repair_polygon_soup(PointRange& points, + PolygonRange& polygons, + const NamedParameters& np) +{ + using boost::get_param; + using boost::choose_param; + + typedef typename boost::lookup_named_param_def < + internal_np::geom_traits_t, + NamedParameters, + typename CGAL::Kernel_traits::Point_3 >::type + > ::type Traits; + + Traits traits = choose_param(get_param(np, internal_np::geom_traits), Traits()); + + 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); + + 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 7d587ad4b84..ebf1c64da50 100644 --- a/Polygon_mesh_processing/include/CGAL/polygon_mesh_processing.h +++ b/Polygon_mesh_processing/include/CGAL/polygon_mesh_processing.h @@ -49,6 +49,7 @@ #include #include #include +#include // the named parameter header being not documented the doc is put here for now #ifdef DOXYGEN_RUNNING From 4a33cde776110dd8fc1b9dc7a110a7b4dc794173 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mael=20Rouxel-Labb=C3=A9?= Date: Thu, 26 Jul 2018 16:30:56 +0200 Subject: [PATCH 03/23] Added example and test for repair_polygon_soup --- .../Polygon_mesh_processing/CMakeLists.txt | 1 + .../repair_polygon_soup_example.cpp | 76 +++++ .../Polygon_mesh_processing/CMakeLists.txt | 1 + .../test_repair_polygon_soup.cpp | 289 ++++++++++++++++++ 4 files changed, 367 insertions(+) create mode 100644 Polygon_mesh_processing/examples/Polygon_mesh_processing/repair_polygon_soup_example.cpp create mode 100644 Polygon_mesh_processing/test/Polygon_mesh_processing/test_repair_polygon_soup.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..f53a92a678d 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( "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..93776cfc7d5 --- /dev/null +++ b/Polygon_mesh_processing/examples/Polygon_mesh_processing/repair_polygon_soup_example.cpp @@ -0,0 +1,76 @@ +#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,-2,0)); // unused vertex + + 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); + + p.clear(); + p.push_back(0); p.push_back(3); p.push_back(2); + 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 face yielding only degenerate faces + 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/test/Polygon_mesh_processing/CMakeLists.txt b/Polygon_mesh_processing/test/Polygon_mesh_processing/CMakeLists.txt index 6ffde721a7d..dd51f6e8a48 100644 --- a/Polygon_mesh_processing/test/Polygon_mesh_processing/CMakeLists.txt +++ b/Polygon_mesh_processing/test/Polygon_mesh_processing/CMakeLists.txt @@ -101,6 +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("extrude_test.cpp") + create_single_source_cgal_program("test_repair_polygon_soup.cpp") if( TBB_FOUND ) CGAL_target_use_TBB(test_pmp_distance) 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..c91b911174a --- /dev/null +++ b/Polygon_mesh_processing/test/Polygon_mesh_processing/test_repair_polygon_soup.cpp @@ -0,0 +1,289 @@ +#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_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 slit_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_simplify_polygons(false); + test_remove_invalid_polygons(false); + test_remove_isolated_points(false); + test_slit_pinched_polygons(false); + + return EXIT_SUCCESS; +} From 6d88f053b5df37012c394ba5c904d1b4126a9e7d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mael=20Rouxel-Labb=C3=A9?= Date: Thu, 26 Jul 2018 16:31:11 +0200 Subject: [PATCH 04/23] Added 'additional' --- .../CGAL/Polygon_mesh_processing/orient_polygon_soup.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/orient_polygon_soup.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/orient_polygon_soup.h index 7f1b776a150..cb6ef837f1f 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/orient_polygon_soup.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/orient_polygon_soup.h @@ -468,8 +468,8 @@ struct Polygon_soup_orienter * whose value_type is a model of the concept `RandomAccessContainer` * whose value_type is `std::size_t`. * - * @param points points of the soup of polygons. Some points might be pushed back to resolve - * non-manifold or non-orientability issues. + * @param points points of the soup of polygons. Some additional points might be pushed back to resolve + * non-manifoldness or non-orientability issues. * @param polygons each element in the vector describes a polygon using the index of the points in `points`. * If needed the order of the indices of a polygon might be reversed. * @return `true` if the orientation operation succeded. From 29cbd18597e8cacb07ca31829de9d3266def7ff4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mael=20Rouxel-Labb=C3=A9?= Date: Thu, 26 Jul 2018 16:44:49 +0200 Subject: [PATCH 05/23] Added a few words in the doc --- .../doc/Polygon_mesh_processing/PackageDescription.txt | 4 +++- .../Polygon_mesh_processing/Polygon_mesh_processing.txt | 8 ++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/Polygon_mesh_processing/doc/Polygon_mesh_processing/PackageDescription.txt b/Polygon_mesh_processing/doc/Polygon_mesh_processing/PackageDescription.txt index 3baa705dad3..814d2e447eb 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 @@ -122,6 +122,8 @@ and provides a list of the parameters that are used in this package. - `CGAL::Polygon_mesh_processing::orient_to_bound_a_volume()` ## Combinatorial Repairing Functions ## +- `CGAL::Polygon_mesh_processing::repair_polygon_soup()` +- `CGAL::Polygon_mesh_processing::remove_isolated_points_in_polygon_soup()` - \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()` 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..cbb1876a74c 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 @@ -469,6 +469,12 @@ the connected components of a closed polygon mesh so that it bounds a volume **************************************** \section PMPRepairing Combinatorial Repairing ******************* +\subsection 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 functions `CGAL::Polygon_mesh_processing::repair_polygon_soup()`, +which bundles a handful of repairing techniques to obtain an as-clean-as-possible soup. + \subsection Stitching It happens that a polygon mesh has several edges and vertices that are duplicated. @@ -524,6 +530,8 @@ 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}. +This package offers repairing methods to clean ill-formed polygon soups, +see Section \ref PMPRepairing. To deal with polygon soups that cannot be converted to a combinatorial manifold surface, some points are duplicated. From 4ce664a5afc58201beff66829d3fb5ad9ed15f51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mael=20Rouxel-Labb=C3=A9?= Date: Thu, 26 Jul 2018 16:52:06 +0200 Subject: [PATCH 06/23] Minor doc change --- .../doc/Polygon_mesh_processing/Polygon_mesh_processing.txt | 2 +- .../CGAL/Polygon_mesh_processing/repair_polygon_soup.h | 5 +++-- 2 files changed, 4 insertions(+), 3 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 cbb1876a74c..83de47cab0e 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 @@ -469,7 +469,7 @@ the connected components of a closed polygon mesh so that it bounds a volume **************************************** \section PMPRepairing Combinatorial Repairing ******************* -\subsection Polygon Soup 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 functions `CGAL::Polygon_mesh_processing::repair_polygon_soup()`, diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/repair_polygon_soup.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/repair_polygon_soup.h index c389c16dcaf..26fda994e82 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/repair_polygon_soup.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/repair_polygon_soup.h @@ -311,7 +311,7 @@ std::size_t remove_degenerate_polygons_in_polygon_soup(PointRange& points, /// \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. +/// \returns the number of removed isolated points /// template std::size_t remove_isolated_points_in_polygon_soup(PointRange& points, @@ -380,7 +380,8 @@ std::size_t remove_isolated_points_in_polygon_soup(PointRange& points, /// - splitting of "pinched" polygons, that is polygons where a position appears more than once, /// in multiple non-pinched polygons. /// - removal of invalid polygons, that is polygons with fewer than 2 points; -/// - removal of isolated points, that is points that do not appear in any polygon of the soup. +/// - removal of isolated points, that is points that do not appear in any polygon of the soup, +/// using `CGAL::Polygon_mesh_processing::remove_isolated_points_in_polygon_soup()`. /// /// \tparam PointRange a model of the concept `SequenceContainer` /// \tparam PolygonRange a model of the concept `SequenceContainer` From e6a04b1fa269f5984876d05189377e5349650dee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mael=20Rouxel-Labb=C3=A9?= Date: Thu, 26 Jul 2018 18:26:11 +0200 Subject: [PATCH 07/23] Misc minor changes --- Documentation/doc/Documentation/General.txt | 5 +++ .../orient_polygon_soup.h | 2 +- .../repair_polygon_soup.h | 38 +++++++++---------- .../test_repair_polygon_soup.cpp | 2 +- 4 files changed, 26 insertions(+), 21 deletions(-) diff --git a/Documentation/doc/Documentation/General.txt b/Documentation/doc/Documentation/General.txt index cf46e5e08a8..72bdabf7cf3 100644 --- a/Documentation/doc/Documentation/General.txt +++ b/Documentation/doc/Documentation/General.txt @@ -73,6 +73,11 @@ 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 diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/orient_polygon_soup.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/orient_polygon_soup.h index cb6ef837f1f..663ddaa0776 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/orient_polygon_soup.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/orient_polygon_soup.h @@ -463,7 +463,7 @@ struct Polygon_soup_orienter * The algorithm is described in \cgalCite{gueziec2001cutting}. * * @tparam PointRange a model of the concepts `RandomAccessContainer` - * and `BackInsertionSequence` whose value type is the point type + * and `BackInsertionSequence` whose value type is the point type. * @tparam PolygonRange a model of the concept `RandomAccessContainer` * whose value_type is a model of the concept `RandomAccessContainer` * whose value_type is `std::size_t`. diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/repair_polygon_soup.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/repair_polygon_soup.h index 26fda994e82..23b60ff5db1 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/repair_polygon_soup.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/repair_polygon_soup.h @@ -84,7 +84,7 @@ bool simplify_polygon(PointRange& points, 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_DEBUG +#ifdef CGAL_PMP_REPAIR_POLYGON_SOUP_VERBOSE std::cout << "removing point: polygon[" << next_i << "] = " << polygon[next_i] << std::endl; #endif next_i = (next_i == last) ? 0 : next_i+1; @@ -115,7 +115,7 @@ bool simplify_polygon(PointRange& points, // // For each polygon of the soup, removes consecutive identical (in a geometric sense) points. // -// \tparam PointRange a model of the concept `RandomAccessContainer` +// \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`. @@ -158,13 +158,13 @@ std::size_t simplify_polygons_in_polygon_soup(PointRange& points, // \ingroup PMP_repairing_grp // -// Splits "pinched" poygons, that is polygons for which a point appears more than once, +// 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` +// \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`. +// 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. @@ -199,9 +199,9 @@ std::size_t split_pinched_polygons_in_polygon_soup(PointRange& points, Polygon_3& polygon = polygons[polygon_index]; const std::size_t ini_polygon_size = polygon.size(); -#ifdef CGAL_PMP_REPAIR_POLYGON_SOUP_DEBUG +#ifdef CGAL_PMP_REPAIR_POLYGON_SOUP_VERBOSE std::cout << "Input polygon: "; - print_polygon(std::cout, polygon); + internal::print_polygon(std::cout, polygon); #endif if(ini_polygon_size <= 3) @@ -216,13 +216,13 @@ std::size_t split_pinched_polygons_in_polygon_soup(PointRange& points, { const Point_3& p = points[polygon[i]]; - std::pair is_insert_succesful = + std::pair is_insert_successful = unique_points.insert(std::make_pair(p, i)); - if(!is_insert_succesful.second) + if(!is_insert_successful.second) { // We have already met that point, split the polygon into two smaller polygons - std::size_t prev_id = is_insert_succesful.first->second; + std::size_t prev_id = is_insert_successful.first->second; CGAL_assertion(prev_id < i-1); @@ -232,13 +232,13 @@ std::size_t split_pinched_polygons_in_polygon_soup(PointRange& points, 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_DEBUG +#ifdef CGAL_PMP_REPAIR_POLYGON_SOUP_VERBOSE std::cout << "New polygons:" << std::endl; std::cout << "P1:"; - print_polygon(std::cout, split_polygon_1); + internal::print_polygon(std::cout, split_polygon_1); std::cout << "P2:"; - print_polygon(std::cout, split_polygon_2); + internal::print_polygon(std::cout, split_polygon_2); #endif std::swap(polygon, split_polygon_1); @@ -257,7 +257,7 @@ std::size_t split_pinched_polygons_in_polygon_soup(PointRange& points, // // Removes polygons with fewer than 2 points from the soup. // -// \tparam PointRange a model of the concept `Container` +// \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`. @@ -302,7 +302,7 @@ std::size_t remove_degenerate_polygons_in_polygon_soup(PointRange& points, /// 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` +/// \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`. @@ -344,7 +344,7 @@ std::size_t remove_isolated_points_in_polygon_soup(PointRange& points, { if(!visited[i]) { -#ifdef CGAL_PMP_REPAIR_POLYGON_SOUP_DEBUG +#ifdef CGAL_PMP_REPAIR_POLYGON_SOUP_VERBOSE std::cout << "points[" << i << "] = " << points[i] << " is isolated" << std::endl; #endif std::swap(points[swap_position], points[i]); @@ -383,10 +383,10 @@ std::size_t remove_isolated_points_in_polygon_soup(PointRange& points, /// - removal of isolated points, that is points that do not appear in any polygon of the soup, /// using `CGAL::Polygon_mesh_processing::remove_isolated_points_in_polygon_soup()`. /// -/// \tparam PointRange a model of the concept `SequenceContainer` -/// \tparam PolygonRange a model of the concept `SequenceContainer` +/// \tparam PointRange a model of the concept `SequenceContainer` 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`. +/// 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. 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 index c91b911174a..88b603b0c36 100644 --- 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 @@ -205,7 +205,7 @@ void test_remove_isolated_points(const bool verbose = false) void test_slit_pinched_polygons(const bool /*verbose*/ = false) { - std::cout << "test slit_pinched_polygons... " << std::endl; + std::cout << "test split_pinched_polygons... " << std::endl; std::vector points; std::vector polygons; From 007a466b8e94e04dac2e7058692ee790ec852b45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mael=20Rouxel-Labb=C3=A9?= Date: Thu, 26 Jul 2018 18:26:49 +0200 Subject: [PATCH 08/23] Added a function to merge duplicate vertices --- .../PackageDescription.txt | 3 +- .../repair_polygon_soup_example.cpp | 5 +- .../repair_polygon_soup.h | 127 +++++++++++++++++- .../test_repair_polygon_soup.cpp | 49 +++++++ 4 files changed, 174 insertions(+), 10 deletions(-) diff --git a/Polygon_mesh_processing/doc/Polygon_mesh_processing/PackageDescription.txt b/Polygon_mesh_processing/doc/Polygon_mesh_processing/PackageDescription.txt index 814d2e447eb..62d03d8ea9c 100644 --- a/Polygon_mesh_processing/doc/Polygon_mesh_processing/PackageDescription.txt +++ b/Polygon_mesh_processing/doc/Polygon_mesh_processing/PackageDescription.txt @@ -122,8 +122,9 @@ and provides a list of the parameters that are used in this package. - `CGAL::Polygon_mesh_processing::orient_to_bound_a_volume()` ## Combinatorial Repairing Functions ## -- `CGAL::Polygon_mesh_processing::repair_polygon_soup()` +- `CGAL::Polygon_mesh_processing::merge_duplicate_points_in_polygon_soup()` - `CGAL::Polygon_mesh_processing::remove_isolated_points_in_polygon_soup()` +- `CGAL::Polygon_mesh_processing::repair_polygon_soup()` - \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()` 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 index 93776cfc7d5..feff2acae43 100644 --- 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 @@ -27,7 +27,8 @@ int main(int, char**) 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,-2,0)); // unused vertex + 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); @@ -43,7 +44,7 @@ int main(int, char**) polygons.push_back(p); p.clear(); - p.push_back(0); p.push_back(3); p.push_back(2); + p.push_back(0); p.push_back(3); p.push_back(5); polygons.push_back(p); // degenerate face diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/repair_polygon_soup.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/repair_polygon_soup.h index 23b60ff5db1..881836f76df 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/repair_polygon_soup.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/repair_polygon_soup.h @@ -51,6 +51,18 @@ struct Polygon_types 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) { @@ -372,16 +384,121 @@ std::size_t remove_isolated_points_in_polygon_soup(PointRange& points, return removed_points_n; } +/// \ingroup PMP_repairing_grp +/// +/// Merges the duplicate points in a polygon soup. +/// The index of a point that is merged with another point will change in all the polygons +/// the point appears in. +/// +/// \tparam PointRange a model of the concept `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 + 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 + 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()); +} + /// \ingroup PMP_repairing_grp /// /// Cleans a given polygon soup by removing invalid elements. More precisely, this function /// carries out the following tasks, in the same order as they are listed: +/// - merging of duplicating points, using the function +/// `CGAL::Polygon_mesh_processing::merge_duplicate_points_in_polygon_soup()`. /// - simplification of the polygons to remove identical consecutive points; /// - splitting of "pinched" polygons, that is polygons where a position appears more than once, /// in multiple non-pinched polygons. /// - removal of invalid polygons, that is polygons with fewer than 2 points; /// - removal of isolated points, that is points that do not appear in any polygon of the soup, -/// using `CGAL::Polygon_mesh_processing::remove_isolated_points_in_polygon_soup()`. +/// using the function `CGAL::Polygon_mesh_processing::remove_isolated_points_in_polygon_soup()`. /// /// \tparam PointRange a model of the concept `SequenceContainer` whose value type is the point type /// \tparam PolygonRange a model of the concept `SequenceContainer`. @@ -411,14 +528,10 @@ void repair_polygon_soup(PointRange& points, using boost::get_param; using boost::choose_param; - typedef typename boost::lookup_named_param_def < - internal_np::geom_traits_t, - NamedParameters, - typename CGAL::Kernel_traits::Point_3 >::type - > ::type Traits; - + typedef typename internal::GetPolygonGeomTraits::type Traits; Traits traits = choose_param(get_param(np, internal_np::geom_traits), Traits()); + 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); 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 index 88b603b0c36..941bef2e085 100644 --- 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 @@ -17,6 +17,54 @@ typedef K::Point_3 Point_3; typedef CGAL::Surface_mesh Mesh; typedef std::vector Polygon; +void test_merge_duplicate_points(const bool /*verbose*/ = false) +{ + std::cout << "test merge duplicate points... " << std::endl; + + std::vector 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_simplify_polygons(const bool /*verbose*/ = false) { std::cout << "test simplify_polygons... " << std::endl; @@ -280,6 +328,7 @@ void test_slit_pinched_polygons(const bool /*verbose*/ = false) int main() { + test_merge_duplicate_points(false); test_simplify_polygons(false); test_remove_invalid_polygons(false); test_remove_isolated_points(false); From 4e31832136e835aa226d3884f1456814d3425468 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mael=20Rouxel-Labb=C3=A9?= Date: Mon, 30 Jul 2018 13:08:16 +0200 Subject: [PATCH 09/23] Added a function to detect and merge duplicate faces --- .../CGAL/boost/graph/parameters_interface.h | 2 + BGL/test/BGL/test_cgal_bgl_named_params.cpp | 6 + .../NamedParameters.txt | 16 + .../PackageDescription.txt | 1 + .../Polygon_mesh_processing.txt | 10 +- .../repair_polygon_soup_example.cpp | 7 +- .../repair_polygon_soup.h | 491 +++++++++++++++++- .../test_repair_polygon_soup.cpp | 214 +++++++- 8 files changed, 716 insertions(+), 31 deletions(-) diff --git a/BGL/include/CGAL/boost/graph/parameters_interface.h b/BGL/include/CGAL/boost/graph/parameters_interface.h index 1ec0f1504de..97126bdf358 100644 --- a/BGL/include/CGAL/boost/graph/parameters_interface.h +++ b/BGL/include/CGAL/boost/graph/parameters_interface.h @@ -78,6 +78,8 @@ 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(do_erase_all_duplicates_t, do_erase_all_duplicates, do_erase_all_duplicates) +CGAL_add_named_parameter(do_require_same_orientation_t, do_require_same_orientation, do_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 019ad98dedf..1278ff54531 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::do_erase_all_duplicates).v == 47); + assert(get_param(np, CGAL::internal_np::do_require_same_orientation).v == 48); // Named parameters that we use in the package 'Surface Mesh Simplification' assert(get_param(np, CGAL::internal_np::get_cost_policy).v == 34); @@ -161,6 +163,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<47>(get_param(np, CGAL::internal_np::do_erase_all_duplicates)); + check_same_type<48>(get_param(np, CGAL::internal_np::do_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)); @@ -238,6 +242,8 @@ int main() .clip_volume(A<44>(44)) .use_compact_clipper(A<45>(45)) .apply_per_connected_component(A<46>(46)) + .do_erase_all_duplicates(A<47>(47)) + .do_require_same_orientation(A<48>(48)) ); return EXIT_SUCCESS; diff --git a/Polygon_mesh_processing/doc/Polygon_mesh_processing/NamedParameters.txt b/Polygon_mesh_processing/doc/Polygon_mesh_processing/NamedParameters.txt index 839bd7b52ff..da0f06912d3 100644 --- a/Polygon_mesh_processing/doc/Polygon_mesh_processing/NamedParameters.txt +++ b/Polygon_mesh_processing/doc/Polygon_mesh_processing/NamedParameters.txt @@ -364,6 +364,22 @@ should be considered as part of the clipping volume or not. \b Default value is `true` \cgalNPEnd +\cgalNPBegin{do_erase_all_duplicates} \anchor PMP_do_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{do_require_same_orientation} \anchor PMP_do_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 62d03d8ea9c..d3c0ad13f1f 100644 --- a/Polygon_mesh_processing/doc/Polygon_mesh_processing/PackageDescription.txt +++ b/Polygon_mesh_processing/doc/Polygon_mesh_processing/PackageDescription.txt @@ -123,6 +123,7 @@ and provides a list of the parameters that are used in this package. ## Combinatorial Repairing 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()` - `CGAL::Polygon_mesh_processing::repair_polygon_soup()` - \link PMP_repairing_grp `CGAL::Polygon_mesh_processing::stitch_borders()` \endlink 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 83de47cab0e..e60969e92eb 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 @@ -472,8 +472,14 @@ the connected components of a closed polygon mesh so that it bounds a volume \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 functions `CGAL::Polygon_mesh_processing::repair_polygon_soup()`, -which bundles a handful of repairing techniques to obtain an as-clean-as-possible soup. +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 pol soup. \subsection Stitching 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 index feff2acae43..e44be56106e 100644 --- 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 @@ -43,6 +43,11 @@ int main(int, char**) 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); @@ -56,7 +61,7 @@ int main(int, char**) p.push_back(0); p.push_back(3); p.push_back(4); polygons.push_back(p); - // pinched face yielding only degenerate faces + // 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); diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/repair_polygon_soup.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/repair_polygon_soup.h index 881836f76df..8ed416ddb0a 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/repair_polygon_soup.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/repair_polygon_soup.h @@ -27,10 +27,17 @@ #include #include +#include #include +#include + +#include +#include +#include #include #include +#include #include #include #include @@ -44,10 +51,11 @@ namespace internal { template struct Polygon_types { - typedef typename PointRange::value_type Point_3; - typedef typename PolygonRange::value_type Polygon_3; + typedef typename boost::range_value::type Point_3; + typedef typename boost::range_value::type Polygon_3; - typedef typename std::iterator_traits::value_type V_ID; + 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; }; @@ -73,6 +81,37 @@ void print_polygon(Stream& out, const Polygon& polygon) out << std::endl; } +template +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, @@ -97,7 +136,7 @@ bool simplify_polygon(PointRange& points, { to_remove.push_back(next_i); #ifdef CGAL_PMP_REPAIR_POLYGON_SOUP_VERBOSE - std::cout << "removing point: polygon[" << next_i << "] = " << polygon[next_i] << std::endl; + std::cout << "Removing point: polygon[" << next_i << "] = " << polygon[next_i] << std::endl; #endif next_i = (next_i == last) ? 0 : next_i+1; @@ -175,7 +214,7 @@ std::size_t simplify_polygons_in_polygon_soup(PointRange& 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 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" // @@ -219,7 +258,8 @@ std::size_t split_pinched_polygons_in_polygon_soup(PointRange& points, if(ini_polygon_size <= 3) continue; - CGAL_assertion(!simplify_polygon(points, polygon, traits)); // 'polygon' must not have duplicates + // '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()); @@ -235,12 +275,12 @@ std::size_t split_pinched_polygons_in_polygon_soup(PointRange& points, { // 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); - Polygon_3 split_polygon_2; // might be pinched too, but it'll be checked later + 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()); @@ -387,10 +427,10 @@ std::size_t remove_isolated_points_in_polygon_soup(PointRange& points, /// \ingroup PMP_repairing_grp /// /// Merges the duplicate points in a polygon soup. -/// The index of a point that is merged with another point will change in all the polygons -/// the point appears in. +/// 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 concept `SequenceContainer` and `Swappable` +/// \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` @@ -470,7 +510,6 @@ std::size_t merge_duplicate_points_in_polygon_soup(PointRange& points, std::cout << "Output polygon: "; internal::print_polygon(std::cout, polygon); #endif - } std::swap(points, unique_points); @@ -487,37 +526,434 @@ std::size_t merge_duplicate_points_in_polygon_soup(PointRange& points, 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 + 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 + 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 + 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 + 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; +}; + +// @todo merge this class with 'Vertex_collector' from the other branch +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 + 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 /// -/// Cleans a given polygon soup by removing invalid elements. More precisely, this function -/// carries out the following tasks, in the same order as they are listed: -/// - merging of duplicating points, using the function -/// `CGAL::Polygon_mesh_processing::merge_duplicate_points_in_polygon_soup()`. -/// - simplification of the polygons to remove identical consecutive points; -/// - splitting of "pinched" polygons, that is polygons where a position appears more than once, -/// in multiple non-pinched polygons. -/// - removal of invalid polygons, that is polygons with fewer than 2 points; -/// - removal of isolated points, that is points that do not appear in any polygon of the soup, -/// using the function `CGAL::Polygon_mesh_processing::remove_isolated_points_in_polygon_soup()`. +/// 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 `SequenceContainer` 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` -/// and `Swappable` whose value_type is `std::size_t`. +/// \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" described below +/// \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{do_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{do_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::do_erase_all_duplicates), false); + const bool same_orientation = choose_param(get_param(np, internal_np::do_require_same_orientation), false); + +#ifdef CGAL_PMP_REPAIR_POLYGON_SOUP_VERBOSE + 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); +#ifdef CGAL_PMP_REPAIR_POLYGON_SOUP_VERBOSE + 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{do_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{do_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 @@ -535,6 +971,7 @@ void repair_polygon_soup(PointRange& points, 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); } 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 index 941bef2e085..2d34c7451a7 100644 --- 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 @@ -1,9 +1,12 @@ +#define CGAL_PMP_REPAIR_POLYGON_SOUP_VERBOSE + #include #include #include +#include #include #include #include @@ -17,6 +20,92 @@ 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; + + 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::do_erase_all_duplicates(true) + .do_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::do_erase_all_duplicates(true) + .do_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; @@ -328,7 +538,9 @@ void test_slit_pinched_polygons(const bool /*verbose*/ = false) 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); From b9325608b363098c86b3e4e4b952da325aa7542f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mael=20Rouxel-Labb=C3=A9?= Date: Mon, 30 Jul 2018 13:08:50 +0200 Subject: [PATCH 10/23] Misc minor doc fixes --- .../NamedParameters.txt | 20 +++++++++---------- .../connected_components.h | 8 ++++---- .../Polygon_mesh_processing/detect_features.h | 4 ++-- .../polygon_soup_to_polygon_mesh.h | 3 ++- .../CGAL/Polygon_mesh_processing/remesh.h | 2 +- .../CGAL/Polygon_mesh_processing/repair.h | 6 +++--- 6 files changed, 22 insertions(+), 21 deletions(-) diff --git a/Polygon_mesh_processing/doc/Polygon_mesh_processing/NamedParameters.txt b/Polygon_mesh_processing/doc/Polygon_mesh_processing/NamedParameters.txt index da0f06912d3..2a7faa3bc19 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{do_erase_all_duplicates} \anchor PMP_do_erase_all_duplicates 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 Date: Wed, 1 Aug 2018 15:10:39 +0200 Subject: [PATCH 11/23] Improved repair polygon soup verbosity granularity --- .../repair_polygon_soup.h | 85 ++++++++++++++----- .../test_repair_polygon_soup.cpp | 2 +- 2 files changed, 67 insertions(+), 20 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/repair_polygon_soup.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/repair_polygon_soup.h index 8ed416ddb0a..08bee5946f7 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/repair_polygon_soup.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/repair_polygon_soup.h @@ -42,6 +42,12 @@ #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 { @@ -135,8 +141,8 @@ bool simplify_polygon(PointRange& points, 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 - std::cout << "Removing point: polygon[" << next_i << "] = " << polygon[next_i] << std::endl; +#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; @@ -204,6 +210,11 @@ std::size_t simplify_polygons_in_polygon_soup(PointRange& points, ++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; } @@ -250,7 +261,7 @@ std::size_t split_pinched_polygons_in_polygon_soup(PointRange& points, Polygon_3& polygon = polygons[polygon_index]; const std::size_t ini_polygon_size = polygon.size(); -#ifdef CGAL_PMP_REPAIR_POLYGON_SOUP_VERBOSE +#ifdef CGAL_PMP_REPAIR_POLYGON_SOUP_VERBOSE_PP std::cout << "Input polygon: "; internal::print_polygon(std::cout, polygon); #endif @@ -273,6 +284,11 @@ std::size_t split_pinched_polygons_in_polygon_soup(PointRange& points, 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); @@ -284,7 +300,7 @@ std::size_t split_pinched_polygons_in_polygon_soup(PointRange& points, 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 +#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); @@ -328,7 +344,13 @@ std::size_t remove_invalid_polygons_in_polygon_soup(PointRange& /*points*/, 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()) @@ -337,7 +359,14 @@ std::size_t remove_invalid_polygons_in_polygon_soup(PointRange& /*points*/, to_remove.pop_back(); } - return ini_polygons_size - polygons.size(); + 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 @@ -396,7 +425,7 @@ std::size_t remove_isolated_points_in_polygon_soup(PointRange& points, { if(!visited[i]) { -#ifdef CGAL_PMP_REPAIR_POLYGON_SOUP_VERBOSE +#ifdef CGAL_PMP_REPAIR_POLYGON_SOUP_VERBOSE_PP std::cout << "points[" << i << "] = " << points[i] << " is isolated" << std::endl; #endif std::swap(points[swap_position], points[i]); @@ -421,6 +450,11 @@ std::size_t remove_isolated_points_in_polygon_soup(PointRange& points, } } +#ifdef CGAL_PMP_REPAIR_POLYGON_SOUP_VERBOSE + if(removed_points_n > 0) + std::cout << "Removed " << removed_points_n << " isolated point(s)" << std::endl; +#endif + return removed_points_n; } @@ -482,7 +516,7 @@ std::size_t merge_duplicate_points_in_polygon_soup(PointRange& points, std::pair is_insert_successful = point_to_id.insert(std::make_pair(points[i], unique_points.size())); -#ifdef CGAL_PMP_REPAIR_POLYGON_SOUP_VERBOSE +#ifdef CGAL_PMP_REPAIR_POLYGON_SOUP_VERBOSE_PP if(!is_insert_successful.second) std::cout << "points[" < @@ -548,7 +587,7 @@ void canonical_polygon_markers(const PointRange& points, 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 +#ifdef CGAL_PMP_REPAIR_POLYGON_SOUP_VERBOSE_PP std::cout << "first: " << first << " points[" << *min_id << "] = " << points[*min_id] << std::endl; #endif @@ -560,7 +599,7 @@ void canonical_polygon_markers(const PointRange& points, reversed = traits.less_xyz_3_object()(points[polygon[pos_prev]], points[polygon[pos_next]]); -#ifdef CGAL_PMP_REPAIR_POLYGON_SOUP_VERBOSE +#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 @@ -604,7 +643,7 @@ Polygon construct_canonical_polygon(const PointRange& points, if(polygon.size() < 2) return polygon; -#ifdef CGAL_PMP_REPAIR_POLYGON_SOUP_VERBOSE +#ifdef CGAL_PMP_REPAIR_POLYGON_SOUP_VERBOSE_PP std::cout << "Input polygon:"; internal::print_polygon(std::cout, polygon); #endif @@ -613,7 +652,7 @@ Polygon construct_canonical_polygon(const PointRange& points, 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 +#ifdef CGAL_PMP_REPAIR_POLYGON_SOUP_VERBOSE_PP std::cout << "Canonical polygon:"; internal::print_polygon(std::cout, canonical_polygon); #endif @@ -790,7 +829,7 @@ DuplicateOutputIterator collect_duplicate_polygons(const PointRange& points, if(!is_insert_successful.second) { const P_ID other_polygon_id = *(is_insert_successful.first); -#ifdef CGAL_PMP_REPAIR_POLYGON_SOUP_VERBOSE +#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); @@ -851,7 +890,7 @@ std::size_t merge_duplicate_polygons_in_polygon_soup(const PointRange& points, const bool erase_all_duplicates = choose_param(get_param(np, internal_np::do_erase_all_duplicates), false); const bool same_orientation = choose_param(get_param(np, internal_np::do_require_same_orientation), false); -#ifdef CGAL_PMP_REPAIR_POLYGON_SOUP_VERBOSE +#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 @@ -861,7 +900,11 @@ std::size_t merge_duplicate_polygons_in_polygon_soup(const PointRange& points, std::vector > all_duplicate_polygons; internal::collect_duplicate_polygons(points, polygons, std::back_inserter(all_duplicate_polygons), traits, same_orientation); -#ifdef CGAL_PMP_REPAIR_POLYGON_SOUP_VERBOSE + + 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 @@ -891,7 +934,7 @@ std::size_t merge_duplicate_polygons_in_polygon_soup(const PointRange& points, polygons.erase(polygons.begin() + swap_position, polygons.end()); #ifdef CGAL_PMP_REPAIR_POLYGON_SOUP_VERBOSE - std::cout << "Removed " << removed_polygons_n << " polygon(s)" << std::endl; + std::cout << "Removed " << removed_polygons_n << " duplicate polygon(s)" << std::endl; std::cout << polygons.size() << " polygon(s) left" << std::endl; #endif @@ -967,6 +1010,10 @@ void repair_polygon_soup(PointRange& points, 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); 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 index 2d34c7451a7..63a3473095f 100644 --- 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 @@ -1,4 +1,4 @@ -#define CGAL_PMP_REPAIR_POLYGON_SOUP_VERBOSE +#define CGAL_PMP_REPAIR_POLYGON_SOUP_VERBOSE_PP #include From 94108a44fee91b7e22e2510b54956128c4e26199 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mael=20Rouxel-Labb=C3=A9?= Date: Wed, 1 Aug 2018 15:13:25 +0200 Subject: [PATCH 12/23] Fixed lonely function call --- .../include/CGAL/Polygon_mesh_processing/repair_polygon_soup.h | 1 - 1 file changed, 1 deletion(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/repair_polygon_soup.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/repair_polygon_soup.h index 08bee5946f7..6b421315b4a 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/repair_polygon_soup.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/repair_polygon_soup.h @@ -1019,7 +1019,6 @@ void repair_polygon_soup(PointRange& points, 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); } From 459ff9a9b4364b6f3893cde1a99a07e8719b41f4 Mon Sep 17 00:00:00 2001 From: Clement Jamin Date: Wed, 3 May 2017 16:47:06 +0200 Subject: [PATCH 13/23] Introduce CGAL_GENERATE_MEMBER_DETECTOR macro --- STL_Extension/include/CGAL/Has_member.h | 43 +++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 STL_Extension/include/CGAL/Has_member.h diff --git a/STL_Extension/include/CGAL/Has_member.h b/STL_Extension/include/CGAL/Has_member.h new file mode 100644 index 00000000000..f4d243fa8ad --- /dev/null +++ b/STL_Extension/include/CGAL/Has_member.h @@ -0,0 +1,43 @@ +// 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. +// +// +// +// 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 From ef6a6af122fb754a618a4aeb5261dee4a59cb6db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mael=20Rouxel-Labb=C3=A9?= Date: Fri, 3 Aug 2018 14:21:17 +0200 Subject: [PATCH 14/23] Made STL reader compatible with SequenceContainers It's useful to not be constrained to cpp11::arrays when the polygon soup is to be processed through reparation functions (for example) --- .../IO/facets_in_complex_3_to_triangle_mesh.h | 2 +- Polyhedron_IO/include/CGAL/IO/OFF_reader.h | 37 ++------ Polyhedron_IO/include/CGAL/IO/STL_reader.h | 73 +++++++++------ .../include/CGAL/IO/reader_helpers.h | 90 +++++++++++++++++++ 4 files changed, 145 insertions(+), 57 deletions(-) create mode 100644 Polyhedron_IO/include/CGAL/IO/reader_helpers.h 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/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 From 31246ab517288bc52ae0d9865a2cce7a6449fda6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mael=20Rouxel-Labb=C3=A9?= Date: Fri, 3 Aug 2018 14:24:34 +0200 Subject: [PATCH 15/23] Test the STL reader a bit more thoroughly --- Polyhedron_IO/test/Polyhedron_IO/stl2off.cpp | 68 ++++++++++++-------- 1 file changed, 40 insertions(+), 28 deletions(-) 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; } From e994e2b6685042508ef9650820de331d99827cb5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mael=20Rouxel-Labb=C3=A9?= Date: Fri, 3 Aug 2018 16:28:38 +0200 Subject: [PATCH 16/23] Added SPDX license identifier --- STL_Extension/include/CGAL/Has_member.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/STL_Extension/include/CGAL/Has_member.h b/STL_Extension/include/CGAL/Has_member.h index f4d243fa8ad..d35faad9448 100644 --- a/STL_Extension/include/CGAL/Has_member.h +++ b/STL_Extension/include/CGAL/Has_member.h @@ -12,7 +12,9 @@ // 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 From f30c89c40f86635ab19a5f97f142496272779a05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mael=20Rouxel-Labb=C3=A9?= Date: Tue, 7 Aug 2018 09:12:49 +0200 Subject: [PATCH 17/23] Added some missing includes --- .../include/CGAL/Polygon_mesh_processing/repair_polygon_soup.h | 1 + .../test/Polygon_mesh_processing/test_repair_polygon_soup.cpp | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/repair_polygon_soup.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/repair_polygon_soup.h index 6b421315b4a..9f78f219666 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/repair_polygon_soup.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/repair_polygon_soup.h @@ -39,6 +39,7 @@ #include #include #include +#include #include #include 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 index 63a3473095f..4de93325030 100644 --- 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 @@ -1,4 +1,4 @@ -#define CGAL_PMP_REPAIR_POLYGON_SOUP_VERBOSE_PP +#define CGAL_PMP_REPAIR_POLYGON_SOUP_VERBOSE_PP #include @@ -7,6 +7,7 @@ #include #include +#include #include #include #include From ab9b0479b36f4f956eabcb4b1b6b7a3bd0f2720c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mael=20Rouxel-Labb=C3=A9?= Date: Fri, 17 Aug 2018 15:32:26 +0200 Subject: [PATCH 18/23] Removed unnecessary 'do_' in named parameters --- BGL/include/CGAL/boost/graph/parameters_interface.h | 4 ++-- BGL/test/BGL/test_cgal_bgl_named_params.cpp | 12 ++++++------ .../doc/Polygon_mesh_processing/NamedParameters.txt | 4 ++-- .../Polygon_mesh_processing/repair_polygon_soup.h | 12 ++++++------ .../test_repair_polygon_soup.cpp | 8 ++++---- 5 files changed, 20 insertions(+), 20 deletions(-) diff --git a/BGL/include/CGAL/boost/graph/parameters_interface.h b/BGL/include/CGAL/boost/graph/parameters_interface.h index 97126bdf358..6559e3c0511 100644 --- a/BGL/include/CGAL/boost/graph/parameters_interface.h +++ b/BGL/include/CGAL/boost/graph/parameters_interface.h @@ -78,8 +78,8 @@ 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(do_erase_all_duplicates_t, do_erase_all_duplicates, do_erase_all_duplicates) -CGAL_add_named_parameter(do_require_same_orientation_t, do_require_same_orientation, do_require_same_orientation) +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 1278ff54531..4525dd69657 100644 --- a/BGL/test/BGL/test_cgal_bgl_named_params.cpp +++ b/BGL/test/BGL/test_cgal_bgl_named_params.cpp @@ -85,8 +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::do_erase_all_duplicates).v == 47); - assert(get_param(np, CGAL::internal_np::do_require_same_orientation).v == 48); + assert(get_param(np, CGAL::internal_np::erase_all_duplicates).v == 47); + assert(get_param(np, CGAL::internal_np::require_same_orientation).v == 48); // Named parameters that we use in the package 'Surface Mesh Simplification' assert(get_param(np, CGAL::internal_np::get_cost_policy).v == 34); @@ -163,8 +163,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<47>(get_param(np, CGAL::internal_np::do_erase_all_duplicates)); - check_same_type<48>(get_param(np, CGAL::internal_np::do_require_same_orientation)); + check_same_type<47>(get_param(np, CGAL::internal_np::erase_all_duplicates)); + check_same_type<48>(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)); @@ -242,8 +242,8 @@ int main() .clip_volume(A<44>(44)) .use_compact_clipper(A<45>(45)) .apply_per_connected_component(A<46>(46)) - .do_erase_all_duplicates(A<47>(47)) - .do_require_same_orientation(A<48>(48)) + .erase_all_duplicates(A<47>(47)) + .require_same_orientation(A<48>(48)) ); return EXIT_SUCCESS; diff --git a/Polygon_mesh_processing/doc/Polygon_mesh_processing/NamedParameters.txt b/Polygon_mesh_processing/doc/Polygon_mesh_processing/NamedParameters.txt index 2a7faa3bc19..eaf310e91af 100644 --- a/Polygon_mesh_processing/doc/Polygon_mesh_processing/NamedParameters.txt +++ b/Polygon_mesh_processing/doc/Polygon_mesh_processing/NamedParameters.txt @@ -364,7 +364,7 @@ should be considered as part of the clipping volume or not. Default: `true` \cgalNPEnd -\cgalNPBegin{do_erase_all_duplicates} \anchor PMP_do_erase_all_duplicates +\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. @@ -373,7 +373,7 @@ or if one (arbitrarily chosen) face should be kept. Default: `false` \cgalNPEnd -\cgalNPBegin{do_require_same_orientation} \anchor PMP_do_require_same_orientation +\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 diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/repair_polygon_soup.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/repair_polygon_soup.h index 9f78f219666..1796517c74e 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/repair_polygon_soup.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/repair_polygon_soup.h @@ -865,11 +865,11 @@ DuplicateOutputIterator collect_duplicate_polygons(const PointRange& points, /// 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{do_erase_all_duplicates} +/// \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{do_require_same_orientation} +/// \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`. @@ -888,8 +888,8 @@ std::size_t merge_duplicate_polygons_in_polygon_soup(const PointRange& points, typedef typename internal::Polygon_types::P_ID P_ID; - const bool erase_all_duplicates = choose_param(get_param(np, internal_np::do_erase_all_duplicates), false); - const bool same_orientation = choose_param(get_param(np, internal_np::do_require_same_orientation), false); + 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; @@ -987,12 +987,12 @@ std::size_t merge_duplicate_polygons_in_polygon_soup(PointRange& points, /// /// and, for each functor `Foo`, a function `Foo foo_object()`. /// \cgalParamEnd -/// \cgalParamBegin{do_erase_all_duplicates} +/// \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{do_require_same_orientation} +/// \cgalParamBegin{require_same_orientatio /// 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. 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 index 4de93325030..006f4dc3f0e 100644 --- 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 @@ -265,14 +265,14 @@ void test_merge_duplicate_polygons(const bool /*verbose*/ = false) // Remove all duplicates polygons_copy = polygons; res = PMP::merge_duplicate_polygons_in_polygon_soup(points, polygons_copy, - params::do_erase_all_duplicates(true) - .do_require_same_orientation(false)); + 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::do_erase_all_duplicates(true) - .do_require_same_orientation(true)); + params::erase_all_duplicates(true) + .require_same_orientation(true)); assert(res == 2 && polygons.size() == 4); } From 834f90b4c14ddab29f01e27e0015dbdea9a4e886 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mael=20Rouxel-Labb=C3=A9?= Date: Fri, 17 Aug 2018 15:33:42 +0200 Subject: [PATCH 19/23] Fixed typo --- .../doc/Polygon_mesh_processing/Polygon_mesh_processing.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 5795a4a4a3b..8dbb6d4d80c 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 @@ -479,7 +479,7 @@ and geometrical errors. This package offers the following functions: 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 pol soup. +to obtain an as-clean-as-possible polygon soup. \subsection Stitching From 900ad1116671d2fdc35666fb8de64efc9582027d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mael=20Rouxel-Labb=C3=A9?= Date: Fri, 17 Aug 2018 16:45:33 +0200 Subject: [PATCH 20/23] Added new example to examples.txt --- Polygon_mesh_processing/doc/Polygon_mesh_processing/examples.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/Polygon_mesh_processing/doc/Polygon_mesh_processing/examples.txt b/Polygon_mesh_processing/doc/Polygon_mesh_processing/examples.txt index 2811a37ecef..4e458b9aaef 100644 --- a/Polygon_mesh_processing/doc/Polygon_mesh_processing/examples.txt +++ b/Polygon_mesh_processing/doc/Polygon_mesh_processing/examples.txt @@ -21,4 +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/repair_polygon_soup_example.cpp */ From 31393d087966eaf1822d4eb437529390991c1299 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mael=20Rouxel-Labb=C3=A9?= Date: Fri, 17 Aug 2018 16:46:55 +0200 Subject: [PATCH 21/23] Removed useless explicit link --- .../doc/Polygon_mesh_processing/PackageDescription.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Polygon_mesh_processing/doc/Polygon_mesh_processing/PackageDescription.txt b/Polygon_mesh_processing/doc/Polygon_mesh_processing/PackageDescription.txt index d3c0ad13f1f..171b06ce8c2 100644 --- a/Polygon_mesh_processing/doc/Polygon_mesh_processing/PackageDescription.txt +++ b/Polygon_mesh_processing/doc/Polygon_mesh_processing/PackageDescription.txt @@ -126,7 +126,7 @@ and provides a list of the parameters that are used in this package. - `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()` -- \link PMP_repairing_grp `CGAL::Polygon_mesh_processing::stitch_borders()` \endlink +- `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()` From 19c99923b2ed7c11f78c4f96b963d97133a7b981 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Loriot?= Date: Fri, 17 Aug 2018 17:09:20 +0200 Subject: [PATCH 22/23] fix typo that was preventing from building the doc --- .../include/CGAL/Polygon_mesh_processing/repair_polygon_soup.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/repair_polygon_soup.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/repair_polygon_soup.h index 1796517c74e..3c39086b89f 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/repair_polygon_soup.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/repair_polygon_soup.h @@ -992,7 +992,7 @@ std::size_t merge_duplicate_polygons_in_polygon_soup(PointRange& points, /// 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_orientatio +/// \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. From 11015420ef89e205694a2e99e117eb94e4f6e3d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mael=20Rouxel-Labb=C3=A9?= Date: Wed, 5 Sep 2018 10:45:51 +0200 Subject: [PATCH 23/23] Fixed comment command in the user manual --- .../doc/Polygon_mesh_processing/Polygon_mesh_processing.txt | 4 ++-- 1 file changed, 2 insertions(+), 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 8dbb6d4d80c..2a089bbeafb 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 @@ -503,7 +503,7 @@ with duplicated border edges. \cgalExample{Polygon_mesh_processing/stitch_borders_example.cpp} ******************* -\cond + ******************* \subsection PolygonSoups Polygon Soups