import { useMemo } from 'react'; 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 Dome3DInput { diameter: number; rise: number; wallHeight: number; cpi: number; cpeBarlavento: number; cpeTopo: number; cpeLateral: number; } function domeColor(cpe: number, cpi: number, isDark: boolean): THREE.Color { const p = cpe - cpi; const intensity = Math.min(1, Math.abs(p) / 1.5); const l = isDark ? (65 - intensity * 15) : (60 - intensity * 25); if (p > 0) { return new THREE.Color(`hsl(${215 - intensity * 10}, ${70 + intensity * 25}%, ${Math.max(35, l)}%)`); } return new THREE.Color(`hsl(0, ${70 + intensity * 25}%, ${Math.max(40, l + 5)}%)`); } function DomeModel({ diameter, rise, wallHeight, cpi, cpeBarlavento, cpeTopo, cpeLateral }: Dome3DInput) { const theme = useCanvasTheme(); const isDark = theme === 'dark'; const radius = diameter / 2; const segments = 64; // Cúpula (casca esférica) — gerada por segmentos de 0° a 360° para fechar o domo const domeGeoms = useMemo(() => { const arr: { startTheta: number; endTheta: number; color: THREE.Color }[] = []; // Divide a circunferência completa (360°) em 6 zonas (simétricas) const zones = [ { fromDeg: 0, toDeg: 60, cpe: cpeBarlavento }, { fromDeg: 60, toDeg: 120, cpe: cpeTopo }, { fromDeg: 120, toDeg: 180, cpe: cpeLateral }, { fromDeg: 180, toDeg: 240, cpe: cpeLateral }, { fromDeg: 240, toDeg: 300, cpe: cpeTopo }, { fromDeg: 300, toDeg: 360, cpe: cpeBarlavento }, ]; for (const z of zones) { arr.push({ startTheta: (z.fromDeg * Math.PI) / 180, endTheta: (z.toDeg * Math.PI) / 180, color: domeColor(z.cpe, cpi, isDark), }); } return arr; }, [cpeBarlavento, cpeTopo, cpeLateral, cpi, isDark]); // Raio da esfera da calota esférica baseada na flecha (rise) e raio da base (radius) const rSphere = useMemo(() => { return (radius * radius + rise * rise) / (2 * rise); }, [radius, rise]); return ( {/* Parede cilíndrica inferior */} {/* Detalhes de anéis metálicos nas bordas */} {/* Cúpula de cobertura (Spherical Cap) segmentada */} {domeGeoms.map((zone, idx) => { const phiSteps = 16; const segments2 = 16; const vertices: number[] = []; const normals: number[] = []; const indices: number[] = []; const phiStart = zone.startTheta; const phiRange = zone.endTheta - zone.startTheta; for (let i = 0; i <= phiSteps; i++) { const phi = phiStart + (i / phiSteps) * phiRange; for (let j = 0; j <= segments2; j++) { const t = j / segments2; const y = t * rise; // Equação da esfera da calota const yLocal = (rSphere - rise) + y; const r = Math.sqrt(Math.max(0, rSphere * rSphere - yLocal * yLocal)); const x = r * Math.cos(phi); const z = r * Math.sin(phi); vertices.push(x, wallHeight + y, z); // Normal analítica perfeita da esfera normals.push(x / rSphere, yLocal / rSphere, z / rSphere); } } for (let i = 0; i < phiSteps; i++) { for (let j = 0; j < segments2; j++) { const a = i * (segments2 + 1) + j; const b = (i + 1) * (segments2 + 1) + j; const c = (i + 1) * (segments2 + 1) + (j + 1); const d = i * (segments2 + 1) + (j + 1); indices.push(a, b, c, a, c, d); } } return ( ); })} {/* Seta indicativa de direção do vento */} Vento {/* Texto informativo */} Diâm = {diameter}m | Flecha = {rise}m ); } export default function Dome3DViewer(props: Dome3DInput) { const theme = useCanvasTheme(); const isDark = theme === 'dark'; const fallback = ( ); const maxDim = Math.max(props.diameter, props.wallHeight + props.rise); return ( ); }