分享图
动画工坊
引擎就绪
<!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分