5 changed files with 337 additions and 3 deletions
@ -0,0 +1,64 @@ |
|||||||
|
|
||||||
|
#pragma once |
||||||
|
|
||||||
|
#include <glm/glm.hpp> |
||||||
|
|
||||||
|
#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); |
||||||
|
|
||||||
@ -0,0 +1,23 @@ |
|||||||
|
|
||||||
|
#pragma once |
||||||
|
|
||||||
|
#include <SDL2/SDL.h> |
||||||
|
|
||||||
|
|
||||||
|
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); |
||||||
|
|
||||||
@ -0,0 +1,207 @@ |
|||||||
|
|
||||||
|
#include <cassert> |
||||||
|
|
||||||
|
#include <GL/glew.h> |
||||||
|
#include <glm/glm.hpp> |
||||||
|
#include <glm/gtc/matrix_transform.hpp> |
||||||
|
|
||||||
|
#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
|
||||||
|
|
||||||
@ -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); |
||||||
|
} |
||||||
|
|
||||||
Loading…
Reference in new issue