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