分享图
动画工坊
引擎就绪
<!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>
        @import url('https://fonts.googleapis.com/css2?family=Rajdhani:wght@500;600;700&family=JetBrains+Mono:wght@400;700&display=swap');

        :root {
            --bg-color: #050608;
            --grid-color: rgba(255, 255, 255, 0.03);
            --primary-glow: #00f0ff;    /* Input */
            --secondary-glow: #ff0055;  /* Output */
            --accent-glow: #facc15;     /* Tilt/EHL */
            --text-main: #e2e8f0;
            --text-muted: #64748b;
            --panel-bg: rgba(10, 15, 20, 0.65);
            --panel-border: rgba(255, 255, 255, 0.1);
        }

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

        #animation-container {
            position: relative;
            width: 100%;
            height: 100%;
        }

        svg {
            width: 100%;
            height: 100%;
            display: block;
        }

        /* HUD Panels */
        .hud-panel {
            position: absolute;
            background: var(--panel-bg);
            backdrop-filter: blur(10px);
            -webkit-backdrop-filter: blur(10px);
            border: 1px solid var(--panel-border);
            padding: 1.5rem;
            border-radius: 4px;
            pointer-events: none;
            box-shadow: 0 10px 30px rgba(0,0,0,0.5);
        }

        .hud-panel::before {
            content: '';
            position: absolute;
            top: 0; left: 0; width: 3px; height: 100%;
        }

        /* Top Left: Title & IFR Concept */
        #triz-info {
            top: 2rem;
            left: 2rem;
            max-width: 380px;
        }
        #triz-info::before { background: var(--accent-glow); }

        /* Top Right: Telemetry Data */
        #telemetry {
            top: 2rem;
            right: 2rem;
            width: 280px;
        }
        #telemetry::before { background: var(--primary-glow); }

        /* Bottom Right: Live Graph */
        #graph-panel {
            bottom: 2rem;
            right: 2rem;
            width: 350px;
            height: 120px;
        }
        #graph-panel::before { background: var(--secondary-glow); }

        h1 {
            font-family: 'Rajdhani', sans-serif;
            font-size: 1.5rem;
            text-transform: uppercase;
            letter-spacing: 2px;
            margin: 0 0 0.5rem 0;
            color: #fff;
        }

        h2 {
            font-family: 'Rajdhani', sans-serif;
            font-size: 0.9rem;
            color: var(--accent-glow);
            margin: 0 0 1rem 0;
            text-transform: uppercase;
            letter-spacing: 1px;
        }

        .desc-text {
            font-size: 0.75rem;
            line-height: 1.6;
            color: var(--text-muted);
            margin-bottom: 0.8rem;
        }

        .desc-text strong {
            color: #fff;
            font-weight: 700;
        }

        /* Data Readouts */
        .data-row {
            display: flex;
            justify-content: space-between;
            align-items: flex-end;
            margin-bottom: 0.8rem;
            border-bottom: 1px solid rgba(255,255,255,0.05);
            padding-bottom: 0.3rem;
        }
        .data-row:last-child { margin-bottom: 0; border-bottom: none; }

        .data-label {
            font-size: 0.7rem;
            color: var(--text-muted);
            text-transform: uppercase;
        }

        .data-value {
            font-size: 1.2rem;
            font-weight: 700;
        }
        
        .val-input { color: var(--primary-glow); }
        .val-output { color: var(--secondary-glow); }
        .val-ratio { color: #fff; }
        .val-angle { color: var(--accent-glow); }

        /* Graph Canvas */
        #ratio-graph {
            width: 100%;
            height: 100%;
        }

        /* SVG specific styles */
        .glow-line {
            filter: drop-shadow(0 0 8px currentColor);
        }
    </style>
</head>
<body>

