From c9023f2f8b6f2f6573ea580b885d562f7d7e809e Mon Sep 17 00:00:00 2001 From: Alex Selimov Date: Tue, 15 Apr 2025 23:30:13 -0400 Subject: [PATCH] Update README and add norm calculations to vector --- README.md | 29 ++-- include/vec3.h | 9 +- tests/unit_tests/vec_test.cpp | 299 +++++++++++++++++++++------------- 3 files changed, 213 insertions(+), 124 deletions(-) diff --git a/README.md b/README.md index 9236f1b..1aca5e5 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,21 @@ -# C++ Project Template -When setting out on a new project in C++ there are a few configuration steps -which need to be completed prior to actually getting down to writing code. -This repository is going to be a C++ project template that already has the -following components: +# Vec3 -- Directory Structure -- Make Build (CMake) -- CUDA integration -- Unit Test Framework (Google Test) -- API Documentation (Doxygen) +Simple header only library defining a 3 dimensional vector type. + +## Installation + +To use from another CMake project you just need to add the following to your CMakeLists.txt: + +```cmake +include(FetchContent) +FetchContent_Declare(Vec3 + GIT_REPOSITORY https://www.alexselimov.com/git/aselimov/Vec3.git +) + +FetchContent_GetProperties(Vec3) +if(NOT Vec3_POPULATED) + FetchContent_Populate(Vec3) + include_directories(${Vec3_SOURCE_DIR}/include) +endif() +``` diff --git a/include/vec3.h b/include/vec3.h index 0a2e948..ec65c0b 100644 --- a/include/vec3.h +++ b/include/vec3.h @@ -1,6 +1,7 @@ #ifndef VEC3_H #define VEC3_H +#include template struct Vec3 { T x; T y; @@ -20,14 +21,18 @@ template struct Vec3 { z *= scalar; }; - inline T dot(Vec3 other) { + inline T dot(Vec3 other) const { return x * other.x + y * other.y + z * other.z; } - inline Vec3 cross(Vec3 other) { + inline Vec3 cross(Vec3 other) const { return {y * other.z - z * other.y, z * other.x - x * other.z, x * other.y - y * other.x}; } + + inline double squared_norm2() const { return x * x + y * y + z * z; } + + inline double norm2() const { return std::sqrt(squared_norm2()); } }; #endif diff --git a/tests/unit_tests/vec_test.cpp b/tests/unit_tests/vec_test.cpp index 1b18dd6..4fa9d95 100644 --- a/tests/unit_tests/vec_test.cpp +++ b/tests/unit_tests/vec_test.cpp @@ -7,173 +7,173 @@ class Vec3Test : public ::testing::Test { protected: // Test vectors that will be used across multiple tests - Vec3 intVec1, intVec2; - Vec3 floatVec1, floatVec2; - Vec3 doubleVec1, doubleVec2; + Vec3 int_vec1, int_vec2; + Vec3 float_vec1, float_vec2; + Vec3 double_vec1, double_vec2; void SetUp() override { // Initialize test vectors - intVec1 = {1, 2, 3}; - intVec2 = {4, 5, 6}; + int_vec1 = {1, 2, 3}; + int_vec2 = {4, 5, 6}; - floatVec1 = {1.5f, 2.5f, 3.5f}; - floatVec2 = {4.5f, 5.5f, 6.5f}; + float_vec1 = {1.5f, 2.5f, 3.5f}; + float_vec2 = {4.5f, 5.5f, 6.5f}; - doubleVec1 = {1.5, 2.5, 3.5}; - doubleVec2 = {4.5, 5.5, 6.5}; + double_vec1 = {1.5, 2.5, 3.5}; + double_vec2 = {4.5, 5.5, 6.5}; } }; // Test vector addition TEST_F(Vec3Test, Addition) { // Test integer vectors - auto intResult = intVec1 + intVec2; - EXPECT_EQ(intResult.x, 5); - EXPECT_EQ(intResult.y, 7); - EXPECT_EQ(intResult.z, 9); + auto int_result = int_vec1 + int_vec2; + EXPECT_EQ(int_result.x, 5); + EXPECT_EQ(int_result.y, 7); + EXPECT_EQ(int_result.z, 9); // Test float vectors - auto floatResult = floatVec1 + floatVec2; - EXPECT_FLOAT_EQ(floatResult.x, 6.0f); - EXPECT_FLOAT_EQ(floatResult.y, 8.0f); - EXPECT_FLOAT_EQ(floatResult.z, 10.0f); + auto float_result = float_vec1 + float_vec2; + EXPECT_FLOAT_EQ(float_result.x, 6.0f); + EXPECT_FLOAT_EQ(float_result.y, 8.0f); + EXPECT_FLOAT_EQ(float_result.z, 10.0f); // Test double vectors - auto doubleResult = doubleVec1 + doubleVec2; - EXPECT_DOUBLE_EQ(doubleResult.x, 6.0); - EXPECT_DOUBLE_EQ(doubleResult.y, 8.0); - EXPECT_DOUBLE_EQ(doubleResult.z, 10.0); + auto double_result = double_vec1 + double_vec2; + EXPECT_DOUBLE_EQ(double_result.x, 6.0); + EXPECT_DOUBLE_EQ(double_result.y, 8.0); + EXPECT_DOUBLE_EQ(double_result.z, 10.0); } // Test vector subtraction TEST_F(Vec3Test, Subtraction) { // Test integer vectors - auto intResult = intVec2 - intVec1; - EXPECT_EQ(intResult.x, 3); - EXPECT_EQ(intResult.y, 3); - EXPECT_EQ(intResult.z, 3); + auto int_result = int_vec2 - int_vec1; + EXPECT_EQ(int_result.x, 3); + EXPECT_EQ(int_result.y, 3); + EXPECT_EQ(int_result.z, 3); // Test float vectors - auto floatResult = floatVec2 - floatVec1; - EXPECT_FLOAT_EQ(floatResult.x, 3.0f); - EXPECT_FLOAT_EQ(floatResult.y, 3.0f); - EXPECT_FLOAT_EQ(floatResult.z, 3.0f); + auto float_result = float_vec2 - float_vec1; + EXPECT_FLOAT_EQ(float_result.x, 3.0f); + EXPECT_FLOAT_EQ(float_result.y, 3.0f); + EXPECT_FLOAT_EQ(float_result.z, 3.0f); // Test double vectors - auto doubleResult = doubleVec2 - doubleVec1; - EXPECT_DOUBLE_EQ(doubleResult.x, 3.0); - EXPECT_DOUBLE_EQ(doubleResult.y, 3.0); - EXPECT_DOUBLE_EQ(doubleResult.z, 3.0); + auto double_result = double_vec2 - double_vec1; + EXPECT_DOUBLE_EQ(double_result.x, 3.0); + EXPECT_DOUBLE_EQ(double_result.y, 3.0); + EXPECT_DOUBLE_EQ(double_result.z, 3.0); } // Test vector scaling TEST_F(Vec3Test, Scale) { // Test integer scaling - Vec3 intVecScaled = intVec1; - intVecScaled.scale(2); - EXPECT_EQ(intVecScaled.x, 2); - EXPECT_EQ(intVecScaled.y, 4); - EXPECT_EQ(intVecScaled.z, 6); + Vec3 int_vec_scaled = int_vec1; + int_vec_scaled.scale(2); + EXPECT_EQ(int_vec_scaled.x, 2); + EXPECT_EQ(int_vec_scaled.y, 4); + EXPECT_EQ(int_vec_scaled.z, 6); // Test float scaling - Vec3 floatVecScaled = floatVec1; - floatVecScaled.scale(2.0f); - EXPECT_FLOAT_EQ(floatVecScaled.x, 3.0f); - EXPECT_FLOAT_EQ(floatVecScaled.y, 5.0f); - EXPECT_FLOAT_EQ(floatVecScaled.z, 7.0f); + Vec3 float_vec_scaled = float_vec1; + float_vec_scaled.scale(2.0f); + EXPECT_FLOAT_EQ(float_vec_scaled.x, 3.0f); + EXPECT_FLOAT_EQ(float_vec_scaled.y, 5.0f); + EXPECT_FLOAT_EQ(float_vec_scaled.z, 7.0f); // Test double scaling - Vec3 doubleVecScaled = doubleVec1; - doubleVecScaled.scale(2.0); - EXPECT_DOUBLE_EQ(doubleVecScaled.x, 3.0); - EXPECT_DOUBLE_EQ(doubleVecScaled.y, 5.0); - EXPECT_DOUBLE_EQ(doubleVecScaled.z, 7.0); + Vec3 double_vec_scaled = double_vec1; + double_vec_scaled.scale(2.0); + EXPECT_DOUBLE_EQ(double_vec_scaled.x, 3.0); + EXPECT_DOUBLE_EQ(double_vec_scaled.y, 5.0); + EXPECT_DOUBLE_EQ(double_vec_scaled.z, 7.0); // Test scaling by zero - Vec3 zeroScaled = floatVec1; - zeroScaled.scale(0.0f); - EXPECT_FLOAT_EQ(zeroScaled.x, 0.0f); - EXPECT_FLOAT_EQ(zeroScaled.y, 0.0f); - EXPECT_FLOAT_EQ(zeroScaled.z, 0.0f); + Vec3 zero_scaled = float_vec1; + zero_scaled.scale(0.0f); + EXPECT_FLOAT_EQ(zero_scaled.x, 0.0f); + EXPECT_FLOAT_EQ(zero_scaled.y, 0.0f); + EXPECT_FLOAT_EQ(zero_scaled.z, 0.0f); // Test scaling by negative number - Vec3 negScaled = intVec1; - negScaled.scale(-1); - EXPECT_EQ(negScaled.x, -1); - EXPECT_EQ(negScaled.y, -2); - EXPECT_EQ(negScaled.z, -3); + Vec3 neg_scaled = int_vec1; + neg_scaled.scale(-1); + EXPECT_EQ(neg_scaled.x, -1); + EXPECT_EQ(neg_scaled.y, -2); + EXPECT_EQ(neg_scaled.z, -3); } // Test dot product TEST_F(Vec3Test, DotProduct) { // Test integer dot product - int intDot = intVec1.dot(intVec2); - EXPECT_EQ(intDot, 32); // 1*4 + 2*5 + 3*6 = 4 + 10 + 18 = 32 + int int_dot = int_vec1.dot(int_vec2); + EXPECT_EQ(int_dot, 32); // 1*4 + 2*5 + 3*6 = 4 + 10 + 18 = 32 // Test float dot product - float floatDot = floatVec1.dot(floatVec2); + float float_dot = float_vec1.dot(float_vec2); EXPECT_FLOAT_EQ( - floatDot, + float_dot, 43.25f); // 1.5*4.5 + 2.5*5.5 + 3.5*6.5 = 6.75 + 13.75 + 22.75 = 43.25 // Test double dot product - double doubleDot = doubleVec1.dot(doubleVec2); - EXPECT_DOUBLE_EQ(doubleDot, 43.25); // Same calculation as float + double double_dot = double_vec1.dot(double_vec2); + EXPECT_DOUBLE_EQ(double_dot, 43.25); // Same calculation as float // Test dot product with self (should equal squared length) - int selfDot = intVec1.dot(intVec1); - EXPECT_EQ(selfDot, 14); // 1*1 + 2*2 + 3*3 = 1 + 4 + 9 = 14 + int self_dot = int_vec1.dot(int_vec1); + EXPECT_EQ(self_dot, 14); // 1*1 + 2*2 + 3*3 = 1 + 4 + 9 = 14 // Test dot product with zero vector - Vec3 zeroVec = {0, 0, 0}; - EXPECT_EQ(intVec1.dot(zeroVec), 0); + Vec3 zero_vec = {0, 0, 0}; + EXPECT_EQ(int_vec1.dot(zero_vec), 0); } // Test cross product TEST_F(Vec3Test, CrossProduct) { // Test integer cross product - auto intCross = intVec1.cross(intVec2); - EXPECT_EQ(intCross.x, -3); // (2*6 - 3*5) = 12 - 15 = -3 - EXPECT_EQ(intCross.y, 6); // (3*4 - 1*6) = 12 - 6 = 6 - EXPECT_EQ(intCross.z, -3); // (1*5 - 2*4) = 5 - 8 = -3 + auto int_cross = int_vec1.cross(int_vec2); + EXPECT_EQ(int_cross.x, -3); // (2*6 - 3*5) = 12 - 15 = -3 + EXPECT_EQ(int_cross.y, 6); // (3*4 - 1*6) = 12 - 6 = 6 + EXPECT_EQ(int_cross.z, -3); // (1*5 - 2*4) = 5 - 8 = -3 // Test float cross product - auto floatCross = floatVec1.cross(floatVec2); - EXPECT_FLOAT_EQ(floatCross.x, + auto float_cross = float_vec1.cross(float_vec2); + EXPECT_FLOAT_EQ(float_cross.x, -3.0f); // (2.5*6.5 - 3.5*5.5) = 16.25 - 19.25 = -3 - EXPECT_FLOAT_EQ(floatCross.y, + EXPECT_FLOAT_EQ(float_cross.y, 6.0f); // (3.5*4.5 - 1.5*6.5) = 15.75 - 9.75 = 6 - EXPECT_FLOAT_EQ(floatCross.z, + EXPECT_FLOAT_EQ(float_cross.z, -3.0f); // (1.5*5.5 - 2.5*4.5) = 8.25 - 11.25 = -3 // Test double cross product - auto doubleCross = doubleVec1.cross(doubleVec2); - EXPECT_DOUBLE_EQ(doubleCross.x, -3.0); - EXPECT_DOUBLE_EQ(doubleCross.y, 6.0); - EXPECT_DOUBLE_EQ(doubleCross.z, -3.0); + auto double_cross = double_vec1.cross(double_vec2); + EXPECT_DOUBLE_EQ(double_cross.x, -3.0); + EXPECT_DOUBLE_EQ(double_cross.y, 6.0); + EXPECT_DOUBLE_EQ(double_cross.z, -3.0); // Test cross product with self (should be zero) - auto selfCross = intVec1.cross(intVec1); - EXPECT_EQ(selfCross.x, 0); - EXPECT_EQ(selfCross.y, 0); - EXPECT_EQ(selfCross.z, 0); + auto self_cross = int_vec1.cross(int_vec1); + EXPECT_EQ(self_cross.x, 0); + EXPECT_EQ(self_cross.y, 0); + EXPECT_EQ(self_cross.z, 0); // Test cross product of standard basis vectors (i × j = k) - Vec3 i = {1, 0, 0}; - Vec3 j = {0, 1, 0}; - Vec3 k = {0, 0, 1}; + Vec3 vec_i = {1, 0, 0}; + Vec3 vec_j = {0, 1, 0}; + Vec3 vec_k = {0, 0, 1}; - auto i_cross_j = i.cross(j); + auto i_cross_j = vec_i.cross(vec_j); EXPECT_EQ(i_cross_j.x, 0); EXPECT_EQ(i_cross_j.y, 0); EXPECT_EQ(i_cross_j.z, 1); - auto j_cross_k = j.cross(k); + auto j_cross_k = vec_j.cross(vec_k); EXPECT_EQ(j_cross_k.x, 1); EXPECT_EQ(j_cross_k.y, 0); EXPECT_EQ(j_cross_k.z, 0); - auto k_cross_i = k.cross(i); + auto k_cross_i = vec_k.cross(vec_i); EXPECT_EQ(k_cross_i.x, 0); EXPECT_EQ(k_cross_i.y, 1); EXPECT_EQ(k_cross_i.z, 0); @@ -182,46 +182,121 @@ TEST_F(Vec3Test, CrossProduct) { // Test with edge cases TEST_F(Vec3Test, EdgeCases) { // Test with max values - Vec3 maxVec = {std::numeric_limits::max(), - std::numeric_limits::max(), - std::numeric_limits::max()}; + Vec3 max_vec = {std::numeric_limits::max(), + std::numeric_limits::max(), + std::numeric_limits::max()}; // Addition with max values may overflow, but we want to test the operation // works - auto maxAddition = maxVec + intVec1; + auto max_addition = max_vec + int_vec1; // Test with min values - Vec3 minVec = {std::numeric_limits::min(), - std::numeric_limits::min(), - std::numeric_limits::min()}; + Vec3 min_vec = {std::numeric_limits::min(), + std::numeric_limits::min(), + std::numeric_limits::min()}; // Subtraction with min values may underflow, but we want to test the // operation works - auto minSubtraction = minVec - intVec1; + auto min_subtraction = min_vec - int_vec1; // Test with mixed values - Vec3 mixedVec1 = {0.0, -1.0, std::numeric_limits::infinity()}; - Vec3 mixedVec2 = {-0.0, 1.0, - -std::numeric_limits::infinity()}; + Vec3 mixed_vec1 = {0.0, -1.0, + std::numeric_limits::infinity()}; + Vec3 mixed_vec2 = {-0.0, 1.0, + -std::numeric_limits::infinity()}; - auto mixedAddition = mixedVec1 + mixedVec2; + auto mixed_addition = mixed_vec1 + mixed_vec2; // 0.0 + (-0.0) = 0.0 - EXPECT_DOUBLE_EQ(mixedAddition.x, 0.0); + EXPECT_DOUBLE_EQ(mixed_addition.x, 0.0); // -1.0 + 1.0 = 0.0 - EXPECT_DOUBLE_EQ(mixedAddition.y, 0.0); + EXPECT_DOUBLE_EQ(mixed_addition.y, 0.0); // inf + (-inf) = NaN - EXPECT_TRUE(std::isnan(mixedAddition.z)); + EXPECT_TRUE(std::isnan(mixed_addition.z)); // Test with NaN - Vec3 nanVec = {std::numeric_limits::quiet_NaN(), - std::numeric_limits::quiet_NaN(), - std::numeric_limits::quiet_NaN()}; + Vec3 nan_vec = {std::numeric_limits::quiet_NaN(), + std::numeric_limits::quiet_NaN(), + std::numeric_limits::quiet_NaN()}; // Any operation with NaN should result in NaN - auto nanResult = doubleVec1 + nanVec; - EXPECT_TRUE(std::isnan(nanResult.x)); - EXPECT_TRUE(std::isnan(nanResult.y)); - EXPECT_TRUE(std::isnan(nanResult.z)); + auto nan_result = double_vec1 + nan_vec; + EXPECT_TRUE(std::isnan(nan_result.x)); + EXPECT_TRUE(std::isnan(nan_result.y)); + EXPECT_TRUE(std::isnan(nan_result.z)); +} + +// Test squared_norm2 and norm2 methods +TEST_F(Vec3Test, NormMethods) { + // Test squared_norm2 for integer vector + EXPECT_DOUBLE_EQ(int_vec1.squared_norm2(), 14.0); // 1*1 + 2*2 + 3*3 = 14 + + // Test norm2 for integer vector + EXPECT_DOUBLE_EQ(int_vec1.norm2(), std::sqrt(14.0)); // √14 ≈ 3.741657... + + // Test squared_norm2 for float vector + EXPECT_DOUBLE_EQ( + float_vec1.squared_norm2(), + 20.75); // 1.5*1.5 + 2.5*2.5 + 3.5*3.5 = 2.25 + 6.25 + 12.25 = 20.75 + + // Test norm2 for float vector + EXPECT_DOUBLE_EQ(float_vec1.norm2(), std::sqrt(20.75)); // √20.75 ≈ 4.5552... + + // Test squared_norm2 for double vector + EXPECT_DOUBLE_EQ(double_vec1.squared_norm2(), + 20.75); // Same as float calculation + + // Test norm2 for double vector + EXPECT_DOUBLE_EQ(double_vec1.norm2(), std::sqrt(20.75)); + + // Test with zero vector + Vec3 zero_vec = {0, 0, 0}; + EXPECT_DOUBLE_EQ(zero_vec.squared_norm2(), 0.0); + EXPECT_DOUBLE_EQ(zero_vec.norm2(), 0.0); + + // Test with unit vectors + Vec3 unit_x = {1.0, 0.0, 0.0}; + Vec3 unit_y = {0.0, 1.0, 0.0}; + Vec3 unit_z = {0.0, 0.0, 1.0}; + + EXPECT_DOUBLE_EQ(unit_x.squared_norm2(), 1.0); + EXPECT_DOUBLE_EQ(unit_x.norm2(), 1.0); + + EXPECT_DOUBLE_EQ(unit_y.squared_norm2(), 1.0); + EXPECT_DOUBLE_EQ(unit_y.norm2(), 1.0); + + EXPECT_DOUBLE_EQ(unit_z.squared_norm2(), 1.0); + EXPECT_DOUBLE_EQ(unit_z.norm2(), 1.0); + + // Test with negative components + Vec3 neg_vec = {-1, -2, -3}; + EXPECT_DOUBLE_EQ(neg_vec.squared_norm2(), 14.0); // (-1)² + (-2)² + (-3)² = 14 + EXPECT_DOUBLE_EQ(neg_vec.norm2(), std::sqrt(14.0)); +} + +// Test norm methods with special values +TEST_F(Vec3Test, NormEdgeCases) { + // Test with infinity + Vec3 inf_vec = {std::numeric_limits::infinity(), 0.0, 0.0}; + EXPECT_TRUE(std::isinf(inf_vec.squared_norm2())); + EXPECT_TRUE(std::isinf(inf_vec.norm2())); + + // Test with large values that might cause overflow in intermediate + // calculations + double large_val = std::sqrt(std::numeric_limits::max()) / 2.0; + Vec3 large_vec = {large_val, large_val, large_val}; + + // The squared norm should be approximately 3 * large_val² + double expected_squared = 3.0 * large_val * large_val; + EXPECT_DOUBLE_EQ(large_vec.squared_norm2(), expected_squared); + + // The norm should be √(3) * large_val + double expected_norm = std::sqrt(3.0) * large_val; + EXPECT_DOUBLE_EQ(large_vec.norm2(), expected_norm); + + // Test with NaN values + Vec3 nan_vec = {std::numeric_limits::quiet_NaN(), 0.0, 0.0}; + EXPECT_TRUE(std::isnan(nan_vec.squared_norm2())); + EXPECT_TRUE(std::isnan(nan_vec.norm2())); } // Main function that runs the tests