location: CGAL\boost\graph\Euler_operations.h
http://doc.cgal.org/latest/Surface_mesh_simplification/classEdgeCollapsableSurfaceMesh.html
/** * 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`. * * 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'`. * * 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`). * \pre g must be a triangulated graph * \pre `does_satisfy_link_condition(v0v1,g) == true`. */ template<typename Graph> typename boost::graph_traits<Graph>::vertex_descriptor collapse_edge(typename boost::graph_traits<Graph>::edge_descriptor v0v1, 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 qp = opposite(pq, g); halfedge_descriptor pt = opposite(prev(pq, g), g); halfedge_descriptor qb = opposite(prev(qp, g), g); bool lTopFaceExists = ! is_border(pq,g); bool lBottomFaceExists = ! is_border(qp,g); bool lTopLeftFaceExists = lTopFaceExists && ! is_border(pt,g); bool lBottomRightFaceExists = lBottomFaceExists && ! is_border(qb,g); CGAL_precondition( !lTopFaceExists || (lTopFaceExists && ( degree(target(pt, g), g) > 2 ) ) ) ; CGAL_precondition( !lBottomFaceExists || (lBottomFaceExists && ( degree(target(qb, g), g) > 2 ) ) ) ; vertex_descriptor q = target(pq, g); vertex_descriptor p = source(pq, g); bool lP_Erased = false, lQ_Erased = false ; if ( lTopFaceExists ) { CGAL_precondition( ! is_border(opposite(pt, g),g) ) ; // p-q-t is a face of the mesh if ( lTopLeftFaceExists ) { //CGAL_ECMS_TRACE(3, "Removing p-t E" << pt.idx() << " (V" // << p.idx() << "->V" << target(pt, g).idx() // << ") by joining top-left face" ) ; join_face(pt,g); } else { //CGAL_ECMS_TRACE(3, "Removing p-t E" << pt.idx() << " (V" << p.idx() // << "->V" << target(pt, g).idx() << ") by erasing top face" ) ; remove_face(opposite(pt, g),g); if ( !lBottomFaceExists ) { //CGAL_ECMS_TRACE(3, "Bottom face doesn't exist so vertex P already removed" ) ; lP_Erased = true ; } } } if ( lBottomFaceExists ) { CGAL_precondition( ! is_border(opposite(qb, g),g) ) ; // p-q-b is a face of the mesh if ( lBottomRightFaceExists ) { //CGAL_ECMS_TRACE(3, "Removing q-b E" << qb.idx() << " (V" // << q.idx() << "->V" << target(qb, g).idx() // << ") by joining bottom-right face" ) ; join_face(qb,g); } else { //CGAL_ECMS_TRACE(3, "Removing q-b E" << qb.idx() << " (V" // << 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 ; } } } CGAL_assertion( !lP_Erased || !lQ_Erased ) ; if ( !lP_Erased && !lQ_Erased ) { //CGAL_ECMS_TRACE(3, "Removing vertex P by joining pQ" ) ; join_vertex(pq,g); lP_Erased = true ; } CGAL_expensive_assertion(is_valid(g)); return lP_Erased ? q : p ; }
remind: before you read this passage, make sure you have already read these passages below:
CGAL join_face source code analysis
CGAL remove_face source code analysis
CGAL join_vertex source code analysis
This function cannot satisfy our requirement, as we need the vertex p removed in any cases. However this function probably removes the vertex q for the efficiency consideration. we will state these details in our explanation.
we aren't gonna explain this function line by line, only the general ideas of each parts in the function will be given.
halfedge_descriptor pq = halfedge(v0v1,g); halfedge_descriptor qp = opposite(pq, g); halfedge_descriptor pt = opposite(prev(pq, g), g); halfedge_descriptor qb = opposite(prev(qp, g), g); bool lTopFaceExists = ! is_border(pq,g); bool lBottomFaceExists = ! is_border(qp,g); bool lTopLeftFaceExists = lTopFaceExists && ! is_border(pt,g); bool lBottomRightFaceExists = lBottomFaceExists && ! is_border(qb,g);
Therefore, we get into this if condition sentence:
if ( lBottomRightFaceExists ) { //CGAL_ECMS_TRACE(3, "Removing q-b E" << qb.idx() << " (V" // << q.idx() << "->V" << target(qb, g).idx() // << ") by joining bottom-right face" ) ; join_face(qb,g); }
Then we just use join_vertex funtion to collapse edge pq.
In summary, this function had done two jobs, the first is to ensure tpq and bqp are not triangles any more, then use join_vertex function to collapse edge.
One spacial case need to mention is like below:
In this case, we will go into this branch:
else { //CGAL_ECMS_TRACE(3, "Removing q-b E" << qb.idx() << " (V" // << 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 ; } }
namespace CGAL { namespace Euler { template< typename Graph > void my_remove_face(typename boost::graph_traits<Graph>::halfedge_descriptor h, Graph& g) { typedef typename boost::graph_traits<Graph> Traits; typedef typename Traits::halfedge_descriptor halfedge_descriptor; typedef typename Traits::face_descriptor face_descriptor; CGAL_precondition(! is_border(h,g)); face_descriptor f = face(h, g); halfedge_descriptor hop = opposite(h, g); halfedge_descriptor nh = next(h, g); halfedge_descriptor nhop = opposite(nh, g); halfedge_descriptor ph = prev(h, g); halfedge_descriptor phop = opposite(ph, g); internal::set_border(h,g); internal::set_border(hop,g); internal::set_border(nh,g); internal::set_border(nhop,g); internal::set_border(ph,g); internal::set_border(phop,g); internal::set_vertex_halfedge(ph, g); internal::set_vertex_halfedge(nhop, g); internal::remove_tip(ph, g); internal::remove_tip(nhop, g); remove_face(f, g); remove_edge(edge(h, g), g); } }//namespace Euler template<typename Graph> typename boost::graph_traits<Graph>::halfedge_descriptor my_join_vertex(typename boost::graph_traits<Graph>::halfedge_descriptor h, Graph& g) { typedef typename boost::graph_traits<Graph> Traits; typedef typename Traits::halfedge_descriptor halfedge_descriptor; typedef typename Traits::vertex_descriptor vertex_descriptor; typedef Halfedge_around_target_iterator<Graph> halfedge_around_vertex_iterator; halfedge_descriptor hop = opposite(h, g) , hprev = prev(hop, g) , gprev = prev(h, g) , hnext = next(hop, g) , gnext = next(h, g); vertex_descriptor v_to_remove = target(hop, g) , v = target(h, g); // this assertion fires needlessly // CGAL_precondition(std::distance( // halfedges_around_face(e, g).first, // halfedges_around_face(e, g).second) >= 4); CGAL_assertion( halfedge(v_to_remove, v, g).first == h ); if((degree(v_to_remove, g) <= 2 || (degree(v, g) <= 2) ) && face(h,g) != Traits::null_face() ) { set_halfedge(face(h,g), gprev, g); } halfedge_around_vertex_iterator ieb, iee; for(boost::tie(ieb, iee) = halfedges_around_target(hop, g); ieb != iee; ++ieb) { CGAL_assertion( target(*ieb,g) == v_to_remove); set_target(*ieb ,v , g); } set_next(hprev, hnext, g); set_next(gprev, gnext, g); set_halfedge(v, gprev, g); // internal::set_constant_vertex_is_border(g, v); remove_edge(edge(h, g), g); remove_vertex(v_to_remove, g); return hprev; } template<typename Graph> typename boost::graph_traits<Graph>::vertex_descriptor my_collapse_edge(typename boost::graph_traits<Graph>::halfedge_descriptor v0v1, 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 = v0v1; // std::cout << "source:" << source(pq, g)->id() << " target: " << // target(pq, g)->id() << std::endl; halfedge_descriptor qp = opposite(pq, g); halfedge_descriptor pt = opposite(prev(pq, g), g); halfedge_descriptor qb = opposite(prev(qp, g), g); halfedge_descriptor bp = opposite(next(qp, g), g); bool lTopFaceExists = ! is_border(pq,g); bool lBottomFaceExists = ! is_border(qp,g); bool lTopLeftFaceExists = lTopFaceExists && ! is_border(pt,g); bool lBottomRightFaceExists = lBottomFaceExists && ! is_border(qb,g); bool lBottomLeftFaceExists = lBottomFaceExists && ! is_border(bp, g); CGAL_precondition( !lTopFaceExists || (lTopFaceExists && ( degree(target(pt, g), g) > 2 ) ) ) ; CGAL_precondition( !lBottomFaceExists || (lBottomFaceExists && ( degree(target(qb, g), g) > 2 ) ) ) ; vertex_descriptor q = target(pq, g); vertex_descriptor p = source(pq, g); bool lP_Erased = false, lQ_Erased = false ; if ( lTopFaceExists ) { CGAL_precondition( ! is_border(opposite(pt, g),g) ) ; // p-q-t is a face of the mesh if ( lTopLeftFaceExists ) { //CGAL_ECMS_TRACE(3, "Removing p-t E" << pt.idx() << " (V" // << p.idx() << "->V" << target(pt, g).idx() // << ") by joining top-left face" ) ; if(degree(p, g) > 2) EulerImpl::join_face(pt,g); } else { //CGAL_ECMS_TRACE(3, "Removing p-t E" << pt.idx() << " (V" << p.idx() // << "->V" << target(pt, g).idx() << ") by erasing top face" ) ; Euler::remove_face(opposite(pt, g),g); if ( !lBottomFaceExists ) { //CGAL_ECMS_TRACE(3, "Bottom face doesn't exist so vertex P already removed" ) ; lP_Erased = true ; } } } if ( lBottomFaceExists ) { CGAL_precondition( ! is_border(opposite(qb, g),g) ) ; // p-q-b is a face of the mesh if ( lBottomRightFaceExists ) { //CGAL_ECMS_TRACE(3, "Removing q-b E" << qb.idx() << " (V" // << q.idx() << "->V" << target(qb, g).idx() // << ") by joining bottom-right face" ) ; if( degree(q, g) > 2 && degree(p, g) > 2) EulerImpl::join_face(qb,g); } else { //CGAL_ECMS_TRACE(3, "Removing q-b E" << qb.idx() << " (V" // << q.idx() << "->V" << target(qb, g).idx() // << ") by erasing bottom face" ) ; if( lBottomLeftFaceExists ) { if( degree(q, g) > 2 && degree(p, g) > 2) EulerImpl::join_face(bp, g); } else { Euler::my_remove_face(opposite(bp, g),g); } } } CGAL_assertion( !lP_Erased || !lQ_Erased ) ; if ( !lP_Erased && !lQ_Erased ) { //CGAL_ECMS_TRACE(3, "Removing vertex P by joining pQ" ) ; my_join_vertex(pq,g); lP_Erased = true ; } CGAL_expensive_assertion(is_valid(g)); return lP_Erased ? q : p ; } }//namespace //CGAL
first, let us list all the modified places and then I'll explain them. This is not an easy work, it took me 2 days to figure out.
In my_collapse_edge function,
if ( lTopLeftFaceExists )
{
//CGAL_ECMS_TRACE(3, "Removing p-t E" << pt.idx() << " (V"
// << p.idx() << "->V" << target(pt, g).idx()
// << ") by joining top-left face" ) ;
if(degree(p, g) > 2)
EulerImpl::join_face(pt,g);
}
we add this line to avoid joining face when t,p,q are collinear, the reason is same for the following similar places we add.
if ( lBottomFaceExists )
{
CGAL_precondition( ! is_border(opposite(qb, g),g) ) ; // p-q-b is a face of the mesh
if ( lBottomRightFaceExists )
{
//CGAL_ECMS_TRACE(3, "Removing q-b E" << qb.idx() << " (V"
// << q.idx() << "->V" << target(qb, g).idx()
// << ") by joining bottom-right face" ) ;
if( degree(q, g) > 2 && degree(p, g) > 2)
EulerImpl::join_face(qb,g);
}
else
{
//CGAL_ECMS_TRACE(3, "Removing q-b E" << qb.idx() << " (V"
// << q.idx() << "->V" << target(qb, g).idx()
// << ") by erasing bottom face" ) ;
if( lBottomLeftFaceExists )
{
if( degree(q, g) > 2 && degree(p, g) > 2)
EulerImpl::join_face(bp, g);
}
else
{
Euler::my_remove_face(opposite(bp, g),g);
}
}
}
if the bottom left face exists, to make sure pqb is not a triangle, we need to join the face, otherwise we remove the face pqb in our own way to assure the vertex q removed.
my_remove_face is to remove the face like the right case in the above picture.
template< typename Graph >
void my_remove_face(typename boost::graph_traits<Graph>::halfedge_descriptor h,
Graph& g)
{
typedef typename boost::graph_traits<Graph> Traits;
typedef typename Traits::halfedge_descriptor halfedge_descriptor;
typedef typename Traits::face_descriptor face_descriptor;
CGAL_precondition(! is_border(h,g));
face_descriptor f = face(h, g);
halfedge_descriptor hop = opposite(h, g);
halfedge_descriptor nh = next(h, g);
halfedge_descriptor nhop = opposite(nh, g);
halfedge_descriptor ph = prev(h, g);
halfedge_descriptor phop = opposite(ph, g);
internal::set_border(h,g);
internal::set_border(hop,g);
internal::set_border(nh,g);
internal::set_border(nhop,g);
internal::set_border(ph,g);
internal::set_border(phop,g);
internal::set_vertex_halfedge(ph, g);
internal::set_vertex_halfedge(nhop, g);
internal::remove_tip(ph, g);
internal::remove_tip(nhop, g);
remove_face(f, g);
remove_edge(edge(h, g), g);
}
in my_join_vertex function, we add :
if((degree(v_to_remove, g) <= 2 || (degree(v, g) <= 2) ) && face(h,g) != Traits::null_face() )
{
set_halfedge(face(h,g), gprev, g);
}
these lines are used to address the situation when t,p,q or p, q, b are collinear, because in these cases, the incident halfedge of the incident face of the halfedge h could probably be h, therefore, we need to set another incident halfedge for the face.
Finally, let us see some edge collapsing pictures from the hierarchical PBD: