分享图
动画工坊
引擎就绪
<!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: #070b12;
      --panel-bg: rgba(13, 22, 36, 0.7);
      --panel-border: rgba(56, 189, 248, 0.18);
      --accent-gold: #f59e0b;
      --accent-gold-glow: rgba(245, 158, 11, 0.4);
      --accent-cyan: #00f2fe;
      --accent-cyan-dim: rgba(0, 242, 254, 0.25);
      --accent-emerald: #10b981;
      --accent-rose: #f43f5e;
      --text-main: #e2e8f0;
      --text-muted: #64748b;
      --font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
      --font-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", sans-serif;
    }

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

    body {
      background-color: var(--bg);
      color: var(--text-main);
      font-family: var(--font-sans);
      overflow: hidden;
      width: 100vw;
      height: 100vh;
      display: flex;
      flex-direction: column;
      align-items: center;
      justify-content: center;
      background-image: 
        radial-gradient(circle at 50% 30%, rgba(14, 165, 233, 0.08) 0%, transparent 65%),
        linear-gradient(rgba(255, 255, 255, 0.02) 1px, transparent 1px),
        linear-gradient(90deg, rgba(255, 255, 255, 0.02) 1px, transparent 1px);
      background-size: 100% 100%, 36px 36px, 36px 36px;
    }

    /* 视窗容器 */
    .viewport-container {
      position: relative;
      width: 100vw;
      height: 100vh;
      max-width: 1440px;
      max-height: 900px;
      display: flex;
      flex-direction: column;
      justify-content: center;
      align-items: center;
    }

    /* SVG 核心画布 */
    svg#stage {
      width: 100%;
      height: 100%;
      max-height: 88vh;
      overflow: visible;
      filter: drop-shadow(0 20px 40px rgba(0, 0, 0, 0.6));
    }

    /* 角落说明文字:文字极简,尺寸小于 13px,严格靠边避让主区域 */
    .hud-tag {
      position: absolute;
      pointer-events: none;
      font-family: var(--font-mono);
      font-size: 11px;
      letter-spacing: 0.08em;
      text-transform: uppercase;
      color: var(--text-muted);
      z-index: 10;
      display: flex;
      flex-direction: column;
      gap: 3px;
    }

    .hud-top-left {
      top: 16px;
      left: 24px;
    }

    .hud-top-left .title-badge {
      display: inline-flex;
      align-items: center;
      gap: 6px;
      font-size: 13px;
      font-weight: 600;
      color: var(--text-main);
      margin-bottom: 2px;
      letter-spacing: 0.04em;
    }

    .status-dot {
      width: 6px;
      height: 6px;
      border-radius: 50%;
      background: var(--accent-emerald);
      box-shadow: 0 0 8px var(--accent-emerald);
      display: inline-block;
      animation: pulse-dot 2s infinite ease-in-out;
    }

    @keyframes pulse-dot {
      0%, 100% { opacity: 0.5; transform: scale(0.9); }
      50% { opacity: 1; transform: scale(1.15); }
    }

    .hud-top-right {
      top: 16px;
      right: 24px;
      text-align: right;
    }

    .metric-row {
      display: flex;
      justify-content: flex-end;
      gap: 8px;
    }
    .metric-val {
      color: var(--accent-cyan);
      font-weight: 500;
    }

    /* 底部极简紧凑悬浮控制胶囊,高度紧凑,避让中央动画 */
    .control-capsule {
      position: absolute;
      bottom: 14px;
      display: flex;
      align-items: center;
      gap: 16px;
      background: var(--panel-bg);
      border: 1px solid var(--panel-border);
      backdrop-filter: blur(12px);
      -webkit-backdrop-filter: blur(12px);
      padding: 6px 18px;
      border-radius: 9999px;
      box-shadow: 0 10px 30px rgba(0, 0, 0, 0.45);
      z-index: 20;
    }

    .ctrl-item {
      display: flex;
      align-items: center;
      gap: 8px;
      font-size: 11px;
      color: var(--text-muted);
      font-family: var(--font-mono);
    }

    .ctrl-btn {
      background: rgba(255, 255, 255, 0.06);
      border: 1px solid rgba(255, 255, 255, 0.12);
      color: var(--text-main);
      padding: 4px 10px;
      border-radius: 6px;
      font-size: 11px;
      cursor: pointer;
      display: flex;
      align-items: center;
      gap: 4px;
      transition: all 0.2s ease;
    }
    .ctrl-btn:hover {
      background: rgba(56, 189, 248, 0.18);
      border-color: var(--accent-cyan);
      color: #fff;
    }

    input[type=range] {
      -webkit-appearance: none;
      width: 72px;
      height: 3px;
      background: rgba(255, 255, 255, 0.15);
      border-radius: 2px;
      outline: none;
      cursor: pointer;
    }
    input[type=range]::-webkit-slider-thumb {
      -webkit-appearance: none;
      width: 11px;
      height: 11px;
      border-radius: 50%;
      background: var(--accent-cyan);
      box-shadow: 0 0 6px var(--accent-cyan);
      cursor: pointer;
    }
  </style>
