diff --git a/HandmadeMath.h b/HandmadeMath.h index b452a5c..6293a20 100644 --- a/HandmadeMath.h +++ b/HandmadeMath.h @@ -6,8 +6,8 @@ both C and C++. ============================================================================= - CONFIG + ============================================================================= By default, all angles in Handmade Math are specified in radians. However, it can be configured to use degrees or turns instead. Use one of the following @@ -24,11 +24,13 @@ HMM_AngleDeg(degrees) HMM_AngleTurn(turns) + The definitions of these functions change depending on the default unit. + ----------------------------------------------------------------------------- Handmade Math ships with SSE (SIMD) implementations of several common - operations. To disable the use of SSE intrinsics, you must - define HANDMADE_MATH_NO_SSE before including this file: + operations. To disable the use of SSE intrinsics, you must define + HANDMADE_MATH_NO_SSE before including this file: #define HANDMADE_MATH_NO_SSE #include "HandmadeMath.h" @@ -1669,6 +1671,8 @@ static inline float HMM_DeterminantM4(HMM_Mat4 Matrix) } COVERAGE(HMM_InvGeneralM4, 1) +// Returns a general-purpose inverse of an HMM_Mat4. Note that special-purpose inverses of many transformations +// are available and will be more efficient. static inline HMM_Mat4 HMM_InvGeneralM4(HMM_Mat4 Matrix) { ASSERT_COVERED(HMM_InvGeneralM4); @@ -1698,6 +1702,9 @@ static inline HMM_Mat4 HMM_InvGeneralM4(HMM_Mat4 Matrix) */ COVERAGE(HMM_Orthographic_RH_NO, 1) +// Produces a right-handed orthographic projection matrix with Z ranging from -1 to 1 (the GL convention). +// Left, Right, Bottom, and Top specify the coordinates of their respective clipping planes. +// Near and Far specify the distances to the near and far clipping planes. static inline HMM_Mat4 HMM_Orthographic_RH_NO(float Left, float Right, float Bottom, float Top, float Near, float Far) { ASSERT_COVERED(HMM_Orthographic_RH_NO); @@ -1706,18 +1713,20 @@ static inline HMM_Mat4 HMM_Orthographic_RH_NO(float Left, float Right, float Bot Result.Elements[0][0] = 2.0f / (Right - Left); Result.Elements[1][1] = 2.0f / (Top - Bottom); + Result.Elements[2][2] = 2.0f / (Near - Far); Result.Elements[3][3] = 1.0f; Result.Elements[3][0] = (Left + Right) / (Left - Right); Result.Elements[3][1] = (Bottom + Top) / (Bottom - Top); - - Result.Elements[2][2] = 2.0f / (Near - Far); - Result.Elements[3][2] = (Far + Near) / (Near - Far); + Result.Elements[3][2] = (Near + Far) / (Near - Far); return Result; } COVERAGE(HMM_Orthographic_RH_ZO, 1) +// Produces a right-handed orthographic projection matrix with Z ranging from 0 to 1 (the DirectX convention). +// Left, Right, Bottom, and Top specify the coordinates of their respective clipping planes. +// Near and Far specify the distances to the near and far clipping planes. static inline HMM_Mat4 HMM_Orthographic_RH_ZO(float Left, float Right, float Bottom, float Top, float Near, float Far) { ASSERT_COVERED(HMM_Orthographic_RH_ZO); @@ -1726,18 +1735,20 @@ static inline HMM_Mat4 HMM_Orthographic_RH_ZO(float Left, float Right, float Bot Result.Elements[0][0] = 2.0f / (Right - Left); Result.Elements[1][1] = 2.0f / (Top - Bottom); + Result.Elements[2][2] = 1.0f / (Near - Far); Result.Elements[3][3] = 1.0f; Result.Elements[3][0] = (Left + Right) / (Left - Right); Result.Elements[3][1] = (Bottom + Top) / (Bottom - Top); - - Result.Elements[2][2] = 1.0f / (Near - Far); Result.Elements[3][2] = (Near) / (Near - Far); return Result; } COVERAGE(HMM_Orthographic_LH_NO, 1) +// Produces a left-handed orthographic projection matrix with Z ranging from -1 to 1 (the GL convention). +// Left, Right, Bottom, and Top specify the coordinates of their respective clipping planes. +// Near and Far specify the distances to the near and far clipping planes. static inline HMM_Mat4 HMM_Orthographic_LH_NO(float Left, float Right, float Bottom, float Top, float Near, float Far) { ASSERT_COVERED(HMM_Orthographic_LH_NO); @@ -1749,6 +1760,9 @@ static inline HMM_Mat4 HMM_Orthographic_LH_NO(float Left, float Right, float Bot } COVERAGE(HMM_Orthographic_LH_ZO, 1) +// Produces a left-handed orthographic projection matrix with Z ranging from 0 to 1 (the DirectX convention). +// Left, Right, Bottom, and Top specify the coordinates of their respective clipping planes. +// Near and Far specify the distances to the near and far clipping planes. static inline HMM_Mat4 HMM_Orthographic_LH_ZO(float Left, float Right, float Bottom, float Top, float Near, float Far) { ASSERT_COVERED(HMM_Orthographic_LH_ZO); @@ -1759,7 +1773,9 @@ static inline HMM_Mat4 HMM_Orthographic_LH_ZO(float Left, float Right, float Bot return Result; } -COVERAGE(HMM_InvOrthographic, 1) +COVERAGE(HMM_InvOrthographic, 1) +// Returns an inverse for the given orthographic projection matrix. Works for all orthographic +// projection matrices, regardless of handedness or NDC convention. static inline HMM_Mat4 HMM_InvOrthographic(HMM_Mat4 OrthoMatrix) { ASSERT_COVERED(HMM_InvOrthographic); @@ -1842,7 +1858,7 @@ static inline HMM_Mat4 HMM_Perspective_LH_ZO(float FOV, float AspectRatio, float } COVERAGE(HMM_InvPerspective, 1) -static inline HMM_Mat4 HMM_InvPerspective(HMM_Mat4 PerspectiveMatrix) +static inline HMM_Mat4 HMM_InvPerspective(HMM_Mat4 PerspectiveMatrix) { ASSERT_COVERED(HMM_InvPerspective); diff --git a/README.md b/README.md index a6d4d55..a632f21 100644 --- a/README.md +++ b/README.md @@ -1,22 +1,23 @@ # Handmade Math -A single-file, cross-platform, public domain game math library for both C and C++. Supports vectors, matrices, quaternions, and all the utilities you'd expect. +A single-file, cross-platform, public domain graphics math library for both C and C++. Supports vectors, matrices, quaternions, and all the utilities you'd expect. To get started, go download [the latest release](https://github.com/HandmadeMath/HandmadeMath/releases). -> If you are upgrading to version 2 of Handmade Math, save yourself some time and use our [automatic update tool](./update). +> If you are upgrading to Handmade Math 2.0, save yourself some time and use our [automatic update tool](./update). Here's what sets Handmade Math apart: - **A simple single-header library.** Just `#include "HandmadeMath.h"`. - **Supports both C and C++.** While libraries like GLM only support C++, Handmade Math supports both C and C++, with convenient overloads wherever possible. For example, C++ codebases get operator overloading, and C11 codebases get `_Generic` versions of common operations. +- **Supports all graphics APIs.** Handmade Math has left- and right-handed versions of each operation, as well as support for zero-to-one and negative-one-to-one NDC conventions. - **Swizzling, sort of.** Handmade Math's vector types use unions to provide several ways of accessing the same underlying data. For example, the components of an `HMM_Vec3` can be accessed as `XYZ`, `RGB`, or `UVW` - or subsets can be accessed like `.XY` and `.YZ`. - **Your choice of angle unit.** While Handmade Math uses radians by default, you can configure it to use degrees or [turns](https://www.computerenhance.com/p/turns-are-better-than-radians) instead. ## Usage -Simply `#include "HandmadeMath.h"`. All functions are `static inline`, so no need for an "implementation" file as with some other single-header libraries. +Simply `#include "HandmadeMath.h"`. All functions are `static inline`, so there is no need for an "implementation" file as with some other single-header libraries. A few config options are available. See the header comment in [the source](./HandmadeMath.h) for details. @@ -29,7 +30,7 @@ This library is in the public domain. You can do whatever you want with it. **Where can I contact you to ask questions?** -Feel free to make Github issues for any questions, concerns, or problems you encounter. +Feel free to make GitHub issues for any questions, concerns, or problems you encounter. **What if I don't want the `HMM_` prefix?** diff --git a/test/HandmadeTest.h b/test/HandmadeTest.h index 381bd3e..a6c9362 100644 --- a/test/HandmadeTest.h +++ b/test/HandmadeTest.h @@ -148,25 +148,35 @@ INITIALIZER(_HMT_COVERCASE_FUNCNAME_INIT(name)) { \ } \ } \ -#define HMT_EXPECT_FLOAT_EQ(_actual, _expected) { \ +#define HMT_EXPECT_FLOAT_EQ_MSG(_actual, _expected, _msg) { \ _HMT_CASE_START(); \ float actual = (_actual); \ float diff = actual - (_expected); \ if (diff < -FLT_EPSILON || FLT_EPSILON < diff) { \ _HMT_CASE_FAIL(); \ - printf("Expected %f, got %f", (_expected), actual); \ + if ((_msg)[0] == 0) { \ + printf("Expected %f, got %f", (_expected), actual); \ + } else { \ + printf("%s: Expected %f, got %f", (_msg), (_expected), actual); \ + } \ } \ -} \ +} +#define HMT_EXPECT_FLOAT_EQ(_actual, _expected) HMT_EXPECT_FLOAT_EQ_MSG(_actual, _expected, ""); -#define HMT_EXPECT_NEAR(_actual, _expected, _epsilon) { \ +#define HMT_EXPECT_NEAR_MSG(_actual, _expected, _epsilon, _msg) { \ _HMT_CASE_START(); \ float actual = (_actual); \ float diff = actual - (_expected); \ if (diff < -(_epsilon) || (_epsilon) < diff) { \ _HMT_CASE_FAIL(); \ - printf("Expected %f, got %f", (_expected), actual); \ + if ((_msg)[0] == 0) { \ + printf("Expected %f, got %f", (_expected), actual); \ + } else { \ + printf("%s: Expected %f, got %f", (_msg), (_expected), actual); \ + } \ } \ -} \ +} +#define HMT_EXPECT_NEAR(_actual, _expected, _epsilon) HMT_EXPECT_NEAR_MSG(_actual, _expected, _epsilon, ""); #define HMT_EXPECT_LT(_actual, _expected) { \ _HMT_CASE_START(); \ @@ -192,7 +202,46 @@ INITIALIZER(_HMT_COVERCASE_FUNCNAME_INIT(name)) { \ #define EXPECT_TRUE(_actual) HMT_EXPECT_TRUE(_actual) #define EXPECT_FALSE(_actual) HMT_EXPECT_FALSE(_actual) #define EXPECT_FLOAT_EQ(_actual, _expected) HMT_EXPECT_FLOAT_EQ(_actual, _expected) +#define EXPECT_V4_EQ(_actual, _expected) \ + HMT_EXPECT_FLOAT_EQ_MSG(_actual.X, _expected.X, "incorrect X"); \ + HMT_EXPECT_FLOAT_EQ_MSG(_actual.Y, _expected.Y, "incorrect Y"); \ + HMT_EXPECT_FLOAT_EQ_MSG(_actual.Z, _expected.Z, "incorrect Z"); \ + HMT_EXPECT_FLOAT_EQ_MSG(_actual.W, _expected.W, "incorrect W"); +#define EXPECT_M4_EQ(_actual, _expected) \ + HMT_EXPECT_FLOAT_EQ_MSG(_actual.Elements[0][0], _expected.Elements[0][0], "incorrect [0][0]"); \ + HMT_EXPECT_FLOAT_EQ_MSG(_actual.Elements[0][1], _expected.Elements[0][1], "incorrect [0][1]"); \ + HMT_EXPECT_FLOAT_EQ_MSG(_actual.Elements[0][2], _expected.Elements[0][2], "incorrect [0][2]"); \ + HMT_EXPECT_FLOAT_EQ_MSG(_actual.Elements[0][3], _expected.Elements[0][3], "incorrect [0][3]"); \ + HMT_EXPECT_FLOAT_EQ_MSG(_actual.Elements[1][0], _expected.Elements[1][0], "incorrect [1][0]"); \ + HMT_EXPECT_FLOAT_EQ_MSG(_actual.Elements[1][1], _expected.Elements[1][1], "incorrect [1][1]"); \ + HMT_EXPECT_FLOAT_EQ_MSG(_actual.Elements[1][2], _expected.Elements[1][2], "incorrect [1][2]"); \ + HMT_EXPECT_FLOAT_EQ_MSG(_actual.Elements[1][3], _expected.Elements[1][3], "incorrect [1][3]"); \ + HMT_EXPECT_FLOAT_EQ_MSG(_actual.Elements[2][0], _expected.Elements[2][0], "incorrect [2][0]"); \ + HMT_EXPECT_FLOAT_EQ_MSG(_actual.Elements[2][1], _expected.Elements[2][1], "incorrect [2][1]"); \ + HMT_EXPECT_FLOAT_EQ_MSG(_actual.Elements[2][2], _expected.Elements[2][2], "incorrect [2][2]"); \ + HMT_EXPECT_FLOAT_EQ_MSG(_actual.Elements[2][3], _expected.Elements[2][3], "incorrect [2][3]"); \ + HMT_EXPECT_FLOAT_EQ_MSG(_actual.Elements[3][0], _expected.Elements[3][0], "incorrect [3][0]"); \ + HMT_EXPECT_FLOAT_EQ_MSG(_actual.Elements[3][1], _expected.Elements[3][1], "incorrect [3][1]"); \ + HMT_EXPECT_FLOAT_EQ_MSG(_actual.Elements[3][2], _expected.Elements[3][2], "incorrect [3][2]"); \ + HMT_EXPECT_FLOAT_EQ_MSG(_actual.Elements[3][3], _expected.Elements[3][3], "incorrect [3][3]"); #define EXPECT_NEAR(_actual, _expected, _epsilon) HMT_EXPECT_NEAR(_actual, _expected, _epsilon) +#define EXPECT_M4_NEAR(_actual, _expected, _epsilon) \ + HMT_EXPECT_NEAR_MSG(_actual.Elements[0][0], _expected.Elements[0][0], _epsilon, "incorrect [0][0]"); \ + HMT_EXPECT_NEAR_MSG(_actual.Elements[0][1], _expected.Elements[0][1], _epsilon, "incorrect [0][1]"); \ + HMT_EXPECT_NEAR_MSG(_actual.Elements[0][2], _expected.Elements[0][2], _epsilon, "incorrect [0][2]"); \ + HMT_EXPECT_NEAR_MSG(_actual.Elements[0][3], _expected.Elements[0][3], _epsilon, "incorrect [0][3]"); \ + HMT_EXPECT_NEAR_MSG(_actual.Elements[1][0], _expected.Elements[1][0], _epsilon, "incorrect [1][0]"); \ + HMT_EXPECT_NEAR_MSG(_actual.Elements[1][1], _expected.Elements[1][1], _epsilon, "incorrect [1][1]"); \ + HMT_EXPECT_NEAR_MSG(_actual.Elements[1][2], _expected.Elements[1][2], _epsilon, "incorrect [1][2]"); \ + HMT_EXPECT_NEAR_MSG(_actual.Elements[1][3], _expected.Elements[1][3], _epsilon, "incorrect [1][3]"); \ + HMT_EXPECT_NEAR_MSG(_actual.Elements[2][0], _expected.Elements[2][0], _epsilon, "incorrect [2][0]"); \ + HMT_EXPECT_NEAR_MSG(_actual.Elements[2][1], _expected.Elements[2][1], _epsilon, "incorrect [2][1]"); \ + HMT_EXPECT_NEAR_MSG(_actual.Elements[2][2], _expected.Elements[2][2], _epsilon, "incorrect [2][2]"); \ + HMT_EXPECT_NEAR_MSG(_actual.Elements[2][3], _expected.Elements[2][3], _epsilon, "incorrect [2][3]"); \ + HMT_EXPECT_NEAR_MSG(_actual.Elements[3][0], _expected.Elements[3][0], _epsilon, "incorrect [3][0]"); \ + HMT_EXPECT_NEAR_MSG(_actual.Elements[3][1], _expected.Elements[3][1], _epsilon, "incorrect [3][1]"); \ + HMT_EXPECT_NEAR_MSG(_actual.Elements[3][2], _expected.Elements[3][2], _epsilon, "incorrect [3][2]"); \ + HMT_EXPECT_NEAR_MSG(_actual.Elements[3][3], _expected.Elements[3][3], _epsilon, "incorrect [3][3]"); #define EXPECT_LT(_actual, _expected) HMT_EXPECT_LT(_actual, _expected) #define EXPECT_GT(_actual, _expected) HMT_EXPECT_GT(_actual, _expected) #endif // HMT_SAFE_MACROS diff --git a/test/categories/MatrixOps.h b/test/categories/MatrixOps.h index 7475cc6..0e66839 100644 --- a/test/categories/MatrixOps.h +++ b/test/categories/MatrixOps.h @@ -246,60 +246,56 @@ TEST(InvMatrix, InvGeneral) } } -TEST(InvMatrix, Mat4Inverses) +TEST(InvMatrix, InvOrthographic) { { HMM_Mat4 Matrix = HMM_Orthographic_RH_NO(-160+100, 160+100, -90+200, 90+200, 10, 10000); - HMM_Mat4 Expect = HMM_M4D(1.0f); HMM_Mat4 Inverse = HMM_InvOrthographic(Matrix); - HMM_Mat4 Result = HMM_MulM4(Matrix, Inverse); - - EXPECT_FLOAT_EQ(Result.Elements[0][0], Expect.Elements[0][0]); - EXPECT_FLOAT_EQ(Result.Elements[0][1], Expect.Elements[0][1]); - EXPECT_FLOAT_EQ(Result.Elements[0][2], Expect.Elements[0][2]); - EXPECT_FLOAT_EQ(Result.Elements[0][3], Expect.Elements[0][3]); - - EXPECT_FLOAT_EQ(Result.Elements[1][0], Expect.Elements[1][0]); - EXPECT_FLOAT_EQ(Result.Elements[1][1], Expect.Elements[1][1]); - EXPECT_FLOAT_EQ(Result.Elements[1][2], Expect.Elements[1][2]); - EXPECT_FLOAT_EQ(Result.Elements[1][3], Expect.Elements[1][3]); - - EXPECT_FLOAT_EQ(Result.Elements[2][0], Expect.Elements[2][0]); - EXPECT_FLOAT_EQ(Result.Elements[2][1], Expect.Elements[2][1]); - EXPECT_FLOAT_EQ(Result.Elements[2][2], Expect.Elements[2][2]); - EXPECT_FLOAT_EQ(Result.Elements[2][3], Expect.Elements[2][3]); - - EXPECT_FLOAT_EQ(Result.Elements[3][0], Expect.Elements[3][0]); - EXPECT_FLOAT_EQ(Result.Elements[3][1], Expect.Elements[3][1]); - EXPECT_FLOAT_EQ(Result.Elements[3][2], Expect.Elements[3][2]); - EXPECT_FLOAT_EQ(Result.Elements[3][3], Expect.Elements[3][3]); + EXPECT_M4_EQ(HMM_MulM4(Matrix, Inverse), HMM_M4D(1.0f)); } { - HMM_Mat4 Matrix = HMM_Perspective_RH_NO(HMM_AngleDeg(120), 16.0/9.0, 10, 10000); - HMM_Mat4 Expect = HMM_M4D(1.0f); - HMM_Mat4 Inverse = HMM_InvPerspective(Matrix); - HMM_Mat4 Result = HMM_MulM4(Matrix, Inverse); - - EXPECT_FLOAT_EQ(Result.Elements[0][0], Expect.Elements[0][0]); - EXPECT_FLOAT_EQ(Result.Elements[0][1], Expect.Elements[0][1]); - EXPECT_FLOAT_EQ(Result.Elements[0][2], Expect.Elements[0][2]); - EXPECT_FLOAT_EQ(Result.Elements[0][3], Expect.Elements[0][3]); - - EXPECT_FLOAT_EQ(Result.Elements[1][0], Expect.Elements[1][0]); - EXPECT_FLOAT_EQ(Result.Elements[1][1], Expect.Elements[1][1]); - EXPECT_FLOAT_EQ(Result.Elements[1][2], Expect.Elements[1][2]); - EXPECT_FLOAT_EQ(Result.Elements[1][3], Expect.Elements[1][3]); - - EXPECT_FLOAT_EQ(Result.Elements[2][0], Expect.Elements[2][0]); - EXPECT_FLOAT_EQ(Result.Elements[2][1], Expect.Elements[2][1]); - EXPECT_FLOAT_EQ(Result.Elements[2][2], Expect.Elements[2][2]); - EXPECT_FLOAT_EQ(Result.Elements[2][3], Expect.Elements[2][3]); - - EXPECT_FLOAT_EQ(Result.Elements[3][0], Expect.Elements[3][0]); - EXPECT_FLOAT_EQ(Result.Elements[3][1], Expect.Elements[3][1]); - EXPECT_FLOAT_EQ(Result.Elements[3][2], Expect.Elements[3][2]); - EXPECT_FLOAT_EQ(Result.Elements[3][3], Expect.Elements[3][3]); + HMM_Mat4 Matrix = HMM_Orthographic_RH_ZO(-160+100, 160+100, -90+200, 90+200, 10, 10000); + HMM_Mat4 Inverse = HMM_InvOrthographic(Matrix); + EXPECT_M4_EQ(HMM_MulM4(Matrix, Inverse), HMM_M4D(1.0f)); } + { + HMM_Mat4 Matrix = HMM_Orthographic_LH_NO(-160+100, 160+100, -90+200, 90+200, 10, 10000); + HMM_Mat4 Inverse = HMM_InvOrthographic(Matrix); + EXPECT_M4_EQ(HMM_MulM4(Matrix, Inverse), HMM_M4D(1.0f)); + } + { + HMM_Mat4 Matrix = HMM_Orthographic_LH_ZO(-160+100, 160+100, -90+200, 90+200, 10, 10000); + HMM_Mat4 Inverse = HMM_InvOrthographic(Matrix); + EXPECT_M4_EQ(HMM_MulM4(Matrix, Inverse), HMM_M4D(1.0f)); + } +} + +TEST(InvMatrix, InvPerspective) +{ + { + HMM_Mat4 Matrix = HMM_Perspective_RH_NO(HMM_AngleDeg(120), 16.0/9.0, 10, 10000); + HMM_Mat4 Inverse = HMM_InvPerspective(Matrix); + EXPECT_M4_EQ(HMM_MulM4(Matrix, Inverse), HMM_M4D(1.0f)); + } + { + HMM_Mat4 Matrix = HMM_Perspective_RH_ZO(HMM_AngleDeg(120), 16.0/9.0, 10, 10000); + HMM_Mat4 Inverse = HMM_InvPerspective(Matrix); + EXPECT_M4_EQ(HMM_MulM4(Matrix, Inverse), HMM_M4D(1.0f)); + } + { + HMM_Mat4 Matrix = HMM_Perspective_LH_NO(HMM_AngleDeg(120), 16.0/9.0, 10, 10000); + HMM_Mat4 Inverse = HMM_InvPerspective(Matrix); + EXPECT_M4_EQ(HMM_MulM4(Matrix, Inverse), HMM_M4D(1.0f)); + } + { + HMM_Mat4 Matrix = HMM_Perspective_LH_ZO(HMM_AngleDeg(120), 16.0/9.0, 10, 10000); + HMM_Mat4 Inverse = HMM_InvPerspective(Matrix); + EXPECT_M4_EQ(HMM_MulM4(Matrix, Inverse), HMM_M4D(1.0f)); + } +} + +TEST(InvMatrix, InvLookAt) +{ { HMM_Vec3 Eye = {10.0f, 10.0f, 10.0f}; HMM_Vec3 Center = {100.0f, 200.0f, 30.0f}; @@ -308,106 +304,56 @@ TEST(InvMatrix, Mat4Inverses) HMM_Mat4 Expect = HMM_M4D(1.0f); HMM_Mat4 Inverse = HMM_InvLookAt(Matrix); HMM_Mat4 Result = HMM_MulM4(Matrix, Inverse); - - EXPECT_NEAR(Result.Elements[0][0], Expect.Elements[0][0], 0.001f); - EXPECT_NEAR(Result.Elements[0][1], Expect.Elements[0][1], 0.001f); - EXPECT_NEAR(Result.Elements[0][2], Expect.Elements[0][2], 0.001f); - EXPECT_NEAR(Result.Elements[0][3], Expect.Elements[0][3], 0.001f); - - EXPECT_NEAR(Result.Elements[1][0], Expect.Elements[1][0], 0.001f); - EXPECT_NEAR(Result.Elements[1][1], Expect.Elements[1][1], 0.001f); - EXPECT_NEAR(Result.Elements[1][2], Expect.Elements[1][2], 0.001f); - EXPECT_NEAR(Result.Elements[1][3], Expect.Elements[1][3], 0.001f); - - EXPECT_NEAR(Result.Elements[2][0], Expect.Elements[2][0], 0.001f); - EXPECT_NEAR(Result.Elements[2][1], Expect.Elements[2][1], 0.001f); - EXPECT_NEAR(Result.Elements[2][2], Expect.Elements[2][2], 0.001f); - EXPECT_NEAR(Result.Elements[2][3], Expect.Elements[2][3], 0.001f); - - EXPECT_NEAR(Result.Elements[3][0], Expect.Elements[3][0], 0.001f); - EXPECT_NEAR(Result.Elements[3][1], Expect.Elements[3][1], 0.001f); - EXPECT_NEAR(Result.Elements[3][2], Expect.Elements[3][2], 0.001f); - EXPECT_NEAR(Result.Elements[3][3], Expect.Elements[3][3], 0.001f); + EXPECT_M4_NEAR(Result, Expect, 0.001f); } + { + HMM_Vec3 Eye = {10.0f, 10.0f, 10.0f}; + HMM_Vec3 Center = {100.0f, 200.0f, 30.0f}; + HMM_Vec3 Up = {0.0f, 0.0f, 1.0f}; + HMM_Mat4 Matrix = HMM_LookAt_LH(Eye, Center, Up); + HMM_Mat4 Expect = HMM_M4D(1.0f); + HMM_Mat4 Inverse = HMM_InvLookAt(Matrix); + HMM_Mat4 Result = HMM_MulM4(Matrix, Inverse); + EXPECT_M4_NEAR(Result, Expect, 0.001f); + } +} + +TEST(InvMatrix, InvRotate) +{ { HMM_Vec3 Axis = {1.0f, -1.0f, 0.5f}; HMM_Mat4 Matrix = HMM_Rotate_RH(HMM_AngleDeg(30), HMM_NormV3(Axis)); HMM_Mat4 Expect = HMM_M4D(1.0f); HMM_Mat4 Inverse = HMM_InvRotate(Matrix); HMM_Mat4 Result = HMM_MulM4(Matrix, Inverse); - - EXPECT_NEAR(Result.Elements[0][0], Expect.Elements[0][0], 0.001f); - EXPECT_NEAR(Result.Elements[0][1], Expect.Elements[0][1], 0.001f); - EXPECT_NEAR(Result.Elements[0][2], Expect.Elements[0][2], 0.001f); - EXPECT_NEAR(Result.Elements[0][3], Expect.Elements[0][3], 0.001f); - - EXPECT_NEAR(Result.Elements[1][0], Expect.Elements[1][0], 0.001f); - EXPECT_NEAR(Result.Elements[1][1], Expect.Elements[1][1], 0.001f); - EXPECT_NEAR(Result.Elements[1][2], Expect.Elements[1][2], 0.001f); - EXPECT_NEAR(Result.Elements[1][3], Expect.Elements[1][3], 0.001f); - - EXPECT_NEAR(Result.Elements[2][0], Expect.Elements[2][0], 0.001f); - EXPECT_NEAR(Result.Elements[2][1], Expect.Elements[2][1], 0.001f); - EXPECT_NEAR(Result.Elements[2][2], Expect.Elements[2][2], 0.001f); - EXPECT_NEAR(Result.Elements[2][3], Expect.Elements[2][3], 0.001f); - - EXPECT_NEAR(Result.Elements[3][0], Expect.Elements[3][0], 0.001f); - EXPECT_NEAR(Result.Elements[3][1], Expect.Elements[3][1], 0.001f); - EXPECT_NEAR(Result.Elements[3][2], Expect.Elements[3][2], 0.001f); - EXPECT_NEAR(Result.Elements[3][3], Expect.Elements[3][3], 0.001f); + EXPECT_M4_NEAR(Result, Expect, 0.001f); } { - HMM_Vec3 Scale = {1.0f, -1.0f, 0.5f}; - HMM_Mat4 Matrix = HMM_Scale(Scale); + HMM_Vec3 Axis = {1.0f, -1.0f, 0.5f}; + HMM_Mat4 Matrix = HMM_Rotate_LH(HMM_AngleDeg(30), HMM_NormV3(Axis)); HMM_Mat4 Expect = HMM_M4D(1.0f); - HMM_Mat4 Inverse = HMM_InvScale(Matrix); + HMM_Mat4 Inverse = HMM_InvRotate(Matrix); HMM_Mat4 Result = HMM_MulM4(Matrix, Inverse); - - EXPECT_FLOAT_EQ(Result.Elements[0][0], Expect.Elements[0][0]); - EXPECT_FLOAT_EQ(Result.Elements[0][1], Expect.Elements[0][1]); - EXPECT_FLOAT_EQ(Result.Elements[0][2], Expect.Elements[0][2]); - EXPECT_FLOAT_EQ(Result.Elements[0][3], Expect.Elements[0][3]); - - EXPECT_FLOAT_EQ(Result.Elements[1][0], Expect.Elements[1][0]); - EXPECT_FLOAT_EQ(Result.Elements[1][1], Expect.Elements[1][1]); - EXPECT_FLOAT_EQ(Result.Elements[1][2], Expect.Elements[1][2]); - EXPECT_FLOAT_EQ(Result.Elements[1][3], Expect.Elements[1][3]); - - EXPECT_FLOAT_EQ(Result.Elements[2][0], Expect.Elements[2][0]); - EXPECT_FLOAT_EQ(Result.Elements[2][1], Expect.Elements[2][1]); - EXPECT_FLOAT_EQ(Result.Elements[2][2], Expect.Elements[2][2]); - EXPECT_FLOAT_EQ(Result.Elements[2][3], Expect.Elements[2][3]); - - EXPECT_FLOAT_EQ(Result.Elements[3][0], Expect.Elements[3][0]); - EXPECT_FLOAT_EQ(Result.Elements[3][1], Expect.Elements[3][1]); - EXPECT_FLOAT_EQ(Result.Elements[3][2], Expect.Elements[3][2]); - EXPECT_FLOAT_EQ(Result.Elements[3][3], Expect.Elements[3][3]); + EXPECT_M4_NEAR(Result, Expect, 0.001f); } - { - HMM_Vec3 Move = {1.0f, -1.0f, 0.5f}; - HMM_Mat4 Matrix = HMM_Translate(Move); - HMM_Mat4 Expect = HMM_M4D(1.0f); - HMM_Mat4 Inverse = HMM_InvTranslate(Matrix); - HMM_Mat4 Result = HMM_MulM4(Matrix, Inverse); - - EXPECT_FLOAT_EQ(Result.Elements[0][0], Expect.Elements[0][0]); - EXPECT_FLOAT_EQ(Result.Elements[0][1], Expect.Elements[0][1]); - EXPECT_FLOAT_EQ(Result.Elements[0][2], Expect.Elements[0][2]); - EXPECT_FLOAT_EQ(Result.Elements[0][3], Expect.Elements[0][3]); - - EXPECT_FLOAT_EQ(Result.Elements[1][0], Expect.Elements[1][0]); - EXPECT_FLOAT_EQ(Result.Elements[1][1], Expect.Elements[1][1]); - EXPECT_FLOAT_EQ(Result.Elements[1][2], Expect.Elements[1][2]); - EXPECT_FLOAT_EQ(Result.Elements[1][3], Expect.Elements[1][3]); - - EXPECT_FLOAT_EQ(Result.Elements[2][0], Expect.Elements[2][0]); - EXPECT_FLOAT_EQ(Result.Elements[2][1], Expect.Elements[2][1]); - EXPECT_FLOAT_EQ(Result.Elements[2][2], Expect.Elements[2][2]); - EXPECT_FLOAT_EQ(Result.Elements[2][3], Expect.Elements[2][3]); - - EXPECT_FLOAT_EQ(Result.Elements[3][0], Expect.Elements[3][0]); - EXPECT_FLOAT_EQ(Result.Elements[3][1], Expect.Elements[3][1]); - EXPECT_FLOAT_EQ(Result.Elements[3][2], Expect.Elements[3][2]); - EXPECT_FLOAT_EQ(Result.Elements[3][3], Expect.Elements[3][3]); - } +} + +TEST(InvMatrix, InvScale) +{ + HMM_Vec3 Scale = {1.0f, -1.0f, 0.5f}; + HMM_Mat4 Matrix = HMM_Scale(Scale); + HMM_Mat4 Expect = HMM_M4D(1.0f); + HMM_Mat4 Inverse = HMM_InvScale(Matrix); + HMM_Mat4 Result = HMM_MulM4(Matrix, Inverse); + EXPECT_M4_EQ(Result, Expect); +} + +TEST(InvMatrix, InvTranslate) +{ + HMM_Vec3 Move = {1.0f, -1.0f, 0.5f}; + HMM_Mat4 Matrix = HMM_Translate(Move); + HMM_Mat4 Expect = HMM_M4D(1.0f); + HMM_Mat4 Inverse = HMM_InvTranslate(Matrix); + HMM_Mat4 Result = HMM_MulM4(Matrix, Inverse); + EXPECT_M4_EQ(Result, Expect); } diff --git a/test/categories/Projection.h b/test/categories/Projection.h index b944100..f524b18 100644 --- a/test/categories/Projection.h +++ b/test/categories/Projection.h @@ -2,67 +2,84 @@ TEST(Projection, Orthographic) { +#define ORTHO_BOUNDS -8.0f, 12.0f, 5.0f, 10.0f, 1.0f, 100.0f + + // Right-handed { - HMM_Mat4 projection = HMM_Orthographic_RH_NO(-10.0f, 10.0f, -5.0f, 5.0f, 1.0f, 10.0f); - HMM_Vec4 original = HMM_V4(5.0f, 5.0f, -1.0f, 1.0); - HMM_Vec4 projected = HMM_MulM4V4(projection, original); + // Near and far distances correspond to negative Z, hence the Z coordinates here are negative. + HMM_Vec4 minCorner = HMM_V4(-8.0f, 5.0f, -1.0f, 1.0); + HMM_Vec4 maxCorner = HMM_V4(12.0f, 10.0f, -100.0f, 1.0); - EXPECT_FLOAT_EQ(projected.X, 0.5f); - EXPECT_FLOAT_EQ(projected.Y, 1.0f); - EXPECT_FLOAT_EQ(projected.Z, -1.0f); - EXPECT_FLOAT_EQ(projected.W, 1.0f); + // Z from -1 to 1 (GL convention) + { + HMM_Mat4 projection = HMM_Orthographic_RH_NO(ORTHO_BOUNDS); + EXPECT_V4_EQ(HMM_MulM4V4(projection, minCorner), HMM_V4(-1.0f, -1.0f, -1.0f, 1.0f)); + EXPECT_V4_EQ(HMM_MulM4V4(projection, maxCorner), HMM_V4(1.0f, 1.0f, 1.0f, 1.0f)); + } - /* Z0 */ - projection = HMM_Orthographic_RH_ZO(-10.0f, 10.0f, -5.0f, 5.0f, 1.0f, -10.0f); - projected = HMM_MulM4V4(projection, original); - EXPECT_FLOAT_EQ(projected.Z, 0.0f); + // Z from 0 to 1 (DX convention) + { + HMM_Mat4 projection = HMM_Orthographic_RH_ZO(ORTHO_BOUNDS); + EXPECT_V4_EQ(HMM_MulM4V4(projection, minCorner), HMM_V4(-1.0f, -1.0f, 0.0f, 1.0f)); + EXPECT_V4_EQ(HMM_MulM4V4(projection, maxCorner), HMM_V4(1.0f, 1.0f, 1.0f, 1.0f)); + } } - { - HMM_Mat4 projection = HMM_Orthographic_LH_NO(-10.0f, 10.0f, -5.0f, 5.0f, 1.0f, -10.0f); - HMM_Vec4 original = HMM_V4(5.0f, 5.0f, 1.0f, 1.0); - HMM_Vec4 projected = HMM_MulM4V4(projection, original); - - EXPECT_FLOAT_EQ(projected.X, 0.5f); - EXPECT_FLOAT_EQ(projected.Y, 1.0f); - EXPECT_FLOAT_EQ(projected.Z, -1.0f); - EXPECT_FLOAT_EQ(projected.W, 1.0f); - /* Z0 */ - projection = HMM_Orthographic_LH_ZO(-10.0f, 10.0f, -5.0f, 5.0f, 1.0f, -10.0f); - projected = HMM_MulM4V4(projection, original); - EXPECT_FLOAT_EQ(projected.Z, 0.0f); + // Left-handed + { + // Near and far distances correspond to positive Z, hence the Z coordinates here are positive. + HMM_Vec4 minCorner = HMM_V4(-8.0f, 5.0f, 1.0f, 1.0); + HMM_Vec4 maxCorner = HMM_V4(12.0f, 10.0f, 100.0f, 1.0); + + // Z from -1 to 1 (GL convention) + { + HMM_Mat4 projection = HMM_Orthographic_LH_NO(ORTHO_BOUNDS); + EXPECT_V4_EQ(HMM_MulM4V4(projection, minCorner), HMM_V4(-1.0f, -1.0f, -1.0f, 1.0f)); + EXPECT_V4_EQ(HMM_MulM4V4(projection, maxCorner), HMM_V4(1.0f, 1.0f, 1.0f, 1.0f)); + } + + // Z from 0 to 1 (DX convention) + { + HMM_Mat4 projection = HMM_Orthographic_LH_ZO(ORTHO_BOUNDS); + EXPECT_V4_EQ(HMM_MulM4V4(projection, minCorner), HMM_V4(-1.0f, -1.0f, 0.0f, 1.0f)); + EXPECT_V4_EQ(HMM_MulM4V4(projection, maxCorner), HMM_V4(1.0f, 1.0f, 1.0f, 1.0f)); + } } } TEST(Projection, Perspective) { + // Right-handed { - HMM_Mat4 projection = HMM_Perspective_RH_NO(HMM_AngleDeg(90.0f), 2.0f, 1.0f, 15.0f); - HMM_Vec4 original = HMM_V4(5.0f, 5.0f, -1.0f, 1.0f); - HMM_Vec4 projected = HMM_MulM4V4(projection, original); - EXPECT_FLOAT_EQ(projected.X, 2.5f); - EXPECT_FLOAT_EQ(projected.Y, 5.0f); - EXPECT_FLOAT_EQ(projected.Z, -1.0f); - EXPECT_FLOAT_EQ(projected.W, 1.0f); + // Z from -1 to 1 (GL convention) + { + HMM_Mat4 projection = HMM_Perspective_RH_NO(HMM_AngleDeg(90.0f), 2.0f, 1.0f, 15.0f); + HMM_Vec4 original = HMM_V4(5.0f, 5.0f, -1.0f, 1.0f); + EXPECT_V4_EQ(HMM_MulM4V4(projection, original), HMM_V4(2.5f, 5.0f, -1.0f, 1.0f)); + } - /* ZO */ - projection = HMM_Perspective_RH_ZO(HMM_AngleDeg(90.0f), 2.0f, 1.0f, 15.0f); - projected = HMM_MulM4V4(projection, original); - EXPECT_FLOAT_EQ(projected.Z, 0.0f); + // Z from 0 to 1 (DX convention) + { + HMM_Mat4 projection = HMM_Perspective_RH_ZO(HMM_AngleDeg(90.0f), 2.0f, 1.0f, 15.0f); + HMM_Vec4 original = HMM_V4(5.0f, 5.0f, -1.0f, 1.0f); + EXPECT_V4_EQ(HMM_MulM4V4(projection, original), HMM_V4(2.5f, 5.0f, 0.0f, 1.0f)); + } } - + + // Left-handed { - HMM_Mat4 projection = HMM_Perspective_LH_NO(HMM_AngleDeg(90.0f), 2.0f, 1.0f, 15.0f); - HMM_Vec4 original = HMM_V4(5.0f, 5.0f, 1.0f, 1.0f); - HMM_Vec4 projected = HMM_MulM4V4(projection, original); - EXPECT_FLOAT_EQ(projected.X, 2.5f); - EXPECT_FLOAT_EQ(projected.Y, 5.0f); - EXPECT_FLOAT_EQ(projected.Z, -1.0f); - EXPECT_FLOAT_EQ(projected.W, 1.0f); - - /* ZO */ - projection = HMM_Perspective_LH_ZO(HMM_AngleDeg(90.0f), 2.0f, 1.0f, 15.0f); - projected = HMM_MulM4V4(projection, original); - EXPECT_FLOAT_EQ(projected.Z, 0.0f); + // Z from -1 to 1 (GL convention) + { + HMM_Mat4 projection = HMM_Perspective_LH_NO(HMM_AngleDeg(90.0f), 2.0f, 1.0f, 15.0f); + HMM_Vec4 original = HMM_V4(5.0f, 5.0f, 1.0f, 1.0f); + EXPECT_V4_EQ(HMM_MulM4V4(projection, original), HMM_V4(2.5f, 5.0f, -1.0f, 1.0f)); + } + + // Z from 0 to 1 (DX convention) + { + HMM_Mat4 projection = HMM_Perspective_LH_ZO(HMM_AngleDeg(90.0f), 2.0f, 1.0f, 15.0f); + HMM_Vec4 original = HMM_V4(5.0f, 5.0f, 1.0f, 1.0f); + EXPECT_V4_EQ(HMM_MulM4V4(projection, original), HMM_V4(2.5f, 5.0f, 0.0f, 1.0f)); + } } }