#include #include #include #include #include #include #include #include #define STBI_ONLY_PNG #include constexpr const char *NAME = "Progressia Texture Previewer"; constexpr const char *VERSION = "1.0"; void load_textures(int argc, char **argv); void create_window(); void load_opengl(); void display_window(); void main_loop(); std::string programName; int main(int argc, char **argv) { programName = argv[0]; load_textures(argc, argv); create_window(); load_opengl(); display_window(); main_loop(); return 0; } class Texture { private: std::string name; int width, height; GLvoid *data; GLuint gl_id; public: Texture(const std::string &name) : name(name), data(NULL), gl_id(-1) {} Texture(const Texture&) = delete; Texture& operator=(const Texture&) = delete; void load_opengl(); void unload_opengl(); bool hasData() const { return data != NULL; } bool hasID() const { return gl_id != -1; } GLuint getId() const { return gl_id; } const std::string& getName() { return name; } void load_stb() { int channels; data = stbi_load(name.c_str(), &width, &height, &channels, STBI_rgb_alpha); if (!hasData()) { std::cerr << programName << ": Could not load image due to format or IO error: " << name << std::endl; return; } std::cout << "Loaded image " << name << std::endl; } void unload_stb() { if (hasData()) { std::cout << "Freeing STB data for " << name << std::endl; stbi_image_free(data); data = NULL; } } ~Texture() { unload_stb(); unload_opengl(); } }; std::vector> LOADED_TEXTURES; bool loadTexturesIntoOpenGLWhenLoaded = false; void load_texture(const std::string &path) { Texture *texture = new Texture(path); texture->load_stb(); if (!texture->hasData()) { delete texture; return; } LOADED_TEXTURES.emplace_back(texture); if (loadTexturesIntoOpenGLWhenLoaded) { texture->load_opengl(); } } void unload_textures(); void load_textures(int argc, char **argv) { std::vector textures; for (size_t i = 0; i < argc - 1; ++i) { textures.emplace_back(argv[i + 1]); if (textures.at(i) == "--help") { std::cout << NAME << " version " << VERSION << std::endl; std::cout << "Usage: " << programName << " [PATH... | --help | --version]\n" " PATH adds a texture specified by PATH to the list of displayed textures\n" " --help displays this message and exits\n" " --version displays version information and exits" << std::endl; std::exit(EXIT_SUCCESS); } if (textures.at(i) == "--version") { std::cout << NAME << " version " << VERSION << std::endl; std::exit(EXIT_SUCCESS); } } std::for_each(textures.begin(), textures.end(), &load_texture); std::cout << "Loaded " << LOADED_TEXTURES.size() << " images" << std::endl; } void unload_textures() { LOADED_TEXTURES.clear(); } GLFWwindow *window; enum MODE { BLOCK, PLANE } currentMode = BLOCK; enum CAMERA { ANIMATED, ISOMETRIC } currentCamera = ANIMATED; int selection = 0; bool light_theme = true; int width, height; void on_glfw_error(int errorCode, const char *description); void glfw_shutdown_hook(); void on_key(GLFWwindow* window, int key, int scancode, int action, int mods); void on_drop(GLFWwindow* window, int count, const char** paths); void create_window() { glfwSetErrorCallback(&on_glfw_error); if (!glfwInit()) { std::cerr << programName << ": could not initialize GLFW" << std::endl; std::exit(EXIT_FAILURE); } std::atexit(&glfw_shutdown_hook); std::cout << "GLFW initialized" << std::endl; glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE); glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE); glfwWindowHint(GLFW_FOCUSED, GLFW_TRUE); glfwWindowHint(GLFW_MAXIMIZED, GLFW_FALSE); glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 2); glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 1); glfwWindowHint(GLFW_SAMPLES, 4); window = glfwCreateWindow(800, 600, NAME, NULL, NULL); glfwSetKeyCallback(window, &on_key); glfwSetDropCallback(window, &on_drop); if (window == nullptr) { std::cerr << programName << ": could not create window" << std::endl; std::exit(EXIT_FAILURE); } } void on_glfw_error(int errorCode, const char *description) { std::cerr << programName << ": GLFW has failed: (" << errorCode << ") " << description << std::endl; std::exit(EXIT_FAILURE); } void glfw_shutdown_hook() { glfwTerminate(); std::cout << "GLFW terminated" << std::endl; } void switch_mode(); void switch_camera(); void switch_theme(); void change_texture(int direction); void close_texture(); void reload_texture(); void on_key(GLFWwindow*, int key, int, int action, int mods) { if (action != GLFW_PRESS) return; switch (key) { case GLFW_KEY_SPACE: if ((mods & GLFW_MOD_SHIFT) != 0) { switch_theme(); } else { switch_mode(); } return; case GLFW_KEY_LEFT: change_texture(-1); return; case GLFW_KEY_RIGHT: change_texture(+1); return; case GLFW_KEY_DELETE: close_texture(); return; case GLFW_KEY_F5: reload_texture(); return; case GLFW_KEY_TAB: switch_camera(); return; default: // Do nothing break; } } void on_drop(GLFWwindow*, int count, const char** paths) { for (size_t i = 0; i < count; i++) { load_texture(paths[i]); } } void switch_mode() { switch (currentMode) { case BLOCK: currentMode = PLANE; break; case PLANE: currentMode = BLOCK; break; } } void switch_camera() { switch (currentCamera) { case ANIMATED: currentCamera = ISOMETRIC; break; case ISOMETRIC: currentCamera = ANIMATED; break; } } void switch_theme() { light_theme = !light_theme; } void change_texture(int direction) { if (LOADED_TEXTURES.size() < 2) return; selection += direction; } void close_texture() { if (LOADED_TEXTURES.empty()) return; LOADED_TEXTURES.erase(LOADED_TEXTURES.begin() + selection); selection -= 1; } void reload_texture() { if (LOADED_TEXTURES.empty()) return; auto it = LOADED_TEXTURES.begin() + selection; std::string name = (*it)->getName(); LOADED_TEXTURES.erase(it); load_texture(name); for (size_t i = 0; i < LOADED_TEXTURES.size(); ++i) { if (LOADED_TEXTURES[i]->getName() == name) { selection = i; break; } } } GLuint program; GLint attrib_coords; GLint attrib_textureCoords; GLint attrib_color; GLint uniform_tranform; GLint uniform_useTexture; void unload_opengl(); void check_opengl(); void load_shaders(); void load_model(); void load_opengl() { glfwMakeContextCurrent(window); gladLoadGL(glfwGetProcAddress); glfwSwapInterval(1); std::atexit(&unload_opengl); load_shaders(); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); check_opengl(); std::cout << "OpenGL initialized" << std::endl; } enum VariableType { ATTRIBUTE, UNIFORM }; GLint find_shader_variable(const char *name, VariableType); enum LogType { SHADER, PROGRAM }; void log_shader_error(GLuint id, LogType); void load_shaders() { GLint compile_ok = GL_FALSE, link_ok = GL_FALSE; GLuint vs = glCreateShader(GL_VERTEX_SHADER); const char *vs_source = R"(#version 120 attribute vec3 coords; attribute vec2 textureCoords; attribute vec3 color; uniform mat4 transform; varying vec2 textureCoords_var; varying vec3 color_var; void main(void) { gl_Position = transform * vec4(coords, 1.0); color_var = color; textureCoords_var = textureCoords; })"; glShaderSource(vs, 1, &vs_source, NULL); glCompileShader(vs); glGetShaderiv(vs, GL_COMPILE_STATUS, &compile_ok); if (!compile_ok) { log_shader_error(vs, SHADER); std::cerr << programName << ": Error in vertex shader" << std::endl; std::exit(EXIT_FAILURE); } GLuint fs = glCreateShader(GL_FRAGMENT_SHADER); const char *fs_source = R"(#version 120 varying vec2 textureCoords_var; varying vec3 color_var; uniform bool useTexture; uniform sampler2D texture; void main(void) { if (useTexture) { gl_FragColor = vec4(color_var, 1.0) * texture2D(texture, textureCoords_var); } else { gl_FragColor = vec4(color_var, 1.0); } })"; glShaderSource(fs, 1, &fs_source, NULL); glCompileShader(fs); glGetShaderiv(fs, GL_COMPILE_STATUS, &compile_ok); if (!compile_ok) { log_shader_error(fs, SHADER); std::cerr << programName << ": Error in fragment shader" << std::endl; std::exit(EXIT_FAILURE); } program = glCreateProgram(); glAttachShader(program, vs); glAttachShader(program, fs); glLinkProgram(program); glGetProgramiv(program, GL_LINK_STATUS, &link_ok); if (!link_ok) { log_shader_error(program, SHADER); std::cerr << programName << ": Could not link shaders" << std::endl; std::exit(EXIT_FAILURE); } attrib_coords = find_shader_variable("coords", ATTRIBUTE); attrib_textureCoords = find_shader_variable("textureCoords", ATTRIBUTE); attrib_color = find_shader_variable("color", ATTRIBUTE); uniform_tranform = find_shader_variable("transform", UNIFORM); uniform_useTexture = find_shader_variable("useTexture", UNIFORM); glUseProgram(program); glActiveTexture(GL_TEXTURE0); glUniform1i(find_shader_variable("texture", UNIFORM), 0); for (auto &texture : LOADED_TEXTURES) { texture->load_opengl(); } loadTexturesIntoOpenGLWhenLoaded = true; check_opengl(); } GLint find_shader_variable(const char *name, VariableType type) { GLint result = (type == ATTRIBUTE ? glGetAttribLocation : glGetUniformLocation)(program, name); if (result < 0) { std::cerr << programName << ": Could not find " << (type == ATTRIBUTE ? "attribute " : "uniform ") << name << std::endl; std::exit(EXIT_FAILURE); } return result; } void log_shader_error(GLuint id, LogType type) { GLsizei logLength; (type == SHADER ? glGetShaderiv : glGetProgramiv)(id, GL_INFO_LOG_LENGTH, &logLength); GLchar *log = new GLchar[logLength]; (type == SHADER ? glGetShaderInfoLog : glGetProgramInfoLog)(id, logLength, NULL, log); std::cerr << programName << ": OpenGL:\n" << log << std::endl; delete[] log; std::exit(EXIT_FAILURE); } void unload_opengl() { if (glIsProgram(program)) glDeleteProgram(program); std::cout << "OpenGL terminated" << std::endl; } void check_opengl() { GLenum error = glGetError(); if (error != GL_NO_ERROR) { std::cerr << programName << ": OpenGL error " << std::hex << error << std::endl; std::exit(EXIT_FAILURE); } } void Texture::load_opengl() { if (hasData() && !hasID()) { glGenTextures(1, &gl_id); glBindTexture(GL_TEXTURE_2D, gl_id); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data ); check_opengl(); std::cout << "Loaded into OpenGL " << name << std::endl; unload_stb(); } } void Texture::unload_opengl() { if (hasID()) { glDeleteTextures(1, &gl_id); gl_id = -1; std::cout << "Unloaded from OpenGL " << name << std::endl; } } void display_window() { glfwShowWindow(window); } void draw_background(); void draw_object(); void draw_block(); void draw_plane(); void bind_coords(const GLfloat*); void bind_color(const GLfloat*); void bind_texture_coords(const GLfloat*); void set_texture(const Texture&); void set_use_texture(bool); void set_transform(const glm::mat4&); void main_loop() { while (!glfwWindowShouldClose(window)) { glfwGetFramebufferSize(window, &width, &height); glViewport(0, 0, width, height); glEnableVertexAttribArray(attrib_coords); glEnableVertexAttribArray(attrib_color); glEnableVertexAttribArray(attrib_textureCoords); draw_background(); draw_object(); glDisableVertexAttribArray(attrib_textureCoords); glDisableVertexAttribArray(attrib_color); glDisableVertexAttribArray(attrib_coords); check_opengl(); glfwSwapBuffers(window); glfwPollEvents(); } } namespace background { constexpr const GLfloat COORDS[] = { -1.0, -1.0, 0.0, -1.0, +1.0, 0.0, +1.0, +1.0, 0.0, +1.0, -1.0, 0.0, }; constexpr const GLfloat COLORS_LIGHT[] = { +0.80, +0.80, +0.85, +0.95, +0.95, +1.00, +0.95, +0.95, +1.00, +0.80, +0.80, +0.85, }; constexpr const GLfloat COLORS_DARK[] = { +0.1, +0.1, +0.15, +0.2, +0.2, +0.25, +0.2, +0.2, +0.25, +0.1, +0.1, +0.15, }; } namespace block { constexpr const GLfloat COORDS[] = { // front -1.0, -1.0, 1.0, 1.0, -1.0, 1.0, 1.0, 1.0, 1.0, -1.0, 1.0, 1.0, // top -1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, -1.0, -1.0, 1.0, -1.0, // back 1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, 1.0, -1.0, 1.0, 1.0, -1.0, // bottom -1.0, -1.0, -1.0, 1.0, -1.0, -1.0, 1.0, -1.0, 1.0, -1.0, -1.0, 1.0, // left -1.0, -1.0, -1.0, -1.0, -1.0, 1.0, -1.0, 1.0, 1.0, -1.0, 1.0, -1.0, // right 1.0, -1.0, 1.0, 1.0, -1.0, -1.0, 1.0, 1.0, -1.0, 1.0, 1.0, 1.0, }; constexpr const GLfloat COLORS[] = { // front 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, // top 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, // back 0.6, 0.6, 0.6, 0.6, 0.6, 0.6, 0.6, 0.6, 0.6, 0.6, 0.6, 0.6, // bottom 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, // left 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, // right 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, }; constexpr const GLfloat TEX_COORDS[] = { // front 0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0, // top 0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0, // back 0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0, // bottom 0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0, // left 0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0, // right 0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0, }; } namespace plane { constexpr const GLfloat COORDS[] = { -1.0, -1.0, 0.0, -1.0, +1.0, 0.0, +1.0, +1.0, 0.0, +1.0, -1.0, 0.0, }; constexpr const GLfloat COLORS[] = { +1.0, +1.0, +1.0, +1.0, +1.0, +1.0, +1.0, +1.0, +1.0, +1.0, +1.0, +1.0, }; constexpr const GLfloat TEX_COORDS[] = { -3.0, -3.0, -3.0, +3.0, +3.0, +3.0, +3.0, -3.0, }; } void draw_background() { bind_coords(background::COORDS); bind_color(light_theme ? background::COLORS_LIGHT : background::COLORS_DARK); set_transform(glm::identity()); set_use_texture(false); glDrawArrays(GL_QUADS, 0, 4); } void draw_object() { if (LOADED_TEXTURES.empty()) { // Do nothing return; } if (selection < 0) selection = LOADED_TEXTURES.size() - 1; if (selection >= LOADED_TEXTURES.size()) selection = 0; set_texture(*(LOADED_TEXTURES[selection])); switch (currentMode) { case BLOCK: draw_block(); return; case PLANE: draw_plane(); return; } } void draw_block() { glEnable(GL_CULL_FACE); glEnable(GL_DEPTH_TEST); glClear(GL_DEPTH_BUFFER_BIT); bind_coords(block::COORDS); bind_color(block::COLORS); bind_texture_coords(block::TEX_COORDS); GLfloat aspectRatio = static_cast(width) / height; glm::mat4 transform = glm::identity(); transform = glm::scale(transform, glm::vec3( std::min(1.0f, 1.0f / aspectRatio), std::min(1.0f, aspectRatio), 1.0f )); switch (currentCamera) { case ANIMATED: { float t = static_cast(glfwGetTime()) / 20; transform = glm::rotate(transform, t * 3, glm::vec3(1, 0, 0)); transform = glm::rotate(transform, t * 7, glm::vec3(0, 1, 0)); transform = glm::rotate(transform, t * 11, glm::vec3(0, 0, 1)); } break; case ISOMETRIC: constexpr const float SQRT_2 = 1.41421356237309504880168872420969808; constexpr const float SQRT_3 = 1.73205080756887729352744634150587236; constexpr const float SQRT_6 = SQRT_2 * SQRT_3; transform *= glm::mat4( 1 / SQRT_2, 0, -1 / SQRT_2, 0, 1 / SQRT_6, SQRT_2 / SQRT_3, 1 / SQRT_6, 0, 1 / SQRT_3, -1 / SQRT_3, 1 / SQRT_3, 0, 0, 0, 0, 1 ); break; } transform = glm::scale(transform, glm::vec3(0.5)); set_transform(transform); set_use_texture(true); glDrawArrays(GL_QUADS, 0, 4*6); glDisable(GL_DEPTH_TEST); glDisable(GL_CULL_FACE); } void draw_plane() { bind_coords(plane::COORDS); bind_color(plane::COLORS); bind_texture_coords(plane::TEX_COORDS); GLfloat aspectRatio = static_cast(width) / height; set_transform(glm::scale(glm::identity(), glm::vec3( std::max(1.0f, 1.0f / aspectRatio), std::max(1.0f, aspectRatio), 1.0f ))); set_use_texture(true); glDrawArrays(GL_QUADS, 0, 4); } void bind_coords(const GLfloat *data) { glVertexAttribPointer( attrib_coords, 3, GL_FLOAT, GL_FALSE, 0, data ); } void bind_color(const GLfloat *data) { glVertexAttribPointer( attrib_color, 3, GL_FLOAT, GL_FALSE, 0, data ); } void bind_texture_coords(const GLfloat *data) { glVertexAttribPointer( attrib_textureCoords, 2, GL_FLOAT, GL_FALSE, 0, data ); } void set_texture(const Texture &texture) { glBindTexture(GL_TEXTURE_2D, texture.getId()); } void set_use_texture(bool use) { glUniform1i(uniform_useTexture, use ? 1 : 0); } void set_transform(const glm::mat4 &m) { glUniformMatrix4fv(uniform_tranform, 1, false, glm::value_ptr(m)); }