feat(cpp-object-oriented-modular-refactoring): step 8 - element-geometry-vector3

This commit is contained in:
KOKO\Mimi
2026-08-16 07:27:51 +09:00
parent cbad5c3592
commit a9ff75b3fa
9 changed files with 320 additions and 233 deletions
+55 -85
View File
@@ -5,11 +5,11 @@
#include <tuple>
#include <utility>
#include "fesa/math/vector3.h"
namespace fesa {
namespace {
using Vector3 = std::array<double, 3>;
constexpr std::array<double, 4> kXiSigns{-1.0, 1.0, 1.0, -1.0};
constexpr std::array<double, 4> kEtaSigns{-1.0, -1.0, 1.0, 1.0};
@@ -25,37 +25,6 @@ struct ElementWork {
double area_weight;
};
Vector3 Add(const Vector3& left, const Vector3& right) {
return {left[0] + right[0], left[1] + right[1], left[2] + right[2]};
}
Vector3 Subtract(const Vector3& left, const Vector3& right) {
return {left[0] - right[0], left[1] - right[1], left[2] - right[2]};
}
Vector3 Scale(double factor, const Vector3& value) {
return {factor * value[0], factor * value[1], factor * value[2]};
}
double Dot(const Vector3& left, const Vector3& right) {
return left[0] * right[0] + left[1] * right[1] + left[2] * right[2];
}
Vector3 Cross(const Vector3& left, const Vector3& right) {
return {left[1] * right[2] - left[2] * right[1],
left[2] * right[0] - left[0] * right[2],
left[0] * right[1] - left[1] * right[0]};
}
double Norm(const Vector3& value) {
return std::hypot(value[0], value[1], value[2]);
}
bool IsFinite(const Vector3& value) {
return std::all_of(value.begin(), value.end(),
[](double component) { return std::isfinite(component); });
}
ShapeData ShapeDataAt(double xi, double eta) {
ShapeData data{};
for (std::size_t node = 0U; node < 4U; ++node) {
@@ -73,7 +42,7 @@ Vector3 WeightedSum(const std::array<double, 4>& weights,
const std::array<Vector3, 4>& values) {
Vector3 result{};
for (std::size_t node = 0U; node < values.size(); ++node) {
result = Add(result, Scale(weights[node], values[node]));
result = result + weights[node] * values[node];
}
return result;
}
@@ -85,7 +54,7 @@ Vector3 DerivativeSum(const std::array<double, 4>& derivatives,
// numerical cancellation without changing the covariant tangent.
std::array<Vector3, 4> relative{};
for (std::size_t node = 0U; node < coordinates.size(); ++node) {
relative[node] = Subtract(coordinates[node], coordinates[0]);
relative[node] = coordinates[node] - coordinates[0];
}
return WeightedSum(derivatives, relative);
}
@@ -106,7 +75,7 @@ bool SameCoordinates(const Vector3& left, const Vector3& right) {
double Orientation(const Vector3& first, const Vector3& second,
const Vector3& third, const Vector3& normal) {
return Dot(Cross(Subtract(second, first), Subtract(third, first)), normal);
return (second - first).Cross(third - first).Dot(normal);
}
bool HasOppositeSigns(double first, double second) {
@@ -197,8 +166,8 @@ Result<ShellGeometry> PreprocessShellGeometry(
"Shell geometry references an unavailable internal node.");
}
current.coordinates[local_node] =
nodes[element.node_indices[local_node]].coordinates;
if (!IsFinite(current.coordinates[local_node])) {
Vector3{nodes[element.node_indices[local_node]].coordinates};
if (!current.coordinates[local_node].IsFinite()) {
return GeometryFailure(
"invalid-shell-geometry", element.location, "ELEMENT",
element.source_id.source_label_text,
@@ -222,17 +191,17 @@ Result<ShellGeometry> PreprocessShellGeometry(
DerivativeSum(center.xi_derivatives, current.coordinates);
const Vector3 center_eta =
DerivativeSum(center.eta_derivatives, current.coordinates);
const Vector3 center_cross = Cross(center_xi, center_eta);
const double center_measure = Norm(center_cross);
if (!IsFinite(center_xi) || !IsFinite(center_eta) ||
!IsFinite(center_cross) || !std::isfinite(center_measure) ||
const Vector3 center_cross = center_xi.Cross(center_eta);
const double center_measure = center_cross.Norm();
if (!center_xi.IsFinite() || !center_eta.IsFinite() ||
!center_cross.IsFinite() || !std::isfinite(center_measure) ||
!(center_measure > 0.0)) {
return GeometryFailure(
"invalid-shell-geometry", element.location, "ELEMENT",
element.source_id.source_label_text,
"Shell center has no finite nonzero normal candidate.");
}
current.normal = Scale(1.0 / center_measure, center_cross);
current.normal = (1.0 / center_measure) * center_cross;
if (SegmentsProperlyIntersect(
current.coordinates[0], current.coordinates[1],
@@ -253,11 +222,11 @@ Result<ShellGeometry> PreprocessShellGeometry(
DerivativeSum(shape.xi_derivatives, current.coordinates);
const Vector3 tangent_eta =
DerivativeSum(shape.eta_derivatives, current.coordinates);
const Vector3 area_vector = Cross(tangent_xi, tangent_eta);
const double measure = Norm(area_vector);
if (!IsFinite(tangent_xi) || !IsFinite(tangent_eta) ||
!IsFinite(area_vector) || !std::isfinite(measure) ||
!(measure > 0.0) || !(Dot(area_vector, current.normal) > 0.0)) {
const Vector3 area_vector = tangent_xi.Cross(tangent_eta);
const double measure = area_vector.Norm();
if (!tangent_xi.IsFinite() || !tangent_eta.IsFinite() ||
!area_vector.IsFinite() || !std::isfinite(measure) ||
!(measure > 0.0) || !(area_vector.Dot(current.normal) > 0.0)) {
return GeometryFailure("invalid-shell-geometry", element.location,
"ELEMENT", element.source_id.source_label_text,
"Shell surface is zero-area or locally reversed "
@@ -272,8 +241,8 @@ Result<ShellGeometry> PreprocessShellGeometry(
}
current.area_weight = area_weight;
work.push_back(current);
geometry.element_data.push_back(
{static_cast<EntityIndex>(element_index), current.normal, area_weight});
geometry.element_data.push_back({static_cast<EntityIndex>(element_index),
current.normal.Components(), area_weight});
}
std::vector<std::vector<std::size_t>> incident(nodes.size());
@@ -301,8 +270,8 @@ Result<ShellGeometry> PreprocessShellGeometry(
for (std::size_t first = 0U; first < node_incident.size(); ++first) {
for (std::size_t second = first + 1U; second < node_incident.size();
++second) {
const double pair_dot = Dot(work[node_incident[first]].normal,
work[node_incident[second]].normal);
const double pair_dot = work[node_incident[first]].normal.Dot(
work[node_incident[second]].normal);
if (!std::isfinite(pair_dot) || !(pair_dot > 0.0)) {
return GeometryFailure("opposed-incident-normal",
nodes[node_index].location, "NODE",
@@ -320,52 +289,53 @@ Result<ShellGeometry> PreprocessShellGeometry(
}
Vector3 director_sum{};
for (const std::size_t element_index : node_incident) {
director_sum = Add(director_sum,
Scale(work[element_index].area_weight / maximum_weight,
work[element_index].normal));
director_sum =
director_sum + (work[element_index].area_weight / maximum_weight) *
work[element_index].normal;
}
const double director_norm = Norm(director_sum);
if (!IsFinite(director_sum) || !std::isfinite(director_norm) ||
const double director_norm = director_sum.Norm();
if (!director_sum.IsFinite() || !std::isfinite(director_norm) ||
!(director_norm > 0.0)) {
return GeometryFailure(
"invalid-shell-director", nodes[node_index].location, "NODE",
nodes[node_index].source_id.source_label_text,
"Area-weighted shell director is nonfinite or zero.");
}
const Vector3 director = Scale(1.0 / director_norm, director_sum);
const Vector3 director = (1.0 / director_norm) * director_sum;
const std::array<Vector3, 3> global_axes{
Vector3{1.0, 0.0, 0.0}, Vector3{0.0, 1.0, 0.0}, Vector3{0.0, 0.0, 1.0}};
std::size_t selected_axis = 0U;
double selected_alignment = std::abs(Dot(global_axes[0], director));
double selected_alignment = std::abs(global_axes[0].Dot(director));
for (std::size_t axis = 1U; axis < global_axes.size(); ++axis) {
const double alignment = std::abs(Dot(global_axes[axis], director));
const double alignment = std::abs(global_axes[axis].Dot(director));
if (alignment < selected_alignment) {
selected_alignment = alignment;
selected_axis = axis;
}
}
const Vector3 tangent_candidate =
Subtract(global_axes[selected_axis],
Scale(Dot(global_axes[selected_axis], director), director));
const double tangent_norm = Norm(tangent_candidate);
if (!IsFinite(tangent_candidate) || !std::isfinite(tangent_norm) ||
global_axes[selected_axis] -
global_axes[selected_axis].Dot(director) * director;
const double tangent_norm = tangent_candidate.Norm();
if (!tangent_candidate.IsFinite() || !std::isfinite(tangent_norm) ||
!(tangent_norm > 0.0)) {
return GeometryFailure(
"invalid-shell-director", nodes[node_index].location, "NODE",
nodes[node_index].source_id.source_label_text,
"Least-aligned-axis tangent frame construction failed.");
}
const Vector3 tangent_a = Scale(1.0 / tangent_norm, tangent_candidate);
const Vector3 tangent_b = Cross(director, tangent_a);
if (!IsFinite(tangent_b) || !(Norm(tangent_b) > 0.0)) {
const Vector3 tangent_a = (1.0 / tangent_norm) * tangent_candidate;
const Vector3 tangent_b = director.Cross(tangent_a);
if (!tangent_b.IsFinite() || !(tangent_b.Norm() > 0.0)) {
return GeometryFailure(
"invalid-shell-director", nodes[node_index].location, "NODE",
nodes[node_index].source_id.source_label_text,
"Right-handed shell tangent frame construction failed.");
}
geometry.nodal_frames.push_back(
{static_cast<EntityIndex>(node_index), director, tangent_a, tangent_b});
{static_cast<EntityIndex>(node_index), director.Components(),
tangent_a.Components(), tangent_b.Components()});
}
// Build lookup only after frame storage is complete so later code never
@@ -392,7 +362,7 @@ Result<ShellGeometry> PreprocessShellGeometry(
"ELEMENT", element.source_id.source_label_text,
"Shell element is missing a nodal director.");
}
directors[local_node] = frame->director;
directors[local_node] = Vector3{frame->director};
}
for (const auto& point : ShellGeometryValidationPoints()) {
@@ -404,12 +374,12 @@ Result<ShellGeometry> PreprocessShellGeometry(
DerivativeSum(shape.xi_derivatives, work[element_index].coordinates);
const Vector3 midsurface_eta =
DerivativeSum(shape.eta_derivatives, work[element_index].coordinates);
const Vector3 area_vector = Cross(midsurface_xi, midsurface_eta);
const double surface_measure = Norm(area_vector);
if (!IsFinite(midsurface_xi) || !IsFinite(midsurface_eta) ||
!IsFinite(area_vector) || !std::isfinite(surface_measure) ||
const Vector3 area_vector = midsurface_xi.Cross(midsurface_eta);
const double surface_measure = area_vector.Norm();
if (!midsurface_xi.IsFinite() || !midsurface_eta.IsFinite() ||
!area_vector.IsFinite() || !std::isfinite(surface_measure) ||
!(surface_measure > 0.0) ||
!(Dot(area_vector, work[element_index].normal) > 0.0)) {
!(area_vector.Dot(work[element_index].normal) > 0.0)) {
return GeometryFailure("invalid-shell-geometry", element.location,
"ELEMENT", element.source_id.source_label_text,
"Shell surface basis is nonfinite, zero, or "
@@ -421,14 +391,14 @@ Result<ShellGeometry> PreprocessShellGeometry(
WeightedSum(shape.eta_derivatives, directors);
const Vector3 director_value = WeightedSum(shape.values, directors);
const Vector3 covariant_xi =
Add(midsurface_xi, Scale(0.5 * thickness * zeta, director_xi));
midsurface_xi + 0.5 * thickness * zeta * director_xi;
const Vector3 covariant_eta =
Add(midsurface_eta, Scale(0.5 * thickness * zeta, director_eta));
const Vector3 covariant_zeta = Scale(0.5 * thickness, director_value);
midsurface_eta + 0.5 * thickness * zeta * director_eta;
const Vector3 covariant_zeta = 0.5 * thickness * director_value;
const double jacobian =
Dot(covariant_xi, Cross(covariant_eta, covariant_zeta));
if (!IsFinite(covariant_xi) || !IsFinite(covariant_eta) ||
!IsFinite(covariant_zeta) || !std::isfinite(jacobian) ||
covariant_xi.Dot(covariant_eta.Cross(covariant_zeta));
if (!covariant_xi.IsFinite() || !covariant_eta.IsFinite() ||
!covariant_zeta.IsFinite() || !std::isfinite(jacobian) ||
!(jacobian > 0.0)) {
return GeometryFailure(
"invalid-shell-jacobian", element.location, "ELEMENT",
@@ -436,13 +406,13 @@ Result<ShellGeometry> PreprocessShellGeometry(
"Shell Jacobian is nonfinite or nonpositive at a required point.");
}
const Vector3 reciprocal_xi =
Scale(1.0 / jacobian, Cross(covariant_eta, covariant_zeta));
(1.0 / jacobian) * covariant_eta.Cross(covariant_zeta);
const Vector3 reciprocal_eta =
Scale(1.0 / jacobian, Cross(covariant_zeta, covariant_xi));
(1.0 / jacobian) * covariant_zeta.Cross(covariant_xi);
const Vector3 reciprocal_zeta =
Scale(1.0 / jacobian, Cross(covariant_xi, covariant_eta));
if (!IsFinite(reciprocal_xi) || !IsFinite(reciprocal_eta) ||
!IsFinite(reciprocal_zeta)) {
(1.0 / jacobian) * covariant_xi.Cross(covariant_eta);
if (!reciprocal_xi.IsFinite() || !reciprocal_eta.IsFinite() ||
!reciprocal_zeta.IsFinite()) {
return GeometryFailure(
"invalid-shell-jacobian", element.location, "ELEMENT",
element.source_id.source_label_text,