From ee7d6cc0fdae6a252fcd2a94a28647381c42239b Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Sat, 16 Apr 2016 19:49:12 +0900 Subject: [PATCH] Refactor and re-design tinyobjloader. * Separete attribs(vtx,normal,texcoords) and shape. * Support different index for vtx/normal/texcoord. --- README.md | 1 + .../catmark_torus_creases0.obj | 0 .../cornell_box_multimaterial.obj | 0 cube.mtl => models/cube.mtl | 0 cube.obj => models/cube.obj | 0 .../missing_material_file.obj | 0 no_material.obj => models/no_material.obj | 0 test-nan.obj => models/test-nan.obj | 0 test.cc | 74 ++++-- tiny_obj_loader.h | 240 +++++++++--------- 10 files changed, 170 insertions(+), 145 deletions(-) rename catmark_torus_creases0.obj => models/catmark_torus_creases0.obj (100%) rename cornell_box_multimaterial.obj => models/cornell_box_multimaterial.obj (100%) rename cube.mtl => models/cube.mtl (100%) rename cube.obj => models/cube.obj (100%) rename missing_material_file.obj => models/missing_material_file.obj (100%) rename no_material.obj => models/no_material.obj (100%) rename test-nan.obj => models/test-nan.obj (100%) diff --git a/README.md b/README.md index 8bc365a..056f7c3 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,7 @@ Tiny but powerful single file wavefront obj loader written in C++. No dependency What's new ---------- +* XX YY, ZZZZ : New data strcutre and API! * Jan 29, 2016 : Support n-polygon(no triangulation) and OpenSubdiv crease tag! Thanks dboogert! * Nov 26, 2015 : Now single-header only!. * Nov 08, 2015 : Improved API. diff --git a/catmark_torus_creases0.obj b/models/catmark_torus_creases0.obj similarity index 100% rename from catmark_torus_creases0.obj rename to models/catmark_torus_creases0.obj diff --git a/cornell_box_multimaterial.obj b/models/cornell_box_multimaterial.obj similarity index 100% rename from cornell_box_multimaterial.obj rename to models/cornell_box_multimaterial.obj diff --git a/cube.mtl b/models/cube.mtl similarity index 100% rename from cube.mtl rename to models/cube.mtl diff --git a/cube.obj b/models/cube.obj similarity index 100% rename from cube.obj rename to models/cube.obj diff --git a/missing_material_file.obj b/models/missing_material_file.obj similarity index 100% rename from missing_material_file.obj rename to models/missing_material_file.obj diff --git a/no_material.obj b/models/no_material.obj similarity index 100% rename from no_material.obj rename to models/no_material.obj diff --git a/test-nan.obj b/models/test-nan.obj similarity index 100% rename from test-nan.obj rename to models/test-nan.obj diff --git a/test.cc b/test.cc index 2539faa..88d8be1 100644 --- a/test.cc +++ b/test.cc @@ -8,11 +8,35 @@ #include #include -static void PrintInfo(const std::vector& shapes, const std::vector& materials, bool triangulate = true) +static void PrintInfo(const tinyobj::attrib_t &attrib, const std::vector& shapes, const std::vector& materials, bool triangulate = true) { + std::cout << "# of positions : " << (attrib.positions.size() / 3) << std::endl; + std::cout << "# of normals : " << (attrib.normals.size() / 3) << std::endl; + std::cout << "# of texcoords : " << (attrib.texcoords.size() / 2) << std::endl; + std::cout << "# of shapes : " << shapes.size() << std::endl; std::cout << "# of materials : " << materials.size() << std::endl; + for (size_t v = 0; v < attrib.positions.size() / 3; v++) { + printf(" v[%ld] = (%f, %f, %f)\n", v, + static_cast(attrib.positions[3*v+0]), + static_cast(attrib.positions[3*v+1]), + static_cast(attrib.positions[3*v+2])); + } + + for (size_t v = 0; v < attrib.normals.size() / 3; v++) { + printf(" n[%ld] = (%f, %f, %f)\n", v, + static_cast(attrib.normals[3*v+0]), + static_cast(attrib.normals[3*v+1]), + static_cast(attrib.normals[3*v+2])); + } + + for (size_t v = 0; v < attrib.texcoords.size() / 2; v++) { + printf(" uv[%ld] = (%f, %f)\n", v, + static_cast(attrib.texcoords[2*v+0]), + static_cast(attrib.texcoords[2*v+1])); + } + for (size_t i = 0; i < shapes.size(); i++) { printf("shape[%ld].name = %s\n", i, shapes[i].name.c_str()); printf("Size of shape[%ld].indices: %ld\n", i, shapes[i].mesh.indices.size()); @@ -22,11 +46,19 @@ static void PrintInfo(const std::vector& shapes, const std::ve printf("Size of shape[%ld].material_ids: %ld\n", i, shapes[i].mesh.material_ids.size()); assert((shapes[i].mesh.indices.size() % 3) == 0); for (size_t f = 0; f < shapes[i].mesh.indices.size() / 3; f++) { - printf(" idx[%ld] = %d, %d, %d. mat_id = %d\n", f, shapes[i].mesh.indices[3*f+0], shapes[i].mesh.indices[3*f+1], shapes[i].mesh.indices[3*f+2], shapes[i].mesh.material_ids[f]); + tinyobj::index_t i0 = shapes[i].mesh.indices[3*f+0]; + tinyobj::index_t i1 = shapes[i].mesh.indices[3*f+1]; + tinyobj::index_t i2 = shapes[i].mesh.indices[3*f+2]; + printf(" idx[%ld] = %d/%d/%d, %d/%d/%d, %d/%d/%d. mat_id = %d\n", f, + i0.vertex_index, i0.normal_index, i0.texcoord_index, + i1.vertex_index, i1.normal_index, i1.texcoord_index, + i2.vertex_index, i2.normal_index, i2.texcoord_index, + shapes[i].mesh.material_ids[f]); } } else { for (size_t f = 0; f < shapes[i].mesh.indices.size(); f++) { - printf(" idx[%ld] = %d\n", f, shapes[i].mesh.indices[f]); + tinyobj::index_t idx = shapes[i].mesh.indices[f]; + printf(" idx[%ld] = %d/%d/%d\n", f, idx.vertex_index, idx.normal_index, idx.texcoord_index); } printf("Size of shape[%ld].material_ids: %ld\n", i, shapes[i].mesh.material_ids.size()); @@ -44,14 +76,14 @@ static void PrintInfo(const std::vector& shapes, const std::ve static_cast(shapes[i].mesh.num_vertices[v])); } - printf("shape[%ld].vertices: %ld\n", i, shapes[i].mesh.positions.size()); - assert((shapes[i].mesh.positions.size() % 3) == 0); - for (size_t v = 0; v < shapes[i].mesh.positions.size() / 3; v++) { - printf(" v[%ld] = (%f, %f, %f)\n", v, - shapes[i].mesh.positions[3*v+0], - shapes[i].mesh.positions[3*v+1], - shapes[i].mesh.positions[3*v+2]); - } + //printf("shape[%ld].vertices: %ld\n", i, shapes[i].mesh.positions.size()); + //assert((shapes[i].mesh.positions.size() % 3) == 0); + //for (size_t v = 0; v < shapes[i].mesh.positions.size() / 3; v++) { + // printf(" v[%ld] = (%f, %f, %f)\n", v, + // static_cast(shapes[i].mesh.positions[3*v+0]), + // static_cast(shapes[i].mesh.positions[3*v+1]), + // static_cast(shapes[i].mesh.positions[3*v+2])); + //} printf("shape[%ld].num_tags: %ld\n", i, shapes[i].mesh.tags.size()); for (size_t t = 0; t < shapes[i].mesh.tags.size(); t++) { @@ -70,7 +102,7 @@ static void PrintInfo(const std::vector& shapes, const std::ve printf(" floats: ["); for (size_t j = 0; j < shapes[i].mesh.tags[t].floatValues.size(); ++j) { - printf("%f", shapes[i].mesh.tags[t].floatValues[j]); + printf("%f", static_cast(shapes[i].mesh.tags[t].floatValues[j])); if (j < (shapes[i].mesh.tags[t].floatValues.size()-1)) { printf(", "); @@ -128,11 +160,12 @@ TestLoadObj( { std::cout << "Loading " << filename << std::endl; + tinyobj::attrib_t attrib; std::vector shapes; std::vector materials; std::string err; - bool ret = tinyobj::LoadObj(shapes, materials, err, filename, basepath, triangulate); + bool ret = tinyobj::LoadObj(&attrib, &shapes, &materials, &err, filename, basepath, triangulate); if (!err.empty()) { std::cerr << err << std::endl; @@ -143,7 +176,7 @@ TestLoadObj( return false; } - PrintInfo(shapes, materials, triangulate); + PrintInfo(attrib, shapes, materials, triangulate); return true; } @@ -223,13 +256,13 @@ std::string matStream( virtual ~MaterialStringStreamReader() {} virtual bool operator() ( const std::string& matId, - std::vector& materials, - std::map& matMap, - std::string& err) + std::vector* materials, + std::map* matMap, + std::string* err) { (void)matId; (void)err; - LoadMtl(matMap, materials, m_matSStream); + LoadMtl(matMap, materials, &m_matSStream); return true; } @@ -238,10 +271,11 @@ std::string matStream( }; MaterialStringStreamReader matSSReader(matStream); + tinyobj::attrib_t attrib; std::vector shapes; std::vector materials; std::string err; - bool ret = tinyobj::LoadObj(shapes, materials, err, objStream, matSSReader); + bool ret = tinyobj::LoadObj(&attrib, &shapes, &materials, &err, &objStream, &matSSReader); if (!err.empty()) { std::cerr << err << std::endl; @@ -251,7 +285,7 @@ std::string matStream( return false; } - PrintInfo(shapes, materials); + PrintInfo(attrib, shapes, materials); return true; } diff --git a/tiny_obj_loader.h b/tiny_obj_loader.h index 8bdd0a3..b3a7589 100644 --- a/tiny_obj_loader.h +++ b/tiny_obj_loader.h @@ -1,10 +1,11 @@ // -// Copyright 2012-2015, Syoyo Fujita. +// Copyright 2012-2016, Syoyo Fujita. // // Licensed under 2-clause BSD license. // // +// version devel : Change data structure. Support different index for vertex/normal/texcoord(#73, #39) // version 0.9.20: Fixes creating per-face material using `usemtl`(#68) // version 0.9.17: Support n-polygon and crease tag(OpenSubdiv extension) // version 0.9.16: Make tinyobjloader header-only @@ -81,11 +82,16 @@ typedef struct { std::vector stringValues; } tag_t; +// Index struct to support differnt indices for vtx/normal/texcoord. +// -1 means not used. typedef struct { - std::vector positions; - std::vector normals; - std::vector texcoords; - std::vector indices; + int vertex_index; + int normal_index; + int texcoord_index; +} index_t; + +typedef struct { + std::vector indices; std::vector num_vertices; // The number of vertices per face. Up to 255. std::vector material_ids; // per-face material ID @@ -97,15 +103,21 @@ typedef struct { mesh_t mesh; } shape_t; +typedef struct { + std::vector positions; + std::vector normals; + std::vector texcoords; +} attrib_t; + class MaterialReader { public: MaterialReader() {} virtual ~MaterialReader(); virtual bool operator()(const std::string &matId, - std::vector &materials, - std::map &matMap, - std::string &err) = 0; + std::vector *materials, + std::map *matMap, + std::string *err) = 0; }; class MaterialFileReader : public MaterialReader { @@ -114,14 +126,15 @@ public: : m_mtlBasePath(mtl_basepath) {} virtual ~MaterialFileReader() {} virtual bool operator()(const std::string &matId, - std::vector &materials, - std::map &matMap, std::string &err); + std::vector *materials, + std::map *matMap, std::string *err); private: std::string m_mtlBasePath; }; /// Loads .obj from a file. +/// 'attrib', 'shapes' and 'materials' will be filled with parsed shape data /// 'shapes' will be filled with parsed shape data /// The function returns error string. /// Returns true when loading .obj become success. @@ -129,9 +142,10 @@ private: /// 'mtl_basepath' is optional, and used for base path for .mtl file. /// 'triangulate' is optional, and used whether triangulate polygon face in .obj /// or not. -bool LoadObj(std::vector &shapes, // [output] - std::vector &materials, // [output] - std::string &err, // [output] +bool LoadObj(attrib_t *attrib, + std::vector *shapes, + std::vector *materials, + std::string *err, const char *filename, const char *mtl_basepath = NULL, bool triangulate = true); @@ -139,16 +153,17 @@ bool LoadObj(std::vector &shapes, // [output] /// std::istream for materials. /// Returns true when loading .obj become success. /// Returns warning and error message into `err` -bool LoadObj(std::vector &shapes, // [output] - std::vector &materials, // [output] - std::string &err, // [output] - std::istream &inStream, MaterialReader &readMatFn, +bool LoadObj(attrib_t *attrib, + std::vector *shapes, + std::vector *materials, + std::string *err, + std::istream *inStream, MaterialReader *readMatFn, bool triangulate = true); /// Loads materials into std::map -void LoadMtl(std::map &material_map, // [output] - std::vector &materials, // [output] - std::istream &inStream); +void LoadMtl(std::map *material_map, + std::vector *materials, + std::istream *inStream); } #ifdef TINYOBJLOADER_IMPLEMENTATION @@ -204,7 +219,7 @@ struct obj_shape { }; #define IS_SPACE( x ) ( ( (x) == ' ') || ( (x) == '\t') ) -#define IS_DIGIT( x ) ( (unsigned int)( (x) - '0' ) < (unsigned int)10 ) +#define IS_DIGIT( x ) ( static_cast( (x) - '0' ) < static_cast(10) ) #define IS_NEW_LINE( x ) ( ( (x) == '\r') || ( (x) == '\n') || ( (x) == '\0') ) // Make index zero-base, and also support relative index. @@ -447,45 +462,6 @@ static vertex_index parseTriple(const char *&token, int vsize, int vnsize, 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); - - if (it != vertexCache.end()) { - // found cache - return it->second; - } - - assert(in_positions.size() > static_cast(3 * i.v_idx + 2)); - - positions.push_back(in_positions[3 * static_cast(i.v_idx) + 0]); - positions.push_back(in_positions[3 * static_cast(i.v_idx) + 1]); - positions.push_back(in_positions[3 * static_cast(i.v_idx) + 2]); - - if ((i.vn_idx >= 0) && - (static_cast(i.vn_idx * 3 + 2) < in_normals.size())) { - normals.push_back(in_normals[3 * static_cast(i.vn_idx) + 0]); - normals.push_back(in_normals[3 * static_cast(i.vn_idx) + 1]); - normals.push_back(in_normals[3 * static_cast(i.vn_idx) + 2]); - } - - if ((i.vt_idx >= 0) && - (static_cast(i.vt_idx * 2 + 1) < in_texcoords.size())) { - texcoords.push_back(in_texcoords[2 * static_cast(i.vt_idx) + 0]); - texcoords.push_back(in_texcoords[2 * static_cast(i.vt_idx) + 1]); - } - - unsigned int idx = static_cast(positions.size() / 3 - 1); - vertexCache[i] = idx; - - return idx; -} - static void InitMaterial(material_t &material) { material.name = ""; material.ambient_texname = ""; @@ -510,13 +486,13 @@ static void InitMaterial(material_t &material) { } static bool exportFaceGroupToShape( - shape_t &shape, std::map vertexCache, + shape_t &shape, const std::vector &in_positions, const std::vector &in_normals, const std::vector &in_texcoords, const std::vector > &faceGroup, std::vector &tags, const int material_id, const std::string &name, - bool clearCache, bool triangulate) { + bool triangulate) { if (faceGroup.empty()) { return false; } @@ -538,19 +514,20 @@ static bool exportFaceGroupToShape( 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); + index_t idx0, idx1, idx2; + idx0.vertex_index = i0.v_idx; + idx0.normal_index = i0.vn_idx; + idx0.texcoord_index = i0.vt_idx; + idx1.vertex_index = i1.v_idx; + idx1.normal_index = i1.vn_idx; + idx1.texcoord_index = i1.vt_idx; + idx2.vertex_index = i2.v_idx; + idx2.normal_index = i2.vn_idx; + idx2.texcoord_index = i2.vt_idx; - shape.mesh.indices.push_back(v0); - shape.mesh.indices.push_back(v1); - shape.mesh.indices.push_back(v2); + shape.mesh.indices.push_back(idx0); + shape.mesh.indices.push_back(idx1); + shape.mesh.indices.push_back(idx2); shape.mesh.num_vertices.push_back(3); shape.mesh.material_ids.push_back(material_id); @@ -558,12 +535,10 @@ static bool exportFaceGroupToShape( } else { for (size_t k = 0; k < npolys; k++) { - unsigned int v = - updateVertex(vertexCache, shape.mesh.positions, shape.mesh.normals, - shape.mesh.texcoords, in_positions, in_normals, - in_texcoords, face[k]); - - shape.mesh.indices.push_back(v); + index_t idx; + idx.vertex_index = face[k].v_idx; + idx.normal_index = face[k].vn_idx; + idx.texcoord_index = face[k].vt_idx; } shape.mesh.num_vertices.push_back(static_cast(npolys)); @@ -574,14 +549,11 @@ static bool exportFaceGroupToShape( shape.name = name; shape.mesh.tags.swap(tags); - if (clearCache) - vertexCache.clear(); - return true; } -void LoadMtl(std::map &material_map, - std::vector &materials, std::istream &inStream) { +void LoadMtl(std::map *material_map, + std::vector *materials, std::istream *inStream) { // Create a default material anyway. material_t material; @@ -589,8 +561,8 @@ void LoadMtl(std::map &material_map, size_t maxchars = 8192; // Alloc enough size. std::vector buf(maxchars); // Alloc enough size. - while (inStream.peek() != -1) { - inStream.getline(&buf[0], static_cast(maxchars)); + while (inStream->peek() != -1) { + inStream->getline(&buf[0], static_cast(maxchars)); std::string linebuf(&buf[0]); @@ -624,9 +596,9 @@ void LoadMtl(std::map &material_map, if ((0 == strncmp(token, "newmtl", 6)) && IS_SPACE((token[6]))) { // flush previous material. if (!material.name.empty()) { - material_map.insert(std::pair( - material.name, static_cast(materials.size()))); - materials.push_back(material); + material_map->insert(std::pair( + material.name, static_cast(materials->size()))); + materials->push_back(material); } // initial temporary material @@ -803,15 +775,15 @@ void LoadMtl(std::map &material_map, } } // flush last material. - material_map.insert(std::pair( - material.name, static_cast(materials.size()))); - materials.push_back(material); + material_map->insert(std::pair( + material.name, static_cast(materials->size()))); + materials->push_back(material); } bool MaterialFileReader::operator()(const std::string &matId, - std::vector &materials, - std::map &matMap, - std::string &err) { + std::vector *materials, + std::map *matMap, + std::string *err) { std::string filepath; if (!m_mtlBasePath.empty()) { @@ -821,29 +793,37 @@ bool MaterialFileReader::operator()(const std::string &matId, } std::ifstream matIStream(filepath.c_str()); - LoadMtl(matMap, materials, matIStream); + LoadMtl(matMap, materials, &matIStream); if (!matIStream) { std::stringstream ss; ss << "WARN: Material file [ " << filepath << " ] not found. Created a default material."; - err += ss.str(); + if (err) { + (*err) += ss.str(); + } } return true; } -bool LoadObj(std::vector &shapes, // [output] - std::vector &materials, // [output] - std::string &err, const char *filename, const char *mtl_basepath, +bool LoadObj(attrib_t *attrib, + std::vector *shapes, + std::vector *materials, + std::string *err, const char *filename, const char *mtl_basepath, bool trianglulate) { - shapes.clear(); + attrib->positions.clear(); + attrib->normals.clear(); + attrib->texcoords.clear(); + shapes->clear(); std::stringstream errss; std::ifstream ifs(filename); if (!ifs) { errss << "Cannot open file [" << filename << "]" << std::endl; - err = errss.str(); + if (err) { + (*err) = errss.str(); + } return false; } @@ -853,13 +833,14 @@ bool LoadObj(std::vector &shapes, // [output] } MaterialFileReader matFileReader(basePath); - return LoadObj(shapes, materials, err, ifs, matFileReader, trianglulate); + return LoadObj(attrib, shapes, materials, err, &ifs, &matFileReader, trianglulate); } -bool LoadObj(std::vector &shapes, // [output] - std::vector &materials, // [output] - std::string &err, std::istream &inStream, - MaterialReader &readMatFn, bool triangulate) { +bool LoadObj(attrib_t *attrib, + std::vector *shapes, + std::vector *materials, + std::string *err, std::istream *inStream, + MaterialReader *readMatFn, bool triangulate) { std::stringstream errss; std::vector v; @@ -871,15 +852,15 @@ bool LoadObj(std::vector &shapes, // [output] // material std::map material_map; - std::map vertexCache; + //std::map vertexCache; int material = -1; shape_t shape; int maxchars = 8192; // Alloc enough size. std::vector buf(static_cast(maxchars)); // Alloc enough size. - while (inStream.peek() != -1) { - inStream.getline(&buf[0], maxchars); + while (inStream->peek() != -1) { + inStream->getline(&buf[0], maxchars); std::string linebuf(&buf[0]); @@ -985,8 +966,8 @@ bool LoadObj(std::vector &shapes, // [output] if (newMaterialId != material) { // Create per-face material - exportFaceGroupToShape(shape, vertexCache, v, vn, vt, faceGroup, tags, - material, name, true, triangulate); + exportFaceGroupToShape(shape, v, vn, vt, faceGroup, tags, + material, name, triangulate); faceGroup.clear(); material = newMaterialId; } @@ -1005,8 +986,10 @@ bool LoadObj(std::vector &shapes, // [output] #endif std::string err_mtl; - bool ok = readMatFn(namebuf, materials, material_map, err_mtl); - err += err_mtl; + bool ok = (*readMatFn)(namebuf, materials, &material_map, &err_mtl); + if (err) { + (*err) += err_mtl; + } if (!ok) { faceGroup.clear(); // for safety @@ -1021,10 +1004,10 @@ bool LoadObj(std::vector &shapes, // [output] // flush previous face group. bool ret = - exportFaceGroupToShape(shape, vertexCache, v, vn, vt, faceGroup, tags, - material, name, true, triangulate); + exportFaceGroupToShape(shape, v, vn, vt, faceGroup, tags, + material, name, triangulate); if (ret) { - shapes.push_back(shape); + shapes->push_back(shape); } shape = shape_t(); @@ -1058,10 +1041,10 @@ bool LoadObj(std::vector &shapes, // [output] // flush previous face group. bool ret = - exportFaceGroupToShape(shape, vertexCache, v, vn, vt, faceGroup, tags, - material, name, true, triangulate); + exportFaceGroupToShape(shape, v, vn, vt, faceGroup, tags, + material, name, triangulate); if (ret) { - shapes.push_back(shape); + shapes->push_back(shape); } // material = -1; @@ -1129,14 +1112,21 @@ bool LoadObj(std::vector &shapes, // [output] // Ignore unknown command. } - bool ret = exportFaceGroupToShape(shape, vertexCache, v, vn, vt, faceGroup, - tags, material, name, true, triangulate); + bool ret = exportFaceGroupToShape(shape, v, vn, vt, faceGroup, + tags, material, name, triangulate); if (ret) { - shapes.push_back(shape); + shapes->push_back(shape); } faceGroup.clear(); // for safety - err += errss.str(); + if (err) { + (*err) += errss.str(); + } + + attrib->positions.swap(v); + attrib->normals.swap(vn); + attrib->texcoords.swap(vt); + return true; }