碎形之二(3D 樹木曲線)


在〈實作 3D 海龜繪圖〉最後談到了,你有辦法畫個 3D 版本的〈樹 木曲線〉嗎?我實作了一個版本 Customizable tree curve

Customizable tree curve

2D 版樹木曲線

想要能實作 3D 版本的樹木曲線,基本上得先實作出 2D 版本的遞迴樹木曲線吧!想要畫出 2D 版本的遞迴樹木曲線,基本上也得先畫出…嗯…一個 Y 吧!…XD

// 必須用到之前寫的 3D 海龜繪圖程式碼

leng = 50;
leng_scale = 0.7;
angle = 20;
width = 2;
fn = 4;

t = turtle3D( 
    pt3D(0, 0, 0), 
    [pt3D(1, 0, 0), pt3D(0, 1, 0), pt3D(0, 0, 1)] // 海龜座標向量
);

module tree(t, leng, leng_scale, angle, width, fn) {
    t2 = moveX(t, leng);
    polyline3D([getPt(t), getPt(t2)], width, fn);

    t3 = moveX(turnZ(t2, angle), leng * leng_scale);
    polyline3D([getPt(t2), getPt(t3)], width, fn);

    t4 = moveX(turnZ(t2, -angle), leng * leng_scale);
    polyline3D([getPt(t2), getPt(t4)], width, fn);
}

tree(t, leng, leng_scale, angle, width, fn);

碎形之二

如果想從 Y 的支幹也長出 Y,基本上,可以重複呼叫 tree 模組,因為遞迴要有終止條件,因此加上個最短長度限制,結果會是:

// 必須用到之前寫的 3D 海龜繪圖程式碼

leng = 50;
leng_limit = 5;
leng_scale = 0.7;
angle = 20;
width = 2;
fn = 4;

t = turtle3D( 
    pt3D(0, 0, 0), 
    [pt3D(1, 0, 0), pt3D(0, 1, 0), pt3D(0, 0, 1)] // 海龜座標向量
);

module tree(t, leng, leng_scale, leng_limit, angle, width, fn) {
    if(leng > leng_limit) {
        t2 = moveX(t, leng);
        polyline3D([getPt(t), getPt(t2)], width, fn);

        t3 = moveX(turnZ(t2, angle), leng * leng_scale);
        polyline3D([getPt(t2), getPt(t3)], width, fn);

        t4 = moveX(turnZ(t2, -angle), leng * leng_scale);
        polyline3D([getPt(t2), getPt(t4)], width, fn);

        tree(
            turnZ(t2, angle), 
            leng * leng_scale, 
            leng_scale, 
            leng_limit, 
            angle, width, fn);

        tree(
            turnZ(t2, -angle), 
            leng * leng_scale, 
            leng_scale, 
            leng_limit, 
            angle, width, fn);

    }    
}

tree(t, leng, leng_scale, leng_limit, angle, width, fn);

2D 版本的遞迴樹就出現了:

碎形之二

不過,實際上,除了最末端的枝幹之外,其他枝幹都重複繪製了,也就是這段重複作了繪製的動作:

t3 = moveX(turnZ(t2, angle), leng * leng_scale);
polyline3D([getPt(t2), getPt(t3)], width, fn);

t4 = moveX(turnZ(t2, -angle), leng * leng_scale);
polyline3D([getPt(t2), getPt(t4)], width, fn);

刪掉這兩段,會少一次末端枝幹,不過,程式碼可以精簡一些:

// 必須用到之前寫的 3D 海龜繪圖程式碼

leng = 50;
leng_limit = 5;
leng_scale = 0.7;
angle = 20;
width = 2;
fn = 4;

t = turtle3D( 
    pt3D(0, 0, 0), 
    [pt3D(1, 0, 0), pt3D(0, 1, 0), pt3D(0, 0, 1)] // 海龜座標向量
);

module tree(t, leng, leng_scale, leng_limit, angle, width, fn) {
    if(leng > leng_limit) {
        t2 = moveX(t, leng);
        polyline3D([getPt(t), getPt(t2)], width, fn);

        tree(
            turnZ(t2, angle), 
            leng * leng_scale, 
            leng_scale, 
            leng_limit, 
            angle, width, fn);

        tree(
            turnZ(t2, -angle), 
            leng * leng_scale, 
            leng_scale, 
            leng_limit, 
            angle, width, fn);

    }    
}

tree(t, leng, leng_scale, leng_limit, angle, width, fn);

3D 版樹木曲線

要將上面的 2D 版本調整成 3D 版本非常簡單,如果 tree 可以指定兩個繞 z 軸轉動角度的話,並且將其中一個角設成 0 度會如何呢?

// 必須用到之前寫的 3D 海龜繪圖程式碼

