:
face:
:
VertexRef,vcg:
:
face:
:
FFAdj>{};
voidOneRingNeighborhoodJP(MyFace*f)
{
MyVertex*v=f->V(0);
MyFace*start=f;
vcg:
:
face:
:
JumpingPosp(f,0,v);
//constructorthattakesface,edgeandvertex
do
{
p.NextFE();
}while(p.f!
=start);
}
顶点-面(VF)的邻接关系
VCGlib中,顶点与面之间也实现了邻接关系,即给定一个顶点v,可以找出所有与其关联的面。
设v_star=(f0,f1,f2,…,fk)为一个列表,该列表中的元素都是与顶点v关联的面。
可以通过以下属性索引v_star:
∙vcg:
:
vertex:
:
VFAdj:
这是一个顶点的属性,包含了指向f0的指针
∙vcg:
:
face:
:
VFAdj:
这是面的属性,该面三个顶点中的每一个顶点都包含了指向v_star列表中下一个面的指针
这两个属性不仅是指针,还包含顶点在所指向的面中的索引。
例如上图中,有:
v.VFp()==f2
v.VFi()==0
f2->VFp(0)==f3
f2->VFi(0)==1
//f2中索引为0的顶点(即v)指向了面f3,而v在f3中的索引为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
VCGLib 使用说明
能做什么 :
1)最基本的,它提供Mesh(triangularmesh,tetrahedralmesh,三角网格或四面体网格)数据结构的定义,该数据结构支持对Mesh数据的快速访问(拓扑信息、空间查询等)以及高效执行网格上算法;
2)在Mesh数据结构基础上,实现大量高效的网格算法,如网格修补、平滑、变形、曲率计算、细分、泊松盘采样、等值面计算等;
3)IO支持,读写PLY、OBJ、STL、3DS、OFF、DXF等格式网格文件;
4)UI支持,如OpenGL网格显示,Trackball交互等。
使用说明
1)定义Mesh类型;
访问及指定Mesh的顶点、三角形(对三角网格,如果是四面体网格则是四面体,这里默认只讲三角网格)等数据;`
定义Mesh类型的典型代码如下(API文档主页 BasicConcepts, 在线版 ):
#include "vcg/complex/complex.h"
//类型声明
class MyVertex;
class MyEdge;
class MyFace;
typedef vcg:
:
UsedTypes< vcg:
:
Use:
:
AsVertexType, vcg:
:
Use :
:
AsEdgeType, vcg:
:
Use:
:
AsFaceType> MyUsedTypes;
//顶点类型
class MyVertex:
publicvcg:
:
Vertex<MyUsedTypes, vcg:
:
vertex:
:
Coord3f, vcg:
:
vertex:
:
Normal3f,vcg:
:
vertex:
:
BitFlags > {};
//边类型
class MyEdge:
publicvcg:
:
Edge<MyUsedTypes, vcg:
:
edge:
:
VertexRef, vcg:
:
edge:
:
EFAdj,vcg:
:
edge:
:
BitFlags > {};
//面类型,三角形
class MyFace:
publiccvcg:
:
Face<MyUsedTypes, vcg:
:
face:
:
VertexRef, vcg:
:
face:
:
Normal3f,vcg:
:
face:
:
FFAdj, vcg:
:
face:
:
BitFlags > {};
//网格类型
Typedefvcg:
:
tri:
:
TriMesh< std:
:
vector, std:
:
vector, std:
:
vector > GLMesh;
抛开MyUseTypes不看,上面代码定义的网格类型为:
网格包含属性:
顶点、边、三角形数组(std:
:
vector<>);
每个顶点包含属性:
空间坐标(3个float表示)、顶点法向量、标志位;
每个边包含属性:
顶点指针(指向该边的两个顶点)、边-面邻接信息、标志位;
每个三角形面包含属性:
顶点指针(指向该三角形的三个顶点)、面法向量、面-面邻接信息、标志位。
VCGlib使用Reference数据结构,对每个边、面用指针记录其顶点、邻接面等信息,其他网格数据结构见wikipediaPolygonMesh条目 。
为了做到足够通用, VCGlib使用了C++templatemetaprogramming(模板元编程)方法 。
上面代码中的MyVertex、MyEdge、MyFace、GLMesh等类型包含哪些属性(模板参数)、属性的顺序(模板参数顺序)都是可以根据需要随意指定的(当然,必须包含足够的属性以执行相应网格算法),一般来说,最好使顶点、边、面包含标志位属性(BitFlags),BitFlags指示该顶点、边、面是否可写、可读、已删除(为了效率,例如,删除顶点操作可能并不立即删除顶点数据,而仅仅打个标志位,待所有操作完成再更新顶点数据)等。
不去深入VCGlib元编程机理(说实话我还没弄清楚),可选个数模板参数是通过默认模板参数实现的,vcg:
:
Vertex/Edge/Face<>将继承其模板参数。
下面列举所有可选的模板参数:
网格vcg:
:
tri:
:
TriMesh<>最多可有四个参数:
顶点容器、边容器、面容器、半边容器(vcg:
:
HEdge<>);
顶点vcg:
:
Vertex<> 可以包含的属性有:
坐标、法向量、颜色、纹理坐标、标志位、网格质量(网格在该点出优劣评价指标)、曲率、半径、顶点-边邻接信息、顶点-面邻接信息、顶点-半边邻接信息,等(API文档Modules选项卡VertexComponents, 在线版 );
边vcg:
:
Edge<> 可以包含的属性有:
顶点指针、颜色、标志位、网格质量、边-顶点邻接信息、边-边邻接信息、边-面邻接信息、边-半边邻接信息,等(API文档Modules选项卡EdgeComponents, 在线版);
面vcg:
:
Face<> 可以包含的属性有:
顶点指针、法向量、颜色、标志位、网格质量、顶点-面邻接信息、面-边邻接信息、面-面邻接信息,等(API文档Modules选项卡FaceComponents, 在线版 )。
访问Mesh数据示例代码如下:
// loadmesh ...
int i=0,j=0;
// 见vcg:
:
tri:
:
TriMesh<>-------------------------------------------------------------
mesh.VN();
mesh.EN();
mesh.FN();
// 顶点、边、面个数,可能小于vs/es/fs.size()
// 因为有些元素被删除时仅仅打了标志位而并未删除存储数据
std:
:
vector&vs=mesh.vert; // 顶点数组
std:
:
vector&es=mesh.edge; // 边数组
std:
:
vector&fs=mesh.face; // 面数组
// 见vcg:
:
Vertex<>及其模板参数
-------------------------------------------------------
GLMesh:
:
VertexType&v=mesh.vert[i]; // 第i个顶点,
假设v.isD()==false,即未标志为已删除
v.P().Z();v.P().V(j); // 顶点坐标,其xyz分量
v.N().X(); // 顶点法向,其x分量
// 见vcg:
:
Edge<>及其模板参数---------------------------------------------------------
GLMesh:
:
EdgeType&e=mesh.edge[i]; // 第i个边,
假设e.isD()==false
GLMesh:
:
VertexType*pve=e.V(j); // j=0,1,边的两个端点顶点的指针GLMesh:
:
FaceType*pfa=e.EFp(); // 边-面邻接信息,该边连接的第一个面
// 见vcg:
:
Face<>及其模板参数---------------------------------------------------------
GLMesh:
:
FaceType&f=mesh.face[i]; // 第i个面(三角形),
假设f.isD()==false
GLMesh:
:
VertexType*pvf=f.V(j); // j=0,1,2,三角形面的三个顶点的指针
f.N(); // 面的法向量
GLMesh:
:
FaceType*pfb=f.FFp(j); // 面-面邻接信息,j=0,1,2,面f通过其第j个边连接的第一个面
// 可以通过返回的引用(左值)修改数据,但不要随便修改,见下文------------------------------------
v.P().Y()+=3.2f;
e.V(j) =&v;
f.V(j) =&v;
// 遍历所有顶点、边、面需要跳过标记为已删除的元素---------------------------------------------
for(size_ti=0;ii)
{
if(vs[i].IsD())
continue;
//dosomethingforeachvertexvs[i]...
} // 除非已经删除了所有标记为已删除元素的存储数据,
比如:
vcg:
:
tri:
:
Allocator:
:
CompactVertexVector(mesh);vcg:
:
tri:
:
Allocator:
:
CompactEdgeVector(mesh);vcg:
:
tri:
:
Allocator:
:
CompactFaceVector(mesh);
for(size_ti=0;ii)
{
// dosomethingforeachfacefs[i]...
}
填充(Fill)Mesh数据的示例代码如下 (API文档主页Creatinganddestroyingelements, 在线版 ,代码摘自那里 ):
VCGlibReference数据结构,依赖于指针,直接操作顶点、边、面数组mesh.vert/edge/face可能产生std:
:
vector<> 存储重新分配,此时,相关指针将失效,vcg:
:
tri:
:
Allocator<> 处理这些问题
GLMeshm;
GLMesh:
:
VertexIteratorvi =vcg:
:
tri:
:
Allocator<GLMesh>:
:
AddVertices(m, 3);
GLMesh:
:
FaceIteratorfi =vcg:
:
tri:
:
Allocator<GLMesh>:
:
AddFaces(m, 1);
GLMesh:
:
VertexPointerivp[4];
ivp[0]=&*vi;
vi->P()=GLMesh:
:
CoordType(0.0f,0.0f,0.0f);
++vi;
ivp[1]=&*vi;
vi->P()=GLMesh:
:
CoordType(1.0f,0.0f,0.0f);
++vi;
ivp[2]=&*vi;
vi->P()=GLMesh:
:
CoordType(0.0f,1.0f,0.0f);
++vi;
fi->V(0)=ivp[0];
fi->V
(1)=ivp[1];
fi->V
(2)=ivp[2];
// Alternative,morecompact,methodforaddingasinglevertex
ivp[3]=&*vcg:
:
tri:
:
Allocator<GLMesh>:
:
AddVertex(m,GLMesh:
:
CoordType(1.0f,1.0f,0.0f));
// Alternative,methodforaddingasingleface(onceyouhavethevertexpointers)
vcg:
:
tri:
:
Allocator<GLMesh>:
:
AddFace(m,ivp[1],ivp[0],ivp[3]);
// 同理,如果自己保存了顶点等数据指针,需要在修改顶点、边、面数组后更新该指针--------------------
// apotentiallydangerouspointertoameshelement
GLMesh:
:
FacePointerfp=&m.face[0];
vcg:
:
tri:
:
Allocator<GLMesh>:
:
PointerUpdater<GLMesh:
:
FacePointer> pu;
// nowthefppointercouldbenomorevalidduetoeventualre-allocationofthem.face
vcg:
:
tri:
:
Allocator<GLMesh>:
:
AddVertices(m,3);vcg:
:
tri:
:
Allocator<GLMesh>:
:
AddFaces(m,1,pu);
// checkifanupdateofthepointerisneededanddoit.
if(pu.NeedUpdate())
pu.Update(fp);
// 删除元素的代码如下
vcg:
:
tri:
:
Allocator<GLMesh>:
:
DeleteFace(m,m.face[0]);
// 拷贝网格的代码如下,GLMesh没有拷贝构造函数,也没有operator=
GLMeshm2;
vcg:
:
tri:
:
Append<GLMesh,GLMesh>:
:
MeshCopy(m2,m);
2)IO,读写PLY、OBJ等网格文件;
IO,读写网格文件示例代码如下(API文档主页 Loadingandsavingmeshes, 在线版 ):
// Mesh文件一般至少包含顶点数组信息,还可以包含连接信息(三角形)、顶点法向量、顶点颜色、面颜色、
// 面法向量、纹理坐标等等属性,
用mask的二进制位来标记或控制读取或写入了Mesh文件的哪些属性 //见vcg:
:
tri:
:
io:
:
Mask,
读取PLY需要包含文件"vcglib/wrap/ply/plylib.cpp"(见这里)
// 头文件包含:
#include"wrap/io_trimesh/import.h"
#include"wrap/io_trimesh/export.h"
GLMeshm;
int mask; // 读取PLY文件,并检查返回值,参数mask为可选,mask是返回参数:
读入了哪些属性
if(vcg:
:
tri:
:
io:
:
ImporterPLY:
:
Open(m, "file_to_open.ply",mask) !
=vcg:
:
ply:
:
E_NOERROR)
{
std:
:
cout << "Load PLY file ERROR\n";
}
// somemodification to m and mask ...
// 保存 PLY 文件,mask 是输入参数,控制 m 的哪些属性被写入到文件
vcg:
:
tri:
:
io:
:
ExporterPLY<GLMesh>:
:
Save(m,"file_to_save.ply",mask);
// 读取或写入OBJ文件的代码,mask作用同上
if(vcg:
:
tri:
:
io:
:
ImporterOBJ<GLMesh>:
:
Open(m, "file_to_open.obj",mask)!
=vcg:
:
tri:
:
io:
:
ImporterOBJ<GLMesh>:
:
E_NOERROR)
{
std:
:
cout << "Load OBJ file ERROR\n";
}
// some modification to m and mask ...
vcg:
:
tri:
:
io:
:
ExporterOBJ<GLMesh>:
:
Save(m, "file_to_save.obj",mask);
// 读取、写入网格文件,将根据文件扩展名自动匹配文件格式---------------------------------------
int oerr=vcg:
:
tri:
:
io:
:
Importer<GLMesh>:
:
Open(m,"file_to_open.off",