iT邦幫忙

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

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

Day 23 : 網格細分實作

前言

本篇介紹的是邊分裂的方式,網格細分需要大量使用到先前所學到的拓樸觀念,實作也與單純的觀念理解上有些許的不同,在撰寫的複雜度也有所提升因此分為兩次說明。

https://ithelp.ithome.com.tw/upload/images/20180110/20107175d6I01UwdsO.png
本次要實作的邊分裂方式

細分函數建立

首先在 index.html 中加入一顆按鈕。

<div id="refinement" class="refinement" style="cursor: pointer;"></div>

並且在 index.scss 建立這顆按鈕的樣式。

.refinement {
    position: absolute;
    background-image: url('../images/refinement_white_18dp.png');
    width: 72px;
    height: 72px;
    top: 72px;
    &:hover {
        left: 2px;
    }
}

建立一個 Refinement 模組,並且加入一個用於設定Viewer的函數。

var Refinement = function () {
    this.viewer = null;
};

Refinement.prototype.setViewer = function (viewer) {
    var self = this;
    self.viewer = viewer;
};

module.exports = Refinement;

接著在 index.js 加入宣告,並且設定 viewer 函數進行綁定。

import Refinement from './Refinement';

refinement.setViewer(viewer);

最後將按鈕綁定一個 midedge 事件,用來建立中間邊線方法。

$("#refinement").onclick = function (e) {
    refinement.midedge();
};

整理基礎資訊

首先要開始建立 midedge 函數,先準備好 viewer 中的 geometry 與 topology 備用。

Refinement.prototype.midedge = function () {
    var self = this;

    var geometry = self.viewer.meshs[0].geometry;
    var topology = self.viewer.meshs[0].topology;

    // Do Something
};

接著要搜尋所有的邊並且都進行網格細分,因此建立一個迴圈跑完所有的邊。

var len = topology.edge.length;
var i = 0;
for (i = 0; i < len; i++) {
    // Do Something
}

由於需要的參數會有很多,需要先整理起來備用避免使用混亂。

var edge = topology.edge[i];
var center = edge.center;
var vertexIDs = edge.vertexIDs;
var faceIDs = edge.faceIDs;

整理頂點資訊

搭配前回筆者繪製的示意圖,透過 topology 函數中的 creat 方法建立新的 vertex 頂點 D ,並且複製邊線的中心點座標到頂點 D

var VertexD = topology.create('vertex');
VertexD.vector3 = center.clone();

透過邊線內的面資訊找到兩個面的物件,整理好備用。

var faceTop = topology.face[faceIDs[0]];
var faceBottom = topology.face[faceIDs[1]];

最後再整理一下每個舊有的頂點ID,其中頂點 C 與頂點 E 需要由面拓樸找到有三個頂點,再扣掉邊拓樸儲存的兩個頂點,剩下的頂點就是我們需要的頂點 CE

var vertexIdA = vertexIDs[0];
var vertexIdB = vertexIDs[1];
var vertexIdC = faceTop.vertexIDs.find(
    (element, index, array) => (vertexIDs.indexOf(element) === -1)
);
var vertexIdD = VertexD.ID;
var vertexIdE = faceBottom.vertexIDs.find(
    (element, index, array) => (vertexIDs.indexOf(element) === -1)
);

整理邊線資訊

首先整理舊有的邊線,在 topology.js 中的 edgeIDWithVertices 函數可以幫助我們透過頂點的ID查找邊線。

var edgeAC = topology.edgeIDWithVertices(vertexIdA, vertexIdC);
var edgeBC = topology.edgeIDWithVertices(vertexIdB, vertexIdC);
var edgeAE = topology.edgeIDWithVertices(vertexIdA, vertexIdE);
var edgeBE = topology.edgeIDWithVertices(vertexIdB, vertexIdE);

接著要創造新的四條邊線,兩條是由線段 AB 所拆分出來的 ADBD;另外兩條是新增的線段 CDED

var edgeAD = topology.create('edge').ID;
var edgeBD = topology.create('edge').ID;
var edgeCD = topology.create('edge').ID;
var edgeED = topology.create('edge').ID;

整理面資訊

