CGAL collapse_edge source code analysis

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.


CGAL collapse_edge source code analysis_第1张图片


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);

these bool variables denote whether the specific face exists. From the picture we draw, we know lTopFaceExists is false, lTopLeftFaceExists is false, lBottomFaceExists is true, lBottomRightFaceExists is true. 


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);
    }

After joining face, the halfedge qb and bq are removed.

CGAL collapse_edge source code analysis_第2张图片

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:

CGAL collapse_edge source code analysis_第3张图片

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 ;
      }  
    }

In remove_face function, the vertices q, b will be removed, therefore the vertex removed is q not p. However, in fact, no matter vertex p or q is removed, the overall topology of the graph is not changed. Although the topology is the same, the position is different, this is not we want. Hence, let's do some modification.


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.

CGAL collapse_edge source code analysis_第4张图片

  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.

CGAL collapse_edge source code analysis_第5张图片



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:

CGAL collapse_edge source code analysis_第6张图片

CGAL collapse_edge source code analysis_第7张图片

CGAL collapse_edge source code analysis_第8张图片

你可能感兴趣的:(CGAL collapse_edge source code analysis)