【Aegisub】不規(guī)則三角網(wǎng)(TIN)、德勞內(nèi)(delaunay)三角網(wǎng)的生成算法

三角網(wǎng)當然就是一堆點連起來形成的三角形網(wǎng)絡(luò)
在生成不規(guī)則三角網(wǎng)TIN(即Triangulated Irregular Network)的時候,非常常見的就是delaunay三角網(wǎng)了
那么什么樣的三角網(wǎng)是德勞內(nèi)三角網(wǎng)呢?就是如果有一堆離散的點,比如其中有個點A,你找到這堆離散點里和A最近的兩個點,連接起來,就構(gòu)成了一個delaunay三角形,所以這一堆點都和與它最近的兩個點相連,連完以后,就成了一張狄洛尼(德勞內(nèi))三角網(wǎng)了
這樣形成的德勞內(nèi)三角網(wǎng)有一些特性,比如“空圓特性”:
即?對于delaunay三角網(wǎng)中任意兩個有公共邊的三角形,它們中任意一個三角形的外接圓中都不會包含有另一個三角形的頂點。如圖1.01

在圖1.01中,ABD和BCD就是兩個德勞內(nèi)三角形,因為C點不會落在ABD的外接圓內(nèi),同時A點也沒有落在BCD的外接圓內(nèi),這個就滿足“空圓特性”
知道了delaunay三角網(wǎng)是啥以后,接下來就是用什么算法實現(xiàn)了。實際在各種算法中,都會利用到這個“空圓特性”,比如你求出某個三角形的外接圓,然后看這個外接圓里面有沒有其它的點在里面,如果有,那這個三角形就不是delaunay三角形,因為剛剛說了,德勞內(nèi)三角形的外接圓里面不會有其它的點落在里面。
本文接下來要介紹的算法是插入法(有興趣的也可以自己去試試別的方法,比如分治法)
插入法的大概步驟是:
1、隨機生成一些離散點(當然注意隨機生成的點不能重合、兩點不能坐標都一樣了)
2、將點集的點按照x坐標升序排列
3、計算出一個能包含住點集中所有點的大大的三角形(所有的點都在這個特大三角形內(nèi))
4、將排好序的點逐一插入、并利用“空圓特性”連線
OK,一個個的解決啊,首先是生成一堆離散點:
先說最簡單的,在一個矩形范圍內(nèi)生成一堆點。那假設(shè)你用math.random的話,隨機產(chǎn)生的數(shù)就可能會一樣,如果直接隨機,那么產(chǎn)生了這些點以后就要去除重復(fù)、把坐標相同重復(fù)多余的點去掉。有沒有什么辦法可以讓產(chǎn)生點集的時候不產(chǎn)生重復(fù)的點呢,想想啊,不重復(fù)意味著坐標不同,就是說x坐標或者y坐標但凡有一個不一樣就可以了,所以只要在隨機的時候控制比如x的坐標,讓x坐標不一樣即可,比如圖1.02

圖1.02,假設(shè)矩形的左上角坐標是x0,y0、矩形寬w高h,現(xiàn)在想產(chǎn)生n個點。建立一個表coor用來裝隨機點。因為要設(shè)定每個點的x不同,所以讓每一次循環(huán)時x的大小都絕對大于上一次。一共要產(chǎn)生n個點,所以把矩形的橫寬w分為n份(n個區(qū)間),每一份寬就是w/n,所以每一次循環(huán)時x增加最大不過w/n即可。那么這里當然得用到math.random了,math.random一般生成整數(shù)很友好,但是現(xiàn)在不是要生成整數(shù)的時候啊,畢竟w/n多半會是小數(shù),所以此時用math.random()來產(chǎn)生小數(shù),math.random在不填任何參數(shù)時,會產(chǎn)生0到1的隨機小數(shù)(不含端點、不會隨機出0或1)
那么區(qū)間長w/n,w/n*math.random()就是x在這個區(qū)間范圍內(nèi)隨機了。好,x坐標保證每次循環(huán)結(jié)果不一樣以后,y現(xiàn)在就可以隨便了,y直接在y0到y(tǒng)0+h隨機無所謂
然后,還可以順帶計算一個特大三角形(超級三角形),因為反正都要算超級三角形嘛。剛剛提到,超級三角形只要能讓所有點都在該三角形內(nèi)部即可,所以怎么樣的三角形“都可以”,只要夠大就可以了,那還不容易嗎?比如圖1.03這一堆點

