nuclear@4: /* nuclear@4: Cubemapper - a program for converting panoramic images into cubemaps nuclear@4: Copyright (C) 2017 John Tsiombikas nuclear@4: nuclear@4: This program is free software: you can redistribute it and/or modify nuclear@4: it under the terms of the GNU General Public License as published by nuclear@4: the Free Software Foundation, either version 3 of the License, or nuclear@4: (at your option) any later version. nuclear@4: nuclear@4: This program is distributed in the hope that it will be useful, nuclear@4: but WITHOUT ANY WARRANTY; without even the implied warranty of nuclear@4: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the nuclear@4: GNU General Public License for more details. nuclear@4: nuclear@4: You should have received a copy of the GNU General Public License nuclear@4: along with this program. If not, see . nuclear@4: */ nuclear@0: #include nuclear@0: #include nuclear@0: #include nuclear@0: #include nuclear@0: #include nuclear@0: #include nuclear@0: #include "app.h" nuclear@0: #include "opengl.h" nuclear@0: #include "texture.h" nuclear@0: #include "mesh.h" nuclear@0: #include "meshgen.h" nuclear@0: nuclear@2: static void draw_equilateral(); nuclear@2: static void draw_cubemap(); nuclear@0: static bool parse_args(int argc, char **argv); nuclear@0: nuclear@3: static const char *img_fname, *img_suffix; nuclear@0: static float cam_theta, cam_phi; nuclear@0: nuclear@5: static Texture *tex; nuclear@5: static Mesh *mesh; nuclear@0: nuclear@0: static int win_width, win_height; nuclear@2: static int show_cubemap; nuclear@2: nuclear@2: static unsigned int fbo; nuclear@2: static unsigned int cube_tex; nuclear@2: static int cube_size; nuclear@0: nuclear@0: nuclear@0: bool app_init(int argc, char **argv) nuclear@0: { nuclear@0: if(!parse_args(argc, argv)) { nuclear@0: return false; nuclear@0: } nuclear@0: if(!img_fname) { nuclear@0: fprintf(stderr, "please specify an equilateral panoramic image\n"); nuclear@0: return false; nuclear@0: } nuclear@0: nuclear@0: if(!init_opengl()) { nuclear@0: return false; nuclear@0: } nuclear@0: nuclear@2: glEnable(GL_MULTISAMPLE); nuclear@0: nuclear@0: Mesh::use_custom_sdr_attr = false; nuclear@5: mesh = new Mesh; nuclear@5: gen_sphere(mesh, 1.0, 80, 40); nuclear@5: mesh->flip(); nuclear@0: Mat4 xform; nuclear@0: xform.rotation_y(-M_PI / 2.0); // rotate the sphere to face the "front" part of the image nuclear@5: mesh->apply_xform(xform, xform); nuclear@0: nuclear@0: xform.scaling(-1, 1, 1); // flip horizontal texcoord since we're inside the sphere nuclear@5: mesh->texcoord_apply_xform(xform); nuclear@0: nuclear@5: tex = new Texture; nuclear@5: if(!tex->load(img_fname)) { nuclear@0: return false; nuclear@0: } nuclear@5: printf("loaded image: %dx%d\n", tex->get_width(), tex->get_height()); nuclear@2: nuclear@3: if(!(img_suffix = strrchr(img_fname, '.'))) { nuclear@3: img_suffix = ".jpg"; nuclear@3: } nuclear@3: nuclear@2: // create cubemap nuclear@5: cube_size = tex->get_height(); nuclear@2: glGenTextures(1, &cube_tex); nuclear@2: glBindTexture(GL_TEXTURE_CUBE_MAP, cube_tex); nuclear@2: glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); nuclear@2: glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR); nuclear@2: glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); nuclear@2: glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); nuclear@2: glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); nuclear@2: nuclear@2: for(int i=0; i<6; i++) { nuclear@2: glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, GL_RGB16F, cube_size, cube_size, nuclear@2: 0, GL_RGB, GL_FLOAT, 0); nuclear@2: } nuclear@2: nuclear@2: nuclear@2: // create fbo nuclear@2: glGenFramebuffers(1, &fbo); nuclear@2: nuclear@2: // tex-gen for cubemap visualization nuclear@2: glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR); nuclear@2: glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR); nuclear@2: glTexGeni(GL_R, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR); nuclear@2: float planes[][4] = {{1, 0, 0, 0}, {0, 1, 0, 0}, {0, 0, 1, 0}}; nuclear@2: glTexGenfv(GL_S, GL_OBJECT_PLANE, planes[0]); nuclear@2: glTexGenfv(GL_T, GL_OBJECT_PLANE, planes[1]); nuclear@2: glTexGenfv(GL_R, GL_OBJECT_PLANE, planes[2]); nuclear@0: return true; nuclear@0: } nuclear@0: nuclear@0: void app_cleanup() nuclear@0: { nuclear@5: delete mesh; nuclear@5: delete tex; nuclear@0: } nuclear@0: nuclear@0: void app_draw() nuclear@0: { nuclear@0: glClear(GL_COLOR_BUFFER_BIT); nuclear@0: nuclear@0: Mat4 view_matrix; nuclear@0: view_matrix.pre_rotate_x(deg_to_rad(cam_phi)); nuclear@0: view_matrix.pre_rotate_y(deg_to_rad(cam_theta)); nuclear@0: nuclear@0: glMatrixMode(GL_MODELVIEW); nuclear@0: glLoadMatrixf(view_matrix[0]); nuclear@0: nuclear@2: if(show_cubemap) { nuclear@2: draw_cubemap(); nuclear@2: nuclear@2: glColor3f(0, 0, 0); nuclear@2: app_print_text(10, 10, "cubemap"); nuclear@2: glColor3f(0, 0.8, 1); nuclear@2: app_print_text(8, 13, "cubemap"); nuclear@2: } else { nuclear@2: draw_equilateral(); nuclear@2: nuclear@2: glColor3f(0, 0, 0); nuclear@2: app_print_text(10, 10, "equilateral"); nuclear@2: glColor3f(1, 0.8, 0); nuclear@2: app_print_text(8, 13, "equilateral"); nuclear@2: } nuclear@2: glColor3f(1, 1, 1); nuclear@0: nuclear@0: app_swap_buffers(); nuclear@0: assert(glGetError() == GL_NO_ERROR); nuclear@0: } nuclear@0: nuclear@0: void render_cubemap() nuclear@0: { nuclear@2: printf("rendering cubemap %dx%d\n", cube_size, cube_size); nuclear@0: nuclear@2: float *pixels = new float[cube_size * cube_size * 3]; nuclear@2: nuclear@2: glViewport(0, 0, cube_size, cube_size); nuclear@0: nuclear@0: Mat4 viewmat[6]; nuclear@0: viewmat[0].rotation_y(deg_to_rad(90)); // +X nuclear@2: viewmat[1].rotation_y(deg_to_rad(-90)); // -X nuclear@2: viewmat[2].rotation_x(deg_to_rad(90)); // +Y nuclear@2: viewmat[2].rotate_y(deg_to_rad(180)); nuclear@2: viewmat[3].rotation_x(deg_to_rad(-90)); // -Y nuclear@2: viewmat[3].rotate_y(deg_to_rad(180)); nuclear@2: viewmat[4].rotation_y(deg_to_rad(180)); // +Z nuclear@0: nuclear@2: // this must coincide with the order of GL_TEXTURE_CUBE_MAP_* values nuclear@3: static const char *fname_pattern[] = { nuclear@3: "cubemap_px%s", nuclear@3: "cubemap_nx%s", nuclear@3: "cubemap_py%s", nuclear@3: "cubemap_ny%s", nuclear@3: "cubemap_pz%s", nuclear@3: "cubemap_nz%s" nuclear@0: }; nuclear@3: static char fname[64]; nuclear@0: nuclear@0: glMatrixMode(GL_PROJECTION); nuclear@2: glPushMatrix(); nuclear@0: glLoadIdentity(); nuclear@2: gluPerspective(90, 1.0, 0.5, 500.0); nuclear@2: glScalef(-1, -1, 1); nuclear@2: nuclear@2: glBindFramebuffer(GL_FRAMEBUFFER, fbo); nuclear@0: nuclear@0: for(int i=0; i<6; i++) { nuclear@2: glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, nuclear@2: GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, cube_tex, 0); nuclear@2: nuclear@0: glClear(GL_COLOR_BUFFER_BIT); nuclear@0: nuclear@0: glMatrixMode(GL_MODELVIEW); nuclear@0: glLoadMatrixf(viewmat[i][0]); nuclear@0: nuclear@2: draw_equilateral(); nuclear@0: nuclear@2: glGetTexImage(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, GL_RGB, GL_FLOAT, pixels); nuclear@1: nuclear@3: sprintf(fname, fname_pattern[i], img_suffix); nuclear@3: if(img_save_pixels(fname, pixels, cube_size, cube_size, IMG_FMT_RGBF) == -1) { nuclear@3: fprintf(stderr, "failed to save %dx%d image: %s\n", cube_size, cube_size, fname); nuclear@0: } nuclear@0: } nuclear@0: nuclear@2: glBindFramebuffer(GL_FRAMEBUFFER, 0); nuclear@0: glViewport(0, 0, win_width, win_height); nuclear@0: nuclear@2: glMatrixMode(GL_PROJECTION); nuclear@2: glPopMatrix(); nuclear@2: nuclear@0: delete [] pixels; nuclear@2: nuclear@2: glBindTexture(GL_TEXTURE_CUBE_MAP, cube_tex); nuclear@2: glGenerateMipmap(GL_TEXTURE_CUBE_MAP); nuclear@0: } nuclear@0: nuclear@2: static void draw_equilateral() nuclear@0: { nuclear@5: tex->bind(); nuclear@0: glEnable(GL_TEXTURE_2D); nuclear@5: mesh->draw(); nuclear@0: glDisable(GL_TEXTURE_2D); nuclear@0: } nuclear@0: nuclear@2: static void draw_cubemap() nuclear@0: { nuclear@2: glPushAttrib(GL_ENABLE_BIT); nuclear@2: nuclear@2: glBindTexture(GL_TEXTURE_CUBE_MAP, cube_tex); nuclear@2: glEnable(GL_TEXTURE_CUBE_MAP); nuclear@2: glEnable(GL_TEXTURE_GEN_S); nuclear@2: glEnable(GL_TEXTURE_GEN_T); nuclear@2: glEnable(GL_TEXTURE_GEN_R); nuclear@2: nuclear@5: mesh->draw(); nuclear@2: nuclear@2: glPopAttrib(); nuclear@0: } nuclear@0: nuclear@0: void app_reshape(int x, int y) nuclear@0: { nuclear@0: glViewport(0, 0, x, y); nuclear@0: nuclear@0: glMatrixMode(GL_PROJECTION); nuclear@0: glLoadIdentity(); nuclear@0: gluPerspective(50.0, (float)x / (float)y, 0.5, 500.0); nuclear@0: nuclear@0: win_width = x; nuclear@0: win_height = y; nuclear@0: } nuclear@0: nuclear@0: void app_keyboard(int key, bool press) nuclear@0: { nuclear@0: if(press) { nuclear@0: switch(key) { nuclear@0: case 27: nuclear@0: app_quit(); nuclear@0: break; nuclear@0: nuclear@1: case ' ': nuclear@2: show_cubemap = !show_cubemap; nuclear@2: app_redisplay(); nuclear@1: break; nuclear@1: nuclear@2: case 'c': nuclear@0: render_cubemap(); nuclear@5: show_cubemap = 1; nuclear@5: app_redisplay(); nuclear@0: break; nuclear@0: } nuclear@0: } nuclear@0: } nuclear@0: nuclear@0: static float prev_x, prev_y; nuclear@0: static bool bnstate[16]; nuclear@0: nuclear@0: void app_mouse_button(int bn, bool press, int x, int y) nuclear@0: { nuclear@0: if(bn < (int)(sizeof bnstate / sizeof *bnstate)) { nuclear@0: bnstate[bn] = press; nuclear@0: } nuclear@0: prev_x = x; nuclear@0: prev_y = y; nuclear@0: } nuclear@0: nuclear@0: void app_mouse_motion(int x, int y) nuclear@0: { nuclear@0: float dx = x - prev_x; nuclear@0: float dy = y - prev_y; nuclear@0: prev_x = x; nuclear@0: prev_y = y; nuclear@0: nuclear@0: if(!dx && !dy) return; nuclear@0: nuclear@0: if(bnstate[0]) { nuclear@0: cam_theta += dx * 0.5; nuclear@0: cam_phi += dy * 0.5; nuclear@0: nuclear@0: if(cam_phi < -90) cam_phi = -90; nuclear@0: if(cam_phi > 90) cam_phi = 90; nuclear@0: app_redisplay(); nuclear@0: } nuclear@0: } nuclear@0: nuclear@0: static bool parse_args(int argc, char **argv) nuclear@0: { nuclear@0: for(int i=1; i