From 39bd1778bf3b7d93b115767d8c3f8ae7f77a3aaa Mon Sep 17 00:00:00 2001 From: Josh Holtrop Date: Mon, 3 Sep 2012 09:14:47 -0400 Subject: [PATCH] add WFObj object-loading library sources --- SConstruct | 2 +- src/client/wfobj/WFObj.cc | 590 ++++++++++++++++++++++++++++++++++++++ src/client/wfobj/WFObj.h | 138 +++++++++ 3 files changed, 729 insertions(+), 1 deletion(-) create mode 100644 src/client/wfobj/WFObj.cc create mode 100644 src/client/wfobj/WFObj.h diff --git a/SConstruct b/SConstruct index 310c856..4d1393d 100755 --- a/SConstruct +++ b/SConstruct @@ -36,7 +36,7 @@ SFML_PATH_PREFIX = '/c/apps' if platform == 'windows' else '/opt' SFML_PATH = '%s/SFML-%s' % (SFML_PATH_PREFIX, SFML_VERSION) if 'SFML_PATH' in os.environ: SFML_PATH = os.environ['SFML_PATH'] -CPPFLAGS = ['-I%s/include' % SFML_PATH] +CPPFLAGS = ['-I%s/include' % SFML_PATH, '-DGL_INCLUDE_FILE=\\"GL/glew.h\\"'] LIBPATH = ['%s/lib' % SFML_PATH] for dirent in os.listdir('src'): CPPFLAGS.append('-I%s/src/%s' % (os.getcwd(), dirent)) diff --git a/src/client/wfobj/WFObj.cc b/src/client/wfobj/WFObj.cc new file mode 100644 index 0000000..c358795 --- /dev/null +++ b/src/client/wfobj/WFObj.cc @@ -0,0 +1,590 @@ + +#include +#include +#include +#include +#include /* isspace() */ +#include /* strlen() */ +#include /* atoi */ + +#include +#include +#include +#include +#include + +#include "WFObj.h" + +using namespace std; + +#define WHITESPACE " \n\r\t\v" +//#define DEBUGGL + +/****** static functions ******/ + +static string trimString(const string in) +{ + size_t firstpos = in.find_first_not_of(WHITESPACE); + if (firstpos == string::npos) + return ""; + return string(in, firstpos, in.find_last_not_of(WHITESPACE) - firstpos + 1); +} + +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; +} + +vector tokenize(const string & input) +{ + vector tokens; + string in = input; + for (;;) + { + string token = stripFirstToken(in); + if (token == "") + break; + tokens.push_back(token); + } + return tokens; +} + +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) +{ + size_t pos = str.find_last_of("/\\"); + return pos != string::npos ? string(str, 0, pos + 1) : "./"; +} + +//#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() +{ + if (m_valid) + { + glDeleteBuffers(1, &m_data_vbo); + glDeleteBuffers(1, &m_index_vbo); + } + m_valid = false; + clear(); +} + +WFObj::~WFObj() +{ +} + +void WFObj::clear() +{ + for (size_t i = 0; i < sizeof(m_vertices)/sizeof(m_vertices[0]); i++) + m_vertices[i].clear(); + m_faces.clear(); + m_materials.clear(); + m_valid = false; + m_path = ""; + m_current_material_name = ""; +} + +bool WFObj::load(const char *fname, loadfile_t lf, loadtexture_t lt) +{ + m_loadfile = lf; + m_loadtexture = lt; + if (m_loadfile == NULL) + { + m_loadfile = loadfile; + } + + clear(); + + Buffer buff; + if (!m_loadfile(fname, buff)) + return false; + + m_path = fname; + + return load(buff); +} + +bool WFObj::load(const WFObj::Buffer &buff) +{ + string buildup; + size_t idx = 0; + while (idx < buff.length) + { + string input = trimString(getLine(buff, idx, &idx)); + 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); + } + + updateAABB(); + + m_valid = buildVBO(); + return m_valid; +} + +string WFObj::getLine(const Buffer & 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) +{ + vector tokens = tokenize(input); + + if (tokens.size() == 0) + return; + + string type = tokens[0]; + if (type == "v") + m_vertices[VERTEX].push_back(readVertex(tokens)); + else if (type == "vt") + m_vertices[VERTEX_TEXTURE].push_back(readVertex(tokens)); + else if (type == "vn") + m_vertices[VERTEX_NORMAL].push_back(readVertex(tokens)); + else if (type == "f") + { + if (m_faces.find(m_current_material_name) == m_faces.end()) + { + m_faces[m_current_material_name] = vector(); + m_num_materials = m_faces.size(); + } + vector faces = readFaces(tokens); + for (vector::iterator it = faces.begin(); it != faces.end(); it++) + m_faces[m_current_material_name].push_back(*it); + } + else if (type == "usemtl") + { + if (tokens.size() >= 2) + m_current_material_name = tokens[1]; + } + else if (type == "mtllib") + { + if (tokens.size() >= 2) + loadMaterial(tokens[1]); + } + else if (type == "s") + { + /* ignore smoothing */ + } + else if (type == "g") + { + /* ignore group name */ + } + else + { + cerr << "WFObj: warning: unhandled command '" << type << "'" << endl; + } +} + +void WFObj::updateAABB() +{ + bool firstVertex = true; + for (int i = 0, sz = m_vertices[VERTEX].size(); i < sz; i++) + { + Vertex & v = m_vertices[VERTEX][i]; + if (firstVertex) + { + m_aabb[0] = m_aabb[3] = v[0]; + m_aabb[1] = m_aabb[4] = v[1]; + m_aabb[2] = m_aabb[5] = v[2]; + firstVertex = false; + } + else + { + if (v[0] < m_aabb[0]) + m_aabb[0] = v[0]; + else if (v[0] > m_aabb[3]) + m_aabb[3] = v[0]; + if (v[1] < m_aabb[1]) + m_aabb[1] = v[1]; + else if (v[1] > m_aabb[4]) + m_aabb[4] = v[1]; + if (v[2] < m_aabb[2]) + m_aabb[2] = v[2]; + else if (v[2] > m_aabb[5]) + m_aabb[5] = v[2]; + } + } +} + +WFObj::Vertex WFObj::readVertex(const vector & parts) +{ + int partslen = parts.size(); + Vertex v; + for (int i = 1; i < partslen && i <= 4; i++) + { + v[i - 1] = atof(parts[i].c_str()); + } + return v; +} + +vector WFObj::readFaces(const std::vector & parts) +{ + vector faces; + VertexRef refs[4]; + int parts_len = parts.size(); + if (parts_len < 4 || parts_len > 5) + { + cerr << "WFObj: error: faces can only have 3 or 4 vertices!" << endl; + return faces; + } + for (int i = 1; i < parts_len; i++) + refs[i - 1] = readVertexRef(parts[i]); + Face f; + f.vertices[0] = refs[0]; + f.vertices[1] = refs[1]; + f.vertices[2] = refs[2]; + faces.push_back(f); + if (parts_len == 5) /* 4 vertex refs */ + { + f.vertices[0] = refs[2]; + f.vertices[1] = refs[3]; + f.vertices[2] = refs[0]; + faces.push_back(f); + } + return faces; +} + +WFObj::VertexRef WFObj::readVertexRef(const std::string ref) +{ + vector parts = splitString(ref, '/'); + VertexRef fr; + for (int i = 0, sz = parts.size(); i < sz; i++) + { + string idx_str = trimString(parts[i]); + if (idx_str.size() > 0) + { + int idx = atoi(idx_str.c_str()); + switch (i) + { + case 0: fr.vertex = idx; break; + case 1: fr.texture = idx; break; + case 2: fr.normal = idx; break; + } + } + } + return fr; +} + +void WFObj::loadMaterial(const std::string & name) +{ + Buffer buff; + string path = resolvePath(name); + if (!m_loadfile(path.c_str(), buff)) + { + cerr << "WFObj: error: couldn't open material file '" << path << "'" + << endl; + return; + } + + size_t idx = 0; + string mat_name; + while (idx < buff.length) + { + string input = trimString(getLine(buff, idx, &idx)); + if (input.size() == 0 || input[0] == '#') + continue; + vector tokens = tokenize(input); + if (tokens.size() < 2) + continue; + if (tokens[0] == "newmtl") + { + mat_name = tokens[1]; + m_materials[mat_name] = Material(); + } + else if (mat_name == "") + { + cerr << "WFObj: error: material directive" + " with no 'newmtl' statement" << endl; + } + else if (tokens[0] == "Ns") + { + m_materials[mat_name].shininess = atof(tokens[1].c_str()); + m_materials[mat_name].flags |= Material::SHININESS_BIT; + } + else if (tokens[0] == "Ka") + { + if (tokens.size() >= 4) + { + for (int i = 0; i < 3; i++) + m_materials[mat_name].ambient[i] = + atof(tokens[i + 1].c_str()); + m_materials[mat_name].ambient[3] = 1.0; + m_materials[mat_name].flags |= Material::AMBIENT_BIT; + } + else + cerr << "WFObj: error: 'Ka' material directive requires" + " 3 parameters" << endl; + } + else if (tokens[0] == "Kd") + { + if (tokens.size() >= 4) + { + for (int i = 0; i < 3; i++) + m_materials[mat_name].diffuse[i] = + atof(tokens[i + 1].c_str()); + m_materials[mat_name].diffuse[3] = 1.0; + m_materials[mat_name].flags |= Material::DIFFUSE_BIT; + } + else + cerr << "WFObj: error: 'Kd' material directive requires" + " 3 parameters" << endl; + } + else if (tokens[0] == "Ks") + { + if (tokens.size() >= 4) + { + for (int i = 0; i < 3; i++) + m_materials[mat_name].specular[i] = + atof(tokens[i + 1].c_str()); + m_materials[mat_name].specular[3] = 1.0; + m_materials[mat_name].flags |= Material::SPECULAR_BIT; + } + else + cerr << "WFObj: error: 'Ks' material directive requires" + " 3 parameters" << endl; + } + else if (tokens[0] == "Ni" || tokens[0] == "d" || tokens[0] == "illum") + { + /* ignore these parameters */ + } + else if (tokens[0] == "map_Kd") + { + m_materials[mat_name].texture = loadTexture(resolvePath(tokens[1])); + if (m_materials[mat_name].texture != 0) + m_materials[mat_name].flags |= Material::TEXTURE_BIT; + } + else + { + cerr << "WFObj: warning: unrecognized material directive: '" + << tokens[0] << "'" << endl; + } + } +} + +bool WFObj::loadfile(const char *path, Buffer & buff) +{ + struct stat st; + + if ( (stat(path, &st) == 0) && (st.st_size > 0) ) + { + int fd = open(path, O_RDONLY); + if (fd > 0) + { + buff.alloc(st.st_size); + int num_read = read(fd, buff.data, st.st_size); + close(fd); + if (num_read > 0) + return true; + } + } + + return false; +} + +bool WFObj::buildVBO() +{ + map flat_vertices; + int vid = 0, texture_ref_count = 0, n_vrefs = 0; + m_do_textures = m_loadtexture != NULL + && m_vertices[VERTEX_TEXTURE].size() > 0; + for (map >::iterator it = m_faces.begin(); + it != m_faces.end(); + it++) + { + if (m_materials.find(it->first) == m_materials.end()) + { + cerr << "Material '" << it->first << "' referenced in '" + << m_path << "' is not defined." << endl; + return false; + } + for (vector::iterator fit = it->second.begin(); + fit != it->second.end(); + fit++) + { + for (int i = 0; i < 3; i++) + { + VertexRef vf = fit->vertices[i]; + if ( (vf.vertex < 1) + || (vf.texture < 0) + || (vf.normal < 1) + || (vf.vertex > m_vertices[VERTEX].size()) + || (vf.normal > m_vertices[VERTEX_NORMAL].size()) + || (vf.texture > m_vertices[VERTEX_TEXTURE].size()) ) + { + cerr << "WFObj: error: invalid vertex reference (<" + << vf.vertex << ", " << vf.texture << ", " + << vf.normal << ">)" << endl; + return false; + } + if (vf.texture != 0) + texture_ref_count++; + if (flat_vertices.find(vf) == flat_vertices.end()) + { + flat_vertices[vf] = vid++; + } + n_vrefs++; + } + } + } + if (texture_ref_count == 0) + m_do_textures = false; + m_n_floats_per_vref = + 3 + /* vertex coordinates */ + 3 + /* normal coordinates */ + (m_do_textures ? 2 : 0); /* texture coordinates */ + size_t n_data_elements = m_n_floats_per_vref * flat_vertices.size(); + GLfloat * data = new GLfloat[n_data_elements]; + for (map::iterator it = flat_vertices.begin(); + it != flat_vertices.end(); + it++) + { + int base = m_n_floats_per_vref * it->second; + VertexRef vr = it->first; + data[base + 0] = m_vertices[VERTEX][vr.vertex - 1][0]; + data[base + 1] = m_vertices[VERTEX][vr.vertex - 1][1]; + data[base + 2] = m_vertices[VERTEX][vr.vertex - 1][2]; + data[base + 3] = m_vertices[VERTEX_NORMAL][vr.normal - 1][0]; + data[base + 4] = m_vertices[VERTEX_NORMAL][vr.normal - 1][1]; + data[base + 5] = m_vertices[VERTEX_NORMAL][vr.normal - 1][2]; + if (m_do_textures && vr.texture > 0) + { + data[base + 6] = m_vertices[VERTEX_TEXTURE][vr.texture - 1][0]; + data[base + 7] = m_vertices[VERTEX_TEXTURE][vr.texture - 1][1]; + } + } + GLshort * indices = new GLshort[n_vrefs]; + vid = 0; + for (map >::iterator it = m_faces.begin(); + it != m_faces.end(); + it++) + { + int first = vid; + for (vector::iterator fit = it->second.begin(); + fit != it->second.end(); + fit++) + { + for (int i = 0; i < 3; i++) + { + VertexRef vf = fit->vertices[i]; + indices[vid] = flat_vertices[vf]; + vid++; + } + } + m_materials[it->first].first_vertex = first; + m_materials[it->first].num_vertices = vid - first; + } + glGenBuffers(1, &m_data_vbo); + glGenBuffers(1, &m_index_vbo); + /* move data from client side to GL */ + glBindBuffer(GL_ARRAY_BUFFER, m_data_vbo); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_index_vbo); + glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * n_data_elements, data, + GL_STATIC_DRAW); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(GLshort) * n_vrefs, indices, + GL_STATIC_DRAW); + delete[] data; + delete[] indices; + return true; +} + +void WFObj::bindBuffers() +{ + glBindBuffer(GL_ARRAY_BUFFER, m_data_vbo); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_index_vbo); +} + +GLuint WFObj::loadTexture(const std::string & path) +{ + if (m_loadtexture == NULL) + return 0; + if (m_textures.find(path) != m_textures.end()) + return m_textures[path]; + GLuint id = m_loadtexture(path.c_str()); + m_textures[path] = id; + return id; +} + +string WFObj::resolvePath(const string & name) +{ + return (name[0] != '/') ? basePath(m_path) + name : name; +} + +bool WFObj::VertexRef::operator<(const VertexRef & other) const +{ + if (vertex != other.vertex) + return vertex < other.vertex; + if (texture != other.texture) + return texture < other.texture; + return normal < other.normal; +} diff --git a/src/client/wfobj/WFObj.h b/src/client/wfobj/WFObj.h new file mode 100644 index 0000000..c5ecbd7 --- /dev/null +++ b/src/client/wfobj/WFObj.h @@ -0,0 +1,138 @@ + +#ifndef WFOBJ_H +#define WFOBJ_H + +#ifdef GL_INCLUDE_FILE +#include GL_INCLUDE_FILE +#else +#include +#endif + +#include +#include +#include + +class WFObj +{ +public: + /* types */ + class Buffer + { + public: + Buffer() : m_alloc(false) {data = NULL; length = 0;} + ~Buffer() { if (m_alloc) delete data; } + uint8_t *data; + size_t length; + void alloc(size_t sz) + { + length = sz; + data = new uint8_t[sz]; + m_alloc = true; + } + protected: + bool m_alloc; + }; + + class Material + { + public: + enum { + SHININESS_BIT = 0x01, + AMBIENT_BIT = 0x02, + DIFFUSE_BIT = 0x04, + SPECULAR_BIT = 0x08, + TEXTURE_BIT = 0x10 + }; + Material() : flags(0) {} + GLfloat shininess; + GLfloat ambient[4]; + GLfloat diffuse[4]; + GLfloat specular[4]; + GLuint texture; + int flags; + int first_vertex; + int num_vertices; + }; + + typedef bool (*loadfile_t)(const char *fname, Buffer & buff); + typedef GLuint (*loadtexture_t)(const char *fname); + + enum { VERTEX, VERTEX_TEXTURE, VERTEX_NORMAL, VERTEX_TYPES }; + + /* constructors */ + WFObj(); + ~WFObj(); + + /* methods */ + bool load(const char *fname, loadfile_t lf = NULL, loadtexture_t lt = NULL); + bool load(const Buffer &buff); + const float * const getAABB() { return m_aabb; } + size_t getStride() { return sizeof(GLfloat) * m_n_floats_per_vref; } + size_t getVertexOffset() { return 0; } + size_t getNormalOffset() { return 3 * sizeof(GLfloat); } + size_t getTextureCoordOffset() { return 6 * sizeof(GLfloat); } + void bindBuffers(); + size_t getNumMaterials() { return m_num_materials; } + std::map & getMaterials() { return m_materials; } + bool doTextures() { return m_do_textures; } + +protected: + /* types */ + class Vertex + { + public: + float operator[](int idx) const { return data[idx]; } + float & operator[](int idx) { return data[idx]; } + float * getData() { return data; } + private: + float data[4]; + }; + + class VertexRef + { + public: + VertexRef() : vertex(0), texture(0), normal(0) {} + size_t vertex; + size_t texture; + size_t normal; + bool operator<(const VertexRef & other) const; + }; + + class Face + { + public: + VertexRef vertices[3]; + }; + + /* methods */ + void clear(); + void processInputLine(const std::string & input); + Vertex readVertex(const std::vector & parts); + std::vector readFaces(const std::vector & parts); + VertexRef readVertexRef(const std::string ref); + void updateAABB(); + static bool loadfile(const char *path, Buffer & buff); + std::string getLine(const Buffer & buff, size_t idx, size_t *update_idx); + void loadMaterial(const std::string & name); + bool buildVBO(); + GLuint loadTexture(const std::string & path); + std::string resolvePath(const std::string & name); + + /* variables */ + std::vector m_vertices[VERTEX_TYPES]; + std::map< std::string, std::vector< Face > > m_faces; + std::map< std::string, Material > m_materials; + float m_aabb[6]; + std::string m_path; + loadfile_t m_loadfile; + loadtexture_t m_loadtexture; + std::string m_current_material_name; + bool m_valid; + GLuint m_data_vbo, m_index_vbo; + bool m_do_textures; + size_t m_n_floats_per_vref; + size_t m_num_materials; + std::map m_textures; +}; + +#endif