CGAL中的 Nef_polyhedron_3 是处理3D 多面体布尔运算(交、并、差、补)的核心类,特别适合处理带孔洞、非流形、自相交的复杂 3D 几何体,是工业级几何处理的重要工具。Nef_P支持任意拓扑结构的布尔运算,并且能保证布尔运算的精确无误差。
这篇文章基于Nef_polyhedron的参考文档,对Nef_polyhedron做一个学习记录。
参见:CGAL 6.1.1 - 3D Boolean Operations on Nef Polyhedra: User Manual。
1 引言
在实体建模中,主要使用两种表示方案:构造实体几何(CSG)和边界表示(B-rep)。
构造实体几何(CSG) 是一种通过简单几何基元 + 布尔运算构建复杂三维实体的建模方法,核心是用 “搭积木 + 逻辑运算” 描述物体,数据紧凑、修改方便,广泛用于 CAD、3D 打印与游戏开发。“搭积木 + 逻辑运算”这一过程通过逻辑树来完成,CSG模型并不是真正的几何实体,无法直接渲染 / 显示,需实时计算边界(BRep 转换)
B-rep:描述实体边界上所有低维特征(顶点、边、面)的邻接关系与几何属性,是存顶点、边、面,真正的网格。但普通网格一做布尔就容易崩:出现洞、细线、尖点、多个面贴在一起 → 流行结构变成 非流形。
而CGAL团队设计的Nef多面体就很好的将上面两个结合起来:
Nef多面体是真正的网格,能够存储点线面,能够处理带洞、空心、交叉、非流形、无限大平面……等任何复杂形状,并且bool运算不会崩溃。
2 定义
Nef 多面体 = 从无限大平面(半空间)出发,通过交、并、差、补,拼出来的任何 3D 形状。
这个类的层次关系
/*
Nef_polyhedron_3 ← 外壳类(对外接口)
↓
Nef_polyhedron_3_rep ← 实际存储数据的“本体”
↓
SNC_structure ← 核心SNC 结构的具体实现
↓
Vertex/Edge/Facet/Volume ← 3D 全局几何元素
Sphere_map (球面映射) ← 顶点局部邻域结构
*/
Nef_polyhedron_3:对外接口,主要用到这个。
Nef_polyhedron_3_rep:两个核心成员
SNC_structure snc_; // 真正的 SNC 结构(顶点/边/面/体+拓扑)
SNC_point_locator* pl_; // 点定位器,用于判断一个点是否在nef内部
SNC_structure:核心数据结构:定义如下
| 元素 | 维度 | 作用 | 关键特性 |
|---|---|---|---|
| Vertex | 0D | 顶点 | 每个顶点绑定一个 Sphere_map(球面映射) |
| Halfedge | 1D | 有向边(正反两条) | 互指反向边,关联顶点 + 面 |
| Halffacet | 2D | 有向面(正反两个) | 由边构成环(外环 + 内环),关联体 |
| Volume | 3D | 体(空间区域) | 由壳(Shell)组成,标记 “内部 / 外部” |
Sphere_map(球面映射):SNC_structure下的另一个结构,每个顶点都有一个 “球面映射”,这是 Nef 能处理非流形的核心,Nef在顶点处套一个无限小的球,球面上的几何 / 拓扑结构(边、面的交点和连线)就是这个顶点的球面映射;它用 2D 球面拓扑,完整描述了 3D 顶点周围的所有邻接关系,哪怕是非流形顶点也能精准表示。

把顶点周围的所有面 / 边,与这个小球求交:
边与球面的交点 → 球面映射的顶点(SVertex)
面与球面的交线 → 球面映射的边(SHalfedge)
球面被分割后的区域 → 球面映射的面(SFace)

