/* Lodestone OBJ Loader */ #include #include #include #include #include #include #include "lsOBJLoader.h" using namespace std; /* external interface to scenegraph */ static lsInterface *ls = NULL; void lsOBJLoaderInit(lsInterface *l) { ls = l; } /* Lodestone interface */ int lsOBJSupportsFile(FILE *file) { return 1; } /* OBJ Loader structures */ enum DataElem { UNKNOWN_DE = 0, VERTEX_DE, VERTEX_TEXTURECOORD_DE, VERTEX_NORMAL_DE, MTL_LIB_DE, NEW_MTL_DE, MTL_DIFFUSE_DE, MTL_AMBIENT_DE, MTL_SPECULAR_DE, MTL_SHININESS_DE, MTL_ILLUM_DE, MTL_MAP_KD_DE, MTL_MAP_KA_DE, MTL_MAP_KS_DE, MTL_REFL_DE, FACE_DE, USE_MTL_DE, GROUP_DE, SMOOTHING_GROUP_DE, OBJECT_DE }; map _dataElemMap; void initDataElemMap(void); struct TiePoint { int index[3]; TiePoint( int v = -1, int vt = -1, int vn = -1 ) { index[0] = v; index[1] = vt; index[2] = vn; } inline void set ( int v = -1, int vt = -1, int vn = -1 ) { index[0] = v; index[1] = vt; index[2] = vn; } }; struct Face { vector tieVec; }; struct Mesh { string name; list faceList; lsStateSet mtlPtr; }; int readMTL ( const char *fileName, map & mtlMap ); bool readString(FILE *f, string &st) { st.erase(); signed char c; bool started = false; while(!feof(f)) { c = fgetc(f); if(isspace(c)) { if(started) break; } else { st.append(1,(char)c); started = true; } } return started; } bool readStringTo(FILE *f, string &st, char end) { st.erase(); signed char c; bool started = false; while(!feof(f)) { c = fgetc(f); if(c == end) { if(started) break; } else { st.append(1,(char)c); started = true; } } return started; } float readFloat(FILE *f) { string st; readString(f,st); float val; if(sscanf(st.c_str(), "%f", &val) == 0) { fprintf(stderr, "Expected float, got '%s'\n", st.c_str()); return -1e30; } return val; } int readInt(FILE *f) { string st; readString(f,st); int val; if(sscanf(st.c_str(), "%d", &val) == 0) { fprintf(stderr, "Expected int, got '%s'\n", st.c_str()); return -0x7fffffff; } return val; } bool readTo(FILE *f, char end) { char c; while(!feof(f)) { c = fgetc(f); if(c == end) { return true; } } return false; } /* Lodestone Central Function */ lsNode lsOBJLoad(FILE *file) { if(ls == NULL) { fprintf(stderr, "lsOBJ: Lodestone interface not set, aborting!\n"); return NULL; } if(!file) { fprintf(stderr, "lsOBJ: Lodestone interface not set, aborting!\n"); return NULL; } initDataElemMap(); lsNode rootPtr, nodePtr; lsGeometry geoPtr; string elem; map::const_iterator elemI; vector coordPtr; vector texCoordPtr; vector normalPtr; DataElem dataElem; char *nextToken; const char *token; int index, indexType; int i,j,n,primCount[3]; list meshList; map mtlMap; map::iterator mtlI; Mesh emptyMesh; Face emptyFace; TiePoint emptyTie; int indexMask, meshIndexMask; list::iterator faceI; list::iterator meshI; // create the first mesh entry meshList.push_back(emptyMesh); meshI = meshList.begin(); primCount[0] = 0; primCount[1] = 0; primCount[2] = 0; for (readString(file,elem); !feof(file); readString(file,elem)) { if (elem[0] == '#' || elem[0] == '$' ) readTo(file, '\n'); else { elemI = _dataElemMap.find(elem); dataElem = ((elemI == _dataElemMap.end()) ? UNKNOWN_DE : elemI->second ); switch (dataElem) { case OBJECT_DE: case GROUP_DE: case SMOOTHING_GROUP_DE: readTo(file, '\n'); break; case VERTEX_DE: primCount[0]++; coordPtr.push_back(readFloat(file)); coordPtr.push_back(readFloat(file)); coordPtr.push_back(readFloat(file)); break; case VERTEX_TEXTURECOORD_DE: primCount[1]++; texCoordPtr.push_back(readFloat(file)); texCoordPtr.push_back(readFloat(file)); break; case VERTEX_NORMAL_DE: primCount[2]++; normalPtr.push_back(readFloat(file)); normalPtr.push_back(readFloat(file)); normalPtr.push_back(readFloat(file)); break; case MTL_LIB_DE: readString(file, elem); readMTL ( elem.c_str(), mtlMap ); readTo(file, '\n'); break; case USE_MTL_DE: readString(file, elem); if (meshI->faceList.empty() == false) { meshList.push_front(emptyMesh); meshI = meshList.begin(); } mtlI = mtlMap.find(elem); if (mtlI == mtlMap.end()) { printf("Unkown mtl %s\n", elem.c_str()); } else meshI->mtlPtr = mtlI->second; break; case FACE_DE: meshI->faceList.push_front(emptyFace); faceI = meshI->faceList.begin(); readStringTo(file, elem, '\n'); token = elem.c_str(); indexType = 0; while (token && *token) { for (; *token == '/'; token++) indexType++; for (; isspace(*token); token++) indexType = 0; index = strtol(token, &nextToken, 10); if (token == nextToken) break; if (indexType == 0) faceI->tieVec.push_back(emptyTie); if (index >= 0) index--; else index = primCount[indexType] + index; faceI->tieVec.back().index[indexType] = index; token = nextToken; } break; case UNKNOWN_DE: default: printf( "Unkown obj data elem: %s\n", elem.c_str()); readTo(file, '\n'); break; } } } // create Geometry objects for (meshI = meshList.begin(); meshI != meshList.end(); meshI++) { // create and check mesh index mask // also check uniformity meshIndexMask = 0; bool uniform = true; int vertSum = 0; if ( meshI->faceList.empty() == false) { for ( faceI = meshI->faceList.begin(); faceI != meshI->faceList.end(); faceI++) { vertSum += n; indexMask = 0; n = faceI->tieVec.size(); for (i = 0; i < n; i++) { int index = -1; for (j = 0; j < 3; j++) { if (faceI->tieVec[i].index[j] >= 0) { indexMask |= (1 << j); if(index < 0) { index = faceI->tieVec[i].index[j]; } else { if(index != faceI->tieVec[i].index[j]) uniform = false; } } } } if (meshIndexMask == 0) meshIndexMask = indexMask; else if (meshIndexMask != indexMask) { printf("IndexMask unmatch (i,j), can not create geo"); meshIndexMask = 0; break; } } } else { printf("Mesh with empty faceList\n"); continue; } if((meshIndexMask & 1) == 0) { printf("Mesh without vertices\n"); continue; } if (meshI->mtlPtr == NULL) { static lsStateSet defaultState = NULL; if(defaultState == NULL) { static float diffuse[3] = { .8, .8, .8 }; static float specular[3] = { 1, 1, 1 }; defaultState = ls->createStateSet(); ls->stateMaterialfv(defaultState, GL_FRONT_AND_BACK, GL_DIFFUSE, diffuse); ls->stateMaterialfv(defaultState, GL_FRONT_AND_BACK, GL_SPECULAR, specular); ls->stateMaterialf(defaultState, GL_FRONT_AND_BACK, GL_SHININESS, 20 ); ls->closeStateSet(defaultState); } meshI->mtlPtr = defaultState; } ls->activateStateSet(meshI->mtlPtr); geoPtr = ls->createGeometry(); if(uniform) { ls->setVertexCount(geoPtr, coordPtr.size() / 3); ls->setVertices(geoPtr, &coordPtr[0]); if (meshIndexMask & 2) { ls->setTexCoordCount(geoPtr, texCoordPtr.size() / 2); ls->setTexCoords(geoPtr, &texCoordPtr[0]); } if (meshIndexMask & 4) { ls->setNormalCount(geoPtr, normalPtr.size() / 3); ls->setNormals(geoPtr, &normalPtr[0]); } for ( faceI = meshI->faceList.begin(); faceI != meshI->faceList.end(); faceI++) { n = faceI->tieVec.size(); GLuint *ind = new GLuint [n]; // create the index values for (i = 0; i < n; i++) ind[i] = faceI->tieVec[i].index[0]; ls->addFace(geoPtr, GL_POLYGON, n, ind); delete [] ind; } } else // uniform? { ls->setVertexCount(geoPtr, vertSum); if (meshIndexMask & 2) ls->setTexCoordCount(geoPtr, vertSum); if (meshIndexMask & 4) ls->setNormalCount(geoPtr, vertSum); int vcount = 0; for ( faceI = meshI->faceList.begin(); faceI != meshI->faceList.end(); faceI++) { n = faceI->tieVec.size(); GLuint *ind = new GLuint [n]; // create the index values for(i = 0; i < n; i++) { ind[i] = vcount + i; ls->addVertex(geoPtr, &coordPtr[faceI->tieVec[i].index[0] * 3]); if ( meshIndexMask & 2) ls->addTexCoord(geoPtr, &texCoordPtr[faceI->tieVec[i].index[1] * 2]); if ( meshIndexMask & 4) ls->addNormal(geoPtr, &normalPtr[faceI->tieVec[i].index[2] * 3]); } ls->addFace(geoPtr, GL_POLYGON, n, ind); delete [] ind; vcount += n; } // face loop } // uniform ? ls->closeGeometry(geoPtr); if (meshList.size() > 1) { if (rootPtr == NULL) { rootPtr = ls->createGroup(); ls->addChild(rootPtr, nodePtr); } else { ls->addChild(rootPtr, nodePtr); } } else { rootPtr = nodePtr; } } return rootPtr; } void initDataElemMap(void) { if (_dataElemMap.empty()) { _dataElemMap[""] = UNKNOWN_DE; _dataElemMap["v"] = VERTEX_DE; _dataElemMap["vt"] = VERTEX_TEXTURECOORD_DE; _dataElemMap["vn"] = VERTEX_NORMAL_DE; _dataElemMap["f"] = FACE_DE; _dataElemMap["fo"] = FACE_DE; _dataElemMap["mtllib"] = MTL_LIB_DE; _dataElemMap["newmtl"] = NEW_MTL_DE; _dataElemMap["Kd"] = MTL_DIFFUSE_DE; _dataElemMap["Ka"] = MTL_AMBIENT_DE; _dataElemMap["Ks"] = MTL_SPECULAR_DE; _dataElemMap["Ns"] = MTL_SHININESS_DE; _dataElemMap["map_Kd"] = MTL_MAP_KD_DE; _dataElemMap["map_Ka"] = MTL_MAP_KA_DE; _dataElemMap["map_Ks"] = MTL_MAP_KS_DE; _dataElemMap["illum"] = MTL_ILLUM_DE; _dataElemMap["refl"] = MTL_REFL_DE; _dataElemMap["usemtl"] = USE_MTL_DE; _dataElemMap["g"] = GROUP_DE; _dataElemMap["s"] = SMOOTHING_GROUP_DE; _dataElemMap["o"] = OBJECT_DE; } } int readMTL(const char *fileName, map & mtlMap) { int mtlCount = 0; float vec[4]; float val; string elem; map::const_iterator elemI; DataElem dataElem; map imageMap; map::iterator iI; lsTexture texture; lsStateSet mtlPtr = NULL; char *foundName = ls->findFile(fileName); if (!foundName) { printf("MaterialLib %s not found.\n", fileName); return 0; } ifstream in(fileName); if (!in) { printf("MaterialLib %s couldn't be opened.\n", fileName); return 0; } vec[3] = 1.; for (in >> elem; in.eof() == false; in >> elem) { if (elem[0] == '#' || elem[0] == '$' ) { in.ignore(INT_MAX, '\n'); continue; } elemI = _dataElemMap.find(elem); dataElem = ((elemI == _dataElemMap.end()) ? UNKNOWN_DE : elemI->second); switch (dataElem) { case NEW_MTL_DE: in >> elem; if(mtlPtr) ls->closeStateSet(mtlPtr); mtlPtr = ls->createStateSet(); ls->stateColorMaterial(mtlPtr, GL_FRONT_AND_BACK, GL_NONE); mtlMap[elem] = mtlPtr; mtlCount++; break; case MTL_DIFFUSE_DE: if (mtlPtr == NULL) { printf( "Invalid %s entry in %s\n", elem.c_str(), fileName); } else { in >> vec[0] >> vec[1] >> vec[2]; ls->stateMaterialfv(mtlPtr, GL_FRONT_AND_BACK, GL_DIFFUSE, vec); } break; case MTL_AMBIENT_DE: if (mtlPtr == NULL) { printf( "Invalid %s entry in %s\n", elem.c_str(), fileName); } else { in >> vec[0] >> vec[1] >> vec[2]; ls->stateMaterialfv(mtlPtr, GL_FRONT_AND_BACK, GL_AMBIENT, vec); } break; case MTL_SPECULAR_DE: if (mtlPtr == NULL) { printf( "Invalid %s entry in %s\n", elem.c_str(), fileName); } else { in >> vec[0] >> vec[1] >> vec[2]; ls->stateMaterialfv(mtlPtr, GL_FRONT_AND_BACK, GL_SPECULAR, vec); } break; case MTL_SHININESS_DE: if (mtlPtr == NULL) { printf( "Invalid %s entry in %s\n", elem.c_str(), fileName); } else { in >> val; ls->stateMaterialf(mtlPtr, GL_FRONT_AND_BACK, GL_SHININESS, val); } break; case MTL_ILLUM_DE: if (mtlPtr == NULL) { printf( "Invalid %s entry in %s\n", elem.c_str(), fileName); } else { in >> elem ; // TODO: What to do with illum ?!? } break; case MTL_REFL_DE: ls->texGenf(texture, GL_S, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP); ls->texGenf(texture, GL_T, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP); break; case MTL_MAP_KD_DE: case MTL_MAP_KA_DE: case MTL_MAP_KS_DE: if (mtlPtr == NULL) { printf( "Invalid %s entry in %s\n", elem.c_str(), fileName); } else { in >> elem; iI = imageMap.find(elem); if (iI == imageMap.end()) { texture = ls->createTexture(); ls->texSetImage(texture, elem.c_str()); imageMap[elem] = texture; } else texture = iI->second; if (texture) { ls->stateSetTexture(mtlPtr, texture); } } break; default: printf( "Invalid %s entry in %s\n", elem.c_str(), fileName ); in.ignore(INT_MAX, '\n'); break; } } if(mtlPtr) ls->closeStateSet(mtlPtr); return mtlCount; }