CGAL ::Surface Mesh 参考文档examples详解
1 引言CGAL::Surface_mesh是 CGAL 中用于表示多面体表面的半边数据结构Halfedge Data Structure, HDS是传统指针式半边结构和3D 多面体表面的轻量化替代方案核心优势是基于整数索引而非指针兼顾内存效率、易用性和算法兼容性。本文主要对CGAL官网给出关于Surface Mesh的example做一个要点记录方便学习和回顾。examples 1第一个示例展示了如何通过添加2个面来创建一个非常简单的Surface_mesh以及如何检查一个面是否被正确添加到网格中。#includeCGAL/Surface_mesh/Surface_mesh.h #includeCGAL/Simple_cartesian.h typedef CGAL::Simple_cartesiandouble Kernel; typedef Kernel::Point_3 Point_3; typedef CGAL::Surface_meshPoint_3 Mesh; typedef Mesh::Vertex_index vertex_descriptor; typedef Mesh::Face_index face_descriptor; /* 这个例子展示的的Mesh的2流行的结构要求即一条边最多属于两个平面这两个面在边的两侧且他们的绕行方向相反 第一次添加面(u,v,w)其绕行方向为u-v-w-u 第二次添加面(u,v,x), 其绕行方向为u-v-x-u 可以看到边u-v的方向始终是u到v的这两个面的绕行方向一致所以第二个面添加失败了 而添加第三个面(u,x,v)其绕行方向为v到u所以添加成功了 */ void test1() { Mesh m; vertex_descriptor u m.add_vertex(Point_3(0,1,0)); vertex_descriptor v m.add_vertex(Point_3(0,0,0)); vertex_descriptor w m.add_vertex(Point_3(1,1,0)); vertex_descriptor x m.add_vertex(Point_3(1,0,0)); m.add_face(u,v,w); face_descriptor f m.add_face(u,v,x); //添加失败返回的是null_face() if (fMesh::null_face()) { std::cout 由于方向错误无法添加该面。 std::endl; f m.add_face(u,x,v); assert(f!Mesh::null_face()); } }这个例子展示的的Mesh的2流行的结构要求即一条边最多属于两个平面这两个面在边的两侧且他们的绕行方向相反。第一次添加面(u,v,w)其绕行方向为u-v-w-u第二次添加面(u,v,x), 其绕行方向为u-v-x-u可以看到边u-v的方向始终是u到v的这两个面的绕行方向一致所以第二个面添加失败了而添加第三个面(u,x,v)其绕行方向为v到u所以添加成功了。重要函数返回值函数名作用Vertex_indexadd_vertexPoint pMesh中添加点Face_indexadd_face(Point p,Point q,Point v)向Mesh中添加面example 2第二个示例展示了如何访问与顶点相关联的点既可以针对单个顶点也可以作为整个网格的点范围。这样的范围可以在for循环中访问或者传递给需要点范围作为输入的函数。#includeCGAL/Simple_cartesian.h #includeCGAL/Surface_mesh/Surface_mesh.h #includeCGAL/convex_hull_3.h #includeiostream using namespace std; typedef CGAL::Simple_cartesiandouble K; typedef K::Point_3 Point_3; typedef CGAL::Surface_meshPoint_3 Mesh; typedef Mesh::Vertex_index Vertex_index; typedef Mesh::Face_index Face_index; /* 本实例主要展示如何访问mesh每个面的环绕点 通过vertices_around_face()获得点的索引通过mesh.point(vertex_index)返回点 */ void test2() { Mesh m; Vertex_index v0 m.add_vertex(Point_3(0,0,0)); Vertex_index v1 m.add_vertex(Point_3(1,0,0)); Vertex_index v2 m.add_vertex(Point_3(0,1,0)); Vertex_index v3 m.add_vertex(Point_3(0,0,1)); Face_index fd m.add_face(v0,v1,v2); Face_index ff m.add_face(v1,v0,v3); for (Vertex_index vd : vertices_around_face(m.halfedge(fd),m)) { cout vertices around face v012: m.point(vd) endl; } cout endl; for (const Point_3 P: m.points()) { std::cout vertices of Mesh m: P std::endl; } }本例主要介绍了如何遍历组成一个面所有顶点和如何遍历mesh的所有点。其中遍历组成一个面的所有顶点的for循环如下for (Vertex_index vd : vertices_around_face(mesh.halfedge(fd),mesh))其逻辑是半边数据本身的面并不存储顶点通过mesh.halfedge(face)拿到面face关联的半边halfedge然后通过halfedge关联的顶点遍历该半边所属面的所有顶点。遍历mesh的所有点。for (const Point_3 P: mesh.points())example 3下面的示例展示了如何从一个范围中获取迭代器类型、获取起始迭代器和结束迭代器的其他方法以及基于范围的循环的其他形式。#includeCGAL/Simple_cartesian.h #includeCGAL/Surface_mesh/Surface_mesh.h #includeiostream using namespace std; typedef CGAL::Simple_cartesiandouble Kernel; typedef Kernel::Point_3 Point_3; typedef CGAL::Surface_meshPoint_3 Mesh; typedef Mesh::Face_index Face_index; typedef Mesh::Vertex_index Vertex_index; typedef Mesh::Halfedge_index Halfedg_index; /* * 这个示例展示了如何遍历mesh的点线 面 * */ void test3() { Mesh m; Vertex_index u m.add_vertex(Point_3(0,1,0)); Vertex_index v m.add_vertex(Point_3(0,0,0)); Vertex_index w m.add_vertex(Point_3(1,0,0)); Vertex_index x m.add_vertex(Point_3(1,1,0)); Face_index f m.add_face(u,v,w,x); { cout all vertices: endl; Mesh::Vertex_range::iterator vb, ve; Mesh::Vertex_range r m.vertices(); vb r.begin(); ve r.end(); vb begin(r); ve end(r); for (tie(vb, ve) m.vertices(); vb ! ve; vb) { cout *vb m.point(*vb) endl; } //foreach遍历 for (Vertex_index vd:m.vertices()) { cout vd endl; } } { Mesh::Face_range::iterator fb, fe; Mesh::Face_range f m.faces(); fb f.begin(); fe f.end(); for (tie(fb, fe) m.faces(); fb ! fe;fb) { cout face number: *fb m.halfedge(*fb) endl; //遍历每个面的点 for (Vertex_index vi : CGAL::vertices_around_face(m.halfedge(*fb), m)) { cout m.point(vi) endl; } } } { Mesh::Halfedge_range::iterator hb, he; Mesh::Halfedge_range edges m.halfedges(); for (hb edges.begin(), he edges.end(); hb ! he;hb) { cout halfedge: *hb hb-id() endl; } } }这个例子主要介绍如何遍历点面。以点为例方式1Mesh::Vertex_range::iterator v_begin, v_end; for (tie(v_begin, v_end) m.vertices(); v_begin ! v_end; v_begin) { cout *v_begin m.point(*v_begin) endl; }方式2Mesh::Vertex_range::iterator vb, ve; Mesh::Vertex_range r m.vertices(); vb r.begin(); ve r.end(); for (; vb ! ve; vb) { cout *vb m.point(*vb) endl; }方式3//foreach遍历 for (Vertex_index vd:m.vertices()) { cout vd endl; }example 4下面的示例展示了如何枚举给定半边目标点周围的顶点。第二个循环表明这些循环器类型中的每一种都配有一个等效的迭代器和一个用于创建迭代器范围的自由函数。//circulator #includevector #includeCGAL/Simple_cartesian.h #includeCGAL/Surface_mesh/Surface_mesh.h #includeiostream using namespace std; typedef CGAL::Simple_cartesiandouble Kernel; typedef Kernel::Point_3 Point_3; typedef CGAL::Surface_meshPoint_3 Mesh; typedef Mesh::Face_index Face_index; typedef Mesh::Vertex_index Vertex_index; typedef Mesh::Halfedge_index Halfedg_index; void test4() { Mesh m; Vertex_index u m.add_vertex(Point_3(0, 1, 0)); Vertex_index v m.add_vertex(Point_3(0, 0, 0)); Vertex_index w m.add_vertex(Point_3(1, 0, 0)); Vertex_index x m.add_vertex(Point_3(1, 1, 0)); Face_index f m.add_face(u, v, w, x); /* 这段代码介绍了点的环形迭代器的使用 Vertex_around_target_circulatorMesh为环绕target点的点迭代器 */ { cout vertices around vertex: v endl; CGAL::Vertex_around_target_circulatorMesh v_begin(m.halfedge(v),m) , v_end(v_begin) ; do { cout m.point(*v_begin) endl; v_begin; }while(v_begin!v_end); } { cout vertices around face: f endl; for (Vertex_index vi:CGAL::vertices_around_face(m.halfedge(f),m) ) { cout vi endl; } } }本例主要介绍了点和面的环形迭代器点P的环形迭代器遍历目标点P周围的所有点// v_begin 是起始循环器v_end 是终止标记和v_begin初始值相同 CGAL::Vertex_around_target_circulatorMesh v_begin(m.halfedge(vertex_index), m), // 起始循环器以vertex_index为目标顶点 v_end(v_begin); // 终止标记循环回到v_begin时停止 do { cout m.point(*v_begin) endl; // 解引用循环器拿到当前邻接顶点 v_begin; // 循环器自增 } while (v_begin ! v_end);面F的环形迭代器: 遍历组成面的所有点同example3for (Vertex_index vi:CGAL::vertices_around_face(m.halfedge(f),m) ) { cout vi endl; }example 5此示例展示了如何使用属性系统最常见的功能。#includestring #includeiostream #includeCGAL/Surface_mesh/Surface_mesh.h #includeCGAL/Simple_cartesian.h #includeCGAL/boost/graph/generators.h typedef CGAL::Simple_cartesiandouble Kernel; typedef Kernel::Point_3 Point_3; typedef CGAL::Surface_meshPoint_3 Mesh; typedef Mesh::Vertex_index Vertex_index; typedef Mesh::Face_index Face_index; using namespace std; void test5() { Mesh m; Vertex_index v0 m.add_vertex(Point_3(0,2,0)); Vertex_index v1 m.add_vertex(Point_3(2,2,0)); Vertex_index v2 m.add_vertex(Point_3(0,0,0)); Vertex_index v3 m.add_vertex(Point_3(1,0,0)); Vertex_index v4 m.add_vertex(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); //为Meshd的点vertex添加一个属性map属性类型为string属性名字为v::name默认值为m1 Mesh::Property_mapVertex_index, std::string name; //created表示是否成功创建了这个属性map如果已经存在了这个属性map则created为false第一次创建这个属性map时created为true bool created; tie(name, created) m.add_property_mapVertex_index, std::string(v::name, m1);//属性名,初始值 assert(created); name[v0] hello; name[v1] world; { Mesh::Property_mapVertex_index, std::string name; bool created; tie(name,created)m.add_property_mapVertex_index,std::string(v::name,); assert(!created); } //尝试获取一个不存在的属性mapgnus这个属性map不存在所以gnus.has_value()为false std::optionalMesh::Property_mapFace_index, std::string gnus m.property_mapFace_index, std::string(v:gnus); assert(!gnus.has_value()); //一个点的坐标其实就是一个属性map属性类型为Point_3属性名字为points为surface_mesh的默认属性 Mesh::Property_mapVertex_index, Point_3 location m.points(); cout the point location of m endl; for (Vertex_index vd:m.vertices()) { cout vertex: vd --- name[vd] location[vd] endl; } //新建一个m2 Mesh m2; CGAL::make_triangle(Point_3(0,0,1),Point_3(1,0,1),Point_3(0,1,1),m2); m2.add_property_mapVertex_index, std::string(v:name,m2); //add_property_map的返回值是一个pair //本段为pairProperty_mapVertex_index,int,bool Mesh::Property_mapVertex_index, int index; index m2.add_property_mapVertex_index, int(v:index,-1).first; int i 0; for (auto v:vertices(m2)) { index[v] i; } //合并前m的属性列表 cout property map of m before merge with m2 endl; vectorstd::string props m.propertiesVertex_index(); for (string p:props) { cout p endl; } m.join(m2); //合并后m的属性列表 cout property map of m after merge with m2 endl; for (string p: m.propertiesVertex_index()) { cout p endl; } //合并后m1的属性列表不变但是m2中的点会写入到m1中 cout the peoproty [name] of m after merge with m2 endl; for (auto v: vertices(m)) { cout vertex: v : name[v] endl; } //移除属性 m.remove_property_map(name); }Surface_mesh的动态属性机制是其最核心的特性之一 允许在运行时为顶点、半边、边、面添加自定义属性且所有属性以连续内存块存储兼顾易用性和效率这和helfedgeDS的traits有很大不同。这个例子主要介绍了Mesh的自定义属性的创建属性创建pair Mesh::Property_mapVertex_index, string, bool pros m.add_property_mapVertex_index, std::string(v::属性名, 初始值);Mesh::Property_mapVertex_index, std::string name; //created表示是否成功创建了这个属性map如果已经存在了这个属性map则created为false第一次创建这个属性map时created为true bool created; tie(name, created) m.add_property_mapVertex_index, std::string(v::name, m1);//属性名,初始值属性移除m.remove_property_map(name);example 6我们可以直接在曲面网格上应用克鲁斯卡尔最小生成树算法。#include CGAL/Simple_cartesian.h #include CGAL/Surface_mesh.h #include boost/graph/kruskal_min_spanning_tree.hpp #include iostream #include fstream #include list #include string using namespace std; typedef CGAL::Simple_cartesiandouble Kernel; typedef Kernel::Point_3 Point_3; typedef CGAL::Surface_meshPoint_3 Mesh; typedef boost::graph_traitsMesh::vertex_descriptor vertex_descriptor; typedef boost::graph_traitsMesh::edge_descriptor edge_descriptor; typedef boost::graph_traitsMesh::vertex_iterator vertex_iterator; void kruskal(const Mesh mesh, const string output_file D:\\mst.wrl) { // 保存最小生成树的边 listedge_descriptor res_edges; // 调用boost的kruskal算法 boost::kruskal_minimum_spanning_tree(mesh, back_inserter(res_edges)); ofstream out_file(output_file); if (!out_file.is_open()) { cerr 无法打开文件 output_file endl; return; } out_file #VRML V2.0 utf8\n; out_file Shape {\n; out_file appearance Appearance {\n; out_file material Material { emissiveColor 1 0 0 }\n; out_file }\n; out_file geometry IndexedLineSet {\n; out_file coord Coordinate {\n; out_file point [\n; vertex_iterator v_begin, v_end; for (tie(v_begin, v_end) vertices(mesh); v_begin ! v_end; v_begin) { out_file mesh.point(*v_begin) \n; } out_file ]\n }\n coordIndex [\n; for (listedge_descriptor::iterator it res_edges.begin(); it ! res_edges.end(); it) { edge_descriptor e *it; vertex_descriptor s source(e, mesh); vertex_descriptor t target(e, mesh); out_file s , t , -1\n; } out_file ]\n }\n }\n; out_file.close(); cout VRML文件已成功写入 output_file endl; }本例中我们调用boost中的kruskal算法生成最小生成树我更改了官网案例中直接在console打印VRML文件直接在D盘输出.wrl格式的文件。那么本例可以归纳为1 调用boost下kruskal的方法为//存储最小生成树节点的list listedge_descriptor res_edges; // 调用boost的kruskal算法 boost::kruskal_minimum_spanning_tree(mesh, back_inserter(res_edges));当然少不了头文件和类型定义#include boost/graph/kruskal_min_spanning_tree.hpp typedef boost::graph_traitsMesh::vertex_descriptor vertex_descriptor; typedef boost::graph_traitsMesh::edge_descriptor edge_descriptor; typedef boost::graph_traitsMesh::vertex_iterator vertex_iterator;2 BGL(Boost Graph Library) 和 surface mesh的兼容API3 本例也提供提供了一种将几何对象的数据结构输出为VRML结构的思路参考by the wey本例输出的.wrl文件我使用Mashlab并没有打开成功格式有问题。example 7第二个示例展示了我们如何将属性映射用于诸如Prime最小生成树之类的算法。该算法在内部也会使用一个顶点索引属性映射并调用get(boost::vertex_index_t,sm)。对于Surface_mesh类这简化为一个恒等函数因为顶点就是索引。#include CGAL/Simple_cartesian.h #include CGAL/Surface_mesh.h #include iostream #include fstream #include string #include vector using namespace std; // Boost图算法头文件 #include boost/graph/prim_minimum_spanning_tree.hpp #include boost/graph/graph_traits.hpp // 类型定义 typedef CGAL::Simple_cartesiandouble Kernel; typedef Kernel::Point_3 Point; typedef CGAL::Surface_meshPoint Mesh; typedef boost::graph_traitsMesh::vertex_descriptor vertex_descriptor; typedef boost::graph_traitsMesh::vertices_size_type vertices_size_type; void test(int argc, char** argv) { Mesh mesh; string fname argc 1 ? D:\\CGAL_examples\\data\\meshes\\knot1.off : argv[1]; if (!CGAL::IO::read_polygon_mesh(fname,mesh)) { cerr 文件读取错误 , 文件名 fname endl; return; } //添加属性processor用于存储每个顶点在Prim算法中的前驱节点 Mesh::Property_mapvertex_descriptor, vertex_descriptor predecessor; predecessor mesh.add_property_mapvertex_descriptor, vertex_descriptor(v:predecessor).first; boost::prim_minimum_spanning_tree( mesh, // 输入图 predecessor, // 存储前驱节点的属性图 boost::root_vertex(*mesh.vertices().first)); // Prim算法的起始顶点 // pair顶点索引前驱节点索引,存储最小生成树边的信息 vectorpairvertices_size_type, vertices_size_type mst_edges; for (vertex_descriptor vd:mesh.vertices()) { // 根顶点的前驱是自身跳过其他顶点提取边vd, 前驱 if (predecessor[vd]!vd) { //非根顶点的 vd 和 predecessor[vd] 构成 MST 中的一条边转为整数索引后存入 mst_edges // 把顶点描述符转为整数索引PLY文件需要整数索引 vertices_size_type v size_t(vd); vertices_size_type p size_t(predecessor[vd]); // 存入 MST 边列表 mst_edges.emplace_back(v,p); } } string ply_file_path D:\\pri.ply; ofstream ply_file(ply_file_path); if (!ply_file.is_open()) { cerr 文件错误 ply_file_path endl; return; } //输出为ply格式文件 ply_file ply\n; ply_file format ascii 1.0\n; ply_file element vertex num_vertices(mesh) \n; ply_file property float x\n; ply_file property float y\n; ply_file property float z\n; ply_file element edge mst_edges.size() \n; ply_file property int vertex1\n; ply_file property int vertex2\n; ply_file end_header\n; for (vertex_descriptor vd:mesh.vertices()) { Point p mesh.point(vd); ply_file p.x() p.y() p.z() \n; } for (const auto edge: mst_edges) { ply_file edge.first edge.second n; } ply_file.close(); mesh.remove_property_map(predecessor); }本例中我们调用boost中的Prime算法生成最小生成树MST并将MST输出问ply格式的文件。显然可以总结为Prime的调用方法:// Boost图算法头文件与别名 #include boost/graph/prim_minimum_spanning_tree.hpp #include boost/graph/graph_traits.hpp typedef boost::graph_traitsMesh::vertex_descriptor vertex_descriptor; typedef boost::graph_traitsMesh::vertices_size_type vertices_size_type; //添加属性processor用于存储每个顶点在Prim算法中的前驱节点 Mesh::Property_mapvertex_descriptor, vertex_descriptor predecessor; predecessor mesh.add_property_mapvertex_descriptor, vertex_descriptor(v:predecessor).first; boost::prim_minimum_spanning_tree( mesh, // 输入图 predecessor, // 存储前驱节点的属性图 boost::root_vertex(*mesh.vertices().first)); // Prim算法的起始顶点 // pair顶点索引前驱节点索引,存储最小生成树边的信息 vectorpairvertices_size_type, vertices_size_type mst_edges; for (vertex_descriptor vd:mesh.vertices()) { // 根顶点的前驱是自身跳过其他顶点提取边vd, 前驱 if (predecessor[vd]!vd) { //非根顶点的 vd 和 predecessor[vd] 构成 MST 中的一条边转为整数索引后存入 mst_edges // 把顶点描述符转为整数索引PLY文件需要整数索引 vertices_size_type v size_t(vd); vertices_size_type p size_t(predecessor[vd]); // 存入 MST 边列表 mst_edges.emplace_back(v,p); } }下面是官网数据的测试结果展示输入图结构最小生成树example 8这个例子展示了surface_mesh的垃圾回收机制。#includeiostream #includeCGAL/Simple_cartesian.h #includeCGAL/Surface_mesh/Surface_mesh.h using namespace std; typedef CGAL::Simple_cartesiandouble Kernel; typedef Kernel::Point_3 Point_3; typedef CGAL::Surface_meshPoint_3 Mesh; typedef Mesh::Vertex_index Vertex_index; void test8() { Mesh m; Vertex_index u; for (unsigned int i 0; i 5;i) { Mesh::Vertex_index v m.add_vertex(Point_3(0,0,i1)); if (i 2) u v; } //删除顶点3 m.remove_vertex(u); cout insert 5 vertex ,delete vertex_3endl #顶点数 /# 顶点数 #移除的顶点数 m.number_of_vertices() / m.number_of_vertices() m.number_of_removed_vertices() std::endl; std::cout 所有顶点包括 std::endl; { //通过默认的迭代器访问所有顶点移除的顶点不会被访问 for(Vertex_index vd: m.vertices()){ cout m.point(vd) endl; } } //v:removed是vertex的默认属性 Mesh::Property_mapMesh::Vertex_index, bool removed; //removed将保存每个顶点是否被移除的状态 removed m.property_mapMesh::Vertex_index, bool(v:removed).value(); cout\n------------------------------------\n #顶点数 /# 顶点数 #移除的顶点数 m.number_of_vertices() / m.number_of_vertices()m.number_of_removed_vertices() endl; { //直接暴力使用索引访问所有顶点被逻辑移除的点也可以访问到 unsigned int i 0, end m.number_of_vertices() m.number_of_removed_vertices(); for (; i end;i) { Vertex_index vh(i); assert(m.is_removed(vh)removed[vh]); cout m.point(vh) ((m.is_removed(vh)) ? R\n : \n); } } m.collect_garbage(); cout after collect_garbage()\n #顶点数 /# 顶点数 #移除的顶点数: m.number_of_vertices() / m.number_of_vertices() m.number_of_removed_vertices() endl; { unsigned int i 0, end m.number_of_vertices() m.number_of_removed_vertices(); for (; i end;i) { Vertex_index vh(i); cout m.point(vh) ((m.is_removed(vh)) ? R\n : \n); } } }调用m.remove_vertex(vertex_index)只是逻辑删除仅将顶点的v:removed属性标记为true元素和索引仍保留属性值也存在。调用mesh.collect_garbage()才是物理删除彻底删除所有标记为removed的元素压缩内存重新整理索引。example 9下面展示了通过调用CGAL::drawSM() 来可视化表面网格。#include CGAL/Simple_cartesian.h #include CGAL/Surface_mesh.h #include CGAL/draw_surface_mesh.h #include fstream #include random typedef CGAL::Simple_cartesiandouble Kernel; typedef Kernel::Point_3 Point; typedef CGAL::Surface_meshPoint Mesh; // 简化命名避免重复写Mesh::XXX typedef Mesh::Vertex_index Vertex_idx; typedef Mesh::Edge_index Edge_idx; typedef Mesh::Face_index Face_idx; CGAL::IO::Color random_color() { int rgb1 rand() % 100; int rgb2 rand() % 256; int rgb3 rand() % 150; return CGAL::IO::Color(rgb1,125,rgb3, 180); } int test9(int argc, char* argv[]) { // CGAL::data_file_path() 是CGAL内置的样例路径本地文件直接写绝对路径即可 const std::string filename (argc 1) ? argv[1] : D:/CGAL_examples/data/meshes/elephant.off; Mesh sm; if (!CGAL::IO::read_polygon_mesh(filename, sm)) { std::cerr Invalid input file: filename std::endl; return EXIT_FAILURE; } // 1. 添加颜色属性 auto v_color sm.add_property_mapVertex_idx, CGAL::IO::Color(v:color).first; auto e_color sm.add_property_mapEdge_idx, CGAL::IO::Color(e:color).first; auto f_color sm.add_property_mapFace_idx, CGAL::IO::Color(f:color, CGAL::IO::Color(255, 255, 255, 128)).first; // 2. 设置顶点颜色 for (Vertex_idx v : sm.vertices()) { v_color[v] (v.idx() % 2) ? CGAL::IO::black() : CGAL::IO::blue(); } // 3. 设置边颜色 for (Edge_idx e : sm.edges()) { e_color[e] CGAL::IO::gray(); } if (!sm.faces().empty()) { for (Face_idx f: sm.faces()) { f_color[f] random_color(); } } CGAL::draw(sm); return EXIT_SUCCESS; }展示注意事项代码中的文件写的是绝对路径需要更改为你自己的文件地址这个文件在CGAL官网的examples中官网地址为:https://github.com/CGAL/cgal/releases/download/v6.1.1/CGAL-6.1.1-examples.zip另外需要项目运行需要正确配置cmake等参见Win11下载CGAL6.0并使用VS2022搭载 Qt,可视化CGAL几何对象参考来源CGAL 6.1.1 - Surface Mesh: User Manual
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2408636.html
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!