3 Infimaximal Box 无穷极大盒
用一个 足够大但有限 的虚拟盒子,把无限的东西 框住,让计算机能像处理普通有限模型一样计算。
4 正则化布尔运算
普通布尔运算可能会产生:孤点,细线,薄片
这些现实中不存在,也没用。
正则化 = 自动把这些垃圾去掉,只保留真正的 “实体”。
公式:正则化 = 先取内部,再取闭包
5 示例程序
下面将展示官网给的几个示例,方便快速上手这个类,每个示例中详细解释。
这些例子中涉及到CGAL中的代数运算内核,Surface_mesh 类,Polyhedron_3类,以及简单可视化。
#include<CGAL/Exact_integer.h>
#include<CGAL/Homogeneous.h>
#include<CGAL/Nef_polyhedron_3.h>
typedef CGAL::Homogeneous<CGAL::Exact_integer> Kernel;
typedef CGAL::Nef_polyhedron_3<Kernel> NefP;
void test1() {
//空集
NefP N0(NefP::EMPTY);
//全空间
NefP N1(NefP::COMPLETE);
//complement() 求补集
assert(N0==N1.complement());
assert(N0!=N1);
}
显然空集的补集等于全集。
5.1 构造和比较
#include<CGAL/Exact_integer.h>
#include<CGAL/Extended_homogeneous.h>
#include<CGAL/Nef_polyhedron_3.h>
#include<cassert>
typedef CGAL::Extended_homogeneous<CGAL::Exact_integer> Kernel;
typedef CGAL::Nef_polyhedron_3<Kernel> NefP;
typedef NefP::Plane_3 Plane_3;
void test2(){
NefP N0; //默认空集构造
NefP N1(NefP::EMPTY); //空集构造
NefP N2(NefP::COMPLETE); //全集构造
NefP N3(Plane_3(1, 2, 5, -1)); //指定平面构造,默认半空间包含平面
NefP N4(Plane_3(1, 2, 5, -1), NefP::INCLUDED); //指定平面构造,包含平面
NefP N5(Plane_3(1, 2, 5, -1), NefP::EXCLUDED); //指定平面构造,不包含平面
assert(N0 == N1); //空集相等
assert(N3 == N4); //包含平面和默认半空间相等
assert(N0 != N2); //空集和全集不相等
assert(N3 != N5); //包含平面和不包含平面不相等
assert(N4 >= N5);
assert(N5 <= N4);
assert(N4 > N5);
assert(N5 < N4);
N5 = N5.closure(); //闭包操作后包含平面
assert(N4 >= N5);
assert(N4 <= N5);
}
值得注意的是,对于
Nef_polyhedron Nef(Plane_3(A,B,C,D));
Nef所代表的半空间为:Ax+By+Cz+D<=0 , 例子中已经展示了默认是带INCLUDE包含平面。
为什么是<=0,官方文档如下:

创建一个 Nef 多面体,该多面体包含位于p负侧的半空间,若b==INCLUDED则包含p,若b==EXCLUDED则排除p。
5.2 点集运算
为了方便理解,下图展示了Nef布尔运算的符号表示

