SOURCE

console 命令行工具 X clear

                    
>
console
class VehiclePhysics {
  constructor(config) {
    this.config = {
      mass: 1500,         // 质量 (kg)
      power: 100000,      // 功率 (W)
      wheelBase: 2.5,     // 轴距 (m)
      maxSteer: Math.PI/4,// 最大转向角
      dragCoeff: 0.3,     // 空气阻力系数
      ...config
    };
    
    this.state = {
      x: 0, y: 0,         // 位置 (m)
      v: 0,               // 速度 (m/s)
      theta: 0,           // 航向角 (rad)
      delta: 0,           // 前轮转角 (rad)
      throttle: 0         // 油门 0-1
    };
  }

  // 物理模型更新
  update(dt, controls) {
    const { mass, power, wheelBase, dragCoeff } = this.config;
    const { throttle, delta } = controls;

    // 计算驱动力
    const tractionForce = Math.min(throttle * power / Math.max(this.state.v, 1), 5000);
    
    // 计算阻力
    const dragForce = 0.5*dragCoeff*1.225*2.5*this.state.v**2;
    
    // 加速度
    const acceleration = (tractionForce - dragForce) / mass;
    
    // 更新速度
    this.state.v += acceleration * dt;
    
    // 更新位置和航向
    const beta = Math.atan(Math.tan(delta) / 2);
    this.state.x += this.state.v * Math.cos(this.state.theta + beta) * dt;
    this.state.y += this.state.v * Math.sin(this.state.theta + beta) * dt;
    this.state.theta += this.state.v * Math.tan(delta) * Math.cos(beta) / wheelBase * dt;
    
    // 限制转向角度
    this.state.delta = Math.max(-this.config.maxSteer, 
      Math.min(this.config.maxSteer, delta));
  }
}

class ControlSystem {
  constructor() {
    this.controls = {
      throttle: 0,
      delta: 0,
      autoPilot: true
    };
    
    this.keyState = {};
    this.setupControls();
  }

  setupControls() {
    // 键盘事件监听
    document.addEventListener('keydown', (e) => this.handleKey(e, true));
    document.addEventListener('keyup', (e) => this.handleKey(e, false));
  }

  handleKey(e, isDown) {
    this.keyState[e.key] = isDown;
    
    // 手动控制时关闭自动模式
    if(isDown) this.controls.autoPilot = false;
  }

  update(targetPoint) {
    if(this.controls.autoPilot) {
      this.autoControl(targetPoint);
    } else {
      this.manualControl();
    }
    return this.controls;
  }

  manualControl() {
    // 上下控制油门 (W/S 或 上下箭头)
    if(this.keyState['ArrowUp'] || this.keyState['w']) {
      this.controls.throttle = Math.min(this.controls.throttle + 0.1, 1);
    } else if(this.keyState['ArrowDown'] || this.keyState['s']) {
      this.controls.throttle = Math.max(this.controls.throttle - 0.1, 0);
    }

    // 左右控制转向 (A/D 或 左右箭头)
    if(this.keyState['ArrowLeft'] || this.keyState['a']) {
      this.controls.delta = Math.max(this.controls.delta - 0.1, -Math.PI/4);
    } else if(this.keyState['ArrowRight'] || this.keyState['d']) {
      this.controls.delta = Math.min(this.controls.delta + 0.1, Math.PI/4);
    }
  }

  autoControl(target) {
    // 自动漂移控制逻辑
    const dx = target.x - this.controls.currentX;
    const dy = target.y - this.controls.currentY;
    const targetAngle = Math.atan2(dy, dx);
    
    // 转向控制
    const angleDiff = targetAngle - this.controls.currentTheta;
    this.controls.delta = Math.sin(angleDiff) * 0.5;
    
    // 油门控制
    this.controls.throttle = 0.3 + Math.random() * 0.2;
  }
}

class DriftCarSim {
  constructor(containerId) {
    this.container = document.getElementById(containerId);
    this.initDOM();
    this.initSimulation();
    this.setupEventListeners();
    this.startLoop();
  }

  initDOM() {
    this.container.innerHTML = `
      <div class="container">
        <canvas></canvas>
        <div class="controls"></div>
      </div>
    `;
    this.canvas = this.container.querySelector('canvas');
    this.ctx = this.canvas.getContext('2d');
    this.resizeCanvas();
    this.initControls();
  }

  initSimulation() {
    this.vehicle = new VehiclePhysics({
      wheelBase: 2.5,
      maxSteer: Math.PI/4
    });
    
    this.controlSystem = new ControlSystem();
    this.target = { x: 100, y: 100 };
    
    this.state = {
      history: [],
      targets: [],
      showParams: true,
      showTrail: true,
      showTargetLine: true
    };
  }

