diff --git a/CMakeLists.txt b/CMakeLists.txt index ae77dfb..7ff7ce6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -19,8 +19,12 @@ add_executable(tests_opengl src/DataHandling/Model3D.cpp src/DataHandling/Model3D.h src/Vectors.h - src/KeyStateManager.cpp - src/KeyStateManager.h) + src/InputStatus.cpp + src/InputStatus.h + src/Camera.cpp + src/Camera.h + src/Quaternion.cpp + src/Quaternion.h) target_link_directories(tests_opengl PRIVATE src) diff --git a/README.md b/README.md index 5a4de64..0115826 100644 --- a/README.md +++ b/README.md @@ -2,15 +2,23 @@ The goal of the course is to introduce us to the OpenGL render pipeline and how to use the library. -The code here was, in part, inspired by the code which was given as part of the course but could not run on windows. +The code here was, in part, inspired by the code which was given as part of the course but could not run elsewhere than Windows. It is aimed to replace it and serve as a base for my own project as part of the course. +As this project evolves I am thinking more and more about making it a "complete" minimalist game-engine if I have the time. + ## TODOs - - [ ] Camera movement + - [x] Camera movement - [ ] Entities - - [ ] Quaternion computations? Maybe use OpenGLMathematics + - [x] Quaternion computations? ~~Maybe use OpenGLMathematics~~ Ended up doing it from scratch. + +### Possible expansions + - [ ] Loading other file types + - [ ] Asset manager + - [ ] Detect key press' edges ? + - [ ] Switch from GLUT to GLFW ## Dependencies diff --git a/src/Camera.cpp b/src/Camera.cpp new file mode 100644 index 0000000..f1d66b9 --- /dev/null +++ b/src/Camera.cpp @@ -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(); +} diff --git a/src/Camera.h b/src/Camera.h new file mode 100644 index 0000000..46c674e --- /dev/null +++ b/src/Camera.h @@ -0,0 +1,44 @@ +// +// Created by trotfunky on 19/10/2019. +// + +#ifndef TESTS_OPENGL_CAMERA_H +#define TESTS_OPENGL_CAMERA_H + +#include +#include + +#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 diff --git a/src/InputStatus.cpp b/src/InputStatus.cpp new file mode 100644 index 0000000..1b11198 --- /dev/null +++ b/src/InputStatus.cpp @@ -0,0 +1,165 @@ +// +// Created by trotfunky on 07/10/2019. +// + +#include "InputStatus.h" +#include + +// Initialize static members + +Vec2i InputStatus::window_center; +Vec2f InputStatus::mouse_sensitivity{100, 100}; + +std::map InputStatus::ascii_keys_status = {}; +std::map InputStatus::special_keys_status = {}; +std::map 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; +} \ No newline at end of file diff --git a/src/KeyStateManager.h b/src/InputStatus.h similarity index 89% rename from src/KeyStateManager.h rename to src/InputStatus.h index 6ab15ca..0346356 100644 --- a/src/KeyStateManager.h +++ b/src/InputStatus.h @@ -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 #include @@ -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 ascii_keys_status; @@ -58,4 +61,4 @@ private: }; -#endif //TESTS_OPENGL_KEYSTATEMANAGER_H +#endif //TESTS_OPENGL_INPUTSTATUS_H diff --git a/src/KeyStateManager.cpp b/src/KeyStateManager.cpp deleted file mode 100644 index 40e90ca..0000000 --- a/src/KeyStateManager.cpp +++ /dev/null @@ -1,151 +0,0 @@ -// -// Created by trotfunky on 07/10/2019. -// - -#include "KeyStateManager.h" -#include - -// Initialize static members - -std::map KeyStateManager::ascii_keys_status = {}; -std::map KeyStateManager::special_keys_status = {}; -std::map 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; -} \ No newline at end of file diff --git a/src/Quaternion.cpp b/src/Quaternion.cpp new file mode 100644 index 0000000..02bf993 --- /dev/null +++ b/src/Quaternion.cpp @@ -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() +{ + 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& original) +{ + if (this != &original) + { + std::copy(std::begin(original.coordinates),std::end(original.coordinates),std::begin(coordinates)); + } +} + +Quaternion::Quaternion(const CoordinatesVector& 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(rot * originalPosition * rot.inverse()); +} + +Vec3d operator*(const Vec3d& op,const Quaternion& rot) +{ + Quaternion originalPosition = op; + return static_cast(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; +} diff --git a/src/Quaternion.h b/src/Quaternion.h new file mode 100644 index 0000000..1e6b227 --- /dev/null +++ b/src/Quaternion.h @@ -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& 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& original); + Quaternion(const CoordinatesVector& 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 diff --git a/src/Vectors.h b/src/Vectors.h index 07eafec..029a883 100644 --- a/src/Vectors.h +++ b/src/Vectors.h @@ -6,6 +6,7 @@ #define TESTS_OPENGL_VECTORS_H #include +#include #include /// Group of coordinates with the input operator overloaded. @@ -99,6 +100,23 @@ struct CoordinatesVector return *this; } + bool operator==(const CoordinatesVector& op) + { + for (unsigned int i = 0;i& op) + { + return !(*this == op); + } + friend std::fstream& operator>>(std::fstream& stream, CoordinatesVector& vector) { for (unsigned int i = 0;i