#include<CGAL/Exact_integer.h>
#include<CGAL/Extended_homogeneous.h>
#include<CGAL/Nef_polyhedron_3.h>
#include<cassert>
typedef CGAL::Extended_homogeneous<CGAL::Exact_integer> Kernel;
typedef CGAL::Nef_polyhedron_3<Kernel> NefP;
typedef NefP::Plane_3 Plane_3;
void test3() {
//半空间的判断,对于平面Ax+By+Cz+D=0;
//有NefP N(Plane_3(A, B, C, D));
//则N表示半空间Ax+By+Cz+D>=0,包含平面
NefP N1(Plane_3(1, 0, 0, -1)); //x=1 半空间X<=1
NefP N2(Plane_3(-1, 0, 0, -1)); //x=-1 半空间X>=-1
NefP N3(Plane_3(0, 1, 0, -1)); //y=1 半空间Y<=1
NefP N4(Plane_3(0, -1, 0, -1)); //y=-1 半空间Y>=-1
NefP N5(Plane_3(0, 0, 1, -1)); //z=1 半空间Z<=1
NefP N6(Plane_3(0, 0, -1, -1)); //z=-1 半空间Z>=-1
NefP I1(!N1 + !N2); //x<-1 u x>1
NefP I2(N3 - !N4); //-1<=y<=1
NefP I3(N5 ^ N6); //Z<-1 u Z>1
NefP Cube1(I2*!I1); //y=[-1,1] ,x=[-1,1]
Cube1 *= !I3;//Cube1=Cube1*!I3 //y=[-1,1] ,x=[-1,1],z=[-1,1]
NefP Cube2 = N1 * N2 * N3 * N4 * N5 * N6; //y=[-1,1] ,x=[-1,1],z=[-1,1]
assert(Cube1 == Cube2);
assert(Cube1 == Cube1.closure()); //cube1本身就是闭集
//
assert(Cube1 == Cube1.regularization()); //正则化,闭集的正则化是它自己
//
assert((N1 - N1.boundary()) == N1.interior());//闭集减去边界是内部
assert(I1.closure() == I1.complement().interior().complement()); //闭集的闭包是它的补集的内部的补集
assert(I1.regularization() == I1.interior().closure()); //正则化是内部的闭包
}
5.3 仿射变换
#include<CGAL/Exact_integer.h>
#include<CGAL/Extended_homogeneous.h>
#include<CGAL/Nef_polyhedron_3.h>
#include<CGAL/IO/Nef_polyhedron_iostream_3.h>
#include<cassert>
typedef CGAL::Extended_homogeneous<CGAL::Exact_integer> Kernel;
typedef CGAL::Nef_polyhedron_3<Kernel> NefP;
typedef NefP::Plane_3 Plane_3;
typedef NefP::Vector_3 Vector_3;
typedef NefP::Aff_transformation_3 Aff_transformation_3;
void test4() {
NefP N1(Plane_3(0,1,0,0)); //y<=0
//平移变换:x'=x+5,y'=y+7,z'=z+9
Aff_transformation_3 translate(CGAL::TRANSLATION, Vector_3(5,7,9)); //y+7<=0
//矩阵变换,对任意点 (x,y,z),变换后为 (x,−z,y); (0,1,0,7)->(0,0,1,7) ->z-7<=0
Aff_transformation_3 rotX90(
1,0,0,
0,0,-1,
0, 1, 0,
1); //最后一个1为齐次因子 //z-7<=0
//缩放,缩放倍数为3/2
Aff_transformation_3 scale(CGAL::SCALING, 3, 2); //2z-21<=0
N1.transform(translate);
assert(N1 == NefP(Plane_3(0, 1, 0, -7)));
N1.transform(rotX90);
assert(N1 == NefP(Plane_3(0, 0, 1, -7)));
N1.transform(scale);
assert(N1 == NefP(Plane_3(0, 0, 2, -21)));
}
拓展:
Aff_transformation_3是CGAL代数内核中的一个类,主要提供仿射变换,支持:
| 常量名称 | 中文释义 |
|---|---|
CGAL::TRANSLATION | 平移变换 |
CGAL::ROTATION | 旋转变换 |
CGAL::SCALING | 缩放变换 |
CGAL::REFLECTION | 反射变换(镜像) |
CGAL::IDENTITY | 恒等变换 |
此外还支持矩阵变换。
5.4 Polyhedron_3 转 Nef_Polyhedron_3
#include<CGAL/Exact_integer.h>
#include<CGAL/Homogeneous.h>
#include<CGAL/Polyhedron_3.h>
#include<CGAL/Nef_polyhedron_3.h>
#include<CGAL/IO/Nef_polyhedron_iostream_3.h>
#include<cassert>
#include<iostream>
using namespace std;
typedef CGAL::Homogeneous<CGAL::Exact_integer> Kernel;
typedef CGAL::Polyhedron_3<Kernel> Polyhedron;
typedef CGAL::Nef_polyhedron_3<Kernel> Nef_Polyhedron;
typedef Kernel::Vector_3 Vector_3;
typedef Kernel::Aff_transformation_3 Aff_transformation_3;
void test5() {
Polyhedron P;
ifstream input_file("D:/cube.off");
if (!input_file.is_open()) {
cout << "error opening file" << endl;
}
input_file >> P;
input_file.close();
if (P.empty()) {
cout << "error reading file" << endl;
}
if (P.is_closed()) {
Nef_Polyhedron N1(P);
Nef_Polyhedron N2(N1);
Aff_transformation_3 aff(CGAL::TRANSLATION,Vector_3(2,2,3,1));
N2.transform(aff);
N1 += N2;
if (N1.is_simple()) {
ofstream output_file("D:/union.off");
if (!output_file.is_open()) {
cout << "error opening output file" << endl;
}
N1.convert_to_Polyhedron(P);
output_file << P;
output_file.close();
cout << "成功写入"<<endl;
}
else
cerr << "N1不是2流行" << endl;
}
else {
std::cout << "input polyhedron is not closed!" << std::endl;
}
}
这里的cube.off文件可以在CGAL的example文档下找到:路径为examples\data\meshs\cube.off。
当然你也可以创建一个记事本,修改名称为cube.off,粘贴如下内容:
OFF
8 12 0
-1 -1 -1
-1 1 -1
1 1 -1
1 -1 -1
-1 -1 1
-1 1 1
1 1 1
1 -1 1
3 0 1 2
3 0 2 3
3 0 3 4
3 0 4 1
3 1 4 5
3 1 5 2
3 2 5 6
3 2 6 3
3 3 6 7
3 3 7 4
3 4 7 5
3 5 7 6
这个例子主要做的事为:将cube.off文件表示的立方体,从Polyhedron转换为Nef_polyhedron格式的N1,在通过拷贝构造N2,对N2做平移变换,之后布尔求和得到新的N1,最后再将N1输出为Polyhedron格式。
我们用meshlab打开这个cube.off ,和union.off文件
如下图


