#include #include #include #include #include // isspace() #include // strlen() #include #include #include #include #include #include #include "WFObj.h" using namespace std; #define WHITESPACE " \n\r\t\v" //#define DEBUGGL /****** static functions ******/ static string trimString(string s) { size_t lastpos = s.find_last_not_of(WHITESPACE); if (lastpos == string::npos) return ""; s.erase(lastpos + 1); s.erase(0, s.find_first_not_of(WHITESPACE)); return s; } static string stripFirstToken(string & input) { size_t firstnonspace = input.find_first_not_of(WHITESPACE); if (firstnonspace == string::npos) return ""; size_t spaceafter = input.find_first_of(WHITESPACE, firstnonspace); string token = input.substr(firstnonspace, spaceafter - firstnonspace); input.erase(0, spaceafter); return token; } static vector splitString(const string & str, char delim) { vector ret; string s = str; size_t pos; while ( (pos = s.find(delim)) != string::npos ) { string t = s.substr(0, pos); ret.push_back(t); s.erase(0, pos + 1); } if (s != "") ret.push_back(s); return ret; } static string basePath(const string & str) { string path = str; size_t pos; if ( (pos = path.find_last_of("/\\")) != string::npos ) { path.erase(pos + 1); return path; } return ""; } //#define DEBUG_GL_ERROR #ifdef DEBUG_GL_ERROR #define checkGLError() checkGLErrorLine(__FUNCTION__, __LINE__) static void checkGLErrorLine(const char * function, int line) { GLenum err = glGetError(); if (err != 0) { cerr << "gl error in " << function << ": " << err << " (0x" << hex << err << ") at line " << dec << line << endl; } } #else #define checkGLError() #endif /****** WFObj functions ******/ WFObj::WFObj(loadfile_t lf, loadtexture_t lt) { m_loadfile = lf; m_loadtexture = lt; if (m_loadfile == NULL) { m_loadfile = loadfile; } } WFObj::~WFObj() { } void WFObj::clear() { m_data = std::vector< std::vector >(); m_loadedVertex = false; } bool WFObj::load(const char *fname) { clear(); m_path = fname; Buffer buff; if (!m_loadfile(fname, &buff)) return false; return load(buff); } bool WFObj::load(const WFObj::Buffer &buff) { for (size_t idx = 0; idx < buff.size;) { string line = getLine(buff, idx, &idx) string input = trimString(line); int sz = input.size(); if (sz == 0 || input[0] == '#') continue; if (input[sz-1] == '\\') { input[sz-1] = ' '; buildup = input; continue; } if (buildup != "") input = buildup + input; buildup = ""; processInputLine(input); } return true; } string WFObj::getLine(const Buff & buff, size_t idx, size_t *update_idx) { size_t len = 0; while (idx + len < buff.length) { uint8_t ch = buff.data[idx + len]; if (ch == 0) { *update_idx = idx + len + 1; break; } if (ch == '\r' || ch == '\n') { *update_idx = idx + len + 1; uint8_t nextch = buff.data[*update_idx]; if (ch == '\r' && nextch == '\n') (*update_idx)++; break; } len++; } return string((const char *) &buff.data[idx], len); } void WFObj::processInputLine(const std::string & input) { string line = input; vector lineParts; for (;;) { string token = stripFirstToken(line); if (token == "") break; lineParts.push_back(token); } if (lineParts.size() > 0) m_data.push_back(lineParts); } GLuint WFObj::render(bool doTextureInfo, bool enableBlending) { checkGLError(); GLuint list = glGenLists(1); glNewList(list, GL_COMPILE); int len = m_data.size(); enum { VERTEX, VERTEX_TEXTURE, VERTEX_NORMAL, VERTEX_TYPES }; vector vertices[VERTEX_TYPES]; int numVertsLast = 0; bool inFace = false; bool inMaterial = false; string currentMaterialName; WFMtl material(this); for (int i = 0; i < len; i++) { string type = m_data[i][0]; if (type == "v") { Vertex v = readVertex(m_data[i]); updateAABB(v.getData()); vertices[VERTEX].push_back(v); } else if (type == "vt") vertices[VERTEX_TEXTURE].push_back(readVertex(m_data[i])); else if (type == "vn") vertices[VERTEX_NORMAL].push_back(readVertex(m_data[i])); else if (type == "f") { int numVerts = m_data[i].size() - 1; if (inFace && (numVerts != numVertsLast || numVertsLast > 4)) { #ifdef DEBUGGL cout << "glEnd()" << endl; #endif glEnd(); inFace = false; } if (!inFace) { if (numVerts == 3) { #ifdef DEBUGGL cout << "glBegin(GL_TRIANGLES)" << endl; #endif glBegin(GL_TRIANGLES); } else if (numVerts == 4) { #ifdef DEBUGGL cout << "glBegin(GL_QUADS)" << endl; #endif glBegin(GL_QUADS); } else { #ifdef DEBUGGL cout << "glBegin(GL_POLYGON)" << endl; #endif glBegin(GL_POLYGON); } inFace = true; } for (int v = 1; v <= numVerts; v++) { int vertexIndices[3] = {0, 0, 0}; parseVertexIndices(m_data[i][v], vertexIndices); for (int j = 0; j < 3; j++) { if (vertexIndices[j] < 0) vertexIndices[j] += vertices[j].size() + 1; } bool valid = true; for (int j = 0; j < 3; j++) { if (vertexIndices[j] > (int) vertices[j].size()) { valid = false; break; } } if (vertexIndices[VERTEX] <= 0) valid = false; if (!valid) continue; if (vertexIndices[VERTEX_NORMAL] != 0) { /* a normal is present */ #ifdef DEBUGGL cout << " glNormal3f(" << vertices[VERTEX_NORMAL][vertexIndices[VERTEX_NORMAL]-1] [0] << ", " << vertices[VERTEX_NORMAL][vertexIndices[VERTEX_NORMAL]-1] [1] << ", " << vertices[VERTEX_NORMAL][vertexIndices[VERTEX_NORMAL]-1] [2] << ")" << endl; #endif glNormal3fv(vertices[VERTEX_NORMAL] [vertexIndices[VERTEX_NORMAL]-1].getData()); } if (vertexIndices[VERTEX_TEXTURE] != 0) { /* a texture coordinate is present */ #ifdef DEBUGGL cout << " glTexCoord2f(" << vertices[VERTEX_TEXTURE][vertexIndices[VERTEX_TEXTURE]-1] [0] << ", " << vertices[VERTEX_TEXTURE][vertexIndices[VERTEX_TEXTURE]-1] [1] << ')' << endl; #endif glTexCoord2fv(vertices[VERTEX_TEXTURE] [vertexIndices[VERTEX_TEXTURE]-1].getData()); } #ifdef DEBUGGL cout << " glVertex3f(" << vertices[VERTEX][vertexIndices[VERTEX]-1][0] << ", " << vertices[VERTEX][vertexIndices[VERTEX]-1][1] << ", " << vertices[VERTEX][vertexIndices[VERTEX]-1][2] << ")" << " [" << vertexIndices[VERTEX] << "]" << endl; #endif glVertex3fv(vertices[VERTEX][vertexIndices[VERTEX]-1].getData()); } numVertsLast = numVerts; } else if (type == "usemtl") { if (inFace) { #ifdef DEBUGGL cout << "glEnd()" << endl; #endif glEnd(); inFace = false; } if (inMaterial) { material.renderEnd(currentMaterialName, doTextureInfo, enableBlending); inMaterial = false; } if (m_data[i].size() >= 2) { currentMaterialName = m_data[i][1]; material.renderBegin(currentMaterialName, doTextureInfo, enableBlending); inMaterial = true; } } else if (type == "mtllib") { if (m_data[i].size() >= 2) { FileLoader::Path path( basePath(m_path.fullPath) + m_data[i][1], m_data[i][1]); material.load(path); } } } if (inFace) { #ifdef DEBUGGL cout << "glEnd()" << endl; #endif glEnd(); inFace = false; } if (inMaterial) { material.renderEnd(currentMaterialName, doTextureInfo, enableBlending); inMaterial = false; } glEndList(); checkGLError(); return list; } void WFObj::updateAABB(const float * const vertex) { if (m_loadedVertex) { if (vertex[0] < m_aabb[0]) m_aabb[0] = vertex[0]; else if (vertex[0] > m_aabb[3]) m_aabb[3] = vertex[0]; if (vertex[1] < m_aabb[1]) m_aabb[1] = vertex[1]; else if (vertex[1] > m_aabb[4]) m_aabb[4] = vertex[1]; if (vertex[2] < m_aabb[2]) m_aabb[2] = vertex[2]; else if (vertex[2] > m_aabb[5]) m_aabb[5] = vertex[2]; } else { m_aabb[0] = m_aabb[3] = vertex[0]; m_aabb[1] = m_aabb[4] = vertex[1]; m_aabb[2] = m_aabb[5] = vertex[2]; m_loadedVertex = true; } } WFObj::Vertex WFObj::readVertex(const vector & parts) { int partslen = parts.size(); Vertex v; for (int i = 1; i < partslen && i <= 4; i++) { sscanf(parts[i].c_str(), "%f", &v[i - 1]); } return v; } void WFObj::parseVertexIndices(const string & vtxref, int * ret) { vector parts = splitString(vtxref, '/'); int num = parts.size(); for (int i = 0; i < num && i < 3; i++) sscanf(parts[i].c_str(), "%d", ret + i); } /****** WFMtl functions ******/ void WFObj::WFMtl::clear() { m_data = map< string, vector< vector > >(); m_currentMaterialName = ""; m_attributesPushed = false; } bool WFObj::WFMtl::load(const FileLoader::Path & path) { clear(); FileLoader::Buffer buff = m_obj->m_fileLoader->load(path); if (buff.size <= 0) return false; m_path = path; string str(buff.data, buff.size); stringstream istr(str, ios_base::in); load(istr, buff.size); return true; } bool WFObj::WFMtl::load(std::istream & istr, unsigned int size) { char buf[size+1]; string buildup; while (istr.good()) { istr.getline(buf, size+1); string input = trimString(buf); int sz = input.size(); if (sz == 0 || input[0] == '#') continue; if (input[sz-1] == '\\') { input[sz-1] = ' '; buildup = input; continue; } if (buildup != "") input = buildup + input; buildup = ""; processInputLine(input); } return true; } void WFObj::WFMtl::processInputLine(const std::string & input) { string line = input; vector lineParts; for (;;) { string token = stripFirstToken(line); if (token == "") break; lineParts.push_back(token); } if (lineParts.size() > 0) { if ( (lineParts.size() >= 2) && (lineParts[0] == "newmtl") ) { m_currentMaterialName = lineParts[1]; } else if (m_currentMaterialName != "") { m_data[m_currentMaterialName].push_back(lineParts); } } } void WFObj::WFMtl::renderBegin(const string & mtlname, bool doTextureInfo, bool enableBlending) { map< string, vector< vector > >::iterator it = m_data.find(mtlname); if (it == m_data.end()) return; vector< vector > & stmts = it->second; int num_stmts = stmts.size(); bool foundTexture = false; bool didSomething = false; checkGLError(); for (int i = 0; i < num_stmts; i++) { string & type = stmts[i][0]; if (type == "Ka") /* set ambient color */ { if ( (stmts[i].size() == 4) && (stmts[i][1] != "spectral") ) { pushAttributes(); float mat[4] = {0.0f, 0.0f, 0.0f, 1.0f}; for (int j = 0; j < 3; j++) sscanf(stmts[i][j+1].c_str(), "%f", &mat[j]); #ifdef DEBUGGL cout << " glMaterialfv(GL_FRONT, GL_AMBIENT, {"; for (int j = 0; j < 4; j++) cout << mat[j] << (j < 3 ? ", " : "})"); cout << endl; #endif glMaterialfv(GL_FRONT, GL_AMBIENT, mat); didSomething = true; } } else if (type == "Kd") /* set diffuse color */ { if ( (stmts[i].size() == 4) && (stmts[i][1] != "spectral") ) { pushAttributes(); float mat[4] = {0.0f, 0.0f, 0.0f, 1.0f}; for (int j = 0; j < 3; j++) sscanf(stmts[i][j+1].c_str(), "%f", &mat[j]); #ifdef DEBUGGL cout << " glMaterialfv(GL_FRONT, GL_DIFFUSE, {"; for (int j = 0; j < 4; j++) cout << mat[j] << (j < 3 ? ", " : "})"); cout << endl; #endif glMaterialfv(GL_FRONT, GL_DIFFUSE, mat); didSomething = true; } } else if (type == "Ks") /* set specular color */ { if ( (stmts[i].size() == 4) && (stmts[i][1] != "spectral") ) { pushAttributes(); float mat[4] = {0.0f, 0.0f, 0.0f, 1.0f}; for (int j = 0; j < 3; j++) sscanf(stmts[i][j+1].c_str(), "%f", &mat[j]); #ifdef DEBUGGL cout << " glMaterialfv(GL_FRONT, GL_SPECULAR, {"; for (int j = 0; j < 4; j++) cout << mat[j] << (j < 3 ? ", " : "})"); cout << endl; #endif glMaterialfv(GL_FRONT, GL_SPECULAR, mat); didSomething = true; } } else if (type == "Ns") /* set shininess */ { if (stmts[i].size() == 2) { pushAttributes(); GLfloat shininess = 0.0f; sscanf(stmts[i][1].c_str(), "%f", &shininess); #ifdef DEBUGGL cout << " glMaterialf(GL_FRONT, GL_SHININESS, " << shininess << ")" << endl; #endif glMaterialf(GL_FRONT, GL_SHININESS, shininess); didSomething = true; } } else if (type == "map_Kd") /* load a diffuse texture */ { if (doTextureInfo) { if (stmts[i].size() == 2) { if (m_obj->m_textureLoader != NULL) { FileLoader::Path path( basePath(m_path.fullPath) + stmts[i][1], stmts[i][1]); GLuint tex = m_obj->m_textureLoader->load(path, *m_obj->m_fileLoader); if (tex > 0) { pushAttributes(); /* jh 2009-11-16 */ #ifdef DEBUGGL cout << " glBindTexture(GL_TEXTURE_2D, " << tex << ")" << endl; #endif glBindTexture(GL_TEXTURE_2D, tex); foundTexture = true; didSomething = true; } } } } } } checkGLError(); if (didSomething) { pushAttributes(); if (doTextureInfo) { if (enableBlending) { glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); } if (foundTexture) { #ifdef DEBUGGL cout << " glEnable(GL_TEXTURE_2D)" << endl; #endif glEnable(GL_TEXTURE_2D); } else { #ifdef DEBUGGL cout << " glDisable(GL_TEXTURE_2D)" << endl; #endif glDisable(GL_TEXTURE_2D); } } } checkGLError(); } void WFObj::WFMtl::pushAttributes() { if (m_attributesPushed) return; m_attributesPushed = true; #ifdef DEBUGGL cout << " glPushAttrib(GL_LIGHTING_BIT | GL_TEXTURE_BIT | GL_ENABLE_BIT | GL_COLOR_BUFFER_BIT)" << endl; #endif checkGLError(); glPushAttrib(GL_LIGHTING_BIT | GL_TEXTURE_BIT | GL_ENABLE_BIT | GL_COLOR_BUFFER_BIT); checkGLError(); } void WFObj::WFMtl::renderEnd(const string & mtlname, bool doTextureInfo, bool enableBlending) { map< string, vector< vector > >::iterator it = m_data.find(mtlname); if (it == m_data.end()) return; if (m_attributesPushed) { #ifdef DEBUGGL cout << " glPopAttrib()" << endl; #endif checkGLError(); glPopAttrib(); checkGLError(); m_attributesPushed = false; } } /****** WFFileLoader functions ******/ int WFObj::WFFileLoader::getSize(const Path & path) { struct stat st; if (path.fullPath == "") return -1; if (stat(path.fullPath.c_str(), &st)) return -2; return st.st_size; } FileLoader::Buffer WFObj::WFFileLoader::load(const Path & path) { int size = getSize(path); if (size > 0) { int fd = open(path.fullPath.c_str(), O_RDONLY); if (fd > 0) { Buffer buf(size); int num_read = read(fd, buf.data, size); close(fd); if (num_read > 0) return buf; } } return Buffer(0); }