独立渲染引擎就绪引擎就绪
<!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分
等待动画代码生成...
