diff options
Diffstat (limited to 'gfx/models')
-rw-r--r-- | gfx/models/mesh.cpp | 70 | ||||
-rw-r--r-- | gfx/models/mesh.h | 17 | ||||
-rw-r--r-- | gfx/models/obj.h | 36 | ||||
-rw-r--r-- | gfx/models/obj.ll | 121 | ||||
-rw-r--r-- | gfx/models/obj_loader.cpp | 429 | ||||
-rw-r--r-- | gfx/models/obj_loader.h | 53 |
6 files changed, 207 insertions, 519 deletions
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 <memory>
+#include <algorithm>
+#include <glm/glm.hpp>
+#include <iterator>
+#include <resource.h>
#include <vector>
-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<Vertex>, std::vector<unsigned int>> && vandi, GLenum m) :
+ Mesh(vandi.first, vandi.second, m)
+{
}
Mesh::Mesh(std::span<Vertex> vertices, std::span<unsigned int> indices, GLenum m) :
@@ -64,6 +42,34 @@ Mesh::Mesh(std::span<Vertex> vertices, std::span<unsigned int> indices, GLenum m glBindVertexArray(0);
}
+Mesh::Data
+Mesh::packObjParser(const ObjParser & obj)
+{
+ std::vector<Vertex> vertices;
+ std::vector<ObjParser::FaceElement> vertexOrder;
+ std::vector<unsigned int> 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 <GL/glew.h>
#include <array>
#include <cstddef>
+#include <filesystem>
+#include <gfx/models/vertex.hpp>
#include <span>
#include <special_members.hpp>
-#include <string>
+#include <utility>
+#include <vector>
-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<Vertex>, std::vector<unsigned int>>;
+ explicit Mesh(const std::filesystem::path & fileName);
+ explicit Mesh(const ObjParser & obj);
Mesh(std::span<Vertex> vertices, std::span<unsigned int> 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 <FlexLexer.h> +#endif +#include <filesystem> +#include <fstream> +#include <glm/glm.hpp> +#include <memory> +#include <vector> + +class ObjParser : yyFlexLexer { +public: + explicit ObjParser(const std::filesystem::path & fileName) : ObjParser {std::make_unique<std::ifstream>(fileName)} + { + } + + explicit ObjParser(std::unique_ptr<std::istream> in) : yyFlexLexer(in.get()) + { + ObjParser::yylex(); + } + + int yylex() override; + + std::vector<glm::vec4> vertices; + std::vector<glm::vec3> texCoords; + std::vector<glm::vec3> normals; + using FaceElement = glm::vec<3, int>; + using Face = std::vector<FaceElement>; + std::vector<Face> 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 <gfx/models/obj.h> +#include <glm/glm.hpp> +#include <memory> +#include <string> +#include <vector> +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 + +%% + +<INITIAL>{comment} { + // fprintf(stderr, "COMMENT %s\n", YYText()); +} +<INITIAL>"f " { + BEGIN(FACE); + faces.emplace_back(); + faces.back().emplace_back(); + axis = 0; +} +<INITIAL>"mtllib " { + BEGIN(MTLLIB); +} +<INITIAL>"o " { + BEGIN(OBJECT); +} +<INITIAL>"s " { + BEGIN(SMOOTH); +} +<INITIAL>"usemtl " { + BEGIN(USEMTL); +} +<INITIAL>"v " { + BEGIN(VERTEX); + vertices.emplace_back(0, 0, 0, 1); + axis = 0; +} +<INITIAL>"vn " { + BEGIN(NORMAL); + normals.emplace_back(0, 0, 0); + axis = 0; +} +<INITIAL>"vt " { + BEGIN(TEXCOORD); + texCoords.emplace_back(0, 1, 1); + axis = 0; +} +<USEMTL>{linestring} { + // fprintf(stderr, "USEMTL <%s>\n", YYText()); +} +<MTLLIB>{linestring} { + // fprintf(stderr, "MTLLIB <%s>\n", YYText()); +} +<OBJECT>{linestring} { + // fprintf(stderr, "OBJECT <%s>\n", YYText()); +} +<SMOOTH>{truthy} { + // fprintf(stderr, "Set smooth\n"); +} +<SMOOTH>{falsey} { + // fprintf(stderr, "Set flat\n"); +} +<VERTEX>{float} { + vertices.back()[axis++] = std::stof(YYText()); +} +<NORMAL>{float} { + normals.back()[axis++] = std::stof(YYText()); +} +<TEXCOORD>{float} { + texCoords.back()[axis++] = std::stof(YYText()); +} +<FACE>{index} { + faces.back().back()[axis] = std::stoi(YYText()); +} +<FACE>\/ { + axis++; +} +<FACE>[ \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 <algorithm> -#include <fstream> -#include <map> -#include <memory> -#include <resource.h> -#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::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<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; -} 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 <filesystem> -#include <glm/glm.hpp> -#include <string> -#include <vector> - -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<glm::vec3> positions; - std::vector<glm::vec2> texCoords; - std::vector<glm::vec3> normals; - std::vector<unsigned int> indices; - - void CalcNormals(); -}; - -class OBJModel { -public: - std::vector<OBJIndex> OBJIndices; - std::vector<glm::vec3> vertices; - std::vector<glm::vec2> uvs; - std::vector<glm::vec3> normals; - bool hasUVs; - bool hasNormals; - - explicit OBJModel(const std::filesystem::path & fileName); - - IndexedModel ToIndexedModel(); - -private: - unsigned int FindLastVertexIndex( - const std::vector<OBJIndex *> & 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 |