#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 ASPECT_RATIO 16.f/9.f #define NEAR_CLIP_PLANE 20.f // forward declarations inline glm::vec3 convertv3f(v3f v); // interface void cameraInitPerspective(camera& cam, glm::vec3 position, glm::vec3 target, glm::vec3 world_up) { 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: using pythagoras' to get absolute value of relative axis for vAngle component real32 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; } 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 } v2f 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); v2f v(vU.x, vU.y); return v; } v3f cameraCreateRay(camera& cam, v2i vp_coords, v2i vp_dims) { #if 0 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_start_ndc = glm::vec4(x, y, -1.f, 1.f); glm::vec4 ray_end_ndc = glm::vec4(x, y, 0, 1.f); glm::mat4 M = glm::inverse(cam.projection * cam.view); glm::vec4 ray_start_world = M * ray_start_ndc; glm::vec4 ray_end_world = M * ray_end_ndc; ray_start_world /= ray_start_world.w; ray_end_world /= ray_end_world.w; glm::vec3 ray_origin = glm::vec3(ray_start_world); glm::vec3 ray_dir = glm::normalize(glm::vec3(ray_end_world - ray_start_world)); return v3f(ray_dir.x, ray_dir.y, ray_dir.z); #else // 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 v3f(ray_world.x, ray_world.y, ray_world.z); #endif } bool cameraIntersectPlane(camera& cam, v3f ray, v3f plane_origin, v3f plane_normal, v3f& intersection) { glm::vec3 c_o = cam.position; glm::vec3 r = convertv3f(ray); //glm::vec3 r = cam.forward; glm::vec3 p_o = convertv3f(plane_origin); glm::vec3 p_n = convertv3f(plane_normal); float divisor = glm::dot(r, p_n); if (divisor <= 0.000001f && divisor >= -0.000001f) // NOTE: ray and plane are co-planar return false; #if 1 float distance = glm::dot((p_o - c_o), p_n) / divisor; glm::vec3 xsect = c_o + (r * distance); #else float distance = glm::dot(n, (p_o - c_o)) / glm::dot(n, r); glm::vec3 xsect = c_o + r * distance; #endif intersection = v3f(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, int32 xrel, int32 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 inline glm::vec3 convertv3f(v3f v) { return glm::vec3(v.x, v.y, v.z); }