独立渲染引擎就绪引擎就绪
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>自适应变径轮(Whegs)机械原理演示</title>
<style>
@import url('https://fonts.googleapis.com/css2?family=Share+Tech+Mono&family=Teko:wght@400;600&display=swap');
:root {
--bg-color: #060913;
--grid-color: #111a33;
--cyan: #00f3ff;
--cyan-dim: #007b80;
--red: #ff2a55;
--orange: #ff9d00;
--text-main: #e0f7fa;
--text-dim: #7097a8;
}
body, html {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
background-color: var(--bg-color);
display: flex;
justify-content: center;
align-items: center;
font-family: 'Share Tech Mono', monospace;
overflow: hidden;
color: var(--text-main);
}
#animation-container {
position: relative;
width: 100%;
max-width: 1200px;
aspect-ratio: 16 / 9;
background: radial-gradient(circle at 50% 50%, #0a1124 0%, #060913 100%);
box-shadow: 0 0 50px rgba(0, 243, 255, 0.05) inset;
border: 1px solid rgba(0, 243, 255, 0.1);
overflow: hidden;
}
svg {
width: 100%;
height: 100%;
display: block;
}
/* Overlay UI Styling */
.hud-panel {
position: absolute;
background: rgba(6, 9, 19, 0.8);
border: 1px solid var(--cyan-dim);
backdrop-filter: blur(4px);
padding: 15px;
box-shadow: 0 0 20px rgba(0, 0, 0, 0.5);
z-index: 10;
}
.hud-panel::before {
content: '';
position: absolute;
top: 0; left: 0; width: 100%; height: 2px;
background: linear-gradient(90deg, var(--cyan), transparent);
}
#data-panel {
top: 30px;
left: 30px;
width: 280px;
}
#triz-panel {
top: 30px;
right: 30px;
width: 320px;
}
.hud-title {
font-family: 'Teko', sans-serif;
font-size: 24px;
letter-spacing: 1px;
color: var(--cyan);
margin-bottom: 10px;
text-transform: uppercase;
}
.hud-row {
display: flex;
justify-content: space-between;
margin-bottom: 8px;
font-size: 14px;
}
.hud-label { color: var(--text-dim); }
.hud-value { font-weight: bold; color: var(--text-main); text-shadow: 0 0 5px rgba(255,255,255,0.3); }
.progress-bar-bg {
width: 100%;
height: 6px;
background: #1a2640;
margin-top: 4px;
position: relative;
overflow: hidden;
}
.progress-bar-fill {
height: 100%;
background: var(--cyan);
width: 0%;
transition: background-color 0.2s;
}
.triz-tag {
display: inline-block;
background: rgba(0, 243, 255, 0.1);
color: var(--cyan);
padding: 2px 6px;
font-size: 12px;
border: 1px solid var(--cyan-dim);
margin-bottom: 8px;
}
.triz-desc {
font-size: 13px;
line-height: 1.5;
color: var(--text-dim);
}
.highlight-text { color: var(--orange); font-weight: bold; }
/* SVG Specific Styling */
.glow-cyan { filter: drop-shadow(0 0 8px rgba(0, 243, 255, 0.6)); }
.glow-red { filter: drop-shadow(0 0 10px rgba(255, 42, 85, 0.8)); }
.stair-path {
fill: #0c1424;
stroke: var(--cyan-dim);
stroke-width: 2;
}
.stair-top-edge {
stroke: var(--cyan);
stroke-width: 3;
stroke-linecap: round;
}
</style>
</head>
<body>
<div id="animation-container">
<!-- Overlay UI -->
<div class="hud-panel" id="data-panel">
<div class="hud-title">SYS: ADAPTIVE_WHEGS</div>
<div class="hud-row">
<span class="hud-label">MODE:</span>
<span class="hud-value" id="val-mode">ROLLING</span>
</div>
<div class="hud-row">
<span class="hud-label">DIAMETER:</span>
<span class="hud-value" id="val-diameter">20.0 cm</span>
</div>
<div class="hud-row" style="margin-top: 15px;">
<span class="hud-label">RESISTANCE TORQUE:</span>
<span class="hud-value" id="val-torque">12 N·m</span>
</div>
<div class="progress-bar-bg">
<div class="progress-bar-fill" id="bar-torque"></div>
</div>
<div class="hud-row" style="margin-top: 15px;">
<span class="hud-label">LATCH STATUS:</span>
<span class="hud-value" id="val-latch" style="color: var(--cyan)">LOCKED</span>
</div>
</div>
<div class="hud-panel" id="triz-panel">
<div class="hud-title">IFR ANALYSIS</div>
<div class="triz-tag">最终理想解 (IFR)</div>
<div class="triz-desc">
系统在不需要复杂电控和额外驱动的情况下,<span class="highlight-text">依靠自身受到的阻力(有害因素)作为触发源</span>,自动适应地形变化。
</div>
<div class="triz-tag" style="margin-top:10px;">空间与时间的物理矛盾分离</div>
<div class="triz-desc">
<br/>- 平地移动时:结构必须是连续圆轮(高效率)。
<br/>- 越障爬坡时:结构必须是分散辐条(强抓地)。
<br/>→ 解决方案:由内部<span class="highlight-text">纯机械弹簧锁扣</span>根据实时扭矩进行瞬态构型切换。
</div>
</div>
<!-- Main SVG Canvas -->
<svg id="canvas" viewBox="0 0 1200 675" preserveAspectRatio="xMidYMid slice">
<defs>
<pattern id="grid" width="40" height="40" patternUnits="userSpaceOnUse">
<path d="M 40 0 L 0 0 0 40" fill="none" stroke="var(--grid-color)" stroke-width="1"/>
</pattern>
<filter id="neon-cyan" x="-20%" y="-20%" width="140%" height="140%">
<feGaussianBlur stdDeviation="4" result="blur" />
<feMerge>
<feMergeNode in="blur"/>
<feMergeNode in="SourceGraphic"/>
</feMerge>
</filter>
<filter id="neon-red" x="-20%" y="-20%" width="140%" height="140%">
<feGaussianBlur stdDeviation="5" result="blur" />
<feComponentTransfer in="blur" result="glow">
<feFuncA type="linear" slope="2"/>
</feComponentTransfer>
<feMerge>
<feMergeNode in="glow"/>
<feMergeNode in="SourceGraphic"/>
</feMerge>
</filter>
<linearGradient id="metal-grad" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stop-color="#2a3b5c" />
<stop offset="50%" stop-color="#141e33" />
<stop offset="100%" stop-color="#0a1124" />
</linearGradient>
<marker id="arrow" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse">
<path d="M 0 1 L 10 5 L 0 9 z" fill="var(--orange)" />
</marker>
</defs>
<!-- Background Grid -->
<rect width="100%" height="100%" fill="url(#grid)" />
<!-- Terrain Context Group -->
<g id="terrain" transform="translate(0, 0)">
<!-- Flat Ground -->
<path d="M -100 500 L 600 500 L 600 700 L -100 700 Z" class="stair-path" />
<line x1="-100" y1="500" x2="600" y2="500" class="stair-top-edge" />
<!-- Stair Step -->
<path d="M 600 500 L 600 320 L 1300 320 L 1300 700 L 600 700 Z" class="stair-path" />
<line x1="600" y1="320" x2="1300" y2="320" class="stair-top-edge" filter="url(#neon-cyan)" />
<!-- Dimensions/Guidelines -->
<line x1="600" y1="500" x2="600" y2="540" stroke="var(--text-dim)" stroke-width="1" stroke-dasharray="4" />
<line x1="600" y1="320" x2="560" y2="320" stroke="var(--text-dim)" stroke-width="1" stroke-dasharray="4" />
<path d="M 580 320 L 580 500" stroke="var(--text-dim)" stroke-width="1" marker-start="url(#arrow)" marker-end="url(#arrow)" />
<text x="560" y="415" fill="var(--text-dim)" font-size="14" transform="rotate(-90, 560, 415)" text-anchor="middle">OBSTACLE ΔH</text>
</g>
<!-- Dynamic Vectors Group -->
<g id="force-vectors" opacity="0">
<!-- Pushing Force -->
<path d="M 0 0 L -60 60" stroke="var(--orange)" stroke-width="4" marker-end="url(#arrow)" id="vec-push" filter="url(#neon-red)"/>
<!-- Hooking Force -->
<path d="M 0 0 L 40 40" stroke="var(--cyan)" stroke-width="4" marker-end="url(#arrow)" id="vec-hook" filter="url(#neon-cyan)"/>
</g>
<!-- The Wheg Assembly -->
<!-- Will be dynamically transformed via JS -->
<g id="wheel-assembly" transform="translate(200, 400)">
<!-- Ideal/Reference Circle (Faint) -->
<circle cx="0" cy="0" r="100" fill="none" stroke="rgba(0, 243, 255, 0.15)" stroke-width="1" stroke-dasharray="5 5" />
<circle cx="0" cy="0" r="175" fill="none" stroke="rgba(255, 157, 0, 0.1)" stroke-width="1" stroke-dasharray="5 5" />
<!-- Rotating Component -->
<g id="wheel-rotator">
<!-- Inner Hub -->
<circle cx="0" cy="0" r="40" fill="url(#metal-grad)" stroke="var(--cyan-dim)" stroke-width="2" />
<circle cx="0" cy="0" r="15" fill="#060913" stroke="var(--cyan)" stroke-width="3" filter="url(#neon-cyan)" />
<path d="M 0 -15 L 0 15 M -15 0 L 15 0" stroke="var(--cyan)" stroke-width="2" />
<!-- Legs Container (Generated via JS) -->
<g id="legs-container"></g>
</g>
</g>
<!-- Fading Overlay for Loop Reset -->
<rect id="fade-overlay" width="100%" height="100%" fill="#060913" opacity="0" pointer-events="none" />
</svg>
</div>
<script>
/**
* 核心参数配置
*/
const CONFIG = {
R_MIN: 100, // 收缩时半径 (px, 对应物理模型20cm)
R_MAX: 175, // 展开时腿长 (px, 对应物理模型35cm)
GROUND_Y: 500, // 平地Y坐标
STEP_X: 600, // 台阶边缘X坐标
STEP_Y: 320, // 台阶顶面Y坐标
TORQUE_THRESH: 50, // 触发阈值
LEG_COUNT: 3 // 轮腿数量
};
// DOM 元素引用
const elWheelAssy = document.getElementById('wheel-assembly');
const elWheelRotator = document.getElementById('wheel-rotator');
const elLegsContainer = document.getElementById('legs-container');
const elForceVectors = document.getElementById('force-vectors');
const elVecPush = document.getElementById('vec-push');
const elVecHook = document.getElementById('vec-hook');
const elFade = document.getElementById('fade-overlay');
// UI 元素引用
const uiMode = document.getElementById('val-mode');
const uiDia = document.getElementById('val-diameter');
const uiTorque = document.getElementById('val-torque');
const uiTorqueBar = document.getElementById('bar-torque');
const uiLatch = document.getElementById('val-latch');
/**
* 初始化生成轮腿SVG结构
*/
function buildWheelGeometry() {
elLegsContainer.innerHTML = '';
const angleStep = 360 / CONFIG.LEG_COUNT;
for (let i = 0; i < CONFIG.LEG_COUNT; i++) {
const rot = i * angleStep;
// 创建单个腿的组
const legGroup = document.createElementNS("http://www.w3.org/2000/svg", "g");
legGroup.setAttribute("transform", `rotate(${rot})`);
legGroup.setAttribute("class", "leg-group");
legGroup.dataset.index = i;
// 1. 固定导轨 (滑轨)
const track = document.createElementNS("http://www.w3.org/2000/svg", "line");
track.setAttribute("x1", "40"); track.setAttribute("y1", "0");
track.setAttribute("x2", `${CONFIG.R_MAX - 20}`); track.setAttribute("y2", "0");
track.setAttribute("stroke", "#141e33"); track.setAttribute("stroke-width", "12");
track.setAttribute("stroke-linecap", "round");
legGroup.appendChild(track);
// 2. 滑动连杆组 (包含弹簧、外轮缘、锁扣)
const sliderGroup = document.createElementNS("http://www.w3.org/2000/svg", "g");
sliderGroup.setAttribute("class", "slider-group");
// 弹簧视觉线 (简化为波浪线或多边形,这里用虚线代表拉伸)
const spring = document.createElementNS("http://www.w3.org/2000/svg", "line");
spring.setAttribute("x1", "40"); spring.setAttribute("y1", "0");
spring.setAttribute("x2", `${CONFIG.R_MIN - 10}`); spring.setAttribute("y2", "0");
spring.setAttribute("stroke", "var(--cyan-dim)");
spring.setAttribute("stroke-width", "6");
spring.setAttribute("stroke-dasharray", "4 4");
spring.setAttribute("class", "leg-spring");
sliderGroup.appendChild(spring);
// 外轮缘弧线段 (120度)
const halfAngle = (angleStep / 2) * (Math.PI / 180);
// 调整弧线两端留一点间隙,避免完全重合,体现机械拼接感
const gap = 0.05;
const x1 = CONFIG.R_MIN * Math.cos(-halfAngle + gap);
const y1 = CONFIG.R_MIN * Math.sin(-halfAngle + gap);
const x2 = CONFIG.R_MIN * Math.cos(halfAngle - gap);
const y2 = CONFIG.R_MIN * Math.sin(halfAngle - gap);
const rim = document.createElementNS("http://www.w3.org/2000/svg", "path");
const d = `M ${x1} ${y1} A ${CONFIG.R_MIN} ${CONFIG.R_MIN} 0 0 1 ${x2} ${y2}`;
rim.setAttribute("d", d);
rim.setAttribute("fill", "none");
rim.setAttribute("stroke", "url(#metal-grad)");
rim.setAttribute("stroke-width", "16");
rim.setAttribute("stroke-linecap", "round");
sliderGroup.appendChild(rim);
// 轮缘外侧高亮纹理
const rimOuter = document.createElementNS("http://www.w3.org/2000/svg", "path");
rimOuter.setAttribute("d", d);
rimOuter.setAttribute("fill", "none");
rimOuter.setAttribute("stroke", "var(--cyan)");
rimOuter.setAttribute("stroke-width", "3");
rimOuter.setAttribute("stroke-dasharray", "10 15");
sliderGroup.appendChild(rimOuter);
// 主支撑杆
const strut = document.createElementNS("http://www.w3.org/2000/svg", "line");
strut.setAttribute("x1", "40"); strut.setAttribute("y1", "0");
strut.setAttribute("x2", `${CONFIG.R_MIN}`); strut.setAttribute("y2", "0");
strut.setAttribute("stroke", "#2a3b5c");
strut.setAttribute("stroke-width", "8");
sliderGroup.appendChild(strut);
// 离心/扭矩弹簧锁扣 (触发销)
const latch = document.createElementNS("http://www.w3.org/2000/svg", "rect");
latch.setAttribute("x", "50"); latch.setAttribute("y", "-8");
latch.setAttribute("width", "10"); latch.setAttribute("height", "16");
latch.setAttribute("fill", "var(--cyan)");
latch.setAttribute("class", "leg-latch");
sliderGroup.appendChild(latch);
legGroup.appendChild(sliderGroup);
elLegsContainer.appendChild(legGroup);
}
}
/**
* 动画状态机与时间线控制器
*/
const TIMELINE = {
DURATION: 8000, // 总周期 8 秒
PHASES: [
{ id: 'ROLL', start: 0.00, end: 0.25 }, // 0 - 2s: 平地滚动
{ id: 'IMPACT', start: 0.25, end: 0.35 }, // 2 - 2.8s: 撞击台阶,扭矩激增
{ id: 'UNLOCK', start: 0.35, end: 0.45 }, // 2.8 - 3.6s: 锁扣释放,变径展开
{ id: 'CLIMB', start: 0.45, end: 0.75 }, // 3.6 - 6s: 跨越台阶,像桨一样爬升
{ id: 'RECOVER', start: 0.75, end: 0.85 }, // 6 - 6.8s: 越障完毕,阻力消失,收缩复原
{ id: 'ROLL_UP', start: 0.85, end: 0.95 }, // 6.8 - 7.6s: 在高台上继续滚动
{ id: 'FADE', start: 0.95, end: 1.00 } // 7.6 - 8s: 淡出重置
]
};
// 缓动函数
function easeInOutQuad(t) { return t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t; }
function easeOutElastic(t) {
const c4 = (2 * Math.PI) / 3;
return t === 0 ? 0 : t === 1 ? 1 : Math.pow(2, -10 * t) * Math.sin((t * 10 - 0.75) * c4) + 1;
}
function lerp(a, b, t) { return a + (b - a) * t; }
let startTime = null;
function updateAnimation(timestamp) {
if (!startTime) startTime = timestamp;
let elapsed = timestamp - startTime;
let progress = (elapsed % TIMELINE.DURATION) / TIMELINE.DURATION;
// 当前状态变量
let x = 0, y = 0, angle = 0;
let expansion = 0; // 0 (收缩 R_MIN) 到 1 (展开 R_MAX)
let torquePct = 0.1; // 10% 基础扭矩
let modeText = "ROLLING";
let isRedAlert = false;
let showVectors = false;
let vecData = { pX:0, pY:0, hX:0, hY:0 }; // 力向量数据
let opacity = 1;
// --- 状态计算 ---
if (progress < 0.25) {
// 阶段 1:平地滚动
let p = progress / 0.25;
let startX = 100;
let endX = CONFIG.STEP_X - CONFIG.R_MIN - 2; // 刚好接触台阶
x = lerp(startX, endX, p);
y = CONFIG.GROUND_Y - CONFIG.R_MIN;
angle = (x - startX) / CONFIG.R_MIN * (180 / Math.PI);
torquePct = 0.1;
modeText = "CRUISE (ROUND TIRE)";
} else if (progress < 0.35) {
// 阶段 2:撞击台阶,扭矩激增
let p = (progress - 0.25) / 0.10;
x = CONFIG.STEP_X - CONFIG.R_MIN - 2;
y = CONFIG.GROUND_Y - CONFIG.R_MIN;
// 模拟电机受阻时的微小抖动和扭矩攀升
angle = ((x - 100) / CONFIG.R_MIN * (180 / Math.PI)) + Math.sin(p * Math.PI * 10) * 2 * p;
torquePct = lerp(0.1, 1.0, p);
modeText = "OBSTACLE DETECTED";
if (p > 0.5) isRedAlert = true;
} else if (progress < 0.45) {
// 阶段 3:锁扣释放,纯机械变径展开
let p = (progress - 0.35) / 0.10;
let easeP = easeOutElastic(p);
x = CONFIG.STEP_X - CONFIG.R_MIN - 2;
// 展开时,半径变大,支撑点在平地,所以轴心必须被抬高
expansion = easeP;
let currentR = lerp(CONFIG.R_MIN, CONFIG.R_MAX, expansion);
y = CONFIG.GROUND_Y - currentR;
// 保持之前累积的角度,不再滚动
angle = ((x - 100) / CONFIG.R_MIN * (180 / Math.PI));
// 扭矩释放
torquePct = lerp(1.0, 0.4, p);
modeText = "MECHANICAL TRIGGER";
isRedAlert = true;
} else if (progress < 0.75) {
// 阶段 4:像风车桨一样“刨”上台阶
let p = (progress - 0.45) / 0.30;
let easeP = easeInOutQuad(p);
expansion = 1.0;
torquePct = 0.8; // 爬坡需较大扭矩但未超限
modeText = "WHEGS ENGAGED";
isRedAlert = false;
showVectors = true;
// 复杂的运动学模拟:轴心绕台阶边缘划出一个弧线
// 起点 (x0, y0)
let x0 = CONFIG.STEP_X - CONFIG.R_MIN - 2;
let y0 = CONFIG.GROUND_Y - CONFIG.R_MAX;
// 终点 (x1, y1) 在台阶上方
let x1 = CONFIG.STEP_X + CONFIG.R_MAX;
let y1 = CONFIG.STEP_Y - CONFIG.R_MAX;
// 简单使用二阶贝塞尔曲线模拟跨越轨迹
let cx = CONFIG.STEP_X - 20;
let cy = CONFIG.STEP_Y - CONFIG.R_MAX - 50;
x = (1-easeP)*(1-easeP)*x0 + 2*(1-easeP)*easeP*cx + easeP*easeP*x1;
y = (1-easeP)*(1-easeP)*y0 + 2*(1-easeP)*easeP*cy + easeP*easeP*y1;
// 腿的旋转:整个爬坡过程刚好转过 120 度 (一根腿的夹角),实现交替步态
let startAngle = ((x0 - 100) / CONFIG.R_MIN * (180 / Math.PI));
angle = startAngle + easeP * 120;
// 计算力向量位置 (大致在轮的后下方和前上方)
vecData.pX = x - CONFIG.R_MAX * 0.5; vecData.pY = y + CONFIG.R_MAX * 0.8;
vecData.hX = x + CONFIG.R_MAX * 0.8; vecData.hY = y - CONFIG.R_MAX * 0.2;
} else if (progress < 0.85) {
// 阶段 5:越障完毕,阻力消失,拉簧收缩复原
let p = (progress - 0.75) / 0.10;
let easeP = easeInOutQuad(p);
// 终点X
let xStart = CONFIG.STEP_X + CONFIG.R_MAX;
let xEnd = xStart + 80;
x = lerp(xStart, xEnd, p);
expansion = 1.0 - easeP; // 收缩
let currentR = lerp(CONFIG.R_MIN, CONFIG.R_MAX, expansion);
y = CONFIG.STEP_Y - currentR;
// 同步旋转补偿
let baseAngle = ((xStart - 100) / CONFIG.R_MIN * (180 / Math.PI)) + 120;
angle = baseAngle + (x - xStart) / currentR * (180 / Math.PI);
torquePct = lerp(0.8, 0.1, p);
modeText = "RECOVERY (SPRING RETRACT)";
} else if (progress < 0.95) {
// 阶段 6:高台继续滚动
let p = (progress - 0.85) / 0.10;
let xStart = CONFIG.STEP_X + CONFIG.R_MAX + 80;
let xEnd = 1200;
x = lerp(xStart, xEnd, p);
y = CONFIG.STEP_Y - CONFIG.R_MIN;
expansion = 0;
let baseAngle = ((CONFIG.STEP_X + CONFIG.R_MAX - 100) / CONFIG.R_MIN * (180 / Math.PI)) + 120 + (80 / CONFIG.R_MIN * (180/Math.PI));
angle = baseAngle + (x - xStart) / CONFIG.R_MIN * (180 / Math.PI);
torquePct = 0.1;
modeText = "CRUISE (ROUND TIRE)";
} else {
// 阶段 7:淡出重置
opacity = 1.0 - (progress - 0.95) / 0.05;
x = 1200;
y = CONFIG.STEP_Y - CONFIG.R_MIN;
expansion = 0;
torquePct = 0;
modeText = "RESTARTING...";
}
// --- 应用到 DOM ---
// 主容器变换
elWheelAssy.setAttribute("transform", `translate(${x}, ${y})`);
elWheelRotator.setAttribute("transform", `rotate(${angle})`);
elFade.setAttribute("opacity", 1 - opacity);
// 内部轮腿展开变换
const sliders = document.querySelectorAll('.slider-group');
const springs = document.querySelectorAll('.leg-spring');
const latches = document.querySelectorAll('.leg-latch');
let expandDist = expansion * (CONFIG.R_MAX - CONFIG.R_MIN);
sliders.forEach((slider, idx) => {
// 轮缘和腿向外平移
slider.setAttribute("transform", `translate(${expandDist}, 0)`);
// 弹簧随之拉长视觉效果
let springLine = springs[idx];
springLine.setAttribute("x2", `${CONFIG.R_MIN - 10 + expandDist}`);
// 弹簧受力变色
if (expansion > 0.1) {
springLine.setAttribute("stroke", "var(--orange)");
} else {
springLine.setAttribute("stroke", "var(--cyan-dim)");
}
// 锁扣状态更新
let latch = latches[idx];
if (progress >= 0.25 && progress < 0.35) {
// 预备断开,闪红光
latch.setAttribute("fill", isRedAlert ? "var(--red)" : "var(--cyan)");
latch.setAttribute("filter", "url(#neon-red)");
} else if (progress >= 0.35 && progress < 0.75) {
// 缩回释放状态
latch.setAttribute("transform", "translate(0, 10)"); // 机械销收起
latch.setAttribute("fill", "var(--red)");
latch.setAttribute("filter", "none");
} else {
// 正常锁定
latch.setAttribute("transform", "translate(0, 0)");
latch.setAttribute("fill", "var(--cyan)");
latch.setAttribute("filter", "url(#neon-cyan)");
}
});
// 力向量显示控制
if (showVectors) {
elForceVectors.setAttribute("opacity", "1");
// 将向量箭头放置在受力点附件,做简单的反向推力动画
let pulse = (Math.sin(timestamp / 50) + 1) * 10;
elVecPush.setAttribute("d", `M ${vecData.pX} ${vecData.pY} L ${vecData.pX - 40 - pulse} ${vecData.pY + 40 + pulse}`);
elVecHook.setAttribute("d", `M ${vecData.hX} ${vecData.hY} L ${vecData.hX + 30 + pulse} ${vecData.hY - 30 - pulse}`);
} else {
elForceVectors.setAttribute("opacity", "0");
}
// --- UI 数据面板更新 ---
let actualDia = 20.0 + (expansion * 15.0); // 20cm to 35cm
uiDia.innerText = actualDia.toFixed(1) + " cm";
uiMode.innerText = modeText;
let torqueVal = torquePct * 70; // 最大模拟 70 N.m
uiTorque.innerText = torqueVal.toFixed(1) + " N·m";
uiTorqueBar.style.width = (torquePct * 100) + "%";
if (isRedAlert) {
uiTorqueBar.style.backgroundColor = "var(--red)";
uiTorqueBar.style.boxShadow = "0 0 10px var(--red)";
uiTorque.style.color = "var(--red)";
} else if (torquePct > 0.5) {
uiTorqueBar.style.backgroundColor = "var(--orange)";
uiTorqueBar.style.boxShadow = "0 0 10px var(--orange)";
uiTorque.style.color = "var(--orange)";
} else {
uiTorqueBar.style.backgroundColor = "var(--cyan)";
uiTorqueBar.style.boxShadow = "none";
uiTorque.style.color = "var(--text-main)";
}
if (progress >= 0.35 && progress < 0.75) {
uiLatch.innerText = "RELEASED (MECHANICAL)";
uiLatch.style.color = "var(--red)";
} else if (progress >= 0.75 && progress < 0.85) {
uiLatch.innerText = "RE-ENGAGING";
uiLatch.style.color = "var(--orange)";
} else {
uiLatch.innerText = "LOCKED";
uiLatch.style.color = "var(--cyan)";
}
// 继续下一帧
requestAnimationFrame(updateAnimation);
}
// 初始化并自动播放
window.addEventListener('DOMContentLoaded', () => {
buildWheelGeometry();
requestAnimationFrame(updateAnimation);
});
</script>
</body>
</html>
积分规则:第一轮对话扣减8分,后续每轮扣6分
等待动画代码生成...
