516 lines
12 KiB
C
516 lines
12 KiB
C
/*
|
|
* .obj file viewer based on "smooth" by Nate Robins, 1997
|
|
*
|
|
* Brian Paul
|
|
* 1 Oct 2009
|
|
*/
|
|
|
|
#include <math.h>
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <assert.h>
|
|
#include <stdarg.h>
|
|
#include <GL/glew.h>
|
|
#include <GL/glut.h>
|
|
#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;
|
|
}
|