独立渲染引擎就绪引擎就绪
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>IFR: 柔性自适应履带系统原理验证</title>
<style>
:root {
--bg-color: #050914;
--grid-color: rgba(0, 229, 255, 0.05);
--accent-cyan: #00E5FF;
--accent-orange: #FF5D00;
--accent-green: #00FF66;
--panel-bg: rgba(5, 9, 20, 0.7);
--text-main: #E0F7FA;
--text-dim: #8492A6;
--font-tech: 'Consolas', 'Courier New', monospace;
}
body, html {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
background-color: var(--bg-color);
color: var(--text-main);
font-family: var(--font-tech);
overflow: hidden;
display: flex;
justify-content: center;
align-items: center;
}
/* 核心动画容器 */
#canvas-container {
position: relative;
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
}
svg {
width: 100%;
height: 100%;
max-width: 1400px;
/* 确保图形始终居中并按比例缩放 */
object-fit: contain;
}
/* 左上角信息浮层:遵守极简、小字号、不遮挡主体的原则 */
.hud-panel {
position: absolute;
top: 20px;
left: 20px;
width: 320px;
background: var(--panel-bg);
border: 1px solid rgba(0, 229, 255, 0.2);
border-left: 4px solid var(--accent-cyan);
padding: 16px;
backdrop-filter: blur(10px);
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5);
z-index: 10;
}
.hud-title {
font-size: 14px;
font-weight: bold;
color: var(--accent-cyan);
margin-bottom: 8px;
letter-spacing: 1px;
text-transform: uppercase;
}
.hud-desc {
font-size: 12px;
color: var(--text-dim);
line-height: 1.5;
margin-bottom: 12px;
}
.hud-highlight {
color: var(--accent-orange);
font-weight: bold;
}
/* 交互控制区 */
.controls {
margin-top: 12px;
padding-top: 12px;
border-top: 1px dashed rgba(255, 255, 255, 0.1);
}
.control-row {
display: flex;
justify-content: space-between;
align-items: center;
font-size: 12px;
margin-bottom: 8px;
}
input[type=range] {
-webkit-appearance: none;
width: 150px;
background: transparent;
}
input[type=range]::-webkit-slider-thumb {
-webkit-appearance: none;
height: 12px;
width: 12px;
border-radius: 50%;
background: var(--accent-cyan);
cursor: pointer;
margin-top: -5px;
box-shadow: 0 0 10px var(--accent-cyan);
}
input[type=range]::-webkit-slider-runnable-track {
width: 100%;
height: 2px;
cursor: pointer;
background: rgba(0, 229, 255, 0.3);
}
/* 遥测数据浮层 */
.telemetry-panel {
position: absolute;
bottom: 20px;
right: 20px;
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 8px;
z-index: 10;
}
.telemetry-box {
background: rgba(0, 0, 0, 0.6);
border: 1px solid rgba(0, 255, 102, 0.2);
padding: 8px 12px;
font-size: 10px;
color: var(--accent-green);
display: flex;
flex-direction: column;
align-items: flex-end;
}
.tel-label {
color: var(--text-dim);
font-size: 9px;
margin-bottom: 2px;
}
.tel-value {
font-size: 14px;
font-weight: bold;
}
/* SVG 内的发光与动画样式 */
.neon-glow {
filter: drop-shadow(0 0 8px var(--accent-orange));
}
.cyan-glow {
filter: drop-shadow(0 0 5px var(--accent-cyan));
}
/* 扫描线覆盖 */
.scanlines {
position: absolute;
top: 0; left: 0; width: 100%; height: 100%;
background: linear-gradient(
to bottom,
rgba(255,255,255,0),
rgba(255,255,255,0) 50%,
rgba(0,0,0,0.1) 50%,
rgba(0,0,0,0.1)
);
background-size: 100% 4px;
pointer-events: none;
z-index: 20;
opacity: 0.3;
}
</style>
</head>
<body>
<div id="canvas-container">
<div class="scanlines"></div>
<!-- 左上角说明 -->
<div class="hud-panel">
<div class="hud-title">系统状态: 理想最终解 (IFR)</div>
<div class="hud-desc">
传统刚性履带在不规则台阶上极易悬空打滑。本方案运用 <span class="hud-highlight">资源利用</span> 策略,消除复杂调平机构。
<br><br>
<strong>运作原理:</strong> 将刚性底盘转化为多点独立柔性悬挂。履带 <span class="hud-highlight">直接利用台阶自身的物理轮廓</span> 作为受力模板,实现形态的自适应完美贴合。
</div>
<div class="controls">
<div class="control-row">
<span>前进速度 (VS)</span>
<input type="range" id="speed-slider" min="0" max="15" value="5" step="0.5">
<span id="speed-val">5.0</span>
</div>
<div class="control-row">
<span>台阶崎岖度 (RF)</span>
<input type="range" id="roughness-slider" min="20" max="150" value="80">
<span id="roughness-val">80</span>
</div>
</div>
</div>
<!-- 右下角悬挂遥测数据 -->
<div class="telemetry-panel" id="telemetry">
<!-- 动态生成 -->
</div>
<!-- 核心SVG可视化 -->
<svg viewBox="0 0 1200 600" preserveAspectRatio="xMidYMid meet">
<defs>
<pattern id="gridPattern" width="40" height="40" patternUnits="userSpaceOnUse">
<rect width="40" height="40" fill="none" stroke="rgba(0, 229, 255, 0.05)" stroke-width="1"/>
<path d="M 40 0 L 0 0 L 0 40" fill="none" stroke="rgba(0, 229, 255, 0.1)" stroke-width="1"/>
</pattern>
<linearGradient id="terrainGrad" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="rgba(0, 255, 102, 0.2)" />
<stop offset="100%" stop-color="rgba(0, 255, 102, 0.0)" />
</linearGradient>
<!-- 驱动轮内部结构 -->
<g id="drive-wheel-model">
<circle cx="0" cy="0" r="70" fill="rgba(10, 15, 29, 0.9)" stroke="#00E5FF" stroke-width="3" />
<circle cx="0" cy="0" r="55" fill="none" stroke="rgba(0, 229, 255, 0.3)" stroke-width="1" stroke-dasharray="5, 5" />
<circle cx="0" cy="0" r="20" fill="#00E5FF" />
<!-- 轮齿/辐条 -->
<path d="M 0 -20 L 0 -70 M 0 20 L 0 70 M -20 0 L -70 0 M 20 0 L 70 0 M -14 -14 L -49 -49 M 14 14 L 49 49 M -14 14 L -49 49 M 14 -14 L 49 -49" stroke="#00E5FF" stroke-width="4"/>
</g>
<!-- 负重轮内部结构 -->
<g id="road-wheel-model">
<circle cx="0" cy="0" r="35" fill="rgba(10, 15, 29, 0.9)" stroke="#00E5FF" stroke-width="2" />
<circle cx="0" cy="0" r="10" fill="#FF5D00" />
<path d="M 0 -10 L 0 -35 M 0 10 L 0 35 M -10 0 L -35 0 M 10 0 L 35 0" stroke="rgba(0, 229, 255, 0.6)" stroke-width="2"/>
</g>
</defs>
<!-- 背景网格 -->
<rect width="100%" height="100%" fill="url(#gridPattern)" />
<!-- 基准线 -->
<line x1="0" y1="200" x2="1200" y2="200" stroke="rgba(255,255,255,0.1)" stroke-width="1" stroke-dasharray="10,10"/>
<text x="20" y="195" fill="rgba(255,255,255,0.3)" font-size="10">CHASSIS_BASE_LINE Y:200</text>
<!-- 动态台阶地形 -->
<path id="terrain-path" fill="url(#terrainGrad)" stroke="#00FF66" stroke-width="3" d="" />
<!-- 车辆系统主体 (固定在中央,地形相对运动) -->
<g id="vehicle">
<!-- 刚性车架/底盘轮廓 -->
<path d="M 150 150 L 1050 150 L 1100 200 L 1100 250 L 100 250 L 100 200 Z" fill="rgba(0, 229, 255, 0.03)" stroke="rgba(0, 229, 255, 0.4)" stroke-width="2"/>
<text x="120" y="170" fill="#00E5FF" font-size="12" font-weight="bold">MAIN CHASSIS FRAME // RIGID</text>
<!-- 悬挂阻尼弹簧组 (由JS动态更新) -->
<g id="suspensions-group"></g>
<!-- 负重轮组 (由JS动态更新) -->
<g id="road-wheels-group"></g>
<!-- 主驱动轮 (前后固定) -->
<g id="drive-wheel-rear" transform="translate(200, 220)">
<use href="#drive-wheel-model" />
</g>
<g id="drive-wheel-front" transform="translate(1000, 220)">
<use href="#drive-wheel-model" />
</g>
<!-- 自适应柔性履带边界 -->
<path id="flexible-track" fill="none" stroke="#FF5D00" stroke-width="12" stroke-linejoin="round" stroke-linecap="round" stroke-dasharray="30 15" class="neon-glow" d="" />
<!-- 履带内衬结构线 -->
<path id="flexible-track-inner" fill="none" stroke="#FFF" stroke-width="2" stroke-linejoin="round" d="" opacity="0.8"/>
</g>
</svg>
</div>
<script>
document.addEventListener('DOMContentLoaded', () => {
// 系统状态变量
let offset = 0;
let speed = 5.0;
let maxRoughness = 80; // 地形高度突变的波动范围
let isRunning = true;
let animationFrameId;
// DOM 节点引用
const terrainPath = document.getElementById('terrain-path');
const trackPath = document.getElementById('flexible-track');
const trackInnerPath = document.getElementById('flexible-track-inner');
const dwRear = document.getElementById('drive-wheel-rear');
const dwFront = document.getElementById('drive-wheel-front');
const suspensionsGroup = document.getElementById('suspensions-group');
const roadWheelsGroup = document.getElementById('road-wheels-group');
const telemetryContainer = document.getElementById('telemetry');
// 控件引用
const speedSlider = document.getElementById('speed-slider');
const speedVal = document.getElementById('speed-val');
const roughnessSlider = document.getElementById('roughness-slider');
const roughnessVal = document.getElementById('roughness-val');
speedSlider.addEventListener('input', (e) => {
speed = parseFloat(e.target.value);
speedVal.textContent = speed.toFixed(1);
});
roughnessSlider.addEventListener('input', (e) => {
maxRoughness = parseInt(e.target.value);
roughnessVal.textContent = maxRoughness;
generateTerrain(); // 重新生成地形
});
// 车辆机械几何参数
const CHASSIS_Y = 200; // 悬挂挂载点基准高度
const DW_Y = 230; // 驱动轮中心高度
const DW_R = 70; // 驱动轮半径
const DW_REAR_X = 200;
const DW_FRONT_X = 1000;
const RW_COUNT = 7; // 负重轮数量
const RW_R = 35; // 负重轮半径
const RW_START_X = 320;
const RW_END_X = 880;
const RW_SPACING = (RW_END_X - RW_START_X) / (RW_COUNT - 1);
const MAX_COMPRESSION_Y = 230; // 负重轮最高可被挤压到的中心点Y (Y越小越高)
const MAX_EXTENSION_Y = 380; // 负重轮最低可下垂到的中心点Y
// 悬挂与轮子数据结构
const roadWheels = [];
for(let i=0; i<RW_COUNT; i++) {
roadWheels.push({
id: i,
cx: RW_START_X + i * RW_SPACING,
cy: MAX_EXTENSION_Y,
rotation: 0
});
// 初始化遥测UI
if(i < 6) { // 只展示前6个遥测数据以保持布局美观
const telBox = document.createElement('div');
telBox.className = 'telemetry-box';
telBox.innerHTML = `
<div class="tel-label">SUSPENSION ${i+1}</div>
<div class="tel-value" id="tel-val-${i}">0%</div>
`;
telemetryContainer.appendChild(telBox);
}
}
// 初始化 SVG 元素
roadWheels.forEach(rw => {
// 轮子
const rwEl = document.createElementNS("http://www.w3.org/2000/svg", "g");
rwEl.setAttribute("id", `rw-${rw.id}`);
const useEl = document.createElementNS("http://www.w3.org/2000/svg", "use");
useEl.setAttribute("href", "#road-wheel-model");
rwEl.appendChild(useEl);
roadWheelsGroup.appendChild(rwEl);
// 阻尼弹簧 (连杆 + 折线)
const springEl = document.createElementNS("http://www.w3.org/2000/svg", "path");
springEl.setAttribute("id", `spring-${rw.id}`);
springEl.setAttribute("fill", "none");
springEl.setAttribute("stroke", "rgba(0, 229, 255, 0.8)");
springEl.setAttribute("stroke-width", "3");
suspensionsGroup.appendChild(springEl);
// 中心液压杆
const pistonEl = document.createElementNS("http://www.w3.org/2000/svg", "line");
pistonEl.setAttribute("id", `piston-${rw.id}`);
pistonEl.setAttribute("stroke", "rgba(255, 255, 255, 0.4)");
pistonEl.setAttribute("stroke-width", "8");
suspensionsGroup.appendChild(pistonEl);
});
// 地形数据生成 (不规则台阶)
let terrainSegments = [];
let totalTerrainWidth = 0;
function generateTerrain() {
terrainSegments = [];
let curX = 0;
let curY = 350; // 基准高度
// 生成一段很长的循环台阶地形
for(let i=0; i<100; i++) {
// 随机宽度 (阶梯深度)
let width = 60 + Math.random() * 100;
// 随机高度变化 (阶梯高度)
let deltaY = (Math.random() - 0.5) * maxRoughness * 2;
curY += deltaY;
// 限制高度范围,防止跑出屏幕
if(curY < 280) curY = 280;
if(curY > 500) curY = 500;
terrainSegments.push({
startX: curX,
endX: curX + width,
y: curY
});
curX += width;
}
totalTerrainWidth = curX;
}
// 获取特定绝对X坐标的地形Y高度
function getTerrainY(absX) {
// 处理循环逻辑
let loopedX = absX % totalTerrainWidth;
if(loopedX < 0) loopedX += totalTerrainWidth;
for(let i=0; i<terrainSegments.length; i++) {
let seg = terrainSegments[i];
if(loopedX >= seg.startX && loopedX <= seg.endX) {
return seg.y;
}
}
return 350; // default
}
// 计算负重轮由于接触地形而产生的Y坐标
function calculateWheelY(cx, r) {
let highestPoint = 9999; // SVG中Y越小越高
// 在轮子宽度的范围内采样,找到托起轮子的最高台阶
for(let x = cx - r; x <= cx + r; x += 4) {
let ty = getTerrainY(x + offset); // 加上当前的物理偏移量
if(ty < highestPoint) {
highestPoint = ty;
}
}
// 轮子中心位于最高点之上一个半径的距离
let targetY = highestPoint - r;
// 物理限位:刚性底盘的极限压缩和悬挂的极限伸长
if(targetY < MAX_COMPRESSION_Y) targetY = MAX_COMPRESSION_Y;
if(targetY > MAX_EXTENSION_Y) targetY = MAX_EXTENSION_Y;
return targetY;
}
// 渲染动态弹簧路径
function drawSpring(x, topY, bottomY) {
let segments = 8;
let stepY = (bottomY - topY) / segments;
let width = 12;
let path = `M ${x} ${topY} `;
for(let i=1; i<segments; i++) {
let xOffset = (i % 2 === 0) ? -width : width;
path += `L ${x + xOffset} ${topY + i * stepY} `;
}
path += `L ${x} ${bottomY}`;
return path;
}
generateTerrain();
let trackStrokeOffset = 0;
let wheelRotation = 0;
// 核心动画循环
function animate() {
if(!isRunning) return;
offset += speed;
trackStrokeOffset -= speed * 1.5; // 履带流动感
wheelRotation += speed * 1.2;
// 1. 渲染地形剖面
let dTerrain = `M -100 600 `;
let prevY = getTerrainY(-100 + offset);
dTerrain += `L -100 ${prevY} `;
// 屏幕范围内的台阶绘制
for(let x = -100; x <= 1300; x += 10) {
let ty = getTerrainY(x + offset);
if(Math.abs(ty - prevY) > 0.1) {
// 绘制直角台阶
dTerrain += `L ${x} ${prevY} L ${x} ${ty} `;
prevY = ty;
}
}
dTerrain += `L 1300 ${prevY} L 1300 600 Z`;
terrainPath.setAttribute("d", dTerrain);
// 2. 更新悬挂与负重轮
roadWheels.forEach((rw, index) => {
rw.cy = calculateWheelY(rw.cx, RW_R);
// 应用变换
const rwEl = document.getElementById(`rw-${rw.id}`);
rwEl.setAttribute("transform", `translate(${rw.cx}, ${rw.cy}) rotate(${wheelRotation})`);
// 更新弹簧与液压杆
const springEl = document.getElementById(`spring-${rw.id}`);
const pistonEl = document.getElementById(`piston-${rw.id}`);
springEl.setAttribute("d", drawSpring(rw.cx, CHASSIS_Y + 10, rw.cy - 10));
pistonEl.setAttribute("x1", rw.cx);
pistonEl.setAttribute("y1", CHASSIS_Y);
pistonEl.setAttribute("x2", rw.cx);
pistonEl.setAttribute("y2", rw.cy);
// 更新遥测数据 (计算压缩率)
if(index < 6) {
let totalTravel = MAX_EXTENSION_Y - MAX_COMPRESSION_Y;
let currentTravel = MAX_EXTENSION_Y - rw.cy;
let compressionPct = (currentTravel / totalTravel) * 100;
const telValEl = document.getElementById(`tel-val-${index}`);
if(telValEl) {
telValEl.textContent = `${compressionPct.toFixed(0)}%`;
// 根据压力改变颜色
if(compressionPct > 80) telValEl.style.color = '#FF5D00';
else if(compressionPct > 40) telValEl.style.color = '#FFC107';
else telValEl.style.color = '#00FF66';
}
}
});
// 更新驱动轮旋转
dwRear.setAttribute("transform", `translate(${DW_REAR_X}, ${DW_Y}) rotate(${wheelRotation})`);
dwFront.setAttribute("transform", `translate(${DW_FRONT_X}, ${DW_Y}) rotate(${wheelRotation})`);
// 3. 构建柔性履带闭合路径 (IFR核心视觉展现)
// 上半部分直线 (返回侧,不接触地面)
let trackPathStr = `M ${DW_REAR_X} ${DW_Y - DW_R} `;
trackPathStr += `L ${DW_FRONT_X} ${DW_Y - DW_R} `;
// 前驱动轮右下圆弧
trackPathStr += `A ${DW_R} ${DW_R} 0 0 1 ${DW_FRONT_X + DW_R} ${DW_Y} `;
trackPathStr += `A ${DW_R} ${DW_R} 0 0 1 ${DW_FRONT_X} ${DW_Y + DW_R} `;
// 下半部分接触面 (极其重要:连接所有负重轮的底部,形成贴合地形的波浪曲线)
// 倒序连接,形成闭合环路
for(let i = roadWheels.length - 1; i >= 0; i--) {
let rw = roadWheels[i];
// 使用曲线(Q)或短直线(L)连接。这里使用平滑控制点简化。
trackPathStr += `L ${rw.cx} ${rw.cy + RW_R + 5} `;
}
// 连接到后驱动轮底部
trackPathStr += `L ${DW_REAR_X} ${DW_Y + DW_R} `;
// 后驱动轮左上圆弧
trackPathStr += `A ${DW_R} ${DW_R} 0 0 1 ${DW_REAR_X - DW_R} ${DW_Y} `;
trackPathStr += `A ${DW_R} ${DW_R} 0 0 1 ${DW_REAR_X} ${DW_Y - DW_R} Z`;
// 应用履带路径与动画纹理位移
trackPath.setAttribute("d", trackPathStr);
trackPath.setAttribute("stroke-dashoffset", trackStrokeOffset);
trackInnerPath.setAttribute("d", trackPathStr);
animationFrameId = requestAnimationFrame(animate);
}
// 启动
animate();
// 处理窗口大小变化导致重载或重新显示,确保动画运行
window.addEventListener('focus', () => {
if(!animationFrameId) {
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: #05080f;
--grid-color: rgba(0, 229, 255, 0.05);
--cyan: #00E5FF;
--cyan-dim: rgba(0, 229, 255, 0.3);
--amber: #FFB000;
--amber-dim: rgba(255, 176, 0, 0.3);
--dark-blue: #0A1128;
--text-main: #E2E8F0;
--text-dim: #94A3B8;
}
body, html {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
background-color: var(--bg-color);
font-family: 'Segoe UI', system-ui, -apple-system, sans-serif;
overflow: hidden;
display: flex;
justify-content: center;
align-items: center;
color: var(--text-main);
}
#app-container {
position: relative;
width: 100%;
max-width: 1400px;
height: 100%;
max-height: 800px;
display: flex;
justify-content: center;
align-items: center;
box-sizing: border-box;
background-image:
linear-gradient(var(--grid-color) 1px, transparent 1px),
linear-gradient(90deg, var(--grid-color) 1px, transparent 1px);
background-size: 40px 40px;
background-position: center center;
box-shadow: inset 0 0 100px rgba(0,0,0,0.8);
}
/* 绝对定位的 UI 信息层,避开中央动画区 */
.overlay-hud {
position: absolute;
pointer-events: none;
z-index: 10;
}
.hud-top-left {
top: 40px;
left: 40px;
}
.hud-bottom-right {
bottom: 40px;
right: 40px;
text-align: right;
}
.hud-top-right {
top: 40px;
right: 40px;
text-align: right;
}
.hud-bottom-left {
bottom: 40px;
left: 40px;
}
.title {
font-size: 24px;
font-weight: 800;
color: var(--amber);
letter-spacing: 2px;
margin: 0 0 8px 0;
text-transform: uppercase;
text-shadow: 0 0 15px var(--amber-dim);
}
.subtitle {
font-size: 14px;
color: var(--cyan);
letter-spacing: 4px;
margin: 0 0 20px 0;
}
.desc-box {
background: rgba(10, 17, 40, 0.8);
border-left: 3px solid var(--cyan);
padding: 12px 16px;
width: 280px;
backdrop-filter: blur(4px);
}
.desc-text {
font-size: 12px;
line-height: 1.6;
color: var(--text-dim);
margin: 0;
}
.highlight {
color: var(--cyan);
font-weight: 600;
}
.data-row {
font-family: 'Courier New', Courier, monospace;
font-size: 12px;
color: var(--cyan);
margin: 6px 0;
display: flex;
justify-content: space-between;
width: 200px;
}
.data-label { color: var(--text-dim); }
.triz-badge {
display: inline-block;
background: var(--amber-dim);
color: var(--amber);
border: 1px solid var(--amber);
padding: 4px 8px;
font-size: 12px;
font-weight: bold;
border-radius: 2px;
margin-bottom: 12px;
}
/* 核心动画 SVG */
svg {
width: 100%;
height: 100%;
filter: drop-shadow(0 10px 30px rgba(0,0,0,0.5));
}
</style>
</head>
<body>
<div id="app-container">
<!-- UI 信息覆盖层 -->
<div class="overlay-hud hud-top-left">
<div class="triz-badge">TRIZ: 最终理想解 (IFR)</div>
<h1 class="title">自适应柔性履带阵列</h1>
<h2 class="subtitle">ADAPTIVE CONFORMAL TRACK</h2>
<div class="desc-box">
<p class="desc-text">
<span class="highlight">破除矛盾:</span>传统刚性直履带无法贴合不规则台阶。<br><br>
<span class="highlight">理想解:</span>将刚性履带化为多段独立悬挂的柔性单元,<span class="highlight">巧妙利用地形高差和重力</span>作为动态形变源,无需复杂主动控制即可实现完美共形。
</p>
</div>
</div>
<div class="overlay-hud hud-top-right">
<div class="data-row"><span class="data-label">SYS_STATUS</span><span>ACTIVE</span></div>
<div class="data-row"><span class="data-label">TERRAIN_TYPE</span><span>IRREGULAR</span></div>
<div class="data-row"><span class="data-label">CONFORMITY</span><span>99.8%</span></div>
<div style="width: 200px; height: 1px; background: var(--cyan-dim); margin: 10px 0 10px auto;"></div>
<div class="data-row"><span class="data-label">Δ_VARIANCE</span><span id="hud-variance">00.0 mm</span></div>
<div class="data-row"><span class="data-label">PITCH_ANGLE</span><span id="hud-pitch">0.0°</span></div>
</div>
<div class="overlay-hud hud-bottom-left">
<div style="display: flex; align-items: center; gap: 10px; opacity: 0.6;">
<div style="width: 40px; height: 2px; background: var(--cyan);"></div>
<span style="font-size: 10px; letter-spacing: 2px; color: var(--cyan);">SIMULATION V2.4 // REALTIME RENDER</span>
</div>
</div>
<!-- 核心 SVG 动画区 -->
<svg viewBox="0 0 1200 700" preserveAspectRatio="xMidYMid meet">
<defs>
<!-- 渐变与发光滤镜 -->
<linearGradient id="terrainGrad" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#0F172A" stop-opacity="0.9"/>
<stop offset="100%" stop-color="#05080F" stop-opacity="1"/>
</linearGradient>
<filter id="cyanGlow" x="-20%" y="-20%" width="140%" height="140%">
<feGaussianBlur stdDeviation="4" result="blur" />
<feComposite in="SourceGraphic" in2="blur" operator="over" />
</filter>
<filter id="amberGlow" x="-20%" y="-20%" width="140%" height="140%">
<feGaussianBlur stdDeviation="6" result="blur" />
<feComposite in="SourceGraphic" in2="blur" operator="over" />
</filter>
<pattern id="diagHatch" width="10" height="10" patternTransform="rotate(45 0 0)" patternUnits="userSpaceOnUse">
<line x1="0" y1="0" x2="0" y2="10" stroke="#00E5FF" stroke-width="1" stroke-opacity="0.1" />
</pattern>
</defs>
<!-- 地形图层 -->
<g id="terrain-group">
<path id="terrain-fill" fill="url(#terrainGrad)" />
<path id="terrain-stroke" fill="none" stroke="var(--cyan)" stroke-width="2" filter="url(#cyanGlow)" />
<path id="terrain-grid" fill="url(#diagHatch)" opacity="0.5"/>
</g>
<!-- 车辆系统 -->
<g id="vehicle-system">
<!-- 悬挂连杆 (独立于底盘旋转,保持重力垂直下垂感) -->
<g id="suspensions"></g>
<!-- 履带本体 -->
<!-- 履带底色加粗 -->
<path id="track-base" fill="none" stroke="#1E293B" stroke-width="32" stroke-linejoin="round" stroke-linecap="round"/>
<!-- 履带机械纹理及高亮 -->
<path id="track-highlight" fill="none" stroke="var(--amber)" stroke-width="4" stroke-dasharray="14 8" stroke-linejoin="round" stroke-linecap="round" filter="url(#amberGlow)"/>
<!-- 动态负重轮群 -->
<g id="road-wheels"></g>
<!-- 底盘及主动轮 -->
<g id="chassis-group">
<!-- 科技感底盘几何体 -->
<polygon points="-140,-25 140,-25 170,10 150,35 -150,35 -170,10" fill="#0A1128" stroke="var(--cyan)" stroke-width="2" filter="url(#cyanGlow)"/>
<polygon points="-120,-15 120,-15 140,10 120,25 -120,25 -140,10" fill="none" stroke="var(--cyan)" stroke-width="1" opacity="0.3"/>
<!-- 内部核心发光体 -->
<circle cx="0" cy="5" r="10" fill="var(--cyan)" filter="url(#cyanGlow)" opacity="0.6"/>
<line x1="-100" y1="5" x2="100" y2="5" stroke="var(--cyan)" stroke-width="1" opacity="0.4"/>
<!-- 前后驱动轮 (相对底盘固定) -->
<g id="rear-drive-wheel" transform="translate(-160, 5)">
<circle cx="0" cy="0" r="18" fill="#05080F" stroke="var(--amber)" stroke-width="3" filter="url(#amberGlow)"/>
<circle cx="0" cy="0" r="6" fill="var(--cyan)"/>
<path d="M0,-18 L0,18 M-18,0 L18,0" stroke="var(--amber)" stroke-width="2" opacity="0.6"/>
</g>
<g id="front-drive-wheel" transform="translate(160, 5)">
<circle cx="0" cy="0" r="18" fill="#05080F" stroke="var(--amber)" stroke-width="3" filter="url(#amberGlow)"/>
<circle cx="0" cy="0" r="6" fill="var(--cyan)"/>
<path d="M0,-18 L0,18 M-18,0 L18,0" stroke="var(--amber)" stroke-width="2" opacity="0.6"/>
</g>
</g>
</g>
</svg>
</div>
<script>
/**
* TRIZ 最终理想解:柔性自适应履带模拟
* 核心逻辑:刚性拆解,局部自由度释放,利用重力被动共形。
*/
// --- 1. 物理参数定义 ---
const R = 16; // 负重轮半径
const WHEEL_OFFSETS = [-120, -85, -50, -15, 20, 55, 90, 125]; // 悬挂点相对底盘中心的X偏移
const DRIVE_OFFSET_X = 160; // 驱动轮X偏移
const DRIVE_OFFSET_Y = 5; // 驱动轮Y偏移
const SUSPENSION_REST_LEN = 50; // 悬挂基础长度
// --- 2. 不规则台阶地形生成 ---
// 返回给定 X 坐标处的基础地形高度 (Y向下为正)
function getBaseTerrain(x) {
if (x < 150) return 550;
if (x < 350) return 460;
if (x < 420) return 380;
if (x < 600) return 320;
if (x < 750) return 220;
if (x < 950) return 150;
return 150;
}
// 初始化地形多边形
function initTerrain() {
const fillPath = document.getElementById('terrain-fill');
const strokePath = document.getElementById('terrain-stroke');
const gridPath = document.getElementById('terrain-grid');
let d = `M -100,700 `;
let strokeD = '';
for (let x = -100; x <= 1300; x += 5) {
let y = getBaseTerrain(x);
d += `L ${x},${y} `;
if(x === -100) strokeD += `M ${x},${y} `;
else strokeD += `L ${x},${y} `;
}
d += `L 1300,700 Z`;
fillPath.setAttribute('d', d);
strokePath.setAttribute('d', strokeD);
gridPath.setAttribute('d', d);
}
// --- 3. 核心计算:碰撞与共形 ---
// 利用闵可夫斯基和(Minkowski sum)简化轮子在复杂地形上的滚动轨迹
// 对于中心在 cx 的轮子,其最低合法Y坐标由局部地形的最高隆起决定
function getWheelCenterY(cx) {
let minY = Infinity;
// 在轮子投影宽度内扫描
for (let dx = -R; dx <= R; dx += 2) {
let tx = cx + dx;
let ty = getBaseTerrain(tx);
// 轮子几何约束公式:保证轮缘不陷入地形
let maxAllowedCy = ty - Math.sqrt(R * R - dx * dx);
if (maxAllowedCy < minY) {
minY = maxAllowedCy;
}
}
return minY;
}
// --- 4. 动画初始化与 DOM 绑定 ---
const suspensionsGroup = document.getElementById('suspensions');
const roadWheelsGroup = document.getElementById('road-wheels');
const chassisGroup = document.getElementById('chassis-group');
const trackBase = document.getElementById('track-base');
const trackHighlight = document.getElementById('track-highlight');
// UI 数据绑定
const hudVariance = document.getElementById('hud-variance');
const hudPitch = document.getElementById('hud-pitch');
let roadWheels = [];
let suspensions = [];
// 创建 DOM 元素
WHEEL_OFFSETS.forEach((offsetX, i) => {
// 悬挂外筒
const outerCyl = document.createElementNS('http://www.w3.org/2000/svg', 'line');
outerCyl.setAttribute('stroke', 'var(--cyan)');
outerCyl.setAttribute('stroke-width', '8');
outerCyl.setAttribute('opacity', '0.7');
// 悬挂内杆
const innerRod = document.createElementNS('http://www.w3.org/2000/svg', 'line');
innerRod.setAttribute('stroke', '#E2E8F0');
innerRod.setAttribute('stroke-width', '3');
suspensionsGroup.appendChild(outerCyl);
suspensionsGroup.appendChild(innerRod);
suspensions.push({ outerCyl, innerRod });
// 负重轮
const wheelGroup = document.createElementNS('http://www.w3.org/2000/svg', 'g');
const circle = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
circle.setAttribute('r', R);
circle.setAttribute('fill', '#05080F');
circle.setAttribute('stroke', 'var(--cyan)');
circle.setAttribute('stroke-width', '2');
const dot = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
dot.setAttribute('r', '3');
dot.setAttribute('fill', 'var(--amber)');
const cross = document.createElementNS('http://www.w3.org/2000/svg', 'path');
cross.setAttribute('d', `M0,-${R} L0,${R} M-${R},0 L${R},0`);
cross.setAttribute('stroke', 'var(--cyan)');
cross.setAttribute('stroke-width', '1');
cross.setAttribute('opacity', '0.5');
wheelGroup.appendChild(circle);
wheelGroup.appendChild(cross);
wheelGroup.appendChild(dot);
roadWheelsGroup.appendChild(wheelGroup);
roadWheels.push({ element: wheelGroup, offsetX: offsetX });
});
// --- 5. 动画主循环 ---
let progressX = 0;
let smoothedChassisY = 400;
let smoothedPitch = 0;
let trackDashOffset = 0;
function render(timestamp) {
progressX += 1.8; // 行驶速度
trackDashOffset -= 2.5; // 履带滚动视觉速度
// 循环边界重置逻辑
let isResetFrame = false;
if (progressX > 1150) {
progressX = 50;
isResetFrame = true; // 绕回时切断平滑过渡
}
const currentX = progressX;
// 5.1 计算所有负重轮的状态
let wheelYs = [];
let wheelPositions = []; // 保存当前帧轮子绝对坐标用于绘制履带
WHEEL_OFFSETS.forEach((offsetX, i) => {
let wx = currentX + offsetX;
let wy = getWheelCenterY(wx);
wheelYs.push(wy);
wheelPositions.push({x: wx, y: wy});
// 更新负重轮位置与自转
roadWheels[i].element.setAttribute('transform', `translate(${wx}, ${wy}) rotate(${trackDashOffset * 2})`);
});
// 5.2 底盘姿态解算 (基于地形宏观趋势)
// 目标Y高度:保证底盘不与负重轮碰撞,取负重轮均值偏上
const avgWheelY = wheelYs.reduce((a,b)=>a+b, 0) / wheelYs.length;
const targetChassisY = Math.min(...wheelYs) - SUSPENSION_REST_LEN;
// 目标俯仰角:利用首尾轮计算趋势斜率
const firstWy = wheelYs[0];
const lastWy = wheelYs[wheelYs.length - 1];
const targetPitchRad = Math.atan2(lastWy - firstWy, WHEEL_OFFSETS[WHEEL_OFFSETS.length-1] - WHEEL_OFFSETS[0]);
// 平滑滤波 (低通)
if (isResetFrame) {
smoothedChassisY = targetChassisY;
smoothedPitch = targetPitchRad;
} else {
smoothedChassisY += (targetChassisY - smoothedChassisY) * 0.15;
smoothedPitch += (targetPitchRad - smoothedPitch) * 0.1;
}
const pitchDeg = smoothedPitch * (180 / Math.PI);
// 更新底盘变换
chassisGroup.setAttribute('transform', `translate(${currentX}, ${smoothedChassisY}) rotate(${pitchDeg})`);
// 5.3 悬挂连杆渲染 (连接底盘铰接点与负重轮)
// 悬挂筒表现为伸缩:一半外筒,一半内杆
const cosP = Math.cos(smoothedPitch);
const sinP = Math.sin(smoothedPitch);
// 用于计算悬挂形变极差的数组
let suspensionLengths = [];
WHEEL_OFFSETS.forEach((offsetX, i) => {
// 底盘上的铰接点绝对坐标 (考虑底盘旋转)
const hingeYLocal = 25; // 铰接点在底盘局部坐标的Y
const ax = currentX + offsetX * cosP - hingeYLocal * sinP;
const ay = smoothedChassisY + offsetX * sinP + hingeYLocal * cosP;
const wx = wheelPositions[i].x;
const wy = wheelPositions[i].y;
const midX = (ax + wx) / 2;
const midY = (ay + wy) / 2;
suspensions[i].outerCyl.setAttribute('x1', ax);
suspensions[i].outerCyl.setAttribute('y1', ay);
suspensions[i].outerCyl.setAttribute('x2', midX);
suspensions[i].outerCyl.setAttribute('y2', midY);
suspensions[i].innerRod.setAttribute('x1', midX);
suspensions[i].innerRod.setAttribute('y1', midY);
suspensions[i].innerRod.setAttribute('x2', wx);
suspensions[i].innerRod.setAttribute('y2', wy);
// 记录长度用于数据显示
suspensionLengths.push(Math.sqrt((wx-ax)**2 + (wy-ay)**2));
});
// 5.4 动态生成履带闭合路径 (包络主动轮和所有负重轮)
// 计算前后驱动轮的绝对坐标
const rdx = currentX - DRIVE_OFFSET_X * cosP - DRIVE_OFFSET_Y * sinP;
const rdy = smoothedChassisY - DRIVE_OFFSET_X * sinP + DRIVE_OFFSET_Y * cosP;
const fdx = currentX + DRIVE_OFFSET_X * cosP - DRIVE_OFFSET_Y * sinP;
const fdy = smoothedChassisY + DRIVE_OFFSET_X * sinP + DRIVE_OFFSET_Y * cosP;
// 组装路径字符串:后驱动 -> 前驱动 -> 负重轮N -> ... -> 负重轮0 -> 闭合
// 由于设置了巨粗的 stroke 并且连线居中,SVG linecap会自动完美包络外轮廓
let trackD = `M ${rdx},${rdy} L ${fdx},${fdy} `;
for(let i = wheelPositions.length - 1; i >= 0; i--) {
trackD += `L ${wheelPositions[i].x},${wheelPositions[i].y} `;
}
trackD += 'Z';
trackBase.setAttribute('d', trackD);
trackHighlight.setAttribute('d', trackD);
trackHighlight.setAttribute('stroke-dashoffset', trackDashOffset);
// 5.5 更新 HUD 仪表盘数据
if (!isResetFrame && Math.floor(timestamp) % 5 === 0) {
const minLen = Math.min(...suspensionLengths);
const maxLen = Math.max(...suspensionLengths);
const variance = maxLen - minLen;
hudVariance.textContent = variance.toFixed(1).padStart(4, '0') + " mm";
// 如果极差过大,高亮警告色
hudVariance.style.color = variance > 60 ? '#f43f5e' : 'var(--cyan)';
hudPitch.textContent = pitchDeg.toFixed(1) + "°";
}
// 触发下一帧
requestAnimationFrame(render);
}
// 启动
initTerrain();
progressX = 50; // 起始位置
requestAnimationFrame(render);
</script>
</body>
</html>
积分规则:第一轮对话扣减8分,后续每轮扣6分
等待动画代码生成...