左图为cube.off,右图为union.off,
值得注意的是:
Aff_transformation_3 aff(CGAL::TRANSLATION,Vector_3(2,2,3,1));
中的平移参数最后一个1为齐次因子,省略也行,另外如果参数太小,平移变换的长度小于边长,这两个cube就会重叠,N1就不再是2流型。
5.5 Nef_polyhedron转换为Polyhedron
这个例子展示了Nef_polyhedron转换为Polyhedron的过程
#include<CGAL/Exact_predicates_exact_constructions_kernel.h>
#include<CGAL/Nef_polyhedron_3.h>
#include<CGAL/Polyhedron_3.h>
#include<CGAL/Surface_mesh.h>
#include<CGAL/boost/graph/convert_nef_polyhedron_to_polygon_mesh.h>
#include <CGAL/draw_polyhedron.h>
#include <CGAL/draw_surface_mesh.h>
#include<iostream>
#include<fstream>
#include<sstream>
typedef CGAL::Exact_predicates_exact_constructions_kernel Exact_kernel;
typedef CGAL::Polyhedron_3<Exact_kernel> Polyhedron;
typedef CGAL::Surface_mesh<Exact_kernel::Point_3> Surface_mesh;
typedef CGAL::Nef_polyhedron_3<Exact_kernel> Nef_polyhedron;
void fill_cube_1(Polyhedron& poly)
{
std::string input =
"OFF\n\
8 12 0\n\
-1 -1 -1\n\
-1 1 -1\n\
1 1 -1\n\
1 -1 -1\n\
-1 -1 1\n\
-1 1 1\n\
1 1 1\n\
1 -1 1\n\
3 0 1 3\n\
3 3 1 2\n\
3 0 4 1\n\
3 1 4 5\n\
3 3 2 7\n\
3 7 2 6\n\
3 4 0 3\n\
3 7 4 3\n\
3 6 4 7\n\
3 6 5 4\n\
3 1 5 6\n\
3 2 1 6";
std::stringstream ss;
ss << input;
ss >> poly;
}
void fill_cube_2(Polyhedron& poly)
{
std::string input =
"OFF\n\
8 12 0\n\
-0.5 -0.5 -0.5\n\
-0.5 0.5 -0.5\n\
0.5 0.5 -0.5\n\
0.5 -0.5 -0.5\n\
-0.5 -0.5 0.5\n\
-0.5 0.5 0.5\n\
0.5 0.5 0.5\n\
0.5 -0.5 0.5\n\
3 0 1 3\n\
3 3 1 2\n\
3 0 4 1\n\
3 1 4 5\n\
3 3 2 7\n\
3 7 2 6\n\
3 4 0 3\n\
3 7 4 3\n\
3 6 4 7\n\
3 6 5 4\n\
3 1 5 6\n\
3 2 1 6";
std::stringstream ss;
ss << input;
ss >> poly;
}
void test6() {
Polyhedron c1, c2;
fill_cube_1(c1);
fill_cube_2(c2);
Nef_polyhedron n1(c1), n2(c2);
Nef_polyhedron n3 = n1-n2;
Surface_mesh output;
CGAL::convert_nef_polyhedron_to_polygon_mesh(n3,output);
std::ofstream out_file("D:/output.off");
out_file << output;
out_file.close();
}
这个例子展示了Nef_polyhedron转换为Polyhedron的过程,c1和c2都是立方体只是边长不同,通过布尔运算从n1中剔除n2,同样用Mehslab展示output.off文件:

