diff --git a/Polygon_mesh_processing/examples/Polygon_mesh_processing/CMakeLists.txt b/Polygon_mesh_processing/examples/Polygon_mesh_processing/CMakeLists.txt index 23fad404af5..b3364c27c55 100644 --- a/Polygon_mesh_processing/examples/Polygon_mesh_processing/CMakeLists.txt +++ b/Polygon_mesh_processing/examples/Polygon_mesh_processing/CMakeLists.txt @@ -88,6 +88,7 @@ create_single_source_cgal_program( "hole_filling_example_LCC.cpp" ) create_single_source_cgal_program( "detect_features_example.cpp" ) create_single_source_cgal_program( "manifoldness_repair_example.cpp" ) create_single_source_cgal_program( "repair_polygon_soup_example.cpp" ) +create_single_source_cgal_program( "clean_degeneracies.cpp" ) if(OpenMesh_FOUND) create_single_source_cgal_program( "compute_normals_example_OM.cpp" ) diff --git a/Polygon_mesh_processing/examples/Polygon_mesh_processing/clean_degeneracies.cpp b/Polygon_mesh_processing/examples/Polygon_mesh_processing/clean_degeneracies.cpp new file mode 100644 index 00000000000..ce7e30acd0b --- /dev/null +++ b/Polygon_mesh_processing/examples/Polygon_mesh_processing/clean_degeneracies.cpp @@ -0,0 +1,33 @@ +#include +#include + +#include + +#include +#include +#include + +typedef CGAL::Exact_predicates_inexact_constructions_kernel K; +typedef CGAL::Surface_mesh Mesh; + +typedef boost::graph_traits::halfedge_descriptor halfedge_descriptor; +typedef boost::graph_traits::edge_descriptor edge_descriptor; +typedef boost::graph_traits::face_descriptor face_descriptor; + +namespace PMP = CGAL::Polygon_mesh_processing; + +int main(int argc, char** argv) +{ + const char* filename = (argc > 1) ? argv[1] : "data/pig.off"; + std::ifstream input(filename); + + Mesh mesh; + if (!input || !(input >> mesh) || !CGAL::is_triangle_mesh(mesh)) { + std::cerr << "Not a valid input file." << std::endl; + return 1; + } + + PMP::remove_almost_degenerate_faces(mesh); + + return 0; +} 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..90ce2dadbe9 --- /dev/null +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/remove_degeneracies.h @@ -0,0 +1,220 @@ +// Copyright (c) 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 +#include // @tmp +#include +#include // @tmp + +namespace CGAL { +namespace Polygon_mesh_processing { +namespace internal { + +template +bool is_face_incident_to_border(const typename boost::graph_traits::face_descriptor f, + const TriangleMesh& tmesh) +{ + typedef typename boost::graph_traits::halfedge_descriptor halfedge_descriptor; + + for(halfedge_descriptor h : CGAL::halfedges_around_face(halfedge(f, tmesh), tmesh)) + { + if(is_border_edge(h, tmesh)) + return true; + } + + return false; +} + +template +void add_if_badly_shaped(const typename boost::graph_traits::face_descriptor f, + TriangleMesh& tmesh, + EdgeContainer& edges_to_collapse, + EdgeContainer& edges_to_flip, + const NamedParameters& np) +{ + typedef typename boost::graph_traits::halfedge_descriptor halfedge_descriptor; + + // @todo parameters + const double needle_threshold = 4; // longest edge / shortest edge over this ratio ==> needle + const double cap_threshold = std::cos(160. / 180 * CGAL_PI); // angle over 120° ==> cap + + if(is_face_incident_to_border(f, tmesh)) + return; + + halfedge_descriptor res = CGAL::Polygon_mesh_processing::is_needle_triangle_face(f, tmesh, needle_threshold, np); + if(res != boost::graph_traits::null_halfedge()) + { + std::cout << "add new needle: " << res << std::endl; + edges_to_collapse.insert(edge(res, tmesh)); + } + else // let's not make it possible to have a face be both a cap and a needle fo(for now) + { + res = CGAL::Polygon_mesh_processing::is_cap_triangle_face(f, tmesh, cap_threshold, np); + if(res != boost::graph_traits::null_halfedge()) + { + std::cout << "add new cap: " << res << std::endl; + edges_to_flip.insert(edge(res, tmesh)); + } + } +} + +} // namespace internal + +template +bool remove_almost_degenerate_faces(const FaceRange& face_range, + TriangleMesh& tmesh, + const NamedParameters& np) +{ + 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::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 + for(edge_descriptor e : edges_to_collapse) + { + std::cout << "treat needle: " << e << " (" << tmesh.point(source (e, tmesh)) << " --- " << tmesh.point(target(e, tmesh)) << ")" << std::endl; + if(CGAL::Euler::does_satisfy_link_condition(e, tmesh)) + { + vertex_descriptor v = Euler::collapse_edge(e, tmesh); // @todo move 'v' to the midpoint? + + edges_to_flip.erase(e); + + // The geometry of all the faces incident to 'v' has changed and so we recompute their badness + // @fixme nasty complexity, use tags or something... + for(halfedge_descriptor inc_h : CGAL::halfedges_around_target(v, tmesh)) + { + if(is_border_edge(inc_h, tmesh)) + continue; + + // since the 'bad' edge of a face incident to 'v' might not be an incident edge + for(halfedge_descriptor other_h : CGAL::halfedges_around_face(inc_h, tmesh)) + { + edge_descriptor other_e = edge(other_h, tmesh); + + if(other_e != e) // erasing the current position while looping is undefined behavior + edges_to_collapse.erase(other_e); + edges_to_flip.erase(other_e); + } + + // adding directly to 'edges_to_flip' + internal::add_if_badly_shaped(face(inc_h, tmesh), tmesh, next_edges_to_collapse, edges_to_flip, np); + } + } + else + { + std::cerr << "Warning: uncollapsable edge! " << tmesh.point(source(e, tmesh)) << " --- " + << tmesh.point(target(e, tmesh)) << std::endl; + } + } + + // treat caps + for(edge_descriptor e : edges_to_flip) + { + std::cout << "treat cap: " << e << " (" << tmesh.point(source (e, tmesh)) << " --- " << tmesh.point(target(e, tmesh)) << ")" << std::endl; + halfedge_descriptor h = halfedge(e, tmesh); + + // 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) + { + std::cout << "Flippin!" << std::endl; + Euler::flip_edge(h, tmesh); + + internal::add_if_badly_shaped(face(h, tmesh), tmesh, next_edges_to_collapse, next_edges_to_flip, np); + internal::add_if_badly_shaped(face(opposite(h, tmesh), tmesh), tmesh, next_edges_to_collapse, next_edges_to_flip, np); + } + else + { + std::cerr << "Warning: unflippable edge! " << tmesh.point(source(h, tmesh)) << " --- " + << tmesh.point(target(h, tmesh)) << std::endl; + } + } + + std::swap(edges_to_collapse, next_edges_to_collapse); + std::swap(edges_to_flip, next_edges_to_flip); + } + + return false; +} + +template +bool remove_almost_degenerate_faces(const FaceRange& face_range, + TriangleMesh& tmesh) +{ + return remove_almost_degenerate_faces(face_range, tmesh, parameters::all_default()); +} + +template +bool remove_almost_degenerate_faces(TriangleMesh& tmesh, + const CGAL_PMP_NP_CLASS& np) +{ + return remove_almost_degenerate_faces(faces(tmesh), tmesh, np); +} + +template +bool remove_almost_degenerate_faces(TriangleMesh& tmesh) +{ + return remove_almost_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.h b/Polygon_mesh_processing/include/CGAL/polygon_mesh_processing.h index 1bfacaf330a..ffb4862c504 100644 --- a/Polygon_mesh_processing/include/CGAL/polygon_mesh_processing.h +++ b/Polygon_mesh_processing/include/CGAL/polygon_mesh_processing.h @@ -52,6 +52,7 @@ #include #include #include +#include // the named parameter header being not documented the doc is put here for now #ifdef DOXYGEN_RUNNING