vcglib 说明(转载)

先来看看 VCGlib 能做什么

  • 最基本的,它提供 Mesh(triangular mesh,tetrahedralmesh,三角网格或四面体网格)数据结构的定义,该数据结构支持对 Mesh数据的快速访问(拓扑信息、空间查询等)以及高效执行网格上算法;
  • 在 Mesh数据结构基础上,实现大量高效的网格算法,如网格修补、平滑、变形、曲率计算、细分、泊松盘采样、等值面计算等;
  • IO 支持,读写 PLY、OBJ、STL、3DS、OFF、DXF 等格式网格文件;
  • UI 支持,如 OpenGL 网格显示,Trackball 交互等。

VCGlib 的文档很简陋, 在线文档 并不是很全,可以自己用 Doxygen从下载的源代码生成 html API 文档 ,为此只需要(Windows 用户):

  1. 安装 Doxygen (Doxywizard) 和 Graphviz,将“Graphviz安装目录\Graphviz2.36\bin”添加到环境变量Path;
  2. 将本博客所附程序中的 “OpenGL Shadow\_Libs\vcglib\docs\doxyfile-all”文件拷贝到下载的 VCGlib 文件夹下的 “vcglib\docs\Doxygen” 下,这个文件是我配置好的 Doxygen配置文件;
  3. 用 Doxywizard 打开上一步拷贝的文件,点击“Run” 选项卡下的 “Run doxygen” 按钮,生成的 API 文档将位于“vcglib\docs\Doxygen\html-all” 下(html-all 文件夹将有 129M 大小)。

VCGlib 是纯头文件库,要 安装 只需将下载 VCGlib库目录添加到程序的头文件包含路径(有些IO函数如读写PLY需要包含相应.cpp文件)。

后面按照如下步骤讲解:
  1. 定义 Mesh 类型;
  2. 访问及指定 Mesh 的顶点、三角形(对三角网格,如果是四面体网格则是四面体,这里默认只讲三角网格)等数据;
  3. IO,读写 PLY、OBJ 等网格文件;
  4. 构造网格的拓扑信息,如顶点或三角形面法向量、三角形相邻三角形、边连接的三角形等信息;
  5. 网格处理,如法向量平滑、网格修补等。

定义 Mesh 类型的典型代码如下(API 文档主页 Basic Concepts,在线版 ):

#include "vcg/complex/complex.h"
// 类型声明
class MyVertex;class MyEdge;
class MyFace;
typedef vcg::UsedTypes<</FONT>
  vcg::Use::AsVertexType,
  vcg::Use  ::AsEdgeType,
  vcg::Use  ::AsFaceType >
MyUsedTypes;
// 顶点类型
class MyVertex : public vcg::Vertex<</FONT>MyUsedTypes,
  vcg::vertex::Coord3f,
  vcg::vertex::Normal3f,
  vcg::vertex::BitFlags > { };
// 边类型
class MyEdge : public vcg::Edge<</FONT>MyUsedTypes,
 vcg::edge::VertexRef,
 vcg::edge::EFAdj,
 vcg::edge::BitFlags > { };
// 面类型,三角形 class MyFace : public vcg::Face<</FONT>MyUsedTypes,  vcg::face::VertexRef,  vcg::face::Normal3f,  vcg::face::FFAdj,  vcg::face::BitFlags > { }; // 网格类型 typedef vcg::tri::TriMesh<</FONT>  std::vector,  std::vector,  std::vector > GLMesh;

抛开 MyUseTypes 不看,上面代码定义的网格类型为:

  • 网格包含属性:顶点、边、三角形数组(std::vector<>);
  • 每个顶点包含属性:空间坐标(3个float表示)、顶点法向量、标志位;
  • 每个边包含属性:顶点指针(指向该边的两个顶点)、边-面邻接信息、标志位;
  • 每个三角形面包含属性:顶点指针(指向该三角形的三个顶点)、面法向量、面-面邻接信息、标志位。

VCGlib 使用 Reference 数据结构,对每个边、面用指针记录其顶点、邻接面等信息,其他网格数据结构见wikipedia Polygon Mesh 条目

为了做到足够通用, VCGlib 使用了C++ templatemetaprogramming(模板元编程)方法 。上面代码中的MyVertex、MyEdge、MyFace、GLMesh等类型包含哪些属性(模板参数)、属性的顺序(模板参数顺序)都是可以根据需要随意指定的(当然,必须包含足够的属性以执行相应网格算法),一般来说,最好使顶点、边、面包含标志位属性(BitFlags),BitFlags指示该顶点、边、面是否可写、可读、已删除(为了效率,例如,删除顶点操作可能并不立即删除顶点数据,而仅仅打个标志位,待所有操作完成再更新顶点数据)等。不去深入VCGlib元编程机理(说实话我还没弄清楚),可选个数模板参数是通过默认模板参数实现的,vcg::Vertex/Edge/Face<>将继承其模板参数。