可以看到在大立方体中心挖去了一个小立方体。
5.6 Extended Kernel的使用
#include<CGAL/Exact_integer.h>
//Extended_homogeneous.h支持无限点,用于表示半空间和无限平面
#include<CGAL/Extended_homogeneous.h>
#include<CGAL/Nef_polyhedron_3.h>
#include<CGAL/IO/Nef_polyhedron_iostream_3.h>
#include<CGAL/Polyhedron_3.h>
#include<fstream>
typedef CGAL::Exact_integer NT;
typedef CGAL::Polyhedron_3<CGAL::Extended_homogeneous<NT>> Polyhedron;
//vc++写法,等价于typedef CGAL::Extended_homogeneous<NT> Kernel;
struct Kernel :public CGAL::Extended_homogeneous<NT> {};
typedef CGAL::Nef_polyhedron_3<Kernel> Nef_polyhedron;
typedef Nef_polyhedron::Point_3 Point_3;
//有理数类型
typedef Nef_polyhedron::RT RT;
typedef Nef_polyhedron::Plane_3 Plane_3;
typedef Nef_polyhedron::Vertex_const_iterator Vertex_const_iterator;
void test7() {
Polyhedron P;
std::ifstream input_file("D:/cube.off");
if (!input_file.is_open()) {
std::cout << "error opening file" << std::endl;
return;
}
input_file >> P;
input_file.close();
Nef_polyhedron N(P);
Vertex_const_iterator v;
for (v = N.vertices_begin(); v != N.vertices_end();++v) {
//获取当前顶点坐标
Point_3 p(v->point());
//hx()、hy()、hz()分别是点p的x、y、z坐标的齐次坐标分量
if (p.hx().degree()>0||p.hy().degree()>0||p.hz().degree()>0) {
std::cout << "extended vertex at" << p << std::endl;
}
else {
std::cout << "standard vertex at" << p << std::endl;
}
//坐标原点,RT(0,1)==0/1
if (p==Point_3(RT(0,1),RT(0,1),RT(0,1))) {
std::cout << "found vertex (right,back,top) of the infimaximal box" << std::endl;
}
}
}
Extended_homogeneous.h支持无限点,用于表示半空间和无限平面,相比于Homogeneous范围更大。
5.7 画Nef Polyhedron
#include<CGAL/Exact_predicates_exact_constructions_kernel.h>
#include<CGAL/Nef_polyhedron_3.h>
#include<CGAL/Polyhedron_3.h>
#include<CGAL/draw_nef_3.h>
#include<CGAL/Surface_mesh/Surface_mesh.h>
#include<CGAL/boost/graph/convert_nef_polyhedron_to_polygon_mesh.h>
#include<fstream>
#include<iostream>
using namespace std;
typedef CGAL::Exact_predicates_exact_constructions_kernel Kernel;
typedef CGAL::Polyhedron_3<Kernel> Polyhedron;
typedef CGAL::Nef_polyhedron_3<Kernel> Nef_polyhedron;
typedef CGAL::Surface_mesh<Kernel::Point_3> Surface_mesh;
void test8(int argc,char** argv) {
Polyhedron P1, P2;
ifstream input_file1((argc>1?argv[1]:CGAL::data_file_path("D:/CGAL_examples/data/meshes/cross_quad.off")));
input_file1 >> P1;
input_file1.close();
ifstream input_file2((argc > 2 ? argv[2] : CGAL::data_file_path("D:/CGAL_examples/data/meshes/beam.off")));
input_file2 >> P2;
input_file2.close();
Nef_polyhedron N1(P1);
Nef_polyhedron N2(P2);
Nef_polyhedron sub = N1 - N2;
Surface_mesh mesh;
CGAL::convert_nef_polyhedron_to_polygon_mesh(sub,mesh);
ofstream output_file("D:/cross.off");
output_file << mesh;
output_file.close();
//CGAL::draw(sub);
}
这个例子主要解释如何可视化Nef, 依旧是CGAL::draw,不过需要有QT依赖,展示

