feat(cpp-object-oriented-modular-refactoring): step 7 - vector3-value-type

This commit is contained in:
KOKO\Mimi
2026-08-16 07:08:42 +09:00
parent 1e8bf3546a
commit 060a41b2b7
3 changed files with 184 additions and 0 deletions
+89
View File
@@ -0,0 +1,89 @@
#ifndef FESA_MATH_VECTOR3_H_
#define FESA_MATH_VECTOR3_H_ // NOLINT(readability-identifier-naming)
#include <array>
#include <cmath>
#include <cstddef>
#include <optional>
namespace fesa {
/// @brief Represents an owning fixed-size three-dimensional value vector.
class Vector3 {
public:
/// @brief Constructs the zero vector.
constexpr Vector3() noexcept = default;
/// @brief Constructs a vector from three Cartesian components.
/// @param x First component in the caller-defined coordinate system.
/// @param y Second component in the caller-defined coordinate system.
/// @param z Third component in the caller-defined coordinate system.
constexpr Vector3(double x, double y, double z) noexcept
: components_{{x, y, z}} {}
/// @brief Returns the first component.
constexpr double X() const noexcept { return components_[0]; }
/// @brief Returns the second component.
constexpr double Y() const noexcept { return components_[1]; }
/// @brief Returns the third component.
constexpr double Z() const noexcept { return components_[2]; }
/// @brief Returns a component by zero-based index.
/// @pre index is less than three.
constexpr double operator[](std::size_t index) const noexcept {
return components_[index];
}
/// @brief Adds corresponding vector components.
constexpr Vector3 operator+(const Vector3& rhs) const noexcept {
return Vector3{X() + rhs.X(), Y() + rhs.Y(), Z() + rhs.Z()};
}
/// @brief Subtracts corresponding vector components.
constexpr Vector3 operator-(const Vector3& rhs) const noexcept {
return Vector3{X() - rhs.X(), Y() - rhs.Y(), Z() - rhs.Z()};
}
/// @brief Multiplies every component by a scalar.
constexpr Vector3 operator*(double scalar) const noexcept {
return Vector3{X() * scalar, Y() * scalar, Z() * scalar};
}
/// @brief Computes the Euclidean dot product with rhs.
double Dot(const Vector3& rhs) const noexcept {
return X() * rhs.X() + Y() * rhs.Y() + Z() * rhs.Z();
}
/// @brief Computes the right-handed cross product with rhs.
Vector3 Cross(const Vector3& rhs) const noexcept {
return Vector3{Y() * rhs.Z() - Z() * rhs.Y(), Z() * rhs.X() - X() * rhs.Z(),
X() * rhs.Y() - Y() * rhs.X()};
}
/// @brief Computes the Euclidean norm.
double Norm() const noexcept { return std::sqrt(Dot(*this)); }
/// @brief Returns a unit vector when the norm is usable.
/// @return Empty when the norm is exactly zero or nonfinite.
std::optional<Vector3> Normalized() const noexcept {
const double norm = Norm(); // NOLINT(readability-identifier-naming)
if (norm == 0.0 || !std::isfinite(norm)) {
return std::nullopt;
}
return Vector3{X() / norm, Y() / norm, Z() / norm};
}
/// @brief Reports whether all components are finite.
bool IsFinite() const noexcept {
return std::isfinite(X()) && std::isfinite(Y()) && std::isfinite(Z());
}
private:
std::array<double, 3> components_{};
};
} // namespace fesa
#endif // FESA_MATH_VECTOR3_H_
+1
View File
@@ -17,6 +17,7 @@ add_executable(
unit/fem/dof_manager_test.cpp unit/fem/dof_manager_test.cpp
unit/math/matrix_test.cpp unit/math/matrix_test.cpp
unit/math/sparse_matrix_test.cpp unit/math/sparse_matrix_test.cpp
unit/math/vector3_test.cpp
unit/math/vector_test.cpp unit/math/vector_test.cpp
unit/io/abaqus/domain_mapper_test.cpp unit/io/abaqus/domain_mapper_test.cpp
unit/io/abaqus/input_reader_test.cpp unit/io/abaqus/input_reader_test.cpp
+94
View File
@@ -0,0 +1,94 @@
#include "fesa/math/vector3.h"
#include <gtest/gtest.h>
#include <limits>
#include <optional>
namespace fesa {
namespace {
TEST(Vector3, StoresComponentsWithExactArithmetic) {
constexpr Vector3 zero;
static_assert(zero.X() == 0.0);
static_assert(zero.Y() == 0.0);
static_assert(zero.Z() == 0.0);
constexpr Vector3 lhs{1.0, -2.0, 3.0};
constexpr Vector3 rhs{-4.0, 5.0, 6.0};
constexpr Vector3 sum = lhs + rhs;
constexpr Vector3 difference = lhs - rhs;
constexpr Vector3 scaled = lhs * -2.0;
EXPECT_DOUBLE_EQ(lhs.X(), 1.0);
EXPECT_DOUBLE_EQ(lhs.Y(), -2.0);
EXPECT_DOUBLE_EQ(lhs.Z(), 3.0);
EXPECT_DOUBLE_EQ(lhs[0], 1.0);
EXPECT_DOUBLE_EQ(lhs[1], -2.0);
EXPECT_DOUBLE_EQ(lhs[2], 3.0);
EXPECT_DOUBLE_EQ(sum[0], -3.0);
EXPECT_DOUBLE_EQ(sum[1], 3.0);
EXPECT_DOUBLE_EQ(sum[2], 9.0);
EXPECT_DOUBLE_EQ(difference[0], 5.0);
EXPECT_DOUBLE_EQ(difference[1], -7.0);
EXPECT_DOUBLE_EQ(difference[2], -3.0);
EXPECT_DOUBLE_EQ(scaled[0], -2.0);
EXPECT_DOUBLE_EQ(scaled[1], 4.0);
EXPECT_DOUBLE_EQ(scaled[2], -6.0);
}
TEST(Vector3, ComputesDotProduct) {
const Vector3 lhs{1.0, 2.0, 3.0};
const Vector3 rhs{4.0, -5.0, 6.0};
EXPECT_DOUBLE_EQ(lhs.Dot(rhs), 12.0);
}
TEST(Vector3, UsesRightHandedCrossProductOrientation) {
const Vector3 x_axis{1.0, 0.0, 0.0};
const Vector3 y_axis{0.0, 1.0, 0.0};
const Vector3 positive_z = x_axis.Cross(y_axis);
const Vector3 negative_z = y_axis.Cross(x_axis);
EXPECT_DOUBLE_EQ(positive_z.X(), 0.0);
EXPECT_DOUBLE_EQ(positive_z.Y(), 0.0);
EXPECT_DOUBLE_EQ(positive_z.Z(), 1.0);
EXPECT_DOUBLE_EQ(negative_z.X(), 0.0);
EXPECT_DOUBLE_EQ(negative_z.Y(), 0.0);
EXPECT_DOUBLE_EQ(negative_z.Z(), -1.0);
}
TEST(Vector3, ComputesEuclideanNorm) {
EXPECT_DOUBLE_EQ(Vector3(2.0, -3.0, 6.0).Norm(), 7.0);
}
TEST(Vector3, NormalizesNonzeroVectors) {
const std::optional<Vector3> normalized = Vector3(3.0, 0.0, 4.0).Normalized();
ASSERT_TRUE(normalized.has_value());
EXPECT_DOUBLE_EQ(normalized->X(), 0.6);
EXPECT_DOUBLE_EQ(normalized->Y(), 0.0);
EXPECT_DOUBLE_EQ(normalized->Z(), 0.8);
}
TEST(Vector3, ReportsFiniteComponents) {
const double infinity = std::numeric_limits<double>::infinity();
const double not_a_number = std::numeric_limits<double>::quiet_NaN();
EXPECT_TRUE(Vector3(1.0, -2.0, 3.0).IsFinite());
EXPECT_FALSE(Vector3(infinity, 0.0, 0.0).IsFinite());
EXPECT_FALSE(Vector3(0.0, not_a_number, 0.0).IsFinite());
}
TEST(Vector3, RejectsZeroAndNonfiniteNormalization) {
const double infinity = std::numeric_limits<double>::infinity();
const double not_a_number = std::numeric_limits<double>::quiet_NaN();
EXPECT_FALSE(Vector3().Normalized().has_value());
EXPECT_FALSE(Vector3(infinity, 0.0, 0.0).Normalized().has_value());
EXPECT_FALSE(Vector3(0.0, not_a_number, 0.0).Normalized().has_value());
}
} // namespace
} // namespace fesa