vue2+three.js做出一个精美的3D地图——5.使用射线(Raycaster)和Canvas贴图来做一些交互


前言

代码地址 : https://gitee.com/txcst/3-dmap.git 只想好好做开源。
上一期我们已经把精灵(sprite)和模型引入进来了,这一期主要使用射线 去捕捉几何体,产生一些交互,顺便添加一张背景图片

一、如何与模型产生交互

光线投射Raycaster:主要用于在三维物体中拾取几何体,你可以把他当做一把枪,鼠标按下的时候就是扣下了扳机。发射出一颗子弹打穿你场景里的所有几何体并呈现给你
setRaycaster() {
      let onMouseMove = (event) => {
        var Sx = event.clientX; //鼠标单击位置横坐标
        var Sy = event.clientY; //鼠标单击位置纵坐标
        //屏幕坐标转WebGL标准设备坐标
        var x = (Sx / window.innerWidth) * 2 - 1; //WebGL标准设备横坐标
        var y = -(Sy / window.innerHeight) * 2 + 1; //WebGL标准设备纵坐标
        //创建一个射线投射器`Raycaster`
        var raycaster = new THREE.Raycaster();
        //通过鼠标单击位置标准设备坐标和相机参数计算射线投射器`Raycaster`的射线属性.ray
        raycaster.setFromCamera(new THREE.Vector2(x, y), camera);
        //返回.intersectObjects()参数中射线选中的网格模型对象
        // 未选中对象返回空数组[],选中一个数组1个元素,选中两个数组两个元素
        //加上true 代表遍历子元素
        var intersects = raycaster.intersectObjects(scene.children, true);
        // intersects.length大于0说明,说明选中了模型
        if (intersects.length > 0) {
        }
      };
      // document.addEventListener('click', onMouseMove, false)
      document
        .getElementsByTagName("canvas")[0]
        .addEventListener("click", onMouseMove, false);
    },

intersects[0].object 就代表着你射线透射穿透的第一个模型

接下来我们给我们要找的几何体加一个标识,好让我们去识别到它
让我们回到 getSpiritAndGltf函数,
在生成兵马俑的精灵的时候 给他添加上name,叫做 bingmayong

  let sprite = new THREE.Sprite(spriteMaterial);
        sprite.position.set(item.arr[0][0], item.arr[0][1], 8);
        sprite.scale.set(8, 8, 8);
        sprite.name = "bingmayong";

在setRaycaster方法 射线中找到名字叫bingmayong的 sprite

var intersects = raycaster.intersectObjects(scene.children, true);
    // intersects.length大于0说明,说明选中了模型
    if (intersects.length > 0) {
       //由于我的sprite 前面没有几何体  所以第一个就是他  如果你想找的几何体有遮挡,你可以使用find方法
      if (intersects[0].object.name == 'bingmayong') {
           console.log('点击了 bingmayong')
       
      } 

    }

点击的效果已经出来了
在这里插入图片描述

二、使用Canvas贴图

1.随便创建一个画布贴图,绑定一个平面几何体,并让这个几何体隐藏起来

visible属性决定了几何体的显示和隐藏

//创建canvans贴图
    getCanvas() {
      canvas = document.createElement("canvas");
      canvas.width = 195
      canvas.height = 130
      //创建canvas贴图
      let canvasTexture = new THREE.CanvasTexture(canvas);
      let material = new THREE.MeshBasicMaterial({ map: canvasTexture });
      //创建平面几何体
      let planeGeometry = new THREE.PlaneGeometry(20, 21);
      //创建网格对象
      canvasPlane = new THREE.Mesh(planeGeometry, material);
      canvasPlane.position.set(0, 0, -20)
      canvasPlane.rotation.x = -5.5;
      //隐藏
      canvasPlane.visible = false
      map.add(canvasPlane);
    },

接下来的逻辑 ,当点到bingmayong的sprite时 把兵马俑的资料和位置传递出来,修改几何体 planeGeometry 的位置和canvas贴图 并让他显示

