From 834e2fd3acf9efe7a9e062c809f498489821576c Mon Sep 17 00:00:00 2001 From: bailwillharr Date: Fri, 2 Sep 2022 12:06:59 +0100 Subject: [PATCH] Initial commit --- .gitignore | 10 + .gitmodules | 18 + CMakeLists.txt | 142 +++++++ config.h.in | 3 + dependencies/SDL | 1 + dependencies/freetype | 1 + dependencies/glad | 1 + dependencies/glm | 1 + dependencies/spdlog | 1 + dependencies/stb | 1 + include/components/camera.hpp | 53 +++ include/components/component.hpp | 41 ++ include/components/custom.hpp | 20 + include/components/mesh_renderer.hpp | 39 ++ include/components/text_ui_renderer.hpp | 38 ++ include/components/transform.hpp | 32 ++ include/input.hpp | 87 +++++ include/inputs/keyboard.hpp | 374 ++++++++++++++++++ include/inputs/mouse.hpp | 22 ++ include/log.hpp | 16 + include/object.hpp | 136 +++++++ include/resource_manager.hpp | 79 ++++ include/resources/font.hpp | 33 ++ include/resources/mesh.hpp | 54 +++ include/resources/resource.hpp | 22 ++ include/resources/shader.hpp | 75 ++++ include/resources/texture.hpp | 27 ++ include/sceneroot.hpp | 27 ++ include/window.hpp | 213 ++++++++++ src/components/camera.cpp | 101 +++++ src/components/component.cpp | 30 ++ src/components/custom.cpp | 24 ++ src/components/mesh_renderer.cpp | 50 +++ src/components/text_ui_renderer.cpp | 81 ++++ src/components/transform.cpp | 16 + src/input.cpp | 232 +++++++++++ src/object.cpp | 136 +++++++ src/resource_manager.cpp | 71 ++++ src/resources/font.cpp | 96 +++++ src/resources/mesh.cpp | 119 ++++++ src/resources/resource.cpp | 20 + src/resources/shader.cpp | 223 +++++++++++ src/resources/texture.cpp | 131 +++++++ src/sceneroot.cpp | 91 +++++ src/window.cpp | 494 ++++++++++++++++++++++++ 45 files changed, 3482 insertions(+) create mode 100644 .gitignore create mode 100644 .gitmodules create mode 100644 CMakeLists.txt create mode 100644 config.h.in create mode 160000 dependencies/SDL create mode 160000 dependencies/freetype create mode 160000 dependencies/glad create mode 160000 dependencies/glm create mode 160000 dependencies/spdlog create mode 160000 dependencies/stb create mode 100644 include/components/camera.hpp create mode 100644 include/components/component.hpp create mode 100644 include/components/custom.hpp create mode 100644 include/components/mesh_renderer.hpp create mode 100644 include/components/text_ui_renderer.hpp create mode 100644 include/components/transform.hpp create mode 100644 include/input.hpp create mode 100644 include/inputs/keyboard.hpp create mode 100644 include/inputs/mouse.hpp create mode 100644 include/log.hpp create mode 100644 include/object.hpp create mode 100644 include/resource_manager.hpp create mode 100644 include/resources/font.hpp create mode 100644 include/resources/mesh.hpp create mode 100644 include/resources/resource.hpp create mode 100644 include/resources/shader.hpp create mode 100644 include/resources/texture.hpp create mode 100644 include/sceneroot.hpp create mode 100644 include/window.hpp create mode 100644 src/components/camera.cpp create mode 100644 src/components/component.cpp create mode 100644 src/components/custom.cpp create mode 100644 src/components/mesh_renderer.cpp create mode 100644 src/components/text_ui_renderer.cpp create mode 100644 src/components/transform.cpp create mode 100644 src/input.cpp create mode 100644 src/object.cpp create mode 100644 src/resource_manager.cpp create mode 100644 src/resources/font.cpp create mode 100644 src/resources/mesh.cpp create mode 100644 src/resources/resource.cpp create mode 100644 src/resources/shader.cpp create mode 100644 src/resources/texture.cpp create mode 100644 src/sceneroot.cpp create mode 100644 src/window.cpp diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3ef0a7d --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +.ccls-cache/ +.vscode/ +.vs/ +out/ +build/ +Debug/ +Release/ +MinSizeRel/ +CMakeSettings.json +compile_commands.json diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..d92b0d2 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,18 @@ +[submodule "dependencies/SDL"] + path = dependencies/SDL + url = https://github.com/libsdl-org/SDL +[submodule "dependencies/stb"] + path = dependencies/stb + url = https://github.com/nothings/stb +[submodule "dependencies/glm"] + path = dependencies/glm + url = https://github.com/g-truc/glm +[submodule "dependencies/glad"] + path = dependencies/glad + url = https://github.com/dav1dde/glad +[submodule "dependencies/spdlog"] + path = dependencies/spdlog + url = https://github.com/gabime/spdlog +[submodule "dependencies/freetype"] + path = dependencies/freetype + url = https://gitlab.freedesktop.org/freetype/freetype.git diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..4d7703e --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,142 @@ +cmake_minimum_required(VERSION 3.12) + +# options + +project(engine LANGUAGES CXX + VERSION "0.1.0" +) + +add_library(${PROJECT_NAME} SHARED + + "src/window.cpp" + + "src/input.cpp" #TODO make input_manager + + "src/object.cpp" + "src/sceneroot.cpp" + + "src/components/component.cpp" + "src/components/transform.cpp" # TODO move functionality into "Object" class + "src/components/camera.cpp" + "src/components/mesh_renderer.cpp" + "src/components/text_ui_renderer.cpp" + "src/components/custom.cpp" + + "src/resources/resource.cpp" + "src/resources/mesh.cpp" + "src/resources/shader.cpp" + "src/resources/texture.cpp" + "src/resources/font.cpp" + + "src/resource_manager.cpp" + + # PUBLIC API + + "include/log.hpp" + + "include/window.hpp" + + "include/inputs/keyboard.hpp" + "include/inputs/mouse.hpp" + + "include/input.hpp" + + "include/object.hpp" + "include/sceneroot.hpp" + + "include/components/component.hpp" + "include/components/transform.hpp" + "include/components/camera.hpp" + "include/components/mesh_renderer.hpp" + "include/components/text_ui_renderer.hpp" + "include/components/custom.hpp" + + "include/resources/resource.hpp" + "include/resources/mesh.hpp" + "include/resources/shader.hpp" + "include/resources/texture.hpp" + "include/resources/font.hpp" + + "include/resource_manager.hpp" + + ) + +# compiling options: + +set_property(TARGET ${PROJECT_NAME} PROPERTY CXX_STANDARD 20) +set_property(TARGET ${PROJECT_NAME} PROPERTY CXX_STANDARD_REQUIRED ON) + +if (MSVC) + target_compile_options(${PROJECT_NAME} PRIVATE /W3) + target_compile_options(${PROJECT_NAME} PRIVATE /M3) +endif() + +target_include_directories(${PROJECT_NAME} PUBLIC include) +target_include_directories(${PROJECT_NAME} PRIVATE src) + +# Pass some project information into the source code +configure_file(config.h.in config.h) +target_include_directories(${PROJECT_NAME} PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) + +# libraries: + +# MinGW library if using it +if (MINGW) + target_link_libraries(${PROJECT_NAME} PUBLIC mingw32) +endif() + +# SDL2: +set(SDL2_DISABLE_INSTALL ON) +set(SDL_SHARED ON) +set(SDL_STATIC OFF) +set(SDL_TEST OFF) +add_subdirectory(dependencies/SDL) +target_include_directories(${PROJECT_NAME} PUBLIC dependencies/SDL/include) +target_link_libraries(${PROJECT_NAME} PUBLIC SDL2::SDL2) +target_link_libraries(${PROJECT_NAME} PUBLIC SDL2::SDL2main) +# copy over SDL2 library: +add_custom_command( + TARGET ${PROJECT_NAME} PRE_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_if_different + $ $) + +# GLM: +add_subdirectory(dependencies/glm) +target_include_directories(${PROJECT_NAME} PUBLIC dependencies/glm) + +# GLAD: +set(GLAD_PROFILE "core" CACHE INTERNAL "" FORCE) # TODO remove this "CACHE INTERNAL..." thing +set(GLAD_API "gl=3.3" CACHE INTERNAL "" FORCE) +if (CMAKE_BUILD_TYPE STREQUAL "Debug") + set(GLAD_GENERATOR "c-debug" CACHE INTERNAL "" FORCE) +else() + set(GLAD_GENERATOR "c" CACHE INTERNAL "" FORCE) +endif() +set(GLAD_SPEC "gl" CACHE INTERNAL "" FORCE) +add_subdirectory(dependencies/glad) +set_property(TARGET glad PROPERTY POSITION_INDEPENDENT_CODE ON) +target_link_libraries(${PROJECT_NAME} PUBLIC glad) +target_include_directories(${PROJECT_NAME} PUBLIC dependencies/glad/include) + +# spdlog +set(SPDLOG_BUILD_SHARED ON CACHE INTERNAL "" FORCE) +add_subdirectory(dependencies/spdlog) +target_link_libraries(${PROJECT_NAME} PUBLIC spdlog) +target_include_directories(${PROJECT_NAME} PUBLIC dependencies/spdlog/include) + + + +# freetype +set(BUILD_SHARED_LIBS ON) +add_subdirectory(dependencies/freetype) +target_link_libraries(${PROJECT_NAME} PRIVATE freetype) +target_include_directories(${PROJECT_NAME} PRIVATE dependencies/freetype/include) +set(BUILD_SHARED_LIBS OFF) + +# stb +target_include_directories(${PROJECT_NAME} PRIVATE dependencies/stb) + + + +# public API: + diff --git a/config.h.in b/config.h.in new file mode 100644 index 0000000..8d03266 --- /dev/null +++ b/config.h.in @@ -0,0 +1,3 @@ +#pragma once + +#define SDLTEST_VERSION @PROJECT_VERSION@ diff --git a/dependencies/SDL b/dependencies/SDL new file mode 160000 index 0000000..2847696 --- /dev/null +++ b/dependencies/SDL @@ -0,0 +1 @@ +Subproject commit 284769633864df4b1395f164dc16d03163973a63 diff --git a/dependencies/freetype b/dependencies/freetype new file mode 160000 index 0000000..dd91f6e --- /dev/null +++ b/dependencies/freetype @@ -0,0 +1 @@ +Subproject commit dd91f6e7f5a051818070c49715125fb72074023e diff --git a/dependencies/glad b/dependencies/glad new file mode 160000 index 0000000..ea756f7 --- /dev/null +++ b/dependencies/glad @@ -0,0 +1 @@ +Subproject commit ea756f7cc5e11dcef3cafdab87d45b3b528c875d diff --git a/dependencies/glm b/dependencies/glm new file mode 160000 index 0000000..cc98465 --- /dev/null +++ b/dependencies/glm @@ -0,0 +1 @@ +Subproject commit cc98465e3508535ba8c7f6208df934c156a018dc diff --git a/dependencies/spdlog b/dependencies/spdlog new file mode 160000 index 0000000..f44fa31 --- /dev/null +++ b/dependencies/spdlog @@ -0,0 +1 @@ +Subproject commit f44fa31f5110331af196d0ed7f00ae1c7b8ef3cc diff --git a/dependencies/stb b/dependencies/stb new file mode 160000 index 0000000..af1a5bc --- /dev/null +++ b/dependencies/stb @@ -0,0 +1 @@ +Subproject commit af1a5bc352164740c1cc1354942b1c6b72eacb8a diff --git a/include/components/camera.hpp b/include/components/camera.hpp new file mode 100644 index 0000000..c85a3d9 --- /dev/null +++ b/include/components/camera.hpp @@ -0,0 +1,53 @@ +#pragma once + +#include "component.hpp" + +#include "object.hpp" + +#include + +#include +#include + +namespace components { + +class Camera : public Component { + +public: + Camera(Object* parent); + ~Camera(); + + // called every frame, don't call manually + void updateCam(glm::mat4 transform); + + void makeActive(); + bool isActive(); + + void usePerspective(float fovDeg); + void useOrtho(); + + bool m_noClear = false; + + glm::vec3 m_lightPos = { 0.0f, 100.0f, 0.0f }; + + glm::vec4 clearColor{0.1f, 0.1f, 0.1f, 1.0f}; + + glm::mat4 m_projMatrix{ 1.0f }; + +private: + + enum class Modes { + PERSPECTIVE, + ORTHOGRAPHIC + }; + + Modes m_mode = Modes::ORTHOGRAPHIC; + + // perspective mode settings + float m_fovDeg = 90.0f; + + static glm::vec4 s_clearColor; + +}; + +} diff --git a/include/components/component.hpp b/include/components/component.hpp new file mode 100644 index 0000000..f5e7f84 --- /dev/null +++ b/include/components/component.hpp @@ -0,0 +1,41 @@ +#pragma once + +class Object; +class Window; +class Input; +class ResourceManager; + +class Component { + +public: + + enum class TypeEnum { + TRANSFORM, + CAMERA, + RENDERER, + UI, + CUSTOM, + }; + + Component(Object* parent, TypeEnum type); + Component(const Component&) = delete; + Component& operator=(const Component&) = delete; + virtual ~Component() = 0; + + int getID(); + TypeEnum getType(); + + Object& parent; + +protected: + Window& win; + Input& inp; + ResourceManager& res; + +private: + static int s_next_component_id; + + int m_id = s_next_component_id; + TypeEnum m_type; + +}; diff --git a/include/components/custom.hpp b/include/components/custom.hpp new file mode 100644 index 0000000..b669ed8 --- /dev/null +++ b/include/components/custom.hpp @@ -0,0 +1,20 @@ +#pragma once + +#include "component.hpp" + +#include + +namespace components { + + class CustomComponent : public Component { + + public: + CustomComponent(Object* parent); + virtual ~CustomComponent() = 0; + + virtual void onInit(); + virtual void onUpdate(glm::mat4 t); + + }; + +} \ No newline at end of file diff --git a/include/components/mesh_renderer.hpp b/include/components/mesh_renderer.hpp new file mode 100644 index 0000000..89d11c9 --- /dev/null +++ b/include/components/mesh_renderer.hpp @@ -0,0 +1,39 @@ +#pragma once + +#include "component.hpp" + +#include "resources/shader.hpp" +#include "resources/mesh.hpp" +#include "resources/texture.hpp" + +#include +#include +#include + +namespace components { + +class Renderer : public Component { + +public: + Renderer(Object*); + ~Renderer() override; + + // called every frame, do not call manually + void render(glm::mat4 transform); + + void setMesh(const std::string& name); + void setTexture(const std::string& name); + + std::shared_ptr m_mesh = nullptr; + std::shared_ptr m_texture; + + glm::vec3 m_color = { 1.0f, 1.0f, 1.0f }; + glm::vec3 m_emission = { 0.0f, 0.0f, 0.0f }; + +private: + + std::shared_ptr m_shader; + +}; + +} diff --git a/include/components/text_ui_renderer.hpp b/include/components/text_ui_renderer.hpp new file mode 100644 index 0000000..cb7cdd3 --- /dev/null +++ b/include/components/text_ui_renderer.hpp @@ -0,0 +1,38 @@ +#pragma once + +#include "component.hpp" + +#include "resources/font.hpp" +#include "resources/mesh.hpp" + +#include + +namespace components { + +class UI : public Component { + +public: + UI(Object*); + ~UI() override; + + // called every frame, do not call manually + void render(glm::mat4 transform); + + std::string m_text = "hello world"; + glm::vec3 m_color = {1.0f, 1.0f, 1.0f}; + + enum class Alignment { + NONE = 0, + LEFT = 1, + RIGHT = 2 + }; + Alignment m_alignment = Alignment::LEFT; + bool m_scaled = true; + +private: + std::shared_ptr m_font; + std::shared_ptr m_shader; + +}; + +} diff --git a/include/components/transform.hpp b/include/components/transform.hpp new file mode 100644 index 0000000..1bd0c33 --- /dev/null +++ b/include/components/transform.hpp @@ -0,0 +1,32 @@ +#pragma once + +#include "component.hpp" + +#include +#include +#include + +#include +#include +#include + +namespace components { + +class Transform : public Component { + +// Scale, rotate (XYZ), translate + +private: + glm::mat4 m_transformMatrix{1.0f}; + +public: + Transform(Object*); + ~Transform() override; + + glm::vec3 position{0.0f}; + glm::quat rotation{}; + glm::vec3 scale{1.0f}; + +}; + +} diff --git a/include/input.hpp b/include/input.hpp new file mode 100644 index 0000000..2ec66af --- /dev/null +++ b/include/input.hpp @@ -0,0 +1,87 @@ +#pragma once + +#include "inputs/mouse.hpp" +#include "inputs/keyboard.hpp" + +#include +#include +#include + +class Window; + +enum class InputDevice : int { + MOUSE, + KEYBOARD, + CONTROLLER, + SIZE +}; + +// This class should be used to get platform/input-device independent input +class Input { + +public: + + // requires a window reference to get input from + Input(const Window& win); + Input(const Input&) = delete; + Input& operator=(const Input&) = delete; + ~Input(); + + // Add a mouse input + void addInputButton(const std::string& name, inputs::MouseButton button); + void addInputAxis(const std::string& name, inputs::MouseAxis axis); + void addInputButtonAsAxis(const std::string& name, inputs::MouseButton high, inputs::MouseButton low); + // Add a keyboard input + void addInputButton(const std::string& name, inputs::Key button); + void addInputButtonAsAxis(const std::string& name, inputs::Key high, inputs::Key low); + + void delInputButton(int index); + void delInputAxis(int index); + + void setDeviceActive(enum InputDevice device, bool active); + bool getDeviceActive(enum InputDevice device) const; + + float getAxis(const std::string& axisName) const; + bool getButton(const std::string& buttonName) const; + bool getButtonPress(const std::string& buttonName) const; + bool getButtonRelease(const std::string& buttonName) const; + + +private: + + struct ButtonEntry { + std::string name; + enum InputDevice device; + int button; // enumeration of device buttons or axes + }; + + struct AxisEntry { + std::string name; + enum InputDevice device; + int axis; + bool isButtonAxis; + int high; + int low; + }; + + const Window& m_win; + + std::vector m_buttonEntries; + std::vector m_axisEntries; + + std::array(InputDevice::SIZE)> m_enabledDevices; + + // private methods + + float getDeviceAxis(enum InputDevice device, int axis) const; + bool getDeviceButton(enum InputDevice device, int button) const; + bool getDeviceButtonDown(enum InputDevice device, int button) const; + bool getDeviceButtonUp(enum InputDevice device, int button) const; + + float getButtonAxis(enum InputDevice device, int high, int low) const; + + void addInputButton(const std::string& name, InputDevice device, int button); + void addInputAxis(const std::string& name, InputDevice device, int axis); + void addInputButtonAsAxis(const std::string& name, InputDevice device, int high, int low); + +}; diff --git a/include/inputs/keyboard.hpp b/include/inputs/keyboard.hpp new file mode 100644 index 0000000..a8a6e66 --- /dev/null +++ b/include/inputs/keyboard.hpp @@ -0,0 +1,374 @@ +#pragma once + +// Keyboard scancodes, taken from SDL_scancode.h + +namespace inputs { + +enum class Key : int { + UNKNOWN = 0, + + /** + * \name Usage page 0x07 + * + * These values are from usage page 0x07 (USB keyboard page). + */ + /* @{ */ + + A = 4, + B = 5, + C = 6, + D = 7, + E = 8, + F = 9, + G = 10, + H = 11, + I = 12, + J = 13, + K = 14, + L = 15, + M = 16, + N = 17, + O = 18, + P = 19, + Q = 20, + R = 21, + S = 22, + T = 23, + U = 24, + V = 25, + W = 26, + X = 27, + Y = 28, + Z = 29, + + N1 = 30, + N2 = 31, + N3 = 32, + N4 = 33, + N5 = 34, + N6 = 35, + N7 = 36, + N8 = 37, + N9 = 38, + N0 = 39, + + RETURN = 40, + ESCAPE = 41, + BACKSPACE = 42, + TAB = 43, + SPACE = 44, + + MINUS = 45, + EQUALS = 46, + LEFTBRACKET = 47, + RIGHTBRACKET = 48, + BACKSLASH = 49, /**< Located at the lower left of the return + * key on ISO keyboards and at the right end + * of the QWERTY row on ANSI keyboards. + * Produces REVERSE SOLIDUS (backslash) and + * VERTICAL LINE in a US layout, REVERSE + * SOLIDUS and VERTICAL LINE in a UK Mac + * layout, NUMBER SIGN and TILDE in a UK + * Windows layout, DOLLAR SIGN and POUND SIGN + * in a Swiss German layout, NUMBER SIGN and + * APOSTROPHE in a German layout, GRAVE + * ACCENT and POUND SIGN in a French Mac + * layout, and ASTERISK and MICRO SIGN in a + * French Windows layout. + */ + NONUSHASH = 50, /**< ISO USB keyboards actually use this code + * instead of 49 for the same key, but all + * OSes I've seen treat the two codes + * identically. So, as an implementor, unless + * your keyboard generates both of those + * codes and your OS treats them differently, + * you should generate BACKSLASH + * instead of this code. As a user, you + * should not rely on this code because SDL + * will never generate it with most (all?) + * keyboards. + */ + SEMICOLON = 51, + APOSTROPHE = 52, + GRAVE = 53, /**< Located in the top left corner (on both ANSI + * and ISO keyboards). Produces GRAVE ACCENT and + * TILDE in a US Windows layout and in US and UK + * Mac layouts on ANSI keyboards, GRAVE ACCENT + * and NOT SIGN in a UK Windows layout, SECTION + * SIGN and PLUS-MINUS SIGN in US and UK Mac + * layouts on ISO keyboards, SECTION SIGN and + * DEGREE SIGN in a Swiss German layout (Mac: + * only on ISO keyboards), CIRCUMFLEX ACCENT and + * DEGREE SIGN in a German layout (Mac: only on + * ISO keyboards), SUPERSCRIPT TWO and TILDE in a + * French Windows layout, COMMERCIAL AT and + * NUMBER SIGN in a French Mac layout on ISO + * keyboards, and LESS-THAN SIGN and GREATER-THAN + * SIGN in a Swiss German, German, or French Mac + * layout on ANSI keyboards. + */ + COMMA = 54, + PERIOD = 55, + SLASH = 56, + + CAPSLOCK = 57, + + F1 = 58, + F2 = 59, + F3 = 60, + F4 = 61, + F5 = 62, + F6 = 63, + F7 = 64, + F8 = 65, + F9 = 66, + F10 = 67, + F11 = 68, + F12 = 69, + + PRINTSCREEN = 70, + SCROLLLOCK = 71, + PAUSE = 72, + INSERT = 73, /**< insert on PC, help on some Mac keyboards (but + does send code 73, not 117) */ + HOME = 74, + PAGEUP = 75, + DELETE = 76, + END = 77, + PAGEDOWN = 78, + RIGHT = 79, + LEFT = 80, + DOWN = 81, + UP = 82, + + NUMLOCKCLEAR = 83, /**< num lock on PC, clear on Mac keyboards + */ + KP_DIVIDE = 84, + KP_MULTIPLY = 85, + KP_MINUS = 86, + KP_PLUS = 87, + KP_ENTER = 88, + KP_1 = 89, + KP_2 = 90, + KP_3 = 91, + KP_4 = 92, + KP_5 = 93, + KP_6 = 94, + KP_7 = 95, + KP_8 = 96, + KP_9 = 97, + KP_0 = 98, + KP_PERIOD = 99, + + NONUSBACKSLASH = 100, /**< This is the additional key that ISO + * keyboards have over ANSI ones, + * located between left shift and Y. + * Produces GRAVE ACCENT and TILDE in a + * US or UK Mac layout, REVERSE SOLIDUS + * (backslash) and VERTICAL LINE in a + * US or UK Windows layout, and + * LESS-THAN SIGN and GREATER-THAN SIGN + * in a Swiss German, German, or French + * layout. */ + APPLICATION = 101, /**< windows contextual menu, compose */ + POWER = 102, /**< The USB document says this is a status flag, + * not a physical key - but some Mac keyboards + * do have a power key. */ + KP_EQUALS = 103, + F13 = 104, + F14 = 105, + F15 = 106, + F16 = 107, + F17 = 108, + F18 = 109, + F19 = 110, + F20 = 111, + F21 = 112, + F22 = 113, + F23 = 114, + F24 = 115, + EXECUTE = 116, + HELP = 117, + MENU = 118, + SELECT = 119, + STOP = 120, + AGAIN = 121, /**< redo */ + UNDO = 122, + CUT = 123, + COPY = 124, + PASTE = 125, + FIND = 126, + MUTE = 127, + VOLUMEUP = 128, + VOLUMEDOWN = 129, +/* not sure whether there's a reason to enable these */ +/* LOCKINGCAPSLOCK = 130, */ +/* LOCKINGNUMLOCK = 131, */ +/* LOCKINGSCROLLLOCK = 132, */ + KP_COMMA = 133, + KP_EQUALSAS400 = 134, + + INTERNATIONAL1 = 135, /**< used on Asian keyboards, see + footnotes in USB doc */ + INTERNATIONAL2 = 136, + INTERNATIONAL3 = 137, /**< Yen */ + INTERNATIONAL4 = 138, + INTERNATIONAL5 = 139, + INTERNATIONAL6 = 140, + INTERNATIONAL7 = 141, + INTERNATIONAL8 = 142, + INTERNATIONAL9 = 143, + LANG1 = 144, /**< Hangul/English toggle */ + LANG2 = 145, /**< Hanja conversion */ + LANG3 = 146, /**< Katakana */ + LANG4 = 147, /**< Hiragana */ + LANG5 = 148, /**< Zenkaku/Hankaku */ + LANG6 = 149, /**< reserved */ + LANG7 = 150, /**< reserved */ + LANG8 = 151, /**< reserved */ + LANG9 = 152, /**< reserved */ + + ALTERASE = 153, /**< Erase-Eaze */ + SYSREQ = 154, + CANCEL = 155, + CLEAR = 156, + PRIOR = 157, + RETURN2 = 158, + SEPARATOR = 159, + OUT = 160, + OPER = 161, + CLEARAGAIN = 162, + CRSEL = 163, + EXSEL = 164, + + KP_00 = 176, + KP_000 = 177, + THOUSANDSSEPARATOR = 178, + DECIMALSEPARATOR = 179, + CURRENCYUNIT = 180, + CURRENCYSUBUNIT = 181, + KP_LEFTPAREN = 182, + KP_RIGHTPAREN = 183, + KP_LEFTBRACE = 184, + KP_RIGHTBRACE = 185, + KP_TAB = 186, + KP_BACKSPACE = 187, + KP_A = 188, + KP_B = 189, + KP_C = 190, + KP_D = 191, + KP_E = 192, + KP_F = 193, + KP_XOR = 194, + KP_POWER = 195, + KP_PERCENT = 196, + KP_LESS = 197, + KP_GREATER = 198, + KP_AMPERSAND = 199, + KP_DBLAMPERSAND = 200, + KP_VERTICALBAR = 201, + KP_DBLVERTICALBAR = 202, + KP_COLON = 203, + KP_HASH = 204, + KP_SPACE = 205, + KP_AT = 206, + KP_EXCLAM = 207, + KP_MEMSTORE = 208, + KP_MEMRECALL = 209, + KP_MEMCLEAR = 210, + KP_MEMADD = 211, + KP_MEMSUBTRACT = 212, + KP_MEMMULTIPLY = 213, + KP_MEMDIVIDE = 214, + KP_PLUSMINUS = 215, + KP_CLEAR = 216, + KP_CLEARENTRY = 217, + KP_BINARY = 218, + KP_OCTAL = 219, + KP_DECIMAL = 220, + KP_HEXADECIMAL = 221, + + LCTRL = 224, + LSHIFT = 225, + LALT = 226, /**< alt, option */ + LGUI = 227, /**< windows, command (apple), meta */ + RCTRL = 228, + RSHIFT = 229, + RALT = 230, /**< alt gr, option */ + RGUI = 231, /**< windows, command (apple), meta */ + + MODE = 257, /**< I'm not sure if this is really not covered + * by any of the above, but since there's a + * special KMOD_MODE for it I'm adding it here + */ + + /* @} *//* Usage page 0x07 */ + + /** + * \name Usage page 0x0C + * + * These values are mapped from usage page 0x0C (USB consumer page). + */ + /* @{ */ + + AUDIONEXT = 258, + AUDIOPREV = 259, + AUDIOSTOP = 260, + AUDIOPLAY = 261, + AUDIOMUTE = 262, + MEDIASELECT = 263, + WWW = 264, + MAIL = 265, + CALCULATOR = 266, + COMPUTER = 267, + AC_SEARCH = 268, + AC_HOME = 269, + AC_BACK = 270, + AC_FORWARD = 271, + AC_STOP = 272, + AC_REFRESH = 273, + AC_BOOKMARKS = 274, + + /* @} *//* Usage page 0x0C */ + + /** + * \name Walther keys + * + * These are values that Christian Walther added (for mac keyboard?). + */ + /* @{ */ + + BRIGHTNESSDOWN = 275, + BRIGHTNESSUP = 276, + DISPLAYSWITCH = 277, /**< display mirroring/dual display + switch, video mode switch */ + KBDILLUMTOGGLE = 278, + KBDILLUMDOWN = 279, + KBDILLUMUP = 280, + EJECT = 281, + SLEEP = 282, + + APP1 = 283, + APP2 = 284, + + /* @} *//* Walther keys */ + + /** + * \name Usage page 0x0C (additional media keys) + * + * These values are mapped from usage page 0x0C (USB consumer page). + */ + /* @{ */ + + AUDIOREWIND = 285, + AUDIOFASTFORWARD = 286, + + /* @} *//* Usage page 0x0C (additional media keys) */ + + /* Add any other keys here. */ + + NUM_SCANCODES = 512 /**< not a key, just marks the number of scancodes + for array bounds */ +}; + +} diff --git a/include/inputs/mouse.hpp b/include/inputs/mouse.hpp new file mode 100644 index 0000000..0715f25 --- /dev/null +++ b/include/inputs/mouse.hpp @@ -0,0 +1,22 @@ +#pragma once + +namespace inputs { + +enum class MouseButton : int { + M_LEFT, + M_MIDDLE, + M_RIGHT, + M_X1, + M_X2, + M_INVALID=7, + M_SIZE=7 +}; + +enum class MouseAxis : int { + X, + Y, + X_SCR, + Y_SCR +}; + +} diff --git a/include/log.hpp b/include/log.hpp new file mode 100644 index 0000000..c46c1a6 --- /dev/null +++ b/include/log.hpp @@ -0,0 +1,16 @@ +#pragma once + +#ifdef NDEBUG +#define SPDLOG_ACTIVE_LEVEL 2 // info +#else +#define SPDLOG_ACTIVE_LEVEL 0 // trace +#endif + +#include + +#define TRACE SPDLOG_TRACE +#define DEBUG SPDLOG_DEBUG +#define INFO SPDLOG_INFO +#define WARN SPDLOG_WARN +#define ERROR SPDLOG_ERROR +#define CRITICAL SPDLOG_CRITICAL \ No newline at end of file diff --git a/include/object.hpp b/include/object.hpp new file mode 100644 index 0000000..3c6d06d --- /dev/null +++ b/include/object.hpp @@ -0,0 +1,136 @@ +#pragma once + +#include + +#include +#include +#include +#include +#include + +class Window; +class Input; +class ResourceManager; + +class SceneRoot; +class Component; + +namespace components { + class Transform; + class Camera; + class Renderer; + class UI; + class CustomComponent; +} + +struct GameIO { + Window * const win; + Input * const input; + ResourceManager * const resMan; +}; + +// This object lives until it is deleted by its parent(s) or finally when the "Scene" is destroyed. +// Therefore it is safe to return raw pointers +class Object { + +public: + Object(std::string name, Object* parent, SceneRoot& root, struct GameIO things); + Object(const Object&) = delete; + Object& operator=(const Object&) = delete; + ~Object(); + + Window& win; + Input& inp; + ResourceManager& res; + + SceneRoot& root; + + std::string getName(); + + Object* getParent(); + + Object* getChild(std::string name); + std::vector getChildren(); + + Object* createChild(std::string name); + void deleteChild(std::string name); + + void printTree(int level = 0); + + // Returns the component of type T + // Returns nullptr if the component is not found. + template T* getComponent(); + + // returns the component added + template T* createComponent(); + + template void deleteComponent(); + + struct CompList { + std::vector> cameras; + std::vector> renderers; + std::vector> uis; + std::vector> customs; + }; + + // Adds to the provided vector all components of this object and of its children recursively. + // Ignores 'Transform' + void getAllSubComponents(struct CompList& compList, glm::mat4 t); + +private: + static int s_object_count; + int m_id = s_object_count; + std::string m_name; + std::list> m_children{}; + std::list> m_components{}; + + // If nullptr, this is the root object + Object* const m_parent; + struct GameIO m_gameIO; + +}; + +// implementation of template functions + +template T* Object::getComponent() +{ + if (std::is_base_of::value == false) { + throw std::runtime_error("getComponent() error: specified type is not a subclass of 'Component'"); + } + for (const auto& component : m_components) { + T* derived = dynamic_cast(component.get()); + if (derived != nullptr) { + return derived; + } + } + return nullptr; +} + +template T* Object::createComponent() +{ + if (std::is_base_of::value == false) { + throw std::runtime_error("addComponent() error: specified type is not a subclass of 'Component'"); + } + if (getComponent() != nullptr) { + throw std::runtime_error("addComponent() error: attempt to add component of a type already present"); + } + m_components.emplace_back(std::make_unique(this)); + return dynamic_cast(m_components.back().get()); +} + +template void Object::deleteComponent() +{ + if (std::is_base_of::value == false) { + throw std::runtime_error("deleteComponent() error: specified type is not a subclass of 'Component'"); + } + if (std::is_same::value) { + throw std::runtime_error("deleteComponent() error: attempt to remove the 'Transform' component"); + } + for (auto itr = m_components.begin(); itr != m_components.end(); ++itr) { + if (dynamic_cast((*itr).get()) != nullptr) { + m_components.erase(itr); + return; + } + } + throw std::runtime_error("deleteComponent() error: attempt to delete component that is not present."); +} diff --git a/include/resource_manager.hpp b/include/resource_manager.hpp new file mode 100644 index 0000000..6a2bd8a --- /dev/null +++ b/include/resource_manager.hpp @@ -0,0 +1,79 @@ +#pragma once + +#include "resources/resource.hpp" + +#include +#include + +// Doesn't own resources, only holds weak_ptrs + +class ResourceManager { + +public: + ResourceManager(); + ResourceManager(const ResourceManager&) = delete; + ResourceManager& operator=(const ResourceManager&) = delete; + ~ResourceManager() = default; + + template + std::shared_ptr create(const std::string& name); + + // creates the resource if it is not in the map or the weak_ptr has expired + template + std::shared_ptr get(const std::string& name); + + std::unique_ptr getResourcesListString(); + + std::vector> getAllResourcesOfType(const std::string& type); + + std::filesystem::path getFilePath(const std::string& name); + +private: + std::filesystem::path m_resourcesPath; + std::unordered_map> m_resources; + +}; + +template +std::shared_ptr ResourceManager::create(const std::string& name) +{ + if (std::is_base_of::value == false) { + throw std::runtime_error("ResourceManager::create() error: specified type is not a subclass of 'Resource'"); + } + auto resource = std::make_shared(getFilePath(name)); + m_resources.emplace(name, std::dynamic_pointer_cast(resource)); + return resource; +} + +template +std::shared_ptr ResourceManager::get(const std::string& name) +{ + + if (std::is_base_of::value == false) { + throw std::runtime_error("ResourceManager::get() error: specified type is not a subclass of 'Resource'"); + } + + if (m_resources.contains(name)) { + + std::weak_ptr res = m_resources.at(name); + + if (res.expired() == false) { + // resource definitely exists + auto castedSharedPtr = std::dynamic_pointer_cast(res.lock()); + if (castedSharedPtr == nullptr) { + throw std::runtime_error("error: attempt to get Resource which already exists as another type"); + } + else { + return castedSharedPtr; + } + } + else { + // Resource in map but no longer exists. Delete it. + m_resources.erase(name); + } + + } + + return create(name); + +} diff --git a/include/resources/font.hpp b/include/resources/font.hpp new file mode 100644 index 0000000..a93a960 --- /dev/null +++ b/include/resources/font.hpp @@ -0,0 +1,33 @@ +#pragma once + +#include "resource.hpp" + +#include + +#include + +#include + +namespace resources { + +class Font : public Resource { + +public: + + struct Character { + unsigned int textureID; // openGL texture handle + glm::ivec2 size; + glm::ivec2 bearing; // offset from baseline to top-left of glyph + long advance; // offset to the next glyph + }; + + Font(const std::filesystem::path& resPath); + ~Font() override; + Character getChar(char c); + + private: + std::map m_characters; + +}; + +} diff --git a/include/resources/mesh.hpp b/include/resources/mesh.hpp new file mode 100644 index 0000000..8df7012 --- /dev/null +++ b/include/resources/mesh.hpp @@ -0,0 +1,54 @@ +#pragma once + +#include "resource.hpp" + +#include "resources/shader.hpp" + +#include + +#include +#include + +#include +#include + +struct Vertex { + glm::vec3 pos; + glm::vec3 norm; + glm::vec2 uv; +}; + +namespace resources { + +class Mesh : public Resource { + +public: + Mesh(const std::vector& vertices); + Mesh(const std::vector& vertices, const std::vector& indices); + Mesh(const std::filesystem::path& resPath); + ~Mesh() override; + + void drawMesh(const Shader& shader); + + static void invalidate() + { + s_active_vao = -1; + } + + std::vector m_vertices; + std::vector m_indices; + +private: + static int s_active_vao; + + GLuint m_vao; + GLuint m_vbo; + GLuint m_ebo; + + void bindVAO() const; + + void initMesh(); + +}; + +} diff --git a/include/resources/resource.hpp b/include/resources/resource.hpp new file mode 100644 index 0000000..09ed37b --- /dev/null +++ b/include/resources/resource.hpp @@ -0,0 +1,22 @@ +#pragma once + +#include +#include + +class Resource { + +public: + Resource(const std::filesystem::path& resPath, const std::string& type); + Resource(const Resource&) = delete; + Resource& operator=(const Resource&) = delete; + virtual ~Resource() = 0; + + std::string getType(); + +protected: + std::filesystem::path m_resourcePath; + +private: + const std::string m_type; + +}; diff --git a/include/resources/shader.hpp b/include/resources/shader.hpp new file mode 100644 index 0000000..1ac8c65 --- /dev/null +++ b/include/resources/shader.hpp @@ -0,0 +1,75 @@ +#pragma once + +#include "resource.hpp" + +#include + +#include + +#include +#include + +namespace resources { + +class Shader : public Resource { + +public: + Shader(const std::filesystem::path& resPath); + ~Shader() override; + + enum class UniformType { + FLOAT_MAT4 = GL_FLOAT_MAT4, + FLOAT_VEC2 = GL_FLOAT_VEC2, + FLOAT_VEC3 = GL_FLOAT_VEC3, + SAMPLER_2D = GL_SAMPLER_2D, + NOTFOUND + }; + + void makeActive() const; + + bool setUniform_m4(const std::string& name, const glm::mat4&) const; + bool setUniform_v2(const std::string& name, const glm::vec2&) const; + bool setUniform_v3(const std::string& name, const glm::vec3&) const; + bool setUniform_i(const std::string& name, int) const; + bool setUniform_f(const std::string& name, float) const; + + UniformType getUniformType(const std::string& name) const; + int getAttribLocation(const std::string& name) const; + + static void invalidate() + { + s_activeProgram = -1; + } + +private: + + struct Uniform { + GLint size; + UniformType type; + GLuint location; + }; + + struct Attribute { + GLint size; + UniformType type; + GLuint location; + }; + + // fields + + GLuint m_program; + + std::map m_uniforms{}; + std::map m_attributes{}; + + // static members + + // Only valid if glUseProgram is never called outside this class's method + static GLuint s_activeProgram; + + // -1 if not found + int getUniformLocation(const std::string& name) const; + +}; + +} diff --git a/include/resources/texture.hpp b/include/resources/texture.hpp new file mode 100644 index 0000000..988b98e --- /dev/null +++ b/include/resources/texture.hpp @@ -0,0 +1,27 @@ +#pragma once + +#include "resource.hpp" + +#include + +namespace resources { + +class Texture : public Resource { + +private: + + static GLuint s_binded_texture; + + GLuint m_texture; + + +public: + Texture(const std::filesystem::path& resPath); + ~Texture() override; + + void bindTexture() const; + + static void invalidate(); +}; + +} diff --git a/include/sceneroot.hpp b/include/sceneroot.hpp new file mode 100644 index 0000000..dcb6814 --- /dev/null +++ b/include/sceneroot.hpp @@ -0,0 +1,27 @@ +#pragma once + +#include "object.hpp" + +#include + +// Holds everything you would expect to find in a game scene +class SceneRoot : public Object { + +public: + // create a new empty scene + SceneRoot(struct GameIO things); + SceneRoot(const std::filesystem::path& file, struct GameIO things); + + SceneRoot(const SceneRoot&) = delete; + SceneRoot& operator=(const SceneRoot&) = delete; + ~SceneRoot(); + + void updateStuff(); + + void activateCam(int id); + void deactivateCam(int id); + +private: + std::vector m_activeCameras{}; + +}; diff --git a/include/window.hpp b/include/window.hpp new file mode 100644 index 0000000..3373314 --- /dev/null +++ b/include/window.hpp @@ -0,0 +1,213 @@ +#pragma once + +#include "inputs/keyboard.hpp" +#include "inputs/mouse.hpp" + +#pragma warning (push, 0) +#include +#pragma warning (pop) + +#include + +#include +#include + +extern const uint64_t BILLION; + +class Window { + +public: + Window(const std::string& title); + Window(const Window&) = delete; + Window& operator=(const Window&) = delete; + ~Window(); + + // Return the title name + std::string getTitle() const; + + // Make this window the current OpenGL context. + // This is already done in window initialisation. + void makeContextCurrent(); + + // Tell the GPU to render the back buffer to the screen. + // Run this on every frame. + void swapBuffers(); + // Update the window state to capture any events that have occurred. + // Run this on every frame. + void getInputAndEvents(); + + // if 'true', swapBuffers() will wait in order to synchronise with the + // monitor's refresh rate. + void setVSync(bool enable); + // Returns true if VSync is enabled. + bool getVSync() const; + + glm::ivec2 getViewportSize(); + + void setTitle(std::string title); + + // Hides the window (it will appear closed to the user). + void hide(); + // Shows the window again. + void show(); + // Raises the window above other windows and sets the input focus + void focus(); + // Returns true if the window has focus + bool hasFocus() const; + + // Sets the close flag, check this with shouldClose() + void setCloseFlag(); + // Returns true if the window should remain open + bool isRunning() const; + + void setFullscreen(bool fullscreen, bool exclusive=true); + void toggleFullscreen(); + + bool isFullscreen() const; + + // Relative mouse mode captures the cursor for FPS style use. Returns false if unsupported. + bool setRelativeMouseMode(bool enabled); + + // returns true if relative mouse mode is enabled + bool mouseCaptured(); + + // window events + + // Returns true if the window was just resized during the previous frame + bool getWindowResized() const; + // Set the window resized flag (to recalculate aspect ratios and such) + inline void setResizedFlag() + { + m_justResized = true; + } + + // keyboard events + + // returns true if key is down + bool getKey(inputs::Key key) const; + // returns true if key was just pressed + bool getKeyPress(inputs::Key key) const; + // returns true if key was just released + bool getKeyRelease(inputs::Key key) const; + + // mouse events + + // returns true if button is down + bool getButton(inputs::MouseButton button) const; + // returns true if button was just pressed + bool getButtonPress(inputs::MouseButton button) const; + // returns true if button was just released + bool getButtonRelease(inputs::MouseButton button) const; + + // retrieves x coordinate of the mouse + int getMouseX() const; + // retrieves y coordinate of the mouse + int getMouseY() const; + // retrieves mouse x coordinate normalised for OpenGL + float getMouseNormX() const; + // retrieves mouse y coordinate normalised for OpenGL + float getMouseNormY() const; + // retrieves dx of the mouse since the last frame + int getMouseDX() const; + // retrieves dy of the mouse since the last frame + int getMouseDY() const; + // retrieves amount scrolled vertically + float getMouseScrollX() const; + // retrieves amount scrolled horizontally + float getMouseScrollY() const; + + // joystick/gamepad events (maybe), other misc events + + + + // returns the performance counter value in nanoseconds; + uint64_t getNanos() const; + // get the time recorded at the end of the last frame + uint64_t getLastFrameStamp() const; + + // returns the number of frames elapsed since window creation + uint64_t getFrameCount() const; + uint64_t getStartTime() const;; + float dt() const; // returns delta time in seconds + uint64_t getFPS() const; + uint64_t getAvgFPS() const; + + void resetAvgFPS(); + + bool infoBox(const std::string& title, const std::string& msg); + + /* STATIC METHODS */ + static void errorBox(const std::string& message); + + private: + SDL_Window* m_handle; + SDL_GLContext m_glContext; + + bool m_shouldClose = false; + + std::string m_title; + + bool m_fullscreen = false; + bool m_justResized = false; + bool m_keyboardFocus = true; + + // size in screen coordinates + glm::ivec2 m_winSize = glm::vec2(640, 480); + // actual framebuffer size + glm::ivec2 m_fbSize; + + // performance counter frequency + uint64_t m_counterFreq; + + // number of frames swapped + uint64_t m_frames = 0; + // frame count offset for fpsAvg + uint64_t m_avgFpsStartCount = 0; + // in nanoseconds + uint64_t m_startTime; + // in nanoseconds + uint64_t m_lastFrameStamp; + // in nanoseconds; elapsed time between frames + uint64_t m_lastFrameTime = 1; // not 0 to avoid division by zero + // in nanoseconds + uint64_t m_avgFpsStart; + + // input stuff + + enum class ButtonDelta { + SAME = 0, + PRESSED, + RELEASED + }; + + struct { + std::array keys; + std::array deltas; + } m_keyboard{ }; + + struct { + std::array(inputs::MouseButton::M_SIZE)> buttons; + std::array deltas; + Sint32 x; + Sint32 y; + Sint32 dx; + Sint32 dy; + float xscroll; + float yscroll; + bool captured = false; + } m_mouse{ }; + + // private methods + + void onResize(Sint32 width, Sint32 height); + void resetInputDeltas(); + + // event methods (like callbacks) + + void onWindowEvent(SDL_WindowEvent& e); + void onKeyEvent(SDL_KeyboardEvent& e); + void onMouseButtonEvent(SDL_MouseButtonEvent& e); + void onMouseMotionEvent(SDL_MouseMotionEvent& e); + void onMouseWheelEvent(SDL_MouseWheelEvent& e); + +}; diff --git a/src/components/camera.cpp b/src/components/camera.cpp new file mode 100644 index 0000000..ce313b5 --- /dev/null +++ b/src/components/camera.cpp @@ -0,0 +1,101 @@ +#include "components/camera.hpp" + +#include "resource_manager.hpp" +#include "resources/shader.hpp" + +#include "sceneroot.hpp" + +#include "window.hpp" + +#include "log.hpp" + +static const std::string VIEW_MAT_UNIFORM = "viewMat"; +static const std::string PROJ_MAT_UNIFORM = "projMat"; +static const std::string WINDOW_SIZE_UNIFORM = "windowSize"; + +namespace components { + +glm::vec4 Camera::s_clearColor{-999.0f, -999.0f, -999.0f, -999.0f}; + +Camera::Camera(Object* parent) : Component(parent, TypeEnum::CAMERA) +{ + parent->root.activateCam(getID()); + glEnable(GL_DEPTH_TEST); + + glDisable(GL_STENCIL_TEST); + + glEnable(GL_CULL_FACE); +} + +Camera::~Camera() +{ + parent.root.deactivateCam(getID()); +} + +void Camera::updateCam(glm::mat4 transform) +{ + + if (parent.win.getWindowResized()) { + if (m_mode == Modes::PERSPECTIVE) { + usePerspective(m_fovDeg); + } else { + useOrtho(); + } + } + + glm::mat4 viewMatrix = glm::inverse(transform); + + using namespace resources; + + auto resPtrs = parent.res.getAllResourcesOfType("shader"); + for (const auto& resPtr : resPtrs) { + // shader ref count increased by 1, but only in this scope + auto lockedPtr = resPtr.lock(); + auto shader = dynamic_cast(lockedPtr.get()); + shader->setUniform_m4(VIEW_MAT_UNIFORM, viewMatrix); + shader->setUniform_m4(PROJ_MAT_UNIFORM, m_projMatrix); + shader->setUniform_v2(WINDOW_SIZE_UNIFORM, win.getViewportSize()); + shader->setUniform_v3("lightPos", m_lightPos); + } + + if (s_clearColor != clearColor) { + s_clearColor = clearColor; + glClearColor(clearColor.r, clearColor.g, clearColor.b, clearColor.a); + } + + glClear((m_noClear ? 0 : GL_COLOR_BUFFER_BIT) | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); + +} + +static glm::vec2 getViewportSize() +{ + GLint64 viewportParams[4]; + glGetInteger64v(GL_VIEWPORT, viewportParams); + return { viewportParams[2], viewportParams[3] }; +} + +void Camera::usePerspective(float fovDeg) +{ + constexpr float NEAR = 0.1f; + constexpr float FAR = 1000.0f; + + m_mode = Modes::PERSPECTIVE; + m_fovDeg = fovDeg; + + glm::vec2 viewportDim = getViewportSize(); + + float fovRad = glm::radians(fovDeg); + m_projMatrix = glm::perspectiveFov(fovRad, viewportDim.x, viewportDim.y, NEAR, FAR); +} + +void Camera::useOrtho() +{ + m_mode = Modes::ORTHOGRAPHIC; + + glm::vec2 viewportDim = getViewportSize(); + float aspect = viewportDim.x / viewportDim.y; + + m_projMatrix = glm::ortho(-10.0f * aspect, 10.0f * aspect, -10.0f, 10.0f, -100.0f, 100.0f); +} + +} diff --git a/src/components/component.cpp b/src/components/component.cpp new file mode 100644 index 0000000..9bc9f45 --- /dev/null +++ b/src/components/component.cpp @@ -0,0 +1,30 @@ +#include "components/component.hpp" + +#include "object.hpp" + +#include + +int Component::s_next_component_id = 0; + +Component::Component(Object* parent, TypeEnum type) : + m_type(type), parent(*parent), + win(parent->win), + inp(parent->inp), + res(parent->res) +{ + s_next_component_id++; +} + +Component::~Component() +{ +} + +int Component::getID() +{ + return m_id; +} + +Component::TypeEnum Component::getType() +{ + return m_type; +} diff --git a/src/components/custom.cpp b/src/components/custom.cpp new file mode 100644 index 0000000..9338213 --- /dev/null +++ b/src/components/custom.cpp @@ -0,0 +1,24 @@ +#include "components/custom.hpp" + +namespace components { + + CustomComponent::CustomComponent(Object* parent) : Component(parent, TypeEnum::CUSTOM) + { + } + + CustomComponent::~CustomComponent() + { + + } + + void CustomComponent::onInit() + { + + } + + void CustomComponent::onUpdate(glm::mat4 t) + { + + } + +} \ No newline at end of file diff --git a/src/components/mesh_renderer.cpp b/src/components/mesh_renderer.cpp new file mode 100644 index 0000000..189fdc2 --- /dev/null +++ b/src/components/mesh_renderer.cpp @@ -0,0 +1,50 @@ +#include "components/mesh_renderer.hpp" + +#include "object.hpp" + +#include "resource_manager.hpp" + +#include + +namespace components { + +Renderer::Renderer(Object* parent) : Component(parent, TypeEnum::RENDERER) +{ + m_shader = this->parent.res.get("shaders/basic.glsl"); + m_texture = this->parent.res.get("textures/missing.png"); +} + +Renderer::~Renderer() +{ + +} + +void Renderer::render(glm::mat4 transform) +{ + + m_shader->setUniform_f("ambientStrength", 0.4f); + m_shader->setUniform_v3("ambientColor", { 1.0f, 1.0f, 1.0f }); + + m_shader->setUniform_v3("lightColor", { 1.0f, 1.0f, 1.0f }); + + m_shader->setUniform_m4("modelMat", transform ); + + m_texture->bindTexture(); + m_shader->setUniform_v3("baseColor", m_color ); + m_shader->setUniform_v3("emission", m_emission ); + + if (m_mesh) + m_mesh->drawMesh(*m_shader); +} + +void Renderer::setMesh(const std::string& name) +{ + m_mesh = parent.res.get(name); +} + +void Renderer::setTexture(const std::string& name) +{ + m_texture = parent.res.get(name); +} + +} diff --git a/src/components/text_ui_renderer.cpp b/src/components/text_ui_renderer.cpp new file mode 100644 index 0000000..5a26fff --- /dev/null +++ b/src/components/text_ui_renderer.cpp @@ -0,0 +1,81 @@ +#include "components/text_ui_renderer.hpp" + +#include "object.hpp" +#include "resource_manager.hpp" +#include "resources/texture.hpp" + +namespace components { + +UI::UI(Object* parent) : Component(parent, TypeEnum::UI) +{ + const std::string FONTFILE{ "fonts/LiberationMono-Regular.ttf" }; + m_font = parent->res.get(FONTFILE); + m_shader = parent->res.get("shaders/font.glsl"); + + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); +} + +UI::~UI() +{ +} + +void UI::render(glm::mat4 transform) +{ + + glActiveTexture(GL_TEXTURE0); + m_shader->setUniform_m4("modelMat", transform); + + m_shader->setUniform_v3("textColor", m_color); + m_shader->setUniform_i("textScaling", (int)m_scaled); + + std::vector glyphs; + for (char c : m_text) { + glyphs.push_back(m_font->getChar(c)); + } + + constexpr float scale = 0.002f; + float x = 0.0f; + + if (m_alignment == Alignment::RIGHT) { + + // first find the length of the text + float len = 0.0f; + for (const auto& glyph : glyphs) { + len += (glyph.advance >> 6) * scale; + } + + x = -len; + + } + + for (const auto& glyph : glyphs) { + + float xpos = x + glyph.bearing.x * scale; + float ypos = (glyph.bearing.y - glyph.size.y) * scale; + + float w = glyph.size.x * scale; + float h = glyph.size.y * scale; + + resources::Mesh mesh({ + {{xpos, ypos + h, 0.0f}, {}, {0.0f, 0.0f}}, + {{xpos, ypos , 0.0f}, {}, {0.0f, 1.0f}}, + {{xpos + w, ypos, 0.0f}, {}, {1.0f, 1.0f}}, + {{xpos, ypos + h, 0.0f}, {}, {0.0f, 0.0f}}, + {{xpos + w, ypos, 0.0f}, {}, {1.0f, 1.0f}}, + {{xpos + w, ypos + h, 0.0f}, {}, {1.0f, 0.0f}}, + }); + + glBindTexture(GL_TEXTURE_2D, glyph.textureID); + + mesh.drawMesh(*m_shader); + + x += (glyph.advance >> 6) * scale; + + } + + resources::Texture::invalidate(); + +} + +} diff --git a/src/components/transform.cpp b/src/components/transform.cpp new file mode 100644 index 0000000..7a3cd97 --- /dev/null +++ b/src/components/transform.cpp @@ -0,0 +1,16 @@ +#include "components/transform.hpp" + +#include + +namespace components { + +Transform::Transform(Object* parent) : Component(parent, TypeEnum::TRANSFORM) +{ + +} + +Transform::~Transform() +{ +} + +} diff --git a/src/input.cpp b/src/input.cpp new file mode 100644 index 0000000..ace2186 --- /dev/null +++ b/src/input.cpp @@ -0,0 +1,232 @@ +#include "input.hpp" + +#include "window.hpp" + +#include +#include + +Input::Input(const Window &win) : m_win(win) +{ + m_enabledDevices.fill(true); +} + +Input::~Input() +{ +} + +// private methods + +float Input::getDeviceAxis(enum InputDevice device, int axis) const +{ + switch (device) { + case InputDevice::MOUSE: + switch (static_cast(axis)) { + case inputs::MouseAxis::X: + return static_cast(m_win.getMouseDX()); + case inputs::MouseAxis::Y: + return static_cast(m_win.getMouseDY()); + case inputs::MouseAxis::X_SCR: + return m_win.getMouseScrollX(); + case inputs::MouseAxis::Y_SCR: + return m_win.getMouseScrollY(); + default: break; + } + break; + case InputDevice::KEYBOARD: + break; + case InputDevice::CONTROLLER: + break; + default: break; + } + throw std::runtime_error("Error getting device axis"); +} + +bool Input::getDeviceButton(enum InputDevice device, int button) const +{ + switch (device) { + case InputDevice::MOUSE: + return m_win.getButton(static_cast(button)); + case InputDevice::KEYBOARD: + return m_win.getKey(static_cast(button)); + case InputDevice::CONTROLLER: + break; + default: break; + } + throw std::runtime_error("Error getting device button"); +} + +bool Input::getDeviceButtonDown(enum InputDevice device, int button) const +{ + switch (device) { + case InputDevice::MOUSE: + return m_win.getButtonPress(static_cast(button)); + case InputDevice::KEYBOARD: + return m_win.getKeyPress(static_cast(button)); + case InputDevice::CONTROLLER: + break; + default: break; + } + throw std::runtime_error("Error getting device button"); +} + +bool Input::getDeviceButtonUp(enum InputDevice device, int button) const +{ + switch (device) { + case InputDevice::MOUSE: + return m_win.getButtonRelease(static_cast(button)); + case InputDevice::KEYBOARD: + return m_win.getKeyRelease(static_cast(button)); + case InputDevice::CONTROLLER: + break; + default: break; + } + throw std::runtime_error("Error getting device button"); +} + +float Input::getButtonAxis(enum InputDevice device, int high, int low) const +{ + float value = 0.0f; + if (getDeviceButton(device, high)) value += 1.0f; + if (low != 0) { + if (getDeviceButton(device, low)) value += -1.0f; + } + return value; +} + +// public methods + +void Input::addInputButton(const std::string& name, InputDevice device, int button) +{ + m_buttonEntries.push_back( { name, device, button } ); +} + +void Input::addInputAxis(const std::string& name, InputDevice device, int axis) +{ + m_axisEntries.push_back( { name, device, axis, false, 0, 0 } ); +} + +void Input::addInputButtonAsAxis(const std::string& name, InputDevice device, int high, int low) +{ + m_axisEntries.push_back( { name, device, 0, true, high, low } ); +} + +// OVERLOADS: + +// Add a mouse input +void Input::addInputButton(const std::string& name, inputs::MouseButton button) +{ + addInputButton(name, InputDevice::MOUSE, static_cast(button)); +} + +void Input::addInputAxis(const std::string& name, inputs::MouseAxis axis) +{ + addInputAxis(name, InputDevice::MOUSE, static_cast(axis)); +} + +void Input::addInputButtonAsAxis(const std::string& name, inputs::MouseButton high, inputs::MouseButton low) +{ + addInputButtonAsAxis(name, InputDevice::MOUSE, static_cast(high), static_cast(low)); +} + +// Add a keyboard input (TODO: add KeyboardButton enum class) +void Input::addInputButton(const std::string& name, inputs::Key button) +{ + addInputButton(name, InputDevice::KEYBOARD, static_cast(button)); +} + +void Input::addInputButtonAsAxis(const std::string& name, inputs::Key high, inputs::Key low) +{ + addInputButtonAsAxis(name, InputDevice::KEYBOARD, static_cast(high), static_cast(low)); +} + +void Input::delInputButton(int index) +{ + std::vector::iterator it = m_buttonEntries.begin(); + std::advance(it, index); + m_buttonEntries.erase(it); +} + +void Input::delInputAxis(int index) +{ + std::vector::iterator it = m_axisEntries.begin(); + std::advance(it, index); + m_axisEntries.erase(it); +} + +void Input::setDeviceActive(enum InputDevice device, bool active) +{ + m_enabledDevices[static_cast(device)] = active; +} + +bool Input::getDeviceActive(enum InputDevice device) const +{ + return m_enabledDevices[static_cast(device)]; +} + +float Input::getAxis(const std::string& axisName) const +{ + for (const AxisEntry& e : m_axisEntries) { + if (e.name == axisName) { + if (m_enabledDevices[static_cast(e.device)]) { + if (e.isButtonAxis) { + return getButtonAxis(e.device, e.high, e.low); + } else { + return getDeviceAxis(e.device, e.axis); + } + } + } + } + return 0.0f; // instead of throwing an exception, just return nothing +// throw std::runtime_error("Unable to find mapping in input table"); +} + +bool Input::getButton(const std::string& buttonName) const +{ + bool isDown = false; + + for (const ButtonEntry& e : m_buttonEntries) { + if (e.name == buttonName) { + if (m_enabledDevices[static_cast(e.device)]) { + if (getDeviceButton(e.device, e.button) == true) { + isDown = true; + break; + } + } + } + } + return isDown; +} + +bool Input::getButtonPress(const std::string& buttonName) const +{ + bool isPressed = false; + + for (const ButtonEntry& e : m_buttonEntries) { + if (e.name == buttonName) { + if (m_enabledDevices[static_cast(e.device)]) { + if (getDeviceButtonDown(e.device, e.button) == true) { + isPressed = true; + break; + } + } + } + } + return isPressed; +} + +bool Input::getButtonRelease(const std::string& buttonName) const +{ + bool isReleased = false; + + for (const ButtonEntry& e : m_buttonEntries) { + if (e.name == buttonName) { + if (m_enabledDevices[static_cast(e.device)]) { + if (getDeviceButtonUp(e.device, e.button) == true) { + isReleased = true; + break; + } + } + } + } + return isReleased; +} diff --git a/src/object.cpp b/src/object.cpp new file mode 100644 index 0000000..6ec1790 --- /dev/null +++ b/src/object.cpp @@ -0,0 +1,136 @@ +#include "object.hpp" + +#include "components/transform.hpp" +#include "components/camera.hpp" +#include "components/mesh_renderer.hpp" +#include "components/text_ui_renderer.hpp" +#include "components/custom.hpp" + +#include + +int Object::s_object_count = 0; + +Object::Object(std::string name, Object* parent, SceneRoot& root, struct GameIO things) + : m_name(name), m_parent(parent), root(root), + m_gameIO(things), + win(*things.win), + inp(*things.input), + res(*things.resMan) +{ + s_object_count++; + // all objects come with at least a transform component + createComponent(); +} + +Object::~Object() +{ +} + +std::string Object::getName() +{ + return m_name; +} + +Object* Object::getParent() +{ + return m_parent; +} + +Object* Object::getChild(std::string name) +{ + for (const auto& child : m_children) { + if (name == child->getName()) { + return child.get(); + } + } + return nullptr; +} + +std::vector Object::getChildren() +{ + std::vector newVector{}; + for (const auto& child : m_children) { + newVector.push_back(child.get()); + } + return newVector; +} + +Object* Object::createChild(std::string name) +{ + if (getChild(name) != nullptr) { + throw std::runtime_error("Attempt to create child object with existing name"); + } + m_children.emplace_back(std::make_unique(name, this, root, m_gameIO)); + return m_children.back().get(); +} + +void Object::deleteChild(std::string name) +{ + for (auto itr = m_children.begin(); itr != m_children.end(); ++itr) { + if ((*itr)->getName() == name) { + m_children.erase(itr); + return; + } + } + throw std::runtime_error("Unable to delete child '" + name + "' as it does not exist"); +} + +void Object::printTree(int level) +{ + std::string buf; + for (int i = 0; i < level; i++) { + if (i+1 == level) { + buf += "\\_______"; + } else { + buf += " "; + } + } + buf += m_name; + INFO(buf); + for (const auto& child : this->getChildren()) { + child->printTree(level+1); + } +} + +void Object::getAllSubComponents(struct CompList& compList, glm::mat4 parentTransform) +{ + using namespace components; + + glm::mat4 objTransform{1.0f}; + + auto t = getComponent(); + + // rotation + objTransform = glm::mat4_cast(t->rotation); + + // position + reinterpret_cast(objTransform[3]) = t->position; + + // scale (effectively applied first + objTransform = glm::scale(objTransform, t->scale); + + const glm::mat4 newTransform = parentTransform * objTransform; + + for (const auto& compUnq : m_components) { + const auto comp = compUnq.get(); + switch (comp->getType()) { + case Component::TypeEnum::CAMERA: + compList.cameras.emplace_back(dynamic_cast(comp), newTransform); + break; + case Component::TypeEnum::RENDERER: + compList.renderers.emplace_back(dynamic_cast(comp), newTransform); + break; + case Component::TypeEnum::UI: + compList.uis.emplace_back(dynamic_cast(comp), newTransform); + break; + case Component::TypeEnum::CUSTOM: + compList.customs.emplace_back(dynamic_cast(comp), newTransform); + break; + default: + break; + } + } + for (const auto& child : m_children) { + child->getAllSubComponents(compList, newTransform); + } +} diff --git a/src/resource_manager.cpp b/src/resource_manager.cpp new file mode 100644 index 0000000..34941e1 --- /dev/null +++ b/src/resource_manager.cpp @@ -0,0 +1,71 @@ +#include "resource_manager.hpp" + +#ifdef _MSC_VER +#include +#define MAX_PATH 260 +#endif + +ResourceManager::ResourceManager() +{ +#ifdef _MSC_VER + CHAR exeDirBuf[MAX_PATH + 1]; + GetModuleFileNameA(NULL, exeDirBuf, MAX_PATH + 1); + std::filesystem::path cwd = std::filesystem::path(exeDirBuf).parent_path(); +#else + std::filesystem::path cwd = std::filesystem::current_path(); +#endif + + if (std::filesystem::is_directory(cwd / "res")) { + m_resourcesPath = std::filesystem::absolute("res"); + } else { + m_resourcesPath = cwd.parent_path() / "share" / "sdltest"; + } + + if (std::filesystem::is_directory(m_resourcesPath) == false) { + m_resourcesPath = cwd.root_path() / "usr" / "local" / "share" / "sdltest"; + } + + if (std::filesystem::is_directory(m_resourcesPath) == false) { + throw std::runtime_error("Unable to determine resources location"); + } +} + +std::unique_ptr ResourceManager::getResourcesListString() +{ + auto bufPtr = std::make_unique(); + std::string& buf = *bufPtr; + int maxLength = 0; + for (const auto& [name, ptr] : m_resources) { + if (name.length() > maxLength) + maxLength = name.length(); + } + for (const auto& [name, ptr] : m_resources) { + buf += name; + for (int i = 0; i < (maxLength - name.length() + 4); i++) { + buf += " "; + } + buf += std::to_string(ptr.use_count()) + "\n"; + } + return bufPtr; +} + + +std::vector> ResourceManager::getAllResourcesOfType(const std::string& type) +{ + std::vector> resources; + for (const auto& [name, ptr] : m_resources) { + if (ptr.expired() == false) { + if (ptr.lock()->getType() == type) { + resources.push_back(ptr); + } + } + } + return resources; +} + +// private + +std::filesystem::path ResourceManager::getFilePath(const std::string& name) +{ + return m_resourcesPath / name; +} diff --git a/src/resources/font.cpp b/src/resources/font.cpp new file mode 100644 index 0000000..41df828 --- /dev/null +++ b/src/resources/font.cpp @@ -0,0 +1,96 @@ +#include "resources/font.hpp" + +#include +#include FT_FREETYPE_H + +namespace resources { + +Font::Font(const std::filesystem::path& resPath) : Resource(resPath, "font") +{ + + FT_Library library; + FT_Face face; + int err; + + err = FT_Init_FreeType(&library); + if (err) { + throw std::runtime_error("Failed to initialise freetype library"); + } + + err = FT_New_Face(library, resPath.string().c_str(), 0, &face); + if (err == FT_Err_Unknown_File_Format) { + FT_Done_FreeType(library); + throw std::runtime_error("Unknown file format for font '" + resPath.string() + "'"); + } + else if (err != 0) { + FT_Done_FreeType(library); + throw std::runtime_error("Unable to open font '" + resPath.string() + "'"); + } + + err = FT_Set_Pixel_Sizes(face, 0, 64); + if (err) { + FT_Done_Face(face); + FT_Done_FreeType(library); + throw std::runtime_error("Attempt to set pixel size to one unavailable in the font"); + } + + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + + for (unsigned char c = 0; c < 128; c++) { + + err = FT_Load_Char(face, c, FT_LOAD_RENDER); + if (err) { + FT_Done_Face(face); + FT_Done_FreeType(library); + throw std::runtime_error("Unable to load char glyph"); + } + + // generate texture + unsigned int texture; + glGenTextures(1, &texture); + glBindTexture(GL_TEXTURE_2D, texture); + glTexImage2D( + GL_TEXTURE_2D, + 0, + GL_RED, + face->glyph->bitmap.width, + face->glyph->bitmap.rows, + 0, + GL_RED, + GL_UNSIGNED_BYTE, + face->glyph->bitmap.buffer + ); + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + + Character character = { + texture, + glm::ivec2{face->glyph->bitmap.width, face->glyph->bitmap.rows}, // Size of Glyph + glm::ivec2{face->glyph->bitmap_left, face->glyph->bitmap_top}, // Offset from baseline (bottom-left) to top-left of glyph + face->glyph->advance.x + }; + + m_characters.insert(std::make_pair(c, character)); + + } + + glPixelStorei(GL_UNPACK_ALIGNMENT, 4); // reset alignment settings + + FT_Done_Face(face); + FT_Done_FreeType(library); + +} + +Font::~Font() +{ +} + +Font::Character Font::getChar(char c) +{ + return m_characters.at(c); +} + +} diff --git a/src/resources/mesh.cpp b/src/resources/mesh.cpp new file mode 100644 index 0000000..c155882 --- /dev/null +++ b/src/resources/mesh.cpp @@ -0,0 +1,119 @@ +#include "resources/mesh.hpp" + +namespace resources { + +struct MeshFileHeader { + unsigned int vertex_count; + unsigned int index_count; + int material; +}; + +static void loadMeshFromFile(const std::filesystem::path& path, std::vector* vertices, std::vector* indices) +{ + + // TODO + // Replace this aberation with something that's readable and doesn't use FILE* + + struct MeshFileHeader header{}; + + FILE* fp = fopen(path.string().c_str(), "rb"); + if (fp == NULL) { + throw std::runtime_error("Unable to open mesh file " + path.string()); + } + + fread(&header, sizeof(struct MeshFileHeader), 1, fp); + + indices->resize(header.index_count); + vertices->resize(header.vertex_count); + + fread(&(*indices)[0], sizeof(unsigned int) * header.index_count, 1, fp); + fread(&((*vertices)[0].pos[0]), sizeof(float) * 8 * header.vertex_count, 1, fp); + + fclose(fp); + +} + +static void loadObjFromFile(const std::filesystem::path& path, std::vector* vertices, std::vector* indices) +{ + +} + +// -1 means invalidated +int Mesh::s_active_vao = -1; + +void Mesh::bindVAO() const +{ + if (s_active_vao != m_vao) { + glBindVertexArray(m_vao); + s_active_vao = m_vao; + } +} + +void Mesh::initMesh() +{ + glGenVertexArrays(1, &m_vao); + bindVAO(); + glGenBuffers(1, &m_vbo); + glGenBuffers(1, &m_ebo); + + glBindBuffer(GL_ARRAY_BUFFER, m_vbo); + glBufferData(GL_ARRAY_BUFFER, m_vertices.size()*sizeof(Vertex), &m_vertices[0], GL_STATIC_DRAW); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_ebo); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, m_indices.size() * sizeof(unsigned int), &(m_indices[0]), GL_STATIC_DRAW); + + // position + glEnableVertexAttribArray(0); + glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)offsetof(Vertex, pos)); + // normal + glEnableVertexAttribArray(1); + glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)offsetof(Vertex, norm)); + // uv + glEnableVertexAttribArray(2); + glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)offsetof(Vertex, uv)); + +} + +void Mesh::drawMesh(const Shader& shader) +{ + bindVAO(); + shader.makeActive(); +#ifndef SDLTEST_NOGFX + glDrawElements(GL_TRIANGLES, static_cast(m_indices.size()), GL_UNSIGNED_INT, 0); +#endif +} + +Mesh::Mesh(const std::vector& vertices) : Resource("", "mesh") +{ + // constructor for custom meshes without an index array + m_vertices = vertices; // COPY over vertices + for (int i = 0; i < m_vertices.size(); i++) { + m_indices.push_back(i); + } + initMesh(); +} + +Mesh::Mesh(const std::vector& vertices, const std::vector& indices) : Resource("", "mesh") +{ + m_vertices = vertices; // COPY over vertices + m_indices = indices; // COPY over indices; + initMesh(); +} + +// To be used with the resource manager +Mesh::Mesh(const std::filesystem::path& resPath) : Resource(resPath, "mesh") +{ + loadMeshFromFile(resPath, &m_vertices, &m_indices); + initMesh(); +} + +Mesh::~Mesh() +{ + glDeleteVertexArrays(1, &m_vao); + glDeleteBuffers(1, &m_vbo); + glDeleteBuffers(1, &m_ebo); + if (s_active_vao == m_vao) { + s_active_vao = -1; + } +} + +} diff --git a/src/resources/resource.cpp b/src/resources/resource.cpp new file mode 100644 index 0000000..faa40b7 --- /dev/null +++ b/src/resources/resource.cpp @@ -0,0 +1,20 @@ +#include "resources/resource.hpp" + +#include + +Resource::Resource(const std::filesystem::path& resPath, const std::string& type) : m_resourcePath(resPath), m_type(type) +{ + if (m_type != "mesh") + TRACE("Creating {} resource: {}", type, resPath.filename().string()); +} + +Resource::~Resource() +{ + if (m_type != "mesh") + TRACE("Destroyed {} resource: {}", m_type, m_resourcePath.filename().string()); +} + +std::string Resource::getType() +{ + return m_type; +} diff --git a/src/resources/shader.cpp b/src/resources/shader.cpp new file mode 100644 index 0000000..59c1a7f --- /dev/null +++ b/src/resources/shader.cpp @@ -0,0 +1,223 @@ +#include "resources/shader.hpp" + +#include + +#include + +#include +#include +#include +#include + +static std::unique_ptr> readFile(const char * path) +{ + std::ifstream file(path, std::ios::binary | std::ios::ate); + //file.exceptions(std::ifstream::failbit); // throw exception if file cannot be opened + if (file.fail()) { + throw std::runtime_error("Failed to open file for reading: " + std::string(path)); + } + size_t size = file.tellg(); + file.seekg(0, std::ios::beg); + auto buf = std::make_unique>(); + buf->resize(size); + file.rdbuf()->sgetn(buf->data(), size); + return buf; +} + +static GLuint compile(const char *path, GLenum type) +{ + auto src = readFile(path); + + // compile shader + GLuint handle = glCreateShader(type); + GLint size = src->size(); + GLchar *data = src->data(); + glShaderSource(handle, 1, &data, &size); + glCompileShader(handle); + + // check for compilation error + GLint compiled; + glGetShaderiv(handle, GL_COMPILE_STATUS, &compiled); + if (compiled == 0) { + GLint log_len; + glGetShaderiv(handle, GL_INFO_LOG_LENGTH, &log_len); + GLchar *log_msg = (GLchar *)calloc(1, log_len); + if (log_msg == NULL) { + throw std::runtime_error("Error allocating memory for shader compilation error log"); + } + glGetShaderInfoLog(handle, log_len, NULL, log_msg); + throw std::runtime_error("Shader compilation error in " + std::string(path) + " log:\n" + std::string(log_msg)); + } return handle; +} + +namespace resources { + +// I've got to do this because of GL's stupid state machine +GLuint Shader::s_activeProgram = 0; + +Shader::Shader(const std::filesystem::path& resPath) : Resource(resPath, "shader") +{ + + const std::string vertexShaderPath = (resPath.parent_path()/std::filesystem::path(resPath.stem().string() + ".vert")).string(); + const std::string fragmentShaderPath = (resPath.parent_path()/std::filesystem::path(resPath.stem().string() + ".frag")).string(); + GLuint vs = compile(vertexShaderPath.c_str(), GL_VERTEX_SHADER); + GLuint fs = compile(fragmentShaderPath.c_str(), GL_FRAGMENT_SHADER); + m_program = glCreateProgram(); + glAttachShader(m_program, vs); + glAttachShader(m_program, fs); + glLinkProgram(m_program); + glValidateProgram(m_program); + + // flag shader objects for deletion, this does not take effect until the program is deleted + glDeleteShader(vs); + glDeleteShader(fs); + + GLint linked, validated; + glGetProgramiv(m_program, GL_LINK_STATUS, &linked); + glGetProgramiv(m_program, GL_VALIDATE_STATUS, &validated); + if (linked == 0 || validated == 0) { + GLint log_len; + glGetProgramiv(m_program, GL_INFO_LOG_LENGTH, &log_len); + GLchar *log_msg = (GLchar *)calloc(1, log_len); + if (log_msg == NULL) { + throw std::runtime_error("Error allocating memory for shader linking error log"); + } + glGetProgramInfoLog(m_program, log_len, NULL, log_msg); + throw std::runtime_error("Program linking error with " + vertexShaderPath + " and " + fragmentShaderPath + " log:\n" + log_msg); + } + + DEBUG("For shader {}:", resPath.filename().string()); + + // now get uniforms + GLint count; + glGetProgramiv(m_program, GL_ACTIVE_UNIFORMS, &count); + for (int i = 0; i < count; i++) { + char nameBuf[64] = {}; + GLint size; + GLenum type; + glGetActiveUniform(m_program, i, 63, NULL, &size, &type, nameBuf); + m_uniforms[nameBuf] = Uniform{size, static_cast(type), (GLuint)i}; + DEBUG("\tuniform {}", nameBuf); +} + + // now get all attributes + glGetProgramiv(m_program, GL_ACTIVE_ATTRIBUTES, &count); + for (int i = 0; i < count; i++) { + char nameBuf[64] = {}; + GLint size; + GLenum type; + glGetActiveAttrib(m_program, i, 63, NULL, &size, &type, nameBuf); + m_attributes[nameBuf] = Attribute{size, static_cast(type), (GLuint)i}; + DEBUG("\tattrib {}", nameBuf); + } + +} + +Shader::~Shader() +{ + glDeleteProgram(m_program); +} + +void Shader::makeActive() const +{ + if (s_activeProgram != m_program) { + glUseProgram(m_program); + s_activeProgram = m_program; + } +} + +int Shader::getUniformLocation(const std::string& name) const +{ + auto it = m_uniforms.find(name); + if (it != m_uniforms.end()) { + Uniform u = it->second; + return u.location; + } + else { + return -1; + } +} + +bool Shader::setUniform_m4(const std::string& name, const glm::mat4& m) const +{ + makeActive(); + int loc = getUniformLocation(name); + if (loc != -1) { + glUniformMatrix4fv(loc, 1, GL_FALSE, &m[0][0]); + return true; + } + else { + return false; + } +} + +bool Shader::setUniform_v2(const std::string& name, const glm::vec2& v) const +{ + makeActive(); + int loc = getUniformLocation(name); + if (loc != -1) { + glUniform2f(loc, v.x, v.y); + return true; + } + else { + return false; + } + +} + +bool Shader::setUniform_v3(const std::string& name, const glm::vec3& v) const +{ + makeActive(); + int loc = getUniformLocation(name); + if (loc != -1) { + glUniform3f(loc, v.x, v.y, v.z); + return true; + } + else { + return false; + } +} + +bool Shader::setUniform_i(const std::string& name, int n) const +{ + makeActive(); + int loc = getUniformLocation(name); + if (loc != -1) { + glUniform1i(loc, n); + return true; + } + else { + return false; + } +} + +bool Shader::setUniform_f(const std::string& name, float n) const +{ + makeActive(); + int loc = getUniformLocation(name); + if (loc != -1) { + glUniform1f(loc, n); + return true; + } + else { + return false; + } +} + +Shader::UniformType Shader::getUniformType(const std::string& name) const +{ + auto it = m_uniforms.find(name); + if (it != m_uniforms.end()) { + return it->second.type; + } + else { + return UniformType::NOTFOUND; + } +} + +int Shader::getAttribLocation(const std::string& name) const +{ + return m_attributes.at(name).location; +} + +} diff --git a/src/resources/texture.cpp b/src/resources/texture.cpp new file mode 100644 index 0000000..c46afbf --- /dev/null +++ b/src/resources/texture.cpp @@ -0,0 +1,131 @@ +#include "resources/texture.hpp" + +#define STB_IMAGE_IMPLEMENTATION +#include "stb_image.h" + +#include + +#include + +namespace resources { + +// -1 means invalid / no bind +GLuint Texture::s_binded_texture = -1; + +void Texture::invalidate() +{ + s_binded_texture = -1; +} + +// returns false if unable to open file +static bool readPNG(const std::string& path, std::vector& texbuf, int *width, int *height, bool *isRGBA) +{ + int x, y, n; + unsigned char *data = stbi_load(path.c_str(), &x, &y, &n, 0); + + + if (data == nullptr) { + return false; + } + + const size_t size = x * y * n; + + texbuf.resize(size); + memcpy(texbuf.data(), data, size); + + *width = x; + *height = y; + if (n == 4) { + *isRGBA = true; + } else { + *isRGBA = false; + } + + stbi_image_free(data); + + return true; + +} + +static bool readGLRaw(const std::string& path, std::vector& texbuf, int *width, int *height, bool *isRGBA) +{ + FILE *fp = fopen(path.c_str(), "rb"); + if (!fp) { + return false; + } + + fseek(fp, 0x02, SEEK_SET); + uint64_t tex_data_offset; + fread(&tex_data_offset, sizeof(uint64_t), 1, fp); + + fseek(fp, 0L, SEEK_END); + uint64_t end = ftell(fp); + + texbuf.resize(end); + fseek(fp, tex_data_offset, SEEK_SET); + fread(texbuf.data(), 1, end, fp); + + fclose(fp); + + *width = 4096; + *height = 4096; + *isRGBA = false; + + return true; + +} + +Texture::Texture(const std::filesystem::path& resPath) : Resource(resPath, "texture") +{ + + std::vector texbuf; + + int width, height; + bool isRGBA, success; + + if (resPath.extension() == ".png") { + success = readPNG(resPath.string(), texbuf, &width, &height, &isRGBA); + } else { + success = readGLRaw(resPath.string(), texbuf, &width, &height, &isRGBA); + } + + if (!success) { + throw std::runtime_error("Unable to open texture: " + resPath.string()); + } + + glGenTextures(1, &m_texture); + + bindTexture(); // glBindTexture + + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + + if (isRGBA) { + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, texbuf.data()); + } else { + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, texbuf.data()); + } + + glGenerateMipmap(GL_TEXTURE_2D); + + DEBUG("loaded texture {} width: {} height: {} size: {}", resPath.filename().string(), width, height, texbuf.size()); + +} + +Texture::~Texture() +{ + if (s_binded_texture == m_texture) { + s_binded_texture = -1; + } +} + +void Texture::bindTexture() const +{ + if (s_binded_texture != m_texture) { + glBindTexture(GL_TEXTURE_2D, m_texture); + s_binded_texture = m_texture; + } +} + +} diff --git a/src/sceneroot.cpp b/src/sceneroot.cpp new file mode 100644 index 0000000..2a3797c --- /dev/null +++ b/src/sceneroot.cpp @@ -0,0 +1,91 @@ +#include "sceneroot.hpp" + +#include "window.hpp" + +#include "components/custom.hpp" +#include "components/camera.hpp" +#include "components/transform.hpp" +#include "components/mesh_renderer.hpp" +#include "components/text_ui_renderer.hpp" + +#include + +#include +#include + +#include "log.hpp" + +SceneRoot::SceneRoot(struct GameIO things) : Object("root", nullptr, *this, things) +{ +} + +SceneRoot::SceneRoot(const std::filesystem::path& file, struct GameIO things) : SceneRoot(things) +{ + // TODO: make this a resource + //loadFromSceneFile(file); +} + +SceneRoot::~SceneRoot() +{ +} + +// private methods + +// public methods + +void SceneRoot::updateStuff() +{ + + using namespace components; + using namespace glm; + + struct CompList compList{}; + + getAllSubComponents(compList, glm::mat4{1.0f}); + + // update + + for (const auto& [c, t] : compList.customs) { + c->onUpdate(t); + } + + // render + + for (const auto& [c, t] : compList.cameras) { + for (int id : m_activeCameras) { + if (c->getID() == id) { + c->updateCam(t); + for (const auto& [c, t] : compList.renderers) { + c->render(t); + } + + break; + } + } + } + + for (const auto& [c, t] : compList.uis) { + c->render(t); + } + +} + +void SceneRoot::activateCam(int id) +{ + auto& v = m_activeCameras; + + if (std::find(v.begin(), v.end(), id) == v.end()) { + v.push_back(id); + } +} + +void SceneRoot::deactivateCam(int id) +{ + auto& v = m_activeCameras; + + for (auto it = v.begin(); it != v.end(); it++) { + if (*it == id) { + v.erase(it); + } + } +} diff --git a/src/window.cpp b/src/window.cpp new file mode 100644 index 0000000..1dddc44 --- /dev/null +++ b/src/window.cpp @@ -0,0 +1,494 @@ +#include "window.hpp" + +#include + +#include +#include + +const uint64_t BILLION = 1000000000; + +Window::Window(const std::string& title) : m_title(title) +{ + + // init SDL + if (SDL_Init(SDL_INIT_VIDEO) != 0) { + const std::string errMsg("Unable to initialise SDL: " + std::string(SDL_GetError())); + if (SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "SDL error", errMsg.c_str(), NULL) != 0) { + std::cerr << errMsg << "\nAre you in a graphical environment?\n"; + } + throw std::runtime_error(errMsg); + } + + m_counterFreq = SDL_GetPerformanceFrequency(); + m_startTime = getNanos(); + m_lastFrameStamp = m_startTime - 1; + m_avgFpsStart = m_startTime; + + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); + + // create the window + m_handle = SDL_CreateWindow( + m_title.c_str(), + SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, + static_cast(m_winSize.x), + static_cast(m_winSize.y), + SDL_WINDOW_RESIZABLE | SDL_WINDOW_OPENGL | SDL_WINDOW_ALLOW_HIGHDPI); + if (m_handle == NULL) { + SDL_Quit(); + throw std::runtime_error("Unable to create window: " + std::string(SDL_GetError())); + } + + // get window size + int winWidth, winHeight; + SDL_GetWindowSize(m_handle, &winWidth, &winHeight); + m_winSize.x = winWidth; + m_winSize.y = winHeight; + + const int WINDOWED_MIN_WIDTH = 640; + const int WINDOWED_MIN_HEIGHT = 480; + SDL_SetWindowMinimumSize(m_handle, WINDOWED_MIN_WIDTH, WINDOWED_MIN_HEIGHT); + + m_glContext = SDL_GL_CreateContext(m_handle); + if (m_glContext == NULL) { + SDL_DestroyWindow(m_handle); + SDL_Quit(); + throw std::runtime_error("Unable to create OpenGL context: " + std::string(SDL_GetError())); + } + + if (!gladLoadGLLoader((GLADloadproc)SDL_GL_GetProcAddress)) { + SDL_DestroyWindow(m_handle); + SDL_Quit(); + throw std::runtime_error("Unable to initialise GLAD"); + } + + onResize(m_winSize.x, m_winSize.y); + +} + +Window::~Window() +{ + SDL_GL_DeleteContext(m_glContext); + SDL_DestroyWindow(m_handle); + SDL_Quit(); +} + +// private methods + +void Window::onResize(Sint32 width, Sint32 height) +{ + // get window size + m_winSize.x = static_cast(width); + m_winSize.y = static_cast(height); + + // get framebuffer size + int fbWidth, fbHeight; + SDL_GL_GetDrawableSize(m_handle, &fbWidth, &fbHeight); + m_fbSize.x = static_cast(fbWidth); + m_fbSize.y = static_cast(fbHeight); + glViewport(0, 0, fbWidth, fbHeight); + + m_justResized = true; +} + +void Window::resetInputDeltas() +{ + m_justResized = false; + + m_keyboard.deltas.fill(ButtonDelta::SAME); + + m_mouse.deltas.fill(ButtonDelta::SAME); + m_mouse.dx = 0.0f; + m_mouse.dy = 0.0f; + m_mouse.xscroll = 0.0f; + m_mouse.yscroll = 0.0f; +} + +// TODO event methods (like callbacks) + +void Window::onWindowEvent(SDL_WindowEvent &e) +{ + + switch (e.event) { + case SDL_WINDOWEVENT_SIZE_CHANGED: + onResize(e.data1, e.data2); + break; + case SDL_WINDOWEVENT_FOCUS_GAINED: + m_keyboardFocus = true; + break; + case SDL_WINDOWEVENT_FOCUS_LOST: + m_keyboardFocus = false; + break; + } +} + +void Window::onKeyEvent(SDL_KeyboardEvent &e) +{ + bool keyWasDown = m_keyboard.keys[e.keysym.scancode]; + bool keyIsDown = (e.state == SDL_PRESSED); + m_keyboard.keys[e.keysym.scancode] = keyIsDown; + if (keyIsDown != keyWasDown) { // (if key was pressed or released) + m_keyboard.deltas[e.keysym.scancode] = keyIsDown ? ButtonDelta::PRESSED : ButtonDelta::RELEASED; + } +} + +void Window::onMouseButtonEvent(SDL_MouseButtonEvent &e) +{ + enum inputs::MouseButton button = inputs::MouseButton::M_INVALID; + switch (e.button) { + case SDL_BUTTON_LEFT: + button = inputs::MouseButton::M_LEFT; + break; + case SDL_BUTTON_MIDDLE: + button = inputs::MouseButton::M_MIDDLE; + break; + case SDL_BUTTON_RIGHT: + button = inputs::MouseButton::M_RIGHT; + break; + case SDL_BUTTON_X1: + button = inputs::MouseButton::M_X1; + break; + case SDL_BUTTON_X2: + button = inputs::MouseButton::M_X2; + break; + } + + bool buttonWasDown = m_mouse.buttons[static_cast(button)]; + bool buttonIsDown = (e.state == SDL_PRESSED); + m_mouse.buttons[static_cast(button)] = buttonIsDown; + if (buttonIsDown != buttonWasDown) { // (if button was pressed or released) + // only sets delta if it hasn't already been set this frame (to detect very fast presses) + if (m_mouse.deltas[static_cast(button)] == ButtonDelta::SAME) { + m_mouse.deltas[static_cast(button)] = buttonIsDown ? ButtonDelta::PRESSED : ButtonDelta::RELEASED; + } + } +} + +void Window::onMouseMotionEvent(SDL_MouseMotionEvent &e) +{ + m_mouse.x = e.x; + m_mouse.y = e.y; + m_mouse.dx = e.xrel; + m_mouse.dy = e.yrel; +} + +void Window::onMouseWheelEvent(SDL_MouseWheelEvent &e) +{ + if (e.direction == SDL_MOUSEWHEEL_NORMAL) { + m_mouse.xscroll = e.preciseX; + m_mouse.yscroll = e.preciseY; + } else { // flipped + m_mouse.xscroll = -e.preciseX; + m_mouse.yscroll = -e.preciseY; + } +} + +// public methods + +std::string Window::getTitle() const +{ + return m_title; +} + +void Window::makeContextCurrent() +{ + if (SDL_GL_MakeCurrent(m_handle, m_glContext) != 0) { + throw std::runtime_error("Failed to make GL context current"); + } +} + +void Window::swapBuffers() +{ +#ifndef SDLTEST_NOGFX + SDL_GL_SwapWindow(m_handle); +#endif + m_frames++; + uint64_t currentFrameStamp = getNanos(); + m_lastFrameTime = currentFrameStamp - m_lastFrameStamp; + m_lastFrameStamp = currentFrameStamp; +} + +void Window::getInputAndEvents() +{ + + resetInputDeltas(); + + // loop through all available events + SDL_Event e; + while (SDL_PollEvent(&e)) { + switch (e.type) { + + case SDL_QUIT: + setCloseFlag(); + break; + + case SDL_WINDOWEVENT: + onWindowEvent(e.window); + break; + + case SDL_KEYDOWN: // FALL THROUGH + case SDL_KEYUP: + onKeyEvent(e.key); + break; + + case SDL_MOUSEBUTTONDOWN: // FALL THROUGH + case SDL_MOUSEBUTTONUP: + onMouseButtonEvent(e.button); + break; + + case SDL_MOUSEMOTION: + onMouseMotionEvent(e.motion); + break; + + case SDL_MOUSEWHEEL: + onMouseWheelEvent(e.wheel); + break; + + } + } + +} + +void Window::setVSync(bool enable) +{ + if (SDL_GL_SetSwapInterval(enable ? 1 : 0) != 0) { + throw std::runtime_error("Failed to set swap interval"); + } +} + +bool Window::getVSync() const +{ + return SDL_GL_GetSwapInterval() == 0 ? false : true; +} + +glm::ivec2 Window::getViewportSize() +{ + return m_fbSize; +} + +void Window::setTitle(std::string title) +{ + SDL_SetWindowTitle(m_handle, title.c_str()); +} + +bool Window::getWindowResized() const +{ + return m_justResized; +} + +void Window::show() +{ + SDL_ShowWindow(m_handle); +} + +void Window::hide() +{ + SDL_HideWindow(m_handle); +} + +void Window::focus() +{ + SDL_RaiseWindow(m_handle); + m_keyboardFocus = true; +} + +bool Window::hasFocus() const +{ + return m_keyboardFocus; +} + +void Window::setCloseFlag() +{ + m_shouldClose = true; +} + +bool Window::isRunning() const +{ + return !m_shouldClose; +} + +void Window::setFullscreen(bool fullscreen, bool exclusive) +{ + if (SDL_SetWindowFullscreen(m_handle, fullscreen ? (exclusive ? SDL_WINDOW_FULLSCREEN : SDL_WINDOW_FULLSCREEN_DESKTOP) : 0) != 0) { + throw std::runtime_error("Unable to set window to fullscreen/windowed"); + } + m_fullscreen = fullscreen; + if (fullscreen) { + int width, height; + SDL_GetWindowSize(m_handle, &width, &height); + onResize(width, height); + } +} + +void Window::toggleFullscreen() +{ + setFullscreen(!m_fullscreen); +} + +bool Window::isFullscreen() const +{ + return m_fullscreen; +} + +bool Window::setRelativeMouseMode(bool enabled) +{ + m_mouse.captured = enabled; + int code = SDL_SetRelativeMouseMode(static_cast(enabled)); + if (code != 0) { + throw std::runtime_error("Unable to set relative mouse mode"); + } else { + return true; + } +} + +bool Window::mouseCaptured() +{ + return m_mouse.captured; +} + +// getting input + +bool Window::getKey(inputs::Key key) const +{ + return m_keyboard.keys[static_cast(key)]; +} + +bool Window::getKeyPress(inputs::Key key) const +{ + return m_keyboard.deltas[static_cast(key)] == ButtonDelta::PRESSED; +} + +bool Window::getKeyRelease(inputs::Key key) const +{ + return m_keyboard.deltas[static_cast(key)] == ButtonDelta::RELEASED; +} + +// TODO mouse input + +bool Window::getButton(inputs::MouseButton button) const +{ + return m_mouse.buttons[static_cast(button)]; +} + +bool Window::getButtonPress(inputs::MouseButton button) const +{ + return m_mouse.deltas[static_cast(button)] == ButtonDelta::PRESSED; +} + +bool Window::getButtonRelease(inputs::MouseButton button) const +{ + return m_mouse.deltas[static_cast(button)] == ButtonDelta::RELEASED; +} + +int Window::getMouseX() const +{ + return static_cast(m_mouse.x); +} + +int Window::getMouseY() const +{ + return static_cast(m_mouse.y); +} + +float Window::getMouseNormX() const +{ + return ((float)m_mouse.x * 2.0f / (float)m_winSize.x) - 1.0f; +} + +float Window::getMouseNormY() const +{ + return ((float)m_mouse.y * -2.0f / (float)m_winSize.y) + 1.0f; +} + +int Window::getMouseDX() const +{ + return static_cast(m_mouse.dx); +} + +int Window::getMouseDY() const +{ + return static_cast(m_mouse.dy); +} + +float Window::getMouseScrollX() const +{ + return m_mouse.xscroll; +} + +float Window::getMouseScrollY() const +{ + return m_mouse.yscroll; +} + +// TODO game pad + +// get timer value +uint64_t Window::getNanos() const +{ + uint64_t count; + + count = SDL_GetPerformanceCounter(); + if (m_counterFreq == BILLION) { + return count; + } else { + return count * (BILLION / m_counterFreq); + } +} + +uint64_t Window::getLastFrameStamp() const +{ + return m_lastFrameStamp; +} + +uint64_t Window::getFrameCount() const +{ + return m_frames; +} + +uint64_t Window::getStartTime() const +{ + return m_startTime; +} + +float Window::dt() const +{ + return (float)m_lastFrameTime / (float)BILLION; +} + +uint64_t Window::getFPS() const +{ + if (m_lastFrameTime == 0) return 0; + return BILLION / m_lastFrameTime; +} + +uint64_t Window::getAvgFPS() const +{ + uint64_t delta_t = getNanos() - m_avgFpsStart; + if (delta_t == 0) return 0; + return BILLION * (m_frames - m_avgFpsStartCount) / delta_t; +} + +void Window::resetAvgFPS() +{ + m_avgFpsStart = getNanos(); + m_avgFpsStartCount = getFrameCount(); +} + +bool Window::infoBox(const std::string& title, const std::string& msg) +{ + if (isFullscreen() == false) { + SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_INFORMATION, title.c_str(), msg.c_str(), m_handle); + return true; + } else { + return false; + } +} + +/* STATIC METHODS */ + +// Display an error message box +void Window::errorBox(const std::string& message) +{ + SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Game Error", message.c_str(), NULL); +}