1.创建楼栋
在上一篇文章中已经了解了创建物体的几个要素,本文在上一基础来进行实现,同样的所有的变量声明还是在init中完成,或者直接在mounted中声明,不要在data中,可以试验下在data中的效果,在这个例子中会很明显的感觉到浏览器渲染的卡顿
撸起,在init中添加创建楼栋this.createFloor();
createFloor(){
let _this = this;
let a = createFloorOne(10,-66.6,7,'A栋',['402'],['501']);
a.rotateY(Math.PI / 2);
a.position.set(-36.9,0,103);
_this.mesh.add(a);
let C = createFloorOne(10,-66.6,7,'C栋');
_this.mesh.add(C);
/*x,y 表示楼栋起点坐标,floorNum 表示有多少楼层 name 楼栋名称,contractWarning 表示合同到期预警,freeRoom 表示空闲房间 两个都是数组 */
function createFloor(x,y,floorNum,name,freeRoom,contractWarning) {
// l楼的长度 w 楼的宽度 h每层楼的高度 t墙的厚度 hd窗户高度的初始底数,既是第一层窗户的离地距离
let l= 20, w = 60 , h = 8, hd = 6 , t = 0.5; freeRoom = freeRoom || [];contractWarning = contractWarning || [];
let group = new THREE.Group();// 声明组对象
/**** 楼栋的矩形平面图 外轮廓起点值x,y轴小于内轮廓值 中间转点值则大于 内轮廓***/
let BRoadOut = [
[x, y],
[x, y + w],
[x + l, y + w],
[x + l, y],
[x, y]
];
// 内墙轮廓
let BRoadIn = [
[x + t, y + t],
[x + t, y + w - t],
[x + l - t, y + w - t],
[x + l - t, y + t],
[x + t, y + t]
];
/*楼栋*/
let BRoadShape = createShape(BRoadOut, BRoadIn);// 生成矩形
let BRoadGeometry = createExtrudeBufferGeometry(BRoadShape, h * floorNum + 2);// 拉伸矩形成长方体,并绕y轴旋转
let BRoadMesh = createMesh1(BRoadGeometry, {color: 0xd3d7d4, side: THREE.DoubleSide});
BRoadMesh.name = name;// 设置几何体的name值
BRoadMesh.roomType = 'B';// 设置几何体类型
BRoadMesh.castShadow = true;//开启阴影投射
group.add(BRoadMesh);
/* 楼顶 */
let BFloorRoofTop = [
[x + t, y + t],
[x + t, y + w - t],
[x + l - t, y + w - t],
[x + l - t, y + t],
[x + t, y + t]
];
let BFloorRoofTopShape = createShape(BFloorRoofTop);
let BFloorRoofTopGeometry = createExtrudeBufferGeometry(BFloorRoofTopShape, h * floorNum + 0.5);
let BFloorRoofTopMesh = createMesh1(BFloorRoofTopGeometry, {color: 0xfafafa, side: THREE.DoubleSide});
BFloorRoofTopMesh.name = name;
BFloorRoofTopMesh.roomType = 'F';
group.add(BFloorRoofTopMesh);
// 楼栋名称logo
let geometry = new THREE.CircleGeometry(2.5, 32);// 创建圆形几何体
let brandMesh = createMesh2(geometry,{color:0xC6E2FF,map:createCircleText(name),side: THREE.DoubleSide});// 绘制文字
brandMesh.position.set(x + (l - 9) / 2 + 4.5,h * floorNum - 0.5,Math.abs(y || 0) + 0.06);
group.add(brandMesh);
// 设置两个几何体之间的距离0.06,否则会重叠,遮盖
// 正面
let xNum = 2;
let whiteWindow = createWindow(0xffffff);// 普通房间使用白色窗户
let redWindow = createWindow(0xca0b0b);// 空置房间使用红色窗户
let warningWindow = createWindow(0xE6A23C);// 即将到期房间使用黄色
for (let i = 0; i < xNum; i++) {
for (let j = 0; j < floorNum; j++) {
let windows = whiteWindow.clone();
freeRoom.forEach(item =>{
if ((j+1)+'01' === item) {
windows = redWindow.clone();
windows.roomStatus = '1';
}
});
contractWarning.forEach(item =>{
if ((j+1)+'01' === item) {
windows = warningWindow.clone();
windows.roomStatus = '2';
}
});
windows.name = name +'-'+ (j + 1) + '层' +'-'+ (j+1)+'01';
windows.roomType = 'R';
windows.position.x = x + (l - 9 * (xNum - 1)) / 2 + 9 * i;
windows.position.y = hd + h * j;
windows.position.z = Math.abs(y || 0) + 0.06;
group.add(windows)
}
}
// // 背面
for (let i = 0; i < xNum; i++) {
for (let j = 0; j < floorNum; j++) {
let windows = whiteWindow.clone();
freeRoom.forEach(item =>{
if ((j+1)+'02' === item) {
windows = redWindow.clone();
windows.roomStatus = '1';
}
});
contractWarning.forEach(item =>{
if ((j+1)+'02' === item) {
windows = warningWindow.clone();
windows.roomStatus = '2';
}
});
windows.roomType = 'R';
windows.name = name +'-'+ (j + 1) + '层'+'-'+ (j+1)+'02';
/** 正反面的窗户 x轴位置 = x起点 + (墙宽度 - 窗户总间隔) / 2 + 窗户之间的间隔平均值 ------
* (墙宽度 - 窗户总间隔) / 2 得到的是当前墙面的窗户 距离两边墙沿的距离 **/
windows.position.x = x + (l - 9 * (xNum - 1)) / 2 + 9 * i;
windows.position.y = hd + h * j;
windows.position.z = Math.abs(y || 0) - w - 0.06;
windows.rotateY(Math.PI);
group.add(windows)
}
}
// 右侧
for (let i = 0; i < 7; i++) {
for (let j = 0; j < floorNum; j++) {
let windows = whiteWindow.clone();
let room =i < 3 ? '01':i > 3 ? '02': '';
freeRoom.forEach(item =>{
if ((j + 1) + room === item) {
windows = redWindow.clone();
windows.roomStatus = '1';
}
});
contractWarning.forEach(item =>{
if ((j + 1) + room === item) {
windows = warningWindow.clone();
windows.roomStatus = '2';
}
});
let number = room ? '-'+(j + 1) + room : '';
windows.roomType = 'R';
if(i === 3)windows.roomType = 'F';
windows.name = name + '-' + (j + 1) + '层' + number;
windows.position.z = Math.abs(y || 0) - (w - 8 * (7 - 1)) / 2 - 8 * i;
windows.position.y = hd + h * j;
/*x轴的位置 等于x轴值 + 楼栋的宽度 0.2 + 窗户与楼栋的间隔*/
windows.position.x = x + l + 0.06;
windows.rotateY(Math.PI / 2);
group.add(windows)
}
}
// 左侧
let group4 = new THREE.Group();
for (let i = 0; i < 7; i++) {
for (let j = 0; j < floorNum; j++) {
let windows = whiteWindow.clone();
let room =i < 3 ? '01':i > 3 ? '02': '';
freeRoom.forEach(item =>{
if ((j + 1) + room === item) {
windows = redWindow.clone();
windows.roomStatus = '1';
}
});
contractWarning.forEach(item =>{
if ((j + 1) + room === item) {
windows = warningWindow.clone();
windows.roomStatus = '2';
}
});
let number = room ? '-'+(j + 1) + room : '';
windows.roomType = 'R';
if(i === 3)windows.roomType = 'F';
windows.name = name + '-' + (j + 1) + '层' + number;
windows.position.z = Math.abs(y || 0) - (w - 8 * (7 - 1)) / 2 - 8 * i;
windows.position.y = hd + h * j;
windows.rotateY(Math.PI / -2);
group4.add(windows)
}
}
group4.position.x = x - 0.06;
group.add(group4);
return group;
}
function createShape(array1,array2) {// 创建对象轮廓,绘制一个二维平面形状
let shape;
if (array1) {
shape = new THREE.Shape();// 创建二维形状对象,绘制外轮廓
shape.moveTo(array1[0][0], array1[0][1]); // 开始绘制的点
for (let i = 1; i < array1.length; i++) {
shape.lineTo(array1[i][0], array1[i][1]) // 需要到达的点的坐标
}
if (array2) {//
let path = new THREE.Path();// 创建二维路径对象 绘制内轮廓
path.moveTo(array2[0][0], array2[0][1]);
for (let i = 1; i < array2.length; i++) {
path.lineTo(array2[i][0], array2[i][1])
}
shape.holes.push(path) // //把内轮廓参数添加到shape对象的holes属性上 设置内轮廓
}
return shape
}
}
function createExtrudeBufferGeometry(shape,depth) {// 拉伸几何体
let extrudeSetting = {
steps: 1, // 拉伸几何体,分为几段
depth : depth, // 拉伸几何体的高度
bevelEnabled: false // 设置为true的情况下会有斜角
};
let geometry = new THREE.ExtrudeBufferGeometry(shape,extrudeSetting) ;
geometry.rotateX( Math.PI / -2);
return geometry;
}
function createMesh(geometry,obj) {// 非光泽表面的材质,没有镜面高光。
let material = new THREE.MeshLambertMaterial(obj); //材质对象Material
return new THREE.Mesh(geometry, material);
}
function createMesh1(geometry,obj) {// 具有镜面高光的光泽表面的材质
let material = new THREE.MeshPhongMaterial(obj); //材质对象Material
return new THREE.Mesh(geometry, material);
}
function createCircleText(text) {// 创建一个圆形文本贴图
let canvas = document.createElement('canvas');
canvas.width = 32;
canvas.height = 32;
let ctx = canvas.getContext('2d');
// 背景
ctx.arc(16, 16, 17, 0, 2 * Math.PI);// 画圆
ctx.fillStyle = '#FFB90F';
ctx.fill();// 充满画布
// 文字
ctx.font = "12px bold";
ctx.fillStyle = '#FF4500';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText(text, 16, 16);
// CanvasTexture 从Canvas元素中创建纹理贴图 needsUpdate默认为true 这是必须的,以便使得Canvas中的数据能够载入
return new THREE.CanvasTexture(canvas);
}
function createWindow(color) {// 创建窗户
let geometry = new THREE.BoxBufferGeometry(4, 4, 1);
let mesh = createMesh(geometry, {color: color, side: THREE.DoubleSide});
mesh.position.z = -0.5;
return mesh
}
},
以上方法用threejs中的几何体来创建一个楼栋,样子虽丑但是涉及的功能类api还是有一些的,可以很好地学习threejs的一些api,接下来就是要给房子添加点击事件了,让我们在点击时能准确的获取我们想要的房号值
2.添加点击事件
监听事件,在mounted内的$nextTick document.body.addEventListener('click', this.choose);
choose(el){
let x = el.clientX,y = el.clientY;
this.left = x + 20;
this.top = y + 20;
let mouse = new THREE.Vector2();// 创建一个新的Vector2。
let getBoundingClientRect = this.container && this.container.getBoundingClientRect();// 获取scene内容元素的大小及其相对于窗口的位置
//这里的container就是画布所在的div,以整个scene所在的容器来界定范围,不以window.innerWidth为范围
// 因为我采用的左侧边导航栏样式,以及顶部背景条,所以减去这两个值
mouse.x = ((x - getBoundingClientRect.left) / this.container.offsetWidth) * 2 - 1;
mouse.y = -((y - getBoundingClientRect.top) / this.container.offsetHeight) * 2 + 1;
let raycaster = new THREE.Raycaster();// 鼠标拾取 -- 在三维空间中计算出鼠标移过了什么物体
raycaster.setFromCamera(mouse, this.camera);// 将鼠标位置统一为当前三维坐标。x 和 y 方向的取值范围是 (-1 到 +1)
let intersects = raycaster.intersectObjects( this.scene.children,true );// 获取当前鼠标位置的物体以及物体的后代
if(intersects.length > 0){
// console.log(intersects[0].object.name,intersects[0].object);
if(!intersects[0].object.name)return;
let obj = intersects[0].object;
clearTimeout(this.timeOut);// 点击事件发生后 清除初始化时的展示框关闭延时器
// 房产状态不是合同到期预警的
if(obj.roomStatus !== '2'){
let area = Math.random() * 1000 + 1;
this.$message(obj.name + '空置房间面积' + area + 'm²');
}
// 房产状态不是空置房产提示的
if(obj.roomStatus !== '1'){
let num = Math.random() * 10 + 1;
this.$message(obj.name + '到期个数'+ num )
}
}
},