【翻译】CGAL-Surface Mesh Manual


在这里插入图片描述

Author: Mario Botsch, Daniel Sieger, Philipp Moeller and Andreas Fabri

Surface_mesh类是一个半凸数据结构的实现,可以用来表示一个多面体曲面。它是CGAL包Halfedge数据结构和3D多面体表面的替代方案。主要区别在于它是基于索引的,而不是基于指针的。此外,向顶点、半边、边和面添加信息的机制要简单得多,并且是在运行时而不是在编译时完成的。
由于该数据结构使用整数索引作为顶点、半边、边和面的描述符,因此它比基于64位指针的版本占用更少的内存。由于索引是连续的,它们可以用作存储属性的向量的索引。
当删除元素时,它们仅被标记为已删除,并且必须调用垃圾收集函数来真正删除它们。
Surface_mesh类可以通过它的类成员函数使用,也可以通过包CGAL和Boost Graph Library中描述的BGL API使用,因为它是MutableFaceGraph和FaceListGraph概念的模型。因此,可以在一个表面网格上应用包三角曲面网格简化、三角曲面网格分割和三角曲面网格变形算法。

1 Usage

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.1 Example

下面的例子展示了如何通过添加2个面来创建一个非常简单的Surface_mesh,以及如何检查一个面是否正确地添加到网格中。

File Surface_mesh/check_orientation.cpp

#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 Connectivity

曲面网格是一种以边为中心的数据结构,能够保持顶点、边和面的关联信息。每条边由两个方向相反的半边表示。每个半边存储一个对附属面和附属顶点的引用。此外,它存储下一个和前一个半边结构及附带面的引用。对于每个面和每个顶点,存储一个附属的半边。半边结构中不存储相对的半边结构的索引,因为Surface_mesh在内存中连续存储相对的半边结构。

下图演示了允许在曲面网格中导航的函数:Surface_mesh::opposite()Surface_mesh::next()Surface_mesh::prev()Surface_mesh::target()Surface_mesh::face()。此外,Surface_mesh::halfedge() 函数允许获取与顶点和面相关的半元。另外,也可以使用包CGAL和Boost Graph Library中定义的同名免费函数。
在这里插入图片描述

Figure 1 在一个表面网格中,从外部看到的半网格和顶点的连通性

半边结构附带一个面组成一个循环,根据我们从表面的哪一边看,半边的序列似乎是顺时针或逆时针方向的。在本手册中,当我们谈到遍历的方向时,我们会观察表面,使围绕一个面的半凸面逆时针方向,如图1所示.

3 Ranges and Iterators

Surface_mesh提供范围迭代器来枚举所有顶点、半边、边和面。它提供了返回与 Boost.Range 库兼容的成员函数。

File Surface_mesh/sm_iterators.cpp

#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));
  m.add_face(u,v,w,x);/* face_descriptor f = */ 
  {
    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 Circulators

在包CGAL和Boost Graph Library中,围绕面和顶点的Circulators作为类模板被提供。
围绕面的Circulators基本上调用Surface_mesh::next(),以便围绕面逆时针地从一个半边移动到另一个半边,当解引用时,可返回半边或半边附带的顶点或相反的面。

CGAL::Halfedge_around_face_circulator<Mesh>
CGAL::Vertex_around_face_circulator<Mesh>
CGAL::Face_around_face_circulator<Mesh>

围绕一条边的目标顶点的Circulators基本上调用Surface_mesh::opposite(Surface_mesh::next()),以便围绕同一目标顶点顺时针从一个半边到另一个半边。

CGAL::Halfedge_around_target_circulator<Mesh>
CGAL::Vertex_around_target_circulator<Mesh>
CGAL::Face_around_target_circulator<Mesh>

所有的Circulators模型BidirectionalCirculator。除此之外,它们还支持转换为bool值,以便更方便地检查空值。

4.1 Example

下面的示例演示如何枚举给围绕定半边目标的顶点。第二个循环表明,每种Circulator类型都有一个等效的迭代器和一个创建范围迭代器的自由函数。

File Surface_mesh/sm_circulators.cpp

#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 Properties

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()),也可以是全部删除他们(Surface_mesh::remove_all_property_maps())。

