Three.js 開發機房(三)
之前三節都沒涉及到機房,只是一些零零散散的知識點,這一節我們就開始正式畫外牆。
首先我了明顯理解以下啥是牆?其實說白了就是一個長方體,長不確定,寬一般也就是40cm,高也就是兩米,這就是一個簡單的牆,當然很多牆上都有窗戶、門啥的,其實也就是在長方體的固定的位置掏個洞,然後放上我們需要方的東西,比如門,窗戶。
在畫牆之前我們需要對一個機房的俯檢視進行分析,就比如下面這張機房的圖片
(圖片來自網路)
就像圖片中顯示的一樣,這個機房非常標準,是個很標準的長方形機房,長900cm, 寬600cm,左側的牆體是玻璃隔斷,還有一扇門,
那好,我們就可以開幹了,首先我們要初始化一個機房的結構佈局的Json,注意門不能和窗戶重合,有門的地方窗戶需要分成門左邊和門右邊兩個陣列(當然你也可以寫多個判斷進行操作,但是比較麻煩)。
{ houseWidth: 900, // 房間長度 houseHeight: 600, // 房間寬 angle: 45, // 房間朝向 wall: [ {position:{x: 0, y: 0, endX: 900, endY: 0}, door: {isDoor: false}, windows: {isWindows:false}}, {position:{x: 900, y: 0, endX: 900, endY: 600}, door: {isDoor: false}, windows: {isWindows: false}}, {position:{x: 0, y: 600, endX: 900, endY: 600}, door: {isDoor: false}, windows: {isWindows:false}}, {position:{x: 0, y: 0, endX: 0, endY: 600}, door: {isDoor: true, doorNum: 2, door_PointL [{x: 0, y: 200, endX: 0, endY: 400, doorDirection: 2}]}, windows: {isWindows: true, windows__Point: [{x: 0, y: 0, endX: 0, endY: 150}, {x: 0, y: 450, endX: 0, endY: 600}]}} ] },
接下來我們開始畫地板,我們目前就將地板和機房大小做一樣:
createFloor() { let _self = this; this.imgRendering.load("地板的圖片", texture => { texture.wrapS = texture.wrapT = THREE.RepeatWrapping; texture.repeat.set(8, 8); var floorGeometry = new THREE.BoxGeometry(this.houseWidth, this.houseHeight, 1); var floorMaterial = new THREE.MeshBasicMaterial({ map: texture, side: THREE.DoubleSide }); floorMaterial.opacity = 1; floorMaterial.transparent = true; var floor = new THREE.Mesh(floorGeometry, floorMaterial); floor.position.y = 0; floor.rotation.x = Math.PI / 2; _self.scene.add(floor); }) }
執行效果如下圖:
紫色是我加給整個Html的顏色,主要是方便觀看地板,接下來我們就開始畫牆了,在畫牆之前我們先初始化一個畫長方體(窗寬高均預設為1)的函式:
initLambert() { var cubeGeometry = new THREE.BoxGeometry(1, 1, 1); this.initLambertMod = new THREE.Mesh(cubeGeometry, this.wallMatArray); };
封裝好之後我們在畫牆的時候就不用每畫一道牆就新建一個幾何體和材質,我們只需要克隆我們剛才初始化的牆體就好了
之後我們正式封裝具有具體長度、角度和位置在的牆
/** * 畫長方體 * @param { 長方體的長度 } width * @param { 長方體的高度 } height * @param { 長方體的厚度 } depth * @param { 長方體旋轉的角度 } angle * @param { 長方體的材質 } material * @param { 長方體的X軸座標 } x * @param { 長方體的Y軸座標 } y * @param { 長方體的Z軸座標 } z */ createLambert(width, height, depth, angle, material, x, y, z) { var code = this.initLambertMod.clone(); code.scale.set(width, height, depth) code.position.set(x, y, z); code.rotation.set(0, angle * Math.PI, 0); //-逆時針旋轉,+順時針 return code; };
這樣我們就將一個具有長寬高、方向、位置的長方體就畫出來了,
只是畫出來還不行,我們需要將資料和模型關聯起來,我們先對 this.data.wall 進行遍歷得到這道牆的具體資訊,是否有門窗,牆的起始點和結束點,知道了起始點和結束點,我們就能算出這道牆具體有多長,還有這道牆的角度
如上圖,有以上兩個點我們能得出該條線的資訊
長度:Math.sqrt(Math.pow(Math.abs(300 -0), 2) +Math.pow(Math.abs(0 -300), 2));
角度:Math.asin((300- 0) / (0 - 300)) / Math.PI
這樣我們就知道了該條線的具體資訊,下面我們就能畫牆了:
createHouseWall() { this.data.wall.map((item) => { var position = item.position; var w = position.endX - position.x; var h = position.endY - position.y; var x = (position.x + w / 2) - (this.houseWidth / 2); var z = (position.y + h / 2) - (this.houseHeight / 2); var width = Math.sqrt(Math.pow(w, 2) + Math.pow(h, 2)); var angle = Math.asin(h / width) / Math.PI; if (item.windows.isWindows || item.door.isDoor) { // 有窗戶或有門或都有 } else { // 沒門、沒窗戶 let code = this.createLambert(width, 200, 10, angle, this.matArrayB, x, 100, z); this.scene.add(code); } }); };
執行完我們就能看到如下圖這樣的結果了
還差一面牆,上面既有門又有窗戶,那我們就先作既有門又有窗戶的,獻上一張圖爽一下
要實現這樣,那我們首先要封裝一個幾何ti裁切函式:
/** * 幾何體裁切函式 * @param { 被採裁切的集合體 } bsp * @param { 要裁掉的集合體 } less_bsp * @param { 區分是機房的牆還是機櫃裁切的 } mat */ returnResultBsp(bsp, less_bsp, mat) { switch (mat) { case 1: var material = new THREE.MeshPhongMaterial({ color: 0x9cb2d1, specular: 0x9cb2d1, shininess: 30, transparent: true, opacity: 1 }); break; case 2: var material = new THREE.MeshPhongMaterial({ color: 0x42474c, specular: 0xafc0ca, shininess: 30, transparent: true, opacity: 1 }); break; default: } var sphere1BSP = new ThreeBSP(bsp); var cube2BSP = new ThreeBSP(less_bsp); //0x9cb2d1 淡紫,0xC3C3C3 白灰 , 0xafc0ca灰 var resultBSP = sphere1BSP.subtract(cube2BSP); var result = resultBSP.toMesh(material); result.material.flatshading = THREE.FlatShading; result.geometry.computeFaceNormals(); //重新計算幾何體側面法向量 result.geometry.computeVertexNormals(); result.material.needsUpdate = true; //更新紋理 result.geometry.buffersNeedUpdate = true; result.geometry.uvsNeedUpdate = true; if (mat == 2) { result.nature = "Cabinet"; } return result; };
之後我們就開始對有門或者有窗戶的牆面開始處理,先整理資料,將資料整理成我麼能夠最簡單就能處理的
createHouseWall() { this.data.wall.map((item) => { var position = item.position; var w = position.endX - position.x; var h = position.endY - position.y; var x = (position.x + w / 2) - (this.houseWidth / 2); var z = (position.y + h / 2) - (this.houseHeight / 2); var width = Math.sqrt(Math.pow(w, 2) + Math.pow(h, 2)); var angle = Math.asin(h / width) / Math.PI; if (item.windows.isWindows || item.door.isDoor) { // 有窗戶或有門或都有 // 當然判斷裡面還是分開成有門或者有窗戶,但互不干涉 var window__List = []; // 盛放窗戶的陣列 var door__List = []; // 盛放門的陣列 if (item.windows.isWindows) { item.windows.windows__Point.map((windows__Point, window__index) => { let window__Json = {}; let windows__w = windows__Point.endX - windows__Point.x;
let windows__h = windows__Point.endY - windows__Point.y; window__Json.window__x = (windows__Point.x + windows__w / 2) - (this.houseWidth / 2); window__Json.window__z = (windows__Point.y + windows__h / 2) - (this.houseHeight / 2); window__Json.window__width = Math.sqrt(Math.pow(windows__w, 2) + Math.pow(windows__h, 2)); window__Json.w_Height = 120; window__Json.window__y = 100; window__List.push(window__Json); }); } if (item.door.isDoor) { var door__num = item.door.doorNum || 1; item.door.door_Point.map((door__Point, door__index) => { var door__Json = {}; var windows__w = door__Point.endX - door__Point.x; var windows__h = door__Point.endY - door__Point.y; if (door__num == 2) { let doubleDoorList = []; for (var i = 0; i < 2; i++) { door__Json = {}; door__Json.door__x = (door__Point.x + windows__w / 2) - (this.houseWidth / 2) + (door__Point.endX - door__Point.x) / 2 * i; door__Json.door__z = (door__Point.y + windows__h / 2) - (this.houseHeight / 2) + (door__Point.endY - door__Point.y) / 2 * i; door__Json.door__width = (Math.sqrt(Math.pow(windows__w, 2) + Math.pow(windows__h, 2))) / 2; door__Json.door__height = 180; door__Json.door__y = 100; door__Json.doorDirection = door__Point.doorDirection; if (door__Point.doorDirection < 2) { doubleDoorList.unshift(door__Json); } else { doubleDoorList.push(door__Json); } } door__List.push(doubleDoorList); } else { door__Json.door__x = (door__Point.x + windows__w / 2) - (this.houseWidth / 2); door__Json.door__z = (door__Point.y + windows__h / 2) - (this.houseHeight / 2); door__Json.door__width = Math.sqrt(Math.pow(windows__w, 2) + Math.pow(windows__h, 2)); door__Json.door__height = 180; door__Json.door__y = 100; door__Json.doorDirection = door__Point.doorDirection; door__List.push(door__Json); } }); } } else { // 沒門、沒窗戶 let code = this.createLambert(width, 200, 10, angle, this.matArrayB, x, 100, z); this.scene.add(code); } }); };
整理完成之後我們就要開始對以上資料進行操作了,此時我們就需要建立函式cerateWallHadDoorOrGlass來開始畫有玻璃和門的牆了
//畫有門和有窗子的牆(工具函式) cerateWallHadDoorOrGlass(width, height, depth, angle, material, x, y, z, door__list, windows__List) { //茶色:0x58ACFA 透明玻璃色:0XECF1F3 var glass_material = new THREE.MeshBasicMaterial({ color: 0XECF1F3 }); glass_material.opacity = 0.5; glass_material.transparent = true; var wall = this.returnLambertObject(width, height, depth, angle, material, x, y, z); windows__List.map((item, index) => { var window_cube = this.returnLambertObject(item.window__width, item.w_Height, depth, angle, material, item.window__x, item.window__y, item.window__z); wall = this.returnResultBsp(wall, window_cube, 1); let code = this.returnLambertObject(item.window__width, item.w_Height, 2, angle, glass_material, item.window__x, item.window__y, item.window__z); this.scene.add(code); }); var status__result = [0.5, 0.5, 0, 0, ] door__list.map((item, index) => { if (item.length == 2) { item.map((c_item, c_index) => { let door_cube = this.returnLambertObject(c_item.door__width, c_item.door__height, 10, angle, this.matArrayB, c_item.door__x, c_item.door__y, c_item.door__z); wall = this.returnResultBsp(wall, door_cube, 1); let doorgeometry = new THREE.BoxGeometry(100, 180, 2); let door = ""; if (c_index == 0) { door = new THREE.Mesh(doorgeometry, this.LeftDoorRenderingList); } else { door = new THREE.Mesh(doorgeometry, this.DoorRenderingList); } door.position.set(c_item.door__x, c_item.door__y, c_item.door__z); door.rotation.y = status__result[c_item.doorDirection] * Math.PI; door.nature = "door"; door.direction = c_item.doorDirection; door.isClose = 1; door.doorIndex = c_index; this.scene.add(door); }); } else { let door_cube = this.returnLambertObject(item.door__width, item.door__height, 10, angle, this.matArrayB, item.door__x, item.door__y, item.door__z); wall = this.returnResultBsp(wall, door_cube, 1); let doorgeometry = new THREE.BoxGeometry(100, 180, 2); let door = new THREE.Mesh(doorgeometry, this.DoorRenderingList); door.position.set(item.door__x, item.door__y, item.door__z); door.rotation.y = status__result[item.doorDirection] * Math.PI; door.nature = "door"; door.direction = item.doorDirection; door.isClose = 1; this.scene.add(door); } }); this.scene.add(wall); };
如此,大功告成,我們在放一面沒有門但有玻璃的牆看看
畫牆這塊就到這兒,這篇文章整整花費了我一下午的時間,專案是直接從vue init webpack dome 開始的,各位看客如果覺得還行,麻煩給個“推薦”,哈哈哈,全當我一下午的辛苦沒白費! * _ *