From 35e61250e98b6bd48a6753b92a681263f074b2a5 Mon Sep 17 00:00:00 2001 From: cinnaboot Date: Wed, 2 Mar 2022 17:22:00 -0500 Subject: [PATCH] re-add camera and input interfaces --- include/camera.h | 64 ++++++++++++++ include/input.h | 23 +++++ include/tangerine.h | 4 +- src/camera.cpp | 207 ++++++++++++++++++++++++++++++++++++++++++++ src/input.cpp | 42 +++++++++ 5 files changed, 337 insertions(+), 3 deletions(-) create mode 100644 include/camera.h create mode 100644 include/input.h create mode 100644 src/camera.cpp create mode 100644 src/input.cpp diff --git a/include/camera.h b/include/camera.h new file mode 100644 index 0000000..208ca8c --- /dev/null +++ b/include/camera.h @@ -0,0 +1,64 @@ + +#pragma once + +#include + +#include "types.h" + + +struct Camera +{ + float hAngle; + float vAngle; + + glm::vec3 position; + glm::vec3 forward; + glm::vec3 up; + glm::vec3 left; + glm::vec3 target; + glm::vec3 world_up; + + glm::mat4 model; + glm::mat4 view; + glm::mat4 projection; + glm::mat4 MVP; +}; + + +// FIXME: we should keep to our convention of passing pointers to structs in +// these interface functions +glm::vec2 +cameraUnproject(Camera& cam, int x, int y, int vp_width, int vp_height); + +glm::vec3 +cameraCreateRay(Camera& cam, glm::ivec2 vp_coords, glm::ivec2 vp_dims); + +bool +cameraIntersectPlane(Camera& cam, + glm::vec3 ray, + glm::vec3 plane_origin, + glm::vec3 plane_normal, + glm::vec3& intersection); + +void +cameraInitPerspective(Camera* cam, + glm::vec3 position, + glm::vec3 target, + glm::vec3 world_up, + float aspect_ratio = 16.f / 9.f); + +void +cameraMove(Camera& cam, + bool up, + bool left, + bool down, + bool right, + bool forward, + bool backward); + +void +cameraRotate(Camera& cam, i32 xrel, i32 yrel); + +void +cameraRoll(Camera& cam, bool CW, bool CCW); + diff --git a/include/input.h b/include/input.h new file mode 100644 index 0000000..0875a86 --- /dev/null +++ b/include/input.h @@ -0,0 +1,23 @@ + +#pragma once + +#include + + +struct InputState +{ + bool window_closed; + bool escape; + bool left; + bool right; + bool up; + bool down; +}; + +void +inputProcessEvent(InputState* is, SDL_Event& e); + +// NOTE: convenience function that provides a while(SDL_PollEvents()) loop +void +inputProcessEvents(InputState* is); + diff --git a/include/tangerine.h b/include/tangerine.h index d16958b..5c3ce6b 100644 --- a/include/tangerine.h +++ b/include/tangerine.h @@ -69,6 +69,7 @@ #include #include "asset.h" +#include "camera.h" #include "entity.h" #include "dumbLog.h" #include "GLDebug.h" @@ -160,9 +161,6 @@ struct LightsBuffer void* buffer; }; -// FIXME: re-implement Camera interface -struct Camera; - struct RenderState { bool running; diff --git a/src/camera.cpp b/src/camera.cpp new file mode 100644 index 0000000..81c5ccc --- /dev/null +++ b/src/camera.cpp @@ -0,0 +1,207 @@ + +#include + +#include +#include +#include + +#include "camera.h" + +// TODO: add these props to scene json +#define MOVE_SPEED 5.f +#define ROTATE_SPEED 0.005f +#define CAMERA_Z_CLAMP_ANGLE 85.f +#define FOV 60.f +#define NEAR_CLIP_PLANE 20.f + + +// forward declarations + + +// interface + +void +cameraInitPerspective(Camera* cam, + glm::vec3 position, + glm::vec3 target, + glm::vec3 world_up, + float aspect_ratio) +{ + assert(aspect_ratio > 0); + + cam->position = position; + cam->target = target; + cam->world_up = world_up; + cam->projection = glm::infinitePerspective(glm::radians(FOV), + aspect_ratio, + NEAR_CLIP_PLANE); + + cam->forward = glm::normalize(target - position); + cam->left = glm::normalize(glm::cross(cam->world_up, cam->forward)); + cam->up = glm::normalize(glm::cross(cam->forward, cam->left)); + + cam->hAngle = glm::atan(cam->forward.x, cam->forward.y); + // NOTE: get absolute value of relative axis for vAngle component + float len = glm::sqrt(glm::pow(cam->forward.y, 2) + + glm::pow(cam->forward.x, 2)); + cam->vAngle = glm::atan(cam->forward.z, len); + + cam->view = + glm::lookAt(cam->position, cam->position + cam->forward, cam->up); + cam->model = glm::mat4(1.0f); + cam->MVP = cam->projection * cam->view * cam->model; +} + +// TODO: re-add orthographic camera +void +cameraInitOrthographic(/*camera& cam, */) +{ +#if 0 + // left, right, bottom, top, zNear, zFar + cam.projection = glm::ortho(0.f, 1280.0f, 0.f, 720.0f, 0.1f, 100.0f); + cam.view = glm::lookAt( + glm::vec3(0.0f, 0.0f, 1.0f), // camera position + glm::vec3(0.0f, 0.0f, 0.0f), // look at position + glm::vec3(0,1,0) // "up" vector + ); + + cam.model = glm::mat4(1.0f); + cam.MVP = cam.projection * cam.view * cam.model; +#endif +} + +glm::vec2 +cameraUnproject(Camera& cam, int x, int y, int vp_width, int vp_height) +{ + // NOTE: using depth buffer may not be as accurate as doing ray-cast + GLfloat depth; + glReadPixels(x, y, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, &depth); + glm::vec4 viewport = glm::vec4(0, 0, vp_width, vp_height); + glm::vec3 wincoord = glm::vec3(x, y, depth); + glm::vec3 vU = glm::unProject(wincoord, cam.view, cam.projection, viewport); + + return glm::vec2(vU.x, vU.y); +} + +glm::vec3 +cameraCreateRay(Camera& cam, glm::ivec2 vp_coords, glm::ivec2 vp_dims) +{ + // NOTE: http://antongerdelan.net/opengl/raycasting.html + float x = 2.f * vp_coords.x / vp_dims.x - 1.f; + float y = 2.f * vp_coords.y / vp_dims.y - 1.f; + glm::vec4 ray_clip = glm::vec4(x, y, -1.f, 1.f); + glm::vec4 ray_eye = glm::inverse(cam.projection) * ray_clip; + ray_eye = glm::vec4(ray_eye.x, ray_eye.y, -1.f, 0); // NOTE: reset as ray + glm::vec4 ray_world = glm::normalize(glm::inverse(cam.view) * ray_eye); + + return glm::vec3(ray_world.x, ray_world.y, ray_world.z); +} + +bool +cameraIntersectPlane(Camera& cam, + glm::vec3 ray, + glm::vec3 plane_origin, + glm::vec3 plane_normal, + glm::vec3& intersection) +{ + // NOTE: https://www.scratchapixel.com/lessons/3d-basic-rendering/minimal-ray-tracer-rendering-simple-shapes/ray-plane-and-ray-disk-intersection + float divisor = glm::dot(ray, plane_normal); + + if (divisor <= 0.000001f && divisor >= -0.000001f) // NOTE: ray and plane are co-planar + return false; + + float distance = + glm::dot((plane_origin - cam.position), plane_normal) / divisor; + glm::vec3 xsect = cam.position + (ray * distance); + intersection = glm::vec3(xsect.x, xsect.y, xsect.z); + + return true; +} + +void +cameraMove(Camera& cam, + bool up, + bool left, + bool down, + bool right, + bool forward, + bool backward) +{ + if (!up && !left && !down && !right && !forward && !backward) + return; + + glm::vec3 f = cam.forward; + glm::vec3 u = cam.up; + glm::vec3 old = cam.position; + glm::vec3 &p = cam.position; + glm::vec3 v(0.f); // normalized direction + + // TODO: still seems like we're adding magnitude when moving in 2 directions +#if 0 + if (forward) v = glm::normalize(v + f); + if (backward) v = glm::normalize(v - f); + if (up) v = glm::normalize(v + u); + if (down) v = glm::normalize(v - u); + if (left) v -= glm::normalize(glm::cross(f, u)); + if (right) v -= glm::normalize(glm::cross(u, f)); +#else + if (forward) v += f; + if (backward) v -= f; + if (up) v += u; + if (down) v -= u; + if (left) v -= glm::cross(f, u); + if (right) v -= glm::cross(u, f); +#endif + + p += (v * MOVE_SPEED); + glm::vec3 diff = old - p; + cam.view = glm::translate(cam.view, diff); + cam.MVP = cam.projection * cam.view * cam.model; +} + +void +cameraRotate(Camera& cam, i32 xrel, i32 yrel) +{ + float &h = cam.hAngle; + float &v = cam.vAngle; + h += ROTATE_SPEED * xrel; + v -= ROTATE_SPEED * yrel; + + // clamp vAngle to prevent gimbal lock + float a = glm::radians(CAMERA_Z_CLAMP_ANGLE); + if (v < (-1 * a)) v = (-1 * a); + if (v > a) v = a; + + cam.forward = glm::vec3( + glm::cos(v) * glm::sin(h), + glm::cos(v) * glm::cos(h), + glm::sin(v) + ); + + glm::normalize(cam.forward); + cam.left = glm::normalize(glm::cross(cam.forward, cam.world_up)); + cam.up = glm::normalize(glm::cross(cam.left, cam.forward)); + + cam.view = glm::lookAt(cam.position, cam.position + cam.forward, cam.up); + cam.MVP = cam.projection * cam.view * cam.model; +} + +void +cameraRoll(Camera& cam, bool CW, bool CCW) +{ + if ((!CW && !CCW) || (CW && CCW)) + return; + + float a = 0.005f; + if (CW) a *= 1; + if (CCW) a *= -1; + glm::mat4 m = glm::rotate(glm::mat4(1.f), a, cam.forward); + glm::vec4 v(cam.up.x, cam.up.y, cam.up.z, 0); + v = v * m; + cam.up = glm::vec3(v.x, v.y, v.z); + cam.view *= m; + cam.MVP = cam.projection * cam.view * cam.model; +} + +// internal + diff --git a/src/input.cpp b/src/input.cpp new file mode 100644 index 0000000..8a15fa3 --- /dev/null +++ b/src/input.cpp @@ -0,0 +1,42 @@ + +#include "input.h" + + +void +inputProcessEvent(InputState* is, SDL_Event& e) +{ + switch (e.type) { + case SDL_QUIT: + is->window_closed = true; + break; + case SDL_KEYDOWN: + switch (e.key.keysym.sym) { + case SDLK_ESCAPE: is->escape = true; break; + case SDLK_LEFT: is->left = true; break; + case SDLK_RIGHT: is->right = true; break; + case SDLK_UP: is->up = true; break; + case SDLK_DOWN: is->down = true; break; + } + break; + case SDL_KEYUP: + switch (e.key.keysym.sym) { + case SDLK_ESCAPE: is->escape = false; break; + case SDLK_LEFT: is->left = false; break; + case SDLK_RIGHT: is->right = false; break; + case SDLK_UP: is->up = false; break; + case SDLK_DOWN: is->down = false; break; + } + break; + default: break; + } +} + +void +inputProcessEvents(InputState* is) +{ + SDL_Event e; + + while (SDL_PollEvent(&e)) + inputProcessEvent(is, e); +} +