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
相关产品推荐
相关产品推荐