<div id="animation-container">
    <svg viewBox="0 0 1600 900" preserveAspectRatio="xMidYMid slice">
        <defs>
            <!-- Background Grid Pattern -->
            <pattern id="grid" width="40" height="40" patternUnits="userSpaceOnUse">
                <path d="M 40 0 L 0 0 0 40" fill="none" stroke="rgba(255,255,255,0.03)" stroke-width="1"/>
            </pattern>

            <!-- Glow Filters -->
            <filter id="glow-cyan" x="-20%" y="-20%" width="140%" height="140%">
                <feGaussianBlur stdDeviation="8" result="blur" />
                <feMerge>
                    <feMergeNode in="blur"/>
                    <feMergeNode in="SourceGraphic"/>
                </feMerge>
            </filter>
            
            <filter id="glow-pink" x="-20%" y="-20%" width="140%" height="140%">
                <feGaussianBlur stdDeviation="8" result="blur" />
                <feMerge>
                    <feMergeNode in="blur"/>
                    <feMergeNode in="SourceGraphic"/>
                </feMerge>
            </filter>

            <filter id="glow-ehl" x="-50%" y="-50%" width="200%" height="200%">
                <feGaussianBlur stdDeviation="6" result="blur" />
                <feComponentTransfer in="blur" result="glow">
                    <feFuncA type="linear" slope="2"/>
                </feComponentTransfer>
                <feMerge>
                    <feMergeNode in="glow"/>
                    <feMergeNode in="SourceGraphic"/>
                </feMerge>
            </filter>

            <!-- Gradients -->
            <linearGradient id="metal-grad" x1="0%" y1="0%" x2="100%" y2="100%">
                <stop offset="0%" stop-color="#475569" />
                <stop offset="50%" stop-color="#1e293b" />
                <stop offset="100%" stop-color="#0f172a" />
            </linearGradient>

            <radialGradient id="ball-grad" cx="30%" cy="30%" r="70%">
                <stop offset="0%" stop-color="#94a3b8" />
                <stop offset="70%" stop-color="#334155" />
                <stop offset="100%" stop-color="#0f172a" />
            </radialGradient>
        </defs>

        <!-- Background -->
        <rect width="100%" height="100%" fill="url(#grid)" />
        
        <!-- Center Origin Group -->
        <g transform="translate(800, 400)">
            
            <!-- Main Axis Line -->
            <line x1="-700" y1="350" x2="700" y2="350" stroke="#334155" stroke-width="2" stroke-dasharray="10, 10" />
            <text x="650" y="340" fill="#64748b" font-size="12" text-anchor="end">SYSTEM MAIN AXIS</text>

            <!-- Static Housing / Disks Outline -->
            <!-- Input Torus Disk (Left) -->
            <path d="M -300 -100 C -200 -100, -180 50, -180 150 C -180 250, -200 400, -300 400 L -400 400 L -400 -100 Z" fill="url(#metal-grad)" stroke="#1e293b" stroke-width="2"/>
            <!-- Output Torus Disk (Right) -->
            <path d="M 300 -100 C 200 -100, 180 50, 180 150 C 180 250, 200 400, 300 400 L 400 400 L 400 -100 Z" fill="url(#metal-grad)" stroke="#1e293b" stroke-width="2"/>

            <!-- Rotating Indicators (Visualizing Speed) -->
            <path id="input-spin" d="M -290 -90 C -190 -90, -170 50, -170 150 C -170 250, -190 390, -290 390" fill="none" stroke="var(--primary-glow)" stroke-width="4" stroke-dasharray="20 40" filter="url(#glow-cyan)" opacity="0.8"/>
            <path id="output-spin" d="M 290 -90 C 190 -90, 170 50, 170 150 C 170 250, 190 390, 290 390" fill="none" stroke="var(--secondary-glow)" stroke-width="4" stroke-dasharray="20 40" filter="url(#glow-pink)" opacity="0.8"/>


            <!-- Dynamic Mechanics Group -->
            <g id="mechanics">
                
                <!-- Dynamic Radius Lines (R_in, R_out) -->
                <line id="rad-in-line" x1="0" y1="350" x2="0" y2="0" stroke="var(--primary-glow)" stroke-width="2" stroke-dasharray="4 4" opacity="0.6"/>
                <line id="rad-out-line" x1="0" y1="350" x2="0" y2="0" stroke="var(--secondary-glow)" stroke-width="2" stroke-dasharray="4 4" opacity="0.6"/>
                
                <text id="rad-in-text" x="0" y="0" fill="var(--primary-glow)" font-size="12" text-anchor="end" dx="-10">R_in</text>
                <text id="rad-out-text" x="0" y="0" fill="var(--secondary-glow)" font-size="12" text-anchor="start" dx="10">R_out</text>

                <!-- Traction Ball Sub-system -->
                <!-- The ball's center is vertically offset from main axis. Let's say main axis is at Y=350 relative to center, ball is at Y=0 -->
                <g id="traction-ball-group">
                    <!-- The Steel Ball -->
                    <circle cx="0" cy="0" r="160" fill="url(#ball-grad)" stroke="#475569" stroke-width="2" />
                    
                    <!-- Ball Spin visual lines (equator/meridians abstraction) -->
                    <ellipse cx="0" cy="0" rx="160" ry="40" fill="none" stroke="#475569" stroke-width="1" opacity="0.5" transform="rotate(90)" />

                    <!-- Tilt Axis -->
                    <line x1="0" y1="-220" x2="0" y2="220" stroke="var(--accent-glow)" stroke-width="3" filter="url(#glow-ehl)" />
                    
                    <!-- Axis End Caps -->
                    <circle cx="0" cy="-220" r="8" fill="var(--bg-color)" stroke="var(--accent-glow)" stroke-width="2"/>
                    <circle cx="0" cy="220" r="8" fill="var(--bg-color)" stroke="var(--accent-glow)" stroke-width="2"/>
                </g>

                <!-- EHL Oil Film Contact Points (Drawn over the ball to stay fixed relative to disks while axis tilts) -->
                <g id="contact-in" filter="url(#glow-ehl)">
                    <ellipse cx="0" cy="0" rx="6" ry="18" fill="#facc15" />
                    <circle cx="0" cy="0" r="12" fill="none" stroke="#facc15" stroke-width="2" class="pulse-ring"/>
                </g>
                <g id="contact-out" filter="url(#glow-ehl)">
                    <ellipse cx="0" cy="0" rx="6" ry="18" fill="#facc15" />
                    <circle cx="0" cy="0" r="12" fill="none" stroke="#facc15" stroke-width="2" class="pulse-ring"/>
                </g>

            </g>
        </g>
    </svg>
