#pragma once #include "fesa/Math/Math.hpp" #include "fesa/Util/Util.hpp" #include #include #include #include #include namespace fesa { struct ShapeData { std::array n{}; std::array dr{}; std::array ds{}; }; inline ShapeData shapeFunctions(Real r, Real s) { return { { 0.25 * (1.0 - r) * (1.0 - s), 0.25 * (1.0 + r) * (1.0 - s), 0.25 * (1.0 + r) * (1.0 + s), 0.25 * (1.0 - r) * (1.0 + s), }, { -0.25 * (1.0 - s), 0.25 * (1.0 - s), 0.25 * (1.0 + s), -0.25 * (1.0 + s), }, { -0.25 * (1.0 - r), -0.25 * (1.0 + r), 0.25 * (1.0 + r), 0.25 * (1.0 - r), } }; } struct LocalBasis { Vec3 e1; Vec3 e2; Vec3 e3; }; struct MITC4NaturalPoint { Real xi = 0.0; Real eta = 0.0; }; struct MITC4TyingPoint { std::string label; MITC4NaturalPoint natural; std::array edge_node_indices{}; }; inline std::array mitc4NodeNaturalCoordinates() { return {{{-1.0, -1.0}, {1.0, -1.0}, {1.0, 1.0}, {-1.0, 1.0}}}; } inline std::array mitc4TyingPoints() { return {{{"A", {0.0, -1.0}, {0, 1}}, {"B", {-1.0, 0.0}, {0, 3}}, {"C", {0.0, 1.0}, {3, 2}}, {"D", {1.0, 0.0}, {1, 2}}}}; } struct MITC4DirectorFrame { Vec3 v1; Vec3 v2; Vec3 vn; }; struct MITC4MidsurfaceDerivatives { ShapeData shape; Vec3 g1; Vec3 g2; }; struct MITC4Geometry { std::array coordinates{}; Real thickness = 0.0; ShapeData center_shape; Vec3 g1_center; Vec3 g2_center; Vec3 center_normal; std::array nodal_frames{}; std::vector diagnostics; bool ok() const { return !hasError(diagnostics); } }; struct MITC4IntegrationBasis { ShapeData shape; Vec3 g1; Vec3 g2; Vec3 g3; Real jacobian = 0.0; LocalBasis local; std::vector diagnostics; bool ok() const { return !hasError(diagnostics); } }; inline Vec3 globalEX() { return {1.0, 0.0, 0.0}; } inline Vec3 globalEY() { return {0.0, 1.0, 0.0}; } inline Vec3 globalEZ() { return {0.0, 0.0, 1.0}; } inline Diagnostic mitc4Diagnostic(std::string code, std::string message) { return makeDiagnostic(Severity::Error, std::move(code), std::move(message), "mitc4", "", 0); } inline void appendDiagnostics(std::vector& target, const std::vector& source) { target.insert(target.end(), source.begin(), source.end()); } inline MITC4MidsurfaceDerivatives mitc4MidsurfaceDerivatives(const std::array& coordinates, Real xi, Real eta) { MITC4MidsurfaceDerivatives result; result.shape = shapeFunctions(xi, eta); for (std::size_t i = 0; i < 4; ++i) { result.g1 = result.g1 + result.shape.dr[i] * coordinates[i]; result.g2 = result.g2 + result.shape.ds[i] * coordinates[i]; } return result; } inline std::optional firstNormalizedCross(const std::array& axes, const Vec3& vector, Real tolerance) { for (const Vec3& axis : axes) { auto candidate = normalizedIfValid(cross(axis, vector), tolerance); if (candidate) { return candidate; } } return std::nullopt; } inline std::optional buildMITC4DirectorFrame(const Vec3& normal, Real tolerance) { auto v1 = normalizedIfValid(cross(globalEY(), normal), tolerance); if (!v1) { v1 = firstNormalizedCross({globalEZ(), globalEX(), globalEY()}, normal, tolerance); } if (!v1) { return std::nullopt; } auto v2 = normalizedIfValid(cross(normal, *v1), tolerance); if (!v2) { return std::nullopt; } return MITC4DirectorFrame{*v1, *v2, normal}; } inline MITC4Geometry buildMITC4Geometry(const std::array& coordinates, Real thickness, Real tolerance = 1.0e-12) { MITC4Geometry geometry; geometry.coordinates = coordinates; geometry.thickness = thickness; if (!isFinite(thickness) || thickness <= tolerance) { geometry.diagnostics.push_back( mitc4Diagnostic("FESA-MITC4-THICKNESS", "MITC4 shell thickness must be positive and finite")); } for (const Vec3& coordinate : coordinates) { if (!isFinite(coordinate)) { geometry.diagnostics.push_back( mitc4Diagnostic("FESA-MITC4-COORDINATE", "MITC4 element coordinates must be finite")); break; } } const auto center = mitc4MidsurfaceDerivatives(coordinates, 0.0, 0.0); geometry.center_shape = center.shape; geometry.g1_center = center.g1; geometry.g2_center = center.g2; const auto normal = normalizedIfValid(cross(center.g1, center.g2), tolerance); if (!normal) { geometry.diagnostics.push_back( mitc4Diagnostic("FESA-MITC4-SINGULAR-NORMAL", "MITC4 element center normal is near zero")); return geometry; } geometry.center_normal = *normal; const auto frame = buildMITC4DirectorFrame(*normal, tolerance); if (!frame) { geometry.diagnostics.push_back( mitc4Diagnostic("FESA-MITC4-SINGULAR-BASIS", "MITC4 nodal director basis could not be constructed")); return geometry; } geometry.nodal_frames.fill(*frame); return geometry; } inline MITC4IntegrationBasis computeMITC4IntegrationBasis(const MITC4Geometry& geometry, Real xi, Real eta, Real zeta, Real tolerance = 1.0e-12) { MITC4IntegrationBasis result; result.diagnostics = geometry.diagnostics; result.shape = shapeFunctions(xi, eta); for (std::size_t i = 0; i < 4; ++i) { const Vec3& coordinate = geometry.coordinates[i]; const Vec3& normal = geometry.nodal_frames[i].vn; result.g1 = result.g1 + result.shape.dr[i] * coordinate + (0.5 * zeta * geometry.thickness * result.shape.dr[i]) * normal; result.g2 = result.g2 + result.shape.ds[i] * coordinate + (0.5 * zeta * geometry.thickness * result.shape.ds[i]) * normal; result.g3 = result.g3 + (0.5 * geometry.thickness * result.shape.n[i]) * normal; } result.jacobian = dot(cross(result.g1, result.g2), result.g3); if (!isFinite(result.jacobian) || std::fabs(result.jacobian) <= tolerance) { result.diagnostics.push_back( mitc4Diagnostic("FESA-MITC4-SINGULAR-JACOBIAN", "MITC4 element Jacobian is near zero")); } const auto e3 = normalizedIfValid(result.g3, tolerance); if (!e3) { result.diagnostics.push_back( mitc4Diagnostic("FESA-MITC4-SINGULAR-BASIS", "MITC4 integration basis normal is near zero")); return result; } auto e1 = normalizedIfValid(cross(result.g2, *e3), tolerance); if (!e1) { e1 = firstNormalizedCross({globalEY(), globalEZ(), globalEX()}, *e3, tolerance); } if (!e1) { result.diagnostics.push_back( mitc4Diagnostic("FESA-MITC4-SINGULAR-BASIS", "MITC4 integration basis tangent could not be constructed")); return result; } const auto e2 = normalizedIfValid(cross(*e3, *e1), tolerance); if (!e2) { result.diagnostics.push_back( mitc4Diagnostic("FESA-MITC4-SINGULAR-BASIS", "MITC4 integration basis is not right-handed")); return result; } result.local = {*e1, *e2, *e3}; return result; } inline LocalBasis computeLocalBasis(const std::array& coordinates) { const MITC4Geometry geometry = buildMITC4Geometry(coordinates, 1.0); if (!geometry.ok()) { throw std::runtime_error("invalid MITC4 geometry"); } const MITC4DirectorFrame& frame = geometry.nodal_frames[0]; return {frame.v1, frame.v2, frame.vn}; } } // namespace fesa