'use client'; import React, { useState, useEffect, useRef, useMemo } from 'react'; const VIEW_WIDTH = 1000; const VIEW_HEIGHT = 600; const CENTER_Y = 320; const FRONT_X = 300; const REAR_X = 750; // 辅助函数:生成齿轮路径 const createGearPath = (cx: number, cy: number, radius: number, teeth: number, holeRadius: number = 0) => { const outerRadius = radius; const innerRadius = radius * 0.85; const hole = holeRadius > 0 ? ` M ${cx} ${cy - holeRadius} A ${holeRadius} ${holeRadius} 0 1 0 ${cx} ${cy + holeRadius} A ${holeRadius} ${holeRadius} 0 1 0 ${cx} ${cy - holeRadius} Z` : ''; let d = ""; const angleStep = (Math.PI * 2) / teeth; for (let i = 0; i < teeth; i++) { const theta = i * angleStep; const thetaNext = (i + 1) * angleStep; const thetaMid1 = theta + angleStep * 0.25; const thetaMid2 = theta + angleStep * 0.75; // gap // 齿顶 const x0 = cx + outerRadius * Math.cos(theta); const y0 = cy + outerRadius * Math.sin(theta); const x1 = cx + outerRadius * Math.cos(thetaMid1); const y1 = cy + outerRadius * Math.sin(thetaMid1); // 齿底 const x2 = cx + innerRadius * Math.cos(thetaMid1); const y2 = cy + innerRadius * Math.sin(thetaMid1); const x3 = cx + innerRadius * Math.cos(thetaNext); const y3 = cy + innerRadius * Math.sin(thetaNext); if (i === 0) d += `M ${x0} ${y0}`; d += ` L ${x1} ${y1} L ${x2} ${y2} A ${innerRadius} ${innerRadius} 0 0 1 ${x3} ${y3}`; } d += " Z"; return d + hole; }; // 辅助函数:计算链条路径(两个圆的公切线包裹) const getChainPath = (r1: number, r2: number) => { const dist = REAR_X - FRONT_X; // 计算切线角度 const beta = Math.asin((r1 - r2) / dist); // 接触点坐标计算 // 前齿轮 (Front) const x1_top = FRONT_X + r1 * Math.sin(beta); const y1_top = CENTER_Y - r1 * Math.cos(beta); const x1_bot = FRONT_X + r1 * Math.sin(-beta); // symmetric approx for bottom const y1_bot = CENTER_Y + r1 * Math.cos(beta); // 后齿轮 (Rear) const x2_top = REAR_X + r2 * Math.sin(beta); const y2_top = CENTER_Y - r2 * Math.cos(beta); const x2_bot = REAR_X + r2 * Math.sin(-beta); const y2_bot = CENTER_Y + r2 * Math.cos(beta); // 修正角度以绘制圆弧 const startAngleFront = -Math.PI / 2 + beta; const endAngleFront = Math.PI * 1.5 - beta; const startAngleRear = -Math.PI / 2 + beta; const endAngleRear = Math.PI * 1.5 - beta; // 构建路径:上切线 -> 后齿轮弧 -> 下切线 -> 前齿轮弧 // SVG Arc: A rx ry x-axis-rotation large-arc-flag sweep-flag x y // 简化处理:直接连接切点并画弧 const largeArcFront = 1; const largeArcRear = 1; return ` M ${x1_top} ${y1_top} L ${x2_top} ${y2_top} A ${r2} ${r2} 0 ${largeArcRear} 1 ${x2_bot} ${y2_bot} L ${x1_bot} ${y1_bot} A ${r1} ${r1} 0 ${largeArcFront} 1 ${x1_top} ${y1_top} `; }; export const BicycleGearAnimation = () => { // 动画状态 const [time, setTime] = useState(0); const requestRef = useRef(); // 物理模拟参数 const [gearMode, setGearMode] = useState<'climb' | 'cruise' | 'sprint'>('cruise'); const [transitionProgress, setTransitionProgress] = useState(0); // 0 to 1 // 目标半径配置 const configs = useMemo(() => ({ climb: { frontR: 45, rearR: 45, label: '爬坡模式 (Low Gear)', ratio: '1:1', desc: '扭矩大,省力,速度慢' }, cruise: { frontR: 65, rearR: 30, label: '巡航模式 (Mid Gear)', ratio: '2.1:1', desc: '平衡速度与力量' }, sprint: { frontR: 90, rearR: 20, label: '冲刺模式 (High Gear)', ratio: '4.5:1', desc: '扭矩小,费力,速度极快' } }), []); // 当前插值计算的半径 const currentConfig = useRef({ frontR: 65, rearR: 30 }); // 自动切换逻辑 useEffect(() => { const modes: ('climb' | 'cruise' | 'sprint')[] = ['climb', 'cruise', 'sprint']; let index = 1; const interval = setInterval(() => { index = (index + 1) % 3; setGearMode(modes[index]); }, 5000); // 每5秒切换一次模式 return () => clearInterval(interval); }, []); // 动画循环 useEffect(() => { const animate = (t: number) => { setTime(t / 1000); // 平滑过渡半径 const target = configs[gearMode]; const speed = 0.05; // 变形速度 const diffFront = target.frontR - currentConfig.current.frontR; const diffRear = target.rearR - currentConfig.current.rearR; if (Math.abs(diffFront) > 0.1) currentConfig.current.frontR += diffFront * speed; if (Math.abs(diffRear) > 0.1) currentConfig.current.rearR += diffRear * speed; requestRef.current = requestAnimationFrame(animate); }; requestRef.current = requestAnimationFrame(animate); return () => cancelAnimationFrame(requestRef.current!); }, [gearMode, configs]); // 计算当前旋转角度 // 假设踏频恒定为 60 RPM (1 rev per second) -> 2PI rad/s const pedalSpeed = 2; // rad/s const frontRotation = time * pedalSpeed * -1; // 逆时针为正,但在SVG坐标系中通常顺时针旋转看起来像前进 // 链条线速度 V = omega * r const chainVelocity = pedalSpeed * currentConfig.current.frontR; // 后轮角速度 omega_rear = V / r_rear const rearSpeed = chainVelocity / currentConfig.current.rearR; const rearRotation = time * rearSpeed * -1; // 链条动画偏移 const chainDashOffset = -time * chainVelocity * 1.5; // 视觉上的链条移动 // 齿数估算 (视觉用) const frontTeeth = Math.floor(currentConfig.current.frontR / 3.5); const rearTeeth = Math.floor(currentConfig.current.rearR / 3.5); const chainPath = getChainPath(currentConfig.current.frontR, currentConfig.current.rearR); return ( {/* 背景网格 */} {/* 标题与UI */} BICYCLE GEAR PHYSICS 自行车传动原理可视化 {/* 状态面板 */} CURRENT MODE / 当前模式 {configs[gearMode].label} {configs[gearMode].desc} GEAR RATIO / 齿比 {(currentConfig.current.frontR / currentConfig.current.rearR).toFixed(2)}:1 {/* 物理公式 */} Input Speed (输入转速) × Ratio = Output Speed (输出转速) ω_out = ω_in × (R_front / R_rear) {/* 主绘图区域 */} {/* 连接轴 */} {/* 后轮 (Wheel) - 视觉上的大轮子 */} {/* 辐条 */} {Array.from({ length: 12 }).map((_, i) => ( ))} {/* 轮胎动效 */} {/* 链条 (Chain) */} {/* 使用 strokeDasharray 模拟链条节,使用 strokeDashoffset 模拟运动 */} {/* 前齿轮 (Front Chainring) */} {/* 牙盘主体 */} {/* 曲柄 (Cranks) */} {/* 脚踏轴 */} {/* 对侧曲柄 */} {/* 中心轴 */} {/* 后齿轮 (Rear Sprocket/Cog) */} {/* 标注文字 */} Drive Gear (主动轮) Input Power Driven Gear (从动轮) Output Speed {/* 速度指示器 */} SPEED: {Math.round(rearSpeed * 10)} km/h (Sim) ); };