独立渲染引擎就绪就绪
<!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 href="https://fonts.googleapis.com/css2?family=Orbitron:wght@400;700;900&family=Chakra+Petch:wght@300;400;500;600;700&display=swap" rel="stylesheet">
<style>
*{margin:0;padding:0;box-sizing:border-box}
body{background:#060b18;color:#e2e8f0;font-family:'Chakra Petch',sans-serif;height:100vh;display:flex;flex-direction:column;overflow:hidden}
.hdr{padding:10px 20px;display:flex;align-items:baseline;gap:14px;border-bottom:1px solid rgba(6,182,212,.15);background:linear-gradient(180deg,rgba(12,20,37,.95),rgba(6,11,24,.7))}
.hdr h1{font-family:'Orbitron',monospace;font-size:15px;font-weight:700;letter-spacing:2px;color:#22d3ee;text-transform:uppercase}
.hdr .sub{font-size:12px;color:#94a3b8;font-weight:300}
.hdr .badge{margin-left:auto;padding:3px 10px;border:1px solid #f59e0b;color:#f59e0b;font-size:10px;font-weight:600;letter-spacing:1px;border-radius:2px;font-family:'Orbitron',monospace}
.mc{flex:1;display:flex;justify-content:center;align-items:center;padding:4px 8px;min-height:0}
#mainSvg{width:100%;height:100%;max-height:calc(100vh - 110px)}
.ctrl{padding:8px 20px;display:flex;align-items:center;gap:20px;border-top:1px solid rgba(6,182,212,.12);background:#0c1425;flex-wrap:wrap}
.cg{display:flex;align-items:center;gap:6px}
.cg label{font-size:10px;color:#94a3b8;letter-spacing:.5px;white-space:nowrap}
.cg input[type=range]{width:90px;accent-color:#06b6d4;height:4px}
.cg .val{font-size:11px;color:#22d3ee;min-width:32px;font-weight:600}
.btn{padding:4px 14px;background:transparent;border:1px solid #06b6d4;color:#06b6d4;font-family:'Chakra Petch',sans-serif;font-size:11px;cursor:pointer;letter-spacing:1px;transition:all .2s;border-radius:2px}
.btn:hover{background:rgba(6,182,212,.12)}
.btn.active{background:#06b6d4;color:#060b18;font-weight:600}
@media(prefers-reduced-motion:reduce){*{animation-duration:0s!important;transition-duration:0s!important}}
</style>
</head>
<body>
<header class="hdr">
<h1>Serpentine Propulsion</h1>
<span class="sub">万向节+微型电机串联 · 仿生单向摩擦鳞片</span>
<span class="badge">IFR</span>
</header>
<main class="mc">
<svg id="mainSvg" viewBox="0 0 1400 800" preserveAspectRatio="xMidYMid meet">
<defs>
<filter id="glowG" x="-80%" y="-80%" width="260%" height="260%">
<feGaussianBlur in="SourceGraphic" stdDeviation="4" result="b"/><feMerge><feMergeNode in="b"/><feMergeNode in="SourceGraphic"/></feMerge>
</filter>
<filter id="glowC" x="-80%" y="-80%" width="260%" height="260%">
<feGaussianBlur in="SourceGraphic" stdDeviation="3" result="b"/><feMerge><feMergeNode in="b"/><feMergeNode in="SourceGraphic"/></feMerge>
</filter>
<filter id="glowO" x="-60%" y="-60%" width="220%" height="220%">
<feGaussianBlur in="SourceGraphic" stdDeviation="2.5" result="b"/><feMerge><feMergeNode in="b"/><feMergeNode in="SourceGraphic"/></feMerge>
</filter>
<linearGradient id="modG" x1="0" y1="0" x2="0" y2="1"><stop offset="0%" stop-color="#4b5563"/><stop offset="100%" stop-color="#2d3748"/></linearGradient>
<linearGradient id="headG" x1="0" y1="0" x2="1" y2="0"><stop offset="0%" stop-color="#4b5563"/><stop offset="100%" stop-color="#718096"/></linearGradient>
<marker id="arrO" markerWidth="7" markerHeight="5" refX="7" refY="2.5" orient="auto"><polygon points="0 0,7 2.5,0 5" fill="#f97316"/></marker>
<marker id="arrCy" markerWidth="8" markerHeight="6" refX="8" refY="3" orient="auto"><polygon points="0 0,8 3,0 6" fill="#22d3ee"/></marker>
<pattern id="gridP" width="60" height="60" patternUnits="userSpaceOnUse">
<line x1="0" y1="0" x2="0" y2="60" stroke="rgba(20,50,90,.22)" stroke-width=".5"/>
<line x1="0" y1="0" x2="60" y2="0" stroke="rgba(20,50,90,.15)" stroke-width=".5"/>
</pattern>
<linearGradient id="trailG" x1="0" y1="0" x2="1" y2="0">
<stop offset="0%" stop-color="#22d3ee" stop-opacity="0"/>
<stop offset="100%" stop-color="#22d3ee" stop-opacity=".5"/>
</linearGradient>
</defs>
<!-- 背景 -->
<rect width="1400" height="800" fill="#080d1c"/>
<rect id="gridBg" width="1400" height="480" fill="url(#gridP)"/>
<!-- 分割线 -->
<line x1="0" y1="488" x2="1400" y2="488" stroke="rgba(6,182,212,.25)" stroke-width="1"/>
<line x1="0" y1="488" x2="1400" y2="488" stroke="rgba(6,182,212,.08)" stroke-width="4"/>
<!-- 信号图区域标签 -->
<text x="50" y="520" fill="#94a3b8" font-size="11" font-family="Orbitron,monospace" letter-spacing="1">PHASE SIGNAL</text>
<text x="760" y="520" fill="#94a3b8" font-size="11" font-family="Orbitron,monospace" letter-spacing="1">FRICTION SCALE MECHANISM</text>
<!-- 动态图层 -->
<g id="groundG"></g>
<g id="trailG_"></g>
<g id="snakeG"></g>
<g id="forceG"></g>
<g id="annoG"></g>
<g id="sigG"></g>
<g id="scaleG"></g>
<g id="velG"></g>
</svg>
</main>
<div class="ctrl">
<div class="cg"><label>振幅</label><input type="range" id="rAmp" min="0.1" max="0.6" step="0.01" value="0.35"><span class="val" id="vAmp">0.35</span></div>
<div class="cg"><label>频率</label><input type="range" id="rFreq" min="0.8" max="5" step="0.1" value="2.5"><span class="val" id="vFreq">2.5</span></div>
<div class="cg"><label>相位差</label><input type="range" id="rPhase" min="0.3" max="1.2" step="0.05" value="0.65"><span class="val" id="vPhase">0.65</span></div>
<div class="cg"><label>模块数</label><input type="range" id="rMod" min="5" max="14" step="1" value="10"><span class="val" id="vMod">10</span></div>
<button class="btn active" id="btnPlay">PAUSE</button>
<button class="btn" id="btnReset">RESET</button>
</div>
<script>
document.addEventListener('DOMContentLoaded',()=>{
const NS='http://www.w3.org/2000/svg';
const svg=document.getElementById('mainSvg');
/* ===== 配置 ===== */
const cfg={N:10,L:52,A:0.35,w:2.5,dp:0.65,W:24,play:true,spd:1};
let time=0,scrollX=0;
let trailPts=[];
/* ===== 图层引用 ===== */
const $=id=>document.getElementById(id);
const gG=$('groundG'),tG=$('trailG_'),sG=$('snakeG'),fG=$('forceG'),aG=$('annoG'),sigG=$('sigG'),scG=$('scaleG'),vG=$('velG');
/* ===== 工具函数 ===== */
function ce(tag,attrs){const e=document.createElementNS(NS,tag);for(const[k,v]of Object.entries(attrs))e.setAttribute(k,v);return e}
/* ===== 地面参考线 ===== */
const gLines=[];
for(let i=0;i<35;i++){const l=ce('line',{x1:i*100,y1:70,x2:i*100,y2:455,stroke:'rgba(25,55,95,.3)','stroke-width':'1','stroke-dasharray':'5 10'});gG.appendChild(l);gLines.push(l)}
/* ===== 蛇体元素 ===== */
let modEls=[],jntEls=[],sclEls=[],frcEls=[],headEl=null,tailEl=null;
function buildSnake(){
sG.innerHTML='';fG.innerHTML='';modEls=[];jntEls=[];sclEls=[];frcEls=[];
const N=cfg.N,L=cfg.L,W=cfg.W;
for(let i=0;i<N;i++){
const g=ce('g',{});
/* 主体 */
g.appendChild(ce('rect',{x:-L/2+3,y:-W/2,width:L-6,height:W,rx:3,ry:3,fill:i===0?'url(#headG)':'url(#modG)',stroke:'#5a6577','stroke-width':'.6'}));
/* 电机圆 */
g.appendChild(ce('circle',{cx:0,cy:0,r:4.5,fill:'#141c2e',stroke:'#6b7fa0','stroke-width':'.6'}));
g.appendChild(ce('line',{x1:-3,y1:0,x2:3,y2:0,stroke:'#6b7fa0','stroke-width':'.5'}));
g.appendChild(ce('line',{x1:0,y1:-3,x2:0,y2:3,stroke:'#6b7fa0','stroke-width':'.5'}));
/* 鳞片 */
const ns=5;
for(let s=0;s<ns;s++){
const sx=-L/2+8+s*(L-16)/(ns-1), sy=W/2;
const sc=ce('polygon',{points:`${sx-2.5},${sy} ${sx+2.5},${sy} ${sx},${sy+4.5}`,fill:'#3e4a5c','stroke-width':'0'});
g.appendChild(sc);sclEls.push({el:sc,mi:i});
}
/* 编号 */
const lb=ce('text',{x:0,y:-W/2-5,'text-anchor':'middle',fill:'#4a5a72','font-size':'7','font-family':'Chakra Petch,sans-serif'});
lb.textContent='M'+i;g.appendChild(lb);
sG.appendChild(g);modEls.push(g);
/* 推力箭头 */
const ar=ce('line',{x1:0,y1:0,x2:0,y2:0,stroke:'#f97316','stroke-width':'2.2','marker-end':'url(#arrO)',opacity:'0',filter:'url(#glowO)'});
fG.appendChild(ar);frcEls.push(ar);
}
/* 万向节 */
for(let i=1;i<N;i++){
const c=ce('circle',{r:'5.5',fill:'#f59e0b',stroke:'#fcd34d','stroke-width':'1',filter:'url(#glowG)'});
sG.appendChild(c);jntEls.push(c);
}
/* 蛇头 */
headEl=ce('polygon',{fill:'#718096',stroke:'#a0aec0','stroke-width':'.6'});
sG.appendChild(headEl);
/* 蛇尾 */
tailEl=ce('polygon',{fill:'#374151',stroke:'#5a6577','stroke-width':'.6'});
sG.appendChild(tailEl);
}
buildSnake();
/* ===== 运动学计算 ===== */
function compute(t){
const N=cfg.N,L=cfg.L,A=cfg.A,w=cfg.w,dp=cfg.dp;
const bends=[],absA=[0],jts=[{x:0,y:0}];
for(let i=0;i<N-1;i++)bends.push(A*Math.sin(w*t-i*dp));
for(let i=1;i<N;i++){absA[i]=absA[i-1]+bends[i-1];const p=jts[i-1];jts.push({x:p.x+L*Math.cos(absA[i-1]),y:p.y+L*Math.sin(absA[i-1])})}
/* 将蛇头固定在屏幕右侧 */
const hx=jts[N-1].x+(jts[N-1].x-jts[N-2].x)/L*L*0.5;
const hy=jts[N-1].y+(jts[N-1].y-jts[N-2].y)/L*L*0.5;
const tx=960,ty=260;
const ox=tx-hx,oy=ty-hy;
jts.forEach(p=>{p.x+=ox;p.y+=oy});
return{jts,absA,bends};
}
/* ===== 信号图 ===== */
const sigColors=['#ef4444','#f59e0b','#10b981','#3b82f6','#8b5cf6','#ec4899'];
let sigPaths=[],sigDots=[],sigVLine=null;
function buildSignals(){
sigG.innerHTML='';sigPaths=[];sigDots=[];
const bx=50,by=620,bw=620,bh=140;
/* 坐标轴 */
sigG.appendChild(ce('line',{x1:bx,y1:by-bh/2,x2:bx,y2:by+bh/2,stroke:'rgba(100,140,200,.25)','stroke-width':'1'}));
sigG.appendChild(ce('line',{x1:bx,y1:by,x2:bx+bw,y2:by,stroke:'rgba(100,140,200,.2)','stroke-width':'1'}));
/* 标签 */
const labels=['θ','t'];
sigG.appendChild(ce('text',{x:bx-8,y:by-bh/2+10,fill:'#64748b','font-size':'9','font-family':'Chakra Petch'}));
sigG.lastChild.textContent='θ';
sigG.appendChild(ce('text',{x:bx+bw-5,y:by+12,fill:'#64748b','font-size':'9','font-family':'Chakra Petch'}));
sigG.lastChild.textContent='t';
/* 正弦波 - 显示5条代表波形 */
const showN=Math.min(6,cfg.N);
const step=Math.max(1,Math.floor(cfg.N/showN));
for(let k=0;k<showN;k++){
const mi=k*step;if(mi>=cfg.N)break;
const p=ce('path',{fill:'none',stroke:sigColors[k%sigColors.length],'stroke-width':'1.5',opacity:'0.7'});
sigG.appendChild(p);sigPaths.push({el:p,mi});
const d=ce('circle',{r:4,fill:sigColors[k%sigColors.length],stroke:'#080d1c','stroke-width':'1.5'});
sigG.appendChild(d);sigDots.push({el:d,mi});
/* 模块标签 */
const lb=ce('text',{x:bx+bw+8,y:by-10+k*16,fill:sigColors[k%sigColors.length],'font-size':'9','font-family':'Chakra Petch'});
lb.textContent='M'+mi;sigG.appendChild(lb);
}
/* 当前时间竖线 */
sigVLine=ce('line',{x1:bx,y1:by-bh/2,x2:bx,y2:by+bh/2,stroke:'#22d3ee','stroke-width':'1.5',opacity:'0.6'});
sigG.appendChild(sigVLine);
}
buildSignals();
/* ===== 摩擦鳞片机理图 ===== */
let scaleArrows=[],scaleModule=null,scaleScales=[];
function buildScaleDetail(){
scG.innerHTML='';scaleArrows=[];scaleScales=[];
const bx=780,by=560;
/* 底板 */
scG.appendChild(ce('rect',{x:bx,y:by,width:560,height:210,rx:4,fill:'rgba(12,20,37,.6)',stroke:'rgba(6,182,212,.1)','stroke-width':'1'}));
/* 地面线 */
scG.appendChild(ce('line',{x1:bx+30,y1:by+155,x2:bx+530,y2:by+155,stroke:'rgba(100,140,200,.3)','stroke-width':'2'}));
scG.appendChild(ce('text',{x:bx+270,y:by+175,fill:'#4a5a72','font-size':'9','text-anchor':'middle','font-family':'Chakra Petch'}));
scG.lastChild.textContent='地面参考面';
/* 模块截面 */
scaleModule=ce('rect',{x:bx+160,y:by+110,width:240,height:40,rx:4,fill:'url(#modG)',stroke:'#5a6577','stroke-width':'.8'});
scG.appendChild(scaleModule);
/* 电机 */
scG.appendChild(ce('circle',{cx:bx+280,cy:by+130,r:10,fill:'#141c2e',stroke:'#6b7fa0','stroke-width':'.8'}));
scG.appendChild(ce('text',{x:bx+280,y:by+134,fill:'#6b7fa0','font-size':'8','text-anchor':'middle','font-family':'Chakra Petch'}));
scG.lastChild.textContent='M';
/* 鳞片组 - 向右滑动(低阻力) */
const lx=bx+50;
scG.appendChild(ce('text',{x:lx+55,y:by+35,fill:'#10b981','font-size':'10','text-anchor':'middle','font-family':'Chakra Petch','font-weight':'600'}));
scG.lastChild.textContent='向前滑动 → 低阻力';
for(let s=0;s<6;s++){
const sx=lx+s*22,sy=by+150;
const sc=ce('polygon',{points:`${sx},${sy} ${sx+10},${sy-5} ${sx+10},${sy}`,fill:'#10b981',opacity:'0.8'});
scG.appendChild(sc);scaleScales.push({el:sc,dir:'fwd'});
}
/* 向后推压(高阻力) */
const rx=bx+340;
scG.appendChild(ce('text',{x:rx+55,y:by+35,fill:'#f97316','font-size':'10','text-anchor':'middle','font-family':'Chakra Petch','font-weight':'600'}));
scG.lastChild.textContent='向后推压 → 高阻力(锚固)';
for(let s=0;s<6;s++){
const sx=rx+s*22,sy=by+150;
const sc=ce('polygon',{points:`${sx},${sy} ${sx+10},${sy} ${sx+10},${sy+8} ${sx},${sy}`,fill:'#f97316',opacity:'0.8',filter:'url(#glowO)'});
scG.appendChild(sc);scaleScales.push({el:sc,dir:'bwd'});
}
/* 方向箭头 */
const af=ce('line',{x1:lx+20,y1:by+85,x2:lx+100,y2:by+85,stroke:'#10b981','stroke-width':'2','marker-end':'url(#arrCy)',opacity:'0.8'});
scG.appendChild(af);scaleArrows.push(af);
const ab=ce('line',{x1:rx+100,y1:by+85,x2:rx+20,y2:by+85,stroke:'#f97316','stroke-width':'2','marker-end':'url(#arrO)',opacity:'0.8'});
scG.appendChild(ab);scaleArrows.push(ab);
/* 摩擦系数标注 */
scG.appendChild(ce('text',{x:lx+55,y:by+195,fill:'#64748b','font-size':'9','text-anchor':'middle','font-family':'Chakra Petch'}));
scG.lastChild.textContent='μ_fwd ≈ 0.2';
scG.appendChild(ce('text',{x:rx+55,y:by+195,fill:'#64748b','font-size':'9','text-anchor':'middle','font-family':'Chakra Petch'}));
scG.lastChild.textContent='μ_bwd ≈ 0.6 (比 > 1:2)';
}
buildScaleDetail();
/* ===== 速度指示器 ===== */
let velBar=null,velText=null;
function buildVel(){
vG.innerHTML='';
vG.appendChild(ce('text',{x:1250,y:38,fill:'#94a3b8','font-size':'10','font-family':'Orbitron,monospace','text-anchor':'end'}));
vG.lastChild.textContent='PROPULSION';
velBar=ce('rect',{x:1260,y:26,width:0,height:14,rx:2,fill:'#22d3ee',opacity:'0.8'});
vG.appendChild(velBar);
velText=ce('text',{x:1260,y:55,fill:'#22d3ee','font-size':'11','font-family':'Chakra Petch','font-weight':'600'});
vG.appendChild(velText);
/* 背景条 */
vG.appendChild(ce('rect',{x:1260,y:26,width:100,height:14,rx:2,fill:'none',stroke:'rgba(6,182,212,.25)','stroke-width':'1'}));
}
buildVel();
/* ===== IFR标注 ===== */
let annoEls=[];
function buildAnno(){
aG.innerHTML='';annoEls=[];
const annos=[
{x:960,y:130,text:'万向节+微型电机',sub:'驱动-关节一体化 · 消除传动损耗',color:'#f59e0b',tx:1080,ty:80},
{x:700,y:380,text:'仿生单向摩擦鳞片',sub:'正向低阻/反向锚固 · 巧解力学边界',color:'#22d3ee',tx:460,ty:410},
{x:960,y:230,text:'推进方向',sub:'',color:'#f97316',tx:1060,ty:230}
];
annos.forEach((a,i)=>{
const g=ce('g',{opacity:'0'});
/* 引线 */
g.appendChild(ce('line',{x1:a.x,y1:a.y,x2:a.tx,y1:a.ty,stroke:a.color,'stroke-width':'1','stroke-dasharray':'3 3',opacity:'0.5'}));
g.appendChild(ce('circle',{cx:a.x,cy:a.y,r:3,fill:a.color,opacity:'0.6'}));
/* 主标签 */
const t1=ce('text',{x:a.tx+(a.tx>a.x?8:-8),y:a.ty,fill:a.color,'font-size':'12','font-weight':'600','font-family':'Chakra Petch','text-anchor':a.tx>a.x?'start':'end'});
t1.textContent=a.text;g.appendChild(t1);
if(a.sub){
const t2=ce('text',{x:a.tx+(a.tx>a.x?8:-8),y:a.ty+15,fill:'#94a3b8','font-size':'9','font-family':'Chakra Petch','text-anchor':a.tx>a.x?'start':'end'});
t2.textContent=a.sub;g.appendChild(t2);
}
aG.appendChild(g);annoEls.push(g);
});
}
buildAnno();
/* ===== 更新函数 ===== */
function updateSnake(){
const{jts,absA,bends}=compute(time);
const N=cfg.N,L=cfg.L,W=cfg.W;
/* 模块位置 */
for(let i=0;i<N;i++){
const cx=(jts[i].x+jts[Math.min(i+1,N-1)].x)/2;
const cy=(jts[i].y+jts[Math.min(i+1,N-1)].y)/2;
const ang=absA[i]*180/Math.PI;
modEls[i].setAttribute('transform',`translate(${cx},${cy}) rotate(${ang})`);
}
/* 万向节位置 */
for(let i=1;i<N;i++){
jntEls[i-1].setAttribute('cx',jts[i].x);
jntEls[i-1].setAttribute('cy',jts[i].y);
}
/* 蛇头 */
const ha=absA[N-1];const hx=jts[N-1].x,hy=jts[N-1].y;
const hdx=Math.cos(ha),hdy=Math.sin(ha);
const nx=-hdy,ny=hdx;
headEl.setAttribute('points',
`${hx+hdx*18},${hy+hdy*18} ${hx+nx*10},${hy+ny*10} ${hx-nx*10},${hy-ny*10}`);
/* 蛇尾 */
const ta=absA[0];const tx=jts[0].x,ty=jts[0].y;
const tdx=Math.cos(ta),tdy=Math.sin(ta);
const tnx=-tdy,tny=tdx;
tailEl.setAttribute('points',
`${tx-tdx*14},${ty-tdy*14} ${tx+tnx*6},${ty+tny*6} ${tx-tnx*6},${ty-tny*6}`);
/* 鳞片状态 */
sclEls.forEach(s=>{
const mi=s.mi;
const rate=cfg.A*cfg.w*Math.cos(cfg.w*time-mi*cfg.dp);
const anchor=Math.abs(rate)/(cfg.A*cfg.w);
if(anchor>0.45){
const bright=0.3+anchor*0.7;
s.el.setAttribute('fill','#22d3ee');
s.el.setAttribute('opacity',bright.toFixed(2));
if(anchor>0.7)s.el.setAttribute('filter','url(#glowC)');else s.el.removeAttribute('filter');
}else{
s.el.setAttribute('fill','#3e4a5c');s.el.setAttribute('opacity','0.6');s.el.removeAttribute('filter');
}
});
/* 推力箭头 */
for(let i=0;i<N;i++){
const rate=cfg.A*cfg.w*Math.cos(cfg.w*time-i*cfg.dp);
const anchor=Math.abs(rate)/(cfg.A*cfg.w);
if(anchor>0.4){
const cx=(jts[i].x+jts[Math.min(i+1,N-1)].x)/2;
const cy=(jts[i].y+jts[Math.min(i+1,N-1)].y)/2;
const a=absA[i];
const len=12+anchor*22;
frcEls[i].setAttribute('x1',cx);frcEls[i].setAttribute('y1',cy);
frcEls[i].setAttribute('x2',cx+len*Math.cos(a));frcEls[i].setAttribute('y2',cy+len*Math.sin(a));
frcEls[i].setAttribute('opacity',(anchor*0.85).toFixed(2));
}else{
frcEls[i].setAttribute('opacity','0');
}
}
/* 速度 */
const avgAnchor=sclEls.length>0?sclEls.reduce((s,c)=>{const mi=c.mi;return s+Math.abs(cfg.A*cfg.w*Math.cos(cfg.w*time-mi*cfg.dp))/(cfg.A*cfg.w)},0)/cfg.N:0;
const vel=avgAnchor*cfg.A*cfg.w*0.8;
velBar.setAttribute('width',Math.min(100,vel*35).toFixed(1));
velText.textContent='v = '+(vel*10).toFixed(1)+' units/s';
/* 轨迹 */
const cmx=jts.reduce((s,p)=>s+p.x,0)/jts.length;
const cmy=jts.reduce((s,p)=>s+p.y,0)/jts.length;
trailPts.push({x:cmx,y:cmy});
if(trailPts.length>150)trailPts.shift();
}
function updateGround(){
scrollX+=cfg.spd*30*(cfg.play?1:0)*0.016;
gLines.forEach((l,i)=>{l.setAttribute('x1',i*100-scrollX%100);l.setAttribute('x2',i*100-scrollX%100)});
const gbg=$('gridBg');
gbg.setAttribute('patternTransform',`translate(${-scrollX%60},0)`);
}
function updateTrail(){
tG.innerHTML='';
if(trailPts.length<2)return;
let d='M '+trailPts[0].x+' '+trailPts[0].y;
for(let i=1;i<trailPts.length;i++)d+=' L '+trailPts[i].x+' '+trailPts[i].y;
const p=ce('path',{d:d,fill:'none',stroke:'url(#trailG)','stroke-width':'2','stroke-linecap':'round'});
tG.appendChild(p);
}
function updateSignals(){
const bx=50,by=620,bw=620,bh=120;
const tWindow=4*Math.PI;
const showN=sigPaths.length;
sigPaths.forEach((sp,k)=>{
const mi=sp.mi;
let d='';
for(let px=0;px<=bw;px+=2){
const phase=px/bw*tWindow;
const val=cfg.A*Math.sin(phase-mi*cfg.dp);
const x=bx+px,y=by-val/cfg.A*(bh/2);
d+=(px===0?'M':'L')+x.toFixed(1)+' '+y.toFixed(1);
}
sp.el.setAttribute('d',d);
});
/* 当前时间点 */
const curPhase=((cfg.w*time)%tWindow+tWindow)%tWindow;
const curX=bx+curPhase/tWindow*bw;
sigVLine.setAttribute('x1',curX);sigVLine.setAttribute('x2',curX);
sigDots.forEach((sd,k)=>{
const mi=sd.mi;
const val=cfg.A*Math.sin(cfg.w*time-mi*cfg.dp);
sd.el.setAttribute('cx',curX);sd.el.setAttribute('cy',by-val/cfg.A*(bh/2));
});
}
function updateScaleDetail(){
/* 动画化鳞片机理图中的模块 - 添加微妙的脉冲 */
const pulse=0.5+0.5*Math.sin(time*3);
if(scaleModule){
const bx=780,by=560;
const offy=Math.sin(time*2.5)*5;
scaleModule.setAttribute('y',by+110+offy);
}
scaleScales.forEach(s=>{
if(s.dir==='fwd'){
const flex=0.7+0.3*Math.sin(time*2.5);
s.el.setAttribute('opacity',flex.toFixed(2));
}else{
const brace=0.7+0.3*Math.abs(Math.cos(time*2.5));
s.el.setAttribute('opacity',brace.toFixed(2));
}
});
}
function updateAnno(){
const elapsed=time;
annoEls.forEach((g,i)=>{
const delay=1.5+i*0.8;
const a=Math.min(1,Math.max(0,(elapsed-delay)/0.8));
g.setAttribute('opacity',a.toFixed(2));
});
}
/* ===== 主动画循环 ===== */
let lastT=0;
function loop(ts){
const dt=Math.min((ts-lastT)/1000,0.05);lastT=ts;
if(cfg.play){
time+=dt*cfg.spd;
}
updateGround();
updateSnake();
updateTrail();
updateSignals();
updateScaleDetail();
updateAnno();
requestAnimationFrame(loop);
}
requestAnimationFrame(loop);
/* ===== 控制面板 ===== */
const rAmp=$('rAmp'),rFreq=$('rFreq'),rPhase=$('rPhase'),rMod=$('rMod');
const vAmp=$('vAmp'),vFreq=$('vFreq'),vPhase=$('vPhase'),vMod=$('vMod');
rAmp.oninput=()=>{cfg.A=+rAmp.value;vAmp.textContent=cfg.A.toFixed(2)};
rFreq.oninput=()=>{cfg.w=+rFreq.value;vFreq.textContent=cfg.w.toFixed(1)};
rPhase.oninput=()=>{cfg.dp=+rPhase.value;vPhase.textContent=cfg.dp.toFixed(2)};
rMod.oninput=()=>{
const nv=+rMod.value;if(nv===cfg.N)return;
cfg.N=nv;vMod.textContent=nv;
buildSnake();buildSignals();buildAnno();trailPts=[];
};
$('btnPlay').onclick=function(){cfg.play=!cfg.play;this.textContent=cfg.play?'PAUSE':'PLAY';this.classList.toggle('active',cfg.play)};
$('btnReset').onclick=()=>{time=0;scrollX=0;trailPts=[];cfg.play=true;$('btnPlay').textContent='PAUSE';$('btnPlay').classList.add('active')};
});
</script>
</body>
</html>
实现说明
这个动画完整呈现了仿生蜿蜒波推进模块系统的最终理想解(IFR)工作原理,核心设计如下:
蛇体运动学
- 每个模块的弯曲角度由 相位递增正弦信号 驱动:
θ_i = A·sin(ωt - i·Δφ),从头到尾形成行波 - 通过链式积分计算各关节绝对角度与位置,蛇头固定在视口右侧,背景网格向左滚动模拟前进
IFR 核心创新点可视化
- 万向节+微型电机一体化(金色发光圆)—— 消除长轴传动损耗,每个关节同时是驱动器
- 仿生单向摩擦鳞片(青色状态变化)—— 当模块横向推压地面时鳞片发亮(锚固),滑行时暗淡(低阻),直接展示力学边界条件的巧妙解法
- 推进力箭头(橙色)—— 在每个模块上动态显示由鳞片锚固产生的向前推力分量,直观呈现"侧向波→直线推进"的转换
辅助面板
- 相位信号图:展示各模块接收的延时正弦控制信号及当前时刻标记
- 摩擦鳞片机理图:对比展示鳞片在向前滑动(低阻)和向后推压(高阻锚固)两种状态下的形变差异
- 推进速度指示器:实时显示整体推进效率
交互控制
振幅、频率、相位差、模块数四个滑块可实时调节波形参数,观察不同配置下的运动效果;播放/暂停与重置按钮控制动画状态。
积分规则:第一轮对话扣减6分,后续每轮扣4分
等待动画代码生成...
