什么是分形藝術:
在說明什么是分形藝術前,我們先按照下面的方法構造一個圖形。看下圖,首先畫一個線段,然后把它平分成三段,去掉中間那一段並用兩條等長的線段代替。這樣,原來的一條線段就變成了四條小的線段。用相同的方法把每一條小的線段的中間三分之一替換為等邊三角形的兩邊,得到了16條更小的線段。然后繼續對16條線段進行相同的操作,並無限地迭代下去。下圖是這個圖形前五次迭代的過程,可以看到這樣的分辨率下已經不能顯示出第五次迭代后圖形的所有細節了。這樣的圖形可以用Logo語言很輕松地畫出來。
在線運行地址:在線運行
源碼下載地址:源碼下載
你可能注意到一個有趣的事實:整個線條的長度每一次都變成了原來的4/3。如果最初的線段長為一個單位,那么第一次操作后總長度變成了4/3,第二次操作后總長增加到16/9,第n次操作后長度為(4/3)^n。毫無疑問,操作無限進行下去,這條曲線將達到無限長。難以置信的是這條無限長的曲線卻“始終只有那么大”。
當把三條這樣的曲線頭尾相接組成一個封閉圖形時,有趣的事情發生了。這個雪花一樣的圖形有着無限長的邊界,但是它的總面積卻是有限的。換句話說,無限長的曲線圍住了一塊有限的面積。有人可能會問為什么面積是有限的。雖然從上面的圖上看結論很顯然,但這里我們還是要給出一個簡單的證明。三條曲線中每一條的第n次迭代前有4^(n-1)個長為(1/3)^(n-1)的線段,迭代后多出的面積為4^(n-1)個邊長為(1/3)^n的等邊三角形。把4^(n-1)擴大到4^n,再把所有邊長為(1/3)^n的等邊三角形擴大為同樣邊長的正方形,總面積仍是有限的,因為無窮級數Σ4^n/9^n顯然收斂。這個神奇的雪花圖形叫做Koch雪花,其中那條無限長的曲線就叫做Koch曲線。他是由瑞典數學家Helge von Koch最先提出來的。
分形這一課題提出的時間比較晚。Koch曲線於1904年提出,是最早提出的分形圖形之一。我們仔細觀察一下這條特別的曲線。它有一個很強的特點:你可以把它分成若干部分,每一個部分都和原來一樣(只是大小不同)。這樣的圖形叫做“自相似”圖形(self-similar),它是分形圖形(fractal)最主要的特征。自相似往往都和遞歸、無窮之類的東西聯系在一起。比如,自相似圖形往往是用遞歸法構造出來的,可以無限地分解下去。一條Koch曲線中包含有無數大小不同的Koch曲線。你可以對這條曲線的尖端部分不斷放大,但你所看到的始終和最開始一樣。它的復雜性不隨尺度減小而消失。另外值得一提的是,這條曲線是一條連續的,但處處不光滑(不可微)的曲線。曲線上的任何一個點都是尖點。
分形圖形有一種特殊的計算維度的方法。我們可以看到,在有限空間內就可以達到無限長的分形曲線似乎已經超越了一維的境界,但說它是二維圖形又還不夠。Hausdorff維度就是專門用來對付這種分形圖形的。簡單地說,Hausdorff維度描述分形圖形中整個圖形的大小與一維大小的關系。比如,正方形是一個分形圖形,因為它可以分成四個一模一樣的小正方形,每一個小正方形的邊長都是原來的1/2。當然,你也可以把正方形分成9個邊長為1/3的小正方形。事實上,一個正方形可以分割為a^2個邊長為1/a的小正方形。那個指數2就是正方形的維度。矩形、三角形都是一樣,給你a^2個同樣的形狀才能拼成一個邊長為a倍的相似形,因此它們都是二維的。我們把這里的“邊長”理解為一維上的長度,那個1/a則是兩個相似形的相似比。如果一個自相似形包含自身N份,每一份的一維大小都是原來的1/s,則這個相似形的Hausdorff維度為log(N)/log(s)。一個立方體可以分成8份,每一份的一維長度都是原來的一半,因此立方體的維度為log(8)/log(2)=3。同樣地,一個Koch曲線包含四個小Koch曲線,大小兩個Koch曲線的相似比為1/3,因此Koch曲線的Hausdorff維度為log(4)/log(3)。它約等於1.26,是一個介於1和2之間的實數。
編程實現:
首先,繪制構造Koch曲線的初始圖形,也就是一條直線:
注意:直角坐標系中y軸和fx中相反
private void kochCurve(double x0, double y0, double angle, double length) { double x1 = x0 + length * Math.cos(angle); double y1 = y0 - length * Math.sin(angle); Line line = new Line(x0, y0, x1, y1); line.setStroke(Color.rgb(255, 255, 255, alpha)); root.getChildren().add(line); }
這里用線段的起點(x0, y0),方向(和正向x軸之間的角度)及長度來描述一條線段。
繪制一條從(-1.0, 0.0)到(1.0, 0.0)的Koch曲線用下面的方法:
double length = 2.0; double angle = 0.0; double x0 = -1.0; double y0 = 0.0; kochCurve(x0, y0, angle, length);
一條Koch曲線是由4條比例縮寫為整體1/3的Koch曲線組成。
注意:直角坐標系中y軸和fx中相反
length /= 3; n--; kochCurve(x0, y0, angle, length, n, alpha); x0 += length * Math.cos(angle); y0 -= length * Math.sin(angle);//直角坐標系中y軸和fx中相反 angle += Math.PI / 3; kochCurve(x0, y0, angle, length, n, alpha); x0 += length * Math.cos(angle); y0 -= length * Math.sin(angle); angle -= Math.PI * 2 / 3; kochCurve(x0, y0, angle, length, n, alpha); x0 += length * Math.cos(angle); y0 -= length * Math.sin(angle); angle += Math.PI / 3; kochCurve(x0, y0, angle, length, n, alpha);
這樣就畫出了第一次迭代的結果。
為了控制遞歸的深度,我們需要給kochCurve添加一個參數n。
private void kochCurve(double x0, double y0, double angle, double length, int n, double alpha) { if (n == 0) { double x1 = x0 + length * Math.cos(angle); double y1 = y0 - length * Math.sin(angle); Line line = new Line(x0, y0, x1, y1); line.setStroke(Color.WHITE); root.getChildren().add(line); } else { length /= 3; n--; kochCurve(x0, y0, angle, length, n, alpha); x0 += length * Math.cos(angle); y0 -= length * Math.sin(angle); angle += Math.PI / 3; kochCurve(x0, y0, angle, length, n, alpha); x0 += length * Math.cos(angle); y0 -= length * Math.sin(angle); angle -= Math.PI * 2 / 3; kochCurve(x0, y0, angle, length, n, alpha); x0 += length * Math.cos(angle); y0 -= length * Math.sin(angle); angle += Math.PI / 3; kochCurve(x0, y0, angle, length, n, alpha); } }
n是函數遞歸的層數,也是Koch曲線迭代的次數。
kochCurve(x0, y0, angle, length, 0)畫出的是初始的圖形
kochCurve(x0, y0, angle, length, 1)畫出第一次迭代
下面是n = 2, 3, 4, 5的結果。
現在繪制3條Koch曲線就構成了Koch雪花。
kochCurve(x0, y0, angle, length, n, 1); x0 += length * Math.cos(angle); y0 -= length * Math.sin(angle); angle -= Math.PI * 2 / 3; kochCurve(x0, y0, angle, length, n, 1); x0 += length * Math.cos(angle); y0 -= length * Math.sin(angle); angle -= Math.PI * 2 / 3; kochCurve(x0, y0, angle, length, n, 1);
這樣的Koch雪花看起來有點單調,於是我在每條曲線中增加了兩條線段。並增加了一點透明度的變化。
在線運行地址:在線運行
源碼下載地址:源碼下載