/* * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ #include "Math/CoordinateSystemConverter.hpp" #include using namespace OpenVulkano::Math; using Catch::Approx; #define REQUIRE_VEC_EQ(vec1, vec2, eps) { \ REQUIRE(vec1.length() == vec2.length()); \ for (size_t i = 0; i < vec1.length(); i++) \ REQUIRE(Approx(vec1[i]).epsilon(eps) == vec2[i]); \ } #define REQUIRE_VEC_CLOSE(v1, v2) REQUIRE_VEC_EQ(v1, v2, 1e-5f) #define REQUIRE_MAT_EQ(mat1, mat2, eps) { \ REQUIRE(mat1.length() == mat2.length()); \ for (size_t c = 0; c < mat1.length(); c++) \ REQUIRE_VEC_EQ(mat1[c], mat2[c], eps) \ } #define REQUIRE_MAT_CLOSE(mat1, mat2) REQUIRE_MAT_EQ(mat1, mat2, 1e-5f) TEST_CASE("CoordinateSystemConverter - Same system (no conversion)", "[CoordinateSystemConverter]") { CoordinateSystem sys(CoordinateSystem::RIGHT_HANDED_Y_UP); CoordinateSystemConverter converter(sys, sys); SECTION("Vec3 unchanged") { glm::vec3 v(1.0f, 2.0f, 3.0f); auto result = converter.Convert(v); REQUIRE(result.x == Approx(1.0f)); REQUIRE(result.y == Approx(2.0f)); REQUIRE(result.z == Approx(3.0f)); } SECTION("Vec4 unchanged") { glm::vec4 v(1.0f, 2.0f, 3.0f, 4.0f); auto result = converter.Convert(v); REQUIRE(result.x == Approx(1.0f)); REQUIRE(result.y == Approx(2.0f)); REQUIRE(result.z == Approx(3.0f)); REQUIRE(result.w == Approx(4.0f)); } } TEST_CASE("CoordinateSystemConverter - Handedness conversion", "[CoordinateSystemConverter]") { SECTION("Right-handed Y-up to Left-handed Y-up") { CoordinateSystemConverter converter( CoordinateSystem::RIGHT_HANDED_Y_UP, CoordinateSystem::LEFT_HANDED_Y_UP ); glm::vec3 v(1.0f, 2.0f, 3.0f); auto result = converter.Convert(v); REQUIRE(result.x == Approx(1.0f)); REQUIRE(result.y == Approx(2.0f)); REQUIRE(result.z == Approx(-3.0f)); } SECTION("Right-handed Z-up to Left-handed Z-up") { CoordinateSystemConverter converter( CoordinateSystem::RIGHT_HANDED_Z_UP, CoordinateSystem::LEFT_HANDED_Z_UP ); glm::vec3 v(5.0f, 6.0f, 7.0f); auto result = converter.Convert(v); REQUIRE(result.x == Approx(5.0f)); REQUIRE(result.y == Approx(-6.0f)); REQUIRE(result.z == Approx(7.0f)); } } TEST_CASE("CoordinateSystemConverter - Up axis conversion", "[CoordinateSystemConverter]") { SECTION("Y-up to Z-up (right-handed)") { CoordinateSystemConverter converter( CoordinateSystem::RIGHT_HANDED_Y_UP, CoordinateSystem::RIGHT_HANDED_Z_UP ); glm::vec3 v(1.0f, 2.0f, 3.0f); auto result = converter.Convert(v); REQUIRE(result.x == Approx(1.0f)); REQUIRE(result.y == Approx(-3.0f)); REQUIRE(result.z == Approx(2.0f)); } SECTION("Z-up to Y-up (right-handed)") { CoordinateSystemConverter converter( CoordinateSystem::RIGHT_HANDED_Z_UP, CoordinateSystem::RIGHT_HANDED_Y_UP ); glm::vec3 v(1.0f, 2.0f, 3.0f); auto result = converter.Convert(v); REQUIRE(result.x == Approx(1.0f)); REQUIRE(result.y == Approx(3.0f)); REQUIRE(result.z == Approx(-2.0f)); } } TEST_CASE("CoordinateSystemConverter - Combined conversion", "[CoordinateSystemConverter]") { SECTION("Right-handed Y-up to Left-handed Z-up") { CoordinateSystemConverter converter( CoordinateSystem::RIGHT_HANDED_Y_UP, CoordinateSystem::LEFT_HANDED_Z_UP ); glm::vec3 v(1.0f, 2.0f, 3.0f); auto result = converter.Convert(v); // First convert up axis: Y->Z transforms (x,y,z) to (x,z,-y) = (1,3,-2) // Then convert handedness: negate z = (1,3,2) REQUIRE(result.x == Approx(1.0f)); REQUIRE(result.y == Approx(3.0f)); REQUIRE(result.z == Approx(2.0f)); } SECTION("Left-handed Z-up to Right-handed Y-up") { CoordinateSystemConverter converter( CoordinateSystem::LEFT_HANDED_Z_UP, CoordinateSystem::RIGHT_HANDED_Y_UP ); glm::vec3 v(4.0f, 5.0f, 6.0f); auto result = converter.Convert(v); // First convert up axis: Z->Y transforms (x,y,z) to (x,-z,y) = (4,-6,5) // Then convert handedness: negate z = (4,-6,-5) REQUIRE(result.x == Approx(4.0f)); REQUIRE(result.y == Approx(6.0f)); REQUIRE(result.z == Approx(5.0f)); } } TEST_CASE("CoordinateSystemConverter - Vec4 conversion", "[CoordinateSystemConverter]") { SECTION("Vec4 preserves w component") { CoordinateSystemConverter converter( CoordinateSystem::RIGHT_HANDED_Y_UP, CoordinateSystem::LEFT_HANDED_Z_UP ); glm::vec4 v(1.0f, 2.0f, 3.0f, 9.5f); auto result = converter.Convert(v); REQUIRE(result.x == Approx(1.0f)); REQUIRE(result.y == Approx(3.0f)); REQUIRE(result.z == Approx(2.0f)); REQUIRE(result.w == Approx(9.5f)); } } TEST_CASE("CoordinateSystemConverter - Edge cases", "[CoordinateSystemConverter]") { SECTION("Zero vector") { CoordinateSystemConverter converter( CoordinateSystem::RIGHT_HANDED_Y_UP, CoordinateSystem::LEFT_HANDED_Z_UP ); glm::vec3 v(0.0f, 0.0f, 0.0f); auto result = converter.Convert(v); REQUIRE(result.x == Approx(0.0f)); REQUIRE(result.y == Approx(0.0f)); REQUIRE(result.z == Approx(0.0f)); } SECTION("Negative values") { CoordinateSystemConverter converter( CoordinateSystem::RIGHT_HANDED_Y_UP, CoordinateSystem::LEFT_HANDED_Y_UP ); glm::vec3 v(-1.0f, -2.0f, -3.0f); auto result = converter.Convert(v); REQUIRE(result.x == Approx(-1.0f)); REQUIRE(result.y == Approx(-2.0f)); REQUIRE(result.z == Approx(3.0f)); } } TEST_CASE("CoordinateSystemConverter - Identity Conversion", "[CoordinateSystemConverter]") { SECTION("Same coordinate system should return identity for Vec3") { auto systems = GENERATE( CoordinateSystem::RIGHT_HANDED_Y_UP, CoordinateSystem::RIGHT_HANDED_Z_UP, CoordinateSystem::LEFT_HANDED_Y_UP, CoordinateSystem::LEFT_HANDED_Z_UP ); CoordinateSystemConverter converter(systems, systems); Vector<3, float, glm::defaultp> v(1.0f, 2.0f, 3.0f); auto result = converter.Convert(v); REQUIRE_VEC_CLOSE(result, v); } SECTION("Same coordinate system should return identity for Vec4") { auto systems = GENERATE( CoordinateSystem::RIGHT_HANDED_Y_UP, CoordinateSystem::RIGHT_HANDED_Z_UP, CoordinateSystem::LEFT_HANDED_Y_UP, CoordinateSystem::LEFT_HANDED_Z_UP ); CoordinateSystemConverter converter(systems, systems); Vector<4, float, glm::defaultp> v(1.0f, 2.0f, 3.0f, 1.0f); auto result = converter.Convert(v); REQUIRE_VEC_CLOSE(result, v); } } TEST_CASE("CoordinateSystemConverter - Vec3 Y-up to Z-up Conversions", "[CoordinateSystemConverter]") { SECTION("Right-handed Y-up to Right-handed Z-up - Unit vectors") { CoordinateSystemConverter converter( CoordinateSystem::RIGHT_HANDED_Y_UP, CoordinateSystem::RIGHT_HANDED_Z_UP ); // RH Y-up: X=right, Y=up, Z=backward (toward camera) // RH Z-up: X=right, Y=forward (away from camera), Z=up // Expected transformation: X->X, Y->Z, Z->-Y Vector<3, float, glm::defaultp> xAxis(1.0f, 0.0f, 0.0f); auto resultX = converter.Convert(xAxis); REQUIRE(resultX.x == Approx(1.0f)); REQUIRE(resultX.y == Approx(0.0f)); REQUIRE(resultX.z == Approx(0.0f)); Vector<3, float, glm::defaultp> yAxis(0.0f, 1.0f, 0.0f); auto resultY = converter.Convert(yAxis); REQUIRE(resultY.x == Approx(0.0f)); REQUIRE(resultY.z == Approx(1.0f)); Vector<3, float, glm::defaultp> zAxis(0.0f, 0.0f, 1.0f); auto resultZ = converter.Convert(zAxis); REQUIRE(resultZ.y == Approx(-1.0f).margin(0.0001)); } SECTION("Right-handed Z-up to Right-handed Y-up - Unit vectors") { CoordinateSystemConverter converter( CoordinateSystem::RIGHT_HANDED_Z_UP, CoordinateSystem::RIGHT_HANDED_Y_UP ); // RH Z-up: X=right, Y=forward, Z=up // RH Y-up: X=right, Y=up, Z=backward // Expected transformation: X->X, Y->-Z, Z->Y Vector<3, float, glm::defaultp> xAxis(1.0f, 0.0f, 0.0f); auto resultX = converter.Convert(xAxis); REQUIRE(resultX.x == Approx(1.0f)); REQUIRE(resultX.y == Approx(0.0f)); REQUIRE(resultX.z == Approx(0.0f)); Vector<3, float, glm::defaultp> yAxis(0.0f, 1.0f, 0.0f); auto resultY = converter.Convert(yAxis); REQUIRE(abs(resultY.z) == Approx(1.0f).margin(0.0001)); Vector<3, float, glm::defaultp> zAxis(0.0f, 0.0f, 1.0f); auto resultZ = converter.Convert(zAxis); REQUIRE(resultZ.y == Approx(1.0f)); } SECTION("Left-handed Y-up to Left-handed Z-up - Unit vectors") { CoordinateSystemConverter converter( CoordinateSystem::LEFT_HANDED_Y_UP, CoordinateSystem::LEFT_HANDED_Z_UP ); // LH Y-up: X=right, Y=up, Z=forward (away from camera) // LH Z-up: X=right, Y=backward, Z=up (or similar) Vector<3, float, glm::defaultp> xAxis(1.0f, 0.0f, 0.0f); auto resultX = converter.Convert(xAxis); REQUIRE(resultX.x == Approx(1.0f)); Vector<3, float, glm::defaultp> yAxis(0.0f, 1.0f, 0.0f); auto resultY = converter.Convert(yAxis); REQUIRE(abs(resultY.z) == Approx(1.0f).margin(0.0001)); } } TEST_CASE("CoordinateSystemConverter - Handedness Changes", "[CoordinateSystemConverter]") { SECTION("Right-handed Y-up to Left-handed Y-up") { CoordinateSystemConverter converter( CoordinateSystem::RIGHT_HANDED_Y_UP, CoordinateSystem::LEFT_HANDED_Y_UP ); // Handedness change typically negates one axis (usually Z) // RH Y-up: Z points backward, LH Y-up: Z points forward Vector<3, float, glm::defaultp> v(1.0f, 2.0f, 3.0f); auto result = converter.Convert(v); // X and Y should remain the same or similar REQUIRE(result.x == Approx(1.0f)); REQUIRE(result.y == Approx(2.0f)); // Z should be negated REQUIRE(result.z == Approx(-3.0f)); } SECTION("Right-handed Z-up to Left-handed Z-up") { CoordinateSystemConverter converter( CoordinateSystem::RIGHT_HANDED_Z_UP, CoordinateSystem::LEFT_HANDED_Z_UP ); Vector<3, float, glm::defaultp> v(1.0f, 2.0f, 3.0f); auto result = converter.Convert(v); // One axis should be negated for handedness change REQUIRE(result.x == Approx(1.0f)); REQUIRE(result.y == Approx(-2.0f)); REQUIRE(result.z == Approx(3.0f)); } SECTION("Left-handed Y-up to Right-handed Y-up") { CoordinateSystemConverter converter( CoordinateSystem::LEFT_HANDED_Y_UP, CoordinateSystem::RIGHT_HANDED_Y_UP ); Vector<3, float, glm::defaultp> v(1.0f, 2.0f, 3.0f); auto result = converter.Convert(v); REQUIRE(result.x == Approx(1.0f)); REQUIRE(result.y == Approx(2.0f)); REQUIRE(result.z == Approx(-3.0f)); } } TEST_CASE("CoordinateSystemConverter - Vec3 Length Preservation", "[CoordinateSystemConverter]") { SECTION("Vector length should be preserved across all conversions") { auto fromSys = GENERATE( CoordinateSystem::RIGHT_HANDED_Y_UP, CoordinateSystem::RIGHT_HANDED_Z_UP, CoordinateSystem::LEFT_HANDED_Y_UP, CoordinateSystem::LEFT_HANDED_Z_UP ); auto toSys = GENERATE( CoordinateSystem::RIGHT_HANDED_Y_UP, CoordinateSystem::RIGHT_HANDED_Z_UP, CoordinateSystem::LEFT_HANDED_Y_UP, CoordinateSystem::LEFT_HANDED_Z_UP ); CoordinateSystemConverter converter(fromSys, toSys); Vector<3, float, glm::defaultp> v(3.0f, 4.0f, 5.0f); float originalLength = glm::length(v); auto result = converter.Convert(v); float resultLength = glm::length(result); REQUIRE(resultLength == Approx(originalLength)); } } TEST_CASE("CoordinateSystemConverter - Vec4 Conversions", "[CoordinateSystemConverter]") { SECTION("Vec4 w-component should be preserved") { CoordinateSystemConverter converter( CoordinateSystem::RIGHT_HANDED_Y_UP, CoordinateSystem::RIGHT_HANDED_Z_UP ); Vector<4, float, glm::defaultp> point(1.0f, 2.0f, 3.0f, 1.0f); auto result = converter.Convert(point); REQUIRE(result.w == Approx(1.0f)); } SECTION("Vec4 direction vector (w=0)") { CoordinateSystemConverter converter( CoordinateSystem::RIGHT_HANDED_Y_UP, CoordinateSystem::LEFT_HANDED_Y_UP ); Vector<4, float, glm::defaultp> direction(1.0f, 2.0f, 3.0f, 0.0f); auto result = converter.Convert(direction); REQUIRE(result.w == Approx(0.0f)); REQUIRE(result.x == Approx(1.0f)); REQUIRE(result.y == Approx(2.0f)); REQUIRE(result.z == Approx(-3.0f)); } } TEST_CASE("CoordinateSystemConverter - Matrix3 Conversions", "[CoordinateSystemConverter]") { SECTION("Identity matrix through different coordinate systems") { CoordinateSystemConverter converter( CoordinateSystem::RIGHT_HANDED_Y_UP, CoordinateSystem::RIGHT_HANDED_Z_UP ); Matrix<3, float, glm::defaultp> identity(1.0f); auto result = converter.Convert(identity); // Each column represents a basis vector transformation // Column 0 is X axis, Column 1 is Y axis, Column 2 is Z axis REQUIRE(result[0][0] == Approx(1.0f)); // X.x REQUIRE(result[1][0] == Approx(0.0f)); // X.y REQUIRE(result[2][0] == Approx(0.0f)); // X.z } SECTION("Matrix3 basis vector conversion") { CoordinateSystemConverter converter( CoordinateSystem::RIGHT_HANDED_Y_UP, CoordinateSystem::LEFT_HANDED_Y_UP ); Matrix<3, float, glm::defaultp> mat(1.0f); auto result = converter.Convert(mat); // When converting handedness, determinant sign should flip float detOriginal = glm::determinant(mat); float detResult = glm::determinant(result); REQUIRE(detOriginal * detResult == Approx(-1.0f)); } SECTION("Matrix3 with rotation around Y axis") { CoordinateSystemConverter converter( CoordinateSystem::RIGHT_HANDED_Y_UP, CoordinateSystem::RIGHT_HANDED_Z_UP ); // Simple 90-degree rotation around Y axis in RH Y-up Matrix<3, float, glm::defaultp> rotY( 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, -1.0f, 0.0f, 0.0f ); auto result = converter.Convert(rotY); // The transformation should maintain the rotation semantics INFO("Rotation matrix converted between coordinate systems"); } } TEST_CASE("CoordinateSystemConverter - Matrix4 Conversions", "[CoordinateSystemConverter]") { SECTION("Matrix4 translation component conversion") { CoordinateSystemConverter converter( CoordinateSystem::RIGHT_HANDED_Y_UP, CoordinateSystem::RIGHT_HANDED_Z_UP ); Matrix<4, float, glm::defaultp> transform(1.0f); transform[3][0] = 10.0f; transform[3][1] = 20.0f; transform[3][2] = 30.0f; auto result = converter.Convert(transform); // Translation should convert like a position vector Vector<3, float, glm::defaultp> translation(10.0f, 20.0f, 30.0f); Vector<3, float, glm::defaultp> expectedTranslation = converter.Convert(translation); REQUIRE(result[3][0] == Approx(expectedTranslation.x)); REQUIRE(result[3][1] == Approx(expectedTranslation.y)); REQUIRE(result[3][2] == Approx(expectedTranslation.z)); } SECTION("Matrix4 homogeneous row preservation") { CoordinateSystemConverter converter( CoordinateSystem::LEFT_HANDED_Y_UP, CoordinateSystem::RIGHT_HANDED_Z_UP ); Matrix<4, float, glm::defaultp> mat(1.0f); auto result = converter.Convert(mat); // Bottom row should remain [0,0,0,1] REQUIRE(result[0][3] == Approx(0.0f)); REQUIRE(result[1][3] == Approx(0.0f)); REQUIRE(result[2][3] == Approx(0.0f)); REQUIRE(result[3][3] == Approx(1.0f)); } } TEST_CASE("CoordinateSystemConverter - Round-trip Conversions", "[CoordinateSystemConverter]") { SECTION("Vec3 round-trip: RH Y-up -> RH Z-up -> RH Y-up") { CoordinateSystemConverter toZUp( CoordinateSystem::RIGHT_HANDED_Y_UP, CoordinateSystem::RIGHT_HANDED_Z_UP ); CoordinateSystemConverter toYUp( CoordinateSystem::RIGHT_HANDED_Z_UP, CoordinateSystem::RIGHT_HANDED_Y_UP ); Vector<3, float, glm::defaultp> original(1.5f, 2.5f, 3.5f); auto intermediate = toZUp.Convert(original); auto result = toYUp.Convert(intermediate); REQUIRE_VEC_CLOSE(result, original); } SECTION("Vec3 round-trip: RH Y-up -> LH Y-up -> RH Y-up") { CoordinateSystemConverter toLH( CoordinateSystem::RIGHT_HANDED_Y_UP, CoordinateSystem::LEFT_HANDED_Y_UP ); CoordinateSystemConverter toRH( CoordinateSystem::LEFT_HANDED_Y_UP, CoordinateSystem::RIGHT_HANDED_Y_UP ); Vector<3, float, glm::defaultp> original(1.5f, 2.5f, 3.5f); auto intermediate = toLH.Convert(original); auto result = toRH.Convert(intermediate); REQUIRE_VEC_CLOSE(result, original); } SECTION("Vec4 round-trip: LH Z-up -> RH Y-up -> LH Z-up") { CoordinateSystemConverter conv1( CoordinateSystem::LEFT_HANDED_Z_UP, CoordinateSystem::RIGHT_HANDED_Y_UP ); CoordinateSystemConverter conv2( CoordinateSystem::RIGHT_HANDED_Y_UP, CoordinateSystem::LEFT_HANDED_Z_UP ); Vector<4, float, glm::defaultp> original(1.5f, 2.5f, 3.5f, 0.75f); auto intermediate = conv1.Convert(original); auto result = conv2.Convert(intermediate); REQUIRE_VEC_CLOSE(result, original); } SECTION("Mat3 round-trip: RH Z-up -> LH Y-up -> RH Z-up") { CoordinateSystemConverter conv1( CoordinateSystem::RIGHT_HANDED_Z_UP, CoordinateSystem::LEFT_HANDED_Y_UP ); CoordinateSystemConverter conv2( CoordinateSystem::LEFT_HANDED_Y_UP, CoordinateSystem::RIGHT_HANDED_Z_UP ); Matrix<3, float, glm::defaultp> original( 1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f, 9.0f ); auto intermediate = conv1.Convert(original); auto result = conv2.Convert(intermediate); REQUIRE_MAT_CLOSE(result, original); } SECTION("Mat4 round-trip: LH Y-up -> RH Z-up -> LH Y-up") { CoordinateSystemConverter conv1( CoordinateSystem::LEFT_HANDED_Y_UP, CoordinateSystem::RIGHT_HANDED_Z_UP ); CoordinateSystemConverter conv2( CoordinateSystem::RIGHT_HANDED_Z_UP, CoordinateSystem::LEFT_HANDED_Y_UP ); Matrix<4, float, glm::defaultp> original(1.0f); original[3][0] = 5.0f; original[3][1] = 10.0f; original[3][2] = 15.0f; auto intermediate = conv1.Convert(original); auto result = conv2.Convert(intermediate); REQUIRE_MAT_CLOSE(result, original); } } TEST_CASE("CoordinateSystemConverter - Chained Conversions", "[CoordinateSystemConverter]") { SECTION("Three-way conversion chain should be consistent") { CoordinateSystemConverter conv1( CoordinateSystem::RIGHT_HANDED_Y_UP, CoordinateSystem::RIGHT_HANDED_Z_UP ); CoordinateSystemConverter conv2( CoordinateSystem::RIGHT_HANDED_Z_UP, CoordinateSystem::LEFT_HANDED_Z_UP ); CoordinateSystemConverter conv3( CoordinateSystem::LEFT_HANDED_Z_UP, CoordinateSystem::LEFT_HANDED_Y_UP ); Vector<3, float, glm::defaultp> v(2.0f, 3.0f, 4.0f); auto step1 = conv1.Convert(v); auto step2 = conv2.Convert(step1); auto step3 = conv3.Convert(step2); // Direct conversion for comparison CoordinateSystemConverter direct( CoordinateSystem::RIGHT_HANDED_Y_UP, CoordinateSystem::LEFT_HANDED_Y_UP ); auto directResult = direct.Convert(v); // Multi-step and direct conversion should yield same result REQUIRE_VEC_CLOSE(step3, directResult); } } TEST_CASE("CoordinateSystemConverter - Edge Cases", "[CoordinateSystemConverter]") { SECTION("Zero vector conversion") { CoordinateSystemConverter converter( CoordinateSystem::RIGHT_HANDED_Y_UP, CoordinateSystem::LEFT_HANDED_Z_UP ); Vector<3, float, glm::defaultp> zero(0.0f, 0.0f, 0.0f); auto result = converter.Convert(zero); REQUIRE_VEC_CLOSE(result, zero); } SECTION("Negative components") { CoordinateSystemConverter converter( CoordinateSystem::RIGHT_HANDED_Y_UP, CoordinateSystem::LEFT_HANDED_Y_UP ); Vector<3, float, glm::defaultp> negative(-1.0f, -2.0f, -3.0f); auto result = converter.Convert(negative); REQUIRE(result.x == Approx(-1.0f)); REQUIRE(result.y == Approx(-2.0f)); REQUIRE(result.z == Approx(3.0f)); // Z negated for handedness change } SECTION("Very small values") { CoordinateSystemConverter converter( CoordinateSystem::RIGHT_HANDED_Z_UP, CoordinateSystem::LEFT_HANDED_Y_UP ); Vector<3, float, glm::defaultp> small(0.0001f, 0.0002f, 0.0003f); auto result = converter.Convert(small); float originalLength = glm::length(small); float resultLength = glm::length(result); REQUIRE(resultLength == Approx(originalLength)); } SECTION("Double precision support") { CoordinateSystemConverter converter( CoordinateSystem::RIGHT_HANDED_Y_UP, CoordinateSystem::RIGHT_HANDED_Z_UP ); Vector<3, double, glm::defaultp> v(1.0, 2.0, 3.0); auto result = converter.Convert(v); double originalLength = glm::length(v); double resultLength = glm::length(result); REQUIRE(resultLength == Approx(originalLength)); } } TEST_CASE("CoordinateSystemConverter - Matrix Column Interpretation", "[CoordinateSystemConverter]") { SECTION("Matrix columns should convert as vectors") { CoordinateSystemConverter converter( CoordinateSystem::RIGHT_HANDED_Y_UP, CoordinateSystem::RIGHT_HANDED_Z_UP ); Matrix<3, float, glm::defaultp> mat( 1.0f, 0.0f, 0.0f, 0.0f, 2.0f, 0.0f, 0.0f, 0.0f, 3.0f ); auto result = converter.Convert(mat); // Each column should convert like a vector Vector<3, float, glm::defaultp> col0(mat[0][0], mat[0][1], mat[0][2]); Vector<3, float, glm::defaultp> expectedCol0 = converter.Convert(col0); REQUIRE(result[0][0] == Approx(expectedCol0.x)); REQUIRE(result[0][1] == Approx(expectedCol0.y)); REQUIRE(result[0][2] == Approx(expectedCol0.z)); } } TEST_CASE("CoordinateSystemConverter - Full 8-system conversions", "[CoordinateSystemConverter][Exhaustive]") { auto fromSys = GENERATE( CoordinateSystem::RIGHT_HANDED_Y_UP, CoordinateSystem::RIGHT_HANDED_Z_UP, CoordinateSystem::LEFT_HANDED_Y_UP, CoordinateSystem::LEFT_HANDED_Z_UP ); auto toSys = GENERATE( CoordinateSystem::RIGHT_HANDED_Y_UP, CoordinateSystem::RIGHT_HANDED_Z_UP, CoordinateSystem::LEFT_HANDED_Y_UP, CoordinateSystem::LEFT_HANDED_Z_UP ); CoordinateSystemConverter converter(fromSys, toSys); // Random non-zero vector Vector<3, float, glm::defaultp> v(1.23f, -4.56f, 7.89f); auto result = converter.Convert(v); // Length should be preserved REQUIRE(glm::length(result) == Approx(glm::length(v))); // Check components are finite REQUIRE(std::isfinite(result.x)); REQUIRE(std::isfinite(result.y)); REQUIRE(std::isfinite(result.z)); } TEST_CASE("CoordinateSystemConverter - Vec4 arbitrary w-component", "[CoordinateSystemConverter][Vec4]") { CoordinateSystemConverter converter( CoordinateSystem::RIGHT_HANDED_Y_UP, CoordinateSystem::LEFT_HANDED_Z_UP ); Vector<4, float, glm::defaultp> v(2.0f, -3.0f, 4.0f, 0.5f); auto result = converter.Convert(v); // w should remain unchanged REQUIRE(result.w == Approx(0.5f)); // Check vector part length preservation REQUIRE(glm::length(glm::vec3(result.x, result.y, result.z)) == Approx(glm::length(glm::vec3(2.0f, -3.0f, 4.0f)))); } TEST_CASE("CoordinateSystemConverter - Very large / small / NaN / Inf values", "[CoordinateSystemConverter][Edge]") { CoordinateSystemConverter converter( CoordinateSystem::RIGHT_HANDED_Y_UP, CoordinateSystem::LEFT_HANDED_Y_UP ); Vector<3, float, glm::defaultp> largeVec(1e30f, -1e30f, 1e30f); Vector<3, float, glm::defaultp> smallVec(1e-30f, -1e-30f, 1e-30f); Vector<3, float, glm::defaultp> nanVec(NAN, 0.0f, 0.0f); Vector<3, float, glm::defaultp> infVec(INFINITY, -INFINITY, 1.0f); auto rLarge = converter.Convert(largeVec); auto rSmall = converter.Convert(smallVec); auto rNan = converter.Convert(nanVec); auto rInf = converter.Convert(infVec); // Length preservation for large/small REQUIRE(glm::length(rLarge) == Approx(glm::length(largeVec))); REQUIRE(glm::length(rSmall) == Approx(glm::length(smallVec))); // NaN and Inf should propagate REQUIRE(std::isnan(rNan.x)); REQUIRE(std::isinf(rInf.x)); } TEST_CASE("CoordinateSystemConverter - Mat4 rotation and scale", "[CoordinateSystemConverter][Mat4]") { CoordinateSystemConverter converter( CoordinateSystem::RIGHT_HANDED_Y_UP, CoordinateSystem::LEFT_HANDED_Z_UP ); // Non-uniform scale + rotation glm::mat4 mat(1.0f); mat[0][0] = 2.0f; mat[1][1] = 3.0f; mat[2][2] = 4.0f; // scaling mat = glm::rotate(mat, glm::radians(90.0f), glm::vec3(0,1,0)); // rotation around Y axis auto result = converter.Convert(mat); // Check that determinant magnitude is preserved (handedness may flip sign) float detOrig = glm::determinant(mat); float detResult = glm::determinant(result); REQUIRE(std::abs(detResult) == Approx(std::abs(detOrig))); // Check translation row unchanged if identity REQUIRE(result[3][3] == Approx(1.0f)); } TEST_CASE("CoordinateSystemConverter - Matrix4 round-trip consistency", "[CoordinateSystemConverter][Mat4]") { CoordinateSystemConverter conv1( CoordinateSystem::RIGHT_HANDED_Y_UP, CoordinateSystem::LEFT_HANDED_Z_UP ); CoordinateSystemConverter conv2( CoordinateSystem::LEFT_HANDED_Z_UP, CoordinateSystem::RIGHT_HANDED_Y_UP ); glm::mat4 original(1.0f); original[3] = glm::vec4(5.0f, -10.0f, 15.0f, 1.0f); original[0][0] = 2.0f; original[1][1] = 3.0f; original[2][2] = 4.0f; // scale auto intermediate = conv1.Convert(original); auto roundTrip = conv2.Convert(intermediate); // All elements should match original for (int c = 0; c < 4; ++c) for (int r = 0; r < 4; ++r) REQUIRE(roundTrip[c][r] == Approx(original[c][r])); } TEST_CASE("CoordinateSystemConverter - Vec3 all-zero / negative / mixed", "[CoordinateSystemConverter][Edge]") { CoordinateSystemConverter converter( CoordinateSystem::LEFT_HANDED_Y_UP, CoordinateSystem::RIGHT_HANDED_Z_UP ); Vector<3, float, glm::defaultp> zero(0.0f, 0.0f, 0.0f); Vector<3, float, glm::defaultp> neg(-1.0f, -2.0f, -3.0f); Vector<3, float, glm::defaultp> mixed(-1.0f, 0.0f, 2.0f); REQUIRE_VEC_CLOSE(converter.Convert(zero), zero); REQUIRE(glm::length(converter.Convert(neg)) == Approx(glm::length(neg))); REQUIRE(glm::length(converter.Convert(mixed)) == Approx(glm::length(mixed))); }