</div>

<!-- Left HUD: Context & IFR -->
<div id="triz-info" class="hud-panel">
    <h1>全环面球面牵引</h1>
    <h2>TRIZ: 最终理想解 (IFR)</h2>
    <div class="desc-text">
        <strong>问题重构:</strong>传统行星排依靠刚性齿轮啮合,固定节圆导致传动比无法在单体内平滑演变。
    </div>
    <div class="desc-text">
        <strong>理想状态实现:</strong>用高硬度钢球替换带齿行星轮,用平滑牵引环替代太阳轮/齿圈。通过液压控制支架改变钢球自转轴倾角,<strong>无需改变系统基本拓扑</strong>即可无级改变接触半径。
    </div>
    <div class="desc-text">
        <strong>资源极致利用:</strong>利用 <strong>EHL (弹性流体动压) 极压油膜</strong>,在 >300 MPa 压力下油膜发生玻璃化转变,产生巨大抗剪切力传递扭矩,彻底消除了金属实体干涉的物理矛盾。
    </div>
</div>

<!-- Right HUD: Telemetry -->
<div id="telemetry" class="hud-panel">
    <div class="data-row">
        <span class="data-label">Input Speed (Sun)</span>
        <span class="data-value val-input" id="val-in-rpm">3000 RPM</span>
    </div>
    <div class="data-row">
        <span class="data-label">Planet Tilt Angle (&theta;)</span>
        <span class="data-value val-angle" id="val-tilt">0.00&deg;</span>
    </div>
    <div class="data-row">
        <span class="data-label">Traction Ratio (i)</span>
        <span class="data-value val-ratio" id="val-ratio">1.000</span>
    </div>
    <div class="data-row">
        <span class="data-label">Output Speed (Ring)</span>
        <span class="data-value val-output" id="val-out-rpm">3000 RPM</span>
    </div>
