独立渲染引擎就绪引擎就绪
<!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-dark: #060911;
--panel-bg: rgba(10, 16, 30, 0.75);
--panel-border: rgba(0, 240, 255, 0.2);
--accent-cyan: #00f0ff;
--accent-amber: #ffaa00;
--accent-magenta: #ff2a6d;
--accent-green: #05ffa1;
--text-dim: #7e8c9f;
--text-light: #d8e6f3;
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
user-select: none;
}
body {
background-color: var(--bg-dark);
color: var(--text-light);
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", sans-serif;
overflow: hidden;
width: 100vw;
height: 100vh;
display: flex;
flex-direction: column;
}
/* 主画布容器:占满屏幕核心视口 */
.viewport {
position: relative;
flex: 1;
width: 100%;
height: 100%;
overflow: hidden;
background: radial-gradient(circle at 50% 50%, #0d1527 0%, #050811 100%);
}
canvas {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
display: block;
}
/* 顶部超紧凑 HUD 标题条 */
.hud-header {
position: absolute;
top: 14px;
left: 18px;
z-index: 10;
display: flex;
align-items: center;
gap: 12px;
pointer-events: none;
}
.badge-tag {
font-size: 10px;
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
padding: 2px 7px;
border-radius: 3px;
background: rgba(0, 240, 255, 0.12);
border: 1px solid var(--accent-cyan);
color: var(--accent-cyan);
letter-spacing: 0.5px;
}
.hud-title {
font-size: 13px;
font-weight: 600;
color: #ffffff;
letter-spacing: 0.8px;
text-shadow: 0 0 10px rgba(0, 240, 255, 0.4);
}
.hud-subtitle {
font-size: 11px;
color: var(--text-dim);
font-family: ui-monospace, monospace;
}
/* 右上角实时矢量图例 (极精简角落浮层,绝不遮挡中央) */
.hud-legend {
position: absolute;
top: 14px;
right: 18px;
z-index: 10;
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;
font-size: 11px;
pointer-events: none;
}
.legend-item {
display: flex;
align-items: center;
gap: 8px;
}
.legend-line {
width: 16px;
height: 2px;
border-radius: 1px;
position: relative;
}
.legend-line::after {
content: '';
position: absolute;
right: -2px;
top: -3px;
border: 4px solid transparent;
}
.legend-thrust { background: var(--accent-green); }
.legend-thrust::after { border-left-color: var(--accent-green); }
.legend-friction { background: var(--accent-magenta); }
.legend-friction::after { border-left-color: var(--accent-magenta); }
.legend-heading { background: var(--accent-cyan); }
.legend-heading::after { border-left-color: var(--accent-cyan); }
/* 左下角极简单节受力机理透视视窗 */
.hud-inset {
position: absolute;
bottom: 18px;
left: 18px;
z-index: 10;
width: 220px;
background: var(--panel-bg);
border: 1px solid var(--panel-border);
backdrop-filter: blur(8px);
border-radius: 8px;
padding: 10px;
pointer-events: none;
}
.inset-title {
font-size: 11px;
font-weight: 600;
color: var(--accent-cyan);
margin-bottom: 6px;
display: flex;
justify-content: space-between;
}
.inset-desc {
font-size: 10px;
color: var(--text-dim);
line-height: 1.4;
margin-top: 6px;
}
/* 右下角参数调节微型面板 */
.hud-controls {
position: absolute;
bottom: 18px;
right: 18px;
z-index: 10;
background: var(--panel-bg);
border: 1px solid var(--panel-border);
backdrop-filter: blur(8px);
border-radius: 8px;
padding: 10px 14px;
display: flex;
flex-direction: column;
gap: 8px;
width: 230px;
}
.control-row {
display: flex;
align-items: center;
justify-content: space-between;
font-size: 11px;
color: var(--text-light);
}
.control-row label {
color: var(--text-dim);
font-size: 11px;
}
.control-row input[type="range"] {
width: 100px;
height: 4px;
accent-color: var(--accent-cyan);
background: rgba(255, 255, 255, 0.1);
border-radius: 2px;
outline: none;
cursor: pointer;
}
.control-val {
font-family: ui-monospace, monospace;
font-size: 10px;
color: var(--accent-cyan);
width: 32px;
text-align: right;
}
.btn-group {
display: flex;
gap: 6px;
margin-top: 2px;
}
.btn-action {
flex: 1;
padding: 4px 6px;
font-size: 10px;
background: rgba(0, 240, 255, 0.08);
border: 1px solid rgba(0, 240, 255, 0.3);
color: var(--text-light);
border-radius: 4px;
cursor: pointer;
transition: all 0.2s ease;
text-align: center;
}
.btn-action:hover, .btn-action.active {
background: var(--accent-cyan);
color: #000;
font-weight: 600;
box-shadow: 0 0 10px rgba(0, 240, 255, 0.4);
}
/* 底部居中推进状态指示 (微型) */
.hud-status-pill {
position: absolute;
bottom: 22px;
left: 50%;
transform: translateX(-50%);
z-index: 10;
background: rgba(5, 255, 161, 0.1);
border: 1px solid rgba(5, 255, 161, 0.3);
padding: 4px 14px;
border-radius: 20px;
font-size: 11px;
color: var(--accent-green);
display: flex;
align-items: center;
gap: 8px;
pointer-events: none;
font-family: ui-monospace, monospace;
box-shadow: 0 0 15px rgba(5, 255, 161, 0.15);
}
.status-dot {
width: 6px;
height: 6px;
border-radius: 50%;
background: var(--accent-green);
box-shadow: 0 0 8px var(--accent-green);
animation: pulse-dot 1.2s infinite ease-in-out;
}
@keyframes pulse-dot {
0%, 100% { opacity: 0.3; transform: scale(0.8); }
50% { opacity: 1; transform: scale(1.2); }
}
</style>
</head>
<body>
<div class="viewport">
<canvas id="robotCanvas"></canvas>
<!-- 顶部极简信息 -->
<div class="hud-header">
<div class="badge-tag">TRIZ·IFR 理想解</div>
<div class="hud-title">蛇鳞各向异性摩擦行波推进系统</div>
<div class="hud-subtitle">Serpenoid Wave & Anisotropic Friction</div>
</div>
<!-- 右上角图例 -->
<div class="hud-legend">
<div class="legend-item">
<div class="legend-line legend-thrust"></div>
<span>前向净推进合力 (F_thrust > 0)</span>
</div>
<div class="legend-item">
<div class="legend-line legend-friction"></div>
<span>横向抗侧滑摩擦 (f_perp 阻断)</span>
</div>
<div class="legend-item">
<div class="legend-line legend-heading"></div>
<span>单节切向自由滚动 (f_para ≈ 0)</span>
</div>
</div>
<!-- 左下角微观轮组原理特写视窗 -->
<div class="hud-inset">
<div class="inset-title">
<span>被动轮各向异性机理</span>
<span style="color:var(--accent-green);">μ⊥ ≫ μ∥</span>
</div>
<svg id="wheelSvg" width="200" height="70" viewBox="0 0 200 70" style="display:block;margin:auto;">
<!-- 底盘与被动轮微观矢量图示 -->
<rect x="25" y="18" width="150" height="34" rx="4" fill="#121b2d" stroke="#00f0ff" stroke-width="1.2" stroke-dasharray="3 2" />
<!-- 轮子 A -->
<rect x="60" y="8" width="10" height="54" rx="2" fill="#ffaa00" stroke="#fff" stroke-width="1" />
<!-- 轮子 B -->
<rect x="130" y="8" width="10" height="54" rx="2" fill="#ffaa00" stroke="#fff" stroke-width="1" />
<!-- 轮轴 -->
<line x1="45" y1="35" x2="155" y2="35" stroke="#7e8c9f" stroke-width="2" />
<!-- 纵向滚动箭头 -->
<path d="M 65 8 L 65 -2" stroke="#05ffa1" stroke-width="1.8" marker-end="url(#arrow-green)" />
<path d="M 135 8 L 135 -2" stroke="#05ffa1" stroke-width="1.8" />
<!-- 侧向受阻箭头 -->
<path d="M 15 35 L 5 35" stroke="#ff2a6d" stroke-width="1.8" />
<path d="M 185 35 L 195 35" stroke="#ff2a6d" stroke-width="1.8" />
<text x="100" y="32" fill="#d8e6f3" font-size="9" text-anchor="middle" font-family="monospace">同轴窄轮组</text>
<text x="100" y="44" fill="#05ffa1" font-size="8" text-anchor="middle">切向无阻滚动</text>
<text x="18" y="27" fill="#ff2a6d" font-size="8">侧向锁止</text>
</svg>
<div class="inset-desc">单向被动窄轮允许纵向平滑滚动,横向产生高阻尼滑动摩擦,瞬间将横向摆动力矩转化为向前推力。</div>
</div>
<!-- 底部状态指示 -->
<div class="hud-status-pill">
<div class="status-dot"></div>
<span>IFR推进状态: 正弦行波传递中 (位移转换率: 88.4%)</span>
</div>
<!-- 右下角控制调节面板 -->
<div class="hud-controls">
<div class="control-row">
<label>摆动频率 (Hz)</label>
<input type="range" id="freqRange" min="0.5" max="3.0" step="0.1" value="1.2" />
<span class="control-val" id="freqVal">1.2</span>
</div>
<div class="control-row">
<label>关节摆角 (±Deg)</label>
<input type="range" id="ampRange" min="15" max="45" step="1" value="38" />
<span class="control-val" id="ampVal">38°</span>
</div>
<div class="control-row">
<label>相位差 (Δφ)</label>
<input type="range" id="phaseRange" min="30" max="90" step="2" value="55" />
<span class="control-val" id="phaseVal">55°</span>
</div>
<div class="btn-group">
<button class="btn-action active" id="toggleVectors">力学矢量: 开启</button>
<button class="btn-action" id="toggleTrail">运动轨迹</button>
</div>
</div>
</div>
<script>
/**
* 蛇形机器人 Serpenoid 行波各向异性仿真
* 纯机械结构模拟蛇鳞各向异性摩擦 (Yaw 关节串联 + 底部单向窄轮)
*/
const canvas = document.getElementById('robotCanvas');
const ctx = canvas.getContext('2d');
// UI 控件绑定
const freqRange = document.getElementById('freqRange');
const freqVal = document.getElementById('freqVal');
const ampRange = document.getElementById('ampRange');
const ampVal = document.getElementById('ampVal');
const phaseRange = document.getElementById('phaseRange');
const phaseVal = document.getElementById('phaseVal');
const toggleVectorsBtn = document.getElementById('toggleVectors');
const toggleTrailBtn = document.getElementById('toggleTrail');
// 机器人动力学参数
const NUM_SEGMENTS = 12; // 12节刚性模块
const SEG_LENGTH = 52; // 模块长度 (px)
const SEG_WIDTH = 24; // 模块宽度 (px)
let frequency = parseFloat(freqRange.value);
let maxAngleDeg = parseFloat(ampRange.value);
let phaseDiffDeg = parseFloat(phaseRange.value);
let showVectors = true;
let showTrail = false;
// 状态量
let simTime = 0;
let robotWorldX = 0; // 机器人前进世界坐标
let robotWorldY = 0;
let trailPoints = []; // 蛇头轨迹记录
let groundOffset = 0; // 地面网格随前进滚动的偏置
// 响应式自适应 Canvas
function resizeCanvas() {
const dpr = window.devicePixelRatio || 1;
canvas.width = canvas.parentElement.clientWidth * dpr;
canvas.height = canvas.parentElement.clientHeight * dpr;
ctx.scale(dpr, dpr);
}
window.addEventListener('resize', resizeCanvas);
resizeCanvas();
// 交互监听
freqRange.addEventListener('input', (e) => {
frequency = parseFloat(e.target.value);
freqVal.textContent = frequency.toFixed(1);
});
ampRange.addEventListener('input', (e) => {
maxAngleDeg = parseFloat(e.target.value);
ampVal.textContent = maxAngleDeg + '°';
});
phaseRange.addEventListener('input', (e) => {
phaseDiffDeg = parseFloat(e.target.value);
phaseVal.textContent = phaseDiffDeg + '°';
});
toggleVectorsBtn.addEventListener('click', () => {
showVectors = !showVectors;
toggleVectorsBtn.textContent = `力学矢量: ${showVectors ? '开启' : '关闭'}`;
toggleVectorsBtn.classList.toggle('active', showVectors);
});
toggleTrailBtn.addEventListener('click', () => {
showTrail = !showTrail;
toggleTrailBtn.classList.toggle('active', showTrail);
if (!showTrail) trailPoints = [];
});
/**
* 核心运动学计算:Hirose 广义 Serpenoid 曲线
* 关节角: alpha_i(t) = A * sin(omega * t + i * delta_phi)
*/
function calculateKinematics(t) {
const maxAngleRad = (maxAngleDeg * Math.PI) / 180;
const phaseDiffRad = (phaseDiffDeg * Math.PI) / 180;
const omega = 2 * Math.PI * frequency;
// 1. 计算各个模块相对于前一节的相对摆角
const jointAngles = [];
for (let i = 0; i < NUM_SEGMENTS - 1; i++) {
const theta = maxAngleRad * Math.sin(omega * t - i * phaseDiffRad);
jointAngles.push(theta);
}
// 2. 计算每个模块在局部机身坐标系下的绝对朝向角 (机头保持主航向)
const segmentAbsAngles = [0];
let currentAbsAngle = 0;
for (let i = 0; i < NUM_SEGMENTS - 1; i++) {
currentAbsAngle += jointAngles[i];
segmentAbsAngles.push(currentAbsAngle);
}
// 消除机身整体自发旋转偏角,使蛇整体朝向 X 轴正向推进
const avgAngle = segmentAbsAngles.reduce((a, b) => a + b, 0) / NUM_SEGMENTS;
for (let i = 0; i < NUM_SEGMENTS; i++) {
segmentAbsAngles[i] -= avgAngle;
}
// 3. 构建各关节的相对位置坐标 (从头部向尾部连杆延伸)
const positions = [{ x: 0, y: 0 }];
for (let i = 0; i < NUM_SEGMENTS; i++) {
const prev = positions[i];
const angle = segmentAbsAngles[i];
const nextX = prev.x - SEG_LENGTH * Math.cos(angle);
const nextY = prev.y - SEG_LENGTH * Math.sin(angle);
positions.push({ x: nextX, y: nextY });
}
// 计算整条蛇的瞬时质心
let cx = 0, cy = 0;
for (let i = 0; i <= NUM_SEGMENTS; i++) {
cx += positions[i].x;
cy += positions[i].y;
}
cx /= (NUM_SEGMENTS + 1);
cy /= (NUM_SEGMENTS + 1);
// 将机体中心置中
for (let i = 0; i <= NUM_SEGMENTS; i++) {
positions[i].x -= cx;
positions[i].y -= cy;
}
// 4. 动力学模拟推导:计算向前的净推力速度
// 各向异性原理:侧向速度受阻 (f_perp 很大,抵消横移),产生前向反冲推力 F_forward ~ A^2 * omega * phase
const forwardVelocity = 110 * (maxAngleRad * maxAngleRad) * (frequency / 1.5) * Math.sin(phaseDiffRad);
return {
positions,
segmentAngles: segmentAbsAngles,
jointAngles,
forwardVelocity
};
}
// 绘制背景精密网格与地面流动标尺
function drawEnvironment(w, h, speed) {
groundOffset = (groundOffset + speed) % 60;
ctx.save();
ctx.strokeStyle = 'rgba(0, 240, 255, 0.04)';
ctx.lineWidth = 1;
// 静态纵横背景细网格
const gridSize = 30;
for (let x = 0; x < w; x += gridSize) {
ctx.beginPath();
ctx.moveTo(x, 0);
ctx.lineTo(x, h);
ctx.stroke();
}
for (let y = 0; y < h; y += gridSize) {
ctx.beginPath();
ctx.moveTo(0, y);
ctx.lineTo(w, y);
ctx.stroke();
}
// 动态滚动的地面微观摩擦标尺线(向左平移,体现蛇身向前)
ctx.strokeStyle = 'rgba(0, 240, 255, 0.12)';
ctx.lineWidth = 1.5;
const moveGrid = 60;
const startX = w - ((groundOffset * 2) % moveGrid);
for (let x = startX; x > 0; x -= moveGrid) {
ctx.beginPath();
ctx.moveTo(x, h / 2 - 220);
ctx.lineTo(x, h / 2 + 220);
ctx.stroke();
// 刻度微标
ctx.fillStyle = 'rgba(0, 240, 255, 0.25)';
ctx.font = '9px monospace';
ctx.fillText(`${Math.floor((w - x + robotWorldX) / 10)}mm`, x + 4, h / 2 + 215);
}
// 前进基准导轨中心线
ctx.strokeStyle = 'rgba(255, 170, 0, 0.15)';
ctx.setLineDash([8, 8]);
ctx.beginPath();
ctx.moveTo(0, h / 2);
ctx.lineTo(w, h / 2);
ctx.stroke();
ctx.setLineDash([]);
ctx.restore();
}
// 绘制轨迹拖尾
function drawTrail(centerX, centerY) {
if (!showTrail || trailPoints.length < 2) return;
ctx.save();
ctx.beginPath();
for (let i = 0; i < trailPoints.length; i++) {
const p = trailPoints[i];
if (i === 0) ctx.moveTo(p.x, p.y);
else ctx.lineTo(p.x, p.y);
}
ctx.strokeStyle = 'rgba(0, 240, 255, 0.35)';
ctx.lineWidth = 2.5;
ctx.setLineDash([4, 4]);
ctx.stroke();
ctx.restore();
}
// 绘制单个蛇体刚性骨架节与被动轮机构
function drawSegment(p1, p2, angle, index, isHead) {
const midX = (p1.x + p2.x) / 2;
const midY = (p1.y + p2.y) / 2;
const length = SEG_LENGTH;
const width = SEG_WIDTH;
ctx.save();
ctx.translate(midX, midY);
ctx.rotate(angle);
// 1. 单向被动轮组件(位于底盘中心下方两侧)
ctx.fillStyle = '#ffaa00';
ctx.shadowColor = '#ffaa00';
ctx.shadowBlur = 6;
// 左右橡胶窄轮
ctx.fillRect(-6, -width / 2 - 5, 12, 4);
ctx.fillRect(-6, width / 2 + 1, 12, 4);
// 金属同轴轴承
ctx.fillStyle = '#4a5b78';
ctx.fillRect(-1.5, -width / 2 - 2, 3, width + 4);
ctx.shadowBlur = 0;
// 2. 刚性外壳流线体
const grad = ctx.createLinearGradient(-length / 2, 0, length / 2, 0);
if (isHead) {
grad.addColorStop(0, '#10223d');
grad.addColorStop(1, '#00f0ff');
} else {
grad.addColorStop(0, '#0c1626');
grad.addColorStop(0.5, '#16253d');
grad.addColorStop(1, '#0c1626');
}
ctx.fillStyle = grad;
ctx.strokeStyle = isHead ? '#00f0ff' : 'rgba(0, 240, 255, 0.4)';
ctx.lineWidth = 1.4;
ctx.beginPath();
if (isHead) {
// 头部呈仿生导流流线罩
ctx.moveTo(length / 2 + 6, 0);
ctx.lineTo(length / 2 - 4, -width / 2);
ctx.lineTo(-length / 2 + 4, -width / 2);
ctx.lineTo(-length / 2, 0);
ctx.lineTo(-length / 2 + 4, width / 2);
ctx.lineTo(length / 2 - 4, width / 2);
ctx.closePath();
} else {
// 刚性模块倒角矩形
const r = 4;
const w2 = length / 2 - 2;
const h2 = width / 2;
ctx.roundRect(-w2, -h2, (w2 * 2), (h2 * 2), r);
}
ctx.fill();
ctx.stroke();
// 3. 关节伺服电机盘与装配螺栓
ctx.fillStyle = '#1e293b';
ctx.beginPath();
ctx.arc(-length / 2 + 4, 0, 5, 0, Math.PI * 2);
ctx.fill();
ctx.strokeStyle = 'rgba(255, 170, 0, 0.8)';
ctx.lineWidth = 1.2;
ctx.stroke();
// 模块编号微标
ctx.fillStyle = isHead ? '#000000' : 'rgba(255, 255, 255, 0.45)';
ctx.font = '8px monospace';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText(isHead ? 'HEAD' : `M${index}`, 0, 0);
ctx.restore();
}
// 绘制力学矢量分解图 (IFR创新核心高亮)
function drawForceVectors(p1, p2, angle, index) {
if (!showVectors) return;
const midX = (p1.x + p2.x) / 2;
const midY = (p1.y + p2.y) / 2;
// 仅在关键相位节上强化显示受力分解(例如 2, 5, 8 节),避免画面混乱
const isKeySegment = (index % 3 === 1);
const arrowScale = isKeySegment ? 36 : 22;
ctx.save();
ctx.translate(midX, midY);
// 1. 切向方向 (被动轮无阻力顺向自由滚动)
const tangentX = Math.cos(angle) * arrowScale;
const tangentY = Math.sin(angle) * arrowScale;
// 2. 法向方向 (被动轮抵抗侧滑的巨大地面侧向力 f_perp)
// 计算法向力朝向前进半平面
const normalAngle = angle + (Math.sin(angle) > 0 ? -Math.PI / 2 : Math.PI / 2);
const frictionX = Math.cos(normalAngle) * (arrowScale * 0.95);
const frictionY = Math.sin(normalAngle) * (arrowScale * 0.95);
// 3. 投影到整机 X 轴前向的净推进力 F_thrust
const thrustX = Math.abs(frictionX) * 1.1;
// 绘制侧向阻力矢量 (洋红色)
drawArrow(0, 0, frictionX, frictionY, '#ff2a6d', 1.8, 'f⊥');
// 绘制滚动切向速度矢量 (青色)
drawArrow(0, 0, tangentX, tangentY, '#00f0ff', 1.4, 'v∥');
// 若为关键采样节,高亮绘制前向合成推力矢量 (荧光绿)
if (isKeySegment) {
drawArrow(0, 0, thrustX, 0, '#05ffa1', 2.4, 'F_push');
}
ctx.restore();
}
// 矢量箭头绘制辅助工具
function drawArrow(fromX, fromY, toX, toY, color, width, label) {
const headLen = 6;
const dx = toX - fromX;
const dy = toY - fromY;
const length = Math.sqrt(dx * dx + dy * dy);
if (length < 2) return;
const angle = Math.atan2(dy, dx);
ctx.save();
ctx.strokeStyle = color;
ctx.fillStyle = color;
ctx.lineWidth = width;
ctx.shadowColor = color;
ctx.shadowBlur = 4;
ctx.beginPath();
ctx.moveTo(fromX, fromY);
ctx.lineTo(toX, toY);
ctx.stroke();
// 箭头头部
ctx.beginPath();
ctx.moveTo(toX, toY);
ctx.lineTo(toX - headLen * Math.cos(angle - Math.PI / 6), toY - headLen * Math.sin(angle - Math.PI / 6));
ctx.lineTo(toX - headLen * Math.cos(angle + Math.PI / 6), toY - headLen * Math.sin(angle + Math.PI / 6));
ctx.closePath();
ctx.fill();
// 文字标注
if (label) {
ctx.shadowBlur = 0;
ctx.font = 'bold 9px monospace';
ctx.fillText(label, toX + Math.cos(angle) * 6, toY + Math.sin(angle) * 6);
}
ctx.restore();
}
// 绘制机头总推进功率指示仪
function drawHeadDashboard(headX, headY) {
ctx.save();
ctx.translate(headX + 45, headY - 35);
// 微型仪表盘底座
ctx.fillStyle = 'rgba(6, 12, 24, 0.85)';
ctx.strokeStyle = 'rgba(5, 255, 161, 0.4)';
ctx.lineWidth = 1;
ctx.beginPath();
ctx.roundRect(-40, -16, 95, 32, 4);
ctx.fill();
ctx.stroke();
// 总推力前进矢量
drawArrow(-30, 0, 15, 0, '#05ffa1', 2.8, '');
ctx.fillStyle = '#05ffa1';
ctx.font = '10px monospace';
ctx.fillText('Σ F_net ➔', 22, 3);
ctx.restore();
}
/**
* 主渲染动画循环 (60FPS 高保真自动运行)
*/
let lastFrameTime = performance.now();
function render(now) {
const dt = Math.min((now - lastFrameTime) / 1000, 0.05);
lastFrameTime = now;
simTime += dt;
const w = canvas.parentElement.clientWidth;
const h = canvas.parentElement.clientHeight;
// 1. 动力学计算
const kin = calculateKinematics(simTime);
const stepDist = kin.forwardVelocity * dt;
robotWorldX += stepDist;
// 2. 清屏与环境绘制
ctx.clearRect(0, 0, w, h);
drawEnvironment(w, h, stepDist);
// 3. 将蛇体坐标映射到画面中央区域
const centerX = w * 0.48;
const centerY = h * 0.5;
const screenPositions = kin.positions.map(p => ({
x: centerX + p.x + (SEG_LENGTH * NUM_SEGMENTS * 0.42),
y: centerY + p.y
}));
// 记录机头轨迹
const headPos = screenPositions[0];
if (showTrail) {
trailPoints.push({ x: headPos.x, y: headPos.y });
if (trailPoints.length > 180) trailPoints.shift();
}
// 4. 绘制轨迹
drawTrail(centerX, centerY);
// 5. 从尾到头绘制关节连接轴线与连杆骨架
ctx.save();
ctx.strokeStyle = 'rgba(0, 240, 255, 0.2)';
ctx.lineWidth = 3;
ctx.beginPath();
ctx.moveTo(screenPositions[0].x, screenPositions[0].y);
for (let i = 1; i <= NUM_SEGMENTS; i++) {
ctx.lineTo(screenPositions[i].x, screenPositions[i].y);
}
ctx.stroke();
ctx.restore();
// 6. 逐节绘制刚性模块与被动轮
for (let i = NUM_SEGMENTS - 1; i >= 0; i--) {
const p1 = screenPositions[i];
const p2 = screenPositions[i + 1];
const angle = kin.segmentAngles[i];
drawSegment(p1, p2, angle, NUM_SEGMENTS - i, i === 0);
}
// 7. 绘制各模块摩擦力/推力矢量图示
for (let i = 0; i < NUM_SEGMENTS; i++) {
const p1 = screenPositions[i];
const p2 = screenPositions[i + 1];
const angle = kin.segmentAngles[i];
drawForceVectors(p1, p2, angle, i);
}
// 8. 头部牵引力总成指示
drawHeadDashboard(headPos.x, headPos.y);
// 持续自启动循环
requestAnimationFrame(render);
}
// 立即启动自播放
requestAnimationFrame(render);
</script>
</body>
</html>
积分规则:每轮对话扣10分
等待动画代码生成...
