Compare commits

...

2 Commits

Author SHA1 Message Date
Ben Visness
422bc588e9 Fix inverse perspective 2023-02-20 13:03:46 -06:00
Ben Visness
beb837a3c6 Tweak docs, add tests, find bugs 2023-02-02 19:18:24 -06:00
5 changed files with 253 additions and 207 deletions

View File

@@ -6,8 +6,8 @@
both C and C++. both C and C++.
============================================================================= =============================================================================
CONFIG CONFIG
=============================================================================
By default, all angles in Handmade Math are specified in radians. However, it 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 can be configured to use degrees or turns instead. Use one of the following
@@ -24,11 +24,13 @@
HMM_AngleDeg(degrees) HMM_AngleDeg(degrees)
HMM_AngleTurn(turns) HMM_AngleTurn(turns)
The definitions of these functions change depending on the default unit.
----------------------------------------------------------------------------- -----------------------------------------------------------------------------
Handmade Math ships with SSE (SIMD) implementations of several common Handmade Math ships with SSE (SIMD) implementations of several common
operations. To disable the use of SSE intrinsics, you must operations. To disable the use of SSE intrinsics, you must define
define HANDMADE_MATH_NO_SSE before including this file: HANDMADE_MATH_NO_SSE before including this file:
#define HANDMADE_MATH_NO_SSE #define HANDMADE_MATH_NO_SSE
#include "HandmadeMath.h" #include "HandmadeMath.h"
@@ -1669,6 +1671,8 @@ static inline float HMM_DeterminantM4(HMM_Mat4 Matrix)
} }
COVERAGE(HMM_InvGeneralM4, 1) 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) static inline HMM_Mat4 HMM_InvGeneralM4(HMM_Mat4 Matrix)
{ {
ASSERT_COVERED(HMM_InvGeneralM4); ASSERT_COVERED(HMM_InvGeneralM4);
@@ -1698,6 +1702,9 @@ static inline HMM_Mat4 HMM_InvGeneralM4(HMM_Mat4 Matrix)
*/ */
COVERAGE(HMM_Orthographic_RH_NO, 1) 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) 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); 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[0][0] = 2.0f / (Right - Left);
Result.Elements[1][1] = 2.0f / (Top - Bottom); 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][3] = 1.0f;
Result.Elements[3][0] = (Left + Right) / (Left - Right); Result.Elements[3][0] = (Left + Right) / (Left - Right);
Result.Elements[3][1] = (Bottom + Top) / (Bottom - Top); Result.Elements[3][1] = (Bottom + Top) / (Bottom - Top);
Result.Elements[3][2] = (Near + Far) / (Near - Far);
Result.Elements[2][2] = 2.0f / (Near - Far);
Result.Elements[3][2] = (Far + Near) / (Near - Far);
return Result; return Result;
} }
COVERAGE(HMM_Orthographic_RH_ZO, 1) 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) 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); 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[0][0] = 2.0f / (Right - Left);
Result.Elements[1][1] = 2.0f / (Top - Bottom); 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][3] = 1.0f;
Result.Elements[3][0] = (Left + Right) / (Left - Right); Result.Elements[3][0] = (Left + Right) / (Left - Right);
Result.Elements[3][1] = (Bottom + Top) / (Bottom - Top); Result.Elements[3][1] = (Bottom + Top) / (Bottom - Top);
Result.Elements[2][2] = 1.0f / (Near - Far);
Result.Elements[3][2] = (Near) / (Near - Far); Result.Elements[3][2] = (Near) / (Near - Far);
return Result; return Result;
} }
COVERAGE(HMM_Orthographic_LH_NO, 1) 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) 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); 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) 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) 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); 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; 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) static inline HMM_Mat4 HMM_InvOrthographic(HMM_Mat4 OrthoMatrix)
{ {
ASSERT_COVERED(HMM_InvOrthographic); ASSERT_COVERED(HMM_InvOrthographic);
@@ -1841,10 +1857,10 @@ static inline HMM_Mat4 HMM_Perspective_LH_ZO(float FOV, float AspectRatio, float
return Result; return Result;
} }
COVERAGE(HMM_InvPerspective, 1) COVERAGE(HMM_InvPerspective_RH, 1)
static inline HMM_Mat4 HMM_InvPerspective(HMM_Mat4 PerspectiveMatrix) static inline HMM_Mat4 HMM_InvPerspective_RH(HMM_Mat4 PerspectiveMatrix)
{ {
ASSERT_COVERED(HMM_InvPerspective); ASSERT_COVERED(HMM_InvPerspective_RH);
HMM_Mat4 Result = {0}; HMM_Mat4 Result = {0};
Result.Elements[0][0] = 1.0f / PerspectiveMatrix.Elements[0][0]; Result.Elements[0][0] = 1.0f / PerspectiveMatrix.Elements[0][0];
@@ -1858,6 +1874,23 @@ static inline HMM_Mat4 HMM_InvPerspective(HMM_Mat4 PerspectiveMatrix)
return Result; return Result;
} }
COVERAGE(HMM_InvPerspective_LH, 1)
static inline HMM_Mat4 HMM_InvPerspective_LH(HMM_Mat4 PerspectiveMatrix)
{
ASSERT_COVERED(HMM_InvPerspective_LH);
HMM_Mat4 Result = {0};
Result.Elements[0][0] = 1.0f / PerspectiveMatrix.Elements[0][0];
Result.Elements[1][1] = 1.0f / PerspectiveMatrix.Elements[1][1];
Result.Elements[2][2] = 0.0f;
Result.Elements[2][3] = 1.0f / PerspectiveMatrix.Elements[3][2];
Result.Elements[3][3] = PerspectiveMatrix.Elements[2][2] * -Result.Elements[2][3];
Result.Elements[3][2] = PerspectiveMatrix.Elements[2][3];
return Result;
}
COVERAGE(HMM_Translate, 1) COVERAGE(HMM_Translate, 1)
static inline HMM_Mat4 HMM_Translate(HMM_Vec3 Translation) static inline HMM_Mat4 HMM_Translate(HMM_Vec3 Translation)
{ {

View File

@@ -1,22 +1,23 @@
# Handmade Math # 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). 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: Here's what sets Handmade Math apart:
- **A simple single-header library.** Just `#include "HandmadeMath.h"`. - **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 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`. - **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. - **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 ## 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. 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?** **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?** **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(); \ _HMT_CASE_START(); \
float actual = (_actual); \ float actual = (_actual); \
float diff = actual - (_expected); \ float diff = actual - (_expected); \
if (diff < -FLT_EPSILON || FLT_EPSILON < diff) { \ if (diff < -FLT_EPSILON || FLT_EPSILON < diff) { \
_HMT_CASE_FAIL(); \ _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(); \ _HMT_CASE_START(); \
float actual = (_actual); \ float actual = (_actual); \
float diff = actual - (_expected); \ float diff = actual - (_expected); \
if (diff < -(_epsilon) || (_epsilon) < diff) { \ if (diff < -(_epsilon) || (_epsilon) < diff) { \
_HMT_CASE_FAIL(); \ _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) { \ #define HMT_EXPECT_LT(_actual, _expected) { \
_HMT_CASE_START(); \ _HMT_CASE_START(); \
@@ -192,7 +202,46 @@ INITIALIZER(_HMT_COVERCASE_FUNCNAME_INIT(name)) { \
#define EXPECT_TRUE(_actual) HMT_EXPECT_TRUE(_actual) #define EXPECT_TRUE(_actual) HMT_EXPECT_TRUE(_actual)
#define EXPECT_FALSE(_actual) HMT_EXPECT_FALSE(_actual) #define EXPECT_FALSE(_actual) HMT_EXPECT_FALSE(_actual)
#define EXPECT_FLOAT_EQ(_actual, _expected) HMT_EXPECT_FLOAT_EQ(_actual, _expected) #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_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_LT(_actual, _expected) HMT_EXPECT_LT(_actual, _expected)
#define EXPECT_GT(_actual, _expected) HMT_EXPECT_GT(_actual, _expected) #define EXPECT_GT(_actual, _expected) HMT_EXPECT_GT(_actual, _expected)
#endif // HMT_SAFE_MACROS #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 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 Inverse = HMM_InvOrthographic(Matrix);
HMM_Mat4 Result = HMM_MulM4(Matrix, Inverse); EXPECT_M4_EQ(HMM_MulM4(Matrix, Inverse), HMM_M4D(1.0f));
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_Perspective_RH_NO(HMM_AngleDeg(120), 16.0/9.0, 10, 10000); HMM_Mat4 Matrix = HMM_Orthographic_RH_ZO(-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 Inverse = HMM_InvPerspective(Matrix); EXPECT_M4_EQ(HMM_MulM4(Matrix, Inverse), HMM_M4D(1.0f));
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_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_RH(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_RH(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_LH(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_LH(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 Eye = {10.0f, 10.0f, 10.0f};
HMM_Vec3 Center = {100.0f, 200.0f, 30.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 Expect = HMM_M4D(1.0f);
HMM_Mat4 Inverse = HMM_InvLookAt(Matrix); HMM_Mat4 Inverse = HMM_InvLookAt(Matrix);
HMM_Mat4 Result = HMM_MulM4(Matrix, Inverse); HMM_Mat4 Result = HMM_MulM4(Matrix, Inverse);
EXPECT_M4_NEAR(Result, Expect, 0.001f);
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);
} }
{
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_Vec3 Axis = {1.0f, -1.0f, 0.5f};
HMM_Mat4 Matrix = HMM_Rotate_RH(HMM_AngleDeg(30), HMM_NormV3(Axis)); HMM_Mat4 Matrix = HMM_Rotate_RH(HMM_AngleDeg(30), HMM_NormV3(Axis));
HMM_Mat4 Expect = HMM_M4D(1.0f); HMM_Mat4 Expect = HMM_M4D(1.0f);
HMM_Mat4 Inverse = HMM_InvRotate(Matrix); HMM_Mat4 Inverse = HMM_InvRotate(Matrix);
HMM_Mat4 Result = HMM_MulM4(Matrix, Inverse); HMM_Mat4 Result = HMM_MulM4(Matrix, Inverse);
EXPECT_M4_NEAR(Result, Expect, 0.001f);
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);
} }
{ {
HMM_Vec3 Scale = {1.0f, -1.0f, 0.5f}; HMM_Vec3 Axis = {1.0f, -1.0f, 0.5f};
HMM_Mat4 Matrix = HMM_Scale(Scale); HMM_Mat4 Matrix = HMM_Rotate_LH(HMM_AngleDeg(30), HMM_NormV3(Axis));
HMM_Mat4 Expect = HMM_M4D(1.0f); 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); HMM_Mat4 Result = HMM_MulM4(Matrix, Inverse);
EXPECT_M4_NEAR(Result, Expect, 0.001f);
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_Vec3 Move = {1.0f, -1.0f, 0.5f};
HMM_Mat4 Matrix = HMM_Translate(Move); TEST(InvMatrix, InvScale)
HMM_Mat4 Expect = HMM_M4D(1.0f); {
HMM_Mat4 Inverse = HMM_InvTranslate(Matrix); HMM_Vec3 Scale = {1.0f, -1.0f, 0.5f};
HMM_Mat4 Result = HMM_MulM4(Matrix, Inverse); HMM_Mat4 Matrix = HMM_Scale(Scale);
HMM_Mat4 Expect = HMM_M4D(1.0f);
EXPECT_FLOAT_EQ(Result.Elements[0][0], Expect.Elements[0][0]); HMM_Mat4 Inverse = HMM_InvScale(Matrix);
EXPECT_FLOAT_EQ(Result.Elements[0][1], Expect.Elements[0][1]); HMM_Mat4 Result = HMM_MulM4(Matrix, Inverse);
EXPECT_FLOAT_EQ(Result.Elements[0][2], Expect.Elements[0][2]); EXPECT_M4_EQ(Result, Expect);
EXPECT_FLOAT_EQ(Result.Elements[0][3], Expect.Elements[0][3]); }
EXPECT_FLOAT_EQ(Result.Elements[1][0], Expect.Elements[1][0]); TEST(InvMatrix, InvTranslate)
EXPECT_FLOAT_EQ(Result.Elements[1][1], Expect.Elements[1][1]); {
EXPECT_FLOAT_EQ(Result.Elements[1][2], Expect.Elements[1][2]); HMM_Vec3 Move = {1.0f, -1.0f, 0.5f};
EXPECT_FLOAT_EQ(Result.Elements[1][3], Expect.Elements[1][3]); HMM_Mat4 Matrix = HMM_Translate(Move);
HMM_Mat4 Expect = HMM_M4D(1.0f);
EXPECT_FLOAT_EQ(Result.Elements[2][0], Expect.Elements[2][0]); HMM_Mat4 Inverse = HMM_InvTranslate(Matrix);
EXPECT_FLOAT_EQ(Result.Elements[2][1], Expect.Elements[2][1]); HMM_Mat4 Result = HMM_MulM4(Matrix, Inverse);
EXPECT_FLOAT_EQ(Result.Elements[2][2], Expect.Elements[2][2]); EXPECT_M4_EQ(Result, Expect);
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]);
}
} }

View File

@@ -2,67 +2,84 @@
TEST(Projection, Orthographic) 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); // Near and far distances correspond to negative Z, hence the Z coordinates here are negative.
HMM_Vec4 original = HMM_V4(5.0f, 5.0f, -1.0f, 1.0); HMM_Vec4 minCorner = HMM_V4(-8.0f, 5.0f, -1.0f, 1.0);
HMM_Vec4 projected = HMM_MulM4V4(projection, original); HMM_Vec4 maxCorner = HMM_V4(12.0f, 10.0f, -100.0f, 1.0);
EXPECT_FLOAT_EQ(projected.X, 0.5f); // Z from -1 to 1 (GL convention)
EXPECT_FLOAT_EQ(projected.Y, 1.0f); {
EXPECT_FLOAT_EQ(projected.Z, -1.0f); HMM_Mat4 projection = HMM_Orthographic_RH_NO(ORTHO_BOUNDS);
EXPECT_FLOAT_EQ(projected.W, 1.0f); 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 */ // Z from 0 to 1 (DX convention)
projection = HMM_Orthographic_RH_ZO(-10.0f, 10.0f, -5.0f, 5.0f, 1.0f, -10.0f); {
projected = HMM_MulM4V4(projection, original); HMM_Mat4 projection = HMM_Orthographic_RH_ZO(ORTHO_BOUNDS);
EXPECT_FLOAT_EQ(projected.Z, 0.0f); 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 */ // Left-handed
projection = HMM_Orthographic_LH_ZO(-10.0f, 10.0f, -5.0f, 5.0f, 1.0f, -10.0f); {
projected = HMM_MulM4V4(projection, original); // Near and far distances correspond to positive Z, hence the Z coordinates here are positive.
EXPECT_FLOAT_EQ(projected.Z, 0.0f); 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) TEST(Projection, Perspective)
{ {
// Right-handed
{ {
HMM_Mat4 projection = HMM_Perspective_RH_NO(HMM_AngleDeg(90.0f), 2.0f, 1.0f, 15.0f); // Z from -1 to 1 (GL convention)
HMM_Vec4 original = HMM_V4(5.0f, 5.0f, -1.0f, 1.0f); {
HMM_Vec4 projected = HMM_MulM4V4(projection, original); HMM_Mat4 projection = HMM_Perspective_RH_NO(HMM_AngleDeg(90.0f), 2.0f, 1.0f, 15.0f);
EXPECT_FLOAT_EQ(projected.X, 2.5f); HMM_Vec4 original = HMM_V4(5.0f, 5.0f, -1.0f, 1.0f);
EXPECT_FLOAT_EQ(projected.Y, 5.0f); EXPECT_V4_EQ(HMM_MulM4V4(projection, original), HMM_V4(2.5f, 5.0f, -1.0f, 1.0f));
EXPECT_FLOAT_EQ(projected.Z, -1.0f); }
EXPECT_FLOAT_EQ(projected.W, 1.0f);
/* ZO */ // Z from 0 to 1 (DX convention)
projection = HMM_Perspective_RH_ZO(HMM_AngleDeg(90.0f), 2.0f, 1.0f, 15.0f); {
projected = HMM_MulM4V4(projection, original); HMM_Mat4 projection = HMM_Perspective_RH_ZO(HMM_AngleDeg(90.0f), 2.0f, 1.0f, 15.0f);
EXPECT_FLOAT_EQ(projected.Z, 0.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); // Z from -1 to 1 (GL convention)
HMM_Vec4 original = HMM_V4(5.0f, 5.0f, 1.0f, 1.0f); {
HMM_Vec4 projected = HMM_MulM4V4(projection, original); HMM_Mat4 projection = HMM_Perspective_LH_NO(HMM_AngleDeg(90.0f), 2.0f, 1.0f, 15.0f);
EXPECT_FLOAT_EQ(projected.X, 2.5f); HMM_Vec4 original = HMM_V4(5.0f, 5.0f, 1.0f, 1.0f);
EXPECT_FLOAT_EQ(projected.Y, 5.0f); EXPECT_V4_EQ(HMM_MulM4V4(projection, original), HMM_V4(2.5f, 5.0f, -1.0f, 1.0f));
EXPECT_FLOAT_EQ(projected.Z, -1.0f); }
EXPECT_FLOAT_EQ(projected.W, 1.0f);
// Z from 0 to 1 (DX convention)
/* ZO */ {
projection = HMM_Perspective_LH_ZO(HMM_AngleDeg(90.0f), 2.0f, 1.0f, 15.0f); HMM_Mat4 projection = HMM_Perspective_LH_ZO(HMM_AngleDeg(90.0f), 2.0f, 1.0f, 15.0f);
projected = HMM_MulM4V4(projection, original); HMM_Vec4 original = HMM_V4(5.0f, 5.0f, 1.0f, 1.0f);
EXPECT_FLOAT_EQ(projected.Z, 0.0f); EXPECT_V4_EQ(HMM_MulM4V4(projection, original), HMM_V4(2.5f, 5.0f, 0.0f, 1.0f));
}
} }
} }