分享图
动画工坊
引擎就绪
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>行星轮越障 · 主动平衡云台 — IFR 原理动画</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=Orbitron:wght@400;700;900&family=IBM+Plex+Mono:wght@300;400;500&display=swap" rel="stylesheet">
<script src="https://cdn.tailwindcss.com"></script>
<style>
:root{--bg:#060a12;--fg:#d0d8e8;--muted:#3e4a64;--accent:#00e5ff;--warm:#ff7a1a;--green:#00ff88;--danger:#ff4455;--card:#0b1020;--border:#162038}
*{margin:0;padding:0;box-sizing:border-box}
body{background:var(--bg);color:var(--fg);font-family:'IBM Plex Mono',monospace;min-height:100vh;display:flex;flex-direction:column;align-items:center;justify-content:center;padding:12px;overflow-x:hidden}
.wrap{width:100%;max-width:1120px;display:flex;flex-direction:column;align-items:center;gap:12px}
header{text-align:center;width:100%}
header h1{font-family:'Orbitron',sans-serif;font-weight:900;font-size:clamp(.95rem,2.4vw,1.5rem);letter-spacing:.14em;color:var(--accent);text-transform:uppercase}
header p{font-size:.7rem;color:var(--muted);margin-top:3px;letter-spacing:.06em}
.svg-box{width:100%;border:1px solid var(--border);border-radius:10px;overflow:hidden;background:linear-gradient(180deg,#080e1a,#060a12);box-shadow:0 0 80px rgba(0,229,255,.04),0 4px 30px rgba(0,0,0,.5)}
.svg-box svg{width:100%;height:auto;display:block}
.ctrls{display:flex;gap:16px;align-items:center;flex-wrap:wrap;justify-content:center;padding:10px 18px;background:var(--card);border:1px solid var(--border);border-radius:8px;width:100%;max-width:820px}
.cg{display:flex;align-items:center;gap:5px}
.cg label{font-size:.62rem;color:var(--muted);text-transform:uppercase;letter-spacing:.08em;white-space:nowrap}
.cg input[type=range]{-webkit-appearance:none;width:96px;height:3px;background:var(--border);border-radius:2px;outline:none}
.cg input[type=range]::-webkit-slider-thumb{-webkit-appearance:none;width:12px;height:12px;background:var(--accent);border-radius:50%;cursor:pointer;box-shadow:0 0 6px rgba(0,229,255,.5)}
.cg .v{font-size:.72rem;color:var(--accent);min-width:32px;text-align:right;font-weight:500}
.ind{display:flex;align-items:center;gap:5px;padding:3px 10px;border-radius:14px;border:1px solid var(--border);background:rgba(0,229,255,.04)}
.dot{width:7px;height:7px;border-radius:50%;background:var(--muted);transition:all .3s}
.dot.on{background:var(--green);box-shadow:0 0 10px rgba(0,255,136,.6)}
.ind span{font-size:.58rem;color:var(--muted);letter-spacing:.06em;text-transform:uppercase;transition:color .3s}
.ind span.on{color:var(--green)}
@media(prefers-reduced-motion:reduce){.dot{animation:none!important}}
</style>
</head>
<body>
<div class="wrap">
  <header>
    <h1>Planetary Wheel + Active Gimbal</h1>
    <p>TRIZ IFR · 功能解耦:底层暴力越障 / 上层主动稳姿 · 资源自洽消解矛盾</p>
  </header>
  <div class="svg-box">
    <svg id="S" viewBox="0 0 1200 700" xmlns="http://www.w3.org/2000/svg">
      <defs>
        <pattern id="gp" width="40" height="40" patternUnits="userSpaceOnUse">
          <path d="M40 0L0 0 0 40" fill="none" stroke="#0f1828" stroke-width=".5"/>
        </pattern>
        <filter id="fGC"><feGaussianBlur stdDeviation="5" result="b"/><feFlood flood-color="#00e5ff" flood-opacity=".45"/><feComposite in2="b" operator="in" result="g"/><feMerge><feMergeNode in="g"/><feMergeNode in="SourceGraphic"/></feMerge></filter>
        <filter id="fGG"><feGaussianBlur stdDeviation="4" result="b"/><feFlood flood-color="#00ff88" flood-opacity=".5"/><feComposite in2="b" operator="in" result="g"/><feMerge><feMergeNode in="g"/><feMergeNode in="SourceGraphic"/></feMerge></filter>
        <filter id="fGW"><feGaussianBlur stdDeviation="3" result="b"/><feFlood flood-color="#ff7a1a" flood-opacity=".35"/><feComposite in2="b" operator="in" result="g"/><feMerge><feMergeNode in="g"/><feMergeNode in="SourceGraphic"/></feMerge></filter>
        <linearGradient id="gC" x1="0" y1="0" x2="0" y2="1"><stop offset="0%" stop-color="#ff9030"/><stop offset="100%" stop-color="#c85c00"/></linearGradient>
        <linearGradient id="gP" x1="0" y1="0" x2="0" y2="1"><stop offset="0%" stop-color="#283048"/><stop offset="100%" stop-color="#1c2438"/></linearGradient>
        <linearGradient id="gCh" x1="0" y1="0" x2="0" y2="1"><stop offset="0%" stop-color="#2e2e52"/><stop offset="100%" stop-color="#1e1e3a"/></linearGradient>
      </defs>
      <rect width="1200" height="700" fill="url(#gp)" opacity=".55"/>
    </svg>
  </div>
  <div class="ctrls">
    <div class="cg"><label>旋转臂长</label><input type="range" id="rA" min="55" max="120" value="90" step="5"><span class="v" id="vA">90</span></div>
    <div class="cg"><label>台阶高度</label><input type="range" id="rH" min="30" max="120" value="85" step="5"><span class="v" id="vH">85</span></div>
    <div class="cg"><label>速度</label><input type="range" id="rS" min="0.3" max="2.5" value="1" step="0.1"><span class="v" id="vS">1.0</span></div>
    <div class="ind"><div class="dot" id="gDot"></div><span id="gLbl">云台待机</span></div>
  </div>
</div>

<script>
(function(){
'use strict';
/* ───── 常量与配置 ───── */
const NS='http://www.w3.org/2000/svg';
const svg=document.getElementById('S');
const C={armLen:90,stepH:85,speed:1,wr:18,gy:580,sx:480,cw:140,ch:30,pw:170,ph:10,crgW:80,crgH:35,gap:48,maxTilt:14,cyc:10000};

/* ───── 工具函数 ───── */
function mk(t,a,p){const e=document.createElementNS(NS,t);for(const[k,v]of Object.entries(a))e.setAttribute(k,v);(p||svg).appendChild(e);return e}
function lr(a,b,t){return a+(b-a)*t}
function eio(t){return t<.5?2*t*t:-1+(4-2*t)*t}
function dg(r){return r*180/Math.PI}
function rd(d){return d*Math.PI/180}

/* ───── 几何计算 ───── */
function flatY(){return C.gy-C.armLen*Math.cos(Math.PI/6)-C.wr}
function stepY(){return(C.gy-C.stepH)-C.armLen*Math.cos(Math.PI/6)-C.wr}

/* ───── 构建静态场景 ───── */
// 地面
const gG=mk('g',{id:'gnd'});
mk('rect',{x:0,y:C.gy,width:1200,height:120,fill:'#090e18'},gG);
mk('line',{x1:0,y1:C.gy,x2:1200,y2:C.gy,stroke:'#1e2e48','stroke-width':2},gG);
for(let i=0;i<32;i++){const x=i*38+8;mk('line',{x1:x,y1:C.gy+10,x2:x+14,y2:C.gy+10,stroke:'#0c1220','stroke-width':1},gG)}

// 台阶
const sG=mk('g',{id:'stp'});
function buildStep(){
  sG.innerHTML='';
  const st=C.gy-C.stepH;
  mk('rect',{x:C.sx,y:st,width:720,height:C.stepH,fill:'#0c1424',stroke:'#1e2e48','stroke-width':1.5},sG);
  mk('line',{x1:C.sx,y1:st,x2:C.sx,y2:C.gy,stroke:'#304060','stroke-width':2.5},sG);
  mk('line',{x1:C.sx,y1:st,x2:1200,y2:st,stroke:'#1e2e48','stroke-width':1.5},sG);
  // 高度标注
  mk('line',{x1:C.sx-22,y1:st,x2:C.sx-22,y2:C.gy,stroke:'#283850','stroke-width':.8,'stroke-dasharray':'3,3'},sG);
  mk('line',{x1:C.sx-27,y1:st,x2:C.sx-17,y2:st,stroke:'#283850','stroke-width':.8},sG);
  mk('line',{x1:C.sx-27,y1:C.gy,x2:C.sx-17,y2:C.gy,stroke:'#283850','stroke-width':.8},sG);
  const ht=mk('text',{x:C.sx-30,y:(st+C.gy)/2+3,fill:'#38506a','font-family':'IBM Plex Mono','font-size':9,'text-anchor':'middle',transform:`rotate(-90,${C.sx-30},${(st+C.gy)/2})`},sG);
  ht.textContent='H='+C.stepH;
  // 台阶表面纹理
  for(let i=0;i<18;i++){const x=C.sx+i*40+10;mk('line',{x1:x,y1:st+8,x2:x+16,y2:st+8,stroke:'#0a1018','stroke-width':1},sG)}
}
buildStep();

/* ───── 动态元素层 ───── */
// 补偿区高亮
const cmpZone=mk('rect',{fill:'none',stroke:'#00e5ff','stroke-width':1,'stroke-dasharray':'6,5',rx:6,opacity:0});

// 旋转弧线轨迹
const trailArc=mk('path',{fill:'none',stroke:'#00e5ff','stroke-width':1.8,opacity:0,'stroke-dasharray':'7,5'});

// 底盘组
const chG=mk('g');
const chRect=mk('rect',{x:-C.cw/2,y:-C.ch/2,width:C.cw,height:C.ch,rx:4,fill:'url(#gCh)',stroke:'#4a4a7c','stroke-width':1.5},chG);
mk('line',{x1:-C.cw/2+12,y1:-5,x2:C.cw/2-12,y2:-5,stroke:'#3a3a5c','stroke-width':.5},chG);
mk('line',{x1:-C.cw/2+12,y1:5,x2:C.cw/2-12,y2:5,stroke:'#3a3a5c','stroke-width':.5},chG);
// 底盘标签
const chLbl=mk('text',{x:0,y:C.ch/2+14,'text-anchor':'middle',fill:'#3a4a68','font-family':'IBM Plex Mono','font-size':8,'letter-spacing':'.08em'},chG);
chLbl.textContent='CHASSIS';

// 行星臂组
const armG=mk('g');
const spokes=[],wheels=[];
function buildArm(){
  // 清除旧内容
  while(armG.firstChild)armG.removeChild(armG.firstChild);
  spokes.length=0;wheels.length=0;
  for(let i=0;i<3;i++){
    const a=i*2*Math.PI/3;
    const ex=C.armLen*Math.sin(a),ey=-C.armLen*Math.cos(a);
    spokes.push(mk('line',{x1:0,y1:0,x2:ex,y2:ey,stroke:'#5a5a8c','stroke-width':3.5,'stroke-linecap':'round'},armG));
    // 轮子
    const wg=mk('g',{},armG);
    mk('circle',{cx:ex,cy:ey,r:C.wr,fill:'#1e1e40',stroke:'#6a6a9c','stroke-width':2},wg);
    mk('circle',{cx:ex,cy:ey,r:C.wr*.42,fill:'#28284a',stroke:'#4a4a7c','stroke-width':1},wg);
    // 轮辐(用于显示滚动)
    const dg2=mk('g',{},wg);
    mk('line',{x1:ex-7,y1:ey,x2:ex+7,y2:ey,stroke:'#5a5a8c','stroke-width':1},dg2);
    mk('line',{x1:ex,y1:ey-7,x2:ex,y2:ey+7,stroke:'#5a5a8c','stroke-width':1},dg2);
    wheels.push({g:wg,rotG:dg2,cx:ex,cy:ey});
  }
  // 轮毂中心
  mk('circle',{cx:0,cy:0,r:9,fill:'#222248',stroke:'#6a6aac','stroke-width':1.5},armG);
  mk('circle',{cx:0,cy:0,r:3.5,fill:'#00e5ff',opacity:.85},armG);
}
buildArm();

// 液压作动筒
const hyG=mk('g');
const hL=mk('line',{stroke:'#00889a','stroke-width':7,'stroke-linecap':'round',opacity:.7},hyG);
const hLi=mk('line',{stroke:'#00e5ff','stroke-width':2.5,'stroke-linecap':'round',opacity:.8},hyG);
const hR=mk('line',{stroke:'#00889a','stroke-width':7,'stroke-linecap':'round',opacity:.7},hyG);
const hRi=mk('line',{stroke:'#00e5ff','stroke-width':2.5,'stroke-linecap':'round',opacity:.8},hyG);

// 平台组
const plG=mk('g');
mk('rect',{x:-C.pw/2,y:-C.ph/2,width:C.pw,height:C.ph,rx:3,fill:'url(#gP)',stroke:'#4a5a80','stroke-width':1.5},plG);
// 水平仪
const lvlG=mk('g',{transform:`translate(${C.pw/2-22},0)`},plG);
mk('rect',{x:-12,y:-7,width:24,height:14,rx:7,fill:'#080e1a',stroke:'#1a3050','stroke-width':.5},lvlG);
const lvlB=mk('circle',{cx:0,cy:0,r:3.5,fill:'#00ff88',opacity:.9},lvlG);

// 货物组
const crG=mk('g');
mk('rect',{x:-C.crgW/2,y:-C.crgH,width:C.crgW,height:C.crgH,rx:2,fill:'url(#gC)',stroke:'#ffa040','stroke-width':1},crG);
mk('line',{x1:-C.crgW/2+10,y1:-C.crgH/2,x2:C.crgW/2-10,y2:-C.crgH/2,stroke:'#ffb860','stroke-width':.5,opacity:.4},crG);
const crLbl=mk('text',{x:0,y:-C.crgH/2+4,'text-anchor':'middle',fill:'#fff0d0','font-family':'IBM Plex Mono','font-size':9,'font-weight':'500'},crG);
crLbl.textContent='CARGO';

// 幽灵货物(展示无补偿时的倾斜)
const ghG=mk('g',{opacity:0});
mk('rect',{x:-C.crgW/2,y:-C.crgH,width:C.crgW,height:C.crgH,rx:2,fill:'none',stroke:'#ff4455','stroke-width':1.5,'stroke-dasharray':'5,5'},ghG);
const ghLbl=mk('text',{x:0,y:-C.crgH-10,'text-anchor':'middle',fill:'#ff4455','font-family':'IBM Plex Mono','font-size':8,opacity:.8},ghG);
ghLbl.textContent='无补偿 → 倾覆';

// 陀螺仪指示器
const gyG=mk('g',{transform:'translate(1060,105)'});
mk('circle',{cx:0,cy:0,r:44,fill:'#070c16',stroke:'#162038','stroke-width':1.5},gyG);
mk('circle',{cx:0,cy:0,r:34,fill:'none',stroke:'#101828','stroke-width':.5},gyG);
mk('line',{x1:-39,y1:0,x2:39,y2:0,stroke:'#101828','stroke-width':.5},gyG);
mk('line',{x1:0,y1:-39,x2:0,y2:39,stroke:'#101828','stroke-width':.5},gyG);
const gyRing=mk('ellipse',{cx:0,cy:0,rx:28,ry:28,fill:'none',stroke:'#00e5ff','stroke-width':1.5,opacity:.3},gyG);
const gyLev=mk('line',{x1:-22,y1:0,x2:22,y2:0,stroke:'#00ff88','stroke-width':2.5,opacity:.6},gyG);
mk('text',{x:0,y:60,'text-anchor':'middle',fill:'#2a4060','font-family':'IBM Plex Mono','font-size':9,'letter-spacing':'.1em'},gyG).textContent='GYRO';
const gyAng=mk('text',{x:0,y:74,'text-anchor':'middle',fill:'#00e5ff','font-family':'IBM Plex Mono','font-size':11,'font-weight':'500'},gyG);

// 阶段文本
const phG=mk('g',{transform:'translate(55,38)'});
const phM=mk('text',{fill:'#00e5ff','font-family':'Orbitron','font-size':14,'letter-spacing':'.12em',opacity:.9},phG);
const phS=mk('text',{y:20,fill:'#4a6080','font-family':'IBM Plex Mono','font-size':11,'letter-spacing':'.05em'},phG);

// IFR 功能解耦标注
const ifrG=mk('g',{transform:'translate(55,618)'});
mk('rect',{x:0,y:0,width:215,height:60,rx:5,fill:'#080e1a',stroke:'#162038','stroke-width':.8,opacity:.9},ifrG);
mk('text',{x:108,y:15,'text-anchor':'middle',fill:'#00e5ff','font-family':'Orbitron','font-size':9,'letter-spacing':'.1em'},ifrG).textContent='IFR 功能解耦';
const ifrL1=mk('text',{x:12,y:32,fill:'#ff7a1a','font-family':'IBM Plex Mono','font-size':9.5},ifrG);
ifrL1.textContent='▼ 底层:行星轮组 — 暴力越障';
const ifrL2=mk('text',{x:12,y:48,fill:'#00e5ff','font-family':'IBM Plex Mono','font-size':9.5},ifrG);
ifrL2.textContent='▲ 上层:液压云台 — 主动稳姿';

// 臂长尺寸标注
const dmG=mk('g',{opacity:0});
const dmLn=mk('line',{stroke:'#00e5ff','stroke-width':.8,'stroke-dasharray':'3,2',opacity:.6},dmG);
const dmTx=mk('text',{fill:'#00e5ff','font-family':'IBM Plex Mono','font-size':9,opacity:.7,'text-anchor':'middle'},dmG);

// 响应时间标注(越障时显示)
const rtG=mk('g',{opacity:0});
const rtTx=mk('text',{fill:'#00ff88','font-family':'IBM Plex Mono','font-size':9,'font-weight':'500'},rtG);

/* ───── 动画状态 ───── */
let prog=0,lastT=null;

/* ───── 状态函数 ───── */
function getState(p){
  const fy=flatY(),sy=stepY();
  let hx,hy,aa,ct,ph,pp;
  if(p<.20){ph='flat';pp=p/.20;hx=lr(130,365,eio(pp));hy=fy;aa=pp*.4;ct=0}
  else if(p<.56){ph='cross';pp=(p-.20)/.36;const ep=eio(pp);hx=lr(365,535,ep);hy=lr(fy,sy,ep);aa=.4+pp*(2*Math.PI/3);ct=rd(C.maxTilt)*Math.sin(Math.PI*pp)}
  else if(p<.82){ph='top';pp=(p-.56)/.26;hx=lr(535,870,eio(pp));hy=sy;aa=.4+2*Math.PI/3+pp*.4;ct=0}
  else{ph='reset';pp=(p-.82)/.18;hx=lr(870,910,pp);hy=sy;aa=.4+2*Math.PI/3+.4;ct=0}
  return{hx,hy,aa,ct,ph,pp}
}

/* ───── 更新帧 ───── */
function tick(ts){
  if(!lastT)lastT=ts;
  const dt=(ts-lastT)*C.speed;
  lastT=ts;
  prog+=dt/C.cyc;
  if(prog>=1)prog-=1;

  const s=getState(prog);
  const{hx,hy,aa,ct,ph,pp}=s;

  // 全局淡入淡出
  let gOp=1;
  if(ph==='reset')gOp=1-eio(pp);
  else if(ph==='flat'&&pp<.08)gOp=pp/.08;

  const isX=ph==='cross';
  const fail=C.stepH>C.armLen;

  // ── 行星臂 ──
  armG.setAttribute('transform',`translate(${hx},${hy}) rotate(${dg(aa)})`);

  // 轮子滚动视觉效果(轮辐旋转)
  const rollAng=dg(hx/C.wr);
  for(let i=0;i<wheels.length;i++){
    wheels[i].rotG.setAttribute('transform',`rotate(${rollAng},${wheels[i].cx},${wheels[i].cy})`);
  }

  // ── 底盘 ──
  chG.setAttribute('transform',`translate(${hx},${hy}) rotate(${dg(ct)})`);

  // ── 平台(始终保持水平) ──
  const plY=hy-C.ch/2-C.gap;
  plG.setAttribute('transform',`translate(${hx},${plY})`);

  // ── 货物(始终水平) ──
  crG.setAttribute('transform',`translate(${hx},${plY-C.ph/2})`);
  crG.setAttribute('opacity',gOp);
  if(isX&&!fail)crG.setAttribute('filter','url(#fGW)');else crG.removeAttribute('filter');

  // ── 液压作动筒 ──
  const cosT=Math.cos(ct),sinT=Math.sin(ct);
  const ctlX=hx+(-52)*cosT-(-C.ch/2)*sinT;
  const ctlY=hy+(-52)*sinT+(-C.ch/2)*cosT;
  const ctrX=hx+52*cosT-(-C.ch/2)*sinT;
  const ctrY=hy+52*sinT+(-C.ch/2)*cosT;
  const pbY=plY+C.ph/2;
  const hOff=8; // 液压左右偏移

  hL.setAttribute('x1',ctlX);hL.setAttribute('y1',ctlY);hL.setAttribute('x2',hx-hOff);hL.setAttribute('y2',pbY);
  hLi.setAttribute('x1',ctlX);hLi.setAttribute('y1',ctlY);hLi.setAttribute('x2',hx-hOff);hLi.setAttribute('y2',pbY);
  hR.setAttribute('x1',ctrX);hR.setAttribute('y1',ctrY);hR.setAttribute('x2',hx+hOff);hR.setAttribute('y2',pbY);
  hRi.setAttribute('x1',ctrX);hRi.setAttribute('y1',ctrY);hRi.setAttribute('x2',hx+hOff);hRi.setAttribute('y2',pbY);

  const hOp=isX?.8+Math.sin(ts*.008)*.15:.55;
  hL.setAttribute('opacity',hOp*gOp);hR.setAttribute('opacity',hOp*gOp);
  hLi.setAttribute('opacity',(isX?1:.65)*gOp);hRi.setAttribute('opacity',(isX?1:.65)*gOp);
  if(isX&&!fail)hyG.setAttribute('filter','url(#fGC)');else hyG.removeAttribute('filter');

  // ── 幽灵货物 ──
  if(isX&&!fail){
    const gOp2=.25+.2*Math.sin(ts*.005);
    ghG.setAttribute('opacity',gOp2*gOp);
    ghG.setAttribute('transform',`translate(${hx},${plY-C.ph/2}) rotate(${dg(ct)})`);
  }else{ghG.setAttribute('opacity',0)}

  // ── 补偿区高亮 ──
  if(isX&&!fail){
    cmpZone.setAttribute('opacity',(.25+.12*Math.sin(ts*.004))*gOp);
    cmpZone.setAttribute('x',hx-95);cmpZone.setAttribute('y',plY-C.crgH-20);
    cmpZone.setAttribute('width',190);cmpZone.setAttribute('height',C.crgH+C.ph+C.gap+C.ch+30);
  }else{cmpZone.setAttribute('opacity',0)}

  // ── 旋转弧线轨迹 ──
  if(isX&&!fail){
    const r=C.armLen;
    const sa=.4,ea=aa;
    const sx2=hx+r*Math.sin(sa),sy2=hy-r*Math.cos(sa);
    const ex2=hx+r*Math.sin(ea),ey2=hy-r*Math.cos(ea);
    const la=(ea-sa)>Math.PI?1:0;
    trailArc.setAttribute('d',`M${sx2} ${sy2} A${r} ${r} 0 ${la} 1 ${ex2} ${ey2}`);
    trailArc.setAttribute('opacity',(.18+.15*pp)*gOp);
  }else{trailArc.setAttribute('opacity',0)}

  // ── 臂长尺寸标注 ──
  if(isX&&pp>.08&&pp<.92&&!fail){
    dmG.setAttribute('opacity',.6*gOp);
    const wa=aa; // 当前臂角度
    const ex3=hx+C.armLen*Math.sin(wa),ey3=hy-C.armLen*Math.cos(wa);
    dmLn.setAttribute('x1',hx);dmLn.setAttribute('y1',hy);dmLn.setAttribute('x2',ex3);dmLn.setAttribute('y2',ey3);
    dmTx.setAttribute('x',(hx+ex3)/2+16);dmTx.setAttribute('y',(hy+ey3)/2);
    dmTx.textContent='R='+C.armLen;
  }else{dmG.setAttribute('opacity',0)}

  // ── 响应时间标注 ──
  if(isX&&!fail){
    rtG.setAttribute('opacity',(.5+.3*Math.sin(ts*.006))*gOp);
    rtTx.setAttribute('x',hx+100);rtTx.setAttribute('y',plY-C.crgH/2+3);
    rtTx.textContent='响应 < 50ms';
  }else{rtG.setAttribute('opacity',0)}

  // ── 陀螺仪 ──
  const absTilt=Math.abs(ct);
  if(absTilt>rd(.5)){
    const ry2=28*Math.cos(ct);
    gyRing.setAttribute('ry',Math.max(6,Math.abs(ry2)));
    gyRing.setAttribute('opacity',.7);
  }else{
    gyRing.setAttribute('ry',28);gyRing.setAttribute('opacity',.25);
  }
  gyLev.setAttribute('opacity',isX?1:.5);
  if(isX&&!fail)gyLev.setAttribute('filter','url(#fGG)');else gyLev.removeAttribute('filter');
  gyAng.textContent=fail?'--':(dg(ct)).toFixed(1)+'°';

  // ── 阶段文本 ──
  const pMap={
    flat:['ROLLING','行星轮平地行驶 — 双轮接地'],
    cross:fail?['OVERLOAD','台阶高度超过臂长 — 越障失效']:['CROSSING','支架翻转跨步 · 云台实时补偿'],
    top:fail?['BLOCKED','无法完成跨越']:['STABILIZED','越障完成 · 货物绝对水平'],
    reset:['','']
  };
  const[mt,st]=pMap[ph];
  phM.textContent=mt;phS.textContent=st;
  phM.setAttribute('fill',fail?'#ff4455':'#00e5ff');
  phS.setAttribute('fill',fail?'#ff6666':'#4a6080');
  phG.setAttribute('opacity',gOp);

  // ── 状态指示灯 ──
  const gd=document.getElementById('gDot'),gl=document.getElementById('gLbl');
  if(isX&&!fail){gd.classList.add('on');gl.classList.add('on');gl.textContent='云台补偿中'}
  else{gd.classList.remove('on');gl.classList.remove('on');gl.textContent=fail?'越障失效':'云台待机'}

  requestAnimationFrame(tick);
}

/* ───── 控件绑定 ───── */
const rA=document.getElementById('rA'),rH=document.getElementById('rH'),rS=document.getElementById('rS');
const vA=document.getElementById('vA'),vH=document.getElementById('vH'),vS=document.getElementById('vS');

rA.addEventListener('input',()=>{
  C.armLen=+rA.value;vA.textContent=C.armLen;buildArm();buildStep();
});
rH.addEventListener('input',()=>{
  C.stepH=+rH.value;vH.textContent=C.stepH;buildStep();
});
rS.addEventListener('input',()=>{
  C.speed=+rS.value;vS.textContent=C.speed.toFixed(1);
});

/* ───── 启动动画 ───── */
function start(){requestAnimationFrame(tick)}
if(document.readyState==='loading')document.addEventListener('DOMContentLoaded',start);else start();
})();
</script>
</body>
</html>

实现说明

动画原理:整个场景通过 requestAnimationFrame 驱动,以归一化进度 0→1 循环播放四个阶段——平地行驶、台阶跨越、台阶顶面行驶、淡出重置。核心状态函数 getState() 根据进度插值计算轮毂坐标、臂旋转角、底盘倾角等关键参数。

IFR 视觉表达

  • 功能解耦:底盘(跟随倾斜)与平台(始终水平)分为独立渲染层,液压作动筒连接二者并实时伸缩补偿,用 青色辉光 高亮这一核心创新点
  • 幽灵货物:越障时以红色虚线绘制"无补偿时货物随底盘倾斜"的假想状态,与实际保持水平的货物形成鲜明对比
  • 陀螺仪面板:右上角实时显示底盘倾角,绿色水平线始终水平,直观传达"云台补偿成功"
  • 旋转弧线轨迹:越障时绘制臂旋转路径,配合臂长尺寸标注,突出行星轮翻转跨步的物理机理

交互控制:三个滑块分别调节旋转臂长、台阶高度和动画速度。当台阶高度超过臂长时,动画自动切换为"越障失效"状态(红色警示文本),直观展示方案的适用边界。

积分规则:第一轮对话扣减8分,后续每轮扣6分