VCG 网格基本示例
#include<iostream>
#include<vcg\complex\complex.h>
#include<wrap\io_trimesh\import_obj.h>
#include<wrap\io_trimesh\export_obj.h>
using namespace std;
class MyVertex;
class MyEdge;
class MyFace;
struct MyUsedTypes :public vcg::UsedTypes<vcg::Use<MyVertex>::AsVertexType, vcg::Use<MyEdge>::AsEdgeType, vcg::Use<MyFace>::AsFaceType> {};
class MyVertex :public vcg::Vertex<MyUsedTypes, vcg::vertex::Coord3f, vcg::vertex::Normal3f, vcg::vertex::BitFlags> {};
class MyEdge :public vcg::Edge<MyUsedTypes>{};
class MyFace :public vcg::Face<MyUsedTypes, vcg::face::FFAdj, vcg::face::VertexRef, vcg::face::BitFlags> {};
class MyMesh :public vcg::tri::TriMesh<std::vector<MyVertex>, std::vector<MyEdge>, std::vector<MyFace>> {};
int main() {
MyMesh ms;
int loadMask;
auto error = vcg::tri::io::ImporterOBJ<MyMesh>::Open(ms,"../data/cube.obj", loadMask);
if (error != vcg::tri::io::ImporterOBJ<MyMesh>::E_NOERROR) {
cout << "error reading file" << endl;
exit(0);
}
vcg::tri::RequirePerVertexNormal(ms);
vcg::tri::UpdateNormal<MyMesh>::PerVertexNormalized(ms);
cout<<"vn = "<<ms.VN()<<"\tfn = "<<ms.FN()<<endl;
vcg::tri::io::ExporterOBJ<MyMesh>::Save(ms, "../output/ocube.obj", loadMask);
}
Adjacency
VCG Lib不提供独特的硬编码方式去编码三角形和边之间的邻接关系。它取决于简素携带的属性以及属性的使用方式。一般面片的定义中会包含属性vcg::face::VertexRef,它存储了三个指向MyVertex的指针,可以通过函数V()(纯索引数据结构实现)访问。目前几乎所有在 vcglib 中实现的算法都假定vcg::face::VertexRef存在。因此,MyFace中不包含该属性,虽然定义上没有问题,但几乎没有算法有效。
还有其它可用于遍历网格的邻接关系,例如收集顶点的一环邻域。VCG Lib有两种主要的邻接关系:face-face(FF)和vertex-face(VF)。
FF Adjacency
FF邻接存储在面的vcg::face::FFAdj属性中,通过边编码面之间的邻接关系。如图所示:
顶点以逆时针方向从0到2编号,边的端点为i和(i+1)mod3,从0到2编号。因此,在上图中,面片f0和f1的公共边在面片f1中是编号为1的边,而在面片f0中是编号为0的边。
对于面片f的第i条边,属性vcg::face::FFAdj存储了:
FFp(i):与f共享第i条边的面片指针。如果该边是边界边,则指针指向面片本身。FFi(i):在指向的面片中与f的第i条边对应的边的索引。 假设p0和p1是两个面片指针,则有如下关系:
f1->FFp(1) == p0
f1->FFi(1) == 0
f0->FFp(0) == p1
f0->FFi(0) == 1
准确的说,FFp(i)指向一个邻接面,此处并不是特指。因为在非流形情况下,同一条边可能有两个以上面片对应,VCG Lib也处理这种情况。下图中给出了同一条边由四个面共享的情况:
这种情况下,FF邻接是一个循环列表。该列表由VCG Lib函数完成,即更新vcg::tri::UpdateTopology::FaceFace(MeshType&m)。VCG Lib提供了一种检查网格在特定边上是否是流形的:bool IsManifold(MyFace *f, int e){return (f0==f0->FFp(0)->FFp(f0->FFi(0)))}。
pos
Pos属性是Cell-Tuple(ref)的VCG Lib实现。三角网格中的Pos是顶点组成的三元组:pos = (v,e,f),v是边e的顶点,边e属于面片f。下图给出了三角网格中几个pos,例如c0 = (v,e0,f)。给定一个Posc,只需要改变三元组的一个元素就可以获得另一个邻接Posc'。将从一个Pos到其邻接Pos的操作称作Flip,记作FlipV、FlipE和FlipF,分别表示Flip一个顶点、一条边和一个面片。
例如,对于c1来说,除了三元组的顶点分量外,c2Pos的其它部分与c1相同,即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是否在相对于顶点的面的CCW边上。具体来说,c0是相对于顶点的面的CCW边,而c6是相对于顶点的CW边。另外,由于 FF 邻接的定义方式,当pos在边界上时,它会反弹回来。这种Flip对在 VCG Lib 中被广泛用于流形顶点的单环邻域上。
以下代码展示如何使用pos遍历顶点的一环邻域:
//sf/apps/sample/trimesh_pos_dmo/trimesh_pos_demo.cpp
#include<vcg/simplex/face/pos.h>
...personal mesh type, as the head of this page
void OneRingNeighborhood(MyFace *f){
MyVertex *v = f->V(0);
MyFace *start = f;
vcg::face::Pos<MyFace>p(f,0,v);
do{
p.FlipF();
p.FlipE();
}while(p.f!=start);
}
注意,起始pos的顶点是任意选择的。通常可能想要知道它是什么,这可以通过vcg::vertex::VFAdj属性来实现,该属性联结了和顶点相关的每一个面。但是,如果顶点在边界上,上述一环邻域的实现就无效了。简单举个例子,从posc4出发可以找到c5,c6以及与它在同一个面上的c3。但如果是在边界情况下,可以获取pos的序列:c5--bounce--c6--FlipF-FlipE--c3--FlipF-FlipE--c1--bounce--c0--FlipF-FlipE--c4,对应面f2,f2,f1,f0,f0,f1,这不是想要的一环邻域。VCG Lib提供了pos的变体解决这种问题。
Jumping Pos
Jumping Pos的工作原理与Pos完全相同,只是它在遇到边界时不会反弹。相反,它在顶点周围跳跃,就好像共享顶点的边界面(如图中的面f0和f2)是相邻的。
//sf/apps/sample/trimesh_pos_dmo/trimesh_pos_demo.cpp
#include<vcg/simplex/face/jumping_pos.h>
...personal mesh type, as the head of this page
void OneRingNeighborhoodJP(MyFace *f){
MyVertex *v = f->V(0);
MyFace *start = f;
vcg::face::JumpingPos(MyFace)p(f,0,v);
do{
p.NextFE();
}while(p.f!=start);
}
VF adjacency
VCG Lib实现了VF邻接,即给定一个顶点v,可以检索所有与其相关的面片。令v_star=(f0,f1,f2,...,fk)是和v相关的面片集合。VCG Lib使用如下属性可以以最佳时间(O(star_v))检索v_star:
vcg::vertex::VFAdj是包含f0指针的顶点属性。vcg::face::VFAdj是当前面片中与每个顶点相关联的指向下一个面片的指针。 这两个属性不仅仅是指针,也包含索引,该索引引用指向面中顶点的索引,样式与vcg::face::FFAdj相同。下图中给出了一个示例,与FF邻接相似,也需要使用vcg::tri::UpdateTopology::VertexFace(MeshType&m)更新拓扑。
上图中,
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是一个简单的迭代器,可以在VF邻接关系中遍历一环邻域。
//sf/apps/sample/trimesh_pos_dmo/trimesh_vfiter_demo.cpp
#include<vcg/simplex/face/pos.h>
...personal mesh type, as the head of this page
void OneRingNeighBorhoodVF(MyVertex *v){
vcg::face::VFIterator<MyFace>vfi(v);
while(!vfi.End()){
MyFace *f = vfi.F();
//do soething with face f
}
}
Stars and Rings
- vcg::face::VFOrderedStarFF
- vcg::face::VVStarVF
- vcg::face::VFStarVF
- vcg::face::VFExtendedStarVF
- vcg::face::EFStarFF
Few facts on FF and VF adjacency
如果网格是流形的,使用Pos计算顶点的单环邻域(需要FF邻接)与使用VFIterator计算相同(需要VF邻接)。 如果使用Pos,访问面的顺序可以是CW或CCW,而使用VIterator则未指定顺序。如果网格是非流形的,Pos可能找不到顶点的单环邻域的所有面,但VFIterator总是可以的。
Boundary relations and adjacency
在许多算法中,都需要简化面片的边界条件,例如,想知道给定面f在指定边e上是否有一个或多个相邻面。 使用FF邻接,这可以通过使用face::IsBorder(f,e) 静态函数简单地完成,该函数简单地检查存储在边e上的面f中的指针是否指向f本身。如果使用Pos操作,则有一个Pos的成员函数 IsBorder(),它报告当前pos的边界条件。 类似地,为了测试网格上特定位置的多样性,有一个pos 类的face::IsManifold(f,e) 静态函数和一个 IsManifold(e)函数成员。
如果不使用FF邻接,评估边界条件可能不是很有效,因此vcg库提供了一种将网格的当前边界条件转换为顶点和面标志的技术。使用UpdateFlags静态类的成员来计算反映当前网格状态的标志,并使用面类的IsB(e)成员函数访问这些标志。 请记住,如果更改网格拓扑,基于标志的边界信息可能会变得无效。 另一方面,考虑到许多非网格修改算法不需要明确的FF邻接,而只需要边界信息(典型示例:大多数网格平滑和曲率计算算法)。
请注意,对于非流形条件,边界标志也设置为true。