diff --git a/BGL/include/CGAL/boost/graph/Euler_operations.h b/BGL/include/CGAL/boost/graph/Euler_operations.h index 2472d30f220..109de182736 100644 --- a/BGL/include/CGAL/boost/graph/Euler_operations.h +++ b/BGL/include/CGAL/boost/graph/Euler_operations.h @@ -1020,40 +1020,33 @@ add_face_to_border(typename boost::graph_traits::halfedge_descriptor h1, * collapses an edge in a graph. * * \tparam Graph must be a model of `MutableFaceGraph` - * Let `v0` and `v1` be the source and target vertices, and let `e` and `e'` be the halfedges of edge `v0v1`. + * Let `h` be the halfedge of `e`, and let `v0` and `v1` be the source and target vertices of `h`. + * Let `p_h` and `p_o_h` be respectively the edges of `prev(h,g)` and `prev(opposite(h, g), g)`. + * Let `o_n_h` and `o_n_o_h` be respectively the edges of `opposite(next(h,g))` and `opposite(next(opposite(h, g), g))`. * - * For `e`, let `en` and `ep` be the next and previous - * halfedges, that is `en = next(e, g)`, `ep = prev(e, g)`, and let - * `eno` and `epo` be their opposite halfedges, that is - * `eno = opposite(en, g)` and `epo = opposite(ep, g)`. - * Analoguously, for `e'` define `en'`, `ep'`, `eno'`, and `epo'`. + * After the collapse of edge `e` the following holds: + * - The edge `e` is no longer in `g`. + * - The faces incident to edge `e` are no longer in `g`. + * - `v0` is no longer in `g`. + * - If `h` is not a border halfedge, `p_h` is no longer in `g` and is replaced by `o_n_h`. + * - If the opposite of `h` is not a border halfedge, `p_o_h` is no longer in `g` and is replaced by `o_n_o_h`. + * - The halfedges kept in `g` that had `v0` as target and source now have `v1` as target and source, respectively. + * - No other incidence information is changed in `g`. * - * Then, after the collapse of edge `v0v1` the following holds for `e` (and analoguously for `e'`) - * - * - * \returns vertex `vkept` (which can be either `v0` or `v1`). + * \returns vertex `v1`. * \pre g must be a triangulated graph - * \pre `does_satisfy_link_condition(v0v1,g) == true`. + * \pre `does_satisfy_link_condition(e,g) == true`. */ template typename boost::graph_traits::vertex_descriptor -collapse_edge(typename boost::graph_traits::edge_descriptor v0v1, +collapse_edge(typename boost::graph_traits::edge_descriptor e, Graph& g) { typedef boost::graph_traits< Graph > Traits; typedef typename Traits::vertex_descriptor vertex_descriptor; typedef typename Traits::halfedge_descriptor halfedge_descriptor; - halfedge_descriptor pq = halfedge(v0v1,g); + halfedge_descriptor pq = halfedge(e,g); halfedge_descriptor qp = opposite(pq, g); halfedge_descriptor pt = opposite(prev(pq, g), g); halfedge_descriptor qb = opposite(prev(qp, g), g); @@ -1068,51 +1061,9 @@ collapse_edge(typename boost::graph_traits::edge_descriptor v0v1, vertex_descriptor q = target(pq, g); vertex_descriptor p = source(pq, g); -#if 0 - if(lTopLeftFaceExists && lBottomRightFaceExists){ - std::cerr << " // do it low level" << std::endl; - halfedge_descriptor qt = next(pq,g); - halfedge_descriptor pb = next(qp,g); - halfedge_descriptor ppt = prev(pt,g); - halfedge_descriptor pqb = prev(qb,g); - if(halfedge(q,g) == pq){ - set_halfedge(q, pqb,g); - } - vertex_descriptor t = target(qt,g); - if(halfedge(t,g) == pt){ - set_halfedge(t, qt,g); - } - vertex_descriptor b = target(pb,g); - if(halfedge(b,g) == qb){ - set_halfedge(t, pb,g); - } - set_face(qt, face(pt,g),g); - set_halfedge(face(qt,g),qt,g); - set_face(pb, face(qb,g),g); - set_halfedge(face(pb,g),pb,g); - set_next(qt, next(pt,g),g); - set_next(pb, next(qb,g),g); - set_next(ppt, qt,g); - set_next(pqb,pb,g); - remove_face(face(pq,g),g); - remove_face(face(qp,g),g); - remove_edge(v0v1,g); - remove_edge(edge(pt,g),g); - remove_edge(edge(qb,g),g); - remove_vertex(p,g); - Halfedge_around_target_circulator beg(ppt,g), end(pqb,g); - while(beg != end){ - CGAL_assertion(target(*beg,g) == p); - set_target(*beg,q,g); - --beg; - } - return q; - // return the vertex kept - } -#endif - bool lP_Erased = false, lQ_Erased = false ; + bool lP_Erased = false; if ( lTopFaceExists ) { @@ -1137,7 +1088,7 @@ collapse_edge(typename boost::graph_traits::edge_descriptor v0v1, //CGAL_ECMS_TRACE(3, "Bottom face doesn't exist so vertex P already removed" ) ; lP_Erased = true ; - } + } } } @@ -1158,19 +1109,20 @@ collapse_edge(typename boost::graph_traits::edge_descriptor v0v1, // << q.idx() << "->V" << target(qb, g).idx() // << ") by erasing bottom face" ) ; - remove_face(opposite(qb, g),g); - if ( !lTopFaceExists ) { //CGAL_ECMS_TRACE(3, "Top face doesn't exist so vertex Q already removed" ) ; - lQ_Erased = true ; - } + lP_Erased = true ; + + // q will be removed, swap p and q + internal::swap_vertices(p, q, g); + } + + remove_face(opposite(qb, g),g); } } - CGAL_assertion( !lP_Erased || !lQ_Erased ) ; - - if ( !lP_Erased && !lQ_Erased ) + if ( !lP_Erased ) { //CGAL_ECMS_TRACE(3, "Removing vertex P by joining pQ" ) ; @@ -1180,19 +1132,22 @@ collapse_edge(typename boost::graph_traits::edge_descriptor v0v1, CGAL_expensive_assertion(is_valid_polygon_mesh(g)); - return lP_Erased ? q : p ; + return q; } /** - * Collapses the edge `v0v1` replacing it with v0 or v1, as described in the paragraph above + * collapses an edge in a graph having non-collapsable edges. + * + * Let `h` be the halfedge of `e`, and let `v0` and `v1` be the source and target vertices of `h`. + * Collapses the edge `e` replacing it with `v1`, as described in the paragraph above * and guarantees that an edge `e2`, for which `get(edge_is_constrained_map, e2)==true`, * is not removed after the collapse. * - * * \tparam Graph must be a model of `MutableFaceGraph` * \tparam EdgeIsConstrainedMap mut be a model of `ReadablePropertyMap` with the edge descriptor of `Graph` * as key type and a Boolean as value type. It indicates if an edge is constrained or not. * + * \returns vertex `v1`. * \pre This function requires `g` to be an oriented 2-manifold with or without boundaries. * Furthermore, the edge `v0v1` must satisfy the link condition, which guarantees that the surface mesh is also 2-manifold after the edge collapse. * \pre `get(edge_is_constrained_map, v0v1)==false`. @@ -1262,14 +1217,16 @@ collapse_edge(typename boost::graph_traits::edge_descriptor v0v1, { // the vertex is of valence 3 and we simply need to remove the vertex // and its indicent edges - bool lP_Erased = false; halfedge_descriptor edge = next(edges_to_erase[0],g) == edges_to_erase[1]? edges_to_erase[0]:edges_to_erase[1]; - if (target(edge,g) == p) - lP_Erased = true; + if (target(edge,g) != p) + { + // q will be removed, swap it with p + internal::swap_vertices(p, q, g); + } remove_center_vertex(edge,g); - return lP_Erased? q : p; + return q; } else { @@ -1294,19 +1251,29 @@ collapse_edge(typename boost::graph_traits::edge_descriptor v0v1, join_vertex(pq,g); return q; } - bool lQ_Erased = is_border(opposite(next(pq,g),g),g); + if( is_border(opposite(next(pq,g),g),g) ) + { + // q will be removed, swap it with p + internal::swap_vertices(p, q, g); + } remove_face(opposite(edges_to_erase[0],g),g); - return lQ_Erased?p:q; + return q; } if (! (is_border(edges_to_erase[0],g))){ + // q will be removed, swap it with p + internal::swap_vertices(p, q, g); join_face(edges_to_erase[0],g); join_vertex(qp,g); - return p; + return q; + } + if(!is_border(opposite(next(qp,g),g),g)) + { + // q will be removed, swap it with p + internal::swap_vertices(p, q, g); } - bool lP_Erased= is_border(opposite(next(qp,g),g),g); remove_face(opposite(edges_to_erase[0],g),g); - return lP_Erased?q:p; + return q; }; } diff --git a/BGL/include/CGAL/boost/graph/helpers.h b/BGL/include/CGAL/boost/graph/helpers.h index 5e2c03e95e9..f34e24b7687 100644 --- a/BGL/include/CGAL/boost/graph/helpers.h +++ b/BGL/include/CGAL/boost/graph/helpers.h @@ -1397,6 +1397,101 @@ clear_impl(FaceGraph& g) } } +template +void swap_vertices( + typename boost::graph_traits::vertex_descriptor& p, + typename boost::graph_traits::vertex_descriptor& q, + FaceGraph& g) +{ + typedef typename boost::graph_traits::halfedge_descriptor halfedge_descriptor; + + halfedge_descriptor hq=halfedge(q, g); + halfedge_descriptor hp=halfedge(p, g); + BOOST_FOREACH(halfedge_descriptor h, halfedges_around_target(hq, g)) + set_target(h, p, g); + BOOST_FOREACH(halfedge_descriptor h, halfedges_around_target(hp, g)) + set_target(h, q, g); + set_halfedge(p, hq, g); + set_halfedge(q, hp, g); +} + +template +void swap_edges( + const typename boost::graph_traits::halfedge_descriptor& h1, + const typename boost::graph_traits::halfedge_descriptor& h2, + FaceGraph& g) +{ + typedef typename boost::graph_traits::halfedge_descriptor halfedge_descriptor; + typedef typename boost::graph_traits::face_descriptor face_descriptor; + typedef typename boost::graph_traits::vertex_descriptor vertex_descriptor; + const halfedge_descriptor oh1 = opposite(h1, g), oh2 = opposite(h2, g); + + // backup vertex pointers + vertex_descriptor s1 = target(oh1, g), s2 = target(oh2, g); + vertex_descriptor t1 = target(h1, g), t2 = target(h2, g); + + // backup face pointers + face_descriptor f1 = face(h1, g), f2 = face(h2, g); + face_descriptor fo1 = face(oh1, g), fo2 = face(oh2, g); + + // backup next prev pointers + halfedge_descriptor nh1 = next(h1, g), nh2 = next(h2, g); + halfedge_descriptor ph1 = prev(h1, g), ph2 = prev(h2, g); + halfedge_descriptor noh1 = next(oh1, g), noh2 = next(oh2, g); + halfedge_descriptor poh1 = prev(oh1, g), poh2 = prev(oh2, g); + + // handle particular cases where next/prev are halfedges to be swapt + if (nh1 == oh2) nh1 = oh1; + if (nh1 == h2) nh1 = h1; + if (nh2 == oh1) nh2 = oh2; + if (nh2 == h1) nh2 = h2; + if (ph1 == oh2) ph1 = oh1; + if (ph1 == h2) ph1 = h1; + if (ph2 == oh1) ph2 = oh2; + if (ph2 == h1) ph2 = h2; + if (noh1 == oh2) noh1 = oh1; + if (noh1 == h2) noh1 = h1; + if (noh2 == oh1) noh2 = oh2; + if (noh2 == h1) noh2 = h2; + if (poh1 == oh2) poh1 = oh1; + if (poh1 == h2) poh1 = h1; + if (poh2 == oh1) poh2 = oh2; + if (poh2 == h1) poh2 = h2; + + // (1) exchange next pointers + set_next(h1, nh2, g); + set_next(h2, nh1, g); + set_next(ph1, h2, g); + set_next(ph2, h1, g); + set_next(oh1, noh2, g); + set_next(oh2, noh1, g); + set_next(poh1, oh2, g); + set_next(poh2, oh1, g); + + // (2) exchange vertex-halfedge pointers + set_target(h1, t2, g); + set_target(h2, t1, g); + set_target(oh1, s2, g); + set_target(oh2, s1, g); + if (halfedge(t1, g)==h1) set_halfedge(t1, h2, g); + if (halfedge(t2, g)==h2) set_halfedge(t2, h1, g); + if (halfedge(s1, g)==oh1) set_halfedge(s1, oh2, g); + if (halfedge(s2, g)==oh2) set_halfedge(s2, oh1, g); + + // (3) exchange face-halfedge pointers + set_face(h1, f2, g); + set_face(h2, f1, g); + set_face(oh1, fo2, g); + set_face(oh2, fo1, g); + + face_descriptor nf = boost::graph_traits::null_face(); + if (f1 != nf && halfedge(f1, g)==h1) set_halfedge(f1, h2, g); + if (f2 != nf && halfedge(f2, g)==h2) set_halfedge(f2, h1, g); + if (fo1 != nf && halfedge(fo1, g)==oh1) set_halfedge(fo1, oh2, g); + if (fo2 != nf && halfedge(fo2, g)==oh2) set_halfedge(fo2, oh1, g); +} + + } //end of internal namespace /** diff --git a/BGL/test/BGL/CMakeLists.txt b/BGL/test/BGL/CMakeLists.txt index e3938cdd9c6..74efdb21fc3 100644 --- a/BGL/test/BGL/CMakeLists.txt +++ b/BGL/test/BGL/CMakeLists.txt @@ -98,6 +98,8 @@ create_single_source_cgal_program( "test_Face_filtered_graph.cpp" ) create_single_source_cgal_program( "test_Euler_operations.cpp" ) +create_single_source_cgal_program( "test_Collapse_edge.cpp" ) + create_single_source_cgal_program( "test_graph_traits.cpp" ) create_single_source_cgal_program( "test_Properties.cpp" ) @@ -105,6 +107,7 @@ create_single_source_cgal_program( "test_Properties.cpp" ) if(OpenMesh_FOUND) target_link_libraries( test_clear PRIVATE ${OPENMESH_LIBRARIES}) target_link_libraries( test_Euler_operations PRIVATE ${OPENMESH_LIBRARIES}) + target_link_libraries( test_Collapse_edge PRIVATE ${OPENMESH_LIBRARIES}) target_link_libraries( test_Face_filtered_graph PRIVATE ${OPENMESH_LIBRARIES}) target_link_libraries( test_graph_traits PRIVATE ${OPENMESH_LIBRARIES} ) target_link_libraries( test_Properties PRIVATE ${OPENMESH_LIBRARIES}) diff --git a/BGL/test/BGL/data/flat_hexahedron.off b/BGL/test/BGL/data/flat_hexahedron.off new file mode 100644 index 00000000000..b7c3562356d --- /dev/null +++ b/BGL/test/BGL/data/flat_hexahedron.off @@ -0,0 +1,24 @@ +OFF +10 10 0 +-1.5 0 0 +-0.5 0 0 +0.5 0 0 +1.5 0 0 +-0.75 -0.5 0 +0 -0.5 0 +0.75 -0.5 0 +-0.75 0.5 0 +0 0.5 0 +0.75 0.5 0 + +3 0 1 7 +3 7 1 8 +3 8 1 2 +3 2 9 8 +3 9 2 3 + +3 0 4 1 +3 1 4 5 +3 1 5 2 +3 2 5 6 +3 2 6 3 diff --git a/BGL/test/BGL/test_Collapse_edge.cpp b/BGL/test/BGL/test_Collapse_edge.cpp new file mode 100644 index 00000000000..d1f9e0e22d8 --- /dev/null +++ b/BGL/test/BGL/test_Collapse_edge.cpp @@ -0,0 +1,226 @@ +#include "test_Prefix.h" +#include +#include + +template < typename Mesh> +typename boost::graph_traits:: +halfedge_descriptor find_halfedge(double x1, double y1, + double x2, double y2, + Mesh& m, + bool is_border = false) +{ + typedef typename boost::property_map::type VPMAP; + typedef typename boost::property_traits::value_type Point; + + typedef typename boost::graph_traits::halfedge_descriptor halfedge_descriptor; + VPMAP vpmap = get(CGAL::vertex_point, m); + BOOST_FOREACH(halfedge_descriptor h, halfedges(m)) + { + if(get(vpmap, source(h, m)) == Point(x1,y1,0) + && get(vpmap, target(h, m)) == Point(x2,y2,0)) + { + if(is_border == CGAL::is_border(h, m)) + return h; + else + return opposite(h, m); + } + } + return boost::graph_traits::null_halfedge(); +} + +template +void +collapse_edge_test() +{ + CGAL_GRAPH_TRAITS_MEMBERS(Mesh); + typedef typename boost::graph_traits:: vertex_descriptor vertex_descriptor; + typedef typename boost::graph_traits:: halfedge_descriptor halfedge_descriptor; + + const std::string fname = "data/flat_hexahedron.off"; + Mesh m; + if(!CGAL::read_off(fname, m)) { + std::cout << "Error reading file: " << fname << std::endl; + } + bool m_is_valid = CGAL::is_valid(m); + assert(m_is_valid); + + Mesh test_mesh; + CGAL::copy_face_graph(m, test_mesh); + m_is_valid = CGAL::is_valid(m); + assert(m_is_valid); + + //case 1: General Case. + { + halfedge_descriptor he = find_halfedge(-0.5,0, + 0.5,0, + test_mesh); + halfedge_descriptor en = next(he, test_mesh); + halfedge_descriptor eno = opposite(en, test_mesh); + halfedge_descriptor eno_prime = opposite(next(opposite(he, test_mesh), test_mesh), test_mesh); + vertex_descriptor v1 = target(he, test_mesh); + bool ok = CGAL::Euler::collapse_edge(edge(he, test_mesh), test_mesh) == v1; + assert(ok); + char found = 0; + BOOST_FOREACH(halfedge_descriptor it, CGAL::halfedges_around_target(v1,test_mesh)) + { + if(it == eno + || it == eno_prime){ + ++found; + } + } + assert(found == 2); + CGAL::clear(test_mesh); + + } + //case 2: collapsing edge is not itself a border, but is incident upon a border edge that is removed. + { + CGAL::copy_face_graph(m, test_mesh); + halfedge_descriptor he = find_halfedge(0,0.5, + -0.75,0.5, + test_mesh); + CGAL::Euler::remove_face(he, test_mesh); + + he = find_halfedge(-0.5,0, + 0.5,0, + test_mesh); + halfedge_descriptor en = next(he, test_mesh); + halfedge_descriptor eno = opposite(en, test_mesh); + halfedge_descriptor eno_prime = opposite(next(opposite(he, test_mesh), test_mesh), test_mesh); + vertex_descriptor v1 = target(he, test_mesh); + bool ok = CGAL::Euler::collapse_edge(edge(he, test_mesh), test_mesh) == v1; + assert(ok); + char found = 0; + BOOST_FOREACH(halfedge_descriptor it, CGAL::halfedges_around_target(v1,test_mesh)) + { + if(it == eno + || it == eno_prime){ + ++found; + } + } + assert(found == 2); + CGAL::clear(test_mesh); + } + //case 3: collapsing edge is not itself a border, but is incident upon a border edge that is not removed + { + CGAL::copy_face_graph(m, test_mesh); + halfedge_descriptor he = find_halfedge(1.5,0, + 0.75,0.5, + test_mesh); + CGAL::Euler::remove_face(he, test_mesh); + + he = find_halfedge(-0.5,0, + 0.5,0, + test_mesh); + halfedge_descriptor en = next(he, test_mesh); + halfedge_descriptor eno = opposite(en, test_mesh); + halfedge_descriptor eno_prime = opposite(next(opposite(he, test_mesh), test_mesh), test_mesh); + vertex_descriptor v1 = target(he, test_mesh); + bool ok = CGAL::Euler::collapse_edge(edge(he, test_mesh), test_mesh) == v1; + assert(ok); + char found = 0; + BOOST_FOREACH(halfedge_descriptor it, CGAL::halfedges_around_target(v1,test_mesh)) + { + if(it == eno + || it == eno_prime){ + ++found; + } + } + assert(found == 2); + CGAL::clear(test_mesh); + } + //case 4: collapsing edge is itself a border + { + CGAL::copy_face_graph(m, test_mesh); + halfedge_descriptor he = find_halfedge(-0.5, 0, + 0, -0.5, + test_mesh); + CGAL::Euler::remove_face(he, test_mesh); + he = find_halfedge(0, -0.5, + -0.5, 0, + test_mesh); + CGAL::Euler::remove_face(he, test_mesh); + he = find_halfedge(0, -0.5, + 0.75, -0.5, + test_mesh); + CGAL::Euler::remove_face(he, test_mesh); + + + he = find_halfedge(-0.5,0, + 0.5,0, + test_mesh); + halfedge_descriptor en = next(he, test_mesh); + halfedge_descriptor eno = opposite(en, test_mesh); + halfedge_descriptor ep_prime = prev(opposite(he, test_mesh), test_mesh); + halfedge_descriptor eno_prime = opposite(next(opposite(he, test_mesh), test_mesh), test_mesh); + vertex_descriptor v1 = target(he, test_mesh); + bool ok = CGAL::Euler::collapse_edge(edge(he, test_mesh), test_mesh) == v1; + assert(ok); + char found = 0; + BOOST_FOREACH(halfedge_descriptor it, CGAL::halfedges_around_target(v1,test_mesh)) + { + if(it == eno + || it == eno_prime + || it == ep_prime){ + ++found; + } + } + assert(found == 3); + CGAL::clear(test_mesh); + } + //case 5 singular case. + { + CGAL::copy_face_graph(m, test_mesh); + halfedge_descriptor he = find_halfedge(0.75,0.5, + 1.5,0, + test_mesh); + CGAL::Euler::remove_face(he, test_mesh); + he = find_halfedge(0.75,-0.5, + 1.5,0, + test_mesh); + CGAL::Euler::remove_face(he, test_mesh); + he = find_halfedge(0,0.5, + 0.5,0, + test_mesh); + CGAL::Euler::remove_face(he, test_mesh); + he = find_halfedge(0.5,0, + 0,-0.5, + test_mesh); + CGAL::Euler::remove_face(he, test_mesh); + + he = find_halfedge(-0.5,0, + 0.5,0, + test_mesh); + CGAL::Euler::remove_face(he, test_mesh); + halfedge_descriptor ep = prev(he, test_mesh); + halfedge_descriptor eno_prime = opposite(next(opposite(he, test_mesh), test_mesh), test_mesh); + vertex_descriptor v1 = target(he, test_mesh); + bool ok = CGAL::Euler::collapse_edge(edge(he, test_mesh), test_mesh) == v1; + assert(ok); + char found = 0; + BOOST_FOREACH(halfedge_descriptor it, CGAL::halfedges_around_target(v1,test_mesh)) + { + if(it == ep) + ++found; + else if( it == eno_prime){ + ++found; + } + } + assert(found == 2); + CGAL::clear(test_mesh); + } +} + + +int main() +{ + + collapse_edge_test(); + collapse_edge_test(); + +#ifdef CGAL_USE_OPENMESH + collapse_edge_test(); +#endif + + std::cerr << "done\n"; + return 0; +} diff --git a/BGL/test/BGL/test_Euler_operations.cpp b/BGL/test/BGL/test_Euler_operations.cpp index cdf53ec5f4c..3ae73d6c38d 100644 --- a/BGL/test/BGL/test_Euler_operations.cpp +++ b/BGL/test/BGL/test_Euler_operations.cpp @@ -380,7 +380,27 @@ does_satisfy_link_condition() assert(CGAL::Euler::does_satisfy_link_condition(*edges(f.m).first,f.m)); } - +template +void +test_swap_edges() +{ + typedef typename boost::graph_traits::halfedge_descriptor halfedge_descriptor; + std::size_t nbh=12; + Kernel::Point_3 pt(0,0,0); + // test all possible pairs of halfedges + for (std::size_t i=0; i void @@ -400,6 +420,7 @@ test_Euler_operations() remove_center_vertex_test(); join_split_inverse(); does_satisfy_link_condition(); + test_swap_edges(); } int main() diff --git a/Installation/CHANGES.md b/Installation/CHANGES.md index 99da627eda0..aaca52d651b 100644 --- a/Installation/CHANGES.md +++ b/Installation/CHANGES.md @@ -43,6 +43,10 @@ Release date: September 2018 - Added a function to apply a transformation to a mesh: - `CGAL::Polygon_mesh_processing::transform()` +- Fix a bug in `isotropic_remeshing()` making constrained vertices missing in the output +- Guarantee that constrained vertices are kept in the mesh after calling `isotropic_remeshing()` + (and not only the points associated to constrained vertices as it was before). + ### 3D Mesh Generation - **Breaking change:** The template parameters of the class template @@ -71,6 +75,10 @@ Release date: September 2018 - Add helper function `CGAL::is_valid_polygon_mesh` that checks the validity of a polygon mesh using BGL functions. +- Improve the function `CGAL::Euler::collapse_edge` so that the target + vertex of the collapsed edge is always kept after the collapse. + + Release 4.12 ------------ 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 97a400388ce..acf556b71e3 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 @@ -331,8 +331,6 @@ namespace internal { { tag_halfedges_status(face_range); //called first - constrain_patch_corners(face_range); - BOOST_FOREACH(face_descriptor f, face_range) { if (is_degenerate_triangle_face(halfedge(f,mesh_),mesh_,vpmap_,GeomTraits())){ @@ -663,29 +661,30 @@ namespace internal { vertex_descriptor va = source(he, mesh_); vertex_descriptor vb = target(he, mesh_); - bool swap_done = false; - if (is_on_patch_border(va) && !is_on_patch_border(vb)) + bool is_va_constrained = is_constrained(va) || is_on_patch_border(va); + bool is_vb_constrained = is_constrained(vb) || is_on_patch_border(vb); + + + // do not collapse edge with two constrained vertices + if (is_va_constrained && is_vb_constrained) continue; + + bool can_swap = !is_vb_constrained; + if (is_va_constrained) { he = opposite(he, mesh_); - va = source(he, mesh_); - vb = target(he, mesh_); - swap_done = true; - CGAL_assertion(is_on_patch_border(vb) && !is_on_patch_border(va)); + e=edge(he, mesh_); + std::swap(va, vb); + can_swap=false; } if(!collapse_does_not_invert_face(he)) { - if (!swap_done//if swap has already been done, don't re-swap - && collapse_does_not_invert_face(opposite(he, mesh_))) + if (can_swap//if swap allowed (no constrained vertices) + && collapse_does_not_invert_face(opposite(he, mesh_))) { he = opposite(he, mesh_); - va = source(he, mesh_); - vb = target(he, mesh_); - - if (is_on_patch_border(va) && !is_on_patch_border(vb)) - continue;//we cannot swap again. It would lead to a face inversion - else if (is_corner(va) && !is_corner(vb)) - continue;//idem + e=edge(he, mesh_); + std::swap(va, vb); } else continue;//both directions invert a face @@ -693,9 +692,7 @@ namespace internal { CGAL_assertion(collapse_does_not_invert_face(he)); CGAL_assertion(is_collapse_allowed(e)); - if (degree(va, mesh_) < 3 - || degree(vb, mesh_) < 3 - || !CGAL::Euler::does_satisfy_link_condition(e, mesh_))//necessary to collapse + if (!CGAL::Euler::does_satisfy_link_condition(e, mesh_))//necessary to collapse continue; //check that collapse would not create an edge with length > high @@ -710,9 +707,9 @@ namespace internal { break; } } - //before collapsing va into vb, check that it does not break a corner - //if it is allowed, perform the collapse - if (collapse_ok && !is_constrained(va) && !is_corner(va)) + // before collapsing va into vb, check that it does not break a corner + // or a constrained vertex + if (collapse_ok) { //"collapse va into vb along e" // remove edges incident to va and vb, because their lengths will change @@ -728,12 +725,13 @@ namespace internal { } //before collapse + halfedge_descriptor he_opp= opposite(he, mesh_); bool mesh_border_case = is_on_border(he); - bool mesh_border_case_opp = is_on_border(opposite(he, mesh_)); - halfedge_descriptor ep_p = prev(opposite(he, mesh_), mesh_); + bool mesh_border_case_opp = is_on_border(he_opp); + halfedge_descriptor ep_p = prev(he_opp, mesh_); halfedge_descriptor epo_p = opposite(ep_p, mesh_); halfedge_descriptor en = next(he, mesh_); - halfedge_descriptor en_p = next(opposite(he, mesh_), mesh_); + halfedge_descriptor en_p = next(he_opp, mesh_); Halfedge_status s_ep_p = status(ep_p); Halfedge_status s_epo_p = status(epo_p); Halfedge_status s_ep = status(prev(he, mesh_)); @@ -743,34 +741,33 @@ namespace internal { //do it before collapse is performed to be sure everything is valid if (!mesh_border_case) merge_status(en, s_epo, s_ep); - if (!mesh_border_case_opp) + if (!mesh_border_case_opp && s_ep_p!=PATCH_BORDER) merge_status(en_p, s_epo_p, s_ep_p); halfedge_and_opp_removed(he); if (!mesh_border_case) halfedge_and_opp_removed(prev(he, mesh_)); if (!mesh_border_case_opp) - halfedge_and_opp_removed(prev(opposite(he, mesh_), mesh_)); - - //constrained case - bool constrained_case = is_constrained(va) || is_constrained(vb); - if (constrained_case) { - CGAL_assertion(is_constrained(va) ^ is_constrained(vb));//XOR - set_constrained(va, false); - set_constrained(vb, false); + if (s_ep_p!=PATCH_BORDER) + halfedge_and_opp_removed(prev(he_opp, mesh_)); + else{ + // swap edges so as to keep constained edges: + // replace en_p by epo_p and ep_p by eno_p + ::CGAL::internal::swap_edges(en_p, epo_p, mesh_); + CGAL_assertion(is_valid(mesh_)); + } } //perform collapse - Point target_point = get(vpmap_, vb); + CGAL_assertion(target(halfedge(e, mesh_), mesh_) == vb); vertex_descriptor vkept = CGAL::Euler::collapse_edge(e, mesh_); - put(vpmap_, vkept, target_point); + CGAL_assertion(is_valid(mesh_)); + CGAL_assertion(vkept == vb);//is the constrained point still here ++nb_collapses; //fix constrained case - if (constrained_case)//we have made sure that collapse goes to constrained vertex - set_constrained(vkept, true); - + CGAL_assertion((is_constrained(vkept) || is_on_patch_border(vb)) == (is_va_constrained || is_vb_constrained)); fix_degenerate_faces(vkept, short_edges, sq_low); #ifdef CGAL_PMP_REMESHING_DEBUG @@ -944,10 +941,10 @@ 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())); - put(propmap_normals, v, vn); + Vector_3 vn = PMP::compute_vertex_normal(v, mesh_ + , PMP::parameters::vertex_point_map(vpmap_) + .geom_traits(GeomTraits())); + put(propmap_normals, v, vn); Vector_3 move = CGAL::NULL_VECTOR; unsigned int star_size = 0; @@ -1369,10 +1366,6 @@ private: { return get(vcmap_, v); } - void set_constrained(const vertex_descriptor& v, const bool b) - { - put(vcmap_, v, b); - } bool is_isolated(const vertex_descriptor& v) const { return halfedges_around_target(v, mesh_).empty(); @@ -1415,43 +1408,6 @@ private: return PMP::compute_face_normal(f, mesh_, parameters::vertex_point_map(vpmap_)); } - template - void constrain_patch_corners(const FaceRange& face_range) - { - boost::container::flat_set visited; - - BOOST_FOREACH(face_descriptor f, face_range) - { - BOOST_FOREACH(halfedge_descriptor h, - halfedges_around_face(halfedge(f, mesh_), mesh_)) - { - vertex_descriptor vt = target(h, mesh_); - //treat target(h, mesh_) - if (visited.find(vt) != visited.end()) - continue;//already treated - - if (status(h) == PATCH)//h not on patch boundary - continue; //so neither is target(h, mesh_) - - //count incident MESH_BORDER edges - unsigned int nb_incident_borders = 0; - BOOST_FOREACH(halfedge_descriptor hv, - halfedges_around_target(h, mesh_)) - { - CGAL_assertion(vt == target(hv, mesh_)); - if ( (status(hv) == PATCH_BORDER && status(opposite(hv, mesh_)) == MESH_BORDER) - || (status(hv) == MESH_BORDER && status(opposite(hv, mesh_)) == PATCH_BORDER)) - nb_incident_borders++; - } - - if (nb_incident_borders == 1) //this is a special corner - set_constrained(vt, true); - - visited.insert(vt); - } - } - } - template void tag_halfedges_status(const FaceRange& face_range) {