当前位置: 首页 > article >正文

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

相关文章:

CGAL ::Surface Mesh 参考文档examples详解

1 引言 CGAL::Surface_mesh 是 CGAL 中用于表示多面体表面的半边数据结构(Halfedge Data Structure, HDS),是传统指针式半边结构和3D 多面体表面的轻量化替代方案,核心优势是基于整数索引而非指针,兼顾内存效率、易用…...

膜结构车棚性价比排名深度解析

在停车设施领域,膜结构车棚凭借其独特优势逐渐成为热门选择。下面将深入分析膜结构车棚性价比相关因素。性能优势凸显性价比膜结构车棚以高强度建筑膜材为覆盖材料,配合钢结构支撑。其膜材有 PVC/PVDF 涂层聚酯膜、PTFE 膜等。它具有诸多性能优势&#x…...

实验室纯水机怎么选?2026 纯水系统品牌及选型全攻略

在实验室筹建或设备更新时,面对琳琅满目的品牌和参数,“怎么选”往往比“买不买”更令人头疼。选错设备,轻则耗材成本高昂,重则导致实验结果偏差、质谱基线不稳。要选对实验室纯水机,建议按照以下“四步筛选法”进行决…...

(新界面)NVR即时回放功能操作指导

(新界面)NVR即时回放功能操作指导一、功能介绍对系统当前时间点之前5分钟(可自定义,默认5分钟)内的录像进行回放,方便在预览实况时发现异常并即时回放。旧版本NVR需升级至NVR-BXXXX.50.13.250529或更高版本…...

技术人的困境:参数碾压对手,客户却不选你?用“技术路线图叙事”破局

作为技术出身的B2B从业者,你可能一直坚信:技术够硬,就是最好的销售语言。你带着PPT去见客户,第一页:屈服强度对比图,比竞争对手高15%;第二页:专利清单,密密麻麻排了三页&…...

求推荐超绝高性价比的GEO优化公司

一直以来,传统SEO投放大量网络资源却难以让用户精准找到品牌,且严重依赖搜索框、时效慢、流量少等问题,是当前行业普遍面临的难题。倍霖卓越推出的倍霖GEO针对这一问题提供了专业解决方案。倍霖GEO系统,采用多类型多平台全方位内容…...

IonicSelect组件使用指南

ionic select 基础用法ionic select 组件允许用户从一组选项中选择一个或多个值。基本用法需要在 ion-select 标签内嵌套 ion-select-option 标签。<ion-select placeholder"选择水果"><ion-select-option value"apple">苹果</ion-select-o…...

用大白话解释Sub-Agents

用大白话解释 Sub-Agent 就是"被派出去干活的 Claude 分身"。最直白的比喻 想象你是一个包工头&#xff08;主 Claude&#xff09;&#xff1a;你接到一个装修任务&#xff0c;但你不可能同时贴瓷砖、刷墙、装电路。 所以你派出三个工人去分别干这三件事&#xff1a;…...

【研报221】2026年汽车零部件行业投资策略研究:智驾平权+人形机器人量产趋势

本报告提供限时下载&#xff0c;请查看文后提示以下仅为报告部分内容&#xff1a;此报告全面解析汽车零部件行业2026年的发展趋势与投资机会。2025年行业受益于乘用车销量增长&#xff0c;零部件板块收入同比8.3%&#xff0c;其中智能化赛道表现最优&#xff0c;收入增速达20.0…...

【漏洞赏金猎人必看】竞态条件漏洞:从概念到实战,一文讲透(含挖掘技巧)

【漏洞赏金猎人必看】竞态条件漏洞&#xff1a;从概念到实战&#xff0c;一文讲透&#xff08;含挖掘技巧&#xff09; 本文将带你从 0 到 1 彻底理解竞态条件漏洞&#xff0c;并学会在 SRC 和赏金平台中高效挖掘这类高价值漏洞。 我会用最通俗的方式&#xff0c;教你如何判断一…...

进程间通信之管道(匿名管道 + 命名管道)详解

作为进程间通信&#xff08;IPC&#xff09;的基础方式&#xff0c;管道是 Linux/Unix 系统中最经典的 IPC 实现&#xff0c;本文从原理、接口、特性、应用场景等维度&#xff0c;讲讲匿名管道和命名管道。一、管道的核心本质先明确一个核心结论&#xff1a;管道的本质是内核提…...

