iT邦幫忙

2018 iT 邦幫忙鐵人賽
DAY 24
0
Modern Web

在Three.js探索CAD的奧秘系列 第 24

Day 24 : 封閉網格與非封閉網格

前言

封閉網格,是指實體模型,其網格是完全包覆並佈滿整個模型表面,通常用於需要真實製造出完整實體的情況;非封閉網格,則是指薄殼模型,其網格不一定要完全包覆整個模型表面,通常用於不需要真實製造只需要顯示的情況。

封閉網格

上回我們建立的網格細分演算法,只能用於完全封閉的網格,其原因是封閉的網格模型中,所有的邊線都一定會有兩個鄰面。
https://ithelp.ithome.com.tw/upload/images/20180110/20107175d6I01UwdsO.png
如同先前的示意圖,每個邊都擁有上下兩個面

非封閉網格

非封閉網格,可能遇到邊線剛好是模型的邊緣,就會缺少某一個鄰面的資訊。
https://ithelp.ithome.com.tw/upload/images/20180110/20107175qkoMIjdKZg.png
如同先前的示意圖,該邊只有一個鄰近的面

相容兩種網格模型

為了網格細分演算法可以通吃兩種不同的網格模型,我們需要改寫前回的程式碼。
首先要在 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 非封閉模型正常進行網格細分。

https://lh3.googleusercontent.com/i17QpWsrXa-dNJymYmDVRklxmhZC08yXAjM8t97HucVEpYCTFjNdRGGtxLWjx845z1HS3YUmj5oOwItZPQ=s0-tmp.gif
讀取非封閉網格檔案 monkey.stl 的效果

同場加映,修正網格塗抹效果

由於先前的塗抹效果並沒有使用到 center 資訊,所以筆者並沒有將所有中心點重新計算,而本次網格細分功能都會用到 center 資訊,因此就會導致網格位置的計算錯誤,所以就要來還技術債啦。
https://lh3.googleusercontent.com/o80Dlin9HJxmDSN5TeEJrcVzi2Qf_8377ZirpQ7YhCJMFE3GdO74xK1GxhdEVjL0BkV1ySalm9QC0GioGA=s0-tmp.gif
未修正前網格細分計算錯誤

首先在 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://lh3.googleusercontent.com/pFOz_WILNa5uvZ3Yf_orf2vA1JDxTdQVtV1fmSe5d5jHi6hFiOfbU2q2mzl_icLWd7DoxTHlfhbpVTYQ9A=s0-tmp.gif
修正後網格細分正確計算

範例模型下載位置

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

後記

這次的範例非常簡單,但是還是需要修改一下,才能夠更彈性應用於不同模型。另外也修正了中心點的座標問題,大家對於網格的操作手法也越來越熟練了嗎。


上一篇
Day 23 : 網格細分實作
下一篇
Day 25 : 球範圍選擇
系列文
在Three.js探索CAD的奧秘30

尚未有邦友留言

立即登入留言