分享图
动画工坊
引擎就绪
<!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 rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=Syne:wght@400;700;800&family=DM+Mono:wght@300;400;500&display=swap" rel="stylesheet">
<style>
:root{--bg:#070b12;--fg:#dce4f0;--muted:#4e5d74;--accent:#00e5a0;--accent2:#ff6b35;--card:#0e1420;--border:#1a2538;--track:#12182a;--tread:#253352;--body:#0b5e5e;--wheel:#0a0f1a}
*{margin:0;padding:0;box-sizing:border-box}
body{background:var(--bg);color:var(--fg);font-family:'DM Mono',monospace;min-height:100vh;display:flex;flex-direction:column;align-items:center;overflow-x:hidden}
.wrap{max-width:1080px;width:100%;padding:1.8rem 1.2rem 2.5rem}
header{text-align:center;margin-bottom:1.4rem}
h1{font-family:'Syne',sans-serif;font-weight:800;font-size:clamp(1.3rem,3.5vw,2.1rem);letter-spacing:-.02em;color:var(--fg);line-height:1.2}
h1 em{font-style:normal;color:var(--accent)}
.sub{font-size:.78rem;color:var(--muted);margin-top:.45rem;letter-spacing:.06em}
.scene{width:100%;background:var(--card);border:1px solid var(--border);border-radius:10px;overflow:hidden;position:relative}
.scene svg{width:100%;height:auto;display:block}
.controls{display:flex;gap:1.6rem;margin-top:1.3rem;flex-wrap:wrap;justify-content:center;align-items:flex-end}
.cg{display:flex;flex-direction:column;align-items:center;gap:.35rem}
.cg label{font-size:.68rem;color:var(--muted);text-transform:uppercase;letter-spacing:.1em}
input[type=range]{-webkit-appearance:none;width:150px;height:4px;background:var(--border);border-radius:2px;outline:none}
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 var(--accent)}
.btn{background:transparent;border:1px solid var(--accent2);color:var(--accent2);padding:.42rem 1.3rem;border-radius:5px;cursor:pointer;font-family:'DM Mono',monospace;font-size:.75rem;transition:all .2s}
.btn:hover{background:var(--accent2);color:var(--bg)}
.cards{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:.9rem;margin-top:1.4rem}
.card{background:var(--card);border:1px solid var(--border);border-radius:7px;padding:.9rem 1rem}
.card h3{font-family:'Syne',sans-serif;font-size:.82rem;color:var(--accent);margin-bottom:.3rem}
.card p{font-size:.7rem;color:var(--muted);line-height:1.65}
</style>
</head>
<body>
<div class="wrap">
  <header>
    <h1>柔性铰接底盘 · <em>自适应攀爬</em></h1>
    <p class="sub">多段万向节串联 + 独立轮毂电机 · 被动贴合不规则台阶轮廓</p>
  </header>
  <div class="scene">
    <svg id="svg" viewBox="0 0 1000 530" xmlns="http://www.w3.org/2000/svg"></svg>
  </div>
  <div class="controls">
    <div class="cg"><label>台阶不均匀度</label><input type="range" id="slIrreg" min="0" max="50" value="22"></div>
    <div class="cg"><label>动画速度</label><input type="range" id="slSpeed" min="0.2" max="2.5" value="1" step="0.1"></div>
    <div class="cg"><button class="btn" id="btnReset">重置动画</button></div>
  </div>
  <div class="cards">
    <div class="card"><h3>IFR:形态随地形重塑</h3><p>底盘从固定刚性变为被动贴合,以"变形"消解"刚性结构"与"不规则几何"之间的矛盾,无需额外感知与规划即可自适应。</p></div>
    <div class="card"><h3>资源利用:重力 + 阻力</h3><p>台阶立面阻力使舱段被动折叠抬起,跨越后重力使其恢复平直——零额外能耗自适应,巧妙利用场中已有资源。</p></div>
    <div class="card"><h3>核心创新:柔性铰接 + 差速</h3><p>45°最大弯折角配合独立电机差速协同,使整车如毛毛虫般沿轮廓蠕动,包络任意不规则台阶几何。</p></div>
  </div>
</div>

