当前位置:   article > 正文

three.js点击模型弹框显示属性信息_three.js弹出显示模型不同部分信息

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);


		}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115

添加弹窗

	//添加弹窗
      popupWidget=new PopupWidget('暂无信息','信息',labelRenderer)
      scene.add(popupWidget.CSS2Label)
      // 添加动画
      this.animate();
  • 1
  • 2
  • 3
  • 4
  • 5

添加动画

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);
        }
      }
		}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34

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
  }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129

工具类

/**
 * @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;
  }

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74

弹框样式

/*弹出气泡样式 开始*/
.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); */
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111

绑定模型属性格式

绑定属性可以使html片段,可以是属性信息,属性信息类似于如下格式:

{
        content:[{
          label:'报警信息',
          value:'无异常'
        },
          {
            label:'温度',
            value:'37℃'
          }
        ],
        title:'监控点'
      }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

绑定属性

/**
   * 加载标记点
   * @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)
      }
    }
  }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31

最终效果

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
如有不足之处,欢迎来指正,QQ 1826356125

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Gausst松鼠会/article/detail/95825
推荐阅读
相关标签
  

闽ICP备14008679号