ThreeJS中的点击与交互——Raycaster的用法

基础概念

坐标系

我们的手机屏幕是二维的,但是我们展示物体的世界是三维的,当我们在构建一个物体的时候我们是以一个三维世界既是世界坐标来构建,而转化为屏幕坐标展示在我们眼前,则需要经历多道矩阵变化,中间webGL替我们操作了许多事情。

clipboard.png

  • 世界坐标系:在webGL中,世界坐标系是以屏幕中心为原点(0, 0, 0),且是始终不变的。你面对屏幕,你的右边是x正轴,上面是y正轴,屏幕指向你的为z正轴。长度单位这样来定:窗口范围按此单位恰好是(-1,-1,-1)到(1,1,1)。
  • 屏幕坐标系:

webGL的重要功能之一就是将三维的世界坐标经过变换、投影等计算,最终算出它在显示设备上对应的位置,这个位置就称为设备坐标。在屏幕、打印机等设备上的坐标是二维坐标。

  • 视点坐标系:

是以视点(照相机)为原点,以视线的方向为Z+轴正方向的坐标系中的方向。webGL会将世界坐标先变换到视点坐标,然后进行裁剪,只有在视线范围(视见体)之内的场景才会进入下一阶段的计算。

Raycaster

Raycaster threeJs官方文档

这个类设计用于鼠标去获取在3D世界被鼠标选中的一些物体

Raycaster( origin, direction, near, far ) 

origin — 射线的起点向量。
direction — 射线的方向向量,应该归一标准化。
near — 所有返回的结果应该比 near 远。Near不能为负,默认值为0。
far — 所有返回的结果应该比 far 近。Far 不能小于 near,默认值为无穷大。



找到点击物体的大致思路

 

鼠标在屏幕上点击的时候,得到二维坐标p(x, y),再加上深度坐标的范围(0, 1), 就可以形成两个三位坐标A(x1, y1, 0), B(x2, y, 1), 由于它们的Z轴坐标是0和1,则转变到投影坐标系的话,一定分别是前剪切平面上的点和后剪切平面上的点,也就是说,在投影坐标系中,A点一定在能看见的所有模型的最前面,B点一定在能看见的所有的模型的最后边,将AB点连成线,AB线穿过的物体就是被点击的物体。而 Three.js提供一个射线类Raycasting来拾取场景里面的物体。更方便的使用鼠标来操作3D场景。(不过在实际代码中我们组成射线的两个点是摄像机所在视点与屏幕上点击的点连接而成的射线)

来一个Raycasting的官方实例

代码实现

function onDocumentMouseDown(e) {
    e.preventDefault();
    
    //将鼠标点击位置的屏幕坐标转成threejs中的标准坐标,具体解释见代码释义
    mouse.x = (e.clientX / window.innerWidth) * 2 - 1;
    mouse.y = -(e.clientY / window.innerHeight) * 2 + 1;
    //新建一个三维单位向量 假设z方向就是0.5
    //根据照相机,把这个向量转换到视点坐标系
      var vector = new THREE.Vector3(mouse.x, mouse.y,0.5).unproject(camera);

    //在视点坐标系中形成射线,射线的起点向量是照相机, 射线的方向向量是照相机到点击的点,这个向量应该归一标准化。
    var raycaster = new THREE.Raycaster(camera.position, vector.sub(camera.position).normalize());

    //射线和模型求交,选中一系列直线
    var intersects = raycaster.intersectObjects(objects);
    console.log('imtersrcts=' + intersects)

    if (intersects.length > 0) {
        //选中第一个射线相交的物体
        SELECTED = intersects[0].object;
        var intersected = intersects[0].object;
        console.log(intersects[0].object)
    }


}

代码释义