</div>

<!-- Bottom Right HUD: Graph -->
<div id="graph-panel" class="hud-panel">
    <div style="font-size: 0.6rem; color: var(--text-muted); margin-bottom: 5px; text-transform: uppercase;">Ratio Curve History</div>
    <svg id="ratio-graph">
        <!-- Axes -->
        <line x1="0" y1="100%" x2="100%" y2="100%" stroke="rgba(255,255,255,0.2)" stroke-width="1"/>
        <line x1="0" y1="0" x2="0" y2="100%" stroke="rgba(255,255,255,0.2)" stroke-width="1"/>
        <!-- Ratio 1.0 Reference Line -->
        <line x1="0" y1="50%" x2="100%" y2="50%" stroke="rgba(255,255,255,0.1)" stroke-width="1" stroke-dasharray="2 2"/>
        <!-- Dynamic Path -->
        <polyline id="graph-path" fill="none" stroke="var(--secondary-glow)" stroke-width="2" vector-effect="non-scaling-stroke" />
    </svg>
</div>

<script>
document.addEventListener("DOMContentLoaded", () => {
    // Math & Geometry Constants
    const R_BALL = 160;          // Radius of the traction ball
    const MAIN_AXIS_Y = 350;     // Distance from ball center to main system axis
    const BASE_ANGLE_DEG = 45;   // Base contact angle from horizontal when tilt is 0
    const BASE_ANGLE_RAD = BASE_ANGLE_DEG * Math.PI / 180;
    const INPUT_RPM = 3000;
    
    // SVG Elements
    const ballGroup = document.getElementById('traction-ball-group');
    const contactIn = document.getElementById('contact-in');
    const contactOut = document.getElementById('contact-out');
    const radInLine = document.getElementById('rad-in-line');
    const radOutLine = document.getElementById('rad-out-line');
    const radInText = document.getElementById('rad-in-text');
    const radOutText = document.getElementById('rad-out-text');
    const inputSpin = document.getElementById('input-spin');
    const outputSpin = document.getElementById('output-spin');
    
    // UI Elements
    const elTilt = document.getElementById('val-tilt');
    const elRatio = document.getElementById('val-ratio');
    const elOutRpm = document.getElementById('val-out-rpm');
    const graphPath = document.getElementById('graph-path');

    // Animation State
    let startTime = null;
    let inputSpinOffset = 0;
    let outputSpinOffset = 0;
    
    // Graph Data
    const graphHistory = [];
    const MAX_GRAPH_POINTS = 100;

    function update(timestamp) {
        if (!startTime) startTime = timestamp;
        const elapsed = timestamp - startTime;

        // 1. Calculate Tilt Angle (Oscillating between -20 and +20 degrees)
        // Using a smooth sine wave for automatic continuous demonstration
        const tiltDeg = 20 * Math.sin(elapsed * 0.0015);
        const tiltRad = tiltDeg * Math.PI / 180;

        // 2. Update Ball Tilt Visual
        ballGroup.setAttribute('transform', `rotate(${tiltDeg})`);

        // 3. Calculate Geometry & Contact Points
        // Input is on the left. Normal angle is 180 - BASE_ANGLE. As ball tilts right (+deg), contact point moves UP (smaller angle).
        const angleIn = Math.PI - BASE_ANGLE_RAD - tiltRad;
        const pxIn = R_BALL * Math.cos(angleIn);
        const pyIn = -R_BALL * Math.sin(angleIn); // Negative because SVG Y is down, we want Up visually
        
        // Output is on the right. Normal angle is BASE_ANGLE. As ball tilts right (+deg), contact point moves DOWN (smaller angle).
        const angleOut = BASE_ANGLE_RAD - tiltRad;
        const pxOut = R_BALL * Math.cos(angleOut);
        const pyOut = -R_BALL * Math.sin(angleOut);

        // 4. Calculate Radii relative to main axis (MAIN_AXIS_Y is positive down)
        const radiusIn = MAIN_AXIS_Y - pyIn;
        const radiusOut = MAIN_AXIS_Y - pyOut;
        
        // Ratio = R_in / R_out (Speed of output = Input * R_in / R_out)
        const ratio = radiusIn / radiusOut;
        const outputRPM = INPUT_RPM * ratio;

        // 5. Update SVG Positions
        // Contact point glowing ellipses (rotate them to align with tangent)
        contactIn.setAttribute('transform', `translate(${pxIn}, ${pyIn}) rotate(${ (angleIn * 180/Math.PI) + 90 })`);
        contactOut.setAttribute('transform', `translate(${pxOut}, ${pyOut}) rotate(${ (angleOut * 180/Math.PI) + 90 })`);

        // Radius lines (from main axis up to contact point)
        radInLine.setAttribute('x1', pxIn);
        radInLine.setAttribute('y1', MAIN_AXIS_Y);
        radInLine.setAttribute('x2', pxIn);
        radInLine.setAttribute('y2', pyIn);

        radOutLine.setAttribute('x1', pxOut);
        radOutLine.setAttribute('y1', MAIN_AXIS_Y);
        radOutLine.setAttribute('x2', pxOut);
        radOutLine.setAttribute('y2', pyOut);

        // Text positions
        radInText.setAttribute('x', pxIn);
        radInText.setAttribute('y', MAIN_AXIS_Y - radiusIn/2);
        radOutText.setAttribute('x', pxOut);
        radOutText.setAttribute('y', MAIN_AXIS_Y - radiusOut/2);

        // 6. Animate Rotation Spinners
        // Speed multiplier for visual effect
        const baseSpinSpeed = 2; 
        inputSpinOffset -= baseSpinSpeed;
        outputSpinOffset -= baseSpinSpeed * ratio;
        
        inputSpin.setAttribute('stroke-dashoffset', inputSpinOffset);
        outputSpin.setAttribute('stroke-dashoffset', outputSpinOffset);

        // 7. Update UI Text
        elTilt.textContent = (tiltDeg > 0 ? '+' : '') + tiltDeg.toFixed(2) + '°';
        elRatio.textContent = ratio.toFixed(3);
        elOutRpm.textContent = Math.round(outputRPM) + ' RPM';

        // 8. Update Graph
        graphHistory.push(ratio);
        if (graphHistory.length > MAX_GRAPH_POINTS) {
            graphHistory.shift();
        }
        
        // Build SVG polyline points for graph
        // Map ratio [0.5, 2.0] to Y [100%, 0%]
        const graphWidth = 350;
        const graphHeight = 90; // Fit within 120px panel
        
        let pointsString = "";
        graphHistory.forEach((val, index) => {
            const x = (index / (MAX_GRAPH_POINTS - 1)) * graphWidth;
            // Map ratio range (approx 0.6 to 1.6 based on geometry) to graph height
            // Let's strictly map 0.5 to bottom (graphHeight), 2.0 to top (0), 1.0 to center
            let y = graphHeight - ((val - 0.5) / 1.5) * graphHeight; 
            pointsString += `${x},${y} `;
        });
        graphPath.setAttribute('points', pointsString.trim());

        // Loop
        requestAnimationFrame(update);
    }

    // Start animation
    requestAnimationFrame(update);
});
</script>
</body>
</html>
积分规则:第一轮对话扣减8分,后续每轮扣6分