兩個多邊形之間的切線算法,最通俗易懂的講解

兩個多邊形之間的切線算法

前言

之前的文章中討論了點到多邊形的切線,這裏繼續討論兩個分離的多邊形之間的切線問題。與點到多邊形的切線相似,只是略微複雜。因爲:

  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)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章