<template> <div class="cantainer"> <div id="bengzhan" ref="threeDom"> <div id="lableBox" class="lableBox"> <div class="label" v-for="(item, i) in labelData" :key="i" :id="item.id" style="display: none"> <div class="outer"> <div class="title">{{ item.name }}</div> <ul> <li v-for="(v, j) in item.data" :key="j"> <span class="pointName">{{ v.pointName }}</span> <span class="value">{{ v.value }}</span> </li> </ul> <div class="triangle"></div> </div> </div> </div> </div> </div> </template> <script> import { ref, reactive, toRefs, onMounted, onUnmounted, nextTick, onBeforeMount } 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 { CSS3DRenderer, CSS3DObject } from 'three/examples/jsm/renderers/CSS3DRenderer.js'; import gsap from 'gsap'; import { CSSRulePlugin } from 'gsap/CSSRulePlugin'; gsap.registerPlugin(CSSRulePlugin); // 引入css插件,完成某些css动画 import { Move, CameraReverseOutline, ReturnDownForward, EyeSharp, Eye } from '@vicons/ionicons5'; export default { name: 'dqtxc', components: { Move, CameraReverseOutline, ReturnDownForward, EyeSharp, Eye, }, props: { bzInfo: Object, }, setup(props) { const state = reactive({ width: null, height: null, Scene: null, Camera: null, Renderer: null, Controls: null, granaryArr: [], texture: null, LabelRenderer: null, cnt: 0, mixer4: null, // labelData: [], labelData: [ { name: '一号泵机', data: [ { pointName: '开关状态:', value: '关', }, { pointName: '电压:', value: '0V', }, { pointName: '电流:', value: '0A', }, ], id: `label1`, position: { x: 46, y: -5, z: -6, }, }, { name: '二号泵机', data: [ { pointName: '开关状态:', value: '关', }, { pointName: '电压:', value: '0V', }, { pointName: '电流:', value: '0A', }, ], id: `label2`, position: { x: 50, y: -5, z: -16, }, }, { name: '三号泵机', data: [ { pointName: '开关状态:', value: '关', }, { pointName: '电压:', value: '0V', }, { pointName: '电流:', value: '0A', }, ], id: `label3`, position: { x: 55, y: -5, z: -22, }, }, { name: '四号泵机', data: [ { pointName: '开关状态:', value: '关', }, { pointName: '电压:', value: '0V', }, { pointName: '电流:', value: '0A', }, ], id: `label4`, position: { x: 60, y: -5, z: -28, }, }, { name: '五号泵机', data: [ { pointName: '开关状态:', value: '关', }, { pointName: '电压:', value: '0V', }, { pointName: '电流:', value: '0A', }, ], id: `label5`, position: { x: 65, y: -5, z: -34, }, }, { name: '六号泵机', data: [ { pointName: '开关状态:', value: '关', }, { pointName: '电压:', value: '0V', }, { pointName: '电流:', value: '0A', }, ], id: `label6`, position: { x: 70, y: -5, z: -40, }, }, ], timer: null, BZData: [], }); const Scene = new THREE.Scene(); // const axesHelper = new THREE.AxesHelper(10000); // Scene.add(axesHelper); const clock = new THREE.Clock(); const threeDom = ref(null); const cameraPosition = { // x: 65, // y: -5, // z: -34, x: 0.6684777522359171, y: 73.34509967795945, z: -75.51598392913485, }; const cameraLookat = { x: 0, y: 0, z: 0, }; const cameraReset = (position, lookAt, time = 3) => { gsap.to(state.Camera.position, { x: position.x, y: position.y, z: position.z, duration: time, ease: 'power4.out', }); gsap.to(state.Camera.lookAt, { x: lookAt.x, y: lookAt.y, z: lookAt.z, duration: time, ease: 'power4.out', }); gsap.to(state.Controls.target, { x: lookAt.x, y: lookAt.y, z: lookAt.z, duration: time, ease: 'power4.out', }); }; const initLabelRenderer = () => { state.LabelRenderer = new CSS3DRenderer(); state.LabelRenderer.setSize(state.width, state.height); state.LabelRenderer.domElement.style.position = 'absolute'; state.LabelRenderer.domElement.style.top = '0px'; threeDom.value.appendChild(state.LabelRenderer.domElement); }; const initRenderer = () => { state.width = document.getElementById('bengzhan').clientWidth; state.height = document.getElementById('bengzhan').clientHeight; state.Renderer = new THREE.WebGLRenderer({ antialias: true, //抗锯齿 alpha: true, //开启alpha }); state.Renderer.shadowMap.enabled = true; // state.Renderer.setClearColor("skyblue"); state.Renderer.setPixelRatio(window.devicePixelRatio); state.Renderer.setSize(state.width, state.height); state.Renderer.toneMapping = THREE.ACESFilmicToneMapping; state.Renderer.toneMappingExposure = 2.3; // 曝光系数 state.Renderer.setClearColor(0xffffff); threeDom.value.appendChild(state.Renderer.domElement); }; //创建一个透视相机,45是相机的视角 , 宽高比是屏幕的宽高比 , 最近能看到0.1 , 最远能看到10000 const initCamera = () => { state.Camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 10, 100000); //将相机放到x:1000 , y:1000 , z:1000的位置 // state.Camera.position.set(30, 0, 10); state.Camera.position.set(1000, 1000, 1000); }; const initControls = () => { //调试模型光线位置代码 // const axesHelper = new THREE.AxesHelper(10000); // Scene.add(axesHelper); state.Controls = new OrbitControls(state.Camera, state.LabelRenderer.domElement); //上下翻转的最大角度 state.Controls.maxPolarAngle = 5; // //上下翻转的最小角度 state.Controls.minPolarAngle = 0.8; //是否允许缩放 state.Controls.enableZoom = true; // 使动画循环使用时阻尼或自转 意思是否有惯性 state.Controls.enableDamping = false; // 动态阻尼系数 就是鼠标拖拽旋转灵敏度 state.Controls.dampingFactor = 0.04; // 是否可以旋转 state.Controls.enableRotate = true; // 是否可以缩放与速度 state.Controls.enableZoom = true; // 设置相机距离原点的最远距离 state.Controls.minDistance = 100; // 设置相机距离原点的最远距离 state.Controls.maxDistance = 12000; // 是否开启右键拖拽 state.Controls.enablePan = true; }; 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('modelFiles/Three_Gltf/'); //设置解压库文件路径 Gltfloader.setDRACOLoader(dracoLoader); Gltfloader.load( url, (gltf) => { gltf.scene.name = name; gltf.scene.scale.set(0.35, 0.35, 0.35); gltf.scene.position.set(10, 10, 10); // gltf.scene.position.z = 10; cameraReset(cameraPosition, cameraLookat); state.mixer = new THREE.AnimationMixer(gltf.scene); // state.mixer.clipAction(gltf.animations[0]).play(); //渲染glb文件自带的动画效果 Scene.add(gltf.scene); // Scene.add(cube); Scene.traverse(function (child) { if (child.isMesh) { child.frustumCulled = false; child.material.side = THREE.DoubleSide; state.granaryArr.push(child); } }); }, (function (xhr) { console.log(Math.floor((xhr.loaded / xhr.total) * 100)); })`` ); }; const initLight = () => { let ambientLight = new THREE.AmbientLight(0x0557d7, 2); // 创建环境光 Scene.add(ambientLight); }; const initSpotLight = () => { let spotLight = new THREE.SpotLight(0x2e3d46, 0.9); spotLight.position.set(20, 500, 100); const SpotLightHelper = new THREE.SpotLightHelper(spotLight, 1); // 光源开启阴影效果 // spotLight.castShadow = true; // Scene.add(SpotLightHelper); Scene.add(spotLight); // 将聚光灯添加到场景中 }; const initDirectionalLight = () => { // 平行光设置 var directionalLight = new THREE.DirectionalLight(0x0557d7, 1); // 两点确定一条直线,我们要找两个点,一个是我们设置的点(position),一个是物体(target) directionalLight.position.set(120, 100, 50); // directionalLight.target = mesh; Scene.add(directionalLight); }; const hdr = 'modelFiles/three_glb/kloppenheim_061_4k.hdr'; // const hdr = 'public/static/three_glb/kloppenheim_061_4k.hdr'; const initSky = () => { var pmremGenerator = new THREE.PMREMGenerator(state.Renderer); // 使用hdr作为背景色 pmremGenerator.compileEquirectangularShader(); new RGBELoader().load(hdr, function (texture) { const envMap = pmremGenerator.fromEquirectangular(texture).texture; // envMap.mapping = THREE.EquirectangularReflectionMapping; // state.Renderer.outputEncoding = THREE.sRGBEncoding; envMap.isPmremTexture = true; pmremGenerator.dispose(); Scene.environment = envMap; Scene.background = envMap; }); }; // 创建气泡窗 function createLable() { state.labelData.forEach((item, index) => { let labelCSS3D = Scene.getObjectByName(`label${index + 1}`); if (labelCSS3D === undefined) { let lableDiv = document.getElementById(`label${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.03, 0.03, 0.03); labelCSS3D.name = 'Zha'; Scene.add(labelCSS3D); console.log('labelCSS3D.position', labelCSS3D.position); } else { labelCSS3D.visible = true; } }); } const meshOnClick = (event) => { let raycaster = new THREE.Raycaster(); const pointer = new THREE.Vector2(); console.log(event.clientX, event.clientY); let Top = 60, Left = 60; pointer.x = ((event.clientX - Left) / state.width) * 2 - 1; pointer.y = -((event.clientY - Top) / state.height) * 2 + 1; raycaster.setFromCamera(pointer, state.Camera); let intersects = raycaster.intersectObjects(state.granaryArr, true); console.log(intersects); console.log('------', state.Camera.position); if (intersects.length > 0) { console.log(intersects[0].object); } else { console.log('NoNoNo'); } }; // 标注水位 const createWaterLevel = () => { let info = { name: 'text', text: ' 水位: 0.5M ', fontsize: 30, Color: 'green', backgroundColor: 'rgba(0,0,0,0)', position: { x: 2, y: 10, z: 0 }, fillRect: { x: 0, y: 0, w: 300, h: 60 }, // rotateY: -180, }; let canvas = document.createElement('canvas'); let ctx = canvas.getContext('2d'); //制作矩形 ctx.fillStyle = info.backgroundColor; ctx.fillRect(info.fillRect.x, info.fillRect.y, info.fillRect.w, info.fillRect.h); //设置文字 ctx.fillStyle = info.Color; ctx.font = `normal ${info.fontsize}px "楷体"`; let textWord = info.text; ctx.fillText(textWord, 10, 40); //生成图片 let url = canvas.toDataURL('image/png'); //将图片构建到纹理中 let geometry1 = new THREE.PlaneGeometry(20, 10); let texture = THREE.ImageUtils.loadTexture(url, null, function (t) {}); let material1 = new THREE.MeshBasicMaterial({ map: texture, side: THREE.DoubleSide, opacity: 1, transparent: true, }); let rect = new THREE.Mesh(geometry1, material1); rect.name = info.name; rect.position.set(info.position.x, info.position.y, info.position.z); // rect.rotateY(info.rotateY); Scene.add(rect); }; const onWindowResize = () => { state.Camera.aspect = window.innerWidth / window.innerHeight; state.Camera.updateProjectionMatrix(); state.Renderer.setSize(state.width, state.height); state.LabelRenderer.setSize(state.width, state.height); document.addEventListener('click', meshOnClick); Render(); }; const initPointLight = () => { const pointLight = new THREE.PointLight(0xffffff, 1, 200); pointLight.intensity = 100; pointLight.position.set(0, 200, 0); Scene.add(pointLight); const sphereSize = 1; //点光源辅助线 // const pointLightHelper = new THREE.PointLightHelper( // pointLight, // sphereSize // ); // Scene.add(pointLightHelper); }; const Render = () => { requestAnimationFrame(Render); if (state.mixer) state.mixer.update(clock.getDelta()); //渲染glb文件自带的动画效果 state.Controls.update(); // 轨道控制器的更新 state.Renderer.clear(); // 清除画布 state.Renderer.render(Scene, state.Camera); state.LabelRenderer.render(Scene, state.Camera); const delta = clock.getDelta(); //调试打印相机XYZ的方向位置 // console.log(2222, state.Camera); }; onBeforeMount(() => { // initGltfFloor(); // initModal('wushuichang', 'modelFiles/three_glb/3.gltf'); initModal('wushuichang', 'public/static/three_glb/ChangJing-comp.gltf'); }); onMounted(() => { nextTick(() => { if (document.readyState === 'complete') { // createLable(); // createWaterLevel(); } document.addEventListener('click', meshOnClick); window.addEventListener('resize', onWindowResize, false); }); initLight(); //环境光 initPointLight(); //点光 initSpotLight(); //聚光灯 initDirectionalLight(); //平行光 initRenderer(); initLabelRenderer(); initCamera(); initControls(); initSky(); Render(); }); onUnmounted(() => { 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(); state.Renderer.dispose(); state.Renderer.content = null; document.removeEventListener('click', meshOnClick); window.removeEventListener('resize', onWindowResize, false); cancelAnimationFrame(Render); clearInterval(state.timer); }); return { threeDom, ...toRefs(state), initRenderer, initLabelRenderer, initCamera, initControls, initDirectionalLight, initSky, initLight, Render, meshOnClick, createLable, }; }, computed: {}, methods: {}, }; </script> <style lang="less" scoped> .mt15 { margin-top: 15px; } .cantainer { position: relative; width: 100%; height: 100%; #ShowBox { position: absolute; right: 10px; top: 10px; z-index: 9999; font-size: 18px; color: aqua; cursor: pointer; } #bengzhan { width: 100%; height: 100%; background-image: radial-gradient(circle, #8594b4, #c0c8d8); } .label { padding: 10px; z-index: 9999 !important; width: 200px; .title { line-height: 40px; font-size: 18px; padding: 0 10px; border-bottom: 1px solid #e7f1ff; color: turquoise; } ul { li { padding: 0 10px; line-height: 30px; display: flex; justify-content: space-between; &:nth-child(2n) { background: #e7f1ff; } .pointName { color: #888; } .value { color: #0066ff; } } } .outer { position: absolute; top: 0; left: 0; width: 140px; border-radius: 10px; box-shadow: -2px 0 20px #0060ff; background: rgba(255, 255, 255, 0.712); } .inner { position: absolute; top: -7px; left: -7px; width: 100%; border-radius: 10px; border: 1px solid #0060ff; box-shadow: -2px 0 9px #0060ff; background: rgba(255, 255, 255, 0.685); } .triangle { position: absolute; right: 0; bottom: 0; width: 0; height: 0; border-right: 5px solid #0060ff; border-bottom: 5px solid #0060ff; border-left: 5px solid transparent; border-top: 5px solid transparent; } } } </style>