</head>
<body>

  <div class="viewport-container">
    <!-- 左上角极简说明 -->
    <div class="hud-tag hud-top-left">
      <div class="title-badge">
        <span class="status-dot"></span>
        单源贯穿软轴驱动机构 · 仿生蛇形推进
      </div>
      <div>1个尾部电机 → 贯穿式软轴(恒速自转) → 12组30°相位偏心轮 → 正弦波动</div>
    </div>

    <!-- 右上角核心物理参数 -->
    <div class="hud-tag hud-top-right">
      <div class="metric-row">节段数: <span class="metric-val">12 节</span> | 偏心距: <span class="metric-val">e = 6mm</span></div>
      <div class="metric-row">全身体长: <span class="metric-val">600mm (1λ)</span> | 相位差: <span class="metric-val">Δφ = 30°</span></div>
      <div class="metric-row">摆动幅值: <span class="metric-val">±22°</span> | 传动源: <span class="metric-val">单电机+软轴</span></div>
    </div>

    <!-- 核心 SVG 画布 -->
    <svg id="stage" viewBox="0 0 1300 760" preserveAspectRatio="xMidYMid meet">
      <defs>
        <!-- 线性与径向渐变 -->
        <linearGradient id="shaftGrad" x1="0%" y1="0%" x2="0%" y2="100%">
          <stop offset="0%" stop-color="#fef08a" />
          <stop offset="50%" stop-color="#f59e0b" />
          <stop offset="100%" stop-color="#b45309" />
        </linearGradient>

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

        <linearGradient id="headGrad" x1="0%" y1="0%" x2="100%" y2="0%">
          <stop offset="0%" stop-color="#0284c7" />
          <stop offset="100%" stop-color="#38bdf8" />
        </linearGradient>

        <linearGradient id="tailGrad" x1="0%" y1="0%" x2="100%" y2="0%">
          <stop offset="0%" stop-color="#d97706" />
          <stop offset="100%" stop-color="#b45309" />
        </linearGradient>

        <!-- 发光滤镜 -->
        <filter id="glow-gold" x="-30%" y="-30%" width="160%" height="160%">
          <feGaussianBlur stdDeviation="3.5" result="blur" />
          <feMerge>
            <feMergeNode in="blur" />
            <feMergeNode in="SourceGraphic" />
          </feMerge>
        </filter>

        <filter id="glow-cyan" x="-30%" y="-30%" width="160%" height="160%">
          <feGaussianBlur stdDeviation="2.5" result="blur" />
          <feMerge>
            <feMergeNode in="blur" />
            <feMergeNode in="SourceGraphic" />
          </feMerge>
        </filter>

        <!-- 鳞片微观结构图案 -->
        <pattern id="groundGrid" width="40" height="40" patternUnits="userSpaceOnUse">
          <circle cx="20" cy="20" r="1" fill="#334155" opacity="0.3" />
        </pattern>
      </defs>

      <!-- 动态推进背景标线:向后流动,直观表现蛇体向前巡航前进 -->
      <g id="ground-speed-lines" opacity="0.3"></g>

      <!--====================================================
          区域1:【俯视全景机构运作图】 (Main Top Inspection View)
          居中主要展区:展示12节骨架、内部偏心轮、柔性软轴与行波包络
      =====================================================-->
      <g id="top-view-group" transform="translate(60, 240)">
        <!-- 导向中线与波形包络参考带 -->
        <path id="wave-envelope-pos" stroke="#38bdf8" stroke-dasharray="3,3" stroke-width="0.8" fill="none" opacity="0.25" />
        <path id="wave-envelope-neg" stroke="#38bdf8" stroke-dasharray="3,3" stroke-width="0.8" fill="none" opacity="0.25" />
        <line x1="0" y1="0" x2="1140" y2="0" stroke="#334155" stroke-dasharray="4,6" stroke-width="1" opacity="0.4" />

        <!-- 蛇体整体链节容器 (由JS实时更新位置与旋转) -->
        <g id="snake-body"></g>

        <!-- 贯穿式 φ4mm 弹簧钢软轴骨干 (一条连续平滑金黄色发光样条曲线) -->
        <path id="continuous-flex-shaft" fill="none" stroke="url(#shaftGrad)" stroke-width="4.5" stroke-linecap="round" filter="url(#glow-gold)" opacity="0.95" />
        <!-- 软轴内部扭矩螺旋线光效 -->
        <path id="continuous-flex-shaft-core" fill="none" stroke="#fff" stroke-dasharray="4,7" stroke-width="1.8" opacity="0.85" />

        <!-- 偏心轮与销钉摇臂层 (位于软轴之上) -->
        <g id="eccentric-cams-layer"></g>

        <!-- 行波传播矢量箭头标注 (向尾部传播) -->
        <g id="wave-propagation-indicator" transform="translate(100, 100)">
          <line x1="0" y1="0" x2="180" y2="0" stroke="#38bdf8" stroke-width="1.5" stroke-dasharray="4,3" />
          <polygon points="185,0 175,-4 175,4" fill="#38bdf8" />
          <text x="90" y="-8" fill="#38bdf8" font-size="10" font-family="var(--font-mono)" text-anchor="middle" letter-spacing="1">正弦弯曲行波传播方向 (向尾部 λ)</text>
        </g>

        <!-- 实体净前进推力箭头 (向头部前进) -->
        <g id="thrust-propagation-indicator" transform="translate(940, -100)">
          <line x1="0" y1="0" x2="-180" y2="0" stroke="#10b981" stroke-width="2.5" />
          <polygon points="-188,0 -176,-5 -176,5" fill="#10b981" />
          <text x="-90" y="-9" fill="#10b981" font-size="11" font-family="var(--font-mono)" font-weight="600" text-anchor="middle" letter-spacing="1">净推进位移方向 (F_net)</text>
        </g>
      </g>

      <!--====================================================
          区域2:底部微观机构与力学拆解 (小尺寸布局在下方,不遮挡主体)
      =====================================================-->
      <!-- 视窗A:单节偏心轮滑槽机构特写 (Section A: Cam-Slot Detail) -->
      <g id="detail-cam-slot" transform="translate(140, 520)">
        <rect x="0" y="0" width="370" height="180" rx="8" fill="rgba(15, 23, 42, 0.65)" stroke="var(--panel-border)" stroke-width="1" />
        <text x="18" y="24" fill="#94a3b8" font-size="11" font-family="var(--font-mono)" font-weight="600">节段内部·单自由度偏心滑槽传动</text>

        <!-- 局部机壳轮廓 -->
        <g transform="translate(180, 100)">
          <!-- 外壳滑槽约束 -->
          <rect x="-110" y="-55" width="220" height="110" rx="8" fill="none" stroke="#475569" stroke-width="1.2" stroke-dasharray="3,3" />
          <line x1="-120" y1="0" x2="120" y2="0" stroke="#334155" stroke-width="0.8" />
          
          <!-- 滑槽导轨 -->
          <rect x="-70" y="-36" width="140" height="72" rx="4" fill="rgba(30, 41, 59, 0.5)" stroke="#64748b" stroke-width="1" />
          <rect id="slot-slider" x="-18" y="-30" width="36" height="60" rx="3" fill="rgba(56, 189, 248, 0.2)" stroke="#0284c7" stroke-width="1.5" />

          <!-- 中心自转软轴孔 -->
          <circle cx="0" cy="0" r="10" fill="#1e293b" stroke="#f59e0b" stroke-width="2" />
          <circle cx="0" cy="0" r="3" fill="#fef08a" />
          
          <!-- 偏心轮与旋转销轴 -->
          <g id="demo-cam-rotator">
            <circle cx="0" cy="0" r="26" fill="none" stroke="#f59e0b" stroke-dasharray="3,2" stroke-width="1" />
            <!-- 偏心距 e=18px (等效放大示意) -->
            <circle id="demo-eccentric-pin" cx="0" cy="-20" r="5" fill="#f43f5e" filter="url(#glow-gold)" />
            <line x1="0" y1="0" x2="0" y2="-20" stroke="#f59e0b" stroke-width="2.5" />
            <!-- 横向摆动连杆滑块 -->
            <path id="demo-linkage" d="M 0,-20 L 0,-20" stroke="#38bdf8" stroke-width="3" stroke-linecap="round" />
          </g>

          <text x="0" y="66" fill="#64748b" font-size="10" font-family="var(--font-mono)" text-anchor="middle">
            旋转运动 ⟳ 转为 横向单自由度摆动 ⟷
          </text>
        </g>
      </g>

      <!-- 视窗B:底部各向异性摩擦鳞片动力学 (Section B: Anisotropic Friction Scale) -->
      <g id="detail-scales" transform="translate(680, 520)">
        <rect x="0" y="0" width="480" height="180" rx="8" fill="rgba(15, 23, 42, 0.65)" stroke="var(--panel-border)" stroke-width="1" />
        <text x="18" y="24" fill="#94a3b8" font-size="11" font-family="var(--font-mono)" font-weight="600">底部鳞片·各向异性摩擦力学分解 (咬合 vs 滑移)</text>

        <g transform="translate(50, 45)">
          <!-- 地面接触基线 -->
          <line x1="0" y1="90" x2="380" y2="90" stroke="#475569" stroke-width="2" />
          <!-- 地面微齿地貌 -->
          <path d="M 0,90 l 10,6 l 10,-6 l 10,6 l 10,-6 l 10,6 l 10,-6 l 10,6 l 10,-6 l 10,6 l 10,-6 l 10,6 l 10,-6 l 10,6 l 10,-6 l 10,6 l 10,-6 l 10,6 l 10,-6 l 10,6 l 10,-6 l 10,6 l 10,-6 l 10,6 l 10,-6 l 10,6 l 10,-6 l 10,6 l 10,-6 l 10,6 l 10,-6 l 10,6 l 10,-6 l 10,6 l 10,-6 l 10,6 l 10,-6 l 10,6 l 10,-6" fill="none" stroke="#334155" stroke-width="1" />

          <!-- 蛇体下腹外壳 -->
          <rect x="40" y="22" width="280" height="26" rx="4" fill="#1e293b" stroke="#64748b" stroke-width="1" />
          <text x="180" y="39" fill="#94a3b8" font-size="10" font-family="var(--font-mono)" text-anchor="middle">蛇体底部腹板</text>

          <!-- 3组 30° 斜置弹性鳞片 -->
          <!-- 鳞片1 -->
          <path d="M 80,48 L 50,90" stroke="#f59e0b" stroke-width="3" stroke-linecap="round" />
          <circle cx="50" cy="90" r="3" fill="#f43f5e" />
          <!-- 鳞片2 -->
          <path d="M 170,48 L 140,90" stroke="#f59e0b" stroke-width="3" stroke-linecap="round" />
          <circle cx="140" cy="90" r="3" fill="#f43f5e" />
          <!-- 鳞片3 -->
          <path d="M 260,48 L 230,90" stroke="#f59e0b" stroke-width="3" stroke-linecap="round" />
          <circle cx="230" cy="90" r="3" fill="#f43f5e" />

          <!-- 角度与力学矢量 -->
          <!-- 倾角弧线 -->
          <path d="M 130,90 A 20 20 0 0 1 146,78" fill="none" stroke="#38bdf8" stroke-width="1.2" />
          <text x="116" y="80" fill="#38bdf8" font-size="9" font-family="var(--font-mono)">30°</text>

          <!-- 力学矢量标示 -->
          <!-- 后退自锁阻力 (大) -->
          <g transform="translate(140, 90)">
            <line x1="0" y1="0" x2="-45" y2="0" stroke="#f43f5e" stroke-width="2" />
            <polygon points="-48,0 -40,-3 -40,3" fill="#f43f5e" />
            <text x="-25" y="-6" fill="#f43f5e" font-size="9" font-family="var(--font-mono)" text-anchor="middle">后退自锁 (μ_high)</text>
          </g>

          <!-- 前向滑动低阻 (小) -->
          <g transform="translate(260, 48)">
            <line x1="0" y1="0" x2="55" y2="0" stroke="#10b981" stroke-width="2" />
            <polygon points="60,0 52,-3 52,3" fill="#10b981" />
            <text x="32" y="-6" fill="#10b981" font-size="9" font-family="var(--font-mono)" text-anchor="middle">前向顺滑 (μ_low)</text>
          </g>

          <text x="190" y="122" fill="#64748b" font-size="10" font-family="var(--font-mono)" text-anchor="middle">
            横向抗滑 + 轴向各向异性摩擦 → 波动能量 100% 转换为前向推力
          </text>
        </g>
      </g>
    </svg>

    <!-- 底部极简控制胶囊 (高度精简,绝不遮挡视线) -->
    <div class="control-capsule">
      <div class="ctrl-item">
        <button id="btn-toggle" class="ctrl-btn">
          <span id="play-icon">⏸</span>
          <span id="play-text">暂停</span>
        </button>
      </div>
      <div class="ctrl-item">
        <span>驱动转速</span>
        <input type="range" id="speed-slider" min="0.4" max="2.5" step="0.1" value="1.2" />
      </div>
      <div class="ctrl-item">
        <span>行波振幅</span>
        <input type="range" id="amp-slider" min="20" max="65" step="1" value="48" />
      </div>
      <div class="ctrl-item">
        <button id="btn-xray" class="ctrl-btn">
          <span>透视机壳: ON</span>
        </button>
      </div>
    </div>
  </div>

  <script>
    (function () {
      // 核心参数常量 (严谨对应工程数据)
      const SEGMENTS_COUNT = 12;      // 12节
      const SEGMENT_LENGTH = 72;      // 画布中每节标称长度
      const ECCENTRIC_DIST = 12;      // 偏心距 e (对应 6mm 比例)
      const PHASE_DIFF = Math.PI / 6; // 30° 递增,12 节累计 360° 正好 1 个完整波长

      // 运行状态
      let isRunning = true;
      let time = 0;
      let speedScale = 1.2;
      let amplitude = 48; // 正弦振幅
      let showXray = true;
      let groundOffset = 0;

      // DOM 容器索引
      const snakeGroup = document.getElementById("snake-body");
      const camsLayer = document.getElementById("eccentric-cams-layer");
      const flexShaft = document.getElementById("continuous-flex-shaft");
      const flexShaftCore = document.getElementById("continuous-flex-shaft-core");
      const groundLines = document.getElementById("ground-speed-lines");
      const envPos = document.getElementById("wave-envelope-pos");
      const envNeg = document.getElementById("wave-envelope-neg");

      // 特写展示区元素
      const demoCamRotator = document.getElementById("demo-cam-rotator");
      const demoLinkage = document.getElementById("demo-linkage");
      const slotSlider = document.getElementById("slot-slider");

      // 控制器 DOM
      const btnToggle = document.getElementById("btn-toggle");
      const playIcon = document.getElementById("play-icon");
      const playText = document.getElementById("play-text");
      const speedSlider = document.getElementById("speed-slider");
      const ampSlider = document.getElementById("amp-slider");
      const btnXray = document.getElementById("btn-xray");

      // 动态生成背景流动标记线
      for (let i = 0; i < 28; i++) {
        const line = document.createElementNS("http://www.w3.org/2000/svg", "line");
        line.setAttribute("y1", "40");
        line.setAttribute("y2", "440");
        line.setAttribute("stroke", "#1e293b");
        line.setAttribute("stroke-width", "1");
        line.setAttribute("stroke-dasharray", "4,8");
        groundLines.appendChild(line);
      }

      // 构建 12 个节段的主体 SVG 节点
      const segmentNodes = [];
      const camNodes = [];

      for (let i = 0; i < SEGMENTS_COUNT; i++) {
        // 节段外壳 Group
        const gSeg = document.createElementNS("http://www.w3.org/2000/svg", "g");
        gSeg.setAttribute("class", "snake-segment");

        // 尼龙机壳几何 (带球铰倒角)
        const shell = document.createElementNS("http://www.w3.org/2000/svg", "rect");
        shell.setAttribute("x", -SEGMENT_LENGTH / 2);
        shell.setAttribute("y", -24);
        shell.setAttribute("width", SEGMENT_LENGTH - 4);
        shell.setAttribute("height", 48);
        shell.setAttribute("rx", "12");
        shell.setAttribute("fill", "url(#segGrad)");
        shell.setAttribute("stroke", "#475569");
        shell.setAttribute("stroke-width", "1.2");
        gSeg.appendChild(shell);

        // 节间球铰与限位套环
        const hinge = document.createElementNS("http://www.w3.org/2000/svg", "circle");
        hinge.setAttribute("cx", -SEGMENT_LENGTH / 2);
        hinge.setAttribute("cy", 0);
        hinge.setAttribute("r", 5);
        hinge.setAttribute("fill", "#0f172a");
        hinge.setAttribute("stroke", "#64748b");
        hinge.setAttribute("stroke-width", "1.5");
        gSeg.appendChild(hinge);

        // 内部骨架导向槽
        const slot = document.createElementNS("http://www.w3.org/2000/svg", "line");
        slot.setAttribute("x1", 0);
        slot.setAttribute("y1", -18);
        slot.setAttribute("x2", 0);
        slot.setAttribute("y2", 18);
        slot.setAttribute("stroke", "rgba(56, 189, 248, 0.4)");
        slot.setAttribute("stroke-width", "2");
        gSeg.appendChild(slot);

        // 首尾特殊件区别 (第0节为头部舵机/导向头,第11节为尾部电机仓)
        if (i === 0) {
          const headCap = document.createElementNS("http://www.w3.org/2000/svg", "path");
          headCap.setAttribute("d", `M ${SEGMENT_LENGTH / 2 - 2},-22 C ${SEGMENT_LENGTH / 2 + 30},-16 ${SEGMENT_LENGTH / 2 + 30},16 ${SEGMENT_LENGTH / 2 - 2},22 Z`);
          headCap.setAttribute("fill", "url(#headGrad)");
          headCap.setAttribute("opacity", "0.85");
          gSeg.appendChild(headCap);
          // 仿生眼部发光传感器
          const eye = document.createElementNS("http://www.w3.org/2000/svg", "circle");
          eye.setAttribute("cx", SEGMENT_LENGTH / 2 + 12);
          eye.setAttribute("cy", -8);
          eye.setAttribute("r", 2.5);
          eye.setAttribute("fill", "#38bdf8");
          eye.setAttribute("filter", "url(#glow-cyan)");
          gSeg.appendChild(eye);
        } else if (i === SEGMENTS_COUNT - 1) {
          // 尾部 12V 减速电机指示
          const motor = document.createElementNS("http://www.w3.org/2000/svg", "rect");
          motor.setAttribute("x", -SEGMENT_LENGTH / 2 - 20);
          motor.setAttribute("y", -14);
          motor.setAttribute("width", 20);
          motor.setAttribute("height", 28);
          motor.setAttribute("rx", 3);
          motor.setAttribute("fill", "url(#tailGrad)");
          gSeg.appendChild(motor);
        }

        snakeGroup.appendChild(gSeg);
        segmentNodes.push({ g: gSeg, shell });

        // 偏心轮机构层 (独立图层,旋转动态直观清晰)
        const gCam = document.createElementNS("http://www.w3.org/2000/svg", "g");
        
        // 偏心圆盘
        const camDisk = document.createElementNS("http://www.w3.org/2000/svg", "circle");
        camDisk.setAttribute("r", 10);
        camDisk.setAttribute("fill", "rgba(245, 158, 11, 0.25)");
        camDisk.setAttribute("stroke", "#f59e0b");
        camDisk.setAttribute("stroke-width", "1.5");

        // 偏心销
        const camPin = document.createElementNS("http://www.w3.org/2000/svg", "circle");
        camPin.setAttribute("r", 3);
        camPin.setAttribute("fill", "#f43f5e");

        // 推动连杆
        const pushRod = document.createElementNS("http://www.w3.org/2000/svg", "line");
        pushRod.setAttribute("stroke", "#00f2fe");
        pushRod.setAttribute("stroke-width", "2");
        pushRod.setAttribute("stroke-linecap", "round");

        gCam.appendChild(camDisk);
        gCam.appendChild(pushRod);
        gCam.appendChild(camPin);

        camsLayer.appendChild(gCam);
        camNodes.push({ g: gCam, disk: camDisk, pin: camPin, rod: pushRod });
      }

      // 实时正弦波与多刚体动力学计算
      function updatePhysics() {
        // 时间累计 (行波向前推进)
        if (isRunning) {
          time += 0.032 * speedScale;
          groundOffset = (groundOffset + 1.8 * speedScale) % 40;
        }

        // 更新地面参照系运动 (营造向前巡航运动感)
        const glines = groundLines.children;
        for (let j = 0; j < glines.length; j++) {
          const xPos = j * 45 - groundOffset;
          glines[j].setAttribute("x1", xPos);
          glines[j].setAttribute("x2", xPos);
        }

        // 计算 12 个骨架节段的全局坐标与姿态角
        const spinePoints = [];
        const segmentTransforms = [];

        // 机器蛇头部位于右侧 (X约 960),尾部在左侧 (X约 160)
        // 蜿蜒行波公式:y(x, t) = A * sin(omega * t - k * x)
        for (let i = 0; i < SEGMENTS_COUNT; i++) {
          // 相位随着节段向后依次滞后 30° (累计 360° 全身体长刚好一个波长)
          const nodePhase = time - i * PHASE_DIFF;
          
          // X 轴线性排布 (从头到尾)
          const x = 960 - i * SEGMENT_LENGTH;
          // Y 轴正弦横向位移
          const y = Math.sin(nodePhase) * amplitude;

          // 计算切线姿态角 (一阶导数)
          const dy_dx = -amplitude * Math.cos(nodePhase) * (PHASE_DIFF / SEGMENT_LENGTH);
          const angle = Math.atan(dy_dx) * (180 / Math.PI);

          spinePoints.push({ x, y, angle, phase: nodePhase });
          segmentTransforms.push({ x, y, angle });
        }

        // 渲染连续样条贯穿软轴 (Cubic Bezier Spline 拟合穿过各节中心偏心孔)
        if (spinePoints.length > 0) {
          let pathD = `M ${spinePoints[0].x} ${spinePoints[0].y}`;
          for (let i = 0; i < spinePoints.length - 1; i++) {
            const p0 = spinePoints[i];
            const p1 = spinePoints[i + 1];
            const mx = (p0.x + p1.x) / 2;
            const my = (p0.y + p1.y) / 2;
            pathD += ` Q ${p0.x} ${p0.y} ${mx} ${my}`;
          }
          const last = spinePoints[spinePoints.length - 1];
          pathD += ` T ${last.x} ${last.y}`;
          flexShaft.setAttribute("d", pathD);
          flexShaftCore.setAttribute("d", pathD);
        }

        // 渲染正弦包络波形极限参考线
        let envPosD = `M 960 ${amplitude}`;
        let envNegD = `M 960 ${-amplitude}`;
        for (let i = 1; i < SEGMENTS_COUNT; i++) {
          const x = 960 - i * SEGMENT_LENGTH;
          envPosD += ` L ${x} ${amplitude}`;
          envNegD += ` L ${x} ${-amplitude}`;
        }
        envPos.setAttribute("d", envPosD);
        envNeg.setAttribute("d", envNegD);

        // 更新每个节段姿态与内部机构
        for (let i = 0; i < SEGMENTS_COUNT; i++) {
          const p = spinePoints[i];
          const seg = segmentNodes[i];
          const cam = camNodes[i];

          // 节段外壳旋转与位移
          seg.g.setAttribute("transform", `translate(${p.x}, ${p.y}) rotate(${p.angle})`);
          seg.shell.setAttribute("fill-opacity", showXray ? "0.3" : "0.95");
          seg.shell.setAttribute("stroke-opacity", showXray ? "0.7" : "1");

          // 偏心轮随软轴旋转:自转角 = time (恒速自转) - i * 30°
          const camRotAngle = (time - i * PHASE_DIFF) * 1.5;
          const pinX = Math.cos(camRotAngle) * ECCENTRIC_DIST;
          const pinY = Math.sin(camRotAngle) * ECCENTRIC_DIST;

          cam.g.setAttribute("transform", `translate(${p.x}, ${p.y}) rotate(${p.angle})`);
          cam.pin.setAttribute("cx", pinX);
          cam.pin.setAttribute("cy", pinY);

          // 连杆连接偏心销与侧壁滑槽
          cam.rod.setAttribute("x1", pinX);
          cam.rod.setAttribute("y1", pinY);
          cam.rod.setAttribute("x2", 0);
          cam.rod.setAttribute("y2", pinY > 0 ? 18 : -18);
        }

        // 同步更新下方特写视窗 (单节机构与滑槽动态交互演示)
        const demoAngle = time * 2;
        const demoPinX = Math.cos(demoAngle) * 20;
        const demoPinY = Math.sin(demoAngle) * 20;
        
        demoCamRotator.querySelector("#demo-eccentric-pin").setAttribute("cx", demoPinX);
        demoCamRotator.querySelector("#demo-eccentric-pin").setAttribute("cy", demoPinY);
        demoLinkage.setAttribute("x1", demoPinX);
        demoLinkage.setAttribute("y1", demoPinY);
        demoLinkage.setAttribute("x2", 0);
        demoLinkage.setAttribute("y2", demoPinY > 0 ? 28 : -28);

        slotSlider.setAttribute("transform", `translate(0, ${demoPinY * 0.7})`);

        requestAnimationFrame(updatePhysics);
      }

      // 启动自循环动画
      requestAnimationFrame(updatePhysics);

      // 控件交互绑定
      btnToggle.addEventListener("click", () => {
        isRunning = !isRunning;
        playIcon.textContent = isRunning ? "⏸" : "▶";
        playText.textContent = isRunning ? "暂停" : "继续";
      });

      speedSlider.addEventListener("input", (e) => {
        speedScale = parseFloat(e.target.value);
      });

      ampSlider.addEventListener("input", (e) => {
        amplitude = parseFloat(e.target.value);
      });

      btnXray.addEventListener("click", () => {
        showXray = !showXray;
        btnXray.querySelector("span").textContent = `透视机壳: ${showXray ? "ON" : "OFF"}`;
      });

      // 页面从后台切换回或重载时,自动维持平滑播放
      document.addEventListener("visibilitychange", () => {
        if (!document.hidden && isRunning) {
          requestAnimationFrame(updatePhysics);
        }
      });
    })();
  </script>
</body>
</html>
积分规则:每轮对话扣10分