threejs如何自动寻点

更新时间: 2022-04-11 09:35:28

之前做的那个三维编辑器,实现了基本的拖拽模型,布点,今天再加上另一个需求,点击左侧测点列表的时候,摄像头找到对应的测点并且找到最佳的位置观察,效果如下:

# 原理

因为我的测点正好是正方形,所以我的原理就是,找了这个正方形周围的27个点来判断这27个点到测点表面的射线,最后对比哪个点碰到的障碍物少,哪个点就是最佳观测点,然后将摄像头平滑的移到这个点上去。

上图所示的就是正方体周围的观测点,由于角度问题,我只展示了正面能看见的几个点。

但是一次循环27个点还是太慢了,因此我们优化一下方法:

  1. 首先判断正方体的6个面中哪个面暴露在外没有遮挡
  2. 然后判断这个面附近的9个观测点的射线就可以了(假设是上面,附近的9个观测点如图中黄点)
  3. 平滑的将摄像头移动到最佳观测点

# 实现

# 定义6个面的射线检测坐标

首先把6个面的射线检测的起点终点坐标列出来:

// target为目标测点, size为目标测点的大小, distance为检测射线的长度
const facePoints = [
    //[每个面正中心的点坐标, 每个面正中心往法线方向distance的坐标]
    [[target.x, target.y + size/2, target.z],[target.x, target.y + size/2 + distance * 2, target.z]], //上
    [[target.x, target.y, target.z + size/2],[target.x, target.y, target.z + size/2 + distance * 2]], //前
    [[target.x, target.y, target.z - size/2],[target.x, target.y, target.z - size/2 - distance * 2]], //后
    [[target.x, target.y - size/2, target.z],[target.x, target.y - size/2 - distance * 2, target.z]], //下
    [[target.x + size/2, target.y, target.z],[target.x + size/2 + distance * 2, target.y, target.z]], //左
    [[target.x - size/2, target.y, target.z],[target.x - size/2 - distance * 2, target.y, target.z]], //右
]
1
2
3
4
5
6
7
8
9
10

# 定义这6个面对应的9个点

const axisUp = [
    [target.x + distance, target.y + distance, target.z + distance], // 左前上角
    [target.x + distance, target.y + distance, target.z - distance], //左后上角
    [target.x, target.y + distance, target.z], //上方
    [target.x - distance, target.y + distance, target.z + distance], // 右前上角
    [target.x - distance, target.y + distance, target.z - distance], //右后上角
    [target.x, target.y + distance, target.z + distance], //前上方向
    [target.x, target.y + distance, target.z - distance], // 后上方向
    [target.x + distance, target.y + distance, target.z],  //左上方向
    [target.x - distance, target.y + distance, target.z], //右上方向
]
const axisDown = [
    [target.x + distance, target.y - distance, target.z + distance], // 左前下角
    [target.x + distance, target.y - distance, target.z - distance], //左后下角
    [target.x, target.y - distance, target.z], //下方
    [target.x - distance, target.y - distance, target.z + distance], // 右前下角
    [target.x - distance, target.y - distance, target.z - distance], //右后下角
    [target.x, target.y - distance, target.z + distance], //前下方向
    [target.x, target.y - distance, target.z - distance], // 后下方向
    [target.x + distance, target.y - distance, target.z],  //左下方向
    [target.x - distance, target.y - distance, target.z], //右下方向
]
const axisLeft = [
    [target.x + distance, target.y + distance, target.z + distance], // 左前上角
    [target.x + distance, target.y + distance, target.z - distance], //左后上角
    [target.x + distance, target.y, target.z], //左方
    [target.x + distance, target.y - distance, target.z + distance], // 左前下角
    [target.x + distance, target.y - distance, target.z - distance], //左后下角
    [target.x + distance, target.y + distance, target.z],  //左上方向
    [target.x + distance, target.y - distance, target.z],  //左下方向
    [target.x + distance, target.y, target.z + distance], //左前方向
    [target.x + distance, target.y, target.z - distance], //左后方向
]
const axisRight = [
    [target.x - distance, target.y + distance, target.z + distance], // 右前上角
    [target.x - distance, target.y + distance, target.z - distance], //右后上角
    [target.x - distance, target.y, target.z], //右方
    [target.x - distance, target.y - distance, target.z + distance], // 右前下角
    [target.x - distance, target.y - distance, target.z - distance], //右后下角
    [target.x - distance, target.y + distance, target.z],  //右上方向
    [target.x - distance, target.y - distance, target.z],  //右下方向
    [target.x - distance, target.y, target.z + distance], //右前方向
    [target.x - distance, target.y, target.z - distance], //右后方向
]
const axisFront = [
    [target.x + distance, target.y + distance, target.z + distance], // 左前上角
    [target.x + distance, target.y - distance, target.z + distance], // 左前下角
    [target.x, target.y, target.z + distance], //前方
    [target.x - distance, target.y + distance, target.z + distance], // 右前上角
    [target.x - distance, target.y - distance, target.z + distance], // 右前下角
    [target.x + distance, target.y, target.z + distance], //左前方向
    [target.x - distance, target.y, target.z + distance], //右前方向
    [target.x, target.y + distance, target.z + distance], //前上方向
    [target.x, target.y - distance, target.z + distance], //前下方向
]
const axisBack = [
    [target.x + distance, target.y + distance, target.z - distance], // 左后上角
    [target.x + distance, target.y - distance, target.z - distance], // 左后下角
    [target.x, target.y, target.z - distance], //后方
    [target.x - distance, target.y + distance, target.z - distance], // 右后上角
    [target.x - distance, target.y - distance, target.z - distance], // 右后下角
    [target.x + distance, target.y, target.z - distance], //左后方向
    [target.x - distance, target.y, target.z - distance], //右后方向
    [target.x, target.y + distance, target.z - distance], //后上方向
    [target.x, target.y - distance, target.z - distance], //后下方向
]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66

