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

Cannon-es与Three.js中物体穿透地面平面问题求助

Cannon-ES + Three.js 物体穿透地面与异常位移问题修复

针对你遇到的方块/模型穿透地面、调整地面旋转后物体异常上移的问题,核心问题出在物体初始位置偏移、模型与物理体中心不对齐这两点,以下是分步修复方案:

1. 地面物理体的旋转确认

Cannon-ES的Plane默认处于XY平面(法线沿Z轴),而你的重力沿Y轴负方向,因此将地面绕X轴旋转Math.PI/2是正确的——这会让地面处于XZ平面(法线沿Y轴),与重力方向垂直。你当前的旋转代码没问题,只需确保可视化网格与物理体同步即可(你已经实现了这部分)。

2. 修复物体初始位置(关键)

你的boxBody和模型对应的body都未设置初始位置,默认在(0,0,0)。而地面旋转后,地面平面为Y=0,物体物理体的中心在Y=0,但物体有高度(比如方块半高15),导致物体底部直接处于地面以下,触发穿透。

给物体设置高于地面的初始位置:

// 模型物理体初始位置
body.position.set(0, 50, 0);
// 方块物理体初始位置
boxBody.position.set(0, 50, 0);

3. 对齐模型与物理体的中心

OBJ模型的原点可能不在几何中心,导致物理体与可视化模型位置偏移,看起来像是穿透。需要将模型几何中心移至原点:

loader.load('resources/Model_8_8.obj', (object) => {
  const geometry = object.children[0].geometry;
  // 计算模型包围盒中心
  const boundingBox = new THREE.Box3().setFromObject(object);
  const center = boundingBox.getCenter(new THREE.Vector3());
  // 将模型几何平移至原点
  geometry.translate(-center.x, -center.y, -center.z);
  
  mesh = new THREE.Mesh(geometry, material);
  scene.add(mesh);

  // 重新计算物理体尺寸
  const width = boundingBox.max.x - boundingBox.min.x;
  const height = boundingBox.max.y - boundingBox.min.y;
  const depth = boundingBox.max.z - boundingBox.min.z;
  const shape = new CANNON.Box(new CANNON.Vec3(width / 2, height / 2, depth / 2));
  
  body = new CANNON.Body({ mass:10, shape:shape });
  body.position.set(0, 50, 0); // 设置初始位置
  world.addBody(body);
  
  // ... 其余代码保持不变 ...
});

完整修改后的代码

<html>
  <head>
    <title>My 3D Model</title>
    <script type="module">
        import * as THREE from "https://cdn.skypack.dev/three@0.136.0/build/three.module.js";
        import { GUI } from "https://cdn.skypack.dev/dat.gui@0.7.7/build/dat.gui.module.js";
        import { TrackballControls } from "https://cdn.skypack.dev/three@0.136.0/examples/jsm/controls/TrackballControls.js";
        import {OrbitControls} from "https://cdn.skypack.dev/three@0.136.0/examples/jsm/controls/OrbitControls.js";
        import { STLLoader } from "https://cdn.skypack.dev/three@0.136.0/examples/jsm/loaders/STLLoader.js";
        import { OBJLoader } from "https://cdn.skypack.dev/three@0.136.0/examples/jsm/loaders/OBJLoader.js";
        import * as CANNON from 'https://unpkg.com/cannon-es@0.19.0/dist/cannon-es.js'

        // webgl 
        const renderer = new THREE.WebGLRenderer();
        renderer.setSize(window.innerWidth,window.innerHeight);
        document.body.appendChild(renderer.domElement);

        // scene
        const scene = new THREE.Scene();
        const camera = new THREE.PerspectiveCamera(
            75,
            window.innerWidth/window.innerHeight,
            0.1,
            1000
        );
        const orbit = new OrbitControls(camera,renderer.domElement);
        camera.position.set(0,200,-100);
        orbit.update();

        const ambientLight = new THREE.AmbientLight();
        scene.add( ambientLight );
        const directionalLight = new THREE.DirectionalLight( 0xffffff, 5.0 );
        directionalLight.position.set( 10, 100, -100 );
        scene.add( directionalLight );
        
        const world = new CANNON.World({
          gravity : new CANNON.Vec3(0,-9.81,0)
        });

        const groundBody = new CANNON.Body({
            shape : new CANNON.Plane(),
            type : CANNON.Body.STATIC,
        })
        world.addBody(groundBody);
        groundBody.quaternion.setFromEuler(Math.PI/2,0,0);

        let mesh,body;
        const material = new THREE.MeshPhysicalMaterial({ color: 0xaaaaaa })
        const loader = new OBJLoader()
        loader.load('resources/Model_8_8.obj', (object) => {
          const geometry = object.children[0].geometry;
          // 修正模型中心
          const boundingBox = new THREE.Box3().setFromObject(object);
          const center = boundingBox.getCenter(new THREE.Vector3());
          geometry.translate(-center.x, -center.y, -center.z);
          
          mesh = new THREE.Mesh(geometry, material)
          scene.add(mesh)

          // 计算物理体尺寸
          const width = boundingBox.max.x - boundingBox.min.x;
          const height = boundingBox.max.y - boundingBox.min.y;
          const depth = boundingBox.max.z - boundingBox.min.z;
          const shape = new CANNON.Box(new CANNON.Vec3(width / 2, height / 2, depth / 2))
          
          body= new CANNON.Body({
            mass :10,
            shape :shape
          })
          body.position.set(0, 50, 0); // 设置初始位置
          world.addBody(body);

          const boxGeometry = new THREE.BoxGeometry(30,30,30);
          const boxMat = new THREE.MeshBasicMaterial({
              color: 0x00ff00,
              wireframe : true
          });
          const boxMesh = new THREE.Mesh(boxGeometry,boxMat);
          scene.add(boxMesh);

          const groundGeo = new THREE.PlaneGeometry(300,300);
          const groundMat = new THREE.MeshBasicMaterial({
              color:0xffffff,
              side : THREE.DoubleSide,
              wireframe: true
          });
          const groundMesh = new THREE.Mesh(groundGeo,groundMat);
          scene.add(groundMesh);

          const boxBody = new CANNON.Body({
              mass : 10,
              shape : new CANNON.Box(new CANNON.Vec3(15,15,15))
          });
          boxBody.position.set(0, 50, 0); // 设置初始位置
          world.addBody(boxBody);
      
          const timeStep = 1 / 60 ;
          function animate(){
              world.step(timeStep);
              groundMesh.position.copy(groundBody.position);
              groundMesh.quaternion.copy(groundBody.quaternion);
              boxMesh.position.copy(boxBody.position);
              boxMesh.quaternion.copy(boxBody.quaternion);
              mesh.position.copy(body.position);
              mesh.quaternion.copy(body.quaternion);
              renderer.render(scene,camera);
          }
          renderer.setAnimationLoop(animate);
          window.addEventListener('resize',function(){
              camera.aspect = window.innerWidth / window.innerHeight;
              camera.updateProjectionMatrix();
              renderer.setSize(window.innerWidth,window.innerHeight);
          });
        })
    </script>
  </head>
  <body>
  </body>
</html>

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.24 13:00:01