leng = 50;
leng_limit = 5;
leng_scale = 0.7;
angle1 = 20;
angle2 = 0;
width = 2;
fn = 4;

t = turtle3D( 
    pt3D(0, 0, 0), 
    [pt3D(1, 0, 0), pt3D(0, 1, 0), pt3D(0, 0, 1)] // 海龜座標向量
);

module tree(t, leng, leng_scale, leng_limit, angle1, angle2, width, fn) {
    if(leng > leng_limit) {
        t2 = moveX(t, leng);
        polyline3D([getPt(t), getPt(t2)], width, fn);

        tree(
            turnZ(t2, angle1), 
            leng * leng_scale, leng_scale, leng_limit, 
            angle1, angle2, 
            width, fn);

        tree(
            turnZ(t2, angle2), 
            leng * leng_scale, leng_scale, leng_limit, 
            angle1, angle2, 
            width, fn);
    }    
}

tree(t, leng, leng_scale, leng_limit, angle1, angle2, width, fn);

長得有點奇怪,一邊往上長,又一邊往左長:

碎形之二

如果第二次遞迴呼叫 tree 之前,turnZ 改成 turnX 呢?樹的外型不會變,因為 turnX 也只是轉 0 度,如果 angle2 不是轉 0 度而是轉 127 度呢?

// 必須用到之前寫的 3D 海龜繪圖程式碼

leng = 50;
leng_limit = 5;
leng_scale = 0.7;
angle1 = 20;
angle2 = 127;
width = 2;
fn = 4;

t = turtle3D( 
    pt3D(0, 0, 0), 
    [pt3D(1, 0, 0), pt3D(0, 1, 0), pt3D(0, 0, 1)] // 海龜座標向量
);

module tree(t, leng, leng_scale, leng_limit, angle1, angle2, width, fn) {
    if(leng > leng_limit) {
        t2 = moveX(t, leng);
        polyline3D([getPt(t), getPt(t2)], width, fn);

        tree(
            turnZ(t2, angle1), 
            leng * leng_scale, leng_scale, leng_limit, 
            angle1, angle2, 
            width, fn);

        tree(
            turnX(t2, angle2), 
            leng * leng_scale, leng_scale, leng_limit, 
            angle1, angle2, 
            width, fn);
    }    
}

tree(t, leng, leng_scale, leng_limit, angle1, angle2, width, fn); 

形成了一顆 3D 立體小樹了:

碎形之二

我選擇 127 這個角度的原因,是因為它是個質數,基本上只要能不整除 360 的數都可以,這麼一來繞 X 軸時,就不會整除 360 度,樹枝在盤旋上昇時就不會正好都位於同一個角度上,實際上,如果讓 leng_scale 也有 leng_scale1leng_scale2,並各選擇一個適當值,而 angle1angle2 也選個適當角度,也就會有顆漂亮的樹了,為了符合 angle1angle2 實際上就是繞 z 軸與 x 軸,就改名為 angleZangleX 吧!

// 必須用到之前寫的 3D 海龜繪圖程式碼

leng = 100;
leng_limit = 1;
leng_scale1 = 0.4;
leng_scale2 = 0.9;
angleZ = 60;
angleX = 135;
width = 2;
fn = 4;

t = turtle3D( 
    pt3D(0, 0, 0), 
    [pt3D(1, 0, 0), pt3D(0, 1, 0), pt3D(0, 0, 1)] // 海龜座標向量
);

module tree(t, leng, leng_scale1, leng_scale2, leng_limit, 
            angleZ, angleX, width, fn) {
    if(leng > leng_limit) {
        t2 = moveX(t, leng);
        polyline3D([getPt(t), getPt(t2)], width, fn);

        tree(
            turnZ(t2, angleZ), 
            leng * leng_scale1, leng_scale1, leng_scale2, leng_limit, 
            angleZ, angleX, 
            width, fn);

        tree(
            turnX(t2, angleX), 
            leng * leng_scale2, leng_scale1, leng_scale2, leng_limit, 
            angleZ, angleX, 
            width, fn);
    }    
}

tree(t, leng, leng_scale1, leng_scale2, leng_limit, 
     angleZ, angleX, width, fn);

為了讓樹看起來比較濃密一些,我也調整了 lengleng_limit,完 成的效果如下:

碎形之二

當然,你還可以繼續在樹幹寬度等上頭下功夫,讓一開始的主幹夠粗,而每一次的支幹比主幹細一些,或者是我寫的 Customizable tree curve,增加更多方向的遞迴(當然,電腦就更辛苦一些了),我還在上頭加了點亂數,讓枝幹略呈不規則狀,讓遞迴結果看 起來更像是樹,這部份就留給你自己來嘗試了!