#include "../include/WavefrontOBJ.h" #include <JGL/types/VertexArray.h> #include <JGL/logger/logger.h> std::pair<float, unsigned long> ParseNumber(const std::string& file_text, const unsigned long& offset) { std::string number; unsigned long new_offset = offset; bool decimal_used = false; if (offset >= file_text.size()) return {0.0f, offset}; for (; new_offset < file_text.size(); new_offset++) { if (file_text[new_offset] == '-') { if (number.empty()) { number.push_back(file_text[new_offset]); continue; } Logger::Error("Error while parsing number, Index: " + std::to_string(new_offset) + " Extra negative sign."); return {0.0f, offset}; } else if (file_text[new_offset] == '.') { if (!decimal_used) { number.push_back(file_text[new_offset]); decimal_used = true; continue; } Logger::Error("Error parsing number at: " + std::to_string(new_offset) + " Extra decimal point."); return {0.0f, offset}; } else if (isdigit(file_text[new_offset])) number.push_back(file_text[new_offset]); else break; } return {std::stof(number), new_offset}; } std::pair<Vector2, unsigned long> ParseVector2(const std::string& file_text, const unsigned long& offset) { auto x_result = ParseNumber(file_text, offset); auto y_result = ParseNumber(file_text, x_result.second + 1); // If the new offset is the same as the offset we passed in then we know it didn't work. if (x_result.second == offset || y_result.second == x_result.second) return {Vector2(0, 0), offset}; return {Vector2(x_result.first, y_result.first), y_result.second + 1}; } std::pair<Vector3, unsigned long> ParseVector3(const std::string& file_text, const unsigned long& offset) { auto x_result = ParseNumber(file_text, offset); auto y_result = ParseNumber(file_text, x_result.second + 1); auto z_result = ParseNumber(file_text, y_result.second + 1); // If the new offset is the same as the offset we passed in then we know it didn't work. if (x_result.second == offset || y_result.second == x_result.second || z_result.second == y_result.second) return {Vector3(0,0,0), offset}; return {Vector3(x_result.first, y_result.first, z_result.first), z_result.second + 1}; } std::pair<std::array<Vector3, 3>, unsigned long> ParseFaceData(const std::string& file_text, const unsigned long& offset) { unsigned long new_offset = offset; std::array<Vector3, 3> face_data; for (unsigned int i = 0; i < 3; i++) { Vector3 vertex = {-1, -1, -1}; // Vertex if (std::isdigit(file_text[new_offset])) { auto v_result = ParseNumber(file_text, new_offset); vertex.x = v_result.first; new_offset = v_result.second; } // UV if (new_offset < file_text.size() && file_text[new_offset] == '/') { new_offset++; if (new_offset < file_text.size() && (std::isdigit(file_text[new_offset]))) { auto vt_result = ParseNumber(file_text, new_offset); vertex.y = vt_result.first; new_offset = vt_result.second; } } // Normal if (new_offset < file_text.size() && file_text[new_offset] == '/') { new_offset++; if (new_offset < file_text.size() && (std::isdigit(file_text[new_offset]))) { auto vn_result = ParseNumber(file_text, new_offset); vertex.z = vn_result.first; new_offset = vn_result.second; } } face_data[i] = vertex; new_offset++; } return {face_data, new_offset}; } VertexArray JGL::LoadWavefrontOBJ(const std::string &file_text) { std::vector<std::array<Vector3, 3>> faceline_data; std::vector<TextureCoordinate> temp_uvs; std::vector<Normal> temp_normals; std::vector<Vertex> temp_vertices; unsigned long offset = 0; while (offset < file_text.size()) { char c = file_text[offset]; // TODO the entire line a comment is on should be skipped. // Skip forward to the character after the next newline. if (c == '#' || c == '\n' || c == '\r') { offset++; continue; } // Vertices if (c == 'v' && offset + 1 < file_text.size() && file_text[offset + 1] == ' ') { offset += 2; auto vertex_result = ParseVector3(file_text, offset); if (vertex_result.second != offset) { temp_vertices.push_back(vertex_result.first); offset = vertex_result.second; } } // Normals. else if (c == 'v' && offset + 2 < file_text.size() && file_text[offset + 1] == 'n' && file_text[offset + 2] == ' ') { offset += 3; auto normal_result = ParseVector3(file_text, offset); if (normal_result.second != offset) { temp_normals.push_back(normal_result.first); offset = normal_result.second; } } // Texture Coordinates else if (c == 'v' && offset + 2 < file_text.size() && file_text[offset + 1] == 't' && file_text[offset + 2] == ' ') { offset += 3; auto uv_result = ParseVector2(file_text, offset); if (uv_result.second != offset) { temp_uvs.push_back(uv_result.first); offset = uv_result.second; } } // Face lines. else if (c == 'f' && offset + 1 < file_text.size() && file_text[offset + 1] == ' ') { offset += 2; auto faceline_result = ParseFaceData(file_text, offset); if (faceline_result.second != offset) { faceline_data.push_back(faceline_result.first); offset = faceline_result.second; } } else offset++; } // Pick everything out of the face lines and set up how OpenGL expects. std::vector<Vertex> final_vertices; std::vector<TextureCoordinate> final_uvs; std::vector<Normal> final_normals; std::vector<unsigned int> final_indices; for (const auto &face: faceline_data) { for (const auto &vp: face) { Vertex vertex; TextureCoordinate uv; Normal normal; if (vp.x != -1) vertex = temp_vertices[vp.x - 1]; if (vp.y != -1) uv = temp_uvs[vp.y - 1]; if (vp.z != -1) normal = temp_normals[vp.z - 1]; final_vertices.push_back(vertex); final_uvs.push_back(uv); final_normals.push_back(normal); final_indices.push_back((unsigned int) final_vertices.size() - 1); } } return VertexArray( final_vertices.data(), final_vertices.size(), final_indices.data(), final_indices.size(), final_normals.data(), final_normals.size(), final_uvs.data(), final_uvs.size()); }