在〈路徑擠出(二)〉中談到了,若獨立地處理每個切面,在某些路徑可能會造成斷裂狀,想避免這種情況,可以令切面與切面間有前後關係。
這比較像是將一開始的 2D 形狀看成是個環,路徑在環中,環循著路徑不斷地前進,當路徑變化時,你的環也會做出必要的旋轉,從離散的時間點來看,旋轉後的環是有前後關係的。
那麼程式面上該如何做到這種關係呢?方式之一是,若前一個切面的法向量為下圖紅色,下個切面法向量與下圖綠色:
那麼令紅色向量轉動到與綠色向量方向一致,而轉動紅色向量的時候,切面也要轉動,令紅色向量始終是切面的法向量,因為轉動的角度與綠色向量有關,切面的轉動也就有了前後關係。
那麼該怎麼轉動紅色向量轉動到與綠色向量方向一致呢?紅色向量與綠色向量會構成一個平面,計算出這個平面上兩個向量的夾角,並以該平面的法向量來轉動:
而因為紅色向量始終是切面的法向量,紅色向量轉的度數,就是切面上各點轉的度數,只不過問題來了,上圖的黑色向量不會是 x、y 或 z 軸,而是任意的一個軸,如何繞著指定的軸轉動呢?這是軸角(Axis–angle)轉動表示,可以用〈四元數旋轉矩陣〉來計算,而 p5.Matrix 的 rotate
方法就提供了實作。
至於兩個向量間的夾角,可以用以下函式計算:
function angleBetween(vt1, vt2) {
return _toRadians(acos(p5.Vector.dot(vt1, vt2) / (vt1.mag() * vt2.mag())));
}
由於切面與切面之間有前後關係,這代表著用來旋轉、移動切面的矩陣,也會有前後關係,要注意的是,p5.Matrix
的矩陣計算,在撰寫順序上與 p5.js 的矩陣套用是相同的,因此計算矩陣時,撰寫順序是先 translate
,之後旋轉的累計,底下的 transformMatrices
是以路徑及各切面的法向量來計算各個轉換矩陣:
function transformMatrices(path, nVts) {
const matrices = path.map(p => {
const m = new p5.Matrix();
m.translate(p);
return m;
});
// 第 0 個切面法向量的 theta、phi
const angles = thetaPhi(nVts[0]);
for(let i = 0; i < matrices.length; i++) {
const m = matrices[i];
// 角軸旋轉累積
for(let j = i; j > 1; j--) {
const vt1 = nVts[j - 1];
const vt2 = nVts[j];
const axis = p5.Vector.cross(vt1, vt2);
const a = angleBetween(vt1, vt2);
m.rotate(a, axis.x, axis.y, axis.z);
}
m.rotateZ(angles.theta);
m.rotateY(angles.phi);
}
return matrices;
}
有了轉換矩陣,就可以重新實作 pathExtrude
:
function pathExtrude(shape, path) {
const m = new p5.Matrix();
m.rotateZ(HALF_PI)
m.rotateX(-HALF_PI);
const shape3D = shape.map(p => [p[0], p[1], 0])
.map(p => applyMatrixForPoint(m, p));
const vts = path.map(p => createVector(p[0], p[1], p[2]));
const fstNvt = p5.Vector.sub(vts[0], vts[1]);
const nVts = [fstNvt, fstNvt];
for(let i = 1; i < vts.length - 1; i++) {
nVts.push(p5.Vector.sub(vts[i], vts[i + 1]))
}
const matrices = transformMatrices(path, nVts);
const sections = transformMatrices(path, nVts).map(
m => shape3D.map(p => applyMatrixForPoint(m, p))
);
sweep(sections);
}
這麼一來,〈路徑擠出(二)〉中的問題,在這邊就能獲得解決了:
看來這個版本比較好嗎?不一定,這只是一種猜想切面如何翻轉的方式,猜想的結果可能不是你要的,來看看這個環面紐結有什麼問題:
放大其中一部份來看:
第一個切面與最後一個切面顯然差距很大?在〈路徑擠出(一)〉中的版本,卻沒有這個問題?
再來看〈路徑擠出(二)〉中的螺旋,在這邊的實作中會有什麼問題:
看得出來嗎?將兩張圖擺一起:
左邊的切面顯然比較一致,右邊的切面有沿著路徑扭轉的情況,沒有哪個對哪個錯,只是哪個會是你腦袋中想要的圖形。
路徑擠出本來就是在只提供點,資訊不足的情況下嘗試腦補的功能,除了這邊談到的兩種方式外,也許你也可以發現其他的方式,當然,有數學公式、能提供切面翻轉詳細資訊,撰寫出專用的擠出功能,才能繪製出精確的結果。