分享图
动画工坊
引擎就绪
<!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-base: #07090f;
            --bg-grid: rgba(30, 41, 59, 0.5);
            --silicone-core: #1e293b;
            --silicone-edge: #334155;
            --air-high: #00f0ff;
            --air-low: #0f172a;
            --text-main: #94a3b8;
            --text-highlight: #e2e8f0;
            --accent-orange: #ff5500;
        }

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

        body {
            background-color: var(--bg-base);
            color: var(--text-main);
            font-family: 'SF Pro Display', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
            height: 100vh;
            width: 100vw;
            overflow: hidden;
            display: flex;
            justify-content: center;
            align-items: center;
            background-image: 
                linear-gradient(var(--bg-grid) 1px, transparent 1px),
                linear-gradient(90deg, var(--bg-grid) 1px, transparent 1px);
            background-size: 40px 40px;
            background-position: center center;
        }

        /* 纯净的动画主容器 */
        #animation-container {
            position: relative;
            width: 100%;
            height: 100%;
            display: flex;
            justify-content: center;
            align-items: center;
            z-index: 10;
        }

        svg {
            width: 100%;
            height: 100%;
            max-width: 1200px;
            max-height: 900px;
            filter: drop-shadow(0 0 30px rgba(0, 240, 255, 0.05));
        }

        /* 绝对定位的极简文字说明层,严格遵守不遮挡核心动画的规则 */
        .hud-overlay {
            position: absolute;
            pointer-events: none;
            z-index: 20;
            font-size: 12px;
            line-height: 1.6;
            letter-spacing: 0.5px;
        }

        .hud-top-left { top: 30px; left: 40px; }
        .hud-top-right { top: 30px; right: 40px; text-align: right; }
        .hud-bottom-left { bottom: 30px; left: 40px; }
        .hud-bottom-right { bottom: 30px; right: 40px; text-align: right; }

        .hud-title {
            font-size: 14px;
            font-weight: 600;
            color: var(--text-highlight);
            margin-bottom: 8px;
            text-transform: uppercase;
            letter-spacing: 2px;
        }

        .hud-data {
            font-family: 'JetBrains Mono', 'Courier New', monospace;
            color: var(--air-high);
            font-weight: 500;
        }

        .hud-separator {
            width: 20px;
            height: 1px;
            background: var(--text-main);
            margin: 8px 0;
            opacity: 0.3;
        }
        .hud-top-right .hud-separator, .hud-bottom-right .hud-separator {
            margin-left: auto;
        }

        /* SVG 内部样式 */
        .snake-spine {
            fill: none;
            stroke: var(--silicone-core);
            stroke-width: 4;
            stroke-linecap: round;
        }

        .snake-skin {
            fill: rgba(30, 41, 59, 0.3);
            stroke: var(--silicone-edge);
            stroke-width: 1.5;
            backdrop-filter: blur(4px);
        }

        .chamber {
            transition: fill 0.1s;
        }

        .valve-line {
            fill: none;
            stroke: var(--silicone-edge);
            stroke-width: 2;
            stroke-dasharray: 4 4;
            opacity: 0.5;
        }

        .flow-particle {
            fill: var(--air-high);
            filter: blur(1px);
        }

        /* 发光滤镜 */
        .glow {
            filter: drop-shadow(0 0 8px rgba(0, 240, 255, 0.6));
        }
        .glow-orange {
            filter: drop-shadow(0 0 8px rgba(255, 85, 0, 0.6));
        }
    </style>
