bw/src/views/bim/bimHome/components/ThreeView2.vue

865 lines
24 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<template>
<div>
<div id="gltf"></div>
<div id="dom"></div>
<div class="btnGroup">
<div class="button" @click="toHomeView">
</div>
<div class="button" @click="setLabel">
{{ isAddLabel ? '' : '' }}
</div>
<div class="button" @click="setBottom()">
{{ isRemoveBottom ? '' : '' }}
</div>
<!-- <div class="button" @click="roam()">
</div> -->
<div class="button" @click="warn()">
</div>
<div class="button" @click="check()">
</div>
<div class="button" @click="changeMap()">
寿
</div>
</div>
<div class="wall_tooltips" v-show="isShowWall">
<p>
<span class="qing"></span>小于1年
</p>
<p>
<span class="blue"></span>大于1年小于2年
</p>
<p>
<span class="yellow"></span>大于2年小于3年
</p>
<p>
<span class="red"></span>大于3年
</p>
</div>
</div>
</template>
<script setup>
import { onMounted, ref, watch } from 'vue';
import * as THREE from 'three';
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js";
import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader.js";
import { RoomEnvironment } from "three/examples/jsm/environments/RoomEnvironment.js";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
import TWEEN from "@tweenjs/tween.js";
import imgs from "../../../../assets/finish.png";
import {
CSS2DObject,
CSS2DRenderer,
} from "three/examples/jsm/renderers/CSS2DRenderer";
import Bus from '@/utils/bus.js'
import bimStore from '@/store/modules/bim';
import gsap from "gsap";
onMounted(() => {
init();
loadSence();
renderScene();
//点击模型
document.addEventListener('click', onMouseDown);
// 右击事件
window.addEventListener('contextmenu', function (e) {
if (e.button === 2) {
onMouseDownRight(e);
}
});
// document.addEventListener("mousemove", onMouseMove);
});
let scene, renderer, camera, controls, clickedObject, model;
// 克隆颜色和模型
let objArr = [];
let objM = [];
// 监测设备
let warnSets = [];
// 设置巡检
let checkArr = [];
let checkindex = 0;
let mouse = new THREE.Vector2();
let labelRenderer = new CSS2DRenderer(); //新建CSS2DRenderer
const data = reactive({
isAddLabel: true,
isRemoveBottom: true,
isShowWall: false //墙体
});
const { isAddLabel, isRemoveBottom, isShowWall } = toRefs(data);
// 建筑树点击
Bus.on('clickBuild', (isParent) => {
// Todo
console.log('clickBuild', isParent);
var clickName = bimStore().activateTree.clickName;
var Floor = scene.getObjectByName(clickName);
boxLight(Floor);
return;
})
// 设备树点击
Bus.on('clickDevice', (isParent) => {
if (!isParent) {// 点击子级
var clickName = bimStore().activateDevice.clickName;
var Floor = scene.getObjectByName(clickName);
removeLabel();
boxLight(Floor);
// 拉近距离
// nearCamera(Floor);
return;
}
})
// 系统树点击
Bus.on('clickApplication', (isParent) => {
// Todo
if (!isParent) {
xiaof();
}
})
// 监听底部菜单切换
watch(() => bimStore().activateMenu, value => {
// 更换场景颜色
scene.background = new THREE.Color(value.background);
toHomeView();
// 移除消防图标
removexf();
}, { deep: true });
const init = () => {
// 场景
scene = new THREE.Scene();
scene.background = new THREE.Color(bimStore().activateMenu.background);
// 相机
camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.set(-65.82410159732751, 111.17437832684305, -45.026345528621334);
camera.lookAt(scene.position);
//渲染器
renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.outputEncoding = THREE.sRGBEncoding;
document.getElementById('gltf').appendChild(renderer.domElement);
controls = new OrbitControls(camera, renderer.domElement);
const pmremGenerator = new THREE.PMREMGenerator(renderer);
scene.environment = pmremGenerator.fromScene(new RoomEnvironment()).texture;
var vector = new THREE.Vector3();
console.log('刚开始场景', vector);
}
const loadSence = () => {
const gltfLoader = new GLTFLoader();
const dracoLoader = new DRACOLoader();
dracoLoader.setDecoderPath('/draco/gltf/')//设置解压库文件路径
gltfLoader.setDRACOLoader(dracoLoader)
gltfLoader.load('/jzc/jz.gltf', (gltf) => {
model = gltf.scene;
// 克隆初始的材料和模型
model.traverse(e => {
objArr.push(e);
objM.push(e.material);
if (enableGroup.indexOf(e.name) != -1) {
xfobj.push(e);
}
if (yjGroup.indexOf(e.name) != -1) {
yjobj.push(e);
}
})
// 获取巡检设备
const checkname = [{
name: 'start',
isPass: true
}, {
name: 'middle1',
isPass: true
}, {
name: 'middle2',
isPass: false
}, {
name: 'end',
isPass: true
}]
checkname.forEach((item, index) => {
var targetO = model.getObjectByName(item.name);
if (targetO) {
targetO.isPass = item.isPass;
checkArr.push(
targetO
)
}
})
scene.add(model);
});
};
const renderScene = () => {
requestAnimationFrame(renderScene);
const cameraPosition = camera.position;
const cameraX = cameraPosition.x;
const cameraY = cameraPosition.y;
const cameraZ = cameraPosition.z;
// console.log('坐标', cameraX, cameraY, cameraZ);
TWEEN.update();
controls.update();
renderer.render(scene, camera);
labelRenderer.render(scene, camera);
};
const onWindowResize = () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
window.addEventListener("resize", onWindowResize, false);
// 能选中的组
const enableGroup = [
'yuanliao_room',
'tanghua_room',
'touliao_room',
'guolvcao_1',// 过滤槽
'T3',
'T4',
'T5',
'T6',
'T7',
'T8',
'T9',
'T10',
'T11',// 立仓
'T12'
]
// 预警设备
const yjGroup = [
'T7',
]
const isSelent = (obj) => {
var o = obj
while (true) {
if (o.name != "jzgltf" && enableGroup.indexOf(o.name) != -1) {
return o;
} else {
if (o.parent && o.parent.name == "jzgltf") {
return null;
} else {
o = o.parent;
}
}
}
}
let selectedObjects = ref([]);
let selectBoxByClick, selectBoxByMouseon, pointLabel, selectedObject;
var marginLeft = 0;
var marginTop = 70;
// 鼠标移动效果
const onMouseMove = (event) => {
var raycaster = new THREE.Raycaster()
mouse.x = ((event.clientX - marginLeft) / window.innerWidth) * 2 - 1;
mouse.y = -((event.clientY - marginTop) / window.innerHeight) * 2 + 1;
raycaster.setFromCamera(mouse, camera);
const intersects = raycaster.intersectObjects(scene.children, true);
if (intersects.length > 0) {
const clickedObject = intersects[0].object;
selectedObject = isSelent(clickedObject);
if (selectedObject) {
selectedObjects[0] = selectedObject;
if (selectBoxByMouseon) {
scene.remove(selectBoxByMouseon);
}
selectBoxByMouseon = new THREE.BoxHelper(selectedObjects[0], '#ffffff');
scene.add(selectBoxByMouseon);
}
}
}
const emit = defineEmits(['handleRightClick', 'checkComplete'])
// 鼠标右击事件
const onMouseDownRight = (event) => {
var raycaster = new THREE.Raycaster()
mouse.x = ((event.clientX - marginLeft) / window.innerWidth) * 2 - 1;
mouse.y = -((event.clientY - marginTop) / window.innerHeight) * 2 + 1;
raycaster.setFromCamera(mouse, camera);
const intersects = raycaster.intersectObjects(scene.children, true);
// console.log(123, event);
if (intersects.length > 0) {
const clickedObject = intersects[0].object;
console.log('这是我选中的模型', clickedObject);
selectedObject = isSelent(clickedObject);
addLabel(selectedObject, 'details');
}
}
// 鼠标点击效果
const onMouseDown = (event) => {
var raycaster = new THREE.Raycaster()
// 将鼠标点击位置的屏幕坐标转换成threejs中的标准坐标
mouse.x = ((event.clientX - marginLeft) / window.innerWidth) * 2 - 1;
mouse.y = -((event.clientY - marginTop) / window.innerHeight) * 2 + 1;
raycaster.setFromCamera(mouse, camera);
const intersects = raycaster.intersectObjects(scene.children, true);
console.log(123, event);
if (intersects.length > 0) {
clickedObject = intersects[0].object;
console.log('这是我选中的模型', clickedObject);
selectedObject = isSelent(clickedObject);
// 高亮所在区域
boxLight(selectedObject);
// 染色点击的模型
console.log(1, clickedObject);
if (bimStore().activateIndex == '0') {
// 联动左侧菜单
Bus.emit('handleTreeClick', clickedObject);
return;
}
if (bimStore().activateIndex == '1') {
// 联动左侧菜单
Bus.emit('handleTreeClick1', clickedObject);
return;
}
}
}
const changeColor = (Floor) => {
// Floor.children.forEach((obj) => {
// if (obj.isMesh) {
// console.log(123, obj);
// obj.material.transparent = true;
// obj.material.opacity = 0.3;
// obj.material.color = new THREE.Color('rgb( 0 , 0 , 225 )');
// }
// })
// 染色
Floor.traverse(e => {
if (e.isMesh) {
e.material.transparent = true;
e.material.opacity = 0.3;
e.material.color = new THREE.Color('rgb( 0 , 0 , 225 )');
}
})
}
// 清除模型颜色
const cleanColor = () => {
// 恢复之前被选中模型的材质
if (objArr.length > 0 && objM.length > 0) {
objArr.forEach((ele, index) => {
console.log(objM[index]);
ele.material = objM[index]
})
objArr = [];
objM = [];
}
scene.traverse(e => {
objArr.push(e);
objM.push(e.material);
})
}
// 预警
const warn = () => {
['T7'].forEach(item => {
warnSets.push(scene.getObjectByName(item));
})
warnSets[0].traverse(e => {
e.material = new THREE.MeshBasicMaterial({
transparent: true,
opacity: 0.9,
color: new THREE.Color('rgb( 237,33,2 )')
});
})
yjf();
// animateCamera(warnSets[0]);
}
const animateCamera = (one) => {
const lookO = one.getWorldPosition(new THREE.Vector3())
gsap.to(controls.target, {
x: lookO.x,
y: lookO.y + 1.2,
z: lookO.z,
duration: 4,
ease: "power1.inOut",
onComplete: () => { },
});
}
const boxLight = (selectedObject) => {
if (selectedObject) {
selectedObjects[0] = selectedObject;
if (selectBoxByClick) {
scene.remove(selectBoxByClick);
}
selectBoxByClick = new THREE.BoxHelper(selectedObjects[0], '#00ffff');
scene.add(selectBoxByClick);
centerSelectedGroup(selectedObject);
}
}
// 居中显示选中
const centerSelectedGroup = (obj) => {
let tween = new TWEEN.Tween(controls.target).to(obj.position, 1000).easing(TWEEN.Easing.Quadratic.Out);
controls.enabled = false;
tween.onComplete(function () {
controls.enabled = true;
});
tween.start();
}
// 返回初始值
const toHomeView = () => {
// 清除选中框
if (selectBoxByClick) {
scene.remove(selectBoxByClick);
}
// 清除标签
removeLabel();
isAddLabel.value = true;
// 清除精灵图
controls.reset();
// 恢复初始颜色
cleanColor();
// 隐藏图例
isShowWall.value = false;
}
// 拉近距离
const nearCamera = (floor) => {
controls.reset();
gsap.to(camera.position, {
x: floor.position.x,
y: floor.position.y,
z: floor.position.z,
duration: 2,
ease: "power1.inOut",
onComplete: () => {
},
});
}
//标签
const setLabel = () => {
if (isAddLabel.value) {
let obj = scene.getObjectByName('T11');
addLabel(obj, 'title');
} else {
removeLabel();
}
isAddLabel.value = !isAddLabel.value;
}
// 添加标签
const addLabel = (obj, t) => {
removeLabel();
centerSelectedGroup(obj);
pointLabel = createLableObj(obj.name, t);
obj.add(pointLabel);
labelRenderer.setSize(window.innerWidth, window.innerHeight);
labelRenderer.domElement.style.position = "absolute";
labelRenderer.domElement.style.top = 0;
labelRenderer.domElement.style.pointerEvents = 'none';// 必须加上
document.body.appendChild(labelRenderer.domElement);
// 将呈现器的输出添加到HTML元素
document.getElementById("dom").appendChild(renderer.domElement);
};
const createLableObj = (text, t) => {
let laberDiv = document.createElement("div"); //创建div容器
// 只显示标签
if (t === 'title') {
laberDiv.className = "laber_name";
laberDiv.innerHTML = `<div class="arrow_outer">
<div>设备名称:${text}</div>
<div>状态:启用</div>
<div>压力50 Pa</div>
</div>`
//只显示详情
} else if (t === 'details') {
laberDiv.style.pointerEvents = 'auto';// 必须加
laberDiv.className = "laber_details";
laberDiv.innerHTML = `<div class="detail-btn">详情</div>`;
laberDiv.addEventListener('click', function (event) {
emit('handleRightClick', selectedObject);
console.log(labelRenderer.domElement);
})
// 巡检逻辑
} else {
setTimeout(()=>{
laberDiv.innerHTML = `<div class="checking"><span class="text">巡检中...</span></div>`;
},1000)
setTimeout(()=>{
laberDiv.className = "laber_state";
let innerHTML = t ? `<div class="arrow_state">
<div>设备名称:${text}</div>
<div>
巡检结果:巡检通过
<img src=${imgs} width="24px"/>
</div>
<div>
巡检时间2023-12-23
</div>
</div>`: `<div class="arrow_state1">
<div>设备名称:${text}</div>
<div>
巡检结果:巡检不通过,已记录异常
<img src=${imgs} width="24px"/>
</div>
<div>
巡检时间2023-01-11
</div>
</div>`;
laberDiv.innerHTML = innerHTML;
},3000)
}
let pointLabel = new CSS2DObject(laberDiv);
return pointLabel;
};
const removeLabel = () => {
if (pointLabel) {
pointLabel.visible = false;
}
// document.body.removeChild(labelRenderer.domElement);
}
// 设置地板
const setBottom = () => {
var target = scene.getObjectByName('tanghua_2F');
centerSelectedGroup(target);
console.log(target);
if (isRemoveBottom.value) {
target.visible = false;
} else {
target.visible = true;
}
isRemoveBottom.value = !isRemoveBottom.value;
}
// 消防
const xiaof = () => {
xfobj.forEach((o) => {
xfadd(o, Math.random() < 0.3);
})
}
// 预警
const yjf = () => {
yjobj.forEach((o) => {
xfadd(o, 'yj');
})
}
let xfobj = [];
let yjobj = [];
let sprite = null;
// 消防标签
const xfadd = (obj, state) => {
const texLoader = new THREE.TextureLoader();
let texture = null;
if(state == 'yj') {
texture = texLoader.load("/yj.png");
} else if (state) {
texture = texLoader.load("/ygr.png");
} else {
texture = texLoader.load("/yg.png");
}
const spriteMaterial = new THREE.SpriteMaterial({
map: texture,
});
sprite = new THREE.Sprite(spriteMaterial);
// 控制精灵大小
sprite.scale.set(5, 5, 5);
sprite.position.y = 12; //标签底部箭头和空对象标注点重合
obj.add(sprite); //tag会标注在空对象obj对应的位置
}
// 移除消防标签
const removexf = () => {
console.log(xfobj.length);
xfobj.forEach((o) => {
// 倒序
let obj = o.children[o.children.length-1];
if(obj && obj.type === 'Sprite') {
o.remove(o.children[o.children.length-1]);
}
})
}
// 更换纹理贴图方法
const changeMap = (img) => {
isShowWall.value = true;
// 获取墙体
model.traverse(e => {
if (['touliao_wall1', 'touliao_wall2', 'touliao_wall3', 'touliao_wall4', 'touliao_wall5', 'touliao_wall6', 'touliao_wall7', 'touliao_wall8'].indexOf(e.name) != -1) {
console.log(e)
e.material = new THREE.MeshBasicMaterial({
transparent: true,
opacity: 0.5,
color: new THREE.Color('rgb( 88,211,247)')
});
} else if (['yuanliao_wall1', 'yuanliao_wall2', 'yuanliao_wall3', 'yuanliao_wall4', 'yuanliao_wall5', 'yuanliao_wall6', 'yuanliao_wall7', 'yuanliao_wall8', 'yuanliao_wall9','yuanliao_wall10'].indexOf(e.name) != -1) {
e.material = new THREE.MeshBasicMaterial({
transparent: true,
opacity: 0.5,
color: new THREE.Color('rgb( 0,128,255)')
});
} else if (['tanghua_wall1', 'tanghua_wall2','tanghua_wall3', 'tanghua_wall4','tanghua_wall5', 'tanghua_wall6','tanghua_wall7', 'tanghua_wall8'].indexOf(e.name) != -1) {
e.material = new THREE.MeshBasicMaterial({
transparent: true,
opacity: 0.5,
color: new THREE.Color('rgb(247,254,46)')
});
} else if (['tanghua_wall9', 'tanghua_wall10','tanghua_wall11'].indexOf(e.name) != -1) {
e.material = new THREE.MeshBasicMaterial({
transparent: true,
opacity: 0.5,
color: new THREE.Color('rgb(253,45,45)')
});
}
})
}
// 巡检
const check = () => {
let yn = 8, duration = 0;
if (checkindex < checkArr.length ) {
// 1提前把下一个目标找到转动镜头
let nextObj;// 下一个目标
if (checkindex == checkArr.length ) {
nextObj = checkArr[0].getWorldPosition(new THREE.Vector3())
}
if(checkindex < checkArr.length) {
nextObj = checkArr[checkindex].getWorldPosition(new THREE.Vector3())
}
if (checkindex === 0) {
duration = 0
} else {
duration = 10
}
// 2转动镜头
gsap.to(controls.target, {
x: nextObj.x,
y: nextObj.y + 2,
z: nextObj.z,
duration: duration,
ease: "power1.inOut",
onComplete: () => {
// 3移动摄像机
gsap.to(camera.position, {
x: nextObj.x-0.5,
y: nextObj.y + yn,
z: nextObj.z,
duration: 10,
ease: 'power1.inOut',
onComplete: () => {
// 显示标签,已经检查通过
addLabel(checkArr[checkindex], checkArr[checkindex].isPass);
setTimeout(() => {
if (checkindex == checkArr.length) { // 巡检结束
checkComplete();
} else {
check()
}
}, 4000)
checkindex++;
}
});
},
});
}
}
// 巡检结束
const checkComplete = () => {
toHomeView();
bimStore().setCheckArr(checkArr);
emit('checkComplete', checkArr);
return;
}
</script>
<style lang='scss'>
#gltf {
height: 100%;
width: 100%;
}
.btnGroup {
width: 180px;
position: absolute;
left: 260px;
top: 10px;
z-index: 999;
display: flex;
flex-direction: column;
justify-content: space-between;
color: #ffff;
>div {
cursor: pointer;
// background: #0549a7;
border: 1px solid #3cbfdf;
background: rgba(0, 0, 0, .8);
width: 110px;
height: 40px;
border-radius: 5px;
line-height: 40px;
text-align: center;
margin-top: 10px;
}
}
.laber_name {
width: 120px;
height: 90px;
padding: 5px 10px;
white-space: nowrap;
background-color: #ffffffb3;
border-radius: 12px;
font-size: 12px;
display: flex;
flex-direction: column;
span {
line-height: 24px;
}
}
.laber_details {
width: 100px;
height: 50px;
border-radius: 5px;
cursor: pointer;
background-color: #00174b;
color: #fff;
display: flex;
align-items: center;
justify-content: center;
z-index: 999;
}
.arrow_state {
display: inline-block;
// width: 200px;
// height: 100px;
padding: 10px;
background: #0dbc79;
color: #fff;
padding: 15px;
border-radius: 5px;
>div {
line-height: 32px;
}
}
.arrow_state1 {
display: inline-block;
padding: 10px;
background: #e03528;
color: #fff;
padding: 15px;
border-radius: 5px;
>div {
line-height: 32px;
}
}
.wall_tooltips {
position: absolute;
top: 20px;
left: 400px;
z-index: 999;
display: flex;
color: #fff;
width: 600px;
justify-content: space-around;
align-items: center;
span {
display: inline-block;
width: 16px;
height: 8px;
margin-right: 5px;
&.qing {
background: #3cbfdf;
}
&.blue {
background: #2c68f3;
}
&.yellow {
background: #dff04e;
}
&.red {
background: red;
}
}
}
.arrow_outer {
width: 100px;
display: flex;
flex-wrap: wrap;
justify-content: space-between;
align-items: center;
>div {
line-height: 24px;
}
}
.arrow_state {
display: inline-block;
width: 200px;
background: #2dfa82c4;
color: #fff;
padding: 15px;
border-radius: 5px;
>div {
line-height: 32px;
}
}
.arrow_state1 {
display: inline-block;
background: #e03528;
color: #fff;
padding: 15px;
border-radius: 5px;
>div {
line-height: 32px;
}
}
@keyframes textAnimation {
0% {
transform: scale(1);
}
50% {
transform: scale(1.2);
}
100% {
transform: scale(1);
}
}
.checking {
display: inline-block;
width: 100px;
height: 100px;
background: rgb(255, 255, 255);
color: #141313;
padding: 15px;
text-align: center;
line-height: 100px;
border-radius: 5px;
border-radius: 50%;
.text{
display: inline-block;
animation: textAnimation 2s infinite;
}
}
</style>