377 lines
18 KiB
TypeScript
377 lines
18 KiB
TypeScript
|
||
|
||
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>
|
||
);
|
||
}
|