Repository: HandmadeMath/HandmadeMath (c)

Issue:
Inconsistent use of radians vs. degrees
The trigonometric functions in Handmade Math use radians for angles. `HMM_Perspective`, on the other hand, uses degrees.

We should make this usage consistent between the two, or find a way to clarify which uses radians and which uses degrees.



Discussion:
Oh, i was not aware of this  but i really think degrees, just because i dont want to have to convert what im passing in to a function every single time to radians. 

@bvisness If you could handle this bug that would be awesome because im not sure of all of the use cases that have this error

As I'm thinking about it more I really don't this this is a big deal, because:
- It is literally just the FOV parameter of `HMM_Perspective` that uses degrees, and that is a case that makes sense to use degrees.
- My gut tells me that the trig functions in a math library should use radians, not degrees
- Changing the trig functions to take degrees instead of radians would be very subtle and difficult to debug across projects that already use Handmade Math.

Or we could just change the FOV parameter to take radians, and then everything would be consistent (but we would have to convert to radians when using it.) Thoughts?

I like the FOV the way it is everything else if its fine to you i'd just leave it

Very well then!

Reopening because this continues to bother me and I think it shows up in more places now. (Like HMM_Rotate, which takes an angle in degrees.)

At the very least, we need to name parameters Degrees or Radians depending on what they expect. But I think we should choose a thing and stick to it for all but very special cases (like maybe the FOV of HMM_Perspective).

This seems like a good candidate for a 2.0 release since this would obviously be mega-breaking for people.
Do we have a list of what expects radians and what expects to degrees. We can do some sort of analysis then decide on what we want to do here 
My analysis:
- Trig functions take radians. 
- HMM_Perspective takes degrees for the FOV and converts to radians during the calculation. 
- HMM_Rotate takes an angle in degrees and converts it to radians during the calculation. 
- HMM_QuaternionFromAxisAngle takes an angle in radians. 

