多邊形裁剪二:Weiler-Atherton演算法

yangxi_001發表於2014-07-13

Weiler-Atherton任意多邊形裁剪

  Sutherland-Hodgeman演算法解決了裁剪視窗為凸多邊形視窗的問題,但一些應用需要涉及任意多邊形視窗(含凹多邊形視窗)的裁剪。Weiler-Atherton多邊形裁剪演算法正是滿足這種要求的演算法。

 

WeilerAtherton任意多邊形裁剪演算法描述:

  在演算法中,裁剪視窗、被裁剪多邊形可以是任意多邊形:凸的、凹的(內角大於180o)、甚至是帶有內環的(子區),見下圖。

  裁剪視窗和被裁剪多邊形處於完全對等的地位,這裡我們稱:

  1、被裁剪多邊形為主多邊形,記為A;

  2、裁剪視窗為裁剪多邊形,記為B。

  主多邊形A和裁剪多邊形B的邊界將整個二維平面分成了四個區域:
  1、A∩B(交:屬於A且屬於B); 
  2、A-B(差:屬於A不屬於B);
  3、B-A(差:屬於B不屬於A);
  4、A∪B(並:屬於A或屬於B,取反;即:不屬於A且不屬於B)。

  內裁剪即通常意義上的裁剪,取圖元位於視窗之內的部分,結果為A∩B。

  外裁剪取圖元位於視窗之外的部分,結果為A-B。

  觀察下圖不難發現裁剪結果區域的邊界由被裁剪多 邊形的部分邊界和裁剪視窗的部分邊界兩部分構成,並且在交點處邊界發生交替,即由被裁剪多邊形的邊界轉至裁剪視窗的邊界,或者反之。由於多邊形構成一個封閉的區域,所以,如果被裁剪多邊形和裁剪視窗有交點,則交點成對出現。這些交點分成兩類:

  一類稱“入”點,即被裁剪多邊形由此點進入裁剪視窗,如圖中a、c、e;
  一類稱“出”點,即被裁剪多邊形由此點離開裁剪視窗,如圖中b、d、f。

                 

 

WeilerAtherton任意多邊形裁剪演算法思想:

  假設被裁剪多邊形和裁剪視窗的頂點序列都按順時針方向排列。當兩個多邊形相交時,交點必然成對出現,其中一個是從被裁剪多邊形進入裁剪視窗的交點,稱為“入點”,另一個是從被裁剪多邊形離開裁剪視窗的交點,稱為“出點”。

  演算法從被裁剪多邊形的一個入點開始,碰到入點,沿著被裁剪多邊形按順時針方向蒐集頂點序列;

  而當遇到出點時,則沿著裁剪視窗按順時針方向蒐集頂點序列。

  按上述規則,如此交替地沿著兩個多邊形的邊線行進,直到回到起始點。這時,收集到的全部頂點序列就是裁剪所得的一個多邊形。

  由於可能存在分裂的多邊形,因此演算法要考慮:將蒐集過的入點的入點記號刪去,以免重複跟蹤。將所有的入點蒐集完畢後演算法結束。

 

三、WeilerAtherton任意多邊形裁剪演算法步驟:

  1、順時針輸入被裁剪多邊形頂點序列Ⅰ放入陣列1中。

  2、順時針輸入裁剪視窗頂點序列Ⅱ放入陣列2中。

  3、求出被裁剪多邊形和裁剪視窗相交的所有交點,並給每個交點打上“入”、“出”標記。

    然後將交點按順序插入序列Ⅰ得到新的頂點序列Ⅲ,並放入陣列3中;

    同樣也將交點按順序插入序列Ⅱ得到新的頂點序列Ⅳ,放入陣列4中;

  4、初始化輸出陣列Q,令陣列Q為空。接著從陣列3中尋找“入”點。

    如果“入”點沒找到,程式結束。

  5、如果找到“入”點,則將“入”點放入S中暫存。

  6、將“入”點錄入到輸出陣列Q中。並從陣列3中將該“入”點的“入”點標記刪去。

  7、沿陣列3順序取頂點:

    如果頂點不是“出點”,則將頂點錄入到輸出陣列Q中,流程轉第7步。
    否則,流程轉第8步。

  8、沿陣列4順序取頂點:

    如果頂點不是“入點”,則將頂點錄入到輸出陣列Q中,流程轉第8步。
    否則,流程轉第9步。

  9、如果頂點不等於起始點S,流程轉第6步,繼續跟蹤陣列3。

    否則,將陣列Q輸出;

    流程轉第4步,尋找可能存在的分裂多邊形。

    演算法在第4步:滿足“入”點沒找到的條件時,演算法結束。演算法的生成過程見下圖所示。

 

 