你可以用如圖1.04這樣的三角形包住它們,也可以用如圖1.05這樣的超級三角形包住他們


總之確保特大三角形包含點集中的所有點即可。那么我這里寫的代碼就是:首先這些點集的范圍是知道的,因為這些點都在我們設(shè)定的矩形框內(nèi),所以超級三角形只要框得住這矩形即可,所以讓超級三角形左下的點x坐標在x0減掉10倍矩形寬w、y坐標在y0+h的基礎(chǔ)上加上10倍的矩形高h即可,然后超級三角形的其它點的坐標同理計算一下就可以了,反正三角形夠大就可以了,你也可以弄比如圖1.06這樣的等邊三角形

圖1.06就是所有點都在一個矩形框內(nèi),根據(jù)這矩形算出一個等邊三角形夠大。
甚至你說你生成的點集所在范圍就不大(反正視頻屏幕就那么大),你直接設(shè)定特大三角形的左下x是-9999999,y是9999999這種只要保證能包含住所有離散點就都可以
然后因為現(xiàn)在產(chǎn)生的隨機點剛好是x升序的,所以就不用排序了(如果要排序的點集,后面會講怎么排序)
那么此時就到了“最后一步”,逐點插入了。
先來具體解釋一下逐點插入是怎么個方法,然后再慢慢地講算法
假設(shè)現(xiàn)在一共有3個離散點如圖1.07,那么連成delaunay三角網(wǎng)就得到了圖1.08對吧


問題就是這是怎么個連法的呢,用什么方法連接這些離散點的呢:
(1).計算出一個能包含所有點的超級三角形ABC(圖1.09)

當然此時ABC中還沒有開始插入點的,只是為了方便看到特大三角形包含住了所有點,所以圖1.09的三角形ABC里才有3個點,實際上還沒有開始插呢
(2).插入第一個點P1(注意之前說了點集中點的順序是需要按x升序的),如圖1.10

(3).檢查P1是否在ABC的外接圓內(nèi),如圖1.11

如果在,剛剛說了,則ABC不是德勞內(nèi)三角形,則不需要三角形ABC,所以要刪除ABC這個三角形,然后重新連線,怎么連,因為你發(fā)現(xiàn)P1在△ABC外接圓內(nèi),所以將P1點和A、B、C連、形成三個三角形,如圖1.12

如圖1.12,因為P1在△ABC外接圓內(nèi)部,所以現(xiàn)在不要△ABC了,而是重新連出了3個△,分別是△AP1B、△P1BC和△AP1C,注意,現(xiàn)在已經(jīng)沒有△ABC了??!△ABC已經(jīng)刪除了
(4).插入第二個點P2,如圖1.13

(5).現(xiàn)在有的三角形是△AP1B、△P1BC和△AP1C,遍歷每個三角形并做其外接圓,然后檢查P2是否在其外接圓內(nèi)部,如圖1.14

這里呢,P2在△AP1B外接圓的右側(cè),那么直接判定△AP1B是德勞內(nèi)三角形,所以我們要把它保存起來,那么當然是存在一個表里,比如表起名叫u6h2吧,而且當然這個表用來儲存我們最后要獲得到的delaunay三角形,所以這個表在你寫代碼的時候肯定要一開始就建立好,你最后函數(shù)返回的就是這個裝有delaunay三角形的表。記得一開始就要建立這個表喲,用來放我們找到的delaunay三角形的!
OK,因為這里說了離散點都是按x升序排好的,所以當遍歷(即插入)下一個點時,若該點在外接圓的右側(cè),則表示以后所有的點都在該外接圓的右側(cè),則保證了Delaunay三角形的空圓特性,所以說,P2在△AP1B外接圓的右側(cè),就直接判定△AP1B是合法的德勞內(nèi)三角形,所以添加進一開始就建好的名叫u6h2的表里面。然后注意,既然已經(jīng)把△AP1B添加進u6h2表里了,那么現(xiàn)在就要刪除掉△AP1B了,就像剛剛刪除掉△ABC一樣,只是現(xiàn)在不需要重新連線了
然后再看,發(fā)現(xiàn)P2在△AP1C的外接圓外邊,此時這種情況就不做任何改變、不刪除、不連線、也不把△AP1C添加進u6h2里。就是說當你發(fā)現(xiàn)這個點它既不在這三角形外接圓內(nèi)也不確定是在外接圓右側(cè)的,那么就判定不做任何改變!然后繼續(xù)檢查下一個三角形△P1BC
現(xiàn)在發(fā)現(xiàn)P2在△P1BC的外接圓內(nèi)部,所以說,△P1BC一定不是德勞內(nèi)三角形,所以刪除△P1BC,并重新連線,就像剛剛一樣,將P2和P1、B、C連出三個三角形,那么現(xiàn)在的三角形就是如圖1.15這樣的了

