Added camera!

- Can be translated locally or moved to a point in the world
 - Rotates locally around local x- and z-axis and around global y-axis.

Added key checks to test camera movement.

Added Quaternions (Inherits from CoordinatesVector<double,4>) with a view to handling the camera orientation.

Added equality operators for CoordinatesVectors.

Removed slow `glutGet()` calls.
Added custom reshape callback as we now store the window parameters to avoid some `glutGet()` calls.

Renamed `KeyStateManager` to `InputStatus` as it handles more than key states now.

Mouse movement callback now captures the cursor, handles its re-centering and when the cursor enters the window far away from its last position.

TODO : Consider limiting rotations for the camera (Prevents flipping the head around)
This commit is contained in:
Teo-CD 2019-10-28 23:45:44 +01:00 committed by trotFunky
parent e79f88bb9b
commit 4a5b0f5829
11 changed files with 494 additions and 174 deletions

70
src/Camera.cpp Normal file
View file

@ -0,0 +1,70 @@
//
// Created by trotfunky on 19/10/2019.
//
#include "Camera.h"
Camera::Camera(const Vec3d& eye_pos, const Vec3d& look_direction, const Vec3d& up_vector) :
eye(eye_pos),
gaze(look_direction),
gaze_up(up_vector)
{
// Normalize the gaze vectors as they form the base for the local coordinates
gaze.normalize();
gaze_up.normalize();
compute_base_change();
}
void Camera::translate(const Vec3d& translation)
{
eye = eye + translation * rotation_quaternion;
}
void Camera::rotate(const Vec3d& rotation)
{
Quaternion x_rotation{gaze*sin(rotation.x/2)};
x_rotation.w = cos(rotation.x/2);
x_rotation.normalize();
gaze_up = x_rotation * gaze_up;
Quaternion z_rotation{gaze.cross_product(gaze_up)*sin(rotation.z/2)};
z_rotation.w = cos(rotation.z/2);
z_rotation.normalize();
gaze = z_rotation * gaze;
gaze_up = z_rotation * gaze_up;
Quaternion y_rotation{0,sin(rotation.y),0,cos(rotation.y)};
y_rotation.normalize();
gaze = y_rotation * gaze;
gaze_up = y_rotation * gaze_up;
compute_base_change();
}
void Camera::look()
{
gluLookAt(eye.x,eye.y,eye.z,
eye.x+gaze.x,eye.y+gaze.y,eye.z+gaze.z,
gaze_up.x,gaze_up.y,gaze_up.z);
}
void Camera::set_position(const Vec3d& eye_pos)
{
gaze = eye_pos;
}
void Camera::compute_base_change()
{
// Third vector of the base, should already be normalized
Vec3d gaze_normal = gaze.cross_product(gaze_up);
double quaternion_w = std::sqrt(1 + gaze.x + gaze_up.y + gaze_normal.z) / 2;
rotation_quaternion = {(gaze_normal.y - gaze_up.z)/(4*quaternion_w),
(gaze.z - gaze_normal.x)/(4*quaternion_w),
(gaze_up.x - gaze.y)/(4*quaternion_w),
quaternion_w};
rotation_quaternion.normalize();
}

44
src/Camera.h Normal file
View file