四、WeilerAtherton任意多邊形裁剪演算法特點:

  1、裁剪視窗可以是矩形、任意凸多邊形、任意凹多邊形。

  2、可實現被裁剪多邊形相對裁剪視窗的內裁或外裁,即保留視窗內的圖形或保留視窗外的圖形,因此在三維消隱中可以用來處理物體表面間的相互遮擋關係。

  3、裁剪思想新穎,方法簡潔,裁剪一次完成,與裁剪視窗的邊數無關。

 

五、WeilerAtherton任意多邊形裁剪演算法小結:

  前面介紹的是內裁演算法,即保留裁剪視窗內的圖形。而外裁演算法(保留裁剪視窗外的圖形)同內裁演算法差不多。

  外裁演算法與內裁演算法不同的是:

  1、從被裁剪多邊形的一個“出點”開始,碰到出點,沿著被裁剪多邊形按順時針方向蒐集頂點序列;

  2、而當遇到“入點”時,則沿著裁剪視窗按逆時針方向蒐集頂點序列。

  按上述規則,如此交替地沿著兩個多邊形的邊線行進,直到回到起始點為止。這時,收集到的全部頂點序列就是裁剪所得的一個多邊形。

  由於可能存在分裂的多邊形,因此演算法要考慮:將蒐集過的“出點”的出點記號刪去,以免重複跟蹤。將所有的出點蒐集完畢後演算法結束。

  Weiler-Atherton演算法的的設計思想很巧妙,裁剪是一次完成,不象Sutherland-Hodgman多邊形裁剪演算法,每次只對裁剪視窗的一條邊界及其延長線進行裁剪,如裁剪視窗有n條邊,則要呼叫n次S-H演算法後才能最後得出裁剪結果。

  但Weiler-Atherton演算法的程式設計實現比Sutherland-Hodgman演算法稍難,主要難在入、出點的查尋以及跨陣列搜尋上。

 

