3D 線段


在我的作品中,Character tower generatorText Sphere 或者是 Spinning picture ornament 都會用到螺線。

Character tower generator

Text Sphere

Spinning picture ornament

後續在討論模型如何結合圓的設計時,會談到如何設計出這些模型,這邊先討論一下,這模型中的螺線怎麼實現,最簡單的作法之一是,在螺線的路徑上,密集地建立小方塊來組成,如果你看看上面一些模型的原始碼,我確實是這麼做的,然而,我也非常地不滿意,我一直在思考,有沒有更優雅的方式來建立這類線段?

如果能計算出螺線的路徑上所有座標點,並有個 polyline3D 之類的模組,這個需求就可以解決了,只是這 polyline3D 要怎麼建立呢?

兩點決定一線

線段 中,我們曾經討論過,如何在 XY 平面建立 2D 線段,那時是從兩點決定一線開始實作,如果想要能指定 XYZ 座標的點,建立 3D 線段的話,當然也是要先將兩點決定一線實現出來。

你也許會想,實現 2D 線段時,使用了內建的 polygon 模組,OpenSCAD 還有個內建的 polyhedron 模組,可以指定座標建立 3D 模型,可不可以利用呢?我想,基本上是可以,只不過,要使用 polyhedron ,就必須為 3D 線段建立頂點,這可不是件容易的事,有沒有更簡單的方式?

還記得在 布林運算與 hull 轉換 中談過的 hull 操作嗎?它會像一塊布將含蓋的模型圍起來,例如當時舉的例子是:

radius = 10;
hull() {
    circle(radius);
    translate([2 * radius, 0, 0]) 
        circle(radius);
}

這會將兩個圓圍起來成為:

布林運算與 hull 轉換

如果將兩個圓拉開一點呢?看起來會成為一條線,對吧!如果兩個圓的圓心座標可以指定,而將直徑當成是線段寬度,那是不是也能用來建立線段呢?這也就是 線段 中我談到的,還有其他實作線段的方式之一。

那麼,如果不是個兩個圓,而是個兩個球,球心座標可以指定,並將這兩個球用 hull 的話會如何呢?耶?這似乎是個實作 3D 線段的簡單方法!

module line3D(p1, p2, thickness, fn = 24) {
    $fn = fn;

    hull() {
        translate(p1) sphere(thickness / 2);
        translate(p2) sphere(thickness / 2);
    }
}

line3D([1, 1, 1], [10, 10, 10], 1, 3);

耶!果然成功了:

3D 線段

由於是由圓來組成線段的兩個端點,這邊特意設計了 fn 參數,指定的 fn 決定了線段的圓滑度,用來創造不同的線段質感,當然,fn 越大,就會需要更多的計算,由於使用了 hull,當線段多時,fn 越大,繪製模型就會越慢。

polyline3D

可以兩點決定一線了,同樣地,只要兩個點為一組,將全部的點消耗完就可以了:

module line3D(p1, p2, thickness, fn = 24) {
    $fn = fn;

    hull() {
        translate(p1) sphere(thickness / 2);
        translate(p2) sphere(thickness / 2);
    }
}

module polyline3D(points, thickness, fn) {
    module polyline3D_inner(points, index) {
        if(index < len(points)) {
            line3D(points[index - 1], points[index], thickness, fn);
            polyline3D_inner(points, index + 1);
        }
    }

    polyline3D_inner(points, 1);
}

points = [[1, 2, 3], [4, -5, -6], [-1, -3, -5], [0, 0, 0]];
polyline3D(points, 1, 3);

完成的效果不錯:

3D 線段

試著做螺線

接下來就是個練習了,請你試著只實作 Character tower generator 中的螺線,底下列出一種可能的方式:

module line3D(p1, p2, thickness, fn = 24) {
    $fn = fn;

    hull() {
        translate(p1) sphere(thickness / 2);
        translate(p2) sphere(thickness / 2);
    }
}

module polyline3D(points, thickness, fn) {
    module polyline3D_inner(points, index) {
        if(index < len(points)) {
            line3D(points[index - 1], points[index], thickness, fn);
            polyline3D_inner(points, index + 1);
        }
    }

    polyline3D_inner(points, 1);
}

r = 20;
h = 5;
fa = 15;
circles = 10;

points = [
    for(a = [0:fa:360 * circles]) 
        [r * cos(a), r * sin(a), h / (360 / fa) * (a / fa)]
];
polyline3D(points, 1, 3);

完成的效果如下:

3D 線段

如果不清楚這程式碼是怎麼一回事,可以看看之後的 文字與圓柱 之說明。

再來一個練習,試著只建立 Spinning picture ornament 中的螺線,底下是一個參考實作:

module line3D(p1, p2, thickness, fn = 24) {
    $fn = fn;

    hull() {
        translate(p1) sphere(thickness / 2);
        translate(p2) sphere(thickness / 2);
    }
}

module polyline3D(points, thickness, fn) {
    module polyline3D_inner(points, index) {
        if(index < len(points)) {
            line3D(points[index - 1], points[index], thickness, fn);
            polyline3D_inner(points, index + 1);
        }
    }

    polyline3D_inner(points, 1);
}

r = 50;
points = [
    for(a = [0:180]) 
        [r * cos(-90 + a) * cos(a), r * cos(-90 + a) * sin(a), r * sin(-90 + a)]
];

for(i = [0:7]) {
    rotate(45 * i) polyline3D(points, 2, 3);
}

完成的效果如下:

3D 線段

如果不清楚程式碼中的計算是怎麼一回事,可以參考一下 文字與圓球 中的說明。

最後,可以將這些日子以來,一直不滿意的問題給解決掉,不正是一件值得開心的事嗎?! :)