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);

當然,想繪製三角形有許多方式,使用海龜繪圖之目的,在於它動作有限,這強制你得以簡單的方式來描述規則,例如三角形就是重複三次的前進、轉動罷了:

2D 碎形/Sierpinski 三角形

三個相疊的三角形

這邊使用 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 會比較方便。

2D 碎形/Sierpinski 三角形

遞迴的三個三角形

如果你想指定 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 三角形

2D 碎形/Sierpinski 三角形

這只是碎形的其中一個例子,許多碎形圖案的構造,就是試圖找出、觀察複雜圖案中的簡單規律,實際上,這類規律還可以用更單純的語言來表示,而不是透過程式語言,這會在後續文件再來介紹。

分享到 LinkedIn 分享到 Facebook 分享到 Twitter