3D 碎形/立體樹

March 14, 2022

相對於 2D 碎形的構造,3D 的碎形增加了一點挑戰性,可以試著先構造 2D 碎形,然後想想看,現在多了一個維度可以運動,該怎麼善加利用!

2D 碎形樹

先來畫個 2D 的碎形樹好了,一開始不需要多,就只是讓種子發芽吧!

use <polyline_join.scad>
use <turtle/t3d.scad>

trunk_leng = 50;
branch_scale = 0.7;
branch_angle = 20;
width = 2;
fn = 4;

module line(t1, t2, width) {
    polyline_join([t3d(t1, "point"), t3d(t2, "point")])
        sphere(width / 2);
}

module shoot(t, trunk_leng, branch_scale, branch_angle, width) {
    t2 = t3d(t, "forward", leng = trunk_leng);
    line(t, t2, width); // 主幹

    branch_leng = trunk_leng * branch_scale;
    
    // 左分支
    t3 = t3d(t2, "turn", angle = branch_angle);
    line(
        t2, 
        t3d(t3, "forward", leng = branch_leng), 
        width
    );

    // 右分支
    t4 = t3d(t2, "turn", angle = -branch_angle);  
    line(
        t2, 
        t3d(t4, "forward", leng = branch_leng), 
        width
    );
    
}

shoot(t3d(), trunk_leng, branch_scale, branch_angle, width);

這會畫出個 Y 字形,代表發芽時的主幹與分支,因為打算發展為 3D 版本,這邊畫線時,是將球以凸包連結,分支的左、右是從海龜觀點來看:

3D 碎形/立體樹

類似地,如果每個分支都視為另一個主幹的開始會如何呢?支幹長度小於線的粗細時,畫出來也沒意義,因此可做為遞迴終止條件:

use <polyline_join.scad>
use <turtle/t3d.scad>

trunk_leng = 50;
branch_scale = 0.7;
branch_angle = 20;
width = 2;
$fn = 4;

module line(t1, t2, width) {
    polyline_join([t3d(t1, "point"), t3d(t2, "point")])
        sphere(width / 2);
}

module shoot(t, trunk_leng, branch_scale, branch_angle, width) {
    t2 = t3d(t, "forward", leng = trunk_leng);
    line(t, t2, width);

    branch_leng = trunk_leng * branch_scale;
    
    t3 = t3d(t2, "turn", angle = branch_angle);
    line(
        t2, 
        t3d(t3, "forward", leng = branch_leng), 
        width
    );

    t4 = t3d(t2, "turn", angle = -branch_angle);  
    line(
        t2, 
        t3d(t4, "forward", leng = branch_leng), 
        width
    );
}

module tree(t, trunk_leng, branch_scale, branch_angle, width) {
    if(trunk_leng > width * 2) { 
        t2 = t3d(t, "forward", leng = trunk_leng);
        line(t, t2, width);

        branch_leng = trunk_leng * branch_scale;
        
        t3 = t3d(t2, "turn", angle = branch_angle);
        tree(t3, branch_leng, branch_scale, branch_angle, width);

        t4 = t3d(t2, "turn", angle = -branch_angle);        
        tree(t4, branch_leng, branch_scale, branch_angle, width);
    }
    else {
        // 遞迴終止,發個芽
        shoot(t, trunk_leng, branch_scale, branch_angle, width);
    }
}

tree(t3d(), trunk_leng, branch_scale, branch_angle, width);

會得到一棵像是花椰菜的 2D 樹狀圖案:

3D 碎形/立體樹

3D 碎形樹

在建立了 2D 碎形樹後,現在來擴展為 3D,因為 3D 海龜不再只能轉彎,還可以翻身與抬頭,如果在構造分支前,海龜先翻個身呢?

module tree(t, trunk_leng, branch_scale, branch_angle, width) {
    if(trunk_leng > width * 2) {
        t2 = t3d(t, "forward", leng = trunk_leng);
        line(t, t2, width);

        rolled = t3d(t2, "roll", angle = 120);    // 先翻個 120 度

        branch_leng = trunk_leng * branch_scale;
        
        t3 = t3d(rolled, "turn", angle = branch_angle);
        tree(t3, branch_leng, branch_scale, branch_angle, width);

        t4 = t3d(rolled, "turn", angle = -branch_angle);        
        tree(t4, branch_leng, branch_scale, branch_angle, width);
    }
    else {
        shoot(t, trunk_leng, branch_scale, branch_angle, width);
    }
}

只要如上修改 tree,就可以得到一棵立體樹:

3D 碎形/立體樹

如果你想讓這棵樹看來更自然一些,可以將發芽時的分支著色,這可以透過 OpenSCAD 的 color 模組;另外,讓前進的長度,以及翻身、轉動的角度乘上一個隨機數,可以讓樹每次生成時都不同,進一步地,還可以讓繪圖時的枝幹粗細不同。

以下是我基於以上概念實現的程式碼,有興趣可以自行研究一下:

use <polyline_join.scad>
use <turtle/t3d.scad>
use <util/rand.scad>

trunk_leng = 50;
branch_scale = 0.7;
branch_angle = 30;
width = 2;
$fn = 4;

module line(t1, t2, start_width, end_width) {
    polyline_join([t3d(t1, "point"), t3d(t2, "point")]) {
        sphere(start_width / 2);
        sphere(end_width / 2);
    }
}

module shoot(t, trunk_leng, branch_scale, branch_angle, width) {
    t2 = t3d(t, "forward", leng = trunk_leng);
    line(t, t2, width, width);

    branch_leng = trunk_leng * branch_scale;
    
    color("green") {
        t3 = t3d(t2, "turn", angle = branch_angle);
        line(
            t2, 
            t3d(t3, "forward", leng = branch_leng), 
            width,
            width * rand(1, 3)
        );

        t4 = t3d(t2, "turn", angle = -branch_angle);  
        line(
            t2, 
            t3d(t4, "forward", leng = branch_leng), 
            width,
            width * rand(1, 3)
        );
    }
}

module tree(t, trunk_leng, branch_scale, branch_angle, width) {
    if(trunk_leng > width * 2) {
        t2 = t3d(t, "forward", leng = trunk_leng * rand(0.6, 1));
        line(t, t2, trunk_leng / 3, trunk_leng / 3 * branch_scale);

        rolled = t3d(t2, "roll", angle = 120 * rand(0.6, 1));
        branch_leng = trunk_leng * branch_scale;
        
        t3 = t3d(rolled, "turn", angle = branch_angle * rand(0.6, 1));
        tree(t3, branch_leng, branch_scale, branch_angle, width);

        t4 = t3d(rolled, "turn", angle = -branch_angle * rand(0.6, 1));        
        tree(t4, branch_leng, branch_scale, branch_angle, width);
    }
    else {
        shoot(t, trunk_leng, branch_scale, branch_angle, width);
    }
}

tree(t3d(), trunk_leng, branch_scale, branch_angle, width);

dotSCAD 的 rand 函式,封裝了 OpenSCAD 的 rands 函式,可以指定隨機數範圍,以下是隨機產生的立體樹:

3D 碎形/立體樹

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