From 633477b8aa1006ce1f53e180aea73f26fe756b00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mael=20Rouxel-Labb=C3=A9?= Date: Tue, 23 Jul 2019 15:17:52 +0200 Subject: [PATCH 01/56] Remove useless border check (the vertex is interior) --- .../include/CGAL/Polygon_mesh_processing/repair.h | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/repair.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/repair.h index 3418e8d8bd1..55d4b9ac73e 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/repair.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/repair.h @@ -1302,13 +1302,11 @@ bool remove_degenerate_faces(const FaceRange& face_range, for(vertex_descriptor vd : vertices_to_remove) { - halfedge_descriptor hd=halfedge(vd, tmesh); - for(halfedge_descriptor hd2 : halfedges_around_target(hd, tmesh)) - if (!is_border(hd2, tmesh)) - degenerate_face_set.erase( face(hd2, tmesh) ); + for(halfedge_descriptor hd2 : halfedges_around_target(vd, tmesh)) + degenerate_face_set.erase( face(hd2, tmesh) ); // remove the central vertex and check if the new face is degenerated - hd=CGAL::Euler::remove_center_vertex(hd, tmesh); + halfedge_descriptor hd = CGAL::Euler::remove_center_vertex(halfedge(vd, tmesh), tmesh); if (is_degenerate_triangle_face(face(hd, tmesh), tmesh, np)) { degenerate_face_set.insert( face(hd, tmesh) ); From d5eff6a663fb7f8949c5f05311e59a9a1990b53a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mael=20Rouxel-Labb=C3=A9?= Date: Wed, 24 Jul 2019 10:41:30 +0200 Subject: [PATCH 02/56] Minor doc fix --- .../include/CGAL/Polygon_mesh_processing/repair.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/repair.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/repair.h index 55d4b9ac73e..0b003fc702a 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/repair.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/repair.h @@ -1093,15 +1093,15 @@ bool remove_degenerate_edges(TriangleMesh& tmesh) // - `Compare_distance_3` to compute the distance between 2 points // - `Collinear_3` to check whether 3 points are collinear // - `Less_xyz_3` to compare lexicographically two points -/// - `Equal_3` to check whether 2 points are identical. -/// For each functor Foo, a function `Foo foo_object()` must be provided. +// - `Equal_3` to check whether 2 points are identical. +// For each functor Foo, a function `Foo foo_object()` must be provided. // \cgalParamEnd // \cgalNamedParamsEnd // // \todo the function might not be able to remove all degenerate faces. // We should probably do something with the return type. // -/// \return `true` if all degenerate faces were successfully removed, and `false` otherwise. +// \return `true` if all degenerate faces were successfully removed, and `false` otherwise. template bool remove_degenerate_faces(const FaceRange& face_range, TriangleMesh& tmesh, From dac6c04529056030243bd19d4d8cfc6848561b40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mael=20Rouxel-Labb=C3=A9?= Date: Wed, 24 Jul 2019 10:42:03 +0200 Subject: [PATCH 03/56] Make lack of degenerate faces a precondition of self-intersection removal It's just too tedious / ugly maintaining a range while removing degenerate edges and faces. --- .../include/CGAL/Polygon_mesh_processing/repair.h | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/repair.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/repair.h index 0b003fc702a..b6500b3291d 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/repair.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/repair.h @@ -2889,15 +2889,14 @@ bool remove_self_intersections(TriangleMesh& tm, const NamedParameters& np) if (verbose) std::cout << "DEBUG: Starting remove_self_intersections, is_valid(tm)? " << is_valid_polygon_mesh(tm) << "\n"; - // first handle the removal of degenerate faces - remove_degenerate_faces(tm, np); + CGAL_precondition_code(std::set degenerate_face_set;) + CGAL_precondition_code(degenerate_faces(face_range, tmesh, + std::inserter(degenerate_face_set, degenerate_face_set.begin()), np);) + CGAL_precondition(degenerate_face_set.empty()); if (!preserve_genus) duplicate_non_manifold_vertices(tm, np); - if (verbose) - std::cout << "DEBUG: After degenerate faces removal, is_valid(tm)? " << is_valid_polygon_mesh(tm) << "\n"; - // Look for self-intersections in the polyhedron and remove them int step=-1; bool all_fixed = true; // indicates if the filling of all created holes went fine From 3a89e8240e484ffda27f7cd6f9b6ff863df48f32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mael=20Rouxel-Labb=C3=A9?= Date: Thu, 25 Jul 2019 09:25:54 +0200 Subject: [PATCH 04/56] Split large and eclectic file PMP/repair.h into smaller files --- .../Polygon_mesh_processing/manifoldness.h | 430 +++ .../remove_degeneracies.h | 1823 +++++++++++ .../remove_self_intersections.h | 684 ++++ .../CGAL/Polygon_mesh_processing/repair.h | 2899 +---------------- .../shape_predicates.h | 153 + 5 files changed, 3099 insertions(+), 2890 deletions(-) create mode 100644 Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/manifoldness.h create mode 100644 Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/remove_degeneracies.h create mode 100644 Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/remove_self_intersections.h diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/manifoldness.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/manifoldness.h new file mode 100644 index 00000000000..ca7f6183c4a --- /dev/null +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/manifoldness.h @@ -0,0 +1,430 @@ +// Copyright (c) 2015 GeometryFactory (France). +// All rights reserved. +// +// This file is part of CGAL (www.cgal.org). +// You can redistribute it and/or modify it under the terms of the GNU +// General Public License as published by the Free Software Foundation, +// either version 3 of the License, or (at your option) any later version. +// +// Licensees holding a valid commercial license may use this file in +// accordance with the commercial license agreement provided with the software. +// +// This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE +// WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. +// +// $URL$ +// $Id$ +// SPDX-License-Identifier: GPL-3.0+ +// +// +// Author(s) : Sebastien Loriot, +// Mael Rouxel-Labbé + +#ifndef CGAL_POLYGON_MESH_PROCESSING_MANIFOLDNESS_H +#define CGAL_POLYGON_MESH_PROCESSING_MANIFOLDNESS_H + +#include + +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include + +namespace CGAL { +namespace Polygon_mesh_processing { + +/// \ingroup PMP_repairing_grp +/// checks whether a vertex of a polygon mesh is non-manifold. +/// +/// @tparam PolygonMesh a model of `HalfedgeListGraph` +/// +/// @param v a vertex of `pm` +/// @param pm a triangle mesh containing `v` +/// +/// \warning This function has linear runtime with respect to the size of the mesh. +/// +/// \sa `duplicate_non_manifold_vertices()` +/// +/// \return `true` if the vertex is non-manifold, `false` otherwise. +template +bool is_non_manifold_vertex(typename boost::graph_traits::vertex_descriptor v, + const PolygonMesh& pm) +{ + typedef typename boost::graph_traits::halfedge_descriptor halfedge_descriptor; + + typedef CGAL::dynamic_halfedge_property_t Halfedge_property_tag; + typedef typename boost::property_map::const_type Visited_halfedge_map; + + // Dynamic pmaps do not have default initialization values (yet) + Visited_halfedge_map visited_halfedges = get(Halfedge_property_tag(), pm); + for(halfedge_descriptor h : halfedges(pm)) + put(visited_halfedges, h, false); + + std::size_t incident_null_faces_counter = 0; + for(halfedge_descriptor h : halfedges_around_target(v, pm)) + { + put(visited_halfedges, h, true); + if(CGAL::is_border(h, pm)) + ++incident_null_faces_counter; + } + + if(incident_null_faces_counter > 1) + { + // The vertex is the sole connection between two connected components --> non-manifold + return true; + } + + for(halfedge_descriptor h : halfedges(pm)) + { + if(v == target(h, pm)) + { + // Haven't seen that halfedge yet ==> more than one umbrella incident to 'v' ==> non-manifold + if(!get(visited_halfedges, h)) + return true; + } + } + + return false; +} + +namespace internal { + +template +struct Vertex_collector +{ + typedef typename boost::graph_traits::vertex_descriptor vertex_descriptor; + + bool has_old_vertex(const vertex_descriptor v) const { return collections.count(v) != 0; } + + void collect_vertices(vertex_descriptor v1, vertex_descriptor v2) + { + std::vector& verts = collections[v1]; + if(verts.empty()) + verts.push_back(v1); + verts.push_back(v2); + } + + template + void dump(OutputIterator out) + { + typedef std::pair > Pair_type; + for(const Pair_type& p : collections) + *out++ = p.second; + } + + void dump(Emptyset_iterator) { } + + std::map > collections; +}; + +template +typename boost::graph_traits::vertex_descriptor +create_new_vertex_for_sector(typename boost::graph_traits::halfedge_descriptor sector_begin_h, + typename boost::graph_traits::halfedge_descriptor sector_last_h, + PolygonMesh& pm, + const VPM& vpm, + const ConstraintMap& cmap) +{ + typedef typename boost::graph_traits::vertex_descriptor vertex_descriptor; + typedef typename boost::graph_traits::halfedge_descriptor halfedge_descriptor; + + vertex_descriptor old_vd = target(sector_begin_h, pm); + vertex_descriptor new_vd = add_vertex(pm); + put(vpm, new_vd, get(vpm, old_vd)); + + put(cmap, new_vd, true); + + set_halfedge(new_vd, sector_begin_h, pm); + halfedge_descriptor h = sector_begin_h; + do + { + set_target(h, new_vd, pm); + + if(h == sector_last_h) + break; + else + h = prev(opposite(h, pm), pm); + } + while(h != sector_begin_h); // for safety + CGAL_assertion(h != sector_begin_h); + + return new_vd; +} + +template +std::size_t make_umbrella_manifold(typename boost::graph_traits::halfedge_descriptor h, + PolygonMesh& pm, + internal::Vertex_collector& dmap, + const NamedParameters& np) +{ + typedef typename boost::graph_traits::vertex_descriptor vertex_descriptor; + typedef typename boost::graph_traits::halfedge_descriptor halfedge_descriptor; + + using boost::get_param; + using boost::choose_param; + + typedef typename GetVertexPointMap::type VertexPointMap; + VertexPointMap vpm = choose_param(get_param(np, internal_np::vertex_point), + get_property_map(vertex_point, pm)); + + typedef typename boost::lookup_named_param_def // default (no constraint pmap) + >::type VerticesMap; + VerticesMap cmap = choose_param(get_param(np, internal_np::vertex_is_constrained), + Constant_property_map(false)); + + std::size_t nb_new_vertices = 0; + + vertex_descriptor old_v = target(h, pm); + put(cmap, old_v, true); // store the duplicates + + // count the number of borders + int border_counter = 0; + halfedge_descriptor ih = h, done = ih, border_h = h; + do + { + if(is_border(ih, pm)) + { + border_h = ih; + ++border_counter; + } + + ih = prev(opposite(ih, pm), pm); + } + while(ih != done); + + bool is_non_manifold_within_umbrella = (border_counter > 1); + + // if there is a single sector, then simply move the full umbrella to a new vertex, and we're done + if(!is_non_manifold_within_umbrella) + { + // note that since this is marked as a non-manifold vertex, we necessarily need to create + // a new vertex for this umbrella (the main umbrella is not marked as non-manifold) + halfedge_descriptor last_h = opposite(next(h, pm), pm); + vertex_descriptor new_v = create_new_vertex_for_sector(h, last_h, pm, vpm, cmap); + dmap.collect_vertices(old_v, new_v); + nb_new_vertices = 1; + } + // if there is more than one sector, look at each sector and split them away from the main one + else + { + // the first manifold sector, described by two halfedges + halfedge_descriptor sector_start_h = border_h; + CGAL_assertion(is_border(border_h, pm)); + + bool should_stop = false; + bool is_main_sector = true; + do + { + CGAL_assertion(is_border(sector_start_h, pm)); + + // collect the sector and split it away if it must be + halfedge_descriptor sector_last_h = sector_start_h; + do + { + halfedge_descriptor next_h = prev(opposite(sector_last_h, pm), pm); + + if(is_border(next_h, pm)) + break; + + sector_last_h = next_h; + } + while(sector_last_h != sector_start_h); + CGAL_assertion(!is_border(sector_last_h, pm)); + CGAL_assertion(sector_last_h != sector_start_h); + + halfedge_descriptor next_start_h = prev(opposite(sector_last_h, pm), pm); + + // there are multiple CCs incident to this particular vertex, and we should create a new vertex + // if it's not the first umbrella around 'old_v' or not the first sector, but not if it's + // the first umbrella and first sector. + bool must_create_new_vertex = (!is_main_sector || dmap.has_old_vertex(old_v)); + + // In any case, we must set up the next pointer correctly + set_next(sector_start_h, opposite(sector_last_h, pm), pm); + + if(must_create_new_vertex) + { + vertex_descriptor new_v = create_new_vertex_for_sector(sector_start_h, sector_last_h, pm, vpm, cmap); + dmap.collect_vertices(old_v, new_v); + ++nb_new_vertices; + } + + is_main_sector = false; + sector_start_h = next_start_h; + should_stop = (sector_start_h == border_h); + } + while(!should_stop); + } + + return nb_new_vertices; +} + +} // end namespace internal + +/// \ingroup PMP_repairing_grp +/// collects the non-manifold vertices (if any) present in the mesh. A non-manifold vertex `v` is returned +/// via one incident halfedge `h` such that `target(h, pm) = v` for all the umbrellas that `v` apppears in +/// (an umbrella being the set of faces incident to all the halfedges reachable by walking around `v` +/// using `hnext = prev(opposite(h, pm), pm)`, starting from `h`). +/// +/// @tparam PolygonMesh a model of `HalfedgeListGraph` +/// @tparam OutputIterator a model of `OutputIterator` holding objects of type +/// `boost::graph_traits::%halfedge_descriptor` +/// +/// @param pm a triangle mesh +/// @param out the output iterator that collects halfedges incident to `v` +/// +/// \sa `is_non_manifold_vertex()` +/// \sa `duplicate_non_manifold_vertices()` +/// +/// \return the output iterator. +template +OutputIterator non_manifold_vertices(const PolygonMesh& pm, + OutputIterator out) +{ + // Non-manifoldness can appear either: + // - if 'pm' is pinched at a vertex. While traversing the incoming halfedges at this vertex, + // we will meet strictly more than one border halfedge. + // - if there are multiple umbrellas around a vertex. In that case, we will find a non-visited + // halfedge that has for target a vertex that is already visited. + + typedef typename boost::graph_traits::vertex_descriptor vertex_descriptor; + typedef typename boost::graph_traits::halfedge_descriptor halfedge_descriptor; + + typedef CGAL::dynamic_vertex_property_t Vertex_property_tag; + typedef typename boost::property_map::const_type Visited_vertex_map; + typedef CGAL::dynamic_halfedge_property_t Halfedge_property_tag; + typedef typename boost::property_map::const_type Visited_halfedge_map; + + Visited_vertex_map visited_vertices = get(Vertex_property_tag(), pm); + Visited_halfedge_map visited_halfedges = get(Halfedge_property_tag(), pm); + + // Dynamic pmaps do not have default initialization values (yet) + for(vertex_descriptor v : vertices(pm)) + put(visited_vertices, v, false); + for(halfedge_descriptor h : halfedges(pm)) + put(visited_halfedges, h, false); + + for(halfedge_descriptor h : halfedges(pm)) + { + // If 'h' is not visited yet, we walk around the target of 'h' and mark these + // halfedges as visited. Thus, if we are here and the target is already marked as visited, + // it means that the vertex is non manifold. + if(!get(visited_halfedges, h)) + { + put(visited_halfedges, h, true); + bool is_non_manifold = false; + + vertex_descriptor vd = target(h, pm); + if(get(visited_vertices, vd)) // already seen this vertex, but not from this star + is_non_manifold = true; + + put(visited_vertices, vd, true); + + // While walking the star of this halfedge, if we meet a border halfedge more than once, + // it means the mesh is pinched and we are also in the case of a non-manifold situation + halfedge_descriptor ih = h, done = ih; + int border_counter = 0; + do + { + put(visited_halfedges, ih, true); + if(is_border(ih, pm)) + ++border_counter; + + ih = prev(opposite(ih, pm), pm); + } + while(ih != done); + + if(border_counter > 1) + is_non_manifold = true; + + if(is_non_manifold) + *out++ = h; + } + } + + return out; +} + +/// \ingroup PMP_repairing_grp +/// duplicates all the non-manifold vertices of the input mesh. +/// +/// @tparam PolygonMesh a model of `HalfedgeListGraph` and `MutableHalfedgeGraph` +/// @tparam NamedParameters a sequence of \ref pmp_namedparameters "Named Parameters" +/// +/// @param pm the surface mesh to be repaired +/// @param np optional \ref pmp_namedparameters "Named Parameters" described below +/// +/// \cgalNamedParamsBegin +/// \cgalParamBegin{vertex_point_map} the property map with the points associated to the vertices of `pmesh`. +/// The type of this map is model of `ReadWritePropertyMap`. +/// If this parameter is omitted, an internal property map for +/// `CGAL::vertex_point_t` should be available in `PolygonMesh` +/// \cgalParamEnd +/// \cgalParamBegin{vertex_is_constrained_map} a writable property map with `vertex_descriptor` +/// as key and `bool` as `value_type`. `put(pmap, v, true)` will be called for each duplicated +/// vertices, as well as the original non-manifold vertex in the input mesh. +/// \cgalParamEnd +/// \cgalParamBegin{output_iterator} a model of `OutputIterator` with value type +/// `std::vector`. The first vertex of each vector is a non-manifold vertex +/// of the input mesh, followed by the new vertices that were created to fix this precise +/// non-manifold configuration. +/// \cgalParamEnd +/// \cgalNamedParamsEnd +/// +/// \return the number of vertices created. +template +std::size_t duplicate_non_manifold_vertices(PolygonMesh& pm, + const NamedParameters& np) +{ + using boost::get_param; + using boost::choose_param; + + typedef boost::graph_traits GT; + typedef typename GT::halfedge_descriptor halfedge_descriptor; + + typedef typename boost::lookup_named_param_def < + internal_np::output_iterator_t, + NamedParameters, + Emptyset_iterator + > ::type Output_iterator; + + Output_iterator out = choose_param(get_param(np, internal_np::output_iterator), Emptyset_iterator()); + + std::vector non_manifold_cones; + non_manifold_vertices(pm, std::back_inserter(non_manifold_cones)); + + internal::Vertex_collector dmap; + std::size_t nb_new_vertices = 0; + if(!non_manifold_cones.empty()) + { + for(halfedge_descriptor h : non_manifold_cones) + nb_new_vertices += internal::make_umbrella_manifold(h, pm, dmap, np); + + dmap.dump(out); + } + + return nb_new_vertices; +} + +template +std::size_t duplicate_non_manifold_vertices(PolygonMesh& pm) +{ + return duplicate_non_manifold_vertices(pm, parameters::all_default()); +} + +} // namespace Polygon_mesh_processing +} // namespace CGAL + +#endif // CGAL_POLYGON_MESH_PROCESSING_MANIFOLDNESS_H diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/remove_degeneracies.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/remove_degeneracies.h new file mode 100644 index 00000000000..76cedf801ed --- /dev/null +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/remove_degeneracies.h @@ -0,0 +1,1823 @@ +// Copyright (c) 2015-2019 GeometryFactory (France). +// All rights reserved. +// +// This file is part of CGAL (www.cgal.org). +// You can redistribute it and/or modify it under the terms of the GNU +// General Public License as published by the Free Software Foundation, +// either version 3 of the License, or (at your option) any later version. +// +// Licensees holding a valid commercial license may use this file in +// accordance with the commercial license agreement provided with the software. +// +// This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE +// WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. +// +// $URL$ +// $Id$ +// SPDX-License-Identifier: GPL-3.0+ +// +// Author(s) : Sebastien Loriot, +// Mael Rouxel-Labbé + +#ifndef CGAL_POLYGON_MESH_PROCESSING_REMOVE_DEGENERACIES_H +#define CGAL_POLYGON_MESH_PROCESSING_REMOVE_DEGENERACIES_H + +#include + +#include + +#include +#include + +#include + +#ifdef CGAL_PMP_REMOVE_DEGENERATE_FACES_DEBUG +#include +#include +#endif + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace CGAL { +namespace Polygon_mesh_processing { +namespace debug { + +template +std::ostream& +dump_edge_neighborhood(typename boost::graph_traits::edge_descriptor ed, + TriangleMesh& tmesh, + const VertexPointMap& vpmap, + std::ostream& out) +{ + typedef boost::graph_traits GT; + typedef typename GT::vertex_descriptor vertex_descriptor; + typedef typename GT::halfedge_descriptor halfedge_descriptor; + typedef typename GT::face_descriptor face_descriptor; + + halfedge_descriptor h = halfedge(ed, tmesh); + + std::map vertices; + std::set faces; + int vindex = 0; + + for(halfedge_descriptor hd : halfedges_around_target(h, tmesh)) + { + if(vertices.insert(std::make_pair(source(hd, tmesh), vindex)).second) + ++vindex; + + if(!is_border(hd, tmesh)) + faces.insert(face(hd, tmesh)); + } + + h = opposite(h, tmesh); + for(halfedge_descriptor hd : halfedges_around_target(h, tmesh)) + { + if(vertices.insert(std::make_pair(source(hd, tmesh), vindex)).second) + ++vindex; + + if(!is_border(hd, tmesh)) + faces.insert(face(hd, tmesh)); + } + + std::vector ordered_vertices(vertices.size()); + typedef std::pair Pair_type; + for(const Pair_type& p : vertices) + ordered_vertices[p.second] = p.first; + + out << "OFF\n" << ordered_vertices.size() << " " << faces.size() << " 0\n"; + for(vertex_descriptor vd : ordered_vertices) + out << get(vpmap, vd) << "\n"; + for(face_descriptor fd : faces) + { + out << "3"; + h = halfedge(fd, tmesh); + for(halfedge_descriptor hd : halfedges_around_face(h, tmesh)) + out << " " << vertices[target(hd, tmesh)]; + out << "\n"; + } + + return out; +} + +template +void dump_cc_faces(const FaceRange& cc_faces, const TriangleMesh& tm, std::ostream& output) +{ + typedef typename boost::graph_traits::vertex_descriptor vertex_descriptor; + typedef typename boost::graph_traits::face_descriptor face_descriptor; + + typedef typename boost::property_map::const_type Vpm; + typedef typename boost::property_traits::value_type Point_3; + + Vpm vpm = get(boost::vertex_point, tm); + + int id = 0; + std::map vids; + + for(face_descriptor f : cc_faces) + { + if(vids.insert(std::make_pair(target(halfedge(f, tm), tm), id)).second) ++id; + if(vids.insert(std::make_pair(target(next(halfedge(f, tm), tm), tm), id)).second) ++id; + if(vids.insert(std::make_pair(target(next(next(halfedge(f, tm), tm), tm), tm), id)).second) ++id; + } + + std::vector points(vids.size()); + typedef std::pair Pair_type; + for(const Pair_type& p : vids) + points[p.second] = get(vpm, p.first); + + output << std::setprecision(17); + output << "OFF\n" << vids.size() << " " << cc_faces.size() << " 0\n"; + for(Point_3 p : points) + output << p << "\n"; + for(face_descriptor f : cc_faces) + { + output << "3 " + << vids[target(halfedge(f, tm), tm)] << " " + << vids[target(next(halfedge(f, tm), tm), tm)] << " " + << vids[target(next(next(halfedge(f, tm), tm), tm), tm)] << "\n"; + } +} + +} // namespace debug + +namespace internal { + +template +struct Less_vertex_point +{ + typedef typename boost::graph_traits::vertex_descriptor vertex_descriptor; + + Less_vertex_point(const Traits& traits, const VertexPointMap& vpmap) + : m_traits(traits), m_vpmap(vpmap) + {} + + bool operator()(vertex_descriptor v1, vertex_descriptor v2) const { + return m_traits.less_xyz_3_object()(get(m_vpmap, v1), get(m_vpmap, v2)); + } + + const Traits& m_traits; + const VertexPointMap& m_vpmap; +}; + +} // end namespace internal + +// this function removes a border edge even if it does not satisfy the link condition. +// null_vertex() is returned if the removal changes the topology of the input +template +typename boost::graph_traits::vertex_descriptor +remove_a_border_edge(typename boost::graph_traits::edge_descriptor ed, + TriangleMesh& tm, + EdgeSet& edge_set, + FaceSet& face_set) +{ + typedef boost::graph_traits GT; + typedef typename GT::vertex_descriptor vertex_descriptor; + typedef typename GT::halfedge_descriptor halfedge_descriptor; + typedef typename GT::edge_descriptor edge_descriptor; + typedef typename GT::face_descriptor face_descriptor; + + halfedge_descriptor h = halfedge(ed, tm); + + if(is_border(h, tm)) + h = opposite(h, tm); + + halfedge_descriptor opp_h = opposite(h, tm); + CGAL_assertion(is_border(opp_h, tm)); + CGAL_assertion(!is_border(h, tm)); + + CGAL_assertion(next(next(opp_h, tm), tm) != opp_h); // not working for a hole made of 2 edges + CGAL_assertion(next(next(next(opp_h, tm), tm), tm) != opp_h); // not working for a hole make of 3 edges + + if(CGAL::Euler::does_satisfy_link_condition(edge(h, tm), tm)) + { + edge_set.erase(ed); + halfedge_descriptor h = halfedge(ed, tm); + if(is_border(h, tm)) + h = opposite(h, tm); + + edge_set.erase(edge(prev(h, tm), tm)); + face_set.erase(face(h, tm)); + + return CGAL::Euler::collapse_edge(ed, tm); + } + + // collect edges that have one vertex in the link of + // the vertices of h and one of the vertex of h as other vertex + std::set common_incident_edges; + for(halfedge_descriptor hos : halfedges_around_source(h, tm)) + { + for(halfedge_descriptor hot : halfedges_around_target(h, tm)) + { + if(target(hos, tm) == source(hot, tm)) + { + common_incident_edges.insert(edge(hot, tm)); + common_incident_edges.insert(edge(hos, tm)); + } + } + } + + CGAL_assertion(common_incident_edges.size() >= 2); + + // in the following loop, we visit define a connected component of + // faces bounded by edges in common_incident_edges and h. We look + // for the maximal one. This set of faces is the one that will + // disappear while collapsing ed + std::set marked_faces; + + std::vector queue; + queue.push_back(opposite(next(h, tm), tm)); + queue.push_back(opposite(prev(h, tm), tm)); + marked_faces.insert(face(h, tm)); + + do + { + std::vector boundary; + while(!queue.empty()) + { + halfedge_descriptor back=queue.back(); + queue.pop_back(); + face_descriptor fback=face(back, tm); + if(common_incident_edges.count(edge(back, tm))) + { + boundary.push_back(back); + continue; + } + + if(fback==GT::null_face() || !marked_faces.insert(fback).second) + continue; + + queue.push_back(opposite(next(back, tm), tm)); + if(is_border(queue.back(), tm)) + { +#ifdef CGAL_PMP_REMOVE_DEGENERATE_FACES_DEBUG + std::cout << "Boundary reached during exploration, the region to be removed is not a topological disk, not handled for now.\n"; +#endif + return GT::null_vertex(); + } + + queue.push_back(opposite(prev(back, tm), tm)); + if(is_border(queue.back(), tm)) + { +#ifdef CGAL_PMP_REMOVE_DEGENERATE_FACES_DEBUG + std::cout << "Boundary reached during exploration, the region to be removed is not a topological disk, not handled for now.\n"; +#endif + return GT::null_vertex(); + } + } + + CGAL_assertion(boundary.size() == 2); + common_incident_edges.erase(edge(boundary[0], tm)); + common_incident_edges.erase(edge(boundary[1], tm)); + if(!is_border(boundary[0], tm) || common_incident_edges.empty()) + queue.push_back(boundary[0]); + if(!is_border(boundary[1], tm) || common_incident_edges.empty()) + queue.push_back(boundary[1]); + } + while(!common_incident_edges.empty()); + + // hk1 and hk2 are bounding the region that will be removed. + // The edge of hk2 will be removed and hk2 will be replaced + // by the opposite edge of hk1 + halfedge_descriptor hk1 = queue.front(); + halfedge_descriptor hk2 = queue.back(); + if(target(hk1, tm)!=source(hk2, tm)) + std::swap(hk1, hk2); + + CGAL_assertion(target(hk1, tm) == source(hk2, tm)); + CGAL_assertion(source(hk1, tm) == source(h, tm)); + CGAL_assertion(target(hk2, tm) == target(h, tm)); + + CGAL_assertion(is_valid_polygon_mesh(tm)); + if(!is_selection_a_topological_disk(marked_faces, tm)) + { +#ifdef CGAL_PMP_REMOVE_DEGENERATE_FACES_DEBUG + std::cout << "The region to be removed is not a topological disk, not handled for now.\n"; +#endif + return GT::null_vertex(); + } + + if(is_border(hk1, tm) && is_border(hk2, tm)) + { +#ifdef CGAL_PMP_REMOVE_DEGENERATE_FACES_DEBUG + std::cout << "The region to be removed is an isolated region, not handled for now.\n"; +#endif + return GT::null_vertex(); + } + + // collect vertices and edges to remove and do remove faces + std::set edges_to_remove; + std::set vertices_to_remove; + for(face_descriptor fd : marked_faces) + { + halfedge_descriptor hd = halfedge(fd, tm); + for(int i=0; i<3; ++i) + { + edges_to_remove.insert(edge(hd, tm)); + vertices_to_remove.insert(target(hd, tm)); + hd = next(hd, tm); + } + } + + vertex_descriptor vkept = source(hk1, tm); + + //back-up next, prev halfedge pointers to be restored after removal + halfedge_descriptor hp = prev(opp_h, tm); + halfedge_descriptor hn = next(opp_h, tm); + halfedge_descriptor hk1_opp_next = next(hk2, tm); + halfedge_descriptor hk1_opp_prev = prev(hk2, tm); + face_descriptor hk1_opp_face = face(hk2, tm); + + // we will remove the target of hk2, update vertex pointers + for(halfedge_descriptor hot : halfedges_around_target(hk2, tm)) + set_target(hot, vkept, tm); + + // update halfedge pointers since hk2 will be removed + set_halfedge(vkept, opposite(hk1, tm), tm); + set_halfedge(target(hk1, tm), hk1, tm); + + // do not remove hk1 and its vertices + vertices_to_remove.erase(vkept); + vertices_to_remove.erase(target(hk1, tm)); + edges_to_remove.erase(edge(hk1, tm)); + + bool hk2_equals_hp = (hk2 == hp); + CGAL_assertion(is_border(hk2, tm) == hk2_equals_hp); + + /* + - case hk2!=hp + + /\ / + hk1/ \hk2 / + / \ / + ____/______\/____ + hn h_opp hp + + - case hk2==hp + + /\ + hk1/ \hk2 == hp + / \ + ____/______\ + hn h_opp + */ + + // remove vertices + for(vertex_descriptor vd : vertices_to_remove) + remove_vertex(vd, tm); + + // remove edges + for(edge_descriptor ed : edges_to_remove) + { + edge_set.erase(ed); + remove_edge(ed, tm); + } + + // remove faces + for(face_descriptor fd : marked_faces) + { + face_set.erase(fd); + remove_face(fd, tm); + } + + // now update pointers + set_face(opposite(hk1, tm), hk1_opp_face, tm); + if(!hk2_equals_hp) + { + set_next(hp, hn, tm); + set_next(opposite(hk1, tm), hk1_opp_next, tm); + set_next(hk1_opp_prev, opposite(hk1, tm), tm); + set_halfedge(hk1_opp_face, opposite(hk1, tm), tm); + } + else + { + set_next(hk1_opp_prev, opposite(hk1, tm), tm); + set_next(opposite(hk1, tm), hn, tm); + } + + CGAL_assertion(is_valid_polygon_mesh(tm)); + return vkept; +} + +template +typename boost::graph_traits::vertex_descriptor +remove_a_border_edge(typename boost::graph_traits::edge_descriptor ed, + TriangleMesh& tm) +{ + std::set::edge_descriptor> edge_set; + std::set::face_descriptor> face_set; + + return remove_a_border_edge(ed, tm, edge_set, face_set); +} + +template +bool remove_degenerate_edges(const EdgeRange& edge_range, + TriangleMesh& tmesh, + FaceSet& face_set, + const NamedParameters& np) +{ + CGAL_assertion(CGAL::is_triangle_mesh(tmesh)); + CGAL_assertion(CGAL::is_valid_polygon_mesh(tmesh)); + + using boost::get_param; + using boost::choose_param; + + typedef TriangleMesh TM; + typedef typename boost::graph_traits GT; + typedef typename GT::vertex_descriptor vertex_descriptor; + typedef typename GT::halfedge_descriptor halfedge_descriptor; + typedef typename GT::edge_descriptor edge_descriptor; + typedef typename GT::face_descriptor face_descriptor; + + typedef typename GetVertexPointMap::type VertexPointMap; + VertexPointMap vpmap = choose_param(get_param(np, internal_np::vertex_point), + get_property_map(vertex_point, tmesh)); + + typedef typename GetGeomTraits::type Traits; + + std::size_t nb_deg_faces = 0; + bool all_removed = false; + bool some_removed = true; + bool preserve_genus = boost::choose_param(boost::get_param(np, internal_np::preserve_genus), true); + + // collect edges of length 0 + while(some_removed && !all_removed) + { + some_removed = false; + all_removed = true; + std::set degenerate_edges_to_remove; + degenerate_edges(edge_range, tmesh, std::inserter(degenerate_edges_to_remove, + degenerate_edges_to_remove.end())); + +#ifdef CGAL_PMP_REMOVE_DEGENERATE_FACES_DEBUG + std::cout << "Found " << degenerate_edges_to_remove.size() << " null edges.\n"; +#endif + + // first try to remove all collapsable edges + typename std::set::iterator it = degenerate_edges_to_remove.begin(); + while(it != degenerate_edges_to_remove.end()) + { + edge_descriptor e = *it; + if(CGAL::Euler::does_satisfy_link_condition(e, tmesh)) + { + halfedge_descriptor h = halfedge(e, tmesh); + degenerate_edges_to_remove.erase(it); + + // remove edges that could also be set for removal + if(face(h, tmesh) != GT::null_face()) + { + ++nb_deg_faces; + degenerate_edges_to_remove.erase(edge(prev(h, tmesh), tmesh)); + face_set.erase(face(h, tmesh)); + } + + if(face(opposite(h, tmesh), tmesh) != GT::null_face()) + { + ++nb_deg_faces; + degenerate_edges_to_remove.erase(edge(prev(opposite(h, tmesh), tmesh), tmesh)); + face_set.erase(face(opposite(h, tmesh), tmesh)); + } + + //now remove the edge + CGAL::Euler::collapse_edge(e, tmesh); + + // some_removed is not updated on purpose because if nothing + // happens below then nothing can be done + it = degenerate_edges_to_remove.begin(); + } + else // link condition not satisfied + { + ++it; + } + } + + CGAL_assertion(is_valid_polygon_mesh(tmesh)); +#ifdef CGAL_PMP_REMOVE_DEGENERATE_FACES_DEBUG + std::cout << "Remaining " << degenerate_edges_to_remove.size() << " null edges to be handled.\n"; +#endif + + while(!degenerate_edges_to_remove.empty()) + { + edge_descriptor ed = *degenerate_edges_to_remove.begin(); + degenerate_edges_to_remove.erase(degenerate_edges_to_remove.begin()); + halfedge_descriptor h = halfedge(ed, tmesh); + + if(CGAL::Euler::does_satisfy_link_condition(ed, tmesh)) + { + // remove edges that could also be set for removal + if(face(h, tmesh) != GT::null_face()) + { + ++nb_deg_faces; + degenerate_edges_to_remove.erase(edge(prev(h, tmesh), tmesh)); + face_set.erase(face(h, tmesh)); + } + + if(face(opposite(h, tmesh), tmesh)!=GT::null_face()) + { + ++nb_deg_faces; + degenerate_edges_to_remove.erase(edge(prev(opposite(h, tmesh), tmesh), tmesh)); + face_set.erase(face(opposite(h, tmesh), tmesh)); + } + + //now remove the edge + CGAL::Euler::collapse_edge(ed, tmesh); + some_removed = true; + } + else // link condition not satisfied + { + // handle the case when the edge is incident to a triangle hole + // we first fill the hole and try again + if(is_border(ed, tmesh)) + { + halfedge_descriptor hd = halfedge(ed, tmesh); + if(!is_border(hd, tmesh)) + hd = opposite(hd, tmesh); + + if(is_triangle(hd, tmesh)) + { + if(!preserve_genus) + { + Euler::fill_hole(hd, tmesh); + degenerate_edges_to_remove.insert(ed); // reinsert the edge for future processing + } + else + { + all_removed=false; + } + + continue; + } + +#ifdef CGAL_PMP_REMOVE_DEGENERATE_FACES_DEBUG + std::cout << "Calling remove_a_border_edge\n"; +#endif + + vertex_descriptor vd = remove_a_border_edge(ed, tmesh, degenerate_edges_to_remove, face_set); + if(vd == GT::null_vertex()) + { + // TODO: if some border edges are later removed, the edge might be processable later + // for example if it belongs to boundary cycle of edges where the number of non-degenerate + // edges is 2. That's what happen with fused_vertices.off in the testsuite where the edges + // are not processed the same way with Polyhedron and Surface_mesh. In the case of Polyhedron + // more degenerate edges could be removed. + all_removed = false; + } + else + some_removed = true; + + continue; + } + else + { + halfedge_descriptor hd = halfedge(ed, tmesh); + // if both vertices are boundary vertices we can't do anything + bool impossible = false; + for(halfedge_descriptor h : halfedges_around_target(hd, tmesh)) + { + if(is_border(h, tmesh)) + { + impossible = true; + break; + } + } + + if(impossible) + { + impossible = false; + for(halfedge_descriptor h : halfedges_around_source(hd, tmesh)) + { + if(is_border(h, tmesh)) + { + impossible = true; + break; + } + } + + if(impossible) + { + all_removed = false; + continue; + } + } + } + + // When the edge does not satisfy the link condition, it means that it cannot be + // collapsed as is. In the following if there is a topological issue + // with contracting the edge (component or geometric feature that disappears), + // nothing is done. + // We start by marking the faces that are incident to an edge endpoint. + // If the set of marked faces is a topologically disk, then we simply remove all the simplicies + // inside the disk and star the hole with the edge vertex kept. + // If the set of marked faces is not a topological disk, it has some non-manifold vertices + // on its boundary. We need to mark additional faces to make it a topological disk. + // We can then apply the star hole procedure. + // Right now we additionally mark the smallest connected components of non-marked faces + // (using the number of faces) + + //backup central point + typename Traits::Point_3 pt = get(vpmap, source(ed, tmesh)); + + // mark faces of the link of each endpoints of the edge which collapse is not topologically valid + std::set marked_faces; + + // first endpoint + for(halfedge_descriptor hd : CGAL::halfedges_around_target(halfedge(ed, tmesh), tmesh)) + if(!is_border(hd, tmesh)) + marked_faces.insert(face(hd, tmesh)); + + // second endpoint + for(halfedge_descriptor hd : CGAL::halfedges_around_target(opposite(halfedge(ed, tmesh), tmesh), tmesh)) + if(!is_border(hd, tmesh)) + marked_faces.insert(face(hd, tmesh)); + + // extract the halfedges on the boundary of the marked region + std::vector border; + for(face_descriptor fd : marked_faces) + { + for(halfedge_descriptor hd : CGAL::halfedges_around_face(halfedge(fd, tmesh), tmesh)) + { + halfedge_descriptor hd_opp = opposite(hd, tmesh); + if(is_border(hd_opp, tmesh) || marked_faces.count(face(hd_opp, tmesh)) == 0) + { + border.push_back(hd); + } + } + } + + if(border.empty()) + { + // a whole connected component (without boundary) got selected and will disappear (not handled for now) +#ifdef CGAL_PMP_REMOVE_DEGENERATE_FACES_DEBUG + std::cout << "Trying to remove a whole connected component, not handled yet\n"; +#endif + all_removed = false; + continue; + } + + // define cc of border halfedges: two halfedges are in the same cc + // if they are on the border of the cc of non-marked faces. + typedef CGAL::Union_find UF_ds; + UF_ds uf; + std::map handles; + + // one cc per border halfedge + for(halfedge_descriptor hd : border) + handles.insert(std::make_pair(hd, uf.make_set(hd))); + + // join cc's + for(halfedge_descriptor hd : border) + { + CGAL_assertion(marked_faces.count(face(hd, tmesh)) > 0); + CGAL_assertion(marked_faces.count(face(opposite(hd, tmesh), tmesh)) == 0); + halfedge_descriptor candidate = hd; + + do + { + candidate = prev(opposite(candidate, tmesh), tmesh); + } + while(!marked_faces.count(face(opposite(candidate, tmesh), tmesh))); + + uf.unify_sets(handles[hd], handles[opposite(candidate, tmesh)]); + } + + std::size_t nb_cc = uf.number_of_sets(); + if(nb_cc != 1) + { + // if more than one connected component is found then the patch + // made of marked faces contains "non-manifold" vertices. + // The smallest components need to be marked so that the patch + // made of marked faces is a topological disk + + // we will explore in parallel the connected components and will stop + // when all but one connected component have been entirely explored. + // We add one face at a time for each cc in order to not explore a + // potentially very large cc. + std::vector > stacks_per_cc(nb_cc); + std::vector > faces_per_cc(nb_cc); + std::vector exploration_finished(nb_cc, false); + + // init the stacks of halfedges using the cc of the boundary + std::size_t index = 0; + std::map ccs; + + typedef std::pair Pair_type; + for(const Pair_type& p : handles) + { + halfedge_descriptor opp_hedge = opposite(p.first, tmesh); + if(is_border(opp_hedge, tmesh)) + continue; // nothing to do on the boundary + + typedef typename std::map::iterator Map_it; + std::pair insert_res = ccs.insert(std::make_pair(*uf.find(p.second), index)); + + if(insert_res.second) + ++index; + + stacks_per_cc[insert_res.first->second].push_back(prev(opp_hedge, tmesh)); + stacks_per_cc[insert_res.first->second].push_back(next(opp_hedge, tmesh)); + faces_per_cc[insert_res.first->second].insert(face(opp_hedge, tmesh)); + } + + if(index != nb_cc) + { + // most probably, one cc is a cycle of border edges +#ifdef CGAL_PMP_REMOVE_DEGENERATE_FACES_DEBUG + std::cout << "Trying to remove a component with a cycle of halfedges (nested hole or whole component), not handled yet.\n"; +#endif + all_removed = false; + continue; + } + + std::size_t nb_ccs_to_be_explored = nb_cc; + index = 0; + //explore the cc's + do + { + // try to extract one more face for a given cc + do + { + CGAL_assertion(!exploration_finished[index]); + CGAL_assertion(!stacks_per_cc.empty()); + CGAL_assertion(!stacks_per_cc[index].empty()); + + halfedge_descriptor hd = stacks_per_cc[index].back(); + stacks_per_cc[index].pop_back(); + hd = opposite(hd, tmesh); + + if(!is_border(hd, tmesh) && !marked_faces.count(face(hd, tmesh))) + { + if(faces_per_cc[index].insert(face(hd, tmesh)).second) + { + stacks_per_cc[index].push_back(next(hd, tmesh)); + stacks_per_cc[index].push_back(prev(hd, tmesh)); + break; + } + } + + if(stacks_per_cc[index].empty()) + break; + } + while(true); + + // the exploration of a cc is finished when its stack is empty + exploration_finished[index]=stacks_per_cc[index].empty(); + if(exploration_finished[index]) + --nb_ccs_to_be_explored; + + if(nb_ccs_to_be_explored==1) + break; + + while(exploration_finished[(++index)%nb_cc]); + + index = index%nb_cc; + } + while(true); + + /// \todo use the area criteria? this means maybe continue exploration of larger cc + // mark faces of completetly explored cc + for(index=0; index vertices_to_keep; + std::set halfedges_to_keep; + for(halfedge_descriptor hd : border) + { + if(!marked_faces.count(face(opposite(hd, tmesh), tmesh))) + { + halfedges_to_keep.insert(hd); + vertices_to_keep.insert(target(hd, tmesh)); + } + } + + // backup next,prev relationships to set after patch removal + std::vector > next_prev_halfedge_pairs; + halfedge_descriptor first_border_hd = *(halfedges_to_keep.begin()); + halfedge_descriptor current_border_hd = first_border_hd; + do + { + halfedge_descriptor prev_border_hd = current_border_hd; + current_border_hd = next(current_border_hd, tmesh); + + while(marked_faces.count(face(opposite(current_border_hd, tmesh), tmesh))) + current_border_hd=next(opposite(current_border_hd, tmesh), tmesh); + + next_prev_halfedge_pairs.push_back(std::make_pair(prev_border_hd, current_border_hd)); + } + while(current_border_hd!=first_border_hd); + + // collect vertices and edges to remove and do remove faces + std::set edges_to_remove; + std::set vertices_to_remove; + for(face_descriptor fd : marked_faces) + { + halfedge_descriptor hd=halfedge(fd, tmesh); + for(int i=0; i<3; ++i) + { + if(!halfedges_to_keep.count(hd)) + edges_to_remove.insert(edge(hd, tmesh)); + + if(!vertices_to_keep.count(target(hd, tmesh))) + vertices_to_remove.insert(target(hd, tmesh)); + + hd = next(hd, tmesh); + } + + remove_face(fd, tmesh); + face_set.erase(fd); + } + + // remove vertices + for(vertex_descriptor vd : vertices_to_remove) + remove_vertex(vd, tmesh); + + // remove edges + for(edge_descriptor ed : edges_to_remove) + { + degenerate_edges_to_remove.erase(ed); + remove_edge(ed, tmesh); + } + + // add a new face, set all border edges pointing to it + // and update halfedge vertex of patch boundary vertices + face_descriptor new_face = add_face(tmesh); + typedef std::pair Pair_type; + for(const Pair_type& p : next_prev_halfedge_pairs) + { + set_face(p.first, new_face, tmesh); + set_next(p.first, p.second, tmesh); + set_halfedge(target(p.first, tmesh), p.first, tmesh); + } + + set_halfedge(new_face, first_border_hd, tmesh); + + // triangulate the new face and update the coordinate of the central vertex + halfedge_descriptor new_hd=Euler::add_center_vertex(first_border_hd, tmesh); + put(vpmap, target(new_hd, tmesh), pt); + + for(halfedge_descriptor hd : halfedges_around_target(new_hd, tmesh)) + { + if(is_degenerate_edge(edge(hd, tmesh), tmesh, np)) + degenerate_edges_to_remove.insert(edge(hd, tmesh)); + + if(face(hd, tmesh) != GT::null_face() && is_degenerate_triangle_face(face(hd, tmesh), tmesh)) + face_set.insert(face(hd, tmesh)); + } + + CGAL_assertion(is_valid_polygon_mesh(tmesh)); + } + } + } + + return all_removed; +} + +template +bool remove_degenerate_edges(const EdgeRange& edge_range, + TriangleMesh& tmesh, + const CGAL_PMP_NP_CLASS& np) +{ + std::set::face_descriptor> face_set; + return remove_degenerate_edges(edge_range, tmesh, face_set, np); +} + +template +bool remove_degenerate_edges(TriangleMesh& tmesh, + const CGAL_PMP_NP_CLASS& np) +{ + std::set::face_descriptor> face_set; + return remove_degenerate_edges(edges(tmesh), tmesh, face_set, np); +} + +template +bool remove_degenerate_edges(const EdgeRange& edge_range, + TriangleMesh& tmesh) +{ + std::set::face_descriptor> face_set; + return remove_degenerate_edges(edge_range, tmesh, face_set, parameters::all_default()); +} + +template +bool remove_degenerate_edges(TriangleMesh& tmesh) +{ + std::set::face_descriptor> face_set; + return remove_degenerate_edges(edges(tmesh), tmesh, face_set, parameters::all_default()); +} + +// \ingroup PMP_repairing_grp +// removes the degenerate faces from a triangulated surface mesh. +// A face is considered degenerate if two of its vertices share the same location, +// or more generally if all its vertices are collinear. +// +// @pre `CGAL::is_triangle_mesh(tmesh)` +// +// @tparam TriangleMesh a model of `FaceListGraph` and `MutableFaceGraph` +// @tparam NamedParameters a sequence of \ref pmp_namedparameters "Named Parameters" +// +// @param tmesh the triangulated surface mesh to be repaired +// @param np optional \ref pmp_namedparameters "Named Parameters" described below +// +// \cgalNamedParamsBegin +// \cgalParamBegin{vertex_point_map} the property map with the points associated to the vertices of `pmesh`. +// The type of this map is model of `ReadWritePropertyMap`. +// If this parameter is omitted, an internal property map for +// `CGAL::vertex_point_t` must be available in `TriangleMesh` +// \cgalParamEnd +// \cgalParamBegin{geom_traits} a geometric traits class instance. +// The traits class must provide the nested type `Point_3`, +// and the nested functors: +// - `Compare_distance_3` to compute the distance between 2 points +// - `Collinear_3` to check whether 3 points are collinear +// - `Less_xyz_3` to compare lexicographically two points +// - `Equal_3` to check whether 2 points are identical. +// For each functor Foo, a function `Foo foo_object()` must be provided. +// \cgalParamEnd +// \cgalNamedParamsEnd +// +// \todo the function might not be able to remove all degenerate faces. +// We should probably do something with the return type. +// +// \return `true` if all degenerate faces were successfully removed, and `false` otherwise. +template +bool remove_degenerate_faces(const FaceRange& face_range, + TriangleMesh& tmesh, + const NamedParameters& np) +{ + CGAL_assertion(CGAL::is_triangle_mesh(tmesh)); + + using boost::get_param; + using boost::choose_param; + + typedef TriangleMesh TM; + typedef typename boost::graph_traits GT; + typedef typename GT::vertex_descriptor vertex_descriptor; + typedef typename GT::halfedge_descriptor halfedge_descriptor; + typedef typename GT::edge_descriptor edge_descriptor; + typedef typename GT::face_descriptor face_descriptor; + + typedef typename GetVertexPointMap::type VertexPointMap; + VertexPointMap vpmap = choose_param(get_param(np, internal_np::vertex_point), + get_property_map(vertex_point, tmesh)); + typedef typename GetGeomTraits::type Traits; + Traits traits = choose_param(get_param(np, internal_np::geom_traits), Traits()); + + typedef typename boost::property_traits::value_type Point_3; + typedef typename boost::property_traits::reference Point_ref; + + std::set degenerate_face_set; + degenerate_faces(face_range, tmesh, std::inserter(degenerate_face_set, degenerate_face_set.begin()), np); + + const std::size_t faces_size = faces(tmesh).size(); + + if(degenerate_face_set.empty()) + return true; + + if(degenerate_face_set.size() == faces_size) + { + clear(tmesh); + return true; + } + + // Sanitize the face range by adding adjacent degenerate faces + const std::size_t range_size = face_range.size(); + bool is_range_full_mesh = (range_size == faces_size); + if(!is_range_full_mesh) + { + std::list faces_to_visit(degenerate_face_set.begin(), degenerate_face_set.end()); + + while(!faces_to_visit.empty()) + { + face_descriptor fd = faces_to_visit.front(); + faces_to_visit.pop_front(); + + for(halfedge_descriptor hd : halfedges_around_face(halfedge(fd, tmesh), tmesh)) + { + for(halfedge_descriptor inc_hd : halfedges_around_target(hd, tmesh)) + { + face_descriptor adj_fd = face(inc_hd, tmesh); + if(adj_fd == GT::null_face() || adj_fd == fd) + continue; + + if(is_degenerate_triangle_face(adj_fd, tmesh)) + { + if(degenerate_face_set.insert(adj_fd).second) + { + // successful insertion means we did not know about this face before + faces_to_visit.push_back(adj_fd); + } + } + } + } + } + } + + // Note that there can't be any null edge incident to the degenerate faces range, + // otherwise we would have a null face incident to the face range, and that is not possible + // after the sanitization above + std::set edge_range; + for(face_descriptor fd : degenerate_face_set) + for(halfedge_descriptor hd : halfedges_around_face(halfedge(fd, tmesh), tmesh)) + edge_range.insert(edge(hd, tmesh)); + + // First remove edges of length 0 + bool all_removed = remove_degenerate_edges(edge_range, tmesh, degenerate_face_set, np); + + CGAL_assertion_code(for(face_descriptor fd : degenerate_face_set) {) + CGAL_assertion(is_degenerate_triangle_face(fd, tmesh)); + CGAL_assertion_code(}) + +#ifdef CGAL_PMP_REMOVE_DEGENERATE_FACES_DEBUG + { + std::cout <<"Done with null edges.\n"; + std::ofstream output("/tmp/no_null_edges.off"); + output << std::setprecision(17) << tmesh << "\n"; + output.close(); + } +#endif + + // Then, remove triangles made of 3 collinear points + + // start by filtering out border faces + // TODO: shall we avoid doing that in case a non-manifold vertex on the boundary or if a whole component disappear? + std::set border_deg_faces; + for(face_descriptor f : degenerate_face_set) + { + halfedge_descriptor h = halfedge(f, tmesh); + for(int i=0; i<3; ++i) + { + if(is_border(opposite(h, tmesh), tmesh)) + { + border_deg_faces.insert(f); + break; + } + + h = next(h, tmesh); + } + } + + while(!border_deg_faces.empty()) + { + face_descriptor f_to_rm = *border_deg_faces.begin(); + border_deg_faces.erase(border_deg_faces.begin()); + + halfedge_descriptor h = halfedge(f_to_rm, tmesh); + for(int i=0; i<3; ++i) + { + face_descriptor f = face(opposite(h, tmesh), tmesh); + if(f!=GT::null_face()) + { + if(is_degenerate_triangle_face(f, tmesh, np)) + border_deg_faces.insert(f); + } + + h = next(h, tmesh); + } + + while(!is_border(opposite(h, tmesh), tmesh)) + { + h = next(h, tmesh); + } + + degenerate_face_set.erase(f_to_rm); + Euler::remove_face(h, tmesh); + } + + // Ignore faces with null edges + if(!all_removed) + { + std::map are_degenerate_edges; + + for(face_descriptor fd : degenerate_face_set) + { + for(halfedge_descriptor hd : halfedges_around_face(halfedge(fd, tmesh), tmesh)) + { + edge_descriptor ed = edge(hd, tmesh); + std::pair::iterator, bool> is_insert_successful = + are_degenerate_edges.insert(std::make_pair(ed, false)); + + bool is_degenerate = false; + if(is_insert_successful.second) + { + // did not previously exist in the map, so actually have to check if it is degenerate + if(traits.equal_3_object()(get(vpmap, target(ed, tmesh)), get(vpmap, source(ed, tmesh)))) + is_degenerate = true; + } + + is_insert_successful.first->second = is_degenerate; + + if(is_degenerate) + { + halfedge_descriptor h = halfedge(ed, tmesh); + if(!is_border(h, tmesh)) + degenerate_face_set.erase(face(h, tmesh)); + + h = opposite(h, tmesh); + if(!is_border(h, tmesh)) + degenerate_face_set.erase(face(h, tmesh)); + } + } + } + } + + // first remove degree 3 vertices that are part of a cap + // (only the vertex in the middle of the opposite edge) + // This removal does not change the shape of the mesh. + while(!degenerate_face_set.empty()) + { + std::set vertices_to_remove; + for(face_descriptor fd : degenerate_face_set) + { + for(halfedge_descriptor hd : halfedges_around_face(halfedge(fd, tmesh), tmesh)) + { + vertex_descriptor vd = target(hd, tmesh); + if(degree(vd, tmesh) == 3 && is_border(vd, tmesh)==GT::null_halfedge()) + { + vertices_to_remove.insert(vd); + break; + } + } + } + + for(vertex_descriptor vd : vertices_to_remove) + { + for(halfedge_descriptor hd2 : halfedges_around_target(vd, tmesh)) + degenerate_face_set.erase(face(hd2, tmesh)); + + // remove the central vertex and check if the new face is degenerated + halfedge_descriptor hd = CGAL::Euler::remove_center_vertex(halfedge(vd, tmesh), tmesh); + if(is_degenerate_triangle_face(face(hd, tmesh), tmesh, np)) + { + degenerate_face_set.insert(face(hd, tmesh)); + } + } + + if(vertices_to_remove.empty()) + break; + } + + while(!degenerate_face_set.empty()) + { +#ifdef CGAL_PMP_REMOVE_DEGENERATE_FACES_DEBUG + std::cout << "Loop on removing deg faces\n"; + + // ensure the mesh is not broken + { + std::ofstream out("/tmp/out.off"); + out << tmesh; + out.close(); + + std::vector points; + std::vector > triangles; + std::ifstream in("/tmp/out.off"); + CGAL::read_OFF(in, points, triangles); + if(!CGAL::Polygon_mesh_processing::is_polygon_soup_a_polygon_mesh(triangles)) + { + std::cerr << "Warning: got a polygon soup (may simply be a non-manifold vertex)!\n"; + } + } +#endif + + face_descriptor fd = *degenerate_face_set.begin(); + + // look whether an incident triangle is also degenerate + bool detect_cc_of_degenerate_triangles = false; + for(halfedge_descriptor hd : halfedges_around_face(halfedge(fd, tmesh), tmesh)) + { + face_descriptor adjacent_face = face(opposite(hd, tmesh), tmesh); + if(adjacent_face!=GT::null_face() && degenerate_face_set.count(adjacent_face)) + { + detect_cc_of_degenerate_triangles = true; + break; + } + } + + if(!detect_cc_of_degenerate_triangles) + { +#ifdef CGAL_PMP_REMOVE_DEGENERATE_FACES_DEBUG + std::cout << " no degenerate neighbors, using a flip.\n"; +#endif + degenerate_face_set.erase(degenerate_face_set.begin()); + + // flip the longest edge of the triangle + Point_ref p1 = get(vpmap, target(halfedge(fd, tmesh), tmesh)); + Point_ref p2 = get(vpmap, target(next(halfedge(fd, tmesh), tmesh), tmesh)); + Point_ref p3 = get(vpmap, source(halfedge(fd, tmesh), tmesh)); + + CGAL_assertion(p1!=p2 && p1!=p3 && p2!=p3); + + typename Traits::Compare_distance_3 compare_distance = traits.compare_distance_3_object(); + + halfedge_descriptor edge_to_flip; + if(compare_distance(p1,p2, p1,p3) != CGAL::SMALLER) // p1p2 > p1p3 + { + if(compare_distance(p1,p2, p2,p3) != CGAL::SMALLER) // p1p2 > p2p3 + // flip p1p2 + edge_to_flip = next(halfedge(fd, tmesh), tmesh); + else + // flip p2p3 + edge_to_flip = prev(halfedge(fd, tmesh), tmesh); + } + else + { + if(compare_distance(p1,p3, p2,p3) != CGAL::SMALLER) // p1p3>p2p3 + //flip p3p1 + edge_to_flip = halfedge(fd, tmesh); + else + //flip p2p3 + edge_to_flip = prev(halfedge(fd, tmesh), tmesh); + } + + face_descriptor opposite_face=face(opposite(edge_to_flip, tmesh), tmesh); + if(opposite_face == GT::null_face()) + { + // simply remove the face + Euler::remove_face(edge_to_flip, tmesh); + } + else + { + // condition for the flip to be valid (the edge to be created do not already exists) + if(!halfedge(target(next(edge_to_flip, tmesh), tmesh), + target(next(opposite(edge_to_flip, tmesh), tmesh), tmesh), + tmesh).second) + { + Euler::flip_edge(edge_to_flip, tmesh); + } + else + { + all_removed = false; +#ifdef CGAL_PMP_REMOVE_DEGENERATE_FACES_DEBUG + std::cout << " WARNING: flip is not possible\n"; + // \todo Let p and q be the vertices opposite to `edge_to_flip`, and let + // r be the vertex of `edge_to_flip` that is the furthest away from + // the edge `pq`. In that case I think we should remove all the triangles + // so that the triangle pqr is in the mesh. +#endif + } + } + } + else + { + // Process a connected component of degenerate faces + // get all the faces from the connected component + // and the boundary edges + std::set cc_faces; + std::vector queue; + std::vector boundary_hedges; + std::vector inside_hedges; + queue.push_back(fd); + cc_faces.insert(fd); + + while(!queue.empty()) + { + face_descriptor top=queue.back(); + queue.pop_back(); + for(halfedge_descriptor hd : halfedges_around_face(halfedge(top, tmesh), tmesh)) + { + face_descriptor adjacent_face = face(opposite(hd, tmesh), tmesh); + if(adjacent_face==GT::null_face() || degenerate_face_set.count(adjacent_face)==0) + { + boundary_hedges.push_back(hd); + } + else + { + if(cc_faces.insert(adjacent_face).second) + queue.push_back(adjacent_face); + + if(hd < opposite(hd, tmesh)) + inside_hedges.push_back(hd); + } + } + } + +#ifdef CGAL_PMP_REMOVE_DEGENERATE_FACES_DEBUG + std::cout << " Deal with a cc of " << cc_faces.size() << " degenerate faces.\n"; + /// dump cc_faces + { + int id = 0; + std::map vids; + for(face_descriptor f : cc_faces) + { + if(vids.insert(std::make_pair(target(halfedge(f, tmesh), tmesh), id)).second) ++id; + if(vids.insert(std::make_pair(target(next(halfedge(f, tmesh), tmesh), tmesh), id)).second) ++id; + if(vids.insert(std::make_pair(target(next(next(halfedge(f, tmesh), tmesh), tmesh), tmesh), id)).second) ++id; + } + + std::ofstream output("/tmp/cc_faces.off"); + output << std::setprecision(17); + output << "OFF\n" << vids.size() << " " << cc_faces.size() << " 0\n"; + std::vector points(vids.size()); + typedef std::pair Pair_type; + for(const Pair_type& p : vids) + + points[p.second] = get(vpmap, p.first); + for(const Point_3& p : points) + output << p << "\n"; + + for(face_descriptor f : cc_faces) + { + output << "3 " + << vids[target(halfedge(f, tmesh), tmesh)] << " " + << vids[target(next(halfedge(f, tmesh), tmesh), tmesh)] << " " + << vids[target(next(next(halfedge(f, tmesh), tmesh), tmesh), tmesh)] << "\n"; + } + + for(std::size_t pid=2; pid!=points.size(); ++pid) + { + CGAL_assertion(collinear(points[0], points[1], points[pid])); + } + } +#endif + + // find vertices strictly inside the cc + std::set boundary_vertices; + for(halfedge_descriptor hd : boundary_hedges) + boundary_vertices.insert(target(hd, tmesh)); + + std::set inside_vertices; + for(halfedge_descriptor hd : inside_hedges) + { + if(!boundary_vertices.count(target(hd, tmesh))) + inside_vertices.insert(target(hd, tmesh)); + if(!boundary_vertices.count(source(hd, tmesh))) + inside_vertices.insert(source(hd, tmesh)); + } + + // v-e+f = 1 for a topological disk and e = (3f+#boundary_edges)/2 + if(boundary_vertices.size()+inside_vertices.size() - + (cc_faces.size()+boundary_hedges.size())/2 != 1) + { + //cc_faces does not define a topological disk + /// \todo Find to way to handle that case +#ifdef CGAL_PMP_REMOVE_DEGENERATE_FACES_DEBUG + std::cout << " WARNING: Cannot remove the component of degenerate faces: not a topological disk.\n"; +#endif + + for(face_descriptor f : cc_faces) + degenerate_face_set.erase(f); + + continue; + } + + // preliminary step to check if the operation is possible + // sort the boundary points along the common supporting line + // we first need a reference point + typedef internal::Less_vertex_point Less_vertex; + std::pair< + typename std::set::iterator, + typename std::set::iterator > ref_vertices = + boost::minmax_element(boundary_vertices.begin(), + boundary_vertices.end(), + Less_vertex(traits, vpmap)); + + // and then we sort the vertices using this reference point + typedef std::set Sorted_point_set; + Sorted_point_set sorted_points; + for(vertex_descriptor v : boundary_vertices) + sorted_points.insert(get(vpmap, v)); + + CGAL_assertion(sorted_points.size()== + std::set(sorted_points.begin(), + sorted_points.end()).size()); + + CGAL_assertion(get(vpmap, *ref_vertices.first) == *sorted_points.begin()); + CGAL_assertion(get(vpmap, *ref_vertices.second) == *std::prev(sorted_points.end())); + + const Point_3& xtrm1 = *sorted_points.begin(); + const Point_3& xtrm2 = *std::prev(sorted_points.end()); + + // recover halfedges on the hole, bounded by the reference vertices + std::vector side_one, side_two; + + // look for the outgoing border halfedge of the first extreme point + for(halfedge_descriptor hd : boundary_hedges) + { + if(get(vpmap, source(hd, tmesh)) == xtrm1) + { + side_one.push_back(hd); + break; + } + } + + CGAL_assertion(side_one.size() == 1); + + bool non_monotone_border = false; + + while(get(vpmap, target(side_one.back(), tmesh)) != xtrm2) + { + vertex_descriptor prev_vertex = target(side_one.back(), tmesh); + for(halfedge_descriptor hd : boundary_hedges) + { + if(source(hd, tmesh) == prev_vertex) + { + if(get(vpmap, target(hd, tmesh)) < get(vpmap, prev_vertex)) + non_monotone_border = true; + + side_one.push_back(hd); + break; + } + } + + if(non_monotone_border) + break; + } + + if(non_monotone_border) + { +#ifdef CGAL_PMP_REMOVE_DEGENERATE_FACES_DEBUG + std::cout << " WARNING: Cannot remove the component of degenerate faces: border not a monotonic cycle.\n"; +#endif + + for(face_descriptor f : cc_faces) + degenerate_face_set.erase(f); + + continue; + } + + // look for the outgoing border halfedge of second extreme vertex + for(halfedge_descriptor hd : boundary_hedges) + { + if(source(hd, tmesh) == target(side_one.back(), tmesh)) + { + side_two.push_back(hd); + break; + } + } + + CGAL_assertion(side_two.size() == 1); + + while(target(side_two.back(), tmesh) != source(side_one.front(), tmesh)) + { + vertex_descriptor prev_vertex = target(side_two.back(), tmesh); + for(halfedge_descriptor hd : boundary_hedges) + { + if(source(hd, tmesh) == prev_vertex) + { + if(get(vpmap, target(hd, tmesh)) > get(vpmap, prev_vertex)) + non_monotone_border = true; + + side_two.push_back(hd); + break; + } + } + + if(non_monotone_border) + break; + } + + if(non_monotone_border) + { +#ifdef CGAL_PMP_REMOVE_DEGENERATE_FACES_DEBUG + std::cout << " WARNING: Cannot remove the component of degenerate faces: border not a monotonic cycle.\n"; +#endif + + for(face_descriptor f : cc_faces) + degenerate_face_set.erase(f); + + continue; + } + + CGAL_assertion(side_one.size()+side_two.size()==boundary_hedges.size()); + + // reverse the order of the second side so as to follow + // the same order than side one + std::reverse(side_two.begin(), side_two.end()); + for(halfedge_descriptor& h : side_two) + h = opposite(h, tmesh); + + //make sure the points of the vertices along side_one are correctly sorted + std::vector side_points; + side_points.reserve(side_one.size()+1); + side_points.push_back(get(vpmap, source(side_one.front(), tmesh))); + + for(halfedge_descriptor h : side_one) + side_points.push_back(get(vpmap, target(h, tmesh))); + + CGAL_assertion(get(vpmap,source(side_one.front(), tmesh)) == side_points.front()); + CGAL_assertion(get(vpmap,target(side_one.back(), tmesh)) == side_points.back()); + + //\todo the reordering could lead to the apparition of null edges. + std::sort(side_points.begin(), side_points.end()); + + CGAL_assertion(std::unique(side_points.begin(), side_points.end())==side_points.end()); + for(std::size_t i=0; i::iterator side_one_it = side_one.begin(); + typename std::vector::iterator side_two_it = side_two.begin(); + for(;it_pt!=it_pt_end;++it_pt) + { + // check if it_pt is the point of the target of one or two halfedges + bool target_of_side_one = (get(vpmap, target(*side_one_it, tmesh)) == *it_pt); + bool target_of_side_two = (get(vpmap, target(*side_two_it, tmesh)) == *it_pt); + + if(target_of_side_one && target_of_side_two) + { + for(halfedge_descriptor h : halfedges_around_target(*side_one_it, tmesh)) + { + if(source(h, tmesh) == target(*side_two_it, tmesh)) + { + non_collapsable = true; + break; + } + } + } + else + { + CGAL_assertion(target_of_side_one || target_of_side_two); + vertex_descriptor v1 = target_of_side_one ? target(*side_one_it, tmesh) + : target(*side_two_it, tmesh); + vertex_descriptor v2 = target_of_side_two ? target(next(opposite(*side_one_it, tmesh), tmesh), tmesh) + : target(next(*side_two_it, tmesh), tmesh); + for(halfedge_descriptor h : halfedges_around_target(v1, tmesh)) + { + if(source(h, tmesh)==v2) + { + non_collapsable=true; + break; + } + } + } + + if(non_collapsable) break; + if(target_of_side_one) ++side_one_it; + if(target_of_side_two) ++side_two_it; + } + + if(non_collapsable) + { + for(face_descriptor f : cc_faces) + degenerate_face_set.erase(f); + +#ifdef CGAL_PMP_REMOVE_DEGENERATE_FACES_DEBUG + std::cout << " WARNING: cannot remove a connected components of degenerate faces.\n"; +#endif + continue; + } + + // now proceed to the fix + // update the face and halfedge vertex pointers on the boundary + for(halfedge_descriptor h : boundary_hedges) + { + set_face(h, GT::null_face(), tmesh); + set_halfedge(target(h, tmesh), h, tmesh); + } + + // update next/prev pointers of boundary_hedges + for(halfedge_descriptor h : boundary_hedges) + { + halfedge_descriptor next_candidate = next(h, tmesh); + while(face(next_candidate, tmesh)!=GT::null_face()) + next_candidate = next(opposite(next_candidate, tmesh), tmesh); + + set_next(h, next_candidate, tmesh); + } + + // remove degenerate faces + for(face_descriptor f : cc_faces) + { + degenerate_face_set.erase(f); + remove_face(f, tmesh); + } + + // remove interior edges + for(halfedge_descriptor h : inside_hedges) + remove_edge(edge(h, tmesh), tmesh); + + // remove interior vertices + for(vertex_descriptor v : inside_vertices) + remove_vertex(v, tmesh); + +#ifdef CGAL_PMP_REMOVE_DEGENERATE_FACES_DEBUG + std::cout << " side_one.size() " << side_one.size() << "\n"; + std::cout << " side_two.size() " << side_two.size() << "\n"; +#endif + + CGAL_assertion(source(side_one.front(), tmesh) == *ref_vertices.first); + CGAL_assertion(source(side_two.front(), tmesh) == *ref_vertices.first); + CGAL_assertion(target(side_one.back(), tmesh) == *ref_vertices.second); + CGAL_assertion(target(side_two.back(), tmesh) == *ref_vertices.second); + + // now split each side to contains the same sequence of points + // first side + int hi = 0; + + for(typename Sorted_point_set::iterator it=std::next(sorted_points.begin()), + it_end=sorted_points.end(); it!=it_end; ++it) + { + CGAL_assertion(*std::prev(it) == get(vpmap, source(side_one[hi], tmesh))); + + if(*it != get(vpmap, target(side_one[hi], tmesh))) + { + // split the edge and update the point + halfedge_descriptor h1 = next(opposite(side_one[hi], tmesh), tmesh); + put(vpmap, target(Euler::split_edge(side_one[hi], tmesh), tmesh), *it); + + // split_edge updates the halfedge of the source vertex of h, + // since we reuse later the halfedge of the first refernce vertex + // we must set it as we need. + if(source(h1, tmesh) == *ref_vertices.first) + set_halfedge(*ref_vertices.first, prev(prev(side_one[hi], tmesh), tmesh), tmesh); + + // retriangulate the opposite face + if(face(h1, tmesh) != GT::null_face()) + Euler::split_face(h1, opposite(side_one[hi], tmesh), tmesh); + } + else + { + ++hi; + } + } + + // second side + hi = 0; + for(typename Sorted_point_set::iterator it=std::next(sorted_points.begin()), + it_end=sorted_points.end(); it!=it_end; ++it) + { + CGAL_assertion(*std::prev(it) == get(vpmap, source(side_two[hi], tmesh))); + if(*it != get(vpmap, target(side_two[hi], tmesh))) + { + // split the edge and update the point + halfedge_descriptor h2 = Euler::split_edge(side_two[hi], tmesh); + put(vpmap, target(h2, tmesh), *it); + + // split_edge updates the halfedge of the source vertex of h, + // since we reuse later the halfedge of the first refernce vertex + // we must set it as we need. + if(source(h2, tmesh) == *ref_vertices.first) + set_halfedge(*ref_vertices.first, opposite(h2, tmesh), tmesh); + + // retriangulate the face + if(face(h2, tmesh) != GT::null_face()) + Euler::split_face(h2, next(side_two[hi], tmesh), tmesh); + } + else + { + ++hi; + } + } + + CGAL_assertion(target(halfedge(*ref_vertices.first, tmesh), tmesh) == *ref_vertices.first); + CGAL_assertion(face(halfedge(*ref_vertices.first, tmesh), tmesh) == GT::null_face()); + +#ifdef CGAL_PMP_REMOVE_DEGENERATE_FACES_DEBUG + { + halfedge_descriptor h_side2 = halfedge(*ref_vertices.first, tmesh); + halfedge_descriptor h_side1 = next(h_side2, tmesh); + + do + { + CGAL_assertion(get(vpmap, source(h_side1, tmesh)) == get(vpmap, target(h_side2, tmesh))); + CGAL_assertion(get(vpmap, target(h_side1, tmesh)) == get(vpmap, source(h_side2, tmesh))); + + if(target(next(opposite(h_side1, tmesh), tmesh), tmesh) == + target(next(opposite(h_side2, tmesh), tmesh), tmesh)) + { + CGAL_assertion(!"Forbidden simplification"); + } + + h_side2 = prev(h_side2, tmesh); + h_side1 = next(h_side1, tmesh); + } + while(target(h_side1, tmesh) != *ref_vertices.second); + } +#endif + + // remove side1 and replace its opposite hedges by those of side2 + halfedge_descriptor h_side2 = halfedge(*ref_vertices.first, tmesh); + halfedge_descriptor h_side1 = next(h_side2, tmesh); + for(;;) + { + CGAL_assertion(get(vpmap, source(h_side1, tmesh)) == get(vpmap, target(h_side2, tmesh))); + CGAL_assertion(get(vpmap, target(h_side1, tmesh)) == get(vpmap, source(h_side2, tmesh))); + + // backup target vertex + vertex_descriptor vertex_to_remove = target(h_side1, tmesh); + if(vertex_to_remove!=*ref_vertices.second) + { + vertex_descriptor replacement_vertex = source(h_side2, tmesh); + // replace the incident vertex + for(halfedge_descriptor hd : halfedges_around_target(h_side1, tmesh)) + set_target(hd, replacement_vertex, tmesh); + } + + // prev side2 hedge for next loop + halfedge_descriptor h_side2_for_next_turn = prev(h_side2, tmesh); + + // replace the opposite of h_side1 by h_side2 + halfedge_descriptor opposite_h_side1 = opposite(h_side1, tmesh); + face_descriptor the_face = face(opposite_h_side1, tmesh); + set_face(h_side2, the_face, tmesh); + + if(the_face!=GT::null_face()) + set_halfedge(the_face, h_side2, tmesh); + + set_next(h_side2, next(opposite_h_side1, tmesh), tmesh); + set_next(prev(opposite_h_side1, tmesh), h_side2, tmesh); + + // take the next hedges + edge_descriptor edge_to_remove = edge(h_side1, tmesh); + h_side1 = next(h_side1, tmesh); + + // now remove the extra edge + remove_edge(edge_to_remove, tmesh); + + // ... and the extra vertex if it's not the second reference + if(vertex_to_remove == *ref_vertices.second) + { + // update the halfedge pointer of the last vertex (others were already from side 2) + CGAL_assertion(target(opposite(h_side2, tmesh), tmesh) == vertex_to_remove); + set_halfedge(vertex_to_remove, opposite(h_side2, tmesh), tmesh); + break; + } + else + { + remove_vertex(vertex_to_remove , tmesh); + } + + h_side2 = h_side2_for_next_turn; + } + } + } + + return all_removed; +} + +template +bool remove_degenerate_faces(const FaceRange& face_range, + TriangleMesh& tmesh) +{ + return remove_degenerate_faces(face_range, tmesh, CGAL::parameters::all_default()); +} + +template +bool remove_degenerate_faces(TriangleMesh& tmesh, + const CGAL_PMP_NP_CLASS& np) +{ + return remove_degenerate_faces(faces(tmesh), tmesh, np); +} + +template +bool remove_degenerate_faces(TriangleMesh& tmesh) +{ + return remove_degenerate_faces(tmesh, CGAL::parameters::all_default()); +} + +} // namespace Polygon_mesh_processing +} // namespace CGAL + +#endif // CGAL_POLYGON_MESH_PROCESSING_REMOVE_DEGENERACIES_H diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/remove_self_intersections.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/remove_self_intersections.h new file mode 100644 index 00000000000..ce6857c601b --- /dev/null +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/remove_self_intersections.h @@ -0,0 +1,684 @@ +// Copyright (c) 2015-2019 GeometryFactory (France). +// All rights reserved. +// +// This file is part of CGAL (www.cgal.org). +// You can redistribute it and/or modify it under the terms of the GNU +// General Public License as published by the Free Software Foundation, +// either version 3 of the License, or (at your option) any later version. +// +// Licensees holding a valid commercial license may use this file in +// accordance with the commercial license agreement provided with the software. +// +// This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE +// WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. +// +// $URL$ +// $Id$ +// SPDX-License-Identifier: GPL-3.0+ +// +// Author(s) : Sebastien Loriot, +// Mael Rouxel-Labbé + +#ifndef CGAL_POLYGON_MESH_PROCESSING_REMOVE_SELF_INTERSECTIONS_H +#define CGAL_POLYGON_MESH_PROCESSING_REMOVE_SELF_INTERSECTIONS_H + +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace CGAL { +namespace Polygon_mesh_processing { + +/// \cond SKIP_IN_MANUAL +template +std::pair +remove_self_intersections_one_step(TriangleMesh& tmesh, + std::set& faces_to_remove, + VertexPointMap& vpmap, + int step, + bool preserve_genus, + bool verbose) +{ + std::set faces_to_remove_copy = faces_to_remove; + + if(verbose) + { + std::cout << "DEBUG: running remove_self_intersections_one_step, step " << step + << " with " << faces_to_remove.size() << " intersecting faces\n"; + } + + CGAL_assertion(tmesh.is_valid()); + + typedef boost::graph_traits graph_traits; + typedef typename graph_traits::vertex_descriptor vertex_descriptor; + typedef typename graph_traits::halfedge_descriptor halfedge_descriptor; + typedef typename graph_traits::edge_descriptor edge_descriptor; + + bool something_was_done = false; // indicates if a region was successfully remeshed + bool all_fixed = true; // indicates if all removal went well + // indicates if a removal was not possible because the region handle has + // some boundary cycle of halfedges + bool topology_issue = false; + + if(verbose) + { + std::cout << " DEBUG: is_valid in one_step(tmesh)? "; + std::cout.flush(); + std::cout << is_valid_polygon_mesh(tmesh) << "\n"; + } + + if(!faces_to_remove.empty()) + { + while(!faces_to_remove.empty()) + { + // Process a connected component of faces to remove. + // collect all the faces from the connected component + std::set cc_faces; + std::vector queue(1, *faces_to_remove.begin()); // temporary queue + cc_faces.insert(queue.back()); + while(!queue.empty()) + { + face_descriptor top = queue.back(); + queue.pop_back(); + halfedge_descriptor h = halfedge(top, tmesh); + for(int i=0; i<3; ++i) + { + face_descriptor adjacent_face = face(opposite(h, tmesh), tmesh); + if(adjacent_face!=boost::graph_traits::null_face()) + { + if(faces_to_remove.count(adjacent_face) != 0 && cc_faces.insert(adjacent_face).second) + queue.push_back(adjacent_face); + } + + h = next(h, tmesh); + } + } + + // expand the region to be filled + if(step > 0) + { + expand_face_selection(cc_faces, tmesh, step, + make_boolean_property_map(cc_faces), + Emptyset_iterator()); + } + + // try to compactify the selection region by also selecting all the faces included + // in the bounding box of the initial selection + std::vector stack_for_expension; + Bbox_3 bb; + for(face_descriptor fd : cc_faces) + { + for(halfedge_descriptor h : halfedges_around_face(halfedge(fd, tmesh), tmesh)) + { + bb += get(vpmap, target(h, tmesh)).bbox(); + face_descriptor nf = face(opposite(h, tmesh), tmesh); + if(nf != boost::graph_traits::null_face() && cc_faces.count(nf) == 0) + { + stack_for_expension.push_back(opposite(h, tmesh)); + } + } + } + + while(!stack_for_expension.empty()) + { + halfedge_descriptor h = stack_for_expension.back(); + stack_for_expension.pop_back(); + if(cc_faces.count(face(h, tmesh)) == 1) + continue; + + if(do_overlap(bb, get(vpmap, target(next(h, tmesh), tmesh)).bbox())) + { + cc_faces.insert(face(h, tmesh)); + halfedge_descriptor candidate = opposite(next(h, tmesh), tmesh); + if(face(candidate, tmesh) != boost::graph_traits::null_face()) + stack_for_expension.push_back(candidate); + + candidate = opposite(prev(h, tmesh), tmesh); + if(face(candidate, tmesh) != boost::graph_traits::null_face()) + stack_for_expension.push_back(candidate); + } + } + + // remove faces from the set to process + for(face_descriptor f : cc_faces) + faces_to_remove.erase(f); + + if(cc_faces.size() == 1) + continue; // it is a triangle nothing better can be done + + //Check for non-manifold vertices in the selection and remove them by selecting all incident faces: + // extract the set of halfedges that is on the boundary of the holes to be + // made. In addition, we make sure no hole to be created contains a vertex + // visited more than once along a hole border (pinched surface) + // We save the size of boundary_hedges to make sur halfedges added + // from non_filled_hole are not removed. + bool non_manifold_vertex_remaining_in_selection = false; + do + { + bool non_manifold_vertex_removed = false; //here non-manifold is for the 1D polyline + std::vector boundary_hedges; + for(face_descriptor fh : cc_faces) + { + halfedge_descriptor h = halfedge(fh, tmesh); + for(int i=0; i<3; ++i) + { + if(is_border(opposite(h, tmesh), tmesh) || cc_faces.count(face(opposite(h, tmesh), tmesh)) == 0) + boundary_hedges.push_back(h); + + h = next(h, tmesh); + } + } + + // detect vertices visited more than once along + // a hole border. We then remove all faces incident + // to such a vertex to force the removal of the vertex. + // Actually even if two holes are sharing a vertex, this + // vertex will be removed. It is not needed but since + // we do not yet have one halfedge per hole it is simpler + // and does not harm + std::set border_vertices; + for(halfedge_descriptor h : boundary_hedges) + { + if(!border_vertices.insert(target(h, tmesh)).second) + { + bool any_face_added = false; + for(halfedge_descriptor hh : halfedges_around_target(h, tmesh)) + { + if(!is_border(hh, tmesh)) + { + // add the face to the current selection + any_face_added |= cc_faces.insert(face(hh, tmesh)).second; + faces_to_remove.erase(face(hh, tmesh)); + } + } + + if(any_face_added) + non_manifold_vertex_removed = true; + else + non_manifold_vertex_remaining_in_selection = true; + } + } + + if(!non_manifold_vertex_removed) + break; + } + while(true); + + if(preserve_genus && non_manifold_vertex_remaining_in_selection) + { + topology_issue = true; + if(verbose) + std::cout << " DEBUG: CC not handled due to the presence at least one non-manifold vertex\n"; + + continue; // cannot replace a patch containing a nm vertex by a disk + } + + // before running this function if preserve_genus=false, we duplicated + // all of them + CGAL_assertion(!non_manifold_vertex_remaining_in_selection); + + // Collect halfedges on the boundary of the region to be selected + // (pointing inside the domain to be remeshed) + std::vector cc_border_hedges; + for(face_descriptor fd : cc_faces) + { + halfedge_descriptor h = halfedge(fd, tmesh); + for(int i=0; i<3;++i) + { + if(is_border(opposite(h, tmesh), tmesh) || cc_faces.count(face(opposite(h, tmesh), tmesh))== 0) + cc_border_hedges.push_back(h); + + h = next(h, tmesh); + } + } + + if(!is_selection_a_topological_disk(cc_faces, tmesh)) + { + // check if the selection contains cycles of border halfedges + bool only_border_edges = true; + std::set mesh_border_hedge; + + for(halfedge_descriptor h : cc_border_hedges) + { + if(!is_border(opposite(h, tmesh), tmesh)) + only_border_edges = false; + else + mesh_border_hedge.insert(opposite(h, tmesh)); + } + + int nb_cycles=0; + while(!mesh_border_hedge.empty()) + { + // we must count the number of cycle of boundary edges + halfedge_descriptor h_b = *mesh_border_hedge.begin(), h=h_b; + mesh_border_hedge.erase(mesh_border_hedge.begin()); + do + { + h = next(h, tmesh); + if(h == h_b) + { + // found a cycle + ++nb_cycles; + break; + } + else + { + typename std::set::iterator it = mesh_border_hedge.find(h); + if(it == mesh_border_hedge.end()) + break; // not a cycle + + mesh_border_hedge.erase(it); + } + } + while(true); + } + + if(nb_cycles > (only_border_edges ? 1 : 0)) + { + if(verbose) + std::cout << " DEBUG: CC not handled due to the presence of " + << nb_cycles << " of boundary edges\n"; + + topology_issue = true; + continue; + } + else + { + if(preserve_genus) + { + if(verbose) + std::cout << " DEBUG: CC not handled because it is not a topological disk (preserve_genus=true)\n"; + + all_fixed = false; + continue; + } + + // count the number of cycles of halfedges of the boundary + std::map bhs; + for(halfedge_descriptor h : cc_border_hedges) + bhs[source(h, tmesh)] = target(h, tmesh); + + int nbc=0; + while(!bhs.empty()) + { + ++nbc; + std::pair top=*bhs.begin(); + bhs.erase(bhs.begin()); + + do + { + typename std::map::iterator + it_find = bhs.find(top.second); + if(it_find == bhs.end()) break; + top = *it_find; + bhs.erase(it_find); + } + while(true); + } + + if(nbc!=1) + { + if(verbose) + std::cout << " DEBUG: CC not handled because it is not a topological disk(" + << nbc << " boundary cycles)\n"; + + all_fixed = false; + continue; + } + else + { + if(verbose) + std::cout << " DEBUG: CC that is not a topological disk but has only one boundary cycle(preserve_genus=false)\n"; + } + } + } + + // sort halfedges so that they describe the sequence + // of halfedges of the hole to be made + CGAL_assertion(cc_border_hedges.size() > 2); + for(std::size_t i=0; i < cc_border_hedges.size()-2; ++i) + { + vertex_descriptor tgt = target(cc_border_hedges[i], tmesh); + for(std::size_t j=i+1; j cc_interior_vertices; + std::set cc_interior_edges; + + // first collect all vertices and edges incident to the faces to remove + for(face_descriptor fh : cc_faces) + { + for(halfedge_descriptor h : halfedges_around_face(halfedge(fh, tmesh), tmesh)) + { + if(halfedge(target(h, tmesh), tmesh) == h) // limit the number of insertions + cc_interior_vertices.insert(target(h, tmesh)); + + cc_interior_edges.insert(edge(h, tmesh)); + } + } + + // and then remove those on the boundary + for(halfedge_descriptor h : cc_border_hedges) + { + cc_interior_vertices.erase(target(h, tmesh)); + cc_interior_edges.erase(edge(h, tmesh)); + } + + if(verbose) + { + std::cout << " DEBUG: is_valid(tmesh) in one_step, before mesh changes? "; + std::cout << is_valid_polygon_mesh(tmesh) << std::endl; + } + + //try hole_filling. + typedef CGAL::Triple Face_indices; + typedef typename boost::property_traits::value_type Point; + + std::vector hole_points, third_points; + hole_points.reserve(cc_border_hedges.size()); + third_points.reserve(cc_border_hedges.size()); + std::vector border_vertices; + + for(halfedge_descriptor h : cc_border_hedges) + { + vertex_descriptor v = source(h, tmesh); + hole_points.push_back(get(vpmap, v)); + border_vertices.push_back(v); + third_points.push_back(get(vpmap, target(next(opposite(h, tmesh), tmesh), tmesh))); // TODO fix me for mesh border edges + } + + CGAL_assertion(hole_points.size() >= 3); + + // try to triangulate the hole using default parameters + //(using Delaunay search space if CGAL_HOLE_FILLING_DO_NOT_USE_DT3 is not defined) + std::vector patch; + if(hole_points.size()>3) + triangulate_hole_polyline(hole_points, third_points, std::back_inserter(patch)); + else + patch.push_back(Face_indices(0,1,2)); // trivial hole filling + + if(patch.empty()) + { +#ifndef CGAL_HOLE_FILLING_DO_NOT_USE_DT3 + if(verbose) + std::cout << " DEBUG: Failed to fill a hole using Delaunay search space.\n"; + + triangulate_hole_polyline(hole_points, third_points, std::back_inserter(patch), + parameters::use_delaunay_triangulation(false)); +#endif + if(patch.empty()) + { + if(verbose) + std::cout << " DEBUG: Failed to fill a hole using the whole search space.\n"; + all_fixed = false; + continue; + } + } + + // make sure that the hole filling is valid, we check that no + // edge already in the mesh is present in patch. + bool non_manifold_edge_found = false; + for(const Face_indices& triangle : patch) + { + std::array edges = make_array(triangle.first, triangle.second, + triangle.second, triangle.third, + triangle.third, triangle.first); + for(int k=0; k<3; ++k) + { + int vi=edges[2*k], vj=edges[2*k+1]; + // ignore boundary edges + if(vi+1==vj || (vj==0 && static_cast(vi)==border_vertices.size()-1)) + continue; + + halfedge_descriptor h = halfedge(border_vertices[vi], border_vertices[vj], tmesh).first; + if(h != boost::graph_traits::null_halfedge() && + cc_interior_edges.count(edge(h, tmesh)) == 0) + { + non_manifold_edge_found=true; + break; + } + } + + if(non_manifold_edge_found) + break; + } + + if(non_manifold_edge_found) + { + if(verbose) + std::cout << " DEBUG: Triangulation produced is non-manifold when plugged into the mesh.\n"; + + all_fixed = false; + continue; + } + + // plug the new triangles in the mesh, reusing previous edges and faces + std::vector edge_stack(cc_interior_edges.begin(), cc_interior_edges.end()); + std::vector face_stack(cc_faces.begin(), cc_faces.end()); + + std::map, halfedge_descriptor> halfedge_map; + int i = 0; + + // register border halfedges + for(halfedge_descriptor h : cc_border_hedges) + { + int j = static_cast(std::size_t(i+1)%cc_border_hedges.size()); + halfedge_map.insert(std::make_pair(std::make_pair(i, j), h)); + set_halfedge(target(h, tmesh), h, tmesh); // update vertex halfedge pointer + CGAL_assertion(border_vertices[i] == source(h, tmesh) && border_vertices[j] == target(h, tmesh)); + ++i; + } + + std::vector hedges; + hedges.reserve(4); + face_descriptor f = boost::graph_traits::null_face(); + for(const Face_indices& triangle : patch) + { + // get the new face + if(face_stack.empty()) + { + f = add_face(tmesh); + } + else + { + f = face_stack.back(); + face_stack.pop_back(); + } + + std::array indices = make_array(triangle.first, triangle.second, triangle.third, triangle.first); + for(int i=0; i<3; ++i) + { + // get the corresponding halfedge (either a new one or an already created) + typename std::map, halfedge_descriptor >::iterator insert_res = + halfedge_map.insert(std::make_pair(std::make_pair(indices[i], indices[i+1]), + boost::graph_traits::null_halfedge())).first; + + if(insert_res->second == boost::graph_traits::null_halfedge()) + { + if(edge_stack.empty()) + { + insert_res->second = halfedge(add_edge(tmesh), tmesh); + } + else + { + insert_res->second = halfedge(edge_stack.back(), tmesh); + edge_stack.pop_back(); + } + + halfedge_map[std::make_pair(indices[i+1], indices[i])] = opposite(insert_res->second, tmesh); + } + + hedges.push_back(insert_res->second); + } + + hedges.push_back(hedges.front()); + + // update halfedge connections + face pointers + for(int i=0; i<3;++i) + { + set_next(hedges[i], hedges[i+1], tmesh); + set_face(hedges[i], f, tmesh); + set_target(hedges[i], border_vertices[indices[i+1]], tmesh); + } + + set_halfedge(f, hedges[0], tmesh); + hedges.clear(); + } + + // now remove remaining edges, + for(edge_descriptor e : edge_stack) + remove_edge(e, tmesh); + + // vertices, + for(vertex_descriptor vh : cc_interior_vertices) + remove_vertex(vh, tmesh); + + // and remaning faces + for(face_descriptor f : face_stack) + remove_face(f, tmesh); + + if(verbose) + std::cout << " DEBUG: " << cc_faces.size() << " triangles removed, " << patch.size() << " created\n"; + + CGAL_assertion(is_valid_polygon_mesh(tmesh)); + + something_was_done = true; + } + } + + if(!something_was_done) + { + faces_to_remove.swap(faces_to_remove_copy); + if(verbose) + std::cout << " DEBUG: Nothing was changed during this step, self-intersections won't be recomputed." << std::endl; + } + + return std::make_pair(all_fixed, topology_issue); +} + +template +bool remove_self_intersections(const FaceRange& face_range, + TriangleMesh& tmesh, + const NamedParameters& np) +{ + typedef boost::graph_traits graph_traits; + typedef typename graph_traits::face_descriptor face_descriptor; + + // named parameter extraction + typedef typename GetVertexPointMap::type VertexPointMap; + VertexPointMap vpm = boost::choose_param(boost::get_param(np, internal_np::vertex_point), + get_property_map(vertex_point, tmesh)); + + const int max_steps = boost::choose_param(boost::get_param(np, internal_np::number_of_iterations), 7); + bool verbose = boost::choose_param(boost::get_param(np, internal_np::verbosity_level), 0) > 0; + bool preserve_genus = boost::choose_param(boost::get_param(np, internal_np::preserve_genus), true); + + if(verbose) + std::cout << "DEBUG: Starting remove_self_intersections, is_valid(tmesh)? " << is_valid_polygon_mesh(tmesh) << "\n"; + + CGAL_precondition_code(std::set degenerate_face_set;) + CGAL_precondition_code(degenerate_faces(face_range, tmesh, + std::inserter(degenerate_face_set, degenerate_face_set.begin()), np);) + CGAL_precondition(degenerate_face_set.empty()); + + if(!preserve_genus) + duplicate_non_manifold_vertices(tmesh, np); + + // Look for self-intersections in the polyhedron and remove them + int step = -1; + bool all_fixed = true; // indicates if the filling of all created holes went fine + bool topology_issue = false; // indicates if some boundary cycles of edges are blocking the fixing + std::set faces_to_remove; + while(++step Face_pair; + std::vector self_inter; + // TODO : possible optimization to reduce the range to check with the bbox + // of the previous patches or something. + self_intersections(face_range, tmesh, std::back_inserter(self_inter)); + + for(const Face_pair& fp : self_inter) + { + faces_to_remove.insert(fp.first); + faces_to_remove.insert(fp.second); + } + } + + if(faces_to_remove.empty() && all_fixed) + { + if(verbose) + std::cout << "DEBUG: There is no more face to remove." << std::endl; + break; + } + + std::tie(all_fixed, topology_issue) = + remove_self_intersections_one_step(tmesh, faces_to_remove, vpm, step, preserve_genus, verbose); + if(all_fixed && topology_issue) + { + if(verbose) + std::cout<< "DEBUG: Process stopped because of boundary cycles" + " of boundary edges involved in self-intersections.\n"; + return false; + } + } + + return step < max_steps; +} + +template +bool remove_self_intersections(const FaceRange& face_range, + TriangleMesh& tmesh) +{ + return remove_self_intersections(face_range, tmesh, parameters::all_default()); +} + +template +bool remove_self_intersections(TriangleMesh& tmesh, + const CGAL_PMP_NP_CLASS& np) +{ + return remove_self_intersections(faces(tmesh), tmesh, np); +} + +template +bool remove_self_intersections(TriangleMesh& tmesh) +{ + return remove_self_intersections(faces(tmesh), tmesh, parameters::all_default()); +} + +/// \endcond + +} // namespace Polygon_mesh_processing +} // namespace CGAL + +#endif // CGAL_POLYGON_MESH_PROCESSING_REMOVE_SELF_INTERSECTIONS_H diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/repair.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/repair.h index b6500b3291d..2ae7bcb789a 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/repair.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/repair.h @@ -25,2311 +25,14 @@ #include -#include -#include -#include -#include -#include -#include +#include +#include +#include -// headers for self-intersection removal -#include -#include -#include - -#include -#include -#include - -#include -#include - -#include - -#ifdef CGAL_PMP_REMOVE_DEGENERATE_FACES_DEBUG -#include -#include -#endif - -#include -#include -#include - -#include -#include -#include -#include -#include #include -namespace CGAL{ +namespace CGAL { namespace Polygon_mesh_processing { -namespace debug{ - - template - std::ostream& dump_edge_neighborhood( - typename boost::graph_traits::edge_descriptor ed, - TriangleMesh& tmesh, - const VertexPointMap& vpmap, - std::ostream& out) - { - typedef boost::graph_traits GT; - typedef typename GT::halfedge_descriptor halfedge_descriptor; - typedef typename GT::vertex_descriptor vertex_descriptor; - typedef typename GT::face_descriptor face_descriptor; - - halfedge_descriptor h = halfedge(ed, tmesh); - - std::map vertices; - std::set faces; - int vindex=0; - for(halfedge_descriptor hd : halfedges_around_target(h, tmesh)) - { - if ( vertices.insert(std::make_pair(source(hd, tmesh), vindex)).second ) - ++vindex; - if (!is_border(hd, tmesh)) - faces.insert( face(hd, tmesh) ); - } - - h=opposite(h, tmesh); - for(halfedge_descriptor hd : halfedges_around_target(h, tmesh)) - { - if ( vertices.insert(std::make_pair(source(hd, tmesh), vindex)).second ) - ++vindex; - if (!is_border(hd, tmesh)) - faces.insert( face(hd, tmesh) ); - } - - std::vector ordered_vertices(vertices.size()); - typedef std::pair Pair_type; - for(const Pair_type& p : vertices) - ordered_vertices[p.second]=p.first; - - out << "OFF\n" << ordered_vertices.size() << " " << faces.size() << " 0\n"; - for(vertex_descriptor vd : ordered_vertices) - out << get(vpmap, vd) << "\n"; - for(face_descriptor fd : faces) - { - out << "3"; - h=halfedge(fd,tmesh); - for(halfedge_descriptor hd : halfedges_around_face(h, tmesh)) - out << " " << vertices[target(hd, tmesh)]; - out << "\n"; - } - return out; - } - - template - void dump_cc_faces(const FaceRange& cc_faces, const TriangleMesh& tm, std::ostream& output) - { - typedef typename boost::property_map::const_type Vpm; - typedef typename boost::property_traits::value_type Point_3; - typedef typename boost::graph_traits::vertex_descriptor vertex_descriptor; - typedef typename boost::graph_traits::face_descriptor face_descriptor; - - Vpm vpm = get(boost::vertex_point, tm); - - int id=0; - std::map vids; - for(face_descriptor f : cc_faces) - { - if ( vids.insert( std::make_pair( target(halfedge(f, tm), tm), id) ).second ) ++id; - if ( vids.insert( std::make_pair( target(next(halfedge(f, tm), tm), tm), id) ).second ) ++id; - if ( vids.insert( std::make_pair( target(next(next(halfedge(f, tm), tm), tm), tm), id) ).second ) ++id; - } - output << std::setprecision(17); - output << "OFF\n" << vids.size() << " " << cc_faces.size() << " 0\n"; - std::vector points(vids.size()); - typedef std::pair Pair_type; - for(Pair_type p : vids) - points[p.second]=get(vpm, p.first); - for(Point_3 p : points) - output << p << "\n"; - for(face_descriptor f : cc_faces) - { - output << "3 " - << vids[ target(halfedge(f, tm), tm) ] << " " - << vids[ target(next(halfedge(f, tm), tm), tm) ] << " " - << vids[ target(next(next(halfedge(f, tm), tm), tm), tm) ] << "\n"; - } - } - -} //end of namespace debug - -namespace internal { - -template -struct Less_vertex_point{ - typedef typename boost::graph_traits::vertex_descriptor vertex_descriptor; - const Traits& m_traits; - const VertexPointMap& m_vpmap; - Less_vertex_point(const Traits& traits, const VertexPointMap& vpmap) - : m_traits(traits) - , m_vpmap(vpmap) {} - bool operator()(vertex_descriptor v1, vertex_descriptor v2) const{ - return m_traits.less_xyz_3_object()(get(m_vpmap, v1), get(m_vpmap, v2)); - } -}; - -} // end namespace internal - -/// \ingroup PMP_repairing_grp -/// collects the degenerate edges within a given range of edges. -/// -/// @tparam EdgeRange a model of `Range` with value type `boost::graph_traits::%edge_descriptor` -/// @tparam TriangleMesh a model of `HalfedgeGraph` -/// @tparam NamedParameters a sequence of \ref pmp_namedparameters "Named Parameters" -/// -/// @param edges a subset of edges of `tm` -/// @param tm a triangle mesh -/// @param out an output iterator in which the degenerate edges are written -/// @param np optional \ref pmp_namedparameters "Named Parameters" described below -/// -/// \cgalNamedParamsBegin -/// \cgalParamBegin{vertex_point_map} the property map with the points associated to the vertices of `tm`. -/// The type of this map is model of `ReadWritePropertyMap`. -/// If this parameter is omitted, an internal property map for -/// `CGAL::vertex_point_t` should be available in `TriangleMesh` -/// \cgalParamEnd -/// \cgalParamBegin{geom_traits} a geometric traits class instance. -/// The traits class must provide the nested type `Point_3`, -/// and the nested functor `Equal_3` to check whether two points are identical. -/// \cgalParamEnd -/// \cgalNamedParamsEnd -template -OutputIterator degenerate_edges(const EdgeRange& edges, - const TriangleMesh& tm, - OutputIterator out, - const NamedParameters& np) -{ - typedef typename boost::graph_traits::edge_descriptor edge_descriptor; - - for(edge_descriptor ed : edges) - { - if(is_degenerate_edge(ed, tm, np)) - *out++ = ed; - } - return out; -} - -template -OutputIterator degenerate_edges(const EdgeRange& edges, - const TriangleMesh& tm, - OutputIterator out, - typename boost::enable_if< - typename boost::has_range_iterator - >::type* = 0) -{ - return degenerate_edges(edges, tm, out, CGAL::parameters::all_default()); -} - -/// \ingroup PMP_repairing_grp -/// calls the function `degenerate_edges()` with the range: `edges(tm)`. -/// -/// See above for the comprehensive description of the parameters. -/// -template -OutputIterator degenerate_edges(const TriangleMesh& tm, - OutputIterator out, - const NamedParameters& np -#ifndef DOXYGEN_RUNNING - , typename boost::disable_if< - boost::has_range_iterator - >::type* = 0 -#endif - ) -{ - return degenerate_edges(edges(tm), tm, out, np); -} - -template -OutputIterator -degenerate_edges(const TriangleMesh& tm, OutputIterator out) -{ - return degenerate_edges(edges(tm), tm, out, CGAL::parameters::all_default()); -} - -/// \ingroup PMP_repairing_grp -/// collects the degenerate faces within a given range of faces. -/// -/// @tparam FaceRange a model of `Range` with value type `boost::graph_traits::%face_descriptor` -/// @tparam TriangleMesh a model of `FaceGraph` -/// @tparam NamedParameters a sequence of \ref pmp_namedparameters "Named Parameters" -/// -/// @param faces a subset of faces of `tm` -/// @param tm a triangle mesh -/// @param out an output iterator in which the degenerate faces are put -/// @param np optional \ref pmp_namedparameters "Named Parameters" described below -/// -/// \cgalNamedParamsBegin -/// \cgalParamBegin{vertex_point_map} the property map with the points associated to the vertices of `tm`. -/// The type of this map is model of `ReadWritePropertyMap`. -/// If this parameter is omitted, an internal property map for -/// `CGAL::vertex_point_t` should be available in `TriangleMesh` -/// \cgalParamEnd -/// \cgalParamBegin{geom_traits} a geometric traits class instance. -/// The traits class must provide the nested functor `Collinear_3` -/// to check whether three points are collinear. -/// \cgalParamEnd -/// \cgalNamedParamsEnd -/// -template -OutputIterator degenerate_faces(const FaceRange& faces, - const TriangleMesh& tm, - OutputIterator out, - const NamedParameters& np) -{ - typedef typename boost::graph_traits::face_descriptor face_descriptor; - - for(face_descriptor fd : faces) - { - if(is_degenerate_triangle_face(fd, tm, np)) - *out++ = fd; - } - return out; -} - -template -OutputIterator degenerate_faces(const FaceRange& faces, - const TriangleMesh& tm, - OutputIterator out, - typename boost::enable_if< - boost::has_range_iterator - >::type* = 0) -{ - return degenerate_faces(faces, tm, out, CGAL::parameters::all_default()); -} - -/// \ingroup PMP_repairing_grp -/// calls the function `degenerate_faces()` with the range: `faces(tm)`. -/// -/// See above for the comprehensive description of the parameters. -/// -template -OutputIterator degenerate_faces(const TriangleMesh& tm, - OutputIterator out, - const NamedParameters& np -#ifndef DOXYGEN_RUNNING - , typename boost::disable_if< - boost::has_range_iterator - >::type* = 0 -#endif - ) -{ - return degenerate_faces(faces(tm), tm, out, np); -} - -template -OutputIterator degenerate_faces(const TriangleMesh& tm, OutputIterator out) -{ - return degenerate_faces(faces(tm), tm, out, CGAL::parameters::all_default()); -} - -// this function removes a border edge even if it does not satisfy the link condition. -// null_vertex() is returned if the removal changes the topology of the input -template -typename boost::graph_traits::vertex_descriptor -remove_a_border_edge(typename boost::graph_traits::edge_descriptor ed, - TriangleMesh& tm, - EdgeSet& edge_set, - FaceSet& face_set) -{ - typedef boost::graph_traits GT; - typedef typename GT::edge_descriptor edge_descriptor; - typedef typename GT::halfedge_descriptor halfedge_descriptor; - typedef typename GT::face_descriptor face_descriptor; - typedef typename GT::vertex_descriptor vertex_descriptor; - - halfedge_descriptor h=halfedge(ed,tm); - - if ( is_border(h,tm) ) - h=opposite(h,tm); - - halfedge_descriptor opp_h = opposite(h,tm); - CGAL_assertion(is_border(opp_h,tm)); - CGAL_assertion(!is_border(h,tm)); - - CGAL_assertion(next(next(opp_h, tm), tm) !=opp_h); // not working for a hole made of 2 edges - CGAL_assertion(next(next(next(opp_h, tm), tm), tm) !=opp_h); // not working for a hole make of 3 edges - - if (CGAL::Euler::does_satisfy_link_condition(edge(h,tm),tm)) - { - edge_set.erase(ed); - halfedge_descriptor h=halfedge(ed, tm); - if ( is_border(h, tm) ) - h = opposite(h, tm); - - edge_set.erase(edge(prev(h, tm), tm)); - face_set.erase(face(h, tm)); - - return CGAL::Euler::collapse_edge(ed, tm); - } - - // collect edges that have one vertex in the link of - // the vertices of h and one of the vertex of h as other vertex - std::set common_incident_edges; - for(halfedge_descriptor hos : halfedges_around_source(h, tm)) - for(halfedge_descriptor hot : halfedges_around_target(h, tm)) - { - if( target(hos, tm) == source(hot, tm) ) - { - common_incident_edges.insert( edge(hot, tm) ); - common_incident_edges.insert( edge(hos, tm) ); - } - } - - CGAL_assertion(common_incident_edges.size() >=2 ); - - // in the following loop, we visit define a connected component of - // faces bounded by edges in common_incident_edges and h. We look - // for the maximal one. This set of faces is the one that will - // disappear while collapsing ed - std::set marked_faces; - - std::vector queue; - queue.push_back( opposite(next(h,tm), tm) ); - queue.push_back( opposite(prev(h,tm), tm) ); - marked_faces.insert( face(h, tm) ); - - do - { - std::vector boundary; - while(!queue.empty()) - { - halfedge_descriptor back=queue.back(); - queue.pop_back(); - face_descriptor fback=face(back,tm); - if (common_incident_edges.count(edge(back,tm))) - { - boundary.push_back(back); - continue; - } - - if ( fback==GT::null_face() || !marked_faces.insert(fback).second ) - continue; - - queue.push_back( opposite(next(back,tm), tm) ); - if ( is_border(queue.back(), tm) ) - { -#ifdef CGAL_PMP_REMOVE_DEGENERATE_FACES_DEBUG - std::cout << "Boundary reached during exploration, the region to be removed is not a topological disk, not handled for now.\n"; -#endif - return GT::null_vertex(); - } - - queue.push_back( opposite(prev(back,tm), tm) ); - if ( is_border(queue.back(), tm) ) - { -#ifdef CGAL_PMP_REMOVE_DEGENERATE_FACES_DEBUG - std::cout << "Boundary reached during exploration, the region to be removed is not a topological disk, not handled for now.\n"; -#endif - return GT::null_vertex(); - } - } - - CGAL_assertion( boundary.size() == 2 ); - common_incident_edges.erase( edge(boundary[0], tm) ); - common_incident_edges.erase( edge(boundary[1], tm) ); - if (!is_border(boundary[0], tm) || common_incident_edges.empty()) - queue.push_back(boundary[0]); - if (!is_border(boundary[1], tm) || common_incident_edges.empty()) - queue.push_back(boundary[1]); - } - while(!common_incident_edges.empty()); - - // hk1 and hk2 are bounding the region that will be removed. - // The edge of hk2 will be removed and hk2 will be replaced - // by the opposite edge of hk1 - halfedge_descriptor hk1=queue.front(); - halfedge_descriptor hk2=queue.back(); - if ( target(hk1,tm)!=source(hk2,tm) ) - std::swap(hk1, hk2); - - CGAL_assertion( target(hk1,tm)==source(hk2,tm) ); - CGAL_assertion( source(hk1,tm)==source(h,tm) ); - CGAL_assertion( target(hk2,tm)==target(h,tm) ); - - CGAL_assertion(is_valid_polygon_mesh(tm)); - if (!is_selection_a_topological_disk(marked_faces, tm)) - { -#ifdef CGAL_PMP_REMOVE_DEGENERATE_FACES_DEBUG - std::cout << "The region to be removed is not a topological disk, not handled for now.\n"; -#endif - return GT::null_vertex(); - - } - - if (is_border(hk1, tm) && is_border(hk2, tm)) - { -#ifdef CGAL_PMP_REMOVE_DEGENERATE_FACES_DEBUG - std::cout << "The region to be removed is an isolated region, not handled for now.\n"; -#endif - return GT::null_vertex(); - } - - // collect vertices and edges to remove and do remove faces - std::set edges_to_remove; - std::set vertices_to_remove; - for(face_descriptor fd : marked_faces) - { - halfedge_descriptor hd=halfedge(fd, tm); - for(int i=0; i<3; ++i) - { - edges_to_remove.insert( edge(hd, tm) ); - vertices_to_remove.insert( target(hd,tm) ); - hd=next(hd, tm); - } - } - - vertex_descriptor vkept=source(hk1,tm); - - //back-up next, prev halfedge pointers to be restored after removal - halfedge_descriptor hp=prev(opp_h, tm); - halfedge_descriptor hn=next(opp_h, tm); - halfedge_descriptor hk1_opp_next = next(hk2, tm); - halfedge_descriptor hk1_opp_prev = prev(hk2, tm); - face_descriptor hk1_opp_face = face(hk2,tm); - - // we will remove the target of hk2, update vertex pointers - for(halfedge_descriptor hot : halfedges_around_target(hk2, tm)) - set_target(hot, vkept, tm); - - // update halfedge pointers since hk2 will be removed - set_halfedge(vkept, opposite(hk1, tm), tm); - set_halfedge(target(hk1,tm), hk1, tm); - - // do not remove hk1 and its vertices - vertices_to_remove.erase( vkept ); - vertices_to_remove.erase( target(hk1, tm) ); - edges_to_remove.erase( edge(hk1,tm) ); - - bool hk2_equals_hp = hk2==hp; - CGAL_assertion( is_border(hk2, tm) == hk2_equals_hp ); - - /* - - case hk2!=hp - - /\ / - hk1/ \hk2 / - / \ / - ____/______\/____ - hn h_opp hp - - - case hk2==hp - - /\ - hk1/ \hk2 == hp - / \ - ____/______\ - hn h_opp - */ - - // remove vertices - for(vertex_descriptor vd : vertices_to_remove) - remove_vertex(vd, tm); - - // remove edges - for(edge_descriptor ed : edges_to_remove) - { - edge_set.erase(ed); - remove_edge(ed, tm); - } - - // remove faces - for(face_descriptor fd : marked_faces) - { - face_set.erase(fd); - remove_face(fd, tm); - } - - // now update pointers - set_face(opposite(hk1, tm), hk1_opp_face, tm); - if (!hk2_equals_hp) - { - set_next(hp, hn, tm); - set_next(opposite(hk1, tm), hk1_opp_next, tm); - set_next(hk1_opp_prev, opposite(hk1, tm), tm); - set_halfedge(hk1_opp_face, opposite(hk1, tm), tm); - } - else - { - set_next(hk1_opp_prev, opposite(hk1, tm), tm); - set_next(opposite(hk1, tm), hn, tm); - } - - CGAL_assertion(is_valid_polygon_mesh(tm)); - return vkept; -} - -template -typename boost::graph_traits::vertex_descriptor -remove_a_border_edge(typename boost::graph_traits::edge_descriptor ed, - TriangleMesh& tm) -{ - std::set::edge_descriptor> edge_set; - std::set::face_descriptor> face_set; - - return remove_a_border_edge(ed, tm, edge_set, face_set); -} - -template -bool remove_degenerate_edges(const EdgeRange& edge_range, - TriangleMesh& tmesh, - FaceSet& face_set, - const NamedParameters& np) -{ - CGAL_assertion(CGAL::is_triangle_mesh(tmesh)); - CGAL_assertion(CGAL::is_valid_polygon_mesh(tmesh)); - - using boost::get_param; - using boost::choose_param; - - typedef TriangleMesh TM; - typedef typename boost::graph_traits GT; - typedef typename GT::edge_descriptor edge_descriptor; - typedef typename GT::halfedge_descriptor halfedge_descriptor; - typedef typename GT::face_descriptor face_descriptor; - typedef typename GT::vertex_descriptor vertex_descriptor; - - typedef typename GetVertexPointMap::type VertexPointMap; - VertexPointMap vpmap = choose_param(get_param(np, internal_np::vertex_point), - get_property_map(vertex_point, tmesh)); - - typedef typename GetGeomTraits::type Traits; - - std::size_t nb_deg_faces = 0; - bool all_removed=false; - bool some_removed=true; - - bool preserve_genus = boost::choose_param(boost::get_param(np, internal_np::preserve_genus), true); - - // collect edges of length 0 - while(some_removed && !all_removed) - { - some_removed=false; - all_removed=true; - std::set degenerate_edges_to_remove; - degenerate_edges(edge_range, tmesh, std::inserter(degenerate_edges_to_remove, - degenerate_edges_to_remove.end())); - -#ifdef CGAL_PMP_REMOVE_DEGENERATE_FACES_DEBUG - std::cout << "Found " << degenerate_edges_to_remove.size() << " null edges.\n"; -#endif - - // first try to remove all collapsable edges - typename std::set::iterator it = degenerate_edges_to_remove.begin(); - while (it!=degenerate_edges_to_remove.end()) - { - edge_descriptor e = *it; - if (CGAL::Euler::does_satisfy_link_condition(e,tmesh)) - { - halfedge_descriptor h = halfedge(e, tmesh); - degenerate_edges_to_remove.erase(it); - - // remove edges that could also be set for removal - if ( face(h, tmesh)!=GT::null_face() ) - { - ++nb_deg_faces; - degenerate_edges_to_remove.erase(edge(prev(h, tmesh), tmesh)); - face_set.erase(face(h, tmesh)); - } - - if (face(opposite(h, tmesh), tmesh)!=GT::null_face()) - { - ++nb_deg_faces; - degenerate_edges_to_remove.erase(edge(prev(opposite(h, tmesh), tmesh), tmesh)); - face_set.erase(face(opposite(h, tmesh), tmesh)); - } - - //now remove the edge - CGAL::Euler::collapse_edge(e, tmesh); - - // some_removed is not updated on purpose because if nothing - // happens below then nothing can be done - it = degenerate_edges_to_remove.begin(); - } - else - ++it; - } - - CGAL_assertion( is_valid_polygon_mesh(tmesh) ); -#ifdef CGAL_PMP_REMOVE_DEGENERATE_FACES_DEBUG - std::cout << "Remaining " << degenerate_edges_to_remove.size() << " null edges to be handled.\n"; -#endif - - while (!degenerate_edges_to_remove.empty()) - { - edge_descriptor ed = *degenerate_edges_to_remove.begin(); - degenerate_edges_to_remove.erase(degenerate_edges_to_remove.begin()); - - halfedge_descriptor h = halfedge(ed, tmesh); - - if (CGAL::Euler::does_satisfy_link_condition(ed,tmesh)) - { - // remove edges that could also be set for removal - if ( face(h, tmesh)!=GT::null_face() ) - { - ++nb_deg_faces; - degenerate_edges_to_remove.erase(edge(prev(h, tmesh), tmesh)); - face_set.erase(face(h, tmesh)); - } - - if (face(opposite(h, tmesh), tmesh)!=GT::null_face()) - { - ++nb_deg_faces; - degenerate_edges_to_remove.erase(edge(prev(opposite(h, tmesh), tmesh), tmesh)); - face_set.erase(face(opposite(h, tmesh), tmesh)); - } - - //now remove the edge - CGAL::Euler::collapse_edge(ed, tmesh); - some_removed = true; - } - else - { - //handle the case when the edge is incident to a triangle hole - //we first fill the hole and try again - if ( is_border(ed, tmesh) ) - { - halfedge_descriptor hd = halfedge(ed,tmesh); - if (!is_border(hd,tmesh)) - hd=opposite(hd,tmesh); - - if (is_triangle(hd, tmesh)) - { - if (!preserve_genus) - { - Euler::fill_hole(hd, tmesh); - degenerate_edges_to_remove.insert(ed); // reinsert the edge for future processing - } - else - { - all_removed=false; - } - continue; - } - -#ifdef CGAL_PMP_REMOVE_DEGENERATE_FACES_DEBUG - std::cout << "Calling remove_a_border_edge\n"; -#endif - - vertex_descriptor vd = remove_a_border_edge(ed, tmesh, degenerate_edges_to_remove, face_set); - if (vd == GT::null_vertex()) - { - // TODO: if some border edges are later removed, the edge might be processable later - // for example if it belongs to boundary cycle of edges where the number of non-degenerate - // edges is 2. That's what happen with fused_vertices.off in the testsuite where the edges - // are not processed the same way with Polyhedron and Surface_mesh. In the case of Polyhedron - // more degenerate edges could be removed. - all_removed=false; - } - else - some_removed=true; - - continue; - } - else - { - halfedge_descriptor hd = halfedge(ed,tmesh); - // if both vertices are boundary vertices we can't do anything - bool impossible = false; - for(halfedge_descriptor h : halfedges_around_target(hd, tmesh)) - { - if (is_border(h, tmesh)) - { - impossible = true; - break; - } - } - - if (impossible) - { - impossible=false; - for(halfedge_descriptor h : halfedges_around_source(hd, tmesh)) - { - if (is_border(h, tmesh)) - { - impossible = true; - break; - } - } - - if (impossible) - { - all_removed=false; - continue; - } - } - } - - // When the edge does not satisfy the link condition, it means that it cannot be - // collapsed as is. In the following if there is a topological issue - // with contracting the edge (component or geometric feature that disappears), - // nothing is done. - // We start by marking the faces that are incident to an edge endpoint. - // If the set of marked faces is a topologically disk, then we simply remove all the simplicies - // inside the disk and star the hole with the edge vertex kept. - // If the set of marked faces is not a topological disk, it has some non-manifold vertices - // on its boundary. We need to mark additional faces to make it a topological disk. - // We can then apply the star hole procedure. - // Right now we additionally mark the smallest connected components of non-marked faces - // (using the number of faces) - - //backup central point - typename Traits::Point_3 pt = get(vpmap, source(ed, tmesh)); - - // mark faces of the link of each endpoints of the edge which collapse is not topologically valid - std::set marked_faces; - - // first endpoint - for(halfedge_descriptor hd : CGAL::halfedges_around_target(halfedge(ed,tmesh), tmesh) ) - if (!is_border(hd,tmesh)) marked_faces.insert( face(hd, tmesh) ); - - // second endpoint - for(halfedge_descriptor hd : CGAL::halfedges_around_target(opposite(halfedge(ed, tmesh), tmesh), tmesh) ) - if (!is_border(hd,tmesh)) marked_faces.insert( face(hd, tmesh) ); - - // extract the halfedges on the boundary of the marked region - std::vector border; - for(face_descriptor fd : marked_faces) - { - for(halfedge_descriptor hd : CGAL::halfedges_around_face(halfedge(fd,tmesh), tmesh)) - { - halfedge_descriptor hd_opp = opposite(hd, tmesh); - if ( is_border(hd_opp, tmesh) || - marked_faces.count( face(hd_opp, tmesh) ) == 0 ) - { - border.push_back( hd ); - } - } - } - - if (border.empty() ) - { - // a whole connected component (without boundary) got selected and will disappear (not handled for now) -#ifdef CGAL_PMP_REMOVE_DEGENERATE_FACES_DEBUG - std::cout << "Trying to remove a whole connected component, not handled yet\n"; -#endif - all_removed=false; - continue; - } - - // define cc of border halfedges: two halfedges are in the same cc - // if they are on the border of the cc of non-marked faces. - typedef CGAL::Union_find UF_ds; - UF_ds uf; - std::map handles; - // one cc per border halfedge - for(halfedge_descriptor hd : border) - handles.insert( std::make_pair(hd, uf.make_set(hd)) ); - - // join cc's - for(halfedge_descriptor hd : border) - { - CGAL_assertion( marked_faces.count( face( hd, tmesh) ) > 0); - CGAL_assertion( marked_faces.count( face( opposite(hd, tmesh), tmesh) ) == 0 ); - halfedge_descriptor candidate = hd; - - do { - candidate = prev( opposite(candidate, tmesh), tmesh ); - } while( !marked_faces.count( face( opposite(candidate, tmesh), tmesh) ) ); - - uf.unify_sets( handles[hd], handles[opposite(candidate, tmesh)] ); - } - - std::size_t nb_cc = uf.number_of_sets(); - if ( nb_cc != 1 ) - { - // if more than one connected component is found then the patch - // made of marked faces contains "non-manifold" vertices. - // The smallest components need to be marked so that the patch - // made of marked faces is a topological disk - - // we will explore in parallel the connected components and will stop - // when all but one connected component have been entirely explored. - // We add one face at a time for each cc in order to not explore a - // potentially very large cc. - std::vector< std::vector > stacks_per_cc(nb_cc); - std::vector< std::set > faces_per_cc(nb_cc); - std::vector< bool > exploration_finished(nb_cc, false); - - // init the stacks of halfedges using the cc of the boundary - std::size_t index=0; - std::map< halfedge_descriptor, std::size_t > ccs; - typedef std::pair Pair_type; - for(Pair_type p : handles) - { - halfedge_descriptor opp_hedge = opposite(p.first, tmesh); - if (is_border(opp_hedge, tmesh)) continue; // nothing to do on the boundary - - typedef typename std::map< halfedge_descriptor, std::size_t >::iterator Map_it; - std::pair insert_res= - ccs.insert( std::make_pair(*uf.find( p.second ), index) ); - - if (insert_res.second) - ++index; - - stacks_per_cc[ insert_res.first->second ].push_back( prev(opp_hedge, tmesh) ); - stacks_per_cc[ insert_res.first->second ].push_back( next(opp_hedge, tmesh) ); - faces_per_cc[ insert_res.first->second ].insert( face(opp_hedge, tmesh) ); - } - - if (index != nb_cc) - { - // most probably, one cc is a cycle of border edges -#ifdef CGAL_PMP_REMOVE_DEGENERATE_FACES_DEBUG - std::cout << "Trying to remove a component with a cycle of halfedges (nested hole or whole component), not handled yet.\n"; -#endif - all_removed=false; - continue; - } - - std::size_t nb_ccs_to_be_explored = nb_cc; - index=0; - //explore the cc's - do - { - // try to extract one more face for a given cc - do - { - CGAL_assertion( !exploration_finished[index] ); - CGAL_assertion( !stacks_per_cc.empty() ); - CGAL_assertion( !stacks_per_cc[index].empty() ); - halfedge_descriptor hd = stacks_per_cc[index].back(); - stacks_per_cc[index].pop_back(); - hd = opposite(hd, tmesh); - if ( !is_border(hd,tmesh) && !marked_faces.count(face(hd, tmesh) ) ) - { - if ( faces_per_cc[index].insert( face(hd, tmesh) ).second ) - { - stacks_per_cc[index].push_back( next(hd, tmesh) ); - stacks_per_cc[index].push_back( prev(hd, tmesh) ); - break; - } - } - if (stacks_per_cc[index].empty()) break; - } - while(true); - - // the exploration of a cc is finished when its stack is empty - exploration_finished[index]=stacks_per_cc[index].empty(); - if ( exploration_finished[index] ) - --nb_ccs_to_be_explored; - if ( nb_ccs_to_be_explored==1 ) - break; - while ( exploration_finished[(++index)%nb_cc] ); - index=index%nb_cc; - } - while(true); - - /// \todo use the area criteria? this means maybe continue exploration of larger cc - // mark faces of completetly explored cc - for (index=0; index< nb_cc; ++index) - { - if( exploration_finished[index] ) - { - for(face_descriptor fd : faces_per_cc[index]) - marked_faces.insert(fd); - } - } - } - - // make sure the selection is a topological disk (otherwise we need another treatment) - if (!is_selection_a_topological_disk(marked_faces, tmesh)) - { -#ifdef CGAL_PMP_REMOVE_DEGENERATE_FACES_DEBUG - std::cout << "Trying to handle a non-topological disk, do nothing\n"; -#endif - all_removed=false; - continue; - } - - some_removed = true; - // collect simplices to be removed - std::set vertices_to_keep; - std::set halfedges_to_keep; - for(halfedge_descriptor hd : border) - { - if ( !marked_faces.count(face(opposite(hd, tmesh), tmesh)) ) - { - halfedges_to_keep.insert( hd ); - vertices_to_keep.insert( target(hd, tmesh) ); - } - } - - // backup next,prev relationships to set after patch removal - std::vector< std::pair > next_prev_halfedge_pairs; - halfedge_descriptor first_border_hd=*( halfedges_to_keep.begin() ); - halfedge_descriptor current_border_hd=first_border_hd; - do - { - halfedge_descriptor prev_border_hd=current_border_hd; - current_border_hd=next(current_border_hd, tmesh); - while( marked_faces.count( face( opposite(current_border_hd, tmesh), tmesh) ) ) - current_border_hd=next(opposite(current_border_hd, tmesh), tmesh); - next_prev_halfedge_pairs.push_back( std::make_pair(prev_border_hd, current_border_hd) ); - } - while(current_border_hd!=first_border_hd); - - // collect vertices and edges to remove and do remove faces - std::set edges_to_remove; - std::set vertices_to_remove; - for(face_descriptor fd : marked_faces) - { - halfedge_descriptor hd=halfedge(fd, tmesh); - for(int i=0; i<3; ++i) - { - if ( !halfedges_to_keep.count(hd) ) - edges_to_remove.insert( edge(hd, tmesh) ); - - if ( !vertices_to_keep.count(target(hd,tmesh)) ) - vertices_to_remove.insert( target(hd,tmesh) ); - - hd=next(hd, tmesh); - } - - remove_face(fd, tmesh); - face_set.erase(fd); - } - - // remove vertices - for(vertex_descriptor vd : vertices_to_remove) - remove_vertex(vd, tmesh); - - // remove edges - for(edge_descriptor ed : edges_to_remove) - { - degenerate_edges_to_remove.erase(ed); - remove_edge(ed, tmesh); - } - - // add a new face, set all border edges pointing to it - // and update halfedge vertex of patch boundary vertices - face_descriptor new_face = add_face(tmesh); - typedef std::pair Pair_type; - for(const Pair_type& p : next_prev_halfedge_pairs) - { - set_face(p.first, new_face, tmesh); - set_next(p.first, p.second, tmesh); - set_halfedge(target(p.first, tmesh), p.first, tmesh); - } - - set_halfedge(new_face, first_border_hd, tmesh); - // triangulate the new face and update the coordinate of the central vertex - halfedge_descriptor new_hd=Euler::add_center_vertex(first_border_hd, tmesh); - put(vpmap, target(new_hd, tmesh), pt); - - for(halfedge_descriptor hd : halfedges_around_target(new_hd, tmesh)) - { - if(is_degenerate_edge(edge(hd, tmesh), tmesh, np)) - degenerate_edges_to_remove.insert(edge(hd, tmesh)); - - if(face(hd, tmesh) != GT::null_face() && is_degenerate_triangle_face(face(hd, tmesh), tmesh)) - face_set.insert(face(hd, tmesh)); - } - - CGAL_assertion( is_valid_polygon_mesh(tmesh) ); - } - } - } - - return all_removed; -} - -template -bool remove_degenerate_edges(const EdgeRange& edge_range, - TriangleMesh& tmesh, - const CGAL_PMP_NP_CLASS& np) -{ - std::set::face_descriptor> face_set; - return remove_degenerate_edges(edge_range, tmesh, face_set, np); -} - -template -bool remove_degenerate_edges(TriangleMesh& tmesh, - const CGAL_PMP_NP_CLASS& np) -{ - std::set::face_descriptor> face_set; - return remove_degenerate_edges(edges(tmesh), tmesh, face_set, np); -} - -template -bool remove_degenerate_edges(const EdgeRange& edge_range, - TriangleMesh& tmesh) -{ - std::set::face_descriptor> face_set; - return remove_degenerate_edges(edge_range, tmesh, face_set, parameters::all_default()); -} - -template -bool remove_degenerate_edges(TriangleMesh& tmesh) -{ - std::set::face_descriptor> face_set; - return remove_degenerate_edges(edges(tmesh), tmesh, face_set, parameters::all_default()); -} - -// \ingroup PMP_repairing_grp -// removes the degenerate faces from a triangulated surface mesh. -// A face is considered degenerate if two of its vertices share the same location, -// or more generally if all its vertices are collinear. -// -// @pre `CGAL::is_triangle_mesh(tmesh)` -// -// @tparam TriangleMesh a model of `FaceListGraph` and `MutableFaceGraph` -// @tparam NamedParameters a sequence of \ref pmp_namedparameters "Named Parameters" -// -// @param tmesh the triangulated surface mesh to be repaired -// @param np optional \ref pmp_namedparameters "Named Parameters" described below -// -// \cgalNamedParamsBegin -// \cgalParamBegin{vertex_point_map} the property map with the points associated to the vertices of `pmesh`. -// The type of this map is model of `ReadWritePropertyMap`. -// If this parameter is omitted, an internal property map for -// `CGAL::vertex_point_t` must be available in `TriangleMesh` -// \cgalParamEnd -// \cgalParamBegin{geom_traits} a geometric traits class instance. -// The traits class must provide the nested type `Point_3`, -// and the nested functors: -// - `Compare_distance_3` to compute the distance between 2 points -// - `Collinear_3` to check whether 3 points are collinear -// - `Less_xyz_3` to compare lexicographically two points -// - `Equal_3` to check whether 2 points are identical. -// For each functor Foo, a function `Foo foo_object()` must be provided. -// \cgalParamEnd -// \cgalNamedParamsEnd -// -// \todo the function might not be able to remove all degenerate faces. -// We should probably do something with the return type. -// -// \return `true` if all degenerate faces were successfully removed, and `false` otherwise. -template -bool remove_degenerate_faces(const FaceRange& face_range, - TriangleMesh& tmesh, - const NamedParameters& np) -{ - CGAL_assertion(CGAL::is_triangle_mesh(tmesh)); - - using boost::get_param; - using boost::choose_param; - - typedef TriangleMesh TM; - typedef typename boost::graph_traits GT; - typedef typename GT::edge_descriptor edge_descriptor; - typedef typename GT::halfedge_descriptor halfedge_descriptor; - typedef typename GT::face_descriptor face_descriptor; - typedef typename GT::vertex_descriptor vertex_descriptor; - - typedef typename GetVertexPointMap::type VertexPointMap; - VertexPointMap vpmap = choose_param(get_param(np, internal_np::vertex_point), - get_property_map(vertex_point, tmesh)); - typedef typename GetGeomTraits::type Traits; - Traits traits = choose_param(get_param(np, internal_np::geom_traits), Traits()); - - typedef typename boost::property_traits::value_type Point_3; - typedef typename boost::property_traits::reference Point_ref; - - std::set degenerate_face_set; - degenerate_faces(face_range, tmesh, std::inserter(degenerate_face_set, degenerate_face_set.begin()), np); - - const std::size_t faces_size = faces(tmesh).size(); - - if(degenerate_face_set.empty()) - return true; - - if(degenerate_face_set.size() == faces_size) - { - clear(tmesh); - return true; - } - - // Sanitize the face range by adding adjacent degenerate faces - const std::size_t range_size = face_range.size(); - bool is_range_full_mesh = (range_size == faces_size); - if(!is_range_full_mesh) - { - std::list faces_to_visit(degenerate_face_set.begin(), degenerate_face_set.end()); - - while(!faces_to_visit.empty()) - { - face_descriptor fd = faces_to_visit.front(); - faces_to_visit.pop_front(); - - for(halfedge_descriptor hd : halfedges_around_face(halfedge(fd, tmesh), tmesh)) - { - for(halfedge_descriptor inc_hd : halfedges_around_target(hd, tmesh)) - { - face_descriptor adj_fd = face(inc_hd, tmesh); - if(adj_fd == GT::null_face() || adj_fd == fd) - continue; - - if(is_degenerate_triangle_face(adj_fd, tmesh)) - { - if(degenerate_face_set.insert(adj_fd).second) - { - // successful insertion means we did not know about this face before - faces_to_visit.push_back(adj_fd); - } - } - } - } - } - } - - // Note that there can't be any null edge incident to the degenerate faces range, - // otherwise we would have a null face incident to the face range, and that is not possible - // after the sanitization above - std::set edge_range; - for(face_descriptor fd : degenerate_face_set) - for(halfedge_descriptor hd : halfedges_around_face(halfedge(fd, tmesh), tmesh)) - edge_range.insert(edge(hd, tmesh)); - - // First remove edges of length 0 - bool all_removed = remove_degenerate_edges(edge_range, tmesh, degenerate_face_set, np); - - CGAL_assertion_code(for(face_descriptor fd : degenerate_face_set) {) - CGAL_assertion(is_degenerate_triangle_face(fd, tmesh)); - CGAL_assertion_code(}) - -#ifdef CGAL_PMP_REMOVE_DEGENERATE_FACES_DEBUG - { - std::cout <<"Done with null edges.\n"; - std::ofstream output("/tmp/no_null_edges.off"); - output << std::setprecision(17) << tmesh << "\n"; - output.close(); - } -#endif - - // Then, remove triangles made of 3 collinear points - - // start by filtering out border faces - // TODO: shall we avoid doing that in case a non-manifold vertex on the boundary or if a whole component disappear? - std::set border_deg_faces; - for(face_descriptor f : degenerate_face_set) - { - halfedge_descriptor h = halfedge(f, tmesh); - for (int i=0; i<3; ++i) - { - if ( is_border( opposite(h, tmesh), tmesh) ) - { - border_deg_faces.insert(f); - break; - } - h = next(h, tmesh); - } - } - - while( !border_deg_faces.empty() ) - { - face_descriptor f_to_rm = *border_deg_faces.begin(); - border_deg_faces.erase(border_deg_faces.begin()); - - halfedge_descriptor h = halfedge(f_to_rm, tmesh); - for (int i=0; i<3; ++i) - { - face_descriptor f = face(opposite(h, tmesh), tmesh); - if ( f!=GT::null_face() ) - { - if (is_degenerate_triangle_face(f, tmesh, np) ) - border_deg_faces.insert(f); - } - h = next(h, tmesh); - } - - while( !is_border(opposite(h, tmesh), tmesh) ) - { - h = next(h, tmesh); - } - - degenerate_face_set.erase(f_to_rm); - Euler::remove_face(h, tmesh); - } - - // Ignore faces with null edges - if(!all_removed) - { - std::map are_degenerate_edges; - - for(face_descriptor fd : degenerate_face_set) - { - for(halfedge_descriptor hd : halfedges_around_face(halfedge(fd, tmesh), tmesh)) - { - edge_descriptor ed = edge(hd, tmesh); - std::pair::iterator, bool> is_insert_successful = - are_degenerate_edges.insert(std::make_pair(ed, false)); - - bool is_degenerate = false; - if(is_insert_successful.second) - { - // did not previously exist in the map, so actually have to check if it is degenerate - if(traits.equal_3_object()(get(vpmap, target(ed, tmesh)), get(vpmap, source(ed, tmesh)))) - is_degenerate = true; - } - - is_insert_successful.first->second = is_degenerate; - - if(is_degenerate) - { - halfedge_descriptor h = halfedge(ed, tmesh); - if(!is_border(h, tmesh)) - degenerate_face_set.erase(face(h, tmesh)); - - h = opposite(h, tmesh); - if(!is_border(h, tmesh)) - degenerate_face_set.erase(face(h, tmesh)); - } - } - } - } - - // first remove degree 3 vertices that are part of a cap - // (only the vertex in the middle of the opposite edge) - // This removal does not change the shape of the mesh. - while (!degenerate_face_set.empty()) - { - std::set vertices_to_remove; - for(face_descriptor fd : degenerate_face_set) - { - for(halfedge_descriptor hd : halfedges_around_face(halfedge(fd, tmesh), tmesh)) - { - vertex_descriptor vd = target(hd, tmesh); - if (degree(vd, tmesh) == 3 && is_border(vd, tmesh)==GT::null_halfedge()) - { - vertices_to_remove.insert(vd); - break; - } - } - } - - for(vertex_descriptor vd : vertices_to_remove) - { - for(halfedge_descriptor hd2 : halfedges_around_target(vd, tmesh)) - degenerate_face_set.erase( face(hd2, tmesh) ); - - // remove the central vertex and check if the new face is degenerated - halfedge_descriptor hd = CGAL::Euler::remove_center_vertex(halfedge(vd, tmesh), tmesh); - if (is_degenerate_triangle_face(face(hd, tmesh), tmesh, np)) - { - degenerate_face_set.insert( face(hd, tmesh) ); - } - } - - if (vertices_to_remove.empty()) - break; - } - - while (!degenerate_face_set.empty()) - { -#ifdef CGAL_PMP_REMOVE_DEGENERATE_FACES_DEBUG - std::cout << "Loop on removing deg faces\n"; - // ensure the mesh is not broken - { - std::ofstream out("/tmp/out.off"); - out << tmesh; - out.close(); - - std::vector points; - std::vector > triangles; - std::ifstream in("/tmp/out.off"); - CGAL::read_OFF(in, points, triangles); - if (!CGAL::Polygon_mesh_processing::is_polygon_soup_a_polygon_mesh(triangles)) - { - std::cerr << "Warning: got a polygon soup (may simply be a non-manifold vertex)!\n"; - } - } -#endif - - face_descriptor fd = *degenerate_face_set.begin(); - - // look whether an incident triangle is also degenerate - bool detect_cc_of_degenerate_triangles = false; - for(halfedge_descriptor hd : halfedges_around_face(halfedge(fd, tmesh), tmesh) ) - { - face_descriptor adjacent_face = face( opposite(hd, tmesh), tmesh ); - if ( adjacent_face!=GT::null_face() && - degenerate_face_set.count(adjacent_face) ) - { - detect_cc_of_degenerate_triangles = true; - break; - } - } - - if (!detect_cc_of_degenerate_triangles) - { -#ifdef CGAL_PMP_REMOVE_DEGENERATE_FACES_DEBUG - std::cout << " no degenerate neighbors, using a flip.\n"; -#endif - degenerate_face_set.erase(degenerate_face_set.begin()); - - // flip the longest edge of the triangle - Point_ref p1 = get(vpmap, target( halfedge(fd, tmesh), tmesh) ); - Point_ref p2 = get(vpmap, target(next(halfedge(fd, tmesh), tmesh), tmesh) ); - Point_ref p3 = get(vpmap, source( halfedge(fd, tmesh), tmesh) ); - - CGAL_assertion(p1!=p2 && p1!=p3 && p2!=p3); - - typename Traits::Compare_distance_3 compare_distance = traits.compare_distance_3_object(); - - halfedge_descriptor edge_to_flip; - if (compare_distance(p1,p2, p1,p3) != CGAL::SMALLER) // p1p2 > p1p3 - { - if (compare_distance(p1,p2, p2,p3) != CGAL::SMALLER) // p1p2 > p2p3 - // flip p1p2 - edge_to_flip = next( halfedge(fd, tmesh), tmesh ); - else - // flip p2p3 - edge_to_flip = prev( halfedge(fd, tmesh), tmesh ); - } - else - if (compare_distance(p1,p3, p2,p3) != CGAL::SMALLER) // p1p3>p2p3 - //flip p3p1 - edge_to_flip = halfedge(fd, tmesh); - else - //flip p2p3 - edge_to_flip = prev( halfedge(fd, tmesh), tmesh ); - - face_descriptor opposite_face=face( opposite(edge_to_flip, tmesh), tmesh); - if ( opposite_face == GT::null_face() ) - { - // simply remove the face - Euler::remove_face(edge_to_flip, tmesh); - } - else - { - // condition for the flip to be valid (the edge to be created do not already exists) - if ( !halfedge(target(next(edge_to_flip, tmesh), tmesh), - target(next(opposite(edge_to_flip, tmesh), tmesh), tmesh), - tmesh).second ) - { - Euler::flip_edge(edge_to_flip, tmesh); - } - else - { - all_removed=false; -#ifdef CGAL_PMP_REMOVE_DEGENERATE_FACES_DEBUG - std::cout << " WARNING: flip is not possible\n"; - // \todo Let p and q be the vertices opposite to `edge_to_flip`, and let - // r be the vertex of `edge_to_flip` that is the furthest away from - // the edge `pq`. In that case I think we should remove all the triangles - // so that the triangle pqr is in the mesh. -#endif - } - } - } - else - { - // Process a connected component of degenerate faces - // get all the faces from the connected component - // and the boundary edges - std::set cc_faces; - std::vector queue; - std::vector boundary_hedges; - std::vector inside_hedges; - queue.push_back(fd); - cc_faces.insert(fd); - - while(!queue.empty()) - { - face_descriptor top=queue.back(); - queue.pop_back(); - for(halfedge_descriptor hd : halfedges_around_face(halfedge(top, tmesh), tmesh) ) - { - face_descriptor adjacent_face = face( opposite(hd, tmesh), tmesh ); - if ( adjacent_face==GT::null_face() || - degenerate_face_set.count(adjacent_face)==0 ) - { - boundary_hedges.push_back(hd); - } - else - { - if (cc_faces.insert(adjacent_face).second) - queue.push_back(adjacent_face); - - if ( hd < opposite(hd, tmesh) ) - inside_hedges.push_back(hd); - } - } - } - -#ifdef CGAL_PMP_REMOVE_DEGENERATE_FACES_DEBUG - std::cout << " Deal with a cc of " << cc_faces.size() << " degenerate faces.\n"; - /// dump cc_faces - { - int id=0; - std::map vids; - for(face_descriptor f : cc_faces) - { - if ( vids.insert( std::make_pair( target(halfedge(f, tmesh), tmesh), id) ).second ) ++id; - if ( vids.insert( std::make_pair( target(next(halfedge(f, tmesh), tmesh), tmesh), id) ).second ) ++id; - if ( vids.insert( std::make_pair( target(next(next(halfedge(f, tmesh), tmesh), tmesh), tmesh), id) ).second ) ++id; - } - - std::ofstream output("/tmp/cc_faces.off"); - output << std::setprecision(17); - output << "OFF\n" << vids.size() << " " << cc_faces.size() << " 0\n"; - std::vector points(vids.size()); - typedef std::pair Pair_type; - for(Pair_type p : vids) - - points[p.second]=get(vpmap, p.first); - for(typename Traits::Point_3 p : points) - output << p << "\n"; - - for(face_descriptor f : cc_faces) - { - output << "3 " - << vids[ target(halfedge(f, tmesh), tmesh) ] << " " - << vids[ target(next(halfedge(f, tmesh), tmesh), tmesh) ] << " " - << vids[ target(next(next(halfedge(f, tmesh), tmesh), tmesh), tmesh) ] << "\n"; - } - - for (std::size_t pid=2; pid!=points.size(); ++pid) - { - CGAL_assertion(collinear(points[0], points[1], points[pid])); - } - } -#endif - - // find vertices strictly inside the cc - std::set boundary_vertices; - for(halfedge_descriptor hd : boundary_hedges) - boundary_vertices.insert( target(hd, tmesh) ); - - std::set inside_vertices; - for(halfedge_descriptor hd : inside_hedges) - { - if (!boundary_vertices.count( target(hd, tmesh) )) - inside_vertices.insert( target(hd, tmesh) ); - if (!boundary_vertices.count( source(hd, tmesh) )) - inside_vertices.insert( source(hd, tmesh) ); - } - - // v-e+f = 1 for a topological disk and e = (3f+#boundary_edges)/2 - if (boundary_vertices.size()+inside_vertices.size() - - (cc_faces.size()+boundary_hedges.size())/2 != 1) - { - //cc_faces does not define a topological disk - /// \todo Find to way to handle that case -#ifdef CGAL_PMP_REMOVE_DEGENERATE_FACES_DEBUG - std::cout << " WARNING: Cannot remove the component of degenerate faces: not a topological disk.\n"; -#endif - - for(face_descriptor f : cc_faces) - degenerate_face_set.erase(f); - - continue; - } - - // preliminary step to check if the operation is possible - // sort the boundary points along the common supporting line - // we first need a reference point - typedef internal::Less_vertex_point Less_vertex; - std::pair< - typename std::set::iterator, - typename std::set::iterator > ref_vertices = - boost::minmax_element( boundary_vertices.begin(), - boundary_vertices.end(), - Less_vertex(traits, vpmap) ); - - // and then we sort the vertices using this reference point - typedef std::set Sorted_point_set; - Sorted_point_set sorted_points; - for(vertex_descriptor v : boundary_vertices) - sorted_points.insert( get(vpmap,v) ); - - CGAL_assertion(sorted_points.size()== - std::set(sorted_points.begin(), - sorted_points.end()).size()); - - CGAL_assertion( get( vpmap, *ref_vertices.first)==*sorted_points.begin() ); - CGAL_assertion( get( vpmap, *ref_vertices.second)==*std::prev(sorted_points.end()) ); - - const typename Traits::Point_3& xtrm1 = *sorted_points.begin(); - const typename Traits::Point_3& xtrm2 = *std::prev(sorted_points.end()); - - // recover halfedges on the hole, bounded by the reference vertices - std::vector side_one, side_two; - - // look for the outgoing border halfedge of the first extreme point - for(halfedge_descriptor hd : boundary_hedges) - { - if ( get(vpmap, source(hd, tmesh)) == xtrm1 ) - { - side_one.push_back(hd); - break; - } - } - CGAL_assertion(side_one.size()==1); - - bool non_monotone_border = false; - - while( get(vpmap, target(side_one.back(), tmesh)) != xtrm2 ) - { - vertex_descriptor prev_vertex = target(side_one.back(), tmesh); - for(halfedge_descriptor hd : boundary_hedges) - { - if ( source(hd, tmesh) == prev_vertex ) - { - if ( get(vpmap, target(hd, tmesh)) < get(vpmap, prev_vertex) ) - non_monotone_border = true; - - side_one.push_back(hd); - break; - } - } - - if (non_monotone_border) - break; - } - - if (non_monotone_border) - { -#ifdef CGAL_PMP_REMOVE_DEGENERATE_FACES_DEBUG - std::cout << " WARNING: Cannot remove the component of degenerate faces: border not a monotonic cycle.\n"; -#endif - - for(face_descriptor f : cc_faces) - degenerate_face_set.erase(f); - - continue; - } - - // look for the outgoing border halfedge of second extreme vertex - for(halfedge_descriptor hd : boundary_hedges) - { - if ( source(hd, tmesh) == target(side_one.back(), tmesh) ) - { - side_two.push_back(hd); - break; - } - } - CGAL_assertion(side_two.size()==1); - - while( target(side_two.back(), tmesh) != source(side_one.front(), tmesh) ) - { - vertex_descriptor prev_vertex = target(side_two.back(), tmesh); - for(halfedge_descriptor hd : boundary_hedges) - { - if ( source(hd, tmesh) == prev_vertex ) - { - if ( get(vpmap, target(hd, tmesh)) > get(vpmap, prev_vertex) ) - non_monotone_border = true; - - side_two.push_back(hd); - break; - } - } - - if (non_monotone_border) - break; - } - - if (non_monotone_border) - { -#ifdef CGAL_PMP_REMOVE_DEGENERATE_FACES_DEBUG - std::cout << " WARNING: Cannot remove the component of degenerate faces: border not a monotonic cycle.\n"; -#endif - - for(face_descriptor f : cc_faces) - degenerate_face_set.erase(f); - - continue; - } - - CGAL_assertion( side_one.size()+side_two.size()==boundary_hedges.size() ); - - // reverse the order of the second side so as to follow - // the same order than side one - std::reverse(side_two.begin(), side_two.end()); - for(halfedge_descriptor& h : side_two) - h=opposite(h, tmesh); - - //make sure the points of the vertices along side_one are correctly sorted - std::vector side_points; - side_points.reserve(side_one.size()+1); - side_points.push_back(get(vpmap,source(side_one.front(), tmesh))); - - for(halfedge_descriptor h : side_one) - side_points.push_back(get(vpmap,target(h, tmesh))); - - CGAL_assertion(get(vpmap,source(side_one.front(), tmesh))==side_points.front()); - CGAL_assertion(get(vpmap,target(side_one.back(), tmesh))==side_points.back()); - - //\todo the reordering could lead to the apparition of null edges. - std::sort(side_points.begin(), side_points.end()); - - CGAL_assertion(std::unique(side_points.begin(), side_points.end())==side_points.end()); - for(std::size_t i=0;i::iterator side_one_it = side_one.begin(); - typename std::vector::iterator side_two_it = side_two.begin(); - for(;it_pt!=it_pt_end;++it_pt) - { - // check if it_pt is the point of the target of one or two halfedges - bool target_of_side_one = get(vpmap, target(*side_one_it, tmesh))==*it_pt; - bool target_of_side_two = get(vpmap, target(*side_two_it, tmesh))==*it_pt; - - if (target_of_side_one && target_of_side_two) - { - for(halfedge_descriptor h : halfedges_around_target(*side_one_it, tmesh)) - { - if (source(h, tmesh)==target(*side_two_it, tmesh)) - { - non_collapsable=true; - break; - } - } - } - else - { - CGAL_assertion(target_of_side_one || target_of_side_two); - vertex_descriptor v1 = target_of_side_one ? target(*side_one_it, tmesh) - : target(*side_two_it, tmesh); - vertex_descriptor v2 = target_of_side_two ? target(next(opposite(*side_one_it, tmesh), tmesh), tmesh) - : target(next(*side_two_it, tmesh), tmesh); - for(halfedge_descriptor h : halfedges_around_target(v1, tmesh)) - { - if (source(h, tmesh)==v2) - { - non_collapsable=true; - break; - } - } - } - - if(non_collapsable) break; - if (target_of_side_one) ++side_one_it; - if (target_of_side_two) ++side_two_it; - } - - if (non_collapsable) - { - for(face_descriptor f : cc_faces) - degenerate_face_set.erase(f); - -#ifdef CGAL_PMP_REMOVE_DEGENERATE_FACES_DEBUG - std::cout << " WARNING: cannot remove a connected components of degenerate faces.\n"; -#endif - continue; - } - - // now proceed to the fix - // update the face and halfedge vertex pointers on the boundary - for(halfedge_descriptor h : boundary_hedges) - { - set_face(h, GT::null_face(), tmesh); - set_halfedge(target(h,tmesh), h, tmesh); - } - - // update next/prev pointers of boundary_hedges - for(halfedge_descriptor h : boundary_hedges) - { - halfedge_descriptor next_candidate = next( h, tmesh); - while (face(next_candidate, tmesh)!=GT::null_face()) - next_candidate = next( opposite( next_candidate, tmesh), tmesh); - - set_next(h, next_candidate, tmesh); - } - - // remove degenerate faces - for(face_descriptor f : cc_faces) - { - degenerate_face_set.erase(f); - remove_face(f, tmesh); - } - - // remove interior edges - for(halfedge_descriptor h : inside_hedges) - remove_edge(edge(h, tmesh), tmesh); - - // remove interior vertices - for(vertex_descriptor v : inside_vertices) - remove_vertex(v, tmesh); - -#ifdef CGAL_PMP_REMOVE_DEGENERATE_FACES_DEBUG - std::cout << " side_one.size() " << side_one.size() << "\n"; - std::cout << " side_two.size() " << side_two.size() << "\n"; -#endif - - CGAL_assertion( source(side_one.front(), tmesh) == *ref_vertices.first ); - CGAL_assertion( source(side_two.front(), tmesh) == *ref_vertices.first ); - CGAL_assertion( target(side_one.back(), tmesh) == *ref_vertices.second ); - CGAL_assertion( target(side_two.back(), tmesh) == *ref_vertices.second ); - - // now split each side to contains the same sequence of points - // first side - int hi=0; - for (typename Sorted_point_set::iterator it=std::next(sorted_points.begin()), - it_end=sorted_points.end(); it!=it_end; ++it) - { - CGAL_assertion( *std::prev(it) == get(vpmap, source(side_one[hi], tmesh) ) ); - if( *it != get(vpmap, target(side_one[hi], tmesh) ) ) - { - // split the edge and update the point - halfedge_descriptor h1 = next(opposite(side_one[hi], tmesh), tmesh); - put(vpmap, - target(Euler::split_edge(side_one[hi], tmesh), tmesh), - *it); - - // split_edge updates the halfedge of the source vertex of h, - // since we reuse later the halfedge of the first refernce vertex - // we must set it as we need. - if ( source(h1,tmesh) == *ref_vertices.first) - set_halfedge(*ref_vertices.first, prev( prev(side_one[hi], tmesh), tmesh), tmesh ); - - // retriangulate the opposite face - if ( face(h1, tmesh) != GT::null_face()) - Euler::split_face(h1, opposite(side_one[hi], tmesh), tmesh); - } - else - { - ++hi; - } - } - - // second side - hi=0; - for (typename Sorted_point_set::iterator it=std::next(sorted_points.begin()), - it_end=sorted_points.end(); it!=it_end; ++it) - { - CGAL_assertion( *std::prev(it) == get(vpmap, source(side_two[hi], tmesh) ) ); - if( *it != get(vpmap, target(side_two[hi], tmesh) ) ) - { - // split the edge and update the point - halfedge_descriptor h2 = Euler::split_edge(side_two[hi], tmesh); - put(vpmap, target(h2, tmesh), *it); - - // split_edge updates the halfedge of the source vertex of h, - // since we reuse later the halfedge of the first refernce vertex - // we must set it as we need. - if ( source(h2,tmesh) == *ref_vertices.first) - set_halfedge(*ref_vertices.first, opposite( h2, tmesh), tmesh ); - - // retriangulate the face - if ( face(h2, tmesh) != GT::null_face()) - Euler::split_face(h2, next(side_two[hi], tmesh), tmesh); - } - else - { - ++hi; - } - } - - CGAL_assertion( target(halfedge(*ref_vertices.first, tmesh), tmesh) == *ref_vertices.first ); - CGAL_assertion( face(halfedge(*ref_vertices.first, tmesh), tmesh) == GT::null_face() ); - -#ifdef CGAL_PMP_REMOVE_DEGENERATE_FACES_DEBUG - { - halfedge_descriptor h_side2 = halfedge(*ref_vertices.first, tmesh); - halfedge_descriptor h_side1 = next(h_side2, tmesh); - - do - { - CGAL_assertion( get(vpmap, source(h_side1, tmesh)) == get(vpmap, target(h_side2, tmesh)) ); - CGAL_assertion( get(vpmap, target(h_side1, tmesh)) == get(vpmap, source(h_side2, tmesh)) ); - - if ( target(next(opposite(h_side1, tmesh), tmesh), tmesh) == - target(next(opposite(h_side2, tmesh), tmesh), tmesh) ) - { - CGAL_assertion(!"Forbidden simplification"); - } - - h_side2 = prev(h_side2, tmesh); - h_side1 = next(h_side1, tmesh); - } - while( target(h_side1, tmesh) != *ref_vertices.second ); - } -#endif - - // remove side1 and replace its opposite hedges by those of side2 - halfedge_descriptor h_side2 = halfedge(*ref_vertices.first, tmesh); - halfedge_descriptor h_side1 = next(h_side2, tmesh); - while(true) - { - CGAL_assertion( get(vpmap, source(h_side1, tmesh)) == get(vpmap, target(h_side2, tmesh)) ); - CGAL_assertion( get(vpmap, target(h_side1, tmesh)) == get(vpmap, source(h_side2, tmesh)) ); - - // backup target vertex - vertex_descriptor vertex_to_remove = target(h_side1, tmesh); - if (vertex_to_remove!=*ref_vertices.second) - { - vertex_descriptor replacement_vertex = source(h_side2, tmesh); - // replace the incident vertex - for(halfedge_descriptor hd : halfedges_around_target(h_side1, tmesh)) - set_target(hd, replacement_vertex, tmesh); - } - - // prev side2 hedge for next loop - halfedge_descriptor h_side2_for_next_turn = prev(h_side2, tmesh); - - // replace the opposite of h_side1 by h_side2 - halfedge_descriptor opposite_h_side1 = opposite( h_side1, tmesh); - face_descriptor the_face = face(opposite_h_side1, tmesh); - set_face(h_side2, the_face, tmesh); - - if (the_face!=GT::null_face()) - set_halfedge(the_face, h_side2, tmesh); - - set_next(h_side2, next(opposite_h_side1, tmesh), tmesh); - set_next(prev(opposite_h_side1, tmesh), h_side2, tmesh); - - // take the next hedges - edge_descriptor edge_to_remove = edge(h_side1, tmesh); - h_side1 = next(h_side1, tmesh); - - // now remove the extra edge - remove_edge(edge_to_remove, tmesh); - - // ... and the extra vertex if it's not the second reference - if (vertex_to_remove==*ref_vertices.second) - { - // update the halfedge pointer of the last vertex (others were already from side 2) - CGAL_assertion( target(opposite(h_side2, tmesh), tmesh) == vertex_to_remove ); - set_halfedge(vertex_to_remove, opposite(h_side2, tmesh), tmesh); - break; - } - else - { - remove_vertex(vertex_to_remove , tmesh); - } - - h_side2 = h_side2_for_next_turn; - } - } - } - - return all_removed; -} - -template -bool remove_degenerate_faces(const FaceRange& face_range, - TriangleMesh& tmesh) -{ - return remove_degenerate_faces(face_range, tmesh, parameters::all_default()); -} - -template -bool remove_degenerate_faces(TriangleMesh& tmesh, - const CGAL_PMP_NP_CLASS& np) -{ - return remove_degenerate_faces(faces(tmesh), tmesh, np); -} - -template -bool remove_degenerate_faces(TriangleMesh& tmesh) -{ - return remove_degenerate_faces(tmesh, - CGAL::Polygon_mesh_processing::parameters::all_default()); -} - -/// \ingroup PMP_repairing_grp -/// checks whether a vertex of a polygon mesh is non-manifold. -/// -/// @tparam PolygonMesh a model of `HalfedgeListGraph` -/// -/// @param v a vertex of `pm` -/// @param pm a triangle mesh containing `v` -/// -/// \warning This function has linear runtime with respect to the size of the mesh. -/// -/// \sa `duplicate_non_manifold_vertices()` -/// -/// \return `true` if the vertex is non-manifold, `false` otherwise. -template -bool is_non_manifold_vertex(typename boost::graph_traits::vertex_descriptor v, - const PolygonMesh& pm) -{ - typedef typename boost::graph_traits::halfedge_descriptor halfedge_descriptor; - - typedef CGAL::dynamic_halfedge_property_t Halfedge_property_tag; - typedef typename boost::property_map::const_type Visited_halfedge_map; - - // Dynamic pmaps do not have default initialization values (yet) - Visited_halfedge_map visited_halfedges = get(Halfedge_property_tag(), pm); - for(halfedge_descriptor h : halfedges(pm)) - put(visited_halfedges, h, false); - - std::size_t incident_null_faces_counter = 0; - for(halfedge_descriptor h : halfedges_around_target(v, pm)) - { - put(visited_halfedges, h, true); - if(CGAL::is_border(h, pm)) - ++incident_null_faces_counter; - } - - if(incident_null_faces_counter > 1) - { - // The vertex is the sole connection between two connected components --> non-manifold - return true; - } - - for(halfedge_descriptor h : halfedges(pm)) - { - if(v == target(h, pm)) - { - // Haven't seen that halfedge yet ==> more than one umbrella incident to 'v' ==> non-manifold - if(!get(visited_halfedges, h)) - return true; - } - } - - return false; -} - -namespace internal { - -template -struct Vertex_collector -{ - typedef typename boost::graph_traits::vertex_descriptor vertex_descriptor; - - bool has_old_vertex(const vertex_descriptor v) const { return collections.count(v) != 0; } - - void collect_vertices(vertex_descriptor v1, vertex_descriptor v2) - { - std::vector& verts = collections[v1]; - if(verts.empty()) - verts.push_back(v1); - verts.push_back(v2); - } - - template - void dump(OutputIterator out) - { - typedef std::pair > Pair_type; - for(const Pair_type& p : collections) - *out++ = p.second; - } - - void dump(Emptyset_iterator) { } - - std::map > collections; -}; - -template -typename boost::graph_traits::vertex_descriptor -create_new_vertex_for_sector(typename boost::graph_traits::halfedge_descriptor sector_begin_h, - typename boost::graph_traits::halfedge_descriptor sector_last_h, - PolygonMesh& pm, - const VPM& vpm, - const ConstraintMap& cmap) -{ - typedef typename boost::graph_traits::vertex_descriptor vertex_descriptor; - typedef typename boost::graph_traits::halfedge_descriptor halfedge_descriptor; - - vertex_descriptor old_vd = target(sector_begin_h, pm); - vertex_descriptor new_vd = add_vertex(pm); - put(vpm, new_vd, get(vpm, old_vd)); - - put(cmap, new_vd, true); - - set_halfedge(new_vd, sector_begin_h, pm); - halfedge_descriptor h = sector_begin_h; - do - { - set_target(h, new_vd, pm); - - if(h == sector_last_h) - break; - else - h = prev(opposite(h, pm), pm); - } - while(h != sector_begin_h); // for safety - CGAL_assertion(h != sector_begin_h); - - return new_vd; -} - -template -std::size_t make_umbrella_manifold(typename boost::graph_traits::halfedge_descriptor h, - PolygonMesh& pm, - internal::Vertex_collector& dmap, - const NamedParameters& np) -{ - typedef typename boost::graph_traits::vertex_descriptor vertex_descriptor; - typedef typename boost::graph_traits::halfedge_descriptor halfedge_descriptor; - - using boost::get_param; - using boost::choose_param; - - typedef typename GetVertexPointMap::type VertexPointMap; - VertexPointMap vpm = choose_param(get_param(np, internal_np::vertex_point), - get_property_map(vertex_point, pm)); - - typedef typename boost::lookup_named_param_def // default (no constraint pmap) - >::type VerticesMap; - VerticesMap cmap = choose_param(get_param(np, internal_np::vertex_is_constrained), - Constant_property_map(false)); - - std::size_t nb_new_vertices = 0; - - vertex_descriptor old_v = target(h, pm); - put(cmap, old_v, true); // store the duplicates - - // count the number of borders - int border_counter = 0; - halfedge_descriptor ih = h, done = ih, border_h = h; - do - { - if(is_border(ih, pm)) - { - border_h = ih; - ++border_counter; - } - - ih = prev(opposite(ih, pm), pm); - } - while(ih != done); - - bool is_non_manifold_within_umbrella = (border_counter > 1); - - // if there is a single sector, then simply move the full umbrella to a new vertex, and we're done - if(!is_non_manifold_within_umbrella) - { - // note that since this is marked as a non-manifold vertex, we necessarily need to create - // a new vertex for this umbrella (the main umbrella is not marked as non-manifold) - halfedge_descriptor last_h = opposite(next(h, pm), pm); - vertex_descriptor new_v = create_new_vertex_for_sector(h, last_h, pm, vpm, cmap); - dmap.collect_vertices(old_v, new_v); - nb_new_vertices = 1; - } - // if there is more than one sector, look at each sector and split them away from the main one - else - { - // the first manifold sector, described by two halfedges - halfedge_descriptor sector_start_h = border_h; - CGAL_assertion(is_border(border_h, pm)); - - bool should_stop = false; - bool is_main_sector = true; - do - { - CGAL_assertion(is_border(sector_start_h, pm)); - - // collect the sector and split it away if it must be - halfedge_descriptor sector_last_h = sector_start_h; - do - { - halfedge_descriptor next_h = prev(opposite(sector_last_h, pm), pm); - - if(is_border(next_h, pm)) - break; - - sector_last_h = next_h; - } - while(sector_last_h != sector_start_h); - CGAL_assertion(!is_border(sector_last_h, pm)); - CGAL_assertion(sector_last_h != sector_start_h); - - halfedge_descriptor next_start_h = prev(opposite(sector_last_h, pm), pm); - - // there are multiple CCs incident to this particular vertex, and we should create a new vertex - // if it's not the first umbrella around 'old_v' or not the first sector, but not if it's - // the first umbrella and first sector. - bool must_create_new_vertex = (!is_main_sector || dmap.has_old_vertex(old_v)); - - // In any case, we must set up the next pointer correctly - set_next(sector_start_h, opposite(sector_last_h, pm), pm); - - if(must_create_new_vertex) - { - vertex_descriptor new_v = create_new_vertex_for_sector(sector_start_h, sector_last_h, pm, vpm, cmap); - dmap.collect_vertices(old_v, new_v); - ++nb_new_vertices; - } - - is_main_sector = false; - sector_start_h = next_start_h; - should_stop = (sector_start_h == border_h); - } - while(!should_stop); - } - - return nb_new_vertices; -} - -} // end namespace internal - -/// \ingroup PMP_repairing_grp -/// collects the non-manifold vertices (if any) present in the mesh. A non-manifold vertex `v` is returned -/// via one incident halfedge `h` such that `target(h, pm) = v` for all the umbrellas that `v` apppears in -/// (an umbrella being the set of faces incident to all the halfedges reachable by walking around `v` -/// using `hnext = prev(opposite(h, pm), pm)`, starting from `h`). -/// -/// @tparam PolygonMesh a model of `HalfedgeListGraph` -/// @tparam OutputIterator a model of `OutputIterator` holding objects of type -/// `boost::graph_traits::%halfedge_descriptor` -/// -/// @param pm a triangle mesh -/// @param out the output iterator that collects halfedges incident to `v` -/// -/// \sa `is_non_manifold_vertex()` -/// \sa `duplicate_non_manifold_vertices()` -/// -/// \return the output iterator. -template -OutputIterator non_manifold_vertices(const PolygonMesh& pm, - OutputIterator out) -{ - // Non-manifoldness can appear either: - // - if 'pm' is pinched at a vertex. While traversing the incoming halfedges at this vertex, - // we will meet strictly more than one border halfedge. - // - if there are multiple umbrellas around a vertex. In that case, we will find a non-visited - // halfedge that has for target a vertex that is already visited. - - typedef typename boost::graph_traits::vertex_descriptor vertex_descriptor; - typedef typename boost::graph_traits::halfedge_descriptor halfedge_descriptor; - - typedef CGAL::dynamic_vertex_property_t Vertex_property_tag; - typedef typename boost::property_map::const_type Visited_vertex_map; - typedef CGAL::dynamic_halfedge_property_t Halfedge_property_tag; - typedef typename boost::property_map::const_type Visited_halfedge_map; - - Visited_vertex_map visited_vertices = get(Vertex_property_tag(), pm); - Visited_halfedge_map visited_halfedges = get(Halfedge_property_tag(), pm); - - // Dynamic pmaps do not have default initialization values (yet) - for(vertex_descriptor v : vertices(pm)) - put(visited_vertices, v, false); - for(halfedge_descriptor h : halfedges(pm)) - put(visited_halfedges, h, false); - - for(halfedge_descriptor h : halfedges(pm)) - { - // If 'h' is not visited yet, we walk around the target of 'h' and mark these - // halfedges as visited. Thus, if we are here and the target is already marked as visited, - // it means that the vertex is non manifold. - if(!get(visited_halfedges, h)) - { - put(visited_halfedges, h, true); - bool is_non_manifold = false; - - vertex_descriptor vd = target(h, pm); - if(get(visited_vertices, vd)) // already seen this vertex, but not from this star - is_non_manifold = true; - - put(visited_vertices, vd, true); - - // While walking the star of this halfedge, if we meet a border halfedge more than once, - // it means the mesh is pinched and we are also in the case of a non-manifold situation - halfedge_descriptor ih = h, done = ih; - int border_counter = 0; - do - { - put(visited_halfedges, ih, true); - if(is_border(ih, pm)) - ++border_counter; - - ih = prev(opposite(ih, pm), pm); - } - while(ih != done); - - if(border_counter > 1) - is_non_manifold = true; - - if(is_non_manifold) - *out++ = h; - } - } - - return out; -} - -/// \ingroup PMP_repairing_grp -/// duplicates all the non-manifold vertices of the input mesh. -/// -/// @tparam PolygonMesh a model of `HalfedgeListGraph` and `MutableHalfedgeGraph` -/// @tparam NamedParameters a sequence of \ref pmp_namedparameters "Named Parameters" -/// -/// @param pm the surface mesh to be repaired -/// @param np optional \ref pmp_namedparameters "Named Parameters" described below -/// -/// \cgalNamedParamsBegin -/// \cgalParamBegin{vertex_point_map} the property map with the points associated to the vertices of `pmesh`. -/// The type of this map is model of `ReadWritePropertyMap`. -/// If this parameter is omitted, an internal property map for -/// `CGAL::vertex_point_t` should be available in `PolygonMesh` -/// \cgalParamEnd -/// \cgalParamBegin{vertex_is_constrained_map} a writable property map with `vertex_descriptor` -/// as key and `bool` as `value_type`. `put(pmap, v, true)` will be called for each duplicated -/// vertices, as well as the original non-manifold vertex in the input mesh. -/// \cgalParamEnd -/// \cgalParamBegin{output_iterator} a model of `OutputIterator` with value type -/// `std::vector`. The first vertex of each vector is a non-manifold vertex -/// of the input mesh, followed by the new vertices that were created to fix this precise -/// non-manifold configuration. -/// \cgalParamEnd -/// \cgalNamedParamsEnd -/// -/// \return the number of vertices created. -template -std::size_t duplicate_non_manifold_vertices(PolygonMesh& pm, - const NamedParameters& np) -{ - using boost::get_param; - using boost::choose_param; - - typedef boost::graph_traits GT; - typedef typename GT::halfedge_descriptor halfedge_descriptor; - - typedef typename boost::lookup_named_param_def < - internal_np::output_iterator_t, - NamedParameters, - Emptyset_iterator - > ::type Output_iterator; - - Output_iterator out = choose_param(get_param(np, internal_np::output_iterator), Emptyset_iterator()); - - std::vector non_manifold_cones; - non_manifold_vertices(pm, std::back_inserter(non_manifold_cones)); - - internal::Vertex_collector dmap; - std::size_t nb_new_vertices = 0; - if(!non_manifold_cones.empty()) - { - for(halfedge_descriptor h : non_manifold_cones) - nb_new_vertices += internal::make_umbrella_manifold(h, pm, dmap, np); - - dmap.dump(out); - } - - return nb_new_vertices; -} - -template -std::size_t duplicate_non_manifold_vertices(PolygonMesh& pm) -{ - return duplicate_non_manifold_vertices(pm, parameters::all_default()); -} /// \ingroup PMP_repairing_grp /// removes the isolated vertices from any polygon mesh. @@ -2350,602 +53,18 @@ std::size_t remove_isolated_vertices(PolygonMesh& pmesh) for(vertex_descriptor v : vertices(pmesh)) { - if (CGAL::halfedges_around_target(v, pmesh).first - == CGAL::halfedges_around_target(v, pmesh).second) + if(CGAL::halfedges_around_target(v, pmesh).first == CGAL::halfedges_around_target(v, pmesh).second) to_be_removed.push_back(v); } + std::size_t nb_removed = to_be_removed.size(); for(vertex_descriptor v : to_be_removed) - { remove_vertex(v, pmesh); - } + return nb_removed; } -/// \cond SKIP_IN_MANUAL -template -std::pair< bool, bool > -remove_self_intersections_one_step(TriangleMesh& tm, - std::set& faces_to_remove, - VertexPointMap& vpmap, - int step, - bool preserve_genus, - bool verbose) -{ - std::set faces_to_remove_copy = faces_to_remove; - - if (verbose) - std::cout << "DEBUG: running remove_self_intersections_one_step, step " << step - << " with " << faces_to_remove.size() << " intersecting faces\n"; - - CGAL_assertion(tm.is_valid()); - - typedef boost::graph_traits graph_traits; - typedef typename graph_traits::vertex_descriptor vertex_descriptor; - typedef typename graph_traits::edge_descriptor edge_descriptor; - typedef typename graph_traits::halfedge_descriptor halfedge_descriptor; - - bool something_was_done = false; // indicates if a region was successfully remeshed - bool all_fixed = true; // indicates if all removal went well - // indicates if a removal was not possible because the region handle has - // some boundary cycle of halfedges - bool topology_issue = false; - - if (verbose) - { - std::cout << " DEBUG: is_valid in one_step(tm)? "; - std::cout.flush(); - std::cout << is_valid_polygon_mesh(tm) << "\n"; - } - - if(!faces_to_remove.empty()){ - - while(!faces_to_remove.empty()) - { - // Process a connected component of faces to remove. - // collect all the faces from the connected component - std::set cc_faces; - std::vector queue(1, *faces_to_remove.begin()); // temporary queue - cc_faces.insert(queue.back()); - while(!queue.empty()) - { - face_descriptor top=queue.back(); - queue.pop_back(); - halfedge_descriptor h = halfedge(top,tm); - for (int i=0;i<3; ++i) - { - face_descriptor adjacent_face = face( opposite(h, tm), tm ); - if ( adjacent_face!=boost::graph_traits::null_face()) - { - if (faces_to_remove.count(adjacent_face) != 0 && - cc_faces.insert(adjacent_face).second) - queue.push_back(adjacent_face); - } - h = next(h, tm); - } - } - - // expand the region to be filled - if (step > 0) - expand_face_selection(cc_faces, tm, step, - make_boolean_property_map(cc_faces), - Emptyset_iterator()); - - // try to compactify the selection region by also selecting all the faces included - // in the bounding box of the initial selection - std::vector stack_for_expension; - Bbox_3 bb; - for(face_descriptor fd : cc_faces) - { - for(halfedge_descriptor h : halfedges_around_face(halfedge(fd, tm), tm)) - { - bb += get(vpmap, target(h, tm)).bbox(); - face_descriptor nf = face(opposite(h, tm), tm); - if (nf != boost::graph_traits::null_face() && - cc_faces.count(nf)==0) - { - stack_for_expension.push_back(opposite(h, tm)); - } - } - } - - while(!stack_for_expension.empty()) - { - halfedge_descriptor h=stack_for_expension.back(); - stack_for_expension.pop_back(); - if ( cc_faces.count(face(h,tm))==1) continue; - if ( do_overlap(bb, get(vpmap, target(next(h, tm), tm)).bbox()) ) - { - cc_faces.insert(face(h,tm)); - halfedge_descriptor candidate = opposite(next(h, tm), tm); - if ( face(candidate, tm) != boost::graph_traits::null_face() ) - stack_for_expension.push_back( candidate ); - candidate = opposite(prev(h, tm), tm); - if ( face(candidate, tm) != boost::graph_traits::null_face() ) - stack_for_expension.push_back( candidate ); - } - } - - // remove faces from the set to process - for(face_descriptor f : cc_faces) - faces_to_remove.erase(f); - - if (cc_faces.size()==1) continue; // it is a triangle nothing better can be done - - //Check for non-manifold vertices in the selection and remove them by selecting all incident faces: - // extract the set of halfedges that is on the boundary of the holes to be - // made. In addition, we make sure no hole to be created contains a vertex - // visited more than once along a hole border (pinched surface) - // We save the size of boundary_hedges to make sur halfedges added - // from non_filled_hole are not removed. - bool non_manifold_vertex_remaining_in_selection = false; - do{ - bool non_manifold_vertex_removed = false; //here non-manifold is for the 1D polyline - std::vector boundary_hedges; - for(face_descriptor fh : cc_faces) - { - halfedge_descriptor h = halfedge(fh,tm); - for (int i=0;i<3; ++i) - { - if ( is_border( opposite(h, tm), tm) || - cc_faces.count( face( opposite(h, tm), tm) ) == 0) - { - boundary_hedges.push_back(h); - } - h=next(h, tm); - } - } - - // detect vertices visited more than once along - // a hole border. We then remove all faces incident - // to such a vertex to force the removal of the vertex. - // Actually even if two holes are sharing a vertex, this - // vertex will be removed. It is not needed but since - // we do not yet have one halfedge per hole it is simpler - // and does not harm - std::set border_vertices; - for(halfedge_descriptor h : boundary_hedges) - { - if (!border_vertices.insert(target(h,tm)).second){ - bool any_face_added = false; - for(halfedge_descriptor hh : halfedges_around_target(h,tm)){ - if (!is_border(hh, tm)) - { - any_face_added |= cc_faces.insert(face(hh, tm)).second; // add the face to the current selection - faces_to_remove.erase(face(hh, tm)); - } - } - if (any_face_added) - non_manifold_vertex_removed=true; - else - non_manifold_vertex_remaining_in_selection=true; - } - } - - if (!non_manifold_vertex_removed) - break; - } - while(true); - - if (preserve_genus && non_manifold_vertex_remaining_in_selection) - { - topology_issue = true; - if(verbose) - std::cout << " DEBUG: CC not handled due to the presence at least one non-manifold vertex\n"; - continue; // cannot replace a patch containing a nm vertex by a disk - } - - // before running this function if preserve_genus=false, we duplicated - // all of them - CGAL_assertion( !non_manifold_vertex_remaining_in_selection ); - - - // Collect halfedges on the boundary of the region to be selected - // (pointing inside the domain to be remeshed) - std::vector cc_border_hedges; - for(face_descriptor fd : cc_faces) - { - halfedge_descriptor h = halfedge(fd, tm); - for (int i=0; i<3;++i) - { - if ( is_border(opposite(h, tm), tm) || - cc_faces.count( face(opposite(h, tm), tm) )== 0) - { - cc_border_hedges.push_back(h); - } - h=next(h, tm); - } - } - - if(!is_selection_a_topological_disk(cc_faces, tm)) - { - // check if the selection contains cycles of border halfedges - bool only_border_edges = true; - std::set mesh_border_hedge; - - for(halfedge_descriptor h : cc_border_hedges) - { - if ( !is_border(opposite(h, tm), tm) ) - only_border_edges = false; - else - mesh_border_hedge.insert( opposite(h, tm) ); - } - int nb_cycles=0; - while(!mesh_border_hedge.empty()) - { - // we must count the number of cycle of boundary edges - halfedge_descriptor h_b = *mesh_border_hedge.begin(), h=h_b; - mesh_border_hedge.erase( mesh_border_hedge.begin() ); - do{ - h=next(h, tm); - if (h==h_b) - { - // found a cycle - ++nb_cycles; - break; - } - else - { - typename std::set::iterator it = - mesh_border_hedge.find(h); - if ( it == mesh_border_hedge.end() ) - break; // not a cycle - mesh_border_hedge.erase(it); - } - }while(true); - } - - if(nb_cycles > (only_border_edges ? 1 : 0) ) - { - if(verbose) - std::cout << " DEBUG: CC not handled due to the presence of " - << nb_cycles << " of boundary edges\n"; - topology_issue = true; - continue; - } - else - { - if (preserve_genus) - { - if(verbose) - std::cout << " DEBUG: CC not handled because it is not a topological disk (preserve_genus=true)\n"; - all_fixed = false; - continue; - } - // count the number of cycles of halfedges of the boundary - std::map bhs; - for(halfedge_descriptor h : cc_border_hedges) - { - bhs[source(h, tm)]=target(h, tm); - } - int nbc=0; - while(!bhs.empty()) - { - ++nbc; - std::pair top=*bhs.begin(); - bhs.erase(bhs.begin()); - do - { - typename std::map::iterator - it_find = bhs.find(top.second); - if (it_find == bhs.end()) break; - top = *it_find; - bhs.erase(it_find); - } - while(true); - } - if (nbc!=1){ - if(verbose) - std::cout << " DEBUG: CC not handled because it is not a topological disk(" - << nbc << " boundary cycles)\n"; - all_fixed = false; - continue; - } - else - { - if(verbose) - std::cout << " DEBUG: CC that is not a topological disk but has only one boundary cycle(preserve_genus=false)\n"; - } - } - } - - // sort halfedges so that they describe the sequence - // of halfedges of the hole to be made - CGAL_assertion( cc_border_hedges.size() > 2 ); - for(std::size_t i=0; i < cc_border_hedges.size()-2; ++i) - { - vertex_descriptor tgt = target(cc_border_hedges[i], tm); - for(std::size_t j=i+1; j cc_interior_vertices; - std::set cc_interior_edges; - - // first collect all vertices and edges incident to the faces to remove - for(face_descriptor fh : cc_faces) - { - for(halfedge_descriptor h : halfedges_around_face(halfedge(fh,tm),tm)) - { - if (halfedge(target(h, tm), tm)==h) // limit the number of insertions - cc_interior_vertices.insert(target(h, tm)); - cc_interior_edges.insert(edge(h,tm)); - } - } - // and then remove those on the boundary - for(halfedge_descriptor h : cc_border_hedges) - { - cc_interior_vertices.erase(target(h, tm)); - cc_interior_edges.erase(edge(h,tm)); - } - - if (verbose) - { - std::cout << " DEBUG: is_valid(tm) in one_step, before mesh changes? "; - std::cout << is_valid_polygon_mesh(tm) << std::endl; - } - - //try hole_filling. - typedef CGAL::Triple Face_indices; - typedef typename boost::property_traits::value_type Point; - std::vector hole_points, third_points; - hole_points.reserve(cc_border_hedges.size()); - third_points.reserve(cc_border_hedges.size()); - std::vector border_vertices; - for(halfedge_descriptor h : cc_border_hedges) - { - vertex_descriptor v = source(h, tm); - hole_points.push_back( get(vpmap, v) ); - border_vertices.push_back(v); - third_points.push_back(get(vpmap, target(next(opposite(h, tm), tm), tm))); // TODO fix me for mesh border edges - } - CGAL_assertion(hole_points.size() >= 3); - - // try to triangulate the hole using default parameters - //(using Delaunay search space if CGAL_HOLE_FILLING_DO_NOT_USE_DT3 is not defined) - std::vector patch; - if (hole_points.size()>3) - triangulate_hole_polyline(hole_points, - third_points, - std::back_inserter(patch)); - else - patch.push_back(Face_indices(0,1,2)); // trivial hole filling - - if(patch.empty()) - { -#ifndef CGAL_HOLE_FILLING_DO_NOT_USE_DT3 - if (verbose) - std::cout << " DEBUG: Failed to fill a hole using Delaunay search space.\n"; - triangulate_hole_polyline(hole_points, - third_points, - std::back_inserter(patch), - parameters::use_delaunay_triangulation(false)); -#endif - if (patch.empty()) - { - if (verbose) - std::cout << " DEBUG: Failed to fill a hole using the whole search space.\n"; - all_fixed = false; - continue; - } - } - - // make sure that the hole filling is valid, we check that no - // edge already in the mesh is present in patch. - bool non_manifold_edge_found = false; - for(const Face_indices& triangle : patch) - { - std::array edges = - make_array(triangle.first, triangle.second, - triangle.second, triangle.third, - triangle.third, triangle.first); - for (int k=0; k<3; ++k) - { - int vi=edges[2*k], vj=edges[2*k+1]; - // ignore boundary edges - if (vi+1==vj || (vj==0 && static_cast(vi)==border_vertices.size()-1) ) - continue; - halfedge_descriptor h = halfedge(border_vertices[vi], border_vertices[vj], tm).first; - if (h!=boost::graph_traits::null_halfedge() && - cc_interior_edges.count(edge(h, tm))==0) - { - non_manifold_edge_found=true; - break; - } - } - if (non_manifold_edge_found) break; - } - if (non_manifold_edge_found) - { - if (verbose) - std::cout << " DEBUG: Triangulation produced is non-manifold when plugged into the mesh.\n"; - all_fixed = false; - continue; - } - - // plug the new triangles in the mesh, reusing previous edges and faces - std::vector edge_stack(cc_interior_edges.begin(), cc_interior_edges.end()); - std::vector face_stack(cc_faces.begin(), cc_faces.end()); - - std::map< std::pair, halfedge_descriptor > halfedge_map; - int i=0; - // register border halfedges - for(halfedge_descriptor h : cc_border_hedges) - { - int j = static_cast( std::size_t(i+1)%cc_border_hedges.size() ); - halfedge_map.insert(std::make_pair( std::make_pair(i, j), h) ); - set_halfedge(target(h, tm), h, tm); // update vertex halfedge pointer - CGAL_assertion( border_vertices[i] == source(h, tm) && - border_vertices[j] == target(h, tm) ); - ++i; - } - - std::vector hedges; - hedges.reserve(4); - face_descriptor f = boost::graph_traits::null_face(); - for(const Face_indices& triangle : patch) - { - // get the new face - if (face_stack.empty()) - f=add_face(tm); - else - { - f=face_stack.back(); - face_stack.pop_back(); - } - - std::array indices = - make_array( triangle.first, - triangle.second, - triangle.third, - triangle.first ); - for (int i=0; i<3; ++i) - { - // get the corresponding halfedge (either a new one or an already created) - typename std::map< std::pair , halfedge_descriptor >::iterator insert_res = - halfedge_map.insert( - std::make_pair( std::make_pair(indices[i], indices[i+1]), - boost::graph_traits::null_halfedge() ) ).first; - if (insert_res->second == boost::graph_traits::null_halfedge()) - { - if (edge_stack.empty()) - insert_res->second = halfedge(add_edge(tm), tm); - else - { - insert_res->second = halfedge(edge_stack.back(), tm); - edge_stack.pop_back(); - } - - halfedge_map[std::make_pair(indices[i+1], indices[i])] = - opposite(insert_res->second, tm); - } - hedges.push_back(insert_res->second); - } - hedges.push_back(hedges.front()); - // update halfedge connections + face pointers - for(int i=0; i<3;++i) - { - set_next(hedges[i], hedges[i+1], tm); - set_face(hedges[i], f, tm); - set_target(hedges[i], border_vertices[indices[i+1]], tm); - } - set_halfedge(f, hedges[0], tm); - hedges.clear(); - } - - // now remove remaining edges, - for(edge_descriptor e : edge_stack) - remove_edge(e, tm); - // vertices, - for(vertex_descriptor vh : cc_interior_vertices) - remove_vertex(vh, tm); - // and remaning faces - for(face_descriptor f : face_stack) - remove_face(f, tm); - - if (verbose) - std::cout << " DEBUG: " << cc_faces.size() << " triangles removed, " - << patch.size() << " created\n"; - - CGAL_assertion(is_valid_polygon_mesh(tm)); - - something_was_done = true; - } - } - if (!something_was_done) - { - faces_to_remove.swap(faces_to_remove_copy); - if (verbose) - std::cout<<" DEBUG: Nothing was changed during this step, self-intersections won't be recomputed."< -bool remove_self_intersections(TriangleMesh& tm, const NamedParameters& np) -{ - typedef boost::graph_traits graph_traits; - typedef typename graph_traits::face_descriptor face_descriptor; - - // named parameter extraction - typedef typename GetVertexPointMap::type VertexPointMap; - VertexPointMap vpm = boost::choose_param(boost::get_param(np, internal_np::vertex_point), - get_property_map(vertex_point, tm)); - - const int max_steps = boost::choose_param(boost::get_param(np, internal_np::number_of_iterations), 7); - bool verbose = boost::choose_param(boost::get_param(np, internal_np::verbosity_level), 0) > 0; - bool preserve_genus = boost::choose_param(boost::get_param(np, internal_np::preserve_genus), true); - - if (verbose) - std::cout << "DEBUG: Starting remove_self_intersections, is_valid(tm)? " << is_valid_polygon_mesh(tm) << "\n"; - - CGAL_precondition_code(std::set degenerate_face_set;) - CGAL_precondition_code(degenerate_faces(face_range, tmesh, - std::inserter(degenerate_face_set, degenerate_face_set.begin()), np);) - CGAL_precondition(degenerate_face_set.empty()); - - if (!preserve_genus) - duplicate_non_manifold_vertices(tm, np); - - // Look for self-intersections in the polyhedron and remove them - int step=-1; - bool all_fixed = true; // indicates if the filling of all created holes went fine - bool topology_issue = false; // indicates if some boundary cycles of edges are blocking the fixing - std::set faces_to_remove; - while( ++step Face_pair; - std::vector self_inter; - // TODO : possible optimization to reduce the range to check with the bbox - // of the previous patches or something. - self_intersections(tm, std::back_inserter(self_inter)); - - for(Face_pair fp : self_inter) - { - faces_to_remove.insert(fp.first); - faces_to_remove.insert(fp.second); - } - } - - if ( faces_to_remove.empty() && all_fixed){ - if (verbose) - std::cout<<"DEBUG: There is no more face to remove."< -bool remove_self_intersections(TriangleMesh& tm) -{ - return remove_self_intersections(tm, parameters::all_default()); -} -/// \endcond - -} } // end of CGAL::Polygon_mesh_processing +} // namespace Polygon_mesh_processing +} // namespace CGAL #endif // CGAL_POLYGON_MESH_PROCESSING_REPAIR_H diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/shape_predicates.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/shape_predicates.h index 6201e81046e..7b04e43bbc4 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/shape_predicates.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/shape_predicates.h @@ -32,6 +32,7 @@ #include #include +#include #include #include @@ -94,6 +95,81 @@ bool is_degenerate_edge(typename boost::graph_traits::edge_descript return is_degenerate_edge(e, pm, parameters::all_default()); } +/// \ingroup PMP_repairing_grp +/// collects the degenerate edges within a given range of edges. +/// +/// @tparam EdgeRange a model of `Range` with value type `boost::graph_traits::%edge_descriptor` +/// @tparam TriangleMesh a model of `HalfedgeGraph` +/// @tparam NamedParameters a sequence of \ref pmp_namedparameters "Named Parameters" +/// +/// @param edges a subset of edges of `tm` +/// @param tm a triangle mesh +/// @param out an output iterator in which the degenerate edges are written +/// @param np optional \ref pmp_namedparameters "Named Parameters" described below +/// +/// \cgalNamedParamsBegin +/// \cgalParamBegin{vertex_point_map} the property map with the points associated to the vertices of `tm`. +/// The type of this map is model of `ReadWritePropertyMap`. +/// If this parameter is omitted, an internal property map for +/// `CGAL::vertex_point_t` should be available in `TriangleMesh` +/// \cgalParamEnd +/// \cgalParamBegin{geom_traits} a geometric traits class instance. +/// The traits class must provide the nested type `Point_3`, +/// and the nested functor `Equal_3` to check whether two points are identical. +/// \cgalParamEnd +/// \cgalNamedParamsEnd +template +OutputIterator degenerate_edges(const EdgeRange& edges, + const TriangleMesh& tm, + OutputIterator out, + const NamedParameters& np) +{ + typedef typename boost::graph_traits::edge_descriptor edge_descriptor; + + for(edge_descriptor ed : edges) + if(is_degenerate_edge(ed, tm, np)) + *out++ = ed; + + return out; +} + +template +OutputIterator degenerate_edges(const EdgeRange& edges, + const TriangleMesh& tm, + OutputIterator out, + typename boost::enable_if< + typename boost::has_range_iterator + >::type* = 0) +{ + return degenerate_edges(edges, tm, out, CGAL::parameters::all_default()); +} + +/// \ingroup PMP_repairing_grp +/// calls the function `degenerate_edges()` with the range: `edges(tm)`. +/// +/// See above for the comprehensive description of the parameters. +/// +template +OutputIterator degenerate_edges(const TriangleMesh& tm, + OutputIterator out, + const NamedParameters& np +#ifndef DOXYGEN_RUNNING + , + typename boost::disable_if< + boost::has_range_iterator + >::type* = 0 +#endif + ) +{ + return degenerate_edges(edges(tm), tm, out, np); +} + +template +OutputIterator degenerate_edges(const TriangleMesh& tm, OutputIterator out) +{ + return degenerate_edges(edges(tm), tm, out, CGAL::parameters::all_default()); +} + /// \ingroup PMP_repairing_grp /// checks whether a triangle face is degenerate. /// A triangle face is considered degenerate if the geometric positions of its vertices are collinear. @@ -151,6 +227,83 @@ bool is_degenerate_triangle_face(typename boost::graph_traits::fac return CGAL::Polygon_mesh_processing::is_degenerate_triangle_face(f, tm, parameters::all_default()); } +/// \ingroup PMP_repairing_grp +/// collects the degenerate faces within a given range of faces. +/// +/// @tparam FaceRange a model of `Range` with value type `boost::graph_traits::%face_descriptor` +/// @tparam TriangleMesh a model of `FaceGraph` +/// @tparam NamedParameters a sequence of \ref pmp_namedparameters "Named Parameters" +/// +/// @param faces a subset of faces of `tm` +/// @param tm a triangle mesh +/// @param out an output iterator in which the degenerate faces are put +/// @param np optional \ref pmp_namedparameters "Named Parameters" described below +/// +/// \cgalNamedParamsBegin +/// \cgalParamBegin{vertex_point_map} the property map with the points associated to the vertices of `tm`. +/// The type of this map is model of `ReadWritePropertyMap`. +/// If this parameter is omitted, an internal property map for +/// `CGAL::vertex_point_t` should be available in `TriangleMesh` +/// \cgalParamEnd +/// \cgalParamBegin{geom_traits} a geometric traits class instance. +/// The traits class must provide the nested functor `Collinear_3` +/// to check whether three points are collinear. +/// \cgalParamEnd +/// \cgalNamedParamsEnd +/// +template +OutputIterator degenerate_faces(const FaceRange& faces, + const TriangleMesh& tm, + OutputIterator out, + const NamedParameters& np) +{ + typedef typename boost::graph_traits::face_descriptor face_descriptor; + + for(face_descriptor fd : faces) + { + if(is_degenerate_triangle_face(fd, tm, np)) + *out++ = fd; + } + return out; +} + +template +OutputIterator degenerate_faces(const FaceRange& faces, + const TriangleMesh& tm, + OutputIterator out, + typename boost::enable_if< + boost::has_range_iterator + >::type* = 0) +{ + return degenerate_faces(faces, tm, out, CGAL::parameters::all_default()); +} + +/// \ingroup PMP_repairing_grp +/// calls the function `degenerate_faces()` with the range: `faces(tm)`. +/// +/// See above for the comprehensive description of the parameters. +/// +template +OutputIterator degenerate_faces(const TriangleMesh& tm, + OutputIterator out, + const NamedParameters& np +#ifndef DOXYGEN_RUNNING + , + typename boost::disable_if< + boost::has_range_iterator + >::type* = 0 +#endif + ) +{ + return degenerate_faces(faces(tm), tm, out, np); +} + +template +OutputIterator degenerate_faces(const TriangleMesh& tm, OutputIterator out) +{ + return degenerate_faces(faces(tm), tm, out, CGAL::parameters::all_default()); +} + /// \ingroup PMP_repairing_grp /// checks whether a triangle face is needle. /// A triangle is said to be a needle if its longest edge is much longer than its shortest edge. From 68e80471b2f1263a0b1e33f6da1d5bb5cbe9845a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mael=20Rouxel-Labb=C3=A9?= Date: Tue, 30 Jul 2019 11:43:29 +0200 Subject: [PATCH 05/56] Revert some changes about passing a face range to remove_self_intersections --- .../remove_self_intersections.h | 30 +++++-------------- 1 file changed, 8 insertions(+), 22 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/remove_self_intersections.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/remove_self_intersections.h index ce6857c601b..d2156452619 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/remove_self_intersections.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/remove_self_intersections.h @@ -585,9 +585,8 @@ remove_self_intersections_one_step(TriangleMesh& tmesh, return std::make_pair(all_fixed, topology_issue); } -template -bool remove_self_intersections(const FaceRange& face_range, - TriangleMesh& tmesh, +template +bool remove_self_intersections(TriangleMesh& tmesh, const NamedParameters& np) { typedef boost::graph_traits graph_traits; @@ -606,18 +605,18 @@ bool remove_self_intersections(const FaceRange& face_range, std::cout << "DEBUG: Starting remove_self_intersections, is_valid(tmesh)? " << is_valid_polygon_mesh(tmesh) << "\n"; CGAL_precondition_code(std::set degenerate_face_set;) - CGAL_precondition_code(degenerate_faces(face_range, tmesh, - std::inserter(degenerate_face_set, degenerate_face_set.begin()), np);) + CGAL_precondition_code(degenerate_faces(tmesh, std::inserter(degenerate_face_set, degenerate_face_set.begin()), np);) CGAL_precondition(degenerate_face_set.empty()); if(!preserve_genus) duplicate_non_manifold_vertices(tmesh, np); - // Look for self-intersections in the polyhedron and remove them + // Look for self-intersections in the mesh and remove them int step = -1; bool all_fixed = true; // indicates if the filling of all created holes went fine bool topology_issue = false; // indicates if some boundary cycles of edges are blocking the fixing std::set faces_to_remove; + while(++step self_inter; // TODO : possible optimization to reduce the range to check with the bbox // of the previous patches or something. - self_intersections(face_range, tmesh, std::back_inserter(self_inter)); + self_intersections(tmesh, std::back_inserter(self_inter)); for(const Face_pair& fp : self_inter) { @@ -644,6 +643,7 @@ bool remove_self_intersections(const FaceRange& face_range, std::tie(all_fixed, topology_issue) = remove_self_intersections_one_step(tmesh, faces_to_remove, vpm, step, preserve_genus, verbose); + if(all_fixed && topology_issue) { if(verbose) @@ -656,24 +656,10 @@ bool remove_self_intersections(const FaceRange& face_range, return step < max_steps; } -template -bool remove_self_intersections(const FaceRange& face_range, - TriangleMesh& tmesh) -{ - return remove_self_intersections(face_range, tmesh, parameters::all_default()); -} - -template -bool remove_self_intersections(TriangleMesh& tmesh, - const CGAL_PMP_NP_CLASS& np) -{ - return remove_self_intersections(faces(tmesh), tmesh, np); -} - template bool remove_self_intersections(TriangleMesh& tmesh) { - return remove_self_intersections(faces(tmesh), tmesh, parameters::all_default()); + return remove_self_intersections(tmesh, parameters::all_default()); } /// \endcond From 7e7abafd08f6ef446fae5644b80fac9cf528a77a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mael=20Rouxel-Labb=C3=A9?= Date: Tue, 30 Jul 2019 13:00:32 +0200 Subject: [PATCH 06/56] Add some code to treat self-intersections locally --- .../remove_self_intersections.h | 186 ++++++++++++++++++ .../self_intersections.h | 1 + 2 files changed, 187 insertions(+) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/remove_self_intersections.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/remove_self_intersections.h index d2156452619..05eacafc64f 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/remove_self_intersections.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/remove_self_intersections.h @@ -64,6 +64,14 @@ remove_self_intersections_one_step(TriangleMesh& tmesh, << " with " << faces_to_remove.size() << " intersecting faces\n"; } + if(!does_self_intersect(faces_to_remove, tmesh, parameters::vertex_point_map(vpmap))) + { + if(verbose) + std::cout << "DEBUG: Range has no self-intersections\n"; + + return std::make_pair(true, false); + } + CGAL_assertion(tmesh.is_valid()); typedef boost::graph_traits graph_traits; @@ -662,6 +670,184 @@ bool remove_self_intersections(TriangleMesh& tmesh) return remove_self_intersections(tmesh, parameters::all_default()); } +template +int identify_self_intersecting_ccs(const FaceContainer& all_intersecting_faces, + TriangleMesh& mesh, + SICCContainer& self_intersecting_ccs) +{ + typedef typename boost::graph_traits::halfedge_descriptor halfedge_descriptor; + typedef typename boost::graph_traits::face_descriptor face_descriptor; + + typedef CGAL::dynamic_face_property_t Face_bool_tag; + typedef typename boost::property_map::type Is_self_intersecting_face_map; + + typedef CGAL::dynamic_face_property_t Face_color_tag; + typedef typename boost::property_map::type SI_CC_face_map; + + std::list si_faces(all_intersecting_faces.begin(), all_intersecting_faces.end()); + + Is_self_intersecting_face_map is_si_fmap = get(Face_bool_tag(), mesh); + SI_CC_face_map colors = get(Face_color_tag(), mesh); + + // multiple iterations means we must reset properties + for(face_descriptor f : faces(mesh)) + { + put(is_si_fmap, f, false); + put(colors, f, 0); + } + + for(face_descriptor f : si_faces) + put(is_si_fmap, f, true); + + int color = 0; + while(!si_faces.empty()) + { + face_descriptor seed_f = si_faces.front(); + si_faces.pop_front(); + + if(get(colors, seed_f) != 0) // 'seed_f' is already colored, nothing to do + continue; + put(colors, seed_f, ++color); + + std::stack to_color; + to_color.push(seed_f); + + while(!to_color.empty()) + { + face_descriptor f = to_color.top(); + to_color.pop(); + + // add neighbors to stack @todo should this be vertex-neighbors and not edge neighbors + for(halfedge_descriptor h : CGAL::halfedges_around_face(halfedge(f, mesh), mesh)) + { + halfedge_descriptor opp_h = opposite(h, mesh); + face_descriptor opp_f = face(opp_h, mesh); + + if(is_border(opp_h, mesh) || !get(is_si_fmap, opp_f)) + continue; + + // 'f' is already colored; nothing to do + if(get(colors, opp_f) != 0) { + CGAL_assertion(get(colors, opp_f) == color); + } else { + put(colors, opp_f, color); + to_color.push(opp_f); + } + } + } + } + + self_intersecting_ccs.resize(color); + for(face_descriptor f : all_intersecting_faces) + { + CGAL_assertion(get(is_si_fmap, f)); + CGAL_assertion(0 < get(colors, f) && get(colors, f) <= color); + self_intersecting_ccs[get(colors, f) - 1].insert(f); // -1 because colors start at '1' + } + + return color; +} + +template +bool remove_self_intersections_locally(TriangleMesh& tmesh, + const NamedParameters& np) +{ + typedef boost::graph_traits graph_traits; + typedef typename graph_traits::face_descriptor face_descriptor; + + // named parameter extraction + typedef typename GetVertexPointMap::type VertexPointMap; + VertexPointMap vpm = boost::choose_param(boost::get_param(np, internal_np::vertex_point), + get_property_map(vertex_point, tmesh)); + + const int max_steps = boost::choose_param(boost::get_param(np, internal_np::number_of_iterations), 7); + bool verbose = boost::choose_param(boost::get_param(np, internal_np::verbosity_level), 0) > 0; + bool preserve_genus = boost::choose_param(boost::get_param(np, internal_np::preserve_genus), true); + + if(verbose) + std::cout << "DEBUG: Starting remove_self_intersections, is_valid(tmesh)? " << is_valid_polygon_mesh(tmesh) << "\n"; + + CGAL_precondition_code(std::set degenerate_face_set;) + CGAL_precondition_code(degenerate_faces(tmesh, std::inserter(degenerate_face_set, degenerate_face_set.begin()), np);) + CGAL_precondition(degenerate_face_set.empty()); + + if(!preserve_genus) + duplicate_non_manifold_vertices(tmesh, np); + + // Look for self-intersections in the mesh and remove them + int step = -1; + bool all_fixed = false; // indicates if the filling of all created holes went fine + bool topology_issue = false; // indicates if some boundary cycles of edges are blocking the fixing + std::set all_intersecting_faces; + + while(++step < max_steps) + { + if(all_fixed) + { + if(verbose) + std::cout << "DEBUG: Fixed all local self-intersections!" << std::endl; + break; + } + + if(all_intersecting_faces.empty()) // the previous round might have been blocked due to topological constraints + { + typedef std::pair Face_pair; + std::vector self_inter; + // TODO : possible optimization to reduce the range to check with the bbox + // of the previous patches or something. + self_intersections(tmesh, std::back_inserter(self_inter)); + + for(const Face_pair& fp : self_inter) + { + all_intersecting_faces.insert(fp.first); + all_intersecting_faces.insert(fp.second); + } + } + + if(verbose) + std::cout << all_intersecting_faces.size() << " faces part of self-intersections" << std::endl; + + if(all_intersecting_faces.empty()) + { + if(verbose) + std::cout << "DEBUG: There is no more face to remove." << std::endl; + break; + } + + std::vector > faces_to_remove_ccs; + identify_self_intersecting_ccs(all_intersecting_faces, tmesh, faces_to_remove_ccs); + std::cout << faces_to_remove_ccs.size() << " CCs to handle" << std::endl; + + all_fixed = true; + + for(std::set& cc : faces_to_remove_ccs) + { + bool local_fixed; + std::tie(local_fixed, topology_issue) = + remove_self_intersections_one_step(tmesh, cc, vpm, step, preserve_genus, verbose); + + if(topology_issue) + { + if(verbose) + std::cout << "DEBUG: Process stopped because of boundary cycles" + " of boundary edges involved in self-intersections.\n"; + + return false; + } + + all_fixed = (all_fixed && local_fixed); + } + } + + return step < max_steps; +} + +template +bool remove_self_intersections_locally(TriangleMesh& tmesh) +{ + return remove_self_intersections_locally(tmesh, parameters::all_default()); +} + /// \endcond } // namespace Polygon_mesh_processing diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/self_intersections.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/self_intersections.h index 8723edaae79..3ea814e94c3 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/self_intersections.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/self_intersections.h @@ -78,6 +78,7 @@ struct Intersect_facets OutputIterator* m_iterator; bool* m_intersected; }; + // typedefs typedef typename Kernel::Segment_3 Segment; typedef typename Kernel::Triangle_3 Triangle; From 96d1cee7a72e960c4c27d9ea496fbbe739ab1989 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mael=20Rouxel-Labb=C3=A9?= Date: Tue, 30 Jul 2019 13:01:10 +0200 Subject: [PATCH 07/56] Refactor a bit 'self_intersections()' ahead of later use (A later would be to consider that two faces intersect if they are e.g. vertex-adjacent) --- .../self_intersections.h | 156 ++++++++++-------- 1 file changed, 85 insertions(+), 71 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/self_intersections.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/self_intersections.h index 3ea814e94c3..58bbf6c7974 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/self_intersections.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/self_intersections.h @@ -86,7 +86,7 @@ struct Intersect_facets typedef typename boost::property_map::const_type Ppmap; // members - const TM& m_tmesh; + const TM& g; const VertexPointMap m_vpmap; mutable OutputIterator m_iterator; mutable bool m_intersected; @@ -96,10 +96,9 @@ struct Intersect_facets typename Kernel::Construct_triangle_3 triangle_functor; typename Kernel::Do_intersect_3 do_intersect_3_functor; - Intersect_facets(const TM& tmesh, OutputIterator it, VertexPointMap vpmap, const Kernel& kernel) : - m_tmesh(tmesh), + g(tmesh), m_vpmap(vpmap), m_iterator(it), m_intersected(false), @@ -109,100 +108,115 @@ struct Intersect_facets do_intersect_3_functor(kernel.do_intersect_3_object()) { } + halfedge_descriptor common_vertex(halfedge_descriptor& bh, halfedge_descriptor ch) const + { + halfedge_descriptor v = boost::graph_traits::null_halfedge(); + + if(target(bh, g) == target(ch, g)) + v = ch; + else if(target(bh, g) == target(next(ch, g), g)) + v = next(ch, g); + else if(target(bh, g) == target(next(next(ch, g), g), g)) + v = next(next(ch, g), g); + + if(v == boost::graph_traits::null_halfedge()) + { + bh = next(bh, g); + if(target(bh, g) == target(ch, g)) + v = ch; + else if(target(bh, g) == target(next(ch, g), g)) + v = next(ch, g); + else if(target(bh, g) == target(next(next(ch, g), g), g)) + v = next(next(ch, g), g); + + if(v == boost::graph_traits::null_halfedge()) + { + bh = next(bh, g); + if(target(bh, g) == target(ch, g)) + v = ch; + else if(target(bh, g) == target(next(ch, g), g)) + v = next(ch, g); + else if(target(bh, g) == target(next(next(ch, g), g), g)) + v = next(next(ch, g), g); + } + } + + return v; + } + void operator()(const Box* b, const Box* c) const { - halfedge_descriptor h = halfedge(b->info(), m_tmesh); - halfedge_descriptor opp_h; + halfedge_descriptor bh = halfedge(b->info(), g); + halfedge_descriptor opp_bh; // check for shared egde - for(unsigned int i=0; i<3; ++i){ - opp_h = opposite(h, m_tmesh); - if(face(opp_h, m_tmesh) == c->info()){ + for(unsigned int i=0; i<3; ++i) + { + opp_bh = opposite(bh, g); + if(face(opp_bh, g) == c->info()) + { // there is an intersection if the four points are coplanar and // the triangles overlap - if(CGAL::coplanar(get(m_vpmap, target(h, m_tmesh)), - get(m_vpmap, target(next(h, m_tmesh), m_tmesh)), - get(m_vpmap, source(h, m_tmesh)), - get(m_vpmap, target(next(opp_h, m_tmesh), m_tmesh))) && - CGAL::coplanar_orientation(get(m_vpmap, source(h, m_tmesh)), - get(m_vpmap, target(h, m_tmesh)), - get(m_vpmap, target(next(h, m_tmesh), m_tmesh)), - get(m_vpmap, target(next(opp_h, m_tmesh), m_tmesh))) - == CGAL::POSITIVE){ + if(CGAL::coplanar(get(m_vpmap, target(bh, g)), + get(m_vpmap, target(next(bh, g), g)), + get(m_vpmap, source(bh, g)), + get(m_vpmap, target(next(opp_bh, g), g))) && + CGAL::coplanar_orientation(get(m_vpmap, source(bh, g)), + get(m_vpmap, target(bh, g)), + get(m_vpmap, target(next(bh, g), g)), + get(m_vpmap, target(next(opp_bh, g), g))) == CGAL::POSITIVE) + { *m_iterator_wrapper++ = std::make_pair(b->info(), c->info()); return; } else { // there is a shared edge but no intersection return; } } - h = next(h, m_tmesh); + + bh = next(bh, g); } // check for shared vertex --> maybe intersection, maybe not - halfedge_descriptor g = halfedge(c->info(),m_tmesh); - halfedge_descriptor v; - - if(target(h,m_tmesh) == target(g,m_tmesh)) - v = g; - if(target(h,m_tmesh) == target(next(g,m_tmesh),m_tmesh)) - v = next(g,m_tmesh); - if(target(h,m_tmesh) == target(next(next(g,m_tmesh),m_tmesh),m_tmesh)) - v = next(next(g,m_tmesh),m_tmesh); - - if(v == halfedge_descriptor()){ - h = next(h,m_tmesh); - if(target(h,m_tmesh) == target(g,m_tmesh)) - v = g; - if(target(h,m_tmesh) == target(next(g,m_tmesh),m_tmesh)) - v = next(g,m_tmesh); - if(target(h,m_tmesh) == target(next(next(g,m_tmesh),m_tmesh),m_tmesh)) - v = next(next(g,m_tmesh),m_tmesh); - if(v == halfedge_descriptor()){ - h = next(h,m_tmesh); - if(target(h,m_tmesh) == target(g,m_tmesh)) - v = g; - if(target(h,m_tmesh) == target(next(g,m_tmesh),m_tmesh)) - v = next(g,m_tmesh); - if(target(h,m_tmesh) == target(next(next(g,m_tmesh),m_tmesh),m_tmesh)) - v = next(next(g,m_tmesh),m_tmesh); - } - } - - if(v != halfedge_descriptor()){ + halfedge_descriptor ch = halfedge(c->info(), g); + halfedge_descriptor v = common_vertex(bh, ch); + if(v != boost::graph_traits::null_halfedge()) + { // found shared vertex: - CGAL_assertion(target(h,m_tmesh) == target(v,m_tmesh)); + CGAL_assertion(target(bh, g) == target(v, g)); + // geometric check if the opposite segments intersect the triangles - Triangle t1 = triangle_functor( get(m_vpmap,target(h,m_tmesh)), - get(m_vpmap, target(next(h,m_tmesh),m_tmesh)), - get(m_vpmap, target(next(next(h,m_tmesh),m_tmesh),m_tmesh))); - Triangle t2 = triangle_functor( get(m_vpmap, target(v,m_tmesh)), - get(m_vpmap, target(next(v,m_tmesh),m_tmesh)), - get(m_vpmap, target(next(next(v,m_tmesh),m_tmesh),m_tmesh))); + // Note that there is no shared edge here, the case was handled above. + Triangle t1 = triangle_functor(get(m_vpmap,target(bh, g)), + get(m_vpmap, target(next(bh, g), g)), + get(m_vpmap, target(next(next(bh, g), g), g))); + Triangle t2 = triangle_functor(get(m_vpmap, target(v, g)), + get(m_vpmap, target(next(v, g), g)), + get(m_vpmap, target(next(next(v, g), g), g))); - Segment s1 = segment_functor( get(m_vpmap, target(next(h,m_tmesh),m_tmesh)), - get(m_vpmap, target(next(next(h,m_tmesh),m_tmesh),m_tmesh))); - Segment s2 = segment_functor( get(m_vpmap, target(next(v,m_tmesh),m_tmesh)), - get(m_vpmap, target(next(next(v,m_tmesh),m_tmesh),m_tmesh))); + Segment s1 = segment_functor(get(m_vpmap, target(next(bh, g), g)), + get(m_vpmap, target(next(next(bh, g), g), g))); + Segment s2 = segment_functor(get(m_vpmap, target(next(v, g), g)), + get(m_vpmap, target(next(next(v, g), g), g))); - if(do_intersect_3_functor(t1,s2)){ + if(do_intersect_3_functor(t1, s2)) *m_iterator_wrapper++ = std::make_pair(b->info(), c->info()); - } else if(do_intersect_3_functor(t2,s1)){ + else if(do_intersect_3_functor(t2, s1)) *m_iterator_wrapper++ = std::make_pair(b->info(), c->info()); - } + return; } // check for geometric intersection - Triangle t1 = triangle_functor( get(m_vpmap, target(h,m_tmesh)), - get(m_vpmap, target(next(h,m_tmesh),m_tmesh)), - get(m_vpmap, target(next(next(h,m_tmesh),m_tmesh),m_tmesh))); - Triangle t2 = triangle_functor( get(m_vpmap, target(g,m_tmesh)), - get(m_vpmap, target(next(g,m_tmesh),m_tmesh)), - get(m_vpmap, target(next(next(g,m_tmesh),m_tmesh),m_tmesh))); - if(do_intersect_3_functor(t1, t2)){ + Triangle t1 = triangle_functor(get(m_vpmap, target(bh, g)), + get(m_vpmap, target(next(bh, g), g)), + get(m_vpmap, target(next(next(bh, g), g), g))); + Triangle t2 = triangle_functor(get(m_vpmap, target(ch, g)), + get(m_vpmap, target(next(ch, g), g)), + get(m_vpmap, target(next(next(ch, g), g), g))); + + if(do_intersect_3_functor(t1, t2)) *m_iterator_wrapper++ = std::make_pair(b->info(), c->info()); - } - } // end operator () + } }; // end struct Intersect_facets struct Throw_at_output { From ce3197a01b756b2ec71d360520a122a9f03bc83e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mael=20Rouxel-Labb=C3=A9?= Date: Sat, 3 Aug 2019 14:48:49 +0200 Subject: [PATCH 08/56] Add a default template parameter value for convenience --- BGL/include/CGAL/boost/graph/named_params_helper.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/BGL/include/CGAL/boost/graph/named_params_helper.h b/BGL/include/CGAL/boost/graph/named_params_helper.h index 90e8bdc08cd..71154790deb 100644 --- a/BGL/include/CGAL/boost/graph/named_params_helper.h +++ b/BGL/include/CGAL/boost/graph/named_params_helper.h @@ -115,7 +115,8 @@ namespace CGAL { namespace Polygon_mesh_processing { - template + template > class GetVertexPointMap { typedef typename property_map_selector::const_type From 35cd0513cb56344101bbdc064a77a3efeb068020 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mael=20Rouxel-Labb=C3=A9?= Date: Sat, 3 Aug 2019 14:51:11 +0200 Subject: [PATCH 09/56] Put a simple check before the more expensive angle computation --- .../internal/Smoothing/mesh_smoothing_impl.h | 28 +++++++++---------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Smoothing/mesh_smoothing_impl.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Smoothing/mesh_smoothing_impl.h index 3d2583b7d25..456ff16e1dc 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Smoothing/mesh_smoothing_impl.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Smoothing/mesh_smoothing_impl.h @@ -95,10 +95,18 @@ public: const halfedge_descriptor h = halfedge(e, mesh_); const halfedge_descriptor opp_h = opposite(h, mesh_); - vertex_descriptor v0 = source(h, mesh_); - vertex_descriptor v1 = target(h, mesh_); - vertex_descriptor v2 = target(next(h, mesh_), mesh_); - vertex_descriptor v3 = target(next(opp_h, mesh_), mesh_); + const vertex_descriptor v0 = source(h, mesh_); + const vertex_descriptor v1 = target(h, mesh_); + const vertex_descriptor v2 = target(next(h, mesh_), mesh_); + const vertex_descriptor v3 = target(next(opp_h, mesh_), mesh_); + + // Don't want to flip if the other diagonal already exists + // @todo remeshing can be used to still flip those + std::pair other_hd_already_exists = edge(v2, v3, mesh_); + if(other_hd_already_exists.second) + return false; + + // not local Delaunay := sum of the opposite angles is greater than pi const Point_ref p0 = get(vpmap_, v0); const Point_ref p1 = get(vpmap_, v1); const Point_ref p2 = get(vpmap_, v2); @@ -107,17 +115,7 @@ public: double alpha = get_radian_angle(Vector(p0 - p2), Vector(p1 - p2), traits_); double beta = get_radian_angle(Vector(p1 - p3), Vector(p0 - p3), traits_); - // not local Delaunay if the sum of the angles is greater than pi - if(alpha + beta <= CGAL_PI) - return false; - - // Don't want to flip if the other diagonal already exists - // @todo remeshing can be used to still flip those - std::pair other_hd_already_exists = edge(v2, v3, mesh_); - if(other_hd_already_exists.second) - return false; - - return true; + return (alpha + beta > CGAL_PI); } template From 814f8c67e56297167e9a6f57d90fa166fabf5a4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mael=20Rouxel-Labb=C3=A9?= Date: Sat, 3 Aug 2019 14:52:35 +0200 Subject: [PATCH 10/56] Fix non-manifold vertex duplication --- .../Polygon_mesh_processing/manifoldness.h | 75 ++++++++++++++----- 1 file changed, 58 insertions(+), 17 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/manifoldness.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/manifoldness.h index ca7f6183c4a..382ac9d8054 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/manifoldness.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/manifoldness.h @@ -104,6 +104,11 @@ struct Vertex_collector typedef typename boost::graph_traits::vertex_descriptor vertex_descriptor; bool has_old_vertex(const vertex_descriptor v) const { return collections.count(v) != 0; } + void tag_old_vertex(const vertex_descriptor v) + { + CGAL_precondition(!has_old_vertex(v)); + collections[v]; + } void collect_vertices(vertex_descriptor v1, vertex_descriptor v2) { @@ -204,16 +209,25 @@ std::size_t make_umbrella_manifold(typename boost::graph_traits::ha while(ih != done); bool is_non_manifold_within_umbrella = (border_counter > 1); - - // if there is a single sector, then simply move the full umbrella to a new vertex, and we're done if(!is_non_manifold_within_umbrella) { - // note that since this is marked as a non-manifold vertex, we necessarily need to create - // a new vertex for this umbrella (the main umbrella is not marked as non-manifold) - halfedge_descriptor last_h = opposite(next(h, pm), pm); - vertex_descriptor new_v = create_new_vertex_for_sector(h, last_h, pm, vpm, cmap); - dmap.collect_vertices(old_v, new_v); - nb_new_vertices = 1; + const bool first_time_meeting_v = !dmap.has_old_vertex(old_v); + if(first_time_meeting_v) + { + // The star is manifold, so if it is the first time we have met that vertex, + // there is nothing to do, we just keep the same vertex. + set_halfedge(old_v, h, pm); // to ensure halfedge(old_v, pm) stays valid + dmap.tag_old_vertex(old_v); // so that we know we have met old_v already, next time, we'll have to duplicate + } + else + { + // This is not the canonical star associated to 'v'. + // Create a new vertex, and move the whole star to that new vertex + halfedge_descriptor last_h = opposite(next(h, pm), pm); + vertex_descriptor new_v = create_new_vertex_for_sector(h, last_h, pm, vpm, cmap); + dmap.collect_vertices(old_v, new_v); + nb_new_vertices = 1; + } } // if there is more than one sector, look at each sector and split them away from the main one else @@ -246,8 +260,8 @@ std::size_t make_umbrella_manifold(typename boost::graph_traits::ha halfedge_descriptor next_start_h = prev(opposite(sector_last_h, pm), pm); // there are multiple CCs incident to this particular vertex, and we should create a new vertex - // if it's not the first umbrella around 'old_v' or not the first sector, but not if it's - // the first umbrella and first sector. + // if it's not the first umbrella around 'old_v' or not the first sector, but only not if it's + // both the first umbrella and first sector. bool must_create_new_vertex = (!is_main_sector || dmap.has_old_vertex(old_v)); // In any case, we must set up the next pointer correctly @@ -259,6 +273,11 @@ std::size_t make_umbrella_manifold(typename boost::graph_traits::ha dmap.collect_vertices(old_v, new_v); ++nb_new_vertices; } + else + { + // Ensure that halfedge(old_v, pm) stays valid + set_halfedge(old_v, sector_start_h, pm); + } is_main_sector = false; sector_start_h = next_start_h; @@ -302,17 +321,25 @@ OutputIterator non_manifold_vertices(const PolygonMesh& pm, typedef typename boost::graph_traits::vertex_descriptor vertex_descriptor; typedef typename boost::graph_traits::halfedge_descriptor halfedge_descriptor; - typedef CGAL::dynamic_vertex_property_t Vertex_property_tag; - typedef typename boost::property_map::const_type Visited_vertex_map; + typedef CGAL::dynamic_vertex_property_t Vertex_bool_tag; + typedef typename boost::property_map::const_type Known_manifold_vertex_map; + typedef CGAL::dynamic_vertex_property_t Vertex_halfedge_tag; + typedef typename boost::property_map::const_type Visited_vertex_map; typedef CGAL::dynamic_halfedge_property_t Halfedge_property_tag; typedef typename boost::property_map::const_type Visited_halfedge_map; - Visited_vertex_map visited_vertices = get(Vertex_property_tag(), pm); + Known_manifold_vertex_map known_nm_vertices = get(Vertex_bool_tag(), pm); + Visited_vertex_map visited_vertices = get(Vertex_halfedge_tag(), pm); Visited_halfedge_map visited_halfedges = get(Halfedge_property_tag(), pm); + halfedge_descriptor null_h = boost::graph_traits::null_halfedge(); + // Dynamic pmaps do not have default initialization values (yet) for(vertex_descriptor v : vertices(pm)) - put(visited_vertices, v, false); + { + put(known_nm_vertices, v, false); + put(visited_vertices, v, null_h); + } for(halfedge_descriptor h : halfedges(pm)) put(visited_halfedges, h, false); @@ -326,11 +353,22 @@ OutputIterator non_manifold_vertices(const PolygonMesh& pm, put(visited_halfedges, h, true); bool is_non_manifold = false; - vertex_descriptor vd = target(h, pm); - if(get(visited_vertices, vd)) // already seen this vertex, but not from this star + vertex_descriptor v = target(h, pm); + + if(get(visited_vertices, v) != null_h) // already seen this vertex, but not from this star + { is_non_manifold = true; - put(visited_vertices, vd, true); + // if this is the second time we visit that vertex and the first star was manifold, we have + // never reported the first star, but we must now + if(!get(known_nm_vertices, v)) + *out++ = get(visited_vertices, v); // that's a halfedge of the first star we've seen 'v' in + } + else + { + // first time we meet this vertex, just mark it so, and keep the halfedge we found the vertex with in memory + put(visited_vertices, v, h); + } // While walking the star of this halfedge, if we meet a border halfedge more than once, // it means the mesh is pinched and we are also in the case of a non-manifold situation @@ -350,7 +388,10 @@ OutputIterator non_manifold_vertices(const PolygonMesh& pm, is_non_manifold = true; if(is_non_manifold) + { *out++ = h; + put(known_nm_vertices, v, true); + } } } From a1e2cba1c9e98d89446449b7ee0324f4a49109b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mael=20Rouxel-Labb=C3=A9?= Date: Sat, 3 Aug 2019 15:02:48 +0200 Subject: [PATCH 11/56] Minor comment change --- .../include/CGAL/Polygon_mesh_processing/manifoldness.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/manifoldness.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/manifoldness.h index 382ac9d8054..0a297ac2116 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/manifoldness.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/manifoldness.h @@ -275,7 +275,7 @@ std::size_t make_umbrella_manifold(typename boost::graph_traits::ha } else { - // Ensure that halfedge(old_v, pm) stays valid + // We are in the first sector and first star, ensure that halfedge(old_v, pm) stays valid set_halfedge(old_v, sector_start_h, pm); } From 994be6a41ca3a666fa277caa1795b3053c922316 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mael=20Rouxel-Labb=C3=A9?= Date: Tue, 6 Aug 2019 16:53:19 +0200 Subject: [PATCH 12/56] Progress on local self intersection removal --- .../remove_self_intersections.h | 823 +++++++++++------- 1 file changed, 522 insertions(+), 301 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/remove_self_intersections.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/remove_self_intersections.h index 05eacafc64f..7408646b94f 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/remove_self_intersections.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/remove_self_intersections.h @@ -24,12 +24,17 @@ #include +#include +#include #include -#include +#include // only for the preconditions #include +#include #include #include +#include +#include #include #include #include @@ -40,22 +45,434 @@ #include #include #include +#include #include #include namespace CGAL { namespace Polygon_mesh_processing { +namespace internal { -/// \cond SKIP_IN_MANUAL -template -std::pair -remove_self_intersections_one_step(TriangleMesh& tmesh, - std::set& faces_to_remove, - VertexPointMap& vpmap, - int step, - bool preserve_genus, - bool verbose) +// @TMP +template +void dump_cc(const FaceContainer& cc_faces, + const TriangleMesh& mesh, + const std::string filename) { + typedef typename boost::graph_traits::face_descriptor face_descriptor; + + std::ofstream out(filename); + out.precision(17); + + out << "OFF\n"; + out << 3*cc_faces.size() << " " << cc_faces.size() << " 0\n"; + + for(const face_descriptor f : cc_faces) + { + out << mesh.point(source(halfedge(f, mesh), mesh)) << "\n"; + out << mesh.point(target(halfedge(f, mesh), mesh)) << "\n"; + out << mesh.point(target(next(halfedge(f, mesh), mesh), mesh)) << "\n"; + } + + int id = 0; + for(const face_descriptor f : cc_faces) + { + CGAL_USE(f); + out << "3 " << id << " " << id+1 << " " << id+2 << "\n"; + id += 3; + } + + out.close(); +} + +template +void replace_face_range_with_patch(const std::vector::vertex_descriptor>& border_vertices, + const std::set::vertex_descriptor>& interior_vertices, + const std::vector::halfedge_descriptor>& border_hedges, + const std::set::edge_descriptor>& interior_edges, + const std::set::face_descriptor>& pol_faces, + const std::vector >& patch, + PolygonMesh& pmesh, + VPM& vpm, + const bool verbose) +{ + typedef typename boost::graph_traits::vertex_descriptor vertex_descriptor; + typedef typename boost::graph_traits::halfedge_descriptor halfedge_descriptor; + typedef typename boost::graph_traits::edge_descriptor edge_descriptor; + typedef typename boost::graph_traits::face_descriptor face_descriptor; + + CGAL_static_assertion((std::is_same::value_type, Point>::value)); + + typedef std::vector Point_face; + typedef std::vector Vertex_face; + + // To be used to create new elements + std::vector vertex_stack(interior_vertices.begin(), interior_vertices.end()); + std::vector edge_stack(interior_edges.begin(), interior_edges.end()); + std::vector face_stack(pol_faces.begin(), pol_faces.end()); + + // Introduce new vertices, convert the patch in vertex patches + std::vector vertex_patch; + vertex_patch.reserve(patch.size()); + + std::map point_to_vs; + + // first, add those for which the vertex will not change + for(const vertex_descriptor v : border_vertices) + point_to_vs[get(vpm, v)] = v; + + // now build a correspondence map and the faces with vertices + const vertex_descriptor null_v = boost::graph_traits::null_vertex(); + for(const Point_face& face : patch) + { + Vertex_face vface; + vface.reserve(face.size()); + + for(const Point& p : face) + { + bool success; + typename std::map::iterator it; + std::tie(it, success) = point_to_vs.insert(std::make_pair(p, null_v)); + vertex_descriptor& v = it->second; + + if(success) // first time we meet that point, means it's an interior point and we need to make a new vertex + { + if(vertex_stack.empty()) + { + v = add_vertex(pmesh);; + } + else + { + v = vertex_stack.back(); + vertex_stack.pop_back(); + } + + put(vpm, v, p); + } + + vface.push_back(v); + } + + vertex_patch.push_back(vface); + } + + typedef std::pair Vertex_pair; + typedef std::map Vertex_pair_halfedge_map; + + Vertex_pair_halfedge_map halfedge_map; + + // register border halfedges + int i = 0; + for(halfedge_descriptor h : border_hedges) + { + const vertex_descriptor vs = source(h, pmesh); + const vertex_descriptor vt = target(h, pmesh); + halfedge_map.insert(std::make_pair(std::make_pair(vs, vt), h)); + + set_halfedge(target(h, pmesh), h, pmesh); // update vertex halfedge pointer + ++i; + } + + face_descriptor f = boost::graph_traits::null_face(); + + for(const Vertex_face& vface : vertex_patch) + { + if(face_stack.empty()) + { + f = add_face(pmesh); + } + else + { + f = face_stack.back(); + face_stack.pop_back(); + } + + std::vector hedges; + hedges.reserve(vface.size()); + + for(std::size_t i=0, n=vface.size(); i::null_halfedge())); + halfedge_descriptor& h = it->second; + + if(success) // this halfedge is an interior halfedge + { + if(edge_stack.empty()) + { + h = halfedge(add_edge(pmesh), pmesh); + } + else + { + h = halfedge(edge_stack.back(), pmesh); + edge_stack.pop_back(); + } + + halfedge_map[std::make_pair(vj, vi)] = opposite(h, pmesh); + } + + hedges.push_back(h); + } + + CGAL_assertion(vface.size() == hedges.size()); + + // update halfedge connections + face pointers + for(int i=0, n=vface.size(); i +void replace_face_range_with_patch(const std::set::face_descriptor>& faces, + const std::vector >& patch, + PolygonMesh& pmesh, + VPM& vpm, + const bool verbose) +{ + typedef typename boost::graph_traits::vertex_descriptor vertex_descriptor; + typedef typename boost::graph_traits::halfedge_descriptor halfedge_descriptor; + typedef typename boost::graph_traits::edge_descriptor edge_descriptor; + typedef typename boost::graph_traits::face_descriptor face_descriptor; + + std::vector border_vertices; + std::set interior_vertices; + std::vector border_hedges; + std::set interior_edges; + + for(face_descriptor fh : faces) + { + for(halfedge_descriptor h : halfedges_around_face(halfedge(fh, pmesh), pmesh)) + { + if(halfedge(target(h, pmesh), pmesh) == h) // limit the number of insertions + interior_vertices.insert(target(h, pmesh)); + } + } + + for(face_descriptor fh : faces) + { + for(halfedge_descriptor h : halfedges_around_face(halfedge(fh, pmesh), pmesh)) + { + CGAL_assertion(!is_border(h, pmesh)); + + const edge_descriptor e = edge(h, pmesh); + const halfedge_descriptor opp_h = opposite(h, pmesh); + const face_descriptor opp_f = face(opp_h, pmesh); + + if(is_border(opp_h, pmesh) || faces.count(opp_f) == 0) + { + vertex_descriptor v = target(h, pmesh); + interior_vertices.erase(v); + border_hedges.push_back(h); + border_vertices.push_back(v); + } + else + { + interior_edges.insert(e); + } + } + } + + return replace_face_range_with_patch(border_vertices, interior_vertices, + border_hedges, interior_edges, faces, patch, + pmesh, vpm, verbose); +} + +template +bool is_new_patch_close_enough_to_initial_patch(const TriangleMesh& old_patch, + const TriangleMesh& new_patch) +{ +#if defined(CGAL_LINKED_WITH_TBB) + typedef CGAL::Parallel_tag Tag +#else + typedef CGAL::Sequential_tag Tag; +#endif + + double d = Polygon_mesh_processing::approximate_Hausdorff_distance( + old_patch, new_patch, parameters::number_of_points_per_area_unit(4000)); + + std::cout << "d: " << d << std::endl; + return (d <= 0.1); // @fixme hardcoded +} + +template +bool remove_self_intersections_with_smoothing(std::set::face_descriptor>& faces, + const bool constrain_sharp_edges, + TriangleMesh& tmesh, + VertexPointMap& vpmap, + const GeomTraits& gt, + const bool verbose) +{ + std::cout << " - -- -- - - - -- -" << std::endl; + std::cout << "repair with smoothing..." << std::endl; + std::cout << "constraining sharp edges: " << std::boolalpha << constrain_sharp_edges << std::endl; + + typedef typename boost::graph_traits::vertex_descriptor vertex_descriptor; + typedef typename boost::graph_traits::halfedge_descriptor halfedge_descriptor; + typedef typename boost::graph_traits::edge_descriptor edge_descriptor; + typedef typename boost::graph_traits::face_descriptor face_descriptor; + + typedef typename GeomTraits::FT FT; + typedef typename boost::property_traits::value_type Point; + typedef typename GeomTraits::Vector_3 Vector; + + typedef CGAL::Face_filtered_graph Filtered_graph; + + CGAL_precondition(does_self_intersect(faces, tmesh)); + + // keep in memory the face patch so that we can restore if smoothing fails + std::vector > patch; + patch.reserve(faces.size()); + for(const face_descriptor f : faces) + { + std::vector face_points; + for(const halfedge_descriptor h : CGAL::halfedges_around_face(halfedge(f, tmesh), tmesh)) + face_points.push_back(get(vpmap, target(h, tmesh))); + + patch.push_back(face_points); + } + + // also extract the full mesh because we need to compute the Hausdorff distance + Filtered_graph ffg(tmesh, faces); + TriangleMesh patch_mesh; + CGAL::copy_face_graph(ffg, patch_mesh, parameters::vertex_point_map(vpmap)); + + typedef typename boost::property_map::type EIFMap; + EIFMap eif = get(CGAL::edge_is_feature, tmesh); + + std::map is_border_of_selection; + for(face_descriptor f : faces) + { + for(halfedge_descriptor h : CGAL::halfedges_around_face(halfedge(f, tmesh), tmesh)) + { + // meet it once --> switch to 'true'; meet it twice --> switch back to 'false' + const edge_descriptor e = edge(h, tmesh); + is_border_of_selection[e] = !(is_border_of_selection[e]); + } + } + + const double bound = 60.; // @fixme hardcoded + const double cos_angle = std::cos(bound * CGAL_PI / 180.); + + for(const auto& ep : is_border_of_selection) + { + bool flag = ep.second; + if(constrain_sharp_edges && !flag) + { + const halfedge_descriptor h = halfedge(ep.first, tmesh); + const face_descriptor f1 = face(h, tmesh); + const face_descriptor f2 = face(opposite(h, tmesh), tmesh); + + const Vector n1 = compute_face_normal(f1, tmesh, parameters::vertex_point_map(vpmap).geom_traits(gt)); + const Vector n2 = compute_face_normal(f2, tmesh, parameters::vertex_point_map(vpmap).geom_traits(gt)); + const FT cos = n1 * n2; + + // do not mark as sharp edges with a dihedral angle that is almost 'pi' because this is likely + // due to a foldness on the mesh rather than a sharp edge that we wish to preserve + // (Ideally this would be pre-treated as part of the flatness treatment) + flag = (cos <= cos_angle && cos >= -cos_angle); + } + + put(eif, ep.first, flag); + } + + // TMP OUTPUT ------------------------------------------------------------------------------------ + std::ofstream out_sel("results/constrained_edges.selection.txt"); + out_sel << std::endl << std::endl; // edge selection is on the third line + + int counter = 0; + std::map vid; + for(vertex_descriptor v : vertices(tmesh)) + vid[v] = counter++; + + counter = 0; + for(const auto& ep : is_border_of_selection) + { + edge_descriptor e = ep.first; + if(get(eif, e)) + { +// std::cout << e << " is constrained" << std::endl; + ++counter; + out_sel << vid[source(e, tmesh)] << " " << vid[target(e, tmesh)] << " "; + } + } + out_sel.close(); + std::cout << "total : " << counter << " constrained edges" << std::endl; + // TMP OUTPUT ------------------------------------------------------------------------------------ + + smooth_mesh(faces, tmesh, CGAL::parameters::edge_is_constrained_map(eif) + .number_of_iterations(100) // @todo something more sensible? + .use_safety_constraints(false)); + + std::ofstream out1("results/post_smooth.off"); + out1 << std::setprecision(17) << tmesh; + out1.close(); + + Filtered_graph ffg_post(tmesh, faces); + TriangleMesh patch_mesh_post; + CGAL::copy_face_graph(ffg_post, patch_mesh_post, parameters::vertex_point_map(vpmap)); + + bool is_acceptable = (!does_self_intersect(faces, tmesh) && + is_new_patch_close_enough_to_initial_patch(patch_mesh, patch_mesh_post)); + std::cout << "is_acceptable: " << std::boolalpha << is_acceptable << std::endl; + if(!is_acceptable) // restore the patch + { + replace_face_range_with_patch(faces, patch, tmesh, vpmap, verbose); + + std::ofstream out2("results/post_fixup.off"); + out2 << std::setprecision(17) << tmesh; + out2.close(); + } + + return is_acceptable; +} + +template +std::pair +remove_self_intersections_one_step(std::set::face_descriptor>& faces_to_remove, + TriangleMesh& tmesh, + VertexPointMap& vpmap, + const GeomTraits& gt, + const int step, + const bool preserve_genus, + const bool treat_only_local_self_intersections, + const bool verbose) +{ + typedef boost::graph_traits graph_traits; + typedef typename graph_traits::vertex_descriptor vertex_descriptor; + typedef typename graph_traits::halfedge_descriptor halfedge_descriptor; + typedef typename graph_traits::edge_descriptor edge_descriptor; + typedef typename graph_traits::face_descriptor face_descriptor; + + typedef typename boost::property_traits::value_type Point; + std::set faces_to_remove_copy = faces_to_remove; if(verbose) @@ -64,21 +481,8 @@ remove_self_intersections_one_step(TriangleMesh& tmesh, << " with " << faces_to_remove.size() << " intersecting faces\n"; } - if(!does_self_intersect(faces_to_remove, tmesh, parameters::vertex_point_map(vpmap))) - { - if(verbose) - std::cout << "DEBUG: Range has no self-intersections\n"; - - return std::make_pair(true, false); - } - CGAL_assertion(tmesh.is_valid()); - typedef boost::graph_traits graph_traits; - typedef typename graph_traits::vertex_descriptor vertex_descriptor; - typedef typename graph_traits::halfedge_descriptor halfedge_descriptor; - typedef typename graph_traits::edge_descriptor edge_descriptor; - bool something_was_done = false; // indicates if a region was successfully remeshed bool all_fixed = true; // indicates if all removal went well // indicates if a removal was not possible because the region handle has @@ -96,6 +500,8 @@ remove_self_intersections_one_step(TriangleMesh& tmesh, { while(!faces_to_remove.empty()) { + std::cout << faces_to_remove.size() << " to remove" << std::endl; + // Process a connected component of faces to remove. // collect all the faces from the connected component std::set cc_faces; @@ -119,6 +525,12 @@ remove_self_intersections_one_step(TriangleMesh& tmesh, } } + if(verbose) + { + std::cout << " DEBUG: " << cc_faces.size() << " faces in CC\n"; + dump_cc(cc_faces, tmesh, "results/cc_ini.off"); + } + // expand the region to be filled if(step > 0) { @@ -164,12 +576,60 @@ remove_self_intersections_one_step(TriangleMesh& tmesh, } } + if(treat_only_local_self_intersections) + { + if(!does_self_intersect(cc_faces, tmesh, parameters::vertex_point_map(vpmap).geom_traits(gt))) + { + if(verbose) + std::cout << " DEBUG: No self-intersection in CC\n"; + + for(face_descriptor f : cc_faces) + faces_to_remove.erase(f); + + continue; + } + } + + if(verbose) + { + std::cout << " DEBUG: " << cc_faces.size() << " faces in expanded CC\n"; + dump_cc(cc_faces, tmesh, "results/cc_expanded.off"); + } + + if(cc_faces.size() == 1) // it is a triangle nothing better can be done + continue; + + // First, try to smooth if only care about local self-intersections + // Two different approaches: + // - First, try to constrain edges that are in the zone to smooth and whose dihedral angle is large, + // but not too large (we don't want to constrain edges that are due to foldings) + // - If that fails, try to smooth without any constraints, but make sure that the deviation from + // the first zone is small + + // @todo is here the correct position? Probably want to do it before manifoldness shenanigans + // @todo actually, would like to extract manifoldness shenanigans from that + + bool fixed_by_smoothing = false; + if(treat_only_local_self_intersections) + { + fixed_by_smoothing = remove_self_intersections_with_smoothing(cc_faces, true, tmesh, vpmap, gt, verbose); + if(!fixed_by_smoothing) // try again, but without constraining sharp edges + fixed_by_smoothing = remove_self_intersections_with_smoothing(cc_faces, false, tmesh, vpmap, gt, verbose); + } + + std::ofstream out2("results/post_fixup.off"); + out2 << std::setprecision(17) << tmesh; + out2.close(); + // remove faces from the set to process for(face_descriptor f : cc_faces) faces_to_remove.erase(f); - if(cc_faces.size() == 1) - continue; // it is a triangle nothing better can be done + if(fixed_by_smoothing) + { + something_was_done = true; + continue; + } //Check for non-manifold vertices in the selection and remove them by selecting all incident faces: // extract the set of halfedges that is on the boundary of the holes to be @@ -250,7 +710,7 @@ remove_self_intersections_one_step(TriangleMesh& tmesh, halfedge_descriptor h = halfedge(fd, tmesh); for(int i=0; i<3;++i) { - if(is_border(opposite(h, tmesh), tmesh) || cc_faces.count(face(opposite(h, tmesh), tmesh))== 0) + if(is_border(opposite(h, tmesh), tmesh) || cc_faces.count(face(opposite(h, tmesh), tmesh)) == 0) cc_border_hedges.push_back(h); h = next(h, tmesh); @@ -332,9 +792,10 @@ remove_self_intersections_one_step(TriangleMesh& tmesh, do { - typename std::map::iterator - it_find = bhs.find(top.second); - if(it_find == bhs.end()) break; + typename std::map::iterator it_find = bhs.find(top.second); + if(it_find == bhs.end()) + break; + top = *it_find; bhs.erase(it_find); } @@ -414,13 +875,13 @@ remove_self_intersections_one_step(TriangleMesh& tmesh, std::vector hole_points, third_points; hole_points.reserve(cc_border_hedges.size()); third_points.reserve(cc_border_hedges.size()); - std::vector border_vertices; + std::vector cc_border_vertices; for(halfedge_descriptor h : cc_border_hedges) { vertex_descriptor v = source(h, tmesh); hole_points.push_back(get(vpmap, v)); - border_vertices.push_back(v); + cc_border_vertices.push_back(v); third_points.push_back(get(vpmap, target(next(opposite(h, tmesh), tmesh), tmesh))); // TODO fix me for mesh border edges } @@ -464,10 +925,10 @@ remove_self_intersections_one_step(TriangleMesh& tmesh, { int vi=edges[2*k], vj=edges[2*k+1]; // ignore boundary edges - if(vi+1==vj || (vj==0 && static_cast(vi)==border_vertices.size()-1)) + if(vi+1==vj || (vj==0 && static_cast(vi) == cc_border_vertices.size()-1)) continue; - halfedge_descriptor h = halfedge(border_vertices[vi], border_vertices[vj], tmesh).first; + halfedge_descriptor h = halfedge(cc_border_vertices[vi], cc_border_vertices[vj], tmesh).first; if(h != boost::graph_traits::null_halfedge() && cc_interior_edges.count(edge(h, tmesh)) == 0) { @@ -489,93 +950,23 @@ remove_self_intersections_one_step(TriangleMesh& tmesh, continue; } - // plug the new triangles in the mesh, reusing previous edges and faces - std::vector edge_stack(cc_interior_edges.begin(), cc_interior_edges.end()); - std::vector face_stack(cc_faces.begin(), cc_faces.end()); - - std::map, halfedge_descriptor> halfedge_map; - int i = 0; - - // register border halfedges - for(halfedge_descriptor h : cc_border_hedges) - { - int j = static_cast(std::size_t(i+1)%cc_border_hedges.size()); - halfedge_map.insert(std::make_pair(std::make_pair(i, j), h)); - set_halfedge(target(h, tmesh), h, tmesh); // update vertex halfedge pointer - CGAL_assertion(border_vertices[i] == source(h, tmesh) && border_vertices[j] == target(h, tmesh)); - ++i; - } - - std::vector hedges; - hedges.reserve(4); - face_descriptor f = boost::graph_traits::null_face(); - for(const Face_indices& triangle : patch) - { - // get the new face - if(face_stack.empty()) - { - f = add_face(tmesh); - } - else - { - f = face_stack.back(); - face_stack.pop_back(); - } - - std::array indices = make_array(triangle.first, triangle.second, triangle.third, triangle.first); - for(int i=0; i<3; ++i) - { - // get the corresponding halfedge (either a new one or an already created) - typename std::map, halfedge_descriptor >::iterator insert_res = - halfedge_map.insert(std::make_pair(std::make_pair(indices[i], indices[i+1]), - boost::graph_traits::null_halfedge())).first; - - if(insert_res->second == boost::graph_traits::null_halfedge()) - { - if(edge_stack.empty()) - { - insert_res->second = halfedge(add_edge(tmesh), tmesh); - } - else - { - insert_res->second = halfedge(edge_stack.back(), tmesh); - edge_stack.pop_back(); - } - - halfedge_map[std::make_pair(indices[i+1], indices[i])] = opposite(insert_res->second, tmesh); - } - - hedges.push_back(insert_res->second); - } - - hedges.push_back(hedges.front()); - - // update halfedge connections + face pointers - for(int i=0; i<3;++i) - { - set_next(hedges[i], hedges[i+1], tmesh); - set_face(hedges[i], f, tmesh); - set_target(hedges[i], border_vertices[indices[i+1]], tmesh); - } - - set_halfedge(f, hedges[0], tmesh); - hedges.clear(); - } - - // now remove remaining edges, - for(edge_descriptor e : edge_stack) - remove_edge(e, tmesh); - - // vertices, - for(vertex_descriptor vh : cc_interior_vertices) - remove_vertex(vh, tmesh); - - // and remaning faces - for(face_descriptor f : face_stack) - remove_face(f, tmesh); - if(verbose) - std::cout << " DEBUG: " << cc_faces.size() << " triangles removed, " << patch.size() << " created\n"; + std::cout << " DEBUG: Plugging new triangles in...\n"; + + // plug the new triangles in the mesh, reusing previous edges and faces + std::vector > point_patch; + point_patch.reserve(patch.size()); + for(const Face_indices& face : patch) + { + std::vector point_face = { hole_points[face.first], + hole_points[face.second], + hole_points[face.third] }; + point_patch.push_back(point_face); + } + + replace_face_range_with_patch(cc_border_vertices, cc_interior_vertices, + cc_border_hedges, cc_interior_edges, + cc_faces, point_patch, tmesh, vpmap, verbose); CGAL_assertion(is_valid_polygon_mesh(tmesh)); @@ -593,6 +984,10 @@ remove_self_intersections_one_step(TriangleMesh& tmesh, return std::make_pair(all_fixed, topology_issue); } +} // namespace internal + +/// \cond SKIP_IN_MANUAL + template bool remove_self_intersections(TriangleMesh& tmesh, const NamedParameters& np) @@ -605,9 +1000,14 @@ bool remove_self_intersections(TriangleMesh& tmesh, VertexPointMap vpm = boost::choose_param(boost::get_param(np, internal_np::vertex_point), get_property_map(vertex_point, tmesh)); + typedef typename GetGeomTraits::type GeomTraits; + GeomTraits gt = boost::choose_param(boost::get_param(np, internal_np::geom_traits), GeomTraits()); + const int max_steps = boost::choose_param(boost::get_param(np, internal_np::number_of_iterations), 7); bool verbose = boost::choose_param(boost::get_param(np, internal_np::verbosity_level), 0) > 0; bool preserve_genus = boost::choose_param(boost::get_param(np, internal_np::preserve_genus), true); + verbose = true; + const bool treat_self_intersections_locally_only = true; if(verbose) std::cout << "DEBUG: Starting remove_self_intersections, is_valid(tmesh)? " << is_valid_polygon_mesh(tmesh) << "\n"; @@ -650,14 +1050,13 @@ bool remove_self_intersections(TriangleMesh& tmesh, } std::tie(all_fixed, topology_issue) = - remove_self_intersections_one_step(tmesh, faces_to_remove, vpm, step, preserve_genus, verbose); + internal::remove_self_intersections_one_step(faces_to_remove, tmesh, vpm, gt, step, preserve_genus, + treat_self_intersections_locally_only, verbose); if(all_fixed && topology_issue) { if(verbose) - std::cout<< "DEBUG: Process stopped because of boundary cycles" - " of boundary edges involved in self-intersections.\n"; - return false; + std::cout << "DEBUG: boundary cycles of boundary edges involved in self-intersections.\n"; } } @@ -670,184 +1069,6 @@ bool remove_self_intersections(TriangleMesh& tmesh) return remove_self_intersections(tmesh, parameters::all_default()); } -template -int identify_self_intersecting_ccs(const FaceContainer& all_intersecting_faces, - TriangleMesh& mesh, - SICCContainer& self_intersecting_ccs) -{ - typedef typename boost::graph_traits::halfedge_descriptor halfedge_descriptor; - typedef typename boost::graph_traits::face_descriptor face_descriptor; - - typedef CGAL::dynamic_face_property_t Face_bool_tag; - typedef typename boost::property_map::type Is_self_intersecting_face_map; - - typedef CGAL::dynamic_face_property_t Face_color_tag; - typedef typename boost::property_map::type SI_CC_face_map; - - std::list si_faces(all_intersecting_faces.begin(), all_intersecting_faces.end()); - - Is_self_intersecting_face_map is_si_fmap = get(Face_bool_tag(), mesh); - SI_CC_face_map colors = get(Face_color_tag(), mesh); - - // multiple iterations means we must reset properties - for(face_descriptor f : faces(mesh)) - { - put(is_si_fmap, f, false); - put(colors, f, 0); - } - - for(face_descriptor f : si_faces) - put(is_si_fmap, f, true); - - int color = 0; - while(!si_faces.empty()) - { - face_descriptor seed_f = si_faces.front(); - si_faces.pop_front(); - - if(get(colors, seed_f) != 0) // 'seed_f' is already colored, nothing to do - continue; - put(colors, seed_f, ++color); - - std::stack to_color; - to_color.push(seed_f); - - while(!to_color.empty()) - { - face_descriptor f = to_color.top(); - to_color.pop(); - - // add neighbors to stack @todo should this be vertex-neighbors and not edge neighbors - for(halfedge_descriptor h : CGAL::halfedges_around_face(halfedge(f, mesh), mesh)) - { - halfedge_descriptor opp_h = opposite(h, mesh); - face_descriptor opp_f = face(opp_h, mesh); - - if(is_border(opp_h, mesh) || !get(is_si_fmap, opp_f)) - continue; - - // 'f' is already colored; nothing to do - if(get(colors, opp_f) != 0) { - CGAL_assertion(get(colors, opp_f) == color); - } else { - put(colors, opp_f, color); - to_color.push(opp_f); - } - } - } - } - - self_intersecting_ccs.resize(color); - for(face_descriptor f : all_intersecting_faces) - { - CGAL_assertion(get(is_si_fmap, f)); - CGAL_assertion(0 < get(colors, f) && get(colors, f) <= color); - self_intersecting_ccs[get(colors, f) - 1].insert(f); // -1 because colors start at '1' - } - - return color; -} - -template -bool remove_self_intersections_locally(TriangleMesh& tmesh, - const NamedParameters& np) -{ - typedef boost::graph_traits graph_traits; - typedef typename graph_traits::face_descriptor face_descriptor; - - // named parameter extraction - typedef typename GetVertexPointMap::type VertexPointMap; - VertexPointMap vpm = boost::choose_param(boost::get_param(np, internal_np::vertex_point), - get_property_map(vertex_point, tmesh)); - - const int max_steps = boost::choose_param(boost::get_param(np, internal_np::number_of_iterations), 7); - bool verbose = boost::choose_param(boost::get_param(np, internal_np::verbosity_level), 0) > 0; - bool preserve_genus = boost::choose_param(boost::get_param(np, internal_np::preserve_genus), true); - - if(verbose) - std::cout << "DEBUG: Starting remove_self_intersections, is_valid(tmesh)? " << is_valid_polygon_mesh(tmesh) << "\n"; - - CGAL_precondition_code(std::set degenerate_face_set;) - CGAL_precondition_code(degenerate_faces(tmesh, std::inserter(degenerate_face_set, degenerate_face_set.begin()), np);) - CGAL_precondition(degenerate_face_set.empty()); - - if(!preserve_genus) - duplicate_non_manifold_vertices(tmesh, np); - - // Look for self-intersections in the mesh and remove them - int step = -1; - bool all_fixed = false; // indicates if the filling of all created holes went fine - bool topology_issue = false; // indicates if some boundary cycles of edges are blocking the fixing - std::set all_intersecting_faces; - - while(++step < max_steps) - { - if(all_fixed) - { - if(verbose) - std::cout << "DEBUG: Fixed all local self-intersections!" << std::endl; - break; - } - - if(all_intersecting_faces.empty()) // the previous round might have been blocked due to topological constraints - { - typedef std::pair Face_pair; - std::vector self_inter; - // TODO : possible optimization to reduce the range to check with the bbox - // of the previous patches or something. - self_intersections(tmesh, std::back_inserter(self_inter)); - - for(const Face_pair& fp : self_inter) - { - all_intersecting_faces.insert(fp.first); - all_intersecting_faces.insert(fp.second); - } - } - - if(verbose) - std::cout << all_intersecting_faces.size() << " faces part of self-intersections" << std::endl; - - if(all_intersecting_faces.empty()) - { - if(verbose) - std::cout << "DEBUG: There is no more face to remove." << std::endl; - break; - } - - std::vector > faces_to_remove_ccs; - identify_self_intersecting_ccs(all_intersecting_faces, tmesh, faces_to_remove_ccs); - std::cout << faces_to_remove_ccs.size() << " CCs to handle" << std::endl; - - all_fixed = true; - - for(std::set& cc : faces_to_remove_ccs) - { - bool local_fixed; - std::tie(local_fixed, topology_issue) = - remove_self_intersections_one_step(tmesh, cc, vpm, step, preserve_genus, verbose); - - if(topology_issue) - { - if(verbose) - std::cout << "DEBUG: Process stopped because of boundary cycles" - " of boundary edges involved in self-intersections.\n"; - - return false; - } - - all_fixed = (all_fixed && local_fixed); - } - } - - return step < max_steps; -} - -template -bool remove_self_intersections_locally(TriangleMesh& tmesh) -{ - return remove_self_intersections_locally(tmesh, parameters::all_default()); -} - /// \endcond } // namespace Polygon_mesh_processing From de3305f4df0388acbeb262210697d834f6cdc0f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mael=20Rouxel-Labb=C3=A9?= Date: Tue, 6 Aug 2019 16:53:41 +0200 Subject: [PATCH 13/56] Add new headers to PMP.h --- Polygon_mesh_processing/include/CGAL/polygon_mesh_processing.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Polygon_mesh_processing/include/CGAL/polygon_mesh_processing.h b/Polygon_mesh_processing/include/CGAL/polygon_mesh_processing.h index bc7d0ad16af..58c84dc218f 100644 --- a/Polygon_mesh_processing/include/CGAL/polygon_mesh_processing.h +++ b/Polygon_mesh_processing/include/CGAL/polygon_mesh_processing.h @@ -41,6 +41,8 @@ #include #include #include +#include +#include #include #include #include From 114ff8a9671d03db4b4dd4744944099924814fe8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mael=20Rouxel-Labb=C3=A9?= Date: Wed, 7 Aug 2019 15:27:04 +0200 Subject: [PATCH 14/56] Minor sanity improval (cosmetic chance) --- .../include/CGAL/Polygon_mesh_processing/distance.h | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/distance.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/distance.h index 944fe523fb3..b98a4d63d1c 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/distance.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/distance.h @@ -573,10 +573,8 @@ double approximate_Hausdorff_distance( VertexPointMap vpm_2) { std::vector sample_points; - sample_triangle_mesh( - tm1, - std::back_inserter(sample_points), - np); + sample_triangle_mesh(tm1,std::back_inserter(sample_points),np); + return approximate_Hausdorff_distance(sample_points, tm2, vpm_2); } From e0f3a66de6a3407f001cabbee2c74e5abdd6ed58 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mael=20Rouxel-Labb=C3=A9?= Date: Wed, 7 Aug 2019 15:27:30 +0200 Subject: [PATCH 15/56] Fix a few header includes --- .../internal/Smoothing/mesh_smoothing_impl.h | 2 +- .../CGAL/Polygon_mesh_processing/manifoldness.h | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Smoothing/mesh_smoothing_impl.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Smoothing/mesh_smoothing_impl.h index 456ff16e1dc..0a23f8f81b0 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Smoothing/mesh_smoothing_impl.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Smoothing/mesh_smoothing_impl.h @@ -30,7 +30,7 @@ #endif #include -#include +#include #include #include diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/manifoldness.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/manifoldness.h index 0a297ac2116..d3b5a249606 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/manifoldness.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/manifoldness.h @@ -25,16 +25,16 @@ #include -#include -#include -#include -#include - #include #include -#include -#include +#include +#include +#include +#include +#include + +#include #include #include #include From 4b996c61f6479fb851bb5ff3afb414703519459b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mael=20Rouxel-Labb=C3=A9?= Date: Wed, 7 Aug 2019 15:29:00 +0200 Subject: [PATCH 16/56] Add a range parameter to 'remove_self_intersections' --- .../remove_self_intersections.h | 180 +++++++++++------- 1 file changed, 116 insertions(+), 64 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/remove_self_intersections.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/remove_self_intersections.h index 7408646b94f..b0f0a7945f0 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/remove_self_intersections.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/remove_self_intersections.h @@ -85,16 +85,17 @@ void dump_cc(const FaceContainer& cc_faces, out.close(); } -template -void replace_face_range_with_patch(const std::vector::vertex_descriptor>& border_vertices, - const std::set::vertex_descriptor>& interior_vertices, - const std::vector::halfedge_descriptor>& border_hedges, - const std::set::edge_descriptor>& interior_edges, - const std::set::face_descriptor>& pol_faces, - const std::vector >& patch, - PolygonMesh& pmesh, - VPM& vpm, - const bool verbose) +template +FaceOutputIterator replace_face_range_with_patch(const std::vector::vertex_descriptor>& border_vertices, + const std::set::vertex_descriptor>& interior_vertices, + const std::vector::halfedge_descriptor>& border_hedges, + const std::set::edge_descriptor>& interior_edges, + const std::set::face_descriptor>& pol_faces, + const std::vector >& patch, + PolygonMesh& pmesh, + VPM& vpm, + FaceOutputIterator out, + const bool verbose) { typedef typename boost::graph_traits::vertex_descriptor vertex_descriptor; typedef typename boost::graph_traits::halfedge_descriptor halfedge_descriptor; @@ -180,11 +181,13 @@ void replace_face_range_with_patch(const std::vector hedges; @@ -244,18 +247,24 @@ void replace_face_range_with_patch(const std::vector -void replace_face_range_with_patch(const std::set::face_descriptor>& faces, - const std::vector >& patch, - PolygonMesh& pmesh, - VPM& vpm, - const bool verbose) +template +FaceOutputIterator replace_face_range_with_patch(const std::set::face_descriptor>& faces, + const std::vector >& patch, + PolygonMesh& pmesh, + VPM& vpm, + FaceOutputIterator out, + const bool verbose) { typedef typename boost::graph_traits::vertex_descriptor vertex_descriptor; typedef typename boost::graph_traits::halfedge_descriptor halfedge_descriptor; @@ -302,7 +311,18 @@ void replace_face_range_with_patch(const std::set +void replace_face_range_with_patch(const std::set::face_descriptor>& faces, + const std::vector >& patch, + PolygonMesh& pmesh, + VPM& vpm, + const bool verbose = false) +{ + CGAL::Emptyset_iterator out; + replace_face_range_with_patch(faces, patch, pmesh, vpm, out, verbose); } template @@ -457,12 +477,13 @@ bool remove_self_intersections_with_smoothing(std::set std::pair remove_self_intersections_one_step(std::set::face_descriptor>& faces_to_remove, + std::set::face_descriptor>& working_face_range, TriangleMesh& tmesh, VertexPointMap& vpmap, const GeomTraits& gt, const int step, const bool preserve_genus, - const bool treat_only_local_self_intersections, + const bool only_treat_self_intersections_locally, const bool verbose) { typedef boost::graph_traits graph_traits; @@ -576,7 +597,7 @@ remove_self_intersections_one_step(std::set -bool remove_self_intersections(TriangleMesh& tmesh, +template +bool remove_self_intersections(const FaceRange& face_range, + TriangleMesh& tmesh, const NamedParameters& np) { typedef boost::graph_traits graph_traits; @@ -1007,13 +1044,15 @@ bool remove_self_intersections(TriangleMesh& tmesh, bool verbose = boost::choose_param(boost::get_param(np, internal_np::verbosity_level), 0) > 0; bool preserve_genus = boost::choose_param(boost::get_param(np, internal_np::preserve_genus), true); verbose = true; - const bool treat_self_intersections_locally_only = true; + const bool only_treat_self_intersections_locally = true; + + std::set working_face_range(face_range.begin(), face_range.end()); if(verbose) std::cout << "DEBUG: Starting remove_self_intersections, is_valid(tmesh)? " << is_valid_polygon_mesh(tmesh) << "\n"; CGAL_precondition_code(std::set degenerate_face_set;) - CGAL_precondition_code(degenerate_faces(tmesh, std::inserter(degenerate_face_set, degenerate_face_set.begin()), np);) + CGAL_precondition_code(degenerate_faces(working_face_range, tmesh, std::inserter(degenerate_face_set, degenerate_face_set.begin()), np);) CGAL_precondition(degenerate_face_set.empty()); if(!preserve_genus) @@ -1033,7 +1072,7 @@ bool remove_self_intersections(TriangleMesh& tmesh, std::vector self_inter; // TODO : possible optimization to reduce the range to check with the bbox // of the previous patches or something. - self_intersections(tmesh, std::back_inserter(self_inter)); + self_intersections(working_face_range, tmesh, std::back_inserter(self_inter)); for(const Face_pair& fp : self_inter) { @@ -1050,8 +1089,9 @@ bool remove_self_intersections(TriangleMesh& tmesh, } std::tie(all_fixed, topology_issue) = - internal::remove_self_intersections_one_step(faces_to_remove, tmesh, vpm, gt, step, preserve_genus, - treat_self_intersections_locally_only, verbose); + internal::remove_self_intersections_one_step( + faces_to_remove, working_face_range, tmesh, vpm, gt, + step, preserve_genus, only_treat_self_intersections_locally, verbose); if(all_fixed && topology_issue) { @@ -1063,6 +1103,18 @@ bool remove_self_intersections(TriangleMesh& tmesh, return step < max_steps; } +template +bool remove_self_intersections(const FaceRange& face_range, TriangleMesh& tmesh) +{ + return remove_self_intersections(face_range, tmesh, parameters::all_default()); +} + +template +bool remove_self_intersections(TriangleMesh& tmesh, const CGAL_PMP_NP_CLASS& np) +{ + return remove_self_intersections(faces(tmesh), tmesh, np); +} + template bool remove_self_intersections(TriangleMesh& tmesh) { From e7a71ec7978e050af620cf3d3b1ba0a89a0dbdf1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mael=20Rouxel-Labb=C3=A9?= Date: Wed, 7 Aug 2019 15:29:37 +0200 Subject: [PATCH 17/56] Misc cleaning --- .../remove_self_intersections.h | 99 +++---------------- .../include/CGAL/polygon_mesh_processing.h | 1 + 2 files changed, 16 insertions(+), 84 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/remove_self_intersections.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/remove_self_intersections.h index b0f0a7945f0..fde491853ce 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/remove_self_intersections.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/remove_self_intersections.h @@ -53,38 +53,6 @@ namespace CGAL { namespace Polygon_mesh_processing { namespace internal { -// @TMP -template -void dump_cc(const FaceContainer& cc_faces, - const TriangleMesh& mesh, - const std::string filename) -{ - typedef typename boost::graph_traits::face_descriptor face_descriptor; - - std::ofstream out(filename); - out.precision(17); - - out << "OFF\n"; - out << 3*cc_faces.size() << " " << cc_faces.size() << " 0\n"; - - for(const face_descriptor f : cc_faces) - { - out << mesh.point(source(halfedge(f, mesh), mesh)) << "\n"; - out << mesh.point(target(halfedge(f, mesh), mesh)) << "\n"; - out << mesh.point(target(next(halfedge(f, mesh), mesh), mesh)) << "\n"; - } - - int id = 0; - for(const face_descriptor f : cc_faces) - { - CGAL_USE(f); - out << "3 " << id << " " << id+1 << " " << id+2 << "\n"; - id += 3; - } - - out.close(); -} - template FaceOutputIterator replace_face_range_with_patch(const std::vector::vertex_descriptor>& border_vertices, const std::set::vertex_descriptor>& interior_vertices, @@ -335,11 +303,12 @@ bool is_new_patch_close_enough_to_initial_patch(const TriangleMesh& old_patch, typedef CGAL::Sequential_tag Tag; #endif - double d = Polygon_mesh_processing::approximate_Hausdorff_distance( - old_patch, new_patch, parameters::number_of_points_per_area_unit(4000)); + double d = Polygon_mesh_processing::approximate_Hausdorff_distance( // @todo should it be symmetric? + new_patch, old_patch, parameters::number_of_points_on_edges(10) + .number_of_points_on_faces(100)); std::cout << "d: " << d << std::endl; - return (d <= 0.1); // @fixme hardcoded + return (d <= 0.1); // @fixme hardcoded, must depend on local edge length } template @@ -350,9 +319,11 @@ bool remove_self_intersections_with_smoothing(std::set::vertex_descriptor vertex_descriptor; typedef typename boost::graph_traits::halfedge_descriptor halfedge_descriptor; @@ -423,57 +394,24 @@ bool remove_self_intersections_with_smoothing(std::set vid; - for(vertex_descriptor v : vertices(tmesh)) - vid[v] = counter++; - - counter = 0; - for(const auto& ep : is_border_of_selection) - { - edge_descriptor e = ep.first; - if(get(eif, e)) - { -// std::cout << e << " is constrained" << std::endl; - ++counter; - out_sel << vid[source(e, tmesh)] << " " << vid[target(e, tmesh)] << " "; - } - } - out_sel.close(); - std::cout << "total : " << counter << " constrained edges" << std::endl; - // TMP OUTPUT ------------------------------------------------------------------------------------ - + // @todo choice of number of iterations? Till convergence && max of 100? smooth_mesh(faces, tmesh, CGAL::parameters::edge_is_constrained_map(eif) - .number_of_iterations(100) // @todo something more sensible? + .number_of_iterations(10) .use_safety_constraints(false)); - std::ofstream out1("results/post_smooth.off"); - out1 << std::setprecision(17) << tmesh; - out1.close(); - Filtered_graph ffg_post(tmesh, faces); TriangleMesh patch_mesh_post; CGAL::copy_face_graph(ffg_post, patch_mesh_post, parameters::vertex_point_map(vpmap)); bool is_acceptable = (!does_self_intersect(faces, tmesh) && is_new_patch_close_enough_to_initial_patch(patch_mesh, patch_mesh_post)); - std::cout << "is_acceptable: " << std::boolalpha << is_acceptable << std::endl; - if(!is_acceptable) // restore the patch - { + if(!is_acceptable) replace_face_range_with_patch(faces, patch, tmesh, vpmap, verbose); - std::ofstream out2("results/post_fixup.off"); - out2 << std::setprecision(17) << tmesh; - out2.close(); - } - return is_acceptable; } +// the parameter 'step' controls how many extra layers of faces we take around the range 'faces_to_remove' template std::pair remove_self_intersections_one_step(std::set::face_descriptor>& faces_to_remove, @@ -515,14 +453,13 @@ remove_self_intersections_one_step(std::set cc_faces; @@ -547,10 +484,7 @@ remove_self_intersections_one_step(std::set 0) @@ -612,10 +546,7 @@ remove_self_intersections_one_step(std::set faces_to_remove; - while(++step #include #include +#include // the named parameter header being not documented the doc is put here for now #ifdef DOXYGEN_RUNNING From d60d7c578e4bab99663a080da594686558c9b641 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mael=20Rouxel-Labb=C3=A9?= Date: Wed, 7 Aug 2019 15:32:23 +0200 Subject: [PATCH 18/56] Add an example and a test for self intersection removal (wip) --- .../Polygon_mesh_processing/CMakeLists.txt | 4 + ...mesh_self_intersection_removal_example.cpp | 80 +++++++++++ .../Polygon_mesh_processing/CMakeLists.txt | 3 + .../test_pmp_remove_self_intersections.cpp | 126 ++++++++++++++++++ 4 files changed, 213 insertions(+) create mode 100644 Polygon_mesh_processing/examples/Polygon_mesh_processing/mesh_self_intersection_removal_example.cpp create mode 100644 Polygon_mesh_processing/test/Polygon_mesh_processing/test_pmp_remove_self_intersections.cpp diff --git a/Polygon_mesh_processing/examples/Polygon_mesh_processing/CMakeLists.txt b/Polygon_mesh_processing/examples/Polygon_mesh_processing/CMakeLists.txt index aef2bd7c852..8a9cf7949a0 100644 --- a/Polygon_mesh_processing/examples/Polygon_mesh_processing/CMakeLists.txt +++ b/Polygon_mesh_processing/examples/Polygon_mesh_processing/CMakeLists.txt @@ -91,6 +91,7 @@ create_single_source_cgal_program( "detect_features_example.cpp" ) create_single_source_cgal_program( "manifoldness_repair_example.cpp" ) create_single_source_cgal_program( "repair_polygon_soup_example.cpp" ) create_single_source_cgal_program( "mesh_smoothing_example.cpp") +create_single_source_cgal_program( "mesh_self_intersection_removal_example.cpp") if(OpenMesh_FOUND) create_single_source_cgal_program( "compute_normals_example_OM.cpp" ) @@ -116,5 +117,8 @@ find_package(Ceres QUIET) if(TARGET ceres) target_compile_definitions( mesh_smoothing_example PRIVATE CGAL_PMP_USE_CERES_SOLVER ) target_link_libraries( mesh_smoothing_example PRIVATE ceres ) + + target_compile_definitions( mesh_self_intersection_removal_example PRIVATE CGAL_PMP_USE_CERES_SOLVER ) + target_link_libraries( mesh_self_intersection_removal_example PRIVATE ceres ) endif(TARGET ceres) diff --git a/Polygon_mesh_processing/examples/Polygon_mesh_processing/mesh_self_intersection_removal_example.cpp b/Polygon_mesh_processing/examples/Polygon_mesh_processing/mesh_self_intersection_removal_example.cpp new file mode 100644 index 00000000000..bc8c88bf3a1 --- /dev/null +++ b/Polygon_mesh_processing/examples/Polygon_mesh_processing/mesh_self_intersection_removal_example.cpp @@ -0,0 +1,80 @@ +#define CGAL_PMP_REPAIR_POLYGON_SOUP_VERBOSE + +#include +#include + +#include +#include +#include +#include +#include + +#include + +#include +#include + +typedef CGAL::Exact_predicates_inexact_constructions_kernel K; +typedef CGAL::Surface_mesh Mesh; + +typedef typename boost::graph_traits::halfedge_descriptor halfedge_descriptor; +typedef typename boost::graph_traits::face_descriptor face_descriptor; + +namespace PMP = CGAL::Polygon_mesh_processing; + +template +void read_mesh(const char* filename, + Mesh& sm) +{ + typedef typename K::Point_3 Point; + + std::ifstream in(filename, std::ios::binary); + if(!in.good()) + { + std::cerr << "Error: can't read file: " << filename << std::endl; + std::exit(1); + } + + std::string fn(filename); + if(fn.substr(fn.find_last_of(".") + 1) == "stl") + { + std::vector points; + std::vector > faces; + CGAL::read_STL(in, points, faces); + + if(!CGAL::Polygon_mesh_processing::orient_polygon_soup(points, faces)) + std::cerr << "W: File does not describe a polygon mesh" << std::endl; + + CGAL::Polygon_mesh_processing::polygon_soup_to_polygon_mesh(points, faces, sm); + } + else if(fn.substr(fn.find_last_of(".") + 1) == "off") + { + if(!in || !(in >> sm)) + { + std::cerr << "Error: cannot OFF open mesh\n"; + return; + } + } + else + { + std::cerr << "Unknown file type" << std::endl; + return; + } +} + +int main(int argc, char* argv[]) +{ + const char* filename = (argc > 1) ? argv[1] : "/home/mrouxell/DATA/denser_dinosaur_si.off"; + std::ifstream input(filename); + + Mesh mesh; + read_mesh(filename, mesh); + + PMP::remove_degenerate_faces(mesh); + + std::ofstream("in.off") << std::setprecision(17) << mesh; + PMP::remove_self_intersections(mesh); + std::ofstream("out.off") << std::setprecision(17) << mesh; + + 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 3f05050bd45..a9bba68e2cc 100644 --- a/Polygon_mesh_processing/test/Polygon_mesh_processing/CMakeLists.txt +++ b/Polygon_mesh_processing/test/Polygon_mesh_processing/CMakeLists.txt @@ -95,6 +95,7 @@ endif() create_single_source_cgal_program("remove_degeneracies_test.cpp") create_single_source_cgal_program("test_pmp_manifoldness.cpp") create_single_source_cgal_program("test_mesh_smoothing.cpp") + create_single_source_cgal_program("test_pmp_remove_self_intersections.cpp") if( TBB_FOUND ) CGAL_target_use_TBB(test_pmp_distance) @@ -111,5 +112,7 @@ find_package(Ceres QUIET) if(TARGET ceres) target_compile_definitions( test_mesh_smoothing PRIVATE CGAL_PMP_USE_CERES_SOLVER ) target_link_libraries( test_mesh_smoothing PRIVATE ceres ) + target_compile_definitions( test_pmp_remove_self_intersections PRIVATE CGAL_PMP_USE_CERES_SOLVER ) + target_link_libraries( test_pmp_remove_self_intersections PRIVATE ceres ) endif(TARGET ceres) diff --git a/Polygon_mesh_processing/test/Polygon_mesh_processing/test_pmp_remove_self_intersections.cpp b/Polygon_mesh_processing/test/Polygon_mesh_processing/test_pmp_remove_self_intersections.cpp new file mode 100644 index 00000000000..e84c9120009 --- /dev/null +++ b/Polygon_mesh_processing/test/Polygon_mesh_processing/test_pmp_remove_self_intersections.cpp @@ -0,0 +1,126 @@ +#include +#include + +#include +#include + +#include +#include +#include + +namespace PMP = CGAL::Polygon_mesh_processing; + +typedef CGAL::Exact_predicates_inexact_constructions_kernel K; + +typedef CGAL::Surface_mesh Surface_mesh; + +typedef boost::graph_traits::edge_descriptor edge_descriptor; +typedef boost::graph_traits::face_descriptor face_descriptor; + +void fix_self_intersections(const char* fname) +{ + std::cout << std::endl << "---------------" << std::endl; + std::cout << "Test " << fname << std::endl; + + std::ifstream input(fname); + Surface_mesh mesh; + if (!input || !(input >> mesh) || mesh.is_empty()) { + std::cerr << "Error: " << fname << " is not a valid off file.\n"; + std::exit(1); + } + + Surface_mesh mesh_cpy = mesh; + + PMP::remove_self_intersections(mesh); + assert( CGAL::is_valid_polygon_mesh(mesh) ); + CGAL_warning( !PMP::does_self_intersect(mesh) ); + + std::ofstream out("post_repair.off"); + out.precision(17); + out << mesh; + out.close(); + + // just to check compilation + PMP::remove_self_intersections(mesh, CGAL::parameters::number_of_iterations(10)); +} + +void fix_local_self_intersections(const char* mesh_filename, const char* mesh_selection_filename) +{ + std::cout << std::endl << "---------------" << std::endl; + std::cout << "Test " << mesh_filename << " with selection " << mesh_selection_filename << std::endl; + + std::ifstream input(mesh_filename); + Surface_mesh mesh; + if (!input || !(input >> mesh) || mesh.is_empty()) { + std::cerr << "Error: " << mesh_filename << " is not a valid off file.\n"; + std::exit(1); + } + + std::ifstream selection_input(mesh_selection_filename); + std::list selected_faces; + std::string line; + // skip the first line (faces are on the second line) + if(!selection_input || !std::getline(selection_input, line) || !std::getline(selection_input, line)) + { + std::cerr << "Error: could not read selection: " << mesh_selection_filename << std::endl; + std::exit(1); + } + + std::istringstream face_line(line); + std::size_t face_id; + while(face_line >> face_id) + selected_faces.push_back(*(faces(mesh).begin() + face_id)); + std::cout << selected_faces.size() << " faces selected" << std::endl; + + PMP::remove_self_intersections(selected_faces, mesh, CGAL::parameters::verbosity_level(true)); + assert( CGAL::is_valid_polygon_mesh(mesh) ); + CGAL_warning( !PMP::does_self_intersect(selected_faces, mesh) ); + + std::ofstream out("post_repair.off"); + out.precision(17); + out << mesh; + out.close(); + + std::unordered_map face_index_map; + int counter = 0; + BOOST_FOREACH(face_descriptor fd, faces(mesh)) + face_index_map[fd] = counter++; + + std::ofstream out_final_selection("post_repair.selection.txt"); + out_final_selection << std::endl; // first line are vertex indices + + BOOST_FOREACH(const face_descriptor fd, selected_faces) + out_final_selection << face_index_map[fd] << " "; + out_final_selection << std::endl << std::endl; + + PMP::remove_self_intersections(selected_faces, mesh); + assert( CGAL::is_valid_polygon_mesh(mesh) ); + CGAL_warning( !PMP::does_self_intersect(selected_faces, mesh) ); +} + +int main() +{ +#if 0 + fix_self_intersections("data_repair/brain.off"); + fix_self_intersections("data_repair/flute.off"); + fix_self_intersections("data_repair/dinosaur.off"); + fix_self_intersections("data_repair/hemispheres.off"); + + // selection is adjacent to a self-intersection but does not contain any intersection + fix_local_self_intersections("data_repair/brain.off", "data_repair/brain-complete.selection.txt"); + + // selection covers nicely a self-intersection + fix_local_self_intersections("data_repair/brain.off", "data_repair/brain-adjacent.selection.txt"); + + // selection contains part of a self intersection + fix_local_self_intersections("data_repair/brain.off", "data_repair/brain-partial.selection.txt"); + + // selection contains disjoint parts of a self intersection + fix_local_self_intersections("data_repair/brain.off", "data_repair/brain-disjoint.selection.txt"); +#endif + + // Remove only self-intersections within a single hemisphere + fix_local_self_intersections("data_repair/hemispheres.off", "data_repair/hemispheres-half.selection.txt"); + + return 0; +} From c6d20b9c7cbb51d18c26861f6f34c9b93bc6ee1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mael=20Rouxel-Labb=C3=A9?= Date: Wed, 28 Aug 2019 14:43:28 +0200 Subject: [PATCH 19/56] Tiny verbose addition --- .../mesh_self_intersection_removal_example.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Polygon_mesh_processing/examples/Polygon_mesh_processing/mesh_self_intersection_removal_example.cpp b/Polygon_mesh_processing/examples/Polygon_mesh_processing/mesh_self_intersection_removal_example.cpp index bc8c88bf3a1..9e83a0ac345 100644 --- a/Polygon_mesh_processing/examples/Polygon_mesh_processing/mesh_self_intersection_removal_example.cpp +++ b/Polygon_mesh_processing/examples/Polygon_mesh_processing/mesh_self_intersection_removal_example.cpp @@ -69,6 +69,7 @@ int main(int argc, char* argv[]) Mesh mesh; read_mesh(filename, mesh); + std::cout << num_vertices(mesh) << " vertices and " << num_faces(mesh) << " faces" << std::endl; PMP::remove_degenerate_faces(mesh); @@ -76,5 +77,7 @@ int main(int argc, char* argv[]) PMP::remove_self_intersections(mesh); std::ofstream("out.off") << std::setprecision(17) << mesh; + std::cout << "Success? " << !PMP::does_self_intersect(mesh) << std::endl; + return 0; } From c2d3ee8e1ef7975bfe01e83ff272fa881e4daa78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mael=20Rouxel-Labb=C3=A9?= Date: Thu, 5 Sep 2019 15:32:06 +0200 Subject: [PATCH 20/56] Add hole-filling with constraints as a fourth possible step to treat local SI It is tried before hole-filling without constraints, which is used as a last resort (before non-local hole-filling) --- .../remove_self_intersections.h | 1221 +++++++++++++---- .../CGAL/Polygon_mesh_processing/repair.h | 2 + 2 files changed, 941 insertions(+), 282 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/remove_self_intersections.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/remove_self_intersections.h index 3adfbfa4e68..49b168d650b 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/remove_self_intersections.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/remove_self_intersections.h @@ -49,40 +49,82 @@ #include #include +// Self-intersection removal is done by making a big-enough hole and filling it +// +// Local self-intersection removal is more subtle and only considers self-intersections +// within a connected component. It then tries to fix those by trying successively: +// - smoothing with the sharp edges in the area being constrained +// - smoothing without the sharp edges in the area being constrained +// - hole-filling with the sharp edges in the area being constrained +// - hole-filling without the sharp edges in the area being constrained +// +// The working area grows as long as we haven't been able to fix the self-intersection, +// up to a user-defined number of times. + namespace CGAL { namespace Polygon_mesh_processing { namespace internal { -template -FaceOutputIterator replace_face_range_with_patch(const std::vector::vertex_descriptor>& border_vertices, - const std::set::vertex_descriptor>& interior_vertices, - const std::vector::halfedge_descriptor>& border_hedges, - const std::set::edge_descriptor>& interior_edges, - const std::set::face_descriptor>& pol_faces, - const std::vector >& patch, - PolygonMesh& pmesh, - VPM& vpm, - FaceOutputIterator out, - const bool verbose) +template +double compute_hausdorff_distance(const TriangleMesh& old_patch, + const TriangleMesh& new_patch) { +#if defined(CGAL_LINKED_WITH_TBB) + typedef CGAL::Parallel_tag Tag +#else + typedef CGAL::Sequential_tag Tag; +#endif + + // @todo should it be symmetric? + const double d = Polygon_mesh_processing::approximate_Hausdorff_distance( + new_patch, old_patch, parameters::number_of_points_on_edges(10) + .number_of_points_on_faces(100)); + return d; +} + +// -*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*- + +// @todo these could be extracted to somewhere else, it's useful in itself +template +FaceOutputIterator replace_faces_with_patch(const std::vector::vertex_descriptor>& border_vertices, + const std::set::vertex_descriptor>& interior_vertices, + const std::vector::halfedge_descriptor>& border_hedges, + const std::set::edge_descriptor>& interior_edges, + const std::set::face_descriptor>& faces, + const std::vector >& patch, + PolygonMesh& pmesh, + VPM& vpm, + FaceOutputIterator out, + const bool verbose) +{ + CGAL_static_assertion((std::is_same::value_type, Point>::value)); + typedef typename boost::graph_traits::vertex_descriptor vertex_descriptor; typedef typename boost::graph_traits::halfedge_descriptor halfedge_descriptor; typedef typename boost::graph_traits::edge_descriptor edge_descriptor; typedef typename boost::graph_traits::face_descriptor face_descriptor; - CGAL_static_assertion((std::is_same::value_type, Point>::value)); - typedef std::vector Point_face; typedef std::vector Vertex_face; + typedef CGAL::Face_filtered_graph Filtered_graph; + + CGAL_precondition(pmesh.is_valid()); // @tmp + CGAL_precondition(is_valid_polygon_mesh(pmesh)); + + // extract the old patch from the full mesh to compute the Hausdorff distance + const Filtered_graph ffg(pmesh, faces); + PolygonMesh pre_mesh; + CGAL::copy_face_graph(ffg, pre_mesh, parameters::vertex_point_map(vpm)); + // To be used to create new elements std::vector vertex_stack(interior_vertices.begin(), interior_vertices.end()); std::vector edge_stack(interior_edges.begin(), interior_edges.end()); - std::vector face_stack(pol_faces.begin(), pol_faces.end()); + std::vector face_stack(faces.begin(), faces.end()); // Introduce new vertices, convert the patch in vertex patches - std::vector vertex_patch; - vertex_patch.reserve(patch.size()); + std::vector patch_with_vertices; + patch_with_vertices.reserve(patch.size()); std::map point_to_vs; @@ -104,11 +146,11 @@ FaceOutputIterator replace_face_range_with_patch(const std::vectorsecond; - if(success) // first time we meet that point, means it's an interior point and we need to make a new vertex + if(success) // first time we meet that point, means it`s an interior point and we need to make a new vertex { if(vertex_stack.empty()) { - v = add_vertex(pmesh);; + v = add_vertex(pmesh); } else { @@ -122,7 +164,7 @@ FaceOutputIterator replace_face_range_with_patch(const std::vector Vertex_pair; @@ -144,20 +186,25 @@ FaceOutputIterator replace_face_range_with_patch(const std::vector::null_face(); - for(const Vertex_face& vface : vertex_patch) + std::vector new_faces; + for(const Vertex_face& vface : patch_with_vertices) { if(face_stack.empty()) { f = add_face(pmesh); - *out++ = f; } else { f = face_stack.back(); face_stack.pop_back(); - *out++ = f; } + *out++ = f; + +#ifdef CGAL_PMP_REMOVE_SELF_INTERSECTION_DEBUG + new_faces.push_back(f); +#endif + std::vector hedges; hedges.reserve(vface.size()); @@ -217,22 +264,35 @@ FaceOutputIterator replace_face_range_with_patch(const std::vector distance between old and new patches: " << d << std::endl; + return out; } template -FaceOutputIterator replace_face_range_with_patch(const std::set::face_descriptor>& faces, - const std::vector >& patch, - PolygonMesh& pmesh, - VPM& vpm, - FaceOutputIterator out, - const bool verbose) +FaceOutputIterator replace_faces_with_patch(const std::set::face_descriptor>& face_range, + const std::vector >& patch, + PolygonMesh& pmesh, + VPM& vpm, + FaceOutputIterator out, + const bool verbose) { typedef typename boost::graph_traits::vertex_descriptor vertex_descriptor; typedef typename boost::graph_traits::halfedge_descriptor halfedge_descriptor; @@ -244,7 +304,7 @@ FaceOutputIterator replace_face_range_with_patch(const std::set border_hedges; std::set interior_edges; - for(face_descriptor fh : faces) + for(face_descriptor fh : face_range) { for(halfedge_descriptor h : halfedges_around_face(halfedge(fh, pmesh), pmesh)) { @@ -253,7 +313,7 @@ FaceOutputIterator replace_face_range_with_patch(const std::set -void replace_face_range_with_patch(const std::set::face_descriptor>& faces, - const std::vector >& patch, - PolygonMesh& pmesh, - VPM& vpm, - const bool verbose = false) +void replace_faces_with_patch(const std::set::face_descriptor>& faces, + const std::vector >& patch, + PolygonMesh& pmesh, + VPM& vpm, + const bool verbose = false) { CGAL::Emptyset_iterator out; - replace_face_range_with_patch(faces, patch, pmesh, vpm, out, verbose); + replace_faces_with_patch(faces, patch, pmesh, vpm, out, verbose); } -template -bool is_new_patch_close_enough_to_initial_patch(const TriangleMesh& old_patch, - const TriangleMesh& new_patch) +// -*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*- + +template +void back_up_face_range_as_point_patch(std::vector >& point_patch, + const FaceRange& face_range, + const PolygonMesh& tmesh, + const VertexPointMap& vpmap) { -#if defined(CGAL_LINKED_WITH_TBB) - typedef CGAL::Parallel_tag Tag -#else - typedef CGAL::Sequential_tag Tag; -#endif + typedef typename boost::graph_traits::halfedge_descriptor halfedge_descriptor; + typedef typename boost::graph_traits::face_descriptor face_descriptor; - double d = Polygon_mesh_processing::approximate_Hausdorff_distance( // @todo should it be symmetric? - new_patch, old_patch, parameters::number_of_points_on_edges(10) - .number_of_points_on_faces(100)); + point_patch.reserve(face_range.size()); - std::cout << "d: " << d << std::endl; - return (d <= 0.1); // @fixme hardcoded, must depend on local edge length -} - -template -bool remove_self_intersections_with_smoothing(std::set::face_descriptor>& faces, - const bool constrain_sharp_edges, - TriangleMesh& tmesh, - VertexPointMap& vpmap, - const GeomTraits& gt, - const bool verbose) -{ - if(verbose) - { - std::cout << " DEBUG: repair with smoothing... (constraining sharp edges: "; - std::cout << std::boolalpha << constrain_sharp_edges << ")" << std::endl; - } - - typedef typename boost::graph_traits::halfedge_descriptor halfedge_descriptor; - typedef typename boost::graph_traits::edge_descriptor edge_descriptor; - typedef typename boost::graph_traits::face_descriptor face_descriptor; - - typedef typename GeomTraits::FT FT; - typedef typename boost::property_traits::value_type Point; - typedef typename GeomTraits::Vector_3 Vector; - - typedef CGAL::Face_filtered_graph Filtered_graph; - - CGAL_precondition(does_self_intersect(faces, tmesh)); - - // keep in memory the face patch so that we can restore if smoothing fails - std::vector > patch; - patch.reserve(faces.size()); - for(const face_descriptor f : faces) + for(const face_descriptor f : face_range) { std::vector face_points; for(const halfedge_descriptor h : CGAL::halfedges_around_face(halfedge(f, tmesh), tmesh)) face_points.push_back(get(vpmap, target(h, tmesh))); - patch.push_back(face_points); + point_patch.push_back(face_points); } +} - // also extract the full mesh because we need to compute the Hausdorff distance - Filtered_graph ffg(tmesh, faces); - TriangleMesh patch_mesh; - CGAL::copy_face_graph(ffg, patch_mesh, parameters::vertex_point_map(vpmap)); +// -*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*- - typedef typename boost::property_map::type EIFMap; - EIFMap eif = get(CGAL::edge_is_feature, tmesh); +template +void constrain_sharp_and_border_edges(const FaceRange& faces, + EdgeConstrainMap& eif, + const bool constrain_sharp_edges, + TriangleMesh& tmesh, + VertexPointMap& vpmap, + const GeomTraits& gt) +{ + typedef typename boost::graph_traits::halfedge_descriptor halfedge_descriptor; + typedef typename boost::graph_traits::edge_descriptor edge_descriptor; + typedef typename boost::graph_traits::face_descriptor face_descriptor; + + typedef typename GeomTraits::FT FT; + typedef typename GeomTraits::Vector_3 Vector; + + for(edge_descriptor e : edges(tmesh)) // @todo is that needed? + put(eif, e, false); std::map is_border_of_selection; for(face_descriptor f : faces) { + // @fixme what about nm vertices for(halfedge_descriptor h : CGAL::halfedges_around_face(halfedge(f, tmesh), tmesh)) { - // meet it once --> switch to 'true'; meet it twice --> switch back to 'false' + // Default initialization is guaranteed to be `false`. Thus, meet it once will switch + // the value to `true` and meeting it twice will switch back to `false`. const edge_descriptor e = edge(h, tmesh); is_border_of_selection[e] = !(is_border_of_selection[e]); } } - const double bound = 60.; // @fixme hardcoded +#if 1 + CGAL::Polygon_mesh_processing::detect_sharp_edges_pp(faces, tmesh, 60., eif, + parameters::weak_dihedral_angle(20.)); + + // borders are also constrained + for(const auto& ep : is_border_of_selection) + if(ep.second) + put(eif, ep.first, true); +#else + // this is basically the code that is in detect_features (at the very bottom) + // but we do not want a folding to be marked as a sharp feature so the dihedral angle is also + // bounded from above + const double bound = 45.; // @fixme hardcoded const double cos_angle = std::cos(bound * CGAL_PI / 180.); for(const auto& ep : is_border_of_selection) @@ -382,35 +436,764 @@ bool remove_self_intersections_with_smoothing(std::set= -cos_angle); + flag = (c <= cos_angle && c >= -cos_angle); } + is_border_of_selection[ep.first] = flag; // @TMP ONLY NEEDED FOR CONSTRAINED EDGES OUTPUT put(eif, ep.first, flag); } + // @tmp ------------------------------ + std::ofstream out("results/constrained_edges.polylines.txt"); + out << std::setprecision(17); + for(edge_descriptor e : edges(tmesh)) + if(get(eif, e)) + out << "2 " << tmesh.point(source(ep.first, tmesh)) << " " << tmesh.point(target(ep.first, tmesh)) << std::endl; + out.close(); + // @tmp ------------------------------ +#endif +} + +// -*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*- + +template +bool remove_self_intersections_with_smoothing(std::set::face_descriptor>& face_range, + const bool constrain_sharp_edges, + TriangleMesh& tmesh, + VertexPointMap& vpmap, + const GeomTraits& gt, + const bool verbose) +{ + namespace CP = CGAL::parameters; + + typedef typename boost::graph_traits::halfedge_descriptor halfedge_descriptor; + typedef typename boost::graph_traits::edge_descriptor edge_descriptor; + typedef typename boost::graph_traits::face_descriptor face_descriptor; + + typedef typename boost::property_traits::value_type Point; + + typedef CGAL::Face_filtered_graph Filtered_graph; + + if(verbose) + { + std::cout << " DEBUG: repair with smoothing... (constraining sharp edges: "; + std::cout << std::boolalpha << constrain_sharp_edges << ")" << std::endl; + } + + CGAL_precondition(does_self_intersect(face_range, tmesh)); + + // Keep in memory the face patch so that we can restore the mesh if smoothing is unsatisfactory + std::vector > old_patch; + back_up_face_range_as_point_patch(old_patch, face_range, tmesh, vpmap); + + // Extract the face range as a mesh because we need to compute the Hausdorff distance + const Filtered_graph ffg(tmesh, face_range); + TriangleMesh patch_mesh; + CGAL::copy_face_graph(ffg, patch_mesh, CP::vertex_point_map(vpmap)); + + // Constrain sharp and border edges + typedef typename boost::property_map::type EIFMap; + EIFMap eif = get(CGAL::edge_is_feature, tmesh); + constrain_sharp_and_border_edges(face_range, eif, constrain_sharp_edges, tmesh, vpmap, gt); + // @todo choice of number of iterations? Till convergence && max of 100? - smooth_mesh(faces, tmesh, CGAL::parameters::edge_is_constrained_map(eif) - .number_of_iterations(10) - .use_safety_constraints(false)); + Polygon_mesh_processing::smooth_mesh(face_range, tmesh, CP::edge_is_constrained_map(eif) + .number_of_iterations(10) + .use_safety_constraints(false)); - Filtered_graph ffg_post(tmesh, faces); - TriangleMesh patch_mesh_post; - CGAL::copy_face_graph(ffg_post, patch_mesh_post, parameters::vertex_point_map(vpmap)); + // Extract the smoothed face range (again for Hausdorff computations) + const Filtered_graph ffg_post(tmesh, face_range); + TriangleMesh post_smoothing_patch_mesh; + CGAL::copy_face_graph(ffg_post, post_smoothing_patch_mesh, CP::vertex_point_map(vpmap)); - bool is_acceptable = (!does_self_intersect(faces, tmesh) && - is_new_patch_close_enough_to_initial_patch(patch_mesh, patch_mesh_post)); - if(!is_acceptable) - replace_face_range_with_patch(faces, patch, tmesh, vpmap, verbose); + bool is_acceptable = (!does_self_intersect(face_range, tmesh, CP::vertex_point_map(vpmap)) && + compute_hausdorff_distance(patch_mesh, post_smoothing_patch_mesh) < 0.1); // @fixme hardcoded + + if(!is_acceptable) // restore mesh + replace_faces_with_patch(face_range, old_patch, tmesh, vpmap, verbose); return is_acceptable; } -// the parameter 'step' controls how many extra layers of faces we take around the range 'faces_to_remove' +// -*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*- + +template +bool order_border_halfedge_range(std::vector::halfedge_descriptor>& hrange, + const TriangleMesh& tmesh) +{ + typedef typename boost::graph_traits::vertex_descriptor vertex_descriptor; + typedef typename boost::graph_traits::halfedge_descriptor halfedge_descriptor; + + CGAL_precondition(hrange.size() > 2); + + for(std::size_t i=0; i +void dump_cc(const FaceContainer& cc_faces, + const TriangleMesh& mesh, + const std::string filename) +{ + typedef typename boost::graph_traits::face_descriptor face_descriptor; + + typedef typename GetVertexPointMap::const_type VertexPointMap; + VertexPointMap vpm =get_const_property_map(vertex_point, mesh); + + std::ofstream out(filename); + out.precision(17); + + out << "OFF\n"; + out << 3*cc_faces.size() << " " << cc_faces.size() << " 0\n"; + + for(const face_descriptor f : cc_faces) + { + out << get(vpm, source(halfedge(f, mesh), mesh)) << "\n"; + out << get(vpm, target(halfedge(f, mesh), mesh)) << "\n"; + out << get(vpm, target(next(halfedge(f, mesh), mesh), mesh)) << "\n"; + } + + int id = 0; + for(const face_descriptor f : cc_faces) + { + CGAL_USE(f); + out << "3 " << id << " " << id+1 << " " << id+2 << "\n"; + id += 3; + } + + out.close(); +} + +template +void dump_tentative_hole(std::vector >& point_patch, + const std::string filename) +{ + std::ofstream out(filename); + out << std::setprecision(17); + + std::map unique_points_with_id; + for(const std::vector& face : point_patch) + for(const Point& p : face) + unique_points_with_id.insert(std::make_pair(p, 0)); + + out << "OFF\n"; + out << unique_points_with_id.size() << " " << point_patch.size() << " 0\n"; + + int unique_id = 0; + for(auto& pp : unique_points_with_id) + { + out << pp.first << "\n"; + pp.second = unique_id++; + } + + for(const std::vector& face : point_patch) + { + out << face.size(); + for(const Point& p : face) + out << " " << unique_points_with_id.at(p); + out << "\n"; + } + + out << std::endl; + out.close(); +} + +// -*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*- + +// Hole filling can be influenced by setting a third point associated to an edge on the border of the hole. +// This third point is supposed to represent how the mesh continues on the other side of the hole. +// If that edge is a border edge, there is no third point (since the opposite face is the null face). +// Similarly if the edge is an internal sharp edge, we don't really want to use the opposite face because +// there is by definition a strong discontinuity and it might thus mislead the hole filling algorithm. +// +// Rather, we construct an artifical third point that is in the same plane as the face incident to `h`, +// defined as the third point of the imaginary equilateral triangle incident to opp(h, tmesh) +template +typename boost::property_traits::value_type +construct_artificial_third_point(const typename boost::graph_traits::halfedge_descriptor h, + const TriangleMesh& tmesh, + const VertexPointMap& vpmap, + const GeomTraits& gt) +{ + typedef typename GeomTraits::FT FT; + typedef typename boost::property_traits::value_type Point; + typedef typename boost::property_traits::reference Point_ref; + typedef typename GeomTraits::Vector_3 Vector; + + const Point_ref p1 = get(vpmap, source(h, tmesh)); + const Point_ref p2 = get(vpmap, target(h, tmesh)); + const Point_ref opp_p = get(vpmap, target(next(h, tmesh), tmesh)); + + // sqrt(3)/2 to have an equilateral triangle with p1, p2, and third_point + const FT dist = 0.5 * CGAL::sqrt(3.) * CGAL::approximate_sqrt(gt.compute_squared_distance_3_object()(p1, p2)); + + const Vector ve1 = gt.construct_vector_3_object()(p1, p2); + const Vector ve2 = gt.construct_vector_3_object()(p1, opp_p); + + // gram schmidt + const FT e1e2_sp = gt.compute_scalar_product_3_object()(ve1, ve2); + Vector orthogonalized_ve2 = gt.construct_sum_of_vectors_3_object()( + ve2, gt.construct_scaled_vector_3_object()(ve1, - e1e2_sp)); + Polygon_mesh_processing::internal::normalize(orthogonalized_ve2, gt); + + const Point mid_p1p2 = gt.construct_midpoint_3_object()(p1, p2); + const Point third_p = gt.construct_translated_point_3_object()( + mid_p1p2, gt.construct_scaled_vector_3_object()(orthogonalized_ve2, -dist)); + + return third_p; +} + +template +bool construct_tentative_hole_patch(std::vector::vertex_descriptor>& cc_border_vertices, + std::set::vertex_descriptor>& cc_interior_vertices, + std::set::edge_descriptor>& cc_interior_edges, + std::vector >& point_patch, + const std::vector& hole_points, + const std::vector& third_points, + const std::vector::halfedge_descriptor>& cc_border_hedges, + const std::set::face_descriptor>& cc_faces, + const TriangleMesh& tmesh, + VertexPointMap& vpmap, + const GeomTraits& gt, + const bool verbose) +{ + CGAL_static_assertion((std::is_same::value_type, Point>::value)); + + typedef typename boost::graph_traits::vertex_descriptor vertex_descriptor; + typedef typename boost::graph_traits::halfedge_descriptor halfedge_descriptor; + typedef typename boost::graph_traits::edge_descriptor edge_descriptor; + typedef typename boost::graph_traits::face_descriptor face_descriptor; + + typedef CGAL::Triple Face_indices; + + CGAL_assertion(cc_border_hedges.size() == cc_border_hedges.size()); + CGAL_assertion(hole_points.size() == third_points.size()); + + // Collect vertices and edges inside the current selection cc: first collect all vertices and + // edges incident to the faces to remove... + for(const face_descriptor f : cc_faces) + { + for(halfedge_descriptor h : halfedges_around_face(halfedge(f, tmesh), tmesh)) + { + if(halfedge(target(h, tmesh), tmesh) == h) // limit the number of insertions + cc_interior_vertices.insert(target(h, tmesh)); + + cc_interior_edges.insert(edge(h, tmesh)); + } + } + + // ... and then remove those on the boundary + for(halfedge_descriptor h : cc_border_hedges) + { + cc_interior_vertices.erase(target(h, tmesh)); + cc_interior_edges.erase(edge(h, tmesh)); + } + + // try to triangulate the hole using default parameters + // (using Delaunay search space if CGAL_HOLE_FILLING_DO_NOT_USE_DT3 is not defined) + std::vector patch; + if(hole_points.size() > 3) + triangulate_hole_polyline(hole_points, third_points, std::back_inserter(patch)); + else + patch.push_back(Face_indices(0, 1, 2)); // trivial hole filling + + if(patch.empty()) + { +#ifndef CGAL_HOLE_FILLING_DO_NOT_USE_DT3 + if(verbose) + std::cout << " DEBUG: Failed to fill a hole using Delaunay search space.\n"; + + triangulate_hole_polyline(hole_points, third_points, std::back_inserter(patch), + parameters::use_delaunay_triangulation(false)); +#endif + if(patch.empty()) + { + if(verbose) + std::cout << " DEBUG: Failed to fill a hole using the whole search space.\n"; + return false; + } + } + + // @tmp ----------------------------------------------------------- + std::cout << patch.size() << " faces in the patch" << std::endl; + std::vector > to_dump; + for(const Face_indices& face : patch) + { + std::vector point_face = { hole_points[face.first], + hole_points[face.second], + hole_points[face.third] }; + to_dump.push_back(point_face); + } + + CGAL_assertion(to_dump.size() == patch.size()); + + static int hole_id = 0; + std::stringstream oss; + oss << "results/tentative_hole_" << hole_id++ << ".off" << std::ends; + const std::string filename = oss.str().c_str(); + + dump_tentative_hole(to_dump, filename); + // @tmp ----------------------------------------------------------- + + // make sure that the hole filling is valid, we check that no + // edge already in the mesh is present in patch. + bool non_manifold_edge_found = false; + for(const Face_indices& triangle : patch) + { + std::array edges = make_array(triangle.first, triangle.second, + triangle.second, triangle.third, + triangle.third, triangle.first); + for(int k=0; k<3; ++k) + { + const int vi = edges[2*k], vj = edges[2*k+1]; + + // ignore boundary edges + if(vi+1 == vj || (vj == 0 && static_cast(vi) == cc_border_vertices.size()-1)) + continue; + + halfedge_descriptor h = halfedge(cc_border_vertices[vi], cc_border_vertices[vj], tmesh).first; + if(h != boost::graph_traits::null_halfedge() && + cc_interior_edges.count(edge(h, tmesh)) == 0) + { + non_manifold_edge_found = true; + break; + } + } + + if(non_manifold_edge_found) + break; + } + + if(non_manifold_edge_found) + { + if(verbose) + std::cout << " DEBUG: Triangulation produced is non-manifold when plugged into the mesh.\n"; + + return false; + } + + point_patch.reserve(point_patch.size() + patch.size()); + for(const Face_indices& face : patch) + { + std::vector point_face = { hole_points[face.first], + hole_points[face.second], + hole_points[face.third] }; + point_patch.push_back(point_face); + } + + if(verbose) + std::cout << " DEBUG: Found acceptable hole-filling patch.\n"; + + return true; +} + +// This function constructs the ranges `hole_points` and `third_points`. Note that for a sub-hole, +// these two ranges are constructed in another function because we don't want to set 'third_points' +// for edges that are on the border of the sub-hole but not on the border of the (full) hole. +template +bool construct_tentative_hole_patch(std::vector::vertex_descriptor>& cc_border_vertices, + std::set::vertex_descriptor>& cc_interior_vertices, + std::set::edge_descriptor>& cc_interior_edges, + std::vector::value_type> >& point_patch, + const std::vector::halfedge_descriptor>& cc_border_hedges, + const std::set::face_descriptor>& cc_faces, + const TriangleMesh& tmesh, + VertexPointMap& vpmap, + const GeomTraits& gt, + const bool verbose) +{ + typedef typename boost::graph_traits::vertex_descriptor vertex_descriptor; + typedef typename boost::graph_traits::halfedge_descriptor halfedge_descriptor; + + typedef typename boost::property_traits::value_type Point; + + std::vector hole_points, third_points; + hole_points.reserve(cc_border_hedges.size()); + third_points.reserve(cc_border_hedges.size()); + + for(const halfedge_descriptor h : cc_border_hedges) + { + const vertex_descriptor v = source(h, tmesh); + hole_points.push_back(get(vpmap, v)); + cc_border_vertices.push_back(v); + + CGAL_assertion(!is_border(h, tmesh)); + + if(is_border_edge(h, tmesh)) + third_points.push_back(construct_artificial_third_point(h, tmesh, vpmap, gt)); + else + third_points.push_back(get(vpmap, target(next(opposite(h, tmesh), tmesh), tmesh))); + } + + // @tmp + std::cout << cc_border_hedges.size() << " border edges" << std::endl; + std::cout << "third points:\n"; + for(const auto& pt : third_points) + std::cout << pt << "\n"; + std::cout << std::endl; + + CGAL_assertion(hole_points.size() >= 3); + + return construct_tentative_hole_patch(cc_border_vertices, cc_interior_vertices, cc_interior_edges, + point_patch, hole_points, third_points, cc_border_hedges, cc_faces, + tmesh, vpmap, gt, verbose); +} + +// In that overload, we don't know the border of the patch because the face range is a sub-region +// of the hole. We also construct `hole_points` and `third_points`, but with no third point for internal +// sharp edges because a local self-intersection is usually caused by folding and thus we do not want +// a third point resulting from folding to constrain the way we fill the hole in the wrong way. +template +bool construct_tentative_sub_hole_patch(std::vector::value_type> >& point_patch, + const std::set::face_descriptor>& sub_cc_faces, + const std::set::face_descriptor>& cc_faces, + TriangleMesh& tmesh, + VertexPointMap& vpmap, + const GeomTraits& gt, + const bool verbose) +{ + typedef typename boost::graph_traits::vertex_descriptor vertex_descriptor; + typedef typename boost::graph_traits::halfedge_descriptor halfedge_descriptor; + typedef typename boost::graph_traits::edge_descriptor edge_descriptor; + typedef typename boost::graph_traits::face_descriptor face_descriptor; + + typedef typename boost::property_traits::value_type Point; + + // Collect halfedges on the boundary of the region to be selected + // (pointing inside the domain to be remeshed) + std::set internal_hedges; + std::vector cc_border_hedges; + for(const face_descriptor fd : sub_cc_faces) + { + halfedge_descriptor h = halfedge(fd, tmesh); + for(int i=0; i<3;++i) + { + if(is_border(opposite(h, tmesh), tmesh)) + { + cc_border_hedges.push_back(h); + } + else + { + const face_descriptor opp_f = face(opposite(h, tmesh), tmesh); + if(sub_cc_faces.count(opp_f) == 0) + { + cc_border_hedges.push_back(h); + if(cc_faces.count(opp_f) != 0) + internal_hedges.insert(h); + } + } + + h = next(h, tmesh); + } + } + + // Sort halfedges so that they describe the sequence of halfedges of the hole to be made + if(!order_border_halfedge_range(cc_border_hedges, tmesh)) + { + if(verbose) + std::cout << " DEBUG: More than one border in sub-hole. Not currently handled." << std::endl; + + return false; + } + + // @todo we don't care about those sets, so instead there could be a system of output iterators + // in construct_tentative_hole_patch() instead (and here would be emptyset iterators). + std::set cc_interior_vertices; + std::set cc_interior_edges; + + std::vector cc_border_vertices; + cc_border_vertices.reserve(cc_border_hedges.size()); + + std::vector hole_points, third_points; + hole_points.reserve(cc_border_hedges.size()); + third_points.reserve(cc_border_hedges.size()); + + for(const halfedge_descriptor h : cc_border_hedges) + { + const vertex_descriptor v = source(h, tmesh); + hole_points.push_back(get(vpmap, v)); + cc_border_vertices.push_back(v); + + CGAL_assertion(!is_border(h, tmesh)); + + if(internal_hedges.count(h) == 0 && // `h` is on the border of the full CC + !is_border_edge(h, tmesh)) + { + third_points.push_back(get(vpmap, target(next(opposite(h, tmesh), tmesh), tmesh))); + } + else // `h` is on the border of the sub CC but not on the border of the full CC + { + const Point tp = construct_artificial_third_point(h, tmesh, vpmap, gt); + third_points.push_back(tp); + } + } + + return construct_tentative_hole_patch(cc_border_vertices, cc_interior_vertices, cc_interior_edges, point_patch, + hole_points, third_points, cc_border_hedges, sub_cc_faces, + tmesh, vpmap, gt, verbose); +} + +// -*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*- + +// This function is only called when the hole is NOT subdivided into smaller holes +template +bool fill_hole(std::vector::halfedge_descriptor>& cc_border_hedges, + std::set::face_descriptor>& cc_faces, + std::set::face_descriptor>& working_face_range, + TriangleMesh& tmesh, + VertexPointMap& vpmap, + const GeomTraits& gt, + const bool verbose) +{ + typedef typename boost::graph_traits::vertex_descriptor vertex_descriptor; + typedef typename boost::graph_traits::edge_descriptor edge_descriptor; + typedef typename boost::graph_traits::face_descriptor face_descriptor; + + typedef typename boost::property_traits::value_type Point; + + if(verbose) + std::cout << " DEBUG: Attempting hole-filling (no constraints), " << cc_faces.size() << " faces\n"; + + if(!order_border_halfedge_range(cc_border_hedges, tmesh)) + { + CGAL_assertion(false); // we shouldn't fail to orient the boundary cycle of the complete hole + return false; + } + + std::set cc_interior_vertices; + std::set cc_interior_edges; + + std::vector cc_border_vertices; + cc_border_vertices.reserve(cc_border_hedges.size()); + + std::vector > point_patch; + + if(!construct_tentative_hole_patch(cc_border_vertices, cc_interior_vertices, cc_interior_edges, point_patch, + cc_border_hedges, cc_faces, tmesh, vpmap, gt, verbose)) + { + if(verbose) + std::cout << " DEBUG: Failed to find acceptable hole patch\n"; + + return false; + } + + // Could renew the range directly within the patch replacement function + // to avoid erasing and re-adding the same face + for(const face_descriptor f : cc_faces) + working_face_range.erase(f); + + // Plug the new triangles in the mesh, reusing previous edges and faces + replace_faces_with_patch(cc_border_vertices, cc_interior_vertices, + cc_border_hedges, cc_interior_edges, + cc_faces, point_patch, tmesh, vpmap, + std::inserter(working_face_range, working_face_range.end()), + verbose); + + static int filed_hole_id = 0; + std::stringstream oss; + oss << "results/filled_basic_" << filed_hole_id++ << ".off" << std::ends; + std::ofstream(oss.str().c_str()) << std::setprecision(17) << tmesh; + + CGAL_postcondition(is_valid_polygon_mesh(tmesh)); + + return true; +} + +// Same function as above but border of the hole is not known +template +bool fill_hole(std::set::face_descriptor>& cc_faces, + std::set::face_descriptor>& working_face_range, + TriangleMesh& tmesh, + VertexPointMap& vpmap, + const GeomTraits& gt, + const bool verbose) +{ + typedef typename boost::graph_traits::halfedge_descriptor halfedge_descriptor; + typedef typename boost::graph_traits::face_descriptor face_descriptor; + + std::vector cc_border_hedges; + for(face_descriptor fd : cc_faces) + { + halfedge_descriptor h = halfedge(fd, tmesh); + for(int i=0; i<3; ++i) + { + if(is_border(opposite(h, tmesh), tmesh) || cc_faces.count(face(opposite(h, tmesh), tmesh)) == 0) + cc_border_hedges.push_back(h); + + h = next(h, tmesh); + } + } + + if(order_border_halfedge_range(cc_border_hedges, tmesh)) + return fill_hole(cc_border_hedges, cc_faces, working_face_range, tmesh, vpmap, gt, verbose); + else + return false; +} + +template +bool fill_hole_with_constraints(std::vector::halfedge_descriptor>& cc_border_hedges, + std::set::face_descriptor>& cc_faces, + std::set::face_descriptor>& working_face_range, + TriangleMesh& tmesh, + VertexPointMap& vpmap, + const GeomTraits& gt, + const bool verbose) +{ + typedef typename boost::graph_traits::vertex_descriptor vertex_descriptor; + typedef typename boost::graph_traits::halfedge_descriptor halfedge_descriptor; + typedef typename boost::graph_traits::edge_descriptor edge_descriptor; + typedef typename boost::graph_traits::face_descriptor face_descriptor; + + typedef typename boost::property_traits::value_type Point; + + typedef CGAL::Face_filtered_graph Filtered_graph; + + if(verbose) + std::cout << " DEBUG: Attempting local hole-filling with constrained sharp edges..." << std::endl; + + // If we are treating self intersections locally, first try to constrain sharp edges in the hole + typedef typename boost::property_map::type EIFMap; + EIFMap eif = get(CGAL::edge_is_feature, tmesh); + constrain_sharp_and_border_edges(cc_faces, eif, true /*constrain_sharp_edges*/, tmesh, vpmap, gt); + + // Partition the hole using these constrained edges + std::set visited_faces; + std::vector > point_patch; + + int cc_counter = 0; + for(face_descriptor f : cc_faces) + { + if(!visited_faces.insert(f).second) // already visited that face + continue; + + // gather the faces of the sub-hole + std::set sub_cc; + Polygon_mesh_processing::connected_component(f, tmesh, std::inserter(sub_cc, sub_cc.end()), + CGAL::parameters::edge_is_constrained_map(eif)); + + visited_faces.insert(sub_cc.begin(), sub_cc.end()); + + std::cout << "CC of size " << sub_cc.size() << " (total: " << cc_faces.size() << ")" << std::endl; + ++cc_counter; + + dump_cc(sub_cc, tmesh, "results/current_cc.off"); + + // The mesh is not modified, but 'point_patch' gets filled + if(!construct_tentative_sub_hole_patch(point_patch, sub_cc, cc_faces, tmesh, vpmap, gt, verbose)) + { + // Something went wrong while finding a potential cover for the a sub-hole --> use basic hole-filling + return fill_hole(cc_border_hedges, cc_faces, working_face_range, tmesh, vpmap, gt, verbose); + } + } + + std::cout << cc_counter << " independent sub holes" << std::endl; + + // We're assembling multiple patches so we could have the same face appearing multiple times... +// clean_point_patch(point_patch); @fixme + + // Keep in memory the old patch in case something goes wrong + // or if the result is unsatisfactory (self-intersections, big hausdorff distance) + std::vector > old_patch; + back_up_face_range_as_point_patch(old_patch, cc_faces, tmesh, vpmap); + + // Extract the face range as a mesh because we need to compute the Hausdorff distance + const Filtered_graph ffg(tmesh, cc_faces); + TriangleMesh patch_mesh; + CGAL::copy_face_graph(ffg, patch_mesh, parameters::vertex_point_map(vpmap)); + + // Plug the hole-filling patch in the mesh + std::set new_faces; + replace_faces_with_patch(cc_faces, point_patch, tmesh, vpmap, std::inserter(new_faces, new_faces.end()), verbose); + + // Extract the plugged-in face range as a mesh (again for Hausdorff computations) + const Filtered_graph ffg_post(tmesh, new_faces); + TriangleMesh patch_mesh_post; + CGAL::copy_face_graph(ffg_post, patch_mesh_post, parameters::vertex_point_map(vpmap)); + + const bool is_acceptable = (!does_self_intersect(new_faces, tmesh, parameters::vertex_point_map(vpmap)) && + compute_hausdorff_distance(patch_mesh, patch_mesh_post) < 0.1); + + for(const face_descriptor f : cc_faces) + working_face_range.erase(f); + + if(!is_acceptable) + { + if(verbose) + std::cout << " DEBUG: Failed to fill hole with constrained sharp edges. Using basic version." << std::endl; + + // 'old_faces' is equal to 'cc_faces' but needs to be grabbed again since the mesh has been modified + std::set old_faces; + replace_faces_with_patch(new_faces, old_patch, tmesh, vpmap, std::inserter(old_faces, old_faces.end()), verbose); + working_face_range.insert(old_faces.begin(), old_faces.end()); + + return fill_hole(old_faces, working_face_range, tmesh, vpmap, gt, verbose); + } + else + { + working_face_range.insert(new_faces.begin(), new_faces.end()); + return true; + } +} + +template +bool remove_self_intersections_with_hole_filling(std::vector::halfedge_descriptor>& cc_border_hedges, + std::set::face_descriptor>& cc_faces, + std::set::face_descriptor>& working_face_range, + const bool local_self_intersection_removal, + TriangleMesh& tmesh, + VertexPointMap& vpmap, + const GeomTraits& gt, + const bool verbose) +{ + // @tmp ------------------------------ + std::ofstream out("results/zone_border.polylines.txt"); + out << std::setprecision(17); + for(const auto& h : cc_border_hedges) + out << "2 " << tmesh.point(source(h, tmesh)) << " " << tmesh.point(target(h, tmesh)) << std::endl; + out.close(); + // @tmp ------------------------------ + +#ifdef CGAL_PMP_REMOVE_SELF_INTERSECTIONS_NO_CONSTRAINTS_IN_HOLE_FILLING + if(true) +#else + // Do not try to impose sharp edge constraints if we are not doing local-only self intersections removal + if(!local_self_intersection_removal) +#endif + return fill_hole(cc_border_hedges, cc_faces, working_face_range, tmesh, vpmap, gt, verbose); + else + return fill_hole_with_constraints(cc_border_hedges, cc_faces, working_face_range, tmesh, vpmap, gt, verbose); +} + +// the parameter `step` controls how many extra layers of faces we take around the range `faces_to_remove` template std::pair remove_self_intersections_one_step(std::set::face_descriptor>& faces_to_remove, @@ -450,7 +1233,7 @@ remove_self_intersections_one_step(std::set 0) @@ -535,7 +1324,7 @@ remove_self_intersections_one_step(std::set 2); - for(std::size_t i=0; i < cc_border_hedges.size()-2; ++i) - { - vertex_descriptor tgt = target(cc_border_hedges[i], tmesh); - for(std::size_t j=i+1; j cc_interior_vertices; - std::set cc_interior_edges; - - // first collect all vertices and edges incident to the faces to remove - for(face_descriptor fh : cc_faces) - { - for(halfedge_descriptor h : halfedges_around_face(halfedge(fh, tmesh), tmesh)) - { - if(halfedge(target(h, tmesh), tmesh) == h) // limit the number of insertions - cc_interior_vertices.insert(target(h, tmesh)); - - cc_interior_edges.insert(edge(h, tmesh)); - } - } - - // and then remove those on the boundary - for(halfedge_descriptor h : cc_border_hedges) - { - cc_interior_vertices.erase(target(h, tmesh)); - cc_interior_edges.erase(edge(h, tmesh)); - } - - if(verbose) - { - std::cout << " DEBUG: is_valid(tmesh) in one_step, before mesh changes? "; - std::cout << is_valid_polygon_mesh(tmesh) << std::endl; - } - - //try hole_filling. - typedef CGAL::Triple Face_indices; - typedef typename boost::property_traits::value_type Point; - - std::vector hole_points, third_points; - hole_points.reserve(cc_border_hedges.size()); - third_points.reserve(cc_border_hedges.size()); - std::vector cc_border_vertices; - - for(halfedge_descriptor h : cc_border_hedges) - { - vertex_descriptor v = source(h, tmesh); - hole_points.push_back(get(vpmap, v)); - cc_border_vertices.push_back(v); - third_points.push_back(get(vpmap, target(next(opposite(h, tmesh), tmesh), tmesh))); // TODO fix me for mesh border edges - } - - CGAL_assertion(hole_points.size() >= 3); - - // try to triangulate the hole using default parameters - //(using Delaunay search space if CGAL_HOLE_FILLING_DO_NOT_USE_DT3 is not defined) - std::vector patch; - if(hole_points.size()>3) - triangulate_hole_polyline(hole_points, third_points, std::back_inserter(patch)); - else - patch.push_back(Face_indices(0,1,2)); // trivial hole filling - - if(patch.empty()) - { -#ifndef CGAL_HOLE_FILLING_DO_NOT_USE_DT3 - if(verbose) - std::cout << " DEBUG: Failed to fill a hole using Delaunay search space.\n"; - - triangulate_hole_polyline(hole_points, third_points, std::back_inserter(patch), - parameters::use_delaunay_triangulation(false)); -#endif - if(patch.empty()) - { - if(verbose) - std::cout << " DEBUG: Failed to fill a hole using the whole search space.\n"; - all_fixed = false; - continue; - } - } - - // make sure that the hole filling is valid, we check that no - // edge already in the mesh is present in patch. - bool non_manifold_edge_found = false; - for(const Face_indices& triangle : patch) - { - std::array edges = make_array(triangle.first, triangle.second, - triangle.second, triangle.third, - triangle.third, triangle.first); - for(int k=0; k<3; ++k) - { - int vi=edges[2*k], vj=edges[2*k+1]; - // ignore boundary edges - if(vi+1==vj || (vj==0 && static_cast(vi) == cc_border_vertices.size()-1)) - continue; - - halfedge_descriptor h = halfedge(cc_border_vertices[vi], cc_border_vertices[vj], tmesh).first; - if(h != boost::graph_traits::null_halfedge() && - cc_interior_edges.count(edge(h, tmesh)) == 0) - { - non_manifold_edge_found=true; - break; - } - } - - if(non_manifold_edge_found) - break; - } - - if(non_manifold_edge_found) + // sort halfedges so that they describe the sequence of halfedges of the hole to be made + if(!remove_self_intersections_with_hole_filling(cc_border_hedges, cc_faces, working_face_range, + only_treat_self_intersections_locally, + tmesh, vpmap, gt, verbose)) { if(verbose) - std::cout << " DEBUG: Triangulation produced is non-manifold when plugged into the mesh.\n"; + std::cout << " DEBUG: Failed to fill hole\n"; all_fixed = false; - continue; } - - if(verbose) - std::cout << " DEBUG: Plugging new triangles in...\n"; - - // plug the new triangles in the mesh, reusing previous edges and faces - std::vector > point_patch; - point_patch.reserve(patch.size()); - for(const Face_indices& face : patch) + else { - std::vector point_face = { hole_points[face.first], - hole_points[face.second], - hole_points[face.third] }; - point_patch.push_back(point_face); + something_was_done = true; } - - // Could renew the range directly within the patch replacement function - // to avoid erasing and re-adding the same face - for(const face_descriptor f : cc_faces) - working_face_range.erase(f); - - replace_face_range_with_patch(cc_border_vertices, cc_interior_vertices, - cc_border_hedges, cc_interior_edges, - cc_faces, point_patch, tmesh, vpmap, - std::inserter(working_face_range, working_face_range.end()), - verbose); - - CGAL_postcondition(is_valid_polygon_mesh(tmesh)); - - something_was_done = true; } } @@ -942,9 +1598,13 @@ remove_self_intersections_one_step(std::set 0; bool preserve_genus = choose_parameter(get_parameter(np, internal_np::preserve_genus), true); - verbose = true; const bool only_treat_self_intersections_locally = true; + verbose = true; // @tmp + std::set working_face_range(face_range.begin(), face_range.end()); if(verbose) std::cout << "DEBUG: Starting remove_self_intersections, is_valid(tmesh)? " << is_valid_polygon_mesh(tmesh) << "\n"; - CGAL_precondition_code(std::set degenerate_face_set;) - CGAL_precondition_code(degenerate_faces(working_face_range, tmesh, std::inserter(degenerate_face_set, degenerate_face_set.begin()), np);) - CGAL_precondition(degenerate_face_set.empty()); - if(!preserve_genus) duplicate_non_manifold_vertices(tmesh, np); @@ -1015,7 +1672,7 @@ bool remove_self_intersections(const FaceRange& face_range, if(faces_to_remove.empty() && all_fixed) { if(verbose) - std::cout << "DEBUG: There is no more face to remove." << std::endl; + std::cout << "DEBUG: There are no more faces to remove." << std::endl; break; } diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/repair.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/repair.h index 2ae7bcb789a..8df80148e7b 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/repair.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/repair.h @@ -29,6 +29,8 @@ #include #include +#include + #include namespace CGAL { From 0c497580cde8ee715e6aa4cd19bfad34f9f98b0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mael=20Rouxel-Labb=C3=A9?= Date: Thu, 5 Sep 2019 15:52:02 +0200 Subject: [PATCH 21/56] Misselleneaous... Miscalleneous.. Miscenalleous... Miscelleanous... Misc cleaning! --- .../remove_self_intersections.h | 40 ++++--------------- 1 file changed, 8 insertions(+), 32 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/remove_self_intersections.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/remove_self_intersections.h index 49b168d650b..fe78f11c773 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/remove_self_intersections.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/remove_self_intersections.h @@ -75,8 +75,7 @@ double compute_hausdorff_distance(const TriangleMesh& old_patch, typedef CGAL::Sequential_tag Tag; #endif - // @todo should it be symmetric? - const double d = Polygon_mesh_processing::approximate_Hausdorff_distance( + const double d = Polygon_mesh_processing::approximate_symmetric_Hausdorff_distance( new_patch, old_patch, parameters::number_of_points_on_edges(10) .number_of_points_on_faces(100)); return d; @@ -112,11 +111,6 @@ FaceOutputIterator replace_faces_with_patch(const std::vector vertex_stack(interior_vertices.begin(), interior_vertices.end()); std::vector edge_stack(interior_edges.begin(), interior_edges.end()); @@ -184,9 +178,12 @@ FaceOutputIterator replace_faces_with_patch(const std::vector::null_face(); + face_descriptor f = boost::graph_traits::null_face(); +#ifdef CGAL_PMP_REMOVE_SELF_INTERSECTION_DEBUG std::vector new_faces; +#endif + for(const Vertex_face& vface : patch_with_vertices) { if(face_stack.empty()) @@ -200,7 +197,6 @@ FaceOutputIterator replace_faces_with_patch(const std::vector distance between old and new patches: " << d << std::endl; - return out; } @@ -422,7 +410,7 @@ void constrain_sharp_and_border_edges(const FaceRange& faces, // this is basically the code that is in detect_features (at the very bottom) // but we do not want a folding to be marked as a sharp feature so the dihedral angle is also // bounded from above - const double bound = 45.; // @fixme hardcoded + const double bound = 60.; // @fixme hardcoded const double cos_angle = std::cos(bound * CGAL_PI / 180.); for(const auto& ep : is_border_of_selection) @@ -453,7 +441,7 @@ void constrain_sharp_and_border_edges(const FaceRange& faces, out << std::setprecision(17); for(edge_descriptor e : edges(tmesh)) if(get(eif, e)) - out << "2 " << tmesh.point(source(ep.first, tmesh)) << " " << tmesh.point(target(ep.first, tmesh)) << std::endl; + out << "2 " << tmesh.point(source(e, tmesh)) << " " << tmesh.point(target(e, tmesh)) << std::endl; out.close(); // @tmp ------------------------------ #endif @@ -548,11 +536,6 @@ bool order_border_halfedge_range(std::vector= 3); + CGAL_postcondition(hole_points.size() >= 3); return construct_tentative_hole_patch(cc_border_vertices, cc_interior_vertices, cc_interior_edges, point_patch, hole_points, third_points, cc_border_hedges, cc_faces, From d6bb0e5bf0d6dd632817484a6e0ee9992a37d850 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mael=20Rouxel-Labb=C3=A9?= Date: Thu, 5 Sep 2019 15:58:57 +0200 Subject: [PATCH 22/56] Use a generic dynamic pmap rather than the specific edge_is_feature one Don't want the state of a feature edge to be remembered from one step of the algorithm to the other --- .../remove_self_intersections.h | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/remove_self_intersections.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/remove_self_intersections.h index fe78f11c773..b53efd69c87 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/remove_self_intersections.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/remove_self_intersections.h @@ -178,7 +178,6 @@ FaceOutputIterator replace_faces_with_patch(const std::vector::null_face(); #ifdef CGAL_PMP_REMOVE_SELF_INTERSECTION_DEBUG std::vector new_faces; @@ -485,8 +484,10 @@ bool remove_self_intersections_with_smoothing(std::set::type EIFMap; - EIFMap eif = get(CGAL::edge_is_feature, tmesh); + typedef CGAL::dynamic_edge_property_t Edge_property_tag; + typedef typename boost::property_map::type EIFMap; + EIFMap eif = get(Edge_property_tag(), tmesh); + constrain_sharp_and_border_edges(face_range, eif, constrain_sharp_edges, tmesh, vpmap, gt); // @todo choice of number of iterations? Till convergence && max of 100? @@ -1057,8 +1058,10 @@ bool fill_hole_with_constraints(std::vector::type EIFMap; - EIFMap eif = get(CGAL::edge_is_feature, tmesh); + typedef CGAL::dynamic_edge_property_t Edge_property_tag; + typedef typename boost::property_map::type EIFMap; + EIFMap eif = get(Edge_property_tag(), tmesh); + constrain_sharp_and_border_edges(cc_faces, eif, true /*constrain_sharp_edges*/, tmesh, vpmap, gt); // Partition the hole using these constrained edges From 09419dca5c3b10f8854049937146e14a23297d64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mael=20Rouxel-Labb=C3=A9?= Date: Mon, 18 Nov 2019 12:06:42 +0100 Subject: [PATCH 23/56] More work towards local self-intersection removal (bug fixes, etc.) --- .../remove_self_intersections.h | 769 ++++++++++-------- 1 file changed, 427 insertions(+), 342 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/remove_self_intersections.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/remove_self_intersections.h index b53efd69c87..fd536ef364c 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/remove_self_intersections.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/remove_self_intersections.h @@ -49,6 +49,12 @@ #include #include +// @tmp to debug +#define CGAL_PMP_REMOVE_SELF_INTERSECTION_DEBUG + +// #define CGAL_PMP_REMOVE_SELF_INTERSECTIONS_NO_SMOOTHING +// #define CGAL_PMP_REMOVE_SELF_INTERSECTIONS_NO_CONSTRAINTS_IN_HOLE_FILLING + // Self-intersection removal is done by making a big-enough hole and filling it // // Local self-intersection removal is more subtle and only considers self-intersections @@ -65,6 +71,13 @@ namespace CGAL { namespace Polygon_mesh_processing { namespace internal { +#ifdef CGAL_PMP_REMOVE_SELF_INTERSECTION_DEBUG + static int self_intersections_solved_by_constrained_smoothing = 0; + static int self_intersections_solved_by_unconstrained_smoothing = 0; + static int self_intersections_solved_by_constrained_hole_filling = 0; + static int self_intersections_solved_by_unconstrained_hole_filling = 0; +#endif + template double compute_hausdorff_distance(const TriangleMesh& old_patch, const TriangleMesh& new_patch) @@ -106,10 +119,8 @@ FaceOutputIterator replace_faces_with_patch(const std::vector Point_face; typedef std::vector Vertex_face; - typedef CGAL::Face_filtered_graph Filtered_graph; - - CGAL_precondition(pmesh.is_valid()); // @tmp - CGAL_precondition(is_valid_polygon_mesh(pmesh)); + CGAL_precondition(pmesh.is_valid()); // @tmp remove + CGAL_precondition(is_valid_polygon_mesh(pmesh)); // @tmp switch to '_expensive_' // To be used to create new elements std::vector vertex_stack(interior_vertices.begin(), interior_vertices.end()); @@ -262,8 +273,8 @@ FaceOutputIterator replace_faces_with_patch(const std::vector is_border_of_selection; for(face_descriptor f : faces) { @@ -397,7 +405,7 @@ void constrain_sharp_and_border_edges(const FaceRange& faces, } } -#if 1 +#if 0 CGAL::Polygon_mesh_processing::detect_sharp_edges_pp(faces, tmesh, 60., eif, parameters::weak_dihedral_angle(20.)); @@ -458,10 +466,6 @@ bool remove_self_intersections_with_smoothing(std::set::halfedge_descriptor halfedge_descriptor; - typedef typename boost::graph_traits::edge_descriptor edge_descriptor; - typedef typename boost::graph_traits::face_descriptor face_descriptor; - typedef typename boost::property_traits::value_type Point; typedef CGAL::Face_filtered_graph Filtered_graph; @@ -500,11 +504,26 @@ bool remove_self_intersections_with_smoothing(std::set::vertex_descriptor vertex_descriptor; - typedef typename boost::graph_traits::halfedge_descriptor halfedge_descriptor; CGAL_precondition(hrange.size() > 2); @@ -666,15 +684,13 @@ bool construct_tentative_hole_patch(std::vector::halfedge_descriptor>& cc_border_hedges, const std::set::face_descriptor>& cc_faces, const TriangleMesh& tmesh, - VertexPointMap& vpmap, - const GeomTraits& gt, + VertexPointMap& /*vpmap*/, + const GeomTraits& /*gt*/, const bool verbose) { CGAL_static_assertion((std::is_same::value_type, Point>::value)); - typedef typename boost::graph_traits::vertex_descriptor vertex_descriptor; typedef typename boost::graph_traits::halfedge_descriptor halfedge_descriptor; - typedef typename boost::graph_traits::edge_descriptor edge_descriptor; typedef typename boost::graph_traits::face_descriptor face_descriptor; typedef CGAL::Triple Face_indices; @@ -1036,6 +1052,36 @@ bool fill_hole(std::set::face_descrip return false; } +// Patch is not valid if: +// - we insert the same face more than once +// - insert non-manifold edges +template +bool check_point_patch_sanity(const std::vector >& point_patch) +{ + std::set > unique_faces; + std::map, int> unique_edges; + + for(const std::vector& face : point_patch) + { + if(!unique_faces.emplace(face.begin(), face.end()).second) // this face had already been found + return false; + + int i = (unique_edges.insert(std::make_pair(std::set { face[0], face[1] }, 0)).first->second)++; + if(i == 2) // non-manifold edge + return false; + + i = (unique_edges.insert(std::make_pair(std::set { face[1], face[2] }, 0)).first->second)++; + if(i == 2) // non-manifold edge + return false; + + i = (unique_edges.insert(std::make_pair(std::set { face[2], face[0] }, 0)).first->second)++; + if(i == 2) // non-manifold edge + return false; + } + + return true; +} + template bool fill_hole_with_constraints(std::vector::halfedge_descriptor>& cc_border_hedges, std::set::face_descriptor>& cc_faces, @@ -1045,9 +1091,6 @@ bool fill_hole_with_constraints(std::vector::vertex_descriptor vertex_descriptor; - typedef typename boost::graph_traits::halfedge_descriptor halfedge_descriptor; - typedef typename boost::graph_traits::edge_descriptor edge_descriptor; typedef typename boost::graph_traits::face_descriptor face_descriptor; typedef typename boost::property_traits::value_type Point; @@ -1097,7 +1140,11 @@ bool fill_hole_with_constraints(std::vector graph_traits; typedef typename graph_traits::vertex_descriptor vertex_descriptor; typedef typename graph_traits::halfedge_descriptor halfedge_descriptor; - typedef typename graph_traits::edge_descriptor edge_descriptor; typedef typename graph_traits::face_descriptor face_descriptor; std::set faces_to_remove_copy = faces_to_remove; @@ -1212,364 +1276,369 @@ remove_self_intersections_one_step(std::set cc_faces; + std::vector queue(1, *faces_to_remove.begin()); // temporary queue + cc_faces.insert(queue.back()); + while(!queue.empty()) { - // Process a connected component of faces to remove. - // collect all the faces from the connected component - std::set cc_faces; - std::vector queue(1, *faces_to_remove.begin()); // temporary queue - cc_faces.insert(queue.back()); - while(!queue.empty()) + face_descriptor top = queue.back(); + queue.pop_back(); + halfedge_descriptor h = halfedge(top, tmesh); + for(int i=0; i<3; ++i) { - face_descriptor top = queue.back(); - queue.pop_back(); - halfedge_descriptor h = halfedge(top, tmesh); - for(int i=0; i<3; ++i) + face_descriptor adjacent_face = face(opposite(h, tmesh), tmesh); + if(adjacent_face!=boost::graph_traits::null_face()) { - face_descriptor adjacent_face = face(opposite(h, tmesh), tmesh); - if(adjacent_face!=boost::graph_traits::null_face()) - { - if(faces_to_remove.count(adjacent_face) != 0 && cc_faces.insert(adjacent_face).second) - queue.push_back(adjacent_face); - } - - h = next(h, tmesh); - } - } - - if(verbose) - { - std::cout << "----------------\n"; - std::cout << " DEBUG: " << cc_faces.size() << " faces in CC\n"; - std::cout << " DEBUG: first face: " << get(vpmap, source(halfedge(*(cc_faces.begin()), tmesh), tmesh)) << " " - << get(vpmap, target(halfedge(*(cc_faces.begin()), tmesh), tmesh)) << " " - << get(vpmap, target(next(halfedge(*(cc_faces.begin()), tmesh), tmesh), tmesh)) << "\n"; - } - - // expand the region to be filled - if(step > 0) - { - expand_face_selection(cc_faces, tmesh, step, - make_boolean_property_map(cc_faces), - Emptyset_iterator()); - } - - // try to compactify the selection region by also selecting all the faces included - // in the bounding box of the initial selection - std::vector stack_for_expension; - Bbox_3 bb; - for(face_descriptor fd : cc_faces) - { - for(halfedge_descriptor h : halfedges_around_face(halfedge(fd, tmesh), tmesh)) - { - bb += get(vpmap, target(h, tmesh)).bbox(); - face_descriptor nf = face(opposite(h, tmesh), tmesh); - if(nf != boost::graph_traits::null_face() && cc_faces.count(nf) == 0) - { - stack_for_expension.push_back(opposite(h, tmesh)); - } - } - } - - while(!stack_for_expension.empty()) - { - halfedge_descriptor h = stack_for_expension.back(); - stack_for_expension.pop_back(); - if(cc_faces.count(face(h, tmesh)) == 1) - continue; - - if(do_overlap(bb, get(vpmap, target(next(h, tmesh), tmesh)).bbox())) - { - cc_faces.insert(face(h, tmesh)); - halfedge_descriptor candidate = opposite(next(h, tmesh), tmesh); - if(face(candidate, tmesh) != boost::graph_traits::null_face()) - stack_for_expension.push_back(candidate); - - candidate = opposite(prev(h, tmesh), tmesh); - if(face(candidate, tmesh) != boost::graph_traits::null_face()) - stack_for_expension.push_back(candidate); - } - } - - if(only_treat_self_intersections_locally) - { - if(!does_self_intersect(cc_faces, tmesh, parameters::vertex_point_map(vpmap).geom_traits(gt))) - { - if(verbose) - std::cout << " DEBUG: No self-intersection in CC\n"; - - for(const face_descriptor f : cc_faces) - faces_to_remove.erase(f); - - continue; - } - } - - if(verbose) - std::cout << " DEBUG: " << cc_faces.size() << " faces in expanded CC\n"; - - // @tmp ----------------------------------------------------- - static int exp_cc_id = 0; - std::stringstream oss; - oss << "results/expanded_cc_" << exp_cc_id++ << ".off" << std::ends; - dump_cc(cc_faces, tmesh, oss.str().c_str()); - // @tmp ----------------------------------------------------- - - //Check for non-manifold vertices in the selection and remove them by selecting all incident faces: - // extract the set of halfedges that is on the boundary of the holes to be - // made. In addition, we make sure no hole to be created contains a vertex - // visited more than once along a hole border (pinched surface) - // We save the size of boundary_hedges to make sur halfedges added - // from non_filled_hole are not removed. - bool non_manifold_vertex_remaining_in_selection = false; - do - { - bool non_manifold_vertex_removed = false; //here non-manifold is for the 1D polyline - std::vector boundary_hedges; - for(face_descriptor fh : cc_faces) - { - halfedge_descriptor h = halfedge(fh, tmesh); - for(int i=0; i<3; ++i) - { - if(is_border(opposite(h, tmesh), tmesh) || cc_faces.count(face(opposite(h, tmesh), tmesh)) == 0) - boundary_hedges.push_back(h); - - h = next(h, tmesh); - } + if(faces_to_remove.count(adjacent_face) != 0 && cc_faces.insert(adjacent_face).second) + queue.push_back(adjacent_face); } - // detect vertices visited more than once along - // a hole border. We then remove all faces incident - // to such a vertex to force the removal of the vertex. - // Actually even if two holes are sharing a vertex, this - // vertex will be removed. It is not needed but since - // we do not yet have one halfedge per hole it is simpler - // and does not harm - std::set border_vertices; - for(halfedge_descriptor h : boundary_hedges) - { - if(!border_vertices.insert(target(h, tmesh)).second) - { - bool any_face_added = false; - for(halfedge_descriptor hh : halfedges_around_target(h, tmesh)) - { - if(!is_border(hh, tmesh)) - { - // add the face to the current selection - any_face_added |= cc_faces.insert(face(hh, tmesh)).second; - faces_to_remove.erase(face(hh, tmesh)); - } - } - - if(any_face_added) - non_manifold_vertex_removed = true; - else - non_manifold_vertex_remaining_in_selection = true; - } - } - - if(!non_manifold_vertex_removed) - break; + h = next(h, tmesh); } - while(true); + } - if(preserve_genus && non_manifold_vertex_remaining_in_selection) + if(verbose) + { + std::cout << " DEBUG: " << cc_faces.size() << " faces in CC\n"; + std::cout << " DEBUG: first face: " << get(vpmap, source(halfedge(*(cc_faces.begin()), tmesh), tmesh)) << " " + << get(vpmap, target(halfedge(*(cc_faces.begin()), tmesh), tmesh)) << " " + << get(vpmap, target(next(halfedge(*(cc_faces.begin()), tmesh), tmesh), tmesh)) << "\n"; + } + + // expand the region to be filled + if(step > 0) + { + expand_face_selection(cc_faces, tmesh, step, + make_boolean_property_map(cc_faces), + Emptyset_iterator()); + } + + + // try to compactify the selection region by also selecting all the faces included + // in the bounding box of the initial selection + std::vector stack_for_expension; + Bbox_3 bb; + for(face_descriptor fd : cc_faces) + { + for(halfedge_descriptor h : halfedges_around_face(halfedge(fd, tmesh), tmesh)) + { + bb += get(vpmap, target(h, tmesh)).bbox(); + face_descriptor nf = face(opposite(h, tmesh), tmesh); + if(nf != boost::graph_traits::null_face() && cc_faces.count(nf) == 0) + { + stack_for_expension.push_back(opposite(h, tmesh)); + } + } + } + + while(!stack_for_expension.empty()) + { + halfedge_descriptor h = stack_for_expension.back(); + stack_for_expension.pop_back(); + if(cc_faces.count(face(h, tmesh)) == 1) + continue; + + if(do_overlap(bb, get(vpmap, target(next(h, tmesh), tmesh)).bbox())) + { + cc_faces.insert(face(h, tmesh)); + halfedge_descriptor candidate = opposite(next(h, tmesh), tmesh); + if(face(candidate, tmesh) != boost::graph_traits::null_face()) + stack_for_expension.push_back(candidate); + + candidate = opposite(prev(h, tmesh), tmesh); + if(face(candidate, tmesh) != boost::graph_traits::null_face()) + stack_for_expension.push_back(candidate); + } + } + + if(only_treat_self_intersections_locally) + { + if(!does_self_intersect(cc_faces, tmesh, parameters::vertex_point_map(vpmap).geom_traits(gt))) { - topology_issue = true; if(verbose) - std::cout << " DEBUG: CC not handled due to the presence at least one non-manifold vertex\n"; + std::cout << " DEBUG: No self-intersection in CC\n"; - continue; // cannot replace a patch containing a nm vertex by a disk + for(const face_descriptor f : cc_faces) + faces_to_remove.erase(f); + + continue; } + } - // before running this function if preserve_genus=false, we duplicated - // all of them - CGAL_assertion(!non_manifold_vertex_remaining_in_selection); + if(verbose) + std::cout << " DEBUG: " << cc_faces.size() << " faces in expanded CC\n"; - // Collect halfedges on the boundary of the region to be selected - // (pointing inside the domain to be remeshed) - std::vector cc_border_hedges; - for(face_descriptor fd : cc_faces) + // remove faces from the set to process + for(const face_descriptor f : cc_faces) + faces_to_remove.erase(f); + + // @tmp ----------------------------------------------------- + std::stringstream ex_oss; + std::cout << "Output FULLY expanded CC #" << exp_cc_id << std::endl; + ex_oss << "results/fully_expanded_cc_" << exp_cc_id++ << ".off" << std::ends; + dump_cc(cc_faces, tmesh, ex_oss.str().c_str()); + // @tmp ----------------------------------------------------- + + //Check for non-manifold vertices in the selection and remove them by selecting all incident faces: + // extract the set of halfedges that is on the boundary of the holes to be + // made. In addition, we make sure no hole to be created contains a vertex + // visited more than once along a hole border (pinched surface) + // We save the size of boundary_hedges to make sur halfedges added + // from non_filled_hole are not removed. + bool non_manifold_vertex_remaining_in_selection = false; + do + { + bool non_manifold_vertex_removed = false; //here non-manifold is for the 1D polyline + std::vector boundary_hedges; + for(face_descriptor fh : cc_faces) { - halfedge_descriptor h = halfedge(fd, tmesh); + halfedge_descriptor h = halfedge(fh, tmesh); for(int i=0; i<3; ++i) { if(is_border(opposite(h, tmesh), tmesh) || cc_faces.count(face(opposite(h, tmesh), tmesh)) == 0) - cc_border_hedges.push_back(h); + boundary_hedges.push_back(h); h = next(h, tmesh); } } - if(cc_faces.size() == 1) // it is a triangle nothing better can be done - continue; - - working_face_range.insert(cc_faces.begin(), cc_faces.end()); - - // Now, we have a proper selection that we can work on. - - // First, try to smooth if we only care about local self-intersections - // Two different approaches: - // - First, try to constrain edges that are in the zone to smooth and whose dihedral angle is large, - // but not too large (we don't want to constrain edges that are created by foldings) - // - If that fails, try to smooth without any constraints, but make sure that the deviation from - // the first zone is small - // - // If smoothing fails, the face patch is restored to its pre-smoothing state. - // - // Note that there is no need to update the working range because smoothing doesn`t change - // the number of faces (and old faces are re-used). - bool fixed_by_smoothing = false; - -#ifndef CGAL_PMP_REMOVE_SELF_INTERSECTIONS_NO_SMOOTHING - if(only_treat_self_intersections_locally) + // detect vertices visited more than once along + // a hole border. We then remove all faces incident + // to such a vertex to force the removal of the vertex. + // Actually even if two holes are sharing a vertex, this + // vertex will be removed. It is not needed but since + // we do not yet have one halfedge per hole it is simpler + // and does not harm + std::set border_vertices; + for(halfedge_descriptor h : boundary_hedges) { - fixed_by_smoothing = remove_self_intersections_with_smoothing(cc_faces, true, tmesh, vpmap, gt, verbose); - if(!fixed_by_smoothing) // try again, but without constraining sharp edges - fixed_by_smoothing = remove_self_intersections_with_smoothing(cc_faces, false, tmesh, vpmap, gt, verbose); - } -#endif - - // remove faces from the set to process - for(const face_descriptor f : cc_faces) - faces_to_remove.erase(f); - -#ifndef CGAL_PMP_REMOVE_SELF_INTERSECTIONS_NO_SMOOTHING - if(fixed_by_smoothing) - { - something_was_done = true; - continue; - } - else if(verbose) - { - std::cout << " DEBUG: Could not be solved via smoothing\n"; - } -#endif - - if(verbose) - std::cout << " DEBUG: Trying hole-filling based approach\n"; - - if(!is_selection_a_topological_disk(cc_faces, tmesh)) - { - // check if the selection contains cycles of border halfedges - bool only_border_edges = true; - std::set mesh_border_hedge; - - for(halfedge_descriptor h : cc_border_hedges) + if(!border_vertices.insert(target(h, tmesh)).second) { - if(!is_border(opposite(h, tmesh), tmesh)) - only_border_edges = false; + bool any_face_added = false; + for(halfedge_descriptor hh : halfedges_around_target(h, tmesh)) + { + if(!is_border(hh, tmesh)) + { + // add the face to the current selection + any_face_added |= cc_faces.insert(face(hh, tmesh)).second; + faces_to_remove.erase(face(hh, tmesh)); + } + } + + if(any_face_added) + non_manifold_vertex_removed = true; else - mesh_border_hedge.insert(opposite(h, tmesh)); + non_manifold_vertex_remaining_in_selection = true; + } + } + + if(!non_manifold_vertex_removed) + break; + } + while(true); + + if(preserve_genus && non_manifold_vertex_remaining_in_selection) + { + topology_issue = true; + if(verbose) + std::cout << " DEBUG: CC not handled due to the presence at least one non-manifold vertex\n"; + + continue; // cannot replace a patch containing a nm vertex by a disk + } + + // before running this function if preserve_genus=false, we duplicated + // all of them + CGAL_assertion(!non_manifold_vertex_remaining_in_selection); + + // Collect halfedges on the boundary of the region to be selected + // (pointing inside the domain to be remeshed) + std::vector cc_border_hedges; + for(face_descriptor fd : cc_faces) + { + halfedge_descriptor h = halfedge(fd, tmesh); + for(int i=0; i<3; ++i) + { + if(is_border(opposite(h, tmesh), tmesh) || cc_faces.count(face(opposite(h, tmesh), tmesh)) == 0) + cc_border_hedges.push_back(h); + + h = next(h, tmesh); + } + } + + if(cc_faces.size() == 1) // it is a triangle nothing better can be done + continue; + + working_face_range.insert(cc_faces.begin(), cc_faces.end()); + + // Now, we have a proper selection that we can work on. + +#ifndef CGAL_PMP_REMOVE_SELF_INTERSECTIONS_NO_SMOOTHING + // First, try to smooth if we only care about local self-intersections + // Two different approaches: + // - First, try to constrain edges that are in the zone to smooth and whose dihedral angle is large, + // but not too large (we don't want to constrain edges that are created by foldings) + // - If that fails, try to smooth without any constraints, but make sure that the deviation from + // the first zone is small + // + // If smoothing fails, the face patch is restored to its pre-smoothing state. + // + // Note that there is no need to update the working range because smoothing doesn`t change + // the number of faces (and old faces are re-used). + bool fixed_by_smoothing = false; + + if(only_treat_self_intersections_locally) + { + fixed_by_smoothing = remove_self_intersections_with_smoothing(cc_faces, true, tmesh, vpmap, gt, verbose); + if(!fixed_by_smoothing) // try again, but without constraining sharp edges + { + if(verbose) + std::cout << " DEBUG: Could not be solved via smoothing with constraints\n"; + + fixed_by_smoothing = remove_self_intersections_with_smoothing(cc_faces, false, tmesh, vpmap, gt, verbose); + } + } + + if(fixed_by_smoothing) + { + if(verbose) + std::cout << " DEBUG: Solved with smoothing!\n"; + + something_was_done = true; + continue; + } + else if(verbose) + { + std::cout << " DEBUG: Could not be solved via smoothing\n"; + } +#endif + + if(verbose) + std::cout << " DEBUG: Trying hole-filling based approach\n"; + + if(!is_selection_a_topological_disk(cc_faces, tmesh)) + { + // check if the selection contains cycles of border halfedges + bool only_border_edges = true; + std::set mesh_border_hedge; + + for(halfedge_descriptor h : cc_border_hedges) + { + if(!is_border(opposite(h, tmesh), tmesh)) + only_border_edges = false; + else + mesh_border_hedge.insert(opposite(h, tmesh)); + } + + int nb_cycles = 0; + while(!mesh_border_hedge.empty()) + { + // we must count the number of cycle of boundary edges + halfedge_descriptor h_b = *mesh_border_hedge.begin(), h=h_b; + mesh_border_hedge.erase(mesh_border_hedge.begin()); + do + { + h = next(h, tmesh); + if(h == h_b) + { + // found a cycle + ++nb_cycles; + break; + } + else + { + typename std::set::iterator it = mesh_border_hedge.find(h); + if(it == mesh_border_hedge.end()) + break; // not a cycle + + mesh_border_hedge.erase(it); + } + } + while(true); + } + + if(nb_cycles > (only_border_edges ? 1 : 0)) + { + if(verbose) + std::cout << " DEBUG: CC not handled due to the presence of " + << nb_cycles << " of boundary edges\n"; + + topology_issue = true; + continue; + } + else + { + if(preserve_genus) + { + if(verbose) + std::cout << " DEBUG: CC not handled because it is not a topological disk (preserve_genus=true)\n"; + + all_fixed = false; + continue; } - int nb_cycles = 0; - while(!mesh_border_hedge.empty()) + // count the number of cycles of halfedges of the boundary + std::map bhs; + for(halfedge_descriptor h : cc_border_hedges) + bhs[source(h, tmesh)] = target(h, tmesh); + + int nbc=0; + while(!bhs.empty()) { - // we must count the number of cycle of boundary edges - halfedge_descriptor h_b = *mesh_border_hedge.begin(), h=h_b; - mesh_border_hedge.erase(mesh_border_hedge.begin()); + ++nbc; + std::pair top=*bhs.begin(); + bhs.erase(bhs.begin()); + do { - h = next(h, tmesh); - if(h == h_b) - { - // found a cycle - ++nb_cycles; + typename std::map::iterator it_find = bhs.find(top.second); + if(it_find == bhs.end()) break; - } - else - { - typename std::set::iterator it = mesh_border_hedge.find(h); - if(it == mesh_border_hedge.end()) - break; // not a cycle - mesh_border_hedge.erase(it); - } + top = *it_find; + bhs.erase(it_find); } while(true); } - if(nb_cycles > (only_border_edges ? 1 : 0)) + if(nbc != 1) { if(verbose) - std::cout << " DEBUG: CC not handled due to the presence of " - << nb_cycles << " of boundary edges\n"; + std::cout << " DEBUG: CC not handled because it is not a topological disk(" + << nbc << " boundary cycles)\n"; - topology_issue = true; + all_fixed = false; continue; } else { - if(preserve_genus) - { - if(verbose) - std::cout << " DEBUG: CC not handled because it is not a topological disk (preserve_genus=true)\n"; - - all_fixed = false; - continue; - } - - // count the number of cycles of halfedges of the boundary - std::map bhs; - for(halfedge_descriptor h : cc_border_hedges) - bhs[source(h, tmesh)] = target(h, tmesh); - - int nbc=0; - while(!bhs.empty()) - { - ++nbc; - std::pair top=*bhs.begin(); - bhs.erase(bhs.begin()); - - do - { - typename std::map::iterator it_find = bhs.find(top.second); - if(it_find == bhs.end()) - break; - - top = *it_find; - bhs.erase(it_find); - } - while(true); - } - - if(nbc != 1) - { - if(verbose) - std::cout << " DEBUG: CC not handled because it is not a topological disk(" - << nbc << " boundary cycles)\n"; - - all_fixed = false; - continue; - } - else - { - if(verbose) - std::cout << " DEBUG: CC that is not a topological disk but has only one boundary cycle(preserve_genus=false)\n"; - } + if(verbose) + std::cout << " DEBUG: CC that is not a topological disk but has only one boundary cycle(preserve_genus=false)\n"; } } + } - // sort halfedges so that they describe the sequence of halfedges of the hole to be made - if(!remove_self_intersections_with_hole_filling(cc_border_hedges, cc_faces, working_face_range, - only_treat_self_intersections_locally, - tmesh, vpmap, gt, verbose)) - { - if(verbose) - std::cout << " DEBUG: Failed to fill hole\n"; + // sort halfedges so that they describe the sequence of halfedges of the hole to be made + if(!remove_self_intersections_with_hole_filling(cc_border_hedges, cc_faces, working_face_range, + only_treat_self_intersections_locally, + tmesh, vpmap, gt, verbose)) + { + if(verbose) + std::cout << " DEBUG: Failed to fill hole\n"; - all_fixed = false; - } - else - { - something_was_done = true; - } + all_fixed = false; + } + else + { + something_was_done = true; } } @@ -1610,13 +1679,20 @@ bool remove_self_intersections(const FaceRange& face_range, typedef typename GetGeomTraits::type GeomTraits; GeomTraits gt = choose_parameter(get_parameter(np, internal_np::geom_traits), GeomTraits()); - const int max_steps = choose_parameter(get_parameter(np, internal_np::number_of_iterations), 7); bool verbose = choose_parameter(get_parameter(np, internal_np::verbosity_level), 0) > 0; bool preserve_genus = choose_parameter(get_parameter(np, internal_np::preserve_genus), true); - const bool only_treat_self_intersections_locally = true; + const bool only_treat_self_intersections_locally = true; verbose = true; // @tmp + // When treating intersections locally, we don't want to grow the working range too much + const int default_max_step = only_treat_self_intersections_locally ? 2 : 7; + const int max_steps = choose_parameter(get_parameter(np, internal_np::number_of_iterations), default_max_step); + + // @fixme give it its own named parameter rather than abusing 'with_dihedral_angle'? + const double strong_dihedral_angle = choose_parameter(get_parameter(np, internal_np::with_dihedral_angle), 60.); + const double weak_dihedral_angle = choose_parameter(get_parameter(np, internal_np::weak_dihedral_angle), 20.); + std::set working_face_range(face_range.begin(), face_range.end()); if(verbose) @@ -1641,6 +1717,8 @@ bool remove_self_intersections(const FaceRange& face_range, // of the previous patches or something. self_intersections(working_face_range, tmesh, std::back_inserter(self_inter)); + std::cout << self_inter.size() << " intersecting pairs" << std::endl; + for(const Face_pair& fp : self_inter) { faces_to_remove.insert(fp.first); @@ -1667,6 +1745,13 @@ bool remove_self_intersections(const FaceRange& face_range, } } +#ifdef CGAL_PMP_REMOVE_SELF_INTERSECTION_DEBUG + std::cout << "solved by constrained smoothing: " << internal::self_intersections_solved_by_constrained_smoothing << std::endl; + std::cout << "solved by unconstrained smoothing: " << internal::self_intersections_solved_by_unconstrained_smoothing << std::endl; + std::cout << "solved by constrained hole-filling: " << internal::self_intersections_solved_by_constrained_hole_filling << std::endl; + std::cout << "solved by unconstrained hole-filling: " << internal::self_intersections_solved_by_unconstrained_hole_filling << std::endl; +#endif + return step < max_steps; } From 159a46c08bb8555a68b597306a84e4682ee8289d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mael=20Rouxel-Labb=C3=A9?= Date: Thu, 2 Jan 2020 16:15:04 +0100 Subject: [PATCH 24/56] Update license headers --- .../CGAL/Polygon_mesh_processing/manifoldness.h | 16 +++------------- .../remove_degeneracies.h | 13 ++----------- .../remove_self_intersections.h | 13 ++----------- 3 files changed, 7 insertions(+), 35 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/manifoldness.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/manifoldness.h index 43b90a95aa0..5ffa00ffeda 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/manifoldness.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/manifoldness.h @@ -1,25 +1,15 @@ -// Copyright (c) 2015 GeometryFactory (France). +// Copyright (c) 2015-2019 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+ -// +// SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-Commercial // // Author(s) : Sebastien Loriot, // Mael Rouxel-Labbé - +// #ifndef CGAL_POLYGON_MESH_PROCESSING_MANIFOLDNESS_H #define CGAL_POLYGON_MESH_PROCESSING_MANIFOLDNESS_H diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/remove_degeneracies.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/remove_degeneracies.h index afb95994480..a61062ddf25 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/remove_degeneracies.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/remove_degeneracies.h @@ -2,23 +2,14 @@ // 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+ +// SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-Commercial // // Author(s) : Sebastien Loriot, // Mael Rouxel-Labbé - +// #ifndef CGAL_POLYGON_MESH_PROCESSING_REMOVE_DEGENERACIES_H #define CGAL_POLYGON_MESH_PROCESSING_REMOVE_DEGENERACIES_H diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/remove_self_intersections.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/remove_self_intersections.h index fd536ef364c..620ca683c7a 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/remove_self_intersections.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/remove_self_intersections.h @@ -2,23 +2,14 @@ // 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+ +// SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-Commercial // // Author(s) : Sebastien Loriot, // Mael Rouxel-Labbé - +// #ifndef CGAL_POLYGON_MESH_PROCESSING_REMOVE_SELF_INTERSECTIONS_H #define CGAL_POLYGON_MESH_PROCESSING_REMOVE_SELF_INTERSECTIONS_H From 1eb323fcbdb8b6d782cbb899d770d0325dddb6a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mael=20Rouxel-Labb=C3=A9?= Date: Thu, 23 Jan 2020 11:58:01 +0100 Subject: [PATCH 25/56] Relax assertion due to numerical errors that can sneak in Logically speaking, the vector nb is the bisector of ni and nj, so ni.nb and nj.nb are positive by construction. --- .../CGAL/Polygon_mesh_processing/compute_normal.h | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/compute_normal.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/compute_normal.h index e3696de26ff..89c9593b761 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/compute_normal.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/compute_normal.h @@ -6,7 +6,7 @@ // $URL$ // $Id$ // SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-Commercial -// +// // // Author(s) : Jane Tournois // Mael Rouxel-Labbé @@ -271,6 +271,8 @@ bool does_enclose_other_normals(const std::size_t i, const std::size_t j, const // with theta_j - theta_i = theta_bound const FT sp_diff_bound = nbn * 0.00017453292431333; const FT sp_bl = sp(nb, nl); + + // norm of nl is 1 by construction if(CGAL::abs(sp_bi - sp_bl) <= sp_diff_bound) continue; @@ -382,9 +384,10 @@ compute_most_visible_normal_2_points(std::vector= - std::numeric_limits::epsilon()); - CGAL_assertion(sp_bi >= 0); + sp_bi = (std::max)(FT(0), sp_bi); if(sp_bi <= min_sp) continue; From da917e6895768871596f575b77df223c055c22e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mael=20Rouxel-Labb=C3=A9?= Date: Mon, 3 Feb 2020 18:10:16 +0100 Subject: [PATCH 26/56] Fix missing ` in doc --- Kernel_23/doc/Kernel_23/CGAL/Vector_3.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Kernel_23/doc/Kernel_23/CGAL/Vector_3.h b/Kernel_23/doc/Kernel_23/CGAL/Vector_3.h index 924b35f04d1..f83f350c686 100644 --- a/Kernel_23/doc/Kernel_23/CGAL/Vector_3.h +++ b/Kernel_23/doc/Kernel_23/CGAL/Vector_3.h @@ -68,12 +68,12 @@ introduces a vector `v` initialized to `(x, y, z)`. Vector_3(int x, int y, int z); /*! -introduces a vector `v` initialized to `(x, y, z). +introduces a vector `v` initialized to `(x, y, z)`. */ Vector_3(double x, double y, double z); /*! -introduces a vector `v` initialized to `(hx/hw, hy/hw, hz/hw). +introduces a vector `v` initialized to `(hx/hw, hy/hw, hz/hw)`. */ Vector_3(const Kernel::RT &hx, const Kernel::RT &hy, const Kernel::RT &hz, const Kernel::RT &hw = RT(1)); From 38367410c3071b2815040205d86c5326753336fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mael=20Rouxel-Labb=C3=A9?= Date: Mon, 3 Feb 2020 18:10:41 +0100 Subject: [PATCH 27/56] Fix some corner cases with the new compute normals and degenerate faces --- .../Polygon_mesh_processing/compute_normal.h | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/compute_normal.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/compute_normal.h index 89c9593b761..aea4d41350d 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/compute_normal.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/compute_normal.h @@ -310,18 +310,18 @@ typename GT::Vector_3 compute_normals_bisector(const typename GT::Vector_3& ni, Vector_3 nb = cv_3(CGAL::NULL_VECTOR); - if(almost_equal(ni, nj, traits)) + if(almost_equal(ni, nj, traits) || nk == CGAL::NULL_VECTOR) { if(almost_equal(nj, nk, traits)) nb = ni; else // ni == nj, but nij != nk nb = compute_normals_bisector(nj, nk, traits); } - else if(almost_equal(ni, nk, traits)) // ni != nj + else if(almost_equal(ni, nk, traits) || nj == CGAL::NULL_VECTOR) // ni != nj { nb = compute_normals_bisector(nj, nk, traits); } - else if(almost_equal(nj, nk, traits)) // ni != nj, ni != nk + else if(almost_equal(nj, nk, traits) || ni == CGAL::NULL_VECTOR) // ni != nj, ni != nk { nb = compute_normals_bisector(ni, nk, traits); } @@ -344,7 +344,7 @@ typename GT::Vector_3 compute_normals_bisector(const typename GT::Vector_3& ni, return csv_3(csv_3(cslv_3(ni, third), cslv_3(nj, third)), cslv_3(nk, third)); } - nb = cv_3(CGAL::ORIGIN, c); // note that this isn't normalized + nb = cv_3(CGAL::ORIGIN, c); // not normalized } return nb; @@ -431,6 +431,10 @@ compute_most_visible_normal_3_points(const std::vector 2); + // The vertex has only two incident faces with opposite normals (fold)... + // @todo devise something based on the directions of the 2/3/4 incident edges? + if(incident_faces.size() == 2 && res == CGAL::NULL_VECTOR) + return res; + + CGAL_assertion(incident_faces.size() >= 2); return compute_most_visible_normal_3_points(incident_faces, face_normals, traits); } From 6339e80f7101986236efe8465443d2c7dd676ac4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mael=20Rouxel-Labb=C3=A9?= Date: Mon, 3 Feb 2020 18:12:15 +0100 Subject: [PATCH 28/56] Remove Hausdorff checks, properly pass parameters, polish --- .../remove_self_intersections.h | 532 ++++++++++-------- 1 file changed, 297 insertions(+), 235 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/remove_self_intersections.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/remove_self_intersections.h index 620ca683c7a..f4f8635cd87 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/remove_self_intersections.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/remove_self_intersections.h @@ -18,7 +18,6 @@ #include #include #include -#include // only for the preconditions #include #include #include @@ -32,6 +31,7 @@ #include #include +#include #include #include #include @@ -40,9 +40,6 @@ #include #include -// @tmp to debug -#define CGAL_PMP_REMOVE_SELF_INTERSECTION_DEBUG - // #define CGAL_PMP_REMOVE_SELF_INTERSECTIONS_NO_SMOOTHING // #define CGAL_PMP_REMOVE_SELF_INTERSECTIONS_NO_CONSTRAINTS_IN_HOLE_FILLING @@ -69,22 +66,6 @@ namespace internal { static int self_intersections_solved_by_unconstrained_hole_filling = 0; #endif -template -double compute_hausdorff_distance(const TriangleMesh& old_patch, - const TriangleMesh& new_patch) -{ -#if defined(CGAL_LINKED_WITH_TBB) - typedef CGAL::Parallel_tag Tag -#else - typedef CGAL::Sequential_tag Tag; -#endif - - const double d = Polygon_mesh_processing::approximate_symmetric_Hausdorff_distance( - new_patch, old_patch, parameters::number_of_points_on_edges(10) - .number_of_points_on_faces(100)); - return d; -} - // -*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*- // @todo these could be extracted to somewhere else, it's useful in itself @@ -97,8 +78,7 @@ FaceOutputIterator replace_faces_with_patch(const std::vector >& patch, PolygonMesh& pmesh, VPM& vpm, - FaceOutputIterator out, - const bool verbose) + FaceOutputIterator out) { CGAL_static_assertion((std::is_same::value_type, Point>::value)); @@ -110,8 +90,7 @@ FaceOutputIterator replace_faces_with_patch(const std::vector Point_face; typedef std::vector Vertex_face; - CGAL_precondition(pmesh.is_valid()); // @tmp remove - CGAL_precondition(is_valid_polygon_mesh(pmesh)); // @tmp switch to '_expensive_' + CGAL_precondition(is_valid_polygon_mesh(pmesh)); // To be used to create new elements std::vector vertex_stack(interior_vertices.begin(), interior_vertices.end()); @@ -258,14 +237,17 @@ FaceOutputIterator replace_faces_with_patch(const std::vector >& patch, PolygonMesh& pmesh, VPM& vpm, - FaceOutputIterator out, - const bool verbose) + FaceOutputIterator out) { typedef typename boost::graph_traits::vertex_descriptor vertex_descriptor; typedef typename boost::graph_traits::halfedge_descriptor halfedge_descriptor; @@ -328,18 +309,17 @@ FaceOutputIterator replace_faces_with_patch(const std::set void replace_faces_with_patch(const std::set::face_descriptor>& faces, const std::vector >& patch, PolygonMesh& pmesh, - VPM& vpm, - const bool verbose = false) + VPM& vpm) { CGAL::Emptyset_iterator out; - replace_faces_with_patch(faces, patch, pmesh, vpm, out, verbose); + replace_faces_with_patch(faces, patch, pmesh, vpm, out); } // -*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*- @@ -348,7 +328,7 @@ template >& point_patch, const FaceRange& face_range, const PolygonMesh& tmesh, - const VertexPointMap& vpmap) + const VertexPointMap vpmap) { typedef typename boost::graph_traits::halfedge_descriptor halfedge_descriptor; typedef typename boost::graph_traits::face_descriptor face_descriptor; @@ -370,10 +350,12 @@ void back_up_face_range_as_point_patch(std::vector >& point_p template void constrain_sharp_and_border_edges(const FaceRange& faces, + TriangleMesh& tmesh, EdgeConstrainMap& eif, const bool constrain_sharp_edges, - TriangleMesh& tmesh, - VertexPointMap& vpmap, + const double dihedral_angle, + const double /*weak_DA*/, + VertexPointMap vpmap, const GeomTraits& gt) { typedef typename boost::graph_traits::halfedge_descriptor halfedge_descriptor; @@ -396,9 +378,9 @@ void constrain_sharp_and_border_edges(const FaceRange& faces, } } -#if 0 - CGAL::Polygon_mesh_processing::detect_sharp_edges_pp(faces, tmesh, 60., eif, - parameters::weak_dihedral_angle(20.)); +#if 0 // Until detect_features++ is integrated + CGAL::Polygon_mesh_processing::detect_sharp_edges_pp(faces, tmesh, dihedral_angle, eif, + parameters::weak_dihedral_angle(weak_DA)); // borders are also constrained for(const auto& ep : is_border_of_selection) @@ -408,7 +390,7 @@ void constrain_sharp_and_border_edges(const FaceRange& faces, // this is basically the code that is in detect_features (at the very bottom) // but we do not want a folding to be marked as a sharp feature so the dihedral angle is also // bounded from above - const double bound = 60.; // @fixme hardcoded + const double bound = dihedral_angle; const double cos_angle = std::cos(bound * CGAL_PI / 180.); for(const auto& ep : is_border_of_selection) @@ -430,18 +412,18 @@ void constrain_sharp_and_border_edges(const FaceRange& faces, flag = (c <= cos_angle && c >= -cos_angle); } - is_border_of_selection[ep.first] = flag; // @TMP ONLY NEEDED FOR CONSTRAINED EDGES OUTPUT + is_border_of_selection[ep.first] = flag; // Only needed for output, really put(eif, ep.first, flag); } +#endif - // @tmp ------------------------------ +#ifdef CGAL_PMP_REMOVE_SELF_INTERSECTION_OUTPUT std::ofstream out("results/constrained_edges.polylines.txt"); out << std::setprecision(17); for(edge_descriptor e : edges(tmesh)) if(get(eif, e)) out << "2 " << tmesh.point(source(e, tmesh)) << " " << tmesh.point(target(e, tmesh)) << std::endl; out.close(); - // @tmp ------------------------------ #endif } @@ -449,23 +431,21 @@ void constrain_sharp_and_border_edges(const FaceRange& faces, template bool remove_self_intersections_with_smoothing(std::set::face_descriptor>& face_range, - const bool constrain_sharp_edges, TriangleMesh& tmesh, - VertexPointMap& vpmap, - const GeomTraits& gt, - const bool verbose) + const bool constrain_sharp_edges, + const double dihedral_angle, + const double weak_DA, + VertexPointMap vpmap, + const GeomTraits& gt) { namespace CP = CGAL::parameters; typedef typename boost::property_traits::value_type Point; - typedef CGAL::Face_filtered_graph Filtered_graph; - - if(verbose) - { - std::cout << " DEBUG: repair with smoothing... (constraining sharp edges: "; - std::cout << std::boolalpha << constrain_sharp_edges << ")" << std::endl; - } +#ifdef CGAL_PMP_REMOVE_SELF_INTERSECTION_DEBUG + std::cout << " DEBUG: repair with smoothing... (constraining sharp edges: "; + std::cout << std::boolalpha << constrain_sharp_edges << ")" << std::endl; +#endif CGAL_precondition(does_self_intersect(face_range, tmesh)); @@ -473,39 +453,52 @@ bool remove_self_intersections_with_smoothing(std::set > old_patch; back_up_face_range_as_point_patch(old_patch, face_range, tmesh, vpmap); - // Extract the face range as a mesh because we need to compute the Hausdorff distance - const Filtered_graph ffg(tmesh, face_range); +#ifdef CGAL_PMP_REMOVE_SELF_INTERSECTION_OUTPUT + const CGAL::Face_filtered_graph ffg(tmesh, face_range); TriangleMesh patch_mesh; CGAL::copy_face_graph(ffg, patch_mesh, CP::vertex_point_map(vpmap)); + std::ofstream out_p("results/patch_mesh.off"); + out_p << std::setprecision(17); + out_p << patch_mesh; + out_p.close(); +#endif + + std::vector::face_descriptor> dfaces; + degenerate_faces(tmesh, std::back_inserter(dfaces)); + std::cout << dfaces.size() << " dfaces" << std::endl; + // Constrain sharp and border edges typedef CGAL::dynamic_edge_property_t Edge_property_tag; typedef typename boost::property_map::type EIFMap; EIFMap eif = get(Edge_property_tag(), tmesh); - constrain_sharp_and_border_edges(face_range, eif, constrain_sharp_edges, tmesh, vpmap, gt); + constrain_sharp_and_border_edges(face_range, tmesh, eif, constrain_sharp_edges, dihedral_angle, weak_DA, vpmap, gt); // @todo choice of number of iterations? Till convergence && max of 100? Polygon_mesh_processing::smooth_mesh(face_range, tmesh, CP::edge_is_constrained_map(eif) .number_of_iterations(10) .use_safety_constraints(false)); - // Extract the smoothed face range (again for Hausdorff computations) - const Filtered_graph ffg_post(tmesh, face_range); +#ifdef CGAL_PMP_REMOVE_SELF_INTERSECTION_OUTPUT + const CGAL::Face_filtered_graph ffg_post(tmesh, face_range); TriangleMesh post_smoothing_patch_mesh; CGAL::copy_face_graph(ffg_post, post_smoothing_patch_mesh, CP::vertex_point_map(vpmap)); - const bool does_patch_self_intersect = does_self_intersect(face_range, tmesh, CP::vertex_point_map(vpmap)); - const double hausdorff_dist = compute_hausdorff_distance(patch_mesh, post_smoothing_patch_mesh); + std::ofstream out("results/post_constrained_smoothing_patch_mesh.off"); + out << std::setprecision(17); + out << post_smoothing_patch_mesh; + out.close(); +#endif - bool is_acceptable = (!does_patch_self_intersect /*&& hausdorff_dist < 1e10*/); // @fixme hardcoded + const bool does_patch_self_intersect = does_self_intersect(face_range, tmesh, CP::vertex_point_map(vpmap)); std::cout << "does self intersect: " << does_patch_self_intersect << std::endl; - std::cout << "hausdorff dist: " << hausdorff_dist << std::endl; - std::cout << "is_acceptable: " << is_acceptable << std::endl; - if(!is_acceptable) // restore mesh - replace_faces_with_patch(face_range, old_patch, tmesh, vpmap, verbose); + if(does_patch_self_intersect) // restore mesh + { + replace_faces_with_patch(face_range, old_patch, tmesh, vpmap); + } #ifdef CGAL_PMP_REMOVE_SELF_INTERSECTION_DEBUG else { @@ -516,7 +509,7 @@ bool remove_self_intersections_with_smoothing(std::set void dump_cc(const FaceContainer& cc_faces, const TriangleMesh& mesh, @@ -620,6 +615,8 @@ void dump_tentative_hole(std::vector >& point_patch, out.close(); } +#endif // CGAL_PMP_REMOVE_SELF_INTERSECTION_OUTPUT + // -*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*- // Hole filling can be influenced by setting a third point associated to an edge on the border of the hole. @@ -634,7 +631,7 @@ template typename boost::property_traits::value_type construct_artificial_third_point(const typename boost::graph_traits::halfedge_descriptor h, const TriangleMesh& tmesh, - const VertexPointMap& vpmap, + const VertexPointMap vpmap, const GeomTraits& gt) { typedef typename GeomTraits::FT FT; @@ -669,15 +666,14 @@ template ::vertex_descriptor>& cc_border_vertices, std::set::vertex_descriptor>& cc_interior_vertices, std::set::edge_descriptor>& cc_interior_edges, - std::vector >& point_patch, const std::vector& hole_points, const std::vector& third_points, const std::vector::halfedge_descriptor>& cc_border_hedges, const std::set::face_descriptor>& cc_faces, + std::vector >& point_patch, const TriangleMesh& tmesh, - VertexPointMap& /*vpmap*/, - const GeomTraits& /*gt*/, - const bool verbose) + VertexPointMap /*vpmap*/, + const GeomTraits& /*gt*/) { CGAL_static_assertion((std::is_same::value_type, Point>::value)); @@ -720,21 +716,23 @@ bool construct_tentative_hole_patch(std::vector > to_dump; for(const Face_indices& face : patch) @@ -753,7 +751,7 @@ bool construct_tentative_hole_patch(std::vector bool construct_tentative_hole_patch(std::vector::vertex_descriptor>& cc_border_vertices, std::set::vertex_descriptor>& cc_interior_vertices, std::set::edge_descriptor>& cc_interior_edges, - std::vector::value_type> >& point_patch, const std::vector::halfedge_descriptor>& cc_border_hedges, const std::set::face_descriptor>& cc_faces, + std::vector::value_type> >& point_patch, const TriangleMesh& tmesh, - VertexPointMap& vpmap, - const GeomTraits& gt, - const bool verbose) + VertexPointMap vpmap, + const GeomTraits& gt) { typedef typename boost::graph_traits::vertex_descriptor vertex_descriptor; typedef typename boost::graph_traits::halfedge_descriptor halfedge_descriptor; @@ -848,8 +847,8 @@ bool construct_tentative_hole_patch(std::vector= 3); return construct_tentative_hole_patch(cc_border_vertices, cc_interior_vertices, cc_interior_edges, - point_patch, hole_points, third_points, cc_border_hedges, cc_faces, - tmesh, vpmap, gt, verbose); + hole_points, third_points, cc_border_hedges, cc_faces, + point_patch, tmesh, vpmap, gt); } // In that overload, we don't know the border of the patch because the face range is a sub-region @@ -861,9 +860,8 @@ bool construct_tentative_sub_hole_patch(std::vector::face_descriptor>& sub_cc_faces, const std::set::face_descriptor>& cc_faces, TriangleMesh& tmesh, - VertexPointMap& vpmap, - const GeomTraits& gt, - const bool verbose) + VertexPointMap vpmap, + const GeomTraits& gt) { typedef typename boost::graph_traits::vertex_descriptor vertex_descriptor; typedef typename boost::graph_traits::halfedge_descriptor halfedge_descriptor; @@ -903,8 +901,9 @@ bool construct_tentative_sub_hole_patch(std::vector::halfedge_ std::set::face_descriptor>& cc_faces, std::set::face_descriptor>& working_face_range, TriangleMesh& tmesh, - VertexPointMap& vpmap, - const GeomTraits& gt, - const bool verbose) + VertexPointMap vpmap, + const GeomTraits& gt) { typedef typename boost::graph_traits::vertex_descriptor vertex_descriptor; typedef typename boost::graph_traits::edge_descriptor edge_descriptor; @@ -964,8 +962,9 @@ bool fill_hole(std::vector::halfedge_ typedef typename boost::property_traits::value_type Point; - if(verbose) - std::cout << " DEBUG: Attempting hole-filling (no constraints), " << cc_faces.size() << " faces\n"; +#ifdef CGAL_PMP_REMOVE_SELF_INTERSECTION_DEBUG + std::cout << " DEBUG: Attempting hole-filling (no constraints), " << cc_faces.size() << " faces\n"; +#endif if(!order_border_halfedge_range(cc_border_hedges, tmesh)) { @@ -981,11 +980,12 @@ bool fill_hole(std::vector::halfedge_ std::vector > point_patch; - if(!construct_tentative_hole_patch(cc_border_vertices, cc_interior_vertices, cc_interior_edges, point_patch, - cc_border_hedges, cc_faces, tmesh, vpmap, gt, verbose)) + if(!construct_tentative_hole_patch(cc_border_vertices, cc_interior_vertices, cc_interior_edges, + cc_border_hedges, cc_faces, point_patch, tmesh, vpmap, gt)) { - if(verbose) - std::cout << " DEBUG: Failed to find acceptable hole patch\n"; +#ifdef CGAL_PMP_REMOVE_SELF_INTERSECTION_DEBUG + std::cout << " DEBUG: Failed to find acceptable hole patch\n"; +#endif return false; } @@ -999,13 +999,14 @@ bool fill_hole(std::vector::halfedge_ replace_faces_with_patch(cc_border_vertices, cc_interior_vertices, cc_border_hedges, cc_interior_edges, cc_faces, point_patch, tmesh, vpmap, - std::inserter(working_face_range, working_face_range.end()), - verbose); + std::inserter(working_face_range, working_face_range.end())); +#ifdef CGAL_PMP_REMOVE_SELF_INTERSECTION_OUTPUT static int filed_hole_id = 0; std::stringstream oss; oss << "results/filled_basic_" << filed_hole_id++ << ".off" << std::ends; std::ofstream(oss.str().c_str()) << std::setprecision(17) << tmesh; +#endif CGAL_postcondition(is_valid_polygon_mesh(tmesh)); @@ -1017,9 +1018,8 @@ template bool fill_hole(std::set::face_descriptor>& cc_faces, std::set::face_descriptor>& working_face_range, TriangleMesh& tmesh, - VertexPointMap& vpmap, - const GeomTraits& gt, - const bool verbose) + VertexPointMap vpmap, + const GeomTraits& gt) { typedef typename boost::graph_traits::halfedge_descriptor halfedge_descriptor; typedef typename boost::graph_traits::face_descriptor face_descriptor; @@ -1038,7 +1038,7 @@ bool fill_hole(std::set::face_descrip } if(order_border_halfedge_range(cc_border_hedges, tmesh)) - return fill_hole(cc_border_hedges, cc_faces, working_face_range, tmesh, vpmap, gt, verbose); + return fill_hole(cc_border_hedges, cc_faces, working_face_range, tmesh, vpmap, gt); else return false; } @@ -1078,25 +1078,25 @@ bool fill_hole_with_constraints(std::vector::face_descriptor>& cc_faces, std::set::face_descriptor>& working_face_range, TriangleMesh& tmesh, - VertexPointMap& vpmap, - const GeomTraits& gt, - const bool verbose) + const double dihedral_angle, + const double weak_DA, + VertexPointMap vpmap, + const GeomTraits& gt) { typedef typename boost::graph_traits::face_descriptor face_descriptor; typedef typename boost::property_traits::value_type Point; - typedef CGAL::Face_filtered_graph Filtered_graph; - - if(verbose) - std::cout << " DEBUG: Attempting local hole-filling with constrained sharp edges..." << std::endl; +#ifdef CGAL_PMP_REMOVE_SELF_INTERSECTION_DEBUG + std::cout << " DEBUG: Attempting local hole-filling with constrained sharp edges..." << std::endl; +#endif // If we are treating self intersections locally, first try to constrain sharp edges in the hole typedef CGAL::dynamic_edge_property_t Edge_property_tag; typedef typename boost::property_map::type EIFMap; EIFMap eif = get(Edge_property_tag(), tmesh); - constrain_sharp_and_border_edges(cc_faces, eif, true /*constrain_sharp_edges*/, tmesh, vpmap, gt); + constrain_sharp_and_border_edges(cc_faces, tmesh, eif, true /*constrain_sharp_edges*/, dihedral_angle, weak_DA, vpmap, gt); // Partition the hole using these constrained edges std::set visited_faces; @@ -1118,63 +1118,75 @@ bool fill_hole_with_constraints(std::vector use basic hole-filling - return fill_hole(cc_border_hedges, cc_faces, working_face_range, tmesh, vpmap, gt, verbose); + return fill_hole(cc_border_hedges, cc_faces, working_face_range, tmesh, vpmap, gt); } } std::cout << cc_counter << " independent sub holes" << std::endl; +#ifdef CGAL_PMP_REMOVE_SELF_INTERSECTION_OUTPUT + std::ofstream out("results/hole_fillers.off"); + out.precision(17); + + out << "OFF\n"; + out << 3*point_patch.size() << " " << point_patch.size() << " 0\n"; + + for(const auto& f : point_patch) + { + for(const auto& pt : f) + out << pt << "\n"; + } + + int id = 0; + for(std::size_t i=0; i > old_patch; back_up_face_range_as_point_patch(old_patch, cc_faces, tmesh, vpmap); - // Extract the face range as a mesh because we need to compute the Hausdorff distance - const Filtered_graph ffg(tmesh, cc_faces); - TriangleMesh patch_mesh; - CGAL::copy_face_graph(ffg, patch_mesh, parameters::vertex_point_map(vpmap)); - // Plug the hole-filling patch in the mesh std::set new_faces; - replace_faces_with_patch(cc_faces, point_patch, tmesh, vpmap, std::inserter(new_faces, new_faces.end()), verbose); - - // Extract the plugged-in face range as a mesh (again for Hausdorff computations) - const Filtered_graph ffg_post(tmesh, new_faces); - TriangleMesh patch_mesh_post; - CGAL::copy_face_graph(ffg_post, patch_mesh_post, parameters::vertex_point_map(vpmap)); + replace_faces_with_patch(cc_faces, point_patch, tmesh, vpmap, std::inserter(new_faces, new_faces.end())); const bool does_patch_self_intersect = does_self_intersect(new_faces, tmesh, CGAL::parameters::vertex_point_map(vpmap)); - const double hausdorff_dist = compute_hausdorff_distance(patch_mesh, patch_mesh_post); - - const bool is_acceptable = (!does_patch_self_intersect /*&& hausdorff_dist < 1e10*/); for(const face_descriptor f : cc_faces) working_face_range.erase(f); - if(!is_acceptable) + if(does_patch_self_intersect) { - if(verbose) - std::cout << " DEBUG: Failed to fill hole with constrained sharp edges. Using basic version." << std::endl; +#ifdef CGAL_PMP_REMOVE_SELF_INTERSECTION_DEBUG + std::cout << " DEBUG: Failed to fill hole with constrained sharp edges. Using basic version." << std::endl; +#endif // 'old_faces' is equal to 'cc_faces' but needs to be grabbed again since the mesh has been modified std::set old_faces; - replace_faces_with_patch(new_faces, old_patch, tmesh, vpmap, std::inserter(old_faces, old_faces.end()), verbose); + replace_faces_with_patch(new_faces, old_patch, tmesh, vpmap, std::inserter(old_faces, old_faces.end())); working_face_range.insert(old_faces.begin(), old_faces.end()); - return fill_hole(old_faces, working_face_range, tmesh, vpmap, gt, verbose); + return fill_hole(old_faces, working_face_range, tmesh, vpmap, gt); } else { @@ -1187,40 +1199,43 @@ template bool remove_self_intersections_with_hole_filling(std::vector::halfedge_descriptor>& cc_border_hedges, std::set::face_descriptor>& cc_faces, std::set::face_descriptor>& working_face_range, - const bool local_self_intersection_removal, TriangleMesh& tmesh, - VertexPointMap& vpmap, - const GeomTraits& gt, - const bool verbose) + bool local_self_intersection_removal, + const double strong_dihedral_angle, + const double weak_dihedral_angle, + VertexPointMap vpmap, + const GeomTraits& gt) { - // @tmp ------------------------------ +#ifdef CGAL_PMP_REMOVE_SELF_INTERSECTION_OUTPUT std::ofstream out("results/zone_border.polylines.txt"); out << std::setprecision(17); for(const auto& h : cc_border_hedges) out << "2 " << tmesh.point(source(h, tmesh)) << " " << tmesh.point(target(h, tmesh)) << std::endl; out.close(); - // @tmp ------------------------------ - - bool success; +#endif #ifdef CGAL_PMP_REMOVE_SELF_INTERSECTIONS_NO_CONSTRAINTS_IN_HOLE_FILLING - if(false) -#else // Do not try to impose sharp edge constraints if we are not doing local-only self intersections removal - if(local_self_intersection_removal) + local_self_intersection_removal = false; #endif - success = fill_hole_with_constraints(cc_border_hedges, cc_faces, working_face_range, tmesh, vpmap, gt, verbose); + + bool success = false; + if(local_self_intersection_removal) + { + success = fill_hole_with_constraints(cc_border_hedges, cc_faces, working_face_range, tmesh, + strong_dihedral_angle, weak_dihedral_angle, vpmap, gt); + } else - success = fill_hole(cc_border_hedges, cc_faces, working_face_range, tmesh, vpmap, gt, verbose); + { + success = fill_hole(cc_border_hedges, cc_faces, working_face_range, tmesh, vpmap, gt); + } #ifdef CGAL_PMP_REMOVE_SELF_INTERSECTION_DEBUG if(success) { - #ifdef CGAL_PMP_REMOVE_SELF_INTERSECTIONS_NO_CONSTRAINTS_IN_HOLE_FILLING if(local_self_intersection_removal) ++self_intersections_solved_by_constrained_hole_filling; else - #endif ++self_intersections_solved_by_unconstrained_hole_filling; } #endif @@ -1234,12 +1249,13 @@ std::pair remove_self_intersections_one_step(std::set::face_descriptor>& faces_to_remove, std::set::face_descriptor>& working_face_range, TriangleMesh& tmesh, - VertexPointMap& vpmap, - const GeomTraits& gt, const int step, const bool preserve_genus, const bool only_treat_self_intersections_locally, - const bool verbose) + const double strong_dihedral_angle, + const double weak_dihedral_angle, + VertexPointMap vpmap, + const GeomTraits& gt) { typedef boost::graph_traits graph_traits; typedef typename graph_traits::vertex_descriptor vertex_descriptor; @@ -1248,13 +1264,10 @@ remove_self_intersections_one_step(std::set faces_to_remove_copy = faces_to_remove; - if(verbose) - { - std::cout << "DEBUG: running remove_self_intersections_one_step, step " << step - << " with " << faces_to_remove.size() << " intersecting faces\n"; - } - - CGAL_assertion(tmesh.is_valid()); +#ifdef CGAL_PMP_REMOVE_SELF_INTERSECTION_DEBUG + std::cout << "DEBUG: running remove_self_intersections_one_step, step " << step + << " with " << faces_to_remove.size() << " intersecting faces\n"; +#endif bool something_was_done = false; // indicates if a region was successfully remeshed bool all_fixed = true; // indicates if all removal went well @@ -1262,17 +1275,17 @@ remove_self_intersections_one_step(std::set 0) @@ -1313,6 +1338,13 @@ remove_self_intersections_one_step(std::set (only_border_edges ? 1 : 0)) { - if(verbose) - std::cout << " DEBUG: CC not handled due to the presence of " - << nb_cycles << " of boundary edges\n"; +#ifdef CGAL_PMP_REMOVE_SELF_INTERSECTION_DEBUG + std::cout << " DEBUG: CC not handled due to the presence of " + << nb_cycles << " of boundary edges\n"; +#endif topology_issue = true; continue; @@ -1569,8 +1615,9 @@ remove_self_intersections_one_step(std::set::type GeomTraits; GeomTraits gt = choose_parameter(get_parameter(np, internal_np::geom_traits), GeomTraits()); - bool verbose = choose_parameter(get_parameter(np, internal_np::verbosity_level), 0) > 0; bool preserve_genus = choose_parameter(get_parameter(np, internal_np::preserve_genus), true); + const bool only_treat_self_intersections_locally = choose_parameter(get_parameter(np, internal_np::apply_per_connected_component), false); - const bool only_treat_self_intersections_locally = true; - verbose = true; // @tmp - - // When treating intersections locally, we don't want to grow the working range too much + // When treating intersections locally, we don't want to grow the working range too much as + // either the solution is found fast, or it's too difficult and neither local smoothing or local + // hole filling are going to provide nice results. const int default_max_step = only_treat_self_intersections_locally ? 2 : 7; const int max_steps = choose_parameter(get_parameter(np, internal_np::number_of_iterations), default_max_step); // @fixme give it its own named parameter rather than abusing 'with_dihedral_angle'? const double strong_dihedral_angle = choose_parameter(get_parameter(np, internal_np::with_dihedral_angle), 60.); - const double weak_dihedral_angle = choose_parameter(get_parameter(np, internal_np::weak_dihedral_angle), 20.); - std::set working_face_range(face_range.begin(), face_range.end()); + // detect_feature_pp NP (unused for now) + const double weak_dihedral_angle = 0.; // choose_parameter(get_parameter(np, internal_np::weak_dihedral_angle), 20.); - if(verbose) - std::cout << "DEBUG: Starting remove_self_intersections, is_valid(tmesh)? " << is_valid_polygon_mesh(tmesh) << "\n"; +#ifdef CGAL_PMP_REMOVE_SELF_INTERSECTION_DEBUG + std::cout << "DEBUG: Starting remove_self_intersections, is_valid(tmesh)? " << is_valid_polygon_mesh(tmesh) << "\n"; + std::cout << "\tpreserve_genus: " << preserve_genus << std::endl; + std::cout << "\tonly_treat_self_intersections_locally: " << only_treat_self_intersections_locally << std::endl; + std::cout << "\tmax_steps: " << max_steps << std::endl; + std::cout << "\tstrong_dihedral_angle: " << strong_dihedral_angle << std::endl; + std::cout << "\tweak_dihedral_angle: " << weak_dihedral_angle << std::endl; +#endif if(!preserve_genus) duplicate_non_manifold_vertices(tmesh, np); @@ -1697,6 +1756,7 @@ bool remove_self_intersections(const FaceRange& face_range, bool all_fixed = true; // indicates if the filling of all created holes went fine bool topology_issue = false; // indicates if some boundary cycles of edges are blocking the fixing std::set faces_to_remove; + std::set working_face_range(face_range.begin(), face_range.end()); while(++step < max_steps) { @@ -1704,6 +1764,7 @@ bool remove_self_intersections(const FaceRange& face_range, { typedef std::pair Face_pair; std::vector self_inter; + // TODO : possible optimization to reduce the range to check with the bbox // of the previous patches or something. self_intersections(working_face_range, tmesh, std::back_inserter(self_inter)); @@ -1719,21 +1780,22 @@ bool remove_self_intersections(const FaceRange& face_range, if(faces_to_remove.empty() && all_fixed) { - if(verbose) - std::cout << "DEBUG: There are no more faces to remove." << std::endl; +#ifdef CGAL_PMP_REMOVE_SELF_INTERSECTION_DEBUG + std::cout << "DEBUG: There are no more faces to remove." << std::endl; +#endif break; } std::tie(all_fixed, topology_issue) = internal::remove_self_intersections_one_step( - faces_to_remove, working_face_range, tmesh, vpm, gt, - step, preserve_genus, only_treat_self_intersections_locally, verbose); + faces_to_remove, working_face_range, tmesh, + step, preserve_genus, only_treat_self_intersections_locally, + strong_dihedral_angle, weak_dihedral_angle, vpm, gt); +#ifdef CGAL_PMP_REMOVE_SELF_INTERSECTION_DEBUG if(all_fixed && topology_issue) - { - if(verbose) std::cout << "DEBUG: boundary cycles of boundary edges involved in self-intersections.\n"; - } +#endif } #ifdef CGAL_PMP_REMOVE_SELF_INTERSECTION_DEBUG From 81f04ac2d9b93882def4ac7789dda539872485d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mael=20Rouxel-Labb=C3=A9?= Date: Mon, 3 Feb 2020 18:12:45 +0100 Subject: [PATCH 29/56] Move remove_self_intersections to PMP::experimental --- .../mesh_self_intersection_removal_example.cpp | 2 +- .../CGAL/Polygon_mesh_processing/remove_self_intersections.h | 5 ++--- .../demo/Polyhedron/Plugins/PMP/Repair_polyhedron_plugin.cpp | 2 +- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/Polygon_mesh_processing/examples/Polygon_mesh_processing/mesh_self_intersection_removal_example.cpp b/Polygon_mesh_processing/examples/Polygon_mesh_processing/mesh_self_intersection_removal_example.cpp index 9e83a0ac345..67a220c7ae4 100644 --- a/Polygon_mesh_processing/examples/Polygon_mesh_processing/mesh_self_intersection_removal_example.cpp +++ b/Polygon_mesh_processing/examples/Polygon_mesh_processing/mesh_self_intersection_removal_example.cpp @@ -74,7 +74,7 @@ int main(int argc, char* argv[]) PMP::remove_degenerate_faces(mesh); std::ofstream("in.off") << std::setprecision(17) << mesh; - PMP::remove_self_intersections(mesh); + PMP::experimental::remove_self_intersections(mesh); std::ofstream("out.off") << std::setprecision(17) << mesh; std::cout << "Success? " << !PMP::does_self_intersect(mesh) << std::endl; diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/remove_self_intersections.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/remove_self_intersections.h index f4f8635cd87..7c5b1b5604a 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/remove_self_intersections.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/remove_self_intersections.h @@ -1703,7 +1703,7 @@ remove_self_intersections_one_step(std::set bool remove_self_intersections(const FaceRange& face_range, @@ -1826,8 +1826,7 @@ bool remove_self_intersections(TriangleMesh& tmesh) return remove_self_intersections(tmesh, parameters::all_default()); } -/// \endcond - +} // namespace experimental } // namespace Polygon_mesh_processing } // namespace CGAL diff --git a/Polyhedron/demo/Polyhedron/Plugins/PMP/Repair_polyhedron_plugin.cpp b/Polyhedron/demo/Polyhedron/Plugins/PMP/Repair_polyhedron_plugin.cpp index 1e8fb6578ef..f4196b90f22 100644 --- a/Polyhedron/demo/Polyhedron/Plugins/PMP/Repair_polyhedron_plugin.cpp +++ b/Polyhedron/demo/Polyhedron/Plugins/PMP/Repair_polyhedron_plugin.cpp @@ -209,7 +209,7 @@ void Polyhedron_demo_repair_polyhedron_plugin::on_actionRemoveSelfIntersections_ if (poly_item) { bool solved = - CGAL::Polygon_mesh_processing::remove_self_intersections( + CGAL::Polygon_mesh_processing::experimental::remove_self_intersections( *poly_item->polyhedron()); if (!solved) CGAL::Three::Three::information(tr("Some self-intersection could not be fixed")); From a9f29a51d9aa0c4b1d9cf923be794d577d7dad66 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mael=20Rouxel-Labb=C3=A9?= Date: Mon, 3 Feb 2020 18:13:05 +0100 Subject: [PATCH 30/56] Tests improvements --- .../test_pmp_remove_self_intersections.cpp | 126 +++++++++--------- 1 file changed, 65 insertions(+), 61 deletions(-) diff --git a/Polygon_mesh_processing/test/Polygon_mesh_processing/test_pmp_remove_self_intersections.cpp b/Polygon_mesh_processing/test/Polygon_mesh_processing/test_pmp_remove_self_intersections.cpp index e84c9120009..370f2fd1141 100644 --- a/Polygon_mesh_processing/test/Polygon_mesh_processing/test_pmp_remove_self_intersections.cpp +++ b/Polygon_mesh_processing/test/Polygon_mesh_processing/test_pmp_remove_self_intersections.cpp @@ -1,3 +1,7 @@ +#define CGAL_PMP_COMPUTE_NORMAL_DEBUG_PP +#define CGAL_PMP_REMOVE_SELF_INTERSECTION_DEBUG +#define CGAL_PMP_REMOVE_SELF_INTERSECTION_OUTPUT + #include #include @@ -8,7 +12,8 @@ #include #include -namespace PMP = CGAL::Polygon_mesh_processing; +namespace PMP = ::CGAL::Polygon_mesh_processing; +namespace CP = ::CGAL::parameters; typedef CGAL::Exact_predicates_inexact_constructions_kernel K; @@ -17,91 +22,89 @@ typedef CGAL::Surface_mesh Surface_mesh; typedef boost::graph_traits::edge_descriptor edge_descriptor; typedef boost::graph_traits::face_descriptor face_descriptor; -void fix_self_intersections(const char* fname) +void fix_self_intersections(const char* mesh_filename, + const char* mesh_selection_filename = nullptr) { std::cout << std::endl << "---------------" << std::endl; - std::cout << "Test " << fname << std::endl; - - std::ifstream input(fname); - Surface_mesh mesh; - if (!input || !(input >> mesh) || mesh.is_empty()) { - std::cerr << "Error: " << fname << " is not a valid off file.\n"; - std::exit(1); - } - - Surface_mesh mesh_cpy = mesh; - - PMP::remove_self_intersections(mesh); - assert( CGAL::is_valid_polygon_mesh(mesh) ); - CGAL_warning( !PMP::does_self_intersect(mesh) ); - - std::ofstream out("post_repair.off"); - out.precision(17); - out << mesh; - out.close(); - - // just to check compilation - PMP::remove_self_intersections(mesh, CGAL::parameters::number_of_iterations(10)); -} - -void fix_local_self_intersections(const char* mesh_filename, const char* mesh_selection_filename) -{ - std::cout << std::endl << "---------------" << std::endl; - std::cout << "Test " << mesh_filename << " with selection " << mesh_selection_filename << std::endl; + std::cout << "Test " << mesh_filename << std::endl; + if(mesh_selection_filename) + std::cout << "With selection " << mesh_selection_filename << std::endl; std::ifstream input(mesh_filename); Surface_mesh mesh; - if (!input || !(input >> mesh) || mesh.is_empty()) { + if(!input || !(input >> mesh) || mesh.is_empty()) + { std::cerr << "Error: " << mesh_filename << " is not a valid off file.\n"; std::exit(1); } - std::ifstream selection_input(mesh_selection_filename); std::list selected_faces; - std::string line; - // skip the first line (faces are on the second line) - if(!selection_input || !std::getline(selection_input, line) || !std::getline(selection_input, line)) + + if(mesh_selection_filename) { - std::cerr << "Error: could not read selection: " << mesh_selection_filename << std::endl; - std::exit(1); + std::ifstream selection_input(mesh_selection_filename); + std::string line; + // skip the first line (faces are on the second line) + if(!selection_input || !std::getline(selection_input, line) || !std::getline(selection_input, line)) + { + std::cerr << "Error: could not read selection: " << mesh_selection_filename << std::endl; + std::exit(1); + } + + std::istringstream face_line(line); + std::size_t face_id; + while(face_line >> face_id) + selected_faces.push_back(*(faces(mesh).begin() + face_id)); + std::cout << selected_faces.size() << " faces selected" << std::endl; + + PMP::experimental::remove_self_intersections(selected_faces, mesh); + } + else + { + PMP::experimental::remove_self_intersections(mesh); } - - std::istringstream face_line(line); - std::size_t face_id; - while(face_line >> face_id) - selected_faces.push_back(*(faces(mesh).begin() + face_id)); - std::cout << selected_faces.size() << " faces selected" << std::endl; - - PMP::remove_self_intersections(selected_faces, mesh, CGAL::parameters::verbosity_level(true)); - assert( CGAL::is_valid_polygon_mesh(mesh) ); - CGAL_warning( !PMP::does_self_intersect(selected_faces, mesh) ); std::ofstream out("post_repair.off"); out.precision(17); out << mesh; out.close(); - std::unordered_map face_index_map; - int counter = 0; - BOOST_FOREACH(face_descriptor fd, faces(mesh)) - face_index_map[fd] = counter++; + assert(CGAL::is_valid_polygon_mesh(mesh)); + assert(!PMP::does_self_intersect(mesh)); +} - std::ofstream out_final_selection("post_repair.selection.txt"); - out_final_selection << std::endl; // first line are vertex indices +void fix_local_self_intersections(const char* fname) +{ + std::cout << std::endl << "-----LOCAL------" << std::endl; + std::cout << "Test " << fname << std::endl; - BOOST_FOREACH(const face_descriptor fd, selected_faces) - out_final_selection << face_index_map[fd] << " "; - out_final_selection << std::endl << std::endl; + std::ifstream input(fname); + Surface_mesh mesh; + if(!input || !(input >> mesh) || mesh.is_empty()) + { + std::cerr << "Error: " << fname << " is not a valid off file.\n"; + std::exit(1); + } - PMP::remove_self_intersections(selected_faces, mesh); - assert( CGAL::is_valid_polygon_mesh(mesh) ); - CGAL_warning( !PMP::does_self_intersect(selected_faces, mesh) ); + PMP::experimental::remove_self_intersections(mesh, CP::apply_per_connected_component(true)); + + std::ofstream out("post_local_repair.off"); + out.precision(17); + out << mesh; + out.close(); + + assert(CGAL::is_valid_polygon_mesh(mesh)); + assert(!PMP::does_self_intersect(mesh)); } int main() { + std::cout.precision(17); + std::cerr.precision(17); + + fix_local_self_intersections("data_repair/brain.off"); + #if 0 - fix_self_intersections("data_repair/brain.off"); fix_self_intersections("data_repair/flute.off"); fix_self_intersections("data_repair/dinosaur.off"); fix_self_intersections("data_repair/hemispheres.off"); @@ -120,7 +123,8 @@ int main() #endif // Remove only self-intersections within a single hemisphere - fix_local_self_intersections("data_repair/hemispheres.off", "data_repair/hemispheres-half.selection.txt"); +// fix_local_self_intersections("data_repair/hemispheres.off"); +// fix_self_intersections("data_repair/hemispheres.off", "data_repair/hemispheres-half.selection.txt"); return 0; } From 1a3f0cf982c2ba3ff7d86b3044be1283ce321af7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mael=20Rouxel-Labb=C3=A9?= Date: Mon, 3 Feb 2020 18:24:08 +0100 Subject: [PATCH 31/56] Fix corner case in sin weight normal computations --- .../include/CGAL/Polygon_mesh_processing/compute_normal.h | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/compute_normal.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/compute_normal.h index aea4d41350d..df56f9b4a08 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/compute_normal.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/compute_normal.h @@ -543,10 +543,14 @@ compute_vertex_normal_as_sum_of_weighted_normals(typename boost::graph_traits Date: Tue, 4 Feb 2020 15:56:55 +0100 Subject: [PATCH 32/56] More fixes for normal computations of meshes with degenerate faces --- .../include/CGAL/Polygon_mesh_processing/compute_normal.h | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/compute_normal.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/compute_normal.h index df56f9b4a08..88060791f04 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/compute_normal.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/compute_normal.h @@ -264,6 +264,8 @@ bool does_enclose_other_normals(const std::size_t i, const std::size_t j, const continue; const Vector_ref nl = get(face_normals, incident_faces[l]); + if(nl == CGAL::NULL_VECTOR) + continue; // this is a bound on how much the scalar product between (v1,v2) and (v1, v3) can change // when the angle changes theta_bound := 0.01° @@ -431,9 +433,8 @@ compute_most_visible_normal_3_points(const std::vector Date: Tue, 4 Feb 2020 15:58:00 +0100 Subject: [PATCH 33/56] Add some tests of meshes with degeneracies --- .../pmp_compute_normals_test.cpp | 71 +++++++++++++++++-- 1 file changed, 66 insertions(+), 5 deletions(-) diff --git a/Polygon_mesh_processing/test/Polygon_mesh_processing/pmp_compute_normals_test.cpp b/Polygon_mesh_processing/test/Polygon_mesh_processing/pmp_compute_normals_test.cpp index ef4e8a08f8a..a243df48db2 100644 --- a/Polygon_mesh_processing/test/Polygon_mesh_processing/pmp_compute_normals_test.cpp +++ b/Polygon_mesh_processing/test/Polygon_mesh_processing/pmp_compute_normals_test.cpp @@ -1,4 +1,4 @@ -// #define CGAL_PMP_COMPUTE_NORMAL_DEBUG +// #define CGAL_PMP_COMPUTE_NORMAL_DEBUG_PP #include #include @@ -6,7 +6,10 @@ #include #include +#include #include +#include +#include #include #include @@ -27,6 +30,7 @@ void test(const Mesh& mesh, typedef typename K::Vector_3 Vector; typedef typename boost::graph_traits::vertex_descriptor vertex_descriptor; + typedef typename boost::graph_traits::halfedge_descriptor halfedge_descriptor; typedef typename boost::graph_traits::face_descriptor face_descriptor; typedef typename CGAL::GetVertexPointMap::const_type VPMap; @@ -63,12 +67,58 @@ void test(const Mesh& mesh, PMP::compute_normals(mesh, vnormals, fnormals, CGAL::parameters::vertex_point_map(vpmap) .geom_traits(K())); - for(vertex_descriptor v : vertices(mesh)) { - assert(get(vnormals, v) != CGAL::NULL_VECTOR); +#if 1//def CGAL_PMP_COMPUTE_NORMAL_DEBUG_PP + std::ofstream vn_out("vertex_normals.cgal.polylines.txt"); + std::ofstream fn_out("face_normals.cgal.polylines.txt"); + + const CGAL::Bbox_3 bb = PMP::bbox(mesh); + const auto bbox_diagonal = CGAL::sqrt(CGAL::square(bb.xmax() - bb.xmin()) + + CGAL::square(bb.ymax() - bb.ymin()) + + CGAL::square(bb.zmax() - bb.zmin())); + + for(vertex_descriptor v : vertices(mesh)) + vn_out << "2 " << get(vpmap, v) << " " << get(vpmap, v) + 0.1 * bbox_diagonal * get(vnormals, v) << "\n"; + vn_out.close(); + + for(face_descriptor f : faces(mesh)) + { + std::list vertices; + for(halfedge_descriptor h : CGAL::halfedges_around_face(halfedge(f, mesh), mesh)) + vertices.push_back(get(vpmap, target(h, mesh))); + + const typename K::Point_3& c = CGAL::centroid(vertices.begin(), vertices.end()); + fn_out << "2 " << c << " " << c + 0.1 * bbox_diagonal * get(fnormals, f) << "\n"; + } + fn_out.close(); +#endif + + // Check sanity of output + for(const face_descriptor f : faces(mesh)) + { + // tests on non triangular meshes are @todo + if(CGAL::is_triangle(halfedge(f, mesh), mesh)) + { + if(PMP::is_degenerate_triangle_face(f, mesh)) + assert(get(fnormals, f) == CGAL::NULL_VECTOR); + else + assert(get(fnormals, f) != CGAL::NULL_VECTOR); + } } - for(face_descriptor f : faces(mesh)) { - assert(get(fnormals, f) != CGAL::NULL_VECTOR); + for(const vertex_descriptor v : vertices(mesh)) + { + if(get(vnormals, v) == CGAL::NULL_VECTOR) + { + for(halfedge_descriptor h : CGAL::halfedges_around_face(halfedge(v, mesh), mesh)) + { + if(!is_border(h, mesh)) + { + // There are other cases where a vertex normal can be null without the face normals being null, + // (only two incident faces with opposite normals, for example), but they are not tested for now. + assert(get(fnormals, face(h, mesh)) == CGAL::NULL_VECTOR); + } + } + } } } @@ -161,6 +211,17 @@ int main() test("data/mannequin-devil.off"); test("data/U.off"); + test("data_degeneracies/deg_on_border.off"); + test("data_degeneracies/degtri_edge.off"); + test("data_degeneracies/degtri_three.off"); + test("data_degeneracies/degtri_four.off"); + test("data_degeneracies/degtri_nullface.off"); + test("data_degeneracies/degtri_single.off"); + test("data_degeneracies/existing_flip.off"); + test("data_degeneracies/fused_vertices.off"); + test("data_degeneracies/small_ccs.off"); + test("data_degeneracies/trihole.off"); + std::cerr << "All done." << std::endl; return EXIT_SUCCESS; } From 677ea630f87ddbfb991295c592ab0703d45acb17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mael=20Rouxel-Labb=C3=A9?= Date: Wed, 5 Feb 2020 11:32:40 +0100 Subject: [PATCH 34/56] Don't flip anything in weird situations --- .../internal/Smoothing/mesh_smoothing_impl.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Smoothing/mesh_smoothing_impl.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Smoothing/mesh_smoothing_impl.h index 19836618a2b..6c49ec4ae5c 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Smoothing/mesh_smoothing_impl.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Smoothing/mesh_smoothing_impl.h @@ -91,6 +91,10 @@ public: const vertex_descriptor v2 = target(next(h, mesh_), mesh_); const vertex_descriptor v3 = target(next(opp_h, mesh_), mesh_); + std::set unique_vs { v0, v1, v2, v3 }; + if(unique_vs.size() != 4) + return false; + // Don't want to flip if the other diagonal already exists // @todo remeshing can be used to still flip those std::pair other_hd_already_exists = edge(v2, v3, mesh_); From 238e18f002747a775ed66118d0044daf4234ad5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mael=20Rouxel-Labb=C3=A9?= Date: Wed, 5 Feb 2020 15:32:22 +0100 Subject: [PATCH 35/56] Add polygon_mesh_to_polygon_soup (undocumented) --- .../polygon_soup_to_polygon_mesh.h | 94 +++++++++++++++++-- 1 file changed, 85 insertions(+), 9 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/polygon_soup_to_polygon_mesh.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/polygon_soup_to_polygon_mesh.h index 93e4f629576..97b2e24f343 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/polygon_soup_to_polygon_mesh.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/polygon_soup_to_polygon_mesh.h @@ -17,23 +17,99 @@ #include -#include -#include #include -#include -#include -#include +#include +#include +#include +#include +#include +#include + +#include #include #include #include -namespace CGAL +#include + +namespace CGAL { +namespace Polygon_mesh_processing { +namespace internal { + +// \ingroup PMP_repairing_grp +// +// Adds the vertices and faces of a mesh into a (possibly non-empty) soup. +// +// @tparam PolygonMesh a model of `FaceListGraph` with an internal point property map +// @tparam PointRange a model of the concepts `RandomAccessContainer` and +// `BackInsertionSequence` whose value type is the point type +// @tparam PolygonRange a model of the concepts `RandomAccessContainer` and `BackInsertionSequence` whose +// `value_type` is itself a model of the concepts `RandomAccessContainer` and +// `BackInsertionSequence` whose `value_type` is `std::size_t`. +// +// @param mesh the mesh whose faces are being put in the soup +// @param points points of the soup of polygons +// @param polygons each element in the vector describes a polygon using the index of the points in `points` +// +// \sa `CGAL::Polygon_mesh_processing::orient_polygon_soup()` +// \sa `CGAL::Polygon_mesh_processing::is_polygon_soup_a_polygon_mesh()` +// \sa `CGAL::Polygon_mesh_processing::polygon_soup_to_polygon_mesh()` +// +template +void polygon_mesh_to_polygon_soup(const PolygonMesh& mesh, + PointRange& points, + PolygonRange& polygons, + const NamedParameters& np) { -namespace Polygon_mesh_processing -{ -namespace internal + typedef typename boost::graph_traits::vertex_descriptor vertex_descriptor; + typedef typename boost::graph_traits::halfedge_descriptor halfedge_descriptor; + typedef typename boost::graph_traits::face_descriptor face_descriptor; + + using parameters::choose_parameter; + using parameters::get_parameter; + + typedef typename GetVertexPointMap::const_type VPM; + VPM vpm = choose_parameter(get_parameter(np, internal_np::vertex_point), + get_const_property_map(vertex_point, mesh)); + + CGAL::dynamic_vertex_property_t Vertex_index; + typedef typename boost::property_map::type VIM; + VIM vim = get(Vertex_index(), mesh); + + typedef typename boost::range_value Polygon; + + std::size_t index = points.size(); // so that multiple meshes can be put into the same soup + + points.reserve(points.size() + vertices(mesh).size()); + polygons.reserve(polygons.size() + faces(mesh).size()); + + for(const vertex_descriptor v : vertices(mesh)) + { + points.push_back(get(vpmap, v)); + put(vim, v, index++); + } + + for(const face_descriptor f : faces(mesh)) + { + Polygon polygon; + for(halfedge_descriptor h : CGAL::halfedges_around_face(halfedge(f, mesh), mesh)) + polygon.push_back(get(vim, target(h, mesh))); + + polygons.push_back(polygon); + } +} + +template +void polygon_mesh_to_polygon_soup(const PolygonMesh& mesh, + PointRange& points, + PolygonRange& polygons) { + return polygon_mesh_to_polygon_soup(mesh, points, polygons, CGAL::parameters::all_default()); +} + +// ------------------------------------------------------------------------------------------------- + template From a2bdfd57c945de925c75c009136f1151f32401bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mael=20Rouxel-Labb=C3=A9?= Date: Wed, 5 Feb 2020 18:07:21 +0100 Subject: [PATCH 36/56] Never allow degenerate faces to be created in smoothing Regardless of whether "safe moves only" is being used or not. Degenerate faces are annoying because the normal is undefined (null_vector) and then things get tricky. The hope is that if a move would create a degenerate face, we can just not move it, and hope that the degenerate face would not appear on the next move. --- .../internal/Smoothing/mesh_smoothing_impl.h | 35 ++++++++++++++----- 1 file changed, 26 insertions(+), 9 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Smoothing/mesh_smoothing_impl.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Smoothing/mesh_smoothing_impl.h index 6c49ec4ae5c..ebcc20d5015 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Smoothing/mesh_smoothing_impl.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Smoothing/mesh_smoothing_impl.h @@ -544,12 +544,13 @@ public: // Gram Schmidt so that the new location is on the tangent plane of v (i.e. do mv -= (mv*n)*n) const FT sp = traits_.compute_scalar_product_3_object()(vn, move); - move = traits_.construct_sum_of_vectors_3_object()(move, - traits_.construct_scaled_vector_3_object()(vn, - sp)); + move = traits_.construct_sum_of_vectors_3_object()( + move, traits_.construct_scaled_vector_3_object()(vn, - sp)); - Point new_pos = pos + move; - - if((!use_sanity_checks || !does_move_create_bad_faces(v, new_pos)) && + const Point new_pos = pos + move; + if(move != CGAL::NULL_VECTOR && + !does_move_create_degenerate_faces(v, new_pos) && + (!use_sanity_checks || !does_move_create_bad_faces(v, new_pos)) && (!enforce_no_min_angle_regression || does_improve_min_angle_in_star(v, new_pos))) { #ifdef CGAL_PMP_SMOOTHING_DEBUG @@ -619,11 +620,10 @@ private: return get(vcmap_, v); } - // check for degenerate or inversed faces - bool does_move_create_bad_faces(const vertex_descriptor v, - const Point& new_pos) const + // Null faces are bad because they make normal computation difficult + bool does_move_create_degenerate_faces(const vertex_descriptor v, + const Point& new_pos) const { - // check for null faces and face inversions for(halfedge_descriptor main_he : halfedges_around_source(v, mesh_)) { const halfedge_descriptor prev_he = prev(main_he, mesh_); @@ -632,6 +632,23 @@ private: if(traits_.collinear_3_object()(lpt, rpt, new_pos)) return true; + } + + return false; + } + + // check for degenerate or inversed faces + bool does_move_create_bad_faces(const vertex_descriptor v, + const Point& new_pos) const + { + // check for face inversions + for(halfedge_descriptor main_he : halfedges_around_source(v, mesh_)) + { + const halfedge_descriptor prev_he = prev(main_he, mesh_); + const Point_ref lpt = get(vpmap_, target(main_he, mesh_)); + const Point_ref rpt = get(vpmap_, source(prev_he, mesh_)); + + CGAL_assertion(!traits_.collinear_3_object()(lpt, rpt, new_pos)); // checked above const Point_ref old_pos = get(vpmap_, v); Vector ov_1 = traits_.construct_vector_3_object()(old_pos, lpt); From bd31fb867257305a5fbef1533921eea5ffea6206 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mael=20Rouxel-Labb=C3=A9?= Date: Wed, 5 Feb 2020 18:08:54 +0100 Subject: [PATCH 37/56] Fix compilation of polygon_mesh_to_PS --- .../Polygon_mesh_processing/polygon_soup_to_polygon_mesh.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/polygon_soup_to_polygon_mesh.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/polygon_soup_to_polygon_mesh.h index 97b2e24f343..2f5abf3d890 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/polygon_soup_to_polygon_mesh.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/polygon_soup_to_polygon_mesh.h @@ -73,7 +73,7 @@ void polygon_mesh_to_polygon_soup(const PolygonMesh& mesh, VPM vpm = choose_parameter(get_parameter(np, internal_np::vertex_point), get_const_property_map(vertex_point, mesh)); - CGAL::dynamic_vertex_property_t Vertex_index; + typedef CGAL::dynamic_vertex_property_t Vertex_index; typedef typename boost::property_map::type VIM; VIM vim = get(Vertex_index(), mesh); @@ -86,7 +86,7 @@ void polygon_mesh_to_polygon_soup(const PolygonMesh& mesh, for(const vertex_descriptor v : vertices(mesh)) { - points.push_back(get(vpmap, v)); + points.push_back(get(vpm, v)); put(vim, v, index++); } From fb92ab7171ad13aa70663dfe2e8f6a861e2af91f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mael=20Rouxel-Labb=C3=A9?= Date: Wed, 5 Feb 2020 18:09:51 +0100 Subject: [PATCH 38/56] Do smoothing on an extracted mesh instead of the main mesh The point is to avoid having to restore the initial mesh which is costly and also difficult if the initial patch has non-manifoldness. --- .../remove_self_intersections.h | 341 ++++++++++-------- 1 file changed, 191 insertions(+), 150 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/remove_self_intersections.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/remove_self_intersections.h index 7c5b1b5604a..4e867281a4f 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/remove_self_intersections.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/remove_self_intersections.h @@ -18,9 +18,12 @@ #include #include #include +#include +#include #include #include #include +#include #include #include @@ -60,10 +63,10 @@ namespace Polygon_mesh_processing { namespace internal { #ifdef CGAL_PMP_REMOVE_SELF_INTERSECTION_DEBUG - static int self_intersections_solved_by_constrained_smoothing = 0; - static int self_intersections_solved_by_unconstrained_smoothing = 0; - static int self_intersections_solved_by_constrained_hole_filling = 0; - static int self_intersections_solved_by_unconstrained_hole_filling = 0; +static int self_intersections_solved_by_constrained_smoothing = 0; +static int self_intersections_solved_by_unconstrained_smoothing = 0; +static int self_intersections_solved_by_constrained_hole_filling = 0; +static int self_intersections_solved_by_unconstrained_hole_filling = 0; #endif // -*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*- @@ -238,7 +241,7 @@ FaceOutputIterator replace_faces_with_patch(const std::vector >& point_patch, const FaceRange& face_range, const PolygonMesh& tmesh, - const VertexPointMap vpmap) + const VertexPointMap vpm) { typedef typename boost::graph_traits::halfedge_descriptor halfedge_descriptor; typedef typename boost::graph_traits::face_descriptor face_descriptor; @@ -339,7 +342,7 @@ void back_up_face_range_as_point_patch(std::vector >& point_p { std::vector face_points; for(const halfedge_descriptor h : CGAL::halfedges_around_face(halfedge(f, tmesh), tmesh)) - face_points.push_back(get(vpmap, target(h, tmesh))); + face_points.push_back(get(vpm, target(h, tmesh))); point_patch.push_back(face_points); } @@ -355,7 +358,7 @@ void constrain_sharp_and_border_edges(const FaceRange& faces, const bool constrain_sharp_edges, const double dihedral_angle, const double /*weak_DA*/, - VertexPointMap vpmap, + VertexPointMap vpm, const GeomTraits& gt) { typedef typename boost::graph_traits::halfedge_descriptor halfedge_descriptor; @@ -399,11 +402,14 @@ void constrain_sharp_and_border_edges(const FaceRange& faces, if(constrain_sharp_edges && !flag) { const halfedge_descriptor h = halfedge(ep.first, tmesh); + CGAL_assertion(!is_border(edge(h, tmesh), tmesh)); + const face_descriptor f1 = face(h, tmesh); const face_descriptor f2 = face(opposite(h, tmesh), tmesh); - const Vector n1 = compute_face_normal(f1, tmesh, parameters::vertex_point_map(vpmap).geom_traits(gt)); - const Vector n2 = compute_face_normal(f2, tmesh, parameters::vertex_point_map(vpmap).geom_traits(gt)); + // @todo cache normals + const Vector n1 = compute_face_normal(f1, tmesh, parameters::vertex_point_map(vpm).geom_traits(gt)); + const Vector n2 = compute_face_normal(f2, tmesh, parameters::vertex_point_map(vpm).geom_traits(gt)); const FT c = gt.compute_scalar_product_3_object()(n1, n2); // Do not mark as sharp edges with a dihedral angle that is almost `pi` because this is likely @@ -435,11 +441,14 @@ bool remove_self_intersections_with_smoothing(std::set::halfedge_descriptor halfedge_descriptor; + typedef typename boost::graph_traits::face_descriptor face_descriptor; + typedef typename boost::property_traits::value_type Point; #ifdef CGAL_PMP_REMOVE_SELF_INTERSECTION_DEBUG @@ -449,67 +458,67 @@ bool remove_self_intersections_with_smoothing(std::set > old_patch; - back_up_face_range_as_point_patch(old_patch, face_range, tmesh, vpmap); + std::cout << "range: " << face_range.size() << " faces" << std::endl; + std::vector::face_descriptor> dfaces; + degenerate_faces(face_range, tmesh, std::back_inserter(dfaces)); + std::cout << dfaces.size() << " dfaces" << std::endl; + + // Rather than working directly on the mesh, copy a range and work on this instead + const CGAL::Face_filtered_graph ffg(tmesh, face_range); + TriangleMesh local_mesh; + CGAL::copy_face_graph(ffg, local_mesh, CP::vertex_point_map(vpm)); #ifdef CGAL_PMP_REMOVE_SELF_INTERSECTION_OUTPUT - const CGAL::Face_filtered_graph ffg(tmesh, face_range); - TriangleMesh patch_mesh; - CGAL::copy_face_graph(ffg, patch_mesh, CP::vertex_point_map(vpmap)); - - std::ofstream out_p("results/patch_mesh.off"); - out_p << std::setprecision(17); - out_p << patch_mesh; + std::ofstream out_p("results/local_mesh.off"); + out_p << std::setprecision(17) << local_mesh; out_p.close(); #endif - std::vector::face_descriptor> dfaces; - degenerate_faces(tmesh, std::back_inserter(dfaces)); - std::cout << dfaces.size() << " dfaces" << std::endl; - // Constrain sharp and border edges typedef CGAL::dynamic_edge_property_t Edge_property_tag; typedef typename boost::property_map::type EIFMap; - EIFMap eif = get(Edge_property_tag(), tmesh); + EIFMap eif = get(Edge_property_tag(), local_mesh); - constrain_sharp_and_border_edges(face_range, tmesh, eif, constrain_sharp_edges, dihedral_angle, weak_DA, vpmap, gt); + VertexPointMap local_vpm = get_property_map(vertex_point, local_mesh); + + constrain_sharp_and_border_edges(faces(local_mesh), local_mesh, eif, constrain_sharp_edges, + dihedral_angle, weak_DA, local_vpm, gt); // @todo choice of number of iterations? Till convergence && max of 100? - Polygon_mesh_processing::smooth_mesh(face_range, tmesh, CP::edge_is_constrained_map(eif) - .number_of_iterations(10) - .use_safety_constraints(false)); + Polygon_mesh_processing::smooth_mesh(faces(local_mesh), local_mesh, CP::edge_is_constrained_map(eif) + .number_of_iterations(100) + .use_safety_constraints(false)); #ifdef CGAL_PMP_REMOVE_SELF_INTERSECTION_OUTPUT - const CGAL::Face_filtered_graph ffg_post(tmesh, face_range); - TriangleMesh post_smoothing_patch_mesh; - CGAL::copy_face_graph(ffg_post, post_smoothing_patch_mesh, CP::vertex_point_map(vpmap)); - - std::ofstream out("results/post_constrained_smoothing_patch_mesh.off"); - out << std::setprecision(17); - out << post_smoothing_patch_mesh; + std::ofstream out("results/post_smoothing_local_mesh.off"); + out << std::setprecision(17) << local_mesh; out.close(); #endif - const bool does_patch_self_intersect = does_self_intersect(face_range, tmesh, CP::vertex_point_map(vpmap)); + if(does_self_intersect(local_mesh)) + return false; - std::cout << "does self intersect: " << does_patch_self_intersect << std::endl; - - if(does_patch_self_intersect) // restore mesh + std::vector > patch; + for(const face_descriptor f : faces(local_mesh)) { - replace_faces_with_patch(face_range, old_patch, tmesh, vpmap); + halfedge_descriptor h = halfedge(f, local_mesh); + patch.emplace_back(std::initializer_list{get(local_vpm, target(h, local_mesh)), + get(local_vpm, target(next(h, local_mesh), local_mesh)), + get(local_vpm, target(prev(h, local_mesh), local_mesh))}); } + + std::set new_faces; + replace_faces_with_patch(face_range, patch, tmesh, vpm, std::inserter(new_faces, new_faces.end())); + CGAL_assertion(!does_self_intersect(new_faces, tmesh, parameters::vertex_point_map(vpm))); + #ifdef CGAL_PMP_REMOVE_SELF_INTERSECTION_DEBUG + if(constrain_sharp_edges) + ++self_intersections_solved_by_constrained_smoothing; else - { - if(constrain_sharp_edges) - ++self_intersections_solved_by_constrained_smoothing; - else - ++self_intersections_solved_by_unconstrained_smoothing; - } + ++self_intersections_solved_by_unconstrained_smoothing; #endif - return !does_patch_self_intersect; + return true; } // -*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*- @@ -631,7 +640,7 @@ template typename boost::property_traits::value_type construct_artificial_third_point(const typename boost::graph_traits::halfedge_descriptor h, const TriangleMesh& tmesh, - const VertexPointMap vpmap, + const VertexPointMap vpm, const GeomTraits& gt) { typedef typename GeomTraits::FT FT; @@ -639,9 +648,9 @@ construct_artificial_third_point(const typename boost::graph_traits::reference Point_ref; typedef typename GeomTraits::Vector_3 Vector; - const Point_ref p1 = get(vpmap, source(h, tmesh)); - const Point_ref p2 = get(vpmap, target(h, tmesh)); - const Point_ref opp_p = get(vpmap, target(next(h, tmesh), tmesh)); + const Point_ref p1 = get(vpm, source(h, tmesh)); + const Point_ref p2 = get(vpm, target(h, tmesh)); + const Point_ref opp_p = get(vpm, target(next(h, tmesh), tmesh)); // sqrt(3)/2 to have an equilateral triangle with p1, p2, and third_point const FT dist = 0.5 * CGAL::sqrt(3.) * CGAL::approximate_sqrt(gt.compute_squared_distance_3_object()(p1, p2)); @@ -672,7 +681,7 @@ bool construct_tentative_hole_patch(std::vector::face_descriptor>& cc_faces, std::vector >& point_patch, const TriangleMesh& tmesh, - VertexPointMap /*vpmap*/, + VertexPointMap /*vpm*/, const GeomTraits& /*gt*/) { CGAL_static_assertion((std::is_same::value_type, Point>::value)); @@ -707,23 +716,23 @@ bool construct_tentative_hole_patch(std::vector patch; + std::vector hole_faces; if(hole_points.size() > 3) - triangulate_hole_polyline(hole_points, third_points, std::back_inserter(patch)); + triangulate_hole_polyline(hole_points, third_points, std::back_inserter(hole_faces)); else - patch.push_back(Face_indices(0, 1, 2)); // trivial hole filling + hole_faces.emplace_back(0, 1, 2); // trivial hole filling - if(patch.empty()) + if(hole_faces.empty()) { #ifndef CGAL_HOLE_FILLING_DO_NOT_USE_DT3 #ifdef CGAL_PMP_REMOVE_SELF_INTERSECTION_DEBUG std::cout << " DEBUG: Failed to fill a hole using Delaunay search space.\n"; #endif - triangulate_hole_polyline(hole_points, third_points, std::back_inserter(patch), + triangulate_hole_polyline(hole_points, third_points, std::back_inserter(hole_faces), parameters::use_delaunay_triangulation(false)); #endif - if(patch.empty()) + if(hole_faces.empty()) { #ifdef CGAL_PMP_REMOVE_SELF_INTERSECTION_DEBUG std::cout << " DEBUG: Failed to fill a hole using the whole search space.\n"; @@ -733,17 +742,16 @@ bool construct_tentative_hole_patch(std::vector > to_dump; - for(const Face_indices& face : patch) + for(const Face_indices& face : hole_faces) { - std::vector point_face = { hole_points[face.first], - hole_points[face.second], - hole_points[face.third] }; - to_dump.push_back(point_face); + to_dump.emplace_back(std::initializer_list{hole_points[face.first], + hole_points[face.second], + hole_points[face.third]}); } - CGAL_assertion(to_dump.size() == patch.size()); + CGAL_assertion(to_dump.size() == hole_faces.size()); static int hole_id = 0; std::stringstream oss; @@ -754,9 +762,9 @@ bool construct_tentative_hole_patch(std::vector edges = make_array(triangle.first, triangle.second, triangle.second, triangle.third, @@ -791,13 +799,12 @@ bool construct_tentative_hole_patch(std::vector point_face = { hole_points[face.first], - hole_points[face.second], - hole_points[face.third] }; - point_patch.push_back(point_face); + point_patch.emplace_back(std::initializer_list{hole_points[face.first], + hole_points[face.second], + hole_points[face.third]}); } #ifdef CGAL_PMP_REMOVE_SELF_INTERSECTION_DEBUG @@ -816,9 +823,9 @@ bool construct_tentative_hole_patch(std::vector::edge_descriptor>& cc_interior_edges, const std::vector::halfedge_descriptor>& cc_border_hedges, const std::set::face_descriptor>& cc_faces, - std::vector::value_type> >& point_patch, + std::vector::value_type> >& patch, const TriangleMesh& tmesh, - VertexPointMap vpmap, + VertexPointMap vpm, const GeomTraits& gt) { typedef typename boost::graph_traits::vertex_descriptor vertex_descriptor; @@ -833,22 +840,22 @@ bool construct_tentative_hole_patch(std::vector= 3); return construct_tentative_hole_patch(cc_border_vertices, cc_interior_vertices, cc_interior_edges, hole_points, third_points, cc_border_hedges, cc_faces, - point_patch, tmesh, vpmap, gt); + patch, tmesh, vpm, gt); } // In that overload, we don't know the border of the patch because the face range is a sub-region @@ -856,11 +863,11 @@ bool construct_tentative_hole_patch(std::vector -bool construct_tentative_sub_hole_patch(std::vector::value_type> >& point_patch, +bool construct_tentative_sub_hole_patch(std::vector::value_type> >& patch, const std::set::face_descriptor>& sub_cc_faces, const std::set::face_descriptor>& cc_faces, TriangleMesh& tmesh, - VertexPointMap vpmap, + VertexPointMap vpm, const GeomTraits& gt) { typedef typename boost::graph_traits::vertex_descriptor vertex_descriptor; @@ -923,7 +930,7 @@ bool construct_tentative_sub_hole_patch(std::vector::halfedge_ std::set::face_descriptor>& cc_faces, std::set::face_descriptor>& working_face_range, TriangleMesh& tmesh, - VertexPointMap vpmap, + VertexPointMap vpm, const GeomTraits& gt) { typedef typename boost::graph_traits::vertex_descriptor vertex_descriptor; @@ -978,10 +985,9 @@ bool fill_hole(std::vector::halfedge_ std::vector cc_border_vertices; cc_border_vertices.reserve(cc_border_hedges.size()); - std::vector > point_patch; - + std::vector > patch; if(!construct_tentative_hole_patch(cc_border_vertices, cc_interior_vertices, cc_interior_edges, - cc_border_hedges, cc_faces, point_patch, tmesh, vpmap, gt)) + cc_border_hedges, cc_faces, patch, tmesh, vpm, gt)) { #ifdef CGAL_PMP_REMOVE_SELF_INTERSECTION_DEBUG std::cout << " DEBUG: Failed to find acceptable hole patch\n"; @@ -998,7 +1004,7 @@ bool fill_hole(std::vector::halfedge_ // Plug the new triangles in the mesh, reusing previous edges and faces replace_faces_with_patch(cc_border_vertices, cc_interior_vertices, cc_border_hedges, cc_interior_edges, - cc_faces, point_patch, tmesh, vpmap, + cc_faces, patch, tmesh, vpm, std::inserter(working_face_range, working_face_range.end())); #ifdef CGAL_PMP_REMOVE_SELF_INTERSECTION_OUTPUT @@ -1018,7 +1024,7 @@ template bool fill_hole(std::set::face_descriptor>& cc_faces, std::set::face_descriptor>& working_face_range, TriangleMesh& tmesh, - VertexPointMap vpmap, + VertexPointMap vpm, const GeomTraits& gt) { typedef typename boost::graph_traits::halfedge_descriptor halfedge_descriptor; @@ -1038,21 +1044,21 @@ bool fill_hole(std::set::face_descrip } if(order_border_halfedge_range(cc_border_hedges, tmesh)) - return fill_hole(cc_border_hedges, cc_faces, working_face_range, tmesh, vpmap, gt); + return fill_hole(cc_border_hedges, cc_faces, working_face_range, tmesh, vpm, gt); else return false; } // Patch is not valid if: // - we insert the same face more than once -// - insert non-manifold edges -template -bool check_point_patch_sanity(const std::vector >& point_patch) +// - insert (geometric) non-manifold edges +template +bool check_patch_sanity(const std::vector >& patch) { std::set > unique_faces; std::map, int> unique_edges; - for(const std::vector& face : point_patch) + for(const std::vector& face : patch) { if(!unique_faces.emplace(face.begin(), face.end()).second) // this face had already been found return false; @@ -1070,6 +1076,55 @@ bool check_point_patch_sanity(const std::vector >& point_patc return false; } + // Check for self-intersections + // Don't know anything better than just making a mesh out of the soup for now... + std::vector points; + std::vector > faces; + std::map ids; + + std::size_t c = 0; + for(const std::vector& face : patch) + { + std::vector ps_f; + for(const Point& pt : face) + { + std::size_t id; + std::pair::iterator, bool> is_insert_successful = + ids.insert(std::make_pair(pt, c)); + if(is_insert_successful.second) // first time we've seen that point + { + ++c; + points.push_back(pt); + } + else // already seen that point + { + id = is_insert_successful.first->second; + } + + ps_f.push_back(id); + } + + faces.push_back(ps_f); + } + + TriangleMesh patch_mesh; + if(!orient_polygon_soup(points, faces)) + return false; + + if(is_polygon_soup_a_polygon_mesh(faces)) + polygon_soup_to_polygon_mesh(points, faces, patch_mesh); + else + return false; + + bool self_intersect = does_self_intersect(patch_mesh); + + if(self_intersect) + { +#ifdef CGAL_PMP_REMOVE_SELF_INTERSECTION_DEBUG + std::cout << " DEBUG: Tentative patch has self-intersections." << std::endl; +#endif + } + return true; } @@ -1080,7 +1135,7 @@ bool fill_hole_with_constraints(std::vector::face_descriptor face_descriptor; @@ -1096,11 +1151,11 @@ bool fill_hole_with_constraints(std::vector::type EIFMap; EIFMap eif = get(Edge_property_tag(), tmesh); - constrain_sharp_and_border_edges(cc_faces, tmesh, eif, true /*constrain_sharp_edges*/, dihedral_angle, weak_DA, vpmap, gt); + constrain_sharp_and_border_edges(cc_faces, tmesh, eif, true /*constrain_sharp_edges*/, dihedral_angle, weak_DA, vpm, gt); // Partition the hole using these constrained edges std::set visited_faces; - std::vector > point_patch; + std::vector > patch; int cc_counter = 0; for(face_descriptor f : cc_faces) @@ -1122,11 +1177,11 @@ bool fill_hole_with_constraints(std::vector use basic hole-filling - return fill_hole(cc_border_hedges, cc_faces, working_face_range, tmesh, vpmap, gt); + return fill_hole(cc_border_hedges, cc_faces, working_face_range, tmesh, vpm, gt); } } @@ -1137,16 +1192,16 @@ bool fill_hole_with_constraints(std::vector(patch)) { - std::cout << "unhealthy point patch" << std::endl; - return fill_hole(cc_border_hedges, cc_faces, working_face_range, tmesh, vpmap, gt); + std::cout << "Unhealthy patch, use base fill_hole" << std::endl; + return fill_hole(cc_border_hedges, cc_faces, working_face_range, tmesh, vpm, gt); } - // Keep in memory the old patch in case something goes wrong - // or if the result is unsatisfactory (self-intersections) - std::vector > old_patch; - back_up_face_range_as_point_patch(old_patch, cc_faces, tmesh, vpmap); - // Plug the hole-filling patch in the mesh std::set new_faces; - replace_faces_with_patch(cc_faces, point_patch, tmesh, vpmap, std::inserter(new_faces, new_faces.end())); + replace_faces_with_patch(cc_faces, patch, tmesh, vpm, std::inserter(new_faces, new_faces.end())); - const bool does_patch_self_intersect = does_self_intersect(new_faces, tmesh, CGAL::parameters::vertex_point_map(vpmap)); + // Otherwise it should have failed the sanity check + CGAL_assertion(!does_self_intersect(new_faces, tmesh, parameters::vertex_point_map(vpm))); + // Update working range with the new faces for(const face_descriptor f : cc_faces) working_face_range.erase(f); - if(does_patch_self_intersect) - { -#ifdef CGAL_PMP_REMOVE_SELF_INTERSECTION_DEBUG - std::cout << " DEBUG: Failed to fill hole with constrained sharp edges. Using basic version." << std::endl; -#endif + working_face_range.insert(new_faces.begin(), new_faces.end()); - // 'old_faces' is equal to 'cc_faces' but needs to be grabbed again since the mesh has been modified - std::set old_faces; - replace_faces_with_patch(new_faces, old_patch, tmesh, vpmap, std::inserter(old_faces, old_faces.end())); - working_face_range.insert(old_faces.begin(), old_faces.end()); - - return fill_hole(old_faces, working_face_range, tmesh, vpmap, gt); - } - else - { - working_face_range.insert(new_faces.begin(), new_faces.end()); - return true; - } + return true; } template @@ -1203,7 +1240,7 @@ bool remove_self_intersections_with_hole_filling(std::vector graph_traits; @@ -1312,9 +1349,9 @@ remove_self_intersections_one_step(std::set::null_face() && cc_faces.count(nf) == 0) { @@ -1370,7 +1407,7 @@ remove_self_intersections_one_step(std::set Date: Wed, 5 Feb 2020 18:11:10 +0100 Subject: [PATCH 39/56] Add some more verbosity --- .../Polygon_mesh_processing/compute_normal.h | 20 ++++++++++++++++--- .../internal/Smoothing/mesh_smoothing_impl.h | 16 +++++++++++++++ .../Polygon_mesh_processing/smooth_mesh.h | 12 +++++++++++ 3 files changed, 45 insertions(+), 3 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/compute_normal.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/compute_normal.h index 88060791f04..0936d0062b1 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/compute_normal.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/compute_normal.h @@ -97,6 +97,12 @@ void sum_normals(const PM& pmesh, const Point_ref pvnn = get(vpmap, target(next(he, pmesh), pmesh)); const Vector n = internal::triangle_normal(pv, pvn, pvnn, traits); + +#ifdef CGAL_PMP_COMPUTE_NORMAL_DEBUG + std::cout << "Normal of " << f << " pts: " << pv << " ; " << pvn << " ; " << pvnn << std::endl; + std::cout << " --> " << n << std::endl; +#endif + sum = traits.construct_sum_of_vectors_3_object()(sum, n); he = next(he, pmesh); @@ -523,6 +529,10 @@ compute_vertex_normal_as_sum_of_weighted_normals(typename boost::graph_traits::vertex_descript const bool must_compute_face_normals = is_default_parameter(get_parameter(np, internal_np::face_normal)); #ifdef CGAL_PMP_COMPUTE_NORMAL_DEBUG_PP - std::cout << std::endl << std::endl; - std::cout << "----------------------------------------------------------------------" << std::endl; - std::cout << "compute vertex at " << get(vpmap, v) + std::cout << "<----- compute vertex normal at " << get(vpmap, v) << ", must compute face normals? " << must_compute_face_normals << std::endl; #endif diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Smoothing/mesh_smoothing_impl.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Smoothing/mesh_smoothing_impl.h index ebcc20d5015..e47cc8c6c6e 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Smoothing/mesh_smoothing_impl.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Smoothing/mesh_smoothing_impl.h @@ -129,6 +129,10 @@ public: template void operator()(const FaceRange& face_range) { +#ifdef CGAL_PMP_SMOOTHING_VERBOSE + std::cout << "Flipping faces" << std::endl; +#endif + // edges to consider std::vector edge_range; edge_range.reserve(3 * face_range.size()); @@ -168,6 +172,10 @@ public: ++flipped_n; halfedge_descriptor h = halfedge(e, mesh_); + +#ifdef CGAL_PMP_SMOOTHING_VERBOSE + std::cout << "Flipping " << edge(h, mesh_) << std::endl; +#endif Euler::flip_edge(h, mesh_); add_to_stack_if_unmarked(edge(next(h, mesh_), mesh_), marks, edge_range); @@ -534,6 +542,10 @@ public: if(is_border(v, mesh_) || is_constrained(v)) continue; +#ifdef CGAL_PMP_SMOOTHING_DEBUG + std::cout << "Considering " << v << " pos: " << get(vpmap_, v) << std::endl; +#endif + // compute normal to v Vector vn = compute_vertex_normal(v, mesh_, CGAL::parameters::vertex_point_map(vpmap_) .geom_traits(traits_)); @@ -610,6 +622,10 @@ public: Point_ref p_query = get(vpmap_, v); const Point projected = tree.closest_point(p_query); +#ifdef CGAL_PMP_SMOOTHING_DEBUG_PP + std::cout << p_query << " to " << projected << std::endl; +#endif + put(vpmap_, v, projected); } } diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/smooth_mesh.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/smooth_mesh.h index bbe02cfda6b..084b20ca59d 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/smooth_mesh.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/smooth_mesh.h @@ -235,8 +235,16 @@ void smooth_mesh(const FaceRange& faces, for(unsigned int i=0; i Date: Wed, 5 Feb 2020 18:11:26 +0100 Subject: [PATCH 40/56] Mini code cleaning --- .../include/CGAL/Polygon_mesh_processing/smooth_mesh.h | 2 +- .../CGAL/Polygon_mesh_processing/stitch_borders.h | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/smooth_mesh.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/smooth_mesh.h index 084b20ca59d..83cb9629627 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/smooth_mesh.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/smooth_mesh.h @@ -183,7 +183,7 @@ void smooth_mesh(const FaceRange& faces, } ECMap ecmap = choose_parameter(get_parameter(np, internal_np::edge_is_constrained), - Constant_property_map(false)); + Constant_property_map(false)); // a constrained edge has constrained extremities for(face_descriptor f : faces) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/stitch_borders.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/stitch_borders.h index 6a2a519fca4..dfdaab30ec0 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/stitch_borders.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/stitch_borders.h @@ -843,7 +843,7 @@ std::size_t stitch_borders(PolygonMesh& pmesh, template std::size_t stitch_borders(PolygonMesh& pmesh, - const HalfedgePairsRange& hedge_pairs_to_stitch) + const HalfedgePairsRange& hedge_pairs_to_stitch) { return stitch_borders(pmesh, hedge_pairs_to_stitch, CGAL::parameters::all_default()); } @@ -890,9 +890,9 @@ std::size_t stitch_borders(PolygonMesh& pmesh, halfedge_descriptor; std::vector< std::pair > hedge_pairs_to_stitch; - typedef typename GetVertexPointMap::const_type VPMap; - VPMap vpm = choose_parameter(get_parameter(np, internal_np::vertex_point), - get_const_property_map(vertex_point, pmesh)); + typedef typename GetVertexPointMap::const_type VPM + VPM vpm = choose_parameter(get_parameter(np, internal_np::vertex_point), + get_const_property_map(vertex_point, pmesh)); #ifdef CGAL_PMP_STITCHING_DEBUG std::cout << "------- Stitch cycles..." << std::endl; @@ -907,7 +907,7 @@ std::size_t stitch_borders(PolygonMesh& pmesh, internal::collect_duplicated_stitchable_boundary_edges(pmesh, std::back_inserter(hedge_pairs_to_stitch), - internal::Less_for_halfedge(pmesh, vpm), + internal::Less_for_halfedge(pmesh, vpm), vpm, np); res += stitch_borders(pmesh, hedge_pairs_to_stitch, np); From f33618e5e8125d8347daa18f70fc2b5c228cb92a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mael=20Rouxel-Labb=C3=A9?= Date: Thu, 6 Feb 2020 09:54:35 +0100 Subject: [PATCH 41/56] Uniformize macros --- .../Polygon_mesh_processing/compute_normal.h | 6 +++--- .../internal/Smoothing/curvature_flow_impl.h | 4 ++-- .../internal/Smoothing/mesh_smoothing_impl.h | 14 +++++++------- .../internal/Smoothing/smoothing_evaluation.h | 6 +++--- .../CGAL/Polygon_mesh_processing/smooth_mesh.h | 10 +++++----- .../Polygon_mesh_processing/smooth_shape.h | 4 ++-- .../test_shape_smoothing.cpp | 18 +++++++++--------- 7 files changed, 31 insertions(+), 31 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/compute_normal.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/compute_normal.h index 0936d0062b1..c0e4966b215 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/compute_normal.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/compute_normal.h @@ -98,7 +98,7 @@ void sum_normals(const PM& pmesh, const Vector n = internal::triangle_normal(pv, pvn, pvnn, traits); -#ifdef CGAL_PMP_COMPUTE_NORMAL_DEBUG +#ifdef CGAL_PMP_COMPUTE_NORMAL_DEBUG_PP std::cout << "Normal of " << f << " pts: " << pv << " ; " << pvn << " ; " << pvnn << std::endl; std::cout << " --> " << n << std::endl; #endif @@ -211,7 +211,7 @@ void compute_face_normals(const PolygonMesh& pmesh, { typename Kernel::Vector_3 vec = compute_face_normal(f, pmesh, np); put(face_normals, f, vec); -#ifdef CGAL_PMP_COMPUTE_NORMAL_DEBUG +#ifdef CGAL_PMP_COMPUTE_NORMAL_DEBUG_PP std::cout << "normal at face " << f << " is " << get(face_normals, f) << std::endl; #endif } @@ -669,7 +669,7 @@ compute_vertex_normal(typename boost::graph_traits::vertex_descript } } -#ifdef CGAL_PMP_COMPUTE_NORMAL_DEBUG +#ifdef CGAL_PMP_COMPUTE_NORMAL_DEBUG_PP std::cout << "Incident face normals:" << std::endl; for(halfedge_descriptor h : CGAL::halfedges_around_target(v, pmesh)) { diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Smoothing/curvature_flow_impl.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Smoothing/curvature_flow_impl.h index e95b46b5ee4..a201c6e8dde 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Smoothing/curvature_flow_impl.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Smoothing/curvature_flow_impl.h @@ -170,7 +170,7 @@ public: // calls compute once to factorize with the preconditioner if(!solver.factor(A, D)) { -#ifdef CGAL_PMP_SMOOTHING_VERBOSE +#ifdef CGAL_PMP_SMOOTHING_DEBUG std::cerr << "Could not factorize linear system with preconditioner." << std::endl; #endif return false; @@ -180,7 +180,7 @@ public: !solver.linear_solver(by, Xy) || !solver.linear_solver(bz, Xz)) { -#ifdef CGAL_PMP_SMOOTHING_VERBOSE +#ifdef CGAL_PMP_SMOOTHING_DEBUG std::cerr << "Could not solve linear system." << std::endl; #endif return false; diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Smoothing/mesh_smoothing_impl.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Smoothing/mesh_smoothing_impl.h index e47cc8c6c6e..1c5e03cd09e 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Smoothing/mesh_smoothing_impl.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Smoothing/mesh_smoothing_impl.h @@ -129,7 +129,7 @@ public: template void operator()(const FaceRange& face_range) { -#ifdef CGAL_PMP_SMOOTHING_VERBOSE +#ifdef CGAL_PMP_SMOOTHING_DEBUG std::cout << "Flipping faces" << std::endl; #endif @@ -173,7 +173,7 @@ public: halfedge_descriptor h = halfedge(e, mesh_); -#ifdef CGAL_PMP_SMOOTHING_VERBOSE +#ifdef CGAL_PMP_SMOOTHING_DEBUG_PP std::cout << "Flipping " << edge(h, mesh_) << std::endl; #endif Euler::flip_edge(h, mesh_); @@ -542,7 +542,7 @@ public: if(is_border(v, mesh_) || is_constrained(v)) continue; -#ifdef CGAL_PMP_SMOOTHING_DEBUG +#ifdef CGAL_PMP_SMOOTHING_DEBUG_PP std::cout << "Considering " << v << " pos: " << get(vpmap_, v) << std::endl; #endif @@ -565,7 +565,7 @@ public: (!use_sanity_checks || !does_move_create_bad_faces(v, new_pos)) && (!enforce_no_min_angle_regression || does_improve_min_angle_in_star(v, new_pos))) { -#ifdef CGAL_PMP_SMOOTHING_DEBUG +#ifdef CGAL_PMP_SMOOTHING_DEBUG_PP std::cout << "moving " << get(vpmap_, v) << " to " << new_pos << std::endl; total_displacement += CGAL::approximate_sqrt(traits_.compute_squared_length_3_object()(move)); #endif @@ -579,7 +579,7 @@ public: } else // some sanity check failed { -#ifdef CGAL_PMP_SMOOTHING_DEBUG +#ifdef CGAL_PMP_SMOOTHING_DEBUG_PP std::cout << "move is rejected!" << std::endl; #endif if(apply_moves_in_single_batch) @@ -676,7 +676,7 @@ private: if(!is_positive(traits_.compute_scalar_product_3_object()(old_n, new_n))) { -#ifdef CGAL_PMP_SMOOTHING_DEBUG +#ifdef CGAL_PMP_SMOOTHING_DEBUG_PP std::cout << "Moving vertex would result in the inversion of a face normal!" << std::endl; #endif return true; @@ -718,7 +718,7 @@ private: get_radian_angle(Vector(lpt, rpt), Vector(lpt, new_pos), traits_) < old_min_angle || get_radian_angle(Vector(rpt, new_pos), Vector(rpt, lpt), traits_) < old_min_angle) { -#ifdef CGAL_PMP_SMOOTHING_DEBUG +#ifdef CGAL_PMP_SMOOTHING_DEBUG_PP const Point_ref old_pos = get(vpmap_, v); std::cout << "deterioration of min angle in the star!" << std::endl; diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Smoothing/smoothing_evaluation.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Smoothing/smoothing_evaluation.h index f2c2ccbe000..420a654bedf 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Smoothing/smoothing_evaluation.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Smoothing/smoothing_evaluation.h @@ -61,7 +61,7 @@ public: angles_.push_back(traits_.compute_approximate_angle_3_object()(a, b, c)); } -#ifdef CGAL_PMP_SMOOTHING_VERBOSE +#ifdef CGAL_PMP_SMOOTHING_DEBUG std::cout << "angles_ size = " << angles_.size() << std::endl; #endif } @@ -79,7 +79,7 @@ public: for(face_descriptor f : faces(mesh_)) areas_.push_back(face_area(f, mesh_)); -#ifdef CGAL_PMP_SMOOTHING_VERBOSE +#ifdef CGAL_PMP_SMOOTHING_DEBUG std::cout << "areas_ size = " << areas_.size() << std::endl; #endif } @@ -97,7 +97,7 @@ public: for(face_descriptor f : faces(mesh_)) aspect_ratios_.push_back(CGAL::Polygon_mesh_processing::face_aspect_ratio(f, mesh_)); -#ifdef CGAL_PMP_SMOOTHING_VERBOSE +#ifdef CGAL_PMP_SMOOTHING_DEBUG std::cout << "aspect_ratios_ size = " << aspect_ratios_.size() << std::endl; #endif } diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/smooth_mesh.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/smooth_mesh.h index 83cb9629627..f5fbd98d437 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/smooth_mesh.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/smooth_mesh.h @@ -235,13 +235,13 @@ void smooth_mesh(const FaceRange& faces, for(unsigned int i=0; i @@ -31,7 +31,7 @@ bool equal_doubles(double d1, double d2, double e) template void test_implicit_constrained_devil(Mesh mesh) { -#ifdef CGAL_PMP_SMOOTHING_VERBOSE +#ifdef CGAL_PMP_SMOOTHING_DEBUG std::cout << "-- test_implicit_constrained_devil --" << std::endl; #endif @@ -69,7 +69,7 @@ void test_implicit_constrained_devil(Mesh mesh) ++i; } -#ifdef CGAL_PMP_SMOOTHING_VERBOSE +#ifdef CGAL_PMP_SMOOTHING_DEBUG std::ofstream out("output_implicit_constrained_devil.off"); out << mesh; out.close(); @@ -79,7 +79,7 @@ void test_implicit_constrained_devil(Mesh mesh) template void test_implicit_constrained_elephant(Mesh mesh) { -#ifdef CGAL_PMP_SMOOTHING_VERBOSE +#ifdef CGAL_PMP_SMOOTHING_DEBUG std::cout << "-- test_implicit_constrained_elephant --" << std::endl; #endif @@ -117,7 +117,7 @@ void test_implicit_constrained_elephant(Mesh mesh) ++i; } -#ifdef CGAL_PMP_SMOOTHING_VERBOSE +#ifdef CGAL_PMP_SMOOTHING_DEBUG std::ofstream out("output_implicit_constrained_elephant.off"); out << mesh; out.close(); @@ -127,14 +127,14 @@ void test_implicit_constrained_elephant(Mesh mesh) template void test_curvature_flow_time_step(Mesh mesh) { -#ifdef CGAL_PMP_SMOOTHING_VERBOSE +#ifdef CGAL_PMP_SMOOTHING_DEBUG std::cout << "-- test_curvature_flow_time_step --" << std::endl; #endif const double time_step = 1e-15; PMP::smooth_shape(mesh, time_step); -#ifdef CGAL_PMP_SMOOTHING_VERBOSE +#ifdef CGAL_PMP_SMOOTHING_DEBUG std::ofstream out("output_devil_time_step.off"); out << mesh; out.close(); @@ -144,14 +144,14 @@ void test_curvature_flow_time_step(Mesh mesh) template void test_curvature_flow(Mesh mesh) { -#ifdef CGAL_PMP_SMOOTHING_VERBOSE +#ifdef CGAL_PMP_SMOOTHING_DEBUG std::cout << "-- test_curvature_flow --" << std::endl; #endif const double time_step = 1.0; PMP::smooth_shape(mesh, time_step); -#ifdef CGAL_PMP_SMOOTHING_VERBOSE +#ifdef CGAL_PMP_SMOOTHING_DEBUG std::ofstream out("output_precision_elephant.off"); out << mesh; out.close(); From af075b20335cd32954f88e73186b7a840a0bdc9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mael=20Rouxel-Labb=C3=A9?= Date: Thu, 6 Feb 2020 09:55:10 +0100 Subject: [PATCH 42/56] Remove assertion that can be triggered by numerical errors --- .../include/CGAL/Polygon_mesh_processing/compute_normal.h | 2 -- 1 file changed, 2 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/compute_normal.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/compute_normal.h index c0e4966b215..727b92412e9 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/compute_normal.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/compute_normal.h @@ -393,8 +393,6 @@ compute_most_visible_normal_2_points(std::vector= - std::numeric_limits::epsilon()); - sp_bi = (std::max)(FT(0), sp_bi); if(sp_bi <= min_sp) continue; From 239d4479fa18e11d47da75c090fd371ba48cb2e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mael=20Rouxel-Labb=C3=A9?= Date: Thu, 6 Feb 2020 09:56:22 +0100 Subject: [PATCH 43/56] Try to work around stretched faces giving 0 angle in smoothing --- .../internal/Smoothing/mesh_smoothing_impl.h | 102 ++++++++++-------- 1 file changed, 59 insertions(+), 43 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Smoothing/mesh_smoothing_impl.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Smoothing/mesh_smoothing_impl.h index 1c5e03cd09e..f64390034ef 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Smoothing/mesh_smoothing_impl.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Smoothing/mesh_smoothing_impl.h @@ -50,9 +50,11 @@ namespace Polygon_mesh_processing { namespace internal { template -double get_radian_angle(const V& v1, const V& v2, const GT& gt) +typename GT::FT get_radian_angle(const V& v1, const V& v2, const GT& gt) { - return gt.compute_approximate_angle_3_object()(v1, v2) * CGAL_PI / 180.; + typedef typename GT::FT FT; + + return gt.compute_approximate_angle_3_object()(v1, v2) * CGAL_PI / FT(180); } // super naive for now. Not sure it even makes sense to do something like that for surfaces @@ -68,6 +70,7 @@ class Delaunay_edge_flipper typedef typename boost::graph_traits::face_descriptor face_descriptor; typedef typename boost::property_traits::reference Point_ref; + typedef typename GeomTraits::FT FT; typedef typename GeomTraits::Vector_3 Vector; public: @@ -107,8 +110,8 @@ public: const Point_ref p2 = get(vpmap_, v2); const Point_ref p3 = get(vpmap_, v3); - double alpha = get_radian_angle(Vector(p0 - p2), Vector(p1 - p2), traits_); - double beta = get_radian_angle(Vector(p1 - p3), Vector(p0 - p3), traits_); + FT alpha = get_radian_angle(Vector(p0 - p2), Vector(p1 - p2), traits_); + FT beta = get_radian_angle(Vector(p1 - p3), Vector(p0 - p3), traits_); return (alpha + beta > CGAL_PI); } @@ -200,6 +203,7 @@ class Angle_smoother typedef typename boost::graph_traits::halfedge_descriptor halfedge_descriptor; typedef typename boost::property_traits::reference Point_ref; + typedef typename GeomTraits::FT FT; typedef typename GeomTraits::Vector_3 Vector; typedef std::pair He_pair; @@ -241,7 +245,7 @@ public: Vector operator()(const vertex_descriptor v) const { Vector move = CGAL::NULL_VECTOR; - double weights_sum = 0.; + FT weights_sum = FT(0); for(halfedge_descriptor main_he : halfedges_around_source(v, mesh_)) { @@ -259,26 +263,37 @@ public: Vector right_v(pt, right_pt); // rotate - double angle = get_radian_angle(right_v, left_v, traits_); - CGAL_warning(angle != 0.); // no degenerate faces is a precondition - if(angle == 0.) - continue; - Vector bisector = rotate_edge(main_he, incident_pair); - double scaling_factor = CGAL::approximate_sqrt( - traits_.compute_squared_distance_3_object()(get(vpmap_, source(main_he, mesh_)), - get(vpmap_, target(main_he, mesh_)))); + FT scaling_factor = CGAL::approximate_sqrt( + traits_.compute_squared_distance_3_object()(get(vpmap_, source(main_he, mesh_)), + get(vpmap_, target(main_he, mesh_)))); bisector = traits_.construct_scaled_vector_3_object()(bisector, scaling_factor); Vector ps_psi(ps, traits_.construct_translated_point_3_object()(pt, bisector)); + FT angle = get_radian_angle(right_v, left_v, traits_); + + CGAL_warning(angle != FT(0)); + if(angle == FT(0)) + { + // no degenerate faces is a precondition, angle can be 0 but it should be a numerical error + CGAL_assertion(!is_degenerate_triangle_face(face(main_he, mesh_), mesh_)); + + std::cout << "zero angle at " << pt << std::endl; + std::ofstream out("results/debug.off"); + out << std::setprecision(17) << mesh_; + out.close(); + + return ps_psi; // if the angle is 0 + } + // small angles carry more weight - double weight = 1. / (angle*angle); + FT weight = 1. / CGAL::square(angle); weights_sum += weight; move += weight * ps_psi; } - if(weights_sum != 0.) + if(weights_sum != FT(0)) move /= weights_sum; return move; @@ -298,6 +313,7 @@ class Area_smoother typedef typename boost::property_traits::value_type Point; typedef typename boost::property_traits::reference Point_ref; + typedef typename GeomTraits::FT FT; typedef typename GeomTraits::Vector_3 Vector; public: @@ -308,27 +324,27 @@ public: { } private: - double element_area(const vertex_descriptor v1, - const vertex_descriptor v2, - const vertex_descriptor v3) const + FT element_area(const vertex_descriptor v1, + const vertex_descriptor v2, + const vertex_descriptor v3) const { - return CGAL::to_double(CGAL::approximate_sqrt(traits_.compute_squared_area_3_object()(get(vpmap_, v1), - get(vpmap_, v2), - get(vpmap_, v3)))); + return CGAL::approximate_sqrt(traits_.compute_squared_area_3_object()(get(vpmap_, v1), + get(vpmap_, v2), + get(vpmap_, v3))); } - double element_area(const Point& P, - const vertex_descriptor v2, - const vertex_descriptor v3) const + FT element_area(const Point& P, + const vertex_descriptor v2, + const vertex_descriptor v3) const { - return CGAL::to_double(CGAL::approximate_sqrt(traits_.compute_squared_area_3_object()(P, - get(vpmap_, v2), - get(vpmap_, v3)))); + return CGAL::approximate_sqrt(traits_.compute_squared_area_3_object()(P, + get(vpmap_, v2), + get(vpmap_, v3))); } - double compute_average_area_around(const vertex_descriptor v) const + FT compute_average_area_around(const vertex_descriptor v) const { - double sum_areas = 0.; + FT sum_areas = 0; unsigned int number_of_edges = 0; for(halfedge_descriptor h : halfedges_around_source(v, mesh_)) @@ -337,7 +353,7 @@ private: vertex_descriptor vi = source(next(h, mesh_), mesh_); vertex_descriptor vj = target(next(h, mesh_), mesh_); - double S = element_area(v, vi, vj); + FT S = element_area(v, vi, vj); sum_areas += S; ++number_of_edges; } @@ -347,7 +363,7 @@ private: struct Face_energy { - Face_energy(const Point& pi, const Point& pj, const double s_av) + Face_energy(const Point& pi, const Point& pj, const FT s_av) : qx(pi.x()), qy(pi.y()), qz(pi.z()), rx(pj.x()), ry(pj.y()), rz(pj.z()), @@ -356,7 +372,7 @@ private: // next two functions are just for convencience, the only thing ceres cares about is the operator() template - double area(const T x, const T y, const T z) const + FT area(const T x, const T y, const T z) const { return CGAL::approximate_sqrt(CGAL::squared_area(Point(x, y, z), Point(qx, qy, qz), @@ -364,7 +380,7 @@ private: } template - double evaluate(const T x, const T y, const T z) const { return area(x, y, z) - s_av; } + FT evaluate(const T x, const T y, const T z) const { return area(x, y, z) - s_av; } template bool operator()(const T* const x, const T* const y, const T* const z, @@ -400,9 +416,9 @@ private: } private: - const double qx, qy, qz; - const double rx, ry, rz; - const double s_av; + const FT qx, qy, qz; + const FT rx, ry, rz; + const FT s_av; }; public: @@ -411,12 +427,12 @@ public: #ifdef CGAL_PMP_USE_CERES_SOLVER const Point_ref vp = get(vpmap_, v); - const double S_av = compute_average_area_around(v); + const FT S_av = compute_average_area_around(v); - const double initial_x = vp.x(); - const double initial_y = vp.y(); - const double initial_z = vp.z(); - double x = initial_x, y = initial_y, z = initial_z; + const FT initial_x = vp.x(); + const FT initial_y = vp.y(); + const FT initial_z = vp.z(); + FT x = initial_x, y = initial_y, z = initial_z; ceres::Problem problem; @@ -533,7 +549,7 @@ public: Optimizer compute_move(mesh_, vpmap_, traits_); #ifdef CGAL_PMP_SMOOTHING_DEBUG - double total_displacement = 0; + FT total_displacement = 0; #endif std::size_t moved_points = 0; @@ -690,7 +706,7 @@ private: const Point& new_pos) const { // check if the minimum angle of the star has not deteriorated - double old_min_angle = CGAL_PI; + FT old_min_angle = CGAL_PI; for(halfedge_descriptor main_he : halfedges_around_source(v, mesh_)) { const Point_ref old_pos = get(vpmap_, v); From c2baf0a2ae66f80212e57ce820db0f9f155e406d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mael=20Rouxel-Labb=C3=A9?= Date: Thu, 6 Feb 2020 12:02:41 +0100 Subject: [PATCH 44/56] Add missing ';' --- .../include/CGAL/Polygon_mesh_processing/stitch_borders.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/stitch_borders.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/stitch_borders.h index dfdaab30ec0..269f6f28a49 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/stitch_borders.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/stitch_borders.h @@ -890,7 +890,7 @@ std::size_t stitch_borders(PolygonMesh& pmesh, halfedge_descriptor; std::vector< std::pair > hedge_pairs_to_stitch; - typedef typename GetVertexPointMap::const_type VPM + typedef typename GetVertexPointMap::const_type VPM; VPM vpm = choose_parameter(get_parameter(np, internal_np::vertex_point), get_const_property_map(vertex_point, pmesh)); From 19c7ea8f30c5abaeed1c6125319d126d0f96eb5f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mael=20Rouxel-Labb=C3=A9?= Date: Thu, 6 Feb 2020 12:06:54 +0100 Subject: [PATCH 45/56] Remove non-existent header --- .../CGAL/Polygon_mesh_processing/remove_self_intersections.h | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/remove_self_intersections.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/remove_self_intersections.h index 4e867281a4f..98a0fb171b8 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/remove_self_intersections.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/remove_self_intersections.h @@ -1,4 +1,4 @@ -// Copyright (c) 2015-2019 GeometryFactory (France). +// Copyright (c) 2015-2020 GeometryFactory (France). // All rights reserved. // // This file is part of CGAL (www.cgal.org). @@ -23,7 +23,6 @@ #include #include #include -#include #include #include From 3f3227901148c54ac312f9cf25d64257ea0dee59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mael=20Rouxel-Labb=C3=A9?= Date: Thu, 6 Feb 2020 15:48:22 +0100 Subject: [PATCH 46/56] More null angle handling in smoothing --- .../internal/Smoothing/mesh_smoothing_impl.h | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Smoothing/mesh_smoothing_impl.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Smoothing/mesh_smoothing_impl.h index f64390034ef..0875cbe47d5 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Smoothing/mesh_smoothing_impl.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Smoothing/mesh_smoothing_impl.h @@ -271,19 +271,18 @@ public: Vector ps_psi(ps, traits_.construct_translated_point_3_object()(pt, bisector)); FT angle = get_radian_angle(right_v, left_v, traits_); - - CGAL_warning(angle != FT(0)); if(angle == FT(0)) { - // no degenerate faces is a precondition, angle can be 0 but it should be a numerical error - CGAL_assertion(!is_degenerate_triangle_face(face(main_he, mesh_), mesh_)); - std::cout << "zero angle at " << pt << std::endl; std::ofstream out("results/debug.off"); out << std::setprecision(17) << mesh_; out.close(); - return ps_psi; // if the angle is 0 + // no degenerate faces is a precondition, angle can be 0 but it should be a numerical error + CGAL_warning(!is_degenerate_triangle_face(face(main_he, mesh_), mesh_)); + + std::cout << "move: " << ps_psi << std::endl; + return ps_psi; // since a small angle gives more weight, a null angle give priority (?) } // small angles carry more weight From 52027123220166cd60b8e6407308aa56cd415d8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mael=20Rouxel-Labb=C3=A9?= Date: Thu, 6 Feb 2020 15:49:00 +0100 Subject: [PATCH 47/56] Fix polygon soup initialization --- .../CGAL/Polygon_mesh_processing/remove_self_intersections.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/remove_self_intersections.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/remove_self_intersections.h index 98a0fb171b8..e1ce96e35e4 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/remove_self_intersections.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/remove_self_intersections.h @@ -1087,7 +1087,7 @@ bool check_patch_sanity(const std::vector >& patch) std::vector ps_f; for(const Point& pt : face) { - std::size_t id; + std::size_t id = c; std::pair::iterator, bool> is_insert_successful = ids.insert(std::make_pair(pt, c)); if(is_insert_successful.second) // first time we've seen that point @@ -1100,6 +1100,7 @@ bool check_patch_sanity(const std::vector >& patch) id = is_insert_successful.first->second; } + CGAL_assertion(id < points.size()); ps_f.push_back(id); } From 035536527cc41067de298eaa83fcfbc39510745e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mael=20Rouxel-Labb=C3=A9?= Date: Thu, 6 Feb 2020 15:49:33 +0100 Subject: [PATCH 48/56] Misc cleaning --- .../internal/Smoothing/mesh_smoothing_impl.h | 12 ++++++++++-- .../remove_self_intersections.h | 6 ------ .../CGAL/Polygon_mesh_processing/smooth_mesh.h | 2 +- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Smoothing/mesh_smoothing_impl.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Smoothing/mesh_smoothing_impl.h index 0875cbe47d5..4bcd379081c 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Smoothing/mesh_smoothing_impl.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Smoothing/mesh_smoothing_impl.h @@ -133,7 +133,7 @@ public: void operator()(const FaceRange& face_range) { #ifdef CGAL_PMP_SMOOTHING_DEBUG - std::cout << "Flipping faces" << std::endl; + std::cout << "Flipping edges" << std::endl; #endif // edges to consider @@ -187,6 +187,10 @@ public: add_to_stack_if_unmarked(edge(prev(opposite(h, mesh_), mesh_), mesh_), marks, edge_range); } } + +#ifdef CGAL_PMP_SMOOTHING_DEBUG + std::cout << flipped_n << " flips" << std::endl; +#endif } private: @@ -549,6 +553,7 @@ public: #ifdef CGAL_PMP_SMOOTHING_DEBUG FT total_displacement = 0; + std::cout << "apply_moves_in_single_batch: " << apply_moves_in_single_batch << std::endl; #endif std::size_t moved_points = 0; @@ -582,7 +587,6 @@ public: { #ifdef CGAL_PMP_SMOOTHING_DEBUG_PP std::cout << "moving " << get(vpmap_, v) << " to " << new_pos << std::endl; - total_displacement += CGAL::approximate_sqrt(traits_.compute_squared_length_3_object()(move)); #endif if(apply_moves_in_single_batch) @@ -590,6 +594,10 @@ public: else put(vpmap_, v, new_pos); +#ifdef CGAL_PMP_SMOOTHING_DEBUG + total_displacement += CGAL::approximate_sqrt(traits_.compute_squared_length_3_object()(move)); +#endif + ++moved_points; } else // some sanity check failed diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/remove_self_intersections.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/remove_self_intersections.h index e1ce96e35e4..6c73f4ad225 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/remove_self_intersections.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/remove_self_intersections.h @@ -457,11 +457,6 @@ bool remove_self_intersections_with_smoothing(std::set::face_descriptor> dfaces; - degenerate_faces(face_range, tmesh, std::back_inserter(dfaces)); - std::cout << dfaces.size() << " dfaces" << std::endl; - // Rather than working directly on the mesh, copy a range and work on this instead const CGAL::Face_filtered_graph ffg(tmesh, face_range); TriangleMesh local_mesh; @@ -1703,7 +1698,6 @@ remove_self_intersections_one_step(std::set::value)) From 0ddf33c9df05f337a2d038539631018e5a17b2f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mael=20Rouxel-Labb=C3=A9?= Date: Thu, 6 Feb 2020 15:51:24 +0100 Subject: [PATCH 49/56] Use PM_to_PS in the demo --- .../polygon_soup_to_polygon_mesh.h | 4 +-- .../Polyhedron/Scene_polygon_soup_item.cpp | 28 ++----------------- 2 files changed, 4 insertions(+), 28 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/polygon_soup_to_polygon_mesh.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/polygon_soup_to_polygon_mesh.h index 2f5abf3d890..f9f6d07f769 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/polygon_soup_to_polygon_mesh.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/polygon_soup_to_polygon_mesh.h @@ -74,10 +74,10 @@ void polygon_mesh_to_polygon_soup(const PolygonMesh& mesh, get_const_property_map(vertex_point, mesh)); typedef CGAL::dynamic_vertex_property_t Vertex_index; - typedef typename boost::property_map::type VIM; + typedef typename boost::property_map::const_type VIM; VIM vim = get(Vertex_index(), mesh); - typedef typename boost::range_value Polygon; + typedef typename boost::range_value::type Polygon; std::size_t index = points.size(); // so that multiple meshes can be put into the same soup diff --git a/Polyhedron/demo/Polyhedron/Scene_polygon_soup_item.cpp b/Polyhedron/demo/Polyhedron/Scene_polygon_soup_item.cpp index 7a84ee2d911..1c8c805e639 100644 --- a/Polyhedron/demo/Polyhedron/Scene_polygon_soup_item.cpp +++ b/Polyhedron/demo/Polyhedron/Scene_polygon_soup_item.cpp @@ -43,6 +43,7 @@ #include #include +#include #include #include @@ -366,37 +367,12 @@ void Scene_polygon_soup_item::init_polygon_soup(std::size_t nb_pts, std::size_t d->oriented = false; } - -#include template void polygon_mesh_to_soup(PolygonMesh& mesh, Polygon_soup& soup) { soup.clear(); - typedef typename boost::property_map::type VPMap; - VPMap vpmap = get(boost::vertex_point, mesh); - std::map::vertex_descriptor, int> vim; - int index=0; - //fill points - for(typename boost::graph_traits::vertex_iterator vit = - vertices(mesh).begin(); vit != vertices(mesh).end(); ++vit) - { - soup.points.push_back(get(vpmap, *vit)); - vim.insert(std::make_pair(*vit, index++)); - } - //fill triangles - for(typename boost::graph_traits::face_iterator fit = - faces(mesh).begin(); fit != faces(mesh).end(); ++fit) - { - Polygon_soup::Polygon_3 polygon; - for(typename boost::graph_traits::halfedge_descriptor hd : - CGAL::halfedges_around_face(halfedge(*fit, mesh), mesh)) - { - polygon.push_back(vim[target(hd, mesh)]); - } - soup.polygons.push_back(polygon); - } + CGAL::Polygon_mesh_processing::internal::polygon_mesh_to_polygon_soup(mesh, soup.points, soup.polygons); soup.fill_edges(); - } From f1969fc5b80b81b3790005573732e7ae8488a96e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mael=20Rouxel-Labb=C3=A9?= Date: Thu, 6 Feb 2020 16:18:42 +0100 Subject: [PATCH 50/56] Remove unused debug functions --- .../remove_degeneracies.h | 100 ------------------ 1 file changed, 100 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/remove_degeneracies.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/remove_degeneracies.h index a61062ddf25..c70b5c91920 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/remove_degeneracies.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/remove_degeneracies.h @@ -41,106 +41,6 @@ namespace CGAL { namespace Polygon_mesh_processing { -namespace debug { - -template -std::ostream& -dump_edge_neighborhood(typename boost::graph_traits::edge_descriptor ed, - TriangleMesh& tmesh, - const VertexPointMap& vpmap, - std::ostream& out) -{ - typedef boost::graph_traits GT; - typedef typename GT::vertex_descriptor vertex_descriptor; - typedef typename GT::halfedge_descriptor halfedge_descriptor; - typedef typename GT::face_descriptor face_descriptor; - - halfedge_descriptor h = halfedge(ed, tmesh); - - std::map vertices; - std::set faces; - int vindex = 0; - - for(halfedge_descriptor hd : halfedges_around_target(h, tmesh)) - { - if(vertices.insert(std::make_pair(source(hd, tmesh), vindex)).second) - ++vindex; - - if(!is_border(hd, tmesh)) - faces.insert(face(hd, tmesh)); - } - - h = opposite(h, tmesh); - for(halfedge_descriptor hd : halfedges_around_target(h, tmesh)) - { - if(vertices.insert(std::make_pair(source(hd, tmesh), vindex)).second) - ++vindex; - - if(!is_border(hd, tmesh)) - faces.insert(face(hd, tmesh)); - } - - std::vector ordered_vertices(vertices.size()); - typedef std::pair Pair_type; - for(const Pair_type& p : vertices) - ordered_vertices[p.second] = p.first; - - out << "OFF\n" << ordered_vertices.size() << " " << faces.size() << " 0\n"; - for(vertex_descriptor vd : ordered_vertices) - out << get(vpmap, vd) << "\n"; - for(face_descriptor fd : faces) - { - out << "3"; - h = halfedge(fd, tmesh); - for(halfedge_descriptor hd : halfedges_around_face(h, tmesh)) - out << " " << vertices[target(hd, tmesh)]; - out << "\n"; - } - - return out; -} - -template -void dump_cc_faces(const FaceRange& cc_faces, const TriangleMesh& tm, std::ostream& output) -{ - typedef typename boost::graph_traits::vertex_descriptor vertex_descriptor; - typedef typename boost::graph_traits::face_descriptor face_descriptor; - - typedef typename boost::property_map::const_type Vpm; - typedef typename boost::property_traits::value_type Point_3; - - Vpm vpm = get(boost::vertex_point, tm); - - int id = 0; - std::map vids; - - for(face_descriptor f : cc_faces) - { - if(vids.insert(std::make_pair(target(halfedge(f, tm), tm), id)).second) ++id; - if(vids.insert(std::make_pair(target(next(halfedge(f, tm), tm), tm), id)).second) ++id; - if(vids.insert(std::make_pair(target(next(next(halfedge(f, tm), tm), tm), tm), id)).second) ++id; - } - - std::vector points(vids.size()); - typedef std::pair Pair_type; - for(const Pair_type& p : vids) - points[p.second] = get(vpm, p.first); - - output << std::setprecision(17); - output << "OFF\n" << vids.size() << " " << cc_faces.size() << " 0\n"; - for(Point_3 p : points) - output << p << "\n"; - for(face_descriptor f : cc_faces) - { - output << "3 " - << vids[target(halfedge(f, tm), tm)] << " " - << vids[target(next(halfedge(f, tm), tm), tm)] << " " - << vids[target(next(next(halfedge(f, tm), tm), tm), tm)] << "\n"; - } -} - -} // namespace debug - namespace internal { template From 29ddd67b8daad3fb5c1f6fed20f2f5043f07ba49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mael=20Rouxel-Labb=C3=A9?= Date: Thu, 6 Feb 2020 17:14:16 +0100 Subject: [PATCH 51/56] Clean temp code --- .../internal/Smoothing/mesh_smoothing_impl.h | 6 - .../internal/remove_degeneracies.h | 709 ------------------ .../remove_degeneracies.h | 685 ++++++++++++++++- 3 files changed, 684 insertions(+), 716 deletions(-) delete mode 100644 Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/remove_degeneracies.h diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Smoothing/mesh_smoothing_impl.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Smoothing/mesh_smoothing_impl.h index 4bcd379081c..16f8de52d49 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Smoothing/mesh_smoothing_impl.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Smoothing/mesh_smoothing_impl.h @@ -277,15 +277,9 @@ public: FT angle = get_radian_angle(right_v, left_v, traits_); if(angle == FT(0)) { - std::cout << "zero angle at " << pt << std::endl; - std::ofstream out("results/debug.off"); - out << std::setprecision(17) << mesh_; - out.close(); - // no degenerate faces is a precondition, angle can be 0 but it should be a numerical error CGAL_warning(!is_degenerate_triangle_face(face(main_he, mesh_), mesh_)); - std::cout << "move: " << ps_psi << std::endl; return ps_psi; // since a small angle gives more weight, a null angle give priority (?) } diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/remove_degeneracies.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/remove_degeneracies.h deleted file mode 100644 index f6e582cf93f..00000000000 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/remove_degeneracies.h +++ /dev/null @@ -1,709 +0,0 @@ -// Copyright (c) 2019 GeometryFactory (France). -// All rights reserved. -// -// This file is part of CGAL (www.cgal.org). -// -// $URL$ -// $Id$ -// SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-Commercial -// -// Author(s) : Sebastien Loriot, -// Mael Rouxel-Labbé - -#ifndef CGAL_POLYGON_MESH_PROCESSING_REMOVE_DEGENERACIES_H -#define CGAL_POLYGON_MESH_PROCESSING_REMOVE_DEGENERACIES_H - -#include - -#include -#include - -#include -#include -#include -#include - -#include - -#include -#include -#ifdef CGAL_PMP_DEBUG_REMOVE_DEGENERACIES -#include -#include -#endif - -namespace CGAL { -namespace Polygon_mesh_processing { -namespace internal { - -template -std::array::halfedge_descriptor, 2> -is_badly_shaped(const typename boost::graph_traits::face_descriptor f, - TriangleMesh& tmesh, - const VPM& vpm, - const ECM& ecm, - const Traits& gt, - const double cap_threshold, // angle over 160° ==> cap - const double needle_threshold, // longest edge / shortest edge over this ratio ==> needle - const double collapse_length_threshold) // max length of edges allowed to be collapsed -{ - namespace PMP = CGAL::Polygon_mesh_processing; - - typedef typename boost::graph_traits::halfedge_descriptor halfedge_descriptor; - - const halfedge_descriptor null_h = boost::graph_traits::null_halfedge(); - - halfedge_descriptor res = PMP::is_needle_triangle_face(f, tmesh, needle_threshold, - parameters::vertex_point_map(vpm) - .geom_traits(gt)); - if(res != null_h && !get(ecm, edge(res, tmesh))) - { - // don't want to collapse edges that are too large - if(collapse_length_threshold == 0 || - edge_length(res, tmesh, parameters::vertex_point_map(vpm).geom_traits(gt)) <= collapse_length_threshold) - { - return make_array(res, null_h); - } - } - else // let's not make it possible to have a face be both a cap and a needle (for now) - { - res = PMP::is_cap_triangle_face(f, tmesh, cap_threshold, parameters::vertex_point_map(vpm).geom_traits(gt)); - if(res != null_h && !get(ecm, edge(res, tmesh))) - return make_array(null_h, res); - } - - return make_array(null_h, null_h); -} - -template -void collect_badly_shaped_triangles(const typename boost::graph_traits::face_descriptor f, - TriangleMesh& tmesh, - const VPM& vpm, - const ECM& ecm, - const Traits& gt, - const double cap_threshold, // angle over this threshold (as a cosine) ==> cap - const double needle_threshold, // longest edge / shortest edge over this ratio ==> needle - const double collapse_length_threshold, // max length of edges allowed to be collapsed - EdgeContainer& edges_to_collapse, - EdgeContainer& edges_to_flip) -{ - typedef typename boost::graph_traits::halfedge_descriptor halfedge_descriptor; - - std::array res = is_badly_shaped(f, tmesh, vpm, ecm, gt, cap_threshold, - needle_threshold, collapse_length_threshold); - - if(res[0] != boost::graph_traits::null_halfedge()) - { -#ifdef CGAL_PMP_DEBUG_REMOVE_DEGENERACIES - std::cout << "add new needle: " << edge(res[0], tmesh) << std::endl; -#endif - edges_to_collapse.insert(edge(res[0], tmesh)); - } - else // let's not make it possible to have a face be both a cap and a needle (for now) - { - if(res[1] != boost::graph_traits::null_halfedge()) - { -#ifdef CGAL_PMP_DEBUG_REMOVE_DEGENERACIES - std::cout << "add new cap: " << edge(res[1],tmesh) << std::endl; -#endif - edges_to_flip.insert(edge(res[1], tmesh)); - } - } -} - -/* -// Following Ronfard et al. 96 we look at variation of the normal after the collapse -// the collapse must be topologically valid -template -bool is_collapse_geometrically_valid(typename boost::graph_traits::halfedge_descriptor h, - const TriangleMesh& tmesh, - const NamedParameters& np) -{ - using CGAL::parameters::choose_parameter; - using CGAL::parameters::get_parameter; - - typedef typename boost::graph_traits::halfedge_descriptor halfedge_descriptor; - typedef typename GetVertexPointMap::const_type VPM; - typedef typename boost::property_traits::reference Point_ref; - typedef typename GetGeomTraits::type Traits; - - VPM vpm = choose_parameter(get_parameter(np, internal_np::vertex_point), - get_const_property_map(vertex_point, tmesh)); - Traits gt = choose_parameter(get_parameter(np, internal_np::geom_traits), Traits()); - -/// @todo handle boundary edges - - h = opposite(h, tmesh); // Euler::collapse edge keeps the target and removes the source - - // source is kept, target is removed - CGAL_assertion(target(h, tmesh) == vertex_removed); - Point_ref kept = get(vpm, source(h, tmesh)); - Point_ref removed= get(vpm, target(h, tmesh)); - - // consider triangles incident to the vertex removed - halfedge_descriptor stop = prev(opposite(h, tmesh), tmesh); - halfedge_descriptor hi = opposite(next(h, tmesh), tmesh); - - std::vector triangles; - while(hi != stop) - { - if(!is_border(hi, tmesh)) - { - Point_ref a = get(vpm, target(next(hi, tmesh), tmesh)); - Point_ref b = get(vpm, source(hi, tmesh)); - - //ack a-b-point_remove and a-b-point_kept has a compatible orientation - /// @todo use a predicate - typename Traits::Vector_3 n1 = gt.construct_cross_product_vector_3_object()(removed-a, b-a); - typename Traits::Vector_3 n2 = gt.construct_cross_product_vector_3_object()(kept-a, b-a); - if(gt.compute_scalar_product_3_object()(n1, n2) <= 0) - return false; - } - - hi = opposite(next(hi, tmesh), tmesh); - } - - return true; -} -*/ - -template -boost::optional get_collapse_volume(typename boost::graph_traits::halfedge_descriptor h, - const TriangleMesh& tmesh, - const VPM& vpm, - const Traits& gt) -{ - typedef typename boost::graph_traits::halfedge_descriptor halfedge_descriptor; - - typedef typename boost::property_traits::reference Point_ref; - typedef typename Traits::Vector_3 Vector_3; - - const typename Traits::Point_3 origin(ORIGIN); - -/// @todo handle boundary edges - - h = opposite(h, tmesh); // Euler::collapse edge keeps the target and removes the source - - // source is kept, target is removed - Point_ref kept = get(vpm, source(h, tmesh)); - Point_ref removed= get(vpm, target(h, tmesh)); - - // init volume with incident triangles (reversed orientation - double delta_vol = volume(removed, kept, get(vpm, target(next(h, tmesh), tmesh)), origin) + - volume(kept, removed, get(vpm, target(next(opposite(h, tmesh), tmesh), tmesh)), origin); - - // consider triangles incident to the vertex removed - halfedge_descriptor stop = prev(opposite(h, tmesh), tmesh); - halfedge_descriptor hi = opposite(next(h, tmesh), tmesh); - - std::vector triangles; - while(hi != stop) - { - if(!is_border(hi, tmesh)) - { - Point_ref a = get(vpm, target(next(hi, tmesh), tmesh)); - Point_ref b = get(vpm, source(hi, tmesh)); - - //ack a-b-point_remove and a-b-point_kept has a compatible orientation - /// @todo use a predicate - Vector_3 v_ab = gt.construct_vector_3_object()(a, b); - Vector_3 v_ar = gt.construct_vector_3_object()(a, removed); - Vector_3 v_ak = gt.construct_vector_3_object()(a, kept); - - Vector_3 n1 = gt.construct_cross_product_vector_3_object()(v_ar, v_ab); - Vector_3 n2 = gt.construct_cross_product_vector_3_object()(v_ak, v_ab); - if(gt.compute_scalar_product_3_object()(n1, n2) <= 0) - return boost::none; - - delta_vol += volume(b, a, removed, origin) + volume(a, b, kept, origin); // opposite orientation - } - - hi = opposite(next(hi, tmesh), tmesh); - } - - return CGAL::abs(delta_vol); -} - -template -typename boost::graph_traits::halfedge_descriptor -get_best_edge_orientation(typename boost::graph_traits::edge_descriptor e, - const TriangleMesh& tmesh, - const VPM& vpm, - const VCM& vcm, - const Traits& gt) -{ - typedef typename boost::graph_traits::halfedge_descriptor halfedge_descriptor; - - halfedge_descriptor h = halfedge(e, tmesh), ho = opposite(h, tmesh); - - CGAL_assertion(!get(vcm, source(h, tmesh)) || !get(vcm, target(h, tmesh))); - - boost::optional dv1 = get_collapse_volume(h, tmesh, vpm, gt); - boost::optional dv2 = get_collapse_volume(ho, tmesh, vpm, gt); - - // the resulting point of the collapse of a halfedge is the target of the halfedge before collapse - if(get(vcm, source(h, tmesh))) - return dv2 != boost::none ? ho - : boost::graph_traits::null_halfedge(); - - if(get(vcm, target(h, tmesh))) - return dv1 != boost::none ? h - : boost::graph_traits::null_halfedge(); - - if(dv1 != boost::none) - { - if(dv2 != boost::none) - return (*dv1 < *dv2) ? h : ho; - - return h; - } - - if(dv2 != boost::none) - return ho; - - return boost::graph_traits::null_halfedge(); -} - -// adapted from triangulate_faces -template -bool should_flip(typename boost::graph_traits::edge_descriptor e, - const TriangleMesh& tmesh, - const VPM& vpm, - const Traits& gt) -{ - typedef typename boost::graph_traits::halfedge_descriptor halfedge_descriptor; - - typedef typename boost::property_traits::reference Point_ref; - typedef typename Traits::Vector_3 Vector_3; - - CGAL_precondition(!is_border(e, tmesh)); - - halfedge_descriptor h = halfedge(e, tmesh); - - Point_ref p0 = get(vpm, target(h, tmesh)); - Point_ref p1 = get(vpm, target(next(h, tmesh), tmesh)); - Point_ref p2 = get(vpm, source(h, tmesh)); - Point_ref p3 = get(vpm, target(next(opposite(h, tmesh), tmesh), tmesh)); - - /* Chooses the diagonal that will split the quad in two triangles that maximize - * the scalar product of of the un-normalized normals of the two triangles. - * The lengths of the un-normalized normals (computed using cross-products of two vectors) - * are proportional to the area of the triangles. - * Maximize the scalar product of the two normals will avoid skinny triangles, - * and will also taken into account the cosine of the angle between the two normals. - * In particular, if the two triangles are oriented in different directions, - * the scalar product will be negative. - */ - -// CGAL::cross_product(p2-p1, p3-p2) * CGAL::cross_product(p0-p3, p1-p0); -// CGAL::cross_product(p1-p0, p1-p2) * CGAL::cross_product(p3-p2, p3-p0); - - const Vector_3 v01 = gt.construct_vector_3_object()(p0, p1); - const Vector_3 v12 = gt.construct_vector_3_object()(p1, p2); - const Vector_3 v23 = gt.construct_vector_3_object()(p2, p3); - const Vector_3 v30 = gt.construct_vector_3_object()(p3, p0); - - const double p1p3 = gt.compute_scalar_product_3_object()( - gt.construct_cross_product_vector_3_object()(v12, v23), - gt.construct_cross_product_vector_3_object()(v30, v01)); - - const Vector_3 v21 = gt.construct_opposite_vector_3_object()(v12); - const Vector_3 v03 = gt.construct_opposite_vector_3_object()(v30); - - const double p0p2 = gt.compute_scalar_product_3_object()( - gt.construct_cross_product_vector_3_object()(v01, v21), - gt.construct_cross_product_vector_3_object()(v23, v03)); - - return p0p2 <= p1p3; -} - -} // namespace internal - -namespace experimental { - -// @todo check what to use as priority queue with removable elements, set might not be optimal -template -bool remove_almost_degenerate_faces(const FaceRange& face_range, - TriangleMesh& tmesh, - const double cap_threshold, - const double needle_threshold, - const double collapse_length_threshold, - const NamedParameters& np) -{ - using CGAL::parameters::choose_parameter; - using CGAL::parameters::get_parameter; - - typedef typename boost::graph_traits::vertex_descriptor vertex_descriptor; - typedef typename boost::graph_traits::halfedge_descriptor halfedge_descriptor; - typedef typename boost::graph_traits::edge_descriptor edge_descriptor; - typedef typename boost::graph_traits::face_descriptor face_descriptor; - - typedef Constant_property_map Default_VCM; - typedef typename internal_np::Lookup_named_param_def::type VCM; - VCM vcm_np = choose_parameter(get_parameter(np, internal_np::vertex_is_constrained), Default_VCM(false)); - - typedef Constant_property_map Default_ECM; - typedef typename internal_np::Lookup_named_param_def::type ECM; - ECM ecm = choose_parameter(get_parameter(np, internal_np::edge_is_constrained), Default_ECM(false)); - - typedef typename GetVertexPointMap::const_type VPM; - VPM vpm = choose_parameter(get_parameter(np, internal_np::vertex_point), - get_const_property_map(vertex_point, tmesh)); - - typedef typename GetGeomTraits::type Traits; - Traits gt = choose_parameter(get_parameter(np, internal_np::geom_traits), Traits()); - - // Vertex property map that combines the VCM and the fact that extremities of a constrained edge should be constrained - typedef CGAL::dynamic_vertex_property_t Vertex_property_tag; - typedef typename boost::property_map::type DVCM; - DVCM vcm = get(Vertex_property_tag(), tmesh); - - for(face_descriptor f : face_range) - { - if(f == boost::graph_traits::null_face()) - continue; - - for(halfedge_descriptor h : CGAL::halfedges_around_face(halfedge(f, tmesh), tmesh)) - { - if(get(ecm, edge(h, tmesh))) - { - put(vcm, source(h, tmesh), true); - put(vcm, target(h, tmesh), true); - } - else if(get(vcm_np, target(h, tmesh))) - { - put(vcm, target(h, tmesh), true); - } - } - } - - // Start the process of removing bad elements - std::set edges_to_collapse; - std::set edges_to_flip; - - // @todo could probably do something a bit better by looping edges, consider the incident faces - // f1 / f2 and look at f1 if f1 next_edges_to_collapse; - std::set next_edges_to_flip; - - // treat needles -#ifdef CGAL_PMP_DEBUG_REMOVE_DEGENERACIES_EXTRA - int kk=0; - std::ofstream(std::string("tmp/n-00000.off")) << tmesh; -#endif - while(!edges_to_collapse.empty()) - { - edge_descriptor e = *edges_to_collapse.begin(); - edges_to_collapse.erase(edges_to_collapse.begin()); - - CGAL_assertion(!get(ecm, e)); - - if(get(vcm, source(e, tmesh)) && get(vcm, target(e, tmesh))) - continue; - -#ifdef CGAL_PMP_DEBUG_REMOVE_DEGENERACIES - std::cout << " treat needle: " << e << " (" << tmesh.point(source (e, tmesh)) - << " --- " << tmesh.point(target(e, tmesh)) << ")" << std::endl; -#endif - if(CGAL::Euler::does_satisfy_link_condition(e, tmesh)) - { - // the following edges are removed by the collapse - halfedge_descriptor h = halfedge(e, tmesh); - CGAL_assertion(!is_border(h, tmesh)); // because extracted from a face - - std::array nc = - internal::is_badly_shaped(face(h, tmesh), tmesh, vpm, ecm, gt, - cap_threshold, needle_threshold, collapse_length_threshold); - - if(nc[0] != h) - { -#ifdef CGAL_PMP_DEBUG_REMOVE_DEGENERACIES - std::cerr << "Warning: Needle criteria no longer verified " << tmesh.point(source(e, tmesh)) << " " - << tmesh.point(target(e, tmesh)) << std::endl; -#endif - // the opposite edge might also have been inserted in the set and might still be a needle - h = opposite(h, tmesh); - if(is_border(h, tmesh)) - continue; - - nc = internal::is_badly_shaped(face(h, tmesh), tmesh, vpm, ecm, gt, - cap_threshold, needle_threshold, - collapse_length_threshold); - if(nc[0] != h) - continue; - } - - for(int i=0; i<2; ++i) - { - if(!is_border(h, tmesh)) - { - edge_descriptor pe = edge(prev(h, tmesh), tmesh); - edges_to_flip.erase(pe); - next_edges_to_collapse.erase(pe); - edges_to_collapse.erase(pe); - } - - h = opposite(h, tmesh); - } - - // pick the orientation of edge to keep the vertex minimizing the volume variation - h = internal::get_best_edge_orientation(e, tmesh, vpm, vcm, gt); - - if(h == boost::graph_traits::null_halfedge()) - { -#ifdef CGAL_PMP_DEBUG_REMOVE_DEGENERACIES - std::cerr << "Warning: geometrically invalid edge collapse! " - << tmesh.point(source(e, tmesh)) << " " - << tmesh.point(target(e, tmesh)) << std::endl; -#endif - next_edges_to_collapse.insert(e); - continue; - } - - edges_to_flip.erase(e); - next_edges_to_collapse.erase(e); // for edges added in faces incident to a vertex kept after a collapse -#ifdef CGAL_PMP_DEBUG_REMOVE_DEGENERACIES_EXTRA - std::cerr << " " << kk << " -- Collapsing " << tmesh.point(source(h, tmesh)) << " " - << tmesh.point(target(h, tmesh)) << std::endl; -#endif - // moving to the midpoint is not a good idea. On a circle for example you might endpoint with - // a bad geometry because you iteratively move one point - // auto mp = midpoint(tmesh.point(source(h, tmesh)), tmesh.point(target(h, tmesh))); - - vertex_descriptor v = Euler::collapse_edge(edge(h, tmesh), tmesh); - - //tmesh.point(v) = mp; - // examine all faces incident to the vertex kept - for(halfedge_descriptor hv : halfedges_around_target(v, tmesh)) - { - if(!is_border(hv, tmesh)) - { - internal::collect_badly_shaped_triangles(face(hv, tmesh), tmesh, vpm, ecm, gt, - cap_threshold, needle_threshold, collapse_length_threshold, - edges_to_collapse, edges_to_flip); - } - } - -#ifdef CGAL_PMP_DEBUG_REMOVE_DEGENERACIES_EXTRA - std::string nb = std::to_string(++kk); - if(kk<10) nb = std::string("0")+nb; - if(kk<100) nb = std::string("0")+nb; - if(kk<1000) nb = std::string("0")+nb; - if(kk<10000) nb = std::string("0")+nb; - std::ofstream(std::string("tmp/n-")+nb+std::string(".off")) << tmesh; -#endif - something_was_done = true; - } - else - { -#ifdef CGAL_PMP_DEBUG_REMOVE_DEGENERACIES - std::cerr << "Warning: uncollapsable edge! " << tmesh.point(source(e, tmesh)) << " " - << tmesh.point(target(e, tmesh)) << std::endl; -#endif - next_edges_to_collapse.insert(e); - } - } - - // treat caps -#ifdef CGAL_PMP_DEBUG_REMOVE_DEGENERACIES_EXTRA - kk=0; - std::ofstream(std::string("tmp/c-000.off")) << tmesh; -#endif - while(!edges_to_flip.empty()) - { - edge_descriptor e = *edges_to_flip.begin(); - edges_to_flip.erase(edges_to_flip.begin()); - - CGAL_assertion(!get(ecm, e)); - - if(get(vcm, source(e, tmesh)) && get(vcm, target(e, tmesh))) - continue; - -#ifdef CGAL_PMP_DEBUG_REMOVE_DEGENERACIES - std::cout << "treat cap: " << e << " (" << tmesh.point(source(e, tmesh)) - << " --- " << tmesh.point(target(e, tmesh)) << ")" << std::endl; -#endif - - halfedge_descriptor h = halfedge(e, tmesh); - std::array nc = internal::is_badly_shaped(face(h, tmesh), tmesh, vpm, ecm, gt, - cap_threshold, needle_threshold, - collapse_length_threshold); - // First check the triangle is still a cap - if(nc[1] != h) - { -#ifdef CGAL_PMP_DEBUG_REMOVE_DEGENERACIES - std::cerr << "Warning: Cap criteria no longer verified " << tmesh.point(source(e, tmesh)) << " --- " - << tmesh.point(target(e, tmesh)) << std::endl; -#endif - // the opposite edge might also have been inserted in the set and might still be a cap - h = opposite(h, tmesh); - if(is_border(h, tmesh)) - continue; - - nc = internal::is_badly_shaped(face(h, tmesh), tmesh, vpm, ecm, gt, - cap_threshold, needle_threshold, collapse_length_threshold); - if(nc[1] != h) - continue; - } - - // special case on the border - if(is_border(opposite(h, tmesh), tmesh)) - { - // remove the triangle - edges_to_flip.erase(edge(prev(h, tmesh), tmesh)); - edges_to_flip.erase(edge(next(h, tmesh), tmesh)); - next_edges_to_collapse.erase(edge(prev(h, tmesh), tmesh)); - next_edges_to_collapse.erase(edge(next(h, tmesh), tmesh)); - Euler::remove_face(h, tmesh); - something_was_done = true; - continue; - } - - // condition for the flip to be valid (the edge to be created does not already exist) - if(!halfedge(target(next(h, tmesh), tmesh), - target(next(opposite(h, tmesh), tmesh), tmesh), tmesh).second) - { - - if(!internal::should_flip(e, tmesh, vpm, gt)) - { -#ifdef CGAL_PMP_DEBUG_REMOVE_DEGENERACIES - std::cout << "Flipping prevented: not the best diagonal" << std::endl; -#endif - next_edges_to_flip.insert(e); - continue; - } - -#ifdef CGAL_PMP_DEBUG_REMOVE_DEGENERACIES - std::cout << "Flipping" << std::endl; -#endif -#ifdef CGAL_PMP_DEBUG_REMOVE_DEGENERACIES_EXTRA - std::cerr << "step " << kk << "\n"; - std::cerr << " Flipping " << tmesh.point(source(h, tmesh)) << " " - << tmesh.point(target(h, tmesh)) << std::endl; -#endif - Euler::flip_edge(h, tmesh); - CGAL_assertion(edge(h, tmesh) == e); - - // handle face updates - for(int i=0; i<2; ++i) - { - CGAL_assertion(!is_border(h, tmesh)); - std::array nc = - internal::is_badly_shaped(face(h, tmesh), tmesh, vpm, ecm, gt, - cap_threshold, needle_threshold, collapse_length_threshold); - - if(nc[1] != boost::graph_traits::null_halfedge()) - { - if(edge(nc[1], tmesh) != e) - next_edges_to_flip.insert(edge(nc[1], tmesh)); - } - else - { - if(nc[0] != boost::graph_traits::null_halfedge()) - { - next_edges_to_collapse.insert(edge(nc[0], tmesh)); - } - } - h = opposite(h, tmesh); - } - something_was_done = true; - } -#ifdef CGAL_PMP_DEBUG_REMOVE_DEGENERACIES - else - { - std::cerr << "Warning: unflippable edge! " << tmesh.point(source(h, tmesh)) << " --- " - << tmesh.point(target(h, tmesh)) << std::endl; - next_edges_to_flip.insert(e); - } -#endif -#ifdef CGAL_PMP_DEBUG_REMOVE_DEGENERACIES_EXTRA - std::string nb = std::to_string(++kk); - if(kk<10) nb = std::string("0")+nb; - if(kk<100) nb = std::string("0")+nb; - if(kk<1000) nb = std::string("0")+nb; - if(kk<10000) nb = std::string("0")+nb; - std::ofstream(std::string("tmp/c-")+nb+std::string(".off")) << tmesh; -#endif - } - - std::swap(edges_to_collapse, next_edges_to_collapse); - std::swap(edges_to_flip, next_edges_to_flip); - - if(!something_was_done) - return false; - } - - return false; -} - -template -bool remove_almost_degenerate_faces(const FaceRange& face_range, - TriangleMesh& tmesh, - const double cap_threshold, - const double needle_threshold, - const double collapse_length_threshold) -{ - return remove_almost_degenerate_faces(face_range, tmesh, - cap_threshold, needle_threshold, collapse_length_threshold, - CGAL::parameters::all_default()); -} - -template -bool remove_almost_degenerate_faces(TriangleMesh& tmesh, - const double cap_threshold, - const double needle_threshold, - const double collapse_length_threshold, - const CGAL_PMP_NP_CLASS& np) -{ - return remove_almost_degenerate_faces(faces(tmesh), tmesh, cap_threshold, needle_threshold, - collapse_length_threshold, np); -} - -template -bool remove_almost_degenerate_faces(TriangleMesh& tmesh, - const double cap_threshold, - const double needle_threshold, - const double collapse_length_threshold) -{ - return remove_almost_degenerate_faces(faces(tmesh), tmesh, - cap_threshold, needle_threshold, collapse_length_threshold, - CGAL::parameters::all_default()); -} - -} // namespace experimental -} // namespace Polygon_mesh_processing -} // namespace CGAL - -#endif // CGAL_POLYGON_MESH_PROCESSING_REMOVE_DEGENERACIES_H diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/remove_degeneracies.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/remove_degeneracies.h index c70b5c91920..65c7bd4fc5b 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/remove_degeneracies.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/remove_degeneracies.h @@ -16,10 +16,13 @@ #include #include +#include #include #include - +#include +#include +#include #include #ifdef CGAL_PMP_REMOVE_DEGENERATE_FACES_DEBUG @@ -36,13 +39,693 @@ #include #include #include +#include #include #include +// First part of the file: remove_ALMOST_degenerate_faces (needles/caps) +// Second part of the file: remove_degenerate_edges/faces + namespace CGAL { namespace Polygon_mesh_processing { namespace internal { +template +std::array::halfedge_descriptor, 2> +is_badly_shaped(const typename boost::graph_traits::face_descriptor f, + TriangleMesh& tmesh, + const VPM& vpm, + const ECM& ecm, + const Traits& gt, + const double cap_threshold, // angle over 160° ==> cap + const double needle_threshold, // longest edge / shortest edge over this ratio ==> needle + const double collapse_length_threshold) // max length of edges allowed to be collapsed +{ + namespace PMP = CGAL::Polygon_mesh_processing; + + typedef typename boost::graph_traits::halfedge_descriptor halfedge_descriptor; + + const halfedge_descriptor null_h = boost::graph_traits::null_halfedge(); + + halfedge_descriptor res = PMP::is_needle_triangle_face(f, tmesh, needle_threshold, + parameters::vertex_point_map(vpm) + .geom_traits(gt)); + if(res != null_h && !get(ecm, edge(res, tmesh))) + { + // don't want to collapse edges that are too large + if(collapse_length_threshold == 0 || + edge_length(res, tmesh, parameters::vertex_point_map(vpm).geom_traits(gt)) <= collapse_length_threshold) + { + return make_array(res, null_h); + } + } + else // let's not make it possible to have a face be both a cap and a needle (for now) + { + res = PMP::is_cap_triangle_face(f, tmesh, cap_threshold, parameters::vertex_point_map(vpm).geom_traits(gt)); + if(res != null_h && !get(ecm, edge(res, tmesh))) + return make_array(null_h, res); + } + + return make_array(null_h, null_h); +} + +template +void collect_badly_shaped_triangles(const typename boost::graph_traits::face_descriptor f, + TriangleMesh& tmesh, + const VPM& vpm, + const ECM& ecm, + const Traits& gt, + const double cap_threshold, // angle over this threshold (as a cosine) ==> cap + const double needle_threshold, // longest edge / shortest edge over this ratio ==> needle + const double collapse_length_threshold, // max length of edges allowed to be collapsed + EdgeContainer& edges_to_collapse, + EdgeContainer& edges_to_flip) +{ + typedef typename boost::graph_traits::halfedge_descriptor halfedge_descriptor; + + std::array res = is_badly_shaped(f, tmesh, vpm, ecm, gt, cap_threshold, + needle_threshold, collapse_length_threshold); + + if(res[0] != boost::graph_traits::null_halfedge()) + { +#ifdef CGAL_PMP_DEBUG_REMOVE_DEGENERACIES + std::cout << "add new needle: " << edge(res[0], tmesh) << std::endl; +#endif + edges_to_collapse.insert(edge(res[0], tmesh)); + } + else // let's not make it possible to have a face be both a cap and a needle (for now) + { + if(res[1] != boost::graph_traits::null_halfedge()) + { +#ifdef CGAL_PMP_DEBUG_REMOVE_DEGENERACIES + std::cout << "add new cap: " << edge(res[1],tmesh) << std::endl; +#endif + edges_to_flip.insert(edge(res[1], tmesh)); + } + } +} + +/* +// Following Ronfard et al. 96 we look at variation of the normal after the collapse +// the collapse must be topologically valid +template +bool is_collapse_geometrically_valid(typename boost::graph_traits::halfedge_descriptor h, + const TriangleMesh& tmesh, + const NamedParameters& np) +{ + using CGAL::parameters::choose_parameter; + using CGAL::parameters::get_parameter; + + typedef typename boost::graph_traits::halfedge_descriptor halfedge_descriptor; + typedef typename GetVertexPointMap::const_type VPM; + typedef typename boost::property_traits::reference Point_ref; + typedef typename GetGeomTraits::type Traits; + + VPM vpm = choose_parameter(get_parameter(np, internal_np::vertex_point), + get_const_property_map(vertex_point, tmesh)); + Traits gt = choose_parameter(get_parameter(np, internal_np::geom_traits), Traits()); + +/// @todo handle boundary edges + + h = opposite(h, tmesh); // Euler::collapse edge keeps the target and removes the source + + // source is kept, target is removed + CGAL_assertion(target(h, tmesh) == vertex_removed); + Point_ref kept = get(vpm, source(h, tmesh)); + Point_ref removed= get(vpm, target(h, tmesh)); + + // consider triangles incident to the vertex removed + halfedge_descriptor stop = prev(opposite(h, tmesh), tmesh); + halfedge_descriptor hi = opposite(next(h, tmesh), tmesh); + + std::vector triangles; + while(hi != stop) + { + if(!is_border(hi, tmesh)) + { + Point_ref a = get(vpm, target(next(hi, tmesh), tmesh)); + Point_ref b = get(vpm, source(hi, tmesh)); + + //ack a-b-point_remove and a-b-point_kept has a compatible orientation + /// @todo use a predicate + typename Traits::Vector_3 n1 = gt.construct_cross_product_vector_3_object()(removed-a, b-a); + typename Traits::Vector_3 n2 = gt.construct_cross_product_vector_3_object()(kept-a, b-a); + if(gt.compute_scalar_product_3_object()(n1, n2) <= 0) + return false; + } + + hi = opposite(next(hi, tmesh), tmesh); + } + + return true; +} +*/ + +template +boost::optional get_collapse_volume(typename boost::graph_traits::halfedge_descriptor h, + const TriangleMesh& tmesh, + const VPM& vpm, + const Traits& gt) +{ + typedef typename boost::graph_traits::halfedge_descriptor halfedge_descriptor; + + typedef typename boost::property_traits::reference Point_ref; + typedef typename Traits::Vector_3 Vector_3; + + const typename Traits::Point_3 origin(ORIGIN); + +/// @todo handle boundary edges + + h = opposite(h, tmesh); // Euler::collapse edge keeps the target and removes the source + + // source is kept, target is removed + Point_ref kept = get(vpm, source(h, tmesh)); + Point_ref removed= get(vpm, target(h, tmesh)); + + // init volume with incident triangles (reversed orientation + double delta_vol = volume(removed, kept, get(vpm, target(next(h, tmesh), tmesh)), origin) + + volume(kept, removed, get(vpm, target(next(opposite(h, tmesh), tmesh), tmesh)), origin); + + // consider triangles incident to the vertex removed + halfedge_descriptor stop = prev(opposite(h, tmesh), tmesh); + halfedge_descriptor hi = opposite(next(h, tmesh), tmesh); + + std::vector triangles; + while(hi != stop) + { + if(!is_border(hi, tmesh)) + { + Point_ref a = get(vpm, target(next(hi, tmesh), tmesh)); + Point_ref b = get(vpm, source(hi, tmesh)); + + //ack a-b-point_remove and a-b-point_kept has a compatible orientation + /// @todo use a predicate + Vector_3 v_ab = gt.construct_vector_3_object()(a, b); + Vector_3 v_ar = gt.construct_vector_3_object()(a, removed); + Vector_3 v_ak = gt.construct_vector_3_object()(a, kept); + + Vector_3 n1 = gt.construct_cross_product_vector_3_object()(v_ar, v_ab); + Vector_3 n2 = gt.construct_cross_product_vector_3_object()(v_ak, v_ab); + if(gt.compute_scalar_product_3_object()(n1, n2) <= 0) + return boost::none; + + delta_vol += volume(b, a, removed, origin) + volume(a, b, kept, origin); // opposite orientation + } + + hi = opposite(next(hi, tmesh), tmesh); + } + + return CGAL::abs(delta_vol); +} + +template +typename boost::graph_traits::halfedge_descriptor +get_best_edge_orientation(typename boost::graph_traits::edge_descriptor e, + const TriangleMesh& tmesh, + const VPM& vpm, + const VCM& vcm, + const Traits& gt) +{ + typedef typename boost::graph_traits::halfedge_descriptor halfedge_descriptor; + + halfedge_descriptor h = halfedge(e, tmesh), ho = opposite(h, tmesh); + + CGAL_assertion(!get(vcm, source(h, tmesh)) || !get(vcm, target(h, tmesh))); + + boost::optional dv1 = get_collapse_volume(h, tmesh, vpm, gt); + boost::optional dv2 = get_collapse_volume(ho, tmesh, vpm, gt); + + // the resulting point of the collapse of a halfedge is the target of the halfedge before collapse + if(get(vcm, source(h, tmesh))) + return dv2 != boost::none ? ho + : boost::graph_traits::null_halfedge(); + + if(get(vcm, target(h, tmesh))) + return dv1 != boost::none ? h + : boost::graph_traits::null_halfedge(); + + if(dv1 != boost::none) + { + if(dv2 != boost::none) + return (*dv1 < *dv2) ? h : ho; + + return h; + } + + if(dv2 != boost::none) + return ho; + + return boost::graph_traits::null_halfedge(); +} + +// adapted from triangulate_faces +template +bool should_flip(typename boost::graph_traits::edge_descriptor e, + const TriangleMesh& tmesh, + const VPM& vpm, + const Traits& gt) +{ + typedef typename boost::graph_traits::halfedge_descriptor halfedge_descriptor; + + typedef typename boost::property_traits::reference Point_ref; + typedef typename Traits::Vector_3 Vector_3; + + CGAL_precondition(!is_border(e, tmesh)); + + halfedge_descriptor h = halfedge(e, tmesh); + + Point_ref p0 = get(vpm, target(h, tmesh)); + Point_ref p1 = get(vpm, target(next(h, tmesh), tmesh)); + Point_ref p2 = get(vpm, source(h, tmesh)); + Point_ref p3 = get(vpm, target(next(opposite(h, tmesh), tmesh), tmesh)); + + /* Chooses the diagonal that will split the quad in two triangles that maximize + * the scalar product of of the un-normalized normals of the two triangles. + * The lengths of the un-normalized normals (computed using cross-products of two vectors) + * are proportional to the area of the triangles. + * Maximize the scalar product of the two normals will avoid skinny triangles, + * and will also taken into account the cosine of the angle between the two normals. + * In particular, if the two triangles are oriented in different directions, + * the scalar product will be negative. + */ + +// CGAL::cross_product(p2-p1, p3-p2) * CGAL::cross_product(p0-p3, p1-p0); +// CGAL::cross_product(p1-p0, p1-p2) * CGAL::cross_product(p3-p2, p3-p0); + + const Vector_3 v01 = gt.construct_vector_3_object()(p0, p1); + const Vector_3 v12 = gt.construct_vector_3_object()(p1, p2); + const Vector_3 v23 = gt.construct_vector_3_object()(p2, p3); + const Vector_3 v30 = gt.construct_vector_3_object()(p3, p0); + + const double p1p3 = gt.compute_scalar_product_3_object()( + gt.construct_cross_product_vector_3_object()(v12, v23), + gt.construct_cross_product_vector_3_object()(v30, v01)); + + const Vector_3 v21 = gt.construct_opposite_vector_3_object()(v12); + const Vector_3 v03 = gt.construct_opposite_vector_3_object()(v30); + + const double p0p2 = gt.compute_scalar_product_3_object()( + gt.construct_cross_product_vector_3_object()(v01, v21), + gt.construct_cross_product_vector_3_object()(v23, v03)); + + return p0p2 <= p1p3; +} + +} // namespace internal + +namespace experimental { + +// @todo check what to use as priority queue with removable elements, set might not be optimal +template +bool remove_almost_degenerate_faces(const FaceRange& face_range, + TriangleMesh& tmesh, + const double cap_threshold, + const double needle_threshold, + const double collapse_length_threshold, + const NamedParameters& np) +{ + using CGAL::parameters::choose_parameter; + using CGAL::parameters::get_parameter; + + typedef typename boost::graph_traits::vertex_descriptor vertex_descriptor; + typedef typename boost::graph_traits::halfedge_descriptor halfedge_descriptor; + typedef typename boost::graph_traits::edge_descriptor edge_descriptor; + typedef typename boost::graph_traits::face_descriptor face_descriptor; + + typedef Constant_property_map Default_VCM; + typedef typename internal_np::Lookup_named_param_def::type VCM; + VCM vcm_np = choose_parameter(get_parameter(np, internal_np::vertex_is_constrained), Default_VCM(false)); + + typedef Constant_property_map Default_ECM; + typedef typename internal_np::Lookup_named_param_def::type ECM; + ECM ecm = choose_parameter(get_parameter(np, internal_np::edge_is_constrained), Default_ECM(false)); + + typedef typename GetVertexPointMap::const_type VPM; + VPM vpm = choose_parameter(get_parameter(np, internal_np::vertex_point), + get_const_property_map(vertex_point, tmesh)); + + typedef typename GetGeomTraits::type Traits; + Traits gt = choose_parameter(get_parameter(np, internal_np::geom_traits), Traits()); + + // Vertex property map that combines the VCM and the fact that extremities of a constrained edge should be constrained + typedef CGAL::dynamic_vertex_property_t Vertex_property_tag; + typedef typename boost::property_map::type DVCM; + DVCM vcm = get(Vertex_property_tag(), tmesh); + + for(face_descriptor f : face_range) + { + if(f == boost::graph_traits::null_face()) + continue; + + for(halfedge_descriptor h : CGAL::halfedges_around_face(halfedge(f, tmesh), tmesh)) + { + if(get(ecm, edge(h, tmesh))) + { + put(vcm, source(h, tmesh), true); + put(vcm, target(h, tmesh), true); + } + else if(get(vcm_np, target(h, tmesh))) + { + put(vcm, target(h, tmesh), true); + } + } + } + + // Start the process of removing bad elements + std::set edges_to_collapse; + std::set edges_to_flip; + + // @todo could probably do something a bit better by looping edges, consider the incident faces + // f1 / f2 and look at f1 if f1 next_edges_to_collapse; + std::set next_edges_to_flip; + + // treat needles +#ifdef CGAL_PMP_DEBUG_REMOVE_DEGENERACIES_EXTRA + int kk=0; + std::ofstream(std::string("tmp/n-00000.off")) << tmesh; +#endif + while(!edges_to_collapse.empty()) + { + edge_descriptor e = *edges_to_collapse.begin(); + edges_to_collapse.erase(edges_to_collapse.begin()); + + CGAL_assertion(!get(ecm, e)); + + if(get(vcm, source(e, tmesh)) && get(vcm, target(e, tmesh))) + continue; + +#ifdef CGAL_PMP_DEBUG_REMOVE_DEGENERACIES + std::cout << " treat needle: " << e << " (" << tmesh.point(source (e, tmesh)) + << " --- " << tmesh.point(target(e, tmesh)) << ")" << std::endl; +#endif + if(CGAL::Euler::does_satisfy_link_condition(e, tmesh)) + { + // the following edges are removed by the collapse + halfedge_descriptor h = halfedge(e, tmesh); + CGAL_assertion(!is_border(h, tmesh)); // because extracted from a face + + std::array nc = + internal::is_badly_shaped(face(h, tmesh), tmesh, vpm, ecm, gt, + cap_threshold, needle_threshold, collapse_length_threshold); + + if(nc[0] != h) + { +#ifdef CGAL_PMP_DEBUG_REMOVE_DEGENERACIES + std::cerr << "Warning: Needle criteria no longer verified " << tmesh.point(source(e, tmesh)) << " " + << tmesh.point(target(e, tmesh)) << std::endl; +#endif + // the opposite edge might also have been inserted in the set and might still be a needle + h = opposite(h, tmesh); + if(is_border(h, tmesh)) + continue; + + nc = internal::is_badly_shaped(face(h, tmesh), tmesh, vpm, ecm, gt, + cap_threshold, needle_threshold, + collapse_length_threshold); + if(nc[0] != h) + continue; + } + + for(int i=0; i<2; ++i) + { + if(!is_border(h, tmesh)) + { + edge_descriptor pe = edge(prev(h, tmesh), tmesh); + edges_to_flip.erase(pe); + next_edges_to_collapse.erase(pe); + edges_to_collapse.erase(pe); + } + + h = opposite(h, tmesh); + } + + // pick the orientation of edge to keep the vertex minimizing the volume variation + h = internal::get_best_edge_orientation(e, tmesh, vpm, vcm, gt); + + if(h == boost::graph_traits::null_halfedge()) + { +#ifdef CGAL_PMP_DEBUG_REMOVE_DEGENERACIES + std::cerr << "Warning: geometrically invalid edge collapse! " + << tmesh.point(source(e, tmesh)) << " " + << tmesh.point(target(e, tmesh)) << std::endl; +#endif + next_edges_to_collapse.insert(e); + continue; + } + + edges_to_flip.erase(e); + next_edges_to_collapse.erase(e); // for edges added in faces incident to a vertex kept after a collapse +#ifdef CGAL_PMP_DEBUG_REMOVE_DEGENERACIES_EXTRA + std::cerr << " " << kk << " -- Collapsing " << tmesh.point(source(h, tmesh)) << " " + << tmesh.point(target(h, tmesh)) << std::endl; +#endif + // moving to the midpoint is not a good idea. On a circle for example you might endpoint with + // a bad geometry because you iteratively move one point + // auto mp = midpoint(tmesh.point(source(h, tmesh)), tmesh.point(target(h, tmesh))); + + vertex_descriptor v = Euler::collapse_edge(edge(h, tmesh), tmesh); + + //tmesh.point(v) = mp; + // examine all faces incident to the vertex kept + for(halfedge_descriptor hv : halfedges_around_target(v, tmesh)) + { + if(!is_border(hv, tmesh)) + { + internal::collect_badly_shaped_triangles(face(hv, tmesh), tmesh, vpm, ecm, gt, + cap_threshold, needle_threshold, collapse_length_threshold, + edges_to_collapse, edges_to_flip); + } + } + +#ifdef CGAL_PMP_DEBUG_REMOVE_DEGENERACIES_EXTRA + std::string nb = std::to_string(++kk); + if(kk<10) nb = std::string("0")+nb; + if(kk<100) nb = std::string("0")+nb; + if(kk<1000) nb = std::string("0")+nb; + if(kk<10000) nb = std::string("0")+nb; + std::ofstream(std::string("tmp/n-")+nb+std::string(".off")) << tmesh; +#endif + something_was_done = true; + } + else + { +#ifdef CGAL_PMP_DEBUG_REMOVE_DEGENERACIES + std::cerr << "Warning: uncollapsable edge! " << tmesh.point(source(e, tmesh)) << " " + << tmesh.point(target(e, tmesh)) << std::endl; +#endif + next_edges_to_collapse.insert(e); + } + } + + // treat caps +#ifdef CGAL_PMP_DEBUG_REMOVE_DEGENERACIES_EXTRA + kk=0; + std::ofstream(std::string("tmp/c-000.off")) << tmesh; +#endif + while(!edges_to_flip.empty()) + { + edge_descriptor e = *edges_to_flip.begin(); + edges_to_flip.erase(edges_to_flip.begin()); + + CGAL_assertion(!get(ecm, e)); + + if(get(vcm, source(e, tmesh)) && get(vcm, target(e, tmesh))) + continue; + +#ifdef CGAL_PMP_DEBUG_REMOVE_DEGENERACIES + std::cout << "treat cap: " << e << " (" << tmesh.point(source(e, tmesh)) + << " --- " << tmesh.point(target(e, tmesh)) << ")" << std::endl; +#endif + + halfedge_descriptor h = halfedge(e, tmesh); + std::array nc = internal::is_badly_shaped(face(h, tmesh), tmesh, vpm, ecm, gt, + cap_threshold, needle_threshold, + collapse_length_threshold); + // First check the triangle is still a cap + if(nc[1] != h) + { +#ifdef CGAL_PMP_DEBUG_REMOVE_DEGENERACIES + std::cerr << "Warning: Cap criteria no longer verified " << tmesh.point(source(e, tmesh)) << " --- " + << tmesh.point(target(e, tmesh)) << std::endl; +#endif + // the opposite edge might also have been inserted in the set and might still be a cap + h = opposite(h, tmesh); + if(is_border(h, tmesh)) + continue; + + nc = internal::is_badly_shaped(face(h, tmesh), tmesh, vpm, ecm, gt, + cap_threshold, needle_threshold, collapse_length_threshold); + if(nc[1] != h) + continue; + } + + // special case on the border + if(is_border(opposite(h, tmesh), tmesh)) + { + // remove the triangle + edges_to_flip.erase(edge(prev(h, tmesh), tmesh)); + edges_to_flip.erase(edge(next(h, tmesh), tmesh)); + next_edges_to_collapse.erase(edge(prev(h, tmesh), tmesh)); + next_edges_to_collapse.erase(edge(next(h, tmesh), tmesh)); + Euler::remove_face(h, tmesh); + something_was_done = true; + continue; + } + + // condition for the flip to be valid (the edge to be created does not already exist) + if(!halfedge(target(next(h, tmesh), tmesh), + target(next(opposite(h, tmesh), tmesh), tmesh), tmesh).second) + { + + if(!internal::should_flip(e, tmesh, vpm, gt)) + { +#ifdef CGAL_PMP_DEBUG_REMOVE_DEGENERACIES + std::cout << "Flipping prevented: not the best diagonal" << std::endl; +#endif + next_edges_to_flip.insert(e); + continue; + } + +#ifdef CGAL_PMP_DEBUG_REMOVE_DEGENERACIES + std::cout << "Flipping" << std::endl; +#endif +#ifdef CGAL_PMP_DEBUG_REMOVE_DEGENERACIES_EXTRA + std::cerr << "step " << kk << "\n"; + std::cerr << " Flipping " << tmesh.point(source(h, tmesh)) << " " + << tmesh.point(target(h, tmesh)) << std::endl; +#endif + Euler::flip_edge(h, tmesh); + CGAL_assertion(edge(h, tmesh) == e); + + // handle face updates + for(int i=0; i<2; ++i) + { + CGAL_assertion(!is_border(h, tmesh)); + std::array nc = + internal::is_badly_shaped(face(h, tmesh), tmesh, vpm, ecm, gt, + cap_threshold, needle_threshold, collapse_length_threshold); + + if(nc[1] != boost::graph_traits::null_halfedge()) + { + if(edge(nc[1], tmesh) != e) + next_edges_to_flip.insert(edge(nc[1], tmesh)); + } + else + { + if(nc[0] != boost::graph_traits::null_halfedge()) + { + next_edges_to_collapse.insert(edge(nc[0], tmesh)); + } + } + h = opposite(h, tmesh); + } + something_was_done = true; + } +#ifdef CGAL_PMP_DEBUG_REMOVE_DEGENERACIES + else + { + std::cerr << "Warning: unflippable edge! " << tmesh.point(source(h, tmesh)) << " --- " + << tmesh.point(target(h, tmesh)) << std::endl; + next_edges_to_flip.insert(e); + } +#endif +#ifdef CGAL_PMP_DEBUG_REMOVE_DEGENERACIES_EXTRA + std::string nb = std::to_string(++kk); + if(kk<10) nb = std::string("0")+nb; + if(kk<100) nb = std::string("0")+nb; + if(kk<1000) nb = std::string("0")+nb; + if(kk<10000) nb = std::string("0")+nb; + std::ofstream(std::string("tmp/c-")+nb+std::string(".off")) << tmesh; +#endif + } + + std::swap(edges_to_collapse, next_edges_to_collapse); + std::swap(edges_to_flip, next_edges_to_flip); + + if(!something_was_done) + return false; + } + + return false; +} + +template +bool remove_almost_degenerate_faces(const FaceRange& face_range, + TriangleMesh& tmesh, + const double cap_threshold, + const double needle_threshold, + const double collapse_length_threshold) +{ + return remove_almost_degenerate_faces(face_range, tmesh, + cap_threshold, needle_threshold, collapse_length_threshold, + CGAL::parameters::all_default()); +} + +template +bool remove_almost_degenerate_faces(TriangleMesh& tmesh, + const double cap_threshold, + const double needle_threshold, + const double collapse_length_threshold, + const CGAL_PMP_NP_CLASS& np) +{ + return remove_almost_degenerate_faces(faces(tmesh), tmesh, cap_threshold, needle_threshold, + collapse_length_threshold, np); +} + +template +bool remove_almost_degenerate_faces(TriangleMesh& tmesh, + const double cap_threshold, + const double needle_threshold, + const double collapse_length_threshold) +{ + return remove_almost_degenerate_faces(faces(tmesh), tmesh, + cap_threshold, needle_threshold, collapse_length_threshold, + CGAL::parameters::all_default()); +} + +} // namespace experimental + +//////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////// +/// Remove degenerate_edges/faces + +namespace internal { + template struct Less_vertex_point { From b7d26464211398963a21641c02af2ad103314371 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mael=20Rouxel-Labb=C3=A9?= Date: Thu, 6 Feb 2020 17:14:29 +0100 Subject: [PATCH 52/56] Fix sanity check not failing on self-intersections --- .../Polygon_mesh_processing/remove_self_intersections.h | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/remove_self_intersections.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/remove_self_intersections.h index 6c73f4ad225..d0efae77202 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/remove_self_intersections.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/remove_self_intersections.h @@ -1103,21 +1103,18 @@ bool check_patch_sanity(const std::vector >& patch) } TriangleMesh patch_mesh; - if(!orient_polygon_soup(points, faces)) - return false; - if(is_polygon_soup_a_polygon_mesh(faces)) polygon_soup_to_polygon_mesh(points, faces, patch_mesh); else return false; - bool self_intersect = does_self_intersect(patch_mesh); - - if(self_intersect) + if(does_self_intersect(patch_mesh)) { #ifdef CGAL_PMP_REMOVE_SELF_INTERSECTION_DEBUG std::cout << " DEBUG: Tentative patch has self-intersections." << std::endl; #endif + + return false; } return true; From b7ed9716b0ba3374dd8be6f67c0e8f470985a4e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mael=20Rouxel-Labb=C3=A9?= Date: Thu, 6 Feb 2020 17:14:57 +0100 Subject: [PATCH 53/56] Fix includes to removed header --- .../test/Polygon_mesh_processing/test_remove_caps_needles.cpp | 2 +- .../demo/Polyhedron/Plugins/PMP/Repair_polyhedron_plugin.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Polygon_mesh_processing/test/Polygon_mesh_processing/test_remove_caps_needles.cpp b/Polygon_mesh_processing/test/Polygon_mesh_processing/test_remove_caps_needles.cpp index f6e418b23a7..fa784f53c8e 100644 --- a/Polygon_mesh_processing/test/Polygon_mesh_processing/test_remove_caps_needles.cpp +++ b/Polygon_mesh_processing/test/Polygon_mesh_processing/test_remove_caps_needles.cpp @@ -3,7 +3,7 @@ #include #include -#include +#include #include #include diff --git a/Polyhedron/demo/Polyhedron/Plugins/PMP/Repair_polyhedron_plugin.cpp b/Polyhedron/demo/Polyhedron/Plugins/PMP/Repair_polyhedron_plugin.cpp index f4196b90f22..d4e264588fb 100644 --- a/Polyhedron/demo/Polyhedron/Plugins/PMP/Repair_polyhedron_plugin.cpp +++ b/Polyhedron/demo/Polyhedron/Plugins/PMP/Repair_polyhedron_plugin.cpp @@ -16,7 +16,7 @@ #include #include #include -#include +#include #include "ui_RemoveNeedlesDialog.h" From 682254a3b8b2fb02d2da7e951956cfaf8ec63444 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mael=20Rouxel-Labb=C3=A9?= Date: Thu, 6 Feb 2020 17:19:36 +0100 Subject: [PATCH 54/56] Change test's debug macros --- .../test_pmp_remove_self_intersections.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Polygon_mesh_processing/test/Polygon_mesh_processing/test_pmp_remove_self_intersections.cpp b/Polygon_mesh_processing/test/Polygon_mesh_processing/test_pmp_remove_self_intersections.cpp index 370f2fd1141..44f6ae5aec7 100644 --- a/Polygon_mesh_processing/test/Polygon_mesh_processing/test_pmp_remove_self_intersections.cpp +++ b/Polygon_mesh_processing/test/Polygon_mesh_processing/test_pmp_remove_self_intersections.cpp @@ -1,4 +1,5 @@ -#define CGAL_PMP_COMPUTE_NORMAL_DEBUG_PP +// #define CGAL_PMP_SMOOTHING_DEBUG +#define CGAL_PMP_COMPUTE_NORMAL_DEBUG #define CGAL_PMP_REMOVE_SELF_INTERSECTION_DEBUG #define CGAL_PMP_REMOVE_SELF_INTERSECTION_OUTPUT From 567b43d693c53a6b5cb56df504c418c66c33b9ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mael=20Rouxel-Labb=C3=A9?= Date: Fri, 7 Feb 2020 08:43:03 +0100 Subject: [PATCH 55/56] Add missing header --- Polyhedron_IO/include/CGAL/IO/reader_helpers.h | 1 + 1 file changed, 1 insertion(+) diff --git a/Polyhedron_IO/include/CGAL/IO/reader_helpers.h b/Polyhedron_IO/include/CGAL/IO/reader_helpers.h index d74fe645bc6..42b2f98c0ed 100644 --- a/Polyhedron_IO/include/CGAL/IO/reader_helpers.h +++ b/Polyhedron_IO/include/CGAL/IO/reader_helpers.h @@ -14,6 +14,7 @@ #include #include #include +#include #include #include From 119f5b913bc1ac9ecae8d42440e8c65e76cec975 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mael=20Rouxel-Labb=C3=A9?= Date: Fri, 7 Feb 2020 16:51:24 +0100 Subject: [PATCH 56/56] Add some data --- .../data_degeneracies/deg_on_border.off | 10 ++++++++++ .../data_degeneracies/existing_flip.off | 19 +++++++++++++++++++ 2 files changed, 29 insertions(+) create mode 100644 Polygon_mesh_processing/test/Polygon_mesh_processing/data_degeneracies/deg_on_border.off create mode 100644 Polygon_mesh_processing/test/Polygon_mesh_processing/data_degeneracies/existing_flip.off diff --git a/Polygon_mesh_processing/test/Polygon_mesh_processing/data_degeneracies/deg_on_border.off b/Polygon_mesh_processing/test/Polygon_mesh_processing/data_degeneracies/deg_on_border.off new file mode 100644 index 00000000000..d8518b7cdfb --- /dev/null +++ b/Polygon_mesh_processing/test/Polygon_mesh_processing/data_degeneracies/deg_on_border.off @@ -0,0 +1,10 @@ +OFF +5 3 0 +0 0 0 +1 0 0 +2 -1 0 +3 -1 0 +4 0 0 +3 0 1 4 +3 1 2 3 +3 1 3 4 diff --git a/Polygon_mesh_processing/test/Polygon_mesh_processing/data_degeneracies/existing_flip.off b/Polygon_mesh_processing/test/Polygon_mesh_processing/data_degeneracies/existing_flip.off new file mode 100644 index 00000000000..cca5987fabe --- /dev/null +++ b/Polygon_mesh_processing/test/Polygon_mesh_processing/data_degeneracies/existing_flip.off @@ -0,0 +1,19 @@ +OFF +7 10 0 +0.314482809221655 -0.012877345839067217 -0.24601469358849659 +-0.081905665816609866 0.11959178909305103 0.59582950965713732 +1 1 0 +0.5 0.5 1 +0.75 0.75 0.5 +0.67302179212919955 0.77012616600142381 -0.44072953863256931 +0.14300722468117555 0.12954038296069673 0.88891433254868446 +3 0 5 2 +3 0 3 6 +3 5 0 4 +3 2 4 3 +3 4 0 1 +3 4 1 6 +3 4 2 5 +3 3 0 2 +3 6 3 4 +3 6 1 0