两个多边形之间的切线算法
前言
之前的文章中讨论了点到多边形的切线,这里继续讨论两个分离的多边形之间的切线问题。与点到多边形的切线相似,只是略微复杂。因为:
- 必须以同样的方式考虑两个多边形的顶点,因此算法复杂度必然增加;
- 对于两个多边形之间,存在4条切线,如下图。
当两个多边形都是凸多边形时,存在快速切线算法。对于非凸多边形,必须采用暴力搜索;或者先求凸包,然后再求切线。当多边形是简单多边形(不自交)时,后者算法更快。因为两个凸包能够使用Melkman’s algorithm在O(m)和O(n)的时间复杂度内计算;然后凸包之间的切线能够在O(m+n),甚至O(log(m+n))的时间复杂度内计算。
暴力算法
我们不妨记两个多边形分别为.
最简单的暴力算法是测试连接和所有顶点的每条线。由于有mn个顶点对,因此有mn条线需要测试,这就可以给出一个O(mn)的算法了。尽管该算法很慢但适用于任意简单多边形。
如果其中一个多边形是凸的,假设是,那么可以将时间复杂度降低到O(mlogn)。直接借助点到多边形的切线一文中给出的二分搜索算法,对于的每个顶点,到的切线能够在O(logn)的时间内找到,于是m个顶点分别测试完,需要O(mlogn)的时间。在有些情况下,这个时间复杂度勉强够用了。
线性搜索算法
如何两个多边形都是凸的,那么很容易给出一个线性时间O(m+n)的算法。
算法的思路是在两个Polygon之间选择性地搜索切线的终点,直到线的两端点都满足相切条件。该算法最初由Preparata & Hong在1977年提出,后来O'Rourke在1998年给出了完整的表述。
因为切线必然不与两个Polygon相交,所以算法首先寻找能够“看到”另一个Polygon的顶点。例如在左侧的顶点“看不见”,它与的切线必然与相交。
找到这样的两个初始点后,连接这两个初始点的线段按照一定的次序改变其中一个端点。
首先,其中一个端点在其所属的Polygon上顺序前进,直到这条线段与Polygon相切;
然后另一个Polygon上的端点也顺序前进,直到线段与该Polygon相切;
接下来回到前一个Polygon,继续这个过程,直到连接两个端点的线段同时与两个Polygon都相切。
顶点变化的方向决定了,4条切线中的哪一条会被找到。例如:在下图中,假定初始线段为line#1,然后上的顶点眼顺时针方向前进,而上的顶点沿着逆时针方向前进,那么我们最终会找到line#5,它是的Rightmost切线,的leftmost的切线。
因为在算法中顶点始终朝一个方向前进,而且没有回溯,所以 最多检查了(m+n)个点对,因此算法复杂度是O(m+n)。
该算法的一个难点是如何寻找初始的两个顶点,要求分别在和上,彼此能够看见另一个Polygon。这个可以利用点到多边形的切线算法来做到。首先在上任选一点,V0,寻找该点到的上部(下部)切线,假设相切于顶点,然后从该点寻找相对于的上部(下部)切线,切点记为,顶点和就是非常好的两个初始顶点。
下面给出了一个计算Right-Left切线的一个C++实现 RLtangent_PolyPolyC()。该算法在调用时,只需改变两个Polygon的输入次序,就能得到Left-Right切线。对该函数做轻微的修改就可以得到Right-Right切线和Left-Left切线。
// Assume that classes are already given for the objects:
// Point with coordinates {float x, y;}
//===================================================================
// isLeft(): test if a point is Left|On|Right of an infinite line.
// Input: three points P0, P1, and P2
// Return: >0 for P2 left of the line through P0 and P1
// =0 for P2 on the line
// <0 for P2 right of the line
// See: Algorithm 1 on Area of Triangles
inline float
isLeft( Point P0, Point P1, Point P2 )
{
return (P1.x - P0.x)*(P2.y - P0.y) - (P2.x - P0.x)*(P1.y - P0.y);
}
// Rtangent_PointPolyC(): binary search for convex polygon right tangent
// Ltangent_PointPolyC(): binary search for convex polygon left tangent
// 这两个算法已经在https://blog.csdn.net/u013279723/article/details/104239723中给出
// RLtangent_PolyPolyC(): get the RL tangent between two convex polygons
// Input: m = number of vertices in polygon 1
// V = array of vertices for convex polygon 1 with V[m]=V[0]
// n = number of vertices in polygon 2
// W = array of vertices for convex polygon 2 with W[n]=W[0]
// Output: *t1 = index of tangent point V[t1] for polygon 1
// *t2 = index of tangent point W[t2] for polygon 2
void
RLtangent_PolyPolyC( int m, Point* V, int n, Point* W, int* t1, int* t2 )
{
int ix1, ix2; // search indices for polygons 1 and 2
// first get the initial vertex on each polygon
ix1 = Rtangent_PointPolyC(W[0], m, V); // right tangent from W[0] to V
ix2 = Ltangent_PointPolyC(V[ix1], n, W); // left tangent from V[ix1] to W
// ping-pong linear search until it stabilizes
int done = FALSE; // flag when done
while (done == FALSE) {
done = TRUE; // assume done until...
while (isLeft(W[ix2], V[ix1], V[ix1+1]) <= 0){
++ix1; // get Rtangent from W[ix2] to V
}
while (isLeft(V[ix1], W[ix2], W[ix2-1]) >= 0){
--ix2; // get Ltangent from V[ix1] to W
done = FALSE; // not done if had to adjust this
}
}
*t1 = ix1;
*t2 = ix2;
return;
}
//===================================================================
二分搜索算法
存在更快的O(log(m+n))时间复杂度的算法,对于寻找两个凸多边形的外部切线。[Kirkpatrick & Snoeyink, 1995] 给出了这样一个算法,http://www.cs.ubc.ca/spider/snoeyink/papers/nosep.ps.gz 这篇文章中对该算法进行了详细描述,有兴趣的可以查阅。
延伸阅读
David Kirkpatrick & Jack Snoeyink, "Computing Common Tangents without a Separating Line", Workshop on Algorithms and Data Structures, 183-193 (1995) [downloadable with C code from http://www.cs.ubc.ca/spider/snoeyink/papers/nosep.ps.gz ]
Joseph O'Rourke, Computational Geometry in C (2nd Edition), Sects 3.7-3.8, 88-95 (1998)