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