独立渲染引擎就绪引擎就绪
<!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=Rajdhani:wght@400;500;600;700&family=Share+Tech+Mono&display=swap" rel="stylesheet">
<style>
:root {
/* 核心调色板:赛博工程蓝图风格 */
--bg-color: #050914;
--grid-color: rgba(30, 58, 138, 0.4);
--panel-bg: rgba(9, 14, 23, 0.75);
--panel-border: #1e3a8a;
--neon-blue: #00f0ff;
--neon-pink: #ff0055;
--neon-green: #00ff66;
--neon-orange: #ff5500;
--text-main: #e2e8f0;
--text-muted: #94a3b8;
--font-display: 'Rajdhani', sans-serif;
--font-mono: 'Share Tech Mono', monospace;
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
background-color: var(--bg-color);
color: var(--text-main);
font-family: var(--font-display);
overflow: hidden;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
width: 100vw;
position: relative;
}
/* 背景网格 - 模拟前进运动 */
.grid-bg {
position: absolute;
top: 0;
left: 0;
width: 200vw;
height: 200vh;
background-image:
linear-gradient(to right, var(--grid-color) 1px, transparent 1px),
linear-gradient(to bottom, var(--grid-color) 1px, transparent 1px);
background-size: 50px 50px;
transform: translate(-50px, -50px);
animation: moveGrid 2s linear infinite;
z-index: 0;
opacity: 0.5;
}
@keyframes moveGrid {
0% { transform: translateX(0); }
100% { transform: translateX(-50px); } /* 网格向左移动,视觉上机器蛇向右前进 */
}
/* 动画主容器 */
#animation-container {
position: relative;
width: 100%;
height: 100%;
z-index: 10;
display: flex;
justify-content: center;
align-items: center;
}
svg {
width: 100%;
height: 100%;
display: block;
}
/* 数据仪表面板 */
.dashboard {
position: absolute;
z-index: 20;
background: var(--panel-bg);
border: 1px solid var(--panel-border);
border-radius: 4px;
backdrop-filter: blur(8px);
-webkit-backdrop-filter: blur(8px);
padding: 24px;
box-shadow: 0 0 20px rgba(0, 240, 255, 0.1), inset 0 0 10px rgba(0, 240, 255, 0.05);
}
.panel-left {
top: 40px;
left: 40px;
width: 380px;
border-left: 4px solid var(--neon-blue);
}
.panel-right {
bottom: 40px;
right: 40px;
width: 340px;
border-right: 4px solid var(--neon-pink);
}
h1 {
font-size: 24px;
font-weight: 700;
color: var(--neon-blue);
margin-bottom: 12px;
letter-spacing: 1px;
text-transform: uppercase;
}
h2 {
font-size: 16px;
color: var(--neon-pink);
margin-bottom: 16px;
font-family: var(--font-mono);
border-bottom: 1px solid rgba(255, 0, 85, 0.3);
padding-bottom: 8px;
}
p, .triz-desc {
font-size: 15px;
line-height: 1.6;
color: var(--text-main);
margin-bottom: 12px;
}
.highlight {
color: var(--neon-blue);
font-weight: 600;
text-shadow: 0 0 8px rgba(0, 240, 255, 0.4);
}
/* 数据监控台 */
.data-row {
display: flex;
justify-content: space-between;
margin-bottom: 8px;
font-family: var(--font-mono);
font-size: 14px;
background: rgba(0, 0, 0, 0.4);
padding: 6px 12px;
border-radius: 2px;
border-left: 2px solid var(--neon-orange);
}
.data-label { color: var(--text-muted); }
.data-val { color: var(--neon-green); }
/* 交互控制器 */
.control-group {
margin-top: 20px;
}
.control-item {
margin-bottom: 16px;
}
.control-item label {
display: flex;
justify-content: space-between;
font-family: var(--font-mono);
font-size: 13px;
color: var(--neon-blue);
margin-bottom: 6px;
}
input[type="range"] {
-webkit-appearance: none;
width: 100%;
height: 4px;
background: rgba(0, 240, 255, 0.2);
border-radius: 2px;
outline: none;
}
input[type="range"]::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: 16px;
height: 16px;
border-radius: 50%;
background: var(--neon-blue);
cursor: pointer;
box-shadow: 0 0 10px var(--neon-blue);
}
/* SVG 样式 */
.snake-segment {
stroke: var(--text-main);
stroke-width: 12;
stroke-linecap: round;
filter: drop-shadow(0 0 5px rgba(226, 232, 240, 0.5));
transition: stroke 0.3s;
}
.snake-joint {
fill: var(--bg-color);
stroke: var(--neon-pink);
stroke-width: 3;
filter: drop-shadow(0 0 6px var(--neon-pink));
}
.wheel {
fill: var(--bg-color);
stroke: var(--neon-orange);
stroke-width: 2;
}
.force-arrow {
stroke-width: 3;
stroke-linecap: round;
}
.force-lateral { stroke: var(--neon-orange); }
.force-thrust { stroke: var(--neon-green); }
.force-text {
font-family: var(--font-mono);
font-size: 14px;
font-weight: bold;
user-select: none;
}
/* 图例指示器 */
.legend {
display: flex;
align-items: center;
gap: 12px;
margin-top: 16px;
font-size: 13px;
}
.legend-item { display: flex; align-items: center; gap: 6px; }
.color-box { width: 12px; height: 12px; border-radius: 2px; }
.bg-orange { background: var(--neon-orange); box-shadow: 0 0 8px var(--neon-orange); }
.bg-green { background: var(--neon-green); box-shadow: 0 0 8px var(--neon-green); }
</style>
</head>
<body>
<div class="grid-bg"></div>
<div class="dashboard panel-left">
<h1>Serpenoid 动力学模型</h1>
<h2>[ TRIZ 最终理想解 IFR ]</h2>
<div class="triz-desc">
<p><strong>矛盾破除:</strong>离散的刚性电机旋转 $\rightarrow$ 连续的生物形态蜿蜒推力。</p>
<p><strong>资源利用:</strong>巧妙利用系统腹部结构,引入被动定向摩擦。无需额外主动推进轮,仅依靠姿态扭变即可将横向力转化为前进的 <span class="highlight">最终理想解</span>。</p>
</div>
<div class="data-row" style="margin-top: 20px;">
<span class="data-label">单向轮正向滚动摩擦 (推进)</span>
<span class="data-val">μ ≤ 0.1</span>
</div>
<div class="data-row">
<span class="data-label">单向轮横向滑动摩擦 (阻挡)</span>
<span class="data-val">μ ≥ 0.8</span>
</div>
<div class="data-row">
<span class="data-label">相邻关节相位差 Δφ</span>
<span class="data-val" id="val-phase">45° (π/4)</span>
</div>
<div class="legend">
<div class="legend-item"><div class="color-box bg-orange"></div> 侧向静摩擦力 (被阻挡)</div>
<div class="legend-item"><div class="color-box bg-green"></div> 前进推力 (被转化)</div>
</div>
</div>
<div class="dashboard panel-right">
<h2>系统运动控制台</h2>
<div class="control-group">
<div class="control-item">
<label><span>正弦波传递频率 (ω)</span> <span id="label-speed">1.5 Hz</span></label>
<input type="range" id="ctrl-speed" min="0.5" max="4.0" step="0.1" value="1.5">
</div>
<div class="control-item">
<label><span>关节摆动幅度 (A)</span> <span id="label-amp">35°</span></label>
<input type="range" id="ctrl-amp" min="10" max="60" step="1" value="35">
</div>
<div class="control-item">
<label><span>驻波相位差 (Δφ)</span> <span id="label-phase">45°</span></label>
<input type="range" id="ctrl-phase" min="15" max="90" step="5" value="45">
</div>
</div>
<div style="margin-top:20px; font-size:12px; color:var(--text-muted); font-family:var(--font-mono);">
> STATUS: AUTO-RUNNING<br>
> SENSOR: KINEMATICS ACTIVE<br>
> ENVIRONMENT: RIGID SURFACE
</div>
</div>
<div id="animation-container">
<svg id="snakeCanvas" viewBox="0 0 1600 900" preserveAspectRatio="xMidYMid meet">
<defs>
<!-- 箭头标记 -->
<marker id="arrow-lateral" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="6" markerHeight="6" orient="auto">
<path d="M0,1 L8,5 L0,9" fill="none" stroke="#ff5500" stroke-width="2" />
</marker>
<marker id="arrow-thrust" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="6" markerHeight="6" orient="auto">
<path d="M0,1 L8,5 L0,9" fill="none" stroke="#00ff66" stroke-width="2" />
</marker>
<!-- 发光滤镜 -->
<filter id="glow-cyan" x="-50%" y="-50%" width="200%" height="200%">
<feGaussianBlur stdDeviation="4" result="blur" />
<feComposite in="SourceGraphic" in2="blur" operator="over" />
</filter>
</defs>
<!-- 动态渲染层 -->
<g id="snake-layer"></g>
<g id="force-layer"></g>
</svg>
</div>
<script>
document.addEventListener('DOMContentLoaded', () => {
// 系统参数
const config = {
segments: 14, // 舱段数量 (10-15要求)
segmentLength: 45, // 每个舱段的长度
speed: 1.5, // 默认角速度 ω
amplitude: 35, // 默认摆幅角度 A
phaseDiff: 45, // 相邻相位差 Δφ
centerX: 800, // 视口中心X
centerY: 450 // 视口中心Y
};
// DOM 元素
const layerSnake = document.getElementById('snake-layer');
const layerForce = document.getElementById('force-layer');
const ctrlSpeed = document.getElementById('ctrl-speed');
const ctrlAmp = document.getElementById('ctrl-amp');
const ctrlPhase = document.getElementById('ctrl-phase');
// 状态变量
let time = 0;
let lastTimestamp = 0;
// 预构建SVG元素字典,避免每帧重复创建DOM
const elements = {
bodies: [],
joints: [],
wheelsL: [],
wheelsR: [],
forces: []
};
// 初始化构建 SVG 结构
function initSVG() {
layerSnake.innerHTML = '';
layerForce.innerHTML = '';
for (let i = 0; i < config.segments; i++) {
// 身体线段
const body = document.createElementNS("http://www.w3.org/2000/svg", "line");
body.setAttribute("class", "snake-segment");
layerSnake.appendChild(body);
elements.bodies.push(body);
// 腹部定向轮 (左/右)
const wheelL = document.createElementNS("http://www.w3.org/2000/svg", "rect");
wheelL.setAttribute("class", "wheel");
wheelL.setAttribute("width", "16");
wheelL.setAttribute("height", "6");
wheelL.setAttribute("rx", "2");
layerSnake.appendChild(wheelL);
elements.wheelsL.push(wheelL);
const wheelR = document.createElementNS("http://www.w3.org/2000/svg", "rect");
wheelR.setAttribute("class", "wheel");
wheelR.setAttribute("width", "16");
wheelR.setAttribute("height", "6");
wheelR.setAttribute("rx", "2");
layerSnake.appendChild(wheelR);
elements.wheelsR.push(wheelR);
// 关节圆点 (位于身体之后起连接作用,首段作为头部)
const joint = document.createElementNS("http://www.w3.org/2000/svg", "circle");
joint.setAttribute("class", "snake-joint");
joint.setAttribute("r", "8");
layerSnake.appendChild(joint);
elements.joints.push(joint);
// 力学矢量指示器 (仅部分段显示)
if (i % 3 === 1) { // 间隔显示力,避免画面杂乱
const forceGroup = document.createElementNS("http://www.w3.org/2000/svg", "g");
const lineLat = document.createElementNS("http://www.w3.org/2000/svg", "line");
lineLat.setAttribute("class", "force-arrow force-lateral");
lineLat.setAttribute("marker-end", "url(#arrow-lateral)");
const lineThrust = document.createElementNS("http://www.w3.org/2000/svg", "line");
lineThrust.setAttribute("class", "force-arrow force-thrust");
lineThrust.setAttribute("marker-end", "url(#arrow-thrust)");
const text = document.createElementNS("http://www.w3.org/2000/svg", "text");
text.setAttribute("class", "force-text");
text.setAttribute("fill", "#00ff66");
text.setAttribute("dy", "-10");
forceGroup.appendChild(lineLat);
forceGroup.appendChild(lineThrust);
forceGroup.appendChild(text);
layerForce.appendChild(forceGroup);
elements.forces.push({
index: i,
group: forceGroup,
lineLat: lineLat,
lineThrust: lineThrust,
text: text
});
}
}
// 突出头部
elements.joints[0].setAttribute("r", "12");
elements.joints[0].setAttribute("fill", "#00f0ff");
elements.joints[0].setAttribute("stroke", "#fff");
}
// 更新交互参数
function updateParams() {
config.speed = parseFloat(ctrlSpeed.value);
config.amplitude = parseFloat(ctrlAmp.value);
config.phaseDiff = parseFloat(ctrlPhase.value);
document.getElementById('label-speed').textContent = config.speed.toFixed(1) + ' Hz';
document.getElementById('label-amp').textContent = config.amplitude + '°';
document.getElementById('label-phase').textContent = config.phaseDiff + '°';
document.getElementById('val-phase').textContent = `${config.phaseDiff}° (${(config.phaseDiff/180).toFixed(2)}π)`;
}
// 核心运动学计算与渲染
function animate(timestamp) {
if (!lastTimestamp) lastTimestamp = timestamp;
const dt = (timestamp - lastTimestamp) / 1000;
lastTimestamp = timestamp;
// 更新时间系统
time += dt * config.speed * 2;
// 1. 计算相对关节角度 (正弦波列)
const relativeAngles = [];
const ampRad = config.amplitude * Math.PI / 180;
const phaseRad = config.phaseDiff * Math.PI / 180;
for (let i = 0; i < config.segments; i++) {
// 时间延迟正弦波: α_i = A * sin(ωt - i * Δφ)
const angle = ampRad * Math.sin(time - i * phaseRad);
relativeAngles.push(angle);
}
// 2. 积分计算绝对角度
const absoluteAngles = [];
// 使用头部摆动来抵消整体偏航,使蛇身整体保持水平前进方向
let headAngle = 0;
let sum = 0;
for(let i=0; i<config.segments; i++) {
sum += relativeAngles[i];
}
// 简化的偏航平衡算法
let baseAngle = -sum / config.segments;
absoluteAngles[0] = baseAngle + relativeAngles[0];
for (let i = 1; i < config.segments; i++) {
absoluteAngles[i] = absoluteAngles[i-1] + relativeAngles[i];
}
// 3. 正向运动学计算各节点坐标
const points = [{x: 0, y: 0}];
for (let i = 0; i < config.segments; i++) {
const prev = points[i];
const nx = prev.x + config.segmentLength * Math.cos(absoluteAngles[i]);
const ny = prev.y + config.segmentLength * Math.sin(absoluteAngles[i]);
points.push({x: nx, y: ny});
}
// 4. 计算质心并平移,使机器蛇居中 (配合背景网格运动形成前进错觉)
let cx = 0, cy = 0;
for(let p of points) { cx += p.x; cy += p.y; }
cx /= points.length;
cy /= points.length;
const offsetX = config.centerX - cx;
const offsetY = config.centerY - cy;
for(let p of points) {
p.x += offsetX;
p.y += offsetY;
}
// 5. 更新 SVG DOM
for (let i = 0; i < config.segments; i++) {
const p1 = points[i];
const p2 = points[i+1];
const angle = absoluteAngles[i];
// 身体线段
elements.bodies[i].setAttribute("x1", p1.x);
elements.bodies[i].setAttribute("y1", p1.y);
elements.bodies[i].setAttribute("x2", p2.x);
elements.bodies[i].setAttribute("y2", p2.y);
// 关节圆点
elements.joints[i].setAttribute("cx", p1.x);
elements.joints[i].setAttribute("cy", p1.y);
// 腹部单向轮定位 (计算法线向量平移)
const midX = (p1.x + p2.x) / 2;
const midY = (p1.y + p2.y) / 2;
const perpX = -Math.sin(angle);
const perpY = Math.cos(angle);
const wheelOffset = 12; // 轮子距离中心线的距离
// 通过 transform 进行中心旋转和平移
const deg = angle * 180 / Math.PI;
elements.wheelsL[i].setAttribute("transform", `translate(${midX + perpX * wheelOffset - 8}, ${midY + perpY * wheelOffset - 3}) rotate(${deg}, 8, 3)`);
elements.wheelsR[i].setAttribute("transform", `translate(${midX - perpX * wheelOffset - 8}, ${midY - perpY * wheelOffset - 3}) rotate(${deg}, 8, 3)`);
}
// 尾部收尾节点
if(!elements.tailJoint) {
elements.tailJoint = document.createElementNS("http://www.w3.org/2000/svg", "circle");
elements.tailJoint.setAttribute("class", "snake-joint");
elements.tailJoint.setAttribute("r", "5");
layerSnake.appendChild(elements.tailJoint);
}
const lastP = points[config.segments];
elements.tailJoint.setAttribute("cx", lastP.x);
elements.tailJoint.setAttribute("cy", lastP.y);
// 6. IFR 核心演示:力学矢量场更新
elements.forces.forEach(f => {
const i = f.index;
const p1 = points[i];
const p2 = points[i+1];
const midX = (p1.x + p2.x) / 2;
const midY = (p1.y + p2.y) / 2;
const angle = absoluteAngles[i];
// 计算横向摆动速度 (即角度变化率造成的横向速度)
// 简略用导数估算:v_lat = A * ω * cos(ωt - i*Δφ)
const latVelocity = config.amplitude * config.speed * Math.cos(time - i * phaseRad);
// 当横向速度较大时,显示受阻和推力
const forceMag = latVelocity * 0.8;
// 法向向量 (取决于横向速度方向)
const sign = latVelocity > 0 ? 1 : -1;
const perpX = -Math.sin(angle) * sign;
const perpY = Math.cos(angle) * sign;
// 切向向量 (前进方向)
const tanX = Math.cos(angle);
const tanY = Math.sin(angle);
// 若横向运动幅度足够,绘制力箭头
if (Math.abs(forceMag) > 10) {
f.group.style.opacity = 1;
// 侧向摩擦力 (抵抗滑动,指向反方向)
const fLatLen = Math.abs(forceMag) * 1.5;
f.lineLat.setAttribute("x1", midX + perpX * 20);
f.lineLat.setAttribute("y1", midY + perpY * 20);
f.lineLat.setAttribute("x2", midX + perpX * (20 + fLatLen));
f.lineLat.setAttribute("y2", midY + perpY * (20 + fLatLen));
// 转换出的前进推力
const fThrustLen = Math.abs(forceMag) * 1.2;
f.lineThrust.setAttribute("x1", midX);
f.lineThrust.setAttribute("y1", midY);
f.lineThrust.setAttribute("x2", midX + tanX * (20 + fThrustLen));
f.lineThrust.setAttribute("y2", midY + tanY * (20 + fThrustLen));
f.text.setAttribute("x", midX + tanX * (30 + fThrustLen));
f.text.setAttribute("y", midY + tanY * (30 + fThrustLen));
f.text.textContent = "推力";
} else {
f.group.style.opacity = 0; // 摆动到极值点时横向速度为0,不产生推力
}
});
// 循环动画
requestAnimationFrame(animate);
}
// 绑定事件
ctrlSpeed.addEventListener('input', updateParams);
ctrlAmp.addEventListener('input', updateParams);
ctrlPhase.addEventListener('input', updateParams);
// 初始化与启动
initSVG();
updateParams();
requestAnimationFrame(animate);
});
</script>
</body>
</html>
积分规则:第一轮对话扣减8分,后续每轮扣6分
等待动画代码生成...
