分享图
动画工坊
引擎就绪
<!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=Chakra+Petch:wght@300;500;700&family=Noto+Sans+SC:wght@300;400;700&display=swap" rel="stylesheet">
<style>
:root{--bg:#060a12;--fg:#c0d0e8;--muted:#3e506e;--accent:#00e8b0;--accent2:#ff6b35;--bullet:#ffb020;--danger:#ff3050;--card:#0c1320;--border:#162240;--barrel:#3a7aff}
*{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;overflow-x:hidden}
.wrap{max-width:1260px;margin:0 auto;padding:12px 16px 24px}
header{text-align:center;padding:10px 0 6px}
header h1{font-family:'Chakra Petch',sans-serif;font-weight:700;font-size:clamp(20px,3vw,30px);letter-spacing:3px;color:var(--accent);text-transform:uppercase}
header p{font-size:13px;color:var(--muted);margin-top:2px;letter-spacing:1px}
.main{display:flex;gap:16px;margin-top:10px;align-items:flex-start}
.svg-box{flex:1;background:var(--card);border:1px solid var(--border);border-radius:14px;overflow:hidden;position:relative;min-height:420px}
.svg-box svg{display:block;width:100%;height:100%}
.panel{width:270px;display:flex;flex-direction:column;gap:12px;flex-shrink:0}
.card{background:var(--card);border:1px solid var(--border);border-radius:12px;padding:14px 16px}
.card h3{font-family:'Chakra Petch',sans-serif;font-weight:700;font-size:14px;color:var(--accent);letter-spacing:1.5px;margin-bottom:10px;text-transform:uppercase}
.card svg{display:block;width:100%}
.ctrl{margin-bottom:10px}
.ctrl:last-child{margin-bottom:0}
.ctrl label{display:flex;justify-content:space-between;font-size:12px;color:var(--muted);margin-bottom:5px}
.ctrl label span{color:var(--fg);font-weight:700;font-family:'Chakra Petch',sans-serif}
input[type=range]{width:100%;-webkit-appearance:none;appearance:none;height:3px;background:var(--border);border-radius:2px;outline:none}
input[type=range]::-webkit-slider-thumb{-webkit-appearance:none;appearance:none;width:14px;height:14px;border-radius:50%;background:var(--accent);cursor:pointer;box-shadow:0 0 8px rgba(0,232,176,.35)}
.leg{display:flex;align-items:center;gap:8px;font-size:11px;color:var(--muted);margin-bottom:5px}
.leg:last-child{margin-bottom:0}
.dot{width:9px;height:9px;border-radius:50%;flex-shrink:0}
.step{display:flex;gap:8px;margin-bottom:8px;font-size:11px;line-height:1.6}
.step:last-child{margin-bottom:0}
.sn{width:18px;height:18px;border-radius:50%;background:var(--accent);color:var(--bg);display:flex;align-items:center;justify-content:center;font-weight:700;font-size:10px;flex-shrink:0;font-family:'Chakra Petch',sans-serif}
.st{color:var(--fg);opacity:.85}
.st b{color:var(--accent);font-weight:700}
.ifr-note{margin-top:6px;padding:8px 10px;background:rgba(0,232,176,.06);border:1px solid rgba(0,232,176,.15);border-radius:8px;font-size:11px;color:var(--accent);line-height:1.6}
.ifr-note b{color:var(--accent2)}
@media(max-width:860px){.main{flex-direction:column}.panel{width:100%}}
</style>
</head>
<body>
<div class="wrap">
<header>
<h1>Centrifugal Feed Funnel</h1>
<p>倒锥形离心受弹漏斗 · 柔性星型导流梳 · 旋转枪管切向进弹</p>
</header>
<div class="main">
<div class="svg-box"><svg id="mainSvg" viewBox="0 0 800 700" xmlns="http://www.w3.org/2000/svg"></svg></div>
<aside class="panel">
<div class="card">
<h3>Cross Section</h3>
<svg id="secSvg" viewBox="0 0 260 210" xmlns="http://www.w3.org/2000/svg"></svg>
</div>
<div class="card">
<h3>Parameters</h3>
<div class="ctrl"><label>旋转速度 <span id="vSpeed">1.0x</span></label><input type="range" id="sSpeed" min="0.2" max="3" step="0.1" value="1"></div>
<div class="ctrl"><label>漏斗锥角 <span id="vCone">45°</span></label><input type="range" id="sCone" min="30" max="60" step="5" value="45"></div>
<div class="ctrl"><label>切向偏置 <span id="vOff">15°</span></label><input type="range" id="sOff" min="5" max="30" step="5" value="15"></div>
</div>
<div class="card">
<h3>Legend</h3>
<div class="leg"><div class="dot" style="background:#ffb020"></div>弹药 Bullet</div>
<div class="leg"><div class="dot" style="background:#00e8b0"></div>柔性导流梳 Comb</div>
<div class="leg"><div class="dot" style="background:#ff6b35"></div>切向进弹口 Feed Port</div>
<div class="leg"><div class="dot" style="background:#3a7aff"></div>旋转枪管 Barrel</div>
<div class="leg"><div class="dot" style="background:#ff3050"></div>离心力 Centrifugal</div>
</div>
<div class="card">
<h3>Process</h3>
<div class="step"><div class="sn">1</div><div class="st">子弹从静止弹夹<b>垂直落入</b>漏斗中心</div></div>
<div class="step"><div class="sn">2</div><div class="st">漏斗旋转带动子弹<b>公转同步</b></div></div>
<div class="step"><div class="sn">3</div><div class="st">离心力使子弹沿锥面<b>向外滑动</b></div></div>
<div class="step"><div class="sn">4</div><div class="st">导流梳<b>规整姿态</b>对齐进弹口</div></div>
<div class="step"><div class="sn">5</div><div class="st">子弹被<b>离心甩入</b>枪管</div></div>
<div class="ifr-note"><b>IFR 理想解:</b>将阻碍供弹的"旋转离心"转化为驱动进弹的动力源,以极低复杂度破除时空错位矛盾。</div>
</div>
</aside>
</div>
</div>

<script>
(function(){
"use strict";
const NS="http://www.w3.org/2000/svg";
/* ── 工具函数 ── */
function ce(t,a){const e=document.createElementNS(NS,t);if(a)for(const[k,v]of Object.entries(a))e.setAttribute(k,v);return e}
function polar(cx,cy,r,ang){return{x:cx+r*Math.cos(ang),y:cy+r*Math.sin(ang)}}
function arcPath(cx,cy,r,a1,a2){const s=polar(cx,cy,r,a1),e=polar(cx,cy,r,a2),large=a2-a1>Math.PI?1:0;return`M${s.x.toFixed(1)},${s.y.toFixed(1)} A${r},${r} 0 ${large} 1 ${e.x.toFixed(1)},${e.y.toFixed(1)}`}

/* ── 主视图参数 ── */
const CX=400,CY=360,FR=210,NB=6;
let speedMul=1,coneAng=45,tangOff=15;
let curAng=0,bullets=[],sparks=[],lastSpawn=0;

const svg=document.getElementById("mainSvg");

/* ── Defs ── */
const defs=ce("defs");
// 漏斗径向渐变
const fg=ce("radialGradient",{id:"fgr",cx:"50%",cy:"50%",r:"55%"});
fg.appendChild(ce("stop",{offset:"0%","stop-color":"#1a3858","stop-opacity":".25"}));
fg.appendChild(ce("stop",{offset:"70%","stop-color":"#0e1a2e","stop-opacity":".65"}));
fg.appendChild(ce("stop",{offset:"100%","stop-color":"#080e1a","stop-opacity":".92"}));
defs.appendChild(fg);
// 子弹渐变
const bg2=ce("radialGradient",{id:"bgr",cx:"35%",cy:"35%",r:"65%"});
bg2.appendChild(ce("stop",{offset:"0%","stop-color":"#ffe070"}));
bg2.appendChild(ce("stop",{offset:"100%","stop-color":"#c07010"}));
defs.appendChild(bg2);
// 发光滤镜
function mkGlow(id,sd){const f=ce("filter",{id,x:"-50%",y:"-50%",width:"200%",height:"200%"});f.appendChild(ce("feGaussianBlur",{stdDeviation:String(sd),result:"b"}));const m=ce("feMerge");m.appendChild(ce("feMergeNode",{in:"b"}));m.appendChild(ce("feMergeNode",{in:"SourceGraphic"}));f.appendChild(m);defs.appendChild(f)}
mkGlow("gl1",4);mkGlow("gl2",8);mkGlow("gl3",12);
// 箭头标记
function mkArrow(id,col){const m=ce("marker",{id,markerWidth:7,markerHeight:7,refX:5,refY:3.5,orient:"auto"});m.appendChild(ce("path",{d:"M0,0 L7,3.5 L0,7 Z",fill:col}));defs.appendChild(m)}
mkArrow("arrR","#ff3050");mkArrow("arrO","#ff6b35");
svg.appendChild(defs);

/* ── 背景网格 ── */
const bgG=ce("g",{opacity:".35"});
for(let x=0;x<=800;x+=50)bgG.appendChild(ce("line",{x1:x,y1:0,x2:x,y2:700,stroke:"#0d1828","stroke-width":".5"}));
for(let y=0;y<=700;y+=50)bgG.appendChild(ce("line",{x1:0,y1:y,x2:800,y2:y,stroke:"#0d1828","stroke-width":".5"}));
svg.appendChild(bgG);

/* ── 弹夹标识(静态) ── */
const stG=ce("g");
stG.appendChild(ce("rect",{x:CX-28,y:38,width:56,height:36,rx:4,fill:"none",stroke:"#4a6a9c","stroke-width":"1.5","stroke-dasharray":"4 2"}));
const mTxt=ce("text",{x:CX,y:60,"text-anchor":"middle",fill:"#4a6a9c","font-size":"10","font-family":"'Noto Sans SC'"});
mTxt.textContent="静止弹夹";stG.appendChild(mTxt);
stG.appendChild(ce("line",{x1:CX,y1:76,x2:CX,y2:CY-FR-18,stroke:"#4a6a9c","stroke-width":"1","stroke-dasharray":"3 3"}));
stG.appendChild(ce("polygon",{points:`${CX},${CY-FR-12} ${CX-4},${CY-FR-20} ${CX+4},${CY-FR-20}`,fill:"#4a6a9c"}));
svg.appendChild(stG);

/* ── 漏斗底层 ── */
const fBase=ce("g");
fBase.appendChild(ce("circle",{cx:CX,cy:CY,r:FR,fill:"url(#fgr)",stroke:"#2a4a7a","stroke-width":"2"}));
// 同心深度环
for(let i=1;i<=5;i++){const r=FR*(1-i*.16),op=.12+i*.04;fBase.appendChild(ce("circle",{cx:CX,cy:CY,r:Math.max(1,r),fill:"none",stroke:"#2a4a7a","stroke-width":".7",opacity:String(op),"stroke-dasharray":i>1?"2 5":"none"}))}
// 中心入弹点
const cDot=ce("circle",{cx:CX,cy:CY,r:5,fill:"#3a7aff",filter:"url(#gl2)"});
fBase.appendChild(cDot);
svg.appendChild(fBase);

/* ── 旋转组 ── */
const rotG=ce("g",{id:"rotG"});

// 导流梳
function buildCombs(){
const old=rotG.querySelectorAll(".comb");old.forEach(e=>e.remove());
for(let i=0;i<NB;i++){
  const a0=(i/NB)*Math.PI*2;
  const cg=ce("g",{class:"comb"});
  const armLen=FR-28;
  // 主臂
  const x1=CX+18*Math.cos(a0),y1=CY+18*Math.sin(a0);
  const x2=CX+armLen*Math.cos(a0),y2=CY+armLen*Math.sin(a0);
  cg.appendChild(ce("line",{x1,y1,x2,y2,stroke:"#00e8b0","stroke-width":"3",opacity:".6","stroke-linecap":"round"}));
  // 梳齿
  for(let t=1;t<=5;t++){
    const f=t/6;
    const tx=CX+(18+f*(armLen-18))*Math.cos(a0);
    const ty=CY+(18+f*(armLen-18))*Math.sin(a0);
    const pa=a0+Math.PI/2;
    const tl=7+f*6;
    const wave=Math.sin(f*Math.PI*3)*1.5;
    cg.appendChild(ce("line",{
      x1:tx-tl*Math.cos(pa)+wave*Math.cos(a0),y1:ty-tl*Math.sin(pa)+wave*Math.sin(a0),
      x2:tx+tl*Math.cos(pa)+wave*Math.cos(a0),y2:ty+tl*Math.sin(pa)+wave*Math.sin(a0),
      stroke:"#00e8b0","stroke-width":"1.8",opacity:".4","stroke-linecap":"round"
    }));
  }
  rotG.appendChild(cg);
}}

// 进弹口 + 枪管
function buildPorts(){
const old=rotG.querySelectorAll(".port");old.forEach(e=>e.remove());
for(let i=0;i<NB;i++){
  const a0=(i/NB)*Math.PI*2;
  const offR=tangOff*Math.PI/180;
  const pg=ce("g",{class:"port"});
  // 进弹口弧线
  const sa=a0-.13,ea=a0+.13;
  pg.appendChild(ce("path",{d:arcPath(CX,CY,FR,sa,ea),fill:"none",stroke:"#ff6b35","stroke-width":"7",opacity:".75","stroke-linecap":"round",filter:"url(#gl1)"}));
  // 切向偏置指示
  const offA=a0+offR;
  const ip=polar(CX,CY,FR-8,offA);
  const op=polar(CX,CY,FR+18,offA);
  pg.appendChild(ce("line",{x1:ip.x,y1:ip.y,x2:op.x,y2:op.y,stroke:"#ff6b35","stroke-width":"1.5",opacity:".5","marker-end":"url(#arrO)"}));
  // 枪管
  const bx=CX+(FR+30)*Math.cos(a0),by=CY+(FR+30)*Math.sin(a0);
  pg.appendChild(ce("circle",{cx:bx,cy:by,r:13,fill:"#080e1a",stroke:"#3a7aff","stroke-width":"2"}));
  pg.appendChild(ce("circle",{cx:bx,cy:by,r:6,fill:"#0e1830",stroke:"#3a7aff","stroke-width":"1.2"}));
  rotG.appendChild(pg);
}}

buildCombs();buildPorts();
svg.appendChild(rotG);

/* ── 离心力箭头(静态层) ── */
const cfG=ce("g",{opacity:".35"});
for(let i=0;i<NB;i++){
  const a=(i/NB)*Math.PI*2+Math.PI/NB;
  const r1=FR*.48,r2=FR*.72;
  const p1=polar(CX,CY,r1,a),p2=polar(CX,CY,r2,a);
  cfG.appendChild(ce("line",{x1:p1.x,y1:p1.y,x2:p2.x,y2:p2.y,stroke:"#ff3050","stroke-width":"1.5","marker-end":"url(#arrR)"}));
}
svg.appendChild(cfG);

/* ── 轨迹层 & 子弹层 & 火花层 ── */
const trailG=ce("g");svg.appendChild(trailG);
const bulG=ce("g");svg.appendChild(bulG);
const sparkG=ce("g");svg.appendChild(sparkG);

/* ── 标注层 ── */
const lbG=ce("g");
const rTxt=ce("text",{x:CX+FR+55,y:CY-2,fill:"#ff3050","font-size":"14","font-family":"'Chakra Petch'","font-weight":"700",opacity:".6"});
rTxt.textContent="ω";lbG.appendChild(rTxt);
// 旋转方向弧线
lbG.appendChild(ce("path",{d:arcPath(CX,CY,FR+48,-.5,.5),fill:"none",stroke:"#ff3050","stroke-width":"1.5",opacity:".45","marker-end":"url(#arrR)"}));
svg.appendChild(lbG);

/* ── 中心脉冲动画 ── */
let pulsePhase=0;

/* ═══ 子弹类 ═══ */
class Bullet{
  constructor(t){
    this.phase=0; // 0=drop 1=spiral 2=enter 3=done
    this.ang=curAng+Math.random()*.3;
    this.r=0;this.t=0;
    this.rv=48+Math.random()*12; // 径向初速
    this.trail=[];this.maxTr=28;
    this.el=ce("circle",{r:"7",fill:"url(#bgr)",filter:"url(#gl1)"});
    this.trEl=ce("path",{fill:"none",stroke:"#ffb020","stroke-width":"3","stroke-linecap":"round"});
    bulG.appendChild(this.el);trailG.appendChild(this.trEl);
    this.flashEl=null;
  }
  update(dt){
    this.t+=dt;
    const omega=2.2*speedMul;
    switch(this.phase){
      case 0:
        this.r=Math.min(14,this.t*55);
        this.ang+=omega*dt;
        if(this.t>.35){this.phase=1;this.t=0}
        break;
      case 1:
        this.r+=this.rv*dt;
        this.ang+=omega*dt;
        this.rv+=18*dt*speedMul;
        if(this.r>=FR-22){this.phase=2;this.t=0;this.snapPort()}
        break;
      case 2:
        this.r+=90*dt;
        this.ang+=omega*dt*.3;
        if(this.t>.25)this.phase=3;
        break;
    }
    const p=polar(CX,CY,Math.max(0,this.r),this.ang);
    this.el.setAttribute("cx",p.x.toFixed(1));
    this.el.setAttribute("cy",p.y.toFixed(1));
    this.el.setAttribute("opacity",this.phase===3?Math.max(0,1-this.t*4).toFixed(2):"1");
    // 入弹口闪光
    if(this.phase===2&&this.t<.15&&!this.flashEl){
      this.flashEl=ce("circle",{cx:p.x,cy:p.y,r:"18",fill:"#ff6b35",opacity:".7",filter:"url(#gl3)"});
      sparkG.appendChild(this.flashEl);
      spawnSparks(p.x,p.y,8);
    }
    if(this.flashEl){
      const fo=Math.max(0,.7-this.t*3);
      this.flashEl.setAttribute("opacity",fo.toFixed(2));
      if(fo<=0&&this.flashEl.parentNode){this.flashEl.parentNode.removeChild(this.flashEl);this.flashEl=null}
    }
    // 轨迹
    this.trail.push({x:p.x,y:p.y});
    if(this.trail.length>this.maxTr)this.trail.shift();
    if(this.trail.length>1){
      let d="M"+this.trail[0].x.toFixed(1)+","+this.trail[0].y.toFixed(1);
      for(let i=1;i<this.trail.length;i++)d+="L"+this.trail[i].x.toFixed(1)+","+this.trail[i].y.toFixed(1);
      this.trEl.setAttribute("d",d);
      const op=this.phase===3?Math.max(0,1-this.t*4):.5;
      this.trEl.setAttribute("opacity",op.toFixed(2));
    }
  }
  snapPort(){
    const ps=Math.PI*2/NB;
    const idx=Math.round(this.ang/ps);
    this.ang=idx*ps+(tangOff*Math.PI/180);
  }
  destroy(){
    if(this.el.parentNode)this.el.parentNode.removeChild(this.el);
    if(this.trEl.parentNode)this.trEl.parentNode.removeChild(this.trEl);
    if(this.flashEl&&this.flashEl.parentNode)this.flashEl.parentNode.removeChild(this.flashEl);
  }
}

/* ── 火花 ── */
function spawnSparks(x,y,n){
  for(let i=0;i<n;i++){
    const a=Math.random()*Math.PI*2;
    const spd=60+Math.random()*120;
    sparks.push({x,y,vx:Math.cos(a)*spd,vy:Math.sin(a)*spd,life:1,
      el:ce("circle",{r:1.5+Math.random()*1.5,fill:i%2?"#ffb020":"#ff6b35",filter:"url(#gl1)"})});
    sparkG.appendChild(sparks[sparks.length-1].el);
  }
}

/* ═══ 主动画循环 ═══ */
let prev=0;
function loop(ts){
  if(!prev)prev=ts;
  const dt=Math.min((ts-prev)/1000,.05);
  prev=ts;

  // 旋转
  curAng+=2.2*speedMul*dt;
  rotG.setAttribute("transform",`rotate(${(curAng*180/Math.PI)%360},${CX},${CY})`);

  // 中心脉冲
  pulsePhase+=dt*3;
  const pr=5+2*Math.sin(pulsePhase);
  cDot.setAttribute("r",pr.toFixed(1));

  // 生成子弹
  const interval=Math.max(400,1100/speedMul);
  if(ts-lastSpawn>interval){
    bullets.push(new Bullet(ts));
    lastSpawn=ts;
  }

  // 更新子弹
  bullets.forEach(b=>b.update(dt));
  bullets=bullets.filter(b=>{if(b.phase===3&&b.t>.4){b.destroy();return false}return true});

  // 更新火花
  sparks.forEach(s=>{
    s.x+=s.vx*dt;s.y+=s.vy*dt;
    s.life-=dt*2.5;
    s.el.setAttribute("cx",s.x.toFixed(1));
    s.el.setAttribute("cy",s.y.toFixed(1));
    s.el.setAttribute("opacity",Math.max(0,s.life).toFixed(2));
  });
  sparks=sparks.filter(s=>{if(s.life<=0){if(s.el.parentNode)s.el.parentNode.removeChild(s.el);return false}return true});

  requestAnimationFrame(loop);
}
requestAnimationFrame(loop);

/* ── 滑块控制 ── */
document.getElementById("sSpeed").addEventListener("input",function(){speedMul=+this.value;document.getElementById("vSpeed").textContent=speedMul.toFixed(1)+"x"});
document.getElementById("sCone").addEventListener("input",function(){coneAng=+this.value;document.getElementById("vCone").textContent=coneAng+"°";drawSection()});
document.getElementById("sOff").addEventListener("input",function(){tangOff=+this.value;document.getElementById("vOff").textContent=tangOff+"°";buildPorts()});

/* ═══════════════════════════════════════════
   剖面图
   ═══════════════════════════════════════════ */
const secSvg=document.getElementById("secSvg");
const SCX=130,SMagY=12,SGapY=42;
let secBulletPhase=0,secBulletT=0;

function drawSection(){
  while(secSvg.firstChild)secSvg.removeChild(secSvg.firstChild);
  // 背景
  secSvg.appendChild(ce("rect",{x:0,y:0,width:260,height:210,fill:"#080e18",rx:6}));
  // 弹夹
  secSvg.appendChild(ce("rect",{x:SCX-22,y:SMagY,width:44,height:28,rx:3,fill:"none",stroke:"#4a6a9c","stroke-width":"1.2","stroke-dasharray":"3 2"}));
  const mt=ce("text",{x:SCX,y:SMagY+17,"text-anchor":"middle",fill:"#4a6a9c","font-size":"8","font-family":"'Noto Sans SC'"});
  mt.textContent="弹夹";secSvg.appendChild(mt);
  // 下落箭头
  secSvg.appendChild(ce("line",{x1:SCX,y1:SMagY+30,x2:SCX,y2:SGapY+6,stroke:"#4a6a9c","stroke-width":".8","stroke-dasharray":"2 2"}));

  // 漏斗(倒梯形)
  const halfAngle=coneAng/2;
  const fTop=SGapY+10,fBot=160,fH=fBot-fTop;
  const topHW=70,botHW=topHW-fH/Math.tan(halfAngle*Math.PI/180);
  const thw=Math.max(12,botHW);
  secSvg.appendChild(ce("polygon",{
    points:`${SCX-topHW},${fTop} ${SCX+topHW},${fTop} ${SCX+thw},${fBot} ${SCX-thw},${fBot}`,
    fill:"#0e1a2e",stroke:"#2a4a7a","stroke-width":"1.5"
  }));
  // 导流梳齿(剖面中的短线)
  for(let i=1;i<=4;i++){
    const fy=fTop+i*(fH/5);
    const frac=(fy-fTop)/fH;
    const hw=topHW-frac*(topHW-thw);
    const toothL=5;
    secSvg.appendChild(ce("line",{x1:SCX-hw+3,y1:fy,x2:SCX-hw+3+toothL,y2:fy,stroke:"#00e8b0","stroke-width":"1.5",opacity:".5","stroke-linecap":"round"}));
    secSvg.appendChild(ce("line",{x1:SCX+hw-3,y1:fy,x2:SCX+hw-3-toothL,y2:fy,stroke:"#00e8b0","stroke-width":"1.5",opacity:".5","stroke-linecap":"round"}));
  }
  // 锥角标注
  secSvg.appendChild(ce("line",{x1:SCX+thw,y1:fBot,x2:SCX+topHW,y2:fTop,stroke:"#2a4a7a","stroke-width":".6","stroke-dasharray":"2 2"}));
  const at=ce("text",{x:SCX+topHW+4,y:fTop+fH*.35,fill:"#4a6a9c","font-size":"9","font-family":"'Chakra Petch'"});
  at.textContent=coneAng+"°";secSvg.appendChild(at);
  // 进弹口(底部两侧小口)
  const portW=10;
  secSvg.appendChild(ce("rect",{x:SCX-thw-portW,y:fBot-4,width:portW,height:8,rx:2,fill:"#ff6b35",opacity:".7"}));
  secSvg.appendChild(ce("rect",{x:SCX+thw,y:fBot-4,width:portW,height:8,rx:2,fill:"#ff6b35",opacity:".7"}));
  // 枪管
  secSvg.appendChild(ce("rect",{x:SCX-thw-portW-8,y:fBot+4,width:10,height:30,rx:2,fill:"#080e1a",stroke:"#3a7aff","stroke-width":"1.2"}));
  secSvg.appendChild(ce("rect",{x:SCX+thw+portW-2,y:fBot+4,width:10,height:30,rx:2,fill:"#080e1a",stroke:"#3a7aff","stroke-width":"1.2"}));
  // 标注
  const lb1=ce("text",{x:16,y:fTop+14,fill:"#00e8b0","font-size":"8","font-family":"'Noto Sans SC'"});
  lb1.textContent="导流梳";secSvg.appendChild(lb1);
  const lb2=ce("text",{x:SCX+thw+portW+12,y:fBot,fill:"#ff6b35","font-size":"8","font-family":"'Noto Sans SC'"});
  lb2.textContent="进弹口";secSvg.appendChild(lb2);
  const lb3=ce("text",{x:SCX+thw+portW+12,y:fBot+22,fill:"#3a7aff","font-size":"8","font-family":"'Noto Sans SC'"});
  lb3.textContent="枪管";secSvg.appendChild(lb3);
  // 离心力箭头
  secSvg.appendChild(ce("line",{x1:SCX,y1:fTop+fH*.5,x2:SCX+30,y1:fTop+fH*.5,x2:SCX+50,y:fTop+fH*.5,stroke:"#ff3050","stroke-width":"1.2","marker-end":"url(#arrR)",opacity:".5"}));
  const cf=ce("text",{x:SCX+52,y:fTop+fH*.5+3,fill:"#ff3050","font-size":"7",opacity:".6","font-family":"'Chakra Petch'"});
  cf.textContent="F_c";secSvg.appendChild(cf);

  // 动画子弹
  secSvg.appendChild(ce("circle",{id:"secBul",cx:SCX,cy:SMagY+28,r:5,fill:"url(#bgr)",filter:"url(#gl1)"}));
}

drawSection();

// 剖面图子弹动画
(function animSec(){
  const el=document.getElementById("secBul");
  if(!el){requestAnimationFrame(animSec);return}
  const dt=1/60;
  secBulletT+=dt*speedMul;
  const halfAngle=coneAng/2;
  const fTop=SGapY+10,fBot=160,fH=fBot-fTop;
  const topHW=70,botHW=Math.max(12,topHW-fH/Math.tan(halfAngle*Math.PI/180));
  const cycle=3.5;
  const t=(secBulletT%cycle)/cycle;
  let bx,by;
  if(t<.15){ // 下落
    const f=t/.15;
    bx=SCX;by=SMagY+28+f*(fTop-20-(SMagY+28));
  }else if(t<.7){ // 沿锥面滑出
    const f=(t-.15)/.55;
    const fy=fTop+f*fH;
    const frac=f;
    const hw=topHW-frac*(topHW-botHW);
    bx=SCX+hw-6;by=fy;
  }else if(t<.85){ // 进入进弹口
    const f=(t-.7)/.15;
    bx=SCX+botHW-6+f*14;by=fBot-4+f*8;
  }else{ // 进入枪管
    const f=(t-.85)/.15;
    bx=SCX+botHW+8;by=fBot+4+f*20;
  }
  el.setAttribute("cx",bx.toFixed(1));
  el.setAttribute("cy",by.toFixed(1));
  const op=t>.9?Math.max(0,1-(t-.9)/.1):1;
  el.setAttribute("opacity",op.toFixed(2));
  requestAnimationFrame(animSec);
})();

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

实现说明:

  1. 主视图(俯视):以大型 SVG 呈现旋转漏斗的顶视图,包含同心深度环暗示倒锥形结构、6 组柔性星型导流梳(青绿色带波浪梳齿)、6 个切向进弹口(橙色高亮弧线 + 偏置箭头)和枪管指示器。整个旋转组通过 requestAnimationFrame 驱动 transform: rotate() 实现连续旋转。

  2. 子弹动画:每颗子弹经历四个阶段——垂直落入(中心出现)→ 螺旋外滑(随漏斗公转 + 离心径向加速)→ 对齐进弹口(角度吸附到最近端口 + 切向偏置)→ 离心甩入枪管(闪光 + 火花粒子)。金色渐变子弹带拖尾轨迹,入弹瞬间触发橙色辉光脉冲和粒子爆发。

  3. 剖面图(侧视):右侧面板显示倒锥形漏斗的截面示意图,子弹沿锥面下滑并在进弹口处转弯进入枪管,锥角随滑块实时变化。

  4. 交互控制:三个滑块分别调节旋转速度(影响公转角速度和子弹生成频率)、漏斗锥角(改变剖面形状)、切向偏置角(调整进弹口偏移),参数变化即时反映到动画中。

  5. IFR 视觉引导:红色离心力箭头(静态层)始终指示径向外推方向,当子弹在进弹口处被"甩入"时,闪光与火花突出展示"离心力 = 驱动力"这一核心矛盾转化。

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