@ -0,0 +1,44 @@
//
// Created by trotfunky on 19/10/2019.
//
#ifndef TESTS_OPENGL_CAMERA_H
#define TESTS_OPENGL_CAMERA_H
#include <cmath>
#include <GL/glu.h>
#include "Quaternion.h"
#include "Vectors.h"
class Camera {
public:
Camera(const Vec3d& eye_pos,const Vec3d& look_direction,const Vec3d& up_vector);
/// Translates the camera relative to its current position and look direction.
/// \param translation Translation where x is pointing in the direction of vision.
void translate(const Vec3d& translation);
/// Rotates the gaze around its local x and z but around global y, relatively to the current orientation.
/// This is to provide a coherent movement in regards to mouse movement.
/// \param rotation Angles are radians to rotate about each axis.
void rotate(const Vec3d& rotation);
/// Calls the gluLookAt function for the camera.
void look();
void set_position(const Vec3d& eye_pos);
private:
Vec3d eye;
Vec3d gaze;
Vec3d gaze_up;
/// Quaternion from the world coordinates to the local coordinates
Quaternion rotation_quaternion;
/// Compute rotation quaternion from the global coordinates to the local coordinates.
/// As per https://math.stackexchange.com/questions/2122668/calculate-orientation-quaternion-given-two-axes-of-a-coordinate-system
void compute_base_change();
};
#endif //TESTS_OPENGL_CAMERA_H

165
src/InputStatus.cpp Normal file
View file

@ -0,0 +1,165 @@
//
// Created by trotfunky on 07/10/2019.
//
#include "InputStatus.h"
#include <iostream>
// Initialize static members
Vec2i InputStatus::window_center;
Vec2f InputStatus::mouse_sensitivity{100, 100};
std::map<unsigned char,bool> InputStatus::ascii_keys_status = {};
std::map<int,bool> InputStatus::special_keys_status = {};
std::map<int,bool> InputStatus::mouse_button_status = {};
Vec2i InputStatus::current_mouse_delta;
Vec2i InputStatus::last_mouse_delta;
Vec2i InputStatus::last_mouse_position;
void InputStatus::register_glut_callbacks()
{
glutKeyboardFunc(InputStatus::key_press);
glutKeyboardUpFunc(InputStatus::key_up);
glutSpecialFunc(InputStatus::special_key_press);
glutSpecialUpFunc(InputStatus::special_key_up);
glutMouseFunc(InputStatus::mouse_click);
glutPassiveMotionFunc(InputStatus::mouse_movement);
glutMotionFunc(InputStatus::mouse_movement);
}
// ==================
// Keyboard
// ==================
bool InputStatus::is_key_pressed(unsigned char key)
{
if (ascii_keys_status.find(key) == ascii_keys_status.end())
{
return false;
}
return ascii_keys_status.at(key);
}
bool InputStatus::is_special_key_pressed(int key)
{
if (special_keys_status.find(key) == special_keys_status.end())
{
return false;
}
return special_keys_status.at(key);
}
void InputStatus::key_press(unsigned char event_key, int mouse_x, int mouse_y)
{
update_key(event_key,true);
mouse_movement(mouse_x,mouse_y);
}
void InputStatus::key_up(unsigned char event_key, int mouse_x, int mouse_y)
{
update_key(event_key,false);
mouse_movement(mouse_x,mouse_y);
}
void InputStatus::special_key_press(int event_key, int mouse_x, int mouse_y)
{
update_special_key(event_key,true);
mouse_movement(mouse_x,mouse_y);
}
void InputStatus::special_key_up(int event_key, int mouse_x, int mouse_y)
{
update_special_key(event_key,false);
mouse_movement(mouse_x,mouse_y);
}
void InputStatus::update_key(unsigned char event_key, bool new_status)
{
if (ascii_keys_status.find(event_key) != ascii_keys_status.end())
{
ascii_keys_status.at(event_key) = new_status;
}
else
{
ascii_keys_status.insert({event_key,new_status});
}
}
void InputStatus::update_special_key(int event_key, bool new_status)
{
if (special_keys_status.find(event_key) != special_keys_status.end())
{
special_keys_status.at(event_key) = new_status;
}
else
{
special_keys_status.insert({event_key,new_status});
}
}
// ==================
// Mouse
// ==================
void InputStatus::mouse_movement(int mouse_x, int mouse_y)
{
Vec2i current_frame;
current_frame.x = mouse_x;
current_frame.y = mouse_y;
// Prevent counting the glutWarpPointer movement into account
if (current_frame != window_center)
{
Vec2i frame_delta = current_frame - last_mouse_position;
// Prevent jumping around when entering the window
if (frame_delta.magnitude() < window_center.magnitude()/10)
{
current_mouse_delta.x += frame_delta.x;
current_mouse_delta.y += frame_delta.y;
// Re-center mouse
glutWarpPointer(window_center.x, window_center.y);
}
}
last_mouse_position = current_frame;
}
void InputStatus::mouse_click(int mouse_button, int button_state, int mouse_x, int mouse_y)
{
bool new_status = button_state == GLUT_DOWN;
if (mouse_button_status.find(mouse_button) != mouse_button_status.end())
{
mouse_button_status.at(mouse_button) = new_status;
}
else
{
mouse_button_status.insert({mouse_button,new_status});
}
mouse_movement(mouse_x,mouse_y);
}
bool InputStatus::is_mouse_button_pressed(int mouse_button)
{
if (mouse_button_status.find(mouse_button) == mouse_button_status.end())
{
return false;
}
return mouse_button_status.at(mouse_button);
}
const Vec2i& InputStatus::get_mouse_delta(bool update)
{
if (update)
{
last_mouse_delta = current_mouse_delta;
current_mouse_delta.x = 0;
current_mouse_delta.y = 0;
}
return last_mouse_delta;
}