<script>
(function(){
/* ===== 配置 ===== */
const NS='http://www.w3.org/2000/svg';
const SEG=5, WHEELS=SEG+1, SEGLEN=62, WR=11;
const GY=440, SX=260, TREAD=115, BASER=78;
const TRACK_SW=WR*2+6; // 履带描边宽度

/* ===== 状态 ===== */
let irreg=22, spd=1, prog=0, lastT=null;
let profile=[], arcLen=[];

const svg=document.getElementById('svg');

/* ===== 工具函数 ===== */
function el(tag,a){const e=document.createElementNS(NS,tag);for(const k in a)e.setAttribute(k,a[k]);return e}
function grp(a){return el('g',a||{})}

/* ===== 台阶轮廓 ===== */
function buildProfile(ir){
  const h=[BASER, BASER+ir*.55, BASER-ir*.3, BASER+ir*.2];
  const p=[];let y=GY;
  p.push({x:-150,y});p.push({x:SX,y});
  for(let i=0;i<h.length;i++){y-=h[i];p.push({x:SX+i*TREAD,y});p.push({x:SX+(i+1)*TREAD,y})}
  p.push({x:1150,y});return p;
}
function compArcLen(p){const l=[0];for(let i=1;i<p.length;i++){const dx=p[i].x-p[i-1].x,dy=p[i].y-p[i-1].y;l.push(l[i-1]+Math.sqrt(dx*dx+dy*dy))}return l}
function posAt(d){
  const tot=arcLen[arcLen.length-1];d=Math.max(0,Math.min(d,tot));
  for(let i=1;i<arcLen.length;i++){if(d<=arcLen[i]){const t=(d-arcLen[i-1])/(arcLen[i]-arcLen[i-1]);
    return{x:profile[i-1].x+t*(profile[i].x-profile[i-1].x),y:profile[i-1].y+t*(profile[i].y-profile[i-1].y)}}}
  const l=profile[profile.length-1];return{x:l.x,y:l.y}
}
function getWheels(fd){const w=[];for(let i=0;i<WHEELS;i++)w.push(posAt(fd-i*SEGLEN));return w}

/* ===== SVG 元素 ===== */
let gBg,gStair,gStepLabels,gRobot,gTrack,gTrackTread,gBodySegs=[],gWheels=[],gHinges=[],gSensors=[],gAngleArcs=[],gAngleLbls=[],gForceArrows=[],gPhase,gWrapGlow=[];
let stairPath,stairEdge;

function createElements(){
  // defs
  const defs=el('defs');
  // 网格图案
  const pat=el('pattern',{id:'grid',width:40,height:40,patternUnits:'userSpaceOnUse'});
  pat.appendChild(el('path',{d:'M 40 0 L 0 0 0 40',fill:'none',stroke:'#131c2e','stroke-width':.5}));
  defs.appendChild(pat);
  // 发光滤镜
  const f1=el('filter',{id:'glow',x:'-50%',y:'-50%',width:'200%',height:'200%'});
  f1.innerHTML='<feGaussianBlur stdDeviation="4" result="b"/><feMerge><feMergeNode in="b"/><feMergeNode in="SourceGraphic"/></feMerge>';
  defs.appendChild(f1);
  const f2=el('filter',{id:'glowBig',x:'-80%',y:'-80%',width:'260%',height:'260%'});
  f2.innerHTML='<feGaussianBlur stdDeviation="8" result="b"/><feMerge><feMergeNode in="b"/><feMergeNode in="SourceGraphic"/></feMerge>';
  defs.appendChild(f2);
  const f3=el('filter',{id:'glowSoft',x:'-100%',y:'-100%',width:'300%',height:'300%'});
  f3.innerHTML='<feGaussianBlur stdDeviation="12" result="b"/><feMerge><feMergeNode in="b"/><feMergeNode in="SourceGraphic"/></feMerge>';
  defs.appendChild(f3);
  // 箭头标记
  const mk=el('marker',{id:'arrO',viewBox:'0 0 10 10',refX:10,refY:5,markerWidth:7,markerHeight:7,orient:'auto-start-reverse'});
  mk.appendChild(el('path',{d:'M 0 1 L 10 5 L 0 9 Z',fill:'#ff6b35'}));
  defs.appendChild(mk);
  const mkG=el('marker',{id:'arrG',viewBox:'0 0 10 10',refX:10,refY:5,markerWidth:6,markerHeight:6,orient:'auto-start-reverse'});
  mkG.appendChild(el('path',{d:'M 0 1 L 10 5 L 0 9 Z',fill:'#00e5a0'}));
  defs.appendChild(mkG);
  svg.appendChild(defs);

  // 背景
  gBg=grp();svg.appendChild(gBg);
  gBg.appendChild(el('rect',{x:0,y:0,width:1000,height:530,fill:'#080c14'}));
  gBg.appendChild(el('rect',{x:0,y:0,width:1000,height:530,fill:'url(#grid)',opacity:.5}));

  // 台阶
  gStair=grp();svg.appendChild(gStair);
  stairPath=el('path',{fill:'#111928',stroke:'none'});
  gStair.appendChild(stairPath);
  stairEdge=el('path',{fill:'none',stroke:'#1e3050','stroke-width':1.5});
  gStair.appendChild(stairEdge);

  // 台阶高度标签
  gStepLabels=grp();svg.appendChild(gStepLabels);

  // 机器人
  gRobot=grp();svg.appendChild(gRobot);
  // 履带发光底层
  const trackGlow=el('path',{fill:'none',stroke:'#00e5a0','stroke-width':TRACK_SW+10,'stroke-linejoin':'round','stroke-linecap':'round',opacity:0,filter:'url(#glowSoft)'});
  trackGlow.id='trackGlow';
  gRobot.appendChild(trackGlow);
  // 履带主体
  gTrack=el('path',{fill:'none',stroke:'#151d30','stroke-width':TRACK_SW,'stroke-linejoin':'round','stroke-linecap':'round'});
  gRobot.appendChild(gTrack);
  // 履带花纹
  gTrackTread=el('path',{fill:'none',stroke:'#253352','stroke-width':TRACK_SW-2,'stroke-linejoin':'round','stroke-linecap':'round','stroke-dasharray':'3.5 7',opacity:.7});
  gRobot.appendChild(gTrackTread);
  // 防滑筋高亮
  const treadHi=el('path',{fill:'none',stroke:'#2e4468','stroke-width':TRACK_SW-6,'stroke-linejoin':'round','stroke-linecap':'round','stroke-dasharray':'1 9.5','stroke-dashoffset':'3',opacity:.5});
  treadHi.id='treadHi';
  gRobot.appendChild(treadHi);

  // 车体段
  for(let i=0;i<SEG;i++){const s=el('rect',{rx:3,ry:3,fill:'#0b4e4e',stroke:'#0d6e6e','stroke-width':.8,opacity:.85});gBodySegs.push(s);gRobot.appendChild(s)}
  // 轮子
  for(let i=0;i<WHEELS;i++){const w=el('circle',{r:WR,fill:'#0a0f1a',stroke:'#1a2a40','stroke-width':1.5});gWheels.push(w);gRobot.appendChild(w)}
  // 铰接关节
  for(let i=1;i<WHEELS-1;i++){const h=el('circle',{r:5,fill:'#0d1117',stroke:'#00e5a0','stroke-width':1.2,opacity:.6});gHinges.push(h);gRobot.appendChild(h)}
  // 传感器点
  for(let i=1;i<WHEELS-1;i++){const s=el('circle',{r:2.5,fill:'#00e5a0',opacity:.3,filter:'url(#glow)'});gSensors.push(s);gRobot.appendChild(s)}
  // 角度弧线
  for(let i=0;i<SEG-1;i++){const a=el('path',{fill:'none',stroke:'#00e5a0','stroke-width':1.2,opacity:0});gAngleArcs.push(a);gRobot.appendChild(a)}
  // 角度标签
  for(let i=0;i<SEG-1;i++){const t=el('text',{'font-size':'9','font-family':'DM Mono, monospace',fill:'#00e5a0',opacity:0,'text-anchor':'middle'});gAngleLbls.push(t);gRobot.appendChild(t)}

  // 力箭头组
  gForceArrows=grp();svg.appendChild(gForceArrows);

  // 台阶边缘包裹高亮
  for(let i=0;i<4;i++){const w=el('circle',{r:6,fill:'none',stroke:'#ff6b35','stroke-width':2,opacity:0,filter:'url(#glowBig)'});gWrapGlow.push(w);svg.appendChild(w)}

  // 阶段文字
  gPhase=el('text',{x:500,y:28,'font-size':'12','font-family':'Syne, sans-serif','font-weight':700,fill:'#4e5d74','text-anchor':'middle','letter-spacing':'.08em'});
  svg.appendChild(gPhase);

  // IFR标注
  const ifr=el('text',{x:18,y:518,'font-size':'9','font-family':'DM Mono, monospace',fill:'#1e3050','letter-spacing':'.05em'});
  ifr.textContent='IFR: 形态被动重塑 → 矛盾自消解';
  svg.appendChild(ifr);
}

/* ===== 更新台阶视觉 ===== */
function updateStairs(){
  // 填充形状
  let d='M '+profile[0].x+' '+(GY+90);
  for(const p of profile)d+=' L '+p.x+' '+p.y;
  d+=' L '+profile[profile.length-1].x+' '+(GY+90)+' Z';
  stairPath.setAttribute('d',d);
  // 边缘线
  let e='M '+profile[0].x+' '+profile[0].y;
  for(let i=1;i<profile.length;i++)e+=' L '+profile[i].x+' '+profile[i].y;
  stairEdge.setAttribute('d',e);
  // 高度标签
  while(gStepLabels.firstChild)gStepLabels.removeChild(gStepLabels.firstChild);
  const hts=[BASER, BASER+irreg*.55, BASER-irreg*.3, BASER+irreg*.2];
  let cy=GY;
  for(let i=0;i<hts.length;i++){
    const x=SX+i*TREAD+TREAD/2;
    const my=cy-hts[i]/2;
    const t=el('text',{x:x,y:my+3,'font-size':'9','font-family':'DM Mono, monospace',fill:'#2e4468','text-anchor':'middle'});
    t.textContent=Math.round(hts[i])+'mm';
    gStepLabels.appendChild(t);
    cy-=hts[i];
  }
}

/* ===== 动画帧 ===== */
function animate(ts){
  if(!lastT)lastT=ts;
  const dt=(ts-lastT)/1000;lastT=ts;
  prog+=dt*spd*0.045;
  if(prog>1)prog=0;

  const totLen=arcLen[arcLen.length-1];
  const startD=80, endD=totLen-120;
  const fd=startD+prog*(endD-startD);
  const wheels=getWheels(fd);

  // 履带路径
  let td='M '+wheels[0].x+' '+wheels[0].y;
  for(let i=1;i<WHEELS;i++)td+=' L '+wheels[i].x+' '+wheels[i].y;
  gTrack.setAttribute('d',td);
  gTrackTread.setAttribute('d',td);
  document.getElementById('treadHi').setAttribute('d',td);
  document.getElementById('trackGlow').setAttribute('d',td);

  // 履带花纹动画
  const tOff=-fd*0.65;
  gTrackTread.setAttribute('stroke-dashoffset',tOff);
  document.getElementById('treadHi').setAttribute('stroke-dashoffset',tOff+3);

  // 车体段
  for(let i=0;i<SEG;i++){
    const a=wheels[i],b=wheels[i+1];
    const dx=b.x-a.x,dy=b.y-a.y;
    const len=Math.max(1,Math.sqrt(dx*dx+dy*dy));
    const ang=Math.atan2(dy,dx)*180/Math.PI;
    const bw=len-WR*2+4, bh=TRACK_SW-12;
    gBodySegs[i].setAttribute('x',-bw/2);
    gBodySegs[i].setAttribute('y',-bh/2);
    gBodySegs[i].setAttribute('width',bw);
    gBodySegs[i].setAttribute('height',bh);
    gBodySegs[i].setAttribute('transform','translate('+(a.x+dx/2)+','+(a.y+dy/2)+') rotate('+ang+')');
  }

  // 轮子
  for(let i=0;i<WHEELS;i++){
    gWheels[i].setAttribute('cx',wheels[i].x);
    gWheels[i].setAttribute('cy',wheels[i].y);
  }

  // 计算弯折角
  const angles=[];
  for(let i=1;i<WHEELS-1;i++){
    const v1x=wheels[i-1].x-wheels[i].x, v1y=wheels[i-1].y-wheels[i].y;
    const v2x=wheels[i+1].x-wheels[i].x, v2y=wheels[i+1].y-wheels[i].y;
    const dot=v1x*v2x+v1y*v2y;
    const m1=Math.sqrt(v1x*v1x+v1y*v1y), m2=Math.sqrt(v2x*v2x+v2y*v2y);
    const cosA=Math.max(-1,Math.min(1,dot/(Math.max(.001,m1*m2))));
    angles.push(Math.acos(cosA)*180/Math.PI);
  }

  // 铰接关节 + 传感器
  for(let i=0;i<angles.length;i++){
    const wi=i+1; // wheel index
    const a=angles[i];
    const active=a>5;
    const intensity=Math.min(1,a/45);
    // 关节
    gHinges[i].setAttribute('cx',wheels[wi].x);
    gHinges[i].setAttribute('cy',wheels[wi].y);
    gHinges[i].setAttribute('opacity',active?.5+intensity*.5:.3);
    gHinges[i].setAttribute('stroke',active?'#ff6b35':'#00e5a0');
    gHinges[i].setAttribute('r',active?5+intensity*2:5);
    // 传感器
    gSensors[i].setAttribute('cx',wheels[wi].x);
    gSensors[i].setAttribute('cy',wheels[wi].y);
    gSensors[i].setAttribute('opacity',active?.3+intensity*.6:.1);
    gSensors[i].setAttribute('fill',active?'#ff6b35':'#00e5a0');
    gSensors[i].setAttribute('r',active?2.5+intensity*1.5:2.5);
  }

  // 角度弧线 + 标签
  for(let i=0;i<angles.length;i++){
    const wi=i+1;
    const a=angles[i];
    const active=a>5;
    const intensity=Math.min(1,a/45);
    if(active){
      const v1x=wheels[i].x-wheels[wi].x, v1y=wheels[i].y-wheels[wi].y;
      const v2x=wheels[i+2].x-wheels[wi].x, v2y=wheels[i+2].y-wheels[wi].y;
      const a1=Math.atan2(v1y,v1x);
      const a2=Math.atan2(v2y,v2x);
      const r=20;
      // 确定弧线方向
      let startA=a1, endA=a2, sweep=1;
      let diff=endA-startA;
      while(diff>Math.PI)diff-=2*Math.PI;
      while(diff<-Math.PI)diff+=2*Math.PI;
      if(diff<0){sweep=0}
      const sx=wheels[wi].x+r*Math.cos(startA), sy=wheels[wi].y+r*Math.sin(startA);
      const ex=wheels[wi].x+r*Math.cos(endA), ey=wheels[wi].y+r*Math.sin(endA);
      gAngleArcs[i].setAttribute('d','M '+sx+' '+sy+' A '+r+' '+r+' 0 0 '+sweep+' '+ex+' '+ey);
      gAngleArcs[i].setAttribute('opacity',.4+intensity*.5);
      gAngleArcs[i].setAttribute('stroke',intensity>.6?'#ff6b35':'#00e5a0');
      // 标签
      const midA=(startA+endA)/2;
      // 修正中点角度
      let mAngle=startA+diff/2;
      const lx=wheels[wi].x+(r+12)*Math.cos(mAngle), ly=wheels[wi].y+(r+12)*Math.sin(mAngle);
      gAngleLbls[i].setAttribute('x',lx);
      gAngleLbls[i].setAttribute('y',ly+3);
      gAngleLbls[i].textContent=Math.round(a)+'°';
      gAngleLbls[i].setAttribute('opacity',.5+intensity*.4);
      gAngleLbls[i].setAttribute('fill',intensity>.6?'#ff6b35':'#00e5a0');
    }else{
      gAngleArcs[i].setAttribute('opacity',0);
      gAngleLbls[i].setAttribute('opacity',0);
    }
  }

  // 力箭头(阻力 & 重力)
  while(gForceArrows.firstChild)gForceArrows.removeChild(gForceArrows.firstChild);
  for(let i=1;i<WHEELS-1;i++){
    const a=angles[i-1];
    if(a>8){
      const wi=wheels[i];
      const v1x=wheels[i-1].x-wi.x, v1y=wheels[i-1].y-wi.y;
      const v2x=wheels[i+1].x-wi.x, v2y=wheels[i+1].y-wi.y;
      const intensity=Math.min(1,a/45);
      // 阻力箭头:从前方舱段指向铰接点(台阶阻力方向)
      const fLen=18+intensity*14;
      const fAng=Math.atan2(v1y,v1x);
      const fx1=wi.x+fLen*Math.cos(fAng), fy1=wi.y+fLen*Math.sin(fAng);
      const arr1=el('line',{x1:fx1,y1:fy1,x2:wi.x,y2:wi.y,stroke:'#ff6b35','stroke-width':1.5,'marker-end':'url(#arrO)',opacity:.3+intensity*.4});
      gForceArrows.appendChild(arr1);
      // 重力箭头:向下
      if(a>15){
        const gLen=14+intensity*10;
        const arr2=el('line',{x1:wi.x,y1:wi.y-gLen,x2:wi.x,y2:wi.y,stroke:'#00e5a0','stroke-width':1.2,'marker-end':'url(#arrG)',opacity:.2+intensity*.3});
        gForceArrows.appendChild(arr2);
      }
    }
  }

  // 台阶边缘包裹高亮
  // 找出哪些轮子在台阶棱边附近
  const stepEdges=[];
  let cy=GY;
  for(let i=0;i<4;i++){
    const rh=[BASER, BASER+irreg*.55, BASER-irreg*.3, BASER+irreg*.2];
    stepEdges.push({x:SX+i*TREAD, y:cy-rh[i]});
    cy-=rh[i];
  }
  for(let si=0;si<stepEdges.length;si++){
    const se=stepEdges[si];
    let minD=Infinity;
    for(const w of wheels){const d=Math.sqrt((w.x-se.x)**2+(w.y-se.y)**2);if(d<minD)minD=d}
    const near=minD<SEGLEN*1.2;
    const glow=Math.max(0,1-minD/(SEGLEN*1.2));
    gWrapGlow[si].setAttribute('cx',se.x);
    gWrapGlow[si].setAttribute('cy',se.y);
    gWrapGlow[si].setAttribute('opacity',near?glow*.7:0);
    gWrapGlow[si].setAttribute('r',4+glow*5);
  }

  // 履带整体发光
  let maxAngle=0;
  for(const a of angles)if(a>maxAngle)maxAngle=a;
  const climbing=maxAngle>8;
  document.getElementById('trackGlow').setAttribute('opacity',climbing?Math.min(.15,maxAngle/45*.15):0);

  // 阶段文字
  let phase='';
  if(prog<.12)phase='▶ 平地行驶 — 各舱段直线状态';
  else if(prog<.25)phase='▶ 前端触碰台阶 — 传感器反馈,电机增扭';
  else if(prog<.7)phase='▶ 蠕动攀爬 — 舱段被动折叠,履带包裹棱边';
  else if(prog<.88)phase='▶ 跨越顶部 — 重力驱动舱段恢复平直';
  else phase='▶ 顶部平台 — 柔性底盘恢复刚性直线';
  gPhase.textContent=phase;

  requestAnimationFrame(animate);
}

/* ===== 初始化 ===== */
function init(){
  svg.innerHTML='';
  profile=buildProfile(irreg);
  arcLen=compArcLen(profile);
  createElements();
  updateStairs();
  prog=0;lastT=null;
  requestAnimationFrame(animate);
}

/* ===== 事件 ===== */
document.getElementById('slIrreg').addEventListener('input',function(){
  irreg=+this.value;
  profile=buildProfile(irreg);
  arcLen=compArcLen(profile);
  updateStairs();
});
document.getElementById('slSpeed').addEventListener('input',function(){spd=+this.value});
document.getElementById('btnReset').addEventListener('click',function(){
  prog=0;lastT=null;
});

/* ===== 启动 ===== */
document.addEventListener('DOMContentLoaded',init);
})();
</script>
</body>
</html>

实现说明

核心动画机制:定义了一段包含4级不规则台阶的折线路径作为楼梯轮廓,机器人的6个轮心以固定间距(62px)沿此路径依次排布。当前端轮心随时间沿路径推进时,后续轮心自动跟随,从而在台阶棱边处产生舱段间的弯折角——完全模拟"被动贴合"的物理过程。

IFR 视觉引导

  • 铰接关节在弯折时由绿转橙并发光膨胀,直观展示"阻力驱动折叠"
  • 角度弧线与度数标签实时显示各关节弯折角,接近45°极限时颜色加重
  • 力箭头:橙色箭头表示台阶阻力方向,绿色箭头表示重力恢复方向
  • 台阶棱边高亮环:当履带包裹棱边时发出橙色光晕,突出"资源利用"核心创新
  • 底部阶段文字实时解说当前动作时序

交互控制

  • 「台阶不均匀度」滑块实时改变各级台阶高度差,观察柔性底盘对不规则几何的适应力
  • 「动画速度」滑块可慢放以仔细观察弯折过程
  • 「重置动画」按钮回到初始状态重新播放
积分规则:第一轮对话扣减8分,后续每轮扣6分