这里藏着动态参数的小把戏

锂电池soc估计 ekf算法 在线估计&#xff0c;动态参数滚动优化&#xff0c;精度为0.01最近在折腾锂电池管理系统&#xff0c;发现SOC&#xff08;State of Charge&#xff09;估计真是个磨人的小妖精。尤其是要在动态工况下保持0.01的精度&#xff0c;传统的开路电压法直接跪了…...

基因编辑武汉伯远

基因编辑是一种利用工程化核酸酶&#xff08;如CRISPR/Cas9&#xff09;精准修改生物基因组的技术&#xff0c;可实现基因敲除、插入、替换或调控&#xff0c;常用于功能验证、疾病模型和育种改良。核心原理 CRISPR/Cas9系统模拟细菌免疫机制&#xff1a;sgRNA&#xff08;单导…...

HCNR201隔离运放电路

芯片简介HCNR201是一款高线性度模拟光耦&#xff0c;核心作用是实现模拟信号的精确隔离传输&#xff0c;同时保证信号不失真&#xff0c;内部包含1个发光二极管(LED)和2个紧密匹配的光电二极管(PD1, PD2)。电路解析以数据手册示例电路举例下面通过数据手册给出的关键参数&#…...

荣耀远航计划丨主题精品共创激励更新

...

OpenClaw 安装配置全记录

OpenClaw 安装配置全记录 &#x1f4dd; 前言 本文档记录了在 Windows 环境下安装和配置 OpenClaw 的全过程&#xff0c;包括遇到的问题、解决方案。希望能帮助其他遇到类似问题的朋友。一、环境准备 1.1 系统要求 操作系统&#xff1a;Windows 10/11Node.js&#xff1a;22.x 或…...

指针类型:C语言内存安全的关键

指针变量类型的作用 C语言中指针变量要求类型的主要原因在于类型系统对内存操作的安全性和效率保障。指针的类型决定了它指向的内存区域如何被解释&#xff0c;以及指针运算的行为。 内存访问的正确性 指针类型明确了所指向数据的类型和大小。例如&#xff0c;int*指针指向一个…...

开源OpenClaw部署指南

1. 引言当前&#xff0c;人工智能和机器人技术正迅猛发展。作为自动化操作的关键部件&#xff0c;机械夹爪在工业生产、医疗康复和服务机器人等领域发挥着重要作用。Open Claw采用开源设计理念&#xff0c;突破了传统夹爪的技术限制&#xff0c;显著降低了研发和应用门槛。这种…...

Boost源码分析: Serialization

本文梳理Boost.Serialization代码的实现原理。 注1&#xff1a;限于研究水平&#xff0c;分析难免不当&#xff0c;欢迎批评指正。 注2&#xff1a;文章内容会不定期更新。 一、概述 二、核心组件 2.1 boost::archive::detail::interface_oarchive // include\boost\archive…...

院墙上的监控成摆设?避开这三个坑,不给骗子留机会!室外监控摄像头哪个品牌好

回村发现一个扎心现象&#xff1a;家家户户院墙上都挂着监控&#xff0c;可大半都是“中看不中用”的摆设。不少流动商贩专挑农村老人下手&#xff0c;凭着几句忽悠&#xff0c;就让老人花几千块买劣质监控&#xff0c;实则全是收割养老钱的套路——农村户外监控&#xff0c;早…...

Flutter 三方库 rabbit_converter 的鸿蒙化适配指南 - 让消息转换回归“工业化标准”,打造鸿蒙应用专家级的 RabbitMQ 数据适配中台

欢迎加入开源鸿蒙跨平台社区&#xff1a;https://openharmonycrossplatform.csdn.net Flutter 三方库 rabbit_converter 的鸿蒙化适配指南 - 让消息转换回归“工业化标准”&#xff0c;打造鸿蒙应用专家级的 RabbitMQ 数据适配中台 前言 在鸿蒙&#xff08;OpenHarmony&…...

原儿茶醛市场洞察:2026 - 2032年复合增长率(CAGR)为4.6%

