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

相关推荐
中文Python3 小时前
小白中文Python-双色球LSTM模型出号程序
开发语言·人工智能·python·lstm·中文python·小白学python
越努力越幸运5083 小时前
JavaScript进阶篇垃圾回收、闭包、函数提升、剩余参数、展开运算符、对象解构
开发语言·javascript
czhc11400756633 小时前
C# 1116 流程控制 常量
开发语言·c#
程序员ys3 小时前
Vue的响应式系统是怎么实现的
前端·javascript·vue.js
aduzhe3 小时前
关于在嵌入式中打印float类型遇到的bug
前端·javascript·bug
程序定小飞3 小时前
基于springboot的汽车资讯网站开发与实现
java·开发语言·spring boot·后端·spring
鹏多多4 小时前
vue过滤器filter的详解及和computed的区别
前端·javascript·vue.js
孟陬4 小时前
在浏览器控制台中优雅地安装 npm 包 console.install('lodash')
javascript·node.js
Moment4 小时前
LangChain 1.0 发布:agent 框架正式迈入生产级
前端·javascript·后端