独立渲染引擎就绪引擎就绪
<!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-color: #060b13;
--grid-color: rgba(0, 240, 255, 0.08);
--neon-cyan: #00f3ff;
--neon-magenta: #ff0055;
--neon-blue: #0066ff;
--text-main: #c7e3ff;
--text-dim: #5c8bba;
--panel-bg: rgba(6, 11, 19, 0.75);
--panel-border: rgba(0, 243, 255, 0.2);
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
user-select: none;
}
body, html {
width: 100%;
height: 100%;
overflow: hidden;
background-color: var(--bg-color);
font-family: 'SF Pro Display', -apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
color: var(--text-main);
}
/* 核心动画区域容器 */
#animation-container {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
z-index: 1;
}
/* 动态网格背景(模拟前进) */
#dynamic-grid {
position: absolute;
top: -50%;
left: -50%;
width: 200%;
height: 200%;
background-image:
linear-gradient(var(--grid-color) 1px, transparent 1px),
linear-gradient(90deg, var(--grid-color) 1px, transparent 1px);
background-size: 60px 60px;
z-index: 0;
transform: perspective(600px) rotateX(20deg);
transform-origin: center center;
}
/* 覆盖层UI面板 */
.ui-panel {
position: absolute;
background: var(--panel-bg);
border: 1px solid var(--panel-border);
border-radius: 6px;
padding: 12px 16px;
backdrop-filter: blur(8px);
-webkit-backdrop-filter: blur(8px);
box-shadow: 0 4px 24px rgba(0, 0, 0, 0.5), inset 0 0 12px rgba(0, 243, 255, 0.05);
z-index: 10;
}
.panel-top-left { top: 20px; left: 20px; max-width: 280px; }
.panel-top-right { top: 20px; right: 20px; width: 240px; }
.panel-bottom-left { bottom: 20px; left: 20px; width: 280px; }
.panel-bottom-right { bottom: 20px; right: 20px; }
/* 文字排版,极致缩小以避免遮挡 */
h1 {
font-size: 14px;
font-weight: 600;
color: var(--neon-cyan);
margin-bottom: 6px;
letter-spacing: 1px;
text-transform: uppercase;
}
p, .label {
font-size: 11px;
line-height: 1.5;
color: var(--text-dim);
margin-bottom: 4px;
}
.highlight { color: var(--neon-cyan); font-weight: bold; }
.warn { color: var(--neon-magenta); }
/* 交互控件 */
.control-group {
margin-top: 10px;
}
.control-header {
display: flex;
justify-content: space-between;
font-size: 11px;
margin-bottom: 4px;
color: var(--text-main);
}
input[type=range] {
-webkit-appearance: none;
width: 100%;
background: transparent;
margin-bottom: 8px;
}
input[type=range]::-webkit-slider-thumb {
-webkit-appearance: none;
height: 12px;
width: 12px;
border-radius: 50%;
background: var(--neon-cyan);
cursor: pointer;
margin-top: -4px;
box-shadow: 0 0 8px var(--neon-cyan);
}
input[type=range]::-webkit-slider-runnable-track {
width: 100%;
height: 3px;
cursor: pointer;
background: rgba(0, 243, 255, 0.2);
border-radius: 2px;
}
/* 遥测图表 */
canvas.telemetry {
width: 100%;
height: 60px;
background: rgba(0,0,0,0.4);
border: 1px solid rgba(255,255,255,0.05);
border-radius: 4px;
margin-top: 6px;
}
.legend-item {
display: flex;
align-items: center;
font-size: 10px;
margin-top: 4px;
}
.legend-color {
width: 8px; height: 8px; border-radius: 50%; margin-right: 6px;
}
/* SVG 样式 */
svg { overflow: visible; }
.snake-segment { fill: #111a24; stroke: #1a2b3c; stroke-width: 2; }
.snake-wheel { fill: #000; stroke: #333; stroke-width: 1; }
.snake-wheel-active { stroke: var(--neon-cyan); filter: drop-shadow(0 0 3px var(--neon-cyan)); }
.snake-joint { fill: #060b13; stroke: var(--neon-blue); stroke-width: 2; transition: fill 0.1s; }
/* 发光滤镜 */
.glow-cyan { filter: drop-shadow(0 0 6px rgba(0, 240, 255, 0.6)); }
.glow-magenta { filter: drop-shadow(0 0 8px rgba(255, 0, 85, 0.8)); }
</style>
</head>
<body>
<!-- 动态背景 -->
<div id="dynamic-grid"></div>
<!-- 核心动画容器 -->
<div id="animation-container">
<svg id="robot-canvas" width="600" height="800" viewBox="-300 -400 600 800">
<defs>
<filter id="neon-glow" x="-50%" y="-50%" width="200%" height="200%">
<feGaussianBlur in="SourceGraphic" stdDeviation="3" result="blur1" />
<feGaussianBlur in="SourceGraphic" stdDeviation="8" result="blur2" />
<feMerge>
<feMergeNode in="blur2" />
<feMergeNode in="blur1" />
<feMergeNode in="SourceGraphic" />
</feMerge>
</filter>
<!-- 侧向受力阻力箭头定义 -->
<marker id="arrow-red" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="6" markerHeight="6" orient="auto">
<path d="M 0 0 L 10 5 L 0 10 Z" fill="#ff0055" />
</marker>
<!-- 纵向推进受力箭头定义 -->
<marker id="arrow-cyan" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="6" markerHeight="6" orient="auto">
<path d="M 0 0 L 10 5 L 0 10 Z" fill="#00f3ff" />
</marker>
</defs>
<!-- 蛇形机体组 -->
<g id="snake-body-group"></g>
<!-- 视觉引导组:轨迹与受力分析 -->
<g id="analysis-layer"></g>
</svg>
</div>
<!-- UI: 左上 说明与机理 -->
<div class="ui-panel panel-top-left">
<h1>驻波推进机体 (IFR模型)</h1>
<p>通过 <span class="highlight">1-DOF 伺服关节串联</span> 模拟脊椎,破除刚性限制。</p>
<p>利用Serpenoid曲线生成相位差,产生自头至尾的横向驻波。</p>
<div style="margin-top: 8px; padding-top: 8px; border-top: 1px dashed rgba(255,255,255,0.1);">
<p class="label">资源利用创新点 (TRIZ):</p>
<p>底置<span class="highlight">被动轮/特氟龙滑块</span>。利用极大的横向摩擦力阻挡侧滑,从而将横向波动转化为极小的纵向摩擦推进力。</p>
</div>
</div>
<!-- UI: 右上 相位遥测 -->
<div class="ui-panel panel-top-right">
<h1 style="font-size: 12px;">关节相位差遥测</h1>
<canvas id="phase-graph" class="telemetry"></canvas>
<div class="legend-item"><div class="legend-color" style="background:#00f3ff;"></div> 关节 1 (头部)</div>
<div class="legend-item"><div class="legend-color" style="background:#ff0055;"></div> 关节 2</div>
<div class="legend-item"><div class="legend-color" style="background:#ffcc00;"></div> 关节 3</div>
</div>
<!-- UI: 左下 交互控制 -->
<div class="ui-panel panel-bottom-left">
<h1 style="font-size: 12px;">波形参数控制</h1>
<div class="control-group">
<div class="control-header">
<span>运动频率 (波速)</span>
<span id="val-freq">2.0 Hz</span>
</div>
<input type="range" id="ctrl-freq" min="0" max="4" step="0.1" value="2.0">
</div>
<div class="control-group">
<div class="control-header">
<span>蛇行振幅 (偏转角)</span>
<span id="val-amp">±35°</span>
</div>
<input type="range" id="ctrl-amp" min="0.2" max="0.8" step="0.05" value="0.6">
</div>
<div class="control-group" style="margin-bottom:0;">
<div class="control-header">
<span>相邻相位差 (驻波比)</span>
<span id="val-phase">0.7 rad</span>
</div>
<input type="range" id="ctrl-phase" min="0.2" max="1.5" step="0.1" value="0.7">
</div>
</div>
<!-- UI: 右下 状态图例 -->
<div class="ui-panel panel-bottom-right" style="width: 180px;">
<h1 style="font-size: 12px;">力学图示</h1>
<div class="legend-item">
<svg width="20" height="10" style="margin-right:4px;"><line x1="0" y1="5" x2="16" y2="5" stroke="#ff0055" stroke-width="2" marker-end="url(#arrow-red)"/></svg>
横向抗滑阻力
</div>
<div class="legend-item">
<svg width="20" height="10" style="margin-right:4px;"><line x1="0" y1="5" x2="16" y2="5" stroke="#00f3ff" stroke-width="2" marker-end="url(#arrow-cyan)"/></svg>
前向推进分力
</div>
<div class="legend-item" style="margin-top: 8px;">
<div class="legend-color" style="background:#ff0055; box-shadow: 0 0 6px #ff0055;"></div> 峰值力矩输出 (电机)
</div>
</div>
<script>
/**
* 刚性机体驻波推进器模拟核心
*/
// --- 参数配置 ---
const NUM_SEGMENTS = 12; // 舱段数量 (>10的刚性圆柱)
const SEG_LENGTH = 36; // 舱段视觉长度
const SEG_WIDTH = 14; // 舱段宽度
const WHEEL_W = 4;
const WHEEL_H = 16;
let params = {
freq: 2.0, // 时间频率 w
amp: 0.6, // 振幅 alpha
phaseShift: 0.7 // 相位差 k (空间频率)
};
// --- DOM元素获取 ---
const svgGroup = document.getElementById('snake-body-group');
const analysisLayer = document.getElementById('analysis-layer');
const grid = document.getElementById('dynamic-grid');
const phaseCanvas = document.getElementById('phase-graph');
const ctx = phaseCanvas.getContext('2d');
// 控件更新逻辑
document.getElementById('ctrl-freq').addEventListener('input', e => { params.freq = parseFloat(e.target.value); document.getElementById('val-freq').innerText = params.freq.toFixed(1) + ' Hz'; });
document.getElementById('ctrl-amp').addEventListener('input', e => { params.amp = parseFloat(e.target.value); document.getElementById('val-amp').innerText = '±' + Math.round(params.amp * 180 / Math.PI) + '°'; });
document.getElementById('ctrl-phase').addEventListener('input', e => { params.phaseShift = parseFloat(e.target.value); document.getElementById('val-phase').innerText = params.phaseShift.toFixed(1) + ' rad'; });
// 响应式画布
function resizeCanvas() {
phaseCanvas.width = phaseCanvas.parentElement.clientWidth - 32;
phaseCanvas.height = 60;
}
window.addEventListener('resize', resizeCanvas);
resizeCanvas();
// --- 构建 SVG 元素 ---
const segments = [];
for (let i = 0; i < NUM_SEGMENTS; i++) {
// 舱段组 (包含本体,两侧被动轮)
const gSeg = document.createElementNS("http://www.w3.org/2000/svg", "g");
// 主体刚性圆柱
const rect = document.createElementNS("http://www.w3.org/2000/svg", "rect");
rect.setAttribute("x", -SEG_WIDTH/2);
rect.setAttribute("y", -SEG_LENGTH/2);
rect.setAttribute("width", SEG_WIDTH);
rect.setAttribute("height", SEG_LENGTH);
rect.setAttribute("rx", 6);
rect.setAttribute("class", "snake-segment");
gSeg.appendChild(rect);
// 左侧被动轮
const wheelL = document.createElementNS("http://www.w3.org/2000/svg", "rect");
wheelL.setAttribute("x", -SEG_WIDTH/2 - WHEEL_W - 1);
wheelL.setAttribute("y", -WHEEL_H/2);
wheelL.setAttribute("width", WHEEL_W);
wheelL.setAttribute("height", WHEEL_H);
wheelL.setAttribute("class", "snake-wheel snake-wheel-active");
gSeg.appendChild(wheelL);
// 右侧被动轮
const wheelR = document.createElementNS("http://www.w3.org/2000/svg", "rect");
wheelR.setAttribute("x", SEG_WIDTH/2 + 1);
wheelR.setAttribute("y", -WHEEL_H/2);
wheelR.setAttribute("width", WHEEL_W);
wheelR.setAttribute("height", WHEEL_H);
wheelR.setAttribute("class", "snake-wheel snake-wheel-active");
gSeg.appendChild(wheelR);
// 关节电机 (除了最后一节)
let joint = null;
if (i < NUM_SEGMENTS - 1) {
joint = document.createElementNS("http://www.w3.org/2000/svg", "circle");
joint.setAttribute("r", 5);
joint.setAttribute("class", "snake-joint");
// 挂载在舱段底部位置
joint.setAttribute("cx", 0);
joint.setAttribute("cy", SEG_LENGTH/2);
gSeg.appendChild(joint);
}
svgGroup.appendChild(gSeg);
segments.push({ g: gSeg, joint: joint, theta: 0, x: 0, y: 0 });
}
// 力学分析箭头 (复用池)
const forceArrows = [];
for (let i=0; i<NUM_SEGMENTS; i+=2) { // 采样展示,避免视觉混乱
const arrowLateral = document.createElementNS("http://www.w3.org/2000/svg", "line");
arrowLateral.setAttribute("stroke", "#ff0055");
arrowLateral.setAttribute("stroke-width", "2");
arrowLateral.setAttribute("marker-end", "url(#arrow-red)");
arrowLateral.style.opacity = "0.7";
const arrowForward = document.createElementNS("http://www.w3.org/2000/svg", "line");
arrowForward.setAttribute("stroke", "#00f3ff");
arrowForward.setAttribute("stroke-width", "2");
arrowForward.setAttribute("marker-end", "url(#arrow-cyan)");
arrowForward.style.opacity = "0.8";
analysisLayer.appendChild(arrowLateral);
analysisLayer.appendChild(arrowForward);
forceArrows.push({ lat: arrowLateral, fwd: arrowForward, idx: i });
}
// --- 动画主循环 ---
let startTime = Date.now();
let gridYOffset = 0;
let phaseHistory = [[], [], []];
function animate() {
const t = (Date.now() - startTime) / 1000.0;
// 1. 运动学解算:Serpenoid Curve (驻波形变)
// 根据方程:theta(i, t) = alpha * sin(w*t - k*i)
// 为了让机体总体朝上(-Y方向),基准角度为 -PI/2
let totalX = 0, totalY = 0;
const w = params.freq * Math.PI * 2;
const k = params.phaseShift;
const alpha = params.amp;
// 先计算所有关节的绝对朝向角度
for (let i = 0; i < NUM_SEGMENTS; i++) {
// i=0 为头部。波从头部传向尾部,产生向前的推力
segments[i].theta = -Math.PI/2 + alpha * Math.sin(w * t - k * i);
// 记录前三个关节的相位历史用于UI展示
if (i < 3) {
phaseHistory[i].push(segments[i].theta + Math.PI/2);
if (phaseHistory[i].length > phaseCanvas.width) phaseHistory[i].shift();
}
}
// 2. 几何拓扑积分计算坐标
// 设定尾部为初始计算起点,然后整体平移使其重心居中
let currentX = 0, currentY = 0;
// 以尾巴作为物理铰接链起点 (逆推)
for (let i = NUM_SEGMENTS - 1; i >= 0; i--) {
// 每节的质心
segments[i].x = currentX - (SEG_LENGTH/2) * Math.cos(segments[i].theta);
segments[i].y = currentY - (SEG_LENGTH/2) * Math.sin(segments[i].theta);
totalX += segments[i].x;
totalY += segments[i].y;
// 更新连接点给下一节
currentX = currentX - SEG_LENGTH * Math.cos(segments[i].theta);
currentY = currentY - SEG_LENGTH * Math.sin(segments[i].theta);
}
// 计算重心偏差,用以修正将整条蛇定在画面中央偏下
const cgX = totalX / NUM_SEGMENTS;
const cgY = totalY / NUM_SEGMENTS;
const offsetX = -cgX;
const offsetY = -cgY + 100; // 偏下以便看到上方轨迹
// 3. 更新 SVG 渲染与受力视觉分析
for (let i = 0; i < NUM_SEGMENTS; i++) {
const seg = segments[i];
const finalX = seg.x + offsetX;
const finalY = seg.y + offsetY;
// SVG的旋转是以度为单位
const deg = seg.theta * 180 / Math.PI + 90; // +90是因为rect默认朝上
seg.g.setAttribute("transform", `translate(${finalX}, ${finalY}) rotate(${deg})`);
// 电机峰值力矩视觉特效:根据相邻节角速度差异(即力矩大小)
if (seg.joint && i < NUM_SEGMENTS - 1) {
// 当前关节转速近似为波导数的绝对值
const torque = Math.abs(alpha * w * Math.cos(w * t - k * i));
if (torque > alpha * w * 0.7) { // 接近峰值时亮起警告色
seg.joint.setAttribute("fill", "#ff0055");
seg.joint.setAttribute("class", "snake-joint glow-magenta");
} else {
seg.joint.setAttribute("fill", "#060b13");
seg.joint.setAttribute("class", "snake-joint glow-cyan");
}
}
}
// 更新力学箭头
// 分析原理:当某节与整体前进方向( -Y )存在夹角时,产生横向滑动趋势。
// 被动轮提供极大横向摩擦(红色箭头),并产生反作用的前向分力(蓝色箭头)
forceArrows.forEach(fa => {
const seg = segments[fa.idx];
const finalX = seg.x + offsetX;
const finalY = seg.y + offsetY;
// 偏航角:相对正前方的偏角
const yaw = seg.theta - (-Math.PI/2);
// 偏航角越大,说明该节正在横向扫动,受到的抗滑力越大
const lateralForce = Math.sin(yaw) * 40; // 放大倍数用于视觉展示
const forwardForce = Math.abs(lateralForce) * 0.6 + 10;
// 横向抗滑阻力绘制 (垂直于舱体)
// 舱体法向量: cos(theta + PI/2), sin(theta + PI/2)
const nx = Math.cos(seg.theta + Math.PI/2);
const ny = Math.sin(seg.theta + Math.PI/2);
fa.lat.setAttribute("x1", finalX);
fa.lat.setAttribute("y1", finalY);
// 如果yaw为正,向左偏,阻力向右(法向正向);反之向左
const latScale = -lateralForce;
fa.lat.setAttribute("x2", finalX + nx * latScale);
fa.lat.setAttribute("y2", finalY + ny * latScale);
// 力度小时隐藏
fa.lat.style.opacity = Math.abs(latScale) > 10 ? 0.8 : 0;
// 纵向推力绘制 (沿着机体向前)
// 前向向量: cos(theta), sin(theta)
const fx = Math.cos(seg.theta);
const fy = Math.sin(seg.theta);
fa.fwd.setAttribute("x1", finalX);
fa.fwd.setAttribute("y1", finalY);
fa.fwd.setAttribute("x2", finalX + fx * forwardForce);
fa.fwd.setAttribute("y2", finalY + fy * forwardForce);
});
// 4. 背景网格相对运动 (体现宏观前进位移)
// 前进速度近似与 (振幅^2 * 频率 * 驻波比) 成正比
const forwardSpeed = params.amp * params.amp * params.freq * 20 * Math.sin(params.phaseShift);
gridYOffset += forwardSpeed * 0.016; // 60fps delta
grid.style.backgroundPosition = `center ${gridYOffset}px`;
// 5. 渲染遥测波形
drawTelemetry();
requestAnimationFrame(animate);
}
// 绘制相位差遥测图
function drawTelemetry() {
ctx.clearRect(0, 0, phaseCanvas.width, phaseCanvas.height);
const colors = ["#00f3ff", "#ff0055", "#ffcc00"];
for (let i = 0; i < 3; i++) {
ctx.beginPath();
ctx.strokeStyle = colors[i];
ctx.lineWidth = 1.5;
for (let x = 0; x < phaseHistory[i].length; x++) {
// 归一化振幅显示
const yOffset = phaseCanvas.height / 2;
const val = phaseHistory[i][x];
// alpha最大范围是0.8,映射到Canvas高度
const y = yOffset - (val / 0.8) * (yOffset - 5);
if (x === 0) ctx.moveTo(x, y);
else ctx.lineTo(x, y);
}
ctx.stroke();
}
}
// 初始化启动 (确保无交互自动播放)
window.addEventListener('DOMContentLoaded', () => {
requestAnimationFrame(animate);
});
</script>
</body>
</html>
积分规则:第一轮对话扣减8分,后续每轮扣6分
等待动画代码生成...
