#ifndef GLHELPERS_H #define GLHELPERS_H #include "path_glsl_files.h" class GlHelper { public: GLFWwindow* window; std::vector all_paths; std::vector vertex_shader_paths; std::vector tess_control_shader_paths; std::vector tess_evaluation_shader_paths; std::vector fragment_shader_paths; double time_of_last_shader_compilation = 0; bool isFirstCompilation = true; GlHelper(int task) { last_time = get_seconds(); setShaderPaths(task, all_paths, vertex_shader_paths, tess_control_shader_paths, tess_evaluation_shader_paths, fragment_shader_paths); } // Use GLFW and GLAD to create a window GLFWwindow* createWindow() { // Initialize GLFW if (!glfwInit()) { std::cerr << "Failed to initialize GLFW" << std::endl; return NULL; } // Set GLFW to create an OpenGL context (version 440 core) glfwWindowHint(GLFW_SAMPLES, 4); glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4); glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0); glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); GLFWwindow* window = glfwCreateWindow(width, height, "shader-pipeline", NULL, NULL); if (!window) { std::cerr << "Failed to create GLFW window" << std::endl; glfwTerminate(); return NULL; } // Make the OpenGL context current glfwMakeContextCurrent(window); // Initialize GLAD if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) { std::cerr << "Failed to initialized GLAD" << std::endl; glfwTerminate(); return NULL; } this->window = window; return window; } // Handle window rescaling and update the projection matrix accordingly void setPerspectiveMatrixBasedOnWindowScale() { const auto& reshape = []( GLFWwindow* window, int _width, int _height) { ::width = _width, ::height = _height; // windows can't handle variables named near and far. float nearVal = 0.01f; float farVal = 100.0f; float top = static_cast(tan(35. / 360. * M_PI)) * nearVal; float right = top * (float)::width / (float)::height; float left = -right; float bottom = -top; proj.setConstant(4, 4, 0.); proj(0, 0) = (2.0f * nearVal) / (right - left); proj(1, 1) = (2.0f * nearVal) / (top - bottom); proj(0, 2) = (right + left) / (right - left); proj(1, 2) = (top + bottom) / (top - bottom); proj(2, 2) = -(farVal + nearVal) / (farVal - nearVal); proj(3, 2) = -1.0f; proj(2, 3) = -(2.0f * farVal * nearVal) / (farVal - nearVal); }; // Set up window resizing glfwSetWindowSizeCallback(window, reshape); { int width_window, height_window; glfwGetWindowSize(window, &width_window, &height_window); reshape(window, width_window, height_window); } } // Setup a VAO from a mesh void createVAO() { icosahedron(V, F); mesh_to_vao(V, F, VAO); igl::opengl::report_gl_error("mesh_to_vao"); } // Make GLFW listen for keyboard and mouse inputs // and change the user input state accordingly void setKeyboardAndMouseCallbacks() { std::cout << R"( Usage: [] the window can be rescaled [Click and drag] to orbit view [Scroll] to translate view in and out A,a toggle animation L,l toggle wireframe rending Z,z reset view to look along z-axis )"; // Close the window if user presses ESC or CTRL+C glfwSetKeyCallback( window, [](GLFWwindow* window, int key, int scancode, int action, int mods) { if (key == 256 || (key == 67 && (mods & GLFW_MOD_CONTROL))) { glfwSetWindowShouldClose(window, true); } }); // Listen to keypresses on A, L and Z glfwSetCharModsCallback( window, [](GLFWwindow* window, unsigned int codepoint, int modifier) { switch (codepoint) { case 'A': case 'a': is_animating ^= 1; if (is_animating) { last_time = get_seconds(); } break; case 'L': case 'l': wire_frame ^= 1; if (wire_frame) { glDisable(GL_CULL_FACE); } else { glEnable(GL_CULL_FACE); } break; case 'Z': case 'z': view.matrix().block(0, 0, 3, 3).setIdentity(); break; default: std::cout << "Unrecognized key: " << (unsigned char)codepoint << std::endl; break; } }); glfwSetMouseButtonCallback( window, [](GLFWwindow* window, int button, int action, int mods) { mouse_down = action == GLFW_PRESS; }); glfwSetCursorPosCallback( window, [](GLFWwindow* window, double x, double y) { static double mouse_last_x = x; static double mouse_last_y = y; float dx = static_cast(x - mouse_last_x); float dy = static_cast(y - mouse_last_y); if (mouse_down) { // Two axis valuator with fixed up float factor = std::abs(view.matrix()(2, 3)); view.rotate( Eigen::AngleAxisf( dx * factor / float(width), Eigen::Vector3f(0, 1, 0))); view.rotate( Eigen::AngleAxisf( dy * factor / float(height), view.matrix().topLeftCorner(3, 3).inverse() * Eigen::Vector3f(1, 0, 0))); } mouse_last_x = x; mouse_last_y = y; }); glfwSetScrollCallback(window, [](GLFWwindow* window, double xoffset, double yoffset) { view.matrix()(2, 3) = std::min(std::max(view.matrix()(2, 3) + (float)yoffset, -100.0f), -2.0f); }); } bool glslFileChanged() { for (std::string& path : all_paths) { if (last_modification_time(path) > time_of_last_shader_compilation) { if (isFirstCompilation) { isFirstCompilation = false; } else { std::cout << path << " has changed since last compilation attempt." << std::endl; } return true; } } return false; } bool compileShaderIfChanged() { if (glslFileChanged()) { time_of_last_shader_compilation = get_seconds(); // (re)compile shader if (!create_shader_program_from_files( vertex_shader_paths, tess_control_shader_paths, tess_evaluation_shader_paths, fragment_shader_paths, prog_id)) { // failed to compile shader glDeleteProgram(prog_id); prog_id = 0; return false; } } return true; } // Update the uniforms used in the GLSL shaders void updateShaderUniforms() { // select program glUseProgram(prog_id); // Attach uniforms { if (is_animating) { double now = get_seconds(); animation_seconds += now - last_time; last_time = now; } glUniform1f(glGetUniformLocation(prog_id, "animation_seconds"), static_cast(animation_seconds)); } glUniformMatrix4fv( glGetUniformLocation(prog_id, "proj"), 1, false, proj.data()); glUniformMatrix4fv( glGetUniformLocation(prog_id, "view"), 1, false, view.matrix().data()); // Draw mesh as wireframe if (wire_frame) { glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); } else { glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); } } }; #endif