#include <filesystem> #include <glm/ext/matrix_transform.hpp> #include <glm/geometric.hpp> #include <glm/trigonometric.hpp> #include <iostream> #include <fggl/gfx/window.hpp> #include <fggl/gfx/camera.hpp> #include <fggl/gfx/compat.hpp> #include <fggl/gfx/ogl/compat.hpp> #include <fggl/data/procedural.hpp> #include <fggl/ecs/ecs.hpp> #include <fggl/debug/debug.h> #include <fggl/data/storage.hpp> #include <fggl/util/chrono.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; } } enum camera_type { cam_free, cam_arcball }; camera_type cam_mode = cam_free; //TODO proper input system void process_camera(fggl::gfx::Window& window, fggl::ecs::ECS& ecs, fggl::gfx::Input& input, fggl::ecs::entity_t cam) { if ( glfwGetKey(window.handle(), GLFW_KEY_F2) == GLFW_PRESS ) { cam_mode = cam_free; } if ( glfwGetKey(window.handle(), GLFW_KEY_F3) == GLFW_PRESS ) { cam_mode = cam_arcball; } auto camTransform = ecs.getComponent<fggl::math::Transform>(cam); auto camComp = ecs.getComponent<fggl::gfx::Camera>(cam); const glm::vec3 dir = ( camTransform->origin() - camComp->target ); const glm::vec3 forward = glm::normalize( dir ); // scroll wheel glm::vec3 motion(0.0f); 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 ); } constexpr float ROT_SPEED = 0.05f; constexpr float PAN_SPEED = 0.05f; constexpr glm::mat4 MAT_IDENTITY(1.0f); void process_freecam(fggl::gfx::Window& window, fggl::ecs::ECS& ecs, fggl::gfx::Input& input, fggl::ecs::entity_t cam) { float rotationValue = 0.0f; glm::vec3 translation(0.0f); // calulate rotation (user input) if ( glfwGetKey(window.handle(), GLFW_KEY_Q) == GLFW_PRESS ) { rotationValue = ROT_SPEED; } else if ( glfwGetKey(window.handle(), GLFW_KEY_E) == GLFW_PRESS ) { rotationValue = -ROT_SPEED; } // calulate movement (user input) if ( glfwGetKey(window.handle(), GLFW_KEY_W) == GLFW_PRESS ) { translation -= fggl::math::RIGHT; } if ( glfwGetKey(window.handle(), GLFW_KEY_S) == GLFW_PRESS ) { translation += fggl::math::RIGHT; } if ( glfwGetKey(window.handle(), GLFW_KEY_D) == GLFW_PRESS ) { translation += fggl::math::FORWARD; } if ( glfwGetKey(window.handle(), GLFW_KEY_A) == GLFW_PRESS ) { translation -= fggl::math::FORWARD; } // apply rotation/movement 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 ); // apply movement if ( translation != glm::vec3(0.0f) ) { const auto rotation = (position - pivot); const float angle = atan2( rotation.x, rotation.z ); const auto rotationMat = glm::rotate( MAT_IDENTITY, angle, fggl::math::UP ); auto deltaMove = (rotationMat * glm::vec4( translation, 1.0f )) * PAN_SPEED; deltaMove.w = 0.0f; position += deltaMove; pivot += deltaMove; } // apply rotation if ( rotationValue != 0.0f ) { glm::mat4 rotation = glm::rotate( MAT_IDENTITY, rotationValue, fggl::math::UP ); position = ( rotation * ( position - pivot ) ) + pivot; } camTransform->origin( position ); camComp->target = pivot; } int main(int argc, char* argv[]) { // setup ECS fggl::ecs::ECS ecs; // build our main window auto glfwModule = fggl::gfx::ecsInitGlfw(ecs); fggl::gfx::Window win( fggl::gfx::Input::instance() ); win.title("FGGL Demo"); win.fullscreen( true ); // storage API fggl::data::Storage storage; discover( storage.resolvePath(fggl::data::Data, "res") ); // Opengl APIs auto glModule = fggl::gfx::ecsInitOpenGL(ecs, win, storage); fggl::gfx::loadPipeline(glModule, "unlit", false); fggl::gfx::loadPipeline(glModule, "phong", false); fggl::gfx::loadPipeline(glModule, "normals", false); // debug layer fggl::debug::DebugUI debug(win); debug.visible(true); // create ECS 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); } auto floorEnt = ecs.createEntity(); { ecs.addComponent<fggl::math::Transform>( floorEnt ); fggl::data::Mesh mesh = fggl::data::make_quad_xz(); ecs.addComponent<fggl::gfx::StaticMesh>(floorEnt, mesh, "phong"); fggl::gfx::onStaticMeshAdded(ecs, floorEnt, glModule); } 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(); ecs.addComponent<fggl::gfx::StaticMesh>(entity, mesh, "phong"); // pretend we have callbacks fggl::gfx::onStaticMeshAdded(ecs, entity, glModule); } fggl::gfx::Input& input = fggl::gfx::Input::instance(); bool joystickWindow = true; bool gamepadWindow = true; fggl::util::Timer time; time.frequency( glfwGetTimerFrequency() ); time.setup( glfwGetTimerValue() ); while( !win.closeRequested() ) { // // Setup setup // time.tick( glfwGetTimerValue() ); input.frame(); glfwModule->context.pollEvents(); debug.frameStart(); // // update step // process_camera(win, ecs, input, camEnt); if ( cam_mode == cam_arcball ) { if ( input.mouseDown( fggl::gfx::MOUSE_2 ) ) { process_arcball(win, ecs, input, camEnt); } } else if ( cam_mode == cam_free ) { process_freecam(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(); debug.showDemo(); /* 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 // win.activate(); glModule->ogl.clear(); // model rendering system fggl::gfx::renderMeshes(glModule, ecs, time.delta()); debug.draw(); // swap the windows - frame rendering over win.swap(); } return 0; }