CGAL笔记之单元格复合体和多面体篇—曲面网格
0、前言
类Surface_mesh是半边数据结构的实现,可用于表示多面体表面。它是 CGAL 包Halfedge Data Structures和3D Polyhedral Surface的替代品。主要区别在于它是基于索引的,而不是基于指针的。此外,向顶点、半边、边和面添加信息的机制要简单得多,并且是在运行时完成的,而不是在编译时完成的。
因为数据结构使用整数索引作为顶点、半边、边和面的描述符,所以它比基于 64 位指针的版本具有更低的内存占用。由于索引是连续的,因此它们可以用作存储属性的向量的索引。
当元素被移除时,它们只是被标记为已移除,必须调用垃圾回收函数才能真正移除它们。
该类Surface_mesh
可以通过其类成员函数以及CGAL 包和 Boost Graph Library中描述的 BGL API 来使用,因为它是concept
的 MutableFaceGraph
和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()
。
typedef Surface_mesh<Point> Mesh;
Mesh m;
Mesh::Vertex_index u = m.add_vertex(Point(0,1,0));
Mesh::Vertex_index v = m.add_vertex(Point(0,0,0));
Mesh::Vertex_index w = m.add_vertex(Point(1,0,0));
m.add_face(u, v, w);
Surface_mesh
与基于索引的Vertex_index、Halfedge_index、Edge_index
和Face_index
一样,没有成员函数来访问连通性或属性。Surface_mesh
必须使用创建它们的实例的函数来获取此信息。
1、示例
下面的示例展示了如何Surface_mesh
通过添加 2 个面来创建一个非常简单的网格,以及如何检查面是否已正确添加到网格中。
#include <CGAL/Simple_cartesian.h>
#include <CGAL/Surface_mesh.h>
typedef CGAL::Simple_cartesian<double> K;
typedef CGAL::Surface_mesh<K::Point_3> Mesh;
typedef Mesh::Vertex_index vertex_descriptor;
typedef Mesh::Face_index face_descriptor;
int main()
{
Mesh m;
// Add the points as vertices
vertex_descriptor u = m.add_vertex(K::Point_3(0,1,0));
vertex_descriptor v = m.add_vertex(K::Point_3(0,0,0));
vertex_descriptor w = m.add_vertex(K::Point_3(1,1,0));
vertex_descriptor x = m.add_vertex(K::Point_3(1,0,0));
m.add_face(u,v,w);
face_descriptor f = m.add_face(u,v,x);
if(f == Mesh::null_face())
{
std::cerr<<"The face could not be added because of an orientation error."<<std::endl;
f = m.add_face(u,x,v);
assert(f != Mesh::null_face());
}
return 0;
}
2、连通性
表面网格是一种以边为中心的数据结构,能够维护顶点、边和面的关联信息。每条边由两个方向相反的半边表示。每个半边存储对入射面和入射顶点的引用。此外,它还会将下一个和上一个半边事件的引用存储到其事件面。对于每个面和每个顶点,都会存储一个关联的半边。半边不存储相对半边的索引,因为Surface_mesh
在内存中连续存储相对半边。
下图说明了允许在表面网格中导航的函数:Surface_mesh::opposite()
、Surface_mesh::next()
、Surface_mesh::prev()
、Surface_mesh::target()
和Surface_mesh::face()
。此外,这些函数Surface_mesh::halfedge()
允许获得与顶点和面相关联的半边。或者,可以使用包CGAL 和 Boost Graph Library中定义的具有相同名称的函数。
入射到面的半边形成一个循环。根据我们从表面的哪一侧看,半边的序列似乎是顺时针或逆时针方向。当在本手册中我们谈到遍历的方向时,我们会看到表面周围的半边是逆时针方向的。
连通性不允许表示有孔的面。
3、范围和迭代器
Surface_mesh
提供迭代器范围以枚举所有顶点、半边、边和面。它提供返回与Boost.Range库兼容的元素范围的成员函数。
3.1 示例
以下示例显示如何从范围中获取迭代器类型、获取开始和结束迭代器的备选方案以及基于范围的循环的备选方案。
#include <vector>
#include <CGAL/Simple_cartesian.h>
#include <CGAL/Surface_mesh.h>
typedef CGAL::Simple_cartesian<double> K;
typedef CGAL::Surface_mesh<K::Point_3> Mesh;
typedef Mesh::Vertex_index vertex_descriptor;
typedef Mesh::Face_index face_descriptor;
int main()
{
Mesh m;
// u x
// +------------+
// | |
// | |
// | f |
// | |
// | |
// +------------+
// v w
// Add the points as vertices
vertex_descriptor u = m.add_vertex(K::Point_3(0,1,0));
vertex_descriptor v = m.add_vertex(K::Point_3(0,0,0));
vertex_descriptor w = m.add_vertex(K::Point_3(1,0,0));
vertex_descriptor x = m.add_vertex(K::Point_3(1,1,0));
/* face_descriptor f = */ m.add_face(u,v,w,x);
{
std::cout << "all vertices " << std::endl;
// The vertex iterator type is a nested type of the Vertex_range
Mesh::Vertex_range::iterator vb, ve;
Mesh::Vertex_range r = m.vertices();
// The iterators can be accessed through the C++ range API
vb = r.begin();
ve = r.end();
// or the boost Range API
vb = boost::begin(r);
ve = boost::end(r);
// or with boost::tie, as the CGAL range derives from std::pair
for(boost::tie(vb, ve) = m.vertices(); vb != ve; ++vb){
std::cout << *vb << std::endl;
}
// Instead of the classical for loop one can use
// the boost macro for a range
for(vertex_descriptor vd : m.vertices()){
std::cout << vd << std::endl;
}
// or the C++11 for loop. Note that there is a ':' and not a ',' as in BOOST_FOREACH
for(vertex_descriptor vd : m.vertices()){
std::cout << vd << std::endl;
}
}
return 0;
}
4、循环器
在 CGAL 包和 Boost Graph Library中,围绕面和顶点的循环器作为类模板提供。
围绕面的循环器基本上是Surface_mesh::next()
为了从 halfedge 到 halfedge 逆时针绕着面进行调用,当取消引用时返回 halfedge 或入射顶点或相反的面。
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>
所有循环器模型BidirectionalCirculator
。除此之外,它们还支持转换为bool
以更方便地检查是否为空。
1、示例
以下示例显示如何枚举给定半边的目标周围的顶点。第二个循环显示这些循环器类型中的每一个都带有一个等效的迭代器和一个创建迭代器范围的自由函数。
#include <CGAL/Simple_cartesian.h>
#include <CGAL/Surface_mesh.h>
#include <vector>
typedef CGAL::Simple_cartesian<double> K;
typedef CGAL::Surface_mesh<K::Point_3> Mesh;
typedef Mesh::Vertex_index vertex_descriptor;
typedef Mesh::Face_index face_descriptor;
int main()
{
Mesh m;
// u x
// +------------+
// | |
// | |
// | f |
// | |
// | |
// +------------+
// v w
// Add the points as vertices
vertex_descriptor u = m.add_vertex(K::Point_3(0,1,0));
vertex_descriptor v = m.add_vertex(K::Point_3(0,0,0));
vertex_descriptor w = m.add_vertex(K::Point_3(1,0,0));
vertex_descriptor x = m.add_vertex(K::Point_3(1,1,0));
face_descriptor f = m.add_face(u,v,w,x);
{
std::cout << "vertices around vertex " << v << std::endl;
CGAL::Vertex_around_target_circulator<Mesh> vbegin(m.halfedge(v),m), done(vbegin);
do {
std::cout << *vbegin++ << std::endl;
} while(vbegin != done);
}
{
std::cout << "vertices around face " << f << std::endl;
CGAL::Vertex_around_face_iterator<Mesh> vbegin, vend;
for(boost::tie(vbegin, vend) = vertices_around_face(m.halfedge(f), m);
vbegin != vend;
++vbegin){
std::cout << *vbegin << std::endl;
}
}
// or the same again, but directly with a range based loop
for(vertex_descriptor vd : vertices_around_face(m.halfedge(f), m)){
std::cout << vd << std::endl;
}
return 0;
}
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”属性映射将被保留并且保持对它的引用是安全的。
1、示例
此示例说明如何使用属性系统的最常见功能。
#include <string>
#include <CGAL/Simple_cartesian.h>
#include <CGAL/Surface_mesh.h>
typedef CGAL::Simple_cartesian<double> K;
typedef CGAL::Surface_mesh<K::Point_3> Mesh;
typedef Mesh::Vertex_index vertex_descriptor;
typedef Mesh::Face_index face_descriptor;
int main()
{
Mesh m;
vertex_descriptor v0 = m.add_vertex(K::Point_3(0,2,0));
vertex_descriptor v1 = m.add_vertex(K::Point_3(2,2,0));
vertex_descriptor v2 = m.add_vertex(K::Point_3(0,0,0));
vertex_descriptor v3 = m.add_vertex(K::Point_3(2,0,0));
vertex_descriptor v4 = m.add_vertex(K::Point_3(1,1,0));
m.add_face(v3, v1, v4);
m.add_face(v0, v4, v1);
m.add_face(v0, v2, v4);
m.add_face(v2, v3, v4);
// give each vertex a name, the default is empty
Mesh::Property_map<vertex_descriptor,std::string> name;
bool created;
boost::tie(name, created) = m.add_property_map<vertex_descriptor,std::string>("v:name","");
assert(created);
// add some names to the vertices
name[v0] = "hello";
name[v2] = "world";
{
// You get an existing property, and created will be false
Mesh::Property_map<vertex_descriptor,std::string> name;
bool created;
boost::tie(name, created) = m.add_property_map<vertex_descriptor,std::string>("v:name", "");
assert(! created);
}
// You can't get a property that does not exist
Mesh::Property_map<face_descriptor,std::string> gnus;
bool found;
boost::tie(gnus, found) = m.property_map<face_descriptor,std::string>("v:gnus");
assert(! found);
// retrieve the point property for which exists a convenience function
Mesh::Property_map<vertex_descriptor, K::Point_3> location = m.points();
for(vertex_descriptor vd : m.vertices()) {
std::cout << name[vd] << " @ " << location[vd] << std::endl;
}
std::vector<std::string> props = m.properties<vertex_descriptor>();
for(std::string p : props){
std::cout << p << std::endl;
}
// delete the string property again
m.remove_property_map(name);
return 0;
}
6、边界
半边存储对面的引用,即它的入射面。一条半边h
在边界上,如果它没有入射面,即如果。如果一条边的任何半边在边界上,则该边在边界上。如果顶点的任何关联半边在边界上,则该顶点在边界上。sm.face(h) == Surface_mesh::null_face()
一个顶点只有一个关联的半边。如果用户注意关联的半边是边界半边,如果顶点在边界上,则无需在函数中查看所有入射半边以查找顶点is_border()
。为了只检查关联的半边是否在边界上,Surface_mesh::is_border(Vertex_index v, bool check_all_incident_halfedges = true)
必须使用调用函数check_all_incident_halfedges = false
。
在应用了可能使此属性无效的操作后,用户负责正确设置与顶点关联的半边。函数Surface_mesh::set_vertex_halfedge_to_border_halfedge(Vertex_index v)
、Surface_mesh::set_vertex_halfedge_to_border_halfedge(Halfedge_index h)
和分别为单个顶点v、面边界上的所有顶点和表面网格的所有顶点hSurface_mesh::set_vertex_halfedge_to_border_halfedge()
设置边界半边。
7、Surface Mesh和 BGL API
该类是Boost Graph Library 中定义的Surface_mesh
概念模型。这使得可以直接在表面网格上IncidenceGraph
应用诸如Dijkstra 最短路径或Kruskal 最小生成树等算法。
BGL API 的类型和自由函数都有相似的类型或成员函数,例如
BGL | Surface_mesh | 评论 |
---|---|---|
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() | 计算未删除的顶点并且没有 BGL 等价物 |
n = num_vertices(g) | n = sm.number_of_vertices() + sm.number_of_removed_vertices() | 计算已使用和已删除的顶点,以便在使用的最大顶点索引上有一个上限 |
返回顶点数而不考虑删除的顶点会更好,但这会与底层顶点/边索引映射交互不良。[0,num_vertices(g))
索引映射将不再落在许多算法中假定的范围内。
该类也是包CGAL 和 Boost Graph Library 中定义的concept的Surface_mesh
的模型。这个和concept类型的 HalfedgeGraph
类似,通过引入半边和面的概念以及围绕面和顶点的半边循环来改进 BGL 的图形概念。同样,有类似的类型和,例如:MutableFaceGraph
和HalfedgeGraph
BGL | 表面网格 |
---|---|
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) |
ETC。 |
CGAL包和 Boost Graph Library中描述的 BGL API使我们能够编写在表面网格上运行的几何算法,适用于FaceGraph
, 或的任何模型MutableFaceGraph
。即表面网格简化、变形或分割算法适用于Surface_mesh
和Polyhedron_3
。
BGL 算法使用属性映射将信息关联到顶点和边。一个重要的属性是索引,一个图的顶点0
之间的整数。这允许算法创建适当大小的向量以存储每个顶点信息。例如,用于存储在图形遍历期间是否已访问顶点的布尔值。num_vertices(g)``g
检索图的顶点索引属性映射的 BGL 方法g
是vipm = get(boost::vertex_index, g)
,然后get(vipm, vd)
为了检索顶点描述符的索引vd
,它是get(vertex_index, g, vd)
直接获取顶点索引。
1、示例
第一个示例表明我们可以直接在表面网格上应用 Kruskal 的最小生成树算法。
#include <CGAL/Simple_cartesian.h>
#include <CGAL/Surface_mesh.h>
#include <boost/graph/kruskal_min_spanning_tree.hpp>
#include <iostream>
#include <fstream>
#include <list>
typedef CGAL::Simple_cartesian<double> Kernel;
typedef Kernel::Point_3 Point;
typedef CGAL::Surface_mesh<Point> Mesh;
typedef boost::graph_traits<Mesh>::vertex_descriptor vertex_descriptor;
typedef boost::graph_traits<Mesh>::vertex_iterator vertex_iterator;
typedef boost::graph_traits<Mesh>::edge_descriptor edge_descriptor;
void kruskal(const Mesh& sm)
{
// We use the default edge weight which is the squared length of the edge
std::list<edge_descriptor> mst;
boost::kruskal_minimum_spanning_tree(sm,
std::back_inserter(mst));
std::cout << "#VRML V2.0 utf8\n"
"Shape {\n"
" appearance Appearance {\n"
" material Material { emissiveColor 1 0 0}}\n"
" geometry\n"
" IndexedLineSet {\n"
" coord Coordinate {\n"
" point [ \n";
vertex_iterator vb,ve;
for(boost::tie(vb, ve) = vertices(sm); vb!=ve; ++vb){
std::cout << " " << sm.point(*vb) << "\n";
}
std::cout << " ]\n"
" }\n"
" coordIndex [\n";
for(std::list<edge_descriptor>::iterator it = mst.begin(); it != mst.end(); ++it)
{
edge_descriptor e = *it ;
vertex_descriptor s = source(e,sm);
vertex_descriptor t = target(e,sm);
std::cout << " " << s << ", " << t << ", -1\n";
}
std::cout << "]\n"
" }#IndexedLineSet\n"
"}# Shape\n";
}
int main(int argc, char** argv)
{
Mesh sm;
std::string fname = argc==1?CGAL::data_file_path("meshes/knot1.off"):argv[1];
if(!CGAL::IO::read_polygon_mesh(fname, sm))
{
std::cerr << "Invalid input file." << std::endl;
return EXIT_FAILURE;
}
kruskal(sm);
return 0;
}
第二个示例展示了我们如何将属性映射用于诸如 Prim 的最小生成树之类的算法。该算法在内部也使用顶点索引属性映射调用get(boost::vertex_index_t,sm)
。对于这个类,Surface_mesh
这归结为一个身份函数,因为顶点是索引。
#include <CGAL/Simple_cartesian.h>
#include <CGAL/Surface_mesh.h>
#include <iostream>
#include <fstream>
// workaround a bug in Boost-1.54
#include <CGAL/boost/graph/dijkstra_shortest_paths.h>
#include <boost/graph/prim_minimum_spanning_tree.hpp>
typedef CGAL::Simple_cartesian<double> Kernel;
typedef Kernel::Point_3 Point;
typedef CGAL::Surface_mesh<Point> Mesh;
typedef boost::graph_traits<Mesh>::vertex_descriptor vertex_descriptor;
int main(int argc, char* argv[])
{
Mesh sm;
std::string fname = argc==1?CGAL::data_file_path("meshes/knot1.off"):argv[1];
if(!CGAL::IO::read_polygon_mesh(fname, sm))
{
std::cerr << "Invalid input file." << std::endl;
return EXIT_FAILURE;
}
Mesh::Property_map<vertex_descriptor,vertex_descriptor> predecessor;
predecessor = sm.add_property_map<vertex_descriptor,vertex_descriptor>("v:predecessor").first;
boost::prim_minimum_spanning_tree(sm, predecessor, boost::root_vertex(*vertices(sm).first));
std::cout << "#VRML V2.0 utf8\n"
"DirectionalLight {\n"
"direction 0 -1 0\n"
"}\n"
"Shape {\n"
" appearance Appearance {\n"
" material Material { emissiveColor 1 0 0}}\n"
" geometry\n"
" IndexedLineSet {\n"
" coord Coordinate {\n"
" point [ \n";
for(vertex_descriptor vd : vertices(sm)){
std::cout << " " << sm.point(vd) << "\n";
}
std::cout << " ]\n"
" }\n"
" coordIndex [\n";
for(vertex_descriptor vd : vertices(sm)){
if(predecessor[vd]!=vd){
std::cout << " " << std::size_t(vd) << ", " << std::size_t(predecessor[vd]) << ", -1\n";
}
}
std::cout << "]\n"
" }#IndexedLineSet\n"
"}# Shape\n";
sm.remove_property_map(predecessor);
return 0;
}
8、表面网格 I/O
作为模型FaceGraph
(请参阅部分曲面网格和 BGL API),CGAL::Surface_mesh
可以使用多种不同的文件格式进行读取和写入。有关详细信息,请参阅CGAL 和 Boost Graph Library包的I/O 函数以及多边形网格处理包的I/O 函数。
此外,该包还提供来自CGAL 和 Boost Graph Library 中Surface_mesh
包的 I/O 函数的特定重载。这允许直接从内部属性映射读取/写入,有关更多信息,请参阅I/O 函数。
9、内存管理
内存管理是半自动的。内存随着更多元素被添加到结构中而增长,但当元素被移除时内存不会减少。
当您添加元素并且基础向量的容量耗尽时,向量会重新分配内存。由于描述符基本上是索引,因此它们在重新分配后引用相同的元素。
当您删除一个元素时,它只会被标记为已删除。在内部,它被放在一个空闲列表中,当您将元素添加到表面网格时,它们会从空闲列表中取出,以防它不为空。
对于所有元素,我们提供了一个函数来获取已使用元素的数量,以及已使用和删除元素的数量。对于顶点,函数分别是Surface_mesh::number_of_vertices()
和Surface_mesh::number_of_removed_vertices()
。num_vertices(const G&)
第一个函数与BGL 包的免费函数略有不同。由于 BGL 样式算法使用元素的索引来访问临时大小向量中的数据,因此num_vertices()
此函数必须返回一个大于元素的最大索引的数字。
诸如Surface_mesh::Vertex_iterator
仅枚举未标记为已删除的元素的迭代器。
要真正缩小使用的内存,Surface_mesh::collect_garbage()
必须调用。垃圾收集还会压缩与表面网格相关的属性。
但是请注意,通过垃圾收集元素可以获得新的索引。如果您保留顶点描述符,它们很可能不再引用正确的顶点。
1、 示例
#include <iostream>
#include <CGAL/Simple_cartesian.h>
#include <CGAL/Surface_mesh.h>
typedef CGAL::Simple_cartesian<double> K;
typedef CGAL::Surface_mesh<K::Point_3>网格;
typedef Mesh::Vertex_index vertex_descriptor;
主函数()
{
网格米;
Mesh::Vertex_index u;
对于(无符号 整数i=0;i < 5;++i){
网格::顶点索引 v = m。add_vertex (K::Point_3(0,0,i+1));
如果(i==2)你=v;
}
m.remove_vertex(u);
std::cout << "插入 5 个顶点并移除 3 个顶点后\n"
<< “#个顶点/#个顶点+#个删除的顶点=”
<< m.number_of_vertices()
<< " / " << m.number_of_vertices() + m.number_of_removed_vertices() << std::endl;
std::cout << "遍历顶点\n" ;
{
for (vertex_descriptor vd : m.vertices()){
std::cout << m.point(vd) << std::endl;
}
}
// 被使用或被移除的状态存储在属性映射中
Mesh::Property_map<Mesh::Vertex_index,bool> 移除
= m.property_map<Mesh::Vertex_index, bool >( "v:removed" ).first;
std::cout << "\n遍历顶点并删除顶点\n"
<< “#个顶点/#个顶点+#个删除的顶点=”
<< m.number_of_vertices()
<< " / " << m.number_of_vertices() + m.number_of_removed_vertices() << std::endl;
{
unsigned int i = 0, end = m.number_of_vertices() + m.number_of_removed_vertices();
对于( ; i < 结束; ++i) {
顶点描述符 vh(i);
assert(m.is_removed(vh) == removed[vh]);
std::cout << m.point(vh) << ((m.is_removed(vh)) ? " R\n" : "\n" );
}
}
m.collect_garbage();
std::cout << "\n垃圾回收后\n"
<< “#个顶点/#个顶点+#个删除的顶点=”
<< m.number_of_vertices()
<< " / " << m.number_of_vertices() + m.number_of_removed_vertices() << std::endl;
{
unsigned int i = 0, end = m.number_of_vertices() + m.number_of_removed_vertices();
对于( ; i < 结束; ++i) {
顶点描述符 vh(i);
std::cout << m.point(vh) << ((m.is_removed(vh)) ? " R\n" : "\n" );
}
}
返回0;
}
10、绘制Surface Mesh
可以通过调用CGAL::draw()来可视化表面网格,如以下示例所示。此函数打开一个新窗口,显示给定的表面网格。对该函数的调用是阻塞的,也就是说,只要用户关闭窗口,程序就会继续。
#include <CGAL/Surface_mesh.h>
#include <CGAL/draw_surface_mesh.h>
#include <fstream>
typedef CGAL::Simple_cartesian<double> Kernel;
typedef Kernel::Point_3 Point;
typedef CGAL::Surface_mesh<Point> Mesh;
int main(int argc, char* argv[])
{
const std::string filename = (argc>1) ? argv[1] : CGAL::data_file_path("meshes/elephant.off");
Mesh sm;
if(!CGAL::IO::read_polygon_mesh(filename, sm))
{
std::cerr << "Invalid input file." << std::endl;
return EXIT_FAILURE;
}
CGAL::draw(sm);
return EXIT_SUCCESS;
}
此函数需要, 且仅在定义CGAL_Qt5
宏时可用。CGAL_USE_BASIC_VIEWER
与 cmake 目标链接CGAL::CGAL_Basic_viewer
将链接CGAL_Qt5
并添加定义CGAL_USE_BASIC_VIEWER
。
11 实现细节
作为我们选择的索引的整数类型boost::uint32_t
。在 64 位操作系统上,它们只占指针大小的一半。他们仍然允许拥有 20 亿个元素的网格。
我们用于std::vector
存储属性。因此,通过访问属性映射的第 0 个元素的地址,您可以访问底层原始数组。这可能很有用,例如将点数组传递给 OpenGL。
我们对删除的元素使用*空闲列表。*这意味着当一个顶点被移除并稍后add_vertex
被调用时,被移除元素的内存将被重用。这尤其意味着第 n 个插入的元素不一定具有索引n-1
,并且在遍历元素时它们不会按插入顺序枚举。