PaperRead - Efficient Booleans algorithms for triangulated meshes of geometric modeling
Paper - Efficient Booleans algorithms for triangulated meshes of geometric modeling
Jiang X, Peng Q, Cheng X, et al. Efficient Booleans algorithms for triangulated meshes of geometric modeling[J]. Computer-aided Design and Applications, 2016, 13(4): 419-430.
更多introduction的介绍可以直接看原文,这里主要介绍文中使用的具体实现。
2. Methods
更个过程分为两个阶段:两个mesh之间的相交检测和布尔运算。如下图Fig2。
这篇文章的主要贡献点如下:
- 利用八叉树对两个网格的公共空间进行划分,以加快交叉点检测的速度,减少内存占用;
- 分析了浮点运算误差和交叉口奇异性,提高了算法的稳定性;
- 为了实现并、交、差运算,提出了基于相交三角形的稳定技术。该方法对封闭网格和开放网格都是快速的;
- 该算法具有较强的鲁棒性,可用于含有大量布尔差分运算的铣削仿真系统中;
2.1 相交检测
相交检测在布尔运算中是很重要的一布。
2.1.1 Building Octree of the common space
对于两个相交的mesh而言,相交部分的空间的大小是小的。octree能够利用空间划分加速相交测试。
给定两个网格\(S_A\)和\(S_B\)。\(Box_A\)和\(Box_B\)分别为\(S_A\)和\(S_B\)的最小AABB。公式如下:
\(Box_B\)可以用相同方式计算。两个的交集如下:
为了保证能够把相交三角形都包含在内部,对上面的式子进行了扩展,如下:
其中\(l\)为\(S_A\)和\(S_B\)中的最长边。
2.1.2 Floating point arithmetic errors and singularity of intersections
浮点运算在计算几何中是不可避免的。尤其是布尔运算,在计算相交性的时候会经常用到。浮点运算会导致求交的不连续性,从而进一步导致布尔运算的失败。
布尔运算相关的相交计算,可以认为是很多的ray-triangle之间的测试。将一条射线与一个三角形相交比普通的“解决方案”问题要大得多。
上图说明了为什么浮点运算会引起最后结果计算失败。也展示了如何进行鲁棒性更强的计算。图4a所示,一条射线与\(\triangle abc\)和\(\triangle abd\)的公共边ab相交。由于浮点计算误差的存在,可能出现这种情况,在计算\(\triangle abc\)和射线R的交点的时候,计算出点p稍微在ab的右边,在计算\(\triangle abd\)和射线R的交点的时候,计算出点p稍微在ab的左边。这样判断的结果是射线和\(\triangle abc\)、\(\triangle abd\)都不相交。为了使得检测具有更高的鲁棒性,将射线认为是“fat”点的几何体。图4d所示。
fat test就是容忍度的问题。如图e所示,计算的时候得到两条线的交点为\(P(x',y')\),给定容忍度\(\epsilon\),P左边的直线的距离小于容忍度,就认为点在左边的直线上。
交叉点的奇异性也会破坏相交计算。奇异性通常发生在边和面之间具有不同的角度的时候。四面体的一条边和top面以很大的角度相交,而和front面以较小的角度相交。这两个面共享ef边。在相交检测的时候,m点到ef边的距离为d1小于容忍度,m*和ef边的距离d2大于容忍度,但对于上表面而言,点m位于ef边上,这样就产生了奇异性。这样四面体就和front面相交了两次,这将破坏交叉循环(???),因此算法将失败。
2.1.3 计算相交线
相交线计算的时候需要考虑两种情况,共面还是不共面,针对共面问题,可以转换为2D 三角形相交问题(注意:此处需要注意的是,针对共面的情况,有两种,一种是两个三角形共面,另一种是一个三角形的边和另一个三角形共面)。这里主要介绍不共面时候相交线的计算。为了保证交叉点的连续性,提出了一种改进的交叉点检测策略。
通常情况下,有三种相交情况(如下图所示):
- 交点为边的顶点,EndP;
- 在边上有一个交点,EdgeP;
- 面内有一个交点,FaceP;
注意:下图对论文中的相交的场景,额外补充了两种场景。此时对应的相交情况如下(其中边是一个三角形的,面是另一个三角形的,令EdgeV的权重为2,EdgeP的权重为1,FaceP的权重为0):
- 边的顶点,面内的点;EdgeVFaceP, EdgeVFaceP (2+0 = 2)
- 边的顶点,面的顶点;EdgeVFaceV, EdgeVEdgeV (2+2=4)
- 边的顶点,面的边上的点;EdgeVFaceE, EdgeVEdgeP (2+1=3)
- 边上的点,面内的点;EdgePFaceP, EdgePFaceP (1+0=1)
- 边上的点,面的顶点;EdgePFaceV, EdgePEdgeV (1+2=3)
- 边上的点,面的边上的点;EdgePFaceE, EdgePEdgeP (1+1=2 )
同时考虑到一个边和一个面最多有一个交点,相同的一个点可能被不同的相交对共享。
三种交点的权重比为:EndP > EdgeP > FaceP
。
在上面的场景中,计算得到两个交点m和m*
。他的相交计算过程可以描述如下(假设top和front都是三角形):
- 计算边和三角形Top的交点,得到交点位于边和三角形Top的边ef上面;
- 计算边和三角形Front的交点,得到交点位于边和三角形Front的面上;
- 然后发现,这条边已经和三角形Front的边ef有交点了,
- 那么就需要通过交点的权重,对交点进行取舍,保留交点m;
根据相交类型m为EdgeP,m*
为FaceP。因此m的权重比m*
大,m*
的坐标和特点被认为和m是一致的。算法的详细伪代码如下(TODO:伪代码应该需要修正):
Algorithm 1 Calculate intersection lines
for each pair of triangles (T1, T2) do
for each edge e \in T1 do
m = Intersection(e, T2);
// 1.如果e和T2的交点类型为EndP,需要将e和交点周边的其他三角形的也要进行记录该交点
// 2.如果e和T2的交点的类型为EdgeP,需要将e和边相邻的其他三角形的也需要记录该交点
// 3.存储<edge,triangle>对应的交点
// 4.判断e和三角形T2是不是还有其他的交点
// 5.如果有则按照优先级,对点的特性和坐标值进行修正
if (exist_intersection(e, T2) && m* = Intersection(e, T2))
Properties(m) = Properties(m*) = Priority(m,m*)
Coordinate(m*) = Coordinate(m)
end for
for each edge e \in T2 do
m = Intersection(e, T1)
if (exist_intersection(e, T1) && m* = Intersection(e, T1))
Properties(m) = Properties(m*) = Priority(m,m*)
Coordinate(m*) = Coordinate(m)
end for
end for
需要对伪代码进行修正,修正后如下:
Algorithm 1 Calculate intersection lines
for each pair of triangles (T1, T2) do
for each edge e \in T1 do
m = Intersection(e, T2);
// 具体实现的时候需要区分哪些是已经通过计算了的,哪些是推导出来的,为了避免后面的重复计算
switch Case(m)
{
case EdgeVFaceP:
add edge face pair around edge v in VecIntersectionPair;
case EdgePFaceV:
add edge face pair around face v in VecIntersectionPair;
case EdgeVFaceV:
add edge face pair around edge v in VecIntersectionPair;
add edge face pair around face v in VecIntersectionPair;
case EdgeVFaceE:
add edge face pair around edge v in VecIntersectionPair;
add edge face pair around face e in VecIntersectionPair;
case EdgePFaceE:
add edge face pair around face e in VecIntersectionPair;
case EdgePFaceP:
add edge face pair in VecIntersectionPair;
}
// 判断VecIntersectionPair中是否存在交点,如果有则取出来,并对其进行更改
// if (exist_intersection(e, T2) && m* = Intersection(e, T2))
// Properties(m) = Properties(m*) = Priority(m,m*)
// Coordinate(m*) = Coordinate(m)
end for
for each edge e \in T2 do
// similar logic as above
end for
end for
2.1.4. 对相交三角形重新三角化
相交的三角形上,通常会有多个相交线段,这些相交线段将三角形划分为多个多边形面。(如何划分??)接下来这些多边形需要被三角化。
- Recursive ear cutting algorithm很简单容易实现,但是性能比较差,不容易扩展处理空洞的情况;
- incremental randomized算法,性能不错,但不容易实现;
- sweep line算法是当前使用最广泛的实现,文中采用了Poly2Tri(Wu L.: Poly2Tri: Fast and Robust Simple Polygon Trian-gulation With/ Without Holes by Sweep Line Algorithm,http://sites-final.uclouvain.be/mema/Poly2Tri/, 2005.),相关代码实现:https://github.com/greenm01/poly2tri
文中并没有指出,该如何去获取三角形之间的相交线段,该如何获取呢?
像图中的多边形,可以将其划分为多个区域然后分别采用,greenm01/poly2tri: Automatically exported from code.google.com/p/poly2tri (github.com)进行剖分。
对应的论文解析,可以参见:PaperRead - Sweep-line algorithm for constrained Delaunay triangulation - grassofsky - 博客园 (cnblogs.com)
2.2 布尔运算
文中的方法分为两部分:
- 区分一个点是mesh内部的还是外部的;
- 创建sub-meshes,然后进行merge;
本文中仅对属于相交三角形的点进行了区分。
2.2.1. Point classify(对相交三角形的顶点进行判断)
假设mn为\(\Delta abc\)和\(\Delta def\)的交线。这两个三角形分别属于不同的mesh。\(n_{abc}\)和\(n_{def}\)分别是\(\Delta abc\)和\(\Delta def\)的法向量,具体为\((x_{n1}, y_{n1}, z_{n1})\), \((x_{n2}, y_{n2}, z_{n2})\)。\(p(x_1,y_1,z_1)\)和\(q(x_2,y_2,z_2)\)分别是\(\Delta abc\)和\(\Delta def\)的上面的一个点。那么平面方程如下:
上图中,两个三角形相交于m,n点,对a,c,e,d点的内外位置进行判断。a位于包含\(\Delta def\)的mesh的内部,d位于包含\(\Delta abc\)的mesh的内部。c,e点位于外部。对于退化的相交场景,至少一个三角形的一个点在另一个三角形对应的mesh上。当相交三角形的所有顶点都进行了判断,分别标记了“in”,“out”,“on”,下面就要进行sub-meshes的创建。
注意:此处对于点in out的判断会存在误判的情况,特别是当点位于边界的情况,针对这种情况,在计算位置的时候考虑第四种情况,UNKNOW。
2.2.2. create sub-meshes and merging them
不同类型的mesh定义:
- \(M_A\), \(M_B\)待处理的两个mesh;
- \(M_{AinB}\),来自\(M_A\)位于\(M_B\)内部的三角形集合;
- \(M_{AoutB}\),来自\(M_A\)位于\(M_B\)外部的三角形集合;
- 同理定义:\(M_{BinA}, M_{BoutA}\);
- \(M_{onAB}\):同时属于\(M_A\), \(M_B\)的网格(共面相交的时候);
那么不同布尔操作的结果如下:
- \(M_A \cup M_B (union):\ M_{AoutB} + M_{BoutA} + M_{onAB}\)
- \(M_A \cap M_B (intersection): \ M_{AinB} + M_{BinA} + M_{onAB}\)
- \(M_A - M_B(difference):\ M_{AoutB} + M_{BinA}\)
具体伪代码类似如下:
// M_new: 新的空的网格
// V'_new: the set of intersected points 交点的集合
// for each V labeled "in" && V not in M_new do 遍历网格A中标记为in的顶点V && 顶点V不在新的网格内
// add V into a contain Ver_C 将顶点V添加到container Ver_C中
// for each V_c in Ver_C do 遍历Ver_C中的每个点Vc
// search all neighbouring vertexes V_nei_C and triangles T_nei_C of V_c 找到V_c的相邻的顶点和相邻的三角形
// for each V' in V_nei_C && V' not in V'_new && V' not in M_new do
// 遍历邻域中的每个点,并且这个点不是相交点,没有被包含到新的mesh中
// add V' into M_new; 将这个邻近点添加到新的网格中
// add V' into Ver_C; 将这个点添加到container Ver_C中
// end for
// for each T' in T_nei_C && T' not in M_new do
// add T' into M_new
// end for
// remove V_c from Ver_C;
// end for
// end for
计算过程示意图如下:
注意:按照上述方法计算的时候,还需要额外考虑,部分三角形会由所有的交点组成的情况。类似如下:
绿色标记部分为交点组成的三角形,那么利用上述的生长的方法进行三角形生长的时候,并不会包含绿色区域。因此,对算法进行了改进:
M_new: 新的空的网格
V'_new: the set of intersected points 交点的集合
E'_new: 相交线段的集合
for each V labeled "in" && V not in M_new do 遍历网格A中标记为in的顶点V && 顶点V不在新的网格内
add V into a contain Ver_C 将顶点V添加到container Ver_C中
for each V_c in Ver_C do 遍历Ver_C中的每个点Vc
search all neighbouring vertexes V_nei_C and triangles T_nei_C of V_c 找到V_c的相邻的顶点和相邻的三角形
for each V' in V_nei_C do
if V' not in V'_new && V' not in M_new do
遍历邻域中的每个点,并且这个点不是相交点,没有被包含到新的mesh中
add V' into M_new; 将这个邻近点添加到新的网格中
add V' into Ver_C; 将这个点添加到container Ver_C中
end for
for each T' in T_nei_C && T' not in M_new do
add T' into M_new
获取三角形T'中V_c的对边E_V_c
将对边E_V_c添加到容器中E_V_c_c
while E_V_c_c 还有值
get E_V_c from E_V_c_c
if E_V_c有效 && E_V_c的两个顶点都是交点 && E_V_c不是相交线段 do
add E_V_c的相邻三角形 T'' into M_new
找到T''三角形不是相交线段的其他边**可能存在不是相交线的两条边**)赋值给 E_V_c_c
endif
end for
remove V_c from Ver_C;
end for
end for
作者: grassofsky
出处: http://www.cnblogs.com/grass-and-moon
本文版权归作者,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出, 原文链接 如有问题, 可邮件(grass-of-sky@163.com)咨询.