分享图
动画工坊
引擎就绪
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>TRIZ 理想最终解: 多段铰接式柔性底盘</title>
    <style>
        :root {
            --bg-base: #06080D;
            --bg-grid: #131A2A;
            --stair-fill: #0F1522;
            --stair-stroke: #3B82F6;
            --track-outer: #1E293B;
            --track-tread: #F59E0B;
            --track-inner: #0B1120;
            --joint-base: #10B981;
            --joint-bend: #EF4444;
            --text-main: #E2E8F0;
            --text-dim: #94A3B8;
            --hud-bg: rgba(6, 8, 13, 0.7);
            --hud-border: rgba(59, 130, 246, 0.3);
        }

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

        body {
            background-color: var(--bg-base);
            color: var(--text-main);
            font-family: "SF Mono", "Fira Code", "JetBrains Mono", Consolas, monospace;
            height: 100vh;
            width: 100vw;
            overflow: hidden;
            display: flex;
            justify-content: center;
            align-items: center;
            background-image: 
                linear-gradient(to right, var(--bg-grid) 1px, transparent 1px),
                linear-gradient(to bottom, var(--bg-grid) 1px, transparent 1px);
            background-size: 40px 40px;
            background-position: center center;
        }

        /* 绝对定位的 HUD (Head-Up Display) 覆盖层,避免遮挡中央动画 */
        .hud-panel {
            position: absolute;
            background: var(--hud-bg);
            border: 1px solid var(--hud-border);
            backdrop-filter: blur(8px);
            -webkit-backdrop-filter: blur(8px);
            padding: 16px;
            border-radius: 8px;
            z-index: 10;
        }

        .hud-top-left {
            top: 24px;
            left: 24px;
            width: 300px;
        }

        .hud-bottom-center {
            bottom: 24px;
            left: 50%;
            transform: translateX(-50%);
            width: 400px;
            display: flex;
            flex-direction: column;
            gap: 12px;
            align-items: center;
        }

        .hud-title {
            font-size: 14px;
            font-weight: bold;
            color: var(--stair-stroke);
            margin-bottom: 8px;
            letter-spacing: 1px;
            text-transform: uppercase;
        }

        .hud-desc {
            font-size: 12px;
            color: var(--text-dim);
            line-height: 1.5;
            margin-bottom: 8px;
        }

        .hud-highlight {
            color: var(--track-tread);
            font-weight: bold;
        }

        .metrics {
            font-size: 11px;
            display: flex;
            justify-content: space-between;
            border-top: 1px dashed var(--hud-border);
            padding-top: 8px;
            margin-top: 8px;
        }

        /* 控制滑块样式 */
        input[type=range] {
            -webkit-appearance: none;
            width: 100%;
            background: transparent;
        }
        input[type=range]:focus {
            outline: none;
        }
        input[type=range]::-webkit-slider-runnable-track {
            width: 100%;
            height: 4px;
            cursor: pointer;
            background: var(--bg-grid);
            border-radius: 2px;
        }
        input[type=range]::-webkit-slider-thumb {
            height: 16px;
            width: 8px;
            border-radius: 2px;
            background: var(--track-tread);
            cursor: pointer;
            -webkit-appearance: none;
            margin-top: -6px;
            box-shadow: 0 0 8px var(--track-tread);
        }
        
        .control-labels {
            display: flex;
            justify-content: space-between;
            width: 100%;
            font-size: 10px;
            color: var(--text-dim);
        }

        /* SVG 容器 */
        .svg-container {
            width: 100%;
            height: 100%;
            max-width: 1200px;
            max-height: 800px;
            display: flex;
            justify-content: center;
            align-items: center;
        }

        svg {
            width: 100%;
            height: 100%;
            filter: drop-shadow(0 0 20px rgba(0,0,0,0.5));
        }

        .glow {
            transition: all 0.1s ease;
        }

    </style>
