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

482 lines
14 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>
<canvas id="three"></canvas>
<div class="btnGroup">
<div class="button" @click="toHomeView">
</div>
<div class="button" @click="setLabel">
{{ isAddLabel ? '' : '' }}
</div>
<div class="button" @click="walk">
</div>
</div>
<div id="dom"></div>
</div>
</template>
<script setup>
import * as THREE from "three";
import { ref, onMounted } from "vue";
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js";
// 引入轨道控制器:支持鼠标左中右键操作和键盘方向键操作
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";
import { OBJLoader } from "three/examples/jsm/loaders/OBJLoader";
// 引入轨道控制器:支持鼠标左中右键操作和键盘方向键操作
import { EffectComposer } from "three/examples/jsm/postprocessing/EffectComposer.js";
import { RenderPass } from "three/examples/jsm/postprocessing/RenderPass.js";
import { OutlinePass } from "three/examples/jsm/postprocessing/OutlinePass.js";
import { ShaderPass } from "three/examples/jsm/postprocessing/ShaderPass.js";
import { FXAAShader } from "three/examples/jsm/shaders/FXAAShader.js";
import {
CSS2DObject,
CSS2DRenderer,
} from "three/examples/jsm/renderers/CSS2DRenderer";
// import renderModel from "../renderModel";
import homeIcon from '@/assets/image/bg7.jpg'
import TWEEN from "@tweenjs/tween.js";
import Bus from '@/utils/bus.js'
import bimStore from '@/store/modules/bim';
let scene = ref(null);
let renderer = ref(null);
let camera = ref(null);
// 控制器
let orbit = ref(null);
let group = ref(null);
let mouse = new THREE.Vector2();
let raycaster = new THREE.Raycaster();
let labelRenderer = new CSS2DRenderer(); //新建CSS2DRenderer
let positionObj = ref(null);
// 选中的模型
let selectedObjects = ref([]);
const props = defineProps({
background: { // 背景颜色
default: '#fff',
type: String
},
sceneUrl: { // 模型路径
default: '/jz/glb/scene.gltf',
type: String
},
light: {
default: '0xffffff',
type: String
}
})
const data = reactive({
gltfObj: null,
btnLabelName: '添加标签',
isAddLabel: true
});
const { gltfObj, isAddLabel } = toRefs(data);
watch(() => props.sceneUrl, val => {
init();
loadSence();
});
let objArr = [];
let objM = [];
// 建筑树点击
Bus.on('clickBuild', (isParent) => {
// Todo
console.log('clickBuild', isParent);
if (!isParent) {// 点击子级
var clickName = bimStore().activateTree.clickName;
// cleanColor();
var Floor = gltfObj.value.scene.getObjectByName(clickName);
// 染色
// Floor.traverse(e => {
// e.material = new THREE.MeshLambertMaterial({
// color: 0x00ff00,
// });
// })
// 高亮轮廓
outlineObj([Floor]);
return;
}
// 父级
toHomeView();
})
// 设备树点击
Bus.on('clickDevice', (isParent) => {
if (!isParent) {// 点击子级
var clickName = bimStore().activateDevice.clickName;
var Floor = gltfObj.value.scene.getObjectByName(clickName);
console.log(1, Floor);
// 高亮轮廓
outlineObj([Floor])
return;
}
// 父级
toHomeView();
})
// 系统树点击
Bus.on('clickApplication', e => {
// Todo
console.log('clickApplication');
})
onMounted(() => {
init();
loadSence();
// 启动动画
renderScene();
document.addEventListener("click", onMouseDown);
document.addEventListener("mousemove", onMouseDown);
});
const setLabel = () => {
if(isAddLabel.value) {
addLabel();
} else {
removeLabel();
}
isAddLabel.value = !isAddLabel.value;
}
const removeLabel = () => {
document.body.removeChild(labelRenderer.domElement);
}
// 添加标签
const addLabel = () => {
let obj = gltfObj.value.scene.getObjectByName('set2');
let text = "设备二";
let pointLabel = createLableObj(text);
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) => {
let laberDiv = document.createElement("div"); //创建div容器
laberDiv.className = "laber_name";
laberDiv.innerHTML = `<div class="arrow_outer"></div><span>设备名称:${text}</span><span>状态:启用</span><span>压力50 Pa</span>`
let pointLabel = new CSS2DObject(laberDiv);
return pointLabel;
};
// 清除模型颜色
const cleanColor = () => {
// 恢复之前被选中模型的材质
if (objArr.length > 0 && objM.length > 0) {
objArr.forEach((ele, index) => {
console.log(objM[index]);
ele.material = objM[index]
})
objArr = [];
objM = [];
}
gltfObj.value.scene.traverse(e => {
objArr.push(e);
objM.push(e.material);
})
}
const init = () => {
// scene = new THREE.Scene("#00ffff");
// const texture = new THREE.TextureLoader().load(homeIcon);
// texture.mapping = THREE.EquirectangularReflectionMapping;
// scene.background = texture
// scene.environment = texture
scene = new THREE.Scene();
console.log(123, props.background);
scene.background = new THREE.Color(props.background);
const canvas = document.querySelector("#three");
var cubeLoader = new THREE.CubeTextureLoader();
// 创建一个渲染器并设置大小WebGLRenderer将会使用电脑显卡来渲染场景
renderer = new THREE.WebGLRenderer({ canvas, antialias: true, alpha: true, preserveDrawingBuffer: true });
//曝光
renderer.toneMappingExposure = 2;
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 0.25, 2000)
// 将摄像机对准场景的中心
camera.position.set(-92.936, 180.990, -28.179);
camera.lookAt(scene.position);
// 创建控件对象
orbit = new OrbitControls(camera, renderer.domElement);
orbit.autoRotate = true;
setTimeout(function () {
orbit.autoRotate = false;
}, 7000);
// renderer.setClearColor(new THREE.Color("#0e0934"));
renderer.setSize(window.innerWidth, window.innerHeight);
// 环境光 光源颜色从0x444444更改为0x888888,可以看到threejs场景中的网格模型表面变的更亮。
// intensity 强度属性0.5
var alight = new THREE.AmbientLight(props.light, 1);
alight.name = "aLight";
scene.add(alight);
};
const loadSence = () => {
const gltfLoader = new GLTFLoader();
gltfLoader.load(props.sceneUrl, (gltf) => {
// model.value = gltf.scene;
gltfObj.value = gltf;
var model = gltf.scene;
scene.add(model);
});
};
const renderScene = () => {
// orbit.update(); // 拖动
// 使用requestAnimationFrame函数进行渲染
requestAnimationFrame(renderScene);
renderer.render(scene, camera);
// 写在requestAnimationFrame之后,否则会报错
labelRenderer.render(scene, camera);
if (composer) {
composer.render()
}
};
// 相机移动动画
const initTween = (targetX, targetY, targetZ) => {
let initPosition = {
x: camera.position.x,
y: camera.position.y,
z: camera.position.z,
};
let tween = new TWEEN.Tween(initPosition)
.to({ x: targetX, y: targetY, z: targetZ }, 2000)
.easing(TWEEN.Easing.Sinusoidal.InOut);
let onUpdate = (pos) => {
let x = pos.x;
let y = pos.y;
let z = pos.z;
if (pos.z < 0) {
camera.position.set(x, y, z - 12);
} else {
camera.position.set(x, y, z + 12);
}
};
tween.onUpdate(onUpdate);
tween.start();
if (positionObj.z < 0) {
orbit.target.set(
positionObj.x,
positionObj.y - 0.4,
-12
);
} else {
orbit.target.set(
positionObj.x,
positionObj.y - 0.4,
12
);
}
}
// 储存被选中的模型和材质
let selectedObject = null;
let selectedMaterial = null;
// 能选中的组
const enableGroup = [
'set1',
'set2',
'set3',
'set4',
'yuanliao_room',
'tanghua_room',
'touliao_room',
]
// const onMouseDown = (event) => {
// // 计算鼠标点击位置的归一化设备坐标
// mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
// mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
// var raycaster = new THREE.Raycaster()
// cleanColor();
// // 更新射线的起点和方向
// raycaster.setFromCamera(mouse, camera);
// // 计算射线和场景中所有可点击物体的相交情况
// const intersects = raycaster.intersectObjects(scene.children, true);
// // 如果找到了模型,将其材质修改为绿色
// if (intersects.length > 0) {
// const clickedObject = intersects[0].object;
// // 储存被选中的模型和材质
// selectedObject = clickedObject;
// // // 修改材质为绿色
// selectedObject.material = new THREE.MeshBasicMaterial({ color: 0x00ff00 });
// nearCamera(intersects);
// }
// }
const onMouseDown = (event) => {
var raycaster = new THREE.Raycaster()
// 计算鼠标点击位置的归一化设备坐标
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
mouse.y = -(event.clientY / 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 = clickedObject.parent;
if (enableGroup.indexOf(selectedObject.name) != -1) {
outlineObj([selectedObject])
// nearCamera(intersects);
}
}
}
let composer = null
let outlinePass = null
let renderPass = null
const outlineObj = (selectedObject_list) => {
// 创建一个EffectComposer效果组合器对象然后在该对象上添加后期处理通道。
composer = new EffectComposer(renderer)
// 新建一个场景通道 为了覆盖到原理来的场景上
renderPass = new RenderPass(scene, camera)
composer.addPass(renderPass);
// 物体边缘发光通道
outlinePass = new OutlinePass(new THREE.Vector2(window.innerWidth, window.innerHeight), scene, camera, selectedObject_list)
outlinePass.selectedObjects = selectedObject_list
// 下面都注掉。不然太开了
outlinePass.edgeStrength = 10.0 // 边框的亮度
outlinePass.edgeThickness = 1.0 // 边框宽度
outlinePass.visibleEdgeColor.set(0x00ff00) // 边框颜色
composer.addPass(outlinePass)
// 自定义的着色器通道 作为参数
var effectFXAA = new ShaderPass(FXAAShader)
effectFXAA.uniforms.resolution.value.set(1 / window.innerWidth, 1 / window.innerHeight)
effectFXAA.renderToScreen = true
composer.addPass(effectFXAA)
}
const nearCamera = (intersects) => {
console.log(5555, intersects)
// 拉近场景
if (!intersects[0]) {
return;
} else {
if (intersects[0].object.name) {
selectedObjects = [];
selectedObjects.push(intersects[0].object);
positionObj = {
x: intersects[0].object.position.x,
y: intersects[0].object.position.y,
z: intersects[0].object.position.z,
};
initTween(
positionObj.x,
positionObj.y,
positionObj.z + 10
);
}
}
}
const toHomeView = () => {
camera.position.set(-92.936, 180.990, -28.179);
camera.lookAt(scene.position);
// cleanColor();
outlineObj([]);
}
// 返回主页
const toHomeView1 = () => {
let initPosition = {
x: camera.position.x,
y: camera.position.y,
z: camera.position.z,
};
let tween = new TWEEN.Tween(initPosition)
.to({ x: 0, y: -(5 * 24) / 12, z: (6 * 100) / 5 }, 2000)
.easing(TWEEN.Easing.Sinusoidal.InOut);
let onUpdate = (pos) => {
let x = pos.x;
let y = pos.y;
let z = pos.z;
camera.position.set(x, y, z);
};
tween.onUpdate(onUpdate);
tween.start();
orbit.target.set(0, 0, 0);
}
</script>
<style lang='scss'>
#three {
height: 100%;
width: 100%;
}
.btnGroup {
width: 180px;
position: absolute;
left: 0px;
bottom: 40px;
z-index: 999;
display: flex;
flex-direction: column;
justify-content: space-between;
cursor: pointer;
color: #ffff;
>div {
background: #477efa;
width: 120px;
height: 40px;
border-radius: 0 50px 50px 0;
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-top-right-radius: 12px;
border-bottom-right-radius: 12px;
font-size: 12px;
display: flex;
flex-direction: column;
&::before {
// content: "";
// display: inline-block;
// width: 0;
// height: 0;
// border: 12px solid transparent;
// border-right-color: #ffffffb3;
// position: absolute;
// left: -23px;
// top: 0;
}
.arrow_outer {
// position: absolute;
// left: -22px;
// top: 6px;
// margin: 0;
// width: 12px;
// height: 12px;
// background-color: #eb6852;
// border-radius: 100%;
// border: 1px solid #fff;
}
span {
line-height: 24px;
}
}
</style>