三角剖分算法(delaunay)

開篇

在作一個Low Poly的課題,而這種低多邊形的成像效果在如今設計中愈來愈被喜歡,其中的低多邊形都是由三角形組成的。git

而如何自動生成這些看起來很特殊的三角形,就是本章要討論的內容。github

 

項目地址: https://github.com/zhiyishou/polyer算法

Demo:https://zhiyishou.github.io/Polyer數組

 

選擇

其是最早是由不少離散的點組成,基於這個肯定的點集,將點集鏈接成必定大小的三角形,且分配要相對合理,才能呈現出漂亮的三角化。緩存

這時則要求使用三角剖分算法(Delaunay),引於百度百科《Delaunay三角剖分算法》對Delaunay三角形的定義爲:優化

【定義】三角剖分:假設V是二維實數域上的有限點集,邊e是由點集中的點做爲端點構成的封閉線段, E爲e的集合。那麼該點集V的一個三角剖分T=(V,E)是一個平面圖G,該平面圖知足條件:
1.除了端點,平面圖中的邊不包含點集中的任何點。
2.沒有相交邊。
3.平面圖中全部的面都是三角面,且全部三角面的合集是散點集V的凸包。
在實際中運用的最多的三角剖分是Delaunay三角剖分,它是一種特殊的三角剖分。先從Delaunay邊提及:
【定義】Delaunay邊:假設E中的一條邊e(兩個端點爲a,b),e若知足下列條件,則稱之爲Delaunay邊:存在一個圓通過a,b兩點,圓內(注意是圓內,圓上最多三點共圓)不含點集V中任何其餘的點,這一特性又稱空圓特性。
【定義】Delaunay三角剖分:若是點集V的一個三角剖分T只包含Delaunay邊,那麼該三角剖分稱爲Delaunay三角剖分。
【定義】假設T爲V的任一三角剖分,則T是V的一個Delaunay三角剖分,當前僅當T中的每一個三角形的外接圓的內部不包含V中任何的點。 
 
 如圖,將離散點聯結成Delaunay三角形
 

算法

關於Delaunay三角形的算法,有翻邊算法、逐點插入算法、分割合併算法、Bowyer-Watson算法等。
而在這幾種算法中,逐點插入算法比較簡單、易懂,在本文中只針對該算法進行討論,該算法也是目前使用最爲普遍的Delaunay算法。
 
在該算法中,主要應用Delaunay三角形【定義4】,理解下來就是 每個三角形的外接圓圓內不能存在點集內的其它任何一點,而有時候會出現點在外接圓上的狀況,這種狀況被稱爲「退化」。
 
 在文章 《Triangulate》 裏對該方法進行了分析,並提出了僞代碼思路:
subroutine triangulate
input : vertex list
output : triangle list
   initialize the triangle list
   determine the supertriangle
   add supertriangle vertices to the end of the vertex list
   add the supertriangle to the triangle list
   for each sample point in the vertex list
      initialize the edge buffer
      for each triangle currently in the triangle list
         calculate the triangle circumcircle center and radius
         if the point lies in the triangle circumcircle then
            add the three triangle edges to the edge buffer
            remove the triangle from the triangle list
         endif
      endfor
      delete all doubly specified edges from the edge buffer
         this leaves the edges of the enclosing polygon only
      add to the triangle list all triangles formed between the point 
         and the edges of the enclosing polygon
   endfor
   remove any triangles from the triangle list that use the supertriangle vertices
   remove the supertriangle vertices from the vertex list
end
其方法雖然可實現三角化,可是效率仍是不過高
在看過 https://github.com/ironwallaby/delaunay該js也是基於該僞代碼進行編寫的,可是做者在其中進行了一次排序優化,使得代碼運行效率獲得了提升
 
優化後的僞代碼爲:
input: 頂點列表(vertices)                       //vertices爲外部生成的隨機或亂序頂點列表
output:已肯定的三角形列表(triangles)
    初始化頂點列表
    建立索引列表(indices = new Array(vertices.length))    //indices數組中的值爲0,1,2,3,......,vertices.length-1
    基於vertices中的頂點x座標對indices進行sort           //sort後的indices值順序爲頂點座標x從小到大排序(也可對y座標,本例中針對x座標)
    肯定超級三角形
    將超級三角形保存至未肯定三角形列表(temp triangles)
    將超級三角形push到triangles列表
    遍歷基於indices順序的vertices中每個點            //基於indices後,則頂點則是由x從小到大出現
      初始化邊緩存數組(edge buffer)
      遍歷temp triangles中的每個三角形
        計算該三角形的圓心和半徑
        若是該點在外接圓的右側           則該三角形爲Delaunay三角形,保存到triangles           並在temp裏去除掉           跳過         若是該點在外接圓外(即也不是外接圓右側)
          則該三角形爲不肯定               //後面會在問題中討論
          跳過         若是該點在外接圓內           則該三角形不爲Delaunay三角形           將三邊保存至edge buffer           在temp中去除掉該三角形
      對edge buffer進行去重
      將edge buffer中的邊與當前的點進行組合成若干三角形並保存至temp triangles中
    將triangles與temp triangles進行合併
    除去與超級三角形有關的三角形
