Unity中關於圖形切割的問題----計算機圖形學之三角化(Triangulation)在遊戲中的應用

  1. 寫在之前

      最近的一個2D項目中,要求對一個方塊不斷進行切割,切掉較小者,留下較大者,如此,便引出了一個問題:不同形狀圖形的數據的更新。而圖形的數據結果的計算,就涉及到了計算機圖形學相關的知識---三角化的概念邊產生。

    2. 問題拆分

      a> 一個簡單的圖形(此處僅指2D)--如正方形,在代碼中的表示方法

      b> 三角化概念以及三角化的方法

    3. 關於圖形的表示方法

      如下圖,四個點的座標信息保存在一個雙向循環鏈表的結構中,這樣,我們只需要知道一個點就可以按照我們預定的方向拿到剩下的所有點了。

                          

                                             可識別的圖形(左)和轉換後的數據示意圖(右)

    這樣,根據三點不同時都在同一條直線的原則,一個平面圖形的數據就構建完成了。

      4. 三角化

      因爲計算機的GPU在繪製面的時候,是以三角形爲基本單位的,所有的複雜圖形都是大量的三角形拼接成的。這就是三角化概念的由來:將簡單多邊形分解成許多三角形,由這些三角形的頂點構成這個多邊形。這個拆解的過程就是三角化的過程。簡單舉例,上圖的矩形三角化之後有兩個三角形:△P0 P1 P2、△P2 P3 P0,也就是點集合的索引:012、230的確定的過程就是三角化。

      關於多邊形的三角化算法,在這裏,介紹一種比較簡單容易理解的三角化法--耳切法。

      耳切法適用於簡單多邊形的三角化,這裏需要說明幾個概念。

簡單多邊形:幾何學中將互不相交的一些線段成對連接形成的閉合路徑的平面圖形,稱爲簡單多邊形。任意一個簡單多邊形至少有3個頂點。

“耳朵”(Ear):組成多邊形的頂點,其相鄰的兩個點連成一條線段,這條線段完全落在這個多邊形的內部,而且你會發現,這個頂點在該多邊形中是一個凸頂點,將該點移除之後,該簡單多邊形的邊數減少1個,重複這樣的過程,最終將該多邊形三角化。

以下便是對耳切法理論(Ear Clip)的說明和應用:

                                                                

                                                                                   一個簡單多邊形(10條邊) 

       定義上面的多邊形點集合P:(0~9),並使用雙向鏈表將這些點保存起來。接着,根據“耳朵”的定義,找出點集合P中所有的“耳”點(即符合條件的凸頂點),即初始狀態下的“耳朵”集合E,所有的凸頂點(Convex Vertices)集合C,所有的凹頂點(即該點的兩條邊形成的內角大於180度,稱Reflex Vertices)R。得到初始狀態 E={3,4,6,9},C = {0,1,3,4,6,9},R = {2,5,7,8}。

      刪掉E中的頂點3,得到第一個三角形 T0 = {2,3,4},如此,移除頂點3之後,如下圖所示: 

                                                                       

                                                                           移除頂點3後的多邊形(9條邊)

        之前頂點2是凹頂點,耳點3移除之後,仍然是凹頂點。頂點4仍舊是耳點,所以更新後的耳點集合E={4,6,9},凸頂點C={0,1,4,6,9},凹頂點R集合沒發生變化。

        移除耳點4之後,得到第二個三角形 T1 ={2,4,5},頂點5現在變成了耳點被添加進E集合中:E={5,6,9},C= {0,1,5,6,9},R = {2,7,8}。(頂點2處的內角稍微大於180度)。如下圖:

                                                                            

                                                                         圖3 移除頂點4後的多邊形(8條邊)

        移除耳點5,此時頂點6仍然是耳點,E = {6,9},但頂點2的內角小於180度,頂點2成爲了凸點被添加到C集合中:C={0,1,2,6,9},R = {7,8},此過程產生第三個三角形T2 = {2,5,6}。如圖所示:

                                                                                

                                                                                 圖4 移除頂點5之後的多邊形(7條邊)

      移除耳點6,此時一直是凹頂點的2成爲了耳點被添加到E中,E = {9,2},凹頂點集合R不變,凸頂點集合C = {0,1,2,9},此過程產生第四個三角形:T3={2,6,7}。如圖所示:

                                                                                     

                                                                               圖5 移除頂點6之後的多邊形(6條邊)

      移除耳點9,此時頂點0和8變成了耳點被添加到E中:E =  {0,2,8},凹頂點R中,頂點8已不是凹頂點,故R = {7},凸頂點 C ={0,1,2,8}。由此產生第五個三角形 T4 = {8,9,0}。如下圖所示:

                                                                                     

                                                                             圖6 移除頂點9之後的多邊形(5條邊)

      移除耳點0,E集合變爲 E = {1,2,8},C = {2,8},R = {7},由此產生第六個三角形T5={8,0,1}。如下圖所示:

                                                                                    

                                                                                圖7 移除耳點0之後的多變形(4條邊)

      移除耳點2,剩下三個點,自然到了三角化的最小單元,作爲迭代結束條件,產生第七個三角形並記錄下來: T6 = {1,2,7},加上最後剩下的三角形作爲第八個三角形 T7 = {1,7,8},並退出迭代,整個三角化過程完成;同時,R爲空,C={1,7,8}。如下圖所示:

                                                                                  

                                                                        圖8 移除耳點2之後的多邊形(3條邊)

                                                                        

                                                                               圖9 被三角化之後的完整多邊形

      從以上的過程,我們發現,每次移除一個點,其多邊形的邊數必定會減少1,直至剩餘3個點。整個迭代中,以移除某個點之後,剩下的點是否等於3爲結束條件,並不斷更新點集合E、C、R。這裏的三個點集合中, 其中集合C和集合R在整個迭代過程中只是一個輔助計算結果,原文中,作者並未對該兩個集合的作用做出解釋。這個問題等到了最後在討論。

     以上的迭代過程中,必須要知道一下的幾個知識,纔可以很好的理解這個迭代算法的含義:

    1> 怎樣判斷一個頂點爲凸點?根據對凹頂點的定義,該點處相鄰的兩條邊組成的多邊形的內角大於180度,由此可以用向量叉乘得到:的值小於0。其實對這兩個向量叉乘的結果取絕對值,得到的結果是這個三角形的面積。

    2> 怎樣判斷一個頂點爲凹點?根據對凸頂點的定義,該點處相鄰的兩條邊組成的內角小於180度,同樣的值大於0。

    3> 怎樣判斷一個頂點爲“耳”點?關鍵是線段是否都落在多邊形內,並且與其他邊不相交,即:該點處的內角小於180度,即該點是否包含在凸點集合C中;

