1/* 2 * .obj file viewer based on "smooth" by Nate Robins, 1997 3 * 4 * Brian Paul 5 * 1 Oct 2009 6 */ 7 8#include <math.h> 9#include <stdlib.h> 10#include <stdio.h> 11#include <assert.h> 12#include <stdarg.h> 13#include <GL/glew.h> 14#include "glut_wrap.h" 15#include "glm.h" 16#include "skybox.h" 17#include "trackball.h" 18#include "shaderutil.h" 19 20 21static char *Model_file = NULL; /* name of the obect file */ 22static GLMmodel *Model; 23static GLfloat Scale = 4.0; /* scaling factor */ 24static GLboolean Performance = GL_FALSE; 25static GLboolean Stats = GL_FALSE; 26static GLboolean Animate = GL_TRUE; 27static GLuint SkyboxTex; 28static GLboolean Skybox = GL_TRUE; 29static GLboolean Cull = GL_TRUE; 30static GLboolean WireFrame = GL_FALSE; 31static GLenum FrontFace = GL_CCW; 32static GLfloat Yrot = 0.0; 33static GLint WinWidth = 1024, WinHeight = 768; 34static GLuint NumInstances = 1; 35 36 37 38typedef struct 39{ 40 float CurQuat[4]; 41 float Distance; 42 /* When mouse is moving: */ 43 GLboolean Rotating, Translating; 44 GLint StartX, StartY; 45 float StartDistance; 46} ViewInfo; 47 48static ViewInfo View; 49 50static void 51InitViewInfo(ViewInfo *view) 52{ 53 view->Rotating = GL_FALSE; 54 view->Translating = GL_FALSE; 55 view->StartX = view->StartY = 0; 56 view->Distance = 12.0; 57 view->StartDistance = 0.0; 58 view->CurQuat[0] = 0.0; 59 view->CurQuat[1] = 1.0; 60 view->CurQuat[2] = 0.0; 61 view->CurQuat[3] = 0.0; 62} 63 64 65 66/* text: general purpose text routine. draws a string according to 67 * format in a stroke font at x, y after scaling it by the scale 68 * specified (scale is in window-space (lower-left origin) pixels). 69 * 70 * x - position in x (in window-space) 71 * y - position in y (in window-space) 72 * scale - scale in pixels 73 * format - as in printf() 74 */ 75static void 76text(GLuint x, GLuint y, GLfloat scale, char* format, ...) 77{ 78 va_list args; 79 char buffer[255], *p; 80 GLfloat font_scale = 119.05 + 33.33; 81 82 va_start(args, format); 83 vsprintf(buffer, format, args); 84 va_end(args); 85 86 glMatrixMode(GL_PROJECTION); 87 glPushMatrix(); 88 glLoadIdentity(); 89 gluOrtho2D(0, glutGet(GLUT_WINDOW_WIDTH), 0, glutGet(GLUT_WINDOW_HEIGHT)); 90 91 glMatrixMode(GL_MODELVIEW); 92 glPushMatrix(); 93 glLoadIdentity(); 94 95 glPushAttrib(GL_ENABLE_BIT); 96 glDisable(GL_LIGHTING); 97 glDisable(GL_TEXTURE_2D); 98 glDisable(GL_DEPTH_TEST); 99 glTranslatef(x, y, 0.0); 100 101 glScalef(scale/font_scale, scale/font_scale, scale/font_scale); 102 103 for(p = buffer; *p; p++) 104 glutStrokeCharacter(GLUT_STROKE_ROMAN, *p); 105 106 glPopAttrib(); 107 108 glPopMatrix(); 109 glMatrixMode(GL_PROJECTION); 110 glPopMatrix(); 111 glMatrixMode(GL_MODELVIEW); 112} 113 114 115static float 116ComputeFPS(void) 117{ 118 static double t0 = -1.0; 119 static int frames = 0; 120 double t = glutGet(GLUT_ELAPSED_TIME) / 1000.0; 121 static float fps = 0; 122 123 frames++; 124 125 if (t0 < 0.0) { 126 t0 = t; 127 fps = 0.0; 128 } 129 else if (t - t0 >= 4.0) { 130 fps = (frames / (t - t0) + 0.5); 131 t0 = t; 132 frames = 0; 133 return fps; 134 } 135 136 return 0.0; 137} 138 139 140static void 141init_model(void) 142{ 143 float objScale; 144 145 /* read in the model */ 146 Model = glmReadOBJ(Model_file); 147 objScale = glmUnitize(Model); 148 glmFacetNormals(Model); 149 if (Model->numnormals == 0) { 150 GLfloat smoothing_angle = 90.0; 151 printf("Generating normals.\n"); 152 glmVertexNormals(Model, smoothing_angle); 153 } 154 155 glmLoadTextures(Model); 156 glmReIndex(Model); 157 glmMakeVBOs(Model); 158 if (0) 159 glmPrint(Model); 160} 161 162static void 163init_skybox(void) 164{ 165 SkyboxTex = LoadSkyBoxCubeTexture("alpine_east.rgb", 166 "alpine_west.rgb", 167 "alpine_up.rgb", 168 "alpine_down.rgb", 169 "alpine_south.rgb", 170 "alpine_north.rgb"); 171 glmSpecularTexture(Model, SkyboxTex); 172} 173 174 175static void 176init_gfx(void) 177{ 178 glEnable(GL_DEPTH_TEST); 179 glEnable(GL_CULL_FACE); 180 glEnable(GL_NORMALIZE); 181 glClearColor(0.3, 0.3, 0.9, 0.0); 182} 183 184 185static void 186reshape(int width, int height) 187{ 188 float ar = 0.5 * (float) width / (float) height; 189 190 WinWidth = width; 191 WinHeight = height; 192 193 glViewport(0, 0, width, height); 194 195 glMatrixMode(GL_PROJECTION); 196 glLoadIdentity(); 197 glFrustum(-ar, ar, -0.5, 0.5, 1.0, 300.0); 198 glMatrixMode(GL_MODELVIEW); 199 glLoadIdentity(); 200 glTranslatef(0.0, 0.0, -3.0); 201} 202 203 204static void 205Idle(void) 206{ 207 float q[4]; 208 trackball(q, 100, 0, 99.99, 0); 209 add_quats(q, View.CurQuat, View.CurQuat); 210 211 glutPostRedisplay(); 212} 213 214 215static void 216display(void) 217{ 218 GLfloat rot[4][4]; 219 float fps; 220 221 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); 222 223 glPushMatrix(); 224 glTranslatef(0.0, 0.0, -View.Distance); 225 glRotatef(Yrot, 0, 1, 0); 226 build_rotmatrix(rot, View.CurQuat); 227 glMultMatrixf(&rot[0][0]); 228 glScalef(Scale, Scale, Scale ); 229 230 glUseProgram(0); 231 232 if (Skybox) 233 DrawSkyBoxCubeTexture(SkyboxTex); 234 235 if (WireFrame) 236 glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); 237 else 238 glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); 239 240 if (Cull) 241 glEnable(GL_CULL_FACE); 242 else 243 glDisable(GL_CULL_FACE); 244 245 if (NumInstances == 1) { 246 glmDrawVBO(Model); 247 } 248 else { 249 /* draw > 1 instance */ 250 float dr = 360.0 / NumInstances; 251 float r; 252 for (r = 0.0; r < 360.0; r += dr) { 253 glPushMatrix(); 254 glRotatef(r, 0, 1, 0); 255 glTranslatef(1.4, 0.0, 0.0); 256 glmDrawVBO(Model); 257 glPopMatrix(); 258 } 259 } 260 261 glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); 262 glDisable(GL_CULL_FACE); 263 264 glPopMatrix(); 265 266 if (Stats) { 267 glColor3f(1.0, 1.0, 1.0); 268 text(5, glutGet(GLUT_WINDOW_HEIGHT) - (5+20*1), 20, "%s", 269 Model->pathname); 270 text(5, glutGet(GLUT_WINDOW_HEIGHT) - (5+20*2), 20, "%d vertices", 271 Model->numvertices); 272 text(5, glutGet(GLUT_WINDOW_HEIGHT) - (5+20*3), 20, "%d triangles", 273 Model->numtriangles); 274 text(5, glutGet(GLUT_WINDOW_HEIGHT) - (5+20*4), 20, "%d normals", 275 Model->numnormals); 276 text(5, glutGet(GLUT_WINDOW_HEIGHT) - (5+20*5), 20, "%d texcoords", 277 Model->numtexcoords); 278 text(5, glutGet(GLUT_WINDOW_HEIGHT) - (5+20*6), 20, "%d groups", 279 Model->numgroups); 280 text(5, glutGet(GLUT_WINDOW_HEIGHT) - (5+20*7), 20, "%d materials", 281 Model->nummaterials); 282 } 283 284 glutSwapBuffers(); 285 286 fps = ComputeFPS(); 287 if (fps) { 288 printf("%f FPS\n", fps); 289 fflush(stdout); 290 } 291} 292 293 294static void 295keyboard(unsigned char key, int x, int y) 296{ 297 switch (key) { 298 case 'h': 299 printf("help\n\n"); 300 printf("a - Toggle animation\n"); 301 printf("d/D - Decrease/Incrase number of models\n"); 302 printf("w - Toggle wireframe/filled\n"); 303 printf("c - Toggle culling\n"); 304 printf("n - Toggle facet/smooth normal\n"); 305 printf("r - Reverse polygon winding\n"); 306 printf("p - Toggle performance indicator\n"); 307 printf("s - Toggle skybox\n"); 308 printf("z/Z - Scale model smaller/larger\n"); 309 printf("i - Show model info/stats\n"); 310 printf("q/escape - Quit\n\n"); 311 break; 312 case 'a': 313 Animate = !Animate; 314 if (Animate) 315 glutIdleFunc(Idle); 316 else 317 glutIdleFunc(NULL); 318 break; 319 case 'd': 320 if (NumInstances > 1) 321 NumInstances--; 322 break; 323 case 'D': 324 NumInstances++; 325 break; 326 case 'i': 327 Stats = !Stats; 328 break; 329 case 'p': 330 Performance = !Performance; 331 break; 332 case 'w': 333 WireFrame = !WireFrame; 334 break; 335 case 'c': 336 Cull = !Cull; 337 printf("Polygon culling: %d\n", Cull); 338 break; 339 case 'r': 340 if (FrontFace == GL_CCW) 341 FrontFace = GL_CW; 342 else 343 FrontFace = GL_CCW; 344 glFrontFace(FrontFace); 345 printf("Front face:: %s\n", FrontFace == GL_CCW ? "CCW" : "CW"); 346 break; 347 case 's': 348 Skybox = !Skybox; 349 if (Skybox) 350 glmSpecularTexture(Model, SkyboxTex); 351 else 352 glmSpecularTexture(Model, 0); 353 break; 354 case 'z': 355 Scale *= 0.9; 356 break; 357 case 'Z': 358 Scale *= 1.1; 359 break; 360 case 'q': 361 case 27: 362 exit(0); 363 break; 364 } 365 366 glutPostRedisplay(); 367} 368 369 370static void 371menu(int item) 372{ 373 keyboard((unsigned char)item, 0, 0); 374} 375 376 377/** 378 * Handle mouse button. 379 */ 380static void 381Mouse(int button, int state, int x, int y) 382{ 383 if (button == GLUT_LEFT_BUTTON) { 384 if (state == GLUT_DOWN) { 385 View.StartX = x; 386 View.StartY = y; 387 View.Rotating = GL_TRUE; 388 } 389 else if (state == GLUT_UP) { 390 View.Rotating = GL_FALSE; 391 } 392 } 393 else if (button == GLUT_MIDDLE_BUTTON) { 394 if (state == GLUT_DOWN) { 395 View.StartX = x; 396 View.StartY = y; 397 View.StartDistance = View.Distance; 398 View.Translating = GL_TRUE; 399 } 400 else if (state == GLUT_UP) { 401 View.Translating = GL_FALSE; 402 } 403 } 404} 405 406 407/** 408 * Handle mouse motion 409 */ 410static void 411Motion(int x, int y) 412{ 413 int i; 414 if (View.Rotating) { 415 float x0 = (2.0 * View.StartX - WinWidth) / WinWidth; 416 float y0 = (WinHeight - 2.0 * View.StartY) / WinHeight; 417 float x1 = (2.0 * x - WinWidth) / WinWidth; 418 float y1 = (WinHeight - 2.0 * y) / WinHeight; 419 float q[4]; 420 421 trackball(q, x0, y0, x1, y1); 422 View.StartX = x; 423 View.StartY = y; 424 for (i = 0; i < 1; i++) 425 add_quats(q, View.CurQuat, View.CurQuat); 426 427 glutPostRedisplay(); 428 } 429 else if (View.Translating) { 430 float dz = 0.02 * (y - View.StartY); 431 View.Distance = View.StartDistance + dz; 432 glutPostRedisplay(); 433 } 434} 435 436 437static void 438DoFeatureChecks(void) 439{ 440 if (!GLEW_VERSION_2_0) { 441 /* check for individual extensions */ 442 if (!GLEW_ARB_texture_cube_map) { 443 printf("Sorry, GL_ARB_texture_cube_map is required.\n"); 444 exit(1); 445 } 446 if (!GLEW_ARB_vertex_shader) { 447 printf("Sorry, GL_ARB_vertex_shader is required.\n"); 448 exit(1); 449 } 450 if (!GLEW_ARB_fragment_shader) { 451 printf("Sorry, GL_ARB_fragment_shader is required.\n"); 452 exit(1); 453 } 454 if (!GLEW_ARB_vertex_buffer_object) { 455 printf("Sorry, GL_ARB_vertex_buffer_object is required.\n"); 456 exit(1); 457 } 458 } 459 if (!ShadersSupported()) { 460 printf("Sorry, GLSL is required\n"); 461 exit(1); 462 } 463} 464 465 466int 467main(int argc, char** argv) 468{ 469 glutInitWindowSize(WinWidth, WinHeight); 470 glutInit(&argc, argv); 471 472 if (argc > 1) { 473 Model_file = argv[1]; 474 } 475 if (!Model_file) { 476 fprintf(stderr, "usage: objview file.obj\n"); 477 fprintf(stderr, "(using default bunny.obj)\n"); 478 Model_file = "bunny.obj"; 479 } 480 481 glutInitDisplayMode(GLUT_RGB | GLUT_DEPTH | GLUT_DOUBLE); 482 glutCreateWindow("objview"); 483 484 glewInit(); 485 486 DoFeatureChecks(); 487 488 glutReshapeFunc(reshape); 489 glutDisplayFunc(display); 490 glutKeyboardFunc(keyboard); 491 glutMouseFunc(Mouse); 492 glutMotionFunc(Motion); 493 if (Animate) 494 glutIdleFunc(Idle); 495 496 glutCreateMenu(menu); 497 glutAddMenuEntry("[a] Toggle animate", 'a'); 498 glutAddMenuEntry("[d] Fewer models", 'd'); 499 glutAddMenuEntry("[D] More models", 'D'); 500 glutAddMenuEntry("[w] Toggle wireframe/filled", 'w'); 501 glutAddMenuEntry("[c] Toggle culling on/off", 'c'); 502 glutAddMenuEntry("[r] Reverse polygon winding", 'r'); 503 glutAddMenuEntry("[z] Scale model smaller", 'z'); 504 glutAddMenuEntry("[Z] Scale model larger", 'Z'); 505 glutAddMenuEntry("[p] Toggle performance indicator", 'p'); 506 glutAddMenuEntry("[i] Show model stats", 'i'); 507 glutAddMenuEntry("", 0); 508 glutAddMenuEntry("[q] Quit", 27); 509 glutAttachMenu(GLUT_RIGHT_BUTTON); 510 511 InitViewInfo(&View); 512 513 init_model(); 514 init_skybox(); 515 init_gfx(); 516 517 glutMainLoop(); 518 519 return 0; 520} 521