現(xiàn)在就只有△AP1C、△P1BP2、△BP2C和△P1CP2了
捋一下啊,之前的△AP1B因為是德勞內(nèi)三角形,所以人家已經(jīng)去u6h2了,這里就刪除掉它了,而△AP1C保持不變,然后原本的△P1BC被分成了3個三角形,△P1BC也被刪除了,所以當點P2插入以后就經(jīng)歷了這些變化,三角形就變成只有△AP1C、△P1BP2、△BP2C和△P1CP2這幾個了
(6).插入第三個離散點P3,如圖1.16

(7).當然繼續(xù)遍歷現(xiàn)在所有的三角形,檢查P3是否在其外接圓的內(nèi)部,如圖1.17

P3不在△P1BP2的外接圓內(nèi)部、并且在△P1BP2的外接圓右側(cè),所以△P1BP2是合法的德勞內(nèi)三角形,所以將△P1BP2放進表u6h2里,并刪除掉這個三角形。P3在△BP2C的外接圓外面,但是不在內(nèi)部,所以和剛剛一樣,就不做任何的改變、不做任何改動。繼續(xù)看,P3在△AP1C的外接圓內(nèi)部、同時也在△P1CP2的外接圓內(nèi)部,所以△AP1C和△P1CP2不是合法的德勞內(nèi)三角形,要刪除掉△AP1C和△P1CP2,并重新連線,也就是P3和A、P1、P2、C連成4個三角形
△AP1P3、△AP3C、△P1P2P3、△P2P3C,然后這樣的話,現(xiàn)在剩下的三角形就有: △BP2C、△AP1P3、△AP3C、△P1P2P3和△P2P3C,如圖1.18

(8).因為現(xiàn)在已經(jīng)插入完所有的離散點了,所以在最后就別忘了,把剩下的這幾個三角形放進u6h2這個裝德勞內(nèi)三角形的表里
這樣一來,u6h2里面就有原來的△AP1B、△P1BP2和最后加進去的△BP2C、△AP1P3、△AP3C、△P1P2P3、△P2P3C,這些三角形都是合法的德勞內(nèi)三角形,因為滿足“空圓特性”、因為不滿足的都刪除掉且重新連線了。
那么當然最后要返回的是一開始的離散點連接成的三角網(wǎng),所以,還要再把u6h2這個表里和超級三角形有關(guān)的三角形都刪除掉
現(xiàn)在u6h2里有△AP1B、△P1BP2、△BP2C、△AP1P3、△AP3C、△P1P2P3、△P2P3C,其中△AP1B的兩個頂點和超級三角形的頂點一樣、△P1BP2中的點B是超級三角形ABC的頂點、△BP2C有和超級三角形有關(guān)系的頂點B和C、△AP1P3有和特大三角形有關(guān)的點A、△AP3C有點A和點C、△P2P3C有和特大三角形有關(guān)系的點C,所以△AP1B、△P1BP2、△BP2C、△AP1P3、△AP3C、△P2P3C這些三角形都要從u6h2這個表里刪除掉,于是乎,表u6h2里就剩下△P1P2P3了,這個就是我們要的離散點連接成的德勞內(nèi)三角網(wǎng),也就是圖1.08
以上就是插入法的大概方法
那么知道了怎么用插入法來連出delaunay三角網(wǎng)以后,具體的算法要怎么規(guī)劃呢?
1、一個三角形用什么表示:一個三角形就是3個點,它們的順序怎么換,都能連接成三角形,如ABC、BCA、CAB等等等等都是同一個三角形,所以直接把3個點的坐標裝進一個table,這個table就表示了一個三角形,如{{0,0},{23,66},{-5,2.5871}}
2、如何判斷插入的點是否在某三角形外接圓內(nèi)部或右側(cè)或外部:根據(jù)三角形的3點坐標求得外接圓的中心坐標,以及外接圓的半徑,那么插入的一點P(x,y)就能判斷在不在圓內(nèi)了。假如說點P(x,y)到你求出的外接圓圓心的距離比你外接圓的半徑小,那么當然就說明P在圓內(nèi);假設(shè)點P的橫坐標x比你求出的外接圓圓心的橫坐標大,并且該數(shù)值比外接圓半徑還大的話,那當然說明P在外接圓的右側(cè),很簡單,如圖1.19是個圓

