#include <filesystem> #include <glm/geometric.hpp> #include <glm/trigonometric.hpp> #include <iostream> #include <fggl/gfx/window.hpp> #include <fggl/gfx/ogl.hpp> #include <fggl/gfx/renderer.hpp> #include <fggl/gfx/shader.hpp> #include <fggl/gfx/camera.hpp> #include <fggl/data/procedural.hpp> #include <fggl/ecs/ecs.hpp> #include <fggl/debug/debug.h> #include <fggl/data/storage.hpp> #include <imgui.h> constexpr bool showNormals = false; template <typename T> int sgn(T val) { return (T(0) < val) - (val < T(0)); } // prototype of resource discovery void discover(std::filesystem::path base) { std::vector< std::filesystem::path > contentPacks; for ( auto& item : std::filesystem::directory_iterator(base) ) { // content pack detection if ( std::filesystem::is_directory( item ) ) { auto manifest = item.path() / "manifest.yml"; if ( std::filesystem::exists( manifest ) ) { contentPacks.push_back( item.path() ); } } } // what did we find? std::cerr << "found pack(s): " << std::endl; for ( auto& pack : contentPacks ) { std::cerr << pack << std::endl; } } //TODO proper input system void process_camera(fggl::gfx::Window& window, fggl::ecs::ECS& ecs, fggl::gfx::Input& input, fggl::ecs::entity_t cam) { auto camTransform = ecs.getComponent<fggl::math::Transform>(cam); auto camComp = ecs.getComponent<fggl::gfx::Camera>(cam); float moveSpeed = 1.0f; glm::vec3 dir = ( camTransform->origin() - camComp->target ); glm::vec3 forward = glm::normalize( dir ); glm::vec3 motion(0.0f); if ( glfwGetKey(window.handle(), GLFW_KEY_W) == GLFW_PRESS ) { if ( glm::length( dir ) > 2.5f ) { motion -= (forward * moveSpeed); } } if ( glfwGetKey(window.handle(), GLFW_KEY_S) == GLFW_PRESS ) { if ( glm::length( dir ) < 25.0f ) { motion += (forward * moveSpeed); } } // scroll wheel float delta = (float)input.scrollDeltaY(); if ( (glm::length( dir ) < 25.0f && delta < 0.0f) || (glm::length( dir ) > 2.5f && delta > 0.0f) ) motion -= (forward * delta); camTransform->origin( camTransform->origin() + motion ); } void process_arcball(fggl::gfx::Window& window, fggl::ecs::ECS& ecs, fggl::gfx::Input& input, fggl::ecs::entity_t cam) { // see https://asliceofrendering.com/camera/2019/11/30/ArcballCamera/ auto camTransform = ecs.getComponent<fggl::math::Transform>(cam); auto camComp = ecs.getComponent<fggl::gfx::Camera>(cam); glm::vec4 position(camTransform->origin(), 1.0f); glm::vec4 pivot(camComp->target, 1.0f); glm::mat4 view = glm::lookAt( camTransform->origin(), camComp->target, camTransform->up() ); glm::vec3 viewDir = -glm::transpose(view)[2]; glm::vec3 rightDir = glm::transpose(view)[0]; float deltaAngleX = ( 2 * M_PI / window.width() ); float deltaAngleY = ( M_PI / window.height() ); float xAngle = ( input.cursorDeltaX() ) * deltaAngleX; float yAngle = ( input.cursorDeltaY() ) * deltaAngleY; auto cosAngle = glm::dot( viewDir, fggl::math::UP ); if ( cosAngle * sgn(deltaAngleY) > 0.99f ) { deltaAngleY = 0; } // rotate the camera around the pivot on the first axis glm::mat4x4 rotationMatrixX(1.0f); rotationMatrixX = glm::rotate( rotationMatrixX, xAngle, fggl::math::UP ); position = ( rotationMatrixX * ( position - pivot ) ) + pivot; // rotate the camera aroud the pivot on the second axis glm::mat4x4 rotationMatrixY(1.0f); rotationMatrixY = glm::rotate(rotationMatrixY, yAngle, rightDir ); glm::vec3 finalPos = ( rotationMatrixY * ( position - pivot ) ) + pivot; camTransform->origin( finalPos ); } int main(int argc, char* argv[]) { fggl::gfx::Context ctx; // build our main window fggl::gfx::Window win( fggl::gfx::Input::instance() ); win.title("FGGL Demo"); win.fullscreen( true ); // opengl time fggl::gfx::Graphics ogl(win); fggl::gfx::MeshRenderer meshRenderer; // debug layer fggl::debug::DebugUI debug(win); debug.visible(true); // storage API fggl::data::Storage storage; discover( storage.resolvePath(fggl::data::Data, "res") ); fggl::gfx::ShaderCache cache(storage); fggl::gfx::ShaderConfig config; config.name = "unlit"; config.vertex = "unlit_vert.glsl"; config.fragment = "unlit_frag.glsl"; auto shader = cache.load(config); fggl::gfx::ShaderConfig configPhong; configPhong.name = "phong"; configPhong.vertex = configPhong.name + "_vert.glsl"; configPhong.fragment = configPhong.name + "_frag.glsl"; // configPhong.fragment = configPhong.name + "_normals_frag.glsl"; auto shaderPhong = cache.load(configPhong); fggl::gfx::ShaderConfig configNormals; configNormals.name = "normals"; configNormals.hasGeom = true; configNormals.vertex = configNormals.name + "_vert.glsl"; configNormals.geometry = configNormals.name + "_geom.glsl"; configNormals.fragment = configNormals.name + "_frag.glsl"; auto shaderNormals = cache.load( configNormals ); // create ECS fggl::ecs::ECS ecs; ecs.registerComponent<fggl::gfx::MeshToken>(); ecs.registerComponent<fggl::gfx::Camera>(); ecs.registerComponent<fggl::math::Transform>(); // make camera auto camEnt = ecs.createEntity(); { auto cameraTf = ecs.addComponent<fggl::math::Transform>(camEnt); cameraTf->origin( glm::vec3(0.0f, 3.0f, 3.0f) ); ecs.addComponent<fggl::gfx::Camera>(camEnt); } int nCubes = 3; int nSections = 2; constexpr float HALF_PI = M_PI / 2.0f; for ( int i=0; i<nCubes; i++ ) { auto entity = ecs.createEntity(); // set the position auto result = ecs.addComponent<fggl::math::Transform>(entity); result->origin( glm::vec3( i * 5.0f, 0.0f, 0.0f) ); fggl::data::Mesh mesh; for (int i=-(nSections/2); i<=nSections/2; i++) { const auto shapeOffset = glm::vec3( 0.0f, 0.0f, i * 1.0f ); const auto cubeMat = glm::translate( fggl::math::mat4( 1.0f ) , shapeOffset ); const auto leftSlope = fggl::math::modelMatrix( glm::vec3(-1.0f, 0.0f, 0.0f) + shapeOffset, glm::vec3( 0.0f, -HALF_PI, 0.0f) ); const auto rightSlope = fggl::math::modelMatrix( glm::vec3( 1.0f, 0.0f, 0.0f) + shapeOffset, glm::vec3( 0.0f, HALF_PI, 0.0f) ); fggl::data::make_cube( mesh, cubeMat ); fggl::data::make_slope( mesh, leftSlope ); fggl::data::make_slope( mesh, rightSlope ); } mesh.removeDups(); auto token = meshRenderer.upload( mesh ); token.pipeline = shaderPhong; ecs.addComponent<fggl::gfx::MeshToken>(entity, token); } fggl::gfx::Input& input = fggl::gfx::Input::instance(); bool joystickWindow = true; bool gamepadWindow = true; float time = 0.0f; float dt = 16.0f; while( !win.closeRequested() ) { input.frame(); ctx.pollEvents(); debug.frameStart(); // update step time += dt; process_camera(win, ecs, input, camEnt); if ( input.mouseDown( fggl::gfx::MOUSE_2 ) ) { process_arcball(win, ecs, input, camEnt); } // imgui joystick debug ImGui::Begin("Joysticks", &joystickWindow); for ( int i=0; i<16; i++ ) { bool present = input.hasJoystick(i); std::string title = "Joystick " + std::to_string(i); if ( ImGui::TreeNode(title.c_str()) ) { ImGui::Text("present: %s", present ? "yes" : "no" ); if ( present ) { const fggl::gfx::Joystick& joyData = input.joystick(i); ImGui::Text( "%s", joyData.name ); ImGui::Text( "gamepad: %s", joyData.gamepad ? "yes" : "no" ); ImGui::Text( "axes: %d, buttons: %d, hats: %d", joyData.axisCount, joyData.buttonCount, joyData.hatCount); if (ImGui::TreeNode("axis##2")) { // dump data for ( int axid = 0; axid < joyData.axisCount; axid++ ) { ImGui::Text("axis %d, value: %f", axid, joyData.axes[axid] ); } ImGui::TreePop(); } if (ImGui::TreeNode("buttons##2")) { // dump data for ( int btnid = 0; btnid < joyData.buttonCount; btnid++ ) { ImGui::Text("button %d, value: %s", btnid, joyData.buttons[btnid] == GLFW_PRESS ? "down" : "up" ); } ImGui::TreePop(); } if (ImGui::TreeNode("hats##2")) { // dump data for ( int btnid = 0; btnid < joyData.hatCount; btnid++ ) { ImGui::Text("button %d, value: %d", btnid, joyData.hats[btnid] ); } ImGui::TreePop(); } } ImGui::TreePop(); ImGui::Separator(); } } ImGui::End(); // imgui gamepad debug ImGui::Begin("GamePad", &gamepadWindow); for ( int i=0; i<16; i++ ) { std::string title = "GamePad " + std::to_string(i); bool present = input.hasJoystick(i); if ( ImGui::TreeNode(title.c_str()) ) { ImGui::Text("present: %s", present ? "yes" : "no" ); if ( present ) { if ( ImGui::TreeNode("buttons##2") ) { for ( auto& btn : fggl::gfx::PadButtons ) { auto label = fggl::gfx::PadButtonLabels[ (int) btn ]; ImGui::Text( "%s: %i %i %i", label.c_str(), input.padDown(i, btn), input.padPressed(i, btn), input.padReleased(i, btn) ); } ImGui::TreePop(); } if ( ImGui::TreeNode("axes##2") ) { for ( auto& axis : fggl::gfx::PadAxes ) { auto label = fggl::gfx::PadAxisLabels[ (int) axis ]; ImGui::Text("%s: %f %f", label.c_str(), input.padAxis(i, axis), input.padAxisDelta(i, axis) ); } ImGui::TreePop(); } } ImGui::TreePop(); ImGui::Separator(); } } ImGui::End(); /* float amount = glm::radians( time / 2048.0f * 360.0f ); auto spinners = ecs.getEntityWith<fggl::math::Transform>(); for ( auto entity : spinners ) { auto transform = ecs.getComponent<fggl::math::Transform>(entity); transform->euler(glm::vec3(0.0f, amount, 0.0f)); }*/ // render step ogl.clear(); // render using real shader auto renderables = ecs.getEntityWith<fggl::gfx::MeshToken>(); for ( auto renderable : renderables ) { auto token = ecs.getComponent<fggl::gfx::MeshToken>(renderable); token->pipeline = shaderPhong; } meshRenderer.render(win, ecs, camEnt, 16.0f); // render using normals shader if ( showNormals ) { for ( auto renderable : renderables ) { auto token = ecs.getComponent<fggl::gfx::MeshToken>(renderable); token->pipeline = shaderNormals; } meshRenderer.render(win, ecs, camEnt, 16.0f); } debug.draw(); win.swap(); } return 0; }