three.js点击模型弹框显示属性信息

思路

可以将属性信息绑定到模型中,添加点击事件,使用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);
        }
      }
		}

Popup类

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

  • 3
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值