diff --git a/build.sh b/build.sh index 3f75f29674e9eb5bf125c48d4ac7b4f292f39241..9530b5d70bc35277b9659b6d2f95f93f9e674b4b 100755 --- a/build.sh +++ b/build.sh @@ -43,6 +43,8 @@ popd # additional stuff # +EXE="gdb $EXE" + # gamemoderun if [ -x "$(command -v gamemoderun)" ]; then EXE="gamemoderun $EXE" diff --git a/demo/data/shader2D_frag.glsl b/demo/data/shader2D_frag.glsl new file mode 100644 index 0000000000000000000000000000000000000000..6d4fafa8f51abb0ef8ee365a8598e70f12bcb876 --- /dev/null +++ b/demo/data/shader2D_frag.glsl @@ -0,0 +1,9 @@ +#version 330 core + +in vec3 colour; +out vec4 FragColor; + +void main() +{ + FragColor = vec4(colour, 1.0f); +} diff --git a/demo/data/shader2D_vert.glsl b/demo/data/shader2D_vert.glsl new file mode 100644 index 0000000000000000000000000000000000000000..223a72b07d65b8b5eae73a207bf661b0ebba5436 --- /dev/null +++ b/demo/data/shader2D_vert.glsl @@ -0,0 +1,13 @@ +#version 330 core +layout (location = 0) in vec2 aPos; +layout (location = 1) in vec3 aColour; + +uniform mat4 projection; + +out vec3 colour; + +void main() +{ + gl_Position = projection * vec4(aPos, 0.0f, 1.0f); + colour = aColour; +} diff --git a/demo/main.cpp b/demo/main.cpp index 281b687a6dfa8dcda8bf13c47b28ee7fe8ac5125..857a63d35247ae8dc672e17a088cba4462f9758b 100644 --- a/demo/main.cpp +++ b/demo/main.cpp @@ -5,6 +5,8 @@ #include <utility> #include <fggl/app.hpp> +#include <fggl/scenes/menu.hpp> + #include <fggl/gfx/window.hpp> #include <fggl/gfx/camera.hpp> #include <fggl/input/camera_input.h> @@ -139,10 +141,10 @@ private: }; -class GameScene : public fggl::scenes::Scene, public fggl::AppState { +class GameScene : public fggl::AppState { public: explicit GameScene(fggl::App& app) : fggl::AppState(app), m_world(nullptr), m_sceneTime(nullptr), m_inputs(nullptr) { }; - ~GameScene() override = default; + //~GameScene() override = default; void activate() override { auto& locator = fggl::util::ServiceLocator::instance(); @@ -158,7 +160,7 @@ public: setup(); } - void setup() override { + void setup() { auto types = m_world->types(); // create camera using strings @@ -269,11 +271,6 @@ public: } void deactivate() override { - cleanup(); - } - - void cleanup() override { - } void update() override { @@ -286,7 +283,7 @@ public: } } - void render() override { + void render(fggl::gfx::Paint& paint) override { debugInspector(); fggl::gfx::renderMeshes(glModule, *m_world, m_sceneTime->delta()); } @@ -379,19 +376,14 @@ int main(int argc, const char* argv[]) { // -- should not be our problem - this is a broken api auto window = windowing.createWindow("Demo Game"); + window->make_graphics<fggl::gfx::OpenGL4>(); window->fullscreen( true ); - // -- - - auto& graphics = app.use<fggl::gfx::ecsOpenGLModule>(window, storage); - - // -- should not be out problem - this is a broken api - fggl::gfx::loadPipeline(glModule, "unlit", false); - fggl::gfx::loadPipeline(glModule, "phong", false); - fggl::gfx::loadPipeline(glModule, "normals", false); - // -- + app.setWindow( std::move(window) ); // and now our states - app.add_state<MenuScene>("menu"); + auto menu = app.add_state<fggl::scenes::BasicMenu>("menu"); + + // game state app.add_state<GameScene>("game"); diff --git a/fggl/CMakeLists.txt b/fggl/CMakeLists.txt index dc239001cedd25ee7096dcbf5c2fce4f41b0872e..20a631910e3a52191e8db22e71278ee7f104f7a1 100644 --- a/fggl/CMakeLists.txt +++ b/fggl/CMakeLists.txt @@ -17,6 +17,7 @@ target_sources(${PROJECT_NAME} ecs3/fast/Container.cpp ecs3/prototype/world.cpp scenes/Scene.cpp + scenes/menu.cpp ecs3/module/module.cpp input/camera_input.cpp data/heightmap.cpp diff --git a/fggl/app.cpp b/fggl/app.cpp index 918c3d7814151f798eff232853e05f7f69fbc3ee..11124cd096e7a25a60bae8b9c99afdff4107eba7 100644 --- a/fggl/app.cpp +++ b/fggl/app.cpp @@ -2,6 +2,8 @@ #include <cstdlib> #include <memory> +#include <spdlog/spdlog.h> + #include <fggl/app.hpp> #include <fggl/ecs3/types.hpp> #include <fggl/ecs3/module/module.h> @@ -16,8 +18,7 @@ namespace fggl { m_running(true), m_types(std::make_unique<ecs3::TypeRegistry>()), m_modules(std::make_unique<ecs3::ModuleManager>(*m_types)), - m_states() { - } + m_states() {} int App::run(int argc, const char** argv) { @@ -29,9 +30,25 @@ namespace fggl { while ( m_running ) { auto& state = m_states.active(); - + state.update(); - state.render(); + + // window rendering to frame buffer + if ( m_window != nullptr ) { + m_modules->onFrameStart(); + m_window->frameStart(); + + // get draw instructions + fggl::gfx::Paint paint; + state.render(paint); + + // execute draw instructions + auto& graphics = m_window->graphics(); + graphics.draw2D( paint ); + + m_window->frameEnd(); + m_modules->onFrameEnd(); + } } { diff --git a/fggl/debug/debug.cpp b/fggl/debug/debug.cpp index 94b2571a41bf8bedc7495fc2e5f14f8a94f467e3..debd1e434eb5ef9baf2f5a70fbf7aea277f59d89 100644 --- a/fggl/debug/debug.cpp +++ b/fggl/debug/debug.cpp @@ -7,7 +7,7 @@ using fggl::gfx::Window; using fggl::debug::DebugUI; -DebugUI::DebugUI(std::shared_ptr<Window>& win) : m_visible(false) { +DebugUI::DebugUI(std::shared_ptr<fggl::gfx::GlfwWindow>& win) : m_visible(false) { IMGUI_CHECKVERSION(); ImGui::CreateContext(); diff --git a/fggl/ecs3/module/module.cpp b/fggl/ecs3/module/module.cpp index 9494c917d4ca1c77efc1d26f2bfe8205a932b50b..d1fd59c33a49a37fb135116b1ed5c47aab3b6d2c 100644 --- a/fggl/ecs3/module/module.cpp +++ b/fggl/ecs3/module/module.cpp @@ -3,3 +3,12 @@ // #include <fggl/ecs3/module/module.h> + +namespace fggl::ecs3 { + + void Module::onFrameStart() { + } + + void Module::onFrameEnd() { + } +} diff --git a/fggl/gfx/ogl/backend.cpp b/fggl/gfx/ogl/backend.cpp index 96a9c97988357c78d69cbf0dc7dffa4a6e67e440..99b06318b7ea7f2f730170bfa09bf6e5bbca9d1f 100644 --- a/fggl/gfx/ogl/backend.cpp +++ b/fggl/gfx/ogl/backend.cpp @@ -5,9 +5,9 @@ using namespace fggl::gfx; -GlGraphics::GlGraphics(const std::shared_ptr<Window> window) : m_window(window) { +GlGraphics::GlGraphics(const Window& window) { spdlog::debug("[OGL] attaching window context"); - window->activate(); + window.activate(); GLenum err = glewInit(); if ( GLEW_OK != err ) { throw std::runtime_error("couldn't init glew"); @@ -22,8 +22,6 @@ GlGraphics::~GlGraphics() { } void GlGraphics::clear() { - m_window->activate(); - glClearColor(0.0f, 0.0f, 0.0f, 0.0f); glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT ); } diff --git a/fggl/gfx/ogl/renderer.cpp b/fggl/gfx/ogl/renderer.cpp index f35a1a87a061fdb960769f23ed51b6fb98e325a4..b063dd395dc327a7f13d4f80bc16752cd0bd672e 100644 --- a/fggl/gfx/ogl/renderer.cpp +++ b/fggl/gfx/ogl/renderer.cpp @@ -2,12 +2,19 @@ #include <spdlog/spdlog.h> #include <fggl/gfx/ogl/renderer.hpp> +#include <fggl/gfx/paint.hpp> + +#include <fggl/math/triangulation.hpp> #include <fggl/data/model.hpp> +#include <fggl/util/service.h> #include <fggl/gfx/camera.hpp> + +#include <glm/ext/matrix_clip_space.hpp> #include <glm/ext/matrix_transform.hpp> #include <glm/glm.hpp> #include <glm/gtc/type_ptr.hpp> +#include <memory> /** @@ -22,9 +29,126 @@ * Add support for models with weights (for animations) * OpenGL ES for the FOSS thinkpad users who can't run anything even remotely modern */ - namespace fggl::gfx { + using data::Vertex2D; + using data::Mesh2D; + + GlRenderToken setupVertex2D() { + GlRenderToken token{}; + glGenVertexArrays(1, &token.vao); + glBindVertexArray(token.vao); + + glGenBuffers(2, token.buffs); + + glBindBuffer(GL_ARRAY_BUFFER, token.buffs[0]); + glEnableVertexAttribArray(0); + glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex2D), reinterpret_cast<void*>(offsetof(Vertex2D, position))); + glEnableVertexAttribArray(1); + glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex2D), reinterpret_cast<void*>(offsetof(Vertex2D, colour))); + + glBindVertexArray(0); + return token; + } + + OpenGL4Backend::OpenGL4Backend() : fggl::gfx::Graphics() { + GLenum err = glewInit(); + if ( GLEW_OK != err ) { + throw std::runtime_error("couldn't init glew"); + } + glViewport(0, 0, 2560, 1440); + + m_token2D = setupVertex2D(); + + auto& locator = util::ServiceLocator::instance(); + + auto storage = locator.get<data::Storage>(); + m_cache = std::make_unique<ShaderCache>(storage); + + ShaderConfig shader2DConfig = ShaderFromName( "shader2D" ); + m_cache->load( shader2DConfig ); + }; + + void OpenGL4Backend::clear() { + glClearColor(0.0f, 0.0f, 0.0f, 0.0f); + glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT ); + } + + static void generateMesh( const gfx::Paint& paint, Mesh2D& mesh ) { + for ( auto& cmd : paint.cmds() ) { + + auto path = cmd.path; + + std::vector<data::Vertex2D> verts; + math::vec3 colour{1.0f, 1.0f, 1.0f}; + auto idx = 0; + auto colourIdx = 0; + + for ( auto& type : path.m_types ) { + if ( type == PathType::PATH ) { + verts.push_back({ + .position = path.m_points[ idx++ ], + .colour = colour + }); + } else if ( type == PathType::MOVE ) { + // polygon finished + if ( verts.size() < 3 ) { + // empty, point, or line + } else if ( verts.size() == 3 ) { + // triangle + } else { + // polygon + math::fanTriangulation( verts, mesh ); + } + + verts.clear(); + verts.push_back({ + .position = path.m_points[ idx++ ], + .colour = colour + }); + } else if ( type == PathType::COLOUR ) { + colour = path.m_colours[ colourIdx++ ]; + } else { + // unsupported type + } + } + + if ( !verts.empty() ) { + math::fanTriangulation( verts, mesh ); + } + + } + } + + void OpenGL4Backend::draw2D( const gfx::Paint& paint ) { + + // generate the mesh from a paint command list + data::Mesh2D mesh; + generateMesh( paint, mesh ); + + // render the resulting mesh + auto shader2D = m_cache->get( "shader2D" ); + + glUseProgram( shader2D ); + + auto projMat = glm::ortho(0.0f, 1920.0f, 0.0f, 1080.f); + glUniformMatrix4fv( glGetUniformLocation(shader2D, "projection"), 1, GL_FALSE, glm::value_ptr( projMat ) ); + + glBindVertexArray( m_token2D.vao ); + + glBindBuffer( GL_ARRAY_BUFFER, m_token2D.buffs[0] ); + glBufferData( GL_ARRAY_BUFFER, mesh.vertexList.size() * sizeof(Vertex2D), mesh.vertexList.data(), GL_DYNAMIC_DRAW ); + + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_token2D.buffs[1]); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, mesh.indexList.size() * sizeof(uint32_t), mesh.indexList.data(), GL_DYNAMIC_DRAW); + + glDrawElements( GL_TRIANGLES, mesh.indexList.size(), GL_UNSIGNED_INT, (GLvoid*)0); + + glBindVertexArray( 0 ); + glUseProgram( 0 ); + //glDisable(GL_PRIMITIVE_RESTART); + } + template<typename T> static GLuint createArrayBuffer(std::vector<T>& vertexData) { GLuint buffId; diff --git a/fggl/gfx/window.cpp b/fggl/gfx/window.cpp index 6a45c5cc1c04cc2227c82766fa834cff060bd1ea..199ec90eeaeb5fdb8d1be7ca3238ba91c468e634 100644 --- a/fggl/gfx/window.cpp +++ b/fggl/gfx/window.cpp @@ -5,6 +5,7 @@ #include <string> #include <stdexcept> #include <utility> +#include <GLFW/glfw3.h> #include <spdlog/spdlog.h> using namespace fggl::gfx; @@ -64,13 +65,13 @@ static void framebuffer_resize(GLFWwindow* window, int width, int height) { glfwMakeContextCurrent( window ); glViewport(0, 0, width, height); - auto fgglWindow = reinterpret_cast<Window*>(glfwGetWindowUserPointer( window )); + auto fgglWindow = reinterpret_cast<GlfwWindow*>(glfwGetWindowUserPointer( window )); fgglWindow->framesize( width, height ); } static void fggl_input_cursor(GLFWwindow* window, double x, double y) { auto& input = GlfwInputManager::instance(); - auto fgglWin = (Window*)glfwGetWindowUserPointer(window); + auto fgglWin = (GlfwWindow*)glfwGetWindowUserPointer(window); #ifndef FGGL_INPUT_SCREEN_COORDS // convert to nice ranges... @@ -186,7 +187,7 @@ void GlfwContext::pollEvents() { fggl_joystick_poll(); } -Window::Window() : m_window(nullptr), m_framesize() { +GlfwWindow::GlfwWindow() : m_window(nullptr), m_framesize() { spdlog::debug("[glfw] creating window"); glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4); glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); @@ -223,19 +224,27 @@ Window::Window() : m_window(nullptr), m_framesize() { spdlog::debug("[glfw] window creation complete"); } -Window::~Window() { +GlfwWindow::~GlfwWindow() { if ( m_window != nullptr ) { glfwDestroyWindow(m_window); m_window = nullptr; } } -void Window::activate() const { - assert( m_window != nullptr ); - glfwMakeContextCurrent(m_window); +void GlfwWindow::frameStart() { + assert( m_window != nullptr ); + assert( m_graphics != nullptr ); + + glfwMakeContextCurrent(m_window); + m_graphics->clear(); } -void Window::swap() { - assert( m_window != nullptr ); +void GlfwWindow::frameEnd() { glfwSwapBuffers(m_window); } + +void GlfwWindow::activate() const { + assert( m_window != nullptr ); + glfwMakeContextCurrent(m_window); +} + diff --git a/fggl/scenes/menu.cpp b/fggl/scenes/menu.cpp new file mode 100644 index 0000000000000000000000000000000000000000..d99dc3c0fb3921e97ec390b5c6ef5d425d3be6d1 --- /dev/null +++ b/fggl/scenes/menu.cpp @@ -0,0 +1,199 @@ +#include <fggl/scenes/menu.hpp> + +namespace fggl::scenes { + + static void buttonBorder( gfx::Path2D& path, glm::vec2 pos, glm::vec2 size ) { + // outer box + path.colour( {1.0f, 0.0f, 0.0f} ); + path.pathTo( { pos.x + size.x, pos.y } ); + path.pathTo( { pos.x + size.x, pos.y + size.y } ); + path.pathTo( { pos.x, pos.y + size.y } ); + path.close(); + + // inner box + math::vec2 innerTop { pos.x + 5, pos.y + 5 }; + math::vec2 innerBottom { pos.x + size.x - 5, pos.y + size.y - 5 }; + + path.colour( {1.0f, 1.0f, 0.0f} ); + path.moveTo( { innerTop.x, innerTop.y } ); + path.pathTo( { innerBottom.x, innerTop.y } ); + path.pathTo( { innerBottom.x, innerBottom.y } ); + path.pathTo( { innerTop.x, innerBottom.y } ); + path.pathTo( { innerTop.x, innerTop.y } ); + } + + static void makeBox( gfx::Path2D& path, glm::vec2 topLeft, glm::vec2 bottomRight ) { + path.moveTo( { topLeft.x, topLeft.y } ); + path.pathTo( { bottomRight.x, topLeft.y } ); + path.pathTo( { bottomRight.x, bottomRight.y } ); + path.pathTo( { topLeft.x, bottomRight.y } ); + path.pathTo( { topLeft.x, topLeft.y } ); + } + + static void makeProgress( gfx::Path2D& path, glm::vec2 topLeft, glm::vec2 size, float value ) { + const auto bottomRight { topLeft + size }; + + // background + path.colour( {0.5f, 0.5f, 0.5f} ); + makeBox( path, topLeft, bottomRight ); + + // fill + math::vec2 innerTop { topLeft.x + 5, topLeft.y + 5 }; + math::vec2 innerBottom { bottomRight.x - 5, bottomRight.y - 5 }; + + // figure out how wide the bar should be + float barWidth = (innerBottom.x - innerTop.x) * value; + float trueBottom = innerBottom.x; + innerBottom.x = innerTop.x + barWidth; + + // draw the bar + path.colour( {0.8f, 0.0f, 0.0f} ); + makeBox( path, innerTop, innerBottom ); + + // part of the bar that's not filled in + math::vec2 emptyTop { innerBottom.x, innerTop.y }; + math::vec2 emptyBottom { trueBottom, innerBottom.y }; + path.colour( {0.4f, 0.0f, 0.0f} ); + makeBox( path, emptyTop, emptyBottom ); + + } + + static void makeSlider( gfx::Path2D& path, glm::vec2 topLeft, glm::vec2 size, float value ) { + + makeProgress( path, topLeft, size, value ); + + // dimensions + const auto bottomRight { topLeft + size }; + const math::vec2 innerTop { topLeft.x + 5, topLeft.y + 5 }; + const math::vec2 innerBottom { bottomRight.x - 5, bottomRight.y - 5 }; + + // selector bar + float trackWidth = innerBottom.x - innerTop.x; + float selectorValue = trackWidth * value; + float selectorWidth = 6; + + math::vec2 selectorTop { innerTop.x + selectorValue - ( selectorWidth/2), topLeft.y }; + math::vec2 selectorBottom { selectorTop.x + selectorWidth, bottomRight.y }; + path.colour( {1.0f, 1.0f, 1.0f} ); + makeBox( path, selectorTop, selectorBottom ); + } + + + + static void makeButton( gfx::Path2D& path, glm::vec2 pos, glm::vec2 size, bool active, bool pressed) { + // locations + math::vec2 outerTop { pos }; + math::vec2 outerBottom { pos + size }; + math::vec2 innerTop { pos.x + 5, pos.y + 5 }; + math::vec2 innerBottom { pos.x + size.x - 5, pos.y + size.y - 5 }; + + math::vec3 baseColour{ 0.5f, 0.5f, 0.5f }; + + if ( active ) { + baseColour *= 1.2f; + } + + if ( pressed ) { + baseColour *= 0.8f; + } + + math::vec3 lightColour{ baseColour * 1.2f }; + math::vec3 darkColour{ baseColour * 0.8f }; + if ( pressed ) { + // flip light and dark for selected buttons + auto tmp = darkColour; + darkColour = lightColour; + lightColour = tmp; + } + + // bottom side + path.colour( darkColour ); + path.moveTo( outerTop ); + path.pathTo( innerTop ); + path.pathTo( { innerBottom.x, innerTop.y } ); + path.pathTo( { outerBottom.x, outerTop.y } ); + path.pathTo( outerTop ); + + // left side + path.colour( darkColour ); + path.moveTo( outerTop ); + path.pathTo( innerTop ); + path.pathTo( { innerTop.x, innerBottom.y } ); + path.pathTo( { outerTop.x, outerBottom.y } ); + path.pathTo( outerTop ); + + // top side + path.colour( lightColour ); + path.moveTo( { outerTop.x, outerBottom.y} ); + path.pathTo( { innerTop.x, innerBottom.y} ); + path.pathTo( innerBottom ); + path.pathTo( outerBottom ); + path.pathTo( { outerTop.x, outerBottom.y} ); + + // right side + path.colour( lightColour ); + path.moveTo( outerBottom ); + path.pathTo( innerBottom ); + path.pathTo( { innerBottom.x, innerTop.y } ); + path.pathTo( { outerBottom.x, outerTop.y } ); + path.pathTo( outerBottom ); + + // inner box + path.colour( baseColour ); + makeBox( path, innerTop, innerBottom ); + } + + BasicMenu::BasicMenu(fggl::App& app) : AppState(app) { + + } + + void BasicMenu::update() { + + } + + void BasicMenu::render(gfx::Paint& paint) { + + + const math::vec2 btnSize{ 150.0f, 30.0f }; + const float spacing = 5; + + const float padX = 50.0f; + const float padY = 50.0f; + + math::vec2 pos { 1920.0f - ( padX + btnSize.x ), padY }; + for (int i=0; i<10; i++) { + gfx::Path2D btn( pos ); + makeButton( btn, pos, btnSize, i==2, i==1 ); + paint.fill( btn ); + pos.y += (btnSize.y + spacing); + } + + pos.x = padX; + pos.y = padY; + for ( int i = 0; i <= 10; i++ ) { + gfx::Path2D btn( pos ); + makeProgress( btn, pos, btnSize, i / 10.f ); + paint.fill( btn ); + + pos.y += (btnSize.y + spacing); + } + + for ( int i = 0; i <= 10; i++ ) { + gfx::Path2D btn( pos ); + makeSlider( btn, pos, btnSize, i / 10.0f ); + paint.fill( btn ); + + pos.y += (btnSize.y + spacing); + } + + } + + void BasicMenu::activate() { + + } + + void BasicMenu::deactivate() { + + } + +}; diff --git a/include/fggl/app.hpp b/include/fggl/app.hpp index 964cfe597e9e9676fada4008e64c9108635b9491..34145abd67605a4f0d933b406684d3f50c318711 100644 --- a/include/fggl/app.hpp +++ b/include/fggl/app.hpp @@ -30,6 +30,8 @@ #include <fggl/ecs3/types.hpp> #include <fggl/ecs3/module/module.h> +#include <fggl/gfx/window.hpp> +#include <fggl/gfx/paint.hpp> #include <fggl/util/states.hpp> namespace fggl { @@ -72,7 +74,7 @@ namespace fggl { * It is not safe to assume the render target will always be the same, as the scene may be * rendered in mutliple passes (eg, for VR requirements). */ - virtual void render() = 0; + virtual void render(gfx::Paint& paint) = 0; virtual void activate() {} virtual void deactivate() {} @@ -90,15 +92,19 @@ namespace fggl { App(const App& app) = delete; App& operator=(App other) = delete; + inline void setWindow( std::unique_ptr<gfx::Window>&& window ) { + m_window = std::move(window); + } + /** * Perform main game loop functions. */ int run(int argc, const char** argv); template<typename T> - void add_state(const Identifer& name) { + T& add_state(const Identifer& name) { static_assert( std::is_base_of<AppState,T>::value, "States must be AppStates"); - m_states.put<T>(name, *this); + return m_states.put<T>(name, *this); } template<typename T, typename... Args> @@ -129,6 +135,7 @@ namespace fggl { bool m_running; std::unique_ptr<ecs3::TypeRegistry> m_types; std::unique_ptr<ecs3::ModuleManager> m_modules; + std::unique_ptr<gfx::Window> m_window; AppMachine m_states; }; diff --git a/include/fggl/data/model.hpp b/include/fggl/data/model.hpp index 88ef649b973f461330f34d9922e8a48ca1cbd797..955231ac9a5c162f495f1bb893117c64ba787d3a 100644 --- a/include/fggl/data/model.hpp +++ b/include/fggl/data/model.hpp @@ -13,6 +13,26 @@ namespace fggl::data { math::vec3 colour; }; + struct Vertex2D{ + fggl::math::vec2 position; + fggl::math::vec3 colour; + }; + + struct Mesh2D { + std::vector<Vertex2D> vertexList; + std::vector<uint32_t> indexList; + + inline std::size_t add_vertex(const Vertex2D& vertex) { + vertexList.push_back( vertex ); + return vertexList.size() - 1; + } + + inline void add_index(uint32_t idx) { + indexList.push_back( idx ); + } + }; + + // comparison operators inline bool operator<(const Vertex& lhs, const Vertex& rhs) { diff --git a/include/fggl/debug/debug.h b/include/fggl/debug/debug.h index 0b75b81f3113026da7684aa7b1bc510635bd0296..24577331993b48de15464db4e874a6f811787e7e 100644 --- a/include/fggl/debug/debug.h +++ b/include/fggl/debug/debug.h @@ -18,7 +18,7 @@ namespace fggl::debug { class DebugUI { public: - explicit DebugUI(std::shared_ptr<gfx::Window>& window); + explicit DebugUI(std::shared_ptr<gfx::GlfwWindow>& window); ~DebugUI(); void frameStart(); diff --git a/include/fggl/ecs3/module/module.h b/include/fggl/ecs3/module/module.h index 255cea9c1a6c7084aceb97df3bf43db0d9af4e0f..b38d1e3441695e8d1b3b060e8dc8704a1594ad24 100644 --- a/include/fggl/ecs3/module/module.h +++ b/include/fggl/ecs3/module/module.h @@ -20,6 +20,9 @@ namespace fggl::ecs3 { [[nodiscard]] virtual std::string name() const = 0; virtual void onLoad(ModuleManager& manager, TypeRegistry& tr) {}; + + virtual void onFrameStart(); + virtual void onFrameEnd(); }; class ModuleManager { @@ -42,6 +45,18 @@ namespace fggl::ecs3 { m_types.callbackAdd( Component<C>::typeID(), cb); } + void onFrameStart() { + for ( auto& [id,ptr] : m_modules ) { + ptr->onFrameStart(); + } + } + + void onFrameEnd() { + for ( auto& [id,ptr] : m_modules ) { + ptr->onFrameEnd(); + } + } + private: TypeRegistry& m_types; std::map<std::string, std::shared_ptr<Module>> m_modules; diff --git a/include/fggl/gfx/compat.hpp b/include/fggl/gfx/compat.hpp index b7933526ed94f0a3f02c74853c989beda24a51df..d1fcfa04329cec66d5ae38547abf65f097944851 100644 --- a/include/fggl/gfx/compat.hpp +++ b/include/fggl/gfx/compat.hpp @@ -29,12 +29,16 @@ namespace fggl::gfx { } inline - std::shared_ptr<Window> createWindow(const std::string& title) { - auto window = std::make_shared<Window>(); + std::unique_ptr<GlfwWindow> createWindow(const std::string& title) { + auto window = std::make_unique<GlfwWindow>(); window->title(title); return window; } + void onFrameStart() override { + context.pollEvents(); + } + [[nodiscard]] std::string name() const override { return "gfx::glfw"; diff --git a/include/fggl/gfx/ogl/backend.hpp b/include/fggl/gfx/ogl/backend.hpp index 96b863e0d410f1505d0e87249fc0f4f0bf8bf45b..c30f2cbdfdbf47f9892a0da1d287fbc815a8afba 100644 --- a/include/fggl/gfx/ogl/backend.hpp +++ b/include/fggl/gfx/ogl/backend.hpp @@ -14,13 +14,10 @@ namespace fggl::gfx { class GlGraphics { public: - GlGraphics(const std::shared_ptr<Window> window); + GlGraphics(const Window& window); ~GlGraphics(); void clear(); - - private: - std::shared_ptr<Window> m_window; }; class Shader { diff --git a/include/fggl/gfx/ogl/compat.hpp b/include/fggl/gfx/ogl/compat.hpp index 19741fab830b0e92c6ff70d346b55996b49c3d58..fe09fd0e57c239e6ecd8ca877206aa86e65655a0 100644 --- a/include/fggl/gfx/ogl/compat.hpp +++ b/include/fggl/gfx/ogl/compat.hpp @@ -30,12 +30,12 @@ namespace fggl::gfx { // fake module support - allows us to still RAII // struct ecsOpenGLModule : ecs3::Module { - fggl::gfx::Graphics ogl; fggl::gfx::MeshRenderer renderer; fggl::gfx::ShaderCache cache; - ecsOpenGLModule(std::shared_ptr<Window> window, std::shared_ptr<fggl::data::Storage> storage) : - ogl(std::move(window)), renderer(), cache(std::move(storage)) { } + ecsOpenGLModule(Window& window, std::shared_ptr<fggl::data::Storage> storage) : + renderer(), + cache(std::move(storage)) { } std::string name() const override { return "gfx::opengl"; diff --git a/include/fggl/gfx/ogl/renderer.hpp b/include/fggl/gfx/ogl/renderer.hpp index fcbdb8d56c6125f14a72487bc1cb5ca0399358ca..aed7979f2bcff3a123b6a8a9387198bcd705c0c1 100644 --- a/include/fggl/gfx/ogl/renderer.hpp +++ b/include/fggl/gfx/ogl/renderer.hpp @@ -1,11 +1,17 @@ #ifndef FGGL_GFX_RENDERER_H #define FGGL_GFX_RENDERER_H +#include <memory> #include <vector> #include <fggl/data/model.hpp> #include <fggl/ecs3/ecs.hpp> + #include <fggl/gfx/ogl/backend.hpp> +#include <fggl/gfx/ogl/shader.hpp> + +#include <fggl/gfx/renderer.hpp> +#include <fggl/gfx/paint.hpp> namespace fggl::gfx { @@ -35,8 +41,23 @@ namespace fggl::gfx { float total; }; + class OpenGL4Backend : public Graphics { + public: + OpenGL4Backend(); + ~OpenGL4Backend() override = default; + + void clear() override; + + void draw2D(const Paint& paint) override; + + private: + GlRenderToken m_token2D; + std::unique_ptr<ShaderCache> m_cache; + }; + + using OpenGL4 = OpenGL4Backend; + // specialisation hooks - using Graphics = GlGraphics; using MeshRenderer = GlMeshRenderer; using MeshToken = GlRenderToken; diff --git a/include/fggl/gfx/ogl/shader.hpp b/include/fggl/gfx/ogl/shader.hpp index b7e0d48bb1a6b70e25323e3bb95aed323451362f..c74d89f7baf52fee5b52910c84d6cb763301766b 100644 --- a/include/fggl/gfx/ogl/shader.hpp +++ b/include/fggl/gfx/ogl/shader.hpp @@ -22,6 +22,14 @@ namespace fggl::gfx { bool hasGeom = false; }; + inline ShaderConfig ShaderFromName( const std::string& name ) { + return { + .name = name, + .vertex = name + "_vert.glsl", + .fragment = name + "_frag.glsl" + }; + } + struct BinaryCache { void* data = nullptr; GLsizei size = 0; diff --git a/include/fggl/gfx/paint.hpp b/include/fggl/gfx/paint.hpp new file mode 100644 index 0000000000000000000000000000000000000000..74ac95ede16bb7a50afaeba22885b55e28a5531c --- /dev/null +++ b/include/fggl/gfx/paint.hpp @@ -0,0 +1,88 @@ +#ifndef FGGL_GFX_PAINT_H +#define FGGL_GFX_PAINT_H + +#include <vector> +#include <fggl/math/types.hpp> + +namespace fggl::gfx { + + using RadianAngle = float; + + enum class PathType { + MOVE, + PATH, + BAZIER2, + BAZIER3, + COLOUR, + CLOSE + }; + + struct Path2D { + inline explicit Path2D( math::vec2 start ) : m_points(), m_types() { + moveTo( start ); + } + + inline void moveTo(math::vec2 pos) { + m_points.push_back( pos ); + m_types.push_back( PathType::MOVE ); + } + + inline void pathTo(math::vec2 pos) { + m_points.push_back( pos ); + m_types.push_back( PathType::PATH ); + } + + void bezierTo(math::vec2 cp1, math::vec2 pos); + void bezierTo(math::vec2 cp1, math::vec2 cp2, math::vec2 pos); + + void arc(math::vec2 center, float radius, RadianAngle startAngle, RadianAngle endAngle, bool ccw); + + void colour(math::vec3 colour) { + m_colours.push_back( colour ); + m_types.push_back( PathType::COLOUR ); + } + + void close() { + pathTo( m_points[0] ); + m_types.push_back( PathType::CLOSE ); + } + + std::vector< math::vec2 > m_points; + std::vector< PathType > m_types; + std::vector< math::vec3 > m_colours; + }; + + enum class PaintType { + FILL, + STROKE + }; + + struct PaintCmd { + PaintType type; + Path2D path; + }; + + class Paint { + public: + Paint() = default; + Paint(Paint& paint) = delete; + + inline void fill(Path2D& path) { + m_cmds.push_back( { PaintType::FILL, path } ); + } + + void stroke(Path2D& path) { + m_cmds.push_back( { PaintType::STROKE, path } ); + } + + const std::vector< PaintCmd >& cmds() const { + return m_cmds; + } + + private: + std::vector< PaintCmd > m_cmds; + }; + +} + +#endif diff --git a/include/fggl/gfx/renderer.hpp b/include/fggl/gfx/renderer.hpp new file mode 100644 index 0000000000000000000000000000000000000000..c112cd2f9316746ea52f128fc740db6724c6dfcc --- /dev/null +++ b/include/fggl/gfx/renderer.hpp @@ -0,0 +1,20 @@ +#include <functional> +#include <memory> + +namespace fggl::gfx { + + class RenderBackend { + public: + RenderBackend(); + virtual ~RenderBackend() = default; + + virtual void clear() = 0; + virtual void swap() = 0; + + }; + + using RenderBackendPtr = std::unique_ptr<RenderBackend>; + using RenderBackendFactory = std::function< fggl::gfx::RenderBackendPtr&&() >; + + +}; diff --git a/include/fggl/gfx/vector.hpp b/include/fggl/gfx/vector.hpp new file mode 100644 index 0000000000000000000000000000000000000000..979f9e2d85280923f545566c46885b6d0f3f554e --- /dev/null +++ b/include/fggl/gfx/vector.hpp @@ -0,0 +1,20 @@ +#ifndef FGGL_GFX_VECTOR_H +#define FGGL_GFX_VECTOR_H + +#include <fggl/math/types.hpp> +#include <vector> + +namespace fggl::gfx { + + struct Rectangle { + math::vec2 topLeft; + math::vec2 size; + }; + + struct Polygon { + std::vector<math::vec2> points; + }; + +} + +#endif diff --git a/include/fggl/gfx/vulkan/common.hpp b/include/fggl/gfx/vulkan/common.hpp new file mode 100644 index 0000000000000000000000000000000000000000..8e22017609247c6e748444bdff8849c0e1dfe6cf --- /dev/null +++ b/include/fggl/gfx/vulkan/common.hpp @@ -0,0 +1,6 @@ +#ifndef FGGL_GFX_VK_COMMON_H +#define FGGL_GFX_VK_COMMON_H + +#include <vulkan/vulkan.hpp> + +#endif diff --git a/include/fggl/gfx/vulkan/vulkan.hpp b/include/fggl/gfx/vulkan/vulkan.hpp new file mode 100644 index 0000000000000000000000000000000000000000..e32d6b3f612d306385fe11b0362d119d9e295d00 --- /dev/null +++ b/include/fggl/gfx/vulkan/vulkan.hpp @@ -0,0 +1,35 @@ +#ifndef FGGL_GFX_VK_CORE_H +#define FGGL_GFX_VK_CORE_H + +#include <memory> + +#include <fggl/app.hpp> + +#include <vulkan/vulkan.hpp> +#include <vulkan/vulkan_raii.hpp> + +namespace fggl::gfx::vkgfx { + + constexpr char const* EngineName = "FGGL"; + + class VulkanGraphics { + + private: + vk::raii::Instance m_instance; + std::unique_ptr<vk::raii::SwapchainKHR> m_swapchain; + }; + + class VulkanContext { + public: + VulkanContext(); + + VulkanGraphics& createGraphics(); + + private: + vk::raii::Context m_context; + }; + + +} + +#endif diff --git a/include/fggl/gfx/window.hpp b/include/fggl/gfx/window.hpp index f81224656eea180f34325ebe955e3bc7285e7d0e..fbb7900896d58d2fd75269f9f6296a9f62fbddf2 100644 --- a/include/fggl/gfx/window.hpp +++ b/include/fggl/gfx/window.hpp @@ -9,6 +9,7 @@ #include <fggl/gfx/common.hpp> #include <fggl/math/types.hpp> #include <fggl/gfx/input.hpp> +#include <fggl/gfx/windowing.hpp> namespace fggl::gfx { @@ -39,15 +40,18 @@ namespace fggl::gfx { GlDebugContext = GLFW_OPENGL_DEBUG_CONTEXT, NoError = GLFW_CONTEXT_NO_ERROR }; - class Window { + class GlfwWindow : public Window { public: - Window(); - ~Window(); - Window(Window&) = delete; + GlfwWindow(); + ~GlfwWindow(); + GlfwWindow(Window&) = delete; + GlfwWindow(Window&&) = delete; // window <-> opengl stuff - void activate() const; - void swap(); + void activate() const override; + + void frameStart() override; + void frameEnd() override; inline float width() const { return m_framesize.x; diff --git a/include/fggl/gfx/windowing.hpp b/include/fggl/gfx/windowing.hpp index 71d9f0e28b5c8cd9d738bed50c3249e3d4f5f814..9f912a07a82a600e8adcde8b52eec59b5fae642b 100644 --- a/include/fggl/gfx/windowing.hpp +++ b/include/fggl/gfx/windowing.hpp @@ -3,24 +3,37 @@ #define FGGL_GFX_WINDOWING_H #include <memory> +#include <fggl/gfx/paint.hpp> namespace fggl::gfx { class Graphics { public: - virtual void clear(); + virtual ~Graphics() = default; + virtual void clear() = 0; + + virtual void draw2D(const Paint& paint) = 0; }; class Window { public: - Window(); + virtual ~Window() = default; + virtual void activate() const = 0; template<typename T> void make_graphics() { + activate(); m_graphics = std::make_unique<T>(); } - private: + virtual void frameStart() = 0; + virtual void frameEnd() = 0; + + Graphics& graphics() { + return *m_graphics; + } + + protected: std::unique_ptr<Graphics> m_graphics; }; diff --git a/include/fggl/math/easing.hpp b/include/fggl/math/easing.hpp index 81e484feb22752dd4832af944c18e411ab634e80..db53218384da48e18b8eeedffd6b62d4d6de9992 100644 --- a/include/fggl/math/easing.hpp +++ b/include/fggl/math/easing.hpp @@ -2,8 +2,8 @@ // Created by webpigeon on 12/12/2021. // -#ifndef FGGL_UTILS_HPP -#define FGGL_UTILS_HPP +#ifndef FGGL_MATH_EASING_H +#define FGGL_MATH_EASING_H #include <fggl/math/types.hpp> diff --git a/include/fggl/math/triangulation.hpp b/include/fggl/math/triangulation.hpp new file mode 100644 index 0000000000000000000000000000000000000000..9861a1de15ab06f1354a67b4996ea3d0a2a9afb6 --- /dev/null +++ b/include/fggl/math/triangulation.hpp @@ -0,0 +1,164 @@ +#ifndef FGGL_MATH_TRIS_H +#define FGGL_MATH_TRIS_H + +#include <fggl/math/types.hpp> +#include <fggl/data/model.hpp> + +namespace fggl::math { + + using Polygon = std::vector<math::vec2>; + using PolygonVertex = std::vector<data::Vertex2D>; + + constexpr int POSITIVE = 1; + constexpr int NEGATIVE = -1; + constexpr int UNSET = 0; + + /** + * Put an angle in the range [-PI, PI]. + */ + inline float clampAngle( float radianAngle ) { + if ( radianAngle <= M_PI ) { + return radianAngle + M_PI_2; + } else if ( radianAngle > M_PI ) { + return radianAngle - M_PI_2; + } else { + return radianAngle; + } + } + + static void checkSign( float value, int& sign, int& firstSign, int& flips ) { + if ( value > 0 ) { + if ( sign == UNSET ) { + firstSign = POSITIVE; + } else if ( sign < 0 ) { + flips++; + } + } else if ( value < 0 ) { + if ( sign == UNSET ) { + firstSign = NEGATIVE; + } else if ( sign > 0 ) { + flips++; + } + sign = NEGATIVE; + } + } + + /** + * Check if a polygon is convex. + * + * see https://math.stackexchange.com/a/1745427 + */ + bool isConvex(const Polygon& polygon) { + if ( polygon.size() < 3 ) { + return false; + } + + const auto n = polygon.size(); + + auto wSign = UNSET; + + auto xSign = UNSET; + auto xFirstSign = UNSET; + auto xFlips = 0; + + auto ySign = UNSET; + auto yFirstSign = UNSET; + auto yFlips = 0; + + auto curr = polygon[ n - 1 ]; + auto next = polygon[ n ]; + + for ( auto& v : polygon ) { + auto prev = curr; + curr = next; + next = v; + + auto before = curr - prev; + auto after = next - curr; + + checkSign( after.x, xSign, xFirstSign, xFlips ); + if ( xFlips > 2 ) { + return false; + } + + checkSign( after.y, ySign, yFirstSign, yFlips ); + if ( yFlips > 2 ) { + return false; + } + + auto w = before.x * after.y - after.x * before.y; + if ( wSign == UNSET && w != 0 ) { + wSign = w; + } else if ( wSign > 0 && w < 0 ) { + return false; + } else if ( wSign < 0 && w > 0 ){ + return false; + } + } + + if ( xSign != UNSET && ( xFirstSign != UNSET ) && ( xSign != xFirstSign ) ) { + xFlips += 1; + } + if ( ySign != UNSET && ( yFirstSign != UNSET ) && ( ySign != yFirstSign ) ) { + yFlips += 1; + } + if ( xFlips != 2 || yFlips != 2 ) { + return false; + } + + return true; + } + + static data::Vertex2D pointToVertex( const math::vec2& point ) { + return data::Vertex2D{ + .position = point, + .colour = {1.0f, 1.0f, 1.0f} + }; + } + + /** + * Fast Triangulation for convex polygons. + */ + void fanTriangulation(const Polygon& polygon, data::Mesh2D& mesh) { + assert(polygon.size() >= 3); + + // add the first two points to the mesh + auto firstIdx = mesh.add_vertex( pointToVertex(polygon[0]) ); + auto prevIdx = mesh.add_vertex( pointToVertex(polygon[1]) ); + + // deal with the indicies + const auto nTris = polygon.size() - 2; + for ( auto i=0; i < nTris; i++ ) { + mesh.add_index( firstIdx ); + mesh.add_index( prevIdx ); + + auto currIdx = mesh.add_vertex( pointToVertex(polygon[i + 2]) ); + mesh.add_index( currIdx ); + prevIdx = currIdx; + } + } + + void fanTriangulation(const PolygonVertex polygon, data::Mesh2D& mesh) { + assert(polygon.size() >= 3); + + // add the first two points to the mesh + auto firstIdx = mesh.add_vertex( polygon[0] ); + auto prevIdx = mesh.add_vertex( polygon[1] ); + + // deal with the indicies + const auto nTris = polygon.size() - 2; + for ( auto i=0; i < nTris; i++ ) { + mesh.add_index( firstIdx ); + mesh.add_index( prevIdx ); + + auto currIdx = mesh.add_vertex( polygon[i + 2] ); + mesh.add_index( currIdx ); + prevIdx = currIdx; + } + } + + +} // namespace fggl::util + +#endif + diff --git a/include/fggl/scenes/menu.hpp b/include/fggl/scenes/menu.hpp new file mode 100644 index 0000000000000000000000000000000000000000..c36040195c0d8229a748c515095daf4dfba8d226 --- /dev/null +++ b/include/fggl/scenes/menu.hpp @@ -0,0 +1,25 @@ +#ifndef FGGL_SCENES_MENU_H +#define FGGL_SCENES_MENU_H + +#include <fggl/app.hpp> +#include <fggl/input/input.hpp> + +namespace fggl::scenes { + + class BasicMenu : public AppState { + public: + BasicMenu(App& owner); + + void update() override; + void render(gfx::Paint& paint) override; + + void activate() override; + void deactivate() override; + + private: + input::Input* m_inputs; + }; + +} // namepace fggl::scenes + +#endif diff --git a/include/fggl/util/service.h b/include/fggl/util/service.h index 9d1c26108c224409a204bbbed41d2462d02629ae..4fe8bd83528897b6b8937979aec41249c7e56d11 100644 --- a/include/fggl/util/service.h +++ b/include/fggl/util/service.h @@ -34,6 +34,12 @@ namespace fggl::util { m_services[info] = ptr; } + template<typename T> + std::shared_ptr<T> get() { + auto info = std::type_index(typeid(T)); + return std::static_pointer_cast<T>(m_services.at(info)); + } + template<typename T> std::shared_ptr<T> providePtr() { auto info = std::type_index(typeid(T)); diff --git a/include/fggl/util/states.hpp b/include/fggl/util/states.hpp index 66ca6d3343d4ecc09272e9873abe89020cfb1b2b..a72fe314a483b4512046f67514f3db7dd4e1a736 100644 --- a/include/fggl/util/states.hpp +++ b/include/fggl/util/states.hpp @@ -42,7 +42,7 @@ namespace fggl::util { StateMachine& operator=(StateMachine other) = delete; template<typename T, typename... Args> - void put(const Identifer& name, Args&&... args) { + T& put(const Identifer& name, Args&&... args) { static_assert( std::is_base_of<StateType,T>::value, "States must be AppStates"); m_states[name] = std::make_unique<T>( std::forward<Args...>(args...) ); @@ -50,6 +50,8 @@ namespace fggl::util { if ( m_active.empty() ) { m_active = name; } + + return *(T*)(m_states[name].get()); } void change(const Identifer& name) {