浏览器中浏览3D图形的时候,想要与3D图形之间做一些点击事件和交互操作,比较常用的一个解决方案就是使用Raycaster对象来实现(射线拾取)。
three.js利用射线Raycaster进行碰撞检测获取射线穿透对象。
常用方法:
/**
* @param { 事件对象 } event
* @param { 场景对象 } scene
* @param { 镜头对象 } camera
*/
function getCanvasIntersects(event, scene, camera) {
// 声明 raycaster 和 mouse 变量
let rayCaster = new THREE.Raycaster();
let mouse = new THREE.Vector2();
event.preventDefault();
if (event.touches) {
mouse.x = (event.touches[0].pageX / window.innerWidth) * 2 - 1;
mouse.y = -(event.touches[0].pageY / window.innerHeight) * 2 + 1;
} else {
// 通过鼠标点击位置,计算出 raycaster 所需点的位置,以屏幕为中心点,范围 -1 到 1
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
}
//通过鼠标点击的位置(二维坐标)和当前相机的矩阵计算出射线位置
rayCaster.setFromCamera(mouse, camera);
// 返回射线选中的对象 第二个参数如果不填 默认是false
let intersects = rayCaster.intersectObjects(scene.children, true);
//返回选中的对象数组
return intersects;
}
//- 点击事件 获取某一个盒子canvas中模型对象
function getBoxClickObjFn(event, scene, camera, canvas) {
let intsersects = getCanvasIntersects1(event, scene, camera, canvas);
if (intsersects.length > 0) {
return intsersects;
}
}
通过构造函数new THREE.Raycaster(camera.position, ray);创建一个射线发射器对象,camera.position是相机的位置,ray表示射线方向向量。
通过射线发射器对象的方法intersectObjects可以返回射线旋转的所有对象,该方法的参数是一个Object3D对象构成的数组,表示射线对象的选择范围,凡是选中的都会以数组的形式返回, 返回的数据结构是[ { distance, point, face, faceIndex, indices, object },{ distance, point, face, faceIndex, indices, object } … ], 如果两个网格模型屏幕坐标位置是重合的,那么都会被选中,因此可以通过数组下标的形式访问第几个对象, 被选中的网格模型对象以object属性的形式存在,代码intersects[0].object就表示被选中所有的网格模型中的第一个网格模型对象。 通过语句intersects[0].object.material.opacity = 0.6;可以更改材质对象的透明度。
但当绘制3D模型的canvas基于某一个DOM节点(或者说canvas不是基于body绘制),未填充整个屏幕,此时通过上面的方法会出现鼠标点中网格模型位置却获取不到网格模型对象(网格模型对象较小发现问题更容易),点击周围可能会取到网格对象模型。
结论:上面的方法只针对于3D模型加载canvas填充整个屏幕时有效,不针对于基于DOM节点加载3D模型对象情况。
那么要解决绘制3D模型的canvas基于某一个DOM节点获取对象偏差问题就应该计算DOM节点基于window窗体的比例来获取网格模型对象。
/**
* @param { 事件对象 } event
* @param { 场景对象 } scene
* @param { 镜头对象 } camera
* @param canvas 绘制盒子
* 当canvas不占满整屏时射线拾取存在偏差,获取点击对象
*/
function getCanvasIntersects(event, scene, camera, canvas) {
event.preventDefault();
// 获取元素的大小及其相对于视口的位置
let getBoundingClientRect = canvas.getBoundingClientRect();
// 屏幕坐标转标准设备坐标
let x = ((event.clientX - getBoundingClientRect.left) / canvas.offsetWidth) * 2 - 1; // 标准设备横坐标
let y = -((event.clientY - getBoundingClientRect.top) / canvas.offsetHeight) * 2 + 1; // 标准设备纵坐标
let vector = new THREE.Vector3(x, y, 1); // 标准设备坐标
// 标准设备坐标转世界坐标
let worldVector = vector.unproject(camera);
// 射线投射方向单位向量(worldVector坐标减相机位置坐标)
let ray = worldVector.sub(camera.position).normalize();
// 创建射线投射器对象
let rayCaster = new THREE.Raycaster(camera.position, ray);
// 返回射线选中的对象 第二个参数如果不填 默认是false
let intersects = rayCaster.intersectObjects(scene.children, true);
//返回选中的对象数组
return intersects;
}
//- 点击事件 获取某一个盒子canvas中模型对象
function getBoxClickObjFn(event, scene, camera, canvas) {
let intsersects = getCanvasIntersects1(event, scene, camera, canvas);
if (intsersects.length > 0) {
return intsersects;
}
}