圓柱與環面迷宮

March 17, 2022

基於方形迷宮的資料,可以進行許多變化,這是因為不少立體的面,可以展開、變形為方形的面,例如,圓柱的弧面剪開來,就是一張方形的面,而環面可以剪開成為圓柱,再剪開成為方形的面。

圓柱迷宮

方形迷宮的細胞資料中,上牆類型可以畫為弧,為了便於建立弧作為牆,可以先設計一個可以畫立體弧的 arc_wall,可以接受半徑與弧的張角:

module arc_wall(radius, a, wall_thickness) {
    half_thickness = wall_thickness / 2;
    arc_x = radius * cos(a);
    arc_y = radius * sin(a);
    polyline_join([[radius, 0, 0], [arc_x, arc_y, 0]]) {
        sphere(half_thickness, $fn = 5);
        rotate(a) 
            sphere(half_thickness, $fn = 5);
    }
}

雖然稱為 arc_wall,不過其實只是將兩個球 hull 連結為 3D 線段罷了,這是因為在 $fn 較大時,多個弧連接起來,就會像是個圓了,當然,如果你想真正設計個弧形連結也是可以;至於右牆只要在弧的終點畫個垂直於 z 軸的線。

完成一個細胞的上牆與右牆後,接著依細胞資料的 x 資訊計算轉動角度,y 資訊放至對應高度,就可以將牆放到對應的位置。

簡單來說,這就像是將一張方形的紙,彎成圓柱面,不過,方形迷宮的左邊界與右邊界並不互通的話,變成圓柱迷宮後,0 度的地方會有一條從上至下的垂直接合。

dotSCAD 的 mz_square 可以設定 x_wrapping 參數為 true,讓 x 方向的路徑像圓柱接合後可以左右互通,因此想完成圓柱迷宮可以如下撰寫:

use <maze/mz_square.scad>
use <maze/mz_square_get.scad>
use <polyline_join.scad>

$fn = 36;
radius = 100;
layers = 5;
wall_thickness = 6;

cylinder_maze(radius, layers, wall_thickness);

module cylinder_maze(radius, layers, wall_thickness) {
    module arc_wall(radius, a, wall_thickness) {
        half_thickness = wall_thickness / 2;
        arc_x = radius * cos(a);
        arc_y = radius * sin(a);
        polyline_join([[radius, 0, 0], [arc_x, arc_y, 0]]) {
            sphere(half_thickness, $fn = 5);
            rotate(a) 
                sphere(half_thickness, $fn = 5);
        }
    }

    rows = layers;
    columns = $fn;

    cell_width = 2 * PI * radius / $fn;
    a_step = 360 / $fn;
    half_thickness = wall_thickness / 2;

    cells = mz_square(rows, columns, x_wrapping = true);

    for(row = cells, cell = row) {
        x = mz_square_get(cell, "x");
        y = mz_square_get(cell, "y");
        type = mz_square_get(cell, "t");
        
        h = y * cell_width;
        a = a_step * x;
        
        rotate(a)
        translate([0, 0, h]) 
        // 繪製單一細胞的牆
        {
            if(type == "TOP_WALL" || type == "TOP_RIGHT_WALL") {
                translate([0, 0, cell_width])
                    arc_wall(radius, a_step, wall_thickness);
            }

            if(type == "RIGHT_WALL" || type == "TOP_RIGHT_WALL") {
                rotate(a_step)           
                polyline_join([[radius, 0, 0], [radius, 0, cell_width]])
                    sphere(half_thickness, $fn = 5);
            } 
        }
        
        // 繪製最底層的牆
        if(y == 0) {
            rotate(a)
                arc_wall(radius, a_step, wall_thickness);
        }
    }
}

這會完成以下的成果:

圓柱與環面迷宮

ptf_bend 函式

自行處理細胞的資料,可以掌握較多的細節,例如,方才程式碼的 arc_wall 中特別旋轉了球的角度,這可以讓每個角度的牆面真的就是繞著圓在轉。