I believe GLM uses degrees for [their perspective matrix](https://glm.g-truc.net/0.9.4/api/a00151.html#ga283629a5ac7fb9037795435daf22560f), although they have a #define that makes it use radians. I regret to inform you that they also use degrees for their rotation matrix. This is stupid and I’m not sure what to do about that. At least they are consistent and appear to use degrees for everything, including their quaternion stuff. 

I still think radians are the more natural choice for everything except perspective FOV (and maybe even for that). For one thing, radians are “correct”. But also, we always have to convert to radians for the actual computation, which is pointless work for a library focused on performance. 

I think we should standardize on radians for everything. Avoid the ambiguity and some unnecessary work. 
Maybe it’s worth having a flag for radians vs. degrees for people whose engines use degrees for everything. It should be basically trivial to add a macro to our internal use that converts degrees to radians or does nothing. 
Ok yeah, I would like to switch everything to radians by default but add a #define allowing you to use degrees. We could even have the define’s behavior on by default, in a sense, if we wanted to have degrees be the default to match GLM or whatever. (I think the functions should be implemented in radians because the math is all in radians though.)

Patch:
diff --git a/HandmadeMath.h b/HandmadeMath.h
--- a/HandmadeMath.h
+++ b/HandmadeMath.h
@@ -4,6 +4,8 @@
   This is a single header file with a bunch of useful functions for game and
   graphics math operations.
 
+  All angles are in radians.
+
   =============================================================================
 
   You MUST
@@ -544,6 +546,16 @@ HMM_INLINE float HMM_PowerF(float Base, float Exponent)
  * Utility functions
  */
 
+COVERAGE(HMM_ToDegrees, 1)
+HMM_INLINE float HMM_ToDegrees(float Radians)
+{
+    ASSERT_COVERED(HMM_ToDegrees);
+
+    float Result = Radians * (180.0f / HMM_PI32);
+
+    return (Result);
+}
+
 COVERAGE(HMM_ToRadians, 1)
 HMM_INLINE float HMM_ToRadians(float Degrees)
 {
@@ -1404,7 +1416,7 @@ HMM_INLINE hmm_mat4 HMM_Orthographic(float Left, float Right, float Bottom, floa
 }
 
 COVERAGE(HMM_Perspective, 1)
-HMM_INLINE hmm_mat4 HMM_Perspective(float FOV, float AspectRatio, float Near, float Far)
+HMM_INLINE hmm_mat4 HMM_Perspective(float FOVRadians, float AspectRatio, float Near, float Far)
 {
     ASSERT_COVERED(HMM_Perspective);
 
@@ -1412,7 +1424,7 @@ HMM_INLINE hmm_mat4 HMM_Perspective(float FOV, float AspectRatio, float Near, fl
 
     // See https://www.khronos.org/registry/OpenGL-Refpages/gl2.1/xhtml/gluPerspective.xml
 
-    float Cotangent = 1.0f / HMM_TanF(FOV * (HMM_PI32 / 360.0f));
+    float Cotangent = 1.0f / HMM_TanF(FOVRadians / 2.0f);
 
     Result.Elements[0][0] = Cotangent / AspectRatio;
     Result.Elements[1][1] = Cotangent;
@@ -1438,7 +1450,7 @@ HMM_INLINE hmm_mat4 HMM_Translate(hmm_vec3 Translation)
     return (Result);
 }
 
-HMM_EXTERN hmm_mat4 HMM_Rotate(float Angle, hmm_vec3 Axis);
+HMM_EXTERN hmm_mat4 HMM_Rotate(float AngleRadians, hmm_vec3 Axis);
 
 COVERAGE(HMM_Scale, 1)
 HMM_INLINE hmm_mat4 HMM_Scale(hmm_vec3 Scale)
@@ -1675,7 +1687,7 @@ HMM_INLINE hmm_quaternion HMM_NLerp(hmm_quaternion Left, float Time, hmm_quatern
 HMM_EXTERN hmm_quaternion HMM_Slerp(hmm_quaternion Left, float Time, hmm_quaternion Right);
 HMM_EXTERN hmm_mat4 HMM_QuaternionToMat4(hmm_quaternion Left);
 HMM_EXTERN hmm_quaternion HMM_Mat4ToQuaternion(hmm_mat4 Left);
-HMM_EXTERN hmm_quaternion HMM_QuaternionFromAxisAngle(hmm_vec3 Axis, float AngleOfRotation);
+HMM_EXTERN hmm_quaternion HMM_QuaternionFromAxisAngle(hmm_vec3 Axis, float AngleOfRotationRadians);
 
 #ifdef __cplusplus
 }
@@ -2978,7 +2990,7 @@ hmm_mat4 HMM_DivideMat4f(hmm_mat4 Matrix, float Scalar)
 #endif
 
 COVERAGE(HMM_Rotate, 1)
-hmm_mat4 HMM_Rotate(float Angle, hmm_vec3 Axis)
+hmm_mat4 HMM_Rotate(float AngleRadians, hmm_vec3 Axis)
 {
     ASSERT_COVERED(HMM_Rotate);
 
@@ -2986,8 +2998,8 @@ hmm_mat4 HMM_Rotate(float Angle, hmm_vec3 Axis)
 
     Axis = HMM_NormalizeVec3(Axis);
 
-    float SinTheta = HMM_SinF(HMM_ToRadians(Angle));
-    float CosTheta = HMM_CosF(HMM_ToRadians(Angle));
+    float SinTheta = HMM_SinF(AngleRadians);
+    float CosTheta = HMM_CosF(AngleRadians);
     float CosValue = 1.0f - CosTheta;
 
     Result.Elements[0][0] = (Axis.X * Axis.X * CosValue) + CosTheta;
@@ -3205,17 +3217,17 @@ hmm_quaternion HMM_Mat4ToQuaternion(hmm_mat4 M)
 }
 
 COVERAGE(HMM_QuaternionFromAxisAngle, 1)
-hmm_quaternion HMM_QuaternionFromAxisAngle(hmm_vec3 Axis, float AngleOfRotation)
+hmm_quaternion HMM_QuaternionFromAxisAngle(hmm_vec3 Axis, float AngleOfRotationRadians)
 {
     ASSERT_COVERED(HMM_QuaternionFromAxisAngle);
 
     hmm_quaternion Result;
 
     hmm_vec3 AxisNormalized = HMM_NormalizeVec3(Axis);
-    float SineOfRotation = HMM_SinF(AngleOfRotation / 2.0f);
+    float SineOfRotation = HMM_SinF(AngleOfRotationRadians / 2.0f);
 
     Result.XYZ = HMM_MultiplyVec3f(AxisNormalized, SineOfRotation);
-    Result.W = HMM_CosF(AngleOfRotation / 2.0f);
+    Result.W = HMM_CosF(AngleOfRotationRadians / 2.0f);
 
     return (Result);
 }


Test patch:
diff --git a/test/categories/Projection.h b/test/categories/Projection.h
--- a/test/categories/Projection.h
+++ b/test/categories/Projection.h
@@ -15,7 +15,7 @@ TEST(Projection, Orthographic)
 
 TEST(Projection, Perspective)
 {
-    hmm_mat4 projection = HMM_Perspective(90.0f, 2.0f, 5.0f, 15.0f);
+    hmm_mat4 projection = HMM_Perspective(HMM_ToRadians(90.0f), 2.0f, 5.0f, 15.0f);
 
     {
         hmm_vec3 original = HMM_Vec3(5.0f, 5.0f, -15.0f);
diff --git a/test/categories/QuaternionOps.h b/test/categories/QuaternionOps.h
--- a/test/categories/QuaternionOps.h
+++ b/test/categories/QuaternionOps.h
@@ -111,7 +111,7 @@ TEST(QuaternionOps, Mat4ToQuat)
 
     // Rotate 90 degrees on the X axis
     {
-        hmm_mat4 m = HMM_Rotate(90, HMM_Vec3(1, 0, 0));
+        hmm_mat4 m = HMM_Rotate(HMM_ToRadians(90.0f), HMM_Vec3(1, 0, 0));
         hmm_quaternion result = HMM_Mat4ToQuaternion(m);
 
         float cosf = 0.707107f; // cos(90/2 degrees)
@@ -125,7 +125,7 @@ TEST(QuaternionOps, Mat4ToQuat)
 
     // Rotate 90 degrees on the Y axis (axis not normalized, just for fun)
     {
-        hmm_mat4 m = HMM_Rotate(90, HMM_Vec3(0, 2, 0));
+        hmm_mat4 m = HMM_Rotate(HMM_ToRadians(90.0f), HMM_Vec3(0, 2, 0));
         hmm_quaternion result = HMM_Mat4ToQuaternion(m);
 
         float cosf = 0.707107f; // cos(90/2 degrees)
@@ -139,7 +139,7 @@ TEST(QuaternionOps, Mat4ToQuat)
 
     // Rotate 90 degrees on the Z axis
     {
-        hmm_mat4 m = HMM_Rotate(90, HMM_Vec3(0, 0, 1));
+        hmm_mat4 m = HMM_Rotate(HMM_ToRadians(90.0f), HMM_Vec3(0, 0, 1));
         hmm_quaternion result = HMM_Mat4ToQuaternion(m);
 
         float cosf = 0.707107f; // cos(90/2 degrees)
@@ -153,7 +153,7 @@ TEST(QuaternionOps, Mat4ToQuat)
 
     // Rotate 45 degrees on the X axis (this hits case 4)
     {
-        hmm_mat4 m = HMM_Rotate(45, HMM_Vec3(1, 0, 0));
+        hmm_mat4 m = HMM_Rotate(HMM_ToRadians(45.0f), HMM_Vec3(1, 0, 0));
         hmm_quaternion result = HMM_Mat4ToQuaternion(m);
 
         float cosf = 0.9238795325f; // cos(90/2 degrees)
diff --git a/test/categories/ScalarMath.h b/test/categories/ScalarMath.h
--- a/test/categories/ScalarMath.h
+++ b/test/categories/ScalarMath.h
@@ -36,6 +36,13 @@ TEST(ScalarMath, Trigonometry)
     // checking that things work by default.
 }
 
+TEST(ScalarMath, ToDegrees)
+{
+    EXPECT_FLOAT_EQ(HMM_ToDegrees(0.0f), 0.0f);
+    EXPECT_FLOAT_EQ(HMM_ToDegrees(HMM_PI32), 180.0f);
+    EXPECT_FLOAT_EQ(HMM_ToDegrees(-HMM_PI32), -180.0f);
+}
+
 TEST(ScalarMath, ToRadians)
 {
     EXPECT_FLOAT_EQ(HMM_ToRadians(0.0f), 0.0f);
diff --git a/test/categories/Transformation.h b/test/categories/Transformation.h
--- a/test/categories/Transformation.h
+++ b/test/categories/Transformation.h
@@ -17,21 +17,23 @@ TEST(Transformations, Rotate)
 {
     hmm_vec3 original = HMM_Vec3(1.0f, 1.0f, 1.0f);
 
-    hmm_mat4 rotateX = HMM_Rotate(90, HMM_Vec3(1, 0, 0));
+    float angle = HMM_ToRadians(90.0f);
+
+    hmm_mat4 rotateX = HMM_Rotate(angle, HMM_Vec3(1, 0, 0));
     hmm_vec4 rotatedX = HMM_MultiplyMat4ByVec4(rotateX, HMM_Vec4v(original, 1));
     EXPECT_FLOAT_EQ(rotatedX.X, 1.0f);
     EXPECT_FLOAT_EQ(rotatedX.Y, -1.0f);
     EXPECT_FLOAT_EQ(rotatedX.Z, 1.0f);
     EXPECT_FLOAT_EQ(rotatedX.W, 1.0f);
 
-    hmm_mat4 rotateY = HMM_Rotate(90, HMM_Vec3(0, 1, 0));
+    hmm_mat4 rotateY = HMM_Rotate(angle, HMM_Vec3(0, 1, 0));
     hmm_vec4 rotatedY = HMM_MultiplyMat4ByVec4(rotateY, HMM_Vec4v(original, 1));
     EXPECT_FLOAT_EQ(rotatedY.X, 1.0f);
     EXPECT_FLOAT_EQ(rotatedY.Y, 1.0f);
     EXPECT_FLOAT_EQ(rotatedY.Z, -1.0f);
     EXPECT_FLOAT_EQ(rotatedY.W, 1.0f);
 
-    hmm_mat4 rotateZ = HMM_Rotate(90, HMM_Vec3(0, 0, 1));
+    hmm_mat4 rotateZ = HMM_Rotate(angle, HMM_Vec3(0, 0, 1));
     hmm_vec4 rotatedZ = HMM_MultiplyMat4ByVec4(rotateZ, HMM_Vec4v(original, 1));
     EXPECT_FLOAT_EQ(rotatedZ.X, -1.0f);
     EXPECT_FLOAT_EQ(rotatedZ.Y, 1.0f);