那么一個點如果在這個圓右側(cè),是怎樣的,當然就是圖1.20這樣的了

所以如果點P的橫坐標比外接圓圓心的橫坐標大,且大了半徑r還多,即判斷P在圓右側(cè)。若外接圓圓心橫坐標為cx,而P的橫坐標為x的話,那當cx+r<x的時候,就說明P在右側(cè)
然后當然,如果P到圓心的距離大于半徑了,就說明P在外部,這個就能判斷出P是否在外接圓外部了。所以總共3種需要判斷的位置關(guān)系的方法就有了(①P在外接圓內(nèi)部 ②P在右側(cè) ③P在外接圓外)
3、還需要準備哪些東西?
①需要有臨時存儲三角形的表,就像前文中會有三角形的刪除和重連操作,那這些三角形就需要臨時的表來裝。
②需要有臨時儲存三角形的邊的表,用來連線,因為連線的時候你發(fā)現(xiàn),如果你插入的點它同時在兩個三角形的外接圓里的話,結(jié)果就是重新連成4個三角形(而不是6個),因為如果你插入的點只在一個三角形的外接圓內(nèi)部的話,那直接重新連成3個三角形即可,但是如果插入的點同時在兩個三角形的外接圓內(nèi)的話,直接每個三角形重新連出3個新的就不行、因為那樣會連出總共6個三角形,所以這里需要去掉重復(fù)的,而且是完全去掉,所以只留4個新連出的三角形
那么這樣就不如建立一個臨時的三角形邊的表,用來幫助連線操作。(當然一條邊就是兩個點,所以形式是{{2,9},{-33,11}}這種的,這就表示一條邊,當然現(xiàn)在需要的臨時儲存邊的表要裝很多條邊,所以就是這種一條條邊都裝在那臨時的邊的表里)比如一個△ABC在插入了P以后判斷出要重新連,那么就在邊表里加入AB、BC、AC三條邊,然后插入的點P再和這3條邊連成3個三角形即可。所以這樣,比如點P同時在三角形ABC的外接圓和三角形BCD的外接圓里的話,就先在邊表里裝入AB、BC、AC、BC、BD、DC這6條邊,然后裝好以后,再把重復(fù)的完全去掉,現(xiàn)在重復(fù)的是BC,那么邊表里要完全去掉BC邊,即得到AB、AC、BD、DC這4條邊,這樣再讓這四條邊和P連成4個三角形即可。(注意反復(fù)強調(diào)要完全去除重復(fù),你看現(xiàn)在壓根就沒有BC邊了)
③需要記錄刪除哪些三角形,因為之前說了,有刪除三角形的操作,那么得知道要刪除的三角形是裝臨時三角形的table中的第幾個元素,要知道這個索引, 所以需要一個臨時記錄索引的表
好吧,再帶著剛剛準備的工具,重新看一遍逐點插入法:
假設(shè)現(xiàn)在有4個離散點,如圖1.21

假設(shè)離散點裝在名叫r4m8的表里
(1).建立表u6h2用來存放delaunay三角形,這是最后要返回的表
? ? ? ? ? 建立表v9e5用來存放臨時的三角形,這是中間要操作的,要刪除和重連新三角形的表
現(xiàn)在一開始先在表v9e5里加入超級三角形,如圖1.22