下面列举所有可选的模板参数:
  • 网格 vcg::tri::TriMesh<>最多可有四个参数:顶点容器、边容器、面容器、半边容器(vcg::HEdge<>);
  • 顶点 vcg::Vertex<>  可以包含的属性有:坐标、法向量、颜色、纹理坐标、标志位、网格质量(网格在该点出优劣评价指标)、曲率、半径、顶点-边邻接信息、顶点-面邻接信息、顶点-半边邻接信息,等(API文档 Modules 选项卡 Vertex Components, 在线版 );
  • 边 vcg::Edge<>  可以包含的属性有:顶点指针、颜色、标志位、网格质量、边-顶点邻接信息、边-边邻接信息、边-面邻接信息、边-半边邻接信息,等(API文档 Modules 选项卡 Edge Components, 在线版 );
  • 面 vcg::Face<>  可以包含的属性有:顶点指针、法向量、颜色、标志位、网格质量、顶点-面邻接信息、面-边邻接信息、面-面邻接信息,等(API 文档Modules 选项卡 Face Components, 在线版 )。
访问 Mesh 数据示例代码如下:
  // load mesh ...
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_t i=0; ii){
    if(vs[i].IsD()) continue;
    // do some thing for each vertex vs[i] ...
}
    // 除非已经删除了所有标记为已删除元素的存储数据,比如:
vcg::tri::Allocator::CompactVertexVector(mesh);
vcg::tri::Allocator::CompactEdgeVector(mesh);
vcg::tri::Allocator::CompactFaceVector(mesh);
for(size_t i=0; ii){
    // do some thing for each face fs[i] ...
}

填充(Fill)Mesh 数据的示例代码如下 (API 文档主页 Creating and destroyingelements, 在线版 ,代码摘自那里):

