首页 > 其他分享 >初识OpenMesh

初识OpenMesh

时间:2023-03-19 17:45:56浏览次数:45  
标签:半边 迭代 OpenMesh 网格 mesh 初识 MyMesh

本文是笔者修读《计算机图形学》时,课程作业的副产物。

本文的大部分内容是官方文档的“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,可以在上面函数的下划线_后面加上ccwcw来得到。注意这种顺序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类:

  1. 在三角形网格和多边形网格之间选择一个。
  2. 确定mesh kernel
  3. 使用Trait类来参数化网格。
  4. 通过自定义属性动态地将数据绑定到网格或网格的组成成分(顶点、(半)边、面)

选择三角形网格还是多边形网格?

你应该尽量优先选择三角形网格。渲染三角形要比渲染任意多边形快得多。而且某些算法只针对三角形网格实现。

参见:

OpenMesh::PolyMeshT

OpenMesh::TriMeshT

选择合适的Kernel

网格kernel指定了网格实体(顶点、(半)边、面)的内部存储方式。事实上,这些实体被保存在所谓的属性(properties)中。一个属性本身提供了一个类似数组的接口。kernel定义了相应的处理类型,也就是项目之间相互引用的方式。因为属性有一个类似数组的接口,所以句柄在内部被表示为索引。

默认的kernel是ArrayKernelT。这对大多数情况来说是合适的。但根据应用的不同,有些其他kernel会更好。

参见:

Mesh Kernels

Mesh Traits

前两个小节描述的是现成mesh和kernel,而本节描述的是用户自定义的东西。

最后的 MyMesh 数据结构将会提供以下类型:

  • 点和标量类型: MyMesh::Point and MyMesh::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

相关文章

  • Vue2入门之超详细教程三-初识模板语法
    1、简介模板语法就是按照固定的模板去书写代码,分为插值语法和指令语法。差值语法:功能:用于解析标签体内容写法:{{xxxx}},xxx是js表达式,且可以读取......
  • 初识c语言
    1.程序语言C语言是目前极为流行的一种计算机程序设计语言,它既具有高级语言的功能,又具有汇编语言的一些特性,且支持ANSIC。C语言的特点:通用性及易写易读,是一种结构化程序......
  • 初识 Vue.js
    Vue.js的概述1Web前端技术的发展1.1Ajax......
  • 1 初识HTML
    1初识HTML1.1什么是HTMLHyperTextMarkupLanguage(超文本标记语言)超文本包括:文字、图片、音频、视频、动画等1.2HTML发展史1.2.1HTML5的优势1.世界知名......
  • Steamlit初识和安装入门
    Streamlit 是可以用于快速搭建Web应用的Python库。Streamlit基于tornado框架,封装了大量互动组件,同时也支持大量表格、图表、数据表等对象的渲染,并且支持栅格化响应式布......
  • 爬虫初识
    目录爬虫初识昨日回顾今日内容详细1爬虫介绍2request模块介绍3request发送get请求4request携带参数5url编码解码6携带请求头7发送post请求携带数据8自动登录携带......
  • 初识Node和内置模块
    初识Node与内置模块概述:了解Node.js,熟悉内置模块:fs模块、path模块、http模块初识Node.js浏览器中的JavaScript运行环境运行环境是指代码正常运行所需的必要环境对于C......
  • mDNS协议初识
    mDNS协议初识TRANSLATEwithxEnglishArabicHebrewPolishBulgarianHindiPortugueseCatalanHmongDawRomanianChineseSimplifiedHungarian......
  • 初识HTTP、HTTP报文
    初识HTTP1、HTTP是什么HyperTextTransferProtocol超文本传输协议HTML:超文本标记语言超文本:原先一个个单一的文本,通过超链接将其联系起来,由原先的单一的文本变成了......
  • 1. 初识Kubernetes
    WhatIsK8S狭义上讲,K8S是一个应用编排器。绝大部分情况下,它被用于 编排 容器化 的云原生微服务应用。具体的,它可以实现:自动化部署应用按需对应用进行扩容或缩容应......