只不過有時不需要這類細節,例如 3D 列印時,某些細節其實也印不出來,牆有沒有真的繞著圓在轉也就不是那麼重要,這時就會想,有沒有更簡單的方式?

其實就平面折彎為圓柱面或弧面,算是蠻常見的需求,為了便於實現此需求,dotSCAD 提供了 ptf_bend 函式,你可以指定原本方形的區域大小、目標圓柱面的半徑、弧面的張角,它可以將指定的平面點座標轉換至弧面的點座標。

記得嗎?mz_squarewalls 可以將迷宮細胞資訊轉換為線的資訊,而每段線就是兩個座標組成的 list,於是就可以完成以下的程式,畫出來的圓柱迷宮不細看,其實與上面的範例圖是很相似的:

use <maze/mz_square.scad>
use <maze/mz_squarewalls.scad>
use <ptf/ptf_bend.scad>
use <polyline_join.scad>

$fn = 36;
radius = 100;
layers = 5;
wall_thickness = 6;

cylinder_maze(radius, layers, wall_thickness);

module cylinder_maze(radius, layers, wall_thickness) {
    rows = layers;
    columns = $fn;

    cell_width = 2 * PI * radius / $fn;
    half_thickness = wall_thickness / 2;
    size = [columns, rows] * cell_width; 

    cells = mz_square(rows, columns, x_wrapping = true);
    walls = mz_squarewalls(cells, cell_width, left_border = false);
    
    for(wall = walls) {
        bended = [for(p = wall) ptf_bend(size, p, radius, 360)];
        polyline_join(bended)
            sphere(half_thickness, $fn = 5);
    }
}

環面迷宮/ptf_torus

方才談到,環面可以剪開成為圓柱,再剪開成為方形的面,將這個過程反過來,套至至平面迷宮,就可以構成環面迷宮。

想將平面迷宮變成環面迷宮,原本的平面迷宮必須左右相通、上下相通,左右相通可以設定 mz_square 可以設定 x_wrapping 參數為 true,上下相通可以設定 y_wrapping 參數為 true

有興趣的話,可以自行試著推導平面至環境的轉換公式,沒興趣自行推導的話,可以使用 dotSCAD 的 ptf_torus 函式,可以指定原本方形的區域大小、目標環面的大、小半徑、弧面的大小、張角,它可以將指定的平面點座標轉換至環面的點座標。

因此想完成環面迷宮的話,可以撰寫以下的程式碼:

use <maze/mz_square.scad>
use <maze/mz_squarewalls.scad>
use <ptf/ptf_torus.scad>
use <polyline_join.scad>

$fn = 12;
r1 = 100;
r2 = 50;
wall_thickness = 10;

torus_maze(r1, r2, wall_thickness);

module torus_maze(r1, r2, wall_thickness) {
    rows = $fn * 3;
    columns = $fn;

    cell_width = 2 * PI * r1 / $fn;
    half_thickness = wall_thickness / 2;
    size = [columns, rows] * cell_width; 

    cells = mz_square(rows, columns, x_wrapping = true, y_wrapping = true);
    walls = mz_squarewalls(cells, cell_width, left_border = false);
    
    for(wall = walls) {
        bended = [for(p = wall) ptf_torus(size, p, [r1, r2], [360, 360])];
        polyline_join(bended)
            sphere(half_thickness, $fn = 5);
    }
}

color("black")
rotate_extrude()
translate([r1 + r2, 0])
    circle(r2);

最後我加上了一個黑色的環,以便於看出環面迷宮:

圓柱與環面迷宮

到這邊應該也可以知道,dotSCAD 為什麼要設計 mz_squarewalls 函式傳回線段資料,而不是一個 mz_squarewalls 模組直接繪製迷宮了吧!

傳回資料就可以利用資料做更多的變化,使用程式進行 3D 建模時,不單是參數化方便,在發現繪模型時,其實可以重用一些運算好的資料,就可以進一步抽取出來,作為其他模型需要的資料來源。

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