独立渲染引擎就绪引擎就绪
<!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>
@import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@300;400;700&display=swap');
:root {
--bg-color: #030712;
--panel-bg: rgba(17, 24, 39, 0.7);
--border-color: rgba(55, 65, 81, 0.5);
--text-main: #f3f4f6;
--text-muted: #9ca3af;
--cyan: #00f0ff;
--magenta: #ff007a;
--amber: #ffaa00;
--glass-blur: blur(12px);
}
body, html {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
background-color: var(--bg-color);
background-image:
radial-gradient(circle at 50% 50%, rgba(0, 240, 255, 0.03) 0%, transparent 60%),
linear-gradient(rgba(255,255,255,0.02) 1px, transparent 1px),
linear-gradient(90deg, rgba(255,255,255,0.02) 1px, transparent 1px);
background-size: 100% 100%, 40px 40px, 40px 40px;
font-family: 'JetBrains Mono', ui-monospace, SFMono-Regular, monospace;
overflow: hidden;
color: var(--text-main);
}
#canvas-container {
position: absolute;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
z-index: 1;
}
svg {
width: 100%;
height: 100%;
display: block;
}
/* 绝对安全红线:文字排版控制,极简、小字号、边缘定位 */
.overlay {
position: absolute;
z-index: 10;
background: var(--panel-bg);
backdrop-filter: var(--glass-blur);
-webkit-backdrop-filter: var(--glass-blur);
border: 1px solid var(--border-color);
border-radius: 8px;
padding: 16px;
box-shadow: 0 4px 30px rgba(0, 0, 0, 0.5);
}
.top-left {
top: 24px;
left: 24px;
max-width: 320px;
}
.bottom-left {
bottom: 24px;
left: 24px;
}
.right-panel {
top: 24px;
right: 24px;
width: 260px;
}
h1 {
font-size: 14px;
font-weight: 700;
margin: 0 0 8px 0;
color: var(--cyan);
letter-spacing: 1px;
text-transform: uppercase;
}
p {
font-size: 11px;
line-height: 1.6;
color: var(--text-muted);
margin: 0;
}
.highlight {
color: var(--text-main);
font-weight: bold;
}
/* 图例样式 */
.legend-item {
display: flex;
align-items: center;
font-size: 11px;
margin-bottom: 6px;
color: var(--text-muted);
}
.legend-item:last-child {
margin-bottom: 0;
}
.color-box {
width: 12px;
height: 12px;
border-radius: 2px;
margin-right: 10px;
}
.line-box {
width: 16px;
height: 2px;
margin-right: 6px;
}
/* 控制面板样式 */
.control-group {
margin-bottom: 16px;
}
.control-group:last-child {
margin-bottom: 0;
}
.control-header {
display: flex;
justify-content: space-between;
font-size: 11px;
margin-bottom: 8px;
color: var(--text-muted);
}
.val-display {
color: var(--cyan);
font-variant-numeric: tabular-nums;
}
input[type=range] {
-webkit-appearance: none;
width: 100%;
background: transparent;
}
input[type=range]:focus {
outline: none;
}
input[type=range]::-webkit-slider-runnable-track {
width: 100%;
height: 4px;
cursor: pointer;
background: rgba(255,255,255,0.1);
border-radius: 2px;
}
input[type=range]::-webkit-slider-thumb {
height: 12px;
width: 12px;
border-radius: 50%;
background: var(--cyan);
cursor: pointer;
-webkit-appearance: none;
margin-top: -4px;
box-shadow: 0 0 10px var(--cyan);
transition: transform 0.1s;
}
input[type=range]::-webkit-slider-thumb:hover {
transform: scale(1.3);
}
.btn-auto {
width: 100%;
padding: 8px;
background: rgba(0, 240, 255, 0.1);
border: 1px solid var(--cyan);
color: var(--cyan);
border-radius: 4px;
font-family: inherit;
font-size: 11px;
cursor: pointer;
transition: all 0.2s;
text-transform: uppercase;
letter-spacing: 1px;
}
.btn-auto:hover {
background: var(--cyan);
color: var(--bg-color);
}
.status-dot {
display: inline-block;
width: 6px;
height: 6px;
border-radius: 50%;
background: var(--cyan);
margin-right: 6px;
box-shadow: 0 0 8px var(--cyan);
animation: pulse 2s infinite;
}
@keyframes pulse {
0% { opacity: 1; }
50% { opacity: 0.3; }
100% { opacity: 1; }
}
/* 隐藏UI以突出动画的响应式设计 */
@media (max-width: 768px) {
.overlay { padding: 10px; }
.top-left { max-width: 250px; }
h1 { font-size: 12px; }
p, .legend-item, .control-header { font-size: 10px; }
}
</style>
</head>
<body>
<!-- 顶部左侧:项目背景与核心说明 -->
<div class="overlay top-left">
<h1>一体化弹性关节 IFR</h1>
<p>基于<span class="highlight">最终理想解</span>设计:采用连续聚氨酯弹性体替代分立弹簧。利用6条42°螺旋槽将材料切分为<span class="highlight">6条等效弹性带</span>。</p>
<p style="margin-top:8px;">消除离散接触带来的摩擦迟滞与跳变,实现<span class="highlight">连续介质下的等效六力线解耦</span>。</p>
</div>
<!-- 底部左侧:图例 -->
<div class="overlay bottom-left">
<div class="legend-item">
<div class="color-box" style="background: var(--cyan); box-shadow: 0 0 8px var(--cyan);"></div>
<span>中性状态 / 无应力</span>
</div>
<div class="legend-item">
<div class="color-box" style="background: var(--amber); box-shadow: 0 0 8px var(--amber);"></div>
<span>拉伸应变 (Tension)</span>
</div>
<div class="legend-item">
<div class="color-box" style="background: var(--magenta); box-shadow: 0 0 8px var(--magenta);"></div>
<span>压缩应变 (Compression)</span>
</div>
<div class="legend-item">
<div class="line-box" style="border-top: 1px dashed #fff;"></div>
<span>理论等效力线向量</span>
</div>
</div>
<!-- 右侧:交互控制面板 -->
<div class="overlay right-panel">
<div style="font-size: 11px; margin-bottom: 12px; color: var(--text-main); display: flex; align-items: center;">
<span class="status-dot" id="auto-dot"></span>
<span id="auto-text">自动演示模式中...</span>
</div>
<div class="control-group">
<div class="control-header">
<span>轴向载荷 (Z向拉压)</span>
<span class="val-display" id="val-z">0.0 mm</span>
</div>
<input type="range" id="slider-z" min="-15" max="15" step="0.1" value="0">
</div>
<div class="control-group">
<div class="control-header">
<span>剪切载荷 (X向平移)</span>
<span class="val-display" id="val-x">0.0 mm</span>
</div>
<input type="range" id="slider-x" min="-15" max="15" step="0.1" value="0">
</div>
<div class="control-group">
<div class="control-header">
<span>扭矩载荷 (绕Z轴扭转)</span>
<span class="val-display" id="val-rz">0.0°</span>
</div>
<input type="range" id="slider-rz" min="-30" max="30" step="0.1" value="0">
</div>
<div class="control-group" style="margin-top: 20px;">
<button class="btn-auto" id="btn-reset">恢复自动播放</button>
</div>
</div>
<!-- 核心动画容器 -->
<div id="canvas-container">
<svg id="scene" 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="-30%" y="-30%" width="160%" height="160%">
<feGaussianBlur stdDeviation="8" result="blur" />
<feComposite in="SourceGraphic" in2="blur" operator="over" />
</filter>
<!-- 渐变定义 -->
<linearGradient id="metal-grad" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stop-color="#1f2937" />
<stop offset="50%" stop-color="#374151" />
<stop offset="100%" stop-color="#111827" />
</linearGradient>
</defs>
<!-- 动态内容由 JS 注入,避免静态DOM过长 -->
<g id="render-group"></g>
</svg>
</div>
<script>
/**
* 伪3D等距投影与结构变形计算引擎
* 遵循 IFR 思想,实时计算并渲染6条等效弹性带在空间中的受力与形变。
*/
const svg = document.getElementById('scene');
const renderGroup = document.getElementById('render-group');
let width, height, cx, cy;
// 系统物理参数 (基于用户提供的数据并等比例放大用于显示)
// 原尺寸: D=88, L=22. 比例大约 4:1
const SCALE = 2.5;
const R = 44 * SCALE; // 弹性体半径
const H = 22 * SCALE; // 弹性体高度
const FLANGE_R = 52 * SCALE; // 法兰盘半径 (略大于弹性体)
const FLANGE_H = 8 * SCALE; // 法兰盘厚度
const SPIRAL_ANGLE = 42 * (Math.PI / 180); // 螺旋槽角度 42°
const SLOT_WIDTH_RAD = 15 * (Math.PI / 180); // 单条槽宽对应的角弧度(近似视觉调整)
// 摄像机参数
const CAM = {
rotX: 20 * (Math.PI / 180), // 俯视角
rotY: 0, // 环绕角
zoom: 1
};
// 状态变量
const state = {
axial: 0, // Z向位移
shearX: 0, // X向剪切
shearY: 0, // Y向剪切 (隐藏未绑UI)
twist: 0, // 绕Z轴扭转
autoPlay: true,
time: 0
};
// 初始化尺寸
function resize() {
width = window.innerWidth;
height = window.innerHeight;
cx = width / 2;
cy = height / 2;
// 动态调整缩放以适应屏幕
CAM.zoom = Math.min(width / 800, height / 600);
if(CAM.zoom > 1.2) CAM.zoom = 1.2;
}
window.addEventListener('resize', resize);
resize();
// 核心3D点定义
class Point3D {
constructor(x, y, z) {
this.x = x; this.y = y; this.z = z;
}
}
// 颜色插值器:根据形变计算应力颜色
function getStressColor(strain) {
// strain: 0 = 中性(青色), >0 = 拉伸(橙黄), <0 = 压缩(紫红)
// #00f0ff (0, 240, 255) -> 中性
// #ffaa00 (255, 170, 0) -> 拉伸
// #ff007a (255, 0, 122) -> 压缩
// 归一化 strain (-0.5 到 0.5 作为极值)
let s = Math.max(-0.5, Math.min(0.5, strain)) * 2; // -1 到 1
let r, g, b;
if (s >= 0) {
// 中性 -> 拉伸 (Cyan -> Amber)
r = Math.floor(0 + s * 255);
g = Math.floor(240 - s * (240 - 170));
b = Math.floor(255 - s * 255);
} else {
// 中性 -> 压缩 (Cyan -> Magenta)
s = Math.abs(s);
r = Math.floor(0 + s * 255);
g = Math.floor(240 - s * 240);
b = Math.floor(255 - s * (255 - 122));
}
return `rgb(${r},${g},${b})`;
}
// 3D投影到2D平面 (带简单等轴侧透视)
function project(p3d) {
// 1. 绕世界 Y 轴旋转摄像机 (环绕查看)
let x1 = p3d.x * Math.cos(CAM.rotY) - p3d.z * Math.sin(CAM.rotY);
let z1 = p3d.x * Math.sin(CAM.rotY) + p3d.z * Math.cos(CAM.rotY);
let y1 = p3d.y;
// 2. 绕摄像机局部 X 轴倾斜 (俯视)
// 这里的坐标系:Y朝上,投影屏幕平面为 XY,深度为 Z
let x2 = x1;
let y2 = y1 * Math.cos(CAM.rotX) - z1 * Math.sin(CAM.rotX);
let z2 = y1 * Math.sin(CAM.rotX) + z1 * Math.cos(CAM.rotX);
// 屏幕坐标映射 (注意 SVG 中 Y 轴向下,所以反转 Y)
return {
x: cx + x2 * CAM.zoom,
y: cy - y2 * CAM.zoom,
z: z2, // 用于深度排序
org: p3d
};
}
// 构建圆柱面路径 (近似多边形)
function createCirclePoints(radius, yPos, steps = 36) {
const pts = [];
for (let i = 0; i < steps; i++) {
let angle = (i / steps) * Math.PI * 2;
pts.push(new Point3D(radius * Math.cos(angle), yPos, radius * Math.sin(angle)));
}
return pts;
}
/**
* 构建系统几何拓扑
*/
function buildGeometry() {
let polygons = [];
// ==========================================
// 1. 计算下法兰盘 (固定)
// ==========================================
const bottomBaseY = -H / 2;
const bottomFlangePts = createCirclePoints(FLANGE_R, bottomBaseY);
const bottomFlangeBottomPts = createCirclePoints(FLANGE_R, bottomBaseY - FLANGE_H);
// ==========================================
// 2. 计算上法兰盘 (随载荷运动)
// ==========================================
const topBaseY = H / 2;
// 施加形变矩阵 (简化的刚体变换应用于上法兰的所有点)
const topFlangePts = createCirclePoints(FLANGE_R, topBaseY).map(p => transformPoint(p));
const topFlangeTopPts = createCirclePoints(FLANGE_R, topBaseY + FLANGE_H).map(p => transformPoint(p));
// 法兰盘渲染元素压入队列 (分为顶面和侧面厚度)
polygons.push({ type: 'flange', pts: bottomFlangeBottomPts, zIndex: 0, style: 'bottom-face' });
polygons.push({ type: 'flange', pts: bottomFlangePts, zIndex: 0, style: 'top-face' });
polygons.push({ type: 'flange', pts: topFlangePts, zIndex: 0, style: 'bottom-face' });
polygons.push({ type: 'flange', pts: topFlangeTopPts, zIndex: 0, style: 'top-face' });
// ==========================================
// 3. 计算 6 条等效弹性带 (核心创新点)
// ==========================================
const bandWidthAngle = (Math.PI * 2 / 6) - SLOT_WIDTH_RAD; // 实体带占据的角度
const initialBandLength = Math.sqrt(H*H + Math.pow(R * Math.sin(SPIRAL_ANGLE), 2) + Math.pow(R - R*Math.cos(SPIRAL_ANGLE), 2));
for (let i = 0; i < 6; i++) {
// 底部连接点角度 (60度均分)
let baseAngle = i * (Math.PI / 3);
// 顶部中性连接点角度 (叠加42度螺旋角)
let topAngle = baseAngle + SPIRAL_ANGLE;
// 计算带的四个角点
// 底部左右
let bl = new Point3D(R * Math.cos(baseAngle - bandWidthAngle/2), bottomBaseY, R * Math.sin(baseAngle - bandWidthAngle/2));
let br = new Point3D(R * Math.cos(baseAngle + bandWidthAngle/2), bottomBaseY, R * Math.sin(baseAngle + bandWidthAngle/2));
// 顶部左右 (变形前)
let tl_0 = new Point3D(R * Math.cos(topAngle - bandWidthAngle/2), topBaseY, R * Math.sin(topAngle - bandWidthAngle/2));
let tr_0 = new Point3D(R * Math.cos(topAngle + bandWidthAngle/2), topBaseY, R * Math.sin(topAngle + bandWidthAngle/2));
// 中心受力线点
let c_bottom = new Point3D(R * Math.cos(baseAngle), bottomBaseY, R * Math.sin(baseAngle));
let c_top_0 = new Point3D(R * Math.cos(topAngle), topBaseY, R * Math.sin(topAngle));
// 应用载荷变换到顶部点
let tl = transformPoint(tl_0);
let tr = transformPoint(tr_0);
let c_top = transformPoint(c_top_0);
// 计算当前带的伸缩率 strain
let currentLen = Math.sqrt(Math.pow(c_top.x - c_bottom.x, 2) + Math.pow(c_top.y - c_bottom.y, 2) + Math.pow(c_top.z - c_bottom.z, 2));
let strain = (currentLen - initialBandLength) / initialBandLength;
// 放大应变系数用于视觉强化
let visualStrain = strain * 5;
// 构建带有曲率的网格 (细分以形成柱面弧度)
// 为了性能和 SVG 的简洁,我们用几个四边形面片来逼近曲面,或者直接绘制投影后的多边形
// 这里采用投影后生成 path 的方式
polygons.push({
type: 'band',
id: i,
pts3d: [bl, br, tr, tl], // 用于生成边界
centerLine: [c_bottom, c_top], // 等效力线
strain: visualStrain,
zIndex: 0
});
}
return polygons;
}
// 应用当前的载荷位移状态到顶点
function transformPoint(p) {
// 扭转
let x1 = p.x * Math.cos(state.twist) - p.z * Math.sin(state.twist);
let z1 = p.x * Math.sin(state.twist) + p.z * Math.cos(state.twist);
// 平移
return new Point3D(
x1 + state.shearX,
p.y + state.axial,
z1 + state.shearY
);
}
// 核心渲染循环
function render() {
// 处理自动播放动画逻辑
if (state.autoPlay) {
state.time += 0.012;
// 摄像机缓慢环绕
CAM.rotY = state.time * 0.5;
// 组合复杂工况展示理想解的六维适应性
// 阶段 1: 轴向拉压
// 阶段 2: 扭转
// 阶段 3: 侧向剪切
let cycle = (state.time * 0.5) % (Math.PI * 2);
// 平滑过渡的状态机公式
state.axial = Math.sin(state.time * 1.5) * 8 * Math.max(0, Math.sin(state.time * 0.5));
state.twist = Math.cos(state.time * 1.8) * 0.4 * Math.max(0, Math.sin(state.time * 0.5 + Math.PI*0.3));
state.shearX = Math.sin(state.time * 1.2) * 12 * Math.max(0, Math.sin(state.time * 0.5 + Math.PI*0.6));
// 更新UI滑块
syncUI(false);
} else {
// 手动模式下,依然让摄像机极慢旋转增加生动感
CAM.rotY += 0.002;
}
// 构建几何体并投影
const geometry = buildGeometry();
// 投影并计算 Z-index (画家算法深度排序)
geometry.forEach(item => {
if (item.type === 'flange') {
item.proj = item.pts.map(p => project(p));
// 法兰中心的 Z 作为排序依据
item.zIndex = item.proj.reduce((sum, p) => sum + p.z, 0) / item.proj.length;
} else if (item.type === 'band') {
item.proj = item.pts3d.map(p => project(p));
item.projCenter = item.centerLine.map(p => project(p));
item.zIndex = item.proj.reduce((sum, p) => sum + p.z, 0) / 4;
}
});
// 排序
geometry.sort((a, b) => a.zIndex - b.zIndex);
// 生成 SVG DOM 字符串
let svgContent = '';
// 添加中心虚线轴
const axisTop = project(transformPoint(new Point3D(0, H/2 + FLANGE_H, 0)));
const axisBot = project(new Point3D(0, -H/2 - FLANGE_H, 0));
svgContent += `<line x1="${axisBot.x}" y1="${axisBot.y}" x2="${axisTop.x}" y2="${axisTop.y}" stroke="rgba(255,255,255,0.15)" stroke-width="1" stroke-dasharray="4,4" />`;
// 绘制排好序的元素
geometry.forEach(item => {
if (item.type === 'flange') {
// 将点序列转换为多边形
let d = `M ${item.proj[0].x} ${item.proj[0].y} ` + item.proj.slice(1).map(p => `L ${p.x} ${p.y}`).join(' ') + ' Z';
let fill = item.style === 'top-face' ? 'url(#metal-grad)' : '#111827';
let stroke = 'rgba(0, 240, 255, 0.4)';
svgContent += `<path d="${d}" fill="${fill}" stroke="${stroke}" stroke-width="1" opacity="0.9" />`;
// 绘制侧面厚度连接 (简化的边缘线)
if (item.style === 'top-face') {
svgContent += `<path d="${d}" fill="none" stroke="#00f0ff" stroke-width="1.5" opacity="0.8" filter="url(#glow)"/>`;
}
}
else if (item.type === 'band') {
// 绘制弹性带 (曲面近似)
let p = item.proj;
// 使用三次贝塞尔曲线近似螺旋切割的边缘曲率
// p[0] BL, p[1] BR, p[2] TR, p[3] TL
let cp1x = p[0].x + (p[3].x - p[0].x) * 0.3;
let cp1y = p[0].y + (p[3].y - p[0].y) * 0.3;
let cp2x = p[3].x - (p[3].x - p[0].x) * 0.3;
let cp2y = p[3].y - (p[3].y - p[0].y) * 0.3;
let cp3x = p[2].x - (p[2].x - p[1].x) * 0.3;
let cp3y = p[2].y - (p[2].y - p[1].y) * 0.3;
let cp4x = p[1].x + (p[2].x - p[1].x) * 0.3;
let cp4y = p[1].y + (p[2].y - p[1].y) * 0.3;
let d = `M ${p[0].x} ${p[0].y}
L ${p[1].x} ${p[1].y}
C ${cp4x} ${cp4y}, ${cp3x} ${cp3y}, ${p[2].x} ${p[2].y}
L ${p[3].x} ${p[3].y}
C ${cp2x} ${cp2y}, ${cp1x} ${cp1y}, ${p[0].x} ${p[0].y} Z`;
let color = getStressColor(item.strain);
// 实体填充
svgContent += `<path d="${d}" fill="${color}" fill-opacity="0.3" stroke="${color}" stroke-width="2" filter="url(#glow)" />`;
// 核心视觉暗示:中心等效力线 (动态流动虚线)
let cLine = item.projCenter;
// 计算流动偏移量 (基于时间)
let dashOffset = -state.time * 20;
svgContent += `<line x1="${cLine[0].x}" y1="${cLine[0].y}" x2="${cLine[1].x}" y2="${cLine[1].y}"
stroke="#ffffff" stroke-width="2" stroke-dasharray="6,4" stroke-dashoffset="${dashOffset}"
opacity="0.9" filter="url(#glow-strong)" />`;
// 力线端点高亮
svgContent += `<circle cx="${cLine[0].x}" cy="${cLine[0].y}" r="2" fill="#fff" />`;
svgContent += `<circle cx="${cLine[1].x}" cy="${cLine[1].y}" r="2" fill="#fff" />`;
}
});
// 绘制虚拟外罩 (过载限位),使用极细的半透明线框
const coverPts = createCirclePoints(FLANGE_R + 8, topBaseY);
const coverProj = coverPts.map(p => project(p));
let coverD = `M ${coverProj[0].x} ${coverProj[0].y} ` + coverProj.slice(1).map(p => `L ${p.x} ${p.y}`).join(' ') + ' Z';
svgContent += `<path d="${coverD}" fill="none" stroke="rgba(255,255,255,0.05)" stroke-width="1" stroke-dasharray="2,2"/>`;
renderGroup.innerHTML = svgContent;
requestAnimationFrame(render);
}
// ==========================================
// UI 交互与事件绑定
// ==========================================
const sZ = document.getElementById('slider-z');
const sX = document.getElementById('slider-x');
const sRz = document.getElementById('slider-rz');
const vZ = document.getElementById('val-z');
const vX = document.getElementById('val-x');
const vRz = document.getElementById('val-rz');
const btnReset = document.getElementById('btn-reset');
const autoDot = document.getElementById('auto-dot');
const autoText = document.getElementById('auto-text');
function stopAuto() {
if(state.autoPlay) {
state.autoPlay = false;
autoDot.style.animation = 'none';
autoDot.style.background = 'var(--text-muted)';
autoDot.style.boxShadow = 'none';
autoText.innerText = '手动干预模式';
autoText.style.color = 'var(--text-muted)';
}
}
function syncUI(readFromDOM = true) {
if (readFromDOM) {
state.axial = parseFloat(sZ.value);
state.shearX = parseFloat(sX.value);
// 滑块度数转弧度
state.twist = parseFloat(sRz.value) * (Math.PI / 180);
} else {
sZ.value = state.axial.toFixed(1);
sX.value = state.shearX.toFixed(1);
sRz.value = (state.twist * (180 / Math.PI)).toFixed(1);
}
vZ.innerText = parseFloat(sZ.value).toFixed(1) + ' mm';
vX.innerText = parseFloat(sX.value).toFixed(1) + ' mm';
vRz.innerText = parseFloat(sRz.value).toFixed(1) + '°';
}
[sZ, sX, sRz].forEach(el => {
el.addEventListener('input', () => {
stopAuto();
syncUI(true);
});
});
btnReset.addEventListener('click', () => {
state.autoPlay = true;
autoDot.style.animation = 'pulse 2s infinite';
autoDot.style.background = 'var(--cyan)';
autoDot.style.boxShadow = '0 0 8px var(--cyan)';
autoText.innerText = '自动演示模式中...';
autoText.style.color = 'var(--text-main)';
});
// 启动渲染循环,自动播放立即生效
requestAnimationFrame(render);
</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>一体化等效六力线弹性关节 - 原理演示</title>
<style>
@import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@300;400;700&display=swap');
:root {
--bg-color: #050811;
--panel-bg: rgba(13, 19, 33, 0.75);
--border-color: rgba(0, 240, 255, 0.2);
--text-main: #f3f4f6;
--text-muted: #8b9bb4;
--cyan: #00f0ff;
--magenta: #ff007a;
--amber: #ffaa00;
}
* {
box-sizing: border-box;
}
body, html {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
background-color: var(--bg-color);
background-image:
radial-gradient(circle at 50% 50%, rgba(0, 240, 255, 0.05) 0%, transparent 70%),
linear-gradient(rgba(255,255,255,0.03) 1px, transparent 1px),
linear-gradient(90deg, rgba(255,255,255,0.03) 1px, transparent 1px);
background-size: 100% 100%, 36px 36px, 36px 36px;
font-family: 'JetBrains Mono', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, monospace;
overflow: hidden;
color: var(--text-main);
display: flex;
align-items: center;
justify-content: center;
}
#canvas-container {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
z-index: 1;
}
svg {
width: 100%;
height: 100%;
max-width: 1200px;
max-height: 900px;
}
.overlay {
position: absolute;
z-index: 10;
background: var(--panel-bg);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
border: 1px solid var(--border-color);
border-radius: 8px;
padding: 14px 18px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.6);
pointer-events: auto;
}
.top-left {
top: 20px;
left: 20px;
max-width: 320px;
}
.bottom-left {
bottom: 20px;
left: 20px;
}
.right-panel {
top: 20px;
right: 20px;
width: 260px;
}
h1 {
font-size: 13px;
font-weight: 700;
margin: 0 0 6px 0;
color: var(--cyan);
letter-spacing: 1px;
text-transform: uppercase;
}
p {
font-size: 11px;
line-height: 1.5;
color: var(--text-muted);
margin: 0;
}
.highlight {
color: var(--text-main);
font-weight: bold;
}
.legend-item {
display: flex;
align-items: center;
font-size: 11px;
margin-bottom: 5px;
color: var(--text-muted);
}
.legend-item:last-child {
margin-bottom: 0;
}
.color-box {
width: 12px;
height: 10px;
border-radius: 2px;
margin-right: 8px;
}
.line-box {
width: 16px;
height: 2px;
margin-right: 8px;
}
.control-group {
margin-bottom: 12px;
}
.control-group:last-child {
margin-bottom: 0;
}
.control-header {
display: flex;
justify-content: space-between;
font-size: 11px;
margin-bottom: 6px;
color: var(--text-muted);
}
.val-display {
color: var(--cyan);
font-variant-numeric: tabular-nums;
}
input[type=range] {
-webkit-appearance: none;
width: 100%;
background: transparent;
margin: 0;
}
input[type=range]:focus {
outline: none;
}
input[type=range]::-webkit-slider-runnable-track {
width: 100%;
height: 4px;
cursor: pointer;
background: rgba(255,255,255,0.15);
border-radius: 2px;
}
input[type=range]::-webkit-slider-thumb {
height: 12px;
width: 12px;
border-radius: 50%;
background: var(--cyan);
cursor: pointer;
-webkit-appearance: none;
margin-top: -4px;
box-shadow: 0 0 8px var(--cyan);
}
.btn-auto {
width: 100%;
padding: 7px;
background: rgba(0, 240, 255, 0.12);
border: 1px solid var(--cyan);
color: var(--cyan);
border-radius: 4px;
font-family: inherit;
font-size: 11px;
cursor: pointer;
transition: all 0.2s;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.btn-auto:hover {
background: var(--cyan);
color: var(--bg-color);
}
.status-dot {
display: inline-block;
width: 6px;
height: 6px;
border-radius: 50%;
background: var(--cyan);
margin-right: 6px;
box-shadow: 0 0 6px var(--cyan);
animation: pulse 1.8s infinite;
}
@keyframes pulse {
0% { opacity: 1; }
50% { opacity: 0.3; }
100% { opacity: 1; }
}
@media (max-width: 768px) {
.overlay { padding: 10px; }
.top-left { max-width: 220px; }
.right-panel { width: 200px; }
}
</style>
</head>
<body>
<!-- 顶部说明 -->
<div class="overlay top-left">
<h1>一体化弹性关节 (IFR 构型)</h1>
<p>短圆柱弹性体(聚氨酯)开 <span class="highlight">6条 42° 螺旋槽</span>形成连续 6 根等效弹簧带。实现无摩擦、无间隙的等效六力线空间解耦传递。</p>
</div>
<!-- 底部图例 -->
<div class="overlay bottom-left">
<div class="legend-item">
<div class="color-box" style="background: var(--cyan); box-shadow: 0 0 6px var(--cyan);"></div>
<span>中性状态 (平衡)</span>
</div>
<div class="legend-item">
<div class="color-box" style="background: var(--amber); box-shadow: 0 0 6px var(--amber);"></div>
<span>拉伸应变 (Tension)</span>
</div>
<div class="legend-item">
<div class="color-box" style="background: var(--magenta); box-shadow: 0 0 6px var(--magenta);"></div>
<span>压缩应变 (Compression)</span>
</div>
<div class="legend-item">
<div class="line-box" style="border-top: 2px dashed #ffffff;"></div>
<span>等效六维受力线向量</span>
</div>
</div>
<!-- 右侧控制 -->
<div class="overlay right-panel">
<div style="font-size: 11px; margin-bottom: 10px; color: var(--text-main); display: flex; align-items: center;">
<span class="status-dot" id="auto-dot"></span>
<span id="auto-text">自动多载荷演练中</span>
</div>
<div class="control-group">
<div class="control-header">
<span>轴向位移 (Z向)</span>
<span class="val-display" id="val-z">0.0 mm</span>
</div>
<input type="range" id="slider-z" min="-18" max="18" step="0.1" value="0">
</div>
<div class="control-group">
<div class="control-header">
<span>剪切平移 (X向)</span>
<span class="val-display" id="val-x">0.0 mm</span>
</div>
<input type="range" id="slider-x" min="-20" max="20" step="0.1" value="0">
</div>
<div class="control-group">
<div class="control-header">
<span>扭转角 (绕Z轴)</span>
<span class="val-display" id="val-rz">0.0°</span>
</div>
<input type="range" id="slider-rz" min="-30" max="30" step="0.1" value="0">
</div>
<div class="control-group" style="margin-top: 14px;">
<button class="btn-auto" id="btn-reset">恢复自动演示</button>
</div>
</div>
<!-- 核心 SVG 场景 -->
<div id="canvas-container">
<svg id="scene" viewBox="0 0 1000 750" preserveAspectRatio="xMidYMid meet">
<defs>
<filter id="glow" x="-20%" y="-20%" width="140%" height="140%">
<feGaussianBlur stdDeviation="3" result="blur" />
<feComposite in="SourceGraphic" in2="blur" operator="over" />
</filter>
<filter id="glow-strong" x="-30%" y="-30%" width="160%" height="160%">
<feGaussianBlur stdDeviation="6" result="blur" />
<feComposite in="SourceGraphic" in2="blur" operator="over" />
</filter>
<linearGradient id="metal-grad-top" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stop-color="#1e293b" />
<stop offset="50%" stop-color="#334155" />
<stop offset="100%" stop-color="#0f172a" />
</linearGradient>
<linearGradient id="metal-grad-bot" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stop-color="#0f172a" />
<stop offset="50%" stop-color="#1e293b" />
<stop offset="100%" stop-color="#020617" />
</linearGradient>
</defs>
<g id="render-group"></g>
</svg>
</div>
<script>
/**
* 一体化等效六力线弹性关节 3D-in-SVG 渲染引擎
*/
const renderGroup = document.getElementById('render-group');
const cx = 500;
const cy = 375;
// 物理尺寸参数 (按比例映射至 1000x750 视口)
const R = 120; // 弹性体半径 (对应 D=88mm)
const H = 70; // 弹性体高度 (对应 L=22mm)
const FLANGE_R = 145; // 法兰盘半径
const FLANGE_H = 22; // 法兰盘厚度
const SPIRAL_ANGLE = 42 * (Math.PI / 180); // 槽螺旋角 42°
const BAND_WIDTH_ANGLE = (Math.PI * 2 / 6) * 0.65; // 弹性带宽占角
const CAM = {
rotX: 25 * (Math.PI / 180), // 俯视角
rotY: 0.3, // 环绕角
zoom: 1.15
};
const state = {
axial: 0,
shearX: 0,
shearY: 0,
twist: 0,
autoPlay: true,
time: 0
};
class Point3D {
constructor(x, y, z) {
this.x = x; this.y = y; this.z = z;
}
}
// 投影计算
function project(p3d) {
let x1 = p3d.x * Math.cos(CAM.rotY) - p3d.z * Math.sin(CAM.rotY);
let z1 = p3d.x * Math.sin(CAM.rotY) + p3d.z * Math.cos(CAM.rotY);
let y1 = p3d.y;
let x2 = x1;
let y2 = y1 * Math.cos(CAM.rotX) - z1 * Math.sin(CAM.rotX);
let z2 = y1 * Math.sin(CAM.rotX) + z1 * Math.cos(CAM.rotX);
return {
x: cx + x2 * CAM.zoom,
y: cy - y2 * CAM.zoom,
z: z2
};
}
// 颜色映射:根据拉伸/压缩应变计算色彩
function getStressColor(strain) {
let s = Math.max(-0.6, Math.min(0.6, strain)) * 1.6;
let r, g, b;
if (s >= 0) {
// 中性(#00f0ff) -> 拉伸(#ffaa00)
r = Math.floor(0 + s * 255);
g = Math.floor(240 - s * 70);
b = Math.floor(255 - s * 255);
} else {
// 中性(#00f0ff) -> 压缩(#ff007a)
let absS = Math.abs(s);
r = Math.floor(0 + absS * 255);
g = Math.floor(240 - absS * 240);
b = Math.floor(255 - absS * 133);
}
return `rgb(${r},${g},${b})`;
}
// 应用上法兰位移变换
function transformTopPoint(p) {
let x1 = p.x * Math.cos(state.twist) - p.z * Math.sin(state.twist);
let z1 = p.x * Math.sin(state.twist) + p.z * Math.cos(state.twist);
return new Point3D(
x1 + state.shearX,
p.y + state.axial,
z1 + state.shearY
);
}
function createCircle(radius, yPos, isTop = false, steps = 32) {
const pts = [];
for (let i = 0; i < steps; i++) {
let angle = (i / steps) * Math.PI * 2;
let p = new Point3D(radius * Math.cos(angle), yPos, radius * Math.sin(angle));
pts.push(isTop ? transformTopPoint(p) : p);
}
return pts;
}
// 几何构建与深度渲染
function buildScene() {
let elements = [];
const botY = -H / 2;
const topY = H / 2;
// 1. 下法兰 (固定端)
const botFlangeBottom = createCircle(FLANGE_R, botY - FLANGE_H);
const botFlangeTop = createCircle(FLANGE_R, botY);
elements.push({
type: 'disk',
pts: botFlangeBottom,
fill: 'url(#metal-grad-bot)',
stroke: 'rgba(0, 240, 255, 0.3)',
strokeWidth: 1.5,
opacity: 0.95
});
elements.push({
type: 'cylinder-side',
topPts: botFlangeTop,
botPts: botFlangeBottom,
fill: '#0f172a',
stroke: 'rgba(0, 240, 255, 0.4)'
});
elements.push({
type: 'disk',
pts: botFlangeTop,
fill: 'url(#metal-grad-top)',
stroke: 'rgba(0, 240, 255, 0.6)',
strokeWidth: 1.5,
opacity: 0.95
});
// 2. 上法兰 (运动端)
const topFlangeBottom = createCircle(FLANGE_R, topY, true);
const topFlangeTop = createCircle(FLANGE_R, topY + FLANGE_H, true);
elements.push({
type: 'disk',
pts: topFlangeBottom,
fill: 'url(#metal-grad-bot)',
stroke: 'rgba(0, 240, 255, 0.4)',
strokeWidth: 1.5,
opacity: 0.95
});
elements.push({
type: 'cylinder-side',
topPts: topFlangeTop,
botPts: topFlangeBottom,
fill: '#1e293b',
stroke: 'rgba(0, 240, 255, 0.6)'
});
elements.push({
type: 'disk',
pts: topFlangeTop,
fill: 'url(#metal-grad-top)',
stroke: '#00f0ff',
strokeWidth: 2,
opacity: 1
});
// 3. 计算 6 条螺旋弹性带
const nominalLen = Math.sqrt(
Math.pow(H, 2) +
Math.pow(R * Math.sin(SPIRAL_ANGLE), 2) +
Math.pow(R - R * Math.cos(SPIRAL_ANGLE), 2)
);
for (let i = 0; i < 6; i++) {
let baseAng = i * (Math.PI / 3);
let topAng = baseAng + SPIRAL_ANGLE;
// 弹性带底部四角
let p_bl = new Point3D(R * Math.cos(baseAng - BAND_WIDTH_ANGLE/2), botY, R * Math.sin(baseAng - BAND_WIDTH_ANGLE/2));
let p_br = new Point3D(R * Math.cos(baseAng + BAND_WIDTH_ANGLE/2), botY, R * Math.sin(baseAng + BAND_WIDTH_ANGLE/2));
// 弹性带顶部四角
let p_tl = transformTopPoint(new Point3D(R * Math.cos(topAng - BAND_WIDTH_ANGLE/2), topY, R * Math.sin(topAng - BAND_WIDTH_ANGLE/2)));
let p_tr = transformTopPoint(new Point3D(R * Math.cos(topAng + BAND_WIDTH_ANGLE/2), topY, R * Math.sin(topAng + BAND_WIDTH_ANGLE/2)));
// 受力中心线
let c_bot = new Point3D(R * Math.cos(baseAng), botY, R * Math.sin(baseAng));
let c_top = transformTopPoint(new Point3D(R * Math.cos(topAng), topY, R * Math.sin(topAng)));
// 应变与载荷计算
let currentLen = Math.hypot(c_top.x - c_bot.x, c_top.y - c_bot.y, c_top.z - c_bot.z);
let strain = (currentLen - nominalLen) / nominalLen;
elements.push({
type: 'band',
pts: [p_bl, p_br, p_tr, p_tl],
centerLine: [c_bot, c_top],
strain: strain * 4,
index: i
});
}
// 投影并求深度 (z)
elements.forEach(item => {
if (item.type === 'disk') {
item.proj = item.pts.map(p => project(p));
item.z = item.proj.reduce((s, p) => s + p.z, 0) / item.proj.length;
} else if (item.type === 'cylinder-side') {
item.topProj = item.topPts.map(p => project(p));
item.botProj = item.botPts.map(p => project(p));
item.z = (item.topProj.reduce((s, p) => s + p.z, 0) + item.botProj.reduce((s, p) => s + p.z, 0)) / (item.topProj.length * 2);
} else if (item.type === 'band') {
item.proj = item.pts.map(p => project(p));
item.projCenter = item.centerLine.map(p => project(p));
item.z = (item.projCenter[0].z + item.projCenter[1].z) / 2;
}
});
// 深度排序 (从远到近绘制)
elements.sort((a, b) => a.z - b.z);
// 渲染生成 SVG
let svgHtml = '';
// 中心定位辅助轴线
let axBot = project(new Point3D(0, botY - FLANGE_H * 1.5, 0));
let axTop = project(transformTopPoint(new Point3D(0, topY + FLANGE_H * 1.5, 0)));
svgHtml += `<line x1="${axBot.x}" y1="${axBot.y}" x2="${axTop.x}" y2="${axTop.y}" stroke="rgba(255,255,255,0.12)" stroke-width="1.5" stroke-dasharray="4,4" />`;
elements.forEach(item => {
if (item.type === 'disk') {
let pathD = `M ${item.proj[0].x} ${item.proj[0].y} ` + item.proj.slice(1).map(p => `L ${p.x} ${p.y}`).join(' ') + ' Z';
svgHtml += `<path d="${pathD}" fill="${item.fill}" stroke="${item.stroke}" stroke-width="${item.strokeWidth || 1}" opacity="${item.opacity || 1}"/>`;
}
else if (item.type === 'cylinder-side') {
// 绘制侧表面可见网格
let n = item.topProj.length;
for (let j = 0; j < n; j++) {
let next = (j + 1) % n;
// 面法向量粗略判断
let p1 = item.topProj[j];
let p2 = item.topProj[next];
let p3 = item.botProj[next];
let p4 = item.botProj[j];
// 仅绘制朝前的侧面片
let cross = (p2.x - p1.x) * (p4.y - p1.y) - (p2.y - p1.y) * (p4.x - p1.x);
if (cross > 0) {
let d = `M ${p1.x} ${p1.y} L ${p2.x} ${p2.y} L ${p3.x} ${p3.y} L ${p4.x} ${p4.y} Z`;
svgHtml += `<path d="${d}" fill="${item.fill}" stroke="${item.stroke}" stroke-width="0.6" opacity="0.85"/>`;
}
}
}
else if (item.type === 'band') {
let p = item.proj;
let cColor = getStressColor(item.strain);
// 弹性带四边形带曲率轮廓
let d = `M ${p[0].x} ${p[0].y} L ${p[1].x} ${p[1].y} L ${p[2].x} ${p[2].y} L ${p[3].x} ${p[3].y} Z`;
// 弹性体半透明实体带
svgHtml += `<path d="${d}" fill="${cColor}" fill-opacity="0.38" stroke="${cColor}" stroke-width="2" filter="url(#glow)"/>`;
// 等效力线 (动态虚线流动)
let c0 = item.projCenter[0];
let c1 = item.projCenter[1];
let dashOffset = -state.time * 24;
svgHtml += `<line x1="${c0.x}" y1="${c0.y}" x2="${c1.x}" y2="${c1.y}"
stroke="#ffffff" stroke-width="2.2" stroke-dasharray="6,4" stroke-dashoffset="${dashOffset}"
opacity="0.95" filter="url(#glow-strong)"/>`;
// 硫化连接根部锚点
svgHtml += `<circle cx="${c0.x}" cy="${c0.y}" r="3" fill="#00f0ff" stroke="#fff" stroke-width="1"/>`;
svgHtml += `<circle cx="${c1.x}" cy="${c1.y}" r="3" fill="#00f0ff" stroke="#fff" stroke-width="1"/>`;
}
});
renderGroup.innerHTML = svgHtml;
}
// 动画主循环
function animate() {
if (state.autoPlay) {
state.time += 0.015;
// 空间摄像机缓慢多角度巡览
CAM.rotY = 0.3 + Math.sin(state.time * 0.4) * 0.45;
// 复合载荷协同动作 (拉压 + 剪切 + 扭转)
state.axial = Math.sin(state.time * 1.6) * 14;
state.shearX = Math.cos(state.time * 1.1) * 16;
state.twist = Math.sin(state.time * 1.3) * 0.32;
syncUI(false);
}
buildScene();
requestAnimationFrame(animate);
}
// UI 交互绑定
const sZ = document.getElementById('slider-z');
const sX = document.getElementById('slider-x');
const sRz = document.getElementById('slider-rz');
const vZ = document.getElementById('val-z');
const vX = document.getElementById('val-x');
const vRz = document.getElementById('val-rz');
const btnReset = document.getElementById('btn-reset');
const autoDot = document.getElementById('auto-dot');
const autoText = document.getElementById('auto-text');
function stopAuto() {
if (state.autoPlay) {
state.autoPlay = false;
autoDot.style.animation = 'none';
autoDot.style.background = 'var(--text-muted)';
autoDot.style.boxShadow = 'none';
autoText.innerText = '手动控制模式';
autoText.style.color = 'var(--text-muted)';
}
}
function syncUI(fromUser = true) {
if (fromUser) {
state.axial = parseFloat(sZ.value);
state.shearX = parseFloat(sX.value);
state.twist = parseFloat(sRz.value) * (Math.PI / 180);
} else {
sZ.value = state.axial.toFixed(1);
sX.value = state.shearX.toFixed(1);
sRz.value = (state.twist * (180 / Math.PI)).toFixed(1);
}
vZ.innerText = `${parseFloat(sZ.value) >= 0 ? '+' : ''}${parseFloat(sZ.value).toFixed(1)} mm`;
vX.innerText = `${parseFloat(sX.value) >= 0 ? '+' : ''}${parseFloat(sX.value).toFixed(1)} mm`;
vRz.innerText = `${parseFloat(sRz.value) >= 0 ? '+' : ''}${parseFloat(sRz.value).toFixed(1)}°`;
}
[sZ, sX, sRz].forEach(el => {
el.addEventListener('input', () => {
stopAuto();
syncUI(true);
});
});
btnReset.addEventListener('click', () => {
state.autoPlay = true;
autoDot.style.animation = 'pulse 1.8s infinite';
autoDot.style.background = 'var(--cyan)';
autoDot.style.boxShadow = '0 0 6px var(--cyan)';
autoText.innerText = '自动多载荷演练中';
autoText.style.color = 'var(--text-main)';
});
// 立即启动渲染
buildScene();
requestAnimationFrame(animate);
</script>
</body>
</html>
积分规则:每轮对话扣10分
等待动画代码生成...
