类 Surface_mesh 是半边数据结构的实现,可用于表示多面体表面。它是CGAL包 Halfedge Data Structures 和 3D Polyhedral Surface 的替代品。主要区别在于它是基于索引而非基于指针的。此外,向顶点、半边、边和面添加信息的机制要简单得多,并且在运行时而不是在编译时完成。
由于数据结构使用整数索引作为顶点、半边、边和面的描述符,因此它的内存占用比基于64位指针的版本要低。由于索引是连续的,因此它们可以用作存储属性的向量的索引。
当元素被移除时,它们只被标记为已移除,必须调用垃圾收集函数才能真正移除它们。
类Surface_mesh可以通过其类成员函数以及通过包CGAL和Boost Graph Library中描述的BGL API使用,因为它是MutableFaceGraph和FaceListGraph概念的模型。因此,可以在表面网格上应用三角表面网格简化、三角表面网格分割和三角表面网格变形等算法。
MutableFaceGraph:MutableFaceGraph是一个可变图模型,它允许修改图的结构和属性。MutableFaceGraph概念模型定义了添加、删除和修改图中的顶点、半边、面和边的方法。它还定义了检查两个顶点之间是否存在边、检查两个面之间是否存在共享边等操作。MutableFaceGraph概念模型适用于需要动态修改图的形状和结构的算法。
FaceListGraph:FaceListGraph是一个只读图模型,它提供了只读访问图中的顶点、半边、面和边的方法。FaceListGraph概念模型适用于需要遍历图中的元素但不修改它们的情况。它提供了获取图中所有顶点、所有面、所有半边等的方法,但不支持添加、删除或修改这些元素。
1、使用
主类 Surface_mesh 提供了四个嵌套类,表示半边数据结构的基本元素:Surface_mesh::Vertex_index、Surface_mesh::Halfedge_index、Surface_mesh::Face_index、 Surface_mesh::Edge_index。
这些类型只是整型的包装器,其主要目的是保证类型安全。它们是默认可构造的,会产生无效元素。可以通过一组不保持连通性的低级函数向 Surface_mesh 添加和删除新元素。一个例外是 Surface_mesh::add_face(),它试图向网格(由一系列顶点定义)添加新面,如果操作在拓扑上无效,则失败。在这种情况下,返回的 Face_index 是 Surface_mesh::null_face()。
由于 Surface_mesh 是基于索引的 Vertex_index,Halfedge_index,Edge_index 和 Face_index 没有成员函数来访问连接或属性。必须使用创建它们的 Surface_mesh 实例的函数来获取此信息。
2、连通性
表面网格是一种以边为中心的数据结构,能够维护顶点、边和面的关联信息。每条边由两个方向相反的半边表示。每个半边存储一个关联面和一个关联顶点的引用。此外,它还存储一个与其关联面关联的下一个和上一个半边的引用。对于每个面和每个顶点,都存储一个关联半边。半边不存储相反半边的索引,因为 Surface_mesh 在内存中连续存储相反半边。
下图说明了允许在表面网格中导航的功能:Surface_mesh::opposite()、Surface_mesh::next()、Surface_mesh::prev()、Surface_mesh::target()和Surface_mesh::face()。此外,函数Surface_mesh::halfedge()允许获取与顶点和面相关联的半边。或者,可以使用在 Boost Graph Library 和igraph包中定义的同名自由函数。
面上的半边形成一个循环。根据我们从哪一侧看表面,半边的顺序似乎是顺时针或逆时针方向。在本手册中,当我们谈到遍历的方向时,我们看表面时,面周围的半边是逆时针方向的,如图27.1所示。
连接不允许表示有孔的面。
3、范围和迭代
Surface_mesh 提供迭代器范围来枚举所有顶点、半边、边和面。它提供成员函数来返回与 Boost.Range 库兼容的元素范围。
4、循环迭代器
CGAL 和 Boost Graph Library 中提供了围绕面和顶点的循环器作为类模板。
围绕面的循环器基本上调用 Surface_mesh::next(),以便按逆时针方向从半边到半边围绕面,并且在被解引用时返回半边或关联的顶点或对面的面。
CGAL::Halfedge_around_face_circulator<Mesh>
CGAL::Vertex_around_face_circulator<Mesh>
CGAL::Face_around_face_circulator<Mesh>
围绕目标顶点的循环器基本上调用 Surface_mesh::opposite(Surface_mesh::next()),以便按顺时针方向围绕相同的目标顶点从半边到半边。
CGAL::Halfedge_around_target_circulator<Mesh>
CGAL::Vertex_around_target_circulator<Mesh>
CGAL::Face_around_target_circulator<Mesh>
所有循环器都实现了 双向循环器模型。除此之外,它们还支持转换为 bool,以便更方便地检查是否为空。
5、特性
Surface_mesh 提供了一种机制来在运行时为顶点、半边、边和面指定新属性。每个属性都由一个字符串和其键类型标识。给定属性的所有值都作为连续的内存块存储。每当向数据结构中添加具有键类型的新元素或调用函数 Surface_mesh::collect_garbage() 时,对属性的引用将被无效化。删除元素后,元素的属性将继续存在。通过无效的元素访问属性将导致未定义的行为。
默认维护一个属性,即 "v:point"。当通过 Surface_mesh::add_vertex() 向数据结构添加新点时,必须提供此属性的值。可以直接使用 Surface_mesh::points() 或 Surface_mesh::point(Surface_mesh::Vertex_index v) 访问此属性。
当元素被删除时,它只是被标记为已删除,并在调用 Surface_mesh::collect_garbage() 时被真正删除。垃圾回收也会真正删除这些元素的属性。
连接性也存储在属性中,即名为 "v:connectivity"、"h:connectivity" 和 "f:connectivity" 的属性。删除元素的标记与此非常相似,我们有 "v:removed"、"e:removed" 和 "f:removed"。
提供方便的函数来删除用户添加的属性映射,要么通过索引类型 (Surface_mesh::remove_property_maps<I>()),要么全部删除 (Surface_mesh::remove_all_property_maps())。
要清除网格,您可以选择获取所有已添加的属性映射都被移除的网格 (Surface_mesh::clear()),或者保留它们 (Surface_mesh::clear_without_removing_property_maps())。请注意,在这两种情况下,"v:point" 属性映射都将被保留,保留对其的引用是安全的。
6、边界
半边存储了对面的引用,即它的关联面。如果半边h没有关联面,即sm.face(h) == Surface_mesh::null_face(),则它在边界上。如果半边的任何一半在边界上,则边在边界上。如果顶点的任何一半在边界上,则顶点在边界上。
一个顶点只有一个关联的半边。如果用户注意到了关联的半边是边界半边,则如果顶点在边界上,则不需要在is_border()函数中查看所有关联的半边。为了只检查关联的半边是否在边界上,必须使用check_all_incident_halfedges = false调用函数Surface_mesh::is_border(Vertex_index v, bool check_all_incident_halfedges = true)。
用户负责在应用可能使此属性无效的操作后,正确设置与顶点关联的半边。函数 Surface_mesh::set_vertex_halfedge_to_border_halfedge(Vertex_index v)、Surface_mesh::set_vertex_halfedge_to_border_halfedge(Halfedge_index h) 和 Surface_mesh::set_vertex_halfedge_to_border_halfedge() 分别用于为单个顶点 v、h 表面边界上的所有顶点以及曲面网格的所有顶点设置边界半边。
7、表面网格和BGL的 API(和boost库的联合使用,不了解也可以)
类 Surface_mesh 是 Boost Graph Library 中定义的概念 IncidenceGraph 的模型。这使得可以直接在表面网格上应用 Dijkstra 最短路径或 Kruskal 最小生成树等算法。
例如,BGL API的类型和自由函数都具有类似的类型或成员函数
BGL | Surface_mesh | Remark | |
boost::graph_traits<G>::vertex_descriptor | Surface_mesh::Vertex_index | ||
boost::graph_traits<G>::edge_descriptor | Surface_mesh::Edge_index | ||
vertices(const G& g) | sm.vertices() | ||
edges(const G& g) | sm.edges() | ||
vd = source(ed,g) | vd = sm.source(ed) | ||
na | n = sm.number_of_vertices() | counts non-deleted vertices and has no BGL equivalent | |
n = num_vertices(g) | n = sm.number_of_vertices() + sm.number_of_removed_vertices() | counts used and deleted vertices in order to have an upper bound on the largest vertex index used |
在不考虑删除的顶点的情况下返回顶点数量会更好,但这会与底层的顶点/边索引映射产生严重冲突。索引映射将不再落在许多算法中假设的[0,num_vertices(g)]范围内。
类 Surface_mesh 也是CGAL和 Boost Graph Library 中定义的概念 MutableFaceGraph 的模型。这个概念和类似的概念,如 HalfedgeGraph,通过引入半边和面的概念,以及围绕面和顶点的半边循环,细化了 BGL 的图概念。同样,也有类似的类型和函数,例如
BGL | Surface_mesh |
boost::graph_traits<G>::halfedge_descriptor | Surface_mesh::Halfedge_index |
boost::graph_traits<G>::face_descriptor | Surface_mesh::Face_index |
halfedges(const G& g) | sm.halfedges() |
faces(const G& g) | sm.faces() |
hd = next(hd, g) | hd = sm.next(hd) |
hd = prev(hd, g) | hd = sm.prev(hd) |
hd = opposite(hd,g) | hd = sm.opposite(hd) |
hd = halfedge(vd,g) | hd = sm.halfedge(vd) |
包CGAL和Boost Graph Library中描述的BGL API使我们能够编写在曲面网格上运行的几何算法,这些算法适用于任何FaceGraph或MutableFaceGraph模型。这是表面网格简化、变形或分割算法对 Surface_mesh 和 Polyhedron_3 有效。
BGL算法使用属性映射来将信息与顶点和边相关联。一个重要的属性是索引,对于图g的顶点,索引是一个介于0和num_vertices(g)之间的整数。这允许算法创建一个适当大小的向量,以存储每个顶点信息。例如,一个布尔值,用于存储在图遍历过程中是否已经访问过某个顶点。
BGL检索图g的顶点索引属性映射的方法是vipm = get(boost::vertex_index, g),然后依次使用get(vipm, vd)检索顶点描述符vd的索引,直接获取顶点索引的方法是get(vertex_index, g, vd)。
8、I/O
作为FaceGraph的模型(请参见剖面曲面网格和BGL API),可以使用多种不同的文件格式读取和写入CGAL::Surface_Mesh。有关更多信息,请参阅C++图形库和Boost Graph Library包的I/O函数,以及多边形网格处理包的I/O函数。
此外,此软件包还提供了来自C++标准库和Boost Graph Library软件包的I/O函数针对Surface_mesh的特定重载。这使得可以直接从内部属性映射中读取/写入,有关更多信息,请参阅I/O函数。
9、内存管理
内存管理是半自动的。随着向结构中添加更多元素,内存会增长,但当删除元素时,内存不会缩小。
当您添加元素并且底层向量的容量耗尽时,向量会重新分配内存。由于描述符基本上是索引,因此在重新分配后它们指向相同的元素。
当你删除一个元素时,它只会被标记为已删除。在内部,它会被放在一个空闲列表中,当你向曲面网格添加元素时,如果空闲列表不为空,它们将从空闲列表中取出。
对于所有元素,我们提供了一个函数来获取已使用的元素数量,以及已使用和已删除的元素数量。对于顶点,函数分别是 Surface_mesh::number_of_vertices() 和 Surface_mesh::number_of_removed_vertices()。第一个函数与 BGL 包的自由函数 num_vertices(const G++) 略有不同。由于 BGL 风格的算法使用元素的索引来访问大小为 num_vertices() 的临时向量中的数据,因此该函数必须返回一个大于元素最大索引的数字。
诸如 Surface_mesh::Vertex_iterator 之类的迭代器仅枚举未标记为已删除的元素。
要真正减少已使用的内存,必须调用 Surface_mesh::collect_garbage()。垃圾回收也会压缩与曲面网格关联的属性。
但是请注意,通过垃圾回收,元素会获得新的索引。如果你保留了顶点描述符,它们很可能不再指向正确的顶点。
10、显示表面网格
通过调用getRoot::draw<SM>(),可以显示表面网格,此函数打开一个新窗口,显示给定的表面网格。调用此函数是阻塞的,即用户关闭窗口后程序会立即继续。
11、实施细则
我们选择boost::uint32_t作为索引的整数类型。在64位操作系统上,它们的大小只有指针的一半。它们仍然允许具有20亿个元素的网格。
我们使用std::vector来存储属性。因此,通过访问属性映射的第0个元素的地址,您可以访问底层原始数组。这可能很有用,例如,可以将点数组传递给OpenGL。
我们使用一个自由列表来存储被删除的元素。这意味着当一个顶点被删除,并且稍后调用 add_vertex 时,被删除元素的内存会被重用。这尤其意味着第 n 个插入的元素不一定是索引 n-1,并且在遍历元素时,它们将不会按照插入顺序被枚举。
12、实施历史
该软件包参考Openmesh.
BGL是Boost Graph Library的缩写,它是一个C++库,提供了许多图算法和数据结构。BGL是Boost库的一部分,是一个经过广泛测试的可靠库,适用于许多不同的应用程序和领域。