</head>
<body>

    <!-- 极简文字说明层 (放置在角落) -->
    <div class="hud-overlay hud-top-left">
        <div class="hud-title">System IFR // 气动软体架构</div>
        <div>消除机械铰接磨损,实现理想最终解</div>
        <div class="hud-separator"></div>
        <div>利用柔性体内部不对称气压分布</div>
        <div>产生宏观形变,实现蠕动波驱动</div>
    </div>

    <div class="hud-overlay hud-top-right">
        <div class="hud-title">Operational Data</div>
        <div>PUMP_PRESSURE: <span class="hud-data">2.50 BAR</span></div>
        <div>VALVE_FREQ: <span class="hud-data">20.0 HZ</span></div>
        <div>STATUS: <span class="hud-data" style="color: #00ff66;">NOMINAL</span></div>
    </div>

    <div class="hud-overlay hud-bottom-left">
        <div class="hud-title">Chamber Status</div>
        <div>左气腔膨胀 → 躯干右弯</div>
        <div>交替充排气产生纵向推力</div>
        <div style="display: flex; gap: 8px; margin-top: 4px;">
            <span style="display:inline-block;width:10px;height:10px;background:var(--air-high);border-radius:2px;box-shadow:0 0 5px var(--air-high);"></span>
            <span>充气建压区</span>
            <span style="display:inline-block;width:10px;height:10px;background:var(--air-low);border-radius:2px;border:1px solid #334155;"></span>
            <span>排气释压区</span>
        </div>
    </div>

    <div class="hud-overlay hud-bottom-right">
        <div class="hud-title">Control Sequence</div>
        <div id="seq-step-1">1. 微型泵建压</div>
        <div id="seq-step-2">2. 阀岛开启 (交替)</div>
        <div id="seq-step-3">3. 弯曲波传递</div>
        <div id="seq-step-4">4. 持续蠕动</div>
    </div>

    <!-- 动画核心容器 -->
    <div id="animation-container">
        <svg id="visualizer" viewBox="0 0 1000 800" preserveAspectRatio="xMidYMid meet">
            <defs>
                <linearGradient id="pumpGrad" x1="0%" y1="0%" x2="100%" y2="100%">
                    <stop offset="0%" stop-color="#1e293b" />
                    <stop offset="100%" stop-color="#0f172a" />
                </linearGradient>
                <radialGradient id="chamberGlow" cx="50%" cy="50%" r="50%">
                    <stop offset="0%" stop-color="#00f0ff" stop-opacity="0.8"/>
                    <stop offset="100%" stop-color="#00f0ff" stop-opacity="0"/>
                </radialGradient>
            </defs>

            <!-- 底部基站:泵和电磁阀岛 -->
            <g transform="translate(500, 720)">
                <!-- 气泵 -->
                <rect x="-80" y="-30" width="160" height="60" rx="6" fill="url(#pumpGrad)" stroke="#334155" stroke-width="2"/>
                <text x="0" y="5" font-family="monospace" font-size="12" fill="#64748b" text-anchor="middle">MICRO-PUMP SYSTEM</text>
                
                <!-- 泵工作指示灯 -->
                <circle cx="-60" cy="0" r="4" fill="#00ff66" class="glow" id="pump-led"/>
                
                <!-- 连接气管 -->
                <path d="M -30, -30 C -30,-60 -20,-80 0,-100" class="valve-line"/>
                <path d="M 30, -30 C 30,-60 20,-80 0,-100" class="valve-line"/>

                <!-- 电磁阀 (L/R) -->
                <rect x="-45" y="-30" width="30" height="10" fill="#0f172a" stroke="#475569"/>
                <rect x="15" y="-30" width="30" height="10" fill="#0f172a" stroke="#475569"/>
                <circle cx="-30" cy="-25" r="2" fill="#ff5500" id="valve-led-l"/>
                <circle cx="30" cy="-25" r="2" fill="#ff5500" id="valve-led-r"/>
            </g>

            <!-- 软体蛇实体组 -->
            <!-- 初始位于画面中央,由 JS 动态生成控制 -->
            <g id="snake-body"></g>

        </svg>
    </div>

    <script>
        document.addEventListener('DOMContentLoaded', () => {
            const svgNS = "http://www.w3.org/2000/svg";
            const snakeContainer = document.getElementById('snake-body');
            
            // --- 物理与几何参数 ---
            const NUM_SEGMENTS = 24;       // 节段数
            const SEG_LENGTH = 20;         // 每节长度
            const BASE_WIDTH = 12;         // 脊椎半宽
            const CHAMBER_MAX_EXPAND = 22; // 气腔最大膨胀宽度
            const WAVE_SPEED = 6.0;        // 蠕动波速
            const WAVE_FREQ = 0.45;        // 空间频率
            
            let time = 0;
            let segments = [];

            // --- 初始化 DOM 结构 ---
            function initSnake() {
                // 生成皮肤轮廓路径
                const skinPath = document.createElementNS(svgNS, 'path');
                skinPath.setAttribute('class', 'snake-skin');
                skinPath.setAttribute('id', 'skin-outline');
                snakeContainer.appendChild(skinPath);

                // 生成中央脊椎线
                const spinePath = document.createElementNS(svgNS, 'path');
                spinePath.setAttribute('class', 'snake-spine');
                spinePath.setAttribute('id', 'central-spine');
                snakeContainer.appendChild(spinePath);

                // 生成左右独立气腔
                for (let i = 0; i < NUM_SEGMENTS; i++) {
                    const group = document.createElementNS(svgNS, 'g');
                    
                    // 左气腔
                    const leftChamber = document.createElementNS(svgNS, 'path');
                    leftChamber.setAttribute('class', 'chamber');
                    leftChamber.setAttribute('stroke', '#334155');
                    leftChamber.setAttribute('stroke-width', '1');
                    
                    // 右气腔
                    const rightChamber = document.createElementNS(svgNS, 'path');
                    rightChamber.setAttribute('class', 'chamber');
                    rightChamber.setAttribute('stroke', '#334155');
                    rightChamber.setAttribute('stroke-width', '1');

                    // 气腔内部分隔肋 (视觉细节)
                    const rib = document.createElementNS(svgNS, 'line');
                    rib.setAttribute('stroke', '#1e293b');
                    rib.setAttribute('stroke-width', '2');

                    group.appendChild(leftChamber);
                    group.appendChild(rightChamber);
                    group.appendChild(rib);
                    
                    segments.push({
                        group: group,
                        left: leftChamber,
                        right: rightChamber,
                        rib: rib
                    });
                    
                    snakeContainer.appendChild(group);
                }
            }

            // --- 核心运动学计算与渲染 ---
            function updateKinematics(t) {
                // 整体位置偏移,保持在画面中央偏下
                let cx = 500;
                let cy = 620; 
                let baseAngle = 0; 
                
                let points = [];
                points.push({x: cx, y: cy, angle: baseAngle, curvature: 0});

                // 1. 计算脊椎Serpenoid曲线
                for (let i = 1; i <= NUM_SEGMENTS; i++) {
                    // 构造行波方程
                    // 引入包络函数,使头尾摆幅较小,中间摆幅大,更符合真实物理约束
                    const envelope = Math.sin((i / NUM_SEGMENTS) * Math.PI);
                    const phase = i * WAVE_FREQ - t * WAVE_SPEED;
                    
                    // 局部曲率(对应气压差)
                    const curvature = Math.sin(phase) * 0.45 * envelope;
                    
                    baseAngle += curvature;
                    cx += Math.sin(baseAngle) * SEG_LENGTH;
                    cy -= Math.cos(baseAngle) * SEG_LENGTH; // 向上延伸
                    
                    points.push({x: cx, y: cy, angle: baseAngle, curvature: curvature});
                }

                // 2. 更新 SVG 路径
                let spineD = `M ${points[0].x} ${points[0].y}`;
                let leftContour = [];
                let rightContour = [];

                for (let i = 0; i < NUM_SEGMENTS; i++) {
                    let p1 = points[i];
                    let p2 = points[i+1];
                    let cur = p2.curvature;
                    
                    spineD += ` L ${p2.x} ${p2.y}`;

                    // 计算法向量
                    let nx = Math.cos(p1.angle);
                    let ny = Math.sin(p1.angle);

                    // 气腔膨胀逻辑:曲率为正->右弯->左侧膨胀;曲率为负->左弯->右侧膨胀
                    let leftPressure = Math.max(0, cur) * 2.5; // 标准化压力 0~1 左右
                    let rightPressure = Math.max(0, -cur) * 2.5;

                    let leftWidth = BASE_WIDTH + leftPressure * CHAMBER_MAX_EXPAND;
                    let rightWidth = BASE_WIDTH + rightPressure * CHAMBER_MAX_EXPAND;

                    // 计算轮廓顶点
                    let lpx = p1.x - nx * leftWidth;
                    let lpy = p1.y - ny * leftWidth;
                    let rpx = p1.x + nx * rightWidth;
                    let rpy = p1.y + ny * rightWidth;
                    
                    leftContour.push({x: lpx, y: lpy});
                    rightContour.push({x: rpx, y: rpy});

                    // 渲染单个气腔形变
                    // 为了平滑,需要知道下一节的法向
                    let nx2 = Math.cos(p2.angle);
                    let ny2 = Math.sin(p2.angle);
                    let leftWidth2 = BASE_WIDTH + Math.max(0, points[i+1]?.curvature || 0) * 2.5 * CHAMBER_MAX_EXPAND;
                    let rightWidth2 = BASE_WIDTH + Math.max(0, -(points[i+1]?.curvature || 0)) * 2.5 * CHAMBER_MAX_EXPAND;
                    
                    let lpx2 = p2.x - nx2 * leftWidth2;
                    let lpy2 = p2.y - ny2 * leftWidth2;
                    let rpx2 = p2.x + nx2 * rightWidth2;
                    let rpy2 = p2.y + ny2 * rightWidth2;

                    // 构造气腔的多边形路径
                    let leftD = `M ${p1.x} ${p1.y} L ${lpx} ${lpy} L ${lpx2} ${lpy2} L ${p2.x} ${p2.y} Z`;
                    let rightD = `M ${p1.x} ${p1.y} L ${rpx} ${rpy} L ${rpx2} ${rpy2} L ${p2.x} ${p2.y} Z`;

                    // 设置颜色(模拟气压)
                    let colHigh = [0, 240, 255];
                    let colLow = [15, 23, 42];
                    
                    function blendColor(c1, c2, ratio) {
                        ratio = Math.min(1, Math.max(0, ratio));
                        return `rgb(${Math.round(c2[0] + (c1[0]-c2[0])*ratio)}, 
                                    ${Math.round(c2[1] + (c1[1]-c2[1])*ratio)}, 
                                    ${Math.round(c2[2] + (c1[2]-c2[2])*ratio)})`;
                    }

                    segments[i].left.setAttribute('d', leftD);
                    segments[i].left.setAttribute('fill', blendColor(colHigh, colLow, leftPressure));
                    if(leftPressure > 0.5) segments[i].left.setAttribute('class', 'chamber glow');
                    else segments[i].left.setAttribute('class', 'chamber');

                    segments[i].right.setAttribute('d', rightD);
                    segments[i].right.setAttribute('fill', blendColor(colHigh, colLow, rightPressure));
                    if(rightPressure > 0.5) segments[i].right.setAttribute('class', 'chamber glow');
                    else segments[i].right.setAttribute('class', 'chamber');

                    // 更新肋骨线
                    segments[i].rib.setAttribute('x1', lpx);
                    segments[i].rib.setAttribute('y1', lpy);
                    segments[i].rib.setAttribute('x2', rpx);
                    segments[i].rib.setAttribute('y2', rpy);
                }

                // 渲染脊椎和外皮
                document.getElementById('central-spine').setAttribute('d', spineD);
                
                let skinD = `M ${leftContour[0].x} ${leftContour[0].y}`;
                for(let i=1; i<NUM_SEGMENTS; i++) skinD += ` L ${leftContour[i].x} ${leftContour[i].y}`;
                // 蛇头圆滑过渡
                let head = points[NUM_SEGMENTS];
                skinD += ` C ${head.x - Math.cos(head.angle)*20} ${head.y - Math.sin(head.angle)*20},
                               ${head.x + Math.cos(head.angle)*20} ${head.y + Math.sin(head.angle)*20},
                               ${rightContour[NUM_SEGMENTS-1].x} ${rightContour[NUM_SEGMENTS-1].y}`;
                for(let i=NUM_SEGMENTS-2; i>=0; i--) skinD += ` L ${rightContour[i].x} ${rightContour[i].y}`;
                // 蛇尾闭合
                skinD += ` Z`;
                document.getElementById('skin-outline').setAttribute('d', skinD);

                // --- 同步底部阀门状态指示灯 ---
                // 取根部节段的压力状态来模拟阀门开闭
                let baseCurvature = points[1].curvature;
                const ledL = document.getElementById('valve-led-l');
                const ledR = document.getElementById('valve-led-r');
                
                if (baseCurvature > 0.1) {
                    ledL.setAttribute('fill', '#00f0ff');
                    ledL.setAttribute('class', 'glow');
                    ledR.setAttribute('fill', '#ff5500');
                    ledR.setAttribute('class', '');
                } else if (baseCurvature < -0.1) {
                    ledL.setAttribute('fill', '#ff5500');
                    ledL.setAttribute('class', '');
                    ledR.setAttribute('fill', '#00f0ff');
                    ledR.setAttribute('class', 'glow');
                }
            }

            // --- 粒子系统:模拟气流 ---
            const particles = [];
            function spawnParticle() {
                if(Math.random() > 0.3) return; // 节流
                
                const p = document.createElementNS(svgNS, 'circle');
                p.setAttribute('r', '1.5');
                p.setAttribute('class', 'flow-particle');
                
                // 随机选择左管或右管
                const isLeft = Math.random() > 0.5;
                const startX = isLeft ? 470 : 530;
                const startY = 690;
                const endX = 500;
                const endY = 620;

                snakeContainer.appendChild(p);
                particles.push({
                    el: p,
                    sx: startX, sy: startY,
                    ex: endX, ey: endY,
                    progress: 0,
                    speed: 0.02 + Math.random() * 0.02
                });
            }

            function updateParticles() {
                for(let i = particles.length - 1; i >= 0; i--) {
                    let p = particles[i];
                    p.progress += p.speed;
                    
                    if(p.progress >= 1) {
                        p.el.remove();
                        particles.splice(i, 1);
                        continue;
                    }
                    
                    // 简单的贝塞尔曲线轨迹
                    let t = p.progress;
                    let curX = (1-t)*(1-t)*p.sx + 2*(1-t)*t*p.sx + t*t*p.ex;
                    let curY = (1-t)*(1-t)*p.sy + 2*(1-t)*t*640 + t*t*p.ey; // 控制点y使其有弧度

                    p.el.setAttribute('cx', curX);
                    p.el.setAttribute('cy', curY);
                    p.el.setAttribute('opacity', 1 - t);
                }
            }

            // --- 主渲染循环 ---
            let lastTime = performance.now();
            function render(currentTime) {
                const dt = (currentTime - lastTime) / 1000; // 秒
                lastTime = currentTime;
                time += dt;

                updateKinematics(time);
                spawnParticle();
                updateParticles();

                // 细微闪烁泵指示灯模拟高频工作
                document.getElementById('pump-led').style.opacity = 0.8 + Math.sin(currentTime * 0.05) * 0.2;

                requestAnimationFrame(render);
            }

            // 初始化并自动启动
            initSnake();
            requestAnimationFrame(render);
        });
    </script>
</body>
</html>
积分规则:第一轮对话扣减8分,后续每轮扣6分