本文主要是介绍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.
对于面 f 的第 i 个边, vcg::face::FFAdj
属性存储了
FFp(i)
: 指向公用面 f的第 i 个边的面的指针, 如果该边是边界, 那么就指向 f 自己.FFi(i)
: f 的第 i 个边在指向的邻接面中的索引.
对于上面的例子, 假定 f0,f1 都是指针,那么就有下面的对应关系:
f1->FFp(1) == f0
f1->FFi(1) == 0f0->FFp(0) == f1
f0->FFi(0) == 1
请注意, 准确的说, 应当是 FFp(i)
指向一个邻接面, 而不是指向那个邻接面. (原文: FFp(i) points to a adjacent face, not to the adjacent face ). 原因就是在有非流形边的情况下, 有可能有多个面共用一条边, 这种情况 VCG 是无缝支持的. 下图展示了4 个面使用同一条边.
在这种情况下,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 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 typeclass 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 vertexdo{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 vertexdo{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)
.
v.VFp() == f2
v.VFi() == 0f2->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 facefor(;!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邻接关系.
这篇关于VCG文档 - 邻接与拓扑(Adjacency and Topology)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!