clipboard.png

  //得到
 mouse.x = (e.clientX / window.innerWidth) * 2 - 1;
 mouse.y = -(e.clientY / window.innerHeight) * 2 + 1;
  
 推导过程:
 设A点为点击点(x1,y1),x1=e.clintX, y1=e.clientY
 设A点在世界坐标中的坐标值为B(x2,y2);
 
 由于A点的坐标值的原点是以屏幕左上角为(0,0);
 我们可以计算可得以屏幕中心为原点的B'值
 x2' = x1 - innerWidth/2
 y2' = innerHeight/2 - y1
 又由于在世界坐标的范围是[-1,1],要得到正确的B值我们必须要将坐标标准化
 x2 = (x1 -innerWidth/2)/(innerwidth/2) = (x1/innerWidth)*2-1
 同理得 y2 = -(y1/innerHeight)*2 +1

事件中鼠标的 (x,y) 位置
clientX 鼠标相对于浏览器左上角x轴的坐标; 不随滚动条滚动而改变;
clientY 鼠标相对于浏览器左上角y轴的坐标; 不随滚动条滚动而改变;
pageX 鼠标相对于浏览器左上角x轴的坐标; 随滚动条滚动而改变;
pageY 鼠标相对于浏览器左上角y轴的坐标; 随滚动条滚动而改变;
screenX 鼠标相对于显示器屏幕左上角x轴的坐标;
screenY 鼠标相对于显示器屏幕左上角y轴的坐标;
offsetX 鼠标相对于事件源左上角X轴的坐标;
offsetY 鼠标相对于事件源左上角Y轴的坐标;

特别需要注意的是,在双屏场景中,screenX 和 screenY 要考虑到屏幕顺序问题,如果主屏在右,副屏在左,那么副屏中的 screenX 为负值!!!!

offsetWidth、clientWidth、scrollWidth

<script>
    /*
     ****** 元素视图属性
     * offsetWidth 水平方向 width + 左右padding + 左右border-width
     * offsetHeight 垂直方向 height + 上下padding + 上下border-width
     * 
     * clientWidth 水平方向 width + 左右padding
     * clientHeight 垂直方向 height + 上下padding
     * 
     * offsetTop 获取当前元素到 定位父节点 的top方向的距离
     * offsetLeft 获取当前元素到 定位父节点 的left方向的距离
     * 
     * scrollWidth 元素内容真实的宽度,内容不超出盒子高度时为盒子的clientWidth
     * scrollHeight 元素内容真实的高度,内容不超出盒子高度时为盒子的clientHeight
     * 
     ****** 元素视图属性结束
     * 
     ****** Window视图属性(低版本IE浏览器[<IE9]不支持) 【自测包含滚动条,但网络教程都说不包含???】
     * innerWidth 浏览器窗口可视区宽度(不包括浏览器控制台、菜单栏、工具栏) 
     * innerHeight 浏览器窗口可视区高度(不包括浏览器控制台、菜单栏、工具栏)
     * ***** Window视图属性结束
     * 
     ****** Document文档视图
     * (低版本IE的innerWidth、innerHeight的代替方案)
     * document.documentElement.clientWidth 浏览器窗口可视区宽度(不包括浏览器控制台、菜单栏、工具栏、滚动条)
     * document.documentElement.clientHeight 浏览器窗口可视区高度(不包括浏览器控制台、菜单栏、工具栏、滚动条)
     * 
     * document.documentElement.offsetHeight 获取整个文档的高度(包含body的margin)
     * document.body.offsetHeight 获取整个文档的高度(不包含body的margin)
     * 
     * document.documentElement.scrollTop 返回文档的滚动top方向的距离(当窗口发生滚动时值改变)
     * document.documentElement.scrollLeft 返回文档的滚动left方向的距离(当窗口发生滚动时值改变)
     ****** Document文档视图结束
     * 
     ****** 元素方法
     * 1. getBoundingClientRect() 获取元素到body
     *  bottom: 元素底边(包括border)到可视区最顶部的距离
     *  left: 元素最左边(不包括border)到可视区最左边的距离
     *  right: 元素最右边(包括border)到可视区最左边的距离
     *  top: 元素顶边(不包括border)到可视区最顶部的距离
     *  height: 元素的offsetHeight
     *  width: 元素的offsetWidth
     *  x: 元素左上角的x坐标 
     *  y: 元素左上角的y坐标 
     * 
     * 2. scrollIntoView() 让元素滚动到可视区
     * 
     * ***** 元素方法结束
     * 
     */
</script>

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值