分享图
动画工坊
引擎就绪
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>柔性铰接底盘 · 攀爬原理动画</title>
<link href="https://fonts.googleapis.com/css2?family=Orbitron:wght@400;700;900&family=Noto+Sans+SC:wght@300;400;700&display=swap" rel="stylesheet">
<style>
:root{--bg:#060a12;--bg2:#0c1220;--fg:#d8e4f0;--muted:#5a7090;--accent:#00e5c8;--accent2:#ff6b35;--gold:#ffd700;--coral:#ff3366;--card:#0e1726;--border:#1c2d45}
*{margin:0;padding:0;box-sizing:border-box}
body{background:var(--bg);color:var(--fg);font-family:'Noto Sans SC',sans-serif;min-height:100vh;display:flex;flex-direction:column;align-items:center;overflow-x:hidden}
header{text-align:center;padding:1.6rem 1rem .8rem}
header h1{font-family:'Orbitron',sans-serif;font-weight:900;font-size:clamp(1rem,3vw,1.7rem);letter-spacing:.14em;color:var(--accent);text-shadow:0 0 28px rgba(0,229,200,.25)}
header p{color:var(--muted);font-size:.85rem;margin-top:.35rem;font-weight:300;letter-spacing:.06em}
.wrap{width:95%;max-width:1020px;background:var(--card);border:1px solid var(--border);border-radius:12px;overflow:hidden;box-shadow:0 0 50px rgba(0,229,200,.04),0 16px 40px rgba(0,0,0,.35);margin:.8rem 0}
svg{width:100%;height:auto;display:block}
.ctrls{display:flex;gap:1.6rem;padding:.9rem 1.6rem;background:var(--bg2);border-top:1px solid var(--border);flex-wrap:wrap;justify-content:center;align-items:center}
.cg{display:flex;align-items:center;gap:.6rem}
.cg label{font-size:.78rem;color:var(--muted);white-space:nowrap}
.cg input[type=range]{-webkit-appearance:none;appearance:none;width:110px;height:4px;background:var(--border);border-radius:2px;outline:none}
.cg input[type=range]::-webkit-slider-thumb{-webkit-appearance:none;width:15px;height:15px;background:var(--accent);border-radius:50%;cursor:pointer;box-shadow:0 0 6px rgba(0,229,200,.35)}
.cg .vd{font-family:'Orbitron',sans-serif;font-size:.72rem;color:var(--accent);min-width:2.4rem}
.btn{font-family:'Noto Sans SC',sans-serif;font-size:.78rem;padding:.35rem .9rem;background:transparent;color:var(--accent);border:1px solid var(--accent);border-radius:6px;cursor:pointer;transition:all .2s}
.btn:hover{background:rgba(0,229,200,.1)}
.cards{width:95%;max-width:1020px;display:grid;grid-template-columns:repeat(auto-fit,minmax(240px,1fr));gap:.9rem;padding:0 0 2rem}
.card{background:var(--card);border:1px solid var(--border);border-radius:8px;padding:1.1rem}
.card h3{font-family:'Orbitron',sans-serif;font-size:.65rem;letter-spacing:.1em;color:var(--accent2);margin-bottom:.5rem;text-transform:uppercase}
.card p{font-size:.8rem;color:var(--muted);line-height:1.65}
.hl{color:var(--accent);font-weight:700}
</style>
</head>
<body>
<header>
<h1>FLEXIBLE ARTICULATED CHASSIS</h1>
<p>多段铰接柔性底盘 · 被动重塑攀爬原理 · IFR 演示</p>
</header>
<div class="wrap">
<svg id="svg" viewBox="0 0 820 470" xmlns="http://www.w3.org/2000/svg">
<defs>
<filter id="gl" x="-50%" y="-50%" width="200%" height="200%"><feGaussianBlur stdDeviation="4" result="b"/><feMerge><feMergeNode in="b"/><feMergeNode in="SourceGraphic"/></feMerge></filter>
<filter id="gl2" x="-50%" y="-50%" width="200%" height="200%"><feGaussianBlur stdDeviation="7" result="b"/><feMerge><feMergeNode in="b"/><feMergeNode in="SourceGraphic"/></feMerge></filter>
<pattern id="grid" width="22" height="22" patternUnits="userSpaceOnUse"><path d="M22 0L0 0 0 22" fill="none" stroke="#141e32" stroke-width=".4"/></pattern>
<linearGradient id="stG" x1="0" y1="0" x2="0" y2="1"><stop offset="0%" stop-color="#1a2640"/><stop offset="100%" stop-color="#0f1828"/></linearGradient>
</defs>
<rect width="820" height="470" fill="#070c16"/>
<rect width="820" height="470" fill="url(#grid)" opacity=".35"/>
<g id="gStairs"></g>
<g id="gShadow"></g>
<g id="gTrack"></g>
<g id="gSegs"></g>
<g id="gJoints"></g>
<g id="gFx"></g>
<g id="gAnn"></g>
</svg>
<div class="ctrls">
<div class="cg"><label>动画速度</label><input type="range" id="rSpeed" min=".2" max="3" step=".1" value="1"><span class="vd" id="vSpeed">1.0x</span></div>
<div class="cg"><label>台阶高度</label><input type="range" id="rStep" min="30" max="80" step="5" value="55"><span class="vd" id="vStep">55</span></div>
<button class="btn" id="bReset">重置动画</button>
</div>
</div>
<div class="cards">
<div class="card"><h3>Core Principle</h3><p>底盘由 <span class="hl">5 段独立驱动舱段</span> 通过万向节串联,放弃刚性轮毂,使车体形态随地形被动重塑,像毛毛虫一样蠕动攀爬。</p></div>
<div class="card"><h3>Key Parameters</h3><p>万向节最大偏转 <span class="hl">45°</span>,独立驱动舱段 <span class="hl">5 段</span>,各段电机独立驱动外围宽体橡胶履带。</p></div>
<div class="card"><h3>IFR Insight</h3><p>系统无需主动感知地形——<span class="hl">接触力本身即控制信号</span>,地形塑造形态,形态适配地形,资源自洽,矛盾自消解。</p></div>
</div>

<script>
(function(){
const NS='http://www.w3.org/2000/svg';
const el=id=>document.getElementById(id);
const gStairs=el('gStairs'),gShadow=el('gShadow'),gTrack=el('gTrack'),gSegs=el('gSegs'),gJoints=el('gJoints'),gFx=el('gFx'),gAnn=el('gAnn');

/* ── 参数 ── */
const NSeg=5,SW=50,SH=20,JG=6,TT=7,MB=Math.PI/4,UNIT=SW+JG,RL=NSeg*SW+(NSeg-1)*JG;
let stepRise=55,stepRun=105,groundY=395,speed=1,progress=0,lastT=null,paused=false,pauseT=0;

/* ── 路径 ── */
let prof=[],cumD=[];
function buildProf(){
  const sX=300;
  prof=[
    {x:20,y:groundY},{x:sX,y:groundY},
    {x:sX,y:groundY-stepRise},{x:sX+stepRun,y:groundY-stepRise},
    {x:sX+stepRun,y:groundY-2*stepRise},{x:sX+2*stepRun,y:groundY-2*stepRise},
    {x:sX+2*stepRun,y:groundY-3*stepRise},{x:800,y:groundY-3*stepRise}
  ];
  cumD=[0];
  for(let i=1;i<prof.length;i++){const dx=prof[i].x-prof[i-1].x,dy=prof[i].y-prof[i-1].y;cumD.push(cumD[i-1]+Math.sqrt(dx*dx+dy*dy));}
}
function totalLen(){return cumD[cumD.length-1]}
function ptAt(a){
  a=Math.max(0,Math.min(a,totalLen()));
  for(let i=1;i<cumD.length;i++){
    if(a<=cumD[i]+.01){
      const sl=cumD[i]-cumD[i-1],t=sl>0?(a-cumD[i-1])/sl:0;
      return{x:prof[i-1].x+t*(prof[i].x-prof[i-1].x),y:prof[i-1].y+t*(prof[i].y-prof[i-1].y),a:Math.atan2(prof[i].y-prof[i-1].y,prof[i].x-prof[i-1].x)};
    }
  }
  const l=prof[prof.length-1];return{x:l.x,y:l.y,a:0};
}

/* ── 运动学链 ── */
function compute(fe){
  const st=[];
  // 段0跟随路径
  const p0=ptAt(fe-SW/2);
  st.push({cx:p0.x+Math.sin(p0.a)*SH/2,cy:p0.y-Math.cos(p0.a)*SH/2,a:p0.a,ja:0});
  for(let i=1;i<NSeg;i++){
    const prev=st[i-1];
    const des=ptAt(fe-SW/2-i*UNIT);
    let d=des.a-prev.a;
    while(d>Math.PI)d-=2*Math.PI;while(d<-Math.PI)d+=2*Math.PI;
    d=Math.max(-MB,Math.min(MB,d));
    const sa=prev.a+d;
    const pd={x:Math.cos(prev.a),y:Math.sin(prev.a)};
    const cd={x:Math.cos(sa),y:Math.sin(sa)};
    const pb={x:prev.cx-pd.x*SW/2,y:prev.cy-pd.y*SW/2};
    const gd={x:-(pd.x+cd.x)/2,y:-(pd.y+cd.y)/2};
    const gl=Math.sqrt(gd.x*gd.x+gd.y*gd.y)||1;
    gd.x/=gl;gd.y/=gl;
    const cf={x:pb.x+gd.x*JG,y:pb.y+gd.y*JG};
    st.push({cx:cf.x-cd.x*SW/2,cy:cf.y-cd.y*SW/2,a:sa,ja:d});
  }
  return st;
}

/* ── SVG 工具 ── */
function mk(t,a){const e=document.createElementNS(NS,t);for(const[k,v]of Object.entries(a))e.setAttribute(k,v);return e;}

/* ── 绘制台阶 ── */
function drawStairs(){
  gStairs.innerHTML='';
  const sX=300;
  for(let i=0;i<3;i++){
    const x1=sX+i*stepRun,y1=groundY,x2=x1+stepRun,y2=groundY-(i+1)*stepRise;
    gStairs.appendChild(mk('polygon',{points:`${x1},${y1} ${x2},${y1} ${x2},${y2} ${x1},${y2}`,fill:'url(#stG)',stroke:'#253550','stroke-width':1}));
    // 横线纹理
    for(let yy=Math.min(y1,y2)+12;yy<Math.max(y1,y2);yy+=14){
      gStairs.appendChild(mk('line',{x1:x1+4,x2:x2-4,y1:yy,y2:yy,stroke:'#1a2842','stroke-width':.6}));
    }
    gStairs.appendChild(mk('line',{x1:x1,y1:y1,x2:x1,y2:y2,stroke:'#3a5a80','stroke-width':1.8}));
    gStairs.appendChild(mk('line',{x1:x1,y1:y2,x2:x2,y2:y2,stroke:'#3a5a80','stroke-width':1.2}));
    // 台阶编号
    const tx=mk('text',{x:(x1+x2)/2,y:y2-8,'text-anchor':'middle','font-family':'Orbitron,sans-serif','font-size':'9',fill:'#2e4a68',opacity:.7});
    tx.textContent='S'+(i+1);gStairs.appendChild(tx);
  }
  // 地面线
  gStairs.appendChild(mk('line',{x1:20,y1:groundY,x2:sX,y2:groundY,stroke:'#3a5a80','stroke-width':1.5}));
  const topY=groundY-3*stepRise;
  gStairs.appendChild(mk('line',{x1:sX+2*stepRun,y1:topY,x2:800,y2:topY,stroke:'#3a5a80','stroke-width':1.5}));
  // 高度标注
  const dimX=sX-18;
  for(let i=0;i<3;i++){
    const yt=groundY-(i+1)*stepRise,yb=groundY-i*stepRise;
    gStairs.appendChild(mk('line',{x1:dimX,y1:yt,x2:dimX,y2:yb,stroke:'#4a6a90','stroke-width':.8,'marker-start':'none','marker-end':'none'}));
    gStairs.appendChild(mk('line',{x1:dimX-3,y1:yt,x2:dimX+3,y2:yt,stroke:'#4a6a90','stroke-width':.8}));
    gStairs.appendChild(mk('line',{x1:dimX-3,y1:yb,x2:dimX+3,y2:yb,stroke:'#4a6a90','stroke-width':.8}));
    const dt=mk('text',{x:dimX-4,y:(yt+yb)/2+3,'text-anchor':'end','font-family':'Orbitron,sans-serif','font-size':'7',fill:'#5a8aaa'});
    dt.textContent=stepRise;gStairs.appendChild(dt);
  }
}

/* ── 角落计算 ── */
function corners(s){
  const c=Math.cos(s.a),sn=Math.sin(s.a),hw=SW/2,hh=SH/2;
  return[
    {x:s.cx+c*hw-sn*hh,y:s.cy+sn*hw+c*hh},   // 前下
    {x:s.cx+c*hw+sn*hh,y:s.cy+sn*hw-c*hh},   // 前上
    {x:s.cx-c*hw+sn*hh,y:s.cy-sn*hw-c*hh},   // 后上
    {x:s.cx-c*hw-sn*hh,y:s.cy-sn*hw+c*hh},   // 后下
  ];
}

/* ── 弧线点 ── */
function arcPts(cx,cy,r,sa,ea,n){
  const p=[];for(let i=0;i<=n;i++){const a=sa+(i/n)*(ea-sa);p.push({x:cx+r*Math.cos(a),y:cy+r*Math.sin(a)});}return p;
}

/* ── 绘制机器人 ── */
function drawRobot(st,fe){
  gShadow.innerHTML='';gTrack.innerHTML='';gSegs.innerHTML='';gJoints.innerHTML='';gFx.innerHTML='';

  // 投影
  for(let i=0;i<NSeg;i++){
    const s=st[i],cn=corners(s);
    const sp=cn.map(p=>`${p.x+3},${p.y+5}`).join(' ');
    gShadow.appendChild(mk('polygon',{points:sp,fill:'rgba(0,0,0,.18)',filter:'url(#gl)'}));
  }

  // 履带轮廓路径
  const allC=st.map(s=>corners(s));
  let d='';
  // 底边 前→后
  d+=`M${allC[0][0].x},${allC[0][0].y} `;
  for(let i=0;i<NSeg;i++){d+=`L${allC[i][3].x},${allC[i][3].y} `;if(i<NSeg-1)d+=`L${allC[i+1][0].x},${allC[i+1][0].y} `;}
  // 后盖弧
  const ls=st[NSeg-1];
  const bcx=ls.cx-Math.cos(ls.a)*SW/2,bcy=ls.cy-Math.sin(ls.a)*SW/2;
  const bap=arcPts(bcx,bcy,SH/2,ls.a+Math.PI/2,ls.a+3*Math.PI/2,10);
  for(const p of bap)d+=`L${p.x},${p.y} `;
  // 顶边 后→前
  for(let i=NSeg-1;i>=0;i--){d+=`L${allC[i][2].x},${allC[i][2].y} `;if(i>0)d+=`L${allC[i-1][1].x},${allC[i-1][1].y} `;}
  // 前盖弧
  const fs=st[0];
  const fcx=fs.cx+Math.cos(fs.a)*SW/2,fcy=fs.cy+Math.sin(fs.a)*SW/2;
  const fap=arcPts(fcx,fcy,SH/2,fs.a-Math.PI/2,fs.a+Math.PI/2,10);
  for(const p of fap)d+=`L${p.x},${p.y} `;
  d+='Z';

  // 履带底色
  gTrack.appendChild(mk('path',{d,fill:'none',stroke:'#c85520','stroke-width':TT,'stroke-linejoin':'round','stroke-linecap':'round',opacity:.75}));
  // 履带花纹
  gTrack.appendChild(mk('path',{d,fill:'none',stroke:'#ff8844','stroke-width':TT-2.5,'stroke-dasharray':'4 5.5','stroke-dashoffset':-fe*1.4,'stroke-linejoin':'round','stroke-linecap':'round',opacity:.45}));
  // 履带高光
  gTrack.appendChild(mk('path',{d,fill:'none',stroke:'#ffaa66','stroke-width':1.2,'stroke-dasharray':'2 9','stroke-dashoffset':-fe*1.4+3,'stroke-linejoin':'round','stroke-linecap':'round',opacity:.3}));

  // 舱段
  for(let i=0;i<NSeg;i++){
    const s=st[i],cn=allC[i];
    const pts=cn.map(p=>`${p.x},${p.y}`).join(' ');
    gSegs.appendChild(mk('polygon',{points:pts,fill:'#0b2530',stroke:'#00c8aa','stroke-width':1.4,opacity:.92}));
    // 电机轮
    gSegs.appendChild(mk('circle',{cx:s.cx,cy:s.cy,r:6.5,fill:'none',stroke:'#00e5c8','stroke-width':.9,opacity:.6}));
    gSegs.appendChild(mk('circle',{cx:s.cx,cy:s.cy,r:2.8,fill:'#00e5c8',opacity:.85}));
    // 舱段编号
    const nt=mk('text',{x:s.cx,y:s.cy+3.5,'text-anchor':'middle','font-family':'Orbitron,sans-serif','font-size':'6',fill:'#005544',opacity:.7,'font-weight':'700'});
    nt.textContent=i+1;gSegs.appendChild(nt);
  }

  // 关节
  for(let i=0;i<NSeg-1;i++){
    const s1=st[i],s2=st[i+1];
    const b1={x:s1.cx-Math.cos(s1.a)*SW/2,y:s1.cy-Math.sin(s1.a)*SW/2};
    const f2={x:s2.cx+Math.cos(s2.a)*SW/2,y:s2.cy+Math.sin(s2.a)*SW/2};
    const jx=(b1.x+f2.x)/2,jy=(b1.y+f2.y)/2;
    const ad=Math.abs(s2.ja)*180/Math.PI;
    const active=ad>5;
    // 连接线
    gJoints.appendChild(mk('line',{x1:b1.x,y1:b1.y,x2:f2.x,y2:f2.y,stroke:active?'#ffd700':'#1a3a4a','stroke-width':active?1.5:.8,opacity:active?.7:.4}));
    // 关节点
    gJoints.appendChild(mk('circle',{cx:jx,cy:jy,r:active?4.5:3,fill:active?'#ffd700':'#15303e',stroke:active?'#ffaa00':'#254050','stroke-width':active?1.8:1,filter:active?'url(#gl)':'none'}));
    // 角度标注
    if(ad>8){
      const at=mk('text',{x:jx,y:jy-10,'text-anchor':'middle','font-family':'Orbitron,sans-serif','font-size':'8.5',fill:'#ffd700',filter:'url(#gl)',opacity:.95});
      at.textContent=Math.round(ad)+'°';gJoints.appendChild(at);
    }
  }

  // 接触力效果
  for(let i=0;i<NSeg;i++){
    const s=st[i];
    // 检测是否在台阶立面附近
    const faceXs=[300,300+stepRun,300+2*stepRun];
    for(const fx of faceXs){
      if(Math.abs(s.cx-fx)<SW*.7&&Math.abs(s.a)>0.15){
        const fy1=groundY,fy2=groundY-stepRise;
        // 力箭头
        const al=22;
        const ax=fx-4,ay=Math.max(fy2,Math.min(fy1,s.cy));
        const ex=ax-al*Math.sin(s.a)*.6,ey=ay+al*Math.cos(s.a)*.6;
        gFx.appendChild(mk('line',{x1:ax,y1:ay,x2:ex,y2:ey,stroke:'#ff3366','stroke-width':2,opacity:.7}));
        // 箭头头部
        const hd=5,ha=.45;
        gFx.appendChild(mk('polygon',{points:`${ex},${ey} ${ex+hd*Math.cos(s.a+ha+Math.PI/2)},${ey+hd*Math.sin(s.a+ha+Math.PI/2)} ${ex+hd*Math.cos(s.a-ha+Math.PI/2)},${ey+hd*Math.sin(s.a-ha+Math.PI/2)}`,fill:'#ff3366',opacity:.7}));
        // 接触火花
        for(let k=0;k<3;k++){
          const ox=ax+4+Math.random()*8,oy=ay-4+Math.random()*8;
          gFx.appendChild(mk('circle',{cx:ox,cy:oy,r:1+Math.random(),fill:'#ff6644',opacity:.3+Math.random()*.4}));
        }
      }
    }
  }
}

/* ── 标注 ── */
function drawAnn(st,fe){
  gAnn.innerHTML='';
  const tl=totalLen();
  // 阶段标签
  let phase='';
  if(fe<280)phase='平地行进 — 舱段共面';
  else if(fe<tl-200)phase='自适应攀爬 — 被动折叠';
  else phase='恢复平直 — 自重复位';
  const pt=mk('text',{x:410,y:452,'text-anchor':'middle','font-family':'Noto Sans SC,sans-serif','font-size':'12.5',fill:'#00e5c8',opacity:.7,'font-weight':'400'});
  pt.textContent='▸ '+phase;gAnn.appendChild(pt);

  // IFR 标注(攀爬阶段显示)
  if(fe>310&&fe<tl-180){
    const op=Math.min(1,(fe-310)/60)*Math.min(1,(tl-180-fe)/60)*.85;
    // 连接线
    const tgt=st[0];
    const lx=Math.min(tgt.cx+40,620),ly=Math.max(tgt.cy-50,60);
    gAnn.appendChild(mk('line',{x1:tgt.cx,y1:tgt.cy-14,x2:lx,y2:ly+12,stroke:'#ffd700','stroke-width':.7,'stroke-dasharray':'3 3',opacity:op*.6}));
    const t1=mk('text',{x:lx+4,y:ly,'text-anchor':'start','font-family':'Noto Sans SC,sans-serif','font-size':'10.5',fill:'#ffd700',opacity:op,'font-weight':'700'});
    t1.textContent='IFR:接触力 = 控制信号';gAnn.appendChild(t1);
    const t2=mk('text',{x:lx+4,y:ly+15,'text-anchor':'start','font-family':'Noto Sans SC,sans-serif','font-size':'9',fill:'#aa9040',opacity:op*.8});
    t2.textContent='地形塑造形态 → 形态适配地形';gAnn.appendChild(t2);
  }

  // 履带驱动方向标注
  if(fe>290&&fe<tl-100){
    const op2=Math.min(1,(fe-290)/50)*.55;
    // 在履带上方画运动箭头
    const midSeg=st[Math.floor(NSeg/2)];
    const arrX=midSeg.cx,arrY=midSeg.cy-SH/2-TT-6;
    const ad=midSeg.a;
    gAnn.appendChild(mk('line',{x1:arrX-12*Math.cos(ad),y1:arrY-12*Math.sin(ad),x2:arrX+12*Math.cos(ad),y2:arrY+12*Math.sin(ad),stroke:'#ff6b35','stroke-width':1.3,opacity:op2,'marker-end':'none'}));
    const at=mk('text',{x:arrX,y:arrY-6,'text-anchor':'middle','font-family':'Orbitron,sans-serif','font-size':'7',fill:'#ff6b35',opacity:op2});
    at.textContent='TRACK DRIVE';gAnn.appendChild(at);
  }

  // 进度条
  const pbW=180,pbX=620,pbY=450,pbH=4;
  gAnn.appendChild(mk('rect',{x:pbX,y:pbY,width:pbW,height:pbH,rx:2,fill:'#1a2540',opacity:.5}));
  const pct=Math.max(0,Math.min(1,(fe-RL)/(tl-RL)));
  gAnn.appendChild(mk('rect',{x:pbX,y:pbY,width:pbW*pct,height:pbH,rx:2,fill:'#00e5c8',opacity:.6}));
}

/* ── 动画循环 ── */
function animate(ts){
  if(!lastT)lastT=ts;
  const dt=(ts-lastT)/1000;
  lastT=ts;

  if(paused){
    pauseT-=dt;
    if(pauseT<=0)paused=false;
  }else{
    progress+=dt*55*speed;
    if(progress>totalLen()+60){
      progress=RL+20;
      paused=true;pauseT=.6;
    }
  }

  const fe=progress;
  const st=compute(fe);
  drawRobot(st,fe);
  drawAnn(st,fe);

  requestAnimationFrame(animate);
}

/* ── 控件 ── */
const rSpeed=el('rSpeed'),vSpeed=el('vSpeed'),rStep=el('rStep'),vStep=el('vStep'),bReset=el('bReset');
rSpeed.oninput=()=>{speed=parseFloat(rSpeed.value);vSpeed.textContent=speed.toFixed(1)+'x';};
rStep.oninput=()=>{stepRise=parseInt(rStep.value);vStep.textContent=stepRise;buildProf();drawStairs();};
bReset.onclick=()=>{progress=RL+20;lastT=null;paused=false;};

/* ── 初始化 ── */
buildProf();drawStairs();
progress=RL+20;

function start(){requestAnimationFrame(animate);}
if(document.readyState==='complete')start();
else window.addEventListener('load',start);
document.addEventListener('DOMContentLoaded',()=>{if(!lastT)start();});
})();
</script>
</body>
</html>

实现说明:

  1. 核心原理动画化:采用正向运动学链(Forward Kinematic Chain)模拟 5 段铰接底盘的被动折叠过程。首段沿地形路径行走,后续各段受 45° 关节限位约束逐级跟随弯曲,自然呈现"毛毛虫式"攀爬形态。

  2. IFR 思想视觉引导

    • 接触力即控制信号:当舱段接触台阶立面时,自动显示红色力箭头与接触火花,金色标注 "IFR:接触力 = 控制信号" 在攀爬阶段淡入,强调无需主动传感的自适应机制。
    • 关节激活可视化:弯曲超过 5° 的关节以金色发光高亮,并实时显示偏转角度数值,直观展示"地形塑造形态"的过程。
  3. 履带动画:通过 stroke-dashoffset 随进度变量连续偏移,模拟履带花纹的滚动效果;三层叠绘(底色 + 花纹 + 高光)营造厚度与质感。

  4. 交互控制

    • 速度滑块(0.2x–3x):调节攀爬动画速率
    • 台阶高度滑块(30–80):动态重建台阶几何,实时观察不同高度的适应性
    • 重置按钮:重新从平地起步
  5. 自动播放:页面加载后立即启动 requestAnimationFrame 循环,循环结束自动重置并短暂停顿后重播,确保"重开即播"。

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