diff --git a/README.md b/README.md index 34bd08f..3dc8e56 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. @@ -164,8 +165,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 2539faa..e13b172 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; } @@ -271,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 8bdd0a3..4fafda1 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.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 @@ -44,6 +45,7 @@ #include #include #include +#include namespace tinyobj { @@ -97,6 +99,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 = std::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 +192,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 +207,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] @@ -203,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') ) @@ -516,11 +614,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]; @@ -571,6 +672,34 @@ static bool exportFaceGroupToShape( } } + 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 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; shape.mesh.tags.swap(tags); @@ -587,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) { @@ -834,7 +960,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 +979,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; @@ -876,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) { @@ -986,7 +1110,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 +1146,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 +1183,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 +1254,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; }