</head>
<body>

    <!-- 左上角说明面板 -->
    <div class="hud-panel hud-top-left">
        <div class="hud-title">TRIZ 最终理想解 (IFR)</div>
        <div class="hud-desc">
            <span class="hud-highlight">核心矛盾:</span>刚性底盘无法适应不规则台阶,易悬空打滑。<br>
            <span class="hud-highlight">理想状态:</span>底盘在不增加复杂动力源的前提下,<span style="color:#10B981">自动顺应台阶轮廓</span>。
        </div>
        <div class="hud-desc" style="margin-bottom: 0;">
            <span class="hud-highlight">资源利用:</span>利用台阶立面的<span style="color:#EF4444">阻抗反作用力</span>,迫使无源扭转弹簧铰接舱段被动折叠,达到最大面积贴合。
        </div>
        <div class="metrics">
            <span>履带预紧力: 50N</span>
            <span>最大铰接角: 90°</span>
        </div>
    </div>

    <!-- 底部控制面板 -->
    <div class="hud-panel hud-bottom-center">
        <div class="control-labels">
            <span>平地行驶</span>
            <span>被动折叠</span>
            <span>攀爬越障</span>
            <span>恢复平直</span>
        </div>
        <input type="range" id="timeline-slider" min="50" max="950" value="50">
        <div class="control-labels" style="justify-content: center; color: var(--stair-stroke);">
            <span>[ 自动播放中 / 可拖动滑块接管控制 ]</span>
        </div>
    </div>

    <div class="svg-container">
        <svg viewBox="0 0 1000 600" preserveAspectRatio="xMidYMid meet">
            <defs>
                <!-- 阶梯纹理 -->
                <pattern id="stair-pattern" width="20" height="20" patternUnits="userSpaceOnUse">
                    <path d="M 20 0 L 0 20 M 10 -10 L -10 10 M 30 10 L 10 30" stroke="#131A2A" stroke-width="2"/>
                </pattern>
                
                <!-- 发光滤镜 -->
                <filter id="neon-glow-blue" x="-20%" y="-20%" width="140%" height="140%">
                    <feGaussianBlur stdDeviation="4" result="blur" />
                    <feComposite in="SourceGraphic" in2="blur" operator="over" />
                </filter>
                <filter id="neon-glow-red" x="-50%" y="-50%" width="200%" height="200%">
                    <feGaussianBlur stdDeviation="8" result="blur" />
                    <feComposite in="SourceGraphic" in2="blur" operator="over" />
                </filter>
                
                <!-- 箭头标记 -->
                <marker id="arrow" viewBox="0 0 10 10" refX="5" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse">
                    <path d="M 0 0 L 10 5 L 0 10 z" fill="#EF4444" />
                </marker>
            </defs>

            <!-- 静态场景:楼梯 -->
            <g id="staircase">
                <!-- 楼梯主体 -->
                <path d="M -100 450 L 500 450 L 500 250 L 1100 250 L 1100 650 L -100 650 Z" 
                      fill="url(#stair-pattern)" 
                      stroke="var(--stair-stroke)" 
                      stroke-width="2"/>
                <!-- 楼梯发光边缘 -->
                <path d="M -100 450 L 500 450 L 500 250 L 1100 250" 
                      fill="none" 
                      stroke="var(--stair-stroke)" 
                      stroke-width="3" 
                      filter="url(#neon-glow-blue)"/>
            </g>

            <!-- 动态受力指示器 (阻抗反作用力) -->
            <g id="force-indicator" opacity="0" transition="opacity 0.2s">
                <path d="M 540 350 L 510 350" stroke="#EF4444" stroke-width="4" marker-end="url(#arrow)" filter="url(#neon-glow-red)"/>
                <text x="550" y="355" fill="#EF4444" font-size="12" font-family="monospace">反作用力触发折叠</text>
            </g>

            <!-- 动态场景:机器人底盘 -->
            <g id="robot">
                <!-- 履带外层 (黑色背景) -->
                <path id="track-bg" fill="none" stroke="var(--track-outer)" stroke-width="56" stroke-linejoin="round" stroke-linecap="round"/>
                
                <!-- 履带纹理 (滚动动画层) -->
                <path id="track-tread" fill="none" stroke="var(--track-tread)" stroke-width="56" stroke-linejoin="round" stroke-linecap="round" stroke-dasharray="6 14"/>
                
                <!-- 履带内圈镂空 -->
                <path id="track-inner" fill="none" stroke="var(--track-inner)" stroke-width="44" stroke-linejoin="round" stroke-linecap="round"/>
                
                <!-- 内部刚性支撑舱段 (3段) -->
                <path id="chassis-links" fill="none" stroke="#334155" stroke-width="16" stroke-linejoin="round" stroke-linecap="round"/>
                
                <!-- 铰接点 / 驱动轮 -->
                <circle id="j1" r="10" fill="var(--joint-base)" />
                <circle id="j2" r="10" fill="var(--joint-base)" />
                <circle id="j3" r="10" fill="var(--joint-base)" />
                <circle id="j4" r="10" fill="var(--joint-base)" />

                <!-- 弯折应力发光指示层 -->
                <circle id="glow-j2" r="10" fill="#EF4444" opacity="0" filter="url(#neon-glow-red)" pointer-events="none"/>
                <circle id="glow-j3" r="10" fill="#EF4444" opacity="0" filter="url(#neon-glow-red)" pointer-events="none"/>
            </g>

            <!-- 状态标签追踪器 -->
            <g id="state-tracker" opacity="0">
                <line id="tracker-line" x1="0" y1="0" x2="0" y2="0" stroke="var(--track-tread)" stroke-width="1" stroke-dasharray="2 2"/>
                <rect id="tracker-bg" x="0" y="0" width="120" height="24" fill="var(--hud-bg)" stroke="var(--track-tread)" stroke-width="1" rx="4"/>
                <text id="tracker-text" x="60" y="16" fill="var(--track-tread)" font-size="10" text-anchor="middle" font-weight="bold">最大抓地力贴合</text>
            </g>
        </svg>
    </div>

    <script>
        /**
         * 运动学参数定义
         */
        const L = 80;            // 单个舱段的刚性长度
        const R = 28;            // 履带的包络厚度半径
        const clWallX = 500 - R; // 立面中心线X
        const clGroundY = 450 - R; // 平地中心线Y
        const clTopY = 250 - R;  // 台阶顶面中心线Y
        
        const slider = document.getElementById('timeline-slider');
        let s = parseFloat(slider.value);
        let speed = 2.5;         // 自动播放速度
        let treadOffset = 0;     // 履带滚动偏移量
        let isAutoPlaying = true;
        let interactionTimeout = null;

        // 根据路径坐标s获取车头P4的位置
        function getPosFromS(s) {
            const wallHeight = clGroundY - clTopY;
            
            // 在平地
            if (s <= clWallX) {
                return { x: s, y: clGroundY };
            }
            // 正在爬墙
            if (s <= clWallX + wallHeight) {
                return { x: clWallX, y: clGroundY - (s - clWallX) };
            }
            // 越过台阶在顶面
            return { x: clWallX + (s - clWallX - wallHeight), y: clTopY };
        }

        // 给定前面一个节点的位置,求解路径上严格距离为 L 的后一个节点位置
        function findPreviousJoint(p, L) {
            let candidates = [];
            const eps = 0.01;

            // 1. 顶面相交
            if (p.y <= clTopY + eps) {
                let dy = p.y - clTopY;
                if (L*L >= dy*dy) {
                    let dx = Math.sqrt(L*L - dy*dy);
                    let x = p.x - dx;
                    if (x >= clWallX - eps) candidates.push({x: x, y: clTopY});
                }
            }

            // 2. 立面相交 (后一个点在墙上)
            let dxWall = p.x - clWallX;
            if (L*L >= dxWall*dxWall) {
                let dy = Math.sqrt(L*L - dxWall*dxWall);
                let y = p.y + dy; // Y变大表示向下(往回)
                if (y >= clTopY - eps && y <= clGroundY + eps) {
                    candidates.push({x: clWallX, y: y});
                }
            }

            // 3. 平地相交
            let dyGround = p.y - clGroundY;
            if (L*L >= dyGround*dyGround) {
                let dx = Math.sqrt(L*L - dyGround*dyGround);
                let x = p.x - dx;
                if (x <= clWallX + eps) candidates.push({x: x, y: clGroundY});
            }

            // 将二维坐标映射为一维路径距离以便比较谁在"后面"
            function getPathPos(pt) {
                if (pt.y >= clGroundY - eps && pt.x <= clWallX + eps) return pt.x;
                if (Math.abs(pt.x - clWallX) <= eps && pt.y <= clGroundY + eps && pt.y >= clTopY - eps) return clWallX + (clGroundY - pt.y);
                if (pt.y <= clTopY + eps && pt.x >= clWallX - eps) return clWallX + (clGroundY - clTopY) + (pt.x - clWallX);
                return -999;
            }

            let pPos = getPathPos(p);
            let best = null;
            let minPos = -Infinity;

            for (let c of candidates) {
                let cPos = getPathPos(c);
                if (cPos < pPos - eps) { // 必须严格在当前点后方
                    if (cPos > minPos) {
                        minPos = cPos;
                        best = c;
                    }
                }
            }
            return best || { x: p.x - L, y: p.y }; // 兜底返回水平后退
        }

        // 更新SVG节点位置
        function setJoint(id, p) {
            const el = document.getElementById(id);
            el.setAttribute('cx', p.x);
            el.setAttribute('cy', p.y);
        }

        // 计算关节处的弯折角度并更新发光效果
        function updateGlow(P_prev, P_curr, P_next, glowId, jointId) {
            let dx1 = P_prev.x - P_curr.x;
            let dy1 = P_prev.y - P_curr.y;
            let dx2 = P_next.x - P_curr.x;
            let dy2 = P_next.y - P_curr.y;
            
            let dot = dx1*dx2 + dy1*dy2;
            let len1 = Math.sqrt(dx1*dx1 + dy1*dy1);
            let len2 = Math.sqrt(dx2*dx2 + dy2*dy2);
            
            let cosVal = dot / (len1 * len2);
            cosVal = Math.max(-1, Math.min(1, cosVal)); // 防溢出
            
            let angle = Math.acos(cosVal); // 直线为 PI
            let bendAmount = (Math.PI - angle) / (Math.PI / 2); // 直线=0, 90度=1
            bendAmount = Math.max(0, bendAmount);

            const glowEl = document.getElementById(glowId);
            const jointEl = document.getElementById(jointId);

            if (bendAmount > 0.1) {
                glowEl.setAttribute('opacity', bendAmount * 0.8);
                glowEl.setAttribute('cx', P_curr.x);
                glowEl.setAttribute('cy', P_curr.y);
                glowEl.setAttribute('r', 10 + bendAmount * 15);
                jointEl.setAttribute('fill', 'var(--joint-bend)');
            } else {
                glowEl.setAttribute('opacity', 0);
                jointEl.setAttribute('fill', 'var(--joint-base)');
            }
            return bendAmount;
        }

        // 核心渲染循环
        function renderLoop() {
            if (isAutoPlaying) {
                s += speed;
                if (s > 950) s = 50; // 无缝循环
                slider.value = s;
            } else {
                s = parseFloat(slider.value);
            }

            // 逆向解算四点坐标
            let P4 = getPosFromS(s);
            let P3 = findPreviousJoint(P4, L);
            let P2 = findPreviousJoint(P3, L);
            let P1 = findPreviousJoint(P2, L);

            // 构建路径字符串
            let pathD = `M ${P1.x} ${P1.y} L ${P2.x} ${P2.y} L ${P3.x} ${P3.y} L ${P4.x} ${P4.y}`;
            
            document.getElementById('track-bg').setAttribute('d', pathD);
            document.getElementById('track-tread').setAttribute('d', pathD);
            document.getElementById('track-inner').setAttribute('d', pathD);
            document.getElementById('chassis-links').setAttribute('d', pathD);

            setJoint('j1', P1);
            setJoint('j2', P2);
            setJoint('j3', P3);
            setJoint('j4', P4);

            // 更新履带滚动动画
            treadOffset -= 1.5;
            document.getElementById('track-tread').setAttribute('stroke-dashoffset', treadOffset);

            // 更新关节弯折发光状态 (TRIZ 视觉引导核心)
            let bend2 = updateGlow(P1, P2, P3, 'glow-j2', 'j2');
            let bend3 = updateGlow(P2, P3, P4, 'glow-j3', 'j3');
            
            // 阻抗力提示箭头控制
            const forceIndicator = document.getElementById('force-indicator');
            if (P4.x === clWallX && P4.y < clGroundY && P4.y > clTopY + 50) {
                forceIndicator.setAttribute('opacity', '1');
            } else {
                forceIndicator.setAttribute('opacity', '0');
            }

            // 动态状态跟踪框控制
            const tracker = document.getElementById('state-tracker');
            const trackerBg = document.getElementById('tracker-bg');
            const trackerText = document.getElementById('tracker-text');
            const trackerLine = document.getElementById('tracker-line');
            
            let maxBend = Math.max(bend2, bend3);
            if (maxBend > 0.5) {
                tracker.setAttribute('opacity', '1');
                
                // 定位在发生最大弯折的关节上方
                let targetJoint = bend3 > bend2 ? P3 : P2;
                let tx = targetJoint.x;
                let ty = targetJoint.y - 80;
                
                trackerBg.setAttribute('x', tx - 60);
                trackerBg.setAttribute('y', ty);
                trackerText.setAttribute('x', tx);
                trackerText.setAttribute('y', ty + 16);
                
                trackerLine.setAttribute('x1', tx);
                trackerLine.setAttribute('y1', ty + 24);
                trackerLine.setAttribute('x2', targetJoint.x);
                trackerLine.setAttribute('y2', targetJoint.y - 10);
            } else {
                tracker.setAttribute('opacity', '0');
            }

            requestAnimationFrame(renderLoop);
        }

        // 交互控制逻辑
        slider.addEventListener('input', () => {
            isAutoPlaying = false;
            clearTimeout(interactionTimeout);
        });

        slider.addEventListener('change', () => {
            clearTimeout(interactionTimeout);
            interactionTimeout = setTimeout(() => {
                isAutoPlaying = true;
            }, 2000); // 停止操作2秒后恢复自动播放
        });

        // 启动动画 (满足页面加载后自动播放的要求)
        window.addEventListener('DOMContentLoaded', () => {
            renderLoop();
        });

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