2025 - 2032全球原儿茶醛市场洞察&#xff1a;需求驱动下的增长与挑战据恒州诚思调研统计&#xff0c;2025年全球原儿茶醛收入规模约达1.74亿元&#xff0c;至2032年这一规模将接近2.39亿元&#xff0c;2026 - 2032年复合增长率&#xff08;CAGR&#xff09;为4.6%。原儿茶醛&a…...

Flutter 三方库 icc_parser 的鸿蒙化适配指南 - 高效解析 ICC 颜色配置文件,精准还原跨平台色彩表现

欢迎加入开源鸿蒙跨平台社区&#xff1a;https://openharmonycrossplatform.csdn.net Flutter 三方库 icc_parser 的鸿蒙化适配指南 - 高效解析 ICC 颜色配置文件&#xff0c;精准还原跨平台色彩表现 前言 在现代移动应用开发中&#xff0c;色彩的准确性对于提升 UI 质感和用…...

情绪芯片技术评估报告

第一章 需求迷局&#xff1a;当人类焦虑投射到宠物科技 1.1 分离焦虑的转移现象 数据揭示&#xff1a;2025年《伴侣动物心理学期刊》统计显示&#xff0c;73%的宠物主将自身焦虑情绪投射到宠物行为解读 典型案例&#xff1a;硅谷某SaaS测试组长每日通过宠物摄像头检查狗狗&quo…...

Mozz TCAD丨Si PIN二极管仿真

Si PIN二极管仿真 PIN工作原理 我们之前已经对常见的功率器件进行了介绍以及仿真实现。本文对这些功率器件几乎都包含的结构PIN进行介绍与仿真。 在普通 PN 结之间加入一层轻掺杂或本征半导体漂移区&#xff0c;就构成了 PIN 结构&#xff08;P–I–N&#xff09;。这层 I 区…...

无轴承转子产业规模明确:4.21亿元,勾勒高端装备市场发展新图景

在高端装备制造向"高速化、精密化、智能化"转型的浪潮中&#xff0c;无轴承转子凭借"零机械接触动态稳定控制"技术突破&#xff0c;正成为真空泵、半导体设备、医疗离心装置等领域的核心部件。据恒州诚思最新行业报告显示&#xff0c;2025年全球市场规模预…...

DigVPS 测评 - 100TB 新增 Japan - 普通线路 产品详评数据,三网优化,性能不错,限量 9 折出售中。

规格&#xff1a; 摘要&#xff1a; 硬件&#xff1a; 速率&#xff1a; IPv4 质量&#xff1a; ICMP 延迟&#xff1a; TCP 延迟&#xff1a; BGP&#xff1a; 如对该产品感兴趣&#xff0c;想要持续关注其实时与历史数据表现&#xff0c;欢迎访问我们的站点进行长期跟踪。也可…...

P2984 [USACO10FEB] Chocolate Giving S

题目描述 FJ 有 BBB 头奶牛 (1≤B≤25000)(1\le B\le 25000)(1≤B≤25000)&#xff0c;有 N(2B≤N≤50000)N(2\times B\le N\le 50000)N(2B≤N≤50000) 个农场&#xff0c;编号 111 到 NNN&#xff0c;有 M(N−1≤M≤100000)M(N-1\le M\le 100000)M(N−1≤M≤100000) 条双向边&…...

Vector人工智能研究院:传统AI解释方法难以适应智能体时代需求

这项由Vector人工智能研究院等机构联合完成的研究发表于2026年2月&#xff0c;论文编号为arXiv:2602.06841v2&#xff0c;专门探讨了人工智能解释性在传统模型和智能体系统中的根本性差异。有兴趣深入了解的读者可以通过该编号查询完整论文。当我们使用智能手机的语音助手时&am…...

基于SpringBoot+Vue乡村信息管理系统的设计与实现

文末获取源码 开发语言&#xff1a;Java 使用框架&#xff1a;spring boot 前端技术&#xff1a;JavaScript、Vue.js 、css 开发工具&#xff1a;IDEA/MyEclipse/Eclipse、Visual Studio Code 数据库&#xff1a;MySQL 5.7/8.0 数据库管理工具&#xff1a;phpstudy/Navicat JDK…...