2D 碎形/Sierpinski 三角形
March 13, 2022碎形(Fractal)一詞,最早由數學家 Benoit Mandelbrot 在 1975 年提出,是具有自相似性的圖案,也就是一個圖案的部份,仍舊包含了整體的形狀,自然界有許多形狀就有著這種自相似性,Benoit Mandelbrot 發表過的一篇論文《英國的海岸線有多長?統計自相似和分數維度》就討論過海岸地形的自相似性(以及非整數維度)。
乍看碎形圖案似乎很複雜,然而每個圖案中的部份其實重複著相同規則。
從三角形開始
例如,想繪製 Sierpinski 三角形的話,你總得先會繪製簡單的三角形,若利用 dotSCAD 的 t2d
函式,以海龜繪圖繪製的話,可以如下撰寫程式:
use <turtle/t2d.scad>
use <polyline_join.scad>
module triangle(t, leng, width) {
module line(t1, t2, width) {
polyline_join([t2d(t1, "point"), t2d(t2, "point")])
circle(width / 2);
}
commands = [["forward", leng], ["turn", 120]];
t1 = t2d(t, commands);
line(t, t1, width);
t2 = t2d(t1, commands);
line(t1, t2, width);
t3 = t2d(t2, commands);
line(t2, t3, width);
}
leng = 100;
width = 1;
t = t2d();
triangle(t, leng, width);
當然,想繪製三角形有許多方式,使用海龜繪圖之目的,在於它動作有限,這強制你得以簡單的方式來描述規則,例如三角形就是重複三次的前進、轉動罷了:
三個相疊的三角形
這邊使用 footprints2
的原因在於,就繪製碎形而言,自行管理海龜會比較方便,例如,來畫三個相疊的三角形:
use <turtle/t2d.scad>
use <polyline_join.scad>
module triangle(t, leng, width) {
module line(t1, t2, width) {
polyline_join([t2d(t1, "point"), t2d(t2, "point")])
circle(width / 2);
}
commands = [["forward", leng], ["turn", 120]];
t1 = t2d(t, commands);
line(t, t1, width);
t2 = t2d(t1, commands);
line(t1, t2, width);
t3 = t2d(t2, commands);
line(t2, t3, width);
}
leng = 100;
width = 1;
t = t2d();
triangle(t, leng, width);
t2 = t2d(t, "forward", leng = leng);
triangle(t2, leng, width);
t3 = t2d(t, [["turn", 60], ["forward", leng], ["turn", -60]]);
triangle(t3, leng, width);
以第一與第二個三角形為例,第一個三角形的海龜是在原點出發,第二個三角形的海龜是在 x 的 leng
處出發,如果使用 footprints2
的話,t
移動至 x 為 leng
處的足跡會被記錄,就三角形繪製而言這段其實是沒必要記錄,因此才會說直接使用 t2d
會比較方便。
遞迴的三個三角形
如果你想指定 leng
作為大三角形的長度,其中會有三個小三角,可以將上面的程式封裝可以達到目的,leng
的一半就是小三角的邊長:
module three_triangles(t, leng, width) {
half_leng = leng / 2;
triangle(t, half_leng, width);
t2 = t2d(t, "forward", leng = half_leng);
triangle(t2, half_leng, width);
t3 = t2d(t, [["turn", 60], ["forward", half_leng], ["turn", -60]]);
triangle(t3, half_leng, width);
}
現在,如果每個 triangle
當成是一個 three_triangles
來繪製呢?這就構成了遞迴繪圖!不過,由於電腦的物理性限制,遞迴要有終止條件,實際繪製線段時也會有線寬問題,無限繪製小三角本身也沒有意義,既然如此,就以線寬不重疊,能看得出小三角形為遞迴終止條件:
module sierpinski_triangle(t, leng, width) {
if(leng > width * 7) {
half_leng = leng / 2;
sierpinski_triangle(t, half_leng, width);
t2 = t2d(t, "forward", leng = half_leng);
sierpinski_triangle(t2, half_leng, width);
t3 = t2d(t, [["turn", 60], ["forward", half_leng], ["turn", -60]]);
sierpinski_triangle(t3, half_leng, width);
}
else {
// 遞迴終止,繪製三個小三角形
three_triangles(t, leng, width);
}
}
將以上看過的模組整理在一起,可以繪製出什麼呢?
use <turtle/t2d.scad>
use <polyline_join.scad>
leng = 100;
width = 1;
module triangle(t, leng, width) {
module line(t1, t2, width) {
polyline_join([t2d(t1, "point"), t2d(t2, "point")])
circle(width / 2);
}
commands = [["forward", leng], ["turn", 120]];
t1 = t2d(t, commands);
line(t, t1, width);
t2 = t2d(t1, commands);
line(t1, t2, width);
t3 = t2d(t2, commands);
line(t2, t3, width);
}
module three_triangles(t, leng, width) {
half_leng = leng / 2;
triangle(t, half_leng, width);
t2 = t2d(t, "forward", leng = half_leng);
triangle(t2, half_leng, width);
t3 = t2d(t, [["turn", 60], ["forward", half_leng], ["turn", -60]]);
triangle(t3, half_leng, width);
}
module sierpinski_triangle(t, leng, width) {
if(leng > width * 7) {
half_leng = leng / 2;
sierpinski_triangle(t, half_leng, width);
t2 = t2d(t, "forward", leng = half_leng);
sierpinski_triangle(t2, half_leng, width);
t3 = t2d(t, [["turn", 60], ["forward", half_leng], ["turn", -60]]);
sierpinski_triangle(t3, half_leng, width);
}
else {
three_triangles(t, leng, width);
}
}
sierpinski_triangle(t2d(), leng, width);
這就是方才談到的Sierpinski 三角形:
這只是碎形的其中一個例子,許多碎形圖案的構造,就是試圖找出、觀察複雜圖案中的簡單規律,實際上,這類規律還可以用更單純的語言來表示,而不是透過程式語言,這會在後續文件再來介紹。