独立渲染引擎就绪引擎就绪
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>TRIZ 理想最终解: 多段铰接式柔性底盘</title>
<style>
:root {
--bg-base: #06080D;
--bg-grid: #131A2A;
--stair-fill: #0F1522;
--stair-stroke: #3B82F6;
--track-outer: #1E293B;
--track-tread: #F59E0B;
--track-inner: #0B1120;
--joint-base: #10B981;
--joint-bend: #EF4444;
--text-main: #E2E8F0;
--text-dim: #94A3B8;
--hud-bg: rgba(6, 8, 13, 0.7);
--hud-border: rgba(59, 130, 246, 0.3);
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
user-select: none;
}
body {
background-color: var(--bg-base);
color: var(--text-main);
font-family: "SF Mono", "Fira Code", "JetBrains Mono", Consolas, monospace;
height: 100vh;
width: 100vw;
overflow: hidden;
display: flex;
justify-content: center;
align-items: center;
background-image:
linear-gradient(to right, var(--bg-grid) 1px, transparent 1px),
linear-gradient(to bottom, var(--bg-grid) 1px, transparent 1px);
background-size: 40px 40px;
background-position: center center;
}
/* 绝对定位的 HUD (Head-Up Display) 覆盖层,避免遮挡中央动画 */
.hud-panel {
position: absolute;
background: var(--hud-bg);
border: 1px solid var(--hud-border);
backdrop-filter: blur(8px);
-webkit-backdrop-filter: blur(8px);
padding: 16px;
border-radius: 8px;
z-index: 10;
}
.hud-top-left {
top: 24px;
left: 24px;
width: 300px;
}
.hud-bottom-center {
bottom: 24px;
left: 50%;
transform: translateX(-50%);
width: 400px;
display: flex;
flex-direction: column;
gap: 12px;
align-items: center;
}
.hud-title {
font-size: 14px;
font-weight: bold;
color: var(--stair-stroke);
margin-bottom: 8px;
letter-spacing: 1px;
text-transform: uppercase;
}
.hud-desc {
font-size: 12px;
color: var(--text-dim);
line-height: 1.5;
margin-bottom: 8px;
}
.hud-highlight {
color: var(--track-tread);
font-weight: bold;
}
.metrics {
font-size: 11px;
display: flex;
justify-content: space-between;
border-top: 1px dashed var(--hud-border);
padding-top: 8px;
margin-top: 8px;
}
/* 控制滑块样式 */
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: var(--bg-grid);
border-radius: 2px;
}
input[type=range]::-webkit-slider-thumb {
height: 16px;
width: 8px;
border-radius: 2px;
background: var(--track-tread);
cursor: pointer;
-webkit-appearance: none;
margin-top: -6px;
box-shadow: 0 0 8px var(--track-tread);
}
.control-labels {
display: flex;
justify-content: space-between;
width: 100%;
font-size: 10px;
color: var(--text-dim);
}
/* SVG 容器 */
.svg-container {
width: 100%;
height: 100%;
max-width: 1200px;
max-height: 800px;
display: flex;
justify-content: center;
align-items: center;
}
svg {
width: 100%;
height: 100%;
filter: drop-shadow(0 0 20px rgba(0,0,0,0.5));
}
.glow {
transition: all 0.1s ease;
}
</style>
</head>
<body>
<!-- 左上角说明面板 -->
<div class="hud-panel hud-top-left">
<div class="hud-title">TRIZ 最终理想解 (IFR)</div>
<div class="hud-desc">
<span class="hud-highlight">核心矛盾:</span>刚性底盘无法适应不规则台阶,易悬空打滑。<br>
<span class="hud-highlight">理想状态:</span>底盘在不增加复杂动力源的前提下,<span style="color:#10B981">自动顺应台阶轮廓</span>。
</div>
<div class="hud-desc" style="margin-bottom: 0;">
<span class="hud-highlight">资源利用:</span>利用台阶立面的<span style="color:#EF4444">阻抗反作用力</span>,迫使无源扭转弹簧铰接舱段被动折叠,达到最大面积贴合。
</div>
<div class="metrics">
<span>履带预紧力: 50N</span>
<span>最大铰接角: 90°</span>
</div>
</div>
<!-- 底部控制面板 -->
<div class="hud-panel hud-bottom-center">
<div class="control-labels">
<span>平地行驶</span>
<span>被动折叠</span>
<span>攀爬越障</span>
<span>恢复平直</span>
</div>
<input type="range" id="timeline-slider" min="50" max="950" value="50">
<div class="control-labels" style="justify-content: center; color: var(--stair-stroke);">
<span>[ 自动播放中 / 可拖动滑块接管控制 ]</span>
</div>
</div>
<div class="svg-container">
<svg viewBox="0 0 1000 600" preserveAspectRatio="xMidYMid meet">
<defs>
<!-- 阶梯纹理 -->
<pattern id="stair-pattern" width="20" height="20" patternUnits="userSpaceOnUse">
<path d="M 20 0 L 0 20 M 10 -10 L -10 10 M 30 10 L 10 30" stroke="#131A2A" stroke-width="2"/>
</pattern>
<!-- 发光滤镜 -->
<filter id="neon-glow-blue" x="-20%" y="-20%" width="140%" height="140%">
<feGaussianBlur stdDeviation="4" result="blur" />
<feComposite in="SourceGraphic" in2="blur" operator="over" />
</filter>
<filter id="neon-glow-red" x="-50%" y="-50%" width="200%" height="200%">
<feGaussianBlur stdDeviation="8" result="blur" />
<feComposite in="SourceGraphic" in2="blur" operator="over" />
</filter>
<!-- 箭头标记 -->
<marker id="arrow" viewBox="0 0 10 10" refX="5" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse">
<path d="M 0 0 L 10 5 L 0 10 z" fill="#EF4444" />
</marker>
</defs>
<!-- 静态场景:楼梯 -->
<g id="staircase">
<!-- 楼梯主体 -->
<path d="M -100 450 L 500 450 L 500 250 L 1100 250 L 1100 650 L -100 650 Z"
fill="url(#stair-pattern)"
stroke="var(--stair-stroke)"
stroke-width="2"/>
<!-- 楼梯发光边缘 -->
<path d="M -100 450 L 500 450 L 500 250 L 1100 250"
fill="none"
stroke="var(--stair-stroke)"
stroke-width="3"
filter="url(#neon-glow-blue)"/>
</g>
<!-- 动态受力指示器 (阻抗反作用力) -->
<g id="force-indicator" opacity="0" transition="opacity 0.2s">
<path d="M 540 350 L 510 350" stroke="#EF4444" stroke-width="4" marker-end="url(#arrow)" filter="url(#neon-glow-red)"/>
<text x="550" y="355" fill="#EF4444" font-size="12" font-family="monospace">反作用力触发折叠</text>
</g>
<!-- 动态场景:机器人底盘 -->
<g id="robot">
<!-- 履带外层 (黑色背景) -->
<path id="track-bg" fill="none" stroke="var(--track-outer)" stroke-width="56" stroke-linejoin="round" stroke-linecap="round"/>
<!-- 履带纹理 (滚动动画层) -->
<path id="track-tread" fill="none" stroke="var(--track-tread)" stroke-width="56" stroke-linejoin="round" stroke-linecap="round" stroke-dasharray="6 14"/>
<!-- 履带内圈镂空 -->
<path id="track-inner" fill="none" stroke="var(--track-inner)" stroke-width="44" stroke-linejoin="round" stroke-linecap="round"/>
<!-- 内部刚性支撑舱段 (3段) -->
<path id="chassis-links" fill="none" stroke="#334155" stroke-width="16" stroke-linejoin="round" stroke-linecap="round"/>
<!-- 铰接点 / 驱动轮 -->
<circle id="j1" r="10" fill="var(--joint-base)" />
<circle id="j2" r="10" fill="var(--joint-base)" />
<circle id="j3" r="10" fill="var(--joint-base)" />
<circle id="j4" r="10" fill="var(--joint-base)" />
<!-- 弯折应力发光指示层 -->
<circle id="glow-j2" r="10" fill="#EF4444" opacity="0" filter="url(#neon-glow-red)" pointer-events="none"/>
<circle id="glow-j3" r="10" fill="#EF4444" opacity="0" filter="url(#neon-glow-red)" pointer-events="none"/>
</g>
<!-- 状态标签追踪器 -->
<g id="state-tracker" opacity="0">
<line id="tracker-line" x1="0" y1="0" x2="0" y2="0" stroke="var(--track-tread)" stroke-width="1" stroke-dasharray="2 2"/>
<rect id="tracker-bg" x="0" y="0" width="120" height="24" fill="var(--hud-bg)" stroke="var(--track-tread)" stroke-width="1" rx="4"/>
<text id="tracker-text" x="60" y="16" fill="var(--track-tread)" font-size="10" text-anchor="middle" font-weight="bold">最大抓地力贴合</text>
</g>
</svg>
</div>
<script>
/**
* 运动学参数定义
*/
const L = 80; // 单个舱段的刚性长度
const R = 28; // 履带的包络厚度半径
const clWallX = 500 - R; // 立面中心线X
const clGroundY = 450 - R; // 平地中心线Y
const clTopY = 250 - R; // 台阶顶面中心线Y
const slider = document.getElementById('timeline-slider');
let s = parseFloat(slider.value);
let speed = 2.5; // 自动播放速度
let treadOffset = 0; // 履带滚动偏移量
let isAutoPlaying = true;
let interactionTimeout = null;
// 根据路径坐标s获取车头P4的位置
function getPosFromS(s) {
const wallHeight = clGroundY - clTopY;
// 在平地
if (s <= clWallX) {
return { x: s, y: clGroundY };
}
// 正在爬墙
if (s <= clWallX + wallHeight) {
return { x: clWallX, y: clGroundY - (s - clWallX) };
}
// 越过台阶在顶面
return { x: clWallX + (s - clWallX - wallHeight), y: clTopY };
}
// 给定前面一个节点的位置,求解路径上严格距离为 L 的后一个节点位置
function findPreviousJoint(p, L) {
let candidates = [];
const eps = 0.01;
// 1. 顶面相交
if (p.y <= clTopY + eps) {
let dy = p.y - clTopY;
if (L*L >= dy*dy) {
let dx = Math.sqrt(L*L - dy*dy);
let x = p.x - dx;
if (x >= clWallX - eps) candidates.push({x: x, y: clTopY});
}
}
// 2. 立面相交 (后一个点在墙上)
let dxWall = p.x - clWallX;
if (L*L >= dxWall*dxWall) {
let dy = Math.sqrt(L*L - dxWall*dxWall);
let y = p.y + dy; // Y变大表示向下(往回)
if (y >= clTopY - eps && y <= clGroundY + eps) {
candidates.push({x: clWallX, y: y});
}
}
// 3. 平地相交
let dyGround = p.y - clGroundY;
if (L*L >= dyGround*dyGround) {
let dx = Math.sqrt(L*L - dyGround*dyGround);
let x = p.x - dx;
if (x <= clWallX + eps) candidates.push({x: x, y: clGroundY});
}
// 将二维坐标映射为一维路径距离以便比较谁在"后面"
function getPathPos(pt) {
if (pt.y >= clGroundY - eps && pt.x <= clWallX + eps) return pt.x;
if (Math.abs(pt.x - clWallX) <= eps && pt.y <= clGroundY + eps && pt.y >= clTopY - eps) return clWallX + (clGroundY - pt.y);
if (pt.y <= clTopY + eps && pt.x >= clWallX - eps) return clWallX + (clGroundY - clTopY) + (pt.x - clWallX);
return -999;
}
let pPos = getPathPos(p);
let best = null;
let minPos = -Infinity;
for (let c of candidates) {
let cPos = getPathPos(c);
if (cPos < pPos - eps) { // 必须严格在当前点后方
if (cPos > minPos) {
minPos = cPos;
best = c;
}
}
}
return best || { x: p.x - L, y: p.y }; // 兜底返回水平后退
}
// 更新SVG节点位置
function setJoint(id, p) {
const el = document.getElementById(id);
el.setAttribute('cx', p.x);
el.setAttribute('cy', p.y);
}
// 计算关节处的弯折角度并更新发光效果
function updateGlow(P_prev, P_curr, P_next, glowId, jointId) {
let dx1 = P_prev.x - P_curr.x;
let dy1 = P_prev.y - P_curr.y;
let dx2 = P_next.x - P_curr.x;
let dy2 = P_next.y - P_curr.y;
let dot = dx1*dx2 + dy1*dy2;
let len1 = Math.sqrt(dx1*dx1 + dy1*dy1);
let len2 = Math.sqrt(dx2*dx2 + dy2*dy2);
let cosVal = dot / (len1 * len2);
cosVal = Math.max(-1, Math.min(1, cosVal)); // 防溢出
let angle = Math.acos(cosVal); // 直线为 PI
let bendAmount = (Math.PI - angle) / (Math.PI / 2); // 直线=0, 90度=1
bendAmount = Math.max(0, bendAmount);
const glowEl = document.getElementById(glowId);
const jointEl = document.getElementById(jointId);
if (bendAmount > 0.1) {
glowEl.setAttribute('opacity', bendAmount * 0.8);
glowEl.setAttribute('cx', P_curr.x);
glowEl.setAttribute('cy', P_curr.y);
glowEl.setAttribute('r', 10 + bendAmount * 15);
jointEl.setAttribute('fill', 'var(--joint-bend)');
} else {
glowEl.setAttribute('opacity', 0);
jointEl.setAttribute('fill', 'var(--joint-base)');
}
return bendAmount;
}
// 核心渲染循环
function renderLoop() {
if (isAutoPlaying) {
s += speed;
if (s > 950) s = 50; // 无缝循环
slider.value = s;
} else {
s = parseFloat(slider.value);
}
// 逆向解算四点坐标
let P4 = getPosFromS(s);
let P3 = findPreviousJoint(P4, L);
let P2 = findPreviousJoint(P3, L);
let P1 = findPreviousJoint(P2, L);
// 构建路径字符串
let pathD = `M ${P1.x} ${P1.y} L ${P2.x} ${P2.y} L ${P3.x} ${P3.y} L ${P4.x} ${P4.y}`;
document.getElementById('track-bg').setAttribute('d', pathD);
document.getElementById('track-tread').setAttribute('d', pathD);
document.getElementById('track-inner').setAttribute('d', pathD);
document.getElementById('chassis-links').setAttribute('d', pathD);
setJoint('j1', P1);
setJoint('j2', P2);
setJoint('j3', P3);
setJoint('j4', P4);
// 更新履带滚动动画
treadOffset -= 1.5;
document.getElementById('track-tread').setAttribute('stroke-dashoffset', treadOffset);
// 更新关节弯折发光状态 (TRIZ 视觉引导核心)
let bend2 = updateGlow(P1, P2, P3, 'glow-j2', 'j2');
let bend3 = updateGlow(P2, P3, P4, 'glow-j3', 'j3');
// 阻抗力提示箭头控制
const forceIndicator = document.getElementById('force-indicator');
if (P4.x === clWallX && P4.y < clGroundY && P4.y > clTopY + 50) {
forceIndicator.setAttribute('opacity', '1');
} else {
forceIndicator.setAttribute('opacity', '0');
}
// 动态状态跟踪框控制
const tracker = document.getElementById('state-tracker');
const trackerBg = document.getElementById('tracker-bg');
const trackerText = document.getElementById('tracker-text');
const trackerLine = document.getElementById('tracker-line');
let maxBend = Math.max(bend2, bend3);
if (maxBend > 0.5) {
tracker.setAttribute('opacity', '1');
// 定位在发生最大弯折的关节上方
let targetJoint = bend3 > bend2 ? P3 : P2;
let tx = targetJoint.x;
let ty = targetJoint.y - 80;
trackerBg.setAttribute('x', tx - 60);
trackerBg.setAttribute('y', ty);
trackerText.setAttribute('x', tx);
trackerText.setAttribute('y', ty + 16);
trackerLine.setAttribute('x1', tx);
trackerLine.setAttribute('y1', ty + 24);
trackerLine.setAttribute('x2', targetJoint.x);
trackerLine.setAttribute('y2', targetJoint.y - 10);
} else {
tracker.setAttribute('opacity', '0');
}
requestAnimationFrame(renderLoop);
}
// 交互控制逻辑
slider.addEventListener('input', () => {
isAutoPlaying = false;
clearTimeout(interactionTimeout);
});
slider.addEventListener('change', () => {
clearTimeout(interactionTimeout);
interactionTimeout = setTimeout(() => {
isAutoPlaying = true;
}, 2000); // 停止操作2秒后恢复自动播放
});
// 启动动画 (满足页面加载后自动播放的要求)
window.addEventListener('DOMContentLoaded', () => {
renderLoop();
});
</script>
</body>
</html>
积分规则:第一轮对话扣减8分,后续每轮扣6分
等待动画代码生成...
