如何在THREE.js中点击BoxGeometry的特定面?
获取Three.js BoxGeometry特定面坐标用于编程点击
嘿,我来帮你搞定这个问题!要获取BoxGeometry特定面(比如顶面、左右面)的坐标来实现编程点击,其实有两种靠谱的思路,我给你详细拆解下:
方法一:手动提取并识别特定面坐标(适合需要直接操作面数据的场景)
现代Three.js里的BoxGeometry是BufferGeometry类型,顶点数据存在attributes.position里,面的索引存在index属性中。我们可以一步步提取并转换坐标:
先把几何体的局部顶点数据提取出来
先从BufferGeometry里把所有顶点的局部坐标拿出来,方便后续处理:const boxGeo = yourMesh.geometry; const positionAttr = boxGeo.attributes.position; const localVertices = []; for (let i = 0; i < positionAttr.count; i++) { localVertices.push(new THREE.Vector3( positionAttr.getX(i), positionAttr.getY(i), positionAttr.getZ(i) )); }转换为世界坐标
你已经会做世界坐标转换了,直接用mesh.matrixWorld把局部顶点转成世界坐标就行:const worldVertices = localVertices.map(v => v.clone().applyMatrix4(yourMesh.matrixWorld));筛选特定面的顶点
BoxGeometry的局部坐标中心在原点,所以每个大面的顶点都有一个坐标是固定的极值。比如顶面的所有顶点,局部y值等于boxGeo.parameters.height / 2(半高)。我们可以用这个特征筛选出目标面的顶点索引:const halfHeight = boxGeo.parameters.height / 2; const topVertexIndices = []; for (let i = 0; i < localVertices.length; i++) { // 加个小容错,避免浮点精度坑 if (Math.abs(localVertices[i].y - halfHeight) < 0.001) { topVertexIndices.push(i); } }找到对应的三角形面
BoxGeometry的每个大面是由两个三角形组成的,我们遍历index数组里的面(每个面3个顶点索引),找出所有顶点都属于目标顶点组的面:const indexArr = boxGeo.index.array; const topFaces = []; for (let i = 0; i < indexArr.length; i += 3) { const idx1 = indexArr[i]; const idx2 = indexArr[i+1]; const idx3 = indexArr[i+2]; if (topVertexIndices.includes(idx1) && topVertexIndices.includes(idx2) && topVertexIndices.includes(idx3)) { topFaces.push({ vertices: [worldVertices[idx1], worldVertices[idx2], worldVertices[idx3]], // 可以顺便算出面中心,方便点击检测 center: new THREE.Vector3().addVectors(worldVertices[idx1], worldVertices[idx2]) .add(worldVertices[idx3]).divideScalar(3) }); } }现在
topFaces里就是顶面的两个三角形面的世界坐标和中心了,你可以用这些数据来做编程点击判断。
方法二:用Raycaster直接检测(更高效,推荐)
其实没必要手动提取面坐标,Three.js的Raycaster已经帮我们做好了面检测,返回的结果里直接包含命中的面,我们只需要判断这个面属于哪个大面就行:
const raycaster = new THREE.Raycaster(); const mouse = new THREE.Vector2(); // 绑定鼠标点击事件 window.addEventListener('click', (e) => { // 把鼠标坐标转成Three.js需要的标准化设备坐标 mouse.x = (e.clientX / window.innerWidth) * 2 - 1; mouse.y = -(e.clientY / window.innerHeight) * 2 + 1; // 从相机发射射线 raycaster.setFromCamera(mouse, yourCamera); const hits = raycaster.intersectObject(yourMesh); if (hits.length > 0) { const hitFace = hits[0].face; // 取面的第一个顶点的局部坐标来判断所属大面 const vertex = new THREE.Vector3( yourMesh.geometry.attributes.position.getX(hitFace.a), yourMesh.geometry.attributes.position.getY(hitFace.a), yourMesh.geometry.attributes.position.getZ(hitFace.a) ); const { width, height, depth } = yourMesh.geometry.parameters; const halfWidth = width / 2; const halfHeight = height / 2; const halfDepth = depth / 2; // 判断是哪个面 if (Math.abs(vertex.y - halfHeight) < 0.001) { console.log('触发顶面点击!'); // 这里写你的点击逻辑 } else if (Math.abs(vertex.y + halfHeight) < 0.001) { console.log('触发底面点击!'); } else if (Math.abs(vertex.x + halfWidth) < 0.001) { console.log('触发左面点击!'); } else if (Math.abs(vertex.x - halfWidth) < 0.001) { console.log('触发右面点击!'); } // 前后面对应z坐标的正负半深,逻辑同理 } });
一些要注意的坑
- 浮点精度问题:比较坐标的时候一定要加个小的容错值(比如0.001),不然可能因为浮点计算的微小误差导致判断失败。
- 变换兼容:如果你的Mesh有缩放、旋转,
mesh.matrixWorld已经包含了所有变换,所以转换坐标的时候直接用它就没问题。 - 旧版Geometry:如果你用的是很老的Three.js版本(r124及以前),BoxGeometry是Geometry类型,那更简单,直接遍历
mesh.geometry.faces数组,每个Face3对象里有a/b/c三个顶点索引,判断逻辑和上面一致。
内容的提问来源于stack exchange,提问作者Detuned
相关产品推荐
相关产品推荐

