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 Vault3DInput { span: number; length: number; rise: number; cpi: number; cpeProfile: Record; cpeParallel?: Record; viewDirection?: 'perpendicular' | 'parallel'; } function vaultColor(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 VaultModel({ span, length, rise, cpi, cpeProfile, cpeParallel, viewDirection = 'perpendicular' }: Vault3DInput) { const theme = useCanvasTheme(); const isDark = theme === 'dark'; const segments = 64; const points = useMemo(() => { const pts: THREE.Vector3[] = []; for (let i = 0; i <= segments; i++) { const t = i / segments; const x = -span / 2 + t * span; const y = rise * Math.sin(t * Math.PI); pts.push(new THREE.Vector3(x, y, 0)); } return pts; }, [span, rise, segments]); const archShape = useMemo(() => { const s = new THREE.Shape(); s.moveTo(-span / 2, 0); for (let i = 0; i <= segments; i++) { const t = i / segments; const x = -span / 2 + t * span; const y = rise * Math.sin(t * Math.PI); s.lineTo(x, y); } s.lineTo(span / 2, 0); s.closePath(); return s; }, [span, rise, segments]); const renderPerpendicular = () => { const zones = []; const zoneSize = segments / 6; for (let z = 0; z < 6; z++) { const startIdx = Math.floor(z * zoneSize); const endIdx = Math.floor((z + 1) * zoneSize); const key = `zone${z + 1}`; zones.push({ cpe: cpeProfile[key] ?? -0.5, startIdx, endIdx, label: `${z + 1}` }); } return zones.map((zone, idx) => { const color = vaultColor(zone.cpe, cpi, isDark); const verts: number[] = []; for (let i = zone.startIdx; i <= zone.endIdx; i++) { verts.push(points[i].x, points[i].y, points[i].z); verts.push(points[i].x, points[i].y, length); } const indices: number[] = []; for (let i = 0; i < (zone.endIdx - zone.startIdx); i++) { const a = i * 2; const b = i * 2 + 1; const c = i * 2 + 2; const d = i * 2 + 3; indices.push(a, b, c, b, d, c); } const midIdx = Math.floor((zone.startIdx + zone.endIdx) / 2); const midPt = points[midIdx]; return ( {zone.label} ); }); }; const renderParallel = () => { const zones = ['A', 'B', 'C', 'D']; const zStep = length / 4; return zones.map((label, idx) => { const zStart = idx * zStep; const zEnd = (idx + 1) * zStep; const cpe = cpeParallel?.[label] ?? -0.5; const color = vaultColor(cpe, cpi, isDark); const verts: number[] = []; for (let i = 0; i <= segments; i++) { verts.push(points[i].x, points[i].y, zStart); verts.push(points[i].x, points[i].y, zEnd); } const indices: number[] = []; for (let i = 0; i < segments; i++) { const a = i * 2; const b = i * 2 + 1; const c = i * 2 + 2; const d = i * 2 + 3; indices.push(a, b, c, b, d, c); } return ( {label} ); }); }; return ( {viewDirection === 'perpendicular' ? renderPerpendicular() : renderParallel()} {/* Tímpano Traseiro (Z = 0) */} {/* Tímpano Frontal (Z = length) */} {/* Rótulo de dimensões */} Vão = {span}m | Compr = {length}m | Flecha = {rise}m ); } export default function Vault3DViewer({ span, length, rise, cpi, cpeProfile, cpeParallel, viewDirection = 'perpendicular' }: Vault3DInput) { const theme = useCanvasTheme(); const isDark = theme === 'dark'; const fallback = ( ); const maxDimension = Math.max(span, length, rise); return ( ); }