6 IO操作
下面两个示例是CGAL官方给的IO操作的方法
#include <CGAL/Exact_predicates_exact_constructions_kernel.h>
#include <CGAL/Polyhedron_3.h>
#include <CGAL/Nef_polyhedron_3.h>
#include <CGAL/IO/Nef_polyhedron_iostream_3.h>
typedef CGAL::Exact_predicates_exact_constructions_kernel Kernel;
typedef CGAL::Polyhedron_3<Kernel> Polyhedron;
typedef CGAL::Nef_polyhedron_3<Kernel> Nef_polyhedron;
int main() {
Polyhedron P;
std::cin >> P;
Nef_polyhedron N(P);
std::cout << "Exact_predicates_exact_constructions_kernel + SNC_indexed_items"
<< std::endl
<< " allows efficient handling of input "
"using floating point coordinates"
<< std::endl;
if(N.is_simple()) {
N.convert_to_polyhedron(P);
std::cout << P;
}
else {
std::cout << N;
}
}
#include <CGAL/Exact_integer.h>
#include <CGAL/Homogeneous.h>
#include <CGAL/Extended_homogeneous.h>
#include <CGAL/Nef_polyhedron_3.h>
#include <CGAL/IO/Nef_polyhedron_iostream_3.h>
#include <fstream>
typedef CGAL::Exact_integer NT;
typedef CGAL::Homogeneous<NT> SK;
typedef CGAL::Extended_homogeneous<NT> EK;
typedef CGAL::Nef_polyhedron_3<SK> Nef_polyhedron_S;
typedef CGAL::Nef_polyhedron_3<EK> Nef_polyhedron_E;
int main() {
Nef_polyhedron_E E;
Nef_polyhedron_S S;
std::cin >> E;
if(E.is_bounded()) {
std::ofstream out("temp.nef3");
out << E;
std::ifstream in("temp.nef3");
in >> S;
}
}
7 更多示例程序
7.1 探索球面映射
通过get_sphere_map()函数遍历球面映射,该函数会将指定顶点的球面映射以Nef_polyhedron_S2类型返回。
#include<CGAL/Exact_integer.h>
#include<CGAL/Homogeneous.h>
#include<CGAL/Nef_polyhedron_3.h>
#include<CGAL/IO/Nef_polyhedron_iostream_3.h>
#include<CGAL/Polyhedron_3.h>
#include<iostream>
typedef CGAL::Exact_integer RT;
typedef CGAL::Homogeneous<RT> Kernel;
typedef CGAL::Polyhedron_3<Kernel> Polyhedron;
typedef CGAL::Nef_polyhedron_3<Kernel> Nef_polyhedron;
typedef Nef_polyhedron::Vertex_const_iterator Vertex_const_iterator;
typedef Nef_polyhedron::Nef_polyhedron_S2 S2;
typedef S2::SVertex_const_handle SVertex_const_handle;
typedef S2::SHalfedge_const_handle SHalfedge_const_handle;
typedef S2::SHalfloop_const_handle Shalfloop_const_handle;
typedef S2::SFace_const_iterator SFace_const_iterator;
typedef S2::SFace_cycle_const_iterator SFace_cycle_const_iterator;
void test3() {
//
Polyhedron P;
std::ifstream input("D:/cube.off");
if (!input.is_open()) {
std::cout << "error" << std::endl;
}
input >> P;
input.close();
Nef_polyhedron N(P);
//获取N的起始顶点的表面球
S2 S(N.get_sphere_map(N.vertices_begin()));
int i = 0;
//遍历球面映射的每一个曲面
for (SFace_const_iterator sf = S.sfaces_begin();sf!=S.sfaces_end();++sf) {
//遍历每个曲面上的元素
std::cout << "Sface on sphere index:" << i++ << " start with an element:" << std::endl;
for (SFace_cycle_const_iterator it = sf->sface_cycles_begin(); it != sf->sface_cycles_end();++it) {
if (it.is_svertex()) {
SVertex_const_handle vh(it);
std::cout << "vertex with location" << vh->point() << std::endl;
}
else if (it.is_shalfedge()) {
SHalfedge_const_handle hh(it);
std::cout << "Halfedge with source" << hh->source()->point() << " to " << hh->target()->point() << std::endl;
}
else if (it.is_shalfloop()) {
Shalfloop_const_handle lh(it);
std::cout << "loop int plane" << lh->circle() << std::endl;
}
}
}
}
7.2 探索壳结构
Nef 多面体的 Shell(外壳) 是依附于某个 Volume(体积块)的连通表面。每个半面(halffacet)、球面面(sface)、球面半边(shalfedge)都只属于一个 Shell。
Nef多面体的壳是与某个体积相关联的曲面连通部分。下图阐释了壳的概念,展示了一个包含两个体积与三个壳的Nef多面体。

