分享图
动画工坊
引擎就绪
<!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=Fira+Code:wght@300;400;500;600&display=swap" rel="stylesheet">
<style>
:root{--bg:#04080f;--fg:#a0b8d0;--accent:#00e8b0;--gold:#ffc842;--red:#ff3b4e;--dim:#0e1a2e;--card:#081020;--border:#14264a}
*{margin:0;padding:0;box-sizing:border-box}
body{background:var(--bg);color:var(--fg);font-family:'Fira Code',monospace;display:flex;flex-direction:column;align-items:center;min-height:100vh;padding:16px 12px;overflow-x:hidden}
.page-title{font-family:'Orbitron',sans-serif;font-weight:900;font-size:clamp(1rem,2.5vw,1.55rem);letter-spacing:.12em;color:var(--accent);text-align:center;margin-bottom:2px;text-shadow:0 0 24px rgba(0,232,176,.25)}
.page-sub{font-size:.68rem;color:#3e5a78;text-align:center;margin-bottom:12px;letter-spacing:.06em}
.wrap{width:100%;max-width:1260px;display:flex;justify-content:center}
.wrap svg{width:100%;height:auto;display:block}
.controls{display:flex;gap:20px;align-items:center;margin-top:14px;flex-wrap:wrap;justify-content:center}
.cg{display:flex;align-items:center;gap:7px}
.cg label{font-size:.68rem;color:#3e5a78;white-space:nowrap}
.cg input[type=range]{-webkit-appearance:none;width:110px;height:3px;background:var(--dim);border-radius:2px;outline:none}
.cg input[type=range]::-webkit-slider-thumb{-webkit-appearance:none;width:13px;height:13px;border-radius:50%;background:var(--accent);cursor:pointer;box-shadow:0 0 6px rgba(0,232,176,.4)}
.cg .val{font-size:.72rem;color:var(--accent);font-weight:600;min-width:28px;text-align:right}
.btn{background:var(--card);border:1px solid var(--border);color:var(--fg);font-family:'Fira Code',monospace;font-size:.68rem;padding:5px 14px;border-radius:3px;cursor:pointer;transition:all .2s}
.btn:hover{border-color:var(--accent);color:var(--accent)}
.legend{display:flex;gap:18px;margin-top:10px;flex-wrap:wrap;justify-content:center}
.legend-item{display:flex;align-items:center;gap:5px;font-size:.62rem;color:#4a6a8a}
.legend-dot{width:8px;height:8px;border-radius:50%}
</style>
</head>
<body>
<div class="page-title">PSEUDO-RANDOM CODED PULSE</div>
<div class="page-sub">伪随机编码脉冲 · 最终理想解 (IFR) 原理动画 — 窄脉冲序列 + 相关解码 = 高空间分辨率 ∩ 高信噪比</div>
<div class="wrap"><svg id="M" viewBox="0 0 1200 780"></svg></div>
<div class="controls">
  <div class="cg"><label>编码长度</label><input type="range" id="sCL" min="0" max="4" step="1" value="2"><span class="val" id="vCL">31</span></div>
  <div class="cg"><label>噪声水平</label><input type="range" id="sNS" min="10" max="150" step="5" value="60"><span class="val" id="vNS">60%</span></div>
  <div class="cg"><label>动画速度</label><input type="range" id="sSP" min="0.3" max="3" step="0.1" value="1"><span class="val" id="vSP">1x</span></div>
  <button class="btn" id="bReset">重置</button>
</div>
<div class="legend">
  <div class="legend-item"><div class="legend-dot" style="background:#00e8b0"></div>编码脉冲 / 信号</div>
  <div class="legend-item"><div class="legend-dot" style="background:#ff3b4e"></div>随机噪声</div>
  <div class="legend-item"><div class="legend-dot" style="background:#ffc842"></div>相关解码输出</div>
  <div class="legend-item"><div class="legend-dot" style="background:#4a90ff"></div>滑动匹配窗</div>
</div>

<script>
(function(){
/* ====== 常量与配置 ====== */
const NS='http://www.w3.org/2000/svg';
const W=1200,H=780;
const COL={bg:'#04080f',accent:'#00e8b0',gold:'#ffc842',red:'#ff3b4e',blue:'#4a90ff',dim:'#0e1a2e',text:'#5a7a9a',bright:'#b0d0ee',fiber:'#0a2040',fiberGlow:'#00485a',card:'#081428',cardBrd:'#12284a'};

/* 编码长度选项 */
const CL_OPTS=[7,15,31,127,255];
let clIdx=2, codeLen=31, noiseLvl=0.6, speed=1;

/* ====== SVG辅助 ====== */
function el(tag,attrs,parent){
  const e=document.createElementNS(NS,tag);
  if(attrs)for(const[k,v]of Object.entries(attrs))e.setAttribute(k,v);
  if(parent)parent.appendChild(e);
  return e;
}
function txt(parent,x,y,str,attrs){
  const t=el('text',{x,y,'font-family':"'Fira Code',monospace",fill:COL.text,'font-size':'11',...attrs},parent);
  t.textContent=str;return t;
}

/* ====== 生成m序列 ====== */
function genMSeq(len){
  let n=Math.ceil(Math.log2(len+1));
  const tapsMap={3:[3,2],4:[4,3],5:[5,3],6:[6,5],7:[7,6],8:[8,6,5,4],9:[9,5]};
  let taps=tapsMap[n]||[n,n-1];
  let reg=1,seq=[];
  for(let i=0;i<(1<<n)-1;i++){
    seq.push(reg&1);
    let fb=0;for(let t of taps)fb^=(reg>>(t-1))&1;
    reg=(reg>>1)|(fb<<(n-1));
  }
  return seq.slice(0,len);
}
let codeSeq=genMSeq(codeLen);

/* ====== 构建SVG骨架 ====== */
const svg=document.getElementById('M');

/* defs: 滤镜与渐变 */
const defs=el('defs',{},svg);
/* 辉光滤镜 */
function makeGlow(id,col,dev){
  const f=el('filter',{id,x:'-50%',y:'-50%',width:'200%',height:'200%'},defs);
  el('feGaussianBlur',{in:'SourceGraphic',stdDeviation:String(dev),result:'b'},f);
  const cm=el('feColorMatrix',{in:'b',type:'matrix',values:'1 0 0 0 0  0 1 0 0 0  0 0 1 0 0  0 0 0 1.8 0',result:'g'},f);
  const m=el('feMerge',{},f);
  el('feMergeNode',{in:'g'},m);el('feMergeNode',{in:'SourceGraphic'},m);
}
makeGlow('glowA','#00e8b0',4);
makeGlow('glowG','#ffc842',5);
makeGlow('glowR','#ff3b4e',3);
makeGlow('glowB','#4a90ff',3);

/* 渐变 */
const bgGrad=el('linearGradient',{id:'bgG',x1:'0',y1:'0',x2:'0',y2:'1'},defs);
el('stop',{offset:'0%','stop-color':'#060e1a'},bgGrad);
el('stop',{offset:'100%','stop-color':'#020508'},bgGrad);

const fiberGrad=el('linearGradient',{id:'fibG',x1:'0',y1:'0',x2:'1',y2:'0'},defs);
el('stop',{offset:'0%','stop-color':'#003848'},fiberGrad);
el('stop',{offset:'50%','stop-color':'#00586a'},fiberGrad);
el('stop',{offset:'100%','stop-color':'#003848'},fiberGrad);

/* 背景 */
el('rect',{width:W,height:H,fill:'url(#bgG)'},svg);
/* 网格 */
const gridG=el('g',{opacity:'0.06'},svg);
for(let x=0;x<=W;x+=40)el('line',{x1:x,y1:0,x2:x,y2:H,stroke:'#4080aa','stroke-width':'0.5'},gridG);
for(let y=0;y<=H;y+=40)el('line',{x1:0,y1:y,x2:W,y2:y,stroke:'#4080aa','stroke-width':'0.5'},gridG);

/* ====== 区域分组 ====== */
const gSys=el('g',{},svg);    // 系统框图
const gCode=el('g',{},svg);   // 编码显示
const gFiber=el('g',{},svg);  // 光纤与脉冲
const gDecode=el('g',{},svg); // 相关解码
const gResult=el('g',{},svg); // 结果
const gAnim=el('g',{},svg);   // 动态元素
const gLabel=el('g',{},svg);  // 标注

/* ====== 绘制系统框图 ====== */
function drawBox(g,x,y,w,h,label,sub,col){
  el('rect',{x,y,width:w,height:h,rx:4,fill:COL.card,stroke:col||COL.cardBrd,'stroke-width':'1.2'},g);
  txt(g,x+w/2,y+18,label,{'text-anchor':'middle',fill:col||COL.bright,'font-size':'12','font-weight':'600'});
  if(sub)txt(g,x+w/2,y+34,sub,{'text-anchor':'middle',fill:COL.text,'font-size':'9'});
}

/* 光源 */
drawBox(gSys,50,52,130,50,'光源发射','Laser Source','#00b89c');
/* AOM */
drawBox(gSys,250,52,130,50,'声光调制器','AOM',COL.accent);
/* FPGA */
drawBox(gSys,150,130,160,50,'FPGA 时序控制','Pulse Seq Generator',COL.blue);

/* 连线: 光源→AOM */
el('line',{x1:180,y1:77,x2:250,y2:77,stroke:COL.accent,'stroke-width':'2',opacity:'0.5'},gSys);
el('polygon',{points:'248,72 248,82 258,77',fill:COL.accent,opacity:'0.7'},gSys);
/* 连线: FPGA→AOM */
el('line',{x1:230,y1:130,x2:315,y2:102,stroke:COL.blue,'stroke-width':'1.5','stroke-dasharray':'4,3',opacity:'0.5'},gSys);

/* 光纤入口标注 */
txt(gSys,420,80,'⟶ 进入光纤',{"font-size":'11',fill:COL.accent,'font-weight':'600'});

/* 分区标签 */
txt(gLabel,20,35,'▎发射与调制',{"font-size":'12',fill:'#3a5a7a','font-weight':'600','letter-spacing':'0.08em'});
txt(gLabel,20,225,'▎光纤传输与背向散射',{"font-size":'12',fill:'#3a5a7a','font-weight':'600','letter-spacing':'0.08em'});
txt(gLabel,20,390,'▎相关解码 — 核心创新',{"font-size":'12',fill:COL.gold,'font-weight':'700','letter-spacing':'0.08em'});
txt(gLabel,20,610,'▎解码输出 — 最终理想解',{"font-size":'12',fill:COL.gold,'font-weight':'700','letter-spacing':'0.08em'});

/* ====== 编码序列显示 ====== */
const codeDisplayG=el('g',{},gCode);
function drawCodeDisplay(){
  while(codeDisplayG.firstChild)codeDisplayG.removeChild(codeDisplayG.firstChild);
  const displayLen=Math.min(codeSeq.length,40);
  const bw=14,bh=28,gap=2,startX=55,startY=185;
  el('rect',{x:startX-6,y:startY-20,width:displayLen*(bw+gap)+12,height:bh+30,rx:3,fill:'#060d1a',stroke:COL.cardBrd,'stroke-width':'0.8'},codeDisplayG);
  txt(codeDisplayG,startX,startY-6,'伪随机码序列 (前'+displayLen+'位):',{"font-size":'9',fill:COL.text});
  for(let i=0;i<displayLen;i++){
    const v=codeSeq[i];
    el('rect',{x:startX+i*(bw+gap),y:startY,width:bw,height:bh,rx:2,fill:v?COL.accent:'#0a1520',opacity:v?'0.85':'0.3'},codeDisplayG);
    txt(codeDisplayG,startX+i*(bw+gap)+bw/2,startY+bh-8,String(v),{"font-size":'8',fill:v?'#020808':'#2a3a4a','text-anchor':'middle','font-weight':'600'});
  }
  if(codeSeq.length>displayLen){
    txt(codeDisplayG,startX+displayLen*(bw+gap)+8,startY+bh/2+4,'...共'+codeSeq.length+'位',{"font-size":'9',fill:COL.text});
  }
}
drawCodeDisplay();

/* ====== 光纤通道 ====== */
const fiberY=275;
/* 光纤主体 */
el('rect',{x:50,y:fiberY-8,width:1100,height:16,rx:8,fill:'url(#fibG)',opacity:'0.6'},gFiber);
el('rect',{x:50,y:fiberY-4,width:1100,height:8,rx:4,fill:'#003040',opacity:'0.4'},gFiber);
/* 光纤标注 */
txt(gFiber,600,fiberY+30,'传感光纤 (Fiber)',{"font-size":'10',fill:COL.text,'text-anchor':'middle'});

/* 散射点标记 */
const scatterPts=[200,380,560,740,920];
scatterPts.forEach((sx,i)=>{
  el('circle',{cx:sx,cy:fiberY,r:4,fill:'#0a3050',stroke:COL.accent,'stroke-width':'0.8',opacity:'0.5'},gFiber);
  txt(gFiber,sx,fiberY+22,'R'+(i+1),{"font-size":'8',fill:'#3a5a7a','text-anchor':'middle'});
});

/* ====== 相关解码区域 ====== */
const decY=430;
/* 面板背景 */
el('rect',{x:40,y:decY-10,width:1120,height:175,rx:6,fill:'#050c18',stroke:COL.cardBrd,'stroke-width':'0.8'},gDecode);

/* 三个信号通道标签 */
txt(gDecode,55,decY+10,'① 已知编码模板 (Reference)',{"font-size":'9',fill:COL.accent,'font-weight':'600'});
txt(gDecode,55,decY+65,'② 接收信号 (含噪声背向散射)',{"font-size":'9',fill:COL.red,'font-weight':'600'});
txt(gDecode,55,decY+120,'③ 相关输出 (解码后信号)',{"font-size":'9',fill:COL.gold,'font-weight':'600'});

/* 信号路径占位 */
const refPath=el('path',{fill:'none',stroke:COL.accent,'stroke-width':'1.5',opacity:'0.9'},gDecode);
const rcvPath=el('path',{fill:'none',stroke:COL.red,'stroke-width':'1.2',opacity:'0.7'},gDecode);
const corPath=el('path',{fill:'none',stroke:COL.gold,'stroke-width':'2',filter:'url(#glowG)'},gDecode);
/* 相关包络 */
const corEnv=el('path',{fill:'none',stroke:COL.gold,'stroke-width':'1',opacity:'0.3','stroke-dasharray':'3,3'},gDecode);

/* 滑动窗口指示器 */
const sliderG=el('g',{},gDecode);
const sliderRect=el('rect',{x:0,y:decY+15,width:0,height:130,fill:COL.blue,opacity:'0.06',rx:2},sliderG);
const sliderLine=el('line',{x1:0,y1:decY+15,x2:0,y2:decY+145,stroke:COL.blue,'stroke-width':'1.5',opacity:'0.6','stroke-dasharray':'4,2'},sliderG);
const sliderLabel=txt(sliderG,0,decY+155,'匹配窗',{"font-size":'8',fill:COL.blue,'text-anchor':'middle'});

/* 匹配闪烁指示 */
const matchFlash=el('rect',{x:0,y:decY+115,width:0,height:25,rx:3,fill:COL.gold,opacity:'0'},gDecode);

/* ====== 结果区域 ====== */
const resY=630;
el('rect',{x:40,y:resY-10,width:1120,height:145,rx:6,fill:'#060d1a',stroke:'#1a3050','stroke-width':'0.8'},gResult);
txt(gResult,55,resY+10,'最终理想解输出',{"font-size":'11',fill:COL.gold,'font-weight':'700'});
txt(gResult,55,resY+26,'窄脉冲(10ns) → 1m空间分辨率  |  编码序列能量 → 高信噪比  |  相关解码 → 两者兼得',{"font-size":'9',fill:COL.text});

const resPath=el('path',{fill:'none',stroke:COL.gold,'stroke-width':'2.2',filter:'url(#glowG)'},gResult);
/* 分辨率标尺 */
const rulerG=el('g',{},gResult);

/* 指标显示 */
const metricG=el('g',{},gResult);
el('rect',{x:900,y:resY+38,width:240,height:85,rx:4,fill:'#080e1a',stroke:COL.cardBrd,'stroke-width':'0.6'},metricG);
txt(metricG,920,resY+56,'空间分辨率',{"font-size":'9',fill:COL.text});
const mRes=txt(metricG,1100,resY+56,'1 m',{"font-size":'12',fill:COL.accent,'font-weight':'700','text-anchor':'end'});
txt(metricG,920,resY+76,'等效信噪比提升',{"font-size":'9',fill:COL.text});
const mSNR=txt(metricG,1100,resY+76,'+15 dB',{"font-size":'12',fill:COL.gold,'font-weight':'700','text-anchor':'end'});
txt(metricG,920,resY+96,'编码增益',{"font-size":'9',fill:COL.text});
const mGain=txt(metricG,1100,resY+96,'×31',{"font-size":'12',fill:COL.accent,'font-weight':'700','text-anchor':'end'});

/* ====== 动态脉冲元素 ====== */
const pulseEls=[];
const backscatterEls=[];
const MAX_PULSES=12;
const MAX_BS=8;

for(let i=0;i<MAX_PULSES;i++){
  const c=el('circle',{r:'4',fill:COL.accent,opacity:'0',filter:'url(#glowA)'},gAnim);
  pulseEls.push(c);
}
for(let i=0;i<MAX_BS;i++){
  const c=el('circle',{r:'2.5',fill:COL.red,opacity:'0',filter:'url(#glowR)'},gAnim);
  backscatterEls.push(c);
}

/* ====== 动画数据生成 ====== */
function genSignal(sigLen,code,noiseAmp){
  /* 生成参考信号 */
  const ref=new Float32Array(sigLen);
  for(let i=0;i<sigLen;i++)ref[i]=i<code.length?code[i]*2-1:0;

  /* 生成接收信号: 编码脉冲与散射点卷积 + 噪声 */
  const rcv=new Float32Array(sigLen);
  /* 模拟3个散射点 */
  const scatPos=[Math.floor(sigLen*0.2),Math.floor(sigLen*0.5),Math.floor(sigLen*0.75)];
  const scatAmp=[1,0.6,0.8];
  for(let s=0;s<scatPos.length;s++){
    for(let i=0;i<code.length;i++){
      const p=scatPos[s]+i;
      if(p<sigLen)rcv[p]+=code[i]*scatAmp[s];
    }
  }
  /* 加噪声 */
  for(let i=0;i<sigLen;i++)rcv[i]+=(Math.random()-0.5)*2*noiseAmp;

  /* 相关运算 */
  const cor=new Float32Array(sigLen);
  for(let lag=0;lag<sigLen;lag++){
    let sum=0;
    for(let i=0;i<code.length;i++){
      const p=lag+i-code.length;
      if(p>=0&&p<sigLen)sum+=rcv[p]*(code[i]*2-1);
    }
    cor[lag]=sum;
  }

  return{ref,rcv,cor,scatPos};
}

let sigData=null;
const SIG_LEN=500;

function refreshSignal(){
  sigData=genSignal(SIG_LEN,codeSeq,noiseLvl*0.4);
}
refreshSignal();

/* ====== 路径生成 ====== */
function sigToPath(data,startX,endX,yCenter,yScale,step){
  let d='';
  const len=data.length;
  const xScale=(endX-startX)/len;
  for(let i=0;i<len;i+=step){
    const x=startX+i*xScale;
    const y=yCenter-data[i]*yScale;
    d+=(i===0?'M':'L')+x.toFixed(1)+','+y.toFixed(1);
  }
  return d;
}

/* ====== 主动画循环 ====== */
let t=0,phase=0;
let pulseTimer=0,bsTimer=0;

/* 脉冲状态 */
const pulses=[];
const bscatters=[];

function animate(ts){
  if(!lastTS)lastTS=ts;
  const dt=Math.min((ts-lastTS)/1000,0.05)*speed;
  lastTS=ts;
  t+=dt;

  /* ---- 脉冲发射 ---- */
  pulseTimer+=dt;
  if(pulseTimer>0.35){
    pulseTimer=0;
    const codeIdx=Math.floor(t*2.8)%codeSeq.length;
    if(codeSeq[codeIdx]===1){
      pulses.push({x:380,y:fiberY,vx:180+Math.random()*40,life:1,codeBit:1});
    }
  }

  /* 更新脉冲 */
  for(let i=pulses.length-1;i>=0;i--){
    const p=pulses[i];
    p.x+=p.vx*dt;
    p.life-=dt*0.25;
    if(p.x>1130||p.life<=0){pulses.splice(i,1);continue;}
    /* 到达散射点时产生背向散射 */
    for(const sp of scatterPts){
      if(Math.abs(p.x-sp)<6&&p.vx>0&&!p.scattered){
        p.scattered=true;
        bscatters.push({x:sp,y:fiberY,vx:-(120+Math.random()*40),life:1});
      }
    }
  }

  /* 更新背向散射 */
  for(let i=bscatters.length-1;i>=0;i--){
    const b=bscatters[i];
    b.x+=b.vx*dt;
    b.life-=dt*0.5;
    if(b.x<50||b.life<=0)bscatters.splice(i,1);
  }

  /* 渲染脉冲 */
  for(let i=0;i<MAX_PULSES;i++){
    if(i<pulses.length){
      const p=pulses[i];
      pulseEls[i].setAttribute('cx',p.x);
      pulseEls[i].setAttribute('cy',p.y);
      pulseEls[i].setAttribute('opacity',Math.min(p.life,1)*0.9);
      pulseEls[i].setAttribute('r','4');
    }else{
      pulseEls[i].setAttribute('opacity','0');
    }
  }

  /* 渲染背向散射 */
  for(let i=0;i<MAX_BS;i++){
    if(i<bscatters.length){
      const b=bscatters[i];
      backscatterEls[i].setAttribute('cx',b.x);
      backscatterEls[i].setAttribute('cy',b.y+Math.sin(t*8+i)*2);
      backscatterEls[i].setAttribute('opacity',Math.min(b.life,1)*0.6);
    }else{
      backscatterEls[i].setAttribute('opacity','0');
    }
  }

  /* ---- 信号波形更新 ---- */
  const sigStartX=180,sigEndX=1130;
  const step=2;

  /* 参考信号(静态+微动画) */
  refPath.setAttribute('d',sigToPath(sigData.ref,sigStartX,sigEndX,decY+30,12,step));

  /* 接收信号(噪声动态变化) */
  const rcvNow=new Float32Array(sigData.rcv);
  for(let i=0;i<SIG_LEN;i++)rcvNow[i]+=(Math.random()-0.5)*noiseLvl*0.15;
  rcvPath.setAttribute('d',sigToPath(rcvNow,sigStartX,sigEndX,decY+82,10,step));

  /* 滑动相关动画 */
  const slidePeriod=6; // 秒一个完整滑动周期
  const slidePhase=(t%slidePeriod)/slidePeriod;
  const slidePos=slidePhase*SIG_LEN;
  const slideX=sigStartX+slidePhase*(sigEndX-sigStartX);

  sliderRect.setAttribute('x',slideX-20);
  sliderRect.setAttribute('width','40');
  sliderLine.setAttribute('x1',slideX);
  sliderLine.setAttribute('x2',slideX);
  sliderLabel.setAttribute('x',slideX);

  /* 相关输出 - 只显示到当前位置 */
  const corLen=Math.floor(slidePos);
  const corPartial=new Float32Array(SIG_LEN);
  for(let i=0;i<SIG_LEN;i++)corPartial[i]=0;
  /* 计算到当前位置的相关 */
  for(let lag=0;lag<corLen;lag++){
    let sum=0;
    for(let j=0;j<codeSeq.length;j++){
      const p=lag+j-codeSeq.length;
      if(p>=0&&p<SIG_LEN)sum+=sigData.rcv[p]*(codeSeq[j]*2-1);
    }
    corPartial[lag]=sum;
  }

  /* 归一化 */
  let maxCor=0;
  for(let i=0;i<corLen;i++)maxCor=Math.max(maxCor,Math.abs(corPartial[i]));
  const normFactor=maxCor>0?1/maxCor:1;

  const normCor=new Float32Array(SIG_LEN);
  for(let i=0;i<corLen;i++)normCor[i]=corPartial[i]*normFactor;
  corPath.setAttribute('d',sigToPath(normCor,sigStartX,sigEndX,decY+135,18,step));

  /* 匹配闪烁: 在散射点对应位置高亮 */
  for(const sp of sigData.scatPos){
    const spX=sigStartX+(sp/SIG_LEN)*(sigEndX-sigStartX);
    if(Math.abs(slideX-spX)<30&&slidePhase>0.1){
      matchFlash.setAttribute('x',spX-15);
      matchFlash.setAttribute('width','30');
      matchFlash.setAttribute('opacity',String(0.15*Math.max(0,1-Math.abs(slideX-spX)/30)));
    }
  }

  /* ---- 结果区域 ---- */
  /* 最终解码输出 (完整相关结果) */
  const fullCor=new Float32Array(SIG_LEN);
  for(let lag=0;lag<SIG_LEN;lag++){
    let sum=0;
    for(let j=0;j<codeSeq.length;j++){
      const p=lag+j-codeSeq.length;
      if(p>=0&&p<SIG_LEN)sum+=sigData.rcv[p]*(codeSeq[j]*2-1);
    }
    fullCor[lag]=sum;
  }
  let maxFC=0;
  for(let i=0;i<SIG_LEN;i++)maxFC=Math.max(maxFC,Math.abs(fullCor[i]));
  const nf=maxFC>0?1/maxFC:1;
  const normFC=new Float32Array(SIG_LEN);
  for(let i=0;i<SIG_LEN;i++)normFC[i]=fullCor[i]*nf;

  /* 渐显效果: 结果随滑动进度显示 */
  const revealLen=Math.floor(slidePos);
  const displayCor=new Float32Array(SIG_LEN);
  for(let i=0;i<SIG_LEN;i++)displayCor[i]=i<revealLen?normFC[i]:0;

  resPath.setAttribute('d',sigToPath(displayCor,sigStartX,sigEndX,resY+70,30,step));

  /* 分辨率标尺 */
  while(rulerG.firstChild)rulerG.removeChild(rulerG.firstChild);
  const rulerX=sigStartX+10;
  const rulerY=resY+105;
  el('line',{x1:rulerX,y1:rulerY,x2:rulerX+50,y2:rulerY,stroke:COL.accent,'stroke-width':'1.5'},rulerG);
  el('line',{x1:rulerX,y1:rulerY-4,x2:rulerX,y2:rulerY+4,stroke:COL.accent,'stroke-width':'1.5'},rulerG);
  el('line',{x1:rulerX+50,y1:rulerY-4,x2:rulerX+50,y2:rulerY+4,stroke:COL.accent,'stroke-width':'1.5'},rulerG);
  txt(rulerG,rulerX+25,rulerY+14,'1 m (10ns脉冲)',{"font-size":'8',fill:COL.accent,'text-anchor':'middle'});

  /* 更新指标 */
  const gainDB=Math.round(10*Math.log10(codeLen));
  mSNR.textContent='+'+gainDB+' dB';
  mGain.textContent='×'+codeLen;

  requestAnimationFrame(animate);
}

let lastTS=0;

/* ====== 交互控件 ====== */
const sCL=document.getElementById('sCL');
const sNS=document.getElementById('sNS');
const sSP=document.getElementById('sSP');
const vCL=document.getElementById('vCL');
const vNS=document.getElementById('vNS');
const vSP=document.getElementById('vSP');
const bReset=document.getElementById('bReset');

sCL.addEventListener('input',()=>{
  clIdx=parseInt(sCL.value);
  codeLen=CL_OPTS[clIdx];
  vCL.textContent=codeLen;
  codeSeq=genMSeq(codeLen);
  drawCodeDisplay();
  refreshSignal();
});

sNS.addEventListener('input',()=>{
  noiseLvl=parseInt(sNS.value)/100;
  vNS.textContent=Math.round(noiseLvl*100)+'%';
  refreshSignal();
});

sSP.addEventListener('input',()=>{
  speed=parseFloat(sSP.value);
  vSP.textContent=speed.toFixed(1)+'x';
});

bReset.addEventListener('click',()=>{
  t=0;pulseTimer=0;bsTimer=0;
  pulses.length=0;bscatters.length=0;
  refreshSignal();
});

/* ====== 启动 ====== */
requestAnimationFrame(animate);

})();
</script>
</body>
</html>

实现说明

这个动画以 IFR(最终理想解) 为核心视角,直接展示伪随机编码脉冲系统如何同时实现高空间分辨率与高信噪比,无需前后对比。

视觉架构

  1. 发射与调制区:光源 → AOM → FPGA 三大关键部件的框图,脉冲根据伪随机码序列动态发射("1"发光,"0"静默),直观展示"窄脉冲序列=高能量"的核心转化。

  2. 光纤传输区:青色光点沿光纤行进,到达散射点时激发红色背向散射信号回传,噪声用红色抖动表示。

  3. 相关解码区(核心创新)

    • ① 已知编码模板(青色参考信号)
    • ② 含噪声的接收信号(红色波形,实时抖动)
    • ③ 蓝色滑动匹配窗从左向右扫过,相关输出(金色)逐步构建——在散射点对应位置出现尖锐峰值,噪声被抑制
  4. 最终理想解输出:金色高亮波形完整呈现三个散射峰,配合分辨率标尺(1m)和指标面板(信噪比增益、编码增益),验证"窄脉冲 + 长序列 = 分辨率 ∩ 信噪比"。

交互控制

  • 编码长度滑块:7 / 15 / 31 / 127 / 255 位,实时改变编码增益
  • 噪声水平滑块:调节噪声强度,观察相关解码的抗噪能力
  • 动画速度:0.3x ~ 3x
  • 重置按钮:重新开始动画周期
积分规则:第一轮对话扣减8分,后续每轮扣6分