From d2b167f2d1ca15e42a177b65cdf34f35592452f7 Mon Sep 17 00:00:00 2001 From: Dan Goodliffe Date: Sat, 13 Feb 2021 14:52:03 +0000 Subject: New .obj parser, packer, mesher --- Jamroot.jam | 1 + gfx/models/mesh.cpp | 70 ++++---- gfx/models/mesh.h | 17 +- gfx/models/obj.h | 36 ++++ gfx/models/obj.ll | 121 +++++++++++++ gfx/models/obj_loader.cpp | 429 ---------------------------------------------- gfx/models/obj_loader.h | 53 ------ 7 files changed, 208 insertions(+), 519 deletions(-) create mode 100644 gfx/models/obj.h create mode 100644 gfx/models/obj.ll delete mode 100644 gfx/models/obj_loader.cpp delete mode 100644 gfx/models/obj_loader.h diff --git a/Jamroot.jam b/Jamroot.jam index 4e2fefc..e569a31 100644 --- a/Jamroot.jam +++ b/Jamroot.jam @@ -18,6 +18,7 @@ project : requirements release:on tidy:gfx/gl/shaders/fs-basicShader.h tidy:gfx/gl/shaders/vs-basicShader.h + tidy:gfx/models/obj.cpp tidy:boost-* tidy:bugprone-* tidy:clang-* diff --git a/gfx/models/mesh.cpp b/gfx/models/mesh.cpp index 4a3d9d4..25a4b58 100644 --- a/gfx/models/mesh.cpp +++ b/gfx/models/mesh.cpp @@ -1,41 +1,19 @@ #include "mesh.h" -#include "obj_loader.h" +#include "obj.h" #include "vertex.hpp" -#include +#include +#include +#include +#include #include -Mesh::Mesh(const std::string & fileName) : Mesh(OBJModel(fileName).ToIndexedModel()) { } +Mesh::Mesh(const std::filesystem::path & fileName) : Mesh(ObjParser {Resource::mapPath(fileName)}) { } -Mesh::Mesh(const IndexedModel & model) : - m_vertexArrayObject {}, m_vertexArrayBuffers {}, m_numIndices {model.indices.size()}, mode {GL_TRIANGLES} -{ - glGenVertexArrays(1, &m_vertexArrayObject); - glBindVertexArray(m_vertexArrayObject); - - glGenBuffers(NUM_BUFFERS, m_vertexArrayBuffers.data()); - - glBindBuffer(GL_ARRAY_BUFFER, m_vertexArrayBuffers[POSITION_VB]); - glBufferData( - GL_ARRAY_BUFFER, sizeof(model.positions[0]) * model.positions.size(), &model.positions[0], GL_STATIC_DRAW); - glEnableVertexAttribArray(0); - glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, nullptr); - - glBindBuffer(GL_ARRAY_BUFFER, m_vertexArrayBuffers[TEXCOORD_VB]); - glBufferData( - GL_ARRAY_BUFFER, sizeof(model.texCoords[0]) * model.texCoords.size(), &model.texCoords[0], GL_STATIC_DRAW); - glEnableVertexAttribArray(1); - glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 0, nullptr); +Mesh::Mesh(const ObjParser & obj) : Mesh(packObjParser(obj), GL_TRIANGLES) { } - glBindBuffer(GL_ARRAY_BUFFER, m_vertexArrayBuffers[NORMAL_VB]); - glBufferData(GL_ARRAY_BUFFER, sizeof(model.normals[0]) * model.normals.size(), &model.normals[0], GL_STATIC_DRAW); - glEnableVertexAttribArray(2); - glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, 0, nullptr); - - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_vertexArrayBuffers[INDEX_VB]); - glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(model.indices[0]) * model.indices.size(), &model.indices[0], - GL_STATIC_DRAW); - - glBindVertexArray(0); +Mesh::Mesh(std::pair, std::vector> && vandi, GLenum m) : + Mesh(vandi.first, vandi.second, m) +{ } Mesh::Mesh(std::span vertices, std::span indices, GLenum m) : @@ -64,6 +42,34 @@ Mesh::Mesh(std::span vertices, std::span indices, GLenum m glBindVertexArray(0); } +Mesh::Data +Mesh::packObjParser(const ObjParser & obj) +{ + std::vector vertices; + std::vector vertexOrder; + std::vector indices; + std::for_each(obj.faces.begin(), obj.faces.end(), [&](const ObjParser::Face & face) { + for (auto idx = 2U; idx < face.size(); idx += 1) { + auto f = [&](auto idx) { + const auto & fe {face[idx]}; + if (const auto existing = std::find(vertexOrder.begin(), vertexOrder.end(), fe); + existing != vertexOrder.end()) { + indices.push_back(std::distance(vertexOrder.begin(), existing)); + } + else { + indices.push_back(vertices.size()); + vertices.emplace_back(obj.vertices[fe.x - 1], obj.texCoords[fe.y - 1], -obj.normals[fe.z - 1]); + vertexOrder.emplace_back(fe); + } + }; + f(0); + f(idx); + f(idx - 1); + } + }); + return std::make_pair(vertices, indices); +} + Mesh::~Mesh() { glDeleteBuffers(NUM_BUFFERS, m_vertexArrayBuffers.data()); diff --git a/gfx/models/mesh.h b/gfx/models/mesh.h index ebd300b..4982145 100644 --- a/gfx/models/mesh.h +++ b/gfx/models/mesh.h @@ -4,19 +4,22 @@ #include #include #include +#include +#include #include #include -#include +#include +#include -class IndexedModel; -class Vertex; +class ObjParser; enum MeshBufferPositions { POSITION_VB, TEXCOORD_VB, NORMAL_VB, INDEX_VB }; class Mesh { public: - explicit Mesh(const std::string & fileName); - explicit Mesh(const IndexedModel & model); + using Data = std::pair, std::vector>; + explicit Mesh(const std::filesystem::path & fileName); + explicit Mesh(const ObjParser & obj); Mesh(std::span vertices, std::span indices, GLenum = GL_TRIANGLES); virtual ~Mesh(); @@ -26,6 +29,10 @@ public: void Draw() const; private: + explicit Mesh(Data && vandi, GLenum = GL_TRIANGLES); + + static Data packObjParser(const ObjParser &); + static constexpr unsigned int NUM_BUFFERS {4}; GLuint m_vertexArrayObject; diff --git a/gfx/models/obj.h b/gfx/models/obj.h new file mode 100644 index 0000000..96c5e94 --- /dev/null +++ b/gfx/models/obj.h @@ -0,0 +1,36 @@ +#ifndef OBJ_H +#define OBJ_H + +#ifndef yyFlexLexer +# define yyFlexLexer objbaseFlexLexer +# include +#endif +#include +#include +#include +#include +#include + +class ObjParser : yyFlexLexer { +public: + explicit ObjParser(const std::filesystem::path & fileName) : ObjParser {std::make_unique(fileName)} + { + } + + explicit ObjParser(std::unique_ptr in) : yyFlexLexer(in.get()) + { + ObjParser::yylex(); + } + + int yylex() override; + + std::vector vertices; + std::vector texCoords; + std::vector normals; + using FaceElement = glm::vec<3, int>; + using Face = std::vector; + std::vector faces; + glm::length_t axis {0}; +}; + +#endif diff --git a/gfx/models/obj.ll b/gfx/models/obj.ll new file mode 100644 index 0000000..9329a5a --- /dev/null +++ b/gfx/models/obj.ll @@ -0,0 +1,121 @@ +%option batch +%option c++ +%option noyywrap +%option 8bit +%option stack +%option yyclass="ObjParser" +%option prefix="objbase" + +%{ +#pragma GCC diagnostic ignored "-Wsign-compare" +#pragma GCC diagnostic ignored "-Wimplicit-fallthrough" +#if __clang__ +#pragma GCC diagnostic ignored "-Wnull-conversion" +#endif +#include +#include +#include +#include +#include +class objbaseFlexLexer; +%} + +comment #.* +float -?[0-9.]+ +index -?[0-9]+ +linestring [^\r\n]* +truthy (1|on) +falsey (0|off) + +%x FACE +%x MTLLIB +%x OBJECT +%x SMOOTH +%x USEMTL +%x VERTEX +%x NORMAL +%x TEXCOORD + +%% + +{comment} { + // fprintf(stderr, "COMMENT %s\n", YYText()); +} +"f " { + BEGIN(FACE); + faces.emplace_back(); + faces.back().emplace_back(); + axis = 0; +} +"mtllib " { + BEGIN(MTLLIB); +} +"o " { + BEGIN(OBJECT); +} +"s " { + BEGIN(SMOOTH); +} +"usemtl " { + BEGIN(USEMTL); +} +"v " { + BEGIN(VERTEX); + vertices.emplace_back(0, 0, 0, 1); + axis = 0; +} +"vn " { + BEGIN(NORMAL); + normals.emplace_back(0, 0, 0); + axis = 0; +} +"vt " { + BEGIN(TEXCOORD); + texCoords.emplace_back(0, 1, 1); + axis = 0; +} +{linestring} { + // fprintf(stderr, "USEMTL <%s>\n", YYText()); +} +{linestring} { + // fprintf(stderr, "MTLLIB <%s>\n", YYText()); +} +{linestring} { + // fprintf(stderr, "OBJECT <%s>\n", YYText()); +} +{truthy} { + // fprintf(stderr, "Set smooth\n"); +} +{falsey} { + // fprintf(stderr, "Set flat\n"); +} +{float} { + vertices.back()[axis++] = std::stof(YYText()); +} +{float} { + normals.back()[axis++] = std::stof(YYText()); +} +{float} { + texCoords.back()[axis++] = std::stof(YYText()); +} +{index} { + faces.back().back()[axis] = std::stoi(YYText()); +} +\/ { + axis++; +} +[ \t] { + faces.back().emplace_back(); + axis = 0; +} + +<*>[ \t] { +} +<*>[\r\n\f] { + BEGIN(INITIAL); +} + +%% + +// Make iwyu think unistd.h is required +[[maybe_unused]]static auto x=getpid; diff --git a/gfx/models/obj_loader.cpp b/gfx/models/obj_loader.cpp deleted file mode 100644 index 75ab251..0000000 --- a/gfx/models/obj_loader.cpp +++ /dev/null @@ -1,429 +0,0 @@ -#include "obj_loader.h" -#include -#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::filesystem::path & fileName) -{ - hasUVs = false; - hasNormals = false; - std::ifstream file; - file.open(Resource::mapPath(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.string()}; - } -} - -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; -} diff --git a/gfx/models/obj_loader.h b/gfx/models/obj_loader.h deleted file mode 100644 index 2fe3d35..0000000 --- a/gfx/models/obj_loader.h +++ /dev/null @@ -1,53 +0,0 @@ -#ifndef OBJ_LOADER_H_INCLUDED -#define OBJ_LOADER_H_INCLUDED - -#include -#include -#include -#include - -struct OBJIndex { - unsigned int vertexIndex; - unsigned int uvIndex; - unsigned int normalIndex; - - bool - operator<(const OBJIndex & r) const - { - return vertexIndex < r.vertexIndex; - } -}; - -class IndexedModel { -public: - std::vector positions; - std::vector texCoords; - std::vector normals; - std::vector indices; - - void CalcNormals(); -}; - -class OBJModel { -public: - std::vector OBJIndices; - std::vector vertices; - std::vector uvs; - std::vector normals; - bool hasUVs; - bool hasNormals; - - explicit OBJModel(const std::filesystem::path & fileName); - - IndexedModel ToIndexedModel(); - -private: - unsigned int FindLastVertexIndex( - const std::vector & indexLookup, const OBJIndex * currentIndex, const IndexedModel & result); - void CreateOBJFace(const std::string & line); - static glm::vec2 ParseOBJVec2(const std::string & line); - static glm::vec3 ParseOBJVec3(const std::string & line); - static OBJIndex ParseOBJIndex(const std::string & token, bool * hasUVs, bool * hasNormals); -}; - -#endif // OBJ_LOADER_H_INCLUDED -- cgit v1.2.3