point属性代表着 当前点击的对象的位置

 if (intersects[0].object.name == 'bingmayong') {
            // console.log('点击了 bingmayong')
            let bmyText = '兵马俑,亦简称秦兵马俑或秦俑,是第一批全国重点文物保护单位、第一批中国世界遗产,位于今陕西省西安市临潼区秦始皇陵以东1.5千米处的兵马俑坑内。'

            this.setCanvas(bmyText, [intersects[0].point.x, intersects[0].point.y])

          }

获取canvas的上下文对象

  setCanvas(text, position) {
      let ctx = canvas.getContext('2d')
      this.drawText(text, ctx, position)
    },

接下里就是根据传的资料 画新的贴图并且修改位置
因为canvas不支持换行,所以要计算一下字符长度 并手动换行

 drawText(text, ctx, position) {
      ctx.clearRect(0, 0, canvas.width, canvas.height); // 清除整个canvas
      // 设置背景颜色为蓝色  
      ctx.fillStyle = '#36FFFF';
      // 绘制一个矩形,该矩形的背景颜色为前面设置的蓝色  
      ctx.fillRect(0, 0, 200, 130);
      let promise = new Promise(function (resolve, reject) {
        ctx.font = "15px Arial";
        ctx.fillStyle = "#000000";
        var lineWidth = 200; // 每行文本的最大宽度,可根据实际需求进行调整  
        var lineHeight = 20; // 每行文本的高度差值,可根据实际需求进行调整  
        var x = 10; // 文本起始位置的x坐标,可根据实际需求进行调整  
        var y = 20; // 文本起始位置的y坐标,可根据实际需求进行调整  
        var lineCount = 0; // 当前行数,用于控制换行次数,可根据实际需求进行调整  
        for (var i = 0; i < text.length; i++) {
          var charWidth = ctx.measureText(text[i]).width; // 当前字符的宽度,用于判断是否需要换行,并计算该字符占用空间的大小  

          if (x + charWidth > lineWidth) { // 如果当前字符无法容纳在当前行中,需要换行并增加一行的高度差值(即多增加一行)  
            x = 10; // 重置x坐标,开始新的一行,即从下一行的起始位置开始重新绘制字符(相当于清空当前行的绘制)
            y += lineHeight; // 增加一行的高度差值  
            lineCount++; // 增加一行数  
          }
          ctx.fillText(text[i], x, y); // 绘制当前字符  
          x += charWidth; // 更新x坐标,准备绘制下一个字符  
        }
        resolve('成功')
      });
      promise.then(res => {
        //从新创建一个canvas贴图
        let canvasTexture = new THREE.CanvasTexture(canvas);
        let newMaterial = new THREE.MeshBasicMaterial({ map: canvasTexture });
        //替换掉贴图
        canvasPlane.material = newMaterial
        //显示
        canvasPlane.visible = true
        //根据传过来的位置,给几何体从新定位
        canvasPlane.position.set(...position, 14)
      })

    },

现在的效果就算初步完成了 ,然后我们再给故宫的模型加上判断
如果点击到别的地方没有识别到物体 就让canvas隐藏

 if (intersects.length > 0) {
           
          if (intersects[0].object.name == 'bingmayong') {
            // console.log('点击了 bingmayong')
            let bmyText = '兵马俑,亦简称秦兵马俑或秦俑,是第一批全国重点文物保护单位、第一批中国世界遗产,位于今陕西省西安市临潼区秦始皇陵以东1.5千米处的兵马俑坑内。'

            this.setCanvas(bmyText, [intersects[0].point.x, intersects[0].point.y])

          } else if (intersects[0].object.name.includes('Object')) {
            let ggText = '故宫是中国明清两代的皇家宫殿,旧称紫禁城,位于北京中轴线的中心。它占地面积约72万平方米,建筑面积约15万平方米,有大小宫殿七十多座。'

            this.setCanvas(ggText, [intersects[0].point.x, intersects[0].point.y])
          } else {
            canvasPlane.visible = false

          }

        }

我们再给他 加上一个背景图

 getScene() {
      scene = new THREE.Scene();
      //设置背景
    new THREE.TextureLoader().load("/data/sceneBack.png",(texture)=>{
      scene.background = texture
      });
    
    },

