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
+31 -71
View File
@@ -25,11 +25,6 @@ export interface Bar3DInput {
cx: number;
}
/**
* Converte kN para um comprimento visual proporcional no eixo 3D.
*/
const forceToLength = (kN: number): number => Math.min(Math.max(Math.abs(kN) * 0.3, 0.3), 4);
function BarModel({
barType,
section,
@@ -37,94 +32,60 @@ function BarModel({
width,
length,
alpha,
fxKN,
fyKN,
cx,
}: Bar3DInput) {
const barRadius = barType === 'circular' ? (diameter ?? 0.05) / 2 : Math.min(width ?? 0.1, 0.08) / 2;
const barThickness = barType === 'circular' ? barRadius : barRadius * 0.5;
// Cor baseada em Cx
const barColor = useMemo(() => {
const intensity = Math.min(1, Math.abs(cx) / 2.5);
const hue = 215 - intensity * 215;
return new THREE.Color(`hsl(${hue}, ${65 + intensity * 25}%, ${45 - intensity * 10}%)`);
}, [cx]);
// Rotação da barra em torno do eixo Y (alinhada com eixo X inicialmente)
// Direção do vento é +X; α é o ângulo da face da barra em relação ao vento
// Ação do vento deve rotacionar a barra em seu próprio eixo longitudinal (roll).
// O eixo longitudinal na nossa geometria é o X.
const alphaRad = (alpha * Math.PI) / 180;
const barRotation = -alphaRad; // rotação em torno do eixo Y para alinhar a face
// Direção do vetor de força resultante (na direção da força calculada)
const forceMag = Math.sqrt(fxKN * fxKN + fyKN * fyKN);
const forceAngle = Math.atan2(fyKN, fxKN);
const arrowLen = forceToLength(forceMag);
// Centro da barra (origem)
const center = new THREE.Vector3(0, 0, 0);
// Posição da ponta da seta
const arrowEnd = useMemo(
() => new THREE.Vector3(
Math.cos(forceAngle) * arrowLen,
Math.sin(forceAngle) * arrowLen,
0,
),
[forceAngle, arrowLen],
);
const arrowMid = useMemo(
() => new THREE.Vector3(arrowEnd.x / 2, arrowEnd.y / 2, 0),
[arrowEnd],
);
const quat = useMemo(() => {
const dir = arrowEnd.clone().normalize();
const q = new THREE.Quaternion();
q.setFromUnitVectors(new THREE.Vector3(0, 1, 0), dir);
return new THREE.Euler().setFromQuaternion(q);
}, [arrowEnd]);
const headLen = 0.25;
// A rotação deve ser negativa para que o vento (vindo de -Z) atinja as faces corretas conforme a NBR.
const barRotationX = -alphaRad;
return (
<group rotation={[0, barRotation, 0]}>
{/* Eixo principal da barra ao longo do eixo X */}
{barType === 'circular' ? (
<mesh position={[0, 0, 0]} rotation={[0, 0, Math.PI / 2]} castShadow>
<cylinderGeometry args={[barRadius, barRadius, length, 16]} />
<meshStandardMaterial color={barColor} roughness={0.4} metalness={0.3} />
<group>
{/* Seta do Vento (Fixa Globalmente, soprando em +Z) */}
<group position={[0, 0, barRadius + 0.5]}>
<mesh position={[0, 0, 0.4]} rotation={[Math.PI / 2, 0, 0]}>
<cylinderGeometry args={[0.02, 0.02, 0.8, 8]} />
<meshStandardMaterial color="#22c55e" />
</mesh>
) : (
<SectionShape section={section ?? 'placa'} width={width ?? 0.1} length={length} color={barColor} thickness={barThickness} />
)}
{/* Eixos de referência */}
<axesHelper args={[length * 0.5]} />
{/* Vetor de força (resultante) */}
{forceMag > 0.01 && (
<group>
{arrowLen - headLen > 0.01 && (
<mesh position={arrowMid.toArray()} rotation={[quat.x, quat.y, quat.z]} castShadow>
<cylinderGeometry args={[0.04, 0.04, arrowLen - headLen, 10]} />
<meshStandardMaterial color="#ef4444" />
</mesh>
)}
<mesh position={arrowEnd.toArray()} rotation={[quat.x, quat.y, quat.z]} castShadow>
<coneGeometry args={[0.1, headLen, 10]} />
<meshStandardMaterial color="#ef4444" />
<mesh position={[0, 0, 0]} rotation={[-Math.PI / 2, 0, 0]}>
<coneGeometry args={[0.08, 0.2, 8]} />
<meshStandardMaterial color="#22c55e" />
</mesh>
</group>
{/* Barra Rotacionada (Roll) */}
<group rotation={[barRotationX, 0, 0]}>
{/* Eixo principal da barra ao longo do eixo X */}
{barType === 'circular' ? (
<mesh position={[0, 0, 0]} rotation={[0, 0, Math.PI / 2]} castShadow>
<cylinderGeometry args={[barRadius, barRadius, length, 16]} />
<meshStandardMaterial color={barColor} roughness={0.4} metalness={0.3} />
</mesh>
</group>
)}
) : (
<SectionShape section={section ?? 'placa'} width={width ?? 0.1} length={length} color={barColor} thickness={barThickness} />
)}
{/* Eixos de referência locais da barra */}
<axesHelper args={[length * 0.5]} />
</group>
{/* Marca de origem */}
<mesh position={[0, 0, 0]} castShadow>
<sphereGeometry args={[0.06, 12, 12]} />
<meshStandardMaterial color="#fbbf24" emissive="#fbbf24" emissiveIntensity={0.4} />
</mesh>
<axesHelper args={[length * 0.3]} />
<Text
position={[0, -barRadius * 2 - 0.3, 0]}
position={[0, -barRadius * 2 - 0.4, 0]}
fontSize={0.3}
color="#1e40af"
anchorX="center"
@@ -132,7 +93,6 @@ function BarModel({
>
α={alpha}° | Cx={cx.toFixed(2)}
</Text>
{center && null}
</group>
);
}