該點以及由該點附近兩個點組成的三角形中,不包含多邊形的其他頂點,Point In Polygon(PIP)問題的解法:

 

                                   

                                                                            P點與∆ABC的位置示意圖

解法1:面積法,如果P在三角形內,那麼由P與其他三個點構造的三個三角形的面積之和等於原來三角形的面積,如在三角形外部,則其面積之和肯定大於原三角形面積。

解法2:內角和法,∠BAP + ∠CAP +∠CBP+ ∠ABP + ∠ACP + ∠BCP = 180度,那麼P點落在了三角形內部。如果內角和大於180度,那麼P點落在三角形外部;如果內角和小於180度,那麼P點落在∆ABC平面外,即不在∆ABC內。

解法3:同側法,即不管順時針方向還是你是正方向,以其中一個點爲起點到下一個點的過程中,如果點P始終在該邊的同一側(左或右),即P點在內部,否則在外部。那麼又要用到向量的叉乘。如果選擇順時針方向,即A -> C -> B,相反,如果是逆時針方向,叉乘結果大等於0。

解法4:射線法,在點P處發射沿任意方向(一般計算的時候水平的方向)的射線,該射線跟多邊形的交點個數爲奇數,則P點在內部,反之在外部。但是也有一定的問題,如下圖,其中那條粗線指出了該方法的問題,當這條射線正好跟多邊形的一個頂點相交。對於這種情況,在計算之前判斷該點是否落在要選定的射線方向上,如果在,排除該點不計,否則進入下一步計算判斷。

                                                                  

                                                                          點與多邊形位置示意圖

    5. 算法實現      

           

public static int[] Triangulate(List<Vector2> m_points)
    {
        List<int> indices = new List<int>();
        int n = m_points.Count;
        if (n < 3)
            return indices.ToArray();
        int[] V = new int[n];
        if (Area(m_points) > 0) // 將所有頂點按照順時針方向排列
        {
            for (int v = 0; v < n; v++)
                V[v] = v;
        }
        else
        {
            for (int v = 0; v < n; v++)
                V[v] = (n - 1) - v;
        }
        int nv = n;
        int count = 2 * nv;
        for (int v = nv - 1; nv > 2;)// 開始迭代
        {
            if ((count--) <= 0)
                return indices.ToArray();
            int u = v;
            if (nv <= u)
                u = 0;
            v = u + 1;
            if (nv <= v)
                v = 0;
            int w = v + 1;
            if (nv <= w)
                w = 0;
            if (Snip(u, v, w, nv, V, m_points))
            {
                int a, b, c, s, t;
                a = V[u];
                b = V[v];
                c = V[w];
                indices.Add(a);
                indices.Add(b);
                indices.Add(c);
                for (s = v, t = v + 1; t < nv; s++, t++)
                    V[s] = V[t];
                nv--;
                count = 2 * nv;
            }
        }
        indices.Reverse();
        return indices.ToArray();
    }   

   6.文章出處說明

      Triangulation by Ear Clipping,David Eberly, Geometric Tools, Redmond WA 98052

發佈了6 篇原創文章 · 獲贊 1 · 訪問量 390
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章