/* * .obj file viewer based on "smooth" by Nate Robins, 1997 * * Brian Paul * 1 Oct 2009 */ #include #include #include #include #include #include #include #include "glm.h" #include "readtex.h" #include "skybox.h" #include "trackball.h" static char *Model_file = NULL; /* name of the obect file */ static GLMmodel *Model; static GLfloat Scale = 4.0; /* scaling factor */ static GLboolean Performance = GL_FALSE; static GLboolean Stats = GL_FALSE; static GLboolean Animate = GL_TRUE; static GLuint SkyboxTex; static GLboolean Skybox = GL_TRUE; static GLboolean Cull = GL_TRUE; static GLboolean WireFrame = GL_FALSE; static GLenum FrontFace = GL_CCW; static GLfloat Yrot = 0.0; static GLint WinWidth = 1024, WinHeight = 768; static GLuint NumInstances = 1; typedef struct { float CurQuat[4]; float Distance; /* When mouse is moving: */ GLboolean Rotating, Translating; GLint StartX, StartY; float StartDistance; } ViewInfo; static ViewInfo View; static void InitViewInfo(ViewInfo *view) { view->Rotating = GL_FALSE; view->Translating = GL_FALSE; view->StartX = view->StartY = 0; view->Distance = 12.0; view->StartDistance = 0.0; view->CurQuat[0] = 0.0; view->CurQuat[1] = 1.0; view->CurQuat[2] = 0.0; view->CurQuat[3] = 0.0; } /* text: general purpose text routine. draws a string according to * format in a stroke font at x, y after scaling it by the scale * specified (scale is in window-space (lower-left origin) pixels). * * x - position in x (in window-space) * y - position in y (in window-space) * scale - scale in pixels * format - as in printf() */ static void text(GLuint x, GLuint y, GLfloat scale, char* format, ...) { va_list args; char buffer[255], *p; GLfloat font_scale = 119.05 + 33.33; va_start(args, format); vsprintf(buffer, format, args); va_end(args); glMatrixMode(GL_PROJECTION); glPushMatrix(); glLoadIdentity(); gluOrtho2D(0, glutGet(GLUT_WINDOW_WIDTH), 0, glutGet(GLUT_WINDOW_HEIGHT)); glMatrixMode(GL_MODELVIEW); glPushMatrix(); glLoadIdentity(); glPushAttrib(GL_ENABLE_BIT); glDisable(GL_LIGHTING); glDisable(GL_TEXTURE_2D); glDisable(GL_DEPTH_TEST); glTranslatef(x, y, 0.0); glScalef(scale/font_scale, scale/font_scale, scale/font_scale); for(p = buffer; *p; p++) glutStrokeCharacter(GLUT_STROKE_ROMAN, *p); glPopAttrib(); glPopMatrix(); glMatrixMode(GL_PROJECTION); glPopMatrix(); glMatrixMode(GL_MODELVIEW); } static float ComputeFPS(void) { static double t0 = -1.0; static int frames = 0; double t = glutGet(GLUT_ELAPSED_TIME) / 1000.0; static float fps = 0; frames++; if (t0 < 0.0) { t0 = t; fps = 0.0; } else if (t - t0 >= 4.0) { fps = (frames / (t - t0) + 0.5); t0 = t; frames = 0; return fps; } return 0.0; } static void init_model(void) { float objScale; /* read in the model */ Model = glmReadOBJ(Model_file); objScale = glmUnitize(Model); glmFacetNormals(Model); if (Model->numnormals == 0) { GLfloat smoothing_angle = 90.0; printf("Generating normals.\n"); glmVertexNormals(Model, smoothing_angle); } glmLoadTextures(Model); glmReIndex(Model); glmMakeVBOs(Model); if (0) glmPrint(Model); } static void init_skybox(void) { SkyboxTex = LoadSkyBoxCubeTexture("alpine_east.rgb", "alpine_west.rgb", "alpine_up.rgb", "alpine_down.rgb", "alpine_south.rgb", "alpine_north.rgb"); glmSpecularTexture(Model, SkyboxTex); } static void init_gfx(void) { glEnable(GL_DEPTH_TEST); glEnable(GL_CULL_FACE); glEnable(GL_NORMALIZE); glClearColor(0.3, 0.3, 0.9, 0.0); } static void reshape(int width, int height) { float ar = 0.5 * (float) width / (float) height; WinWidth = width; WinHeight = height; glViewport(0, 0, width, height); glMatrixMode(GL_PROJECTION); glLoadIdentity(); glFrustum(-ar, ar, -0.5, 0.5, 1.0, 300.0); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); glTranslatef(0.0, 0.0, -3.0); } static void Idle(void) { float q[4]; trackball(q, 100, 0, 99.99, 0); add_quats(q, View.CurQuat, View.CurQuat); glutPostRedisplay(); } static void display(void) { GLfloat rot[4][4]; float fps; glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glPushMatrix(); glTranslatef(0.0, 0.0, -View.Distance); glRotatef(Yrot, 0, 1, 0); build_rotmatrix(rot, View.CurQuat); glMultMatrixf(&rot[0][0]); glScalef(Scale, Scale, Scale ); glUseProgram(0); if (Skybox) DrawSkyBoxCubeTexture(SkyboxTex); if (WireFrame) glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); else glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); if (Cull) glEnable(GL_CULL_FACE); else glDisable(GL_CULL_FACE); if (NumInstances == 1) { glmDrawVBO(Model); } else { /* draw > 1 instance */ float dr = 360.0 / NumInstances; float r; for (r = 0.0; r < 360.0; r += dr) { glPushMatrix(); glRotatef(r, 0, 1, 0); glTranslatef(1.4, 0.0, 0.0); glmDrawVBO(Model); glPopMatrix(); } } glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); glDisable(GL_CULL_FACE); glPopMatrix(); if (Stats) { glColor3f(1.0, 1.0, 1.0); text(5, glutGet(GLUT_WINDOW_HEIGHT) - (5+20*1), 20, "%s", Model->pathname); text(5, glutGet(GLUT_WINDOW_HEIGHT) - (5+20*2), 20, "%d vertices", Model->numvertices); text(5, glutGet(GLUT_WINDOW_HEIGHT) - (5+20*3), 20, "%d triangles", Model->numtriangles); text(5, glutGet(GLUT_WINDOW_HEIGHT) - (5+20*4), 20, "%d normals", Model->numnormals); text(5, glutGet(GLUT_WINDOW_HEIGHT) - (5+20*5), 20, "%d texcoords", Model->numtexcoords); text(5, glutGet(GLUT_WINDOW_HEIGHT) - (5+20*6), 20, "%d groups", Model->numgroups); text(5, glutGet(GLUT_WINDOW_HEIGHT) - (5+20*7), 20, "%d materials", Model->nummaterials); } glutSwapBuffers(); fps = ComputeFPS(); if (fps) printf("%f FPS\n", fps); } static void keyboard(unsigned char key, int x, int y) { switch (key) { case 'h': printf("help\n\n"); printf("a - Toggle animation\n"); printf("d/D - Decrease/Incrase number of models\n"); printf("w - Toggle wireframe/filled\n"); printf("c - Toggle culling\n"); printf("n - Toggle facet/smooth normal\n"); printf("r - Reverse polygon winding\n"); printf("p - Toggle performance indicator\n"); printf("s - Toggle skybox\n"); printf("z/Z - Scale model smaller/larger\n"); printf("i - Show model info/stats\n"); printf("q/escape - Quit\n\n"); break; case 'a': Animate = !Animate; if (Animate) glutIdleFunc(Idle); else glutIdleFunc(NULL); break; case 'd': if (NumInstances > 1) NumInstances--; break; case 'D': NumInstances++; break; case 'i': Stats = !Stats; break; case 'p': Performance = !Performance; break; case 'w': WireFrame = !WireFrame; break; case 'c': Cull = !Cull; printf("Polygon culling: %d\n", Cull); break; case 'r': if (FrontFace == GL_CCW) FrontFace = GL_CW; else FrontFace = GL_CCW; glFrontFace(FrontFace); printf("Front face:: %s\n", FrontFace == GL_CCW ? "CCW" : "CW"); break; case 's': Skybox = !Skybox; if (Skybox) glmSpecularTexture(Model, SkyboxTex); else glmSpecularTexture(Model, 0); break; case 'z': Scale *= 0.9; break; case 'Z': Scale *= 1.1; break; case 'q': case 27: exit(0); break; } glutPostRedisplay(); } static void menu(int item) { keyboard((unsigned char)item, 0, 0); } /** * Handle mouse button. */ static void Mouse(int button, int state, int x, int y) { if (button == GLUT_LEFT_BUTTON) { if (state == GLUT_DOWN) { View.StartX = x; View.StartY = y; View.Rotating = GL_TRUE; } else if (state == GLUT_UP) { View.Rotating = GL_FALSE; } } else if (button == GLUT_MIDDLE_BUTTON) { if (state == GLUT_DOWN) { View.StartX = x; View.StartY = y; View.StartDistance = View.Distance; View.Translating = GL_TRUE; } else if (state == GLUT_UP) { View.Translating = GL_FALSE; } } } /** * Handle mouse motion */ static void Motion(int x, int y) { int i; if (View.Rotating) { float x0 = (2.0 * View.StartX - WinWidth) / WinWidth; float y0 = (WinHeight - 2.0 * View.StartY) / WinHeight; float x1 = (2.0 * x - WinWidth) / WinWidth; float y1 = (WinHeight - 2.0 * y) / WinHeight; float q[4]; trackball(q, x0, y0, x1, y1); View.StartX = x; View.StartY = y; for (i = 0; i < 1; i++) add_quats(q, View.CurQuat, View.CurQuat); glutPostRedisplay(); } else if (View.Translating) { float dz = 0.02 * (y - View.StartY); View.Distance = View.StartDistance + dz; glutPostRedisplay(); } } static void DoFeatureChecks(void) { char *version = (char *) glGetString(GL_VERSION); if (version[0] == '1') { /* check for individual extensions */ if (!glutExtensionSupported("GL_ARB_texture_cube_map")) { printf("Sorry, GL_ARB_texture_cube_map is required.\n"); exit(1); } if (!glutExtensionSupported("GL_ARB_vertex_shader")) { printf("Sorry, GL_ARB_vertex_shader is required.\n"); exit(1); } if (!glutExtensionSupported("GL_ARB_fragment_shader")) { printf("Sorry, GL_ARB_fragment_shader is required.\n"); exit(1); } if (!glutExtensionSupported("GL_ARB_vertex_buffer_object")) { printf("Sorry, GL_ARB_vertex_buffer_object is required.\n"); exit(1); } } } int main(int argc, char** argv) { glutInitWindowSize(WinWidth, WinHeight); glutInit(&argc, argv); if (argc > 1) { Model_file = argv[1]; } if (!Model_file) { fprintf(stderr, "usage: objview file.obj\n"); fprintf(stderr, "(using default bunny.obj)\n"); Model_file = "bunny.obj"; } glutInitDisplayMode(GLUT_RGB | GLUT_DEPTH | GLUT_DOUBLE); glutCreateWindow("objview"); glewInit(); DoFeatureChecks(); glutReshapeFunc(reshape); glutDisplayFunc(display); glutKeyboardFunc(keyboard); glutMouseFunc(Mouse); glutMotionFunc(Motion); if (Animate) glutIdleFunc(Idle); glutCreateMenu(menu); glutAddMenuEntry("[a] Toggle animate", 'a'); glutAddMenuEntry("[d] Fewer models", 'd'); glutAddMenuEntry("[D] More models", 'D'); glutAddMenuEntry("[w] Toggle wireframe/filled", 'w'); glutAddMenuEntry("[c] Toggle culling on/off", 'c'); glutAddMenuEntry("[r] Reverse polygon winding", 'r'); glutAddMenuEntry("[z] Scale model smaller", 'z'); glutAddMenuEntry("[Z] Scale model larger", 'Z'); glutAddMenuEntry("[p] Toggle performance indicator", 'p'); glutAddMenuEntry("[i] Show model stats", 'i'); glutAddMenuEntry("", 0); glutAddMenuEntry("[q] Quit", 27); glutAttachMenu(GLUT_RIGHT_BUTTON); InitViewInfo(&View); init_model(); init_skybox(); init_gfx(); glutMainLoop(); return 0; }