第44节:物理引擎进阶:Bullet.js集成与高级物理模拟
第44节:物理引擎进阶:Bullet.js集成与高级物理模拟
概述
物理引擎是现代3D应用的核心组件之一。本节将深入探讨Bullet物理引擎与Three.js的集成,涵盖刚体动力学、碰撞检测、约束系统等高级特性,并实现软体和流体物理模拟。

物理系统架构:
核心原理
物理引擎对比
| 特性 | Bullet.js | Cannon.js | Ammo.js |
|---|---|---|---|
| 性能 | 非常高 | 中等 | 高 |
| 特性完整度 | 完整 | 基础 | 完整 |
| 内存使用 | 中等 | 低 | 高 |
| 学习曲线 | 陡峭 | 平缓 | 中等 |
| 社区支持 | 强大 | 活跃 | 一般 |
刚体类型
// 刚体配置示例
class RigidBodyConfig {static createDynamicBody(mass, shape, position, rotation) {return {mass: mass,shape: shape,position: position,rotation: rotation,friction: 0.5,restitution: 0.3,linearDamping: 0.1,angularDamping: 0.1};}static createKinematicBody(shape, position, rotation) {return {mass: 0, // 质量为0表示运动学刚体shape: shape,position: position,rotation: rotation,isKinematic: true};}static createStaticBody(shape, position, rotation) {return {mass: 0, // 质量为0表示静态刚体shape: shape,position: position,rotation: rotation};}
}
完整代码实现
高级物理模拟系统
<template><div class="physics-simulation-container"><!-- 3D渲染视图 --><div class="render-view"><canvas ref="renderCanvas" class="render-canvas"></canvas><!-- 物理调试信息 --><div class="physics-debug"><div class="debug-panel"><h4>物理系统状态</h4><div class="stats-grid"><div class="stat-item"><span>刚体数量:</span><span>{{ rigidBodyCount }}</span></div><div class="stat-item"><span>碰撞对:</span><span>{{ collisionPairs }}</span></div><div class="stat-item"><span>物理帧率:</span><span>{{ physicsFPS }} FPS</span></div><div class="stat-item"><span>模拟时间:</span><span>{{ simulationTime }}ms</span></div><div class="stat-item"><span>内存使用:</span><span>{{ memoryUsage }} MB</span></div></div></div><div class="control-panel"><h4>模拟控制</h4><div class="control-buttons"><button @click="toggleSimulation" class="control-button">{{ isRunning ? '⏸️ 暂停' : '▶️ 开始' }}</button><button @click="resetSimulation" class="control-button">🔄 重置</button><button @click="stepSimulation" class="control-button" :disabled="isRunning">⏭️ 单步</button></div></div></div></div><!-- 配置面板 --><div class="config-panel"><div class="panel-section"><h3>🎯 场景选择</h3><div class="scene-selection"><button v-for="scene in availableScenes" :key="scene.id":class="{ active: currentScene === scene.id }"@click="loadScene(scene.id)"class="scene-button">{{ scene.name }}</button></div></div><div class="panel-section"><h3>⚙️ 物理参数</h3><div class="physics-params"><div class="param-group"><label>重力强度</label><input type="range" v-model="gravityStrength" min="0" max="20" step="0.1"><span>{{ gravityStrength }} m/s²</span></div><div class="param-group"><label>时间缩放</label><input type="range" v-model="timeScale" min="0" max="2" step="0.1"><span>{{ timeScale }}x</span></div><div class="param-group"><label>子步数量</label><input type="range" v-model="substeps" min="1" max="10"><span>{{ substeps }}</span></div><div class="param-group"><label>求解器迭代</label><input type="range" v-model="solverIterations" min="1" max="20"><span>{{ solverIterations }}</span></div></div></div><div class="panel-section"><h3>🎮 交互工具</h3><div class="interaction-tools"><div class="tool-buttons"><button @click="setInteractionMode('spawn')" :class="{ active: interactionMode === 'spawn' }"class="tool-button">🎯 生成物体</button><button @click="setInteractionMode('force')":class="{ active: interactionMode === 'force' }"class="tool-button">💨 施加力</button><button @click="setInteractionMode('constraint')":class="{ active: interactionMode === 'constraint' }"class="tool-button">🔗 创建约束</button></div><div class="tool-options" v-if="interactionMode === 'spawn'"><div class="option-group"><label>物体类型</label><select v-model="spawnObjectType"><option value="cube">立方体</option><option value="sphere">球体</option><option value="cylinder">圆柱体</option><option value="compound">复合形状</option></select></div><div class="option-group"><label>质量</label><input type="range" v-model="spawnMass" min="0.1" max="10" step="0.1"><span>{{ spawnMass }} kg</span></div><button @click="spawnRandomObjects" class="action-button">🎲 随机生成</button></div></div></div><div class="panel-section"><h3>🔧 高级特性</h3><div class="advanced-features"><div class="feature-toggle"><label><input type="checkbox" v-model="enableSoftBodies">软体模拟</label></div><div class="feature-toggle"><label><input type="checkbox" v-model="enableFluidSimulation">流体模拟</label></div><div class="feature-toggle"><label><input type="checkbox" v-model="enableVehiclePhysics">车辆物理</label></div><div class="feature-toggle"><label><input type="checkbox" v-model="showPhysicsDebug">显示物理调试</label></div></div></div><div class="panel-section"><h3>📊 性能监控</h3><div class="performance-monitor"><div class="monitor-item"><span>CPU时间:</span><span>{{ cpuTime }}ms</span></div><div class="monitor-item"><span>碰撞检测:</span><span>{{ collisionTime }}ms</span></div><div class="monitor-item"><span>约束求解:</span><span>{{ constraintTime }}ms</span></div><div class="monitor-item"><span>内存峰值:</span><span>{{ peakMemory }} MB</span></div></div></div></div><!-- 物理调试视图 --><div v-if="showPhysicsDebug" class="physics-debug-view"><canvas ref="debugCanvas" class="debug-canvas"></canvas></div></div>
</template><script>
import { onMounted, onUnmounted, ref, reactive, computed } from 'vue';
import * as THREE from 'three';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';// Bullet物理引擎封装
class BulletPhysicsEngine {constructor() {this.world = null;this.bodies = new Map();this.constraints = new Map();this.softBodies = new Map();this.isInitialized = false;this.init();}async init() {// 动态导入Bullet.jsif (typeof Ammo === 'undefined') {await this.loadAmmoJS();}this.setupPhysicsWorld();this.isInitialized = true;}loadAmmoJS() {return new Promise((resolve, reject) => {const script = document.createElement('script');script.src = 'https://cdn.jsdelivr.net/npm/ammo.js@1.0.0/builds/ammo.wasm.js';script.onload = resolve;script.onerror = reject;document.head.appendChild(script);});}setupPhysicsWorld() {// 碰撞配置const collisionConfiguration = new Ammo.btDefaultCollisionConfiguration();const dispatcher = new Ammo.btCollisionDispatcher(collisionConfiguration);const broadphase = new Ammo.btDbvtBroadphase();const solver = new Ammo.btSequentialImpulseConstraintSolver();// 创建物理世界this.world = new Ammo.btDiscreteDynamicsWorld(dispatcher, broadphase, solver, collisionConfiguration);// 设置重力this.setGravity(0, -9.8, 0);}setGravity(x, y, z) {if (this.world) {this.world.setGravity(new Ammo.btVector3(x, y, z));}}// 创建刚体createRigidBody(config) {const shape = this.createCollisionShape(config.shape);const transform = new Ammo.btTransform();transform.setIdentity();transform.setOrigin(new Ammo.btVector3(config.position.x, config.position.y, config.position.z));const motionState = new Ammo.btDefaultMotionState(transform);const localInertia = new Ammo.btVector3(0, 0, 0);if (config.mass > 0) {shape.calculateLocalInertia(config.mass, localInertia);}const rbInfo = new Ammo.btRigidBodyConstructionInfo(config.mass, motionState, shape, localInertia);const body = new Ammo.btRigidBody(rbInfo);// 设置物理属性if (config.friction !== undefined) {body.setFriction(config.friction);}if (config.restitution !== undefined) {body.setRestitution(config.restitution);}if (config.linearDamping !== undefined) {body.setDamping(config.linearDamping, config.angularDamping || 0.1);}// 添加到世界this.world.addRigidBody(body);const bodyId = this.generateBodyId();this.bodies.set(bodyId, {ammoBody: body,threeObject: config.threeObject,shape: shape,motionState: motionState});return bodyId;}createCollisionShape(shapeConfig) {switch (shapeConfig.type) {case 'box':return new Ammo.btBoxShape(new Ammo.btVector3(shapeConfig.halfExtents.x,shapeConfig.halfExtents.y, shapeConfig.halfExtents.z));case 'sphere':return new Ammo.btSphereShape(shapeConfig.radius);case 'cylinder':return new Ammo.btCylinderShape(new Ammo.btVector3(shapeConfig.halfExtents.x,shapeConfig.halfExtents.y,shapeConfig.halfExtents.z));case 'compound':const compoundShape = new Ammo.btCompoundShape();shapeConfig.children.forEach(child => {const childShape = this.createCollisionShape(child.shape);const transform = new Ammo.btTransform();transform.setIdentity();transform.setOrigin(new Ammo.btVector3(child.position.x, child.position.y, child.position.z));compoundShape.addChildShape(transform, childShape);});return compoundShape;default:return new Ammo.btBoxShape(new Ammo.btVector3(0.5, 0.5, 0.5));}}// 模拟步进stepSimulation(deltaTime, substeps = 1) {if (!this.world) return;const startTime = performance.now();this.world.stepSimulation(deltaTime, substeps);return performance.now() - startTime;}// 同步Three.js对象syncGraphics() {for (const [bodyId, bodyData] of this.bodies) {const transform = new Ammo.btTransform();bodyData.motionState.getWorldTransform(transform);const origin = transform.getOrigin();const rotation = transform.getRotation();// 更新Three.js对象if (bodyData.threeObject) {bodyData.threeObject.position.set(origin.x(), origin.y(), origin.z());bodyData.threeObject.quaternion.set(rotation.x(), rotation.y(), rotation.z(), rotation.w());}}}// 施加力applyForce(bodyId, force, relativePos) {const bodyData = this.bodies.get(bodyId);if (bodyData) {const ammoForce = new Ammo.btVector3(force.x, force.y, force.z);const ammoPos = new Ammo.btVector3(relativePos.x, relativePos.y, relativePos.z);bodyData.ammoBody.applyForce(ammoForce, ammoPos);Ammo.destroy(ammoForce);Ammo.destroy(ammoPos);}}generateBodyId() {return `body_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;}// 清理资源dispose() {// 清理Ammo.js对象for (const bodyData of this.bodies.values()) {this.world.removeRigidBody(bodyData.ammoBody);Ammo.destroy(bodyData.ammoBody);Ammo.destroy(bodyData.shape);Ammo.destroy(bodyData.motionState);}this.bodies.clear();}
}// 软体物理系统
class SoftBodySystem {constructor(physicsEngine) {this.physicsEngine = physicsEngine;this.softBodies = new Map();}// 创建布料createCloth(width, height, resolution, position) {// 创建布料网格const clothGeometry = new THREE.PlaneGeometry(width, height, resolution, resolution);const clothMaterial = new THREE.MeshStandardMaterial({ color: 0x44aaff,side: THREE.DoubleSide });const clothMesh = new THREE.Mesh(clothGeometry, clothMaterial);clothMesh.position.copy(position);// 这里应该创建对应的Bullet软体// 简化实现const softBodyId = `soft_${Date.now()}`;this.softBodies.set(softBodyId, {mesh: clothMesh,vertices: clothGeometry.attributes.position.array});return { softBodyId, mesh: clothMesh };}// 更新软体updateSoftBodies() {for (const bodyData of this.softBodies.values()) {// 更新顶点位置(简化实现)bodyData.mesh.geometry.attributes.position.needsUpdate = true;}}
}// 流体模拟系统
class FluidSimulationSystem {constructor() {this.particles = [];this.emitters = [];this.gridSize = 32;this.setupFluidGrid();}setupFluidGrid() {// 创建流体模拟网格this.fluidGrid = new Array(this.gridSize);for (let i = 0; i < this.gridSize; i++) {this.fluidGrid[i] = new Array(this.gridSize);for (let j = 0; j < this.gridSize; j++) {this.fluidGrid[i][j] = new Array(this.gridSize);for (let k = 0; k < this.gridSize; k++) {this.fluidGrid[i][j][k] = {density: 0,velocity: new THREE.Vector3(),pressure: 0};}}}}// 创建流体发射器createEmitter(position, rate, velocity) {const emitter = {position: position.clone(),rate: rate,velocity: velocity.clone(),timer: 0};this.emitters.push(emitter);return emitter;}// 更新流体模拟update(deltaTime) {this.updateEmitters(deltaTime);this.advectParticles(deltaTime);this.applyViscosity(deltaTime);this.projectVelocity();}updateEmitters(deltaTime) {for (const emitter of this.emitters) {emitter.timer += deltaTime;const particlesToEmit = Math.floor(emitter.timer * emitter.rate);for (let i = 0; i < particlesToEmit; i++) {this.createParticle(emitter.position, emitter.velocity);}emitter.timer -= particlesToEmit / emitter.rate;}}createParticle(position, velocity) {const particle = {position: position.clone(),velocity: velocity.clone(),life: 1.0,size: 0.1 + Math.random() * 0.1};this.particles.push(particle);}advectParticles(deltaTime) {for (const particle of this.particles) {// 简单欧拉积分particle.position.add(particle.velocity.clone().multiplyScalar(deltaTime));particle.life -= deltaTime * 0.5;// 边界碰撞this.handleBoundaryCollision(particle);}// 移除死亡的粒子this.particles = this.particles.filter(p => p.life > 0);}handleBoundaryCollision(particle) {const bounds = 5;if (particle.position.x < -bounds || particle.position.x > bounds) {particle.velocity.x *= -0.8;particle.position.x = THREE.MathUtils.clamp(particle.position.x, -bounds, bounds);}if (particle.position.y < -bounds || particle.position.y > bounds) {particle.velocity.y *= -0.8;particle.position.y = THREE.MathUtils.clamp(particle.position.y, -bounds, bounds);}if (particle.position.z < -bounds || particle.position.z > bounds) {particle.velocity.z *= -0.8;particle.position.z = THREE.MathUtils.clamp(particle.position.z, -bounds, bounds);}}// 简化流体动力学applyViscosity(deltaTime) {// 实现粘性力}projectVelocity() {// 实现速度场投影}
}export default {name: 'PhysicsSimulation',setup() {// 响应式状态const renderCanvas = ref(null);const debugCanvas = ref(null);const isRunning = ref(false);const currentScene = ref('domino');const gravityStrength = ref(9.8);const timeScale = ref(1.0);const substeps = ref(3);const solverIterations = ref(10);const interactionMode = ref('spawn');const spawnObjectType = ref('cube');const spawnMass = ref(1.0);const enableSoftBodies = ref(false);const enableFluidSimulation = ref(false);const enableVehiclePhysics = ref(false);const showPhysicsDebug = ref(false);// 性能统计const rigidBodyCount = ref(0);const collisionPairs = ref(0);const physicsFPS = ref(0);const simulationTime = ref(0);const memoryUsage = ref(0);const cpuTime = ref(0);const collisionTime = ref(0);const constraintTime = ref(0);const peakMemory = ref(0);// 场景配置const availableScenes = [{ id: 'domino', name: '多米诺骨牌' },{ id: 'jenga', name: '叠叠乐' },{ id: 'ragdoll', name: '布娃娃系统' },{ id: 'vehicle', name: '车辆测试' },{ id: 'destruction', name: '破坏模拟' }];// Three.js 和物理引擎对象let renderer, scene, camera, controls;let physicsEngine, softBodySystem, fluidSystem;let animationFrameId;let physicsFrameCount = 0;let lastPhysicsFpsUpdate = 0;// 初始化const init = async () => {await initRenderer();initScene();await initPhysics();loadScene(currentScene.value);startAnimationLoop();};// 初始化渲染器const initRenderer = () => {renderer = new THREE.WebGLRenderer({canvas: renderCanvas.value,antialias: true,powerPreference: "high-performance"});renderer.setSize(window.innerWidth, window.innerHeight);renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));renderer.shadowMap.enabled = true;renderer.shadowMap.type = THREE.PCFSoftShadowMap;};// 初始化场景const initScene = () => {scene = new THREE.Scene();scene.background = new THREE.Color(0x87CEEB);camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 1000);camera.position.set(10, 10, 10);controls = new OrbitControls(camera, renderer.domElement);controls.enableDamping = true;// 照明const ambientLight = new THREE.AmbientLight(0x404040, 0.6);scene.add(ambientLight);const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);directionalLight.position.set(50, 50, 25);directionalLight.castShadow = true;directionalLight.shadow.mapSize.width = 2048;directionalLight.shadow.mapSize.height = 2048;scene.add(directionalLight);};// 初始化物理引擎const initPhysics = async () => {physicsEngine = new BulletPhysicsEngine();await physicsEngine.init();softBodySystem = new SoftBodySystem(physicsEngine);fluidSystem = new FluidSimulationSystem();};// 加载场景const loadScene = (sceneId) => {// 清理现有场景clearScene();currentScene.value = sceneId;switch (sceneId) {case 'domino':createDominoScene();break;case 'jenga':createJengaScene();break;case 'ragdoll':createRagdollScene();break;case 'vehicle':createVehicleScene();break;case 'destruction':createDestructionScene();break;}updatePhysicsStats();};// 创建多米诺骨牌场景const createDominoScene = () => {// 创建地面createGround();// 创建多米诺骨牌const dominoCount = 20;for (let i = 0; i < dominoCount; i++) {const domino = createDomino(i * 1.2 - dominoCount * 0.6, 0.5, 0);scene.add(domino.mesh);}// 创建启动球const ball = createSphere(-dominoCount * 0.6 - 2, 1, 0, 1.0);scene.add(ball.mesh);};// 创建地面const createGround = () => {const groundGeometry = new THREE.PlaneGeometry(50, 50);const groundMaterial = new THREE.MeshStandardMaterial({ color: 0x7CFC00,roughness: 0.8,metalness: 0.2});const groundMesh = new THREE.Mesh(groundGeometry, groundMaterial);groundMesh.rotation.x = -Math.PI / 2;groundMesh.receiveShadow = true;scene.add(groundMesh);// 物理地面const groundBody = physicsEngine.createRigidBody({mass: 0,shape: { type: 'box', halfExtents: { x: 25, y: 0.1, z: 25 } },position: { x: 0, y: 0, z: 0 },threeObject: groundMesh});};// 创建多米诺骨牌const createDomino = (x, y, z) => {const dominoGeometry = new THREE.BoxGeometry(0.2, 1, 1);const dominoMaterial = new THREE.MeshStandardMaterial({ color: 0xFFFFFF });const dominoMesh = new THREE.Mesh(dominoGeometry, dominoMaterial);dominoMesh.position.set(x, y, z);dominoMesh.castShadow = true;const dominoBody = physicsEngine.createRigidBody({mass: 1.0,shape: { type: 'box', halfExtents: { x: 0.1, y: 0.5, z: 0.5 } },position: { x, y, z },threeObject: dominoMesh,friction: 0.5,restitution: 0.1});return { mesh: dominoMesh, body: dominoBody };};// 创建球体const createSphere = (x, y, z, radius = 0.5, mass = 1.0) => {const sphereGeometry = new THREE.SphereGeometry(radius, 32, 32);const sphereMaterial = new THREE.MeshStandardMaterial({ color: 0xFF4444 });const sphereMesh = new THREE.Mesh(sphereGeometry, sphereMaterial);sphereMesh.position.set(x, y, z);sphereMesh.castShadow = true;const sphereBody = physicsEngine.createRigidBody({mass: mass,shape: { type: 'sphere', radius: radius },position: { x, y, z },threeObject: sphereMesh,restitution: 0.7});return { mesh: sphereMesh, body: sphereBody };};// 清理场景const clearScene = () => {// 移除所有物理物体physicsEngine.dispose();// 移除所有Three.js物体(保留灯光和相机)const objectsToRemove = [];scene.traverse(object => {if (object.isMesh && object !== scene) {objectsToRemove.push(object);}});objectsToRemove.forEach(object => scene.remove(object));};// 切换模拟状态const toggleSimulation = () => {isRunning.value = !isRunning.value;};// 重置模拟const resetSimulation = () => {loadScene(currentScene.value);};// 单步模拟const stepSimulation = () => {if (!isRunning.value) {const deltaTime = 1 / 60;physicsEngine.stepSimulation(deltaTime, substeps.value);physicsEngine.syncGraphics();updatePhysicsStats();}};// 设置交互模式const setInteractionMode = (mode) => {interactionMode.value = mode;};// 生成随机物体const spawnRandomObjects = () => {const count = 5;for (let i = 0; i < count; i++) {const x = (Math.random() - 0.5) * 10;const y = 10 + Math.random() * 5;const z = (Math.random() - 0.5) * 10;let object;switch (spawnObjectType.value) {case 'cube':object = createCube(x, y, z, spawnMass.value);break;case 'sphere':object = createSphere(x, y, z, 0.5, spawnMass.value);break;case 'cylinder':object = createCylinder(x, y, z, spawnMass.value);break;}if (object) {scene.add(object.mesh);}}updatePhysicsStats();};// 创建立方体const createCube = (x, y, z, mass = 1.0) => {const cubeGeometry = new THREE.BoxGeometry(1, 1, 1);const cubeMaterial = new THREE.MeshStandardMaterial({ color: new THREE.Color().setHSL(Math.random(), 0.7, 0.5)});const cubeMesh = new THREE.Mesh(cubeGeometry, cubeMaterial);cubeMesh.position.set(x, y, z);cubeMesh.castShadow = true;const cubeBody = physicsEngine.createRigidBody({mass: mass,shape: { type: 'box', halfExtents: { x: 0.5, y: 0.5, z: 0.5 } },position: { x, y, z },threeObject: cubeMesh});return { mesh: cubeMesh, body: cubeBody };};// 创建圆柱体const createCylinder = (x, y, z, mass = 1.0) => {const cylinderGeometry = new THREE.CylinderGeometry(0.5, 0.5, 1, 32);const cylinderMaterial = new THREE.MeshStandardMaterial({ color: new THREE.Color().setHSL(Math.random(), 0.7, 0.5)});const cylinderMesh = new THREE.Mesh(cylinderGeometry, cylinderMaterial);cylinderMesh.position.set(x, y, z);cylinderMesh.castShadow = true;const cylinderBody = physicsEngine.createRigidBody({mass: mass,shape: { type: 'cylinder', halfExtents: { x: 0.5, y: 0.5, z: 0.5 } },position: { x, y, z },threeObject: cylinderMesh});return { mesh: cylinderMesh, body: cylinderBody };};// 更新物理统计const updatePhysicsStats = () => {rigidBodyCount.value = physicsEngine.bodies.size;// 其他统计信息更新...};// 动画循环const startAnimationLoop = () => {const animate = (currentTime) => {animationFrameId = requestAnimationFrame(animate);// 更新控制controls.update();// 物理模拟if (isRunning.value) {const deltaTime = Math.min(0.033, timeScale.value / 60);const physicsTime = physicsEngine.stepSimulation(deltaTime, substeps.value);simulationTime.value = physicsTime.toFixed(2);physicsEngine.syncGraphics();// 更新软体if (enableSoftBodies.value) {softBodySystem.updateSoftBodies();}// 更新流体if (enableFluidSimulation.value) {fluidSystem.update(deltaTime);}}// 更新性能统计updatePerformanceStats();// 渲染renderer.render(scene, camera);};animate();};// 更新性能统计const updatePerformanceStats = () => {physicsFrameCount++;const now = performance.now();if (now - lastPhysicsFpsUpdate >= 1000) {physicsFPS.value = Math.round((physicsFrameCount * 1000) / (now - lastPhysicsFpsUpdate));physicsFrameCount = 0;lastPhysicsFpsUpdate = now;}// 更新内存使用(简化)memoryUsage.value = (performance.memory?.usedJSHeapSize / 1048576 || 0).toFixed(1);};// 其他场景创建函数...const createJengaScene = () => { /* 实现叠叠乐场景 */ };const createRagdollScene = () => { /* 实现布娃娃系统 */ };const createVehicleScene = () => { /* 实现车辆测试场景 */ };const createDestructionScene = () => { /* 实现破坏模拟场景 */ };onMounted(() => {init();window.addEventListener('resize', handleResize);});onUnmounted(() => {if (animationFrameId) {cancelAnimationFrame(animationFrameId);}if (physicsEngine) {physicsEngine.dispose();}if (renderer) {renderer.dispose();}window.removeEventListener('resize', handleResize);});const handleResize = () => {if (!camera || !renderer) return;camera.aspect = window.innerWidth / window.innerHeight;camera.updateProjectionMatrix();renderer.setSize(window.innerWidth, window.innerHeight);};return {// 模板引用renderCanvas,debugCanvas,// 状态数据isRunning,currentScene,gravityStrength,timeScale,substeps,solverIterations,interactionMode,spawnObjectType,spawnMass,enableSoftBodies,enableFluidSimulation,enableVehiclePhysics,showPhysicsDebug,rigidBodyCount,collisionPairs,physicsFPS,simulationTime,memoryUsage,cpuTime,collisionTime,constraintTime,peakMemory,// 配置数据availableScenes,// 方法loadScene,toggleSimulation,resetSimulation,stepSimulation,setInteractionMode,spawnRandomObjects};}
};
</script><style scoped>
.physics-simulation-container {width: 100%;height: 100vh;display: flex;background: #000;overflow: hidden;
}.render-view {flex: 1;position: relative;
}.render-canvas {width: 100%;height: 100%;display: block;
}.physics-debug {position: absolute;top: 20px;left: 20px;display: flex;flex-direction: column;gap: 15px;
}.debug-panel, .control-panel {background: rgba(0, 0, 0, 0.8);padding: 15px;border-radius: 8px;color: white;backdrop-filter: blur(10px);border: 1px solid rgba(255, 255, 255, 0.1);min-width: 250px;
}.debug-panel h4, .control-panel h4 {margin: 0 0 15px 0;color: #00ffff;font-size: 14px;
}.stats-grid {display: flex;flex-direction: column;gap: 8px;
}.stat-item {display: flex;justify-content: space-between;align-items: center;font-size: 13px;
}.stat-item span:first-child {color: #ccc;
}.stat-item span:last-child {color: #00ff88;font-weight: bold;
}.control-buttons {display: flex;flex-direction: column;gap: 8px;
}.control-button {padding: 10px;background: #444;color: white;border: none;border-radius: 6px;cursor: pointer;font-size: 12px;transition: all 0.3s ease;display: flex;align-items: center;justify-content: center;gap: 5px;
}.control-button:hover:not(:disabled) {background: #555;
}.control-button:disabled {opacity: 0.6;cursor: not-allowed;
}.config-panel {width: 350px;background: #2d2d2d;padding: 20px;overflow-y: auto;border-left: 1px solid #444;
}.panel-section {margin-bottom: 25px;padding-bottom: 20px;border-bottom: 1px solid #444;
}.panel-section:last-child {margin-bottom: 0;border-bottom: none;
}.panel-section h3 {color: #00ffff;margin-bottom: 15px;font-size: 16px;
}.scene-selection {display: grid;grid-template-columns: 1fr 1fr;gap: 8px;
}.scene-button {padding: 10px 8px;background: #444;color: white;border: none;border-radius: 6px;cursor: pointer;font-size: 12px;transition: all 0.3s ease;
}.scene-button:hover {background: #555;
}.scene-button.active {background: #00a8ff;
}.physics-params, .interaction-tools, .advanced-features, .performance-monitor {display: flex;flex-direction: column;gap: 15px;
}.param-group, .option-group, .feature-toggle, .monitor-item {display: flex;flex-direction: column;gap: 8px;
}.param-group label, .option-group label {color: #ccc;font-size: 14px;
}.param-group input[type="range"], .option-group input[type="range"] {width: 100%;
}.param-group span, .option-group span {color: #00ff88;font-size: 12px;text-align: right;
}.option-group select {padding: 8px 12px;background: #444;border: 1px solid #666;border-radius: 4px;color: white;font-size: 14px;
}.tool-buttons {display: grid;grid-template-columns: 1fr;gap: 8px;
}.tool-button {padding: 10px;background: #444;color: white;border: none;border-radius: 6px;cursor: pointer;font-size: 12px;transition: all 0.3s ease;
}.tool-button:hover {background: #555;
}.tool-button.active {background: #ffa500;
}.tool-options {padding: 15px;background: rgba(255, 255, 255, 0.05);border-radius: 6px;display: flex;flex-direction: column;gap: 12px;
}.action-button {padding: 10px;background: linear-gradient(45deg, #667eea, #764ba2);color: white;border: none;border-radius: 6px;cursor: pointer;font-size: 12px;transition: all 0.3s ease;
}.action-button:hover {transform: translateY(-2px);box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3);
}.feature-toggle label {display: flex;align-items: center;gap: 8px;color: #ccc;font-size: 14px;cursor: pointer;
}.feature-toggle input[type="checkbox"] {margin: 0;
}.monitor-item {display: flex;justify-content: space-between;align-items: center;padding: 6px 0;border-bottom: 1px solid #444;
}.monitor-item:last-child {border-bottom: none;
}.monitor-item span:first-child {color: #ccc;
}.monitor-item span:last-child {color: #00ff88;font-weight: bold;
}.physics-debug-view {position: absolute;bottom: 20px;right: 370px;width: 300px;height: 200px;background: rgba(0, 0, 0, 0.8);border-radius: 8px;border: 1px solid #444;overflow: hidden;
}.debug-canvas {width: 100%;height: 100%;display: block;
}/* 响应式设计 */
@media (max-width: 1024px) {.physics-simulation-container {flex-direction: column;}.config-panel {width: 100%;height: 400px;}.render-view {height: calc(100vh - 400px);}.physics-debug-view {right: 20px;}
}@media (max-width: 768px) {.scene-selection {grid-template-columns: 1fr;}.physics-debug {position: relative;top: auto;left: auto;margin: 10px;}.physics-debug-view {display: none;}
}
</style>
高级物理特性
约束系统实现
// 高级约束系统
class ConstraintSystem {constructor(physicsEngine) {this.physicsEngine = physicsEngine;this.constraints = new Map();}// 创建点对点约束createPointToPointConstraint(bodyA, bodyB, pivotA, pivotB) {const ammoPivotA = new Ammo.btVector3(pivotA.x, pivotA.y, pivotA.z);const ammoPivotB = new Ammo.btVector3(pivotB.x, pivotB.y, pivotB.z);const constraint = new Ammo.btPoint2PointConstraint(bodyA, bodyB, ammoPivotA, ammoPivotB);this.physicsEngine.world.addConstraint(constraint, true);const constraintId = this.generateConstraintId();this.constraints.set(constraintId, constraint);return constraintId;}// 创建铰链约束createHingeConstraint(bodyA, bodyB, pivot, axis) {const ammoPivot = new Ammo.btVector3(pivot.x, pivot.y, pivot.z);const ammoAxis = new Ammo.btVector3(axis.x, axis.y, axis.z);const constraint = new Ammo.btHingeConstraint(bodyA, bodyB, ammoPivot, ammoAxis, true);// 设置限制constraint.setLimit(-Math.PI / 4, Math.PI / 4, 0.1, 0.5, 1.0);this.physicsEngine.world.addConstraint(constraint, true);const constraintId = this.generateConstraintId();this.constraints.set(constraintId, constraint);return constraintId;}// 创建弹簧约束createSpringConstraint(bodyA, bodyB, anchorA, anchorB, springConfig) {const ammoAnchorA = new Ammo.btVector3(anchorA.x, anchorA.y, anchorA.z);const ammoAnchorB = new Ammo.btVector3(anchorB.x, anchorB.y, anchorB.z);const constraint = new Ammo.btGeneric6DofSpringConstraint(bodyA, bodyB, new Ammo.btTransform(), new Ammo.btTransform(),true);// 设置弹簧参数for (let i = 0; i < 6; i++) {constraint.enableSpring(i, true);constraint.setStiffness(i, springConfig.stiffness);constraint.setDamping(i, springConfig.damping);constraint.setEquilibriumPoint(i, 0);}this.physicsEngine.world.addConstraint(constraint, true);const constraintId = this.generateConstraintId();this.constraints.set(constraintId, constraint);return constraintId;}generateConstraintId() {return `constraint_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;}
}
车辆物理系统
// 车辆物理模拟
class VehiclePhysicsSystem {constructor(physicsEngine) {this.physicsEngine = physicsEngine;this.vehicles = new Map();this.vehicleRayCaster = new Ammo.btVehicleRaycaster();}// 创建车辆createVehicle(chassisConfig, wheelConfigs) {const chassisShape = this.createChassisShape(chassisConfig);const chassisBody = this.createChassisBody(chassisShape, chassisConfig);const tuning = new Ammo.btVehicleTuning();const vehicle = new Ammo.btRaycastVehicle(tuning, chassisBody, this.vehicleRayCaster);// 添加车轮wheelConfigs.forEach((wheelConfig, index) => {this.addWheel(vehicle, wheelConfig, index);});vehicle.setCoordinateSystem(0, 1, 2);this.physicsEngine.world.addVehicle(vehicle);const vehicleId = this.generateVehicleId();this.vehicles.set(vehicleId, {vehicle: vehicle,chassisBody: chassisBody,chassisShape: chassisShape,tuning: tuning});return vehicleId;}// 添加车轮addWheel(vehicle, config, index) {const wheelInfo = new Ammo.btWheelInfo();// 配置车轮参数wheelInfo.m_suspensionStiffness = config.suspensionStiffness || 20.0;wheelInfo.m_wheelsDampingRelaxation = config.dampingRelaxation || 2.3;wheelInfo.m_wheelsDampingCompression = config.dampingCompression || 4.4;wheelInfo.m_frictionSlip = config.frictionSlip || 1000;wheelInfo.m_rollInfluence = config.rollInfluence || 0.1;const connectionPoint = new Ammo.btVector3(config.connectionPoint.x,config.connectionPoint.y, config.connectionPoint.z);const wheelDirection = new Ammo.btVector3(config.wheelDirection.x,config.wheelDirection.y,config.wheelDirection.z);const wheelAxle = new Ammo.btVector3(config.wheelAxle.x,config.wheelAxle.y,config.wheelAxle.z);vehicle.addWheel(connectionPoint,wheelDirection,wheelAxle,config.suspensionRestLength,config.wheelRadius,tuning,config.isFrontWheel);}// 控制车辆controlVehicle(vehicleId, engineForce, steering, braking) {const vehicleData = this.vehicles.get(vehicleId);if (!vehicleData) return;const vehicle = vehicleData.vehicle;// 应用引擎力vehicle.applyEngineForce(engineForce, 2); // 后轮驱动vehicle.applyEngineForce(engineForce, 3);// 转向vehicle.setSteeringValue(steering, 0); // 前轮转向vehicle.setSteeringValue(steering, 1);// 刹车vehicle.setBrake(braking, 2);vehicle.setBrake(braking, 3);}
}
本节详细介绍了Bullet物理引擎与Three.js的高级集成,涵盖了刚体动力学、约束系统、软体物理和流体模拟等高级特性。通过这些技术,可以创建出高度真实和交互性强的物理模拟应用。
