// OBJ_Loader.h - A Single Header OBJ Model Loader /* MIT License Copyright(c) 2016 Robert Smith Permission is hereby granted, free of charge, to any person obtaining a copy of this softwareand associated documentation files(the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and /or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions : The above copyright noticeand this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #pragma once // Iostream - STD I/O Library #include // Vector - STD Vector/Array Library #include // String - STD String Library #include // fStream - STD File I/O Library #include // Math.h - STD math Library #include // Print progress to console while loading (large models) #define OBJL_CONSOLE_OUTPUT // Namespace: OBJL // // Description: The namespace that holds eveyrthing that // is needed and used for the OBJ Model Loader namespace objl { // Structure: Vector2 // // Description: A 2D Vector that Holds Positional Data struct Vector2 { // Default Constructor Vector2() { X = 0.0f; Y = 0.0f; } // Variable Set Constructor Vector2(float X_, float Y_) { X = X_; Y = Y_; } // Bool Equals Operator Overload bool operator==(const Vector2& other) const { return (this->X == other.X && this->Y == other.Y); } // Bool Not Equals Operator Overload bool operator!=(const Vector2& other) const { return !(this->X == other.X && this->Y == other.Y); } // Addition Operator Overload Vector2 operator+(const Vector2& right) const { return Vector2(this->X + right.X, this->Y + right.Y); } // Subtraction Operator Overload Vector2 operator-(const Vector2& right) const { return Vector2(this->X - right.X, this->Y - right.Y); } // Float Multiplication Operator Overload Vector2 operator*(const float& other) const { return Vector2(this->X *other, this->Y * other); } // Positional Variables float X; float Y; }; // Structure: Vector3 // // Description: A 3D Vector that Holds Positional Data struct Vector3 { // Default Constructor Vector3() { X = 0.0f; Y = 0.0f; Z = 0.0f; } // Variable Set Constructor Vector3(float X_, float Y_, float Z_) { X = X_; Y = Y_; Z = Z_; } // Bool Equals Operator Overload bool operator==(const Vector3& other) const { return (this->X == other.X && this->Y == other.Y && this->Z == other.Z); } // Bool Not Equals Operator Overload bool operator!=(const Vector3& other) const { return !(this->X == other.X && this->Y == other.Y && this->Z == other.Z); } // Addition Operator Overload Vector3 operator+(const Vector3& right) const { return Vector3(this->X + right.X, this->Y + right.Y, this->Z + right.Z); } // Subtraction Operator Overload Vector3 operator-(const Vector3& right) const { return Vector3(this->X - right.X, this->Y - right.Y, this->Z - right.Z); } // Float Multiplication Operator Overload Vector3 operator*(const float& other) const { return Vector3(this->X * other, this->Y * other, this->Z * other); } // Float Division Operator Overload Vector3 operator/(const float& other) const { return Vector3(this->X / other, this->Y / other, this->Z / other); } // Positional Variables float X; float Y; float Z; }; // Structure: Vertex // // Description: Model Vertex object that holds // a Position, Normal, and Texture Coordinate struct Vertex { // Position Vector Vector3 Position; // Normal Vector Vector3 normal; // Texture Coordinate Vector Vector2 TextureCoordinate; }; struct Material { Material() { name; Ns = 0.0f; Ni = 0.0f; d = 0.0f; illum = 0; } // Material Name std::string name; // Ambient Color Vector3 Ka; // Diffuse Color Vector3 Kd; // Specular Color Vector3 Ks; // Specular Exponent float Ns; // Optical Density float Ni; // Dissolve float d; // Illumination int illum; // Ambient Texture Map std::string map_Ka; // Diffuse Texture Map std::string map_Kd; // Specular Texture Map std::string map_Ks; // Specular Hightlight Map std::string map_Ns; // Alpha Texture Map std::string map_d; // Bump Map std::string map_bump; }; // Structure: Mesh // // Description: A Simple Mesh Object that holds // a name, a vertex list, and an index list struct Mesh { // Default Constructor Mesh() { } // Variable Set Constructor Mesh(std::vector& _Vertices, std::vector& _Indices) { Vertices = _Vertices; Indices = _Indices; } // Mesh Name std::string MeshName; // Vertex List std::vector Vertices; // Index List std::vector Indices; // Material Material MeshMaterial; }; // Namespace: Math // // Description: The namespace that holds all of the math // functions need for OBJL namespace math { // Vector3 Cross Product Vector3 CrossV3(const Vector3 a, const Vector3 b) { return Vector3(a.Y * b.Z - a.Z * b.Y, a.Z * b.X - a.X * b.Z, a.X * b.Y - a.Y * b.X); } // Vector3 Magnitude Calculation float MagnitudeV3(const Vector3 in) { return (sqrtf(powf(in.X, 2) + powf(in.Y, 2) + powf(in.Z, 2))); } // Vector3 DotProduct float DotV3(const Vector3 a, const Vector3 b) { return (a.X * b.X) + (a.Y * b.Y) + (a.Z * b.Z); } // Angle between 2 Vector3 Objects float AngleBetweenV3(const Vector3 a, const Vector3 b) { float angle = DotV3(a, b); angle /= (MagnitudeV3(a) * MagnitudeV3(b)); return angle = acosf(angle); } // Projection Calculation of a onto b Vector3 ProjV3(const Vector3 a, const Vector3 b) { Vector3 bn = b / MagnitudeV3(b); return bn * DotV3(a, bn); } } // Namespace: Algorithm // // Description: The namespace that holds all of the // Algorithms needed for OBJL namespace algorithm { // Vector3 Multiplication Opertor Overload Vector3 operator*(const float& left, const Vector3& right) { return Vector3(right.X * left, right.Y * left, right.Z * left); } // A test to see if P1 is on the same side as P2 of a line segment ab bool SameSide(Vector3 p1, Vector3 p2, Vector3 a, Vector3 b) { Vector3 cp1 = math::CrossV3(b - a, p1 - a); Vector3 cp2 = math::CrossV3(b - a, p2 - a); if (math::DotV3(cp1, cp2) >= 0) return true; else return false; } // Generate a cross produect normal for a triangle Vector3 GenTriNormal(Vector3 t1, Vector3 t2, Vector3 t3) { Vector3 u = t2 - t1; Vector3 v = t3 - t1; Vector3 normal = math::CrossV3(u,v); return normal; } // Check to see if a Vector3 Point is within a 3 Vector3 Triangle bool inTriangle(Vector3 point, Vector3 tri1, Vector3 tri2, Vector3 tri3) { // Test to see if it is within an infinite prism that the triangle outlines. bool within_tri_prisim = SameSide(point, tri1, tri2, tri3) && SameSide(point, tri2, tri1, tri3) && SameSide(point, tri3, tri1, tri2); // If it isn't it will never be on the triangle if (!within_tri_prisim) return false; // Calulate Triangle's Normal Vector3 n = GenTriNormal(tri1, tri2, tri3); // Project the point onto this normal Vector3 proj = math::ProjV3(point, n); // If the distance from the triangle to the point is 0 // it lies on the triangle if (math::MagnitudeV3(proj) == 0) return true; else return false; } // Split a String into a string array at a given token inline void split(const std::string &in, std::vector &out, std::string token) { out.clear(); std::string temp; for (int i = 0; i < int(in.size()); i++) { std::string test = in.substr(i, token.size()); if (test == token) { if (!temp.empty()) { out.push_back(temp); temp.clear(); i += (int)token.size() - 1; } else { out.push_back(""); } } else if (i + token.size() >= in.size()) { temp += in.substr(i, token.size()); out.push_back(temp); break; } else { temp += in[i]; } } } // Get tail of string after first token and possibly following spaces inline std::string tail(const std::string &in) { size_t token_start = in.find_first_not_of(" \t"); size_t space_start = in.find_first_of(" \t", token_start); size_t tail_start = in.find_first_not_of(" \t", space_start); size_t tail_end = in.find_last_not_of(" \t"); if (tail_start != std::string::npos && tail_end != std::string::npos) { return in.substr(tail_start, tail_end - tail_start + 1); } else if (tail_start != std::string::npos) { return in.substr(tail_start); } return ""; } // Get first token of string inline std::string firstToken(const std::string &in) { if (!in.empty()) { size_t token_start = in.find_first_not_of(" \t"); size_t token_end = in.find_first_of(" \t", token_start); if (token_start != std::string::npos && token_end != std::string::npos) { return in.substr(token_start, token_end - token_start); } else if (token_start != std::string::npos) { return in.substr(token_start); } } return ""; } // Get element at given index position template inline const T & getElement(const std::vector &elements, std::string &index) { int idx = std::stoi(index); if (idx < 0) idx = int(elements.size()) + idx; else idx--; return elements[idx]; } } // Class: Loader // // Description: The OBJ Model Loader class Loader { public: // Default Constructor Loader() { } ~Loader() { LoadedMeshes.clear(); } // Load a file into the loader // // If file is loaded return true // // If the file is unable to be found // or unable to be loaded return false bool LoadFile(std::string Path) { // If the file is not an .obj file return false if (Path.substr(Path.size() - 4, 4) != ".obj") return false; std::ifstream file(Path); if (!file.is_open()) return false; LoadedMeshes.clear(); LoadedVertices.clear(); LoadedIndices.clear(); std::vector Positions; std::vector TCoords; std::vector Normals; std::vector Vertices; std::vector Indices; std::vector MeshMatNames; bool listening = false; std::string meshname; Mesh tempMesh; #ifdef OBJL_CONSOLE_OUTPUT const unsigned int outputEveryNth = 1000; unsigned int outputIndicator = outputEveryNth; #endif std::string curline; while (std::getline(file, curline)) { #ifdef OBJL_CONSOLE_OUTPUT if ((outputIndicator = ((outputIndicator + 1) % outputEveryNth)) == 1) { if (!meshname.empty()) { std::cout << "\r- " << meshname << "\t| vertices > " << Positions.size() << "\t| texcoords > " << TCoords.size() << "\t| normals > " << Normals.size() << "\t| triangles > " << (Vertices.size() / 3) << (!MeshMatNames.empty() ? "\t| material: " + MeshMatNames.back() : ""); } } #endif // Generate a Mesh Object or Prepare for an object to be created if (algorithm::firstToken(curline) == "o" || algorithm::firstToken(curline) == "g" || curline[0] == 'g') { if (!listening) { listening = true; if (algorithm::firstToken(curline) == "o" || algorithm::firstToken(curline) == "g") { meshname = algorithm::tail(curline); } else { meshname = "unnamed"; } } else { // Generate the mesh to put into the array if (!Indices.empty() && !Vertices.empty()) { // Create Mesh tempMesh = Mesh(Vertices, Indices); tempMesh.MeshName = meshname; // Insert Mesh LoadedMeshes.push_back(tempMesh); // Cleanup Vertices.clear(); Indices.clear(); meshname.clear(); meshname = algorithm::tail(curline); } else { if (algorithm::firstToken(curline) == "o" || algorithm::firstToken(curline) == "g") { meshname = algorithm::tail(curline); } else { meshname = "unnamed"; } } } #ifdef OBJL_CONSOLE_OUTPUT std::cout << std::endl; outputIndicator = 0; #endif } // Generate a Vertex Position if (algorithm::firstToken(curline) == "v") { std::vector spos; Vector3 vpos; algorithm::split(algorithm::tail(curline), spos, " "); vpos.X = std::stof(spos[0]); vpos.Y = std::stof(spos[1]); vpos.Z = std::stof(spos[2]); Positions.push_back(vpos); } // Generate a Vertex Texture Coordinate if (algorithm::firstToken(curline) == "vt") { std::vector stex; Vector2 vtex; algorithm::split(algorithm::tail(curline), stex, " "); vtex.X = std::stof(stex[0]); vtex.Y = std::stof(stex[1]); TCoords.push_back(vtex); } // Generate a Vertex Normal; if (algorithm::firstToken(curline) == "vn") { std::vector snor; Vector3 vnor; algorithm::split(algorithm::tail(curline), snor, " "); vnor.X = std::stof(snor[0]); vnor.Y = std::stof(snor[1]); vnor.Z = std::stof(snor[2]); Normals.push_back(vnor); } // Generate a Face (vertices & indices) if (algorithm::firstToken(curline) == "f") { // Generate the vertices std::vector vVerts; GenVerticesFromRawOBJ(vVerts, Positions, TCoords, Normals, curline); // Add Vertices for (int i = 0; i < int(vVerts.size()); i++) { Vertices.push_back(vVerts[i]); LoadedVertices.push_back(vVerts[i]); } std::vector iIndices; VertexTriangluation(iIndices, vVerts); // Add Indices for (int i = 0; i < int(iIndices.size()); i++) { unsigned int indnum = (unsigned int)((Vertices.size()) - vVerts.size()) + iIndices[i]; Indices.push_back(indnum); indnum = (unsigned int)((LoadedVertices.size()) - vVerts.size()) + iIndices[i]; LoadedIndices.push_back(indnum); } } // Get Mesh Material Name if (algorithm::firstToken(curline) == "usemtl") { MeshMatNames.push_back(algorithm::tail(curline)); // Create new Mesh, if Material changes within a group if (!Indices.empty() && !Vertices.empty()) { // Create Mesh tempMesh = Mesh(Vertices, Indices); tempMesh.MeshName = meshname; int i = 2; while(1) { tempMesh.MeshName = meshname + "_" + std::to_string(i); for (auto &m : LoadedMeshes) if (m.MeshName == tempMesh.MeshName) continue; break; } // Insert Mesh LoadedMeshes.push_back(tempMesh); // Cleanup Vertices.clear(); Indices.clear(); } #ifdef OBJL_CONSOLE_OUTPUT outputIndicator = 0; #endif } // Load Materials if (algorithm::firstToken(curline) == "mtllib") { // Generate LoadedMaterial // Generate a path to the material file std::vector temp; algorithm::split(Path, temp, "/"); std::string pathtomat = ""; if (temp.size() != 1) { for (int i = 0; i < temp.size() - 1; i++) { pathtomat += temp[i] + "/"; } } pathtomat += algorithm::tail(curline); #ifdef OBJL_CONSOLE_OUTPUT std::cout << std::endl << "- find materials in: " << pathtomat << std::endl; #endif // Load Materials LoadMaterials(pathtomat); } } #ifdef OBJL_CONSOLE_OUTPUT std::cout << std::endl; #endif // Deal with last mesh if (!Indices.empty() && !Vertices.empty()) { // Create Mesh tempMesh = Mesh(Vertices, Indices); tempMesh.MeshName = meshname; // Insert Mesh LoadedMeshes.push_back(tempMesh); } file.close(); // Set Materials for each Mesh for (int i = 0; i < MeshMatNames.size(); i++) { std::string matname = MeshMatNames[i]; // Find corresponding material name in loaded materials // when found copy material variables into mesh material for (int j = 0; j < LoadedMaterials.size(); j++) { if (LoadedMaterials[j].name == matname) { LoadedMeshes[i].MeshMaterial = LoadedMaterials[j]; break; } } } if (LoadedMeshes.empty() && LoadedVertices.empty() && LoadedIndices.empty()) { return false; } else { return true; } } // Loaded Mesh Objects std::vector LoadedMeshes; // Loaded Vertex Objects std::vector LoadedVertices; // Loaded Index Positions std::vector LoadedIndices; // Loaded Material Objects std::vector LoadedMaterials; private: // Generate vertices from a list of positions, // tcoords, normals and a face line void GenVerticesFromRawOBJ(std::vector& oVerts, const std::vector& iPositions, const std::vector& iTCoords, const std::vector& iNormals, std::string icurline) { std::vector sface, svert; Vertex vVert; algorithm::split(algorithm::tail(icurline), sface, " "); bool noNormal = false; // For every given vertex do this for (int i = 0; i < int(sface.size()); i++) { // See What type the vertex is. int vtype; algorithm::split(sface[i], svert, "/"); // Check for just position - v1 if (svert.size() == 1) { // Only position vtype = 1; } // Check for position & texture - v1/vt1 if (svert.size() == 2) { // Position & Texture vtype = 2; } // Check for Position, Texture and Normal - v1/vt1/vn1 // or if Position and Normal - v1//vn1 if (svert.size() == 3) { if (svert[1] != "") { // Position, Texture, and Normal vtype = 4; } else { // Position & Normal vtype = 3; } } // Calculate and store the vertex switch (vtype) { case 1: // P { vVert.Position = algorithm::getElement(iPositions, svert[0]); vVert.TextureCoordinate = Vector2(0, 0); noNormal = true; oVerts.push_back(vVert); break; } case 2: // P/T { vVert.Position = algorithm::getElement(iPositions, svert[0]); vVert.TextureCoordinate = algorithm::getElement(iTCoords, svert[1]); noNormal = true; oVerts.push_back(vVert); break; } case 3: // P//N { vVert.Position = algorithm::getElement(iPositions, svert[0]); vVert.TextureCoordinate = Vector2(0, 0); vVert.normal = algorithm::getElement(iNormals, svert[2]); oVerts.push_back(vVert); break; } case 4: // P/T/N { vVert.Position = algorithm::getElement(iPositions, svert[0]); vVert.TextureCoordinate = algorithm::getElement(iTCoords, svert[1]); vVert.normal = algorithm::getElement(iNormals, svert[2]); oVerts.push_back(vVert); break; } default: { break; } } } // take care of missing normals // these may not be truly acurate but it is the // best they get for not compiling a mesh with normals if (noNormal) { Vector3 A = oVerts[0].Position - oVerts[1].Position; Vector3 B = oVerts[2].Position - oVerts[1].Position; Vector3 normal = math::CrossV3(A, B); for (int i = 0; i < int(oVerts.size()); i++) { oVerts[i].normal = normal; } } } // Triangulate a list of vertices into a face by printing // inducies corresponding with triangles within it void VertexTriangluation(std::vector& oIndices, const std::vector& iVerts) { // If there are 2 or less verts, // no triangle can be created, // so exit if (iVerts.size() < 3) { return; } // If it is a triangle no need to calculate it if (iVerts.size() == 3) { oIndices.push_back(0); oIndices.push_back(1); oIndices.push_back(2); return; } // Create a list of vertices std::vector tVerts = iVerts; while (true) { // For every vertex for (int i = 0; i < int(tVerts.size()); i++) { // pPrev = the previous vertex in the list Vertex pPrev; if (i == 0) { pPrev = tVerts[tVerts.size() - 1]; } else { pPrev = tVerts[i - 1]; } // pCur = the current vertex; Vertex pCur = tVerts[i]; // pNext = the next vertex in the list Vertex pNext; if (i == tVerts.size() - 1) { pNext = tVerts[0]; } else { pNext = tVerts[i + 1]; } // Check to see if there are only 3 verts left // if so this is the last triangle if (tVerts.size() == 3) { // Create a triangle from pCur, pPrev, pNext for (int j = 0; j < int(tVerts.size()); j++) { if (iVerts[j].Position == pCur.Position) oIndices.push_back(j); if (iVerts[j].Position == pPrev.Position) oIndices.push_back(j); if (iVerts[j].Position == pNext.Position) oIndices.push_back(j); } tVerts.clear(); break; } if (tVerts.size() == 4) { // Create a triangle from pCur, pPrev, pNext for (int j = 0; j < int(iVerts.size()); j++) { if (iVerts[j].Position == pCur.Position) oIndices.push_back(j); if (iVerts[j].Position == pPrev.Position) oIndices.push_back(j); if (iVerts[j].Position == pNext.Position) oIndices.push_back(j); } Vector3 tempVec; for (int j = 0; j < int(tVerts.size()); j++) { if (tVerts[j].Position != pCur.Position && tVerts[j].Position != pPrev.Position && tVerts[j].Position != pNext.Position) { tempVec = tVerts[j].Position; break; } } // Create a triangle from pCur, pPrev, pNext for (int j = 0; j < int(iVerts.size()); j++) { if (iVerts[j].Position == pPrev.Position) oIndices.push_back(j); if (iVerts[j].Position == pNext.Position) oIndices.push_back(j); if (iVerts[j].Position == tempVec) oIndices.push_back(j); } tVerts.clear(); break; } // If Vertex is not an interior vertex float angle = (float)math::AngleBetweenV3(pPrev.Position - pCur.Position, pNext.Position - pCur.Position) * (float) (180 / 3.14159265359); if (angle <= 0 && angle >= 180) continue; // If any vertices are within this triangle bool inTri = false; for (int j = 0; j < int(iVerts.size()); j++) { if (algorithm::inTriangle(iVerts[j].Position, pPrev.Position, pCur.Position, pNext.Position) && iVerts[j].Position != pPrev.Position && iVerts[j].Position != pCur.Position && iVerts[j].Position != pNext.Position) { inTri = true; break; } } if (inTri) continue; // Create a triangle from pCur, pPrev, pNext for (int j = 0; j < int(iVerts.size()); j++) { if (iVerts[j].Position == pCur.Position) oIndices.push_back(j); if (iVerts[j].Position == pPrev.Position) oIndices.push_back(j); if (iVerts[j].Position == pNext.Position) oIndices.push_back(j); } // Delete pCur from the list for (int j = 0; j < int(tVerts.size()); j++) { if (tVerts[j].Position == pCur.Position) { tVerts.erase(tVerts.begin() + j); break; } } // reset i to the start // -1 since loop will add 1 to it i = -1; } // if no triangles were created if (oIndices.size() == 0) break; // if no more vertices if (tVerts.size() == 0) break; } } // Load Materials from .mtl file bool LoadMaterials(std::string path) { // If the file is not a material file return false if (path.substr(path.size() - 4, path.size()) != ".mtl") return false; std::ifstream file(path); // If the file is not found return false if (!file.is_open()) return false; Material tempMaterial; bool listening = false; // Go through each line looking for material variables std::string curline; while (std::getline(file, curline)) { // new material and material name if (algorithm::firstToken(curline) == "newmtl") { if (!listening) { listening = true; if (curline.size() > 7) { tempMaterial.name = algorithm::tail(curline); } else { tempMaterial.name = "none"; } } else { // Generate the material // Push Back loaded Material LoadedMaterials.push_back(tempMaterial); // Clear Loaded Material tempMaterial = Material(); if (curline.size() > 7) { tempMaterial.name = algorithm::tail(curline); } else { tempMaterial.name = "none"; } } } // Ambient Color if (algorithm::firstToken(curline) == "Ka") { std::vector temp; algorithm::split(algorithm::tail(curline), temp, " "); if (temp.size() != 3) continue; tempMaterial.Ka.X = std::stof(temp[0]); tempMaterial.Ka.Y = std::stof(temp[1]); tempMaterial.Ka.Z = std::stof(temp[2]); } // Diffuse Color if (algorithm::firstToken(curline) == "Kd") { std::vector temp; algorithm::split(algorithm::tail(curline), temp, " "); if (temp.size() != 3) continue; tempMaterial.Kd.X = std::stof(temp[0]); tempMaterial.Kd.Y = std::stof(temp[1]); tempMaterial.Kd.Z = std::stof(temp[2]); } // Specular Color if (algorithm::firstToken(curline) == "Ks") { std::vector temp; algorithm::split(algorithm::tail(curline), temp, " "); if (temp.size() != 3) continue; tempMaterial.Ks.X = std::stof(temp[0]); tempMaterial.Ks.Y = std::stof(temp[1]); tempMaterial.Ks.Z = std::stof(temp[2]); } // Specular Exponent if (algorithm::firstToken(curline) == "Ns") { tempMaterial.Ns = std::stof(algorithm::tail(curline)); } // Optical Density if (algorithm::firstToken(curline) == "Ni") { tempMaterial.Ni = std::stof(algorithm::tail(curline)); } // Dissolve if (algorithm::firstToken(curline) == "d") { tempMaterial.d = std::stof(algorithm::tail(curline)); } // Illumination if (algorithm::firstToken(curline) == "illum") { tempMaterial.illum = std::stoi(algorithm::tail(curline)); } // Ambient Texture Map if (algorithm::firstToken(curline) == "map_Ka") { tempMaterial.map_Ka = algorithm::tail(curline); } // Diffuse Texture Map if (algorithm::firstToken(curline) == "map_Kd") { tempMaterial.map_Kd = algorithm::tail(curline); } // Specular Texture Map if (algorithm::firstToken(curline) == "map_Ks") { tempMaterial.map_Ks = algorithm::tail(curline); } // Specular Hightlight Map if (algorithm::firstToken(curline) == "map_Ns") { tempMaterial.map_Ns = algorithm::tail(curline); } // Alpha Texture Map if (algorithm::firstToken(curline) == "map_d") { tempMaterial.map_d = algorithm::tail(curline); } // Bump Map if (algorithm::firstToken(curline) == "map_Bump" || algorithm::firstToken(curline) == "map_bump" || algorithm::firstToken(curline) == "bump") { tempMaterial.map_bump = algorithm::tail(curline); } } // Deal with last material // Push Back loaded Material LoadedMaterials.push_back(tempMaterial); // Test to see if anything was loaded // If not return false if (LoadedMaterials.empty()) return false; // If so return true else return true; } }; }