From c10d9b2a8ee134fb5e72de1a0f26f8c9cc8f6382 Mon Sep 17 00:00:00 2001 From: Jorrit Rouwe Date: Fri, 6 Dec 2024 16:23:34 +0100 Subject: [PATCH] Improving unit test coverage for Math functions (#1380) --- Jolt/Math/Math.h | 2 +- UnitTests/Math/BVec16Tests.cpp | 1 + UnitTests/Math/DMat44Tests.cpp | 79 ++++++++++++++++++++++++++++++++++ UnitTests/Math/DVec3Tests.cpp | 24 +++++++++++ UnitTests/Math/Mat44Tests.cpp | 46 ++++++++++++++++++++ UnitTests/Math/MathTests.cpp | 28 ++++++++++++ UnitTests/Math/QuatTests.cpp | 37 ++++++++++++++++ UnitTests/Math/UVec4Tests.cpp | 17 ++++++++ UnitTests/Math/Vec3Tests.cpp | 17 ++++++++ UnitTests/Math/Vec4Tests.cpp | 7 +++ 10 files changed, 257 insertions(+), 1 deletion(-) diff --git a/Jolt/Math/Math.h b/Jolt/Math/Math.h index 729d5403e..b83183a32 100644 --- a/Jolt/Math/Math.h +++ b/Jolt/Math/Math.h @@ -72,7 +72,7 @@ JPH_INLINE constexpr T Sign(T inV) template constexpr bool IsPowerOf2(T inV) { - return (inV & (inV - 1)) == 0; + return inV > 0 && (inV & (inV - 1)) == 0; } /// Align inV up to the next inAlignment bytes diff --git a/UnitTests/Math/BVec16Tests.cpp b/UnitTests/Math/BVec16Tests.cpp index fade9b678..9e96693dd 100644 --- a/UnitTests/Math/BVec16Tests.cpp +++ b/UnitTests/Math/BVec16Tests.cpp @@ -35,6 +35,7 @@ TEST_SUITE("BVec16Tests") CHECK(v != BVec16(1, 2, 3, 4, 5, 6, 7, 8, 10, 9, 11, 12, 13, 14, 15, 16)); // Check element modification + CHECK(const_cast(v)[15] == 16); // Check const operator v[15] = 17; CHECK(v[15] == 17); CHECK(v == BVec16(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 17)); diff --git a/UnitTests/Math/DMat44Tests.cpp b/UnitTests/Math/DMat44Tests.cpp index 776df538e..50c435195 100644 --- a/UnitTests/Math/DMat44Tests.cpp +++ b/UnitTests/Math/DMat44Tests.cpp @@ -4,6 +4,7 @@ #include "UnitTestFramework.h" #include +#include TEST_SUITE("DMat44Tests") { @@ -11,6 +12,7 @@ TEST_SUITE("DMat44Tests") { DMat44 zero = DMat44::sZero(); + CHECK(zero == DMat44(Vec4(0, 0, 0, 0), Vec4(0, 0, 0, 0), Vec4(0, 0, 0, 0), DVec3(0, 0, 0))); CHECK(zero.GetAxisX() == Vec3::sZero()); CHECK(zero.GetAxisY() == Vec3::sZero()); CHECK(zero.GetAxisZ() == Vec3::sZero()); @@ -22,6 +24,12 @@ TEST_SUITE("DMat44Tests") DMat44 identity = DMat44::sIdentity(); CHECK(identity == DMat44(Vec4(1, 0, 0, 0), Vec4(0, 1, 0, 0), Vec4(0, 0, 1, 0), DVec3(0, 0, 0))); + + // Check non-equality + CHECK(identity != DMat44(Vec4(0, 0, 0, 0), Vec4(0, 1, 0, 0), Vec4(0, 0, 1, 0), DVec3(0, 0, 0))); + CHECK(identity != DMat44(Vec4(1, 0, 0, 0), Vec4(0, 0, 0, 0), Vec4(0, 0, 1, 0), DVec3(0, 0, 0))); + CHECK(identity != DMat44(Vec4(1, 0, 0, 0), Vec4(0, 1, 0, 0), Vec4(0, 0, 0, 0), DVec3(0, 0, 0))); + CHECK(identity != DMat44(Vec4(1, 0, 0, 0), Vec4(0, 1, 0, 0), Vec4(0, 0, 1, 0), DVec3(1, 0, 0))); } TEST_CASE("TestDMat44Construct") @@ -61,6 +69,23 @@ TEST_SUITE("DMat44Tests") CHECK(mat == DMat44(Vec4(17, 18, 19, 20), Vec4(21, 22, 23, 24), Vec4(25, 26, 27, 28), DVec3(13, 14, 15))); } + TEST_CASE("TestDMat44Rotation") + { + Quat q = Quat::sRotation(Vec3(1, 1, 1).Normalized(), 0.2f * JPH_PI); + CHECK(DMat44::sRotation(q).ToMat44() == Mat44::sRotation(q)); + } + + TEST_CASE("TestDMat44Translation") + { + CHECK(DMat44::sTranslation(DVec3(1, 2, 3)) == DMat44(Vec4(1, 0, 0, 0), Vec4(0, 1, 0, 0), Vec4(0, 0, 1, 0), DVec3(1, 2, 3))); + } + + TEST_CASE("TestDMat44RotationTranslation") + { + Quat q = Quat::sRotation(Vec3(1, 1, 1).Normalized(), 0.2f * JPH_PI); + CHECK(DMat44::sRotationTranslation(q, DVec3(1, 2, 3)).ToMat44() == Mat44::sRotationTranslation(q, Vec3(1, 2, 3))); + } + TEST_CASE("TestDMat44MultiplyMat44") { DMat44 mat(Vec4(1, 2, 3, 0), Vec4(5, 6, 7, 0), Vec4(9, 10, 11, 0), DVec3(13, 14, 15)); @@ -164,4 +189,58 @@ TEST_SUITE("DMat44Tests") CHECK_APPROX_EQUAL(rotation_translation, m2); CHECK_APPROX_EQUAL(scale, scale_out); } + + TEST_CASE("TestDMat44ToMat44") + { + DMat44 mat(Vec4(1, 2, 3, 4), Vec4(5, 6, 7, 8), Vec4(9, 10, 11, 12), DVec3(13, 14, 15)); + CHECK(mat.ToMat44() == Mat44(Vec4(1, 2, 3, 4), Vec4(5, 6, 7, 8), Vec4(9, 10, 11, 12), Vec4(13, 14, 15, 1))); + } + + TEST_CASE("TestDMat44Column") + { + DMat44 mat = DMat44::sZero(); + mat.SetColumn4(0, Vec4(1, 2, 3, 4)); + CHECK(mat.GetColumn4(0) == Vec4(1, 2, 3, 4)); + mat.SetColumn3(0, Vec3(5, 6, 7)); + CHECK(mat.GetColumn3(0) == Vec3(5, 6, 7)); + CHECK(mat.GetColumn4(0) == Vec4(5, 6, 7, 0)); + + mat.SetAxisX(Vec3(8, 9, 10)); + mat.SetAxisY(Vec3(11, 12, 13)); + mat.SetAxisZ(Vec3(14, 15, 16)); + mat.SetTranslation(DVec3(17, 18, 19)); + CHECK(mat.GetAxisX() == Vec3(8, 9, 10)); + CHECK(mat.GetAxisY() == Vec3(11, 12, 13)); + CHECK(mat.GetAxisZ() == Vec3(14, 15, 16)); + CHECK(mat.GetTranslation() == DVec3(17, 18, 19)); + } + + TEST_CASE("TestDMat44Transposed") + { + DMat44 mat(Vec4(1, 2, 3, 4), Vec4(5, 6, 7, 8), Vec4(9, 10, 11, 12), DVec3(13, 14, 15)); + Mat44 result = mat.Transposed3x3(); + CHECK(result == Mat44(Vec4(1, 5, 9, 0), Vec4(2, 6, 10, 0), Vec4(3, 7, 11, 0), Vec4(0, 0, 0, 1))); + } + + TEST_CASE("TestDMat44GetQuaternion") + { + Quat rot = Quat::sRotation(Vec3(1, 1, 1).Normalized(), 0.2f * JPH_PI); + DMat44 mat = DMat44::sRotation(rot); + CHECK_APPROX_EQUAL(mat.GetQuaternion(), rot); + } + + TEST_CASE("TestDMat44PrePostTranslated") + { + DMat44 m(Vec4(2, 3, 4, 0), Vec4(5, 6, 7, 0), Vec4(8, 9, 10, 0), DVec3(11, 12, 13)); + DVec3 v(14, 15, 16); + + CHECK(m.PreTranslated(v) == m * DMat44::sTranslation(v)); + CHECK(m.PostTranslated(v) == DMat44::sTranslation(v) * m); + } + + TEST_CASE("TestDMat44ConvertToString") + { + DMat44 v(Vec4(1, 2, 3, 4), Vec4(5, 6, 7, 8), Vec4(9, 10, 11, 12), DVec3(13, 14, 15)); + CHECK(ConvertToString(v) == "1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15"); + } } diff --git a/UnitTests/Math/DVec3Tests.cpp b/UnitTests/Math/DVec3Tests.cpp index a22a4e863..dadcecd19 100644 --- a/UnitTests/Math/DVec3Tests.cpp +++ b/UnitTests/Math/DVec3Tests.cpp @@ -4,6 +4,7 @@ #include "UnitTestFramework.h" #include +#include TEST_SUITE("DVec3Tests") { @@ -16,6 +17,13 @@ TEST_SUITE("DVec3Tests") CHECK(v.GetZ() == 0); } + TEST_CASE("TestDVec3Axis") + { + CHECK(DVec3::sAxisX() == DVec3(1, 0, 0)); + CHECK(DVec3::sAxisY() == DVec3(0, 1, 0)); + CHECK(DVec3::sAxisZ() == DVec3(0, 0, 1)); + } + TEST_CASE("TestVec3NaN") { DVec3 v = DVec3::sNaN(); @@ -56,6 +64,16 @@ TEST_SUITE("DVec3Tests") v.SetComponent(1, 5); v.SetComponent(2, 6); CHECK(v == DVec3(4, 5, 6)); + + // Set the components again + v.SetX(7); + v.SetY(8); + v.SetZ(9); + CHECK(v == DVec3(7, 8, 9)); + + // Set all components + v.Set(10, 11, 12); + CHECK(v == DVec3(10, 11, 12)); } TEST_CASE("TestVec4ToDVec3") @@ -281,4 +299,10 @@ TEST_SUITE("DVec3Tests") CHECK(DVec3(1.2345, -6.7891, 0).GetSign() == DVec3(1, -1, 1)); CHECK(DVec3(0, 2.3456, -7.8912).GetSign() == DVec3(1, 1, -1)); } + + TEST_CASE("TestDVec3ConvertToString") + { + DVec3 v(1, 2, 3); + CHECK(ConvertToString(v) == "1, 2, 3"); + } } diff --git a/UnitTests/Math/Mat44Tests.cpp b/UnitTests/Math/Mat44Tests.cpp index f9e9d21a2..8c62f6e6c 100644 --- a/UnitTests/Math/Mat44Tests.cpp +++ b/UnitTests/Math/Mat44Tests.cpp @@ -4,6 +4,7 @@ #include "UnitTestFramework.h" #include +#include TEST_SUITE("Mat44Tests") { @@ -16,6 +17,38 @@ TEST_SUITE("Mat44Tests") CHECK(zero(row, col) == 0.0f); } + TEST_CASE("TestMat44Column") + { + Mat44 mat = Mat44::sZero(); + mat.SetColumn4(0, Vec4(1, 2, 3, 4)); + CHECK(mat.GetColumn4(0) == Vec4(1, 2, 3, 4)); + mat.SetColumn3(0, Vec3(5, 6, 7)); + CHECK(mat.GetColumn3(0) == Vec3(5, 6, 7)); + CHECK(mat.GetColumn4(0) == Vec4(5, 6, 7, 0)); + + mat.SetAxisX(Vec3(8, 9, 10)); + mat.SetAxisY(Vec3(11, 12, 13)); + mat.SetAxisZ(Vec3(14, 15, 16)); + mat.SetTranslation(Vec3(17, 18, 19)); + CHECK(mat.GetAxisX() == Vec3(8, 9, 10)); + CHECK(mat.GetAxisY() == Vec3(11, 12, 13)); + CHECK(mat.GetAxisZ() == Vec3(14, 15, 16)); + CHECK(mat.GetTranslation() == Vec3(17, 18, 19)); + + mat.SetDiagonal3(Vec3(20, 21, 22)); + CHECK(mat.GetDiagonal3() == Vec3(20, 21, 22)); + CHECK(mat.GetAxisX() == Vec3(20, 9, 10)); + CHECK(mat.GetAxisY() == Vec3(11, 21, 13)); + CHECK(mat.GetAxisZ() == Vec3(14, 15, 22)); + + mat.SetDiagonal4(Vec4(23, 24, 25, 26)); + CHECK(mat.GetDiagonal4() == Vec4(23, 24, 25, 26)); + CHECK(mat.GetAxisX() == Vec3(23, 9, 10)); + CHECK(mat.GetAxisY() == Vec3(11, 24, 13)); + CHECK(mat.GetAxisZ() == Vec3(14, 15, 25)); + CHECK(mat.GetColumn4(3) == Vec4(17, 18, 19, 26)); + } + TEST_CASE("TestMat44NaN") { Mat44 nan = Mat44::sNaN(); @@ -521,4 +554,17 @@ TEST_SUITE("Mat44Tests") CHECK_APPROX_EQUAL(m2.GetAxisX().Cross(m2.GetAxisY()).Dot(m2.GetAxisZ()), 1.0f); // Check perpendicular CHECK_APPROX_EQUAL(scale, scale_out, 0.05f); // Scale may change a bit } + + TEST_CASE("TestDMat44GetQuaternion") + { + Quat rot = Quat::sRotation(Vec3(1, 1, 1).Normalized(), 0.2f * JPH_PI); + Mat44 mat = Mat44::sRotation(rot); + CHECK_APPROX_EQUAL(mat.GetQuaternion(), rot); + } + + TEST_CASE("TestDMat44ConvertToString") + { + Mat44 v(Vec4(1, 2, 3, 4), Vec4(5, 6, 7, 8), Vec4(9, 10, 11, 12), Vec4(13, 14, 15, 16)); + CHECK(ConvertToString(v) == "1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16"); + } } diff --git a/UnitTests/Math/MathTests.cpp b/UnitTests/Math/MathTests.cpp index cd430764e..cb8b4300e 100644 --- a/UnitTests/Math/MathTests.cpp +++ b/UnitTests/Math/MathTests.cpp @@ -53,4 +53,32 @@ TEST_SUITE("Mat44Tests") CHECK(GetNextPowerOf2(0x8000000U - 1) == 0x8000000U); CHECK(GetNextPowerOf2(0x8000000U) == 0x8000000U); } + + TEST_CASE("TestCenterAngleAroundZero") + { + for (int i = 0; i < 10; i += 2) + { + CHECK_APPROX_EQUAL(CenterAngleAroundZero(i * JPH_PI), 0, 1.0e-5f); + CHECK_APPROX_EQUAL(CenterAngleAroundZero((0.5f + i) * JPH_PI), 0.5f * JPH_PI, 1.0e-5f); + CHECK_APPROX_EQUAL(CenterAngleAroundZero((1.5f + i) * JPH_PI), -0.5f * JPH_PI, 1.0e-5f); + CHECK_APPROX_EQUAL(CenterAngleAroundZero(-(0.5f + i) * JPH_PI), -0.5f * JPH_PI, 1.0e-5f); + CHECK_APPROX_EQUAL(CenterAngleAroundZero(-(1.5f + i) * JPH_PI), 0.5f * JPH_PI, 1.0e-5f); + CHECK_APPROX_EQUAL(CenterAngleAroundZero(-(0.99f + i) * JPH_PI), -0.99f * JPH_PI, 1.0e-5f); + CHECK_APPROX_EQUAL(CenterAngleAroundZero((0.99f + i) * JPH_PI), 0.99f * JPH_PI, 1.0e-5f); + } + } + + TEST_CASE("TestIsPowerOf2") + { + for (int i = 0; i < 63; ++i) + CHECK(IsPowerOf2(uint64(1) << 1)); + CHECK(!IsPowerOf2(-2)); + CHECK(!IsPowerOf2(0)); + CHECK(!IsPowerOf2(3)); + CHECK(!IsPowerOf2(5)); + CHECK(!IsPowerOf2(15)); + CHECK(!IsPowerOf2(17)); + CHECK(!IsPowerOf2(65535)); + CHECK(!IsPowerOf2(65537)); + } } diff --git a/UnitTests/Math/QuatTests.cpp b/UnitTests/Math/QuatTests.cpp index a5a52b6b7..89a9176fb 100644 --- a/UnitTests/Math/QuatTests.cpp +++ b/UnitTests/Math/QuatTests.cpp @@ -5,6 +5,7 @@ #include "UnitTestFramework.h" #include #include +#include #include TEST_SUITE("QuatTests") @@ -64,6 +65,16 @@ TEST_SUITE("QuatTests") CHECK(Quat(1, 2, 3, 4) * 5.0f == Quat(5, 10, 15, 20)); CHECK(5.0f * Quat(1, 2, 3, 4) == Quat(5, 10, 15, 20)); CHECK(Quat(2, 4, 6, 8) / 2.0f == Quat(1, 2, 3, 4)); + + Quat v(1, 2, 3, 4); + v += Quat(5, 6, 7, 8); + CHECK(v == Quat(6, 8, 10, 12)); + v -= Quat(4, 3, 2, 1); + CHECK(v == Quat(2, 5, 8, 11)); + v *= 2.0f; + CHECK(v == Quat(4, 10, 16, 22)); + v /= 2.0f; + CHECK(v == Quat(2, 5, 8, 11)); } TEST_CASE("TestQuatPerpendicular") @@ -342,6 +353,8 @@ TEST_SUITE("QuatTests") CHECK_APPROX_EQUAL(twist2, q2); Quat swing2 = twist2.Inversed() * swing1; CHECK_APPROX_EQUAL(swing2, Quat::sIdentity()); + + CHECK(Quat::sZero().GetTwist(Vec3::sAxisX()) == Quat::sIdentity()); } TEST_CASE("TestQuatGetRotationAngle") @@ -449,4 +462,28 @@ TEST_SUITE("QuatTests") CHECK_APPROX_EQUAL(v2t, v1t, 1.0e-5f); } } + + TEST_CASE("TestQuatConvertToString") + { + Quat v(1, 2, 3, 4); + CHECK(ConvertToString(v) == "1, 2, 3, 4"); + } + + TEST_CASE("TestQuatLERP") + { + Quat v1(1, 2, 3, 4); + Quat v2(5, 6, 7, 8); + CHECK(v1.LERP(v2, 0.25f) == Quat(2, 3, 4, 5)); + } + + TEST_CASE("TestQuatSLERP") + { + Quat v1 = Quat::sIdentity(); + Quat v2 = Quat::sRotation(Vec3::sAxisX(), 0.99f * JPH_PI); + CHECK_APPROX_EQUAL(v1.SLERP(v2, 0.25f), Quat::sRotation(Vec3::sAxisX(), 0.25f * 0.99f * JPH_PI)); + + // Check that we ignore the sign + Quat v3 = Quat(1, 2, 3, 4).Normalized(); + CHECK_APPROX_EQUAL(v3.SLERP(-v3, 0.5f), v3); + } } diff --git a/UnitTests/Math/UVec4Tests.cpp b/UnitTests/Math/UVec4Tests.cpp index 54bf889de..e89f9d0f0 100644 --- a/UnitTests/Math/UVec4Tests.cpp +++ b/UnitTests/Math/UVec4Tests.cpp @@ -3,6 +3,7 @@ // SPDX-License-Identifier: MIT #include "UnitTestFramework.h" +#include TEST_SUITE("UVec4Tests") { @@ -25,6 +26,16 @@ TEST_SUITE("UVec4Tests") CHECK(v != UVec4(1, 2, 4, 3)); } + TEST_CASE("TestUVec4Components") + { + UVec4 v(1, 2, 3, 4); + v.SetX(5); + v.SetY(6); + v.SetZ(7); + v.SetW(8); + CHECK(v == UVec4(5, 6, 7, 8)); + } + TEST_CASE("TestUVec4LoadStoreInt4") { alignas(16) uint32 i4[] = { 1, 2, 3, 4 }; @@ -544,4 +555,10 @@ TEST_SUITE("UVec4Tests") CHECK(UVec4::sSort4True(UVec4(0x00000000U, 0xffffffffU, 0xffffffffU, 0xffffffffU), UVec4(1, 2, 3, 4)) == UVec4(2, 3, 4, 4)); CHECK(UVec4::sSort4True(UVec4(0xffffffffU, 0xffffffffU, 0xffffffffU, 0xffffffffU), UVec4(1, 2, 3, 4)) == UVec4(1, 2, 3, 4)); } + + TEST_CASE("TestUVec4ConvertToString") + { + UVec4 v(1, 2, 3, 4); + CHECK(ConvertToString(v) == "1, 2, 3, 4"); + } } diff --git a/UnitTests/Math/Vec3Tests.cpp b/UnitTests/Math/Vec3Tests.cpp index 50d23ca3d..c9d172517 100644 --- a/UnitTests/Math/Vec3Tests.cpp +++ b/UnitTests/Math/Vec3Tests.cpp @@ -3,6 +3,7 @@ // SPDX-License-Identifier: MIT #include "UnitTestFramework.h" +#include TEST_SUITE("Vec3Tests") { @@ -29,6 +30,16 @@ TEST_SUITE("Vec3Tests") v.SetComponent(1, 5); v.SetComponent(2, 6); CHECK(v == Vec3(4, 5, 6)); + + // Set the components + v.SetX(7); + v.SetY(8); + v.SetZ(9); + CHECK(v == Vec3(7, 8, 9)); + + // Set all components + v.Set(10, 11, 12); + CHECK(v == Vec3(10, 11, 12)); } TEST_CASE("TestVec3LoadStoreFloat3") @@ -359,4 +370,10 @@ TEST_SUITE("Vec3Tests") } } #endif // JPH_FLOATING_POINT_EXCEPTIONS_ENABLED + + TEST_CASE("TestVec3ConvertToString") + { + Vec3 v(1, 2, 3); + CHECK(ConvertToString(v) == "1, 2, 3"); + } } diff --git a/UnitTests/Math/Vec4Tests.cpp b/UnitTests/Math/Vec4Tests.cpp index f78577126..83d8e2621 100644 --- a/UnitTests/Math/Vec4Tests.cpp +++ b/UnitTests/Math/Vec4Tests.cpp @@ -3,6 +3,7 @@ // SPDX-License-Identifier: MIT #include "UnitTestFramework.h" +#include TEST_SUITE("Vec4Tests") { @@ -724,4 +725,10 @@ TEST_SUITE("Vec4Tests") CHECK(ma < 3.0e-7); } + + TEST_CASE("TestVec4ConvertToString") + { + Vec4 v(1, 2, 3, 4); + CHECK(ConvertToString(v) == "1, 2, 3, 4"); + } }