Skip to content

Instantly share code, notes, and snippets.

@rbnelr
Created February 29, 2020 17:46
Show Gist options
  • Save rbnelr/7f96c6ea6bc60593ab95ce10203dbc69 to your computer and use it in GitHub Desktop.
Save rbnelr/7f96c6ea6bc60593ab95ce10203dbc69 to your computer and use it in GitHub Desktop.
#include "camera.hpp"
#include "../input.hpp"
#include "assert.h"
void Camera_View::calc_frustrum () {
// frustrum_corners set to cam space by perspective_matrix() or orthographic_matrix()
for (int i=0; i<8; ++i)
frustrum.corners[i] = cam_to_world * frustrum.corners[i];
//near, left, right, bottom, up, far
auto& corn = frustrum.corners;
frustrum.planes[0] = {
(corn[0] + corn[2]) / 2,
normalize(cross(corn[2] - corn[1], corn[0] - corn[1]))
};
frustrum.planes[1] = {
(corn[0] + corn[3]) / 2,
normalize(cross(corn[3] - corn[0], corn[4] - corn[0]))
};
frustrum.planes[2] = {
(corn[1] + corn[2]) / 2,
normalize(cross(corn[1] - corn[2], corn[6] - corn[2]))
};
frustrum.planes[3] = {
(corn[0] + corn[1]) / 2,
normalize(cross(corn[0] - corn[1], corn[5] - corn[1]))
};
frustrum.planes[4] = {
(corn[3] + corn[2]) / 2,
normalize(cross(corn[2] - corn[3], corn[7] - corn[3]))
};
frustrum.planes[5] = {
(corn[4] + corn[6]) / 2,
normalize(cross(corn[7] - corn[4], corn[5] - corn[4]))
};
}
float4x4 Camera::calc_cam_to_clip (View_Frustrum* frust, float4x4* clip_to_cam) {
float aspect = (float)input.window_size.x / (float)input.window_size.y;
if (mode == PERSPECTIVE) {
return perspective_matrix(vfov, aspect, clip_near, clip_far, frust, clip_to_cam);
} else {
assert(mode == ORTHOGRAPHIC);
return orthographic_matrix(ortho_vsize, aspect, clip_near, clip_far, frust, clip_to_cam);
}
}
float4x4 perspective_matrix (float vfov, float aspect, float clip_near, float clip_far, View_Frustrum* frust, float4x4* clip_to_cam) {
float2 frust_scale;
frust_scale.y = tan(vfov / 2);
frust_scale.x = frust_scale.y * aspect;
float hfov = atan(frust_scale.x) * 2;
float2 frust_scale_inv = 1.0f / frust_scale;
float x = frust_scale_inv.x;
float y = frust_scale_inv.y;
float a = (clip_far + clip_near) / (clip_near - clip_far);
float b = (2.0f * clip_far * clip_near) / (clip_near - clip_far);
if (frust) {
frust->corners[0] = float3(-frust_scale.x * clip_near, -frust_scale.y * clip_near, -clip_near);
frust->corners[1] = float3(+frust_scale.x * clip_near, -frust_scale.y * clip_near, -clip_near);
frust->corners[2] = float3(+frust_scale.x * clip_near, +frust_scale.y * clip_near, -clip_near);
frust->corners[3] = float3(-frust_scale.x * clip_near, +frust_scale.y * clip_near, -clip_near);
frust->corners[4] = float3(-frust_scale.x * clip_far , -frust_scale.y * clip_far , -clip_far );
frust->corners[5] = float3(+frust_scale.x * clip_far , -frust_scale.y * clip_far , -clip_far );
frust->corners[6] = float3(+frust_scale.x * clip_far , +frust_scale.y * clip_far , -clip_far );
frust->corners[7] = float3(-frust_scale.x * clip_far , +frust_scale.y * clip_far , -clip_far );
}
if (clip_to_cam) {
*clip_to_cam = float4x4(
1.0f/x, 0, 0, 0,
0, 1.0f/y, 0, 0,
0, 0, 0, -1,
0, 0, 1.0f/b, a/b
);
}
return float4x4(
x, 0, 0, 0,
0, y, 0, 0,
0, 0, a, b,
0, 0, -1, 0
);
}
float4x4 orthographic_matrix (float vsize, float aspect, float clip_near, float clip_far, View_Frustrum* frust, float4x4* clip_to_cam) {
float hsize = vsize * aspect;
float x = 2.0f / hsize;
float y = 2.0f / vsize;
float a = -2.0f / (clip_far - clip_near);
float b = clip_near * a - 1;
if (frust) {
frust->corners[0] = float3(1.0f / -x, 1.0f / -y, -clip_near);
frust->corners[1] = float3(1.0f / +x, 1.0f / -y, -clip_near);
frust->corners[2] = float3(1.0f / +x, 1.0f / +y, -clip_near);
frust->corners[3] = float3(1.0f / -x, 1.0f / +y, -clip_near);
frust->corners[4] = float3(1.0f / -x, 1.0f / -y, -clip_far );
frust->corners[5] = float3(1.0f / +x, 1.0f / -y, -clip_far );
frust->corners[6] = float3(1.0f / +x, 1.0f / +y, -clip_far );
frust->corners[7] = float3(1.0f / -x, 1.0f / +y, -clip_far );
}
if (clip_to_cam) {
*clip_to_cam = float4x4(
1.0f/x, 0, 0, 0,
0, 1.0f/y, 0, 0,
0, 0, 1.0f/a, -b/a,
0, 0, 0, 1
);
}
return float4x4(
x, 0, 0, 0,
0, y, 0, 0,
0, 0, a, b,
0, 0, 0, 1
);
}
float wrap_azimuth (float azimuth) {
return wrap(azimuth, deg(-180), deg(180));
}
float clamp_elevation (float elevation, float down_limit, float up_limit) {
return clamp(elevation, deg(-90) + down_limit, deg(+90) - up_limit);
}
float wrap_roll (float roll) {
return wrap(roll, deg(-180), deg(180));
}
void rotate_with_mouselook (float* azimuth, float* elevation, float vfov) {
auto raw_mouselook = input.get_mouselook_delta();
float delta_x = raw_mouselook.x * vfov / input.mouselook_sensitiviy_divider;
float delta_y = raw_mouselook.y * vfov / input.mouselook_sensitiviy_divider;
*azimuth -= delta_x;
*elevation += delta_y;
*azimuth = wrap_azimuth(*azimuth);
*elevation = clamp_elevation(*elevation, input.view_elevation_down_limit, input.view_elevation_up_limit);
}
float3x3 calc_ae_rotation (float2 ae, float3x3* out_inverse) {
if (out_inverse)
*out_inverse = rotate3_Z(+ae.x) * rotate3_X(+ae.y + deg(90));
return rotate3_X(-ae.y - deg(90)) * rotate3_Z(-ae.x);
}
float3x3 calc_aer_rotation (float3 aer, float3x3* out_inverse) {
if (out_inverse)
*out_inverse = rotate3_Z(+aer.x) * rotate3_X(+aer.y + deg(90)) * rotate3_Z(-aer.z);
return rotate3_Z(+aer.z) * rotate3_X(-aer.y - deg(90)) * rotate3_Z(-aer.x);
}
Camera_View Flycam::update () {
//// look
rotate_with_mouselook(&rot_aer.x, &rot_aer.y, vfov);
float3x3 cam_to_world_rot;
float3x3 world_to_cam_rot = calc_aer_rotation(rot_aer, &cam_to_world_rot);
{ //// movement
float3 move_dir = 0;
if (input.buttons[GLFW_KEY_A] .is_down) move_dir.x -= 1;
if (input.buttons[GLFW_KEY_D] .is_down) move_dir.x += 1;
if (input.buttons[GLFW_KEY_W] .is_down) move_dir.z -= 1;
if (input.buttons[GLFW_KEY_S] .is_down) move_dir.z += 1;
if (input.buttons[GLFW_KEY_LEFT_CONTROL].is_down) move_dir.y -= 1;
if (input.buttons[GLFW_KEY_SPACE] .is_down) move_dir.y += 1;
move_dir = normalizesafe(move_dir);
float move_speed = length(move_dir); // could be analog with gamepad
if (move_speed == 0.0f)
cur_speed = base_speed; // no movement ticks down speed
if (input.buttons[GLFW_KEY_LEFT_SHIFT].is_down) {
move_speed *= fast_multiplier;
cur_speed += base_speed * speedup_factor * input.unscaled_dt;
}
cur_speed = clamp(cur_speed, base_speed, max_speed);
float3 translation_cam_space = cur_speed * move_dir * input.unscaled_dt;
pos += cam_to_world_rot * translation_cam_space;
}
{ //// fov change
if (!input.buttons[GLFW_KEY_F].is_down) {
float delta_log = 0.1f * input.mouse_wheel_delta;
base_speed = powf(2, log2f(base_speed) +delta_log );
} else {
float delta_log = -0.1f * input.mouse_wheel_delta;
vfov = clamp(powf(2, log2f(vfov) +delta_log ), deg(1.0f/10), deg(170));
}
}
//// matrix calc
Camera_View v;
v.world_to_cam = world_to_cam_rot * translate(-pos);
v.cam_to_world = translate(pos) * cam_to_world_rot;
v.cam_to_clip = calc_cam_to_clip(&v.frustrum, &v.clip_to_cam);
v.clip_near = clip_near;
v.clip_far = clip_far;
v.calc_frustrum();
return v;
}
#pragma once
#include "../kissmath.hpp"
#include "../dear_imgui.hpp"
#include "../util/collision.hpp"
enum perspective_mode {
PERSPECTIVE,
ORTHOGRAPHIC
};
struct View_Frustrum {
// The view frustrum planes in world space
// in the order: near, left, right, bottom, up, far
// far was put last because it is not really needed in view frustrum culling since it is undesirable for the far plane to intersect any geometry anyway, so it usually gets get to far enough to never cull anything
Plane planes[6];
// Frustrum corners in world space
// in the order: LBN, RBN, RTN, LTN, LBF, RBF, RTF, LTF
// (L=left R=right B=bottom T=top N=near F=far)
float3 corners[8];
};
struct Camera_View {
// World space to camera space transform
float3x4 world_to_cam;
// Camera space to world space transform
float3x4 cam_to_world;
// Camera space to clip space transform
float4x4 cam_to_clip;
// Clip space to camera space transform
float4x4 clip_to_cam;
// near clip plane distance (positive)
float clip_near;
// far clip plane distance (positive)
float clip_far;
View_Frustrum frustrum;
void calc_frustrum();
};
class Camera {
public:
// camera position
float3 pos;
// TODO: add quaternions
//quaternion base_ori = quaternion::identity;
// camera rotation azimuth, elevation, roll in radians
// azimuth 0 has the camera looking towards +y, rotates the camera ccw around the z axis (deg(90) would have it face -x)
// elevation [0, deg(128)] represents [looking_down (-z), looking_up (+z)]
// roll rolls ccw with 0 having the camera top point up (+z)
float3 rot_aer;
perspective_mode mode = PERSPECTIVE;
// near clipping plane
float clip_near = 1.0f/32;
// far clipping plane
float clip_far = 8192;
// [mode == PERSPECTIVE] vertical fov (horizontal fov depends on render target aspect ratio)
float vfov = deg(70);
// [mode == ORTHOGRAPHIC] vertical size (horizontal size depends on render target aspect ratio)
float ortho_vsize = 10;
Camera (float3 pos=0, float3 rot_aer=0): pos{pos}, rot_aer{rot_aer} {}
virtual ~Camera () = default;
void imgui (const char* name=nullptr) {
if (!imgui_push("Camera", name, false)) return;
int cur_mode = (int)mode;
ImGui::Combo("mode", &cur_mode, "PERSPECTIVE\0ORTHOGRAPHIC\0");
mode = (perspective_mode)cur_mode;
ImGui::DragFloat3("pos", &pos.x, 0.05f);
float3 rot_aer_deg = to_degrees(rot_aer);
if (ImGui::DragFloat3("rot_aer", &rot_aer_deg.x, 0.05f))
rot_aer = to_radians(rot_aer_deg);
ImGui::DragFloat("clip_near", &clip_near, 0.05f);
ImGui::DragFloat("clip_far", &clip_far, 0.05f);
ImGui::SliderAngle("vfov", &vfov, 0, 180.0f);
ImGui::DragFloat("ortho_vsize", &ortho_vsize, 0.05f);
imgui_pop();
}
// Calculate camera projection matrix
float4x4 calc_cam_to_clip (View_Frustrum* frust=nullptr, float4x4* clip_to_cam=nullptr);
};
float4x4 perspective_matrix (float vfov, float aspect, float clip_near=1.0f/32, float clip_far=8192, View_Frustrum* frust=nullptr, float4x4* clip_to_cam=nullptr);
float4x4 orthographic_matrix (float vsize, float aspect, float clip_near=1.0f/32, float clip_far=8192, View_Frustrum* frust=nullptr, float4x4* clip_to_cam=nullptr);
// rotate azimuth, elevation via mouselook
void rotate_with_mouselook (float* azimuth, float* elevation, float vfov);
// Calculate rotation matricies for azimuth, elevation
float3x3 calc_ae_rotation (float2 ae, float3x3* out_inverse=nullptr);
// Calculate rotation matricies for azimuth, elevation and roll
float3x3 calc_aer_rotation (float3 aer, float3x3* out_inverse=nullptr);
// Free flying camera
class Flycam : public Camera {
public:
float base_speed = 0.5f;
float max_speed = 1000.0f;
float speedup_factor = 2;
float fast_multiplier = 4;
float cur_speed = 0;
// TODO: configurable input bindings
Flycam (float3 pos=0, float3 rot_aer=0, float base_speed=0.5f): Camera(pos, rot_aer), base_speed{base_speed} {}
void imgui (const char* name=nullptr) {
if (!imgui_push("Flycam", name)) return;
Camera::imgui(name);
ImGui::DragFloat("base_speed", &base_speed, 0.05f, 0, FLT_MAX / INT_MAX, "%.3f", 1.05f);
ImGui::DragFloat("max_speed", &max_speed, 0.05f, 0, FLT_MAX / INT_MAX, "%.3f", 1.05f);
ImGui::DragFloat("speedup_factor", &speedup_factor, 0.001f);
ImGui::DragFloat("fast_multiplier", &fast_multiplier, 0.05f);
ImGui::Text("cur_speed: %.3f", cur_speed);
imgui_pop();
}
Camera_View update ();
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment