独立渲染引擎就绪引擎就绪
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>TRIZ IFR: 行星轮底盘与双轴云台解耦系统</title>
<style>
:root {
--bg-color: #050a10;
--grid-color: rgba(0, 240, 255, 0.08);
--cyan: #00f0ff;
--orange: #ff5c00;
--green: #00ff66;
--text-main: rgba(255, 255, 255, 0.85);
--text-dim: rgba(255, 255, 255, 0.4);
--hud-font: 'Courier New', Courier, 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, Arial, sans-serif;
overflow: hidden;
width: 100vw;
height: 100vh;
display: flex;
flex-direction: column;
}
/* 核心画布容器 */
.canvas-container {
flex: 1;
position: relative;
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
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;
}
svg {
width: 100%;
height: 100%;
max-width: 1600px;
max-height: 900px;
filter: drop-shadow(0 0 10px rgba(0, 240, 255, 0.1));
}
/* HUD 边缘信息面板 - 绝对定位,字体极小,避免遮挡核心动画 */
.hud-panel {
position: absolute;
font-family: var(--hud-font);
font-size: 12px;
line-height: 1.6;
pointer-events: none;
z-index: 10;
background: rgba(5, 10, 16, 0.6);
backdrop-filter: blur(4px);
border: 1px solid rgba(0, 240, 255, 0.2);
padding: 12px 16px;
border-radius: 4px;
}
.hud-top-left { top: 24px; left: 24px; }
.hud-top-right { top: 24px; right: 24px; text-align: right; }
.hud-bottom-left { bottom: 80px; left: 24px; }
.hud-title {
font-size: 14px;
color: var(--cyan);
font-weight: bold;
margin-bottom: 8px;
letter-spacing: 1px;
text-transform: uppercase;
}
.data-row {
display: flex;
justify-content: space-between;
gap: 20px;
margin-bottom: 4px;
}
.data-label { color: var(--text-dim); }
.data-value { color: var(--green); font-weight: bold; }
.data-value.warning { color: var(--orange); }
/* 交互控制栏 */
.controls {
position: absolute;
bottom: 0;
left: 0;
width: 100%;
height: 60px;
background: rgba(0, 0, 0, 0.8);
border-top: 1px solid rgba(0, 240, 255, 0.2);
display: flex;
align-items: center;
padding: 0 40px;
gap: 20px;
z-index: 20;
}
.timeline-container {
flex: 1;
position: relative;
height: 20px;
cursor: pointer;
display: flex;
align-items: center;
}
.timeline-track {
width: 100%;
height: 4px;
background: rgba(255, 255, 255, 0.1);
border-radius: 2px;
position: relative;
}
.timeline-progress {
position: absolute;
left: 0;
top: 0;
height: 100%;
background: var(--cyan);
border-radius: 2px;
width: 0%;
}
.timeline-handle {
position: absolute;
top: 50%;
left: 0%;
transform: translate(-50%, -50%);
width: 12px;
height: 12px;
background: #fff;
border: 2px solid var(--cyan);
border-radius: 50%;
box-shadow: 0 0 10px var(--cyan);
transition: transform 0.1s;
}
.timeline-container:hover .timeline-handle {
transform: translate(-50%, -50%) scale(1.3);
}
.btn-toggle {
background: transparent;
border: 1px solid var(--cyan);
color: var(--cyan);
font-family: var(--hud-font);
font-size: 12px;
padding: 6px 16px;
border-radius: 4px;
cursor: pointer;
transition: all 0.3s ease;
text-transform: uppercase;
}
.btn-toggle:hover {
background: rgba(0, 240, 255, 0.1);
box-shadow: 0 0 10px rgba(0, 240, 255, 0.3);
}
/* SVG 内发光与动画类 */
.glow-cyan { filter: drop-shadow(0 0 8px rgba(0, 240, 255, 0.6)); }
.glow-orange { filter: drop-shadow(0 0 12px rgba(255, 92, 0, 0.8)); }
.glow-green { filter: drop-shadow(0 0 8px rgba(0, 255, 102, 0.6)); }
.dash-line {
stroke-dasharray: 4 4;
animation: dashMove 20s linear infinite;
}
@keyframes dashMove {
to { stroke-dashoffset: -400; }
}
</style>
</head>
<body>
<div class="canvas-container">
<!-- HUD 绝对定位面板 -->
<div class="hud-panel hud-top-left">
<div class="hud-title">TRIZ IFR | 系统解耦实时监控</div>
<div class="data-row"><span class="data-label">底盘越障状态:</span><span class="data-value" id="hud-chassis-state">平地巡航</span></div>
<div class="data-row"><span class="data-label">底盘俯仰角 (θ1):</span><span class="data-value warning" id="hud-pitch-angle">0.00°</span></div>
<div class="data-row"><span class="data-label">行星架 RPM:</span><span class="data-value" id="hud-bracket-rpm">120</span></div>
<div class="data-row"><span class="data-label">环境地形探测:</span><span class="data-value warning">台阶高度 120mm</span></div>
</div>
<div class="hud-panel hud-top-right">
<div class="hud-title">IMU 补偿云台核心数据</div>
<div class="data-row"><span class="data-label">云台补偿角 (θ2):</span><span class="data-value warning" id="hud-gimbal-angle">0.00°</span></div>
<div class="data-row"><span class="data-label">前端推杆行程:</span><span class="data-value" id="hud-rod-front">150.0 mm</span></div>
<div class="data-row"><span class="data-label">后端推杆行程:</span><span class="data-value" id="hud-rod-rear">150.0 mm</span></div>
<div class="data-row"><span class="data-label">最终载荷姿态 (θ1+θ2):</span><span class="data-value glow-green">绝对水平 (0.00°)</span></div>
</div>
<div class="hud-panel hud-bottom-left" style="background: transparent; border: none;">
<div style="color: var(--text-dim); margin-bottom: 4px;">设计原理 (最终理想解 IFR):</div>
<div style="color: var(--cyan); max-width: 300px;">
利用行星轮将平动受阻转化为翻转越障;<br>利用 IMU+电动推杆实现姿态逆向补偿。<br>【底盘运动】与【载荷水平】在物理空间彻底解耦。
</div>
</div>
<!-- 核心 SVG 动画区 -->
<svg id="main-svg" viewBox="0 0 1600 800" preserveAspectRatio="xMidYMid meet">
<defs>
<!-- 材质渐变 -->
<linearGradient id="chassis-grad" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#0a1a2a" />
<stop offset="100%" stop-color="#05101a" />
</linearGradient>
<linearGradient id="cargo-grad" x1="0" y1="0" x2="1" y2="0">
<stop offset="0%" stop-color="#ff5c00" />
<stop offset="50%" stop-color="#ffa000" />
<stop offset="100%" stop-color="#ff5c00" />
</linearGradient>
<linearGradient id="rod-grad" x1="0" y1="0" x2="1" y2="0">
<stop offset="0%" stop-color="#ffffff" />
<stop offset="100%" stop-color="#8899aa" />
</linearGradient>
<!-- 滤镜 -->
<filter id="glow" x="-20%" y="-20%" width="140%" height="140%">
<feGaussianBlur stdDeviation="4" result="blur" />
<feComposite in="SourceGraphic" in2="blur" operator="over" />
</filter>
</defs>
<!-- 坐标系背景 -->
<g opacity="0.2">
<line x1="0" y1="550" x2="1600" y2="550" stroke="var(--cyan)" stroke-width="1" class="dash-line" />
<line x1="800" y1="0" x2="800" y2="800" stroke="var(--cyan)" stroke-width="1" class="dash-line" />
</g>
<!-- 地形环境层 -->
<path id="ground-path" fill="none" stroke="#223344" stroke-width="4" stroke-linejoin="round" />
<path id="ground-fill" fill="url(#chassis-grad)" opacity="0.3" />
<!-- 绝对水平基准线 -->
<line x1="0" y1="200" x2="1600" y2="200" stroke="var(--green)" stroke-width="1" stroke-dasharray="10 5" opacity="0.6" />
<text x="40" y="190" fill="var(--green)" font-family="var(--hud-font)" font-size="12" opacity="0.8">IFR 理想水平基准面 (0.00°)</text>
<!-- 全局小车容器 -->
<g id="vehicle-container">
<!-- 行星轮底盘组 -->
<g id="chassis-group">
<!-- 后行星架 -->
<g id="rear-planetary" transform="translate(-140, 0)">
<circle cx="0" cy="0" r="65" fill="none" stroke="var(--orange)" stroke-width="1" stroke-dasharray="4 4" opacity="0.3" />
<!-- Y型支架 -->
<path d="M 0 0 L 0 -65 M 0 0 L 56.3 32.5 M 0 0 L -56.3 32.5" stroke="#8899aa" stroke-width="8" stroke-linecap="round" />
<circle cx="0" cy="0" r="15" fill="#445566" />
<!-- 3个小轮 -->
<g id="rw1" transform="translate(0, -65)"><circle r="20" fill="#111" stroke="var(--cyan)" stroke-width="3"/><circle r="8" fill="#445"/></g>
<g id="rw2" transform="translate(56.3, 32.5)"><circle r="20" fill="#111" stroke="var(--cyan)" stroke-width="3"/><circle r="8" fill="#445"/></g>
<g id="rw3" transform="translate(-56.3, 32.5)"><circle r="20" fill="#111" stroke="var(--cyan)" stroke-width="3"/><circle r="8" fill="#445"/></g>
</g>
<!-- 前行星架 -->
<g id="front-planetary" transform="translate(140, 0)">
<circle cx="0" cy="0" r="65" fill="none" stroke="var(--orange)" stroke-width="1" stroke-dasharray="4 4" opacity="0.3" />
<path d="M 0 0 L 0 -65 M 0 0 L 56.3 32.5 M 0 0 L -56.3 32.5" stroke="#8899aa" stroke-width="8" stroke-linecap="round" />
<circle cx="0" cy="0" r="15" fill="#445566" />
<g id="fw1" transform="translate(0, -65)"><circle r="20" fill="#111" stroke="var(--cyan)" stroke-width="3"/><circle r="8" fill="#445"/></g>
<g id="fw2" transform="translate(56.3, 32.5)"><circle r="20" fill="#111" stroke="var(--cyan)" stroke-width="3"/><circle r="8" fill="#445"/></g>
<g id="fw3" transform="translate(-56.3, 32.5)"><circle r="20" fill="#111" stroke="var(--cyan)" stroke-width="3"/><circle r="8" fill="#445"/></g>
</g>
<!-- 底盘本体 -->
<rect x="-180" y="-20" width="360" height="40" rx="10" fill="url(#chassis-grad)" stroke="var(--cyan)" stroke-width="2" />
<!-- 底盘内部骨架装饰 -->
<line x1="-150" y1="0" x2="150" y2="0" stroke="var(--cyan)" stroke-width="1" opacity="0.5" />
<rect x="-160" y="-10" width="320" height="20" rx="5" fill="none" stroke="var(--cyan)" stroke-width="1" opacity="0.3" />
<!-- IMU 传感器 (核心部件) -->
<g transform="translate(0, 0)">
<rect x="-15" y="-15" width="30" height="30" rx="4" fill="#112233" stroke="var(--green)" stroke-width="2" class="glow-green" />
<circle cx="0" cy="0" r="4" fill="var(--green)" />
<text x="25" y="4" fill="var(--green)" font-family="var(--hud-font)" font-size="10">IMU GYRO</text>
<!-- 动态雷达波 -->
<circle id="imu-wave" cx="0" cy="0" r="20" fill="none" stroke="var(--green)" stroke-width="1" opacity="0" />
</g>
<!-- 推杆连接座基点 -->
<circle cx="-120" cy="-20" r="6" fill="#8899aa" />
<circle cx="120" cy="-20" r="6" fill="#8899aa" />
</g>
<!-- 电动推杆 (独立绘制以实现拉伸) -->
<!-- 后推杆 外壳与活塞 -->
<g id="rear-pushrod-group">
<line id="rear-rod-outer" x1="-120" y1="-20" x2="-120" y2="-70" stroke="#445566" stroke-width="14" stroke-linecap="round" />
<line id="rear-rod-inner" x1="-120" y1="-70" x2="-120" y2="-120" stroke="url(#rod-grad)" stroke-width="8" stroke-linecap="round" />
</g>
<!-- 前推杆 外壳与活塞 -->
<g id="front-pushrod-group">
<line id="front-rod-outer" x1="120" y1="-20" x2="120" y2="-70" stroke="#445566" stroke-width="14" stroke-linecap="round" />
<line id="front-rod-inner" x1="120" y1="-70" x2="120" y2="-120" stroke="url(#rod-grad)" stroke-width="8" stroke-linecap="round" />
</g>
<!-- 载荷云台组 (始终绝对水平) -->
<g id="gimbal-cargo-group">
<!-- 云台承载托盘 -->
<path d="M -200 0 L 200 0 L 180 15 L -180 15 Z" fill="#223344" stroke="#8899aa" stroke-width="2" />
<!-- 连接轴承 -->
<circle cx="-120" cy="15" r="5" fill="#fff" />
<circle cx="120" cy="15" r="5" fill="#fff" />
<!-- 货物 -->
<rect x="-140" y="-80" width="280" height="80" rx="4" fill="url(#cargo-grad)" class="glow-orange" opacity="0.9" />
<rect x="-130" y="-70" width="260" height="60" rx="2" fill="none" stroke="#fff" stroke-width="1" opacity="0.3" />
<!-- 货物标识 -->
<g transform="translate(0, -40)">
<path d="M -30 -15 L 0 -25 L 30 -15 L 30 15 L 0 25 L -30 15 Z" fill="none" stroke="#fff" stroke-width="2" />
<text x="0" y="4" fill="#fff" font-family="Arial, sans-serif" font-size="14" font-weight="bold" text-anchor="middle">SENSITIVE PAYLOAD</text>
</g>
</g>
</g>
</svg>
</div>
<!-- 交互控制面板 -->
<div class="controls">
<button class="btn-toggle" id="btn-play">Pause / 暂停</button>
<div class="timeline-container" id="timeline">
<div class="timeline-track">
<div class="timeline-progress" id="progress-bar"></div>
</div>
<div class="timeline-handle" id="progress-handle"></div>
</div>
<div style="font-family: var(--hud-font); font-size: 12px; color: var(--cyan); width: 80px; text-align: right;" id="time-display">0%</div>
</div>
<script>
// 系统参数定义
const config = {
groundBaseY: 600, // 平地Y坐标
stepX: 900, // 台阶触发X坐标
stepHeight: 110, // 台阶高度 (需与行星臂长匹配以保证越过)
bracketRadius: 65, // 行星支架臂长 R
wheelRadius: 20, // 小轮半径 r
chassisHalfLen: 140, // 轴距一半 (前后行星架中心至底盘中心)
cargoBaseDist: 140, // 货物托盘基准高度(相对底盘中心)
cycleDistance: 1600, // 整个动画周期的行驶距离
speed: 3 // 每帧推进像素
};
// DOM 元素引用
const els = {
groundPath: document.getElementById('ground-path'),
groundFill: document.getElementById('ground-fill'),
vehicle: document.getElementById('vehicle-container'),
chassis: document.getElementById('chassis-group'),
frontPlanetary: document.getElementById('front-planetary'),
rearPlanetary: document.getElementById('rear-planetary'),
fw1: document.getElementById('fw1'), fw2: document.getElementById('fw2'), fw3: document.getElementById('fw3'),
rw1: document.getElementById('rw1'), rw2: document.getElementById('rw2'), rw3: document.getElementById('rw3'),
gimbalCargo: document.getElementById('gimbal-cargo-group'),
frontRodOuter: document.getElementById('front-rod-outer'),
frontRodInner: document.getElementById('front-rod-inner'),
rearRodOuter: document.getElementById('rear-rod-outer'),
rearRodInner: document.getElementById('rear-rod-inner'),
imuWave: document.getElementById('imu-wave'),
// HUD 数据
hudPitch: document.getElementById('hud-pitch-angle'),
hudGimbal: document.getElementById('hud-gimbal-angle'),
hudFrontRod: document.getElementById('hud-rod-front'),
hudRearRod: document.getElementById('hud-rod-rear'),
hudState: document.getElementById('hud-chassis-state'),
hudRpm: document.getElementById('hud-bracket-rpm'),
// 控制条
progressBar: document.getElementById('progress-bar'),
progressHandle: document.getElementById('progress-handle'),
timeDisplay: document.getElementById('time-display'),
btnPlay: document.getElementById('btn-play')
};
// 状态变量
let currentX = 0; // 小车的全局位移 0 到 cycleDistance
let isPlaying = true;
let isDragging = false;
let animationId;
// 初始化地形绘制
function drawGround() {
// 平地 -> 台阶起跳 -> 上层平地
const path = `M 0 ${config.groundBaseY} L ${config.stepX} ${config.groundBaseY} L ${config.stepX} ${config.groundBaseY - config.stepHeight} L 2400 ${config.groundBaseY - config.stepHeight}`;
els.groundPath.setAttribute('d', path);
els.groundFill.setAttribute('d', path + ` L 2400 1000 L 0 1000 Z`);
}
// 缓动函数:平滑翻转
function easeInOutQuad(t) {
return t < .5 ? 2 * t * t : -1 + (4 - 2 * t) * t;
}
/**
* 计算单个行星支架的运动学状态
* @param {number} x 支架中心的全局 X 坐标
* @returns { y: 中心Y坐标, theta: 支架旋转角度, wheelTheta: 轮子自转角度 }
*/
function calculatePlanetaryState(x) {
// 定义翻转阶段区域
const flipStartX = config.stepX - config.bracketRadius * 0.8;
const flipDist = config.bracketRadius * 1.5; // 翻转过程中X轴前进距离
const flipEndX = flipStartX + flipDist;
let y = config.groundBaseY - config.wheelRadius - config.bracketRadius;
let theta = 0;
let wheelTheta = (x / (2 * Math.PI * config.wheelRadius)) * 360;
if (x < flipStartX) {
// 平地滚动阶段
y = config.groundBaseY - config.wheelRadius - config.bracketRadius;
theta = 0; // 行星架不转
} else if (x >= flipStartX && x <= flipEndX) {
// 撞击台阶,翻转越障阶段
let progress = (x - flipStartX) / flipDist;
let eased = easeInOutQuad(progress);
// 行星架翻转 120 度
theta = eased * 120;
// 中心Y坐标抬升到台阶高度
y = (config.groundBaseY - config.wheelRadius - config.bracketRadius) - (eased * config.stepHeight);
// 翻转时轮子受阻,自转变慢或倒转
wheelTheta = wheelTheta - eased * 120;
} else {
// 跨上台阶后的平地滚动阶段
y = config.groundBaseY - config.wheelRadius - config.bracketRadius - config.stepHeight;
theta = 120; // 锁定在120度
}
return { y, theta, wheelTheta };
}
// 核心渲染循环
function updateFrame(x) {
// 确保边界
x = Math.max(0, Math.min(x, config.cycleDistance));
// 1. 计算前后行星架的坐标 X
// 假设小车水平视角的中心在屏幕固定位置 (视觉跟随)
// 这里我们让小车绝对移动,摄像机(SVG viewBox)不动,让小车从左走到右
const vehicleScreenX = x * 0.8 - 100;
const frontX = x + config.chassisHalfLen;
const rearX = x - config.chassisHalfLen;
// 2. 获取前后支架状态
const frontState = calculatePlanetaryState(frontX);
const rearState = calculatePlanetaryState(rearX);
// 3. 计算底盘姿态
// Y坐标中心点 (简单平均)
const chassisCenterY = (frontState.y + rearState.y) / 2;
// 俯仰角:利用反正切 (注意SVG中Y轴向下,前端高(Y小)则应当仰起(负角度))
const dy = rearState.y - frontState.y;
const dx = config.chassisHalfLen * 2;
let pitchRad = Math.atan2(dy, dx);
let pitchDeg = pitchRad * (180 / Math.PI);
// 4. 计算推杆和云台姿态 (核心解耦逻辑 IFR)
// 目标:货物始终绝对水平。则云台相对底盘需要旋转 -pitchDeg
const gimbalAngle = -pitchDeg;
// 应用 DOM 变换
// 车辆整体位移和底盘旋转
els.vehicle.setAttribute('transform', `translate(${vehicleScreenX}, 0)`);
// 行星架应用位移差和旋转
// 因为 vehicle 容器在Y轴上没有移动,所以把计算出的 Y 值直接赋给行星架的 translate
els.frontPlanetary.setAttribute('transform', `translate(${config.chassisHalfLen}, ${frontState.y}) rotate(${frontState.theta})`);
els.rearPlanetary.setAttribute('transform', `translate(${-config.chassisHalfLen}, ${rearState.y}) rotate(${rearState.theta})`);
// 小轮自转
const wRots = [
`translate(0, -65) rotate(${frontState.wheelTheta})`,
`translate(56.3, 32.5) rotate(${frontState.wheelTheta})`,
`translate(-56.3, 32.5) rotate(${frontState.wheelTheta})`
];
els.fw1.setAttribute('transform', wRots[0]); els.fw2.setAttribute('transform', wRots[1]); els.fw3.setAttribute('transform', wRots[2]);
const wRotsR = [
`translate(0, -65) rotate(${rearState.wheelTheta})`,
`translate(56.3, 32.5) rotate(${rearState.wheelTheta})`,
`translate(-56.3, 32.5) rotate(${rearState.wheelTheta})`
];
els.rw1.setAttribute('transform', wRotsR[0]); els.rw2.setAttribute('transform', wRotsR[1]); els.rw3.setAttribute('transform', wRotsR[2]);
// 底盘本体姿态
els.chassis.setAttribute('transform', `translate(0, ${chassisCenterY}) rotate(${pitchDeg})`);
// 货物平台绝对水平:挂载在底盘坐标系内,因此中心点在 (0,0),只需反向旋转,并向上平移至基准高度
els.gimbalCargo.setAttribute('transform', `translate(0, ${chassisCenterY}) rotate(${pitchDeg}) translate(0, ${-config.cargoBaseDist}) rotate(${gimbalAngle})`);
// 推杆动态伸缩 (视觉连线计算)
// 挂载点在底盘上为 P_c_L(-120, -20) 和 P_c_R(120, -20)
// 货物挂载点在货物平台系为 P_g_L(-120, 15) 和 P_g_R(120, 15)
// 为了简化SVG DOM计算,推杆画在底盘 group 内
// 计算货物挂载点在底盘坐标系中的坐标:
// 向量 (-120, -config.cargoBaseDist+15) 经历 gimbalAngle 旋转后的位置
const gRad = gimbalAngle * Math.PI / 180;
const cosG = Math.cos(gRad), sinG = Math.sin(gRad);
const cargoAttachY = -config.cargoBaseDist + 15;
// 前推杆
const fTargetX = 120 * cosG - cargoAttachY * sinG;
const fTargetY = 120 * sinG + cargoAttachY * cosG;
els.frontRodInner.setAttribute('x2', fTargetX);
els.frontRodInner.setAttribute('y2', fTargetY);
// 后推杆
const rTargetX = -120 * cosG - cargoAttachY * sinG;
const rTargetY = -120 * sinG + cargoAttachY * cosG;
els.rearRodInner.setAttribute('x2', rTargetX);
els.rearRodInner.setAttribute('y2', rTargetY);
// 5. 更新 HUD 数据面板
els.hudPitch.textContent = pitchDeg.toFixed(2) + '°';
els.hudPitch.style.color = Math.abs(pitchDeg) > 2 ? 'var(--orange)' : 'var(--green)';
els.hudGimbal.textContent = gimbalAngle.toFixed(2) + '°';
els.hudGimbal.style.color = Math.abs(gimbalAngle) > 2 ? 'var(--orange)' : 'var(--green)';
// 计算行程变化 (基础行程视为 150mm)
const baseLen = Math.abs(-20 - cargoAttachY);
const curFLen = Math.hypot(fTargetX - 120, fTargetY - (-20));
const curRLen = Math.hypot(rTargetX - (-120), rTargetY - (-20));
els.hudFrontRod.textContent = (150 + (curFLen - baseLen)).toFixed(1) + ' mm';
els.hudRearRod.textContent = (150 + (curRLen - baseLen)).toFixed(1) + ' mm';
// 状态判定
let stateText = "平地巡航";
let rpm = "120";
if (frontState.theta > 0 && frontState.theta < 120) {
stateText = "前轮翻转越障中"; rpm = "MAX TQ";
} else if (rearState.theta > 0 && rearState.theta < 120) {
stateText = "后轮翻转越障中"; rpm = "MAX TQ";
} else if (frontState.theta === 120 && rearState.theta === 0) {
stateText = "非对称跨越过渡"; rpm = "120";
}
els.hudState.textContent = stateText;
els.hudRpm.textContent = rpm;
// IMU 雷达波特效
if (Math.abs(pitchDeg) > 1) {
const waveScale = 1 + (Date.now() % 1000) / 1000;
const waveOp = 1 - (Date.now() % 1000) / 1000;
els.imuWave.setAttribute('transform', `scale(${waveScale})`);
els.imuWave.setAttribute('opacity', waveOp);
} else {
els.imuWave.setAttribute('opacity', 0);
}
// 更新进度条
const progressPct = (x / config.cycleDistance) * 100;
els.progressBar.style.width = progressPct + '%';
els.progressHandle.style.left = progressPct + '%';
els.timeDisplay.textContent = Math.floor(progressPct) + '%';
}
// 动画循环
function loop() {
if (isPlaying && !isDragging) {
currentX += config.speed;
if (currentX > config.cycleDistance) {
currentX = 0; // 自动循环
}
updateFrame(currentX);
}
animationId = requestAnimationFrame(loop);
}
// 交互控制
els.btnPlay.addEventListener('click', () => {
isPlaying = !isPlaying;
els.btnPlay.textContent = isPlaying ? "Pause / 暂停" : "Play / 播放";
els.btnPlay.style.color = isPlaying ? "var(--cyan)" : "var(--orange)";
els.btnPlay.style.borderColor = isPlaying ? "var(--cyan)" : "var(--orange)";
});
const timeline = document.getElementById('timeline');
function seek(e) {
const rect = timeline.getBoundingClientRect();
let pct = (e.clientX - rect.left) / rect.width;
pct = Math.max(0, Math.min(1, pct));
currentX = pct * config.cycleDistance;
updateFrame(currentX);
}
timeline.addEventListener('mousedown', (e) => {
isDragging = true;
seek(e);
});
window.addEventListener('mousemove', (e) => {
if (isDragging) seek(e);
});
window.addEventListener('mouseup', () => {
isDragging = false;
});
// 初始化并启动
drawGround();
updateFrame(currentX);
// 自动启动动画,满足规则:页面加载后自动播放,无需交互
window.addEventListener('DOMContentLoaded', () => {
isPlaying = true;
loop();
});
</script>
</body>
</html>
积分规则:第一轮对话扣减8分,后续每轮扣6分
等待动画代码生成...
