summaryrefslogtreecommitdiff
path: root/gfx/models/obj_loader.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'gfx/models/obj_loader.cpp')
-rw-r--r--gfx/models/obj_loader.cpp428
1 files changed, 428 insertions, 0 deletions
diff --git a/gfx/models/obj_loader.cpp b/gfx/models/obj_loader.cpp
new file mode 100644
index 0000000..7611a2c
--- /dev/null
+++ b/gfx/models/obj_loader.cpp
@@ -0,0 +1,428 @@
+#include "obj_loader.h"
+#include <algorithm>
+#include <fstream>
+#include <map>
+#include <memory>
+#include <stdexcept>
+#include <utility>
+
+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<std::string> 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<OBJIndex *> indexLookup;
+
+ for (unsigned int i = 0; i < numIndices; i++) {
+ indexLookup.push_back(&OBJIndices[i]);
+ }
+
+ std::sort(indexLookup.begin(), indexLookup.end(), CompareOBJIndexPtr);
+
+ std::map<OBJIndex, unsigned int> normalModelIndexMap;
+ std::map<unsigned int, unsigned int> 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<OBJIndex, unsigned int>(*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<unsigned int, unsigned int>(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<OBJIndex *> & 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<std::string> 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<std::string>
+SplitString(const std::string & s, char delim)
+{
+ std::vector<std::string> 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;
+}