持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第1天,点击查看活动详情
一、前言
相信大家在使用threejs的过程中,都需要点击模型,进行类似替换贴图,触发事件等等操作,但是threejs又不能直接绑定元素,这就需要Raycaster射线类。
原理就是从鼠标点击的位置,发射一条射线,然后按距离远近,将穿过的物品一一返回给我们。
直接试试吧。
二、过程
1、引入three并生成场景
由于不是本文的主要叙述内容,就不再重复了,如果有不会的小伙伴,可以移步我的另一篇文章 # vite2+vue3+ts+threejs搭建项目,摸鱼学习vue3和threejs的日子(二)
2、 射线函数
前提,我的场景是宽1000px,高800px,canvas窗口距离浏览器窗口顶部164px,距离浏览器窗口左侧460px,为什么需要知道这个呢,是因为要进行换算,鼠标点击位置,要减去偏移值,才是鼠标在threejs场景里的点击位置。
如图
生成好场景了,就可以写射线函数onDocumentMouseDown了
需要注意的地方就是mouse的换算公式
mouse.x = ((鼠标点击位置 - 左侧偏移值) / 场景宽度) * 2 - 1
mouse.y = ((鼠标点击位置 - 顶部偏移值) / 场景高度) * - 2 + 1
然后就是要注意一下,由于raycaster只对mesh对象有感应,而导入的模型基本是group的,所以intersectObject的第二个参数必须为true,要检查后代才能拿到该模型。
ps:如果目标模型的同级模型干扰严重,也可以采用在模型外部新建一个透明的mesh进行包裹绑定。
下面是官网的说法
raycaster.intersectObject() ( object : [Object3D](), recursive : Boolean, optionalTarget : Array ) : Array
[object](<> "Object3D") —— 检查与射线相交的物体。\
[recursive](<> "Boolean") —— 若为true,则同时也会检查所有的后代。
否则将只会检查对象本身。默认值为true。\
[optionalTarget](<> "Array") — (可选)设置结果的目标数组。
如果不设置这个值,则一个新的[Array](<> "Array")会被实例化;
如果设置了这个值,则在每次调用之前必须清空这个数组(例如:array.length = 0;)。
检测所有在射线与物体之间,包括或不包括后代的相交部分。
返回结果时,相交部分将按距离进行排序,最近的位于第一个。
本文打算实现是效果就是点击mesh,在生成mesh的时候,我将mesh的name赋值为了cubes,方便更换mesh的纹理图片
具体函数如下
function onDocumentMouseDown(event: any) {
let mouse = new THREE.Vector2() // 生成一个矢量
let raycaster = new THREE.Raycaster() // 生成我们的本次主角射线类
//var windowleft = 460 // threejs的canvas距离窗口左边的距离
//var windowtop = 140 + 24 // threejs的canvas距离窗口顶边的距离
//var sceneWidth = 1000 // 场景宽度
//var sceneHeight = 800 // 场景高度
//mouse.x = ((event.clientX - windowleft) / sceneWidth) * 2 - 1
//mouse.y = ((event.clientY - windowtop) / sceneHeight) * -2 + 1
// 后面我发现这样写死有点蠢,改良了一下,替换上面的6行,换汤不换药
var container = document.getElementById('three3d')
const getBoundingClientRect = container.getBoundingClientRect()
mouse.x = ((event.clientX - getBoundingClientRect.left) / container.offsetWidth) * 2 - 1
mouse.y = ((event.clientY - getBoundingClientRect.top) / container.offsetHeight) * -2 + 1
raycaster.setFromCamera(mouse, camera) // 调用setFromCamera方法,参数是mouse和相机
// intersectObjects第二个参数必须为true
const intersects = raycaster.intersectObjects(scene.children, true)
// 这个intersects就是返回的全部对象了
intersects.forEach(item => {
if(item.object.name === 'cubes') {
const texture = new THREE.TextureLoader().load(
"/assets/puzzle-factory.png"
) //首先,获取到纹理
// item.object.material.map = texture
mesh.material.map = texture
// item.object.needsUpdate = true
}
})
}
绑定到window事件
// 点击模型
window.addEventListener('click', onDocumentMouseDown)
3、效果
点击前
鼠标点击了mesh,返回了6个对象(排序按远近),可以看到第一个就是我们要的cubes,其余5个是坐标系等等其他物品,可以忽略
点击后,精确换图,当然也可以触发别的函数操作
ps: 我是地霊殿__三無,下期我们试试gasp动画库