图里是一个「悬浮正方形片 + 带天线的立方体」的 Nef 多面体:
- Volume 1:外部空间(整个世界除了立方体内部)
- Volume 2:立方体的实心内部
对应的 3 个 Shell:
- Shell 1:左边悬浮正方形片的完整表面
- Shell 2:立方体的外表面,包括天线外壁
- Shell 3:立方体的内表面,内壁,把立方体内部和外部隔开
每个shell的构成
shell1
- 2 个 halffacet(正反两个半面)
- 8 个 shalfedge(4 条边,每条拆成 2 个方向相反的半边)
- 4 个顶点
shell2
- 顶点:立方体 8 个顶点 + 天线顶端端点
- halffacet:所有朝外的半面
- shalfedge:所有外表面的半边
shell3
- 顶点:立方体 8 个顶点 + 天线与立方体连接的端点
- halffacet:所有朝内的半面
- shalfedge:和 Shell 2 是同一批边(只是方向朝内)
如下图,我们放大看天线根部,分析球面图上的元素归属:
- 球面图上有 3 个元素:
- 两个
shalfloop(大圆半环):分别对应和小球相交的两个半面 - 一个
svertex:天线穿过小球的点
- 两个
- 上方
shalfloop:在朝外的半面上 → 属于 Shell 2(外表面) - 下方
shalfloop:在朝内的半面上 → 属于 Shell 3(内表面) svertex:和外表面关联 → 属于 Shell 2