然后
因為是逐點插入,所以for循環(huán)遍歷點集,即for i=1,#r4m8 do
每次循環(huán)時都重新建立一個臨時儲存邊的表,和臨時儲存要刪除的臨時三角形的編號(索引)的表,即local edges={}? ? local idx={}? ? idx就是index的縮寫,表示用來儲存相應(yīng)索引的(即下標)
然后
因為對于每一個插入的點,都需要檢查它和每一個臨時三角形的外接圓的關(guān)系,所以要遍歷臨時三角形的表,一個個判斷其外接圓和插入點的關(guān)系,所以再for i1=1,#v9e5?do
現(xiàn)在判斷臨時三角形中的第一個三角形,△PQR的外接圓是否包含插入的點A,包含,則記錄要刪除的三角形△PQR的索引1、由于要重新連線,所以邊表edges里加入三邊
然后
內(nèi)層for循環(huán)結(jié)束,即for i1=1,#v9e5?do結(jié)束,此時做完了判斷工作和準備工作,如圖1.23

然后
利用收集到的idx,講臨時三角形中需要刪除的三角形刪除掉。因為要將idx里記錄的每一個三角形刪除掉,所以for i2=#idx,1,-1 do,用上remove函數(shù)來刪除,即table.remove(v9e5,idx[i2])
這樣就刪除掉了臨時三角形表v9e5里所有需要刪除的不合法三角形
然后
對邊表edges去除重復(fù)(剛剛講了,要完全去除重復(fù)),去重以后,用此時插入的點和邊表中的邊連成一個個新的三角形并把重新連的三角形添加入臨時三角形表v9e5中,如圖1.24

