分享图
动画工坊
引擎就绪
<!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: #060913;
            --grid-color: #121a2f;
            --cyan-glow: #00f0ff;
            --orange-alert: #ff5500;
            --green-safe: #00ff66;
            --text-main: #a3b8cc;
            --text-dim: #4d6680;
            --panel-bg: rgba(6, 9, 19, 0.85);
            --panel-border: rgba(0, 240, 255, 0.2);
        }

        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }

        body {
            background-color: var(--bg-color);
            color: var(--text-main);
            font-family: ui-monospace, SFMono-Regular, Consolas, "Liberation Mono", Menlo, monospace;
            overflow: hidden;
            width: 100vw;
            height: 100vh;
        }

        /* 绝对定位的极简UI面板,严格限制尺寸与位置,避免遮挡动画主体 */
        .overlay-panel {
            position: absolute;
            background: var(--panel-bg);
            border: 1px solid var(--panel-border);
            padding: 12px 16px;
            border-radius: 4px;
            backdrop-filter: blur(4px);
            pointer-events: none;
            z-index: 10;
        }

        .top-left { top: 20px; left: 20px; }
        .top-right { top: 20px; right: 20px; }
        .bottom-left { bottom: 20px; left: 20px; }
        .bottom-right { bottom: 20px; right: 20px; }

        .text-sm { font-size: 13px; line-height: 1.6; }
        .text-xs { font-size: 11px; line-height: 1.4; color: var(--text-dim); }
        .title { color: var(--cyan-glow); font-weight: bold; font-size: 14px; margin-bottom: 8px; text-transform: uppercase; letter-spacing: 1px; }
        
        .data-row { display: flex; justify-content: space-between; gap: 24px; }
        .data-label { color: var(--text-dim); }
        .data-value { color: var(--green-safe); font-weight: bold; font-variant-numeric: tabular-nums; }
        .data-value.alert { color: var(--orange-alert); }

        #canvas-container {
            width: 100%;
            height: 100%;
            display: flex;
            justify-content: center;
            align-items: center;
        }

        svg {
            width: 100%;
            height: 100%;
            max-height: 100vh;
        }

        /* SVG 内部样式 */
        .grid-line { stroke: var(--grid-color); stroke-width: 1; }
        .terrain-line { fill: none; stroke: var(--text-dim); stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
        .terrain-fill { fill: rgba(77, 102, 128, 0.05); }
        
        .module-body { fill: #0f1626; stroke: var(--cyan-glow); stroke-width: 1.5; }
        .track-path { fill: none; stroke: var(--text-main); stroke-width: 4; stroke-dasharray: 6 4; stroke-linecap: round; }
        .joint-line { fill: none; stroke-width: 4; stroke-linecap: round; transition: stroke 0.2s; }
        .joint-node { fill: var(--bg-color); stroke-width: 2; transition: stroke 0.2s; }

        .annotation { font-family: sans-serif; font-size: 12px; fill: var(--text-dim); }
        .divider { stroke: var(--grid-color); stroke-width: 2; stroke-dasharray: 10 10; }
    </style>
</head>
<body>

    <!-- 极简信息面板:布局于边缘,采用小字号 -->
    <div class="overlay-panel top-left text-sm">
        <div class="title">IFR 系统状态 // 空间分离与动态性原理</div>
        <div class="text-xs">
            > 核心矛盾已消除:传统长履带的高转弯阻力与地形适应性差<br>
            > 创新方案:多段独立短履带 + 柔性扭力弹簧万向节<br>
            > 观测对象:左侧 [台阶自适应] / 右侧 [零半径差速转向]
        </div>
    </div>

    <div class="overlay-panel top-right text-sm">
        <div class="title">差速驱动监控 (俯视图)</div>
        <div id="diff-telemetry">
            <!-- 由JS动态填充 -->
        </div>
        <div class="text-xs" style="margin-top: 8px;">
            *外侧履带增速,内侧减速以实现柔顺过弯
        </div>
    </div>

    <div class="overlay-panel bottom-left text-sm">
        <div class="title">铰接弯折角监控 (侧视图)</div>
        <div id="angle-telemetry">
            <!-- 由JS动态填充 -->
        </div>
        <div class="text-xs" style="margin-top: 8px;">
            *允许最大弯折角: 45°
        </div>
    </div>

    <div class="overlay-panel bottom-right text-sm">
        <div class="text-xs text-right">
            [ 纯运动学原理验证 ]<br>
            自动循环播放中...
        </div>
    </div>

    <div id="canvas-container">
        <svg id="main-canvas" viewBox="0 0 1200 600" preserveAspectRatio="xMidYMid meet">
            <defs>
                <pattern id="bg-grid" width="40" height="40" patternUnits="userSpaceOnUse">
                    <path d="M 40 0 L 0 0 0 40" class="grid-line" />
                </pattern>
                <filter id="glow-cyan" x="-20%" y="-20%" width="140%" height="140%">
                    <feGaussianBlur stdDeviation="4" result="blur" />
                    <feComposite in="SourceGraphic" in2="blur" operator="over" />
                </filter>
                <filter id="glow-orange" x="-20%" y="-20%" width="140%" height="140%">
                    <feGaussianBlur stdDeviation="4" result="blur" />
                    <feComposite in="SourceGraphic" in2="blur" operator="over" />
                </filter>
            </defs>

            <!-- 背景网格 -->
            <rect width="100%" height="100%" fill="url(#bg-grid)" />
            
            <!-- 左右分界线 -->
            <line x1="600" y1="50" x2="600" y2="550" class="divider" />
            
            <!-- 场景标注 -->
            <text x="300" y="40" class="annotation" text-anchor="middle" fill="var(--cyan-glow)">[ 剖面侧视图:被动地形自适应起伏 ]</text>
            <text x="900" y="40" class="annotation" text-anchor="middle" fill="var(--cyan-glow)">[ 俯视平面图:多舱段协同差速过弯 ]</text>

            <!-- 左侧:侧视图地形 (台阶) -->
            <g id="terrain-side">
                <path id="side-path-line" class="terrain-line" d="" />
                <path id="side-path-fill" class="terrain-fill" d="" />
            </g>

            <!-- 右侧:俯视图地形 (L型转角平台) -->
            <g id="terrain-top">
                <path id="top-path-line" class="terrain-line" d="" stroke-dasharray="10 10" />
                <!-- 绘制平台边缘 -->
                <path d="M 650 350 L 800 350 A 50 50 0 0 0 850 300 L 850 100" class="terrain-line" opacity="0.3" />
                <path d="M 650 450 L 800 450 A 150 150 0 0 0 950 300 L 950 100" class="terrain-line" opacity="0.3" />
            </g>

            <!-- 动态渲染的车辆组 -->
            <g id="vehicles">
                <g id="vehicle-side"></g>
                <g id="vehicle-top"></g>
            </g>
        </svg>
    </div>

    <script>
        /**
         * 纯运动学数学模型与渲染逻辑
         * 核心设计:基于TRIZ最终理想解,直接展示系统如何利用柔性铰接消除矛盾。
         */

        const canvas = document.getElementById('main-canvas');
        const vehicleSideGroup = document.getElementById('vehicle-side');
        const vehicleTopGroup = document.getElementById('vehicle-top');
        const diffTelemetry = document.getElementById('diff-telemetry');
        const angleTelemetry = document.getElementById('angle-telemetry');

        // 系统参数
        const NUM_MODULES = 4;
        const MOD_LENGTH = 50;   // 单个舱段基础长度
        const SPACING = 65;      // 舱段中心间距
        const BASE_SPEED = 1.2;  // 基础运行速度

        // 状态变量
        let global_s = 0; // 全局路径进度

        // 存储各个舱段的履带滚动偏移量,用于独立差速展示
        let trackOffsetsSide = [0, 0, 0, 0];
        let trackOffsetsTopL = [0, 0, 0, 0];
        let trackOffsetsTopR = [0, 0, 0, 0];

        /**
         * 侧视图路径函数:模拟台阶地形
         * 输入路径长度s,返回 {x, y, angle}
         */
        function getSideTransform(s) {
            // 地形分段定点
            // 平地 -> 斜坡(爬上台阶) -> 平地平台 -> 斜坡(爬上第二台阶) -> 平地
            if (s < 150) return { x: s, y: 400, angle: 0 };
            s -= 150;
            if (s < 99) return { x: 150 + s * 0.707, y: 400 - s * 0.707, angle: -45 };
            s -= 99;
            if (s < 130) return { x: 220 + s, y: 330, angle: 0 };
            s -= 130;
            if (s < 99) return { x: 350 + s * 0.707, y: 330 - s * 0.707, angle: -45 };
            s -= 99;
            return { x: 420 + s, y: 260, angle: 0 };
        }

        /**
         * 俯视图路径函数:模拟L型狭窄平台过弯
         * 输入路径长度s,返回 {x, y, angle}
         */
        function getTopTransform(s) {
            // 平直 -> 90度圆弧转向 -> 平直
            if (s < 200) return { x: 650 + s, y: 400, angle: 0 };
            s -= 200;
            if (s < 157.08) { // 100 * PI / 2
                let a = s / 100; // 弧度
                return {
                    x: 850 + 100 * Math.sin(a),
                    y: 300 + 100 * Math.cos(a),
                    angle: -a * 180 / Math.PI
                };
            }
            s -= 157.08;
            return { x: 950, y: 300 - s, angle: -90 };
        }

        // 初始化绘制静态地形线
        function initTerrain() {
            let sideD = "M 0 400 L 150 400 L 220 330 L 350 330 L 420 260 L 600 260";
            document.getElementById('side-path-line').setAttribute('d', sideD);
            document.getElementById('side-path-fill').setAttribute('d', sideD + " L 600 500 L 0 500 Z");

            let topD = "M 650 400 L 850 400 A 100 100 0 0 0 950 300 L 950 100";
            document.getElementById('top-path-line').setAttribute('d', topD);
        }

        // 创建SVG元素的辅助函数
        function createSVG(tag, attrs) {
            let el = document.createElementNS("http://www.w3.org/2000/svg", tag);
            for (let k in attrs) el.setAttribute(k, attrs[k]);
            return el;
        }

        // 初始化车辆DOM结构
        function initVehicles() {
            for (let i = 0; i < NUM_MODULES; i++) {
                // 侧视图舱段
                let gSide = createSVG('g', { id: `mod-side-${i}` });
                // 履带(背景)
                gSide.appendChild(createSVG('rect', { x: -28, y: -16, width: 56, height: 32, rx: 8, class: 'track-path' }));
                // 车体
                gSide.appendChild(createSVG('rect', { x: -25, y: -10, width: 50, height: 20, rx: 4, class: 'module-body' }));
                
                // 俯视图舱段
                let gTop = createSVG('g', { id: `mod-top-${i}` });
                // 左履带
                gTop.appendChild(createSVG('rect', { x: -28, y: -24, width: 56, height: 10, rx: 3, class: 'track-path' }));
                // 右履带
                gTop.appendChild(createSVG('rect', { x: -28, y: 14, width: 56, height: 10, rx: 3, class: 'track-path' }));
                // 车体
                gTop.appendChild(createSVG('rect', { x: -25, y: -20, width: 50, height: 40, rx: 4, class: 'module-body' }));

                vehicleSideGroup.appendChild(gSide);
                vehicleTopGroup.appendChild(gTop);

                // 添加关节连接线与节点 (不给第0节车头加)
                if (i > 0) {
                    vehicleSideGroup.appendChild(createSVG('line', { id: `joint-line-side-${i}`, class: 'joint-line' }));
                    vehicleSideGroup.appendChild(createSVG('circle', { id: `joint-node-side-${i}`, r: 4, class: 'joint-node' }));
                    
                    vehicleTopGroup.appendChild(createSVG('line', { id: `joint-line-top-${i}`, class: 'joint-line' }));
                    vehicleTopGroup.appendChild(createSVG('circle', { id: `joint-node-top-${i}`, r: 4, class: 'joint-node' }));
                }
            }
        }

        // 格式化数字
        function fmt(num) {
            return num.toFixed(1).padStart(5, '\u00A0'); // 保持等宽对齐
        }

        // 动画主循环
        function renderLoop() {
            global_s += BASE_SPEED;
            if (global_s > 850) global_s = -50; // 循环播放

            let sideHtml = '';
            let topHtml = '';

            // 临时存储各节点中心用于绘制关节
            let sideCenters = [];
            let topCenters = [];

            for (let i = 0; i < NUM_MODULES; i++) {
                // 计算当前舱段在路径上的位置
                let s_mod = global_s - i * SPACING;
                
                let tSide = getSideTransform(s_mod);
                let tTop = getTopTransform(s_mod);

                sideCenters.push(tSide);
                topCenters.push(tTop);

                // --- 逻辑与数据计算 ---
                
                // 1. 侧视图弯折角计算 (与前一节的夹角)
                let angleDiffSide = 0;
                if (i > 0) {
                    angleDiffSide = Math.abs(sideCenters[i-1].angle - tSide.angle);
                }

                // 2. 俯视图差速计算
                // 在 s=200 到 s=357 的区间内处于转弯状态
                let isTurning = s_mod > 200 && s_mod < 357;
                let vL = BASE_SPEED;
                let vR = BASE_SPEED;
                
                if (isTurning) {
                    // 左转弯,外侧(右)履带增速,内侧(左)履带减速
                    vL = BASE_SPEED * 0.5;
                    vR = BASE_SPEED * 1.5;
                }

                // 更新履带位移状态
                trackOffsetsSide[i] -= BASE_SPEED;
                trackOffsetsTopL[i] -= vL;
                trackOffsetsTopR[i] -= vR;

                // --- DOM 渲染 ---

                // 侧视图舱段更新
                let elSide = document.getElementById(`mod-side-${i}`);
                elSide.setAttribute('transform', `translate(${tSide.x}, ${tSide.y}) rotate(${tSide.angle})`);
                elSide.children[0].setAttribute('stroke-dashoffset', trackOffsetsSide[i]); // 更新履带滚动

                // 俯视图舱段更新
                let elTop = document.getElementById(`mod-top-${i}`);
                elTop.setAttribute('transform', `translate(${tTop.x}, ${tTop.y}) rotate(${tTop.angle})`);
                elTop.children[0].setAttribute('stroke-dashoffset', trackOffsetsTopL[i]); // 左履带
                elTop.children[1].setAttribute('stroke-dashoffset', trackOffsetsTopR[i]); // 右履带

                // 关节渲染与视觉警示
                if (i > 0) {
                    let pSide = sideCenters[i-1];
                    let pTop = topCenters[i-1];
                    
                    // 侧视关节
                    let jlSide = document.getElementById(`joint-line-side-${i}`);
                    let jnSide = document.getElementById(`joint-node-side-${i}`);
                    let midSideX = (tSide.x + pSide.x)/2;
                    let midSideY = (tSide.y + pSide.y)/2;
                    
                    jlSide.setAttribute('x1', tSide.x); jlSide.setAttribute('y1', tSide.y);
                    jlSide.setAttribute('x2', pSide.x); jlSide.setAttribute('y2', pSide.y);
                    jnSide.setAttribute('cx', midSideX); jnSide.setAttribute('cy', midSideY);

                    // 角度大时触发高亮(表示万向节柔性生效)
                    let alertColor = angleDiffSide > 10 ? 'var(--orange-alert)' : 'var(--cyan-glow)';
                    jlSide.setAttribute('stroke', alertColor);
                    jnSide.setAttribute('stroke', alertColor);
                    if(angleDiffSide > 10) jnSide.setAttribute('filter', 'url(#glow-orange)');
                    else jnSide.removeAttribute('filter');

                    // 俯视关节
                    let jlTop = document.getElementById(`joint-line-top-${i}`);
                    let jnTop = document.getElementById(`joint-node-top-${i}`);
                    let midTopX = (tTop.x + pTop.x)/2;
                    let midTopY = (tTop.y + pTop.y)/2;

                    jlTop.setAttribute('x1', tTop.x); jlTop.setAttribute('y1', tTop.y);
                    jlTop.setAttribute('x2', pTop.x); jlTop.setAttribute('y2', pTop.y);
                    jnTop.setAttribute('cx', midTopX); jnTop.setAttribute('cy', midTopY);

                    let turnAlert = isTurning ? 'var(--green-safe)' : 'var(--cyan-glow)';
                    jlTop.setAttribute('stroke', turnAlert);
                    jnTop.setAttribute('stroke', turnAlert);
                }

                // --- UI 面板数据构建 ---
                if (i > 0) {
                    let alertClass = angleDiffSide > 10 ? 'alert' : '';
                    sideHtml += `<div class="data-row"><span class="data-label">关节 ${i-1}-${i} 角度差:</span><span class="data-value ${alertClass}">${fmt(angleDiffSide)}°</span></div>`;
                }
                
                let diffStatus = isTurning ? `<span style="color:var(--orange-alert)">减速</span> / <span style="color:var(--green-safe)">增速</span>` : '匀速同步';
                topHtml += `<div class="data-row"><span class="data-label">舱段 ${i} L/R:</span><span class="data-value">${diffStatus}</span></div>`;
            }

            // 更新DOM数据
            angleTelemetry.innerHTML = sideHtml;
            diffTelemetry.innerHTML = topHtml;

            requestAnimationFrame(renderLoop);
        }

        // 启动系统
        initTerrain();
        initVehicles();
        requestAnimationFrame(renderLoop);

    </script>
</body>
</html>
积分规则:第一轮对话扣减8分,后续每轮扣6分