要清除一个网格,你可以删除一个网格中所有添加的属性映射(Surface_mesh::clear())或保留它们(Surface_mesh::clear_without_removing_property_maps())。注意,在这两种情况下,“v:point”属性映射都将被保留,并且保持对它的引用是安全的。

5.1 Example

这个例子展示了如何使用属性系统中的最常见特性。

File Surface_mesh/sm_properties.cpp

#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 Borders

半边存储一个面引用,即它的附属面。如果一个半边h没有附属面,即sm.face(h) == Surface_mesh::null_face()成立,则说明这个半边在边界上。同时,一个边的任意一个半边都在边界上,则说明这个边在边界上。同理,如果一个顶点它所属的任意一个半边都在边界上,则说明这个顶点在边界上。

一个顶点只有一个相关联的半边。如果用户注意到那个相关连的半边是一个边界半边的话,这种情况则说明顶点也在边界上。这是就没有必要再针对于顶点使用is_border()函数来查看他的所有所属半边了。为了只检查相关联的halfedges是否在边界上,函数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)Surface_mesh::set_vertex_halfedge_to_border_halfedge() 可以分别为单个顶点v,面向h的边界上的所有顶点,以及面向曲面网格的所有顶点设置边界半边。

7 Surface Mesh and the BGL API

Surface_mesh类是一个在Boost Graph Library中被定义的IncidenceGraph概念模型。这使得可以直接在表面网格上应用Dijkstra最短路径或Kruskal最小生成树等算法。
例如,BGL API的类型和自由函数都有相似的类型或成员函数
在这里插入图片描述
如果返回的顶点数不考虑被删除的顶点,效果会更好,但这会与底层顶点/边索引映射产生严重的交互。索引映射将不再位于许多算法中假设的[0,num_vertices(g))范围内。
Surface_mesh类也是包CGAL和Boost Graph Library中定义的MutableFaceGraph概念的模型。与之相类似的如HalfedgeGraph等概念,通过引入半边和面,以及围绕面和围绕顶点的半边循环等概念,改进了BGL的图概念。同样,有类似的类型和函数,例如:
在这里插入图片描述
包CGAL和Boost Graph Library中描述的BGL API使我们能够编写在曲面网格上运行的几何算法,适用于任何模型的FaceGraph或MutableFaceGraph。也就是说,曲面网格简化、变形或分割算法适用于Surface_mesh和Polyhedron_3。
BGL算法使用属性映射来将信息关联到顶点和边。一个重要的属性是索引,一个0到num_vertices(g)之间的整数,用于图g的顶点。这允许算法创建一个适当大小的向量,以存储每个顶点的信息。例如,如果一个顶点在图遍历过程中已经被访问,则存储一个布尔值。
获取图g的顶点索引属性映射的BGL方法是使用vipm = get(boost::vertex_index, g)和get(vipm, vd)来获取顶点描述符vd的索引,或是用get(vertex_index, g, vd)直接获取顶点索引。

7.1 Example

第一个例子表明,我们可以直接在曲面网格上应用Kruskal的最小生成树算法。

File Surface_mesh/sm_kruskal.cpp

#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类,这可以归结为一个恒等函数,因为顶点是索引。

File Surface_mesh/sm_bgl.cpp

#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 Surface Mesh I/O

作为FaceGraph的一个模型(参见章节Surface Mesh和BGL API), CGAL::Surface_mesh可以使用多种不同的文件格式进行读写。更多信息请参考CGAL和Boost Graph Library包的I/O函数,以及Polygon Mesh Processing包的I/O函数。
此外,这个包提供了来自CGAL和Boost Graph Library包的特定于surface_mesh的I/O函数重载。这允许直接从/写入内部属性映射,有关更多信息,请参阅I/O函数。

9 Memory Management

