From fabad91c39b53e86ba1ff42452d21192e5b5c448 Mon Sep 17 00:00:00 2001 From: Ben Visness Date: Sat, 30 Jun 2018 22:17:00 +0200 Subject: [PATCH] Get an FPS cam kind of working! (no mouse input yet) --- example/src/FPSCam.h | 35 +++++++++++++++++ example/src/FollowCam.h | 42 ++++++++++++--------- example/src/debug.h | 2 + example/src/main.cpp | 18 ++++----- test/categories/QuaternionOps.h | 66 ++++++++++++++++++++++++--------- test/categories/VectorOps.h | 26 ++++++++++--- 6 files changed, 139 insertions(+), 50 deletions(-) create mode 100644 example/src/FPSCam.h diff --git a/example/src/FPSCam.h b/example/src/FPSCam.h new file mode 100644 index 0000000..1d33e77 --- /dev/null +++ b/example/src/FPSCam.h @@ -0,0 +1,35 @@ +#include + +#ifndef HMME_FPSCAM_H +#define HMME_FPSCAM_H + +#include "Entity.h" +#include "FollowCam.h" +#include "HandmadeMath.h" + +#include "debug.h" + +class FPSCam : public Entity { +public: + FollowCam cam = FollowCam(0); // TODO: Why on earth is this necessary?? Remove this and fix the error. + Entity target; + + FPSCam() { + target = Entity(); + target.position = HMM_Vec3(1.0f, 0.0f, 0.0f); + + cam = FollowCam(&target); + + AddChild(&target); + AddChild(&cam); + } + + float x = 0; + + void Tick(float deltaSeconds) override { + x += deltaSeconds; + rotation *= HMM_QuaternionFromAxisAngle(HMM_Vec3(0.0f, 1.0f, 0.0f), deltaSeconds * HMM_ToRadians(45.0f)); + } +}; + +#endif diff --git a/example/src/FollowCam.h b/example/src/FollowCam.h index dae3a68..528cbb8 100644 --- a/example/src/FollowCam.h +++ b/example/src/FollowCam.h @@ -16,27 +16,34 @@ public: target = t; } - // HI BEN HERE IS YOUR STREAM OF CONSCIOUSNESS - // - // Up is wonky. Really this whole thing is a little wonky, although it's - // gotten pretty close. You need to figure out how to directly calculate the - // axis and angle so you don't have to do this in two steps. Then it should - // Just Work. - // - // Although you may have to figure out how to make the camera keep a consistent up vector. - // - // Actually that shouldn't be too hard, because once you have a quaternion - // LookAt thing, you can just always pass world up to it. - void Tick(float deltaSeconds) override { + // TODO: Find a way to do this rotation routine in a single quaternion. Maybe that + // just means finding a correct method, then doing some quaternion multiplication + // on paper to see how the axis and angle shake out. + + rotation = GetLookAtRotation(); + } + + hmm_quaternion GetLookAtRotation() { hmm_vec3 fwd = (parentModelMatrix * HMM_Vec4(1.0f, 0.0f, 0.0f, 0.0f)).XYZ; hmm_vec3 up = (parentModelMatrix * HMM_Vec4(0.0f, 1.0f, 0.0f, 0.0f)).XYZ; hmm_vec3 to = target->worldPosition() - worldPosition(); - hmm_quaternion justPointAt = HMM_QuaternionFromAxisAngle( - HMM_Cross(fwd, to), - HMM_ACosF(HMM_Dot(HMM_Normalize(fwd), HMM_Normalize(to))) - ); + hmm_vec3 pointAxis = HMM_Cross(fwd, to); + hmm_quaternion justPointAt; + + // TODO: proper epsilon! and probably implement some kind of nan + // protection because a single nan ruins everything. + if (HMM_ABS(HMM_Length(pointAxis)) < 0.0001f) { + // Already pointing at the thing! + justPointAt = HMM_Quaternion(0.0f, 0.0f, 0.0f, 1.0f); + } else { + justPointAt = HMM_QuaternionFromAxisAngle( + pointAxis, + HMM_ACosF(HMM_Dot(HMM_Normalize(fwd), HMM_Normalize(to))) + ); + } + hmm_vec3 newUp = (HMM_QuaternionToMat4(justPointAt) * HMM_Vec4v(up, 0.0f)).XYZ; hmm_quaternion backUpright = HMM_QuaternionFromAxisAngle( to, @@ -49,7 +56,8 @@ public: // add the vector projection stuff that we somehow have left out! -HMM_ACosF(HMM_Dot(HMM_Normalize(newUp), HMM_Vec3(0.0f, 1.0f, 0.0f))) ); - rotation = backUpright * justPointAt; + + return backUpright * justPointAt; // BEN // diff --git a/example/src/debug.h b/example/src/debug.h index 18e6ed4..5e043f8 100644 --- a/example/src/debug.h +++ b/example/src/debug.h @@ -18,9 +18,11 @@ void printQuaternion(hmm_quaternion q) { } void printMat4(hmm_mat4 m) { + printf("/\n"); for (int r = 0; r < 4; r++) { printf("| %f\t%f\t%f\t%f |\n", m[0][r], m[1][r], m[2][r], m[3][r]); } + printf("\\\n"); } #endif diff --git a/example/src/main.cpp b/example/src/main.cpp index 19b4a7e..3348b50 100644 --- a/example/src/main.cpp +++ b/example/src/main.cpp @@ -11,7 +11,7 @@ #include "Entity.h" #include "Cube.h" #include "MeshRenderComponent.h" -#include "FollowCam.h" +#include "FPSCam.h" void TickTree(Entity *e, float deltaSeconds); void ComputeModelMatrices(Entity *ep, hmm_mat4 parentModelMatrix); @@ -74,10 +74,10 @@ int main() monkey.position = HMM_Vec3(2.1f, 0.0f, 0.0f); monkey.renderComponent = new MeshRenderComponent("MonkeySmooth.obj"); - FollowCam cam = FollowCam(&monkey); - cam.position = HMM_Vec3(-3.0f, -1.0f, 0.0f); - // cam.position = HMM_Vec3(3.0f, 3.0f, 5.0f); - // cam.rotation = HMM_QuaternionFromAxisAngle(HMM_Vec3(0.0f, 1.0f, 0.0f), HMM_ToRadians(90.0f)); + FPSCam fpsCam = FPSCam(); + fpsCam.position = HMM_Vec3(-3.0f, 1.0f, 1.0f); + + Entity *cam = &fpsCam.cam; // Cube c = Cube(); // monkey.position = HMM_Vec3(2.1f, 0.0f, 0.0f); @@ -88,7 +88,7 @@ int main() Entity root = Entity(); root.AddChild(&c1); - root.AddChild(&cam); + root.AddChild(&fpsCam); Entity axes = Entity(); axes.renderComponent = new MeshRenderComponent("Axes.obj"); @@ -114,8 +114,8 @@ int main() ComputeModelMatrices(&root, HMM_Mat4d(1.0f)); // Render! - hmm_mat4 projection = cam.projectionMatrix(); - hmm_mat4 view = cam.viewMatrix(); + hmm_mat4 projection = cam->projectionMatrix(); + hmm_mat4 view = cam->viewMatrix(); hmm_mat4 vp = projection * view; auto it = EntityIterator(&root); @@ -143,7 +143,7 @@ int main() glfwPollEvents(); } while ( // Check if the ESC key was pressed or the window was closed - glfwGetKey(window, GLFW_KEY_ESCAPE ) != GLFW_PRESS + glfwGetKey(window, GLFW_KEY_ESCAPE) != GLFW_PRESS && glfwWindowShouldClose(window) == 0 ); } diff --git a/test/categories/QuaternionOps.h b/test/categories/QuaternionOps.h index 6dd525d..bbcac2d 100644 --- a/test/categories/QuaternionOps.h +++ b/test/categories/QuaternionOps.h @@ -80,29 +80,59 @@ TEST(QuaternionOps, ToMat4) { const float abs_error = 0.0001f; - hmm_quaternion rot = HMM_Quaternion(0.707107f, 0.0f, 0.0f, 0.707107f); + { + // Identity quaternion + hmm_quaternion rot = HMM_Quaternion(0.0f, 0.0f, 0.0f, 1.0f); - hmm_mat4 result = HMM_QuaternionToMat4(rot); + hmm_mat4 result = HMM_QuaternionToMat4(rot); - EXPECT_NEAR(result.Elements[0][0], 1.0f, abs_error); - EXPECT_NEAR(result.Elements[0][1], 0.0f, abs_error); - EXPECT_NEAR(result.Elements[0][2], 0.0f, abs_error); - EXPECT_NEAR(result.Elements[0][3], 0.0f, abs_error); + EXPECT_NEAR(result.Elements[0][0], 1.0f, abs_error); + EXPECT_NEAR(result.Elements[0][1], 0.0f, abs_error); + EXPECT_NEAR(result.Elements[0][2], 0.0f, abs_error); + EXPECT_NEAR(result.Elements[0][3], 0.0f, abs_error); - EXPECT_NEAR(result.Elements[1][0], 0.0f, abs_error); - EXPECT_NEAR(result.Elements[1][1], 0.0f, abs_error); - EXPECT_NEAR(result.Elements[1][2], 1.0f, abs_error); - EXPECT_NEAR(result.Elements[1][3], 0.0f, abs_error); + EXPECT_NEAR(result.Elements[1][0], 0.0f, abs_error); + EXPECT_NEAR(result.Elements[1][1], 1.0f, abs_error); + EXPECT_NEAR(result.Elements[1][2], 0.0f, abs_error); + EXPECT_NEAR(result.Elements[1][3], 0.0f, abs_error); - EXPECT_NEAR(result.Elements[2][0], 0.0f, abs_error); - EXPECT_NEAR(result.Elements[2][1], -1.0f, abs_error); - EXPECT_NEAR(result.Elements[2][2], 0.0f, abs_error); - EXPECT_NEAR(result.Elements[2][3], 0.0f, abs_error); + EXPECT_NEAR(result.Elements[2][0], 0.0f, abs_error); + EXPECT_NEAR(result.Elements[2][1], 0.0f, abs_error); + EXPECT_NEAR(result.Elements[2][2], 1.0f, abs_error); + EXPECT_NEAR(result.Elements[2][3], 0.0f, abs_error); - EXPECT_NEAR(result.Elements[3][0], 0.0f, abs_error); - EXPECT_NEAR(result.Elements[3][1], 0.0f, abs_error); - EXPECT_NEAR(result.Elements[3][2], 0.0f, abs_error); - EXPECT_NEAR(result.Elements[3][3], 1.0f, abs_error); + EXPECT_NEAR(result.Elements[3][0], 0.0f, abs_error); + EXPECT_NEAR(result.Elements[3][1], 0.0f, abs_error); + EXPECT_NEAR(result.Elements[3][2], 0.0f, abs_error); + EXPECT_NEAR(result.Elements[3][3], 1.0f, abs_error); + } + + { + // Straightforward 90 degree rotation + hmm_quaternion rot = HMM_Quaternion(0.707107f, 0.0f, 0.0f, 0.707107f); + + hmm_mat4 result = HMM_QuaternionToMat4(rot); + + EXPECT_NEAR(result.Elements[0][0], 1.0f, abs_error); + EXPECT_NEAR(result.Elements[0][1], 0.0f, abs_error); + EXPECT_NEAR(result.Elements[0][2], 0.0f, abs_error); + EXPECT_NEAR(result.Elements[0][3], 0.0f, abs_error); + + EXPECT_NEAR(result.Elements[1][0], 0.0f, abs_error); + EXPECT_NEAR(result.Elements[1][1], 0.0f, abs_error); + EXPECT_NEAR(result.Elements[1][2], 1.0f, abs_error); + EXPECT_NEAR(result.Elements[1][3], 0.0f, abs_error); + + EXPECT_NEAR(result.Elements[2][0], 0.0f, abs_error); + EXPECT_NEAR(result.Elements[2][1], -1.0f, abs_error); + EXPECT_NEAR(result.Elements[2][2], 0.0f, abs_error); + EXPECT_NEAR(result.Elements[2][3], 0.0f, abs_error); + + EXPECT_NEAR(result.Elements[3][0], 0.0f, abs_error); + EXPECT_NEAR(result.Elements[3][1], 0.0f, abs_error); + EXPECT_NEAR(result.Elements[3][2], 0.0f, abs_error); + EXPECT_NEAR(result.Elements[3][3], 1.0f, abs_error); + } } TEST(QuaternionOps, FromAxisAngle) diff --git a/test/categories/VectorOps.h b/test/categories/VectorOps.h index be2d44e..3abe76c 100644 --- a/test/categories/VectorOps.h +++ b/test/categories/VectorOps.h @@ -136,14 +136,28 @@ TEST(VectorOps, NormalizeZero) TEST(VectorOps, Cross) { - hmm_vec3 v1 = HMM_Vec3(1.0f, 2.0f, 3.0f); - hmm_vec3 v2 = HMM_Vec3(4.0f, 5.0f, 6.0f); + { + // Normal cross + hmm_vec3 v1 = HMM_Vec3(1.0f, 2.0f, 3.0f); + hmm_vec3 v2 = HMM_Vec3(4.0f, 5.0f, 6.0f); - hmm_vec3 result = HMM_Cross(v1, v2); + hmm_vec3 result = HMM_Cross(v1, v2); - EXPECT_FLOAT_EQ(result.X, -3.0f); - EXPECT_FLOAT_EQ(result.Y, 6.0f); - EXPECT_FLOAT_EQ(result.Z, -3.0f); + EXPECT_FLOAT_EQ(result.X, -3.0f); + EXPECT_FLOAT_EQ(result.Y, 6.0f); + EXPECT_FLOAT_EQ(result.Z, -3.0f); + } + + { + // Vector with itself + hmm_vec3 v = HMM_Vec3(1.0f, 2.0f, 3.0f); + + hmm_vec3 result = HMM_Cross(v, v); + + EXPECT_FLOAT_EQ(result.X, 0.0f); + EXPECT_FLOAT_EQ(result.Y, 0.0f); + EXPECT_FLOAT_EQ(result.Z, 0.0f); + } } TEST(VectorOps, DotVec2)