所見即得的設計模型,是提供CAD設計者建立 操作手感 的一種方法,設計者可以透過所見即得的視覺感受,來決定要將模型調整到什麼樣的狀態。在技術上就是透過滑鼠拖動的同時即時渲染模型,並在放開滑鼠後再做完其它的運算以結束模型的修改。
無論是哪一種模型變形演算法,都需要清楚劃分出 開始設計、設計中、設計結束 這三種狀態,以下將透過簡單的網格變形功能來說明。首先對此範例來說 開始設計 在程式中就是 mouseDown 的事件,按下滑鼠時必須要完成的事情有: 當前要更新的模型對象、取得變形前的滑鼠位置、取得網格面變形前的狀態、網格頂點變形前的座標資訊,程式宣告如下:
this.currentObject = null;
this.preFace = new THREE.Face3();
this.preMouse = new THREE.Vector2();
this.preVertices = [];
因此就需要將 開始設計 的事件劃分出來,先將網格變形前的資訊暫存起來,由於程式碼與先前範例類似就不再重複說明。
Editor.prototype.mouseDown = function (viewer) {
var self = this;
var intersected = viewer.intersects[0];
self.enable = true;
self.viewer = viewer;
self.preFace.copy(intersected.face);
self.currentObject = viewer.meshs.find((mesh) => (intersected.object.id === mesh.id));
self.preMouse.copy(self.viewer.mouse);
var point = [self.preFace.a, self.preFace.b, self.preFace.c];
self.preVertices = [];
var vid = 0;
for (var v = 0; v < point.length; v++) {
vid = point[v];
var vertex = new THREE.Vector3();
vertex.copy(self.currentObject.geometry.vertices[vid]);
self.preVertices.push(vertex);
}
};
接下來需要建立第二個步驟 設計中 的事件,首先在 Editor 與 Viewer 模組中都添加一個 action 函數並且綁定在一起,並且在 animate 函數中增加呼叫它。
Viewer.prototype.animate = function () {
var self = this;
//...
self.action();
};
而 Editor 中的 action 函數為了完成所見即得的功能,需要即時的更新顯示網格點座標,但是要注意到不需要立刻重新計算網格向量,因為我們只是要暫時的顯示而已。在這裡我們是使用滑鼠拖動的距離作為變形強度係數,達成拖拉時會有即時變形的效果。
Editor.prototype.action = function () {
var self = this;
if(!self.enable) return;
var distance = self.preMouse.distanceTo(self.viewer.mouse) * 10;
var geometry = self.currentObject.geometry;
var topology = self.currentObject.topology;
var normal = self.preFace.normal;
var point = [self.preFace.a, self.preFace.b, self.preFace.c];
var vid = 0;
for (var v = 0; v < point.length; v++) {
vid = point[v];
var x = self.preVertices[v].x + normal.x * distance;
var y = self.preVertices[v].y + normal.y * distance;
var z = self.preVertices[v].z + normal.z * distance;
geometry.vertices[vid] = new THREE.Vector3(x, y, z);
topology.vertex[vid].vector3 = new THREE.Vector3(x, y, z);
}
//更新模型資訊
geometry.verticesNeedUpdate = true;
geometry.elementsNeedUpdate = true;
};
最後的步驟 設計結束 就需要建立在 mouseUp 事件中,網格面的法向量重新計算的部分就可以在最後再完成,如此一來就可以節省大量的網格即時計算時間,結果如下圖。
Editor.prototype.mouseUp = function (viewer) {
var self = this;
if(!self.enable) return;
self.enable = false;
var geometry = self.currentObject.geometry;
var topology = self.currentObject.topology;
var point = [self.preFace.a, self.preFace.b, self.preFace.c];
var faceIDs = [];
var vid = 0;
for (var v = 0; v < point.length; v++) {
vid = point[v];
faceIDs.push(...topology.vertex[vid].faceIDs);
}
//過濾重複的面
faceIDs = faceIDs.filter(function (face, index, arr) {
return arr.indexOf(face) === index;
});
//重新計算面的法向量
for (var i = 0; i < faceIDs.length; i++) {
var id = faceIDs[i];
var cb = new THREE.Vector3();
var ab = new THREE.Vector3();
var vA = geometry.vertices[geometry.faces[id].a];
var vB = geometry.vertices[geometry.faces[id].b];
var vC = geometry.vertices[geometry.faces[id].c];
cb.subVectors(vC, vB);
ab.subVectors(vA, vB);
cb.cross(ab);
geometry.faces[id].normal.copy(cb.normalize());
}
geometry.normalsNeedUpdate = true;
self.preVertices = [];
};
所見即得拖拉變形效果
https://github.com/QQBoxy/threecad/tree/master/models
https://qqboxy.github.io/threecad/public/example14.html
https://github.com/QQBoxy/threecad/blob/master/client/example14/Editor.js
感謝網友私底下的建議,因為目前為止都只有在前端運作,所以本次直接在 github 放了範例,讓大家可以所見即玩一下看效果,而不只是看看gif圖片動來動去囉。有興趣的朋友也不妨將clone整個專案到自己電腦玩玩看,如果有建議也歡迎發Repo給我,謝謝。