背景图会受到双通道渲染的影响,导致发光 ,这里需要从写一下render方法,在渲染的时候把背景筛出来

 render() {
      scene.traverse((obj) => {
        if (  obj instanceof THREE.Scene) {
                this.materials = obj.background
                obj.background = null
            }
        if (bloomLayer.test(obj.layers) === false) {
          materials[obj.uuid] = obj.material;
          obj.material = darkMaterial;
        }
      });

      bloomComposer.render();
      scene.traverse((obj) => {
        if (materials[obj.uuid]) {
          obj.material = materials[obj.uuid];
          delete materials[obj.uuid];
        }
        if (obj instanceof THREE.Scene) {
          obj.background = this.materials
                delete this.materials
            }
      });
      finalComposer.render();
    },

最终效果

额 感觉有点丑…暂时还没想好样式,先这样把 主要是鼠标点击几何体的一个交互
在这里插入图片描述

总结

提示:这里对文章进行总结:

下一期做什么还没想好 … 有可能会开一个Cesium的新坑,好了就这样

  • 26
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
/* *author:XudongChen *Date:2010-03-09 *QQ:233828249 81023617(不才) *Email:xznd@163.com */ 2009-8-13 1.加载分块地图 2.添加全景标记窗体 2009-8-14 1.增加控制条 2.增加全景标记、公交车标记显示层 3.解决地图定位问题 4.增加经纬度层功能 5.未修正图标层的定位 2009-8-15 1.已修正8.14地图定位错误,还存在放大缩小时定位不准 2.存在ie内存泄漏问题 2009-8-16 1.已修正8.15 ie内存泄漏问题,chrome下可能还存在内存泄漏 2.增加图标定位功能 3.增加鼠标滚轮事件(http://yongzhi.blog.hexun.com/5057947_d.html) 4.通过jquery加载json数据文件(图标显示层数据) 2009-8-17 1.增加建筑物高亮显示(还需完成鼠标mouseover和mouseout事件) 2009-8-18 1.完成建筑物高亮显示,有点小bug 2009-8-19 1.增加记录原始缩放比例的全局变量 2.解决建筑物高亮显示bug 2009-8-20 1.增加三维全景展示功能 2.浏览建筑详细功能 2009-8-21 1.引入jqueryAlert插件,美化弹出窗体 2009-8-23 1.增加搜索功能 2.清理建筑信息显示页和公交信息页多余数据 2009-10-22 1.增加小沙盘拖动类(鹰眼视图) BirdEye.js 完成小沙盘到地图的同步,同步方法:检测mouseup事件触发->修改url->request->计算坐标->同步行为 2.增加小沙盘样式表BirdEye.css 地图图片路径birdeyemap 3.完成window.parent地图->小沙盘的同步 问题:小沙盘->window.parent地图存在bug,可以尝试开启 2009-10-23 1.在小沙盘中增加浮动绿色框 2009-11-6 1.实现“鹰眼地图”不需移动,一幅可以看到见全景, 当主场景移动时,“鹰眼地图”只有小框在移动。 同时“小框” 主场景也在移动。 2009-12-17 1.测距功能事件配置 2009-12-21 1.完成测距功能 2009-12-22 1.配置搜索功能,后台改用s2sh框架 2.完成hessian+spring+hibernate整合,提供建筑信息和公司信息hessian查找服务 2009-12-23 1.配置hessian服务端缓存 2.配置hessian日志记录,输出到文件/log/wzucxd/html 3.完成建筑信息显示页,配置2级缓存 4.配置oscache 5.配置新闻信息模块 2010-1-13 1.完成chrome和ff下的搜索功能 2.搜索功能支持ie6.0+ //设置Theodolite$setPoint var cpointtmp = new CPoint(this.holder.offsetLeft + evt.clientX - this.mvl.offsetLeft, this.mvl.offsetTop + evt.clientY - this.mvl.offsetTop); Theodolite$setPoint(cpointtmp);
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值