第44节:物理引擎进阶:Bullet.js集成与高级物理模拟

第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的高级集成,涵盖了刚体动力学、约束系统、软体物理和流体模拟等高级特性。通过这些技术,可以创建出高度真实和交互性强的物理模拟应用。

相关推荐
智算菩萨1 天前
【Python自然语言处理】实战项目:词向量表示完整实现指南
开发语言·python·自然语言处理
Blossom.1181 天前
联邦迁移学习实战:在数据孤岛中构建个性化推荐模型
开发语言·人工智能·python·深度学习·神经网络·机器学习·迁移学习
yaoxin5211231 天前
288. Java Stream API - 创建随机数的 Stream
java·开发语言
superman超哥1 天前
迭代器适配器(map、filter、fold等):Rust函数式编程的艺术
开发语言·rust·编程语言·rust map·rust filter·rust fold·rust函数式
yuanmenghao1 天前
自动驾驶中间件iceoryx - 同步与通知机制(二)
开发语言·单片机·中间件·自动驾驶·信息与通信
郝学胜-神的一滴1 天前
Qt实现圆角窗口的两种方案详解
开发语言·c++·qt·程序人生
superman超哥1 天前
Iterator Trait 的核心方法:深入理解与实践
开发语言·后端·rust·iterator trait·trait核心方法
冰暮流星1 天前
javascript短路运算
开发语言·前端·javascript
kylezhao20191 天前
在C#中实现异步通信
开发语言·c#
05大叔1 天前
大事件Day01
java·开发语言