14.Ray picking model

RayRay

Ray Ray, like the three-dimensional vector Vector3, is an API related to mathematical geometric calculations and can perform ray intersection calculations.

To learn the concept of ray in Three.js, you can compare it to the ray mentioned in mathematical geometry. In three-dimensional space, a line takes a point as the starting point and then extends infinitely along a certain direction.

// 创建射线对象Ray
const ray = new THREE.Ray();

// 设置射线起点
ray.origin = new THREE.Vector3(1,0,3);

// 表示射线沿着x轴正方向
ray.direction = new THREE.Vector3(1,0,0);

 .intersectTriangle() method

Ray has many mathematical calculation methods. Let's first introduce a method related to triangle intersection calculation. intersectTriangle(), simply put, is to calculate whether a ray and a triangle intersect in 3D space.

Execute the .intersectTriangle() method, and return the intersection coordinates if they intersect, and return null if they do not intersect.

// 三角形三个点坐标
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();//用来记录射线和三角形的交叉点
// `.intersectTriangle()`计算射线和三角形是否相交叉,相交返回交点,不相交返回null
const result = ray.intersectTriangle(p1,p2,p3,false,point);
console.log('交叉点坐标', point);
console.log('查看是否相交', result);

 Parameter 4 of .intersectTriangle() indicates whether to perform back-face elimination. p1, p2, and p3 can be understood as a triangle with two sides, one is the front and the other is the back.

Observe p1, p2, and p3 from one side. If you turn the circle in the order of the three points, it is in a counterclockwise direction, indicating the front side. When viewed from the other side, p1, p2, and p3 are in a clockwise direction, indicating the back side. .

IntersectTriangle() parameter 4 is set to true, which means back-face culling is performed. Although in terms of geometric space, the source code ray and the triangle in this case intersect, in threejs, the back of the triangle faces the ray, which is regarded as an invalid intersection and back-face culling is performed. The return value r is null.

Raycaster (ray picking model)

RaycasterRaycaster

The ray caster Raycaster has a ray attribute .ray, and the value of this attribute is the ray object Ray.

const raycaster = new THREE.Raycaster();

// 设置射线起点
raycaster.ray.origin = new THREE.Vector3(-100, 0, 0);
// 设置射线方向射线方向沿着x轴
raycaster.ray.direction = new THREE.Vector3(1, 0, 0);

 Ray intersection calculation (.intersectObjects() method)

The ray caster Raycaster can calculate the mesh model that intersects its own ray .ray through the .intersectObjects() method. .intersectObjects([mesh1, mesh2, mesh3]) performs ray intersection calculation on the mesh model objects in the parameters. If an unselected object is returned, an empty array [] is returned. If an object is selected, the array will contain 1 element. If multiple objects are selected, the array will contain multiple elements. element, if multiple objects are selected, the objects are sorted in order in the array.

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);

Ray-selected model objects change material color

const intersects = raycaster.intersectObjects([mesh1, mesh2, mesh3]);
if (intersects.length > 0) {
    // 选中模型的第一个模型,设置为红色
    intersects[0].object.material.color.set(0xff0000);
}

Convert screen coordinates to standard device coordinates

// 坐标转化公式
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;
})

Raycaster (click the mouse to select the model)

In actual development, the ray caster is often used. This time, I will show you the ray picking function of the ray caster through a simple small case. Simply put, it is to click the mouse to select a model object. The function of the code below is to click the mouse on the threejs canvas, pick up the grid model through the ray caster Raycaster, and change the color of the selected grid model.

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;
    //创建一个射线投射器`Raycaster`
    const raycaster = new THREE.Raycaster();
    //.setFromCamera()计算射线投射器`Raycaster`的射线属性.ray
    // 形象点说就是在点击位置创建一条射线,射线穿过的模型代表选中
    raycaster.setFromCamera(new THREE.Vector2(x, y), camera);
    //.intersectObjects([mesh1, mesh2, mesh3])对参数中的网格模型对象进行射线交叉计算
    // 未选中对象返回空数组[],选中一个对象,数组1个元素,选中两个对象,数组两个元素
    const intersects = raycaster.intersectObjects([mesh1, mesh2, mesh3]);
    console.log("射线器返回的对象", intersects);
    // intersects.length大于0说明,说明选中了模型
    if (intersects.length > 0) {
        // 选中模型的第一个模型,设置为红色
        intersects[0].object.material.color.set(0xff0000);
    }
})

Canvas size change (ray coordinate calculation)

When you click the mouse on the canvas and use rays to pick up the model, one thing to note is that if the size of the canvas is not fixed but changes, it will affect the coordinate transformation.

// 画布跟随窗口变化
window.onresize = function () {
    const width = window.innerWidth - 200; //canvas画布宽度
    const height = window.innerHeight - 60; //canvas画布高度
    renderer.setSize(width, height);
    camera.aspect = width / height;
    camera.updateProjectionMatrix();
    document.getElementById('left').style.height = height + 'px';
};

renderer.domElement.addEventListener('click', function (event) {
    const width = window.innerWidth-200;
    const height = window.innerHeight-60;
    //屏幕坐标转WebGL标准设备坐标
    const x = (px / width) * 2 - 1;
    const y = -(py / height) * 2 + 1;
})

Ray Picking Hierarchy Model

If a hierarchical model contains multiple mesh model Mesh, when using the .intersectObjects() method to pick it up, the returned result will be the descendant Mesh of the hierarchical model by default, and there is no way to select the hierarchical model as a whole.

Customize a property .ancestors for all sub-object Mesh objects that require ray picking. Then let this property point to the parent object that requires ray picking.

const cunchu = model.getObjectByName('存储罐');
// 射线拾取模型对象(包含多个Mesh)
// 可以给待选对象的所有子孙后代Mesh,设置一个祖先属性ancestors,值指向祖先(待选对象)    
for (let i = 0; i < cunchu.children.length; i++) {
    const group = cunchu.children[i];
    //递归遍历chooseObj,并给chooseObj的所有子孙后代设置一个ancestors属性指向自己
    group.traverse(function (obj) {
        if (obj.isMesh) {
            obj.ancestors = group;
        }
    })
}
// 射线交叉计算拾取模型
const intersects = raycaster.intersectObjects(cunchu.children);
console.log('intersects', intersects);
if (intersects.length > 0) {
    // 通过.ancestors属性判断那个模型对象被选中了
    outlinePass.selectedObjects = [intersects[0].object.ancestors];
}

Guess you like

Origin blog.csdn.net/weixin_60645637/article/details/131505511