mirror of https://github.com/CGAL/cgal
Merge pull request #2737 from maxGimeno/BGL_isotropic_remeshing_keep_constrained_points_intact-GF
BGL: Isotropic_remeshing() keeps constrained vertices intact # Conflicts: # Installation/CHANGES.md
This commit is contained in:
commit
cabb7d9b89
|
|
@ -1020,40 +1020,33 @@ add_face_to_border(typename boost::graph_traits<Graph>::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'`)
|
||||
*
|
||||
* <UL>
|
||||
* <LI>The edge `v0v1` is no longer in `g`.
|
||||
* <LI>The faces incident to edge `v0v1` are no longer in `g`.
|
||||
* <LI>Either `v0`, or `v1` is no longer in `g` while the other remains.
|
||||
* Let `vgone` be the removed vertex and `vkept` be the remaining vertex.
|
||||
* <LI>If `e` was a border halfedge, that is `is_border(e, g) == true`, then `next(ep,g) == en`, and `prev(en,g) == ep`.
|
||||
* <LI>If `e` was not a border halfedge, that is `is_border(e, g) == false`, then `ep` and `epo` are no longer in `g` while `en` and `eno` are kept in `g`.
|
||||
* <LI>For all halfedges `hv` in `halfedges_around_target(vgone, g)`, `target(*hv, g) == vkept` and `source(opposite(*hv, g), g) == vkept`.
|
||||
* <LI>No other incidence information has changed in `g`.
|
||||
* </UL>
|
||||
* \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 Graph>
|
||||
typename boost::graph_traits<Graph>::vertex_descriptor
|
||||
collapse_edge(typename boost::graph_traits<Graph>::edge_descriptor v0v1,
|
||||
collapse_edge(typename boost::graph_traits<Graph>::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<Graph>::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<Graph> 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<Graph>::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<Graph>::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<Graph>::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<Graph>::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<Graph>::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;
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1397,6 +1397,101 @@ clear_impl(FaceGraph& g)
|
|||
}
|
||||
}
|
||||
|
||||
template <class FaceGraph>
|
||||
void swap_vertices(
|
||||
typename boost::graph_traits<FaceGraph>::vertex_descriptor& p,
|
||||
typename boost::graph_traits<FaceGraph>::vertex_descriptor& q,
|
||||
FaceGraph& g)
|
||||
{
|
||||
typedef typename boost::graph_traits<FaceGraph>::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 <class FaceGraph>
|
||||
void swap_edges(
|
||||
const typename boost::graph_traits<FaceGraph>::halfedge_descriptor& h1,
|
||||
const typename boost::graph_traits<FaceGraph>::halfedge_descriptor& h2,
|
||||
FaceGraph& g)
|
||||
{
|
||||
typedef typename boost::graph_traits<FaceGraph>::halfedge_descriptor halfedge_descriptor;
|
||||
typedef typename boost::graph_traits<FaceGraph>::face_descriptor face_descriptor;
|
||||
typedef typename boost::graph_traits<FaceGraph>::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<FaceGraph>::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
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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})
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -0,0 +1,226 @@
|
|||
#include "test_Prefix.h"
|
||||
#include <boost/range/distance.hpp>
|
||||
#include <CGAL/boost/graph/Euler_operations.h>
|
||||
|
||||
template < typename Mesh>
|
||||
typename boost::graph_traits<Mesh>::
|
||||
halfedge_descriptor find_halfedge(double x1, double y1,
|
||||
double x2, double y2,
|
||||
Mesh& m,
|
||||
bool is_border = false)
|
||||
{
|
||||
typedef typename boost::property_map<Mesh, CGAL::vertex_point_t>::type VPMAP;
|
||||
typedef typename boost::property_traits<VPMAP>::value_type Point;
|
||||
|
||||
typedef typename boost::graph_traits<Mesh>::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<Mesh>::null_halfedge();
|
||||
}
|
||||
|
||||
template <typename Mesh>
|
||||
void
|
||||
collapse_edge_test()
|
||||
{
|
||||
CGAL_GRAPH_TRAITS_MEMBERS(Mesh);
|
||||
typedef typename boost::graph_traits<Mesh>:: vertex_descriptor vertex_descriptor;
|
||||
typedef typename boost::graph_traits<Mesh>:: 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<Polyhedron>();
|
||||
collapse_edge_test<SM>();
|
||||
|
||||
#ifdef CGAL_USE_OPENMESH
|
||||
collapse_edge_test<OMesh>();
|
||||
#endif
|
||||
|
||||
std::cerr << "done\n";
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -380,7 +380,27 @@ does_satisfy_link_condition()
|
|||
assert(CGAL::Euler::does_satisfy_link_condition(*edges(f.m).first,f.m));
|
||||
}
|
||||
|
||||
|
||||
template <typename Graph>
|
||||
void
|
||||
test_swap_edges()
|
||||
{
|
||||
typedef typename boost::graph_traits<Graph>::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<nbh-1; ++i)
|
||||
{
|
||||
for(std::size_t j=i+1; j<nbh; ++j)
|
||||
{
|
||||
Graph g;
|
||||
CGAL::make_tetrahedron(pt,pt,pt,pt,g);
|
||||
halfedge_descriptor h1 = *CGAL::cpp11::next(boost::begin(halfedges(g)), i);
|
||||
halfedge_descriptor h2 = *CGAL::cpp11::next(boost::begin(halfedges(g)), j);
|
||||
CGAL::internal::swap_edges(h1, h2, g);
|
||||
CGAL_assertion(CGAL::is_valid_polygon_mesh(g));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Graph>
|
||||
void
|
||||
|
|
@ -400,6 +420,7 @@ test_Euler_operations()
|
|||
remove_center_vertex_test<Graph>();
|
||||
join_split_inverse<Graph>();
|
||||
does_satisfy_link_condition<Graph>();
|
||||
test_swap_edges<Graph>();
|
||||
}
|
||||
|
||||
int main()
|
||||
|
|
|
|||
|
|
@ -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
|
||||
------------
|
||||
|
||||
|
|
|
|||
|
|
@ -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 <typename FaceRange>
|
||||
void constrain_patch_corners(const FaceRange& face_range)
|
||||
{
|
||||
boost::container::flat_set<vertex_descriptor> 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<typename FaceRange>
|
||||
void tag_halfedges_status(const FaceRange& face_range)
|
||||
{
|
||||
|
|
|
|||
Loading…
Reference in New Issue