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/