wfobj/WFObj.cc
2011-04-24 22:46:56 -04:00

532 lines
14 KiB
C++

#define GL_GLEXT_PROTOTYPES
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <ctype.h> /* isspace() */
#include <string.h> /* strlen() */
#include <stdlib.h> /* atoi */
#include <GL/gl.h>
#include <vector>
#include <string>
#include <map>
#include <iostream>
#include <fstream>
#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<string> tokenize(const string & input)
{
vector<string> tokens;
string in = input;
for (;;)
{
string token = stripFirstToken(in);
if (token == "")
break;
tokens.push_back(token);
}
return tokens;
}
static vector<string> splitString(const string & str, char delim)
{
vector<string> 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(loadfile_t lf, loadtexture_t lt)
{
m_loadfile = lf;
m_loadtexture = lt;
if (m_loadfile == NULL)
{
m_loadfile = loadfile;
}
if (m_valid)
{
glDeleteBuffers(1, &m_data_vbo);
glDeleteBuffers(1, &m_index_vbo);
}
m_valid = false;
clear();
}
WFObj::~WFObj()
{
}
void WFObj::clear()
{
for (int i = 0; i < sizeof(m_vertices)/sizeof(m_vertices[0]); i++)
m_vertices[i].clear();
m_faces.clear();
m_face_indices.clear();
m_materials.clear();
m_valid = false;
m_path = "";
m_current_material_name = "";
}
bool WFObj::load(const char *fname)
{
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();
buildVBO();
m_valid = true;
return true;
}
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<string> 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<Face>();
vector<Face> faces = readFaces(tokens);
for (vector<Face>::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<string> & 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::Face> WFObj::readFaces(const std::vector<std::string> & parts)
{
vector<Face> 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<string> 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 = (name[0] != '/') ? basePath(m_path) + name : 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<string> 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::MAT_SHININESS;
}
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].flags |= Material::MAT_AMBIENT;
}
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].flags |= Material::MAT_DIFFUSE;
}
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].flags |= Material::MAT_SPECULAR;
}
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
{
cerr << "WFObj: error: 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<VertexRef, int> flat_vertices;
int vid = 0, texture_ref_count = 0;
bool do_textures = m_loadtexture != NULL
&& m_vertices[VERTEX_TEXTURE].size() > 0;
for (map<string, vector<Face> >::iterator it = m_faces.begin();
it != m_faces.end();
it++)
{
int first = vid;
int num = 0;
for (vector<Face>::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++;
num++;
}
}
}
m_face_indices[it->first] = make_pair(first, num);
}
if (texture_ref_count == 0)
do_textures = false;
size_t n_floats_per_vref =
3 + /* vertex coordinates */
3 + /* normal coordinates */
(do_textures ? 2 : 0); /* texture coordinates */
size_t n_data_elements = n_floats_per_vref * flat_vertices.size();
GLfloat * data = new GLfloat[n_data_elements];
for (map<VertexRef, int>::iterator it = flat_vertices.begin();
it != flat_vertices.end();
it++)
{
int base = 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 (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];
}
}
glGenBuffers(1, &m_data_vbo);
glGenBuffers(1, &m_index_vbo);
/* move data from client side to GL */
glBufferData(m_data_vbo, sizeof(GLfloat) * n_data_elements, data,
GL_STATIC_DRAW);
delete[] data;
return true;
}
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;
}