import { useRef } from 'react'; import { useFrame } from '@react-three/fiber'; import { Grid, Environment, Text } from '@react-three/drei'; import { ViewerOrbitControls as OrbitControls } from './ViewerOrbitControls'; import * as THREE from 'three'; import SceneCanvas from '../SceneCanvas'; import FallbackDiagram from '../FallbackDiagram'; import { useCanvasTheme } from '@/lib/theme'; export interface Dynamics3DInput { /** Altura da estrutura (m) */ height: number; /** Frequência natural f₁ (Hz) */ freq: number; /** Velocidade do vento (m/s) */ windSpeed: number; /** Número de Scruton */ scruton: number; /** Tipo de seção */ sectionShape: string; /** Tamanho da seção (m) */ sectionSize: number; /** Mostrar rua de vórtices */ showVortexStreet: boolean; /** Mostrar modo de oscilação */ showModeShape: boolean; } const SCALE = 0.15; function OscillatingBuilding({ height, freq, scruton, sectionShape, sectionSize, showModeShape, }: { height: number; freq: number; scruton: number; sectionShape: string; sectionSize: number; showModeShape: boolean; }) { const groupRef = useRef(null); const timeRef = useRef(0); const hScaled = height * SCALE; const wScaled = sectionSize * SCALE; useFrame((_, delta) => { timeRef.current += delta; if (groupRef.current && showModeShape) { const amplitude = Math.min(0.3, 0.1 / Math.max(scruton, 0.1)); const displacement = amplitude * Math.sin(2 * Math.PI * freq * timeRef.current); groupRef.current.position.x = displacement; groupRef.current.rotation.z = displacement * 0.02; } }); const sectionColor = '#3b82f6'; return ( {sectionShape === 'circle' ? ( ) : ( )} {showModeShape && ( {[0, 0.25, 0.5, 0.75, 1].map((frac, i, arr) => { if (i === arr.length - 1) return null; const y0 = frac * hScaled; const y1 = arr[i + 1] * hScaled; const amp = 0.03; return ( ); })} )} ); } function VortexStreet({ windSpeed, height, sectionSize, }: { windSpeed: number; height: number; sectionSize: number; }) { const hScaled = height * SCALE; const wScaled = sectionSize * SCALE; return ( {Array.from({ length: 12 }).map((_, i) => { const x = wScaled / 2 + 0.5 + i * 0.5; const sign = i % 2 === 0 ? 1 : -1; const y = hScaled / 2 + sign * wScaled * 0.4 * (1 + i * 0.05); const opacity = Math.max(0.1, 0.8 - i * 0.06); return ( ); })} ); } function DynamicsModel(props: Dynamics3DInput) { const theme = useCanvasTheme(); const isDark = theme === 'dark'; return ( {props.showVortexStreet && ( )} h={props.height}m | f₁={props.freq}Hz | Sc={props.scruton.toFixed(1)} ); } export default function Dynamics3DViewer(props: Dynamics3DInput) { const theme = useCanvasTheme(); const isDark = theme === 'dark'; const cameraDistance = Math.max(props.height * SCALE * 2, 6); const fallback = ( ); return ( ); }