MapOverlay & Boolean Operations 实现讲解(PS/Photoshop布尔运算底层算法)
1. 前置知识
PS中的布尔运算可以说是设计师们非常强大和实用的工具之一 ,笔者探究计算几何的旅程之中,发现了这个算法设计得非常的精妙,所以尝试了实现它!而且这个算法也是 CG2017 PA5-2 FruitNinja(水果忍者) 其中一个解法的基础算法(虽然不是最优解),这篇文章我们就重点介绍实现之中的一些要点。实现这个算法所需要的前置知识如下:
- 计算几何算法:1)Line Segment Intersection(Plane Sweep),详见教材 线段求交:专题图叠合 (2 Line Segment Intersection: Thematic Map Overlay);2)Map Overlay,详见教材 2.3 计算子区域划分的叠合(2.3 Computing the Overlay of Two Subdivisions);
- DCEL
2. 思路分析
这里我们把重点放在MapOverlay实现要点的讲解上面,因为只要正确实现MapOverlay,Boolean Operations已经是水到渠成的事情了。
2.1 Degenerate Case
根据笔者的观察总结,Degenerate Case一共有两种:
- Vertex 和 Edge 相交;
- Vertex 和 Vertex 相交;
而且如果还要细分,上述两种情况都有两种细分:1)S1和S2有相交区域;2)S1和S2没有相交区域。再加上一般的两条Edge相交的情况,对于具体的处理思路,我们遵循以下原则:
1)先处理Vertex 和 Edge 相交,既将Edge按照Vertex位置进行分割;
2)Vertex 和 Vertex 必然至少有一条完全重合的Edge,我们只保留其中一个Vertex,去除重合Edge,重定向其他Edge;
3)两条Edge相交,先分割,再重定向Edge;
现在我们给到上述所有情况的示例,方便大家理解:
从理论上来说,上述所有操作都可以Segment Intersection算法中解决,但是这样做,Vertex 和 Edge 相交会有问题,因为Edge会被分割成两段,会破坏原来Segment的输入集的范围,导致后续的操作变得困难。所以这里笔者在实现的时候,使用两次Segment Intersection扫描算法:
第一次扫描,只处理 Vertex 和 Edge 相交,其余操作均忽略;
第二次扫描,Vertex 和 Edge 相交这种情况就不存在了,我们只需要集中注意力处理一般情况和Vertex 和 Vertex 相交即可;
2.2 Examples
因为教材上面没有对整个算法进行比较详细的可视化讲解,这里我们给到两个MapOverlay全过程的示例,方便大家理解:
2.2.1 Normal Example
2.2.2 Degenerate Example
3. 伪代码
Algorithm MAPOVERLAY(S1,S2
算法 MAPOVERLAY(S1, S2)
Input. Two planar subdivisions S1 and S2 stored in doubly-connected edge lists.
输入:用双向链接边表形式存储的两个平面子区域划分S1 和S2
Output. The overlay of S1 and S2 stored in a doubly-connected edge list D.
输出:S1 与S2 的叠合,也存储为一个双向链接边表D
1. Copy the doubly-connected edge lists for S1 and S2 to a new doublyconnected edge list D.
1. 生成一个新的双向链接边表D,将S1 和S2 对应的两个双向链接边表复制到D 中
2. Compute all intersections between edges from S1 and S2 with the plane sweep algorithm of Section 2.1. In addition to the actions on T and Q required at the event points, do the following:
2. 应用第2.1 节介绍的平面扫描算法,计算出S1 的各边与S2 的各边之间的全部交点在每个事件点处,除了对T和Q所必需的操作外,还要完成如下工作:
(i) Update D as explained above if the event involves edges of both S1 and S2. (This was explained for the case where an edge of S1 passes through a vertex of S2.)
(i) 若该事件点同时涉及到S1 和S2 中的边,则按照上面介绍的方法对D做更新(本节中所介绍的,只是S1 的一条边穿过S2 的一个顶点的情况)
(ii) Store the half-edge immediately to the left of the event point at the vertex in D representing it.
(ii) 找到紧邻于该事件点左侧的那条半边,将它存在于D中对应于该点的顶点处
3. (* Now D is the doubly-connected edge list for O(S1,S2), except that the information about the faces has not been computed yet. *)
3. (* 现在,D 已经基本上可以称作是对应于O(S1, S2)的双向链接边表 *) (* 只是各张面的信息还有待计算出来 *)
4. Determine the boundary cycles in O(S1,S2) by traversing D.
4. 通过对D 的遍历,找出O(S1, S2)中的所有边界环
5. Construct the graph G whose nodes correspond to boundary cycles and whose arcs connect each hole cycle to the cycle to the left of its leftmost vertex, and compute its connected components. (The information to determine the arcs of G has been computed in line 2, second item.)
5. 构造一张图G:其中的每个节点,分别对应于一个边界环;其中的每条弧,都联接了一个空洞环与紧邻于该环最左端顶点左侧的另一个环找出G 中所有的连通子块(* 用以确定G 中各条弧的信息,在上面第2 行的第2 项中已经计算出来了 *)
6. for each connected component in G
6. for (G 的每一连通子块)
7. do Let C be the unique outer boundary cycle in the component and let f denote the face bounded by the cycle. Create a face record for f , set OuterComponent( f ) to some half-edge of C, and construct the list InnerComponents( f ) consisting of pointers to one half-edge in each hole cycle in the component. Let the IncidentFace() pointers38 of all half-edges in the cycles point to the face record of f .
7. do 令C 为该连通子块(唯一)的外边界环;由此环围成的面,记作f生成对应于f 的一个面记录将OuterComponent()指向C 的某条半边生成一个InnerComponents()列表(* 对应于该连通子块中的每一个孔洞环, *)(* 在该列表中都有一个指针,指向对应的空洞环上的某条边 *)将各(内、外)环上所有半边的IncidentFace()指针,指向对应于f 的面记录
8. Label each face of O(S1,S2) with the names of the faces of S1 and S2 containing it, as explained above.
8. 按照上面介绍的方法,给O(S1, S2)中的每张面都做上标记,指明各张面在S1 和S2 中分别被包含于哪张面中
4. 可视化结果示例
首先我们给到Degenerate Example里面MapOveraly的相关结果:
其次,我们使用这个相同的例子进行布尔运算:
5. 项目代码
1.1.10 MapOverlay & Boolean Operations & Half-plane Intersection
Description | Entry method\File |
---|---|
Computing the overlay of two subdivisions | Face compute( Face s1, Face s2 ) |
Compute the intersection of two subdivisions, P1 ∩ P2. | List<Face> intersection( Face s1, Face s2 ) |
Compute the union of two subdivisions, P1 ∪ P2, | List<Face> union( Face s1, Face s2 ) |
Compute the difference of two subdivisions, P1 \ P2. | List<Face> difference( Face s1, Face s2 ) |
Program ( including visualization ) | CG2017 PA5-2 FruitNinja |
6. 参考资料
- Computational Geometry: Algorithms and Applications
- 计算几何 ⎯⎯ 算法与应用, 邓俊辉译,清华大学出版社
- 计算几何 | Computational Geometry
7. 免责声明
※ 本文之中如有错误和不准确的地方,欢迎大家指正哒~
※ 此项目仅用于学习交流,请不要用于任何形式的商用用途,谢谢呢;