独立渲染引擎就绪引擎就绪
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>多腔硅胶软体蛇形机器人连续行波推进原理</title>
<style>
:root {
--bg-space: #070a10;
--panel-bg: rgba(13, 20, 32, 0.7);
--panel-border: rgba(56, 189, 248, 0.18);
--accent-cyan: #00f3ff;
--accent-blue: #38bdf8;
--accent-amber: #f59e0b;
--text-main: #e2e8f0;
--text-dim: #64748b;
--font-code: "JetBrains Mono", "SF Mono", "Fira Code", monospace;
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
user-select: none;
}
body {
background-color: var(--bg-space);
color: var(--text-main);
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", sans-serif;
min-height: 100vh;
display: flex;
flex-direction: column;
overflow: hidden;
position: relative;
}
/* 背景星轨微网格 */
.grid-bg {
position: absolute;
inset: 0;
background-image:
radial-gradient(circle at 50% 50%, rgba(56, 189, 248, 0.05) 0%, transparent 70%),
linear-gradient(rgba(255, 255, 255, 0.018) 1px, transparent 1px),
linear-gradient(90deg, rgba(255, 255, 255, 0.018) 1px, transparent 1px);
background-size: 100% 100%, 32px 32px, 32px 32px;
pointer-events: none;
z-index: 0;
}
/* 顶部极简信息微栏(不遮挡主视区) */
header {
position: absolute;
top: 14px;
left: 20px;
right: 20px;
display: flex;
justify-content: space-between;
align-items: center;
z-index: 10;
pointer-events: none;
}
.brand {
display: flex;
align-items: center;
gap: 10px;
}
.brand-tag {
font-size: 10px;
font-weight: 700;
font-family: var(--font-code);
background: rgba(0, 243, 255, 0.12);
border: 1px solid rgba(0, 243, 255, 0.35);
color: var(--accent-cyan);
padding: 2px 7px;
border-radius: 4px;
letter-spacing: 0.8px;
}
.title-group h1 {
font-size: 13px;
font-weight: 500;
letter-spacing: 0.5px;
color: #f1f5f9;
}
.title-group p {
font-size: 11px;
color: var(--text-dim);
font-family: var(--font-code);
}
.telemetry {
display: flex;
gap: 16px;
font-size: 11px;
font-family: var(--font-code);
background: var(--panel-bg);
border: 1px solid var(--panel-border);
backdrop-filter: blur(8px);
padding: 6px 12px;
border-radius: 6px;
pointer-events: auto;
}
.telemetry-item {
display: flex;
gap: 6px;
align-items: center;
}
.telemetry-item span.label {
color: var(--text-dim);
}
.telemetry-item span.value {
color: var(--accent-cyan);
font-weight: 600;
min-width: 44px;
}
/* 动画核心舞台:完全居中,占比 >85% */
main {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
position: relative;
z-index: 1;
width: 100vw;
height: 100vh;
}
#stage {
width: 100%;
height: 100%;
max-width: 1440px;
max-height: 850px;
display: block;
}
/* 底部角落微型控制舱(小巧、低对比度、无遮挡) */
.bottom-hud {
position: absolute;
bottom: 14px;
left: 20px;
right: 20px;
display: flex;
justify-content: space-between;
align-items: flex-end;
z-index: 10;
pointer-events: none;
}
.legend-card {
background: var(--panel-bg);
border: 1px solid var(--panel-border);
backdrop-filter: blur(8px);
border-radius: 6px;
padding: 8px 12px;
display: flex;
flex-direction: column;
gap: 6px;
pointer-events: auto;
max-width: 280px;
}
.legend-title {
font-size: 10px;
color: var(--text-dim);
letter-spacing: 0.5px;
text-transform: uppercase;
font-family: var(--font-code);
display: flex;
justify-content: space-between;
}
.legend-items {
display: flex;
flex-direction: column;
gap: 4px;
}
.legend-row {
display: flex;
align-items: center;
gap: 8px;
font-size: 11px;
color: #cbd5e1;
}
.dot {
width: 8px;
height: 8px;
border-radius: 50%;
display: inline-block;
flex-shrink: 0;
}
.dot-inflate {
background: var(--accent-cyan);
box-shadow: 0 0 8px var(--accent-cyan);
}
.dot-deflate {
background: #1e293b;
border: 1px solid #475569;
}
.dot-contact {
background: #f59e0b;
box-shadow: 0 0 8px #f59e0b;
}
/* 控制面板 */
.ctrl-deck {
display: flex;
align-items: center;
gap: 12px;
background: var(--panel-bg);
border: 1px solid var(--panel-border);
backdrop-filter: blur(8px);
padding: 7px 14px;
border-radius: 8px;
pointer-events: auto;
}
.ctrl-group {
display: flex;
align-items: center;
gap: 8px;
}
.ctrl-group label {
font-size: 11px;
font-family: var(--font-code);
color: var(--text-dim);
}
input[type="range"] {
-webkit-appearance: none;
width: 80px;
height: 4px;
background: #1e293b;
border-radius: 2px;
outline: none;
cursor: pointer;
}
input[type="range"]::-webkit-slider-thumb {
-webkit-appearance: none;
width: 12px;
height: 12px;
border-radius: 50%;
background: var(--accent-cyan);
box-shadow: 0 0 6px var(--accent-cyan);
cursor: pointer;
}
.btn-group {
display: flex;
gap: 4px;
border-left: 1px solid rgba(255, 255, 255, 0.1);
padding-left: 8px;
}
.mode-btn {
background: rgba(255, 255, 255, 0.04);
border: 1px solid rgba(255, 255, 255, 0.12);
color: #94a3b8;
font-size: 11px;
font-family: var(--font-code);
padding: 4px 8px;
border-radius: 4px;
cursor: pointer;
transition: all 0.2s ease;
}
.mode-btn:hover {
color: #fff;
border-color: rgba(0, 243, 255, 0.4);
}
.mode-btn.active {
background: rgba(0, 243, 255, 0.15);
border-color: var(--accent-cyan);
color: var(--accent-cyan);
font-weight: 600;
}
/* 右上角截面微图示 */
.cross-section-hud {
position: absolute;
top: 56px;
right: 20px;
background: var(--panel-bg);
border: 1px solid var(--panel-border);
backdrop-filter: blur(8px);
border-radius: 6px;
padding: 8px 10px;
display: flex;
flex-direction: column;
align-items: center;
gap: 4px;
pointer-events: none;
z-index: 10;
}
.cs-title {
font-size: 9px;
font-family: var(--font-code);
color: var(--text-dim);
letter-spacing: 0.4px;
}
#crossSectionSvg {
width: 64px;
height: 64px;
}
</style>
</head>
<body>
<div class="grid-bg"></div>
<!-- 顶部轻量级技术标尺 -->
<header>
<div class="brand">
<span class="brand-tag">IFR-SOFTPNEU</span>
<div class="title-group">
<h1>无铰链多腔硅胶波纹管 · 连续行波自适应推进机构</h1>
<p>θᵢ(t) = A · sin(2πft − 2πki/N) · 8段周向120°差动充放气</p>
</div>
</div>
<div class="telemetry">
<div class="telemetry-item">
<span class="label">行波频率</span>
<span class="value" id="valFreq">2.00 Hz</span>
</div>
<div class="telemetry-item">
<span class="label">供气压强</span>
<span class="value" id="valPress">68.0 kPa</span>
</div>
<div class="telemetry-item">
<span class="label">自适应贴合度</span>
<span class="value" style="color: #34d399;" id="valFit">96.4%</span>
</div>
</div>
</header>
<!-- 右上方 120° 三腔室气压周向动态截面微图 -->
<div class="cross-section-hud">
<div class="cs-title">120°周向微气腔截面 (第4段)</div>
<svg id="crossSectionSvg" viewBox="-36 -36 72 72">
<!-- 外环波纹硅胶壁 -->
<circle cx="0" cy="0" r="30" fill="none" stroke="rgba(56,189,248,0.2)" stroke-width="3" stroke-dasharray="2,2"/>
<circle cx="0" cy="0" r="26" fill="rgba(15,23,42,0.6)" stroke="rgba(56,189,248,0.4)" stroke-width="1.5"/>
<!-- 三腔室 (0°, 120°, 240°) -->
<path id="csChamberTop" d="M -7,-6 A 14 14 0 0 1 7,-6 L 15,-18 A 24 24 0 0 0 -15,-18 Z" fill="rgba(0,243,255,0.2)" stroke="#00f3ff" stroke-width="1"/>
<path id="csChamberRight" d="M 6,3 A 14 14 0 0 1 2,10 L 17,19 A 24 24 0 0 0 23,-5 Z" fill="rgba(0,243,255,0.2)" stroke="#00f3ff" stroke-width="1"/>
<path id="csChamberLeft" d="M -2,10 A 14 14 0 0 1 -6,3 L -23,-5 A 24 24 0 0 0 -17,19 Z" fill="rgba(0,243,255,0.2)" stroke="#00f3ff" stroke-width="1"/>
<!-- 中心气管束 -->
<circle cx="0" cy="0" r="4" fill="#0f172a" stroke="rgba(255,255,255,0.3)" stroke-width="1"/>
</svg>
</div>
<!-- 主交互视区:高精细柔性波纹管与贴合蠕动动画 -->
<main>
<svg id="stage" viewBox="0 0 1200 680" preserveAspectRatio="xMidYMid meet">
<defs>
<!-- 气压高亮辉光滤镜 -->
<filter id="glow" x="-30%" y="-30%" width="160%" height="160%">
<feGaussianBlur stdDeviation="4" result="blur" />
<feComposite in="SourceGraphic" in2="blur" operator="over" />
</filter>
<filter id="glow-soft" x="-20%" y="-20%" width="140%" height="140%">
<feGaussianBlur stdDeviation="7" result="blur" />
<feComposite in="SourceGraphic" in2="blur" operator="over" />
</filter>
<!-- 狭窄不规则缝隙管壁纹理 -->
<linearGradient id="wallGradTop" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#0f172a" stop-opacity="0.95"/>
<stop offset="85%" stop-color="#1e293b" stop-opacity="0.7"/>
<stop offset="100%" stop-color="#334155" stop-opacity="0.9"/>
</linearGradient>
<linearGradient id="wallGradBottom" x1="0" y1="1" x2="0" y2="0">
<stop offset="0%" stop-color="#0f172a" stop-opacity="0.95"/>
<stop offset="85%" stop-color="#1e293b" stop-opacity="0.7"/>
<stop offset="100%" stop-color="#334155" stop-opacity="0.9"/>
</linearGradient>
<!-- 硅胶半透材质渐变 -->
<linearGradient id="siliconeGrad" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="rgba(56, 189, 248, 0.45)"/>
<stop offset="35%" stop-color="rgba(14, 165, 233, 0.15)"/>
<stop offset="70%" stop-color="rgba(2, 132, 199, 0.25)"/>
<stop offset="100%" stop-color="rgba(56, 189, 248, 0.5)"/>
</linearGradient>
<!-- 充气高压气腔色彩渐变 -->
<linearGradient id="activeChamberGrad" x1="0" y1="0" x2="1" y2="0">
<stop offset="0%" stop-color="#00f3ff" stop-opacity="0.9"/>
<stop offset="50%" stop-color="#38bdf8" stop-opacity="0.95"/>
<stop offset="100%" stop-color="#0284c7" stop-opacity="0.8"/>
</linearGradient>
</defs>
<!-- 1. 背景标尺与狭窄不规则缝隙通道 (柔性瓦砾/狭道轮廓) -->
<g id="channelGroup">
<!-- 顶部起伏软管壁 -->
<path id="topWall" fill="url(#wallGradTop)" stroke="rgba(100,116,139,0.3)" stroke-width="1.5" />
<path id="topWallContactRim" fill="none" stroke="rgba(56,189,248,0.25)" stroke-width="2" stroke-dasharray="3,3" />
<!-- 底部起伏软管壁 -->
<path id="bottomWall" fill="url(#wallGradBottom)" stroke="rgba(100,116,139,0.3)" stroke-width="1.5" />
<path id="bottomWallContactRim" fill="none" stroke="rgba(56,189,248,0.25)" stroke-width="2" stroke-dasharray="3,3" />
<!-- 狭缝间隙激光测量线 -->
<g id="gapDimensionLines" opacity="0.45"></g>
</g>
<!-- 2. 接触贴合与大面积微压强响应光晕 (IFR核心: 零卡死、柔顺分散应力) -->
<g id="contactStressGlowGroup"></g>
<!-- 3. 仿生多腔蛇本体图层 (8段连续硅胶波纹管) -->
<g id="snakeBody">
<!-- 内部8段气腔充气膨胀实体 (按相位动态着色) -->
<g id="chambersLayer"></g>
<!-- 螺旋防过拉伸凯夫拉纤维限制层 -->
<g id="fibersLayer" opacity="0.4"></g>
<!-- 8节连续锯齿状硅胶波纹管外轮廓 -->
<path id="bellowsSkinUpper" fill="none" stroke="rgba(56,189,248,0.85)" stroke-width="2.5" />
<path id="bellowsSkinLower" fill="none" stroke="rgba(56,189,248,0.85)" stroke-width="2.5" />
<path id="snakeSpine" fill="none" stroke="rgba(255,255,255,0.2)" stroke-width="1.2" stroke-dasharray="4,4" />
<!-- 节段隔断环 (8段分界) -->
<g id="segmentBorders"></g>
<!-- 头部半球导向软硅胶帽与微型探头 -->
<g id="headCap"></g>
<!-- 尾部气源接入与电磁阀组象征 -->
<g id="tailTether"></g>
</g>
<!-- 4. 气流微粒子与动态矢量流动指示 -->
<g id="pneumaticParticlesGroup"></g>
<!-- 5. 动态工程标注层 (极简跟随指针与尺寸标记) -->
<g id="annotationsGroup" pointer-events="none"></g>
</svg>
</main>
<!-- 底部说明与控制栏 (文字极精简,边角停靠,不遮挡中央演示) -->
<div class="bottom-hud">
<div class="legend-card">
<div class="legend-title">
<span>机理图例说明</span>
<span>IFR 理想解</span>
</div>
<div class="legend-items">
<div class="legend-row">
<span class="dot dot-inflate"></span>
<span>高压膨胀段 (伸长向背侧弯曲推进)</span>
</div>
<div class="legend-row">
<span class="dot dot-deflate"></span>
<span>排气卸压段 (波纹褶皱紧缩蓄能)</span>
</div>
<div class="legend-row">
<span class="dot dot-contact"></span>
<span>自适应面接触 (超低压强·无铰链防卡滞)</span>
</div>
</div>
</div>
<!-- 交互控制器 -->
<div class="ctrl-deck">
<div class="ctrl-group">
<label for="speedRange">行波频率 f</label>
<input type="range" id="speedRange" min="0.5" max="3.5" step="0.1" value="2.0">
</div>
<div class="ctrl-group">
<label for="pressRange">气压 P</label>
<input type="range" id="pressRange" min="40" max="85" step="1" value="68">
</div>
<div class="btn-group">
<button class="mode-btn active" id="btnWave" title="相位推进:产生平滑前进蠕动波">行波前进</button>
<button class="mode-btn" id="btnReverse" title="反向推进:逆向相位蠕动退出">反向退避</button>
<button class="mode-btn" id="btnStiff" title="全腔刚化:同步充气以顶开轻质瓦砾">全腔刚化</button>
</div>
</div>
</div>
<script>
/* ==========================================================================
仿生软体蛇形机器人运动学与流体仿真渲染引擎
========================================================================== */
const CONFIG = {
segments: 8, // 8段波纹管
bodyLength: 760, // 软管总基准展开长度 (px)
baseRadius: 24, // 体径约 40-48mm 视觉比例 (px)
corrugationsPerSeg: 4, // 每段含4个波纹折叠
originX: 180, // 机器人尾部起始X
originY: 340, // 通道中心高度基准
sampleCount: 160 // 沿中心轴脊椎采样点数
};
// 系统状态变量
const state = {
time: 0,
freq: 2.0, // 阀切换/行波频率 Hz
pressure: 68.0, // 工作气压 kPa
mode: 'wave', // 'wave' | 'reverse' | 'stiff'
waveSpeedSign: 1, // 1: 正向, -1: 反向, 0: 静态
amplitude: 36, // 行波弯曲幅值 (根据气压动态算)
particles: []
};
// DOM 元素引用
const stage = document.getElementById('stage');
const bellowsSkinUpper = document.getElementById('bellowsSkinUpper');
const bellowsSkinLower = document.getElementById('bellowsSkinLower');
const snakeSpine = document.getElementById('snakeSpine');
const chambersLayer = document.getElementById('chambersLayer');
const fibersLayer = document.getElementById('fibersLayer');
const segmentBorders = document.getElementById('segmentBorders');
const headCap = document.getElementById('headCap');
const tailTether = document.getElementById('tailTether');
const contactStressGlowGroup = document.getElementById('contactStressGlowGroup');
const pneumaticParticlesGroup = document.getElementById('pneumaticParticlesGroup');
const annotationsGroup = document.getElementById('annotationsGroup');
// UI 数值显示
const valFreq = document.getElementById('valFreq');
const valPress = document.getElementById('valPress');
const valFit = document.getElementById('valFit');
const speedRange = document.getElementById('speedRange');
const pressRange = document.getElementById('pressRange');
const btnWave = document.getElementById('btnWave');
const btnReverse = document.getElementById('btnReverse');
const btnStiff = document.getElementById('btnStiff');
// 周向三腔室截面指示
const csChamberTop = document.getElementById('csChamberTop');
const csChamberRight = document.getElementById('csChamberRight');
const csChamberLeft = document.getElementById('csChamberLeft');
/* --------------------------------------------------------------------------
1. 生成不规则柔性通道轮廓 (模拟瓦砾堆、生物体腔道、管道狭缝)
-------------------------------------------------------------------------- */
function buildNarrowChannel() {
const topWall = document.getElementById('topWall');
const bottomWall = document.getElementById('bottomWall');
const topRim = document.getElementById('topWallContactRim');
const bottomRim = document.getElementById('bottomWallContactRim');
const dimLines = document.getElementById('gapDimensionLines');
const width = 1200;
let topD = `M 0,0 L 1200,0 L 1200,`;
let bottomD = `M 0,680 L 1200,680 L 1200,`;
const topPoints = [];
const bottomPoints = [];
// 沿X轴生成弯曲起伏的不规则缝隙轮廓
for (let x = 0; x <= width; x += 30) {
const nx = x * 0.007;
// 狭窄度在 65px ~ 95px 之间浮动
const channelCenter = 340 + Math.sin(nx * 1.5) * 35 + Math.cos(nx * 3.2) * 15;
const halfGap = 44 + Math.sin(nx * 2.8) * 10 + Math.cos(nx * 0.8) * 8;
const yt = channelCenter - halfGap;
const yb = channelCenter + halfGap;
topPoints.push({ x, y: yt });
bottomPoints.push({ x, y: yb });
}
// 构建多边形闭合路径
let pathTopSub = ``;
let pathBottomSub = ``;
topPoints.forEach((pt, i) => {
pathTopSub += `${i === 0 ? 'M' : 'L'} ${pt.x},${pt.y.toFixed(1)} `;
});
bottomPoints.forEach((pt, i) => {
pathBottomSub += `${i === 0 ? 'M' : 'L'} ${pt.x},${pt.y.toFixed(1)} `;
});
topWall.setAttribute('d', `M 0,0 L ${width},0 L ${width},${topPoints[topPoints.length-1].y} ` +
topPoints.slice().reverse().map(p => `L ${p.x},${p.y.toFixed(1)}`).join(' ') + ` Z`);
bottomWall.setAttribute('d', `M 0,680 L ${width},680 L ${width},${bottomPoints[bottomPoints.length-1].y} ` +
bottomPoints.slice().reverse().map(p => `L ${p.x},${p.y.toFixed(1)}`).join(' ') + ` Z`);
topRim.setAttribute('d', pathTopSub);
bottomRim.setAttribute('d', pathBottomSub);
// 在特定断面添加极细微尺寸标注线
[280, 600, 920].forEach(dimX => {
const topY = sampleWallY(dimX, true);
const botY = sampleWallY(dimX, false);
const gapVal = Math.round(botY - topY);
const g = document.createElementNS('http://www.w3.org/2000/svg', 'g');
g.innerHTML = `
<line x1="${dimX}" y1="${topY}" x2="${dimX}" y2="${botY}" stroke="rgba(56,189,248,0.4)" stroke-dasharray="2,2" stroke-width="1"/>
<circle cx="${dimX}" cy="${topY}" r="2" fill="#38bdf8"/>
<circle cx="${dimX}" cy="${botY}" r="2" fill="#38bdf8"/>
<text x="${dimX + 6}" y="${(topY + botY)/2 + 3}" fill="#64748b" font-size="9" font-family="${CONFIG.fontCode || 'monospace'}">
缝隙: ${gapVal}mm
</text>
`;
dimLines.appendChild(g);
});
}
// 通道壁采样函数
function sampleWallY(x, isTop) {
const nx = x * 0.007;
const channelCenter = 340 + Math.sin(nx * 1.5) * 35 + Math.cos(nx * 3.2) * 15;
const halfGap = 44 + Math.sin(nx * 2.8) * 10 + Math.cos(nx * 0.8) * 8;
return isTop ? (channelCenter - halfGap) : (channelCenter + halfGap);
}
/* --------------------------------------------------------------------------
2. 初始化微气流粒子
-------------------------------------------------------------------------- */
function initParticles() {
state.particles = [];
for (let i = 0; i < 28; i++) {
state.particles.push({
segIndex: Math.floor(Math.random() * CONFIG.segments),
progress: Math.random(),
offsetRadial: (Math.random() - 0.5) * 16,
speed: 0.015 + Math.random() * 0.02
});
}
}
/* --------------------------------------------------------------------------
3. 核心解算:按相位生成8段蛇身行波骨架与波纹管外轮廓
-------------------------------------------------------------------------- */
function updatePhysics(dt) {
state.time += dt;
// 动态行波曲率计算 θᵢ(t) = A·sin(2πft − 2πki/N)
// 若处于刚化模式,幅值缩减至接近0,管径微胀
let currentAmp = (state.mode === 'stiff') ? 2.5 : (state.pressure / 70) * state.amplitude;
const spinePoints = [];
const N = CONFIG.segments;
const totalLen = CONFIG.bodyLength;
const stepLen = totalLen / (CONFIG.sampleCount - 1);
// 计算脊椎每个采样点的瞬时法线与中心坐标
for (let i = 0; i < CONFIG.sampleCount; i++) {
const s = i * stepLen; // 沿体长弧长
const segIdx = Math.min(N - 1, Math.floor((s / totalLen) * N));
let yOffset = 0;
let phase = 0;
if (state.mode === 'stiff') {
// 全腔高压加压:平直紧绷,抵抗外部弯折
yOffset = Math.sin(s * 0.008) * 3;
} else {
// 行波方程:按相位沿轴向传递
const waveDirection = state.waveSpeedSign;
phase = 2 * Math.PI * state.freq * state.time * waveDirection - (2 * Math.PI * 1.3 * (s / totalLen));
// 头尾锥形平滑衰减
const envelope = Math.sin((i / (CONFIG.sampleCount - 1)) * Math.PI * 0.95 + 0.05);
yOffset = currentAmp * Math.sin(phase) * envelope;
}
// 基准推进通道轴线中心微偏
const posX = CONFIG.originX + s;
// 与不规则通道轻微自适应贴合
const channelCenter = (sampleWallY(posX, true) + sampleWallY(posX, false)) / 2;
const finalY = channelCenter + yOffset;
spinePoints.push({ x: posX, y: finalY, s, segIdx, phase });
}
// 计算采样点切线与法线向量 (用于精准构建波纹管膨胀边缘)
for (let i = 0; i < spinePoints.length; i++) {
let dx, dy;
if (i === 0) {
dx = spinePoints[1].x - spinePoints[0].x;
dy = spinePoints[1].y - spinePoints[0].y;
} else if (i === spinePoints.length - 1) {
dx = spinePoints[i].x - spinePoints[i - 1].x;
dy = spinePoints[i].y - spinePoints[i - 1].y;
} else {
dx = spinePoints[i + 1].x - spinePoints[i - 1].x;
dy = spinePoints[i + 1].y - spinePoints[i - 1].y;
}
const len = Math.hypot(dx, dy) || 1;
spinePoints[i].nx = -dy / len; // 法向量 X
spinePoints[i].ny = dx / len; // 法向量 Y
}
// 渲染波纹管上下外壁轮廓
renderBellowsSkins(spinePoints);
// 渲染内部8段独立多腔室气压膨胀与色彩映射
renderChambers(spinePoints);
// 渲染螺旋限制纤维
renderFibers(spinePoints);
// 渲染管壁接触与应力波纹 (IFR: 大面积柔软贴合,无刚性集中应力)
renderContactSoftStress(spinePoints);
// 渲染气流微粒子与头部探头
renderPneumaticsAndHead(spinePoints);
// 更新右上角三腔室周向截面微雷达图
updateCrossSectionRadar(spinePoints);
}
/* --------------------------------------------------------------------------
4. 绘制锯齿状硅胶波纹管外壁 (Bellows Convolutions)
-------------------------------------------------------------------------- */
function renderBellowsSkins(spine) {
let upperD = '';
let lowerD = '';
let spineD = '';
const totalSegments = CONFIG.segments;
const rBase = CONFIG.baseRadius * (state.pressure / 68); // 气压越高管径微胀
// 脊椎中心线
spine.forEach((pt, i) => {
spineD += `${i === 0 ? 'M' : 'L'} ${pt.x.toFixed(1)},${pt.y.toFixed(1)} `;
});
snakeSpine.setAttribute('d', spineD);
// 构建波纹褶皱 (波纹管外径沿波浪交替 rBase + rib)
const corrugationFreq = 0.16; // 波纹密度
for (let i = 0; i < spine.length; i++) {
const pt = spine[i];
// 依据该段当前充气弯曲率计算内外侧折叠差:内侧压缩波纹变密、外侧展开平缓
const segPhase = Math.sin(pt.phase || 0);
// 上下侧由于行波弯曲产生的波纹高度差
const ribHeightUpper = 5.5 + segPhase * 1.5;
const ribHeightLower = 5.5 - segPhase * 1.5;
const ribOsc = Math.cos(pt.s * corrugationFreq);
const rUpper = rBase + ribOsc * ribHeightUpper;
const rLower = rBase + ribOsc * ribHeightLower;
const ux = pt.x + pt.nx * rUpper;
const uy = pt.y + pt.ny * rUpper;
const lx = pt.x - pt.nx * rLower;
const ly = pt.y - pt.ny * rLower;
upperD += `${i === 0 ? 'M' : 'L'} ${ux.toFixed(1)},${uy.toFixed(1)} `;
lowerD += `${i === 0 ? 'M' : 'L'} ${lx.toFixed(1)},${ly.toFixed(1)} `;
}
bellowsSkinUpper.setAttribute('d', upperD);
bellowsSkinLower.setAttribute('d', lowerD);
}
/* --------------------------------------------------------------------------
5. 绘制8段气腔充放气与压力色温动态映射
-------------------------------------------------------------------------- */
function renderChambers(spine) {
chambersLayer.innerHTML = '';
segmentBorders.innerHTML = '';
const N = CONFIG.segments;
const ptsPerSeg = Math.floor(spine.length / N);
for (let seg = 0; seg < N; seg++) {
const startIdx = seg * ptsPerSeg;
const endIdx = (seg === N - 1) ? spine.length - 1 : (seg + 1) * ptsPerSeg;
const midSpinePt = spine[Math.floor((startIdx + endIdx) / 2)];
// 算出第 seg 段当前气腔压力状态:上腔 vs 下腔 差动充气
// 充气段膨胀伸长,反侧收缩
let pTop, pBottom;
if (state.mode === 'stiff') {
pTop = 0.95;
pBottom = 0.95;
} else {
const waveDir = state.waveSpeedSign;
const phase = 2 * Math.PI * state.freq * state.time * waveDir - (2 * Math.PI * 1.3 * (seg / N));
// 正弦波动值映射到 0.15 (排气) ~ 1.0 (高压充气)
pTop = 0.55 + 0.45 * Math.sin(phase);
pBottom = 0.55 - 0.45 * Math.sin(phase);
}
// 构建上气腔多边形
let topChamberD = ``;
for (let i = startIdx; i <= endIdx; i++) {
const pt = spine[i];
const r = CONFIG.baseRadius * 0.85;
const x = pt.x + pt.nx * (r * pTop);
const y = pt.y + pt.ny * (r * pTop);
topChamberD += `${i === startIdx ? 'M' : 'L'} ${x.toFixed(1)},${y.toFixed(1)} `;
}
for (let i = endIdx; i >= startIdx; i--) {
const pt = spine[i];
const x = pt.x + pt.nx * 2;
const y = pt.y + pt.ny * 2;
topChamberD += `L ${x.toFixed(1)},${y.toFixed(1)} `;
}
topChamberD += 'Z';
// 构建下气腔多边形
let botChamberD = ``;
for (let i = startIdx; i <= endIdx; i++) {
const pt = spine[i];
const r = CONFIG.baseRadius * 0.85;
const x = pt.x - pt.nx * (r * pBottom);
const y = pt.y - pt.ny * (r * pBottom);
botChamberD += `${i === startIdx ? 'M' : 'L'} ${x.toFixed(1)},${y.toFixed(1)} `;
}
for (let i = endIdx; i >= startIdx; i--) {
const pt = spine[i];
const x = pt.x - pt.nx * 2;
const y = pt.y - pt.ny * 2;
botChamberD += `L ${x.toFixed(1)},${y.toFixed(1)} `;
}
botChamberD += 'Z';
// 渲染填充腔体 (高压时呈明亮电光青、低压时呈深暗蓝)
const pathTop = document.createElementNS('http://www.w3.org/2000/svg', 'path');
pathTop.setAttribute('d', topChamberD);
pathTop.setAttribute('fill', `rgba(0, 243, 255, ${(pTop * 0.65).toFixed(2)})`);
if (pTop > 0.78) {
pathTop.setAttribute('filter', 'url(#glow)');
}
chambersLayer.appendChild(pathTop);
const pathBot = document.createElementNS('http://www.w3.org/2000/svg', 'path');
pathBot.setAttribute('d', botChamberD);
pathBot.setAttribute('fill', `rgba(0, 243, 255, ${(pBottom * 0.65).toFixed(2)})`);
if (pBottom > 0.78) {
pathBot.setAttribute('filter', 'url(#glow)');
}
chambersLayer.appendChild(pathBot);
// 绘制节段间柔性加强分隔环
const borderPt = spine[startIdx];
const rRing = CONFIG.baseRadius + 5;
const line = document.createElementNS('http://www.w3.org/2000/svg', 'line');
line.setAttribute('x1', (borderPt.x + borderPt.nx * rRing).toFixed(1));
line.setAttribute('y1', (borderPt.y + borderPt.ny * rRing).toFixed(1));
line.setAttribute('x2', (borderPt.x - borderPt.nx * rRing).toFixed(1));
line.setAttribute('y2', (borderPt.y - borderPt.ny * rRing).toFixed(1));
line.setAttribute('stroke', 'rgba(148, 163, 184, 0.45)');
line.setAttribute('stroke-width', '2');
segmentBorders.appendChild(line);
// 每隔一段打上微小段编号
if (seg % 2 === 0) {
const segTag = document.createElementNS('http://www.w3.org/2000/svg', 'text');
segTag.setAttribute('x', borderPt.x.toFixed(1));
segTag.setAttribute('y', (borderPt.y - rRing - 7).toFixed(1));
segTag.setAttribute('fill', '#64748b');
segTag.setAttribute('font-size', '8');
segTag.setAttribute('font-family', 'monospace');
segTag.setAttribute('text-anchor', 'middle');
segTag.textContent = `SEG-0${seg + 1}`;
segmentBorders.appendChild(segTag);
}
}
}
/* --------------------------------------------------------------------------
6. 绘制螺旋编织限制纤维 (防止高压下径向过度鼓胀破裂)
-------------------------------------------------------------------------- */
function renderFibers(spine) {
fibersLayer.innerHTML = '';
const step = 4;
let fiberPath1 = '';
let fiberPath2 = '';
for (let i = 0; i < spine.length; i += step) {
const pt = spine[i];
const r = CONFIG.baseRadius;
const angle = i * 0.35;
const offset = Math.sin(angle) * r;
const f1x = pt.x + pt.nx * offset;
const f1y = pt.y + pt.ny * offset;
const f2x = pt.x - pt.nx * offset;
const f2y = pt.y - pt.ny * offset;
fiberPath1 += `${i === 0 ? 'M' : 'L'} ${f1x.toFixed(1)},${f1y.toFixed(1)} `;
fiberPath2 += `${i === 0 ? 'M' : 'L'} ${f2x.toFixed(1)},${f2y.toFixed(1)} `;
}
const p1 = document.createElementNS('http://www.w3.org/2000/svg', 'path');
p1.setAttribute('d', fiberPath1);
p1.setAttribute('stroke', 'rgba(245, 158, 11, 0.3)');
p1.setAttribute('stroke-width', '1');
p1.setAttribute('fill', 'none');
fibersLayer.appendChild(p1);
const p2 = document.createElementNS('http://www.w3.org/2000/svg', 'path');
p2.setAttribute('d', fiberPath2);
p2.setAttribute('stroke', 'rgba(245, 158, 11, 0.3)');
p2.setAttribute('stroke-width', '1');
p2.setAttribute('fill', 'none');
fibersLayer.appendChild(p2);
}
/* --------------------------------------------------------------------------
7. 柔顺贴合与大面积微应力场可视化 (突出消除卡死的“最终理想解”)
-------------------------------------------------------------------------- */
function renderContactSoftStress(spine) {
contactStressGlowGroup.innerHTML = '';
let contactCount = 0;
// 遍历蛇身采样点,检测与上下不规则腔壁的柔性贴合
for (let i = 8; i < spine.length - 8; i += 6) {
const pt = spine[i];
const r = CONFIG.baseRadius + 6;
const topWallY = sampleWallY(pt.x, true);
const botWallY = sampleWallY(pt.x, false);
const upperDist = Math.abs((pt.y - r) - topWallY);
const lowerDist = Math.abs((pt.y + r) - botWallY);
// 如果贴近通道壁 (软接触应力扩散区)
if (upperDist < 12) {
contactCount++;
const contactGlow = document.createElementNS('http://www.w3.org/2000/svg', 'ellipse');
contactGlow.setAttribute('cx', pt.x.toFixed(1));
contactGlow.setAttribute('cy', topWallY.toFixed(1));
contactGlow.setAttribute('rx', '24');
contactGlow.setAttribute('ry', '6');
contactGlow.setAttribute('fill', 'rgba(245, 158, 11, 0.35)');
contactGlow.setAttribute('filter', 'url(#glow-soft)');
contactStressGlowGroup.appendChild(contactGlow);
}
if (lowerDist < 12) {
contactCount++;
const contactGlow = document.createElementNS('http://www.w3.org/2000/svg', 'ellipse');
contactGlow.setAttribute('cx', pt.x.toFixed(1));
contactGlow.setAttribute('cy', botWallY.toFixed(1));
contactGlow.setAttribute('rx', '24');
contactGlow.setAttribute('ry', '6');
contactGlow.setAttribute('fill', 'rgba(245, 158, 11, 0.35)');
contactGlow.setAttribute('filter', 'url(#glow-soft)');
contactStressGlowGroup.appendChild(contactGlow);
}
}
// 实时计算自适应顺应贴合度数值,更新面板
const fitScore = Math.min(99.2, 88.0 + contactCount * 1.4);
valFit.textContent = `${fitScore.toFixed(1)}%`;
}
/* --------------------------------------------------------------------------
8. 气流微粒子、头部柔性镜头帽与尾部微电磁阀组
-------------------------------------------------------------------------- */
function renderPneumaticsAndHead(spine) {
pneumaticParticlesGroup.innerHTML = '';
// 气流粒子沿当前段流动
state.particles.forEach(p => {
p.progress += p.speed * (state.freq / 2);
if (p.progress > 1) {
p.progress = 0;
p.segIndex = Math.floor(Math.random() * CONFIG.segments);
}
const segPts = Math.floor(spine.length / CONFIG.segments);
const ptIdx = Math.min(spine.length - 1, Math.floor((p.segIndex + p.progress) * segPts));
const pt = spine[ptIdx];
if (pt) {
const px = pt.x + pt.nx * p.offsetRadial;
const py = pt.y + pt.ny * p.offsetRadial;
const circle = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
circle.setAttribute('cx', px.toFixed(1));
circle.setAttribute('cy', py.toFixed(1));
circle.setAttribute('r', '1.6');
circle.setAttribute('fill', '#00f3ff');
circle.setAttribute('opacity', (0.3 + Math.sin(p.progress * Math.PI) * 0.6).toFixed(2));
pneumaticParticlesGroup.appendChild(circle);
}
});
// 头部柔性半球硅胶帽与微型光纤摄像头
const headPt = spine[spine.length - 1];
const prevHead = spine[spine.length - 3];
const hAngle = Math.atan2(headPt.y - prevHead.y, headPt.x - prevHead.x) * (180 / Math.PI);
headCap.innerHTML = `
<g transform="translate(${headPt.x}, ${headPt.y}) rotate(${hAngle})">
<!-- 半球柔性硅胶头 -->
<path d="M 0,-${CONFIG.baseRadius} A ${CONFIG.baseRadius} ${CONFIG.baseRadius} 0 0 1 0,${CONFIG.baseRadius} Z"
fill="rgba(56,189,248,0.3)" stroke="#38bdf8" stroke-width="2"/>
<!-- 微型探头/光纤传感器光束 -->
<polygon points="4,-6 40,-20 40,20 4,6" fill="rgba(0,243,255,0.08)" />
<line x1="2" y1="0" x2="42" y2="0" stroke="rgba(0,243,255,0.5)" stroke-width="1.2" stroke-dasharray="3,3" />
<circle cx="2" cy="0" r="3.5" fill="#00f3ff" filter="url(#glow)"/>
</g>
`;
// 尾部多通道气动管路拖曳接口
const tailPt = spine[0];
const nextTail = spine[2];
const tAngle = Math.atan2(nextTail.y - tailPt.y, nextTail.x - tailPt.x) * (180 / Math.PI);
tailTether.innerHTML = `
<g transform="translate(${tailPt.x}, ${tailPt.y}) rotate(${tAngle})">
<rect x="-14" y="-20" width="14" height="40" rx="3" fill="#1e293b" stroke="rgba(56,189,248,0.5)" stroke-width="1.5"/>
<line x1="-14" y1="-8" x2="-45" y2="-12" stroke="#38bdf8" stroke-width="2.5" stroke-linecap="round"/>
<line x1="-14" y1="0" x2="-55" y2="0" stroke="#00f3ff" stroke-width="2.5" stroke-linecap="round"/>
<line x1="-14" y1="8" x2="-45" y2="12" stroke="#38bdf8" stroke-width="2.5" stroke-linecap="round"/>
<text x="-48" y="-18" fill="#64748b" font-size="8" font-family="monospace">8通道微阀阵列</text>
</g>
`;
}
/* --------------------------------------------------------------------------
9. 联动更新右上角 120° 三腔室动态压力雷达
-------------------------------------------------------------------------- */
function updateCrossSectionRadar(spine) {
// 取第 4 节段中点的数据
const midSegPt = spine[Math.floor(spine.length * 0.45)];
if (!midSegPt) return;
const phase = midSegPt.phase || 0;
// 120° 三相差动气压
const p1 = 0.5 + 0.5 * Math.sin(phase);
const p2 = 0.5 + 0.5 * Math.sin(phase + (2 * Math.PI / 3));
const p3 = 0.5 + 0.5 * Math.sin(phase + (4 * Math.PI / 3));
csChamberTop.setAttribute('fill', `rgba(0, 243, 255, ${(0.2 + p1 * 0.7).toFixed(2)})`);
csChamberRight.setAttribute('fill', `rgba(0, 243, 255, ${(0.2 + p2 * 0.7).toFixed(2)})`);
csChamberLeft.setAttribute('fill', `rgba(0, 243, 255, ${(0.2 + p3 * 0.7).toFixed(2)})`);
csChamberTop.setAttribute('stroke-width', p1 > 0.7 ? '2' : '1');
csChamberRight.setAttribute('stroke-width', p2 > 0.7 ? '2' : '1');
csChamberLeft.setAttribute('stroke-width', p3 > 0.7 ? '2' : '1');
}
/* --------------------------------------------------------------------------
10. 交互控制器事件绑定 (平滑调节与模式切换)
-------------------------------------------------------------------------- */
speedRange.addEventListener('input', (e) => {
state.freq = parseFloat(e.target.value);
valFreq.textContent = `${state.freq.toFixed(2)} Hz`;
});
pressRange.addEventListener('input', (e) => {
state.pressure = parseFloat(e.target.value);
valPress.textContent = `${state.pressure.toFixed(1)} kPa`;
});
function setActiveModeBtn(activeBtn) {
[btnWave, btnReverse, btnStiff].forEach(b => b.classList.remove('active'));
activeBtn.classList.add('active');
}
btnWave.addEventListener('click', () => {
state.mode = 'wave';
state.waveSpeedSign = 1;
setActiveModeBtn(btnWave);
});
btnReverse.addEventListener('click', () => {
state.mode = 'reverse';
state.waveSpeedSign = -1;
setActiveModeBtn(btnReverse);
});
btnStiff.addEventListener('click', () => {
state.mode = 'stiff';
state.waveSpeedSign = 0;
setActiveModeBtn(btnStiff);
});
/* --------------------------------------------------------------------------
11. 自动启动主循环 (符合重开即播、自动播放红线)
-------------------------------------------------------------------------- */
let lastTimestamp = 0;
function animationLoop(timestamp) {
if (!lastTimestamp) lastTimestamp = timestamp;
const dt = Math.min((timestamp - lastTimestamp) / 1000, 0.05); // 限幅防抽搐
lastTimestamp = timestamp;
updatePhysics(dt);
requestAnimationFrame(animationLoop);
}
// 页面完全就绪后自启动
window.addEventListener('DOMContentLoaded', () => {
buildNarrowChannel();
initParticles();
requestAnimationFrame(animationLoop);
});
// 窗口尺寸自适应保障
window.addEventListener('resize', () => {
// 保持 SVG viewBox 自适应缩放
});
</script>
</body>
</html>
积分规则:每轮对话扣10分
等待动画代码生成...
