两个多边形之间的切线算法,最通俗易懂的讲解

两个多边形之间的切线算法

前言

之前的文章中讨论了点到多边形的切线,这里继续讨论两个分离的多边形之间的切线问题。与点到多边形的切线相似,只是略微复杂。因为:

  1. 必须以同样的方式考虑两个多边形的顶点,因此算法复杂度必然增加;
  2. 对于两个多边形之间,存在4条切线,如下图。

当两个多边形都是凸多边形时,存在快速切线算法。对于非凸多边形,必须采用暴力搜索;或者先求凸包,然后再求切线。当多边形是简单多边形(不自交)时,后者算法更快。因为两个凸包能够使用Melkman’s algorithm在O(m)和O(n)的时间复杂度内计算;然后凸包之间的切线能够在O(m+n),甚至O(log(m+n))的时间复杂度内计算。

暴力算法

我们不妨记两个多边形分别为\Lambda =\{V_i\}(i=1,...,m),\Omega =\{W_k\}(k=1,...,n).

最简单的暴力算法是测试连接\Lambda\Omega所有顶点的每条线。由于有mn个顶点对,因此有mn条线需要测试,这就可以给出一个O(mn)的算法了。尽管该算法很慢但适用于任意简单多边形。

如果其中一个多边形是凸的,假设是\Omega,那么可以将时间复杂度降低到O(mlogn)。直接借助点到多边形的切线一文中给出的二分搜索算法,对于\Lambda的每个顶点,到\Omega的切线能够在O(logn)的时间内找到,于是m个顶点分别测试完,需要O(mlogn)的时间。在有些情况下,这个时间复杂度勉强够用了。

线性搜索算法

如何两个多边形都是凸的,那么很容易给出一个线性时间O(m+n)的算法。

算法的思路是在两个Polygon之间选择性地搜索切线的终点,直到线的两端点都满足相切条件。该算法最初由Preparata & Hong在1977年提出,后来O'Rourke在1998年给出了完整的表述。

因为切线必然不与两个Polygon相交,所以算法首先寻找能够“看到”另一个Polygon的顶点。例如在\Lambda左侧的顶点“看不见”\Omega,它与\Omega的切线必然与\Lambda相交。

找到这样的两个初始点后,连接这两个初始点的线段按照一定的次序改变其中一个端点。

首先,其中一个端点在其所属的Polygon上顺序前进,直到这条线段与Polygon相切;

然后另一个Polygon上的端点也顺序前进,直到线段与该Polygon相切;

接下来回到前一个Polygon,继续这个过程,直到连接两个端点的线段同时与两个Polygon都相切。

顶点变化的方向决定了,4条切线中的哪一条会被找到。例如:在下图中,假定初始线段为line#1,然后\Omega上的顶点眼顺时针方向前进,而\Lambda上的顶点沿着逆时针方向前进,那么我们最终会找到line#5,它是\Lambda的Rightmost切线,\Omega的leftmost的切线。

因为在算法中顶点始终朝一个方向前进,而且没有回溯,所以 最多检查了(m+n)个点对,因此算法复杂度是O(m+n)。 

 

 

该算法的一个难点是如何寻找初始的两个顶点,要求分别在\Lambda\Omega上,彼此能够看见另一个Polygon。这个可以利用点到多边形的切线算法来做到。首先在\Lambda上任选一点,V0,寻找该点到\Omega的上部(下部)切线,假设相切于顶点W_{k0},然后从该点寻找相对于\Lambda的上部(下部)切线,切点记为V_{i0},顶点V_{i0}W_{k0}就是非常好的两个初始顶点。

下面给出了一个计算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)

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值