六、未測試的程式碼(正確程式碼見以後更新)

  1. typedef struct PWAPoint  
  2. {  
  3.     RtPoint point;  
  4.     int  flag;//0表示被剪裁多邊形,1表示交點  
  5.     int  index;//屬於被剪裁哪個線段的交點  
  6.     int index0;//屬於剪裁哪個線段的交點  
  7.     int  flag0;//0表示入,1表示出,-1表示清除  
  8. }PWAPoint;  
  9.   
  10. typedef struct PWAArray  
  11. {  
  12.     double dist;//交點於啟點的距離  
  13.     int index;//交點儲存位置  
  14. }PWAArray;  
  15.   
  16. //多邊形點必須是順時針方向  
  17. int rtPrunePWA(RtPoint* src, int num, RtPoint* dest, int total)  
  18. {  
  19.     int i = 0, j = 0, k = 0,n = 0,m = 0, w = 0,u = 0;  
  20.     RtPoint sp, ep;//剪裁多邊形  
  21.     RtPoint startP, endP;//被剪裁多邊形  
  22.   
  23.     CRtPoint<double> point;  
  24.     CRtPoint<int> st, et;                   
  25.     CRtLine<int> v1,v2;  
  26.   
  27.     PWAPoint* injectP = (PWAPoint*)malloc(sizeof(PWAPoint) * num * total);  
  28.     PWAPoint* temp = (PWAPoint*)malloc(sizeof(PWAPoint) * num * (total));//剪裁加交點插入  
  29.     PWAPoint* temp0 = (PWAPoint*)malloc(sizeof(PWAPoint) * num * (total));//被剪裁加交點插入  
  30.     PWAArray* inP = (PWAArray*)malloc(sizeof(PWAArray) * num * (total));  
  31.     RtPoint* dst = (RtPoint*)malloc(sizeof(RtPoint) * num * total);//輸出陣列  
  32.   
  33.     //求交點  
  34.     startP = *(dest + total - 1);  
  35.     for (j = 0; j < total; j++)//被剪裁多邊形  
  36.     {  
  37.         endP = *(dest + j);  
  38.         st.SetData(startP.x, startP.y);  
  39.         et.SetData(endP.x, endP.y);  
  40.         v2.SetData(st, et);  
  41.   
  42.         sp = *(src + num - 1);  
  43.         for (i = 0; i < num; i++)//剪裁多邊形  
  44.         {  
  45.             ep = *(src + i);  
  46.             st.SetData(sp.x, sp.y);  
  47.             et.SetData(ep.x, ep.y);  
  48.             v1.SetData(st, et);   
  49.               
  50.             if(v2.Intersect(v1,point))//求交點   
  51.             {  
  52.                 injectP[k].point.x = point[0];  
  53.                 injectP[k].point.y = point[1];  
  54.                 injectP[k].flag = 1;  
  55.                 injectP[k].index = j;  
  56.                 injectP[k].index0 = i;  
  57.             }  
  58.             sp = ep;  
  59.         }  
  60.         startP = endP;  
  61.     }  
  62.   
  63.     //剪裁多邊形插入交點  
  64.     w = 0;  
  65.     for (i = 0; i < num; i++)  
  66.     {  
  67.         temp[w].point = *(src + i);  
  68.         temp[w].flag = 0;  
  69.         w++;  
  70.   
  71.         n = 0;  
  72.         for (j = 0; j < k;j++)  
  73.         {  
  74.             if (injectP[j].index0 == j)//屬於剪裁當前線段的交點  
  75.             {  
  76.                 inP[n].dist = sqrt(pow(injectP[j].point.x - temp[w].point.x , 2) + pow(injectP[j].point.y - temp[w].point.y , 2));  
  77.                 inP[n].index = j;  
  78.                 n++;  
  79.             }  
  80.         }  
  81.   
  82.         for (j = 0; j<n; j++)  
  83.         {  
  84.             for (m = j+1;m<n;m++)  
  85.             {  
  86.                 PWAArray tempp;  
  87.                 if (inP[j].dist > inP[m].dist)  
  88.                 {  
  89.                     tempp = inP[j];  
  90.                     inP[j] = inP[m];  
  91.                     inP[m] = tempp;  
  92.                 }  
  93.             }  
  94.         }  
  95.   
  96.         for (j = 0; j < n;j++)  
  97.         {  
  98.             temp[w] = injectP[inP[j].index];  
  99.             w++;  
  100.         }  
  101.   
  102.     }  
  103.   
  104.     //被剪裁多邊形插入交點  
  105.     u = 0;  
  106.     for (i = 0; i < total; i++)  
  107.     {  
  108.         temp0[u].point = *((dest) + i);  
  109.         temp0[u].flag = 0;  
  110.         u++;  
  111.           
  112.         n = 0;  
  113.         for (j = 0; j < k;j++)  
  114.         {  
  115.             if (injectP[j].index0 == j)//屬於剪裁當前線段的交點  
  116.             {  
  117.                 inP[n].dist = sqrt(pow(injectP[j].point.x - temp0[u].point.x , 2) + pow(injectP[j].point.y - temp0[u].point.y , 2));  
  118.                 inP[n].index = j;  
  119.                 n++;  
  120.             }  
  121.         }  
  122.           
  123.         for (j = 0; j<n; j++)  
  124.         {  
  125.             for (m = j+1;m<n;m++)  
  126.             {  
  127.                 PWAArray tempp;  
  128.                 if (inP[j].dist > inP[m].dist)  
  129.                 {  
  130.                     tempp = inP[j];  
  131.                     inP[j] = inP[m];  
  132.                     inP[m] = tempp;  
  133.                 }  
  134.             }  
  135.         }  
  136.           
  137.         for (j = 0; j < n;j++)  
  138.         {  
  139.             temp0[u] = injectP[inP[j].index];  
  140.             u++;  
  141.         }  
  142.           
  143.     }  
  144.   
  145.     //標記出入點  
  146.     for (i = 0; i < w; i++)  
  147.     {  
  148.         if (temp[i].flag == 1)  
  149.         {  
  150.             if (i == w - 1)  
  151.             {  
  152.                 if(temp[0].flag == 0)  
  153.                 {  
  154.                     temp[i].flag0 = 0;  
  155.                 }  
  156.                 if (temp[0].flag == 1)  
  157.                 {  
  158.                     temp[i].flag0 = 1;  
  159.                 }  
  160.             }  
  161.             else  
  162.             {  
  163.                 if (temp[i + 1].flag == 0)  
  164.                 {  
  165.                     temp[i].flag0 = 0;  
  166.                 }  
  167.                 else  
  168.                 {  
  169.                     temp[i].flag0 = 1;  
  170.                 }  
  171.             }             
  172.         }  
  173.     }  
  174.     for (i = 0; i < u; i++)  
  175.     {  
  176.         if (temp0[i].flag == 1)  
  177.         {  
  178.             if (i == u - 1)  
  179.             {  
  180.                 if(temp0[0].flag == 0)  
  181.                 {  
  182.                     temp0[i].flag0 = 0;  
  183.                 }  
  184.                 if (temp0[0].flag == 1)  
  185.                 {  
  186.                     temp0[i].flag0 = 1;  
  187.                 }  
  188.             }  
  189.             else  
  190.             {  
  191.                 if (temp0[i + 1].flag == 0)  
  192.                 {  
  193.                     temp0[i].flag0 = 0;  
  194.                 }  
  195.                 else  
  196.                 {  
  197.                     temp0[i].flag0 = 1;  
  198.                 }  
  199.             }             
  200.         }  
  201.     }  
  202.   
  203.     k = 0;  
  204.     //統計剪裁區域  
  205. loop3:  
  206.     for (i = 0; i < u; i++)//被剪裁區域  
  207.     {  
  208.         if (0 == temp0[i].flag0)//是入點  
  209.         {  
  210.             dst[k] = temp0[i].point;  
  211.             k++;  
  212.             temp0[i].flag0 = -1;  
  213.             goto loop0;  
  214.         }  
  215.     }  
  216.     return 1;  
  217.   
  218. loop0:  
  219.     for (j = i; j < u; j++)  
  220.     {  
  221.         if (temp0[i].flag0 != 1)//不是出點  
  222.         {  
  223.             dst[k] = temp0[i].point;  
  224.             temp0[i].flag0 = -1;  
  225.             k++;  
  226.         }  
  227.         else  
  228.         {  
  229.             goto loop1;  
  230.         }  
  231.     }  
  232.   
  233. loop1:  
  234.     for (m = 0; m < w; m++)  
  235.     {  
  236.         if (dst[k].x == temp[m].point.x && dst[k].y == temp[m].point.y)  
  237.         {  
  238.             goto loop2;  
  239.         }  
  240.     }  
  241.   
  242. loop2:  
  243.     for (n = m+1;n < w; n++)  
  244.     {  
  245.         if (temp[i].flag0 != 0)//不是入點  
  246.         {  
  247.             dst[k] = temp[i].point;  
  248.             temp[i].flag0 = -1;  
  249.             k++;  
  250.         }  
  251.         else  
  252.         {  
  253.             goto loop3;  
  254.         }  
  255.     }  
  256.   
  257.     free(injectP);  
  258.     free(temp);  
  259.     free(temp0);  
  260.     free(inP);  
  261.     return 0;  
  262. }  

 

相關文章