模組化,也就是Module化,是將我們所需要的功能建立成一個模組增加重用性。在我們的應用是為了建立一個CAD平台,將Three.js模組化可以更方便的擴充各種CAD功能,才能夠在功能越來越複雜的情況下還能夠有條理的進行擴充。本文中筆者習慣使用prototype的方式分離功能項,後續將會大量使用此方法進行函數的整理。
延續上一次的程式,拷貝一份example5,在 /client/example5/ 目錄下建立一個 Viewer.js 檔案,在裡頭建立一個Viewer的函數,並且加入一個引數 container 用來引入要提供給Three.js創建canvas的容器。
var Viewer = function (container) {
this.container = container;
this.scene = null;
this.camera = null;
this.renderer = null;
this.light = null;
this.cameraFov = 60;
this.meshs = [];
};
從原本的繪圖程式中,抽出場景設定、相機設定、渲染器設定、光源設定、執行動畫整理到init內,其中anime只保留函式呼叫。
Viewer.prototype.init = function () {
var self = this;
//場景設定
self.scene = new THREE.Scene();
//相機設定
self.camera = new THREE.PerspectiveCamera(self.cameraFov, window.innerWidth / window.innerHeight, 0.1, 1000);
//渲染器設定
self.renderer = new THREE.WebGLRenderer();
self.renderer.setSize(window.innerWidth, window.innerHeight);
self.container.appendChild(self.renderer.domElement);
//光源設定
var ambientLight = new THREE.AmbientLight(0xffffff, 0.6);
self.scene.add(ambientLight); //加入環境光源
var pointLight = new THREE.PointLight(0xffffff, 0.8, 0);
pointLight.position.set(50, 50, 50);
self.scene.add(pointLight); //加入點光源
self.camera.position.z = 50;
//執行動畫
self.animate();
};
接著建立一個animate函數將原本的程式都整理進去,但由於requestAnimationFrame函數是隸屬於window底下的,它在執行的時候不會帶著我們的Viewer物件進去,因此要將this給bind進去才能夠正常抓到場景、相機跟渲染器。
Viewer.prototype.animate = function () {
var self = this;
//循環動畫
requestAnimationFrame(self.animate.bind(self));
self.renderer.render(self.scene, self.camera);
};
設置好基本的環境以後,要新增一個add函數將用來加入三角網格,並且函數的引數加入geometry,然後將材質與網格物件設定放進來。
Viewer.prototype.add = function (geometry) {
var self = this;
//材質設定
var material = new THREE.MeshPhongMaterial({
color: Math.floor(Math.random() * 16777215) //Random color
});
//網格物件設定
var mesh = new THREE.Mesh(geometry, material);
self.scene.add(mesh);
};
為了將Viewer模組化,因此在程式的最下方加上一個module.exports,這樣我們就可以在別的程式將Viewer給import進來。
module.exports = Viewer;
接著打開 /server/views/example5/index.html 修改HTML將原本的app標籤刪掉,我們改創建一個openfile按鈕與viewer容器提供放置功能。
<div>
<label for="openfile">
<div class="openfile" style="cursor: pointer;"></div>
</label>
<input id="openfile" type="file" accept=".stl" style="display: none;" />
</div>
<div id="viewer"></div>
回到 /client/example5/index.js 主程式,將創建好的Viewer給import進來,接著new一個Viewer函數並提供它一個HTML容器,並且先給viewer執行初始化。
import Viewer from './Viewer';
var viewer = new Viewer($("#viewer"));
viewer.init();
最後將原本onchange函式指派給openfile標籤,並且將呼叫draw函數的部分改為呼叫viewer.add函數,這樣就可以完成基本的模組化,如下圖。
$("#openfile").onchange = function (e) {
var file = e.target.files[0];
if (e.target.files.length == 1) {
var reader = new FileReader();
reader.onloadend = function (e) {
var loader = new STLLoader();
var geometry = loader.parse(e.target.result);
viewer.add(geometry);
};
reader.readAsBinaryString(file);
}
};
模組化後讀入monkey.stl
在前一個階段已經將功能模組化了,但是還欠缺完善讀取多筆STL檔案的能力,首先是場景內的網格管理方式,眼尖的朋友可能已經發現在函數宣告的時候,有一個meshs參數並沒有被使用到,接著我們要將Viewer模組中加入這部分,請在add函數最尾端加上push網格,如此一來就能夠在Viewer內任意的使用mesh物件了。
self.meshs.push(mesh);
接著建立一個清除用的函數 clear,透過一個簡單的for迴圈就能夠移除場景中的網格物件mesh。
Viewer.prototype.clear = function (geometry) {
var self = this;
for (var key in self.meshs) {
self.scene.remove(self.meshs[key]);
}
};
然後同時也在 /server/views/example5/index.html 加入一顆用來清除的按鈕。
<div id="deletefile" class="deletefile" style="cursor: pointer;"></div>
接著我們將按鈕綁定clear函數。
$("#deletefile").onclick = function (e) {
viewer.clear();
};
最後要注意的是,onchange函式中需要加入一行,讓每一次加入geometry後可以清除選取的檔案,下一次的選取檔案才會再進入到onchange函式中喔,完成的結果如下圖:
$("#openfile").value = "";
讀取、再讀取然後清除的範例
為了讓畫布能夠隨著調整視窗大小而變動,我們在init加入一個resize函數監視瀏覽器事件。
window.addEventListener('resize', self.onResize.bind(self), false);
並放入onResize事件提供resize呼叫使用,在這個函數中更新相機的比例與渲染器的大小。
Viewer.prototype.onResize = function () {
var self = this;
self.camera.aspect = window.innerWidth / window.innerHeight;
self.camera.updateProjectionMatrix();
self.renderer.setSize(window.innerWidth, window.innerHeight);
};
視窗大小變更效果
https://github.com/QQBoxy/threecad/tree/master/client/example5
https://github.com/QQBoxy/threecad/tree/master/models
這一次的內容稍微多了一些,大家是不是還能夠正常吸收呢? 請加緊腳步跟上我,目前還是前導環境,我們還沒有進入CAD更多元的應用呢,所以目前為止的內容務必要好好瞭解清楚喔!