赞
踩
可以将属性信息绑定到模型中,添加点击事件,使用Raycaster射线求交获取到最近的模型对象,结合CSS2DObject,就可将信息展示出来了,我封装了一个Popup弹框显示类,具体代码如下,后面会附上完整代码
modelScene() { let self=this let containerElement = document.querySelector(".mapModel"); this.updateContainerElement(containerElement); // 计算 容器元素 高宽,左偏移值,顶偏移值 scene = new THREE.Scene(); sceneManager.switchMapping(scene); // 场景 贴图更换 stats = new Stats(); document.body.appendChild(stats.dom); camera = new THREE.PerspectiveCamera(45, clientWidth / clientHeight, 1, 1000000); camera.position.set(-41.84, 8.44, 114.57); //设置相机位置 camera.lookAt(new THREE.Vector3(0, 0, 0)); //设置相机方向(指向的场景对象) global.camera=camera // 创建一个CSS2渲染器CSS2DRenderer labelRenderer=sceneManager.createCSS2Renderer(window.innerWidth, window.innerHeight) const labelControls = new OrbitControls( camera, labelRenderer.domElement ); let renderer = new THREE.WebGLRenderer(); // 设置与容器元素相同大小 renderer.setSize(clientWidth, clientHeight); containerElement.appendChild(renderer.domElement); global.renderer=renderer const controls = new OrbitControls(camera, renderer.domElement); global.webglControl=controls const light = new THREE.HemisphereLight(0xffffff, 0xcccccc, 1); scene.add(light); // 添加光源 const light_a = new THREE.AmbientLight(); //scene.add(light_a); // 效果组合器 composer = new EffectComposer(renderer); const renderPass = new RenderPass(scene, camera); composer.addPass(renderPass); // 选择模型外边框 outlinePass = sceneManager.createOutlinePass(clientWidth,clientHeight,scene,camera) composer.addPass(outlinePass); const effectFXAA = new ShaderPass(FXAAShader); effectFXAA.uniforms["resolution"].value.set(1 / window.innerWidth, 1 / window.innerHeight); composer.addPass(effectFXAA); // 选中高亮 raycaster = new THREE.Raycaster(); mouse = new THREE.Vector2(1, 1); const mousePosition = { x: 0, y: 0 }; function onMouseMove(event) { event.preventDefault(); mousePosition.x = event.clientX; mousePosition.y = event.clientY; mouse.x = ((event.clientX - offsetLeft) / clientWidth) * 2 - 1; mouse.y = -((event.clientY - offsetTop) / clientHeight) * 2 + 1; } containerElement.addEventListener("mousemove", onMouseMove, false); function onClick (event) { event.preventDefault(); debugger var windowX = event.clientX;//鼠标单击位置横坐标 var windowY = event.clientY;//鼠标单击位置纵坐标 const mousePoint = new THREE.Vector2(); mousePoint.x = (windowX / window.innerWidth) * 2 - 1;//标准设备横坐标 mousePoint.y = -(windowY / window.innerHeight) * 2 + 1;//标准设备纵坐标 //const rayCaster = new THREE.Raycaster(); raycaster.setFromCamera(mousePoint, camera); //返回射线选中的对象 clickSelects= raycaster.intersectObjects(scene.children,true); //console.log(clickSelects); if (clickSelects.length > 0) { let selectObj=clickSelects[0]; //射线在模型表面拾取的点坐标 let point=selectObj.point let position=[point.x,point.y,point.z] if(selectObj.object.popupContent||selectObj.object.parent.popupContent){ let popupContent=selectObj.object.popupContent||selectObj.object.parent.popupContent let title=popupContent.title?popupContent.title:'提示' let content=popupContent.content?popupContent.content:'无内容' let popupClass=popupContent.cssClass?popupContent.cssClass:'' //popupWidget.containerNode.innerHTML='哈哈我被点击拉' popupWidget.setLabelValue(content,title,popupClass) popupWidget.setPosition(position) popupWidget.show() } //触发选择模型 调整位置 /*if(global.selectMode){ let event = document.createEvent('Event'); event.initEvent("clickObject3D", true, true); event.object3D = selectObj.object document.dispatchEvent(event); }*/ } } containerElement.addEventListener("click", onClick, false); }
//添加弹窗
popupWidget=new PopupWidget('暂无信息','信息',labelRenderer)
scene.add(popupWidget.CSS2Label)
// 添加动画
this.animate();
animate() { // TWEEN.update(); let intersection if(clickSelects.length===0){ raycaster.setFromCamera(mouse, camera); intersection = raycaster.intersectObjects(itemList); }else{ intersection=clickSelects } if (intersection.length > 0) { // console.log(intersection[0].object.name); outlinePass.selectedObjects = [intersection[0].object]; } else { outlinePass.selectedObjects = []; } if (this.moveType) { scene.rotateY(0.001); //每次绕y轴旋转0.01弧度 } composer.render(); labelRenderer.render(scene, camera); requestAnimationFrame(this.animate); stats.update(); // 获取相机方向 let d1=new THREE.Vector3() camera.getWorldDirection(d1); if(wanderControl.personModel){ if(d1){ wanderControl.personModel.lookAt(d1.x,d1.y,d1.z); } } }
import { CSS2DObject } from 'three/examples/jsm/renderers/CSS2DRenderer' import Utils from './Utils' /** * @Author :TanShiJun 1826356125@qq.com * @Date :2022/5/26 * @Describe :弹框 * Last Modified by : TSJ * Last Modified time :2022/5/26 **/ export default class PopupWidget { constructor (content,title,labelRenderer) { this.domNode=null this.labelRenderer=labelRenderer this.containerNode=null this.CSS2Label=this._init(content,title) this.hide() } /** * 初始化内容 默认隐藏 * @param content 内容 * @param title 标题 * @returns {CSS2DObject} * @private */ _init(content,title) { let self=this let tempStr='<span class="closeButton" title="关闭">×</span>\n' + ' <div class="popup-content-wrapper">\n' + ' <div class="titlePane">'+title+'</div>\n' + ' <div class="contentPanel">\n' if(Array.isArray(content)){ tempStr+=this._creatContentStr(content) } else if(typeof(content) === "string"||Utils.checkHtml(content)){ tempStr+=content } tempStr+=' </div>\n' + ' </div>\n' + ' <div class="three-popup-tip-container">\n' + ' <div class="three-popup-tip"></div>\n' + ' </div>' this.containerNode=document.createElement('div') this.containerNode.setAttribute('class','three-Popup'); this.containerNode.innerHTML=tempStr const closeDom=Utils.getClass('closeButton',this.containerNode) if(closeDom.length>0){ closeDom[0].addEventListener('click', function () { self.hide() }) } let css2Obj=new CSS2DObject(this.containerNode); css2Obj.position.set(-12.895390080777755,8.051334095702684,-3.9104457197725537) return css2Obj; } show(){ this.CSS2Label.visible = true this.labelRenderer.domElement.style.display='block' } hide(){ this.CSS2Label.visible = false this.labelRenderer.domElement.style.display='none' } // 设置位置 setPosition(position){ this.CSS2Label.position.set(position[0],position[1]+4,position[2]); //this.CSS2Label.position.set(-12.895390080777755,8.051334095702684,-3.9104457197725537); //标签标注在obj世界坐标 } /** * 设置名称 和 值 ; 设置标题 * @param object [] { * key:'', * value:'' * } */ setLabelValue(objects,title,cssClass){ let classList=this.containerNode.classList if(cssClass){ // 获取类名,返回数组 classList.add(cssClass) }else{ if(classList.length===2){ classList.remove(classList[1]) } } const contentDom=Utils.getClass('contentPanel',this.containerNode) if(contentDom.length>0){ contentDom[0].innerHTML=this._creatContentStr(objects) } if(title){ const titleDom=Utils.getClass('titlePane',this.containerNode) if(titleDom.length>0){ titleDom[0].innerHTML=title } } } /** * 创建内容 * @param content * @returns {string} * @private */ _creatContentStr(content){ let contentStr='' if(Array.isArray(content)){ for(let i=0;i<content.length;i++){ contentStr+='<div class="three-Popup-li">' if(content[i].label){ contentStr+='<div class="Popup-label">' +content[i].label+'</div>' } if(content[i].value){ contentStr+='<div class="Popup-value">' +content[i].value+'</div>' } contentStr+='</div>' } }else{ contentStr=content } return contentStr } }
/** * @Author :TanShiJun 1826356125@qq.com * @Date :2022/5/26 * @Describe :工具类 * Last Modified by : TSJ * Last Modified time :2022/5/26 **/ export default class Utils { // 判断是否是dom节点 static isNode(o) { return ( typeof Node === "object" ? o instanceof Node : o && typeof o === "object" && typeof o.nodeType === "number" && typeof o.nodeName==="string" ) } static isElement(o) { return ( typeof HTMLElement === "object" ? o instanceof HTMLElement : //DOM2 o && typeof o === "object" && o !== null && o.nodeType === 1 && typeof o.nodeName==="string" ); } /** * 字符串是否含有html标签的检测 * @param htmlStr */ static checkHtml(htmlStr) { var reg = /<[^>]+>/g; return reg.test(htmlStr); } /** * * 作用:根据指定的类名查找元素 * * 参数: * @param classname:需要查找的类名(字符串) * @param oParent(可有可无):需要查找的元素的父级(对象),如果没 * 传入,默认为document;如果需要缩小范围,提高查找速度,可以 * 给值(建议给) * * 函数内局部变量: * oChild 根据父级oParent获取到的该标签下的所有标签 * arr 存储拥有需要查找的classname的元素 * * 步骤: * 1.判断是否有传入oParent,如果没有传入,则赋予初始值document * 2.获取父级oParent下的所有标签并存储到oChild中 * 3.定义空数组arr * 4.对oChild进行循环,利用字符串函数indexOf对每个元素的类名进行查找( * 类名可能不止一个),如果在类名中找到了传入进来的类名,便将拥有该类名 * 的元素添加到arr中 * 5.循环完毕,将arr返回 */ static getClass(classname, oParent){ if(!oParent){ oParent = document; } var oChild = oParent.getElementsByTagName('*'); var arr = []; for(var i = 0, len = oChild.length; i < len; i ++){ // indexOf,在字符串中查找指定字符,如果查找到了,返回该字符 // 在字符串中的索引;如果没有找到,返回-1 // 索引有可能为0,所以大于等于0就意味着找到 if(oChild[i].className.indexOf(classname) >= 0){ arr.push(oChild[i]); } } return arr; } }
/*弹出气泡样式 开始*/ .three-Popup { /*background-color: rgba(0, 66, 66, 0.4);*/ color: #ffffff; font-size:18px; /* padding:8px 12px;*/ width: 300px; height: auto; } /*视频弹框*/ .three-Popup.popup-video { width: 600px; height: 450px; } .three-Popup .popup-content-wrapper { background: linear-gradient(#00ffff, #00ffff) left top, linear-gradient(#00ffff, #00ffff) left top, linear-gradient(#00ffff, #00ffff) right bottom, linear-gradient(#00ffff, #00ffff) right bottom; background-repeat: no-repeat; background-size: 1px 6px, 6px 1px; background-color: rgba(0, 66, 66, 0.7); /*text-align: center;*/ box-shadow: 0 3px 14px rgba(0,0,0,0.4); padding: 1px; text-align: left; border-radius: 3px; } .closeButton { position: absolute; top: 0; right: 0; padding: 4px 4px 0 0; text-align: center; width: 20px; height: 20px; font: 16px/14px Tahoma, Verdana, sans-serif; text-decoration: none; font-weight: bold; background: transparent; z-index: 20170825; cursor: pointer; } .three-Popup .titlePane { height: 30px; line-height: 30px; font-size: 14px; background-color: rgba(0, 0,0, 0.4); padding-left:5px ; } .three-Popup .contentPanel { margin-top: 5px; } .three-Popup .three-Popup-li { height: auto; line-height: 1; font-size: 0; border-top: 1px dotted #dadada; white-space: nowrap; padding-left: 5px; } .three-Popup .three-Popup-li:first-child { border: none; } .three-Popup .three-Popup-li:hover { /*cursor: pointer; background-color: #F5F7FA;*/ } .three-Popup .contentPanel .Popup-label { display: inline-block; width: 80px; line-height: 30px; font-size: 14px; font-weight: 700; vertical-align: middle; white-space: nowrap; text-overflow: ellipsis; overflow: hidden; } .three-Popup .contentPanel .Popup-value { display: inline-block; padding-left: 5px; font-size: 14px; line-height: 22px; vertical-align: middle; width: 200px; white-space: normal; } .three-popup-tip-container { margin: 0 auto; width: 40px; height: 20px; position: relative; overflow: hidden; } .three-popup-tip-container .three-popup-tip { box-shadow: 0 3px 14px rgba(0, 0, 0, 0.4); width: 17px; height: 17px; padding: 1px; margin: -10px auto 0; -webkit-transform: rotate(45deg); -moz-transform: rotate(45deg); -ms-transform: rotate(45deg); -o-transform: rotate(45deg); /* transform: rotate(45deg); */ }
绑定属性可以使html片段,可以是属性信息,属性信息类似于如下格式:
{
content:[{
label:'报警信息',
value:'无异常'
},
{
label:'温度',
value:'37℃'
}
],
title:'监控点'
}
绑定属性
/** * 加载标记点 * @param markerPointCfgs { * name, * floorName, 所属楼层名称 * size:0.5, 大小 * hasWave:false, 是否开启波纹 * content:[{label,value}] * } */ createMarkerPoint(markerPointCfgs){ for(let i=0;i<markerPointCfgs.length;i++){ const pointConfig=markerPointCfgs[i] // 创建的锥形标记模型,绑定显示的属性 const pyramidPoint=createPyramidPoint(pointConfig.name,pointConfig.size,pointConfig.hasWave) // 绑定属性 if(pointConfig.popupContent){ pyramidPoint.popupContent=pointConfig.popupContent } if(pointConfig.position){ pyramidPoint.position.set(pointConfig.position[0],pointConfig.position[1],pointConfig.position[2]) } const markerGroup=this.markerPoints.find((pt)=>{ return pt.name===pointConfig.floorName }) if(markerGroup){ markerGroup.add(pyramidPoint) //this.scene.add(markerGroup) } } }
如有不足之处,欢迎来指正,QQ 1826356125
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。