独立渲染引擎就绪引擎就绪
<!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>
@import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@300;500;700&display=swap');
:root {
--bg-color: #030712;
--grid-color: rgba(30, 58, 138, 0.2);
--text-primary: #94a3b8;
--text-highlight: #38bdf8;
--spring-comp: #f97316; /* 压缩:橙色 */
--spring-tens: #0ea5e9; /* 拉伸:青色 */
--flange-stroke: #38bdf8;
--flange-fill: rgba(8, 47, 73, 0.6);
}
body, html {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
background-color: var(--bg-color);
background-image:
linear-gradient(var(--grid-color) 1px, transparent 1px),
linear-gradient(90deg, var(--grid-color) 1px, transparent 1px);
background-size: 40px 40px;
background-position: center center;
font-family: 'JetBrains Mono', monospace;
overflow: hidden;
display: flex;
justify-content: center;
align-items: center;
}
/* 绝对定位的说明性文本,严格遵守角落布局和小字号要求 */
.overlay {
position: absolute;
top: 0; left: 0; right: 0; bottom: 0;
pointer-events: none;
display: flex;
flex-direction: column;
justify-content: space-between;
padding: 24px;
z-index: 10;
}
.header, .footer, .sidebar {
background: rgba(3, 7, 18, 0.7);
backdrop-filter: blur(4px);
border: 1px solid rgba(56, 189, 248, 0.2);
padding: 12px 16px;
border-radius: 8px;
}
.header {
align-self: flex-start;
max-width: 320px;
}
.header h1 {
margin: 0 0 8px 0;
font-size: 14px;
color: var(--text-highlight);
text-transform: uppercase;
letter-spacing: 1px;
}
.header p {
margin: 0;
font-size: 11px;
color: var(--text-primary);
line-height: 1.5;
}
.ifr-tag {
display: inline-block;
background: rgba(56, 189, 248, 0.1);
color: var(--text-highlight);
padding: 2px 6px;
border-radius: 4px;
font-size: 10px;
margin-bottom: 6px;
border: 1px solid rgba(56, 189, 248, 0.3);
}
.sidebar {
position: absolute;
right: 24px;
top: 50%;
transform: translateY(-50%);
display: flex;
flex-direction: column;
gap: 12px;
width: 160px;
}
.spring-data {
font-size: 10px;
color: var(--text-primary);
}
.spring-bar-container {
width: 100%;
height: 4px;
background: rgba(255, 255, 255, 0.1);
border-radius: 2px;
margin-top: 4px;
position: relative;
overflow: hidden;
}
.spring-bar {
position: absolute;
height: 100%;
width: 50%;
left: 50%;
transform-origin: left;
border-radius: 2px;
transition: transform 0.1s linear, background-color 0.1s linear;
}
.footer {
align-self: flex-start;
}
.status-display {
font-size: 12px;
color: #fff;
display: flex;
align-items: center;
gap: 8px;
}
.status-dot {
width: 8px;
height: 8px;
background: #22c55e;
border-radius: 50%;
box-shadow: 0 0 8px #22c55e;
}
/* SVG 主体容器 */
#render-container {
width: 100vw;
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
}
svg {
width: 100%;
height: 100%;
max-width: 1000px;
max-height: 1000px;
filter: drop-shadow(0 0 20px rgba(56, 189, 248, 0.1));
}
/* 强制动画焦点在中央,避免被遮挡 */
</style>
</head>
<body>
<div class="overlay">
<div class="header">
<div class="ifr-tag">TRIZ / IFR 最终理想解</div>
<h1>六维弹性解耦基座</h1>
<p>通过3对空间斜置力线构成的“灯笼骨架”,巧妙利用6根标准弹簧实现六维空间的弹性刚度对称。完全消除独立的导向机构,外力作用下弹簧通过协同形变自动平衡,实现结构极简与功能解耦。</p>
</div>
<div class="sidebar" id="data-panel">
<!-- 弹簧数据通过 JS 动态生成 -->
</div>
<div class="footer">
<div class="status-display">
<div class="status-dot" id="status-dot"></div>
<span id="status-text">系统初始化...</span>
</div>
</div>
</div>
<div id="render-container">
<svg id="stage" viewBox="-400 -400 800 800" preserveAspectRatio="xMidYMid meet">
<defs>
<filter id="glow" x="-20%" y="-20%" width="140%" height="140%">
<feGaussianBlur stdDeviation="4" result="blur" />
<feComposite in="SourceGraphic" in2="blur" operator="over" />
</filter>
<filter id="glow-strong" x="-50%" y="-50%" width="200%" height="200%">
<feGaussianBlur stdDeviation="8" result="blur" />
<feComposite in="SourceGraphic" in2="blur" operator="over" />
</filter>
<marker id="arrowhead" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
<polygon points="0 0, 10 3.5, 0 7" fill="#fff" />
</marker>
</defs>
<g id="scene"></g>
</svg>
</div>
<script>
/**
* 3D 渲染与物理引擎内核
*/
// 核心参数
const R = 150; // 法兰分布半径
const H_IDLE = 160; // 上下锚点基础高度差
const BASE_Z = -80; // 基础平面Z坐标
const TOP_Z_IDLE = 80; // 顶部平面Z坐标
// 视图相机参数 (等轴测/透视投影混配)
const CAMERA = {
pitch: 0.6, // 绕X轴俯仰角
yaw: 0.785 // 绕Z轴偏航角 (45度)
};
// 预定义状态序列 (自动播放动画轨迹)
const ANIMATION_SEQUENCE = [
{ duration: 2000, state: {x: 0, y: 0, z: 0, rx: 0, ry: 0, rz: 0}, label: '零位状态 (预紧)' },
{ duration: 3000, state: {x: 0, y: 0, z: -50, rx: 0, ry: 0, rz: 0}, label: 'Z向受力: 轴向压缩 (Fz)' },
{ duration: 3000, state: {x: 0, y: 0, z: 30, rx: 0, ry: 0, rz: 0}, label: 'Z向受力: 轴向拉伸 (Fz)' },
{ duration: 3000, state: {x: 60, y: 0, z: 0, rx: 0, ry: 0, rz: 0}, label: 'X向受力: 径向剪切 (Fx)' },
{ duration: 3000, state: {x: 0, y: 60, z: 0, rx: 0, ry: 0, rz: 0}, label: 'Y向受力: 径向剪切 (Fy)' },
{ duration: 3000, state: {x: 0, y: 0, z: 0, rx: 0, ry: 0, rz: 0.4}, label: '绕Z轴受力: 扭矩 (Mz)' },
{ duration: 3000, state: {x: 0, y: 0, z: 0, rx: -0.3, ry: 0, rz: 0}, label: '绕X轴受力: 弯矩 (Mx)' },
{ duration: 3000, state: {x: 0, y: 0, z: 0, rx: 0, ry: 0.3, rz: 0}, label: '绕Y轴受力: 弯矩 (My)' }
];
// 初始化锚点角度
const BASE_ANGLES = [60, 180, 300].map(a => a * Math.PI / 180);
const TOP_ANGLES = [0, 120, 240].map(a => a * Math.PI / 180);
// 生成法兰多边形的基础点位
function generateFlangePoints(angles) {
const points = [];
const rIn = 45;
angles.forEach(ang => {
points.push({x: rIn * Math.cos(ang - 0.4), y: rIn * Math.sin(ang - 0.4), z: 0});
points.push({x: R * Math.cos(ang - 0.1), y: R * Math.sin(ang - 0.1), z: 0});
points.push({x: R * Math.cos(ang + 0.1), y: R * Math.sin(ang + 0.1), z: 0});
points.push({x: rIn * Math.cos(ang + 0.4), y: rIn * Math.sin(ang + 0.4), z: 0});
});
return points;
}
const baseFlangeLocal = generateFlangePoints(BASE_ANGLES);
const topFlangeLocal = generateFlangePoints(TOP_ANGLES);
// 弹簧连接关系: 上法兰的每个吊耳连接下法兰相邻的两个支座
const SPRING_CONNECTIONS = [
{ topIdx: 0, baseIdx: 2 }, // Top 0° to Base 300°
{ topIdx: 0, baseIdx: 0 }, // Top 0° to Base 60°
{ topIdx: 1, baseIdx: 0 }, // Top 120° to Base 60°
{ topIdx: 1, baseIdx: 1 }, // Top 120° to Base 180°
{ topIdx: 2, baseIdx: 1 }, // Top 240° to Base 180°
{ topIdx: 2, baseIdx: 2 } // Top 240° to Base 300°
];
// 计算初始静止弹簧长度作为基准
const L0 = Math.hypot(
R * Math.cos(TOP_ANGLES[0]) - R * Math.cos(BASE_ANGLES[2]),
R * Math.sin(TOP_ANGLES[0]) - R * Math.sin(BASE_ANGLES[2]),
TOP_Z_IDLE - BASE_Z
);
// 3D 旋转及平移变换
function transform3D(point, pos) {
// 旋转 (Tait-Bryan angles: X-Y-Z)
let cx = Math.cos(pos.rx), sx = Math.sin(pos.rx);
let cy = Math.cos(pos.ry), sy = Math.sin(pos.ry);
let cz = Math.cos(pos.rz), sz = Math.sin(pos.rz);
let x1 = point.x, y1 = point.y, z1 = point.z;
// Rx
let x2 = x1, y2 = y1*cx - z1*sx, z2 = y1*sx + z1*cx;
// Ry
let x3 = x2*cy + z2*sy, y3 = y2, z3 = -x2*sy + z2*cy;
// Rz
let x4 = x3*cz - y3*sz, y4 = x3*sz + y3*cz, z4 = z3;
return {
x: x4 + pos.x,
y: y4 + pos.y,
z: z4 + pos.z
};
}
// 投影到 2D 平面
function project3D(point) {
// Yaw 旋转 (绕Z)
let x1 = point.x * Math.cos(CAMERA.yaw) - point.y * Math.sin(CAMERA.yaw);
let y1 = point.x * Math.sin(CAMERA.yaw) + point.y * Math.cos(CAMERA.yaw);
let z1 = point.z;
// Pitch 旋转 (绕X)
let x2 = x1;
let y2 = y1 * Math.cos(CAMERA.pitch) - z1 * Math.sin(CAMERA.pitch);
let z2 = y1 * Math.sin(CAMERA.pitch) + z1 * Math.cos(CAMERA.pitch);
// 返回 2D 坐标和深度 (深度值越小越靠后)
return { x: x2, y: -y2, depth: -z2 };
}
// 插值函数 (缓动效果)
function easeInOutCubic(t) {
return t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2;
}
// 颜色混合
function interpolateColor(color1, color2, factor) {
const hex2rgb = hex => [
parseInt(hex.substring(1, 3), 16),
parseInt(hex.substring(3, 5), 16),
parseInt(hex.substring(5, 7), 16)
];
const rgb1 = hex2rgb(color1);
const rgb2 = hex2rgb(color2);
const rgb = rgb1.map((c, i) => Math.round(c + factor * (rgb2[i] - c)));
return `rgb(${rgb[0]}, ${rgb[1]}, ${rgb[2]})`;
}
// 构建弹簧 SVG 路径 (波浪形)
function createSpringPath(p1, p2, coils = 10, width = 14) {
const dx = p2.x - p1.x;
const dy = p2.y - p1.y;
const dist = Math.hypot(dx, dy);
// 如果极度压缩,限制最小距离防崩溃
if(dist < 10) return `M ${p1.x} ${p1.y} L ${p2.x} ${p2.y}`;
const nx = -dy / dist * width;
const ny = dx / dist * width;
let d = `M ${p1.x},${p1.y} `;
// 两端留出直线段
d += `L ${p1.x + dx*0.05},${p1.y + dy*0.05} `;
for(let i = 1; i <= coils; i++) {
const t = 0.05 + 0.9 * (i - 0.5) / coils;
const px = p1.x + dx * t + nx * (i%2 ? 1 : -1);
const py = p1.y + dy * t + ny * (i%2 ? 1 : -1);
d += `L ${px},${py} `;
}
d += `L ${p1.x + dx*0.95},${p1.y + dy*0.95} `;
d += `L ${p2.x},${p2.y}`;
return d;
}
// 状态管理
let currentStateIdx = 0;
let nextStateIdx = 1;
let animProgress = 0; // 0 to 1
let lastTime = 0;
const scene = document.getElementById('scene');
const statusText = document.getElementById('status-text');
const statusDot = document.getElementById('status-dot');
const dataPanel = document.getElementById('data-panel');
// 初始化侧边栏数据条
for (let i = 0; i < 6; i++) {
dataPanel.innerHTML += `
<div class="spring-data">
<div style="display:flex; justify-content:space-between; margin-bottom:2px;">
<span>斜置弹簧 ${i+1}</span>
<span id="s-val-${i}">0%</span>
</div>
<div class="spring-bar-container">
<div class="spring-bar" id="s-bar-${i}"></div>
</div>
</div>
`;
}
// 主渲染循环
function animate(time) {
if(!lastTime) lastTime = time;
const deltaTime = time - lastTime;
lastTime = time;
const currentSeq = ANIMATION_SEQUENCE[currentStateIdx];
const nextSeq = ANIMATION_SEQUENCE[nextStateIdx];
animProgress += deltaTime / currentSeq.duration;
if (animProgress >= 1) {
animProgress = 0;
currentStateIdx = nextStateIdx;
nextStateIdx = (nextStateIdx + 1) % ANIMATION_SEQUENCE.length;
statusText.textContent = ANIMATION_SEQUENCE[currentStateIdx].label;
// 状态指示灯闪烁
statusDot.style.background = '#38bdf8';
statusDot.style.boxShadow = '0 0 10px #38bdf8';
setTimeout(() => {
statusDot.style.background = '#22c55e';
statusDot.style.boxShadow = '0 0 8px #22c55e';
}, 300);
}
const easeT = easeInOutCubic(animProgress);
// 当前六维位姿
const pos = {
x: currentSeq.state.x + (nextSeq.state.x - currentSeq.state.x) * easeT,
y: currentSeq.state.y + (nextSeq.state.y - currentSeq.state.y) * easeT,
z: currentSeq.state.z + (nextSeq.state.z - currentSeq.state.z) * easeT + TOP_Z_IDLE,
rx: currentSeq.state.rx + (nextSeq.state.rx - currentSeq.state.rx) * easeT,
ry: currentSeq.state.ry + (nextSeq.state.ry - currentSeq.state.ry) * easeT,
rz: currentSeq.state.rz + (nextSeq.state.rz - currentSeq.state.rz) * easeT
};
renderScene(pos);
requestAnimationFrame(animate);
}
function renderScene(topPos) {
// 1. 计算基座节点 (固定)
const baseNodesGlobal = BASE_ANGLES.map(ang => ({
x: R * Math.cos(ang), y: R * Math.sin(ang), z: BASE_Z
}));
// 2. 计算顶部节点 (运动)
const topNodesGlobal = TOP_ANGLES.map(ang => {
const local = {x: R * Math.cos(ang), y: R * Math.sin(ang), z: 0};
return transform3D(local, topPos);
});
// 收集所有要绘制的图元并计算深度以进行 Z-Sorting
const drawables = [];
// --- 底座法兰 ---
const baseFlangeProj = baseFlangeLocal.map(p => project3D({x: p.x, y: p.y, z: BASE_Z}));
const baseFlangeDepth = baseFlangeProj.reduce((sum, p) => sum + p.depth, 0) / baseFlangeProj.length;
let dBase = `M ${baseFlangeProj[0].x} ${baseFlangeProj[0].y} `;
for(let i=1; i<baseFlangeProj.length; i++) dBase += `L ${baseFlangeProj[i].x} ${baseFlangeProj[i].y} `;
dBase += 'Z';
drawables.push({
type: 'polygon',
depth: baseFlangeDepth,
html: `<path d="${dBase}" fill="var(--flange-fill)" stroke="var(--flange-stroke)" stroke-width="2" stroke-linejoin="round" />`
});
// --- 顶部法兰 ---
const topFlangeProj = topFlangeLocal.map(p => project3D(transform3D(p, topPos)));
const topFlangeDepth = topFlangeProj.reduce((sum, p) => sum + p.depth, 0) / topFlangeProj.length;
let dTop = `M ${topFlangeProj[0].x} ${topFlangeProj[0].y} `;
for(let i=1; i<topFlangeProj.length; i++) dTop += `L ${topFlangeProj[i].x} ${topFlangeProj[i].y} `;
dTop += 'Z';
drawables.push({
type: 'polygon',
depth: topFlangeDepth,
html: `<path d="${dTop}" fill="rgba(6, 182, 212, 0.4)" stroke="#22d3ee" stroke-width="3" stroke-linejoin="round" filter="url(#glow)"/>`
});
// --- 轴心引导线 (虚拟) ---
const centerBase = project3D({x:0, y:0, z:BASE_Z});
const centerTop = project3D(transform3D({x:0, y:0, z:0}, topPos));
drawables.push({
type: 'line',
depth: (centerBase.depth + centerTop.depth)/2,
html: `<line x1="${centerBase.x}" y1="${centerBase.y}" x2="${centerTop.x}" y2="${centerTop.y}" stroke="rgba(255,255,255,0.1)" stroke-dasharray="5,5" stroke-width="2" />`
});
// --- 6 根斜置弹簧 ---
SPRING_CONNECTIONS.forEach((conn, idx) => {
const pTopGlobal = topNodesGlobal[conn.topIdx];
const pBaseGlobal = baseNodesGlobal[conn.baseIdx];
// 计算实际物理长度与形变量
const len = Math.hypot(pTopGlobal.x - pBaseGlobal.x, pTopGlobal.y - pBaseGlobal.y, pTopGlobal.z - pBaseGlobal.z);
const strain = (len - L0) / L0; // 负为压缩,正为拉伸
// 更新侧边栏 UI
const bar = document.getElementById(`s-bar-${idx}`);
const valText = document.getElementById(`s-val-${idx}`);
// 映射颜色: strain < 0 趋向橙色, strain > 0 趋向青色
let springColor;
if (strain < 0) {
springColor = interpolateColor('#94a3b8', '#f97316', Math.min(-strain * 5, 1));
bar.style.backgroundColor = 'var(--spring-comp)';
} else {
springColor = interpolateColor('#94a3b8', '#0ea5e9', Math.min(strain * 5, 1));
bar.style.backgroundColor = 'var(--spring-tens)';
}
// 更新长度条位置: 左侧压缩,右侧拉伸
const percent = Math.max(-50, Math.min(50, strain * 150));
bar.style.transform = `translateX(${percent}%) scaleX(${Math.abs(percent)/50 || 0.05})`;
valText.textContent = `${strain > 0 ? '+' : ''}${(strain * 100).toFixed(1)}%`;
valText.style.color = springColor;
// 投影渲染
const pTop = project3D(pTopGlobal);
const pBase = project3D(pBaseGlobal);
const depth = (pTop.depth + pBase.depth) / 2;
const pathD = createSpringPath(pTop, pBase);
// 内置导向杆
const guideHtml = `<line x1="${pTop.x}" y1="${pTop.y}" x2="${pBase.x}" y2="${pBase.y}" stroke="rgba(255,255,255,0.15)" stroke-width="4" stroke-linecap="round"/>`;
// 弹簧本体
const springHtml = `<path d="${pathD}" fill="none" stroke="${springColor}" stroke-width="6" stroke-linejoin="round" filter="url(#glow)"/>`;
drawables.push({
type: 'spring',
depth: depth,
html: guideHtml + springHtml
});
});
// 排序渲染 (Painter's Algorithm)
drawables.sort((a, b) => a.depth - b.depth);
// 生成外力矢量指示(仅在偏离零位时显示)
let forceArrow = '';
const isDeformed = Math.abs(topPos.x)>1 || Math.abs(topPos.y)>1 || Math.abs(topPos.z - TOP_Z_IDLE)>1 || Math.abs(topPos.rx)>0.01 || Math.abs(topPos.ry)>0.01 || Math.abs(topPos.rz)>0.01;
if (isDeformed) {
// 简化表达,直接在顶部中心添加指示标识
forceArrow = `<circle cx="${centerTop.x}" cy="${centerTop.y}" r="15" fill="none" stroke="#fff" stroke-width="2" stroke-dasharray="4,4">
<animateTransform attributeName="transform" type="rotate" from="0 ${centerTop.x} ${centerTop.y}" to="360 ${centerTop.x} ${centerTop.y}" dur="2s" repeatCount="indefinite"/>
</circle>
<circle cx="${centerTop.x}" cy="${centerTop.y}" r="4" fill="#fff" filter="url(#glow-strong)"/>`;
}
// 批量更新 DOM
scene.innerHTML = drawables.map(d => d.html).join('') + forceArrow;
}
// 启动
window.addEventListener('DOMContentLoaded', () => {
statusText.textContent = ANIMATION_SEQUENCE[0].label;
requestAnimationFrame(animate);
});
</script>
</body>
</html>
积分规则:每轮对话扣10分
等待动画代码生成...