四個面都是需要重新創造的面,舊有的之後會直接刪除。

var faceACD = topology.create('face').ID;
var faceCBD = topology.create('face').ID;
var faceAED = topology.create('face').ID;
var faceEBD = topology.create('face').ID;

建立新的三角網格

在建立新的網格之前,為了確認是否與舊的網格方向一致,需要先計算舊有的網格法向量,與其中一片新的網格法向量,首先建立一個法向量計算函數。

Refinement.prototype.computeNormal = function (vA, vB, vC) {
    var cb = new THREE.Vector3();
    var ab = new THREE.Vector3();
    cb.subVectors(vC, vB);
    ab.subVectors(vA, vB);
    cb.cross(ab);
    return cb.normalize();
};

接著筆者取上方的網格與第一片新網格進行法向量的計算。

//計算原始網格法向量
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);
    topology.addTriangleData(vertexIdA, vertexIdD, vertexIdE, edgeAD, edgeED, edgeAE, faceAED);
    topology.addTriangleData(vertexIdE, vertexIdD, vertexIdB, edgeED, edgeBD, edgeBE, faceEBD);
    topology.addTriangleData(vertexIdB, vertexIdD, vertexIdC, edgeBD, edgeCD, edgeBC, faceCBD);
} else {
    topology.addTriangleData(vertexIdC, vertexIdA, vertexIdD, edgeAC, edgeAD, edgeCD, faceACD);
    topology.addTriangleData(vertexIdA, vertexIdE, vertexIdD, edgeAE, edgeED, edgeAD, faceAED);
    topology.addTriangleData(vertexIdE, vertexIdB, vertexIdD, edgeBE, edgeBD, edgeED, faceEBD);
    topology.addTriangleData(vertexIdB, vertexIdC, vertexIdD, edgeBC, edgeCD, edgeBD, faceCBD);
}

迴圈的最後將舊的拓樸資訊刪除,就完成完整的拓樸資訊建構。

topology.remove(edge);
topology.remove(faceTop);
topology.remove(faceBottom);

最後透過 topology.jsconvertToGeometry 函數生成新的 Geometry 後,將舊的 mesh 刪除,再增加新的 Geometry 就完成了。

var newGeometry = topology.convertToGeometry();
self.viewer.scene.remove(self.viewer.meshs[0]);
self.viewer.add(newGeometry);

修正 Viewer 模組

由於新的三角網格是 Geometry 資料格式,並不是 BufferGeometry 因此需要在 Viewer.jsadd 函數中判斷一下型態。

    var geometry = null;
    if (bufferGeometry.constructor.name === 'BufferGeometry') {
        geometry = new THREE.Geometry().fromBufferGeometry(bufferGeometry);
    } else {
        geometry = bufferGeometry;
    }

topology.jsconvertToGeometry 函數會將顏色資訊物件重複放進去,造成即時高亮網格功能出錯,因此需要改為拷貝的方式將基本顏色重新覆蓋掉。

geometry.faces[i].color = baseColor.clone();

補充說明一下,此範例是全域網格進行細分,之前的 monkey.stl 檔案在部分區域會有一些非封閉網格,導致細分計算失敗的問題是正常的,可以改用 CubeBinary.STL 或是 hat.stl 檔案都可以正常計算。

https://lh3.googleusercontent.com/N_scE3Ml9hFwX06AiBr-dos6N3VdR7xVlyG97xqITKBxAymr5aF6lqr-U62Fjv_c4aPFc33T_fHn9gJffQ=s0-tmp.gif
使用 CubeBinary.STL 實體模型測試結果

範例模型下載位置

https://github.com/QQBoxy/threecad/tree/master/models

線上範例

https://qqboxy.github.io/threecad/public/example18.html

範例原始碼

https://github.com/QQBoxy/threecad/blob/master/client/example18/Editor.js

後記

這次內容比較多,花了超出預算的時間在撰寫,希望大家能夠好好吸收,這篇範例的原理應該是花點時間就能夠想通的。


上一篇
Day 22 : 三角網格細分
下一篇
Day 24 : 封閉網格與非封閉網格
系列文
在Three.js探索CAD的奧秘30

尚未有邦友留言

立即登入留言