diff options
-rw-r--r-- | src/model/Makefile.am | 2 | ||||
-rw-r--r-- | src/model/model.cc | 6 | ||||
-rw-r--r-- | src/model/objfile.cc | 353 | ||||
-rw-r--r-- | src/model/objfile.h | 113 |
4 files changed, 474 insertions, 0 deletions
diff --git a/src/model/Makefile.am b/src/model/Makefile.am index 3552392..dfd462e 100644 --- a/src/model/Makefile.am +++ b/src/model/Makefile.am @@ -12,6 +12,7 @@ noinst_HEADERS = \ mapfile.h \ material.h \ model.h \ + objfile.h \ primitives.h \ quad.h \ tags.h \ @@ -28,6 +29,7 @@ libmodel_la_SOURCES = \ mapfile.cc \ material.cc \ model.cc \ + objfile.cc \ primitives.cc \ quad.cc \ tags.cc \ diff --git a/src/model/model.cc b/src/model/model.cc index ab89dae..776eba7 100644 --- a/src/model/model.cc +++ b/src/model/model.cc @@ -8,6 +8,7 @@ #include "model/model.h" #include "model/asefile.h" #include "model/mapfile.h" +#include "model/objfile.h" #include "model/vertexarray.h" namespace model @@ -138,6 +139,11 @@ Model *Model::load(const std::string & name) // if it can't be found, try the ase model model = ASEFile::load(name); } + + if (!model) { + // if it can't be found, try the obj model + model = OBJFile::load(name); + } if (!model) { con_warn << "Could not open model " << name << std::endl; diff --git a/src/model/objfile.cc b/src/model/objfile.cc new file mode 100644 index 0000000..aa6b6cf --- /dev/null +++ b/src/model/objfile.cc @@ -0,0 +1,353 @@ +/* + model/objfile.cc + This file is part of the Osirion project and is distributed under + the terms of the GNU General Public License version 2 +*/ + +#include "sys/sys.h" +#include "model/objfile.h" + +#include <sstream> +#include <cstring> + +/* NOTE: Does NOT support reverse indexes. Cannot find an example + of this in use, or a program that will export using them. +*/ + +namespace model +{ + +// max geometry bounds +const float MAX_BOUNDS = 16384.0f; + +OBJFile::OBJFile(std::string const &name) +{ + objfile_name.append(name); + objfile_name.append(".obj"); + objfile_ifs.open(objfile_name); + + obj_box.assign(MAX_BOUNDS, - MAX_BOUNDS); + + // a single fragmentgroup will contain all the model polygons + obj_fragmentgroup = new FragmentGroup(); + obj_fragmentgroup->set_type(FragmentGroup::None); + + // reset counters + obj_normalcount = 0; +} + +OBJFile::~OBJFile() +{ + for (VertexList::iterator it = obj_vertexlist.begin(); it != obj_vertexlist.end(); it++) { + delete(*it).second; + } + obj_vertexlist.clear(); + + for (NormalList::iterator it = obj_normallist.begin(); it != obj_normallist.end(); it++) { + delete(*it).second; + } + obj_normallist.clear(); + + for (UVList::iterator it = obj_uvlist.begin(); it != obj_uvlist.end(); it++) { + delete(*it).second; + } + obj_uvlist.clear(); + + for (TriList::iterator it = obj_trilist.begin(); it != obj_trilist.end(); it++) { + delete(*it).second; + } + obj_trilist.clear(); + + for (QuadList::iterator it = obj_quadlist.begin(); it != obj_quadlist.end(); it++) { + delete(*it).second; + } + obj_quadlist.clear(); + + if (objfile_ifs.is_open()) + objfile_ifs.close(); +} + +bool OBJFile::read() +{ + char data[1024]; + memset(data, 0, sizeof(data)); + + math::Vector3f zero; + + size_t current_m = 0; + + Fragment *fragment = 0; + Material *material = 0; + + while (objfile_ifs.getline(data, sizeof(data) - 1)) { + std::istringstream line(data); + + std::string word; + line >> word; + + if( word.compare("usemtl") == 0) { /* material definition */ + + std::string materialname; + material = 0; + + if(line >> materialname) { + for(MaterialList::iterator it = obj_materiallist.begin(); it != obj_materiallist.end(); ++it) { + if( (*it).second->name() == materialname) { + material = (*it).second; + current_m = (*it).first; + break; + } + } + + if(!material) { + material = Material::find(materialname); + + if(material) + obj_materiallist[obj_materiallist.size()] = material; + else { + material = new Material(materialname); + Material::add(material); + material->set_texture(materialname); + + obj_materiallist[obj_materiallist.size()] = material; + } + current_m = obj_materiallist.size()-1; + } + } + else + con_warn << "Invalid material definition" << std::endl; + + + } else if( word.compare("v") == 0) { /* New wertex */ + float x, y, z; + line >> x >> y >> z; + + math::Vector3f *v = new math::Vector3f(x, y, z); + obj_vertexlist[obj_vertexlist.size()] = v; + obj_box.expand(*v * SCALE); + + } else if( word.compare("vt") == 0) { /* wertex texture coordinate */ + float u, v; + line >> u >> v; + + math::Vector2f *uv = new math::Vector2f(u, v); + obj_uvlist[obj_uvlist.size()] = uv; + + } else if( word.compare("vn") == 0) { /* wertex wormal */ + float x, y, z; + line >> x >> y >> z; + + math::Vector3f *nm = new math::Vector3f(x, y, z); + nm->normalize(); + obj_normallist[obj_normalcount] = nm; + obj_normalcount++; + + } else if( word.compare("f") == 0) { /* Face/Polygon */ + size_t v[4]; + size_t uv[4]; + size_t vn[4]; + + bool have_vn[4], have_uv[4]; + size_t nslash; + + std::string strbuf(line.str()); + strbuf.erase(0,2); //remove "f " + + std::stringstream tmp(strbuf); + + std::vector<std::string> points; + int i=0; + while(!tmp.eof()) { + nslash = 0; + + have_vn[i] = 0; + have_uv[i] = 0; + + std::string point; + tmp >> point; + + if(point.compare(" ") == 0) //bordercase for space at end of face definition + continue; + + for(size_t c=0; c<point.length(); c++) { + if(point[c] == '/') { + nslash++; + have_uv[i]=true; + point[c] = ' '; + } + if(point[c] == ' ' && point[c+1] == '/') { + have_uv[i]=false; + have_vn[i]=true; + nslash=2; + point.erase(c+1,1); + } + } + + if(nslash==2 && have_vn[i] == false) { + have_vn[i] = true; + have_uv[i] = true; + } + points.push_back(point); + + std::stringstream tmp2(point); + tmp2 >> v[i]; + if(have_uv[i]) tmp2 >> uv[i]; + if(have_vn[i]) tmp2 >> vn[i]; + i++; + } + + if(points.size() != 4 && points.size() != 3) { + con_warn << "Warning! " << objfile_name << ": Faces without 3 or 4 vertices will be ignored!" << std::endl; + points.clear(); + continue; + } + + math::Vector3f *v0 = obj_vertexlist.find(v[0]-1)->second; + math::Vector3f *v1 = obj_vertexlist.find(v[1]-1)->second; + math::Vector3f *v2 = obj_vertexlist.find(v[2]-1)->second; + math::Vector3f *v3 = obj_vertexlist.find(v[3]-1)->second; + + math::Vector3f *n0 = obj_normallist[vn[0]-1]; + math::Vector3f *n1 = obj_normallist[vn[1]-1]; + math::Vector3f *n2 = obj_normallist[vn[2]-1]; + math::Vector3f *n3 = obj_normallist[vn[3]-1]; + + math::Vector2f *t0 = obj_uvlist.find(uv[0]-1)->second; + math::Vector2f *t1 = obj_uvlist.find(uv[1]-1)->second; + math::Vector2f *t2 = obj_uvlist.find(uv[2]-1)->second; + math::Vector2f *t3 = obj_uvlist.find(uv[3]-1)->second; + + if(points.size() == 3) { + if(!v0 || !v1 || !v2) { + con_warn << "Warning! " << objfile_name << ": Invalid vertex!" << std::endl; + continue; + } + + Triangle *triangle = new Triangle(*v0, *v1, *v2); + + if(!n0) n0 = new math::Vector3f(normal(triangle->v0(), triangle->v1(), triangle->v2())); + if(!n1) n1 = new math::Vector3f(normal(triangle->v1(), triangle->v2(), triangle->v0())); + if(!n2) n2 = new math::Vector3f(normal(triangle->v2(), triangle->v0(), triangle->v1())); + + triangle->n0().assign(n0->x(), n0->y(), n0->z()); + triangle->n1().assign(n1->x(), n1->y(), n1->z()); + triangle->n2().assign(n2->x(), n2->y(), n2->z()); + + if(t0) triangle->t0().assign(t0->x(), 1.0 - t0->y()); + if(t1) triangle->t1().assign(t1->x(), 1.0 - t1->y()); + if(t2) triangle->t2().assign(t2->x(), 1.0 - t2->y()); + + std::pair<size_t, Triangle *> poly(current_m, triangle); + obj_trilist.push_back(poly); + + } else if(points.size() == 4) { + + if(!v0 || !v1 || !v2 || !v3) { + con_warn << "Warning! " << objfile_name << ": Invalid vertex!" << std::endl; + continue; + } + + Quad *quad = new Quad(*v0, *v1, *v2, *v3, zero); + + if(!n0) n0 = new math::Vector3f(normal(quad->v0(), quad->v1(), quad->v3())); + if(!n1) n1 = new math::Vector3f(normal(quad->v1(), quad->v2(), quad->v0())); + if(!n2) n2 = new math::Vector3f(normal(quad->v2(), quad->v3(), quad->v1())); + if(!n3) n3 = new math::Vector3f(normal(quad->v3(), quad->v0(), quad->v2())); + + quad->n0().assign(n0->x(), n0->y(), n0->z()); + quad->n1().assign(n1->x(), n1->y(), n1->z()); + quad->n2().assign(n2->x(), n2->y(), n2->z()); + quad->n3().assign(n3->x(), n3->y(), n3->z()); + + if(t0) quad->t0().assign(t0->x(), 1.0 - t0->y()); + if(t1) quad->t1().assign(t1->x(), 1.0 - t1->y()); + if(t2) quad->t2().assign(t2->x(), 1.0 - t2->y()); + if(t3) quad->t3().assign(t3->x(), 1.0 - t3->y()); + + std::pair<size_t, Quad *> poly(current_m, quad); + obj_quadlist.push_back(poly); + } + } + } + + //Go through tri and quad lists and create fragments for each material + size_t total_m = obj_materiallist.size(); + for(size_t i = 0; i <= total_m; i++) { + material = obj_materiallist[i]; + fragment = new Fragment(Fragment::Triangles, material); + + for(TriList::iterator it = obj_trilist.begin(); it != obj_trilist.end(); ++it) { + if((*it).first == i) { + fragment->add_vertex(((*it).second->v0() * SCALE) , (*it).second->n0(), (*it).second->t0(), false); + fragment->add_vertex(((*it).second->v1() * SCALE) , (*it).second->n1(), (*it).second->t1(), false); + fragment->add_vertex(((*it).second->v2() * SCALE) , (*it).second->n2(), (*it).second->t2(), false); + } + } + + if( fragment->structural_size() + fragment->detail_size() > 0 ) + obj_fragmentgroup->add_fragment(fragment); + + fragment = new Fragment(Fragment::Quads, material); + + for(QuadList::iterator it = obj_quadlist.begin(); it != obj_quadlist.end(); ++it) { + if((*it).first == i) { + fragment->add_vertex(((*it).second->v0() * SCALE) , (*it).second->n0(), (*it).second->t0(), false); + fragment->add_vertex(((*it).second->v1() * SCALE) , (*it).second->n1(), (*it).second->t1(), false); + fragment->add_vertex(((*it).second->v2() * SCALE) , (*it).second->n2(), (*it).second->t2(), false); + fragment->add_vertex(((*it).second->v3() * SCALE) , (*it).second->n3(), (*it).second->t3(), false); + } + } + + if( fragment->structural_size() + fragment->detail_size() > 0 ) + obj_fragmentgroup->add_fragment(fragment); + } + + return true; +} + +Model *OBJFile::load(const std::string &name) +{ + OBJFile objfile(name); + + if (!objfile.is_open()) { + return 0; + } + + if (!objfile.read()) { + return 0; + } + + if (!objfile.fragmentgroup()->size()) + return 0; + + // create a new model + Model *model = new Model(name); + + // center model around (0,0,0) and set the bounding box + math::Vector3f obj_center((objfile.box().min() + objfile.box().max()) * 0.5f); + model->model_box.assign( + objfile.box().min() - obj_center, + objfile.box().max() - obj_center + ); + model->set_radius(model->box().max().length()); + model->set_origin(obj_center * -1.0f); + + objfile.fragmentgroup()->set_location(obj_center * -1.0f); + + for (FragmentGroup::Fragments::const_iterator fit = objfile.fragmentgroup()->fragments().begin(); fit != objfile.fragmentgroup()->fragments().end(); fit++) { + const Fragment *fragment = (*fit); + + if(fragment->type() == Fragment::Triangles) + model->model_tris_count += (fragment->structural_size() + fragment->detail_size()) / 3; + else + model->model_quad_count += (fragment->structural_size() + fragment->detail_size()) / 4; + } + + model->add_group(objfile.fragmentgroup()); + + con_debug << " " << objfile.name() << " " << objfile.vertexcount() << " vertices " << model->model_tris_count << " triangles " << model->model_quad_count << " quads" << std::endl; + + return model; +} + +} // namespace model diff --git a/src/model/objfile.h b/src/model/objfile.h new file mode 100644 index 0000000..4e7f0c7 --- /dev/null +++ b/src/model/objfile.h @@ -0,0 +1,113 @@ +/* + model/objfile.h + This file is part of the Osirion project and is distributed under + the terms of the GNU General Public License version 2 +*/ + +/* http://www.royriggs.com/obj.html + http://en.wikipedia.org/wiki/Wavefront_.obj_file */ + +#ifndef __INCLUDED_MODEL_OBJFILE_H__ +#define __INCLUDED_MODEL_OBJFILE_H__ + +#include "math/vector3f.h" +#include "model/model.h" +#include "model/material.h" +#include "model/fragment.h" +#include "model/triangle.h" +#include "model/quad.h" +#include "filesystem/filestream.h" + +#include <string> +#include <map> +#include <vector> +#include <utility> + +namespace model +{ + +/// class to parse the .obj file structure and load geometry data into a model +class OBJFile +{ +public: + /** + * @brief load a .obj file into a Model + * @param name name of the model to be loaded, without .obj extension or models/ prefix + * If the file can not be read, load() returns the NULL-pointer + */ + static Model *load(std::string const &name); + +private: + + /** + * @brief type definition for a list of materials in the ASE file + */ + typedef std::map<size_t, Material *> MaterialList; + + /** + * @brief type definition for a list of vertices + */ + typedef std::map<size_t, math::Vector3f *> VertexList; + typedef std::map<size_t, math::Vector3f *> NormalList; + typedef std::map<size_t, math::Vector2f *> UVList; + + /** + * @brief type definition for a list of faces + */ + typedef std::list<std::pair<size_t, Triangle *> > TriList; + typedef std::list<std::pair<size_t, Quad *> > QuadList; + + OBJFile(std::string const &name); + ~OBJFile(); + + /** + * @brief read the .obj file + */ + bool read(); + + /** + * @brief parse a line in the .obj file. + */ + void readline(std::string const &line); + + inline const std::string &name() const { + return objfile_name; + } + + inline bool is_open() { + return objfile_ifs.is_open(); + } + + inline size_t vertexcount() const { + return obj_vertexlist.size(); + } + + inline FragmentGroup *fragmentgroup() { + return obj_fragmentgroup; + } + + inline const math::BoundingBox3f & box() const { + return obj_box; + } + + std::string objfile_name; + + filesystem::IFileStream objfile_ifs; + + MaterialList obj_materiallist; + VertexList obj_vertexlist; + UVList obj_uvlist; + NormalList obj_normallist; + TriList obj_trilist; + QuadList obj_quadlist; + + math::BoundingBox3f obj_box; + + FragmentGroup *obj_fragmentgroup; + + size_t obj_normalcount; +}; + +} // namespace model + +#endif // __INCLUDED_MODEL_OBJFILE_H__ |