在开发现在的项目中碰到一个关于算法的难题,就是“正方形组合而成的多边形的顶点排序算法”。为什么要将多边形的顶点排序呢?因为需要使用PNPoly的算法来判断点是否在多边形内。而PNPoly的算法唯一要求的就是多边形的顶点有序数组,至于是逆时针还是顺时针其实都无所谓了。起初碰到这个问题的时候我百度了一下发现了凸包的概念,以及计算凸包的常用算法(详情点击这里:凸包)。但使用极角来排序多边形顶点的方法在项目中会遇到一些问题。以下图为例,N为多边形的中心点,则MN和BN的极角相同的,如此以极角为排序标准的话,顶点顺序就会出现跳跃的现象。
后来在网上搜索的时候,偶然间查到了多边形的填充算法——扫描线算法(详情请点击:区域填充)。在详细的查阅了该算法之后,我突然想到了一种可以将项目中的多边形进行顶点排序的方法——横纵扫描线算法。以下面的图形为例,多边形的顶点为A、C、D、F、G、E、I、J、K、L、M。正方形相交的点,相交次数为偶数次的则被忽略掉(别问我为什么,自己观察)。那么现在我们可以得到一个包含了多边形所有顶点坐标的数组,但却是无序的。我们的目的就是要把它排成有序的。
怎么做?求得这个多边形的边界,即x轴和y轴坐标的最大值和最小值。由此我们可以得到一个矩形边界,如下图。然后横纵分别以正方形的边长为单位画射线,就像小时候在图画本上打格子一样。这样我们就得到了横向4条线,纵向4条线。你能观察到什么?对头,多边形所有的顶点都在这8条线上了,而且是横纵交错。这就为我们排序多边形的顶点打下了基础。如果你聪明如我,应该不难发现,在一条线上相交的几个顶点数量一定是偶数,而且,按照顺序从左到右,从上到下一定是两两相连形成多边形的边。比如边HI和边JK。另外一个规律就是多边形的边横向走过之后,下一个顶点一定在纵向。为什么这么说呢?比如HI边,它的下一个顶点是L点,而不是J点,这是因为,HIJ三点是在一条直线上,如果J点是它的下一个顶点,那么I点就不可能是顶点。由此我们就可以排序多边形的顶点了。
下面我们来看一下这个算法的部分代码(C#)。计算多边形边界和做扫描线这部分代码都很简单,我就不上代码了。我们主要看一下顶点与扫描线相交的排序与存储:
ScanningLineCrosspointComparer comparer = new ScanningLineCrosspointComparer(rule);
group.Sort(comparer);
for (int j = 0, length = group.Count; j < length; j += 2)
{
cachedData[bigAreaId].Add(group[j], group[j + 1]);
cachedData[bigAreaId].Add(group[j + 1], group[j]);
}
ScanningLineCrosspointComparer是group的一个排序类,按照x轴或者y轴从小到大排序坐标点。以HIJK为例,将H点坐标作为key,I点坐标作为它的value存入Dictionary。同样,反过来将I点坐标作为key,H点坐标作为它的value存入Dictionary。点J和点K也以同样的方式处理。
以上面的方式处理过所有的扫描线之后,我们就可以从多边形顶点中随便找一个顶点来作为起点开始排序顶点了。假设我们以L点为例,我们先到横向扫面线的Dictionary里查找以L为key的value,我们会得到M点。然后我们再以M点为起点到纵向扫面线的Dictionary中查找以M为key的value,我们会得到J。以此类推直到找到最后一个顶点,依次将得到的value添加到数组或者List,我们就得到了一个有序的多边形顶点列表。
这种算法可能不是最好的,但目前我能想到的最好似乎只有这种方法了。如果哪位大神有更好的方法,希望积极地拍砖。