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 版本,這邊畫線時,是將球以凸包連結,分支的左、右是從海龜觀點來看:
類似地,如果每個分支都視為另一個主幹的開始會如何呢?支幹長度小於線的粗細時,畫出來也沒意義,因此可做為遞迴終止條件:
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 碎形樹
在建立了 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
,就可以得到一棵立體樹:
如果你想讓這棵樹看來更自然一些,可以將發芽時的分支著色,這可以透過 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
函式,可以指定隨機數範圍,以下是隨機產生的立體樹: