分享图
动画工坊
引擎就绪
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>多腔硅胶软体蛇形机器人连续行波推进原理</title>
  <style>
    :root {
      --bg-space: #070a10;
      --panel-bg: rgba(13, 20, 32, 0.7);
      --panel-border: rgba(56, 189, 248, 0.18);
      --accent-cyan: #00f3ff;
      --accent-blue: #38bdf8;
      --accent-amber: #f59e0b;
      --text-main: #e2e8f0;
      --text-dim: #64748b;
      --font-code: "JetBrains Mono", "SF Mono", "Fira Code", monospace;
    }

    * {
      box-sizing: border-box;
      margin: 0;
      padding: 0;
      user-select: none;
    }

    body {
      background-color: var(--bg-space);
      color: var(--text-main);
      font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", sans-serif;
      min-height: 100vh;
      display: flex;
      flex-direction: column;
      overflow: hidden;
      position: relative;
    }

    /* 背景星轨微网格 */
    .grid-bg {
      position: absolute;
      inset: 0;
      background-image: 
        radial-gradient(circle at 50% 50%, rgba(56, 189, 248, 0.05) 0%, transparent 70%),
        linear-gradient(rgba(255, 255, 255, 0.018) 1px, transparent 1px),
        linear-gradient(90deg, rgba(255, 255, 255, 0.018) 1px, transparent 1px);
      background-size: 100% 100%, 32px 32px, 32px 32px;
      pointer-events: none;
      z-index: 0;
    }

    /* 顶部极简信息微栏(不遮挡主视区) */
    header {
      position: absolute;
      top: 14px;
      left: 20px;
      right: 20px;
      display: flex;
      justify-content: space-between;
      align-items: center;
      z-index: 10;
      pointer-events: none;
    }

    .brand {
      display: flex;
      align-items: center;
      gap: 10px;
    }

    .brand-tag {
      font-size: 10px;
      font-weight: 700;
      font-family: var(--font-code);
      background: rgba(0, 243, 255, 0.12);
      border: 1px solid rgba(0, 243, 255, 0.35);
      color: var(--accent-cyan);
      padding: 2px 7px;
      border-radius: 4px;
      letter-spacing: 0.8px;
    }

    .title-group h1 {
      font-size: 13px;
      font-weight: 500;
      letter-spacing: 0.5px;
      color: #f1f5f9;
    }

    .title-group p {
      font-size: 11px;
      color: var(--text-dim);
      font-family: var(--font-code);
    }

    .telemetry {
      display: flex;
      gap: 16px;
      font-size: 11px;
      font-family: var(--font-code);
      background: var(--panel-bg);
      border: 1px solid var(--panel-border);
      backdrop-filter: blur(8px);
      padding: 6px 12px;
      border-radius: 6px;
      pointer-events: auto;
    }

    .telemetry-item {
      display: flex;
      gap: 6px;
      align-items: center;
    }

    .telemetry-item span.label {
      color: var(--text-dim);
    }

    .telemetry-item span.value {
      color: var(--accent-cyan);
      font-weight: 600;
      min-width: 44px;
    }

    /* 动画核心舞台:完全居中,占比 >85% */
    main {
      flex: 1;
      display: flex;
      align-items: center;
      justify-content: center;
      position: relative;
      z-index: 1;
      width: 100vw;
      height: 100vh;
    }

    #stage {
      width: 100%;
      height: 100%;
      max-width: 1440px;
      max-height: 850px;
      display: block;
    }

    /* 底部角落微型控制舱(小巧、低对比度、无遮挡) */
    .bottom-hud {
      position: absolute;
      bottom: 14px;
      left: 20px;
      right: 20px;
      display: flex;
      justify-content: space-between;
      align-items: flex-end;
      z-index: 10;
      pointer-events: none;
    }

    .legend-card {
      background: var(--panel-bg);
      border: 1px solid var(--panel-border);
      backdrop-filter: blur(8px);
      border-radius: 6px;
      padding: 8px 12px;
      display: flex;
      flex-direction: column;
      gap: 6px;
      pointer-events: auto;
      max-width: 280px;
    }

    .legend-title {
      font-size: 10px;
      color: var(--text-dim);
      letter-spacing: 0.5px;
      text-transform: uppercase;
      font-family: var(--font-code);
      display: flex;
      justify-content: space-between;
    }

    .legend-items {
      display: flex;
      flex-direction: column;
      gap: 4px;
    }

    .legend-row {
      display: flex;
      align-items: center;
      gap: 8px;
      font-size: 11px;
      color: #cbd5e1;
    }

    .dot {
      width: 8px;
      height: 8px;
      border-radius: 50%;
      display: inline-block;
      flex-shrink: 0;
    }

    .dot-inflate {
      background: var(--accent-cyan);
      box-shadow: 0 0 8px var(--accent-cyan);
    }

    .dot-deflate {
      background: #1e293b;
      border: 1px solid #475569;
    }

    .dot-contact {
      background: #f59e0b;
      box-shadow: 0 0 8px #f59e0b;
    }

    /* 控制面板 */
    .ctrl-deck {
      display: flex;
      align-items: center;
      gap: 12px;
      background: var(--panel-bg);
      border: 1px solid var(--panel-border);
      backdrop-filter: blur(8px);
      padding: 7px 14px;
      border-radius: 8px;
      pointer-events: auto;
    }

    .ctrl-group {
      display: flex;
      align-items: center;
      gap: 8px;
    }

    .ctrl-group label {
      font-size: 11px;
      font-family: var(--font-code);
      color: var(--text-dim);
    }

    input[type="range"] {
      -webkit-appearance: none;
      width: 80px;
      height: 4px;
      background: #1e293b;
      border-radius: 2px;
      outline: none;
      cursor: pointer;
    }

    input[type="range"]::-webkit-slider-thumb {
      -webkit-appearance: none;
      width: 12px;
      height: 12px;
      border-radius: 50%;
      background: var(--accent-cyan);
      box-shadow: 0 0 6px var(--accent-cyan);
      cursor: pointer;
    }

    .btn-group {
      display: flex;
      gap: 4px;
      border-left: 1px solid rgba(255, 255, 255, 0.1);
      padding-left: 8px;
    }

    .mode-btn {
      background: rgba(255, 255, 255, 0.04);
      border: 1px solid rgba(255, 255, 255, 0.12);
      color: #94a3b8;
      font-size: 11px;
      font-family: var(--font-code);
      padding: 4px 8px;
      border-radius: 4px;
      cursor: pointer;
      transition: all 0.2s ease;
    }

    .mode-btn:hover {
      color: #fff;
      border-color: rgba(0, 243, 255, 0.4);
    }

    .mode-btn.active {
      background: rgba(0, 243, 255, 0.15);
      border-color: var(--accent-cyan);
      color: var(--accent-cyan);
      font-weight: 600;
    }

    /* 右上角截面微图示 */
    .cross-section-hud {
      position: absolute;
      top: 56px;
      right: 20px;
      background: var(--panel-bg);
      border: 1px solid var(--panel-border);
      backdrop-filter: blur(8px);
      border-radius: 6px;
      padding: 8px 10px;
      display: flex;
      flex-direction: column;
      align-items: center;
      gap: 4px;
      pointer-events: none;
      z-index: 10;
    }

    .cs-title {
      font-size: 9px;
      font-family: var(--font-code);
      color: var(--text-dim);
      letter-spacing: 0.4px;
    }

    #crossSectionSvg {
      width: 64px;
      height: 64px;
    }
  </style>
</head>
<body>
  <div class="grid-bg"></div>

  <!-- 顶部轻量级技术标尺 -->
  <header>
    <div class="brand">
      <span class="brand-tag">IFR-SOFTPNEU</span>
      <div class="title-group">
        <h1>无铰链多腔硅胶波纹管 · 连续行波自适应推进机构</h1>
        <p>θᵢ(t) = A · sin(2πft − 2πki/N) · 8段周向120°差动充放气</p>
      </div>
    </div>

    <div class="telemetry">
      <div class="telemetry-item">
        <span class="label">行波频率</span>
        <span class="value" id="valFreq">2.00 Hz</span>
      </div>
      <div class="telemetry-item">
        <span class="label">供气压强</span>
        <span class="value" id="valPress">68.0 kPa</span>
      </div>
      <div class="telemetry-item">
        <span class="label">自适应贴合度</span>
        <span class="value" style="color: #34d399;" id="valFit">96.4%</span>
      </div>
    </div>
  </header>

  <!-- 右上方 120° 三腔室气压周向动态截面微图 -->
  <div class="cross-section-hud">
    <div class="cs-title">120°周向微气腔截面 (第4段)</div>
    <svg id="crossSectionSvg" viewBox="-36 -36 72 72">
      <!-- 外环波纹硅胶壁 -->
      <circle cx="0" cy="0" r="30" fill="none" stroke="rgba(56,189,248,0.2)" stroke-width="3" stroke-dasharray="2,2"/>
      <circle cx="0" cy="0" r="26" fill="rgba(15,23,42,0.6)" stroke="rgba(56,189,248,0.4)" stroke-width="1.5"/>
      <!-- 三腔室 (0°, 120°, 240°) -->
      <path id="csChamberTop" d="M -7,-6 A 14 14 0 0 1 7,-6 L 15,-18 A 24 24 0 0 0 -15,-18 Z" fill="rgba(0,243,255,0.2)" stroke="#00f3ff" stroke-width="1"/>
      <path id="csChamberRight" d="M 6,3 A 14 14 0 0 1 2,10 L 17,19 A 24 24 0 0 0 23,-5 Z" fill="rgba(0,243,255,0.2)" stroke="#00f3ff" stroke-width="1"/>
      <path id="csChamberLeft" d="M -2,10 A 14 14 0 0 1 -6,3 L -23,-5 A 24 24 0 0 0 -17,19 Z" fill="rgba(0,243,255,0.2)" stroke="#00f3ff" stroke-width="1"/>
      <!-- 中心气管束 -->
      <circle cx="0" cy="0" r="4" fill="#0f172a" stroke="rgba(255,255,255,0.3)" stroke-width="1"/>
    </svg>
  </div>

  <!-- 主交互视区:高精细柔性波纹管与贴合蠕动动画 -->
  <main>
    <svg id="stage" viewBox="0 0 1200 680" preserveAspectRatio="xMidYMid meet">
      <defs>
        <!-- 气压高亮辉光滤镜 -->
        <filter id="glow" x="-30%" y="-30%" width="160%" height="160%">
          <feGaussianBlur stdDeviation="4" result="blur" />
          <feComposite in="SourceGraphic" in2="blur" operator="over" />
        </filter>
        <filter id="glow-soft" x="-20%" y="-20%" width="140%" height="140%">
          <feGaussianBlur stdDeviation="7" result="blur" />
          <feComposite in="SourceGraphic" in2="blur" operator="over" />
        </filter>

        <!-- 狭窄不规则缝隙管壁纹理 -->
        <linearGradient id="wallGradTop" x1="0" y1="0" x2="0" y2="1">
          <stop offset="0%" stop-color="#0f172a" stop-opacity="0.95"/>
          <stop offset="85%" stop-color="#1e293b" stop-opacity="0.7"/>
          <stop offset="100%" stop-color="#334155" stop-opacity="0.9"/>
        </linearGradient>

        <linearGradient id="wallGradBottom" x1="0" y1="1" x2="0" y2="0">
          <stop offset="0%" stop-color="#0f172a" stop-opacity="0.95"/>
          <stop offset="85%" stop-color="#1e293b" stop-opacity="0.7"/>
          <stop offset="100%" stop-color="#334155" stop-opacity="0.9"/>
        </linearGradient>

        <!-- 硅胶半透材质渐变 -->
        <linearGradient id="siliconeGrad" x1="0" y1="0" x2="0" y2="1">
          <stop offset="0%" stop-color="rgba(56, 189, 248, 0.45)"/>
          <stop offset="35%" stop-color="rgba(14, 165, 233, 0.15)"/>
          <stop offset="70%" stop-color="rgba(2, 132, 199, 0.25)"/>
          <stop offset="100%" stop-color="rgba(56, 189, 248, 0.5)"/>
        </linearGradient>

        <!-- 充气高压气腔色彩渐变 -->
        <linearGradient id="activeChamberGrad" x1="0" y1="0" x2="1" y2="0">
          <stop offset="0%" stop-color="#00f3ff" stop-opacity="0.9"/>
          <stop offset="50%" stop-color="#38bdf8" stop-opacity="0.95"/>
          <stop offset="100%" stop-color="#0284c7" stop-opacity="0.8"/>
        </linearGradient>
      </defs>

      <!-- 1. 背景标尺与狭窄不规则缝隙通道 (柔性瓦砾/狭道轮廓) -->
      <g id="channelGroup">
        <!-- 顶部起伏软管壁 -->
        <path id="topWall" fill="url(#wallGradTop)" stroke="rgba(100,116,139,0.3)" stroke-width="1.5" />
        <path id="topWallContactRim" fill="none" stroke="rgba(56,189,248,0.25)" stroke-width="2" stroke-dasharray="3,3" />

        <!-- 底部起伏软管壁 -->
        <path id="bottomWall" fill="url(#wallGradBottom)" stroke="rgba(100,116,139,0.3)" stroke-width="1.5" />
        <path id="bottomWallContactRim" fill="none" stroke="rgba(56,189,248,0.25)" stroke-width="2" stroke-dasharray="3,3" />

        <!-- 狭缝间隙激光测量线 -->
        <g id="gapDimensionLines" opacity="0.45"></g>
      </g>

      <!-- 2. 接触贴合与大面积微压强响应光晕 (IFR核心: 零卡死、柔顺分散应力) -->
      <g id="contactStressGlowGroup"></g>

      <!-- 3. 仿生多腔蛇本体图层 (8段连续硅胶波纹管) -->
      <g id="snakeBody">
        <!-- 内部8段气腔充气膨胀实体 (按相位动态着色) -->
        <g id="chambersLayer"></g>
        <!-- 螺旋防过拉伸凯夫拉纤维限制层 -->
        <g id="fibersLayer" opacity="0.4"></g>
        <!-- 8节连续锯齿状硅胶波纹管外轮廓 -->
        <path id="bellowsSkinUpper" fill="none" stroke="rgba(56,189,248,0.85)" stroke-width="2.5" />
        <path id="bellowsSkinLower" fill="none" stroke="rgba(56,189,248,0.85)" stroke-width="2.5" />
        <path id="snakeSpine" fill="none" stroke="rgba(255,255,255,0.2)" stroke-width="1.2" stroke-dasharray="4,4" />
        <!-- 节段隔断环 (8段分界) -->
        <g id="segmentBorders"></g>
        <!-- 头部半球导向软硅胶帽与微型探头 -->
        <g id="headCap"></g>
        <!-- 尾部气源接入与电磁阀组象征 -->
        <g id="tailTether"></g>
      </g>

      <!-- 4. 气流微粒子与动态矢量流动指示 -->
      <g id="pneumaticParticlesGroup"></g>

      <!-- 5. 动态工程标注层 (极简跟随指针与尺寸标记) -->
      <g id="annotationsGroup" pointer-events="none"></g>
    </svg>
  </main>

  <!-- 底部说明与控制栏 (文字极精简,边角停靠,不遮挡中央演示) -->
  <div class="bottom-hud">
    <div class="legend-card">
      <div class="legend-title">
        <span>机理图例说明</span>
        <span>IFR 理想解</span>
      </div>
      <div class="legend-items">
        <div class="legend-row">
          <span class="dot dot-inflate"></span>
          <span>高压膨胀段 (伸长向背侧弯曲推进)</span>
        </div>
        <div class="legend-row">
          <span class="dot dot-deflate"></span>
          <span>排气卸压段 (波纹褶皱紧缩蓄能)</span>
        </div>
        <div class="legend-row">
          <span class="dot dot-contact"></span>
          <span>自适应面接触 (超低压强·无铰链防卡滞)</span>
        </div>
      </div>
    </div>

    <!-- 交互控制器 -->
    <div class="ctrl-deck">
      <div class="ctrl-group">
        <label for="speedRange">行波频率 f</label>
        <input type="range" id="speedRange" min="0.5" max="3.5" step="0.1" value="2.0">
      </div>

      <div class="ctrl-group">
        <label for="pressRange">气压 P</label>
        <input type="range" id="pressRange" min="40" max="85" step="1" value="68">
      </div>

      <div class="btn-group">
        <button class="mode-btn active" id="btnWave" title="相位推进:产生平滑前进蠕动波">行波前进</button>
        <button class="mode-btn" id="btnReverse" title="反向推进:逆向相位蠕动退出">反向退避</button>
        <button class="mode-btn" id="btnStiff" title="全腔刚化:同步充气以顶开轻质瓦砾">全腔刚化</button>
      </div>
    </div>
  </div>

  <script>
    /* ==========================================================================
       仿生软体蛇形机器人运动学与流体仿真渲染引擎
       ========================================================================== */
    const CONFIG = {
      segments: 8,              // 8段波纹管
      bodyLength: 760,          // 软管总基准展开长度 (px)
      baseRadius: 24,           // 体径约 40-48mm 视觉比例 (px)
      corrugationsPerSeg: 4,    // 每段含4个波纹折叠
      originX: 180,             // 机器人尾部起始X
      originY: 340,             // 通道中心高度基准
      sampleCount: 160          // 沿中心轴脊椎采样点数
    };

    // 系统状态变量
    const state = {
      time: 0,
      freq: 2.0,                // 阀切换/行波频率 Hz
      pressure: 68.0,           // 工作气压 kPa
      mode: 'wave',             // 'wave' | 'reverse' | 'stiff'
      waveSpeedSign: 1,         // 1: 正向, -1: 反向, 0: 静态
      amplitude: 36,            // 行波弯曲幅值 (根据气压动态算)
      particles: []
    };

    // DOM 元素引用
    const stage = document.getElementById('stage');
    const bellowsSkinUpper = document.getElementById('bellowsSkinUpper');
    const bellowsSkinLower = document.getElementById('bellowsSkinLower');
    const snakeSpine = document.getElementById('snakeSpine');
    const chambersLayer = document.getElementById('chambersLayer');
    const fibersLayer = document.getElementById('fibersLayer');
    const segmentBorders = document.getElementById('segmentBorders');
    const headCap = document.getElementById('headCap');
    const tailTether = document.getElementById('tailTether');
    const contactStressGlowGroup = document.getElementById('contactStressGlowGroup');
    const pneumaticParticlesGroup = document.getElementById('pneumaticParticlesGroup');
    const annotationsGroup = document.getElementById('annotationsGroup');

    // UI 数值显示
    const valFreq = document.getElementById('valFreq');
    const valPress = document.getElementById('valPress');
    const valFit = document.getElementById('valFit');
    const speedRange = document.getElementById('speedRange');
    const pressRange = document.getElementById('pressRange');
    const btnWave = document.getElementById('btnWave');
    const btnReverse = document.getElementById('btnReverse');
    const btnStiff = document.getElementById('btnStiff');

    // 周向三腔室截面指示
    const csChamberTop = document.getElementById('csChamberTop');
    const csChamberRight = document.getElementById('csChamberRight');
    const csChamberLeft = document.getElementById('csChamberLeft');

    /* --------------------------------------------------------------------------
       1. 生成不规则柔性通道轮廓 (模拟瓦砾堆、生物体腔道、管道狭缝)
       -------------------------------------------------------------------------- */
    function buildNarrowChannel() {
      const topWall = document.getElementById('topWall');
      const bottomWall = document.getElementById('bottomWall');
      const topRim = document.getElementById('topWallContactRim');
      const bottomRim = document.getElementById('bottomWallContactRim');
      const dimLines = document.getElementById('gapDimensionLines');

      const width = 1200;
      let topD = `M 0,0 L 1200,0 L 1200,`;
      let bottomD = `M 0,680 L 1200,680 L 1200,`;
      
      const topPoints = [];
      const bottomPoints = [];

      // 沿X轴生成弯曲起伏的不规则缝隙轮廓
      for (let x = 0; x <= width; x += 30) {
        const nx = x * 0.007;
        // 狭窄度在 65px ~ 95px 之间浮动
        const channelCenter = 340 + Math.sin(nx * 1.5) * 35 + Math.cos(nx * 3.2) * 15;
        const halfGap = 44 + Math.sin(nx * 2.8) * 10 + Math.cos(nx * 0.8) * 8;

        const yt = channelCenter - halfGap;
        const yb = channelCenter + halfGap;

        topPoints.push({ x, y: yt });
        bottomPoints.push({ x, y: yb });
      }

      // 构建多边形闭合路径
      let pathTopSub = ``;
      let pathBottomSub = ``;
      topPoints.forEach((pt, i) => {
        pathTopSub += `${i === 0 ? 'M' : 'L'} ${pt.x},${pt.y.toFixed(1)} `;
      });
      bottomPoints.forEach((pt, i) => {
        pathBottomSub += `${i === 0 ? 'M' : 'L'} ${pt.x},${pt.y.toFixed(1)} `;
      });

      topWall.setAttribute('d', `M 0,0 L ${width},0 L ${width},${topPoints[topPoints.length-1].y} ` + 
        topPoints.slice().reverse().map(p => `L ${p.x},${p.y.toFixed(1)}`).join(' ') + ` Z`);
      
      bottomWall.setAttribute('d', `M 0,680 L ${width},680 L ${width},${bottomPoints[bottomPoints.length-1].y} ` + 
        bottomPoints.slice().reverse().map(p => `L ${p.x},${p.y.toFixed(1)}`).join(' ') + ` Z`);

      topRim.setAttribute('d', pathTopSub);
      bottomRim.setAttribute('d', pathBottomSub);

      // 在特定断面添加极细微尺寸标注线
      [280, 600, 920].forEach(dimX => {
        const topY = sampleWallY(dimX, true);
        const botY = sampleWallY(dimX, false);
        const gapVal = Math.round(botY - topY);

        const g = document.createElementNS('http://www.w3.org/2000/svg', 'g');
        g.innerHTML = `
          <line x1="${dimX}" y1="${topY}" x2="${dimX}" y2="${botY}" stroke="rgba(56,189,248,0.4)" stroke-dasharray="2,2" stroke-width="1"/>
          <circle cx="${dimX}" cy="${topY}" r="2" fill="#38bdf8"/>
          <circle cx="${dimX}" cy="${botY}" r="2" fill="#38bdf8"/>
          <text x="${dimX + 6}" y="${(topY + botY)/2 + 3}" fill="#64748b" font-size="9" font-family="${CONFIG.fontCode || 'monospace'}">
            缝隙: ${gapVal}mm
          </text>
        `;
        dimLines.appendChild(g);
      });
    }

    // 通道壁采样函数
    function sampleWallY(x, isTop) {
      const nx = x * 0.007;
      const channelCenter = 340 + Math.sin(nx * 1.5) * 35 + Math.cos(nx * 3.2) * 15;
      const halfGap = 44 + Math.sin(nx * 2.8) * 10 + Math.cos(nx * 0.8) * 8;
      return isTop ? (channelCenter - halfGap) : (channelCenter + halfGap);
    }

    /* --------------------------------------------------------------------------
       2. 初始化微气流粒子
       -------------------------------------------------------------------------- */
    function initParticles() {
      state.particles = [];
      for (let i = 0; i < 28; i++) {
        state.particles.push({
          segIndex: Math.floor(Math.random() * CONFIG.segments),
          progress: Math.random(),
          offsetRadial: (Math.random() - 0.5) * 16,
          speed: 0.015 + Math.random() * 0.02
        });
      }
    }

    /* --------------------------------------------------------------------------
       3. 核心解算:按相位生成8段蛇身行波骨架与波纹管外轮廓
       -------------------------------------------------------------------------- */
    function updatePhysics(dt) {
      state.time += dt;

      // 动态行波曲率计算 θᵢ(t) = A·sin(2πft − 2πki/N)
      // 若处于刚化模式,幅值缩减至接近0,管径微胀
      let currentAmp = (state.mode === 'stiff') ? 2.5 : (state.pressure / 70) * state.amplitude;
      
      const spinePoints = [];
      const N = CONFIG.segments;
      const totalLen = CONFIG.bodyLength;
      const stepLen = totalLen / (CONFIG.sampleCount - 1);

      // 计算脊椎每个采样点的瞬时法线与中心坐标
      for (let i = 0; i < CONFIG.sampleCount; i++) {
        const s = i * stepLen; // 沿体长弧长
        const segIdx = Math.min(N - 1, Math.floor((s / totalLen) * N));
        
        let yOffset = 0;
        let phase = 0;

        if (state.mode === 'stiff') {
          // 全腔高压加压:平直紧绷,抵抗外部弯折
          yOffset = Math.sin(s * 0.008) * 3;
        } else {
          // 行波方程:按相位沿轴向传递
          const waveDirection = state.waveSpeedSign;
          phase = 2 * Math.PI * state.freq * state.time * waveDirection - (2 * Math.PI * 1.3 * (s / totalLen));
          // 头尾锥形平滑衰减
          const envelope = Math.sin((i / (CONFIG.sampleCount - 1)) * Math.PI * 0.95 + 0.05);
          yOffset = currentAmp * Math.sin(phase) * envelope;
        }

        // 基准推进通道轴线中心微偏
        const posX = CONFIG.originX + s;
        // 与不规则通道轻微自适应贴合
        const channelCenter = (sampleWallY(posX, true) + sampleWallY(posX, false)) / 2;
        const finalY = channelCenter + yOffset;

        spinePoints.push({ x: posX, y: finalY, s, segIdx, phase });
      }

      // 计算采样点切线与法线向量 (用于精准构建波纹管膨胀边缘)
      for (let i = 0; i < spinePoints.length; i++) {
        let dx, dy;
        if (i === 0) {
          dx = spinePoints[1].x - spinePoints[0].x;
          dy = spinePoints[1].y - spinePoints[0].y;
        } else if (i === spinePoints.length - 1) {
          dx = spinePoints[i].x - spinePoints[i - 1].x;
          dy = spinePoints[i].y - spinePoints[i - 1].y;
        } else {
          dx = spinePoints[i + 1].x - spinePoints[i - 1].x;
          dy = spinePoints[i + 1].y - spinePoints[i - 1].y;
        }
        const len = Math.hypot(dx, dy) || 1;
        spinePoints[i].nx = -dy / len; // 法向量 X
        spinePoints[i].ny = dx / len;  // 法向量 Y
      }

      // 渲染波纹管上下外壁轮廓
      renderBellowsSkins(spinePoints);
      // 渲染内部8段独立多腔室气压膨胀与色彩映射
      renderChambers(spinePoints);
      // 渲染螺旋限制纤维
      renderFibers(spinePoints);
      // 渲染管壁接触与应力波纹 (IFR: 大面积柔软贴合,无刚性集中应力)
      renderContactSoftStress(spinePoints);
      // 渲染气流微粒子与头部探头
      renderPneumaticsAndHead(spinePoints);
      // 更新右上角三腔室周向截面微雷达图
      updateCrossSectionRadar(spinePoints);
    }

    /* --------------------------------------------------------------------------
       4. 绘制锯齿状硅胶波纹管外壁 (Bellows Convolutions)
       -------------------------------------------------------------------------- */
    function renderBellowsSkins(spine) {
      let upperD = '';
      let lowerD = '';
      let spineD = '';

      const totalSegments = CONFIG.segments;
      const rBase = CONFIG.baseRadius * (state.pressure / 68); // 气压越高管径微胀

      // 脊椎中心线
      spine.forEach((pt, i) => {
        spineD += `${i === 0 ? 'M' : 'L'} ${pt.x.toFixed(1)},${pt.y.toFixed(1)} `;
      });
      snakeSpine.setAttribute('d', spineD);

      // 构建波纹褶皱 (波纹管外径沿波浪交替 rBase + rib)
      const corrugationFreq = 0.16; // 波纹密度
      for (let i = 0; i < spine.length; i++) {
        const pt = spine[i];
        
        // 依据该段当前充气弯曲率计算内外侧折叠差:内侧压缩波纹变密、外侧展开平缓
        const segPhase = Math.sin(pt.phase || 0);
        // 上下侧由于行波弯曲产生的波纹高度差
        const ribHeightUpper = 5.5 + segPhase * 1.5;
        const ribHeightLower = 5.5 - segPhase * 1.5;

        const ribOsc = Math.cos(pt.s * corrugationFreq);
        const rUpper = rBase + ribOsc * ribHeightUpper;
        const rLower = rBase + ribOsc * ribHeightLower;

        const ux = pt.x + pt.nx * rUpper;
        const uy = pt.y + pt.ny * rUpper;
        const lx = pt.x - pt.nx * rLower;
        const ly = pt.y - pt.ny * rLower;

        upperD += `${i === 0 ? 'M' : 'L'} ${ux.toFixed(1)},${uy.toFixed(1)} `;
        lowerD += `${i === 0 ? 'M' : 'L'} ${lx.toFixed(1)},${ly.toFixed(1)} `;
      }

      bellowsSkinUpper.setAttribute('d', upperD);
      bellowsSkinLower.setAttribute('d', lowerD);
    }

    /* --------------------------------------------------------------------------
       5. 绘制8段气腔充放气与压力色温动态映射
       -------------------------------------------------------------------------- */
    function renderChambers(spine) {
      chambersLayer.innerHTML = '';
      segmentBorders.innerHTML = '';

      const N = CONFIG.segments;
      const ptsPerSeg = Math.floor(spine.length / N);

      for (let seg = 0; seg < N; seg++) {
        const startIdx = seg * ptsPerSeg;
        const endIdx = (seg === N - 1) ? spine.length - 1 : (seg + 1) * ptsPerSeg;
        
        const midSpinePt = spine[Math.floor((startIdx + endIdx) / 2)];
        
        // 算出第 seg 段当前气腔压力状态:上腔 vs 下腔 差动充气
        // 充气段膨胀伸长,反侧收缩
        let pTop, pBottom;
        if (state.mode === 'stiff') {
          pTop = 0.95;
          pBottom = 0.95;
        } else {
          const waveDir = state.waveSpeedSign;
          const phase = 2 * Math.PI * state.freq * state.time * waveDir - (2 * Math.PI * 1.3 * (seg / N));
          // 正弦波动值映射到 0.15 (排气) ~ 1.0 (高压充气)
          pTop = 0.55 + 0.45 * Math.sin(phase);
          pBottom = 0.55 - 0.45 * Math.sin(phase);
        }

        // 构建上气腔多边形
        let topChamberD = ``;
        for (let i = startIdx; i <= endIdx; i++) {
          const pt = spine[i];
          const r = CONFIG.baseRadius * 0.85;
          const x = pt.x + pt.nx * (r * pTop);
          const y = pt.y + pt.ny * (r * pTop);
          topChamberD += `${i === startIdx ? 'M' : 'L'} ${x.toFixed(1)},${y.toFixed(1)} `;
        }
        for (let i = endIdx; i >= startIdx; i--) {
          const pt = spine[i];
          const x = pt.x + pt.nx * 2;
          const y = pt.y + pt.ny * 2;
          topChamberD += `L ${x.toFixed(1)},${y.toFixed(1)} `;
        }
        topChamberD += 'Z';

        // 构建下气腔多边形
        let botChamberD = ``;
        for (let i = startIdx; i <= endIdx; i++) {
          const pt = spine[i];
          const r = CONFIG.baseRadius * 0.85;
          const x = pt.x - pt.nx * (r * pBottom);
          const y = pt.y - pt.ny * (r * pBottom);
          botChamberD += `${i === startIdx ? 'M' : 'L'} ${x.toFixed(1)},${y.toFixed(1)} `;
        }
        for (let i = endIdx; i >= startIdx; i--) {
          const pt = spine[i];
          const x = pt.x - pt.nx * 2;
          const y = pt.y - pt.ny * 2;
          botChamberD += `L ${x.toFixed(1)},${y.toFixed(1)} `;
        }
        botChamberD += 'Z';

        // 渲染填充腔体 (高压时呈明亮电光青、低压时呈深暗蓝)
        const pathTop = document.createElementNS('http://www.w3.org/2000/svg', 'path');
        pathTop.setAttribute('d', topChamberD);
        pathTop.setAttribute('fill', `rgba(0, 243, 255, ${(pTop * 0.65).toFixed(2)})`);
        if (pTop > 0.78) {
          pathTop.setAttribute('filter', 'url(#glow)');
        }
        chambersLayer.appendChild(pathTop);

        const pathBot = document.createElementNS('http://www.w3.org/2000/svg', 'path');
        pathBot.setAttribute('d', botChamberD);
        pathBot.setAttribute('fill', `rgba(0, 243, 255, ${(pBottom * 0.65).toFixed(2)})`);
        if (pBottom > 0.78) {
          pathBot.setAttribute('filter', 'url(#glow)');
        }
        chambersLayer.appendChild(pathBot);

        // 绘制节段间柔性加强分隔环
        const borderPt = spine[startIdx];
        const rRing = CONFIG.baseRadius + 5;
        const line = document.createElementNS('http://www.w3.org/2000/svg', 'line');
        line.setAttribute('x1', (borderPt.x + borderPt.nx * rRing).toFixed(1));
        line.setAttribute('y1', (borderPt.y + borderPt.ny * rRing).toFixed(1));
        line.setAttribute('x2', (borderPt.x - borderPt.nx * rRing).toFixed(1));
        line.setAttribute('y2', (borderPt.y - borderPt.ny * rRing).toFixed(1));
        line.setAttribute('stroke', 'rgba(148, 163, 184, 0.45)');
        line.setAttribute('stroke-width', '2');
        segmentBorders.appendChild(line);

        // 每隔一段打上微小段编号
        if (seg % 2 === 0) {
          const segTag = document.createElementNS('http://www.w3.org/2000/svg', 'text');
          segTag.setAttribute('x', borderPt.x.toFixed(1));
          segTag.setAttribute('y', (borderPt.y - rRing - 7).toFixed(1));
          segTag.setAttribute('fill', '#64748b');
          segTag.setAttribute('font-size', '8');
          segTag.setAttribute('font-family', 'monospace');
          segTag.setAttribute('text-anchor', 'middle');
          segTag.textContent = `SEG-0${seg + 1}`;
          segmentBorders.appendChild(segTag);
        }
      }
    }

    /* --------------------------------------------------------------------------
       6. 绘制螺旋编织限制纤维 (防止高压下径向过度鼓胀破裂)
       -------------------------------------------------------------------------- */
    function renderFibers(spine) {
      fibersLayer.innerHTML = '';
      const step = 4;
      let fiberPath1 = '';
      let fiberPath2 = '';

      for (let i = 0; i < spine.length; i += step) {
        const pt = spine[i];
        const r = CONFIG.baseRadius;
        const angle = i * 0.35;
        const offset = Math.sin(angle) * r;
        
        const f1x = pt.x + pt.nx * offset;
        const f1y = pt.y + pt.ny * offset;
        const f2x = pt.x - pt.nx * offset;
        const f2y = pt.y - pt.ny * offset;

        fiberPath1 += `${i === 0 ? 'M' : 'L'} ${f1x.toFixed(1)},${f1y.toFixed(1)} `;
        fiberPath2 += `${i === 0 ? 'M' : 'L'} ${f2x.toFixed(1)},${f2y.toFixed(1)} `;
      }

      const p1 = document.createElementNS('http://www.w3.org/2000/svg', 'path');
      p1.setAttribute('d', fiberPath1);
      p1.setAttribute('stroke', 'rgba(245, 158, 11, 0.3)');
      p1.setAttribute('stroke-width', '1');
      p1.setAttribute('fill', 'none');
      fibersLayer.appendChild(p1);

      const p2 = document.createElementNS('http://www.w3.org/2000/svg', 'path');
      p2.setAttribute('d', fiberPath2);
      p2.setAttribute('stroke', 'rgba(245, 158, 11, 0.3)');
      p2.setAttribute('stroke-width', '1');
      p2.setAttribute('fill', 'none');
      fibersLayer.appendChild(p2);
    }

    /* --------------------------------------------------------------------------
       7. 柔顺贴合与大面积微应力场可视化 (突出消除卡死的“最终理想解”)
       -------------------------------------------------------------------------- */
    function renderContactSoftStress(spine) {
      contactStressGlowGroup.innerHTML = '';

      let contactCount = 0;
      // 遍历蛇身采样点,检测与上下不规则腔壁的柔性贴合
      for (let i = 8; i < spine.length - 8; i += 6) {
        const pt = spine[i];
        const r = CONFIG.baseRadius + 6;
        
        const topWallY = sampleWallY(pt.x, true);
        const botWallY = sampleWallY(pt.x, false);

        const upperDist = Math.abs((pt.y - r) - topWallY);
        const lowerDist = Math.abs((pt.y + r) - botWallY);

        // 如果贴近通道壁 (软接触应力扩散区)
        if (upperDist < 12) {
          contactCount++;
          const contactGlow = document.createElementNS('http://www.w3.org/2000/svg', 'ellipse');
          contactGlow.setAttribute('cx', pt.x.toFixed(1));
          contactGlow.setAttribute('cy', topWallY.toFixed(1));
          contactGlow.setAttribute('rx', '24');
          contactGlow.setAttribute('ry', '6');
          contactGlow.setAttribute('fill', 'rgba(245, 158, 11, 0.35)');
          contactGlow.setAttribute('filter', 'url(#glow-soft)');
          contactStressGlowGroup.appendChild(contactGlow);
        }

        if (lowerDist < 12) {
          contactCount++;
          const contactGlow = document.createElementNS('http://www.w3.org/2000/svg', 'ellipse');
          contactGlow.setAttribute('cx', pt.x.toFixed(1));
          contactGlow.setAttribute('cy', botWallY.toFixed(1));
          contactGlow.setAttribute('rx', '24');
          contactGlow.setAttribute('ry', '6');
          contactGlow.setAttribute('fill', 'rgba(245, 158, 11, 0.35)');
          contactGlow.setAttribute('filter', 'url(#glow-soft)');
          contactStressGlowGroup.appendChild(contactGlow);
        }
      }

      // 实时计算自适应顺应贴合度数值,更新面板
      const fitScore = Math.min(99.2, 88.0 + contactCount * 1.4);
      valFit.textContent = `${fitScore.toFixed(1)}%`;
    }

    /* --------------------------------------------------------------------------
       8. 气流微粒子、头部柔性镜头帽与尾部微电磁阀组
       -------------------------------------------------------------------------- */
    function renderPneumaticsAndHead(spine) {
      pneumaticParticlesGroup.innerHTML = '';

      // 气流粒子沿当前段流动
      state.particles.forEach(p => {
        p.progress += p.speed * (state.freq / 2);
        if (p.progress > 1) {
          p.progress = 0;
          p.segIndex = Math.floor(Math.random() * CONFIG.segments);
        }

        const segPts = Math.floor(spine.length / CONFIG.segments);
        const ptIdx = Math.min(spine.length - 1, Math.floor((p.segIndex + p.progress) * segPts));
        const pt = spine[ptIdx];

        if (pt) {
          const px = pt.x + pt.nx * p.offsetRadial;
          const py = pt.y + pt.ny * p.offsetRadial;

          const circle = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
          circle.setAttribute('cx', px.toFixed(1));
          circle.setAttribute('cy', py.toFixed(1));
          circle.setAttribute('r', '1.6');
          circle.setAttribute('fill', '#00f3ff');
          circle.setAttribute('opacity', (0.3 + Math.sin(p.progress * Math.PI) * 0.6).toFixed(2));
          pneumaticParticlesGroup.appendChild(circle);
        }
      });

      // 头部柔性半球硅胶帽与微型光纤摄像头
      const headPt = spine[spine.length - 1];
      const prevHead = spine[spine.length - 3];
      const hAngle = Math.atan2(headPt.y - prevHead.y, headPt.x - prevHead.x) * (180 / Math.PI);

      headCap.innerHTML = `
        <g transform="translate(${headPt.x}, ${headPt.y}) rotate(${hAngle})">
          <!-- 半球柔性硅胶头 -->
          <path d="M 0,-${CONFIG.baseRadius} A ${CONFIG.baseRadius} ${CONFIG.baseRadius} 0 0 1 0,${CONFIG.baseRadius} Z" 
                fill="rgba(56,189,248,0.3)" stroke="#38bdf8" stroke-width="2"/>
          <!-- 微型探头/光纤传感器光束 -->
          <polygon points="4,-6 40,-20 40,20 4,6" fill="rgba(0,243,255,0.08)" />
          <line x1="2" y1="0" x2="42" y2="0" stroke="rgba(0,243,255,0.5)" stroke-width="1.2" stroke-dasharray="3,3" />
          <circle cx="2" cy="0" r="3.5" fill="#00f3ff" filter="url(#glow)"/>
        </g>
      `;

      // 尾部多通道气动管路拖曳接口
      const tailPt = spine[0];
      const nextTail = spine[2];
      const tAngle = Math.atan2(nextTail.y - tailPt.y, nextTail.x - tailPt.x) * (180 / Math.PI);
      tailTether.innerHTML = `
        <g transform="translate(${tailPt.x}, ${tailPt.y}) rotate(${tAngle})">
          <rect x="-14" y="-20" width="14" height="40" rx="3" fill="#1e293b" stroke="rgba(56,189,248,0.5)" stroke-width="1.5"/>
          <line x1="-14" y1="-8" x2="-45" y2="-12" stroke="#38bdf8" stroke-width="2.5" stroke-linecap="round"/>
          <line x1="-14" y1="0" x2="-55" y2="0" stroke="#00f3ff" stroke-width="2.5" stroke-linecap="round"/>
          <line x1="-14" y1="8" x2="-45" y2="12" stroke="#38bdf8" stroke-width="2.5" stroke-linecap="round"/>
          <text x="-48" y="-18" fill="#64748b" font-size="8" font-family="monospace">8通道微阀阵列</text>
        </g>
      `;
    }

    /* --------------------------------------------------------------------------
       9. 联动更新右上角 120° 三腔室动态压力雷达
       -------------------------------------------------------------------------- */
    function updateCrossSectionRadar(spine) {
      // 取第 4 节段中点的数据
      const midSegPt = spine[Math.floor(spine.length * 0.45)];
      if (!midSegPt) return;

      const phase = midSegPt.phase || 0;
      // 120° 三相差动气压
      const p1 = 0.5 + 0.5 * Math.sin(phase);
      const p2 = 0.5 + 0.5 * Math.sin(phase + (2 * Math.PI / 3));
      const p3 = 0.5 + 0.5 * Math.sin(phase + (4 * Math.PI / 3));

      csChamberTop.setAttribute('fill', `rgba(0, 243, 255, ${(0.2 + p1 * 0.7).toFixed(2)})`);
      csChamberRight.setAttribute('fill', `rgba(0, 243, 255, ${(0.2 + p2 * 0.7).toFixed(2)})`);
      csChamberLeft.setAttribute('fill', `rgba(0, 243, 255, ${(0.2 + p3 * 0.7).toFixed(2)})`);

      csChamberTop.setAttribute('stroke-width', p1 > 0.7 ? '2' : '1');
      csChamberRight.setAttribute('stroke-width', p2 > 0.7 ? '2' : '1');
      csChamberLeft.setAttribute('stroke-width', p3 > 0.7 ? '2' : '1');
    }

    /* --------------------------------------------------------------------------
       10. 交互控制器事件绑定 (平滑调节与模式切换)
       -------------------------------------------------------------------------- */
    speedRange.addEventListener('input', (e) => {
      state.freq = parseFloat(e.target.value);
      valFreq.textContent = `${state.freq.toFixed(2)} Hz`;
    });

    pressRange.addEventListener('input', (e) => {
      state.pressure = parseFloat(e.target.value);
      valPress.textContent = `${state.pressure.toFixed(1)} kPa`;
    });

    function setActiveModeBtn(activeBtn) {
      [btnWave, btnReverse, btnStiff].forEach(b => b.classList.remove('active'));
      activeBtn.classList.add('active');
    }

    btnWave.addEventListener('click', () => {
      state.mode = 'wave';
      state.waveSpeedSign = 1;
      setActiveModeBtn(btnWave);
    });

    btnReverse.addEventListener('click', () => {
      state.mode = 'reverse';
      state.waveSpeedSign = -1;
      setActiveModeBtn(btnReverse);
    });

    btnStiff.addEventListener('click', () => {
      state.mode = 'stiff';
      state.waveSpeedSign = 0;
      setActiveModeBtn(btnStiff);
    });

    /* --------------------------------------------------------------------------
       11. 自动启动主循环 (符合重开即播、自动播放红线)
       -------------------------------------------------------------------------- */
    let lastTimestamp = 0;
    function animationLoop(timestamp) {
      if (!lastTimestamp) lastTimestamp = timestamp;
      const dt = Math.min((timestamp - lastTimestamp) / 1000, 0.05); // 限幅防抽搐
      lastTimestamp = timestamp;

      updatePhysics(dt);
      requestAnimationFrame(animationLoop);
    }

    // 页面完全就绪后自启动
    window.addEventListener('DOMContentLoaded', () => {
      buildNarrowChannel();
      initParticles();
      requestAnimationFrame(animationLoop);
    });

    // 窗口尺寸自适应保障
    window.addEventListener('resize', () => {
      // 保持 SVG viewBox 自适应缩放
    });
  </script>
</body>
</html>
积分规则:每轮对话扣10分