分享图
动画工坊
引擎就绪
<!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分