Toggle navigation
集客麦麦@谢坤
首页
随笔
首页
>>
创作中心
>>
Three.js +...
Three.js + Cannonjs 物理模拟
Not Found
使用Threejs + Cannon物理引擎模拟小球自由落体反弹效果 ```js import * as THREE from 'three' import * as CANNON from 'cannon' import { OrbitControls } from 'three/addons/controls/OrbitControls.js'; // 场景 const scene = new THREE.Scene() // 相机 const camera = new THREE.PerspectiveCamera( 45, window.innerWidth/ window.innerHeight, 1, 1000 ); camera.position.x = 3 camera.position.z = 6 camera.lookAt(0,0,0) // 渲染 const renderer = new THREE.WebGLRenderer() renderer.setSize(window.innerWidth, window.innerHeight) document.body.appendChild(renderer.domElement) // 几何体 const radius = 2 // m const boll = new THREE.Mesh( new THREE.SphereGeometry( radius, 32, 16 ), new THREE.MeshBasicMaterial({color: 0xff0000}) ) boll.position.set(0, 15, 0) const ground = new THREE.Mesh( new THREE.PlaneGeometry( 80, 80 ), new THREE.MeshBasicMaterial({color: 0x999999}) ) ground.rotation.x = -Math.PI / 2 scene.add(boll, ground); // connon 物理引擎 const world = new CANNON.World({ // gravity: new CANNON.Vec3(0, -9.82, 0), // m/s² // 使用兼容:这个语法是connon-es的,connonjs不起效,若使用使用下面的方法 }) world.gravity.set(0, -9.82, 0) // 添加物理地面 const groundMaterial = new CANNON.Material('groundMaterial') // 地面物理材料 const sphereMaterial = new CANNON.Material('sphereMaterial') // 小球物理材料 const contactMaterial = new CANNON.ContactMaterial( // 接触材料 groundMaterial, sphereMaterial, { // friction: 0.3 // 摩擦系数0-1 默认: 0.3 restitution: 0.7 // 反弹系数0-1 默认: 0.3 } ) world.addContactMaterial(contactMaterial) // 地面 const groundBody = new CANNON.Body({ // type: CANNON.Body.STATIC, mass: 0, shape: new CANNON.Plane(), material: groundMaterial }) groundBody.quaternion.setFromEuler(-Math.PI / 2, 0, 0) // 面朝上 world.addBody(groundBody) // 添加物理球模拟球面 const sphereBody = new CANNON.Body({ mass: 5, // kg shape: new CANNON.Sphere(radius), // position: new CONNON.Vec3(0, 10, 0), // 或下面这种写法 position: boll.position, material: sphereMaterial }) // sphereBody.position.set(0, 10, 0) // m world.addBody(sphereBody) const updatePhysic = () => { world.step(1/60) // console.log(sphereBody.position) boll.position.copy(sphereBody.position) // 更新对应物理位置和threejs 对应网格 } // 控制器 const controls = new OrbitControls( camera, renderer.domElement ); camera.position.set( 0, 10, 40); controls.enableDamping = true // 阻尼开启 controls.dampingFactor = 0.03 // 阻尼系数 controls.update(); const gridHelper = new THREE.GridHelper( 30, 30 ); const axesHelper = new THREE.AxesHelper( 50 ); scene.add( gridHelper, axesHelper ); animate() function animate() { updatePhysic() requestAnimationFrame(animate) controls.update(); renderer.render(scene, camera) } ``` 代码的详细分析与解读: 代码 ### **一、代码整体功能** 该代码实现了一个三维场景中的物理模拟效果: - **场景元素**:一个红色球体从高处下落,与地面碰撞后反弹,使用轨道控制器(OrbitControls)观察场景。 - **物理引擎**:通过Cannon.js实现重力、碰撞和反弹效果,球体具有质量,地面为静态刚体。 ### **二、关键代码模块解析** #### **1. 场景与相机设置** ```javascript const scene = new THREE.Scene(); // 创建场景 const camera = new THREE.PerspectiveCamera(45, window.innerWidth/window.innerHeight, 1, 1000); camera.position.set(0, 10, 40); // 相机初始位置(高处俯视) ``` - **透视相机**:视野角度45°,宽高比自适应窗口,近裁切面1,远裁切面1000。 - **轨道控制器**:允许鼠标拖拽旋转/缩放视角,`enableDamping`开启阻尼使视角移动更平滑。 #### **2. 几何体与渲染** ```javascript // 红色球体(视觉对象) const boll = new THREE.Mesh( new THREE.SphereGeometry(2, 32, 16), new THREE.MeshBasicMaterial({ color: 0xff0000 }) ); boll.position.set(0, 15, 0); // 初始高度15米 // 灰色地面(视觉对象) const ground = new THREE.Mesh( new THREE.PlaneGeometry(80, 80), new THREE.MeshBasicMaterial({ color: 0x999999 }) ); ground.rotation.x = -Math.PI / 2; // 平面旋转90°使其朝上 ``` - **球体**:半径2米,使用基础材质(无光照效果)。 - **地面**:80x80米的平面,通过旋转`Math.PI/2`弧度(90°)使平面朝上(默认平面沿z轴延伸,旋转后沿y轴朝上)。 #### **3. Cannon.js物理引擎配置** ##### **3.1 世界参数** ```javascript const world = new CANNON.World(); world.gravity.set(0, -9.82, 0); // 重力方向:y轴负方向(竖直向下) ``` - **重力**:模拟地球重力加速度`9.82 m/s²`。 ##### **3.2 物理材质与接触属性** ```javascript const groundMaterial = new CANNON.Material('groundMaterial'); // 地面材质 const sphereMaterial = new CANNON.Material('sphereMaterial'); // 球体材质 // 接触材料(定义碰撞时的摩擦和反弹) const contactMaterial = new CANNON.ContactMaterial( groundMaterial, sphereMaterial, { restitution: 0.7 } // 反弹系数0.7(损失30%能量) ); world.addContactMaterial(contactMaterial); ``` - **反弹系数(restitution)**:值为0时完全不反弹(能量全部损失),值为1时完全弹性碰撞(无能量损失)。此处设置为0.7,球体碰撞后会保留70%的动能。 ##### **3.3 物理刚体(地面与球体)** ```javascript // 地面刚体(静态,质量为0) const groundBody = new CANNON.Body({ mass: 0, // 静态物体质量为0 shape: new CANNON.Plane(), // 平面形状 material: groundMaterial }); groundBody.quaternion.setFromEuler(-Math.PI / 2, 0, 0); // 旋转平面使其朝上 // 球体刚体(动态,质量5kg) const sphereBody = new CANNON.Body({ mass: 5, // 质量5千克 shape: new CANNON.Sphere(radius), // 球体形状,半径与视觉球体一致 position: new CANNON.Vec3(0, 10, 0), // 初始位置(物理位置与视觉位置需同步) material: sphereMaterial }); ``` - **静态刚体(地面)**:`mass: 0`表示不受力影响,固定不动。 - **动态刚体(球体)**:具有质量,会受重力影响下落,并与地面碰撞。 - **旋转同步**:地面的`quaternion`旋转与Three.js中的`rotation.x`对应,确保物理平面与视觉平面对齐。 #### **4. 物理更新与视觉同步** ```javascript const updatePhysic = () => { world.step(1/60); // 按60Hz更新物理世界 boll.position.copy(sphereBody.position); // 将物理位置同步到视觉球体 }; function animate() { updatePhysic(); // 更新物理状态 requestAnimationFrame(animate); // 循环渲染 controls.update(); // 更新控制器状态 renderer.render(scene, camera); // 渲染场景 } ``` - **物理更新**:`world.step(1/60)`表示每秒更新60次,确保物理模拟流畅。 - **视觉同步**:通过`boll.position.copy(sphereBody.position)`将物理引擎计算出的球体位置同步到Three.js的Mesh对象,实现视觉与物理的统一。 #### **5. 辅助工具(网格与坐标轴)** ```javascript const gridHelper = new THREE.GridHelper(30, 30); // 30x30的网格辅助线 const axesHelper = new THREE.AxesHelper(50); // 坐标轴辅助线(x=红,y=绿,z=蓝) scene.add(gridHelper, axesHelper); ``` - **调试工具**:帮助观察场景中的坐标和碰撞位置,网格线间距1米,坐标轴长度50米。