end

 

大多數同窗看過僞代碼後仍是一頭霧水,因此用圖來解釋這個過程,咱們先用三點來作實例:this

 

如圖,隨機的三個點spa

 

根據離散點的最大分佈來求得隨機一個超級三角形(超級三角形意味着該三角形包含了點集中全部的點.net

個人方法是根據類似三角形定理求得與矩形一半的小矩形的對角三角形,擴大一倍後則擴大後的直角三角形斜邊通過點(Xmax,Ymin)設計

可是爲了將全部的點包含在超級三角形內,在右下角對該三角形的頂點進行了橫和高的擴展,並要保證這個擴展三角形底大於高,才能實現包含

這樣求得的超級三角形不會特別大使得計算複雜,並且過程也簡單,並將超級三角形放入temp triangles中

接下來就像是僞代碼中描述的那樣,對temp triangle中的的三角形遍歷畫外接圓,這時先對左邊的第一個點進行判斷,其在圓內

因此該三角形不爲Delaunay三角形,將其三邊保存至edge buffer中,temp triangle中刪除該三角形

將該點與edge buffer中的每個邊相連,組成三個三角形,加入到temp triangles中

再將重複對temp triangles的遍歷並畫外接圓,這時使用的是第二個點來進行判斷

  1. 該點在三角形1外接圓右側,則表示左側三角形爲Delaunay三角形,將該三角形保存至triangles中
  2. 該點在三角形2外接圓外側,爲不肯定三角形,因此跳過(後面會講到爲何要跳過該三角形),但並不在temp triangles中刪除
  3. 該點在三角形3外接圓內側,則這時向清空後的edge buffer加入該三角形的三條邊,並用該點寫edge buffer中的三角邊進行組合,組合成了三個三角形並加入到temp triangles中

再次對temp triangles進行遍歷,這裏該數組裏則含有四個三角形,一個是上次檢查跳過的含有第一個點的三角形和新根據第二個點生成的三個三角形

  1. 該點在三角形1外接圓右側,則該三角形爲Delaunay三角形,保存至triangles中,並在temp triangles中刪除
  2. 該點在三角形2外接圓外側,跳過
  3. 該點在三角形3外接圓內側,將該三邊保存至temp buffer中,並在temp triangles中刪除
  4. 該點在三角形4外接圓內側,將該三邊保存至temp buffer中,並在temp triangles中刪除

這時,temp buffer 中有六條邊,triangles中有兩個三角形,temp triangles中有1個三角形

對temp buffer中的六條邊進行去重,獲得五條邊,將該點與這五條邊組合成五個三角形並加入到temp triagnles 中,這時temp triangles中有6個三角形

因爲三個點已經遍歷結束,到了不會再對第三個點造成的三角形作外接圓,這時則將triangles與temp trianlges合併,合併後的數組表示包含已經肯定的Delaunay三角形和剩下的三角形

這時除去合併後數組中的和超級三角形三個點有關的全部三角形,即進行數組座標的限定,則獲得了最後的結果:

     
這是用最少的三個點來作講解,點數越多的話計算量會越大,可是都是在上面步驟下進行的。   
 

問題

在用點對三角形外接圓位置關係進行判斷的時候,爲何點在外接圓的右側的話能夠肯定該三角形是Delaunay三角形

而當點外接圓的外側且非右側時,爲何要路過三角形,不把該三角形肯定爲Delaunay三角形呢?

 

首先,咱們在開始的時候對原始方法進行優化時,咱們增長了一個indices數組來操做vertices,並對indices依據vertices的x座標進行了從小到大的排序

則咱們在後面遍歷點時是從點集的最左側開始的,如圖:

 當遍歷下一個點時,該點在外接圓的右側,則表示之後全部的點都在該外接圓的右側,則保證了Delaunay三角形的空圓特性
而當點在外接圓外,並不是外接圓右側時,如圖:
在該三角形的外切圓中,當遍歷到點1時,符合在外側的條件,可是不能肯定後面全部的點都保持在外接圓外側
若是說該三角形就爲Delaunay三角形的話,如圖中的點2及後面可能出現的點頗有可能出如今圓內,而使該三角形被按邊分解
在咱們的算法中,若是碰到在點在外側且非右側的話,會跳過,該三角形一直在temp triangles中被檢驗,直到碰到下一個點在圓內或圓右纔會從temp triangles中去除,進行後面的操做
 
而當點在圓上時,也是根據在圓內的方法對其進行操做,實際狀況中會出現這種狀況,上文也講過,稱爲「退化」。
 
最後,附一張delaunay的隨機demo圖:
 
 
 
The end.
相關文章
相關標籤/搜索