前言
之前在做矢量地图相关内容时用到了boost
库,但是在进行矢量切片时,我发现boost::geometry::intersection
存在bug:boost::intersection produces wrong results。回想起计算机图形学一书中有提到相关内容,回去翻看,于是发现了Sutherland Hodgman
算法。
mapbox的开源切片工具也使用了Sutherland Hodgman
算法。
Sutherland Hadgman 算法
Sutherland Hodgman 算法
是一种使用凸多边形裁减凸多边形的算法。如同其他裁减算法一样, Sutherland Hodgman
算法也是每次使用裁减窗口的一条边去裁减目标,当所有的边都裁剪以后,得到的就是最终的结果了。
算法主要内容如下:
用一条边e裁减目标多边形时,对于多边形的每一条边PQ,可能会有如下4种场景:
图中inside
代表裁减窗口内部。对于这4种场景,采取动作如下:
- 保留Q点
- 保留交点I和Q点
- 保留交点I
- 不保留任何点
例如,用边e
裁减矩形ABCD
:
有如下流程:
- 边
AB
满足场景2,保留交点E
和终点B
- 边
BC
满足场景1,保留终点C
- 边
CD
满足场景3,保留交点F
- 边
DA
满足场景4,不保留任何点
最终得到矩形EBCF
。
注意:点在边上作为点在内部处理。
相关定义及算法伪码
假设有基础数据类型定义如下:
1 | struct point |
则, Sutherland Hodgman
伪码如下:
1 | polygon sutherland_hodgman(polygon poly, polygon wnd) |
矩形裁剪框下的Sutherland Hodgman
对于任意的多边形裁剪窗来说,要判断点的位置比较麻烦,需要计算裁减窗口每条边的法向量,然后通过点积进行比较。
同样,求两条边的交点也很麻烦。
但是,一般情况下,裁减窗口都是一个矩形,而且是一个水平放置的矩形。那么这个过程就非常轻松愉快了。
假设矩形裁减窗口定义如下:
1 | struct rect |
例如,当使用矩形的 l
进行裁减时,要判断点的位置,直接 pt.y < rect.l
即可。
而求交点,就是求 x = l
时的 y
值。
使用矩形窗口进行裁减的整个流程可以用如下伪码表示:
1 | polygon sutherland_hodgman(polygon poly, rect rc) |
单看代码的话,可能会感觉反而变复杂了,但实际编码难度则小了不少,性能也有所提高。
跑一个简单的 benchmark
, 得到数据如下:
vol | sh | sh_rc |
---|---|---|
1w | 104 | 10 |
10w | 1031 | 89 |
100w | 10365 | 887 |
表中, sh
代表普通 sutherland hodgman
算法实现,矩形裁减窗口也是使用 polygon
定义。sh_gl
代表使用 rect
类型作为裁减窗口的算法实现。
可以看出,矩形裁减窗口下,算法效率提高了10倍以上。
以上的实现代码可以在这里找到。
存在的问题
如书上描述的那样,Sutherland Hodgman
是用凸多边形裁剪凸多边形的算法,如果用来处理凹多边形,则会有一些问题。
考虑如下的场景:
用边e
裁剪凹多边形ABCDE
,按照直观来看,应该得到两个小三角形FBG
和HDI
。但是按照Sutherland Hodgman
算法的流程,只会得到一个点序列FBGHDI
,其中,边IF
和GH
实际上重叠了。
如wiki所述,这对渲染不会造成影响。Mapbox
的开源工具正是这样进行矢量数据裁剪。并且,为了解决矢量瓦片边框问题,裁剪窗口总是比显示窗口要大的,所以上面这个例子中两个小三角形的连接处是永远不会被渲染,最终的效果应该是这样:
至于怎么裁减这样的凹多边形,就留到后面的文章吧 :D