在 Marching squares(一) 與 Marching squares(二) 談到了如何建立等值線,現在的問題是,若想要的是這樣的輪廓圖呢?
單純地將等值線加粗,沒辦法構成這樣的圖形,你需要的圖形就像梯田,在一個高度的下界與上界之間,畫出一個平面的帶狀區域,
Marching squares 的等值線演算,可以擴展為等值帶演算,你會指定高度的下界與上界,因此每一格資料不僅是被標示為 0 與 1,而會是個三元值,分別代表小於下界、在上下界之間、高於上界,若分別標示為 0、1、2,那麼以維基百科 Marching squares 中的圖來說,就會有 82 種可能性。
上圖是將維基百科 Marching squares 中的圖,加上了細胞角落標示的編號,等值帶是藍色區域的部份(也就是 1111 那個顏色),乍看 82 種可能性,要為等值帶建立頂點很繁雜,不過有些頂點旋轉後是相同的,圖中將它們分類以虛線框圍起來了,這麼一來,基本的等值帶頂點就只有 34 種,其餘就旋轉頂點,就能得到相對應的等值帶。
在程式實作方法類似等值線,首先標示每一格的資料,作為細胞的一角:
function mapToCorners(values, threshold) {
const corners = []
for(let r = 0; r < values.length; r++) {
const row = [];
for(let c = 0; c < values[r].length; c++) {
const v = values[r][c];
row.push({
vt : createVector(c, r, values[r][c]),
thresholdLabel: v > threshold.upper ? '2' : (v <= threshold.upper && v >= threshold.lower ? '1' : '0')
});
}
corners.push(row);
}
return corners;
}
再來建立細胞資料,每個細胞資料會包含四個角落的向量,以及角落值的編號:
function flatMapToCells(corners) {
const cells = [];
for(let r = 0; r < corners.length - 1; r++) {
for(let c = 0; c < corners[r].length - 1; c++) {
cells.push({
vts: [
corners[r][c].vt,
corners[r + 1][c].vt,
corners[r + 1][c + 1].vt,
corners[r][c + 1].vt
],
cornerCase: cornerCase([
corners[r][c],
corners[r + 1][c],
corners[r + 1][c + 1],
corners[r][c + 1]
])
});
}
}
return cells;
}
function cornerCase(corners) {
return corners.map(corner => corner.thresholdLabel).join('');
}
有了細胞資料之後,接下來就是建立等值帶了,這邊需要更細心一些,雖然歸納為 34 種了,也還是很多啦!總之也是小心座標點別對應錯誤了:
function case1222(cellVts, threshold, r = 0) {
const vts = rotated(cellVts, r);
const U = threshold.upper;
const L = threshold.lower;
return [[vts[0], inter_pt(vts[0], vts[1], U), inter_pt(vts[0], vts[3], U)]];
}
function case2111(cellVts, threshold, r = 0) {
const vts = rotated(cellVts, r);
const U = threshold.upper;
const L = threshold.lower;
return [[inter_pt(vts[0], vts[3], U), inter_pt(vts[0], vts[1], U), vts[1], vts[2], vts[3]]];
}
function case0111(cellVts, threshold, r = 0) {
const vts = rotated(cellVts, r);
const U = threshold.upper;
const L = threshold.lower;
return [[inter_pt(vts[0], vts[3], L), inter_pt(vts[0], vts[1], L), vts[1], vts[2], vts[3]]];
}
function case1000(cellVts, threshold, r = 0) {
const vts = rotated(cellVts, r);
const U = threshold.upper;
const L = threshold.lower;
return [[vts[0], inter_pt(vts[0], vts[1], L), inter_pt(vts[0], vts[3], L)]];
}
function case0222(cellVts, threshold, r = 0) {
const vts = rotated(cellVts, r);
const U = threshold.upper;
const L = threshold.lower;
return [[inter_pt(vts[0], vts[3], L), inter_pt(vts[0], vts[1], L), inter_pt(vts[0], vts[1], U), inter_pt(vts[0], vts[3], U)]];
}
function case2211(cellVts, threshold, r = 0) {
const vts = rotated(cellVts, r);
const U = threshold.upper;
const L = threshold.lower;
return [[inter_pt(vts[0], vts[3], U), inter_pt(vts[1], vts[2], U), vts[2], vts[3]]];
}
function case0011(cellVts, threshold, r = 0) {
const vts = rotated(cellVts, r);
const U = threshold.upper;
const L = threshold.lower;
return [[inter_pt(vts[0], vts[3], L), inter_pt(vts[1], vts[2], L), vts[2], vts[3]]];
}
function case2000(cellVts, threshold, r = 0) {
const vts = rotated(cellVts, r);
const U = threshold.upper;
const L = threshold.lower;
return [[inter_pt(vts[0], vts[3], U), inter_pt(vts[0], vts[1], U), inter_pt(vts[0], vts[1], L), inter_pt(vts[0], vts[3], L)]];
}
function case2201(cellVts, threshold, r = 0) {
const vts = rotated(cellVts, r);
const U = threshold.upper;
const L = threshold.lower;
return [[inter_pt(vts[0], vts[3], U), inter_pt(vts[1], vts[2], U), inter_pt(vts[1], vts[2], L), inter_pt(vts[2], vts[3], L), vts[3]]];
}
function case2210(cellVts, threshold, r = 0) {
const vts = rotated(cellVts, r);
const U = threshold.upper;
const L = threshold.lower;
return [[inter_pt(vts[0], vts[3], U), inter_pt(vts[1], vts[2], U), vts[2], inter_pt(vts[2], vts[3], L), inter_pt(vts[0], vts[3], L)]];
}
function case0012(cellVts, threshold, r = 0) {
const vts = rotated(cellVts, r);
const U = threshold.upper;
const L = threshold.lower;
return [[inter_pt(vts[0], vts[3], L), inter_pt(vts[1], vts[2], L), vts[2], inter_pt(vts[2], vts[3], U), inter_pt(vts[0], vts[3], U)]];
}
function case0021(cellVts, threshold, r = 0) {
const vts = rotated(cellVts, r);
const U = threshold.upper;
const L = threshold.lower;
return [[inter_pt(vts[0], vts[3], L), inter_pt(vts[1], vts[2], L), inter_pt(vts[1], vts[2], U), inter_pt(vts[2], vts[3], U), vts[3]]];
}
function case1120(cellVts, threshold, r = 0) {
const vts = rotated(cellVts, r);
const U = threshold.upper;
const L = threshold.lower;
return [[vts[0], vts[1], inter_pt(vts[1], vts[2], U), inter_pt(vts[2], vts[3], U), inter_pt(vts[2], vts[3], L), inter_pt(vts[0], vts[3], L)]];
}
function case1102(cellVts, threshold, r = 0) {
const vts = rotated(cellVts, r);
const U = threshold.upper;
const L = threshold.lower;
return [[vts[0], vts[1], inter_pt(vts[1], vts[2], L), inter_pt(vts[2], vts[3], L), inter_pt(vts[2], vts[3], U), inter_pt(vts[0], vts[3], U)]];
}
function case1012(cellVts, threshold, r = 0) {
const vts = rotated(cellVts, r);
const U = threshold.upper;
const L = threshold.lower;
return [[vts[0], inter_pt(vts[0], vts[1], L), inter_pt(vts[1], vts[2], L), vts[2], inter_pt(vts[2], vts[3], U), inter_pt(vts[0], vts[3], U)]];
}
function case2121(cellVts, threshold, r = 0) {
const vts = rotated(cellVts, r);
const U = threshold.upper;
const L = threshold.lower;
const cp = center(vts);
if(cp.z > U) {
return [
[inter_pt(vts[0], vts[1], U), vts[1], inter_pt(vts[1], vts[2], U), inter_pt(vts[1], cp, U)], [inter_pt(vts[0], vts[3], U), inter_pt(cp, vts[3], U), inter_pt(vts[2], vts[3], U), vts[3]]
];
}
else if(cp.z <= U && cp.z >= L) {
return [
[inter_pt(vts[0], vts[3], U), inter_pt(vts[0], cp, U), inter_pt(vts[0], vts[1], U), vts[1], inter_pt(vts[1], vts[2], U), inter_pt(vts[2], cp, U), inter_pt(vts[2], vts[3], U), vts[3]]
];
}
return [
[inter_pt(vts[0], vts[3], U), inter_pt(vts[0], cp, U), inter_pt(vts[0], cp, L), inter_pt(vts[3], cp, L), inter_pt(vts[2], cp, L), inter_pt(vts[2], cp, U), inter_pt(vts[2], vts[3], U), vts[3]],
[inter_pt(vts[2], vts[1], U), inter_pt(vts[2], cp, U), inter_pt(vts[2], cp, L), inter_pt(vts[1], cp, L), inter_pt(vts[0], cp, L), inter_pt(vts[0], cp, U), inter_pt(vts[0], vts[1], U), vts[1]]
];
}
function case0101(cellVts, threshold, r = 0) {
const vts = rotated(cellVts, r);
const U = threshold.upper;
const L = threshold.lower;
const cp = center(vts);
if(cp.z < L) {
return [
[inter_pt(vts[0], vts[1], L), vts[1], inter_pt(vts[1], vts[2], L), inter_pt(vts[1], cp, L)], [inter_pt(vts[0], vts[3], L), inter_pt(cp, vts[3], L), inter_pt(vts[2], vts[3], L), vts[3]]
];
}
else if(cp.z <= U && cp.z >= L) {
return [
[inter_pt(vts[0], vts[3], L), inter_pt(vts[0], cp, L), inter_pt(vts[0], vts[1], L), vts[1], inter_pt(vts[1], vts[2], L), inter_pt(vts[2], cp, L), inter_pt(vts[2], vts[3], L), vts[3]]
];
}
return [
[inter_pt(vts[0], vts[3], L), inter_pt(vts[0], cp, L), inter_pt(vts[0], cp, U), inter_pt(vts[3], cp, U), inter_pt(vts[2], cp, U), inter_pt(vts[2], cp, L), inter_pt(vts[2], vts[3], L), vts[3]],
[inter_pt(vts[2], vts[1], L), inter_pt(vts[2], cp, L), inter_pt(vts[2], cp, U), inter_pt(vts[1], cp, U), inter_pt(vts[0], cp, U), inter_pt(vts[0], cp, L), inter_pt(vts[0], vts[1], L), vts[1]]
];
}
function case2020(cellVts, threshold, r = 0) {
const vts = rotated(cellVts, r);
const U = threshold.upper;
const L = threshold.lower;
const cp = center(vts);
if(cp.z > U) {
return [
[inter_pt(vts[0], vts[1], U), inter_pt(vts[0], vts[1], L), inter_pt(vts[1], cp, L), inter_pt(vts[1], vts[2], L), inter_pt(vts[1], vts[2], U), inter_pt(vts[1], cp, U)],
[inter_pt(vts[0], vts[3], U), inter_pt(cp, vts[3], U), inter_pt(vts[2], vts[3], U), inter_pt(vts[2], vts[3], L), inter_pt(cp, vts[3], L), inter_pt(vts[0], vts[3], L)]
];
}
else if(cp.z <= U && cp.z >= L) {
return [
[inter_pt(vts[0], vts[3], U), inter_pt(vts[0], cp, U), inter_pt(vts[0], vts[1], U), inter_pt(vts[0], vts[1], L), inter_pt(vts[1], cp, L), inter_pt(vts[1], vts[2], L), inter_pt(vts[1], vts[2], L), inter_pt(cp, vts[2], U), inter_pt(vts[2], vts[3], U), inter_pt(vts[2], vts[3], L), inter_pt(cp, vts[3], L), inter_pt(vts[0], vts[3], L)]
];
}
return [
[inter_pt(vts[0], vts[3], U), inter_pt(vts[0], cp, U), inter_pt(vts[0], vts[1], U), inter_pt(vts[0], vts[1], L), inter_pt(vts[0], cp, L), inter_pt(vts[0], vts[3], L)],
[inter_pt(vts[2], vts[3], L), inter_pt(cp, vts[2], L), inter_pt(vts[1], vts[2], L), inter_pt(vts[1], vts[2], U), inter_pt(cp, vts[2], U), inter_pt(vts[2], vts[3], U)]
];
}
function case2021(cellVts, threshold, r = 0) {
const vts = rotated(cellVts, r);
const U = threshold.upper;
const L = threshold.lower;
const cp = center(vts);
if(cp.z > U) {
return [
[inter_pt(vts[0], vts[1], U), inter_pt(vts[0], vts[1], L), inter_pt(vts[1], cp, L), inter_pt(vts[1], cp, U), inter_pt(vts[1], vts[2], L), inter_pt(vts[1], vts[2], U), inter_pt(vts[1], cp, U)],
[inter_pt(vts[0], vts[3], U), inter_pt(cp, vts[3], U), inter_pt(vts[2], vts[3], U), vts[3]]
];
}
else if(cp.z <= U && cp.z >= L) {
return [
[inter_pt(vts[0], vts[3], U), inter_pt(vts[0], cp, U), inter_pt(vts[0], vts[1], U), inter_pt(vts[0], vts[1], L), inter_pt(vts[1], cp, L), inter_pt(vts[1], vts[2], L), inter_pt(vts[1], vts[2], U), inter_pt(vts[2], cp, U), inter_pt(vts[2], vts[3], U), vts[3]]
];
}
return [
[inter_pt(vts[0], vts[3], U), inter_pt(vts[0], cp, U), inter_pt(vts[0], vts[1], U), inter_pt(vts[0], vts[1], L), inter_pt(vts[0], cp, L), inter_pt(vts[3], cp, L), inter_pt(vts[2], cp, L), inter_pt(vts[1], vts[2], L), inter_pt(vts[1], vts[2], U), inter_pt(vts[2], cp, U), inter_pt(vts[2], vts[3], U), vts[3]]
];
}
function case0201(cellVts, threshold, r = 0) {
const vts = rotated(cellVts, r);
const U = threshold.upper;
const L = threshold.lower;
const cp = center(vts);
if(cp.z < L) {
return [
[inter_pt(vts[0], vts[1], L), inter_pt(vts[1], cp, U), inter_pt(vts[0], vts[1], U), inter_pt(vts[1], vts[2], U), inter_pt(vts[1], vts[2], U), inter_pt(vts[1], cp, L)],
[inter_pt(vts[0], vts[3], L), inter_pt(cp, vts[3], L), inter_pt(vts[2], vts[3], L), vts[3]]
];
}
else if(cp.z <= U && cp.z >= L) {
return [
[inter_pt(vts[0], vts[3], L), inter_pt(vts[0], cp, L), inter_pt(vts[0], vts[1], L), inter_pt(vts[0], vts[1], U), inter_pt(vts[1], cp, U), inter_pt(vts[1], vts[2], U), inter_pt(vts[1], vts[2], L), inter_pt(vts[2], cp, L), inter_pt(vts[2], vts[3], L), vts[3]]
];
}
return [
[inter_pt(vts[0], vts[3], L), inter_pt(vts[0], cp, L), inter_pt(vts[0], vts[1], L), inter_pt(vts[0], vts[1], U), inter_pt(vts[0], cp, U), inter_pt(vts[3], cp, U), inter_pt(vts[2], cp, U), inter_pt(vts[1], vts[2], U), inter_pt(vts[1], vts[2], L), inter_pt(vts[2], cp, L), inter_pt(vts[2], vts[3], L), vts[3]]
];
}
function rotated(vts, n) {
return vts.slice(n, vts.length).concat(vts.slice(0, n));
}
function isobands(cell, threshold) {
const vts = cell.vts;
const U = threshold.upper;
const L = threshold.lower;
switch(cell.cornerCase) {
case '2222': case '0000':
return [];
case '1111':
return [vts];
// 底下每四個一組,逆時針轉動頂點就可以了
case '1222':
return case1222(vts, threshold);
case '2122':
return case1222(vts, threshold, 1);
case '2212':
return case1222(vts, threshold, 2);
case '2221':
return case1222(vts, threshold, 3);
case '2111':
return case2111(vts, threshold);
case '1211':
return case2111(vts, threshold, 1);
case '1121':
return case2111(vts, threshold, 2);
case '1112':
return case2111(vts, threshold, 3);
case '0111':
return case0111(vts, threshold);
case '1011':
return case0111(vts, threshold, 1);
case '1101':
return case0111(vts, threshold, 2);
case '1110':
return case0111(vts, threshold, 3);
case '1000':
return case1000(vts, threshold);
case '0100':
return case1000(vts, threshold, 1);
case '0010':
return case1000(vts, threshold, 2);
case '0001':
return case1000(vts, threshold, 3);
case '0222':
return case0222(vts, threshold);
case '2022':
return case0222(vts, threshold, 1);
case '2202':
return case0222(vts, threshold, 2);
case '2220':
return case0222(vts, threshold, 3);
case '2211':
return case2211(vts, threshold);
case '1221':
return case2211(vts, threshold, 1);
case '1122':
return case2211(vts, threshold, 2);
case '2112':
return case2211(vts, threshold, 3);
case '0011':
return case0011(vts, threshold);
case '1001':
return case0011(vts, threshold, 1);
case '1100':
return case0011(vts, threshold, 2);
case '0110':
return case0011(vts, threshold, 3);
case '2000':
return case2000(vts, threshold);
case '0200':
return case2000(vts, threshold, 1);
case '0020':
return case2000(vts, threshold, 2);
case '0002':
return case2000(vts, threshold, 3);
case '2201':
return case2201(vts, threshold);
case '1220':
return case2201(vts, threshold, 1);
case '0122':
return case2201(vts, threshold, 2);
case '2012':
return case2201(vts, threshold, 3);
case '2210':
return case2210(vts, threshold);
case '0221':
return case2210(vts, threshold, 1);
case '1022':
return case2210(vts, threshold, 2);
case '2102':
return case2210(vts, threshold, 3);
case '0012':
return case0012(vts, threshold);
case '2001':
return case0012(vts, threshold, 1);
case '1200':
return case0012(vts, threshold, 2);
case '0120':
return case0012(vts, threshold, 3);;
case '0021':
return case0021(vts, threshold);
case '1002':
return case0021(vts, threshold, 1);
case '2100':
return case0021(vts, threshold, 2);
case '0210':
return case0021(vts, threshold, 3);
case '1120':
return case1120(vts, threshold);
case '0112':
return case1120(vts, threshold, 1);
case '2011':
return case1120(vts, threshold, 2);
case '1201':
return case1120(vts, threshold, 3);
case '1102':
return case1102(vts, threshold);
case '2110':
return case1102(vts, threshold, 1);
case '0211':
return case1102(vts, threshold, 2);
case '1021':
return case1102(vts, threshold, 3);
case '1012':
return case1012(vts, threshold);
case '2101':
return case1012(vts, threshold, 1);
case '1210':
return case1012(vts, threshold, 2);
case '0121':
return case1012(vts, threshold, 3);
// 鞍部每個 case 有三種情況,另一 case 逆時針轉動頂點就可以了
case '2121':
return case2121(vts, threshold);
case '1212':
return case2121(vts, threshold, 1);
case '0101':
return case0101(vts, threshold);
case '1010':
return case0101(vts, threshold, 1);
case '2020':
return case2020(vts, threshold);
case '0202':
return case2020(vts, threshold, 1);
// 鞍部每個 case 有三種情況,另三 case 逆時針轉動頂點就可以了
case '2021':
return case2021(vts, threshold);
case '1202':
return case2021(vts, threshold, 1);
case '2120':
return case2021(vts, threshold, 2);
case '0212':
return case2021(vts, threshold, 3);
case '0201':
return case0201(vts, threshold);
case '1020':
return case0201(vts, threshold, 1);
case '0102':
return case0201(vts, threshold, 2);
case '2010':
return case0201(vts, threshold, 3);
}
}
這麼一來,就可以建立一個 contours 函式來取得全部的等值帶:
function contours(values, threshold) {
const corners = mapToCorners(values, threshold);
return flatMapToCells(corners)
.map(cell => isobands(cell, threshold))
.filter(lines => lines.length > 0)
.flat();
}
底下完整的範例,結合了〈Perlin 雜訊〉畫出的等值帶(這邊沒有使用 p5.js-widget,因為它載入太長的程式碼會有問題):
function setup() {
createCanvas(300, 300);
noLoop();
noStroke();
}
function draw() {
background(220);
const values = [];
for(let x = 0; x < width; x++) {
const row = [];
for(let y = 0; y < height; y++) {
const c = 255 * noise(x / 100, y / 100);
row.push(c);
}
values.push(row);
}
for(let t = 255; t > 0; t -= 20) {
fill(t, 255 - t, 0);
contours(values, {lower: t - 20, upper: t}).forEach(band => {
beginShape();
band.forEach(p => vertex(p.x, p.y));
endShape(CLOSE);
});
}
}
function contours(values, threshold) {
const corners = mapToCorners(values, threshold);
return flatMapToCells(corners)
.map(cell => isobands(cell, threshold))
.filter(lines => lines.length > 0)
.flat();
}
function mapToCorners(values, threshold) {
const corners = []
for(let r = 0; r < values.length; r++) {
const row = [];
for(let c = 0; c < values[r].length; c++) {
const v = values[r][c];
row.push({
vt : createVector(c, r, values[r][c]),
thresholdLabel: v > threshold.upper ? '2' : (v <= threshold.upper && v >= threshold.lower ? '1' : '0')
});
}
corners.push(row);
}
return corners;
}
function flatMapToCells(corners) {
const cells = [];
for(let r = 0; r < corners.length - 1; r++) {
for(let c = 0; c < corners[r].length - 1; c++) {
cells.push({
vts: [
corners[r][c].vt,
corners[r + 1][c].vt,
corners[r + 1][c + 1].vt,
corners[r][c + 1].vt
],
cornerCase: cornerCase([
corners[r][c],
corners[r + 1][c],
corners[r + 1][c + 1],
corners[r][c + 1]
])
});
}
}
return cells;
}
function cornerCase(corners) {
return corners.map(corner => corner.thresholdLabel).join('');
}
function case1222(cellVts, threshold, r = 0) {
const vts = rotated(cellVts, r);
const U = threshold.upper;
const L = threshold.lower;
return [[vts[0], inter_pt(vts[0], vts[1], U), inter_pt(vts[0], vts[3], U)]];
}
function case2111(cellVts, threshold, r = 0) {
const vts = rotated(cellVts, r);
const U = threshold.upper;
const L = threshold.lower;
return [[inter_pt(vts[0], vts[3], U), inter_pt(vts[0], vts[1], U), vts[1], vts[2], vts[3]]];
}
function case0111(cellVts, threshold, r = 0) {
const vts = rotated(cellVts, r);
const U = threshold.upper;
const L = threshold.lower;
return [[inter_pt(vts[0], vts[3], L), inter_pt(vts[0], vts[1], L), vts[1], vts[2], vts[3]]];
}
function case1000(cellVts, threshold, r = 0) {
const vts = rotated(cellVts, r);
const U = threshold.upper;
const L = threshold.lower;
return [[vts[0], inter_pt(vts[0], vts[1], L), inter_pt(vts[0], vts[3], L)]];
}
function case0222(cellVts, threshold, r = 0) {
const vts = rotated(cellVts, r);
const U = threshold.upper;
const L = threshold.lower;
return [[inter_pt(vts[0], vts[3], L), inter_pt(vts[0], vts[1], L), inter_pt(vts[0], vts[1], U), inter_pt(vts[0], vts[3], U)]];
}
function case2211(cellVts, threshold, r = 0) {
const vts = rotated(cellVts, r);
const U = threshold.upper;
const L = threshold.lower;
return [[inter_pt(vts[0], vts[3], U), inter_pt(vts[1], vts[2], U), vts[2], vts[3]]];
}
function case0011(cellVts, threshold, r = 0) {
const vts = rotated(cellVts, r);
const U = threshold.upper;
const L = threshold.lower;
return [[inter_pt(vts[0], vts[3], L), inter_pt(vts[1], vts[2], L), vts[2], vts[3]]];
}
function case2000(cellVts, threshold, r = 0) {
const vts = rotated(cellVts, r);
const U = threshold.upper;
const L = threshold.lower;
return [[inter_pt(vts[0], vts[3], U), inter_pt(vts[0], vts[1], U), inter_pt(vts[0], vts[1], L), inter_pt(vts[0], vts[3], L)]];
}
function case2201(cellVts, threshold, r = 0) {
const vts = rotated(cellVts, r);
const U = threshold.upper;
const L = threshold.lower;
return [[inter_pt(vts[0], vts[3], U), inter_pt(vts[1], vts[2], U), inter_pt(vts[1], vts[2], L), inter_pt(vts[2], vts[3], L), vts[3]]];
}
function case2210(cellVts, threshold, r = 0) {
const vts = rotated(cellVts, r);
const U = threshold.upper;
const L = threshold.lower;
return [[inter_pt(vts[0], vts[3], U), inter_pt(vts[1], vts[2], U), vts[2], inter_pt(vts[2], vts[3], L), inter_pt(vts[0], vts[3], L)]];
}
function case0012(cellVts, threshold, r = 0) {
const vts = rotated(cellVts, r);
const U = threshold.upper;
const L = threshold.lower;
return [[inter_pt(vts[0], vts[3], L), inter_pt(vts[1], vts[2], L), vts[2], inter_pt(vts[2], vts[3], U), inter_pt(vts[0], vts[3], U)]];
}
function case0021(cellVts, threshold, r = 0) {
const vts = rotated(cellVts, r);
const U = threshold.upper;
const L = threshold.lower;
return [[inter_pt(vts[0], vts[3], L), inter_pt(vts[1], vts[2], L), inter_pt(vts[1], vts[2], U), inter_pt(vts[2], vts[3], U), vts[3]]];
}
function case1120(cellVts, threshold, r = 0) {
const vts = rotated(cellVts, r);
const U = threshold.upper;
const L = threshold.lower;
return [[vts[0], vts[1], inter_pt(vts[1], vts[2], U), inter_pt(vts[2], vts[3], U), inter_pt(vts[2], vts[3], L), inter_pt(vts[0], vts[3], L)]];
}
function case1102(cellVts, threshold, r = 0) {
const vts = rotated(cellVts, r);
const U = threshold.upper;
const L = threshold.lower;
return [[vts[0], vts[1], inter_pt(vts[1], vts[2], L), inter_pt(vts[2], vts[3], L), inter_pt(vts[2], vts[3], U), inter_pt(vts[0], vts[3], U)]];
}
function case1012(cellVts, threshold, r = 0) {
const vts = rotated(cellVts, r);
const U = threshold.upper;
const L = threshold.lower;
return [[vts[0], inter_pt(vts[0], vts[1], L), inter_pt(vts[1], vts[2], L), vts[2], inter_pt(vts[2], vts[3], U), inter_pt(vts[0], vts[3], U)]];
}
function case2121(cellVts, threshold, r = 0) {
const vts = rotated(cellVts, r);
const U = threshold.upper;
const L = threshold.lower;
const cp = center(vts);
if(cp.z > U) {
return [
[inter_pt(vts[0], vts[1], U), vts[1], inter_pt(vts[1], vts[2], U), inter_pt(vts[1], cp, U)], [inter_pt(vts[0], vts[3], U), inter_pt(cp, vts[3], U), inter_pt(vts[2], vts[3], U), vts[3]]
];
}
else if(cp.z <= U && cp.z >= L) {
return [
[inter_pt(vts[0], vts[3], U), inter_pt(vts[0], cp, U), inter_pt(vts[0], vts[1], U), vts[1], inter_pt(vts[1], vts[2], U), inter_pt(vts[2], cp, U), inter_pt(vts[2], vts[3], U), vts[3]]
];
}
return [
[inter_pt(vts[0], vts[3], U), inter_pt(vts[0], cp, U), inter_pt(vts[0], cp, L), inter_pt(vts[3], cp, L), inter_pt(vts[2], cp, L), inter_pt(vts[2], cp, U), inter_pt(vts[2], vts[3], U), vts[3]],
[inter_pt(vts[2], vts[1], U), inter_pt(vts[2], cp, U), inter_pt(vts[2], cp, L), inter_pt(vts[1], cp, L), inter_pt(vts[0], cp, L), inter_pt(vts[0], cp, U), inter_pt(vts[0], vts[1], U), vts[1]]
];
}
function case0101(cellVts, threshold, r = 0) {
const vts = rotated(cellVts, r);
const U = threshold.upper;
const L = threshold.lower;
const cp = center(vts);
if(cp.z < L) {
return [
[inter_pt(vts[0], vts[1], L), vts[1], inter_pt(vts[1], vts[2], L), inter_pt(vts[1], cp, L)], [inter_pt(vts[0], vts[3], L), inter_pt(cp, vts[3], L), inter_pt(vts[2], vts[3], L), vts[3]]
];
}
else if(cp.z <= U && cp.z >= L) {
return [
[inter_pt(vts[0], vts[3], L), inter_pt(vts[0], cp, L), inter_pt(vts[0], vts[1], L), vts[1], inter_pt(vts[1], vts[2], L), inter_pt(vts[2], cp, L), inter_pt(vts[2], vts[3], L), vts[3]]
];
}
return [
[inter_pt(vts[0], vts[3], L), inter_pt(vts[0], cp, L), inter_pt(vts[0], cp, U), inter_pt(vts[3], cp, U), inter_pt(vts[2], cp, U), inter_pt(vts[2], cp, L), inter_pt(vts[2], vts[3], L), vts[3]],
[inter_pt(vts[2], vts[1], L), inter_pt(vts[2], cp, L), inter_pt(vts[2], cp, U), inter_pt(vts[1], cp, U), inter_pt(vts[0], cp, U), inter_pt(vts[0], cp, L), inter_pt(vts[0], vts[1], L), vts[1]]
];
}
function case2020(cellVts, threshold, r = 0) {
const vts = rotated(cellVts, r);
const U = threshold.upper;
const L = threshold.lower;
const cp = center(vts);
if(cp.z > U) {
return [
[inter_pt(vts[0], vts[1], U), inter_pt(vts[0], vts[1], L), inter_pt(vts[1], cp, L), inter_pt(vts[1], vts[2], L), inter_pt(vts[1], vts[2], U), inter_pt(vts[1], cp, U)],
[inter_pt(vts[0], vts[3], U), inter_pt(cp, vts[3], U), inter_pt(vts[2], vts[3], U), inter_pt(vts[2], vts[3], L), inter_pt(cp, vts[3], L), inter_pt(vts[0], vts[3], L)]
];
}
else if(cp.z <= U && cp.z >= L) {
return [
[inter_pt(vts[0], vts[3], U), inter_pt(vts[0], cp, U), inter_pt(vts[0], vts[1], U), inter_pt(vts[0], vts[1], L), inter_pt(vts[1], cp, L), inter_pt(vts[1], vts[2], L), inter_pt(vts[1], vts[2], L), inter_pt(cp, vts[2], U), inter_pt(vts[2], vts[3], U), inter_pt(vts[2], vts[3], L), inter_pt(cp, vts[3], L), inter_pt(vts[0], vts[3], L)]
];
}
return [
[inter_pt(vts[0], vts[3], U), inter_pt(vts[0], cp, U), inter_pt(vts[0], vts[1], U), inter_pt(vts[0], vts[1], L), inter_pt(vts[0], cp, L), inter_pt(vts[0], vts[3], L)],
[inter_pt(vts[2], vts[3], L), inter_pt(cp, vts[2], L), inter_pt(vts[1], vts[2], L), inter_pt(vts[1], vts[2], U), inter_pt(cp, vts[2], U), inter_pt(vts[2], vts[3], U)]
];
}
function case2021(cellVts, threshold, r = 0) {
const vts = rotated(cellVts, r);
const U = threshold.upper;
const L = threshold.lower;
const cp = center(vts);
if(cp.z > U) {
return [
[inter_pt(vts[0], vts[1], U), inter_pt(vts[0], vts[1], L), inter_pt(vts[1], cp, L), inter_pt(vts[1], cp, U), inter_pt(vts[1], vts[2], L), inter_pt(vts[1], vts[2], U), inter_pt(vts[1], cp, U)],
[inter_pt(vts[0], vts[3], U), inter_pt(cp, vts[3], U), inter_pt(vts[2], vts[3], U), vts[3]]
];
}
else if(cp.z <= U && cp.z >= L) {
return [
[inter_pt(vts[0], vts[3], U), inter_pt(vts[0], cp, U), inter_pt(vts[0], vts[1], U), inter_pt(vts[0], vts[1], L), inter_pt(vts[1], cp, L), inter_pt(vts[1], vts[2], L), inter_pt(vts[1], vts[2], U), inter_pt(vts[2], cp, U), inter_pt(vts[2], vts[3], U), vts[3]]
];
}
return [
[inter_pt(vts[0], vts[3], U), inter_pt(vts[0], cp, U), inter_pt(vts[0], vts[1], U), inter_pt(vts[0], vts[1], L), inter_pt(vts[0], cp, L), inter_pt(vts[3], cp, L), inter_pt(vts[2], cp, L), inter_pt(vts[1], vts[2], L), inter_pt(vts[1], vts[2], U), inter_pt(vts[2], cp, U), inter_pt(vts[2], vts[3], U), vts[3]]
];
}
function case0201(cellVts, threshold, r = 0) {
const vts = rotated(cellVts, r);
const U = threshold.upper;
const L = threshold.lower;
const cp = center(vts);
if(cp.z < L) {
return [
[inter_pt(vts[0], vts[1], L), inter_pt(vts[1], cp, U), inter_pt(vts[0], vts[1], U), inter_pt(vts[1], vts[2], U), inter_pt(vts[1], vts[2], U), inter_pt(vts[1], cp, L)],
[inter_pt(vts[0], vts[3], L), inter_pt(cp, vts[3], L), inter_pt(vts[2], vts[3], L), vts[3]]
];
}
else if(cp.z <= U && cp.z >= L) {
return [
[inter_pt(vts[0], vts[3], L), inter_pt(vts[0], cp, L), inter_pt(vts[0], vts[1], L), inter_pt(vts[0], vts[1], U), inter_pt(vts[1], cp, U), inter_pt(vts[1], vts[2], U), inter_pt(vts[1], vts[2], L), inter_pt(vts[2], cp, L), inter_pt(vts[2], vts[3], L), vts[3]]
];
}
return [
[inter_pt(vts[0], vts[3], L), inter_pt(vts[0], cp, L), inter_pt(vts[0], vts[1], L), inter_pt(vts[0], vts[1], U), inter_pt(vts[0], cp, U), inter_pt(vts[3], cp, U), inter_pt(vts[2], cp, U), inter_pt(vts[1], vts[2], U), inter_pt(vts[1], vts[2], L), inter_pt(vts[2], cp, L), inter_pt(vts[2], vts[3], L), vts[3]]
];
}
function rotated(vts, n) {
return vts.slice(n, vts.length).concat(vts.slice(0, n));
}
function isobands(cell, threshold) {
const vts = cell.vts;
const U = threshold.upper;
const L = threshold.lower;
switch(cell.cornerCase) {
case '2222': case '0000':
return [];
case '1111':
return [vts];
// 底下每四個一組,逆時針轉動頂點就可以了
case '1222':
return case1222(vts, threshold);
case '2122':
return case1222(vts, threshold, 1);
case '2212':
return case1222(vts, threshold, 2);
case '2221':
return case1222(vts, threshold, 3);
case '2111':
return case2111(vts, threshold);
case '1211':
return case2111(vts, threshold, 1);
case '1121':
return case2111(vts, threshold, 2);
case '1112':
return case2111(vts, threshold, 3);
case '0111':
return case0111(vts, threshold);
case '1011':
return case0111(vts, threshold, 1);
case '1101':
return case0111(vts, threshold, 2);
case '1110':
return case0111(vts, threshold, 3);
case '1000':
return case1000(vts, threshold);
case '0100':
return case1000(vts, threshold, 1);
case '0010':
return case1000(vts, threshold, 2);
case '0001':
return case1000(vts, threshold, 3);
case '0222':
return case0222(vts, threshold);
case '2022':
return case0222(vts, threshold, 1);
case '2202':
return case0222(vts, threshold, 2);
case '2220':
return case0222(vts, threshold, 3);
case '2211':
return case2211(vts, threshold);
case '1221':
return case2211(vts, threshold, 1);
case '1122':
return case2211(vts, threshold, 2);
case '2112':
return case2211(vts, threshold, 3);
case '0011':
return case0011(vts, threshold);
case '1001':
return case0011(vts, threshold, 1);
case '1100':
return case0011(vts, threshold, 2);
case '0110':
return case0011(vts, threshold, 3);
case '2000':
return case2000(vts, threshold);
case '0200':
return case2000(vts, threshold, 1);
case '0020':
return case2000(vts, threshold, 2);
case '0002':
return case2000(vts, threshold, 3);
case '2201':
return case2201(vts, threshold);
case '1220':
return case2201(vts, threshold, 1);
case '0122':
return case2201(vts, threshold, 2);
case '2012':
return case2201(vts, threshold, 3);
case '2210':
return case2210(vts, threshold);
case '0221':
return case2210(vts, threshold, 1);
case '1022':
return case2210(vts, threshold, 2);
case '2102':
return case2210(vts, threshold, 3);
case '0012':
return case0012(vts, threshold);
case '2001':
return case0012(vts, threshold, 1);
case '1200':
return case0012(vts, threshold, 2);
case '0120':
return case0012(vts, threshold, 3);;
case '0021':
return case0021(vts, threshold);
case '1002':
return case0021(vts, threshold, 1);
case '2100':
return case0021(vts, threshold, 2);
case '0210':
return case0021(vts, threshold, 3);
case '1120':
return case1120(vts, threshold);
case '0112':
return case1120(vts, threshold, 1);
case '2011':
return case1120(vts, threshold, 2);
case '1201':
return case1120(vts, threshold, 3);
case '1102':
return case1102(vts, threshold);
case '2110':
return case1102(vts, threshold, 1);
case '0211':
return case1102(vts, threshold, 2);
case '1021':
return case1102(vts, threshold, 3);
case '1012':
return case1012(vts, threshold);
case '2101':
return case1012(vts, threshold, 1);
case '1210':
return case1012(vts, threshold, 2);
case '0121':
return case1012(vts, threshold, 3);
case '2121':
return case2121(vts, threshold);
case '1212':
return case2121(vts, threshold, 1);
case '0101':
return case0101(vts, threshold);
case '1010':
return case0101(vts, threshold, 1);
case '2020':
return case2020(vts, threshold);
case '0202':
return case2020(vts, threshold, 1);
case '2021':
return case2021(vts, threshold);
case '1202':
return case2021(vts, threshold, 1);
case '2120':
return case2021(vts, threshold, 2);
case '0212':
return case2021(vts, threshold, 3);
case '0201':
return case0201(vts, threshold);
case '1020':
return case0201(vts, threshold, 1);
case '0102':
return case0201(vts, threshold, 2);
case '2010':
return case0201(vts, threshold, 3);
}
}
function inter_pt(v1, v2, threshold) {
return p5.Vector.lerp(v1, v2, (threshold - v1.z) / (v2.z - v1.z));
}
function center(vts) {
return p5.Vector.add(vts[0], vts[1]).add(vts[2]).add(vts[3]).div(4);
}
等值線、帶並不單只是用於等高線、等壓線之類的場合,我曾經實現了 OpenSCAD 版本的 Marching squares,並寫了個簡單的切片程式 Image slicer,可以從圖片生成可列印的三維線稿模型: