rev |
line source |
nuclear@4
|
1 /*
|
nuclear@4
|
2 Cubemapper - a program for converting panoramic images into cubemaps
|
nuclear@4
|
3 Copyright (C) 2017 John Tsiombikas <nuclear@member.fsf.org>
|
nuclear@4
|
4
|
nuclear@4
|
5 This program is free software: you can redistribute it and/or modify
|
nuclear@4
|
6 it under the terms of the GNU General Public License as published by
|
nuclear@4
|
7 the Free Software Foundation, either version 3 of the License, or
|
nuclear@4
|
8 (at your option) any later version.
|
nuclear@4
|
9
|
nuclear@4
|
10 This program is distributed in the hope that it will be useful,
|
nuclear@4
|
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
|
nuclear@4
|
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
nuclear@4
|
13 GNU General Public License for more details.
|
nuclear@4
|
14
|
nuclear@4
|
15 You should have received a copy of the GNU General Public License
|
nuclear@4
|
16 along with this program. If not, see <http://www.gnu.org/licenses/>.
|
nuclear@4
|
17 */
|
nuclear@0
|
18 #include <stdio.h>
|
nuclear@0
|
19 #include <stdlib.h>
|
nuclear@0
|
20 #include <string.h>
|
nuclear@0
|
21 #include <math.h>
|
nuclear@0
|
22 #include <assert.h>
|
nuclear@0
|
23 #include <imago2.h>
|
nuclear@0
|
24 #include "app.h"
|
nuclear@0
|
25 #include "opengl.h"
|
nuclear@0
|
26 #include "texture.h"
|
nuclear@0
|
27 #include "mesh.h"
|
nuclear@0
|
28 #include "meshgen.h"
|
nuclear@0
|
29
|
nuclear@2
|
30 static void draw_equilateral();
|
nuclear@2
|
31 static void draw_cubemap();
|
nuclear@0
|
32 static bool parse_args(int argc, char **argv);
|
nuclear@0
|
33
|
nuclear@1
|
34 static void flip_image(float *pixels, int xsz, int ysz);
|
nuclear@1
|
35
|
nuclear@3
|
36 static const char *img_fname, *img_suffix;
|
nuclear@0
|
37 static float cam_theta, cam_phi;
|
nuclear@0
|
38
|
nuclear@0
|
39 static Texture *pano_tex;
|
nuclear@0
|
40 static Mesh *pano_mesh;
|
nuclear@0
|
41
|
nuclear@0
|
42 static int win_width, win_height;
|
nuclear@2
|
43 static int show_cubemap;
|
nuclear@2
|
44
|
nuclear@2
|
45 static unsigned int fbo;
|
nuclear@2
|
46 static unsigned int cube_tex;
|
nuclear@2
|
47 static int cube_size;
|
nuclear@0
|
48
|
nuclear@0
|
49
|
nuclear@0
|
50 bool app_init(int argc, char **argv)
|
nuclear@0
|
51 {
|
nuclear@0
|
52 if(!parse_args(argc, argv)) {
|
nuclear@0
|
53 return false;
|
nuclear@0
|
54 }
|
nuclear@0
|
55 if(!img_fname) {
|
nuclear@0
|
56 fprintf(stderr, "please specify an equilateral panoramic image\n");
|
nuclear@0
|
57 return false;
|
nuclear@0
|
58 }
|
nuclear@0
|
59
|
nuclear@0
|
60 if(!init_opengl()) {
|
nuclear@0
|
61 return false;
|
nuclear@0
|
62 }
|
nuclear@0
|
63
|
nuclear@2
|
64 glEnable(GL_MULTISAMPLE);
|
nuclear@0
|
65
|
nuclear@0
|
66 Mesh::use_custom_sdr_attr = false;
|
nuclear@0
|
67 pano_mesh = new Mesh;
|
nuclear@0
|
68 gen_sphere(pano_mesh, 1.0, 80, 40);
|
nuclear@0
|
69 pano_mesh->flip();
|
nuclear@0
|
70 Mat4 xform;
|
nuclear@0
|
71 xform.rotation_y(-M_PI / 2.0); // rotate the sphere to face the "front" part of the image
|
nuclear@0
|
72 pano_mesh->apply_xform(xform, xform);
|
nuclear@0
|
73
|
nuclear@0
|
74 xform.scaling(-1, 1, 1); // flip horizontal texcoord since we're inside the sphere
|
nuclear@0
|
75 pano_mesh->texcoord_apply_xform(xform);
|
nuclear@0
|
76
|
nuclear@0
|
77 pano_tex = new Texture;
|
nuclear@0
|
78 if(!pano_tex->load(img_fname)) {
|
nuclear@0
|
79 return false;
|
nuclear@0
|
80 }
|
nuclear@1
|
81 printf("loaded image: %dx%d\n", pano_tex->get_width(), pano_tex->get_height());
|
nuclear@2
|
82
|
nuclear@3
|
83 if(!(img_suffix = strrchr(img_fname, '.'))) {
|
nuclear@3
|
84 img_suffix = ".jpg";
|
nuclear@3
|
85 }
|
nuclear@3
|
86
|
nuclear@2
|
87 // create cubemap
|
nuclear@2
|
88 cube_size = pano_tex->get_height();
|
nuclear@2
|
89 glGenTextures(1, &cube_tex);
|
nuclear@2
|
90 glBindTexture(GL_TEXTURE_CUBE_MAP, cube_tex);
|
nuclear@2
|
91 glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
|
nuclear@2
|
92 glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
nuclear@2
|
93 glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
nuclear@2
|
94 glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
nuclear@2
|
95 glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
|
nuclear@2
|
96
|
nuclear@2
|
97 for(int i=0; i<6; i++) {
|
nuclear@2
|
98 glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, GL_RGB16F, cube_size, cube_size,
|
nuclear@2
|
99 0, GL_RGB, GL_FLOAT, 0);
|
nuclear@2
|
100 }
|
nuclear@2
|
101
|
nuclear@2
|
102
|
nuclear@2
|
103 // create fbo
|
nuclear@2
|
104 glGenFramebuffers(1, &fbo);
|
nuclear@2
|
105
|
nuclear@2
|
106 // tex-gen for cubemap visualization
|
nuclear@2
|
107 glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR);
|
nuclear@2
|
108 glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR);
|
nuclear@2
|
109 glTexGeni(GL_R, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR);
|
nuclear@2
|
110 float planes[][4] = {{1, 0, 0, 0}, {0, 1, 0, 0}, {0, 0, 1, 0}};
|
nuclear@2
|
111 glTexGenfv(GL_S, GL_OBJECT_PLANE, planes[0]);
|
nuclear@2
|
112 glTexGenfv(GL_T, GL_OBJECT_PLANE, planes[1]);
|
nuclear@2
|
113 glTexGenfv(GL_R, GL_OBJECT_PLANE, planes[2]);
|
nuclear@0
|
114 return true;
|
nuclear@0
|
115 }
|
nuclear@0
|
116
|
nuclear@0
|
117 void app_cleanup()
|
nuclear@0
|
118 {
|
nuclear@0
|
119 delete pano_mesh;
|
nuclear@0
|
120 delete pano_tex;
|
nuclear@0
|
121 }
|
nuclear@0
|
122
|
nuclear@0
|
123 void app_draw()
|
nuclear@0
|
124 {
|
nuclear@0
|
125 glClear(GL_COLOR_BUFFER_BIT);
|
nuclear@0
|
126
|
nuclear@0
|
127 Mat4 view_matrix;
|
nuclear@0
|
128 view_matrix.pre_rotate_x(deg_to_rad(cam_phi));
|
nuclear@0
|
129 view_matrix.pre_rotate_y(deg_to_rad(cam_theta));
|
nuclear@0
|
130
|
nuclear@0
|
131 glMatrixMode(GL_MODELVIEW);
|
nuclear@0
|
132 glLoadMatrixf(view_matrix[0]);
|
nuclear@0
|
133
|
nuclear@2
|
134 if(show_cubemap) {
|
nuclear@2
|
135 draw_cubemap();
|
nuclear@2
|
136
|
nuclear@2
|
137 glColor3f(0, 0, 0);
|
nuclear@2
|
138 app_print_text(10, 10, "cubemap");
|
nuclear@2
|
139 glColor3f(0, 0.8, 1);
|
nuclear@2
|
140 app_print_text(8, 13, "cubemap");
|
nuclear@2
|
141 } else {
|
nuclear@2
|
142 draw_equilateral();
|
nuclear@2
|
143
|
nuclear@2
|
144 glColor3f(0, 0, 0);
|
nuclear@2
|
145 app_print_text(10, 10, "equilateral");
|
nuclear@2
|
146 glColor3f(1, 0.8, 0);
|
nuclear@2
|
147 app_print_text(8, 13, "equilateral");
|
nuclear@2
|
148 }
|
nuclear@2
|
149 glColor3f(1, 1, 1);
|
nuclear@0
|
150
|
nuclear@0
|
151 app_swap_buffers();
|
nuclear@0
|
152 assert(glGetError() == GL_NO_ERROR);
|
nuclear@0
|
153 }
|
nuclear@0
|
154
|
nuclear@0
|
155 void render_cubemap()
|
nuclear@0
|
156 {
|
nuclear@2
|
157 printf("rendering cubemap %dx%d\n", cube_size, cube_size);
|
nuclear@0
|
158
|
nuclear@2
|
159 float *pixels = new float[cube_size * cube_size * 3];
|
nuclear@2
|
160
|
nuclear@2
|
161 glViewport(0, 0, cube_size, cube_size);
|
nuclear@0
|
162
|
nuclear@0
|
163 Mat4 viewmat[6];
|
nuclear@0
|
164 viewmat[0].rotation_y(deg_to_rad(90)); // +X
|
nuclear@2
|
165 viewmat[1].rotation_y(deg_to_rad(-90)); // -X
|
nuclear@2
|
166 viewmat[2].rotation_x(deg_to_rad(90)); // +Y
|
nuclear@2
|
167 viewmat[2].rotate_y(deg_to_rad(180));
|
nuclear@2
|
168 viewmat[3].rotation_x(deg_to_rad(-90)); // -Y
|
nuclear@2
|
169 viewmat[3].rotate_y(deg_to_rad(180));
|
nuclear@2
|
170 viewmat[4].rotation_y(deg_to_rad(180)); // +Z
|
nuclear@0
|
171
|
nuclear@2
|
172 // this must coincide with the order of GL_TEXTURE_CUBE_MAP_* values
|
nuclear@3
|
173 static const char *fname_pattern[] = {
|
nuclear@3
|
174 "cubemap_px%s",
|
nuclear@3
|
175 "cubemap_nx%s",
|
nuclear@3
|
176 "cubemap_py%s",
|
nuclear@3
|
177 "cubemap_ny%s",
|
nuclear@3
|
178 "cubemap_pz%s",
|
nuclear@3
|
179 "cubemap_nz%s"
|
nuclear@0
|
180 };
|
nuclear@3
|
181 static char fname[64];
|
nuclear@0
|
182
|
nuclear@0
|
183 glMatrixMode(GL_PROJECTION);
|
nuclear@2
|
184 glPushMatrix();
|
nuclear@0
|
185 glLoadIdentity();
|
nuclear@2
|
186 gluPerspective(90, 1.0, 0.5, 500.0);
|
nuclear@2
|
187 glScalef(-1, -1, 1);
|
nuclear@2
|
188
|
nuclear@2
|
189 glBindFramebuffer(GL_FRAMEBUFFER, fbo);
|
nuclear@0
|
190
|
nuclear@0
|
191 for(int i=0; i<6; i++) {
|
nuclear@2
|
192 glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
|
nuclear@2
|
193 GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, cube_tex, 0);
|
nuclear@2
|
194
|
nuclear@0
|
195 glClear(GL_COLOR_BUFFER_BIT);
|
nuclear@0
|
196
|
nuclear@0
|
197 glMatrixMode(GL_MODELVIEW);
|
nuclear@0
|
198 glLoadMatrixf(viewmat[i][0]);
|
nuclear@0
|
199
|
nuclear@2
|
200 draw_equilateral();
|
nuclear@0
|
201
|
nuclear@2
|
202 //glReadPixels(0, 0, cube_size, cube_size, GL_RGB, GL_FLOAT, pixels);
|
nuclear@2
|
203 glGetTexImage(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, GL_RGB, GL_FLOAT, pixels);
|
nuclear@2
|
204 //flip_image(pixels, cube_size, cube_size);
|
nuclear@1
|
205
|
nuclear@3
|
206 sprintf(fname, fname_pattern[i], img_suffix);
|
nuclear@3
|
207 if(img_save_pixels(fname, pixels, cube_size, cube_size, IMG_FMT_RGBF) == -1) {
|
nuclear@3
|
208 fprintf(stderr, "failed to save %dx%d image: %s\n", cube_size, cube_size, fname);
|
nuclear@0
|
209 }
|
nuclear@0
|
210 }
|
nuclear@0
|
211
|
nuclear@2
|
212 glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
nuclear@0
|
213 glViewport(0, 0, win_width, win_height);
|
nuclear@0
|
214
|
nuclear@2
|
215 glMatrixMode(GL_PROJECTION);
|
nuclear@2
|
216 glPopMatrix();
|
nuclear@2
|
217
|
nuclear@0
|
218 delete [] pixels;
|
nuclear@2
|
219
|
nuclear@2
|
220 glBindTexture(GL_TEXTURE_CUBE_MAP, cube_tex);
|
nuclear@2
|
221 glGenerateMipmap(GL_TEXTURE_CUBE_MAP);
|
nuclear@0
|
222 }
|
nuclear@0
|
223
|
nuclear@2
|
224 static void draw_equilateral()
|
nuclear@0
|
225 {
|
nuclear@0
|
226 pano_tex->bind();
|
nuclear@0
|
227 glEnable(GL_TEXTURE_2D);
|
nuclear@0
|
228 pano_mesh->draw();
|
nuclear@0
|
229 glDisable(GL_TEXTURE_2D);
|
nuclear@0
|
230 }
|
nuclear@0
|
231
|
nuclear@2
|
232 static void draw_cubemap()
|
nuclear@0
|
233 {
|
nuclear@2
|
234 glPushAttrib(GL_ENABLE_BIT);
|
nuclear@2
|
235
|
nuclear@2
|
236 glBindTexture(GL_TEXTURE_CUBE_MAP, cube_tex);
|
nuclear@2
|
237 glEnable(GL_TEXTURE_CUBE_MAP);
|
nuclear@2
|
238 glEnable(GL_TEXTURE_GEN_S);
|
nuclear@2
|
239 glEnable(GL_TEXTURE_GEN_T);
|
nuclear@2
|
240 glEnable(GL_TEXTURE_GEN_R);
|
nuclear@2
|
241
|
nuclear@2
|
242 pano_mesh->draw();
|
nuclear@2
|
243
|
nuclear@2
|
244 glPopAttrib();
|
nuclear@0
|
245 }
|
nuclear@0
|
246
|
nuclear@0
|
247 void app_reshape(int x, int y)
|
nuclear@0
|
248 {
|
nuclear@0
|
249 glViewport(0, 0, x, y);
|
nuclear@0
|
250
|
nuclear@0
|
251 glMatrixMode(GL_PROJECTION);
|
nuclear@0
|
252 glLoadIdentity();
|
nuclear@0
|
253 gluPerspective(50.0, (float)x / (float)y, 0.5, 500.0);
|
nuclear@0
|
254
|
nuclear@0
|
255 win_width = x;
|
nuclear@0
|
256 win_height = y;
|
nuclear@0
|
257 }
|
nuclear@0
|
258
|
nuclear@0
|
259 void app_keyboard(int key, bool press)
|
nuclear@0
|
260 {
|
nuclear@0
|
261 if(press) {
|
nuclear@0
|
262 switch(key) {
|
nuclear@0
|
263 case 27:
|
nuclear@0
|
264 app_quit();
|
nuclear@0
|
265 break;
|
nuclear@0
|
266
|
nuclear@1
|
267 case ' ':
|
nuclear@2
|
268 show_cubemap = !show_cubemap;
|
nuclear@2
|
269 app_redisplay();
|
nuclear@1
|
270 break;
|
nuclear@1
|
271
|
nuclear@2
|
272 case 'c':
|
nuclear@0
|
273 render_cubemap();
|
nuclear@0
|
274 break;
|
nuclear@0
|
275 }
|
nuclear@0
|
276 }
|
nuclear@0
|
277 }
|
nuclear@0
|
278
|
nuclear@0
|
279 static float prev_x, prev_y;
|
nuclear@0
|
280 static bool bnstate[16];
|
nuclear@0
|
281
|
nuclear@0
|
282 void app_mouse_button(int bn, bool press, int x, int y)
|
nuclear@0
|
283 {
|
nuclear@0
|
284 if(bn < (int)(sizeof bnstate / sizeof *bnstate)) {
|
nuclear@0
|
285 bnstate[bn] = press;
|
nuclear@0
|
286 }
|
nuclear@0
|
287 prev_x = x;
|
nuclear@0
|
288 prev_y = y;
|
nuclear@0
|
289 }
|
nuclear@0
|
290
|
nuclear@0
|
291 void app_mouse_motion(int x, int y)
|
nuclear@0
|
292 {
|
nuclear@0
|
293 float dx = x - prev_x;
|
nuclear@0
|
294 float dy = y - prev_y;
|
nuclear@0
|
295 prev_x = x;
|
nuclear@0
|
296 prev_y = y;
|
nuclear@0
|
297
|
nuclear@0
|
298 if(!dx && !dy) return;
|
nuclear@0
|
299
|
nuclear@0
|
300 if(bnstate[0]) {
|
nuclear@0
|
301 cam_theta += dx * 0.5;
|
nuclear@0
|
302 cam_phi += dy * 0.5;
|
nuclear@0
|
303
|
nuclear@0
|
304 if(cam_phi < -90) cam_phi = -90;
|
nuclear@0
|
305 if(cam_phi > 90) cam_phi = 90;
|
nuclear@0
|
306 app_redisplay();
|
nuclear@0
|
307 }
|
nuclear@0
|
308 }
|
nuclear@0
|
309
|
nuclear@0
|
310 static bool parse_args(int argc, char **argv)
|
nuclear@0
|
311 {
|
nuclear@0
|
312 for(int i=1; i<argc; i++) {
|
nuclear@0
|
313 if(argv[i][0] == '-') {
|
nuclear@0
|
314 /*
|
nuclear@0
|
315 } else if(strcmp(argv[i], "-help") == 0) {
|
nuclear@0
|
316 printf("usage: %s [options]\noptions:\n", argv[0]);
|
nuclear@0
|
317 printf(" -help: print usage information and exit\n");
|
nuclear@0
|
318 exit(0);
|
nuclear@0
|
319 } else {*/
|
nuclear@0
|
320 fprintf(stderr, "invalid option: %s\n", argv[i]);
|
nuclear@0
|
321 return false;
|
nuclear@0
|
322 //}
|
nuclear@0
|
323 } else {
|
nuclear@0
|
324 if(img_fname) {
|
nuclear@0
|
325 fprintf(stderr, "unexpected option: %s\n", argv[i]);
|
nuclear@0
|
326 return false;
|
nuclear@0
|
327 }
|
nuclear@0
|
328 img_fname = argv[i];
|
nuclear@0
|
329 }
|
nuclear@0
|
330 }
|
nuclear@0
|
331
|
nuclear@0
|
332 return true;
|
nuclear@0
|
333 }
|
nuclear@1
|
334
|
nuclear@1
|
335 static void flip_image(float *pixels, int xsz, int ysz)
|
nuclear@1
|
336 {
|
nuclear@1
|
337 float *top_ptr = pixels;
|
nuclear@1
|
338 float *bot_ptr = pixels + xsz * (ysz - 1) * 3;
|
nuclear@1
|
339 float *line = new float[xsz * 3];
|
nuclear@1
|
340 int scansz = xsz * 3 * sizeof(float);
|
nuclear@1
|
341
|
nuclear@1
|
342 for(int i=0; i<ysz / 2; i++) {
|
nuclear@1
|
343 memcpy(line, top_ptr, scansz);
|
nuclear@1
|
344 memcpy(top_ptr, bot_ptr, scansz);
|
nuclear@1
|
345 memcpy(bot_ptr, line, scansz);
|
nuclear@1
|
346 top_ptr += xsz * 3;
|
nuclear@1
|
347 bot_ptr -= xsz * 3;
|
nuclear@1
|
348 }
|
nuclear@1
|
349
|
nuclear@1
|
350 delete [] line;
|
nuclear@1
|
351 }
|