/* * © Copyright 2014 – Urheberrechtshinweis * Alle Rechte vorbehalten / All Rights Reserved * * Programmcode ist urheberrechtlich geschuetzt. * Das Urheberrecht liegt, soweit nicht ausdruecklich anders gekennzeichnet, bei Frank Ebner. * Keine Verwendung ohne explizite Genehmigung. * (vgl. § 106 ff UrhG / § 97 UrhG) */ #ifndef OBJREADER_H #define OBJREADER_H #include #include #include #include "../../../geo/Point2.h" #include "../../../geo/Point3.h" /** * prase .obj files */ class OBJReader { public: /** group vertex+normal+texture */ struct VNT { int idxVertex; int idxNormal; int idxTexture; Point3 vertex; Point3 normal; Point2 texture; }; /** one triangle */ struct Face { VNT vnt[3]; Face(VNT v1, VNT v2, VNT v3) : vnt{v1,v2,v3} {;} }; /** one object within the file */ struct Object { std::string material; std::string name; std::vector faces; }; /** internal data */ struct Data { std::vector vertices; std::vector texCoords; std::vector normals; std::vector materialFiles; std::vector objects; Object& curObj() { if (objects.empty()) {objects.push_back(Object());} return objects.back(); } } data; public: /** ctor. use readXYZ() */ OBJReader() { ; } /** read .obj from the given file */ void readFile(const std::string& file) { std::ifstream is(file); std::string line; while(getline(is, line)) {parseLine(line);} is.close(); } /** read obj from the given data string (.obj file contents) */ void readData(const char* data) { readData(std::string(data)); } /** read obj from the given data string (.obj file contents) */ void readData(const std::string& data) { std::stringstream is(data); std::string line; while(getline(is, line)) {parseLine(line);} } /** get the parsed data */ const Data& getData() const {return data;} private: void replaceAll(std::string& str, const std::string& from, const std::string& to) { size_t start_pos = 0; while((start_pos = str.find(from, start_pos)) != std::string::npos) { size_t end_pos = start_pos + from.length(); str.replace(start_pos, end_pos, to); start_pos += to.length(); // In case 'to' contains 'from', like replacing 'x' with 'yx' } } /** remove empty strings from the vector */ std::vector nonEmpty(const std::vector& src) { std::vector res; for (const std::string& s : src) { if (!s.empty()) {res.push_back(s);} } return res; } template void split(const std::string &s, char delim, Out result) { std::stringstream ss(s); std::string item; while (std::getline(ss, item, delim)) { *(result++) = item; } } std::vector split(const std::string &s, char delim) { std::vector elems; split(s, delim, std::back_inserter(elems)); return elems; } /** parse one line of the .obj file */ void parseLine(std::string line) { if (line.length() < 2) {return;} // remove other linebreaks replaceAll(line, "\r", ""); const std::vector tokens = nonEmpty(split(line, ' ')); const std::string token = tokens.front(); if ("mtllib" == token) {data.materialFiles.push_back(tokens[1]);} if ("usemtl" == token) {data.curObj().material = tokens[1];} if ("v" == token) {parseVertex(tokens);} if ("vt" == token) {parseTexCoord(tokens);} if ("vn" == token) {parseNormal(tokens);} if ("f" == token) {parseFace(tokens);} if ("g" == token) {newObject(tokens[1]);} if ("o" == token) {newObject(tokens[1]);} } /** allocate a new object */ void newObject(const std::string& name) { Object o; o.name = name; data.objects.push_back(o); } /** parse one vertex from the tokenizer */ void parseVertex(const std::vector& t) { const float x = std::stof(t[1]); const float y = std::stof(t[2]); const float z = std::stof(t[3]); data.vertices.push_back(Point3(x,y,z)); } /** parse one texture-coordinate from the tokenizer */ void parseTexCoord(const std::vector& t) { const float u = std::stof(t[1]); const float v = std::stof(t[2]); data.texCoords.push_back(Point2(u, -v)); } /** parse one normal from the tokenizer */ void parseNormal(const std::vector& t) { const float x = std::stof(t[1]); const float y = std::stof(t[2]); const float z = std::stof(t[3]); data.normals.push_back(Point3(x,y,z)); } /** parse one face from the tokenizer */ void parseFace(const std::vector& t) { std::vector indices; int numVertices = 0; for (size_t i = 1; i < t.size(); ++i) { // one V/T/N const std::string entry = t[i]; const std::vector vtn = split(entry, '/'); ++numVertices; const std::string v = vtn[0]; const std::string vt = (vtn.size() > 1) ? (vtn[1]) : (""); const std::string vn = (vtn.size() > 2) ? (vtn[2]) : (""); //const std::string vt = t2.getToken('/', false); //const std::string vn = t2.getToken('/', false); // create a new vertex/normal/texture combination VNT vnt; vnt.idxVertex = (std::stoi(v) - 1); vnt.idxNormal = (vn.empty()) ? (-1) : (std::stoi(vn) - 1); vnt.idxTexture = (vt.empty()) ? (-1) : (std::stoi(vt) - 1); if (vnt.idxVertex >= 0) {vnt.vertex = data.vertices[vnt.idxVertex];} if (vnt.idxNormal >= 0) {vnt.normal = data.normals[vnt.idxNormal];} if (vnt.idxTexture >= 0) {vnt.texture = data.texCoords[vnt.idxTexture];} indices.push_back(vnt); } // this will both, create normal triangles and triangulate polygons // see: http://www.mathopenref.com/polygontriangles.html for (int i = 1; i < (int) indices.size()-1; ++i) { Face face(indices[0], indices[1], indices[i+1]); data.curObj().faces.push_back(face); } // sanity check // if (numVertices != 3) {throw "this face is not a triangle!";} } }; #endif // OBJREADER_H