10.Three.js射线拾取,实现点击选中场景中的物体

10.Three.js射线拾取,实现点击选中场景中的物体

1.射线类Ray

Ray(射线)是类似几何数学中的射线的概念,由一个发射起点和射线方向组成。相当于在三维空间中,一条线把一个点作为起点,然后沿着某个方向无限延伸。可以用来判断是否和几何面,是否相交等等。由很多应用。比如用来计算物体间的距离、物体拾取、是否和物体相交等等。

let ray = new THREE.Ray(origin, direction);

2.Raycaster(射线投射器)

射线投射器可以用于拾取Three场景中的物体。通过发射射线获取相交的网络模型。

2.2 Ray属性

射线投射器Raycaster具有一个射线属性.ray,该属性的值就是上节课讲解的射线对象Ray

const raycaster = new THREE.Raycaster();
console.log('射线属性',raycaster.ray);
// 设置射线起点
raycaster.ray.origin = new THREE.Vector3(-200, 0, 0);
// 设置射线方向射线方向沿着x轴
raycaster.ray.direction = new THREE.Vector3(1, 0, 0);

2.3 射线交叉计算(.intersectObjects()方法)

Raycaster通过.intersectObjects()方法可以计算出来与自身射线.ray相交的网格模型。

const raycaster = new THREE.Raycaster();
raycaster.ray.origin = new THREE.Vector3(-100, 0, 0);
raycaster.ray.direction = new THREE.Vector3(1, 0, 0);
// 射线发射拾取模型对象
const intersects = raycaster.intersectObjects([mesh1, mesh2, mesh3]);
console.log("射线器返回的对象", intersects);

3.屏幕坐标系和标准设备坐标系

Three.js Canvas画布具有一个标准设备坐标系,该坐标系的坐标原点在canvas画布的中间位置,x轴水平向右,y轴竖直向上。

而我们鼠标点击后,获取的位置是屏幕坐标系的数值,因而我们需要把屏幕坐标系转为标准设备坐标系进行转换,从而才能获取鼠标在画布标准设备中的位置,进而使用射线拾取的方式来拾取场景中的物体。

在这里插入图片描述
在这里插入图片描述

转换方法如下

// 坐标转化公式
addEventListener('click',function(event){
    const px = event.offsetX;
    const py = event.offsetY;
    //屏幕坐标px、py转标准设备坐标x、y
    //width、height表示canvas画布宽高度
    const x = (px / width) * 2 - 1;
    const y = -(py / height) * 2 + 1;
})

canvas画布的宽度是width,.offsetX的范围是0width,`.offsetX`除以canvas画布宽度width,就可以从绝对值变成相对值,范围是01,相对值乘以2,范围02,再减去1,范围是-11,刚好和canvas画布标准设备坐标的范围-1~1能够对应起来。

对于.offsetY的转标准设备坐标y,和.offsetX转标准设备坐标x相似,唯一要注意地方就是两个坐标系的y坐标相反,同样计算方式,最后取相反数即可。

4. Raycaster(鼠标点击选中模型)

有了上面的一些基础,我们就可以理解如何实现鼠标点击选中模型啦。思路是当鼠标点击场景时,从相机为起点,以相机和点击位置构成的方向发射一条射线,返回所有相交的物体,第一个物体就是拾取到的物体啦!

代码如下:

//射线拾取
      function initRay() {
        let container = renderer.domElement;
        //设置点击事件
        let selectedObject = null;
        container.addEventListener("click", windowClickEvent); //添加点击事件
        let windowClickEvent = function (event) {
          event.preventDefault();
          if (selectedObject) {
            selectedObject = null;
          }
          var intersects = getIntersects(event.layerX, event.layerY);
          if (
            intersects.length > 0 &&
            selectedObject !== intersects[0].object
          ) {
            var res = intersects.filter(function (result) {
              return result && result.object;
            })[0];
            if (res && res.object) {
              selectedObject = res.object;
              //执行点击事件
			 console.log("selectedObject",selectedObject);
           
              
            }
          } 
        };
        
        //获取相交
        function getIntersects(x, y) {
          let getBoundingClientRect = container.getBoundingClientRect();
          // 声明 raycaster 和 mouse 变量
          var raycaster = new THREE.Raycaster();
          var mouse = new THREE.Vector2();
          // 通过鼠标点击位置,计算出 raycaster 所需点的位置,以屏幕为中心点,范围 -1 到 1
          mouse.x =
            ((x - getBoundingClientRect.left) / container.offsetWidth) * 2 - 1;
          mouse.y =
            -((y - getBoundingClientRect.top) / container.offsetHeight) * 2 + 1;
          //通过鼠标点击的位置(二维坐标)和当前相机的矩阵计算出射线位置
          raycaster.setFromCamera(mouse, camera);
          // 获取与raycaster射线相交的数组集合,其中的元素按照距离排序,越近的越靠前
          let intersects = raycaster.intersectObjects(scene.children, true);
          //返回选中的对象数组
          return intersects;
        }
      }

视频地址:https://www.bilibili.com/video/BV11vSGY3E4b/

猜你喜欢

转载自blog.csdn.net/qq_44849312/article/details/143378488