/******************************Module*Header*******************************\ * Module Name: nff.c * * Parse the Wavefront OBJ format. * * Documentation on OBJ available from: * * Wavefront Technologies, 530 E. Montecito St., Santa Barbara, CA 93103, * Phone: 805-962-8117. * * Murray, J. D. and van Ryper, W., _Encyclopedia of Graphics File Formats_, * O'Reilly & Associates, 1994, pp. 727-734. * * Created: 15-Mar-1995 23:27:08 * Author: Gilman Wong [gilmanw] * * Copyright (c) 1995 Microsoft Corporation * \**************************************************************************/ #include #include #include #include #include #include "global.h" #include "nff.h" #define STATIC typedef struct tagOBJPRIV { BOOL bInList; // TRUE while compiling display list // Vertex arrays MyXYZ *pxyzGeoVert; // array of geometric vertices MyXYZ *pxyzTexVert; // array of texture vertices MyXYZ *pxyzNorm; // array of vertex normals // Number allocated for each array GLuint numGeoVert; // total number of geometric vertices GLuint numTexVert; // total number of texture vertices GLuint numNorm; // total number of normals // Number of vertices currently in each array GLuint curGeoVert; // current number of geometric vertices GLuint curTexVert; // current number of texture vertices GLuint curNorm; // current number of normals GLfloat rMax; } OBJPRIV; GLint maxLights; STATIC void parseObjFile(SCENE *, LPTSTR); STATIC void DoDisplayList(SCENE *); STATIC void EndDisplayList(SCENE *); STATIC void parseObjNormal(SCENE *scene, FILE *file, char *ach, int n); STATIC void parseObjTextureVertex(SCENE *scene, FILE *file, char *ach, int n); STATIC void parseObjVertex(SCENE *scene, FILE *file, char *ach, int n); STATIC void parseObjFace(SCENE *scene, FILE *file, char *ach, int n); STATIC void fakeObjLight(SCENE *scene, GLfloat x, GLfloat y, GLfloat z); SCENE *ObjOpenScene(LPTSTR lpstr) { SCENE *scene; scene = (SCENE *) LocalAlloc(LMEM_FIXED, sizeof(SCENE) + sizeof(OBJPRIV)); if (scene) { OBJPRIV *objPriv = (OBJPRIV *) (scene + 1); glGetIntegerv(GL_MAX_LIGHTS, &maxLights); scene->xyzFrom.x = 0.0; scene->xyzFrom.y = 0.0; //scene->xyzFrom.z = 5.0; scene->xyzAt.x = 0.0; scene->xyzAt.y = 0.0; scene->xyzAt.z = 0.0; scene->xyzUp.x = 0.0; scene->xyzUp.y = 1.0; scene->xyzUp.z = 0.0; scene->ViewAngle = 45.0f; scene->Hither = 0.1f; //scene->Yon = 100.0f; scene->AspectRatio = 1.0f; scene->szWindow.cx = 400; scene->szWindow.cy = 400; scene->rgbaClear.r = (GLfloat) 0.0; // default is black scene->rgbaClear.g = (GLfloat) 0.0; scene->rgbaClear.b = (GLfloat) 0.0; scene->rgbaClear.a = (GLfloat) 1.0; scene->Lights.count = 0; scene->Lights.listBase = 1; scene->Objects.count = 1; scene->Objects.listBase = maxLights + 1; scene->pvData = (VOID *) objPriv; objPriv->bInList = FALSE; objPriv->pxyzGeoVert = (MyXYZ *) NULL; objPriv->pxyzTexVert = (MyXYZ *) NULL; objPriv->pxyzNorm = (MyXYZ *) NULL; objPriv->numGeoVert = 0; objPriv->numTexVert = 0; objPriv->numNorm = 0; objPriv->curGeoVert = 0; objPriv->curTexVert = 0; objPriv->curNorm = 0; objPriv->rMax = (GLfloat) 0.0; LBprintf("================================"); LBprintf("Parsing OBJ file, please wait..."); LBprintf("================================"); parseObjFile(scene, lpstr); LBprintf("Here we go!"); scene->xyzFrom.z = 2.0f * objPriv->rMax * tan(90.0f - scene->ViewAngle); scene->Yon = (scene->xyzAt.z - scene->xyzFrom.z) * (scene->xyzAt.z - scene->xyzFrom.z); scene->Yon = sqrt(scene->Yon) * 2.5f; fakeObjLight(scene, objPriv->rMax, objPriv->rMax, scene->xyzFrom.z); } else { LBprintf("ObjOpenScene: memory allocation failure"); } return scene; } STATIC void parseObjFile(SCENE *scene, LPTSTR lpstr) { FILE *file; char ach[512]; char *pch; OBJPRIV *objPriv = (OBJPRIV *) scene->pvData; file = fopen(lpstr, "rt"); if (file) { BOOL bKeepGoing = TRUE; // Do a quick first pass through the file so we know how big to // make the arrays. do { if (fgets(ach, sizeof(ach), file) != NULL) { // Skip leading whitespace. Some input files have this. pch = ach; while (*pch && isspace(*pch)) pch++; switch(pch[0]) { case 'V': case 'v': switch(pch[1]) { case 'N': case 'n': objPriv->numNorm++; break; case 'T': case 't': //objPriv->numTexVert++; break; case 'P': case 'p': break; default: objPriv->numGeoVert++; break; } break; default: break; } } else { bKeepGoing = FALSE; //LBprintf("possible EOF"); } } while (bKeepGoing || !feof(file)); rewind(file); // Allocate the arrays. objPriv->pxyzGeoVert = (MyXYZ *) LocalAlloc(LMEM_FIXED, sizeof(MyXYZ) * (objPriv->numNorm + objPriv->numTexVert + objPriv->numGeoVert)); objPriv->pxyzTexVert = objPriv->pxyzGeoVert + objPriv->numGeoVert; objPriv->pxyzNorm = objPriv->pxyzTexVert + objPriv->numTexVert; // Parse the file for real. bKeepGoing = TRUE; do { if (fgets(ach, sizeof(ach), file) != NULL) { // Skip leading whitespace. Some input files have this. pch = ach; while (*pch && isspace(*pch)) pch++; switch(pch[0]) { case 'G': case 'g': //LBprintf(ach); break; case 'V': case 'v': switch(pch[1]) { case 'N': case 'n': parseObjNormal(scene, file, pch, sizeof(ach) - (pch - ach)); break; case 'T': case 't': //parseObjTextureVertex(scene, file, pch, sizeof(ach) - (pch - ach)); break; case 'P': case 'p': break; default: parseObjVertex(scene, file, pch, sizeof(ach) - (pch - ach)); break; } break; case 'f': case 'F': switch(pch[1]) { case 'O': case 'o': parseObjFace(scene, file, &pch[1], sizeof(ach) - (pch - ach) - 1); break; default: parseObjFace(scene, file, pch, sizeof(ach) - (pch - ach)); break; } break; case '#': LBprintf(pch); break; default: //LBprintf("unknown: %s", ach); break; } } else { bKeepGoing = FALSE; //LBprintf("possible EOF"); } } while (bKeepGoing || !feof(file)); fclose(file); EndDisplayList(scene); } else { LBprintf("fopen failed %s", lpstr); LBprintf("USAGE: viewer [OBJ filename].OBJ"); } } STATIC void DoDisplayList(SCENE *scene) { if (!((OBJPRIV *)scene->pvData)->bInList) { ((OBJPRIV *)scene->pvData)->bInList = TRUE; glNewList(scene->Objects.listBase, GL_COMPILE); //LBprintf("BEGIN DISPLAY LIST"); } } STATIC void EndDisplayList(SCENE *scene) { if (((OBJPRIV *)scene->pvData)->bInList) { ((OBJPRIV *)scene->pvData)->bInList = FALSE; glEndList(); //LBprintf("END DISPLAY LIST"); } } STATIC void parseObjNormal(SCENE *scene, FILE *file, char *ach, int n) { OBJPRIV *objPriv = (OBJPRIV *) scene->pvData; sscanf(ach, "vn %g %g %g", &objPriv->pxyzNorm[objPriv->curNorm].x, &objPriv->pxyzNorm[objPriv->curNorm].y, &objPriv->pxyzNorm[objPriv->curNorm].z); objPriv->curNorm++; } STATIC void parseObjTextureVertex(SCENE *scene, FILE *file, char *ach, int n) { OBJPRIV *objPriv = (OBJPRIV *) scene->pvData; sscanf(ach, "vt %g %g %g", &objPriv->pxyzTexVert[objPriv->curTexVert].x, &objPriv->pxyzTexVert[objPriv->curTexVert].y, &objPriv->pxyzTexVert[objPriv->curTexVert].z); objPriv->curTexVert++; } STATIC void parseObjVertex(SCENE *scene, FILE *file, char *ach, int n) { OBJPRIV *objPriv = (OBJPRIV *) scene->pvData; sscanf(ach, "v %g %g %g", &objPriv->pxyzGeoVert[objPriv->curGeoVert].x, &objPriv->pxyzGeoVert[objPriv->curGeoVert].y, &objPriv->pxyzGeoVert[objPriv->curGeoVert].z); objPriv->curGeoVert++; } STATIC void parseObjFace(SCENE *scene, FILE *file, char *ach, int n) { OBJPRIV *objPriv = (OBJPRIV *) scene->pvData; char *pch; GLuint i, numVert = 0; char achFormat[20]; BOOL bReadTex = FALSE, bReadNorm = FALSE; GLuint v, vt, vn; GLfloat rMax; GLuint vv[3]; MyXYZ faceNorm; DoDisplayList(scene); // Determine number of vertices. // Each vertex is represented by a field of non-whitespace characters // which are numbers or '/' characters. pch = &ach[1]; while ( *pch ) { while (*pch && isspace(*pch)) pch++; if (*pch) numVert++; while (*pch && !isspace(*pch)) pch++; } // Bail if there aren't enough vertices to define a face. if (numVert < 3) { LBprintf("bad line: %s", ach); return; } // Determine type of vertex info available. Each vertex has the form // v[/[vt][/[vn]]]. Some examples include: // // f 1/2/3 ... // f 1//3 ... // f 1/2 ... // f 1/2/ ... // f 1 ... // f 1// ... // etc. pch = &ach[1]; while (*pch && isspace(*pch)) pch++; if (*pch && isdigit(*pch)) lstrcpy(achFormat, "%ld"); while (*pch && isdigit(*pch)) pch++; if (*pch == '/') { lstrcat(achFormat, "/"); pch++; } if (*pch && isdigit(*pch)) { bReadTex = TRUE; lstrcat(achFormat, "%ld"); while (*pch && isdigit(*pch)) pch++; } if (*pch == '/') { lstrcat(achFormat, "/"); pch++; } if (*pch && isdigit(*pch)) { bReadNorm = TRUE; lstrcat(achFormat, "%ld"); while (*pch && isdigit(*pch)) pch++; } // If we need to compute our own normal, let's do it now. pch = &ach[1]; if (!bReadNorm) { for ( i = 0; i < 3; i++ ) { while (*pch && isspace(*pch)) pch++; sscanf(pch, "%ld", &vv[i]); if (vv[i] > 0) vv[i] = vv[i] - 1; else vv[i] = objPriv->curGeoVert + vv[i]; while (*pch && !isspace(*pch)) pch++; } calcNorm((GLfloat *) &faceNorm, (GLfloat *) &objPriv->pxyzGeoVert[vv[0]], (GLfloat *) &objPriv->pxyzGeoVert[vv[1]], (GLfloat *) &objPriv->pxyzGeoVert[vv[2]]); } // Finally, parse out the vertices. pch = &ach[1]; glBegin(numVert == 3 ? GL_TRIANGLES : numVert == 4 ? GL_QUADS : GL_POLYGON); if (!bReadTex && !bReadNorm) { glNormal3fv((GLfloat *) &faceNorm); for ( i = 0; i < numVert; i++ ) { while (*pch && isspace(*pch)) pch++; sscanf(pch, achFormat, &v); if (v > 0) v = v - 1; else v = objPriv->curGeoVert + v; glVertex3fv((GLfloat *)&objPriv->pxyzGeoVert[v]); if (fabs(objPriv->pxyzGeoVert[v].x) > objPriv->rMax) objPriv->rMax = fabs(objPriv->pxyzGeoVert[v].x); if (fabs(objPriv->pxyzGeoVert[v].y) > objPriv->rMax) objPriv->rMax = fabs(objPriv->pxyzGeoVert[v].y); if (fabs(objPriv->pxyzGeoVert[v].z) > objPriv->rMax) objPriv->rMax = fabs(objPriv->pxyzGeoVert[v].z); while (*pch && !isspace(*pch)) pch++; } } else if (!bReadTex && bReadNorm) { for ( i = 0; i < numVert; i++ ) { while (*pch && isspace(*pch)) pch++; sscanf(pch, achFormat, &v, &vn); if (v > 0) v = v - 1; else v = objPriv->curGeoVert + v; if (vn > 0) vn = vn - 1; else vn = objPriv->curGeoVert + vn; glNormal3fv((GLfloat *)&objPriv->pxyzNorm[vn]); glVertex3fv((GLfloat *)&objPriv->pxyzGeoVert[v]); if (fabs(objPriv->pxyzGeoVert[v].x) > objPriv->rMax) objPriv->rMax = fabs(objPriv->pxyzGeoVert[v].x); if (fabs(objPriv->pxyzGeoVert[v].y) > objPriv->rMax) objPriv->rMax = fabs(objPriv->pxyzGeoVert[v].y); if (fabs(objPriv->pxyzGeoVert[v].z) > objPriv->rMax) objPriv->rMax = fabs(objPriv->pxyzGeoVert[v].z); while (*pch && !isspace(*pch)) pch++; } } else if (bReadTex && !bReadNorm) { glNormal3fv((GLfloat *) &faceNorm); for ( i = 0; i < numVert; i++ ) { while (*pch && isspace(*pch)) pch++; sscanf(pch, achFormat, &v, &vt); if (v > 0) v = v - 1; else v = objPriv->curGeoVert + v; if (vt > 0) vt = vt - 1; else vt = objPriv->curGeoVert + vt; //!!! ignore texture vertex for now; just use the geometry glVertex3fv((GLfloat *)&objPriv->pxyzGeoVert[v]); if (fabs(objPriv->pxyzGeoVert[v].x) > objPriv->rMax) objPriv->rMax = fabs(objPriv->pxyzGeoVert[v].x); if (fabs(objPriv->pxyzGeoVert[v].y) > objPriv->rMax) objPriv->rMax = fabs(objPriv->pxyzGeoVert[v].y); if (fabs(objPriv->pxyzGeoVert[v].z) > objPriv->rMax) objPriv->rMax = fabs(objPriv->pxyzGeoVert[v].z); while (*pch && !isspace(*pch)) pch++; } } else { for ( i = 0; i < numVert; i++ ) { while (*pch && isspace(*pch)) pch++; sscanf(pch, achFormat, &v, &vt, &vn); if (v > 0) v = v - 1; else v = objPriv->curGeoVert + v; if (vt > 0) vt = vt - 1; else vt = objPriv->curGeoVert + vt; if (vn > 0) vn = vn - 1; else vn = objPriv->curGeoVert + vn; //!!! ignore texture vertex for now; just use the geometry glNormal3fv((GLfloat *)&objPriv->pxyzNorm[vn]); glVertex3fv((GLfloat *)&objPriv->pxyzGeoVert[v]); if (fabs(objPriv->pxyzGeoVert[v].x) > objPriv->rMax) objPriv->rMax = fabs(objPriv->pxyzGeoVert[v].x); if (fabs(objPriv->pxyzGeoVert[v].y) > objPriv->rMax) objPriv->rMax = fabs(objPriv->pxyzGeoVert[v].y); if (fabs(objPriv->pxyzGeoVert[v].z) > objPriv->rMax) objPriv->rMax = fabs(objPriv->pxyzGeoVert[v].z); while (*pch && !isspace(*pch)) pch++; } } glEnd(); } STATIC void fakeObjLight(SCENE *scene, GLfloat x, GLfloat y, GLfloat z) { MyXYZ xyz; MyRGBA rgb = {1.0f, 1.0f, 1.0f, 1.0f}; xyz.x = x; xyz.y = y; xyz.z = z; if (scene->Lights.count < (maxLights + 1)) { glNewList(scene->Lights.listBase + scene->Lights.count, GL_COMPILE); glLightfv(GL_LIGHT0 + scene->Lights.count, GL_POSITION, (GLfloat *) &xyz); glLightfv(GL_LIGHT0 + scene->Lights.count, GL_DIFFUSE, (GLfloat *) &rgb); glLightfv(GL_LIGHT0 + scene->Lights.count, GL_SPECULAR, (GLfloat *) &rgb); glEndList(); scene->Lights.count++; } }