# 如何进行两点间的射线检测

首先来看下Raycaster构造器:

Raycaster( origin : Vector3, direction : Vector3, near : Float, far : Float )

  • origin —— 光线投射的原点向量。
  • direction —— 向射线提供方向的方向向量,应当被标准化。
  • near —— 返回的所有结果比near远。near不能为负值,其默认值为0。
  • far —— 返回的所有结果都比far近。far不能小于near,其默认值为Infinity(正无穷。)
 // 计算两点之间的射线
function raycaster2Point(startPoint, endPoint, near = 0, model) {
    const vectorStr = new THREE.Vector3(startPoint[0], startPoint[1], startPoint[2]);
    const vectorEnd = new THREE.Vector3(endPoint[0], endPoint[1], endPoint[2]);
    const vectorEnd1 = new THREE.Vector3(endPoint[0], endPoint[1], endPoint[2]);
    const raycaster = new THREE.Raycaster(vectorStr, vectorEnd.sub(vectorStr).normalize(), near, vectorEnd1.distanceTo(vectorStr))

    const intersects = raycaster.intersectObjects(model, true);
    return intersects
}
1
2
3
4
5
6
7
8
9
10

# 判断正方体外露面

let min = 0
let outsideIndex = 0
// 首先判断正方形哪个面露在外面
for( let i = 0; i < facePoints.length; i++) {
    const intersects = raycaster2Point(facePoints[i][0], facePoints[i][1], 0, scene.children);

    if(i == 0) {
        min = intersects.length
        outsideIndex = i
    }else {
        if(intersects.length < min ) {
            min = intersects.length
            outsideIndex = i
        }
    }
    if(min == 0) {
        break
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# 判断最佳观测点

let returnAxis = [0, 0, 0]

switch(outsideIndex) {
    case 0: //上
        // 假如是上面的话优先判断上方一圈
        returnAxis = countBestPoint(facePoints[0][0], axisUp, distance)
        break;
    case 1: //前
        // 假如是前面的话优先判断前面一圈
        returnAxis = countBestPoint(facePoints[1][0], axisFront, distance)
        break;
    case 2: //后
        // 假如是后面的话优先判断后面一圈
        returnAxis = countBestPoint(facePoints[2][0], axisBack, distance)
        break;
    case 3: //下
        // 假如是下面的话优先判断下方一圈
        returnAxis = countBestPoint(facePoints[3][0], axisDown, distance)
        break;
    case 4: //左
        // 假如是左边的话优先判断左边一圈
        returnAxis = countBestPoint(facePoints[4][0], axisLeft, distance)
        break;
    case 5: //右
        // 假如是右边的话优先判断右边一圈
        returnAxis = countBestPoint(facePoints[5][0], axisRight, distance)
        break;
}

function countBestPoint(startPoint, pointList, distance) {
    let objectMin = 0
    let result = []
    for(let j = 0;j < pointList.length; j++) {
        const intersects = raycaster2Point(startPoint, pointList[j] , 0, scene.children)
        if(j == 0) {
            objectMin = intersects.length
            result = pointList[j]
        }else {
            if(intersects.length < objectMin ) {
                objectMin = intersects.length
                result = pointList[j]
            }
        }
        if(objectMin == 0) {
            break
        }
    }
    return result
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49

returnAxis 就是最佳观测点

# 将摄像头平滑的移动到指定的位置

使用tween.js来平滑过渡

// target是目标测点的位置, position是摄像头将要移动到的位置
function moveCamera(target,position) {
    let positionVar = {
        x1: camera.position.x,
        y1: camera.position.y,
        z1: camera.position.z,
        x2: orbitControls.target.x,
        y2: orbitControls.target.y,
        z2: orbitControls.target.z
    };
    //关闭控制器
    orbitControls.enabled = false;
    let tween = new TWEEN.Tween(positionVar);
    tween.to({
        x1: position.x,
        y1: position.y,
        z1: position.z,
        x2: target.x,
        y2: target.y,
        z2: target.z
    }, 1000);

    tween.onUpdate(function() {
        camera.position.x = positionVar.x1;
        camera.position.y = positionVar.y1;
        camera.position.z = positionVar.z1;
        orbitControls.target.x = positionVar.x2;
        orbitControls.target.y = positionVar.y2;
        orbitControls.target.z = positionVar.z2;
        orbitControls.update();
    })

    tween.onComplete(function() {
        ///开启控制器
        orbitControls.enabled = true;
    })

    tween.easing(TWEEN.Easing.Cubic.InOut);
    tween.start();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40

记得要在动画循环中加上: TWEEN.update() 这样动画才能动起来哦!