#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 using namespace fggl::input; using InputManager = std::shared_ptr<fggl::input::Input>; void process_arcball(fggl::gfx::Window& window, fggl::ecs::ECS& ecs, InputManager 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); auto& mouse = input->mouse; 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 = ( -mouse.axisDelta(fggl::input::MouseAxis::X) ) * deltaAngleX; float yAngle = ( -mouse.axisDelta(fggl::input::MouseAxis::Y) ) * 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::ecs::ECS& ecs, InputManager input, fggl::ecs::entity_t cam) { float rotationValue = 0.0f; glm::vec3 translation(0.0f); auto& keyboard = input->keyboard; auto code_q = glfwGetKeyScancode(GLFW_KEY_Q); auto code_e = glfwGetKeyScancode(GLFW_KEY_E); auto code_w = glfwGetKeyScancode(GLFW_KEY_W); auto code_s = glfwGetKeyScancode(GLFW_KEY_S); auto code_d = glfwGetKeyScancode(GLFW_KEY_D); auto code_a = glfwGetKeyScancode(GLFW_KEY_A); // calulate rotation (user input) if ( keyboard.down( code_q ) ) { rotationValue = ROT_SPEED; } else if ( keyboard.down(code_e) ) { rotationValue = -ROT_SPEED; } // calulate movement (user input) if ( keyboard.down(code_w) ) { translation -= fggl::math::RIGHT; } if ( keyboard.down(code_s) ) { translation += fggl::math::RIGHT; } if ( keyboard.down(code_d) ) { translation += fggl::math::FORWARD; } if ( keyboard.down(code_a) ) { 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; } void process_camera(fggl::gfx::Window& window, fggl::ecs::ECS& ecs, InputManager input, fggl::ecs::entity_t cam) { 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 = input->mouse.axisDelta( fggl::input::MouseAxis::SCROLL_Y ); 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 ); if ( cam_mode == cam_arcball || input->mouse.button( fggl::input::MouseButton::MIDDLE ) ) { process_arcball(window, ecs, input, cam); } else if ( cam_mode == cam_free ) { process_freecam(ecs, input, cam); } } int main(int argc, char* argv[]) { // setup ECS fggl::ecs::ECS ecs; auto inputs = std::make_shared<fggl::input::Input>(); // build our main window auto glfwModule = fggl::gfx::ecsInitGlfw(ecs, inputs); fggl::gfx::Window win; 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); } bool joystickWindow = true; bool gamepadWindow = true; fggl::util::Timer time; time.frequency( glfwGetTimerFrequency() ); time.setup( glfwGetTimerValue() ); while( !win.closeRequested() ) { // // Setup setup // time.tick( glfwGetTimerValue() ); inputs->frame( time.delta() ); glfwModule->context.pollEvents(); debug.frameStart(); // // update step // process_camera(win, ecs, inputs, camEnt); // imgui gamepad debug auto& gamepads = inputs->gamepads; ImGui::Begin("GamePad", &gamepadWindow); for ( int i=0; i<16; i++ ) { std::string title = "GamePad " + std::to_string(i); bool present = gamepads.present(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::input::GamepadButtonsMicrosoft ) { ImGui::Text( "%s: %i %i %i", btn.name, gamepads.button(i, btn.id), gamepads.buttonPressed(i, btn.id), gamepads.buttonReleased(i, btn.id) ); } ImGui::TreePop(); } if ( ImGui::TreeNode("axes##2") ) { for ( auto& axis : fggl::input::GamepadAxes ) { ImGui::Text("%s: %f %f", axis.name, gamepads.axis(i, axis.id), gamepads.axisDelta(i, axis.id) ); } 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; }