Add coordinate system converter

This commit is contained in:
Georg Hagen
2025-10-21 15:19:26 +02:00
parent e6e2cdbe80
commit 76440bb739
2 changed files with 989 additions and 0 deletions

View File

@@ -0,0 +1,885 @@
/*
* 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 <catch2/catch_all.hpp>
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)));
}