独立渲染引擎就绪引擎就绪
<!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: #060B14;
--grid-color: #121D30;
--stair-fill: #152238;
--stair-stroke: #2E4773;
--chassis-fill: #1A202C;
--chassis-stroke: #4A5568;
--wheel-carrier: #FF4B2B;
--wheel-small: #718096;
--actuator-body: #2D3748;
--actuator-piston: #00E5FF;
--platform: #FFFFFF;
--text-glow: #00E5FF;
--accent-orange: #FF4B2B;
--font-mono: 'JetBrains Mono', 'Fira Code', 'Courier New', monospace;
}
body, html {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
background-color: var(--bg-color);
color: #fff;
font-family: 'Inter', system-ui, sans-serif;
overflow: hidden;
display: flex;
justify-content: center;
align-items: center;
}
#animation-container {
width: 100%;
height: 100%;
max-width: 1920px;
max-height: 1080px;
position: relative;
}
svg {
width: 100%;
height: 100%;
display: block;
}
.hud-overlay {
position: absolute;
top: 40px;
left: 40px;
pointer-events: none;
z-index: 10;
}
.hud-title {
font-size: 24px;
font-weight: 800;
letter-spacing: 2px;
margin-bottom: 10px;
text-transform: uppercase;
background: linear-gradient(90deg, #fff, #888);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.hud-subtitle {
font-family: var(--font-mono);
font-size: 14px;
color: var(--text-glow);
letter-spacing: 1px;
margin-bottom: 20px;
opacity: 0.8;
}
.hud-panel {
background: rgba(10, 15, 25, 0.8);
border: 1px solid rgba(0, 229, 255, 0.3);
border-left: 3px solid var(--text-glow);
padding: 15px 20px;
font-family: var(--font-mono);
backdrop-filter: blur(4px);
margin-bottom: 15px;
width: fit-content;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.5);
}
.hud-row {
display: flex;
justify-content: space-between;
align-items: center;
gap: 30px;
margin: 8px 0;
font-size: 13px;
}
.hud-label {
color: #8892b0;
text-transform: uppercase;
}
.hud-value {
color: #fff;
font-weight: bold;
font-size: 15px;
text-align: right;
min-width: 80px;
}
.val-cyan { color: var(--text-glow); text-shadow: 0 0 8px rgba(0, 229, 255, 0.5); }
.val-orange { color: var(--accent-orange); text-shadow: 0 0 8px rgba(255, 75, 43, 0.5); }
.val-green { color: #00FF66; text-shadow: 0 0 8px rgba(0, 255, 102, 0.5); }
/* SVG Inner Styling */
.grid-line { stroke: var(--grid-color); stroke-width: 1; }
.stair-path { fill: var(--stair-fill); stroke: var(--stair-stroke); stroke-width: 3; stroke-linejoin: round; }
.chassis { fill: var(--chassis-fill); stroke: var(--chassis-stroke); stroke-width: 4; stroke-linejoin: round; }
.carrier-arm { stroke: var(--wheel-carrier); stroke-width: 12; stroke-linecap: round; }
.wheel { fill: var(--wheel-small); stroke: #111; stroke-width: 3; }
.wheel-hub { fill: #111; stroke: var(--wheel-carrier); stroke-width: 2; }
.actuator-tube { stroke: var(--actuator-body); stroke-width: 18; stroke-linecap: round; }
.actuator-rod { stroke: var(--actuator-piston); stroke-width: 8; stroke-linecap: round; }
.platform-base { fill: #222; stroke: #444; stroke-width: 2; }
.platform-top { fill: var(--platform); stroke: var(--platform); stroke-width: 2; }
.imu-sensor { fill: #000; stroke: var(--text-glow); stroke-width: 2; }
.glow-line { stroke: var(--text-glow); stroke-width: 2; stroke-dasharray: 4 4; opacity: 0.5; }
@keyframes pulse {
0% { opacity: 0.6; }
50% { opacity: 1; filter: drop-shadow(0 0 5px var(--text-glow)); }
100% { opacity: 0.6; }
}
.active-indicator { animation: pulse 1s infinite; }
</style>
</head>
<body>
<div id="animation-container">
<div class="hud-overlay">
<div class="hud-title">Ideal Final Result (IFR)</div>
<div class="hud-subtitle">ACTIVE SUSPENSION DECOUPLING SYSTEM</div>
<div class="hud-panel">
<div style="color: #fff; margin-bottom: 10px; border-bottom: 1px solid #333; padding-bottom: 5px; font-weight: bold;">[ IMU & PAYLOAD TELEMETRY ]</div>
<div class="hud-row">
<span class="hud-label">Chassis Pitch:</span>
<span class="hud-value val-orange" id="hud-chassis-angle">0.00°</span>
</div>
<div class="hud-row">
<span class="hud-label">Payload Pitch (IFR):</span>
<span class="hud-value val-green">0.00° ABS</span>
</div>
<div class="hud-row">
<span class="hud-label">IMU Response Time:</span>
<span class="hud-value val-cyan" id="hud-response-time">14 ms</span>
</div>
</div>
<div class="hud-panel">
<div style="color: #fff; margin-bottom: 10px; border-bottom: 1px solid #333; padding-bottom: 5px; font-weight: bold;">[ ACTUATOR KINEMATICS ]</div>
<div class="hud-row">
<span class="hud-label">Front Stroke:</span>
<span class="hud-value val-cyan" id="hud-front-stroke">0.0 mm</span>
</div>
<div class="hud-row">
<span class="hud-label">Rear Stroke:</span>
<span class="hud-value val-cyan" id="hud-rear-stroke">0.0 mm</span>
</div>
<div class="hud-row">
<span class="hud-label">Carrier Arm (r):</span>
<span class="hud-value">180 mm</span>
</div>
</div>
</div>
<svg id="scene" viewBox="0 0 1600 900" preserveAspectRatio="xMidYMid slice">
<defs>
<pattern id="grid" width="100" height="100" patternUnits="userSpaceOnUse">
<path d="M 100 0 L 0 0 0 100" fill="none" class="grid-line"/>
<path d="M 50 0 L 50 100 M 0 50 L 100 50" fill="none" class="grid-line" style="stroke-opacity: 0.3"/>
</pattern>
<filter id="glow-cyan" x="-20%" y="-20%" width="140%" height="140%">
<feGaussianBlur stdDeviation="6" result="blur" />
<feComposite in="SourceGraphic" in2="blur" operator="over" />
</filter>
<filter id="glow-orange" x="-20%" y="-20%" width="140%" height="140%">
<feGaussianBlur stdDeviation="8" result="blur" />
<feComponentTransfer in="blur" result="glow">
<feFuncA type="linear" slope="1.5"/>
</feComponentTransfer>
<feMerge>
<feMergeNode in="glow"/>
<feMergeNode in="SourceGraphic"/>
</feMerge>
</filter>
<!-- 四星轮系定义 -->
<g id="planetary-wheel">
<!-- 行星架十字交叉 -->
<line x1="-50" y1="0" x2="50" y2="0" class="carrier-arm"/>
<line x1="0" y1="-50" x2="0" y2="50" class="carrier-arm"/>
<!-- 四个小轮 -->
<circle cx="-50" cy="0" r="16" class="wheel"/>
<circle cx="50" cy="0" r="16" class="wheel"/>
<circle cx="0" cy="-50" r="16" class="wheel"/>
<circle cx="0" cy="50" r="16" class="wheel"/>
<!-- 中心轴承 -->
<circle cx="0" cy="0" r="10" class="wheel-hub"/>
</g>
</defs>
<!-- 背景网格 -->
<rect width="100%" height="100%" fill="url(#grid)" />
<!-- 场景组 (用于居中和缩放) -->
<g transform="translate(800, 550)">
<!-- 动态地形 (台阶) -->
<g id="terrain-group"></g>
<!-- 机器人主体 -->
<g id="robot-group">
<!-- 辅助对齐线 (展示IFR解耦原理) -->
<line id="horizontal-reference" x1="-400" y1="-220" x2="400" y2="-220" class="glow-line" />
<text x="410" y="-215" fill="var(--text-glow)" font-family="var(--font-mono)" font-size="14" opacity="0.8">ABSOLUTE HORIZONTAL PLANE</text>
<!-- 底盘与车轮组 (受地形影响产生颠簸和倾斜) -->
<g id="chassis-assembly">
<!-- 底盘本体 -->
<rect x="-240" y="-20" width="480" height="40" rx="10" class="chassis"/>
<!-- IMU传感器 -->
<rect x="-20" y="-35" width="40" height="15" rx="3" class="imu-sensor" filter="url(#glow-cyan)"/>
<circle cx="0" cy="-27.5" r="3" fill="#00E5FF"/>
<!-- 前行星轮组 -->
<g id="front-wheel" transform="translate(180, 0)">
<use href="#planetary-wheel" />
<circle cx="0" cy="0" r="4" fill="#fff"/>
</g>
<!-- 后行星轮组 -->
<g id="rear-wheel" transform="translate(-180, 0)">
<use href="#planetary-wheel" />
<circle cx="0" cy="0" r="4" fill="#fff"/>
</g>
</g>
<!-- 主动悬挂推杆 (连接底盘和载货平台) -->
<!-- 后推杆 -->
<g id="actuator-rear-group">
<line id="actuator-rear-body" class="actuator-tube" x1="-150" y1="0" x2="-150" y2="-100"/>
<line id="actuator-rear-rod" class="actuator-rod" filter="url(#glow-cyan)" x1="-150" y1="-100" x2="-150" y2="-200"/>
<circle id="joint-rear-chassis" cx="-150" cy="0" r="6" fill="#8892b0"/>
<circle id="joint-rear-platform" cx="-150" cy="-200" r="6" fill="#8892b0"/>
</g>
<!-- 前推杆 -->
<g id="actuator-front-group">
<line id="actuator-front-body" class="actuator-tube" x1="150" y1="0" x2="150" y2="-100"/>
<line id="actuator-front-rod" class="actuator-rod" filter="url(#glow-cyan)" x1="150" y1="-100" x2="150" y2="-200"/>
<circle id="joint-front-chassis" cx="150" cy="0" r="6" fill="#8892b0"/>
<circle id="joint-front-platform" cx="150" cy="-200" r="6" fill="#8892b0"/>
</g>
<!-- 载货平台 (IFR目标:永远保持绝对水平) -->
<g id="payload-platform" transform="translate(0, -220)">
<rect x="-260" y="-10" width="520" height="20" rx="4" class="platform-base"/>
<rect x="-250" y="-25" width="500" height="15" rx="2" class="platform-top" filter="url(#glow-cyan)"/>
<!-- 货物示意 -->
<rect x="-80" y="-85" width="160" height="60" rx="2" fill="none" stroke="#00E5FF" stroke-width="2" stroke-dasharray="6 4" opacity="0.6"/>
<path d="M-60 -45 L60 -45 M0 -75 L0 -25" stroke="#00E5FF" stroke-width="1" opacity="0.3"/>
<circle cx="0" cy="-55" r="15" fill="none" stroke="#00E5FF" stroke-width="2" opacity="0.5"/>
<text x="0" y="-51" fill="#00E5FF" font-family="var(--font-mono)" font-size="12" text-anchor="middle" opacity="0.8">CG</text>
</g>
<!-- 数据连线展示 -->
<path id="imu-data-line" d="" fill="none" stroke="#00E5FF" stroke-width="2" stroke-dasharray="4 4" opacity="0.6" filter="url(#glow-cyan)"/>
</g>
</g>
</svg>
</div>
<script>
// 物理与几何参数配置
const CONFIG = {
speed: 180, // X轴前进速度 (px/s)
stairWidth: 700, // 台阶宽度
stairHeight: -160, // 台阶高度 (负值向上)
armLength: 90, // 动画中的行星臂长 (代表真实世界的180mm,为了视觉比例调整)
wheelRadius: 16, // 小轮半径
wheelBase: 360, // 前后轮距
chassisMountX: 160, // 推杆在底盘的安装点X距离中心的绝对值
platformMountX: 160, // 推杆在平台的安装点X距离中心的绝对值
defaultSuspensionH: 220, // 默认悬挂高度
platformBaseY: -220 // 平台基础Y坐标(相对于世界参考系)
};
// DOM 元素缓存
const els = {
terrain: document.getElementById('terrain-group'),
chassisGroup: document.getElementById('chassis-assembly'),
frontWheelGroup: document.getElementById('front-wheel'),
rearWheelGroup: document.getElementById('rear-wheel'),
platformGroup: document.getElementById('payload-platform'),
actFrontBody: document.getElementById('actuator-front-body'),
actFrontRod: document.getElementById('actuator-front-rod'),
jFrontChassis: document.getElementById('joint-front-chassis'),
jFrontPlatform: document.getElementById('joint-front-platform'),
actRearBody: document.getElementById('actuator-rear-body'),
actRearRod: document.getElementById('actuator-rear-rod'),
jRearChassis: document.getElementById('joint-rear-chassis'),
jRearPlatform: document.getElementById('joint-rear-platform'),
imuDataLine: document.getElementById('imu-data-line'),
refLine: document.getElementById('horizontal-reference'),
hudChassisAngle: document.getElementById('hud-chassis-angle'),
hudFrontStroke: document.getElementById('hud-front-stroke'),
hudRearStroke: document.getElementById('hud-rear-stroke'),
hudResponseTime: document.getElementById('hud-response-time')
};
// 状态变量
let globalTime = 0;
let worldOffsetX = 0;
let lastTimestamp = 0;
// 生成无限延伸的台阶路径
function generateTerrainSVG(startX, endX, offsetX) {
let pathD = "";
// 渲染视口内的台阶,前后多渲染一个保证无缝
const startIdx = Math.floor((startX - offsetX) / CONFIG.stairWidth) - 1;
const endIdx = Math.ceil((endX - offsetX) / CONFIG.stairWidth) + 1;
for (let i = startIdx; i <= endIdx; i++) {
const x0 = i * CONFIG.stairWidth + offsetX;
const y0 = i * CONFIG.stairHeight;
const x1 = (i + 1) * CONFIG.stairWidth + offsetX;
const y1 = y0;
if (i === startIdx) {
pathD += `M ${x0} ${y0} L ${x1} ${y1} `;
} else {
// 画垂直面和水平面
pathD += `L ${x0} ${y0} L ${x1} ${y1} `;
}
}
// 闭合路径形成实体块
pathD += `L ${(endIdx+1)*CONFIG.stairWidth + offsetX} 1000 L ${startIdx*CONFIG.stairWidth + offsetX} 1000 Z`;
return `<path d="${pathD}" class="stair-path" />`;
}
// 核心缓动函数,模拟翻转物理过程
function easeInOutCubic(x) {
return x < 0.5 ? 4 * x * x * x : 1 - Math.pow(-2 * x + 2, 3) / 2;
}
// 计算单个行星轮组在地形状况下的 Y轴高度 和 旋转角度
function getWheelKinematics(absoluteX) {
const localX = absoluteX % CONFIG.stairWidth;
const stairIndex = Math.floor(absoluteX / CONFIG.stairWidth);
let baseY = stairIndex * CONFIG.stairHeight;
let extraY = 0;
let rotation = 0;
// 行星轮碰到台阶开始翻转的区域 (翻转区宽度略大于臂长,体现跨步动作)
const flipZone = CONFIG.armLength * 1.8;
const triggerX = CONFIG.stairWidth - flipZone;
if (localX > triggerX) {
// 在翻转区内
let progress = (localX - triggerX) / flipZone;
progress = Math.max(0, Math.min(1, progress));
const eased = easeInOutCubic(progress);
// Y轴高度变化 (从0变化到一个台阶高度)
extraY = eased * CONFIG.stairHeight;
// 行星架旋转 (90度翻转)
rotation = eased * 90;
}
// 加上小轮半径导致的默认偏移
return {
y: baseY + extraY - CONFIG.wheelRadius,
angle: rotation,
isFlipping: localX > triggerX && localX < CONFIG.stairWidth
};
}
// 更新逻辑
function update(deltaTime) {
// 世界向左滚动
worldOffsetX -= (CONFIG.speed * deltaTime);
// 保持机器人X坐标固定在屏幕上,相对地形X变化
const robotAbsoluteX = -worldOffsetX;
// 前后轮在地表上的绝对X坐标
const frontAbsX = robotAbsoluteX + CONFIG.wheelBase / 2;
const rearAbsX = robotAbsoluteX - CONFIG.wheelBase / 2;
// 获取轮组姿态
const frontState = getWheelKinematics(frontAbsX);
const rearState = getWheelKinematics(rearAbsX);
// 提取轮组高度差异
const frontY = frontState.y;
const rearY = rearState.y;
// 计算底盘倾角和中心高度
const dy = frontY - rearY;
const dx = CONFIG.wheelBase;
const chassisAngleRad = Math.atan2(dy, dx);
const chassisAngleDeg = chassisAngleRad * (180 / Math.PI);
const chassisCenterY = (frontY + rearY) / 2;
// 视口相机跟踪底盘中心高度平滑移动,保证主体在画面中央
// 这里简化处理,直接让平台固定在视图Y轴的一个绝对位置,底盘绕其下方的动态点运动
const viewOffsetY = -chassisCenterY;
// --- 渲染更新 ---
// 1. 更新地形
els.terrain.innerHTML = generateTerrainSVG(-800, 800, worldOffsetX % (CONFIG.stairWidth * 10));
els.terrain.setAttribute('transform', `translate(0, ${viewOffsetY})`);
// 2. 更新底盘与车轮 (相对机器人中心)
els.chassisGroup.setAttribute('transform', `translate(0, ${chassisCenterY + viewOffsetY}) rotate(${chassisAngleDeg})`);
// 更新行星架内部旋转
// 由于底盘已经倾斜,车轮自身需要加上相对于世界的滚动,并扣除底盘倾角保持视觉正确
const rollingAngleFront = frontState.angle + (frontAbsX * 0.5); // 附加一点滚动效果
const rollingAngleRear = rearState.angle + (rearAbsX * 0.5);
els.frontWheelGroup.setAttribute('transform', `translate(${CONFIG.wheelBase/2}, 0) rotate(${frontState.angle})`);
els.rearWheelGroup.setAttribute('transform', `translate(${-CONFIG.wheelBase/2}, 0) rotate(${rearState.angle})`);
// 在进行翻转时给轮组添加高亮,强调"暴力克服"
if (frontState.isFlipping) els.frontWheelGroup.style.filter = "url(#glow-orange)";
else els.frontWheelGroup.style.filter = "none";
if (rearState.isFlipping) els.rearWheelGroup.style.filter = "url(#glow-orange)";
else els.rearWheelGroup.style.filter = "none";
// 3. 计算主动推杆运动学 (核心IFR解耦展示)
// 平台始终保持绝对水平,处于固定高度 (相对于屏幕)
const platformY = CONFIG.platformBaseY;
els.platformGroup.setAttribute('transform', `translate(0, ${platformY})`);
els.refLine.setAttribute('y1', platformY);
els.refLine.setAttribute('y2', platformY);
// 计算底盘上推杆安装点的世界坐标 (带相机偏移)
const cosA = Math.cos(chassisAngleRad);
const sinA = Math.sin(chassisAngleRad);
// 前安装点 (底盘系) -> 世界系
const fCx = CONFIG.chassisMountX * cosA;
const fCy = chassisCenterY + viewOffsetY + (CONFIG.chassisMountX * sinA);
// 后安装点 (底盘系) -> 世界系
const rCx = -CONFIG.chassisMountX * cosA;
const rCy = chassisCenterY + viewOffsetY + (-CONFIG.chassisMountX * sinA);
// 平台上推杆安装点 (世界系,因平台水平所以X不变)
const fPx = CONFIG.platformMountX;
const fPy = platformY;
const rPx = -CONFIG.platformMountX;
const rPy = platformY;
// 绘制前推杆
updateActuator(els.jFrontChassis, els.jFrontPlatform, els.actFrontBody, els.actFrontRod, fCx, fCy, fPx, fPy);
// 绘制后推杆
updateActuator(els.jRearChassis, els.jRearPlatform, els.actRearBody, els.actRearRod, rCx, rCy, rPx, rPy);
// 绘制IMU控制线 (从底盘中心连向平台)
const imuX = -20 * cosA - (-27.5 * sinA); // 粗略估算IMU位置
const imuY = chassisCenterY + viewOffsetY + (-20 * sinA) + (-27.5 * cosA);
els.imuDataLine.setAttribute('d', `M ${imuX} ${imuY} Q 0 ${platformY + 50} 0 ${platformY}`);
// 4. 更新HUD数据
els.hudChassisAngle.textContent = (Math.abs(chassisAngleDeg) > 0.1 ? (chassisAngleDeg > 0 ? "+" : "") : "") + chassisAngleDeg.toFixed(2) + "°";
els.hudChassisAngle.className = Math.abs(chassisAngleDeg) > 3 ? "hud-value val-orange" : "hud-value";
const frontStroke = Math.sqrt(Math.pow(fPx-fCx, 2) + Math.pow(fPy-fCy, 2)) - Math.abs(CONFIG.platformBaseY);
const rearStroke = Math.sqrt(Math.pow(rPx-rCx, 2) + Math.pow(rPy-rCy, 2)) - Math.abs(CONFIG.platformBaseY);
els.hudFrontStroke.textContent = (frontStroke > 0 ? "+" : "") + frontStroke.toFixed(1) + " mm";
els.hudRearStroke.textContent = (rearStroke > 0 ? "+" : "") + rearStroke.toFixed(1) + " mm";
// 模拟高频响应时间抖动 (体现<20ms的要求)
if (Math.random() > 0.8) {
els.hudResponseTime.textContent = (12 + Math.random() * 6).toFixed(1) + " ms";
}
}
// 更新单个推杆的SVG元素
function updateActuator(jointC, jointP, body, rod, cx, cy, px, py) {
// 更新关节点
jointC.setAttribute('cx', cx); jointC.setAttribute('cy', cy);
jointP.setAttribute('cx', px); jointP.setAttribute('cy', py);
// 计算连线角度和分段
// 推杆缸体连接底盘,活塞杆连接平台 (或者相反,这里采用缸体在下)
const totalLen = Math.sqrt(Math.pow(px-cx, 2) + Math.pow(py-cy, 2));
const bodyLen = 80; // 缸体固定长度视效
const dirX = (px - cx) / totalLen;
const dirY = (py - cy) / totalLen;
const midX = cx + dirX * bodyLen;
const midY = cy + dirY * bodyLen;
// 绘制缸体
body.setAttribute('x1', cx); body.setAttribute('y1', cy);
body.setAttribute('x2', midX); body.setAttribute('y2', midY);
// 绘制活塞杆
rod.setAttribute('x1', midX); rod.setAttribute('y1', midY);
rod.setAttribute('x2', px); rod.setAttribute('y2', py);
}
// 主循环
function render(timestamp) {
if (!lastTimestamp) lastTimestamp = timestamp;
// 限制最大deltaTime,防止切换标签页后时间跳跃导致错乱
const deltaTime = Math.min((timestamp - lastTimestamp) / 1000, 0.05);
lastTimestamp = timestamp;
update(deltaTime);
requestAnimationFrame(render);
}
// 初始化启动 (满足加载后自动播放的要求)
document.addEventListener("DOMContentLoaded", () => {
requestAnimationFrame(render);
});
</script>
</body>
</html>
积分规则:第一轮对话扣减8分,后续每轮扣6分
等待动画代码生成...
