分形實例-從分形山脈->分形地形山->分形立體山
2 人贊了文章
之前看過一些關於分形的資料,有一些感觸,它其實是數學裡面的分支,他有時候又被稱為碎形,當然名字不重要,該怎麼做出一種分形才重要,分形界幾種常見的分形Mandelbrot集合、Julia集合、Burning Ship,數學專業的同學大佬們可以去研究研究,我對這種東西是比較反感的
像這種分形,我看見它覺得好噁心啊,就像無數個蟲子重疊在一起一樣,但是不得不承認他們真的很奇妙,把他們無限放大,可以得到不同的分型集。
今天呢,我就不再說這種分形了,因為我喜歡玩一玩吃雞,我看到吃雞裡面的沙漠地圖和一篇博客裡面電腦模擬出來的分形山很像,所以自己興趣也來了,也打算做一個。
分形山脈
首先呢,我是從最基礎的山脈做的,我們可以想像一個簡單直線,取其中點進行分形。
取兩個點的中點,可以認為他的x值已經確定,但是y值可以適當的提升或者降低,而這個值是某個範圍內的隨機值,以便體現山脈的不規則性。
把兩個點不斷地取中點,不斷地分配y值,這個過程實際就是一個遞歸的過程了。
代碼如上,這是對最初的兩個點進行分形遞歸的過程,xishu是y值改變的幅度係數,yyi是y值改變的幅度大小,這個幅度可以根據自己認為的情況隨意設置,並不一定要按我的來模擬。
這是分形後的樣子,當然分形次數越多,就越明顯啦。簡單的山脈就這樣完成了。
分形地形山
分型地形山,這個就比較複雜了。之前的分形山脈是一條直線,這次我們一次性分形三條直線,也就是說一次分形一個三角形,
就像這樣,然後把三個中點連起來,原來的邊去掉,就變成了四個三角形
和直線分形一樣,也是一個遞歸的過程,只不過是對三角形分形了,而且你三條邊的幅度和之前一樣可以隨便分配,然後對四個三角形分形,這時候就會面臨一個問題
如果分別對AED,BEF,CDF分形可以知道,DE,DF,EF可以分別產生一個新的中點,
小問題:
這樣如果再對EDF進行分形,就會造成矛盾,當你進行多次分型後,就會產生大量的空白,我就曾經遇到過這樣的問題,分形一兩次沒多大感覺,三次也還過得去,當到了六七次,就是大量的空白了,一點也看不出來是地形山。這時候我們需要創建一個標誌,即表示兩個點之間是否已經取過了中點,對於取過的中點,沒必要重複取了,以免造成沒必要的麻煩。這個標誌是兩個狀態了,0或者1,或者其他標誌隨便設。我把每個帶分形的點稱作父母,分型得到的點成為兒子,我是針對每個父母點都創建了一個兒子集,在不斷分形的過程中一個點會不斷地遇到其他點,生出一個兒子,當然這只是一個比喻,現實生活中不存在的,每當對兩個點進行分形時,查找這兩個點的兒子集,看有沒有交配結果,如果有過了,就像計劃生育一樣,不準這兩個點之間產子了。這樣就避免了兩點之間重複取中點的過程。
public void mountains(Point A,Point B,Point C,int count,int range,int t){ if (count<=6){ //找出ABC的兒子是否相同 int mab=0; int nab=0; int kab=0; while (A.son[mab].x!=0&&kab!=1){ nab=0; while(B.son[nab].x!=0){ if ((A.son[mab].x==B.son[nab].x)&&(A.son[mab].y==B.son[nab].y)){ kab=1; break;} nab++; } if(kab==1){break;} mab++; } int mac=0; int nac=0; int kac=0; while (A.son[mac].x!=0&&kac!=1){ nac=0; while(C.son[nac].x!=0){ if ((A.son[mac].x==C.son[nac].x)&&(A.son[mac].y==C.son[nac].y)){ kac=1; break;} nac++; } if (kac==1){break;} mac++; } int mbc=0; int nbc=0; int kbc=0; while (B.son[mbc].x!=0&&kbc!=1){ nbc=0; while(C.son[nbc].x!=0){ if ((B.son[mbc].x==C.son[nbc].x)&&(B.son[mbc].y==C.son[nbc].y)){ kbc=1; break;} nbc++; } if (kbc==1){break;} mbc++; } Random nns=new Random(); //A,B中點 if(kab==0){ no.c[count]++; no.node[count][no.c[count]].x=(A.x+B.x)/2; no.node[count][no.c[count]].y=(A.y+B.y)/2-range/t+nns.nextInt(range); //記錄兒子的坐標 int sa=0; int sb=0; while (A.son[sa].x!=0) {sa++;} while (B.son[sb].x!=0) {sb++;} A.son[sa].x=no.node[count][no.c[count]].x; A.son[sa].y=no.node[count][no.c[count]].y; B.son[sb].x=no.node[count][no.c[count]].x; B.son[sb].y=no.node[count][no.c[count]].y; }else{ //兩點之間已經有兒子。不用再記錄兒子 no.c[count]++; no.node[count][no.c[count]].x=A.son[mab].x; no.node[count][no.c[count]].y=A.son[mab].y; } if(kbc==0){ no.c[count]++; no.node[count][no.c[count]].x=(B.x+C.x)/2; no.node[count][no.c[count]].y=(B.y+C.y)/2-range/t+nns.nextInt(range); //記錄兒子 int sb=0; int sc=0; while (B.son[sb].x!=0) {sb++;} while (C.son[sc].x!=0) {sc++;} B.son[sb].x=no.node[count][no.c[count]].x; B.son[sb].y=no.node[count][no.c[count]].y; C.son[sc].x=no.node[count][no.c[count]].x; C.son[sc].y=no.node[count][no.c[count]].y; }else{ //兩點之間已經有兒子。不用再記錄兒子 no.c[count]++; no.node[count][no.c[count]].x=B.son[mbc].x; no.node[count][no.c[count]].y=B.son[mbc].y; } if(kac==0){ no.c[count]++; no.node[count][no.c[count]].x=(A.x+C.x)/2; no.node[count][no.c[count]].y=(A.y+C.y)/2-range/t+nns.nextInt(range); //記錄兒子 int sa=0; int sc=0; while (A.son[sa].x!=0) {sa++;} while (C.son[sc].x!=0) {sc++;} A.son[sa].x=no.node[count][no.c[count]].x; A.son[sa].y=no.node[count][no.c[count]].y; C.son[sc].x=no.node[count][no.c[count]].x; C.son[sc].y=no.node[count][no.c[count]].y; }else{ //兩點之間已經有兒子。不用再記錄兒子 no.c[count]++; no.node[count][no.c[count]].x=A.son[mac].x; no.node[count][no.c[count]].y=A.son[mac].y; } System.out.println("("+count+","+(no.c[count]-2)+")="+no.node[count][no.c[count]-2].x+" "+no.node[count][no.c[count]-2].y); System.out.println("("+count+","+(no.c[count]-1)+")="+no.node[count][no.c[count]-1].x+" "+no.node[count][no.c[count]-1].y); System.out.println("("+count+","+no.c[count]+")="+no.node[count][no.c[count]].x+" "+no.node[count][no.c[count]].y); mountains(A,no.node[count][no.c[count]-2],no.node[count][no.c[count]],count+1,range/t,t); mountains(B,no.node[count][no.c[count]-2],no.node[count][no.c[count]-1],count+1,range/t,t); mountains(C,no.node[count][no.c[count]-1],no.node[count][no.c[count]],count+1,range/t,t); mountains(no.node[count][no.c[count]-2],no.node[count][no.c[count]-1],no.node[count][no.c[count]],count+1,range/t,t); }else { g.drawLine(A.x,A.y,B.x,B.y); g.drawLine(A.x,A.y,C.x,C.y); g.drawLine(B.x,B.y,C.x,C.y); } }
這是我寫的主要代碼,但是針對重複取中點的問題,我認為還有其他的方法,大佬們可以想一想其他的方法,或看一看博客。這裡推薦兩篇博客:
分形的基本原理_獨孤劍_新浪博客分形應用:造山 - 莫小江 - 博客園這兩篇是我獲得啟發,大佬們可以自己去看一看。
對了還有分形地形山的效果圖:
分形立體山
分形立體山我是看過莫小江學姐後的博客收到的啟發,當然,我和她的思路不一樣,所以只能從我的基礎上往他的方法靠。之前我已經基本把分形地形山給畫出來了,由於我電腦的原因,我的分形地形山,只能分形幾次,效果不是很明顯。接下來,就是把地形山抬高了,形成立體山。這個我做了很多次研究,也嘗試了好幾次不同的代碼,但是效果都不明顯。看過幾篇博客後,我的想法變成了兩種,一種是之前分形的分形地形山,從視覺上看出是一種山的曲面,也就是說是山的一面。第二種是完全建立一種三維的立體坐標系,之前的x,y值完全是在一個平面上,採用斜二測畫法可以將我們所想要的立體山就可以畫出來。但是分形次數要足夠多,效果才明顯。
實際上在做地形分形時,每個點都對應著三維坐標上的一點,即我們根據視覺上的看到的,是一個三維不規則的分形曲面,它是立體山的分形一面,當然如果想做的更真實,可以用Unity3D去做,那個比較專業化吧,更容易操作。
我根據自己的想法,設了一個最大陡峭值range,和一個陡峭係數t,沒分形一次,陡峭係數就減到range/t,當然不應定是這個數值,可以增加到多少也可以減少到多少,說白了,你要把它看成立體的,無非就是y值得上下抖動,你可以越往中間越高,體現出山峰的感覺,在坐標繫上就是y值越低。
大家也可以參考其他博客的方法進行試驗,總之分形次數越多效果越明顯,時間也花費的越長,我的電腦只能分形7次,系統就滿足不了我要存儲的數據了,主要是分形時兒子集那個東西沒有管理好,造成了資源的大量使用,以致棧空間滿了,所以大家可以嘗試其他的方法來進行試驗。
這是一張經典的圖片,一直到最後一張,效果是非常的逼真了。
還有一個問題就是顏色填充的問題,我個人覺得如果分形次數足夠多了就沒必要加顏色,因為到了後面就有很多的線條重複在一起,顯得已經填充過一樣了。不過,對於初級的分形,還是可以可以填充一下的。
這是填充後的一張簡圖,我個人覺得效果不是很好。說到這裡,有的人會問API裡面有顏色填充的可以填矩形,圓之類的,對於三角形,似乎沒有這樣的方法,在這裡,我就簡單介紹一下那個填充多邊形的方法fillPolygon,這個方法是將多邊形的坐標集合寫出來以及有幾個這樣的坐標點個數。
int px[]={A.x,B.x,C.x,A.x}; int py[]={A.y,B.y,C.y,A.y}; int rgb=(A.y+B.y+C.y)/9; color=new Color(80,80,80); g.setColor(color); g.fillPolygon(px,py, 4); g.drawLine(A.x,A.y,B.x,B.y); g.drawLine(A.x,A.y,C.x,C.y); g.drawLine(B.x,B.y,C.x,C.y);
這是畫一個三角形並填充,px和py集合要重複是因為你要首尾相結合,才能閉合起來。
這就是我簡單的從分形山脈到分形地形山到立體山,往後一段時間我會用其他的方法畫出更好的立體山,到時候與大家分享一下,如果我有什麼不足謝謝大家指點,大家有什麼觀點也可以和我分享分享。
推薦閱讀:
※【青藤數學】動點綜合題2(初二)
※有趣的數字與數學
※【GTM001】GTM on going
※中國奧數精英兵敗里約,什麼打敗了我們?