feat: adicionar super manual didatico com fluxos 2d e sheet panel

This commit is contained in:
2026-07-09 23:40:06 +00:00
parent 9fece3f174
commit 40e73e734a
34 changed files with 1443 additions and 390 deletions
+376
View File
@@ -0,0 +1,376 @@
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>
);
}