如圖1.24,邊表和idx表只是每次輔助你連個線、刪除不合法三角形的,現(xiàn)在已經(jīng)把臨時三角形表里原來的△PQR刪除了,并利用edges的邊連接了新的三角形,然后加進了臨時三角形表里了,所以edges和idx在本次循環(huán)(即外層的for i=1,#r4m8 do)里,已經(jīng)出色的完成了任務(wù)
然后
插入第二點,即for i=1,#r4m8 do已經(jīng)進行到i=2了,每次外層循環(huán)重新建立邊表、idx表,如圖1.25

對于插入的第二個點(即外層循環(huán)for i=1,#r4m8 do的i=2時),遍歷臨時三角形表v9e5中的每一個三角形,檢查其外接圓和插入點的關(guān)系,如圖1.26

顯然B在△APQ的外接圓外面,不做任何改變,B在△APR的外接圓外部,不做任何改變,而B在△AQR的外接圓內(nèi)部,所以判定△AQR需要被刪除、記錄下它的下標、放進idx,并在edges里添加相應(yīng)的邊,如圖1.27

然后
此時已經(jīng)遍歷完臨時三角形表v9e5里的所有三角形。所以還是利用獲得的idx來刪除需要刪除的不合法的臨時三角形。然后將edges里的邊完全去重,再利用edges邊表進行連線、連成新的三個三角形,并把新連的三角形放進臨時三角形表中,如圖1.28

然后
插入第三個點,即外層for循環(huán)到第3個點了,重新建立邊表、idx表,如圖1.29

對于插入的點,遍歷臨時三角形表中的每個三角形,檢查其外接圓和插入點的關(guān)系
所以,C在△APQ的外接圓右側(cè),故△APQ是合法的德勞內(nèi)三角形,直接將其添加入德勞內(nèi)三角形的表u6h2里面,即u6h2[#u6h2+1]=v9e5[i1],并在idx里記錄下該三角形△APQ的下標,因為這個三角形已經(jīng)判定是德勞內(nèi)△了,所以等會要從臨時三角形表里刪除
C在△APR的外接圓內(nèi)部,在idx里記錄下不合法三角形的編號(索引),并在邊表edges里添加相應(yīng)的邊,如圖1.30

C在△ABQ的外接圓右側(cè),故△ABQ為德勞內(nèi)三角形,將其直接添加進德勞內(nèi)三角形表u6h2里,并在idx里記錄下該三角形△ABQ的下標,因為這個三角形已經(jīng)判定是德勞內(nèi)△了,所以等會要從臨時三角形表里刪除
C在△ABR的外接圓內(nèi)部,在idx中記錄下這個需要刪除的三角形的下標,在邊表edges里添加進△ABR的三條邊,如圖1.31

C在△BQR的外接圓外部,所以當然不用做任何操作與改變
然后
遍歷完了臨時三角形表的所有三角形,利用得到的idx對臨時三角形表里的三角形進行相應(yīng)刪除,此時刪掉第1、2、3、4個三角形。對邊表edges進行完全去重,如圖1.32

對edges去重以后,再用它和插入點連線?,F(xiàn)在就是C和AP連成△ACP、C和PR連成△CPR、C和AB連成△ABC、C和BR連成△CBR,連好以后,當然還是一樣添加進臨時三角形表里面,如圖1.33

然后
插入第4個點,即外層for循環(huán)到第4個點了,重新建立邊表、idx表,如圖1.34

又來又來,對于插入的點,遍歷臨時三角形表中的每個三角形,檢查其外接圓和插入點的關(guān)系
插入點D在△BQR的外接圓外部,所以不進行任何改動
D在△ACP的外接圓外部,所以同樣不做任何改動
繼續(xù),D在△CPR的外接圓外部,又是不做任何改動
D在△ABC的外接圓內(nèi)部!所以△ABC一定不是德勞內(nèi)△,在idx里記錄下要刪除的該三角形的編號(索引),并在edges里添加相應(yīng)的邊(即AB、BC、AC)
D在△BCR的外接圓內(nèi)部,所以在idx里記錄下要刪除的該三角形的編號(索引),并在edges里添加相應(yīng)的邊(即BC、BR、CR)
如圖1.35

然后利用idx收集到的索引,對臨時三角形表中的元素進行刪除,然后將邊表edges進行完全去重,如圖1.36

然后再用去重以后的邊表edges里的邊來和D連線。D和AB連成△ABD、D和AC連成△ACD、D和BR連成△BCR、D和CR連成△CDR,并將連好的幾個三角形放進臨時三角形表里,如圖1.37

然后
現(xiàn)在已經(jīng)插入完所有的離散點了,也就是外層for循環(huán)都已經(jīng)結(jié)束了(即for i=1,#r4m8 do出色的完成了它的任務(wù)!辛苦了! 啊不對,是我寫這么一大堆廢話才辛苦,啊,這是什么,噢不, 我竟然吐血了)
那么現(xiàn)在就像前文中講過的那樣,在把所有點插入完以后,最后還要將臨時三角形表里剩下的三角形都加入進德勞內(nèi)三角形表里
來看看這兩個表現(xiàn)在本來有哪些三角形,首先臨時三角形表里的三角形就如圖1.37所示,而德勞內(nèi)三角形表u6h2里有兩個三角形,如圖1.38,當然這在前面也說得很清楚了

所以,最后把臨時三角形表中的三角形添加進德勞內(nèi)三角形表中,就得到圖1.39

那么當然,我們需要的是離散點構(gòu)成的德勞內(nèi)三角網(wǎng),所以這個三角網(wǎng)里不能有超級三角形的頂點參與其中,所以還是像剛剛前面說的,將德勞內(nèi)三角形表u6h2中的和特大三角形的頂點有關(guān)系的三角形全部刪除掉,便能得到只用我們的離散點連成的delaunay三角網(wǎng),所以最后德勞內(nèi)三角形表中就只剩下如圖1.40這兩個三角形了,最后返回你得到的這個表u6h2即可

在這么詳細的講了大概的算法以后,簡單地提一下一些沒講的細節(jié):
1、隨機的點集怎么按x坐標升序排序:用table.sort
2、如何將edges里的邊和插入點連成△的:遍歷edges的每條邊,每條邊就是兩個點一起的,比如{{-23,66},{55,7}},那么假設(shè)它要和點{0,0}連成三角形,就連成{{-23,66},{55,7},{0,0}}即可
3、如何將一個三角形拆成3條邊(因為edges的邊都是一個個的三角形的3條邊加入進edges表里的):就比如三角形{{-23,66},{55,7},{0,0}}你只要拆成{{-23,66},{55,7}}、{{55,7},{0,0}}、{{0,0},{-23,66}}即可
4、如何刪除德勞內(nèi)三角形表中和特大三角形有關(guān)的三角形:只需要遍歷德勞內(nèi)三角形表的每個三角形,檢查這三角形的頂點有沒有和特大三角形的頂點一樣的。比如三角形{{-23,66},{55,7},{0,0}},然后假設(shè)超級三角形的頂點是{55,7}那么就檢查三角形{{-23,66},{55,7},{0,0}}里面有沒有{55,7}即可,有的話,就要刪除掉三角形{{-23,66},{55,7},{0,0}}
然后,我寫的具體的代碼就在視頻里介紹了。