開篇

在做一個Low Poly的課題,而這種低多邊形的成像效果在現在設計中越來越被喜歡,其中的低多邊形都是由三角形組成的。
而如何自動生成這些看起來很特殊的三角形,就是本章要討論的內容。
項目地址: 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中任何的點。
算法
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
其方法雖然可實現三角化,但是效率還是不太高
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
大多數同學看過偽代碼后還是一頭霧水,所以用圖來解釋這個過程,我們先用三點來做實例:

如圖,隨機的三個點
根據離散點的最大分布來求得隨機一個超級三角形(超級三角形意味着該三角形包含了點集中所有的點)
我的方法是根據相似三角形定理求得與矩形一半的小矩形的對角三角形,擴大一倍后則擴大后的直角三角形斜邊經過點(Xmax,Ymin)
但是為了將所有的點包含在超級三角形內,在右下角對該三角形的頂點進行了橫和高的擴展,並要保證這個擴展三角形底大於高,才能實現包含
這樣求得的超級三角形不會特別大使得計算復雜,而且過程也簡單,並將超級三角形放入temp triangles中

接下來就像是偽代碼中描述的那樣,對temp triangle中的的三角形遍歷畫外接圓,這時先對左邊的第一個點進行判斷,其在圓內
所以該三角形不為Delaunay三角形,將其三邊保存至edge buffer中,temp triangle中刪除該三角形

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

再將重復對temp triangles的遍歷並畫外接圓,這時使用的是第二個點來進行判斷
- 該點在三角形1外接圓右側,則表示左側三角形為Delaunay三角形,將該三角形保存至triangles中
- 該點在三角形2外接圓外側,為不確定三角形,所以跳過(后面會講到為什么要跳過該三角形),但並不在temp triangles中刪除
- 該點在三角形3外接圓內側,則這時向清空后的edge buffer加入該三角形的三條邊,並用該點寫edge buffer中的三角邊進行組合,組合成了三個三角形並加入到temp triangles中

再次對temp triangles進行遍歷,這里該數組里則含有四個三角形,一個是上次檢查跳過的含有第一個點的三角形和新根據第二個點生成的三個三角形
- 該點在三角形1外接圓右側,則該三角形為Delaunay三角形,保存至triangles中,並在temp triangles中刪除
- 該點在三角形2外接圓外側,跳過
- 該點在三角形3外接圓內側,將該三邊保存至temp buffer中,並在temp triangles中刪除
- 該點在三角形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坐標進行了從小到大的排序
則我們在后面遍歷點時是從點集的最左側開始的,如圖:


