分享图
动画工坊
引擎就绪
<!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: #0b0f19;
            --grid-color: #1a2333;
            --text-main: #e2e8f0;
            --text-muted: #64748b;
            --screen-cyan: #00f3ff;
            --arm-orange: #f59e0b;
            --truss-blue: #1e293b;
            --icr-magenta: #e879f9;
            --danger-red: #ef4444;
        }

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

        /* 绝对避免遮挡中心动画的边缘文字层 */
        .overlay-layer {
            position: absolute;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            pointer-events: none;
            z-index: 10;
        }

        .text-block {
            position: absolute;
            background: rgba(11, 15, 25, 0.85);
            border: 1px solid rgba(255, 255, 255, 0.1);
            padding: 12px 16px;
            border-radius: 6px;
            backdrop-filter: blur(4px);
        }

        .top-left { top: 20px; left: 20px; max-width: 300px; }
        .top-right { top: 20px; right: 20px; max-width: 280px; text-align: right; }
        .bottom-left { bottom: 20px; left: 20px; max-width: 250px; }
        .bottom-right { bottom: 20px; right: 20px; max-width: 300px; }

        h1 {
            font-size: 14px;
            margin: 0 0 8px 0;
            color: var(--screen-cyan);
            letter-spacing: 1px;
            text-transform: uppercase;
        }

        p {
            font-size: 11px;
            line-height: 1.6;
            margin: 0;
            color: var(--text-muted);
        }

        .highlight { color: var(--arm-orange); font-weight: bold; }
        .highlight-icr { color: var(--icr-magenta); font-weight: bold; }

        .legend-item {
            display: flex;
            align-items: center;
            margin-top: 6px;
            font-size: 11px;
            color: var(--text-main);
            justify-content: flex-end;
        }

        .legend-item .color-box {
            width: 12px;
            height: 12px;
            margin-left: 8px;
            border-radius: 2px;
        }

        /* SVG 动画容器 */
        #sim-container {
            width: 100%;
            height: 100%;
            display: flex;
            justify-content: center;
            align-items: center;
            z-index: 1;
        }

        svg {
            width: 100%;
            height: 100%;
            max-width: 1200px;
            max-height: 900px;
        }

        /* SVG 元素样式 */
        .grid { stroke: var(--grid-color); stroke-width: 1; }
        .truss { fill: var(--truss-blue); stroke: #334155; stroke-width: 2; }
        .link { stroke: var(--arm-orange); stroke-linecap: round; stroke-linejoin: round; }
        .link-shadow { stroke: rgba(0,0,0,0.5); stroke-linecap: round; }
        .pivot { fill: #fff; stroke: var(--arm-orange); stroke-width: 3; }
        .pivot-ground { fill: #1e293b; stroke: var(--screen-cyan); stroke-width: 3; }
        
        .screen-body { fill: #0f172a; stroke: var(--screen-cyan); stroke-width: 2; }
        .screen-front { fill: var(--screen-cyan); opacity: 0.1; }
        .screen-led { stroke: var(--screen-cyan); stroke-width: 4; stroke-linecap: round; filter: drop-shadow(0 0 8px var(--screen-cyan)); }
        
        .trajectory { fill: none; stroke: rgba(255, 255, 255, 0.15); stroke-width: 1.5; stroke-dasharray: 4 4; }
        .trajectory-icr { fill: none; stroke: rgba(232, 121, 249, 0.3); stroke-width: 2; stroke-dasharray: 6 6; }
        
        .icr-line { stroke: var(--icr-magenta); stroke-width: 1; stroke-dasharray: 3 3; opacity: 0.5; }
        .icr-point { fill: var(--bg-color); stroke: var(--icr-magenta); stroke-width: 2; }
        .icr-glow { fill: var(--icr-magenta); opacity: 0.3; filter: blur(4px); }

        .force-arrow { fill: var(--danger-red); opacity: 0; transition: opacity 0.3s; }
        .visible { opacity: 0.8 !important; }
    </style>
</head>
<body>

    <!-- 绝对边缘定位的文字说明层,保证中心画面纯净 -->
    <div class="overlay-layer">
        <div class="text-block top-left">
            <h1>TRIZ最终理想解仿真</h1>
            <p>基于“非对称重型四连杆塞拉门机构”。<br>
               <span class="highlight">IFR核心:</span>不增加双电机与长导轨,利用纯连杆几何变形,在开启瞬间强制屏幕产生向后的“退让抽拉微动”,完美抵消厚度带来的前角干涉。</p>
        </div>

        <div class="text-block top-right">
            <h1>关键参数状态</h1>
            <p>短摇臂(L1): <span class="highlight">450mm</span></p>
            <p>长摇臂(L2): <span class="highlight">780mm</span></p>
            <p>当前曲柄角: <span id="angle-display">180</span>°</p>
            <p>驱动状态: <span id="status-display" class="highlight">闭合就绪</span></p>
        </div>

        <div class="text-block bottom-left">
            <h1>运动学拓扑 (俯视图)</h1>
            <p>瞬时旋转中心 (ICR) 轨迹外凸量约 150mm。四连杆的非对称性使得 ICR 随屏幕运动发生大范围偏移,从而在闭合末端 15° 将单纯的旋转转化为<span class="highlight">斜向平移贴合</span>。</p>
        </div>

        <div class="text-block bottom-right">
            <div class="legend-item">雷亚架/基座 <div class="color-box" style="background: var(--truss-blue); border: 1px solid #334155;"></div></div>
            <div class="legend-item">高强度合金摇臂 <div class="color-box" style="background: var(--arm-orange);"></div></div>
            <div class="legend-item">LED屏幕主体 <div class="color-box" style="background: var(--screen-cyan); box-shadow: 0 0 5px var(--screen-cyan);"></div></div>
            <div class="legend-item">瞬心(ICR)及辅助线 <div class="color-box" style="background: var(--icr-magenta);"></div></div>
        </div>
    </div>

    <!-- 核心 SVG 动画区 -->
    <div id="sim-container">
        <svg id="canvas" viewBox="-400 -300 800 600" preserveAspectRatio="xMidYMid meet">
            <!-- 动态背景网格 -->
            <defs>
                <pattern id="gridPattern" width="40" height="40" patternUnits="userSpaceOnUse">
                    <path d="M 40 0 L 0 0 0 40" fill="none" class="grid"/>
                </pattern>
                <filter id="glow">
                    <feGaussianBlur stdDeviation="3" result="coloredBlur"/>
                    <feMerge>
                        <feMergeNode in="coloredBlur"/>
                        <feMergeNode in="SourceGraphic"/>
                    </feMerge>
                </filter>
            </defs>
            <rect x="-1000" y="-1000" width="2000" height="2000" fill="url(#gridPattern)" />

            <!-- 静态雷亚架 (Truss) -->
            <g id="truss-group" transform="translate(180, 0)">
                <rect x="0" y="-200" width="80" height="400" class="truss" rx="4" />
                <path d="M0 -180 L80 -140 M0 -140 L80 -100 M0 -100 L80 -60 M0 -60 L80 -20 M0 -20 L80 20 M0 20 L80 60 M0 60 L80 100 M0 100 L80 140 M0 140 L80 180" stroke="#334155" stroke-width="4" fill="none"/>
                <!-- 电机指示 -->
                <circle cx="-45" cy="90" r="18" fill="#1e293b" stroke="#94a3b8" stroke-width="2"/>
                <text x="-45" y="94" fill="#94a3b8" font-size="10" text-anchor="middle">MOTOR</text>
            </g>

            <!-- 轨迹记录层 (初始化时由JS绘制) -->
            <g id="trajectories">
                <path id="path-icr" class="trajectory-icr" d="" />
                <path id="path-screen-corner" class="trajectory" d="" />
            </g>

            <!-- 瞬心辅助线与标记 -->
            <g id="icr-group">
                <line id="icr-line-1" class="icr-line" x1="0" y1="0" x2="0" y2="0" />
                <line id="icr-line-2" class="icr-line" x1="0" y1="0" x2="0" y2="0" />
                <circle id="icr-glow" class="icr-glow" cx="0" cy="0" r="15" />
                <circle id="icr-pt" class="icr-point" cx="0" cy="0" r="4" />
                <text id="icr-label" fill="var(--icr-magenta)" font-size="10" dx="10" dy="-10">ICR</text>
            </g>

            <!-- 机构主体 -->
            <g id="mechanism">
                <!-- 阴影层 -->
                <line id="link1-shadow" class="link-shadow" stroke-width="12" x1="0" y1="0" x2="0" y2="0" transform="translate(4,4)" />
                <line id="link2-shadow" class="link-shadow" stroke-width="14" x1="0" y1="0" x2="0" y2="0" transform="translate(4,4)" />
                
                <!-- 连杆层 -->
                <line id="link1" class="link" stroke-width="12" x1="0" y1="0" x2="0" y2="0" />
                <line id="link2" class="link" stroke-width="14" x1="0" y1="0" x2="0" y2="0" />

                <!-- 屏幕总成 (Coupler) -->
                <g id="screen-assembly">
                    <!-- 屏幕支架安装板 -->
                    <path d="M -10 -90 L 10 -90 L 10 90 L -10 90 Z" fill="#334155" stroke="#0f172a" />
                    <!-- 屏幕主体 -->
                    <rect x="-40" y="-180" width="30" height="360" rx="4" class="screen-body" />
                    <!-- LED发光面 (面向左侧) -->
                    <line x1="-40" y1="-176" x2="-40" y2="176" class="screen-led" />
                    <!-- 运动示意箭头 (抽拉微动) -->
                    <g id="pull-arrow" class="force-arrow" transform="translate(-60, 0)">
                        <path d="M 0 0 L -30 0 M -10 -10 L 0 0 L -10 10" stroke="var(--danger-red)" stroke-width="3" fill="none" stroke-linecap="round" stroke-linejoin="round"/>
                        <text x="-15" y="-15" fill="var(--danger-red)" font-size="12" text-anchor="middle">退让微动</text>
                    </g>
                </g>

                <!-- 铰接点 (Pivots) -->
                <!-- 地面铰点 (固定) -->
                <circle id="pivot-A" class="pivot-ground" cx="0" cy="0" r="8" />
                <circle id="pivot-D" class="pivot-ground" cx="0" cy="0" r="8" />
                <!-- 运动铰点 -->
                <circle id="pivot-B" class="pivot" cx="0" cy="0" r="6" />
                <circle id="pivot-C" class="pivot" cx="0" cy="0" r="6" />
            </g>
        </svg>
    </div>

    <script>
        /**
         * 非对称重型四连杆塞拉门机构 - 运动学求解与渲染
         * 采用严格的正向运动学(Forward Kinematics)圆交点算法保证连杆刚性不变。
         */

        // --- 核心几何参数 (已换算为适合屏幕显示的缩放比例) ---
        // 基于TRIZ IFR要求:实现开启时先抽拉后旋转,闭合时大角度旋转+末端斜向平移。
        // A为主动曲柄铰点(电机), D为从动摇臂铰点(雷亚架固定)
        const scale = 3;
        const A = { x: 45 * scale, y: 30 * scale };         // 坐标: (135, 90)
        const D = { x: 75 * scale, y: -51.5 * scale };      // 坐标: (225, -154.5)
        const L_AB = 45 * scale;                            // 短摇臂(135) - 对应实际450mm
        const L_CD = 234.06;                                // 长摇臂 - 对应实际780mm
        const L_BC = 60 * scale;                            // 屏幕安装孔距(180)

        // 动画控制状态
        let currentAngle = 180; // 初始为180度 (屏幕闭合状态)
        const minAngle = 180;   // 闭合极位
        const maxAngle = 290;   // 开启极位 (收纳在雷亚架侧边)
        let direction = 1;      // 1为开启,-1为闭合
        let isPaused = false;
        const speed = 0.6;      // 动画速度

        // DOM 元素引用
        const dom = {
            link1: document.getElementById('link1'),
            link2: document.getElementById('link2'),
            link1Shadow: document.getElementById('link1-shadow'),
            link2Shadow: document.getElementById('link2-shadow'),
            screenGroup: document.getElementById('screen-assembly'),
            pivotA: document.getElementById('pivot-A'),
            pivotD: document.getElementById('pivot-D'),
            pivotB: document.getElementById('pivot-B'),
            pivotC: document.getElementById('pivot-C'),
            icrLine1: document.getElementById('icr-line-1'),
            icrLine2: document.getElementById('icr-line-2'),
            icrPt: document.getElementById('icr-pt'),
            icrGlow: document.getElementById('icr-glow'),
            icrLabel: document.getElementById('icr-label'),
            pathIcr: document.getElementById('path-icr'),
            pathScreen: document.getElementById('path-screen-corner'),
            txtAngle: document.getElementById('angle-display'),
            txtStatus: document.getElementById('status-display'),
            pullArrow: document.getElementById('pull-arrow')
        };

        // 初始化静态底座锚点
        dom.pivotA.setAttribute('cx', A.x); dom.pivotA.setAttribute('cy', A.y);
        dom.pivotD.setAttribute('cx', D.x); dom.pivotD.setAttribute('cy', D.y);

        // 记录上一帧的C点,用于两圆交点方程的解选择,防止机构反转(Bifurcation)
        let lastC = { x: 0, y: -90 };

        /**
         * 计算给定曲柄角下,连杆机构的各点坐标
         */
        function solveKinematics(thetaDeg) {
            const theta = thetaDeg * Math.PI / 180;
            // B点坐标 (曲柄端点)
            const B = {
                x: A.x + L_AB * Math.cos(theta),
                y: A.y + L_AB * Math.sin(theta)
            };

            // 求解C点 (以B为圆心L_BC为半径的圆,与以D为圆心L_CD为半径的圆的交点)
            const d = Math.hypot(D.x - B.x, D.y - B.y);
            
            // 检查格拉霍夫机构连通性
            if (d > L_BC + L_CD || d < Math.abs(L_BC - L_CD)) {
                return null; // 无法构成三角形,卡死
            }

            const a = (L_BC**2 - L_CD**2 + d**2) / (2 * d);
            const h = Math.sqrt(Math.max(0, L_BC**2 - a**2));
            const P = {
                x: B.x + a * (D.x - B.x) / d,
                y: B.y + a * (D.y - B.y) / d
            };

            // 两个可能的解
            const C1 = {
                x: P.x + h * (D.y - B.y) / d,
                y: P.y - h * (D.x - B.x) / d
            };
            const C2 = {
                x: P.x - h * (D.y - B.y) / d,
                y: P.y + h * (D.x - B.x) / d
            };

            // 利用上一帧记录选择运动连续的解 (距离更近的)
            const dist1 = Math.hypot(C1.x - lastC.x, C1.y - lastC.y);
            const dist2 = Math.hypot(C2.x - lastC.x, C2.y - lastC.y);
            const C = (dist1 < dist2) ? C1 : C2;
            lastC = C;

            // 计算屏幕 (Coupler) 的位姿
            // 假设屏幕以BC连线为基准,中点为原点,与BC平行
            const center = {
                x: (B.x + C.x) / 2,
                y: (B.y + C.y) / 2
            };
            // 屏幕垂直于BC时,计算角度。由于闭合时B=(0,90), C=(0,-90), 矢量BC指向正下,角度为-90度。
            // 我们希望此时屏幕旋转角为0(垂直)。所以相对角度偏移为 +90度。
            const angleBC = Math.atan2(C.y - B.y, C.x - B.x) * 180 / Math.PI;
            const screenAngle = angleBC + 90;

            return { B, C, center, screenAngle };
        }

        /**
         * 计算两条直线的交点 (用于求瞬心 ICR)
         */
        function getIntersection(p1, p2, p3, p4) {
            const denom = (p1.x - p2.x)*(p3.y - p4.y) - (p1.y - p2.y)*(p3.x - p4.x);
            if (Math.abs(denom) < 0.1) return null; // 平行,瞬心在无穷远
            const x = ((p1.x*p2.y - p1.y*p2.x)*(p3.x - p4.x) - (p1.x - p2.x)*(p3.x*p4.y - p3.y*p4.x)) / denom;
            const y = ((p1.x*p2.y - p1.y*p2.x)*(p3.y - p4.y) - (p1.y - p2.y)*(p3.x*p4.y - p3.y*p4.x)) / denom;
            return {x, y};
        }

        /**
         * 更新DOM元素视觉呈现
         */
        function updateVisuals(kinematics) {
            if (!kinematics) return;
            const { B, C, center, screenAngle } = kinematics;

            // 更新连杆
            dom.link1.setAttribute('x1', A.x); dom.link1.setAttribute('y1', A.y);
            dom.link1.setAttribute('x2', B.x); dom.link1.setAttribute('y2', B.y);
            dom.link1Shadow.setAttribute('x1', A.x); dom.link1Shadow.setAttribute('y1', A.y);
            dom.link1Shadow.setAttribute('x2', B.x); dom.link1Shadow.setAttribute('y2', B.y);

            dom.link2.setAttribute('x1', D.x); dom.link2.setAttribute('y1', D.y);
            dom.link2.setAttribute('x2', C.x); dom.link2.setAttribute('y2', C.y);
            dom.link2Shadow.setAttribute('x1', D.x); dom.link2Shadow.setAttribute('y1', D.y);
            dom.link2Shadow.setAttribute('x2', C.x); dom.link2Shadow.setAttribute('y2', C.y);

            // 更新运动铰点
            dom.pivotB.setAttribute('cx', B.x); dom.pivotB.setAttribute('cy', B.y);
            dom.pivotC.setAttribute('cx', C.x); dom.pivotC.setAttribute('cy', C.y);

            // 更新屏幕位置及旋转
            dom.screenGroup.setAttribute('transform', `translate(${center.x}, ${center.y}) rotate(${screenAngle})`);

            // 计算并更新瞬心 ICR (直线AB与直线CD的交点)
            const icr = getIntersection(A, B, D, C);
            if (icr && Math.abs(icr.x) < 2000 && Math.abs(icr.y) < 2000) {
                dom.icrLine1.setAttribute('x1', B.x); dom.icrLine1.setAttribute('y1', B.y);
                dom.icrLine1.setAttribute('x2', icr.x); dom.icrLine1.setAttribute('y2', icr.y);
                
                dom.icrLine2.setAttribute('x1', C.x); dom.icrLine2.setAttribute('y1', C.y);
                dom.icrLine2.setAttribute('x2', icr.x); dom.icrLine2.setAttribute('y2', icr.y);

                dom.icrPt.setAttribute('cx', icr.x); dom.icrPt.setAttribute('cy', icr.y);
                dom.icrGlow.setAttribute('cx', icr.x); dom.icrGlow.setAttribute('cy', icr.y);
                dom.icrLabel.setAttribute('x', icr.x); dom.icrLabel.setAttribute('y', icr.y);
                
                dom.icrPt.style.opacity = 1;
                dom.icrGlow.style.opacity = 1;
                dom.icrLabel.style.opacity = 1;
                dom.icrLine1.style.opacity = 0.5;
                dom.icrLine2.style.opacity = 0.5;
            } else {
                // 接近无穷远时隐藏
                dom.icrPt.style.opacity = 0;
                dom.icrGlow.style.opacity = 0;
                dom.icrLabel.style.opacity = 0;
                dom.icrLine1.style.opacity = 0;
                dom.icrLine2.style.opacity = 0;
            }

            // 更新UI文字
            dom.txtAngle.textContent = currentAngle.toFixed(1);
            if (currentAngle < 195 && direction === -1) {
                // 闭合末端:显示“退让抽拉微动”提示
                dom.pullArrow.classList.add('visible');
                dom.txtStatus.textContent = "退让贴合中...";
                dom.txtStatus.style.color = "var(--danger-red)";
            } else if (currentAngle < 195 && direction === 1) {
                // 开启初段
                dom.pullArrow.classList.add('visible');
                dom.txtStatus.textContent = "抽离启动...";
                dom.txtStatus.style.color = "var(--danger-red)";
            } else {
                dom.pullArrow.classList.remove('visible');
                dom.txtStatus.textContent = direction === 1 ? "折叠收纳中..." : "展开闭合中...";
                dom.txtStatus.style.color = "var(--arm-orange)";
            }
        }

        /**
         * 预计算并绘制特征轨迹线 (视觉引导)
         */
        function drawTrajectories() {
            let pathICR = "";
            let pathScreen = "";
            
            // 记录屏幕左上角的运动轨迹
            // 局部坐标: 屏幕中心(0,0),左上角约位于 (-40, -180)
            const localCorner = { x: -40, y: -180 };

            for (let a = minAngle; a <= maxAngle; a += 2) {
                const k = solveKinematics(a);
                if (!k) continue;

                // 计算屏幕左上角的世界坐标
                const rad = k.screenAngle * Math.PI / 180;
                const cornerX = k.center.x + localCorner.x * Math.cos(rad) - localCorner.y * Math.sin(rad);
                const cornerY = k.center.y + localCorner.x * Math.sin(rad) + localCorner.y * Math.cos(rad);

                if (a === minAngle) {
                    pathScreen += `M ${cornerX} ${cornerY} `;
                } else {
                    pathScreen += `L ${cornerX} ${cornerY} `;
                }

                // 计算ICR轨迹
                const icr = getIntersection(A, k.B, D, k.C);
                if (icr && Math.abs(icr.x) < 800 && Math.abs(icr.y) < 800) {
                    if (pathICR === "") {
                        pathICR += `M ${icr.x} ${icr.y} `;
                    } else {
                        pathICR += `L ${icr.x} ${icr.y} `;
                    }
                } else {
                    pathICR += ""; // 断点
                }
            }

            dom.pathIcr.setAttribute('d', pathICR);
            dom.pathScreen.setAttribute('d', pathScreen);
        }

        /**
         * 主动画循环 (自动播放,不依赖交互)
         */
        function animateLoop() {
            if (!isPaused) {
                currentAngle += speed * direction;

                if (currentAngle >= maxAngle) {
                    currentAngle = maxAngle;
                    isPaused = true;
                    dom.txtStatus.textContent = "完全开启 (收纳)";
                    dom.txtStatus.style.color = "var(--text-muted)";
                    setTimeout(() => { direction = -1; isPaused = false; }, 1500);
                } else if (currentAngle <= minAngle) {
                    currentAngle = minAngle;
                    isPaused = true;
                    dom.txtStatus.textContent = "完全闭合 (工作位)";
                    dom.txtStatus.style.color = "var(--screen-cyan)";
                    setTimeout(() => { direction = 1; isPaused = false; }, 1500);
                }

                const kinematics = solveKinematics(currentAngle);
                updateVisuals(kinematics);
            }
            
            requestAnimationFrame(animateLoop);
        }

        // 初始化执行
        window.addEventListener('DOMContentLoaded', () => {
            // 首先预计算静态轨迹并渲染
            lastC = { x: 0, y: -90 }; // 重置保证正确解
            drawTrajectories();
            
            // 强制重置确保从初始极位自动开始
            currentAngle = 180;
            direction = 1;
            lastC = { x: 0, y: -90 }; 
            
            const initialKinematics = solveKinematics(currentAngle);
            updateVisuals(initialKinematics);

            // 启动动画循环
            requestAnimationFrame(animateLoop);
        });
    </script>
</body>
</html>
积分规则:第一轮对话扣减8分,后续每轮扣6分