From 98da6829acff7a735f5e914fcbf54ff73f6ef43a Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Thu, 20 Aug 2015 18:17:13 +0900 Subject: [PATCH] Initial support of parsing curve primitive('curv') Add clang-format style file. --- .clang-format | 7 + test.cc | 36 +- tiny_obj_loader.cc | 1252 ++++++++++++++++++++++++-------------------- tiny_obj_loader.h | 102 ++-- 4 files changed, 776 insertions(+), 621 deletions(-) create mode 100644 .clang-format diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..8da5110 --- /dev/null +++ b/.clang-format @@ -0,0 +1,7 @@ +--- +BasedOnStyle: LLVM +IndentWidth: 2 +TabWidth: 2 +UseTab: Always +BreakBeforeBraces: Attach +Standard: Cpp03 diff --git a/test.cc b/test.cc index 1ad6d8c..0864bad 100644 --- a/test.cc +++ b/test.cc @@ -7,9 +7,10 @@ #include #include -static void PrintInfo(const std::vector& shapes, const std::vector& materials) +static void PrintInfo(const std::vector& shapes, const std::vector& curves, const std::vector& materials) { std::cout << "# of shapes : " << shapes.size() << std::endl; + std::cout << "# of curves : " << curves.size() << std::endl; std::cout << "# of materials : " << materials.size() << std::endl; for (size_t i = 0; i < shapes.size(); i++) { @@ -31,6 +32,29 @@ static void PrintInfo(const std::vector& shapes, const std::ve } } + for (size_t i = 0; i < curves.size(); i++) { + printf("curve[%ld].name = %s\n", i, curves[i].name.c_str()); + printf("Size of curve[%ld].indices: %ld\n", i, curves[i].indices.size()); + + printf("curves[%ld].vertices: %ld\n", i, curves[i].positions.size()); + assert((curves[i].positions.size() % 3) == 0); + for (size_t v = 0; v < curves[i].positions.size() / 3; v++) { + printf(" v[%ld] = (%f, %f, %f)\n", v, + curves[i].positions[3*v+0], + curves[i].positions[3*v+1], + curves[i].positions[3*v+2]); + } + + for (size_t v = 0; v < curves[i].u_params.size(); v++) { + printf(" u[%ld] = %f\n", v, curves[i].u_params[v]); + } + + for (size_t v = 0; v < curves[i].v_params.size(); v++) { + printf(" u[%ld] = %f\n", v, curves[i].v_params[v]); + } + } + + for (size_t i = 0; i < materials.size(); i++) { printf("material[%ld].name = %s\n", i, materials[i].name.c_str()); printf(" material.Ka = (%f, %f ,%f)\n", materials[i].ambient[0], materials[i].ambient[1], materials[i].ambient[2]); @@ -63,15 +87,16 @@ TestLoadObj( std::cout << "Loading " << filename << std::endl; std::vector shapes; + std::vector curves; std::vector materials; - std::string err = tinyobj::LoadObj(shapes, materials, filename, basepath); + std::string err = tinyobj::LoadObj(shapes, curves, materials, filename, basepath); if (!err.empty()) { std::cerr << err << std::endl; return false; } - PrintInfo(shapes, materials); + PrintInfo(shapes, curves, materials); return true; } @@ -163,15 +188,16 @@ std::string matStream( MaterialStringStreamReader matSSReader(matStream); std::vector shapes; + std::vector curves; std::vector materials; - std::string err = tinyobj::LoadObj(shapes, materials, objStream, matSSReader); + std::string err = tinyobj::LoadObj(shapes, curves, materials, objStream, matSSReader); if (!err.empty()) { std::cerr << err << std::endl; return false; } - PrintInfo(shapes, materials); + PrintInfo(shapes, curves, materials); return true; } diff --git a/tiny_obj_loader.cc b/tiny_obj_loader.cc index 48ecd90..2fe76ad 100644 --- a/tiny_obj_loader.cc +++ b/tiny_obj_loader.cc @@ -5,8 +5,10 @@ // // +// version 0.9.14: Initial support of parsing curve primitive. // version 0.9.13: Report "Material file not found message" in `err`(#46) -// version 0.9.12: Fix groups being ignored if they have 'usemtl' just before 'g' (#44) +// version 0.9.12: Fix groups being ignored if they have 'usemtl' just before +// 'g' (#44) // version 0.9.11: Invert `Tr` parameter(#43) // version 0.9.10: Fix seg fault on windows. // version 0.9.9 : Replace atof() with custom parser. @@ -16,7 +18,8 @@ // version 0.9.6 : Support Ni(index of refraction) mtl parameter. // Parse transmittance material parameter correctly. // version 0.9.5 : Parse multiple group name. -// Add support of specifying the base path to load material file. +// Add support of specifying the base path to load material +// file. // version 0.9.4 : Initial suupport of group tag(g) // version 0.9.3 : Fix parsing triple 'x/y/z' // version 0.9.2 : Add more .mtl load support @@ -40,63 +43,64 @@ namespace tinyobj { -#define TINYOBJ_SSCANF_BUFFER_SIZE (4096) +#define TINYOBJ_SSCANF_BUFFER_SIZE (4096) struct vertex_index { - int v_idx, vt_idx, vn_idx; - vertex_index(){}; - vertex_index(int idx) : v_idx(idx), vt_idx(idx), vn_idx(idx){}; - vertex_index(int vidx, int vtidx, int vnidx) - : v_idx(vidx), vt_idx(vtidx), vn_idx(vnidx){}; + int v_idx, vt_idx, vn_idx; + vertex_index(){}; + vertex_index(int idx) : v_idx(idx), vt_idx(idx), vn_idx(idx){}; + vertex_index(int vidx, int vtidx, int vnidx) + : v_idx(vidx), vt_idx(vtidx), vn_idx(vnidx){}; }; // for std::map static inline bool operator<(const vertex_index &a, const vertex_index &b) { - if (a.v_idx != b.v_idx) - return (a.v_idx < b.v_idx); - if (a.vn_idx != b.vn_idx) - return (a.vn_idx < b.vn_idx); - if (a.vt_idx != b.vt_idx) - return (a.vt_idx < b.vt_idx); + if (a.v_idx != b.v_idx) + return (a.v_idx < b.v_idx); + if (a.vn_idx != b.vn_idx) + return (a.vn_idx < b.vn_idx); + if (a.vt_idx != b.vt_idx) + return (a.vt_idx < b.vt_idx); - return false; + return false; } struct obj_shape { - std::vector v; - std::vector vn; - std::vector vt; + std::vector v; + std::vector vn; + std::vector vt; }; static inline bool isSpace(const char c) { return (c == ' ') || (c == '\t'); } static inline bool isNewLine(const char c) { - return (c == '\r') || (c == '\n') || (c == '\0'); + return (c == '\r') || (c == '\n') || (c == '\0'); } // Make index zero-base, and also support relative index. static inline int fixIndex(int idx, int n) { - if (idx > 0) return idx - 1; - if (idx == 0) return 0; - return n + idx; // negative value = relative + if (idx > 0) + return idx - 1; + if (idx == 0) + return 0; + return n + idx; // negative value = relative } static inline std::string parseString(const char *&token) { - std::string s; - token += strspn(token, " \t"); - size_t e = strcspn(token, " \t\r"); - s = std::string(token, &token[e]); - token += e; - return s; + std::string s; + token += strspn(token, " \t"); + size_t e = strcspn(token, " \t\r"); + s = std::string(token, &token[e]); + token += e; + return s; } static inline int parseInt(const char *&token) { - token += strspn(token, " \t"); - int i = atoi(token); - token += strcspn(token, " \t\r"); - return i; + token += strspn(token, " \t"); + int i = atoi(token); + token += strcspn(token, " \t\r"); + return i; } - // Tries to parse a floating point number located at s. // // s_end should be a location in the string where reading should absolutely @@ -113,7 +117,7 @@ static inline int parseInt(const char *&token) { // Valid strings are for example: // -0 +3.1417e+2 -0.0E-3 1.0324 -1.41 11e2 // -// If the parsing is a success, result is set to the parsed value and true +// If the parsing is a success, result is set to the parsed value and true // is returned. // // The function is greedy and will parse until any of the following happens: @@ -123,18 +127,16 @@ static inline int parseInt(const char *&token) { // The following situations triggers a failure: // - s >= s_end. // - parse failure. -// -static bool tryParseDouble(const char *s, const char *s_end, double *result) -{ - if (s >= s_end) - { +// +static bool tryParseDouble(const char *s, const char *s_end, double *result) { + if (s >= s_end) { return false; } double mantissa = 0.0; // This exponent is base 2 rather than 10. // However the exponent we parse is supposed to be one of ten, - // thus we must take care to convert the exponent/and or the + // thus we must take care to convert the exponent/and or the // mantissa to a * 2^E, where a is the mantissa and E is the // exponent. // To get the final double we will use ldexp, it requires the @@ -147,7 +149,7 @@ static bool tryParseDouble(const char *s, const char *s_end, double *result) char exp_sign = '+'; char const *curr = s; - // How many characters were read in a loop. + // How many characters were read in a loop. int read = 0; // Tells whether a loop terminated due to reaching s_end. bool end_not_reached = false; @@ -157,23 +159,20 @@ static bool tryParseDouble(const char *s, const char *s_end, double *result) */ // Find out what sign we've got. - if (*curr == '+' || *curr == '-') - { + if (*curr == '+' || *curr == '-') { sign = *curr; curr++; - } - else if (isdigit(*curr)) { /* Pass through. */ } - else - { + } else if (isdigit(*curr)) { /* Pass through. */ + } else { goto fail; } // Read the integer part. - while ((end_not_reached = (curr != s_end)) && isdigit(*curr)) - { + while ((end_not_reached = (curr != s_end)) && isdigit(*curr)) { mantissa *= 10; mantissa += static_cast(*curr - 0x30); - curr++; read++; + curr++; + read++; } // We must make sure we actually got something. @@ -184,20 +183,17 @@ static bool tryParseDouble(const char *s, const char *s_end, double *result) goto assemble; // Read the decimal part. - if (*curr == '.') - { + if (*curr == '.') { curr++; read = 1; - while ((end_not_reached = (curr != s_end)) && isdigit(*curr)) - { + while ((end_not_reached = (curr != s_end)) && isdigit(*curr)) { // NOTE: Don't use powf here, it will absolutely murder precision. mantissa += static_cast(*curr - 0x30) * pow(10.0, -read); - read++; curr++; + read++; + curr++; } - } - else if (*curr == 'e' || *curr == 'E') {} - else - { + } else if (*curr == 'e' || *curr == 'E') { + } else { goto assemble; } @@ -205,681 +201,789 @@ static bool tryParseDouble(const char *s, const char *s_end, double *result) goto assemble; // Read the exponent part. - if (*curr == 'e' || *curr == 'E') - { + if (*curr == 'e' || *curr == 'E') { curr++; // Figure out if a sign is present and if it is. - if ((end_not_reached = (curr != s_end)) && (*curr == '+' || *curr == '-')) - { + if ((end_not_reached = (curr != s_end)) && (*curr == '+' || *curr == '-')) { exp_sign = *curr; curr++; - } - else if (isdigit(*curr)) { /* Pass through. */ } - else - { + } else if (isdigit(*curr)) { /* Pass through. */ + } else { // Empty E is not allowed. goto fail; } read = 0; - while ((end_not_reached = (curr != s_end)) && isdigit(*curr)) - { + while ((end_not_reached = (curr != s_end)) && isdigit(*curr)) { exponent *= 10; exponent += static_cast(*curr - 0x30); - curr++; read++; + curr++; + read++; } - exponent *= (exp_sign == '+'? 1 : -1); + exponent *= (exp_sign == '+' ? 1 : -1); if (read == 0) goto fail; } assemble: - *result = (sign == '+'? 1 : -1) * ldexp(mantissa * pow(5.0, exponent), exponent); + *result = + (sign == '+' ? 1 : -1) * ldexp(mantissa * pow(5.0, exponent), exponent); return true; fail: return false; } static inline float parseFloat(const char *&token) { - token += strspn(token, " \t"); + token += strspn(token, " \t"); #ifdef TINY_OBJ_LOADER_OLD_FLOAT_PARSER - float f = (float)atof(token); - token += strcspn(token, " \t\r"); + float f = (float)atof(token); + token += strcspn(token, " \t\r"); #else - const char *end = token + strcspn(token, " \t\r"); - double val = 0.0; - tryParseDouble(token, end, &val); - float f = static_cast(val); - token = end; + const char *end = token + strcspn(token, " \t\r"); + double val = 0.0; + tryParseDouble(token, end, &val); + float f = static_cast(val); + token = end; #endif - return f; + return f; } - static inline void parseFloat2(float &x, float &y, const char *&token) { - x = parseFloat(token); - y = parseFloat(token); + x = parseFloat(token); + y = parseFloat(token); } static inline void parseFloat3(float &x, float &y, float &z, - const char *&token) { - x = parseFloat(token); - y = parseFloat(token); - z = parseFloat(token); + const char *&token) { + x = parseFloat(token); + y = parseFloat(token); + z = parseFloat(token); } // Parse triples: i, i/j/k, i//k, i/j static vertex_index parseTriple(const char *&token, int vsize, int vnsize, - int vtsize) { - vertex_index vi(-1); + int vtsize) { + vertex_index vi(-1); - vi.v_idx = fixIndex(atoi(token), vsize); - token += strcspn(token, "/ \t\r"); - if (token[0] != '/') { - return vi; - } - token++; + vi.v_idx = fixIndex(atoi(token), vsize); + token += strcspn(token, "/ \t\r"); + if (token[0] != '/') { + return vi; + } + token++; - // i//k - if (token[0] == '/') { - token++; - vi.vn_idx = fixIndex(atoi(token), vnsize); - token += strcspn(token, "/ \t\r"); - return vi; - } + // i//k + if (token[0] == '/') { + token++; + vi.vn_idx = fixIndex(atoi(token), vnsize); + token += strcspn(token, "/ \t\r"); + return vi; + } - // i/j/k or i/j - vi.vt_idx = fixIndex(atoi(token), vtsize); - token += strcspn(token, "/ \t\r"); - if (token[0] != '/') { - return vi; - } + // i/j/k or i/j + vi.vt_idx = fixIndex(atoi(token), vtsize); + token += strcspn(token, "/ \t\r"); + if (token[0] != '/') { + return vi; + } - // i/j/k - token++; // skip '/' - vi.vn_idx = fixIndex(atoi(token), vnsize); - token += strcspn(token, "/ \t\r"); - return vi; + // i/j/k + token++; // skip '/' + vi.vn_idx = fixIndex(atoi(token), vnsize); + token += strcspn(token, "/ \t\r"); + return vi; } static unsigned int updateVertex(std::map &vertexCache, - std::vector &positions, std::vector &normals, - std::vector &texcoords, - const std::vector &in_positions, - const std::vector &in_normals, - const std::vector &in_texcoords, const vertex_index &i) { - const std::map::iterator it = vertexCache.find(i); + std::vector &positions, std::vector &normals, + std::vector &texcoords, + const std::vector &in_positions, + const std::vector &in_normals, + const std::vector &in_texcoords, const vertex_index &i) { + const std::map::iterator it = vertexCache.find(i); - if (it != vertexCache.end()) { - // found cache - return it->second; - } + if (it != vertexCache.end()) { + // found cache + return it->second; + } - assert(in_positions.size() > (unsigned int)(3 * i.v_idx + 2)); + assert(in_positions.size() > (unsigned int)(3 * i.v_idx + 2)); - positions.push_back(in_positions[3 * i.v_idx + 0]); - positions.push_back(in_positions[3 * i.v_idx + 1]); - positions.push_back(in_positions[3 * i.v_idx + 2]); + positions.push_back(in_positions[3 * i.v_idx + 0]); + positions.push_back(in_positions[3 * i.v_idx + 1]); + positions.push_back(in_positions[3 * i.v_idx + 2]); - if (i.vn_idx >= 0) { - normals.push_back(in_normals[3 * i.vn_idx + 0]); - normals.push_back(in_normals[3 * i.vn_idx + 1]); - normals.push_back(in_normals[3 * i.vn_idx + 2]); - } + if (i.vn_idx >= 0) { + normals.push_back(in_normals[3 * i.vn_idx + 0]); + normals.push_back(in_normals[3 * i.vn_idx + 1]); + normals.push_back(in_normals[3 * i.vn_idx + 2]); + } - if (i.vt_idx >= 0) { - texcoords.push_back(in_texcoords[2 * i.vt_idx + 0]); - texcoords.push_back(in_texcoords[2 * i.vt_idx + 1]); - } + if (i.vt_idx >= 0) { + texcoords.push_back(in_texcoords[2 * i.vt_idx + 0]); + texcoords.push_back(in_texcoords[2 * i.vt_idx + 1]); + } - unsigned int idx = static_cast(positions.size() / 3 - 1); - vertexCache[i] = idx; + unsigned int idx = static_cast(positions.size() / 3 - 1); + vertexCache[i] = idx; - return idx; + return idx; } void InitMaterial(material_t &material) { - material.name = ""; - material.ambient_texname = ""; - material.diffuse_texname = ""; - material.specular_texname = ""; - material.normal_texname = ""; - for (int i = 0; i < 3; i++) { - material.ambient[i] = 0.f; - material.diffuse[i] = 0.f; - material.specular[i] = 0.f; - material.transmittance[i] = 0.f; - material.emission[i] = 0.f; - } - material.illum = 0; - material.dissolve = 1.f; - material.shininess = 1.f; - material.ior = 1.f; - material.unknown_parameter.clear(); + material.name = ""; + material.ambient_texname = ""; + material.diffuse_texname = ""; + material.specular_texname = ""; + material.normal_texname = ""; + for (int i = 0; i < 3; i++) { + material.ambient[i] = 0.f; + material.diffuse[i] = 0.f; + material.specular[i] = 0.f; + material.transmittance[i] = 0.f; + material.emission[i] = 0.f; + } + material.illum = 0; + material.dissolve = 1.f; + material.shininess = 1.f; + material.ior = 1.f; + material.unknown_parameter.clear(); } static bool exportFaceGroupToShape( - shape_t &shape, std::map vertexCache, - const std::vector &in_positions, - const std::vector &in_normals, - const std::vector &in_texcoords, - const std::vector > &faceGroup, - const int material_id, const std::string &name, bool clearCache) { - if (faceGroup.empty()) { - return false; - } + shape_t &shape, std::map vertexCache, + const std::vector &in_positions, + const std::vector &in_normals, + const std::vector &in_texcoords, + const std::vector > &faceGroup, + const int material_id, const std::string &name, bool clearCache) { + if (faceGroup.empty()) { + return false; + } - // Flatten vertices and indices - for (size_t i = 0; i < faceGroup.size(); i++) { - const std::vector &face = faceGroup[i]; + // Flatten vertices and indices + for (size_t i = 0; i < faceGroup.size(); i++) { + const std::vector &face = faceGroup[i]; - vertex_index i0 = face[0]; - vertex_index i1(-1); - vertex_index i2 = face[1]; + vertex_index i0 = face[0]; + vertex_index i1(-1); + vertex_index i2 = face[1]; - size_t npolys = face.size(); + size_t npolys = face.size(); - // Polygon -> triangle fan conversion - for (size_t k = 2; k < npolys; k++) { - i1 = i2; - i2 = face[k]; + // Polygon -> triangle fan conversion + for (size_t k = 2; k < npolys; k++) { + i1 = i2; + i2 = face[k]; - unsigned int v0 = updateVertex( - vertexCache, shape.mesh.positions, shape.mesh.normals, - shape.mesh.texcoords, in_positions, in_normals, in_texcoords, i0); - unsigned int v1 = updateVertex( - vertexCache, shape.mesh.positions, shape.mesh.normals, - shape.mesh.texcoords, in_positions, in_normals, in_texcoords, i1); - unsigned int v2 = updateVertex( - vertexCache, shape.mesh.positions, shape.mesh.normals, - shape.mesh.texcoords, in_positions, in_normals, in_texcoords, i2); + unsigned int v0 = updateVertex( + vertexCache, shape.mesh.positions, shape.mesh.normals, + shape.mesh.texcoords, in_positions, in_normals, in_texcoords, i0); + unsigned int v1 = updateVertex( + vertexCache, shape.mesh.positions, shape.mesh.normals, + shape.mesh.texcoords, in_positions, in_normals, in_texcoords, i1); + unsigned int v2 = updateVertex( + vertexCache, shape.mesh.positions, shape.mesh.normals, + shape.mesh.texcoords, in_positions, in_normals, in_texcoords, i2); - shape.mesh.indices.push_back(v0); - shape.mesh.indices.push_back(v1); - shape.mesh.indices.push_back(v2); + shape.mesh.indices.push_back(v0); + shape.mesh.indices.push_back(v1); + shape.mesh.indices.push_back(v2); - shape.mesh.material_ids.push_back(material_id); - } - } + shape.mesh.material_ids.push_back(material_id); + } + } - shape.name = name; + shape.name = name; - if (clearCache) - vertexCache.clear(); + if (clearCache) + vertexCache.clear(); - return true; + return true; } std::string LoadMtl(std::map &material_map, - std::vector &materials, - std::istream &inStream) { - std::stringstream err; + std::vector &materials, + std::istream &inStream) { + std::stringstream err; - // Create a default material anyway. - material_t material; - InitMaterial(material); + // Create a default material anyway. + material_t material; + InitMaterial(material); - int maxchars = 8192; // Alloc enough size. - std::vector buf(maxchars); // Alloc enough size. - while (inStream.peek() != -1) { - inStream.getline(&buf[0], maxchars); + int maxchars = 8192; // Alloc enough size. + std::vector buf(maxchars); // Alloc enough size. + while (inStream.peek() != -1) { + inStream.getline(&buf[0], maxchars); - std::string linebuf(&buf[0]); + std::string linebuf(&buf[0]); - // Trim newline '\r\n' or '\n' - if (linebuf.size() > 0) { - if (linebuf[linebuf.size() - 1] == '\n') - linebuf.erase(linebuf.size() - 1); - } - if (linebuf.size() > 0) { - if (linebuf[linebuf.size() - 1] == '\r') - linebuf.erase(linebuf.size() - 1); - } + // Trim newline '\r\n' or '\n' + if (linebuf.size() > 0) { + if (linebuf[linebuf.size() - 1] == '\n') + linebuf.erase(linebuf.size() - 1); + } + if (linebuf.size() > 0) { + if (linebuf[linebuf.size() - 1] == '\r') + linebuf.erase(linebuf.size() - 1); + } - // Skip if empty line. - if (linebuf.empty()) { - continue; - } + // Skip if empty line. + if (linebuf.empty()) { + continue; + } - // Skip leading space. - const char *token = linebuf.c_str(); - token += strspn(token, " \t"); + // Skip leading space. + const char *token = linebuf.c_str(); + token += strspn(token, " \t"); - assert(token); - if (token[0] == '\0') - continue; // empty line + assert(token); + if (token[0] == '\0') + continue; // empty line - if (token[0] == '#') - continue; // comment line + if (token[0] == '#') + continue; // comment line - // new mtl - if ((0 == strncmp(token, "newmtl", 6)) && isSpace((token[6]))) { - // flush previous material. - if (!material.name.empty()) { - material_map.insert( - std::pair(material.name, static_cast(materials.size()))); - materials.push_back(material); - } + // new mtl + if ((0 == strncmp(token, "newmtl", 6)) && isSpace((token[6]))) { + // flush previous material. + if (!material.name.empty()) { + material_map.insert(std::pair( + material.name, static_cast(materials.size()))); + materials.push_back(material); + } - // initial temporary material - InitMaterial(material); + // initial temporary material + InitMaterial(material); - // set new mtl name - char namebuf[TINYOBJ_SSCANF_BUFFER_SIZE]; - token += 7; + // set new mtl name + char namebuf[TINYOBJ_SSCANF_BUFFER_SIZE]; + token += 7; #ifdef _MSC_VER - sscanf_s(token, "%s", namebuf, _countof(namebuf)); + sscanf_s(token, "%s", namebuf, _countof(namebuf)); #else - sscanf(token, "%s", namebuf); + sscanf(token, "%s", namebuf); #endif - material.name = namebuf; - continue; - } + material.name = namebuf; + continue; + } - // ambient - if (token[0] == 'K' && token[1] == 'a' && isSpace((token[2]))) { - token += 2; - float r, g, b; - parseFloat3(r, g, b, token); - material.ambient[0] = r; - material.ambient[1] = g; - material.ambient[2] = b; - continue; - } + // ambient + if (token[0] == 'K' && token[1] == 'a' && isSpace((token[2]))) { + token += 2; + float r, g, b; + parseFloat3(r, g, b, token); + material.ambient[0] = r; + material.ambient[1] = g; + material.ambient[2] = b; + continue; + } - // diffuse - if (token[0] == 'K' && token[1] == 'd' && isSpace((token[2]))) { - token += 2; - float r, g, b; - parseFloat3(r, g, b, token); - material.diffuse[0] = r; - material.diffuse[1] = g; - material.diffuse[2] = b; - continue; - } + // diffuse + if (token[0] == 'K' && token[1] == 'd' && isSpace((token[2]))) { + token += 2; + float r, g, b; + parseFloat3(r, g, b, token); + material.diffuse[0] = r; + material.diffuse[1] = g; + material.diffuse[2] = b; + continue; + } - // specular - if (token[0] == 'K' && token[1] == 's' && isSpace((token[2]))) { - token += 2; - float r, g, b; - parseFloat3(r, g, b, token); - material.specular[0] = r; - material.specular[1] = g; - material.specular[2] = b; - continue; - } + // specular + if (token[0] == 'K' && token[1] == 's' && isSpace((token[2]))) { + token += 2; + float r, g, b; + parseFloat3(r, g, b, token); + material.specular[0] = r; + material.specular[1] = g; + material.specular[2] = b; + continue; + } - // transmittance - if (token[0] == 'K' && token[1] == 't' && isSpace((token[2]))) { - token += 2; - float r, g, b; - parseFloat3(r, g, b, token); - material.transmittance[0] = r; - material.transmittance[1] = g; - material.transmittance[2] = b; - continue; - } + // transmittance + if (token[0] == 'K' && token[1] == 't' && isSpace((token[2]))) { + token += 2; + float r, g, b; + parseFloat3(r, g, b, token); + material.transmittance[0] = r; + material.transmittance[1] = g; + material.transmittance[2] = b; + continue; + } - // ior(index of refraction) - if (token[0] == 'N' && token[1] == 'i' && isSpace((token[2]))) { - token += 2; - material.ior = parseFloat(token); - continue; - } + // ior(index of refraction) + if (token[0] == 'N' && token[1] == 'i' && isSpace((token[2]))) { + token += 2; + material.ior = parseFloat(token); + continue; + } - // emission - if (token[0] == 'K' && token[1] == 'e' && isSpace(token[2])) { - token += 2; - float r, g, b; - parseFloat3(r, g, b, token); - material.emission[0] = r; - material.emission[1] = g; - material.emission[2] = b; - continue; - } + // emission + if (token[0] == 'K' && token[1] == 'e' && isSpace(token[2])) { + token += 2; + float r, g, b; + parseFloat3(r, g, b, token); + material.emission[0] = r; + material.emission[1] = g; + material.emission[2] = b; + continue; + } - // shininess - if (token[0] == 'N' && token[1] == 's' && isSpace(token[2])) { - token += 2; - material.shininess = parseFloat(token); - continue; - } + // shininess + if (token[0] == 'N' && token[1] == 's' && isSpace(token[2])) { + token += 2; + material.shininess = parseFloat(token); + continue; + } - // illum model - if (0 == strncmp(token, "illum", 5) && isSpace(token[5])) { - token += 6; - material.illum = parseInt(token); - continue; - } + // illum model + if (0 == strncmp(token, "illum", 5) && isSpace(token[5])) { + token += 6; + material.illum = parseInt(token); + continue; + } - // dissolve - if ((token[0] == 'd' && isSpace(token[1]))) { - token += 1; - material.dissolve = parseFloat(token); - continue; - } - if (token[0] == 'T' && token[1] == 'r' && isSpace(token[2])) { - token += 2; - // Invert value of Tr(assume Tr is in range [0, 1]) - material.dissolve = 1.0f - parseFloat(token); - continue; - } + // dissolve + if ((token[0] == 'd' && isSpace(token[1]))) { + token += 1; + material.dissolve = parseFloat(token); + continue; + } + if (token[0] == 'T' && token[1] == 'r' && isSpace(token[2])) { + token += 2; + // Invert value of Tr(assume Tr is in range [0, 1]) + material.dissolve = 1.0f - parseFloat(token); + continue; + } - // ambient texture - if ((0 == strncmp(token, "map_Ka", 6)) && isSpace(token[6])) { - token += 7; - material.ambient_texname = token; - continue; - } + // ambient texture + if ((0 == strncmp(token, "map_Ka", 6)) && isSpace(token[6])) { + token += 7; + material.ambient_texname = token; + continue; + } - // diffuse texture - if ((0 == strncmp(token, "map_Kd", 6)) && isSpace(token[6])) { - token += 7; - material.diffuse_texname = token; - continue; - } + // diffuse texture + if ((0 == strncmp(token, "map_Kd", 6)) && isSpace(token[6])) { + token += 7; + material.diffuse_texname = token; + continue; + } - // specular texture - if ((0 == strncmp(token, "map_Ks", 6)) && isSpace(token[6])) { - token += 7; - material.specular_texname = token; - continue; - } + // specular texture + if ((0 == strncmp(token, "map_Ks", 6)) && isSpace(token[6])) { + token += 7; + material.specular_texname = token; + continue; + } - // normal texture - if ((0 == strncmp(token, "map_Ns", 6)) && isSpace(token[6])) { - token += 7; - material.normal_texname = token; - continue; - } + // normal texture + if ((0 == strncmp(token, "map_Ns", 6)) && isSpace(token[6])) { + token += 7; + material.normal_texname = token; + continue; + } - // unknown parameter - const char *_space = strchr(token, ' '); - if (!_space) { - _space = strchr(token, '\t'); - } - if (_space) { - std::ptrdiff_t len = _space - token; - std::string key(token, len); - std::string value = _space + 1; - material.unknown_parameter.insert( - std::pair(key, value)); - } - } - // flush last material. - material_map.insert( - std::pair(material.name, static_cast(materials.size()))); - materials.push_back(material); + // unknown parameter + const char *_space = strchr(token, ' '); + if (!_space) { + _space = strchr(token, '\t'); + } + if (_space) { + std::ptrdiff_t len = _space - token; + std::string key(token, len); + std::string value = _space + 1; + material.unknown_parameter.insert( + std::pair(key, value)); + } + } + // flush last material. + material_map.insert(std::pair( + material.name, static_cast(materials.size()))); + materials.push_back(material); - return err.str(); + return err.str(); } std::string MaterialFileReader::operator()(const std::string &matId, - std::vector &materials, - std::map &matMap) { - std::string filepath; + std::vector &materials, + std::map &matMap) { + std::string filepath; - if (!m_mtlBasePath.empty()) { - filepath = std::string(m_mtlBasePath) + matId; - } else { - filepath = matId; - } + if (!m_mtlBasePath.empty()) { + filepath = std::string(m_mtlBasePath) + matId; + } else { + filepath = matId; + } - std::ifstream matIStream(filepath.c_str()); - std::string err = LoadMtl(matMap, materials, matIStream); - if (!matIStream) { - std::stringstream ss; - ss << "WARN: Material file [ " << filepath << " ] not found. Created a default material."; - err += ss.str(); - } - return err; + std::ifstream matIStream(filepath.c_str()); + std::string err = LoadMtl(matMap, materials, matIStream); + if (!matIStream) { + std::stringstream ss; + ss << "WARN: Material file [ " << filepath + << " ] not found. Created a default material."; + err += ss.str(); + } + return err; } -std::string LoadObj(std::vector &shapes, - std::vector &materials, // [output] - const char *filename, const char *mtl_basepath) { +std::string LoadObj(std::vector &shapes, std::vector &curves, + std::vector &materials, // [output] + const char *filename, const char *mtl_basepath) { - shapes.clear(); + shapes.clear(); - std::stringstream err; + std::stringstream err; - std::ifstream ifs(filename); - if (!ifs) { - err << "Cannot open file [" << filename << "]" << std::endl; - return err.str(); - } + std::ifstream ifs(filename); + if (!ifs) { + err << "Cannot open file [" << filename << "]" << std::endl; + return err.str(); + } - std::string basePath; - if (mtl_basepath) { - basePath = mtl_basepath; - } - MaterialFileReader matFileReader(basePath); + std::string basePath; + if (mtl_basepath) { + basePath = mtl_basepath; + } + MaterialFileReader matFileReader(basePath); - return LoadObj(shapes, materials, ifs, matFileReader); + return LoadObj(shapes, curves, materials, ifs, matFileReader); } -std::string LoadObj(std::vector &shapes, - std::vector &materials, // [output] - std::istream &inStream, MaterialReader &readMatFn) { - std::stringstream err; +std::string LoadObj(std::vector &shapes, // [output] + std::vector &curves, // [output] + std::vector &materials, // [output] + std::istream &inStream, MaterialReader &readMatFn) { + std::stringstream err; - std::vector v; - std::vector vn; - std::vector vt; - std::vector > faceGroup; - std::string name; + std::vector v; + std::vector vn; + std::vector vt; + std::vector > faceGroup; + std::string name; - // material - std::map material_map; - std::map vertexCache; - int material = -1; + // curve + int curveType = -1; + int curveDegree = -1; + std::vector curve_indices; + std::vector u_params; + std::vector v_params; - shape_t shape; + // material + std::map material_map; + std::map vertexCache; + int material = -1; - int maxchars = 8192; // Alloc enough size. - std::vector buf(maxchars); // Alloc enough size. - while (inStream.peek() != -1) { - inStream.getline(&buf[0], maxchars); + shape_t shape; - std::string linebuf(&buf[0]); + int maxchars = 8192; // Alloc enough size. + std::vector buf(maxchars); // Alloc enough size. + while (inStream.peek() != -1) { + inStream.getline(&buf[0], maxchars); - // Trim newline '\r\n' or '\n' - if (linebuf.size() > 0) { - if (linebuf[linebuf.size() - 1] == '\n') - linebuf.erase(linebuf.size() - 1); - } - if (linebuf.size() > 0) { - if (linebuf[linebuf.size() - 1] == '\r') - linebuf.erase(linebuf.size() - 1); - } + std::string linebuf(&buf[0]); - // Skip if empty line. - if (linebuf.empty()) { - continue; - } + // Trim newline '\r\n' or '\n' + if (linebuf.size() > 0) { + if (linebuf[linebuf.size() - 1] == '\n') + linebuf.erase(linebuf.size() - 1); + } + if (linebuf.size() > 0) { + if (linebuf[linebuf.size() - 1] == '\r') + linebuf.erase(linebuf.size() - 1); + } - // Skip leading space. - const char *token = linebuf.c_str(); - token += strspn(token, " \t"); + // Skip if empty line. + if (linebuf.empty()) { + continue; + } - assert(token); - if (token[0] == '\0') - continue; // empty line + // Skip leading space. + const char *token = linebuf.c_str(); + token += strspn(token, " \t"); - if (token[0] == '#') - continue; // comment line + assert(token); + if (token[0] == '\0') + continue; // empty line - // vertex - if (token[0] == 'v' && isSpace((token[1]))) { - token += 2; - float x, y, z; - parseFloat3(x, y, z, token); - v.push_back(x); - v.push_back(y); - v.push_back(z); - continue; - } + if (token[0] == '#') + continue; // comment line - // normal - if (token[0] == 'v' && token[1] == 'n' && isSpace((token[2]))) { - token += 3; - float x, y, z; - parseFloat3(x, y, z, token); - vn.push_back(x); - vn.push_back(y); - vn.push_back(z); - continue; - } + // vertex + if (token[0] == 'v' && isSpace((token[1]))) { + token += 2; + float x, y, z; + parseFloat3(x, y, z, token); + v.push_back(x); + v.push_back(y); + v.push_back(z); + continue; + } - // texcoord - if (token[0] == 'v' && token[1] == 't' && isSpace((token[2]))) { - token += 3; - float x, y; - parseFloat2(x, y, token); - vt.push_back(x); - vt.push_back(y); - continue; - } + // normal + if (token[0] == 'v' && token[1] == 'n' && isSpace((token[2]))) { + token += 3; + float x, y, z; + parseFloat3(x, y, z, token); + vn.push_back(x); + vn.push_back(y); + vn.push_back(z); + continue; + } - // face - if (token[0] == 'f' && isSpace((token[1]))) { - token += 2; - token += strspn(token, " \t"); + // texcoord + if (token[0] == 'v' && token[1] == 't' && isSpace((token[2]))) { + token += 3; + float x, y; + parseFloat2(x, y, token); + vt.push_back(x); + vt.push_back(y); + continue; + } - std::vector face; - while (!isNewLine(token[0])) { - vertex_index vi = - parseTriple(token, static_cast(v.size() / 3), static_cast(vn.size() / 3), static_cast(vt.size() / 2)); - face.push_back(vi); - size_t n = strspn(token, " \t\r"); - token += n; - } + // face + if (token[0] == 'f' && isSpace((token[1]))) { + token += 2; + token += strspn(token, " \t"); - faceGroup.push_back(face); + std::vector face; + while (!isNewLine(token[0])) { + vertex_index vi = parseTriple(token, static_cast(v.size() / 3), + static_cast(vn.size() / 3), + static_cast(vt.size() / 2)); + face.push_back(vi); + size_t n = strspn(token, " \t\r"); + token += n; + } - continue; - } + faceGroup.push_back(face); - // use mtl - if ((0 == strncmp(token, "usemtl", 6)) && isSpace((token[6]))) { + continue; + } - char namebuf[TINYOBJ_SSCANF_BUFFER_SIZE]; - token += 7; + // use mtl + if ((0 == strncmp(token, "usemtl", 6)) && isSpace((token[6]))) { + + char namebuf[TINYOBJ_SSCANF_BUFFER_SIZE]; + token += 7; #ifdef _MSC_VER - sscanf_s(token, "%s", namebuf, _countof(namebuf)); + sscanf_s(token, "%s", namebuf, _countof(namebuf)); #else - sscanf(token, "%s", namebuf); + sscanf(token, "%s", namebuf); #endif - // Create face group per material. - bool ret = exportFaceGroupToShape(shape, vertexCache, v, vn, vt, - faceGroup, material, name, true); - if (ret) { - shapes.push_back(shape); - } - shape = shape_t(); - faceGroup.clear(); + // Create face group per material. + bool ret = exportFaceGroupToShape(shape, vertexCache, v, vn, vt, + faceGroup, material, name, true); + if (ret) { + shapes.push_back(shape); + } + shape = shape_t(); + faceGroup.clear(); - if (material_map.find(namebuf) != material_map.end()) { - material = material_map[namebuf]; - } else { - // { error!! material not found } - material = -1; - } + if (material_map.find(namebuf) != material_map.end()) { + material = material_map[namebuf]; + } else { + // { error!! material not found } + material = -1; + } - continue; - } + continue; + } - // load mtl - if ((0 == strncmp(token, "mtllib", 6)) && isSpace((token[6]))) { - char namebuf[TINYOBJ_SSCANF_BUFFER_SIZE]; - token += 7; + // load mtl + if ((0 == strncmp(token, "mtllib", 6)) && isSpace((token[6]))) { + char namebuf[TINYOBJ_SSCANF_BUFFER_SIZE]; + token += 7; #ifdef _MSC_VER - sscanf_s(token, "%s", namebuf, _countof(namebuf)); + sscanf_s(token, "%s", namebuf, _countof(namebuf)); #else - sscanf(token, "%s", namebuf); + sscanf(token, "%s", namebuf); #endif - std::string err_mtl = readMatFn(namebuf, materials, material_map); - if (!err_mtl.empty()) { - faceGroup.clear(); // for safety - return err_mtl; - } + std::string err_mtl = readMatFn(namebuf, materials, material_map); + if (!err_mtl.empty()) { + faceGroup.clear(); // for safety + return err_mtl; + } - continue; - } + continue; + } - // group name - if (token[0] == 'g' && isSpace((token[1]))) { + // group name + if (token[0] == 'g' && isSpace((token[1]))) { - // flush previous face group. - bool ret = exportFaceGroupToShape(shape, vertexCache, v, vn, vt, - faceGroup, material, name, true); - if (ret) { - shapes.push_back(shape); - } + // flush previous face group. + bool ret = exportFaceGroupToShape(shape, vertexCache, v, vn, vt, + faceGroup, material, name, true); + if (ret) { + shapes.push_back(shape); + } - shape = shape_t(); + shape = shape_t(); - // material = -1; - faceGroup.clear(); + // material = -1; + faceGroup.clear(); - std::vector names; - while (!isNewLine(token[0])) { - std::string str = parseString(token); - names.push_back(str); - token += strspn(token, " \t\r"); // skip tag - } + std::vector names; + while (!isNewLine(token[0])) { + std::string str = parseString(token); + names.push_back(str); + token += strspn(token, " \t\r"); // skip tag + } - assert(names.size() > 0); + assert(names.size() > 0); - // names[0] must be 'g', so skip the 0th element. - if (names.size() > 1) { - name = names[1]; - } else { - name = ""; - } + // names[0] must be 'g', so skip the 0th element. + if (names.size() > 1) { + name = names[1]; + } else { + name = ""; + } - continue; - } + continue; + } - // object name - if (token[0] == 'o' && isSpace((token[1]))) { + // object name + if (token[0] == 'o' && isSpace((token[1]))) { - // flush previous face group. - bool ret = exportFaceGroupToShape(shape, vertexCache, v, vn, vt, - faceGroup, material, name, true); - if (ret) { - shapes.push_back(shape); - } + // flush previous face group. + bool ret = exportFaceGroupToShape(shape, vertexCache, v, vn, vt, + faceGroup, material, name, true); + if (ret) { + shapes.push_back(shape); + } - // material = -1; - faceGroup.clear(); - shape = shape_t(); + // material = -1; + faceGroup.clear(); + shape = shape_t(); - // @todo { multiple object name? } - char namebuf[TINYOBJ_SSCANF_BUFFER_SIZE]; - token += 2; + // @todo { multiple object name? } + char namebuf[TINYOBJ_SSCANF_BUFFER_SIZE]; + token += 2; #ifdef _MSC_VER - sscanf_s(token, "%s", namebuf, _countof(namebuf)); + sscanf_s(token, "%s", namebuf, _countof(namebuf)); #else - sscanf(token, "%s", namebuf); + sscanf(token, "%s", namebuf); #endif - name = std::string(namebuf); + name = std::string(namebuf); - continue; - } + continue; + } - // Ignore unknown command. - } + // curve type + if ((0 == strncmp(token, "cstype", 6)) && isSpace((token[6]))) { + token += 7; - bool ret = exportFaceGroupToShape(shape, vertexCache, v, vn, vt, faceGroup, - material, name, true); - if (ret) { - shapes.push_back(shape); - } - faceGroup.clear(); // for safety + std::string type = parseString(token); - return err.str(); -} + if (type == "bspline") { // 0 + curveType = 0; + } else if (type == "bezier") { // 1 + curveType = 1; + } else if (type == "cardinal") { + curveType = 2; + } + } + + // degree(curve) + if (token[0] == 'd' && token[1] == 'e' && token[2] == 'g' && + isSpace((token[3]))) { + token += 4; + curveDegree = parseInt(token); + continue; + } + + // curve + if (token[0] == 'c' && token[1] == 'u' && token[2] == 'r' && + token[3] == 'v' && isSpace((token[4]))) { + token += 5; + + // parse u0 and u1 + float u0, u1; + parseFloat2(u0, u1, token); + size_t n = strspn(token, " \t\r"); + token += n; + + // parse vertex indices. + curve_indices.clear(); + while (!isNewLine(token[0])) { + vertex_index vi = parseTriple(token, static_cast(v.size() / 3), + static_cast(vn.size() / 3), + static_cast(vt.size() / 2)); + curve_indices.push_back(vi.v_idx); + size_t n = strspn(token, " \t\r"); + token += n; + } + + continue; + } + + // curve param + if ((0 == strncmp(token, "parm", 4)) && isSpace((token[4]))) { + token += 5; + + std::string name = parseString(token); + + if (name == "u") { + u_params.clear(); + } else if (name == "v") { + v_params.clear(); + } + + // parse parameter values. + while (!isNewLine(token[0])) { + float value = parseFloat(token); + if (name == "u") { + u_params.push_back(value); + } else if (name == "v") { + v_params.push_back(value); + } + } + + continue; + } + + // end(curve) + if (token[0] == 'e' && token[1] == 'n' && token[2] == 'd') { + // size_t n = strspn(token, " \t\r"); + // token += n; + + // @todo { Consider 'end' at the end of line: parm u 0 0 0 0 ... end } + + if ((curveType > -1) && (curveDegree > 0) && (curve_indices.size() > 1)) { + curve_t curve; + curve.name = name; + curve.type = curveType; + curve.degree = curveDegree; + curve.positions = v; + + curve.indices = curve_indices; + curve.u_params = u_params; + curve.v_params = v_params; + + curves.push_back(curve); + } + + u_params.clear(); + v_params.clear(); + curve_indices.clear(); + + continue; + } + + // Ignore unknown command. + } + + bool ret = exportFaceGroupToShape(shape, vertexCache, v, vn, vt, faceGroup, + material, name, true); + if (ret) { + shapes.push_back(shape); + } + faceGroup.clear(); // for safety + + return err.str(); } + +} // namespace diff --git a/tiny_obj_loader.h b/tiny_obj_loader.h index 512f32b..389acb6 100644 --- a/tiny_obj_loader.h +++ b/tiny_obj_loader.h @@ -13,82 +13,100 @@ namespace tinyobj { typedef struct { - std::string name; + std::string name; - float ambient[3]; - float diffuse[3]; - float specular[3]; - float transmittance[3]; - float emission[3]; - float shininess; - float ior; // index of refraction - float dissolve; // 1 == opaque; 0 == fully transparent - // illumination model (see http://www.fileformat.info/format/material/) - int illum; + float ambient[3]; + float diffuse[3]; + float specular[3]; + float transmittance[3]; + float emission[3]; + float shininess; + float ior; // index of refraction + float dissolve; // 1 == opaque; 0 == fully transparent + // illumination model (see http://www.fileformat.info/format/material/) + int illum; - std::string ambient_texname; - std::string diffuse_texname; - std::string specular_texname; - std::string normal_texname; - std::map unknown_parameter; + std::string ambient_texname; + std::string diffuse_texname; + std::string specular_texname; + std::string normal_texname; + std::map unknown_parameter; } material_t; typedef struct { - std::vector positions; - std::vector normals; - std::vector texcoords; - std::vector indices; - std::vector material_ids; // per-mesh material ID + std::vector positions; + std::vector normals; + std::vector texcoords; + std::vector indices; + std::vector material_ids; // per-mesh material ID } mesh_t; typedef struct { - std::string name; - mesh_t mesh; + std::vector positions; + std::vector indices; +} line_t; + +typedef struct { + std::string name; + std::vector positions; // control points. xyz + std::vector indices; // index to control point + std::vector u_params; + std::vector v_params; + int degree; + int type; // 0: bspline, 1: bezier, 2: cardinal +} curve_t; + +typedef struct { + std::string name; + mesh_t mesh; } shape_t; class MaterialReader { public: - MaterialReader() {} - virtual ~MaterialReader() {} + MaterialReader() {} + virtual ~MaterialReader() {} - virtual std::string operator()(const std::string &matId, - std::vector &materials, - std::map &matMap) = 0; + virtual std::string operator()(const std::string &matId, + std::vector &materials, + std::map &matMap) = 0; }; class MaterialFileReader : public MaterialReader { public: - MaterialFileReader(const std::string &mtl_basepath) - : m_mtlBasePath(mtl_basepath) {} - virtual ~MaterialFileReader() {} - virtual std::string operator()(const std::string &matId, - std::vector &materials, - std::map &matMap); + MaterialFileReader(const std::string &mtl_basepath) + : m_mtlBasePath(mtl_basepath) {} + virtual ~MaterialFileReader() {} + virtual std::string operator()(const std::string &matId, + std::vector &materials, + std::map &matMap); private: - std::string m_mtlBasePath; + std::string m_mtlBasePath; }; /// Loads .obj from a file. /// 'shapes' will be filled with parsed shape data +/// 'curves' will be filled with parsed curve data(NURBS, Bezier, etc) /// The function returns error string. /// Returns empty string when loading .obj success. /// 'mtl_basepath' is optional, and used for base path for .mtl file. -std::string LoadObj(std::vector &shapes, // [output] - std::vector &materials, // [output] - const char *filename, const char *mtl_basepath = NULL); +std::string LoadObj(std::vector &shapes, // [output] + std::vector &curves, // [output] + std::vector &materials, // [output] + const char *filename, const char *mtl_basepath = NULL); /// Loads object from a std::istream, uses GetMtlIStreamFn to retrieve /// std::istream for materials. /// Returns empty string when loading .obj success. -std::string LoadObj(std::vector &shapes, // [output] - std::vector &materials, // [output] - std::istream &inStream, MaterialReader &readMatFn); +std::string LoadObj(std::vector &shapes, // [output] + std::vector &curves, // [output] + std::vector &materials, // [output] + std::istream &inStream, MaterialReader &readMatFn); /// Loads materials into std::map /// Returns an empty string if successful std::string LoadMtl(std::map &material_map, - std::vector &materials, std::istream &inStream); + std::vector &materials, std::istream &inStream); } #endif // _TINY_OBJ_LOADER_H