浏览器中浏览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节点获取对象偏差问题就应该计算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;
}
}