import { useMemo } from 'react'; import { OrbitControls, Grid, Environment, Text } from '@react-three/drei'; import * as THREE from 'three'; import { useGalpaoStore } from '../store/galpaoStore'; import { useWindStore } from '../store/appStore'; import SceneCanvas from './SceneCanvas'; import FallbackDiagram from './FallbackDiagram'; function pressureColor(cpe: number, cpi: number): THREE.Color { const p = cpe - cpi; const intensity = Math.min(1, Math.abs(p) / 1.2); if (p > 0) { const h = 215 - intensity * 10; const s = 70 + intensity * 25; const l = Math.max(35, 65 - intensity * 25); return new THREE.Color(`hsl(${h}, ${s}%, ${l}%)`); } const h = 0; const s = 70 + intensity * 25; const l = Math.max(40, 65 - intensity * 20); return new THREE.Color(`hsl(${h}, ${s}%, ${l}%)`); } function PressureArrow({ center, normal, p, }: { center: [number, number, number]; normal: [number, number, number]; p: number; }) { const { q } = useWindStore(); const force = p * q; // kN/m2 const normVec = useMemo(() => new THREE.Vector3(...normal).normalize(), [normal]); const centerVec = useMemo(() => new THREE.Vector3(...center), [center]); const isPressure = p > 0; const dir = useMemo(() => isPressure ? normVec.clone().negate() : normVec.clone(), [isPressure, normVec]); const quat = useMemo(() => { const q = new THREE.Quaternion(); q.setFromUnitVectors(new THREE.Vector3(0, 1, 0), dir); return new THREE.Euler().setFromQuaternion(q); }, [dir]); if (Math.abs(force) < 0.05) return null; const length = Math.max(0.6, Math.min(3.0, Math.abs(force) * 1.5)); const color = isPressure ? '#3b82f6' : '#ef4444'; const start = isPressure ? centerVec.clone().sub(dir.clone().multiplyScalar(length)) : centerVec; const end = isPressure ? centerVec : centerVec.clone().add(dir.clone().multiplyScalar(length)); const mid = new THREE.Vector3().addVectors(start, end).multiplyScalar(0.5); const headLen = Math.min(0.4, length * 0.4); return ( {length - headLen > 0 && ( )} ); } export function WarehouseModel() { const { width, length, height, roofPitch, wallCpe, roofCpe } = useGalpaoStore(); const { windAngle, cpi } = useWindStore(); const roofHeight = (width / 2) * Math.tan((roofPitch * Math.PI) / 180); const theta = (roofPitch * Math.PI) / 180; const widthSlope = width / 2 / Math.cos(theta); const wallAColor = useMemo(() => pressureColor(wallCpe.A, cpi), [wallCpe.A, cpi]); const wallBColor = useMemo(() => pressureColor(wallCpe.B, cpi), [wallCpe.B, cpi]); const wallCColor = useMemo(() => pressureColor(wallCpe.C, cpi), [wallCpe.C, cpi]); const wallDColor = useMemo(() => pressureColor(wallCpe.D, cpi), [wallCpe.D, cpi]); const roofEColor = useMemo(() => pressureColor(roofCpe.E, cpi), [roofCpe.E, cpi]); const roofFColor = useMemo(() => pressureColor(roofCpe.F, cpi), [roofCpe.F, cpi]); const roofGColor = useMemo(() => pressureColor(roofCpe.G, cpi), [roofCpe.G, cpi]); const roofHColor = useMemo(() => pressureColor(roofCpe.H, cpi), [roofCpe.H, cpi]); const wallThickness = 0.15; const isParallel = windAngle === 90; // Shapes para os oitões (gables) const leftShape = useMemo(() => { const s = new THREE.Shape(); s.moveTo(-width / 2, 0); s.lineTo(0, roofHeight); s.lineTo(0, 0); s.closePath(); return s; }, [width, roofHeight]); const rightShape = useMemo(() => { const s = new THREE.Shape(); s.moveTo(0, 0); s.lineTo(0, roofHeight); s.lineTo(width / 2, 0); s.closePath(); return s; }, [width, roofHeight]); return ( {/* === PAREDES === */} {/* Lateral Esquerda (X = -width/2) */} {/* Lateral Direita (X = width/2) */} {/* Parede Traseira (Z = -length/2) */} {/* Parede Frontal (Z = length/2) */} {/* === OITÕES (GABLES) === */} {/* Oitão Frontal (Z = length/2) */} {/* Oitão Traseiro (Z = -length/2) */} {/* === TELHADO (DUAS ÁGUAS) === */} {/* Água Esquerda (X < 0) */} {/* Seg 1 (Traseiro-Fim) */} {/* Seg 2 (Traseiro-Meio) */} {/* Seg 3 (Frontal-Meio) */} {/* Seg 4 (Frontal-Fim) */} {isParallel ? 'Telhado (Zonas E/F/G/H)' : 'Telhado E/F (Barlavento)'} {/* Água Direita (X > 0) */} {/* Seg 1 (Traseiro-Fim) */} {/* Seg 2 (Traseiro-Meio) */} {/* Seg 3 (Frontal-Meio) */} {/* Seg 4 (Frontal-Fim) */} {isParallel ? 'Telhado (Zonas E/F/G/H)' : 'Telhado G/H (Sotavento)'} {/* Cumeeira */} {/* Bordas Laterais do Telhado (Beirais) */} {/* === RÓTULOS 3D === */} {/* Rótulo Parede Frontal */} {isParallel ? 'Parede B (Sotavento)' : 'Parede C (Barlavento)'} {/* Rótulo Parede Traseira */} {isParallel ? 'Parede A (Barlavento)' : 'Parede D (Sotavento)'} {/* Rótulo Parede Esquerda */} {isParallel ? 'Parede C (Lateral)' : 'Parede A (Lateral)'} {/* Rótulo Parede Direita */} {isParallel ? 'Parede D (Lateral)' : 'Parede B (Lateral)'} ); } export default function Warehouse3DViewer() { const { width, length, height, roofPitch, wallCpe, roofCpe } = useGalpaoStore(); const { windAngle, cpi } = useWindStore(); const fallback = ( ); const maxDimension = Math.max(width, length, height); return ( ); }