VCG文档 - 邻接与拓扑(Adjacency and Topology)

邻接关系

VCG 库没有单一的,硬编码的方式来对三角形和边之间的关系进行编码. 这一切都取决于存储哪些属性以及如何使用它们. 在前面几节的例子中, 面的定义总是包含 vcg::face::VertexRef 属性, 这个属性存储了可以使用函数 V() 访问的指向MyVertex 的指针. 目前 VCG 中实现的几乎所有算法都假定 vcg::face::VertexRef 存在. 所以, 如果你的定义不包含 vcg::face::VertexRef 属性, 虽然这是正确的, 但几乎没有算法能正常运行.

还有其他的邻接关系可以用来遍历曲面, 例如遍历顶点的1- 邻域. VCG 库主要有两种邻接关系: 面-面(FF)和顶点-面(VF)邻接. 下面会对它们进行详细的介绍.

面-面邻接关系 (FF Adjacency)

vcg::face::FFAdj 属性声明了面-面邻接关系, 它通过面的公用边来编码这个邻接关系. 下图说明了用于索引两个三角面片的顶点和边的约定.

三角形的顶点按逆时针方向标记0,1,2. 边 i( 0,1,2) 就是 顶点 i和 i+1 之间的边.因此 ,在下图中, 面 f0,f1 之间的连接边就是 f0 的边 0 和 f1 的边 1.

Indexing of vertexes and edges

对于面 f 的第 i 个边, vcg::face::FFAdj 属性存储了

  • FFp(i): 指向公用面 f的第 i 个边的面的指针, 如果该边是边界, 那么就指向 f 自己.
  • FFi(i): f 的第 i 个边在指向的邻接面中的索引.

对于上面的例子, 假定 f0,f1 都是指针,那么就有下面的对应关系:

f1->FFp(1) == f0
f1->FFi(1) == 0

f0->FFp(0) == f1
f0->FFi(0) == 1

请注意, 准确的说, 应当是 FFp(i) 指向一个邻接面, 而不是指向那个邻接面. (原文: FFp(i) points to a adjacent face, not to the adjacent face ). 原因就是在有非流形边的情况下, 有可能有多个面共用一条边, 这种情况 VCG 是无缝支持的. 下图展示了4 个面使用同一条边.

Non manifold edge

在这种情况下,FF邻接被构成了循环列表(不一定按照二面角进行排序). VCG 库是通过vcg::tri::UpdateTopology::FaceFace(MeshType &m) 函数来更新的. 这种情况下,VCG 提供了一种方法来检查一条边是否是流形边, 因为邻接关系是相互的. 例如, 如果是流形边的话, f0 指向 f1 , 同时 f1 又将指向 f0 .

bool IsManifold(MyFace *f,int e) { return (f0 == f0->FFp(0)->FFp(f0->FFi(0)))}
Pos

Pos 是 VCG Lib 对 Cell-Tuple{ref} 的实现. 简单起见, 这里给出一个尽可能简短的定义. 在三角网格中, Pos
vectex: pos = (v,e,f) 构成的三元组, e 是以 v 为起点并且属于面 f 的边. 下图使用指向一个顶点,靠在一条边并且属于一个面的小三角形展示 了几个 Pos . 例如 , c0 = (v, e0 ,f).

Pos Example

这里有一个非常好的性质, 给定一个 Pos c, 若仅改变 c 中的一个元素, 那就能唯一的得到 c 的一个邻域.

我们称从一个 Pos c 到它的一个邻域 Pos c' 的操作为 Flip. FlipV,FlipE, FlipF 表明操作的元素是Pos c 的顶点,边或者面.

例如, 考虑 c1 , 和 c2 只有顶点不同.可以写作 c2=FlipV(c1) .

下面有更多的例子对此进行解释.

c2 = FlipV(c1)
c0 = FlipE(c1)
c3 = FlipF(c0)

CCW around v
c4 = FlipE(FlipF(c0))
c5 = FlipE(FlipF(c4))
Bounce
c6 = FlipE(FlipF(c5))
CW around v
c3 = FlipE(FlipF(c6))
c1 = FlipE(FlipF(c3))
Bounce
c0 = FlipE(FlipF(c1))

注意, 复合两个 Flip 操作, FlipF,FlipE, 我们得到了从一个Pos 到下一个(逆时针或者顺时针)Pos 的变换, 方向取决于开始Pos 相对于顶点是否位于面的逆时针边上. 也要注意, 由于 FF 邻接的定义, 当一个 Pos
在边界上时, 它将反转回来. 这一对 Flip 操作在VCG库中被广泛的使用, 例如遍历一个顶点的 1- 邻域.

下面的例子展示了这种使用:

sf/apps/sample/trimesh_pos_demo/trimesh_pos_demo.cpp

#include <vcg/simplex/face/pos.h> // include the definition of pos

...includes to define your mesh type

class MyVertex: ...
class MyFace: public vcg::FaceSimp2<MyVertex,MyEdge,MyFace, vcg::face::VertexRef, vcg::face::FFAdj>{};

