本文是笔者修读《计算机图形学》时,课程作业的副产物。
本文的大部分内容是官方文档的“Using and understanding OpenMesh”一章的翻译。
简介
OpenMesh是一个通用且高效的库,它提供了用于表示和操作多边形网格的数据结构。它允许用户根据应用程序的具体需要创建自定义的网格类型。用户既可以提供自己的数据结构来表示顶点、边和面,也可以方便地使用OpenMesh的预定义结构。此外,OpenMesh还提供了动态属性(properties),允许用户在运行期间将数据附加到网格中。
OpenMesh的官方文档:此处
OpenMesh的特点:
- 不仅限于三角形网格,还可以处理各种多边形网格。
- 可以方便地抽象顶点、半边、边、面等。
- 可以高效地访问顶点的1-邻接顶点。
- 可以处理非流形网格。
OpenMesh的C++实现尤其注重灵活性、高效性和类型安全。
“半边”数据结构
多边形网格的表示有许多方式。OpenMesh使用了一种“半边”数据结构。顾名思义,半边就是边的一半。OpenMesh使用如下的规则记录顶点、边和面:
- 每个顶点记录所有从自己出发的半边。(1)
- 每个面记录一条自己周围的半边。多个面就可记录多条半边。(2)
- 每条半边记录:
- 自己指向的顶点 (3)
- 自己归属的面 (4)
- 按照逆时针方向排列的自己归属的面上的下一条半边 (5)
- 和自己贴着的那条半边(这两条半边共同组成了一条边) (6)
- (可选)自己归属的面上相对于自己的上一条半边 (7)
这样,通过任意的半边、顶点和面都可以循环地遍历到整个网格。这种循环工具被封装在所谓的Circulator中。
相比其他的网格描述方式,半边数据结构需要消耗更多内存,但获得了如下的优势:
- 可以方便地在同一个网格中混合使用包含不同定点数的面。
- 能够方便地表示顶点、边和面。
- 围绕一个顶点循环以获得其1-邻接顶点是多边形网格上许多种算法的一个重要操作。对于基于面的结构,这会导致许多分支结构,基于半边结构的遍历可以在恒定的时间内完成且没有条件分支。
网格的Iterator和Circulator
迭代器
OpenMesh提供了针对顶点、半边、面提供了线性迭代器,可用于方便地遍历这些对象。
所有的迭代器都定义在OpenMesh::Iterators
命名空间中。这些迭代器都是模板类,期望一个自定义的网格类型作为模板参数被完全指定。因此你应该使用网格本身提供的迭代器类型,即MyMesh::VertexIter
而不是OpenMesh::Iterators::VertexIterT<MyMesh>
。
使用例子:
MyMesh mesh;
// 遍历所有顶点
for (MyMesh::VertexIter v_it=mesh.vertices_begin(); v_it!=mesh.vertices_end(); ++v_it)
...; // do something with *v_it, v_it->, or *v_it
// 遍历所有半边
for (MyMesh::HalfedgeIter h_it=mesh.halfedges_begin(); h_it!=mesh.halfedges_end(); ++h_it)
...; // do something with *h_it, h_it->, or *h_it
// 遍历所有边
for (MyMesh::EdgeIter e_it=mesh.edges_begin(); e_it!=mesh.edges_end(); ++e_it)
...; // do something with *e_it, e_it->, or *e_it
// 遍历所有面
for (MyMesh::FaceIter f_it=mesh.faces_begin(); f_it!=mesh.faces_end(); ++f_it)
...; // do something with *f_it, f_it->, or *f_it
这些迭代器对应的const
版本分别是:
ConstVertexIter
,ConstHalfedgeIter
,ConstEdgeIter
,ConstFaceIter
.
使用迭代器时,处于性能方面的考虑,建议使用前置自增写法即++it
而不是it++
。
为了获取迭代器指向的对象,应该简单地对迭代器解引用。早期版本使用的是迭代器的handle()
成员,但这种用法已经废弃了。
跳跃迭代器
OpenMesh使用的是惰性删除,如果你在删除一个对象之后,还没有执行garbage_collection()
,那么这个对象实际上还是存在着的,此时迭代器的行为会有不同。如果进行过垃圾回收,元素将被重排,迭代器就会给出连续的结果了。
-
标准的迭代器仍然会列举出已被删除但尚未被垃圾回收的对象。
-
跳跃迭代器(Skipping Iterators)会跳过这些项。所有的迭代器都有对应的跳跃迭代器,它们可以通过调用这些函数来获得:
vertices_sbegin()
,edges_sbegin()
,halfedges_sbegin()
,faces_sbegin()
这些跳跃迭代器的尾后迭代器仍然是标准形式。
Circulators
OpenMesh还提供了所谓的Circulators,可用于列举与同一类型或另一类型的项目相邻的项目的方法。例如,VertexVertexIter
允许列举紧邻一个顶点的所有顶点。类似地,FaceHalfedgeIter
枚举了属于一个面的所有半边。
一般来说,CenterItem_AuxiliaryInformation_TargetItem_Iter
指定了一个Circulator,它枚举了一个给定中心项周围的所有目标项。
关于顶点的Circulators
VertexVertexIter
: 遍历所有邻接顶点VertexIHalfedgeIter
遍历所有指向自己的半边VertexOHalfedgeIter
:遍历所有从自身出发的半边VertexEdgeIter
:遍历所有邻接的边VertexFaceIter
:遍历所有邻接的面
关于面的Circulators
FaceVertexIter
:遍历这个面上所有顶点FaceHalfedgeIter
:遍历属于这个面的所有半边FaceEdgeIter
:遍历这个面的所有边FaceFaceIter
:遍历所有和自身相邻的面。
其他
HalfedgeLoopIter
:遍历一个半边序列(如围绕一个面的所有半边)
OpenMesh提供如下的一系列函数来获得针对不同对象的Circulators:
/**************************************************
* 顶点 circulators
**************************************************/
// Get the vertex-vertex circulator (1-ring) of vertex _vh
VertexVertexIter OpenMesh::PolyConnectivity::vv_iter (VertexHandle _vh);
// Get the vertex-incoming halfedges circulator of vertex _vh
VertexIHalfedgeIter OpenMesh::PolyConnectivity::vih_iter (VertexHandle _vh);
// Get the vertex-outgoing halfedges circulator of vertex _vh
VertexOHalfedgeIter OpenMesh::PolyConnectivity::voh_iter (VertexHandle _vh);
// Get the vertex-edge circulator of vertex _vh
VertexEdgeIter OpenMesh::PolyConnectivity::ve_iter (VertexHandle _vh);
// Get the vertex-face circulator of vertex _vh
VertexFaceIter OpenMesh::PolyConnectivity::vf_iter (VertexHandle _vh);
/**************************************************
* 面 circulators
**************************************************/
// Get the face-vertex circulator of face _fh
FaceVertexIter OpenMesh::PolyConnectivity::fv_iter (FaceHandle _fh);
// Get the face-halfedge circulator of face _fh
FaceHalfedgeIter OpenMesh::PolyConnectivity::fh_iter (FaceHandle _fh);
// Get the face-edge circulator of face _fh
FaceEdgeIter OpenMesh::PolyConnectivity::fe_iter (FaceHandle _fh);
// Get the face-face circulator of face _fh
FaceFaceIter OpenMesh::PolyConnectivity::ff_iter (FaceHandle _fh);
可以看到这些函数都定义在OpenMesh::PolyConnectivity
中。
标准的Circulator可能是乱序的,如果你希望获得严格按照某个方向遍历的Circulator,可以在上面函数的下划线_
后面加上ccw
或cw
来得到。注意这种顺序Circulator可能会稍慢。
VertexVertexIter vvit = mesh.vv_iter(some_vertex_handle); // 最快(乱序)
VertexVertexCWIter vvcwit = mesh.vv_cwiter(some_vertex_handle); // 顺时针方向
VertexVertexCCWIter vvccwit = mesh.vv_ccwiter(some_vertex_handle); // 逆时针方向
这两种Circulator还可以通过构造函数互相转换。转换后的Circulator仍然指向同一个对象,但推进的顺序颠倒过来了。
所有Circulator同样存在const
版本。它们的类型名对应地加上Const
前缀,获取的函数对应地加上c
前缀:
ConstVertexVertexIter cvvit = mesh.cvv_iter(some_vertex_handle);
构建自己的Mesh类
通过以下四个步骤来构建自己的Mesh类:
- 在三角形网格和多边形网格之间选择一个。
- 确定mesh kernel
- 使用
Trait
类来参数化网格。 - 通过自定义属性动态地将数据绑定到网格或网格的组成成分(顶点、(半)边、面)
选择三角形网格还是多边形网格?
你应该尽量优先选择三角形网格。渲染三角形要比渲染任意多边形快得多。而且某些算法只针对三角形网格实现。
参见:
选择合适的Kernel
网格kernel指定了网格实体(顶点、(半)边、面)的内部存储方式。事实上,这些实体被保存在所谓的属性(properties)中。一个属性本身提供了一个类似数组的接口。kernel定义了相应的处理类型,也就是项目之间相互引用的方式。因为属性有一个类似数组的接口,所以句柄在内部被表示为索引。
默认的kernel是ArrayKernelT
。这对大多数情况来说是合适的。但根据应用的不同,有些其他kernel会更好。
参见:
Mesh Traits
前两个小节描述的是现成mesh和kernel,而本节描述的是用户自定义的东西。
最后的 MyMesh
数据结构将会提供以下类型:
- 点和标量类型:
MyMesh::Point
andMyMesh::Scalar
. - 网格实体:
MyMesh::Vertex
,MyMesh::Halfedge
,MyMesh::Edge
,MyMesh::Face
. - 句柄类型:
MyMesh::VertexHandle
,MyMesh::HalfedgeHandle
,MyMesh::EdgeHandle
,MyMesh::FaceHandle
.
其中,只有句柄类型是固定的,其他类型都可以定制。每个网格类型(见预定义的网格类型)都可以使用所谓的trait
类来参数化。通过使用这个机制,用户可以
- 修改坐标类型
MyMesh::Point
和它对应的标量类型MyMesh::Scalar == MyMesh::Point::value_type
; - 修改normal类型
MyMesh::Normal
; - 修改颜色类型
MyMesh::Color
; - use predefined attributes like normal vector, color, texture coordinates, ... for the mesh items.
- add arbitrary classes to the mesh items.
所有这些修改都封装在MyTraits
类中,该类将被用于特化网格类的模板:
struct MyTraits {
// 你的修改
};
typedef PolyMesh_ArrayKernelT<MyTraits> MyMesh;
本节的剩余内容讲述了如何编写MyTraits
类,此处略去。
自定义属性
本节介绍:
- 如何添加和移除自定义属性。
- 如何设置或获得一个自定义属性的值。
待补
标签:半边,迭代,OpenMesh,网格,mesh,初识,MyMesh From: https://www.cnblogs.com/eslzzyl/p/17233750.html