Theta 迷宮

March 26, 2022

如果想做個圓形迷宮,方式之一透過圓形遮罩以及〈遮罩與蜂巢迷宮〉的方式來實現,另一種方式是使用極座標繪製,常被稱為 Theta 迷宮。

基於方形迷宮變化

之前的文件一直使用方形迷宮來變化,那麼它有沒有辦法變化為 Theta 迷宮呢?基本上是可以,出發點是將底下的圖形:

Theta 迷宮

繞為底下的圓:

Theta 迷宮

方式是將每一行(column)在 x 方向移動一個半徑後變成環狀:

Theta 迷宮

若要用這樣的方式建立 Theta 迷宮,maze_square 的實作也是不用更改,純粹就是繪圖的方式罷了,也就是將上牆換為弧、右牆換為射線,其實也不一定要將上牆換為弧,只要列數夠多,轉換後就相當於構成的多邊形邊數夠多,看來就像個圓。

若只要要這麼實現,可以透過 dotSCAD 的 maze_squarewalls 將細胞資料轉為畫牆用的線段資料,然後透過 prf_ring 函式,它實現了座標轉換,需要指定原本的方形區域尺寸以及環半徑,以便代表線段的每個座標點轉換,轉換為環上的每個座標點。例如:

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

rows = 36;
columns = 5;
cell_width = 2;
thickness = 1;   

leng = rows * cell_width;
radius = 0.5 * leng / PI;
a_step = 360 / leng;

cells = mz_square(rows, columns, y_wrapping = true);
walls = mz_squarewalls(cells, cell_width, bottom_border = false);

size = [columns, rows] * cell_width;
for(wall_pts = walls) {  
   transformed = [for(pt = wall_pts) ptf_ring(size, pt, radius)];
   polyline_join(transformed)
       circle(d = thickness);
}

因為迷宮繞成環,原本的方形上與下必須能接合,這可以設定 mz_squarey_wrappingtrue,而在繪製時,mz_squarewalls 不需要有最下方的牆,這可以設定 bottom_borderfalse,完成後的迷宮會長這樣:

Theta 迷宮

看來蠻像一回事的,不過,這種迷宮變換的方式是,越內環的細胞越密,越外環的細胞越疏,畢竟原始迷宮資料的列數是固定的,也就是每一環的細胞數是固定的,越外環的圓周越大,分配到的空間當然就越大,將原始迷宮行數增加的話,就會更明顯:

Theta 迷宮

動態切分細胞

若不希望內環與外環的細胞,分配到的空間大小有明顯的差距,基本概念可以是 n 環時讓細胞數加倍,例如,n 為 2 倍數時就讓細胞數加倍:

Theta 迷宮

不過,若要令分配到的空間大小不要有明顯的差距,n 並不是固定的數,方式之一是,若細胞得到的弧長,為細胞的由內而外寬度的一定倍數時,就進行細胞的切分。

dotSCAD 的 mz_theta 實現了動態切分細胞,而後建立迷宮的演算,可以指定迷宮的環數,以及第一環要有幾個細胞,傳回的資料會是二維的 list,每一環的細胞數不一定相同,若迷宮資料是 mazemaze[i] 就是 i 環的細胞資料,想要知道該環有多少細胞?那就是 len(maze[i]) 了。

mz_square 的實作而言,若細胞得到的弧長,為細胞的由內而外寬度的 1.5 倍時,會進行細胞的切分,就 3D 建模而言,這個倍數足以應付絕大多數的需求。

對於每個細胞資料,可以使用 mz_theta_get 來取得訊息,例如:

use <maze/mz_theta.scad>
use <maze/mz_theta_get.scad>

rings = 8;
beginning_number = 8;
cell_width = 10;
thickness = 2;

maze = mz_theta(rings, beginning_number);
for(ring = maze) {
    thetaStep = 360 / len(ring);        //  該環細胞的張角
    for(cell = ring) {
        r = mz_theta_get(cell, "r");    // 由內往外第 r 環
        c = mz_theta_get(cell, "c");    // 逆時針的第 c 個
        type = mz_theta_get(cell, "t"); // 牆面類型
        ...看你怎麼畫圖囉...
    }
}

type 表示細胞牆面會是 "NO_WALL"(無牆)、"INWARD_WALL"(內牆)、"CCW_WALL"(逆時針牆) 或 "INWARD_CCW_WALL"(內牆與逆時針牆):

Theta 迷宮

可以將單一細胞的牆面繪製封裝為 drawWall

module drawWall(r, c, type, thetaStep, thickness) {
    function vt_from_angle(theta, r) = [r * cos(theta), r * sin(theta)];

    innerR = (r + 1) * cell_width;
    outerR = (r + 2) * cell_width;
    theta1 = thetaStep * c;
    theta2 = thetaStep * (c + 1);

    innerVt1 = vt_from_angle(theta1, innerR);
    innerVt2 = vt_from_angle(theta2, innerR);
    outerVt2 = vt_from_angle(theta2, outerR);

    if(type == "INWARD_WALL" || type == "INWARD_CCW_WALL") {
        polyline_join([innerVt1, innerVt2])
            circle(d = thickness);
    }

    if(type == "CCW_WALL" || type == "INWARD_CCW_WALL") {
        polyline_join([innerVt2, outerVt2])
            circle(d = thickness);
    }     
}

這麼一來就可以完成迷宮的繪製,別忘了補上最外圍的牆:

use <maze/mz_theta.scad>
use <maze/mz_theta_get.scad>
use <hollow_out.scad>
use <polyline_join.scad>

rings = 8;
beginning_number = 8;
cell_width = 10;
thickness = 2;

maze = mz_theta(rings, beginning_number);
for(ring = maze) {
    thetaStep = 360 / len(ring);
    for(cell = ring) {
        r = mz_theta_get(cell, "r");
        c = mz_theta_get(cell, "c");
        type = mz_theta_get(cell, "t");
        drawWall(r, c, type, thetaStep, thickness);
    }
}

// 最外圍的牆
r = cell_width * (rings + 1);
hollow_out(thickness)
offset(thickness)
    circle(r, $fn = len(maze[rings - 1]));
    
module drawWall(r, c, type, thetaStep, thickness) {
    function vt_from_angle(theta, r) = [r * cos(theta), r * sin(theta)];

    innerR = (r + 1) * cell_width;
    outerR = (r + 2) * cell_width;
    theta1 = thetaStep * c;
    theta2 = thetaStep * (c + 1);

    innerVt1 = vt_from_angle(theta1, innerR);
    innerVt2 = vt_from_angle(theta2, innerR);
    outerVt2 = vt_from_angle(theta2, outerR);

    if(type == "INWARD_WALL" || type == "INWARD_CCW_WALL") {
        polyline_join([innerVt1, innerVt2])
            circle(d = thickness);
    }

    if(type == "CCW_WALL" || type == "INWARD_CCW_WALL") {
        polyline_join([innerVt2, outerVt2])
            circle(d = thickness);
    }     
}

完成的 Theta 迷宮如下:

Theta 迷宮

來看個 20 環,細胞大小看來還蠻平均的:

Theta 迷宮

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