void OneRingNeighborhood( MyFace * f)
{
  MyVertex * v = f->V(0);
  MyFace* start = f;
  vcg::face::Pos<MyFace> p(f,0,v);// constructor that takes face, edge and vertex
  do
  {
    p.FlipF();
    p.FlipE();
  }while(p.f!=start);
}
  • 我们任意选择了一个作为旋转中心的顶点f->V(0). 通常来说是会选定一个已知的顶点. 可以通过使用vcg::vectex::VFAjd 属性来知道一个顶点和哪些面邻接. 更多介绍参考下面的 VF 邻接. 当顶点在边界上时这个操作就不会工作了. 看下面的例子, 从 c4 开始操作,可以一次找到 c5,c6,c3 , 这又回到了 c4 所在的面.当然,如果你使用Pos 本身作为一个警戒的条件, 这种情况就可以避免. 即使那样, 你将得到一个序列: c5,c6,c3,c1,c0,c4 对应着面 f2,f2,f1,f0,f0,f1 , 但这并不是你想要的. VCG 提供了另外一种变化的 Pos 来解决这个问题.
Jumping Pos

Jumping Pos 和 Pos 几乎完全相同, 只是它遇到边界时不会反转. 相反的, 它跳跃到共用顶点的边界面(图中的 f0,f2 ).

sf/apps/sample/trimesh_pos_demo/trimesh_pos_demo.cpp
    #include <vcg/simplex/face/jumping_pos.h> // include the definition of jumping pos
    //...includes to define your mesh type
    //class MyVertex: ...
    class MyFace: public vcg::FaceSimp2<MyVertex,MyEdge,MyFace, vcg::face::VertexRef,vcg::face::FFAdj>{};
    void OneRingNeighborhoodJP( MyFace * f)
    {
      MyVertex * v = f->V(0);
      MyFace* start = f;
      vcg::face::JumpingPos<MyFace> p(f,0,v);// constructor that takes face, edge and vertex
      do
      {
        p.NextFE();
      }while(p.f!=start);
    }

VF Adjacency

VCG Lib 实现了顶点-面的邻接关系. 例如, 给定一个顶点 v, 可以检索到包含顶点v 的所有面. 令 v=(f0,f1,,fk) 是与顶点v 邻接的面的集合. VCG Lib 可以以最优的时间 (O( v )) 访问 v , 这个访问使用下面的属性:

  • vcg::vertex::VFAjd 顶点属性, 包含了指向面的指针.
  • vcg::face::VFAjd 面属性, 包含了指向集合 v 中下一个面的每个顶点的指针.

这两个属性不仅是一个指针, 它们也包含了类似 vcg::face::FFAdj 的索引. 下图给出了一个练习的示例: 类似 FF 邻接关系, 你需要使用 vcg::tri::UpdateTopology::VertexFace(MeshType &m).

Example of vertex-face adjacency

v.VFp() == f2
v.VFi() == 0

f2->VFp(0) == f3
f2->VFi(0) == 1
f3->VFp(1) == f1
f3->VFi(1) == 2
f1->VFp(2) == f0
f1->VFi(2) == 2
f0->VFp(2) == NULL
f0->VFi(2) == -1
VFIterator

VFIterator 是一个用于遍历顶点的邻接面的迭代器(类似于 FF 邻接关系中的 Pos). 下面的代码展示了如何使用它:

sf/apps/sample/trimesh_pos_demo/trimesh_vfiter_demo.cpp
#include <vcg/simplex/face/pos.h> // include the definition of  VFIterator
//...includes to define your mesh type
class MyVertex: public vcg::VertexSimp2<MyVertex,MyEdge,MyFace, vcg::vertex::VFAdj /*,... other attributes*/ >{};
class MyFace: public vcg::FaceSimp2<MyVertex,MyEdge,MyFace, vcg::face::VertexRef,vcg::face::VFAd>{};
void OneRingNeighborhoodVF( MyVertex * v)
{
  vcg::face::VFIterator<MyFace> vfi(v); //initialize the iterator tohe first face
  for(;!vfi.End();++vfi)
  {
    MyFace* f = vfi.F();
    // ...do something with face f
  }
}
Starts and Rings
vcg::face::VFOrderedStarFF
vcg::face::VVStarVF
vcg::face::VFStarVF
vcg::face::VFExtendedStarVF
vcg::face::EFStarFF
Few facts on FF adjacency and VF adjacency

在这里,我们提供一系列简单的描述来解除疑惑,并帮助选择最符合您需求的邻接关系.

如果曲面是流形的,那么使用 Pos 计算顶点的1-邻域和使用VFIterator 计算将是一致的.
如果使用 Pos,遍历面的顺序可能是顺时针或者逆时针的,使用 VFIterator 则未指明顺序. 如果曲面是非流形曲面, 那么无论使用哪种方式都有可能无法遍历所有面.

Boundary relations and adjacency

在许多算法中都需要面的边界条件. 例如, 知道给定的面的一条边是否有一个或多个邻接的面. FF邻接关系时, 使用 face::IsBorder(f,e) 静态函数可以简单判断 f 的边 e 存储的指针是否指向f自己.如果使用Pos , 那么 Pos的成员函数 IsBorder() 可以给出当前的边界状态. 类似的,可以使用 face::IsManifold(f,e)IsManifold(e) 来检测是否是流形边.

如果没有使用 FF 邻接关系, 那么判断边界条件将效率低下. VCG 提供了一种技术将边界状态提供给面和顶点的 Flags. 使用UpdateFlags 函数计算反应当前状态的 Flags , 使用 IsB(e) 函数来访问它. 谨记如果你改变了网格的拓扑关系将会导致边界信息不可用.另一方面, 许多不改变网格的算法仅要求边界信息而不要求FF邻接关系.

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值