独立渲染引擎就绪引擎就绪
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Magnetic Kinematic Coupling - IFR Demonstration</title>
<style>
@import url('https://fonts.googleapis.com/css2?family=Space+Mono:wght@400;700&display=swap');
:root {
--bg-base: #050914;
--bg-panel: rgba(15, 23, 42, 0.75);
--grid-color: rgba(0, 240, 255, 0.05);
--cyan-glow: #00f0ff;
--cyan-dark: #0891b2;
--alert-red: #ef4444;
--alert-red-glow: rgba(239, 68, 68, 0.5);
--metal-light: #94a3b8;
--metal-dark: #334155;
--text-muted: #64748b;
}
* {
box-sizing: border-box;
user-select: none;
}
body {
margin: 0;
padding: 0;
background-color: var(--bg-base);
color: #fff;
font-family: 'Space Mono', monospace;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
overflow: hidden;
background-image:
linear-gradient(var(--grid-color) 1px, transparent 1px),
linear-gradient(90deg, var(--grid-color) 1px, transparent 1px);
background-size: 30px 30px;
background-position: center center;
}
/* Scanline effect */
body::after {
content: "";
position: absolute;
inset: 0;
background: linear-gradient(
to bottom,
rgba(255,255,255,0),
rgba(255,255,255,0) 50%,
rgba(0,0,0,0.1) 50%,
rgba(0,0,0,0.1)
);
background-size: 100% 4px;
pointer-events: none;
z-index: 999;
}
#canvas-container {
position: relative;
width: 100%;
max-width: 1200px;
aspect-ratio: 16 / 9;
background: radial-gradient(circle at 50% 50%, #0f172a 0%, #050914 100%);
border: 1px solid #1e293b;
box-shadow: 0 0 80px rgba(0, 240, 255, 0.05), inset 0 0 40px rgba(0,0,0,0.8);
border-radius: 8px;
overflow: hidden;
}
svg {
width: 100%;
height: 100%;
display: block;
}
/* SVG Element Styles */
.metal-fill { fill: url(#metal-gradient); stroke: #475569; stroke-width: 2; }
.metal-dark-fill { fill: url(#metal-dark-gradient); stroke: #1e293b; stroke-width: 2; }
.v-groove { fill: #0f172a; stroke: var(--cyan-dark); stroke-width: 1.5; }
.magnet { fill: var(--cyan-dark); stroke: var(--cyan-glow); stroke-width: 1; filter: url(#glow-cyan-subtle); transition: all 0.2s; }
.magnet.active { fill: var(--cyan-glow); filter: url(#glow-cyan); }
.steel-ball { fill: url(#ball-gradient); filter: drop-shadow(0 8px 6px rgba(0,0,0,0.6)); }
.bellows-base { fill: none; stroke: #020617; stroke-width: 50; stroke-linecap: round; }
.bellows-ridges { fill: none; stroke: #1e293b; stroke-width: 52; stroke-dasharray: 4 6; stroke-linecap: round; }
.flux-line { fill: none; stroke: var(--cyan-glow); stroke-width: 2; stroke-dasharray: 4 4; opacity: 0; transition: opacity 0.1s; filter: url(#glow-cyan-subtle); }
.flux-line.active { opacity: 0.8; animation: flux-flow 0.5s linear infinite; }
.flux-line.broken { stroke: var(--alert-red); stroke-dasharray: 2 8; opacity: 0.4; animation: none; filter: none; }
@keyframes flux-flow {
to { stroke-dashoffset: -8; }
}
.impact-arrow { fill: var(--alert-red); filter: url(#glow-red); opacity: 0; }
/* HUD Texts */
.hud-panel { fill: var(--bg-panel); stroke: var(--cyan-dark); stroke-width: 1; rx: 6; }
.hud-title { fill: var(--cyan-glow); font-size: 16px; font-weight: bold; letter-spacing: 2px; }
.hud-label { fill: var(--text-muted); font-size: 13px; }
.hud-value { fill: #f8fafc; font-size: 14px; font-weight: bold; }
.hud-value.cyan { fill: var(--cyan-glow); }
.hud-value.red { fill: var(--alert-red); }
.hud-line { stroke: #334155; stroke-width: 1; }
.triz-box { fill: rgba(0, 240, 255, 0.05); stroke: var(--cyan-dark); stroke-width: 1; rx: 4; transition: all 0.3s; }
.triz-box.highlight { fill: rgba(0, 240, 255, 0.2); stroke: var(--cyan-glow); filter: url(#glow-cyan-subtle); }
.triz-title { fill: var(--cyan-glow); font-size: 12px; font-weight: bold; }
.triz-text { fill: var(--metal-light); font-size: 10px; }
/* Controls */
.controls {
position: absolute;
bottom: 20px;
left: 50%;
transform: translateX(-50%);
display: flex;
gap: 15px;
background: var(--bg-panel);
padding: 10px 20px;
border-radius: 30px;
border: 1px solid #1e293b;
backdrop-filter: blur(10px);
z-index: 10;
}
button {
background: transparent;
border: 1px solid var(--cyan-dark);
color: var(--cyan-glow);
font-family: 'Space Mono', monospace;
padding: 8px 16px;
border-radius: 20px;
cursor: pointer;
font-size: 12px;
text-transform: uppercase;
letter-spacing: 1px;
transition: all 0.2s;
}
button:hover {
background: rgba(0, 240, 255, 0.1);
box-shadow: 0 0 15px rgba(0, 240, 255, 0.2);
}
button.active {
background: var(--cyan-dark);
color: #fff;
border-color: var(--cyan-glow);
box-shadow: 0 0 20px rgba(0, 240, 255, 0.4);
}
</style>
</head>
<body>
<div id="canvas-container">
<svg viewBox="0 0 1000 600" id="main-svg">
<defs>
<!-- Gradients -->
<linearGradient id="metal-gradient" x1="0%" y1="0%" x2="0%" y2="100%">
<stop offset="0%" stop-color="#475569" />
<stop offset="50%" stop-color="#334155" />
<stop offset="100%" stop-color="#1e293b" />
</linearGradient>
<linearGradient id="metal-dark-gradient" x1="0%" y1="0%" x2="0%" y2="100%">
<stop offset="0%" stop-color="#334155" />
<stop offset="100%" stop-color="#0f172a" />
</linearGradient>
<radialGradient id="ball-gradient" cx="30%" cy="30%" r="70%">
<stop offset="0%" stop-color="#ffffff" />
<stop offset="30%" stop-color="#94a3b8" />
<stop offset="80%" stop-color="#334155" />
<stop offset="100%" stop-color="#0f172a" />
</radialGradient>
<!-- Filters for Glows -->
<filter id="glow-cyan" x="-20%" y="-20%" width="140%" height="140%">
<feGaussianBlur stdDeviation="8" result="blur" />
<feComposite in="SourceGraphic" in2="blur" operator="over" />
</filter>
<filter id="glow-cyan-subtle" x="-10%" y="-10%" width="120%" height="120%">
<feGaussianBlur stdDeviation="3" result="blur" />
<feComposite in="SourceGraphic" in2="blur" operator="over" />
</filter>
<filter id="glow-red" x="-20%" y="-20%" width="140%" height="140%">
<feGaussianBlur stdDeviation="10" result="blur" />
<feComposite in="SourceGraphic" in2="blur" operator="over" />
</filter>
<!-- Hazard Stripes Pattern -->
<pattern id="hazard-stripes" width="20" height="20" patternTransform="rotate(45)">
<rect width="10" height="20" fill="#fbbf24" opacity="0.7"/>
<rect x="10" width="10" height="20" fill="#1e293b" opacity="0.9"/>
</pattern>
</defs>
<!-- Background Decor -->
<g opacity="0.3">
<line x1="500" y1="0" x2="500" y2="600" stroke="#334155" stroke-dasharray="10 10"/>
<circle cx="500" cy="205" r="350" fill="none" stroke="#1e293b" stroke-width="1"/>
<circle cx="500" cy="205" r="250" fill="none" stroke="#1e293b" stroke-width="1"/>
</g>
<!-- Magnetic Flux Lines -->
<g id="flux-layer">
<!-- Left Flux -->
<path id="flux-l1" class="flux-line active" d="" />
<path id="flux-l2" class="flux-line active" d="" />
<path id="flux-l3" class="flux-line active" d="" />
<!-- Right Flux -->
<path id="flux-r1" class="flux-line active" d="" />
<path id="flux-r2" class="flux-line active" d="" />
<path id="flux-r3" class="flux-line active" d="" />
</g>
<!-- UPPER FLANGE (Robot Arm Side - Fixed/Shakes) -->
<g id="upper-assembly">
<!-- Main block with V-groove cutouts -->
<path class="metal-fill" d="
M 250 20
L 750 20
L 750 180
L 700 180
L 650 130
L 600 180
L 540 180
L 540 100
L 460 100
L 460 180
L 400 180
L 350 130
L 300 180
L 250 180
Z" />
<!-- Robot arm attachment hint -->
<rect x="400" y="0" width="200" height="20" fill="url(#metal-dark-gradient)"/>
<!-- Magnets -->
<rect id="mag-left" class="magnet active" x="310" y="105" width="80" height="20" rx="2" />
<rect id="mag-right" class="magnet active" x="610" y="105" width="80" height="20" rx="2" />
<!-- V-Groove highlights -->
<path class="v-groove" d="M 300 180 L 350 130 L 400 180" />
<path class="v-groove" d="M 600 180 L 650 130 L 700 180" />
<!-- Central Vacuum Pipe (Upper part) -->
<rect x="475" y="100" width="50" height="80" fill="#0f172a" stroke="#334155"/>
</g>
<!-- FLEXIBLE BELLOWS TUBE -->
<!-- We draw it via JS to connect upper (500,180) to lower flange center -->
<path id="bellows-base" class="bellows-base" d="M 500 180 L 500 230" />
<path id="bellows-ridges" class="bellows-ridges" d="M 500 180 L 500 230" />
<!-- LOWER FLANGE (Tool Side - Kinematic) -->
<!-- Center of origin for this group is set to (500, 205) via JS -->
<g id="lower-assembly">
<!-- Tool attachment (Vacuum Cup) -->
<path fill="#0f172a" stroke="#334155" stroke-width="2" d="M -30 25 L 30 25 L 50 120 L -50 120 Z"/>
<rect x="-60" y="120" width="120" height="15" fill="#1e293b" rx="5"/>
<!-- Hazard decal -->
<rect x="-50" y="25" width="100" height="10" fill="url(#hazard-stripes)"/>
<!-- Lower Plate -->
<path class="metal-dark-fill" d="M -220 0 L 220 0 L 200 25 L -200 25 Z" />
<!-- Central Vacuum Pipe (Lower part) -->
<rect x="-25" y="-10" width="50" height="35" fill="#0f172a" stroke="#334155"/>
<!-- Steel Balls (Centers at local X: -150, 150. Y: -25 to fit into upper groove at Y=155) -->
<circle id="ball-left" class="steel-ball" cx="-150" cy="-25" r="28" />
<circle id="ball-right" class="steel-ball" cx="150" cy="-25" r="28" />
<!-- Ball Socket Mounts -->
<path fill="url(#metal-gradient)" d="M -170 0 L -130 0 L -130 -10 C -130 -20, -140 -25, -150 -25 C -160 -25, -170 -20, -170 -10 Z"/>
<path fill="url(#metal-gradient)" d="M 130 0 L 170 0 L 170 -10 C 170 -20, 160 -25, 150 -25 C 140 -25, 130 -20, 130 -10 Z"/>
</g>
<!-- IMPACT ANIMATION ELEMENT -->
<g id="impact-arrow" class="impact-arrow" transform="translate(850, 215)">
<path d="M 0 -20 L -60 -20 L -60 -40 L -120 0 L -60 40 L -60 20 L 0 20 Z" />
<text x="-90" y="-45" fill="var(--alert-red)" font-size="16" font-weight="bold" filter="url(#glow-red)">SHEAR OVERLOAD</text>
</g>
<!-- UI HUD OVERLAY (Right Side) -->
<g id="hud-layer" transform="translate(730, 40)">
<rect class="hud-panel" width="240" height="340" />
<!-- Title -->
<rect x="0" y="15" width="4" height="20" fill="var(--cyan-glow)" />
<text x="15" y="30" class="hud-title">SYSTEM TELEMETRY</text>
<line x1="15" y1="45" x2="225" y2="45" class="hud-line" />
<!-- Data Rows -->
<text x="15" y="75" class="hud-label">Mech Status:</text>
<text x="115" y="75" id="val-status" class="hud-value cyan">COUPLED</text>
<text x="15" y="105" class="hud-label">Hold Force:</text>
<text x="115" y="105" id="val-force" class="hud-value cyan">200.0 N</text>
<text x="15" y="135" class="hud-label">Deflection:</text>
<text x="115" y="135" id="val-angle" class="hud-value cyan">0.00°</text>
<text x="15" y="165" class="hud-label">Lat. Shear:</text>
<text x="115" y="165" id="val-shear" class="hud-value cyan">15.0 N</text>
<!-- TRIZ IFR Highlight Box -->
<rect id="triz-box" class="triz-box" x="15" y="195" width="210" height="125" />
<rect x="15" y="195" width="4" height="125" fill="var(--cyan-dark)" />
<text x="28" y="215" class="triz-title">TRIZ: IDEAL FINAL RESULT</text>
<text x="28" y="235" class="triz-text">矛盾:刚性固定 vs 侧向过载断裂</text>
<text x="28" y="255" class="triz-text">解法:引入磁力运动学耦合</text>
<text x="28" y="275" class="triz-text">状态:平常提供高精度刚性定位,</text>
<text x="28" y="290" class="triz-text"> 过载时自动断开充当保险丝,</text>
<text x="28" y="305" class="triz-text"> 事后自动复原,消除破坏。</text>
</g>
</svg>
</div>
<!-- Interactive Controls -->
<div class="controls">
<button id="btn-auto" class="active">Auto Loop Sequence</button>
<button id="btn-trigger">Trigger Impact</button>
</div>
<script>
// DOM Elements
const lowerAssembly = document.getElementById('lower-assembly');
const upperAssembly = document.getElementById('upper-assembly');
const bellowsBase = document.getElementById('bellows-base');
const bellowsRidges = document.getElementById('bellows-ridges');
const impactArrow = document.getElementById('impact-arrow');
const magLeft = document.getElementById('mag-left');
const magRight = document.getElementById('mag-right');
const trizBox = document.getElementById('triz-box');
const valStatus = document.getElementById('val-status');
const valForce = document.getElementById('val-force');
const valAngle = document.getElementById('val-angle');
const valShear = document.getElementById('val-shear');
// Flux paths
const fluxes = [
document.getElementById('flux-l1'), document.getElementById('flux-l2'), document.getElementById('flux-l3'),
document.getElementById('flux-r1'), document.getElementById('flux-r2'), document.getElementById('flux-r3')
];
// Easing functions
const easeOutQuint = t => 1 - Math.pow(1 - t, 5);
const easeInOutCubic = t => t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2;
const easeInExpo = t => t === 0 ? 0 : Math.pow(2, 10 * t - 10);
const easeOutBack = t => { const c1 = 1.70158; const c3 = c1 + 1; return 1 + c3 * Math.pow(t - 1, 3) + c1 * Math.pow(t - 1, 2); };
// State Variables
let startTime = Date.now();
let isAutoPlay = true;
let manualTriggerTime = 0;
// Physics constants
const PIVOT_L_X = -150;
const PIVOT_L_Y = -25;
const CRITICAL_ANGLE = 3.0; // degrees
// Helper: Calculate global position of a point inside the transformed lower assembly
function getGlobalPos(localX, localY, tx, ty, angleDeg) {
let rad = angleDeg * Math.PI / 180;
// The transform rotates around pivot (PIVOT_L_X, PIVOT_L_Y)
let x1 = localX - PIVOT_L_X;
let y1 = localY - PIVOT_L_Y;
let x2 = x1 * Math.cos(rad) - y1 * Math.sin(rad);
let y2 = x1 * Math.sin(rad) + y1 * Math.cos(rad);
return {
x: x2 + PIVOT_L_X + tx,
y: y2 + PIVOT_L_Y + ty
};
}
// Draw magnetic flux lines
function updateFlux(tx, ty, angleDeg, isBroken) {
// Magnet centers
const mLeft = { x: 350, y: 125 };
const mRight = { x: 650, y: 125 };
// Ball global centers
const bLeft = getGlobalPos(-150, -25, tx, ty, angleDeg);
const bRight = getGlobalPos(150, -25, tx, ty, angleDeg);
// Left fluxes
for(let i=0; i<3; i++) {
let offset = (i-1) * 15;
let d = `M ${mLeft.x + offset} ${mLeft.y} Q ${mLeft.x + offset/2} ${(mLeft.y + bLeft.y)/2} ${bLeft.x} ${bLeft.y}`;
fluxes[i].setAttribute('d', d);
if (isBroken) {
fluxes[i].classList.remove('active');
fluxes[i].classList.add('broken');
} else {
fluxes[i].classList.add('active');
fluxes[i].classList.remove('broken');
}
}
// Right fluxes
for(let i=0; i<3; i++) {
let offset = (i-1) * 15;
let d = `M ${mRight.x + offset} ${mRight.y} Q ${mRight.x + offset/2} ${(mRight.y + bRight.y)/2} ${bRight.x} ${bRight.y}`;
fluxes[i+3].setAttribute('d', d);
if (isBroken) {
fluxes[i+3].classList.remove('active');
fluxes[i+3].classList.add('broken');
} else {
fluxes[i+3].classList.add('active');
fluxes[i+3].classList.remove('broken');
}
}
// Fade out completely if too far
const dist = ty - 205;
fluxes.forEach(f => {
if (dist > 50 && isBroken) f.style.opacity = Math.max(0, 0.4 - (dist-50)/100);
else f.style.opacity = '';
});
}
// Update central bellows tube
function updateBellows(tx, ty, angleDeg) {
// Upper anchor
const upX = 500, upY = 180;
// Lower anchor (center of lower plate local (0,-10))
const lowPt = getGlobalPos(0, -10, tx, ty, angleDeg);
// Draw smooth S-curve
const cpY = upY + (lowPt.y - upY) * 0.5;
const d = `M ${upX} ${upY} C ${upX} ${cpY}, ${lowPt.x} ${cpY}, ${lowPt.x} ${lowPt.y}`;
bellowsBase.setAttribute('d', d);
bellowsRidges.setAttribute('d', d);
}
// Main Animation Loop
function render() {
let now = Date.now();
let elapsed = now - startTime;
// Timeline (Total 8000ms per cycle)
let cycleTime = isAutoPlay ? (elapsed % 8000) : (now - manualTriggerTime);
if (!isAutoPlay && cycleTime > 8000) cycleTime = 8000; // Hold end state
let tx = 500;
let ty = 205;
let angle = 0;
let force = 200;
let shear = 10;
let statusText = "COUPLED";
let statusClass = "cyan";
let isBroken = false;
let showImpact = false;
let upperShakeX = 0;
// Phase logic
if (cycleTime < 2000) {
// 1. Idle Coupled (0 - 2s)
shear = 10 + Math.sin(cycleTime/150)*5;
trizBox.classList.remove('highlight');
}
else if (cycleTime < 2100) {
// 2. Impact Hit (2.0 - 2.1s)
let p = (cycleTime - 2000) / 100;
shear = 10 + p * 800; // Spike
angle = easeInExpo(p) * -CRITICAL_ANGLE;
showImpact = true;
upperShakeX = (Math.random() - 0.5) * 6; // Shake
statusText = "IMPACT DETECTED";
statusClass = "red";
trizBox.classList.add('highlight');
}
else if (cycleTime < 2600) {
// 3. Breakaway & Drop (2.1 - 2.6s)
let p = (cycleTime - 2100) / 500;
isBroken = true;
shear = 0;
force = 200 * (1 - easeOutQuint(p));
statusText = "FUSE BLOWN - SEPARATED";
statusClass = "red";
if (p < 0.3) {
// Pivot fast
let p2 = p / 0.3;
angle = -CRITICAL_ANGLE + easeOutQuint(p2) * -12;
} else {
// Drop down and swing
let p2 = (p - 0.3) / 0.7;
angle = -15 + p2 * 7; // Swing back slightly to -8
ty = 205 + easeOutQuint(p2) * 85;
tx = 500 - easeOutQuint(p2) * 25;
}
}
else if (cycleTime < 4500) {
// 4. Hanging Safe (2.6 - 4.5s)
isBroken = true;
shear = 0;
force = 0;
angle = -8 + Math.sin(cycleTime/300)*1.5; // Dangling swing
ty = 290;
tx = 475;
statusText = "ENERGY RELEASED (SAFE)";
statusClass = "cyan";
}
else if (cycleTime < 6000) {
// 5. Recovery Approach (4.5 - 6.0s)
let p = (cycleTime - 4500) / 1500;
isBroken = true; // still technically broken but approaching
shear = 0;
statusText = "AUTO-RECOVERY...";
ty = 290 - easeInOutCubic(p) * 75; // approach 215
tx = 475 + easeInOutCubic(p) * 25; // approach 500
angle = -8 + easeInOutCubic(p) * 8; // level out to 0
if (p > 0.8) {
force = ((p - 0.8) / 0.2) * 100; // Magnets start pulling
if (cycleTime % 200 < 100) statusText = "MAGNETIC CAPTURE";
}
trizBox.classList.remove('highlight');
}
else if (cycleTime < 6200) {
// 6. Magnetic Snap (6.0 - 6.2s)
let p = (cycleTime - 6000) / 200;
isBroken = false;
shear = 0;
force = 200;
statusText = "LOCKED";
ty = 215 - easeOutBack(p) * 10; // snap to 205 with slight overshoot
tx = 500;
angle = 0;
// Visual snap flash
magLeft.classList.add('active');
magRight.classList.add('active');
}
else {
// 7. Stabilize (6.2 - 8.0s)
shear = 10;
force = 200;
}
// Apply transforms
lowerAssembly.setAttribute('transform', `translate(${tx}, ${ty}) rotate(${angle}, ${PIVOT_L_X}, ${PIVOT_L_Y})`);
upperAssembly.setAttribute('transform', `translate(${upperShakeX}, 0)`);
// Impact Arrow
if (showImpact) {
impactArrow.style.opacity = 1;
impactArrow.setAttribute('transform', `translate(${850 - (cycleTime-2000)}, 215)`);
} else {
impactArrow.style.opacity = 0;
impactArrow.setAttribute('transform', `translate(850, 215)`);
}
// Magnets appearance
if (isBroken) {
magLeft.classList.remove('active');
magRight.classList.remove('active');
} else if (cycleTime < 6000 || cycleTime > 6300) {
// subtle pulse when locked
if (Math.sin(cycleTime/200) > 0.8) {
magLeft.classList.add('active');
magRight.classList.add('active');
} else {
magLeft.classList.remove('active');
magRight.classList.remove('active');
}
}
// Dynamic drawn elements
updateFlux(tx, ty, angle, isBroken);
updateBellows(tx, ty, angle);
// Update HUD
valStatus.textContent = statusText;
valStatus.className = `hud-value ${statusClass}`;
valForce.textContent = force.toFixed(1) + " N";
valForce.className = force < 100 ? "hud-value red" : "hud-value cyan";
valAngle.textContent = Math.abs(angle).toFixed(2) + "°";
valAngle.className = Math.abs(angle) >= CRITICAL_ANGLE ? "hud-value red" : "hud-value cyan";
valShear.textContent = shear.toFixed(1) + " N";
valShear.className = shear > 50 ? "hud-value red" : "hud-value cyan";
requestAnimationFrame(render);
}
// Controls Logic
document.getElementById('btn-auto').addEventListener('click', (e) => {
isAutoPlay = true;
e.target.classList.add('active');
document.getElementById('btn-trigger').classList.remove('active');
});
document.getElementById('btn-trigger').addEventListener('click', (e) => {
isAutoPlay = false;
manualTriggerTime = Date.now() - 1900; // Jump right before impact
e.target.classList.add('active');
document.getElementById('btn-auto').classList.remove('active');
});
// Start animation immediately on load
requestAnimationFrame(render);
</script>
</body>
</html>
积分规则:第一轮对话扣减8分,后续每轮扣6分
等待动画代码生成...
