分享图
动画工坊
引擎就绪
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Rotary Folding Kinematics - IFR</title>
    <link href="https://fonts.googleapis.com/css2?family=Rajdhani:wght@500;700&family=Share+Tech+Mono&display=swap" rel="stylesheet">
    <style>
        :root {
            --bg-color: #050914;
            --grid-color: rgba(6, 182, 212, 0.05);
            --neon-blue: #06b6d4;
            --neon-orange: #f97316;
            --metallic-dark: #1e293b;
            --metallic-light: #334155;
            --foil-color: #f8fafc;
            
            --vacuum-intensity: 1;
            --anim-speed: 1;
        }

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

        body {
            background-color: var(--bg-color);
            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;
            font-family: 'Rajdhani', sans-serif;
            color: white;
            min-height: 100vh;
            display: flex;
            justify-content: center;
            align-items: center;
            overflow: hidden;
        }

        /* Vignette overlay */
        body::after {
            content: '';
            position: absolute;
            top: 0; left: 0; right: 0; bottom: 0;
            background: radial-gradient(circle at center, transparent 30%, var(--bg-color) 100%);
            pointer-events: none;
            z-index: 0;
        }

        .container {
            position: relative;
            width: 100vw;
            height: 100vh;
            display: flex;
            justify-content: center;
            align-items: center;
            z-index: 1;
        }

        svg {
            width: 100%;
            height: 100%;
            max-width: 1400px;
            max-height: 900px;
            filter: drop-shadow(0 0 30px rgba(6, 182, 212, 0.1));
        }

        /* Typography & UI Panels */
        .hud-panel {
            position: absolute;
            background: rgba(15, 23, 42, 0.6);
            backdrop-filter: blur(12px);
            border: 1px solid rgba(6, 182, 212, 0.2);
            padding: 24px;
            border-radius: 4px;
            box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5);
            border-left: 4px solid var(--neon-blue);
        }

        .hud-top-left { top: 40px; left: 40px; }
        .hud-bottom-right { bottom: 40px; right: 40px; border-left: none; border-right: 4px solid var(--neon-orange); }

        h1 {
            font-size: 2rem;
            text-transform: uppercase;
            letter-spacing: 2px;
            margin-bottom: 8px;
            color: #fff;
            text-shadow: 0 0 10px rgba(6, 182, 212, 0.5);
        }

        h2 {
            font-size: 1.2rem;
            color: var(--neon-blue);
            margin-bottom: 20px;
        }

        .data-row {
            display: flex;
            justify-content: space-between;
            align-items: center;
            margin-bottom: 12px;
            font-family: 'Share Tech Mono', monospace;
            font-size: 1.1rem;
        }

        .data-label { color: #94a3b8; }
        .data-value { color: var(--neon-blue); font-weight: bold; text-shadow: 0 0 8px rgba(6, 182, 212, 0.6); }
        .data-value.orange { color: var(--neon-orange); text-shadow: 0 0 8px rgba(249, 115, 22, 0.6); }

        /* Controls */
        .controls {
            margin-top: 24px;
            padding-top: 20px;
            border-top: 1px solid rgba(255,255,255,0.1);
        }

        .control-group {
            margin-bottom: 16px;
        }

        .control-header {
            display: flex;
            justify-content: space-between;
            margin-bottom: 8px;
            font-size: 0.9rem;
            color: #cbd5e1;
            text-transform: uppercase;
        }

        input[type=range] {
            -webkit-appearance: none;
            width: 100%;
            background: transparent;
        }

        input[type=range]::-webkit-slider-thumb {
            -webkit-appearance: none;
            height: 16px;
            width: 8px;
            background: var(--neon-blue);
            cursor: pointer;
            box-shadow: 0 0 10px var(--neon-blue);
        }

        input[type=range]::-webkit-slider-runnable-track {
            width: 100%;
            height: 2px;
            cursor: pointer;
            background: rgba(255,255,255,0.2);
        }

        /* SVG Inner Styling */
        .g-roller { transform-origin: center; }
        .foil-stroke {
            fill: none;
            stroke: var(--foil-color);
            stroke-width: 6;
            stroke-linecap: round;
            stroke-linejoin: round;
            filter: drop-shadow(0 0 6px rgba(255,255,255,0.8));
        }
        
        .vacuum-zone {
            fill: none;
            stroke: var(--neon-blue);
            stroke-width: 16;
            stroke-dasharray: 4 12;
            opacity: calc(0.4 * var(--vacuum-intensity));
            filter: drop-shadow(0 0 8px var(--neon-blue));
            transition: opacity 0.3s;
        }

        .annotation-line {
            stroke: rgba(255,255,255,0.3);
            stroke-width: 1;
            stroke-dasharray: 4 4;
        }

        .annotation-text {
            font-family: 'Rajdhani', sans-serif;
            fill: #94a3b8;
            font-size: 14px;
            letter-spacing: 1px;
        }
        
        .annotation-title {
            fill: var(--neon-blue);
            font-weight: bold;
            font-size: 16px;
        }

        /* Animations */
        @keyframes spin-cw { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }
        @keyframes spin-ccw { from { transform: rotate(0deg); } to { transform: rotate(-360deg); } }
        
        .spin-continuous-cw { animation: spin-cw linear infinite; animation-duration: calc(10s / var(--anim-speed)); }
        .spin-continuous-ccw { animation: spin-ccw linear infinite; animation-duration: calc(10s / var(--anim-speed)); }

        .blade-strike {
            transition: transform 0.05s cubic-bezier(0.1, 0.9, 0.2, 1);
        }
    </style>
</head>
<body>

    <div class="container">
        <!-- Main Kinematics SVG -->
        <svg viewBox="0 0 1200 800" id="visualizer">
            <defs>
                <filter id="glow-blue" x="-20%" y="-20%" width="140%" height="140%">
                    <feGaussianBlur stdDeviation="8" result="blur" />
                    <feComposite in="SourceGraphic" in2="blur" operator="over" />
                </filter>
                <filter id="glow-orange" x="-20%" y="-20%" width="140%" height="140%">
                    <feGaussianBlur stdDeviation="6" result="blur" />
                    <feComposite in="SourceGraphic" in2="blur" operator="over" />
                </filter>
                
                <radialGradient id="grad-main-roller" cx="50%" cy="50%" r="50%">
                    <stop offset="70%" stop-color="#1e293b" />
                    <stop offset="100%" stop-color="#334155" />
                </radialGradient>

                <linearGradient id="grad-foil" x1="0%" y1="0%" x2="100%" y2="0%">
                    <stop offset="0%" stop-color="#cbd5e1" />
                    <stop offset="50%" stop-color="#ffffff" />
                    <stop offset="100%" stop-color="#cbd5e1" />
                </linearGradient>

                <!-- Pattern for mechanical texture -->
                <pattern id="mech-pattern" width="20" height="20" patternUnits="userSpaceOnUse">
                    <circle cx="10" cy="10" r="2" fill="rgba(0,0,0,0.2)"/>
                </pattern>
            </defs>

            <!-- Background Grid Rings -->
            <g opacity="0.1" stroke="#fff" stroke-width="1" fill="none">
                <circle cx="450" cy="400" r="300" stroke-dasharray="10 20"/>
                <circle cx="850" cy="400" r="250" stroke-dasharray="5 15"/>
            </g>

            <!-- ================= Annotations ================= -->
            <g id="annotations" transform="translate(0,0)">
                <!-- IFR Point 1 -->
                <path d="M 450 160 L 350 100 L 200 100" class="annotation-line" />
                <circle cx="450" cy="160" r="4" fill="var(--neon-blue)" />
                <text x="200" y="80" class="annotation-title">TRIZ IFR: PURE ROTATION</text>
                <text x="200" y="100" class="annotation-text">Eliminates reciprocating stop-motion.</text>
                <text x="200" y="120" class="annotation-text">0 vibration at high velocity.</text>

                <!-- IFR Point 2 -->
                <path d="M 700 400 L 800 250 L 980 250" class="annotation-line" />
                <circle cx="700" cy="400" r="4" fill="var(--neon-orange)" />
                <text x="980" y="210" class="annotation-title" text-anchor="end" fill="var(--neon-orange)">RESOURCE: INTERNAL BLADE</text>
                <text x="980" y="230" class="annotation-text" text-anchor="end">Utilizes hidden roller space.</text>
                <text x="980" y="250" class="annotation-text" text-anchor="end">Instantly transfers specific fold line.</text>
            </g>

            <!-- ================= Machinery Base ================= -->
            
            <!-- Main Vacuum Roller (Center: 450, 400, Radius: 200) -->
            <g id="main-roller-assembly">
                <!-- Vacuum indicator arc (Top to Right) -->
                <path d="M 450 200 A 200 200 0 0 1 650 400" class="vacuum-zone" />
                
                <circle cx="450" cy="400" r="200" fill="url(#grad-main-roller)" stroke="#475569" stroke-width="2"/>
                <circle cx="450" cy="400" r="190" fill="url(#mech-pattern)"/>
                
                <!-- Rotating Core of Main Roller -->
                <g class="spin-continuous-cw" style="transform-origin: 450px 400px;">
                    <!-- Spokes -->
                    <line x1="250" y1="400" x2="650" y2="400" stroke="#0f172a" stroke-width="10"/>
                    <line x1="450" y1="200" x2="450" y2="600" stroke="#0f172a" stroke-width="10"/>
                    <circle cx="450" cy="400" r="40" fill="#334155" stroke="#0f172a" stroke-width="4"/>
                    
                    <!-- The Hidden Blade Housing (rotates with roller) -->
                    <!-- We will dynamically trigger the blade extension via JS -->
                    <g id="blade-housing" transform="translate(450, 400)">
                        <rect x="-10" y="-180" width="20" height="180" fill="#0f172a" />
                        <!-- Actual Blade -->
                        <path id="active-blade" d="M -8 -180 L 8 -180 L 12 -220 L -12 -220 Z" fill="var(--neon-orange)" filter="url(#glow-orange)" class="blade-strike" transform="translate(0,0)" />
                    </g>
                </g>
            </g>

            <!-- Secondary Folding Roller (Center: 800, 400, Radius: 140) -->
            <g id="sec-roller-assembly">
                <circle cx="800" cy="400" r="140" fill="url(#grad-main-roller)" stroke="#475569" stroke-width="2"/>
                <!-- Rotating Core -->
                <g class="spin-continuous-ccw" style="transform-origin: 800px 400px;">
                    <circle cx="800" cy="400" r="130" fill="none" stroke="#334155" stroke-width="4" stroke-dasharray="20 10"/>
                    <circle cx="800" cy="400" r="30" fill="#334155" stroke="#0f172a" stroke-width="4"/>
                    <!-- Gripper slots -->
                    <path d="M 660 390 L 680 400 L 660 410 Z" fill="var(--neon-blue)" filter="url(#glow-blue)"/>
                    <path d="M 940 390 L 920 400 L 940 410 Z" fill="var(--neon-blue)"/>
                </g>
            </g>

            <!-- Flying Shear Rotary Cutter (Top Left, Center: 450, 150) -->
            <g id="cutter-assembly" transform="translate(450, 140)">
                <circle cx="0" cy="0" r="50" fill="#1e293b" stroke="var(--neon-orange)" stroke-width="2"/>
                <g class="spin-continuous-cw" style="transform-origin: 0px 0px; animation-duration: calc(5s / var(--anim-speed));">
                    <path d="M -5 -50 L 5 -50 L 10 -70 L -10 -70 Z" fill="var(--neon-orange)" filter="url(#glow-orange)"/>
                    <path d="M -5 50 L 5 50 L 10 70 L -10 70 Z" fill="#334155" />
                    <circle cx="0" cy="0" r="15" fill="#475569"/>
                </g>
            </g>


            <!-- ================= Foil Material Dynamics ================= -->
            <g id="foil-system">
                <!-- 1. Continuous Input Feed -->
                <path d="M 450 -50 L 450 200" class="foil-stroke" stroke-dasharray="20 10" id="feed-line"/>

                <!-- 
                  Dynamic Foil Segments controlled by JS to ensure perfect kinematic timing.
                  We use separate paths for different phases of the foil's journey.
                -->
                
                <!-- Piece A -->
                <g id="piece-A">
                    <!-- Phase 1: On Main Roller -->
                    <path id="piece-A-arc" d="" class="foil-stroke" />
                    <!-- Phase 2: Folding Transfer -->
                    <path id="piece-A-fold" d="" class="foil-stroke" />
                </g>

                <!-- Piece B (Pipeline overlap) -->
                <g id="piece-B">
                    <path id="piece-B-arc" d="" class="foil-stroke" />
                    <path id="piece-B-fold" d="" class="foil-stroke" />
                </g>
            </g>

            <!-- Nip Point Highlight -->
            <circle cx="650" cy="400" r="8" fill="var(--neon-orange)" filter="url(#glow-orange)" opacity="0.8">
                <animate attributeName="r" values="6;10;6" dur="2s" repeatCount="indefinite" />
            </circle>

        </svg>

        <!-- UI Overlays -->
        <div class="hud-panel hud-top-left">
            <h1>Rotary Fold</h1>
            <h2>Kinematic IFR System</h2>
            
            <div class="data-row">
                <span class="data-label">Line Velocity</span>
                <span class="data-value" id="val-speed">25.0 m/min</span>
            </div>
            <div class="data-row">
                <span class="data-label">Main Vacuum</span>
                <span class="data-value" id="val-vacuum">-65.0 kPa</span>
            </div>
            <div class="data-row">
                <span class="data-label">Cut Length</span>
                <span class="data-value">620 mm</span>
            </div>
            <div class="data-row">
                <span class="data-label">Offset Fold</span>
                <span class="data-value orange">315 / 305</span>
            </div>

            <div class="controls">
                <div class="control-group">
                    <div class="control-header">
                        <span>System Speed</span>
                        <span id="label-speed">1.0x</span>
                    </div>
                    <input type="range" id="slider-speed" min="0.1" max="2.0" step="0.1" value="1.0">
                </div>
                <div class="control-group">
                    <div class="control-header">
                        <span>Vacuum Pressure</span>
                        <span id="label-vacuum">100%</span>
                    </div>
                    <input type="range" id="slider-vacuum" min="0" max="100" value="100">
                </div>
            </div>
        </div>

        <div class="hud-panel hud-bottom-right">
            <div class="data-row" style="margin-bottom: 4px;">
                <span class="data-label" style="color:#cbd5e1;">SYSTEM STATUS</span>
            </div>
            <div class="data-row">
                <span class="data-value" style="color:#22c55e; text-shadow: 0 0 10px rgba(34, 197, 94, 0.5);">● CONTINUOUS FLOW ACTIVE</span>
            </div>
            <div style="font-size: 0.85rem; color: #64748b; margin-top: 10px; max-width: 250px;">
                Foil tears and vibrations eliminated. Fold accuracy maintained by pure rotational synchronicity.
            </div>
        </div>

    </div>

    <script>
        /**
         * Core Kinematic Engine
         * Implements the exact mechanical sequence via procedural SVG path generation.
         */
        
        // Configuration Constants
        const R_MAIN = 200;
        const C_MAIN = { x: 450, y: 400 };
        const R_SEC = 140;
        const C_SEC = { x: 800, y: 400 };
        const NIP_X = 650;
        const CYCLE_TIME_BASE = 2000; // ms for one full piece cycle

        // State variables
        let progress = 0; // 0 to 1 over CYCLE_TIME
        let lastTime = performance.now();
        
        // DOM Elements
        const blade = document.getElementById('active-blade');
        const bladeHousing = document.getElementById('blade-housing');
        const feedLine = document.getElementById('feed-line');
        
        const pieces = [
            { arc: document.getElementById('piece-A-arc'), fold: document.getElementById('piece-A-fold'), offset: 0 },
            { arc: document.getElementById('piece-B-arc'), fold: document.getElementById('piece-B-fold'), offset: 0.5 }
        ];

        // Controls
        const rootStyle = document.documentElement.style;
        let speedMult = 1.0;
        
        document.getElementById('slider-speed').addEventListener('input', (e) => {
            speedMult = parseFloat(e.target.value);
            rootStyle.setProperty('--anim-speed', speedMult);
            document.getElementById('label-speed').innerText = speedMult.toFixed(1) + 'x';
            document.getElementById('val-speed').innerText = (25.0 * speedMult).toFixed(1) + ' m/min';
        });

        document.getElementById('slider-vacuum').addEventListener('input', (e) => {
            let val = parseInt(e.target.value);
            rootStyle.setProperty('--vacuum-intensity', val / 100);
            document.getElementById('label-vacuum').innerText = val + '%';
            document.getElementById('val-vacuum').innerText = '-' + (65.0 * (val/100)).toFixed(1) + ' kPa';
        });

        // Math Helpers
        const toRad = deg => deg * Math.PI / 180;
        const getPointOnCircle = (cx, cy, r, angleDeg) => {
            return {
                x: cx + r * Math.cos(toRad(angleDeg)),
                y: cy + r * Math.sin(toRad(angleDeg))
            };
        };

        /**
         * Main Render Loop
         */
        function animate(time) {
            let delta = time - lastTime;
            lastTime = time;
            
            // Advance progress
            progress += (delta / CYCLE_TIME_BASE) * speedMult;
            if (progress >= 1.0) progress -= 1.0;

            // Animate continuous feed line moving down
            let feedOffset = -(progress * 100) % 30;
            feedLine.style.strokeDashoffset = feedOffset;

            // Update Blade Rotation (Housing rotates continuously with Main Roller)
            // We want the blade to be at 0 degrees (Right) exactly when a piece's fold point reaches there.
            // A piece's fold point reaches Right at piece local time p = 0.5.
            // We have 2 pieces per full cycle of the system array, meaning the roller actually 
            // processes 2 pieces per 360 deg rotation, or we can just spin the blade to match.
            // Let's manually sync the blade housing rotation so it aligns at Nip point (Angle 0).
            let bladeAngle = (progress * 360) - 180; // When prog=0.5, angle=0 (Right). When prog=1.0, angle=180 (Left)
            bladeHousing.setAttribute('transform', `translate(450, 400) rotate(${bladeAngle})`);

            // Trigger Blade Extrusion
            // Striking zone: slightly before and after p = 0.5 or p = 1.0 (for piece B)
            let isStriking = (Math.abs(progress - 0.5) < 0.03) || (progress > 0.97) || (progress < 0.03);
            if (isStriking) {
                blade.setAttribute('transform', 'translate(0, -30)'); // Shoot out
            } else {
                blade.setAttribute('transform', 'translate(0, 0)');   // Retract
            }

            // Update Foil Pieces
            pieces.forEach(piece => {
                // Local progress for this specific piece (0 to 1)
                let p = (progress + piece.offset) % 1.0;
                updatePieceLogic(p, piece.arc, piece.fold);
            });

            requestAnimationFrame(animate);
        }

        /**
         * Calculates and draws the SVG paths for a foil piece based on its lifecycle progress (0 to 1)
         * p = 0.0 : Cut occurs at Top (-90 deg). Piece starts moving.
         * p = 0.5 : Center of piece reaches Nip point (0 deg). Blade strikes.
         * p > 0.5 : Piece is folded and dragged into Sec Roller.
         */
        function updatePieceLogic(p, arcElem, foldElem) {
            // Foil parameters (Visual scaling)
            const arcLengthDeg = 75; // Total angular length of cut piece on main roller
            const foldOffsetDeg = arcLengthDeg / 2; // Fold is roughly in the middle

            if (p < 0.5) {
                // PHASE 1: Wrapped on Main Roller
                arcElem.style.display = 'block';
                foldElem.style.display = 'none';

                // Head of the piece travels from Top (-90) to past the Nip (0)
                // When p=0, head is at -90 + arcLengthDeg. Tail is at -90.
                // We want the FOLD point to reach 0 at p=0.5.
                // Fold point angle = -90 + p * 180;
                let foldAngle = -90 + p * 180;
                let headAngle = foldAngle + foldOffsetDeg;
                let tailAngle = foldAngle - foldOffsetDeg;

                let startPt = getPointOnCircle(C_MAIN.x, C_MAIN.y, R_MAIN, tailAngle);
                let endPt = getPointOnCircle(C_MAIN.x, C_MAIN.y, R_MAIN, headAngle);

                // Draw arc
                let largeArcFlag = headAngle - tailAngle > 180 ? 1 : 0;
                let path = `M ${startPt.x} ${startPt.y} A ${R_MAIN} ${R_MAIN} 0 ${largeArcFlag} 1 ${endPt.x} ${endPt.y}`;
                arcElem.setAttribute('d', path);

            } else {
                // PHASE 2: Transfer and Fold
                arcElem.style.display = 'none';
                foldElem.style.display = 'block';

                // p goes from 0.5 to 1.0. Map this to 0 to 1 for fold animation.
                let foldProg = (p - 0.5) * 2; 
                
                // Fade out near the end to simulate exiting the system
                if (foldProg > 0.8) {
                    foldElem.style.opacity = 1 - ((foldProg - 0.8) * 5);
                } else {
                    foldElem.style.opacity = 1;
                }

                // Fold point moves onto Sec Roller.
                // Sec Roller rotates Counter-Clockwise. Nip is at 180 deg for Sec Roller.
                // Fold point travels from 180 deg downwards towards 90 deg.
                let secFoldAngle = 180 - (foldProg * 120); // Travels 120 degrees visually
                let pFold = getPointOnCircle(C_SEC.x, C_SEC.y, R_SEC, secFoldAngle);

                // Calculate trailing tail (still on Main Roller initially, then peels off)
                // The tail was at -foldOffsetDeg when fold hit 0. It continues moving with Main R.
                let tailMainAngle = -foldOffsetDeg + foldProg * 180;
                
                let path = "";
                
                if (tailMainAngle < 0) {
                    // Tail is still glued to main roller
                    let pTail = getPointOnCircle(C_MAIN.x, C_MAIN.y, R_MAIN, tailMainAngle);
                    let pNip = {x: NIP_X, y: C_MAIN.y};
                    // Arc from tail to Nip, then curve to Fold point
                    path = `M ${pTail.x} ${pTail.y} A ${R_MAIN} ${R_MAIN} 0 0 1 ${pNip.x} ${pNip.y} Q ${pNip.x+10} ${pNip.y+20} ${pFold.x} ${pFold.y}`;
                } else {
                    // Tail has peeled off, dragging through the air
                    // Fake a trailing end that follows the fold point
                    let trailX = pFold.x - 30 * (1-foldProg);
                    let trailY = pFold.y - 80 * (1-foldProg);
                    path = `M ${trailX} ${trailY} Q ${trailX+20} ${trailY+40} ${pFold.x} ${pFold.y}`;
                }

                // Add the leading head (hanging downwards/wrapping sec roller)
                // Head was ahead of Nip, now it gets pulled backwards/downwards.
                let headX = pFold.x + 30 * Math.cos(toRad(secFoldAngle - 90));
                let headY = pFold.y + 100 * (1 - foldProg * 0.5); // hangs down, gets shorter as it wraps
                
                path += ` Q ${pFold.x+20} ${pFold.y+40} ${headX} ${headY}`;

                foldElem.setAttribute('d', path);
            }
        }

        // Initialize and start animation immediately on load
        window.addEventListener('DOMContentLoaded', () => {
            requestAnimationFrame(animate);
        });

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