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.




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.


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

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.


  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:




  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值