Threejs 通过Raycast实现点击效果
引言
Threejs中,要想实现3D物体的点击效果,可以通过射线(Raycast)获取到当前点击到的所有物体。我是个threejs新手,如果写的有不对的地方,希望大家批评指正。
实现
1、通过Raycast获取所有物体
在网上有很多这个例子,官网上也有相应的例子,这里就不多赘述了。
const raycaster = new THREE.Raycaster();
const pointer = new THREE.Vector2();
/**
* 获取点击到的所有物体
*/
function getIntersects(event, group) {
event.preventDefault();
// 将鼠标位置归一化为设备坐标。x 和 y 方向的取值范围是 (-1 to +1)
var x = ( event.clientX / window.innerWidth ) * 2 - 1;
var y = - ( event.clientY / window.innerHeight ) * 2 + 1;
// 通过鼠标点击的位置(二维坐标)和当前相机的矩阵计算出射线的位置
raycaster.setFromCamera(new THREE.Vector2(x, y), camera);
// 获取与raycaster射线相交的数组集合,其中的元素按照距离排序,越靠近的越靠前
var intersects = raycaster.intersectObjects(scene, true);
// 返回选中的对象数组
return intersects;
}
/**
* 点击事件
**/
function mouseClick(event) {
const intersects = getIntersects(event, deviceGroup);
if (intersects.length > 0) {
//点击到的第一个物体
let selectObj = intersects[0].object;
console.log(selectObj);
}
}
//注册事件,也可以放到div上,根据业务需求来定
window.addEventListener( 'click', mouseClick );
2、隐藏物体也可以点击
经过测试,通过visible = false隐藏物体后,物体还是可以被点击,我用的r146,不知道是我用的有问题,还是怎么的,隐藏之后的物体还是会被点击上,所以这里我们就得处理这个问题。我的解决方案是,获取到所有的点击物体后,来过滤隐藏的物体。
判断物体是否在场景中隐藏。
/**
* 物体是否在场景中显示
* @param {THREE.Object3D} obj 3D物体
* @return {Boolean} 是否在场景中显示
* */
function ActiveInHierarchy(obj) {
if (!obj.visible) {
return false;
}
//遍历父对象
let parent = obj.parent;
while (parent) {
if (!parent.visible) {
return false;
}
parent = parent.parent;
}
return true;
}
3、canvas并非全屏
canvas不是全屏的时候,用以上方法会造成点击偏移的情况,明明就在点击物体又没点击上。
/**
* 获取点击到的所有物体
* @param {THREE.Event} event 点击事件
* @param {THREE.Camera} camera 摄像机
* @param {THREE.Group} group group
* @param {Boolean} visible 物体隐藏还是显示
* @return {Array} 射线下的所有物体
* */
function GetIntersects(event, camera, group, visible) {
let canvas = event.path[0];
event.preventDefault();
/**
* THREE.Raycaster 对象从屏幕上的点击位置向场景发射的一束光线
* THREE.Raycaster,THREE.Vector2():声明raycaster 和mouse变量
* */
var raycaster = new THREE.Raycaster();
//处理画布与电脑屏幕存在偏移量问题
var divObj = canvas.getBoundingClientRect();
// 获取鼠标点击位置
var Sx = event.clientX - divObj.left;
var Sy = event.clientY - divObj.top;
// 将鼠标位置归一化为设备坐标。x 和 y 方向的取值范围是 (-1 to +1)
var x = (Sx / canvas.width) * 2 - 1;
var y = -(Sy / canvas.height) * 2 + 1;
// 通过鼠标点击的位置(二维坐标)和当前相机的矩阵计算出射线的位置
raycaster.setFromCamera(new THREE.Vector2(x, y), camera);
// 获取与raycaster射线相交的数组集合,其中的元素按照距离排序,越靠近的越靠前
var intersects = raycaster.intersectObjects(group.children, true);
let objs = [];
intersects.forEach(object => {
if (ActiveInHierarchy(object.object) == visible) {
objs.push(object);
}
});
// 返回选中的对象数组query
return objs;
}