- <template>
- <div id="Tunnel" ref="threeDom">
- <div class="YuanDian" v-for="(item, i) in labelData" :key="item.id" :id="item.id" style="display: none">
- <div class="iconBox" v-show="item.show">
- <img src="@/assets/images/ljx.png" alt="" class="iconImg" />
- <div class="ydBox">
- <div class="YuanDianText">{{ item.name }}</div>
- <div class="ydData" v-for="(val, k) in item.data" :key="k" v-show="val.data">
- <div class="leftData">{{ val.phy + ':' }}</div>
- <div class="rightData">{{ val.data + ' ' + val.unit }}</div>
- </div>
- <div class="ydData">
- <div class="leftData">{{ '检测时间:' }}</div>
- <div class="rightData">{{ item.dataTime }}</div>
- </div>
- </div>
- </div>
- <img class="YuanDianIcon" :src="item.icon" @mouseenter="item.show = true" @mouseleave="item.show = false" />
- </div>
- </div>
- </template>
-
- <script setup name="Tunnel">
- import { ref, reactive, toRefs, onMounted } from 'vue';
- import * as THREE from 'three';
- import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'; //控制器
- import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js'; // gltf加载器
- import { RGBELoader } from 'three/examples/jsm/loaders/RGBELoader';
- import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader';
- import { Water } from 'three/examples/jsm/objects/Water.js';
-
- import { CSS3DRenderer, CSS3DObject } from 'three/examples/jsm/renderers/CSS3DRenderer.js';
- import gsap from 'gsap';
- import { CSSRulePlugin } from 'gsap/CSSRulePlugin';
- import bus from '@/bus';
-
- import { pointGetDataList } from '@/api/system/tanchuang';
-
- const show = ref('');
- const width = ref(null);
- const height = ref(null);
- const Camera = ref(null);
- const Renderer = ref(null);
- const Controls = ref(null);
- const granaryArr = ref([]);
- const LabelRenderer = ref(null);
- const animationFrameId = ref(null);
- let Twater = null;
- const labelData = ref([
- {
- name: '激光位移计',
- id: `YuanDian1`,
- position: {
- // x: -1400,
- x: -900,
- y: 30,
- z: -140,
- },
- icon: '/Three/icon/wyj_mx.png',
- data: [],
- show: false,
- pointCode: 'kohqORm',
- dataTime: '',
- },
- {
- name: '激光位移计',
- id: `YuanDian2`,
- position: {
- // x: -1400,
- x: -900,
- y: 30,
- z: -85,
- },
- icon: '/Three/icon/wyj_mx.png',
- data: [],
- show: false,
- pointCode: 'LtfAqBh',
- dataTime: '',
- },
- {
- name: '静力水准仪',
- id: `YuanDian3`,
- position: {
- x: -1390,
- y: -50,
- z: -90,
- },
- icon: '/Three/icon/jlszy_icon.png',
- data: [],
- show: false,
- pointCode: 'xr8JcRb',
- dataTime: '',
- },
- {
- name: '静力水准仪',
- id: `YuanDian4`,
- position: {
- x: -900,
- y: -50,
- z: -90,
- },
- icon: '/Three/icon/jlszy_icon.png',
- data: [],
- show: false,
- pointCode: 'zkbTwJW',
- dataTime: '',
- },
- {
- name: '应变计',
- id: `YuanDian5`,
- position: {
- x: -900,
- y: 80,
- z: 0,
- },
- icon: '/Three/icon/ybj_icon.png',
- data: [],
- show: false,
- pointCode: '13G1Hnc',
- dataTime: '',
- },
- {
- name: '裂缝计',
- id: `YuanDian6`,
- position: {
- x: -900,
- y: 80,
- z: -210,
- },
- icon: '/Three/icon/lfj_mx.png',
- data: [],
- show: false,
- pointCode: 'WAB89Dk',
- dataTime: '',
- },
- ]);
- const deviceCode = ref(null);
- const stCode = ref(null);
- const MiMa = ref(null);
- const dialogVisible = ref(false);
- const loading = ref(false);
- const status = ref(false);
- const timer = ref(null);
-
- const Scene = new THREE.Scene();
- const clock = new THREE.Clock();
- const threeDom = ref(null);
- const cameraPosition = {
- x: -2200.221584999469584,
- y: 200.590211176632594,
- z: 600.665579736149148,
- };
- const cameraLookat = {
- x: 1,
- y: 1,
- z: -700,
- };
-
- /**
- * 初始化模态对话框
- * @param {string} name - 模型的名称
- * @param {string} url - 模型的URL
- */
- const initModal = (name, url) => {
- const Gltfloader = new GLTFLoader();
- var dracoLoader = new DRACOLoader();
- // dracoLoader.setDecoderPath("https://zhzz.hongshan.gov.cn:8865/file/hongshan/Three_Gltf/"); //设置解压库文件路径
- dracoLoader.setDecoderPath('/draco/'); //设置解压库文件路径
- Gltfloader.setDRACOLoader(dracoLoader);
- Gltfloader.load(
- url,
- gltf => {
- gltf.scene.name = name;
- gltf.scene.scale.set(1, 1, 1);
- // gltf.scene.position.x = -70;
- gltf.scene.position.y = 2;
-
- cameraReset(cameraPosition, cameraLookat);
-
- Scene.add(gltf.scene);
- Scene.traverse(function (child) {
- if (child.isMesh) {
- child.frustumCulled = false;
- child.material.side = THREE.DoubleSide;
- child.material.emissive = child.material.color;
- child.material.emissiveMap = child.material.map;
- granaryArr.value.push(child);
- }
- });
- },
- function (xhr) {
- console.log(Math.floor((xhr.loaded / xhr.total) * 100));
- }
- );
- };
-
- /**
- * 恢复相机位置和视角
- *
- * 此函数通过GSAP动画库来调整相机的位置和视角,以实现 Smooth Transition
- *
- * @param {Object} position - 相机的新位置,包含x, y, z坐标
- * @param {Object} lookAt - 相机的新视角目标,包含x, y, z坐标,目前未使用
- * @param {number} time - 动画过渡时间,单位为秒,默认为3秒
- */
- const cameraReset = (position, lookAt, time = 3) => {
- gsap.to(Camera.value.position, {
- x: position.x,
- y: position.y,
- z: position.z,
- duration: time,
- ease: 'power4.out',
- });
- // gsap.to(Camera.value.lookAt, {
- // x: lookAt.x,
- // y: lookAt.y,
- // z: lookAt.z,
- // duration: time,
- // ease: "power4.out",
- // });
- gsap.to(Controls.value.target, {
- x: lookAt.x,
- y: lookAt.y,
- z: lookAt.z,
- duration: time,
- ease: 'power4.out',
- });
- };
-
- /**
- * 初始化渲染器
- * 该函数负责创建和配置Three.js的WebGL渲染器,包括设置视口大小、抗锯齿、阴影、像素比、色调映射等
- */
- const initRenderer = () => {
- width.value = document.getElementById('Tunnel').clientWidth;
- height.value = document.getElementById('Tunnel').clientHeight;
- Renderer.value = new THREE.WebGLRenderer({
- antialias: true,
- // alpha: true, //开启alpha
- });
- Renderer.value.shadowMap.enabled = true;
- Renderer.value.setPixelRatio(window.devicePixelRatio);
- Renderer.value.setSize(width.value, height.value, true);
- // Renderer.value.toneMapping = THREE.ACESFilmicToneMapping;
- Renderer.value.toneMappingExposure = 1; // 曝光系数
- threeDom.value.appendChild(Renderer.value.domElement);
- };
-
- /**
- * 初始化标签渲染器
- * 该函数用于创建和配置一个CSS3D渲染器,用于渲染标签对象
- * 它设置了渲染器的大小,位置,并将其添加到三维场景的DOM元素中
- */
- const initLabelRenderer = () => {
- LabelRenderer.value = new CSS3DRenderer();
- LabelRenderer.value.setSize(width.value, height.value);
- LabelRenderer.value.domElement.style.position = 'absolute';
- LabelRenderer.value.domElement.style.top = '0px';
- threeDom.value.appendChild(LabelRenderer.value.domElement);
- // LabelRenderer.value.domElement.addEventListener("click", meshOnClick);
- };
-
- /**
- * 初始化摄像机
- * 创建一个透视摄像机,并设置其位置
- */
- const initCamera = () => {
- Camera.value = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 4000);
- Camera.value.position.set(500, 500, 500);
- };
-
- /**
- * 初始化控制对象
- * 设置相机控制属性,以实现特定的相机行为
- */
- const initControls = () => {
- Controls.value = new OrbitControls(Camera.value, LabelRenderer.value.domElement);
- //上下翻转的最大角度
- Controls.value.maxPolarAngle = 1.5;
- // //上下翻转的最小角度
- Controls.value.minPolarAngle = 0.2;
- //是否允许缩放
- Controls.value.enableZoom = true;
- // 使动画循环使用时阻尼或自转 意思是否有惯性
- Controls.value.enableDamping = false;
- // 动态阻尼系数 就是鼠标拖拽旋转灵敏度
- Controls.value.dampingFactor = 0.04;
- // 是否可以旋转
- Controls.value.enableRotate = true;
- // 是否可以缩放与速度
- Controls.value.enableZoom = true;
- // 设置相机距离原点的最远距离
- Controls.value.minDistance = 50;
- // 设置相机距离原点的最远距离
- Controls.value.maxDistance = 3000;
- // 是否开启右键拖拽
- Controls.value.enablePan = true;
- // AxesHelper:辅助观察的坐标系
- // const axesHelper = new THREE.AxesHelper(3000);
- // Scene.add(axesHelper);
- };
-
- /**
- * 初始化灯光
- * 创建并添加环境光到场景中,以模拟全局照明效果
- */
- const initLight = () => {
- // 设置场景的背景颜色,即天空颜色
- // Scene.background = new THREE.Color(0x999999); // 设置背景,可以更换为你想要的颜色
- // 设置天空的雾气颜色和雾气近距离
- // Scene.fog = new THREE.Fog(0xffffff, 50, 200); // 距离10开始,到200结束
- // Scene.fog = new THREE.Fog(0x1784f9, 50, 2000); // 距离10开始,到200结束
- // 创建平行光源
- // const directionalLight = new THREE.DirectionalLight(0xffffff, 0.6);
- // const directionalLight = new THREE.DirectionalLight(0xffffff, 1);
- // // directionalLight.position.set(-2500, 1, 1);
- // Scene.add(directionalLight);
- // 创建环境光
- const ambientLight = new THREE.AmbientLight('#ffffff', 0.5);
- ambientLight.visible = true;
- // const ambientLight = new THREE.AmbientLight('#1784f9', 0.8);
- Scene.add(ambientLight);
- // 创建平行光辅助线
- // const directionalLightHelper = new THREE.DirectionalLightHelper(directionalLight, 0.5);
- // Scene.add(directionalLightHelper);
- // 创建点光源
- // const PointLight = new THREE.PointLight(0x1784f9, 1, 300);
- // PointLight.position.set(100, -100, 10);
- // Scene.add(PointLight);
- // const spotLight = new THREE.SpotLight(0xffffff);
- // spotLight.position.set(-1000, 1000, 0);
- // spotLight.castShadow = true;
- // spotLight.intensity = 800;
- // Scene.add(spotLight);
- };
-
- /**
- * 初始化天空盒
- * 使用PMREMGenerator生成环境贴图,并设置为场景的环境光和背景
- * 通过RGBELoader加载HDR纹理,用于创建环境贴图
- * 注意:此函数未使用TextureLoader加载背景图片,而是使用HDR纹理
- */
- const initSky = () => {
- // var pmremGenerator = new THREE.PMREMGenerator(Renderer.value); // 使用hdr作为背景色
- // pmremGenerator.compileEquirectangularShader();
- // new RGBELoader().load("/Gltf/Skey_1K.hdr", function (texture) {
- // const envMap = pmremGenerator.fromEquirectangular(texture).texture;
- // envMap.isPmremTexture = true;
- // Scene.environment = envMap;
- // // Scene.background = envMap;
- // pmremGenerator.dispose();
- // });
- new THREE.TextureLoader().load('/Gltf/bg.png', function (texture) {
- Scene.background = texture;
- });
- };
-
- /**
- * 渲染函数,用于不断更新和渲染场景
- */
-
- const Render = () => {
- animationFrameId.value = requestAnimationFrame(Render);
- Controls.value.update(); // 轨道控制器的更新
- Renderer.value.clear(); // 清除画布
- updateLabelOrientation(Camera.value.position);
- Renderer.value.render(Scene, Camera.value);
- LabelRenderer.value.render(Scene, Camera.value);
- const delta = clock.getDelta();
- // 如需调试请打开这个获取Camera,Controls 的值
- // console.log('Camera.value', Camera.value);
- // console.log('Controls.value', Controls.value);
- };
-
- // 创建气泡窗
- function createLable() {
- labelData.value.forEach((item, index) => {
- let labelCSS3D = Scene.getObjectByName(`YuanDian${index + 1}`);
- if (labelCSS3D === undefined) {
- let lableDiv = document.getElementById(`YuanDian${index + 1}`);
- lableDiv.style.display = 'block';
- labelCSS3D = new CSS3DObject(lableDiv);
- labelCSS3D.position.set(item.position.x, item.position.y, item.position.z);
- // labelCSS3D.scale.set(0.1, 0.1, 0.1);
- labelCSS3D.scale.set(3, 3, 3);
- labelCSS3D.name = `YuanDian${index + 1}`;
- // labelCSS3D.rotation.y = - Math.PI / 6; // 30度转换为弧度
- Scene.add(labelCSS3D);
- } else {
- labelCSS3D.visible = true;
- }
- });
- }
-
- /**
- * 更新所有气泡框的方向,使其始终面向相机
- */
- function updateLabelOrientation(cameraPosition) {
- labelData.value.forEach((item, index) => {
- let labelCSS3D = Scene.getObjectByName(`YuanDian${index + 1}`);
- if (labelCSS3D) {
- const direction = new THREE.Vector3();
- direction.subVectors(cameraPosition, labelCSS3D.position).normalize();
- const angle = Math.atan2(direction.x, direction.z);
- labelCSS3D.rotation.y = angle; // 调整角度使其始终面向相机
- }
- });
- }
-
- const flyTo = data => {
- // 静立水准仪
- if (data.name[0] == '激光位移计') {
- // 摄像机位置
- gsap.to(Camera.value.position, {
- x: -1509.9225299741292,
- y: 437.8471459592194,
- z: 345.1272978647253,
- duration: 3,
- ease: 'power4.out',
- });
- // 视角
- gsap.to(Controls.value.target, {
- x: -148.03951167756108,
- y: -406.06002918439316,
- z: -648.358072973929,
- duration: 3,
- ease: 'power4.out',
- });
- } else if (data.name[0] == '静力水准仪') {
- // 摄像机位置
- gsap.to(Camera.value.position, {
- x: -1797.8251896016222,
- y: 191.14387148699308,
- z: 635.4949495785738,
- duration: 3,
- ease: 'power4.out',
- });
- // 视角
- gsap.to(Controls.value.target, {
- x: -318.0579715399841,
- y: 35.607933619702905,
- z: -983.3798167321406,
- duration: 3,
- ease: 'power4.out',
- });
- } else if (data.name.includes('应变计')) {
- gsap.to(Camera.value.position, {
- x: -1694.6408790629657,
- y: 259.7822136553308,
- z: 583.3976961218505,
- duration: 3,
- ease: 'power4.out',
- });
- gsap.to(Controls.value.target, {
- x: -546.8674967113623,
- y: 24.916489480624683,
- z: -409.17274087310204,
- duration: 3,
- ease: 'power4.out',
- });
- } else if (data.name.includes('裂缝计')) {
- gsap.to(Camera.value.position, {
- x: -1530.357885036637,
- y: 356.99171737793193,
- z: 332.61457706089254,
- duration: 3,
- ease: 'power4.out',
- });
- gsap.to(Controls.value.target, {
- x: -80.11715403668808,
- y: -84.77688943101725,
- z: -787.9032398615595,
- duration: 3,
- ease: 'power4.out',
- });
- }
-
- // 打开弹窗
- labelData.value.forEach(element => {
- // if (element.name == data.name[0]) {
- if (data.name.includes(element.name)) {
- element.show = true;
- } else {
- element.show = false;
- }
- });
- };
-
- // 获取点位信息
- const getPonintInfo = () => {
- const promises = labelData.value.map(item => {
- return pointGetDataList({ pointCode: item.pointCode }).then(res => {
- // 检测时间
- const timeWithDate = res.data.find(time => time.dataTime);
- if (timeWithDate) {
- item.dataTime = timeWithDate.dataTime;
- }
- return { ...item, data: res.data };
- });
- });
-
- Promise.all(promises)
- .then(updatedItems => {
- labelData.value = updatedItems;
- console.log("🚀 ~ getPonintInfo ~ updatedItems:", updatedItems)
- })
- .catch(error => {
- console.error('请求出错:labelData.value', error);
- });
- };
-
- onBeforeMount(() => {
- // initGltfFloor();
- // initModal('ChangJing', '/Gltf/ChuWangCheng.gltf');
- initModal('yjlSD', 'https://newfiber-cloud-1255570142.cos.ap-chengdu.myqcloud.com/yanan/yjlSD3.gltf');
- // 共同弹窗触发事件
- bus.on('Tunnel_flyTo', params => {
- // 打开弹窗
- flyTo(params);
- });
- });
- onMounted(() => {
- getPonintInfo();
- nextTick(() => {
- if (document.readyState === 'complete') {
- createLable();
- // createWaterLevel();
- }
- });
- initRenderer();
- initLabelRenderer();
- initCamera();
- initControls();
- initLight();
- initSky();
- createLable();
- // LoadWater();
- Render();
- });
-
- onBeforeUnmount(() => {
- bus.off('Tunnel_flyTo');
- // document.removeEventListener('click', meshOnClick);
- // window.removeEventListener("resize", onWindowResize, false);
-
- Scene.traverse(e => {
- if (e.BufferGeometry) e.BufferGeometry.dispose();
- if (e.material) {
- if (Array.isArray(e.material)) {
- e.material.forEach(m => {
- m.dispose();
- });
- } else {
- e.material.dispose();
- }
- }
- if (e.isMesh) {
- e.remove();
- }
- });
- Scene.remove();
- Renderer.value.dispose();
- Renderer.value.content = null;
- clearInterval(timer.value);
-
- // 清理渲染器和相机
- Camera.value = null;
-
- cancelAnimationFrame(animationFrameId.value);
- });
- </script>
-
- <style lang="scss" scoped>
- #Tunnel {
- width: 100%;
- height: 100%;
-
- .YuanDian {
- // z-index: 9999;
- position: relative;
- // min-height: 20px;
- .iconBox {
- position: relative;
- // border: 1px solid red;
- }
- .iconImg {
- position: absolute;
- left: 50%;
- // transform: translateX(-50%);
- bottom: 5px;
- width: 19px;
- height: 12px;
- }
- .ydBox {
- z-index: 999999;
- // width: 134px;
- // height: 70px;
- position: absolute;
- left: calc(50% + 19px);
- bottom: 12px;
- width: 60px;
- // height: 26px;
- background: linear-gradient(0deg, rgba(12, 54, 92, 0.6) 0%, rgba(12, 54, 92, 0.6) 100%);
- border-radius: 2px;
- border: 1px solid #04d8ff;
- .ydData {
- display: flex;
- align-items: center;
- justify-content: space-between;
- padding: 0 3px;
- font-size: 3px;
- height: 7px;
- line-height: 7px;
- color: #04d8ff;
- // .leftData {
- // }
- // .rightData {
- // }
- }
- }
- .YuanDianText {
- // padding-bottom: 10px;
- height: 7px;
- line-height: 7px;
- color: #fff;
- text-shadow: 0px 1px 3px rgba(0, 0, 0, 0.9);
- white-space: nowrap;
- word-break: keep-all;
- // cursor: pointer;
- pointer-events: none;
- font-size: 3px;
- text-align: center;
- background: linear-gradient(90deg, rgba(4, 216, 255, 0.8) 0%, rgba(4, 216, 255, 0.2) 100%);
- }
-
- .YuanDianIcon {
- z-index: 999;
- width: 10px;
- height: 10px;
- position: absolute;
- left: 50%;
- transform: translateX(-50%);
- bottom: 0;
- pointer-events: auto;
- }
- }
- }
- </style>