From 49a971e9c2ae57864bbafccfc0b65c3a535c8a2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mael=20Rouxel-Labb=C3=A9?= Date: Fri, 20 Jul 2018 17:30:40 +0200 Subject: [PATCH] Various improvements/fixes to degenerate/needle/cap functions --- .../NamedParameters.txt | 13 +- .../CGAL/Polygon_mesh_processing/helpers.h | 352 +++++++++--------- .../Isotropic_remeshing/remesh_impl.h | 88 ++--- .../CGAL/Polygon_mesh_processing/remesh.h | 7 +- .../CGAL/Polygon_mesh_processing/repair.h | 122 ++++-- .../data_degeneracies/caps_and_needles.off | 17 + .../test_predicates.cpp | 244 ++++++++---- .../Plugins/PMP/Selection_plugin.cpp | 4 +- .../demo/Polyhedron/Scene_polyhedron_item.cpp | 2 +- .../Scene_polyhedron_selection_item.cpp | 3 +- .../Polyhedron/Scene_surface_mesh_item.cpp | 2 +- .../include/CGAL/statistics_helpers.h | 25 +- 12 files changed, 519 insertions(+), 360 deletions(-) create mode 100644 Polygon_mesh_processing/test/Polygon_mesh_processing/data_degeneracies/caps_and_needles.off diff --git a/Polygon_mesh_processing/doc/Polygon_mesh_processing/NamedParameters.txt b/Polygon_mesh_processing/doc/Polygon_mesh_processing/NamedParameters.txt index 002f2a837bb..a2c5456db34 100644 --- a/Polygon_mesh_processing/doc/Polygon_mesh_processing/NamedParameters.txt +++ b/Polygon_mesh_processing/doc/Polygon_mesh_processing/NamedParameters.txt @@ -337,7 +337,7 @@ of a mesh independently.\n Parameter used to pass a visitor class to a function. Its type and behavior depend on the visited function. \n \b Type : `A class` \n -\b Default Specific to the function visited +\b Default : Specific to the function visited \cgalNPEnd \cgalNPBegin{throw_on_self_intersection} \anchor PMP_throw_on_self_intersection @@ -364,18 +364,13 @@ should be considered as part of the clipping volume or not. \b Default value is `true` \cgalNPEnd - \cgalNPBegin{output_iterator} \anchor PMP_output_iterator -Iterator where `std::vector` can be put. -The first vertex of the vector is an input vertex that was non-manifold, -the other vertices in the vertex are the new vertices created to fix -the non-manifoldness. +Parameter to pass an output iterator. \n -\b Type : `iterator` \n -\b Default `Emptyset_iterator` +\b Type : a model of `OutputIterator` \n +\b Default : `Emptyset_iterator` \cgalNPEnd - \cgalNPTableEnd */ diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/helpers.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/helpers.h index b41c37e1c80..8e9cfeef182 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/helpers.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/helpers.h @@ -1,4 +1,4 @@ -// Copyright (c) 2015 GeometryFactory (France). +// Copyright (c) 2015, 2018 GeometryFactory (France). // All rights reserved. // // This file is part of CGAL (www.cgal.org). @@ -17,7 +17,8 @@ // SPDX-License-Identifier: GPL-3.0+ // // -// Author(s) : Konstantinos Katrioplas +// Author(s) : Konstantinos Katrioplas, +// Mael Rouxel-Labbé #ifndef CGAL_POLYGON_MESH_PROCESSING_HELPERS_H #define CGAL_POLYGON_MESH_PROCESSING_HELPERS_H @@ -25,86 +26,40 @@ #include #include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include + namespace CGAL { namespace Polygon_mesh_processing { -namespace internal { - -template -struct Vertex_collector -{ - typedef typename boost::graph_traits::vertex_descriptor vertex_descriptor; - void collect_vertices(vertex_descriptor v1, vertex_descriptor v2) - { - std::vector& verts = collections[v1]; - if (verts.empty()) - verts.push_back(v1); - verts.push_back(v2); - } - - void dump(OutputIterator out) - { - typedef std::pair > Pair_type; - BOOST_FOREACH(const Pair_type& p, collections) - { - *out++=p.second; - } - } - - std::map > collections; -}; - -template -struct Vertex_collector -{ - typedef typename boost::graph_traits::vertex_descriptor vertex_descriptor; - void collect_vertices(vertex_descriptor, vertex_descriptor) - {} - - void dump(Emptyset_iterator) - {} -}; - -// used only for testing -template -void merge_identical_points(PolygonMesh& mesh, - typename boost::graph_traits::vertex_descriptor v_keep, - typename boost::graph_traits::vertex_descriptor v_rm) -{ - typedef typename boost::graph_traits::halfedge_descriptor halfedge_descriptor; - halfedge_descriptor h = halfedge(v_rm, mesh); - halfedge_descriptor start = h; - - do{ - set_target(h, v_keep, mesh); - h = opposite(next(h, mesh), mesh); - } while( h != start ); - - remove_vertex(v_rm, mesh); -} -} // end internal - /// \ingroup PMP_repairing_grp -/// checks whether a vertex is non-manifold. +/// checks whether a vertex of a triangle mesh is non-manifold. /// -/// @tparam PolygonMesh a model of `FaceListGraph` and `MutableFaceGraph` +/// @tparam TriangleMesh a model of `HalfedgeListGraph` /// -/// @param v the vertex -/// @param tm triangle mesh containing v +/// @param v a vertex of `tm` +/// @param tm a triangle mesh containing `v` /// -/// \return true if the vertrex is non-manifold -template -bool is_non_manifold_vertex(typename boost::graph_traits::vertex_descriptor v, - const PolygonMesh& tm) +/// \return `true` if the vertrex is non-manifold, `false` otherwise. +template +bool is_non_manifold_vertex(typename boost::graph_traits::vertex_descriptor v, + const TriangleMesh& tm) { CGAL_assertion(CGAL::is_triangle_mesh(tm)); - typedef boost::graph_traits GT; - typedef typename GT::halfedge_descriptor halfedge_descriptor; + typedef typename boost::graph_traits::halfedge_descriptor halfedge_descriptor; boost::unordered_set halfedges_handled; - BOOST_FOREACH(halfedge_descriptor h, halfedges_around_target(v, tm)) halfedges_handled.insert(h); @@ -121,28 +76,28 @@ bool is_non_manifold_vertex(typename boost::graph_traits::vertex_de /// \ingroup PMP_repairing_grp /// checks whether an edge is degenerate. -/// An edge is considered degenerate if the points of its vertices are identical. +/// An edge is considered degenerate if the geometric positions of its two extremities are identical. /// /// @tparam PolygonMesh a model of `HalfedgeGraph` /// @tparam NamedParameters a sequence of \ref pmp_namedparameters "Named Parameters" /// -/// @param e the edge -/// @param pm polygon mesh containing e +/// @param e an edge of `pm` +/// @param pm polygon mesh containing `e` /// @param np optional \ref pmp_namedparameters "Named Parameters" described below /// /// \cgalNamedParamsBegin -/// \cgalParamBegin{vertex_point_map} the property map with the points associated to the vertices of `pmesh`. The type of this map is model of `ReadWritePropertyMap`. -/// If this parameter is omitted, an internal property map for -/// `CGAL::vertex_point_t` should be available in `PolygonMesh` +/// \cgalParamBegin{vertex_point_map} the property map with the points associated to the vertices of `pm`. +/// The type of this map is model of `ReadWritePropertyMap`. +/// If this parameter is omitted, an internal property map for +/// `CGAL::vertex_point_t` should be available in `PolygonMesh` /// \cgalParamEnd -/// \cgalParamBegin{geom_traits} a geometric traits class instance. -/// The traits class must provide the nested type `Point_3`, -/// and the nested functor : -/// - `Equal_3` to check whether 2 points are identical +/// \cgalParamBegin{geom_traits} a geometric traits class instance. +/// The traits class must provide the nested type `Point_3`, +/// and the nested functor `Equal_3` to check whether two points are identical. /// \cgalParamEnd /// \cgalNamedParamsEnd /// -/// \return true if the edge is degenerate +/// \return `true` if the edge `e` is degenerate, `false` otherwise. template bool is_degenerate_edge(typename boost::graph_traits::edge_descriptor e, const PolygonMesh& pm, @@ -154,12 +109,11 @@ bool is_degenerate_edge(typename boost::graph_traits::edge_descript typedef typename GetVertexPointMap::const_type VertexPointMap; VertexPointMap vpmap = choose_param(get_param(np, internal_np::vertex_point), get_const_property_map(vertex_point, pm)); + typedef typename GetGeomTraits::type Traits; Traits traits = choose_param(get_param(np, internal_np::geom_traits), Traits()); - if ( traits.equal_3_object()(get(vpmap, target(e, pm)), get(vpmap, source(e, pm))) ) - return true; - return false; + return traits.equal_3_object()(get(vpmap, source(e, pm)), get(vpmap, target(e, pm))); } template @@ -171,34 +125,34 @@ bool is_degenerate_edge(typename boost::graph_traits::edge_descript /// \ingroup PMP_repairing_grp /// checks whether a triangle face is degenerate. -/// A triangle face is degenerate if its points are collinear. +/// A triangle face is considered degenerate if the geometric positions of its vertices are collinear. /// /// @tparam TriangleMesh a model of `FaceGraph` /// @tparam NamedParameters a sequence of \ref pmp_namedparameters "Named Parameters" /// -/// @param f the triangle face -/// @param tm triangle mesh containing f +/// @param f a triangle face of `tm` +/// @param tm a triangle mesh containing `f` /// @param np optional \ref pmp_namedparameters "Named Parameters" described below /// /// \cgalNamedParamsBegin -/// \cgalParamBegin{vertex_point_map} the property map with the points associated to the vertices of `pmesh`. The type of this map is model of `ReadWritePropertyMap`. -/// If this parameter is omitted, an internal property map for -/// `CGAL::vertex_point_t` should be available in `TriangleMesh` +/// \cgalParamBegin{vertex_point_map} the property map with the points associated to the vertices of `tm`. +/// The type of this map is model of `ReadWritePropertyMap`. +/// If this parameter is omitted, an internal property map for +/// `CGAL::vertex_point_t` should be available in `TriangleMesh` /// \cgalParamEnd /// \cgalParamBegin{geom_traits} a geometric traits class instance. -/// The traits class must provide the nested type `Point_3`, -/// and the nested functor : -/// - `Collinear_3` to check whether 3 points are collinear +/// The traits class must provide the nested functor `Collinear_3` +/// to check whether three points are collinear. /// \cgalParamEnd /// \cgalNamedParamsEnd /// -/// \return true if the triangle face is degenerate +/// \return `true` if the face `f` is degenerate, `false` otherwise. template bool is_degenerate_triangle_face(typename boost::graph_traits::face_descriptor f, const TriangleMesh& tm, const NamedParameters& np) { - CGAL_assertion(CGAL::is_triangle_mesh(tm)); + CGAL_precondition(CGAL::is_triangle_mesh(tm)); using boost::get_param; using boost::choose_param; @@ -206,15 +160,15 @@ bool is_degenerate_triangle_face(typename boost::graph_traits::fac typedef typename GetVertexPointMap::const_type VertexPointMap; VertexPointMap vpmap = choose_param(get_param(np, internal_np::vertex_point), get_const_property_map(vertex_point, tm)); + typedef typename GetGeomTraits::type Traits; Traits traits = choose_param(get_param(np, internal_np::geom_traits), Traits()); - typename boost::graph_traits::halfedge_descriptor hd = halfedge(f,tm); - const typename Traits::Point_3& p1 = get(vpmap, target( hd, tm) ); - const typename Traits::Point_3& p2 = get(vpmap, target(next(hd, tm), tm) ); - const typename Traits::Point_3& p3 = get(vpmap, source( hd, tm) ); - return traits.collinear_3_object()(p1, p2, p3); + typename boost::graph_traits::halfedge_descriptor h = halfedge(f, tm); + return traits.collinear_3_object()(get(vpmap, source(h, tm)), + get(vpmap, target(h, tm)), + get(vpmap, target(next(h, tm), tm))); } template @@ -226,159 +180,185 @@ bool is_degenerate_triangle_face(typename boost::graph_traits::fac /// \ingroup PMP_repairing_grp /// checks whether a triangle face is needle. -/// A triangle is needle if its longest edge is much longer than the shortest one. +/// A triangle is said to be a needle if its longest edge is much longer than its shortest edge. /// /// @tparam TriangleMesh a model of `FaceGraph` /// @tparam NamedParameters a sequence of \ref pmp_namedparameters "Named Parameters" /// -/// @param f the triangle face -/// @param tm triangle mesh containing f -/// @param threshold the cosine of an angle of f. -/// The threshold is in range [0 1] and corresponds to -/// angles between 0 and 90 degrees. +/// @param f a triangle face of `tm` +/// @param tm triangle mesh containing `f` +/// @param threshold a bound on the ratio of the longest edge length and the shortest edge length /// @param np optional \ref pmp_namedparameters "Named Parameters" described below /// /// \cgalNamedParamsBegin -/// \cgalParamBegin{vertex_point_map} the property map with the points associated to the vertices of `pmesh`. The type of this map is model of `ReadWritePropertyMap`. -/// If this parameter is omitted, an internal property map for -/// `CGAL::vertex_point_t` should be available in `TriangleMesh` +/// \cgalParamBegin{vertex_point_map} the property map with the points associated to the vertices of `tm`. +/// The type of this map is model of `ReadWritePropertyMap`. +/// If this parameter is omitted, an internal property map for +/// `CGAL::vertex_point_t` should be available in `TriangleMesh` /// \cgalParamEnd /// \cgalParamBegin{geom_traits} a geometric traits class instance. -/// The traits class must provide the nested type `Point_3`. +/// The traits class must provide the nested type `FT` and +/// the nested functor `Compute_squared_distance_3`. /// \cgalParamEnd /// \cgalNamedParamsEnd /// -/// \return true if the triangle face is a needle +/// \return the smallest halfedge if the triangle face is a needle, and a null halfedge otherwise. template -bool is_needle_triangle_face(typename boost::graph_traits::face_descriptor f, - const TriangleMesh& tm, - const double threshold, - const NamedParameters& np) +typename boost::graph_traits::halfedge_descriptor +is_needle_triangle_face(typename boost::graph_traits::face_descriptor f, + const TriangleMesh& tm, + const double threshold, + const NamedParameters& np) { - CGAL_assertion(CGAL::is_triangle_mesh(tm)); - CGAL_assertion(threshold >= 0); - CGAL_assertion(threshold <= 1); + CGAL_precondition(CGAL::is_triangle_mesh(tm)); + CGAL_precondition(threshold >= 1.); using boost::get_param; using boost::choose_param; + typedef typename boost::graph_traits::halfedge_descriptor halfedge_descriptor; + typedef typename GetVertexPointMap::const_type VertexPointMap; VertexPointMap vpmap = choose_param(get_param(np, internal_np::vertex_point), get_const_property_map(vertex_point, tm)); - typedef typename GetGeomTraits::type Traits; - typedef typename Traits::FT FT; - typedef boost::graph_traits GT; - typedef typename GT::halfedge_descriptor halfedge_descriptor; - typedef typename GT::vertex_descriptor vertex_descriptor; - typedef typename boost::property_traits::value_type Point_type; - typedef typename Kernel_traits::Kernel::Vector_3 Vector; - BOOST_FOREACH(halfedge_descriptor h, halfedges_around_face(halfedge(f, tm), tm)) + typedef typename GetGeomTraits::type Traits; + Traits traits = choose_param(get_param(np, internal_np::geom_traits), Traits()); + + typedef typename Traits::FT FT; + + const halfedge_descriptor h0 = halfedge(f, tm); + FT max_sq_length = - std::numeric_limits::max(), + min_sq_length = std::numeric_limits::max(); + halfedge_descriptor min_h = boost::graph_traits::null_halfedge(); + + BOOST_FOREACH(halfedge_descriptor h, halfedges_around_face(h0, tm)) { - vertex_descriptor v0 = source(h, tm); - vertex_descriptor v1 = target(h, tm); - vertex_descriptor v2 = target(next(h, tm), tm); - Vector a = get(vpmap, v0) - get (vpmap, v1); - Vector b = get(vpmap, v2) - get(vpmap, v1); - FT aa = a.squared_length(); - FT bb = b.squared_length(); - FT squared_dot_ab = ((a*b)*(a*b)) / (aa * bb); + const FT sq_length = traits.compute_squared_distance_3_object()(get(vpmap, source(h, tm)), + get(vpmap, target(h, tm))); - if(squared_dot_ab > threshold * threshold) - return true; + if(max_sq_length < sq_length) + max_sq_length = sq_length; + + if(min_sq_length > sq_length) + { + min_h = h; + min_sq_length = sq_length; + } } - return false; + const FT sq_threshold = threshold * threshold; + if(max_sq_length / min_sq_length >= sq_threshold) + { + CGAL_assertion(min_h != boost::graph_traits::null_halfedge()); + return min_h; + } + else + return boost::graph_traits::null_halfedge(); } template -bool is_needle_triangle_face(typename boost::graph_traits::face_descriptor f, - const TriangleMesh& tm, - const double threshold) +typename boost::graph_traits::halfedge_descriptor +is_needle_triangle_face(typename boost::graph_traits::face_descriptor f, + const TriangleMesh& tm, + const double threshold) { return is_needle_triangle_face(f, tm, threshold, parameters::all_default()); } /// \ingroup PMP_repairing_grp /// checks whether a triangle face is a cap. -/// A triangle is a cap if it has an angle very close to 180 degrees. +/// A triangle is said to be a cap if one of the its angles is close to `180` degrees. /// /// @tparam TriangleMesh a model of `FaceGraph` /// @tparam NamedParameters a sequence of \ref pmp_namedparameters "Named Parameters" /// -/// @param f the triangle face -/// @param tm triangle mesh containing f -/// @param threshold the cosine of an angle of f. -/// The threshold is in range [-1 0] and corresponds to -/// angles between 90 and 180 degrees. +/// @param f a triangle face of `tm` +/// @param tm triangle mesh containing `f` +/// @param threshold the cosine of a minimum angle such that if `f` has an angle greater than this bound, +/// it is a cap. The threshold is in range `[-1 0]` and corresponds to an angle +/// between `90` and `180` degrees. /// @param np optional \ref pmp_namedparameters "Named Parameters" described below /// /// \cgalNamedParamsBegin -/// \cgalParamBegin{vertex_point_map} the property map with the points associated to the vertices of `pmesh`. The type of this map is model of `ReadWritePropertyMap`. -/// If this parameter is omitted, an internal property map for -/// `CGAL::vertex_point_t` should be available in `TriangleMesh` +/// \cgalParamBegin{vertex_point_map} the property map with the points associated to the vertices of `tm`. +/// The type of this map is model of `ReadWritePropertyMap`. +/// If this parameter is omitted, an internal property map for +/// `CGAL::vertex_point_t` should be available in `TriangleMesh` /// \cgalParamEnd /// \cgalParamBegin{geom_traits} a geometric traits class instance. -/// The traits class must provide the nested type `Point_3` +/// The traits class must provide the nested type `Point_3` /// \cgalParamEnd /// \cgalNamedParamsEnd /// -/// \return true if the triangle face is a cap +/// \return `true` if the triangle face is a cap template -bool is_cap_triangle_face(typename boost::graph_traits::face_descriptor f, - const TriangleMesh& tm, - const double threshold, - const NamedParameters& np) +typename boost::graph_traits::halfedge_descriptor +is_cap_triangle_face(typename boost::graph_traits::face_descriptor f, + const TriangleMesh& tm, + const double threshold, + const NamedParameters& np) { - CGAL_assertion(CGAL::is_triangle_mesh(tm)); - CGAL_assertion(threshold >= -1); - CGAL_assertion(threshold <= 0); + CGAL_precondition(CGAL::is_triangle_mesh(tm)); + CGAL_precondition(threshold >= -1.); + CGAL_precondition(threshold <= 0.); using boost::get_param; using boost::choose_param; + typedef typename boost::graph_traits::vertex_descriptor vertex_descriptor; + typedef typename boost::graph_traits::halfedge_descriptor halfedge_descriptor; + typedef typename GetVertexPointMap::const_type VertexPointMap; VertexPointMap vpmap = choose_param(get_param(np, internal_np::vertex_point), get_const_property_map(vertex_point, tm)); - typedef typename GetGeomTraits::type Traits; - typedef typename Traits::FT FT; - typedef boost::graph_traits GT; - typedef typename GT::halfedge_descriptor halfedge_descriptor; - typedef typename GT::vertex_descriptor vertex_descriptor; - typedef typename boost::property_traits::value_type Point_type; - typedef typename Kernel_traits::Kernel::Vector_3 Vector; - BOOST_FOREACH(halfedge_descriptor h, halfedges_around_face(halfedge(f, tm), tm)) + typedef typename GetGeomTraits::type Traits; + Traits traits = choose_param(get_param(np, internal_np::geom_traits), Traits()); + + typedef typename Traits::FT FT; + typedef typename Traits::Vector_3 Vector_3; + + const FT sq_threshold = threshold * threshold; + const halfedge_descriptor h0 = halfedge(f, tm); + + cpp11::array sq_lengths; + int pos = 0; + BOOST_FOREACH(halfedge_descriptor h, halfedges_around_face(h0, tm)) { - vertex_descriptor v0 = source(h, tm); - vertex_descriptor v1 = target(h, tm); - vertex_descriptor v2 = target(next(h, tm), tm); - Vector a = get(vpmap, v0) - get (vpmap, v1); - Vector b = get(vpmap, v2) - get(vpmap, v1); - FT aa = a.squared_length(); - FT bb = b.squared_length(); - FT squared_dot_ab = ((a*b)*(a*b)) / (aa * bb); - - if(squared_dot_ab > threshold * threshold) - return true; + sq_lengths[pos++] = traits.compute_squared_distance_3_object()(get(vpmap, source(h, tm)), + get(vpmap, target(h, tm))); } - return false; + + pos = 0; + BOOST_FOREACH(halfedge_descriptor h, halfedges_around_face(h0, tm)) + { + const vertex_descriptor v0 = source(h, tm); + const vertex_descriptor v1 = target(h, tm); + const vertex_descriptor v2 = target(next(h, tm), tm); + const Vector_3 a = traits.construct_vector_3_object()(get(vpmap, v1), get(vpmap, v2)); + const Vector_3 b = traits.construct_vector_3_object()(get(vpmap, v1), get(vpmap, v0)); + const FT dot_ab = traits.compute_scalar_product_3_object()(a, b); + const bool neg_sp = (dot_ab <= 0); + const FT sq_a = sq_lengths[(pos+1)%3]; + const FT sq_b = sq_lengths[pos]; + const FT sq_cos = dot_ab * dot_ab / (sq_a * sq_b); + + if(neg_sp && sq_cos >= sq_threshold) + return prev(h, tm); + } + return boost::graph_traits::null_halfedge(); } template -bool is_cap_triangle_face(typename boost::graph_traits::face_descriptor f, - const TriangleMesh& tm, - const double threshold) +typename boost::graph_traits::halfedge_descriptor +is_cap_triangle_face(typename boost::graph_traits::face_descriptor f, + const TriangleMesh& tm, + const double threshold) { return is_cap_triangle_face(f, tm, threshold, parameters::all_default()); } - - - } } // end namespaces CGAL and PMP - - #endif // CGAL_POLYGON_MESH_PROCESSING_HELPERS_H - diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Isotropic_remeshing/remesh_impl.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Isotropic_remeshing/remesh_impl.h index 428d49a6309..e5e914402db 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Isotropic_remeshing/remesh_impl.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/Isotropic_remeshing/remesh_impl.h @@ -311,6 +311,7 @@ namespace internal { public: Incremental_remesher(PolygonMesh& pmesh , VertexPointMap& vpmap + , const GeomTraits& gt , const bool protect_constraints , EdgeIsConstrainedMap ecmap , VertexIsConstrainedMap vcmap @@ -319,6 +320,7 @@ namespace internal { , const bool build_tree = true)//built by the remesher : mesh_(pmesh) , vpmap_(vpmap) + , gt_(gt) , build_tree_(build_tree) , has_border_(false) , input_triangles_() @@ -350,9 +352,10 @@ namespace internal { BOOST_FOREACH(face_descriptor f, face_range) { - if (is_degenerate_triangle_face(f, mesh_)){ + if(is_degenerate_triangle_face(f, mesh_, parameters::vertex_point_map(vpmap_) + .geom_traits(gt_))) continue; - } + Patch_id pid = get_patch_id(f); input_triangles_.push_back(triangle(f)); input_patch_ids_.push_back(pid); @@ -803,7 +806,8 @@ namespace internal { debug_status_map(); debug_self_intersections(); CGAL_assertion(0 == PMP::remove_degenerate_faces(mesh_, - PMP::parameters::vertex_point_map(vpmap_).geom_traits(GeomTraits()))); + parameters::vertex_point_map(vpmap_) + .geom_traits(gt_))); #endif } @@ -908,7 +912,7 @@ namespace internal { debug_status_map(); CGAL_assertion(0 == PMP::remove_degenerate_faces(mesh_ , PMP::parameters::vertex_point_map(vpmap_) - .geom_traits(GeomTraits()))); + .geom_traits(gt_))); debug_self_intersections(); #endif @@ -950,9 +954,9 @@ namespace internal { else if (is_on_patch(v)) { - Vector_3 vn = PMP::compute_vertex_normal(v, mesh_ - , PMP::parameters::vertex_point_map(vpmap_) - .geom_traits(GeomTraits())); + Vector_3 vn = PMP::compute_vertex_normal(v, mesh_, + parameters::vertex_point_map(vpmap_) + .geom_traits(gt_)); put(propmap_normals, v, vn); Vector_3 move = CGAL::NULL_VECTOR; @@ -1444,20 +1448,8 @@ private: if (f == boost::graph_traits::null_face()) return CGAL::NULL_VECTOR; - halfedge_descriptor hd = halfedge(f, mesh_); - typename boost::property_traits::reference - p = get(vpmap_, target(hd, mesh_)); - hd = next(hd,mesh_); - typename boost::property_traits::reference - q = get(vpmap_, target(hd, mesh_)); - hd = next(hd,mesh_); - typename boost::property_traits::reference - r =get(vpmap_, target(hd, mesh_)); - - if (GeomTraits().collinear_3_object()(p,q,r)) - return CGAL::NULL_VECTOR; - else - return PMP::compute_face_normal(f, mesh_, parameters::vertex_point_map(vpmap_)); + return PMP::compute_face_normal(f, mesh_, parameters::vertex_point_map(vpmap_) + .geom_traits(gt_)); } template @@ -1573,27 +1565,31 @@ private: const bool collapse_constraints) { CGAL_assertion_code(std::size_t nb_done = 0); + boost::unordered_set degenerate_faces; BOOST_FOREACH(halfedge_descriptor h, halfedges_around_target(halfedge(v, mesh_), mesh_)) { - if (is_border(h, mesh_)) - continue; - if (is_degenerate_triangle_face(face(h), mesh_)) + if(!is_border(h, mesh_) && + is_degenerate_triangle_face(face(h, mesh_), mesh_, + parameters::vertex_point_map(vpmap_) + .geom_traits(gt_))) degenerate_faces.insert(h); } + while(!degenerate_faces.empty()) { halfedge_descriptor h = *(degenerate_faces.begin()); degenerate_faces.erase(degenerate_faces.begin()); - if (!is_degenerate_triangle_face(face(h), mesh_)) + if (!is_degenerate_triangle_face(face(h, mesh_), mesh_, + parameters::vertex_point_map(vpmap_) + .geom_traits(gt_))) //this can happen when flipping h has consequences further in the mesh continue; //check that opposite is not also degenerate - if (degenerate_faces.find(opposite(h, mesh_)) != degenerate_faces.end()) - degenerate_faces.erase(opposite(h, mesh_)); + degenerate_faces.erase(opposite(h, mesh_)); if(is_border(h, mesh_)) continue; @@ -1639,11 +1635,15 @@ private: short_edges.insert(typename Bimap::value_type(hf, sqlen)); } - if (!is_border(hf, mesh_) - && is_degenerate_triangle_face(face(h), mesh_)) + if(!is_border(hf, mesh_) && + is_degenerate_triangle_face(face(hf, mesh_), mesh_, + parameters::vertex_point_map(vpmap_) + .geom_traits(gt_))) degenerate_faces.insert(hf); - if (!is_border(hfo, mesh_) - && is_degenerate_triangle_face(face(h), mesh_)) + if(!is_border(hfo, mesh_) && + is_degenerate_triangle_face(face(hfo, mesh_), mesh_, + parameters::vertex_point_map(vpmap_) + .geom_traits(gt_))) degenerate_faces.insert(hfo); break; @@ -1660,9 +1660,10 @@ private: BOOST_FOREACH(halfedge_descriptor h, halfedges_around_target(he, mesh_)) { - if (is_border(h, mesh_)) - continue; - if (is_degenerate_triangle_face(face(h), mesh_)) + if(!is_border(h, mesh_) && + is_degenerate_triangle_face(face(h, mesh_), mesh_, + parameters::vertex_point_map(vpmap_) + .geom_traits(gt_))) return true; } return false; @@ -1803,10 +1804,10 @@ private: { std::cout << "Test self intersections..."; std::vector > facets; - PMP::self_intersections( - mesh_, - std::back_inserter(facets), - PMP::parameters::vertex_point_map(vpmap_)); + PMP::self_intersections(mesh_, + std::back_inserter(facets), + PMP::parameters::vertex_point_map(vpmap_) + .geom_traits(gt_)); //CGAL_assertion(facets.empty()); std::cout << "done ("<< facets.size() <<" facets)." << std::endl; } @@ -1815,11 +1816,11 @@ private: { std::cout << "Test self intersections..."; std::vector > facets; - PMP::self_intersections( - faces_around_target(halfedge(v, mesh_), mesh_), - mesh_, - std::back_inserter(facets), - PMP::parameters::vertex_point_map(vpmap_)); + PMP::self_intersections(faces_around_target(halfedge(v, mesh_), mesh_), + mesh_, + std::back_inserter(facets), + PMP::parameters::vertex_point_map(vpmap_) + .geom_traits(gt_)); //CGAL_assertion(facets.empty()); std::cout << "done ("<< facets.size() <<" facets)." << std::endl; } @@ -1902,6 +1903,7 @@ private: private: PolygonMesh& mesh_; VertexPointMap& vpmap_; + const GeomTraits& gt_; bool build_tree_; bool has_border_; std::vector trees; diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/remesh.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/remesh.h index 40994d53a87..b50060998f0 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/remesh.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/remesh.h @@ -163,6 +163,7 @@ void isotropic_remeshing(const FaceRange& faces boost::is_default_param(get_param(np, internal_np::projection_functor)); typedef typename GetGeomTraits::type GT; + GT gt = choose_param(get_param(np, internal_np::geom_traits), GT()); typedef typename GetVertexPointMap::type VPMap; VPMap vpmap = choose_param(get_param(np, internal_np::vertex_point), @@ -227,7 +228,7 @@ void isotropic_remeshing(const FaceRange& faces #endif typename internal::Incremental_remesher - remesher(pmesh, vpmap, protect, ecmap, vcmap, fpmap, fimap, need_aabb_tree); + remesher(pmesh, vpmap, gt, protect, ecmap, vcmap, fpmap, fimap, need_aabb_tree); remesher.init_remeshing(faces); #ifdef CGAL_PMP_REMESHING_VERBOSE @@ -340,6 +341,8 @@ void split_long_edges(const EdgeRange& edges using boost::get_param; typedef typename GetGeomTraits::type GT; + GT gt = choose_param(get_param(np, internal_np::geom_traits), GT()); + typedef typename GetVertexPointMap::type VPMap; VPMap vpmap = choose_param(get_param(np, internal_np::vertex_point), get_property_map(vertex_point, pmesh)); @@ -361,7 +364,7 @@ void split_long_edges(const EdgeRange& edges internal::Connected_components_pmap, FIMap > - remesher(pmesh, vpmap, false/*protect constraints*/, ecmap, + remesher(pmesh, vpmap, gt, false/*protect constraints*/, ecmap, Constant_property_map(false), internal::Connected_components_pmap(faces(pmesh), pmesh, ecmap, fimap, false), fimap, diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/repair.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/repair.h index e03c785e990..15c77eeb5c1 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/repair.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/repair.h @@ -159,17 +159,27 @@ struct Less_vertex_point{ } }; +template +OutputIterator +degenerate_faces(const TriangleMesh& tm, + OutputIterator out, + const NamedParameters& np) +{ + typedef typename boost::graph_traits::face_descriptor face_descriptor; + + BOOST_FOREACH(face_descriptor fd, faces(tm)) + { + if(is_degenerate_triangle_face(fd, tm, np)) + *out++ = fd; + } + return out; +} + template OutputIterator degenerate_faces(const TriangleMesh& tm, OutputIterator out) { - typedef typename boost::graph_traits::face_descriptor face_descriptor; - BOOST_FOREACH(face_descriptor fd, faces(tm)) - { - if ( is_degenerate_triangle_face(fd, tm) ) - *out++=fd; - } - return out; + return degenerate_faces(tm, out, CGAL::parameters::all_default()); } // this function remove a border edge even if it does not satisfy the link condition. @@ -717,9 +727,8 @@ std::size_t remove_degenerate_faces(TriangleMesh& tmesh, // Then, remove triangles made of 3 collinear points std::set degenerate_face_set; - BOOST_FOREACH(face_descriptor fd, faces(tmesh)) - if ( is_degenerate_triangle_face(fd, tmesh, np)) - degenerate_face_set.insert(fd); + degenerate_faces(tmesh, std::inserter(degenerate_face_set, degenerate_face_set.begin()), np); + nb_deg_faces+=degenerate_face_set.size(); // first remove degree 3 vertices that are part of a cap @@ -1274,6 +1283,45 @@ std::size_t remove_degenerate_faces(TriangleMesh& tmesh) CGAL::Polygon_mesh_processing::parameters::all_default()); } +namespace internal { + +template +struct Vertex_collector +{ + typedef typename boost::graph_traits::vertex_descriptor vertex_descriptor; + + void collect_vertices(vertex_descriptor v1, vertex_descriptor v2) + { + std::vector& verts = collections[v1]; + if(verts.empty()) + verts.push_back(v1); + verts.push_back(v2); + } + + void dump(OutputIterator out) + { + typedef std::pair > Pair_type; + BOOST_FOREACH(const Pair_type& p, collections) { + *out++ = p.second; + } + } + + std::map > collections; +}; + +template +struct Vertex_collector +{ + typedef typename boost::graph_traits::vertex_descriptor vertex_descriptor; + void collect_vertices(vertex_descriptor, vertex_descriptor) + {} + + void dump(Emptyset_iterator) + {} +}; + +} // end namespace internal + /// \ingroup PMP_repairing_grp /// duplicates all non-manifold vertices of the input mesh. /// @@ -1284,22 +1332,22 @@ std::size_t remove_degenerate_faces(TriangleMesh& tmesh) /// @param np optional \ref pmp_namedparameters "Named Parameters" described below /// /// \cgalNamedParamsBegin -/// \cgalParamBegin{vertex_point_map} the property map with the points associated to the vertices of `pmesh`. The type of this map is model of `ReadWritePropertyMap`. +/// \cgalParamBegin{vertex_point_map} the property map with the points associated to the vertices of `pmesh`. +/// The type of this map is model of `ReadWritePropertyMap`. /// If this parameter is omitted, an internal property map for -/// `CGAL::vertex_point_t` should be available in `PolygonMesh` +/// `CGAL::vertex_point_t` should be available in `TriangleMesh` /// \cgalParamEnd /// \cgalParamBegin{vertex_is_constrained_map} a writable property map with `vertex_descriptor` /// as key and `bool` as `value_type`. `put(pmap, v, true)` will be called for each duplicated /// vertices and the input one. /// \cgalParamEnd -/// \cgalParamBegin{output_iterator} an output iterator where `std::vector` can be put. -/// The first vertex of the vector is an input vertex that was non-manifold, -/// the other vertices in the vertex are the new vertices created to fix -/// the non-manifoldness. +/// \cgalParamBegin{output_iterator} a model of `OutputIterator` with value type +/// `std::vector`. The first vertex of the vector is a non-manifold vertex +/// of the input mesh, followed by the new vertices that were created to fix the non-manifoldness. /// \cgalParamEnd /// \cgalNamedParamsEnd /// -/// \return the number of vertices created +/// \return the number of vertices created. template std::size_t duplicate_non_manifold_vertices(TriangleMesh& tm, const NamedParameters& np) @@ -1315,7 +1363,7 @@ std::size_t duplicate_non_manifold_vertices(TriangleMesh& tm, typedef typename GetVertexPointMap::type VertexPointMap; VertexPointMap vpm = choose_param(get_param(np, internal_np::vertex_point), - get_property_map(vertex_point, tm)); + get_property_map(vertex_point, tm)); typedef typename boost::lookup_named_param_def < internal_np::vertex_is_constrained_t, @@ -1333,37 +1381,46 @@ std::size_t duplicate_non_manifold_vertices(TriangleMesh& tm, > ::type Output_iterator; Output_iterator out = choose_param(get_param(np, internal_np::output_iterator), - Emptyset_iterator()); + Emptyset_iterator()); internal::Vertex_collector dmap; boost::unordered_set vertices_handled; boost::unordered_set halfedges_handled; - std::size_t nb_new_vertices=0; + std::size_t nb_new_vertices = 0; std::vector non_manifold_cones; BOOST_FOREACH(halfedge_descriptor h, halfedges(tm)) { - if (halfedges_handled.insert(h).second) + // If 'h' is not visited yet, we walk around the target of 'h' and mark these + // halfedges as visited. Thus, if we are here and the target is already marked as visited, + // it means that the vertex is non manifold. + if(halfedges_handled.insert(h).second) { vertex_descriptor vd = target(h, tm); - if ( !vertices_handled.insert(vd).second ) + if(!vertices_handled.insert(vd).second) { put(cmap, vd, true); // store the originals non_manifold_cones.push_back(h); } else + { set_halfedge(vd, h, tm); - halfedge_descriptor start=opposite(next(h, tm), tm); - h=start; - do{ + } + + halfedge_descriptor start = opposite(next(h, tm), tm); + h = start; + do + { halfedges_handled.insert(h); - h=opposite(next(h, tm), tm); - }while(h!=start); + h = opposite(next(h, tm), tm); + } + while(h != start); } } - if (!non_manifold_cones.empty()) { + if(!non_manifold_cones.empty()) + { BOOST_FOREACH(halfedge_descriptor h, non_manifold_cones) { halfedge_descriptor start = h; @@ -1373,13 +1430,16 @@ std::size_t duplicate_non_manifold_vertices(TriangleMesh& tm, dmap.collect_vertices(target(h, tm), new_vd); put(vpm, new_vd, get(vpm, target(h, tm))); set_halfedge(new_vd, h, tm); - do{ + do + { set_target(h, new_vd, tm); - h=opposite(next(h, tm), tm); - } while(h!=start); + h = opposite(next(h, tm), tm); + } + while(h != start); } dmap.dump(out); } + return nb_new_vertices; } diff --git a/Polygon_mesh_processing/test/Polygon_mesh_processing/data_degeneracies/caps_and_needles.off b/Polygon_mesh_processing/test/Polygon_mesh_processing/data_degeneracies/caps_and_needles.off new file mode 100644 index 00000000000..4e206746788 --- /dev/null +++ b/Polygon_mesh_processing/test/Polygon_mesh_processing/data_degeneracies/caps_and_needles.off @@ -0,0 +1,17 @@ +OFF +9 3 0 +0 0 0 +1 0 0 +1 1 0 +0 0 1 +1 0 1 +10 10 1 +0 0 2 +1 0 2 +-0.99619469809 0.08715574274 2 +3 0 1 2 +3 3 4 5 +3 6 7 8 + + + diff --git a/Polygon_mesh_processing/test/Polygon_mesh_processing/test_predicates.cpp b/Polygon_mesh_processing/test/Polygon_mesh_processing/test_predicates.cpp index 3bf2ffcd494..4dd421fe533 100644 --- a/Polygon_mesh_processing/test/Polygon_mesh_processing/test_predicates.cpp +++ b/Polygon_mesh_processing/test/Polygon_mesh_processing/test_predicates.cpp @@ -1,142 +1,248 @@ #include + #include -#include -#include + #include #include -typedef CGAL::Exact_predicates_inexact_constructions_kernel K; -typedef CGAL::Surface_mesh Surface_mesh; +#include + +#include +#include + +typedef CGAL::Exact_predicates_inexact_constructions_kernel K; +typedef K::FT FT; +typedef K::Point_3 Point_3; +typedef CGAL::Surface_mesh Surface_mesh; void check_edge_degeneracy(const char* fname) { - std::ifstream input(fname); + std::cout << "test edge degeneracy..."; + typedef typename boost::graph_traits::edge_descriptor edge_descriptor; + + std::ifstream input(fname); Surface_mesh mesh; if (!input || !(input >> mesh) || mesh.is_empty()) { std::cerr << fname << " is not a valid off file.\n"; - exit(1); + std::exit(1); } - typedef typename boost::graph_traits::edge_descriptor edge_descriptor; std::vector all_edges(edges(mesh).begin(), edges(mesh).end()); - CGAL_assertion(!CGAL::Polygon_mesh_processing::is_degenerate_edge(all_edges[0], mesh)); - CGAL_assertion(!CGAL::Polygon_mesh_processing::is_degenerate_edge(all_edges[1], mesh)); - CGAL_assertion(CGAL::Polygon_mesh_processing::is_degenerate_edge(all_edges[2], mesh)); + assert(!CGAL::Polygon_mesh_processing::is_degenerate_edge(all_edges[0], mesh)); + assert(!CGAL::Polygon_mesh_processing::is_degenerate_edge(all_edges[1], mesh)); + assert(CGAL::Polygon_mesh_processing::is_degenerate_edge(all_edges[2], mesh)); + std::cout << "done" << std::endl; } void check_triangle_face_degeneracy(const char* fname) { - std::ifstream input(fname); + std::cout << "test face degeneracy..."; + typedef typename boost::graph_traits::face_descriptor face_descriptor; + + std::ifstream input(fname); Surface_mesh mesh; if (!input || !(input >> mesh) || mesh.is_empty()) { std::cerr << fname << " is not a valid off file.\n"; - exit(1); + std::exit(1); } - typedef typename boost::graph_traits::face_descriptor face_descriptor; std::vector all_faces(faces(mesh).begin(), faces(mesh).end()); - CGAL_assertion(CGAL::Polygon_mesh_processing::is_degenerate_triangle_face(all_faces[0], mesh)); - CGAL_assertion(!CGAL::Polygon_mesh_processing::is_degenerate_triangle_face(all_faces[1], mesh)); - CGAL_assertion(!CGAL::Polygon_mesh_processing::is_degenerate_triangle_face(all_faces[2], mesh)); - CGAL_assertion(!CGAL::Polygon_mesh_processing::is_degenerate_triangle_face(all_faces[3], mesh)); + assert(CGAL::Polygon_mesh_processing::is_degenerate_triangle_face(all_faces[0], mesh)); + assert(!CGAL::Polygon_mesh_processing::is_degenerate_triangle_face(all_faces[1], mesh)); + assert(!CGAL::Polygon_mesh_processing::is_degenerate_triangle_face(all_faces[2], mesh)); + assert(!CGAL::Polygon_mesh_processing::is_degenerate_triangle_face(all_faces[3], mesh)); + + std::cout << "done" << std::endl; } -// tests repair.h -void test_vertices_merge_and_duplication(const char* fname) +// tests merge_and_duplication +template +void merge_identical_points(typename boost::graph_traits::vertex_descriptor v_keep, + typename boost::graph_traits::vertex_descriptor v_rm, + PolygonMesh& mesh) { - std::ifstream input(fname); - Surface_mesh mesh; - if (!input || !(input >> mesh) || mesh.is_empty()) { - std::cerr << fname << " is not a valid off file.\n"; - exit(1); + typedef typename boost::graph_traits::halfedge_descriptor halfedge_descriptor; + + halfedge_descriptor h = halfedge(v_rm, mesh); + halfedge_descriptor start = h; + + do + { + set_target(h, v_keep, mesh); + h = opposite(next(h, mesh), mesh); } - const std::size_t initial_vertices = vertices(mesh).size(); + while( h != start ); - // create non-manifold vertex - typedef typename boost::graph_traits::vertex_descriptor vertex_descriptor; - std::vector all_vertices(vertices(mesh).begin(), vertices(mesh).end()); - CGAL::Polygon_mesh_processing::internal::merge_identical_points(mesh, all_vertices[1], all_vertices[7]); - - const std::size_t vertices_after_merge = vertices(mesh).size(); - CGAL_assertion(vertices_after_merge == initial_vertices - 1); - - std::vector< std::vector > duplicated_vertices; - CGAL::Polygon_mesh_processing::duplicate_non_manifold_vertices(mesh, - CGAL::parameters::output_iterator(std::back_inserter(duplicated_vertices))); - const std::size_t final_vertices_size = vertices(mesh).size(); - CGAL_assertion(final_vertices_size == vertices_after_merge + 1); - CGAL_assertion(final_vertices_size == initial_vertices); - CGAL_assertion(duplicated_vertices.size() == 2); + remove_vertex(v_rm, mesh); } void test_vertex_non_manifoldness(const char* fname) { + std::cout << "test vertex non manifoldness..."; + + typedef typename boost::graph_traits::vertex_descriptor vertex_descriptor; + typedef typename boost::graph_traits::vertices_size_type size_type; + std::ifstream input(fname); Surface_mesh mesh; if (!input || !(input >> mesh) || mesh.is_empty()) { std::cerr << fname << " is not a valid off file.\n"; - exit(1); + std::exit(1); } + size_type ini_nv = num_vertices(mesh); + // create non-manifold vertex - typedef typename boost::graph_traits::vertex_descriptor vertex_descriptor; - std::vector all_vertices(vertices(mesh).begin(), vertices(mesh).end()); - CGAL::Polygon_mesh_processing::internal::merge_identical_points(mesh, all_vertices[1], all_vertices[7]); - std::vector vertices_with_non_manifold(vertices(mesh).begin(), vertices(mesh).end()); - CGAL_assertion(vertices_with_non_manifold.size() == all_vertices.size() - 1); + Surface_mesh::Vertex_index vertex_to_merge_onto(1); + Surface_mesh::Vertex_index vertex_to_merge(7); + merge_identical_points(vertex_to_merge_onto, vertex_to_merge, mesh); + mesh.collect_garbage(); - BOOST_FOREACH(std::size_t iv, vertices(mesh)) + assert(num_vertices(mesh) == ini_nv - 1); + + BOOST_FOREACH(vertex_descriptor v, vertices(mesh)) { - vertex_descriptor v = vertices_with_non_manifold[iv]; - if(iv == 1) - CGAL_assertion(CGAL::Polygon_mesh_processing::is_non_manifold_vertex(v, mesh)); + if(v == vertex_to_merge_onto) + assert(CGAL::Polygon_mesh_processing::is_non_manifold_vertex(v, mesh)); else - CGAL_assertion(!CGAL::Polygon_mesh_processing::is_non_manifold_vertex(v, mesh)); + assert(!CGAL::Polygon_mesh_processing::is_non_manifold_vertex(v, mesh)); } + + std::cout << "done" << std::endl; } -void test_needle(const char* fname) +void test_vertices_merge_and_duplication(const char* fname) { + std::cout << "test non manifold vertex duplication..."; + + typedef typename boost::graph_traits::vertex_descriptor vertex_descriptor; + std::ifstream input(fname); Surface_mesh mesh; if (!input || !(input >> mesh) || mesh.is_empty()) { std::cerr << fname << " is not a valid off file.\n"; - exit(1); + std::exit(1); } + const std::size_t initial_vertices = num_vertices(mesh); - const double threshold = 0.8; - BOOST_FOREACH(typename boost::graph_traits::face_descriptor f, faces(mesh)) - { - CGAL_assertion(CGAL::Polygon_mesh_processing::is_needle_triangle_face(f, mesh, threshold)); - } + // create non-manifold vertex + Surface_mesh::Vertex_index vertex_to_merge_onto(1); + Surface_mesh::Vertex_index vertex_to_merge(7); + Surface_mesh::Vertex_index vertex_to_merge_2(14); + Surface_mesh::Vertex_index vertex_to_merge_3(21); + + Surface_mesh::Vertex_index vertex_to_merge_onto_2(2); + Surface_mesh::Vertex_index vertex_to_merge_4(8); + + merge_identical_points(vertex_to_merge_onto, vertex_to_merge, mesh); + merge_identical_points(vertex_to_merge_onto, vertex_to_merge_2, mesh); + merge_identical_points(vertex_to_merge_onto, vertex_to_merge_3, mesh); + merge_identical_points(vertex_to_merge_onto_2, vertex_to_merge_4, mesh); + mesh.collect_garbage(); + + const std::size_t vertices_after_merge = num_vertices(mesh); + assert(vertices_after_merge == initial_vertices - 4); + + std::vector > duplicated_vertices; + CGAL::Polygon_mesh_processing::duplicate_non_manifold_vertices(mesh, + CGAL::parameters::output_iterator(std::back_inserter(duplicated_vertices))); + + const std::size_t final_vertices_size = vertices(mesh).size(); + assert(final_vertices_size == initial_vertices); + assert(duplicated_vertices.size() == 2); // two non-manifold vertex + assert(duplicated_vertices.front().size() == 4); + assert(duplicated_vertices.back().size() == 2); + + std::cout << "done" << std::endl; } -void test_cap(const char* fname) +void test_needles_and_caps(const char* fname) { + std::cout << "test needles&caps..."; + + namespace PMP = CGAL::Polygon_mesh_processing; + + typedef typename boost::graph_traits::halfedge_descriptor halfedge_descriptor; + typedef typename boost::graph_traits::face_iterator face_iterator; + typedef typename boost::graph_traits::face_descriptor face_descriptor; + std::ifstream input(fname); Surface_mesh mesh; if (!input || !(input >> mesh) || mesh.is_empty()) { std::cerr << fname << " is not a valid off file.\n"; - exit(1); + std::exit(1); } - const double threshold = -0.8; - BOOST_FOREACH(typename boost::graph_traits::face_descriptor f, faces(mesh)) - { - CGAL_assertion(CGAL::Polygon_mesh_processing::is_cap_triangle_face(f, mesh, threshold)); - } + const FT eps = std::numeric_limits::epsilon(); + + face_iterator fit, fend; + boost::tie(fit, fend) = faces(mesh); + + // (0 0 0) -- (1 0 0) -- (1 1 0) (90° cap angle) + face_descriptor f = *fit; + halfedge_descriptor res = PMP::is_needle_triangle_face(f, mesh, 2/*needle_threshold*/); + assert(res == boost::graph_traits::null_halfedge()); // not a needle + res = PMP::is_needle_triangle_face(f, mesh, CGAL::sqrt(FT(2) - eps)/*needle_threshold*/); + assert(res != boost::graph_traits::null_halfedge()); // is a needle + + res = PMP::is_cap_triangle_face(f, mesh, 0./*cos(pi/2)*/); + assert(mesh.point(target(res, mesh)) == CGAL::ORIGIN); + res = PMP::is_cap_triangle_face(f, mesh, std::cos(91 * CGAL_PI / 180)); + assert(res == boost::graph_traits::null_halfedge()); res = PMP::is_cap_triangle_face(f, mesh, std::cos(boost::math::constants::two_thirds_pi())); + assert(res == boost::graph_traits::null_halfedge()); + ++ fit; + + // (0 0 1) -- (1 0 1) -- (10 10 1) + f = *fit; + res = PMP::is_needle_triangle_face(f, mesh, 20); + assert(res == boost::graph_traits::null_halfedge()); + res = PMP::is_needle_triangle_face(f, mesh, 10 * CGAL::sqrt(FT(2) - eps)); + assert(mesh.point(target(res, mesh)) == Point_3(1,0,1)); + res = PMP::is_needle_triangle_face(f, mesh, 1); + assert(mesh.point(target(res, mesh)) == Point_3(1,0,1)); + + res = PMP::is_cap_triangle_face(f, mesh, 0./*cos(pi/2)*/); + assert(mesh.point(target(res, mesh)) == Point_3(0,0,1)); + res = PMP::is_cap_triangle_face(f, mesh, std::cos(boost::math::constants::two_thirds_pi())); + assert(mesh.point(target(res, mesh)) == Point_3(0,0,1)); + res = PMP::is_cap_triangle_face(f, mesh, std::cos(boost::math::constants::three_quarters_pi())); + assert(res == boost::graph_traits::null_halfedge()); + ++ fit; + + // (0 0 2) -- (1 0 2) -- (-0.99619469809 0.08715574274 2) (175° cap angle) + f = *fit; + res = PMP::is_needle_triangle_face(f, mesh, 2); + assert(res == boost::graph_traits::null_halfedge()); + res = PMP::is_needle_triangle_face(f, mesh, 1.9); + assert(mesh.point(target(res, mesh)) == Point_3(0,0,2) || + mesh.point(target(res, mesh)) == Point_3(1,0,2)); + res = PMP::is_needle_triangle_face(f, mesh, 1); + assert(mesh.point(target(res, mesh)) == Point_3(0,0,2) || + mesh.point(target(res, mesh)) == Point_3(1,0,2)); + + res = PMP::is_cap_triangle_face(f, mesh, 0./*cos(pi/2)*/); + assert(res != boost::graph_traits::null_halfedge() && + mesh.point(target(res, mesh)) != Point_3(0,0,2) && + mesh.point(target(res, mesh)) != Point_3(1,0,2)); + res = PMP::is_cap_triangle_face(f, mesh, std::cos(boost::math::constants::two_thirds_pi())); + assert(res != boost::graph_traits::null_halfedge()); + res = PMP::is_cap_triangle_face(f, mesh, std::cos(175 * CGAL_PI / 180)); + assert(res != boost::graph_traits::null_halfedge()); + res = PMP::is_cap_triangle_face(f, mesh, std::cos(176 * CGAL_PI / 180)); + assert(res == boost::graph_traits::null_halfedge()); + + std::cout << "done" << std::endl; } int main() { check_edge_degeneracy("data_degeneracies/degtri_edge.off"); check_triangle_face_degeneracy("data_degeneracies/degtri_four.off"); - test_vertices_merge_and_duplication("data_degeneracies/non_manifold_vertex_duplicated.off"); - test_vertex_non_manifoldness("data_degeneracies/non_manifold_vertex_duplicated.off"); - test_needle("data_degeneracies/needle.off"); - test_cap("data_degeneracies/cap.off"); + test_vertex_non_manifoldness("data/blobby.off"); + test_vertices_merge_and_duplication("data/blobby.off"); + test_needles_and_caps("data_degeneracies/caps_and_needles.off"); return 0; } diff --git a/Polyhedron/demo/Polyhedron/Plugins/PMP/Selection_plugin.cpp b/Polyhedron/demo/Polyhedron/Plugins/PMP/Selection_plugin.cpp index ac1bfd837d4..28d0d5f779a 100644 --- a/Polyhedron/demo/Polyhedron/Plugins/PMP/Selection_plugin.cpp +++ b/Polyhedron/demo/Polyhedron/Plugins/PMP/Selection_plugin.cpp @@ -742,12 +742,10 @@ public Q_SLOTS: //Edition mode case 1: { - VPmap vpmap = get(CGAL::vertex_point, *selection_item->polyhedron()); bool is_valid = true; BOOST_FOREACH(boost::graph_traits::face_descriptor fd, faces(*selection_item->polyhedron())) { - if (CGAL::Polygon_mesh_processing::is_degenerate_triangle_face(fd, - *selection_item->polyhedron())) + if (CGAL::Polygon_mesh_processing::is_degenerate_triangle_face(fd, *selection_item->polyhedron())) { is_valid = false; break; diff --git a/Polyhedron/demo/Polyhedron/Scene_polyhedron_item.cpp b/Polyhedron/demo/Polyhedron/Scene_polyhedron_item.cpp index 266f5331b94..a71037f40f7 100644 --- a/Polyhedron/demo/Polyhedron/Scene_polyhedron_item.cpp +++ b/Polyhedron/demo/Polyhedron/Scene_polyhedron_item.cpp @@ -1682,7 +1682,7 @@ QString Scene_polyhedron_item::computeStats(int type) if (d->poly->is_pure_triangle()) { if (d->number_of_degenerated_faces == (unsigned int)(-1)) - d->number_of_degenerated_faces = nb_degenerate_faces(d->poly, get(CGAL::vertex_point, *(d->poly))); + d->number_of_degenerated_faces = nb_degenerate_faces(d->poly); return QString::number(d->number_of_degenerated_faces); } else diff --git a/Polyhedron/demo/Polyhedron/Scene_polyhedron_selection_item.cpp b/Polyhedron/demo/Polyhedron/Scene_polyhedron_selection_item.cpp index 488e874d416..6696a5ac9bc 100644 --- a/Polyhedron/demo/Polyhedron/Scene_polyhedron_selection_item.cpp +++ b/Polyhedron/demo/Polyhedron/Scene_polyhedron_selection_item.cpp @@ -2032,8 +2032,9 @@ bool Scene_polyhedron_selection_item_priv::canAddFace(fg_halfedge_descriptor hc, found = true; fg_halfedge_descriptor res = CGAL::Euler::add_face_to_border(t,hc, *item->polyhedron()); + fg_face_descriptor resf = face(res, *item->polyhedron()); - if(CGAL::Polygon_mesh_processing::is_degenerate_triangle_face(res, *item->polyhedron())) + if(CGAL::Polygon_mesh_processing::is_degenerate_triangle_face(resf, *item->polyhedron())) { CGAL::Euler::remove_face(res, *item->polyhedron()); tempInstructions("Edge not selected : resulting facet is degenerated.", diff --git a/Polyhedron/demo/Polyhedron/Scene_surface_mesh_item.cpp b/Polyhedron/demo/Polyhedron/Scene_surface_mesh_item.cpp index 75e60907f61..b2b56ff87d9 100644 --- a/Polyhedron/demo/Polyhedron/Scene_surface_mesh_item.cpp +++ b/Polyhedron/demo/Polyhedron/Scene_surface_mesh_item.cpp @@ -1502,7 +1502,7 @@ QString Scene_surface_mesh_item::computeStats(int type) if(is_triangle_mesh(*d->smesh_)) { if (d->number_of_degenerated_faces == (unsigned int)(-1)) - d->number_of_degenerated_faces = nb_degenerate_faces(d->smesh_, get(CGAL::vertex_point, *(d->smesh_))); + d->number_of_degenerated_faces = nb_degenerate_faces(d->smesh_); return QString::number(d->number_of_degenerated_faces); } else diff --git a/Polyhedron/demo/Polyhedron/include/CGAL/statistics_helpers.h b/Polyhedron/demo/Polyhedron/include/CGAL/statistics_helpers.h index 711a641dcc3..e65bdf7329b 100644 --- a/Polyhedron/demo/Polyhedron/include/CGAL/statistics_helpers.h +++ b/Polyhedron/demo/Polyhedron/include/CGAL/statistics_helpers.h @@ -1,8 +1,6 @@ #ifndef POLYHEDRON_DEMO_STATISTICS_HELPERS_H #define POLYHEDRON_DEMO_STATISTICS_HELPERS_H -#include - #include #include #include @@ -10,12 +8,15 @@ #include #include #include -#include #include #include -#include +#include +#include +#include +#include +#include template void angles(Mesh* poly, double& mini, double& maxi, double& ave) @@ -84,19 +85,15 @@ void edges_length(Mesh* poly, mid = extract_result< tag::median >(acc); } -template -unsigned int nb_degenerate_faces(Mesh* poly, VPmap vpmap) +template +unsigned int nb_degenerate_faces(Mesh* poly) { typedef typename boost::graph_traits::face_descriptor face_descriptor; - typedef typename CGAL::Kernel_traits< typename boost::property_traits::value_type >::Kernel Traits; - unsigned int nb = 0; - BOOST_FOREACH(face_descriptor f, faces(*poly)) - { - if (CGAL::Polygon_mesh_processing::is_degenerate_triangle_face(f, *poly)) - ++nb; - } - return nb; + std::vector degenerate_faces; + CGAL::Polygon_mesh_processing::degenerate_faces(*poly, std::back_inserter(degenerate_faces)); + + return static_cast(degenerate_faces.size()); } template