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

467 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 id="dom"></div>
<div class="btnGroup">
<div class="button" @click="toHomeView">
</div>
<div class="button" @click="setLabel">
{{ isAddLabel ? '' : '' }}
</div>
<div class="button" @click="walk">
</div>
</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';
import gsap from "gsap";
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([]);
let curve = ref(null);
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]);
// 拉近场景
nearCamera(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])
// 拉近距离
nearCamera(Floor);
return;
}
// 父级
// toHomeView();
})
// 系统树点击
Bus.on('clickApplication', e => {
// Todo
console.log('clickApplication');
})
onMounted(() => {
init();
loadSence();
// 启动动画
renderScene();
// document.addEventListener("click", onMouseDown);
document.addEventListener("mousemove", onMouseDown);
});
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();
scene.background = new THREE.Color(props.background);
const canvas = document.querySelector("#three");
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);
// 创建控件对象 限制controls的上下角度范围
orbit = new OrbitControls(camera, renderer.domElement);
orbit.maxPolarAngle = Math.PI / 2.1;
orbit.maxDistance = 12;
renderer.setSize(window.innerWidth, window.innerHeight);
// 环境光 光源颜色从0x444444更改为0x888888,可以看到threejs场景中的网格模型表面变的更亮。
var alight = new THREE.AmbientLight(props.light, 1);
alight.name = "aLight";
scene.add(alight);
// 增加漫游路线
makeCurve();
};
// 加载场景
const loadSence = () => {
const gltfLoader = new GLTFLoader();
gltfLoader.load(props.sceneUrl, (gltf) => {
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 makeCurve = () => {
curve = new THREE.CatmullRomCurve3([
new THREE.Vector3(11.5, 0, 18),
new THREE.Vector3(11.5, 0, 34),
new THREE.Vector3(35, 0, 34),
new THREE.Vector3(35, 0, 31),
new THREE.Vector3(11.5, 0, 31),
]);
// 为曲线添加材质在场景中显示出来不显示也不会影响运动轨迹相当于一个Helper
const points = curve.getPoints(0.1);
const geometry = new THREE.BufferGeometry().setFromPoints(points);
const material = new THREE.LineBasicMaterial({
color: 0xff0000,
});
// 线条模型对象
const line = new THREE.Line(geometry, material);
scene.add(line)
}
const changePosition = (t) => {
const position = curve.getPointAt(t); // t: 当前点在线条上的位置百分比,后面计算
mesh.position.copy(position);
}
const changeLookAt = (t) => {
const tangent = curve.getTangentAt(t);
const lookAtVec = tangent.add(mesh.position); // 位置向量和切线向量相加即为所需朝向的点向量
mesh.lookAt(lookAtVec);
}
const loopTime = 10 * 1000; // loopTime: 循环一圈的时间
const walk = () => {
let time = Date.now();
setInterval(() => {
let t = (time % loopTime) / loopTime; // 计算当前时间进度百分比
changePosition(t);
changeLookAt(t);
}, 1000)
}
// 储存被选中的模型和材质
let selectedObject = null;
let selectedMaterial = null;
// 能选中的组
const enableGroup = [
'guan2',
'set2',
'set3',
'set4',
'yuanliao_room',
'tanghua_room',
'touliao_room',
]
const onMouseMove = (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;
if (enableGroup.indexOf(selectedObject.name) != -1) {
outlineObj([selectedObject])
// nearCamera(intersects);
}
if (enableGroup.indexOf(selectedObject.parent.name) != -1) {
outlineObj([selectedObject.parent])
// 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;
if (enableGroup.indexOf(selectedObject.name) != -1) {
outlineObj([selectedObject])
alert("点击了",selectedObject.name)
// nearCamera(intersects);
}
if (enableGroup.indexOf(selectedObject.parent.name) != -1) {
outlineObj([selectedObject.parent])
alert("点击了",selectedObject.parent.name)
// 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 = (floor) => {
orbit.reset();
gsap.to(camera.position, {
x: floor.position.x - 40,
y: floor.position.y + 60,
z: floor.position.z,
duration: 2,
ease: "power1.inOut",
onComplete: () => {
},
});
}
const toHomeView = () => {
orbit.reset();
outlineObj([]);
}
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);
})
}
</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>