點到多邊形的切線

前言

切線的構造屬於計算幾何中的基礎問題。存在高效的算法可以計算凸集的切線。對於凹集,其切線與對應凸集的切線相同,所以也能高效計算。因此,我們這裏僅僅討論凸多邊形的切線。

尋找切線的算法與尋找極值點的算法(凸多邊形的極值點)類似。通過運用相似的二分搜索方法,我們能構造O(logn)複雜度的切線尋找算法。

定義

直線L與多邊形S相切定義爲:LS接觸,但不穿過S的邊界。也就是L僅僅與S的一個點相交或者與一條邊重合。

根據上面的定義,S中的所有點要麼在L上,要麼都在L的同一側。從外部任意一點,存在兩條切線與S相切。

不妨設\mathbf{L}=\{V_0, V_1, ..., V_{n-1}, V_n}\},以逆時針方向給出。\mathbf{e_i}爲第i條邊,\mathbf{ev}_i=V_{i+1}-V_i爲邊向量,由Vi指向Vi+1。想象一下,站在Vi點面朝\mathbf{e_i}的方向,如果P點在左手邊,那麼稱P點在\mathbf{e_i}左側。

如上圖所示。記P點爲多邊形S外部任意一點。對於任意一個頂點Vi,可以用下面的方法很方便地檢查直線PVi是否是切線:

考慮Vi前後的兩條邊\mathbf{e_{i-1}}\mathbf{e_i}。如果點P在這兩條邊的同一側(同在左側或者同在右側),那麼Vi-1和Vi+1一定在直線PVi的不同側,PVi就一定不是切線。反之,若點P在\mathbf{e_{i-1}}的左側,而在\mathbf{e_i}的右側,或者相反,那麼點P就是一條切線。如上圖,點P在\mathbf{e_{i-1}}右側而在\mathbf{e_i}左側,那麼點P是最右切線;同樣地,點P在\mathbf{e_{k-1}}左側而在\mathbf{e_k}右側,那麼點P是最左切線。

對於凸多邊形,可以用上述方法求出切線。但對於凹多邊形,需要先求凸包,然後再用上述方法求其凸包的切線,其凸包的切線必然是原多邊形的切線。相對於原始多邊形,凸包往往點數大大減少,因此求解起來很快。多邊形凸包算法可以參考之前的博文Melkman Algorithm

對於凹多邊形還有一種方法是:使用暴力搜索,先求出所有的局部切線,然後從這些切線中找到最右側的T_R和最左側的T_L,就是我們要求的兩條切線了。如下圖:

暴力算法

尋找任意多邊形切線的暴力算法很容易實現,通常當多邊形點數n較小時,採用這一算法比較高效。

其C++代碼如下:

// 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);
}


// tests for polygon vertex ordering relative to a fixed point P
#define above(P,Vi,Vj)  (isLeft(P,Vi,Vj) > 0)   // true if Vi is above Vj
#define below(P,Vi,Vj)  (isLeft(P,Vi,Vj) < 0)   // true if Vi is below Vj
//===================================================================
 

// tangent_PointPoly(): find any polygon's exterior tangents
//    Input:  P = a 2D point (exterior to the polygon)
//            n = number of polygon vertices
//            V = array of vertices for any 2D polygon with V[n]=V[0]
//    Output: *rtan = index of rightmost tangent point V[*rtan]
//            *ltan = index of leftmost tangent point V[*ltan]
void
tangent_PointPoly( Point P, int n, Point* V, int* rtan, int* ltan )
{
    float  eprev, enext;        // V[i] previous and next edge turn direction

    *rtan = *ltan = 0;          // initially assume V[0] = both tangents
    eprev = isLeft(V[0], V[1], P);
    for (int i=1; i<n; i++) {
        enext = isLeft(V[i], V[i+1], P);
        if ((eprev <= 0) && (enext > 0)) {
            if (!below(P, V[i], V[*rtan]))
                 *rtan = i;
        }
        else if ((eprev > 0) && (enext <= 0)) {
            if (!above(P, V[i], V[*ltan]))
                 *ltan = i;
        }
        eprev = enext;
    }
    return;
}
//===================================================================
 

二分搜索

對於凸多邊形,存在速度更快的二分搜索算法。只需要在多邊形的點之間定義一個次序,就可以使用類似於凸多邊形的極值點中的相同的二分搜索算法。例如,如下圖,我們定義\mathbf{e_i}是增大的, 當Vi+1相對於點P在Vi之上。或者說點P在\mathbf{e_i}右側。

可以看到對於凸多邊形而言,從最下面的切點開始,先單調增大,到最上面大的切點之後,再單調減小。因此與凸多邊形的極值點算法相比,唯一的差別就是順序關係判斷函數發生了變化,其他部分基本完全一樣。下面直接給出一個C++實現:

// tangent_PointPolyC(): fast binary search for tangents to a convex polygon
//    Input:  P = a 2D point (exterior to the polygon)
//            n = number of polygon vertices
//            V = array of vertices for a 2D convex polygon with V[n] = V[0]
//    Output: *rtan = index of rightmost tangent point V[*rtan]
//            *ltan = index of leftmost tangent point V[*ltan]
void
tangent_PointPolyC( Point P, int n, Point* V, int* rtan, int* ltan )
{
    *rtan = Rtangent_PointPolyC(P, n, V);
    *ltan = Ltangent_PointPolyC(P, n, V);
}


// Rtangent_PointPolyC(): binary search for convex polygon right tangent
//    Input:  P = a 2D point (exterior to the polygon)
//            n = number of polygon vertices
//            V = array of vertices for a 2D convex polygon with V[n] = V[0]
//    Return: index "i" of rightmost tangent point V[i]
int
Rtangent_PointPolyC( Point P, int n, Point* V )
{
    // use binary search for large convex polygons
    int     a, b, c;            // indices for edge chain endpoints
    int     upA, dnC;           // test for up direction of edges a and c

    // rightmost tangent = maximum for the isLeft() ordering
    // test if V[0] is a local maximum
    if (below(P, V[1], V[0]) && !above(P, V[n-1], V[0]))
        return 0;               // V[0] is the maximum tangent point

    for (a=0, b=n;;) {          // start chain = [0,n] with V[n]=V[0]
        c = (a + b) / 2;        // midpoint of [a,b], and 0<c<n
        dnC = below(P, V[c+1], V[c]);
        if (dnC && !above(P, V[c-1], V[c]))
            return c;          // V[c] is the maximum tangent point

        // no max yet, so continue with the binary search
        // pick one of the two subchains [a,c] or [c,b]
        upA = above(P, V[a+1], V[a]);
        if (upA) {                       // edge a points up
            if (dnC)                         // edge c points down
                 b = c;                           // select [a,c]
            else {                           // edge c points up
                 if (above(P, V[a], V[c]))     // V[a] above V[c]
                     b = c;                       // select [a,c]
                 else                          // V[a] below V[c]
                     a = c;                       // select [c,b]
            }
        }
        else {                           // edge a points down
            if (!dnC)                        // edge c points up
                 a = c;                           // select [c,b]
            else {                           // edge c points down
                 if (below(P, V[a], V[c]))     // V[a] below V[c]
                     b = c;                       // select [a,c]
                 else                          // V[a] above V[c]
                     a = c;                       // select [c,b]
            }
        }
    }
}
 


// Ltangent_PointPolyC(): binary search for convex polygon left tangent
//    Input:  P = a 2D point (exterior to the polygon)
//            n = number of polygon vertices
//            V = array of vertices for a 2D convex polygon with V[n]=V[0]
//    Return: index "i" of leftmost tangent point V[i]
int
Ltangent_PointPolyC( Point P, int n, Point* V )
{
    // use binary search for large convex polygons
    int     a, b, c;            // indices for edge chain endpoints
    int     dnA, dnC;           // test for down direction of edges a and c

    // leftmost tangent = minimum for the isLeft() ordering
    // test if V[0] is a local minimum
    if (above(P, V[n-1], V[0]) && !below(P, V[1], V[0]))
        return 0;               // V[0] is the minimum tangent point

    for (a=0, b=n;;) {          // start chain = [0,n] with V[n] = V[0]
        c = (a + b) / 2;        // midpoint of [a,b], and 0<c<n
        dnC = below(P, V[c+1], V[c]);
        if (above(P, V[c-1], V[c]) && !dnC)
            return c;          // V[c] is the minimum tangent point

        // no min yet, so continue with the binary search
        // pick one of the two subchains [a,c] or [c,b]
        dnA = below(P, V[a+1], V[a]);
        if (dnA) {                       // edge a points down
            if (!dnC)                        // edge c points up
                 b = c;                           // select [a,c]
            else {                           // edge c points down
                 if (below(P, V[a], V[c]))     // V[a] below V[c]
                     b = c;                       // select [a,c]
                 else                          // V[a] above V[c]
                     a = c;                       // select [c,b]
            }
        }
        else {                           // edge a points up
            if (dnC)                         // edge c points down
                 a = c;                           // select [c,b]
            else {                           // edge c points up
                 if (above(P, V[a], V[c]))     // V[a] above V[c]
                     b = c;                       // select [a,c]
                 else                          // V[a] below V[c]
                     a = c;                       // select [c,b]
            }
        }
    }
}
//===================================================================
 

 

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