import { useMemo } from 'react'; import { OrbitControls, Grid, Environment, Text } from '@react-three/drei'; import * as THREE from 'three'; import SceneCanvas from '../SceneCanvas'; import FallbackDiagram from '../FallbackDiagram'; import { WindArrow } from './WindArrow'; import { useCanvasTheme } from '@/lib/theme'; export interface IsolatedRoof3DInput { /** Tipo de cobertura: 'shed' (uma água) ou 'gable' (duas águas) */ type: 'shed' | 'gable'; /** Inclinação θ (graus) */ theta: number; /** Altura livre dos suportes (m) */ height: number; /** Largura da cobertura (m) — dimensão transversal (b) */ width: number; /** Profundidade da cobertura (m) — dimensão longitudinal (l) */ depth: number; /** Cpe barlavento (sobre a face exposta ao vento) */ cpeWindward: number; /** Cpe sotavento (face oposta) */ cpeLeeward: number; /** Cpe sob a face superior (sucção) */ cpeTop: number; /** Força resultante na cobertura (kN) */ forceKN: number; } function pressureColor(cpe: number): THREE.Color { const clamped = Math.max(-2.5, Math.min(1.5, cpe)); const t = (clamped + 2.5) / 4.0; const h = 240 - t * 240; // azul -> vermelho return new THREE.Color(`hsl(${h}, 75%, 50%)`); } function IsolatedRoofModel({ type, theta, height, width, depth, cpeWindward, cpeLeeward, forceKN, }: IsolatedRoof3DInput) { const theme = useCanvasTheme(); const isDark = theme === 'dark'; const labelColor = isDark ? '#cbd5e1' : '#475569'; const textColor = isDark ? '#93c5fd' : '#1a202c'; const lineMaterialColor = isDark ? '#94a3b8' : '#64748b'; const pillarColor = isDark ? '#cbd5e1' : '#475569'; const thetaRad = (theta * Math.PI) / 180; const halfDepth = depth / 2; const halfWidth = width / 2; const windwardColor = useMemo(() => pressureColor(cpeWindward), [cpeWindward]); const leewardColor = useMemo(() => pressureColor(cpeLeeward), [cpeLeeward]); const h_diff = depth * Math.tan(thetaRad); const h_half = (depth / 2) * Math.tan(thetaRad); // Altura média da cobertura no centro geométrico const centerY = type === 'shed' ? height + h_diff / 2 : height + h_half / 2; // Definição das colunas de suporte (pilares) const pillars = useMemo(() => { const list: { pos: [number, number, number]; h: number }[] = []; if (type === 'shed') { list.push( { pos: [-halfWidth, height / 2, -halfDepth], h: height }, { pos: [halfWidth, height / 2, -halfDepth], h: height }, { pos: [-halfWidth, (height + h_diff) / 2, halfDepth], h: height + h_diff }, { pos: [halfWidth, (height + h_diff) / 2, halfDepth], h: height + h_diff }, ); } else { list.push( { pos: [-halfWidth, height / 2, -halfDepth], h: height }, { pos: [halfWidth, height / 2, -halfDepth], h: height }, { pos: [-halfWidth, height / 2, halfDepth], h: height }, { pos: [halfWidth, height / 2, halfDepth], h: height }, { pos: [-halfWidth, (height + h_half) / 2, 0], h: height + h_half }, { pos: [halfWidth, (height + h_half) / 2, 0], h: height + h_half }, ); } return list; }, [type, depth, width, height, h_diff, h_half, halfDepth, halfWidth]); return ( {/* Solo translúcido */} {/* === COBERTURA (PAINÉIS 3D SÓLIDOS) === */} {type === 'shed' ? ( // Uma água (Shed): dividida em metade barlavento e metade sotavento {/* Metade Barlavento (Z < 0) */} {/* Metade Sotavento (Z > 0) */} ) : ( // Duas águas (Gable) {/* Água Esquerda / Barlavento (Z < 0) */} {/* Água Direita / Sotavento (Z > 0) */} )} {/* Pilares de Suporte */} {pillars.map((p, i) => ( ))} {/* Vetor de vento (Seta verde/azul batendo no elemento) */} {/* === LINHAS DE COTA (CAD-Style) === */} {/* Cota de Altura (h) */} h = {height}m {/* Cota de Profundidade/Span (d) */} d = {depth}m {/* Rótulo Superior */} θ={theta}° | F = {forceKN.toFixed(1)} kN ); } export default function IsolatedRoof3DViewer({ type, theta, height, width, depth, cpeWindward, cpeLeeward, cpeTop, forceKN, }: IsolatedRoof3DInput) { const cameraDistance = Math.max(Math.max(width, depth) * 1.3, height * 1.5, 8); const fallback = ( ); return ( ); }