几何体编辑与布尔运算
一、算法概述
1.1 算法名称与定位
算法名称:基于OpenCASCADE的几何体编辑与布尔运算算法集
包含的子算法:
- 三维布尔运算(交/并/差/切割)
- 实心体面移动(用户交互平移选中面)
- 面分割(在面上绘制切割线进行分割)
- 整体平移(整模型平移变换)
- 几何体构造(点/线/面/立方体/球/圆柱/棱柱等)
- 多边形几何算法(Delaunay三角化、多边形分解、坐标变换等)
1.2 算法解决的问题
在三维建模和编辑过程中,以下操作是基本且频繁的需求:
- 布尔运算:通过交(∩)、并(∪)、差(-)操作组合多个几何体,实现复杂形状的创建
- 面编辑:用户可以选中模型上的某个面进行平移、旋转等操作,调整局部形状
- 面分割:在模型表面绘制一条线,将面一分为二,为后续编辑做准备
- 多边形处理:对用户绘制的多边形进行三角化、分解、法线计算等
1.3 算法核心实现
所有算法均基于 OpenCASCADE(OCCT) 几何内核实现。OCCT提供了B-Rep(边界表示)的完整数据结构:
TopoDS_Shape--- 几何形状的基类TopoDS_Solid--- 实心体(三维)TopoDS_Face--- 面(二维流形)TopoDS_Edge--- 边(一维流形)TopoDS_Vertex--- 顶点(零维)
二、三维布尔运算
2.1 算法原理
布尔运算基于**B-Rep(边界表示)**模型。给定两个几何体 AAA 和 BBB,OCCT的布尔运算通过以下步骤进行:
第一步:相交计算
计算 AAA 和 BBB 的所有交点/交线,将两个几何体的所有面在相交处打断:
A∩B→{Ainside,Aoutside,Aon}A \cap B \rightarrow \{A_{inside}, A_{outside}, A_{on}\}A∩B→{Ainside,Aoutside,Aon}
B∩A→{Binside,Boutside,Bon}B \cap A \rightarrow \{B_{inside}, B_{outside}, B_{on}\}B∩A→{Binside,Boutside,Bon}
第二步:分类
确定每个面片和边相对于另一个几何体的位置关系(内部/外部/边界上)。
第三步:根据运算类型组合
并集(Union) :
A∪B={Aoutside}∪{Boutside}∪{Aon=Bon}A \cup B = \{A_{outside}\} \cup \{B_{outside}\} \cup \{A_{on}=B_{on}\}A∪B={Aoutside}∪{Boutside}∪{Aon=Bon}
交集(Intersection) :
A∩B={Ainside}∪{Binside}∪{Aon=Bon}A \cap B = \{A_{inside}\} \cup \{B_{inside}\} \cup \{A_{on}=B_{on}\}A∩B={Ainside}∪{Binside}∪{Aon=Bon}
差集(Difference) :
A−B={Aoutside}∪{reversed(Binside)}A - B = \{A_{outside}\} \cup \{\text{reversed}(B_{inside})\}A−B={Aoutside}∪{reversed(Binside)}
2.2 切割运算(Clip)
切割是布尔运算的特殊形式:用用户选定的多边形区域对模型进行裁切。流程如下:
输入: 三角网格 + 多边形区域 + 上下裁剪高度
输出: 裁剪后的三角网格
Step 1: 多边形确定切割平面
├── 用户选定的多边形定义了一个切割区域
├── 将多边形向上和向下扩展(上下高度参数)
└── 形成一个"饼干模"形状的闭合几何体
Step 2: 执行布尔交运算
└── 目标网格 ∩ 切割几何体 → 保留区域
Step 3: 布尔差运算(可选)
└── 目标网格 - 切割几何体 → 移除区域
Step 4: 结果处理
└── 更新裁剪边界的顶点和面索引
此操作常用于:
- 水表面替换:用切割几何体裁掉不规则水面,替换为新生成的平面或曲面
- 局部区域编辑:只对裁切范围内的网格进行操作
三、实心体面移动
3.1 算法原理
面移动是指用户选中模型的一个或多个面,将它们沿指定方向平移。此操作需要保持模型的拓扑完整性------被移动的面必须与相邻面保持连接。
3.2 实现流程
输入: 几何体 + 选中面索引列表 + 平移向量
输出: 平移后的新几何体
Step 1: 建立面索引映射
├── 遍历几何体的所有面,建立 索引 → TopoDS_Face 的映射
└── 同时建立壳(SHELL)的映射
Step 2: 提取选中面的顶点
├── 遍历所有选中面的顶点
├── 记录每个顶点的原始坐标
└── 这些顶点需要被平移
Step 3: 逐壳重建几何体
├── 对几何体中的每个壳(shell)进行处理:
│
├── 遍历壳中的所有面:
│ ├── 遍历面的所有线框(wire):
│ │ └── 遍历线框的所有边(edge):
│ │ ├── 获取边的两个端点
│ │ ├── 判断每个端点是否需要平移
│ │ │ ├── 如果是选中面的顶点 → 使用平移后的新顶点
│ │ │ └── 如果不是 → 保留原顶点
│ │ └── 使用新旧顶点重建边
│ │
│ └── 使用重建的线框重建面
│
└── 将重建后的所有面加入缝合器
Step 4: 缝合所有面
├── 使用缝合器(sewing)将重建的所有面缝合为闭合壳
└── 使用 MakeSolid 生成新的实心体
Step 5: 替换原几何体
└── 将新生成的实心体设为几何体的形状
3.3 关键细节
顶点是否需要平移的判断
通过比较顶点的空间坐标来确定:如果重建边时的端点坐标与选中面的某个顶点坐标相同(在容许精度内),则使用平移后的新顶点。
面对朝向的处理
OCCT中面的朝向(Orientation)会影响面的法线方向。在重建过程中,需要保持面的原始朝向:
if (原始面朝向为反向)
使用反向的线框进行重建
重建后的面也设为反向
else
使用正向的线框进行重建
重建后的面设为正向
缝合精度
使用 BRepOffsetAPI_Sewing 进行缝合时,缝合精度设置为 Precision::Confusion() * 10.0(即默认精度的10倍),以保证复杂几何体也能成功缝合。
四、面分割
4.1 算法原理
面分割是在模型的一个面上沿用户绘制的折线将面切割为两个或多个面。这是实现"平面切割模型"功能的基础。
4.2 实现流程
输入: 几何体 + 面ID + 分割折线顶点序列
输出: 分割后的几何体
Step 1: 构建分割线
├── 对用户绘制的顶点序列:
│ 依次连接相邻顶点生成边
└── 将所有边连接为一条线框(wire)
Step 2: 自相交检查
└── 检查分割线框是否存在自相交
└── 如果自相交 → 返回失败
Step 3: 执行分割
├── 创建分割器(BOPAlgo_Splitter)
├── 添加原始几何体为参数
├── 添加分割线框为工具
└── 执行分割操作
Step 4: 输出结果
├── 检查分割是否成功
└── 将分割后的几何体替换原几何体
4.3 数学原理
分割操作本质上是一个沿非流形边进行的面分裂过程。将一条曲线嵌入到B-Rep模型的面上,相当于在参数空间中将面沿曲线切断:
- 参数化映射:3D空间中的切割线映射到面的参数空间(u,v)
- 参数空间分割:在参数空间中沿切割线将面域一分为二
- 边界更新:更新两个新面的边界,确保它们共享分割线作为公共边
五、整体平移
5.1 算法原理
整体平移是对整个几何体(而非某个面)进行平移变换。由于不涉及拓扑修改,实现比面移动简单得多。
5.2 实现流程
输入: 几何体 + 平移向量 (dx, dy, dz)
输出: 平移后的几何体
Step 1: 构建平移变换矩阵
├── 使用 gp_Trsf 设置平移向量
└── 创建 TopLoc_Location
Step 2: 应用变换
└── 对几何体的每个顶点应用变换:
P_new = P_old + (dx, dy, dz)
Step 3: 替换几何体
└── 将变换后的形状设回几何体
5.3 安全性检查
平移变换必须满足:
- 缩放因子为1(或非常接近1)
- 变换矩阵的行列式为正(不会导致镜像反转)
六、多边形几何算法
6.1 多边形三角化
凸多边形三角化
对于凸多边形,以中心点为公共顶点,其他顶点按顺序连接,形成扇形三角网:
c=1n∑i=0n−1vi\mathbf{c} = \frac{1}{n}\sum_{i=0}^{n-1} \mathbf{v}_ic=n1i=0∑n−1vi
三角化结果:
{T0,T1,...,Tn−2},Ti=(c,vi,v(i+1) mod n)\{T_0, T_1, ..., T_{n-2}\}, \quad T_i = (\mathbf{c}, \mathbf{v}i, \mathbf{v}{(i+1)\bmod n}){T0,T1,...,Tn−2},Ti=(c,vi,v(i+1)modn)
非凸多边形Delaunay三角化
对于非凸多边形(含凹多边形),使用CGAL的约束Delaunay三角化(CDT):以多边形边为约束,对多边形内部的点集进行Delaunay三角剖分。
6.2 多边形Y-单调分解
使用CGAL的 y_monotone_partition_2 算法将非凸多边形分解为多个Y-单调多边形子块:
Y-单调多边形定义:一个多边形如果被任意一条垂直于Y轴的直线切割,交点不超过两个,则为Y-单调多边形。
应用场景:在进行纹理映射时,Y-单调多边形比非凸多边形更容易生成UV坐标。
6.3 近似凸多边形分解
使用CGAL的 approx_convex_partition_2 算法将多边形分解为近似凸的多个子块。
应用场景:凸多边形可以更高效地进行三角化和碰撞检测。
6.4 多边形法线与变换矩阵
法线计算 :
n=∑i=0n−2(vi−c)×(vi+1−c)∥∑(⋯ )∥\mathbf{n} = \frac{\sum_{i=0}^{n-2} (\mathbf{v}i - \mathbf{c}) \times (\mathbf{v}{i+1} - \mathbf{c})}{\|\sum(\cdots)\|}n=∥∑(⋯)∥∑i=0n−2(vi−c)×(vi+1−c)
其中 c\mathbf{c}c 是多边形中心点。
局部坐标系变换矩阵 :
基于多边形中心点和法线构建局部坐标系:
- X轴:多边形第一条边在平面内的投影方向
- Y轴:法线 × X轴
- Z轴:法线方向
变换矩阵:
M=x0y0n0c1\mathbf{M} = \begin{bmatrix} \mathbf{x} & \mathbf{0} \\ \mathbf{y} & \mathbf{0} \\ \mathbf{n} & \mathbf{0} \\ \mathbf{c} & \mathbf{1} \end{bmatrix}M= xync0001
此矩阵用于将多边形顶点从3D世界坐标变换到2D局部坐标,方便进行平面内的三角化和纹理映射。
6.5 桥接网格生成
在两个孔洞的边界边之间生成桥接三角面:
P0 ────── Q0
/ \ / \
/ \ / \
/ \ / \
P1 ────── Q1
算法:
1) 计算两条边界边的长度 L0 = |P1-P0|, L1 = |Q1-Q0|
2) 确定分割数 N = max(ceil(L0/gap), ceil(L1/gap))
3) 在 P0→Q0 和 P1→Q1 方向上均匀插入 N 个插值点
4) 连接对应的插值点生成四边形条带
5) 每个四边形分裂为两个三角形
七、应用场景
7.1 布尔运算应用
| 运算类型 | 三维建模场景 | 具体示例 |
|---|---|---|
| 并集(Union) | 合并多个建筑物单体 | 将分离的屋顶和墙身合并为一个实体 |
| 交集(Intersection) | 提取共有区域 | 计算两个建筑的碰撞区域 |
| 差集(Difference) | 开窗/开门洞 | 从墙体中减去窗户形状 |
| 切割(Clip) | 局部区域编辑 | 裁切水面、修补局部缺陷 |
7.2 面移动应用
| 操作 | 场景 | 示例 |
|---|---|---|
| 面平移 | 建筑立面微调 | 将墙面沿法线方向外扩一定距离 |
| 体推拉 | 实心体编辑 | 拉伸建筑物的某一部分 |
| 整体平移 | 模型对齐 | 将导入模型移动到正确位置 |
7.3 面分割应用
| 操作 | 场景 | 示例 |
|---|---|---|
| 面上画线分割 | 平面切割 | 在模型面上画线,将面分割后删除不需要的部分 |
| 多段线切割 | 复杂轮廓修整 | 沿建筑轮廓线切割多余部分 |
7.4 多边形处理应用
| 操作 | 场景 | 示例 |
|---|---|---|
| 多边形三角化 | 纹理映射 | 将用户绘制的多边形三角化后贴图 |
| 多边形分解 | 简化处理 | 将复杂多边形分解为简单子块 |
| 桥接网格 | 模型修复 | 在两个孔洞之间生成连接网格 |
| UV坐标计算 | 纹理映射 | 将多边形顶点映射到纹理空间 |
八、算法复杂度
| 算法 | 时间复杂度 | 说明 |
|---|---|---|
| 布尔运算(OCCT) | O(n2)O(n^2)O(n2) | nnn为参与运算的面总数 |
| 面移动 | O(F+E+V)O(F+E+V)O(F+E+V) | FFF面数+EEE边数+VVV顶点数 |
| 面分割 | O(k2)O(k^2)O(k2) | kkk为分割线的分段数 |
| 多边形三角化(凸) | O(n)O(n)O(n) | nnn为顶点数 |
| Delaunay三角化 | O(nlogn)O(n\log n)O(nlogn) | nnn为顶点数 |
| 多边形分解 | O(n2)O(n^2)O(n2) | nnn为顶点数 |
| 桥接网格 | O(k)O(k)O(k) | kkk为分割段数 |
九、算法优缺点
9.1 布尔运算
优点:
- 支持任意复杂度的几何体
- OCCT内核成熟稳定,工业级精度
- 支持交/并/差/切割多种运算
缺点:
- 非流形输入可能导致失败
- 运算速度与面数平方成正比
- 小切面/共面/相切情况处理困难
9.2 面移动
优点:
- 保持实心体拓扑结构
- 支持多面同时移动
- 自动缝合相邻面
缺点:
- 移动距离过大时可能导致自相交
- 复杂几何体缝合可能失败
- 不支持非流形几何体
9.3 面分割
优点:
- 支持任意形状的分割线
- 自相交检测防止无效操作
- 与布尔运算内核一致
缺点:
- 分割线必须在面内
- 非平面面分割质量与面曲率相关
9.4 多边形处理
优点:
- 凸多边形三角化极快(O(n)O(n)O(n))
- 支持Y-单调分解和凸分解
- 局部坐标系方便后续处理
缺点:
- 非凸多边形三角化较慢
- 严格凸分解的算法复杂度高,常用近似分解
十、关键公式汇总
-
凸多边形三角化 :c=1n∑vi\mathbf{c} = \frac{1}{n}\sum \mathbf{v}_ic=n1∑vi, Ti=(c,vi,vi+1)T_i = (\mathbf{c}, \mathbf{v}i, \mathbf{v}{i+1})Ti=(c,vi,vi+1)
-
多边形法线 :n=∑(vi−c)×(vi+1−c)∥∑∥\mathbf{n} = \frac{\sum (\mathbf{v}i - \mathbf{c}) \times (\mathbf{v}{i+1} - \mathbf{c})}{\|\sum\|}n=∥∑∥∑(vi−c)×(vi+1−c)
-
局部坐标系变换矩阵 :
M=x0y0n0c1\mathbf{M} = \begin{bmatrix}\mathbf{x} & 0 \\ \mathbf{y} & 0 \\ \mathbf{n} & 0 \\ \mathbf{c} & 1\end{bmatrix}M= xync0001
-
3D→2D变换 :plocal=pworld⋅M−1\mathbf{p}{local} = \mathbf{p}{world} \cdot \mathbf{M}^{-1}plocal=pworld⋅M−1
-
桥接网格分割数 :N=max(⌈L0gap⌉,⌈L1gap⌉)N = \max\left(\left\lceil\frac{L_0}{gap}\right\rceil, \left\lceil\frac{L_1}{gap}\right\rceil\right)N=max(⌈gapL0⌉,⌈gapL1⌉)
-
平移变换 :Pnew=Pold+T\mathbf{P}{new} = \mathbf{P}{old} + \mathbf{T}Pnew=Pold+T
-
布尔运算分类:
- 并集:A∪B={Aoutside}∪{Boutside}∪{Aon=Bon}A\cup B = \{A_{outside}\}\cup\{B_{outside}\}\cup\{A_{on}=B_{on}\}A∪B={Aoutside}∪{Boutside}∪{Aon=Bon}
- 交集:A∩B={Ainside}∪{Binside}∪{Aon=Bon}A\cap B = \{A_{inside}\}\cup\{B_{inside}\}\cup\{A_{on}=B_{on}\}A∩B={Ainside}∪{Binside}∪{Aon=Bon}
- 差集:A−B={Aoutside}∪{reversed(Binside)}A-B = \{A_{outside}\}\cup\{\text{reversed}(B_{inside})\}A−B={Aoutside}∪{reversed(Binside)}