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 开始的,各位看客如果觉得还行,麻烦给个“推荐”,哈哈哈,全当我一下午的辛苦没白费! * _ *