独立渲染引擎就绪引擎就绪
<!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: #080c14;
--panel-bg: rgba(13, 20, 36, 0.75);
--border-color: rgba(0, 229, 255, 0.2);
--border-glow: rgba(0, 229, 255, 0.4);
--cyan-core: #00f0ff;
--orange-accent: #ff6b00;
--orange-glow: rgba(255, 107, 0, 0.4);
--green-ready: #00ff88;
--text-main: #e2e8f0;
--text-muted: #64748b;
--font-mono: "JetBrains Mono", "SF Mono", "Fira Code", monospace;
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
user-select: none;
}
body {
background-color: var(--bg-color);
color: var(--text-main);
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
overflow: hidden;
width: 100vw;
height: 100vh;
display: flex;
flex-direction: column;
justify-content: space-between;
position: relative;
}
/* 背景精细 CAD 测绘网格 */
.bg-grid {
position: absolute;
inset: 0;
background-image:
radial-gradient(circle at 50% 50%, rgba(0, 240, 255, 0.05) 0%, transparent 80%),
linear-gradient(rgba(0, 240, 255, 0.03) 1px, transparent 1px),
linear-gradient(90deg, rgba(0, 240, 255, 0.03) 1px, transparent 1px),
linear-gradient(rgba(255, 255, 255, 0.015) 1px, transparent 1px),
linear-gradient(90deg, rgba(255, 255, 255, 0.015) 1px, transparent 1px);
background-size: 100% 100%, 100px 100px, 100px 100px, 20px 20px, 20px 20px;
pointer-events: none;
z-index: 0;
}
/* 顶部紧凑状态条 - 极低存在感、不遮挡中央 */
.header-bar {
position: absolute;
top: 16px;
left: 20px;
right: 20px;
display: flex;
justify-content: space-between;
align-items: center;
z-index: 20;
pointer-events: none;
}
.brand-tag {
pointer-events: auto;
display: flex;
align-items: center;
gap: 10px;
background: var(--panel-bg);
border: 1px solid var(--border-color);
padding: 6px 14px;
border-radius: 6px;
backdrop-filter: blur(10px);
}
.status-dot {
width: 7px;
height: 7px;
border-radius: 50%;
background: var(--green-ready);
box-shadow: 0 0 10px var(--green-ready);
animation: pulse-dot 2s infinite ease-in-out;
}
.title-text {
font-size: 13px;
font-weight: 600;
letter-spacing: 0.5px;
color: #fff;
}
.title-sub {
font-size: 11px;
color: var(--cyan-core);
font-family: var(--font-mono);
margin-left: 4px;
}
/* 右上角图层切换器 */
.hud-switches {
pointer-events: auto;
display: flex;
gap: 8px;
}
.hud-btn {
background: var(--panel-bg);
border: 1px solid var(--border-color);
color: var(--text-muted);
font-size: 11px;
padding: 5px 10px;
border-radius: 4px;
cursor: pointer;
display: flex;
align-items: center;
gap: 6px;
transition: all 0.2s;
}
.hud-btn:hover {
border-color: var(--cyan-core);
color: var(--text-main);
}
.hud-btn.active {
border-color: var(--cyan-core);
color: var(--cyan-core);
background: rgba(0, 240, 255, 0.08);
box-shadow: 0 0 10px rgba(0, 240, 255, 0.15);
}
.hud-btn .key-dot {
width: 5px;
height: 5px;
border-radius: 50%;
background: currentColor;
}
/* 中央核心动画舞台 */
.viewport-container {
position: absolute;
inset: 0;
display: flex;
justify-content: center;
align-items: center;
z-index: 10;
}
#cad-stage {
width: 100%;
height: 100%;
max-width: 1400px;
max-height: 860px;
}
/* 底部紧凑控制与原理步骤提示条 */
.footer-bar {
position: absolute;
bottom: 16px;
left: 20px;
right: 20px;
z-index: 20;
display: flex;
flex-direction: column;
gap: 8px;
pointer-events: none;
}
.phase-indicator {
display: flex;
justify-content: space-between;
gap: 12px;
pointer-events: auto;
}
.phase-chip {
flex: 1;
background: var(--panel-bg);
border: 1px solid rgba(255, 255, 255, 0.08);
padding: 7px 12px;
border-radius: 5px;
display: flex;
align-items: center;
gap: 8px;
backdrop-filter: blur(12px);
transition: all 0.3s cubic-bezier(0.16, 1, 0.3, 1);
}
.phase-chip.active {
border-color: var(--orange-accent);
background: rgba(255, 107, 0, 0.12);
box-shadow: 0 0 15px rgba(255, 107, 0, 0.2);
}
.phase-idx {
font-family: var(--font-mono);
font-size: 11px;
font-weight: bold;
color: var(--text-muted);
min-width: 16px;
}
.phase-chip.active .phase-idx {
color: var(--orange-accent);
}
.phase-name {
font-size: 12px;
color: var(--text-muted);
white-space: nowrap;
}
.phase-chip.active .phase-name {
color: #fff;
font-weight: 500;
}
/* 控制面板 */
.controls-panel {
pointer-events: auto;
display: flex;
align-items: center;
gap: 14px;
background: var(--panel-bg);
border: 1px solid var(--border-color);
padding: 7px 16px;
border-radius: 6px;
backdrop-filter: blur(12px);
}
.btn-action {
background: transparent;
border: 1px solid var(--border-color);
color: var(--cyan-core);
width: 30px;
height: 30px;
border-radius: 4px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.2s;
}
.btn-action:hover {
background: var(--cyan-core);
color: #000;
box-shadow: 0 0 10px var(--cyan-core);
}
.timeline-slider {
flex: 1;
height: 4px;
-webkit-appearance: none;
background: rgba(255, 255, 255, 0.15);
border-radius: 2px;
outline: none;
cursor: pointer;
}
.timeline-slider::-webkit-slider-thumb {
-webkit-appearance: none;
width: 12px;
height: 12px;
border-radius: 50%;
background: var(--cyan-core);
box-shadow: 0 0 8px var(--cyan-core);
cursor: ew-resize;
transition: transform 0.1s;
}
.timeline-slider::-webkit-slider-thumb:hover {
transform: scale(1.3);
}
.telemetry-tag {
font-family: var(--font-mono);
font-size: 11px;
color: var(--cyan-core);
min-width: 140px;
text-align: right;
}
@keyframes pulse-dot {
0%, 100% { opacity: 0.4; transform: scale(0.9); }
50% { opacity: 1; transform: scale(1.15); }
}
/* 微型悬浮参数卡 - 紧贴右下,不挡中央视觉 */
.param-overlay {
position: absolute;
top: 60px;
right: 20px;
z-index: 15;
background: var(--panel-bg);
border: 1px solid rgba(255, 255, 255, 0.1);
padding: 10px 14px;
border-radius: 6px;
font-size: 11px;
line-height: 1.6;
pointer-events: none;
backdrop-filter: blur(8px);
}
.param-overlay .item {
display: flex;
justify-content: space-between;
gap: 16px;
color: #94a3b8;
}
.param-overlay .val {
font-family: var(--font-mono);
color: var(--cyan-core);
font-weight: 500;
}
</style>
</head>
<body>
<div class="bg-grid"></div>
<!-- 顶部状态栏 -->
<div class="header-bar">
<div class="brand-tag">
<div class="status-dot"></div>
<span class="title-text">行星三轮组阶跃越障机构</span>
<span class="title-sub">[IFR 连续小步自适应解]</span>
</div>
<div class="hud-switches">
<button class="hud-btn active" id="btn-toggle-forces" onclick="toggleLayer('forces')">
<span class="key-dot"></span> 力矩/矢量 (<span id="t-forces">开</span>)
</button>
<button class="hud-btn active" id="btn-toggle-orbits" onclick="toggleLayer('orbits')">
<span class="key-dot"></span> 回转包络线 (<span id="t-orbits">开</span>)
</button>
<button class="hud-btn active" id="btn-toggle-leveler" onclick="toggleLayer('leveler')">
<span class="key-dot"></span> 货箱水平阻尼 (<span id="t-leveler">开</span>)
</button>
</div>
</div>
<!-- 关键参数指示 -->
<div class="param-overlay">
<div class="item"><span>单轮直径 (2r):</span><span class="val">Ø 120 mm</span></div>
<div class="item"><span>回转半径 (R):</span><span class="val">165 mm</span></div>
<div class="item"><span>单向力矩阈值:</span><span class="val">6.0 N·m</span></div>
<div class="item"><span>台阶单级标高:</span><span class="val">140 mm</span></div>
</div>
<!-- 中央主舞台 SVG -->
<div class="viewport-container">
<svg id="cad-stage" viewBox="0 0 1200 680" preserveAspectRatio="xMidYMid meet">
<defs>
<!-- 发光滤镜 -->
<filter id="glow-cyan" 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-orange" x="-30%" y="-30%" width="160%" height="160%">
<feGaussianBlur stdDeviation="4" result="blur" />
<feMerge>
<feMergeNode in="blur" />
<feMergeNode in="SourceGraphic" />
</feMerge>
</filter>
<!-- 箭头标记 -->
<marker id="arrow-cyan" viewBox="0 0 10 10" refX="6" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse">
<path d="M 0 1 L 9 5 L 0 9 z" fill="#00f0ff" />
</marker>
<marker id="arrow-orange" viewBox="0 0 10 10" refX="6" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse">
<path d="M 0 1 L 9 5 L 0 9 z" fill="#ff6b00" />
</marker>
<marker id="arrow-green" viewBox="0 0 10 10" refX="6" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse">
<path d="M 0 1 L 9 5 L 0 9 z" fill="#00ff88" />
</marker>
<!-- 材质填充 -->
<pattern id="hatch-stairs" width="12" height="12" patternTransform="rotate(45 0 0)" patternUnits="userSpaceOnUse">
<line x1="0" y1="0" x2="0" y2="12" stroke="rgba(0, 240, 255, 0.08)" stroke-width="1.5" />
</pattern>
<pattern id="cargo-texture" width="10" height="10" patternUnits="userSpaceOnUse">
<rect width="10" height="10" fill="#132035" />
<path d="M 0 10 L 10 0 M 0 0 L 10 10" stroke="rgba(0, 240, 255, 0.12)" stroke-width="0.75"/>
</pattern>
</defs>
<!-- 1. 楼梯与地形环境 -->
<g id="terrain-group">
<!-- 楼梯剖面基座 -->
<path id="stair-base" d="M -50 540 L 400 540 L 400 420 L 750 420 L 750 300 L 1250 300 L 1250 640 L -50 640 Z"
fill="url(#hatch-stairs)" stroke="rgba(0, 240, 255, 0.4)" stroke-width="2" />
<!-- 楼梯踏面加固线 -->
<path d="M -50 540 L 400 540 L 400 420 L 750 420 L 750 300 L 1250 300"
fill="none" stroke="#00f0ff" stroke-width="3" filter="url(#glow-cyan)" />
<!-- 踏面防滑纹理与倒角高亮 -->
<g stroke="rgba(0, 240, 255, 0.3)" stroke-width="1">
<line x1="400" y1="540" x2="400" y2="420" stroke="#ff6b00" stroke-width="2.5" />
<line x1="750" y1="420" x2="750" y2="300" stroke="#ff6b00" stroke-width="2.5" />
<circle cx="400" cy="420" r="3" fill="#ff6b00" />
<circle cx="750" cy="300" r="3" fill="#ff6b00" />
</g>
<!-- 尺寸标注 (高度与宽度) -->
<g id="layer-dim" stroke="#64748b" stroke-width="1" font-family="JetBrains Mono" font-size="11" fill="#64748b">
<!-- 阶高标尺 -->
<line x1="380" y1="540" x2="380" y2="420" marker-start="url(#arrow-cyan)" marker-end="url(#arrow-cyan)" stroke="#00f0ff" stroke-opacity="0.6"/>
<text x="365" y="485" text-anchor="end" fill="#00f0ff">H=140mm</text>
<line x1="730" y1="420" x2="730" y2="300" marker-start="url(#arrow-cyan)" marker-end="url(#arrow-cyan)" stroke="#00f0ff" stroke-opacity="0.6"/>
<text x="715" y="365" text-anchor="end" fill="#00f0ff">H=140mm</text>
<!-- 踏宽标尺 -->
<line x1="400" y1="560" x2="750" y2="560" marker-start="url(#arrow-cyan)" marker-end="url(#arrow-cyan)" stroke="#00f0ff" stroke-opacity="0.6"/>
<text x="575" y="580" text-anchor="middle" fill="#00f0ff">Step Width = 350mm</text>
</g>
</g>
<!-- 2. 小车主机构动态装配组 (由 JS 驱动位置与姿态) -->
<g id="cart-assembly">
<!-- 回转轨迹层 -->
<g id="layer-orbits" stroke="rgba(0, 240, 255, 0.25)" stroke-dasharray="4,4" fill="none">
<circle id="front-orbit" cx="0" cy="0" r="68" />
<circle id="rear-orbit" cx="0" cy="0" r="68" />
</g>
<!-- 车架主梁 -->
<path id="chassis-beam" d="M 0 0 L 260 0" stroke="#334155" stroke-width="14" stroke-linecap="round" />
<path id="chassis-core" d="M 0 0 L 260 0" stroke="#00f0ff" stroke-width="3" />
<!-- 阻尼器与自调平机构连杆 -->
<g id="layer-leveler">
<!-- 阻尼器套筒 -->
<line id="damper-line" x1="130" y1="0" x2="130" y2="-65" stroke="#ff6b00" stroke-width="4" stroke-dasharray="6,3" />
<circle id="damper-joint-b" cx="130" cy="0" r="5" fill="#ff6b00" />
<circle id="damper-joint-t" cx="130" cy="-65" r="5" fill="#ff6b00" />
<!-- 被动防倾摆臂 -->
<line id="arm-f" x1="40" y1="0" x2="60" y2="-60" stroke="#00f0ff" stroke-width="3" stroke-opacity="0.8" />
<line id="arm-r" x1="220" y1="0" x2="200" y2="-60" stroke="#00f0ff" stroke-width="3" stroke-opacity="0.8" />
</g>
<!-- 货箱平台 (保持水平) -->
<g id="cargo-platform">
<rect id="cargo-box" x="20" y="-120" width="220" height="58" rx="6" fill="url(#cargo-texture)" stroke="#00f0ff" stroke-width="2" />
<!-- 货物重心与水平仪 -->
<line x1="30" y1="-91" x2="230" y2="-91" stroke="rgba(0,240,255,0.3)" stroke-width="1" stroke-dasharray="3,3" />
<circle cx="130" cy="-91" r="9" fill="rgba(0, 255, 136, 0.2)" stroke="#00ff88" stroke-width="1.5" />
<line x1="121" y1="-91" x2="139" y2="-91" stroke="#00ff88" stroke-width="1.5" />
<line x1="130" y1="-100" x2="130" y2="-82" stroke="#00ff88" stroke-width="1.5" />
<text x="145" y="-88" font-family="JetBrains Mono" font-size="10" fill="#00ff88">CARGO LEVEL (0°)</text>
</g>
<!-- 后行星三轮组 -->
<g id="rear-planetary-set" transform="translate(0, 0)">
<!-- 转臂 (Y型等角 120度) -->
<g id="rear-spider" stroke="#475569" stroke-width="12" stroke-linejoin="round" stroke-linecap="round">
<line x1="0" y1="0" x2="0" y2="-68" />
<line x1="0" y1="0" x2="58.89" y2="34" />
<line x1="0" y1="0" x2="-58.89" y2="34" />
</g>
<g id="rear-spider-glow" stroke="#00f0ff" stroke-width="2" stroke-linejoin="round" stroke-linecap="round" fill="none">
<line x1="0" y1="0" x2="0" y2="-68" />
<line x1="0" y1="0" x2="58.89" y2="34" />
<line x1="0" y1="0" x2="-58.89" y2="34" />
</g>
<!-- 3个行星橡胶轮 (R=25, 直径120mm缩比) -->
<g id="rear-wheels">
<g id="rw-0" transform="translate(0, -68)">
<circle r="25" fill="#1e293b" stroke="#00f0ff" stroke-width="3" />
<circle r="8" fill="#080c14" stroke="#64748b" stroke-width="2" />
<line x1="-20" y1="0" x2="20" y2="0" stroke="#00f0ff" stroke-width="1.5" />
<line x1="0" y1="-20" x2="0" y2="20" stroke="#00f0ff" stroke-width="1.5" />
</g>
<g id="rw-1" transform="translate(58.89, 34)">
<circle r="25" fill="#1e293b" stroke="#00f0ff" stroke-width="3" />
<circle r="8" fill="#080c14" stroke="#64748b" stroke-width="2" />
<line x1="-20" y1="0" x2="20" y2="0" stroke="#00f0ff" stroke-width="1.5" />
<line x1="0" y1="-20" x2="0" y2="20" stroke="#00f0ff" stroke-width="1.5" />
</g>
<g id="rw-2" transform="translate(-58.89, 34)">
<circle r="25" fill="#1e293b" stroke="#00f0ff" stroke-width="3" />
<circle r="8" fill="#080c14" stroke="#64748b" stroke-width="2" />
<line x1="-20" y1="0" x2="20" y2="0" stroke="#00f0ff" stroke-width="1.5" />
<line x1="0" y1="-20" x2="0" y2="20" stroke="#00f0ff" stroke-width="1.5" />
</g>
</g>
<!-- 中心单向轴承主轴 -->
<circle cx="0" cy="0" r="14" fill="#0f172a" stroke="#ff6b00" stroke-width="3" />
<circle cx="0" cy="0" r="5" fill="#ff6b00" />
</g>
<!-- 前行星三轮组 -->
<g id="front-planetary-set" transform="translate(260, 0)">
<!-- 转臂 (Y型等角 120度) -->
<g id="front-spider" stroke="#475569" stroke-width="12" stroke-linejoin="round" stroke-linecap="round">
<line x1="0" y1="0" x2="0" y2="-68" />
<line x1="0" y1="0" x2="58.89" y2="34" />
<line x1="0" y1="0" x2="-58.89" y2="34" />
</g>
<g id="front-spider-glow" stroke="#ff6b00" stroke-width="2.5" stroke-linejoin="round" stroke-linecap="round" fill="none" filter="url(#glow-orange)">
<line x1="0" y1="0" x2="0" y2="-68" />
<line x1="0" y1="0" x2="58.89" y2="34" />
<line x1="0" y1="0" x2="-58.89" y2="34" />
</g>
<!-- 3个行星橡胶轮 -->
<g id="front-wheels">
<g id="fw-0" transform="translate(0, -68)">
<circle r="25" fill="#1e293b" stroke="#00f0ff" stroke-width="3" />
<circle r="8" fill="#080c14" stroke="#64748b" stroke-width="2" />
<line x1="-20" y1="0" x2="20" y2="0" stroke="#00f0ff" stroke-width="1.5" />
<line x1="0" y1="-20" x2="0" y2="20" stroke="#00f0ff" stroke-width="1.5" />
</g>
<g id="fw-1" transform="translate(58.89, 34)">
<circle r="25" fill="#1e293b" stroke="#00f0ff" stroke-width="3" />
<circle r="8" fill="#080c14" stroke="#64748b" stroke-width="2" />
<line x1="-20" y1="0" x2="20" y2="0" stroke="#00f0ff" stroke-width="1.5" />
<line x1="0" y1="-20" x2="0" y2="20" stroke="#00f0ff" stroke-width="1.5" />
</g>
<g id="fw-2" transform="translate(-58.89, 34)">
<circle r="25" fill="#1e293b" stroke="#00f0ff" stroke-width="3" />
<circle r="8" fill="#080c14" stroke="#64748b" stroke-width="2" />
<line x1="-20" y1="0" x2="20" y2="0" stroke="#00f0ff" stroke-width="1.5" />
<line x1="0" y1="-20" x2="0" y2="20" stroke="#00f0ff" stroke-width="1.5" />
</g>
</g>
<!-- 中心单向离合轴承 -->
<circle cx="0" cy="0" r="14" fill="#0f172a" stroke="#ff6b00" stroke-width="3" />
<circle cx="0" cy="0" r="5" fill="#ff6b00" />
</g>
<!-- 3. 动态受力与力矩矢量指示层 -->
<g id="layer-forces">
<!-- 前轮受阻碰撞法向力 (台阶阻力) -->
<g id="force-barrier" opacity="0">
<line x1="318.89" y1="34" x2="270" y2="34" stroke="#ff0055" stroke-width="4" marker-end="url(#arrow-orange)" />
<text x="325" y="30" font-family="JetBrains Mono" font-size="11" font-weight="bold" fill="#ff0055">F_阻挡</text>
</g>
<!-- 转臂翻转驱动力矩圆弧 (Torque >= 6N·m) -->
<g id="torque-arc" opacity="0">
<path d="M 260 -35 A 35 35 0 0 1 295 0" fill="none" stroke="#ff6b00" stroke-width="3" marker-end="url(#arrow-orange)" filter="url(#glow-orange)" />
<text x="290" y="-40" font-family="JetBrains Mono" font-size="11" font-weight="bold" fill="#ff6b00">M > 6N·m 触发翻转</text>
</g>
<!-- 爬阶抬升力矢量 -->
<g id="force-lift" opacity="0">
<line x1="260" y1="0" x2="260" y2="-50" stroke="#00ff88" stroke-width="3.5" marker-end="url(#arrow-green)" filter="url(#glow-cyan)" />
<text x="270" y="-30" font-family="JetBrains Mono" font-size="11" font-weight="bold" fill="#00ff88">F_抬升 (Step Up)</text>
</g>
</g>
</g>
</svg>
</div>
<!-- 底部交互控制与时序微步骤 -->
<div class="footer-bar">
<div class="phase-indicator">
<div class="phase-chip" id="chip-0">
<span class="phase-idx">01</span>
<span class="phase-name">平地滚动行驶</span>
</div>
<div class="phase-chip" id="chip-1">
<span class="phase-idx">02</span>
<span class="phase-name">前轮触阶·力矩积聚</span>
</div>
<div class="phase-chip" id="chip-2">
<span class="phase-idx">03</span>
<span class="phase-name">星轮翻转 120° 跨级</span>
</div>
<div class="phase-chip" id="chip-3">
<span class="phase-idx">04</span>
<span class="phase-name">上轮咬合·后组接力</span>
</div>
<div class="phase-chip" id="chip-4">
<span class="phase-idx">05</span>
<span class="phase-name">阻尼自调平·平稳恢复</span>
</div>
</div>
<div class="controls-panel">
<button class="btn-action" id="btn-play-pause" onclick="togglePlay()" title="播放/暂停">
<svg id="icon-pause" width="14" height="14" viewBox="0 0 24 24" fill="currentColor"><rect x="6" y="4" width="4" height="16"/><rect x="14" y="4" width="4" height="16"/></svg>
<svg id="icon-play" width="14" height="14" viewBox="0 0 24 24" fill="currentColor" style="display:none;"><polygon points="5 3 19 12 5 21 5 3"/></svg>
</button>
<button class="btn-action" onclick="resetAnimation()" title="重置">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><path d="M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8"/><path d="M3 3v5h5"/></svg>
</button>
<input type="range" id="timeline" class="timeline-slider" min="0" max="1000" value="0" oninput="onSeek(this.value)" />
<div class="telemetry-tag" id="telemetry-status">
STAGE: 01 | 0.00s
</div>
</div>
</div>
<script>
/* 全局图层显隐开关 */
const layers = {
forces: true,
orbits: true,
leveler: true
};
function toggleLayer(layerName) {
layers[layerName] = !layers[layerName];
const btn = document.getElementById('btn-toggle-' + layerName);
const txt = document.getElementById('t-' + layerName);
if (layers[layerName]) {
btn.classList.add('active');
txt.innerText = '开';
} else {
btn.classList.remove('active');
txt.innerText = '关';
}
const elem = document.getElementById('layer-' + layerName);
if (elem) {
elem.style.display = layers[layerName] ? 'inline' : 'none';
}
}
/* 运动学时序与状态机模型 */
const TOTAL_DURATION = 9000; // 完整单次越障循环 9秒
let isPlaying = true;
let startTime = null;
let pausedTime = 0;
let currentProgress = 0; // 0.0 ~ 1.0
// DOM 元素引用
const cartAssembly = document.getElementById('cart-assembly');
const frontSpider = document.getElementById('front-planetary-set');
const rearSpider = document.getElementById('rear-planetary-set');
const cargoPlatform = document.getElementById('cargo-platform');
const timeline = document.getElementById('timeline');
const telemetry = document.getElementById('telemetry-status');
const iconPlay = document.getElementById('icon-play');
const iconPause = document.getElementById('icon-pause');
// 矢量受力 DOM
const forceBarrier = document.getElementById('force-barrier');
const torqueArc = document.getElementById('torque-arc');
const forceLift = document.getElementById('force-lift');
// 行星轮子单独自转
const fwWheels = [document.getElementById('fw-0'), document.getElementById('fw-1'), document.getElementById('fw-2')];
const rwWheels = [document.getElementById('rw-0'), document.getElementById('rw-1'), document.getElementById('rw-2')];
// 缓动函数
function easeInOutQuad(t) {
return t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t;
}
function clamp(val, min, max) {
return Math.min(Math.max(val, min), max);
}
/* 运动学姿态求解器 */
function computeKinematics(p) {
// p: 0.0 ~ 1.0
// 场景设计:小车从 X=-100 运动到 X=650
// 地形:平地 (Y=506), 踏面一 (X=400, Y=386), 踏面二 (X=750, Y=266)
// 前轮在 X=140 时遇到第一阶 (车身X+260+58.89 = 400 -> 车身X=81)
let carX = -60 + p * 620; // 车架后轴X位置
let rearX = carX;
let frontX = carX + 260;
// 阶段1:前轮触阶 & 翻转120度 (p: 0.16 ~ 0.38)
// 阶段2:后轮触阶 & 翻转120度 (p: 0.50 ~ 0.72)
let frontAngle = 0;
let rearAngle = 0;
let wheelSpin = carX * 1.5; // 自转角
let frontY = 506;
let rearY = 506;
// 前行星架越障解算
const fStart = 0.15, fEnd = 0.38;
let fFlipProgress = 0;
if (p >= fStart && p <= fEnd) {
fFlipProgress = (p - fStart) / (fEnd - fStart);
let smoothP = easeInOutQuad(fFlipProgress);
frontAngle = -smoothP * 120; // 逆时针翻转 120°
frontY = 506 - smoothP * 120; // 上升到第一级台阶
} else if (p > fEnd) {
frontAngle = -120;
frontY = 386;
}
// 后行星架越障解算
const rStart = 0.48, rEnd = 0.71;
let rFlipProgress = 0;
if (p >= rStart && p <= rEnd) {
rFlipProgress = (p - rStart) / (rEnd - rStart);
let smoothP = easeInOutQuad(rFlipProgress);
rearAngle = -smoothP * 120;
rearY = 506 - smoothP * 120;
} else if (p > rEnd) {
rearAngle = -120;
rearY = 386;
}
// 车身主梁倾角与自调平货箱角度
const chassisAngleRad = Math.atan2(frontY - rearY, 260);
const chassisAngleDeg = (chassisAngleRad * 180) / Math.PI;
// 矢量指示器状态
let barrierOpacity = 0;
let torqueOpacity = 0;
let liftOpacity = 0;
if (p >= 0.12 && p < 0.18) {
// 刚顶住台阶,力矩聚积
barrierOpacity = clamp((p - 0.12) / 0.03, 0, 1);
torqueOpacity = barrierOpacity;
} else if (p >= 0.18 && p <= 0.32) {
// 翻转抬升中
barrierOpacity = 0.2;
torqueOpacity = 1.0;
liftOpacity = clamp(Math.sin((p - 0.18) / 0.14 * Math.PI), 0, 1);
}
// 确定阶段指示高亮
let phaseIndex = 0;
if (p < 0.14) phaseIndex = 0;
else if (p < 0.22) phaseIndex = 1;
else if (p < 0.45) phaseIndex = 2;
else if (p < 0.75) phaseIndex = 3;
else phaseIndex = 4;
return {
carX,
frontX,
rearX,
frontY,
rearY,
frontAngle,
rearAngle,
wheelSpin,
chassisAngleDeg,
barrierOpacity,
torqueOpacity,
liftOpacity,
phaseIndex
};
}
/* 渲染一帧 */
function renderFrame(state) {
// 1. 底盘定位与俯仰
cartAssembly.setAttribute('transform', `translate(${state.rearX}, ${state.rearY}) rotate(${state.chassisAngleDeg})`);
// 2. 前后星形转臂旋转 (需要抵消底盘倾角)
frontSpider.setAttribute('transform', `translate(260, 0) rotate(${state.frontAngle - state.chassisAngleDeg})`);
rearSpider.setAttribute('transform', `translate(0, 0) rotate(${state.rearAngle - state.chassisAngleDeg})`);
// 3. 轮子自转 (给车轮视觉滚动效果)
const wRot = `rotate(${state.wheelSpin})`;
fwWheels.forEach(w => w.setAttribute('transform', `${w.getAttribute('data-base') || ''} ${wRot}`));
rwWheels.forEach(w => w.setAttribute('transform', `${w.getAttribute('data-base') || ''} ${wRot}`));
// 4. 自调平货箱:反向旋转抵消车身倾角,保持水平
cargoPlatform.setAttribute('transform', `rotate(${-state.chassisAngleDeg}, 130, 0)`);
// 5. 受力与力矩矢量透明度
if (layers.forces) {
forceBarrier.setAttribute('opacity', state.barrierOpacity);
torqueArc.setAttribute('opacity', state.torqueOpacity);
forceLift.setAttribute('opacity', state.liftOpacity);
}
// 6. 阶段高亮更新
for (let i = 0; i <= 4; i++) {
const chip = document.getElementById(`chip-${i}`);
if (i === state.phaseIndex) {
chip.classList.add('active');
} else {
chip.classList.remove('active');
}
}
// 7. 更新底部滑块与遥测数据
timeline.value = currentProgress * 1000;
const sec = (currentProgress * (TOTAL_DURATION / 1000)).toFixed(2);
telemetry.innerText = `PHASE: 0${state.phaseIndex + 1} | T: ${sec}s | α: ${state.chassisAngleDeg.toFixed(1)}°`;
}
// 初始化轮子相对基座位置缓存
function initWheelTransforms() {
fwWheels[0].setAttribute('data-base', 'translate(0, -68)');
fwWheels[1].setAttribute('data-base', 'translate(58.89, 34)');
fwWheels[2].setAttribute('data-base', 'translate(-58.89, 34)');
rwWheels[0].setAttribute('data-base', 'translate(0, -68)');
rwWheels[1].setAttribute('data-base', 'translate(58.89, 34)');
rwWheels[2].setAttribute('data-base', 'translate(-58.89, 34)');
}
/* 动画循环驱动 */
function animate(timestamp) {
if (!startTime) startTime = timestamp;
if (isPlaying) {
const elapsed = (timestamp - startTime + pausedTime) % TOTAL_DURATION;
currentProgress = elapsed / TOTAL_DURATION;
}
const state = computeKinematics(currentProgress);
renderFrame(state);
requestAnimationFrame(animate);
}
/* 交互控制函数 */
function togglePlay() {
isPlaying = !isPlaying;
if (isPlaying) {
startTime = performance.now();
pausedTime = currentProgress * TOTAL_DURATION;
iconPlay.style.display = 'none';
iconPause.style.display = 'inline';
} else {
pausedTime = currentProgress * TOTAL_DURATION;
iconPlay.style.display = 'inline';
iconPause.style.display = 'none';
}
}
function resetAnimation() {
currentProgress = 0;
pausedTime = 0;
startTime = performance.now();
const state = computeKinematics(0);
renderFrame(state);
}
function onSeek(val) {
currentProgress = val / 1000;
pausedTime = currentProgress * TOTAL_DURATION;
startTime = performance.now();
const state = computeKinematics(currentProgress);
renderFrame(state);
}
// DOM 加载完成自动启动
document.addEventListener('DOMContentLoaded', () => {
initWheelTransforms();
requestAnimationFrame(animate);
});
</script>
</body>
</html>
<!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: #080c14;
--panel-bg: rgba(13, 20, 36, 0.75);
--border-color: rgba(0, 229, 255, 0.2);
--border-glow: rgba(0, 229, 255, 0.4);
--cyan-core: #00f0ff;
--orange-accent: #ff6b00;
--orange-glow: rgba(255, 107, 0, 0.4);
--green-ready: #00ff88;
--text-main: #e2e8f0;
--text-muted: #64748b;
--font-mono: "JetBrains Mono", "SF Mono", "Fira Code", monospace;
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
user-select: none;
}
body {
background-color: var(--bg-color);
color: var(--text-main);
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
overflow: hidden;
width: 100vw;
height: 100vh;
display: flex;
flex-direction: column;
justify-content: space-between;
position: relative;
}
/* 背景精细 CAD 测绘网格 */
.bg-grid {
position: absolute;
inset: 0;
background-image:
radial-gradient(circle at 50% 50%, rgba(0, 240, 255, 0.05) 0%, transparent 80%),
linear-gradient(rgba(0, 240, 255, 0.03) 1px, transparent 1px),
linear-gradient(90deg, rgba(0, 240, 255, 0.03) 1px, transparent 1px),
linear-gradient(rgba(255, 255, 255, 0.015) 1px, transparent 1px),
linear-gradient(90deg, rgba(255, 255, 255, 0.015) 1px, transparent 1px);
background-size: 100% 100%, 100px 100px, 100px 100px, 20px 20px, 20px 20px;
pointer-events: none;
z-index: 0;
}
/* 顶部紧凑状态条 */
.header-bar {
position: absolute;
top: 16px;
left: 20px;
right: 20px;
display: flex;
justify-content: space-between;
align-items: center;
z-index: 20;
pointer-events: none;
}
.brand-tag {
pointer-events: auto;
display: flex;
align-items: center;
gap: 10px;
background: var(--panel-bg);
border: 1px solid var(--border-color);
padding: 6px 14px;
border-radius: 6px;
backdrop-filter: blur(10px);
}
.status-dot {
width: 7px;
height: 7px;
border-radius: 50%;
background: var(--green-ready);
box-shadow: 0 0 10px var(--green-ready);
animation: pulse-dot 2s infinite ease-in-out;
}
.title-text {
font-size: 13px;
font-weight: 600;
letter-spacing: 0.5px;
color: #fff;
}
.title-sub {
font-size: 11px;
color: var(--cyan-core);
font-family: var(--font-mono);
margin-left: 4px;
}
/* 右上角图层切换器 */
.hud-switches {
pointer-events: auto;
display: flex;
gap: 8px;
}
.hud-btn {
background: var(--panel-bg);
border: 1px solid var(--border-color);
color: var(--text-muted);
font-size: 11px;
padding: 5px 10px;
border-radius: 4px;
cursor: pointer;
display: flex;
align-items: center;
gap: 6px;
transition: all 0.2s;
}
.hud-btn:hover {
border-color: var(--cyan-core);
color: var(--text-main);
}
.hud-btn.active {
border-color: var(--cyan-core);
color: var(--cyan-core);
background: rgba(0, 240, 255, 0.08);
box-shadow: 0 0 10px rgba(0, 240, 255, 0.15);
}
.hud-btn .key-dot {
width: 5px;
height: 5px;
border-radius: 50%;
background: currentColor;
}
/* 中央核心动画舞台 */
.viewport-container {
position: absolute;
inset: 0;
display: flex;
justify-content: center;
align-items: center;
z-index: 10;
}
#cad-stage {
width: 100%;
height: 100%;
max-width: 1400px;
max-height: 860px;
}
/* 底部紧凑控制与原理步骤提示条 */
.footer-bar {
position: absolute;
bottom: 16px;
left: 20px;
right: 20px;
z-index: 20;
display: flex;
flex-direction: column;
gap: 8px;
pointer-events: none;
}
.phase-indicator {
display: flex;
justify-content: space-between;
gap: 12px;
pointer-events: auto;
}
.phase-chip {
flex: 1;
background: var(--panel-bg);
border: 1px solid rgba(255, 255, 255, 0.08);
padding: 7px 12px;
border-radius: 5px;
display: flex;
align-items: center;
gap: 8px;
backdrop-filter: blur(12px);
transition: all 0.3s cubic-bezier(0.16, 1, 0.3, 1);
}
.phase-chip.active {
border-color: var(--orange-accent);
background: rgba(255, 107, 0, 0.12);
box-shadow: 0 0 15px rgba(255, 107, 0, 0.2);
}
.phase-idx {
font-family: var(--font-mono);
font-size: 11px;
font-weight: bold;
color: var(--text-muted);
min-width: 16px;
}
.phase-chip.active .phase-idx {
color: var(--orange-accent);
}
.phase-name {
font-size: 12px;
color: var(--text-muted);
white-space: nowrap;
}
.phase-chip.active .phase-name {
color: #fff;
font-weight: 500;
}
/* 控制面板 */
.controls-panel {
pointer-events: auto;
display: flex;
align-items: center;
gap: 14px;
background: var(--panel-bg);
border: 1px solid var(--border-color);
padding: 7px 16px;
border-radius: 6px;
backdrop-filter: blur(12px);
}
.btn-action {
background: transparent;
border: 1px solid var(--border-color);
color: var(--cyan-core);
width: 30px;
height: 30px;
border-radius: 4px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.2s;
}
.btn-action:hover {
background: var(--cyan-core);
color: #000;
box-shadow: 0 0 10px var(--cyan-core);
}
.timeline-slider {
flex: 1;
height: 4px;
-webkit-appearance: none;
background: rgba(255, 255, 255, 0.15);
border-radius: 2px;
outline: none;
cursor: pointer;
}
.timeline-slider::-webkit-slider-thumb {
-webkit-appearance: none;
width: 12px;
height: 12px;
border-radius: 50%;
background: var(--cyan-core);
box-shadow: 0 0 8px var(--cyan-core);
cursor: ew-resize;
transition: transform 0.1s;
}
.timeline-slider::-webkit-slider-thumb:hover {
transform: scale(1.3);
}
.telemetry-tag {
font-family: var(--font-mono);
font-size: 11px;
color: var(--cyan-core);
min-width: 140px;
text-align: right;
}
@keyframes pulse-dot {
0%, 100% { opacity: 0.4; transform: scale(0.9); }
50% { opacity: 1; transform: scale(1.15); }
}
/* 微型悬浮参数卡 */
.param-overlay {
position: absolute;
top: 60px;
right: 20px;
z-index: 15;
background: var(--panel-bg);
border: 1px solid rgba(255, 255, 255, 0.1);
padding: 10px 14px;
border-radius: 6px;
font-size: 11px;
line-height: 1.6;
pointer-events: none;
backdrop-filter: blur(8px);
}
.param-overlay .item {
display: flex;
justify-content: space-between;
gap: 16px;
color: #94a3b8;
}
.param-overlay .val {
font-family: var(--font-mono);
color: var(--cyan-core);
font-weight: 500;
}
</style>
</head>
<body>
<div class="bg-grid"></div>
<!-- 顶部状态栏 -->
<div class="header-bar">
<div class="brand-tag">
<div class="status-dot"></div>
<span class="title-text">行星三轮组阶跃越障机构</span>
<span class="title-sub">[顺时针跨级·中心等高台阶]</span>
</div>
<div class="hud-switches">
<button class="hud-btn active" id="btn-toggle-forces" onclick="toggleLayer('forces')">
<span class="key-dot"></span> 力矩/矢量 (<span id="t-forces">开</span>)
</button>
<button class="hud-btn active" id="btn-toggle-orbits" onclick="toggleLayer('orbits')">
<span class="key-dot"></span> 回转包络线 (<span id="t-orbits">开</span>)
</button>
<button class="hud-btn active" id="btn-toggle-leveler" onclick="toggleLayer('leveler')">
<span class="key-dot"></span> 货箱水平阻尼 (<span id="t-leveler">开</span>)
</button>
</div>
</div>
<!-- 关键参数指示 -->
<div class="param-overlay">
<div class="item"><span>单轮直径 (2r):</span><span class="val">Ø 120 mm</span></div>
<div class="item"><span>回转半径 (R):</span><span class="val">165 mm</span></div>
<div class="item"><span>台阶高度 (H=R):</span><span class="val">68 mm (至中心)</span></div>
<div class="item"><span>越障翻转方向:</span><span class="val">顺时针 (+120°)</span></div>
</div>
<!-- 中央主舞台 SVG -->
<div class="viewport-container">
<svg id="cad-stage" viewBox="0 0 1200 680" preserveAspectRatio="xMidYMid meet">
<defs>
<!-- 发光滤镜 -->
<filter id="glow-cyan" 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-orange" x="-30%" y="-30%" width="160%" height="160%">
<feGaussianBlur stdDeviation="4" result="blur" />
<feMerge>
<feMergeNode in="blur" />
<feMergeNode in="SourceGraphic" />
</feMerge>
</filter>
<!-- 箭头标记 -->
<marker id="arrow-cyan" viewBox="0 0 10 10" refX="6" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse">
<path d="M 0 1 L 9 5 L 0 9 z" fill="#00f0ff" />
</marker>
<marker id="arrow-orange" viewBox="0 0 10 10" refX="6" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse">
<path d="M 0 1 L 9 5 L 0 9 z" fill="#ff6b00" />
</marker>
<marker id="arrow-green" viewBox="0 0 10 10" refX="6" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse">
<path d="M 0 1 L 9 5 L 0 9 z" fill="#00ff88" />
</marker>
<!-- 材质填充 -->
<pattern id="hatch-stairs" width="12" height="12" patternTransform="rotate(45 0 0)" patternUnits="userSpaceOnUse">
<line x1="0" y1="0" x2="0" y2="12" stroke="rgba(0, 240, 255, 0.08)" stroke-width="1.5" />
</pattern>
<pattern id="cargo-texture" width="10" height="10" patternUnits="userSpaceOnUse">
<rect width="10" height="10" fill="#132035" />
<path d="M 0 10 L 10 0 M 0 0 L 10 10" stroke="rgba(0, 240, 255, 0.12)" stroke-width="0.75"/>
</pattern>
</defs>
<!-- 1. 楼梯与地形环境 (台阶高度调整为行星轮中心高度 68px) -->
<g id="terrain-group">
<!-- 楼梯剖面基座: 地面 Y=540, 一阶踏面 Y=472 (高68px), 二阶踏面 Y=404 -->
<path id="stair-base" d="M -50 540 L 400 540 L 400 472 L 750 472 L 750 404 L 1250 404 L 1250 640 L -50 640 Z"
fill="url(#hatch-stairs)" stroke="rgba(0, 240, 255, 0.4)" stroke-width="2" />
<!-- 楼梯踏面加固线 -->
<path d="M -50 540 L 400 540 L 400 472 L 750 472 L 750 404 L 1250 404"
fill="none" stroke="#00f0ff" stroke-width="3" filter="url(#glow-cyan)" />
<!-- 踏面防滑纹理与倒角高亮 -->
<g stroke="rgba(0, 240, 255, 0.3)" stroke-width="1">
<line x1="400" y1="540" x2="400" y2="472" stroke="#ff6b00" stroke-width="2.5" />
<line x1="750" y1="472" x2="750" y2="404" stroke="#ff6b00" stroke-width="2.5" />
<circle cx="400" cy="472" r="3" fill="#ff6b00" />
<circle cx="750" cy="404" r="3" fill="#ff6b00" />
</g>
<!-- 尺寸标注 (高度与宽度) -->
<g id="layer-dim" stroke="#64748b" stroke-width="1" font-family="JetBrains Mono" font-size="11" fill="#64748b">
<!-- 阶高标尺 (与中心等高 H=68mm) -->
<line x1="380" y1="540" x2="380" y2="472" marker-start="url(#arrow-cyan)" marker-end="url(#arrow-cyan)" stroke="#00f0ff" stroke-opacity="0.6"/>
<text x="365" y="510" text-anchor="end" fill="#00f0ff">H=68mm (至中心轴)</text>
<line x1="730" y1="472" x2="730" y2="404" marker-start="url(#arrow-cyan)" marker-end="url(#arrow-cyan)" stroke="#00f0ff" stroke-opacity="0.6"/>
<text x="715" y="442" text-anchor="end" fill="#00f0ff">H=68mm</text>
<!-- 踏宽标尺 -->
<line x1="400" y1="560" x2="750" y2="560" marker-start="url(#arrow-cyan)" marker-end="url(#arrow-cyan)" stroke="#00f0ff" stroke-opacity="0.6"/>
<text x="575" y="580" text-anchor="middle" fill="#00f0ff">Step Width = 350mm</text>
</g>
</g>
<!-- 2. 小车主机构动态装配组 -->
<g id="cart-assembly">
<!-- 回转轨迹层 -->
<g id="layer-orbits" stroke="rgba(0, 240, 255, 0.25)" stroke-dasharray="4,4" fill="none">
<circle id="front-orbit" cx="260" cy="0" r="68" />
<circle id="rear-orbit" cx="0" cy="0" r="68" />
</g>
<!-- 车架主梁 -->
<path id="chassis-beam" d="M 0 0 L 260 0" stroke="#334155" stroke-width="14" stroke-linecap="round" />
<path id="chassis-core" d="M 0 0 L 260 0" stroke="#00f0ff" stroke-width="3" />
<!-- 阻尼器与自调平机构连杆 -->
<g id="layer-leveler">
<!-- 阻尼器套筒 -->
<line id="damper-line" x1="130" y1="0" x2="130" y2="-65" stroke="#ff6b00" stroke-width="4" stroke-dasharray="6,3" />
<circle id="damper-joint-b" cx="130" cy="0" r="5" fill="#ff6b00" />
<circle id="damper-joint-t" cx="130" cy="-65" r="5" fill="#ff6b00" />
<!-- 被动防倾摆臂 -->
<line id="arm-f" x1="40" y1="0" x2="60" y2="-60" stroke="#00f0ff" stroke-width="3" stroke-opacity="0.8" />
<line id="arm-r" x1="220" y1="0" x2="200" y2="-60" stroke="#00f0ff" stroke-width="3" stroke-opacity="0.8" />
</g>
<!-- 货箱平台 (保持水平) -->
<g id="cargo-platform">
<rect id="cargo-box" x="20" y="-120" width="220" height="58" rx="6" fill="url(#cargo-texture)" stroke="#00f0ff" stroke-width="2" />
<!-- 货物重心与水平仪 -->
<line x1="30" y1="-91" x2="230" y2="-91" stroke="rgba(0,240,255,0.3)" stroke-width="1" stroke-dasharray="3,3" />
<circle cx="130" cy="-91" r="9" fill="rgba(0, 255, 136, 0.2)" stroke="#00ff88" stroke-width="1.5" />
<line x1="121" y1="-91" x2="139" y2="-91" stroke="#00ff88" stroke-width="1.5" />
<line x1="130" y1="-100" x2="130" y2="-82" stroke="#00ff88" stroke-width="1.5" />
<text x="145" y="-88" font-family="JetBrains Mono" font-size="10" fill="#00ff88">CARGO LEVEL (0°)</text>
</g>
<!-- 后行星三轮组 -->
<g id="rear-planetary-set" transform="translate(0, 0)">
<!-- 转臂 (Y型等角 120度) -->
<g id="rear-spider" stroke="#475569" stroke-width="12" stroke-linejoin="round" stroke-linecap="round">
<line x1="0" y1="0" x2="0" y2="68" />
<line x1="0" y1="0" x2="58.89" y2="-34" />
<line x1="0" y1="0" x2="-58.89" y2="-34" />
</g>
<g id="rear-spider-glow" stroke="#00f0ff" stroke-width="2" stroke-linejoin="round" stroke-linecap="round" fill="none">
<line x1="0" y1="0" x2="0" y2="68" />
<line x1="0" y1="0" x2="58.89" y2="-34" />
<line x1="0" y1="0" x2="-58.89" y2="-34" />
</g>
<!-- 3个行星橡胶轮 (R=25, 直径120mm缩比) -->
<g id="rear-wheels">
<g id="rw-0" transform="translate(0, 68)">
<circle r="25" fill="#1e293b" stroke="#00f0ff" stroke-width="3" />
<circle r="8" fill="#080c14" stroke="#64748b" stroke-width="2" />
<line x1="-20" y1="0" x2="20" y2="0" stroke="#00f0ff" stroke-width="1.5" />
<line x1="0" y1="-20" x2="0" y2="20" stroke="#00f0ff" stroke-width="1.5" />
</g>
<g id="rw-1" transform="translate(58.89, -34)">
<circle r="25" fill="#1e293b" stroke="#00f0ff" stroke-width="3" />
<circle r="8" fill="#080c14" stroke="#64748b" stroke-width="2" />
<line x1="-20" y1="0" x2="20" y2="0" stroke="#00f0ff" stroke-width="1.5" />
<line x1="0" y1="-20" x2="0" y2="20" stroke="#00f0ff" stroke-width="1.5" />
</g>
<g id="rw-2" transform="translate(-58.89, -34)">
<circle r="25" fill="#1e293b" stroke="#00f0ff" stroke-width="3" />
<circle r="8" fill="#080c14" stroke="#64748b" stroke-width="2" />
<line x1="-20" y1="0" x2="20" y2="0" stroke="#00f0ff" stroke-width="1.5" />
<line x1="0" y1="-20" x2="0" y2="20" stroke="#00f0ff" stroke-width="1.5" />
</g>
</g>
<!-- 中心单向轴承主轴 -->
<circle cx="0" cy="0" r="14" fill="#0f172a" stroke="#ff6b00" stroke-width="3" />
<circle cx="0" cy="0" r="5" fill="#ff6b00" />
</g>
<!-- 前行星三轮组 -->
<g id="front-planetary-set" transform="translate(260, 0)">
<!-- 转臂 (Y型等角 120度) -->
<g id="front-spider" stroke="#475569" stroke-width="12" stroke-linejoin="round" stroke-linecap="round">
<line x1="0" y1="0" x2="0" y2="68" />
<line x1="0" y1="0" x2="58.89" y2="-34" />
<line x1="0" y1="0" x2="-58.89" y2="-34" />
</g>
<g id="front-spider-glow" stroke="#ff6b00" stroke-width="2.5" stroke-linejoin="round" stroke-linecap="round" fill="none" filter="url(#glow-orange)">
<line x1="0" y1="0" x2="0" y2="68" />
<line x1="0" y1="0" x2="58.89" y2="-34" />
<line x1="0" y1="0" x2="-58.89" y2="-34" />
</g>
<!-- 3个行星橡胶轮 -->
<g id="front-wheels">
<g id="fw-0" transform="translate(0, 68)">
<circle r="25" fill="#1e293b" stroke="#00f0ff" stroke-width="3" />
<circle r="8" fill="#080c14" stroke="#64748b" stroke-width="2" />
<line x1="-20" y1="0" x2="20" y2="0" stroke="#00f0ff" stroke-width="1.5" />
<line x1="0" y1="-20" x2="0" y2="20" stroke="#00f0ff" stroke-width="1.5" />
</g>
<g id="fw-1" transform="translate(58.89, -34)">
<circle r="25" fill="#1e293b" stroke="#00f0ff" stroke-width="3" />
<circle r="8" fill="#080c14" stroke="#64748b" stroke-width="2" />
<line x1="-20" y1="0" x2="20" y2="0" stroke="#00f0ff" stroke-width="1.5" />
<line x1="0" y1="-20" x2="0" y2="20" stroke="#00f0ff" stroke-width="1.5" />
</g>
<g id="fw-2" transform="translate(-58.89, -34)">
<circle r="25" fill="#1e293b" stroke="#00f0ff" stroke-width="3" />
<circle r="8" fill="#080c14" stroke="#64748b" stroke-width="2" />
<line x1="-20" y1="0" x2="20" y2="0" stroke="#00f0ff" stroke-width="1.5" />
<line x1="0" y1="-20" x2="0" y2="20" stroke="#00f0ff" stroke-width="1.5" />
</g>
</g>
<!-- 中心单向离合轴承 -->
<circle cx="0" cy="0" r="14" fill="#0f172a" stroke="#ff6b00" stroke-width="3" />
<circle cx="0" cy="0" r="5" fill="#ff6b00" />
</g>
<!-- 3. 动态受力与力矩矢量指示层 -->
<g id="layer-forces">
<!-- 前轮受阻碰撞法向力 (台阶阻力) -->
<g id="force-barrier" opacity="0">
<line x1="285" y1="68" x2="240" y2="68" stroke="#ff0055" stroke-width="4" marker-end="url(#arrow-orange)" />
<text x="290" y="65" font-family="JetBrains Mono" font-size="11" font-weight="bold" fill="#ff0055">F_阻挡</text>
</g>
<!-- 转臂顺时针翻转力矩圆弧 (Clockwise Torque) -->
<g id="torque-arc" opacity="0">
<path d="M 260 -35 A 35 35 0 0 1 295 0" fill="none" stroke="#ff6b00" stroke-width="3" marker-end="url(#arrow-orange)" filter="url(#glow-orange)" />
<text x="305" y="-10" font-family="JetBrains Mono" font-size="11" font-weight="bold" fill="#ff6b00">顺时针力矩 M > 6N·m</text>
</g>
<!-- 爬阶抬升力矢量 -->
<g id="force-lift" opacity="0">
<line x1="260" y1="0" x2="260" y2="-45" stroke="#00ff88" stroke-width="3.5" marker-end="url(#arrow-green)" filter="url(#glow-cyan)" />
<text x="270" y="-25" font-family="JetBrains Mono" font-size="11" font-weight="bold" fill="#00ff88">F_抬升 (跨级上行)</text>
</g>
</g>
</g>
</svg>
</div>
<!-- 底部交互控制与时序微步骤 -->
<div class="footer-bar">
<div class="phase-indicator">
<div class="phase-chip" id="chip-0">
<span class="phase-idx">01</span>
<span class="phase-name">平地滚动行驶</span>
</div>
<div class="phase-chip" id="chip-1">
<span class="phase-idx">02</span>
<span class="phase-name">触阶受阻·积聚顺时针力矩</span>
</div>
<div class="phase-chip" id="chip-2">
<span class="phase-idx">03</span>
<span class="phase-name">星轮顺时针翻转 120°</span>
</div>
<div class="phase-chip" id="chip-3">
<span class="phase-idx">04</span>
<span class="phase-name">前轮落上踏面·后组接力</span>
</div>
<div class="phase-chip" id="chip-4">
<span class="phase-idx">05</span>
<span class="phase-name">平台阻尼回正·恢复水平</span>
</div>
</div>
<div class="controls-panel">
<button class="btn-action" id="btn-play-pause" onclick="togglePlay()" title="播放/暂停">
<svg id="icon-pause" width="14" height="14" viewBox="0 0 24 24" fill="currentColor"><rect x="6" y="4" width="4" height="16"/><rect x="14" y="4" width="4" height="16"/></svg>
<svg id="icon-play" width="14" height="14" viewBox="0 0 24 24" fill="currentColor" style="display:none;"><polygon points="5 3 19 12 5 21 5 3"/></svg>
</button>
<button class="btn-action" onclick="resetAnimation()" title="重置">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><path d="M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8"/><path d="M3 3v5h5"/></svg>
</button>
<input type="range" id="timeline" class="timeline-slider" min="0" max="1000" value="0" oninput="onSeek(this.value)" />
<div class="telemetry-tag" id="telemetry-status">
STAGE: 01 | 0.00s
</div>
</div>
</div>
<script>
/* 全局图层显隐开关 */
const layers = {
forces: true,
orbits: true,
leveler: true
};
function toggleLayer(layerName) {
layers[layerName] = !layers[layerName];
const btn = document.getElementById('btn-toggle-' + layerName);
const txt = document.getElementById('t-' + layerName);
if (layers[layerName]) {
btn.classList.add('active');
txt.innerText = '开';
} else {
btn.classList.remove('active');
txt.innerText = '关';
}
const elem = document.getElementById('layer-' + layerName);
if (elem) {
elem.style.display = layers[layerName] ? 'inline' : 'none';
}
}
/* 运动学时序与状态机模型 */
const TOTAL_DURATION = 9000;
let isPlaying = true;
let startTime = null;
let pausedTime = 0;
let currentProgress = 0;
// DOM 元素引用
const cartAssembly = document.getElementById('cart-assembly');
const frontSpider = document.getElementById('front-planetary-set');
const rearSpider = document.getElementById('rear-planetary-set');
const cargoPlatform = document.getElementById('cargo-platform');
const timeline = document.getElementById('timeline');
const telemetry = document.getElementById('telemetry-status');
const iconPlay = document.getElementById('icon-play');
const iconPause = document.getElementById('icon-pause');
// 矢量受力 DOM
const forceBarrier = document.getElementById('force-barrier');
const torqueArc = document.getElementById('torque-arc');
const forceLift = document.getElementById('force-lift');
// 行星轮子单独自转
const fwWheels = [document.getElementById('fw-0'), document.getElementById('fw-1'), document.getElementById('fw-2')];
const rwWheels = [document.getElementById('rw-0'), document.getElementById('rw-1'), document.getElementById('rw-2')];
// 缓动函数
function easeInOutQuad(t) {
return t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t;
}
function clamp(val, min, max) {
return Math.min(Math.max(val, min), max);
}
/* 运动学姿态求解器 (修改为顺时针旋转 +68px台阶跨越) */
function computeKinematics(p) {
// 地面接触基准:地面 Y=540,轮底接触时中心 Y=447 (540 - 68 - 25 = 447)
// 台阶高 68mm (等于行星轮中心高度),上一阶中心 Y=379
let carX = -40 + p * 600;
let rearX = carX;
let frontX = carX + 260;
let frontAngle = 0;
let rearAngle = 0;
let wheelSpin = carX * 2.0;
let frontY = 447;
let rearY = 447;
// 前行星架越障顺时针翻转 +120° (p: 0.16 ~ 0.38)
const fStart = 0.15, fEnd = 0.38;
if (p >= fStart && p <= fEnd) {
let fFlipProgress = (p - fStart) / (fEnd - fStart);
let smoothP = easeInOutQuad(fFlipProgress);
frontAngle = smoothP * 120; // 顺时针旋转 +120°
frontY = 447 - smoothP * 68; // 抬升至第一阶踏面 (高68mm)
} else if (p > fEnd) {
frontAngle = 120;
frontY = 379;
}
// 后行星架越障顺时针翻转 +120° (p: 0.48 ~ 0.71)
const rStart = 0.48, rEnd = 0.71;
if (p >= rStart && p <= rEnd) {
let rFlipProgress = (p - rStart) / (rEnd - rStart);
let smoothP = easeInOutQuad(rFlipProgress);
rearAngle = smoothP * 120; // 顺时针旋转 +120°
rearY = 447 - smoothP * 68;
} else if (p > rEnd) {
rearAngle = 120;
rearY = 379;
}
// 车身主梁倾角与自调平货箱角度
const chassisAngleRad = Math.atan2(frontY - rearY, 260);
const chassisAngleDeg = (chassisAngleRad * 180) / Math.PI;
// 矢量指示器状态
let barrierOpacity = 0;
let torqueOpacity = 0;
let liftOpacity = 0;
if (p >= 0.12 && p < 0.18) {
barrierOpacity = clamp((p - 0.12) / 0.03, 0, 1);
torqueOpacity = barrierOpacity;
} else if (p >= 0.18 && p <= 0.34) {
barrierOpacity = 0.2;
torqueOpacity = 1.0;
liftOpacity = clamp(Math.sin((p - 0.18) / 0.16 * Math.PI), 0, 1);
}
// 确定阶段指示高亮
let phaseIndex = 0;
if (p < 0.14) phaseIndex = 0;
else if (p < 0.22) phaseIndex = 1;
else if (p < 0.45) phaseIndex = 2;
else if (p < 0.75) phaseIndex = 3;
else phaseIndex = 4;
return {
carX,
frontX,
rearX,
frontY,
rearY,
frontAngle,
rearAngle,
wheelSpin,
chassisAngleDeg,
barrierOpacity,
torqueOpacity,
liftOpacity,
phaseIndex
};
}
/* 渲染一帧 */
function renderFrame(state) {
// 1. 底盘定位与俯仰
cartAssembly.setAttribute('transform', `translate(${state.rearX}, ${state.rearY}) rotate(${state.chassisAngleDeg})`);
// 2. 前后星形转臂顺时针旋转 (抵消底盘倾角)
frontSpider.setAttribute('transform', `translate(260, 0) rotate(${state.frontAngle - state.chassisAngleDeg})`);
rearSpider.setAttribute('transform', `translate(0, 0) rotate(${state.rearAngle - state.chassisAngleDeg})`);
// 3. 轮子自转
const wRot = `rotate(${state.wheelSpin})`;
fwWheels.forEach(w => w.setAttribute('transform', `${w.getAttribute('data-base') || ''} ${wRot}`));
rwWheels.forEach(w => w.setAttribute('transform', `${w.getAttribute('data-base') || ''} ${wRot}`));
// 4. 自调平货箱:反向旋转抵消车身倾角,保持水平
cargoPlatform.setAttribute('transform', `rotate(${-state.chassisAngleDeg}, 130, 0)`);
// 5. 受力与力矩矢量透明度
if (layers.forces) {
forceBarrier.setAttribute('opacity', state.barrierOpacity);
torqueArc.setAttribute('opacity', state.torqueOpacity);
forceLift.setAttribute('opacity', state.liftOpacity);
}
// 6. 阶段高亮更新
for (let i = 0; i <= 4; i++) {
const chip = document.getElementById(`chip-${i}`);
if (i === state.phaseIndex) {
chip.classList.add('active');
} else {
chip.classList.remove('active');
}
}
// 7. 更新底部滑块与遥测数据
timeline.value = currentProgress * 1000;
const sec = (currentProgress * (TOTAL_DURATION / 1000)).toFixed(2);
telemetry.innerText = `PHASE: 0${state.phaseIndex + 1} | T: ${sec}s | α: ${state.chassisAngleDeg.toFixed(1)}°`;
}
// 初始化轮子相对基座位置缓存
function initWheelTransforms() {
fwWheels[0].setAttribute('data-base', 'translate(0, 68)');
fwWheels[1].setAttribute('data-base', 'translate(58.89, -34)');
fwWheels[2].setAttribute('data-base', 'translate(-58.89, -34)');
rwWheels[0].setAttribute('data-base', 'translate(0, 68)');
rwWheels[1].setAttribute('data-base', 'translate(58.89, -34)');
rwWheels[2].setAttribute('data-base', 'translate(-58.89, -34)');
}
/* 动画循环驱动 */
function animate(timestamp) {
if (!startTime) startTime = timestamp;
if (isPlaying) {
const elapsed = (timestamp - startTime + pausedTime) % TOTAL_DURATION;
currentProgress = elapsed / TOTAL_DURATION;
}
const state = computeKinematics(currentProgress);
renderFrame(state);
requestAnimationFrame(animate);
}
/* 交互控制函数 */
function togglePlay() {
isPlaying = !isPlaying;
if (isPlaying) {
startTime = performance.now();
pausedTime = currentProgress * TOTAL_DURATION;
iconPlay.style.display = 'none';
iconPause.style.display = 'inline';
} else {
pausedTime = currentProgress * TOTAL_DURATION;
iconPlay.style.display = 'inline';
iconPause.style.display = 'none';
}
}
function resetAnimation() {
currentProgress = 0;
pausedTime = 0;
startTime = performance.now();
const state = computeKinematics(0);
renderFrame(state);
}
function onSeek(val) {
currentProgress = val / 1000;
pausedTime = currentProgress * TOTAL_DURATION;
startTime = performance.now();
const state = computeKinematics(currentProgress);
renderFrame(state);
}
// DOM 加载完成自动启动
document.addEventListener('DOMContentLoaded', () => {
initWheelTransforms();
requestAnimationFrame(animate);
});
</script>
</body>
</html>
<!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: #080c14;
--panel-bg: rgba(13, 20, 36, 0.75);
--border-color: rgba(0, 229, 255, 0.2);
--border-glow: rgba(0, 229, 255, 0.4);
--cyan-core: #00f0ff;
--orange-accent: #ff6b00;
--orange-glow: rgba(255, 107, 0, 0.4);
--green-ready: #00ff88;
--text-main: #e2e8f0;
--text-muted: #64748b;
--font-mono: "JetBrains Mono", "SF Mono", "Fira Code", monospace;
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
user-select: none;
}
body {
background-color: var(--bg-color);
color: var(--text-main);
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
overflow: hidden;
width: 100vw;
height: 100vh;
display: flex;
flex-direction: column;
justify-content: space-between;
position: relative;
}
/* 背景精细 CAD 测绘网格 */
.bg-grid {
position: absolute;
inset: 0;
background-image:
radial-gradient(circle at 50% 50%, rgba(0, 240, 255, 0.05) 0%, transparent 80%),
linear-gradient(rgba(0, 240, 255, 0.03) 1px, transparent 1px),
linear-gradient(90deg, rgba(0, 240, 255, 0.03) 1px, transparent 1px),
linear-gradient(rgba(255, 255, 255, 0.015) 1px, transparent 1px),
linear-gradient(90deg, rgba(255, 255, 255, 0.015) 1px, transparent 1px);
background-size: 100% 100%, 100px 100px, 100px 100px, 20px 20px, 20px 20px;
pointer-events: none;
z-index: 0;
}
/* 顶部紧凑状态条 */
.header-bar {
position: absolute;
top: 16px;
left: 20px;
right: 20px;
display: flex;
justify-content: space-between;
align-items: center;
z-index: 20;
pointer-events: none;
}
.brand-tag {
pointer-events: auto;
display: flex;
align-items: center;
gap: 10px;
background: var(--panel-bg);
border: 1px solid var(--border-color);
padding: 6px 14px;
border-radius: 6px;
backdrop-filter: blur(10px);
}
.status-dot {
width: 7px;
height: 7px;
border-radius: 50%;
background: var(--green-ready);
box-shadow: 0 0 10px var(--green-ready);
animation: pulse-dot 2s infinite ease-in-out;
}
.title-text {
font-size: 13px;
font-weight: 600;
letter-spacing: 0.5px;
color: #fff;
}
.title-sub {
font-size: 11px;
color: var(--cyan-core);
font-family: var(--font-mono);
margin-left: 4px;
}
/* 右上角图层切换器 */
.hud-switches {
pointer-events: auto;
display: flex;
gap: 8px;
}
.hud-btn {
background: var(--panel-bg);
border: 1px solid var(--border-color);
color: var(--text-muted);
font-size: 11px;
padding: 5px 10px;
border-radius: 4px;
cursor: pointer;
display: flex;
align-items: center;
gap: 6px;
transition: all 0.2s;
}
.hud-btn:hover {
border-color: var(--cyan-core);
color: var(--text-main);
}
.hud-btn.active {
border-color: var(--cyan-core);
color: var(--cyan-core);
background: rgba(0, 240, 255, 0.08);
box-shadow: 0 0 10px rgba(0, 240, 255, 0.15);
}
.hud-btn .key-dot {
width: 5px;
height: 5px;
border-radius: 50%;
background: currentColor;
}
/* 中央核心动画舞台 */
.viewport-container {
position: absolute;
inset: 0;
display: flex;
justify-content: center;
align-items: center;
z-index: 10;
}
#cad-stage {
width: 100%;
height: 100%;
max-width: 1400px;
max-height: 860px;
}
/* 底部紧凑控制与原理步骤提示条 */
.footer-bar {
position: absolute;
bottom: 16px;
left: 20px;
right: 20px;
z-index: 20;
display: flex;
flex-direction: column;
gap: 8px;
pointer-events: none;
}
.phase-indicator {
display: flex;
justify-content: space-between;
gap: 12px;
pointer-events: auto;
}
.phase-chip {
flex: 1;
background: var(--panel-bg);
border: 1px solid rgba(255, 255, 255, 0.08);
padding: 7px 12px;
border-radius: 5px;
display: flex;
align-items: center;
gap: 8px;
backdrop-filter: blur(12px);
transition: all 0.3s cubic-bezier(0.16, 1, 0.3, 1);
}
.phase-chip.active {
border-color: var(--orange-accent);
background: rgba(255, 107, 0, 0.12);
box-shadow: 0 0 15px rgba(255, 107, 0, 0.2);
}
.phase-idx {
font-family: var(--font-mono);
font-size: 11px;
font-weight: bold;
color: var(--text-muted);
min-width: 16px;
}
.phase-chip.active .phase-idx {
color: var(--orange-accent);
}
.phase-name {
font-size: 12px;
color: var(--text-muted);
white-space: nowrap;
}
.phase-chip.active .phase-name {
color: #fff;
font-weight: 500;
}
/* 控制面板 */
.controls-panel {
pointer-events: auto;
display: flex;
align-items: center;
gap: 14px;
background: var(--panel-bg);
border: 1px solid var(--border-color);
padding: 7px 16px;
border-radius: 6px;
backdrop-filter: blur(12px);
}
.btn-action {
background: transparent;
border: 1px solid var(--border-color);
color: var(--cyan-core);
width: 30px;
height: 30px;
border-radius: 4px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.2s;
}
.btn-action:hover {
background: var(--cyan-core);
color: #000;
box-shadow: 0 0 10px var(--cyan-core);
}
.timeline-slider {
flex: 1;
height: 4px;
-webkit-appearance: none;
background: rgba(255, 255, 255, 0.15);
border-radius: 2px;
outline: none;
cursor: pointer;
}
.timeline-slider::-webkit-slider-thumb {
-webkit-appearance: none;
width: 12px;
height: 12px;
border-radius: 50%;
background: var(--cyan-core);
box-shadow: 0 0 8px var(--cyan-core);
cursor: ew-resize;
transition: transform 0.1s;
}
.timeline-slider::-webkit-slider-thumb:hover {
transform: scale(1.3);
}
.telemetry-tag {
font-family: var(--font-mono);
font-size: 11px;
color: var(--cyan-core);
min-width: 140px;
text-align: right;
}
@keyframes pulse-dot {
0%, 100% { opacity: 0.4; transform: scale(0.9); }
50% { opacity: 1; transform: scale(1.15); }
}
/* 微型悬浮参数卡 */
.param-overlay {
position: absolute;
top: 60px;
right: 20px;
z-index: 15;
background: var(--panel-bg);
border: 1px solid rgba(255, 255, 255, 0.1);
padding: 10px 14px;
border-radius: 6px;
font-size: 11px;
line-height: 1.6;
pointer-events: none;
backdrop-filter: blur(8px);
}
.param-overlay .item {
display: flex;
justify-content: space-between;
gap: 16px;
color: #94a3b8;
}
.param-overlay .val {
font-family: var(--font-mono);
color: var(--cyan-core);
font-weight: 500;
}
</style>
</head>
<body>
<div class="bg-grid"></div>
<!-- 顶部状态栏 -->
<div class="header-bar">
<div class="brand-tag">
<div class="status-dot"></div>
<span class="title-text">行星三轮组阶跃越障机构</span>
<span class="title-sub">[双轮双支承·顺时针120°翻阶]</span>
</div>
<div class="hud-switches">
<button class="hud-btn active" id="btn-toggle-forces" onclick="toggleLayer('forces')">
<span class="key-dot"></span> 力矩/矢量 (<span id="t-forces">开</span>)
</button>
<button class="hud-btn active" id="btn-toggle-orbits" onclick="toggleLayer('orbits')">
<span class="key-dot"></span> 回转包络线 (<span id="t-orbits">开</span>)
</button>
<button class="hud-btn active" id="btn-toggle-leveler" onclick="toggleLayer('leveler')">
<span class="key-dot"></span> 货箱水平阻尼 (<span id="t-leveler">开</span>)
</button>
</div>
</div>
<!-- 关键参数指示 -->
<div class="param-overlay">
<div class="item"><span>单轮直径 (2r):</span><span class="val">Ø 120 mm</span></div>
<div class="item"><span>回转半径 (R):</span><span class="val">165 mm</span></div>
<div class="item"><span>平地姿态:</span><span class="val">双轮着地支承</span></div>
<div class="item"><span>越障翻转:</span><span class="val">顺时针 (+120°)</span></div>
<div class="item"><span>台阶单级标高:</span><span class="val">68 mm (至中心轴)</span></div>
</div>
<!-- 中央主舞台 SVG -->
<div class="viewport-container">
<svg id="cad-stage" viewBox="0 0 1200 680" preserveAspectRatio="xMidYMid meet">
<defs>
<!-- 发光滤镜 -->
<filter id="glow-cyan" 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-orange" x="-30%" y="-30%" width="160%" height="160%">
<feGaussianBlur stdDeviation="4" result="blur" />
<feMerge>
<feMergeNode in="blur" />
<feMergeNode in="SourceGraphic" />
</feMerge>
</filter>
<!-- 箭头标记 -->
<marker id="arrow-cyan" viewBox="0 0 10 10" refX="6" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse">
<path d="M 0 1 L 9 5 L 0 9 z" fill="#00f0ff" />
</marker>
<marker id="arrow-orange" viewBox="0 0 10 10" refX="6" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse">
<path d="M 0 1 L 9 5 L 0 9 z" fill="#ff6b00" />
</marker>
<marker id="arrow-green" viewBox="0 0 10 10" refX="6" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse">
<path d="M 0 1 L 9 5 L 0 9 z" fill="#00ff88" />
</marker>
<!-- 材质填充 -->
<pattern id="hatch-stairs" width="12" height="12" patternTransform="rotate(45 0 0)" patternUnits="userSpaceOnUse">
<line x1="0" y1="0" x2="0" y2="12" stroke="rgba(0, 240, 255, 0.08)" stroke-width="1.5" />
</pattern>
<pattern id="cargo-texture" width="10" height="10" patternUnits="userSpaceOnUse">
<rect width="10" height="10" fill="#132035" />
<path d="M 0 10 L 10 0 M 0 0 L 10 10" stroke="rgba(0, 240, 255, 0.12)" stroke-width="0.75"/>
</pattern>
</defs>
<!-- 1. 楼梯与地形环境 (台阶标高 H=68mm,与行星轮中心等高) -->
<g id="terrain-group">
<!-- 楼梯剖面基座: 地面 Y=540, 一阶踏面 Y=472 (高68px), 二阶踏面 Y=404 -->
<path id="stair-base" d="M -50 540 L 400 540 L 400 472 L 750 472 L 750 404 L 1250 404 L 1250 640 L -50 640 Z"
fill="url(#hatch-stairs)" stroke="rgba(0, 240, 255, 0.4)" stroke-width="2" />
<!-- 楼梯踏面加固线 -->
<path d="M -50 540 L 400 540 L 400 472 L 750 472 L 750 404 L 1250 404"
fill="none" stroke="#00f0ff" stroke-width="3" filter="url(#glow-cyan)" />
<!-- 踏面防滑纹理与倒角高亮 -->
<g stroke="rgba(0, 240, 255, 0.3)" stroke-width="1">
<line x1="400" y1="540" x2="400" y2="472" stroke="#ff6b00" stroke-width="2.5" />
<line x1="750" y1="472" x2="750" y2="404" stroke="#ff6b00" stroke-width="2.5" />
<circle cx="400" cy="472" r="3" fill="#ff6b00" />
<circle cx="750" cy="404" r="3" fill="#ff6b00" />
</g>
<!-- 尺寸标注 (高度与宽度) -->
<g id="layer-dim" stroke="#64748b" stroke-width="1" font-family="JetBrains Mono" font-size="11" fill="#64748b">
<!-- 阶高标尺 -->
<line x1="380" y1="540" x2="380" y2="472" marker-start="url(#arrow-cyan)" marker-end="url(#arrow-cyan)" stroke="#00f0ff" stroke-opacity="0.6"/>
<text x="365" y="510" text-anchor="end" fill="#00f0ff">H=68mm (中心等高)</text>
<line x1="730" y1="472" x2="730" y2="404" marker-start="url(#arrow-cyan)" marker-end="url(#arrow-cyan)" stroke="#00f0ff" stroke-opacity="0.6"/>
<text x="715" y="442" text-anchor="end" fill="#00f0ff">H=68mm</text>
<!-- 踏宽标尺 -->
<line x1="400" y1="560" x2="750" y2="560" marker-start="url(#arrow-cyan)" marker-end="url(#arrow-cyan)" stroke="#00f0ff" stroke-opacity="0.6"/>
<text x="575" y="580" text-anchor="middle" fill="#00f0ff">踏面宽度 Step Width = 350mm</text>
</g>
</g>
<!-- 2. 小车主机构动态装配组 -->
<g id="cart-assembly">
<!-- 回转轨迹层 -->
<g id="layer-orbits" stroke="rgba(0, 240, 255, 0.25)" stroke-dasharray="4,4" fill="none">
<circle id="front-orbit" cx="260" cy="0" r="68" />
<circle id="rear-orbit" cx="0" cy="0" r="68" />
</g>
<!-- 车架主梁 -->
<path id="chassis-beam" d="M 0 0 L 260 0" stroke="#334155" stroke-width="14" stroke-linecap="round" />
<path id="chassis-core" d="M 0 0 L 260 0" stroke="#00f0ff" stroke-width="3" />
<!-- 阻尼器与自调平机构连杆 -->
<g id="layer-leveler">
<!-- 阻尼器套筒 -->
<line id="damper-line" x1="130" y1="0" x2="130" y2="-65" stroke="#ff6b00" stroke-width="4" stroke-dasharray="6,3" />
<circle id="damper-joint-b" cx="130" cy="0" r="5" fill="#ff6b00" />
<circle id="damper-joint-t" cx="130" cy="-65" r="5" fill="#ff6b00" />
<!-- 被动防倾摆臂 -->
<line id="arm-f" x1="40" y1="0" x2="60" y2="-60" stroke="#00f0ff" stroke-width="3" stroke-opacity="0.8" />
<line id="arm-r" x1="220" y1="0" x2="200" y2="-60" stroke="#00f0ff" stroke-width="3" stroke-opacity="0.8" />
</g>
<!-- 货箱平台 (保持水平) -->
<g id="cargo-platform">
<rect id="cargo-box" x="20" y="-120" width="220" height="58" rx="6" fill="url(#cargo-texture)" stroke="#00f0ff" stroke-width="2" />
<!-- 货物重心与水平仪 -->
<line x1="30" y1="-91" x2="230" y2="-91" stroke="rgba(0,240,255,0.3)" stroke-width="1" stroke-dasharray="3,3" />
<circle cx="130" cy="-91" r="9" fill="rgba(0, 255, 136, 0.2)" stroke="#00ff88" stroke-width="1.5" />
<line x1="121" y1="-91" x2="139" y2="-91" stroke="#00ff88" stroke-width="1.5" />
<line x1="130" y1="-100" x2="130" y2="-82" stroke="#00ff88" stroke-width="1.5" />
<text x="145" y="-88" font-family="JetBrains Mono" font-size="10" fill="#00ff88">CARGO LEVEL (0°)</text>
</g>
<!-- 后行星三轮组 (初始构型:上1轮、下2轮双轮着地) -->
<g id="rear-planetary-set" transform="translate(0, 0)">
<!-- 转臂 (Y型等角 120度,初始一臂朝上,两臂朝下) -->
<g id="rear-spider" stroke="#475569" stroke-width="12" stroke-linejoin="round" stroke-linecap="round">
<line x1="0" y1="0" x2="0" y2="-68" />
<line x1="0" y1="0" x2="58.89" y2="34" />
<line x1="0" y1="0" x2="-58.89" y2="34" />
</g>
<g id="rear-spider-glow" stroke="#00f0ff" stroke-width="2" stroke-linejoin="round" stroke-linecap="round" fill="none">
<line x1="0" y1="0" x2="0" y2="-68" />
<line x1="0" y1="0" x2="58.89" y2="34" />
<line x1="0" y1="0" x2="-58.89" y2="34" />
</g>
<!-- 3个行星橡胶轮 (R=25,下两轮接触地面支撑) -->
<g id="rear-wheels">
<g id="rw-0" transform="translate(0, -68)">
<circle r="25" fill="#1e293b" stroke="#00f0ff" stroke-width="3" />
<circle r="8" fill="#080c14" stroke="#64748b" stroke-width="2" />
<line x1="-20" y1="0" x2="20" y2="0" stroke="#00f0ff" stroke-width="1.5" />
<line x1="0" y1="-20" x2="0" y2="20" stroke="#00f0ff" stroke-width="1.5" />
</g>
<g id="rw-1" transform="translate(58.89, 34)">
<circle r="25" fill="#1e293b" stroke="#00f0ff" stroke-width="3" />
<circle r="8" fill="#080c14" stroke="#64748b" stroke-width="2" />
<line x1="-20" y1="0" x2="20" y2="0" stroke="#00f0ff" stroke-width="1.5" />
<line x1="0" y1="-20" x2="0" y2="20" stroke="#00f0ff" stroke-width="1.5" />
</g>
<g id="rw-2" transform="translate(-58.89, 34)">
<circle r="25" fill="#1e293b" stroke="#00f0ff" stroke-width="3" />
<circle r="8" fill="#080c14" stroke="#64748b" stroke-width="2" />
<line x1="-20" y1="0" x2="20" y2="0" stroke="#00f0ff" stroke-width="1.5" />
<line x1="0" y1="-20" x2="0" y2="20" stroke="#00f0ff" stroke-width="1.5" />
</g>
</g>
<!-- 中心单向轴承主轴 -->
<circle cx="0" cy="0" r="14" fill="#0f172a" stroke="#ff6b00" stroke-width="3" />
<circle cx="0" cy="0" r="5" fill="#ff6b00" />
</g>
<!-- 前行星三轮组 (初始构型:上1轮、下2轮双轮着地) -->
<g id="front-planetary-set" transform="translate(260, 0)">
<!-- 转臂 (Y型等角 120度) -->
<g id="front-spider" stroke="#475569" stroke-width="12" stroke-linejoin="round" stroke-linecap="round">
<line x1="0" y1="0" x2="0" y2="-68" />
<line x1="0" y1="0" x2="58.89" y2="34" />
<line x1="0" y1="0" x2="-58.89" y2="34" />
</g>
<g id="front-spider-glow" stroke="#ff6b00" stroke-width="2.5" stroke-linejoin="round" stroke-linecap="round" fill="none" filter="url(#glow-orange)">
<line x1="0" y1="0" x2="0" y2="-68" />
<line x1="0" y1="0" x2="58.89" y2="34" />
<line x1="0" y1="0" x2="-58.89" y2="34" />
</g>
<!-- 3个行星橡胶轮 -->
<g id="front-wheels">
<g id="fw-0" transform="translate(0, -68)">
<circle r="25" fill="#1e293b" stroke="#00f0ff" stroke-width="3" />
<circle r="8" fill="#080c14" stroke="#64748b" stroke-width="2" />
<line x1="-20" y1="0" x2="20" y2="0" stroke="#00f0ff" stroke-width="1.5" />
<line x1="0" y1="-20" x2="0" y2="20" stroke="#00f0ff" stroke-width="1.5" />
</g>
<g id="fw-1" transform="translate(58.89, 34)">
<circle r="25" fill="#1e293b" stroke="#00f0ff" stroke-width="3" />
<circle r="8" fill="#080c14" stroke="#64748b" stroke-width="2" />
<line x1="-20" y1="0" x2="20" y2="0" stroke="#00f0ff" stroke-width="1.5" />
<line x1="0" y1="-20" x2="0" y2="20" stroke="#00f0ff" stroke-width="1.5" />
</g>
<g id="fw-2" transform="translate(-58.89, 34)">
<circle r="25" fill="#1e293b" stroke="#00f0ff" stroke-width="3" />
<circle r="8" fill="#080c14" stroke="#64748b" stroke-width="2" />
<line x1="-20" y1="0" x2="20" y2="0" stroke="#00f0ff" stroke-width="1.5" />
<line x1="0" y1="-20" x2="0" y2="20" stroke="#00f0ff" stroke-width="1.5" />
</g>
</g>
<!-- 中心单向离合轴承 -->
<circle cx="0" cy="0" r="14" fill="#0f172a" stroke="#ff6b00" stroke-width="3" />
<circle cx="0" cy="0" r="5" fill="#ff6b00" />
</g>
<!-- 3. 动态受力与力矩矢量指示层 -->
<g id="layer-forces">
<!-- 前右下轮受阻碰撞法向力 (台阶阻力) -->
<g id="force-barrier" opacity="0">
<line x1="343" y1="34" x2="295" y2="34" stroke="#ff0055" stroke-width="4" marker-end="url(#arrow-orange)" />
<text x="348" y="30" font-family="JetBrains Mono" font-size="11" font-weight="bold" fill="#ff0055">F_阻挡</text>
</g>
<!-- 转臂顺时针翻转力矩圆弧 (Clockwise Torque) -->
<g id="torque-arc" opacity="0">
<path d="M 260 -35 A 35 35 0 0 1 295 0" fill="none" stroke="#ff6b00" stroke-width="3" marker-end="url(#arrow-orange)" filter="url(#glow-orange)" />
<text x="305" y="-12" font-family="JetBrains Mono" font-size="11" font-weight="bold" fill="#ff6b00">顺时针力矩 M > 6N·m</text>
</g>
<!-- 爬阶抬升力矢量 -->
<g id="force-lift" opacity="0">
<line x1="260" y1="0" x2="260" y2="-45" stroke="#00ff88" stroke-width="3.5" marker-end="url(#arrow-green)" filter="url(#glow-cyan)" />
<text x="270" y="-25" font-family="JetBrains Mono" font-size="11" font-weight="bold" fill="#00ff88">F_抬升 (跨级上行)</text>
</g>
</g>
</g>
</svg>
</div>
<!-- 底部交互控制与时序微步骤 -->
<div class="footer-bar">
<div class="phase-indicator">
<div class="phase-chip" id="chip-0">
<span class="phase-idx">01</span>
<span class="phase-name">平地双轮着地行驶</span>
</div>
<div class="phase-chip" id="chip-1">
<span class="phase-idx">02</span>
<span class="phase-name">前下轮触阶·顺时针力矩聚积</span>
</div>
<div class="phase-chip" id="chip-2">
<span class="phase-idx">03</span>
<span class="phase-name">前星轮顺时针翻转 120° 跨级</span>
</div>
<div class="phase-chip" id="chip-3">
<span class="phase-idx">04</span>
<span class="phase-name">前组双轮落阶·后组跟进翻转</span>
</div>
<div class="phase-chip" id="chip-4">
<span class="phase-idx">05</span>
<span class="phase-name">全车恢复双轮着地·平稳行驶</span>
</div>
</div>
<div class="controls-panel">
<button class="btn-action" id="btn-play-pause" onclick="togglePlay()" title="播放/暂停">
<svg id="icon-pause" width="14" height="14" viewBox="0 0 24 24" fill="currentColor"><rect x="6" y="4" width="4" height="16"/><rect x="14" y="4" width="4" height="16"/></svg>
<svg id="icon-play" width="14" height="14" viewBox="0 0 24 24" fill="currentColor" style="display:none;"><polygon points="5 3 19 12 5 21 5 3"/></svg>
</button>
<button class="btn-action" onclick="resetAnimation()" title="重置">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><path d="M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8"/><path d="M3 3v5h5"/></svg>
</button>
<input type="range" id="timeline" class="timeline-slider" min="0" max="1000" value="0" oninput="onSeek(this.value)" />
<div class="telemetry-tag" id="telemetry-status">
STAGE: 01 | 0.00s
</div>
</div>
</div>
<script>
/* 全局图层显隐开关 */
const layers = {
forces: true,
orbits: true,
leveler: true
};
function toggleLayer(layerName) {
layers[layerName] = !layers[layerName];
const btn = document.getElementById('btn-toggle-' + layerName);
const txt = document.getElementById('t-' + layerName);
if (layers[layerName]) {
btn.classList.add('active');
txt.innerText = '开';
} else {
btn.classList.remove('active');
txt.innerText = '关';
}
const elem = document.getElementById('layer-' + layerName);
if (elem) {
elem.style.display = layers[layerName] ? 'inline' : 'none';
}
}
/* 运动学时序与状态机模型 */
const TOTAL_DURATION = 9000;
let isPlaying = true;
let startTime = null;
let pausedTime = 0;
let currentProgress = 0;
// DOM 元素引用
const cartAssembly = document.getElementById('cart-assembly');
const frontSpider = document.getElementById('front-planetary-set');
const rearSpider = document.getElementById('rear-planetary-set');
const cargoPlatform = document.getElementById('cargo-platform');
const timeline = document.getElementById('timeline');
const telemetry = document.getElementById('telemetry-status');
const iconPlay = document.getElementById('icon-play');
const iconPause = document.getElementById('icon-pause');
// 矢量受力 DOM
const forceBarrier = document.getElementById('force-barrier');
const torqueArc = document.getElementById('torque-arc');
const forceLift = document.getElementById('force-lift');
// 行星轮子单独自转
const fwWheels = [document.getElementById('fw-0'), document.getElementById('fw-1'), document.getElementById('fw-2')];
const rwWheels = [document.getElementById('rw-0'), document.getElementById('rw-1'), document.getElementById('rw-2')];
// 缓动函数
function easeInOutQuad(t) {
return t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t;
}
function clamp(val, min, max) {
return Math.min(Math.max(val, min), max);
}
/* 运动学姿态求解器:双轮着地基准 + 触阶顺时针翻转 120° 恢复双轮着地 */
function computeKinematics(p) {
// 地面接触基准:地面 Y=540,双轮着地时两小轮轴心 Y=515,转臂中心轴 Y=481 (515 - 34 = 481)
// 台阶高 68mm (至中心轴高度),上一阶双轮着地时转臂中心轴 Y=413 (481 - 68 = 413)
let carX = -40 + p * 600;
let rearX = carX;
let frontX = carX + 260;
let frontAngle = 0;
let rearAngle = 0;
let wheelSpin = carX * 2.0;
let frontY = 481;
let rearY = 481;
// 前行星架越障顺时针翻转 +120° (p: 0.15 ~ 0.38)
// 翻转前:上方1轮(0,-68),下方2轮(±58.89, 34)双轮着地
// 翻转中:绕中心顺时针旋转 120°,上方轮翻跨到右前上方并落入台阶踏面
// 翻转后:角度达 +120°,原本上方轮变为右下轮,三轮构型完全恢复为上方1轮、下方2轮双轮着地!
const fStart = 0.15, fEnd = 0.38;
if (p >= fStart && p <= fEnd) {
let fFlipProgress = (p - fStart) / (fEnd - fStart);
let smoothP = easeInOutQuad(fFlipProgress);
frontAngle = smoothP * 120; // 顺时针旋转 +120°
frontY = 481 - smoothP * 68; // 中心抬升 68mm
} else if (p > fEnd) {
frontAngle = 120;
frontY = 413;
}
// 后行星架越障顺时针翻转 +120° (p: 0.48 ~ 0.71)
const rStart = 0.48, rEnd = 0.71;
if (p >= rStart && p <= rEnd) {
let rFlipProgress = (p - rStart) / (rEnd - rStart);
let smoothP = easeInOutQuad(rFlipProgress);
rearAngle = smoothP * 120; // 顺时针旋转 +120°
rearY = 481 - smoothP * 68;
} else if (p > rEnd) {
rearAngle = 120;
rearY = 413;
}
// 车身主梁倾角与自调平货箱角度
const chassisAngleRad = Math.atan2(frontY - rearY, 260);
const chassisAngleDeg = (chassisAngleRad * 180) / Math.PI;
// 矢量指示器状态
let barrierOpacity = 0;
let torqueOpacity = 0;
let liftOpacity = 0;
if (p >= 0.12 && p < 0.18) {
barrierOpacity = clamp((p - 0.12) / 0.03, 0, 1);
torqueOpacity = barrierOpacity;
} else if (p >= 0.18 && p <= 0.34) {
barrierOpacity = 0.2;
torqueOpacity = 1.0;
liftOpacity = clamp(Math.sin((p - 0.18) / 0.16 * Math.PI), 0, 1);
}
// 确定阶段指示高亮
let phaseIndex = 0;
if (p < 0.14) phaseIndex = 0;
else if (p < 0.22) phaseIndex = 1;
else if (p < 0.45) phaseIndex = 2;
else if (p < 0.75) phaseIndex = 3;
else phaseIndex = 4;
return {
carX,
frontX,
rearX,
frontY,
rearY,
frontAngle,
rearAngle,
wheelSpin,
chassisAngleDeg,
barrierOpacity,
torqueOpacity,
liftOpacity,
phaseIndex
};
}
/* 渲染一帧 */
function renderFrame(state) {
// 1. 底盘定位与俯仰
cartAssembly.setAttribute('transform', `translate(${state.rearX}, ${state.rearY}) rotate(${state.chassisAngleDeg})`);
// 2. 前后星形转臂顺时针旋转 (抵消底盘倾角以保持世界绝对角度正确)
frontSpider.setAttribute('transform', `translate(260, 0) rotate(${state.frontAngle - state.chassisAngleDeg})`);
rearSpider.setAttribute('transform', `translate(0, 0) rotate(${state.rearAngle - state.chassisAngleDeg})`);
// 3. 轮子自转
const wRot = `rotate(${state.wheelSpin})`;
fwWheels.forEach(w => w.setAttribute('transform', `${w.getAttribute('data-base') || ''} ${wRot}`));
rwWheels.forEach(w => w.setAttribute('transform', `${w.getAttribute('data-base') || ''} ${wRot}`));
// 4. 自调平货箱:反向旋转抵消车身倾角,保持水平
cargoPlatform.setAttribute('transform', `rotate(${-state.chassisAngleDeg}, 130, 0)`);
// 5. 受力与力矩矢量透明度
if (layers.forces) {
forceBarrier.setAttribute('opacity', state.barrierOpacity);
torqueArc.setAttribute('opacity', state.torqueOpacity);
forceLift.setAttribute('opacity', state.liftOpacity);
}
// 6. 阶段高亮更新
for (let i = 0; i <= 4; i++) {
const chip = document.getElementById(`chip-${i}`);
if (i === state.phaseIndex) {
chip.classList.add('active');
} else {
chip.classList.remove('active');
}
}
// 7. 更新底部滑块与遥测数据
timeline.value = currentProgress * 1000;
const sec = (currentProgress * (TOTAL_DURATION / 1000)).toFixed(2);
telemetry.innerText = `PHASE: 0${state.phaseIndex + 1} | T: ${sec}s | α: ${state.chassisAngleDeg.toFixed(1)}°`;
}
// 初始化轮子相对基座位置缓存 (上1下2双轮着地构型)
function initWheelTransforms() {
fwWheels[0].setAttribute('data-base', 'translate(0, -68)');
fwWheels[1].setAttribute('data-base', 'translate(58.89, 34)');
fwWheels[2].setAttribute('data-base', 'translate(-58.89, 34)');
rwWheels[0].setAttribute('data-base', 'translate(0, -68)');
rwWheels[1].setAttribute('data-base', 'translate(58.89, 34)');
rwWheels[2].setAttribute('data-base', 'translate(-58.89, 34)');
}
/* 动画循环驱动 */
function animate(timestamp) {
if (!startTime) startTime = timestamp;
if (isPlaying) {
const elapsed = (timestamp - startTime + pausedTime) % TOTAL_DURATION;
currentProgress = elapsed / TOTAL_DURATION;
}
const state = computeKinematics(currentProgress);
renderFrame(state);
requestAnimationFrame(animate);
}
/* 交互控制函数 */
function togglePlay() {
isPlaying = !isPlaying;
if (isPlaying) {
startTime = performance.now();
pausedTime = currentProgress * TOTAL_DURATION;
iconPlay.style.display = 'none';
iconPause.style.display = 'inline';
} else {
pausedTime = currentProgress * TOTAL_DURATION;
iconPlay.style.display = 'inline';
iconPause.style.display = 'none';
}
}
function resetAnimation() {
currentProgress = 0;
pausedTime = 0;
startTime = performance.now();
const state = computeKinematics(0);
renderFrame(state);
}
function onSeek(val) {
currentProgress = val / 1000;
pausedTime = currentProgress * TOTAL_DURATION;
startTime = performance.now();
const state = computeKinematics(currentProgress);
renderFrame(state);
}
// DOM 加载完成自动启动
document.addEventListener('DOMContentLoaded', () => {
initWheelTransforms();
requestAnimationFrame(animate);
});
</script>
</body>
</html>
<!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: #080c14;
--panel-bg: rgba(13, 20, 36, 0.75);
--border-color: rgba(0, 229, 255, 0.2);
--border-glow: rgba(0, 229, 255, 0.4);
--cyan-core: #00f0ff;
--orange-accent: #ff6b00;
--orange-glow: rgba(255, 107, 0, 0.4);
--green-ready: #00ff88;
--text-main: #e2e8f0;
--text-muted: #64748b;
--font-mono: "JetBrains Mono", "SF Mono", "Fira Code", monospace;
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
user-select: none;
}
body {
background-color: var(--bg-color);
color: var(--text-main);
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
overflow: hidden;
width: 100vw;
height: 100vh;
display: flex;
flex-direction: column;
justify-content: space-between;
position: relative;
}
/* 背景精细 CAD 测绘网格 */
.bg-grid {
position: absolute;
inset: 0;
background-image:
radial-gradient(circle at 50% 50%, rgba(0, 240, 255, 0.05) 0%, transparent 80%),
linear-gradient(rgba(0, 240, 255, 0.03) 1px, transparent 1px),
linear-gradient(90deg, rgba(0, 240, 255, 0.03) 1px, transparent 1px),
linear-gradient(rgba(255, 255, 255, 0.015) 1px, transparent 1px),
linear-gradient(90deg, rgba(255, 255, 255, 0.015) 1px, transparent 1px);
background-size: 100% 100%, 100px 100px, 100px 100px, 20px 20px, 20px 20px;
pointer-events: none;
z-index: 0;
}
/* 顶部紧凑状态条 */
.header-bar {
position: absolute;
top: 16px;
left: 20px;
right: 20px;
display: flex;
justify-content: space-between;
align-items: center;
z-index: 20;
pointer-events: none;
}
.brand-tag {
pointer-events: auto;
display: flex;
align-items: center;
gap: 10px;
background: var(--panel-bg);
border: 1px solid var(--border-color);
padding: 6px 14px;
border-radius: 6px;
backdrop-filter: blur(10px);
}
.status-dot {
width: 7px;
height: 7px;
border-radius: 50%;
background: var(--green-ready);
box-shadow: 0 0 10px var(--green-ready);
animation: pulse-dot 2s infinite ease-in-out;
}
.title-text {
font-size: 13px;
font-weight: 600;
letter-spacing: 0.5px;
color: #fff;
}
.title-sub {
font-size: 11px;
color: var(--cyan-core);
font-family: var(--font-mono);
margin-left: 4px;
}
/* 右上角图层切换器 */
.hud-switches {
pointer-events: auto;
display: flex;
gap: 8px;
}
.hud-btn {
background: var(--panel-bg);
border: 1px solid var(--border-color);
color: var(--text-muted);
font-size: 11px;
padding: 5px 10px;
border-radius: 4px;
cursor: pointer;
display: flex;
align-items: center;
gap: 6px;
transition: all 0.2s;
}
.hud-btn:hover {
border-color: var(--cyan-core);
color: var(--text-main);
}
.hud-btn.active {
border-color: var(--cyan-core);
color: var(--cyan-core);
background: rgba(0, 240, 255, 0.08);
box-shadow: 0 0 10px rgba(0, 240, 255, 0.15);
}
.hud-btn .key-dot {
width: 5px;
height: 5px;
border-radius: 50%;
background: currentColor;
}
/* 中央核心动画舞台 */
.viewport-container {
position: absolute;
inset: 0;
display: flex;
justify-content: center;
align-items: center;
z-index: 10;
}
#cad-stage {
width: 100%;
height: 100%;
max-width: 1400px;
max-height: 860px;
}
/* 底部紧凑控制与原理步骤提示条 */
.footer-bar {
position: absolute;
bottom: 16px;
left: 20px;
right: 20px;
z-index: 20;
display: flex;
flex-direction: column;
gap: 8px;
pointer-events: none;
}
.phase-indicator {
display: flex;
justify-content: space-between;
gap: 8px;
pointer-events: auto;
}
.phase-chip {
flex: 1;
background: var(--panel-bg);
border: 1px solid rgba(255, 255, 255, 0.08);
padding: 6px 10px;
border-radius: 5px;
display: flex;
align-items: center;
gap: 6px;
backdrop-filter: blur(12px);
transition: all 0.3s cubic-bezier(0.16, 1, 0.3, 1);
}
.phase-chip.active {
border-color: var(--orange-accent);
background: rgba(255, 107, 0, 0.12);
box-shadow: 0 0 15px rgba(255, 107, 0, 0.2);
}
.phase-idx {
font-family: var(--font-mono);
font-size: 11px;
font-weight: bold;
color: var(--text-muted);
min-width: 16px;
}
.phase-chip.active .phase-idx {
color: var(--orange-accent);
}
.phase-name {
font-size: 11.5px;
color: var(--text-muted);
white-space: nowrap;
}
.phase-chip.active .phase-name {
color: #fff;
font-weight: 500;
}
/* 控制面板 */
.controls-panel {
pointer-events: auto;
display: flex;
align-items: center;
gap: 14px;
background: var(--panel-bg);
border: 1px solid var(--border-color);
padding: 7px 16px;
border-radius: 6px;
backdrop-filter: blur(12px);
}
.btn-action {
background: transparent;
border: 1px solid var(--border-color);
color: var(--cyan-core);
width: 30px;
height: 30px;
border-radius: 4px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.2s;
}
.btn-action:hover {
background: var(--cyan-core);
color: #000;
box-shadow: 0 0 10px var(--cyan-core);
}
.timeline-slider {
flex: 1;
height: 4px;
-webkit-appearance: none;
background: rgba(255, 255, 255, 0.15);
border-radius: 2px;
outline: none;
cursor: pointer;
}
.timeline-slider::-webkit-slider-thumb {
-webkit-appearance: none;
width: 12px;
height: 12px;
border-radius: 50%;
background: var(--cyan-core);
box-shadow: 0 0 8px var(--cyan-core);
cursor: ew-resize;
transition: transform 0.1s;
}
.timeline-slider::-webkit-slider-thumb:hover {
transform: scale(1.3);
}
.telemetry-tag {
font-family: var(--font-mono);
font-size: 11px;
color: var(--cyan-core);
min-width: 140px;
text-align: right;
}
@keyframes pulse-dot {
0%, 100% { opacity: 0.4; transform: scale(0.9); }
50% { opacity: 1; transform: scale(1.15); }
}
/* 微型悬浮参数卡 */
.param-overlay {
position: absolute;
top: 60px;
right: 20px;
z-index: 15;
background: var(--panel-bg);
border: 1px solid rgba(255, 255, 255, 0.1);
padding: 10px 14px;
border-radius: 6px;
font-size: 11px;
line-height: 1.6;
pointer-events: none;
backdrop-filter: blur(8px);
}
.param-overlay .item {
display: flex;
justify-content: space-between;
gap: 16px;
color: #94a3b8;
}
.param-overlay .val {
font-family: var(--font-mono);
color: var(--cyan-core);
font-weight: 500;
}
</style>
</head>
<body>
<div class="bg-grid"></div>
<!-- 顶部状态栏 -->
<div class="header-bar">
<div class="brand-tag">
<div class="status-dot"></div>
<span class="title-text">行星三轮组阶跃越障机构</span>
<span class="title-sub">[双级阶梯·连续双轮自适应顺时针翻转]</span>
</div>
<div class="hud-switches">
<button class="hud-btn active" id="btn-toggle-forces" onclick="toggleLayer('forces')">
<span class="key-dot"></span> 力矩/矢量 (<span id="t-forces">开</span>)
</button>
<button class="hud-btn active" id="btn-toggle-orbits" onclick="toggleLayer('orbits')">
<span class="key-dot"></span> 回转包络线 (<span id="t-orbits">开</span>)
</button>
<button class="hud-btn active" id="btn-toggle-leveler" onclick="toggleLayer('leveler')">
<span class="key-dot"></span> 货箱水平阻尼 (<span id="t-leveler">开</span>)
</button>
</div>
</div>
<!-- 关键参数指示 -->
<div class="param-overlay">
<div class="item"><span>单轮直径 (2r):</span><span class="val">Ø 120 mm</span></div>
<div class="item"><span>回转半径 (R):</span><span class="val">165 mm</span></div>
<div class="item"><span>平地姿态:</span><span class="val">双轮着地支承</span></div>
<div class="item"><span>连跨双阶:</span><span class="val">顺时针 2 × 120° = 240°</span></div>
<div class="item"><span>单级台阶高:</span><span class="val">68 mm (至中心轴)</span></div>
</div>
<!-- 中央主舞台 SVG -->
<div class="viewport-container">
<svg id="cad-stage" viewBox="0 0 1200 680" preserveAspectRatio="xMidYMid meet">
<defs>
<!-- 发光滤镜 -->
<filter id="glow-cyan" 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-orange" x="-30%" y="-30%" width="160%" height="160%">
<feGaussianBlur stdDeviation="4" result="blur" />
<feMerge>
<feMergeNode in="blur" />
<feMergeNode in="SourceGraphic" />
</feMerge>
</filter>
<!-- 箭头标记 -->
<marker id="arrow-cyan" viewBox="0 0 10 10" refX="6" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse">
<path d="M 0 1 L 9 5 L 0 9 z" fill="#00f0ff" />
</marker>
<marker id="arrow-orange" viewBox="0 0 10 10" refX="6" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse">
<path d="M 0 1 L 9 5 L 0 9 z" fill="#ff6b00" />
</marker>
<marker id="arrow-green" viewBox="0 0 10 10" refX="6" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse">
<path d="M 0 1 L 9 5 L 0 9 z" fill="#00ff88" />
</marker>
<!-- 材质填充 -->
<pattern id="hatch-stairs" width="12" height="12" patternTransform="rotate(45 0 0)" patternUnits="userSpaceOnUse">
<line x1="0" y1="0" x2="0" y2="12" stroke="rgba(0, 240, 255, 0.08)" stroke-width="1.5" />
</pattern>
<pattern id="cargo-texture" width="10" height="10" patternUnits="userSpaceOnUse">
<rect width="10" height="10" fill="#132035" />
<path d="M 0 10 L 10 0 M 0 0 L 10 10" stroke="rgba(0, 240, 255, 0.12)" stroke-width="0.75"/>
</pattern>
</defs>
<!-- 1. 楼梯与地形环境 (两级台阶,每级高 68mm) -->
<g id="terrain-group">
<!-- 楼梯剖面基座: 地面 Y=540, 一阶踏面 Y=472 (高68px), 二阶踏面 Y=404 (高68px) -->
<path id="stair-base" d="M -50 540 L 350 540 L 350 472 L 700 472 L 700 404 L 1250 404 L 1250 640 L -50 640 Z"
fill="url(#hatch-stairs)" stroke="rgba(0, 240, 255, 0.4)" stroke-width="2" />
<!-- 楼梯踏面加固线 -->
<path d="M -50 540 L 350 540 L 350 472 L 700 472 L 700 404 L 1250 404"
fill="none" stroke="#00f0ff" stroke-width="3" filter="url(#glow-cyan)" />
<!-- 踏面防滑纹理与倒角高亮 -->
<g stroke="rgba(0, 240, 255, 0.3)" stroke-width="1">
<line x1="350" y1="540" x2="350" y2="472" stroke="#ff6b00" stroke-width="2.5" />
<line x1="700" y1="472" x2="700" y2="404" stroke="#ff6b00" stroke-width="2.5" />
<circle cx="350" cy="472" r="3" fill="#ff6b00" />
<circle cx="700" cy="404" r="3" fill="#ff6b00" />
</g>
<!-- 尺寸标注 (高度与宽度) -->
<g id="layer-dim" stroke="#64748b" stroke-width="1" font-family="JetBrains Mono" font-size="11" fill="#64748b">
<!-- 阶高标尺 -->
<line x1="330" y1="540" x2="330" y2="472" marker-start="url(#arrow-cyan)" marker-end="url(#arrow-cyan)" stroke="#00f0ff" stroke-opacity="0.6"/>
<text x="315" y="510" text-anchor="end" fill="#00f0ff">H1=68mm</text>
<line x1="680" y1="472" x2="680" y2="404" marker-start="url(#arrow-cyan)" marker-end="url(#arrow-cyan)" stroke="#00f0ff" stroke-opacity="0.6"/>
<text x="665" y="442" text-anchor="end" fill="#00f0ff">H2=68mm</text>
<!-- 踏宽标尺 -->
<line x1="350" y1="560" x2="700" y2="560" marker-start="url(#arrow-cyan)" marker-end="url(#arrow-cyan)" stroke="#00f0ff" stroke-opacity="0.6"/>
<text x="525" y="580" text-anchor="middle" fill="#00f0ff">Step Width = 350mm</text>
</g>
</g>
<!-- 2. 小车主机构动态装配组 -->
<g id="cart-assembly">
<!-- 回转轨迹层 -->
<g id="layer-orbits" stroke="rgba(0, 240, 255, 0.25)" stroke-dasharray="4,4" fill="none">
<circle id="front-orbit" cx="260" cy="0" r="68" />
<circle id="rear-orbit" cx="0" cy="0" r="68" />
</g>
<!-- 车架主梁 -->
<path id="chassis-beam" d="M 0 0 L 260 0" stroke="#334155" stroke-width="14" stroke-linecap="round" />
<path id="chassis-core" d="M 0 0 L 260 0" stroke="#00f0ff" stroke-width="3" />
<!-- 阻尼器与自调平机构连杆 -->
<g id="layer-leveler">
<!-- 阻尼器套筒 -->
<line id="damper-line" x1="130" y1="0" x2="130" y2="-65" stroke="#ff6b00" stroke-width="4" stroke-dasharray="6,3" />
<circle id="damper-joint-b" cx="130" cy="0" r="5" fill="#ff6b00" />
<circle id="damper-joint-t" cx="130" cy="-65" r="5" fill="#ff6b00" />
<!-- 被动防倾摆臂 -->
<line id="arm-f" x1="40" y1="0" x2="60" y2="-60" stroke="#00f0ff" stroke-width="3" stroke-opacity="0.8" />
<line id="arm-r" x1="220" y1="0" x2="200" y2="-60" stroke="#00f0ff" stroke-width="3" stroke-opacity="0.8" />
</g>
<!-- 货箱平台 (保持水平) -->
<g id="cargo-platform">
<rect id="cargo-box" x="20" y="-120" width="220" height="58" rx="6" fill="url(#cargo-texture)" stroke="#00f0ff" stroke-width="2" />
<!-- 货物重心与水平仪 -->
<line x1="30" y1="-91" x2="230" y2="-91" stroke="rgba(0,240,255,0.3)" stroke-width="1" stroke-dasharray="3,3" />
<circle cx="130" cy="-91" r="9" fill="rgba(0, 255, 136, 0.2)" stroke="#00ff88" stroke-width="1.5" />
<line x1="121" y1="-91" x2="139" y2="-91" stroke="#00ff88" stroke-width="1.5" />
<line x1="130" y1="-100" x2="130" y2="-82" stroke="#00ff88" stroke-width="1.5" />
<text x="145" y="-88" font-family="JetBrains Mono" font-size="10" fill="#00ff88">CARGO LEVEL (0°)</text>
</g>
<!-- 后行星三轮组 (初始构型:上1轮、下2轮双轮着地) -->
<g id="rear-planetary-set" transform="translate(0, 0)">
<!-- 转臂 (Y型等角 120度) -->
<g id="rear-spider" stroke="#475569" stroke-width="12" stroke-linejoin="round" stroke-linecap="round">
<line x1="0" y1="0" x2="0" y2="-68" />
<line x1="0" y1="0" x2="58.89" y2="34" />
<line x1="0" y1="0" x2="-58.89" y2="34" />
</g>
<g id="rear-spider-glow" stroke="#00f0ff" stroke-width="2" stroke-linejoin="round" stroke-linecap="round" fill="none">
<line x1="0" y1="0" x2="0" y2="-68" />
<line x1="0" y1="0" x2="58.89" y2="34" />
<line x1="0" y1="0" x2="-58.89" y2="34" />
</g>
<!-- 3个行星橡胶轮 (R=25,下两轮接触地面支撑) -->
<g id="rear-wheels">
<g id="rw-0" transform="translate(0, -68)">
<circle r="25" fill="#1e293b" stroke="#00f0ff" stroke-width="3" />
<circle r="8" fill="#080c14" stroke="#64748b" stroke-width="2" />
<line x1="-20" y1="0" x2="20" y2="0" stroke="#00f0ff" stroke-width="1.5" />
<line x1="0" y1="-20" x2="0" y2="20" stroke="#00f0ff" stroke-width="1.5" />
</g>
<g id="rw-1" transform="translate(58.89, 34)">
<circle r="25" fill="#1e293b" stroke="#00f0ff" stroke-width="3" />
<circle r="8" fill="#080c14" stroke="#64748b" stroke-width="2" />
<line x1="-20" y1="0" x2="20" y2="0" stroke="#00f0ff" stroke-width="1.5" />
<line x1="0" y1="-20" x2="0" y2="20" stroke="#00f0ff" stroke-width="1.5" />
</g>
<g id="rw-2" transform="translate(-58.89, 34)">
<circle r="25" fill="#1e293b" stroke="#00f0ff" stroke-width="3" />
<circle r="8" fill="#080c14" stroke="#64748b" stroke-width="2" />
<line x1="-20" y1="0" x2="20" y2="0" stroke="#00f0ff" stroke-width="1.5" />
<line x1="0" y1="-20" x2="0" y2="20" stroke="#00f0ff" stroke-width="1.5" />
</g>
</g>
<!-- 中心单向轴承主轴 -->
<circle cx="0" cy="0" r="14" fill="#0f172a" stroke="#ff6b00" stroke-width="3" />
<circle cx="0" cy="0" r="5" fill="#ff6b00" />
</g>
<!-- 前行星三轮组 (初始构型:上1轮、下2轮双轮着地) -->
<g id="front-planetary-set" transform="translate(260, 0)">
<!-- 转臂 (Y型等角 120度) -->
<g id="front-spider" stroke="#475569" stroke-width="12" stroke-linejoin="round" stroke-linecap="round">
<line x1="0" y1="0" x2="0" y2="-68" />
<line x1="0" y1="0" x2="58.89" y2="34" />
<line x1="0" y1="0" x2="-58.89" y2="34" />
</g>
<g id="front-spider-glow" stroke="#ff6b00" stroke-width="2.5" stroke-linejoin="round" stroke-linecap="round" fill="none" filter="url(#glow-orange)">
<line x1="0" y1="0" x2="0" y2="-68" />
<line x1="0" y1="0" x2="58.89" y2="34" />
<line x1="0" y1="0" x2="-58.89" y2="34" />
</g>
<!-- 3个行星橡胶轮 -->
<g id="front-wheels">
<g id="fw-0" transform="translate(0, -68)">
<circle r="25" fill="#1e293b" stroke="#00f0ff" stroke-width="3" />
<circle r="8" fill="#080c14" stroke="#64748b" stroke-width="2" />
<line x1="-20" y1="0" x2="20" y2="0" stroke="#00f0ff" stroke-width="1.5" />
<line x1="0" y1="-20" x2="0" y2="20" stroke="#00f0ff" stroke-width="1.5" />
</g>
<g id="fw-1" transform="translate(58.89, 34)">
<circle r="25" fill="#1e293b" stroke="#00f0ff" stroke-width="3" />
<circle r="8" fill="#080c14" stroke="#64748b" stroke-width="2" />
<line x1="-20" y1="0" x2="20" y2="0" stroke="#00f0ff" stroke-width="1.5" />
<line x1="0" y1="-20" x2="0" y2="20" stroke="#00f0ff" stroke-width="1.5" />
</g>
<g id="fw-2" transform="translate(-58.89, 34)">
<circle r="25" fill="#1e293b" stroke="#00f0ff" stroke-width="3" />
<circle r="8" fill="#080c14" stroke="#64748b" stroke-width="2" />
<line x1="-20" y1="0" x2="20" y2="0" stroke="#00f0ff" stroke-width="1.5" />
<line x1="0" y1="-20" x2="0" y2="20" stroke="#00f0ff" stroke-width="1.5" />
</g>
</g>
<!-- 中心单向离合轴承 -->
<circle cx="0" cy="0" r="14" fill="#0f172a" stroke="#ff6b00" stroke-width="3" />
<circle cx="0" cy="0" r="5" fill="#ff6b00" />
</g>
<!-- 3. 动态受力与力矩矢量指示层 -->
<g id="layer-forces">
<!-- 前右下轮受阻碰撞法向力 (台阶阻力) -->
<g id="force-barrier" opacity="0">
<line x1="343" y1="34" x2="295" y2="34" stroke="#ff0055" stroke-width="4" marker-end="url(#arrow-orange)" />
<text x="348" y="30" font-family="JetBrains Mono" font-size="11" font-weight="bold" fill="#ff0055">F_阻挡</text>
</g>
<!-- 转臂顺时针翻转力矩圆弧 (Clockwise Torque) -->
<g id="torque-arc" opacity="0">
<path d="M 260 -35 A 35 35 0 0 1 295 0" fill="none" stroke="#ff6b00" stroke-width="3" marker-end="url(#arrow-orange)" filter="url(#glow-orange)" />
<text x="305" y="-12" font-family="JetBrains Mono" font-size="11" font-weight="bold" fill="#ff6b00">顺时针力矩 M > 6N·m</text>
</g>
<!-- 爬阶抬升力矢量 -->
<g id="force-lift" opacity="0">
<line x1="260" y1="0" x2="260" y2="-45" stroke="#00ff88" stroke-width="3.5" marker-end="url(#arrow-green)" filter="url(#glow-cyan)" />
<text x="270" y="-25" font-family="JetBrains Mono" font-size="11" font-weight="bold" fill="#00ff88">F_抬升 (跨级上行)</text>
</g>
</g>
</g>
</svg>
</div>
<!-- 底部交互控制与时序微步骤 -->
<div class="footer-bar">
<div class="phase-indicator">
<div class="phase-chip" id="chip-0">
<span class="phase-idx">01</span>
<span class="phase-name">平地双轮滚动</span>
</div>
<div class="phase-chip" id="chip-1">
<span class="phase-idx">02</span>
<span class="phase-name">前轮顺时针翻第1阶</span>
</div>
<div class="phase-chip" id="chip-2">
<span class="phase-idx">03</span>
<span class="phase-name">后轮顺时针翻第1阶</span>
</div>
<div class="phase-chip" id="chip-3">
<span class="phase-idx">04</span>
<span class="phase-name">前轮顺时针翻第2阶</span>
</div>
<div class="phase-chip" id="chip-4">
<span class="phase-idx">05</span>
<span class="phase-name">后轮顺时针翻第2阶</span>
</div>
<div class="phase-chip" id="chip-5">
<span class="phase-idx">06</span>
<span class="phase-name">高台双轮恢复平驶</span>
</div>
</div>
<div class="controls-panel">
<button class="btn-action" id="btn-play-pause" onclick="togglePlay()" title="播放/暂停">
<svg id="icon-pause" width="14" height="14" viewBox="0 0 24 24" fill="currentColor"><rect x="6" y="4" width="4" height="16"/><rect x="14" y="4" width="4" height="16"/></svg>
<svg id="icon-play" width="14" height="14" viewBox="0 0 24 24" fill="currentColor" style="display:none;"><polygon points="5 3 19 12 5 21 5 3"/></svg>
</button>
<button class="btn-action" onclick="resetAnimation()" title="重置">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><path d="M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8"/><path d="M3 3v5h5"/></svg>
</button>
<input type="range" id="timeline" class="timeline-slider" min="0" max="1000" value="0" oninput="onSeek(this.value)" />
<div class="telemetry-tag" id="telemetry-status">
STAGE: 01 | 0.00s
</div>
</div>
</div>
<script>
/* 全局图层显隐开关 */
const layers = {
forces: true,
orbits: true,
leveler: true
};
function toggleLayer(layerName) {
layers[layerName] = !layers[layerName];
const btn = document.getElementById('btn-toggle-' + layerName);
const txt = document.getElementById('t-' + layerName);
if (layers[layerName]) {
btn.classList.add('active');
txt.innerText = '开';
} else {
btn.classList.remove('active');
txt.innerText = '关';
}
const elem = document.getElementById('layer-' + layerName);
if (elem) {
elem.style.display = layers[layerName] ? 'inline' : 'none';
}
}
/* 运动学时序与状态机模型 (两级台阶连续翻转,总时长 12秒) */
const TOTAL_DURATION = 12000;
let isPlaying = true;
let startTime = null;
let pausedTime = 0;
let currentProgress = 0;
// DOM 元素引用
const cartAssembly = document.getElementById('cart-assembly');
const frontSpider = document.getElementById('front-planetary-set');
const rearSpider = document.getElementById('rear-planetary-set');
const cargoPlatform = document.getElementById('cargo-platform');
const timeline = document.getElementById('timeline');
const telemetry = document.getElementById('telemetry-status');
const iconPlay = document.getElementById('icon-play');
const iconPause = document.getElementById('icon-pause');
// 矢量受力 DOM
const forceBarrier = document.getElementById('force-barrier');
const torqueArc = document.getElementById('torque-arc');
const forceLift = document.getElementById('force-lift');
// 行星轮子单独自转
const fwWheels = [document.getElementById('fw-0'), document.getElementById('fw-1'), document.getElementById('fw-2')];
const rwWheels = [document.getElementById('rw-0'), document.getElementById('rw-1'), document.getElementById('rw-2')];
// 缓动函数
function easeInOutQuad(t) {
return t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t;
}
function clamp(val, min, max) {
return Math.min(Math.max(val, min), max);
}
/* 运动学姿态求解器:连跨两阶台阶,每次顺时针翻转 120° 并恢复双轮着地 */
function computeKinematics(p) {
// 行程:小车从 X=-80 移动至 X=760
// 地形标高:地面 Y=481 (小车中心), 一阶踏面 Y=413 (抬升68mm), 二阶踏面 Y=345 (再抬升68mm)
let carX = -80 + p * 840;
let rearX = carX;
let frontX = carX + 260;
let frontAngle = 0;
let rearAngle = 0;
let wheelSpin = carX * 2.0;
let frontY = 481;
let rearY = 481;
// 阶段定义:
// [1] 前轮翻第1阶 (p: 0.10 ~ 0.23) -> 角度 0° -> 120°, frontY 481 -> 413
// [2] 后轮翻第1阶 (p: 0.28 ~ 0.41) -> 角度 0° -> 120°, rearY 481 -> 413
// [3] 前轮翻第2阶 (p: 0.47 ~ 0.60) -> 角度 120° -> 240°, frontY 413 -> 345
// [4] 后轮翻第2阶 (p: 0.65 ~ 0.78) -> 角度 120° -> 240°, rearY 413 -> 345
// [5] 高台双轮平稳行驶 (p > 0.78)
// 前行星架计算
if (p < 0.10) {
frontAngle = 0;
frontY = 481;
} else if (p >= 0.10 && p <= 0.23) {
let pNorm = (p - 0.10) / 0.13;
let sm = easeInOutQuad(pNorm);
frontAngle = sm * 120;
frontY = 481 - sm * 68;
} else if (p > 0.23 && p < 0.47) {
frontAngle = 120;
frontY = 413;
} else if (p >= 0.47 && p <= 0.60) {
let pNorm = (p - 0.47) / 0.13;
let sm = easeInOutQuad(pNorm);
frontAngle = 120 + sm * 120;
frontY = 413 - sm * 68;
} else {
frontAngle = 240;
frontY = 345;
}
// 后行星架计算
if (p < 0.28) {
rearAngle = 0;
rearY = 481;
} else if (p >= 0.28 && p <= 0.41) {
let pNorm = (p - 0.28) / 0.13;
let sm = easeInOutQuad(pNorm);
rearAngle = sm * 120;
rearY = 481 - sm * 68;
} else if (p > 0.41 && p < 0.65) {
rearAngle = 120;
rearY = 413;
} else if (p >= 0.65 && p <= 0.78) {
let pNorm = (p - 0.65) / 0.13;
let sm = easeInOutQuad(pNorm);
rearAngle = 120 + sm * 120;
rearY = 413 - sm * 68;
} else {
rearAngle = 240;
rearY = 345;
}
// 车身主梁倾角与自调平货箱角度
const chassisAngleRad = Math.atan2(frontY - rearY, 260);
const chassisAngleDeg = (chassisAngleRad * 180) / Math.PI;
// 矢量指示器状态 (在前轮翻第1阶与第2阶时高亮)
let barrierOpacity = 0;
let torqueOpacity = 0;
let liftOpacity = 0;
if ((p >= 0.08 && p <= 0.21) || (p >= 0.45 && p <= 0.58)) {
let localP = (p >= 0.08 && p <= 0.21) ? (p - 0.08) / 0.13 : (p - 0.45) / 0.13;
barrierOpacity = localP < 0.3 ? 1.0 : clamp(1.0 - (localP - 0.3) / 0.7, 0, 1);
torqueOpacity = 1.0;
liftOpacity = Math.sin(localP * Math.PI);
}
// 确定阶段指示高亮
let phaseIndex = 0;
if (p < 0.10) phaseIndex = 0;
else if (p < 0.26) phaseIndex = 1;
else if (p < 0.44) phaseIndex = 2;
else if (p < 0.63) phaseIndex = 3;
else if (p < 0.80) phaseIndex = 4;
else phaseIndex = 5;
return {
carX,
frontX,
rearX,
frontY,
rearY,
frontAngle,
rearAngle,
wheelSpin,
chassisAngleDeg,
barrierOpacity,
torqueOpacity,
liftOpacity,
phaseIndex
};
}
/* 渲染一帧 */
function renderFrame(state) {
// 1. 底盘定位与俯仰
cartAssembly.setAttribute('transform', `translate(${state.rearX}, ${state.rearY}) rotate(${state.chassisAngleDeg})`);
// 2. 前后星形转臂顺时针旋转 (抵消底盘倾角以保持世界绝对角度正确)
frontSpider.setAttribute('transform', `translate(260, 0) rotate(${state.frontAngle - state.chassisAngleDeg})`);
rearSpider.setAttribute('transform', `translate(0, 0) rotate(${state.rearAngle - state.chassisAngleDeg})`);
// 3. 轮子自转
const wRot = `rotate(${state.wheelSpin})`;
fwWheels.forEach(w => w.setAttribute('transform', `${w.getAttribute('data-base') || ''} ${wRot}`));
rwWheels.forEach(w => w.setAttribute('transform', `${w.getAttribute('data-base') || ''} ${wRot}`));
// 4. 自调平货箱:反向旋转抵消车身倾角,保持水平
cargoPlatform.setAttribute('transform', `rotate(${-state.chassisAngleDeg}, 130, 0)`);
// 5. 受力与力矩矢量透明度
if (layers.forces) {
forceBarrier.setAttribute('opacity', state.barrierOpacity);
torqueArc.setAttribute('opacity', state.torqueOpacity);
forceLift.setAttribute('opacity', state.liftOpacity);
}
// 6. 阶段高亮更新
for (let i = 0; i <= 5; i++) {
const chip = document.getElementById(`chip-${i}`);
if (i === state.phaseIndex) {
chip.classList.add('active');
} else {
chip.classList.remove('active');
}
}
// 7. 更新底部滑块与遥测数据
timeline.value = currentProgress * 1000;
const sec = (currentProgress * (TOTAL_DURATION / 1000)).toFixed(2);
telemetry.innerText = `PHASE: 0${state.phaseIndex + 1} | T: ${sec}s | α: ${state.chassisAngleDeg.toFixed(1)}°`;
}
// 初始化轮子相对基座位置缓存 (上1下2双轮着地构型)
function initWheelTransforms() {
fwWheels[0].setAttribute('data-base', 'translate(0, -68)');
fwWheels[1].setAttribute('data-base', 'translate(58.89, 34)');
fwWheels[2].setAttribute('data-base', 'translate(-58.89, 34)');
rwWheels[0].setAttribute('data-base', 'translate(0, -68)');
rwWheels[1].setAttribute('data-base', 'translate(58.89, 34)');
rwWheels[2].setAttribute('data-base', 'translate(-58.89, 34)');
}
/* 动画循环驱动 */
function animate(timestamp) {
if (!startTime) startTime = timestamp;
if (isPlaying) {
const elapsed = (timestamp - startTime + pausedTime) % TOTAL_DURATION;
currentProgress = elapsed / TOTAL_DURATION;
}
const state = computeKinematics(currentProgress);
renderFrame(state);
requestAnimationFrame(animate);
}
/* 交互控制函数 */
function togglePlay() {
isPlaying = !isPlaying;
if (isPlaying) {
startTime = performance.now();
pausedTime = currentProgress * TOTAL_DURATION;
iconPlay.style.display = 'none';
iconPause.style.display = 'inline';
} else {
pausedTime = currentProgress * TOTAL_DURATION;
iconPlay.style.display = 'inline';
iconPause.style.display = 'none';
}
}
function resetAnimation() {
currentProgress = 0;
pausedTime = 0;
startTime = performance.now();
const state = computeKinematics(0);
renderFrame(state);
}
function onSeek(val) {
currentProgress = val / 1000;
pausedTime = currentProgress * TOTAL_DURATION;
startTime = performance.now();
const state = computeKinematics(currentProgress);
renderFrame(state);
}
// DOM 加载完成自动启动
document.addEventListener('DOMContentLoaded', () => {
initWheelTransforms();
requestAnimationFrame(animate);
});
</script>
</body>
</html>
<!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: #080c14;
--panel-bg: rgba(13, 20, 36, 0.75);
--border-color: rgba(0, 229, 255, 0.2);
--border-glow: rgba(0, 229, 255, 0.4);
--cyan-core: #00f0ff;
--orange-accent: #ff6b00;
--orange-glow: rgba(255, 107, 0, 0.4);
--green-ready: #00ff88;
--text-main: #e2e8f0;
--text-muted: #64748b;
--font-mono: "JetBrains Mono", "SF Mono", "Fira Code", monospace;
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
user-select: none;
}
body {
background-color: var(--bg-color);
color: var(--text-main);
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
overflow: hidden;
width: 100vw;
height: 100vh;
display: flex;
flex-direction: column;
justify-content: space-between;
position: relative;
}
/* 背景精细 CAD 测绘网格 */
.bg-grid {
position: absolute;
inset: 0;
background-image:
radial-gradient(circle at 50% 50%, rgba(0, 240, 255, 0.05) 0%, transparent 80%),
linear-gradient(rgba(0, 240, 255, 0.03) 1px, transparent 1px),
linear-gradient(90deg, rgba(0, 240, 255, 0.03) 1px, transparent 1px),
linear-gradient(rgba(255, 255, 255, 0.015) 1px, transparent 1px),
linear-gradient(90deg, rgba(255, 255, 255, 0.015) 1px, transparent 1px);
background-size: 100% 100%, 100px 100px, 100px 100px, 20px 20px, 20px 20px;
pointer-events: none;
z-index: 0;
}
/* 顶部紧凑状态条 */
.header-bar {
position: absolute;
top: 16px;
left: 20px;
right: 20px;
display: flex;
justify-content: space-between;
align-items: center;
z-index: 20;
pointer-events: none;
}
.brand-tag {
pointer-events: auto;
display: flex;
align-items: center;
gap: 10px;
background: var(--panel-bg);
border: 1px solid var(--border-color);
padding: 6px 14px;
border-radius: 6px;
backdrop-filter: blur(10px);
}
.status-dot {
width: 7px;
height: 7px;
border-radius: 50%;
background: var(--green-ready);
box-shadow: 0 0 10px var(--green-ready);
animation: pulse-dot 2s infinite ease-in-out;
}
.title-text {
font-size: 13px;
font-weight: 600;
letter-spacing: 0.5px;
color: #fff;
}
.title-sub {
font-size: 11px;
color: var(--cyan-core);
font-family: var(--font-mono);
margin-left: 4px;
}
/* 右上角图层切换器 */
.hud-switches {
pointer-events: auto;
display: flex;
gap: 8px;
}
.hud-btn {
background: var(--panel-bg);
border: 1px solid var(--border-color);
color: var(--text-muted);
font-size: 11px;
padding: 5px 10px;
border-radius: 4px;
cursor: pointer;
display: flex;
align-items: center;
gap: 6px;
transition: all 0.2s;
}
.hud-btn:hover {
border-color: var(--cyan-core);
color: var(--text-main);
}
.hud-btn.active {
border-color: var(--cyan-core);
color: var(--cyan-core);
background: rgba(0, 240, 255, 0.08);
box-shadow: 0 0 10px rgba(0, 240, 255, 0.15);
}
.hud-btn .key-dot {
width: 5px;
height: 5px;
border-radius: 50%;
background: currentColor;
}
/* 中央核心动画舞台 */
.viewport-container {
position: absolute;
inset: 0;
display: flex;
justify-content: center;
align-items: center;
z-index: 10;
}
#cad-stage {
width: 100%;
height: 100%;
max-width: 1400px;
max-height: 860px;
}
/* 底部紧凑控制与原理步骤提示条 */
.footer-bar {
position: absolute;
bottom: 16px;
left: 20px;
right: 20px;
z-index: 20;
display: flex;
flex-direction: column;
gap: 8px;
pointer-events: none;
}
.phase-indicator {
display: flex;
justify-content: space-between;
gap: 8px;
pointer-events: auto;
}
.phase-chip {
flex: 1;
background: var(--panel-bg);
border: 1px solid rgba(255, 255, 255, 0.08);
padding: 6px 10px;
border-radius: 5px;
display: flex;
align-items: center;
gap: 6px;
backdrop-filter: blur(12px);
transition: all 0.3s cubic-bezier(0.16, 1, 0.3, 1);
}
.phase-chip.active {
border-color: var(--orange-accent);
background: rgba(255, 107, 0, 0.12);
box-shadow: 0 0 15px rgba(255, 107, 0, 0.2);
}
.phase-idx {
font-family: var(--font-mono);
font-size: 11px;
font-weight: bold;
color: var(--text-muted);
min-width: 16px;
}
.phase-chip.active .phase-idx {
color: var(--orange-accent);
}
.phase-name {
font-size: 11.5px;
color: var(--text-muted);
white-space: nowrap;
}
.phase-chip.active .phase-name {
color: #fff;
font-weight: 500;
}
/* 控制面板 */
.controls-panel {
pointer-events: auto;
display: flex;
align-items: center;
gap: 14px;
background: var(--panel-bg);
border: 1px solid var(--border-color);
padding: 7px 16px;
border-radius: 6px;
backdrop-filter: blur(12px);
}
.btn-action {
background: transparent;
border: 1px solid var(--border-color);
color: var(--cyan-core);
width: 30px;
height: 30px;
border-radius: 4px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.2s;
}
.btn-action:hover {
background: var(--cyan-core);
color: #000;
box-shadow: 0 0 10px var(--cyan-core);
}
.timeline-slider {
flex: 1;
height: 4px;
-webkit-appearance: none;
background: rgba(255, 255, 255, 0.15);
border-radius: 2px;
outline: none;
cursor: pointer;
}
.timeline-slider::-webkit-slider-thumb {
-webkit-appearance: none;
width: 12px;
height: 12px;
border-radius: 50%;
background: var(--cyan-core);
box-shadow: 0 0 8px var(--cyan-core);
cursor: ew-resize;
transition: transform 0.1s;
}
.timeline-slider::-webkit-slider-thumb:hover {
transform: scale(1.3);
}
.telemetry-tag {
font-family: var(--font-mono);
font-size: 11px;
color: var(--cyan-core);
min-width: 140px;
text-align: right;
}
@keyframes pulse-dot {
0%, 100% { opacity: 0.4; transform: scale(0.9); }
50% { opacity: 1; transform: scale(1.15); }
}
/* 微型悬浮参数卡 */
.param-overlay {
position: absolute;
top: 60px;
right: 20px;
z-index: 15;
background: var(--panel-bg);
border: 1px solid rgba(255, 255, 255, 0.1);
padding: 10px 14px;
border-radius: 6px;
font-size: 11px;
line-height: 1.6;
pointer-events: none;
backdrop-filter: blur(8px);
}
.param-overlay .item {
display: flex;
justify-content: space-between;
gap: 16px;
color: #94a3b8;
}
.param-overlay .val {
font-family: var(--font-mono);
color: var(--cyan-core);
font-weight: 500;
}
</style>
</head>
<body>
<div class="bg-grid"></div>
<!-- 顶部状态栏 -->
<div class="header-bar">
<div class="brand-tag">
<div class="status-dot"></div>
<span class="title-text">行星三轮组阶跃越障机构</span>
<span class="title-sub">[触阶精准触发·连续双轮顺时针跨级]</span>
</div>
<div class="hud-switches">
<button class="hud-btn active" id="btn-toggle-forces" onclick="toggleLayer('forces')">
<span class="key-dot"></span> 力矩/矢量 (<span id="t-forces">开</span>)
</button>
<button class="hud-btn active" id="btn-toggle-orbits" onclick="toggleLayer('orbits')">
<span class="key-dot"></span> 回转包络线 (<span id="t-orbits">开</span>)
</button>
<button class="hud-btn active" id="btn-toggle-leveler" onclick="toggleLayer('leveler')">
<span class="key-dot"></span> 货箱水平阻尼 (<span id="t-leveler">开</span>)
</button>
</div>
</div>
<!-- 关键参数指示 -->
<div class="param-overlay">
<div class="item"><span>单轮直径 (2r):</span><span class="val">Ø 120 mm</span></div>
<div class="item"><span>回转半径 (R):</span><span class="val">165 mm</span></div>
<div class="item"><span>平地姿态:</span><span class="val">双轮着地支承</span></div>
<div class="item"><span>触发逻辑:</span><span class="val">轮缘精准触立面起翻</span></div>
<div class="item"><span>单级台阶高:</span><span class="val">68 mm (至中心轴)</span></div>
</div>
<!-- 中央主舞台 SVG -->
<div class="viewport-container">
<svg id="cad-stage" viewBox="0 0 1200 680" preserveAspectRatio="xMidYMid meet">
<defs>
<!-- 发光滤镜 -->
<filter id="glow-cyan" 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-orange" x="-30%" y="-30%" width="160%" height="160%">
<feGaussianBlur stdDeviation="4" result="blur" />
<feMerge>
<feMergeNode in="blur" />
<feMergeNode in="SourceGraphic" />
</feMerge>
</filter>
<!-- 箭头标记 -->
<marker id="arrow-cyan" viewBox="0 0 10 10" refX="6" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse">
<path d="M 0 1 L 9 5 L 0 9 z" fill="#00f0ff" />
</marker>
<marker id="arrow-orange" viewBox="0 0 10 10" refX="6" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse">
<path d="M 0 1 L 9 5 L 0 9 z" fill="#ff6b00" />
</marker>
<marker id="arrow-green" viewBox="0 0 10 10" refX="6" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse">
<path d="M 0 1 L 9 5 L 0 9 z" fill="#00ff88" />
</marker>
<!-- 材质填充 -->
<pattern id="hatch-stairs" width="12" height="12" patternTransform="rotate(45 0 0)" patternUnits="userSpaceOnUse">
<line x1="0" y1="0" x2="0" y2="12" stroke="rgba(0, 240, 255, 0.08)" stroke-width="1.5" />
</pattern>
<pattern id="cargo-texture" width="10" height="10" patternUnits="userSpaceOnUse">
<rect width="10" height="10" fill="#132035" />
<path d="M 0 10 L 10 0 M 0 0 L 10 10" stroke="rgba(0, 240, 255, 0.12)" stroke-width="0.75"/>
</pattern>
</defs>
<!-- 1. 楼梯与地形环境 (两级台阶,每级高 68mm) -->
<g id="terrain-group">
<!-- 楼梯剖面基座: 地面 Y=540, 一阶立面 X=350, 踏面 Y=472; 二阶立面 X=700, 踏面 Y=404 -->
<path id="stair-base" d="M -50 540 L 350 540 L 350 472 L 700 472 L 700 404 L 1250 404 L 1250 640 L -50 640 Z"
fill="url(#hatch-stairs)" stroke="rgba(0, 240, 255, 0.4)" stroke-width="2" />
<!-- 楼梯踏面加固线 -->
<path d="M -50 540 L 350 540 L 350 472 L 700 472 L 700 404 L 1250 404"
fill="none" stroke="#00f0ff" stroke-width="3" filter="url(#glow-cyan)" />
<!-- 踏面防滑纹理与倒角高亮 -->
<g stroke="rgba(0, 240, 255, 0.3)" stroke-width="1">
<line x1="350" y1="540" x2="350" y2="472" stroke="#ff6b00" stroke-width="2.5" />
<line x1="700" y1="472" x2="700" y2="404" stroke="#ff6b00" stroke-width="2.5" />
<circle cx="350" cy="472" r="3" fill="#ff6b00" />
<circle cx="700" cy="404" r="3" fill="#ff6b00" />
</g>
<!-- 尺寸标注 (高度与宽度) -->
<g id="layer-dim" stroke="#64748b" stroke-width="1" font-family="JetBrains Mono" font-size="11" fill="#64748b">
<!-- 阶高标尺 -->
<line x1="330" y1="540" x2="330" y2="472" marker-start="url(#arrow-cyan)" marker-end="url(#arrow-cyan)" stroke="#00f0ff" stroke-opacity="0.6"/>
<text x="315" y="510" text-anchor="end" fill="#00f0ff">H1=68mm</text>
<line x1="680" y1="472" x2="680" y2="404" marker-start="url(#arrow-cyan)" marker-end="url(#arrow-cyan)" stroke="#00f0ff" stroke-opacity="0.6"/>
<text x="665" y="442" text-anchor="end" fill="#00f0ff">H2=68mm</text>
<!-- 踏宽标尺 -->
<line x1="350" y1="560" x2="700" y2="560" marker-start="url(#arrow-cyan)" marker-end="url(#arrow-cyan)" stroke="#00f0ff" stroke-opacity="0.6"/>
<text x="525" y="580" text-anchor="middle" fill="#00f0ff">Step Width = 350mm</text>
</g>
</g>
<!-- 2. 小车主机构动态装配组 -->
<g id="cart-assembly">
<!-- 回转轨迹层 -->
<g id="layer-orbits" stroke="rgba(0, 240, 255, 0.25)" stroke-dasharray="4,4" fill="none">
<circle id="front-orbit" cx="260" cy="0" r="68" />
<circle id="rear-orbit" cx="0" cy="0" r="68" />
</g>
<!-- 车架主梁 -->
<path id="chassis-beam" d="M 0 0 L 260 0" stroke="#334155" stroke-width="14" stroke-linecap="round" />
<path id="chassis-core" d="M 0 0 L 260 0" stroke="#00f0ff" stroke-width="3" />
<!-- 阻尼器与自调平机构连杆 -->
<g id="layer-leveler">
<!-- 阻尼器套筒 -->
<line id="damper-line" x1="130" y1="0" x2="130" y2="-65" stroke="#ff6b00" stroke-width="4" stroke-dasharray="6,3" />
<circle id="damper-joint-b" cx="130" cy="0" r="5" fill="#ff6b00" />
<circle id="damper-joint-t" cx="130" cy="-65" r="5" fill="#ff6b00" />
<!-- 被动防倾摆臂 -->
<line id="arm-f" x1="40" y1="0" x2="60" y2="-60" stroke="#00f0ff" stroke-width="3" stroke-opacity="0.8" />
<line id="arm-r" x1="220" y1="0" x2="200" y2="-60" stroke="#00f0ff" stroke-width="3" stroke-opacity="0.8" />
</g>
<!-- 货箱平台 (保持水平) -->
<g id="cargo-platform">
<rect id="cargo-box" x="20" y="-120" width="220" height="58" rx="6" fill="url(#cargo-texture)" stroke="#00f0ff" stroke-width="2" />
<!-- 货物重心与水平仪 -->
<line x1="30" y1="-91" x2="230" y2="-91" stroke="rgba(0,240,255,0.3)" stroke-width="1" stroke-dasharray="3,3" />
<circle cx="130" cy="-91" r="9" fill="rgba(0, 255, 136, 0.2)" stroke="#00ff88" stroke-width="1.5" />
<line x1="121" y1="-91" x2="139" y2="-91" stroke="#00ff88" stroke-width="1.5" />
<line x1="130" y1="-100" x2="130" y2="-82" stroke="#00ff88" stroke-width="1.5" />
<text x="145" y="-88" font-family="JetBrains Mono" font-size="10" fill="#00ff88">CARGO LEVEL (0°)</text>
</g>
<!-- 左边行星三轮组(后轮组,初始构型:上1轮、下2轮双轮着地) -->
<g id="rear-planetary-set" transform="translate(0, 0)">
<!-- 转臂 (Y型等角 120度) -->
<g id="rear-spider" stroke="#475569" stroke-width="12" stroke-linejoin="round" stroke-linecap="round">
<line x1="0" y1="0" x2="0" y2="-68" />
<line x1="0" y1="0" x2="58.89" y2="34" />
<line x1="0" y1="0" x2="-58.89" y2="34" />
</g>
<g id="rear-spider-glow" stroke="#00f0ff" stroke-width="2" stroke-linejoin="round" stroke-linecap="round" fill="none">
<line x1="0" y1="0" x2="0" y2="-68" />
<line x1="0" y1="0" x2="58.89" y2="34" />
<line x1="0" y1="0" x2="-58.89" y2="34" />
</g>
<!-- 3个行星橡胶轮 (R=25,下两轮接触地面支撑) -->
<g id="rear-wheels">
<g id="rw-0" transform="translate(0, -68)">
<circle r="25" fill="#1e293b" stroke="#00f0ff" stroke-width="3" />
<circle r="8" fill="#080c14" stroke="#64748b" stroke-width="2" />
<line x1="-20" y1="0" x2="20" y2="0" stroke="#00f0ff" stroke-width="1.5" />
<line x1="0" y1="-20" x2="0" y2="20" stroke="#00f0ff" stroke-width="1.5" />
</g>
<g id="rw-1" transform="translate(58.89, 34)">
<circle r="25" fill="#1e293b" stroke="#00f0ff" stroke-width="3" />
<circle r="8" fill="#080c14" stroke="#64748b" stroke-width="2" />
<line x1="-20" y1="0" x2="20" y2="0" stroke="#00f0ff" stroke-width="1.5" />
<line x1="0" y1="-20" x2="0" y2="20" stroke="#00f0ff" stroke-width="1.5" />
</g>
<g id="rw-2" transform="translate(-58.89, 34)">
<circle r="25" fill="#1e293b" stroke="#00f0ff" stroke-width="3" />
<circle r="8" fill="#080c14" stroke="#64748b" stroke-width="2" />
<line x1="-20" y1="0" x2="20" y2="0" stroke="#00f0ff" stroke-width="1.5" />
<line x1="0" y1="-20" x2="0" y2="20" stroke="#00f0ff" stroke-width="1.5" />
</g>
</g>
<!-- 中心单向轴承主轴 -->
<circle cx="0" cy="0" r="14" fill="#0f172a" stroke="#ff6b00" stroke-width="3" />
<circle cx="0" cy="0" r="5" fill="#ff6b00" />
</g>
<!-- 右边行星三轮组(前轮组,初始构型:上1轮、下2轮双轮着地) -->
<g id="front-planetary-set" transform="translate(260, 0)">
<!-- 转臂 (Y型等角 120度) -->
<g id="front-spider" stroke="#475569" stroke-width="12" stroke-linejoin="round" stroke-linecap="round">
<line x1="0" y1="0" x2="0" y2="-68" />
<line x1="0" y1="0" x2="58.89" y2="34" />
<line x1="0" y1="0" x2="-58.89" y2="34" />
</g>
<g id="front-spider-glow" stroke="#ff6b00" stroke-width="2.5" stroke-linejoin="round" stroke-linecap="round" fill="none" filter="url(#glow-orange)">
<line x1="0" y1="0" x2="0" y2="-68" />
<line x1="0" y1="0" x2="58.89" y2="34" />
<line x1="0" y1="0" x2="-58.89" y2="34" />
</g>
<!-- 3个行星橡胶轮 -->
<g id="front-wheels">
<g id="fw-0" transform="translate(0, -68)">
<circle r="25" fill="#1e293b" stroke="#00f0ff" stroke-width="3" />
<circle r="8" fill="#080c14" stroke="#64748b" stroke-width="2" />
<line x1="-20" y1="0" x2="20" y2="0" stroke="#00f0ff" stroke-width="1.5" />
<line x1="0" y1="-20" x2="0" y2="20" stroke="#00f0ff" stroke-width="1.5" />
</g>
<g id="fw-1" transform="translate(58.89, 34)">
<circle r="25" fill="#1e293b" stroke="#00f0ff" stroke-width="3" />
<circle r="8" fill="#080c14" stroke="#64748b" stroke-width="2" />
<line x1="-20" y1="0" x2="20" y2="0" stroke="#00f0ff" stroke-width="1.5" />
<line x1="0" y1="-20" x2="0" y2="20" stroke="#00f0ff" stroke-width="1.5" />
</g>
<g id="fw-2" transform="translate(-58.89, 34)">
<circle r="25" fill="#1e293b" stroke="#00f0ff" stroke-width="3" />
<circle r="8" fill="#080c14" stroke="#64748b" stroke-width="2" />
<line x1="-20" y1="0" x2="20" y2="0" stroke="#00f0ff" stroke-width="1.5" />
<line x1="0" y1="-20" x2="0" y2="20" stroke="#00f0ff" stroke-width="1.5" />
</g>
</g>
<!-- 中心单向离合轴承 -->
<circle cx="0" cy="0" r="14" fill="#0f172a" stroke="#ff6b00" stroke-width="3" />
<circle cx="0" cy="0" r="5" fill="#ff6b00" />
</g>
<!-- 3. 动态受力与力矩矢量指示层 -->
<g id="layer-forces">
<!-- 触阶受阻法向力 (台阶阻力) -->
<g id="force-barrier" opacity="0">
<line id="force-line" x1="343" y1="34" x2="295" y2="34" stroke="#ff0055" stroke-width="4" marker-end="url(#arrow-orange)" />
<text id="force-txt" x="348" y="30" font-family="JetBrains Mono" font-size="11" font-weight="bold" fill="#ff0055">F_阻挡</text>
</g>
<!-- 转臂顺时针翻转力矩圆弧 -->
<g id="torque-arc" opacity="0">
<path d="M 260 -35 A 35 35 0 0 1 295 0" fill="none" stroke="#ff6b00" stroke-width="3" marker-end="url(#arrow-orange)" filter="url(#glow-orange)" />
<text x="305" y="-12" font-family="JetBrains Mono" font-size="11" font-weight="bold" fill="#ff6b00">顺时针力矩 M > 6N·m</text>
</g>
<!-- 爬阶抬升力矢量 -->
<g id="force-lift" opacity="0">
<line x1="260" y1="0" x2="260" y2="-45" stroke="#00ff88" stroke-width="3.5" marker-end="url(#arrow-green)" filter="url(#glow-cyan)" />
<text x="270" y="-25" font-family="JetBrains Mono" font-size="11" font-weight="bold" fill="#00ff88">F_抬升 (跨级上行)</text>
</g>
</g>
</g>
</svg>
</div>
<!-- 底部交互控制与时序微步骤 -->
<div class="footer-bar">
<div class="phase-indicator">
<div class="phase-chip" id="chip-0">
<span class="phase-idx">01</span>
<span class="phase-name">平地双轮滚动</span>
</div>
<div class="phase-chip" id="chip-1">
<span class="phase-idx">02</span>
<span class="phase-name">前轮触阶1·翻转120°</span>
</div>
<div class="phase-chip" id="chip-2">
<span class="phase-idx">03</span>
<span class="phase-name">左轮触阶1·翻转120°</span>
</div>
<div class="phase-chip" id="chip-3">
<span class="phase-idx">04</span>
<span class="phase-name">前轮触阶2·翻转120°</span>
</div>
<div class="phase-chip" id="chip-4">
<span class="phase-idx">05</span>
<span class="phase-name">左轮触阶2·翻转120°</span>
</div>
<div class="phase-chip" id="chip-5">
<span class="phase-idx">06</span>
<span class="phase-name">高台双轮恢复平驶</span>
</div>
</div>
<div class="controls-panel">
<button class="btn-action" id="btn-play-pause" onclick="togglePlay()" title="播放/暂停">
<svg id="icon-pause" width="14" height="14" viewBox="0 0 24 24" fill="currentColor"><rect x="6" y="4" width="4" height="16"/><rect x="14" y="4" width="4" height="16"/></svg>
<svg id="icon-play" width="14" height="14" viewBox="0 0 24 24" fill="currentColor" style="display:none;"><polygon points="5 3 19 12 5 21 5 3"/></svg>
</button>
<button class="btn-action" onclick="resetAnimation()" title="重置">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><path d="M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8"/><path d="M3 3v5h5"/></svg>
</button>
<input type="range" id="timeline" class="timeline-slider" min="0" max="1000" value="0" oninput="onSeek(this.value)" />
<div class="telemetry-tag" id="telemetry-status">
STAGE: 01 | 0.00s
</div>
</div>
</div>
<script>
/* 全局图层显隐开关 */
const layers = {
forces: true,
orbits: true,
leveler: true
};
function toggleLayer(layerName) {
layers[layerName] = !layers[layerName];
const btn = document.getElementById('btn-toggle-' + layerName);
const txt = document.getElementById('t-' + layerName);
if (layers[layerName]) {
btn.classList.add('active');
txt.innerText = '开';
} else {
btn.classList.remove('active');
txt.innerText = '关';
}
const elem = document.getElementById('layer-' + layerName);
if (elem) {
elem.style.display = layers[layerName] ? 'inline' : 'none';
}
}
/* 运动学时序与状态机模型 (精准碰撞几何:车身移动距离与触阶点物理严格对应) */
const TOTAL_DURATION = 12000;
let isPlaying = true;
let startTime = null;
let pausedTime = 0;
let currentProgress = 0;
// DOM 元素引用
const cartAssembly = document.getElementById('cart-assembly');
const frontSpider = document.getElementById('front-planetary-set');
const rearSpider = document.getElementById('rear-planetary-set');
const cargoPlatform = document.getElementById('cargo-platform');
const timeline = document.getElementById('timeline');
const telemetry = document.getElementById('telemetry-status');
const iconPlay = document.getElementById('icon-play');
const iconPause = document.getElementById('icon-pause');
// 矢量受力 DOM
const forceBarrier = document.getElementById('force-barrier');
const torqueArc = document.getElementById('torque-arc');
const forceLift = document.getElementById('force-lift');
// 行星轮子单独自转
const fwWheels = [document.getElementById('fw-0'), document.getElementById('fw-1'), document.getElementById('fw-2')];
const rwWheels = [document.getElementById('rw-0'), document.getElementById('rw-1'), document.getElementById('rw-2')];
// 缓动函数
function easeInOutQuad(t) {
return t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t;
}
function clamp(val, min, max) {
return Math.min(Math.max(val, min), max);
}
/*
精确触碰几何学:
- 台阶1立面在 X=350,台阶2立面在 X=700
- 右边前轮右下前缘在车架后轴 +260 + 58.89 + 25 = 后轴 + 343.89 处
- 左边后轮右下前缘在车架后轴 + 0 + 58.89 + 25 = 后轴 + 83.89 处
- 小车后轴 X 从 -80 匀速运动到 760 (全程位移 840)
- 前轮右缘碰台阶1(X=350) -> 后轴 X = 6.11 -> 对应进度 p ≈ 0.102
- 左轮右缘碰台阶1(X=350) -> 后轴 X = 266.11 -> 对应进度 p ≈ 0.412 (碰阶前左轮一直双轮着地平驶,碰到立即翻转)
- 前轮右缘碰台阶2(X=700) -> 后轴 X = 356.11 -> 对应进度 p ≈ 0.519
- 左轮右缘碰台阶2(X=700) -> 后轴 X = 616.11 -> 对应进度 p ≈ 0.828
*/
function computeKinematics(p) {
let carX = -80 + p * 840;
let rearX = carX;
let frontX = carX + 260;
let frontAngle = 0;
let rearAngle = 0;
let wheelSpin = carX * 2.0;
let frontY = 481;
let rearY = 481;
// 1. 右边(前)行星轮翻第1阶 (p: 0.10 ~ 0.22)
if (p < 0.10) {
frontAngle = 0;
frontY = 481;
} else if (p >= 0.10 && p <= 0.22) {
let pNorm = (p - 0.10) / 0.12;
let sm = easeInOutQuad(pNorm);
frontAngle = sm * 120;
frontY = 481 - sm * 68;
} else if (p > 0.22 && p < 0.52) {
frontAngle = 120;
frontY = 413;
} else if (p >= 0.52 && p <= 0.64) {
// 3. 右边(前)行星轮翻第2阶 (p: 0.52 ~ 0.64)
let pNorm = (p - 0.52) / 0.12;
let sm = easeInOutQuad(pNorm);
frontAngle = 120 + sm * 120;
frontY = 413 - sm * 68;
} else {
frontAngle = 240;
frontY = 345;
}
// 2. 左边(后)行星轮:直到 p=0.41 触碰到第1级台阶立面时才开始翻转!
if (p < 0.41) {
rearAngle = 0;
rearY = 481;
} else if (p >= 0.41 && p <= 0.53) {
// 触碰第1级台阶立面,顺时针翻转 120°
let pNorm = (p - 0.41) / 0.12;
let sm = easeInOutQuad(pNorm);
rearAngle = sm * 120;
rearY = 481 - sm * 68;
} else if (p > 0.53 && p < 0.82) {
rearAngle = 120;
rearY = 413;
} else if (p >= 0.82 && p <= 0.94) {
// 4. 左边(后)行星轮触碰第2级台阶立面(p=0.82),开始顺时针翻转第2阶!
let pNorm = (p - 0.82) / 0.12;
let sm = easeInOutQuad(pNorm);
rearAngle = 120 + sm * 120;
rearY = 413 - sm * 68;
} else {
rearAngle = 240;
rearY = 345;
}
// 车身主梁倾角与自调平货箱角度
const chassisAngleRad = Math.atan2(frontY - rearY, 260);
const chassisAngleDeg = (chassisAngleRad * 180) / Math.PI;
// 矢量指示器状态 (在触碰台阶发生翻转时高亮)
let barrierOpacity = 0;
let torqueOpacity = 0;
let liftOpacity = 0;
const isFlipping = (p >= 0.09 && p <= 0.21) || (p >= 0.40 && p <= 0.52) ||
(p >= 0.51 && p <= 0.63) || (p >= 0.81 && p <= 0.93);
if (isFlipping) {
barrierOpacity = 0.9;
torqueOpacity = 1.0;
liftOpacity = 0.85;
}
// 确定阶段指示高亮
let phaseIndex = 0;
if (p < 0.10) phaseIndex = 0;
else if (p < 0.41) phaseIndex = 1;
else if (p < 0.52) phaseIndex = 2;
else if (p < 0.82) phaseIndex = 3;
else if (p < 0.94) phaseIndex = 4;
else phaseIndex = 5;
return {
carX,
frontX,
rearX,
frontY,
rearY,
frontAngle,
rearAngle,
wheelSpin,
chassisAngleDeg,
barrierOpacity,
torqueOpacity,
liftOpacity,
phaseIndex
};
}
/* 渲染一帧 */
function renderFrame(state) {
// 1. 底盘定位与俯仰
cartAssembly.setAttribute('transform', `translate(${state.rearX}, ${state.rearY}) rotate(${state.chassisAngleDeg})`);
// 2. 前后星形转臂顺时针旋转 (抵消底盘倾角以保持世界绝对角度正确)
frontSpider.setAttribute('transform', `translate(260, 0) rotate(${state.frontAngle - state.chassisAngleDeg})`);
rearSpider.setAttribute('transform', `translate(0, 0) rotate(${state.rearAngle - state.chassisAngleDeg})`);
// 3. 轮子自转
const wRot = `rotate(${state.wheelSpin})`;
fwWheels.forEach(w => w.setAttribute('transform', `${w.getAttribute('data-base') || ''} ${wRot}`));
rwWheels.forEach(w => w.setAttribute('transform', `${w.getAttribute('data-base') || ''} ${wRot}`));
// 4. 自调平货箱:反向旋转抵消车身倾角,保持水平
cargoPlatform.setAttribute('transform', `rotate(${-state.chassisAngleDeg}, 130, 0)`);
// 5. 受力与力矩矢量透明度
if (layers.forces) {
forceBarrier.setAttribute('opacity', state.barrierOpacity);
torqueArc.setAttribute('opacity', state.torqueOpacity);
forceLift.setAttribute('opacity', state.liftOpacity);
}
// 6. 阶段高亮更新
for (let i = 0; i <= 5; i++) {
const chip = document.getElementById(`chip-${i}`);
if (i === state.phaseIndex) {
chip.classList.add('active');
} else {
chip.classList.remove('active');
}
}
// 7. 更新底部滑块与遥测数据
timeline.value = currentProgress * 1000;
const sec = (currentProgress * (TOTAL_DURATION / 1000)).toFixed(2);
telemetry.innerText = `PHASE: 0${state.phaseIndex + 1} | T: ${sec}s | α: ${state.chassisAngleDeg.toFixed(1)}°`;
}
// 初始化轮子相对基座位置缓存 (上1下2双轮着地构型)
function initWheelTransforms() {
fwWheels[0].setAttribute('data-base', 'translate(0, -68)');
fwWheels[1].setAttribute('data-base', 'translate(58.89, 34)');
fwWheels[2].setAttribute('data-base', 'translate(-58.89, 34)');
rwWheels[0].setAttribute('data-base', 'translate(0, -68)');
rwWheels[1].setAttribute('data-base', 'translate(58.89, 34)');
rwWheels[2].setAttribute('data-base', 'translate(-58.89, 34)');
}
/* 动画循环驱动 */
function animate(timestamp) {
if (!startTime) startTime = timestamp;
if (isPlaying) {
const elapsed = (timestamp - startTime + pausedTime) % TOTAL_DURATION;
currentProgress = elapsed / TOTAL_DURATION;
}
const state = computeKinematics(currentProgress);
renderFrame(state);
requestAnimationFrame(animate);
}
/* 交互控制函数 */
function togglePlay() {
isPlaying = !isPlaying;
if (isPlaying) {
startTime = performance.now();
pausedTime = currentProgress * TOTAL_DURATION;
iconPlay.style.display = 'none';
iconPause.style.display = 'inline';
} else {
pausedTime = currentProgress * TOTAL_DURATION;
iconPlay.style.display = 'inline';
iconPause.style.display = 'none';
}
}
function resetAnimation() {
currentProgress = 0;
pausedTime = 0;
startTime = performance.now();
const state = computeKinematics(0);
renderFrame(state);
}
function onSeek(val) {
currentProgress = val / 1000;
pausedTime = currentProgress * TOTAL_DURATION;
startTime = performance.now();
const state = computeKinematics(currentProgress);
renderFrame(state);
}
// DOM 加载完成自动启动
document.addEventListener('DOMContentLoaded', () => {
initWheelTransforms();
requestAnimationFrame(animate);
});
</script>
</body>
</html>
积分规则:每轮对话扣10分
等待动画代码生成...
