独立渲染引擎就绪引擎就绪
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>高保真 IFR 原理演示:重型多连杆屏幕驱动机构</title>
<style>
:root {
--bg-color: #09090b;
--grid-color: rgba(39, 39, 42, 0.6);
--truss-color: #27272a;
--truss-edge: #52525b;
--screen-color: #0ea5e9;
--screen-glow: rgba(14, 165, 233, 0.4);
--link-color: #d4d4d8;
--link-stroke: #18181b;
--motor-color: #f59e0b;
--traj-color: #10b981;
--vic-color: #f43f5e;
--text-main: #f4f4f5;
--text-muted: #a1a1aa;
--panel-bg: rgba(9, 9, 11, 0.85);
}
body, html {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
background-color: var(--bg-color);
color: var(--text-main);
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
overflow: hidden;
display: flex;
justify-content: center;
align-items: center;
}
#app-container {
position: relative;
width: 100vw;
height: 100vh;
}
svg {
width: 100%;
height: 100%;
display: block;
}
/* 绝对定位的信息面板,小字号,靠边缘,不遮挡主体 */
.info-panel {
position: absolute;
background: var(--panel-bg);
border: 1px solid #3f3f46;
padding: 16px;
border-radius: 8px;
backdrop-filter: blur(8px);
-webkit-backdrop-filter: blur(8px);
pointer-events: none; /* 避免干扰鼠标交互,虽然本例主要是自动播放 */
z-index: 10;
}
.panel-top-left {
top: 24px;
left: 24px;
max-width: 320px;
}
.panel-bottom-right {
bottom: 24px;
right: 24px;
max-width: 280px;
}
h1 {
font-size: 14px;
margin: 0 0 12px 0;
color: #fff;
font-weight: 600;
letter-spacing: 1px;
border-bottom: 1px solid #3f3f46;
padding-bottom: 8px;
}
.tag-group {
display: flex;
gap: 6px;
margin-bottom: 12px;
flex-wrap: wrap;
}
.tag {
font-size: 10px;
padding: 2px 6px;
background: rgba(245, 158, 11, 0.15);
color: var(--motor-color);
border: 1px solid rgba(245, 158, 11, 0.3);
border-radius: 4px;
}
p {
font-size: 12px;
line-height: 1.6;
color: var(--text-muted);
margin: 0 0 8px 0;
}
p:last-child {
margin-bottom: 0;
}
.data-row {
display: flex;
justify-content: space-between;
font-size: 12px;
margin-bottom: 6px;
border-bottom: 1px dashed #27272a;
padding-bottom: 4px;
}
.data-label { color: var(--text-muted); }
.data-value { color: var(--text-main); font-weight: bold; }
.highlight-value { color: var(--vic-color); }
.legend {
margin-top: 12px;
display: flex;
flex-direction: column;
gap: 6px;
}
.legend-item {
display: flex;
align-items: center;
gap: 8px;
font-size: 11px;
color: var(--text-muted);
}
.legend-color {
width: 12px;
height: 2px;
}
/* SVG 元素动画与样式 */
.glow-screen {
filter: drop-shadow(0 0 15px var(--screen-glow)) drop-shadow(0 0 5px var(--screen-glow));
}
.glow-motor {
filter: drop-shadow(0 0 10px rgba(245, 158, 11, 0.6));
}
#trajectory-path {
stroke-dasharray: 6 6;
animation: dashFlow 20s linear infinite;
}
@keyframes dashFlow {
from { stroke-dashoffset: 1000; }
to { stroke-dashoffset: 0; }
}
@keyframes rotateMotor {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
</style>
</head>
<body>
<div id="app-container">
<!-- 背景图层与动画图层 -->
<svg viewBox="0 0 1600 1000" 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="1"/>
</pattern>
<!-- 桁架斜纹理 -->
<pattern id="hatch" width="10" height="10" patternTransform="rotate(45)" patternUnits="userSpaceOnUse">
<line x1="0" y1="0" x2="0" y2="10" stroke="#3f3f46" stroke-width="2" />
</pattern>
<marker id="arrow" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse">
<path d="M 0 1 L 10 5 L 0 9 z" fill="var(--vic-color)" />
</marker>
</defs>
<!-- 背景 -->
<rect width="100%" height="100%" fill="url(#grid)" />
<g id="scene-center" transform="translate(0, 0)">
<!-- 固定桁架 (雷亚架) -->
<rect x="200" y="200" width="1200" height="80" fill="url(#hatch)" stroke="var(--truss-edge)" stroke-width="4" rx="4"/>
<text x="800" y="245" fill="var(--text-muted)" font-size="24" text-anchor="middle" font-weight="bold" letter-spacing="8">FIXED TRUSS (固定雷亚架)</text>
<!-- 轨迹层 -->
<path id="trajectory-path" fill="none" stroke="var(--traj-color)" stroke-width="3" opacity="0.7"/>
<!-- 虚拟瞬心连线层 -->
<line id="vic-line-ad" stroke="var(--vic-color)" stroke-width="1" stroke-dasharray="8 4" opacity="0.6"/>
<line id="vic-line-bc" stroke="var(--vic-color)" stroke-width="1" stroke-dasharray="8 4" opacity="0.6"/>
<!-- 机构主体层 -->
<g id="mechanism">
<!-- 连杆 BC (从动) -->
<line id="link-bc" stroke="var(--link-color)" stroke-width="16" stroke-linecap="round"/>
<line id="link-bc-inner" stroke="var(--link-stroke)" stroke-width="4" stroke-linecap="round"/>
<!-- 连杆 AD (主动) -->
<line id="link-ad" stroke="var(--link-color)" stroke-width="20" stroke-linecap="round"/>
<line id="link-ad-inner" stroke="var(--link-stroke)" stroke-width="6" stroke-linecap="round"/>
<!-- 屏幕桁架 (Coupler 延伸) -->
<line id="screen-bar" class="glow-screen" stroke="var(--screen-color)" stroke-width="24" stroke-linecap="round"/>
<!-- 屏幕厚度细节 -->
<line id="screen-bar-detail" stroke="#fff" stroke-width="4" stroke-linecap="round" opacity="0.8"/>
<!-- 铰接点 / Pivot Points -->
<!-- 屏幕上的铰点 D 和 C -->
<circle id="pivot-d" r="10" fill="#fff" stroke="var(--link-stroke)" stroke-width="3"/>
<circle id="pivot-c" r="8" fill="#fff" stroke="var(--link-stroke)" stroke-width="3"/>
<!-- 固定铰点 B -->
<circle cx="1000" cy="280" r="12" fill="#52525b" stroke="#18181b" stroke-width="4"/>
<circle cx="1000" cy="280" r="4" fill="#a1a1aa"/>
<!-- 固定主动铰点 A (电机) -->
<g class="glow-motor" transform="translate(600, 280)">
<circle r="18" fill="var(--motor-color)" stroke="#18181b" stroke-width="4"/>
<!-- 旋转指示器 -->
<g id="motor-rotator">
<line x1="-12" y1="0" x2="12" y2="0" stroke="#18181b" stroke-width="4" stroke-linecap="round"/>
<line x1="0" y1="-12" x2="0" y2="12" stroke="#18181b" stroke-width="4" stroke-linecap="round"/>
</g>
<circle r="4" fill="#fff"/>
</g>
<!-- 虚拟瞬心 (VIC) 标记 -->
<g id="vic-point-group" opacity="0">
<circle id="vic-point" r="6" fill="var(--vic-color)"/>
<circle r="12" fill="none" stroke="var(--vic-color)" stroke-width="2">
<animate attributeName="r" values="6;24;6" dur="2s" repeatCount="indefinite"/>
<animate attributeName="opacity" values="1;0;1" dur="2s" repeatCount="indefinite"/>
</circle>
<text x="15" y="4" fill="var(--vic-color)" font-size="12">虚拟瞬心 (VIC)</text>
</g>
</g>
</g>
</svg>
<!-- UI 面板 -->
<div class="info-panel panel-top-left">
<h1>TRIZ 最终理想解 (IFR)</h1>
<div class="tag-group">
<span class="tag">单一驱动源</span>
<span class="tag">连续动作路径</span>
<span class="tag">四连杆机构</span>
</div>
<p><strong>方案原理:</strong>引入重型四连杆机构,废弃单一转轴。利用曲柄摇杆机构的耦合轨迹特性,将原本离散的“先平移、后旋转”两段式动作,融合成一条连续、丝滑的空间运动曲线。</p>
<p><strong>核心机理:</strong>屏幕开启初段,受连杆几何约束,瞬时旋转中心位于极远处,屏幕表现为近乎直线的“向外平移抽出”;随后瞬心迅速拉近,屏幕平滑过渡至大角度旋转展开。</p>
<div class="legend">
<div class="legend-item"><div class="legend-color" style="background: var(--screen-color); box-shadow: 0 0 5px var(--screen-color);"></div> 移动屏幕桁架 (12m×4m 缩比)</div>
<div class="legend-item"><div class="legend-color" style="background: var(--motor-color);"></div> 主动驱动源 (单一电机)</div>
<div class="legend-item"><div class="legend-color" style="background: var(--traj-color);"></div> 屏幕远端连续运动轨迹</div>
<div class="legend-item"><div class="legend-color" style="border-top: 2px dashed var(--vic-color);"></div> 动态虚拟瞬心 (Instant Center) 连线</div>
</div>
</div>
<div class="info-panel panel-bottom-right">
<div class="data-row">
<span class="data-label">运行状态:</span>
<span class="data-value" style="color: var(--traj-color);">自动演示中 / Auto Play</span>
</div>
<div class="data-row">
<span class="data-label">主连杆 (Crank) 长度:</span>
<span class="data-value">800 mm</span>
</div>
<div class="data-row">
<span class="data-label">从动杆 (Rocker) 长度:</span>
<span class="data-value">1200 mm</span>
</div>
<div class="data-row">
<span class="data-label">屏幕远端偏移角:</span>
<span class="data-value" id="ui-angle">0.0°</span>
</div>
<div class="data-row">
<span class="data-label">瞬心(VIC)距表面:</span>
<span class="data-value highlight-value" id="ui-vic">计算中...</span>
</div>
</div>
</div>
<script>
// 机构几何参数定义 (基于 SVG 坐标系)
const K = {
Ax: 600, Ay: 280, // 主动铰点 A (电机)
Bx: 1000, By: 280, // 从动固定铰点 B
r1: 160, // 主连杆 AD 长度 (对应 800mm)
r2: 240, // 从动连杆 BC 长度 (对应 1200mm)
r3: 350, // 连杆点距离 DC
screenExtendLeft: 100, // 屏幕向左延伸
screenExtendRight: 350, // 屏幕向右延伸 (远端)
};
// DOM 元素引用
const els = {
linkAD: document.getElementById('link-ad'),
linkADInner: document.getElementById('link-ad-inner'),
linkBC: document.getElementById('link-bc'),
linkBCInner: document.getElementById('link-bc-inner'),
screenBar: document.getElementById('screen-bar'),
screenBarDetail: document.getElementById('screen-bar-detail'),
pivotD: document.getElementById('pivot-d'),
pivotC: document.getElementById('pivot-c'),
motorRotator: document.getElementById('motor-rotator'),
trajPath: document.getElementById('trajectory-path'),
vicLineAD: document.getElementById('vic-line-ad'),
vicLineBC: document.getElementById('vic-line-bc'),
vicPointGroup: document.getElementById('vic-point-group'),
vicPoint: document.getElementById('vic-point'),
uiAngle: document.getElementById('ui-angle'),
uiVic: document.getElementById('ui-vic')
};
// 四连杆运动学求解器
// 给定曲柄角度 theta1,求另外两点 D, C 的坐标
function solveFourBar(theta1) {
// 曲柄末端 D 坐标
const Dx = K.Ax + K.r1 * Math.cos(theta1);
const Dy = K.Ay + K.r1 * Math.sin(theta1);
// B 到 D 的距离 Ldb
const Ldb = Math.hypot(K.Bx - Dx, K.By - Dy);
// 机构装配干涉保护
if (Ldb > K.r2 + K.r3 || Ldb < Math.abs(K.r2 - K.r3)) {
return null; // 无法构成三角形
}
// B->D 向量的角度
const phi = Math.atan2(K.By - Dy, K.Bx - Dx);
// 余弦定理求角 gamma (三角形 B-D-C,边长 r2, r3, Ldb)
const cosGamma = (K.r3 * K.r3 + Ldb * Ldb - K.r2 * K.r2) / (2 * K.r3 * Ldb);
const gamma = Math.acos(Math.max(-1, Math.min(1, cosGamma))); // 防止精度溢出
// 两种装配形态,取其一 (elbow down configuration)
const theta3 = phi - gamma;
// 从动连杆上铰点 C 坐标
const Cx = Dx + K.r3 * Math.cos(theta3);
const Cy = Dy + K.r3 * Math.sin(theta3);
return { Dx, Dy, Cx, Cy };
}
// 计算两条直线的交点 (用于求虚拟瞬心 VIC)
// 直线1过 (x1,y1) (x2,y2),直线2过 (x3,y3) (x4,y4)
function getIntersection(x1, y1, x2, y2, x3, y3, x4, y4) {
const denom = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4);
if (Math.abs(denom) < 0.1) return null; // 平行或重合,瞬心在无穷远
const px = ((x1 * y2 - y1 * x2) * (x3 - x4) - (x1 - x2) * (x3 * y4 - y3 * x4)) / denom;
const py = ((x1 * y2 - y1 * x2) * (y3 - y4) - (y1 - y2) * (x3 * y4 - y3 * x4)) / denom;
return { x: px, y: py };
}
// 动画配置
const animConfig = {
startAngle: 0.1, // 闭合状态角度 (接近水平向右)
endAngle: Math.PI * 0.85, // 开启状态角度 (左下)
speed: 0.0006, // 运行速度
pathData: [] // 预计算的轨迹点
};
// 预计算运动轨迹,用于绘制背景虚线
function precalculatePath() {
let pathString = "";
const steps = 100;
for (let i = 0; i <= steps; i++) {
const t = i / steps;
const theta = animConfig.startAngle + t * (animConfig.endAngle - animConfig.startAngle);
const pts = solveFourBar(theta);
if (pts) {
// 计算屏幕远端点坐标
const dx = pts.Cx - pts.Dx;
const dy = pts.Cy - pts.Dy;
const len = Math.hypot(dx, dy);
const ux = dx / len;
const uy = dy / len;
// 远端点 E
const Ex = pts.Dx - ux * K.screenExtendLeft; // 这里用左端作为轨迹演示,因为视觉上展开幅度更大
const Ey = pts.Dy - uy * K.screenExtendLeft;
if (i === 0) pathString += `M ${Ex} ${Ey} `;
else pathString += `L ${Ex} ${Ey} `;
}
}
els.trajPath.setAttribute('d', pathString);
}
// 主渲染帧
function renderFrame(time) {
// 利用正弦函数实现往复自动播放 (0 到 1 之间振荡)
const progress = (Math.sin(time * animConfig.speed) + 1) / 2;
// 映射到角度范围
const currentTheta = animConfig.startAngle + progress * (animConfig.endAngle - animConfig.startAngle);
// 更新电机视觉旋转
els.motorRotator.setAttribute('transform', `rotate(${(currentTheta * 180 / Math.PI)} 0 0)`);
const pts = solveFourBar(currentTheta);
if (pts) {
const { Dx, Dy, Cx, Cy } = pts;
// 更新主动连杆 AD
els.linkAD.setAttribute('x1', K.Ax); els.linkAD.setAttribute('y1', K.Ay);
els.linkAD.setAttribute('x2', Dx); els.linkAD.setAttribute('y2', Dy);
els.linkADInner.setAttribute('x1', K.Ax); els.linkADInner.setAttribute('y1', K.Ay);
els.linkADInner.setAttribute('x2', Dx); els.linkADInner.setAttribute('y2', Dy);
// 更新从动连杆 BC
els.linkBC.setAttribute('x1', K.Bx); els.linkBC.setAttribute('y1', K.By);
els.linkBC.setAttribute('x2', Cx); els.linkBC.setAttribute('y2', Cy);
els.linkBCInner.setAttribute('x1', K.Bx); els.linkBCInner.setAttribute('y1', K.By);
els.linkBCInner.setAttribute('x2', Cx); els.linkBCInner.setAttribute('y2', Cy);
// 计算屏幕延伸端点
const vecX = Cx - Dx;
const vecY = Cy - Dy;
const len = Math.hypot(vecX, vecY);
const ux = vecX / len;
const uy = vecY / len;
const screenStartX = Dx - ux * K.screenExtendLeft;
const screenStartY = Dy - uy * K.screenExtendLeft;
const screenEndX = Cx + ux * K.screenExtendRight;
const screenEndY = Cy + uy * K.screenExtendRight;
// 更新屏幕 DOM
els.screenBar.setAttribute('x1', screenStartX); els.screenBar.setAttribute('y1', screenStartY);
els.screenBar.setAttribute('x2', screenEndX); els.screenBar.setAttribute('y2', screenEndY);
els.screenBarDetail.setAttribute('x1', screenStartX); els.screenBarDetail.setAttribute('y1', screenStartY);
els.screenBarDetail.setAttribute('x2', screenEndX); els.screenBarDetail.setAttribute('y2', screenEndY);
// 更新铰点视觉
els.pivotD.setAttribute('cx', Dx); els.pivotD.setAttribute('cy', Dy);
els.pivotC.setAttribute('cx', Cx); els.pivotC.setAttribute('cy', Cy);
// 计算并更新虚拟瞬心 (VIC)
const vic = getIntersection(K.Ax, K.Ay, Dx, Dy, K.Bx, K.By, Cx, Cy);
if (vic && vic.y > -2000 && vic.y < 3000 && vic.x > -2000 && vic.x < 3000) {
// 瞬心在可视或合理计算范围内
els.vicPointGroup.setAttribute('opacity', '1');
els.vicPointGroup.setAttribute('transform', `translate(${vic.x}, ${vic.y})`);
// 绘制延长线
els.vicLineAD.setAttribute('x1', Dx); els.vicLineAD.setAttribute('y1', Dy);
els.vicLineAD.setAttribute('x2', vic.x); els.vicLineAD.setAttribute('y2', vic.y);
els.vicLineBC.setAttribute('x1', Cx); els.vicLineBC.setAttribute('y1', Cy);
els.vicLineBC.setAttribute('x2', vic.x); els.vicLineBC.setAttribute('y2', vic.y);
// 更新 UI 数据
const distToScreen = Math.hypot(vic.x - (Dx+Cx)/2, vic.y - (Dy+Cy)/2);
// 近似换算为物理尺寸,起步极远(>5000),随后迅速拉近
let realDist = Math.round(distToScreen * 5);
if(realDist > 9999) realDist = "∞ (直线平移)";
else realDist = realDist + " mm";
els.uiVic.innerText = realDist;
// 根据距离改变颜色提示(距离越远,颜色越趋近平移蓝色;距离近,显示为旋转红色)
if(distToScreen > 1000) {
els.uiVic.style.color = "var(--screen-color)";
} else {
els.uiVic.style.color = "var(--vic-color)";
}
} else {
// 瞬心趋于无穷远 (绝对平移瞬间)
els.vicPointGroup.setAttribute('opacity', '0');
els.vicLineAD.setAttribute('x1', 0); els.vicLineAD.setAttribute('y1', 0); els.vicLineAD.setAttribute('x2', 0); els.vicLineAD.setAttribute('y2', 0);
els.vicLineBC.setAttribute('x1', 0); els.vicLineBC.setAttribute('y1', 0); els.vicLineBC.setAttribute('x2', 0); els.vicLineBC.setAttribute('y2', 0);
els.uiVic.innerText = "∞ (直线平移)";
els.uiVic.style.color = "var(--screen-color)";
}
// 更新角度 UI
const angleDeg = (progress * 95).toFixed(1); // 近似模拟开合角
els.uiAngle.innerText = `${angleDeg}°`;
}
requestAnimationFrame(renderFrame);
}
// 初始化运行
document.addEventListener("DOMContentLoaded", () => {
precalculatePath();
requestAnimationFrame(renderFrame);
});
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>高保真 IFR 原理演示:对开式重型多连杆屏幕驱动机构</title>
<style>
:root {
--bg-color: #09090b;
--grid-color: rgba(39, 39, 42, 0.6);
--truss-color: #27272a;
--truss-edge: #52525b;
--screen-color: #0ea5e9;
--screen-color-left: #06b6d4;
--screen-glow: rgba(14, 165, 233, 0.4);
--link-color: #d4d4d8;
--link-stroke: #18181b;
--motor-color: #f59e0b;
--shaft-color: #fbbf24;
--traj-color: #10b981;
--text-main: #f4f4f5;
--text-muted: #a1a1aa;
--panel-bg: rgba(9, 9, 11, 0.85);
--danger: #ef4444;
}
body, html {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
background-color: var(--bg-color);
color: var(--text-main);
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
overflow: hidden;
display: flex;
justify-content: center;
align-items: center;
}
#app-container {
position: relative;
width: 100vw;
height: 100vh;
}
svg {
width: 100%;
height: 100%;
display: block;
}
.info-panel {
position: absolute;
background: var(--panel-bg);
border: 1px solid #3f3f46;
padding: 16px;
border-radius: 8px;
backdrop-filter: blur(8px);
-webkit-backdrop-filter: blur(8px);
pointer-events: none;
z-index: 10;
}
.panel-top-left {
top: 24px;
left: 24px;
max-width: 340px;
}
.panel-bottom-right {
bottom: 24px;
right: 24px;
max-width: 280px;
}
h1 {
font-size: 14px;
margin: 0 0 12px 0;
color: #fff;
font-weight: 600;
letter-spacing: 1px;
border-bottom: 1px solid #3f3f46;
padding-bottom: 8px;
}
.tag-group {
display: flex;
gap: 6px;
margin-bottom: 12px;
flex-wrap: wrap;
}
.tag {
font-size: 10px;
padding: 2px 6px;
background: rgba(245, 158, 11, 0.15);
color: var(--motor-color);
border: 1px solid rgba(245, 158, 11, 0.3);
border-radius: 4px;
}
.tag-blue {
background: rgba(14, 165, 233, 0.15);
color: var(--screen-color);
border-color: rgba(14, 165, 233, 0.3);
}
p {
font-size: 12px;
line-height: 1.6;
color: var(--text-muted);
margin: 0 0 8px 0;
}
.data-row {
display: flex;
justify-content: space-between;
font-size: 12px;
margin-bottom: 6px;
border-bottom: 1px dashed #27272a;
padding-bottom: 4px;
}
.data-label { color: var(--text-muted); }
.data-value { color: var(--text-main); font-weight: bold; }
.legend {
margin-top: 12px;
display: flex;
flex-direction: column;
gap: 6px;
}
.legend-item {
display: flex;
align-items: center;
gap: 8px;
font-size: 11px;
color: var(--text-muted);
}
.legend-color {
width: 12px;
height: 2px;
}
.glow-screen { filter: drop-shadow(0 0 12px var(--screen-glow)); }
.glow-motor { filter: drop-shadow(0 0 10px rgba(245, 158, 11, 0.8)); }
.glow-shaft { filter: drop-shadow(0 0 4px rgba(251, 191, 36, 0.5)); }
.trajectory-path {
stroke-dasharray: 4 6;
animation: dashFlow 15s linear infinite;
}
@keyframes dashFlow {
from { stroke-dashoffset: 500; }
to { stroke-dashoffset: 0; }
}
</style>
</head>
<body>
<div id="app-container">
<svg viewBox="0 0 1600 1000" 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="1"/>
<circle cx="0" cy="0" r="1" fill="#3f3f46" />
</pattern>
<pattern id="hatch" width="12" height="12" patternTransform="rotate(45)" patternUnits="userSpaceOnUse">
<line x1="0" y1="0" x2="0" y2="12" stroke="#3f3f46" stroke-width="3" />
</pattern>
</defs>
<!-- 基础网格 -->
<rect width="100%" height="100%" fill="url(#grid)" />
<!-- 中心对称轴 -->
<line x1="800" y1="0" x2="800" y2="1000" stroke="#3f3f46" stroke-width="1" stroke-dasharray="10 10" opacity="0.5"/>
<text x="810" y="50" fill="#52525b" font-size="12" letter-spacing="2">SYMMETRY AXIS / X=800</text>
<!-- 顶部固定雷亚架 (俯视图) -->
<g id="fixed-frame">
<rect x="300" y="160" width="1000" height="100" fill="url(#hatch)" stroke="var(--truss-edge)" stroke-width="4" rx="4"/>
<text x="800" y="215" fill="#71717a" font-size="20" text-anchor="middle" font-weight="bold" letter-spacing="6">MAIN GANTRY TRUSS (TOP VIEW)</text>
</g>
<!-- 中央驱动源与传动轴 -->
<g id="central-drive">
<!-- 传动轴 -->
<line id="shaft-left" class="glow-shaft" x1="800" y1="210" x2="650" y2="210" stroke="var(--shaft-color)" stroke-width="6" stroke-linecap="round"/>
<line id="shaft-right" class="glow-shaft" x1="800" y1="210" x2="950" y2="210" stroke="var(--shaft-color)" stroke-width="6" stroke-linecap="round"/>
<!-- 中央电机 -->
<g class="glow-motor" transform="translate(800, 210)">
<rect x="-30" y="-25" width="60" height="50" fill="#27272a" stroke="var(--motor-color)" stroke-width="3" rx="6"/>
<circle r="14" fill="var(--motor-color)"/>
<g id="central-rotator">
<line x1="-10" y1="0" x2="10" y2="0" stroke="#18181b" stroke-width="3"/>
<line x1="0" y1="-10" x2="0" y2="10" stroke="#18181b" stroke-width="3"/>
</g>
<text x="0" y="45" fill="var(--motor-color)" font-size="11" text-anchor="middle" font-weight="bold">SINGLE DRIVE</text>
</g>
</g>
<!-- 轨迹层 -->
<path id="traj-right" class="trajectory-path" fill="none" stroke="var(--traj-color)" stroke-width="2" opacity="0.8"/>
<path id="traj-left" class="trajectory-path" fill="none" stroke="var(--traj-color)" stroke-width="2" opacity="0.8"/>
<!-- 右侧机构 -->
<g id="mech-right">
<line id="r-link-bc" stroke="var(--link-color)" stroke-width="16" stroke-linecap="round"/>
<line id="r-link-bc-in" stroke="var(--link-stroke)" stroke-width="4" stroke-linecap="round"/>
<line id="r-link-ad" stroke="var(--link-color)" stroke-width="20" stroke-linecap="round"/>
<line id="r-link-ad-in" stroke="var(--link-stroke)" stroke-width="6" stroke-linecap="round"/>
<!-- 右侧门体 -->
<line id="r-door" class="glow-screen" stroke="var(--screen-color)" stroke-width="28" stroke-linecap="square"/>
<line id="r-door-hl" stroke="#fff" stroke-width="4" stroke-linecap="square" opacity="0.6"/>
<!-- 门体密封齿 -->
<circle id="r-door-tip" r="10" fill="#fff" stroke="var(--screen-color)" stroke-width="4"/>
<circle id="r-pivot-d" r="8" fill="#fff" stroke="var(--link-stroke)" stroke-width="3"/>
<circle id="r-pivot-c" r="8" fill="#fff" stroke="var(--link-stroke)" stroke-width="3"/>
<circle cx="1150" cy="210" r="10" fill="#52525b" stroke="#18181b" stroke-width="3"/> <!-- B点固定 -->
<circle cx="950" cy="210" r="12" fill="var(--shaft-color)" stroke="#18181b" stroke-width="3"/> <!-- A点主动 -->
</g>
<!-- 左侧机构 (对称镜像) -->
<g id="mech-left">
<line id="l-link-bc" stroke="var(--link-color)" stroke-width="16" stroke-linecap="round"/>
<line id="l-link-bc-in" stroke="var(--link-stroke)" stroke-width="4" stroke-linecap="round"/>
<line id="l-link-ad" stroke="var(--link-color)" stroke-width="20" stroke-linecap="round"/>
<line id="l-link-ad-in" stroke="var(--link-stroke)" stroke-width="6" stroke-linecap="round"/>
<!-- 左侧门体 -->
<line id="l-door" class="glow-screen" stroke="var(--screen-color-left)" stroke-width="28" stroke-linecap="square"/>
<line id="l-door-hl" stroke="#fff" stroke-width="4" stroke-linecap="square" opacity="0.6"/>
<circle id="l-door-tip" r="10" fill="#fff" stroke="var(--screen-color-left)" stroke-width="4"/>
<circle id="l-pivot-d" r="8" fill="#fff" stroke="var(--link-stroke)" stroke-width="3"/>
<circle id="l-pivot-c" r="8" fill="#fff" stroke="var(--link-stroke)" stroke-width="3"/>
<circle cx="450" cy="210" r="10" fill="#52525b" stroke="#18181b" stroke-width="3"/> <!-- B点对称 -->
<circle cx="650" cy="210" r="12" fill="var(--shaft-color)" stroke="#18181b" stroke-width="3"/> <!-- A点对称 -->
</g>
</svg>
<!-- UI 面板 -->
<div class="info-panel panel-top-left">
<h1>IFR: 对开式连杆驱动系统 (俯视图)</h1>
<div class="tag-group">
<span class="tag">单一中央驱动源</span>
<span class="tag">对称四连杆</span>
<span class="tag-blue">0°直线平齐 ↔ 90°完全展开</span>
</div>
<p><strong>最终理想解:</strong>利用中央电机驱动左右对称的重型多连杆机构,摒弃传统转轴。通过机构耦合特性,屏幕内侧边缘在开启瞬间产生<strong>“向外并远离中心”</strong>的位移,完美避开间隙干涉,随后连续过渡至 90 度垂直开启。</p>
<p><strong>动作连贯:</strong>整个开合过程平滑且无停顿,无需在直线导轨和平移电机之间切换,极大降低了系统复杂度和故障率。</p>
<div class="legend">
<div class="legend-item"><div class="legend-color" style="background: var(--screen-color); box-shadow: 0 0 5px var(--screen-color);"></div> 重型屏幕/门体 (闭合呈 180° 一字)</div>
<div class="legend-item"><div class="legend-color" style="background: var(--motor-color);"></div> 单一中央电机 & 传动轴</div>
<div class="legend-item"><div class="legend-color" style="background: var(--traj-color);"></div> 门体内侧边缘(防撞尖端) 运动轨迹</div>
</div>
</div>
<div class="info-panel panel-bottom-right">
<div class="data-row">
<span class="data-label">运行状态:</span>
<span class="data-value" style="color: var(--traj-color);">自动循环同步 / Sync</span>
</div>
<div class="data-row">
<span class="data-label">左右门体夹角:</span>
<span class="data-value" id="ui-total-angle" style="color: var(--screen-color);">180.0°</span>
</div>
<div class="data-row">
<span class="data-label">单侧开启角度:</span>
<span class="data-value" id="ui-single-angle">0.0°</span>
</div>
<div class="data-row">
<span class="data-label">连杆安全余量:</span>
<span class="data-value" style="color: var(--traj-color);">正常 (无奇异点)</span>
</div>
<div class="data-row">
<span class="data-label">中央驱动负荷:</span>
<span class="data-value" id="ui-load">45%</span>
</div>
</div>
</div>
<script>
// --- 运动学核心模型 (基于右侧机构,左侧自动镜像) ---
const K = {
Ax: 950, Ay: 210, // 主动曲柄铰点
Bx: 1150, By: 210, // 固定摇杆铰点
r1: 170, // 主动曲柄 AD
r2: 240, // 从动摇杆 BC
r3: 240, // 连杆体 DC (屏幕装配基准)
// 以下参数在初始化时自动计算,以保证完美的 0度->90度 几何映射
thetaStart: 0,
thetaEnd: 0,
alphaStart: 0,
// 门体相对 D 点的局部坐标 (确保闭合时水平,且内侧对齐 X=804)
doorLocalStartX: 0, doorLocalStartY: 0,
doorLocalEndX: 0, doorLocalEndY: 0
};
// DOM 元素缓存
const els = {
centralRotator: document.getElementById('central-rotator'),
trajRight: document.getElementById('traj-right'),
trajLeft: document.getElementById('traj-left'),
uiTotalAngle: document.getElementById('ui-total-angle'),
uiSingleAngle: document.getElementById('ui-single-angle'),
uiLoad: document.getElementById('ui-load'),
right: {
linkAD: document.getElementById('r-link-ad'), linkADIn: document.getElementById('r-link-ad-in'),
linkBC: document.getElementById('r-link-bc'), linkBCIn: document.getElementById('r-link-bc-in'),
door: document.getElementById('r-door'), doorHl: document.getElementById('r-door-hl'),
doorTip: document.getElementById('r-door-tip'),
pivotD: document.getElementById('r-pivot-d'), pivotC: document.getElementById('r-pivot-c')
},
left: {
linkAD: document.getElementById('l-link-ad'), linkADIn: document.getElementById('l-link-ad-in'),
linkBC: document.getElementById('l-link-bc'), linkBCIn: document.getElementById('l-link-bc-in'),
door: document.getElementById('l-door'), doorHl: document.getElementById('l-door-hl'),
doorTip: document.getElementById('l-door-tip'),
pivotD: document.getElementById('l-pivot-d'), pivotC: document.getElementById('l-pivot-c')
}
};
// 四连杆正向求解器
function solveFourBar(theta) {
const Dx = K.Ax + K.r1 * Math.cos(theta);
const Dy = K.Ay + K.r1 * Math.sin(theta);
const Ldb = Math.hypot(K.Bx - Dx, K.By - Dy);
if (Ldb > K.r2 + K.r3 || Ldb < Math.abs(K.r2 - K.r3)) return null; // 机构卡死
const phi = Math.atan2(K.By - Dy, K.Bx - Dx);
const cosGamma = (K.r3 * K.r3 + Ldb * Ldb - K.r2 * K.r2) / (2 * K.r3 * Ldb);
const gamma = Math.acos(Math.max(-1, Math.min(1, cosGamma)));
const theta3 = phi - gamma; // 取外展装配形态
const Cx = Dx + K.r3 * Math.cos(theta3);
const Cy = Dy + K.r3 * Math.sin(theta3);
const alpha = Math.atan2(Cy - Dy, Cx - Dx);
return { Dx, Dy, Cx, Cy, alpha };
}
// 初始化计算:寻找精确的 0度 和 90度 映射点
function initMechanism() {
// 设定初始曲柄角 (位于第四象限,向右下方)
K.thetaStart = 2.5;
const startRes = solveFourBar(K.thetaStart);
if (!startRes) return;
K.alphaStart = startRes.alpha;
// 我们需要门体闭合时严格位于 Y=340 的水平线上
const doorY = 340;
// 右门内侧起点 (留出中心 8px 间隙,800 是中线)
const doorStartX_Global = 804;
// 右门外侧终点 (宽度 380px)
const doorEndX_Global = 1184;
// 绑定门体相对 D 点的局部刚性坐标
K.doorLocalStartX = doorStartX_Global - startRes.Dx;
K.doorLocalStartY = doorY - startRes.Dy;
K.doorLocalEndX = doorEndX_Global - startRes.Dx;
K.doorLocalEndY = doorY - startRes.Dy;
// 向下搜索曲柄角,直到连杆体旋转 90 度 (Math.PI / 2)
// 俯视图中,右侧门向外打开对应顺时针旋转 (在屏幕坐标系中 alpha 增加)
const targetRotation = Math.PI / 2;
let found = false;
for (let i = 0; i < 1000; i++) {
let testTheta = K.thetaStart - i * 0.005; // 电机顺时针旋转 (角度减小)
let res = solveFourBar(testTheta);
if (res) {
// 角度差可能跨越 PI 边界,做归一化
let delta = res.alpha - K.alphaStart;
while(delta < -Math.PI) delta += Math.PI * 2;
while(delta > Math.PI) delta -= Math.PI * 2;
if (Math.abs(delta) >= targetRotation) {
K.thetaEnd = testTheta;
found = true;
break;
}
} else {
break;
}
}
if (!found) {
// 安全后备方案
K.thetaEnd = K.thetaStart - 1.2;
}
// 预计算轨迹路径
precalculateTrajectories();
}
// 预渲染两门尖端的运动轨迹虚线
function precalculateTrajectories() {
let pathR = "", pathL = "";
const steps = 60;
for (let i = 0; i <= steps; i++) {
const t = i / steps;
const theta = K.thetaStart + t * (K.thetaEnd - K.thetaStart);
const res = solveFourBar(theta);
if (res) {
const deltaAlpha = res.alpha - K.alphaStart;
const cosA = Math.cos(deltaAlpha);
const sinA = Math.sin(deltaAlpha);
// 右门尖端 (Inner Tip)
const tipRx = res.Dx + K.doorLocalStartX * cosA - K.doorLocalStartY * sinA;
const tipRy = res.Dy + K.doorLocalStartX * sinA + K.doorLocalStartY * cosA;
// 左门尖端 (基于对称中线 X=800 镜像)
const tipLx = 1600 - tipRx;
if (i === 0) {
pathR += `M ${tipRx} ${tipRy} `;
pathL += `M ${tipLx} ${tipRy} `; // Y 轴一致
} else {
pathR += `L ${tipRx} ${tipRy} `;
pathL += `L ${tipLx} ${tipRy} `;
}
}
}
els.trajRight.setAttribute('d', pathR);
els.trajLeft.setAttribute('d', pathL);
}
// 更新单侧 DOM
function updateSide(sideEls, isLeft, Ax, Bx, res) {
// 如果是左侧,执行 X 轴镜像变换 (1600 - X)
const mirrorX = (x) => isLeft ? 1600 - x : x;
const mAx = mirrorX(Ax);
const mBx = mirrorX(Bx);
const mDx = mirrorX(res.Dx);
const mCx = mirrorX(res.Cx);
// 连杆 AD
sideEls.linkAD.setAttribute('x1', mAx); sideEls.linkAD.setAttribute('y1', K.Ay);
sideEls.linkAD.setAttribute('x2', mDx); sideEls.linkAD.setAttribute('y2', res.Dy);
sideEls.linkADIn.setAttribute('x1', mAx); sideEls.linkADIn.setAttribute('y1', K.Ay);
sideEls.linkADIn.setAttribute('x2', mDx); sideEls.linkADIn.setAttribute('y2', res.Dy);
// 连杆 BC
sideEls.linkBC.setAttribute('x1', mBx); sideEls.linkBC.setAttribute('y1', K.By);
sideEls.linkBC.setAttribute('x2', mCx); sideEls.linkBC.setAttribute('y2', res.Cy);
sideEls.linkBCIn.setAttribute('x1', mBx); sideEls.linkBCIn.setAttribute('y1', K.By);
sideEls.linkBCIn.setAttribute('x2', mCx); sideEls.linkBCIn.setAttribute('y2', res.Cy);
// 铰点
sideEls.pivotD.setAttribute('cx', mDx); sideEls.pivotD.setAttribute('cy', res.Dy);
sideEls.pivotC.setAttribute('cx', mCx); sideEls.pivotC.setAttribute('cy', res.Cy);
// 门体刚性旋转
const deltaAlpha = res.alpha - K.alphaStart;
const cosA = Math.cos(deltaAlpha);
const sinA = Math.sin(deltaAlpha);
// 计算右门全局坐标
const rightStartX = res.Dx + K.doorLocalStartX * cosA - K.doorLocalStartY * sinA;
const rightStartY = res.Dy + K.doorLocalStartX * sinA + K.doorLocalStartY * cosA;
const rightEndX = res.Dx + K.doorLocalEndX * cosA - K.doorLocalEndY * sinA;
const rightEndY = res.Dy + K.doorLocalEndX * sinA + K.doorLocalEndY * cosA;
// 应用镜像
const finalStartX = mirrorX(rightStartX);
const finalEndX = mirrorX(rightEndX);
// 渲染门体线条
sideEls.door.setAttribute('x1', finalStartX); sideEls.door.setAttribute('y1', rightStartY);
sideEls.door.setAttribute('x2', finalEndX); sideEls.door.setAttribute('y2', rightEndY);
sideEls.doorHl.setAttribute('x1', finalStartX); sideEls.doorHl.setAttribute('y1', rightStartY);
sideEls.doorHl.setAttribute('x2', finalEndX); sideEls.doorHl.setAttribute('y2', rightEndY);
// 尖端防撞圆
sideEls.doorTip.setAttribute('cx', finalStartX);
sideEls.doorTip.setAttribute('cy', rightStartY);
return deltaAlpha; // 返回实际旋转角用于 UI
}
// 动画引擎
const animSpeed = 0.0007; // 运行速度
function renderFrame(time) {
// 利用平滑缓动生成 0~1 的周期往复
let raw = Math.sin(time * animSpeed);
// 使用钳制放大产生停顿感 (在闭合和全开状态停留观察)
let clamped = Math.max(-0.85, Math.min(0.85, raw)) / 0.85;
// 缓动曲线
let progress = (Math.sin(clamped * Math.PI / 2) + 1) / 2;
const currentTheta = K.thetaStart + progress * (K.thetaEnd - K.thetaStart);
// 中央电机视觉旋转 (加速转动以显现传动感)
els.centralRotator.setAttribute('transform', `rotate(${(currentTheta * -300) % 360} 0 0)`);
const res = solveFourBar(currentTheta);
if (res) {
// 同步驱动右侧与左侧
const rightRot = updateSide(els.right, false, K.Ax, K.Bx, res);
updateSide(els.left, true, K.Ax, K.Bx, res);
// 更新 UI 仪表数据
// rightRot 是弧度,转为角度。因为绝对值,取 abs
const singleDeg = Math.abs(rightRot * 180 / Math.PI);
els.uiSingleAngle.innerText = singleDeg.toFixed(1) + "°";
// 总夹角 = 180 - 2*单侧开启角
const totalDeg = 180 - 2 * singleDeg;
els.uiTotalAngle.innerText = totalDeg.toFixed(1) + "°";
// 动态负荷模拟 (启动时最高,展开时降低)
const load = 15 + Math.abs(Math.sin(progress * Math.PI)) * 65;
els.uiLoad.innerText = load.toFixed(0) + "%";
}
requestAnimationFrame(renderFrame);
}
// 启动
document.addEventListener("DOMContentLoaded", () => {
initMechanism();
requestAnimationFrame(renderFrame);
});
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>高保真 IFR 原理演示:对开式向后收纳连杆门</title>
<style>
:root {
/* 工业黑/冷蓝主色调 */
--bg-color: #050505;
--grid-color: rgba(63, 63, 70, 0.4);
--stage-front: rgba(16, 185, 129, 0.05);
--stage-back: rgba(225, 29, 72, 0.03);
--truss-color: #18181b;
--truss-edge: #3f3f46;
--screen-color: #0ea5e9;
--screen-color-left: #06b6d4;
--screen-glow: rgba(14, 165, 233, 0.5);
--link-color: #a1a1aa;
--link-stroke: #000;
--motor-color: #f59e0b;
--shaft-color: #fbbf24;
--traj-color: #f43f5e;
--text-main: #f4f4f5;
--text-muted: #a1a1aa;
--panel-bg: rgba(5, 5, 5, 0.85);
}
body, html {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
background-color: var(--bg-color);
color: var(--text-main);
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace;
overflow: hidden;
display: flex;
justify-content: center;
align-items: center;
}
#app-container {
position: relative;
width: 100vw;
height: 100vh;
}
svg {
width: 100%;
height: 100%;
display: block;
}
.info-panel {
position: absolute;
background: var(--panel-bg);
border: 1px solid #27272a;
padding: 16px;
border-radius: 6px;
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
pointer-events: none;
z-index: 10;
box-shadow: 0 10px 30px rgba(0,0,0,0.5);
}
.panel-top-left {
top: 24px;
left: 24px;
max-width: 340px;
border-top: 3px solid var(--screen-color);
}
.panel-bottom-right {
bottom: 24px;
right: 24px;
max-width: 280px;
}
h1 {
font-size: 14px;
margin: 0 0 12px 0;
color: #fff;
font-weight: 600;
letter-spacing: 1px;
border-bottom: 1px dashed #27272a;
padding-bottom: 8px;
text-transform: uppercase;
}
.tag-group {
display: flex;
gap: 6px;
margin-bottom: 12px;
flex-wrap: wrap;
}
.tag {
font-size: 10px;
padding: 3px 6px;
background: rgba(245, 158, 11, 0.1);
color: var(--motor-color);
border: 1px solid rgba(245, 158, 11, 0.3);
border-radius: 2px;
}
.tag-blue {
background: rgba(14, 165, 233, 0.1);
color: var(--screen-color);
border-color: rgba(14, 165, 233, 0.3);
}
.tag-red {
background: rgba(244, 63, 94, 0.1);
color: var(--traj-color);
border-color: rgba(244, 63, 94, 0.3);
}
p {
font-size: 12px;
line-height: 1.6;
color: var(--text-muted);
margin: 0 0 8px 0;
}
.data-row {
display: flex;
justify-content: space-between;
font-size: 12px;
margin-bottom: 6px;
border-bottom: 1px dashed #27272a;
padding-bottom: 4px;
}
.data-label { color: #71717a; }
.data-value { color: var(--text-main); font-weight: bold; }
.legend {
margin-top: 12px;
display: flex;
flex-direction: column;
gap: 6px;
background: rgba(255,255,255,0.02);
padding: 8px;
border-radius: 4px;
}
.legend-item {
display: flex;
align-items: center;
gap: 8px;
font-size: 10px;
color: var(--text-muted);
}
.legend-color { width: 12px; height: 3px; }
.glow-screen { filter: drop-shadow(0 0 15px var(--screen-glow)); }
.glow-motor { filter: drop-shadow(0 0 8px rgba(245, 158, 11, 0.5)); }
.trajectory-path {
stroke-dasharray: 4 6;
animation: dashFlow 15s linear infinite;
}
@keyframes dashFlow {
from { stroke-dashoffset: 500; }
to { stroke-dashoffset: 0; }
}
</style>
</head>
<body>
<div id="app-container">
<svg viewBox="0 0 1600 1000" 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="1"/>
<circle cx="0" cy="0" r="1.5" fill="#3f3f46" />
</pattern>
<pattern id="hatch-back" width="16" height="16" patternTransform="rotate(45)" patternUnits="userSpaceOnUse">
<line x1="0" y1="0" x2="0" y2="16" stroke="#27272a" stroke-width="4" />
</pattern>
<linearGradient id="front-gradient" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="transparent"/>
<stop offset="100%" stop-color="rgba(14, 165, 233, 0.05)"/>
</linearGradient>
<linearGradient id="back-gradient" x1="0" y1="1" x2="0" y2="0">
<stop offset="0%" stop-color="transparent"/>
<stop offset="100%" stop-color="rgba(244, 63, 94, 0.05)"/>
</linearGradient>
</defs>
<!-- 基础环境划分 -->
<rect width="100%" height="100%" fill="url(#grid)" />
<!-- 后台区域 (上半部) -->
<rect x="0" y="0" width="1600" height="450" fill="url(#back-gradient)" />
<text x="800" y="50" fill="#f43f5e" font-size="24" text-anchor="middle" font-weight="bold" letter-spacing="8" opacity="0.3">STAGE BACK / 舞台后方 (收纳区)</text>
<!-- 观众席区域 (下半部) -->
<rect x="0" y="450" width="1600" height="550" fill="url(#front-gradient)" />
<text x="800" y="900" fill="#0ea5e9" font-size="28" text-anchor="middle" font-weight="bold" letter-spacing="12" opacity="0.4">AUDIENCE / 观众席区域</text>
<!-- 中心对齐线与舞台边缘 -->
<line x1="800" y1="0" x2="800" y2="1000" stroke="#3f3f46" stroke-width="1" stroke-dasharray="8 8" opacity="0.5"/>
<line x1="0" y1="450" x2="1600" y2="450" stroke="#3f3f46" stroke-width="2" stroke-dasharray="10 5" opacity="0.4"/>
<!-- 舞台后方:主承重雷亚架 (俯视图) -->
<g id="fixed-frame">
<rect x="300" y="100" width="1000" height="120" fill="url(#hatch-back)" stroke="var(--truss-edge)" stroke-width="3" rx="2"/>
<text x="800" y="165" fill="#52525b" font-size="18" text-anchor="middle" font-weight="bold" letter-spacing="4">MAIN GANTRY (BEHIND SCREEN)</text>
</g>
<!-- 舞台后方:中央驱动源与传动轴 -->
<g id="central-drive">
<!-- 传动轴 -->
<line class="glow-motor" x1="800" y1="210" x2="650" y2="210" stroke="var(--shaft-color)" stroke-width="6" stroke-linecap="round"/>
<line class="glow-motor" x1="800" y1="210" x2="950" y2="210" stroke="var(--shaft-color)" stroke-width="6" stroke-linecap="round"/>
<!-- 中央电机 -->
<g class="glow-motor" transform="translate(800, 210)">
<rect x="-35" y="-30" width="70" height="60" fill="#18181b" stroke="var(--motor-color)" stroke-width="3" rx="4"/>
<circle r="16" fill="var(--motor-color)"/>
<g id="central-rotator">
<line x1="-12" y1="0" x2="12" y2="0" stroke="#000" stroke-width="4"/>
<line x1="0" y1="-12" x2="0" y2="12" stroke="#000" stroke-width="4"/>
</g>
<text x="0" y="-40" fill="var(--motor-color)" font-size="11" text-anchor="middle" font-weight="bold">DRIVE UNIT</text>
</g>
</g>
<!-- 向后收纳的轨迹层 -->
<path id="traj-right" class="trajectory-path" fill="none" stroke="var(--traj-color)" stroke-width="2.5" opacity="0.9"/>
<path id="traj-left" class="trajectory-path" fill="none" stroke="var(--traj-color)" stroke-width="2.5" opacity="0.9"/>
<!-- 右侧机构 (均在 Y=450 后方) -->
<g id="mech-right">
<!-- 连杆 -->
<line id="r-link-bc" stroke="var(--link-color)" stroke-width="14" stroke-linecap="round"/>
<line id="r-link-bc-in" stroke="var(--link-stroke)" stroke-width="4" stroke-linecap="round"/>
<line id="r-link-ad" stroke="var(--link-color)" stroke-width="18" stroke-linecap="round"/>
<line id="r-link-ad-in" stroke="var(--link-stroke)" stroke-width="6" stroke-linecap="round"/>
<!-- 右侧门体 -->
<line id="r-door" class="glow-screen" stroke="var(--screen-color)" stroke-width="32" stroke-linecap="square"/>
<line id="r-door-hl" stroke="#fff" stroke-width="4" stroke-linecap="square" opacity="0.7"/>
<circle id="r-door-tip" r="12" fill="#fff" stroke="var(--screen-color)" stroke-width="4"/>
<!-- 铰点 -->
<circle id="r-pivot-d" r="8" fill="#fff" stroke="var(--link-stroke)" stroke-width="3"/>
<circle id="r-pivot-c" r="8" fill="#fff" stroke="var(--link-stroke)" stroke-width="3"/>
<circle cx="1180" cy="210" r="10" fill="#52525b" stroke="#000" stroke-width="3"/> <!-- B点(固定) -->
<circle cx="950" cy="210" r="12" fill="var(--shaft-color)" stroke="#000" stroke-width="3"/> <!-- A点(主动) -->
</g>
<!-- 左侧机构 (对称镜像) -->
<g id="mech-left">
<line id="l-link-bc" stroke="var(--link-color)" stroke-width="14" stroke-linecap="round"/>
<line id="l-link-bc-in" stroke="var(--link-stroke)" stroke-width="4" stroke-linecap="round"/>
<line id="l-link-ad" stroke="var(--link-color)" stroke-width="18" stroke-linecap="round"/>
<line id="l-link-ad-in" stroke="var(--link-stroke)" stroke-width="6" stroke-linecap="round"/>
<!-- 左侧门体 -->
<line id="l-door" class="glow-screen" stroke="var(--screen-color-left)" stroke-width="32" stroke-linecap="square"/>
<line id="l-door-hl" stroke="#fff" stroke-width="4" stroke-linecap="square" opacity="0.7"/>
<circle id="l-door-tip" r="12" fill="#fff" stroke="var(--screen-color-left)" stroke-width="4"/>
<circle id="l-pivot-d" r="8" fill="#fff" stroke="var(--link-stroke)" stroke-width="3"/>
<circle id="l-pivot-c" r="8" fill="#fff" stroke="var(--link-stroke)" stroke-width="3"/>
<circle cx="420" cy="210" r="10" fill="#52525b" stroke="#000" stroke-width="3"/> <!-- B点对称 -->
<circle cx="650" cy="210" r="12" fill="var(--shaft-color)" stroke="#000" stroke-width="3"/> <!-- A点对称 -->
</g>
</svg>
<!-- UI 面板 -->
<div class="info-panel panel-top-left">
<h1>IFR: 对开式连杆门 (向后方收纳)</h1>
<div class="tag-group">
<span class="tag-blue">闭合在同一直线</span>
<span class="tag-red">向舞台后方开合 90°</span>
<span class="tag">结构全隐藏于后台</span>
</div>
<p><strong>布局优化:</strong>将传动轴、雷亚架及所有连杆机构统一后置于舞台深处。门体位于系统最前端,完全面向观众(不受机构遮挡)。</p>
<p><strong>逆向收纳原理:</strong>利用四连杆曲柄摇杆的逆向死点特性,中央电机驱动主曲柄反转,使得屏幕内侧首先<strong>向后方且向外</strong>抽出,随后在后台空间内完美翻转 90 度,全程远离前侧的观众席。</p>
<div class="legend">
<div class="legend-item"><div class="legend-color" style="background: var(--screen-color); box-shadow: 0 0 5px var(--screen-color);"></div> 前置屏幕/门体 (隔绝后台)</div>
<div class="legend-item"><div class="legend-color" style="background: var(--traj-color);"></div> 门体向后方收纳的运动轨迹</div>
<div class="legend-item"><div class="legend-color" style="background: #52525b;"></div> 隐藏式后置机械结构</div>
</div>
</div>
<div class="info-panel panel-bottom-right">
<div class="data-row">
<span class="data-label">运行状态:</span>
<span class="data-value" style="color: var(--traj-color);">后台收纳循环</span>
</div>
<div class="data-row">
<span class="data-label">相对观众席位移:</span>
<span class="data-value" style="color: var(--screen-color);">0 mm (安全)</span>
</div>
<div class="data-row">
<span class="data-label">单侧开启角度:</span>
<span class="data-value" id="ui-single-angle">0.0°</span>
</div>
<div class="data-row">
<span class="data-label">左右门体夹角:</span>
<span class="data-value" id="ui-total-angle" style="color: var(--screen-color);">180.0°</span>
</div>
<div class="data-row">
<span class="data-label">连杆干涉监测:</span>
<span class="data-value" style="color: #10b981;">CLEAR (无碰撞)</span>
</div>
</div>
</div>
<script>
// --- 运动学核心模型 (基于右侧机构,逆向收纳) ---
const K = {
// 固定基座位于舞台后方 (Y=210)
Ax: 950, Ay: 210, // 主动曲柄铰点
Bx: 1180, By: 210, // 固定摇杆铰点 (拉开距离确保后台有足够空间收纳门体)
// 连杆长度优化,确保能够实现无死点的 90 度向后偏转
r1: 180, // 主动曲柄 AD
r2: 280, // 从动摇杆 BC
r3: 280, // 连杆体 DC
thetaStart: 0,
thetaEnd: 0,
alphaStart: 0,
// 门体刚性绑定坐标
doorLocalStartX: 0, doorLocalStartY: 0,
doorLocalEndX: 0, doorLocalEndY: 0
};
const els = {
centralRotator: document.getElementById('central-rotator'),
trajRight: document.getElementById('traj-right'),
trajLeft: document.getElementById('traj-left'),
uiTotalAngle: document.getElementById('ui-total-angle'),
uiSingleAngle: document.getElementById('ui-single-angle'),
right: {
linkAD: document.getElementById('r-link-ad'), linkADIn: document.getElementById('r-link-ad-in'),
linkBC: document.getElementById('r-link-bc'), linkBCIn: document.getElementById('r-link-bc-in'),
door: document.getElementById('r-door'), doorHl: document.getElementById('r-door-hl'),
doorTip: document.getElementById('r-door-tip'),
pivotD: document.getElementById('r-pivot-d'), pivotC: document.getElementById('r-pivot-c')
},
left: {
linkAD: document.getElementById('l-link-ad'), linkADIn: document.getElementById('l-link-ad-in'),
linkBC: document.getElementById('l-link-bc'), linkBCIn: document.getElementById('l-link-bc-in'),
door: document.getElementById('l-door'), doorHl: document.getElementById('l-door-hl'),
doorTip: document.getElementById('l-door-tip'),
pivotD: document.getElementById('l-pivot-d'), pivotC: document.getElementById('l-pivot-c')
}
};
// 求解器
function solveFourBar(theta) {
const Dx = K.Ax + K.r1 * Math.cos(theta);
const Dy = K.Ay + K.r1 * Math.sin(theta);
const Ldb = Math.hypot(K.Bx - Dx, K.By - Dy);
if (Ldb > K.r2 + K.r3 || Ldb < Math.abs(K.r2 - K.r3)) return null;
const phi = Math.atan2(K.By - Dy, K.Bx - Dx);
const cosGamma = (K.r3 * K.r3 + Ldb * Ldb - K.r2 * K.r2) / (2 * K.r3 * Ldb);
const gamma = Math.acos(Math.max(-1, Math.min(1, cosGamma)));
// 取外展装配形态
const theta3 = phi - gamma;
const Cx = Dx + K.r3 * Math.cos(theta3);
const Cy = Dy + K.r3 * Math.sin(theta3);
const alpha = Math.atan2(Cy - Dy, Cx - Dx);
return { Dx, Dy, Cx, Cy, alpha };
}
// 动态求解 90度向后收纳 路径
function initMechanism() {
let found = false;
// 右门要想向后(上方 Y=0方向)开启,必须逆时针旋转 -90度
const targetRotation = -Math.PI / 2;
// 自动寻迹:遍历可能的初始角度,寻找能够使连杆逆时针旋转90度的曲柄区间
for (let testThetaStart = 0; testThetaStart < Math.PI * 2; testThetaStart += 0.2) {
K.thetaStart = testThetaStart;
let startRes = solveFourBar(K.thetaStart);
if (!startRes) continue;
K.alphaStart = startRes.alpha;
for (let dir of [1, -1]) {
let tempTheta = K.thetaStart;
let success = false;
for (let i = 0; i < 1500; i++) {
tempTheta += dir * 0.005;
let res = solveFourBar(tempTheta);
if (!res) break;
let delta = res.alpha - K.alphaStart;
while(delta < -Math.PI) delta += Math.PI * 2;
while(delta > Math.PI) delta -= Math.PI * 2;
if (delta <= targetRotation) {
K.thetaEnd = tempTheta;
success = true;
found = true;
break;
}
}
if (success) break;
}
if (found) break;
}
if (!found) {
// 安全后备
K.thetaEnd = K.thetaStart - 1.5;
}
// 重新获取初始状态并刚性绑定门体
const startRes = solveFourBar(K.thetaStart);
// 门体强制位于靠前的安全位置 Y=450 (完全遮挡后台机构)
const doorY = 450;
// 门体内边缘在中心 X=804,外边缘在 X=1184
const doorStartX_Global = 804;
const doorEndX_Global = 1184;
// 绑定相对 D 点的局部坐标
K.doorLocalStartX = doorStartX_Global - startRes.Dx;
K.doorLocalStartY = doorY - startRes.Dy;
K.doorLocalEndX = doorEndX_Global - startRes.Dx;
K.doorLocalEndY = doorY - startRes.Dy;
precalculateTrajectories();
}
// 预渲染轨迹 (显示向后开启路线)
function precalculateTrajectories() {
let pathR = "", pathL = "";
const steps = 60;
for (let i = 0; i <= steps; i++) {
const t = i / steps;
const theta = K.thetaStart + t * (K.thetaEnd - K.thetaStart);
const res = solveFourBar(theta);
if (res) {
const deltaAlpha = res.alpha - K.alphaStart;
const cosA = Math.cos(deltaAlpha);
const sinA = Math.sin(deltaAlpha);
// 门体内侧防撞尖端坐标
const tipRx = res.Dx + K.doorLocalStartX * cosA - K.doorLocalStartY * sinA;
const tipRy = res.Dy + K.doorLocalStartX * sinA + K.doorLocalStartY * cosA;
const tipLx = 1600 - tipRx;
if (i === 0) {
pathR += `M ${tipRx} ${tipRy} `;
pathL += `M ${tipLx} ${tipRy} `;
} else {
pathR += `L ${tipRx} ${tipRy} `;
pathL += `L ${tipLx} ${tipRy} `;
}
}
}
els.trajRight.setAttribute('d', pathR);
els.trajLeft.setAttribute('d', pathL);
}
// 渲染单侧机构
function updateSide(sideEls, isLeft, Ax, Bx, res) {
const mirrorX = (x) => isLeft ? 1600 - x : x;
const mAx = mirrorX(Ax), mBx = mirrorX(Bx);
const mDx = mirrorX(res.Dx), mCx = mirrorX(res.Cx);
// 更新连杆坐标
sideEls.linkAD.setAttribute('x1', mAx); sideEls.linkAD.setAttribute('y1', K.Ay);
sideEls.linkAD.setAttribute('x2', mDx); sideEls.linkAD.setAttribute('y2', res.Dy);
sideEls.linkADIn.setAttribute('x1', mAx); sideEls.linkADIn.setAttribute('y1', K.Ay);
sideEls.linkADIn.setAttribute('x2', mDx); sideEls.linkADIn.setAttribute('y2', res.Dy);
sideEls.linkBC.setAttribute('x1', mBx); sideEls.linkBC.setAttribute('y1', K.By);
sideEls.linkBC.setAttribute('x2', mCx); sideEls.linkBC.setAttribute('y2', res.Cy);
sideEls.linkBCIn.setAttribute('x1', mBx); sideEls.linkBCIn.setAttribute('y1', K.By);
sideEls.linkBCIn.setAttribute('x2', mCx); sideEls.linkBCIn.setAttribute('y2', res.Cy);
sideEls.pivotD.setAttribute('cx', mDx); sideEls.pivotD.setAttribute('cy', res.Dy);
sideEls.pivotC.setAttribute('cx', mCx); sideEls.pivotC.setAttribute('cy', res.Cy);
// 应用门体的刚性旋转
const deltaAlpha = res.alpha - K.alphaStart;
const cosA = Math.cos(deltaAlpha);
const sinA = Math.sin(deltaAlpha);
// 根据局部坐标计算新的全局坐标
const rightStartX = res.Dx + K.doorLocalStartX * cosA - K.doorLocalStartY * sinA;
const rightStartY = res.Dy + K.doorLocalStartX * sinA + K.doorLocalStartY * cosA;
const rightEndX = res.Dx + K.doorLocalEndX * cosA - K.doorLocalEndY * sinA;
const rightEndY = res.Dy + K.doorLocalEndX * sinA + K.doorLocalEndY * cosA;
// 镜像应用到左门
const finalStartX = mirrorX(rightStartX);
const finalEndX = mirrorX(rightEndX);
sideEls.door.setAttribute('x1', finalStartX); sideEls.door.setAttribute('y1', rightStartY);
sideEls.door.setAttribute('x2', finalEndX); sideEls.door.setAttribute('y2', rightEndY);
sideEls.doorHl.setAttribute('x1', finalStartX); sideEls.doorHl.setAttribute('y1', rightStartY);
sideEls.doorHl.setAttribute('x2', finalEndX); sideEls.doorHl.setAttribute('y2', rightEndY);
sideEls.doorTip.setAttribute('cx', finalStartX); sideEls.doorTip.setAttribute('cy', rightStartY);
return deltaAlpha;
}
// 主循环
const animSpeed = 0.0006;
function renderFrame(time) {
// 平滑缓动 (带停顿)
let raw = Math.sin(time * animSpeed);
let clamped = Math.max(-0.85, Math.min(0.85, raw)) / 0.85;
let progress = (Math.sin(clamped * Math.PI / 2) + 1) / 2;
const currentTheta = K.thetaStart + progress * (K.thetaEnd - K.thetaStart);
// 中心驱动旋转
els.centralRotator.setAttribute('transform', `rotate(${(currentTheta * -300) % 360} 0 0)`);
const res = solveFourBar(currentTheta);
if (res) {
const rightRot = updateSide(els.right, false, K.Ax, K.Bx, res);
updateSide(els.left, true, K.Ax, K.Bx, res);
// 更新仪表盘 (绝对值处理使得显示符合直觉)
const singleDeg = Math.abs(rightRot * 180 / Math.PI);
els.uiSingleAngle.innerText = singleDeg.toFixed(1) + "°";
const totalDeg = 180 - 2 * singleDeg;
els.uiTotalAngle.innerText = totalDeg.toFixed(1) + "°";
}
requestAnimationFrame(renderFrame);
}
// 启动
document.addEventListener("DOMContentLoaded", () => {
initMechanism();
requestAnimationFrame(renderFrame);
});
</script>
</body>
</html>
积分规则:第一轮对话扣减8分,后续每轮扣6分
等待动画代码生成...
