https://blog.csdn.net/u010019717/article/details/52753855
2016.10.18 孫廣東
http://blog.csdn.net/u010019717
日文《【Unity】Ear Clipping Triangulation》, 它使用這個技術 弄了軟體的功能 復原了 Soft Body - p5.js
這也是我Get到的新概念.
一種 網格多邊形三角化算法之一。
我之前 也寫過文章關於在Unity下 使用Mesh 繪制多邊形, 正方體、 三棱錐等。
我在搜索這個概念的時候看到一篇 非常好的翻譯文章:
http://www.cnblogs.com/xignzou/p/3721494.html
抱歉, 我下面也只是做一個粘貼而已。 和一些自己當時的理解
使用EarClipping三角化多邊形(翻譯)
---Triangulation by Ear Clipping(http://www.geometrictools.com/Documentation/TriangulationByEarClipping.pdf)
內容提要
1、簡介
2、Ear Clipping方法
3、含有島洞的多邊形
4、查找相互可見點
5、含有多個島洞的多邊形
6、嵌套多邊形
1、簡介
將簡單多邊形轉換成一組由同樣頂點組成的三角形集合是計算機圖形學中的一個經典問題。問題中,簡單多邊形是指由一組有序頂點組成的,點V0~點Vn-1。相鄰的頂點之間通過邊(Vi,Vi-1)連接,並且邊(Vn-1,V0)連接起始點。每個頂點被兩條邊所共享,而邊的所有交點都是頂點。圖1.1的示例則是說明
圖中,左邊的多邊形是個簡單多邊形,中間的多邊形點1被四條邊共享,不符合定義的條件,不算是簡單多邊形,右側的多邊形中邊14,邊02的交點不是我們定義的頂點之一,因此該圖形也不符合簡單多邊形的定義。
圖1.1 簡單多邊形示例
如果一個多邊形是簡單多邊形,當你延長一條邊的時候,內部有界區域中總是在邊的一側。假設多邊形頂點逆時針排序,那么當你延長邊的時候,內部指的便是你的左邊。 我們圖1.1中的簡單多邊形頂點順序使用的便是逆時針的方法。
將一個簡單多邊分解成三角形集合的方法稱之為多邊形的三角形化(triangulation of thePolygon)。幾何學的知識告訴我們,由n個頂點組成的簡單多邊形總是可以分解成n-2個三角形。解決該問題的方法比較多,他們共同的特點就是算法的復雜度漸近階隨着n的增長沒有約束(Various algorithms have beendeveloped for triangulation, each characterized by its asymptotic order as ngrows without bound.)。最簡單的分割算法是耳剪裁(EarClipping),正是本文檔中所要描述的算法。EarClipping的算法復雜度O(n2_n平方),雖然也存在效率更高的算法,但是被其他組織嚴格使用並沒有公開。水平分解成梯形隨后被自己單調三角多邊形的鑒定陽離子是一種復雜度為O(nlog n)的算法[ 1 , 3 ] 。使用增量的改進隨機算法產生一個O ( n日志? n),其中記錄? n為重對數函數[ 5 ]。此功能是電子?作為各自一個常數非常大的n ,你會在實踐中看到的,所以對於所有的實際目的的隨機方法是線性時間。理論存在的復雜度為O(n)的算法,比較復雜,到目前依舊沒有看到具體的公開實現。
2、Ear Clipping方法
簡單多邊形的耳朵,是指由連續頂點V0,V1和V2(就是這樣的順序相鄰)組成的內部不包含其他任意頂點的三角形。在計算機幾何術語中,v0與V2之間的連線稱之為多邊形的對角線(這個找完之后要在編輯器中顯示出來會更好看出三角形分割), 點V1稱之為耳尖(找耳尖也是這么找的)。雖然你可以將耳尖放到三角形的任意一個頂點上,但是我們認為三角形包含一個耳尖。一個由四個頂點(或者更多)組成的多變形至少有兩個不重疊的耳尖。這個特性提供了一個通過遞歸來解決三角化分割的方法。針對由N個定點組成的多邊形,找到其耳尖,移除唯一耳尖上的頂點,此時剩余頂點組成了一個n-1個頂點的簡單多邊形。我們重復這個操作知道剩余三個頂點。這樣的話會產生一個復雜度為O(N3)的算法。
我發現一個現象, 如果一個頂點是耳尖, 一定是凸頂點。 反之不成立。
隨着一些細節改進,耳朵消除可以在O (N2)的時間來完成。第一步是將多邊形使用雙向鏈表存儲,這樣可以快速的移除耳朵。列表的構建復雜度是O(n),第二部是遍歷頂點尋找耳朵。對於每一個頂點Vi和圍繞該頂點的三角形<Vi-1,Vi,Vi+1>,(總長度為N,所以Vn=V0,兵器V-1=Vn-1),測試其他頂點是否在當前三角形中,如果有一個頂點在三角形里面,則不是耳朵,只有都不在的情況,才算是找到一個耳朵。具體實現的時候我們可以考慮以下因素讓這個算法更為高效。當發現有一個點在三角形里面的時候便可以開始放棄當前測試。一個凹拐角其兩邊的夾角大於180,而一個凸拐角兩邊夾角小於180。存儲多邊形的數據結構使用四個鏈表,具體使用數組而不是標准的動態需要分配合釋放存儲器的鏈表。多邊形的頂點存儲在在一個循環鏈表中,凹頂點和凸頂點存儲在線型表中,耳尖存儲在一個循環列表中。
這里要說明一下,怎么判斷一個拐角是凸凹, 通過兩個臨邊求叉積(法線方向), 如果>0 說明是 角度<180 是凸角度, 反之。。。。
怎么判斷一個點在沒在三角形內呢? 順時針/逆時針判定法。 該方法要求點的順序是順時針或逆時針的,如果是順時針的點,沿着3條邊走,如果目標點P在三角形內,那么P始終在邊的右側。同理,如果是逆時針的話,目標點p應該始終在邊的左側。 例:逆時針的三個點abc,判斷abXap , bcXbp , caXcp,如果這三個向量叉積的Z值都同向,並且都為負的話(左手系),說明p點在三角形內部。(Unity使用的是右手坐標系,也是右手法則,逆時針旋轉,大拇指向旋轉的正方向)
http://blog.csdn.net/evilbox/article/details/42681381
//原理:通過向量之間的對比,利用點乘和差乘實現判斷一個點是否在三角形里面。 叉乘結果用右手判斷法則。
publicstatic bool InTrigon(Vector3_target,Vector3 _center,Vector3 _left,Vector3 _right){
Vector3Ctl=_left-_center;
Vector3Ctr=_right -_center;
Vector3Ctt=_target-_center;
Vector3Ltr=_right-_left;
Vector3Ltc=_right-_center;
Vector3Ltt=_left-_target;
Vector3Rtl=_left-_right;
Vector3Rtc=_center-_right;
Vector3Rtt=_target-_right;
if(
Vector3.Dot(Vector3.Cross(Ctl,Ctr).normalized,Vector3.Cross(Ctl,Ctt).normalized)==1&&
Vector3.Dot(Vector3.Cross(Ltr,Ltc).normalized,Vector3.Cross(Ltr,Ltt).normalized)==1&&
Vector3.Dot(Vector3.Cross(Rtc,Rtl).normalized,Vector3.Cross(Rtc,Rtt).normalized)==1
)
return true;
else
return false;
}
一旦凸頂點和耳朵的鏈表構建成功,每次遍歷都會移除一個耳朵。假設當前Vi是個耳朵並且被移除掉,那么邊結構的相鄰點Vi-1,Vi+1則會發生變化,如果相鄰點是凸頂點,那么依舊保持凸點,如果相鄰點是個耳朵,那么當Vi被移除后則不一定能保持耳朵的狀態,如果相鄰點是個凹點,那么他則有可能變為一個凸點甚至是耳朵。因此當移除頂點Vi后,如果相鄰點是凸點,則必須遍歷相關頂點,通過遍歷查看是否包含其他點,來測試它是否是一個耳朵。我們有n個耳朵,每一次更新都會觸發一個耳朵檢測,每次過程中更新O(n),所以移除進程的復雜度是O(n2)。
圖2.1 右側多邊形展示了左側耳朵2,3,4被移除后的的效果
下面的示例使用圖1.1中的簡單多邊形,具體展示算法的實現和構建。初始夠將的時候凸頂點集合C={0,1,3,4,6,9},初始凹頂點集合R={2,5,7,8},初始的耳朵集合E={3,4,6,9}(按照定義,順序跑一遍就找到了),遍歷,當頂點3被移除的時候,其對應的耳朵是三角形T0=<2,3,4>。圖2.1展示了改進后的多邊形效果。相鄰點2是個凹節點,變化后依舊是凹的,頂點4之前是個耳朵,現在依舊耳朵,所以凹節點結合R保持不變,耳朵集合現在變成了E={4,6,9}(3已經被移除)。
繼續移除點點4,此時的三角形對應是T1=<2,4,5>。圖2.2展示了變化后的效果。
圖 2.2 移除三角形<2,4,5>后的效果
相鄰頂點2依舊保持凹節點,相鄰點5之前是凹頂點,現在變為了凸頂點,經過測試最終發現它是個耳朵。因此定點列表最終的變化結果是,凹節點幾何R={2,7,8},耳朵集合E={5,6,9}(移除4,添加了新的5)。
如果一處頂點5,此時對應的三角形是T2=<2,5,6>,圖2.3展示了變化后的效果。
圖2.3 移除耳朵<2,5,6>后的效果
相鄰頂點2起初是個凹節點,現在變為另一個圖節點,從圖上有點不大容易看出頂點7其實是位於三角形<1,2,6>中間的,所以2不是個耳朵。頂點6起初是個耳朵,現在依舊。操作完成之后各頂點列表中,凹節點集合R={7,8}(移除了2),耳朵集合E={6,9}(移除了5)。
繼續,移除頂點6,此時對應的三角形是T3=<2,6,7>。圖2.4是變化后的前后對比效果。
圖2.4移除耳朵<2,6,7>
相鄰點2是圖節點,保持依舊,但是它由一個非耳朵變成了耳朵節點。相鄰頂點7依舊是個凹節點,因此凹節點集合保持不變。各隊列結果,耳朵集合E={9,2}(添加2移除6),耳朵列表這樣寫是因為新耳朵的加入是在移除了舊的耳朵操作之后(先來后到),在移除舊耳朵之前,它忍讓可以被當做是列表的第一個元素(循環列表)。刪除操作設置第一項是下一個指向的老耳朵,而不是以前的值。
移除頂點9,對應的三角形T4=<8,9,0>。圖2.5展示了操作前后的多邊形對比
相鄰頂點8是個凹節點,操作后編程了一個凸點,並且是一個耳朵。相鄰點0是個凸點,保持依舊,並且由非耳朵變成了耳朵。操作結束后的各隊列集合如下:凹點集合R={7},耳朵集合E={0,2,8}(添加8,添加0,移除9,順序按照了程序的產生方式)
移除頂點0,對應的三角形是T5=<8,0,1>,圖2.6是操作前后的多邊形對比
相鄰頂點8和1都是凸節點並且保持依舊,頂點8依舊是個耳朵,頂點1依舊不是耳朵。因此凹節點集合不變,耳朵列表變為E={2,8}(移除了0)
最后,移除耳朵2頂點,對應的三角形是T6=<1,2,7>。圖2.7展示了操作前后的多邊形對比。
到現在,已經沒有在需要更新的凹點和耳朵列表,到此為止我們只剩下了三個頂點,這三個頂點組成最后的三角形T7=<7,8,1>。所有的三角形分割線是如圖2.8
3、含有島洞的多邊形
耳朵裁剪法也可以應用到包含島洞的多邊形中。考慮如圖3.1所示的一個包含島洞的多邊形,他有一個外多邊形和一個內洞組成。外側多邊形的定點方形和內測島洞的頂點方向必須是相反的(頂點順序要反向)。如果外側的頂點是逆時針順序,那么內測的頂點則必須是順時針順序。
圖中藍色的頂點是互相可見的,通過繪制兩條雙向的邊連接兩個藍色的頂點,可以把圖3.1轉變成一個簡單多邊形。圖3.2中顯示這兩天便,一條藍色,一條紅色,兩條邊是重疊的,這里為了看得清晰特殊標繪出來。
圖中通過小箭頭標識出了邊的方向。
依照這種情景,互相可見的頂點必須復制到不同的數據結構中以供使用。每個數據結構存儲當前點可能是凹點也可能是凸點。即使使用同一個坐標的兩個點,也可能一個是凹點,一個是凸點,比如位於最下面的藍色頂點11(18)。原始的頂點在最初的外多邊形中是凹點,分割后在新的多邊形中,V11與紅色邊相連,構成了一個凹點,與藍色邊相連構成另一個凸點。
原始的外多邊形頂點數據:
{0,1,2,3,4,5,6,7,8,9,10,11,12,13,14}
原始的內多邊形數據
{15,16,17}
分割后頂點V11被復制出V18,頂點V16復制出V19,這時候的簡單多邊形數據如下:
{0,1,2,3,4,5,6,7,8,9,10,11,16,17,15,19,18,12,13,14}
新的多邊形即可以使用耳朵裁剪法切割。
4、查找相互可見點
視覺上,圖3.1我們可以直接看出頂點V11和頂點V16是兩個相互可見的點。而實際上,這個多邊形中存在不止一對這樣的相互可見點(一個來自內多邊形,一個來自外多邊形)。我們需要個算法來查找一對這樣的可見點。
下面的算法便是如此。查找內多邊形中x軸方向最大值的頂點,在圖3.1中,這個點是V16,假設以此為原點構造左邊系,沿着x軸正方向觀察,該軸線可能與外對變形的邊教育一點,或者直接連接到外多邊形的頂點上,很大程度上,我們會獲得一個邊的交點。此時,這條邊的兩個端點則很有可能使我們所要尋找的。如果是一個獨立的點點,那么便和最初的V16組成了一對相互可見點。
下面,我們考慮在x軸正方向的最近可視點是相交邊的端點。如圖4.1所示:
假設M是坐標軸的原點(實際上是頂點V16)。向量M+t(1,0)則是圖中藍色標記的x軸射線,最近的交點使用紅色標記,叫做點I。最近點所在的邊使用綠色標繪出來。邊的結束點用一個最大的x值P,假設P點是與M對應的最近相互可視點,那么連接他們的線,與點I組成的三角形<M,I,P>使用橙色繪制如下。
在圖4.1中,P相對M可見,但是,也存在下面的這種情況,線段<M,P>與多邊形其他的邊相交,即P對M不可見,圖4.2展示了這樣的情況。
灰色表示該區域位於外部多邊形和內部多邊形之間,橙色是其中的一部分,在圖4.1中,有<M.I,P>組成的三角形全部位於(外部-內部)多邊形的一部分,4.2中,外部多邊形被裁剪到了多個三角形中,只有部分子集三角形才算的上是外部-內部多邊形的一部分。
外部多邊形有四個點位於多邊形<M,I,P>之間,一般來說,如果一個頂點存在於一個三角形內部,則至少有一個連接邊,對於所有連接邊,至少有一個是對M可見的。圖4.2中,三角形內有三個連接頂點,標記為A,B,C,這樣的話連接頂點A對M是可見的,因為連接他們的邊<M,R>和邊<1,0>之間的夾角最小。
算法總結如下:
1、尋找內部多邊形x周最大值的頂點M
2、沿X周正方向,尋找最近的相交邊<Vi,Vi+1>,讓其焦點設置為I,構成X軸方向對M的最近可見點
3、如果I是一個外部頂點,則M和I相互可見,算法執行結束
4、如果I只是邊上的一個點,尋找端點中x值片的一個,設置為P
5、尋找位於P內的其他外多邊形的可連接頂點。如果所有的頂點都在<M,I,P>之外,則M與P相互可見,反正結果
6、如果有至少一個點位於三角形<M,I,P>內部,則尋找其中的一個頂點,計算其與x軸(1,0)的夾角,夾角最小的頂點R與M構成相互可見邊,算法結束
7、在這個算法中,有可能有多個頂點同事具有最小的角度,這種情況下,尋找距離M最近的一個點即可
5、含有多個島洞的多邊形
一個多邊形有可能包含多個島洞,這里假設所有的島洞都僅被外多邊形包含,彼此不存在嵌套島洞的情形,圖5.1展示了這樣的一個多邊形。
從圖上可以清晰的看出,內多邊形I1沒有任何一個頂點與外部多邊形相互可見,多邊形I0則擁有多個與外部多邊形相互可見的點。因此,我們可以使用前面介紹的算法,首先把I0和外部多邊形拆分,合並成為一個簡單多邊形,這樣,新形成的外多邊形則和I1構成了一件簡單多邊形,使用耳切法分割集合。
假設有多個內多邊形,擁有最大X值的內部多邊形則可被選中作為與外多邊形合並的首選。重復這個過程知道全部成為簡單多邊形即可。
6、嵌套多邊形
內多邊形也可能包含一些淚如島洞的外多邊形,類如嵌套。這樣導致了嵌套多邊形的樹形結構。根節點是最外圍的外多邊形,子節點則是包含在當前最外多邊形內部的內多邊形。每一個孫子節點,則是構成直接被最外圍多邊形包含的內多邊形的子樹,每個多邊形樹可以按照寬度優先去遍歷。
圖6.1展示了一個嵌套多邊形構成的樹結構,可以分割使用耳切法
樹形結構展示如下:
存儲當前樹節點的數據結構可以定義如下:
解析當前樹形結構的流程算法大致如下:
函數MakeSimple封裝了獲取一個內多邊形和其外多邊形中間相互可見邊的算法,通過復用他們,產生兩個新邊,可以生成一個新的簡單多邊形,這個過程要對沒個內多邊形不停的重復
完成最終的三角划分,以便獲取最終的索引順序,來代替最初的多邊形頂點定義順序。相比原始的值,這里可能需要復制一些頂點,以便被多個三角形使用
http://blog.csdn.net/u010019717