#include "obj_loader.h" #include #include #include #include #include #include static bool CompareOBJIndexPtr(const OBJIndex * a, const OBJIndex * b); static inline unsigned int FindNextChar(unsigned int start, const char * str, unsigned int length, char token); static inline unsigned int ParseOBJIndexValue(const std::string & token, unsigned int start, unsigned int end); static inline float ParseOBJFloatValue(const std::string & token, unsigned int start, unsigned int end); static inline std::vector SplitString(const std::string & s, char delim); OBJModel::OBJModel(const std::string & fileName) { hasUVs = false; hasNormals = false; std::ifstream file; file.open(fileName.c_str()); std::string line; if (file.is_open()) { while (file.good()) { getline(file, line); unsigned int lineLength = line.length(); if (lineLength < 2) { continue; } const char * lineCStr = line.c_str(); switch (lineCStr[0]) { case 'v': switch (lineCStr[1]) { case 't': this->uvs.push_back(ParseOBJVec2(line)); break; case 'n': this->normals.push_back(ParseOBJVec3(line)); break; case ' ': case '\t': this->vertices.push_back(ParseOBJVec3(line)); } break; case 'f': CreateOBJFace(line); break; default: break; }; } } else { throw std::runtime_error {"Unable to load mesh: " + fileName}; } } void IndexedModel::CalcNormals() { for (unsigned int i = 0; i < indices.size(); i += 3) { int i0 = indices[i]; int i1 = indices[i + 1]; int i2 = indices[i + 2]; glm::vec3 v1 = positions[i1] - positions[i0]; glm::vec3 v2 = positions[i2] - positions[i0]; glm::vec3 normal = glm::normalize(glm::cross(v1, v2)); normals[i0] += normal; normals[i1] += normal; normals[i2] += normal; } for (unsigned int i = 0; i < positions.size(); i++) { normals[i] = glm::normalize(normals[i]); } } IndexedModel OBJModel::ToIndexedModel() { IndexedModel result; IndexedModel normalModel; unsigned int numIndices = OBJIndices.size(); std::vector indexLookup; for (unsigned int i = 0; i < numIndices; i++) { indexLookup.push_back(&OBJIndices[i]); } std::sort(indexLookup.begin(), indexLookup.end(), CompareOBJIndexPtr); std::map normalModelIndexMap; std::map indexMap; for (unsigned int i = 0; i < numIndices; i++) { OBJIndex * currentIndex = &OBJIndices[i]; glm::vec3 currentPosition = vertices[currentIndex->vertexIndex]; glm::vec2 currentTexCoord; glm::vec3 currentNormal; if (hasUVs) { currentTexCoord = uvs[currentIndex->uvIndex]; } else { currentTexCoord = glm::vec2(0, 0); } if (hasNormals) { currentNormal = normals[currentIndex->normalIndex]; } else { currentNormal = glm::vec3(0, 0, 0); } unsigned int normalModelIndex; unsigned int resultModelIndex; // Create model to properly generate normals on const auto it = normalModelIndexMap.find(*currentIndex); if (it == normalModelIndexMap.end()) { normalModelIndex = normalModel.positions.size(); normalModelIndexMap.insert(std::pair(*currentIndex, normalModelIndex)); normalModel.positions.push_back(currentPosition); normalModel.texCoords.push_back(currentTexCoord); normalModel.normals.push_back(currentNormal); } else { normalModelIndex = it->second; } // Create model which properly separates texture coordinates unsigned int previousVertexLocation = FindLastVertexIndex(indexLookup, currentIndex, result); if (previousVertexLocation == (unsigned int)-1) { resultModelIndex = result.positions.size(); result.positions.push_back(currentPosition); result.texCoords.push_back(currentTexCoord); result.normals.push_back(currentNormal); } else { resultModelIndex = previousVertexLocation; } normalModel.indices.push_back(normalModelIndex); result.indices.push_back(resultModelIndex); indexMap.insert(std::pair(resultModelIndex, normalModelIndex)); } if (!hasNormals) { normalModel.CalcNormals(); for (unsigned int i = 0; i < result.positions.size(); i++) { result.normals[i] = normalModel.normals[indexMap[i]]; } } return result; }; unsigned int OBJModel::FindLastVertexIndex( const std::vector & indexLookup, const OBJIndex * currentIndex, const IndexedModel & result) { unsigned int start = 0; unsigned int end = indexLookup.size(); unsigned int current = (end - start) / 2 + start; unsigned int previous = start; while (current != previous) { OBJIndex * testIndex = indexLookup[current]; if (testIndex->vertexIndex == currentIndex->vertexIndex) { unsigned int countStart = current; for (unsigned int i = 0; i < current; i++) { OBJIndex * possibleIndex = indexLookup[current - i]; if (possibleIndex == currentIndex) { continue; } if (possibleIndex->vertexIndex != currentIndex->vertexIndex) { break; } countStart--; } for (unsigned int i = countStart; i < indexLookup.size() - countStart; i++) { OBJIndex * possibleIndex = indexLookup[current + i]; if (possibleIndex == currentIndex) { continue; } if (possibleIndex->vertexIndex != currentIndex->vertexIndex) { break; } else if ((!hasUVs || possibleIndex->uvIndex == currentIndex->uvIndex) && (!hasNormals || possibleIndex->normalIndex == currentIndex->normalIndex)) { glm::vec3 currentPosition = vertices[currentIndex->vertexIndex]; glm::vec2 currentTexCoord; glm::vec3 currentNormal; if (hasUVs) { currentTexCoord = uvs[currentIndex->uvIndex]; } else { currentTexCoord = glm::vec2(0, 0); } if (hasNormals) { currentNormal = normals[currentIndex->normalIndex]; } else { currentNormal = glm::vec3(0, 0, 0); } for (unsigned int j = 0; j < result.positions.size(); j++) { if (currentPosition == result.positions[j] && ((!hasUVs || currentTexCoord == result.texCoords[j]) && (!hasNormals || currentNormal == result.normals[j]))) { return j; } } } } return -1; } else { if (testIndex->vertexIndex < currentIndex->vertexIndex) { start = current; } else { end = current; } } previous = current; current = (end - start) / 2 + start; } return -1; } void OBJModel::CreateOBJFace(const std::string & line) { std::vector tokens = SplitString(line, ' '); this->OBJIndices.push_back(ParseOBJIndex(tokens[1], &this->hasUVs, &this->hasNormals)); this->OBJIndices.push_back(ParseOBJIndex(tokens[2], &this->hasUVs, &this->hasNormals)); this->OBJIndices.push_back(ParseOBJIndex(tokens[3], &this->hasUVs, &this->hasNormals)); if ((int)tokens.size() > 4) { this->OBJIndices.push_back(ParseOBJIndex(tokens[1], &this->hasUVs, &this->hasNormals)); this->OBJIndices.push_back(ParseOBJIndex(tokens[3], &this->hasUVs, &this->hasNormals)); this->OBJIndices.push_back(ParseOBJIndex(tokens[4], &this->hasUVs, &this->hasNormals)); } } OBJIndex OBJModel::ParseOBJIndex(const std::string & token, bool * hasUVs, bool * hasNormals) { unsigned int tokenLength = token.length(); const char * tokenString = token.c_str(); unsigned int vertIndexStart = 0; unsigned int vertIndexEnd = FindNextChar(vertIndexStart, tokenString, tokenLength, '/'); OBJIndex result { .vertexIndex = ParseOBJIndexValue(token, vertIndexStart, vertIndexEnd), .uvIndex = 0, .normalIndex = 0, }; if (vertIndexEnd >= tokenLength) { return result; } vertIndexStart = vertIndexEnd + 1; vertIndexEnd = FindNextChar(vertIndexStart, tokenString, tokenLength, '/'); result.uvIndex = ParseOBJIndexValue(token, vertIndexStart, vertIndexEnd); *hasUVs = true; if (vertIndexEnd >= tokenLength) { return result; } vertIndexStart = vertIndexEnd + 1; vertIndexEnd = FindNextChar(vertIndexStart, tokenString, tokenLength, '/'); result.normalIndex = ParseOBJIndexValue(token, vertIndexStart, vertIndexEnd); *hasNormals = true; return result; } glm::vec3 OBJModel::ParseOBJVec3(const std::string & line) { unsigned int tokenLength = line.length(); const char * tokenString = line.c_str(); unsigned int vertIndexStart = 2; while (vertIndexStart < tokenLength) { if (tokenString[vertIndexStart] != ' ') { break; } vertIndexStart++; } unsigned int vertIndexEnd = FindNextChar(vertIndexStart, tokenString, tokenLength, ' '); float x = ParseOBJFloatValue(line, vertIndexStart, vertIndexEnd); vertIndexStart = vertIndexEnd + 1; vertIndexEnd = FindNextChar(vertIndexStart, tokenString, tokenLength, ' '); float y = ParseOBJFloatValue(line, vertIndexStart, vertIndexEnd); vertIndexStart = vertIndexEnd + 1; vertIndexEnd = FindNextChar(vertIndexStart, tokenString, tokenLength, ' '); float z = ParseOBJFloatValue(line, vertIndexStart, vertIndexEnd); return glm::vec3(x, y, z); } glm::vec2 OBJModel::ParseOBJVec2(const std::string & line) { unsigned int tokenLength = line.length(); const char * tokenString = line.c_str(); unsigned int vertIndexStart = 3; while (vertIndexStart < tokenLength) { if (tokenString[vertIndexStart] != ' ') { break; } vertIndexStart++; } unsigned int vertIndexEnd = FindNextChar(vertIndexStart, tokenString, tokenLength, ' '); float x = ParseOBJFloatValue(line, vertIndexStart, vertIndexEnd); vertIndexStart = vertIndexEnd + 1; vertIndexEnd = FindNextChar(vertIndexStart, tokenString, tokenLength, ' '); float y = ParseOBJFloatValue(line, vertIndexStart, vertIndexEnd); return glm::vec2(x, y); } static bool CompareOBJIndexPtr(const OBJIndex * a, const OBJIndex * b) { return a->vertexIndex < b->vertexIndex; } static inline unsigned int FindNextChar(unsigned int start, const char * str, unsigned int length, char token) { unsigned int result = start; while (result < length) { result++; if (str[result] == token) { break; } } return result; } static inline unsigned int ParseOBJIndexValue(const std::string & token, unsigned int start, unsigned int end) { return std::stoul(token.substr(start, end - start)) - 1; } static inline float ParseOBJFloatValue(const std::string & token, unsigned int start, unsigned int end) { return std::stof(token.substr(start, end - start)); } static inline std::vector SplitString(const std::string & s, char delim) { std::vector elems; const char * cstr = s.c_str(); unsigned int strLength = s.length(); unsigned int start = 0; unsigned int end = 0; while (end <= strLength) { while (end <= strLength) { if (cstr[end] == delim) { break; } end++; } elems.push_back(s.substr(start, end - start)); start = end + 1; end = start; } return elems; }