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

863 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 {
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 currentIndex = 0;
let romeObj = []
// 设置巡检
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();
}, { 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 objname = ['start', 'middle1', 'middle2', 'end']
objname.forEach((item, index) => {
var targetModel = model.getObjectByName(item);
if (targetModel) romeObj.push(
targetModel
)
console.log('targetModel', targetModel)
})
// 漫游结束
// 获取巡检设备
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 roam = () => {
if (currentIndex < romeObj.length) {
console.log('照相机坐标', camera.position);
console.log('物体坐标', romeObj[currentIndex].getWorldPosition(new THREE.Vector3()));
var nextPosition = romeObj[currentIndex].getWorldPosition(new THREE.Vector3());
// 在动画完成后触发下一个漫游
gsap.to(camera.position, {
x: nextPosition.x,
y: nextPosition.y + 2.2,
z: nextPosition.z,
duration: 10,
ease: 'power1.inOut',
onComplete: () => {
roam()
}
});
let lookObj;
if (currentIndex == romeObj.length - 1) {
lookObj = romeObj[0].getWorldPosition(new THREE.Vector3())
} else if (currentIndex < romeObj.length) {
lookObj = romeObj[currentIndex + 1].getWorldPosition(new THREE.Vector3())
}
gsap.to(controls.target, {
x: lookObj.x,
y: lookObj.y + 2.2,
z: lookObj.z,
duration: 10,
ease: "power1.inOut",
onComplete: () => { },
});
currentIndex++;
console.log('lookObj', currentIndex)
} else {
toHomeView();
}
}
// 返回初始值
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 {
laberDiv.className = "laber_state";
let innerHTML = t ? `<div class="arrow_state">
<div>设备名称:${text}</div>
<div>
巡检结果:巡检通过
</div>
<div>
巡检时间2023-12-23
</div>
</div>`: `<div class="arrow_state1">
<div>设备名称:${text}</div>
<div>
巡检结果:巡检不通过,已记录异常
</div>
<div>
巡检时间2023-01-11
</div>
</div>`;
laberDiv.innerHTML = innerHTML;
}
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_1F');
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 = [];
// 消防标签
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,
});
const sprite = new THREE.Sprite(spriteMaterial);
// 控制精灵大小
sprite.scale.set(5, 5, 5);
sprite.position.y = 12; //标签底部箭头和空对象标注点重合
obj.add(sprite); //tag会标注在空对象obj对应的位置
console.log(123, obj)
}
// 移除消防标签
const removexf = () => {
model.traverse(e => {
if (e.isMesh && e.children.length > 0) {
e.remove(e.children[0])
} else {
e.traverse(o => {
if (o.isMesh && o.children.length > 0) {
o.remove(o.children[0])
}
})
}
})
scene.remove(sprite);
}
// 更换纹理贴图方法
const changeMap = (img) => {
// 测试移除消防图标
// removexf();
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 = () => {
if (currentIndex < romeObj.length) {
// .getWorldPosition()属性需要用三维向量表示摸个坐标后方可读取
var checkPosition = checkArr[checkindex].getWorldPosition(new THREE.Vector3());
gsap.to(camera.position, {
x: checkPosition.x,
y: checkPosition.y + 5.2,
z: checkPosition.z,
duration: 10,
ease: 'power1.inOut',
onComplete: () => {
if (checkindex == romeObj.length) { // 巡检结束
checkComplete();
}
check()
}
});
var pos1 = new THREE.Vector3( camera.position );
var pos2 = new THREE.Vector3( checkPosition );
var distance = pos1.distanceTo(pos2);
console.log(12354, distance);
let nextObj;
if (checkindex == checkArr.length - 1) {
nextObj = checkArr[0].getWorldPosition(new THREE.Vector3())
} else if (checkindex < checkArr.length) {
nextObj = checkArr[checkindex + 1].getWorldPosition(new THREE.Vector3())
}
gsap.to(controls.target, {
x: nextObj.x,
y: nextObj.y + 5.2,
z: nextObj.z,
duration: 10,
ease: "power1.inOut",
onComplete: () => {
// 显示标签,已经检查通过
// addLabel(checkArr[checkindex], checkArr[checkindex].isPass);
},
});
checkindex++;
console.log('nextObj', checkindex)
}
}
// 巡检结束
const checkComplete = () => {
toHomeView();
checkindex = 0;
bimStore().setCheckArr(romeObj);
emit('checkComplete', romeObj);
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;
}
}
</style>