这个球就是天线根部顶点的表面球映射,对应的数据结构是Nef_polyhedron_S2,参见
CGAL 6.1.1 - 2D Boolean Operations on Nef Polygons Embedded on the Sphere
下面展示的是读取一个 3D 模型,遍历它的所有体积和外壳,找到每个外壳上坐标最小的顶点并输出。
#include<CGAL/Exact_integer.h>
#include<CGAL/Homogeneous.h>
#include<CGAL/Nef_polyhedron_3.h>
#include<CGAL/IO/Nef_polyhedron_iostream_3.h>
#include<CGAL/Polyhedron_3.h>
#include<iostream>
using namespace std;
typedef CGAL::Homogeneous<CGAL::Exact_integer> Kernel;
typedef CGAL::Nef_polyhedron_3<Kernel> Nef_polyhedron;
typedef CGAL::Polyhedron_3<Kernel> Polyhedron;
typedef Nef_polyhedron::Vertex_const_handle Vertex_const_handle;
typedef Nef_polyhedron::Halfedge_const_handle Halfedge_const_handle;
typedef Nef_polyhedron::Halffacet_const_handle Halffacet_const_handle;
typedef Nef_polyhedron::SHalfedge_const_handle SHalfedge_const_handle;
typedef Nef_polyhedron::SHalfloop_const_handle SHalfloop_const_handle;
typedef Nef_polyhedron::SFace_const_handle SFace_const_handle;
typedef Nef_polyhedron::Volume_const_iterator Volume_const_iterator;
typedef Nef_polyhedron::Shell_entry_const_iterator Shell_entry_const_iterator;
typedef Kernel::Point_3 Point_3;
class Shell_explorer {
bool first; //是不是第一个点
Vertex_const_handle v_min; //保存最小的点
public:
Shell_explorer():first(true){}
//如果是第一个点或者新点更小,更新最小的点
void visit(Vertex_const_handle v){
if (this->first||CGAL::lexicographically_xyz_smaller(v->point(),v_min->point())) {
v_min = v;
first = false; //不在是第一个
}
}
void visit(Halfedge_const_handle){}
void visit(Halffacet_const_handle){}
void visit(SHalfedge_const_handle){}
void visit(SHalfloop_const_handle){}
void visit(SFace_const_handle){}
Vertex_const_handle& minimal_vertex() { return this->v_min; }
void reset_minimal_vertex() { first = true; }
};
void test4() {
Polyhedron P;
ifstream input("D:/cube.off");
input >> P; //读取Polyhedron_3模型
Nef_polyhedron N(P); //转换为Nef_polyhedron
int ic = 0;
Volume_const_iterator c;
Shell_explorer SE;
//遍历多面体的所有体元
CGAL_forall_volumes(c, N) {
cout << "Volume" << ic++ << endl;
int is = 0;
Shell_entry_const_iterator it;
//遍历这个体元的所有外壳
CGAL_forall_shells_of(it, c) {
//先假设第一个点为最小点
SE.reset_minimal_vertex();
//CGAL 自动把这个外壳上的 点、边、面 全部遍历一遍,每遇到一个元素,就调用 SE.visit(..)
// 因为我们只实现了顶点的 visit,所以:只有顶点会被处理,自动找到最小顶点
N.visit_shell_objects(SFace_const_handle(it),SE);
Point_3 p(SE.minimal_vertex()->point());
cout << "minimal vertex of shell" << is++ << "is at" << p << endl;
}
}
}
函数:
visit_shell_objects(SFace_const_handle sf, Visitor& V)
作用:从一个球面面(SFace)sf 开始,遍历整个 Shell,并将 Shell 内的所有元素依次交给访问器 V 处理。
参数要求:第二个参数 V 必须是一个类,且提供以下 visit 方法(可以是空实现):
visit(Vertex_const_handle):处理 3D 顶点visit(Halfedge_const_handle):处理半边(注意:Halfedge与SVertex是同一类型)visit(Halffacet_const_handle):处理半面visit(SHalfedge_const_handle):处理球面半边visit(SHalfloop_const_handle):处理球面半环visit(SFace_const_handle):处理球面面
程序的整个步骤
- 对每个 Shell,调用
reset_minimal_vertex()初始化访问器。 - 调用
visit_shell_objects(sf, SE),CGAL 自动遍历 Shell 内所有元素。 - 每当遇到顶点时,
SE.visit(Vertex_const_handle)会被触发,更新最小顶点。 - 遍历结束后,通过
SE.minimal_vertex()获取该 Shell 的最小顶点并输出。 - 重复上述步骤,处理所有 Shell
7.3 点定位
这个示例主要展示了点定位的方法,传给locate()一个具体的点,可以拿到这个点所在结构的抽象对象,适用于判断点是否在内部,碰撞检测等。
#include<CGAL/Exact_integer.h>
#include<CGAL/Homogeneous.h>
#include<CGAL/Nef_polyhedron_3.h>
#include<CGAL/IO/Nef_polyhedron_iostream_3.h>
#include<CGAL/Polyhedron_3.h>
#include<iostream>
using namespace std;
typedef CGAL::Homogeneous<CGAL::Exact_integer> Kernel;
typedef CGAL::Nef_polyhedron_3<Kernel> Nef_polyhedron;
typedef CGAL::Polyhedron_3<Kernel> Polyhedron;
typedef Kernel::Point_3 Point_3;
typedef Nef_polyhedron::Vertex_const_handle Vertex_const_handle;
typedef Nef_polyhedron::Halfedge_const_handle Halfedge_const_handle;
typedef Nef_polyhedron::Halffacet_const_handle Halffacet_const_handle;
typedef Nef_polyhedron::Volume_const_handle Volume_const_handle;
typedef Nef_polyhedron::Object_handle Object_handle;
void test5() {
Polyhedron P;
ifstream input("D:/cube.off");
input >> P;
Nef_polyhedron N(P);
Vertex_const_handle v;
Halfedge_const_handle e;
Halfedge_const_handle f;
Volume_const_handle c;
Object_handle o = N.locate(Point_3(0,0,0));
if (CGAL::assign(v,o)) {
cout << "Locating vertex" << endl;
}
else if(CGAL::assign(e,o)){
cout << "Locating edge" << endl;
}
else if(CGAL::assign(f,o)){
cout << "Locating facet" << endl;
}
else if(CGAL::assign(c,o)){
cout << "Locating volume" << endl;
}
}
int main() {
test5();
return 0;
}
1995

被折叠的 条评论
为什么被折叠?



