From 25f1002ec1ec59ecfaeb80bb3085fe2bb134b72f Mon Sep 17 00:00:00 2001 From: Evert Date: Sat, 14 Apr 2018 15:31:42 +0300 Subject: [PATCH] Add simple camera and input handling classes --- README.md | 8 +++ src/Application.cpp | 71 ++++++++++++++++++--- src/Application.h | 4 ++ src/Camera.cpp | 114 ++++++++++++++++++++++++++++++++++ src/Camera.h | 55 +++++++++++++++++ src/Input.cpp | 64 +++++++++++++++++++ src/Input.h | 39 ++++++++++++ src/Shader.cpp | 146 ++++++++++++++++++++++++++++++++++++++++---- src/Shader.h | 26 ++++++-- 9 files changed, 500 insertions(+), 27 deletions(-) create mode 100644 src/Camera.cpp create mode 100644 src/Camera.h create mode 100644 src/Input.cpp create mode 100644 src/Input.h diff --git a/README.md b/README.md index f3b0f50..f35ae97 100644 --- a/README.md +++ b/README.md @@ -3,3 +3,11 @@ An idea for a 3D game engine that renders voxel planets, puts them into orbits a ## The Goal The goal of this application is to make a scriptable voxel planets engine to use in the making of games. + +## Compile +Currently only tested on Linux. +``` +mkdir build && cd build +cmake .. +make +``` diff --git a/src/Application.cpp b/src/Application.cpp index 724e243..3af29aa 100644 --- a/src/Application.cpp +++ b/src/Application.cpp @@ -52,10 +52,69 @@ void Application::Initialize() // Initialize GLEW glewInit(); + // Create camera + m_camera = new Camera(); + // Run the engine Run(); } +void Application::HandleEvents() +{ + /* Update the input manager */ + Input::getInstance().Flush(); + + /* Check for events */ + SDL_Event e; + while ( SDL_PollEvent(&e) ) + { + switch(e.type) + { + case SDL_QUIT: + Exit(); + break; + case SDL_KEYDOWN: + Input::getInstance().pressKey(e.key.keysym.sym); + break; + case SDL_KEYUP: + Input::getInstance().releaseKey(e.key.keysym.sym); + break; + case SDL_MOUSEMOTION: + Input::getInstance().setMouseCoords(e.motion.x, e.motion.y); + break; + case SDL_MOUSEBUTTONDOWN: + Input::getInstance().pressKey(e.button.button); + break; + case SDL_MOUSEBUTTONUP: + Input::getInstance().releaseKey(e.button.button); + break; + case SDL_MOUSEWHEEL: + Input::getInstance().setMouseWheel(e.wheel.y, e.wheel.x); + break; + } + } + + glm::vec2 mousepos = Input::getInstance().getMouseCoords(); + + if(Input::getInstance().isKeyDown(SDLK_w)) + m_camera->ProcessKeyboard(Camera_Movement::FORWARD, 0.01f); + + if(Input::getInstance().isKeyDown(SDLK_s)) + m_camera->ProcessKeyboard(Camera_Movement::BACKWARD, 0.01f); + + if(Input::getInstance().isKeyDown(SDLK_d)) + m_camera->ProcessKeyboard(Camera_Movement::RIGHT, 0.01f); + + if(Input::getInstance().isKeyDown(SDLK_a)) + m_camera->ProcessKeyboard(Camera_Movement::LEFT, 0.01f); + + m_camera->ProcessMouseScroll((float) Input::getInstance().getMouseWheelVertical() / 10.0f); + + if(Input::getInstance().isKeyPressed(SDL_BUTTON_LEFT)) + std::cout << "mX: " << mousepos.x << " mY: " << mousepos.y << std::endl; +} + + void Application::Run() { m_run = true; @@ -66,14 +125,8 @@ void Application::Run() { last_ = now_; now_ = SDL_GetPerformanceCounter(); - while(SDL_PollEvent(&m_event) != 0) - { - // Close button is pressed - if(m_event.type == SDL_QUIT) - { - Exit(); - } - } + + HandleEvents(); deltaTime_ = ((now_ - last_) / (double)SDL_GetPerformanceFrequency()); @@ -105,7 +158,7 @@ void Application::Run() void Application::Update(GLfloat dtime) { - log_info(std::to_string(dtime)); + } void Application::Render() diff --git a/src/Application.h b/src/Application.h index df264c2..8b2bb50 100644 --- a/src/Application.h +++ b/src/Application.h @@ -2,6 +2,8 @@ #define __APPLICATION_H__ #include "util/Common.h" #include "util/Singleton.h" +#include "Camera.h" +#include "Input.h" class Application : public Singleton { @@ -14,6 +16,7 @@ class Application : public Singleton friend class Singleton; private: + Camera* m_camera; SDL_Window* m_window; SDL_Event m_event; SDL_GLContext m_glContext; @@ -24,6 +27,7 @@ class Application : public Singleton bool m_run; + void HandleEvents(); void Run(); void Render(); void Update(GLfloat dtime); diff --git a/src/Camera.cpp b/src/Camera.cpp new file mode 100644 index 0000000..82f2300 --- /dev/null +++ b/src/Camera.cpp @@ -0,0 +1,114 @@ +#include "Camera.h" + +// TODO: use Roll + +Camera::Camera(glm::vec3 position, glm::vec3 up, GLfloat yaw, GLfloat pitch, GLfloat roll) : + m_front(glm::vec3(0.0f, 0.0f, -1.0f)), + m_movementSpeed(SPEED), + m_mouseSensitivity(SENSITIVTY), + m_zoom(ZOOM) +{ + m_position = position; + m_worldUp = up; + m_yaw = yaw; + m_pitch = pitch; + m_roll = roll; + updateCameraVectors(); +} + +Camera::Camera(GLfloat posX, GLfloat posY, GLfloat posZ, GLfloat upX, GLfloat upY, GLfloat upZ, GLfloat yaw, GLfloat pitch, GLfloat roll) : + m_front(glm::vec3(0.0f, 0.0f, -1.0f)), + m_movementSpeed(SPEED), + m_mouseSensitivity(SENSITIVTY), + m_zoom(ZOOM) +{ + m_position = glm::vec3(posX, posY, posZ); + m_worldUp = glm::vec3(upX, upY, upZ); + m_yaw = yaw; + m_pitch = pitch; + m_roll = roll; + updateCameraVectors(); +} + +Camera::~Camera() +{ + +} + +glm::mat4 Camera::getViewMatrix() +{ + return glm::lookAt(m_position, m_position + m_front, m_up); +} + +void Camera::ProcessKeyboard(Camera_Movement direction, GLfloat deltaTime) +{ + GLfloat velocity = m_movementSpeed * deltaTime; + + if (direction == FORWARD) + m_position += m_front * velocity; + if (direction == BACKWARD) + m_position -= m_front * velocity; + if (direction == LEFT) + m_position -= m_right * velocity; + if (direction == RIGHT) + m_position += m_right * velocity; +} + +void Camera::ProcessMouseMovement(GLfloat xoffset, GLfloat yoffset, GLboolean constrainPitch = true) +{ + xoffset *= m_mouseSensitivity; + yoffset *= m_mouseSensitivity; + + m_yaw += xoffset; + m_pitch += yoffset; + + // Make sure that when pitch is out of bounds, screen doesn't get flipped + if (constrainPitch) + { + if (m_pitch > 89.0f) + { + m_pitch = 89.0f; + } + + if (m_pitch < -89.0f) + { + m_pitch = -89.0f; + } + } + + // Update Front, Right and Up Vectors using the updated Eular angles + updateCameraVectors(); +} + +void Camera::ProcessMouseScroll(GLfloat yoffset) +{ + if (m_zoom >= 44.0f && m_zoom <= 45.0f) + { + m_zoom -= yoffset; + } + + if (m_zoom <= 44.0f) + { + m_zoom = 44.0f; + } + + if (m_zoom >= 45.0f) + { + m_zoom = 45.0f; + } +} + +void Camera::updateCameraVectors() +{ + // Calculate the new Front vector + glm::vec3 front; + front.x = cos(glm::radians(m_yaw)) * cos(glm::radians(m_pitch)); + front.y = sin(glm::radians(m_pitch)); + front.z = sin(glm::radians(m_yaw)) * cos(glm::radians(m_pitch)); + m_front = glm::normalize(front); + + // Also re-calculate the Right and Up vector + // Normalize the vectors, because their length gets closer to 0 the more you look up or down which results in slower movement. + m_right = glm::normalize(glm::cross(m_front, m_worldUp)); + m_up = glm::normalize(glm::cross(m_right, m_front)); +} diff --git a/src/Camera.h b/src/Camera.h new file mode 100644 index 0000000..02de60a --- /dev/null +++ b/src/Camera.h @@ -0,0 +1,55 @@ +#ifndef __CAMERA_H__ +#define __CAMERA_H__ + +#include "util/Common.h" + +// Defines several possible options for camera movement. Used as abstraction to stay away from window-system specific input methods +enum Camera_Movement { + FORWARD, + BACKWARD, + LEFT, + RIGHT +}; + +// Default camera values +const GLfloat YAW = -90.0f; +const GLfloat PITCH = 0.0f; +const GLfloat ROLL = 0.0f; +const GLfloat SPEED = 3.0f; +const GLfloat SENSITIVTY = 0.25f; +const GLfloat ZOOM = 45.0f; + +class Camera +{ +public: + Camera(glm::vec3 position = glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3 up = glm::vec3(0.0f, 1.0f, 0.0f), GLfloat yaw = YAW, GLfloat pitch = PITCH, GLfloat roll = ROLL); + Camera(GLfloat posX, GLfloat posY, GLfloat posZ, GLfloat upX, GLfloat upY, GLfloat upZ, GLfloat yaw, GLfloat pitch, GLfloat roll); + ~Camera(); + + glm::mat4 getViewMatrix(); + glm::vec3 getPosition() const { return m_position; } + void ProcessKeyboard(Camera_Movement direction, GLfloat deltaTime); + void ProcessMouseMovement(GLfloat xoffset, GLfloat yoffset, GLboolean constrainPitch); + void ProcessMouseScroll(GLfloat yoffset); + + GLfloat getFOV() const { return m_zoom; } +private: + glm::vec3 m_position; + glm::vec3 m_front; + glm::vec3 m_up; + glm::vec3 m_right; + glm::vec3 m_worldUp; + + // Eular Angles + GLfloat m_yaw; + GLfloat m_pitch; + GLfloat m_roll; + + // Camera options + GLfloat m_movementSpeed; + GLfloat m_mouseSensitivity; + GLfloat m_zoom; + + void updateCameraVectors(); +}; +#endif // __CAMERA_H__ diff --git a/src/Input.cpp b/src/Input.cpp new file mode 100644 index 0000000..52bb5a9 --- /dev/null +++ b/src/Input.cpp @@ -0,0 +1,64 @@ +#include "Input.h" + +// Remember previous positions +void Input::Flush() +{ + for(auto& it : m_keyMap) + { + m_oldKeyMap[it.first] = it.second; + } + + // Keep old mouse coordinates + m_oldMouseCoords = m_mouseCoords; + + // Reset the mouse wheel scroll, as we will ever only need it once. + m_mwheelY = 0; + m_mwheelX = 0; +} + +void Input::pressKey(unsigned int keyID) +{ + m_keyMap[keyID] = true; +} + +void Input::releaseKey(unsigned int keyID) +{ + m_keyMap[keyID] = false; +} + +void Input::setMouseCoords(float x, float y) +{ + m_mouseCoords.x = x; + m_mouseCoords.y = y; +} + +bool Input::isKeyDown(unsigned int keyID) +{ + auto it = m_keyMap.find(keyID); + if(it != m_keyMap.end()) + return it->second; + else + return false; +} + +bool Input::wasKeyDown(unsigned int keyID) +{ + auto it = m_oldKeyMap.find(keyID); + if(it != m_oldKeyMap.end()) + return it->second; + else + return false; +} + +bool Input::isKeyPressed(unsigned int keyID) +{ + if(isKeyDown(keyID) == true && wasKeyDown(keyID) == false) + return true; + return false; +} + +void Input::setMouseWheel(int vert, int horiz) +{ + m_mwheelY = vert; + m_mwheelX = horiz; +} diff --git a/src/Input.h b/src/Input.h new file mode 100644 index 0000000..c99d00e --- /dev/null +++ b/src/Input.h @@ -0,0 +1,39 @@ +#ifndef __INPUT_H__ +#define __INPUT_H__ + +#include "util/Singleton.h" +#include "util/Math3D.h" + +#include + +class Input : public Singleton +{ + public: + void Flush(); + + void pressKey(unsigned int keyID); + void releaseKey(unsigned int keyID); + + void setMouseCoords(float x, float y); + void setMouseWheel(int vert, int horiz); + + bool isKeyDown(unsigned int keyID); + bool isKeyPressed(unsigned int keyID); + + glm::vec2 getMouseCoords() const { return m_mouseCoords; } + glm::vec2 getOldMouseCoords() const { return m_oldMouseCoords; } + + int getMouseWheelVertical() const { return m_mwheelY; } + int getMouseWheelHorizontal() const { return m_mwheelX; } + + friend class Singleton; + private: + bool wasKeyDown(unsigned int keyID); + std::unordered_map m_keyMap; + std::unordered_map m_oldKeyMap; + glm::vec2 m_mouseCoords; + glm::vec2 m_oldMouseCoords; + int m_mwheelY; + int m_mwheelX; +}; +#endif // __INPUT_H__ diff --git a/src/Shader.cpp b/src/Shader.cpp index 2b373fa..82189b9 100644 --- a/src/Shader.cpp +++ b/src/Shader.cpp @@ -1,4 +1,13 @@ #include "Shader.h" +#include "util/Log.h" + +struct Shader::Attribute { + GLint size; + GLboolean normalized; + GLsizei stride; + GLuint offset; + GLenum type; +}; /** Compile a shader from file */ void Shader::compileShader(const string& filePath, GLuint& id) @@ -39,14 +48,14 @@ void Shader::compileShader(const string& filePath, GLuint& id) glDeleteShader(id); - // Exit with failure. + // Exit with failure. std::printf("%s\n", &(errorLog[0])); - fatalError("Shader " + filePath + " failed to compile"); + fatalError("Shader @" + filePath + " failed to compile."); } } /** Create new shader from vertex and fragment files */ -Shader& Shader::createShader(const string& vertexShaderFilePath, const string& fragmentShaderFilePath) +Shader& Shader::createShader(const string& vertexShaderFilePath, const string& fragmentShaderFilePath) { Shader* shader = new Shader(); @@ -69,6 +78,37 @@ Shader& Shader::createShader(const string& vertexShaderFilePath, const string& f return *shader; } +/** Create new shader from vertex, fragment and geometry files */ +Shader& Shader::createShader(const string& vertexShaderFilePath, const string& fragmentShaderFilePath, const string& geometryShaderFilePath) +{ + Shader* shader = new Shader(); + + shader->m_programID = glCreateProgram(); + shader->m_vertexShaderID = glCreateShader(GL_VERTEX_SHADER); + if(shader->m_vertexShaderID == 0) + { + fatalError("Vertex shader failed to be created!"); + } + + shader->m_fragmentShaderID = glCreateShader(GL_FRAGMENT_SHADER); + if(shader->m_fragmentShaderID == 0) + { + fatalError("Fragment shader failed to be created!"); + } + + shader->m_geometryShaderID = glCreateShader(GL_GEOMETRY_SHADER); + if(shader->m_geometryShaderID == 0) + { + fatalError("Geometry shader failed to be created!"); + } + + Shader::compileShader(vertexShaderFilePath, shader->m_vertexShaderID); + Shader::compileShader(fragmentShaderFilePath, shader->m_fragmentShaderID); + Shader::compileShader(geometryShaderFilePath, shader->m_geometryShaderID); + + return *shader; +} + /** Get uniform location */ GLuint Shader::getUniformLocation(const string& uniformName) { @@ -76,9 +116,11 @@ GLuint Shader::getUniformLocation(const string& uniformName) if (it == m_uniforms.end()) { // Get uniform location - GLint r = glGetUniformLocation(m_programID, uniformName.c_str()); - if ( r == GL_INVALID_OPERATION || r < 0 ) + GLint r = glGetUniformLocation(m_programID, uniformName.c_str()); + if (r == GL_INVALID_OPERATION || r < 0) + { log_warn("Uniform " + uniformName + " doesn't exist in program."); + } // Add it to the cache m_uniforms[uniformName] = r; @@ -91,6 +133,35 @@ GLuint Shader::getUniformLocation(const string& uniformName) } } +GLuint Shader::getAttribLocation(const string& attrbuteName) +{ + GLint attrib = glGetAttribLocation(m_programID, attrbuteName.c_str()); + if (attrib == GL_INVALID_OPERATION || attrib < 0) + { + log_warn("Attribute " + attrbuteName + " doesn't exist in program."); + } + + return attrib; +} + +void Shader::setAttribute(const std::string& name, + GLint size, + GLboolean normalized, + GLsizei stride, + GLuint offset, + GLenum type) +{ + GLuint loc = getAttribLocation(name); + glEnableVertexAttribArray(loc); + glVertexAttribPointer(loc, size, type, normalized, stride * sizeof(GLfloat), + (void*)(offset * sizeof(GLfloat))); + m_attributes[name].size = size; + m_attributes[name].normalized = normalized; + m_attributes[name].stride = stride; + m_attributes[name].offset = offset; + m_attributes[name].type = type; +} + /** Link the shaders together */ void Shader::linkShaders() { @@ -120,8 +191,8 @@ void Shader::linkShaders() glDeleteShader(m_vertexShaderID); glDeleteShader(m_fragmentShaderID); - fatalError("Shader linking failed!"); std::printf("%s\n", &(infoLog[0])); + fatalError("Shader linking failed!"); } // Always detach shaders after a successful link. @@ -131,12 +202,6 @@ void Shader::linkShaders() glDeleteShader(m_fragmentShaderID); } -/** Bind attribute location */ -void Shader::addAttribute(const string& attribName) -{ - glBindAttribLocation(m_programID, m_attributes++, attribName.c_str()); -} - /** Bind the shader for usage */ void Shader::start() { @@ -198,3 +263,60 @@ void Shader::setUniform(const string& name, int val) { glUniform1i(getUniformLocation(name), val); } + +void Shader::use() +{ + if (!m_valid) + { + m_valid = true; + glLinkProgram(m_programID); + + start(); + + GLint result; + glGetProgramiv(m_programID, GL_LINK_STATUS, &result); + + if (result != GL_TRUE) + { + log_error("Shader relink failed!"); + GLsizei maxLength = 0; + glGetProgramiv(m_programID, GL_INFO_LOG_LENGTH, &maxLength); + + char* log = new char[maxLength]; + glGetProgramInfoLog(m_programID, maxLength, &maxLength, log); + + std::printf("%s\n", log); + } + + glBindVertexArray(vao); + glBindBuffer(GL_ARRAY_BUFFER, vbo); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo); + + for (auto it(m_attributes.begin()); it != m_attributes.end(); ++it) + { + GLuint location = getAttribLocation(it->first); + glEnableVertexAttribArray(location); + glVertexAttribPointer( + location, + it->second.size, + it->second.type, + it->second.normalized, + it->second.stride * sizeof(GLfloat), + (void*)(it->second.offset * sizeof(GLfloat)) + ); + } + } else { + start(); + + glBindVertexArray(vao); + glBindBuffer(GL_ARRAY_BUFFER, vbo); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo); + } +} + +void Shader::setBuffers(GLint vao, GLint vbo, GLint ebo) +{ + this->vao = vao; + this->vbo = vbo; + this->ebo = ebo; +} diff --git a/src/Shader.h b/src/Shader.h index 96dc929..c9d6615 100644 --- a/src/Shader.h +++ b/src/Shader.h @@ -4,19 +4,22 @@ #include #include "util/Common.h" -#include "util/Log.h" class Shader { public: static Shader& createShader(const string& vertexShaderFilePath, const string& fragmentShaderFilePath); + static Shader& createShader(const string& vertexShaderFilePath, const string& fragmentShaderFilePath, const string& geometryShaderFilePath); void linkShaders(); - void addAttribute(const string& attribName); - - GLuint getUniformLocation(const string& uniformName); + void setAttribute(const std::string& name, GLint size, GLboolean normalized, GLsizei stride, GLuint offset, GLenum type = GL_FLOAT); + GLuint getUniformLocation(const string& uniformName); + inline GLuint operator[](const std::string& name) { return getUniformLocation(name); } + GLuint getAttribLocation(const string& attrbuteName); + + // Set uniforms void setUniform(const string& name, float x, float y, float z); void setUniform(const string& name, const vec3& v); void setUniform(const string& name, const dvec3& v); @@ -28,16 +31,27 @@ public: void setUniform(const string& name, float val); void setUniform(const string& name, int val); + void setBuffers(GLint vao, GLint vbo, GLint ebo); + + // Bind Attributes and start the shader program + void use(); + + // Start the shader program without binding attributes void start(); void stop(); private: - int m_attributes; + struct Attribute; + bool m_valid; GLuint m_programID; GLuint m_vertexShaderID; GLuint m_fragmentShaderID; + GLuint m_geometryShaderID; - map m_uniforms; + map m_uniforms; + map m_attributes; + + GLint vao, vbo, ebo; static void compileShader(const string& filePath, GLuint& id); };