独立渲染引擎就绪引擎就绪
<!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: #070b12;
--panel-bg: rgba(13, 22, 36, 0.7);
--panel-border: rgba(56, 189, 248, 0.18);
--accent-gold: #f59e0b;
--accent-gold-glow: rgba(245, 158, 11, 0.4);
--accent-cyan: #00f2fe;
--accent-cyan-dim: rgba(0, 242, 254, 0.25);
--accent-emerald: #10b981;
--accent-rose: #f43f5e;
--text-main: #e2e8f0;
--text-muted: #64748b;
--font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
--font-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", sans-serif;
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
user-select: none;
}
body {
background-color: var(--bg);
color: var(--text-main);
font-family: var(--font-sans);
overflow: hidden;
width: 100vw;
height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background-image:
radial-gradient(circle at 50% 30%, rgba(14, 165, 233, 0.08) 0%, transparent 65%),
linear-gradient(rgba(255, 255, 255, 0.02) 1px, transparent 1px),
linear-gradient(90deg, rgba(255, 255, 255, 0.02) 1px, transparent 1px);
background-size: 100% 100%, 36px 36px, 36px 36px;
}
/* 视窗容器 */
.viewport-container {
position: relative;
width: 100vw;
height: 100vh;
max-width: 1440px;
max-height: 900px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
/* SVG 核心画布 */
svg#stage {
width: 100%;
height: 100%;
max-height: 88vh;
overflow: visible;
filter: drop-shadow(0 20px 40px rgba(0, 0, 0, 0.6));
}
/* 角落说明文字:文字极简,尺寸小于 13px,严格靠边避让主区域 */
.hud-tag {
position: absolute;
pointer-events: none;
font-family: var(--font-mono);
font-size: 11px;
letter-spacing: 0.08em;
text-transform: uppercase;
color: var(--text-muted);
z-index: 10;
display: flex;
flex-direction: column;
gap: 3px;
}
.hud-top-left {
top: 16px;
left: 24px;
}
.hud-top-left .title-badge {
display: inline-flex;
align-items: center;
gap: 6px;
font-size: 13px;
font-weight: 600;
color: var(--text-main);
margin-bottom: 2px;
letter-spacing: 0.04em;
}
.status-dot {
width: 6px;
height: 6px;
border-radius: 50%;
background: var(--accent-emerald);
box-shadow: 0 0 8px var(--accent-emerald);
display: inline-block;
animation: pulse-dot 2s infinite ease-in-out;
}
@keyframes pulse-dot {
0%, 100% { opacity: 0.5; transform: scale(0.9); }
50% { opacity: 1; transform: scale(1.15); }
}
.hud-top-right {
top: 16px;
right: 24px;
text-align: right;
}
.metric-row {
display: flex;
justify-content: flex-end;
gap: 8px;
}
.metric-val {
color: var(--accent-cyan);
font-weight: 500;
}
/* 底部极简紧凑悬浮控制胶囊,高度紧凑,避让中央动画 */
.control-capsule {
position: absolute;
bottom: 14px;
display: flex;
align-items: center;
gap: 16px;
background: var(--panel-bg);
border: 1px solid var(--panel-border);
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
padding: 6px 18px;
border-radius: 9999px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.45);
z-index: 20;
}
.ctrl-item {
display: flex;
align-items: center;
gap: 8px;
font-size: 11px;
color: var(--text-muted);
font-family: var(--font-mono);
}
.ctrl-btn {
background: rgba(255, 255, 255, 0.06);
border: 1px solid rgba(255, 255, 255, 0.12);
color: var(--text-main);
padding: 4px 10px;
border-radius: 6px;
font-size: 11px;
cursor: pointer;
display: flex;
align-items: center;
gap: 4px;
transition: all 0.2s ease;
}
.ctrl-btn:hover {
background: rgba(56, 189, 248, 0.18);
border-color: var(--accent-cyan);
color: #fff;
}
input[type=range] {
-webkit-appearance: none;
width: 72px;
height: 3px;
background: rgba(255, 255, 255, 0.15);
border-radius: 2px;
outline: none;
cursor: pointer;
}
input[type=range]::-webkit-slider-thumb {
-webkit-appearance: none;
width: 11px;
height: 11px;
border-radius: 50%;
background: var(--accent-cyan);
box-shadow: 0 0 6px var(--accent-cyan);
cursor: pointer;
}
</style>
</head>
<body>
<div class="viewport-container">
<!-- 左上角极简说明 -->
<div class="hud-tag hud-top-left">
<div class="title-badge">
<span class="status-dot"></span>
单源贯穿软轴驱动机构 · 仿生蛇形推进
</div>
<div>1个尾部电机 → 贯穿式软轴(恒速自转) → 12组30°相位偏心轮 → 正弦波动</div>
</div>
<!-- 右上角核心物理参数 -->
<div class="hud-tag hud-top-right">
<div class="metric-row">节段数: <span class="metric-val">12 节</span> | 偏心距: <span class="metric-val">e = 6mm</span></div>
<div class="metric-row">全身体长: <span class="metric-val">600mm (1λ)</span> | 相位差: <span class="metric-val">Δφ = 30°</span></div>
<div class="metric-row">摆动幅值: <span class="metric-val">±22°</span> | 传动源: <span class="metric-val">单电机+软轴</span></div>
</div>
<!-- 核心 SVG 画布 -->
<svg id="stage" viewBox="0 0 1300 760" preserveAspectRatio="xMidYMid meet">
<defs>
<!-- 线性与径向渐变 -->
<linearGradient id="shaftGrad" x1="0%" y1="0%" x2="0%" y2="100%">
<stop offset="0%" stop-color="#fef08a" />
<stop offset="50%" stop-color="#f59e0b" />
<stop offset="100%" stop-color="#b45309" />
</linearGradient>
<linearGradient id="segGrad" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stop-color="#1e293b" stop-opacity="0.9" />
<stop offset="100%" stop-color="#0f172a" stop-opacity="0.95" />
</linearGradient>
<linearGradient id="headGrad" x1="0%" y1="0%" x2="100%" y2="0%">
<stop offset="0%" stop-color="#0284c7" />
<stop offset="100%" stop-color="#38bdf8" />
</linearGradient>
<linearGradient id="tailGrad" x1="0%" y1="0%" x2="100%" y2="0%">
<stop offset="0%" stop-color="#d97706" />
<stop offset="100%" stop-color="#b45309" />
</linearGradient>
<!-- 发光滤镜 -->
<filter id="glow-gold" x="-30%" y="-30%" width="160%" height="160%">
<feGaussianBlur stdDeviation="3.5" result="blur" />
<feMerge>
<feMergeNode in="blur" />
<feMergeNode in="SourceGraphic" />
</feMerge>
</filter>
<filter id="glow-cyan" x="-30%" y="-30%" width="160%" height="160%">
<feGaussianBlur stdDeviation="2.5" result="blur" />
<feMerge>
<feMergeNode in="blur" />
<feMergeNode in="SourceGraphic" />
</feMerge>
</filter>
<!-- 鳞片微观结构图案 -->
<pattern id="groundGrid" width="40" height="40" patternUnits="userSpaceOnUse">
<circle cx="20" cy="20" r="1" fill="#334155" opacity="0.3" />
</pattern>
</defs>
<!-- 动态推进背景标线:向后流动,直观表现蛇体向前巡航前进 -->
<g id="ground-speed-lines" opacity="0.3"></g>
<!--====================================================
区域1:【俯视全景机构运作图】 (Main Top Inspection View)
居中主要展区:展示12节骨架、内部偏心轮、柔性软轴与行波包络
=====================================================-->
<g id="top-view-group" transform="translate(60, 240)">
<!-- 导向中线与波形包络参考带 -->
<path id="wave-envelope-pos" stroke="#38bdf8" stroke-dasharray="3,3" stroke-width="0.8" fill="none" opacity="0.25" />
<path id="wave-envelope-neg" stroke="#38bdf8" stroke-dasharray="3,3" stroke-width="0.8" fill="none" opacity="0.25" />
<line x1="0" y1="0" x2="1140" y2="0" stroke="#334155" stroke-dasharray="4,6" stroke-width="1" opacity="0.4" />
<!-- 蛇体整体链节容器 (由JS实时更新位置与旋转) -->
<g id="snake-body"></g>
<!-- 贯穿式 φ4mm 弹簧钢软轴骨干 (一条连续平滑金黄色发光样条曲线) -->
<path id="continuous-flex-shaft" fill="none" stroke="url(#shaftGrad)" stroke-width="4.5" stroke-linecap="round" filter="url(#glow-gold)" opacity="0.95" />
<!-- 软轴内部扭矩螺旋线光效 -->
<path id="continuous-flex-shaft-core" fill="none" stroke="#fff" stroke-dasharray="4,7" stroke-width="1.8" opacity="0.85" />
<!-- 偏心轮与销钉摇臂层 (位于软轴之上) -->
<g id="eccentric-cams-layer"></g>
<!-- 行波传播矢量箭头标注 (向尾部传播) -->
<g id="wave-propagation-indicator" transform="translate(100, 100)">
<line x1="0" y1="0" x2="180" y2="0" stroke="#38bdf8" stroke-width="1.5" stroke-dasharray="4,3" />
<polygon points="185,0 175,-4 175,4" fill="#38bdf8" />
<text x="90" y="-8" fill="#38bdf8" font-size="10" font-family="var(--font-mono)" text-anchor="middle" letter-spacing="1">正弦弯曲行波传播方向 (向尾部 λ)</text>
</g>
<!-- 实体净前进推力箭头 (向头部前进) -->
<g id="thrust-propagation-indicator" transform="translate(940, -100)">
<line x1="0" y1="0" x2="-180" y2="0" stroke="#10b981" stroke-width="2.5" />
<polygon points="-188,0 -176,-5 -176,5" fill="#10b981" />
<text x="-90" y="-9" fill="#10b981" font-size="11" font-family="var(--font-mono)" font-weight="600" text-anchor="middle" letter-spacing="1">净推进位移方向 (F_net)</text>
</g>
</g>
<!--====================================================
区域2:底部微观机构与力学拆解 (小尺寸布局在下方,不遮挡主体)
=====================================================-->
<!-- 视窗A:单节偏心轮滑槽机构特写 (Section A: Cam-Slot Detail) -->
<g id="detail-cam-slot" transform="translate(140, 520)">
<rect x="0" y="0" width="370" height="180" rx="8" fill="rgba(15, 23, 42, 0.65)" stroke="var(--panel-border)" stroke-width="1" />
<text x="18" y="24" fill="#94a3b8" font-size="11" font-family="var(--font-mono)" font-weight="600">节段内部·单自由度偏心滑槽传动</text>
<!-- 局部机壳轮廓 -->
<g transform="translate(180, 100)">
<!-- 外壳滑槽约束 -->
<rect x="-110" y="-55" width="220" height="110" rx="8" fill="none" stroke="#475569" stroke-width="1.2" stroke-dasharray="3,3" />
<line x1="-120" y1="0" x2="120" y2="0" stroke="#334155" stroke-width="0.8" />
<!-- 滑槽导轨 -->
<rect x="-70" y="-36" width="140" height="72" rx="4" fill="rgba(30, 41, 59, 0.5)" stroke="#64748b" stroke-width="1" />
<rect id="slot-slider" x="-18" y="-30" width="36" height="60" rx="3" fill="rgba(56, 189, 248, 0.2)" stroke="#0284c7" stroke-width="1.5" />
<!-- 中心自转软轴孔 -->
<circle cx="0" cy="0" r="10" fill="#1e293b" stroke="#f59e0b" stroke-width="2" />
<circle cx="0" cy="0" r="3" fill="#fef08a" />
<!-- 偏心轮与旋转销轴 -->
<g id="demo-cam-rotator">
<circle cx="0" cy="0" r="26" fill="none" stroke="#f59e0b" stroke-dasharray="3,2" stroke-width="1" />
<!-- 偏心距 e=18px (等效放大示意) -->
<circle id="demo-eccentric-pin" cx="0" cy="-20" r="5" fill="#f43f5e" filter="url(#glow-gold)" />
<line x1="0" y1="0" x2="0" y2="-20" stroke="#f59e0b" stroke-width="2.5" />
<!-- 横向摆动连杆滑块 -->
<path id="demo-linkage" d="M 0,-20 L 0,-20" stroke="#38bdf8" stroke-width="3" stroke-linecap="round" />
</g>
<text x="0" y="66" fill="#64748b" font-size="10" font-family="var(--font-mono)" text-anchor="middle">
旋转运动 ⟳ 转为 横向单自由度摆动 ⟷
</text>
</g>
</g>
<!-- 视窗B:底部各向异性摩擦鳞片动力学 (Section B: Anisotropic Friction Scale) -->
<g id="detail-scales" transform="translate(680, 520)">
<rect x="0" y="0" width="480" height="180" rx="8" fill="rgba(15, 23, 42, 0.65)" stroke="var(--panel-border)" stroke-width="1" />
<text x="18" y="24" fill="#94a3b8" font-size="11" font-family="var(--font-mono)" font-weight="600">底部鳞片·各向异性摩擦力学分解 (咬合 vs 滑移)</text>
<g transform="translate(50, 45)">
<!-- 地面接触基线 -->
<line x1="0" y1="90" x2="380" y2="90" stroke="#475569" stroke-width="2" />
<!-- 地面微齿地貌 -->
<path d="M 0,90 l 10,6 l 10,-6 l 10,6 l 10,-6 l 10,6 l 10,-6 l 10,6 l 10,-6 l 10,6 l 10,-6 l 10,6 l 10,-6 l 10,6 l 10,-6 l 10,6 l 10,-6 l 10,6 l 10,-6 l 10,6 l 10,-6 l 10,6 l 10,-6 l 10,6 l 10,-6 l 10,6 l 10,-6 l 10,6 l 10,-6 l 10,6 l 10,-6 l 10,6 l 10,-6 l 10,6 l 10,-6 l 10,6 l 10,-6 l 10,6 l 10,-6" fill="none" stroke="#334155" stroke-width="1" />
<!-- 蛇体下腹外壳 -->
<rect x="40" y="22" width="280" height="26" rx="4" fill="#1e293b" stroke="#64748b" stroke-width="1" />
<text x="180" y="39" fill="#94a3b8" font-size="10" font-family="var(--font-mono)" text-anchor="middle">蛇体底部腹板</text>
<!-- 3组 30° 斜置弹性鳞片 -->
<!-- 鳞片1 -->
<path d="M 80,48 L 50,90" stroke="#f59e0b" stroke-width="3" stroke-linecap="round" />
<circle cx="50" cy="90" r="3" fill="#f43f5e" />
<!-- 鳞片2 -->
<path d="M 170,48 L 140,90" stroke="#f59e0b" stroke-width="3" stroke-linecap="round" />
<circle cx="140" cy="90" r="3" fill="#f43f5e" />
<!-- 鳞片3 -->
<path d="M 260,48 L 230,90" stroke="#f59e0b" stroke-width="3" stroke-linecap="round" />
<circle cx="230" cy="90" r="3" fill="#f43f5e" />
<!-- 角度与力学矢量 -->
<!-- 倾角弧线 -->
<path d="M 130,90 A 20 20 0 0 1 146,78" fill="none" stroke="#38bdf8" stroke-width="1.2" />
<text x="116" y="80" fill="#38bdf8" font-size="9" font-family="var(--font-mono)">30°</text>
<!-- 力学矢量标示 -->
<!-- 后退自锁阻力 (大) -->
<g transform="translate(140, 90)">
<line x1="0" y1="0" x2="-45" y2="0" stroke="#f43f5e" stroke-width="2" />
<polygon points="-48,0 -40,-3 -40,3" fill="#f43f5e" />
<text x="-25" y="-6" fill="#f43f5e" font-size="9" font-family="var(--font-mono)" text-anchor="middle">后退自锁 (μ_high)</text>
</g>
<!-- 前向滑动低阻 (小) -->
<g transform="translate(260, 48)">
<line x1="0" y1="0" x2="55" y2="0" stroke="#10b981" stroke-width="2" />
<polygon points="60,0 52,-3 52,3" fill="#10b981" />
<text x="32" y="-6" fill="#10b981" font-size="9" font-family="var(--font-mono)" text-anchor="middle">前向顺滑 (μ_low)</text>
</g>
<text x="190" y="122" fill="#64748b" font-size="10" font-family="var(--font-mono)" text-anchor="middle">
横向抗滑 + 轴向各向异性摩擦 → 波动能量 100% 转换为前向推力
</text>
</g>
</g>
</svg>
<!-- 底部极简控制胶囊 (高度精简,绝不遮挡视线) -->
<div class="control-capsule">
<div class="ctrl-item">
<button id="btn-toggle" class="ctrl-btn">
<span id="play-icon">⏸</span>
<span id="play-text">暂停</span>
</button>
</div>
<div class="ctrl-item">
<span>驱动转速</span>
<input type="range" id="speed-slider" min="0.4" max="2.5" step="0.1" value="1.2" />
</div>
<div class="ctrl-item">
<span>行波振幅</span>
<input type="range" id="amp-slider" min="20" max="65" step="1" value="48" />
</div>
<div class="ctrl-item">
<button id="btn-xray" class="ctrl-btn">
<span>透视机壳: ON</span>
</button>
</div>
</div>
</div>
<script>
(function () {
// 核心参数常量 (严谨对应工程数据)
const SEGMENTS_COUNT = 12; // 12节
const SEGMENT_LENGTH = 72; // 画布中每节标称长度
const ECCENTRIC_DIST = 12; // 偏心距 e (对应 6mm 比例)
const PHASE_DIFF = Math.PI / 6; // 30° 递增,12 节累计 360° 正好 1 个完整波长
// 运行状态
let isRunning = true;
let time = 0;
let speedScale = 1.2;
let amplitude = 48; // 正弦振幅
let showXray = true;
let groundOffset = 0;
// DOM 容器索引
const snakeGroup = document.getElementById("snake-body");
const camsLayer = document.getElementById("eccentric-cams-layer");
const flexShaft = document.getElementById("continuous-flex-shaft");
const flexShaftCore = document.getElementById("continuous-flex-shaft-core");
const groundLines = document.getElementById("ground-speed-lines");
const envPos = document.getElementById("wave-envelope-pos");
const envNeg = document.getElementById("wave-envelope-neg");
// 特写展示区元素
const demoCamRotator = document.getElementById("demo-cam-rotator");
const demoLinkage = document.getElementById("demo-linkage");
const slotSlider = document.getElementById("slot-slider");
// 控制器 DOM
const btnToggle = document.getElementById("btn-toggle");
const playIcon = document.getElementById("play-icon");
const playText = document.getElementById("play-text");
const speedSlider = document.getElementById("speed-slider");
const ampSlider = document.getElementById("amp-slider");
const btnXray = document.getElementById("btn-xray");
// 动态生成背景流动标记线
for (let i = 0; i < 28; i++) {
const line = document.createElementNS("http://www.w3.org/2000/svg", "line");
line.setAttribute("y1", "40");
line.setAttribute("y2", "440");
line.setAttribute("stroke", "#1e293b");
line.setAttribute("stroke-width", "1");
line.setAttribute("stroke-dasharray", "4,8");
groundLines.appendChild(line);
}
// 构建 12 个节段的主体 SVG 节点
const segmentNodes = [];
const camNodes = [];
for (let i = 0; i < SEGMENTS_COUNT; i++) {
// 节段外壳 Group
const gSeg = document.createElementNS("http://www.w3.org/2000/svg", "g");
gSeg.setAttribute("class", "snake-segment");
// 尼龙机壳几何 (带球铰倒角)
const shell = document.createElementNS("http://www.w3.org/2000/svg", "rect");
shell.setAttribute("x", -SEGMENT_LENGTH / 2);
shell.setAttribute("y", -24);
shell.setAttribute("width", SEGMENT_LENGTH - 4);
shell.setAttribute("height", 48);
shell.setAttribute("rx", "12");
shell.setAttribute("fill", "url(#segGrad)");
shell.setAttribute("stroke", "#475569");
shell.setAttribute("stroke-width", "1.2");
gSeg.appendChild(shell);
// 节间球铰与限位套环
const hinge = document.createElementNS("http://www.w3.org/2000/svg", "circle");
hinge.setAttribute("cx", -SEGMENT_LENGTH / 2);
hinge.setAttribute("cy", 0);
hinge.setAttribute("r", 5);
hinge.setAttribute("fill", "#0f172a");
hinge.setAttribute("stroke", "#64748b");
hinge.setAttribute("stroke-width", "1.5");
gSeg.appendChild(hinge);
// 内部骨架导向槽
const slot = document.createElementNS("http://www.w3.org/2000/svg", "line");
slot.setAttribute("x1", 0);
slot.setAttribute("y1", -18);
slot.setAttribute("x2", 0);
slot.setAttribute("y2", 18);
slot.setAttribute("stroke", "rgba(56, 189, 248, 0.4)");
slot.setAttribute("stroke-width", "2");
gSeg.appendChild(slot);
// 首尾特殊件区别 (第0节为头部舵机/导向头,第11节为尾部电机仓)
if (i === 0) {
const headCap = document.createElementNS("http://www.w3.org/2000/svg", "path");
headCap.setAttribute("d", `M ${SEGMENT_LENGTH / 2 - 2},-22 C ${SEGMENT_LENGTH / 2 + 30},-16 ${SEGMENT_LENGTH / 2 + 30},16 ${SEGMENT_LENGTH / 2 - 2},22 Z`);
headCap.setAttribute("fill", "url(#headGrad)");
headCap.setAttribute("opacity", "0.85");
gSeg.appendChild(headCap);
// 仿生眼部发光传感器
const eye = document.createElementNS("http://www.w3.org/2000/svg", "circle");
eye.setAttribute("cx", SEGMENT_LENGTH / 2 + 12);
eye.setAttribute("cy", -8);
eye.setAttribute("r", 2.5);
eye.setAttribute("fill", "#38bdf8");
eye.setAttribute("filter", "url(#glow-cyan)");
gSeg.appendChild(eye);
} else if (i === SEGMENTS_COUNT - 1) {
// 尾部 12V 减速电机指示
const motor = document.createElementNS("http://www.w3.org/2000/svg", "rect");
motor.setAttribute("x", -SEGMENT_LENGTH / 2 - 20);
motor.setAttribute("y", -14);
motor.setAttribute("width", 20);
motor.setAttribute("height", 28);
motor.setAttribute("rx", 3);
motor.setAttribute("fill", "url(#tailGrad)");
gSeg.appendChild(motor);
}
snakeGroup.appendChild(gSeg);
segmentNodes.push({ g: gSeg, shell });
// 偏心轮机构层 (独立图层,旋转动态直观清晰)
const gCam = document.createElementNS("http://www.w3.org/2000/svg", "g");
// 偏心圆盘
const camDisk = document.createElementNS("http://www.w3.org/2000/svg", "circle");
camDisk.setAttribute("r", 10);
camDisk.setAttribute("fill", "rgba(245, 158, 11, 0.25)");
camDisk.setAttribute("stroke", "#f59e0b");
camDisk.setAttribute("stroke-width", "1.5");
// 偏心销
const camPin = document.createElementNS("http://www.w3.org/2000/svg", "circle");
camPin.setAttribute("r", 3);
camPin.setAttribute("fill", "#f43f5e");
// 推动连杆
const pushRod = document.createElementNS("http://www.w3.org/2000/svg", "line");
pushRod.setAttribute("stroke", "#00f2fe");
pushRod.setAttribute("stroke-width", "2");
pushRod.setAttribute("stroke-linecap", "round");
gCam.appendChild(camDisk);
gCam.appendChild(pushRod);
gCam.appendChild(camPin);
camsLayer.appendChild(gCam);
camNodes.push({ g: gCam, disk: camDisk, pin: camPin, rod: pushRod });
}
// 实时正弦波与多刚体动力学计算
function updatePhysics() {
// 时间累计 (行波向前推进)
if (isRunning) {
time += 0.032 * speedScale;
groundOffset = (groundOffset + 1.8 * speedScale) % 40;
}
// 更新地面参照系运动 (营造向前巡航运动感)
const glines = groundLines.children;
for (let j = 0; j < glines.length; j++) {
const xPos = j * 45 - groundOffset;
glines[j].setAttribute("x1", xPos);
glines[j].setAttribute("x2", xPos);
}
// 计算 12 个骨架节段的全局坐标与姿态角
const spinePoints = [];
const segmentTransforms = [];
// 机器蛇头部位于右侧 (X约 960),尾部在左侧 (X约 160)
// 蜿蜒行波公式:y(x, t) = A * sin(omega * t - k * x)
for (let i = 0; i < SEGMENTS_COUNT; i++) {
// 相位随着节段向后依次滞后 30° (累计 360° 全身体长刚好一个波长)
const nodePhase = time - i * PHASE_DIFF;
// X 轴线性排布 (从头到尾)
const x = 960 - i * SEGMENT_LENGTH;
// Y 轴正弦横向位移
const y = Math.sin(nodePhase) * amplitude;
// 计算切线姿态角 (一阶导数)
const dy_dx = -amplitude * Math.cos(nodePhase) * (PHASE_DIFF / SEGMENT_LENGTH);
const angle = Math.atan(dy_dx) * (180 / Math.PI);
spinePoints.push({ x, y, angle, phase: nodePhase });
segmentTransforms.push({ x, y, angle });
}
// 渲染连续样条贯穿软轴 (Cubic Bezier Spline 拟合穿过各节中心偏心孔)
if (spinePoints.length > 0) {
let pathD = `M ${spinePoints[0].x} ${spinePoints[0].y}`;
for (let i = 0; i < spinePoints.length - 1; i++) {
const p0 = spinePoints[i];
const p1 = spinePoints[i + 1];
const mx = (p0.x + p1.x) / 2;
const my = (p0.y + p1.y) / 2;
pathD += ` Q ${p0.x} ${p0.y} ${mx} ${my}`;
}
const last = spinePoints[spinePoints.length - 1];
pathD += ` T ${last.x} ${last.y}`;
flexShaft.setAttribute("d", pathD);
flexShaftCore.setAttribute("d", pathD);
}
// 渲染正弦包络波形极限参考线
let envPosD = `M 960 ${amplitude}`;
let envNegD = `M 960 ${-amplitude}`;
for (let i = 1; i < SEGMENTS_COUNT; i++) {
const x = 960 - i * SEGMENT_LENGTH;
envPosD += ` L ${x} ${amplitude}`;
envNegD += ` L ${x} ${-amplitude}`;
}
envPos.setAttribute("d", envPosD);
envNeg.setAttribute("d", envNegD);
// 更新每个节段姿态与内部机构
for (let i = 0; i < SEGMENTS_COUNT; i++) {
const p = spinePoints[i];
const seg = segmentNodes[i];
const cam = camNodes[i];
// 节段外壳旋转与位移
seg.g.setAttribute("transform", `translate(${p.x}, ${p.y}) rotate(${p.angle})`);
seg.shell.setAttribute("fill-opacity", showXray ? "0.3" : "0.95");
seg.shell.setAttribute("stroke-opacity", showXray ? "0.7" : "1");
// 偏心轮随软轴旋转:自转角 = time (恒速自转) - i * 30°
const camRotAngle = (time - i * PHASE_DIFF) * 1.5;
const pinX = Math.cos(camRotAngle) * ECCENTRIC_DIST;
const pinY = Math.sin(camRotAngle) * ECCENTRIC_DIST;
cam.g.setAttribute("transform", `translate(${p.x}, ${p.y}) rotate(${p.angle})`);
cam.pin.setAttribute("cx", pinX);
cam.pin.setAttribute("cy", pinY);
// 连杆连接偏心销与侧壁滑槽
cam.rod.setAttribute("x1", pinX);
cam.rod.setAttribute("y1", pinY);
cam.rod.setAttribute("x2", 0);
cam.rod.setAttribute("y2", pinY > 0 ? 18 : -18);
}
// 同步更新下方特写视窗 (单节机构与滑槽动态交互演示)
const demoAngle = time * 2;
const demoPinX = Math.cos(demoAngle) * 20;
const demoPinY = Math.sin(demoAngle) * 20;
demoCamRotator.querySelector("#demo-eccentric-pin").setAttribute("cx", demoPinX);
demoCamRotator.querySelector("#demo-eccentric-pin").setAttribute("cy", demoPinY);
demoLinkage.setAttribute("x1", demoPinX);
demoLinkage.setAttribute("y1", demoPinY);
demoLinkage.setAttribute("x2", 0);
demoLinkage.setAttribute("y2", demoPinY > 0 ? 28 : -28);
slotSlider.setAttribute("transform", `translate(0, ${demoPinY * 0.7})`);
requestAnimationFrame(updatePhysics);
}
// 启动自循环动画
requestAnimationFrame(updatePhysics);
// 控件交互绑定
btnToggle.addEventListener("click", () => {
isRunning = !isRunning;
playIcon.textContent = isRunning ? "⏸" : "▶";
playText.textContent = isRunning ? "暂停" : "继续";
});
speedSlider.addEventListener("input", (e) => {
speedScale = parseFloat(e.target.value);
});
ampSlider.addEventListener("input", (e) => {
amplitude = parseFloat(e.target.value);
});
btnXray.addEventListener("click", () => {
showXray = !showXray;
btnXray.querySelector("span").textContent = `透视机壳: ${showXray ? "ON" : "OFF"}`;
});
// 页面从后台切换回或重载时,自动维持平滑播放
document.addEventListener("visibilitychange", () => {
if (!document.hidden && isRunning) {
requestAnimationFrame(updatePhysics);
}
});
})();
</script>
</body>
</html>
积分规则:每轮对话扣10分
等待动画代码生成...
