Appearance
射线拾取模型
射线 Ray
以一个点作为起点, 沿着某个方向无限延伸
1.创建射线对象 Ray
js
const ray = new THREE.Ray();
2.设置射线起点.origin
js
ray.origin = new THREE.Vector3(1, 0, 3);
// set方法
ray.origin.set(1, 0, 3);
3.射线方向.direction
为三维单位向量, 向量长度保证为 1
js
// 表示射线沿着x轴正方向
ray.direction = new THREE.Vector3(1, 0, 0);
// 表示射线沿着y方向
ray.direction = new THREE.Vector3(0, 1, 0);
不是单位向量要执行.normalize()
归一化
js
ray.direction = new THREE.Vector3(5, 0, 0).normalize();
4.intersectTriangle()
方法
计算一个射线和一个三角形在 3D 空间中是否交叉
js
// 三角形三个点坐标
const p1 = new THREE.Vector3(100, 25, 0);
const p2 = new THREE.Vector3(100, -25, 25);
const p3 = new THREE.Vector3(100, -25, -25);
const point = new THREE.Vector3(); //用来记录射线和三角形的交叉点
// 相交返回交点,不相交返回null
const result = ray.intersectTriangle(p1, p2, p3, false, point);
console.log("交叉点坐标", point);
console.log("查看是否相交", result);
- 三角形有正反两面, 按顶点顺序顺时针表示背面, 逆时针表示正面
- 参数 4 设为 true 时执行背面剔除, 即射线从图形背面穿过来, 视为交叉无效
js
const r = ray.intersectTriangle(p1, p2, p3, true, point);
射线拾取模型(Raycaster)
1.射线投射器
js
const raycaster = new THREE.Raycaster();
// 设置射线起点
raycaster.ray.origin = new THREE.Vector3(-100, 0, 0);
// 设置射线方向射线方向沿着x轴
raycaster.ray.direction = new THREE.Vector3(1, 0, 0);
2.射线交叉计算
js
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);
射线返回对象是一个包含多个信息的数组, 当数组长度>0 时说明选中了模型
js
if (intersects.length > 0) {
console.log("交叉点坐标", intersects[0].point);
console.log("交叉对象", intersects[0].object);
console.log("射线原点和交叉点距离", intersects[0].distance);
}
选中的对象模型可以被获取并修改
js
const intersects = raycaster.intersectObjects([mesh1, mesh2, mesh3]);
if (intersects.length > 0) {
// 选中模型的第一个模型,设置为红色
intersects[0].object.material.color.set(0xff0000);
}
屏幕坐标转化为设备坐标
屏幕坐标
通过 js 原生事件获取鼠标点击屏幕的坐标位置,client
和 offset
区别在于坐标原点位置不同
js
addEventListener("click", function (event) {
const px = event.offsetX;
const py = event.offsetY;
// client
const cx = event.clientX;
const cy = event.clientY;
});
设备坐标
以 Canvas 画布中心为坐标原点,坐标值为相对坐标,范围在[-1,1]之间
坐标转换
offsetX
、offsetY
js
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;
});
clientX
、clientY
js
addEventListener("click", function (event) {
// left、top表示canvas画布布局,距离顶部和左侧的距离(px)
const px = event.clientX - left;
const py = event.clientY - top;
//屏幕坐标px、py转标准设备坐标x、y
//width、height表示canvas画布宽高度
const x = (px / width) * 2 - 1;
const y = -(py / height) * 2 + 1;
});
鼠标点击选中模型
1.坐标系转化
js
renderer.domElement.addEventListener("click", function (event) {
// .offsetY、.offsetX以canvas画布左上角为坐标原点,单位px
const px = event.offsetX;
const py = event.offsetY;
//屏幕坐标px、py转WebGL标准设备坐标x、y
//width、height表示canvas画布宽高度
const x = (px / width) * 2 - 1;
const y = -(py / height) * 2 + 1;
});
2.射线计算
.setFromCamera()
在点击位置创建一条射线,用来选中拾取模型对象
js
//...
//创建一个射线投射器`Raycaster`
const raycaster = new THREE.Raycaster();
raycaster.setFromCamera(new THREE.Vector2(x, y), camera);
//...
3.射线交叉计算
js
//...
const intersects = raycaster.intersectObjects([mesh1, mesh2, mesh3]);
if (intersects.length > 0) {
// 选中模型的第一个模型,设置为红色
intersects[0].object.material.color.set(0xff0000);
}
//...
Canvas 尺寸变化调整射线计算
- 画布全屏
js
renderer.domElement.addEventListener("click", function (event) {
const px = event.offsetX;
const py = event.offsetY;
//屏幕坐标转WebGL标准设备坐标
const x = (px / window.innerWidth) * 2 - 1;
const y = -(py / window.innerHeight) * 2 + 1;
});
- 局部布局
js
renderer.domElement.addEventListener("click", function (event) {
const width = window.innerWidth - 200; //canvas画布宽度
const height = window.innerHeight - 60; //canvas画布高度
//屏幕坐标转WebGL标准设备坐标
const x = (px / width) * 2 - 1;
const y = -(py / height) * 2 + 1;
});
射线拾取层级模型
问题:当要选取的模型是由多个子模型组成的时候,选取该模型会返回某个子对象模型
解决方法:给要拾取的父对象的所有子孙模型添加一个.ancestors
属性,该属性指向父对象
js
addEventListener("click", function (event) {
//...
const storeJar = model.getObjectByName("存储罐");
for (let i = 0; i < storeJar.children.length; i++) {
const group = storeJar.children[i];
//递归遍历chooseObj,并给其所有子孙后代设置一个ancestors属性指向自己
group.traverse(function (obj) {
if (obj.isMesh) {
obj.ancestors = group;
}
});
}
//...
});
射线拾取 sprite 控制场景
1. 给精灵模型绑定函数事件
js
sprite.change = function () {
mesh.material.color.set(0xffffff);
};
sprite2.change = function () {
mesh.material.color.set(0xffff00);
};
2. 选中精灵模型执行事件
js
addEventListener("click", function (event) {
// ...
// 射线交叉计算拾取精灵模型
const intersects = raycaster.intersectObjects([sprite, sprite2]);
if (intersects.length > 0) {
intersects[0].object.change(); //执行选中sprite绑定的change函数
}
});