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

物理系统架构:
Three.js场景 物理引擎接口 刚体动力学 碰撞检测 约束系统 软体物理 流体模拟 运动学刚体 动力学刚体 静态刚体 离散检测 连续检测 触发器 关节约束 弹簧系统 车辆物理 布料模拟 绳索模拟 可变形体 粒子流体 网格流体 交互控制
核心原理
物理引擎对比
| 特性 | Bullet.js | Cannon.js | Ammo.js |
|---|---|---|---|
| 性能 | 非常高 | 中等 | 高 |
| 特性完整度 | 完整 | 基础 | 完整 |
| 内存使用 | 中等 | 低 | 高 |
| 学习曲线 | 陡峭 | 平缓 | 中等 |
| 社区支持 | 强大 | 活跃 | 一般 |
刚体类型
javascript
// 刚体配置示例
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
};
}
}
完整代码实现
高级物理模拟系统
vue
<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.js
if (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>
高级物理特性
约束系统实现
javascript
// 高级约束系统
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)}`;
}
}
车辆物理系统
javascript
// 车辆物理模拟
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的高级集成,涵盖了刚体动力学、约束系统、软体物理和流体模拟等高级特性。通过这些技术,可以创建出高度真实和交互性强的物理模拟应用。