  initControls() {
    const controls = this.container.querySelector('.controls');
    controls.innerHTML = `
      <div style="position:absolute; right:10px; top:10px; background:rgba(255,255,255,0.8); padding:8px; border-radius:4px;">
        <label><input type="checkbox" checked> 显示轨迹</label><br>
        <label><input type="checkbox" checked> 显示目标线</label><br>
        <label><input type="checkbox"> 自动模式</label><br>
        <label><input type="checkbox" checked> 显示参数</label>
      </div>
    `;
    
    controls.querySelectorAll('input').forEach((input, i) => {
      input.onchange = () => this.handleControlChange(i, input.checked);
    });
  }

  handleControlChange(index, checked) {
    switch(index) {
      case 0: this.state.showTrail = checked; break;
      case 1: this.state.showTargetLine = checked; break;
      case 2: this.controlSystem.controls.autoPilot = checked; break;
      case 3: this.state.showParams = checked; break;
    }
  }

  resizeCanvas() {
    this.canvas.width = this.container.offsetWidth;
    this.canvas.height = this.container.offsetHeight;
  }

  setupEventListeners() {
    window.addEventListener('resize', () => this.resizeCanvas());
    this.canvas.addEventListener('mousemove', e => this.handleMouse(e));
  }

  handleMouse(e) {
    if(!this.controlSystem.controls.autoPilot) return;
    
    const rect = this.canvas.getBoundingClientRect();
    this.target = {
      x: (e.clientX - rect.left) / 10,
      y: (e.clientY - rect.top) / 10
    };
  }

  startLoop() {
    const frame = () => {
      this.update();
      this.draw();
      requestAnimationFrame(frame);
    };
    frame();
  }

  update() {
    const controls = this.controlSystem.update(this.target);
    this.vehicle.update(0.016, controls);
    
    // 记录轨迹
    this.state.history.push({...this.vehicle.state});
    if(this.state.history.length > 100) this.state.history.shift();
  }

  draw() {
    this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
    
    // 坐标系变换
    this.ctx.setTransform(1,0,0,1,0,0);
    this.ctx.translate(
      this.canvas.width/2 - this.vehicle.state.x * 10,
      this.canvas.height/2 - this.vehicle.state.y * 10
    );

    // 绘制目标线
    if(this.state.showTargetLine) {
      this.drawPath(this.state.targets, '#FF9800');
    }

    // 绘制轨迹
    if(this.state.showTrail) {
      this.drawPath(this.state.history, '#E91E63');
    }

    // 绘制车辆
    this.drawCar();

    // 显示参数
    if(this.state.showParams) {
      this.drawParams();
    }
  }

  drawPath(points, color) {
    if(points.length < 2) return;
    
    this.ctx.beginPath();
    this.ctx.moveTo(points[0].x * 10, points[0].y * 10);
    points.forEach(p => this.ctx.lineTo(p.x * 10, p.y * 10));
    this.ctx.strokeStyle = color;
    this.ctx.lineWidth = 2;
    this.ctx.stroke();
  }

  drawCar() {
    this.ctx.save();
    this.ctx.translate(this.vehicle.state.x * 10, this.vehicle.state.y * 10);
    this.ctx.rotate(this.vehicle.state.theta);
    
    // 车体
    this.ctx.fillStyle = '#2196F3';
    this.ctx.fillRect(-15, -10, 30, 20);
    
    // 前轮
    this.ctx.fillStyle = '#666';
    this.ctx.fillRect(10, -8, 5, 16);
    this.ctx.fillRect(10, -8 + this.vehicle.state.delta * 30, 5, 16);
    
    this.ctx.restore();
  }

  drawParams() {
    this.ctx.setTransform(1,0,0,1,0,0);
    this.ctx.fillStyle = 'black';
    this.ctx.font = '14px Arial';
    
    const params = [
      `速度: ${(this.vehicle.state.v * 3.6).toFixed(1)} km/h`,
      `油门: ${(this.controlSystem.controls.throttle * 100).toFixed(0)}%`,
      `转向: ${(this.vehicle.state.delta * 180/Math.PI).toFixed(1)}°`,
      `模式: ${this.controlSystem.controls.autoPilot ? '自动' : '手动'}`
    ];
    
    params.forEach((text, i) => {
      this.ctx.fillText(text, 10, 30 + i * 20);
    });
  }
}

// 初始化
new DriftCarSim('simContainer');
<div id="simContainer" style="width:500px;height:500px"></div>
.container {
    position: relative;
    width: 100vw;
    height: 100vh;
    overflow: hidden;
}

#canvas {
    background: #f0f0f0;
}

.controls {
    position: absolute;
    top: 10px;
    left: 10px;
    background: rgba(255, 255, 255, 0.8);
    padding: 10px;
    border-radius: 5px;
}