diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..c81e670 --- /dev/null +++ b/Makefile @@ -0,0 +1,25 @@ +CXX := g++ +CXXFLAGS := -O2 `sdl-config --cflags` +LDFLAGS := -lGL -lGLU `sdl-config --libs` -lSDL_image +TARGET := wfobj-view + +.PHONY: all clean TextureCache wfobj + +all: $(TARGET) + +$(TARGET): $(TARGET).o TextureCache wfobj + $(CXX) -o $(TARGET) $< TextureCache/TextureCache.o wfobj/WFObj.o $(LDFLAGS) + +%.o: %.cc + $(CXX) -c -o $@ $< $(CXXFLAGS) + +TextureCache: + make -C TextureCache + +wfobj: + make -C wfobj + +clean: + make -C TextureCache clean + make -C wfobj clean + -rm -f *.o *~ $(TARGET) diff --git a/TextureCache/Makefile b/TextureCache/Makefile new file mode 100644 index 0000000..19fec6d --- /dev/null +++ b/TextureCache/Makefile @@ -0,0 +1,12 @@ + +CXX := g++ +CXXFLAGS := -O2 +OBJS := TextureCache.o + +all: $(OBJS) + +%.o: %.cc %.hh + $(CXX) -c -o $@ $< $(CXXFLAGS) + +clean: + -rm -f *~ *.o diff --git a/TextureCache/TextureCache.cc b/TextureCache/TextureCache.cc new file mode 100644 index 0000000..8a8ab29 --- /dev/null +++ b/TextureCache/TextureCache.cc @@ -0,0 +1,94 @@ + +#include +#include +#include +#include +#include +#include "TextureCache.hh" +using namespace std; + +GLuint TextureCache::load(const string & filename) +{ + map::iterator it = m_cache.find(filename); + if (it != m_cache.end()) + return it->second; + GLuint tex = loadTexture(filename.c_str()); + m_cache[filename] = tex; + return tex; +} + +GLuint TextureCache::loadTexture(const char * filename, + bool mipmaps, + int mode, + int quality) +{ + GLuint texture; + SDL_Surface * temp = IMG_Load(filename); + if (!temp) + { + cerr << "Failed to load image '" << filename << "'!" << endl; + return 0; + } + + SDL_PixelFormat fmt; + fmt.palette = NULL; + fmt.BitsPerPixel = 32; + fmt.BytesPerPixel = 4; + fmt.Rmask = 0x000000FF; + fmt.Gmask = 0x0000FF00; + fmt.Bmask = 0x00FF0000; + fmt.Amask = 0xFF000000; + fmt.Rshift = 0; + fmt.Gshift = 8; + fmt.Bshift = 16; + fmt.Ashift = 24; + + SDL_Surface * texsurf = SDL_ConvertSurface(temp, &fmt, SDL_SWSURFACE); + SDL_FreeSurface(temp); + if (!texsurf) + { + cerr << '\'' << filename << "' was not converted properly!" << endl; + return 0; + } + unsigned int * pixels = new unsigned int[texsurf->w * texsurf->h]; + int y; + unsigned int dstOffset = texsurf->w * (texsurf->h - 1); + unsigned int srcOffset = 0; + for (y = 0; y < texsurf->h; y++) + { + memcpy(pixels + dstOffset, + ((unsigned int *)texsurf->pixels) + srcOffset, + texsurf->w << 2); + dstOffset -= texsurf->w; + srcOffset += texsurf->w; + } + glGenTextures(1, &texture); + glBindTexture(GL_TEXTURE_2D, texture); + glTexImage2D(GL_TEXTURE_2D, 0, 4, texsurf->w, texsurf->h, 0, + GL_RGBA, GL_UNSIGNED_BYTE, pixels); + + if (quality > 0) + { + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, + mipmaps ? GL_LINEAR_MIPMAP_LINEAR : GL_LINEAR); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, + mipmaps ? GL_LINEAR_MIPMAP_LINEAR : GL_LINEAR); + } + else + { + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, + mipmaps ? GL_NEAREST_MIPMAP_NEAREST : GL_NEAREST); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, + mipmaps ? GL_NEAREST_MIPMAP_NEAREST : GL_NEAREST); + } + + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, mode); + + if (mipmaps) + gluBuild2DMipmaps(GL_TEXTURE_2D, GL_RGBA, + texsurf->w, texsurf->h, GL_RGBA, GL_UNSIGNED_BYTE, pixels); + + SDL_FreeSurface(texsurf); + delete[] pixels; + return texture; +} diff --git a/TextureCache/TextureCache.hh b/TextureCache/TextureCache.hh new file mode 100644 index 0000000..25a9d06 --- /dev/null +++ b/TextureCache/TextureCache.hh @@ -0,0 +1,20 @@ + +#include +#include +#include + +class TextureCache +{ +public: + GLuint load(const std::string & filename); + +private: + /* methods */ + GLuint loadTexture(const char * filename, + bool mipmaps = false, + int mode = GL_DECAL, + int quality = 1); + + /* data */ + std::map< std::string, GLuint > m_cache; +}; diff --git a/wfobj-view.cc b/wfobj-view.cc new file mode 100644 index 0000000..cfda65a --- /dev/null +++ b/wfobj-view.cc @@ -0,0 +1,65 @@ + +/* Libraries we use */ +#include +#include + +/* Some definitions */ +#define WIDTH 800 +#define HEIGHT 800 +#define TITLE "Josh's Wavefront Object Viewer" + +/* Some function prototypes */ +void display(void); +void mouse(int button, int state, int x, int y); +void motion(int x, int y); + +/* Some global variables */ +float rotationMatrix[16]; +int startx, starty; + +/* The program's main entry point */ +int main(int argc, char *argv[]) +{ + /* OpenGL initialization */ + glShadeModel(GL_SMOOTH); + glEnable(GL_DEPTH_TEST); + glEnable(GL_LIGHTING); + glEnable(GL_LIGHT0); + float lpos[] = {0.0f, -6.0f, 0.0f, 1.0f}; + glLightfv(GL_LIGHT0, GL_POSITION, lpos); + glEnable(GL_CULL_FACE); + glClearColor(0, .3, 0.5, 1); + /* Initialize the default rotation matrix */ + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + glGetFloatv(GL_MODELVIEW_MATRIX, rotationMatrix); + return 0; +} + +/* This function is called to display the scene each refresh */ +void display(void) +{ + glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT); + glLoadIdentity(); + glMultMatrixf(rotationMatrix); +} + +/* handle mouse button press/release events */ +void mouse(int button, int state, int x, int y) +{ + startx = x; + starty = y; +} + +/* handle mouse motion events */ +void motion(int x, int y) +{ + glLoadIdentity(); + glRotatef(y-starty, 1, 0, 0); + glRotatef(x-startx, 0, 0, 1); + glMultMatrixf(rotationMatrix); + glGetFloatv(GL_MODELVIEW_MATRIX, rotationMatrix); + startx=x; + starty=y; +} + diff --git a/wfobj/Makefile b/wfobj/Makefile new file mode 100644 index 0000000..6a260c3 --- /dev/null +++ b/wfobj/Makefile @@ -0,0 +1,12 @@ + +CXX := g++ +CXXFLAGS := -O2 +OBJS := WFObj.o + +all: $(OBJS) + +%.o: %.cc %.hh + $(CXX) -c -o $@ $< $(CXXFLAGS) + +clean: + -rm -f *~ *.o diff --git a/wfobj/WFObj.cc b/wfobj/WFObj.cc new file mode 100644 index 0000000..ef511c1 --- /dev/null +++ b/wfobj/WFObj.cc @@ -0,0 +1,557 @@ + +#include +#include +#include +#include // isspace() +#include // strlen() +#include +#include +#include +#include +#include +#include +#include "WFObj.hh" +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 ""; +} + +/****** WFObj functions ******/ + +void WFObj::clear() +{ + m_data = std::vector< std::vector >(); +} + +int WFObj::filesize(const char * filename) +{ + struct stat st; + if (stat(filename, &st)) + return -1; + return st.st_size; +} + +bool WFObj::load(const string & filename, loadTextureFunc_t loadTexture) +{ + clear(); + + int size = filesize(filename.c_str()); + if (size < 1) + return false; + + ifstream ifs(filename.c_str()); + if (!ifs.is_open()) + return false; + char buf[size+1]; + + string buildup; + while (ifs.good()) + { + ifs.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); + } + + ifs.close(); + m_loadTexture = loadTexture; + m_fileName = filename; + return true; +} + + +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() +{ + 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; + for (int i = 0; i < len; i++) + { + string type = m_data[i][0]; + if (type == "v") + vertices[VERTEX].push_back(readVertex(m_data[i])); + 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] > 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); + inMaterial = false; + } + if (m_data[i].size() >= 2) + { + currentMaterialName = m_data[i][1]; + material.renderBegin(currentMaterialName); + inMaterial = true; + } + } + else if (type == "mtllib") + { + if (m_data[i].size() >= 2) + { + material.load(basePath(m_fileName) + m_data[i][1], m_loadTexture); + } + } + } + if (inFace) + { +#ifdef DEBUGGL + cout << "glEnd()" << endl; +#endif + glEnd(); + inFace = false; + } + if (inMaterial) + { + material.renderEnd(currentMaterialName); + inMaterial = false; + } + glEndList(); + return list; +} + +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 WFMtl::clear() +{ + m_data = map< string, vector< vector > >(); + m_currentMaterialName = ""; + m_attributesPushed = false; +} + +int WFMtl::filesize(const char * filename) +{ + struct stat st; + if (stat(filename, &st)) + return -1; + return st.st_size; +} + +bool WFMtl::load(const string & filename, WFObj::loadTextureFunc_t loadTexture) +{ + clear(); + + int size = filesize(filename.c_str()); + if (size < 1) + return false; + + ifstream ifs(filename.c_str()); + if (!ifs.is_open()) + return false; + char buf[size+1]; + + string buildup; + while (ifs.good()) + { + ifs.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); + } + + /* DEBUG */ + map > >::iterator it = m_data.begin(); + while (it != m_data.end()) + { + cout << "Material '" << it->first << "':" << endl; + for (int i = 0; i < it->second.size(); i++) + { + cout << " "; + for (int j = 0; j < it->second[i].size(); j++) + { + cout << '\'' << it->second[i][j] << "' "; + } + cout << endl; + } + it++; + } + /* END DEBUG */ + + ifs.close(); + m_loadTexture = loadTexture; + m_fileName = filename; + return true; +} + +void 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 WFMtl::renderBegin(const string & mtlname) +{ + 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; + 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 == "map_Kd") /* load a diffuse texture */ + { + /* TODO: figure out how i want to load the texture */ + if (stmts[i].size() == 2) + { + GLuint tex = m_loadTexture( (basePath(m_fileName) + + stmts[i][1]).c_str() ); + if (tex > 0) + { +#ifdef DEBUGGL + cout << " glBindTexture(GL_TEXTURE_2D, " << tex << ")" << endl; +#endif + glBindTexture(GL_TEXTURE_2D, tex); + foundTexture = true; + didSomething = true; + } + } + } + } + + if (didSomething) + { + pushAttributes(); + 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); + } + } +} + +void WFMtl::pushAttributes() +{ + if (m_attributesPushed) + return; + m_attributesPushed = true; +#ifdef DEBUGGL + cout << " glPushAttrib(GL_LIGHTING_BIT | GL_TEXTURE_BIT)" << endl; +#endif + glPushAttrib(GL_LIGHTING_BIT | GL_TEXTURE_BIT); +} + +void WFMtl::renderEnd(const string & mtlname) +{ + 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 + glPopAttrib(); + } +} + diff --git a/wfobj/WFObj.hh b/wfobj/WFObj.hh new file mode 100644 index 0000000..f5dde51 --- /dev/null +++ b/wfobj/WFObj.hh @@ -0,0 +1,60 @@ + +#include +#include +#include +#include + +class WFObj +{ +public: + typedef GLuint (*loadTextureFunc_t)(const char * texture); + bool load(const std::string & filename, loadTextureFunc_t loadTexture); + GLuint render(); + +private: + /* types */ + class Vertex + { + public: + float operator[](int idx) const { return data[idx]; } + const float & operator[](int idx) { return data[idx]; } + float * getData() { return data; } + private: + float data[4]; + }; + + /* methods */ + void clear(); + int filesize(const char * filename); + void processInputLine(const std::string & input); + Vertex readVertex(const std::vector & parts); + void parseVertexIndices(const std::string & vtxref, int * ret); + + /* variables */ + std::vector< std::vector > m_data; + loadTextureFunc_t m_loadTexture; + std::string m_fileName; +}; + + +class WFMtl +{ +public: + bool load(const std::string & filename, WFObj::loadTextureFunc_t loadTexture); + void renderBegin(const std::string & mtlname); + void renderEnd(const std::string & mtlname); + +private: + /* methods */ + void clear(); + int filesize(const char * filename); + void processInputLine(const std::string & input); + void pushAttributes(); + + /* variables */ + std::map< std::string, std::vector< std::vector > > m_data; + std::string m_currentMaterialName; + bool m_attributesPushed; + WFObj::loadTextureFunc_t m_loadTexture; + std::string m_fileName; +};