From e528741a8b163ebd7f110e25ef2969ce7da674c7 Mon Sep 17 00:00:00 2001 From: Vazquinhos Date: Fri, 13 May 2016 12:25:48 +0200 Subject: [PATCH 1/7] Flat normals calculation of objects that their normals are empty --- tiny_obj_loader.h | 123 +++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 111 insertions(+), 12 deletions(-) diff --git a/tiny_obj_loader.h b/tiny_obj_loader.h index 8bdd0a3..5127a2e 100644 --- a/tiny_obj_loader.h +++ b/tiny_obj_loader.h @@ -97,6 +97,69 @@ typedef struct { mesh_t mesh; } shape_t; +typedef enum +{ + triangulation = 1, // used whether triangulate polygon face in .obj + calculate_normals = 2, // used whether calculate the normals if the .obj normals are empty + // Some nice stuff here +} load_flags_t; + +class float3 +{ +public: + float3() + : x( 0.0f ) + , y( 0.0f ) + , z( 0.0f ) + { + } + + float3(float coord_x, float coord_y, float coord_z) + : x( coord_x ) + , y( coord_y ) + , z( coord_z ) + { + } + + float3(const float3& from, const float3& to) + { + coord[0] = to.coord[0] - from.coord[0]; + coord[1] = to.coord[1] - from.coord[1]; + coord[2] = to.coord[2] - from.coord[2]; + } + + float3 crossproduct ( const float3 & vec ) + { + float a = y * vec.z - z * vec.y ; + float b = z * vec.x - x * vec.z ; + float c = x * vec.y - y * vec.x ; + return float3( a , b , c ); + } + + void normalize() + { + const float length = sqrt( ( coord[0] * coord[0] ) + + ( coord[1] * coord[1] ) + + ( coord[2] * coord[2] ) ); + if( length != 1 ) + { + coord[0] = (coord[0] / length); + coord[1] = (coord[1] / length); + coord[2] = (coord[2] / length); + } + } + +private: + union + { + float coord[3]; + struct + { + float x,y,z; + }; + }; +}; + class MaterialReader { public: MaterialReader() {} @@ -127,13 +190,12 @@ private: /// Returns true when loading .obj become success. /// Returns warning and error message into `err` /// '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. +/// 'optional flags bool LoadObj(std::vector &shapes, // [output] std::vector &materials, // [output] std::string &err, // [output] const char *filename, const char *mtl_basepath = NULL, - bool triangulate = true); + unsigned int flags = 1 ); /// Loads object from a std::istream, uses GetMtlIStreamFn to retrieve /// std::istream for materials. @@ -143,7 +205,7 @@ bool LoadObj(std::vector &shapes, // [output] std::vector &materials, // [output] std::string &err, // [output] std::istream &inStream, MaterialReader &readMatFn, - bool triangulate = true); + unsigned int flags = 1); /// Loads materials into std::map void LoadMtl(std::map &material_map, // [output] @@ -516,11 +578,14 @@ static bool exportFaceGroupToShape( 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 clearCache, unsigned int flags, std::string& err ) { if (faceGroup.empty()) { return false; } + bool triangulate( ( flags & triangulation ) == triangulation ); + bool normals_calculation( ( flags & calculate_normals ) == calculate_normals ); + // Flatten vertices and indices for (size_t i = 0; i < faceGroup.size(); i++) { const std::vector &face = faceGroup[i]; @@ -569,6 +634,38 @@ static bool exportFaceGroupToShape( shape.mesh.num_vertices.push_back(static_cast(npolys)); shape.mesh.material_ids.push_back(material_id); // per face } + + if( normals_calculation && shape.mesh.normals.empty() ) + { + const unsigned int nIndexs = shape.mesh.indices.size(); + shape.mesh.normals.resize(shape.mesh.positions.size()); + if( nIndexs % 3 == 0 ) + { + for ( register unsigned int iIndices = 0; iIndices < nIndexs; iIndices+=3 ) + { + float3 v1, v2, v3; + memcpy(&v1, &shape.mesh.positions[shape.mesh.indices[iIndices] * 3], sizeof(float3)); + memcpy(&v2, &shape.mesh.positions[shape.mesh.indices[iIndices + 1] * 3], sizeof(float3)); + memcpy(&v3, &shape.mesh.positions[shape.mesh.indices[iIndices + 2] * 3], sizeof(float3)); + + float3 v12( v1,v2 ); + float3 v13( v1,v3 ); + + float3 normal = v12.crossproduct(v13); + normal.normalize(); + + memcpy(&shape.mesh.normals[shape.mesh.indices[iIndices] * 3], &normal, sizeof(float3)); + memcpy(&shape.mesh.normals[shape.mesh.indices[iIndices + 1] * 3], &normal, sizeof(float3)); + memcpy(&shape.mesh.normals[shape.mesh.indices[iIndices + 2] * 3], &normal, sizeof(float3)); + } + } + else + { + std::stringstream ss; + ss << "WARN: The shape " << name << " does not have a topology of triangles, therfore the normals calculation could not be performed. Select the tinyobj::triangulation flag for this object." << std::endl; + err += ss.str(); + } + } } shape.name = name; @@ -834,7 +931,7 @@ bool MaterialFileReader::operator()(const std::string &matId, bool LoadObj(std::vector &shapes, // [output] std::vector &materials, // [output] std::string &err, const char *filename, const char *mtl_basepath, - bool trianglulate) { + unsigned int flags) { shapes.clear(); @@ -853,13 +950,14 @@ bool LoadObj(std::vector &shapes, // [output] } MaterialFileReader matFileReader(basePath); - return LoadObj(shapes, materials, err, ifs, matFileReader, trianglulate); + return LoadObj(shapes, materials, err, ifs, matFileReader, flags); } bool LoadObj(std::vector &shapes, // [output] std::vector &materials, // [output] std::string &err, std::istream &inStream, - MaterialReader &readMatFn, bool triangulate) { + MaterialReader &readMatFn, unsigned int flags) { + std::stringstream errss; std::vector v; @@ -986,7 +1084,7 @@ 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); + material, name, true, flags, err ); faceGroup.clear(); material = newMaterialId; } @@ -1022,7 +1120,7 @@ bool LoadObj(std::vector &shapes, // [output] // flush previous face group. bool ret = exportFaceGroupToShape(shape, vertexCache, v, vn, vt, faceGroup, tags, - material, name, true, triangulate); + material, name, true, flags, err ); if (ret) { shapes.push_back(shape); } @@ -1059,7 +1157,7 @@ bool LoadObj(std::vector &shapes, // [output] // flush previous face group. bool ret = exportFaceGroupToShape(shape, vertexCache, v, vn, vt, faceGroup, tags, - material, name, true, triangulate); + material, name, true, flags, err ); if (ret) { shapes.push_back(shape); } @@ -1130,13 +1228,14 @@ bool LoadObj(std::vector &shapes, // [output] } bool ret = exportFaceGroupToShape(shape, vertexCache, v, vn, vt, faceGroup, - tags, material, name, true, triangulate); + tags, material, name, true, flags, err ); if (ret) { shapes.push_back(shape); } faceGroup.clear(); // for safety err += errss.str(); + return true; } From 33d5e9aa07625db7d24bafab39e3bb55ddf02a5b Mon Sep 17 00:00:00 2001 From: Vazquinhos Date: Fri, 13 May 2016 12:28:10 +0200 Subject: [PATCH 2/7] Test.cc modified with the new flags --- obj/x32/Debug/link.7608-cvtres.read.1.tlog | 1 + obj/x32/Debug/link.7608-cvtres.write.1.tlog | 1 + obj/x32/Debug/link.7608.read.1.tlog | 1 + obj/x32/Debug/link.7608.write.1.tlog | 1 + test.cc | 7 ++++--- 5 files changed, 8 insertions(+), 3 deletions(-) create mode 100644 obj/x32/Debug/link.7608-cvtres.read.1.tlog create mode 100644 obj/x32/Debug/link.7608-cvtres.write.1.tlog create mode 100644 obj/x32/Debug/link.7608.read.1.tlog create mode 100644 obj/x32/Debug/link.7608.write.1.tlog diff --git a/obj/x32/Debug/link.7608-cvtres.read.1.tlog b/obj/x32/Debug/link.7608-cvtres.read.1.tlog new file mode 100644 index 0000000..46b134b --- /dev/null +++ b/obj/x32/Debug/link.7608-cvtres.read.1.tlog @@ -0,0 +1 @@ +ÿþ \ No newline at end of file diff --git a/obj/x32/Debug/link.7608-cvtres.write.1.tlog b/obj/x32/Debug/link.7608-cvtres.write.1.tlog new file mode 100644 index 0000000..46b134b --- /dev/null +++ b/obj/x32/Debug/link.7608-cvtres.write.1.tlog @@ -0,0 +1 @@ +ÿþ \ No newline at end of file diff --git a/obj/x32/Debug/link.7608.read.1.tlog b/obj/x32/Debug/link.7608.read.1.tlog new file mode 100644 index 0000000..46b134b --- /dev/null +++ b/obj/x32/Debug/link.7608.read.1.tlog @@ -0,0 +1 @@ +ÿþ \ No newline at end of file diff --git a/obj/x32/Debug/link.7608.write.1.tlog b/obj/x32/Debug/link.7608.write.1.tlog new file mode 100644 index 0000000..46b134b --- /dev/null +++ b/obj/x32/Debug/link.7608.write.1.tlog @@ -0,0 +1 @@ +ÿþ \ No newline at end of file diff --git a/test.cc b/test.cc index 2539faa..523973f 100644 --- a/test.cc +++ b/test.cc @@ -124,7 +124,7 @@ static bool TestLoadObj( const char* filename, const char* basepath = NULL, - bool triangulate = true) + unsigned int flags = 1 ) { std::cout << "Loading " << filename << std::endl; @@ -132,7 +132,7 @@ TestLoadObj( std::vector materials; std::string err; - bool ret = tinyobj::LoadObj(shapes, materials, err, filename, basepath, triangulate); + bool ret = tinyobj::LoadObj(shapes, materials, err, filename, basepath, flags); if (!err.empty()) { std::cerr << err << std::endl; @@ -143,7 +143,8 @@ TestLoadObj( return false; } - PrintInfo(shapes, materials, triangulate); + bool triangulate( ( flags & tinyobj::triangulation ) == tinyobj::triangulation ); + PrintInfo(shapes, materials, triangulate ); return true; } From 0dcc72239d9f593b7347ed45ce2d71feefff3f6b Mon Sep 17 00:00:00 2001 From: Vazquinhos Date: Fri, 13 May 2016 12:29:42 +0200 Subject: [PATCH 3/7] Files deleted of compilation log --- obj/x32/Debug/link.7608-cvtres.read.1.tlog | 1 - obj/x32/Debug/link.7608-cvtres.write.1.tlog | 1 - obj/x32/Debug/link.7608.read.1.tlog | 1 - obj/x32/Debug/link.7608.write.1.tlog | 1 - 4 files changed, 4 deletions(-) delete mode 100644 obj/x32/Debug/link.7608-cvtres.read.1.tlog delete mode 100644 obj/x32/Debug/link.7608-cvtres.write.1.tlog delete mode 100644 obj/x32/Debug/link.7608.read.1.tlog delete mode 100644 obj/x32/Debug/link.7608.write.1.tlog diff --git a/obj/x32/Debug/link.7608-cvtres.read.1.tlog b/obj/x32/Debug/link.7608-cvtres.read.1.tlog deleted file mode 100644 index 46b134b..0000000 --- a/obj/x32/Debug/link.7608-cvtres.read.1.tlog +++ /dev/null @@ -1 +0,0 @@ -ÿþ \ No newline at end of file diff --git a/obj/x32/Debug/link.7608-cvtres.write.1.tlog b/obj/x32/Debug/link.7608-cvtres.write.1.tlog deleted file mode 100644 index 46b134b..0000000 --- a/obj/x32/Debug/link.7608-cvtres.write.1.tlog +++ /dev/null @@ -1 +0,0 @@ -ÿþ \ No newline at end of file diff --git a/obj/x32/Debug/link.7608.read.1.tlog b/obj/x32/Debug/link.7608.read.1.tlog deleted file mode 100644 index 46b134b..0000000 --- a/obj/x32/Debug/link.7608.read.1.tlog +++ /dev/null @@ -1 +0,0 @@ -ÿþ \ No newline at end of file diff --git a/obj/x32/Debug/link.7608.write.1.tlog b/obj/x32/Debug/link.7608.write.1.tlog deleted file mode 100644 index 46b134b..0000000 --- a/obj/x32/Debug/link.7608.write.1.tlog +++ /dev/null @@ -1 +0,0 @@ -ÿþ \ No newline at end of file From 9aee576b99e38f05cc2e3af804650484358fe3b8 Mon Sep 17 00:00:00 2001 From: Vazquinhos Date: Fri, 13 May 2016 12:37:24 +0200 Subject: [PATCH 4/7] Added dependencies to cmath in order to use sqrt --- tiny_obj_loader.h | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tiny_obj_loader.h b/tiny_obj_loader.h index 5127a2e..f63ff02 100644 --- a/tiny_obj_loader.h +++ b/tiny_obj_loader.h @@ -44,6 +44,7 @@ #include #include #include +#include namespace tinyobj { @@ -138,9 +139,9 @@ public: void normalize() { - const float length = sqrt( ( coord[0] * coord[0] ) + - ( coord[1] * coord[1] ) + - ( coord[2] * coord[2] ) ); + const float length = std::sqrt( ( coord[0] * coord[0] ) + + ( coord[1] * coord[1] ) + + ( coord[2] * coord[2] ) ); if( length != 1 ) { coord[0] = (coord[0] / length); From a20e4ede852de1d4cca132fb89db371d42e00fc0 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Fri, 13 May 2016 20:09:49 +0900 Subject: [PATCH 5/7] Update document and version. --- README.md | 5 +++-- test.cc | 2 +- tiny_obj_loader.h | 1 + 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 8bc365a..9647acc 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 ---------- +* Mar 13, 2016 : Introduce `load_flag_t` and flat normal calculation flag! Thanks Vazquinhos! * 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. @@ -163,8 +164,8 @@ std::vector shapes; std::vector materials; std::string err; -bool triangulate = false; -bool ret = tinyobj::LoadObj(shapes, materials, err, inputfile.c_str(), triangulate); +int flags = 1; // see load_flags_t enum for more information. +bool ret = tinyobj::LoadObj(shapes, materials, err, inputfile.c_str(), flags); if (!err.empty()) { // `err` may contain warning message. std::cerr << err << std::endl; diff --git a/test.cc b/test.cc index 523973f..e13b172 100644 --- a/test.cc +++ b/test.cc @@ -272,7 +272,7 @@ main( //assert(true == TestLoadObj("cornell_box.obj")); //assert(true == TestLoadObj("cube.obj")); assert(true == TestStreamLoadObj()); - assert(true == TestLoadObj("catmark_torus_creases0.obj", NULL, false)); + assert(true == TestLoadObj("catmark_torus_creases0.obj", NULL, 0)); } return 0; diff --git a/tiny_obj_loader.h b/tiny_obj_loader.h index f63ff02..1c972cd 100644 --- a/tiny_obj_loader.h +++ b/tiny_obj_loader.h @@ -5,6 +5,7 @@ // // +// version 0.9.22: Introduce `load_flags_t`. // 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 From 41f46c7fd73dc2fd55702a7aa4cdc55b9ec58328 Mon Sep 17 00:00:00 2001 From: Vazquinhos Date: Fri, 13 May 2016 13:54:27 +0200 Subject: [PATCH 6/7] Error fixed with no triangulation. Removed warnings and modified coding brace style --- tiny_obj_loader.h | 55 ++++++++++++++++++++++------------------------- 1 file changed, 26 insertions(+), 29 deletions(-) diff --git a/tiny_obj_loader.h b/tiny_obj_loader.h index f63ff02..601dafc 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 0.9.21: Flat normal creation flag for .obj files that do not have normals // 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 @@ -635,38 +636,34 @@ static bool exportFaceGroupToShape( shape.mesh.num_vertices.push_back(static_cast(npolys)); shape.mesh.material_ids.push_back(material_id); // per face } + } - if( normals_calculation && shape.mesh.normals.empty() ) - { - const unsigned int nIndexs = shape.mesh.indices.size(); - shape.mesh.normals.resize(shape.mesh.positions.size()); - if( nIndexs % 3 == 0 ) - { - for ( register unsigned int iIndices = 0; iIndices < nIndexs; iIndices+=3 ) - { - float3 v1, v2, v3; - memcpy(&v1, &shape.mesh.positions[shape.mesh.indices[iIndices] * 3], sizeof(float3)); - memcpy(&v2, &shape.mesh.positions[shape.mesh.indices[iIndices + 1] * 3], sizeof(float3)); - memcpy(&v3, &shape.mesh.positions[shape.mesh.indices[iIndices + 2] * 3], sizeof(float3)); + if (normals_calculation && shape.mesh.normals.empty()) { + const size_t nIndexs = shape.mesh.indices.size(); + if (nIndexs % 3 == 0) { + shape.mesh.normals.resize(shape.mesh.positions.size()); + for (register size_t iIndices = 0; iIndices < nIndexs; iIndices += 3) { + float3 v1, v2, v3; + memcpy(&v1, &shape.mesh.positions[shape.mesh.indices[iIndices] * 3], sizeof(float3)); + memcpy(&v2, &shape.mesh.positions[shape.mesh.indices[iIndices + 1] * 3], sizeof(float3)); + memcpy(&v3, &shape.mesh.positions[shape.mesh.indices[iIndices + 2] * 3], sizeof(float3)); - float3 v12( v1,v2 ); - float3 v13( v1,v3 ); + float3 v12(v1, v2); + float3 v13(v1, v3); - float3 normal = v12.crossproduct(v13); - normal.normalize(); + float3 normal = v12.crossproduct(v13); + normal.normalize(); - memcpy(&shape.mesh.normals[shape.mesh.indices[iIndices] * 3], &normal, sizeof(float3)); - memcpy(&shape.mesh.normals[shape.mesh.indices[iIndices + 1] * 3], &normal, sizeof(float3)); - memcpy(&shape.mesh.normals[shape.mesh.indices[iIndices + 2] * 3], &normal, sizeof(float3)); - } - } - else - { - std::stringstream ss; - ss << "WARN: The shape " << name << " does not have a topology of triangles, therfore the normals calculation could not be performed. Select the tinyobj::triangulation flag for this object." << std::endl; - err += ss.str(); - } - } + memcpy(&shape.mesh.normals[shape.mesh.indices[iIndices] * 3], &normal, sizeof(float3)); + memcpy(&shape.mesh.normals[shape.mesh.indices[iIndices + 1] * 3], &normal, sizeof(float3)); + memcpy(&shape.mesh.normals[shape.mesh.indices[iIndices + 2] * 3], &normal, sizeof(float3)); + } + } else { + + std::stringstream ss; + ss << "WARN: The shape " << name << " does not have a topology of triangles, therfore the normals calculation could not be performed. Select the tinyobj::triangulation flag for this object." << std::endl; + err += ss.str(); + } } shape.name = name; From 7ecb0b2f373dea25605b84c89a244adce8128dd1 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Sun, 15 May 2016 16:19:57 +0900 Subject: [PATCH 7/7] Use sefe getline for files with different line breaks. Fixes #81. --- tiny_obj_loader.h | 48 +++++++++++++++++++++++++++++++++++++---------- 1 file changed, 38 insertions(+), 10 deletions(-) diff --git a/tiny_obj_loader.h b/tiny_obj_loader.h index da297b2..4fafda1 100644 --- a/tiny_obj_loader.h +++ b/tiny_obj_loader.h @@ -267,6 +267,40 @@ struct obj_shape { std::vector vt; }; +//See http://stackoverflow.com/questions/6089231/getting-std-ifstream-to-handle-lf-cr-and-crlf +std::istream& safeGetline(std::istream& is, std::string& t) +{ + t.clear(); + + // The characters in the stream are read one-by-one using a std::streambuf. + // That is faster than reading them one-by-one using the std::istream. + // Code that uses streambuf this way must be guarded by a sentry object. + // The sentry object performs various tasks, + // such as thread synchronization and updating the stream state. + + std::istream::sentry se(is, true); + std::streambuf* sb = is.rdbuf(); + + for(;;) { + int c = sb->sbumpc(); + switch (c) { + case '\n': + return is; + case '\r': + if(sb->sgetc() == '\n') + sb->sbumpc(); + return is; + case EOF: + // Also handle the case when the last line has no line ending + if(t.empty()) + is.setstate(std::ios::eofbit); + return is; + default: + t += (char)c; + } + } +} + #define IS_SPACE( x ) ( ( (x) == ' ') || ( (x) == '\t') ) #define IS_DIGIT( x ) ( (unsigned int)( (x) - '0' ) < (unsigned int)10 ) #define IS_NEW_LINE( x ) ( ( (x) == '\r') || ( (x) == '\n') || ( (x) == '\0') ) @@ -682,12 +716,9 @@ void LoadMtl(std::map &material_map, material_t material; InitMaterial(material); - 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)); - - std::string linebuf(&buf[0]); + std::string linebuf; + safeGetline(inStream, linebuf); // Trim newline '\r\n' or '\n' if (linebuf.size() > 0) { @@ -972,12 +1003,9 @@ bool LoadObj(std::vector &shapes, // [output] 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); - - std::string linebuf(&buf[0]); + std::string linebuf; + safeGetline(inStream, linebuf); // Trim newline '\r\n' or '\n' if (linebuf.size() > 0) {