赞
踩
<panel-group @handleSetLineChartData="handleSetLineChartData" /> <div v-if="tagFlage == 'newVisitis'"> <el-tag v-for="(item, index) of tagData" :key="index" :type="item.flag ? 'danger' : ''" @click="tagClick(item, index)">{{ item.name }}</el-tag> </div> <el-row style="background:#fff;padding:16px 16px 0; margin-bottom:32px;height: calc(100vh - 340px);"> <canvas id="three"></canvas> </el-row> <div id="tag" style="display: none;"></div> <div :id="item.message" v-for="(item, index) of tagData" :key="index" style=" visibility:hidden; width:230px; height:180px; position: absolute; color: #fff; z-index: 2; font-size: 16px; background: rgba(86, 183, 195, 0.47); box-shadow: rgba(86, 183, 195, 0.47) 0px 0px 15px 3px;" :style="{ left: item.x1 + 'px', top: item.y1 + 'px' }"> <div style="position:relative;padding: 10px;"> <div style="display: flex;"> <span>标识:</span> <div id="granaryName" style="font-size:16px">平房仓 P_01</div> </div> <div style="display: flex;align-items: flex-end;margin-top: 5px;"> <span>温度:</span> <span id="temperature">19</span>℃ </div> <div style="display: flex;align-items: flex-end;margin-top: 5px;"> <span>名字:</span> <span id="weight">3600</span> </div> <div style="display: flex;align-items: flex-end;margin-top: 5px;"> <span>高度:</span> <span id="granaryHeight">12</span>m </div> <div style="display: flex;align-items: flex-end;margin-top: 5px;"> <span>宽度:</span> <span id="grainHeight">5</span> m </div> </div> </div> <style lang="scss" scoped> .dashboard-editor-container { padding: 32px; background-color: rgb(240, 242, 245); position: relative; .chart-wrapper { background: #fff; padding: 16px 16px 0; margin-bottom: 32px; } } @media (max-width:1024px) { .chart-wrapper { padding: 8px; } } #import-template { width: 100%; height: 100%; } #three { width: 100%; height: calc(591px - 32px); } .el-tag--medium { margin-right: 10px; } </style> import * as THREE from "three"; import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js"; import { OrbitControls } from "three/examples/jsm/controls/OrbitControls"; import { CSS2DObject, CSS2DRenderer } from 'three/examples/jsm/renderers/CSS2DRenderer.js'; data() { return { tagFlage: null, scene: null, // 场景 camera: null, // 照相机 renderer: null, // 渲染器 // mesh: null, // 网格 // textureLoader: null, // 纹理加载器 // mixer: null, // groupBox: null, // stats: null, // 性能监测 // control: null, // 相机控件 // scene2: null, // render3D: null, publicPath: process.env.BASE_URL, // clearAnim: null, // clock: null, publicPath: process.env.BASE_URL, granaryArr: [], chooseMesh: null, messageTags: [], tagData: [ { name: '场地', message: 'HuaZhuang', x: 10, y: 10, z: 15, x1: -90, y1: -60, flag: false, }, { name: '建筑墙面', message: 'JianZhu_ZhuTi', x: 15, y: 5, z: 1, x1: -50, y1: 50, flag: false, }, { name: '条幅', message: 'TeXiao_010101', x: 15, y: 15, z: -10, x1: 80, y1: -60, flag: false, }, ], idArr: ["granaryName", "temperature", "weight", "granaryHeight", "grainHeight" ], messageData: { HuaZhuang: { granaryName: "数据一(HuaZhu)", temperature: 30, weight: 'RC100', granaryHeight: 6, grainHeight: 25.1 }, JianZhu_ZhuTi: { granaryName: "数据二(JianZhu)", temperature: 30, weight: 'SP500', granaryHeight: 66, grainHeight: 23.8 }, TeXiao_010101: { granaryName: "数据三(TeXiao)", temperature: 30, weight: 'DataAPI', granaryHeight: 666, grainHeight: 9.2 } }, } }, mounted() { this.initThree(); }, methods: { initThree() { let this_ = this this_.scene = new THREE.Scene(); this_.scene.background = new THREE.Color(0xeeeeee); this_.scene.fog = new THREE.Fog(0xa0a0a0, 200, 1000); const canvas = document.querySelector("#three"); this_.renderer = new THREE.WebGLRenderer({ canvas, antialias: true, alpha: true }); this_.renderer.shadowMap.enabled = true; this.camera = new THREE.PerspectiveCamera( 40, window.innerWidth / window.innerHeight, 1, 1000 ); const controls = new OrbitControls(this.camera, this.renderer.domElement); const gltfLoader = new GLTFLoader(); gltfLoader.load(`${this.publicPath}factory/XXX.gltf`, (gltf) => { let model = gltf.scene; model.scale.set(1, 1, 1); controlsBox(model); gltf.scene.traverse(function (object) { if (object.type === 'Mesh') { // 批量更改所有Mesh的材质 object.material = new THREE.MeshLambertMaterial({ map: object.material.map, //获取原来材质的颜色贴图属性值 color: object.material.color, //读取原来材质的颜色 }) } }) gltf.scene.children.forEach((obj, index) => { if (obj.type === 'Mesh') { this_.granaryArr.push(obj); } }) this_.scene.add(model); }, undefined, (error) => console.error(error)); // 将模型的中心点设置到canvas坐标系的中心点,保证模型显示是居中的 function controlsBox(model) { let box = new THREE.Box3().setFromObject(model); // 获取模型的包围盒 let mdlen = box.max.x - box.min.x; // 模型长度 let mdwid = box.max.z - box.min.z; // 模型宽度 let mdhei = box.max.y - box.min.y; // 模型高度 let xPoiition = box.min.x + mdlen / 2; // 模型中心点坐标X let yPoiition = box.min.y + mdhei / 2; // 模型中心点坐标Y let zPoiition = box.min.z + mdwid / 2; // 模型中心点坐标Z // 获取模型整体对角线长度,这里获取模型模型对角线的目的是为了保证模型可以完全的展示在视线范围内 let diagonal = Math.sqrt(Math.pow(Math.sqrt(Math.pow(mdlen, 2) + Math.pow(mdwid, 2)), 2) + Math.pow(mdhei, 2)); // 假设我们需要的进入视角为45度 // 设置相机位置,向上偏移,确定可以包裹整个模型 controls.object.position.set(box.max.x + diagonal / 2, (diagonal * 2) / Math.tan(Math.PI / 180 * 45) + Math.abs(box.max.y), box.max.z + diagonal / 2); // 设置相机的视角方向,看向模型的中心点 controls.target.set(xPoiition, yPoiition, zPoiition); controls.update(); // 更新相机 } const hemLight = new THREE.HemisphereLight(0xffffff, 0xffffff); hemLight.position.set(0, 20, 0); this_.scene.add(hemLight); const dirLight = new THREE.DirectionalLight(0xffffff, 0.6); //光源等位置 dirLight.position.set(0, 20, 10); //可以产生阴影 dirLight.castShadow = true; dirLight.shadow.mapSize = new THREE.Vector2(1024, 1024); this_.scene.add(dirLight); // 添加地板 let floorGeometry = new THREE.PlaneGeometry(8000, 3000); let floorMaterial = new THREE.MeshPhongMaterial({ color: 0x5cb3cc, shininess: 0, }); let floor = new THREE.Mesh(floorGeometry, floorMaterial); floor.rotation.x = -0.5 * Math.PI; floor.receiveShadow = true; floor.position.y = -0.001; // this_.scene.add(floor); controls.enableDamping = true; this.tagData.forEach((item) => { this.labelRenderer = new CSS2DRenderer(); let three = document.querySelector('#three') this.labelRenderer.setSize(three.getBoundingClientRect().width,three.getBoundingClientRect().height); this.labelRenderer.domElement.style.position = 'absolute'; this.labelRenderer.domElement.style.top = '320px'; this.labelRenderer.domElement.style.left = '250px'; this.labelRenderer.domElement.style.pointerEvents = 'none'; document.body.appendChild(this.labelRenderer.domElement); var messageTag = this.tag(item.message); //创建粮仓标注的标签 this.scene.add(messageTag); this.messageTags.push(messageTag) }) function animate() { controls.update(); this_.renderer.render(this_.scene, this_.camera); this_.labelRenderer.render(this_.scene, this_.camera); //渲染HTML标签对象 requestAnimationFrame(animate); if (resizeRendererToDisplaySize(this_.renderer)) { const canvas = this_.renderer.domElement; this_.camera.aspect = canvas.clientWidth / canvas.clientHeight; this_.camera.updateProjectionMatrix(); } } animate(); function resizeRendererToDisplaySize(renderer) { const canvas = renderer.domElement; var width = window.innerWidth; var height = window.innerHeight; var canvasPixelWidth = canvas.width / window.devicePixelRatio; var canvasPixelHeight = canvas.height / window.devicePixelRatio; const needResize = canvasPixelWidth !== width || canvasPixelHeight !== height; if (needResize) { renderer.setSize(width, height, false); } return needResize; } }, } tagClick(item, index) { let this_ = this var messageTag = this.tag(item.message); let visibilitys = messageTag.element.style.visibility let chooseMeshData = null chooseMeshData = this_.choose(event, messageTag, 2, item.message); //执行射线拾取的代码 // 选中不同粮仓,HTML标签信息跟着改变 if (chooseMeshData) { //批量更新粮仓chooseMesh的标签信息 this_.idArr.forEach(function (id) { var dom = document.querySelector(`#${item.message} #${id}`) dom.innerHTML = this_.messageData[chooseMeshData.name][id]; }) this.messageTags.forEach(function (messageTag) { // 判断是否为当前需要更新的弹窗对象 if (document.querySelector(`#${item.message}`).id == messageTag.element.id) { // 替判断当前弹窗对象的条件 if (visibilitys == 'visible') { messageTag.element.style.visibility = 'hidden'; this_.tagData[index].flag = false; } else { messageTag.element.style.visibility = 'visible'; this_.tagData[index].flag = true; } // 创建线条材质 var lineMaterial = new THREE.LineBasicMaterial({ color: 0xff0000, linewidth: 100 }); // 获取点击模型和弹窗的位置坐标 var modelPosition = chooseMeshData.position.clone(); var newPositions = new THREE.Vector3().copy(messageTag.position); // 创建新的Vector3对象并复制点击对象的坐标 messageTag.position.copy(newPositions); // var popupPosition = messageTag.position.clone(); var popupPosition = messageTag.position.copy({ x: item.x, y: item.y, z: item.z }); // 创建线条的顶点数组 var positions = []; positions.push(modelPosition.x, modelPosition.y, modelPosition.z); positions.push(popupPosition.x, popupPosition.y, popupPosition.z); // 创建线条的属性数组 var geometry = new THREE.BufferGeometry(); geometry.setAttribute('position', new THREE.Float32BufferAttribute(positions, 3)); // 创建线条对象 var line = new THREE.LineSegments(geometry, lineMaterial); // 添加线条到场景中 line.name = item.message this_.scene.add(line); // 获取场景中的所有线段 var allLineSegments = []; this_.scene.traverse(function (child) { if (child instanceof THREE.LineSegments) { allLineSegments.push(child); } }); // 打印所有线段 if (visibilitys == 'visible') { allLineSegments.forEach((node) => { if (node.name == item.message) { this_.scene.remove(node); } }) } } }); } }, tag(domID) { var dom = document.getElementById(domID); var label = new CSS2DObject(dom); label.name = domID dom.style.pointerEvents = 'none'; return label; }, choose(event, messageTag, type, name) { let chooseMesh = null if (type == 1) { //这个逻辑里用不上,正常是点击模型里的 后来改成点击按钮了 if (this.chooseMesh) { this.chooseMesh.material.color.set(0xffffff); // 把上次选中的mesh设置为原来的颜色 } var Sx = event.clientX; var Sy = event.clientY; let mainCanvas = document.querySelector('canvas') var x = ((event.clientX - mainCanvas.getBoundingClientRect().left) / mainCanvas.offsetWidth) * 2 - 1; var y = -((event.clientY - mainCanvas.getBoundingClientRect().top) / mainCanvas.offsetHeight) * 2 + 1; var raycaster = new THREE.Raycaster(); raycaster.setFromCamera(new THREE.Vector2(x, y), this.camera); var intersects = raycaster.intersectObjects(this.granaryArr); console.log("射线器返回的对象", intersects); if (intersects.length > 0) { this.chooseMesh = intersects[0].object; this.chooseMesh.material.color.set(0x00ffff); //选中改变颜色,这样材质颜色贴图.map和color颜色会相乘 this.chooseMesh.point = intersects[0].point; } else { this.chooseMesh = null; } } else { this.granaryArr.forEach((item) => { if (item.name == name) { console.log(item, 'd点击后的item', item.material.color.getHex()) chooseMesh = item; if (item.material.color.getHex() == 16777215) { chooseMesh.material.color.set(0xff0000); //选中改变颜色,这样材质颜色贴图.map和color颜色会相乘 } else { chooseMesh.material.color.set(0xffffff); // 把上次选中的mesh设置为原来的颜色 } chooseMesh.point = item.position; // chooseMesh.point = item.point; // this.chooseMesh.point = { // x: item.x, // y: item.y, // z: item.z // } } }) } return chooseMesh }, handleSetLineChartData(type) { console.log(type) this.tagFlage = type },
以上就是完整的,
弹窗的位置,和线的结束点是我写死的,因为我不知道创建的弹窗的位置该怎么设置
新手three 有大佬的话 可以给些建议
后续补充了一点新的 弹窗位置和线条的起始结束点
给我的当前弹窗点击拿到后设置
var popupPosition = messageTag.position.copy({
x: item.x,
y: item.y,
z: item.z
});
这组就是线条的结束点 push到一个存起点和结束点的数组里然后放到THREE.Float32BufferAttribute
数据的坐标点基本都这样的
{
name: ‘XXX’,
message: ‘XXX’,
x: -2,
y: 0.8,
z: 1,
x1: -43,
y1: -45,
flag: false,
},
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。