分享图
动画工坊
引擎就绪
<!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: #07090f;
            --grid-color: rgba(30, 40, 70, 0.4);
            
            --color-safe: #00f0ff;
            --color-safe-dim: rgba(0, 240, 255, 0.2);
            --color-safe-glow: rgba(0, 240, 255, 0.6);
            
            --color-warn: #ffb800;
            
            --color-danger: #ff0055;
            --color-danger-dim: rgba(255, 0, 85, 0.2);
            --color-danger-glow: rgba(255, 0, 85, 0.6);
            
            --text-main: #a3adc2;
            --text-bright: #ffffff;
            
            --font-mono: 'Fira Code', 'Roboto Mono', 'Consolas', monospace;
            --font-sans: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
        }

        body, html {
            margin: 0;
            padding: 0;
            width: 100%;
            height: 100%;
            background-color: var(--bg-color);
            display: flex;
            justify-content: center;
            align-items: center;
            font-family: var(--font-sans);
            overflow: hidden;
            color: var(--text-main);
        }

        .anim-container {
            width: 100%;
            height: 100%;
            max-width: 1200px;
            max-height: 800px;
            position: relative;
            box-shadow: inset 0 0 100px rgba(0, 0, 0, 0.8);
        }

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

        /* Typography */
        .text-sm { font-size: 11px; letter-spacing: 1px; }
        .text-md { font-size: 14px; font-weight: 500; }
        .text-lg { font-size: 24px; font-weight: bold; font-family: var(--font-mono); }
        .font-mono { font-family: var(--font-mono); }
        
        .label { fill: var(--text-main); text-anchor: start; alignment-baseline: middle; }
        .label-right { fill: var(--text-main); text-anchor: end; alignment-baseline: middle; }
        .value { fill: var(--text-bright); text-anchor: end; alignment-baseline: middle; }
        
        /* Elements */
        .sensor-box {
            fill: rgba(15, 20, 35, 0.8);
            stroke: rgba(255,255,255,0.1);
            stroke-width: 1;
            transition: all 0.3s ease;
        }

        .plc-core {
            fill: rgba(10, 15, 30, 0.9);
            stroke: var(--color-safe);
            stroke-width: 2;
            transition: stroke 0.3s ease;
        }

        .plc-inner {
            fill: none;
            stroke: var(--color-safe-dim);
            stroke-width: 1;
            stroke-dasharray: 4 4;
        }

        /* Data Streams */
        .pipe-base {
            fill: none;
            stroke: rgba(255, 255, 255, 0.05);
            stroke-width: 6;
            stroke-linecap: round;
        }

        .pipe-flow {
            fill: none;
            stroke-width: 2;
            stroke-linecap: round;
            stroke-dasharray: 8 20;
            animation: flow 1s linear infinite;
            transition: stroke 0.3s ease;
        }

        .pipe-core {
            fill: none;
            stroke: #fff;
            stroke-width: 1;
            opacity: 0.3;
        }

        @keyframes flow {
            to { stroke-dashoffset: -28; }
        }

        @keyframes rotate {
            from { transform: rotate(0deg); }
            to { transform: rotate(360deg); }
        }

        @keyframes pulse-danger {
            0% { filter: drop-shadow(0 0 5px var(--color-danger)); stroke: var(--color-danger); }
            50% { filter: drop-shadow(0 0 20px var(--color-danger)); stroke: #ff4d79; }
            100% { filter: drop-shadow(0 0 5px var(--color-danger)); stroke: var(--color-danger); }
        }

        /* States */
        .state-safe .sensor-box { stroke: var(--color-safe-dim); }
        .state-safe .pipe-flow { stroke: var(--color-safe); filter: drop-shadow(0 0 5px var(--color-safe)); }
        
        .state-danger .sensor-box { stroke: var(--color-danger); fill: rgba(255, 0, 85, 0.1); }
        .state-danger .pipe-flow { stroke: var(--color-danger); filter: drop-shadow(0 0 8px var(--color-danger)); }
        .state-danger .val-text { fill: var(--color-danger); font-weight: bold; }

        .vote-node { fill: #1a2235; stroke: #334155; stroke-width: 2; transition: all 0.3s; }
        .vote-node.active-safe { fill: var(--color-safe); stroke: var(--color-safe-glow); box-shadow: 0 0 10px var(--color-safe); }
        .vote-node.active-danger { fill: var(--color-danger); stroke: var(--color-danger-glow); }

        .sys-danger .plc-core { animation: pulse-danger 1s infinite; }
        .sys-danger .output-flow { stroke: var(--color-danger); filter: drop-shadow(0 0 10px var(--color-danger)); }
        
        .ring { transform-origin: 550px 350px; }
        .ring-spin-slow { animation: rotate 20s linear infinite; }
        .ring-spin-fast { animation: rotate 10s linear infinite reverse; }

        /* UI Overlays */
        .info-panel {
            position: absolute;
            bottom: 20px;
            left: 30px;
            background: rgba(10, 15, 25, 0.8);
            border-left: 3px solid var(--color-safe);
            padding: 12px 20px;
            backdrop-filter: blur(4px);
            pointer-events: none;
        }
        
        .info-title { color: #fff; margin: 0 0 5px 0; font-size: 13px; font-weight: 600; letter-spacing: 1px;}
        .info-desc { color: var(--text-main); margin: 0; font-size: 11px; line-height: 1.6; max-width: 300px;}
        
        .stage-indicator {
            position: absolute;
            top: 20px;
            left: 50%;
            transform: translateX(-50%);
            font-size: 12px;
            color: var(--color-safe);
            background: rgba(0, 240, 255, 0.1);
            padding: 6px 16px;
            border-radius: 20px;
            border: 1px solid var(--color-safe-dim);
            letter-spacing: 2px;
            transition: all 0.5s;
        }
    </style>
</head>
<body>

    <div class="anim-container">
        <div class="stage-indicator font-mono" id="stageText">SYSTEM NORMAL - 监测中</div>

        <svg viewBox="0 0 1100 700" preserveAspectRatio="xMidYMid meet">
            <defs>
                <pattern id="grid" width="40" height="40" patternUnits="userSpaceOnUse">
                    <path d="M 40 0 L 0 0 0 40" fill="none" stroke="var(--grid-color)" stroke-width="0.5" />
                    <circle cx="0" cy="0" r="1" fill="var(--grid-color)" />
                </pattern>
                
                <linearGradient id="meter-grad" x1="0%" y1="0%" x2="0%" y2="100%">
                    <stop offset="0%" stop-color="var(--color-danger)" />
                    <stop offset="50%" stop-color="var(--color-warn)" />
                    <stop offset="100%" stop-color="var(--color-safe)" />
                </linearGradient>

                <filter id="glow-core">
                    <feGaussianBlur stdDeviation="4" result="coloredBlur"/>
                    <feMerge>
                        <feMergeNode in="coloredBlur"/>
                        <feMergeNode in="SourceGraphic"/>
                    </feMerge>
                </filter>
            </defs>

            <!-- Background -->
            <rect width="100%" height="100%" fill="url(#grid)" />
            
            <!-- Decorative Tech Lines -->
            <path d="M 50 50 L 150 50 L 170 70 L 1050 70 L 1070 50 L 1070 20" fill="none" stroke="rgba(255,255,255,0.05)" stroke-width="1" />
            <text x="55" y="45" class="text-sm font-mono" fill="rgba(255,255,255,0.3)">SYS_FUSION_V2.1</text>
            <text x="1065" y="45" class="text-sm font-mono label-right" fill="rgba(255,255,255,0.3)">CYCLE: 100ms</text>

            <!-- Data Pipes -->
            <g id="pipes">
                <!-- S1 to PLC -->
                <path d="M 220 180 C 350 180, 380 290, 480 290" class="pipe-base" />
                <path d="M 220 180 C 350 180, 380 290, 480 290" class="pipe-flow" id="flow1" />
                <path d="M 220 180 C 350 180, 380 290, 480 290" class="pipe-core" />
                
                <!-- S2 to PLC -->
                <path d="M 220 280 C 330 280, 380 330, 480 330" class="pipe-base" />
                <path d="M 220 280 C 330 280, 380 330, 480 330" class="pipe-flow" id="flow2" />
                <path d="M 220 280 C 330 280, 380 330, 480 330" class="pipe-core" />

                <!-- S3 to PLC -->
                <path d="M 220 420 C 330 420, 380 370, 480 370" class="pipe-base" />
                <path d="M 220 420 C 330 420, 380 370, 480 370" class="pipe-flow" id="flow3" />
                <path d="M 220 420 C 330 420, 380 370, 480 370" class="pipe-core" />

                <!-- S4 to PLC -->
                <path d="M 220 520 C 350 520, 380 410, 480 410" class="pipe-base" />
                <path d="M 220 520 C 350 520, 380 410, 480 410" class="pipe-flow" id="flow4" />
                <path d="M 220 520 C 350 520, 380 410, 480 410" class="pipe-core" />
                
                <!-- PLC to Output -->
                <path d="M 620 350 L 780 350" class="pipe-base" />
                <path d="M 620 350 L 780 350" class="pipe-flow output-flow" id="flow-out" />
                <path d="M 620 350 L 780 350" class="pipe-core" />
            </g>

            <!-- Sensors (Input) -->
            <g id="sensors">
                <!-- Sensor 1 -->
                <g id="s1" transform="translate(60, 150)" class="state-safe">
                    <rect x="0" y="0" width="160" height="60" rx="4" class="sensor-box" />
                    <text x="15" y="25" class="text-sm label">CH1 液位 (雷达)</text>
                    <text x="145" y="45" class="text-lg font-mono value val-text" id="val1">2.4m</text>
                    <circle cx="10" cy="42" r="3" fill="var(--color-safe)" />
                </g>

                <!-- Sensor 2 -->
                <g id="s2" transform="translate(60, 250)" class="state-safe">
                    <rect x="0" y="0" width="160" height="60" rx="4" class="sensor-box" />
                    <text x="15" y="25" class="text-sm label">CH2 进料流量 (电磁)</text>
                    <text x="145" y="45" class="text-lg font-mono value val-text" id="val2">120L/s</text>
                    <circle cx="10" cy="42" r="3" fill="var(--color-safe)" />
                </g>

                <!-- Sensor 3 -->
                <g id="s3" transform="translate(60, 390)" class="state-safe">
                    <rect x="0" y="0" width="160" height="60" rx="4" class="sensor-box" />
                    <text x="15" y="25" class="text-sm label">CH3 压力 (变送器)</text>
                    <text x="145" y="45" class="text-lg font-mono value val-text" id="val3">0.4MPa</text>
                    <circle cx="10" cy="42" r="3" fill="var(--color-safe)" />
                </g>

                <!-- Sensor 4 -->
                <g id="s4" transform="translate(60, 490)" class="state-safe">
                    <rect x="0" y="0" width="160" height="60" rx="4" class="sensor-box" />
                    <text x="15" y="25" class="text-sm label">CH4 筛网背压</text>
                    <text x="145" y="45" class="text-lg font-mono value val-text" id="val4">1.2kPa</text>
                    <circle cx="10" cy="42" r="3" fill="var(--color-safe)" />
                </g>
            </g>

            <!-- Central PLC / Algorithm Engine -->
            <g id="plc" transform="translate(0, 0)">
                <!-- Decorative Rings -->
                <circle cx="550" cy="350" r="110" fill="none" stroke="rgba(255,255,255,0.03)" stroke-width="20" class="ring ring-spin-slow" stroke-dasharray="100 20 40 20" />
                <circle cx="550" cy="350" r="95" fill="none" stroke="rgba(0,240,255,0.1)" stroke-width="2" class="ring ring-spin-fast" stroke-dasharray="10 30" />
                
                <!-- Main PLC Body -->
                <path d="M 490 230 L 610 230 L 640 350 L 610 470 L 490 470 L 460 350 Z" class="plc-core" />
                <path d="M 500 245 L 600 245 L 625 350 L 600 455 L 500 455 L 475 350 Z" class="plc-inner" />
                
                <!-- Headers -->
                <text x="550" y="270" text-anchor="middle" class="text-md text-bright" style="letter-spacing: 2px;">加权表决核心</text>
                <text x="550" y="290" text-anchor="middle" class="text-sm font-mono" fill="var(--text-main)">ALGO_FUSION</text>
                
                <!-- Voting Logic Visualization (2 out of 4) -->
                <g transform="translate(550, 360)">
                    <rect x="-40" y="-30" width="80" height="60" rx="4" fill="rgba(0,0,0,0.5)" stroke="#334155" />
                    <text x="0" y="-40" text-anchor="middle" class="text-sm font-mono" fill="#a3adc2">2/4 LOGIC GATE</text>
                    
                    <!-- 4 Signal Receivers -->
                    <circle cx="-20" cy="-10" r="6" id="vote1" class="vote-node" />
                    <circle cx="20" cy="-10" r="6" id="vote2" class="vote-node" />
                    <circle cx="-20" cy="15" r="6" id="vote3" class="vote-node" />
                    <circle cx="20" cy="15" r="6" id="vote4" class="vote-node" />
                </g>
                
                <!-- Internal condition line -->
                <path d="M 550 400 L 550 430" stroke="#334155" stroke-width="2" stroke-dasharray="2 2"/>
                <text x="550" y="445" text-anchor="middle" class="text-sm font-mono" id="core-status" fill="var(--color-safe)">≥ 2 THRESHOLD: FALSE</text>
            </g>

            <!-- Output Gauge / Action -->
            <g id="output" transform="translate(780, 250)">
                <!-- Outer Bezel -->
                <rect x="0" y="0" width="220" height="200" rx="8" fill="rgba(10, 15, 25, 0.9)" stroke="rgba(255,255,255,0.1)" />
                
                <text x="110" y="30" text-anchor="middle" class="text-md label">综合跑浆危险度</text>
                
                <!-- Gauge Background -->
                <path d="M 40 160 A 80 80 0 1 1 180 160" fill="none" stroke="#1a2235" stroke-width="12" stroke-linecap="round" />
                
                <!-- Gauge Value (Animated via JS) -->
                <path id="gauge-fill" d="M 40 160 A 80 80 0 1 1 180 160" fill="none" stroke="url(#meter-grad)" stroke-width="12" stroke-linecap="round" stroke-dasharray="351" stroke-dashoffset="351" style="transition: stroke-dashoffset 0.5s ease-out;" />
                
                <!-- Center Value -->
                <text x="110" y="110" text-anchor="middle" class="text-lg font-mono text-bright" id="risk-val" style="font-size: 32px;">0%</text>
                
                <!-- Action Status -->
                <rect x="40" y="140" width="140" height="30" rx="15" fill="#111" stroke="#334155" />
                <text x="110" y="160" text-anchor="middle" class="text-sm font-mono" id="action-text" fill="var(--text-main)">系统运行正常</text>
            </g>

        </svg>

        <!-- Contextual Information Panel (Strict constraint: small, unobtrusive) -->
        <div class="info-panel">
            <h3 class="info-title">IFR: 无冗余的高可靠决策</h3>
            <p class="info-desc">利用现有4类物理信号进行交叉校验。避免增加额外硬件复杂度,通过 2/4 加权表决逻辑,彻底消除单一传感器故障(如液位计挂泥)导致的系统误动作,实现精准响应。</p>
        </div>
    </div>

    <script>
        document.addEventListener("DOMContentLoaded", () => {
            // DOM Elements
            const s1 = document.getElementById('s1');
            const s3 = document.getElementById('s3');
            const v1 = document.getElementById('val1');
            const v3 = document.getElementById('val3');
            
            const vote1 = document.getElementById('vote1');
            const vote2 = document.getElementById('vote2');
            const vote3 = document.getElementById('vote3');
            const vote4 = document.getElementById('vote4');
            
            const plc = document.getElementById('plc');
            const coreStatus = document.getElementById('core-status');
            const flowOut = document.getElementById('flow-out');
            
            const gaugeFill = document.getElementById('gauge-fill');
            const riskVal = document.getElementById('risk-val');
            const actionText = document.getElementById('action-text');
            const stageText = document.getElementById('stageText');

            // Gauge circumference calculation for stroke-dashoffset
            // r=80, angle= ~270 degrees. Length roughly 351.
            const GAUGE_MAX = 351; 
            
            function setGauge(percent) {
                const offset = GAUGE_MAX - (percent / 100) * GAUGE_MAX;
                gaugeFill.style.strokeDashoffset = offset;
                riskVal.textContent = Math.round(percent) + '%';
            }

            // Normal values with slight jitter
            const baseV1 = 2.4;
            const baseV3 = 0.40;

            let time = 0;
            const LOOP_DURATION = 15000; // 15 seconds per story loop

            function simulationLoop() {
                time += 100; // 100ms refresh cycle
                if (time >= LOOP_DURATION) time = 0;

                // --- Phase Logic ---
                // 0 - 3s: Normal Operations
                // 3 - 7s: Single Sensor Fault (Sensor 1 - Radar Level gives false high)
                // 7 - 11s: Dual Sensor Fault (Sensor 1 + Sensor 3 - Real Danger)
                // 11 - 15s: Recovery to Normal

                let isS1Fault = time > 3000 && time < 11000;
                let isS3Fault = time > 7000 && time < 11000;

                // 1. Update Sensor Data & UI
                if (isS1Fault) {
                    s1.className.baseVal = "state-danger";
                    v1.textContent = (8.5 + Math.random()*0.5).toFixed(1) + 'm ↑';
                    vote1.className.baseVal = "vote-node active-danger";
                } else {
                    s1.className.baseVal = "state-safe";
                    v1.textContent = (baseV1 + Math.random()*0.2 - 0.1).toFixed(2) + 'm';
                    vote1.className.baseVal = "vote-node active-safe";
                }

                // Sensor 2 & 4 are stable for this demonstration
                vote2.className.baseVal = "vote-node active-safe";
                vote4.className.baseVal = "vote-node active-safe";

                if (isS3Fault) {
                    s3.className.baseVal = "state-danger";
                    v3.textContent = (1.2 + Math.random()*0.1).toFixed(2) + 'MPa ↑';
                    vote3.className.baseVal = "vote-node active-danger";
                } else {
                    s3.className.baseVal = "state-safe";
                    v3.textContent = (baseV3 + Math.random()*0.05 - 0.02).toFixed(2) + 'MPa';
                    vote3.className.baseVal = "vote-node active-safe";
                }

                // 2. PLC Logic (2 out of 4)
                let faultCount = (isS1Fault ? 1 : 0) + (isS3Fault ? 1 : 0);
                let isSystemAlarm = faultCount >= 2;

                // 3. Update Central Core & Output
                if (isSystemAlarm) {
                    plc.parentNode.classList.add('sys-danger'); // Add to root SVG or group
                    coreStatus.textContent = "≥ 2 THRESHOLD: TRUE (ALARM)";
                    coreStatus.setAttribute('fill', 'var(--color-danger)');
                    
                    setGauge(92 + Math.random()*5);
                    actionText.textContent = "⚠ 动作触发:切断进料";
                    actionText.setAttribute('fill', 'var(--color-danger)');
                    
                    stageText.textContent = "CRITICAL - 融合算法确认为真实危险,触发动作";
                    stageText.style.color = "var(--color-danger)";
                    stageText.style.borderColor = "var(--color-danger)";
                    stageText.style.background = "rgba(255,0,85,0.1)";

                } else if (faultCount === 1) {
                    plc.parentNode.classList.remove('sys-danger');
                    coreStatus.textContent = "≥ 2 THRESHOLD: FALSE (IGNORE)";
                    coreStatus.setAttribute('fill', 'var(--color-warn)');
                    
                    setGauge(35 + Math.random()*5);
                    actionText.textContent = "单一异常:交叉校验不通过";
                    actionText.setAttribute('fill', 'var(--color-warn)');

                    stageText.textContent = "WARNING - 单路传感器异常,表决拦截,避免误报";
                    stageText.style.color = "var(--color-warn)";
                    stageText.style.borderColor = "var(--color-warn)";
                    stageText.style.background = "rgba(255,184,0,0.1)";
                } else {
                    plc.parentNode.classList.remove('sys-danger');
                    coreStatus.textContent = "≥ 2 THRESHOLD: FALSE";
                    coreStatus.setAttribute('fill', 'var(--color-safe)');
                    
                    setGauge(15 + Math.random()*5);
                    actionText.textContent = "系统运行正常";
                    actionText.setAttribute('fill', 'var(--text-main)');

                    stageText.textContent = "SYSTEM NORMAL - 多源信号稳定融合中";
                    stageText.style.color = "var(--color-safe)";
                    stageText.style.borderColor = "var(--color-safe-dim)";
                    stageText.style.background = "rgba(0,240,255,0.1)";
                }
            }

            // Start animation loop immediately
            setInterval(simulationLoop, 100);
            simulationLoop(); // Initial call
        });
    </script>
</body>
</html>
积分规则:每轮对话扣10分