Sitelet https://github.com/HandmadeMath/HandmadeMath/pull/154/files
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
119 changes: 78 additions & 41 deletions HandmadeMath.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,20 +26,6 @@

-----------------------------------------------------------------------------

By default, Handmade Math's projection matrices use a Normalized Device
Coordinates (NDC) range of X: [-1, 1], Y: [-1, 1], Z: [-1, 1], as is standard
in OpenGL. However, other graphics APIs require the range with Z: [0, 1], as
this has better numerical properties for depth buffers.

To use NDC with Z: [0, 1], you must define HANDMADE_MATH_USE_NDC_Z01 before
including this header, which will make HMM_Perspective and HMM_Orthographic
functions project Z to [0, 1]:

#define HANDMADE_MATH_USE_NDC_Z01
#include "HandmadeMath.h"

-----------------------------------------------------------------------------

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:
Expand Down Expand Up @@ -1711,10 +1697,10 @@ static inline HMM_Mat4 HMM_InvGeneralM4(HMM_Mat4 Matrix)
* Common graphics transformations
*/

COVERAGE(HMM_Orthographic_RH, 1)
static inline HMM_Mat4 HMM_Orthographic_RH(float Left, float Right, float Bottom, float Top, float Near, float Far)
COVERAGE(HMM_Orthographic_N0_RH, 1)
static inline HMM_Mat4 HMM_Orthographic_N0_RH(float Left, float Right, float Bottom, float Top, float Near, float Far)
{
ASSERT_COVERED(HMM_Orthographic_RH);
ASSERT_COVERED(HMM_Orthographic_N0_RH);

HMM_Mat4 Result = {0};

Expand All @@ -1725,23 +1711,49 @@ static inline HMM_Mat4 HMM_Orthographic_RH(float Left, float Right, float Bottom
Result.Elements[3][0] = (Left + Right) / (Left - Right);
Result.Elements[3][1] = (Bottom + Top) / (Bottom - Top);

#ifdef HANDMADE_MATH_USE_NDC_Z01
Result.Elements[2][2] = 1.0f / (Near - Far);
Result.Elements[3][2] = (Near) / (Near - Far);
#else
Result.Elements[2][2] = 2.0f / (Near - Far);
Result.Elements[3][2] = (Far + Near) / (Near - Far);
#endif

return Result;
}

COVERAGE(HMM_Orthographic_LH, 1)
static inline HMM_Mat4 HMM_Orthographic_LH(float Left, float Right, float Bottom, float Top, float Near, float Far)
COVERAGE(HMM_Orthographic_Z0_RH, 1)
static inline HMM_Mat4 HMM_Orthographic_Z0_RH(float Left, float Right, float Bottom, float Top, float Near, float Far)
{
ASSERT_COVERED(HMM_Orthographic_LH);
ASSERT_COVERED(HMM_Orthographic_Z0_RH);

HMM_Mat4 Result = {0};

Result.Elements[0][0] = 2.0f / (Right - Left);
Result.Elements[1][1] = 2.0f / (Top - Bottom);
Result.Elements[3][3] = 1.0f;

HMM_Mat4 Result = HMM_Orthographic_RH(Left, Right, Bottom, Top, Near, Far);
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_N0_LH, 1)
static inline HMM_Mat4 HMM_Orthographic_N0_LH(float Left, float Right, float Bottom, float Top, float Near, float Far)
{
ASSERT_COVERED(HMM_Orthographic_N0_LH);

HMM_Mat4 Result = HMM_Orthographic_N0_RH(Left, Right, Bottom, Top, Near, Far);
Result.Elements[2][2] = -Result.Elements[2][2];

return Result;
}

COVERAGE(HMM_Orthographic_Z0_LH, 1)
static inline HMM_Mat4 HMM_Orthographic_Z0_LH(float Left, float Right, float Bottom, float Top, float Near, float Far)
{
ASSERT_COVERED(HMM_Orthographic_Z0_LH);

HMM_Mat4 Result = HMM_Orthographic_Z0_RH(Left, Right, Bottom, Top, Near, Far);
Result.Elements[2][2] = -Result.Elements[2][2];

return Result;
Expand All @@ -1765,43 +1777,68 @@ static inline HMM_Mat4 HMM_InvOrthographic(HMM_Mat4 OrthoMatrix)
return Result;
}

COVERAGE(HMM_Perspective_RH, 1)
static inline HMM_Mat4 HMM_Perspective_RH(float FOV, float AspectRatio, float Near, float Far)
COVERAGE(HMM_Perspective_N0_RH, 1)
static inline HMM_Mat4 HMM_Perspective_N0_RH(float FOV, float AspectRatio, float Near, float Far)
{
ASSERT_COVERED(HMM_Perspective_RH);
ASSERT_COVERED(HMM_Perspective_N0_RH);

HMM_Mat4 Result = {0};

// See https://www.khronos.org/registry/OpenGL-Refpages/gl2.1/xhtml/gluPerspective.xml

float Cotangent = 1.0f / HMM_TanF(FOV / 2.0f);

Result.Elements[0][0] = Cotangent / AspectRatio;
Result.Elements[1][1] = Cotangent;
Result.Elements[2][3] = -1.0f;

Result.Elements[2][2] = (Near + Far) / (Near - Far);
Result.Elements[3][2] = (2.0f * Near * Far) / (Near - Far);

return Result;
}

COVERAGE(HMM_Perspective_Z0_RH, 1)
static inline HMM_Mat4 HMM_Perspective_Z0_RH(float FOV, float AspectRatio, float Near, float Far)
{
ASSERT_COVERED(HMM_Perspective_Z0_RH);

HMM_Mat4 Result = {0};

// See https://www.khronos.org/registry/OpenGL-Refpages/gl2.1/xhtml/gluPerspective.xml

float Cotangent = 1.0f / HMM_TanF(FOV / 2.0f);
Result.Elements[0][0] = Cotangent / AspectRatio;
Result.Elements[1][1] = Cotangent;
Result.Elements[2][3] = -1.0f;

#ifdef HANDMADE_MATH_USE_NDC_Z01
Result.Elements[2][2] = (Far) / (Near - Far);
Result.Elements[3][2] = (Near * Far) / (Near - Far);
#else
Result.Elements[2][2] = (Near + Far) / (Near - Far);
Result.Elements[3][2] = (2.0f * Near * Far) / (Near - Far);
#endif

return Result;
}

COVERAGE(HMM_Perspective_LH, 1)
static inline HMM_Mat4 HMM_Perspective_LH(float FOV, float AspectRatio, float Near, float Far)
COVERAGE(HMM_Perspective_N0_LH, 1)
static inline HMM_Mat4 HMM_Perspective_N0_LH(float FOV, float AspectRatio, float Near, float Far)
{
ASSERT_COVERED(HMM_Perspective_LH);
ASSERT_COVERED(HMM_Perspective_N0_LH);

HMM_Mat4 Result = HMM_Perspective_RH(FOV, AspectRatio, Near, Far);
Result.Elements[2][2] = -Result.Elements[2][2];
HMM_Mat4 Result = HMM_Perspective_N0_RH(FOV, AspectRatio, Near, Far);
Result.Elements[2][2] = -Result.Elements[2][2];
Result.Elements[2][3] = -Result.Elements[2][3];

return Result;
}

COVERAGE(HMM_Perspective_Z0_LH, 1)
static inline HMM_Mat4 HMM_Perspective_Z0_LH(float FOV, float AspectRatio, float Near, float Far)
{
ASSERT_COVERED(HMM_Perspective_Z0_LH);

return (Result);
HMM_Mat4 Result = HMM_Perspective_Z0_RH(FOV, AspectRatio, Near, Far);
Result.Elements[2][2] = -Result.Elements[2][2];
Result.Elements[2][3] = -Result.Elements[2][3];

return Result;
}

COVERAGE(HMM_InvPerspective, 1)
Expand Down
6 changes: 3 additions & 3 deletions test/categories/MatrixOps.h
Original file line number Diff line number Diff line change
Expand Up @@ -249,7 +249,7 @@ TEST(InvMatrix, InvGeneral)
TEST(InvMatrix, Mat4Inverses)
{
{
HMM_Mat4 Matrix = HMM_Orthographic_RH(-160+100, 160+100, -90+200, 90+200, 10, 10000);
HMM_Mat4 Matrix = HMM_Orthographic_N0_RH(-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);
Expand All @@ -272,10 +272,10 @@ TEST(InvMatrix, Mat4Inverses)
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_FLOAT_EQ(Result.Elements[3][3], Expect.Elements[3][3]);
}
{
HMM_Mat4 Matrix = HMM_Perspective_RH(HMM_AngleDeg(120), 16.0/9.0, 10, 10000);
HMM_Mat4 Matrix = HMM_Perspective_N0_RH(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);
Expand Down
61 changes: 40 additions & 21 deletions test/categories/Projection.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,47 +3,66 @@
TEST(Projection, Orthographic)
{
{
HMM_Mat4 projection = HMM_Orthographic_RH(-10.0f, 10.0f, -5.0f, 5.0f, 0.0f, -10.0f);

HMM_Vec3 original = HMM_V3(5.0f, 5.0f, -5.0f);
HMM_Vec4 projected = HMM_MulM4V4(projection, HMM_V4V(original, 1));
HMM_Mat4 projection = HMM_Orthographic_N0_RH(-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, -2.0f);
EXPECT_FLOAT_EQ(projected.Z, -1.0f);
EXPECT_FLOAT_EQ(projected.W, 1.0f);

/* Z0 */
projection = HMM_Orthographic_Z0_RH(-10.0f, 10.0f, -5.0f, 5.0f, 1.0f, -10.0f);
projected = HMM_MulM4V4(projection, original);
EXPECT_FLOAT_EQ(projected.Z, 0.0f);
}
{
HMM_Mat4 projection = HMM_Orthographic_LH(-10.0f, 10.0f, -5.0f, 5.0f, 0.0f, 10.0f);

HMM_Vec3 original = HMM_V3(5.0f, 5.0f, -5.0f);
HMM_Vec4 projected = HMM_MulM4V4(projection, HMM_V4V(original, 1));

HMM_Mat4 projection = HMM_Orthographic_N0_LH(-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, -2.0f);
EXPECT_FLOAT_EQ(projected.Z, -1.0f);
EXPECT_FLOAT_EQ(projected.W, 1.0f);

/* Z0 */
projection = HMM_Orthographic_Z0_LH(-10.0f, 10.0f, -5.0f, 5.0f, 1.0f, -10.0f);
projected = HMM_MulM4V4(projection, original);
EXPECT_FLOAT_EQ(projected.Z, 0.0f);
}
}

TEST(Projection, Perspective)
{
{
HMM_Mat4 projection = HMM_Perspective_RH(HMM_AngleDeg(90.0f), 2.0f, 5.0f, 15.0f);
HMM_Vec3 original = HMM_V3(5.0f, 5.0f, -15.0f);
HMM_Vec4 projected = HMM_MulM4V4(projection, HMM_V4V(original, 1));
HMM_Mat4 projection = HMM_Perspective_N0_RH(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, 15.0f);
EXPECT_FLOAT_EQ(projected.W, 15.0f);
EXPECT_FLOAT_EQ(projected.Z, -1.0f);
EXPECT_FLOAT_EQ(projected.W, 1.0f);

/* Z0 */
projection = HMM_Perspective_Z0_RH(HMM_AngleDeg(90.0f), 2.0f, 1.0f, 15.0f);
projected = HMM_MulM4V4(projection, original);
EXPECT_FLOAT_EQ(projected.Z, 0.0f);
}

{
HMM_Mat4 projection = HMM_Perspective_LH(HMM_AngleDeg(90.0f), 2.0f, 5.0f, 15.0f);
HMM_Vec3 original = HMM_V3(5.0f, 5.0f, 15.0f);
HMM_Vec4 projected = HMM_MulM4V4(projection, HMM_V4V(original, 1));
HMM_Mat4 projection = HMM_Perspective_N0_LH(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, 15.0f);
EXPECT_FLOAT_EQ(projected.W, 15.0f);
EXPECT_FLOAT_EQ(projected.Z, -1.0f);
EXPECT_FLOAT_EQ(projected.W, 1.0f);

/* Z0 */
projection = HMM_Perspective_Z0_LH(HMM_AngleDeg(90.0f), 2.0f, 1.0f, 15.0f);
projected = HMM_MulM4V4(projection, original);
EXPECT_FLOAT_EQ(projected.Z, 0.0f);
}
}
9 changes: 8 additions & 1 deletion update/update_hmm.c
Original file line number Diff line number Diff line change
Expand Up @@ -482,8 +482,15 @@ Str8List update_file_content(Arena* arena, str8 file_content) {

chr8 check = file_content.str[i+1];
if (check == '(') {
printf("\t[%u]: Find: %.*s, Appending: _RH for old default handedness.\n", Line, str8_PRINTF_ARGS(Find[t]));
Str8List_add(arena, &out, str8_first(file_content, i + 1));

if (t == HAND_PERSPECTIVE || t == HAND_ORTHO) {
printf("\t[%u]: Appending _N0 for old default NDC.\n", Line, str8_PRINTF_ARGS(Find[t]));
Str8List_add(arena, &out, str8_lit("_N0"));
}

printf("\t[%u]: Find: %.*s, Appending: _RH for old default handedness.\n", Line, str8_PRINTF_ARGS(Find[t]));

Str8List_add(arena, &out, str8_lit("_RH("));
file_content = str8_skip(file_content, i+2);
i = -1;
Expand Down