// VCGlib Reference 数据结构,依赖于指针,直接操作顶点、边、面数组 mesh.vert/edge/face 可能
// 产生 std::vector<> 存储重新分配,此时,相关指针将失效,vcg::tri::Allocator<> 处理这些问题
GLMesh m;
GLMesh::VertexIterator vi = vcg::tri::Allocator<<SPAN class=title>GLMesh>::AddVertices(m, 3);
GLMesh::FaceIterator fi = vcg::tri::Allocator<<SPAN class=title>GLMesh>::AddFaces(m, 1);
GLMesh::VertexPointer ivp[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, more compact, method for adding a single vertex
ivp[3]= &*vcg::tri::Allocator<<SPAN class=title>GLMesh>::AddVertex(m,GLMesh::CoordType(1.0f,1.0f,0.0f));
// Alternative, method for adding a single face (once you have the vertex pointers)
vcg::tri::Allocator<<SPAN class=title>GLMesh>::AddFace(m, ivp[1],ivp[0],ivp[3]);

// 同理,如果自己保存了顶点等数据指针,需要在修改顶点、边、面数组后更新该指针 --------------------
// a potentially dangerous pointer to a mesh element
GLMesh::FacePointer fp = &m.face[0];
vcg::tri::Allocator<<SPAN class=title>GLMesh>::PointerUpdater<<SPAN class=title>GLMesh::FacePointer> pu;
// now the fp pointer could be no more valid due to eventual re-allocation of the m.face
vcg::tri::Allocator<<SPAN class=title>GLMesh>::AddVertices(m,3);
vcg::tri::Allocator<<SPAN class=title>GLMesh>::AddFaces(m,1,pu);
// check if an update of the pointer is needed and do it.
if(pu.NeedUpdate()) pu.Update(fp);

// 删除元素的代码如下 --------------------------------------------------------------------
vcg::tri::Allocator<<SPAN class=title>GLMesh>::DeleteFace(m,m.face[0]);

// 拷贝网格的代码如下,GLMesh 没有拷贝构造函数,也没有 operator= -----------------------------
GLMesh m2;
vcg::tri::Append<<SPAN class=title>GLMesh,GLMesh>::MeshCopy(m2,m);

IO,读写网格文件示例代码如下(API 文档主页 Loading and savingmeshes, 在线版 ):

// 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"
GLMesh m; int mask;
// 读取 PLY 文件,并检查返回值,参数 mask 为可选,mask 是返回参数:读入了哪些属性
if( vcg::tri::io::ImporterPLY::Open(m, "file_to_open.ply", mask)
  != vcg::ply::E_NOERROR ) {
  std::cout <</FONT>< "Load PLY file ERROR\n";
}
 // some modification to m and mask ...
// 保存 PLY 文件,mask 是输入参数,控制 m 的哪些属性被写入到文件
vcg::tri::io::ExporterPLY<<SPAN class=attribute>GLMesh>::Save(m, "file_to_save.ply", mask);
// 读取或写入 OBJ 文件的代码,mask 作用同上
if( vcg::tri::io::ImporterOBJ<<SPAN class=title>GLMesh>::Open(m, "file_to_open.obj", mask)
  != vcg::tri::io::ImporterOBJ<<SPAN class=title>GLMesh>::E_NOERROR ) {
    std::cout <<SPAN class=title><</SPAN> "Load OBJ file ERROR\n";
}
  // some modification to m and mask ...
vcg::tri::io::ExporterOBJ<<SPAN class=attribute>GLMesh>::Save(m, "file_to_save.obj", mask);
// 读取、写入网格文件,将根据文件扩展名自动匹配文件格式 ---------------------------------------
int oerr = vcg::tri::io::Importer<<SPAN class=title>GLMesh>::Open(m, "file_to_open.off", mask);
if( oerr != 0 ){
  std::cout <<SPAN class=title><</SPAN> "Load mesh file ERROR: "
    << vcg::tri::io::Importer<<SPAN class=attribute>GLMesh>::ErrorMsg(oerr) <<SPAN class=title><</SPAN> '\n';
}
  // some modification to m and mask ...
int serr = vcg::tri::io::Exporter<<SPAN class=attribute>GLMesh>::Save(m, "file_to_save.3ds", mask);
if( serr != 0 ){
  std::cout <<SPAN class=title><</SPAN> "Save mesh file ERROR: "
    << vcg::tri::io::Exporter<<SPAN class=attribute>GLMesh>::ErrorMsg(oerr) <<SPAN class=title><</SPAN> '\n';
}

构造网格拓扑信息示例代码如下 (API 文档主页 Adjacency andTopology, 在线版 ):

  // load mesh ...
vcg::tri::UpdateNormal<<SPAN class=title>GLMesh>::PerFaceNormalized(mesh); // 计算顶点法向量,并单位化
vcg::tri::UpdateNormal<<SPAN class=title>GLMesh>::PerVertexNormalized(mesh); // 计算面法向量,并单位化
vcg::tri::UpdateTopology<<SPAN class=title>GLMesh>::FaceFace(mesh); // 计算面-面邻接信息
vcg::tri::UpdateTopology<<SPAN class=title>GLMesh>::AllocateEdge(mesh); // 计算边-面邻接信息,需要面-面信息
vcg::Matrix44f mat(&glm::translate(glm::vec3(1,2,3))[0][0]);
vcg::tri::UpdatePosition<<SPAN class=title>GLMesh>::Matrix(mesh, mat, true); // 更新顶点位置,并更新法向量
// 在调用 UpdateTopology<>::FaceFace() 和 UpdateTopology<>::AllocateEdge() 后就构造了边到面
// 的信息,对于 manifold 网格,每个边必连接两个三角形面,下面代码对边 i 查找其连接的面 fa 和 fb
int i=0; GLMesh::EdgeType& e = mesh.edge[i];
GLMesh::FaceType* fa = e.EFp();
GLMesh::FaceType* fb = fa->FFp(e.EFi());

在准备这篇博客之初,研究 VCGlib 时,发现了 VCGlib 的一个 BUG,已经报告给开发者并得到确认( 见这里 ,看看时间,发现这篇博客因为一些原因拖了20多天...)。

网格处理示例代码如下:
vcg::tri::Clean<<SPAN class=title>GLMesh>::RemoveDuplicateVertex(mesh); // 去除重合的顶点
vcg::tri::Smooth<<SPAN class=title>GLMesh>::VertexNormalLaplacian(mesh, 5); // 平滑顶点法向量
float maxSizeHole = 2.0f; // fill 所有直径小于 maxSizeHole 的洞
vcg::tri::Hole<<SPAN class=title>GLMesh>::EarCuttingIntersectionFill
    <<SPAN class=title>vcg::tri::SelfIntersectionEar>>(mesh, maxSizeHole, false);
进一步学习的资源:
  • 下载的 VCGlib 源代码 “vcglib\apps\sample” 下的官方示例代码;
  • 源代码,结合 API 文档;
  • 基于 VCGlib 的软件 MeshLab ,可以用于网格文件处理。
  • 0
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值