独立渲染引擎就绪引擎就绪
<!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: #050914;
--grid-color: rgba(30, 50, 100, 0.15);
--text-main: #e2e8f0;
--text-muted: #64748b;
--accent-cyan: #00f0ff;
--accent-amber: #ff9d00;
--stair-color: #1e293b;
--stair-edge: #38bdf8;
--belt-color: #111827;
--chassis-color: #334155;
--panel-bg: rgba(5, 9, 20, 0.7);
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body, html {
width: 100vw;
height: 100vh;
background-color: var(--bg-color);
background-image:
linear-gradient(var(--grid-color) 1px, transparent 1px),
linear-gradient(90deg, var(--grid-color) 1px, transparent 1px);
background-size: 30px 30px;
font-family: 'SF Mono', Consolas, 'Liberation Mono', Menlo, monospace;
overflow: hidden;
display: flex;
justify-content: center;
align-items: center;
color: var(--text-main);
}
/* 核心动画容器,确保居中且尺寸适中 */
.animation-container {
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
position: relative;
}
svg {
width: 100%;
height: 100%;
max-width: 1200px;
max-height: 800px;
filter: drop-shadow(0 0 20px rgba(0, 240, 255, 0.05));
}
/* 绝对定位的说明面板,严格控制大小和位置,避免遮挡核心区域 */
.info-panel {
position: absolute;
background: var(--panel-bg);
backdrop-filter: blur(8px);
border: 1px solid rgba(0, 240, 255, 0.2);
padding: 12px 16px;
border-radius: 4px;
font-size: 12px;
line-height: 1.5;
pointer-events: none; /* 防止干扰 */
z-index: 10;
}
.panel-top-left { top: 24px; left: 24px; max-width: 300px; }
.panel-bottom-left { bottom: 24px; left: 24px; max-width: 250px; }
.panel-bottom-right { bottom: 24px; right: 24px; width: 220px; }
.panel-top-right { top: 24px; right: 24px; max-width: 200px; text-align: right; }
.title {
color: var(--accent-cyan);
font-size: 14px;
font-weight: bold;
margin-bottom: 8px;
letter-spacing: 1px;
display: flex;
align-items: center;
gap: 6px;
}
.title::before {
content: '';
display: block;
width: 8px;
height: 8px;
background: var(--accent-cyan);
box-shadow: 0 0 8px var(--accent-cyan);
}
h3 {
color: var(--accent-amber);
font-size: 12px;
margin: 8px 0 4px 0;
border-bottom: 1px dashed rgba(255, 157, 0, 0.3);
padding-bottom: 2px;
}
ul {
list-style: none;
color: var(--text-muted);
}
li {
margin-bottom: 4px;
display: flex;
align-items: flex-start;
gap: 6px;
}
li::before {
content: '>';
color: var(--accent-cyan);
opacity: 0.7;
}
.highlight { color: var(--text-main); }
.cyan-text { color: var(--accent-cyan); }
/* 遥测数据动态样式 */
.telemetry-row {
display: flex;
justify-content: space-between;
margin-bottom: 4px;
font-variant-numeric: tabular-nums;
}
.value { color: var(--accent-amber); }
/* SVG 内部样式 */
.stair-path {
fill: var(--stair-color);
stroke: var(--stair-edge);
stroke-width: 2;
stroke-linejoin: round;
}
.stair-damage {
fill: #151d2a;
stroke: #ef4444;
stroke-width: 1.5;
stroke-dasharray: 4 2;
}
.belt-core {
fill: none;
stroke: var(--belt-color);
stroke-width: 24;
stroke-linejoin: round;
stroke-linecap: round;
}
.belt-teeth {
fill: none;
stroke: var(--accent-cyan);
stroke-width: 28;
stroke-dasharray: 4 8;
stroke-linejoin: round;
stroke-linecap: round;
opacity: 0.8;
/* JS 将驱动 dashoffset 以产生运动感 */
}
.chassis-segment {
fill: var(--chassis-color);
stroke: rgba(255,255,255,0.1);
stroke-width: 1;
}
.hinge-joint {
fill: var(--bg-color);
stroke: var(--accent-amber);
stroke-width: 2;
}
.spring-arc {
fill: none;
stroke: var(--accent-amber);
stroke-width: 2;
opacity: 0.3;
transition: opacity 0.2s, stroke 0.2s;
}
.spring-active {
stroke: var(--accent-cyan);
opacity: 1;
filter: drop-shadow(0 0 4px var(--accent-cyan));
}
.main-beam {
fill: none;
stroke: rgba(255, 255, 255, 0.15);
stroke-width: 4;
stroke-dasharray: 10 5;
}
.force-vector {
stroke: var(--accent-amber);
stroke-width: 1.5;
marker-end: url(#arrowhead);
opacity: 0;
transition: opacity 0.1s;
}
</style>
</head>
<body>
<div class="animation-container">
<!-- 信息面板 (绝对定位,小字号,避免遮挡) -->
<div class="info-panel panel-top-left">
<div class="title">地形自适应履带系统</div>
<h3>TRIZ: 最终理想解 (IFR)</h3>
<ul>
<li><span class="highlight">理想状态:</span> 系统自身主动消除台阶形状带来的干涉,履带始终保持线接触推进。</li>
<li><span class="highlight">资源利用:</span> 利用<span class="cyan-text">重力与弹簧被动顺应</span>,无需增加复杂的主动执行机构,即解决“连续爬升”与“悬空打滑”的物理矛盾。</li>
</ul>
</div>
<div class="info-panel panel-bottom-left">
<h3>系统规格参数</h3>
<ul>
<li>模块结构: 4段铝合金铰接</li>
<li>单段长度: 150mm (总600mm)</li>
<li>铰链限位: 上折10° / 下折30°</li>
<li>履带带宽: 160mm (齿高4mm)</li>
<li>工况带速: 0.3m/s</li>
</ul>
</div>
<div class="info-panel panel-bottom-right">
<h3>实时形态遥测</h3>
<div class="telemetry-row"><span>带面速度:</span> <span class="value" id="t-speed">0.30 m/s</span></div>
<div class="telemetry-row"><span>J1 铰接角:</span> <span class="value" id="t-a1">0.0°</span></div>
<div class="telemetry-row"><span>J2 铰接角:</span> <span class="value" id="t-a2">0.0°</span></div>
<div class="telemetry-row"><span>J3 铰接角:</span> <span class="value" id="t-a3">0.0°</span></div>
<div class="telemetry-row"><span>地形贴合率:</span> <span class="value cyan-text" id="t-contact">98%</span></div>
</div>
<div class="info-panel panel-top-right">
<div class="title" style="justify-content: flex-end; color: var(--text-muted);">状态: 自动运行中</div>
<div style="color: var(--text-muted); font-size: 10px; margin-top: 4px;">连续爬升工况模拟</div>
</div>
<!-- SVG 核心动画区域 -->
<svg viewBox="0 0 1200 800" id="main-svg">
<defs>
<marker id="arrowhead" markerWidth="6" markerHeight="4" refX="5" refY="2" orient="auto">
<polygon points="0 0, 6 2, 0 4" fill="var(--accent-amber)" />
</marker>
<filter id="glow">
<feGaussianBlur stdDeviation="3" result="coloredBlur"/>
<feMerge>
<feMergeNode in="coloredBlur"/>
<feMergeNode in="SourceGraphic"/>
</feMerge>
</filter>
<pattern id="grid" width="40" height="40" patternUnits="userSpaceOnUse">
<path d="M 40 0 L 0 0 0 40" fill="none" stroke="rgba(255,255,255,0.03)" stroke-width="1"/>
</pattern>
</defs>
<!-- 背景网格 -->
<rect width="100%" height="100%" fill="url(#grid)" />
<!-- 环境: 破损不规则楼梯 -->
<g id="environment">
<!-- 静态楼梯几何体将由 JS 生成以匹配数学模型 -->
<path id="stair-surface" class="stair-path" d="" />
<!-- 标示破损缺角区域 -->
<path id="stair-broken-area" class="stair-damage" d="" />
</g>
<!-- 主体: 履带系统 -->
<g id="vehicle">
<!-- 主刚性梁 (虚线示意) -->
<line id="main-beam-line" class="main-beam" x1="0" y1="0" x2="0" y2="0" />
<!-- 宽幅同步带 (外层齿纹) -->
<path id="track-belt-teeth" class="belt-teeth" d="" />
<!-- 宽幅同步带 (内层基体) -->
<path id="track-belt-core" class="belt-core" d="" />
<!-- 4段铰接底盘模块 -->
<g id="segments-group">
<!-- JS 动态注入 -->
</g>
<!-- 接触点法向力指示 -->
<g id="force-vectors">
<!-- JS 动态注入 -->
</g>
</g>
</svg>
</div>
<script>
/**
* 物理模型与动画引擎
*/
document.addEventListener("DOMContentLoaded", () => {
// 系统参数
const SEGMENT_COUNT = 4;
const SEGMENT_LENGTH = 75; // 缩放后的像素长度 (对应真实150mm)
const MAX_DOWN_ANGLE = 30 * (Math.PI / 180); // 铰链下折极限
const MAX_UP_ANGLE = 10 * (Math.PI / 180); // 铰链上折极限
const TRACK_THICKNESS = 14;
const SPEED = 1.2; // 动画推进速度
// 楼梯地形数据定义 (x, y) - 包含不规则和破损段
const stairProfile = [
[-200, 700],
[100, 700], // 平地结束
[100, 580], [250, 580], // 台阶1
[250, 460], [380, 460], // 台阶2
[380, 360], [420, 375], // 台阶3 - 破损缺角
[420, 340], [530, 340], // 台阶3 剩余部分
[530, 220], [680, 220], // 台阶4
[680, 100], [830, 100], // 台阶5
[830, -20], [1200, -20] // 顶部平台
];
// 破损区域可视化坐标
const brokenArea = "M 380 360 L 420 375 L 420 340 Z";
// 获取地形Y坐标 (简单的线段插值)
function getTerrainY(x) {
if (x <= stairProfile[0][0]) return stairProfile[0][1];
if (x >= stairProfile[stairProfile.length-1][0]) return stairProfile[stairProfile.length-1][1];
for (let i = 0; i < stairProfile.length - 1; i++) {
let p1 = stairProfile[i];
let p2 = stairProfile[i+1];
if (x >= p1[0] && x <= p2[0]) {
// 垂直面处理:如果在同一个X上有两个点,取较高的(较小的Y)
if (p1[0] === p2[0]) {
return Math.min(p1[1], p2[1]);
}
let t = (x - p1[0]) / (p2[0] - p1[0]);
return p1[1] + t * (p2[1] - p1[1]);
}
}
return 0;
}
// 初始化 DOM 元素
const stairPathEl = document.getElementById('stair-surface');
const stairBrokenEl = document.getElementById('stair-broken-area');
const segmentsGroup = document.getElementById('segments-group');
const forceGroup = document.getElementById('force-vectors');
const beltCoreEl = document.getElementById('track-belt-core');
const beltTeethEl = document.getElementById('track-belt-teeth');
const mainBeamEl = document.getElementById('main-beam-line');
// 绘制地形
let stairPathData = `M ${stairProfile[0][0]} 800 L ${stairProfile[0][0]} ${stairProfile[0][1]} `;
for(let i=1; i<stairProfile.length; i++) {
stairPathData += `L ${stairProfile[i][0]} ${stairProfile[i][1]} `;
}
stairPathData += `L ${stairProfile[stairProfile.length-1][0]} 800 Z`;
stairPathEl.setAttribute('d', stairPathData);
stairBrokenEl.setAttribute('d', brokenArea);
// 创建模块DOM
const segments = [];
for(let i=0; i<SEGMENT_COUNT; i++) {
const g = document.createElementNS('http://www.w3.org/2000/svg', 'g');
// 车体块
const rect = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
rect.setAttribute('x', '0');
rect.setAttribute('y', '-10');
rect.setAttribute('width', SEGMENT_LENGTH);
rect.setAttribute('height', '20');
rect.setAttribute('rx', '4');
rect.setAttribute('class', 'chassis-segment');
// 铰接点 (除了最后一个)
let hinge = null;
let spring = null;
if (i < SEGMENT_COUNT - 1) {
hinge = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
hinge.setAttribute('cx', SEGMENT_LENGTH);
hinge.setAttribute('cy', '0');
hinge.setAttribute('r', '5');
hinge.setAttribute('class', 'hinge-joint');
// 限位扭簧可视化
spring = document.createElementNS('http://www.w3.org/2000/svg', 'path');
spring.setAttribute('d', `M ${SEGMENT_LENGTH-8} -8 A 12 12 0 0 1 ${SEGMENT_LENGTH+8} -8`);
spring.setAttribute('class', 'spring-arc');
}
g.appendChild(rect);
if(hinge) {
g.appendChild(spring);
g.appendChild(hinge);
}
segmentsGroup.appendChild(g);
segments.push({ el: g, springEl: spring, angle: 0 });
}
// 状态变量
let trackPositionX = -150;
let beltDashOffset = 0;
// 遥测UI元素
const telA1 = document.getElementById('t-a1');
const telA2 = document.getElementById('t-a2');
const telA3 = document.getElementById('t-a3');
/**
* 核心运动学计算:模拟被动贴合
* 从后向前计算每个铰接点的位置。受重力影响,铰接点倾向于贴地;
* 受扭簧限位影响,相邻模块夹角不能超过物理限制。
*/
function updateKinematics() {
// 节点坐标 [x, y]
const nodes = [];
// 尾部基准点 (沿X轴匀速推进)
nodes[0] = [trackPositionX, getTerrainY(trackPositionX) - TRACK_THICKNESS];
// 迭代计算其余节点
for(let i=0; i<SEGMENT_COUNT; i++) {
const prevNode = nodes[i];
// 假设下一个节点沿当前阶梯斜率延伸,然后进行限位修正
let nextX = prevNode[0] + SEGMENT_LENGTH;
let targetY = getTerrainY(nextX) - TRACK_THICKNESS;
// 计算自然下垂/上抬角度
let angle = Math.atan2(targetY - prevNode[1], nextX - prevNode[0]);
// 施加铰链限位约束 (相对于前一段的绝对角度差)
if (i > 0) {
const prevAngle = segments[i-1].angle;
let relativeAngle = angle - prevAngle;
// 修正角度超出范围 (下折为正,上折为负)
if (relativeAngle > MAX_DOWN_ANGLE) {
angle = prevAngle + MAX_DOWN_ANGLE;
} else if (relativeAngle < -MAX_UP_ANGLE) {
angle = prevAngle - MAX_UP_ANGLE;
}
// 重新计算受约束后的XY
nextX = prevNode[0] + SEGMENT_LENGTH * Math.cos(angle);
let constrainedY = prevNode[1] + SEGMENT_LENGTH * Math.sin(angle);
// 防穿模检测:如果约束后的Y仍然低于地形,强行抬高 (主梁牵引效果)
const groundY = getTerrainY(nextX) - TRACK_THICKNESS;
if (constrainedY > groundY) {
constrainedY = groundY;
angle = Math.atan2(constrainedY - prevNode[1], nextX - prevNode[0]);
}
targetY = constrainedY;
}
nodes[i+1] = [nextX, targetY];
segments[i].angle = angle;
}
// 更新 DOM 呈现
let beltPathStr = "";
let topBeltPathStr = "";
// 生成包覆带的外边缘轮廓
const topNodes = [];
const bottomNodes = [];
for(let i=0; i<SEGMENT_COUNT; i++) {
const p1 = nodes[i];
const p2 = nodes[i+1];
const angle = segments[i].angle;
// 绘制底盘模块
segments[i].el.setAttribute('transform', `translate(${p1[0]}, ${p1[1]}) rotate(${angle * 180 / Math.PI})`);
// 更新扭簧状态UI (如果处于受力极限)
if (i < SEGMENT_COUNT - 1 && segments[i].springEl) {
const relAngle = segments[i+1].angle - angle;
if (relAngle > MAX_DOWN_ANGLE * 0.9) {
segments[i].springEl.classList.add('spring-active');
} else {
segments[i].springEl.classList.remove('spring-active');
}
}
// 计算法线向量用于绘制履带外壳
const nx = -Math.sin(angle) * (TRACK_THICKNESS + 10);
const ny = Math.cos(angle) * (TRACK_THICKNESS + 10);
topNodes.push([p1[0] - nx, p1[1] - ny]);
bottomNodes.push([p1[0] + nx, p1[1] + ny]);
if(i === SEGMENT_COUNT - 1) {
topNodes.push([p2[0] - nx, p2[1] - ny]);
bottomNodes.push([p2[0] + nx, p2[1] + ny]);
}
}
// 构建环形履带路径
beltPathStr += `M ${bottomNodes[0][0]} ${bottomNodes[0][1]} `;
for(let i=1; i<bottomNodes.length; i++) beltPathStr += `L ${bottomNodes[i][0]} ${bottomNodes[i][1]} `;
// 头部半圆转折
beltPathStr += `A 18 18 0 0 0 ${topNodes[topNodes.length-1][0]} ${topNodes[topNodes.length-1][1]} `;
for(let i=topNodes.length-2; i>=0; i--) beltPathStr += `L ${topNodes[i][0]} ${topNodes[i][1]} `;
// 尾部半圆转折
beltPathStr += `A 18 18 0 0 0 ${bottomNodes[0][0]} ${bottomNodes[0][1]} `;
beltCoreEl.setAttribute('d', beltPathStr);
beltTeethEl.setAttribute('d', beltPathStr);
// 履带滚动动画
beltDashOffset -= SPEED * 2;
beltTeethEl.style.strokeDashoffset = beltDashOffset;
// 主梁位置 (连接头尾的抽象直线)
mainBeamEl.setAttribute('x1', nodes[0][0]);
mainBeamEl.setAttribute('y1', nodes[0][1]-20);
mainBeamEl.setAttribute('x2', nodes[nodes.length-1][0]);
mainBeamEl.setAttribute('y2', nodes[nodes.length-1][1]-20);
// 更新遥测数据
const relA1 = (segments[1].angle - segments[0].angle) * 180 / Math.PI;
const relA2 = (segments[2].angle - segments[1].angle) * 180 / Math.PI;
const relA3 = (segments[3].angle - segments[2].angle) * 180 / Math.PI;
telA1.textContent = relA1.toFixed(1) + '°';
telA2.textContent = relA2.toFixed(1) + '°';
telA3.textContent = relA3.toFixed(1) + '°';
// 高亮受力点颜色的逻辑
telA1.style.color = relA1 > 28 ? 'var(--accent-cyan)' : 'var(--accent-amber)';
telA2.style.color = relA2 > 28 ? 'var(--accent-cyan)' : 'var(--accent-amber)';
telA3.style.color = relA3 > 28 ? 'var(--accent-cyan)' : 'var(--accent-amber)';
}
// 动画主循环
function animate() {
trackPositionX += SPEED;
// 重置循环
if (trackPositionX > 900) {
trackPositionX = -200;
}
updateKinematics();
requestAnimationFrame(animate);
}
// 启动动画 (自动播放)
animate();
});
</script>
</body>
</html>
积分规则:每轮对话扣10分
等待动画代码生成...
