封閉網格,是指實體模型,其網格是完全包覆並佈滿整個模型表面,通常用於需要真實製造出完整實體的情況;非封閉網格,則是指薄殼模型,其網格不一定要完全包覆整個模型表面,通常用於不需要真實製造只需要顯示的情況。
上回我們建立的網格細分演算法,只能用於完全封閉的網格,其原因是封閉的網格模型中,所有的邊線都一定會有兩個鄰面。
如同先前的示意圖,每個邊都擁有上下兩個面
非封閉網格,可能遇到邊線剛好是模型的邊緣,就會缺少某一個鄰面的資訊。
如同先前的示意圖,該邊只有一個鄰近的面
為了網格細分演算法可以通吃兩種不同的網格模型,我們需要改寫前回的程式碼。
首先要在 midedge 函數中加入判斷機制,利用簡單的長度資訊就可以輕鬆判斷是否為封閉。
var isClosed = (faceIDs.length > 1);
接著要修改所有關於下方的面以及E點的建立資訊,都加上封閉判斷。
faceTop = topology.face[faceIDs[0]];
if (isClosed) {
faceBottom = topology.face[faceIDs[1]];
}
//取得頂點ID
vertexIdA = vertexIDs[0];
vertexIdB = vertexIDs[1];
vertexIdC = faceTop.vertexIDs.find(
(element, index, array) => (vertexIDs.indexOf(element) === -1)
);
vertexIdD = VertexD.ID;
if (isClosed) {
vertexIdE = faceBottom.vertexIDs.find(
(element, index, array) => (vertexIDs.indexOf(element) === -1)
);
}
//舊有的邊線
edgeAC = topology.edgeIDWithVertices(vertexIdC, vertexIdA);
edgeBC = topology.edgeIDWithVertices(vertexIdB, vertexIdC);
if (isClosed) {
edgeAE = topology.edgeIDWithVertices(vertexIdA, vertexIdE);
edgeBE = topology.edgeIDWithVertices(vertexIdE, vertexIdB);
}
//創造新的邊線
edgeAD = topology.create('edge').ID;
edgeBD = topology.create('edge').ID;
edgeCD = topology.create('edge').ID;
if (isClosed) {
edgeDE = topology.create('edge').ID;
}
//創造新的面
faceACD = topology.create('face').ID;
faceCBD = topology.create('face').ID;
if (isClosed) {
faceAED = topology.create('face').ID;
faceEBD = topology.create('face').ID;
}
//計算原始網格法向量
var topNormal = self.computeNormal(
topology.vertex[faceTop.vertexIDs[0]].vector3,
topology.vertex[faceTop.vertexIDs[1]].vector3,
topology.vertex[faceTop.vertexIDs[2]].vector3
);
//計算第一個分割面法向量
var subNormal = self.computeNormal(
topology.vertex[vertexIdA].vector3,
topology.vertex[vertexIdC].vector3,
topology.vertex[vertexIdD].vector3
);
//Va, Vb, Vc, Eab, Ebc, Eca, F
if (topNormal.dot(subNormal) >= 0) {
topology.addTriangleData(vertexIdC, vertexIdD, vertexIdA, edgeCD, edgeAD, edgeAC, faceACD);
if (isClosed) {
topology.addTriangleData(vertexIdA, vertexIdD, vertexIdE, edgeAD, edgeDE, edgeAE, faceAED);
topology.addTriangleData(vertexIdE, vertexIdD, vertexIdB, edgeDE, edgeBD, edgeBE, faceEBD);
}
topology.addTriangleData(vertexIdB, vertexIdD, vertexIdC, edgeBD, edgeCD, edgeBC, faceCBD);
} else {
topology.addTriangleData(vertexIdC, vertexIdA, vertexIdD, edgeAC, edgeAD, edgeCD, faceACD);
if (isClosed) {
topology.addTriangleData(vertexIdA, vertexIdE, vertexIdD, edgeAE, edgeDE, edgeAD, faceAED);
topology.addTriangleData(vertexIdE, vertexIdB, vertexIdD, edgeBE, edgeBD, edgeDE, faceEBD);
}
topology.addTriangleData(vertexIdB, vertexIdC, vertexIdD, edgeBC, edgeCD, edgeBD, faceCBD);
}
topology.remove(edge);
topology.remove(faceTop);
if (isClosed) {
topology.remove(faceBottom);
}
如此一來就能夠相容封閉與非封閉兩種網格形式,這時候可以讀入 monkey.stl 非封閉模型正常進行網格細分。
讀取非封閉網格檔案 monkey.stl 的效果
由於先前的塗抹效果並沒有使用到 center 資訊,所以筆者並沒有將所有中心點重新計算,而本次網格細分功能都會用到 center 資訊,因此就會導致網格位置的計算錯誤,所以就要來還技術債啦。
未修正前網格細分計算錯誤
首先在 Editor 模組的 mouseDown 函數中加入呼叫一個新函數 computeCenter。
topology.computeCenter(nearest.ID);
此函數在原始的 topology.js 並不存在,因此我們可以打開自己加一下,首先是建立這個函數,並且宣告一些基本參數。
TOPOLOGY.Topology.prototype.computeCenter = function (vertexID) {
var i = 0;
var j = 0;
var k = 0;
var vertex = null;
var edge = null;
var face = null;
var vertices = this.vertex;
var edges = this.edge;
var faces = this.face;
var point = vertices[vertexID];
// Do Something
};
接著筆者參考內建的 computeCenters 全域計算功能,在這裡加入一個迴圈用來找尋每個拓樸邊,分別建立一個新的向量,並將兩個頂點向量相加後除以二,就會得到中心點的位置。
for (i = 0; i < point.edgeIDs.length;i++) {
edge = edges[point.edgeIDs[i]];
if (edge == null) continue;
edge.center = new THREE.Vector3(0, 0, 0);
edge.center.add(vertices[edge.vertexIDs[0]].vector3);
edge.center.add(vertices[edge.vertexIDs[1]].vector3);
edge.center.divideScalar(2);
}
最後再加入一個迴圈用來修正每個拓樸面的中心點,同樣建立一個空向量,將三個頂點向量相加後除以三,就會得到中心點的位置。
for (j = 0; j < point.faceIDs.length; j++) {
face = faces[point.faceIDs[j]];
if (face == null) continue;
face.center = new THREE.Vector3(0, 0, 0);
for (k = 0; k < face.vertexIDs.length; k++) {
face.center.add(vertices[face.vertexIDs[k]].vector3);
}
face.center.divideScalar(face.vertexIDs.length);
}
修正後網格細分正確計算
https://github.com/QQBoxy/threecad/tree/master/models
https://qqboxy.github.io/threecad/public/example19.html
https://github.com/QQBoxy/threecad/blob/master/client/example19/Editor.js
這次的範例非常簡單,但是還是需要修改一下,才能夠更彈性應用於不同模型。另外也修正了中心點的座標問題,大家對於網格的操作手法也越來越熟練了嗎。