View file

@ -2,8 +2,8 @@
// Created by trotfunky on 07/10/2019.
//
#ifndef TESTS_OPENGL_KEYSTATEMANAGER_H
#define TESTS_OPENGL_KEYSTATEMANAGER_H
#ifndef TESTS_OPENGL_INPUTSTATUS_H
#define TESTS_OPENGL_INPUTSTATUS_H
#include <map>
#include <GL/glut.h>
@ -13,9 +13,9 @@
/// Handles the key and mouse events from glut and keep their status up to date.
/// "Static class"
class KeyStateManager {
class InputStatus {
public:
KeyStateManager() = delete;
InputStatus() = delete;
static void register_glut_callbacks();
@ -40,6 +40,9 @@ public:
/// Accumulates the movements of the mouse
static void mouse_movement(int mouse_x, int mouse_y);
static Vec2i window_center;
static Vec2f mouse_sensitivity;
private:
// The maps are used to keep track of the keys which were pressed or released during runtime.
static std::map<unsigned char,bool> ascii_keys_status;
@ -58,4 +61,4 @@ private:
};
#endif //TESTS_OPENGL_KEYSTATEMANAGER_H
#endif //TESTS_OPENGL_INPUTSTATUS_H

View file

@ -1,151 +0,0 @@
//
// Created by trotfunky on 07/10/2019.
//
#include "KeyStateManager.h"
#include <iostream>
// Initialize static members
std::map<unsigned char,bool> KeyStateManager::ascii_keys_status = {};
std::map<int,bool> KeyStateManager::special_keys_status = {};
std::map<int,bool> KeyStateManager::mouse_button_status = {};
Vec2i KeyStateManager::current_mouse_delta;
Vec2i KeyStateManager::last_mouse_delta;
Vec2i KeyStateManager::last_mouse_position;
void KeyStateManager::register_glut_callbacks()
{
glutKeyboardFunc(KeyStateManager::key_press);
glutKeyboardUpFunc(KeyStateManager::key_up);
glutSpecialFunc(KeyStateManager::special_key_press);
glutSpecialUpFunc(KeyStateManager::special_key_up);
glutMouseFunc(KeyStateManager::mouse_click);
glutPassiveMotionFunc(KeyStateManager::mouse_movement);
glutMotionFunc(KeyStateManager::mouse_movement);
}
// ==================
// Keyboard
// ==================
bool KeyStateManager::is_key_pressed(unsigned char key)
{
if (ascii_keys_status.find(key) == ascii_keys_status.end())
{
return false;
}
return ascii_keys_status.at(key);
}
bool KeyStateManager::is_special_key_pressed(int key)
{
if (special_keys_status.find(key) == special_keys_status.end())
{
return false;
}
return special_keys_status.at(key);
}
void KeyStateManager::key_press(unsigned char event_key, int mouse_x, int mouse_y)
{
update_key(event_key,true);
mouse_movement(mouse_x,mouse_y);
}
void KeyStateManager::key_up(unsigned char event_key, int mouse_x, int mouse_y)
{
update_key(event_key,false);
mouse_movement(mouse_x,mouse_y);
}
void KeyStateManager::special_key_press(int event_key, int mouse_x, int mouse_y)
{
update_special_key(event_key,true);
mouse_movement(mouse_x,mouse_y);
}
void KeyStateManager::special_key_up(int event_key, int mouse_x, int mouse_y)
{
update_special_key(event_key,false);
mouse_movement(mouse_x,mouse_y);
}
void KeyStateManager::update_key(unsigned char event_key, bool new_status)
{
if (ascii_keys_status.find(event_key) != ascii_keys_status.end())
{
ascii_keys_status.at(event_key) = new_status;
}
else
{
ascii_keys_status.insert({event_key,new_status});
}
}
void KeyStateManager::update_special_key(int event_key, bool new_status)
{
if (special_keys_status.find(event_key) != special_keys_status.end())
{
special_keys_status.at(event_key) = new_status;
}
else
{
special_keys_status.insert({event_key,new_status});
}
}
// ==================
// Mouse
// ==================
void KeyStateManager::mouse_movement(int mouse_x, int mouse_y)
{
Vec2i current_frame;
current_frame.x = mouse_x;
current_frame.y = mouse_y;
Vec2i frame_delta = current_frame - last_mouse_position;
current_mouse_delta.x += frame_delta.x;
current_mouse_delta.y += frame_delta.y;
last_mouse_position = current_frame;
}
void KeyStateManager::mouse_click(int mouse_button, int button_state, int mouse_x, int mouse_y)
{
bool new_status = button_state == GLUT_DOWN;
if (mouse_button_status.find(mouse_button) != mouse_button_status.end())
{
mouse_button_status.at(mouse_button) = new_status;
}
else
{
mouse_button_status.insert({mouse_button,new_status});
}
mouse_movement(mouse_x,mouse_y);
}
bool KeyStateManager::is_mouse_button_pressed(int mouse_button)
{
if (mouse_button_status.find(mouse_button) == mouse_button_status.end())
{
return false;
}
return mouse_button_status.at(mouse_button);
}
const Vec2i& KeyStateManager::get_mouse_delta(bool update)
{
if (update)
{
last_mouse_delta = current_mouse_delta;
current_mouse_delta.x = 0;
current_mouse_delta.y = 0;
}
return last_mouse_delta;
}

80
src/Quaternion.cpp Normal file
View file

@ -0,0 +1,80 @@
//
// Created by trotfunky on 18/10/2019.
//
#include "Quaternion.h"
Quaternion::Quaternion(double axis_x, double axis_y, double axis_z, double w_value) : CoordinatesVector<double,4>()
{
x = axis_x;
y = axis_y;
z = axis_z;
w = w_value;
}
Quaternion::Quaternion(const Quaternion& original) : x(coordinates[0]), y(coordinates[1]), z(coordinates[2]), w(coordinates[3])
{
std::copy(std::begin(original.coordinates),std::end(original.coordinates),std::begin(coordinates));
}
Quaternion::Quaternion(const CoordinatesVector<double, 4>& original)
{
if (this != &original)
{
std::copy(std::begin(original.coordinates),std::end(original.coordinates),std::begin(coordinates));
}
}
Quaternion::Quaternion(const CoordinatesVector<double, 3>& original)
{
std::copy(std::begin(original.coordinates),std::end(original.coordinates),std::begin(coordinates));
w = 0;
}
Quaternion::operator Vec3d() const
{
Vec3d result;
std::copy(std::begin(coordinates),std::begin(coordinates)+3,std::begin(result.coordinates));
return result;
}
Quaternion Quaternion::inverse() const
{
Quaternion inverse = *this;
inverse.x *= -1;
inverse.y *= -1;
inverse.z *= -1;
return inverse;
}
Quaternion Quaternion::operator*(const Quaternion& op) const
{
Quaternion result;
result.w = w*op.w - x*op.x - y*op.y - z*op.z;
result.x = w*op.x + x*op.w + y*op.z - z*op.y;
result.y = w*op.y - x*op.z + y*op.w + z*op.x;
result.z = w*op.z + x*op.y - y*op.x + z*op.w;
return result;
}
Vec3d operator*(const Quaternion& rot, const Vec3d& op)
{
Quaternion originalPosition = op;
return static_cast<Vec3d>(rot * originalPosition * rot.inverse());
}
Vec3d operator*(const Vec3d& op,const Quaternion& rot)
{
Quaternion originalPosition = op;
return static_cast<Vec3d>(rot.inverse() * originalPosition * rot);
}
Quaternion& Quaternion::operator=(const Quaternion& original)
{
if (this != &original)
{
std::copy(std::begin(original.coordinates),std::end(original.coordinates),std::begin(coordinates));
}
return *this;
}

39
src/Quaternion.h Normal file
View file

@ -0,0 +1,39 @@
//
// Created by trotfunky on 18/10/2019.
//
#ifndef TESTS_OPENGL_QUATERNION_H
#define TESTS_OPENGL_QUATERNION_H
#include "Vectors.h"
struct Quaternion : CoordinatesVector<double,4>{
double& x = coordinates[0];
double& y = coordinates[1];
double& z = coordinates[2];
double& w = coordinates[3];
Quaternion() = default;
Quaternion(double axis_x, double axis_y, double axis_z, double w_value);
Quaternion(const Quaternion& original);
Quaternion(const CoordinatesVector<double,4>& original);
Quaternion(const CoordinatesVector<double,3>& original);
/// Returns the inverse of a *unit-quaternion*.
Quaternion inverse() const;
/// Quaterion multiplication. See also https://en.wikipedia.org/wiki/Quaternion#Hamilton_product
Quaternion operator*(const Quaternion& op) const;
/// Performs the rotation associated to the *unit-quaternion* on the Vec3 operand.
Vec3d friend operator*(const Quaternion& rot, const Vec3d& op);
Vec3d friend operator*(const Vec3d& op, const Quaternion& rot);
Quaternion& operator=(const Quaternion& original);
/// Cast operator
explicit operator Vec3d() const;
};
#endif //TESTS_OPENGL_QUATERNION_H

View file

@ -6,6 +6,7 @@
#define TESTS_OPENGL_VECTORS_H
#include <fstream>
#include <iostream>
#include <cmath>
/// Group of coordinates with the input operator overloaded.
@ -99,6 +100,23 @@ struct CoordinatesVector
return *this;
}
bool operator==(const CoordinatesVector<T,N>& op)
{
for (unsigned int i = 0;i<N;i++)
{
if (coordinates[i] != op.coordinates[i])
{
return false;
}
}
return true;
}
bool operator!=(const CoordinatesVector<T,N>& op)
{
return !(*this == op);
}
friend std::fstream& operator>>(std::fstream& stream, CoordinatesVector<T,N>& vector)
{
for (unsigned int i = 0;i<N;i++)

View file

@ -4,24 +4,27 @@
#include "displayers.h"
#include "DataHandling/Texture.h"
#include "DataHandling/Model3D.h"
#include "KeyStateManager.h"
#include "InputStatus.h"
#include "Camera.h"
volatile unsigned long long int timer_ticks = 0;
static double aspect_ratio;
static Texture tree_texture;
static Model3D raptor;
static Texture raptor_texture;
static Camera camera({10,0,10},{-10,0,-10},{0,1,0});
void manage_inputs()
{
if (KeyStateManager::is_mouse_button_pressed(GLUT_LEFT_BUTTON))
if (InputStatus::is_mouse_button_pressed(GLUT_LEFT_BUTTON))
{
glClearColor(0,0,0.5,1);
}
else if (KeyStateManager::is_key_pressed(' '))
else if (InputStatus::is_key_pressed(' '))
{
glClearColor(0.5,0,0,1);
}
else if (KeyStateManager::is_key_pressed(0x1B))
else if (InputStatus::is_key_pressed(0x1B))
{
glutDestroyWindow(glutGetWindow());
exit(EXIT_SUCCESS);
@ -31,13 +34,43 @@ void manage_inputs()
glClearColor(0,0,0,1);
}
if (KeyStateManager::is_special_key_pressed(GLUT_KEY_RIGHT))
if (InputStatus::is_special_key_pressed(GLUT_KEY_RIGHT))
{
timer_ticks += 5;
camera.translate({0,0,1});
}
if (KeyStateManager::is_special_key_pressed(GLUT_KEY_LEFT))
if (InputStatus::is_special_key_pressed(GLUT_KEY_LEFT))
{
timer_ticks -= 5;
camera.translate({0,0,-1});
}
if (InputStatus::is_key_pressed(' '))
{
camera.translate({0,1,0});
}
if (InputStatus::is_special_key_pressed(GLUT_KEY_PAGE_DOWN))
{
camera.translate({0,-1,0});
}
if (InputStatus::is_special_key_pressed(GLUT_KEY_UP))
{
camera.translate({1,0,0});
}
if (InputStatus::is_special_key_pressed(GLUT_KEY_DOWN))
{
camera.translate({-1,0,0});
}
// Get mouse delta since last frame
static Vec2i mouse_delta;
mouse_delta = InputStatus::get_mouse_delta(true);
if (mouse_delta.x != 0 || mouse_delta.y != 0)
{
camera.rotate({0,
-mouse_delta.x / InputStatus::mouse_sensitivity.x,
-mouse_delta.y / InputStatus::mouse_sensitivity.y});
}
}
@ -51,14 +84,11 @@ void display()
glMatrixMode(GL_PROJECTION);
// Prepare view
glLoadIdentity();
gluPerspective(60,(double)glutGet(GLUT_WINDOW_WIDTH)/(double)glutGet(GLUT_WINDOW_HEIGHT),10,30000);
gluPerspective(60,aspect_ratio,0.1,30000);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
gluLookAt(10, 7.5, 10,
0, 0, 1,
0, 1, 0);
camera.look();
display_rotating_pyramid(-5,-2,-5,2,4,angleY);
display_rotating_pyramid(5,-2,0,1,5,angleY);
@ -74,6 +104,15 @@ void display()
glutSwapBuffers();
}
void reshape(int new_x, int new_y)
{
glViewport(0,0,new_x,new_y);
aspect_ratio = (double)new_x/new_y;
InputStatus::window_center.x = new_x / 2;
InputStatus::window_center.y = new_y / 2;
}
void update_angle(int value)
{
timer_ticks++;
@ -85,7 +124,7 @@ int main(int argc, char** argv)
glutInit(&argc,argv);
// Generate window with GLUT
glutInitDisplayMode(GLUT_RGB| GLUT_DEPTH | GLUT_DOUBLE);
glutCreateWindow("Hello");
glutCreateWindow("OpenGL custom engine tests");
glutIgnoreKeyRepeat(true);
// Init OpenGL
@ -129,8 +168,9 @@ int main(int argc, char** argv)
glutDisplayFunc(display);
glutIdleFunc(display);
glutReshapeFunc(reshape);
glutTimerFunc(50,update_angle,0);
KeyStateManager::register_glut_callbacks();
InputStatus::register_glut_callbacks();
// Enters main loop, managed by GLUT
glutMainLoop();