846 lines
20 KiB
C++

#include <vector>
#include <iostream>
#include <iomanip>
#include <algorithm>
#include <memory>
#include <glad/gl.h>
#include <GLFW/glfw3.h>
#include <glm/ext.hpp>
#define STBI_ONLY_PNG
#include <stb/stb_image.h>
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<std::unique_ptr<Texture>> 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<std::string> 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<glm::mat4>());
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<GLfloat>(width) / height;
glm::mat4 transform = glm::identity<glm::mat4>();
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<float>(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<GLfloat>(width) / height;
set_transform(glm::scale(glm::identity<glm::mat4>(), 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));
}