You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用Three.js与cannon-es开发骰子滚轮时凸多面体碰撞致崩溃求助

问题分析与解决方案

问题根源

你遇到的碰撞崩溃问题,本质是手动构造的CANNON.ConvexPolyhedron顶点与面数据存在错误:
STL文件的顶点是按三角形面重复存储的(同一个空间点会被多个相邻面重复记录),你直接将所有重复顶点存入points,并按顺序生成面索引,导致Cannon在碰撞裁剪时访问到异常的未定义向量,触发Cannot read properties of undefined (reading 'x')错误。


修复方案

方案一:自动计算凸包(推荐,最简单)

利用CANNON.ConvexHull自动处理顶点去重与凸多面体生成,无需手动维护面数据:

function createConvexHull(mesh: THREE.Mesh) {
  const positionArray = mesh.geometry.attributes.position.array;
  const points: CANNON.Vec3[] = [];

  // 收集STL所有顶点(重复也没关系,ConvexHull会自动去重)
  for (let i = 0; i < positionArray.length; i += 3) {
    points.push(new CANNON.Vec3(
      positionArray[i],
      positionArray[i + 1],
      positionArray[i + 2]
    ));
  }

  // 自动生成凸包的顶点与面
  const convexHull = new CANNON.ConvexHull();
  convexHull.build(points);

  const convexGeometry = new CANNON.ConvexPolyhedron({
    vertices: convexHull.vertices,
    faces: convexHull.faces
  });

  const body = new CANNON.Body({ mass: 1 });
  body.addShape(convexGeometry);
  return body;
}

方案二:手动去重顶点(适合需要自定义面数据的场景)

先对STL顶点做去重处理,再基于唯一顶点生成正确的面索引:

function createConvexHull(mesh: THREE.Mesh) {
  const positionArray = mesh.geometry.attributes.position.array;
  const vertexMap = new Map<string, number>();
  const uniquePoints: CANNON.Vec3[] = [];
  const faces: number[][] = [];

  // 第一步:去重顶点,生成唯一顶点列表(用固定精度避免浮点误差)
  for (let i = 0; i < positionArray.length; i += 3) {
    const x = positionArray[i].toFixed(6);
    const y = positionArray[i + 1].toFixed(6);
    const z = positionArray[i + 2].toFixed(6);
    const key = `${x},${y},${z}`;
    
    if (!vertexMap.has(key)) {
      vertexMap.set(key, uniquePoints.length);
      uniquePoints.push(new CANNON.Vec3(
        positionArray[i],
        positionArray[i + 1],
        positionArray[i + 2]
      ));
    }
  }

  // 第二步:基于唯一顶点生成面索引
  for (let i = 0; i < positionArray.length / 3; i += 3) {
    const faceIndices: number[] = [];
    for (let j = 0; j < 3; j++) {
      const idx = i + j;
      const x = positionArray[idx * 3].toFixed(6);
      const y = positionArray[idx * 3 + 1].toFixed(6);
      const z = positionArray[idx * 3 + 2].toFixed(6);
      const key = `${x},${y},${z}`;
      faceIndices.push(vertexMap.get(key)!);
    }
    faces.push(faceIndices);
  }

  // 第三步:创建凸多面体并补全内部计算
  const convexGeometry = new CANNON.ConvexPolyhedron({
    vertices: uniquePoints,
    faces: faces
  });
  convexGeometry.computeNormals(); // 手动计算法线确保碰撞逻辑正常
  convexGeometry.computeEdges();

  const body = new CANNON.Body({ mass: 1 });
  body.addShape(convexGeometry);
  return body;
}

额外调试建议

  • 生成ConvexPolyhedron后,打印convexGeometry.vertices和convexGeometry.faces,检查是否存在索引越界或重复顶点问题;
  • 确保二十面骰子STL是凸多面体(本身符合),凹面体不能用ConvexPolyhedron;
  • 可尝试调整Cannon世界的碰撞参数,比如将broadphase设置为new CANNON.SAPBroadphase(world),提升碰撞检测稳定性。

内容的提问来源于stack exchange,提问作者munHunger

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.02 22:50:23