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 Cylinder3DInput {
diameter: number;
height: number;
/** Cpe profile ao longo da circunferência (0° a 180°) */
cpeProfile: { angle: number; cpe: number }[];
cpi: number;
}
function cylinderColor(cpe: number, cpi: number): THREE.Color {
const p = cpe - cpi;
const intensity = Math.min(1, Math.abs(p) / 1.2);
if (p > 0) {
return new THREE.Color(`hsl(${215 - intensity * 10}, ${70 + intensity * 25}%, ${Math.max(35, 65 - intensity * 25)}%)`);
}
return new THREE.Color(`hsl(0, ${70 + intensity * 25}%, ${Math.max(40, 65 - intensity * 20)}%)`);
}
function CylinderModel({ diameter, height, cpeProfile, cpi }: Cylinder3DInput) {
const theme = useCanvasTheme();
const isDark = theme === 'dark';
const segments = 64;
const radius = diameter / 2;
// Espelha o cpeProfile para cobrir de 0° a 360°
const fullCpeProfile = useMemo(() => {
if (cpeProfile.length === 0) return [];
const arr = [...cpeProfile];
const step = cpeProfile.length > 1 ? cpeProfile[1].angle - cpeProfile[0].angle : 10;
// Espelha de 180° a 360°
for (let angle = 180 + step; angle < 360; angle += step) {
const mirroredAngle = 360 - angle;
const closest = cpeProfile.find(p => Math.abs(p.angle - mirroredAngle) < 0.1) || cpeProfile[cpeProfile.length - 1];
arr.push({ angle, cpe: closest.cpe });
}
// Fecha o ciclo em 360° (igual a 0°)
arr.push({ angle: 360, cpe: cpeProfile[0].cpe });
return arr;
}, [cpeProfile]);
// Cria faces individuais com cor independente por ângulo
const faces = useMemo(() => {
const arr: { angle: number; cpe: number; color: THREE.Color }[] = [];
for (let i = 0; i < fullCpeProfile.length - 1; i++) {
const a = fullCpeProfile[i];
const b = fullCpeProfile[i + 1];
const angleMid = (a.angle + b.angle) / 2;
const cpeMid = (a.cpe + b.cpe) / 2;
arr.push({ angle: angleMid, cpe: cpeMid, color: cylinderColor(cpeMid, cpi) });
}
return arr;
}, [fullCpeProfile, cpi]);
return (
{/* Paredes Verticais do Cilindro */}
{faces.map((face, idx) => {
if (fullCpeProfile.length <= idx + 1) return null;
const stepAngle = fullCpeProfile[1].angle - fullCpeProfile[0].angle;
const a0 = (face.angle - stepAngle / 2) * Math.PI / 180;
const a1 = (face.angle + stepAngle / 2) * Math.PI / 180;
const x0 = Math.cos(a0) * radius;
const z0 = Math.sin(a0) * radius;
const x1 = Math.cos(a1) * radius;
const z1 = Math.sin(a1) * radius;
// Normais dos vértices
const nx0 = Math.cos(a0);
const nz0 = Math.sin(a0);
const nx1 = Math.cos(a1);
const nz1 = Math.sin(a1);
// Array com os 6 vértices para formar dois triângulos (um quad completo)
const vertices = new Float32Array([
x0, 0, z0,
x1, 0, z1,
x1, height, z1,
x0, 0, z0,
x1, height, z1,
x0, height, z0,
]);
const normals = new Float32Array([
nx0, 0, nz0,
nx1, 0, nz1,
nx1, 0, nz1,
nx0, 0, nz0,
nx1, 0, nz1,
nx0, 0, nz0,
]);
return (
);
})}
{/* Tampa superior sólida */}
{/* Anéis de detalhe (bordas do cilindro) */}
{/* Seta indicativa de direção do vento */}
Vento
{/* Texto de Informação */}
Alt = {height}m | Diâm = {diameter}m
);
}
export default function Cylinder3DViewer({ diameter, height, cpeProfile, cpi }: Cylinder3DInput) {
const theme = useCanvasTheme();
const isDark = theme === 'dark';
const fallback = (
);
const maxDim = Math.max(diameter, height);
return (
);
}