Files
BrainWind/app/src/components/WindFlowGrid2D.tsx
T

377 lines
18 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
interface WindFlowGrid2DProps {
type: string;
params: Record<string, any>;
}
export function WindFlowGrid2D({ type, params }: WindFlowGrid2DProps) {
// Estilo global de animação inserido no próprio SVG
const styleBlock = (
<style>{`
@keyframes windFlow {
0% { stroke-dashoffset: 36; }
100% { stroke-dashoffset: 0; }
}
@keyframes vortexRotate {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
@keyframes vibration {
0%, 100% { transform: translateY(0px); }
50% { transform: translateY(-8px); }
}
.wind-line {
stroke: #60a5fa;
stroke-width: 1.8;
stroke-dasharray: 8 16;
animation: windFlow 1.8s linear infinite;
fill: none;
opacity: 0.85;
}
.wind-line-fast {
stroke: #38bdf8;
stroke-width: 2.2;
stroke-dasharray: 6 12;
animation: windFlow 0.9s linear infinite;
fill: none;
}
.wind-line-slow {
stroke: #93c5fd;
stroke-width: 1.5;
stroke-dasharray: 10 20;
animation: windFlow 3s linear infinite;
fill: none;
opacity: 0.5;
}
.vortex-line {
stroke: #f87171;
stroke-width: 1.5;
stroke-dasharray: 4 8;
animation: windFlow 2.5s linear infinite;
fill: none;
}
.vortex-spin-cw {
transform-origin: center;
animation: vortexRotate 3s linear infinite;
}
.vortex-spin-ccw {
transform-origin: center;
animation: vortexRotate 3s linear infinite reverse;
}
.vibe-cyl {
animation: vibration 1.5s ease-in-out infinite;
}
`}</style>
);
// Renderizadores específicos de fluxo ativo com base nos sliders da página
const renderActiveSimulation = () => {
switch (type) {
case 'bridge': {
const alpha = Number(params.alpha || 0);
// O vento muda de ângulo
const rotationAngle = alpha * 3; // amplifica o ângulo para ficar visível
return (
<div className="flex flex-col gap-2">
<h4 className="text-xs font-semibold text-muted-foreground uppercase tracking-wider">Simulação Ativa (com suas variáveis)</h4>
<div className="relative aspect-video w-full bg-slate-950 border border-border rounded-lg overflow-hidden">
<svg viewBox="0 0 400 200" className="w-full h-full">
{styleBlock}
{/* Linhas de vento de fundo */}
<g transform={`rotate(${rotationAngle}, 200, 100)`}>
<path d="M -50,30 L 450,30" className="wind-line-slow" />
<path d="M -50,70 Q 150,50 450,70" className="wind-line" />
<path d="M -50,130 Q 150,150 450,130" className="wind-line" />
<path d="M -50,170 L 450,170" className="wind-line-slow" />
{/* Linhas aceleradas acima e abaixo */}
<path d="M -50,90 Q 200,60 450,90" className="wind-line-fast" />
<path d="M -50,110 Q 200,140 450,110" className="wind-line-fast" />
</g>
{/* Tabuleiro da ponte */}
<g transform="translate(150, 85)">
<rect x="0" y="0" width="100" height="30" rx="4" fill="#1e293b" stroke="#334155" strokeWidth="2" />
{/* Barreiras de proteção */}
<rect x="5" y="-6" width="6" height="6" fill="#64748b" />
<rect x="89" y="-6" width="6" height="6" fill="#64748b" />
</g>
{/* Vetores de Força baseados no ângulo */}
{alpha !== 0 && (
<g>
{/* Vetor de sustentação (Fz) */}
<path
d={alpha > 0 ? "M 200,75 L 200,30" : "M 200,125 L 200,170"}
stroke={alpha > 0 ? "#10b981" : "#ef4444"}
strokeWidth="3"
fill="none"
markerEnd="url(#arrow)"
/>
<text x="210" y={alpha > 0 ? 45 : 160} fill={alpha > 0 ? "#10b981" : "#ef4444"} className="text-xs font-bold font-mono">
{alpha > 0 ? 'Sustentação (+Fz)' : 'Downforce (-Fz)'}
</text>
</g>
)}
{/* Marcadores SVG de setas */}
<defs>
<marker id="arrow" viewBox="0 0 10 10" refX="5" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse">
<path d="M 0 0 L 10 5 L 0 10 z" fill="context-stroke" />
</marker>
</defs>
</svg>
<div className="absolute bottom-2 right-2 bg-slate-900/80 backdrop-blur px-2 py-1 rounded text-[10px] text-sky-400 font-mono">
Ângulo: {alpha}º
</div>
</div>
</div>
);
}
case 'tower': {
const section = params.section || 'square';
const alphaWind = Number(params.alphaWind || 0);
return (
<div className="flex flex-col gap-2">
<h4 className="text-xs font-semibold text-muted-foreground uppercase tracking-wider">Simulação Ativa (com suas variáveis)</h4>
<div className="relative aspect-video w-full bg-slate-950 border border-border rounded-lg overflow-hidden">
<svg viewBox="0 0 400 200" className="w-full h-full">
{styleBlock}
{/* Linhas de vento fluindo */}
<g>
<path d="M -20,20 L 420,20" className="wind-line-slow" />
<path d="M -20,60 C 150,60 170,40 230,40 C 270,40 320,60 420,60" className="wind-line" />
<path d="M -20,140 C 150,140 170,160 230,160 C 270,160 320,140 420,140" className="wind-line" />
<path d="M -20,180 L 420,180" className="wind-line-slow" />
{/* Turbulência traseira (esteira) */}
<path d="M 230,90 Q 300,75 420,90" className="vortex-line" />
<path d="M 230,110 Q 300,125 420,110" className="vortex-line" />
</g>
{/* Perfil da Seção da Torre (Centro) */}
<g transform={`translate(200, 100) rotate(${alphaWind})`}>
{section === 'square' ? (
<rect x="-25" y="-25" width="50" height="50" fill="none" stroke="#64748b" strokeWidth="4" />
) : (
<polygon points="0,-28 25,15 -25,15" fill="none" stroke="#64748b" strokeWidth="4" />
)}
{/* Barras internas treliçadas */}
{section === 'square' ? (
<>
<line x1="-25" y1="-25" x2="25" y2="25" stroke="#475569" strokeWidth="1.5" />
<line x1="25" y1="-25" x2="-25" y2="25" stroke="#475569" strokeWidth="1.5" />
</>
) : (
<>
<line x1="0" y1="-28" x2="0" y2="15" stroke="#475569" strokeWidth="1.5" />
<line x1="-25" y1="15" x2="12.5" y2="-6.5" stroke="#475569" strokeWidth="1.5" />
<line x1="25" y1="15" x2="-12.5" y2="-6.5" stroke="#475569" strokeWidth="1.5" />
</>
)}
</g>
</svg>
<div className="absolute bottom-2 right-2 bg-slate-900/80 backdrop-blur px-2 py-1 rounded text-[10px] text-sky-400 font-mono">
{section === 'square' ? 'Quadrada' : 'Triangular'} | {alphaWind}º
</div>
</div>
</div>
);
}
case 'cylinder': {
const isSmooth = params.roughness === 'smooth' || params.d_over_k > 1e5;
return (
<div className="flex flex-col gap-2">
<h4 className="text-xs font-semibold text-muted-foreground uppercase tracking-wider">Simulação Ativa (com suas variáveis)</h4>
<div className="relative aspect-video w-full bg-slate-950 border border-border rounded-lg overflow-hidden">
<svg viewBox="0 0 400 200" className="w-full h-full">
{styleBlock}
{/* Fluxo contornando cilindro */}
<g>
<path d="M -20,20 L 420,20" className="wind-line-slow" />
<path d="M -20,60 C 150,55 160,35 200,35 C 240,35 250,55 420,55" className="wind-line-fast" />
<path d="M -20,140 C 150,145 160,165 200,165 C 240,165 250,145 420,145" className="wind-line-fast" />
<path d="M -20,180 L 420,180" className="wind-line-slow" />
</g>
{/* Esteira de Vórtices de Von Kármán */}
<g transform="translate(250, 100)">
<circle cx="20" cy="-25" r="10" fill="none" stroke="#ef4444" strokeWidth="1.5" strokeDasharray="3 6" className="vortex-spin-cw" />
<circle cx="60" cy="25" r="12" fill="none" stroke="#ef4444" strokeWidth="1.5" strokeDasharray="3 6" className="vortex-spin-ccw" />
<circle cx="100" cy="-20" r="14" fill="none" stroke="#ef4444" strokeWidth="1.5" strokeDasharray="3 6" className="vortex-spin-cw" />
<path d="M 0,0 Q 40,-35 80,0 Q 120,35 160,0" className="vortex-line" />
</g>
{/* Cilindro */}
<circle cx="200" cy="100" r="35" fill="#334155" stroke="#475569" strokeWidth={isSmooth ? "2" : "5"} />
</svg>
<div className="absolute bottom-2 right-2 bg-slate-900/80 backdrop-blur px-2 py-1 rounded text-[10px] text-sky-400 font-mono">
Superfície: {isSmooth ? 'Lisa (Menor Arrasto)' : 'Rugosa (Maior Arrasto)'}
</div>
</div>
</div>
);
}
case 'dynamics': {
const fn = Number(params.fn || 0.5);
// Frequência regula a velocidade da vibração visual
const vibeDuration = Math.max(0.5, Math.min(2.5, 1 / fn));
return (
<div className="flex flex-col gap-2">
<h4 className="text-xs font-semibold text-muted-foreground uppercase tracking-wider">Simulação Ativa (com suas variáveis)</h4>
<div className="relative aspect-video w-full bg-slate-950 border border-border rounded-lg overflow-hidden">
<svg viewBox="0 0 400 200" className="w-full h-full">
{styleBlock}
{/* Linhas de vento */}
<g>
<path d="M -20,20 L 420,20" className="wind-line-slow" />
<path d="M -20,60 C 150,55 160,35 200,35 C 240,35 250,55 420,55" className="wind-line" />
<path d="M -20,140 C 150,145 160,165 200,165 C 240,165 250,145 420,145" className="wind-line" />
<path d="M -20,180 L 420,180" className="wind-line-slow" />
</g>
{/* Vórtices alternados desalinhados de Von Kármán */}
<g transform="translate(250, 100)">
<path d="M 0,0 Q 40,-25 80,10 Q 120,-15 160,5" className="vortex-line" />
</g>
{/* Cilindro que VIBRA verticalmente na ressonância */}
<g className="vibe-cyl" style={{ animationDuration: `${vibeDuration}s` }}>
<circle cx="200" cy="100" r="30" fill="#ef4444" stroke="#f87171" strokeWidth="2" opacity="0.9" />
<line x1="200" y1="100" x2="200" y2="40" stroke="#f87171" strokeWidth="2" strokeDasharray="2 4" />
</g>
</svg>
<div className="absolute bottom-2 right-2 bg-slate-900/80 backdrop-blur px-2 py-1 rounded text-[10px] text-sky-400 font-mono">
fn: {fn} Hz | Oscilação Ativa
</div>
</div>
</div>
);
}
default:
// Padrão genérico de fluxo para os demais módulos
return (
<div className="flex flex-col gap-2">
<h4 className="text-xs font-semibold text-muted-foreground uppercase tracking-wider">Visualização do Escoamento</h4>
<div className="relative aspect-video w-full bg-slate-950 border border-border rounded-lg overflow-hidden">
<svg viewBox="0 0 400 200" className="w-full h-full">
{styleBlock}
<path d="M -20,30 L 420,30" className="wind-line-slow" />
<path d="M -20,70 Q 200,40 420,70" className="wind-line" />
<path d="M -20,130 Q 200,160 420,130" className="wind-line" />
<path d="M -20,170 L 420,170" className="wind-line-slow" />
{/* Elemento genérico no centro */}
<rect x="180" y="80" width="40" height="40" rx="3" fill="#334155" stroke="#475569" strokeWidth="2" />
</svg>
</div>
</div>
);
}
};
// Comparativos estáticos baseados em grids como sugerido pelo usuário
const renderComparisonGrid = () => {
if (type === 'bridge') {
return (
<div className="grid grid-cols-2 gap-4 mt-4">
<div className="flex flex-col gap-1">
<span className="text-[10px] font-bold text-muted-foreground uppercase">Incidência Descendente (α = -5º)</span>
<div className="aspect-video w-full bg-slate-950 border border-border rounded-lg overflow-hidden">
<svg viewBox="0 0 400 200" className="w-full h-full opacity-90">
{styleBlock}
<g transform="rotate(-15, 200, 100)">
<path d="M -50,50 L 450,50" className="wind-line-slow" />
<path d="M -50,100 L 450,100" className="wind-line" />
<path d="M -50,150 L 450,150" className="wind-line-slow" />
</g>
<rect x="150" y="85" width="100" height="30" rx="4" fill="#334155" />
{/* Seta para baixo */}
<path d="M 200,125 L 200,170" stroke="#ef4444" strokeWidth="3" markerEnd="url(#arrow)" />
</svg>
</div>
<p className="text-[10px] text-muted-foreground">Vento empurra o tabuleiro para baixo, aumentando os esforços de compressão nos pilares.</p>
</div>
<div className="flex flex-col gap-1">
<span className="text-[10px] font-bold text-muted-foreground uppercase">Incidência Ascendente (α = +5º)</span>
<div className="aspect-video w-full bg-slate-950 border border-border rounded-lg overflow-hidden">
<svg viewBox="0 0 400 200" className="w-full h-full opacity-90">
{styleBlock}
<g transform="rotate(15, 200, 100)">
<path d="M -50,50 L 450,50" className="wind-line-slow" />
<path d="M -50,100 L 450,100" className="wind-line" />
<path d="M -50,150 L 450,150" className="wind-line-slow" />
</g>
<rect x="150" y="85" width="100" height="30" rx="4" fill="#334155" />
{/* Seta para cima */}
<path d="M 200,75 L 200,30" stroke="#10b981" strokeWidth="3" markerEnd="url(#arrow)" />
</svg>
</div>
<p className="text-[10px] text-muted-foreground">Efeito aerofólio levanta a ponte. Crítico para pontes estaiadas ou suspensas.</p>
</div>
</div>
);
}
if (type === 'tower') {
return (
<div className="grid grid-cols-2 gap-4 mt-4">
<div className="flex flex-col gap-1">
<span className="text-[10px] font-bold text-muted-foreground uppercase">Vento Normal (0º)</span>
<div className="aspect-video w-full bg-slate-950 border border-border rounded-lg overflow-hidden">
<svg viewBox="0 0 400 200" className="w-full h-full">
{styleBlock}
<path d="M -20,60 C 150,60 170,45 230,45 Q 320,60 420,60" className="wind-line" />
<path d="M -20,140 C 150,140 170,155 230,155 Q 320,140 420,140" className="wind-line" />
<g transform="translate(180, 80)">
<rect x="0" y="0" width="40" height="40" fill="none" stroke="#64748b" strokeWidth="3" />
<line x1="0" y1="0" x2="40" y2="40" stroke="#475569" />
<line x1="40" y1="0" x2="0" y2="40" stroke="#475569" />
</g>
</svg>
</div>
<p className="text-[10px] text-muted-foreground">Vento incidindo perpendicularmente à face frontal. Menor área exposta efetiva.</p>
</div>
<div className="flex flex-col gap-1">
<span className="text-[10px] font-bold text-muted-foreground uppercase">Vento na Diagonal (45º)</span>
<div className="aspect-video w-full bg-slate-950 border border-border rounded-lg overflow-hidden">
<svg viewBox="0 0 400 200" className="w-full h-full">
{styleBlock}
<path d="M -20,40 C 150,40 170,30 230,30 Q 320,40 420,40" className="wind-line" />
<path d="M -20,160 C 150,160 170,170 230,170 Q 320,160 420,160" className="wind-line" />
<g transform="translate(200, 100) rotate(45)">
<rect x="-20" y="-20" width="40" height="40" fill="none" stroke="#64748b" strokeWidth="3" />
<line x1="-20" y1="-20" x2="20" y2="20" stroke="#475569" />
</g>
</svg>
</div>
<p className="text-[10px] text-muted-foreground">Aumenta a esteira e o coeficiente de arrasto Ca nas torres de seção quadrada.</p>
</div>
</div>
);
}
// Grid padrão se não houver um caso comparativo customizado
return null;
};
return (
<div className="flex flex-col gap-4 my-6 bg-slate-900/40 p-4 border rounded-xl">
<div className="flex justify-between items-center">
<h3 className="text-sm font-semibold">Túnel de Vento Virtual (Escoamento 2D)</h3>
<span className="text-[10px] bg-sky-500/10 text-sky-400 font-bold border border-sky-500/20 px-2 py-0.5 rounded-full">SVG Interativo</span>
</div>
{renderActiveSimulation()}
{renderComparisonGrid()}
</div>
);
}