内存管理是半自动的。当向结构中添加更多的元素时,内存会增加,但当元素被删除时,内存不会缩小。
当您添加元素并且底层向量的容量耗尽时,该向量将重新分配内存。由于描述符基本上是索引,它们在重新分配后引用相同的元素。
当您删除一个元素时,它只被标记为已删除。在内部,它被放在一个空闲列表中,当你向曲面网格添加元素时,它们从空闲列表中取出,以防它不是空的。
对于所有元素,我们提供了一个函数来获取已使用元素的数量,以及已使用和已删除元素的数量。对于顶点,这些函数分别是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()。垃圾收集还压缩与表面网格相关的属性。
但是请注意,通过垃圾收集,元素将获得新的索引。如果你保留顶点描述符,它们很可能不再指向正确的顶点。

9.1 Example

File Surface_mesh/sm_memory.cpp

#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> Mesh;
typedef Mesh::Vertex_index vertex_descriptor;
int main()
{
  Mesh m;
  Mesh::Vertex_index u;
  for(unsigned int i=0; i < 5; ++i){
    Mesh::Vertex_index v = m.add_vertex(K::Point_3(0,0,i+1));
    if(i==2) u=v;
  }
  m.remove_vertex(u);
  std::cout << "After insertion of 5 vertices and removal of the 3. vertex\n"
            << "# vertices  / # vertices + # removed vertices = "
            << m.number_of_vertices()
            << " / " << m.number_of_vertices() + m.number_of_removed_vertices() << std::endl;
  std::cout << "Iterate over vertices\n";
  {
    for(vertex_descriptor vd : m.vertices()){
      std::cout << m.point(vd) << std::endl;
    }
  }
  // The status of being used or removed is stored in a property map
  Mesh::Property_map<Mesh::Vertex_index,bool> removed
    = m.property_map<Mesh::Vertex_index,bool>("v:removed").first;
  std::cout << "\nIterate over vertices and deleted vertices\n"
            << "# vertices / # vertices + # removed vertices = "
            << 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();
    for( ; i < end; ++i) {
      vertex_descriptor 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 << "\nAfter garbage collection\n"
            << "# vertices / # vertices + # removed vertices = "
            << 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();
    for( ; i < end; ++i) {
      vertex_descriptor vh(i);
      std::cout << m.point(vh) << ((m.is_removed(vh)) ? "  R\n" : "\n");
    }
  }
  return 0;
}

10 Draw a Surface Mesh

一个表面网格可以通过调用CGAL::draw()来可视化,如下面的例子所示。这个函数打开一个新窗口,显示给定的表面网格。对这个函数的调用是阻塞的,也就是说,只要用户关闭窗口,程序就会继续。

File Surface_mesh/draw_surface_mesh.cpp

#include <CGAL/Simple_cartesian.h>
#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。
在这里插入图片描述

Figure 2 运行draw_surface_mesh程序的结果。一个窗口显示了表面网格,并允许在3D场景中导航。
# 11 Implementation Details 作为整数类型的索引,我们选择boost::uint32_t。在64位操作系统上,它们只需要指针的一半大小。它们仍然允许包含20亿个元素的网格。 我们使用std::vector来存储属性。因此,通过访问属性映射的第0个元素的地址,就可以访问底层的原始数组。这可能是有用的,例如传递一个点数组到OpenGL。 我们对删除的元素使用自由列表。这意味着当一个顶点被移除,然后调用add_vertex时,被移除元素的内存将被重用。这特别意味着插入的第n个元素不一定有索引n-1,并且在迭代元素时,它们不会按插入顺序枚举。 # 12 Implementation History 该包源自Daniel Sieger和Mario Botsch包Surface_mesh[1]的早期版本,其灵感来自OpenMesh和CGAL包3D Polyhedral Surface的设计。 Philipp Moeller和Andreas Fabri对代码进行了修改,以使迭代器满足STL迭代器概念的要求,并更改了API,使其成为包CGAL和Boost Graph Library的MutableFaceGraph和FaceListGraph概念的模型。
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值