学会threejs鼠标交互Raycaster拾取物体
对场景内的模型增加事件监听,实现鼠标交互,须要用到Raycaster(光线投射)
类。
拾取物体的原理
webGL中获取鼠标交互物体的原理:通过三维空间中相机视点与鼠标在屏幕上的地位的连线,造成一条直线,捕捉与此直线相交的空间中的物体,即为交互对象物体。
在three中,Raycaster
为咱们封装了大量的逻辑代码,包含生成相机到鼠标的射线、射线与空间物体的碰撞检测、射线相交物体深度计算、相交物体列表等等。应用起来十分不便。
获取鼠标所在位置的物体
先放代码
//...
constructor: function () {
INTERSECTED: null // 暂存射线相交的物体(交互的模型对象)
mouse = new THREE.Vector2();
}
//...
/**
* 作者: JoyNop
* 性能: 渲染时的回调
* 形容:
* @param mesh {Object} Object3D、Mesh、Group均可,可不传默认应用初始化实例时传入的mesh对象
* @returns {Null} 默认返回null
*/
render: function (mesh = null) {
if (!mesh) mesh = this.mesh;
// 通过摄像机和鼠标地位更新射线
this.raycaster.setFromCamera( this.mouse, app.camera );
// 计算物体和射线的焦点
let intersects = this.raycaster.intersectObject( mesh, true );
if ( intersects.length > 0 ) {
if ( this.INTERSECTED !== intersects[ 0 ].object ) {
if ( this.INTERSECTED ) this.INTERSECTED.material.emissive.setHex( this.INTERSECTED.currentHex );
// 记录以后对象
this.INTERSECTED = intersects[ 0 ].object;
// 记录以后对象自身色彩
this.INTERSECTED.currentHex = this.INTERSECTED.material.emissive.getHex();
// 设置色彩为灰色
this.INTERSECTED.material.emissive.setHex( 0x333333 );
}
} else {
// 复原上一个对象色彩并置空变量
if ( this.INTERSECTED ) this.INTERSECTED.material.emissive.setHex( this.INTERSECTED.currentHex );
this.INTERSECTED = null;
}
}
上面来解释一下
Raycaster
类的.intersectObject()
办法:检测所有在射线与物体之间,包含或不包含后辈的相交局部。返回后果时,相交局部将按间隔进行排序,最近的位于第一个。
对物体进行材质变换,调整emissive
(材质的自发光)的Hex色彩,实现hover类型的交互成果。
鼠标坐标的转换
先上最终的代码
onMouseMove: function (event) {
event.preventDefault();
// 将鼠标地位归一化为设施坐标。x 和 y 方向的取值范畴是 (-1 to +1)
// renderer为three的渲染器
let px = renderer.domElement.getBoundingClientRect().left;
let py = renderer.domElement.getBoundingClientRect().top;
this.mouse.x = ( (event.clientX - px) / (renderer.domElement.offsetWidth) ) * 2 - 1;
this.mouse.y = - ( (event.clientY - py) / (renderer.domElement.offsetHeight) ) * 2 + 1;
},
/*
* 作者: JoyNop
* 性能: 增加鼠标挪动监听,拾取物体
* 形容:
* @param callback {Function} 拾取物体的回调,参数是拾取的mesh
* @returns {Null} 默认返回null
*/
listenOnMouseMove: function (callback = null) {
if (typeof callback === 'function') callback();
let fn = (e) => { this.onMouseMove(e); };
window.addEventListener( 'mousemove', fn, false );
}
增加鼠标挪动监听,就是一个很常见的监听事件,不做解释。
重点是将鼠标坐标转化成设施坐标,从而影响三维空间中发射射线方向的问题。
对于鼠标地位转换,官网案例中给出的代码,是如下这样的:
this.mouse.x = ( event.clientX / window.innerWidth ) * 2 - 1;
this.mouse.y = - ( event.clientY / window.innerHeight ) * 2 + 1;
这样的计算形式会有个问题,如果canvas元素并不是全屏的,即canvas元素的domCanvas.clientWidth !== document.documentElement.clientWidth || domCanvas.clientHeight !== document.documentElement.clientHeight
为true,那么上边的转换就会有问题,鼠标拾取物体的地位就会产生偏移。
坐标转换原理
//得到
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``
具体推导过程可参考:
threejs对象拾取
提炼进去就是
mouse.x = (<鼠标绝对于可视区域的横坐标> / <可视区域的宽>) * 2 - 1;
mouse.y = -(<鼠标绝对于可视区域的纵坐标> / <可视区域的高>) * 2 + 1;
因为canvas并非全屏的元素,所以咱们须要从新计算这几个值值。
- 首先,
renderer.domElement
的大小就是three场景可视区域的范畴。所以通过renderer.domElement推算即可。 - 鼠标绝对于可视区域的横坐标推算为
e.clientX - renderer.domElement.getBoundingClientRect().left
- 可视区域的宽推算为
renderer.domElement.offsetWidth
- 鼠标绝对于可视区域的纵坐标推算为
e.clientY - renderer.domElement.getBoundingClientRect().top
- 可视区域的高推算为
renderer.domElement.offsetHeight
那么,就失去了最终的代码。完满实现拾取物体的鼠标交互。
参考资料
ThreeJS中的点击与交互——Raycaster的用法
threejs对象拾取
本作品采用 知识共享署名-相同方式共享 4.0 国际许可协议 进行许可。
感谢传教!!
共同学习🤝