Tweak docs, add tests, find bugs

This commit is contained in:
Ben Visness
2023-02-02 19:18:24 -06:00
parent 50ab55b3bc
commit beb837a3c6
5 changed files with 234 additions and 205 deletions

View File

@@ -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);

View File

@@ -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?**

View File

@@ -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

View File

@@ -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);
}

View File

@@ -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));
}
}
}