Skip to content

132. 实战:实现酷家乐同款拖拽旋转控制器(二)

Published:

上节实现了框选效果:

image.png

这节来画这些箭头:

image.png

用 Shape + ShapeGeometry 来画。

先画上面这个

尺寸是这样:

宽度下面是 50,上面是 100,左右两个箭头的突出是 25

image.png

高度下面是 150,上面是 50

image.png

根据这个来画 Path

image.png

const arrowTopShape = new THREE.Shape();
arrowTopShape.lineTo(0, 150);
arrowTopShape.lineTo(-25, 150);
arrowTopShape.lineTo(25, 200);
arrowTopShape.lineTo(75, 150);
arrowTopShape.lineTo(50, 150);
arrowTopShape.lineTo(50, 0);
arrowTopShape.lineTo(0, 0);

const arrowTopGeometry = new THREE.ShapeGeometry(arrowTopShape);
const arrowTopMaterial = new THREE.MeshBasicMaterial({
    color: 'blue',
    side: THREE.DoubleSide
});
const arrowTopMesh = new THREE.Mesh(arrowTopGeometry, arrowTopMaterial);
obj.add(arrowTopMesh);
arrowTopMesh.position.copy(center);
arrowTopMesh.position.y += size.y/2;

先把它移到 box3.center 位置,然后网上移动 box3.size.y

这样正好在 obj 上面:

image.png

然后要左移 25,这样箭头才居中(这与我们画的时候的坐标有关,如果你直接把箭头画在中间就不用移动了)

arrowTopMesh.position.x -= 25;

2025-08-10 23.22.06.gif

同样的方式写下另外两个方向的:

const arrowLeftGeometry = new THREE.ShapeGeometry(arrowTopShape);
const arrowLeftMaterial = new THREE.MeshBasicMaterial({
    color: 'green',
    side: THREE.DoubleSide
});
const arrowLeftMesh = new THREE.Mesh(arrowLeftGeometry, arrowLeftMaterial);
obj.add(arrowLeftMesh);
arrowLeftMesh.position.copy(center);
arrowLeftMesh.position.x += size.x/2;
arrowLeftMesh.rotateZ(-Math.PI / 2);
arrowLeftMesh.position.y += 25;

const arrowFrontGeometry = new THREE.ShapeGeometry(arrowTopShape);
const arrowFrontMaterial = new THREE.MeshBasicMaterial({
    color: 'red',
    side: THREE.DoubleSide
});
const arrowFrontMesh = new THREE.Mesh(arrowFrontGeometry, arrowFrontMaterial);
obj.add(arrowFrontMesh);
arrowFrontMesh.position.copy(center);
arrowFrontMesh.position.z += size.z/2;
arrowFrontMesh.rotateX(Math.PI / 2);
arrowFrontMesh.position.x -= 25;

Shape 就不用再画了,调整下旋转角度就行。

2025-08-11 10.34.17.gif

画是画出来了,但现在有个问题:

2025-08-11 10.36.39.gif

酷家乐这个随着摄像机位置的调整,箭头会展示在不同的方向,这样刚好能看到。

我们这个旋转到侧面之后就看不到箭头了。

2025-08-11 10.38.43.gif

所以我们要跟随摄像机位置的调整来改变这三个箭头的位置。

那如何判断相机方向和坐标轴的夹角呢?

同样是用前面学的向量点积:

比如我们判断相机视线方向和 z 轴正半轴的夹角:

image.png

就可以这样写:

image.png

controls.addEventListener('change', () => {
  const dir = camera.getWorldDirection(new THREE.Vector3());
  const zAxis = new THREE.Vector3(0, 0, 1);
  const dot = dir.dot(zAxis);
  const angle = Math.acos(dot);
  
  const res = THREE.MathUtils.radToDeg(angle);
  
  console.log(res);
});

先拿到相机方向,和 z 轴的向量做点积。

之后 acos 求出弧度值再转角度就好了。

试下效果:

2025-09-19 18.34.17.gif

随着相机方向的改变,可以看到夹角在 0 到 180 度之间变化

那我们是不是就可以在角度小于 90 度的时候,红色箭头在正半轴,否则在负半轴呢?

酷家乐这个就是这样:

2025-09-19 19.02.12.gif

我们也实现下:

image.png

export {
  controls,
  camera
}

首先,把这俩对象导出。

然后在控制器里加上这段逻辑:

image.png

就是根据旋转角度,设置箭头的不同的方向、位置。

controls.addEventListener('change', () => {
    const dir = camera.getWorldDirection(new THREE.Vector3());
    const zAxis = new THREE.Vector3(0, 0, 1);
    const dot = dir.dot(zAxis);
    const angle = Math.acos(dot);

    const res = THREE.MathUtils.radToDeg(angle);

    if(res > 90) {
        arrowFrontMesh.position.copy(center);
        arrowFrontMesh.position.z += size.z/2;
        arrowFrontMesh.rotation.z = 0;
    } else {
        arrowFrontMesh.position.copy(center);
        arrowFrontMesh.position.z -= size.z/2;
        arrowFrontMesh.rotation.z = -Math.PI;
    }
});

试一下:

2025-09-19 19.29.17.gif

完美!

蓝色箭头就不用变了,把绿色箭头的也实现一下:

它是判断和 x 轴正方向的夹角:

image.png

const xAxis = new THREE.Vector3(1, 0, 0);
const dot2 = dir.dot(xAxis);
const angle2 = Math.acos(dot2);

const res2 = THREE.MathUtils.radToDeg(angle2);
if(res2 > 90) {
    arrowLeftMesh.position.copy(center);
    arrowLeftMesh.position.y += 25;
    arrowLeftMesh.position.x += size.x/2;
    arrowLeftMesh.rotation.y = 0;
} else {
    arrowLeftMesh.position.copy(center);
    arrowLeftMesh.position.y += 25;
    arrowLeftMesh.position.x -= size.x/2;
    arrowLeftMesh.rotation.y = -Math.PI;
}

实现思路一样。

2025-09-19 19.33.40.gif

对比下酷家乐的:

2025-09-19 19.34.34.gif

和我们的:

2025-09-19 19.35.03.gif

交互是一样的。

案例代码上传了小册仓库

总结

这节我们把控制器的箭头绘制了出来。

绘制箭头用 Shape + ShapeGeometry。

绘制出来之后要跟随相机角度移动。

我们计算相机方向和坐标轴的点积,求出夹角。

然后根据角度是否大于 90 度来修改箭头的位置、方向。

这样就实现了酷家乐同款控制器的外观效果,下节我们来做交互。

评论