From cf78dd845bd1d63675f271c0411036a6373ca624 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Sun, 19 Aug 2012 14:36:48 +0900 Subject: Initial commit. --- README.md | 4 + cornell_box.mtl | 24 ++++ cornell_box.obj | 145 ++++++++++++++++++++ premake4.lua | 33 +++++ test.cc | 45 +++++++ tiny_obj_loader.cc | 381 +++++++++++++++++++++++++++++++++++++++++++++++++++++ tiny_obj_loader.h | 53 ++++++++ 7 files changed, 685 insertions(+) create mode 100644 README.md create mode 100644 cornell_box.mtl create mode 100644 cornell_box.obj create mode 100644 premake4.lua create mode 100644 test.cc create mode 100644 tiny_obj_loader.cc create mode 100644 tiny_obj_loader.h diff --git a/README.md b/README.md new file mode 100644 index 0000000..431527c --- /dev/null +++ b/README.md @@ -0,0 +1,4 @@ +tinyobjloader +============= + +Tiny but poweful single file wavefront obj loader written in C++. no dependency except for C++ STL. diff --git a/cornell_box.mtl b/cornell_box.mtl new file mode 100644 index 0000000..d3a1c7a --- /dev/null +++ b/cornell_box.mtl @@ -0,0 +1,24 @@ +newmtl white +Ka 0 0 0 +Kd 1 1 1 +Ks 0 0 0 + +newmtl red +Ka 0 0 0 +Kd 1 0 0 +Ks 0 0 0 + +newmtl green +Ka 0 0 0 +Kd 0 1 0 +Ks 0 0 0 + +newmtl blue +Ka 0 0 0 +Kd 0 0 1 +Ks 0 0 0 + +newmtl light +Ka 20 20 20 +Kd 1 1 1 +Ks 0 0 0 diff --git a/cornell_box.obj b/cornell_box.obj new file mode 100644 index 0000000..a97672d --- /dev/null +++ b/cornell_box.obj @@ -0,0 +1,145 @@ +# cornell_box.obj and cornell_box.mtl are grabbed from Intel's embree project. +# original cornell box data + # comment + +# empty line including some space + + +mtllib cornell_box.mtl + +o floor +usemtl white +v 552.8 0.0 0.0 +v 0.0 0.0 0.0 +v 0.0 0.0 559.2 +v 549.6 0.0 559.2 + +v 130.0 0.0 65.0 +v 82.0 0.0 225.0 +v 240.0 0.0 272.0 +v 290.0 0.0 114.0 + +v 423.0 0.0 247.0 +v 265.0 0.0 296.0 +v 314.0 0.0 456.0 +v 472.0 0.0 406.0 + +f 1 2 3 4 +f 8 7 6 5 +f 12 11 10 9 + +o light +usemtl light +v 343.0 548.0 227.0 +v 343.0 548.0 332.0 +v 213.0 548.0 332.0 +v 213.0 548.0 227.0 +#f -4 -3 -2 -1 + +o ceiling +usemtl white +v 556.0 548.8 0.0 +v 556.0 548.8 559.2 +v 0.0 548.8 559.2 +v 0.0 548.8 0.0 +f -4 -3 -2 -1 + +o back_wall +usemtl white +v 549.6 0.0 559.2 +v 0.0 0.0 559.2 +v 0.0 548.8 559.2 +v 556.0 548.8 559.2 +f -4 -3 -2 -1 + +o front_wall +usemtl blue +v 549.6 0.0 0 +v 0.0 0.0 0 +v 0.0 548.8 0 +v 556.0 548.8 0 +#f -1 -2 -3 -4 + +o green_wall +usemtl green +v 0.0 0.0 559.2 +v 0.0 0.0 0.0 +v 0.0 548.8 0.0 +v 0.0 548.8 559.2 +f -4 -3 -2 -1 + +o red_wall +usemtl red +v 552.8 0.0 0.0 +v 549.6 0.0 559.2 +v 556.0 548.8 559.2 +v 556.0 548.8 0.0 +f -4 -3 -2 -1 + +o short_block +usemtl white + +v 130.0 165.0 65.0 +v 82.0 165.0 225.0 +v 240.0 165.0 272.0 +v 290.0 165.0 114.0 +f -4 -3 -2 -1 + +v 290.0 0.0 114.0 +v 290.0 165.0 114.0 +v 240.0 165.0 272.0 +v 240.0 0.0 272.0 +f -4 -3 -2 -1 + +v 130.0 0.0 65.0 +v 130.0 165.0 65.0 +v 290.0 165.0 114.0 +v 290.0 0.0 114.0 +f -4 -3 -2 -1 + +v 82.0 0.0 225.0 +v 82.0 165.0 225.0 +v 130.0 165.0 65.0 +v 130.0 0.0 65.0 +f -4 -3 -2 -1 + +v 240.0 0.0 272.0 +v 240.0 165.0 272.0 +v 82.0 165.0 225.0 +v 82.0 0.0 225.0 +f -4 -3 -2 -1 + +o tall_block +usemtl white + +v 423.0 330.0 247.0 +v 265.0 330.0 296.0 +v 314.0 330.0 456.0 +v 472.0 330.0 406.0 +f -4 -3 -2 -1 + +usemtl white +v 423.0 0.0 247.0 +v 423.0 330.0 247.0 +v 472.0 330.0 406.0 +v 472.0 0.0 406.0 +f -4 -3 -2 -1 + +v 472.0 0.0 406.0 +v 472.0 330.0 406.0 +v 314.0 330.0 456.0 +v 314.0 0.0 456.0 +f -4 -3 -2 -1 + +v 314.0 0.0 456.0 +v 314.0 330.0 456.0 +v 265.0 330.0 296.0 +v 265.0 0.0 296.0 +f -4 -3 -2 -1 + +v 265.0 0.0 296.0 +v 265.0 330.0 296.0 +v 423.0 330.0 247.0 +v 423.0 0.0 247.0 +f -4 -3 -2 -1 + diff --git a/premake4.lua b/premake4.lua new file mode 100644 index 0000000..ad020a6 --- /dev/null +++ b/premake4.lua @@ -0,0 +1,33 @@ +lib_sources = { + "tiny_obj_loader.cc" +} + +sources = { + "test.cc", + } + +-- premake4.lua +solution "TinyObjLoaderSolution" + configurations { "Release", "Debug" } + + if (os.is("windows")) then + platforms { "x32", "x64" } + else + platforms { "native", "x32", "x64" } + end + + -- A project defines one build target + project "tinyobjloader" + kind "ConsoleApp" + language "C++" + files { lib_sources, sources } + + configuration "Debug" + defines { "DEBUG" } -- -DDEBUG + flags { "Symbols" } + targetname "test_tinyobjloader_debug" + + configuration "Release" + -- defines { "NDEBUG" } -- -NDEBUG + flags { "Symbols", "Optimize" } + targetname "test_tinyobjloader" diff --git a/test.cc b/test.cc new file mode 100644 index 0000000..1a15f5e --- /dev/null +++ b/test.cc @@ -0,0 +1,45 @@ +#include "tiny_obj_loader.h" + +#include +#include + +int +main( + int argc, + char **argv) +{ + std::string inputfile = "cornell_box.obj"; + std::vector shapes; + + if (argc > 1) { + inputfile = std::string(argv[1]); + } + + std::string err = tinyobj::LoadObj(shapes, inputfile.c_str()); + + if (!err.empty()) { + std::cerr << err << std::endl; + } + + std::cout << "# of shapes : " << shapes.size() << std::endl; + + for (size_t i = 0; i < shapes.size(); i++) { + printf("shape[%ld].name = %s\n", i, shapes[i].name.c_str()); + printf("shape[%ld].indices: %ld\n", i, shapes[i].mesh.indices.size()); + assert((shapes[i].mesh.indices.size() % 3) == 0); + for (size_t f = 0; f < shapes[i].mesh.indices.size(); f++) { + printf(" idx[%ld] = %d\n", f, shapes[i].mesh.indices[f]); + } + + 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]); + } + } + + return 0; +} diff --git a/tiny_obj_loader.cc b/tiny_obj_loader.cc new file mode 100644 index 0000000..f1cf661 --- /dev/null +++ b/tiny_obj_loader.cc @@ -0,0 +1,381 @@ +// +// Copyright 2012, Syoyo Fujita. +// +// Licensed under 2-clause BSD liecense. +// + +// +// version 0.9.0: Initial +// + +// +// @todo { Read .mtl } +// + +#include + +#include +#include +#include +#include +#include + +#include "tiny_obj_loader.h" + +namespace tinyobj { + +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) {}; + +}; +// 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); + + return false; +} + +struct obj_shape { + std::vector v; + std::vector vn; + std::vector vt; +}; + +static inline bool isSpace(const char c) { + return (c == ' ') || (c == '\t'); +} + +// Make index zero-base, and also support relative index. +static inline int fixIndex(int idx, int n) +{ + int i; + + if (idx > 0) { + i = idx - 1; + } else if (idx == 0) { + i = 0; + } else { // negative value = relative + i = n + idx; + } + return i; +} + +static inline float parseFloat(const char*& token) +{ + token += strspn(token, " \t"); + float f = (float)atof(token); + token += strcspn(token, " \t\r"); + return f; +} + +static inline void parseFloat2( + float& x, float& y, + const char*& 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); +} + + +// 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); + + 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/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 + 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); + + if (it != vertexCache.end()) { + // found cache + return it->second; + } + + assert(in_positions.size() > (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.vt_idx >= 0) { + texcoords.push_back(in_texcoords[3*i.vt_idx+0]); + texcoords.push_back(in_texcoords[3*i.vt_idx+1]); + texcoords.push_back(in_texcoords[3*i.vt_idx+2]); + } + + unsigned int idx = positions.size() / 3 - 1; + vertexCache[i] = idx; + + return idx; +} + +static bool +exportFaceGroupToShape( + shape_t& shape, + const std::vector in_positions, + const std::vector in_normals, + const std::vector in_texcoords, + const std::vector >& faceGroup, + const std::string name) +{ + if (faceGroup.empty()) { + return false; + } + + // Flattened version of vertex data + std::vector positions; + std::vector normals; + std::vector texcoords; + std::map vertexCache; + std::vector indices; + + // 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]; + + size_t npolys = face.size(); + + // Polygon -> triangle fan conversion + for (size_t k = 2; k < npolys; k++) { + i1 = i2; + i2 = face[k]; + + unsigned int v0 = updateVertex(vertexCache, positions, normals, texcoords, in_positions, in_normals, in_texcoords, i0); + unsigned int v1 = updateVertex(vertexCache, positions, normals, texcoords, in_positions, in_normals, in_texcoords, i1); + unsigned int v2 = updateVertex(vertexCache, positions, normals, texcoords, in_positions, in_normals, in_texcoords, i2); + + indices.push_back(v0); + indices.push_back(v1); + indices.push_back(v2); + } + + } + + // + // Construct shape. + // + shape.name = name; + shape.mesh.positions.swap(positions); + shape.mesh.normals.swap(normals); + shape.mesh.texcoords.swap(texcoords); + shape.mesh.indices.swap(indices); + + // @todo { material, name } + + return true; + +} + +std::string +LoadObj( + std::vector& shapes, + const char* filename) +{ + + shapes.clear(); + + std::stringstream err; + + std::ifstream ifs(filename); + if (!ifs) { + err << "Cannot open file [" << filename << "]" << std::endl; + return err.str(); + } + + std::vector v; + std::vector vn; + std::vector vt; + std::vector > faceGroup; + std::string name; + + int maxchars = 8192; // Alloc enough size. + std::vector buf(maxchars); // Alloc enough size. + while (ifs.peek() != -1) { + ifs.getline(&buf[0], maxchars); + + std::string linebuf(&buf[0]); + + // Trim newline '\r\n' or '\r' + if (linebuf.size() > 0) { + if (linebuf[linebuf.size()-1] == '\n') linebuf.erase(linebuf.size()-1); + } + if (linebuf.size() > 0) { + if (linebuf[linebuf.size()-1] == '\n') linebuf.erase(linebuf.size()-1); + } + + // Skip if empty line. + if (linebuf.empty()) { + continue; + } + + // Skip leading space. + const char* token = linebuf.c_str(); + token += strspn(token, " \t"); + + assert(token); + if (token[0] == '\0') continue; // empty line + + if (token[0] == '#') continue; // comment 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; + } + + // 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; + } + + // 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; + } + + // face + if (token[0] == 'f' && isSpace((token[1]))) { + token += 2; + token += strspn(token, " \t"); + + std::vector face; + while (token[0] && (token[1] != '\0')) { + vertex_index vi = parseTriple(token, v.size() / 3, vn.size() / 3, vt.size() / 2); + face.push_back(vi); + int n = strspn(token, " \t"); + token += n; + } + + faceGroup.push_back(face); + + continue; + } + + // use mtl + if ((0 == strncmp(token, "usemtl", 6)) && isSpace((token[6]))) { + + // flush previous face group. + shape_t shape; + bool ret = exportFaceGroupToShape(shape, v, vn, vt, faceGroup, name); + if (ret) { + shapes.push_back(shape); + } + + faceGroup.clear(); + + continue; + + } + + // load mtl + if ((0 == strncmp(token, "mtllib", 6)) && isSpace((token[6]))) { + continue; + } + + // object name + if (token[0] == 'o' && isSpace((token[1]))) { + char namebuf[4096]; + token += 2; + sscanf(token, "%s", namebuf); + name = std::string(namebuf); + + continue; + } + + // Ignore unknown command. + } + + shape_t shape; + bool ret = exportFaceGroupToShape(shape, v, vn, vt, faceGroup, name); + if (ret) { + shapes.push_back(shape); + } + faceGroup.clear(); // for safety + + return err.str(); +} + + +}; diff --git a/tiny_obj_loader.h b/tiny_obj_loader.h new file mode 100644 index 0000000..4f64925 --- /dev/null +++ b/tiny_obj_loader.h @@ -0,0 +1,53 @@ +// +// Copyright 2012, Syoyo Fujita. +// +// Licensed under 2-clause BSD liecense. +// +#ifndef _TINY_OBJ_LOADER_H +#define _TINY_OBJ_LOADER_H + +#include +#include + +namespace tinyobj { + +typedef struct +{ + std::string name; + + float ambient[3]; + float diffuse[3]; + float specular[3]; + float transmittance[3]; + + std::string ambient_texname; + std::string diffuse_texname; + std::string specular_texname; +} material_t; + +typedef struct +{ + std::vector positions; + std::vector normals; + std::vector texcoords; + std::vector indices; +} mesh_t; + +typedef struct +{ + std::string name; + material_t material; + mesh_t mesh; +} shape_t; + +/// Loads .obj from a file. +/// 'shapes' will be filled with parsed shape data +/// The function returns error string. +/// Returns empty string when loading .obj success. +std::string LoadObj( + std::vector& shapes, // [output] + const char* filename); + +}; + +#endif // _TINY_OBJ_LOADER_H -- cgit v1.2.3 From 66528fbd6c84a33c44eea1579cc692dc45526037 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Mon, 20 Aug 2012 13:20:06 +0900 Subject: Add initial support for loading .mtl. Thanks githole for the patch! --- test.cc | 7 +++ tiny_obj_loader.cc | 175 ++++++++++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 167 insertions(+), 15 deletions(-) diff --git a/test.cc b/test.cc index 1a15f5e..45a0f8b 100644 --- a/test.cc +++ b/test.cc @@ -39,6 +39,13 @@ main( shapes[i].mesh.positions[3*v+1], shapes[i].mesh.positions[3*v+2]); } + + printf("shape[%ld].material.name = %s\n", i, shapes[i].material.name.c_str()); + printf(" material.Ka = (%f, %f ,%f)\n", shapes[i].material.ambient[0], shapes[i].material.ambient[1], shapes[i].material.ambient[2]); + printf(" material.Kd = (%f, %f ,%f)\n", shapes[i].material.diffuse[0], shapes[i].material.diffuse[1], shapes[i].material.diffuse[2]); + printf(" material.Ks = (%f, %f ,%f)\n", shapes[i].material.specular[0], shapes[i].material.specular[1], shapes[i].material.specular[2]); + printf(" material.Tr = (%f, %f ,%f)\n", shapes[i].material.transmittance[0], shapes[i].material.transmittance[1], shapes[i].material.transmittance[2]); + printf("\n"); } return 0; diff --git a/tiny_obj_loader.cc b/tiny_obj_loader.cc index f1cf661..99e1865 100644 --- a/tiny_obj_loader.cc +++ b/tiny_obj_loader.cc @@ -5,12 +5,10 @@ // // +// version 0.9.1: Add initial .mtl load suppor // version 0.9.0: Initial // -// -// @todo { Read .mtl } -// #include @@ -51,6 +49,10 @@ 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'); +} + // Make index zero-base, and also support relative index. static inline int fixIndex(int idx, int n) { @@ -178,6 +180,7 @@ exportFaceGroupToShape( const std::vector in_normals, const std::vector in_texcoords, const std::vector >& faceGroup, + const material_t material, const std::string name) { if (faceGroup.empty()) { @@ -226,12 +229,129 @@ exportFaceGroupToShape( shape.mesh.texcoords.swap(texcoords); shape.mesh.indices.swap(indices); - // @todo { material, name } + shape.material = material; return true; } + +void InitMaterial(material_t& material) { + material.name = material.ambient_texname = material.diffuse_texname = material.specular_texname = ""; + for (int i = 0; i < 3; i ++) { + material.ambient[i] = material.diffuse[i] = material.specular[i] = material.transmittance[i] = 0.0; + } +} + +std::string LoadMtl ( + std::map& material_map, + const char* filename) +{ + material_map.clear(); + std::stringstream err; + + std::ifstream ifs(filename); + if (!ifs) { + err << "Cannot open file [" << filename << "]" << std::endl; + return err.str(); + } + + material_t material; + + int maxchars = 8192; // Alloc enough size. + std::vector buf(maxchars); // Alloc enough size. + while (ifs.peek() != -1) { + ifs.getline(&buf[0], maxchars); + + std::string linebuf(&buf[0]); + + // Trim newline '\r\n' or '\r' + if (linebuf.size() > 0) { + if (linebuf[linebuf.size()-1] == '\n') linebuf.erase(linebuf.size()-1); + } + if (linebuf.size() > 0) { + if (linebuf[linebuf.size()-1] == '\n') linebuf.erase(linebuf.size()-1); + } + + // Skip if empty line. + if (linebuf.empty()) { + continue; + } + + // Skip leading space. + const char* token = linebuf.c_str(); + token += strspn(token, " \t"); + + assert(token); + if (token[0] == '\0') continue; // empty line + + if (token[0] == '#') continue; // comment line + + // new mtl + if ((0 == strncmp(token, "newmtl", 6)) && isSpace((token[6]))) { + // flush previous material. + material_map.insert(std::pair(material.name, material)); + + // initial temporary material + InitMaterial(material); + + // set new mtl name + char namebuf[4096]; + token += 7; + sscanf(token, "%s", namebuf); + 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; + } + + // 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] == 't' && 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; + } + // Ignore unknown command. + } + + return err.str(); +} + std::string LoadObj( std::vector& shapes, @@ -254,6 +374,10 @@ LoadObj( std::vector > faceGroup; std::string name; + // material + std::map material_map; + material_t material; + int maxchars = 8192; // Alloc enough size. std::vector buf(maxchars); // Alloc enough size. while (ifs.peek() != -1) { @@ -321,10 +445,10 @@ LoadObj( token += strspn(token, " \t"); std::vector face; - while (token[0] && (token[1] != '\0')) { + while (!isNewLine(token[0])) { vertex_index vi = parseTriple(token, v.size() / 3, vn.size() / 3, vt.size() / 2); face.push_back(vi); - int n = strspn(token, " \t"); + int n = strspn(token, " \t\r"); token += n; } @@ -336,31 +460,52 @@ LoadObj( // use mtl if ((0 == strncmp(token, "usemtl", 6)) && isSpace((token[6]))) { - // flush previous face group. - shape_t shape; - bool ret = exportFaceGroupToShape(shape, v, vn, vt, faceGroup, name); - if (ret) { - shapes.push_back(shape); - } - - faceGroup.clear(); + char namebuf[4096]; + token += 7; + sscanf(token, "%s", namebuf); + if (material_map.find(namebuf) != material_map.end()) { + material = material_map[namebuf]; + } else { + // { error!! material not found } + InitMaterial(material); + } continue; } // load mtl if ((0 == strncmp(token, "mtllib", 6)) && isSpace((token[6]))) { + char namebuf[4096]; + token += 7; + sscanf(token, "%s", namebuf); + + std::string err_mtl = LoadMtl(material_map, namebuf); + if (!err_mtl.empty()) { + faceGroup.clear(); // for safety + return err_mtl; + } continue; } // object name if (token[0] == 'o' && isSpace((token[1]))) { + + // flush previous face group. + shape_t shape; + bool ret = exportFaceGroupToShape(shape, v, vn, vt, faceGroup, material, name); + if (ret) { + shapes.push_back(shape); + } + + faceGroup.clear(); + char namebuf[4096]; token += 2; sscanf(token, "%s", namebuf); name = std::string(namebuf); + continue; } @@ -368,7 +513,7 @@ LoadObj( } shape_t shape; - bool ret = exportFaceGroupToShape(shape, v, vn, vt, faceGroup, name); + bool ret = exportFaceGroupToShape(shape, v, vn, vt, faceGroup, material, name); if (ret) { shapes.push_back(shape); } -- cgit v1.2.3 From d62fdaf89da76495caf8ea51723139677ae23c93 Mon Sep 17 00:00:00 2001 From: YarmUI Date: Tue, 21 Aug 2012 18:35:37 +0900 Subject: update .mtl loader (Ke, Ns, map_Ka, map_Kd, map_Ks, map_Ns) --- test.cc | 8 +++++- tiny_obj_loader.cc | 72 +++++++++++++++++++++++++++++++++++++++++++++++++++--- tiny_obj_loader.h | 5 ++++ 3 files changed, 81 insertions(+), 4 deletions(-) diff --git a/test.cc b/test.cc index 45a0f8b..7a8c3dd 100644 --- a/test.cc +++ b/test.cc @@ -8,7 +8,7 @@ main( int argc, char **argv) { - std::string inputfile = "cornell_box.obj"; + std::string inputfile = "test.obj"; std::vector shapes; if (argc > 1) { @@ -45,6 +45,12 @@ main( printf(" material.Kd = (%f, %f ,%f)\n", shapes[i].material.diffuse[0], shapes[i].material.diffuse[1], shapes[i].material.diffuse[2]); printf(" material.Ks = (%f, %f ,%f)\n", shapes[i].material.specular[0], shapes[i].material.specular[1], shapes[i].material.specular[2]); printf(" material.Tr = (%f, %f ,%f)\n", shapes[i].material.transmittance[0], shapes[i].material.transmittance[1], shapes[i].material.transmittance[2]); + printf(" material.Ke = (%f, %f ,%f)\n", shapes[i].material.emission[0], shapes[i].material.emission[1], shapes[i].material.emission[2]); + printf(" material.Ns = %f\n", shapes[i].material.shininess); + printf(" material.map_Ka = %s\n", shapes[i].material.ambient_texname.c_str()); + printf(" material.map_Kd = %s\n", shapes[i].material.diffuse_texname.c_str()); + printf(" material.map_Ks = %s\n", shapes[i].material.specular_texname.c_str()); + printf(" material.map_Ns = %s\n", shapes[i].material.normal_texname.c_str()); printf("\n"); } diff --git a/tiny_obj_loader.cc b/tiny_obj_loader.cc index 99e1865..e601ce3 100644 --- a/tiny_obj_loader.cc +++ b/tiny_obj_loader.cc @@ -237,10 +237,19 @@ exportFaceGroupToShape( void InitMaterial(material_t& material) { - material.name = material.ambient_texname = material.diffuse_texname = material.specular_texname = ""; + material.name = ""; + material.ambient_texname = ""; + material.diffuse_texname = ""; + material.specular_texname = ""; + material.normal_texname = ""; for (int i = 0; i < 3; i ++) { - material.ambient[i] = material.diffuse[i] = material.specular[i] = material.transmittance[i] = 0.0; + 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.shininess = 1.f; } std::string LoadMtl ( @@ -346,7 +355,64 @@ std::string LoadMtl ( material.specular[2] = b; continue; } - // Ignore unknown command. + + // 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; + } + + // 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; + } + + // 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; + } + + // unknown parameter + char* _space = strchr(token, ' '); + if(!_space) { + _space = strchr(token, '\t'); + } + if(_space) { + *_space = '\0'; + std::string key = token; + std::string value = _space + 1; + material.unknown_parameter.insert(std::pair(key, value)); + } } return err.str(); diff --git a/tiny_obj_loader.h b/tiny_obj_loader.h index 4f64925..6c1adcc 100644 --- a/tiny_obj_loader.h +++ b/tiny_obj_loader.h @@ -8,6 +8,7 @@ #include #include +#include namespace tinyobj { @@ -19,10 +20,14 @@ typedef struct float diffuse[3]; float specular[3]; float transmittance[3]; + float emission[3]; + float shininess; std::string ambient_texname; std::string diffuse_texname; std::string specular_texname; + std::string normal_texname; + std::map unknown_parameter; } material_t; typedef struct -- cgit v1.2.3 From fb35a684e5e7711dee1c03da6c64c2179563444b Mon Sep 17 00:00:00 2001 From: YarmUI Date: Tue, 21 Aug 2012 18:46:10 +0900 Subject: bugfix --- cornell_box.obj | 2 +- test.cc | 2 +- tiny_obj_loader.cc | 2 ++ 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/cornell_box.obj b/cornell_box.obj index a97672d..43e021f 100644 --- a/cornell_box.obj +++ b/cornell_box.obj @@ -34,7 +34,7 @@ v 343.0 548.0 227.0 v 343.0 548.0 332.0 v 213.0 548.0 332.0 v 213.0 548.0 227.0 -#f -4 -3 -2 -1 +f -4 -3 -2 -1 o ceiling usemtl white diff --git a/test.cc b/test.cc index 7a8c3dd..0515d99 100644 --- a/test.cc +++ b/test.cc @@ -8,7 +8,7 @@ main( int argc, char **argv) { - std::string inputfile = "test.obj"; + std::string inputfile = "cornell_box.obj"; std::vector shapes; if (argc > 1) { diff --git a/tiny_obj_loader.cc b/tiny_obj_loader.cc index e601ce3..e43c1c4 100644 --- a/tiny_obj_loader.cc +++ b/tiny_obj_loader.cc @@ -414,6 +414,8 @@ std::string LoadMtl ( material.unknown_parameter.insert(std::pair(key, value)); } } + // flush last material. + material_map.insert(std::pair(material.name, material)); return err.str(); } -- cgit v1.2.3 From 04e2eccaa96f8fbd634ab7a502c16f8ff729ad4d Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Wed, 22 Aug 2012 01:38:02 +0900 Subject: Fix build on linux. --- test.cc | 7 +++++++ tiny_obj_loader.cc | 11 +++++++---- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/test.cc b/test.cc index 0515d99..8916696 100644 --- a/test.cc +++ b/test.cc @@ -1,5 +1,7 @@ #include "tiny_obj_loader.h" +#include +#include #include #include @@ -51,6 +53,11 @@ main( printf(" material.map_Kd = %s\n", shapes[i].material.diffuse_texname.c_str()); printf(" material.map_Ks = %s\n", shapes[i].material.specular_texname.c_str()); printf(" material.map_Ns = %s\n", shapes[i].material.normal_texname.c_str()); + std::map::iterator it(shapes[i].material.unknown_parameter.begin()); + std::map::iterator itEnd(shapes[i].material.unknown_parameter.end()); + for (; it != itEnd; it++) { + printf(" material.%s = %s\n", it->first.c_str(), it->second.c_str()); + } printf("\n"); } diff --git a/tiny_obj_loader.cc b/tiny_obj_loader.cc index e43c1c4..ac93e3d 100644 --- a/tiny_obj_loader.cc +++ b/tiny_obj_loader.cc @@ -5,11 +5,14 @@ // // -// version 0.9.1: Add initial .mtl load suppor +// version 0.9.2: Add more .mtl load support +// version 0.9.1: Add initial .mtl load support // version 0.9.0: Initial // +#include +#include #include #include @@ -403,13 +406,13 @@ std::string LoadMtl ( } // unknown parameter - char* _space = strchr(token, ' '); + const char* _space = strchr(token, ' '); if(!_space) { _space = strchr(token, '\t'); } if(_space) { - *_space = '\0'; - std::string key = token; + int len = _space - token; + std::string key(token, len); std::string value = _space + 1; material.unknown_parameter.insert(std::pair(key, value)); } -- cgit v1.2.3 From c552dbc58227ef423e168f057ec475e1f7ffbded Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Thu, 6 Sep 2012 00:19:38 +0900 Subject: Fix texcoord array indexing. --- tiny_obj_loader.cc | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tiny_obj_loader.cc b/tiny_obj_loader.cc index ac93e3d..217df8a 100644 --- a/tiny_obj_loader.cc +++ b/tiny_obj_loader.cc @@ -165,9 +165,8 @@ updateVertex( } if (i.vt_idx >= 0) { - texcoords.push_back(in_texcoords[3*i.vt_idx+0]); - texcoords.push_back(in_texcoords[3*i.vt_idx+1]); - texcoords.push_back(in_texcoords[3*i.vt_idx+2]); + texcoords.push_back(in_texcoords[2*i.vt_idx+0]); + texcoords.push_back(in_texcoords[2*i.vt_idx+1]); } unsigned int idx = positions.size() / 3 - 1; -- cgit v1.2.3 From 3ae1bea017e50a3ff6c451ab34a043dd820df554 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Sat, 10 Nov 2012 14:46:44 +0900 Subject: Fix parsing triple. --- tiny_obj_loader.cc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tiny_obj_loader.cc b/tiny_obj_loader.cc index 217df8a..8dccecf 100644 --- a/tiny_obj_loader.cc +++ b/tiny_obj_loader.cc @@ -5,6 +5,7 @@ // // +// version 0.9.3: Fix parsing triple 'x/y/z' // version 0.9.2: Add more .mtl load support // version 0.9.1: Add initial .mtl load support // version 0.9.0: Initial @@ -129,6 +130,7 @@ static vertex_index parseTriple( } // i/j/k + token++; // skip '/' vi.vn_idx = fixIndex(atoi(token), vnsize); token += strcspn(token, "/ \t\r"); return vi; -- cgit v1.2.3 From 07a52129a570352b32f25e51cb8db7de8232c51f Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Thu, 21 Feb 2013 01:08:58 +0900 Subject: Update README. Add usage example. --- README.md | 67 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 66 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 431527c..e469e29 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,69 @@ tinyobjloader ============= -Tiny but poweful single file wavefront obj loader written in C++. no dependency except for C++ STL. +Tiny but poweful single file wavefront obj loader written in C++. no dependency except for C++ STL. Good for embedding .obj loader to your renderer ;-) + +Features +-------- + +* Vertex +* Texcoord +* Normal +* Material + * Unknown material attributes are treated as key-value. + +Notes +----- + +Polygon is converted into triangle. + + +Usage +----- + + std::string inputfile = "cornell_box.obj"; + std::vector shapes; + + std::string err = tinyobj::LoadObj(shapes, inputfile.c_str()); + + if (!err.empty()) { + std::cerr << err << std::endl; + } + + std::cout << "# of shapes : " << shapes.size() << std::endl; + + for (size_t i = 0; i < shapes.size(); i++) { + printf("shape[%ld].name = %s\n", i, shapes[i].name.c_str()); + printf("shape[%ld].indices: %ld\n", i, shapes[i].mesh.indices.size()); + assert((shapes[i].mesh.indices.size() % 3) == 0); + for (size_t f = 0; f < shapes[i].mesh.indices.size(); f++) { + printf(" idx[%ld] = %d\n", f, shapes[i].mesh.indices[f]); + } + + 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].material.name = %s\n", i, shapes[i].material.name.c_str()); + printf(" material.Ka = (%f, %f ,%f)\n", shapes[i].material.ambient[0], shapes[i].material.ambient[1], shapes[i].material.ambient[2]); + printf(" material.Kd = (%f, %f ,%f)\n", shapes[i].material.diffuse[0], shapes[i].material.diffuse[1], shapes[i].material.diffuse[2]); + printf(" material.Ks = (%f, %f ,%f)\n", shapes[i].material.specular[0], shapes[i].material.specular[1], shapes[i].material.specular[2]); + printf(" material.Tr = (%f, %f ,%f)\n", shapes[i].material.transmittance[0], shapes[i].material.transmittance[1], shapes[i].material.transmittance[2]); + printf(" material.Ke = (%f, %f ,%f)\n", shapes[i].material.emission[0], shapes[i].material.emission[1], shapes[i].material.emission[2]); + printf(" material.Ns = %f\n", shapes[i].material.shininess); + printf(" material.map_Ka = %s\n", shapes[i].material.ambient_texname.c_str()); + printf(" material.map_Kd = %s\n", shapes[i].material.diffuse_texname.c_str()); + printf(" material.map_Ks = %s\n", shapes[i].material.specular_texname.c_str()); + printf(" material.map_Ns = %s\n", shapes[i].material.normal_texname.c_str()); + std::map::iterator it(shapes[i].material.unknown_parameter.begin()); + std::map::iterator itEnd(shapes[i].material.unknown_parameter.end()); + for (; it != itEnd; it++) { + printf(" material.%s = %s\n", it->first.c_str(), it->second.c_str()); + } + printf("\n"); + } -- cgit v1.2.3 From 6556411006b25a2a8193d5eeaf61b533bd32b285 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Thu, 25 Apr 2013 17:01:52 +0900 Subject: Add support of group tag('g') --- cube.mtl | 24 ++++++++++++++++++++++++ cube.obj | 31 +++++++++++++++++++++++++++++++ test.cc | 33 ++++++++++++++++++++++----------- tiny_obj_loader.cc | 26 +++++++++++++++++++++++++- 4 files changed, 102 insertions(+), 12 deletions(-) create mode 100644 cube.mtl create mode 100644 cube.obj diff --git a/cube.mtl b/cube.mtl new file mode 100644 index 0000000..d3a1c7a --- /dev/null +++ b/cube.mtl @@ -0,0 +1,24 @@ +newmtl white +Ka 0 0 0 +Kd 1 1 1 +Ks 0 0 0 + +newmtl red +Ka 0 0 0 +Kd 1 0 0 +Ks 0 0 0 + +newmtl green +Ka 0 0 0 +Kd 0 1 0 +Ks 0 0 0 + +newmtl blue +Ka 0 0 0 +Kd 0 0 1 +Ks 0 0 0 + +newmtl light +Ka 20 20 20 +Kd 1 1 1 +Ks 0 0 0 diff --git a/cube.obj b/cube.obj new file mode 100644 index 0000000..9213e17 --- /dev/null +++ b/cube.obj @@ -0,0 +1,31 @@ +mtllib cube.mtl + +v 0.000000 2.000000 2.000000 +v 0.000000 0.000000 2.000000 +v 2.000000 0.000000 2.000000 +v 2.000000 2.000000 2.000000 +v 0.000000 2.000000 0.000000 +v 0.000000 0.000000 0.000000 +v 2.000000 0.000000 0.000000 +v 2.000000 2.000000 0.000000 +# 8 vertices + +g front cube +usemtl white +f 1 2 3 4 +g back cube +# expects white material +f 8 7 6 5 +g right cube +usemtl red +f 4 3 7 8 +g top cube +usemtl white +f 5 1 4 8 +g left cube +usemtl green +f 5 6 2 1 +g bottom cube +usemtl white +f 2 6 7 3 +# 6 elements diff --git a/test.cc b/test.cc index 8916696..4de4cc2 100644 --- a/test.cc +++ b/test.cc @@ -5,22 +5,17 @@ #include #include -int -main( - int argc, - char **argv) +static bool +TestLoadObj(const char* filename) { - std::string inputfile = "cornell_box.obj"; - std::vector shapes; + std::cout << "Loading " << filename << std::endl; - if (argc > 1) { - inputfile = std::string(argv[1]); - } - - std::string err = tinyobj::LoadObj(shapes, inputfile.c_str()); + std::vector shapes; + std::string err = tinyobj::LoadObj(shapes, filename); if (!err.empty()) { std::cerr << err << std::endl; + return false; } std::cout << "# of shapes : " << shapes.size() << std::endl; @@ -61,5 +56,21 @@ main( printf("\n"); } + return true; +} + +int +main( + int argc, + char **argv) +{ + + if (argc > 1) { + assert(true == TestLoadObj(argv[1])); + } else { + assert(true == TestLoadObj("cornell_box.obj")); + assert(true == TestLoadObj("cube.obj")); + } + return 0; } diff --git a/tiny_obj_loader.cc b/tiny_obj_loader.cc index 8dccecf..7988fab 100644 --- a/tiny_obj_loader.cc +++ b/tiny_obj_loader.cc @@ -1,10 +1,11 @@ // -// Copyright 2012, Syoyo Fujita. +// Copyright 2012-2013, Syoyo Fujita. // // Licensed under 2-clause BSD liecense. // // +// 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 // version 0.9.1: Add initial .mtl load support @@ -560,6 +561,29 @@ LoadObj( continue; } + // group name + if (token[0] == 'g' && isSpace((token[1]))) { + + printf("group\n"); + + // flush previous face group. + shape_t shape; + bool ret = exportFaceGroupToShape(shape, v, vn, vt, faceGroup, material, name); + if (ret) { + shapes.push_back(shape); + } + + faceGroup.clear(); + + // @todo { multiple group name. } + char namebuf[4096]; + token += 2; + sscanf(token, "%s", namebuf); + name = std::string(namebuf); + + continue; + } + // object name if (token[0] == 'o' && isSpace((token[1]))) { -- cgit v1.2.3 From f48a06885b50a0bbaee1c60a610fcb7bd926a86a Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Thu, 25 Apr 2013 17:03:15 +0900 Subject: Update README. --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index e469e29..678f80f 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,7 @@ Tiny but poweful single file wavefront obj loader written in C++. no dependency Features -------- +* Group * Vertex * Texcoord * Normal -- cgit v1.2.3 From 7c362f7dda814cd43afc5c890596dc6e7c0a9070 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Thu, 25 Apr 2013 17:05:53 +0900 Subject: Update README. --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 678f80f..1811880 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,9 @@ tinyobjloader ============= -Tiny but poweful single file wavefront obj loader written in C++. no dependency except for C++ STL. Good for embedding .obj loader to your renderer ;-) +Tiny but poweful single file wavefront obj loader written in C++. No dependency except for C++ STL. It can parse 10M over polygons with moderate memory and time. + +Good for embedding .obj loader to your (global illumination) renderer ;-) Features -------- -- cgit v1.2.3 From 1f39e09ef4a2adecbdfa7ceeac3edb9ef544893d Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Thu, 25 Apr 2013 17:12:48 +0900 Subject: Add licensing term. --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index 1811880..a9bc8fb 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,10 @@ Notes Polygon is converted into triangle. +License +------- + +Licensed under 2 clause BSD. Usage ----- @@ -31,6 +35,7 @@ Usage if (!err.empty()) { std::cerr << err << std::endl; + exit(1); } std::cout << "# of shapes : " << shapes.size() << std::endl; -- cgit v1.2.3 From f391a4184d17e18a9bd00434bf906b672eb93f05 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Thu, 25 Apr 2013 19:46:08 +0900 Subject: Add rendering image sample. --- README.md | 9 +++++++++ images/rungholt.jpg | Bin 0 -> 96018 bytes 2 files changed, 9 insertions(+) create mode 100644 images/rungholt.jpg diff --git a/README.md b/README.md index a9bc8fb..bb7dcaa 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,15 @@ Tiny but poweful single file wavefront obj loader written in C++. No dependency Good for embedding .obj loader to your (global illumination) renderer ;-) + +Example +------- + +https://github.com/syoyo/tinyobjloader/images/rungholt.jpg + +tinyobjloader can successfully load 6M triangles Rungholt scene. +http://graphics.cs.williams.edu/data/meshes.xml + Features -------- diff --git a/images/rungholt.jpg b/images/rungholt.jpg new file mode 100644 index 0000000..17718eb Binary files /dev/null and b/images/rungholt.jpg differ -- cgit v1.2.3 From f2a1a06834b12e9a97be215827337c39d82dc62e Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Thu, 25 Apr 2013 19:47:44 +0900 Subject: Fix link. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index bb7dcaa..c76878d 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ Good for embedding .obj loader to your (global illumination) renderer ;-) Example ------- -https://github.com/syoyo/tinyobjloader/images/rungholt.jpg +https://raw.github.com/syoyo/tinyobjloader/master/images/rungholt.jpg tinyobjloader can successfully load 6M triangles Rungholt scene. http://graphics.cs.williams.edu/data/meshes.xml -- cgit v1.2.3 From ede38bb59eca7780341a501984c72fd336f6749c Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Thu, 25 Apr 2013 19:48:27 +0900 Subject: Add link to github pages. --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index c76878d..84ae09f 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ tinyobjloader ============= +http://syoyo.github.io/tinyobjloader/ + Tiny but poweful single file wavefront obj loader written in C++. No dependency except for C++ STL. It can parse 10M over polygons with moderate memory and time. Good for embedding .obj loader to your (global illumination) renderer ;-) -- cgit v1.2.3 From 911187d9c39e8d4a8d674f3c736a7bbf7af8adb0 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Thu, 25 Apr 2013 19:50:44 +0900 Subject: Fix link once again... --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 84ae09f..6f911e5 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ Good for embedding .obj loader to your (global illumination) renderer ;-) Example ------- -https://raw.github.com/syoyo/tinyobjloader/master/images/rungholt.jpg +![Rungholt](https://github.com/syoyo/tinyobjloader/blob/master/images/rungholt.jpg?raw=true) tinyobjloader can successfully load 6M triangles Rungholt scene. http://graphics.cs.williams.edu/data/meshes.xml -- cgit v1.2.3 From 5284dcb19ff49fa11c7126485a5f7c1f0f9c41ad Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Sat, 29 Jun 2013 13:00:22 +0900 Subject: Parse multiple group name. Not yet finished fully though. --- test.cc | 12 +++++++++--- tiny_obj_loader.cc | 56 ++++++++++++++++++++++++++++++++++++++++++------------ tiny_obj_loader.h | 6 ++++-- 3 files changed, 57 insertions(+), 17 deletions(-) diff --git a/test.cc b/test.cc index 4de4cc2..7148135 100644 --- a/test.cc +++ b/test.cc @@ -6,12 +6,14 @@ #include static bool -TestLoadObj(const char* filename) +TestLoadObj( + const char* filename, + const char* basepath = NULL) { std::cout << "Loading " << filename << std::endl; std::vector shapes; - std::string err = tinyobj::LoadObj(shapes, filename); + std::string err = tinyobj::LoadObj(shapes, filename, basepath); if (!err.empty()) { std::cerr << err << std::endl; @@ -66,7 +68,11 @@ main( { if (argc > 1) { - assert(true == TestLoadObj(argv[1])); + const char* basepath = NULL; + if (argc > 2) { + basepath = argv[2]; + } + assert(true == TestLoadObj(argv[1], basepath)); } else { assert(true == TestLoadObj("cornell_box.obj")); assert(true == TestLoadObj("cube.obj")); diff --git a/tiny_obj_loader.cc b/tiny_obj_loader.cc index 7988fab..0cc9e29 100644 --- a/tiny_obj_loader.cc +++ b/tiny_obj_loader.cc @@ -5,6 +5,8 @@ // // +// version 0.9.5: Parse multiple group name. +// 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 @@ -73,6 +75,17 @@ static inline int fixIndex(int idx, int n) return i; } +static inline std::string parseString(const char*& token) +{ + std::string s; + int b = strspn(token, " \t"); + int e = strcspn(token, " \t\r"); + s = std::string(&token[b], &token[e]); + + token += (e - b); + return s; +} + static inline float parseFloat(const char*& token) { token += strspn(token, " \t"); @@ -259,14 +272,23 @@ void InitMaterial(material_t& material) { std::string LoadMtl ( std::map& material_map, - const char* filename) + const char* filename, + const char* mtl_basepath) { material_map.clear(); std::stringstream err; - std::ifstream ifs(filename); + std::string filepath; + + if (mtl_basepath) { + filepath = std::string(mtl_basepath) + std::string(filename); + } else { + filepath = std::string(filename); + } + + std::ifstream ifs(filepath.c_str()); if (!ifs) { - err << "Cannot open file [" << filename << "]" << std::endl; + err << "Cannot open file [" << filepath << "]" << std::endl; return err.str(); } @@ -428,7 +450,8 @@ std::string LoadMtl ( std::string LoadObj( std::vector& shapes, - const char* filename) + const char* filename, + const char* mtl_basepath) { shapes.clear(); @@ -553,7 +576,7 @@ LoadObj( token += 7; sscanf(token, "%s", namebuf); - std::string err_mtl = LoadMtl(material_map, namebuf); + std::string err_mtl = LoadMtl(material_map, namebuf, mtl_basepath); if (!err_mtl.empty()) { faceGroup.clear(); // for safety return err_mtl; @@ -564,8 +587,6 @@ LoadObj( // group name if (token[0] == 'g' && isSpace((token[1]))) { - printf("group\n"); - // flush previous face group. shape_t shape; bool ret = exportFaceGroupToShape(shape, v, vn, vt, faceGroup, material, name); @@ -575,11 +596,21 @@ LoadObj( faceGroup.clear(); - // @todo { multiple group name. } - char namebuf[4096]; - token += 2; - sscanf(token, "%s", namebuf); - name = std::string(namebuf); + 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); + + // names[0] must be 'g', so skipt 0th element. + if (names.size() > 1) { + name = names[1]; + } else { + name = ""; + } continue; } @@ -596,6 +627,7 @@ LoadObj( faceGroup.clear(); + // @todo { multiple object name? } char namebuf[4096]; token += 2; sscanf(token, "%s", namebuf); diff --git a/tiny_obj_loader.h b/tiny_obj_loader.h index 6c1adcc..bc3ad2e 100644 --- a/tiny_obj_loader.h +++ b/tiny_obj_loader.h @@ -1,5 +1,5 @@ // -// Copyright 2012, Syoyo Fujita. +// Copyright 2012-2013, Syoyo Fujita. // // Licensed under 2-clause BSD liecense. // @@ -49,9 +49,11 @@ typedef struct /// 'shapes' will be filled with parsed shape data /// 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] - const char* filename); + const char* filename, + const char* mtl_basepath = NULL); }; -- cgit v1.2.3 From e94b20c042d4a5c3d5b83f5ab3fd9f4c9f47a3a6 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Sun, 21 Jul 2013 13:48:58 +0900 Subject: Update README. --- README.md | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 6f911e5..b8df296 100644 --- a/README.md +++ b/README.md @@ -16,10 +16,17 @@ Example tinyobjloader can successfully load 6M triangles Rungholt scene. http://graphics.cs.williams.edu/data/meshes.xml +Use +--- + +TinyObjLoader is used in ... + +* bullet3 https://github.com/erwincoumans/bullet3 + Features -------- -* Group +* Group(parse multiple group name) * Vertex * Texcoord * Normal @@ -31,6 +38,11 @@ Notes Polygon is converted into triangle. +TODO +---- + +* Support quad polygon and some tags for OpenSubdiv http://graphics.pixar.com/opensubdiv/ + License ------- -- cgit v1.2.3 From 1544f0c8650d89df995fbc91b7ed5751b5e2b7e7 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Wed, 11 Sep 2013 13:02:37 +0900 Subject: Parse ior and transmittance of material parameter. Add .obj sticher example. --- examples/obj_sticher/obj_sticher.cc | 82 +++++++++++++++++++++ examples/obj_sticher/obj_writer.cc | 139 ++++++++++++++++++++++++++++++++++++ examples/obj_sticher/obj_writer.h | 9 +++ examples/obj_sticher/premake4.lua | 38 ++++++++++ test.cc | 1 + tiny_obj_loader.cc | 17 +++-- tiny_obj_loader.h | 1 + 7 files changed, 283 insertions(+), 4 deletions(-) create mode 100644 examples/obj_sticher/obj_sticher.cc create mode 100644 examples/obj_sticher/obj_writer.cc create mode 100644 examples/obj_sticher/obj_writer.h create mode 100644 examples/obj_sticher/premake4.lua diff --git a/examples/obj_sticher/obj_sticher.cc b/examples/obj_sticher/obj_sticher.cc new file mode 100644 index 0000000..296d384 --- /dev/null +++ b/examples/obj_sticher/obj_sticher.cc @@ -0,0 +1,82 @@ +// +// Stiches multiple .obj files into one .obj. +// +#include "../../tiny_obj_loader.h" +#include "obj_writer.h" + +#include +#include + +typedef std::vector Shape; + +void +StichObjs( + std::vector& out, + const std::vector& shapes) +{ + int numShapes = 0; + for (size_t i = 0; i < shapes.size(); i++) { + numShapes += (int)shapes[i].size(); + } + + printf("Total # of shapes = %d\n", numShapes); + + size_t face_offset = 0; + for (size_t i = 0; i < shapes.size(); i++) { + for (size_t k = 0; k < shapes[i].size(); k++) { + + std::string new_name = shapes[i][k].name; + // Add suffix + char buf[1024]; + sprintf(buf, "_%04d", (int)i); + new_name += std::string(buf); + + printf("shape[%ld][%ld].name = %s\n", i, k, shapes[i][k].name.c_str()); + assert((shapes[i][k].mesh.indices.size() % 3) == 0); + assert((shapes[i][k].mesh.positions.size() % 3) == 0); + + tinyobj::shape_t new_shape = shapes[i][k]; + new_shape.name = new_name; + printf("shape[%ld][%ld].new_name = %s\n", i, k, new_shape.name.c_str()); + + out.push_back(new_shape); + } + } +} + +int +main( + int argc, + char **argv) +{ + if (argc < 3) { + printf("Usage: obj_sticher input0.obj input1.obj ... output.obj\n"); + exit(1); + } + + int num_objfiles = argc - 2; + std::string out_filename = std::string(argv[argc-1]); // last element + + std::vector shapes; + shapes.resize(num_objfiles); + + for (int i = 0; i < num_objfiles; i++) { + std::cout << "Loading " << argv[i+1] << " ... " << std::flush; + + std::string err = tinyobj::LoadObj(shapes[i], argv[i+1]); + if (!err.empty()) { + std::cerr << err << std::endl; + exit(1); + } + + std::cout << "DONE." << std::endl; + } + + std::vector out; + StichObjs(out, shapes); + + bool ret = WriteObj(out_filename, out); + assert(ret); + + return 0; +} diff --git a/examples/obj_sticher/obj_writer.cc b/examples/obj_sticher/obj_writer.cc new file mode 100644 index 0000000..67af93e --- /dev/null +++ b/examples/obj_sticher/obj_writer.cc @@ -0,0 +1,139 @@ +// +// Simple wavefront .obj writer +// +#include "obj_writer.h" + +static std::string GetFileBasename(const std::string& FileName) +{ + if(FileName.find_last_of(".") != std::string::npos) + return FileName.substr(0, FileName.find_last_of(".")); + return ""; +} + +bool WriteMat(const std::string& filename, std::vector shapes) { + FILE* fp = fopen(filename.c_str(), "w"); + if (!fp) { + fprintf(stderr, "Failed to open file [ %s ] for write.\n", filename.c_str()); + return false; + } + + std::map mtl_table; + + for (size_t i = 0; i < shapes.size(); i++) { + mtl_table[shapes[i].material.name] = shapes[i].material; + } + + for (std::map::iterator it = mtl_table.begin(); it != mtl_table.end(); it++) { + + tinyobj::material_t mat = it->second; + + fprintf(fp, "newmtl %s\n", mat.name.c_str()); + fprintf(fp, "Ka %f %f %f\n", mat.ambient[0], mat.ambient[1], mat.ambient[2]); + fprintf(fp, "Kd %f %f %f\n", mat.diffuse[0], mat.diffuse[1], mat.diffuse[2]); + fprintf(fp, "Ks %f %f %f\n", mat.specular[0], mat.specular[1], mat.specular[2]); + fprintf(fp, "Kt %f %f %f\n", mat.transmittance[0], mat.specular[1], mat.specular[2]); + fprintf(fp, "Ke %f %f %f\n", mat.emission[0], mat.emission[1], mat.emission[2]); + fprintf(fp, "Ns %f\n", mat.shininess); + fprintf(fp, "Ni %f\n", mat.ior); + // @todo { texture } + } + + fclose(fp); + + return true; +} + +bool WriteObj(const std::string& filename, std::vector shapes) { + FILE* fp = fopen(filename.c_str(), "w"); + if (!fp) { + fprintf(stderr, "Failed to open file [ %s ] for write.\n", filename.c_str()); + return false; + } + + std::string basename = GetFileBasename(filename); + std::string material_filename = basename + ".mtl"; + + int v_offset = 0; + int vn_offset = 0; + int vt_offset = 0; + + fprintf(fp, "mtllib %s\n", material_filename.c_str()); + + for (size_t i = 0; i < shapes.size(); i++) { + + bool has_vn = false; + bool has_vt = false; + + if (shapes[i].name.empty()) { + fprintf(fp, "g Unknown\n"); + } else { + fprintf(fp, "g %s\n", shapes[i].name.c_str()); + } + + if (!shapes[i].material.name.empty()) { + fprintf(fp, "usemtl %s\n", shapes[i].material.name.c_str()); + } + + // vtx + for (size_t k = 0; k < shapes[i].mesh.positions.size() / 3; k++) { + fprintf(fp, "v %f %f %f\n", + shapes[i].mesh.positions[3*k+0], + shapes[i].mesh.positions[3*k+1], + shapes[i].mesh.positions[3*k+2]); + } + + // normal + for (size_t k = 0; k < shapes[i].mesh.normals.size() / 3; k++) { + fprintf(fp, "vn %f %f %f\n", + shapes[i].mesh.normals[3*k+0], + shapes[i].mesh.normals[3*k+1], + shapes[i].mesh.normals[3*k+2]); + } + if (shapes[i].mesh.normals.size() > 0) has_vn = true; + + // texcoord + for (size_t k = 0; k < shapes[i].mesh.texcoords.size() / 2; k++) { + fprintf(fp, "vt %f %f\n", + shapes[i].mesh.texcoords[2*k+0], + shapes[i].mesh.texcoords[2*k+1]); + } + if (shapes[i].mesh.texcoords.size() > 0) has_vt = true; + + // face + for (size_t k = 0; k < shapes[i].mesh.indices.size() / 3; k++) { + + // Face index is 1-base. + int v0 = shapes[i].mesh.indices[3*k+0] + 1 + v_offset; + int v1 = shapes[i].mesh.indices[3*k+1] + 1 + v_offset; + int v2 = shapes[i].mesh.indices[3*k+2] + 1 + v_offset; + + if (has_vn && has_vt) { + fprintf(fp, "f %d/%d/%d %d/%d/%d %d/%d/%d\n", + v0, v0, v0, v1, v1, v1, v2, v2, v2); + } else if (has_vn && !has_vt) { + fprintf(fp, "f %d//%d %d//%d %d//%d\n", v0, v0, v1, v1, v2, v2); + } else if (!has_vn && has_vt) { + fprintf(fp, "f %d/%d %d/%d %d/%d\n", v0, v0, v1, v1, v2, v2); + } else { + fprintf(fp, "f %d %d %d\n", v0, v1, v2); + } + + } + + v_offset += shapes[i].mesh.positions.size() / 3; + vn_offset += shapes[i].mesh.normals.size() / 3; + vt_offset += shapes[i].mesh.texcoords.size() / 2; + + } + + fclose(fp); + + // + // Write material file + // + bool ret = WriteMat(material_filename, shapes); + + return ret; +} + + diff --git a/examples/obj_sticher/obj_writer.h b/examples/obj_sticher/obj_writer.h new file mode 100644 index 0000000..e31e0e4 --- /dev/null +++ b/examples/obj_sticher/obj_writer.h @@ -0,0 +1,9 @@ +#ifndef __OBJ_WRITER_H__ +#define __OBJ_WRITER_H__ + +#include "../../tiny_obj_loader.h" + +extern bool WriteObj(const std::string& filename, std::vector shapes); + + +#endif // __OBJ_WRITER_H__ diff --git a/examples/obj_sticher/premake4.lua b/examples/obj_sticher/premake4.lua new file mode 100644 index 0000000..9c2deb6 --- /dev/null +++ b/examples/obj_sticher/premake4.lua @@ -0,0 +1,38 @@ +lib_sources = { + "../../tiny_obj_loader.cc" +} + +sources = { + "obj_sticher.cc", + "obj_writer.cc", + } + +-- premake4.lua +solution "ObjStickerSolution" + configurations { "Release", "Debug" } + + if (os.is("windows")) then + platforms { "x32", "x64" } + else + platforms { "native", "x32", "x64" } + end + + includedirs { + "../../" + } + + -- A project defines one build target + project "obj_sticher" + kind "ConsoleApp" + language "C++" + files { lib_sources, sources } + + configuration "Debug" + defines { "DEBUG" } -- -DDEBUG + flags { "Symbols" } + targetname "obj_sticher_debug" + + configuration "Release" + -- defines { "NDEBUG" } -- -NDEBUG + flags { "Symbols", "Optimize" } + targetname "obj_sticher" diff --git a/test.cc b/test.cc index 7148135..578e96f 100644 --- a/test.cc +++ b/test.cc @@ -46,6 +46,7 @@ TestLoadObj( printf(" material.Tr = (%f, %f ,%f)\n", shapes[i].material.transmittance[0], shapes[i].material.transmittance[1], shapes[i].material.transmittance[2]); printf(" material.Ke = (%f, %f ,%f)\n", shapes[i].material.emission[0], shapes[i].material.emission[1], shapes[i].material.emission[2]); printf(" material.Ns = %f\n", shapes[i].material.shininess); + printf(" material.Ni = %f\n", shapes[i].material.ior); printf(" material.map_Ka = %s\n", shapes[i].material.ambient_texname.c_str()); printf(" material.map_Kd = %s\n", shapes[i].material.diffuse_texname.c_str()); printf(" material.map_Ks = %s\n", shapes[i].material.specular_texname.c_str()); diff --git a/tiny_obj_loader.cc b/tiny_obj_loader.cc index 0cc9e29..7894e70 100644 --- a/tiny_obj_loader.cc +++ b/tiny_obj_loader.cc @@ -5,6 +5,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. // version 0.9.4: Initial suupport of group tag(g) @@ -372,14 +374,21 @@ std::string LoadMtl ( continue; } - // specular + // transmittance if (token[0] == 'K' && token[1] == 't' && 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; + 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; } diff --git a/tiny_obj_loader.h b/tiny_obj_loader.h index bc3ad2e..a5324eb 100644 --- a/tiny_obj_loader.h +++ b/tiny_obj_loader.h @@ -22,6 +22,7 @@ typedef struct float transmittance[3]; float emission[3]; float shininess; + float ior; // index of refraction std::string ambient_texname; std::string diffuse_texname; -- cgit v1.2.3 From b55eafe0ac042cd8613780c2f3c91d6d5a01d92f Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Thu, 12 Sep 2013 19:48:25 +0900 Subject: Update README. --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index b8df296..a3ce99e 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,10 @@ Tiny but poweful single file wavefront obj loader written in C++. No dependency Good for embedding .obj loader to your (global illumination) renderer ;-) +What's new +---------- + +* Sep 12, 2013 : Added multiple .obj sticher example. Example ------- -- cgit v1.2.3 From 8cc26a6abb03d39a87706b0ed4e4b485490c86ce Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Fri, 13 Sep 2013 00:52:33 +0900 Subject: Emit facevarying position/normal/uv. --- examples/obj_sticher/obj_writer.cc | 60 ++++++++++++++++++++++++-------------- 1 file changed, 38 insertions(+), 22 deletions(-) diff --git a/examples/obj_sticher/obj_writer.cc b/examples/obj_sticher/obj_writer.cc index 67af93e..7d0f1ba 100644 --- a/examples/obj_sticher/obj_writer.cc +++ b/examples/obj_sticher/obj_writer.cc @@ -74,28 +74,41 @@ bool WriteObj(const std::string& filename, std::vector shapes) fprintf(fp, "usemtl %s\n", shapes[i].material.name.c_str()); } - // vtx - for (size_t k = 0; k < shapes[i].mesh.positions.size() / 3; k++) { - fprintf(fp, "v %f %f %f\n", - shapes[i].mesh.positions[3*k+0], - shapes[i].mesh.positions[3*k+1], - shapes[i].mesh.positions[3*k+2]); + // facevarying vtx + for (size_t k = 0; k < shapes[i].mesh.indices.size() / 3; k++) { + for (int j = 0; j < 3; j++) { + int idx = shapes[i].mesh.indices[3*k+j]; + fprintf(fp, "v %f %f %f\n", + shapes[i].mesh.positions[3*idx+0], + shapes[i].mesh.positions[3*idx+1], + shapes[i].mesh.positions[3*idx+2]); + } } - // normal - for (size_t k = 0; k < shapes[i].mesh.normals.size() / 3; k++) { - fprintf(fp, "vn %f %f %f\n", - shapes[i].mesh.normals[3*k+0], - shapes[i].mesh.normals[3*k+1], - shapes[i].mesh.normals[3*k+2]); + // facevarying normal + if (shapes[i].mesh.normals.size() > 0) { + for (size_t k = 0; k < shapes[i].mesh.indices.size() / 3; k++) { + for (int j = 0; j < 3; j++) { + int idx = shapes[i].mesh.indices[3*k+j]; + fprintf(fp, "vn %f %f %f\n", + shapes[i].mesh.normals[3*idx+0], + shapes[i].mesh.normals[3*idx+1], + shapes[i].mesh.normals[3*idx+2]); + } + } } if (shapes[i].mesh.normals.size() > 0) has_vn = true; // texcoord - for (size_t k = 0; k < shapes[i].mesh.texcoords.size() / 2; k++) { - fprintf(fp, "vt %f %f\n", - shapes[i].mesh.texcoords[2*k+0], - shapes[i].mesh.texcoords[2*k+1]); + if (shapes[i].mesh.texcoords.size() > 0) { + for (size_t k = 0; k < shapes[i].mesh.indices.size() / 3; k++) { + for (int j = 0; j < 3; j++) { + int idx = shapes[i].mesh.indices[3*k+j]; + fprintf(fp, "vt %f %f %f\n", + shapes[i].mesh.texcoords[3*idx+0], + shapes[i].mesh.texcoords[3*idx+1]); + } + } } if (shapes[i].mesh.texcoords.size() > 0) has_vt = true; @@ -103,9 +116,12 @@ bool WriteObj(const std::string& filename, std::vector shapes) for (size_t k = 0; k < shapes[i].mesh.indices.size() / 3; k++) { // Face index is 1-base. - int v0 = shapes[i].mesh.indices[3*k+0] + 1 + v_offset; - int v1 = shapes[i].mesh.indices[3*k+1] + 1 + v_offset; - int v2 = shapes[i].mesh.indices[3*k+2] + 1 + v_offset; + //int v0 = shapes[i].mesh.indices[3*k+0] + 1 + v_offset; + //int v1 = shapes[i].mesh.indices[3*k+1] + 1 + v_offset; + //int v2 = shapes[i].mesh.indices[3*k+2] + 1 + v_offset; + int v0 = (3*k + 0) + 1 + v_offset; + int v1 = (3*k + 1) + 1 + v_offset; + int v2 = (3*k + 2) + 1 + v_offset; if (has_vn && has_vt) { fprintf(fp, "f %d/%d/%d %d/%d/%d %d/%d/%d\n", @@ -120,9 +136,9 @@ bool WriteObj(const std::string& filename, std::vector shapes) } - v_offset += shapes[i].mesh.positions.size() / 3; - vn_offset += shapes[i].mesh.normals.size() / 3; - vt_offset += shapes[i].mesh.texcoords.size() / 2; + v_offset += shapes[i].mesh.indices.size(); + //vn_offset += shapes[i].mesh.normals.size() / 3; + //vt_offset += shapes[i].mesh.texcoords.size() / 2; } -- cgit v1.2.3 From 0d943a8a9dbf41abc46d1282d006291e9f11a6df Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Fri, 13 Sep 2013 00:58:48 +0900 Subject: Fix texcoord export. --- examples/obj_sticher/obj_writer.cc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/obj_sticher/obj_writer.cc b/examples/obj_sticher/obj_writer.cc index 7d0f1ba..aa9b244 100644 --- a/examples/obj_sticher/obj_writer.cc +++ b/examples/obj_sticher/obj_writer.cc @@ -99,14 +99,14 @@ bool WriteObj(const std::string& filename, std::vector shapes) } if (shapes[i].mesh.normals.size() > 0) has_vn = true; - // texcoord + // facevarying texcoord if (shapes[i].mesh.texcoords.size() > 0) { for (size_t k = 0; k < shapes[i].mesh.indices.size() / 3; k++) { for (int j = 0; j < 3; j++) { int idx = shapes[i].mesh.indices[3*k+j]; fprintf(fp, "vt %f %f %f\n", - shapes[i].mesh.texcoords[3*idx+0], - shapes[i].mesh.texcoords[3*idx+1]); + shapes[i].mesh.texcoords[2*idx+0], + shapes[i].mesh.texcoords[2*idx+1]); } } } -- cgit v1.2.3 From b17d15f5a053e699325692d43ed70eaec445c835 Mon Sep 17 00:00:00 2001 From: caryanne Date: Thu, 14 Nov 2013 16:51:06 -0500 Subject: added clearing of unknown parameters map not doing so caused undefined behavior in objects with multiple materials using custom parameters. --- tiny_obj_loader.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/tiny_obj_loader.cc b/tiny_obj_loader.cc index 7894e70..8b05782 100644 --- a/tiny_obj_loader.cc +++ b/tiny_obj_loader.cc @@ -270,6 +270,7 @@ void InitMaterial(material_t& material) { material.emission[i] = 0.f; } material.shininess = 1.f; + material.unknown_parameter.clear(); } std::string LoadMtl ( -- cgit v1.2.3 From 7afab4ad5662b394bedd2bd5be2f7a4dba3bbb5e Mon Sep 17 00:00:00 2001 From: Sean Jones Date: Mon, 25 Nov 2013 20:54:46 +0000 Subject: Cppcheck suggested performance improvement variables should be passed by reference. They are passed by value. It could be passed as a (const) reference which is usually faster and recommended in C++. This gives a 9% performance boost in my project. 26.28s to load instead of 28.94s --- tiny_obj_loader.cc | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tiny_obj_loader.cc b/tiny_obj_loader.cc index 8b05782..bd3b3f2 100644 --- a/tiny_obj_loader.cc +++ b/tiny_obj_loader.cc @@ -196,12 +196,12 @@ updateVertex( static bool exportFaceGroupToShape( shape_t& shape, - const std::vector in_positions, - const std::vector in_normals, - const std::vector in_texcoords, + const std::vector &in_positions, + const std::vector &in_normals, + const std::vector &in_texcoords, const std::vector >& faceGroup, - const material_t material, - const std::string name) + const material_t &material, + const std::string &name) { if (faceGroup.empty()) { return false; -- cgit v1.2.3 From 89a5192702673133c5c740d9cec47da107578500 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Tue, 26 Nov 2013 16:53:12 +0900 Subject: Update readme. --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index a3ce99e..3003166 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,7 @@ Good for embedding .obj loader to your (global illumination) renderer ;-) What's new ---------- +* Nov 26, 2013 : Performance optimization by NeuralSandwich. 9% improvement in his project, thanks! * Sep 12, 2013 : Added multiple .obj sticher example. Example -- cgit v1.2.3 From 89d5b69beebd59c97db54aa1ea19019a8d7d1a05 Mon Sep 17 00:00:00 2001 From: Bradley Clemetson Date: Sat, 25 Jan 2014 18:25:22 -0800 Subject: Added a Cmake build system configuration --- .gitignore | 2 ++ CMakeLists.txt | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+) create mode 100644 .gitignore create mode 100644 CMakeLists.txt diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..493e888 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +#Common folder for building objects +build/ diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..72bb155 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,33 @@ +#Tiny Object Loader Cmake configuration file. +#This configures the Cmake system with multiple properties, depending +#on the platform and configuration it is set to build in. +project(tinyobjloader) +cmake_minimum_required(VERSION 2.8.6) + +#Folder Shortcuts +set(TINYOBJLOADEREXAMPLES_DIR ${CMAKE_CURRENT_SOURCE_DIR}/examples) + +set(tinyobjloader-Source + ${CMAKE_CURRENT_SOURCE_DIR}/tiny_obj_loader.h + ${CMAKE_CURRENT_SOURCE_DIR}/tiny_obj_loader.cc + ) + +set(tinyobjloader-Test-Source + ${CMAKE_CURRENT_SOURCE_DIR}/test.cc + ) + +set(tinyobjloader-examples-objsticher + ${TINYOBJLOADEREXAMPLES_DIR}/obj_sticher/obj_writer.h + ${TINYOBJLOADEREXAMPLES_DIR}/obj_sticher/obj_writer.cc + ${TINYOBJLOADEREXAMPLES_DIR}/obj_sticher/obj_sticher.cc + ) + +add_library(tinyobjloader + ${tinyobjloader-Source} + ) + +add_executable(test ${tinyobjloader-Test-Source}) +target_link_libraries(test tinyobjloader) + +add_executable(obj_sticher ${tinyobjloader-examples-objsticher}) +target_link_libraries(obj_sticher tinyobjloader) \ No newline at end of file -- cgit v1.2.3 From 7cd0643181718e2adc1b173e6297307a9cd22d6c Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Mon, 27 Jan 2014 19:49:02 +0900 Subject: Update README. --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 3003166..5308cb8 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,7 @@ Good for embedding .obj loader to your (global illumination) renderer ;-) What's new ---------- +* Jan 27, 2014 : Added CMake project. Thanks bradc6! * Nov 26, 2013 : Performance optimization by NeuralSandwich. 9% improvement in his project, thanks! * Sep 12, 2013 : Added multiple .obj sticher example. -- cgit v1.2.3 From 665c129488a7508673757547e2de32301758c1ca Mon Sep 17 00:00:00 2001 From: Matt Pharr Date: Wed, 9 Apr 2014 09:45:38 -0700 Subject: Fix small bug in obj_writer.cc (one too many "%f"s in format string). --- examples/obj_sticher/obj_writer.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/obj_sticher/obj_writer.cc b/examples/obj_sticher/obj_writer.cc index aa9b244..1cad9de 100644 --- a/examples/obj_sticher/obj_writer.cc +++ b/examples/obj_sticher/obj_writer.cc @@ -104,7 +104,7 @@ bool WriteObj(const std::string& filename, std::vector shapes) for (size_t k = 0; k < shapes[i].mesh.indices.size() / 3; k++) { for (int j = 0; j < 3; j++) { int idx = shapes[i].mesh.indices[3*k+j]; - fprintf(fp, "vt %f %f %f\n", + fprintf(fp, "vt %f %f\n", shapes[i].mesh.texcoords[2*idx+0], shapes[i].mesh.texcoords[2*idx+1]); } -- cgit v1.2.3 From 6372ccbef2698a8bebdf8834e0a16330cbedebec Mon Sep 17 00:00:00 2001 From: Matt Pharr Date: Wed, 9 Apr 2014 09:46:03 -0700 Subject: Add support for parsing 'illum' and 'd'/'Tr' statements. --- test.cc | 2 ++ tiny_obj_loader.cc | 29 +++++++++++++++++++++++++++++ tiny_obj_loader.h | 3 +++ 3 files changed, 34 insertions(+) diff --git a/test.cc b/test.cc index 578e96f..4ee4adc 100644 --- a/test.cc +++ b/test.cc @@ -47,6 +47,8 @@ TestLoadObj( printf(" material.Ke = (%f, %f ,%f)\n", shapes[i].material.emission[0], shapes[i].material.emission[1], shapes[i].material.emission[2]); printf(" material.Ns = %f\n", shapes[i].material.shininess); printf(" material.Ni = %f\n", shapes[i].material.ior); + printf(" material.dissolve = %f\n", shapes[i].material.dissolve); + printf(" material.illum = %d\n", shapes[i].material.illum); printf(" material.map_Ka = %s\n", shapes[i].material.ambient_texname.c_str()); printf(" material.map_Kd = %s\n", shapes[i].material.diffuse_texname.c_str()); printf(" material.map_Ks = %s\n", shapes[i].material.specular_texname.c_str()); diff --git a/tiny_obj_loader.cc b/tiny_obj_loader.cc index bd3b3f2..c55d2ca 100644 --- a/tiny_obj_loader.cc +++ b/tiny_obj_loader.cc @@ -88,6 +88,14 @@ static inline std::string parseString(const char*& token) return s; } +static inline int parseInt(const char*& token) +{ + token += strspn(token, " \t"); + int i = atoi(token); + token += strcspn(token, " \t\r"); + return i; +} + static inline float parseFloat(const char*& token) { token += strspn(token, " \t"); @@ -269,6 +277,8 @@ void InitMaterial(material_t& material) { material.transmittance[i] = 0.f; material.emission[i] = 0.f; } + material.illum = 0; + material.dissolve = 1.f; material.shininess = 1.f; material.unknown_parameter.clear(); } @@ -411,6 +421,25 @@ std::string LoadMtl ( 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; + material.dissolve = parseFloat(token); + continue; + } + // ambient texture if ((0 == strncmp(token, "map_Ka", 6)) && isSpace(token[6])) { token += 7; diff --git a/tiny_obj_loader.h b/tiny_obj_loader.h index a5324eb..7bc68cf 100644 --- a/tiny_obj_loader.h +++ b/tiny_obj_loader.h @@ -23,6 +23,9 @@ typedef struct 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; -- cgit v1.2.3 From 6b8e3900c62fdc92c6f7ed4583a2f33567b9beff Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Thu, 10 Apr 2014 16:51:35 +0900 Subject: Update README. --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 5308cb8..4fe25a3 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,7 @@ Good for embedding .obj loader to your (global illumination) renderer ;-) What's new ---------- +* Apr 10, 2014 : Add support for parsing 'illum' and 'd'/'Tr' statements. Thanks mmp! * Jan 27, 2014 : Added CMake project. Thanks bradc6! * Nov 26, 2013 : Performance optimization by NeuralSandwich. 9% improvement in his project, thanks! * Sep 12, 2013 : Added multiple .obj sticher example. -- cgit v1.2.3 From 2ce2af9d023e2dbe16e9f583d3a7ebd87d65eb6f Mon Sep 17 00:00:00 2001 From: anton Date: Fri, 18 Apr 2014 17:52:06 +0200 Subject: add missing includes Signed-off-by: anton --- examples/obj_sticher/obj_sticher.cc | 2 ++ examples/obj_sticher/obj_writer.cc | 1 + 2 files changed, 3 insertions(+) diff --git a/examples/obj_sticher/obj_sticher.cc b/examples/obj_sticher/obj_sticher.cc index 296d384..1d230b8 100644 --- a/examples/obj_sticher/obj_sticher.cc +++ b/examples/obj_sticher/obj_sticher.cc @@ -6,6 +6,8 @@ #include #include +#include +#include typedef std::vector Shape; diff --git a/examples/obj_sticher/obj_writer.cc b/examples/obj_sticher/obj_writer.cc index 1cad9de..f44d3f3 100644 --- a/examples/obj_sticher/obj_writer.cc +++ b/examples/obj_sticher/obj_writer.cc @@ -2,6 +2,7 @@ // Simple wavefront .obj writer // #include "obj_writer.h" +#include static std::string GetFileBasename(const std::string& FileName) { -- cgit v1.2.3 From 86b9c625db8b3c63d504c171b370440207c5d749 Mon Sep 17 00:00:00 2001 From: YarmUI Date: Mon, 21 Apr 2014 19:14:06 +0900 Subject: set default material --- tiny_obj_loader.cc | 61 +++++++++++++++++++++++++++++++++--------------------- 1 file changed, 37 insertions(+), 24 deletions(-) diff --git a/tiny_obj_loader.cc b/tiny_obj_loader.cc index c55d2ca..e0fb3db 100644 --- a/tiny_obj_loader.cc +++ b/tiny_obj_loader.cc @@ -201,6 +201,25 @@ updateVertex( 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.unknown_parameter.clear(); +} + static bool exportFaceGroupToShape( shape_t& shape, @@ -209,7 +228,8 @@ exportFaceGroupToShape( const std::vector &in_texcoords, const std::vector >& faceGroup, const material_t &material, - const std::string &name) + const std::string &name, + const bool is_material_seted) { if (faceGroup.empty()) { return false; @@ -257,31 +277,19 @@ exportFaceGroupToShape( shape.mesh.texcoords.swap(texcoords); shape.mesh.indices.swap(indices); - shape.material = material; + if(is_material_seted) { + shape.material = material; + } else { + InitMaterial(shape.material); + shape.material.diffuse[0] = 1.f; + shape.material.diffuse[1] = 1.f; + shape.material.diffuse[2] = 1.f; + } return true; } - -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.unknown_parameter.clear(); -} std::string LoadMtl ( std::map& material_map, @@ -512,6 +520,7 @@ LoadObj( // material std::map material_map; material_t material; + bool is_material_seted = false; int maxchars = 8192; // Alloc enough size. std::vector buf(maxchars); // Alloc enough size. @@ -601,6 +610,7 @@ LoadObj( if (material_map.find(namebuf) != material_map.end()) { material = material_map[namebuf]; + is_material_seted = true; } else { // { error!! material not found } InitMaterial(material); @@ -628,11 +638,12 @@ LoadObj( // flush previous face group. shape_t shape; - bool ret = exportFaceGroupToShape(shape, v, vn, vt, faceGroup, material, name); + bool ret = exportFaceGroupToShape(shape, v, vn, vt, faceGroup, material, name, is_material_seted); if (ret) { shapes.push_back(shape); } + is_material_seted = false; faceGroup.clear(); std::vector names; @@ -659,11 +670,12 @@ LoadObj( // flush previous face group. shape_t shape; - bool ret = exportFaceGroupToShape(shape, v, vn, vt, faceGroup, material, name); + bool ret = exportFaceGroupToShape(shape, v, vn, vt, faceGroup, material, name, is_material_seted); if (ret) { shapes.push_back(shape); } + is_material_seted = false; faceGroup.clear(); // @todo { multiple object name? } @@ -680,10 +692,11 @@ LoadObj( } shape_t shape; - bool ret = exportFaceGroupToShape(shape, v, vn, vt, faceGroup, material, name); + bool ret = exportFaceGroupToShape(shape, v, vn, vt, faceGroup, material, name, is_material_seted); if (ret) { shapes.push_back(shape); } + is_material_seted = false; // for safety faceGroup.clear(); // for safety return err.str(); -- cgit v1.2.3 From c33b0cf6cc3b56dd0118f54c8b3209df72a274f7 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Mon, 21 Apr 2014 19:24:22 +0900 Subject: Update README. --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 4fe25a3..885f8ed 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,7 @@ Good for embedding .obj loader to your (global illumination) renderer ;-) What's new ---------- +* Apr 21, 2014 : Define default material if no material definition exists in .obj. Thanks YarmUI! * Apr 10, 2014 : Add support for parsing 'illum' and 'd'/'Tr' statements. Thanks mmp! * Jan 27, 2014 : Added CMake project. Thanks bradc6! * Nov 26, 2013 : Performance optimization by NeuralSandwich. 9% improvement in his project, thanks! -- cgit v1.2.3 From 08ff49d2d032dd591aa38669f8cddcf881cc13fd Mon Sep 17 00:00:00 2001 From: E J Burns Date: Mon, 28 Apr 2014 14:37:32 +0100 Subject: Added a LoadObj function that reads from a std::istream. + Added a LoadObj function that reads from a std::istream. This should allow more generic usage and possibly make testing a little easier. o This LoadObj accepts a function that returns a std::unique_ptr for a material. + Modified LoadMtl to read from a std::istream to allow more generic usage. + Modified test.cc to check that the changes work as expected and nothing was broken. Tests: + Compiled test.cc, checked diff of output against pre change output. Same output where expected. --- test.cc | 137 ++++++++++++++++++++++++++++++++++++++++++++++------- tiny_obj_loader.cc | 68 ++++++++++++++++---------- tiny_obj_loader.h | 16 +++++++ 3 files changed, 179 insertions(+), 42 deletions(-) diff --git a/test.cc b/test.cc index 4ee4adc..b5e7d0c 100644 --- a/test.cc +++ b/test.cc @@ -3,23 +3,15 @@ #include #include #include +#include #include +#include +#include +#include +#include -static bool -TestLoadObj( - const char* filename, - const char* basepath = NULL) +static void PrintInfo(const std::vector& shapes) { - std::cout << "Loading " << filename << std::endl; - - std::vector shapes; - std::string err = tinyobj::LoadObj(shapes, filename, basepath); - - if (!err.empty()) { - std::cerr << err << std::endl; - return false; - } - std::cout << "# of shapes : " << shapes.size() << std::endl; for (size_t i = 0; i < shapes.size(); i++) { @@ -53,14 +45,124 @@ TestLoadObj( printf(" material.map_Kd = %s\n", shapes[i].material.diffuse_texname.c_str()); printf(" material.map_Ks = %s\n", shapes[i].material.specular_texname.c_str()); printf(" material.map_Ns = %s\n", shapes[i].material.normal_texname.c_str()); - std::map::iterator it(shapes[i].material.unknown_parameter.begin()); - std::map::iterator itEnd(shapes[i].material.unknown_parameter.end()); + auto it = shapes[i].material.unknown_parameter.begin(); + auto itEnd = shapes[i].material.unknown_parameter.end(); for (; it != itEnd; it++) { printf(" material.%s = %s\n", it->first.c_str(), it->second.c_str()); } printf("\n"); } +} + +static bool +TestLoadObj( + const char* filename, + const char* basepath = NULL) +{ + std::cout << "Loading " << filename << std::endl; + + std::vector shapes; + std::string err = tinyobj::LoadObj(shapes, filename, basepath); + if (!err.empty()) { + std::cerr << err << std::endl; + return false; + } + + PrintInfo(shapes); + + return true; +} + + +static bool +TestStreamLoadObj() +{ + std::cout << "Stream Loading " << std::endl; + + std::stringstream objStream; + objStream + << "mtllib cube.mtl\n" + "\n" + "v 0.000000 2.000000 2.000000\n" + "v 0.000000 0.000000 2.000000\n" + "v 2.000000 0.000000 2.000000\n" + "v 2.000000 2.000000 2.000000\n" + "v 0.000000 2.000000 0.000000\n" + "v 0.000000 0.000000 0.000000\n" + "v 2.000000 0.000000 0.000000\n" + "v 2.000000 2.000000 0.000000\n" + "# 8 vertices\n" + "\n" + "g front cube\n" + "usemtl white\n" + "f 1 2 3 4\n" + "g back cube\n" + "# expects white material\n" + "f 8 7 6 5\n" + "g right cube\n" + "usemtl red\n" + "f 4 3 7 8\n" + "g top cube\n" + "usemtl white\n" + "f 5 1 4 8\n" + "g left cube\n" + "usemtl green\n" + "f 5 6 2 1\n" + "g bottom cube\n" + "usemtl white\n" + "f 2 6 7 3\n" + "# 6 elements"; + + auto getMatFileIStreamFunc = + [](const std::string& matId) + { + if (matId == "cube.mtl") { + + std::unique_ptr matStream( + new std::stringstream( + "newmtl white\n" + "Ka 0 0 0\n" + "Kd 1 1 1\n" + "Ks 0 0 0\n" + "\n" + "newmtl red\n" + "Ka 0 0 0\n" + "Kd 1 0 0\n" + "Ks 0 0 0\n" + "\n" + "newmtl green\n" + "Ka 0 0 0\n" + "Kd 0 1 0\n" + "Ks 0 0 0\n" + "\n" + "newmtl blue\n" + "Ka 0 0 0\n" + "Kd 0 0 1\n" + "Ks 0 0 0\n" + "\n" + "newmtl light\n" + "Ka 20 20 20\n" + "Kd 1 1 1\n" + "Ks 0 0 0")); + + return matStream; + } + + std::unique_ptr emptyUP( nullptr ); + return emptyUP; + }; + + std::vector shapes; + std::string err = tinyobj::LoadObj(shapes, objStream, getMatFileIStreamFunc); + + if (!err.empty()) { + std::cerr << err << std::endl; + return false; + } + + PrintInfo(shapes); + return true; } @@ -79,7 +181,8 @@ main( } else { assert(true == TestLoadObj("cornell_box.obj")); assert(true == TestLoadObj("cube.obj")); + assert(true == TestStreamLoadObj()); } - + return 0; } diff --git a/tiny_obj_loader.cc b/tiny_obj_loader.cc index e0fb3db..e7c2346 100644 --- a/tiny_obj_loader.cc +++ b/tiny_obj_loader.cc @@ -290,35 +290,19 @@ exportFaceGroupToShape( } - std::string LoadMtl ( std::map& material_map, - const char* filename, - const char* mtl_basepath) + std::istream& inStream) { material_map.clear(); std::stringstream err; - std::string filepath; - - if (mtl_basepath) { - filepath = std::string(mtl_basepath) + std::string(filename); - } else { - filepath = std::string(filename); - } - - std::ifstream ifs(filepath.c_str()); - if (!ifs) { - err << "Cannot open file [" << filepath << "]" << std::endl; - return err.str(); - } - material_t material; int maxchars = 8192; // Alloc enough size. std::vector buf(maxchars); // Alloc enough size. - while (ifs.peek() != -1) { - ifs.getline(&buf[0], maxchars); + while (inStream.peek() != -1) { + inStream.getline(&buf[0], maxchars); std::string linebuf(&buf[0]); @@ -511,6 +495,31 @@ LoadObj( return err.str(); } + auto getMatFileIStreamFunc = + [&](const std::string& matId) + { + std::string filepath; + + if (mtl_basepath) { + filepath = std::string(mtl_basepath) + matId; + } else { + filepath = matId; + } + + std::unique_ptr ifs( new std::ifstream(filepath.c_str()) ); + return ifs; + }; + + return LoadObj(shapes, ifs, getMatFileIStreamFunc); +} + +std::string LoadObj( + std::vector& shapes, + std::istream& inStream, + GetMtlIStreamFn getMatFn) +{ + std::stringstream err; + std::vector v; std::vector vn; std::vector vt; @@ -524,8 +533,8 @@ LoadObj( int maxchars = 8192; // Alloc enough size. std::vector buf(maxchars); // Alloc enough size. - while (ifs.peek() != -1) { - ifs.getline(&buf[0], maxchars); + while (inStream.peek() != -1) { + inStream.getline(&buf[0], maxchars); std::string linebuf(&buf[0]); @@ -625,11 +634,20 @@ LoadObj( token += 7; sscanf(token, "%s", namebuf); - std::string err_mtl = LoadMtl(material_map, namebuf, mtl_basepath); - if (!err_mtl.empty()) { - faceGroup.clear(); // for safety - return err_mtl; + if (!getMatFn) { + err << "Could not read material, no callable function target."; + return err.str(); + } + + std::unique_ptr matIStream = getMatFn(namebuf); + if (matIStream) { + std::string err_mtl = LoadMtl(material_map, *matIStream); + if (!err_mtl.empty()) { + faceGroup.clear(); // for safety + return err_mtl; + } } + continue; } diff --git a/tiny_obj_loader.h b/tiny_obj_loader.h index 7bc68cf..c4a0a30 100644 --- a/tiny_obj_loader.h +++ b/tiny_obj_loader.h @@ -9,6 +9,8 @@ #include #include #include +#include +#include namespace tinyobj { @@ -49,6 +51,13 @@ typedef struct mesh_t mesh; } shape_t; +/// typedef for a function that returns a pointer to an istream for +/// the material identified by the const std::string& +typedef std::function< + std::unique_ptr( + const std::string&) + > GetMtlIStreamFn; + /// Loads .obj from a file. /// 'shapes' will be filled with parsed shape data /// The function returns error string. @@ -59,6 +68,13 @@ std::string LoadObj( 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::istream& inStream, + GetMtlIStreamFn getMatFn); }; #endif // _TINY_OBJ_LOADER_H -- cgit v1.2.3 From b89a46c4176c64c596de3de2f54c39ad63b8d98c Mon Sep 17 00:00:00 2001 From: E J Burns Date: Tue, 29 Apr 2014 13:15:22 +0100 Subject: Code Modified to remove use of C++11 features. Tested compilation with VS2012, clang++ 3.4 and g++. --- test.cc | 95 ++++++++++++++++++++++++++++-------------------------- tiny_obj_loader.cc | 55 ++++++++++++++++--------------- tiny_obj_loader.h | 40 +++++++++++++++++------ 3 files changed, 107 insertions(+), 83 deletions(-) diff --git a/test.cc b/test.cc index b5e7d0c..82b6178 100644 --- a/test.cc +++ b/test.cc @@ -3,12 +3,9 @@ #include #include #include -#include #include #include #include -#include -#include static void PrintInfo(const std::vector& shapes) { @@ -45,8 +42,8 @@ static void PrintInfo(const std::vector& shapes) printf(" material.map_Kd = %s\n", shapes[i].material.diffuse_texname.c_str()); printf(" material.map_Ks = %s\n", shapes[i].material.specular_texname.c_str()); printf(" material.map_Ns = %s\n", shapes[i].material.normal_texname.c_str()); - auto it = shapes[i].material.unknown_parameter.begin(); - auto itEnd = shapes[i].material.unknown_parameter.end(); + std::map::const_iterator it(shapes[i].material.unknown_parameter.begin()); + std::map::const_iterator itEnd(shapes[i].material.unknown_parameter.end()); for (; it != itEnd; it++) { printf(" material.%s = %s\n", it->first.c_str(), it->second.c_str()); } @@ -113,48 +110,54 @@ TestStreamLoadObj() "usemtl white\n" "f 2 6 7 3\n" "# 6 elements"; - - auto getMatFileIStreamFunc = - [](const std::string& matId) - { - if (matId == "cube.mtl") { - - std::unique_ptr matStream( - new std::stringstream( - "newmtl white\n" - "Ka 0 0 0\n" - "Kd 1 1 1\n" - "Ks 0 0 0\n" - "\n" - "newmtl red\n" - "Ka 0 0 0\n" - "Kd 1 0 0\n" - "Ks 0 0 0\n" - "\n" - "newmtl green\n" - "Ka 0 0 0\n" - "Kd 0 1 0\n" - "Ks 0 0 0\n" - "\n" - "newmtl blue\n" - "Ka 0 0 0\n" - "Kd 0 0 1\n" - "Ks 0 0 0\n" - "\n" - "newmtl light\n" - "Ka 20 20 20\n" - "Kd 1 1 1\n" - "Ks 0 0 0")); - - return matStream; - } - - std::unique_ptr emptyUP( nullptr ); - return emptyUP; - }; - + +std::string matStream( + "newmtl white\n" + "Ka 0 0 0\n" + "Kd 1 1 1\n" + "Ks 0 0 0\n" + "\n" + "newmtl red\n" + "Ka 0 0 0\n" + "Kd 1 0 0\n" + "Ks 0 0 0\n" + "\n" + "newmtl green\n" + "Ka 0 0 0\n" + "Kd 0 1 0\n" + "Ks 0 0 0\n" + "\n" + "newmtl blue\n" + "Ka 0 0 0\n" + "Kd 0 0 1\n" + "Ks 0 0 0\n" + "\n" + "newmtl light\n" + "Ka 20 20 20\n" + "Kd 1 1 1\n" + "Ks 0 0 0"); + + using namespace tinyobj; + class MaterialStringStreamReader: + public MaterialReader + { + public: + MaterialStringStreamReader(const std::string& matSStream): m_matSStream(matSStream) {} + virtual ~MaterialStringStreamReader() {} + virtual std::string operator() ( + const std::string& matId, + std::map& matMap) + { + return LoadMtl(matMap, m_matSStream); + } + + private: + std::stringstream m_matSStream; + }; + + MaterialStringStreamReader matSSReader(matStream); std::vector shapes; - std::string err = tinyobj::LoadObj(shapes, objStream, getMatFileIStreamFunc); + std::string err = tinyobj::LoadObj(shapes, objStream, matSSReader); if (!err.empty()) { std::cerr << err << std::endl; diff --git a/tiny_obj_loader.cc b/tiny_obj_loader.cc index e7c2346..51de34b 100644 --- a/tiny_obj_loader.cc +++ b/tiny_obj_loader.cc @@ -478,6 +478,22 @@ std::string LoadMtl ( return err.str(); } +std::string MaterialFileReader::operator() ( + const std::string& matId, + std::map& matMap) +{ + std::string filepath; + + if (!m_mtlBasePath.empty()) { + filepath = std::string(m_mtlBasePath) + matId; + } else { + filepath = matId; + } + + std::ifstream matIStream(filepath.c_str()); + return LoadMtl(matMap, matIStream); +} + std::string LoadObj( std::vector& shapes, @@ -495,28 +511,19 @@ LoadObj( return err.str(); } - auto getMatFileIStreamFunc = - [&](const std::string& matId) - { - std::string filepath; - - if (mtl_basepath) { - filepath = std::string(mtl_basepath) + matId; - } else { - filepath = matId; - } - - std::unique_ptr ifs( new std::ifstream(filepath.c_str()) ); - return ifs; - }; + std::string basePath; + if (mtl_basepath) { + basePath = mtl_basepath; + } + MaterialFileReader matFileReader( basePath ); - return LoadObj(shapes, ifs, getMatFileIStreamFunc); + return LoadObj(shapes, ifs, matFileReader); } std::string LoadObj( std::vector& shapes, std::istream& inStream, - GetMtlIStreamFn getMatFn) + MaterialReader& readMatFn) { std::stringstream err; @@ -633,19 +640,11 @@ std::string LoadObj( char namebuf[4096]; token += 7; sscanf(token, "%s", namebuf); - - if (!getMatFn) { - err << "Could not read material, no callable function target."; - return err.str(); - } - std::unique_ptr matIStream = getMatFn(namebuf); - if (matIStream) { - std::string err_mtl = LoadMtl(material_map, *matIStream); - if (!err_mtl.empty()) { - faceGroup.clear(); // for safety - return err_mtl; - } + std::string err_mtl = readMatFn(namebuf, material_map); + if (!err_mtl.empty()) { + faceGroup.clear(); // for safety + return err_mtl; } continue; diff --git a/tiny_obj_loader.h b/tiny_obj_loader.h index c4a0a30..c275a88 100644 --- a/tiny_obj_loader.h +++ b/tiny_obj_loader.h @@ -9,8 +9,6 @@ #include #include #include -#include -#include namespace tinyobj { @@ -51,12 +49,30 @@ typedef struct mesh_t mesh; } shape_t; -/// typedef for a function that returns a pointer to an istream for -/// the material identified by the const std::string& -typedef std::function< - std::unique_ptr( - const std::string&) - > GetMtlIStreamFn; +class MaterialReader +{ +public: + MaterialReader(){} + virtual ~MaterialReader(){} + + virtual std::string operator() ( + const std::string& matId, + 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::map& matMap); + + private: + std::string m_mtlBasePath; +}; /// Loads .obj from a file. /// 'shapes' will be filled with parsed shape data @@ -74,7 +90,13 @@ std::string LoadObj( std::string LoadObj( std::vector& shapes, // [output] std::istream& inStream, - GetMtlIStreamFn getMatFn); + MaterialReader& readMatFn); + +/// Loads materials into std::map +/// Returns an empty string if successful +std::string LoadMtl ( + std::map& material_map, + std::istream& inStream); }; #endif // _TINY_OBJ_LOADER_H -- cgit v1.2.3 From 69960487a336bb6036eabe5d3fc498112e8f906e Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Tue, 29 Apr 2014 22:51:11 +0900 Subject: Update README. --- README.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 885f8ed..18df566 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,7 @@ Good for embedding .obj loader to your (global illumination) renderer ;-) What's new ---------- +* Apr 29, 2014 : Add API to read .obj from std::istream. Good for reading compressed .obj or connecting to procedural primitive generator. Thanks burnse! * Apr 21, 2014 : Define default material if no material definition exists in .obj. Thanks YarmUI! * Apr 10, 2014 : Add support for parsing 'illum' and 'd'/'Tr' statements. Thanks mmp! * Jan 27, 2014 : Added CMake project. Thanks bradc6! @@ -24,12 +25,14 @@ Example tinyobjloader can successfully load 6M triangles Rungholt scene. http://graphics.cs.williams.edu/data/meshes.xml -Use ---- +Use case +-------- -TinyObjLoader is used in ... +TinyObjLoader is successfully used in ... * bullet3 https://github.com/erwincoumans/bullet3 +* pbrt-v2 https://https://github.com/mmp/pbrt-v2 +* OpenGL game engine development http://swarminglogic.com/jotting/2013_10_gamedev01 Features -------- -- cgit v1.2.3 From 9b4a9343a15f0dcbe3d4f73c5bf65a7d40f24617 Mon Sep 17 00:00:00 2001 From: E J Burns Date: Tue, 29 Apr 2014 14:00:13 +0100 Subject: Fixed ior init. --- tiny_obj_loader.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/tiny_obj_loader.cc b/tiny_obj_loader.cc index 51de34b..999a9a0 100644 --- a/tiny_obj_loader.cc +++ b/tiny_obj_loader.cc @@ -217,6 +217,7 @@ void InitMaterial(material_t& material) { material.illum = 0; material.dissolve = 1.f; material.shininess = 1.f; + material.ior = 1.f; material.unknown_parameter.clear(); } -- cgit v1.2.3 From b45fbe63458ceaacf895b223a0dcf2961b176a70 Mon Sep 17 00:00:00 2001 From: Narendra Umate Date: Fri, 16 May 2014 13:54:05 -0400 Subject: Fixed trim newline bugs. --- tiny_obj_loader.cc | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) mode change 100644 => 100755 tiny_obj_loader.cc diff --git a/tiny_obj_loader.cc b/tiny_obj_loader.cc old mode 100644 new mode 100755 index 999a9a0..50fa69b --- a/tiny_obj_loader.cc +++ b/tiny_obj_loader.cc @@ -307,12 +307,12 @@ std::string LoadMtl ( std::string linebuf(&buf[0]); - // Trim newline '\r\n' or '\r' + // 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] == '\n') linebuf.erase(linebuf.size()-1); + if (linebuf[linebuf.size()-1] == '\r') linebuf.erase(linebuf.size()-1); } // Skip if empty line. @@ -546,12 +546,12 @@ std::string LoadObj( std::string linebuf(&buf[0]); - // Trim newline '\r\n' or '\r' + // 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] == '\n') linebuf.erase(linebuf.size()-1); + if (linebuf[linebuf.size()-1] == '\r') linebuf.erase(linebuf.size()-1); } // Skip if empty line. -- cgit v1.2.3 From b96ee06bb61cc781a1ac313bfa04669adfc1dd10 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Sun, 18 May 2014 00:50:59 +0900 Subject: Update README. --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 18df566..75039ae 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,7 @@ Good for embedding .obj loader to your (global illumination) renderer ;-) What's new ---------- +* Mar 17, 2014 : Fixed trim newline bugs. Thanks ardneran! * Apr 29, 2014 : Add API to read .obj from std::istream. Good for reading compressed .obj or connecting to procedural primitive generator. Thanks burnse! * Apr 21, 2014 : Define default material if no material definition exists in .obj. Thanks YarmUI! * Apr 10, 2014 : Add support for parsing 'illum' and 'd'/'Tr' statements. Thanks mmp! -- cgit v1.2.3 From f72fb80ac96b91322015aebf8e4cd7c7f6ffb866 Mon Sep 17 00:00:00 2001 From: Mattias Harrysson Date: Mon, 2 Jun 2014 20:52:44 +0200 Subject: fix compiler warnings --- tiny_obj_loader.cc | 4 ++-- tiny_obj_loader.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tiny_obj_loader.cc b/tiny_obj_loader.cc index 50fa69b..5417487 100755 --- a/tiny_obj_loader.cc +++ b/tiny_obj_loader.cc @@ -178,7 +178,7 @@ updateVertex( return it->second; } - assert(in_positions.size() > (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]); @@ -721,4 +721,4 @@ std::string LoadObj( } -}; +} diff --git a/tiny_obj_loader.h b/tiny_obj_loader.h index c275a88..b4c9337 100644 --- a/tiny_obj_loader.h +++ b/tiny_obj_loader.h @@ -97,6 +97,6 @@ std::string LoadObj( std::string LoadMtl ( std::map& material_map, std::istream& inStream); -}; +} #endif // _TINY_OBJ_LOADER_H -- cgit v1.2.3 From 89b3ec95103e7400a4c652dfddae051224baae07 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Fri, 11 Jul 2014 18:34:24 +0900 Subject: Add wercker file. --- wercker.yml | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 wercker.yml diff --git a/wercker.yml b/wercker.yml new file mode 100644 index 0000000..4dbe175 --- /dev/null +++ b/wercker.yml @@ -0,0 +1,11 @@ +services: + - rioki/gcc-cpp@0.0.1 +build: + steps: + - compile + - script: + name: build + code: | + premake4 gmake + make + -- cgit v1.2.3 From e88dd3d3a0c12e9136a45a992344f8404ae15647 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Fri, 11 Jul 2014 18:41:01 +0900 Subject: Validate wercker.yml --- wercker.yml | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/wercker.yml b/wercker.yml index 4dbe175..d9cd8bd 100644 --- a/wercker.yml +++ b/wercker.yml @@ -1,11 +1,9 @@ -services: - - rioki/gcc-cpp@0.0.1 +box: rioki/gcc-cpp@0.0.1 build: - steps: - - compile - - script: - name: build - code: | - premake4 gmake - make - + steps: + # Execute a custom script step. + - script: + name: build + code: | + premake4 gmake + make -- cgit v1.2.3 From c1202c4ba44758afd51649b4f3d6ef2c2fa9ad31 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Fri, 11 Jul 2014 18:45:24 +0900 Subject: Update wercker.yml --- wercker.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/wercker.yml b/wercker.yml index d9cd8bd..f2cc1bd 100644 --- a/wercker.yml +++ b/wercker.yml @@ -5,5 +5,6 @@ build: - script: name: build code: | - premake4 gmake + curl -o premake4 https://github.com/syoyo/orebuildenv/blob/master/build/linux/bin/premake4?raw=true + ./premake4 gmake make -- cgit v1.2.3 From 9528a1ef75d9b6c00c55582b23afc697421785ab Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Fri, 11 Jul 2014 18:58:34 +0900 Subject: Checkout premake4 from orebuildenv. --- wercker.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/wercker.yml b/wercker.yml index f2cc1bd..8adbd15 100644 --- a/wercker.yml +++ b/wercker.yml @@ -5,6 +5,7 @@ build: - script: name: build code: | - curl -o premake4 https://github.com/syoyo/orebuildenv/blob/master/build/linux/bin/premake4?raw=true - ./premake4 gmake + git clone https://github.com/syoyo/orebuildenv.git + chmod +x ./orebuildenv/linux/bin/premake4 + ./orebuildenv/linux/bin/premake4 gmake make -- cgit v1.2.3 From c2b53e59c628c6c83d29f1a0b7f40c57e1ee2a93 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Fri, 11 Jul 2014 19:01:49 +0900 Subject: Fix filepath in wercker.yml --- wercker.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wercker.yml b/wercker.yml index 8adbd15..e8e42ee 100644 --- a/wercker.yml +++ b/wercker.yml @@ -6,6 +6,6 @@ build: name: build code: | git clone https://github.com/syoyo/orebuildenv.git - chmod +x ./orebuildenv/linux/bin/premake4 - ./orebuildenv/linux/bin/premake4 gmake + chmod +x ./orebuildenv/build/linux/bin/premake4 + ./orebuildenv/build/linux/bin/premake4 gmake make -- cgit v1.2.3 From 166677929dbe9817faa55c051f0b5c0dda7314c2 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Fri, 11 Jul 2014 19:05:26 +0900 Subject: Add test run. --- wercker.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/wercker.yml b/wercker.yml index e8e42ee..3c1583c 100644 --- a/wercker.yml +++ b/wercker.yml @@ -9,3 +9,4 @@ build: chmod +x ./orebuildenv/build/linux/bin/premake4 ./orebuildenv/build/linux/bin/premake4 gmake make + ./test_tinyobjloader -- cgit v1.2.3 From 79ac76c767b101dc7a339be59ca9dbaec816cb07 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Fri, 11 Jul 2014 19:07:45 +0900 Subject: Add wercker status. --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 75039ae..bb5418f 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ tinyobjloader ============= +[![wercker status](https://app.wercker.com/status/495a3bac400212cdacdeb4dd9397bf4f/m "wercker status")](https://app.wercker.com/project/bykey/495a3bac400212cdacdeb4dd9397bf4f) + http://syoyo.github.io/tinyobjloader/ Tiny but poweful single file wavefront obj loader written in C++. No dependency except for C++ STL. It can parse 10M over polygons with moderate memory and time. -- cgit v1.2.3 From 6dc2c6916e4ec5e5160d5e5f35079251a4c23c91 Mon Sep 17 00:00:00 2001 From: Nicholas Yue Date: Tue, 15 Jul 2014 12:10:11 -0700 Subject: Added installation steps --- CMakeLists.txt | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 72bb155..94ec330 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -30,4 +30,21 @@ add_executable(test ${tinyobjloader-Test-Source}) target_link_libraries(test tinyobjloader) add_executable(obj_sticher ${tinyobjloader-examples-objsticher}) -target_link_libraries(obj_sticher tinyobjloader) \ No newline at end of file +target_link_libraries(obj_sticher tinyobjloader) + +#Installation +install ( TARGETS + obj_sticher + DESTINATION + bin + ) +install ( TARGETS + tinyobjloader + DESTINATION + lib + ) +install ( FILES + tiny_obj_loader.h + DESTINATION + include + ) -- cgit v1.2.3 From 33cd9a4768b7e643dcb5564ac40a9c4a46aadd48 Mon Sep 17 00:00:00 2001 From: Mykhailo Parfeniuk Date: Sun, 7 Sep 2014 14:19:22 +0300 Subject: Added multiple materials per shape/object --- tiny_obj_loader.cc | 99 ++++++++++++++++++++++++++++-------------------------- tiny_obj_loader.h | 14 +++++--- 2 files changed, 62 insertions(+), 51 deletions(-) diff --git a/tiny_obj_loader.cc b/tiny_obj_loader.cc index 5417487..5c28379 100755 --- a/tiny_obj_loader.cc +++ b/tiny_obj_loader.cc @@ -224,24 +224,22 @@ void InitMaterial(material_t& material) { 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 material_t &material, + const int material, const std::string &name, - const bool is_material_seted) + bool clearCache) { if (faceGroup.empty()) { return false; } - // Flattened version of vertex data - std::vector positions; - std::vector normals; - std::vector texcoords; - std::map vertexCache; - std::vector indices; + size_t offset; + + offset = shape.mesh.indices.size(); // Flatten vertices and indices for (size_t i = 0; i < faceGroup.size(); i++) { @@ -258,13 +256,13 @@ exportFaceGroupToShape( i1 = i2; i2 = face[k]; - unsigned int v0 = updateVertex(vertexCache, positions, normals, texcoords, in_positions, in_normals, in_texcoords, i0); - unsigned int v1 = updateVertex(vertexCache, positions, normals, texcoords, in_positions, in_normals, in_texcoords, i1); - unsigned int v2 = updateVertex(vertexCache, positions, normals, 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); - indices.push_back(v0); - indices.push_back(v1); - indices.push_back(v2); + shape.mesh.indices.push_back(v0); + shape.mesh.indices.push_back(v1); + shape.mesh.indices.push_back(v2); } } @@ -272,27 +270,22 @@ exportFaceGroupToShape( // // Construct shape. // + shape.submeshes.push_back(std::pair(offset, shape.mesh.indices.size()-offset)); + shape.name = name; - shape.mesh.positions.swap(positions); - shape.mesh.normals.swap(normals); - shape.mesh.texcoords.swap(texcoords); - shape.mesh.indices.swap(indices); - if(is_material_seted) { - shape.material = material; - } else { - InitMaterial(shape.material); - shape.material.diffuse[0] = 1.f; - shape.material.diffuse[1] = 1.f; - shape.material.diffuse[2] = 1.f; - } + shape.materials.push_back(material); + + if (clearCache) + vertexCache.clear(); return true; } std::string LoadMtl ( - std::map& material_map, + std::map& material_map, + std::vector& materials, std::istream& inStream) { material_map.clear(); @@ -332,7 +325,11 @@ std::string LoadMtl ( // new mtl if ((0 == strncmp(token, "newmtl", 6)) && isSpace((token[6]))) { // flush previous material. - material_map.insert(std::pair(material.name, material)); + if (!material.name.empty()) + { + material_map.insert(std::pair(material.name, materials.size())); + materials.push_back(material); + } // initial temporary material InitMaterial(material); @@ -474,14 +471,16 @@ std::string LoadMtl ( } } // flush last material. - material_map.insert(std::pair(material.name, material)); + material_map.insert(std::pair(material.name, materials.size())); + materials.push_back(material); return err.str(); } std::string MaterialFileReader::operator() ( const std::string& matId, - std::map& matMap) + std::vector& materials, + std::map& matMap) { std::string filepath; @@ -492,12 +491,13 @@ std::string MaterialFileReader::operator() ( } std::ifstream matIStream(filepath.c_str()); - return LoadMtl(matMap, matIStream); + return LoadMtl(matMap, materials, matIStream); } std::string LoadObj( std::vector& shapes, + std::vector& materials, // [output] const char* filename, const char* mtl_basepath) { @@ -518,11 +518,12 @@ LoadObj( } MaterialFileReader matFileReader( basePath ); - return LoadObj(shapes, ifs, matFileReader); + return LoadObj(shapes, materials, ifs, matFileReader); } std::string LoadObj( std::vector& shapes, + std::vector& materials, // [output] std::istream& inStream, MaterialReader& readMatFn) { @@ -535,9 +536,11 @@ std::string LoadObj( std::string name; // material - std::map material_map; - material_t material; - bool is_material_seted = false; + std::map material_map; + std::map vertexCache; + int material = -1; + + shape_t shape; int maxchars = 8192; // Alloc enough size. std::vector buf(maxchars); // Alloc enough size. @@ -625,13 +628,16 @@ std::string LoadObj( token += 7; sscanf(token, "%s", namebuf); + bool ret = exportFaceGroupToShape(shape, vertexCache, v, vn, vt, faceGroup, material, name, false); + faceGroup.clear(); + if (material_map.find(namebuf) != material_map.end()) { material = material_map[namebuf]; - is_material_seted = true; } else { // { error!! material not found } - InitMaterial(material); + material = -1; } + continue; } @@ -642,7 +648,7 @@ std::string LoadObj( token += 7; sscanf(token, "%s", namebuf); - std::string err_mtl = readMatFn(namebuf, material_map); + std::string err_mtl = readMatFn(namebuf, materials, material_map); if (!err_mtl.empty()) { faceGroup.clear(); // for safety return err_mtl; @@ -655,13 +661,14 @@ std::string LoadObj( if (token[0] == 'g' && isSpace((token[1]))) { // flush previous face group. - shape_t shape; - bool ret = exportFaceGroupToShape(shape, v, vn, vt, faceGroup, material, name, is_material_seted); + bool ret = exportFaceGroupToShape(shape, vertexCache, v, vn, vt, faceGroup, material, name, true); if (ret) { shapes.push_back(shape); } - is_material_seted = false; + shape = shape_t(); + + material = -1; faceGroup.clear(); std::vector names; @@ -687,14 +694,14 @@ std::string LoadObj( if (token[0] == 'o' && isSpace((token[1]))) { // flush previous face group. - shape_t shape; - bool ret = exportFaceGroupToShape(shape, v, vn, vt, faceGroup, material, name, is_material_seted); + bool ret = exportFaceGroupToShape(shape, vertexCache, v, vn, vt, faceGroup, material, name, true); if (ret) { shapes.push_back(shape); } - is_material_seted = false; + material = -1; faceGroup.clear(); + shape = shape_t(); // @todo { multiple object name? } char namebuf[4096]; @@ -709,12 +716,10 @@ std::string LoadObj( // Ignore unknown command. } - shape_t shape; - bool ret = exportFaceGroupToShape(shape, v, vn, vt, faceGroup, material, name, is_material_seted); + bool ret = exportFaceGroupToShape(shape, vertexCache, v, vn, vt, faceGroup, material, name, true); if (ret) { shapes.push_back(shape); } - is_material_seted = false; // for safety faceGroup.clear(); // for safety return err.str(); diff --git a/tiny_obj_loader.h b/tiny_obj_loader.h index b4c9337..a832aa5 100644 --- a/tiny_obj_loader.h +++ b/tiny_obj_loader.h @@ -45,8 +45,9 @@ typedef struct typedef struct { std::string name; - material_t material; mesh_t mesh; + std::vector< std::pair > submeshes; + std::vector< int > materials; } shape_t; class MaterialReader @@ -57,7 +58,8 @@ public: virtual std::string operator() ( const std::string& matId, - std::map& matMap) = 0; + std::vector& materials, + std::map& matMap) = 0; }; class MaterialFileReader: @@ -68,7 +70,8 @@ class MaterialFileReader: virtual ~MaterialFileReader() {} virtual std::string operator() ( const std::string& matId, - std::map& matMap); + std::vector& materials, + std::map& matMap); private: std::string m_mtlBasePath; @@ -81,6 +84,7 @@ class MaterialFileReader: /// '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); @@ -89,13 +93,15 @@ std::string LoadObj( /// Returns empty string when loading .obj success. std::string LoadObj( std::vector& shapes, // [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::map& material_map, + std::vector& materials, std::istream& inStream); } -- cgit v1.2.3 From cbba0a807e09d55261a83cea5b26930ea4b811ce Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Sat, 13 Sep 2014 23:49:25 +0900 Subject: Initial support of multi-materials per group. Introduce material_id attribute per face. --- test.cc | 21 ++++++++++++++------- tiny_obj_loader.cc | 11 +++-------- tiny_obj_loader.h | 5 +++-- 3 files changed, 20 insertions(+), 17 deletions(-) diff --git a/test.cc b/test.cc index 82b6178..1eeec7a 100644 --- a/test.cc +++ b/test.cc @@ -7,16 +7,18 @@ #include #include -static void PrintInfo(const std::vector& shapes) +static void PrintInfo(const std::vector& shapes, const std::vector& materials) { - std::cout << "# of shapes : " << shapes.size() << std::endl; + std::cout << "# of shapes : " << shapes.size() << std::endl; + std::cout << "# of materials : " << materials.size() << std::endl; for (size_t i = 0; i < shapes.size(); i++) { printf("shape[%ld].name = %s\n", i, shapes[i].name.c_str()); printf("shape[%ld].indices: %ld\n", i, shapes[i].mesh.indices.size()); + printf("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(); f++) { - printf(" idx[%ld] = %d\n", f, shapes[i].mesh.indices[f]); + 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]); } printf("shape[%ld].vertices: %ld\n", i, shapes[i].mesh.positions.size()); @@ -28,6 +30,7 @@ static void PrintInfo(const std::vector& shapes) shapes[i].mesh.positions[3*v+2]); } +#if 0 printf("shape[%ld].material.name = %s\n", i, shapes[i].material.name.c_str()); printf(" material.Ka = (%f, %f ,%f)\n", shapes[i].material.ambient[0], shapes[i].material.ambient[1], shapes[i].material.ambient[2]); printf(" material.Kd = (%f, %f ,%f)\n", shapes[i].material.diffuse[0], shapes[i].material.diffuse[1], shapes[i].material.diffuse[2]); @@ -47,6 +50,7 @@ static void PrintInfo(const std::vector& shapes) for (; it != itEnd; it++) { printf(" material.%s = %s\n", it->first.c_str(), it->second.c_str()); } +#endif printf("\n"); } } @@ -59,19 +63,21 @@ TestLoadObj( std::cout << "Loading " << filename << std::endl; std::vector shapes; - std::string err = tinyobj::LoadObj(shapes, filename, basepath); + std::vector materials; + std::string err = tinyobj::LoadObj(shapes, materials, filename, basepath); if (!err.empty()) { std::cerr << err << std::endl; return false; } - PrintInfo(shapes); + PrintInfo(shapes, materials); return true; } +#if 0 // @todo static bool TestStreamLoadObj() { @@ -168,6 +174,7 @@ std::string matStream( return true; } +#endif int main( @@ -184,7 +191,7 @@ main( } else { assert(true == TestLoadObj("cornell_box.obj")); assert(true == TestLoadObj("cube.obj")); - assert(true == TestStreamLoadObj()); + //assert(true == TestStreamLoadObj()); @todo } return 0; diff --git a/tiny_obj_loader.cc b/tiny_obj_loader.cc index 5c28379..d650688 100755 --- a/tiny_obj_loader.cc +++ b/tiny_obj_loader.cc @@ -229,7 +229,7 @@ exportFaceGroupToShape( const std::vector &in_normals, const std::vector &in_texcoords, const std::vector >& faceGroup, - const int material, + const int material_id, const std::string &name, bool clearCache) { @@ -263,19 +263,14 @@ exportFaceGroupToShape( 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); } } - // - // Construct shape. - // - shape.submeshes.push_back(std::pair(offset, shape.mesh.indices.size()-offset)); - shape.name = name; - shape.materials.push_back(material); - if (clearCache) vertexCache.clear(); diff --git a/tiny_obj_loader.h b/tiny_obj_loader.h index a832aa5..1fe36d7 100644 --- a/tiny_obj_loader.h +++ b/tiny_obj_loader.h @@ -40,14 +40,15 @@ typedef struct 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< std::pair > submeshes; - std::vector< int > materials; + //std::vector< std::pair > submeshes; + //std::vector< int > materials; } shape_t; class MaterialReader -- cgit v1.2.3 From e4d4c65a172c87c2da67984c6680502459b62c9f Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Sun, 14 Sep 2014 19:51:24 +0900 Subject: Fix material ID assignment. Fix build of example/obj_sticher. --- README.md | 53 +++++++++++++++++++------------- examples/obj_sticher/obj_sticher.cc | 35 +++++++++++++++++----- examples/obj_sticher/obj_writer.cc | 30 ++++++++++--------- examples/obj_sticher/obj_writer.h | 2 +- test.cc | 60 ++++++++++++++++++------------------- tiny_obj_loader.cc | 5 ++-- tiny_obj_loader.h | 2 -- 7 files changed, 110 insertions(+), 77 deletions(-) diff --git a/README.md b/README.md index bb5418f..f0eadf3 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,7 @@ Good for embedding .obj loader to your (global illumination) renderer ;-) What's new ---------- +* Sep 14, 2014 : Add support for multi-material per object/group. Thanks Mykhailo! * Mar 17, 2014 : Fixed trim newline bugs. Thanks ardneran! * Apr 29, 2014 : Add API to read .obj from std::istream. Good for reading compressed .obj or connecting to procedural primitive generator. Thanks burnse! * Apr 21, 2014 : Define default material if no material definition exists in .obj. Thanks YarmUI! @@ -36,6 +37,7 @@ TinyObjLoader is successfully used in ... * bullet3 https://github.com/erwincoumans/bullet3 * pbrt-v2 https://https://github.com/mmp/pbrt-v2 * OpenGL game engine development http://swarminglogic.com/jotting/2013_10_gamedev01 +* Your project here! Features -------- @@ -67,24 +69,27 @@ Usage std::string inputfile = "cornell_box.obj"; std::vector shapes; + std::vector materials; - std::string err = tinyobj::LoadObj(shapes, inputfile.c_str()); + std::string err = tinyobj::LoadObj(shapes, materials, inputfile.c_str()); if (!err.empty()) { std::cerr << err << std::endl; exit(1); } - - std::cout << "# of shapes : " << shapes.size() << std::endl; + + std::cout << "# of shapes : " << shapes.size() << std::endl; + std::cout << "# of materials : " << materials.size() << std::endl; for (size_t i = 0; i < shapes.size(); i++) { printf("shape[%ld].name = %s\n", i, shapes[i].name.c_str()); - printf("shape[%ld].indices: %ld\n", i, shapes[i].mesh.indices.size()); + printf("Size of shape[%ld].indices: %ld\n", i, shapes[i].mesh.indices.size()); + 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(); f++) { - printf(" idx[%ld] = %d\n", f, shapes[i].mesh.indices[f]); + 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]); } - + 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++) { @@ -93,22 +98,28 @@ Usage shapes[i].mesh.positions[3*v+1], shapes[i].mesh.positions[3*v+2]); } - - printf("shape[%ld].material.name = %s\n", i, shapes[i].material.name.c_str()); - printf(" material.Ka = (%f, %f ,%f)\n", shapes[i].material.ambient[0], shapes[i].material.ambient[1], shapes[i].material.ambient[2]); - printf(" material.Kd = (%f, %f ,%f)\n", shapes[i].material.diffuse[0], shapes[i].material.diffuse[1], shapes[i].material.diffuse[2]); - printf(" material.Ks = (%f, %f ,%f)\n", shapes[i].material.specular[0], shapes[i].material.specular[1], shapes[i].material.specular[2]); - printf(" material.Tr = (%f, %f ,%f)\n", shapes[i].material.transmittance[0], shapes[i].material.transmittance[1], shapes[i].material.transmittance[2]); - printf(" material.Ke = (%f, %f ,%f)\n", shapes[i].material.emission[0], shapes[i].material.emission[1], shapes[i].material.emission[2]); - printf(" material.Ns = %f\n", shapes[i].material.shininess); - printf(" material.map_Ka = %s\n", shapes[i].material.ambient_texname.c_str()); - printf(" material.map_Kd = %s\n", shapes[i].material.diffuse_texname.c_str()); - printf(" material.map_Ks = %s\n", shapes[i].material.specular_texname.c_str()); - printf(" material.map_Ns = %s\n", shapes[i].material.normal_texname.c_str()); - std::map::iterator it(shapes[i].material.unknown_parameter.begin()); - std::map::iterator itEnd(shapes[i].material.unknown_parameter.end()); + } + + 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]); + printf(" material.Kd = (%f, %f ,%f)\n", materials[i].diffuse[0], materials[i].diffuse[1], materials[i].diffuse[2]); + printf(" material.Ks = (%f, %f ,%f)\n", materials[i].specular[0], materials[i].specular[1], materials[i].specular[2]); + printf(" material.Tr = (%f, %f ,%f)\n", materials[i].transmittance[0], materials[i].transmittance[1], materials[i].transmittance[2]); + printf(" material.Ke = (%f, %f ,%f)\n", materials[i].emission[0], materials[i].emission[1], materials[i].emission[2]); + printf(" material.Ns = %f\n", materials[i].shininess); + printf(" material.Ni = %f\n", materials[i].ior); + printf(" material.dissolve = %f\n", materials[i].dissolve); + printf(" material.illum = %d\n", materials[i].illum); + printf(" material.map_Ka = %s\n", materials[i].ambient_texname.c_str()); + printf(" material.map_Kd = %s\n", materials[i].diffuse_texname.c_str()); + printf(" material.map_Ks = %s\n", materials[i].specular_texname.c_str()); + printf(" material.map_Ns = %s\n", materials[i].normal_texname.c_str()); + std::map::const_iterator it(materials[i].unknown_parameter.begin()); + std::map::const_iterator itEnd(materials[i].unknown_parameter.end()); for (; it != itEnd; it++) { printf(" material.%s = %s\n", it->first.c_str(), it->second.c_str()); } printf("\n"); } + diff --git a/examples/obj_sticher/obj_sticher.cc b/examples/obj_sticher/obj_sticher.cc index 1d230b8..1833216 100644 --- a/examples/obj_sticher/obj_sticher.cc +++ b/examples/obj_sticher/obj_sticher.cc @@ -10,11 +10,14 @@ #include typedef std::vector Shape; +typedef std::vector Material; void StichObjs( - std::vector& out, - const std::vector& shapes) + std::vector& out_shape, + std::vector& out_material, + const std::vector& shapes, + const std::vector& materials) { int numShapes = 0; for (size_t i = 0; i < shapes.size(); i++) { @@ -22,9 +25,11 @@ StichObjs( } printf("Total # of shapes = %d\n", numShapes); + int materialIdOffset = 0; size_t face_offset = 0; for (size_t i = 0; i < shapes.size(); i++) { + for (size_t k = 0; k < shapes[i].size(); k++) { std::string new_name = shapes[i][k].name; @@ -38,12 +43,26 @@ StichObjs( assert((shapes[i][k].mesh.positions.size() % 3) == 0); tinyobj::shape_t new_shape = shapes[i][k]; + // Add offset. + for (size_t f = 0; f < new_shape.mesh.material_ids.size(); f++) { + new_shape.mesh.material_ids[f] += materialIdOffset; + } + new_shape.name = new_name; printf("shape[%ld][%ld].new_name = %s\n", i, k, new_shape.name.c_str()); - out.push_back(new_shape); + out_shape.push_back(new_shape); } + + materialIdOffset += materials[i].size(); } + + for (size_t i = 0; i < materials.size(); i++) { + for (size_t k = 0; k < materials[i].size(); k++) { + out_material.push_back(materials[i][k]); + } + } + } int @@ -60,12 +79,13 @@ main( std::string out_filename = std::string(argv[argc-1]); // last element std::vector shapes; + std::vector materials; shapes.resize(num_objfiles); for (int i = 0; i < num_objfiles; i++) { std::cout << "Loading " << argv[i+1] << " ... " << std::flush; - std::string err = tinyobj::LoadObj(shapes[i], argv[i+1]); + std::string err = tinyobj::LoadObj(shapes[i], materials[i], argv[i+1]); if (!err.empty()) { std::cerr << err << std::endl; exit(1); @@ -74,10 +94,11 @@ main( std::cout << "DONE." << std::endl; } - std::vector out; - StichObjs(out, shapes); + std::vector out_shape; + std::vector out_material; + StichObjs(out_shape, out_material, shapes, materials); - bool ret = WriteObj(out_filename, out); + bool ret = WriteObj(out_filename, out_shape, out_material); assert(ret); return 0; diff --git a/examples/obj_sticher/obj_writer.cc b/examples/obj_sticher/obj_writer.cc index f44d3f3..bb12457 100644 --- a/examples/obj_sticher/obj_writer.cc +++ b/examples/obj_sticher/obj_writer.cc @@ -11,22 +11,16 @@ static std::string GetFileBasename(const std::string& FileName) return ""; } -bool WriteMat(const std::string& filename, std::vector shapes) { +bool WriteMat(const std::string& filename, const std::vector& materials) { FILE* fp = fopen(filename.c_str(), "w"); if (!fp) { fprintf(stderr, "Failed to open file [ %s ] for write.\n", filename.c_str()); return false; } - std::map mtl_table; + for (size_t i = 0; i < materials.size(); i++) { - for (size_t i = 0; i < shapes.size(); i++) { - mtl_table[shapes[i].material.name] = shapes[i].material; - } - - for (std::map::iterator it = mtl_table.begin(); it != mtl_table.end(); it++) { - - tinyobj::material_t mat = it->second; + tinyobj::material_t mat = materials[i]; fprintf(fp, "newmtl %s\n", mat.name.c_str()); fprintf(fp, "Ka %f %f %f\n", mat.ambient[0], mat.ambient[1], mat.ambient[2]); @@ -44,7 +38,7 @@ bool WriteMat(const std::string& filename, std::vector shapes) return true; } -bool WriteObj(const std::string& filename, std::vector shapes) { +bool WriteObj(const std::string& filename, const std::vector& shapes, const std::vector& materials) { FILE* fp = fopen(filename.c_str(), "w"); if (!fp) { fprintf(stderr, "Failed to open file [ %s ] for write.\n", filename.c_str()); @@ -57,6 +51,7 @@ bool WriteObj(const std::string& filename, std::vector shapes) int v_offset = 0; int vn_offset = 0; int vt_offset = 0; + int prev_material_id = -1; fprintf(fp, "mtllib %s\n", material_filename.c_str()); @@ -71,9 +66,9 @@ bool WriteObj(const std::string& filename, std::vector shapes) fprintf(fp, "g %s\n", shapes[i].name.c_str()); } - if (!shapes[i].material.name.empty()) { - fprintf(fp, "usemtl %s\n", shapes[i].material.name.c_str()); - } + //if (!shapes[i].material.name.empty()) { + // fprintf(fp, "usemtl %s\n", shapes[i].material.name.c_str()); + //} // facevarying vtx for (size_t k = 0; k < shapes[i].mesh.indices.size() / 3; k++) { @@ -124,6 +119,13 @@ bool WriteObj(const std::string& filename, std::vector shapes) int v1 = (3*k + 1) + 1 + v_offset; int v2 = (3*k + 2) + 1 + v_offset; + int material_id = shapes[i].mesh.material_ids[k]; + if (material_id != prev_material_id) { + std::string material_name = materials[material_id].name; + fprintf(fp, "usemtl %s\n", material_name.c_str()); + prev_material_id = material_id; + } + if (has_vn && has_vt) { fprintf(fp, "f %d/%d/%d %d/%d/%d %d/%d/%d\n", v0, v0, v0, v1, v1, v1, v2, v2, v2); @@ -148,7 +150,7 @@ bool WriteObj(const std::string& filename, std::vector shapes) // // Write material file // - bool ret = WriteMat(material_filename, shapes); + bool ret = WriteMat(material_filename, materials); return ret; } diff --git a/examples/obj_sticher/obj_writer.h b/examples/obj_sticher/obj_writer.h index e31e0e4..00cd792 100644 --- a/examples/obj_sticher/obj_writer.h +++ b/examples/obj_sticher/obj_writer.h @@ -3,7 +3,7 @@ #include "../../tiny_obj_loader.h" -extern bool WriteObj(const std::string& filename, std::vector shapes); +extern bool WriteObj(const std::string& filename, const std::vector& shapes, const std::vector& materials); #endif // __OBJ_WRITER_H__ diff --git a/test.cc b/test.cc index 1eeec7a..1ad6d8c 100644 --- a/test.cc +++ b/test.cc @@ -14,8 +14,8 @@ static void PrintInfo(const std::vector& shapes, const std::ve for (size_t i = 0; i < shapes.size(); i++) { printf("shape[%ld].name = %s\n", i, shapes[i].name.c_str()); - printf("shape[%ld].indices: %ld\n", i, shapes[i].mesh.indices.size()); - printf("shape[%ld].material_ids: %ld\n", i, shapes[i].mesh.material_ids.size()); + printf("Size of shape[%ld].indices: %ld\n", i, shapes[i].mesh.indices.size()); + 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]); @@ -29,28 +29,28 @@ static void PrintInfo(const std::vector& shapes, const std::ve shapes[i].mesh.positions[3*v+1], shapes[i].mesh.positions[3*v+2]); } - -#if 0 - printf("shape[%ld].material.name = %s\n", i, shapes[i].material.name.c_str()); - printf(" material.Ka = (%f, %f ,%f)\n", shapes[i].material.ambient[0], shapes[i].material.ambient[1], shapes[i].material.ambient[2]); - printf(" material.Kd = (%f, %f ,%f)\n", shapes[i].material.diffuse[0], shapes[i].material.diffuse[1], shapes[i].material.diffuse[2]); - printf(" material.Ks = (%f, %f ,%f)\n", shapes[i].material.specular[0], shapes[i].material.specular[1], shapes[i].material.specular[2]); - printf(" material.Tr = (%f, %f ,%f)\n", shapes[i].material.transmittance[0], shapes[i].material.transmittance[1], shapes[i].material.transmittance[2]); - printf(" material.Ke = (%f, %f ,%f)\n", shapes[i].material.emission[0], shapes[i].material.emission[1], shapes[i].material.emission[2]); - printf(" material.Ns = %f\n", shapes[i].material.shininess); - printf(" material.Ni = %f\n", shapes[i].material.ior); - printf(" material.dissolve = %f\n", shapes[i].material.dissolve); - printf(" material.illum = %d\n", shapes[i].material.illum); - printf(" material.map_Ka = %s\n", shapes[i].material.ambient_texname.c_str()); - printf(" material.map_Kd = %s\n", shapes[i].material.diffuse_texname.c_str()); - printf(" material.map_Ks = %s\n", shapes[i].material.specular_texname.c_str()); - printf(" material.map_Ns = %s\n", shapes[i].material.normal_texname.c_str()); - std::map::const_iterator it(shapes[i].material.unknown_parameter.begin()); - std::map::const_iterator itEnd(shapes[i].material.unknown_parameter.end()); + } + + 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]); + printf(" material.Kd = (%f, %f ,%f)\n", materials[i].diffuse[0], materials[i].diffuse[1], materials[i].diffuse[2]); + printf(" material.Ks = (%f, %f ,%f)\n", materials[i].specular[0], materials[i].specular[1], materials[i].specular[2]); + printf(" material.Tr = (%f, %f ,%f)\n", materials[i].transmittance[0], materials[i].transmittance[1], materials[i].transmittance[2]); + printf(" material.Ke = (%f, %f ,%f)\n", materials[i].emission[0], materials[i].emission[1], materials[i].emission[2]); + printf(" material.Ns = %f\n", materials[i].shininess); + printf(" material.Ni = %f\n", materials[i].ior); + printf(" material.dissolve = %f\n", materials[i].dissolve); + printf(" material.illum = %d\n", materials[i].illum); + printf(" material.map_Ka = %s\n", materials[i].ambient_texname.c_str()); + printf(" material.map_Kd = %s\n", materials[i].diffuse_texname.c_str()); + printf(" material.map_Ks = %s\n", materials[i].specular_texname.c_str()); + printf(" material.map_Ns = %s\n", materials[i].normal_texname.c_str()); + std::map::const_iterator it(materials[i].unknown_parameter.begin()); + std::map::const_iterator itEnd(materials[i].unknown_parameter.end()); for (; it != itEnd; it++) { printf(" material.%s = %s\n", it->first.c_str(), it->second.c_str()); } -#endif printf("\n"); } } @@ -77,7 +77,6 @@ TestLoadObj( } -#if 0 // @todo static bool TestStreamLoadObj() { @@ -152,9 +151,10 @@ std::string matStream( virtual ~MaterialStringStreamReader() {} virtual std::string operator() ( const std::string& matId, - std::map& matMap) + std::vector& materials, + std::map& matMap) { - return LoadMtl(matMap, m_matSStream); + return LoadMtl(matMap, materials, m_matSStream); } private: @@ -163,18 +163,18 @@ std::string matStream( MaterialStringStreamReader matSSReader(matStream); std::vector shapes; - std::string err = tinyobj::LoadObj(shapes, objStream, matSSReader); + std::vector materials; + std::string err = tinyobj::LoadObj(shapes, materials, objStream, matSSReader); if (!err.empty()) { std::cerr << err << std::endl; return false; } - PrintInfo(shapes); + PrintInfo(shapes, materials); return true; } -#endif int main( @@ -189,9 +189,9 @@ main( } assert(true == TestLoadObj(argv[1], basepath)); } else { - assert(true == TestLoadObj("cornell_box.obj")); - assert(true == TestLoadObj("cube.obj")); - //assert(true == TestStreamLoadObj()); @todo + //assert(true == TestLoadObj("cornell_box.obj")); + //assert(true == TestLoadObj("cube.obj")); + assert(true == TestStreamLoadObj()); } return 0; diff --git a/tiny_obj_loader.cc b/tiny_obj_loader.cc index d650688..e2f5257 100755 --- a/tiny_obj_loader.cc +++ b/tiny_obj_loader.cc @@ -5,6 +5,7 @@ // // +// version 0.9.7: Support multi-materials(per-face material ID) per object/grou . // version 0.9.6: Support Ni(index of refraction) mtl parameter. // Parse transmittance material parameter correctly. // version 0.9.5: Parse multiple group name. @@ -663,7 +664,7 @@ std::string LoadObj( shape = shape_t(); - material = -1; + //material = -1; faceGroup.clear(); std::vector names; @@ -694,7 +695,7 @@ std::string LoadObj( shapes.push_back(shape); } - material = -1; + //material = -1; faceGroup.clear(); shape = shape_t(); diff --git a/tiny_obj_loader.h b/tiny_obj_loader.h index 1fe36d7..a58d7be 100644 --- a/tiny_obj_loader.h +++ b/tiny_obj_loader.h @@ -47,8 +47,6 @@ typedef struct { std::string name; mesh_t mesh; - //std::vector< std::pair > submeshes; - //std::vector< int > materials; } shape_t; class MaterialReader -- cgit v1.2.3 From 957fa36e7029dc26818a969b2ec95a5fe00183b5 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Sun, 14 Sep 2014 19:52:44 +0900 Subject: typo. --- tiny_obj_loader.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tiny_obj_loader.cc b/tiny_obj_loader.cc index e2f5257..75f0dca 100755 --- a/tiny_obj_loader.cc +++ b/tiny_obj_loader.cc @@ -5,7 +5,7 @@ // // -// version 0.9.7: Support multi-materials(per-face material ID) per object/grou . +// version 0.9.7: Support multi-materials(per-face material ID) per object/group. // version 0.9.6: Support Ni(index of refraction) mtl parameter. // Parse transmittance material parameter correctly. // version 0.9.5: Parse multiple group name. -- cgit v1.2.3 From b35f4989ad90ef5f05e305c85923379882e4f9f0 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Mon, 29 Sep 2014 19:08:46 +0900 Subject: Add link to mallie. --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index f0eadf3..033bbe6 100644 --- a/README.md +++ b/README.md @@ -37,6 +37,7 @@ TinyObjLoader is successfully used in ... * bullet3 https://github.com/erwincoumans/bullet3 * pbrt-v2 https://https://github.com/mmp/pbrt-v2 * OpenGL game engine development http://swarminglogic.com/jotting/2013_10_gamedev01 +* mallie https://lighttransport.github.io/mallie * Your project here! Features -- cgit v1.2.3 From b214cfb4b98be9464ff9f5a052a26fbbece52325 Mon Sep 17 00:00:00 2001 From: Julian Simioni Date: Wed, 29 Oct 2014 18:54:35 -0700 Subject: Fix unused variable warnings --- tiny_obj_loader.cc | 5 ----- 1 file changed, 5 deletions(-) diff --git a/tiny_obj_loader.cc b/tiny_obj_loader.cc index 75f0dca..b73e766 100755 --- a/tiny_obj_loader.cc +++ b/tiny_obj_loader.cc @@ -238,10 +238,6 @@ exportFaceGroupToShape( return false; } - size_t offset; - - offset = shape.mesh.indices.size(); - // Flatten vertices and indices for (size_t i = 0; i < faceGroup.size(); i++) { const std::vector& face = faceGroup[i]; @@ -624,7 +620,6 @@ std::string LoadObj( token += 7; sscanf(token, "%s", namebuf); - bool ret = exportFaceGroupToShape(shape, vertexCache, v, vn, vt, faceGroup, material, name, false); faceGroup.clear(); if (material_map.find(namebuf) != material_map.end()) { -- cgit v1.2.3 From 80b243092b5cb3c3523030a188bd50aeaf8fd1e4 Mon Sep 17 00:00:00 2001 From: root Date: Fri, 14 Nov 2014 14:32:20 +0100 Subject: base of python module --- python/howto.py | 5 ++ python/main.cpp | 144 +++++++++++++++++++++++++++++++++++++++++++++++++++ python/pyTOL.cbp.mak | 96 ++++++++++++++++++++++++++++++++++ 3 files changed, 245 insertions(+) create mode 100644 python/howto.py create mode 100644 python/main.cpp create mode 100644 python/pyTOL.cbp.mak diff --git a/python/howto.py b/python/howto.py new file mode 100644 index 0000000..1342dac --- /dev/null +++ b/python/howto.py @@ -0,0 +1,5 @@ +import tinyobjloader as tol + +model = tol.LoadObj("cube.obj") + +print(model["shapes"], model["materials"]) diff --git a/python/main.cpp b/python/main.cpp new file mode 100644 index 0000000..e41916c --- /dev/null +++ b/python/main.cpp @@ -0,0 +1,144 @@ +//python3 module for tinyobjloader +// +//usage: +// import tinyobjloader as tol +// model = tol.LoadObj(name) +// print(model["shapes"]) +// print(model["materials"] +#include +#include +#include "tiny_obj_loader.h" + +typedef std::vector vectd; + +PyObject* +pyTupleFromfloat3 (float array[3]) +{ + int i; + PyObject* tuple = PyTuple_New(3); + + for(i=0; i<=2 ; i++){ + PyTuple_SetItem(tuple, i, PyFloat_FromDouble(array[i])); + } + + return tuple; +} + +extern "C" +{ + +static PyObject* +pyLoadObj(PyObject* self, PyObject* args) +{ + PyObject *rtntpl, *pyshapes, *pymaterials; + char const* filename; + std::vector shapes; + std::vector materials; + + if(!PyArg_ParseTuple(args, "s", &filename)) + return NULL; + + tinyobj::LoadObj(shapes, materials, filename); + + pyshapes = PyDict_New(); + pymaterials = PyDict_New(); + rtntpl = PyDict_New(); + + for (std::vector::iterator shape = shapes.begin() ; + shape != shapes.end(); shape++) + { + PyObject *meshobj; + PyObject *positions, *normals, *texcoords, *indices, *material_ids; + + meshobj = PyTuple_New(5); + positions = PyList_New(0); + normals = PyList_New(0); + texcoords = PyList_New(0); + indices = PyList_New(0); + material_ids = PyList_New(0); + + tinyobj::mesh_t cm = (*shape).mesh; + for (int i = 0; i <= 4 ; i++ ) + { + PyObject *current; + vectd vect; + + switch (i) + { + case 0: current = positions; + vect = vectd(cm.positions.begin(), cm.positions.end()); + case 1: current = normals; + vect = vectd(cm.normals.begin(), cm.normals.end()); + case 2: current = texcoords; + vect = vectd(cm.texcoords.begin(), cm.texcoords.end()); + case 3: current = indices; + vect = vectd(cm.indices.begin(), cm.indices.end()); + case 4: current = material_ids; + vect = vectd(cm.material_ids.begin(), cm.material_ids.end()); + } + + for (std::vector::iterator it = vect.begin() ; + it != vect.end(); it++) + { + PyList_Insert(current, it - vect.begin(), PyFloat_FromDouble(*it)); + } + + PyTuple_SetItem(meshobj, i, current); + } + + PyDict_SetItemString(pyshapes, (*shape).name.c_str(), meshobj); + } + + for (std::vector::iterator mat = materials.begin() ; + mat != materials.end(); mat++) + { + PyObject *matobj = PyDict_New(); + + PyDict_SetItemString(matobj, "shininess", PyFloat_FromDouble((*mat).shininess)); + PyDict_SetItemString(matobj, "ior", PyFloat_FromDouble((*mat).ior)); + PyDict_SetItemString(matobj, "dissolve", PyFloat_FromDouble((*mat).dissolve)); + PyDict_SetItemString(matobj, "illum", PyLong_FromLong((*mat).illum)); + PyDict_SetItemString(matobj, "ambient_texname", PyUnicode_FromString((*mat).ambient_texname.c_str())); + PyDict_SetItemString(matobj, "diffuse_texname", PyUnicode_FromString((*mat).diffuse_texname.c_str())); + PyDict_SetItemString(matobj, "specular_texname", PyUnicode_FromString((*mat).specular_texname.c_str())); + PyDict_SetItemString(matobj, "normal_texname", PyUnicode_FromString((*mat).normal_texname.c_str())); + PyDict_SetItemString(matobj, "ambient", pyTupleFromfloat3((*mat).ambient)); + PyDict_SetItemString(matobj, "diffuse", pyTupleFromfloat3((*mat).diffuse)); + PyDict_SetItemString(matobj, "specular", pyTupleFromfloat3((*mat).specular)); + PyDict_SetItemString(matobj, "transmittance", pyTupleFromfloat3((*mat).transmittance)); + PyDict_SetItemString(matobj, "emission", pyTupleFromfloat3((*mat).emission)); + + PyDict_SetItemString(pymaterials, (*mat).name.c_str(), matobj); + } + + PyDict_SetItemString(rtntpl, "shapes", pyshapes); + PyDict_SetItemString(rtntpl, "materials", pymaterials); + + return rtntpl; +} + + +static PyMethodDef mMethods[] = { + +{"LoadObj", pyLoadObj, METH_VARARGS}, +{NULL, NULL, 0, NULL} + +}; + + +static struct PyModuleDef moduledef = { + PyModuleDef_HEAD_INIT, + "tinyobjloader", + NULL, + -1, + mMethods +}; + + +PyMODINIT_FUNC +PyInit_tinyobjloader(void) +{ + return PyModule_Create(&moduledef); +} + +} diff --git a/python/pyTOL.cbp.mak b/python/pyTOL.cbp.mak new file mode 100644 index 0000000..b14db6e --- /dev/null +++ b/python/pyTOL.cbp.mak @@ -0,0 +1,96 @@ +#------------------------------------------------------------------------------# +# This makefile was generated by 'cbp2make' tool rev.147 # +#------------------------------------------------------------------------------# +#requirements : Python 3 (- dev) + +WORKDIR = `pwd` + +CC = gcc +CXX = g++ +AR = ar +LD = g++ +WINDRES = windres + +INC = +CFLAGS = -Wall -fexceptions `python3-config --cflags` +RESINC = +LIBDIR = +LIB = +LDFLAGS = `python3-config --ldflags` + +INC_DEBUG = $(INC) +CFLAGS_DEBUG = $(CFLAGS) -g +RESINC_DEBUG = $(RESINC) +RCFLAGS_DEBUG = $(RCFLAGS) +LIBDIR_DEBUG = $(LIBDIR) +LIB_DEBUG = $(LIB) +LDFLAGS_DEBUG = $(LDFLAGS) +OBJDIR_DEBUG = obj/Debug +DEP_DEBUG = +OUT_DEBUG = bin/Debug/tinyobjloader.so + +INC_RELEASE = $(INC) +CFLAGS_RELEASE = $(CFLAGS) -O2 +RESINC_RELEASE = $(RESINC) +RCFLAGS_RELEASE = $(RCFLAGS) +LIBDIR_RELEASE = $(LIBDIR) +LIB_RELEASE = $(LIB) +LDFLAGS_RELEASE = $(LDFLAGS) -s +OBJDIR_RELEASE = obj/Release +DEP_RELEASE = +OUT_RELEASE = bin/Release/tinyobjloader.so + +OBJ_DEBUG = $(OBJDIR_DEBUG)/main.o $(OBJDIR_DEBUG)/tiny_obj_loader.o + +OBJ_RELEASE = $(OBJDIR_RELEASE)/main.o $(OBJDIR_RELEASE)/tiny_obj_loader.o + +all: debug release + +clean: clean_debug clean_release + +before_debug: + test -d bin/Debug || mkdir -p bin/Debug + test -d $(OBJDIR_DEBUG) || mkdir -p $(OBJDIR_DEBUG) + +after_debug: + +debug: before_debug out_debug after_debug + +out_debug: before_debug $(OBJ_DEBUG) $(DEP_DEBUG) + $(LD) -shared $(LIBDIR_DEBUG) $(OBJ_DEBUG) -o $(OUT_DEBUG) $(LDFLAGS_DEBUG) $(LIB_DEBUG) + +$(OBJDIR_DEBUG)/main.o: main.cpp + $(CXX) $(CFLAGS_DEBUG) $(INC_DEBUG) -c main.cpp -o $(OBJDIR_DEBUG)/main.o + +$(OBJDIR_DEBUG)/tiny_obj_loader.o: tiny_obj_loader.cc + $(CC) $(CFLAGS_DEBUG) $(INC_DEBUG) -c tiny_obj_loader.cc -o $(OBJDIR_DEBUG)/tiny_obj_loader.o + +clean_debug: + rm -f $(OBJ_DEBUG) $(OUT_DEBUG) + rm -rf bin/Debug + rm -rf $(OBJDIR_DEBUG) + +before_release: + test -d bin/Release || mkdir -p bin/Release + test -d $(OBJDIR_RELEASE) || mkdir -p $(OBJDIR_RELEASE) + +after_release: + +release: before_release out_release after_release + +out_release: before_release $(OBJ_RELEASE) $(DEP_RELEASE) + $(LD) -shared $(LIBDIR_RELEASE) $(OBJ_RELEASE) -o $(OUT_RELEASE) $(LDFLAGS_RELEASE) $(LIB_RELEASE) + +$(OBJDIR_RELEASE)/main.o: main.cpp + $(CXX) $(CFLAGS_RELEASE) $(INC_RELEASE) -c main.cpp -o $(OBJDIR_RELEASE)/main.o + +$(OBJDIR_RELEASE)/tiny_obj_loader.o: tiny_obj_loader.cc + $(CC) $(CFLAGS_RELEASE) $(INC_RELEASE) -c tiny_obj_loader.cc -o $(OBJDIR_RELEASE)/tiny_obj_loader.o + +clean_release: + rm -f $(OBJ_RELEASE) $(OUT_RELEASE) + rm -rf bin/Release + rm -rf $(OBJDIR_RELEASE) + +.PHONY: before_debug after_debug clean_debug before_release after_release clean_release + -- cgit v1.2.3 From 878a6560cd6b9f9eff32e12df862b2726fb3db74 Mon Sep 17 00:00:00 2001 From: root Date: Fri, 14 Nov 2014 15:22:50 +0100 Subject: fix makefile --- python/main.cpp | 2 +- python/pyTOL.cbp.mak | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/python/main.cpp b/python/main.cpp index e41916c..636a24d 100644 --- a/python/main.cpp +++ b/python/main.cpp @@ -7,7 +7,7 @@ // print(model["materials"] #include #include -#include "tiny_obj_loader.h" +#include "../tiny_obj_loader.h" typedef std::vector vectd; diff --git a/python/pyTOL.cbp.mak b/python/pyTOL.cbp.mak index b14db6e..ba18ae9 100644 --- a/python/pyTOL.cbp.mak +++ b/python/pyTOL.cbp.mak @@ -1,7 +1,7 @@ #------------------------------------------------------------------------------# # This makefile was generated by 'cbp2make' tool rev.147 # #------------------------------------------------------------------------------# -#requirements : Python 3 (- dev) + WORKDIR = `pwd` @@ -62,8 +62,8 @@ out_debug: before_debug $(OBJ_DEBUG) $(DEP_DEBUG) $(OBJDIR_DEBUG)/main.o: main.cpp $(CXX) $(CFLAGS_DEBUG) $(INC_DEBUG) -c main.cpp -o $(OBJDIR_DEBUG)/main.o -$(OBJDIR_DEBUG)/tiny_obj_loader.o: tiny_obj_loader.cc - $(CC) $(CFLAGS_DEBUG) $(INC_DEBUG) -c tiny_obj_loader.cc -o $(OBJDIR_DEBUG)/tiny_obj_loader.o +$(OBJDIR_DEBUG)/tiny_obj_loader.o: ../tiny_obj_loader.cc + $(CC) $(CFLAGS_DEBUG) $(INC_DEBUG) -c ../tiny_obj_loader.cc -o $(OBJDIR_DEBUG)/tiny_obj_loader.o clean_debug: rm -f $(OBJ_DEBUG) $(OUT_DEBUG) @@ -84,8 +84,8 @@ out_release: before_release $(OBJ_RELEASE) $(DEP_RELEASE) $(OBJDIR_RELEASE)/main.o: main.cpp $(CXX) $(CFLAGS_RELEASE) $(INC_RELEASE) -c main.cpp -o $(OBJDIR_RELEASE)/main.o -$(OBJDIR_RELEASE)/tiny_obj_loader.o: tiny_obj_loader.cc - $(CC) $(CFLAGS_RELEASE) $(INC_RELEASE) -c tiny_obj_loader.cc -o $(OBJDIR_RELEASE)/tiny_obj_loader.o +$(OBJDIR_RELEASE)/tiny_obj_loader.o: ../tiny_obj_loader.cc + $(CC) $(CFLAGS_RELEASE) $(INC_RELEASE) -c ../tiny_obj_loader.cc -o $(OBJDIR_RELEASE)/tiny_obj_loader.o clean_release: rm -f $(OBJ_RELEASE) $(OUT_RELEASE) -- cgit v1.2.3 From f750f3faebb688a0c7f3d918fb29a96c2ec652e4 Mon Sep 17 00:00:00 2001 From: root Date: Sun, 16 Nov 2014 19:35:18 +0100 Subject: adding setup.py file --- python/setup.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 python/setup.py diff --git a/python/setup.py b/python/setup.py new file mode 100644 index 0000000..de7b976 --- /dev/null +++ b/python/setup.py @@ -0,0 +1,13 @@ +from distutils.core import setup, Extension + + +m = Extension('tinyobjloader', + sources = ['main.cpp', '../tiny_obj_loader.cc']) + + +setup (name = 'tinyobjloader', + version = '0.1', + description = 'Python module for tinyobjloader', + ext_modules = [m]) + + -- cgit v1.2.3 From e5bbda3835e2e848c951a2aa052847864b956b7d Mon Sep 17 00:00:00 2001 From: Ododo Date: Sun, 16 Nov 2014 21:08:44 +0100 Subject: Update howto.py --- python/howto.py | 560 +++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 559 insertions(+), 1 deletion(-) diff --git a/python/howto.py b/python/howto.py index 1342dac..099a1af 100644 --- a/python/howto.py +++ b/python/howto.py @@ -1,5 +1,563 @@ import tinyobjloader as tol +import json model = tol.LoadObj("cube.obj") -print(model["shapes"], model["materials"]) +#print(model["shapes"], model["materials"]) +print( json.dumps(model, indent=4) ) + +#EXAMPLE OUTPUT + +##{ +## "shapes": { +## "left": [ +## [ +## 2.0, +## 2.0, +## 2.0, +## 2.0, +## 2.0, +## 2.0, +## 2.0, +## 2.0, +## 2.0, +## 2.0 +## ], +## [ +## 2.0, +## 2.0, +## 2.0, +## 2.0, +## 2.0, +## 2.0, +## 2.0, +## 2.0, +## 2.0, +## 2.0 +## ], +## [ +## 2.0, +## 2.0, +## 2.0, +## 2.0, +## 2.0, +## 2.0, +## 2.0, +## 2.0, +## 2.0, +## 2.0 +## ], +## [ +## 2.0, +## 2.0, +## 2.0, +## 2.0, +## 2.0, +## 2.0, +## 2.0, +## 2.0, +## 2.0, +## 2.0 +## ], +## [ +## 2.0, +## 2.0, +## 2.0, +## 2.0, +## 2.0, +## 2.0, +## 2.0, +## 2.0, +## 2.0, +## 2.0 +## ] +## ], +## "bottom": [ +## [ +## 0.0, +## 0.0, +## 0.0, +## 0.0, +## 0.0, +## 0.0, +## 0.0, +## 0.0, +## 0.0, +## 0.0 +## ], +## [ +## 0.0, +## 0.0, +## 0.0, +## 0.0, +## 0.0, +## 0.0, +## 0.0, +## 0.0, +## 0.0, +## 0.0 +## ], +## [ +## 0.0, +## 0.0, +## 0.0, +## 0.0, +## 0.0, +## 0.0, +## 0.0, +## 0.0, +## 0.0, +## 0.0 +## ], +## [ +## 0.0, +## 0.0, +## 0.0, +## 0.0, +## 0.0, +## 0.0, +## 0.0, +## 0.0, +## 0.0, +## 0.0 +## ], +## [ +## 0.0, +## 0.0, +## 0.0, +## 0.0, +## 0.0, +## 0.0, +## 0.0, +## 0.0, +## 0.0, +## 0.0 +## ] +## ], +## "right": [ +## [ +## 1.0, +## 1.0, +## 1.0, +## 1.0, +## 1.0, +## 1.0, +## 1.0, +## 1.0, +## 1.0, +## 1.0 +## ], +## [ +## 1.0, +## 1.0, +## 1.0, +## 1.0, +## 1.0, +## 1.0, +## 1.0, +## 1.0, +## 1.0, +## 1.0 +## ], +## [ +## 1.0, +## 1.0, +## 1.0, +## 1.0, +## 1.0, +## 1.0, +## 1.0, +## 1.0, +## 1.0, +## 1.0 +## ], +## [ +## 1.0, +## 1.0, +## 1.0, +## 1.0, +## 1.0, +## 1.0, +## 1.0, +## 1.0, +## 1.0, +## 1.0 +## ], +## [ +## 1.0, +## 1.0, +## 1.0, +## 1.0, +## 1.0, +## 1.0, +## 1.0, +## 1.0, +## 1.0, +## 1.0 +## ] +## ], +## "front": [ +## [ +## 0.0, +## 0.0, +## 0.0, +## 0.0, +## 0.0, +## 0.0, +## 0.0, +## 0.0, +## 0.0, +## 0.0 +## ], +## [ +## 0.0, +## 0.0, +## 0.0, +## 0.0, +## 0.0, +## 0.0, +## 0.0, +## 0.0, +## 0.0, +## 0.0 +## ], +## [ +## 0.0, +## 0.0, +## 0.0, +## 0.0, +## 0.0, +## 0.0, +## 0.0, +## 0.0, +## 0.0, +## 0.0 +## ], +## [ +## 0.0, +## 0.0, +## 0.0, +## 0.0, +## 0.0, +## 0.0, +## 0.0, +## 0.0, +## 0.0, +## 0.0 +## ], +## [ +## 0.0, +## 0.0, +## 0.0, +## 0.0, +## 0.0, +## 0.0, +## 0.0, +## 0.0, +## 0.0, +## 0.0 +## ] +## ], +## "top": [ +## [ +## 0.0, +## 0.0, +## 0.0, +## 0.0, +## 0.0, +## 0.0, +## 0.0, +## 0.0, +## 0.0, +## 0.0 +## ], +## [ +## 0.0, +## 0.0, +## 0.0, +## 0.0, +## 0.0, +## 0.0, +## 0.0, +## 0.0, +## 0.0, +## 0.0 +## ], +## [ +## 0.0, +## 0.0, +## 0.0, +## 0.0, +## 0.0, +## 0.0, +## 0.0, +## 0.0, +## 0.0, +## 0.0 +## ], +## [ +## 0.0, +## 0.0, +## 0.0, +## 0.0, +## 0.0, +## 0.0, +## 0.0, +## 0.0, +## 0.0, +## 0.0 +## ], +## [ +## 0.0, +## 0.0, +## 0.0, +## 0.0, +## 0.0, +## 0.0, +## 0.0, +## 0.0, +## 0.0, +## 0.0 +## ] +## ], +## "back": [ +## [ +## 0.0, +## 0.0, +## 0.0, +## 0.0, +## 0.0, +## 0.0, +## 0.0, +## 0.0, +## 0.0, +## 0.0 +## ], +## [ +## 0.0, +## 0.0, +## 0.0, +## 0.0, +## 0.0, +## 0.0, +## 0.0, +## 0.0, +## 0.0, +## 0.0 +## ], +## [ +## 0.0, +## 0.0, +## 0.0, +## 0.0, +## 0.0, +## 0.0, +## 0.0, +## 0.0, +## 0.0, +## 0.0 +## ], +## [ +## 0.0, +## 0.0, +## 0.0, +## 0.0, +## 0.0, +## 0.0, +## 0.0, +## 0.0, +## 0.0, +## 0.0 +## ], +## [ +## 0.0, +## 0.0, +## 0.0, +## 0.0, +## 0.0, +## 0.0, +## 0.0, +## 0.0, +## 0.0, +## 0.0 +## ] +## ] +## }, +## "materials": { +## "green": { +## "emission": [ +## 0.0, +## 0.0, +## 0.0 +## ], +## "specular": [ +## 0.0, +## 0.0, +## 0.0 +## ], +## "illum": 0, +## "ior": 1.0, +## "shininess": 1.0, +## "normal_texname": "", +## "specular_texname": "", +## "transmittance": [ +## 0.0, +## 0.0, +## 0.0 +## ], +## "dissolve": 1.0, +## "ambient": [ +## 0.0, +## 0.0, +## 0.0 +## ], +## "diffuse": [ +## 0.0, +## 1.0, +## 0.0 +## ], +## "diffuse_texname": "", +## "ambient_texname": "" +## }, +## "blue": { +## "emission": [ +## 0.0, +## 0.0, +## 0.0 +## ], +## "specular": [ +## 0.0, +## 0.0, +## 0.0 +## ], +## "illum": 0, +## "ior": 1.0, +## "shininess": 1.0, +## "normal_texname": "", +## "specular_texname": "", +## "transmittance": [ +## 0.0, +## 0.0, +## 0.0 +## ], +## "dissolve": 1.0, +## "ambient": [ +## 0.0, +## 0.0, +## 0.0 +## ], +## "diffuse": [ +## 0.0, +## 0.0, +## 1.0 +## ], +## "diffuse_texname": "", +## "ambient_texname": "" +## }, +## "red": { +## "emission": [ +## 0.0, +## 0.0, +## 0.0 +## ], +## "specular": [ +## 0.0, +## 0.0, +## 0.0 +## ], +## "illum": 0, +## "ior": 1.0, +## "shininess": 1.0, +## "normal_texname": "", +## "specular_texname": "", +## "transmittance": [ +## 0.0, +## 0.0, +## 0.0 +## ], +## "dissolve": 1.0, +## "ambient": [ +## 0.0, +## 0.0, +## 0.0 +## ], +## "diffuse": [ +## 1.0, +## 0.0, +## 0.0 +## ], +## "diffuse_texname": "", +## "ambient_texname": "" +## }, +## "white": { +## "emission": [ +## 0.0, +## 0.0, +## 0.0 +## ], +## "specular": [ +## 0.0, +## 0.0, +## 0.0 +## ], +## "illum": 0, +## "ior": 1.0, +## "shininess": 1.0, +## "normal_texname": "", +## "specular_texname": "", +## "transmittance": [ +## 0.0, +## 0.0, +## 0.0 +## ], +## "dissolve": 1.0, +## "ambient": [ +## 0.0, +## 0.0, +## 0.0 +## ], +## "diffuse": [ +## 1.0, +## 1.0, +## 1.0 +## ], +## "diffuse_texname": "", +## "ambient_texname": "" +## }, +## "light": { +## "emission": [ +## 0.0, +## 0.0, +## 0.0 +## ], +## "specular": [ +## 0.0, +## 0.0, +## 0.0 +## ], +## "illum": 0, +## "ior": 1.0, +## "shininess": 1.0, +## "normal_texname": "", +## "specular_texname": "", +## "transmittance": [ +## 0.0, +## 0.0, +## 0.0 +## ], +## "dissolve": 1.0, +## "ambient": [ +## 20.0, +## 20.0, +## 20.0 +## ], +## "diffuse": [ +## 1.0, +## 1.0, +## 1.0 +## ], +## "diffuse_texname": "", +## "ambient_texname": "" +## } +## } +##} -- cgit v1.2.3 From 93d72326144fdea4ec876ae0494a17106647f065 Mon Sep 17 00:00:00 2001 From: root Date: Sun, 16 Nov 2014 23:39:20 +0100 Subject: fix shapes / adding setup file --- python/cornell_box_output.json | 601 +++++++++++++++++++++++++++++++++++++++++ python/howto.py | 558 +------------------------------------- python/main.cpp | 67 +++-- 3 files changed, 637 insertions(+), 589 deletions(-) create mode 100644 python/cornell_box_output.json diff --git a/python/cornell_box_output.json b/python/cornell_box_output.json new file mode 100644 index 0000000..ba2e7e1 --- /dev/null +++ b/python/cornell_box_output.json @@ -0,0 +1,601 @@ +{ + "shapes": { + "ceiling": { + "texcoords": [], + "positions": [ + 556.0, + 548.7999877929688, + 0.0, + 556.0, + 548.7999877929688, + 559.2000122070312, + 0.0, + 548.7999877929688, + 559.2000122070312, + 0.0, + 548.7999877929688, + 0.0 + ], + "indicies": [ + 0.0, + 1.0, + 2.0, + 0.0, + 2.0, + 3.0 + ], + "material_ids": [ + 0.0, + 0.0 + ], + "normals": [] + }, + "floor": { + "texcoords": [], + "positions": [ + 552.7999877929688, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 559.2000122070312, + 549.5999755859375, + 0.0, + 559.2000122070312, + 290.0, + 0.0, + 114.0, + 240.0, + 0.0, + 272.0, + 82.0, + 0.0, + 225.0, + 130.0, + 0.0, + 65.0, + 472.0, + 0.0, + 406.0, + 314.0, + 0.0, + 456.0, + 265.0, + 0.0, + 296.0, + 423.0, + 0.0, + 247.0 + ], + "indicies": [ + 0.0, + 1.0, + 2.0, + 0.0, + 2.0, + 3.0, + 4.0, + 5.0, + 6.0, + 4.0, + 6.0, + 7.0, + 8.0, + 9.0, + 10.0, + 8.0, + 10.0, + 11.0 + ], + "material_ids": [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + "normals": [] + }, + "light": { + "texcoords": [], + "positions": [ + 343.0, + 548.0, + 227.0, + 343.0, + 548.0, + 332.0, + 213.0, + 548.0, + 332.0, + 213.0, + 548.0, + 227.0 + ], + "indicies": [ + 0.0, + 1.0, + 2.0, + 0.0, + 2.0, + 3.0 + ], + "material_ids": [ + 4.0, + 4.0 + ], + "normals": [] + }, + "green_wall": { + "texcoords": [], + "positions": [ + 0.0, + 0.0, + 559.2000122070312, + 0.0, + 0.0, + 0.0, + 0.0, + 548.7999877929688, + 0.0, + 0.0, + 548.7999877929688, + 559.2000122070312 + ], + "indicies": [ + 0.0, + 1.0, + 2.0, + 0.0, + 2.0, + 3.0 + ], + "material_ids": [ + 2.0, + 2.0 + ], + "normals": [] + }, + "back_wall": { + "texcoords": [], + "positions": [ + 549.5999755859375, + 0.0, + 559.2000122070312, + 0.0, + 0.0, + 559.2000122070312, + 0.0, + 548.7999877929688, + 559.2000122070312, + 556.0, + 548.7999877929688, + 559.2000122070312 + ], + "indicies": [ + 0.0, + 1.0, + 2.0, + 0.0, + 2.0, + 3.0 + ], + "material_ids": [ + 0.0, + 0.0 + ], + "normals": [] + }, + "short_block": { + "texcoords": [], + "positions": [ + 130.0, + 165.0, + 65.0, + 82.0, + 165.0, + 225.0, + 240.0, + 165.0, + 272.0, + 290.0, + 165.0, + 114.0, + 290.0, + 0.0, + 114.0, + 290.0, + 165.0, + 114.0, + 240.0, + 165.0, + 272.0, + 240.0, + 0.0, + 272.0, + 130.0, + 0.0, + 65.0, + 130.0, + 165.0, + 65.0, + 290.0, + 165.0, + 114.0, + 290.0, + 0.0, + 114.0, + 82.0, + 0.0, + 225.0, + 82.0, + 165.0, + 225.0, + 130.0, + 165.0, + 65.0, + 130.0, + 0.0, + 65.0, + 240.0, + 0.0, + 272.0, + 240.0, + 165.0, + 272.0, + 82.0, + 165.0, + 225.0, + 82.0, + 0.0, + 225.0 + ], + "indicies": [ + 0.0, + 1.0, + 2.0, + 0.0, + 2.0, + 3.0, + 4.0, + 5.0, + 6.0, + 4.0, + 6.0, + 7.0, + 8.0, + 9.0, + 10.0, + 8.0, + 10.0, + 11.0, + 12.0, + 13.0, + 14.0, + 12.0, + 14.0, + 15.0, + 16.0, + 17.0, + 18.0, + 16.0, + 18.0, + 19.0 + ], + "material_ids": [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + "normals": [] + }, + "tall_block": { + "texcoords": [], + "positions": [ + 423.0, + 0.0, + 247.0, + 423.0, + 330.0, + 247.0, + 472.0, + 330.0, + 406.0, + 472.0, + 0.0, + 406.0, + 472.0, + 0.0, + 406.0, + 472.0, + 330.0, + 406.0, + 314.0, + 330.0, + 456.0, + 314.0, + 0.0, + 456.0, + 314.0, + 0.0, + 456.0, + 314.0, + 330.0, + 456.0, + 265.0, + 330.0, + 296.0, + 265.0, + 0.0, + 296.0, + 265.0, + 0.0, + 296.0, + 265.0, + 330.0, + 296.0, + 423.0, + 330.0, + 247.0, + 423.0, + 0.0, + 247.0 + ], + "indicies": [ + 0.0, + 1.0, + 2.0, + 0.0, + 2.0, + 3.0, + 4.0, + 5.0, + 6.0, + 4.0, + 6.0, + 7.0, + 8.0, + 9.0, + 10.0, + 8.0, + 10.0, + 11.0, + 12.0, + 13.0, + 14.0, + 12.0, + 14.0, + 15.0 + ], + "material_ids": [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + "normals": [] + }, + "red_wall": { + "texcoords": [], + "positions": [ + 552.7999877929688, + 0.0, + 0.0, + 549.5999755859375, + 0.0, + 559.2000122070312, + 556.0, + 548.7999877929688, + 559.2000122070312, + 556.0, + 548.7999877929688, + 0.0 + ], + "indicies": [ + 0.0, + 1.0, + 2.0, + 0.0, + 2.0, + 3.0 + ], + "material_ids": [ + 1.0, + 1.0 + ], + "normals": [] + } + }, + "materials": { + "blue": { + "transmittance": [ + 0.0, + 0.0, + 0.0 + ], + "illum": 0, + "emission": [ + 0.0, + 0.0, + 0.0 + ], + "diffuse_texname": "", + "ambient_texname": "", + "normal_texname": "", + "shininess": 1.0, + "ior": 1.0, + "specular": [ + 0.0, + 0.0, + 0.0 + ], + "specular_texname": "", + "diffuse": [ + 0.0, + 0.0, + 1.0 + ], + "ambient": [ + 0.0, + 0.0, + 0.0 + ], + "dissolve": 1.0 + }, + "light": { + "transmittance": [ + 0.0, + 0.0, + 0.0 + ], + "illum": 0, + "emission": [ + 0.0, + 0.0, + 0.0 + ], + "diffuse_texname": "", + "ambient_texname": "", + "normal_texname": "", + "shininess": 1.0, + "ior": 1.0, + "specular": [ + 0.0, + 0.0, + 0.0 + ], + "specular_texname": "", + "diffuse": [ + 1.0, + 1.0, + 1.0 + ], + "ambient": [ + 20.0, + 20.0, + 20.0 + ], + "dissolve": 1.0 + }, + "white": { + "transmittance": [ + 0.0, + 0.0, + 0.0 + ], + "illum": 0, + "emission": [ + 0.0, + 0.0, + 0.0 + ], + "diffuse_texname": "", + "ambient_texname": "", + "normal_texname": "", + "shininess": 1.0, + "ior": 1.0, + "specular": [ + 0.0, + 0.0, + 0.0 + ], + "specular_texname": "", + "diffuse": [ + 1.0, + 1.0, + 1.0 + ], + "ambient": [ + 0.0, + 0.0, + 0.0 + ], + "dissolve": 1.0 + }, + "green": { + "transmittance": [ + 0.0, + 0.0, + 0.0 + ], + "illum": 0, + "emission": [ + 0.0, + 0.0, + 0.0 + ], + "diffuse_texname": "", + "ambient_texname": "", + "normal_texname": "", + "shininess": 1.0, + "ior": 1.0, + "specular": [ + 0.0, + 0.0, + 0.0 + ], + "specular_texname": "", + "diffuse": [ + 0.0, + 1.0, + 0.0 + ], + "ambient": [ + 0.0, + 0.0, + 0.0 + ], + "dissolve": 1.0 + }, + "red": { + "transmittance": [ + 0.0, + 0.0, + 0.0 + ], + "illum": 0, + "emission": [ + 0.0, + 0.0, + 0.0 + ], + "diffuse_texname": "", + "ambient_texname": "", + "normal_texname": "", + "shininess": 1.0, + "ior": 1.0, + "specular": [ + 0.0, + 0.0, + 0.0 + ], + "specular_texname": "", + "diffuse": [ + 1.0, + 0.0, + 0.0 + ], + "ambient": [ + 0.0, + 0.0, + 0.0 + ], + "dissolve": 1.0 + } + } +} diff --git a/python/howto.py b/python/howto.py index 099a1af..c526821 100644 --- a/python/howto.py +++ b/python/howto.py @@ -1,563 +1,11 @@ import tinyobjloader as tol import json -model = tol.LoadObj("cube.obj") +model = tol.LoadObj("cornell_box.obj") #print(model["shapes"], model["materials"]) print( json.dumps(model, indent=4) ) -#EXAMPLE OUTPUT +#see cornell_box_output.json + -##{ -## "shapes": { -## "left": [ -## [ -## 2.0, -## 2.0, -## 2.0, -## 2.0, -## 2.0, -## 2.0, -## 2.0, -## 2.0, -## 2.0, -## 2.0 -## ], -## [ -## 2.0, -## 2.0, -## 2.0, -## 2.0, -## 2.0, -## 2.0, -## 2.0, -## 2.0, -## 2.0, -## 2.0 -## ], -## [ -## 2.0, -## 2.0, -## 2.0, -## 2.0, -## 2.0, -## 2.0, -## 2.0, -## 2.0, -## 2.0, -## 2.0 -## ], -## [ -## 2.0, -## 2.0, -## 2.0, -## 2.0, -## 2.0, -## 2.0, -## 2.0, -## 2.0, -## 2.0, -## 2.0 -## ], -## [ -## 2.0, -## 2.0, -## 2.0, -## 2.0, -## 2.0, -## 2.0, -## 2.0, -## 2.0, -## 2.0, -## 2.0 -## ] -## ], -## "bottom": [ -## [ -## 0.0, -## 0.0, -## 0.0, -## 0.0, -## 0.0, -## 0.0, -## 0.0, -## 0.0, -## 0.0, -## 0.0 -## ], -## [ -## 0.0, -## 0.0, -## 0.0, -## 0.0, -## 0.0, -## 0.0, -## 0.0, -## 0.0, -## 0.0, -## 0.0 -## ], -## [ -## 0.0, -## 0.0, -## 0.0, -## 0.0, -## 0.0, -## 0.0, -## 0.0, -## 0.0, -## 0.0, -## 0.0 -## ], -## [ -## 0.0, -## 0.0, -## 0.0, -## 0.0, -## 0.0, -## 0.0, -## 0.0, -## 0.0, -## 0.0, -## 0.0 -## ], -## [ -## 0.0, -## 0.0, -## 0.0, -## 0.0, -## 0.0, -## 0.0, -## 0.0, -## 0.0, -## 0.0, -## 0.0 -## ] -## ], -## "right": [ -## [ -## 1.0, -## 1.0, -## 1.0, -## 1.0, -## 1.0, -## 1.0, -## 1.0, -## 1.0, -## 1.0, -## 1.0 -## ], -## [ -## 1.0, -## 1.0, -## 1.0, -## 1.0, -## 1.0, -## 1.0, -## 1.0, -## 1.0, -## 1.0, -## 1.0 -## ], -## [ -## 1.0, -## 1.0, -## 1.0, -## 1.0, -## 1.0, -## 1.0, -## 1.0, -## 1.0, -## 1.0, -## 1.0 -## ], -## [ -## 1.0, -## 1.0, -## 1.0, -## 1.0, -## 1.0, -## 1.0, -## 1.0, -## 1.0, -## 1.0, -## 1.0 -## ], -## [ -## 1.0, -## 1.0, -## 1.0, -## 1.0, -## 1.0, -## 1.0, -## 1.0, -## 1.0, -## 1.0, -## 1.0 -## ] -## ], -## "front": [ -## [ -## 0.0, -## 0.0, -## 0.0, -## 0.0, -## 0.0, -## 0.0, -## 0.0, -## 0.0, -## 0.0, -## 0.0 -## ], -## [ -## 0.0, -## 0.0, -## 0.0, -## 0.0, -## 0.0, -## 0.0, -## 0.0, -## 0.0, -## 0.0, -## 0.0 -## ], -## [ -## 0.0, -## 0.0, -## 0.0, -## 0.0, -## 0.0, -## 0.0, -## 0.0, -## 0.0, -## 0.0, -## 0.0 -## ], -## [ -## 0.0, -## 0.0, -## 0.0, -## 0.0, -## 0.0, -## 0.0, -## 0.0, -## 0.0, -## 0.0, -## 0.0 -## ], -## [ -## 0.0, -## 0.0, -## 0.0, -## 0.0, -## 0.0, -## 0.0, -## 0.0, -## 0.0, -## 0.0, -## 0.0 -## ] -## ], -## "top": [ -## [ -## 0.0, -## 0.0, -## 0.0, -## 0.0, -## 0.0, -## 0.0, -## 0.0, -## 0.0, -## 0.0, -## 0.0 -## ], -## [ -## 0.0, -## 0.0, -## 0.0, -## 0.0, -## 0.0, -## 0.0, -## 0.0, -## 0.0, -## 0.0, -## 0.0 -## ], -## [ -## 0.0, -## 0.0, -## 0.0, -## 0.0, -## 0.0, -## 0.0, -## 0.0, -## 0.0, -## 0.0, -## 0.0 -## ], -## [ -## 0.0, -## 0.0, -## 0.0, -## 0.0, -## 0.0, -## 0.0, -## 0.0, -## 0.0, -## 0.0, -## 0.0 -## ], -## [ -## 0.0, -## 0.0, -## 0.0, -## 0.0, -## 0.0, -## 0.0, -## 0.0, -## 0.0, -## 0.0, -## 0.0 -## ] -## ], -## "back": [ -## [ -## 0.0, -## 0.0, -## 0.0, -## 0.0, -## 0.0, -## 0.0, -## 0.0, -## 0.0, -## 0.0, -## 0.0 -## ], -## [ -## 0.0, -## 0.0, -## 0.0, -## 0.0, -## 0.0, -## 0.0, -## 0.0, -## 0.0, -## 0.0, -## 0.0 -## ], -## [ -## 0.0, -## 0.0, -## 0.0, -## 0.0, -## 0.0, -## 0.0, -## 0.0, -## 0.0, -## 0.0, -## 0.0 -## ], -## [ -## 0.0, -## 0.0, -## 0.0, -## 0.0, -## 0.0, -## 0.0, -## 0.0, -## 0.0, -## 0.0, -## 0.0 -## ], -## [ -## 0.0, -## 0.0, -## 0.0, -## 0.0, -## 0.0, -## 0.0, -## 0.0, -## 0.0, -## 0.0, -## 0.0 -## ] -## ] -## }, -## "materials": { -## "green": { -## "emission": [ -## 0.0, -## 0.0, -## 0.0 -## ], -## "specular": [ -## 0.0, -## 0.0, -## 0.0 -## ], -## "illum": 0, -## "ior": 1.0, -## "shininess": 1.0, -## "normal_texname": "", -## "specular_texname": "", -## "transmittance": [ -## 0.0, -## 0.0, -## 0.0 -## ], -## "dissolve": 1.0, -## "ambient": [ -## 0.0, -## 0.0, -## 0.0 -## ], -## "diffuse": [ -## 0.0, -## 1.0, -## 0.0 -## ], -## "diffuse_texname": "", -## "ambient_texname": "" -## }, -## "blue": { -## "emission": [ -## 0.0, -## 0.0, -## 0.0 -## ], -## "specular": [ -## 0.0, -## 0.0, -## 0.0 -## ], -## "illum": 0, -## "ior": 1.0, -## "shininess": 1.0, -## "normal_texname": "", -## "specular_texname": "", -## "transmittance": [ -## 0.0, -## 0.0, -## 0.0 -## ], -## "dissolve": 1.0, -## "ambient": [ -## 0.0, -## 0.0, -## 0.0 -## ], -## "diffuse": [ -## 0.0, -## 0.0, -## 1.0 -## ], -## "diffuse_texname": "", -## "ambient_texname": "" -## }, -## "red": { -## "emission": [ -## 0.0, -## 0.0, -## 0.0 -## ], -## "specular": [ -## 0.0, -## 0.0, -## 0.0 -## ], -## "illum": 0, -## "ior": 1.0, -## "shininess": 1.0, -## "normal_texname": "", -## "specular_texname": "", -## "transmittance": [ -## 0.0, -## 0.0, -## 0.0 -## ], -## "dissolve": 1.0, -## "ambient": [ -## 0.0, -## 0.0, -## 0.0 -## ], -## "diffuse": [ -## 1.0, -## 0.0, -## 0.0 -## ], -## "diffuse_texname": "", -## "ambient_texname": "" -## }, -## "white": { -## "emission": [ -## 0.0, -## 0.0, -## 0.0 -## ], -## "specular": [ -## 0.0, -## 0.0, -## 0.0 -## ], -## "illum": 0, -## "ior": 1.0, -## "shininess": 1.0, -## "normal_texname": "", -## "specular_texname": "", -## "transmittance": [ -## 0.0, -## 0.0, -## 0.0 -## ], -## "dissolve": 1.0, -## "ambient": [ -## 0.0, -## 0.0, -## 0.0 -## ], -## "diffuse": [ -## 1.0, -## 1.0, -## 1.0 -## ], -## "diffuse_texname": "", -## "ambient_texname": "" -## }, -## "light": { -## "emission": [ -## 0.0, -## 0.0, -## 0.0 -## ], -## "specular": [ -## 0.0, -## 0.0, -## 0.0 -## ], -## "illum": 0, -## "ior": 1.0, -## "shininess": 1.0, -## "normal_texname": "", -## "specular_texname": "", -## "transmittance": [ -## 0.0, -## 0.0, -## 0.0 -## ], -## "dissolve": 1.0, -## "ambient": [ -## 20.0, -## 20.0, -## 20.0 -## ], -## "diffuse": [ -## 1.0, -## 1.0, -## 1.0 -## ], -## "diffuse_texname": "", -## "ambient_texname": "" -## } -## } -##} diff --git a/python/main.cpp b/python/main.cpp index 636a24d..8999563 100644 --- a/python/main.cpp +++ b/python/main.cpp @@ -5,6 +5,7 @@ // model = tol.LoadObj(name) // print(model["shapes"]) // print(model["materials"] + #include #include #include "../tiny_obj_loader.h" @@ -30,7 +31,7 @@ extern "C" static PyObject* pyLoadObj(PyObject* self, PyObject* args) { - PyObject *rtntpl, *pyshapes, *pymaterials; + PyObject *rtndict, *pyshapes, *pymaterials; char const* filename; std::vector shapes; std::vector materials; @@ -42,48 +43,46 @@ pyLoadObj(PyObject* self, PyObject* args) pyshapes = PyDict_New(); pymaterials = PyDict_New(); - rtntpl = PyDict_New(); + rtndict = PyDict_New(); for (std::vector::iterator shape = shapes.begin() ; shape != shapes.end(); shape++) { - PyObject *meshobj; - PyObject *positions, *normals, *texcoords, *indices, *material_ids; - - meshobj = PyTuple_New(5); - positions = PyList_New(0); - normals = PyList_New(0); - texcoords = PyList_New(0); - indices = PyList_New(0); - material_ids = PyList_New(0); + PyObject *meshobj, *current; + char *current_name; + vectd vect; + meshobj = PyDict_New(); tinyobj::mesh_t cm = (*shape).mesh; - for (int i = 0; i <= 4 ; i++ ) - { - PyObject *current; - vectd vect; - switch (i) - { - case 0: current = positions; - vect = vectd(cm.positions.begin(), cm.positions.end()); - case 1: current = normals; - vect = vectd(cm.normals.begin(), cm.normals.end()); - case 2: current = texcoords; - vect = vectd(cm.texcoords.begin(), cm.texcoords.end()); - case 3: current = indices; - vect = vectd(cm.indices.begin(), cm.indices.end()); - case 4: current = material_ids; - vect = vectd(cm.material_ids.begin(), cm.material_ids.end()); - } - - for (std::vector::iterator it = vect.begin() ; + for (int i = 0; i <= 4; i++ ) + { + current = PyList_New(0); + + if (i == 0){ + current_name = "positions"; + vect = vectd(cm.positions.begin(), cm.positions.end()); } + else if (i==1){ + current_name = "normals"; + vect = vectd(cm.normals.begin(), cm.normals.end()); } + else if (i == 2) { + current_name = "texcoords"; + vect = vectd(cm.texcoords.begin(), cm.texcoords.end()); } + else if (i==3) { + current_name = "indicies"; + vect = vectd(cm.indices.begin(), cm.indices.end()); } + else if (i == 4) { + current_name = "material_ids"; + vect = vectd(cm.material_ids.begin(), cm.material_ids.end()); } + + for (vectd::iterator it = vect.begin() ; it != vect.end(); it++) { PyList_Insert(current, it - vect.begin(), PyFloat_FromDouble(*it)); } - PyTuple_SetItem(meshobj, i, current); + PyDict_SetItemString(meshobj, current_name, current); + } PyDict_SetItemString(pyshapes, (*shape).name.c_str(), meshobj); @@ -111,10 +110,10 @@ pyLoadObj(PyObject* self, PyObject* args) PyDict_SetItemString(pymaterials, (*mat).name.c_str(), matobj); } - PyDict_SetItemString(rtntpl, "shapes", pyshapes); - PyDict_SetItemString(rtntpl, "materials", pymaterials); + PyDict_SetItemString(rtndict, "shapes", pyshapes); + PyDict_SetItemString(rtndict, "materials", pymaterials); - return rtntpl; + return rtndict; } -- cgit v1.2.3 From 9587ad9aee5b5801ab1b3eb244bbc06ed38b891a Mon Sep 17 00:00:00 2001 From: root Date: Sun, 16 Nov 2014 23:53:21 +0100 Subject: cleaning code.. --- python/main.cpp | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/python/main.cpp b/python/main.cpp index 8999563..81f7476 100644 --- a/python/main.cpp +++ b/python/main.cpp @@ -31,8 +31,13 @@ extern "C" static PyObject* pyLoadObj(PyObject* self, PyObject* args) { - PyObject *rtndict, *pyshapes, *pymaterials; + PyObject *rtndict, *pyshapes, *pymaterials, + *current, *meshobj; + char const* filename; + char *current_name; + vectd vect; + std::vector shapes; std::vector materials; @@ -48,10 +53,6 @@ pyLoadObj(PyObject* self, PyObject* args) for (std::vector::iterator shape = shapes.begin() ; shape != shapes.end(); shape++) { - PyObject *meshobj, *current; - char *current_name; - vectd vect; - meshobj = PyDict_New(); tinyobj::mesh_t cm = (*shape).mesh; @@ -62,13 +63,13 @@ pyLoadObj(PyObject* self, PyObject* args) if (i == 0){ current_name = "positions"; vect = vectd(cm.positions.begin(), cm.positions.end()); } - else if (i==1){ + else if (i == 1){ current_name = "normals"; vect = vectd(cm.normals.begin(), cm.normals.end()); } else if (i == 2) { current_name = "texcoords"; vect = vectd(cm.texcoords.begin(), cm.texcoords.end()); } - else if (i==3) { + else if (i == 3) { current_name = "indicies"; vect = vectd(cm.indices.begin(), cm.indices.end()); } else if (i == 4) { -- cgit v1.2.3 From aabdad4bc48067ce0f3a6535257b06bd22c0238a Mon Sep 17 00:00:00 2001 From: root Date: Mon, 17 Nov 2014 22:06:25 +0100 Subject: changed to switch structure --- python/main.cpp | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/python/main.cpp b/python/main.cpp index 81f7476..b2c6e96 100644 --- a/python/main.cpp +++ b/python/main.cpp @@ -60,23 +60,27 @@ pyLoadObj(PyObject* self, PyObject* args) { current = PyList_New(0); - if (i == 0){ + switch(i) { + + case 0: current_name = "positions"; - vect = vectd(cm.positions.begin(), cm.positions.end()); } - else if (i == 1){ + vect = vectd(cm.positions.begin(), cm.positions.end()); break; + case 1: current_name = "normals"; - vect = vectd(cm.normals.begin(), cm.normals.end()); } - else if (i == 2) { + vect = vectd(cm.normals.begin(), cm.normals.end()); break; + case 2: current_name = "texcoords"; - vect = vectd(cm.texcoords.begin(), cm.texcoords.end()); } - else if (i == 3) { + vect = vectd(cm.texcoords.begin(), cm.texcoords.end()); break; + case 3: current_name = "indicies"; - vect = vectd(cm.indices.begin(), cm.indices.end()); } - else if (i == 4) { + vect = vectd(cm.indices.begin(), cm.indices.end()); break; + case 4: current_name = "material_ids"; - vect = vectd(cm.material_ids.begin(), cm.material_ids.end()); } - - for (vectd::iterator it = vect.begin() ; + vect = vectd(cm.material_ids.begin(), cm.material_ids.end()); break; + + } + + for (vectd::iterator it = vect.begin() ; it != vect.end(); it++) { PyList_Insert(current, it - vect.begin(), PyFloat_FromDouble(*it)); -- cgit v1.2.3 From c5ed61f358b0c088e5cc1c68e38197383adb2ef9 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Sun, 30 Nov 2014 11:59:55 +0900 Subject: Add link to IBLBaker. --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 033bbe6..38d6587 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,7 @@ TinyObjLoader is successfully used in ... * pbrt-v2 https://https://github.com/mmp/pbrt-v2 * OpenGL game engine development http://swarminglogic.com/jotting/2013_10_gamedev01 * mallie https://lighttransport.github.io/mallie +* IBLBaker (Image Based Lighting Baker). http://www.derkreature.com/iblbaker/ * Your project here! Features @@ -58,7 +59,7 @@ Polygon is converted into triangle. TODO ---- -* Support quad polygon and some tags for OpenSubdiv http://graphics.pixar.com/opensubdiv/ +- [ ] Support quad polygon and some tags for OpenSubdiv http://graphics.pixar.com/opensubdiv/ License ------- -- cgit v1.2.3 From b5352a642b2d347b4afd0b9d5043ee3a2cd023e5 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Sat, 17 Jan 2015 22:27:20 +0900 Subject: Add drone.yml --- .drone.yml | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .drone.yml diff --git a/.drone.yml b/.drone.yml new file mode 100644 index 0000000..33e98b2 --- /dev/null +++ b/.drone.yml @@ -0,0 +1,7 @@ +image: bradrydzewski/base +script: + - make +notify: + email: + recipients: + - syoyo@lighttransport.com -- cgit v1.2.3 From 4779593e440104dd9d35d50c413bf83bb2038900 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Sat, 17 Jan 2015 22:30:02 +0900 Subject: Update drone.yml. --- .drone.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.drone.yml b/.drone.yml index 33e98b2..1724277 100644 --- a/.drone.yml +++ b/.drone.yml @@ -1,5 +1,8 @@ image: bradrydzewski/base script: + - curl -L -O premake4 https://github.com/syoyo/orebuildenv/blob/master/build/linux/bin/premake4?raw=true + - chmod +x ./premake4 + - ./premake4 gmake - make notify: email: -- cgit v1.2.3 From 276c7e151e53b9171ad98e257b71965c6f28a3f1 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Sat, 17 Jan 2015 22:37:31 +0900 Subject: Update drone.yml Add Zup conversion in obj_writer. --- .drone.yml | 2 +- examples/obj_sticher/obj_sticher.cc | 4 +++- examples/obj_sticher/obj_writer.cc | 40 +++++++++++++++++++++++++++---------- examples/obj_sticher/obj_writer.h | 2 +- 4 files changed, 34 insertions(+), 14 deletions(-) diff --git a/.drone.yml b/.drone.yml index 1724277..7fe6321 100644 --- a/.drone.yml +++ b/.drone.yml @@ -1,6 +1,6 @@ image: bradrydzewski/base script: - - curl -L -O premake4 https://github.com/syoyo/orebuildenv/blob/master/build/linux/bin/premake4?raw=true + - curl -L -o premake4 https://github.com/syoyo/orebuildenv/blob/master/build/linux/bin/premake4?raw=true - chmod +x ./premake4 - ./premake4 gmake - make diff --git a/examples/obj_sticher/obj_sticher.cc b/examples/obj_sticher/obj_sticher.cc index 1833216..8ec7a28 100644 --- a/examples/obj_sticher/obj_sticher.cc +++ b/examples/obj_sticher/obj_sticher.cc @@ -81,6 +81,7 @@ main( std::vector shapes; std::vector materials; shapes.resize(num_objfiles); + materials.resize(num_objfiles); for (int i = 0; i < num_objfiles; i++) { std::cout << "Loading " << argv[i+1] << " ... " << std::flush; @@ -98,7 +99,8 @@ main( std::vector out_material; StichObjs(out_shape, out_material, shapes, materials); - bool ret = WriteObj(out_filename, out_shape, out_material); + bool coordTransform = true; + bool ret = WriteObj(out_filename, out_shape, out_material, coordTransform); assert(ret); return 0; diff --git a/examples/obj_sticher/obj_writer.cc b/examples/obj_sticher/obj_writer.cc index bb12457..2c8bd7b 100644 --- a/examples/obj_sticher/obj_writer.cc +++ b/examples/obj_sticher/obj_writer.cc @@ -38,7 +38,7 @@ bool WriteMat(const std::string& filename, const std::vector& shapes, const std::vector& materials) { +bool WriteObj(const std::string& filename, const std::vector& shapes, const std::vector& materials, bool coordTransform) { FILE* fp = fopen(filename.c_str(), "w"); if (!fp) { fprintf(stderr, "Failed to open file [ %s ] for write.\n", filename.c_str()); @@ -74,10 +74,17 @@ bool WriteObj(const std::string& filename, const std::vector& for (size_t k = 0; k < shapes[i].mesh.indices.size() / 3; k++) { for (int j = 0; j < 3; j++) { int idx = shapes[i].mesh.indices[3*k+j]; - fprintf(fp, "v %f %f %f\n", - shapes[i].mesh.positions[3*idx+0], - shapes[i].mesh.positions[3*idx+1], - shapes[i].mesh.positions[3*idx+2]); + if (coordTransform) { + fprintf(fp, "v %f %f %f\n", + shapes[i].mesh.positions[3*idx+0], + shapes[i].mesh.positions[3*idx+2], + -shapes[i].mesh.positions[3*idx+1]); + } else { + fprintf(fp, "v %f %f %f\n", + shapes[i].mesh.positions[3*idx+0], + shapes[i].mesh.positions[3*idx+1], + shapes[i].mesh.positions[3*idx+2]); + } } } @@ -86,10 +93,17 @@ bool WriteObj(const std::string& filename, const std::vector& for (size_t k = 0; k < shapes[i].mesh.indices.size() / 3; k++) { for (int j = 0; j < 3; j++) { int idx = shapes[i].mesh.indices[3*k+j]; - fprintf(fp, "vn %f %f %f\n", - shapes[i].mesh.normals[3*idx+0], - shapes[i].mesh.normals[3*idx+1], - shapes[i].mesh.normals[3*idx+2]); + if (coordTransform) { + fprintf(fp, "vn %f %f %f\n", + shapes[i].mesh.normals[3*idx+0], + shapes[i].mesh.normals[3*idx+2], + -shapes[i].mesh.normals[3*idx+1]); + } else { + fprintf(fp, "vn %f %f %f\n", + shapes[i].mesh.normals[3*idx+0], + shapes[i].mesh.normals[3*idx+1], + shapes[i].mesh.normals[3*idx+2]); + } } } } @@ -119,6 +133,10 @@ bool WriteObj(const std::string& filename, const std::vector& int v1 = (3*k + 1) + 1 + v_offset; int v2 = (3*k + 2) + 1 + v_offset; + int vt0 = (3*k + 0) + 1 + vt_offset; + int vt1 = (3*k + 1) + 1 + vt_offset; + int vt2 = (3*k + 2) + 1 + vt_offset; + int material_id = shapes[i].mesh.material_ids[k]; if (material_id != prev_material_id) { std::string material_name = materials[material_id].name; @@ -128,7 +146,7 @@ bool WriteObj(const std::string& filename, const std::vector& if (has_vn && has_vt) { fprintf(fp, "f %d/%d/%d %d/%d/%d %d/%d/%d\n", - v0, v0, v0, v1, v1, v1, v2, v2, v2); + v0, vt0, v0, v1, vt1, v1, v2, vt2, v2); } else if (has_vn && !has_vt) { fprintf(fp, "f %d//%d %d//%d %d//%d\n", v0, v0, v1, v1, v2, v2); } else if (!has_vn && has_vt) { @@ -141,7 +159,7 @@ bool WriteObj(const std::string& filename, const std::vector& v_offset += shapes[i].mesh.indices.size(); //vn_offset += shapes[i].mesh.normals.size() / 3; - //vt_offset += shapes[i].mesh.texcoords.size() / 2; + vt_offset += shapes[i].mesh.texcoords.size() / 2; } diff --git a/examples/obj_sticher/obj_writer.h b/examples/obj_sticher/obj_writer.h index 00cd792..bb367b6 100644 --- a/examples/obj_sticher/obj_writer.h +++ b/examples/obj_sticher/obj_writer.h @@ -3,7 +3,7 @@ #include "../../tiny_obj_loader.h" -extern bool WriteObj(const std::string& filename, const std::vector& shapes, const std::vector& materials); +extern bool WriteObj(const std::string& filename, const std::vector& shapes, const std::vector& materials, bool coordTransform = false); #endif // __OBJ_WRITER_H__ -- cgit v1.2.3 From fae5b03e7c7cfa7392609e39893348a27f9a0335 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Sat, 17 Jan 2015 23:04:15 +0900 Subject: Update README. --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 38d6587..08bf934 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,7 @@ TinyObjLoader is successfully used in ... * OpenGL game engine development http://swarminglogic.com/jotting/2013_10_gamedev01 * mallie https://lighttransport.github.io/mallie * IBLBaker (Image Based Lighting Baker). http://www.derkreature.com/iblbaker/ +* Stanford CS148 http://web.stanford.edu/class/cs148/assignments/assignment3.pdf * Your project here! Features -- cgit v1.2.3 From 41db59cde5e80147f13828eb0187ae00b590a093 Mon Sep 17 00:00:00 2001 From: Maurice Laveaux Date: Mon, 26 Jan 2015 17:32:56 +0100 Subject: Fixed cmake warning that targets shouldn't be named test. --- CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 94ec330..ad1b8df 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -26,8 +26,8 @@ add_library(tinyobjloader ${tinyobjloader-Source} ) -add_executable(test ${tinyobjloader-Test-Source}) -target_link_libraries(test tinyobjloader) +add_executable(test_loader ${tinyobjloader-Test-Source}) +target_link_libraries(test_loader tinyobjloader) add_executable(obj_sticher ${tinyobjloader-examples-objsticher}) target_link_libraries(obj_sticher tinyobjloader) -- cgit v1.2.3 From 0a500b77e78335ce324cdc213e602f2ce55109f5 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Mon, 2 Feb 2015 17:43:08 +0900 Subject: Small update for README. --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 08bf934..93d9880 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,8 @@ http://syoyo.github.io/tinyobjloader/ Tiny but poweful single file wavefront obj loader written in C++. No dependency except for C++ STL. It can parse 10M over polygons with moderate memory and time. -Good for embedding .obj loader to your (global illumination) renderer ;-) +`tinyobjloader` is good for embedding .obj loader to your (global illumination) renderer ;-) + What's new ---------- @@ -50,7 +51,7 @@ Features * Texcoord * Normal * Material - * Unknown material attributes are treated as key-value. + * Unknown material attributes are treated as key-value(value is string). Notes ----- -- cgit v1.2.3 From 672f252195722635843962b4c1a191f864fe3852 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Sat, 7 Feb 2015 00:01:37 +0900 Subject: Fix per-face material. --- cornell_box_multimaterial.obj | 146 ++++++++++++++++++++++++++++++++++++++++++ tiny_obj_loader.cc | 9 ++- 2 files changed, 153 insertions(+), 2 deletions(-) create mode 100644 cornell_box_multimaterial.obj diff --git a/cornell_box_multimaterial.obj b/cornell_box_multimaterial.obj new file mode 100644 index 0000000..68093be --- /dev/null +++ b/cornell_box_multimaterial.obj @@ -0,0 +1,146 @@ +# cornell_box.obj and cornell_box.mtl are grabbed from Intel's embree project. +# original cornell box data + # comment + +# empty line including some space + + +mtllib cornell_box.mtl + +o floor +usemtl white +v 552.8 0.0 0.0 +v 0.0 0.0 0.0 +v 0.0 0.0 559.2 +v 549.6 0.0 559.2 + +v 130.0 0.0 65.0 +v 82.0 0.0 225.0 +v 240.0 0.0 272.0 +v 290.0 0.0 114.0 + +v 423.0 0.0 247.0 +v 265.0 0.0 296.0 +v 314.0 0.0 456.0 +v 472.0 0.0 406.0 + +f 1 2 3 4 +f 8 7 6 5 +f 12 11 10 9 + +o light +usemtl light +v 343.0 548.0 227.0 +v 343.0 548.0 332.0 +v 213.0 548.0 332.0 +v 213.0 548.0 227.0 +f -4 -3 -2 -1 + +o ceiling +usemtl white +v 556.0 548.8 0.0 +v 556.0 548.8 559.2 +v 0.0 548.8 559.2 +v 0.0 548.8 0.0 +f -4 -3 -2 -1 + +o back_wall +usemtl white +v 549.6 0.0 559.2 +v 0.0 0.0 559.2 +v 0.0 548.8 559.2 +v 556.0 548.8 559.2 +f -4 -3 -2 -1 + +o front_wall +usemtl blue +v 549.6 0.0 0 +v 0.0 0.0 0 +v 0.0 548.8 0 +v 556.0 548.8 0 +#f -1 -2 -3 -4 + +o green_wall +usemtl green +v 0.0 0.0 559.2 +v 0.0 0.0 0.0 +v 0.0 548.8 0.0 +v 0.0 548.8 559.2 +f -4 -3 -2 -1 + +o red_wall +usemtl red +v 552.8 0.0 0.0 +v 549.6 0.0 559.2 +v 556.0 548.8 559.2 +v 556.0 548.8 0.0 +f -4 -3 -2 -1 + +o short_block +usemtl white + +v 130.0 165.0 65.0 +v 82.0 165.0 225.0 +v 240.0 165.0 272.0 +v 290.0 165.0 114.0 +f -4 -3 -2 -1 + +v 290.0 0.0 114.0 +v 290.0 165.0 114.0 +v 240.0 165.0 272.0 +v 240.0 0.0 272.0 +f -4 -3 -2 -1 + +v 130.0 0.0 65.0 +v 130.0 165.0 65.0 +v 290.0 165.0 114.0 +v 290.0 0.0 114.0 +f -4 -3 -2 -1 + +v 82.0 0.0 225.0 +v 82.0 165.0 225.0 +v 130.0 165.0 65.0 +v 130.0 0.0 65.0 +f -4 -3 -2 -1 + +v 240.0 0.0 272.0 +v 240.0 165.0 272.0 +v 82.0 165.0 225.0 +v 82.0 0.0 225.0 +f -4 -3 -2 -1 + +o tall_block +usemtl white + +v 423.0 330.0 247.0 +v 265.0 330.0 296.0 +v 314.0 330.0 456.0 +v 472.0 330.0 406.0 +f -4 -3 -2 -1 + +usemtl white +v 423.0 0.0 247.0 +v 423.0 330.0 247.0 +v 472.0 330.0 406.0 +v 472.0 0.0 406.0 +f -4 -3 -2 -1 + +v 472.0 0.0 406.0 +v 472.0 330.0 406.0 +v 314.0 330.0 456.0 +v 314.0 0.0 456.0 +f -4 -3 -2 -1 +usemtl green + +v 314.0 0.0 456.0 +v 314.0 330.0 456.0 +v 265.0 330.0 296.0 +v 265.0 0.0 296.0 +f -4 -3 -2 -1 + +v 265.0 0.0 296.0 +v 265.0 330.0 296.0 +v 423.0 330.0 247.0 +v 423.0 0.0 247.0 +f -4 -3 -2 -1 + diff --git a/tiny_obj_loader.cc b/tiny_obj_loader.cc index b73e766..2b7bd9c 100755 --- a/tiny_obj_loader.cc +++ b/tiny_obj_loader.cc @@ -1,10 +1,11 @@ // -// Copyright 2012-2013, Syoyo Fujita. +// Copyright 2012-2015, Syoyo Fujita. // // Licensed under 2-clause BSD liecense. // // +// version 0.9.8: Fix multi-materials(per-face material ID). // version 0.9.7: Support multi-materials(per-face material ID) per object/group. // version 0.9.6: Support Ni(index of refraction) mtl parameter. // Parse transmittance material parameter correctly. @@ -620,7 +621,11 @@ std::string LoadObj( token += 7; sscanf(token, "%s", namebuf); - faceGroup.clear(); + // Create face group per material. + bool ret = exportFaceGroupToShape(shape, vertexCache, v, vn, vt, faceGroup, material, name, true); + if (ret) { + faceGroup.clear(); + } if (material_map.find(namebuf) != material_map.end()) { material = material_map[namebuf]; -- cgit v1.2.3 From a67a60d19fd423449c9e2e6ba6ca2071c1f26d72 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Sat, 7 Feb 2015 00:04:17 +0900 Subject: Update README. --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 93d9880..7c3bb0a 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,7 @@ Tiny but poweful single file wavefront obj loader written in C++. No dependency What's new ---------- +* Feb 06, 2015 : Fix parsing multi-material object * Sep 14, 2014 : Add support for multi-material per object/group. Thanks Mykhailo! * Mar 17, 2014 : Fixed trim newline bugs. Thanks ardneran! * Apr 29, 2014 : Add API to read .obj from std::istream. Good for reading compressed .obj or connecting to procedural primitive generator. Thanks burnse! -- cgit v1.2.3 From f28d2eef887f392c9ce46e18594e8021cdfac25b Mon Sep 17 00:00:00 2001 From: CoolerExtreme Date: Thu, 12 Feb 2015 23:31:06 +0530 Subject: Fix parseString ? and slight change to fixIndex function parseString seemed to not increment token after it used strspn to get the length of the whitespace characters at the beginning of token. So strcspn called right after that would return 0 and the created string would be an empty string. Seems to have been working so far since it gets passed strings that don't begin with whitespace characters. --- tiny_obj_loader.cc | 20 ++++++-------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/tiny_obj_loader.cc b/tiny_obj_loader.cc index 2b7bd9c..dde2bc7 100755 --- a/tiny_obj_loader.cc +++ b/tiny_obj_loader.cc @@ -67,26 +67,18 @@ static inline bool isNewLine(const char c) { // Make index zero-base, and also support relative index. static inline int fixIndex(int idx, int n) { - int i; - - if (idx > 0) { - i = idx - 1; - } else if (idx == 0) { - i = 0; - } else { // negative value = relative - i = n + idx; - } - return i; + 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; - int b = strspn(token, " \t"); + token += strspn(token, " \t"); int e = strcspn(token, " \t\r"); - s = std::string(&token[b], &token[e]); - - token += (e - b); + s = std::string(&token, &token[e]); + token += e; return s; } -- cgit v1.2.3 From 011e1b3ebdc3e16f3f0a8d8ee8f537a067f06e91 Mon Sep 17 00:00:00 2001 From: CoolerExtreme Date: Fri, 13 Feb 2015 06:37:37 +0530 Subject: Slight typo --- tiny_obj_loader.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tiny_obj_loader.cc b/tiny_obj_loader.cc index dde2bc7..a974d6e 100755 --- a/tiny_obj_loader.cc +++ b/tiny_obj_loader.cc @@ -77,7 +77,7 @@ static inline std::string parseString(const char*& token) std::string s; token += strspn(token, " \t"); int e = strcspn(token, " \t\r"); - s = std::string(&token, &token[e]); + s = std::string(token, &token[e]); token += e; return s; } -- cgit v1.2.3 From 99792758354e2f97231def04a38f020f56d841e9 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Sun, 15 Feb 2015 16:51:38 +0900 Subject: Format source code. --- tiny_obj_loader.cc | 343 +++++++++++++++++++++++++---------------------------- tiny_obj_loader.h | 115 ++++++++---------- 2 files changed, 213 insertions(+), 245 deletions(-) mode change 100755 => 100644 tiny_obj_loader.cc diff --git a/tiny_obj_loader.cc b/tiny_obj_loader.cc old mode 100755 new mode 100644 index 2b7bd9c..05c5aad --- a/tiny_obj_loader.cc +++ b/tiny_obj_loader.cc @@ -1,12 +1,13 @@ // // Copyright 2012-2015, Syoyo Fujita. -// +// // Licensed under 2-clause BSD liecense. // // // version 0.9.8: Fix multi-materials(per-face material ID). -// version 0.9.7: Support multi-materials(per-face material ID) per object/group. +// version 0.9.7: Support multi-materials(per-face material ID) per +// object/group. // version 0.9.6: Support Ni(index of refraction) mtl parameter. // Parse transmittance material parameter correctly. // version 0.9.5: Parse multiple group name. @@ -18,7 +19,6 @@ // version 0.9.0: Initial // - #include #include #include @@ -35,17 +35,19 @@ namespace tinyobj { 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) {}; - + 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); +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); return false; } @@ -56,17 +58,14 @@ struct obj_shape { std::vector vt; }; -static inline bool isSpace(const char c) { - return (c == ' ') || (c == '\t'); -} +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'); } -// Make index zero-base, and also support relative index. -static inline int fixIndex(int idx, int n) -{ +// Make index zero-base, and also support relative index. +static inline int fixIndex(int idx, int n) { int i; if (idx > 0) { @@ -79,8 +78,7 @@ static inline int fixIndex(int idx, int n) return i; } -static inline std::string parseString(const char*& token) -{ +static inline std::string parseString(const char *&token) { std::string s; int b = strspn(token, " \t"); int e = strcspn(token, " \t\r"); @@ -90,89 +88,73 @@ static inline std::string parseString(const char*& token) return s; } -static inline int parseInt(const char*& token) -{ +static inline int parseInt(const char *&token) { token += strspn(token, " \t"); int i = atoi(token); token += strcspn(token, " \t\r"); return i; } -static inline float parseFloat(const char*& token) -{ +static inline float parseFloat(const char *&token) { token += strspn(token, " \t"); float f = (float)atof(token); token += strcspn(token, " \t\r"); return f; } -static inline void parseFloat2( - float& x, float& y, - const char*& token) -{ +static inline void parseFloat2(float &x, float &y, const char *&token) { x = parseFloat(token); y = parseFloat(token); } -static inline void parseFloat3( - float& x, float& y, float& z, - const char*& token) -{ +static inline void parseFloat3(float &x, float &y, float &z, + 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); - - 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/j/k or i/j - vi.vt_idx = fixIndex(atoi(token), vtsize); - token += strcspn(token, "/ \t\r"); - if (token[0] != '/') { - return vi; - } +static vertex_index parseTriple(const char *&token, int vsize, int vnsize, + int vtsize) { + vertex_index vi(-1); + + vi.v_idx = fixIndex(atoi(token), vsize); + token += strcspn(token, "/ \t\r"); + if (token[0] != '/') { + return vi; + } + token++; - // i/j/k - token++; // skip '/' + // i//k + if (token[0] == '/') { + token++; vi.vn_idx = fixIndex(atoi(token), vnsize); token += strcspn(token, "/ \t\r"); - return vi; + 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; } 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) -{ +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()) { @@ -180,21 +162,21 @@ updateVertex( 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]); + 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]); + texcoords.push_back(in_texcoords[2 * i.vt_idx + 0]); + texcoords.push_back(in_texcoords[2 * i.vt_idx + 1]); } unsigned int idx = positions.size() / 3 - 1; @@ -203,13 +185,13 @@ updateVertex( return idx; } -void InitMaterial(material_t& material) { +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 ++) { + for (int i = 0; i < 3; i++) { material.ambient[i] = 0.f; material.diffuse[i] = 0.f; material.specular[i] = 0.f; @@ -223,25 +205,20 @@ void InitMaterial(material_t& material) { 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) -{ +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; } // Flatten vertices and indices for (size_t i = 0; i < faceGroup.size(); i++) { - const std::vector& face = faceGroup[i]; + const std::vector &face = faceGroup[i]; vertex_index i0 = face[0]; vertex_index i1(-1); @@ -254,9 +231,15 @@ 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); + 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); @@ -264,30 +247,26 @@ exportFaceGroupToShape( shape.mesh.material_ids.push_back(material_id); } - } shape.name = name; if (clearCache) - vertexCache.clear(); + vertexCache.clear(); return true; - } -std::string LoadMtl ( - std::map& material_map, - std::vector& materials, - std::istream& inStream) -{ +std::string LoadMtl(std::map &material_map, + std::vector &materials, + std::istream &inStream) { material_map.clear(); std::stringstream err; material_t material; - - int maxchars = 8192; // Alloc enough size. - std::vector buf(maxchars); // Alloc enough size. + + int maxchars = 8192; // Alloc enough size. + std::vector buf(maxchars); // Alloc enough size. while (inStream.peek() != -1) { inStream.getline(&buf[0], maxchars); @@ -295,10 +274,12 @@ std::string LoadMtl ( // Trim newline '\r\n' or '\n' if (linebuf.size() > 0) { - if (linebuf[linebuf.size()-1] == '\n') linebuf.erase(linebuf.size()-1); + 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); + if (linebuf[linebuf.size() - 1] == '\r') + linebuf.erase(linebuf.size() - 1); } // Skip if empty line. @@ -307,21 +288,23 @@ std::string LoadMtl ( } // Skip leading space. - const char* token = linebuf.c_str(); + const char *token = linebuf.c_str(); token += strspn(token, " \t"); assert(token); - if (token[0] == '\0') continue; // empty line - - if (token[0] == '#') continue; // comment line - + if (token[0] == '\0') + continue; // empty 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, materials.size())); - materials.push_back(material); + if (!material.name.empty()) { + material_map.insert( + std::pair(material.name, materials.size())); + materials.push_back(material); } // initial temporary material @@ -334,7 +317,7 @@ std::string LoadMtl ( material.name = namebuf; continue; } - + // ambient if (token[0] == 'K' && token[1] == 'a' && isSpace((token[2]))) { token += 2; @@ -345,7 +328,7 @@ std::string LoadMtl ( material.ambient[2] = b; continue; } - + // diffuse if (token[0] == 'K' && token[1] == 'd' && isSpace((token[2]))) { token += 2; @@ -356,7 +339,7 @@ std::string LoadMtl ( material.diffuse[2] = b; continue; } - + // specular if (token[0] == 'K' && token[1] == 's' && isSpace((token[2]))) { token += 2; @@ -367,7 +350,7 @@ std::string LoadMtl ( material.specular[2] = b; continue; } - + // transmittance if (token[0] == 'K' && token[1] == 't' && isSpace((token[2]))) { token += 2; @@ -387,7 +370,7 @@ std::string LoadMtl ( } // emission - if(token[0] == 'K' && token[1] == 'e' && isSpace(token[2])) { + if (token[0] == 'K' && token[1] == 'e' && isSpace(token[2])) { token += 2; float r, g, b; parseFloat3(r, g, b, token); @@ -398,7 +381,7 @@ std::string LoadMtl ( } // shininess - if(token[0] == 'N' && token[1] == 's' && isSpace(token[2])) { + if (token[0] == 'N' && token[1] == 's' && isSpace(token[2])) { token += 2; material.shininess = parseFloat(token); continue; @@ -452,29 +435,29 @@ std::string LoadMtl ( } // unknown parameter - const char* _space = strchr(token, ' '); - if(!_space) { + const char *_space = strchr(token, ' '); + if (!_space) { _space = strchr(token, '\t'); } - if(_space) { + if (_space) { int len = _space - token; std::string key(token, len); std::string value = _space + 1; - material.unknown_parameter.insert(std::pair(key, value)); + material.unknown_parameter.insert( + std::pair(key, value)); } } // flush last material. - material_map.insert(std::pair(material.name, materials.size())); + material_map.insert( + std::pair(material.name, materials.size())); materials.push_back(material); return err.str(); } -std::string MaterialFileReader::operator() ( - const std::string& matId, - std::vector& materials, - std::map& matMap) -{ +std::string MaterialFileReader::operator()(const std::string &matId, + std::vector &materials, + std::map &matMap) { std::string filepath; if (!m_mtlBasePath.empty()) { @@ -487,13 +470,9 @@ std::string MaterialFileReader::operator() ( return LoadMtl(matMap, materials, matIStream); } -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 &materials, // [output] + const char *filename, const char *mtl_basepath) { shapes.clear(); @@ -509,34 +488,31 @@ LoadObj( if (mtl_basepath) { basePath = mtl_basepath; } - MaterialFileReader matFileReader( basePath ); - + MaterialFileReader matFileReader(basePath); + return LoadObj(shapes, materials, ifs, matFileReader); } -std::string LoadObj( - std::vector& shapes, - std::vector& materials, // [output] - std::istream& inStream, - MaterialReader& readMatFn) -{ +std::string LoadObj(std::vector &shapes, + 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::vector> faceGroup; std::string name; // material std::map material_map; std::map vertexCache; - int material = -1; + int material = -1; shape_t shape; - int maxchars = 8192; // Alloc enough size. - std::vector buf(maxchars); // Alloc enough size. + int maxchars = 8192; // Alloc enough size. + std::vector buf(maxchars); // Alloc enough size. while (inStream.peek() != -1) { inStream.getline(&buf[0], maxchars); @@ -544,10 +520,12 @@ std::string LoadObj( // Trim newline '\r\n' or '\n' if (linebuf.size() > 0) { - if (linebuf[linebuf.size()-1] == '\n') linebuf.erase(linebuf.size()-1); + 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); + if (linebuf[linebuf.size() - 1] == '\r') + linebuf.erase(linebuf.size() - 1); } // Skip if empty line. @@ -556,13 +534,15 @@ std::string LoadObj( } // Skip leading space. - const char* token = linebuf.c_str(); + const char *token = linebuf.c_str(); token += strspn(token, " \t"); assert(token); - if (token[0] == '\0') continue; // empty line - - if (token[0] == '#') continue; // comment line + if (token[0] == '\0') + continue; // empty line + + if (token[0] == '#') + continue; // comment line // vertex if (token[0] == 'v' && isSpace((token[1]))) { @@ -603,14 +583,15 @@ std::string LoadObj( std::vector face; while (!isNewLine(token[0])) { - vertex_index vi = parseTriple(token, v.size() / 3, vn.size() / 3, vt.size() / 2); + vertex_index vi = + parseTriple(token, v.size() / 3, vn.size() / 3, vt.size() / 2); face.push_back(vi); int n = strspn(token, " \t\r"); token += n; } faceGroup.push_back(face); - + continue; } @@ -622,7 +603,8 @@ std::string LoadObj( sscanf(token, "%s", namebuf); // Create face group per material. - bool ret = exportFaceGroupToShape(shape, vertexCache, v, vn, vt, faceGroup, material, name, true); + bool ret = exportFaceGroupToShape(shape, vertexCache, v, vn, vt, + faceGroup, material, name, true); if (ret) { faceGroup.clear(); } @@ -635,7 +617,6 @@ std::string LoadObj( } continue; - } // load mtl @@ -643,13 +624,13 @@ std::string LoadObj( char namebuf[4096]; token += 7; sscanf(token, "%s", namebuf); - + std::string err_mtl = readMatFn(namebuf, materials, material_map); if (!err_mtl.empty()) { - faceGroup.clear(); // for safety + faceGroup.clear(); // for safety return err_mtl; } - + continue; } @@ -657,14 +638,15 @@ std::string LoadObj( if (token[0] == 'g' && isSpace((token[1]))) { // flush previous face group. - bool ret = exportFaceGroupToShape(shape, vertexCache, v, vn, vt, faceGroup, material, name, true); + bool ret = exportFaceGroupToShape(shape, vertexCache, v, vn, vt, + faceGroup, material, name, true); if (ret) { shapes.push_back(shape); } shape = shape_t(); - //material = -1; + // material = -1; faceGroup.clear(); std::vector names; @@ -690,12 +672,13 @@ std::string LoadObj( if (token[0] == 'o' && isSpace((token[1]))) { // flush previous face group. - bool ret = exportFaceGroupToShape(shape, vertexCache, v, vn, vt, faceGroup, material, name, true); + bool ret = exportFaceGroupToShape(shape, vertexCache, v, vn, vt, + faceGroup, material, name, true); if (ret) { shapes.push_back(shape); } - //material = -1; + // material = -1; faceGroup.clear(); shape = shape_t(); @@ -705,21 +688,19 @@ std::string LoadObj( sscanf(token, "%s", namebuf); name = std::string(namebuf); - continue; } // Ignore unknown command. } - bool ret = exportFaceGroupToShape(shape, vertexCache, v, vn, vt, faceGroup, material, name, true); + bool ret = exportFaceGroupToShape(shape, vertexCache, v, vn, vt, faceGroup, + material, name, true); if (ret) { shapes.push_back(shape); } - faceGroup.clear(); // for safety + faceGroup.clear(); // for safety return err.str(); } - - } diff --git a/tiny_obj_loader.h b/tiny_obj_loader.h index a58d7be..dbd5f70 100644 --- a/tiny_obj_loader.h +++ b/tiny_obj_loader.h @@ -12,68 +12,61 @@ namespace tinyobj { -typedef struct -{ - std::string name; +typedef struct { + 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 +typedef struct { + 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; +typedef struct { + std::string name; + mesh_t mesh; } shape_t; -class MaterialReader -{ +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); +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); - private: - std::string m_mtlBasePath; +private: + std::string m_mtlBasePath; }; /// Loads .obj from a file. @@ -81,27 +74,21 @@ class MaterialFileReader: /// 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 &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 &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::string LoadMtl(std::map &material_map, + std::vector &materials, std::istream &inStream); } -#endif // _TINY_OBJ_LOADER_H +#endif // _TINY_OBJ_LOADER_H -- cgit v1.2.3 From 1390f7f707c6cd76e11f97ba2b182091f2a7f7d5 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Sun, 15 Feb 2015 17:05:57 +0900 Subject: Fix compilation. --- tiny_obj_loader.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tiny_obj_loader.cc b/tiny_obj_loader.cc index 05c5aad..6cf3f59 100644 --- a/tiny_obj_loader.cc +++ b/tiny_obj_loader.cc @@ -210,7 +210,7 @@ static bool exportFaceGroupToShape( const std::vector &in_positions, const std::vector &in_normals, const std::vector &in_texcoords, - const std::vector> &faceGroup, + const std::vector > &faceGroup, const int material_id, const std::string &name, bool clearCache) { if (faceGroup.empty()) { return false; @@ -501,7 +501,7 @@ std::string LoadObj(std::vector &shapes, std::vector v; std::vector vn; std::vector vt; - std::vector> faceGroup; + std::vector > faceGroup; std::string name; // material -- cgit v1.2.3 From 2cceb532148df9a5c375e5e02d0e28268162b46f Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Mon, 16 Feb 2015 00:47:23 +0900 Subject: Update drone.yml. --- .drone.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.drone.yml b/.drone.yml index 7fe6321..e64ba07 100644 --- a/.drone.yml +++ b/.drone.yml @@ -4,6 +4,8 @@ script: - chmod +x ./premake4 - ./premake4 gmake - make +services: + - redis notify: email: recipients: -- cgit v1.2.3 From f0fdaa307dd180076f5862a6585a0eb02bcf334a Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Wed, 18 Feb 2015 12:48:50 +0900 Subject: slight change to fixIndex --- tiny_obj_loader.cc | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/tiny_obj_loader.cc b/tiny_obj_loader.cc index 992662c..6ba4336 100644 --- a/tiny_obj_loader.cc +++ b/tiny_obj_loader.cc @@ -66,16 +66,9 @@ static inline bool isNewLine(const char c) { // Make index zero-base, and also support relative index. static inline int fixIndex(int idx, int n) { - int i; - - if (idx > 0) { - i = idx - 1; - } else if (idx == 0) { - i = 0; - } else { // negative value = relative - i = n + idx; - } - return i; + 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) { -- cgit v1.2.3 From f020169c261bf6ed44ebb9a73d1d4de295aa68e4 Mon Sep 17 00:00:00 2001 From: Simon Otter Date: Mon, 23 Feb 2015 23:34:16 +0100 Subject: Created a bunch of tests for the parser, and a spec. --- float_parser_tests.cc | 55 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 float_parser_tests.cc diff --git a/float_parser_tests.cc b/float_parser_tests.cc new file mode 100644 index 0000000..e8526ec --- /dev/null +++ b/float_parser_tests.cc @@ -0,0 +1,55 @@ +#include +#include + +// Tries to parse a floating point number located at s. +// +// Parses the following EBNF grammar: +// sign = "+" | "-" ; +// END = ? anything not in digit ? +// digit = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" ; +// integer = [sign] , digit , {digit} ; +// decimal = integer , ["." , {digit}] ; +// float = ( decimal , END ) | ( decimal , ("E" | "e") , decimal , END ) ; +// +// Valid strings are for example: +// -0 +3.1417e+2 -0.E-3 1.0324 -1.41 11e2 +// +// If the parsing is a success, result is set to the parsed value and true +// is returned. +// +// The function is greedy, it will parse until it encounters a null-byte or +// a non-conforming character is encountered. +// +// The following situations triggers a failure: +// - parse failure. +// - underflow/overflow. +// +bool tryParseFloat(const char *s, double *result) +{ + return false; +} + +void testParsing(const char *input, bool expectedRes, double expectedValue) +{ + double val = 0.0; + bool res = tryParseFloat(input, &val); + if (res != expectedRes || val != expectedValue) + { + printf("% 20s failed, returned %d and value % 10.4f.\n", input, res, val); + } +} + +int main(int argc, char const *argv[]) +{ + testParsing("0", true, 0.0); + testParsing("-0", true, 0.0); + testParsing("+0", true, 0.0); + testParsing("1", true, 1.0); + testParsing("+1", true, 1.0); + testParsing("-1", true, -1.0); + testParsing("-1.08", true, -1.08); + testParsing("100.0823blabla", true, 100.0823); + testParsing("34.E-2\04", true, 34.0e-2); + testParsing("+34.23E2\02", true, 34.23E2); + return 0; +} \ No newline at end of file -- cgit v1.2.3 From 32414c27b4d4d1819dfefb1fd00bde52aab18673 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Fri, 27 Feb 2015 12:07:02 +0900 Subject: Update TODO. --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 7c3bb0a..b72b911 100644 --- a/README.md +++ b/README.md @@ -63,6 +63,7 @@ TODO ---- - [ ] Support quad polygon and some tags for OpenSubdiv http://graphics.pixar.com/opensubdiv/ +- [ ] Add rubust parser to replace `atof()` #28 License ------- -- cgit v1.2.3 From 8a384a057bf0d7d49e71292556681f46a9cf57ef Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Fri, 27 Feb 2015 12:45:44 +0900 Subject: Update drone.yml --- .drone.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.drone.yml b/.drone.yml index e64ba07..d5c3b4b 100644 --- a/.drone.yml +++ b/.drone.yml @@ -1,11 +1,9 @@ -image: bradrydzewski/base +image: syoyo/centos-gcc48 script: - curl -L -o premake4 https://github.com/syoyo/orebuildenv/blob/master/build/linux/bin/premake4?raw=true - chmod +x ./premake4 - ./premake4 gmake - make -services: - - redis notify: email: recipients: -- cgit v1.2.3 From 32dcf7d5356fb01b65a7df60022da045a98e13d1 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Fri, 27 Feb 2015 15:54:04 +0900 Subject: Use syoyo/ubu-dev image. --- .drone.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.drone.yml b/.drone.yml index d5c3b4b..971557f 100644 --- a/.drone.yml +++ b/.drone.yml @@ -1,4 +1,4 @@ -image: syoyo/centos-gcc48 +image: syoyo/ubu-dev script: - curl -L -o premake4 https://github.com/syoyo/orebuildenv/blob/master/build/linux/bin/premake4?raw=true - chmod +x ./premake4 -- cgit v1.2.3 From 28005f9cdf5894ec2b1dba1b19f497b79d6cdab2 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Sat, 28 Feb 2015 00:47:13 +0900 Subject: Update copyright year. --- tiny_obj_loader.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tiny_obj_loader.h b/tiny_obj_loader.h index dbd5f70..512f32b 100644 --- a/tiny_obj_loader.h +++ b/tiny_obj_loader.h @@ -1,5 +1,5 @@ // -// Copyright 2012-2013, Syoyo Fujita. +// Copyright 2012-2015, Syoyo Fujita. // // Licensed under 2-clause BSD liecense. // -- cgit v1.2.3 From daaec1c9aa3448b5c1155bd44a4fcafabb2c133e Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Sat, 28 Feb 2015 01:09:18 +0900 Subject: Small update. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b72b911..ede414b 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,7 @@ Features * Texcoord * Normal * Material - * Unknown material attributes are treated as key-value(value is string). + * Unknown material attributes are returned as key-value(value is string) map. Notes ----- -- cgit v1.2.3 From 9d7012673e35128f18734e79c8c4ad3a59adef1e Mon Sep 17 00:00:00 2001 From: Joe Hermaszewski Date: Fri, 27 Feb 2015 19:58:43 +0000 Subject: Fix a small typo --- tiny_obj_loader.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tiny_obj_loader.cc b/tiny_obj_loader.cc index 6ba4336..fb78074 100644 --- a/tiny_obj_loader.cc +++ b/tiny_obj_loader.cc @@ -650,7 +650,7 @@ std::string LoadObj(std::vector &shapes, assert(names.size() > 0); - // names[0] must be 'g', so skipt 0th element. + // names[0] must be 'g', so skip the 0th element. if (names.size() > 1) { name = names[1]; } else { -- cgit v1.2.3 From 4ea1cf0b77a69ccc113a2979db19bcbd32f7ea36 Mon Sep 17 00:00:00 2001 From: Simon Otter Date: Tue, 3 Mar 2015 01:37:28 +0100 Subject: Implemented a parser and updated tiny_obj_loader.cc to use it unless a define is set. --- float_parser_tests.cc | 200 +++++++++++++++++++++++++++++++++++++++++++++++--- tiny_obj_loader.cc | 153 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 341 insertions(+), 12 deletions(-) diff --git a/float_parser_tests.cc b/float_parser_tests.cc index e8526ec..420adea 100644 --- a/float_parser_tests.cc +++ b/float_parser_tests.cc @@ -1,41 +1,159 @@ #include #include +#include +#include +#include // Tries to parse a floating point number located at s. // +// s_end should be a location in the string where reading should absolutely +// stop. For example at the end of the string, to prevent buffer overflows. +// // Parses the following EBNF grammar: // sign = "+" | "-" ; // END = ? anything not in digit ? // digit = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" ; // integer = [sign] , digit , {digit} ; -// decimal = integer , ["." , {digit}] ; -// float = ( decimal , END ) | ( decimal , ("E" | "e") , decimal , END ) ; +// decimal = integer , ["." , integer] ; +// float = ( decimal , END ) | ( decimal , ("E" | "e") , integer , END ) ; // // Valid strings are for example: -// -0 +3.1417e+2 -0.E-3 1.0324 -1.41 11e2 +// -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 // is returned. // -// The function is greedy, it will parse until it encounters a null-byte or -// a non-conforming character is encountered. +// The function is greedy and will parse until any of the following happens: +// - a non-conforming character is encountered. +// - s_end is reached. // // The following situations triggers a failure: +// - s >= s_end. // - parse failure. -// - underflow/overflow. // -bool tryParseFloat(const char *s, double *result) +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 + // 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 + // exponent to be in base 2. + int exponent = 0; + + // NOTE: THESE MUST BE DECLARED HERE SINCE WE ARE NOT ALLOWED + // TO JUMP OVER DEFINITIONS. + char sign = '+'; + char exp_sign = '+'; + char const *curr = s; + + // 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; + + /* + BEGIN PARSING. + */ + + // Find out what sign we've got. + if (*curr == '+' || *curr == '-') + { + sign = *curr; + curr++; + } + else if (isdigit(*curr)) { /* Pass through. */ } + else + { + goto fail; + } + + // Read the integer part. + while ((end_not_reached = (curr != s_end)) && isdigit(*curr)) + { + mantissa *= 10; + mantissa += static_cast(*curr - 0x30); + curr++; read++; + } + + // We must make sure we actually got something. + if (read == 0) + goto fail; + // We allow numbers of form "#", "###" etc. + if (!end_not_reached) + goto assemble; + + // Read the decimal part. + if (*curr == '.') + { + curr++; + read = 1; + 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, -read); + read++; curr++; + } + } + else if (*curr == 'e' || *curr == 'E') {} + else + { + goto assemble; + } + + if (!end_not_reached) + goto assemble; + + // Read the exponent part. + 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 == '-')) + { + exp_sign = *curr; + curr++; + } + 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)) + { + exponent *= 10; + exponent += static_cast(*curr - 0x30); + curr++; read++; + } + exponent *= (exp_sign == '+'? 1 : -1); + if (read == 0) + goto fail; + } + +assemble: + *result = (sign == '+'? 1 : -1) * ldexp(mantissa * pow(5, exponent), exponent); + return true; +fail: return false; } void testParsing(const char *input, bool expectedRes, double expectedValue) { double val = 0.0; - bool res = tryParseFloat(input, &val); - if (res != expectedRes || val != expectedValue) + bool res = tryParseDouble(input, input + strlen(input), &val); + if (res != expectedRes || fabs(val - expectedValue) > 0.000001) { - printf("% 20s failed, returned %d and value % 10.4f.\n", input, res, val); + printf("% 20s failed, returned %d and value % 10.5f.\n\t\tExpected value was % 10.5f\n------\n", input, res, val, expectedValue); } } @@ -49,7 +167,65 @@ int main(int argc, char const *argv[]) testParsing("-1", true, -1.0); testParsing("-1.08", true, -1.08); testParsing("100.0823blabla", true, 100.0823); - testParsing("34.E-2\04", true, 34.0e-2); - testParsing("+34.23E2\02", true, 34.23E2); + testParsing("34.0E-2\04", true, 34.0e-2); + testParsing("+34.23E2\032", true, 34.23E2); + testParsing("10.023", true, 10.023); + testParsing("1.0e+23", true, 1.e+23); + testParsing("10e+23", true, 10e+23); + testParsing("20301.000", true, 20301.000); + testParsing("-41044.000", true, -41044.000); + testParsing("-93558.000", true, -93558.000); + testParsing("-79620.000", true, -79620.000); + testParsing("5257.000", true, 5257.000); + testParsing("-7145.000", true, -7145.000); + testParsing("-77314.000", true, -77314.000); + testParsing("-27131.000", true, -27131.000); + testParsing("52744.000", true, 52744.000); + testParsing("48106.000", true, 48106.000); + testParsing("42939.000", true, 42939.000); + testParsing("41187.000", true, 41187.000); + testParsing("70851.000", true, 70851.000); + testParsing("-90431.000", true, -90431.000); + testParsing("-13564.000", true, -13564.000); + testParsing("27150.000", true, 27150.000); + testParsing("71992.000", true, 71992.000); + testParsing("-42845.000", true, -42845.000); + testParsing("24806.000", true, 24806.000); + testParsing("30753.000", true, 30753.000); + testParsing("1.658500e+04", true, 1.658500e+04); + testParsing("9.572500e+04", true, 9.572500e+04); + testParsing("-5.888600e+04", true, -5.888600e+04); + testParsing("-2.998000e+04", true, -2.998000e+04); + testParsing("-4.842700e+04", true, -4.842700e+04); + testParsing("9.575400e+04", true, 9.575400e+04); + testParsing("-8.311500e+04", true, -8.311500e+04); + testParsing("-3.770800e+04", true, -3.770800e+04); + testParsing("-3.141600e+04", true, -3.141600e+04); + testParsing("-4.673700e+04", true, -4.673700e+04); + testParsing("-5.112400e+04", true, -5.112400e+04); + testParsing("7.111000e+03", true, 7.111000e+03); + testParsing("-3.727000e+03", true, -3.727000e+03); + testParsing("6.940700e+04", true, 6.940700e+04); + testParsing("-3.635100e+04", true, -3.635100e+04); + testParsing("2.762100e+04", true, 2.762100e+04); + testParsing("-1.512600e+04", true, -1.512600e+04); + testParsing("3.338000e+03", true, 3.338000e+03); + testParsing("7.598100e+04", true, 7.598100e+04); + testParsing("-8.198400e+04", true, -8.198400e+04); + testParsing("-", false, 0.0); + testParsing(" +", false, 0.0); + testParsing("", false, 0.0); + testParsing(".232", false, 0.0); + testParsing(".232E2", false, 0.0); + testParsing(".232", false, 0.0); + testParsing(".", false, 0.0); + testParsing(".E", false, 0.0); + testParsing("233.E", false, 0.0); + testParsing("233.323E-", false, 0.0); + testParsing("233.323E+", false, 0.0); + + + + return 0; } \ No newline at end of file diff --git a/tiny_obj_loader.cc b/tiny_obj_loader.cc index 6ba4336..8dd5422 100644 --- a/tiny_obj_loader.cc +++ b/tiny_obj_loader.cc @@ -22,6 +22,7 @@ #include #include #include +#include #include #include @@ -87,13 +88,165 @@ static inline int parseInt(const char *&token) { 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 +// stop. For example at the end of the string, to prevent buffer overflows. +// +// Parses the following EBNF grammar: +// sign = "+" | "-" ; +// END = ? anything not in digit ? +// digit = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" ; +// integer = [sign] , digit , {digit} ; +// decimal = integer , ["." , integer] ; +// float = ( decimal , END ) | ( decimal , ("E" | "e") , integer , END ) ; +// +// 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 +// is returned. +// +// The function is greedy and will parse until any of the following happens: +// - a non-conforming character is encountered. +// - s_end is reached. +// +// 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) + { + 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 + // 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 + // exponent to be in base 2. + int exponent = 0; + + // NOTE: THESE MUST BE DECLARED HERE SINCE WE ARE NOT ALLOWED + // TO JUMP OVER DEFINITIONS. + char sign = '+'; + char exp_sign = '+'; + char const *curr = s; + + // 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; + + /* + BEGIN PARSING. + */ + + // Find out what sign we've got. + if (*curr == '+' || *curr == '-') + { + sign = *curr; + curr++; + } + else if (isdigit(*curr)) { /* Pass through. */ } + else + { + goto fail; + } + + // Read the integer part. + while ((end_not_reached = (curr != s_end)) && isdigit(*curr)) + { + mantissa *= 10; + mantissa += static_cast(*curr - 0x30); + curr++; read++; + } + + // We must make sure we actually got something. + if (read == 0) + goto fail; + // We allow numbers of form "#", "###" etc. + if (!end_not_reached) + goto assemble; + + // Read the decimal part. + if (*curr == '.') + { + curr++; + read = 1; + 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, -read); + read++; curr++; + } + } + else if (*curr == 'e' || *curr == 'E') {} + else + { + goto assemble; + } + + if (!end_not_reached) + goto assemble; + + // Read the exponent part. + 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 == '-')) + { + exp_sign = *curr; + curr++; + } + 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)) + { + exponent *= 10; + exponent += static_cast(*curr - 0x30); + curr++; read++; + } + exponent *= (exp_sign == '+'? 1 : -1); + if (read == 0) + goto fail; + } + +assemble: + *result = (sign == '+'? 1 : -1) * ldexp(mantissa * pow(5, exponent), exponent); + return true; +fail: + return false; +} static inline float parseFloat(const char *&token) { token += strspn(token, " \t"); +#ifdef TINY_OBJ_LOADER_OLD_FLOAT_PARSER 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; +#endif return f; } + static inline void parseFloat2(float &x, float &y, const char *&token) { x = parseFloat(token); y = parseFloat(token); -- cgit v1.2.3 From 5615af531640a6a5b0004430b1497775c6030c35 Mon Sep 17 00:00:00 2001 From: Simon Otter Date: Tue, 3 Mar 2015 01:39:38 +0100 Subject: Removed stray parser test file. --- float_parser_tests.cc | 231 -------------------------------------------------- 1 file changed, 231 deletions(-) delete mode 100644 float_parser_tests.cc diff --git a/float_parser_tests.cc b/float_parser_tests.cc deleted file mode 100644 index 420adea..0000000 --- a/float_parser_tests.cc +++ /dev/null @@ -1,231 +0,0 @@ -#include -#include -#include -#include -#include - -// Tries to parse a floating point number located at s. -// -// s_end should be a location in the string where reading should absolutely -// stop. For example at the end of the string, to prevent buffer overflows. -// -// Parses the following EBNF grammar: -// sign = "+" | "-" ; -// END = ? anything not in digit ? -// digit = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" ; -// integer = [sign] , digit , {digit} ; -// decimal = integer , ["." , integer] ; -// float = ( decimal , END ) | ( decimal , ("E" | "e") , integer , END ) ; -// -// 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 -// is returned. -// -// The function is greedy and will parse until any of the following happens: -// - a non-conforming character is encountered. -// - s_end is reached. -// -// The following situations triggers a failure: -// - s >= s_end. -// - parse failure. -// -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 - // 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 - // exponent to be in base 2. - int exponent = 0; - - // NOTE: THESE MUST BE DECLARED HERE SINCE WE ARE NOT ALLOWED - // TO JUMP OVER DEFINITIONS. - char sign = '+'; - char exp_sign = '+'; - char const *curr = s; - - // 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; - - /* - BEGIN PARSING. - */ - - // Find out what sign we've got. - if (*curr == '+' || *curr == '-') - { - sign = *curr; - curr++; - } - else if (isdigit(*curr)) { /* Pass through. */ } - else - { - goto fail; - } - - // Read the integer part. - while ((end_not_reached = (curr != s_end)) && isdigit(*curr)) - { - mantissa *= 10; - mantissa += static_cast(*curr - 0x30); - curr++; read++; - } - - // We must make sure we actually got something. - if (read == 0) - goto fail; - // We allow numbers of form "#", "###" etc. - if (!end_not_reached) - goto assemble; - - // Read the decimal part. - if (*curr == '.') - { - curr++; - read = 1; - 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, -read); - read++; curr++; - } - } - else if (*curr == 'e' || *curr == 'E') {} - else - { - goto assemble; - } - - if (!end_not_reached) - goto assemble; - - // Read the exponent part. - 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 == '-')) - { - exp_sign = *curr; - curr++; - } - 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)) - { - exponent *= 10; - exponent += static_cast(*curr - 0x30); - curr++; read++; - } - exponent *= (exp_sign == '+'? 1 : -1); - if (read == 0) - goto fail; - } - -assemble: - *result = (sign == '+'? 1 : -1) * ldexp(mantissa * pow(5, exponent), exponent); - return true; -fail: - return false; -} - -void testParsing(const char *input, bool expectedRes, double expectedValue) -{ - double val = 0.0; - bool res = tryParseDouble(input, input + strlen(input), &val); - if (res != expectedRes || fabs(val - expectedValue) > 0.000001) - { - printf("% 20s failed, returned %d and value % 10.5f.\n\t\tExpected value was % 10.5f\n------\n", input, res, val, expectedValue); - } -} - -int main(int argc, char const *argv[]) -{ - testParsing("0", true, 0.0); - testParsing("-0", true, 0.0); - testParsing("+0", true, 0.0); - testParsing("1", true, 1.0); - testParsing("+1", true, 1.0); - testParsing("-1", true, -1.0); - testParsing("-1.08", true, -1.08); - testParsing("100.0823blabla", true, 100.0823); - testParsing("34.0E-2\04", true, 34.0e-2); - testParsing("+34.23E2\032", true, 34.23E2); - testParsing("10.023", true, 10.023); - testParsing("1.0e+23", true, 1.e+23); - testParsing("10e+23", true, 10e+23); - testParsing("20301.000", true, 20301.000); - testParsing("-41044.000", true, -41044.000); - testParsing("-93558.000", true, -93558.000); - testParsing("-79620.000", true, -79620.000); - testParsing("5257.000", true, 5257.000); - testParsing("-7145.000", true, -7145.000); - testParsing("-77314.000", true, -77314.000); - testParsing("-27131.000", true, -27131.000); - testParsing("52744.000", true, 52744.000); - testParsing("48106.000", true, 48106.000); - testParsing("42939.000", true, 42939.000); - testParsing("41187.000", true, 41187.000); - testParsing("70851.000", true, 70851.000); - testParsing("-90431.000", true, -90431.000); - testParsing("-13564.000", true, -13564.000); - testParsing("27150.000", true, 27150.000); - testParsing("71992.000", true, 71992.000); - testParsing("-42845.000", true, -42845.000); - testParsing("24806.000", true, 24806.000); - testParsing("30753.000", true, 30753.000); - testParsing("1.658500e+04", true, 1.658500e+04); - testParsing("9.572500e+04", true, 9.572500e+04); - testParsing("-5.888600e+04", true, -5.888600e+04); - testParsing("-2.998000e+04", true, -2.998000e+04); - testParsing("-4.842700e+04", true, -4.842700e+04); - testParsing("9.575400e+04", true, 9.575400e+04); - testParsing("-8.311500e+04", true, -8.311500e+04); - testParsing("-3.770800e+04", true, -3.770800e+04); - testParsing("-3.141600e+04", true, -3.141600e+04); - testParsing("-4.673700e+04", true, -4.673700e+04); - testParsing("-5.112400e+04", true, -5.112400e+04); - testParsing("7.111000e+03", true, 7.111000e+03); - testParsing("-3.727000e+03", true, -3.727000e+03); - testParsing("6.940700e+04", true, 6.940700e+04); - testParsing("-3.635100e+04", true, -3.635100e+04); - testParsing("2.762100e+04", true, 2.762100e+04); - testParsing("-1.512600e+04", true, -1.512600e+04); - testParsing("3.338000e+03", true, 3.338000e+03); - testParsing("7.598100e+04", true, 7.598100e+04); - testParsing("-8.198400e+04", true, -8.198400e+04); - testParsing("-", false, 0.0); - testParsing(" +", false, 0.0); - testParsing("", false, 0.0); - testParsing(".232", false, 0.0); - testParsing(".232E2", false, 0.0); - testParsing(".232", false, 0.0); - testParsing(".", false, 0.0); - testParsing(".E", false, 0.0); - testParsing("233.E", false, 0.0); - testParsing("233.323E-", false, 0.0); - testParsing("233.323E+", false, 0.0); - - - - - return 0; -} \ No newline at end of file -- cgit v1.2.3 From 8d300917a34fd5d94775b19e826277be601087f8 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Tue, 3 Mar 2015 13:16:56 +0900 Subject: Update README. Bump version 0.9.9. --- README.md | 1 + tiny_obj_loader.cc | 1 + 2 files changed, 2 insertions(+) diff --git a/README.md b/README.md index ede414b..7de4c3e 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,7 @@ Tiny but poweful single file wavefront obj loader written in C++. No dependency What's new ---------- +* Mar 03, 2015 : Replace atof() with hand-written parser for robust reading of numeric value. Thanks skurmedel! * Feb 06, 2015 : Fix parsing multi-material object * Sep 14, 2014 : Add support for multi-material per object/group. Thanks Mykhailo! * Mar 17, 2014 : Fixed trim newline bugs. Thanks ardneran! diff --git a/tiny_obj_loader.cc b/tiny_obj_loader.cc index 70ef1b1..9cd3fd2 100644 --- a/tiny_obj_loader.cc +++ b/tiny_obj_loader.cc @@ -5,6 +5,7 @@ // // +// version 0.9.9: Replace atof() with custom parser. // version 0.9.8: Fix multi-materials(per-face material ID). // version 0.9.7: Support multi-materials(per-face material ID) per // object/group. -- cgit v1.2.3 From ba5fde9fd59ce9b36b492ad6c6cfd4c82749d763 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Tue, 3 Mar 2015 13:22:56 +0900 Subject: Remove #28 TODO. --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 7de4c3e..4d194e8 100644 --- a/README.md +++ b/README.md @@ -64,7 +64,6 @@ TODO ---- - [ ] Support quad polygon and some tags for OpenSubdiv http://graphics.pixar.com/opensubdiv/ -- [ ] Add rubust parser to replace `atof()` #28 License ------- -- cgit v1.2.3 From 527000abd633f1ce838feae4a5b58526bcf9a4c9 Mon Sep 17 00:00:00 2001 From: Will Usher Date: Fri, 1 May 2015 17:32:25 -0600 Subject: Add support for referencing multiple mtllibs --- tiny_obj_loader.cc | 1 - 1 file changed, 1 deletion(-) diff --git a/tiny_obj_loader.cc b/tiny_obj_loader.cc index 9cd3fd2..8fe42c8 100644 --- a/tiny_obj_loader.cc +++ b/tiny_obj_loader.cc @@ -406,7 +406,6 @@ static bool exportFaceGroupToShape( std::string LoadMtl(std::map &material_map, std::vector &materials, std::istream &inStream) { - material_map.clear(); std::stringstream err; material_t material; -- cgit v1.2.3 From d828e7521d03884ee8251e3c418a522a4775e8e8 Mon Sep 17 00:00:00 2001 From: Alexander Bock Date: Thu, 21 May 2015 22:22:59 +0200 Subject: Add CMake options to toggle the TestLoader application and OBJ Sticher application so that including the project has a smaller footprint --- CMakeLists.txt | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index ad1b8df..4db707c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -26,18 +26,26 @@ add_library(tinyobjloader ${tinyobjloader-Source} ) -add_executable(test_loader ${tinyobjloader-Test-Source}) -target_link_libraries(test_loader tinyobjloader) +option(TINYOBJLOADER_BUILD_TEST_LOADER "Build Test Loader Application" OFF) -add_executable(obj_sticher ${tinyobjloader-examples-objsticher}) -target_link_libraries(obj_sticher tinyobjloader) +if(TINYOBJLOADER_BUILD_TEST_LOADER) + add_executable(test_loader ${tinyobjloader-Test-Source}) + target_link_libraries(test_loader tinyobjloader) +endif() + +option(TINYOBJLOADER_BUILD_OBJ_STICHER "Build OBJ Sticher Application" OFF) +if (TINYOBJLOADER_BUILD_OBJ_STICHER) + add_executable(obj_sticher ${tinyobjloader-examples-objsticher}) + target_link_libraries(obj_sticher tinyobjloader) + + install ( TARGETS + obj_sticher + DESTINATION + bin + ) +endif() #Installation -install ( TARGETS - obj_sticher - DESTINATION - bin - ) install ( TARGETS tinyobjloader DESTINATION -- cgit v1.2.3 From 49e82e2e00057fe881ba8cff33ebc4f210b32401 Mon Sep 17 00:00:00 2001 From: Alexander Bock Date: Sat, 23 May 2015 22:13:00 +0200 Subject: Fix compile warnings under VS 2013 /W4 warning level --- tiny_obj_loader.cc | 30 +++++++++++++++++++++++------- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/tiny_obj_loader.cc b/tiny_obj_loader.cc index 8fe42c8..d9723e3 100644 --- a/tiny_obj_loader.cc +++ b/tiny_obj_loader.cc @@ -76,7 +76,7 @@ static inline int fixIndex(int idx, int n) { static inline std::string parseString(const char *&token) { std::string s; token += strspn(token, " \t"); - int e = strcspn(token, " \t\r"); + size_t e = strcspn(token, " \t\r"); s = std::string(token, &token[e]); token += e; return s; @@ -325,7 +325,7 @@ updateVertex(std::map &vertexCache, texcoords.push_back(in_texcoords[2 * i.vt_idx + 1]); } - unsigned int idx = positions.size() / 3 - 1; + unsigned int idx = static_cast(positions.size() / 3 - 1); vertexCache[i] = idx; return idx; @@ -448,7 +448,7 @@ std::string LoadMtl(std::map &material_map, // flush previous material. if (!material.name.empty()) { material_map.insert( - std::pair(material.name, materials.size())); + std::pair(material.name, static_cast(materials.size()))); materials.push_back(material); } @@ -458,7 +458,11 @@ std::string LoadMtl(std::map &material_map, // set new mtl name char namebuf[4096]; token += 7; +#ifdef _MSC_VER + sscanf_s(token, "%s", namebuf); +#else sscanf(token, "%s", namebuf); +#endif material.name = namebuf; continue; } @@ -585,7 +589,7 @@ std::string LoadMtl(std::map &material_map, _space = strchr(token, '\t'); } if (_space) { - int len = _space - token; + ptrdiff_t len = _space - token; std::string key(token, len); std::string value = _space + 1; material.unknown_parameter.insert( @@ -594,7 +598,7 @@ std::string LoadMtl(std::map &material_map, } // flush last material. material_map.insert( - std::pair(material.name, materials.size())); + std::pair(material.name, static_cast(materials.size()))); materials.push_back(material); return err.str(); @@ -729,9 +733,9 @@ std::string LoadObj(std::vector &shapes, std::vector face; while (!isNewLine(token[0])) { vertex_index vi = - parseTriple(token, v.size() / 3, vn.size() / 3, vt.size() / 2); + parseTriple(token, static_cast(v.size() / 3), static_cast(vn.size() / 3), static_cast(vt.size() / 2)); face.push_back(vi); - int n = strspn(token, " \t\r"); + size_t n = strspn(token, " \t\r"); token += n; } @@ -745,7 +749,11 @@ std::string LoadObj(std::vector &shapes, char namebuf[4096]; token += 7; +#ifdef _MSC_VER + sscanf_s(token, "%s", namebuf); +#else sscanf(token, "%s", namebuf); +#endif // Create face group per material. bool ret = exportFaceGroupToShape(shape, vertexCache, v, vn, vt, @@ -768,7 +776,11 @@ std::string LoadObj(std::vector &shapes, if ((0 == strncmp(token, "mtllib", 6)) && isSpace((token[6]))) { char namebuf[4096]; token += 7; +#ifdef _MSC_VER + sscanf_s(token, "%s", namebuf); +#else sscanf(token, "%s", namebuf); +#endif std::string err_mtl = readMatFn(namebuf, materials, material_map); if (!err_mtl.empty()) { @@ -830,7 +842,11 @@ std::string LoadObj(std::vector &shapes, // @todo { multiple object name? } char namebuf[4096]; token += 2; +#ifdef _MSC_VER + sscanf_s(token, "%s", namebuf); +#else sscanf(token, "%s", namebuf); +#endif name = std::string(namebuf); continue; -- cgit v1.2.3 From 0aab63eb2046b1be0ea549d8959eef5c1a11e464 Mon Sep 17 00:00:00 2001 From: Alexander Bock Date: Sat, 23 May 2015 22:15:23 +0200 Subject: Fixing ptrdiff_t compile error --- tiny_obj_loader.cc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tiny_obj_loader.cc b/tiny_obj_loader.cc index d9723e3..ca5222a 100644 --- a/tiny_obj_loader.cc +++ b/tiny_obj_loader.cc @@ -24,6 +24,7 @@ #include #include #include +#include #include #include @@ -589,7 +590,7 @@ std::string LoadMtl(std::map &material_map, _space = strchr(token, '\t'); } if (_space) { - ptrdiff_t len = _space - token; + std::ptrdiff_t len = _space - token; std::string key(token, len); std::string value = _space + 1; material.unknown_parameter.insert( -- cgit v1.2.3 From 42d6bfbafb656c969935d0a1fd47099cb65a433c Mon Sep 17 00:00:00 2001 From: MutterOberin Date: Mon, 1 Jun 2015 16:39:36 +0200 Subject: Changed pow function to use double overload --- tiny_obj_loader.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tiny_obj_loader.cc b/tiny_obj_loader.cc index ca5222a..b74b941 100644 --- a/tiny_obj_loader.cc +++ b/tiny_obj_loader.cc @@ -185,7 +185,7 @@ static bool tryParseDouble(const char *s, const char *s_end, double *result) 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, -read); + mantissa += static_cast(*curr - 0x30) * pow(10.0, -read); read++; curr++; } } @@ -228,7 +228,7 @@ static bool tryParseDouble(const char *s, const char *s_end, double *result) } assemble: - *result = (sign == '+'? 1 : -1) * ldexp(mantissa * pow(5, exponent), exponent); + *result = (sign == '+'? 1 : -1) * ldexp(mantissa * pow(5.0, exponent), exponent); return true; fail: return false; -- cgit v1.2.3 From a7759a740a223343bad8c6fc4fc714ba0581e60c Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Thu, 4 Jun 2015 00:01:51 +0900 Subject: Add Appveyor settings. --- appveyor.yml | 12 ++++++++++++ tools/windows/premake5.exe | Bin 0 -> 514560 bytes vcsetup.bat | 1 + 3 files changed, 13 insertions(+) create mode 100644 appveyor.yml create mode 100644 tools/windows/premake5.exe create mode 100644 vcsetup.bat diff --git a/appveyor.yml b/appveyor.yml new file mode 100644 index 0000000..38eaf15 --- /dev/null +++ b/appveyor.yml @@ -0,0 +1,12 @@ +version: 0.9.{build} + +# scripts that runs after repo cloning. +install: + - vcsetup.bat + +platform: x64 +configuration: Release + +build: + parallel: true + project: TinyObjLoaderSolution.sln diff --git a/tools/windows/premake5.exe b/tools/windows/premake5.exe new file mode 100644 index 0000000..c0bf928 Binary files /dev/null and b/tools/windows/premake5.exe differ diff --git a/vcsetup.bat b/vcsetup.bat new file mode 100644 index 0000000..921c1e9 --- /dev/null +++ b/vcsetup.bat @@ -0,0 +1 @@ +.\\tools\\windows\\premake5.exe vs2013 -- cgit v1.2.3 From 4e9e812b096672612981e772311b8d0870e1fa0a Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Thu, 4 Jun 2015 00:06:56 +0900 Subject: Add Appveyor batch. --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 4d194e8..c290cac 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,8 @@ tinyobjloader [![wercker status](https://app.wercker.com/status/495a3bac400212cdacdeb4dd9397bf4f/m "wercker status")](https://app.wercker.com/project/bykey/495a3bac400212cdacdeb4dd9397bf4f) +[![Build status](https://ci.appveyor.com/api/projects/status/tlb421q3t2oyobcn/branch/master?svg=true)](https://ci.appveyor.com/project/syoyo/tinyobjloader/branch/master) + http://syoyo.github.io/tinyobjloader/ Tiny but poweful single file wavefront obj loader written in C++. No dependency except for C++ STL. It can parse 10M over polygons with moderate memory and time. -- cgit v1.2.3 From a2851fdf176ccc6c544074a6b139b662750a4854 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Mon, 8 Jun 2015 14:18:35 +0900 Subject: Add more links to project using tinyobjloader. --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index c290cac..b49a7d7 100644 --- a/README.md +++ b/README.md @@ -45,6 +45,8 @@ TinyObjLoader is successfully used in ... * mallie https://lighttransport.github.io/mallie * IBLBaker (Image Based Lighting Baker). http://www.derkreature.com/iblbaker/ * Stanford CS148 http://web.stanford.edu/class/cs148/assignments/assignment3.pdf +* Awesome Bump http://awesomebump.besaba.com/about/ +* sdlgl3-wavefront OpenGL .obj viewer https://github.com/chrisliebert/sdlgl3-wavefront * Your project here! Features -- cgit v1.2.3 From 1adfc794ae3fb11875ed34aa97ec39e5d27fc7d4 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Tue, 9 Jun 2015 23:49:52 +0900 Subject: Fix sscanf_s seg fault on windows. Fixes #41 --- tiny_obj_loader.cc | 43 +++++++++++++++++++++++-------------------- 1 file changed, 23 insertions(+), 20 deletions(-) diff --git a/tiny_obj_loader.cc b/tiny_obj_loader.cc index b74b941..3f4cfc5 100644 --- a/tiny_obj_loader.cc +++ b/tiny_obj_loader.cc @@ -5,19 +5,20 @@ // // -// version 0.9.9: Replace atof() with custom parser. -// version 0.9.8: Fix multi-materials(per-face material ID). -// version 0.9.7: Support multi-materials(per-face material ID) per +// version 0.9.10: Fix seg fault on windows. +// version 0.9.9 : Replace atof() with custom parser. +// version 0.9.8 : Fix multi-materials(per-face material ID). +// version 0.9.7 : Support multi-materials(per-face material ID) per // object/group. -// 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. -// 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 -// version 0.9.1: Add initial .mtl load support -// version 0.9.0: Initial +// 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. +// 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 +// version 0.9.1 : Add initial .mtl load support +// version 0.9.0 : Initial // #include @@ -36,6 +37,8 @@ namespace tinyobj { +#define TINYOBJ_SSCANF_BUFFER_SIZE (4096) + struct vertex_index { int v_idx, vt_idx, vn_idx; vertex_index(){}; @@ -457,10 +460,10 @@ std::string LoadMtl(std::map &material_map, InitMaterial(material); // set new mtl name - char namebuf[4096]; + char namebuf[TINYOBJ_SSCANF_BUFFER_SIZE]; token += 7; #ifdef _MSC_VER - sscanf_s(token, "%s", namebuf); + sscanf_s(token, "%s", namebuf, _countof(namebuf)); #else sscanf(token, "%s", namebuf); #endif @@ -748,10 +751,10 @@ std::string LoadObj(std::vector &shapes, // use mtl if ((0 == strncmp(token, "usemtl", 6)) && isSpace((token[6]))) { - char namebuf[4096]; + char namebuf[TINYOBJ_SSCANF_BUFFER_SIZE]; token += 7; #ifdef _MSC_VER - sscanf_s(token, "%s", namebuf); + sscanf_s(token, "%s", namebuf, _countof(namebuf)); #else sscanf(token, "%s", namebuf); #endif @@ -775,10 +778,10 @@ std::string LoadObj(std::vector &shapes, // load mtl if ((0 == strncmp(token, "mtllib", 6)) && isSpace((token[6]))) { - char namebuf[4096]; + char namebuf[TINYOBJ_SSCANF_BUFFER_SIZE]; token += 7; #ifdef _MSC_VER - sscanf_s(token, "%s", namebuf); + sscanf_s(token, "%s", namebuf, _countof(namebuf)); #else sscanf(token, "%s", namebuf); #endif @@ -841,10 +844,10 @@ std::string LoadObj(std::vector &shapes, shape = shape_t(); // @todo { multiple object name? } - char namebuf[4096]; + char namebuf[TINYOBJ_SSCANF_BUFFER_SIZE]; token += 2; #ifdef _MSC_VER - sscanf_s(token, "%s", namebuf); + sscanf_s(token, "%s", namebuf, _countof(namebuf)); #else sscanf(token, "%s", namebuf); #endif -- cgit v1.2.3 From 805bd814faf62583ae54091f28eaba0bfb8e7f56 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Sat, 20 Jun 2015 19:41:41 +0900 Subject: Invert 'Tr'. Fixes #43. --- tiny_obj_loader.cc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tiny_obj_loader.cc b/tiny_obj_loader.cc index 3f4cfc5..323573d 100644 --- a/tiny_obj_loader.cc +++ b/tiny_obj_loader.cc @@ -555,7 +555,8 @@ std::string LoadMtl(std::map &material_map, } if (token[0] == 'T' && token[1] == 'r' && isSpace(token[2])) { token += 2; - material.dissolve = parseFloat(token); + // Invert value of Tr(assume Tr is in range [0, 1]) + material.dissolve = 1.0 - parseFloat(token); continue; } -- cgit v1.2.3 From fb361547e577c5a0b8ba339476716cad9c721034 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ricardo=20Sa=CC=81nchez-Sa=CC=81ez?= Date: Mon, 22 Jun 2015 16:14:16 +0100 Subject: Fix groups being ignored if they have 'usemtl' just before 'g' --- tiny_obj_loader.cc | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tiny_obj_loader.cc b/tiny_obj_loader.cc index 323573d..5f6359d 100644 --- a/tiny_obj_loader.cc +++ b/tiny_obj_loader.cc @@ -764,8 +764,10 @@ std::string LoadObj(std::vector &shapes, bool ret = exportFaceGroupToShape(shape, vertexCache, v, vn, vt, faceGroup, material, name, true); if (ret) { - faceGroup.clear(); + shapes.push_back(shape); } + shape = shape_t(); + faceGroup.clear(); if (material_map.find(namebuf) != material_map.end()) { material = material_map[namebuf]; -- cgit v1.2.3 From 3058419d7d5564b03c374f8975fa68281ee6471a Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Tue, 23 Jun 2015 16:18:29 +0900 Subject: Update README. Add bugfix notes. --- README.md | 2 ++ tiny_obj_loader.cc | 2 ++ 2 files changed, 4 insertions(+) diff --git a/README.md b/README.md index b49a7d7..8154958 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,7 @@ Tiny but poweful single file wavefront obj loader written in C++. No dependency What's new ---------- +* Jun 23, 2015 : Various fixes and added more projects using tinyobjloader. Thanks many contributors! * Mar 03, 2015 : Replace atof() with hand-written parser for robust reading of numeric value. Thanks skurmedel! * Feb 06, 2015 : Fix parsing multi-material object * Sep 14, 2014 : Add support for multi-material per object/group. Thanks Mykhailo! @@ -47,6 +48,7 @@ TinyObjLoader is successfully used in ... * Stanford CS148 http://web.stanford.edu/class/cs148/assignments/assignment3.pdf * Awesome Bump http://awesomebump.besaba.com/about/ * sdlgl3-wavefront OpenGL .obj viewer https://github.com/chrisliebert/sdlgl3-wavefront +* pbrt-v3 https://https://github.com/mmp/pbrt-v3 * Your project here! Features diff --git a/tiny_obj_loader.cc b/tiny_obj_loader.cc index 5f6359d..93218e9 100644 --- a/tiny_obj_loader.cc +++ b/tiny_obj_loader.cc @@ -5,6 +5,8 @@ // // +// 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. // version 0.9.8 : Fix multi-materials(per-face material ID). -- cgit v1.2.3 From 164c152216f75a3d9c1e9b4f367118e9a9343180 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Thu, 25 Jun 2015 20:24:24 +0900 Subject: Initialized a material. Add warning message to `err` when material file not found. --- tiny_obj_loader.cc | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/tiny_obj_loader.cc b/tiny_obj_loader.cc index 93218e9..3c31b02 100644 --- a/tiny_obj_loader.cc +++ b/tiny_obj_loader.cc @@ -414,7 +414,9 @@ std::string LoadMtl(std::map &material_map, std::istream &inStream) { std::stringstream err; + // Create a default material anyway. material_t material; + InitMaterial(material); int maxchars = 8192; // Alloc enough size. std::vector buf(maxchars); // Alloc enough size. @@ -623,7 +625,13 @@ std::string MaterialFileReader::operator()(const std::string &matId, } std::ifstream matIStream(filepath.c_str()); - return LoadMtl(matMap, materials, matIStream); + 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, -- cgit v1.2.3 From 82ae20b8339152bb0379a520f766405a815e7903 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Thu, 25 Jun 2015 20:32:46 +0900 Subject: +1 version num. --- tiny_obj_loader.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/tiny_obj_loader.cc b/tiny_obj_loader.cc index 3c31b02..57596dc 100644 --- a/tiny_obj_loader.cc +++ b/tiny_obj_loader.cc @@ -5,6 +5,7 @@ // // +// 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.11: Invert `Tr` parameter(#43) // version 0.9.10: Fix seg fault on windows. -- cgit v1.2.3 From def9fe7f16e2d24d0150c2cec80a5ec7b168bd68 Mon Sep 17 00:00:00 2001 From: The Gitter Badger Date: Thu, 16 Jul 2015 16:02:50 +0000 Subject: Added Gitter badge --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 8154958..6ee59f6 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ tinyobjloader ============= +[![Join the chat at https://gitter.im/syoyo/tinyobjloader](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/syoyo/tinyobjloader?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) + [![wercker status](https://app.wercker.com/status/495a3bac400212cdacdeb4dd9397bf4f/m "wercker status")](https://app.wercker.com/project/bykey/495a3bac400212cdacdeb4dd9397bf4f) [![Build status](https://ci.appveyor.com/api/projects/status/tlb421q3t2oyobcn/branch/master?svg=true)](https://ci.appveyor.com/project/syoyo/tinyobjloader/branch/master) -- cgit v1.2.3 From aa07206fc170ebc3161638e7dea7621a3b4db4e3 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Fri, 24 Jul 2015 11:46:30 +0900 Subject: Suppress double -> float conversion warning. Fixes #50 --- tiny_obj_loader.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tiny_obj_loader.cc b/tiny_obj_loader.cc index 57596dc..48ecd90 100644 --- a/tiny_obj_loader.cc +++ b/tiny_obj_loader.cc @@ -561,7 +561,7 @@ std::string LoadMtl(std::map &material_map, 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.0 - parseFloat(token); + material.dissolve = 1.0f - parseFloat(token); continue; } -- cgit v1.2.3 From 870ead273e106e3aac6de2f5b1712ce1a3d9abfa Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Tue, 4 Aug 2015 14:27:40 +0900 Subject: Initial support of tinyobjloader on Android NDK platform(NDK r10 confirmed to be able to compile). --- jni/Android.mk | 12 ++++++++++++ jni/Application.mk | 2 ++ jni/Makefile | 2 ++ jni/README | 1 + tiny_obj_loader.cc | 1 + 5 files changed, 18 insertions(+) create mode 100644 jni/Android.mk create mode 100644 jni/Application.mk create mode 100644 jni/Makefile create mode 100644 jni/README diff --git a/jni/Android.mk b/jni/Android.mk new file mode 100644 index 0000000..eaed1b7 --- /dev/null +++ b/jni/Android.mk @@ -0,0 +1,12 @@ +# A simple test for the minimal standard C++ library +# + +LOCAL_PATH := $(call my-dir) + +include $(CLEAR_VARS) +LOCAL_MODULE := tinyobjloader +LOCAL_SRC_FILES := ../tiny_obj_loader.cc ../test.cc + +LOCAL_C_INCLUDES := ../ + +include $(BUILD_EXECUTABLE) diff --git a/jni/Application.mk b/jni/Application.mk new file mode 100644 index 0000000..e5d3191 --- /dev/null +++ b/jni/Application.mk @@ -0,0 +1,2 @@ +APP_ABI := all +APP_STL := stlport_static diff --git a/jni/Makefile b/jni/Makefile new file mode 100644 index 0000000..1f13853 --- /dev/null +++ b/jni/Makefile @@ -0,0 +1,2 @@ +all: + ndk-build diff --git a/jni/README b/jni/README new file mode 100644 index 0000000..f93bc08 --- /dev/null +++ b/jni/README @@ -0,0 +1 @@ +Just tests compilation with Android NDK r10. diff --git a/tiny_obj_loader.cc b/tiny_obj_loader.cc index 48ecd90..7d64e46 100644 --- a/tiny_obj_loader.cc +++ b/tiny_obj_loader.cc @@ -29,6 +29,7 @@ #include #include #include +#include #include #include -- cgit v1.2.3 From 191a7dfdc88a5fb960dfee13af6d8230a3e0fbcd Mon Sep 17 00:00:00 2001 From: Nicolas Guillemot Date: Sat, 8 Aug 2015 16:20:57 -0700 Subject: fix sscanf_s buffer size type Uses of sscanf_s give the following warnings in 64-bit builds: tiny_obj_loader.cc(471): warning C4477: 'sscanf_s' : format string '%s' requires an argument of type 'int', but variadic argument 2 has type 'unsigned __int64' tiny_obj_loader.cc(471): note: this argument is used as a buffer size This was fixed by casting the uses of _countof(namebuf) to unsigned, since the MSDN documentation for sscanf_s specifies that "The size parameter is of type **unsigned**, not **size_t**." (https://msdn.microsoft.com/en-us/library/t6z7bya3.aspx) This silences the warnings. --- tiny_obj_loader.cc | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tiny_obj_loader.cc b/tiny_obj_loader.cc index 7d64e46..e9cae10 100644 --- a/tiny_obj_loader.cc +++ b/tiny_obj_loader.cc @@ -469,7 +469,7 @@ std::string LoadMtl(std::map &material_map, char namebuf[TINYOBJ_SSCANF_BUFFER_SIZE]; token += 7; #ifdef _MSC_VER - sscanf_s(token, "%s", namebuf, _countof(namebuf)); + sscanf_s(token, "%s", namebuf, (unsigned)_countof(namebuf)); #else sscanf(token, "%s", namebuf); #endif @@ -767,7 +767,7 @@ std::string LoadObj(std::vector &shapes, char namebuf[TINYOBJ_SSCANF_BUFFER_SIZE]; token += 7; #ifdef _MSC_VER - sscanf_s(token, "%s", namebuf, _countof(namebuf)); + sscanf_s(token, "%s", namebuf, (unsigned)_countof(namebuf)); #else sscanf(token, "%s", namebuf); #endif @@ -796,7 +796,7 @@ std::string LoadObj(std::vector &shapes, char namebuf[TINYOBJ_SSCANF_BUFFER_SIZE]; token += 7; #ifdef _MSC_VER - sscanf_s(token, "%s", namebuf, _countof(namebuf)); + sscanf_s(token, "%s", namebuf, (unsigned)_countof(namebuf)); #else sscanf(token, "%s", namebuf); #endif @@ -862,7 +862,7 @@ std::string LoadObj(std::vector &shapes, char namebuf[TINYOBJ_SSCANF_BUFFER_SIZE]; token += 2; #ifdef _MSC_VER - sscanf_s(token, "%s", namebuf, _countof(namebuf)); + sscanf_s(token, "%s", namebuf, (unsigned)_countof(namebuf)); #else sscanf(token, "%s", namebuf); #endif -- cgit v1.2.3 From 7f2092b29f80fbdb4fb0b43c225a7b0eca12ecd5 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Tue, 1 Sep 2015 20:20:10 +0900 Subject: Support specular highlight, bump, displacement and alpha texture(Remove non-standard "normal map"). Fixes #53. --- test.cc | 5 ++++- tiny_obj_loader.cc | 38 +++++++++++++++++++++++++++++++++++--- tiny_obj_loader.h | 11 +++++++---- 3 files changed, 46 insertions(+), 8 deletions(-) diff --git a/test.cc b/test.cc index 1ad6d8c..3f16e3e 100644 --- a/test.cc +++ b/test.cc @@ -45,7 +45,10 @@ static void PrintInfo(const std::vector& shapes, const std::ve printf(" material.map_Ka = %s\n", materials[i].ambient_texname.c_str()); printf(" material.map_Kd = %s\n", materials[i].diffuse_texname.c_str()); printf(" material.map_Ks = %s\n", materials[i].specular_texname.c_str()); - printf(" material.map_Ns = %s\n", materials[i].normal_texname.c_str()); + printf(" material.map_Ns = %s\n", materials[i].specular_highlight_texname.c_str()); + printf(" material.map_bump = %s\n", materials[i].bump_texname.c_str()); + printf(" material.map_d = %s\n", materials[i].alpha_texname.c_str()); + printf(" material.disp = %s\n", materials[i].displacement_texname.c_str()); std::map::const_iterator it(materials[i].unknown_parameter.begin()); std::map::const_iterator itEnd(materials[i].unknown_parameter.end()); for (; it != itEnd; it++) { diff --git a/tiny_obj_loader.cc b/tiny_obj_loader.cc index e9cae10..4d6fc95 100644 --- a/tiny_obj_loader.cc +++ b/tiny_obj_loader.cc @@ -5,6 +5,7 @@ // // +// version 0.9.14: Support specular highlight, bump, displacement and alpha map(#53) // 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.11: Invert `Tr` parameter(#43) @@ -344,7 +345,10 @@ void InitMaterial(material_t &material) { material.ambient_texname = ""; material.diffuse_texname = ""; material.specular_texname = ""; - material.normal_texname = ""; + material.specular_highlight_texname = ""; + material.bump_texname = ""; + material.displacement_texname = ""; + material.alpha_texname = ""; for (int i = 0; i < 3; i++) { material.ambient[i] = 0.f; material.diffuse[i] = 0.f; @@ -587,10 +591,38 @@ std::string LoadMtl(std::map &material_map, continue; } - // normal texture + // specular highlight texture if ((0 == strncmp(token, "map_Ns", 6)) && isSpace(token[6])) { token += 7; - material.normal_texname = token; + material.specular_highlight_texname = token; + continue; + } + + // bump texture + if ((0 == strncmp(token, "map_bump", 8)) && isSpace(token[8])) { + token += 9; + material.bump_texname = token; + continue; + } + + // alpha texture + if ((0 == strncmp(token, "map_d", 5)) && isSpace(token[5])) { + token += 6; + material.bump_texname = token; + continue; + } + + // bump texture + if ((0 == strncmp(token, "bump", 4)) && isSpace(token[4])) { + token += 5; + material.bump_texname = token; + continue; + } + + // displacement texture + if ((0 == strncmp(token, "disp", 4)) && isSpace(token[4])) { + token += 5; + material.displacement_texname = token; continue; } diff --git a/tiny_obj_loader.h b/tiny_obj_loader.h index 512f32b..00259e7 100644 --- a/tiny_obj_loader.h +++ b/tiny_obj_loader.h @@ -26,10 +26,13 @@ typedef struct { // 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::string ambient_texname; // map_Ka + std::string diffuse_texname; // map_Kd + std::string specular_texname; // map_Ks + std::string specular_highlight_texname; // map_Ns + std::string bump_texname; // map_bump, bump + std::string displacement_texname; // disp + std::string alpha_texname; // map_d std::map unknown_parameter; } material_t; -- cgit v1.2.3 From 475bc83ef3193198f145896abc864429ef07fdbf Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Wed, 2 Sep 2015 18:55:34 +0900 Subject: Fix wrong texname assignment for `map_d`. --- tiny_obj_loader.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tiny_obj_loader.cc b/tiny_obj_loader.cc index 4d6fc95..067f642 100644 --- a/tiny_obj_loader.cc +++ b/tiny_obj_loader.cc @@ -608,7 +608,7 @@ std::string LoadMtl(std::map &material_map, // alpha texture if ((0 == strncmp(token, "map_d", 5)) && isSpace(token[5])) { token += 6; - material.bump_texname = token; + material.alpha_texname = token; continue; } -- cgit v1.2.3 From fcad68bf2d819bd0cdb7975ab956b8cf0f6bfac0 Mon Sep 17 00:00:00 2001 From: Olivier Date: Wed, 21 Oct 2015 03:23:06 +0200 Subject: update --- python/cornell_box_multimaterial_output.json | 581 ++++++++++++++++++++++++++ python/cornell_box_output.json | 601 --------------------------- python/howto.py | 4 +- python/main.cpp | 23 +- python/pyTOL.cbp.mak | 96 ----- 5 files changed, 599 insertions(+), 706 deletions(-) create mode 100644 python/cornell_box_multimaterial_output.json delete mode 100644 python/cornell_box_output.json delete mode 100644 python/pyTOL.cbp.mak diff --git a/python/cornell_box_multimaterial_output.json b/python/cornell_box_multimaterial_output.json new file mode 100644 index 0000000..ac2de10 --- /dev/null +++ b/python/cornell_box_multimaterial_output.json @@ -0,0 +1,581 @@ +{ + "shapes": { + "back_wall": { + "material_ids": [ + 0.0, + 0.0 + ], + "normals": [], + "positions": [ + 549.5999755859375, + 0.0, + 559.2000122070312, + 0.0, + 0.0, + 559.2000122070312, + 0.0, + 548.7999877929688, + 559.2000122070312, + 556.0, + 548.7999877929688, + 559.2000122070312 + ], + "indicies": [ + 0.0, + 1.0, + 2.0, + 0.0, + 2.0, + 3.0 + ], + "texcoords": [] + }, + "floor": { + "material_ids": [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + "normals": [], + "positions": [ + 552.7999877929688, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 559.2000122070312, + 549.5999755859375, + 0.0, + 559.2000122070312, + 290.0, + 0.0, + 114.0, + 240.0, + 0.0, + 272.0, + 82.0, + 0.0, + 225.0, + 130.0, + 0.0, + 65.0, + 472.0, + 0.0, + 406.0, + 314.0, + 0.0, + 456.0, + 265.0, + 0.0, + 296.0, + 423.0, + 0.0, + 247.0 + ], + "indicies": [ + 0.0, + 1.0, + 2.0, + 0.0, + 2.0, + 3.0, + 4.0, + 5.0, + 6.0, + 4.0, + 6.0, + 7.0, + 8.0, + 9.0, + 10.0, + 8.0, + 10.0, + 11.0 + ], + "texcoords": [] + }, + "red_wall": { + "material_ids": [ + 1.0, + 1.0 + ], + "normals": [], + "positions": [ + 552.7999877929688, + 0.0, + 0.0, + 549.5999755859375, + 0.0, + 559.2000122070312, + 556.0, + 548.7999877929688, + 559.2000122070312, + 556.0, + 548.7999877929688, + 0.0 + ], + "indicies": [ + 0.0, + 1.0, + 2.0, + 0.0, + 2.0, + 3.0 + ], + "texcoords": [] + }, + "light": { + "material_ids": [ + 4.0, + 4.0 + ], + "normals": [], + "positions": [ + 343.0, + 548.0, + 227.0, + 343.0, + 548.0, + 332.0, + 213.0, + 548.0, + 332.0, + 213.0, + 548.0, + 227.0 + ], + "indicies": [ + 0.0, + 1.0, + 2.0, + 0.0, + 2.0, + 3.0 + ], + "texcoords": [] + }, + "tall_block": { + "material_ids": [ + 2.0, + 2.0, + 2.0, + 2.0 + ], + "normals": [], + "positions": [ + 314.0, + 0.0, + 456.0, + 314.0, + 330.0, + 456.0, + 265.0, + 330.0, + 296.0, + 265.0, + 0.0, + 296.0, + 265.0, + 0.0, + 296.0, + 265.0, + 330.0, + 296.0, + 423.0, + 330.0, + 247.0, + 423.0, + 0.0, + 247.0 + ], + "indicies": [ + 0.0, + 1.0, + 2.0, + 0.0, + 2.0, + 3.0, + 4.0, + 5.0, + 6.0, + 4.0, + 6.0, + 7.0 + ], + "texcoords": [] + }, + "short_block": { + "material_ids": [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + "normals": [], + "positions": [ + 130.0, + 165.0, + 65.0, + 82.0, + 165.0, + 225.0, + 240.0, + 165.0, + 272.0, + 290.0, + 165.0, + 114.0, + 290.0, + 0.0, + 114.0, + 290.0, + 165.0, + 114.0, + 240.0, + 165.0, + 272.0, + 240.0, + 0.0, + 272.0, + 130.0, + 0.0, + 65.0, + 130.0, + 165.0, + 65.0, + 290.0, + 165.0, + 114.0, + 290.0, + 0.0, + 114.0, + 82.0, + 0.0, + 225.0, + 82.0, + 165.0, + 225.0, + 130.0, + 165.0, + 65.0, + 130.0, + 0.0, + 65.0, + 240.0, + 0.0, + 272.0, + 240.0, + 165.0, + 272.0, + 82.0, + 165.0, + 225.0, + 82.0, + 0.0, + 225.0 + ], + "indicies": [ + 0.0, + 1.0, + 2.0, + 0.0, + 2.0, + 3.0, + 4.0, + 5.0, + 6.0, + 4.0, + 6.0, + 7.0, + 8.0, + 9.0, + 10.0, + 8.0, + 10.0, + 11.0, + 12.0, + 13.0, + 14.0, + 12.0, + 14.0, + 15.0, + 16.0, + 17.0, + 18.0, + 16.0, + 18.0, + 19.0 + ], + "texcoords": [] + }, + "green_wall": { + "material_ids": [ + 2.0, + 2.0 + ], + "normals": [], + "positions": [ + 0.0, + 0.0, + 559.2000122070312, + 0.0, + 0.0, + 0.0, + 0.0, + 548.7999877929688, + 0.0, + 0.0, + 548.7999877929688, + 559.2000122070312 + ], + "indicies": [ + 0.0, + 1.0, + 2.0, + 0.0, + 2.0, + 3.0 + ], + "texcoords": [] + }, + "ceiling": { + "material_ids": [ + 0.0, + 0.0 + ], + "normals": [], + "positions": [ + 556.0, + 548.7999877929688, + 0.0, + 556.0, + 548.7999877929688, + 559.2000122070312, + 0.0, + 548.7999877929688, + 559.2000122070312, + 0.0, + 548.7999877929688, + 0.0 + ], + "indicies": [ + 0.0, + 1.0, + 2.0, + 0.0, + 2.0, + 3.0 + ], + "texcoords": [] + } + }, + "materials": { + "blue": { + "diffuse_texname": "", + "ambient_texname": "", + "illum": 0, + "displacement_texname": "", + "alpha_texname": "", + "emission": [ + 0.0, + 0.0, + 0.0 + ], + "transmittance": [ + 0.0, + 0.0, + 0.0 + ], + "ambient": [ + 0.0, + 0.0, + 0.0 + ], + "bump_texname": "", + "diffuse": [ + 0.0, + 0.0, + 1.0 + ], + "shininess": 1.0, + "specular_highlight_texname": "", + "unknown_parameter": {}, + "ior": 1.0, + "dissolve": 1.0, + "specular": [ + 0.0, + 0.0, + 0.0 + ], + "specular_texname": "" + }, + "white": { + "diffuse_texname": "", + "ambient_texname": "", + "illum": 0, + "displacement_texname": "", + "alpha_texname": "", + "emission": [ + 0.0, + 0.0, + 0.0 + ], + "transmittance": [ + 0.0, + 0.0, + 0.0 + ], + "ambient": [ + 0.0, + 0.0, + 0.0 + ], + "bump_texname": "", + "diffuse": [ + 1.0, + 1.0, + 1.0 + ], + "shininess": 1.0, + "specular_highlight_texname": "", + "unknown_parameter": {}, + "ior": 1.0, + "dissolve": 1.0, + "specular": [ + 0.0, + 0.0, + 0.0 + ], + "specular_texname": "" + }, + "red": { + "diffuse_texname": "", + "ambient_texname": "", + "illum": 0, + "displacement_texname": "", + "alpha_texname": "", + "emission": [ + 0.0, + 0.0, + 0.0 + ], + "transmittance": [ + 0.0, + 0.0, + 0.0 + ], + "ambient": [ + 0.0, + 0.0, + 0.0 + ], + "bump_texname": "", + "diffuse": [ + 1.0, + 0.0, + 0.0 + ], + "shininess": 1.0, + "specular_highlight_texname": "", + "unknown_parameter": {}, + "ior": 1.0, + "dissolve": 1.0, + "specular": [ + 0.0, + 0.0, + 0.0 + ], + "specular_texname": "" + }, + "light": { + "diffuse_texname": "", + "ambient_texname": "", + "illum": 0, + "displacement_texname": "", + "alpha_texname": "", + "emission": [ + 0.0, + 0.0, + 0.0 + ], + "transmittance": [ + 0.0, + 0.0, + 0.0 + ], + "ambient": [ + 20.0, + 20.0, + 20.0 + ], + "bump_texname": "", + "diffuse": [ + 1.0, + 1.0, + 1.0 + ], + "shininess": 1.0, + "specular_highlight_texname": "", + "unknown_parameter": {}, + "ior": 1.0, + "dissolve": 1.0, + "specular": [ + 0.0, + 0.0, + 0.0 + ], + "specular_texname": "" + }, + "green": { + "diffuse_texname": "", + "ambient_texname": "", + "illum": 0, + "displacement_texname": "", + "alpha_texname": "", + "emission": [ + 0.0, + 0.0, + 0.0 + ], + "transmittance": [ + 0.0, + 0.0, + 0.0 + ], + "ambient": [ + 0.0, + 0.0, + 0.0 + ], + "bump_texname": "", + "diffuse": [ + 0.0, + 1.0, + 0.0 + ], + "shininess": 1.0, + "specular_highlight_texname": "", + "unknown_parameter": {}, + "ior": 1.0, + "dissolve": 1.0, + "specular": [ + 0.0, + 0.0, + 0.0 + ], + "specular_texname": "" + } + } +} diff --git a/python/cornell_box_output.json b/python/cornell_box_output.json deleted file mode 100644 index ba2e7e1..0000000 --- a/python/cornell_box_output.json +++ /dev/null @@ -1,601 +0,0 @@ -{ - "shapes": { - "ceiling": { - "texcoords": [], - "positions": [ - 556.0, - 548.7999877929688, - 0.0, - 556.0, - 548.7999877929688, - 559.2000122070312, - 0.0, - 548.7999877929688, - 559.2000122070312, - 0.0, - 548.7999877929688, - 0.0 - ], - "indicies": [ - 0.0, - 1.0, - 2.0, - 0.0, - 2.0, - 3.0 - ], - "material_ids": [ - 0.0, - 0.0 - ], - "normals": [] - }, - "floor": { - "texcoords": [], - "positions": [ - 552.7999877929688, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 559.2000122070312, - 549.5999755859375, - 0.0, - 559.2000122070312, - 290.0, - 0.0, - 114.0, - 240.0, - 0.0, - 272.0, - 82.0, - 0.0, - 225.0, - 130.0, - 0.0, - 65.0, - 472.0, - 0.0, - 406.0, - 314.0, - 0.0, - 456.0, - 265.0, - 0.0, - 296.0, - 423.0, - 0.0, - 247.0 - ], - "indicies": [ - 0.0, - 1.0, - 2.0, - 0.0, - 2.0, - 3.0, - 4.0, - 5.0, - 6.0, - 4.0, - 6.0, - 7.0, - 8.0, - 9.0, - 10.0, - 8.0, - 10.0, - 11.0 - ], - "material_ids": [ - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0 - ], - "normals": [] - }, - "light": { - "texcoords": [], - "positions": [ - 343.0, - 548.0, - 227.0, - 343.0, - 548.0, - 332.0, - 213.0, - 548.0, - 332.0, - 213.0, - 548.0, - 227.0 - ], - "indicies": [ - 0.0, - 1.0, - 2.0, - 0.0, - 2.0, - 3.0 - ], - "material_ids": [ - 4.0, - 4.0 - ], - "normals": [] - }, - "green_wall": { - "texcoords": [], - "positions": [ - 0.0, - 0.0, - 559.2000122070312, - 0.0, - 0.0, - 0.0, - 0.0, - 548.7999877929688, - 0.0, - 0.0, - 548.7999877929688, - 559.2000122070312 - ], - "indicies": [ - 0.0, - 1.0, - 2.0, - 0.0, - 2.0, - 3.0 - ], - "material_ids": [ - 2.0, - 2.0 - ], - "normals": [] - }, - "back_wall": { - "texcoords": [], - "positions": [ - 549.5999755859375, - 0.0, - 559.2000122070312, - 0.0, - 0.0, - 559.2000122070312, - 0.0, - 548.7999877929688, - 559.2000122070312, - 556.0, - 548.7999877929688, - 559.2000122070312 - ], - "indicies": [ - 0.0, - 1.0, - 2.0, - 0.0, - 2.0, - 3.0 - ], - "material_ids": [ - 0.0, - 0.0 - ], - "normals": [] - }, - "short_block": { - "texcoords": [], - "positions": [ - 130.0, - 165.0, - 65.0, - 82.0, - 165.0, - 225.0, - 240.0, - 165.0, - 272.0, - 290.0, - 165.0, - 114.0, - 290.0, - 0.0, - 114.0, - 290.0, - 165.0, - 114.0, - 240.0, - 165.0, - 272.0, - 240.0, - 0.0, - 272.0, - 130.0, - 0.0, - 65.0, - 130.0, - 165.0, - 65.0, - 290.0, - 165.0, - 114.0, - 290.0, - 0.0, - 114.0, - 82.0, - 0.0, - 225.0, - 82.0, - 165.0, - 225.0, - 130.0, - 165.0, - 65.0, - 130.0, - 0.0, - 65.0, - 240.0, - 0.0, - 272.0, - 240.0, - 165.0, - 272.0, - 82.0, - 165.0, - 225.0, - 82.0, - 0.0, - 225.0 - ], - "indicies": [ - 0.0, - 1.0, - 2.0, - 0.0, - 2.0, - 3.0, - 4.0, - 5.0, - 6.0, - 4.0, - 6.0, - 7.0, - 8.0, - 9.0, - 10.0, - 8.0, - 10.0, - 11.0, - 12.0, - 13.0, - 14.0, - 12.0, - 14.0, - 15.0, - 16.0, - 17.0, - 18.0, - 16.0, - 18.0, - 19.0 - ], - "material_ids": [ - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0 - ], - "normals": [] - }, - "tall_block": { - "texcoords": [], - "positions": [ - 423.0, - 0.0, - 247.0, - 423.0, - 330.0, - 247.0, - 472.0, - 330.0, - 406.0, - 472.0, - 0.0, - 406.0, - 472.0, - 0.0, - 406.0, - 472.0, - 330.0, - 406.0, - 314.0, - 330.0, - 456.0, - 314.0, - 0.0, - 456.0, - 314.0, - 0.0, - 456.0, - 314.0, - 330.0, - 456.0, - 265.0, - 330.0, - 296.0, - 265.0, - 0.0, - 296.0, - 265.0, - 0.0, - 296.0, - 265.0, - 330.0, - 296.0, - 423.0, - 330.0, - 247.0, - 423.0, - 0.0, - 247.0 - ], - "indicies": [ - 0.0, - 1.0, - 2.0, - 0.0, - 2.0, - 3.0, - 4.0, - 5.0, - 6.0, - 4.0, - 6.0, - 7.0, - 8.0, - 9.0, - 10.0, - 8.0, - 10.0, - 11.0, - 12.0, - 13.0, - 14.0, - 12.0, - 14.0, - 15.0 - ], - "material_ids": [ - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0 - ], - "normals": [] - }, - "red_wall": { - "texcoords": [], - "positions": [ - 552.7999877929688, - 0.0, - 0.0, - 549.5999755859375, - 0.0, - 559.2000122070312, - 556.0, - 548.7999877929688, - 559.2000122070312, - 556.0, - 548.7999877929688, - 0.0 - ], - "indicies": [ - 0.0, - 1.0, - 2.0, - 0.0, - 2.0, - 3.0 - ], - "material_ids": [ - 1.0, - 1.0 - ], - "normals": [] - } - }, - "materials": { - "blue": { - "transmittance": [ - 0.0, - 0.0, - 0.0 - ], - "illum": 0, - "emission": [ - 0.0, - 0.0, - 0.0 - ], - "diffuse_texname": "", - "ambient_texname": "", - "normal_texname": "", - "shininess": 1.0, - "ior": 1.0, - "specular": [ - 0.0, - 0.0, - 0.0 - ], - "specular_texname": "", - "diffuse": [ - 0.0, - 0.0, - 1.0 - ], - "ambient": [ - 0.0, - 0.0, - 0.0 - ], - "dissolve": 1.0 - }, - "light": { - "transmittance": [ - 0.0, - 0.0, - 0.0 - ], - "illum": 0, - "emission": [ - 0.0, - 0.0, - 0.0 - ], - "diffuse_texname": "", - "ambient_texname": "", - "normal_texname": "", - "shininess": 1.0, - "ior": 1.0, - "specular": [ - 0.0, - 0.0, - 0.0 - ], - "specular_texname": "", - "diffuse": [ - 1.0, - 1.0, - 1.0 - ], - "ambient": [ - 20.0, - 20.0, - 20.0 - ], - "dissolve": 1.0 - }, - "white": { - "transmittance": [ - 0.0, - 0.0, - 0.0 - ], - "illum": 0, - "emission": [ - 0.0, - 0.0, - 0.0 - ], - "diffuse_texname": "", - "ambient_texname": "", - "normal_texname": "", - "shininess": 1.0, - "ior": 1.0, - "specular": [ - 0.0, - 0.0, - 0.0 - ], - "specular_texname": "", - "diffuse": [ - 1.0, - 1.0, - 1.0 - ], - "ambient": [ - 0.0, - 0.0, - 0.0 - ], - "dissolve": 1.0 - }, - "green": { - "transmittance": [ - 0.0, - 0.0, - 0.0 - ], - "illum": 0, - "emission": [ - 0.0, - 0.0, - 0.0 - ], - "diffuse_texname": "", - "ambient_texname": "", - "normal_texname": "", - "shininess": 1.0, - "ior": 1.0, - "specular": [ - 0.0, - 0.0, - 0.0 - ], - "specular_texname": "", - "diffuse": [ - 0.0, - 1.0, - 0.0 - ], - "ambient": [ - 0.0, - 0.0, - 0.0 - ], - "dissolve": 1.0 - }, - "red": { - "transmittance": [ - 0.0, - 0.0, - 0.0 - ], - "illum": 0, - "emission": [ - 0.0, - 0.0, - 0.0 - ], - "diffuse_texname": "", - "ambient_texname": "", - "normal_texname": "", - "shininess": 1.0, - "ior": 1.0, - "specular": [ - 0.0, - 0.0, - 0.0 - ], - "specular_texname": "", - "diffuse": [ - 1.0, - 0.0, - 0.0 - ], - "ambient": [ - 0.0, - 0.0, - 0.0 - ], - "dissolve": 1.0 - } - } -} diff --git a/python/howto.py b/python/howto.py index c526821..a4c6e04 100644 --- a/python/howto.py +++ b/python/howto.py @@ -1,11 +1,9 @@ import tinyobjloader as tol import json -model = tol.LoadObj("cornell_box.obj") +model = tol.LoadObj("cornell_box_multimaterial.obj") #print(model["shapes"], model["materials"]) print( json.dumps(model, indent=4) ) #see cornell_box_output.json - - diff --git a/python/main.cpp b/python/main.cpp index b2c6e96..6ae89cc 100644 --- a/python/main.cpp +++ b/python/main.cpp @@ -31,7 +31,7 @@ extern "C" static PyObject* pyLoadObj(PyObject* self, PyObject* args) { - PyObject *rtndict, *pyshapes, *pymaterials, + PyObject *rtndict, *pyshapes, *pymaterials, *current, *meshobj; char const* filename; @@ -62,7 +62,7 @@ pyLoadObj(PyObject* self, PyObject* args) switch(i) { - case 0: + case 0: current_name = "positions"; vect = vectd(cm.positions.begin(), cm.positions.end()); break; case 1: @@ -77,9 +77,9 @@ pyLoadObj(PyObject* self, PyObject* args) case 4: current_name = "material_ids"; vect = vectd(cm.material_ids.begin(), cm.material_ids.end()); break; - + } - + for (vectd::iterator it = vect.begin() ; it != vect.end(); it++) { @@ -87,7 +87,7 @@ pyLoadObj(PyObject* self, PyObject* args) } PyDict_SetItemString(meshobj, current_name, current); - + } PyDict_SetItemString(pyshapes, (*shape).name.c_str(), meshobj); @@ -97,6 +97,13 @@ pyLoadObj(PyObject* self, PyObject* args) mat != materials.end(); mat++) { PyObject *matobj = PyDict_New(); + PyObject *unknown_parameter = PyDict_New(); + + for (std::map::iterator p = (*mat).unknown_parameter.begin() ; + p != (*mat).unknown_parameter.end(); ++p) + { + PyDict_SetItemString(unknown_parameter, p->first.c_str(), PyUnicode_FromString(p->second.c_str())); + } PyDict_SetItemString(matobj, "shininess", PyFloat_FromDouble((*mat).shininess)); PyDict_SetItemString(matobj, "ior", PyFloat_FromDouble((*mat).ior)); @@ -105,12 +112,16 @@ pyLoadObj(PyObject* self, PyObject* args) PyDict_SetItemString(matobj, "ambient_texname", PyUnicode_FromString((*mat).ambient_texname.c_str())); PyDict_SetItemString(matobj, "diffuse_texname", PyUnicode_FromString((*mat).diffuse_texname.c_str())); PyDict_SetItemString(matobj, "specular_texname", PyUnicode_FromString((*mat).specular_texname.c_str())); - PyDict_SetItemString(matobj, "normal_texname", PyUnicode_FromString((*mat).normal_texname.c_str())); + PyDict_SetItemString(matobj, "specular_highlight_texname", PyUnicode_FromString((*mat).specular_highlight_texname.c_str())); + PyDict_SetItemString(matobj, "bump_texname", PyUnicode_FromString((*mat).bump_texname.c_str())); + PyDict_SetItemString(matobj, "displacement_texname", PyUnicode_FromString((*mat).displacement_texname.c_str())); + PyDict_SetItemString(matobj, "alpha_texname", PyUnicode_FromString((*mat).alpha_texname.c_str())); PyDict_SetItemString(matobj, "ambient", pyTupleFromfloat3((*mat).ambient)); PyDict_SetItemString(matobj, "diffuse", pyTupleFromfloat3((*mat).diffuse)); PyDict_SetItemString(matobj, "specular", pyTupleFromfloat3((*mat).specular)); PyDict_SetItemString(matobj, "transmittance", pyTupleFromfloat3((*mat).transmittance)); PyDict_SetItemString(matobj, "emission", pyTupleFromfloat3((*mat).emission)); + PyDict_SetItemString(matobj, "unknown_parameter", unknown_parameter); PyDict_SetItemString(pymaterials, (*mat).name.c_str(), matobj); } diff --git a/python/pyTOL.cbp.mak b/python/pyTOL.cbp.mak deleted file mode 100644 index ba18ae9..0000000 --- a/python/pyTOL.cbp.mak +++ /dev/null @@ -1,96 +0,0 @@ -#------------------------------------------------------------------------------# -# This makefile was generated by 'cbp2make' tool rev.147 # -#------------------------------------------------------------------------------# - - -WORKDIR = `pwd` - -CC = gcc -CXX = g++ -AR = ar -LD = g++ -WINDRES = windres - -INC = -CFLAGS = -Wall -fexceptions `python3-config --cflags` -RESINC = -LIBDIR = -LIB = -LDFLAGS = `python3-config --ldflags` - -INC_DEBUG = $(INC) -CFLAGS_DEBUG = $(CFLAGS) -g -RESINC_DEBUG = $(RESINC) -RCFLAGS_DEBUG = $(RCFLAGS) -LIBDIR_DEBUG = $(LIBDIR) -LIB_DEBUG = $(LIB) -LDFLAGS_DEBUG = $(LDFLAGS) -OBJDIR_DEBUG = obj/Debug -DEP_DEBUG = -OUT_DEBUG = bin/Debug/tinyobjloader.so - -INC_RELEASE = $(INC) -CFLAGS_RELEASE = $(CFLAGS) -O2 -RESINC_RELEASE = $(RESINC) -RCFLAGS_RELEASE = $(RCFLAGS) -LIBDIR_RELEASE = $(LIBDIR) -LIB_RELEASE = $(LIB) -LDFLAGS_RELEASE = $(LDFLAGS) -s -OBJDIR_RELEASE = obj/Release -DEP_RELEASE = -OUT_RELEASE = bin/Release/tinyobjloader.so - -OBJ_DEBUG = $(OBJDIR_DEBUG)/main.o $(OBJDIR_DEBUG)/tiny_obj_loader.o - -OBJ_RELEASE = $(OBJDIR_RELEASE)/main.o $(OBJDIR_RELEASE)/tiny_obj_loader.o - -all: debug release - -clean: clean_debug clean_release - -before_debug: - test -d bin/Debug || mkdir -p bin/Debug - test -d $(OBJDIR_DEBUG) || mkdir -p $(OBJDIR_DEBUG) - -after_debug: - -debug: before_debug out_debug after_debug - -out_debug: before_debug $(OBJ_DEBUG) $(DEP_DEBUG) - $(LD) -shared $(LIBDIR_DEBUG) $(OBJ_DEBUG) -o $(OUT_DEBUG) $(LDFLAGS_DEBUG) $(LIB_DEBUG) - -$(OBJDIR_DEBUG)/main.o: main.cpp - $(CXX) $(CFLAGS_DEBUG) $(INC_DEBUG) -c main.cpp -o $(OBJDIR_DEBUG)/main.o - -$(OBJDIR_DEBUG)/tiny_obj_loader.o: ../tiny_obj_loader.cc - $(CC) $(CFLAGS_DEBUG) $(INC_DEBUG) -c ../tiny_obj_loader.cc -o $(OBJDIR_DEBUG)/tiny_obj_loader.o - -clean_debug: - rm -f $(OBJ_DEBUG) $(OUT_DEBUG) - rm -rf bin/Debug - rm -rf $(OBJDIR_DEBUG) - -before_release: - test -d bin/Release || mkdir -p bin/Release - test -d $(OBJDIR_RELEASE) || mkdir -p $(OBJDIR_RELEASE) - -after_release: - -release: before_release out_release after_release - -out_release: before_release $(OBJ_RELEASE) $(DEP_RELEASE) - $(LD) -shared $(LIBDIR_RELEASE) $(OBJ_RELEASE) -o $(OUT_RELEASE) $(LDFLAGS_RELEASE) $(LIB_RELEASE) - -$(OBJDIR_RELEASE)/main.o: main.cpp - $(CXX) $(CFLAGS_RELEASE) $(INC_RELEASE) -c main.cpp -o $(OBJDIR_RELEASE)/main.o - -$(OBJDIR_RELEASE)/tiny_obj_loader.o: ../tiny_obj_loader.cc - $(CC) $(CFLAGS_RELEASE) $(INC_RELEASE) -c ../tiny_obj_loader.cc -o $(OBJDIR_RELEASE)/tiny_obj_loader.o - -clean_release: - rm -f $(OBJ_RELEASE) $(OUT_RELEASE) - rm -rf bin/Release - rm -rf $(OBJDIR_RELEASE) - -.PHONY: before_debug after_debug clean_debug before_release after_release clean_release - -- cgit v1.2.3 From e7e7eed616cee946a10b5239545a75c2c968919e Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Sat, 7 Nov 2015 23:08:39 +0900 Subject: Change API and handle no-mtl-file case correctly. Fixes #58. --- examples/obj_sticher/obj_sticher.cc | 5 +- missing_material_file.obj | 145 ++++++++++++++++++++++++++++++++++++ no_material.obj | 133 +++++++++++++++++++++++++++++++++ test-nan.obj | 145 ++++++++++++++++++++++++++++++++++++ test.cc | 21 ++++-- tiny_obj_loader.cc | 61 ++++++++------- tiny_obj_loader.h | 40 +++++----- 7 files changed, 500 insertions(+), 50 deletions(-) create mode 100644 missing_material_file.obj create mode 100644 no_material.obj create mode 100644 test-nan.obj diff --git a/examples/obj_sticher/obj_sticher.cc b/examples/obj_sticher/obj_sticher.cc index 8ec7a28..f59fee4 100644 --- a/examples/obj_sticher/obj_sticher.cc +++ b/examples/obj_sticher/obj_sticher.cc @@ -86,9 +86,12 @@ main( for (int i = 0; i < num_objfiles; i++) { std::cout << "Loading " << argv[i+1] << " ... " << std::flush; - std::string err = tinyobj::LoadObj(shapes[i], materials[i], argv[i+1]); + std::string err; + bool ret = tinyobj::LoadObj(shapes[i], materials[i], err, argv[i+1]); if (!err.empty()) { std::cerr << err << std::endl; + } + if (!ret) { exit(1); } diff --git a/missing_material_file.obj b/missing_material_file.obj new file mode 100644 index 0000000..9e1d98c --- /dev/null +++ b/missing_material_file.obj @@ -0,0 +1,145 @@ +# cornell_box.obj and cornell_box.mtl are grabbed from Intel's embree project. +# original cornell box data + # comment + +# empty line including some space + + +#mtllib no_material.mtl + +o floor +usemtl white +v 552.8 0.0 0.0 +v 0.0 0.0 0.0 +v 0.0 0.0 559.2 +v 549.6 0.0 559.2 + +v 130.0 0.0 65.0 +v 82.0 0.0 225.0 +v 240.0 0.0 272.0 +v 290.0 0.0 114.0 + +v 423.0 0.0 247.0 +v 265.0 0.0 296.0 +v 314.0 0.0 456.0 +v 472.0 0.0 406.0 + +f 1 2 3 4 +f 8 7 6 5 +f 12 11 10 9 + +o light +usemtl light +v 343.0 548.0 227.0 +v 343.0 548.0 332.0 +v 213.0 548.0 332.0 +v 213.0 548.0 227.0 +f -4 -3 -2 -1 + +o ceiling +usemtl white +v 556.0 548.8 0.0 +v 556.0 548.8 559.2 +v 0.0 548.8 559.2 +v 0.0 548.8 0.0 +f -4 -3 -2 -1 + +o back_wall +usemtl white +v 549.6 0.0 559.2 +v 0.0 0.0 559.2 +v 0.0 548.8 559.2 +v 556.0 548.8 559.2 +f -4 -3 -2 -1 + +o front_wall +usemtl blue +v 549.6 0.0 0 +v 0.0 0.0 0 +v 0.0 548.8 0 +v 556.0 548.8 0 +#f -1 -2 -3 -4 + +o green_wall +usemtl green +v 0.0 0.0 559.2 +v 0.0 0.0 0.0 +v 0.0 548.8 0.0 +v 0.0 548.8 559.2 +f -4 -3 -2 -1 + +o red_wall +usemtl red +v 552.8 0.0 0.0 +v 549.6 0.0 559.2 +v 556.0 548.8 559.2 +v 556.0 548.8 0.0 +f -4 -3 -2 -1 + +o short_block +usemtl white + +v 130.0 165.0 65.0 +v 82.0 165.0 225.0 +v 240.0 165.0 272.0 +v 290.0 165.0 114.0 +f -4 -3 -2 -1 + +v 290.0 0.0 114.0 +v 290.0 165.0 114.0 +v 240.0 165.0 272.0 +v 240.0 0.0 272.0 +f -4 -3 -2 -1 + +v 130.0 0.0 65.0 +v 130.0 165.0 65.0 +v 290.0 165.0 114.0 +v 290.0 0.0 114.0 +f -4 -3 -2 -1 + +v 82.0 0.0 225.0 +v 82.0 165.0 225.0 +v 130.0 165.0 65.0 +v 130.0 0.0 65.0 +f -4 -3 -2 -1 + +v 240.0 0.0 272.0 +v 240.0 165.0 272.0 +v 82.0 165.0 225.0 +v 82.0 0.0 225.0 +f -4 -3 -2 -1 + +o tall_block +usemtl white + +v 423.0 330.0 247.0 +v 265.0 330.0 296.0 +v 314.0 330.0 456.0 +v 472.0 330.0 406.0 +f -4 -3 -2 -1 + +usemtl white +v 423.0 0.0 247.0 +v 423.0 330.0 247.0 +v 472.0 330.0 406.0 +v 472.0 0.0 406.0 +f -4 -3 -2 -1 + +v 472.0 0.0 406.0 +v 472.0 330.0 406.0 +v 314.0 330.0 456.0 +v 314.0 0.0 456.0 +f -4 -3 -2 -1 + +v 314.0 0.0 456.0 +v 314.0 330.0 456.0 +v 265.0 330.0 296.0 +v 265.0 0.0 296.0 +f -4 -3 -2 -1 + +v 265.0 0.0 296.0 +v 265.0 330.0 296.0 +v 423.0 330.0 247.0 +v 423.0 0.0 247.0 +f -4 -3 -2 -1 + diff --git a/no_material.obj b/no_material.obj new file mode 100644 index 0000000..6f3688f --- /dev/null +++ b/no_material.obj @@ -0,0 +1,133 @@ +# cornell_box.obj and cornell_box.mtl are grabbed from Intel's embree project. +# original cornell box data + # comment + +# empty line including some space + + +o floor +v 552.8 0.0 0.0 +v 0.0 0.0 0.0 +v 0.0 0.0 559.2 +v 549.6 0.0 559.2 + +v 130.0 0.0 65.0 +v 82.0 0.0 225.0 +v 240.0 0.0 272.0 +v 290.0 0.0 114.0 + +v 423.0 0.0 247.0 +v 265.0 0.0 296.0 +v 314.0 0.0 456.0 +v 472.0 0.0 406.0 + +f 1 2 3 4 +f 8 7 6 5 +f 12 11 10 9 + +o light +v 343.0 548.0 227.0 +v 343.0 548.0 332.0 +v 213.0 548.0 332.0 +v 213.0 548.0 227.0 +f -4 -3 -2 -1 + +o ceiling +v 556.0 548.8 0.0 +v 556.0 548.8 559.2 +v 0.0 548.8 559.2 +v 0.0 548.8 0.0 +f -4 -3 -2 -1 + +o back_wall +v 549.6 0.0 559.2 +v 0.0 0.0 559.2 +v 0.0 548.8 559.2 +v 556.0 548.8 559.2 +f -4 -3 -2 -1 + +o front_wall +v 549.6 0.0 0 +v 0.0 0.0 0 +v 0.0 548.8 0 +v 556.0 548.8 0 +#f -1 -2 -3 -4 + +o green_wall +v 0.0 0.0 559.2 +v 0.0 0.0 0.0 +v 0.0 548.8 0.0 +v 0.0 548.8 559.2 +f -4 -3 -2 -1 + +o red_wall +v 552.8 0.0 0.0 +v 549.6 0.0 559.2 +v 556.0 548.8 559.2 +v 556.0 548.8 0.0 +f -4 -3 -2 -1 + +o short_block + +v 130.0 165.0 65.0 +v 82.0 165.0 225.0 +v 240.0 165.0 272.0 +v 290.0 165.0 114.0 +f -4 -3 -2 -1 + +v 290.0 0.0 114.0 +v 290.0 165.0 114.0 +v 240.0 165.0 272.0 +v 240.0 0.0 272.0 +f -4 -3 -2 -1 + +v 130.0 0.0 65.0 +v 130.0 165.0 65.0 +v 290.0 165.0 114.0 +v 290.0 0.0 114.0 +f -4 -3 -2 -1 + +v 82.0 0.0 225.0 +v 82.0 165.0 225.0 +v 130.0 165.0 65.0 +v 130.0 0.0 65.0 +f -4 -3 -2 -1 + +v 240.0 0.0 272.0 +v 240.0 165.0 272.0 +v 82.0 165.0 225.0 +v 82.0 0.0 225.0 +f -4 -3 -2 -1 + +o tall_block + +v 423.0 330.0 247.0 +v 265.0 330.0 296.0 +v 314.0 330.0 456.0 +v 472.0 330.0 406.0 +f -4 -3 -2 -1 + +v 423.0 0.0 247.0 +v 423.0 330.0 247.0 +v 472.0 330.0 406.0 +v 472.0 0.0 406.0 +f -4 -3 -2 -1 + +v 472.0 0.0 406.0 +v 472.0 330.0 406.0 +v 314.0 330.0 456.0 +v 314.0 0.0 456.0 +f -4 -3 -2 -1 + +v 314.0 0.0 456.0 +v 314.0 330.0 456.0 +v 265.0 330.0 296.0 +v 265.0 0.0 296.0 +f -4 -3 -2 -1 + +v 265.0 0.0 296.0 +v 265.0 330.0 296.0 +v 423.0 330.0 247.0 +v 423.0 0.0 247.0 +f -4 -3 -2 -1 + diff --git a/test-nan.obj b/test-nan.obj new file mode 100644 index 0000000..3c68925 --- /dev/null +++ b/test-nan.obj @@ -0,0 +1,145 @@ +# cornell_box.obj and cornell_box.mtl are grabbed from Intel's embree project. +# original cornell box data + # comment + +# empty line including some space + + +mtllib cornell_box.mtl + +o floor +usemtl white +v nan -nan nan +v inf -inf inf +v 1.#IND -1.#IND 1.#IND +v 1.#INF -1.#INF 1.#INF + +v 130.0 0.0 65.0 +v 82.0 0.0 225.0 +v 240.0 0.0 272.0 +v 290.0 0.0 114.0 + +v 423.0 0.0 247.0 +v 265.0 0.0 296.0 +v 314.0 0.0 456.0 +v 472.0 0.0 406.0 + +f 1 2 3 4 +f 8 7 6 5 +f 12 11 10 9 + +o light +usemtl light +v 343.0 548.0 227.0 +v 343.0 548.0 332.0 +v 213.0 548.0 332.0 +v 213.0 548.0 227.0 +f -4 -3 -2 -1 + +o ceiling +usemtl white +v 556.0 548.8 0.0 +v 556.0 548.8 559.2 +v 0.0 548.8 559.2 +v 0.0 548.8 0.0 +f -4 -3 -2 -1 + +o back_wall +usemtl white +v 549.6 0.0 559.2 +v 0.0 0.0 559.2 +v 0.0 548.8 559.2 +v 556.0 548.8 559.2 +f -4 -3 -2 -1 + +o front_wall +usemtl blue +v 549.6 0.0 0 +v 0.0 0.0 0 +v 0.0 548.8 0 +v 556.0 548.8 0 +#f -1 -2 -3 -4 + +o green_wall +usemtl green +v 0.0 0.0 559.2 +v 0.0 0.0 0.0 +v 0.0 548.8 0.0 +v 0.0 548.8 559.2 +f -4 -3 -2 -1 + +o red_wall +usemtl red +v 552.8 0.0 0.0 +v 549.6 0.0 559.2 +v 556.0 548.8 559.2 +v 556.0 548.8 0.0 +f -4 -3 -2 -1 + +o short_block +usemtl white + +v 130.0 165.0 65.0 +v 82.0 165.0 225.0 +v 240.0 165.0 272.0 +v 290.0 165.0 114.0 +f -4 -3 -2 -1 + +v 290.0 0.0 114.0 +v 290.0 165.0 114.0 +v 240.0 165.0 272.0 +v 240.0 0.0 272.0 +f -4 -3 -2 -1 + +v 130.0 0.0 65.0 +v 130.0 165.0 65.0 +v 290.0 165.0 114.0 +v 290.0 0.0 114.0 +f -4 -3 -2 -1 + +v 82.0 0.0 225.0 +v 82.0 165.0 225.0 +v 130.0 165.0 65.0 +v 130.0 0.0 65.0 +f -4 -3 -2 -1 + +v 240.0 0.0 272.0 +v 240.0 165.0 272.0 +v 82.0 165.0 225.0 +v 82.0 0.0 225.0 +f -4 -3 -2 -1 + +o tall_block +usemtl white + +v 423.0 330.0 247.0 +v 265.0 330.0 296.0 +v 314.0 330.0 456.0 +v 472.0 330.0 406.0 +f -4 -3 -2 -1 + +usemtl white +v 423.0 0.0 247.0 +v 423.0 330.0 247.0 +v 472.0 330.0 406.0 +v 472.0 0.0 406.0 +f -4 -3 -2 -1 + +v 472.0 0.0 406.0 +v 472.0 330.0 406.0 +v 314.0 330.0 456.0 +v 314.0 0.0 456.0 +f -4 -3 -2 -1 + +v 314.0 0.0 456.0 +v 314.0 330.0 456.0 +v 265.0 330.0 296.0 +v 265.0 0.0 296.0 +f -4 -3 -2 -1 + +v 265.0 0.0 296.0 +v 265.0 330.0 296.0 +v 423.0 330.0 247.0 +v 423.0 0.0 247.0 +f -4 -3 -2 -1 + diff --git a/test.cc b/test.cc index 3f16e3e..e012623 100644 --- a/test.cc +++ b/test.cc @@ -67,10 +67,15 @@ TestLoadObj( std::vector shapes; std::vector materials; - std::string err = tinyobj::LoadObj(shapes, materials, filename, basepath); + + std::string err; + bool ret = tinyobj::LoadObj(shapes, materials, err, filename, basepath); if (!err.empty()) { std::cerr << err << std::endl; + } + + if (!ret) { return false; } @@ -152,12 +157,14 @@ std::string matStream( public: MaterialStringStreamReader(const std::string& matSStream): m_matSStream(matSStream) {} virtual ~MaterialStringStreamReader() {} - virtual std::string operator() ( + virtual bool operator() ( const std::string& matId, std::vector& materials, - std::map& matMap) + std::map& matMap, + std::string& err) { - return LoadMtl(matMap, materials, m_matSStream); + LoadMtl(matMap, materials, m_matSStream); + return true; } private: @@ -167,10 +174,14 @@ std::string matStream( MaterialStringStreamReader matSSReader(matStream); std::vector shapes; std::vector materials; - std::string err = tinyobj::LoadObj(shapes, materials, objStream, matSSReader); + std::string err; + bool ret = tinyobj::LoadObj(shapes, materials, err, objStream, matSSReader); if (!err.empty()) { std::cerr << err << std::endl; + } + + if (!ret) { return false; } diff --git a/tiny_obj_loader.cc b/tiny_obj_loader.cc index 067f642..84c1a77 100644 --- a/tiny_obj_loader.cc +++ b/tiny_obj_loader.cc @@ -5,6 +5,7 @@ // // +// version 0.9.15: Change API to handle no mtl file case correctly(#58) // version 0.9.14: Support specular highlight, bump, displacement and alpha map(#53) // 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) @@ -415,10 +416,9 @@ static bool exportFaceGroupToShape( return true; } -std::string LoadMtl(std::map &material_map, - std::vector &materials, - std::istream &inStream) { - std::stringstream err; +void LoadMtl(std::map &material_map, + std::vector &materials, + std::istream &inStream) { // Create a default material anyway. material_t material; @@ -643,13 +643,12 @@ std::string LoadMtl(std::map &material_map, material_map.insert( std::pair(material.name, static_cast(materials.size()))); materials.push_back(material); - - return err.str(); } -std::string MaterialFileReader::operator()(const std::string &matId, - std::vector &materials, - std::map &matMap) { +bool MaterialFileReader::operator()(const std::string &matId, + std::vector &materials, + std::map &matMap, + std::string& err) { std::string filepath; if (!m_mtlBasePath.empty()) { @@ -659,27 +658,29 @@ std::string MaterialFileReader::operator()(const std::string &matId, } std::ifstream matIStream(filepath.c_str()); - std::string err = 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(); } - return err; + return true; } -std::string LoadObj(std::vector &shapes, - std::vector &materials, // [output] - const char *filename, const char *mtl_basepath) { +bool LoadObj(std::vector &shapes, // [output] + std::vector &materials, // [output] + std::string &err, + const char *filename, const char *mtl_basepath) { shapes.clear(); - std::stringstream err; + std::stringstream errss; std::ifstream ifs(filename); if (!ifs) { - err << "Cannot open file [" << filename << "]" << std::endl; - return err.str(); + errss << "Cannot open file [" << filename << "]" << std::endl; + err = errss.str(); + return false; } std::string basePath; @@ -688,13 +689,14 @@ std::string LoadObj(std::vector &shapes, } MaterialFileReader matFileReader(basePath); - return LoadObj(shapes, materials, ifs, matFileReader); + return LoadObj(shapes, materials, err, ifs, matFileReader); } -std::string LoadObj(std::vector &shapes, - std::vector &materials, // [output] - std::istream &inStream, MaterialReader &readMatFn) { - std::stringstream err; +bool LoadObj(std::vector &shapes, // [output] + std::vector &materials, // [output] + std::string& err, + std::istream &inStream, MaterialReader &readMatFn) { + std::stringstream errss; std::vector v; std::vector vn; @@ -833,10 +835,13 @@ std::string LoadObj(std::vector &shapes, sscanf(token, "%s", namebuf); #endif - std::string err_mtl = readMatFn(namebuf, materials, material_map); - if (!err_mtl.empty()) { + std::string err_mtl; + bool ok = readMatFn(namebuf, materials, material_map, err_mtl); + err += err_mtl; + + if (!ok) { faceGroup.clear(); // for safety - return err_mtl; + return false; } continue; @@ -913,6 +918,8 @@ std::string LoadObj(std::vector &shapes, } faceGroup.clear(); // for safety - return err.str(); -} + err += errss.str(); + return true; } + +} // namespace diff --git a/tiny_obj_loader.h b/tiny_obj_loader.h index 00259e7..0a65b3a 100644 --- a/tiny_obj_loader.h +++ b/tiny_obj_loader.h @@ -54,9 +54,10 @@ public: MaterialReader() {} virtual ~MaterialReader() {} - virtual std::string operator()(const std::string &matId, - std::vector &materials, - std::map &matMap) = 0; + virtual bool operator()(const std::string &matId, + std::vector &materials, + std::map &matMap, + std::string &err) = 0; }; class MaterialFileReader : public MaterialReader { @@ -64,9 +65,10 @@ 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); + virtual bool operator()(const std::string &matId, + std::vector &materials, + std::map &matMap, + std::string &err); private: std::string m_mtlBasePath; @@ -75,23 +77,27 @@ private: /// Loads .obj from a file. /// 'shapes' will be filled with parsed shape data /// The function returns error string. -/// Returns empty string when loading .obj success. +/// 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. -std::string LoadObj(std::vector &shapes, // [output] - std::vector &materials, // [output] - const char *filename, const char *mtl_basepath = NULL); +bool LoadObj(std::vector &shapes, // [output] + std::vector &materials, // [output] + std::string& err, // [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); +/// 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); /// Loads materials into std::map -/// Returns an empty string if successful -std::string LoadMtl(std::map &material_map, - std::vector &materials, std::istream &inStream); +void LoadMtl(std::map &material_map, // [output] + std::vector &materials, // [output] + std::istream &inStream); } #endif // _TINY_OBJ_LOADER_H -- cgit v1.2.3 From a13e1d6164a61a57e359aa1f9c6145477b42eda7 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Sat, 7 Nov 2015 23:10:42 +0900 Subject: Update python binding to match new API. --- python/main.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/python/main.cpp b/python/main.cpp index 6ae89cc..f772793 100644 --- a/python/main.cpp +++ b/python/main.cpp @@ -44,7 +44,8 @@ pyLoadObj(PyObject* self, PyObject* args) if(!PyArg_ParseTuple(args, "s", &filename)) return NULL; - tinyobj::LoadObj(shapes, materials, filename); + std::string err; + tinyobj::LoadObj(shapes, materials, err, filename); pyshapes = PyDict_New(); pymaterials = PyDict_New(); -- cgit v1.2.3 From 3681c44aa3a15181b0acde8e5da865fb37eb9e66 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Sat, 7 Nov 2015 23:14:12 +0900 Subject: Update README. --- README.md | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 6ee59f6..10ba57a 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,7 @@ Tiny but poweful single file wavefront obj loader written in C++. No dependency What's new ---------- +* Nov 08, 2015 : Improved API. * Jun 23, 2015 : Various fixes and added more projects using tinyobjloader. Thanks many contributors! * Mar 03, 2015 : Replace atof() with hand-written parser for robust reading of numeric value. Thanks skurmedel! * Feb 06, 2015 : Fix parsing multi-material object @@ -85,10 +86,14 @@ Usage std::vector shapes; std::vector materials; - std::string err = tinyobj::LoadObj(shapes, materials, inputfile.c_str()); + std::string err; + bool ret = tinyobj::LoadObj(shapes, materials, inputfile.c_str()); - if (!err.empty()) { + if (!err.empty()) { // `err` may contain warning message. std::cerr << err << std::endl; + } + + if (!ret) { exit(1); } -- cgit v1.2.3 From c1be55ffe2a6eaa35e54d21ef939590c80f16fda Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Thu, 12 Nov 2015 10:50:40 +0900 Subject: Udate README. Fixes #60. --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 10ba57a..780c24d 100644 --- a/README.md +++ b/README.md @@ -87,7 +87,7 @@ Usage std::vector materials; std::string err; - bool ret = tinyobj::LoadObj(shapes, materials, inputfile.c_str()); + bool ret = tinyobj::LoadObj(shapes, materials, err, inputfile.c_str()); if (!err.empty()) { // `err` may contain warning message. std::cerr << err << std::endl; @@ -133,7 +133,7 @@ Usage printf(" material.map_Ka = %s\n", materials[i].ambient_texname.c_str()); printf(" material.map_Kd = %s\n", materials[i].diffuse_texname.c_str()); printf(" material.map_Ks = %s\n", materials[i].specular_texname.c_str()); - printf(" material.map_Ns = %s\n", materials[i].normal_texname.c_str()); + printf(" material.map_Ns = %s\n", materials[i].specular_highlight_texname.c_str()); std::map::const_iterator it(materials[i].unknown_parameter.begin()); std::map::const_iterator itEnd(materials[i].unknown_parameter.end()); for (; it != itEnd; it++) { -- cgit v1.2.3 From ec78aa2bd1a38630fc8de70188a0a53cdaa524ea Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Wed, 18 Nov 2015 22:44:18 +0900 Subject: Add Cocos2d-x. --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 780c24d..69bc859 100644 --- a/README.md +++ b/README.md @@ -52,6 +52,7 @@ TinyObjLoader is successfully used in ... * Awesome Bump http://awesomebump.besaba.com/about/ * sdlgl3-wavefront OpenGL .obj viewer https://github.com/chrisliebert/sdlgl3-wavefront * pbrt-v3 https://https://github.com/mmp/pbrt-v3 +* cocos2d-x https://github.com/cocos2d/cocos2d-x/ * Your project here! Features -- cgit v1.2.3 From 353d527fe8bf9685548f3bb74fdb0f1da5751d10 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Wed, 18 Nov 2015 22:44:18 +0900 Subject: Add Cocos2d-x. Make tinyobjloader header-only. --- README.md | 5 + premake4.lua | 6 +- test.cc | 1 + tiny_obj_loader.cc | 925 +--------------------------------------------------- tiny_obj_loader.h | 932 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 940 insertions(+), 929 deletions(-) diff --git a/README.md b/README.md index 780c24d..77dc472 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,7 @@ Tiny but poweful single file wavefront obj loader written in C++. No dependency What's new ---------- +* Nov 26, 2015 : Now single-header only!. * Nov 08, 2015 : Improved API. * Jun 23, 2015 : Various fixes and added more projects using tinyobjloader. Thanks many contributors! * Mar 03, 2015 : Replace atof() with hand-written parser for robust reading of numeric value. Thanks skurmedel! @@ -52,6 +53,7 @@ TinyObjLoader is successfully used in ... * Awesome Bump http://awesomebump.besaba.com/about/ * sdlgl3-wavefront OpenGL .obj viewer https://github.com/chrisliebert/sdlgl3-wavefront * pbrt-v3 https://https://github.com/mmp/pbrt-v3 +* cocos2d-x https://github.com/cocos2d/cocos2d-x/ * Your project here! Features @@ -82,6 +84,9 @@ Licensed under 2 clause BSD. Usage ----- + #define TINYOBJLOADER_IMPLEMENTATION // define this in only *one* .cc + #include "tiny_obj_loader.h" + std::string inputfile = "cornell_box.obj"; std::vector shapes; std::vector materials; diff --git a/premake4.lua b/premake4.lua index ad020a6..cbfdfd9 100644 --- a/premake4.lua +++ b/premake4.lua @@ -1,7 +1,3 @@ -lib_sources = { - "tiny_obj_loader.cc" -} - sources = { "test.cc", } @@ -20,7 +16,7 @@ solution "TinyObjLoaderSolution" project "tinyobjloader" kind "ConsoleApp" language "C++" - files { lib_sources, sources } + files { sources } configuration "Debug" defines { "DEBUG" } -- -DDEBUG diff --git a/test.cc b/test.cc index e012623..46673eb 100644 --- a/test.cc +++ b/test.cc @@ -1,3 +1,4 @@ +#define TINYOBJLOADER_IMPLEMENTATION #include "tiny_obj_loader.h" #include diff --git a/tiny_obj_loader.cc b/tiny_obj_loader.cc index 84c1a77..e57d044 100644 --- a/tiny_obj_loader.cc +++ b/tiny_obj_loader.cc @@ -1,925 +1,2 @@ -// -// Copyright 2012-2015, Syoyo Fujita. -// -// Licensed under 2-clause BSD liecense. -// - -// -// version 0.9.15: Change API to handle no mtl file case correctly(#58) -// version 0.9.14: Support specular highlight, bump, displacement and alpha map(#53) -// 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.11: Invert `Tr` parameter(#43) -// version 0.9.10: Fix seg fault on windows. -// version 0.9.9 : Replace atof() with custom parser. -// version 0.9.8 : Fix multi-materials(per-face material ID). -// version 0.9.7 : Support multi-materials(per-face material ID) per -// object/group. -// 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. -// 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 -// version 0.9.1 : Add initial .mtl load support -// version 0.9.0 : Initial -// - -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include - +#define TINYOBJLOADER_IMPLEMENTATION #include "tiny_obj_loader.h" - -namespace tinyobj { - -#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){}; -}; -// 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); - - return false; -} - -struct obj_shape { - 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'); -} - -// 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 -} - -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; -} - -static inline int parseInt(const char *&token) { - 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 -// stop. For example at the end of the string, to prevent buffer overflows. -// -// Parses the following EBNF grammar: -// sign = "+" | "-" ; -// END = ? anything not in digit ? -// digit = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" ; -// integer = [sign] , digit , {digit} ; -// decimal = integer , ["." , integer] ; -// float = ( decimal , END ) | ( decimal , ("E" | "e") , integer , END ) ; -// -// 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 -// is returned. -// -// The function is greedy and will parse until any of the following happens: -// - a non-conforming character is encountered. -// - s_end is reached. -// -// 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) - { - 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 - // 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 - // exponent to be in base 2. - int exponent = 0; - - // NOTE: THESE MUST BE DECLARED HERE SINCE WE ARE NOT ALLOWED - // TO JUMP OVER DEFINITIONS. - char sign = '+'; - char exp_sign = '+'; - char const *curr = s; - - // 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; - - /* - BEGIN PARSING. - */ - - // Find out what sign we've got. - if (*curr == '+' || *curr == '-') - { - sign = *curr; - curr++; - } - else if (isdigit(*curr)) { /* Pass through. */ } - else - { - goto fail; - } - - // Read the integer part. - while ((end_not_reached = (curr != s_end)) && isdigit(*curr)) - { - mantissa *= 10; - mantissa += static_cast(*curr - 0x30); - curr++; read++; - } - - // We must make sure we actually got something. - if (read == 0) - goto fail; - // We allow numbers of form "#", "###" etc. - if (!end_not_reached) - goto assemble; - - // Read the decimal part. - if (*curr == '.') - { - curr++; - read = 1; - 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++; - } - } - else if (*curr == 'e' || *curr == 'E') {} - else - { - goto assemble; - } - - if (!end_not_reached) - goto assemble; - - // Read the exponent part. - 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 == '-')) - { - exp_sign = *curr; - curr++; - } - 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)) - { - exponent *= 10; - exponent += static_cast(*curr - 0x30); - curr++; read++; - } - exponent *= (exp_sign == '+'? 1 : -1); - if (read == 0) - goto fail; - } - -assemble: - *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"); -#ifdef TINY_OBJ_LOADER_OLD_FLOAT_PARSER - 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; -#endif - return f; -} - - -static inline void parseFloat2(float &x, float &y, const char *&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); -} - -// 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); - - 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/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; -} - -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() > (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]); - - 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]); - } - - unsigned int idx = static_cast(positions.size() / 3 - 1); - vertexCache[i] = idx; - - return idx; -} - -void InitMaterial(material_t &material) { - material.name = ""; - material.ambient_texname = ""; - material.diffuse_texname = ""; - material.specular_texname = ""; - material.specular_highlight_texname = ""; - material.bump_texname = ""; - material.displacement_texname = ""; - material.alpha_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; - } - - // 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]; - - size_t npolys = face.size(); - - // 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); - - 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.name = name; - - if (clearCache) - vertexCache.clear(); - - return true; -} - -void LoadMtl(std::map &material_map, - std::vector &materials, - std::istream &inStream) { - - // 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); - - 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); - } - - // Skip if empty line. - if (linebuf.empty()) { - continue; - } - - // Skip leading space. - const char *token = linebuf.c_str(); - token += strspn(token, " \t"); - - assert(token); - if (token[0] == '\0') - continue; // empty 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); - } - - // initial temporary material - InitMaterial(material); - - // set new mtl name - char namebuf[TINYOBJ_SSCANF_BUFFER_SIZE]; - token += 7; -#ifdef _MSC_VER - sscanf_s(token, "%s", namebuf, (unsigned)_countof(namebuf)); -#else - sscanf(token, "%s", namebuf); -#endif - 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; - } - - // 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; - } - - // 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; - } - - // 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; - } - - // 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; - } - - // 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; - } - - // specular texture - if ((0 == strncmp(token, "map_Ks", 6)) && isSpace(token[6])) { - token += 7; - material.specular_texname = token; - continue; - } - - // specular highlight texture - if ((0 == strncmp(token, "map_Ns", 6)) && isSpace(token[6])) { - token += 7; - material.specular_highlight_texname = token; - continue; - } - - // bump texture - if ((0 == strncmp(token, "map_bump", 8)) && isSpace(token[8])) { - token += 9; - material.bump_texname = token; - continue; - } - - // alpha texture - if ((0 == strncmp(token, "map_d", 5)) && isSpace(token[5])) { - token += 6; - material.alpha_texname = token; - continue; - } - - // bump texture - if ((0 == strncmp(token, "bump", 4)) && isSpace(token[4])) { - token += 5; - material.bump_texname = token; - continue; - } - - // displacement texture - if ((0 == strncmp(token, "disp", 4)) && isSpace(token[4])) { - token += 5; - material.displacement_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); -} - -bool MaterialFileReader::operator()(const std::string &matId, - std::vector &materials, - std::map &matMap, - std::string& err) { - std::string filepath; - - if (!m_mtlBasePath.empty()) { - filepath = std::string(m_mtlBasePath) + matId; - } else { - filepath = matId; - } - - std::ifstream matIStream(filepath.c_str()); - LoadMtl(matMap, materials, matIStream); - if (!matIStream) { - std::stringstream ss; - ss << "WARN: Material file [ " << filepath << " ] not found. Created a default material."; - 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) { - - shapes.clear(); - - std::stringstream errss; - - std::ifstream ifs(filename); - if (!ifs) { - errss << "Cannot open file [" << filename << "]" << std::endl; - err = errss.str(); - return false; - } - - std::string basePath; - if (mtl_basepath) { - basePath = mtl_basepath; - } - MaterialFileReader matFileReader(basePath); - - return LoadObj(shapes, materials, err, ifs, matFileReader); -} - -bool LoadObj(std::vector &shapes, // [output] - std::vector &materials, // [output] - std::string& err, - std::istream &inStream, MaterialReader &readMatFn) { - std::stringstream errss; - - 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; - - shape_t shape; - - 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]); - - // 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 leading space. - const char *token = linebuf.c_str(); - token += strspn(token, " \t"); - - assert(token); - if (token[0] == '\0') - continue; // empty line - - if (token[0] == '#') - continue; // comment 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; - } - - // 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; - } - - // 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; - } - - // face - if (token[0] == 'f' && isSpace((token[1]))) { - token += 2; - token += strspn(token, " \t"); - - 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; - } - - faceGroup.push_back(face); - - continue; - } - - // 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, (unsigned)_countof(namebuf)); -#else - 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(); - - if (material_map.find(namebuf) != material_map.end()) { - material = material_map[namebuf]; - } else { - // { error!! material not found } - material = -1; - } - - continue; - } - - // 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, (unsigned)_countof(namebuf)); -#else - sscanf(token, "%s", namebuf); -#endif - - std::string err_mtl; - bool ok = readMatFn(namebuf, materials, material_map, err_mtl); - err += err_mtl; - - if (!ok) { - faceGroup.clear(); // for safety - return false; - } - - continue; - } - - // 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); - } - - shape = shape_t(); - - // 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 - } - - assert(names.size() > 0); - - // names[0] must be 'g', so skip the 0th element. - if (names.size() > 1) { - name = names[1]; - } else { - name = ""; - } - - continue; - } - - // 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); - } - - // material = -1; - faceGroup.clear(); - shape = shape_t(); - - // @todo { multiple object name? } - char namebuf[TINYOBJ_SSCANF_BUFFER_SIZE]; - token += 2; -#ifdef _MSC_VER - sscanf_s(token, "%s", namebuf, (unsigned)_countof(namebuf)); -#else - sscanf(token, "%s", namebuf); -#endif - name = std::string(namebuf); - - 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 - - err += errss.str(); - return true; -} - -} // namespace diff --git a/tiny_obj_loader.h b/tiny_obj_loader.h index 0a65b3a..a0834e0 100644 --- a/tiny_obj_loader.h +++ b/tiny_obj_loader.h @@ -3,6 +3,36 @@ // // Licensed under 2-clause BSD liecense. // + +// +// version 0.9.16: Make tinyobjloader header-only +// version 0.9.15: Change API to handle no mtl file case correctly(#58) +// version 0.9.14: Support specular highlight, bump, displacement and alpha map(#53) +// 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.11: Invert `Tr` parameter(#43) +// version 0.9.10: Fix seg fault on windows. +// version 0.9.9 : Replace atof() with custom parser. +// version 0.9.8 : Fix multi-materials(per-face material ID). +// version 0.9.7 : Support multi-materials(per-face material ID) per +// object/group. +// 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. +// 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 +// version 0.9.1 : Add initial .mtl load support +// version 0.9.0 : Initial +// + +// +// Use this in *one* .cc +// #define TINYOBJLOADER_IMPLEMENTATION +// #include "tiny_obj_loader.h" +// + #ifndef _TINY_OBJ_LOADER_H #define _TINY_OBJ_LOADER_H @@ -100,4 +130,906 @@ void LoadMtl(std::map &material_map, // [output] std::istream &inStream); } +#ifdef TINYOBJLOADER_IMPLEMENTATION +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "tiny_obj_loader.h" + +namespace tinyobj { + +#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){}; +}; +// 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); + + return false; +} + +struct obj_shape { + 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'); +} + +// 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 +} + +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; +} + +static inline int parseInt(const char *&token) { + 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 +// stop. For example at the end of the string, to prevent buffer overflows. +// +// Parses the following EBNF grammar: +// sign = "+" | "-" ; +// END = ? anything not in digit ? +// digit = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" ; +// integer = [sign] , digit , {digit} ; +// decimal = integer , ["." , integer] ; +// float = ( decimal , END ) | ( decimal , ("E" | "e") , integer , END ) ; +// +// 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 +// is returned. +// +// The function is greedy and will parse until any of the following happens: +// - a non-conforming character is encountered. +// - s_end is reached. +// +// 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) + { + 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 + // 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 + // exponent to be in base 2. + int exponent = 0; + + // NOTE: THESE MUST BE DECLARED HERE SINCE WE ARE NOT ALLOWED + // TO JUMP OVER DEFINITIONS. + char sign = '+'; + char exp_sign = '+'; + char const *curr = s; + + // 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; + + /* + BEGIN PARSING. + */ + + // Find out what sign we've got. + if (*curr == '+' || *curr == '-') + { + sign = *curr; + curr++; + } + else if (isdigit(*curr)) { /* Pass through. */ } + else + { + goto fail; + } + + // Read the integer part. + while ((end_not_reached = (curr != s_end)) && isdigit(*curr)) + { + mantissa *= 10; + mantissa += static_cast(*curr - 0x30); + curr++; read++; + } + + // We must make sure we actually got something. + if (read == 0) + goto fail; + // We allow numbers of form "#", "###" etc. + if (!end_not_reached) + goto assemble; + + // Read the decimal part. + if (*curr == '.') + { + curr++; + read = 1; + 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++; + } + } + else if (*curr == 'e' || *curr == 'E') {} + else + { + goto assemble; + } + + if (!end_not_reached) + goto assemble; + + // Read the exponent part. + 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 == '-')) + { + exp_sign = *curr; + curr++; + } + 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)) + { + exponent *= 10; + exponent += static_cast(*curr - 0x30); + curr++; read++; + } + exponent *= (exp_sign == '+'? 1 : -1); + if (read == 0) + goto fail; + } + +assemble: + *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"); +#ifdef TINY_OBJ_LOADER_OLD_FLOAT_PARSER + 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; +#endif + return f; +} + + +static inline void parseFloat2(float &x, float &y, const char *&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); +} + +// 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); + + 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/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; +} + +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() > (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]); + + 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]); + } + + unsigned int idx = static_cast(positions.size() / 3 - 1); + vertexCache[i] = idx; + + return idx; +} + +void InitMaterial(material_t &material) { + material.name = ""; + material.ambient_texname = ""; + material.diffuse_texname = ""; + material.specular_texname = ""; + material.specular_highlight_texname = ""; + material.bump_texname = ""; + material.displacement_texname = ""; + material.alpha_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; + } + + // 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]; + + size_t npolys = face.size(); + + // 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); + + 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.name = name; + + if (clearCache) + vertexCache.clear(); + + return true; +} + +void LoadMtl(std::map &material_map, + std::vector &materials, + std::istream &inStream) { + + // 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); + + 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); + } + + // Skip if empty line. + if (linebuf.empty()) { + continue; + } + + // Skip leading space. + const char *token = linebuf.c_str(); + token += strspn(token, " \t"); + + assert(token); + if (token[0] == '\0') + continue; // empty 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); + } + + // initial temporary material + InitMaterial(material); + + // set new mtl name + char namebuf[TINYOBJ_SSCANF_BUFFER_SIZE]; + token += 7; +#ifdef _MSC_VER + sscanf_s(token, "%s", namebuf, (unsigned)_countof(namebuf)); +#else + sscanf(token, "%s", namebuf); +#endif + 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; + } + + // 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; + } + + // 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; + } + + // 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; + } + + // 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; + } + + // 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; + } + + // specular texture + if ((0 == strncmp(token, "map_Ks", 6)) && isSpace(token[6])) { + token += 7; + material.specular_texname = token; + continue; + } + + // specular highlight texture + if ((0 == strncmp(token, "map_Ns", 6)) && isSpace(token[6])) { + token += 7; + material.specular_highlight_texname = token; + continue; + } + + // bump texture + if ((0 == strncmp(token, "map_bump", 8)) && isSpace(token[8])) { + token += 9; + material.bump_texname = token; + continue; + } + + // alpha texture + if ((0 == strncmp(token, "map_d", 5)) && isSpace(token[5])) { + token += 6; + material.alpha_texname = token; + continue; + } + + // bump texture + if ((0 == strncmp(token, "bump", 4)) && isSpace(token[4])) { + token += 5; + material.bump_texname = token; + continue; + } + + // displacement texture + if ((0 == strncmp(token, "disp", 4)) && isSpace(token[4])) { + token += 5; + material.displacement_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); +} + +bool MaterialFileReader::operator()(const std::string &matId, + std::vector &materials, + std::map &matMap, + std::string& err) { + std::string filepath; + + if (!m_mtlBasePath.empty()) { + filepath = std::string(m_mtlBasePath) + matId; + } else { + filepath = matId; + } + + std::ifstream matIStream(filepath.c_str()); + LoadMtl(matMap, materials, matIStream); + if (!matIStream) { + std::stringstream ss; + ss << "WARN: Material file [ " << filepath << " ] not found. Created a default material."; + 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) { + + shapes.clear(); + + std::stringstream errss; + + std::ifstream ifs(filename); + if (!ifs) { + errss << "Cannot open file [" << filename << "]" << std::endl; + err = errss.str(); + return false; + } + + std::string basePath; + if (mtl_basepath) { + basePath = mtl_basepath; + } + MaterialFileReader matFileReader(basePath); + + return LoadObj(shapes, materials, err, ifs, matFileReader); +} + +bool LoadObj(std::vector &shapes, // [output] + std::vector &materials, // [output] + std::string& err, + std::istream &inStream, MaterialReader &readMatFn) { + std::stringstream errss; + + 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; + + shape_t shape; + + 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]); + + // 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 leading space. + const char *token = linebuf.c_str(); + token += strspn(token, " \t"); + + assert(token); + if (token[0] == '\0') + continue; // empty line + + if (token[0] == '#') + continue; // comment 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; + } + + // 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; + } + + // 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; + } + + // face + if (token[0] == 'f' && isSpace((token[1]))) { + token += 2; + token += strspn(token, " \t"); + + 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; + } + + faceGroup.push_back(face); + + continue; + } + + // 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, (unsigned)_countof(namebuf)); +#else + 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(); + + if (material_map.find(namebuf) != material_map.end()) { + material = material_map[namebuf]; + } else { + // { error!! material not found } + material = -1; + } + + continue; + } + + // 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, (unsigned)_countof(namebuf)); +#else + sscanf(token, "%s", namebuf); +#endif + + std::string err_mtl; + bool ok = readMatFn(namebuf, materials, material_map, err_mtl); + err += err_mtl; + + if (!ok) { + faceGroup.clear(); // for safety + return false; + } + + continue; + } + + // 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); + } + + shape = shape_t(); + + // 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 + } + + assert(names.size() > 0); + + // names[0] must be 'g', so skip the 0th element. + if (names.size() > 1) { + name = names[1]; + } else { + name = ""; + } + + continue; + } + + // 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); + } + + // material = -1; + faceGroup.clear(); + shape = shape_t(); + + // @todo { multiple object name? } + char namebuf[TINYOBJ_SSCANF_BUFFER_SIZE]; + token += 2; +#ifdef _MSC_VER + sscanf_s(token, "%s", namebuf, (unsigned)_countof(namebuf)); +#else + sscanf(token, "%s", namebuf); +#endif + name = std::string(namebuf); + + 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 + + err += errss.str(); + return true; +} + +} // namespace + + +#endif + #endif // _TINY_OBJ_LOADER_H -- cgit v1.2.3 From 783b8c48869fa5dbafbcf27e5d55f81a67ad5a80 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Sat, 5 Dec 2015 12:34:28 +0900 Subject: Suppress clang compiler warnings. --- test.cc | 2 ++ tiny_obj_loader.h | 46 +++++++++++++++++++++++++--------------------- 2 files changed, 27 insertions(+), 21 deletions(-) diff --git a/test.cc b/test.cc index 46673eb..f120c69 100644 --- a/test.cc +++ b/test.cc @@ -164,6 +164,8 @@ std::string matStream( std::map& matMap, std::string& err) { + (void)matId; + (void)err; LoadMtl(matMap, materials, m_matSStream); return true; } diff --git a/tiny_obj_loader.h b/tiny_obj_loader.h index a0834e0..3f0e7b1 100644 --- a/tiny_obj_loader.h +++ b/tiny_obj_loader.h @@ -33,8 +33,8 @@ // #include "tiny_obj_loader.h" // -#ifndef _TINY_OBJ_LOADER_H -#define _TINY_OBJ_LOADER_H +#ifndef TINY_OBJ_LOADER_H +#define TINY_OBJ_LOADER_H #include #include @@ -56,6 +56,8 @@ typedef struct { // illumination model (see http://www.fileformat.info/format/material/) int illum; + int dummy; // Supress padding warning. + std::string ambient_texname; // map_Ka std::string diffuse_texname; // map_Kd std::string specular_texname; // map_Ks @@ -82,7 +84,7 @@ typedef struct { class MaterialReader { public: MaterialReader() {} - virtual ~MaterialReader() {} + virtual ~MaterialReader(); virtual bool operator()(const std::string &matId, std::vector &materials, @@ -148,14 +150,16 @@ void LoadMtl(std::map &material_map, // [output] namespace tinyobj { +MaterialReader::~MaterialReader() {} + #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(){} + 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){}; + : v_idx(vidx), vt_idx(vtidx), vn_idx(vnidx){} }; // for std::map static inline bool operator<(const vertex_index &a, const vertex_index &b) { @@ -423,21 +427,21 @@ updateVertex(std::map &vertexCache, return it->second; } - assert(in_positions.size() > (unsigned int)(3 * i.v_idx + 2)); + assert(in_positions.size() > static_cast(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 * 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) { - 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]); + 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) { - texcoords.push_back(in_texcoords[2 * i.vt_idx + 0]); - texcoords.push_back(in_texcoords[2 * i.vt_idx + 1]); + 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); @@ -446,7 +450,7 @@ updateVertex(std::map &vertexCache, return idx; } -void InitMaterial(material_t &material) { +static void InitMaterial(material_t &material) { material.name = ""; material.ambient_texname = ""; material.diffuse_texname = ""; @@ -529,10 +533,10 @@ void LoadMtl(std::map &material_map, material_t material; InitMaterial(material); - int maxchars = 8192; // Alloc enough size. + size_t maxchars = 8192; // Alloc enough size. std::vector buf(maxchars); // Alloc enough size. while (inStream.peek() != -1) { - inStream.getline(&buf[0], maxchars); + inStream.getline(&buf[0], static_cast(maxchars)); std::string linebuf(&buf[0]); @@ -738,7 +742,7 @@ void LoadMtl(std::map &material_map, } if (_space) { std::ptrdiff_t len = _space - token; - std::string key(token, len); + std::string key(token, static_cast(len)); std::string value = _space + 1; material.unknown_parameter.insert( std::pair(key, value)); @@ -817,7 +821,7 @@ bool LoadObj(std::vector &shapes, // [output] shape_t shape; int maxchars = 8192; // Alloc enough size. - std::vector buf(maxchars); // Alloc enough size. + std::vector buf(static_cast(maxchars)); // Alloc enough size. while (inStream.peek() != -1) { inStream.getline(&buf[0], maxchars); @@ -1032,4 +1036,4 @@ bool LoadObj(std::vector &shapes, // [output] #endif -#endif // _TINY_OBJ_LOADER_H +#endif // TINY_OBJ_LOADER_H -- cgit v1.2.3 From 85d18ea7cd375f1767c7b23be26abe8a3a5e31d7 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Sat, 5 Dec 2015 12:34:44 +0900 Subject: Add simple ninja script. --- build.ninja | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 build.ninja diff --git a/build.ninja b/build.ninja new file mode 100644 index 0000000..3a26d97 --- /dev/null +++ b/build.ninja @@ -0,0 +1,16 @@ +# build.ninja +cc = clang +cxx = clang++ +cflags = -Werror -Weverything +cxxflags = -Werror -Weverything + +rule compile + command = $cxx $cxxflags -c $in -o $out + +rule link + command = $cxx $in -o $out + +build test.o: compile test.cc +build test: link test.o + +default test -- cgit v1.2.3 From 320a670bc8f5d2d7ea5064c21caeaa6e77bdf1b2 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Mon, 4 Jan 2016 23:24:43 +0900 Subject: Add Travis CI script. --- .travis.yml | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..706b8c1 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,12 @@ +language: cpp + +compiler: gcc + +before_install: + - curl -L "https://dl.bintray.com/gogoprog/gengine/linux64/premake4" -o premake4 + - chmod +x premake4 + +script: + - ./premake4 gmake + - make + - ./test_tinyobjloader -- cgit v1.2.3 From 74614d9fd9ce1d4728bdb15063939c57b2a59a97 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Mon, 4 Jan 2016 23:26:29 +0900 Subject: Add Travis badge. --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 77dc472..24bd121 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,8 @@ tinyobjloader [![Join the chat at https://gitter.im/syoyo/tinyobjloader](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/syoyo/tinyobjloader?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) +[![Build Status](https://travis-ci.org/syoyo/tinyobjloader.svg)](https://travis-ci.org/syoyo/tinyobjloader) + [![wercker status](https://app.wercker.com/status/495a3bac400212cdacdeb4dd9397bf4f/m "wercker status")](https://app.wercker.com/project/bykey/495a3bac400212cdacdeb4dd9397bf4f) [![Build status](https://ci.appveyor.com/api/projects/status/tlb421q3t2oyobcn/branch/master?svg=true)](https://ci.appveyor.com/project/syoyo/tinyobjloader/branch/master) -- cgit v1.2.3 From 0e23d499c7db3d77d4bf858d7f4a0d0275e9f064 Mon Sep 17 00:00:00 2001 From: mogemimi Date: Mon, 18 Jan 2016 08:05:54 +0900 Subject: Fix minor typos --- tiny_obj_loader.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tiny_obj_loader.h b/tiny_obj_loader.h index 3f0e7b1..021e420 100644 --- a/tiny_obj_loader.h +++ b/tiny_obj_loader.h @@ -1,7 +1,7 @@ // // Copyright 2012-2015, Syoyo Fujita. // -// Licensed under 2-clause BSD liecense. +// Licensed under 2-clause BSD license. // // @@ -20,7 +20,7 @@ // Parse transmittance material parameter correctly. // version 0.9.5 : Parse multiple group name. // Add support of specifying the base path to load material file. -// version 0.9.4 : Initial suupport of group tag(g) +// version 0.9.4 : Initial support of group tag(g) // version 0.9.3 : Fix parsing triple 'x/y/z' // version 0.9.2 : Add more .mtl load support // version 0.9.1 : Add initial .mtl load support @@ -56,7 +56,7 @@ typedef struct { // illumination model (see http://www.fileformat.info/format/material/) int illum; - int dummy; // Supress padding warning. + int dummy; // Suppress padding warning. std::string ambient_texname; // map_Ka std::string diffuse_texname; // map_Kd -- cgit v1.2.3 From 2fcdd32bc37c28c9219aa2e8843a7082aa505e2f Mon Sep 17 00:00:00 2001 From: dboogert Date: Thu, 28 Jan 2016 18:34:08 +0000 Subject: subd support: n-sided polygons & pixar crease tags + optional parameter to triangulate polygons to maintain compatibility + added create tag test & example subd crease tag file from OpenSubD source code --- catmark_torus_creases0.obj | 101 +++++++++++++++++++++++++++ test.cc | 77 +++++++++++++++++---- tiny_obj_loader.cc | 169 ++++++++++++++++++++++++++++++++++----------- tiny_obj_loader.h | 18 ++++- 4 files changed, 309 insertions(+), 56 deletions(-) create mode 100644 catmark_torus_creases0.obj diff --git a/catmark_torus_creases0.obj b/catmark_torus_creases0.obj new file mode 100644 index 0000000..bf18f15 --- /dev/null +++ b/catmark_torus_creases0.obj @@ -0,0 +1,101 @@ +# +# Copyright 2013 Pixar +# +# Licensed under the Apache License, Version 2.0 (the "Apache License") +# with the following modification; you may not use this file except in +# compliance with the Apache License and the following modification to it: +# Section 6. Trademarks. is deleted and replaced with: +# +# 6. Trademarks. This License does not grant permission to use the trade +# names, trademarks, service marks, or product names of the Licensor +# and its affiliates, except as required to comply with Section 4(c) of +# the License and to reproduce the content of the NOTICE file. +# +# You may obtain a copy of the Apache License at +# +# http:#www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the Apache License with the above modification is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the Apache License for the specific +# language governing permissions and limitations under the Apache License. +# +# This file uses centimeters as units for non-parametric coordinates. + +v 1.25052 0.517982 0.353553 +v 0.597239 0.247384 0.353553 +v 0.597239 0.247384 -0.353553 +v 1.25052 0.517982 -0.353553 +v 0.517982 1.25052 0.353553 +v 0.247384 0.597239 0.353553 +v 0.247384 0.597239 -0.353553 +v 0.517982 1.25052 -0.353553 +v -0.517982 1.25052 0.353553 +v -0.247384 0.597239 0.353553 +v -0.247384 0.597239 -0.353553 +v -0.517982 1.25052 -0.353553 +v -1.25052 0.517982 0.353553 +v -0.597239 0.247384 0.353553 +v -0.597239 0.247384 -0.353553 +v -1.25052 0.517982 -0.353553 +v -1.25052 -0.517982 0.353553 +v -0.597239 -0.247384 0.353553 +v -0.597239 -0.247384 -0.353553 +v -1.25052 -0.517982 -0.353553 +v -0.517982 -1.25052 0.353553 +v -0.247384 -0.597239 0.353553 +v -0.247384 -0.597239 -0.353553 +v -0.517982 -1.25052 -0.353553 +v 0.517982 -1.25052 0.353553 +v 0.247384 -0.597239 0.353553 +v 0.247384 -0.597239 -0.353553 +v 0.517982 -1.25052 -0.353553 +v 1.25052 -0.517982 0.353553 +v 0.597239 -0.247384 0.353553 +v 0.597239 -0.247384 -0.353553 +v 1.25052 -0.517982 -0.353553 +vt 0 0 +vt 1 0 +vt 1 1 +vt 0 1 +f 5/1/1 6/2/2 2/3/3 1/4/4 +f 6/1/5 7/2/6 3/3/7 2/4/8 +f 7/1/9 8/2/10 4/3/11 3/4/12 +f 8/1/13 5/2/14 1/3/15 4/4/16 +f 9/1/17 10/2/18 6/3/19 5/4/20 +f 10/1/21 11/2/22 7/3/23 6/4/24 +f 11/1/25 12/2/26 8/3/27 7/4/28 +f 12/1/29 9/2/30 5/3/31 8/4/32 +f 13/1/33 14/2/34 10/3/35 9/4/36 +f 14/1/37 15/2/38 11/3/39 10/4/40 +f 15/1/41 16/2/42 12/3/43 11/4/44 +f 16/1/45 13/2/46 9/3/47 12/4/48 +f 17/1/49 18/2/50 14/3/51 13/4/52 +f 18/1/53 19/2/54 15/3/55 14/4/56 +f 19/1/57 20/2/58 16/3/59 15/4/60 +f 20/1/61 17/2/62 13/3/63 16/4/64 +f 21/1/65 22/2/66 18/3/67 17/4/68 +f 22/1/69 23/2/70 19/3/71 18/4/72 +f 23/1/73 24/2/74 20/3/75 19/4/76 +f 24/1/77 21/2/78 17/3/79 20/4/80 +f 25/1/81 26/2/82 22/3/83 21/4/84 +f 26/1/85 27/2/86 23/3/87 22/4/88 +f 27/1/89 28/2/90 24/3/91 23/4/92 +f 28/1/93 25/2/94 21/3/95 24/4/96 +f 29/1/97 30/2/98 26/3/99 25/4/100 +f 30/1/101 31/2/102 27/3/103 26/4/104 +f 31/1/105 32/2/106 28/3/107 27/4/108 +f 32/1/109 29/2/110 25/3/111 28/4/112 +f 1/1/113 2/2/114 30/3/115 29/4/116 +f 2/1/117 3/2/118 31/3/119 30/4/120 +f 3/1/121 4/2/122 32/3/123 31/4/124 +f 4/1/125 1/2/126 29/3/127 32/4/128 +t crease 2/1/0 1 5 4.7 +t crease 2/1/0 5 9 4.7 +t crease 2/1/0 9 13 4.7 +t crease 2/1/0 13 17 4.7 +t crease 2/1/0 17 21 4.7 +t crease 2/1/0 21 25 4.7 +t crease 2/1/0 25 29 4.7 +t crease 2/1/0 29 1 4.7 diff --git a/test.cc b/test.cc index 82b6178..02f4702 100644 --- a/test.cc +++ b/test.cc @@ -7,14 +7,16 @@ #include #include -static void PrintInfo(const std::vector& shapes) +static void PrintInfo(const std::vector& shapes, bool triangulate = true) { - std::cout << "# of shapes : " << shapes.size() << std::endl; for (size_t i = 0; i < shapes.size(); i++) { printf("shape[%ld].name = %s\n", i, shapes[i].name.c_str()); printf("shape[%ld].indices: %ld\n", i, shapes[i].mesh.indices.size()); - assert((shapes[i].mesh.indices.size() % 3) == 0); + if (triangulate) + { + assert((shapes[i].mesh.indices.size() % 3) == 0); + } for (size_t f = 0; f < shapes[i].mesh.indices.size(); f++) { printf(" idx[%ld] = %d\n", f, shapes[i].mesh.indices[f]); } @@ -27,7 +29,51 @@ static void PrintInfo(const std::vector& shapes) shapes[i].mesh.positions[3*v+1], shapes[i].mesh.positions[3*v+2]); } - + + printf("shape[%ld].numFaces: %ld\n", i, shapes[i].mesh.numVertices.size()); + for (size_t v = 0; v < shapes[i].mesh.numVertices.size(); v++) { + printf(" numVerts[%ld] = %ld\n", v, + (long) shapes[i].mesh.numVertices[v]); + } + + printf("shape[%ld].numTags: %ld\n", i, shapes[i].mesh.tags.size()); + for (size_t t = 0; t < shapes[i].mesh.tags.size(); t++) { + printf(" tag[%ld] = %s ", t, shapes[i].mesh.tags[t].name.c_str()); + printf(" ints: ["); + for (int j = 0; j < shapes[i].mesh.tags[t].intValues.size(); ++j) + { + printf("%ld", (long) shapes[i].mesh.tags[t].intValues[j]); + if (j < (shapes[i].mesh.tags[t].intValues.size()-1)) + { + printf(", "); + } + } + printf("]"); + + printf(" floats: ["); + for (int j = 0; j < shapes[i].mesh.tags[t].floatValues.size(); ++j) + { + printf("%f", shapes[i].mesh.tags[t].floatValues[j]); + if (j < (shapes[i].mesh.tags[t].floatValues.size()-1)) + { + printf(", "); + } + } + printf("]"); + + printf(" strings: ["); + for (int j = 0; j < shapes[i].mesh.tags[t].stringValues.size(); ++j) + { + printf("%s", shapes[i].mesh.tags[t].stringValues[j].c_str()); + if (j < (shapes[i].mesh.tags[t].stringValues.size()-1)) + { + printf(", "); + } + } + printf("]"); + printf("\n"); + } + printf("shape[%ld].material.name = %s\n", i, shapes[i].material.name.c_str()); printf(" material.Ka = (%f, %f ,%f)\n", shapes[i].material.ambient[0], shapes[i].material.ambient[1], shapes[i].material.ambient[2]); printf(" material.Kd = (%f, %f ,%f)\n", shapes[i].material.diffuse[0], shapes[i].material.diffuse[1], shapes[i].material.diffuse[2]); @@ -54,19 +100,20 @@ static void PrintInfo(const std::vector& shapes) static bool TestLoadObj( const char* filename, - const char* basepath = NULL) + const char* basepath = NULL, + bool triangulate = true) { std::cout << "Loading " << filename << std::endl; std::vector shapes; - std::string err = tinyobj::LoadObj(shapes, filename, basepath); + std::string err = tinyobj::LoadObj(shapes, filename, basepath, triangulate); if (!err.empty()) { std::cerr << err << std::endl; return false; } - PrintInfo(shapes); + PrintInfo(shapes, triangulate); return true; } @@ -78,7 +125,7 @@ TestStreamLoadObj() std::cout << "Stream Loading " << std::endl; std::stringstream objStream; - objStream + objStream << "mtllib cube.mtl\n" "\n" "v 0.000000 2.000000 2.000000\n" @@ -111,7 +158,7 @@ TestStreamLoadObj() "f 2 6 7 3\n" "# 6 elements"; -std::string matStream( +std::string matStream( "newmtl white\n" "Ka 0 0 0\n" "Kd 1 1 1\n" @@ -153,19 +200,19 @@ std::string matStream( private: std::stringstream m_matSStream; - }; + }; MaterialStringStreamReader matSSReader(matStream); std::vector shapes; - std::string err = tinyobj::LoadObj(shapes, objStream, matSSReader); - + std::string err = tinyobj::LoadObj(shapes, objStream, matSSReader); + if (!err.empty()) { std::cerr << err << std::endl; return false; } PrintInfo(shapes); - + return true; } @@ -174,7 +221,6 @@ main( int argc, char **argv) { - if (argc > 1) { const char* basepath = NULL; if (argc > 2) { @@ -185,7 +231,8 @@ main( assert(true == TestLoadObj("cornell_box.obj")); assert(true == TestLoadObj("cube.obj")); assert(true == TestStreamLoadObj()); + assert(true == TestLoadObj("catmark_torus_creases0.obj", NULL, false)); } - + return 0; } diff --git a/tiny_obj_loader.cc b/tiny_obj_loader.cc index 50fa69b..1f65ffd 100755 --- a/tiny_obj_loader.cc +++ b/tiny_obj_loader.cc @@ -1,6 +1,6 @@ // // Copyright 2012-2013, Syoyo Fujita. -// +// // Licensed under 2-clause BSD liecense. // @@ -38,6 +38,14 @@ struct vertex_index { vertex_index(int vidx, int vtidx, int vnidx) : v_idx(vidx), vt_idx(vtidx), vn_idx(vnidx) {}; }; + +struct tag_sizes { + tag_sizes() : num_ints(0), num_floats(0), num_strings(0) {} + int num_ints; + int num_floats; + int num_strings; +}; + // for std::map static inline bool operator<(const vertex_index& a, const vertex_index& b) { @@ -62,7 +70,7 @@ static inline bool isNewLine(const char c) { return (c == '\r') || (c == '\n') || (c == '\0'); } -// Make index zero-base, and also support relative index. +// Make index zero-base, and also support relative index. static inline int fixIndex(int idx, int n) { int i; @@ -122,6 +130,30 @@ static inline void parseFloat3( } +static tag_sizes parseTagTriple(const char* & token) +{ + tag_sizes ts; + + ts.num_ints = atoi(token); + token += strcspn(token, "/ \t\r"); + if (token[0] != '/') { + return ts; + } + token++; + + ts.num_floats = atoi(token); + token += strcspn(token, "/ \t\r"); + if (token[0] != '/') { + return ts; + } + token++; + + ts.num_strings = atoi(token); + token += strcspn(token, "/ \t\r") + 1; + + return ts; +} + // Parse triples: i, i/j/k, i//k, i/j static vertex_index parseTriple( const char* &token, @@ -145,7 +177,7 @@ static vertex_index parseTriple( 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"); @@ -157,7 +189,7 @@ static vertex_index parseTriple( token++; // skip '/' vi.vn_idx = fixIndex(atoi(token), vnsize); token += strcspn(token, "/ \t\r"); - return vi; + return vi; } static unsigned int @@ -184,13 +216,13 @@ updateVertex( 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) { + if (i.vn_idx >= 0 && (i.vn_idx * 3 + 2) < in_normals.size() ) { 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) { + if (i.vt_idx >= 0 && (i.vt_idx * 2 +1) < in_texcoords.size()) { texcoords.push_back(in_texcoords[2*i.vt_idx+0]); texcoords.push_back(in_texcoords[2*i.vt_idx+1]); } @@ -228,9 +260,11 @@ exportFaceGroupToShape( const std::vector &in_normals, const std::vector &in_texcoords, const std::vector >& faceGroup, + std::vector& tags, const material_t &material, const std::string &name, - const bool is_material_seted) + const bool is_material_seted, + const bool triangulate) { if (faceGroup.empty()) { return false; @@ -247,26 +281,38 @@ exportFaceGroupToShape( 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]; - size_t npolys = face.size(); - // Polygon -> triangle fan conversion - for (size_t k = 2; k < npolys; k++) { - i1 = i2; - i2 = face[k]; + if (triangulate) { + vertex_index i0 = face[0]; + vertex_index i1(-1); + vertex_index i2 = face[1]; - unsigned int v0 = updateVertex(vertexCache, positions, normals, texcoords, in_positions, in_normals, in_texcoords, i0); - unsigned int v1 = updateVertex(vertexCache, positions, normals, texcoords, in_positions, in_normals, in_texcoords, i1); - unsigned int v2 = updateVertex(vertexCache, positions, normals, texcoords, in_positions, in_normals, in_texcoords, i2); + // Polygon -> triangle fan conversion + for (size_t k = 2; k < npolys; k++) { + i1 = i2; + i2 = face[k]; - indices.push_back(v0); - indices.push_back(v1); - indices.push_back(v2); + unsigned int v0 = updateVertex(vertexCache, positions, normals, texcoords, in_positions, in_normals, in_texcoords, i0); + unsigned int v1 = updateVertex(vertexCache, positions, normals, texcoords, in_positions, in_normals, in_texcoords, i1); + unsigned int v2 = updateVertex(vertexCache, positions, normals, texcoords, in_positions, in_normals, in_texcoords, i2); + + indices.push_back(v0); + indices.push_back(v1); + indices.push_back(v2); + + shape.mesh.numVertices.push_back(3); + } } + else + { + for (size_t k = 0; k < npolys; k++) { + unsigned int v = updateVertex(vertexCache, positions, normals, texcoords, in_positions, in_normals, in_texcoords, face[k]); + indices.push_back(v); + shape.mesh.numVertices.push_back(npolys); + } + } } // @@ -277,6 +323,7 @@ exportFaceGroupToShape( shape.mesh.normals.swap(normals); shape.mesh.texcoords.swap(texcoords); shape.mesh.indices.swap(indices); + shape.mesh.tags.swap(tags); if(is_material_seted) { shape.material = material; @@ -299,7 +346,7 @@ std::string LoadMtl ( std::stringstream err; material_t material; - + int maxchars = 8192; // Alloc enough size. std::vector buf(maxchars); // Alloc enough size. while (inStream.peek() != -1) { @@ -326,9 +373,9 @@ std::string LoadMtl ( assert(token); if (token[0] == '\0') continue; // empty line - + if (token[0] == '#') continue; // comment line - + // new mtl if ((0 == strncmp(token, "newmtl", 6)) && isSpace((token[6]))) { // flush previous material. @@ -344,7 +391,7 @@ std::string LoadMtl ( material.name = namebuf; continue; } - + // ambient if (token[0] == 'K' && token[1] == 'a' && isSpace((token[2]))) { token += 2; @@ -355,7 +402,7 @@ std::string LoadMtl ( material.ambient[2] = b; continue; } - + // diffuse if (token[0] == 'K' && token[1] == 'd' && isSpace((token[2]))) { token += 2; @@ -366,7 +413,7 @@ std::string LoadMtl ( material.diffuse[2] = b; continue; } - + // specular if (token[0] == 'K' && token[1] == 's' && isSpace((token[2]))) { token += 2; @@ -377,7 +424,7 @@ std::string LoadMtl ( material.specular[2] = b; continue; } - + // transmittance if (token[0] == 'K' && token[1] == 't' && isSpace((token[2]))) { token += 2; @@ -499,7 +546,8 @@ std::string LoadObj( std::vector& shapes, const char* filename, - const char* mtl_basepath) + const char* mtl_basepath, + bool triangulate) { shapes.clear(); @@ -517,20 +565,23 @@ LoadObj( basePath = mtl_basepath; } MaterialFileReader matFileReader( basePath ); - - return LoadObj(shapes, ifs, matFileReader); + + return LoadObj(shapes, ifs, matFileReader, triangulate); } std::string LoadObj( std::vector& shapes, std::istream& inStream, - MaterialReader& readMatFn) + MaterialReader& readMatFn, + bool triangulate) { std::stringstream err; std::vector v; std::vector vn; std::vector vt; + std::vector tags; + std::vector > faceGroup; std::string name; @@ -565,7 +616,7 @@ std::string LoadObj( assert(token); if (token[0] == '\0') continue; // empty line - + if (token[0] == '#') continue; // comment line // vertex @@ -614,7 +665,7 @@ std::string LoadObj( } faceGroup.push_back(face); - + continue; } @@ -641,13 +692,13 @@ std::string LoadObj( char namebuf[4096]; token += 7; sscanf(token, "%s", namebuf); - + std::string err_mtl = readMatFn(namebuf, material_map); if (!err_mtl.empty()) { faceGroup.clear(); // for safety return err_mtl; } - + continue; } @@ -656,7 +707,7 @@ std::string LoadObj( // flush previous face group. shape_t shape; - bool ret = exportFaceGroupToShape(shape, v, vn, vt, faceGroup, material, name, is_material_seted); + bool ret = exportFaceGroupToShape(shape, v, vn, vt, faceGroup, tags, material, name, is_material_seted, triangulate); if (ret) { shapes.push_back(shape); } @@ -688,7 +739,7 @@ std::string LoadObj( // flush previous face group. shape_t shape; - bool ret = exportFaceGroupToShape(shape, v, vn, vt, faceGroup, material, name, is_material_seted); + bool ret = exportFaceGroupToShape(shape, v, vn, vt, faceGroup, tags, material, name, is_material_seted, triangulate); if (ret) { shapes.push_back(shape); } @@ -706,11 +757,51 @@ std::string LoadObj( continue; } + if (token[0] == 't' && isSpace(token[1])) { + tag_t tag; + + char namebuf[4096]; + token += 2; + sscanf(token, "%s", namebuf); + tag.name = std::string(namebuf); + + token += tag.name.size() + 1; + + tag_sizes ts = parseTagTriple(token); + + tag.intValues.resize(ts.num_ints); + + for(int i = 0; i < ts.num_ints; ++i) + { + tag.intValues[i] = atoi(token); + token += strcspn(token, "/ \t\r") + 1; + } + + tag.floatValues.resize(ts.num_floats); + for(int i = 0; i < ts.num_floats; ++i) + { + tag.floatValues[i] = parseFloat(token); + token += strcspn(token, "/ \t\r") + 1; + } + + tag.stringValues.resize(ts.num_strings); + for(int i = 0; i < ts.num_strings; ++i) + { + char stringValueBuffer[4096]; + + sscanf(token, "%s", stringValueBuffer); + tag.stringValues[i] = stringValueBuffer; + token += tag.stringValues[i].size() + 1; + } + + tags.push_back(tag); + } + // Ignore unknown command. } shape_t shape; - bool ret = exportFaceGroupToShape(shape, v, vn, vt, faceGroup, material, name, is_material_seted); + bool ret = exportFaceGroupToShape(shape, v, vn, vt, faceGroup, tags, material, name, is_material_seted, triangulate); if (ret) { shapes.push_back(shape); } diff --git a/tiny_obj_loader.h b/tiny_obj_loader.h index c275a88..bcafbf8 100644 --- a/tiny_obj_loader.h +++ b/tiny_obj_loader.h @@ -34,12 +34,23 @@ typedef struct std::map unknown_parameter; } material_t; +typedef struct +{ + std::string name; + + std::vector intValues; + std::vector floatValues; + std::vector stringValues; +} tag_t; + typedef struct { std::vector positions; std::vector normals; std::vector texcoords; std::vector indices; + std::vector numVertices; //Is it worth it 255 faces + std::vector tags; } mesh_t; typedef struct @@ -82,7 +93,9 @@ class MaterialFileReader: std::string LoadObj( std::vector& shapes, // [output] const char* filename, - const char* mtl_basepath = NULL); + const char* mtl_basepath = NULL, + bool triangulate = true +); /// Loads object from a std::istream, uses GetMtlIStreamFn to retrieve /// std::istream for materials. @@ -90,7 +103,8 @@ std::string LoadObj( std::string LoadObj( std::vector& shapes, // [output] std::istream& inStream, - MaterialReader& readMatFn); + MaterialReader& readMatFn, + bool triangulate = true); /// Loads materials into std::map /// Returns an empty string if successful -- cgit v1.2.3 From 968ad8248e0ae2e5cf232e9a81e4ec4f0d124f97 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Fri, 29 Jan 2016 19:55:04 +0900 Subject: Cosmetic updates. --- README.md | 6 ++++-- tiny_obj_loader.h | 4 ++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 1c0fc22..35cf100 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ Tiny but poweful single file wavefront obj loader written in C++. No dependency What's new ---------- -* Jan 29, 2016 : Support n-polygon and OpenSubdiv crease tag! Thanks dboogert! +* 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. * Jun 23, 2015 : Various fixes and added more projects using tinyobjloader. Thanks many contributors! @@ -84,6 +84,8 @@ Licensed under 2 clause BSD. Usage ----- +TinyObjLoader triangulate input .obj by default. + #define TINYOBJLOADER_IMPLEMENTATION // define this in only *one* .cc #include "tiny_obj_loader.h" @@ -175,7 +177,7 @@ Reading .obj without triangulation. Use `num_vertices[i]` to iterate over faces( for (size_t n = 0; n < shapes[i].mesh.num_vertices.size(); n++) { int ngon = shapes[i].mesh.num_vertices[n]; for (size_t f = 0; f < ngon; f++) { - size_t v = shapes[i].mesh.indices[indexOffset + f]; + unsigend int v = shapes[i].mesh.indices[indexOffset + f]; printf(" face[%ld] v[%ld] = (%f, %f, %f)\n", n, shapes[i].mesh.positions[3*v+0], shapes[i].mesh.positions[3*v+1], diff --git a/tiny_obj_loader.h b/tiny_obj_loader.h index 1ad3f5c..9665d42 100644 --- a/tiny_obj_loader.h +++ b/tiny_obj_loader.h @@ -84,8 +84,8 @@ typedef struct std::vector normals; std::vector texcoords; std::vector indices; - std::vector num_vertices; // up to 255 faces - std::vector material_ids; // per-mesh material ID + std::vector num_vertices; // The number of vertices per face. Up to 255. + std::vector material_ids; // per-face material ID std::vector tags; // SubD tag } mesh_t; -- cgit v1.2.3 From 41515c8c7815e315dc81460930dc9c6467924243 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Sat, 30 Jan 2016 00:48:42 +0900 Subject: Apply clang-format. --- .clang-format | 7 + tiny_obj_loader.h | 470 +++++++++++++++++++++++++++--------------------------- 2 files changed, 238 insertions(+), 239 deletions(-) create mode 100644 .clang-format diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..a94a2c4 --- /dev/null +++ b/.clang-format @@ -0,0 +1,7 @@ +--- +BasedOnStyle: LLVM +IndentWidth: 2 +TabWidth: 2 +UseTab: Never +BreakBeforeBraces: Attach +Standard: Cpp03 diff --git a/tiny_obj_loader.h b/tiny_obj_loader.h index 9665d42..4240c66 100644 --- a/tiny_obj_loader.h +++ b/tiny_obj_loader.h @@ -8,9 +8,11 @@ // version 0.9.17: Support n-polygon and crease tag(OpenSubdiv extension) // version 0.9.16: Make tinyobjloader header-only // version 0.9.15: Change API to handle no mtl file case correctly(#58) -// version 0.9.14: Support specular highlight, bump, displacement and alpha map(#53) +// version 0.9.14: Support specular highlight, bump, displacement and alpha +// map(#53) // 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. @@ -20,7 +22,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 support of group tag(g) // version 0.9.3 : Fix parsing triple 'x/y/z' // version 0.9.2 : Add more .mtl load support @@ -32,7 +35,7 @@ // Use this in *one* .cc // #define TINYOBJLOADER_IMPLEMENTATION // #include "tiny_obj_loader.h" -// +// #ifndef TINY_OBJ_LOADER_H #define TINY_OBJ_LOADER_H @@ -69,24 +72,23 @@ typedef struct { std::map unknown_parameter; } material_t; -typedef struct -{ - std::string name; +typedef struct { + std::string name; - std::vector intValues; - std::vector floatValues; - std::vector stringValues; + std::vector intValues; + std::vector floatValues; + std::vector stringValues; } tag_t; -typedef struct -{ - std::vector positions; - std::vector normals; - std::vector texcoords; - std::vector indices; - std::vector num_vertices; // The number of vertices per face. Up to 255. - std::vector material_ids; // per-face material ID - std::vector tags; // SubD tag +typedef struct { + std::vector positions; + std::vector normals; + std::vector texcoords; + std::vector indices; + std::vector + num_vertices; // The number of vertices per face. Up to 255. + std::vector material_ids; // per-face material ID + std::vector tags; // SubD tag } mesh_t; typedef struct { @@ -111,9 +113,8 @@ 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; @@ -125,11 +126,13 @@ 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. +/// '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] - const char *filename, const char *mtl_basepath = NULL, bool triangulate = true); + std::string &err, // [output] + const char *filename, const char *mtl_basepath = NULL, + bool triangulate = true); /// Loads object from a std::istream, uses GetMtlIStreamFn to retrieve /// std::istream for materials. @@ -137,8 +140,9 @@ bool LoadObj(std::vector &shapes, // [output] /// 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 triangulate = true); + std::string &err, // [output] + std::istream &inStream, MaterialReader &readMatFn, + bool triangulate = true); /// Loads materials into std::map void LoadMtl(std::map &material_map, // [output] @@ -166,21 +170,21 @@ namespace tinyobj { MaterialReader::~MaterialReader() {} -#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() {} + 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){} + : v_idx(vidx), vt_idx(vtidx), vn_idx(vnidx) {} }; struct tag_sizes { - tag_sizes() : num_ints(0), num_floats(0), num_strings(0) {} - int num_ints; - int num_floats; - int num_strings; + tag_sizes() : num_ints(0), num_floats(0), num_strings(0) {} + int num_ints; + int num_floats; + int num_strings; }; // for std::map @@ -209,8 +213,10 @@ static inline bool isNewLine(const char c) { // 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; + if (idx > 0) + return idx - 1; + if (idx == 0) + return 0; return n + idx; // negative value = relative } @@ -230,7 +236,6 @@ static inline int parseInt(const char *&token) { 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 @@ -247,7 +252,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: @@ -257,121 +262,110 @@ 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) - { - 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 - // 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 - // exponent to be in base 2. - int exponent = 0; - - // NOTE: THESE MUST BE DECLARED HERE SINCE WE ARE NOT ALLOWED - // TO JUMP OVER DEFINITIONS. - char sign = '+'; - char exp_sign = '+'; - char const *curr = s; - - // 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; - - /* - BEGIN PARSING. - */ - - // Find out what sign we've got. - if (*curr == '+' || *curr == '-') - { - sign = *curr; - curr++; - } - else if (isdigit(*curr)) { /* Pass through. */ } - else - { - goto fail; - } - - // Read the integer part. - while ((end_not_reached = (curr != s_end)) && isdigit(*curr)) - { - mantissa *= 10; - mantissa += static_cast(*curr - 0x30); - curr++; read++; - } - - // We must make sure we actually got something. - if (read == 0) - goto fail; - // We allow numbers of form "#", "###" etc. - if (!end_not_reached) - goto assemble; - - // Read the decimal part. - if (*curr == '.') - { - curr++; - read = 1; - 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++; - } - } - else if (*curr == 'e' || *curr == 'E') {} - else - { - goto assemble; - } - - if (!end_not_reached) - goto assemble; - - // Read the exponent part. - 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 == '-')) - { - exp_sign = *curr; - curr++; - } - 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)) - { - exponent *= 10; - exponent += static_cast(*curr - 0x30); - curr++; read++; - } - exponent *= (exp_sign == '+'? 1 : -1); - if (read == 0) - goto fail; - } +// +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 + // 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 + // exponent to be in base 2. + int exponent = 0; + + // NOTE: THESE MUST BE DECLARED HERE SINCE WE ARE NOT ALLOWED + // TO JUMP OVER DEFINITIONS. + char sign = '+'; + char exp_sign = '+'; + char const *curr = s; + + // 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; + + /* + BEGIN PARSING. + */ + + // Find out what sign we've got. + if (*curr == '+' || *curr == '-') { + sign = *curr; + curr++; + } else if (isdigit(*curr)) { /* Pass through. */ + } else { + goto fail; + } + + // Read the integer part. + while ((end_not_reached = (curr != s_end)) && isdigit(*curr)) { + mantissa *= 10; + mantissa += static_cast(*curr - 0x30); + curr++; + read++; + } + + // We must make sure we actually got something. + if (read == 0) + goto fail; + // We allow numbers of form "#", "###" etc. + if (!end_not_reached) + goto assemble; + + // Read the decimal part. + if (*curr == '.') { + curr++; + read = 1; + 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++; + } + } else if (*curr == 'e' || *curr == 'E') { + } else { + goto assemble; + } + + if (!end_not_reached) + goto assemble; + + // Read the exponent part. + 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 == '-')) { + exp_sign = *curr; + curr++; + } 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)) { + exponent *= 10; + exponent += static_cast(*curr - 0x30); + curr++; + read++; + } + exponent *= (exp_sign == '+' ? 1 : -1); + if (read == 0) + goto fail; + } assemble: - *result = (sign == '+'? 1 : -1) * ldexp(mantissa * pow(5.0, exponent), exponent); - return true; + *result = + (sign == '+' ? 1 : -1) * ldexp(mantissa * pow(5.0, exponent), exponent); + return true; fail: - return false; + return false; } static inline float parseFloat(const char *&token) { token += strspn(token, " \t"); @@ -388,7 +382,6 @@ static inline float parseFloat(const char *&token) { return f; } - static inline void parseFloat2(float &x, float &y, const char *&token) { x = parseFloat(token); y = parseFloat(token); @@ -401,28 +394,27 @@ static inline void parseFloat3(float &x, float &y, float &z, z = parseFloat(token); } -static tag_sizes parseTagTriple(const char* & token) -{ - tag_sizes ts; +static tag_sizes parseTagTriple(const char *&token) { + tag_sizes ts; - ts.num_ints = atoi(token); - token += strcspn(token, "/ \t\r"); - if (token[0] != '/') { - return ts; - } - token++; + ts.num_ints = atoi(token); + token += strcspn(token, "/ \t\r"); + if (token[0] != '/') { + return ts; + } + token++; - ts.num_floats = atoi(token); - token += strcspn(token, "/ \t\r"); - if (token[0] != '/') { - return ts; - } - token++; + ts.num_floats = atoi(token); + token += strcspn(token, "/ \t\r"); + if (token[0] != '/') { + return ts; + } + token++; - ts.num_strings = atoi(token); - token += strcspn(token, "/ \t\r") + 1; + ts.num_strings = atoi(token); + token += strcspn(token, "/ \t\r") + 1; - return ts; + return ts; } // Parse triples: i, i/j/k, i//k, i/j @@ -479,13 +471,15 @@ updateVertex(std::map &vertexCache, 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())) { + 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())) { + 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]); } @@ -525,8 +519,8 @@ static bool exportFaceGroupToShape( 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) { + std::vector &tags, const int material_id, const std::string &name, + bool clearCache, bool triangulate) { if (faceGroup.empty()) { return false; } @@ -564,22 +558,20 @@ static bool exportFaceGroupToShape( shape.mesh.num_vertices.push_back(3); shape.mesh.material_ids.push_back(material_id); - } } 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]); - + 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); - } - shape.mesh.num_vertices.push_back(static_cast(npolys)); + shape.mesh.num_vertices.push_back(static_cast(npolys)); shape.mesh.material_ids.push_back(material_id); // per face - } } @@ -593,14 +585,13 @@ static bool exportFaceGroupToShape( } void LoadMtl(std::map &material_map, - std::vector &materials, - std::istream &inStream) { + std::vector &materials, std::istream &inStream) { // Create a default material anyway. material_t material; InitMaterial(material); - size_t maxchars = 8192; // Alloc enough size. + 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)); @@ -637,8 +628,8 @@ void LoadMtl(std::map &material_map, 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()))); + material_map.insert(std::pair( + material.name, static_cast(materials.size()))); materials.push_back(material); } @@ -816,15 +807,15 @@ void LoadMtl(std::map &material_map, } } // flush last material. - material_map.insert( - std::pair(material.name, static_cast(materials.size()))); + 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::string &err) { std::string filepath; if (!m_mtlBasePath.empty()) { @@ -837,16 +828,17 @@ bool MaterialFileReader::operator()(const std::string &matId, LoadMtl(matMap, materials, matIStream); if (!matIStream) { std::stringstream ss; - ss << "WARN: Material file [ " << filepath << " ] not found. Created a default material."; + ss << "WARN: Material file [ " << filepath + << " ] not found. Created a default material."; err += ss.str(); } return true; } -bool LoadObj(std::vector &shapes, // [output] +bool LoadObj(std::vector &shapes, // [output] std::vector &materials, // [output] - std::string &err, - const char *filename, const char *mtl_basepath, bool trianglulate) { + std::string &err, const char *filename, const char *mtl_basepath, + bool trianglulate) { shapes.clear(); @@ -868,10 +860,10 @@ bool LoadObj(std::vector &shapes, // [output] return LoadObj(shapes, materials, err, ifs, matFileReader, trianglulate); } -bool LoadObj(std::vector &shapes, // [output] +bool LoadObj(std::vector &shapes, // [output] std::vector &materials, // [output] - std::string& err, - std::istream &inStream, MaterialReader &readMatFn, bool triangulate) { + std::string &err, std::istream &inStream, + MaterialReader &readMatFn, bool triangulate) { std::stringstream errss; std::vector v; @@ -888,7 +880,7 @@ bool LoadObj(std::vector &shapes, // [output] shape_t shape; - int maxchars = 8192; // Alloc enough size. + int maxchars = 8192; // Alloc enough size. std::vector buf(static_cast(maxchars)); // Alloc enough size. while (inStream.peek() != -1) { inStream.getline(&buf[0], maxchars); @@ -960,8 +952,9 @@ bool LoadObj(std::vector &shapes, // [output] 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)); + 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; @@ -984,10 +977,11 @@ bool LoadObj(std::vector &shapes, // [output] #endif // Create face group per material. - bool ret = exportFaceGroupToShape(shape, vertexCache, v, vn, vt, - faceGroup, tags, material, name, true, triangulate); + bool ret = + exportFaceGroupToShape(shape, vertexCache, v, vn, vt, faceGroup, tags, + material, name, true, triangulate); if (ret) { - shapes.push_back(shape); + shapes.push_back(shape); } shape = shape_t(); faceGroup.clear(); @@ -1015,7 +1009,7 @@ bool LoadObj(std::vector &shapes, // [output] std::string err_mtl; bool ok = readMatFn(namebuf, materials, material_map, err_mtl); err += err_mtl; - + if (!ok) { faceGroup.clear(); // for safety return false; @@ -1028,8 +1022,9 @@ bool LoadObj(std::vector &shapes, // [output] if (token[0] == 'g' && isSpace((token[1]))) { // flush previous face group. - bool ret = exportFaceGroupToShape(shape, vertexCache, v, vn, vt, - faceGroup, tags, material, name, true, triangulate); + bool ret = + exportFaceGroupToShape(shape, vertexCache, v, vn, vt, faceGroup, tags, + material, name, true, triangulate); if (ret) { shapes.push_back(shape); } @@ -1062,8 +1057,9 @@ bool LoadObj(std::vector &shapes, // [output] if (token[0] == 'o' && isSpace((token[1]))) { // flush previous face group. - bool ret = exportFaceGroupToShape(shape, vertexCache, v, vn, vt, - faceGroup, tags, material, name, true, triangulate); + bool ret = + exportFaceGroupToShape(shape, vertexCache, v, vn, vt, faceGroup, tags, + material, name, true, triangulate); if (ret) { shapes.push_back(shape); } @@ -1086,50 +1082,47 @@ bool LoadObj(std::vector &shapes, // [output] } if (token[0] == 't' && isSpace(token[1])) { - tag_t tag; + tag_t tag; - char namebuf[4096]; - token += 2; - sscanf(token, "%s", namebuf); - tag.name = std::string(namebuf); + char namebuf[4096]; + token += 2; + sscanf(token, "%s", namebuf); + tag.name = std::string(namebuf); - token += tag.name.size() + 1; + token += tag.name.size() + 1; - tag_sizes ts = parseTagTriple(token); + tag_sizes ts = parseTagTriple(token); - tag.intValues.resize(static_cast(ts.num_ints)); + tag.intValues.resize(static_cast(ts.num_ints)); - for(size_t i = 0; i < static_cast(ts.num_ints); ++i) - { - tag.intValues[i] = atoi(token); - token += strcspn(token, "/ \t\r") + 1; - } + for (size_t i = 0; i < static_cast(ts.num_ints); ++i) { + tag.intValues[i] = atoi(token); + token += strcspn(token, "/ \t\r") + 1; + } - tag.floatValues.resize(static_cast(ts.num_floats)); - for(size_t i = 0; i < static_cast(ts.num_floats); ++i) - { - tag.floatValues[i] = parseFloat(token); - token += strcspn(token, "/ \t\r") + 1; - } + tag.floatValues.resize(static_cast(ts.num_floats)); + for (size_t i = 0; i < static_cast(ts.num_floats); ++i) { + tag.floatValues[i] = parseFloat(token); + token += strcspn(token, "/ \t\r") + 1; + } - tag.stringValues.resize(static_cast(ts.num_strings)); - for(size_t i = 0; i < static_cast(ts.num_strings); ++i) - { - char stringValueBuffer[4096]; + tag.stringValues.resize(static_cast(ts.num_strings)); + for (size_t i = 0; i < static_cast(ts.num_strings); ++i) { + char stringValueBuffer[4096]; - sscanf(token, "%s", stringValueBuffer); - tag.stringValues[i] = stringValueBuffer; - token += tag.stringValues[i].size() + 1; - } + sscanf(token, "%s", stringValueBuffer); + tag.stringValues[i] = stringValueBuffer; + token += tag.stringValues[i].size() + 1; + } - tags.push_back(tag); + tags.push_back(tag); } // Ignore unknown command. } - bool ret = exportFaceGroupToShape(shape, vertexCache, v, vn, vt, faceGroup, tags, - material, name, true, triangulate); + bool ret = exportFaceGroupToShape(shape, vertexCache, v, vn, vt, faceGroup, + tags, material, name, true, triangulate); if (ret) { shapes.push_back(shape); } @@ -1141,7 +1134,6 @@ bool LoadObj(std::vector &shapes, // [output] } // namespace - #endif #endif // TINY_OBJ_LOADER_H -- cgit v1.2.3 From 6082a1a4f3183a58023db745089bd4ed8c573484 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Wed, 3 Feb 2016 18:49:49 +0900 Subject: Add osx for Travis build. --- .travis.yml | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 706b8c1..5b682e7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,12 +1,19 @@ language: cpp +os: + - linux + - osx + compiler: gcc before_install: - - curl -L "https://dl.bintray.com/gogoprog/gengine/linux64/premake4" -o premake4 - - chmod +x premake4 + - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then curl -L "https://dl.bintray.com/gogoprog/gengine/linux64/premake4" -o premake4; fi + - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then chmod +x premake4; fi + - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew upgrade; fi + - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew install premake; fi script: - - ./premake4 gmake + - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; ./premake4 gmake; fi + - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; premake4 gmake; fi - make - ./test_tinyobjloader -- cgit v1.2.3 From 1afdd321311e5643ac726ce12e59608d858f7f75 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Wed, 3 Feb 2016 19:34:28 +0900 Subject: Fix travis script. --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 5b682e7..0153766 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,7 +13,7 @@ before_install: - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew install premake; fi script: - - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; ./premake4 gmake; fi - - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; premake4 gmake; fi + - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then ./premake4 gmake; fi + - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then premake4 gmake; fi - make - ./test_tinyobjloader -- cgit v1.2.3 From 12a55a8f71b28de939aa9372591e84b234c92ede Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Wed, 3 Feb 2016 19:36:45 +0900 Subject: Fix source code layout. --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 35cf100..2444d15 100644 --- a/README.md +++ b/README.md @@ -175,16 +175,16 @@ Reading .obj without triangulation. Use `num_vertices[i]` to iterate over faces( size_t indexOffset = 0; for (size_t n = 0; n < shapes[i].mesh.num_vertices.size(); n++) { - int ngon = shapes[i].mesh.num_vertices[n]; - for (size_t f = 0; f < ngon; f++) { + int ngon = shapes[i].mesh.num_vertices[n]; + for (size_t f = 0; f < ngon; f++) { unsigend int v = shapes[i].mesh.indices[indexOffset + f]; printf(" face[%ld] v[%ld] = (%f, %f, %f)\n", n, shapes[i].mesh.positions[3*v+0], shapes[i].mesh.positions[3*v+1], shapes[i].mesh.positions[3*v+2]); - } - indexOffset += ngon; + } + indexOffset += ngon; } } -- cgit v1.2.3 From 8f58ef5b1d45e3af485ff585f7e9c8057e4fde12 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Thu, 11 Feb 2016 16:50:11 +0900 Subject: Add coverall to Travis CI. Use cmake to build in Travis script. --- .travis.yml | 43 ++++++++++++++++++++++++++++++++++++++----- 1 file changed, 38 insertions(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index 0153766..3748f8e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,16 +4,49 @@ os: - linux - osx +matrix: + include: + # Clang 3.7 + - addons: &clang37 + apt: + sources: + - george-edison55-precise-backports + - ubuntu-toolchain-r-test + - llvm-toolchain-precise-3.7 + packages: + - cmake + - cmake-data + - ninja-build + - g++-4.9 + - clang-3.7 + compiler: clang + env: COMPILER_VERSION=3.7 BUILD_TYPE=Debug + + - addons: *clang37 + compiler: clang + env: COMPILER_VERSION=3.7 BUILD_TYPE=Release + + # Coverage with Clang 3.7 + - addons: *clang37 + compiler: clang + env: COMPILER_VERSION=3.7 BUILD_TYPE=Debug CFLAGS="-O0 --coverage" CXXFLAGS="-O0 --coverage" REPORT_COVERAGE=1 + compiler: gcc before_install: - - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then curl -L "https://dl.bintray.com/gogoprog/gengine/linux64/premake4" -o premake4; fi - - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then chmod +x premake4; fi - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew upgrade; fi - - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew install premake; fi + - if [ -n "$REPORT_COVERAGE" ]; then + pip install --user cpp-coveralls; + fi script: - - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then ./premake4 gmake; fi - - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then premake4 gmake; fi + - mkdir build && cd build + - export CC="${CC}-${COMPILER_VERSION}" + - export CXX="${CXX}-${COMPILER_VERSION}" + - cmake -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DTINYOBJLOADER_BUILD_TEST_LOADER=On -G Ninja .. - make + - cd .. - ./test_tinyobjloader + - if [ -n "$REPORT_COVERAGE" ]; then + coveralls -b . -r . -e examples -e tools -e jni -e python -e images; + fi -- cgit v1.2.3 From 5f7d21afeb71cc07285b86a5eac36d86a117dd04 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Thu, 11 Feb 2016 16:57:30 +0900 Subject: Update Travis script --- .travis.yml | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 3748f8e..2e2417a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,10 +1,14 @@ language: cpp +sudo: false os: - linux - osx matrix: + allow_failures: + - os: osx + include: # Clang 3.7 - addons: &clang37 @@ -26,13 +30,29 @@ matrix: compiler: clang env: COMPILER_VERSION=3.7 BUILD_TYPE=Release + # GCC 4.9 + - addons: &gcc49 + apt: + sources: + - george-edison55-precise-backports + - ubuntu-toolchain-r-test + packages: + - cmake + - cmake-data + - ninja-build + - g++-4.9 + compiler: gcc + env: COMPILER_VERSION=4.9 BUILD_TYPE=Debug + + - addons: *gcc49 + compiler: gcc + env: COMPILER_VERSION=4.9 BUILD_TYPE=Release + # Coverage with Clang 3.7 - addons: *clang37 compiler: clang env: COMPILER_VERSION=3.7 BUILD_TYPE=Debug CFLAGS="-O0 --coverage" CXXFLAGS="-O0 --coverage" REPORT_COVERAGE=1 -compiler: gcc - before_install: - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew upgrade; fi - if [ -n "$REPORT_COVERAGE" ]; then -- cgit v1.2.3 From 1ae9a8ab4b579ca5923c528fd7c5688f4df88ec2 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Thu, 11 Feb 2016 17:01:34 +0900 Subject: print CC version and cmake version. --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index 2e2417a..9bcdf0d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -63,6 +63,8 @@ script: - mkdir build && cd build - export CC="${CC}-${COMPILER_VERSION}" - export CXX="${CXX}-${COMPILER_VERSION}" + - ${CC} -v + - cmake --version - cmake -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DTINYOBJLOADER_BUILD_TEST_LOADER=On -G Ninja .. - make - cd .. -- cgit v1.2.3 From e8418ccc8a599880aa36e1aff3c091fa90222348 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Thu, 11 Feb 2016 17:05:56 +0900 Subject: Disable osx os for a while. --- .travis.yml | 7 ------- 1 file changed, 7 deletions(-) diff --git a/.travis.yml b/.travis.yml index 9bcdf0d..b27fce3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,14 +1,7 @@ language: cpp sudo: false -os: - - linux - - osx - matrix: - allow_failures: - - os: osx - include: # Clang 3.7 - addons: &clang37 -- cgit v1.2.3 From 0889afb2ba763228a6caf2ad57e0db8f7597d9d4 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Thu, 11 Feb 2016 17:19:30 +0900 Subject: Fix travis script. --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index b27fce3..59b0659 100644 --- a/.travis.yml +++ b/.travis.yml @@ -59,7 +59,7 @@ script: - ${CC} -v - cmake --version - cmake -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DTINYOBJLOADER_BUILD_TEST_LOADER=On -G Ninja .. - - make + - ninja -1 - cd .. - ./test_tinyobjloader - if [ -n "$REPORT_COVERAGE" ]; then -- cgit v1.2.3 From f9e253c2f5c7217b5d41481e0a3457024cf96350 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Thu, 11 Feb 2016 17:29:52 +0900 Subject: Fix arg for ninja --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 59b0659..bda01e5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -59,7 +59,7 @@ script: - ${CC} -v - cmake --version - cmake -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DTINYOBJLOADER_BUILD_TEST_LOADER=On -G Ninja .. - - ninja -1 + - ninja - cd .. - ./test_tinyobjloader - if [ -n "$REPORT_COVERAGE" ]; then -- cgit v1.2.3 From 1450980f9167b31cdc3a1898384dcdff45da9624 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Thu, 11 Feb 2016 17:35:34 +0900 Subject: Fix travis script. --- .travis.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index bda01e5..e3c19cb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -60,8 +60,7 @@ script: - cmake --version - cmake -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DTINYOBJLOADER_BUILD_TEST_LOADER=On -G Ninja .. - ninja - - cd .. - - ./test_tinyobjloader + - ./test_tinyobjloader ../cornell_box.obj - if [ -n "$REPORT_COVERAGE" ]; then coveralls -b . -r . -e examples -e tools -e jni -e python -e images; fi -- cgit v1.2.3 From bc737b613beb5b74f1c0275f046723947d1688da Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Thu, 11 Feb 2016 17:53:16 +0900 Subject: Another fix for Travis script. --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index e3c19cb..fa1e435 100644 --- a/.travis.yml +++ b/.travis.yml @@ -60,7 +60,7 @@ script: - cmake --version - cmake -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DTINYOBJLOADER_BUILD_TEST_LOADER=On -G Ninja .. - ninja - - ./test_tinyobjloader ../cornell_box.obj + - ./test_loader ../cornell_box.obj - if [ -n "$REPORT_COVERAGE" ]; then coveralls -b . -r . -e examples -e tools -e jni -e python -e images; fi -- cgit v1.2.3 From 00ba666e7f2f1b217eff83830366c0fe52eec833 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Thu, 11 Feb 2016 18:38:41 +0900 Subject: Fix coverall args. --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index fa1e435..633e15a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -62,5 +62,5 @@ script: - ninja - ./test_loader ../cornell_box.obj - if [ -n "$REPORT_COVERAGE" ]; then - coveralls -b . -r . -e examples -e tools -e jni -e python -e images; + coveralls -b . -r . -e examples -e tools -e jni -e python -e images -E ".*CompilerId.*"; fi -- cgit v1.2.3 From 4fa6c29e346f05629479577eba12552af1efaf79 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Thu, 11 Feb 2016 18:53:59 +0900 Subject: Add Coverall badge. --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 2444d15..69dd3ea 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,8 @@ tinyobjloader [![Build status](https://ci.appveyor.com/api/projects/status/tlb421q3t2oyobcn/branch/master?svg=true)](https://ci.appveyor.com/project/syoyo/tinyobjloader/branch/master) +[![Coverage Status](https://coveralls.io/repos/github/syoyo/tinyobjloader/badge.svg?branch=master)](https://coveralls.io/github/syoyo/tinyobjloader?branch=master) + http://syoyo.github.io/tinyobjloader/ Tiny but poweful single file wavefront obj loader written in C++. No dependency except for C++ STL. It can parse 10M over polygons with moderate memory and time. -- cgit v1.2.3 From 04a7b206b7a5c7656b7956a3bad09e401c0359d2 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Sun, 14 Feb 2016 16:40:36 +0900 Subject: Add github relase setting in travis script. --- .travis.yml | 117 +++++++++++++++++++++++++++++------------------------------- 1 file changed, 57 insertions(+), 60 deletions(-) diff --git a/.travis.yml b/.travis.yml index 633e15a..60f08fb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,66 +1,63 @@ language: cpp sudo: false - matrix: include: - # Clang 3.7 - - addons: &clang37 - apt: - sources: - - george-edison55-precise-backports - - ubuntu-toolchain-r-test - - llvm-toolchain-precise-3.7 - packages: - - cmake - - cmake-data - - ninja-build - - g++-4.9 - - clang-3.7 - compiler: clang - env: COMPILER_VERSION=3.7 BUILD_TYPE=Debug - - - addons: *clang37 - compiler: clang - env: COMPILER_VERSION=3.7 BUILD_TYPE=Release - - # GCC 4.9 - - addons: &gcc49 - apt: - sources: - - george-edison55-precise-backports - - ubuntu-toolchain-r-test - packages: - - cmake - - cmake-data - - ninja-build - - g++-4.9 - compiler: gcc - env: COMPILER_VERSION=4.9 BUILD_TYPE=Debug - - - addons: *gcc49 - compiler: gcc - env: COMPILER_VERSION=4.9 BUILD_TYPE=Release - - # Coverage with Clang 3.7 - - addons: *clang37 - compiler: clang - env: COMPILER_VERSION=3.7 BUILD_TYPE=Debug CFLAGS="-O0 --coverage" CXXFLAGS="-O0 --coverage" REPORT_COVERAGE=1 - + - addons: &1 + apt: + sources: + - george-edison55-precise-backports + - ubuntu-toolchain-r-test + - llvm-toolchain-precise-3.7 + packages: + - cmake + - cmake-data + - ninja-build + - g++-4.9 + - clang-3.7 + compiler: clang + env: COMPILER_VERSION=3.7 BUILD_TYPE=Debug + - addons: *1 + compiler: clang + env: COMPILER_VERSION=3.7 BUILD_TYPE=Release + - addons: &2 + apt: + sources: + - george-edison55-precise-backports + - ubuntu-toolchain-r-test + packages: + - cmake + - cmake-data + - ninja-build + - g++-4.9 + compiler: gcc + env: COMPILER_VERSION=4.9 BUILD_TYPE=Debug + - addons: *2 + compiler: gcc + env: COMPILER_VERSION=4.9 BUILD_TYPE=Release + - addons: *1 + compiler: clang + env: COMPILER_VERSION=3.7 BUILD_TYPE=Debug CFLAGS="-O0 --coverage" CXXFLAGS="-O0 + --coverage" REPORT_COVERAGE=1 before_install: - - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew upgrade; fi - - if [ -n "$REPORT_COVERAGE" ]; then - pip install --user cpp-coveralls; - fi - +- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew upgrade; fi +- if [ -n "$REPORT_COVERAGE" ]; then pip install --user cpp-coveralls; fi script: - - mkdir build && cd build - - export CC="${CC}-${COMPILER_VERSION}" - - export CXX="${CXX}-${COMPILER_VERSION}" - - ${CC} -v - - cmake --version - - cmake -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DTINYOBJLOADER_BUILD_TEST_LOADER=On -G Ninja .. - - ninja - - ./test_loader ../cornell_box.obj - - if [ -n "$REPORT_COVERAGE" ]; then - coveralls -b . -r . -e examples -e tools -e jni -e python -e images -E ".*CompilerId.*"; - fi +- mkdir build && cd build +- export CC="${CC}-${COMPILER_VERSION}" +- export CXX="${CXX}-${COMPILER_VERSION}" +- ${CC} -v +- cmake --version +- cmake -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DTINYOBJLOADER_BUILD_TEST_LOADER=On -G Ninja + .. +- ninja +- ./test_loader ../cornell_box.obj +- if [ -n "$REPORT_COVERAGE" ]; then coveralls -b . -r . -e examples -e tools -e jni + -e python -e images -E ".*CompilerId.*"; fi +deploy: + provider: releases + api_key: + secure: AsXameK4GJn6h6wMmDrKTr7q/o9EI7hX7zWg1W6VaFBQKfkBvOmjJolWimjl6HMoRZ1NpMmK5GDm3zBlTUeABtgVBIyNWgE9vWS39ff6D5iQKcgScFsJkyILt0GikBqbN2pLGQ2t/M1Qh6n1sEIfzqekiCcF5Qvy5yYlYvHtaRGV02QeYAej/xx15/9SMuKTncHhjf63ClYPu8ODid7QUegJUvlQUeXoPsBDbaXMH2uDWoBWF7etX7G2Iob4NE8GX+ZP6dj+Ogi7p4HXThK650mzLL/pUl584EjjY/vePqx0cFhtpiRwvrW8SNPI1aJ1Phwa1enLRUgfS3bnkwQAMw/SCXSK2lnCvkUAXyTgpG03HWrZURj4vhEPXc7qHooO+dsfmi+JanYLaSDyrGpgQznLGjCMnVATimry0KxSufUY8Wt72Wh+nf7N0IgTUCjl32sWnQd/MRZPkxFuaf1h7r9RoH9KZY0yIOV09gABEFCGrOIZA2FcyhC2G26Bc4zyNrfMFpZ2DI76qdcWNdJGkRkpxtH9sGU8JgZu6Em2f1e6+SLgkBsPxbhRk5PwdhA9AXE2p9PmQqhO3jJKusGBZSoHAF7TlwagRY2J01yJxF7ge6zG9U8QuBqs1bB1zdnE34fHWOgs4st3inC+oBDOhvnEg1Nm/qeYVWMBzpwclSg= + file: tiny_obj_loader.h + on: + repo: syoyo/tinyobjloader + tags: true -- cgit v1.2.3 From 45b4924c2735b0a94c01e8f3072794dbc5118271 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Sun, 14 Feb 2016 17:03:59 +0900 Subject: Fix travis script. --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 60f08fb..17c8e71 100644 --- a/.travis.yml +++ b/.travis.yml @@ -53,6 +53,7 @@ script: - ./test_loader ../cornell_box.obj - if [ -n "$REPORT_COVERAGE" ]; then coveralls -b . -r . -e examples -e tools -e jni -e python -e images -E ".*CompilerId.*"; fi +- cd .. deploy: provider: releases api_key: -- cgit v1.2.3 From 60ba85e9cd74890a53c09ade65e421788c10e0a9 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Mon, 15 Feb 2016 14:52:26 +0900 Subject: Add all_branches: true. --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 17c8e71..fdbee15 100644 --- a/.travis.yml +++ b/.travis.yml @@ -59,6 +59,7 @@ deploy: api_key: secure: AsXameK4GJn6h6wMmDrKTr7q/o9EI7hX7zWg1W6VaFBQKfkBvOmjJolWimjl6HMoRZ1NpMmK5GDm3zBlTUeABtgVBIyNWgE9vWS39ff6D5iQKcgScFsJkyILt0GikBqbN2pLGQ2t/M1Qh6n1sEIfzqekiCcF5Qvy5yYlYvHtaRGV02QeYAej/xx15/9SMuKTncHhjf63ClYPu8ODid7QUegJUvlQUeXoPsBDbaXMH2uDWoBWF7etX7G2Iob4NE8GX+ZP6dj+Ogi7p4HXThK650mzLL/pUl584EjjY/vePqx0cFhtpiRwvrW8SNPI1aJ1Phwa1enLRUgfS3bnkwQAMw/SCXSK2lnCvkUAXyTgpG03HWrZURj4vhEPXc7qHooO+dsfmi+JanYLaSDyrGpgQznLGjCMnVATimry0KxSufUY8Wt72Wh+nf7N0IgTUCjl32sWnQd/MRZPkxFuaf1h7r9RoH9KZY0yIOV09gABEFCGrOIZA2FcyhC2G26Bc4zyNrfMFpZ2DI76qdcWNdJGkRkpxtH9sGU8JgZu6Em2f1e6+SLgkBsPxbhRk5PwdhA9AXE2p9PmQqhO3jJKusGBZSoHAF7TlwagRY2J01yJxF7ge6zG9U8QuBqs1bB1zdnE34fHWOgs4st3inC+oBDOhvnEg1Nm/qeYVWMBzpwclSg= file: tiny_obj_loader.h + all_branches: true on: repo: syoyo/tinyobjloader tags: true -- cgit v1.2.3 From ecf1005c72ca6d30a1d8385291ddadbd98071ef3 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Tue, 16 Feb 2016 16:50:09 +0900 Subject: Exclude feature_tests.* from coverall. --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index fdbee15..7286151 100644 --- a/.travis.yml +++ b/.travis.yml @@ -52,7 +52,7 @@ script: - ninja - ./test_loader ../cornell_box.obj - if [ -n "$REPORT_COVERAGE" ]; then coveralls -b . -r . -e examples -e tools -e jni - -e python -e images -E ".*CompilerId.*"; fi + -e python -e images -E ".*CompilerId.*" -E feature_tests.* ; fi - cd .. deploy: provider: releases -- cgit v1.2.3 From d55863ce50c97ee0f5457d0e3e88a5608b66d4bd Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Tue, 16 Feb 2016 16:52:00 +0900 Subject: Fix coverall basedir argument. --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 7286151..ab77137 100644 --- a/.travis.yml +++ b/.travis.yml @@ -51,8 +51,8 @@ script: .. - ninja - ./test_loader ../cornell_box.obj -- if [ -n "$REPORT_COVERAGE" ]; then coveralls -b . -r . -e examples -e tools -e jni - -e python -e images -E ".*CompilerId.*" -E feature_tests.* ; fi +- if [ -n "$REPORT_COVERAGE" ]; then coveralls -b . -r .. -e examples -e tools -e jni + -e python -e images -E ".*CompilerId.*" -E ".*feature_tests.*" ; fi - cd .. deploy: provider: releases -- cgit v1.2.3 From 7eb029edaf379bf2935d17e611185830654ac78b Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Wed, 17 Feb 2016 16:58:05 +0900 Subject: Add link to Android Vulkan demo. --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 69dd3ea..fb43e0c 100644 --- a/README.md +++ b/README.md @@ -59,6 +59,7 @@ TinyObjLoader is successfully used in ... * sdlgl3-wavefront OpenGL .obj viewer https://github.com/chrisliebert/sdlgl3-wavefront * pbrt-v3 https://https://github.com/mmp/pbrt-v3 * cocos2d-x https://github.com/cocos2d/cocos2d-x/ +* Android Vulkan demo https://github.com/SaschaWillems/Vulkan * Your project here! Features -- cgit v1.2.3 From 9c1826381c067597e17e0b60102cd8b6a1222363 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Fri, 19 Feb 2016 12:25:16 +0900 Subject: Update travis script. --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index ab77137..f672abc 100644 --- a/.travis.yml +++ b/.travis.yml @@ -63,3 +63,4 @@ deploy: on: repo: syoyo/tinyobjloader tags: true + skip_cleanup: true -- cgit v1.2.3 From cf52401ca777952952585c7101950e559cb45b32 Mon Sep 17 00:00:00 2001 From: Ambal Date: Sat, 20 Feb 2016 12:09:54 +0200 Subject: Replace calls to 'isdigit', 'isSpace' and 'isNewLine' functions to macros. Other small performance tweaks. --- tiny_obj_loader.h | 86 +++++++++++++++++++++++++++++-------------------------- 1 file changed, 45 insertions(+), 41 deletions(-) diff --git a/tiny_obj_loader.h b/tiny_obj_loader.h index 4240c66..11e5e6e 100644 --- a/tiny_obj_loader.h +++ b/tiny_obj_loader.h @@ -205,11 +205,9 @@ struct obj_shape { 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'); -} +#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') ) // Make index zero-base, and also support relative index. static inline int fixIndex(int idx, int n) { @@ -297,13 +295,13 @@ static bool tryParseDouble(const char *s, const char *s_end, double *result) { if (*curr == '+' || *curr == '-') { sign = *curr; curr++; - } else if (isdigit(*curr)) { /* Pass through. */ + } else if (IS_DIGIT(*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)) && IS_DIGIT(*curr)) { mantissa *= 10; mantissa += static_cast(*curr - 0x30); curr++; @@ -321,7 +319,7 @@ static bool tryParseDouble(const char *s, const char *s_end, double *result) { if (*curr == '.') { curr++; read = 1; - while ((end_not_reached = (curr != s_end)) && isdigit(*curr)) { + while ((end_not_reached = (curr != s_end)) && IS_DIGIT(*curr)) { // NOTE: Don't use powf here, it will absolutely murder precision. mantissa += static_cast(*curr - 0x30) * pow(10.0, -read); read++; @@ -342,14 +340,14 @@ static bool tryParseDouble(const char *s, const char *s_end, double *result) { if ((end_not_reached = (curr != s_end)) && (*curr == '+' || *curr == '-')) { exp_sign = *curr; curr++; - } else if (isdigit(*curr)) { /* Pass through. */ + } else if (IS_DIGIT(*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)) && IS_DIGIT(*curr)) { exponent *= 10; exponent += static_cast(*curr - 0x30); curr++; @@ -625,7 +623,7 @@ void LoadMtl(std::map &material_map, continue; // comment line // new mtl - if ((0 == strncmp(token, "newmtl", 6)) && isSpace((token[6]))) { + if ((0 == strncmp(token, "newmtl", 6)) && IS_SPACE((token[6]))) { // flush previous material. if (!material.name.empty()) { material_map.insert(std::pair( @@ -649,7 +647,7 @@ void LoadMtl(std::map &material_map, } // ambient - if (token[0] == 'K' && token[1] == 'a' && isSpace((token[2]))) { + if (token[0] == 'K' && token[1] == 'a' && IS_SPACE((token[2]))) { token += 2; float r, g, b; parseFloat3(r, g, b, token); @@ -660,7 +658,7 @@ void LoadMtl(std::map &material_map, } // diffuse - if (token[0] == 'K' && token[1] == 'd' && isSpace((token[2]))) { + if (token[0] == 'K' && token[1] == 'd' && IS_SPACE((token[2]))) { token += 2; float r, g, b; parseFloat3(r, g, b, token); @@ -671,7 +669,7 @@ void LoadMtl(std::map &material_map, } // specular - if (token[0] == 'K' && token[1] == 's' && isSpace((token[2]))) { + if (token[0] == 'K' && token[1] == 's' && IS_SPACE((token[2]))) { token += 2; float r, g, b; parseFloat3(r, g, b, token); @@ -682,7 +680,7 @@ void LoadMtl(std::map &material_map, } // transmittance - if (token[0] == 'K' && token[1] == 't' && isSpace((token[2]))) { + if (token[0] == 'K' && token[1] == 't' && IS_SPACE((token[2]))) { token += 2; float r, g, b; parseFloat3(r, g, b, token); @@ -693,14 +691,14 @@ void LoadMtl(std::map &material_map, } // ior(index of refraction) - if (token[0] == 'N' && token[1] == 'i' && isSpace((token[2]))) { + if (token[0] == 'N' && token[1] == 'i' && IS_SPACE((token[2]))) { token += 2; material.ior = parseFloat(token); continue; } // emission - if (token[0] == 'K' && token[1] == 'e' && isSpace(token[2])) { + if (token[0] == 'K' && token[1] == 'e' && IS_SPACE(token[2])) { token += 2; float r, g, b; parseFloat3(r, g, b, token); @@ -711,26 +709,26 @@ void LoadMtl(std::map &material_map, } // shininess - if (token[0] == 'N' && token[1] == 's' && isSpace(token[2])) { + if (token[0] == 'N' && token[1] == 's' && IS_SPACE(token[2])) { token += 2; material.shininess = parseFloat(token); continue; } // illum model - if (0 == strncmp(token, "illum", 5) && isSpace(token[5])) { + if (0 == strncmp(token, "illum", 5) && IS_SPACE(token[5])) { token += 6; material.illum = parseInt(token); continue; } // dissolve - if ((token[0] == 'd' && isSpace(token[1]))) { + if ((token[0] == 'd' && IS_SPACE(token[1]))) { token += 1; material.dissolve = parseFloat(token); continue; } - if (token[0] == 'T' && token[1] == 'r' && isSpace(token[2])) { + if (token[0] == 'T' && token[1] == 'r' && IS_SPACE(token[2])) { token += 2; // Invert value of Tr(assume Tr is in range [0, 1]) material.dissolve = 1.0f - parseFloat(token); @@ -738,56 +736,56 @@ void LoadMtl(std::map &material_map, } // ambient texture - if ((0 == strncmp(token, "map_Ka", 6)) && isSpace(token[6])) { + if ((0 == strncmp(token, "map_Ka", 6)) && IS_SPACE(token[6])) { token += 7; material.ambient_texname = token; continue; } // diffuse texture - if ((0 == strncmp(token, "map_Kd", 6)) && isSpace(token[6])) { + if ((0 == strncmp(token, "map_Kd", 6)) && IS_SPACE(token[6])) { token += 7; material.diffuse_texname = token; continue; } // specular texture - if ((0 == strncmp(token, "map_Ks", 6)) && isSpace(token[6])) { + if ((0 == strncmp(token, "map_Ks", 6)) && IS_SPACE(token[6])) { token += 7; material.specular_texname = token; continue; } // specular highlight texture - if ((0 == strncmp(token, "map_Ns", 6)) && isSpace(token[6])) { + if ((0 == strncmp(token, "map_Ns", 6)) && IS_SPACE(token[6])) { token += 7; material.specular_highlight_texname = token; continue; } // bump texture - if ((0 == strncmp(token, "map_bump", 8)) && isSpace(token[8])) { + if ((0 == strncmp(token, "map_bump", 8)) && IS_SPACE(token[8])) { token += 9; material.bump_texname = token; continue; } // alpha texture - if ((0 == strncmp(token, "map_d", 5)) && isSpace(token[5])) { + if ((0 == strncmp(token, "map_d", 5)) && IS_SPACE(token[5])) { token += 6; material.alpha_texname = token; continue; } // bump texture - if ((0 == strncmp(token, "bump", 4)) && isSpace(token[4])) { + if ((0 == strncmp(token, "bump", 4)) && IS_SPACE(token[4])) { token += 5; material.bump_texname = token; continue; } // displacement texture - if ((0 == strncmp(token, "disp", 4)) && isSpace(token[4])) { + if ((0 == strncmp(token, "disp", 4)) && IS_SPACE(token[4])) { token += 5; material.displacement_texname = token; continue; @@ -914,7 +912,7 @@ bool LoadObj(std::vector &shapes, // [output] continue; // comment line // vertex - if (token[0] == 'v' && isSpace((token[1]))) { + if (token[0] == 'v' && IS_SPACE((token[1]))) { token += 2; float x, y, z; parseFloat3(x, y, z, token); @@ -925,7 +923,7 @@ bool LoadObj(std::vector &shapes, // [output] } // normal - if (token[0] == 'v' && token[1] == 'n' && isSpace((token[2]))) { + if (token[0] == 'v' && token[1] == 'n' && IS_SPACE((token[2]))) { token += 3; float x, y, z; parseFloat3(x, y, z, token); @@ -936,7 +934,7 @@ bool LoadObj(std::vector &shapes, // [output] } // texcoord - if (token[0] == 'v' && token[1] == 't' && isSpace((token[2]))) { + if (token[0] == 'v' && token[1] == 't' && IS_SPACE((token[2]))) { token += 3; float x, y; parseFloat2(x, y, token); @@ -946,12 +944,14 @@ bool LoadObj(std::vector &shapes, // [output] } // face - if (token[0] == 'f' && isSpace((token[1]))) { + if (token[0] == 'f' && IS_SPACE((token[1]))) { token += 2; token += strspn(token, " \t"); std::vector face; - while (!isNewLine(token[0])) { + face.reserve(3); + + while (!IS_NEW_LINE(token[0])) { vertex_index vi = parseTriple(token, static_cast(v.size() / 3), static_cast(vn.size() / 3), static_cast(vt.size() / 2)); @@ -960,13 +960,15 @@ bool LoadObj(std::vector &shapes, // [output] token += n; } - faceGroup.push_back(face); + // replace with emplace_back + std::move on C++11 + faceGroup.push_back(std::vector()); + faceGroup[faceGroup.size() - 1].swap(face); continue; } // use mtl - if ((0 == strncmp(token, "usemtl", 6)) && isSpace((token[6]))) { + if ((0 == strncmp(token, "usemtl", 6)) && IS_SPACE((token[6]))) { char namebuf[TINYOBJ_SSCANF_BUFFER_SIZE]; token += 7; @@ -997,7 +999,7 @@ bool LoadObj(std::vector &shapes, // [output] } // load mtl - if ((0 == strncmp(token, "mtllib", 6)) && isSpace((token[6]))) { + if ((0 == strncmp(token, "mtllib", 6)) && IS_SPACE((token[6]))) { char namebuf[TINYOBJ_SSCANF_BUFFER_SIZE]; token += 7; #ifdef _MSC_VER @@ -1019,7 +1021,7 @@ bool LoadObj(std::vector &shapes, // [output] } // group name - if (token[0] == 'g' && isSpace((token[1]))) { + if (token[0] == 'g' && IS_SPACE((token[1]))) { // flush previous face group. bool ret = @@ -1035,7 +1037,9 @@ bool LoadObj(std::vector &shapes, // [output] faceGroup.clear(); std::vector names; - while (!isNewLine(token[0])) { + names.reserve(2); + + while (!IS_NEW_LINE(token[0])) { std::string str = parseString(token); names.push_back(str); token += strspn(token, " \t\r"); // skip tag @@ -1054,7 +1058,7 @@ bool LoadObj(std::vector &shapes, // [output] } // object name - if (token[0] == 'o' && isSpace((token[1]))) { + if (token[0] == 'o' && IS_SPACE((token[1]))) { // flush previous face group. bool ret = @@ -1081,7 +1085,7 @@ bool LoadObj(std::vector &shapes, // [output] continue; } - if (token[0] == 't' && isSpace(token[1])) { + if (token[0] == 't' && IS_SPACE(token[1])) { tag_t tag; char namebuf[4096]; -- cgit v1.2.3 From 0f00a3b3e8d3c12eb4a23948cc0ff1caecf16067 Mon Sep 17 00:00:00 2001 From: "Mr.doob" Date: Mon, 22 Feb 2016 10:04:33 +0900 Subject: Fixed links. --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index fb43e0c..b10b65e 100644 --- a/README.md +++ b/README.md @@ -50,14 +50,14 @@ Use case TinyObjLoader is successfully used in ... * bullet3 https://github.com/erwincoumans/bullet3 -* pbrt-v2 https://https://github.com/mmp/pbrt-v2 +* pbrt-v2 https://github.com/mmp/pbrt-v2 * OpenGL game engine development http://swarminglogic.com/jotting/2013_10_gamedev01 * mallie https://lighttransport.github.io/mallie * IBLBaker (Image Based Lighting Baker). http://www.derkreature.com/iblbaker/ * Stanford CS148 http://web.stanford.edu/class/cs148/assignments/assignment3.pdf * Awesome Bump http://awesomebump.besaba.com/about/ * sdlgl3-wavefront OpenGL .obj viewer https://github.com/chrisliebert/sdlgl3-wavefront -* pbrt-v3 https://https://github.com/mmp/pbrt-v3 +* pbrt-v3 https://github.com/mmp/pbrt-v3 * cocos2d-x https://github.com/cocos2d/cocos2d-x/ * Android Vulkan demo https://github.com/SaschaWillems/Vulkan * Your project here! -- cgit v1.2.3 From 21c93c51eddfa68df003053f470003661e9d5b8e Mon Sep 17 00:00:00 2001 From: Prayag Verma Date: Mon, 22 Feb 2016 09:38:18 +0530 Subject: Fix a typo and add syntax highlighting language MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `poweful` → `powerful` --- README.md | 183 +++++++++++++++++++++++++++++++------------------------------- 1 file changed, 92 insertions(+), 91 deletions(-) diff --git a/README.md b/README.md index b10b65e..4a063b6 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ tinyobjloader http://syoyo.github.io/tinyobjloader/ -Tiny but poweful single file wavefront obj loader written in C++. No dependency except for C++ STL. It can parse 10M over polygons with moderate memory and time. +Tiny but powerful single file wavefront obj loader written in C++. No dependency except for C++ STL. It can parse 10M over polygons with moderate memory and time. `tinyobjloader` is good for embedding .obj loader to your (global illumination) renderer ;-) @@ -88,106 +88,107 @@ Usage ----- TinyObjLoader triangulate input .obj by default. +```c++ +#define TINYOBJLOADER_IMPLEMENTATION // define this in only *one* .cc +#include "tiny_obj_loader.h" - #define TINYOBJLOADER_IMPLEMENTATION // define this in only *one* .cc - #include "tiny_obj_loader.h" - - std::string inputfile = "cornell_box.obj"; - std::vector shapes; - std::vector materials; +std::string inputfile = "cornell_box.obj"; +std::vector shapes; +std::vector materials; - std::string err; - bool ret = tinyobj::LoadObj(shapes, materials, err, inputfile.c_str()); +std::string err; +bool ret = tinyobj::LoadObj(shapes, materials, err, inputfile.c_str()); - if (!err.empty()) { // `err` may contain warning message. - std::cerr << err << std::endl; - } +if (!err.empty()) { // `err` may contain warning message. + std::cerr << err << std::endl; +} - if (!ret) { - exit(1); - } +if (!ret) { + exit(1); +} - std::cout << "# of shapes : " << shapes.size() << std::endl; - std::cout << "# of materials : " << materials.size() << std::endl; +std::cout << "# of shapes : " << shapes.size() << std::endl; +std::cout << "# of materials : " << materials.size() << std::endl; - 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()); - 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]); - } - - 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]); - } - } - - 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]); - printf(" material.Kd = (%f, %f ,%f)\n", materials[i].diffuse[0], materials[i].diffuse[1], materials[i].diffuse[2]); - printf(" material.Ks = (%f, %f ,%f)\n", materials[i].specular[0], materials[i].specular[1], materials[i].specular[2]); - printf(" material.Tr = (%f, %f ,%f)\n", materials[i].transmittance[0], materials[i].transmittance[1], materials[i].transmittance[2]); - printf(" material.Ke = (%f, %f ,%f)\n", materials[i].emission[0], materials[i].emission[1], materials[i].emission[2]); - printf(" material.Ns = %f\n", materials[i].shininess); - printf(" material.Ni = %f\n", materials[i].ior); - printf(" material.dissolve = %f\n", materials[i].dissolve); - printf(" material.illum = %d\n", materials[i].illum); - printf(" material.map_Ka = %s\n", materials[i].ambient_texname.c_str()); - printf(" material.map_Kd = %s\n", materials[i].diffuse_texname.c_str()); - printf(" material.map_Ks = %s\n", materials[i].specular_texname.c_str()); - printf(" material.map_Ns = %s\n", materials[i].specular_highlight_texname.c_str()); - std::map::const_iterator it(materials[i].unknown_parameter.begin()); - std::map::const_iterator itEnd(materials[i].unknown_parameter.end()); - for (; it != itEnd; it++) { - printf(" material.%s = %s\n", it->first.c_str(), it->second.c_str()); - } - printf("\n"); - } - +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()); + 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]); + } + + 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]); + } +} + +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]); + printf(" material.Kd = (%f, %f ,%f)\n", materials[i].diffuse[0], materials[i].diffuse[1], materials[i].diffuse[2]); + printf(" material.Ks = (%f, %f ,%f)\n", materials[i].specular[0], materials[i].specular[1], materials[i].specular[2]); + printf(" material.Tr = (%f, %f ,%f)\n", materials[i].transmittance[0], materials[i].transmittance[1], materials[i].transmittance[2]); + printf(" material.Ke = (%f, %f ,%f)\n", materials[i].emission[0], materials[i].emission[1], materials[i].emission[2]); + printf(" material.Ns = %f\n", materials[i].shininess); + printf(" material.Ni = %f\n", materials[i].ior); + printf(" material.dissolve = %f\n", materials[i].dissolve); + printf(" material.illum = %d\n", materials[i].illum); + printf(" material.map_Ka = %s\n", materials[i].ambient_texname.c_str()); + printf(" material.map_Kd = %s\n", materials[i].diffuse_texname.c_str()); + printf(" material.map_Ks = %s\n", materials[i].specular_texname.c_str()); + printf(" material.map_Ns = %s\n", materials[i].specular_highlight_texname.c_str()); + std::map::const_iterator it(materials[i].unknown_parameter.begin()); + std::map::const_iterator itEnd(materials[i].unknown_parameter.end()); + for (; it != itEnd; it++) { + printf(" material.%s = %s\n", it->first.c_str(), it->second.c_str()); + } + printf("\n"); +} +``` Reading .obj without triangulation. Use `num_vertices[i]` to iterate over faces(indices). `num_vertices[i]` stores the number of vertices for ith face. +```c++ +#define TINYOBJLOADER_IMPLEMENTATION // define this in only *one* .cc +#include "tiny_obj_loader.h" - #define TINYOBJLOADER_IMPLEMENTATION // define this in only *one* .cc - #include "tiny_obj_loader.h" - - std::string inputfile = "cornell_box.obj"; - std::vector shapes; - std::vector materials; +std::string inputfile = "cornell_box.obj"; +std::vector shapes; +std::vector materials; - std::string err; - bool triangulate = false; - bool ret = tinyobj::LoadObj(shapes, materials, err, inputfile.c_str(), triangulate); +std::string err; +bool triangulate = false; +bool ret = tinyobj::LoadObj(shapes, materials, err, inputfile.c_str(), triangulate); - if (!err.empty()) { // `err` may contain warning message. - std::cerr << err << std::endl; - } - - if (!ret) { - exit(1); +if (!err.empty()) { // `err` may contain warning message. + std::cerr << err << std::endl; +} + +if (!ret) { + exit(1); +} + +for (size_t i = 0; i < shapes.size(); i++) { + + size_t indexOffset = 0; + for (size_t n = 0; n < shapes[i].mesh.num_vertices.size(); n++) { + int ngon = shapes[i].mesh.num_vertices[n]; + for (size_t f = 0; f < ngon; f++) { + unsigend int v = shapes[i].mesh.indices[indexOffset + f]; + printf(" face[%ld] v[%ld] = (%f, %f, %f)\n", n, + shapes[i].mesh.positions[3*v+0], + shapes[i].mesh.positions[3*v+1], + shapes[i].mesh.positions[3*v+2]); + } + indexOffset += ngon; + } - for (size_t i = 0; i < shapes.size(); i++) { - - size_t indexOffset = 0; - for (size_t n = 0; n < shapes[i].mesh.num_vertices.size(); n++) { - int ngon = shapes[i].mesh.num_vertices[n]; - for (size_t f = 0; f < ngon; f++) { - unsigend int v = shapes[i].mesh.indices[indexOffset + f]; - printf(" face[%ld] v[%ld] = (%f, %f, %f)\n", n, - shapes[i].mesh.positions[3*v+0], - shapes[i].mesh.positions[3*v+1], - shapes[i].mesh.positions[3*v+2]); - - } - indexOffset += ngon; - } - - } +} +``` -- cgit v1.2.3 From bc42bc47ad3bca275f1348e88c60d375b3f582d8 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Sat, 12 Mar 2016 02:14:52 +0900 Subject: Don't create new shape by `usemtl`. Fixes #68. --- test.cc | 1 + tiny_obj_loader.h | 23 +++++++++++------------ 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/test.cc b/test.cc index c2047a9..2539faa 100644 --- a/test.cc +++ b/test.cc @@ -139,6 +139,7 @@ TestLoadObj( } if (!ret) { + printf("Failed to load/parse .obj.\n"); return false; } diff --git a/tiny_obj_loader.h b/tiny_obj_loader.h index 11e5e6e..4a43498 100644 --- a/tiny_obj_loader.h +++ b/tiny_obj_loader.h @@ -5,6 +5,7 @@ // // +// 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 // version 0.9.15: Change API to handle no mtl file case correctly(#58) @@ -978,21 +979,19 @@ bool LoadObj(std::vector &shapes, // [output] sscanf(token, "%s", namebuf); #endif - // Create face group per material. - bool ret = - exportFaceGroupToShape(shape, vertexCache, v, vn, vt, faceGroup, tags, - material, name, true, triangulate); - if (ret) { - shapes.push_back(shape); - } - shape = shape_t(); - faceGroup.clear(); - + int newMaterialId = -1; if (material_map.find(namebuf) != material_map.end()) { - material = material_map[namebuf]; + newMaterialId = material_map[namebuf]; } else { // { error!! material not found } - material = -1; + } + + if (newMaterialId != material) { + // Create per-face material + exportFaceGroupToShape(shape, vertexCache, v, vn, vt, faceGroup, tags, + material, name, true, triangulate); + faceGroup.clear(); + material = newMaterialId; } continue; -- cgit v1.2.3 From f2db18dc53211a4e46727d3558f5563b8d5077e2 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Sat, 12 Mar 2016 16:13:04 +0900 Subject: Add bintray deploy in travis build. --- .bintray.in | 43 +++++++++++++++++++++++++++++++++++++++++++ .travis.yml | 23 ++++++++++++----------- tools/travis_postbuild.sh | 12 ++++++++++++ 3 files changed, 67 insertions(+), 11 deletions(-) create mode 100644 .bintray.in create mode 100755 tools/travis_postbuild.sh diff --git a/.bintray.in b/.bintray.in new file mode 100644 index 0000000..e38c1db --- /dev/null +++ b/.bintray.in @@ -0,0 +1,43 @@ +{ + /* Bintray package information. + In case the package already exists on Bintray, only the name, repo and subject + fields are mandatory. */ + + "package": { + "name": "releases", // Bintray package name + "repo": "tinyobjloader", // Bintray repository name + "subject": "syoyo" // Bintray subject (user or organization) + }, + + /* Package version information. + In case the version already exists on Bintray, only the name fields is mandatory. */ + + "version": { + "name": "@VERSION@", + "desc": "@VERSION@", + "released": "@DATE@", + "vcs_tag": "@VERSION@", + "gpgSign": false + }, + + /* Configure the files you would like to upload to Bintray and their upload path. + You can define one or more groups of patterns. + Each group contains three patterns: + + includePattern: Pattern in the form of Ruby regular expression, indicating the path of files to be uploaded to Bintray. + excludePattern: Optional. Pattern in the form of Ruby regular expression, indicating the path of files to be removed from the list of files specified by the includePattern. + uploadPattern: Upload path on Bintray. The path can contain symbols in the form of $1, $2,... that are replaced with capturing groups defined in the include pattern. + + In the example below, the following files are uploaded, + 1. All gem files located under build/bin/ (including sub directories), + except for files under a the do-not-deploy directory. + The files will be uploaded to Bintray under the gems folder. + 2. All files under build/docs. The files will be uploaded to Bintray under the docs folder. + + Note: Regular expressions defined as part of the includePattern property must be wrapped with brackets. */ + + "files": + [ "tiny_obj_loader.h" ], + "publish": true +} + diff --git a/.travis.yml b/.travis.yml index f672abc..1768469 100644 --- a/.travis.yml +++ b/.travis.yml @@ -51,16 +51,17 @@ script: .. - ninja - ./test_loader ../cornell_box.obj -- if [ -n "$REPORT_COVERAGE" ]; then coveralls -b . -r .. -e examples -e tools -e jni - -e python -e images -E ".*CompilerId.*" -E ".*feature_tests.*" ; fi +- if [ -n "$REPORT_COVERAGE" ]; then coveralls -b . -r .. -e examples -e tools -e + jni -e python -e images -E ".*CompilerId.*" -E ".*feature_tests.*" ; fi - cd .. + +before_deploy: + - echo "Creating description file for bintray." + - ./tools/travis_postbuild.sh + deploy: - provider: releases - api_key: - secure: AsXameK4GJn6h6wMmDrKTr7q/o9EI7hX7zWg1W6VaFBQKfkBvOmjJolWimjl6HMoRZ1NpMmK5GDm3zBlTUeABtgVBIyNWgE9vWS39ff6D5iQKcgScFsJkyILt0GikBqbN2pLGQ2t/M1Qh6n1sEIfzqekiCcF5Qvy5yYlYvHtaRGV02QeYAej/xx15/9SMuKTncHhjf63ClYPu8ODid7QUegJUvlQUeXoPsBDbaXMH2uDWoBWF7etX7G2Iob4NE8GX+ZP6dj+Ogi7p4HXThK650mzLL/pUl584EjjY/vePqx0cFhtpiRwvrW8SNPI1aJ1Phwa1enLRUgfS3bnkwQAMw/SCXSK2lnCvkUAXyTgpG03HWrZURj4vhEPXc7qHooO+dsfmi+JanYLaSDyrGpgQznLGjCMnVATimry0KxSufUY8Wt72Wh+nf7N0IgTUCjl32sWnQd/MRZPkxFuaf1h7r9RoH9KZY0yIOV09gABEFCGrOIZA2FcyhC2G26Bc4zyNrfMFpZ2DI76qdcWNdJGkRkpxtH9sGU8JgZu6Em2f1e6+SLgkBsPxbhRk5PwdhA9AXE2p9PmQqhO3jJKusGBZSoHAF7TlwagRY2J01yJxF7ge6zG9U8QuBqs1bB1zdnE34fHWOgs4st3inC+oBDOhvnEg1Nm/qeYVWMBzpwclSg= - file: tiny_obj_loader.h - all_branches: true - on: - repo: syoyo/tinyobjloader - tags: true - skip_cleanup: true + provider: bintray + file: ".bintray.json" + user: "syoyo" + key: + secure: W4F1VZcDcVOMe8Ymvo0bHery/JSmVhadl1NgAnGus6o7zVw7ChElKA1ho/NtqUbtoW8o1qUKMJdLQeh786jolocZJEJlns9JZ5FCet6H2b3kITfUa4GR5T11V/ZYwL3SajW8vZ1xu5UrpP5HHgFMYtxb1MFrNLDI60sh0RnyV/qFFBnCJGZPagF/M1mzbJeDml5xK5lShH0r8QpH+7MeQ1J8ungEyJ7UCyr1ao8gY9eq1/05IpHR9vri/d48EXQWHbqtI8EwCc7064oCYQGyYcLsD4yPEokwrdelkCvDquSpJLmbJENfZCc4vZGXsykjnQ8+gltJomBAivQFB9vc06ETEJssMzitbrfEZUrqFwZj/HZM7CYGXfGQWltL828SppCjsuWrgQ/VYXM5UgRpmhlxbqnuyxnYvKZ9EDW4+EnMkOmIl7WSDovp8E/4CZ0ghs+YyFS4SrgeqFCXS8bvxrkDUUPSipHuGBOt02fRnccKzU+3zU6Q5fghyLczz4ZtnOdk+Niz/njyF0SZfPYTUgb3GzAJ8Su6kvWJCAGdedON3n1F/TtybCE2dIdATxaO2uFQbwYjSOCiq209oCJ7MrsQZibRsa5a9YXyjlLkPxwOeVwo8wJyJclqWswIkhdSO8xvTnkwESv4yLzLutEOlBVlQbJzpyuS6vx0yHOYkwc= diff --git a/tools/travis_postbuild.sh b/tools/travis_postbuild.sh new file mode 100755 index 0000000..5737d86 --- /dev/null +++ b/tools/travis_postbuild.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +DATEVAL=`date +%b-%d-%Y` +VERSIONVAL=master + +# Use tag as version +if [ $TRAVIS_TAG ]; then + VERSIONVAL=$TRAVIS_TAG +fi + +sed -e s%@DATE@%${DATEVAL}% .bintray.in > .bintray.tmp +sed -e s%@VERSION@%${VERSIONVAL}% .bintray.tmp > .bintray.json -- cgit v1.2.3 From bf131698e51ab214e23f81d1c5f77992813ee309 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Sat, 12 Mar 2016 16:20:40 +0900 Subject: Use Bintray friendly data format. --- tools/travis_postbuild.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/travis_postbuild.sh b/tools/travis_postbuild.sh index 5737d86..00c5d49 100755 --- a/tools/travis_postbuild.sh +++ b/tools/travis_postbuild.sh @@ -1,6 +1,6 @@ #!/bin/bash -DATEVAL=`date +%b-%d-%Y` +DATEVAL=`date +%Y-%m-%d` VERSIONVAL=master # Use tag as version -- cgit v1.2.3 From 31c66335358f97b9a59d697effed474fe793a3b9 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Sat, 12 Mar 2016 16:27:58 +0900 Subject: Fix file patterns in bintray description. --- .bintray.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.bintray.in b/.bintray.in index e38c1db..ba1a949 100644 --- a/.bintray.in +++ b/.bintray.in @@ -37,7 +37,7 @@ Note: Regular expressions defined as part of the includePattern property must be wrapped with brackets. */ "files": - [ "tiny_obj_loader.h" ], + [ {"includePattern": "(tiny_obj_loader\.h)", "uploadPattern": "$1"} ], "publish": true } -- cgit v1.2.3 From d688bbd9107590039257e4e8509406fd515b61a3 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Sat, 12 Mar 2016 17:16:50 +0900 Subject: Create dist directory to specify uploading files. --- .bintray.in | 8 +------- .travis.yml | 3 +++ 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/.bintray.in b/.bintray.in index ba1a949..4336d65 100644 --- a/.bintray.in +++ b/.bintray.in @@ -28,16 +28,10 @@ excludePattern: Optional. Pattern in the form of Ruby regular expression, indicating the path of files to be removed from the list of files specified by the includePattern. uploadPattern: Upload path on Bintray. The path can contain symbols in the form of $1, $2,... that are replaced with capturing groups defined in the include pattern. - In the example below, the following files are uploaded, - 1. All gem files located under build/bin/ (including sub directories), - except for files under a the do-not-deploy directory. - The files will be uploaded to Bintray under the gems folder. - 2. All files under build/docs. The files will be uploaded to Bintray under the docs folder. - Note: Regular expressions defined as part of the includePattern property must be wrapped with brackets. */ "files": - [ {"includePattern": "(tiny_obj_loader\.h)", "uploadPattern": "$1"} ], + [ {"includePattern": "dist/(.*)", "uploadPattern": "$1"} ], "publish": true } diff --git a/.travis.yml b/.travis.yml index 1768469..5f152ae 100644 --- a/.travis.yml +++ b/.travis.yml @@ -54,6 +54,9 @@ script: - if [ -n "$REPORT_COVERAGE" ]; then coveralls -b . -r .. -e examples -e tools -e jni -e python -e images -E ".*CompilerId.*" -E ".*feature_tests.*" ; fi - cd .. +- rm -rf dist +- mkdir dist +- cp tiny_obj_loader.h dist/ before_deploy: - echo "Creating description file for bintray." -- cgit v1.2.3 From 06acb38847d02c2039cebc86d0da145f033d0172 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Sat, 12 Mar 2016 17:30:06 +0900 Subject: Resurrect github release deploy. Upload to bintray on specific build(DEPLOY_BUILD=1) --- .travis.yml | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 5f152ae..5154420 100644 --- a/.travis.yml +++ b/.travis.yml @@ -15,7 +15,7 @@ matrix: - g++-4.9 - clang-3.7 compiler: clang - env: COMPILER_VERSION=3.7 BUILD_TYPE=Debug + env: DEPLOY_BUILD=1 COMPILER_VERSION=3.7 BUILD_TYPE=Debug - addons: *1 compiler: clang env: COMPILER_VERSION=3.7 BUILD_TYPE=Release @@ -68,3 +68,16 @@ deploy: user: "syoyo" key: secure: W4F1VZcDcVOMe8Ymvo0bHery/JSmVhadl1NgAnGus6o7zVw7ChElKA1ho/NtqUbtoW8o1qUKMJdLQeh786jolocZJEJlns9JZ5FCet6H2b3kITfUa4GR5T11V/ZYwL3SajW8vZ1xu5UrpP5HHgFMYtxb1MFrNLDI60sh0RnyV/qFFBnCJGZPagF/M1mzbJeDml5xK5lShH0r8QpH+7MeQ1J8ungEyJ7UCyr1ao8gY9eq1/05IpHR9vri/d48EXQWHbqtI8EwCc7064oCYQGyYcLsD4yPEokwrdelkCvDquSpJLmbJENfZCc4vZGXsykjnQ8+gltJomBAivQFB9vc06ETEJssMzitbrfEZUrqFwZj/HZM7CYGXfGQWltL828SppCjsuWrgQ/VYXM5UgRpmhlxbqnuyxnYvKZ9EDW4+EnMkOmIl7WSDovp8E/4CZ0ghs+YyFS4SrgeqFCXS8bvxrkDUUPSipHuGBOt02fRnccKzU+3zU6Q5fghyLczz4ZtnOdk+Niz/njyF0SZfPYTUgb3GzAJ8Su6kvWJCAGdedON3n1F/TtybCE2dIdATxaO2uFQbwYjSOCiq209oCJ7MrsQZibRsa5a9YXyjlLkPxwOeVwo8wJyJclqWswIkhdSO8xvTnkwESv4yLzLutEOlBVlQbJzpyuS6vx0yHOYkwc= + on: + condition: -n "$DEPLOY_BUILD" + +deploy: + provider: releases + api_key: + secure: AsXameK4GJn6h6wMmDrKTr7q/o9EI7hX7zWg1W6VaFBQKfkBvOmjJolWimjl6HMoRZ1NpMmK5GDm3zBlTUeABtgVBIyNWgE9vWS39ff6D5iQKcgScFsJkyILt0GikBqbN2pLGQ2t/M1Qh6n1sEIfzqekiCcF5Qvy5yYlYvHtaRGV02QeYAej/xx15/9SMuKTncHhjf63ClYPu8ODid7QUegJUvlQUeXoPsBDbaXMH2uDWoBWF7etX7G2Iob4NE8GX+ZP6dj+Ogi7p4HXThK650mzLL/pUl584EjjY/vePqx0cFhtpiRwvrW8SNPI1aJ1Phwa1enLRUgfS3bnkwQAMw/SCXSK2lnCvkUAXyTgpG03HWrZURj4vhEPXc7qHooO+dsfmi+JanYLaSDyrGpgQznLGjCMnVATimry0KxSufUY8Wt72Wh+nf7N0IgTUCjl32sWnQd/MRZPkxFuaf1h7r9RoH9KZY0yIOV09gABEFCGrOIZA2FcyhC2G26Bc4zyNrfMFpZ2DI76qdcWNdJGkRkpxtH9sGU8JgZu6Em2f1e6+SLgkBsPxbhRk5PwdhA9AXE2p9PmQqhO3jJKusGBZSoHAF7TlwagRY2J01yJxF7ge6zG9U8QuBqs1bB1zdnE34fHWOgs4st3inC+oBDOhvnEg1Nm/qeYVWMBzpwclSg= + file: tiny_obj_loader.h + all_branches: true + on: + repo: syoyo/tinyobjloader + tags: true + skip_cleanup: true -- cgit v1.2.3 From 6a25058ef58110074a4991e28ab0815e218827f6 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Sat, 12 Mar 2016 17:38:31 +0900 Subject: Multiple deploy target. --- .travis.yml | 34 ++++++++++++++++------------------ 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/.travis.yml b/.travis.yml index 5154420..1ae49ef 100644 --- a/.travis.yml +++ b/.travis.yml @@ -63,21 +63,19 @@ before_deploy: - ./tools/travis_postbuild.sh deploy: - provider: bintray - file: ".bintray.json" - user: "syoyo" - key: - secure: W4F1VZcDcVOMe8Ymvo0bHery/JSmVhadl1NgAnGus6o7zVw7ChElKA1ho/NtqUbtoW8o1qUKMJdLQeh786jolocZJEJlns9JZ5FCet6H2b3kITfUa4GR5T11V/ZYwL3SajW8vZ1xu5UrpP5HHgFMYtxb1MFrNLDI60sh0RnyV/qFFBnCJGZPagF/M1mzbJeDml5xK5lShH0r8QpH+7MeQ1J8ungEyJ7UCyr1ao8gY9eq1/05IpHR9vri/d48EXQWHbqtI8EwCc7064oCYQGyYcLsD4yPEokwrdelkCvDquSpJLmbJENfZCc4vZGXsykjnQ8+gltJomBAivQFB9vc06ETEJssMzitbrfEZUrqFwZj/HZM7CYGXfGQWltL828SppCjsuWrgQ/VYXM5UgRpmhlxbqnuyxnYvKZ9EDW4+EnMkOmIl7WSDovp8E/4CZ0ghs+YyFS4SrgeqFCXS8bvxrkDUUPSipHuGBOt02fRnccKzU+3zU6Q5fghyLczz4ZtnOdk+Niz/njyF0SZfPYTUgb3GzAJ8Su6kvWJCAGdedON3n1F/TtybCE2dIdATxaO2uFQbwYjSOCiq209oCJ7MrsQZibRsa5a9YXyjlLkPxwOeVwo8wJyJclqWswIkhdSO8xvTnkwESv4yLzLutEOlBVlQbJzpyuS6vx0yHOYkwc= - on: - condition: -n "$DEPLOY_BUILD" - -deploy: - provider: releases - api_key: - secure: AsXameK4GJn6h6wMmDrKTr7q/o9EI7hX7zWg1W6VaFBQKfkBvOmjJolWimjl6HMoRZ1NpMmK5GDm3zBlTUeABtgVBIyNWgE9vWS39ff6D5iQKcgScFsJkyILt0GikBqbN2pLGQ2t/M1Qh6n1sEIfzqekiCcF5Qvy5yYlYvHtaRGV02QeYAej/xx15/9SMuKTncHhjf63ClYPu8ODid7QUegJUvlQUeXoPsBDbaXMH2uDWoBWF7etX7G2Iob4NE8GX+ZP6dj+Ogi7p4HXThK650mzLL/pUl584EjjY/vePqx0cFhtpiRwvrW8SNPI1aJ1Phwa1enLRUgfS3bnkwQAMw/SCXSK2lnCvkUAXyTgpG03HWrZURj4vhEPXc7qHooO+dsfmi+JanYLaSDyrGpgQznLGjCMnVATimry0KxSufUY8Wt72Wh+nf7N0IgTUCjl32sWnQd/MRZPkxFuaf1h7r9RoH9KZY0yIOV09gABEFCGrOIZA2FcyhC2G26Bc4zyNrfMFpZ2DI76qdcWNdJGkRkpxtH9sGU8JgZu6Em2f1e6+SLgkBsPxbhRk5PwdhA9AXE2p9PmQqhO3jJKusGBZSoHAF7TlwagRY2J01yJxF7ge6zG9U8QuBqs1bB1zdnE34fHWOgs4st3inC+oBDOhvnEg1Nm/qeYVWMBzpwclSg= - file: tiny_obj_loader.h - all_branches: true - on: - repo: syoyo/tinyobjloader - tags: true - skip_cleanup: true + - provider: bintray + file: ".bintray.json" + user: "syoyo" + key: + secure: W4F1VZcDcVOMe8Ymvo0bHery/JSmVhadl1NgAnGus6o7zVw7ChElKA1ho/NtqUbtoW8o1qUKMJdLQeh786jolocZJEJlns9JZ5FCet6H2b3kITfUa4GR5T11V/ZYwL3SajW8vZ1xu5UrpP5HHgFMYtxb1MFrNLDI60sh0RnyV/qFFBnCJGZPagF/M1mzbJeDml5xK5lShH0r8QpH+7MeQ1J8ungEyJ7UCyr1ao8gY9eq1/05IpHR9vri/d48EXQWHbqtI8EwCc7064oCYQGyYcLsD4yPEokwrdelkCvDquSpJLmbJENfZCc4vZGXsykjnQ8+gltJomBAivQFB9vc06ETEJssMzitbrfEZUrqFwZj/HZM7CYGXfGQWltL828SppCjsuWrgQ/VYXM5UgRpmhlxbqnuyxnYvKZ9EDW4+EnMkOmIl7WSDovp8E/4CZ0ghs+YyFS4SrgeqFCXS8bvxrkDUUPSipHuGBOt02fRnccKzU+3zU6Q5fghyLczz4ZtnOdk+Niz/njyF0SZfPYTUgb3GzAJ8Su6kvWJCAGdedON3n1F/TtybCE2dIdATxaO2uFQbwYjSOCiq209oCJ7MrsQZibRsa5a9YXyjlLkPxwOeVwo8wJyJclqWswIkhdSO8xvTnkwESv4yLzLutEOlBVlQbJzpyuS6vx0yHOYkwc= + on: + condition: -n "$DEPLOY_BUILD" + - provider: releases + api_key: + secure: AsXameK4GJn6h6wMmDrKTr7q/o9EI7hX7zWg1W6VaFBQKfkBvOmjJolWimjl6HMoRZ1NpMmK5GDm3zBlTUeABtgVBIyNWgE9vWS39ff6D5iQKcgScFsJkyILt0GikBqbN2pLGQ2t/M1Qh6n1sEIfzqekiCcF5Qvy5yYlYvHtaRGV02QeYAej/xx15/9SMuKTncHhjf63ClYPu8ODid7QUegJUvlQUeXoPsBDbaXMH2uDWoBWF7etX7G2Iob4NE8GX+ZP6dj+Ogi7p4HXThK650mzLL/pUl584EjjY/vePqx0cFhtpiRwvrW8SNPI1aJ1Phwa1enLRUgfS3bnkwQAMw/SCXSK2lnCvkUAXyTgpG03HWrZURj4vhEPXc7qHooO+dsfmi+JanYLaSDyrGpgQznLGjCMnVATimry0KxSufUY8Wt72Wh+nf7N0IgTUCjl32sWnQd/MRZPkxFuaf1h7r9RoH9KZY0yIOV09gABEFCGrOIZA2FcyhC2G26Bc4zyNrfMFpZ2DI76qdcWNdJGkRkpxtH9sGU8JgZu6Em2f1e6+SLgkBsPxbhRk5PwdhA9AXE2p9PmQqhO3jJKusGBZSoHAF7TlwagRY2J01yJxF7ge6zG9U8QuBqs1bB1zdnE34fHWOgs4st3inC+oBDOhvnEg1Nm/qeYVWMBzpwclSg= + file: tiny_obj_loader.h + all_branches: true + on: + repo: syoyo/tinyobjloader + tags: true + skip_cleanup: true -- cgit v1.2.3 From d6cd178d6a4c6f3082cf4731cf7d138f72eba53f Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Sat, 12 Mar 2016 17:42:31 +0900 Subject: Add test data for issue 68. Deploy to Bintray only for the build with tag. --- .travis.yml | 4 + models/usemtl-issue-68.mtl | 9 + models/usemtl-issue-68.obj | 817 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 830 insertions(+) create mode 100644 models/usemtl-issue-68.mtl create mode 100644 models/usemtl-issue-68.obj diff --git a/.travis.yml b/.travis.yml index 1ae49ef..bde52f6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -68,8 +68,12 @@ deploy: user: "syoyo" key: secure: W4F1VZcDcVOMe8Ymvo0bHery/JSmVhadl1NgAnGus6o7zVw7ChElKA1ho/NtqUbtoW8o1qUKMJdLQeh786jolocZJEJlns9JZ5FCet6H2b3kITfUa4GR5T11V/ZYwL3SajW8vZ1xu5UrpP5HHgFMYtxb1MFrNLDI60sh0RnyV/qFFBnCJGZPagF/M1mzbJeDml5xK5lShH0r8QpH+7MeQ1J8ungEyJ7UCyr1ao8gY9eq1/05IpHR9vri/d48EXQWHbqtI8EwCc7064oCYQGyYcLsD4yPEokwrdelkCvDquSpJLmbJENfZCc4vZGXsykjnQ8+gltJomBAivQFB9vc06ETEJssMzitbrfEZUrqFwZj/HZM7CYGXfGQWltL828SppCjsuWrgQ/VYXM5UgRpmhlxbqnuyxnYvKZ9EDW4+EnMkOmIl7WSDovp8E/4CZ0ghs+YyFS4SrgeqFCXS8bvxrkDUUPSipHuGBOt02fRnccKzU+3zU6Q5fghyLczz4ZtnOdk+Niz/njyF0SZfPYTUgb3GzAJ8Su6kvWJCAGdedON3n1F/TtybCE2dIdATxaO2uFQbwYjSOCiq209oCJ7MrsQZibRsa5a9YXyjlLkPxwOeVwo8wJyJclqWswIkhdSO8xvTnkwESv4yLzLutEOlBVlQbJzpyuS6vx0yHOYkwc= + all_branches: true on: + repo: syoyo/tinyobjloader condition: -n "$DEPLOY_BUILD" + tags: true + skip_cleanup: true - provider: releases api_key: secure: AsXameK4GJn6h6wMmDrKTr7q/o9EI7hX7zWg1W6VaFBQKfkBvOmjJolWimjl6HMoRZ1NpMmK5GDm3zBlTUeABtgVBIyNWgE9vWS39ff6D5iQKcgScFsJkyILt0GikBqbN2pLGQ2t/M1Qh6n1sEIfzqekiCcF5Qvy5yYlYvHtaRGV02QeYAej/xx15/9SMuKTncHhjf63ClYPu8ODid7QUegJUvlQUeXoPsBDbaXMH2uDWoBWF7etX7G2Iob4NE8GX+ZP6dj+Ogi7p4HXThK650mzLL/pUl584EjjY/vePqx0cFhtpiRwvrW8SNPI1aJ1Phwa1enLRUgfS3bnkwQAMw/SCXSK2lnCvkUAXyTgpG03HWrZURj4vhEPXc7qHooO+dsfmi+JanYLaSDyrGpgQznLGjCMnVATimry0KxSufUY8Wt72Wh+nf7N0IgTUCjl32sWnQd/MRZPkxFuaf1h7r9RoH9KZY0yIOV09gABEFCGrOIZA2FcyhC2G26Bc4zyNrfMFpZ2DI76qdcWNdJGkRkpxtH9sGU8JgZu6Em2f1e6+SLgkBsPxbhRk5PwdhA9AXE2p9PmQqhO3jJKusGBZSoHAF7TlwagRY2J01yJxF7ge6zG9U8QuBqs1bB1zdnE34fHWOgs4st3inC+oBDOhvnEg1Nm/qeYVWMBzpwclSg= diff --git a/models/usemtl-issue-68.mtl b/models/usemtl-issue-68.mtl new file mode 100644 index 0000000..24a791e --- /dev/null +++ b/models/usemtl-issue-68.mtl @@ -0,0 +1,9 @@ +newmtl Material.001 +Ka 0 0 0 +Kd 0 0 0 +Ks 0 0 0 + +newmtl Material.003 +Ka 0 0 0 +Kd 1 1 1 +Ks 0 0 0 diff --git a/models/usemtl-issue-68.obj b/models/usemtl-issue-68.obj new file mode 100644 index 0000000..dddc900 --- /dev/null +++ b/models/usemtl-issue-68.obj @@ -0,0 +1,817 @@ +# https://github.com/syoyo/tinyobjloader/issues/68 +# Blender v2.73 (sub 0) OBJ File: 'enemy.blend' +# www.blender.org +mtllib usemtl-issue-68.mtl +o Cube +v 1.864151 -1.219172 -5.532511 +v 0.575869 -0.666304 5.896140 +v 0.940448 1.000000 -1.971128 +v 1.620345 1.000000 -5.815706 +v 1.864152 1.000000 -6.334323 +v 0.575869 -0.129842 5.896143 +v 5.440438 -1.462153 -5.818601 +v 4.896782 -1.462153 -2.744413 +v 1.000825 -0.677484 1.899605 +v 5.440438 -1.246362 -5.818600 +v 1.000825 0.852342 1.899608 +v 4.896782 -1.246362 -2.744412 +v 1.160660 -0.450871 -2.356325 +v 1.704316 -0.450871 -5.430513 +v 1.000825 -0.351920 -1.293797 +v 1.000825 1.000000 -1.293794 +v 1.160660 -0.877888 -2.356326 +v 1.704316 -0.877888 -5.430514 +v 1.000825 -1.219172 -1.452514 +v 1.000825 1.000000 -1.452511 +v 1.000825 -0.351920 1.759410 +v 1.000825 1.000000 1.759413 +v 9.097919 1.221145 -6.212147 +v 8.356775 1.221145 -2.021231 +v 1.864151 -0.109586 -6.334325 +v 0.575869 -0.398073 5.896141 +v 9.097919 0.943958 -6.212148 +v 8.356775 0.943958 -2.021233 +v 1.061916 0.113661 -1.797961 +v 1.000825 0.161258 1.899606 +v 1.000825 0.324040 -1.293795 +v 1.803060 0.113661 -5.988876 +v 1.000825 -0.109586 -1.452513 +v 1.061916 0.776753 -1.797960 +v 1.803061 0.776753 -5.988875 +v 1.000825 0.324040 1.759412 +v 0.000825 -1.219172 -5.532512 +v 0.000825 -0.666304 5.896139 +v 0.000826 1.000000 -6.334325 +v 0.000825 -0.129842 5.896140 +v 0.000825 0.852342 1.899606 +v 0.000825 -0.677484 1.899604 +v 0.000825 -0.351920 -1.293797 +v 0.000825 1.000000 -1.293796 +v 0.000825 1.000000 -1.452513 +v 0.000825 -1.219172 -1.452515 +v 0.000825 -0.351920 1.759409 +v 0.000825 1.000000 1.759411 +v 0.000826 -0.109586 -6.334326 +v 0.000825 -0.398073 5.896140 +v 0.152918 1.000000 -5.815708 +v 0.152917 1.000000 -1.971130 +v 0.940448 1.168419 -1.971128 +v 1.620345 1.168419 -5.815706 +v 0.152918 1.168419 -5.815708 +v 0.152917 1.168419 -1.971130 +v 0.921118 1.091883 -1.050430 +v 0.921118 1.091883 1.516050 +v 0.080533 1.091883 -1.050432 +v 0.080533 1.091883 1.516048 +v 0.613003 -0.553430 5.546911 +v 0.963691 -0.559956 2.248834 +v 0.613003 -0.396857 5.546912 +v 0.963691 -0.070362 2.248835 +v 1.499370 -0.994317 3.966028 +v 1.850058 -0.997914 0.667950 +v 1.499370 -0.908021 3.966029 +v 1.850058 -0.728071 0.667951 +v 1.601022 0.760960 -6.334324 +v 1.601021 0.129454 -6.334325 +v 0.263955 0.760960 -6.334325 +v 0.263955 0.129454 -6.334325 +v 1.334809 0.760960 -7.515329 +v 1.334809 0.129455 -7.515330 +v 0.530168 0.760960 -7.515330 +v 0.530168 0.129455 -7.515330 +v 1.192720 0.649445 -7.515329 +v 1.192720 0.240971 -7.515330 +v 0.672258 0.649445 -7.515330 +v 0.672258 0.240971 -7.515330 +v 1.192719 0.649444 -6.524630 +v 1.192719 0.240970 -6.524631 +v 0.672257 0.649444 -6.524631 +v 0.672257 0.240970 -6.524631 +v 3.851026 0.431116 -1.883326 +v 3.851026 0.946662 -1.883325 +v 4.592170 0.946662 -6.074241 +v 4.592169 0.431116 -6.074242 +v 4.995714 0.561404 -1.918362 +v 4.995714 1.016394 -1.918360 +v 5.736857 1.016394 -6.109276 +v 5.736857 0.561404 -6.109277 +v 3.975454 0.471731 -2.162156 +v 3.975454 0.919244 -2.162155 +v 4.618796 0.919244 -5.800034 +v 4.618795 0.471730 -5.800035 +v 4.969088 0.584825 -2.192568 +v 4.969088 0.979775 -2.192567 +v 5.612430 0.979775 -5.830446 +v 5.612429 0.584825 -5.830447 +v 0.864214 -0.673890 3.184381 +v 0.864213 0.489129 3.184384 +v 0.864213 -0.018552 3.184383 +v 0.000825 0.489129 3.184382 +v 0.000825 -0.673890 3.184381 +v 0.850955 -0.557858 3.309075 +v 0.850955 -0.175321 3.309076 +v 1.737321 -0.996758 1.728192 +v 1.737321 -0.785920 1.728193 +v -1.864151 -1.219172 -5.532511 +v -0.575869 -0.666304 5.896140 +v -0.940448 1.000000 -1.971128 +v -1.620345 1.000000 -5.815706 +v -1.864152 1.000000 -6.334323 +v -0.575869 -0.129842 5.896143 +v -5.440438 -1.462153 -5.818601 +v -4.896782 -1.462153 -2.744413 +v -1.000825 -0.677484 1.899605 +v -5.440438 -1.246362 -5.818600 +v -1.000825 0.852342 1.899608 +v -4.896782 -1.246362 -2.744412 +v -1.160660 -0.450871 -2.356325 +v -1.704316 -0.450871 -5.430513 +v -1.000825 -0.351920 -1.293797 +v -1.000825 1.000000 -1.293794 +v -1.160660 -0.877888 -2.356326 +v -1.704316 -0.877888 -5.430514 +v -1.000825 -1.219172 -1.452514 +v -1.000825 1.000000 -1.452511 +v -1.000825 -0.351920 1.759410 +v -1.000825 1.000000 1.759413 +v -9.097919 1.221145 -6.212147 +v -8.356775 1.221145 -2.021231 +v -1.864151 -0.109586 -6.334325 +v -0.575869 -0.398073 5.896141 +v -9.097919 0.943958 -6.212148 +v -8.356775 0.943958 -2.021233 +v -1.061916 0.113661 -1.797961 +v -1.000825 0.161258 1.899606 +v -1.000825 0.324040 -1.293795 +v -1.803060 0.113661 -5.988876 +v -1.000825 -0.109586 -1.452513 +v -1.061916 0.776753 -1.797960 +v -1.803061 0.776753 -5.988875 +v -1.000825 0.324040 1.759412 +v -0.000825 -1.219172 -5.532512 +v -0.000825 -0.666304 5.896139 +v -0.000826 1.000000 -6.334325 +v -0.000825 -0.129842 5.896140 +v -0.000825 0.852342 1.899606 +v -0.000825 -0.677484 1.899604 +v -0.000825 -0.351920 -1.293797 +v -0.000825 1.000000 -1.293796 +v -0.000825 1.000000 -1.452513 +v -0.000825 -1.219172 -1.452515 +v -0.000825 -0.351920 1.759409 +v -0.000825 1.000000 1.759411 +v -0.000826 -0.109586 -6.334326 +v -0.000825 -0.398073 5.896140 +v -0.152918 1.000000 -5.815708 +v -0.152917 1.000000 -1.971130 +v -0.940448 1.168419 -1.971128 +v -1.620345 1.168419 -5.815706 +v -0.152918 1.168419 -5.815708 +v -0.152917 1.168419 -1.971130 +v -0.921118 1.091883 -1.050430 +v -0.921118 1.091883 1.516050 +v -0.080533 1.091883 -1.050432 +v -0.080533 1.091883 1.516048 +v -0.613003 -0.553430 5.546911 +v -0.963691 -0.559956 2.248834 +v -0.613003 -0.396857 5.546912 +v -0.963691 -0.070362 2.248835 +v -1.499370 -0.994317 3.966028 +v -1.850058 -0.997914 0.667950 +v -1.499370 -0.908021 3.966029 +v -1.850058 -0.728071 0.667951 +v -1.601022 0.760960 -6.334324 +v -1.601021 0.129454 -6.334325 +v -0.263955 0.760960 -6.334325 +v -0.263955 0.129454 -6.334325 +v -1.334809 0.760960 -7.515329 +v -1.334809 0.129455 -7.515330 +v -0.530168 0.760960 -7.515330 +v -0.530168 0.129455 -7.515330 +v -1.192720 0.649445 -7.515329 +v -1.192720 0.240971 -7.515330 +v -0.672258 0.649445 -7.515330 +v -0.672258 0.240971 -7.515330 +v -1.192719 0.649444 -6.524630 +v -1.192719 0.240970 -6.524631 +v -0.672257 0.649444 -6.524631 +v -0.672257 0.240970 -6.524631 +v -3.851026 0.431116 -1.883326 +v -3.851026 0.946662 -1.883325 +v -4.592170 0.946662 -6.074241 +v -4.592169 0.431116 -6.074242 +v -4.995714 0.561404 -1.918362 +v -4.995714 1.016394 -1.918360 +v -5.736857 1.016394 -6.109276 +v -5.736857 0.561404 -6.109277 +v -3.975454 0.471731 -2.162156 +v -3.975454 0.919244 -2.162155 +v -4.618796 0.919244 -5.800034 +v -4.618795 0.471730 -5.800035 +v -4.969088 0.584825 -2.192568 +v -4.969088 0.979775 -2.192567 +v -5.612430 0.979775 -5.830446 +v -5.612429 0.584825 -5.830447 +v -0.864214 -0.673890 3.184381 +v -0.864213 0.489129 3.184384 +v -0.864213 -0.018552 3.184383 +v -0.000825 0.489129 3.184382 +v -0.000825 -0.673890 3.184381 +v -0.850955 -0.557858 3.309075 +v -0.850955 -0.175321 3.309076 +v -1.737321 -0.996758 1.728192 +v -1.737321 -0.785920 1.728193 +vt 0.135351 -0.558072 +vt 0.003035 -0.363507 +vt 0.092282 -0.976844 +vt -0.081322 0.947351 +vt 0.100058 1.958891 +vt 0.050091 1.852185 +vt -0.092752 1.055565 +vt -0.251711 1.059474 +vt 0.075587 0.041384 +vt -0.086008 0.279003 +vt -0.086212 0.249830 +vt -0.276044 1.968137 +vt -0.246101 1.859467 +vt 0.009828 1.911388 +vt -0.133014 1.114769 +vt 0.413322 1.261595 +vt 0.299103 0.624605 +vt 1.243955 0.407183 +vt 0.515404 1.111487 +vt 1.358173 1.044173 +vt -0.081553 0.914324 +vt 0.080042 0.676706 +vt 0.401185 0.474498 +vt 1.295541 0.331328 +vt 0.365315 1.568841 +vt 0.299111 1.575740 +vt 0.143401 0.707357 +vt 0.629403 1.011947 +vt 0.449192 0.167251 +vt 1.409760 0.968317 +vt 0.986264 1.738667 +vt 1.573373 1.877873 +vt 1.417663 1.009490 +vt 0.237182 -0.196235 +vt 0.721785 1.030226 +vt 0.830554 0.870285 +vt 0.877494 1.898608 +vt 1.351399 1.106930 +vt 0.183935 0.557301 +vt 1.507109 1.975312 +vt 0.241636 0.439088 +vt 0.114297 -0.045011 +vt 0.140593 1.808834 +vt -0.015118 0.940452 +vt 0.156405 -1.071134 +vt 0.164119 -0.998223 +vt 0.040336 -1.068281 +vt 0.104459 -1.162571 +vt -0.165787 1.882802 +vt -0.014821 1.660811 +vt -0.287852 0.283965 +vt -0.293374 0.366508 +vt -0.289630 0.900550 +vt 0.035337 -0.191272 +vt 0.247348 0.172213 +vt 0.253300 1.021193 +vt -0.283166 0.952313 +vt -0.283398 0.919286 +vt 0.039792 0.444050 +vt 0.314806 -0.339851 +vt 0.112962 -0.334889 +vt -0.288056 0.254793 +vt -0.023788 -0.973990 +vt -0.155922 -0.359599 +vt 0.220528 -1.165425 +vt 0.108710 -0.748730 +vt -0.286364 1.918670 +vt -0.291973 1.118678 +vt -0.119962 0.896379 +vt -0.123707 0.362337 +vt 0.162891 -0.598569 +vt 0.467532 -0.853353 +vt 0.201549 -1.053262 +vt 0.161663 -0.198915 +vt 0.267667 -0.752638 +vt 0.278705 -0.371021 +vt 0.526390 -0.542053 +vt 0.483821 -0.479457 +vt 0.488162 -0.883689 +vt 0.500110 -0.105561 +vt 0.564618 -0.200418 +vt -0.110331 2.127229 +vt 0.040636 1.905238 +vt -0.010786 1.578087 +vt 0.104092 1.876168 +vt 0.255058 1.654176 +vt -0.054992 2.087323 +vt 0.203048 1.901245 +vt 0.052081 2.123235 +vt 0.042658 1.943733 +vt -0.056437 1.881175 +vt 0.147710 1.941151 +vt 0.050060 2.084741 +vt 0.146264 1.735002 +vt 0.041212 1.737584 +vt 0.048615 1.878591 +vt 0.663065 1.872485 +vt 0.786311 1.691257 +vt 0.507355 1.004102 +vt 0.630601 0.822874 +vt 0.955144 1.689498 +vt 0.860727 1.828333 +vt 0.725565 1.074543 +vt 0.819981 0.935708 +vt 0.674594 1.805657 +vt 0.539432 1.051867 +vt 0.646413 0.894554 +vt 0.781576 1.648344 +vt 0.240127 -0.712141 +vn 0.994400 0.000000 0.105700 +vn 0.000000 1.000000 0.000000 +vn 1.000000 0.000000 0.000000 +vn 0.984700 0.000000 0.174100 +vn 0.211800 0.976600 0.037500 +vn -0.103300 0.000000 -0.994600 +vn 0.103300 -0.000000 0.994600 +vn 0.911400 0.378700 0.161200 +vn -0.157300 -0.987200 -0.027800 +vn 0.113700 -0.993300 0.020100 +vn 0.030600 -0.000000 0.999500 +vn -0.061100 0.998100 -0.010800 +vn -0.030600 0.000000 -0.999500 +vn -0.000000 -0.000000 1.000000 +vn 0.000000 0.000000 -1.000000 +vn -0.755400 0.655300 0.000000 +vn 0.000000 -1.000000 0.000000 +vn -0.000000 -0.180000 0.983700 +vn 0.000000 -0.395500 -0.918500 +vn -0.000000 0.688500 0.725200 +vn 0.000000 -0.585700 -0.810500 +vn -0.000000 0.974900 0.222500 +vn -0.000000 -1.000000 0.002800 +vn -1.000000 0.000000 -0.000000 +vn -0.000000 0.935500 0.353200 +vn 0.755400 0.655300 0.000000 +vn 0.000000 0.935500 -0.353200 +vn 0.673800 0.724900 0.143400 +vn 0.872300 -0.000000 0.489100 +vn -0.872300 0.000000 -0.489100 +vn -0.518300 -0.853500 -0.054200 +vn -0.975500 0.000000 -0.219900 +vn 0.975500 0.000000 -0.219900 +vn -0.913200 0.000000 -0.407500 +vn -0.436900 0.896200 -0.077300 +vn -0.995300 -0.000000 0.096600 +vn -0.297300 -0.953400 -0.052600 +vn 0.473900 -0.876600 0.083800 +vn 0.913200 0.000000 0.407500 +vn 0.342200 0.937700 0.060500 +vn 0.995300 -0.000000 -0.096600 +vn -0.519200 -0.853000 -0.054300 +vn 0.722400 0.676400 0.143800 +vn -0.994400 0.000000 0.105700 +vn -0.984700 0.000000 0.174100 +vn -0.211800 0.976600 0.037500 +vn 0.103300 0.000000 -0.994600 +vn -0.103300 -0.000000 0.994600 +vn -0.911400 0.378700 0.161200 +vn 0.157300 -0.987200 -0.027800 +vn -0.113700 -0.993300 0.020100 +vn -0.030600 -0.000000 0.999500 +vn 0.061100 0.998100 -0.010800 +vn 0.030600 0.000000 -0.999500 +vn -0.691900 0.713200 0.112500 +vn -0.872300 -0.000000 0.489100 +vn 0.872300 0.000000 -0.489100 +vn 0.518300 -0.853500 -0.054200 +vn 0.913200 0.000000 -0.407500 +vn 0.436900 0.896200 -0.077300 +vn 0.995300 0.000000 0.096600 +vn 0.297300 -0.953300 -0.052600 +vn -0.473900 -0.876600 0.083800 +vn -0.913200 -0.000000 0.407500 +vn -0.342200 0.937700 0.060500 +vn -0.995300 -0.000000 -0.096600 +vn 0.519200 -0.853000 -0.054300 +vn -0.714800 0.690100 0.113700 +vn 0.974400 0.089700 0.206200 +vn 0.870400 0.288400 0.399100 +vn 0.691900 0.713200 0.112500 +vn -0.518000 -0.853700 -0.053400 +vn -0.519700 -0.852700 -0.053600 +vn 0.714800 0.690100 0.113700 +vn -0.974400 0.089700 0.206200 +vn -0.870400 0.288400 0.399100 +vn -0.673800 0.724900 0.143400 +vn 0.518000 -0.853700 -0.053400 +vn 0.297300 -0.953400 -0.052600 +vn 0.519700 -0.852700 -0.053600 +vn -0.722400 0.676400 0.143800 +vn -0.000000 0.962300 0.272000 +usemtl Material.001 +s off +f 103/1/1 102/2/1 6/3/1 +f 20/4/2 5/5/2 4/6/2 +f 20/4/2 3/7/2 52/8/2 +f 36/9/3 22/10/3 11/11/3 +f 39/12/2 51/13/2 4/6/2 +f 4/6/4 54/14/4 53/15/4 +f 14/16/5 13/17/5 12/18/5 +f 18/19/6 14/16/6 10/20/6 +f 20/4/3 16/21/3 31/22/3 +f 17/23/7 8/24/7 12/18/7 +f 25/25/4 32/26/4 29/27/4 +f 10/20/4 12/18/4 8/24/4 +f 1/28/8 18/19/8 17/23/8 +f 19/29/4 17/23/4 13/17/4 +f 25/25/4 14/16/4 18/19/4 +f 18/19/9 7/30/9 8/24/9 +f 92/31/10 27/32/10 28/33/10 +f 16/21/3 22/10/3 36/9/3 +f 31/22/3 36/9/3 21/34/3 +f 90/35/11 89/36/11 28/33/11 +f 91/37/12 90/35/12 24/38/12 +f 33/39/4 13/17/4 14/16/4 +f 23/40/4 24/38/4 28/33/4 +f 33/39/3 31/22/3 15/41/3 +f 21/34/3 36/9/3 30/42/3 +f 5/5/4 35/43/4 32/26/4 +f 5/5/4 20/4/4 34/44/4 +f 33/39/4 29/27/4 34/44/4 +f 91/37/13 23/40/13 27/32/13 +f 103/1/1 26/45/1 63/46/1 +f 26/45/14 50/47/14 38/48/14 +f 39/12/15 71/49/15 72/50/15 +f 48/51/16 60/52/16 59/53/16 +f 15/41/17 21/34/17 47/54/17 +f 19/29/17 46/55/17 37/56/17 +f 39/12/2 45/57/2 52/8/2 +f 20/4/2 45/57/2 44/58/2 +f 19/29/18 15/41/18 43/59/18 +f 9/60/19 42/61/19 47/54/19 +f 22/10/20 48/51/20 41/62/20 +f 25/25/21 1/28/21 37/56/21 +f 6/3/14 40/63/14 50/47/14 +f 104/64/22 40/63/22 6/3/22 +f 2/65/23 38/48/23 105/66/23 +f 55/67/2 56/68/2 53/15/2 +f 3/7/14 53/15/14 56/68/14 +f 51/13/15 55/67/15 54/14/15 +f 52/8/24 56/68/24 55/67/24 +f 57/69/2 59/53/2 60/52/2 +f 48/51/25 22/10/25 58/70/25 +f 16/21/26 57/69/26 58/70/26 +f 16/21/27 44/58/27 59/53/27 +f 107/71/28 63/46/28 67/72/28 +f 26/45/1 2/65/1 61/73/1 +f 9/60/1 30/42/1 64/74/1 +f 101/75/1 9/60/1 62/76/1 +f 108/77/1 109/78/1 67/72/1 +f 61/73/29 65/79/29 67/72/29 +f 62/76/30 64/74/30 68/80/30 +f 62/76/31 66/81/31 108/77/31 +f 71/49/32 75/82/32 76/83/32 +f 25/25/15 49/84/15 72/50/15 +f 5/5/15 69/85/15 71/49/15 +f 25/25/15 70/86/15 69/85/15 +f 76/83/15 75/82/15 79/87/15 +f 72/50/17 76/83/17 74/88/17 +f 71/49/2 69/85/2 73/89/2 +f 70/86/33 74/88/33 73/89/33 +f 80/90/3 79/87/3 83/91/3 +f 76/83/15 80/90/15 78/92/15 +f 75/82/15 73/89/15 77/93/15 +f 74/88/15 78/92/15 77/93/15 +f 82/94/15 84/95/15 83/91/15 +f 80/90/2 84/95/2 82/94/2 +f 77/93/17 81/96/17 83/91/17 +f 77/93/24 78/92/24 82/94/24 +f 35/43/13 87/97/13 88/98/13 +f 35/43/12 34/44/12 86/99/12 +f 34/44/11 29/27/11 85/100/11 +f 32/26/10 88/98/10 85/100/10 +f 92/31/34 100/101/34 99/102/34 +f 90/35/35 91/37/35 99/102/35 +f 89/36/36 90/35/36 98/103/36 +f 89/36/37 97/104/37 100/101/37 +f 95/105/13 99/102/13 100/101/13 +f 95/105/12 94/106/12 98/103/12 +f 94/106/11 93/107/11 97/104/11 +f 96/108/10 100/101/10 97/104/10 +f 88/98/38 96/108/38 93/107/38 +f 86/99/39 85/100/39 93/107/39 +f 87/97/40 86/99/40 94/106/40 +f 87/97/41 95/105/41 96/108/41 +f 106/109/42 108/77/42 65/79/42 +f 66/81/1 68/80/1 109/78/1 +f 101/75/1 106/109/1 61/73/1 +f 64/74/43 107/71/43 109/78/43 +f 101/75/23 105/66/23 42/61/23 +f 103/1/1 107/71/1 64/74/1 +f 30/42/1 11/11/1 102/2/1 +f 212/1/44 135/45/44 115/3/44 +f 129/4/2 112/7/2 113/6/2 +f 161/8/2 112/7/2 129/4/2 +f 145/9/24 139/42/24 120/11/24 +f 113/6/2 160/13/2 148/12/2 +f 162/15/45 163/14/45 113/6/45 +f 123/16/46 119/20/46 121/18/46 +f 127/19/47 116/30/47 119/20/47 +f 140/22/24 125/21/24 129/4/24 +f 121/18/48 117/24/48 126/23/48 +f 138/27/45 141/26/45 134/25/45 +f 117/24/45 121/18/45 119/20/45 +f 126/23/49 127/19/49 110/28/49 +f 122/17/45 126/23/45 128/29/45 +f 127/19/45 123/16/45 134/25/45 +f 117/24/50 116/30/50 127/19/50 +f 137/33/51 136/32/51 201/31/51 +f 145/9/24 131/10/24 125/21/24 +f 130/34/24 145/9/24 140/22/24 +f 199/35/52 133/38/52 137/33/52 +f 200/37/53 132/40/53 133/38/53 +f 123/16/45 122/17/45 142/39/45 +f 137/33/45 133/38/45 132/40/45 +f 124/41/24 140/22/24 142/39/24 +f 130/34/24 118/60/24 139/42/24 +f 141/26/45 144/43/45 114/5/45 +f 114/5/45 144/43/45 143/44/45 +f 143/44/45 138/27/45 142/39/45 +f 136/32/54 132/40/54 200/37/54 +f 212/1/44 216/71/44 172/46/44 +f 147/48/14 159/47/14 135/45/14 +f 181/50/15 180/49/15 148/12/15 +f 168/53/26 169/52/26 157/51/26 +f 124/41/17 152/59/17 156/54/17 +f 146/56/17 155/55/17 128/29/17 +f 148/12/2 160/13/2 161/8/2 +f 129/4/2 125/21/2 153/58/2 +f 155/55/18 152/59/18 124/41/18 +f 130/34/19 156/54/19 151/61/19 +f 131/10/20 120/11/20 150/62/20 +f 134/25/21 158/84/21 146/56/21 +f 159/47/14 149/63/14 115/3/14 +f 115/3/22 149/63/22 213/64/22 +f 214/66/23 147/48/23 111/65/23 +f 162/15/2 165/68/2 164/67/2 +f 165/68/14 162/15/14 112/7/14 +f 163/14/15 164/67/15 160/13/15 +f 164/67/3 165/68/3 161/8/3 +f 166/69/2 167/70/2 169/52/2 +f 157/51/25 169/52/25 167/70/25 +f 167/70/16 166/69/16 125/21/16 +f 125/21/27 166/69/27 168/53/27 +f 216/71/55 218/78/55 176/72/55 +f 135/45/44 172/46/44 170/73/44 +f 118/60/44 171/76/44 173/74/44 +f 210/75/44 215/109/44 171/76/44 +f 217/77/44 174/79/44 176/72/44 +f 176/72/56 174/79/56 170/73/56 +f 171/76/57 175/81/57 177/80/57 +f 217/77/58 175/81/58 171/76/58 +f 185/83/33 184/82/33 180/49/33 +f 134/25/15 179/86/15 181/50/15 +f 180/49/15 178/85/15 114/5/15 +f 178/85/15 179/86/15 134/25/15 +f 189/90/15 188/87/15 184/82/15 +f 183/88/17 185/83/17 181/50/17 +f 180/49/2 184/82/2 182/89/2 +f 182/89/32 183/88/32 179/86/32 +f 189/90/24 193/95/24 192/91/24 +f 187/92/15 189/90/15 185/83/15 +f 184/82/15 188/87/15 186/93/15 +f 186/93/15 187/92/15 183/88/15 +f 192/91/15 193/95/15 191/94/15 +f 191/94/2 193/95/2 189/90/2 +f 192/91/17 190/96/17 186/93/17 +f 186/93/3 190/96/3 191/94/3 +f 197/98/54 196/97/54 144/43/54 +f 144/43/53 196/97/53 195/99/53 +f 143/44/52 195/99/52 194/100/52 +f 194/100/51 197/98/51 141/26/51 +f 208/102/59 209/101/59 201/31/59 +f 199/35/60 207/103/60 208/102/60 +f 198/36/61 206/104/61 207/103/61 +f 209/101/62 206/104/62 198/36/62 +f 209/101/54 208/102/54 204/105/54 +f 204/105/53 208/102/53 207/103/53 +f 203/106/52 207/103/52 206/104/52 +f 206/104/51 209/101/51 205/108/51 +f 202/107/63 205/108/63 197/98/63 +f 195/99/64 203/106/64 202/107/64 +f 196/97/65 204/105/65 203/106/65 +f 205/108/66 204/105/66 196/97/66 +f 174/79/67 217/77/67 215/109/67 +f 175/81/44 217/77/44 218/78/44 +f 170/73/44 215/109/44 210/75/44 +f 173/74/68 177/80/68 218/78/68 +f 151/61/23 214/66/23 210/75/23 +f 173/74/44 216/71/44 212/1/44 +f 139/42/44 212/1/44 211/2/44 +f 26/45/1 103/1/1 6/3/1 +f 3/7/2 20/4/2 4/6/2 +f 45/57/2 20/4/2 52/8/2 +f 30/42/3 36/9/3 11/11/3 +f 5/5/2 39/12/2 4/6/2 +f 3/7/4 4/6/4 53/15/4 +f 10/20/5 14/16/5 12/18/5 +f 7/30/6 18/19/6 10/20/6 +f 33/39/3 20/4/3 31/22/3 +f 13/17/7 17/23/7 12/18/7 +f 33/39/4 25/25/4 29/27/4 +f 7/30/4 10/20/4 8/24/4 +f 19/29/69 1/28/69 17/23/69 +f 33/39/4 19/29/4 13/17/4 +f 1/28/70 25/25/70 18/19/70 +f 17/23/9 18/19/9 8/24/9 +f 89/36/10 92/31/10 28/33/10 +f 31/22/3 16/21/3 36/9/3 +f 15/41/3 31/22/3 21/34/3 +f 24/38/11 90/35/11 28/33/11 +f 23/40/12 91/37/12 24/38/12 +f 25/25/4 33/39/4 14/16/4 +f 27/32/4 23/40/4 28/33/4 +f 19/29/3 33/39/3 15/41/3 +f 9/60/3 21/34/3 30/42/3 +f 25/25/4 5/5/4 32/26/4 +f 35/43/4 5/5/4 34/44/4 +f 20/4/4 33/39/4 34/44/4 +f 92/31/13 91/37/13 27/32/13 +f 107/71/1 103/1/1 63/46/1 +f 2/65/14 26/45/14 38/48/14 +f 49/84/15 39/12/15 72/50/15 +f 44/58/16 48/51/16 59/53/16 +f 43/59/17 15/41/17 47/54/17 +f 1/28/17 19/29/17 37/56/17 +f 51/13/2 39/12/2 52/8/2 +f 16/21/2 20/4/2 44/58/2 +f 46/55/18 19/29/18 43/59/18 +f 21/34/19 9/60/19 47/54/19 +f 11/11/20 22/10/20 41/62/20 +f 49/84/21 25/25/21 37/56/21 +f 26/45/14 6/3/14 50/47/14 +f 102/2/22 104/64/22 6/3/22 +f 101/75/23 2/65/23 105/66/23 +f 54/14/2 55/67/2 53/15/2 +f 52/8/14 3/7/14 56/68/14 +f 4/6/15 51/13/15 54/14/15 +f 51/13/24 52/8/24 55/67/24 +f 58/70/2 57/69/2 60/52/2 +f 60/52/25 48/51/25 58/70/25 +f 22/10/26 16/21/26 58/70/26 +f 57/69/27 16/21/27 59/53/27 +f 109/78/71 107/71/71 67/72/71 +f 63/46/1 26/45/1 61/73/1 +f 62/76/1 9/60/1 64/74/1 +f 106/109/1 101/75/1 62/76/1 +f 65/79/1 108/77/1 67/72/1 +f 63/46/29 61/73/29 67/72/29 +f 66/81/30 62/76/30 68/80/30 +f 106/109/72 62/76/72 108/77/72 +f 72/50/32 71/49/32 76/83/32 +f 70/86/15 25/25/15 72/50/15 +f 39/12/15 5/5/15 71/49/15 +f 5/5/15 25/25/15 69/85/15 +f 80/90/15 76/83/15 79/87/15 +f 70/86/17 72/50/17 74/88/17 +f 75/82/2 71/49/2 73/89/2 +f 69/85/33 70/86/33 73/89/33 +f 84/95/3 80/90/3 83/91/3 +f 74/88/15 76/83/15 78/92/15 +f 79/87/15 75/82/15 77/93/15 +f 73/89/15 74/88/15 77/93/15 +f 81/96/15 82/94/15 83/91/15 +f 78/92/2 80/90/2 82/94/2 +f 79/87/17 77/93/17 83/91/17 +f 81/96/24 77/93/24 82/94/24 +f 32/26/13 35/43/13 88/98/13 +f 87/97/12 35/43/12 86/99/12 +f 86/99/11 34/44/11 85/100/11 +f 29/27/10 32/26/10 85/100/10 +f 91/37/34 92/31/34 99/102/34 +f 98/103/35 90/35/35 99/102/35 +f 97/104/36 89/36/36 98/103/36 +f 92/31/37 89/36/37 100/101/37 +f 96/108/13 95/105/13 100/101/13 +f 99/102/12 95/105/12 98/103/12 +f 98/103/11 94/106/11 97/104/11 +f 93/107/10 96/108/10 97/104/10 +f 85/100/38 88/98/38 93/107/38 +f 94/106/39 86/99/39 93/107/39 +f 95/105/40 87/97/40 94/106/40 +f 88/98/41 87/97/41 96/108/41 +f 61/73/73 106/109/73 65/79/73 +f 108/77/1 66/81/1 109/78/1 +f 2/65/1 101/75/1 61/73/1 +f 68/80/74 64/74/74 109/78/74 +f 9/60/23 101/75/23 42/61/23 +f 30/42/1 103/1/1 64/74/1 +f 103/1/1 30/42/1 102/2/1 +f 211/2/44 212/1/44 115/3/44 +f 114/5/2 129/4/2 113/6/2 +f 154/57/2 161/8/2 129/4/2 +f 131/10/24 145/9/24 120/11/24 +f 114/5/2 113/6/2 148/12/2 +f 112/7/45 162/15/45 113/6/45 +f 122/17/46 123/16/46 121/18/46 +f 123/16/47 127/19/47 119/20/47 +f 142/39/24 140/22/24 129/4/24 +f 122/17/48 121/18/48 126/23/48 +f 142/39/45 138/27/45 134/25/45 +f 116/30/45 117/24/45 119/20/45 +f 128/29/75 126/23/75 110/28/75 +f 142/39/45 122/17/45 128/29/45 +f 110/28/76 127/19/76 134/25/76 +f 126/23/50 117/24/50 127/19/50 +f 198/36/51 137/33/51 201/31/51 +f 140/22/24 145/9/24 125/21/24 +f 124/41/24 130/34/24 140/22/24 +f 198/36/52 199/35/52 137/33/52 +f 199/35/53 200/37/53 133/38/53 +f 134/25/45 123/16/45 142/39/45 +f 136/32/45 137/33/45 132/40/45 +f 128/29/24 124/41/24 142/39/24 +f 145/9/24 130/34/24 139/42/24 +f 134/25/45 141/26/45 114/5/45 +f 129/4/45 114/5/45 143/44/45 +f 129/4/45 143/44/45 142/39/45 +f 201/31/54 136/32/54 200/37/54 +f 135/45/44 212/1/44 172/46/44 +f 111/65/14 147/48/14 135/45/14 +f 158/84/15 181/50/15 148/12/15 +f 153/58/26 168/53/26 157/51/26 +f 130/34/17 124/41/17 156/54/17 +f 110/28/17 146/56/17 128/29/17 +f 154/57/2 148/12/2 161/8/2 +f 154/57/2 129/4/2 153/58/2 +f 128/29/18 155/55/18 124/41/18 +f 118/60/19 130/34/19 151/61/19 +f 157/51/20 131/10/20 150/62/20 +f 110/28/21 134/25/21 146/56/21 +f 135/45/14 159/47/14 115/3/14 +f 211/2/22 115/3/22 213/64/22 +f 210/75/23 214/66/23 111/65/23 +f 163/14/2 162/15/2 164/67/2 +f 161/8/14 165/68/14 112/7/14 +f 113/6/15 163/14/15 160/13/15 +f 160/13/3 164/67/3 161/8/3 +f 168/53/2 166/69/2 169/52/2 +f 131/10/25 157/51/25 167/70/25 +f 131/10/16 167/70/16 125/21/16 +f 153/58/27 125/21/27 168/53/27 +f 172/46/77 216/71/77 176/72/77 +f 111/65/44 135/45/44 170/73/44 +f 139/42/44 118/60/44 173/74/44 +f 118/60/44 210/75/44 171/76/44 +f 218/78/44 217/77/44 176/72/44 +f 172/46/56 176/72/56 170/73/56 +f 173/74/57 171/76/57 177/80/57 +f 215/109/78 217/77/78 171/76/78 +f 181/50/33 185/83/33 180/49/33 +f 158/84/15 134/25/15 181/50/15 +f 148/12/15 180/49/15 114/5/15 +f 114/5/15 178/85/15 134/25/15 +f 185/83/15 189/90/15 184/82/15 +f 179/86/17 183/88/17 181/50/17 +f 178/85/2 180/49/2 182/89/2 +f 178/85/32 182/89/32 179/86/32 +f 188/87/24 189/90/24 192/91/24 +f 183/88/15 187/92/15 185/83/15 +f 182/89/15 184/82/15 186/93/15 +f 182/89/15 186/93/15 183/88/15 +f 190/96/15 192/91/15 191/94/15 +f 187/92/2 191/94/2 189/90/2 +f 188/87/17 192/91/17 186/93/17 +f 187/92/3 186/93/3 191/94/3 +f 141/26/54 197/98/54 144/43/54 +f 143/44/53 144/43/53 195/99/53 +f 138/27/52 143/44/52 194/100/52 +f 138/27/51 194/100/51 141/26/51 +f 200/37/59 208/102/59 201/31/59 +f 200/37/60 199/35/60 208/102/60 +f 199/35/61 198/36/61 207/103/61 +f 201/31/79 209/101/79 198/36/79 +f 205/108/54 209/101/54 204/105/54 +f 203/106/53 204/105/53 207/103/53 +f 202/107/52 203/106/52 206/104/52 +f 202/107/51 206/104/51 205/108/51 +f 194/100/63 202/107/63 197/98/63 +f 194/100/64 195/99/64 202/107/64 +f 195/99/65 196/97/65 203/106/65 +f 197/98/66 205/108/66 196/97/66 +f 170/73/80 174/79/80 215/109/80 +f 177/80/44 175/81/44 218/78/44 +f 111/65/44 170/73/44 210/75/44 +f 216/71/81 173/74/81 218/78/81 +f 118/60/23 151/61/23 210/75/23 +f 139/42/44 173/74/44 212/1/44 +f 120/11/44 139/42/44 211/2/44 +usemtl Material.003 +f 41/62/82 104/64/82 102/2/82 +f 211/2/82 213/64/82 150/62/82 +f 11/11/82 41/62/82 102/2/82 +f 120/11/82 211/2/82 150/62/82 -- cgit v1.2.3 From ad9911ef1b124c628581c0f75adb3178f9d03c3c Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Fri, 25 Mar 2016 18:43:52 +0900 Subject: Initialize vertex_index. Fixes #70. --- tiny_obj_loader.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tiny_obj_loader.h b/tiny_obj_loader.h index 4a43498..826ec24 100644 --- a/tiny_obj_loader.h +++ b/tiny_obj_loader.h @@ -175,8 +175,8 @@ MaterialReader::~MaterialReader() {} 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() : v_idx(-1), vt_idx(-1), vn_idx(-1) {} + explicit 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) {} }; -- cgit v1.2.3 From b40e8c942764e4923d6ef31767ea2949b8a1e22b Mon Sep 17 00:00:00 2001 From: Nicolas Guillemot Date: Sat, 2 Apr 2016 15:15:29 -0700 Subject: use sscanf_s in MSVC consistent with other uses of sscanf in the library --- tiny_obj_loader.h | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tiny_obj_loader.h b/tiny_obj_loader.h index 826ec24..b039c40 100644 --- a/tiny_obj_loader.h +++ b/tiny_obj_loader.h @@ -1089,7 +1089,11 @@ bool LoadObj(std::vector &shapes, // [output] char namebuf[4096]; token += 2; +#ifdef _MSC_VER + sscanf_s(token, "%s", namebuf, (unsigned)_countof(namebuf)); +#else sscanf(token, "%s", namebuf); +#endif tag.name = std::string(namebuf); token += tag.name.size() + 1; @@ -1113,7 +1117,11 @@ bool LoadObj(std::vector &shapes, // [output] for (size_t i = 0; i < static_cast(ts.num_strings); ++i) { char stringValueBuffer[4096]; +#ifdef _MSC_VER + sscanf_s(token, "%s", stringValueBuffer, (unsigned)_countof(stringValueBuffer)); +#else sscanf(token, "%s", stringValueBuffer); +#endif tag.stringValues[i] = stringValueBuffer; token += tag.stringValues[i].size() + 1; } -- cgit v1.2.3 From 1b24514ed9e07922df0f4f02a184e760852a5416 Mon Sep 17 00:00:00 2001 From: Jamie Wong Date: Tue, 12 Apr 2016 22:36:56 +0800 Subject: Fix typo in README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4a063b6..8bc365a 100644 --- a/README.md +++ b/README.md @@ -180,7 +180,7 @@ for (size_t i = 0; i < shapes.size(); i++) { for (size_t n = 0; n < shapes[i].mesh.num_vertices.size(); n++) { int ngon = shapes[i].mesh.num_vertices[n]; for (size_t f = 0; f < ngon; f++) { - unsigend int v = shapes[i].mesh.indices[indexOffset + f]; + unsigned int v = shapes[i].mesh.indices[indexOffset + f]; printf(" face[%ld] v[%ld] = (%f, %f, %f)\n", n, shapes[i].mesh.positions[3*v+0], shapes[i].mesh.positions[3*v+1], -- cgit v1.2.3 From d119dcb9765cbee205448125c96c86546bdd13f1 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Fri, 15 Apr 2016 12:31:47 +0900 Subject: Cosmetics. --- tiny_obj_loader.h | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/tiny_obj_loader.h b/tiny_obj_loader.h index b039c40..8bdd0a3 100644 --- a/tiny_obj_loader.h +++ b/tiny_obj_loader.h @@ -38,8 +38,8 @@ // #include "tiny_obj_loader.h" // -#ifndef TINY_OBJ_LOADER_H -#define TINY_OBJ_LOADER_H +#ifndef TINY_OBJ_LOADER_H_ +#define TINY_OBJ_LOADER_H_ #include #include @@ -159,9 +159,6 @@ void LoadMtl(std::map &material_map, // [output] #include #include -#include -#include -#include #include #include @@ -1147,4 +1144,4 @@ bool LoadObj(std::vector &shapes, // [output] #endif -#endif // TINY_OBJ_LOADER_H +#endif // TINY_OBJ_LOADER_H_ -- cgit v1.2.3 From ee7d6cc0fdae6a252fcd2a94a28647381c42239b Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Sat, 16 Apr 2016 19:49:12 +0900 Subject: 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 | 101 --------------- cornell_box_multimaterial.obj | 146 --------------------- cube.mtl | 24 ---- cube.obj | 31 ----- missing_material_file.obj | 145 --------------------- models/catmark_torus_creases0.obj | 101 +++++++++++++++ models/cornell_box_multimaterial.obj | 146 +++++++++++++++++++++ models/cube.mtl | 24 ++++ models/cube.obj | 31 +++++ models/missing_material_file.obj | 145 +++++++++++++++++++++ models/no_material.obj | 133 +++++++++++++++++++ models/test-nan.obj | 145 +++++++++++++++++++++ no_material.obj | 133 ------------------- test-nan.obj | 145 --------------------- test.cc | 74 ++++++++--- tiny_obj_loader.h | 242 +++++++++++++++++------------------ 17 files changed, 896 insertions(+), 871 deletions(-) delete mode 100644 catmark_torus_creases0.obj delete mode 100644 cornell_box_multimaterial.obj delete mode 100644 cube.mtl delete mode 100644 cube.obj delete mode 100644 missing_material_file.obj create mode 100644 models/catmark_torus_creases0.obj create mode 100644 models/cornell_box_multimaterial.obj create mode 100644 models/cube.mtl create mode 100644 models/cube.obj create mode 100644 models/missing_material_file.obj create mode 100644 models/no_material.obj create mode 100644 models/test-nan.obj delete mode 100644 no_material.obj delete mode 100644 test-nan.obj 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/catmark_torus_creases0.obj deleted file mode 100644 index bf18f15..0000000 --- a/catmark_torus_creases0.obj +++ /dev/null @@ -1,101 +0,0 @@ -# -# Copyright 2013 Pixar -# -# Licensed under the Apache License, Version 2.0 (the "Apache License") -# with the following modification; you may not use this file except in -# compliance with the Apache License and the following modification to it: -# Section 6. Trademarks. is deleted and replaced with: -# -# 6. Trademarks. This License does not grant permission to use the trade -# names, trademarks, service marks, or product names of the Licensor -# and its affiliates, except as required to comply with Section 4(c) of -# the License and to reproduce the content of the NOTICE file. -# -# You may obtain a copy of the Apache License at -# -# http:#www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the Apache License with the above modification is -# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the Apache License for the specific -# language governing permissions and limitations under the Apache License. -# -# This file uses centimeters as units for non-parametric coordinates. - -v 1.25052 0.517982 0.353553 -v 0.597239 0.247384 0.353553 -v 0.597239 0.247384 -0.353553 -v 1.25052 0.517982 -0.353553 -v 0.517982 1.25052 0.353553 -v 0.247384 0.597239 0.353553 -v 0.247384 0.597239 -0.353553 -v 0.517982 1.25052 -0.353553 -v -0.517982 1.25052 0.353553 -v -0.247384 0.597239 0.353553 -v -0.247384 0.597239 -0.353553 -v -0.517982 1.25052 -0.353553 -v -1.25052 0.517982 0.353553 -v -0.597239 0.247384 0.353553 -v -0.597239 0.247384 -0.353553 -v -1.25052 0.517982 -0.353553 -v -1.25052 -0.517982 0.353553 -v -0.597239 -0.247384 0.353553 -v -0.597239 -0.247384 -0.353553 -v -1.25052 -0.517982 -0.353553 -v -0.517982 -1.25052 0.353553 -v -0.247384 -0.597239 0.353553 -v -0.247384 -0.597239 -0.353553 -v -0.517982 -1.25052 -0.353553 -v 0.517982 -1.25052 0.353553 -v 0.247384 -0.597239 0.353553 -v 0.247384 -0.597239 -0.353553 -v 0.517982 -1.25052 -0.353553 -v 1.25052 -0.517982 0.353553 -v 0.597239 -0.247384 0.353553 -v 0.597239 -0.247384 -0.353553 -v 1.25052 -0.517982 -0.353553 -vt 0 0 -vt 1 0 -vt 1 1 -vt 0 1 -f 5/1/1 6/2/2 2/3/3 1/4/4 -f 6/1/5 7/2/6 3/3/7 2/4/8 -f 7/1/9 8/2/10 4/3/11 3/4/12 -f 8/1/13 5/2/14 1/3/15 4/4/16 -f 9/1/17 10/2/18 6/3/19 5/4/20 -f 10/1/21 11/2/22 7/3/23 6/4/24 -f 11/1/25 12/2/26 8/3/27 7/4/28 -f 12/1/29 9/2/30 5/3/31 8/4/32 -f 13/1/33 14/2/34 10/3/35 9/4/36 -f 14/1/37 15/2/38 11/3/39 10/4/40 -f 15/1/41 16/2/42 12/3/43 11/4/44 -f 16/1/45 13/2/46 9/3/47 12/4/48 -f 17/1/49 18/2/50 14/3/51 13/4/52 -f 18/1/53 19/2/54 15/3/55 14/4/56 -f 19/1/57 20/2/58 16/3/59 15/4/60 -f 20/1/61 17/2/62 13/3/63 16/4/64 -f 21/1/65 22/2/66 18/3/67 17/4/68 -f 22/1/69 23/2/70 19/3/71 18/4/72 -f 23/1/73 24/2/74 20/3/75 19/4/76 -f 24/1/77 21/2/78 17/3/79 20/4/80 -f 25/1/81 26/2/82 22/3/83 21/4/84 -f 26/1/85 27/2/86 23/3/87 22/4/88 -f 27/1/89 28/2/90 24/3/91 23/4/92 -f 28/1/93 25/2/94 21/3/95 24/4/96 -f 29/1/97 30/2/98 26/3/99 25/4/100 -f 30/1/101 31/2/102 27/3/103 26/4/104 -f 31/1/105 32/2/106 28/3/107 27/4/108 -f 32/1/109 29/2/110 25/3/111 28/4/112 -f 1/1/113 2/2/114 30/3/115 29/4/116 -f 2/1/117 3/2/118 31/3/119 30/4/120 -f 3/1/121 4/2/122 32/3/123 31/4/124 -f 4/1/125 1/2/126 29/3/127 32/4/128 -t crease 2/1/0 1 5 4.7 -t crease 2/1/0 5 9 4.7 -t crease 2/1/0 9 13 4.7 -t crease 2/1/0 13 17 4.7 -t crease 2/1/0 17 21 4.7 -t crease 2/1/0 21 25 4.7 -t crease 2/1/0 25 29 4.7 -t crease 2/1/0 29 1 4.7 diff --git a/cornell_box_multimaterial.obj b/cornell_box_multimaterial.obj deleted file mode 100644 index 68093be..0000000 --- a/cornell_box_multimaterial.obj +++ /dev/null @@ -1,146 +0,0 @@ -# cornell_box.obj and cornell_box.mtl are grabbed from Intel's embree project. -# original cornell box data - # comment - -# empty line including some space - - -mtllib cornell_box.mtl - -o floor -usemtl white -v 552.8 0.0 0.0 -v 0.0 0.0 0.0 -v 0.0 0.0 559.2 -v 549.6 0.0 559.2 - -v 130.0 0.0 65.0 -v 82.0 0.0 225.0 -v 240.0 0.0 272.0 -v 290.0 0.0 114.0 - -v 423.0 0.0 247.0 -v 265.0 0.0 296.0 -v 314.0 0.0 456.0 -v 472.0 0.0 406.0 - -f 1 2 3 4 -f 8 7 6 5 -f 12 11 10 9 - -o light -usemtl light -v 343.0 548.0 227.0 -v 343.0 548.0 332.0 -v 213.0 548.0 332.0 -v 213.0 548.0 227.0 -f -4 -3 -2 -1 - -o ceiling -usemtl white -v 556.0 548.8 0.0 -v 556.0 548.8 559.2 -v 0.0 548.8 559.2 -v 0.0 548.8 0.0 -f -4 -3 -2 -1 - -o back_wall -usemtl white -v 549.6 0.0 559.2 -v 0.0 0.0 559.2 -v 0.0 548.8 559.2 -v 556.0 548.8 559.2 -f -4 -3 -2 -1 - -o front_wall -usemtl blue -v 549.6 0.0 0 -v 0.0 0.0 0 -v 0.0 548.8 0 -v 556.0 548.8 0 -#f -1 -2 -3 -4 - -o green_wall -usemtl green -v 0.0 0.0 559.2 -v 0.0 0.0 0.0 -v 0.0 548.8 0.0 -v 0.0 548.8 559.2 -f -4 -3 -2 -1 - -o red_wall -usemtl red -v 552.8 0.0 0.0 -v 549.6 0.0 559.2 -v 556.0 548.8 559.2 -v 556.0 548.8 0.0 -f -4 -3 -2 -1 - -o short_block -usemtl white - -v 130.0 165.0 65.0 -v 82.0 165.0 225.0 -v 240.0 165.0 272.0 -v 290.0 165.0 114.0 -f -4 -3 -2 -1 - -v 290.0 0.0 114.0 -v 290.0 165.0 114.0 -v 240.0 165.0 272.0 -v 240.0 0.0 272.0 -f -4 -3 -2 -1 - -v 130.0 0.0 65.0 -v 130.0 165.0 65.0 -v 290.0 165.0 114.0 -v 290.0 0.0 114.0 -f -4 -3 -2 -1 - -v 82.0 0.0 225.0 -v 82.0 165.0 225.0 -v 130.0 165.0 65.0 -v 130.0 0.0 65.0 -f -4 -3 -2 -1 - -v 240.0 0.0 272.0 -v 240.0 165.0 272.0 -v 82.0 165.0 225.0 -v 82.0 0.0 225.0 -f -4 -3 -2 -1 - -o tall_block -usemtl white - -v 423.0 330.0 247.0 -v 265.0 330.0 296.0 -v 314.0 330.0 456.0 -v 472.0 330.0 406.0 -f -4 -3 -2 -1 - -usemtl white -v 423.0 0.0 247.0 -v 423.0 330.0 247.0 -v 472.0 330.0 406.0 -v 472.0 0.0 406.0 -f -4 -3 -2 -1 - -v 472.0 0.0 406.0 -v 472.0 330.0 406.0 -v 314.0 330.0 456.0 -v 314.0 0.0 456.0 -f -4 -3 -2 -1 -usemtl green - -v 314.0 0.0 456.0 -v 314.0 330.0 456.0 -v 265.0 330.0 296.0 -v 265.0 0.0 296.0 -f -4 -3 -2 -1 - -v 265.0 0.0 296.0 -v 265.0 330.0 296.0 -v 423.0 330.0 247.0 -v 423.0 0.0 247.0 -f -4 -3 -2 -1 - diff --git a/cube.mtl b/cube.mtl deleted file mode 100644 index d3a1c7a..0000000 --- a/cube.mtl +++ /dev/null @@ -1,24 +0,0 @@ -newmtl white -Ka 0 0 0 -Kd 1 1 1 -Ks 0 0 0 - -newmtl red -Ka 0 0 0 -Kd 1 0 0 -Ks 0 0 0 - -newmtl green -Ka 0 0 0 -Kd 0 1 0 -Ks 0 0 0 - -newmtl blue -Ka 0 0 0 -Kd 0 0 1 -Ks 0 0 0 - -newmtl light -Ka 20 20 20 -Kd 1 1 1 -Ks 0 0 0 diff --git a/cube.obj b/cube.obj deleted file mode 100644 index 9213e17..0000000 --- a/cube.obj +++ /dev/null @@ -1,31 +0,0 @@ -mtllib cube.mtl - -v 0.000000 2.000000 2.000000 -v 0.000000 0.000000 2.000000 -v 2.000000 0.000000 2.000000 -v 2.000000 2.000000 2.000000 -v 0.000000 2.000000 0.000000 -v 0.000000 0.000000 0.000000 -v 2.000000 0.000000 0.000000 -v 2.000000 2.000000 0.000000 -# 8 vertices - -g front cube -usemtl white -f 1 2 3 4 -g back cube -# expects white material -f 8 7 6 5 -g right cube -usemtl red -f 4 3 7 8 -g top cube -usemtl white -f 5 1 4 8 -g left cube -usemtl green -f 5 6 2 1 -g bottom cube -usemtl white -f 2 6 7 3 -# 6 elements diff --git a/missing_material_file.obj b/missing_material_file.obj deleted file mode 100644 index 9e1d98c..0000000 --- a/missing_material_file.obj +++ /dev/null @@ -1,145 +0,0 @@ -# cornell_box.obj and cornell_box.mtl are grabbed from Intel's embree project. -# original cornell box data - # comment - -# empty line including some space - - -#mtllib no_material.mtl - -o floor -usemtl white -v 552.8 0.0 0.0 -v 0.0 0.0 0.0 -v 0.0 0.0 559.2 -v 549.6 0.0 559.2 - -v 130.0 0.0 65.0 -v 82.0 0.0 225.0 -v 240.0 0.0 272.0 -v 290.0 0.0 114.0 - -v 423.0 0.0 247.0 -v 265.0 0.0 296.0 -v 314.0 0.0 456.0 -v 472.0 0.0 406.0 - -f 1 2 3 4 -f 8 7 6 5 -f 12 11 10 9 - -o light -usemtl light -v 343.0 548.0 227.0 -v 343.0 548.0 332.0 -v 213.0 548.0 332.0 -v 213.0 548.0 227.0 -f -4 -3 -2 -1 - -o ceiling -usemtl white -v 556.0 548.8 0.0 -v 556.0 548.8 559.2 -v 0.0 548.8 559.2 -v 0.0 548.8 0.0 -f -4 -3 -2 -1 - -o back_wall -usemtl white -v 549.6 0.0 559.2 -v 0.0 0.0 559.2 -v 0.0 548.8 559.2 -v 556.0 548.8 559.2 -f -4 -3 -2 -1 - -o front_wall -usemtl blue -v 549.6 0.0 0 -v 0.0 0.0 0 -v 0.0 548.8 0 -v 556.0 548.8 0 -#f -1 -2 -3 -4 - -o green_wall -usemtl green -v 0.0 0.0 559.2 -v 0.0 0.0 0.0 -v 0.0 548.8 0.0 -v 0.0 548.8 559.2 -f -4 -3 -2 -1 - -o red_wall -usemtl red -v 552.8 0.0 0.0 -v 549.6 0.0 559.2 -v 556.0 548.8 559.2 -v 556.0 548.8 0.0 -f -4 -3 -2 -1 - -o short_block -usemtl white - -v 130.0 165.0 65.0 -v 82.0 165.0 225.0 -v 240.0 165.0 272.0 -v 290.0 165.0 114.0 -f -4 -3 -2 -1 - -v 290.0 0.0 114.0 -v 290.0 165.0 114.0 -v 240.0 165.0 272.0 -v 240.0 0.0 272.0 -f -4 -3 -2 -1 - -v 130.0 0.0 65.0 -v 130.0 165.0 65.0 -v 290.0 165.0 114.0 -v 290.0 0.0 114.0 -f -4 -3 -2 -1 - -v 82.0 0.0 225.0 -v 82.0 165.0 225.0 -v 130.0 165.0 65.0 -v 130.0 0.0 65.0 -f -4 -3 -2 -1 - -v 240.0 0.0 272.0 -v 240.0 165.0 272.0 -v 82.0 165.0 225.0 -v 82.0 0.0 225.0 -f -4 -3 -2 -1 - -o tall_block -usemtl white - -v 423.0 330.0 247.0 -v 265.0 330.0 296.0 -v 314.0 330.0 456.0 -v 472.0 330.0 406.0 -f -4 -3 -2 -1 - -usemtl white -v 423.0 0.0 247.0 -v 423.0 330.0 247.0 -v 472.0 330.0 406.0 -v 472.0 0.0 406.0 -f -4 -3 -2 -1 - -v 472.0 0.0 406.0 -v 472.0 330.0 406.0 -v 314.0 330.0 456.0 -v 314.0 0.0 456.0 -f -4 -3 -2 -1 - -v 314.0 0.0 456.0 -v 314.0 330.0 456.0 -v 265.0 330.0 296.0 -v 265.0 0.0 296.0 -f -4 -3 -2 -1 - -v 265.0 0.0 296.0 -v 265.0 330.0 296.0 -v 423.0 330.0 247.0 -v 423.0 0.0 247.0 -f -4 -3 -2 -1 - diff --git a/models/catmark_torus_creases0.obj b/models/catmark_torus_creases0.obj new file mode 100644 index 0000000..bf18f15 --- /dev/null +++ b/models/catmark_torus_creases0.obj @@ -0,0 +1,101 @@ +# +# Copyright 2013 Pixar +# +# Licensed under the Apache License, Version 2.0 (the "Apache License") +# with the following modification; you may not use this file except in +# compliance with the Apache License and the following modification to it: +# Section 6. Trademarks. is deleted and replaced with: +# +# 6. Trademarks. This License does not grant permission to use the trade +# names, trademarks, service marks, or product names of the Licensor +# and its affiliates, except as required to comply with Section 4(c) of +# the License and to reproduce the content of the NOTICE file. +# +# You may obtain a copy of the Apache License at +# +# http:#www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the Apache License with the above modification is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the Apache License for the specific +# language governing permissions and limitations under the Apache License. +# +# This file uses centimeters as units for non-parametric coordinates. + +v 1.25052 0.517982 0.353553 +v 0.597239 0.247384 0.353553 +v 0.597239 0.247384 -0.353553 +v 1.25052 0.517982 -0.353553 +v 0.517982 1.25052 0.353553 +v 0.247384 0.597239 0.353553 +v 0.247384 0.597239 -0.353553 +v 0.517982 1.25052 -0.353553 +v -0.517982 1.25052 0.353553 +v -0.247384 0.597239 0.353553 +v -0.247384 0.597239 -0.353553 +v -0.517982 1.25052 -0.353553 +v -1.25052 0.517982 0.353553 +v -0.597239 0.247384 0.353553 +v -0.597239 0.247384 -0.353553 +v -1.25052 0.517982 -0.353553 +v -1.25052 -0.517982 0.353553 +v -0.597239 -0.247384 0.353553 +v -0.597239 -0.247384 -0.353553 +v -1.25052 -0.517982 -0.353553 +v -0.517982 -1.25052 0.353553 +v -0.247384 -0.597239 0.353553 +v -0.247384 -0.597239 -0.353553 +v -0.517982 -1.25052 -0.353553 +v 0.517982 -1.25052 0.353553 +v 0.247384 -0.597239 0.353553 +v 0.247384 -0.597239 -0.353553 +v 0.517982 -1.25052 -0.353553 +v 1.25052 -0.517982 0.353553 +v 0.597239 -0.247384 0.353553 +v 0.597239 -0.247384 -0.353553 +v 1.25052 -0.517982 -0.353553 +vt 0 0 +vt 1 0 +vt 1 1 +vt 0 1 +f 5/1/1 6/2/2 2/3/3 1/4/4 +f 6/1/5 7/2/6 3/3/7 2/4/8 +f 7/1/9 8/2/10 4/3/11 3/4/12 +f 8/1/13 5/2/14 1/3/15 4/4/16 +f 9/1/17 10/2/18 6/3/19 5/4/20 +f 10/1/21 11/2/22 7/3/23 6/4/24 +f 11/1/25 12/2/26 8/3/27 7/4/28 +f 12/1/29 9/2/30 5/3/31 8/4/32 +f 13/1/33 14/2/34 10/3/35 9/4/36 +f 14/1/37 15/2/38 11/3/39 10/4/40 +f 15/1/41 16/2/42 12/3/43 11/4/44 +f 16/1/45 13/2/46 9/3/47 12/4/48 +f 17/1/49 18/2/50 14/3/51 13/4/52 +f 18/1/53 19/2/54 15/3/55 14/4/56 +f 19/1/57 20/2/58 16/3/59 15/4/60 +f 20/1/61 17/2/62 13/3/63 16/4/64 +f 21/1/65 22/2/66 18/3/67 17/4/68 +f 22/1/69 23/2/70 19/3/71 18/4/72 +f 23/1/73 24/2/74 20/3/75 19/4/76 +f 24/1/77 21/2/78 17/3/79 20/4/80 +f 25/1/81 26/2/82 22/3/83 21/4/84 +f 26/1/85 27/2/86 23/3/87 22/4/88 +f 27/1/89 28/2/90 24/3/91 23/4/92 +f 28/1/93 25/2/94 21/3/95 24/4/96 +f 29/1/97 30/2/98 26/3/99 25/4/100 +f 30/1/101 31/2/102 27/3/103 26/4/104 +f 31/1/105 32/2/106 28/3/107 27/4/108 +f 32/1/109 29/2/110 25/3/111 28/4/112 +f 1/1/113 2/2/114 30/3/115 29/4/116 +f 2/1/117 3/2/118 31/3/119 30/4/120 +f 3/1/121 4/2/122 32/3/123 31/4/124 +f 4/1/125 1/2/126 29/3/127 32/4/128 +t crease 2/1/0 1 5 4.7 +t crease 2/1/0 5 9 4.7 +t crease 2/1/0 9 13 4.7 +t crease 2/1/0 13 17 4.7 +t crease 2/1/0 17 21 4.7 +t crease 2/1/0 21 25 4.7 +t crease 2/1/0 25 29 4.7 +t crease 2/1/0 29 1 4.7 diff --git a/models/cornell_box_multimaterial.obj b/models/cornell_box_multimaterial.obj new file mode 100644 index 0000000..68093be --- /dev/null +++ b/models/cornell_box_multimaterial.obj @@ -0,0 +1,146 @@ +# cornell_box.obj and cornell_box.mtl are grabbed from Intel's embree project. +# original cornell box data + # comment + +# empty line including some space + + +mtllib cornell_box.mtl + +o floor +usemtl white +v 552.8 0.0 0.0 +v 0.0 0.0 0.0 +v 0.0 0.0 559.2 +v 549.6 0.0 559.2 + +v 130.0 0.0 65.0 +v 82.0 0.0 225.0 +v 240.0 0.0 272.0 +v 290.0 0.0 114.0 + +v 423.0 0.0 247.0 +v 265.0 0.0 296.0 +v 314.0 0.0 456.0 +v 472.0 0.0 406.0 + +f 1 2 3 4 +f 8 7 6 5 +f 12 11 10 9 + +o light +usemtl light +v 343.0 548.0 227.0 +v 343.0 548.0 332.0 +v 213.0 548.0 332.0 +v 213.0 548.0 227.0 +f -4 -3 -2 -1 + +o ceiling +usemtl white +v 556.0 548.8 0.0 +v 556.0 548.8 559.2 +v 0.0 548.8 559.2 +v 0.0 548.8 0.0 +f -4 -3 -2 -1 + +o back_wall +usemtl white +v 549.6 0.0 559.2 +v 0.0 0.0 559.2 +v 0.0 548.8 559.2 +v 556.0 548.8 559.2 +f -4 -3 -2 -1 + +o front_wall +usemtl blue +v 549.6 0.0 0 +v 0.0 0.0 0 +v 0.0 548.8 0 +v 556.0 548.8 0 +#f -1 -2 -3 -4 + +o green_wall +usemtl green +v 0.0 0.0 559.2 +v 0.0 0.0 0.0 +v 0.0 548.8 0.0 +v 0.0 548.8 559.2 +f -4 -3 -2 -1 + +o red_wall +usemtl red +v 552.8 0.0 0.0 +v 549.6 0.0 559.2 +v 556.0 548.8 559.2 +v 556.0 548.8 0.0 +f -4 -3 -2 -1 + +o short_block +usemtl white + +v 130.0 165.0 65.0 +v 82.0 165.0 225.0 +v 240.0 165.0 272.0 +v 290.0 165.0 114.0 +f -4 -3 -2 -1 + +v 290.0 0.0 114.0 +v 290.0 165.0 114.0 +v 240.0 165.0 272.0 +v 240.0 0.0 272.0 +f -4 -3 -2 -1 + +v 130.0 0.0 65.0 +v 130.0 165.0 65.0 +v 290.0 165.0 114.0 +v 290.0 0.0 114.0 +f -4 -3 -2 -1 + +v 82.0 0.0 225.0 +v 82.0 165.0 225.0 +v 130.0 165.0 65.0 +v 130.0 0.0 65.0 +f -4 -3 -2 -1 + +v 240.0 0.0 272.0 +v 240.0 165.0 272.0 +v 82.0 165.0 225.0 +v 82.0 0.0 225.0 +f -4 -3 -2 -1 + +o tall_block +usemtl white + +v 423.0 330.0 247.0 +v 265.0 330.0 296.0 +v 314.0 330.0 456.0 +v 472.0 330.0 406.0 +f -4 -3 -2 -1 + +usemtl white +v 423.0 0.0 247.0 +v 423.0 330.0 247.0 +v 472.0 330.0 406.0 +v 472.0 0.0 406.0 +f -4 -3 -2 -1 + +v 472.0 0.0 406.0 +v 472.0 330.0 406.0 +v 314.0 330.0 456.0 +v 314.0 0.0 456.0 +f -4 -3 -2 -1 +usemtl green + +v 314.0 0.0 456.0 +v 314.0 330.0 456.0 +v 265.0 330.0 296.0 +v 265.0 0.0 296.0 +f -4 -3 -2 -1 + +v 265.0 0.0 296.0 +v 265.0 330.0 296.0 +v 423.0 330.0 247.0 +v 423.0 0.0 247.0 +f -4 -3 -2 -1 + diff --git a/models/cube.mtl b/models/cube.mtl new file mode 100644 index 0000000..d3a1c7a --- /dev/null +++ b/models/cube.mtl @@ -0,0 +1,24 @@ +newmtl white +Ka 0 0 0 +Kd 1 1 1 +Ks 0 0 0 + +newmtl red +Ka 0 0 0 +Kd 1 0 0 +Ks 0 0 0 + +newmtl green +Ka 0 0 0 +Kd 0 1 0 +Ks 0 0 0 + +newmtl blue +Ka 0 0 0 +Kd 0 0 1 +Ks 0 0 0 + +newmtl light +Ka 20 20 20 +Kd 1 1 1 +Ks 0 0 0 diff --git a/models/cube.obj b/models/cube.obj new file mode 100644 index 0000000..9213e17 --- /dev/null +++ b/models/cube.obj @@ -0,0 +1,31 @@ +mtllib cube.mtl + +v 0.000000 2.000000 2.000000 +v 0.000000 0.000000 2.000000 +v 2.000000 0.000000 2.000000 +v 2.000000 2.000000 2.000000 +v 0.000000 2.000000 0.000000 +v 0.000000 0.000000 0.000000 +v 2.000000 0.000000 0.000000 +v 2.000000 2.000000 0.000000 +# 8 vertices + +g front cube +usemtl white +f 1 2 3 4 +g back cube +# expects white material +f 8 7 6 5 +g right cube +usemtl red +f 4 3 7 8 +g top cube +usemtl white +f 5 1 4 8 +g left cube +usemtl green +f 5 6 2 1 +g bottom cube +usemtl white +f 2 6 7 3 +# 6 elements diff --git a/models/missing_material_file.obj b/models/missing_material_file.obj new file mode 100644 index 0000000..9e1d98c --- /dev/null +++ b/models/missing_material_file.obj @@ -0,0 +1,145 @@ +# cornell_box.obj and cornell_box.mtl are grabbed from Intel's embree project. +# original cornell box data + # comment + +# empty line including some space + + +#mtllib no_material.mtl + +o floor +usemtl white +v 552.8 0.0 0.0 +v 0.0 0.0 0.0 +v 0.0 0.0 559.2 +v 549.6 0.0 559.2 + +v 130.0 0.0 65.0 +v 82.0 0.0 225.0 +v 240.0 0.0 272.0 +v 290.0 0.0 114.0 + +v 423.0 0.0 247.0 +v 265.0 0.0 296.0 +v 314.0 0.0 456.0 +v 472.0 0.0 406.0 + +f 1 2 3 4 +f 8 7 6 5 +f 12 11 10 9 + +o light +usemtl light +v 343.0 548.0 227.0 +v 343.0 548.0 332.0 +v 213.0 548.0 332.0 +v 213.0 548.0 227.0 +f -4 -3 -2 -1 + +o ceiling +usemtl white +v 556.0 548.8 0.0 +v 556.0 548.8 559.2 +v 0.0 548.8 559.2 +v 0.0 548.8 0.0 +f -4 -3 -2 -1 + +o back_wall +usemtl white +v 549.6 0.0 559.2 +v 0.0 0.0 559.2 +v 0.0 548.8 559.2 +v 556.0 548.8 559.2 +f -4 -3 -2 -1 + +o front_wall +usemtl blue +v 549.6 0.0 0 +v 0.0 0.0 0 +v 0.0 548.8 0 +v 556.0 548.8 0 +#f -1 -2 -3 -4 + +o green_wall +usemtl green +v 0.0 0.0 559.2 +v 0.0 0.0 0.0 +v 0.0 548.8 0.0 +v 0.0 548.8 559.2 +f -4 -3 -2 -1 + +o red_wall +usemtl red +v 552.8 0.0 0.0 +v 549.6 0.0 559.2 +v 556.0 548.8 559.2 +v 556.0 548.8 0.0 +f -4 -3 -2 -1 + +o short_block +usemtl white + +v 130.0 165.0 65.0 +v 82.0 165.0 225.0 +v 240.0 165.0 272.0 +v 290.0 165.0 114.0 +f -4 -3 -2 -1 + +v 290.0 0.0 114.0 +v 290.0 165.0 114.0 +v 240.0 165.0 272.0 +v 240.0 0.0 272.0 +f -4 -3 -2 -1 + +v 130.0 0.0 65.0 +v 130.0 165.0 65.0 +v 290.0 165.0 114.0 +v 290.0 0.0 114.0 +f -4 -3 -2 -1 + +v 82.0 0.0 225.0 +v 82.0 165.0 225.0 +v 130.0 165.0 65.0 +v 130.0 0.0 65.0 +f -4 -3 -2 -1 + +v 240.0 0.0 272.0 +v 240.0 165.0 272.0 +v 82.0 165.0 225.0 +v 82.0 0.0 225.0 +f -4 -3 -2 -1 + +o tall_block +usemtl white + +v 423.0 330.0 247.0 +v 265.0 330.0 296.0 +v 314.0 330.0 456.0 +v 472.0 330.0 406.0 +f -4 -3 -2 -1 + +usemtl white +v 423.0 0.0 247.0 +v 423.0 330.0 247.0 +v 472.0 330.0 406.0 +v 472.0 0.0 406.0 +f -4 -3 -2 -1 + +v 472.0 0.0 406.0 +v 472.0 330.0 406.0 +v 314.0 330.0 456.0 +v 314.0 0.0 456.0 +f -4 -3 -2 -1 + +v 314.0 0.0 456.0 +v 314.0 330.0 456.0 +v 265.0 330.0 296.0 +v 265.0 0.0 296.0 +f -4 -3 -2 -1 + +v 265.0 0.0 296.0 +v 265.0 330.0 296.0 +v 423.0 330.0 247.0 +v 423.0 0.0 247.0 +f -4 -3 -2 -1 + diff --git a/models/no_material.obj b/models/no_material.obj new file mode 100644 index 0000000..6f3688f --- /dev/null +++ b/models/no_material.obj @@ -0,0 +1,133 @@ +# cornell_box.obj and cornell_box.mtl are grabbed from Intel's embree project. +# original cornell box data + # comment + +# empty line including some space + + +o floor +v 552.8 0.0 0.0 +v 0.0 0.0 0.0 +v 0.0 0.0 559.2 +v 549.6 0.0 559.2 + +v 130.0 0.0 65.0 +v 82.0 0.0 225.0 +v 240.0 0.0 272.0 +v 290.0 0.0 114.0 + +v 423.0 0.0 247.0 +v 265.0 0.0 296.0 +v 314.0 0.0 456.0 +v 472.0 0.0 406.0 + +f 1 2 3 4 +f 8 7 6 5 +f 12 11 10 9 + +o light +v 343.0 548.0 227.0 +v 343.0 548.0 332.0 +v 213.0 548.0 332.0 +v 213.0 548.0 227.0 +f -4 -3 -2 -1 + +o ceiling +v 556.0 548.8 0.0 +v 556.0 548.8 559.2 +v 0.0 548.8 559.2 +v 0.0 548.8 0.0 +f -4 -3 -2 -1 + +o back_wall +v 549.6 0.0 559.2 +v 0.0 0.0 559.2 +v 0.0 548.8 559.2 +v 556.0 548.8 559.2 +f -4 -3 -2 -1 + +o front_wall +v 549.6 0.0 0 +v 0.0 0.0 0 +v 0.0 548.8 0 +v 556.0 548.8 0 +#f -1 -2 -3 -4 + +o green_wall +v 0.0 0.0 559.2 +v 0.0 0.0 0.0 +v 0.0 548.8 0.0 +v 0.0 548.8 559.2 +f -4 -3 -2 -1 + +o red_wall +v 552.8 0.0 0.0 +v 549.6 0.0 559.2 +v 556.0 548.8 559.2 +v 556.0 548.8 0.0 +f -4 -3 -2 -1 + +o short_block + +v 130.0 165.0 65.0 +v 82.0 165.0 225.0 +v 240.0 165.0 272.0 +v 290.0 165.0 114.0 +f -4 -3 -2 -1 + +v 290.0 0.0 114.0 +v 290.0 165.0 114.0 +v 240.0 165.0 272.0 +v 240.0 0.0 272.0 +f -4 -3 -2 -1 + +v 130.0 0.0 65.0 +v 130.0 165.0 65.0 +v 290.0 165.0 114.0 +v 290.0 0.0 114.0 +f -4 -3 -2 -1 + +v 82.0 0.0 225.0 +v 82.0 165.0 225.0 +v 130.0 165.0 65.0 +v 130.0 0.0 65.0 +f -4 -3 -2 -1 + +v 240.0 0.0 272.0 +v 240.0 165.0 272.0 +v 82.0 165.0 225.0 +v 82.0 0.0 225.0 +f -4 -3 -2 -1 + +o tall_block + +v 423.0 330.0 247.0 +v 265.0 330.0 296.0 +v 314.0 330.0 456.0 +v 472.0 330.0 406.0 +f -4 -3 -2 -1 + +v 423.0 0.0 247.0 +v 423.0 330.0 247.0 +v 472.0 330.0 406.0 +v 472.0 0.0 406.0 +f -4 -3 -2 -1 + +v 472.0 0.0 406.0 +v 472.0 330.0 406.0 +v 314.0 330.0 456.0 +v 314.0 0.0 456.0 +f -4 -3 -2 -1 + +v 314.0 0.0 456.0 +v 314.0 330.0 456.0 +v 265.0 330.0 296.0 +v 265.0 0.0 296.0 +f -4 -3 -2 -1 + +v 265.0 0.0 296.0 +v 265.0 330.0 296.0 +v 423.0 330.0 247.0 +v 423.0 0.0 247.0 +f -4 -3 -2 -1 + diff --git a/models/test-nan.obj b/models/test-nan.obj new file mode 100644 index 0000000..3c68925 --- /dev/null +++ b/models/test-nan.obj @@ -0,0 +1,145 @@ +# cornell_box.obj and cornell_box.mtl are grabbed from Intel's embree project. +# original cornell box data + # comment + +# empty line including some space + + +mtllib cornell_box.mtl + +o floor +usemtl white +v nan -nan nan +v inf -inf inf +v 1.#IND -1.#IND 1.#IND +v 1.#INF -1.#INF 1.#INF + +v 130.0 0.0 65.0 +v 82.0 0.0 225.0 +v 240.0 0.0 272.0 +v 290.0 0.0 114.0 + +v 423.0 0.0 247.0 +v 265.0 0.0 296.0 +v 314.0 0.0 456.0 +v 472.0 0.0 406.0 + +f 1 2 3 4 +f 8 7 6 5 +f 12 11 10 9 + +o light +usemtl light +v 343.0 548.0 227.0 +v 343.0 548.0 332.0 +v 213.0 548.0 332.0 +v 213.0 548.0 227.0 +f -4 -3 -2 -1 + +o ceiling +usemtl white +v 556.0 548.8 0.0 +v 556.0 548.8 559.2 +v 0.0 548.8 559.2 +v 0.0 548.8 0.0 +f -4 -3 -2 -1 + +o back_wall +usemtl white +v 549.6 0.0 559.2 +v 0.0 0.0 559.2 +v 0.0 548.8 559.2 +v 556.0 548.8 559.2 +f -4 -3 -2 -1 + +o front_wall +usemtl blue +v 549.6 0.0 0 +v 0.0 0.0 0 +v 0.0 548.8 0 +v 556.0 548.8 0 +#f -1 -2 -3 -4 + +o green_wall +usemtl green +v 0.0 0.0 559.2 +v 0.0 0.0 0.0 +v 0.0 548.8 0.0 +v 0.0 548.8 559.2 +f -4 -3 -2 -1 + +o red_wall +usemtl red +v 552.8 0.0 0.0 +v 549.6 0.0 559.2 +v 556.0 548.8 559.2 +v 556.0 548.8 0.0 +f -4 -3 -2 -1 + +o short_block +usemtl white + +v 130.0 165.0 65.0 +v 82.0 165.0 225.0 +v 240.0 165.0 272.0 +v 290.0 165.0 114.0 +f -4 -3 -2 -1 + +v 290.0 0.0 114.0 +v 290.0 165.0 114.0 +v 240.0 165.0 272.0 +v 240.0 0.0 272.0 +f -4 -3 -2 -1 + +v 130.0 0.0 65.0 +v 130.0 165.0 65.0 +v 290.0 165.0 114.0 +v 290.0 0.0 114.0 +f -4 -3 -2 -1 + +v 82.0 0.0 225.0 +v 82.0 165.0 225.0 +v 130.0 165.0 65.0 +v 130.0 0.0 65.0 +f -4 -3 -2 -1 + +v 240.0 0.0 272.0 +v 240.0 165.0 272.0 +v 82.0 165.0 225.0 +v 82.0 0.0 225.0 +f -4 -3 -2 -1 + +o tall_block +usemtl white + +v 423.0 330.0 247.0 +v 265.0 330.0 296.0 +v 314.0 330.0 456.0 +v 472.0 330.0 406.0 +f -4 -3 -2 -1 + +usemtl white +v 423.0 0.0 247.0 +v 423.0 330.0 247.0 +v 472.0 330.0 406.0 +v 472.0 0.0 406.0 +f -4 -3 -2 -1 + +v 472.0 0.0 406.0 +v 472.0 330.0 406.0 +v 314.0 330.0 456.0 +v 314.0 0.0 456.0 +f -4 -3 -2 -1 + +v 314.0 0.0 456.0 +v 314.0 330.0 456.0 +v 265.0 330.0 296.0 +v 265.0 0.0 296.0 +f -4 -3 -2 -1 + +v 265.0 0.0 296.0 +v 265.0 330.0 296.0 +v 423.0 330.0 247.0 +v 423.0 0.0 247.0 +f -4 -3 -2 -1 + diff --git a/no_material.obj b/no_material.obj deleted file mode 100644 index 6f3688f..0000000 --- a/no_material.obj +++ /dev/null @@ -1,133 +0,0 @@ -# cornell_box.obj and cornell_box.mtl are grabbed from Intel's embree project. -# original cornell box data - # comment - -# empty line including some space - - -o floor -v 552.8 0.0 0.0 -v 0.0 0.0 0.0 -v 0.0 0.0 559.2 -v 549.6 0.0 559.2 - -v 130.0 0.0 65.0 -v 82.0 0.0 225.0 -v 240.0 0.0 272.0 -v 290.0 0.0 114.0 - -v 423.0 0.0 247.0 -v 265.0 0.0 296.0 -v 314.0 0.0 456.0 -v 472.0 0.0 406.0 - -f 1 2 3 4 -f 8 7 6 5 -f 12 11 10 9 - -o light -v 343.0 548.0 227.0 -v 343.0 548.0 332.0 -v 213.0 548.0 332.0 -v 213.0 548.0 227.0 -f -4 -3 -2 -1 - -o ceiling -v 556.0 548.8 0.0 -v 556.0 548.8 559.2 -v 0.0 548.8 559.2 -v 0.0 548.8 0.0 -f -4 -3 -2 -1 - -o back_wall -v 549.6 0.0 559.2 -v 0.0 0.0 559.2 -v 0.0 548.8 559.2 -v 556.0 548.8 559.2 -f -4 -3 -2 -1 - -o front_wall -v 549.6 0.0 0 -v 0.0 0.0 0 -v 0.0 548.8 0 -v 556.0 548.8 0 -#f -1 -2 -3 -4 - -o green_wall -v 0.0 0.0 559.2 -v 0.0 0.0 0.0 -v 0.0 548.8 0.0 -v 0.0 548.8 559.2 -f -4 -3 -2 -1 - -o red_wall -v 552.8 0.0 0.0 -v 549.6 0.0 559.2 -v 556.0 548.8 559.2 -v 556.0 548.8 0.0 -f -4 -3 -2 -1 - -o short_block - -v 130.0 165.0 65.0 -v 82.0 165.0 225.0 -v 240.0 165.0 272.0 -v 290.0 165.0 114.0 -f -4 -3 -2 -1 - -v 290.0 0.0 114.0 -v 290.0 165.0 114.0 -v 240.0 165.0 272.0 -v 240.0 0.0 272.0 -f -4 -3 -2 -1 - -v 130.0 0.0 65.0 -v 130.0 165.0 65.0 -v 290.0 165.0 114.0 -v 290.0 0.0 114.0 -f -4 -3 -2 -1 - -v 82.0 0.0 225.0 -v 82.0 165.0 225.0 -v 130.0 165.0 65.0 -v 130.0 0.0 65.0 -f -4 -3 -2 -1 - -v 240.0 0.0 272.0 -v 240.0 165.0 272.0 -v 82.0 165.0 225.0 -v 82.0 0.0 225.0 -f -4 -3 -2 -1 - -o tall_block - -v 423.0 330.0 247.0 -v 265.0 330.0 296.0 -v 314.0 330.0 456.0 -v 472.0 330.0 406.0 -f -4 -3 -2 -1 - -v 423.0 0.0 247.0 -v 423.0 330.0 247.0 -v 472.0 330.0 406.0 -v 472.0 0.0 406.0 -f -4 -3 -2 -1 - -v 472.0 0.0 406.0 -v 472.0 330.0 406.0 -v 314.0 330.0 456.0 -v 314.0 0.0 456.0 -f -4 -3 -2 -1 - -v 314.0 0.0 456.0 -v 314.0 330.0 456.0 -v 265.0 330.0 296.0 -v 265.0 0.0 296.0 -f -4 -3 -2 -1 - -v 265.0 0.0 296.0 -v 265.0 330.0 296.0 -v 423.0 330.0 247.0 -v 423.0 0.0 247.0 -f -4 -3 -2 -1 - diff --git a/test-nan.obj b/test-nan.obj deleted file mode 100644 index 3c68925..0000000 --- a/test-nan.obj +++ /dev/null @@ -1,145 +0,0 @@ -# cornell_box.obj and cornell_box.mtl are grabbed from Intel's embree project. -# original cornell box data - # comment - -# empty line including some space - - -mtllib cornell_box.mtl - -o floor -usemtl white -v nan -nan nan -v inf -inf inf -v 1.#IND -1.#IND 1.#IND -v 1.#INF -1.#INF 1.#INF - -v 130.0 0.0 65.0 -v 82.0 0.0 225.0 -v 240.0 0.0 272.0 -v 290.0 0.0 114.0 - -v 423.0 0.0 247.0 -v 265.0 0.0 296.0 -v 314.0 0.0 456.0 -v 472.0 0.0 406.0 - -f 1 2 3 4 -f 8 7 6 5 -f 12 11 10 9 - -o light -usemtl light -v 343.0 548.0 227.0 -v 343.0 548.0 332.0 -v 213.0 548.0 332.0 -v 213.0 548.0 227.0 -f -4 -3 -2 -1 - -o ceiling -usemtl white -v 556.0 548.8 0.0 -v 556.0 548.8 559.2 -v 0.0 548.8 559.2 -v 0.0 548.8 0.0 -f -4 -3 -2 -1 - -o back_wall -usemtl white -v 549.6 0.0 559.2 -v 0.0 0.0 559.2 -v 0.0 548.8 559.2 -v 556.0 548.8 559.2 -f -4 -3 -2 -1 - -o front_wall -usemtl blue -v 549.6 0.0 0 -v 0.0 0.0 0 -v 0.0 548.8 0 -v 556.0 548.8 0 -#f -1 -2 -3 -4 - -o green_wall -usemtl green -v 0.0 0.0 559.2 -v 0.0 0.0 0.0 -v 0.0 548.8 0.0 -v 0.0 548.8 559.2 -f -4 -3 -2 -1 - -o red_wall -usemtl red -v 552.8 0.0 0.0 -v 549.6 0.0 559.2 -v 556.0 548.8 559.2 -v 556.0 548.8 0.0 -f -4 -3 -2 -1 - -o short_block -usemtl white - -v 130.0 165.0 65.0 -v 82.0 165.0 225.0 -v 240.0 165.0 272.0 -v 290.0 165.0 114.0 -f -4 -3 -2 -1 - -v 290.0 0.0 114.0 -v 290.0 165.0 114.0 -v 240.0 165.0 272.0 -v 240.0 0.0 272.0 -f -4 -3 -2 -1 - -v 130.0 0.0 65.0 -v 130.0 165.0 65.0 -v 290.0 165.0 114.0 -v 290.0 0.0 114.0 -f -4 -3 -2 -1 - -v 82.0 0.0 225.0 -v 82.0 165.0 225.0 -v 130.0 165.0 65.0 -v 130.0 0.0 65.0 -f -4 -3 -2 -1 - -v 240.0 0.0 272.0 -v 240.0 165.0 272.0 -v 82.0 165.0 225.0 -v 82.0 0.0 225.0 -f -4 -3 -2 -1 - -o tall_block -usemtl white - -v 423.0 330.0 247.0 -v 265.0 330.0 296.0 -v 314.0 330.0 456.0 -v 472.0 330.0 406.0 -f -4 -3 -2 -1 - -usemtl white -v 423.0 0.0 247.0 -v 423.0 330.0 247.0 -v 472.0 330.0 406.0 -v 472.0 0.0 406.0 -f -4 -3 -2 -1 - -v 472.0 0.0 406.0 -v 472.0 330.0 406.0 -v 314.0 330.0 456.0 -v 314.0 0.0 456.0 -f -4 -3 -2 -1 - -v 314.0 0.0 456.0 -v 314.0 330.0 456.0 -v 265.0 330.0 296.0 -v 265.0 0.0 296.0 -f -4 -3 -2 -1 - -v 265.0 0.0 296.0 -v 265.0 330.0 296.0 -v 423.0 330.0 247.0 -v 423.0 0.0 247.0 -f -4 -3 -2 -1 - 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); - - shape.mesh.indices.push_back(v0); - shape.mesh.indices.push_back(v1); - shape.mesh.indices.push_back(v2); + 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(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; } -- cgit v1.2.3 From 54bd46014c20790d8c8cd19bb94cce2904cfb28a Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Sat, 16 Apr 2016 20:25:33 +0900 Subject: Use google C++ code style. --- .clang-format | 2 +- test.cc | 28 ++-- tiny_obj_loader.h | 391 +++++++++++++++++++++++++----------------------------- 3 files changed, 197 insertions(+), 224 deletions(-) diff --git a/.clang-format b/.clang-format index a94a2c4..74210b0 100644 --- a/.clang-format +++ b/.clang-format @@ -1,5 +1,5 @@ --- -BasedOnStyle: LLVM +BasedOnStyle: Google IndentWidth: 2 TabWidth: 2 UseTab: Never diff --git a/test.cc b/test.cc index 88d8be1..72b4fa2 100644 --- a/test.cc +++ b/test.cc @@ -10,18 +10,18 @@ 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 vertices : " << (attrib.vertices.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++) { + for (size_t v = 0; v < attrib.vertices.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])); + static_cast(attrib.vertices[3*v+0]), + static_cast(attrib.vertices[3*v+1]), + static_cast(attrib.vertices[3*v+2])); } for (size_t v = 0; v < attrib.normals.size() / 3; v++) { @@ -126,15 +126,15 @@ static void PrintInfo(const tinyobj::attrib_t &attrib, const std::vector(materials[i].ambient[0]), static_cast(materials[i].ambient[1]), static_cast(materials[i].ambient[2])); + printf(" material.Kd = (%f, %f ,%f)\n", static_cast(materials[i].diffuse[0]), static_cast(materials[i].diffuse[1]), static_cast(materials[i].diffuse[2])); + printf(" material.Ks = (%f, %f ,%f)\n", static_cast(materials[i].specular[0]), static_cast(materials[i].specular[1]), static_cast(materials[i].specular[2])); + printf(" material.Tr = (%f, %f ,%f)\n", static_cast(materials[i].transmittance[0]), static_cast(materials[i].transmittance[1]), static_cast(materials[i].transmittance[2])); + printf(" material.Ke = (%f, %f ,%f)\n", static_cast(materials[i].emission[0]), static_cast(materials[i].emission[1]), static_cast(materials[i].emission[2])); + printf(" material.Ns = %f\n", static_cast(materials[i].shininess)); + printf(" material.Ni = %f\n", static_cast(materials[i].ior)); + printf(" material.dissolve = %f\n", static_cast(materials[i].dissolve)); + printf(" material.illum = %d\n", materials[i].illum); printf(" material.map_Ka = %s\n", materials[i].ambient_texname.c_str()); printf(" material.map_Kd = %s\n", materials[i].diffuse_texname.c_str()); printf(" material.map_Ks = %s\n", materials[i].specular_texname.c_str()); diff --git a/tiny_obj_loader.h b/tiny_obj_loader.h index b3a7589..e67bed3 100644 --- a/tiny_obj_loader.h +++ b/tiny_obj_loader.h @@ -5,7 +5,8 @@ // // -// version devel : Change data structure. Support different index for vertex/normal/texcoord(#73, #39) +// 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 @@ -57,20 +58,20 @@ typedef struct { float transmittance[3]; float emission[3]; float shininess; - float ior; // index of refraction - float dissolve; // 1 == opaque; 0 == fully transparent + float ior; // index of refraction + float dissolve; // 1 == opaque; 0 == fully transparent // illumination model (see http://www.fileformat.info/format/material/) int illum; - int dummy; // Suppress padding warning. + int dummy; // Suppress padding warning. - std::string ambient_texname; // map_Ka - std::string diffuse_texname; // map_Kd - std::string specular_texname; // map_Ks - std::string specular_highlight_texname; // map_Ns - std::string bump_texname; // map_bump, bump - std::string displacement_texname; // disp - std::string alpha_texname; // map_d + std::string ambient_texname; // map_Ka + std::string diffuse_texname; // map_Kd + std::string specular_texname; // map_Ks + std::string specular_highlight_texname; // map_Ns + std::string bump_texname; // map_bump, bump + std::string displacement_texname; // disp + std::string alpha_texname; // map_d std::map unknown_parameter; } material_t; @@ -93,9 +94,9 @@ typedef struct { 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 - std::vector tags; // SubD tag + num_vertices; // The number of vertices per face. Up to 255. + std::vector material_ids; // per-face material ID + std::vector tags; // SubD tag } mesh_t; typedef struct { @@ -103,14 +104,15 @@ typedef struct { mesh_t mesh; } shape_t; +// Vertex attributes typedef struct { - std::vector positions; - std::vector normals; - std::vector texcoords; + std::vector vertices; // 'v' + std::vector normals; // 'vn' + std::vector texcoords; // 'vt' } attrib_t; class MaterialReader { -public: + public: MaterialReader() {} virtual ~MaterialReader(); @@ -121,15 +123,15 @@ public: }; class MaterialFileReader : public MaterialReader { -public: - MaterialFileReader(const std::string &mtl_basepath) + public: + explicit MaterialFileReader(const std::string &mtl_basepath) : m_mtlBasePath(mtl_basepath) {} virtual ~MaterialFileReader() {} virtual bool operator()(const std::string &matId, std::vector *materials, std::map *matMap, std::string *err); -private: + private: std::string m_mtlBasePath; }; @@ -142,10 +144,8 @@ 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(attrib_t *attrib, - std::vector *shapes, - std::vector *materials, - std::string *err, +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); @@ -153,18 +153,16 @@ bool LoadObj(attrib_t *attrib, /// std::istream for materials. /// Returns true when loading .obj become success. /// Returns warning and error message into `err` -bool LoadObj(attrib_t *attrib, - std::vector *shapes, - std::vector *materials, - std::string *err, +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, - std::vector *materials, - std::istream *inStream); -} + std::vector *materials, std::istream *inStream); + +} // namespace tinyobj #ifdef TINYOBJLOADER_IMPLEMENTATION #include @@ -173,12 +171,11 @@ void LoadMtl(std::map *material_map, #include #include #include +#include #include #include -#include "tiny_obj_loader.h" - namespace tinyobj { MaterialReader::~MaterialReader() {} @@ -202,12 +199,9 @@ struct tag_sizes { // 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; } @@ -218,32 +212,31 @@ struct obj_shape { std::vector vt; }; -#define IS_SPACE( x ) ( ( (x) == ' ') || ( (x) == '\t') ) -#define IS_DIGIT( x ) ( static_cast( (x) - '0' ) < static_cast(10) ) -#define IS_NEW_LINE( x ) ( ( (x) == '\r') || ( (x) == '\n') || ( (x) == '\0') ) +#define IS_SPACE(x) (((x) == ' ') || ((x) == '\t')) +#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. 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) { +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; + (*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"); +static inline int parseInt(const char **token) { + (*token) += strspn((*token), " \t"); + int i = atoi((*token)); + (*token) += strcspn((*token), " \t\r"); return i; } @@ -261,7 +254,7 @@ static inline int parseInt(const char *&token) { // float = ( decimal , END ) | ( decimal , ("E" | "e") , integer , END ) ; // // Valid strings are for example: -// -0 +3.1417e+2 -0.0E-3 1.0324 -1.41 11e2 +// -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 // is returned. @@ -322,11 +315,9 @@ static bool tryParseDouble(const char *s, const char *s_end, double *result) { } // We must make sure we actually got something. - if (read == 0) - goto fail; + if (read == 0) goto fail; // We allow numbers of form "#", "###" etc. - if (!end_not_reached) - goto assemble; + if (!end_not_reached) goto assemble; // Read the decimal part. if (*curr == '.') { @@ -343,8 +334,7 @@ static bool tryParseDouble(const char *s, const char *s_end, double *result) { goto assemble; } - if (!end_not_reached) - goto assemble; + if (!end_not_reached) goto assemble; // Read the exponent part. if (*curr == 'e' || *curr == 'E') { @@ -367,8 +357,7 @@ static bool tryParseDouble(const char *s, const char *s_end, double *result) { read++; } exponent *= (exp_sign == '+' ? 1 : -1); - if (read == 0) - goto fail; + if (read == 0) goto fail; } assemble: @@ -378,121 +367,118 @@ assemble: fail: return false; } -static inline float parseFloat(const char *&token) { - token += strspn(token, " \t"); + +static inline float parseFloat(const char **token) { + (*token) += strspn((*token), " \t"); #ifdef TINY_OBJ_LOADER_OLD_FLOAT_PARSER - float f = (float)atof(token); - token += strcspn(token, " \t\r"); + float f = static_cast(atof(*token)); + (*token) += strcspn((*token), " \t\r"); #else - const char *end = token + strcspn(token, " \t\r"); + const char *end = (*token) + strcspn((*token), " \t\r"); double val = 0.0; - tryParseDouble(token, end, &val); + tryParseDouble((*token), end, &val); float f = static_cast(val); - token = end; + (*token) = end; #endif return f; } -static inline void parseFloat2(float &x, float &y, const char *&token) { - x = parseFloat(token); - y = parseFloat(token); +static inline void parseFloat2(float *x, float *y, const char **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); +static inline void parseFloat3(float *x, float *y, float *z, + const char **token) { + (*x) = parseFloat(token); + (*y) = parseFloat(token); + (*z) = parseFloat(token); } -static tag_sizes parseTagTriple(const char *&token) { +static tag_sizes parseTagTriple(const char **token) { tag_sizes ts; - ts.num_ints = atoi(token); - token += strcspn(token, "/ \t\r"); - if (token[0] != '/') { + ts.num_ints = atoi((*token)); + (*token) += strcspn((*token), "/ \t\r"); + if ((*token)[0] != '/') { return ts; } - token++; + (*token)++; - ts.num_floats = atoi(token); - token += strcspn(token, "/ \t\r"); - if (token[0] != '/') { + ts.num_floats = atoi((*token)); + (*token) += strcspn((*token), "/ \t\r"); + if ((*token)[0] != '/') { return ts; } - token++; + (*token)++; - ts.num_strings = atoi(token); - token += strcspn(token, "/ \t\r") + 1; + ts.num_strings = atoi((*token)); + (*token) += strcspn((*token), "/ \t\r") + 1; return ts; } // Parse triples: i, i/j/k, i//k, i/j -static vertex_index parseTriple(const char *&token, int vsize, int vnsize, +static vertex_index parseTriple(const char **token, int vsize, int vnsize, int vtsize) { vertex_index vi(-1); - vi.v_idx = fixIndex(atoi(token), vsize); - token += strcspn(token, "/ \t\r"); - if (token[0] != '/') { + vi.v_idx = fixIndex(atoi((*token)), vsize); + (*token) += strcspn((*token), "/ \t\r"); + if ((*token)[0] != '/') { return vi; } - token++; + (*token)++; // i//k - if (token[0] == '/') { - token++; - vi.vn_idx = fixIndex(atoi(token), vnsize); - token += strcspn(token, "/ \t\r"); + 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] != '/') { + 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"); + (*token)++; // skip '/' + vi.vn_idx = fixIndex(atoi((*token)), vnsize); + (*token) += strcspn((*token), "/ \t\r"); return vi; } -static void InitMaterial(material_t &material) { - material.name = ""; - material.ambient_texname = ""; - material.diffuse_texname = ""; - material.specular_texname = ""; - material.specular_highlight_texname = ""; - material.bump_texname = ""; - material.displacement_texname = ""; - material.alpha_texname = ""; +static void InitMaterial(material_t *material) { + material->name = ""; + material->ambient_texname = ""; + material->diffuse_texname = ""; + material->specular_texname = ""; + material->specular_highlight_texname = ""; + material->bump_texname = ""; + material->displacement_texname = ""; + material->alpha_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->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->illum = 0; + material->dissolve = 1.f; + material->shininess = 1.f; + material->ior = 1.f; + material->unknown_parameter.clear(); } static bool exportFaceGroupToShape( - 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 triangulate) { + shape_t *shape, const std::vector > &faceGroup, + const std::vector &tags, const int material_id, + const std::string &name, bool triangulate) { if (faceGroup.empty()) { return false; } @@ -508,7 +494,6 @@ static bool exportFaceGroupToShape( size_t npolys = face.size(); if (triangulate) { - // Polygon -> triangle fan conversion for (size_t k = 2; k < npolys; k++) { i1 = i2; @@ -525,15 +510,14 @@ static bool exportFaceGroupToShape( idx2.normal_index = i2.vn_idx; idx2.texcoord_index = i2.vt_idx; - shape.mesh.indices.push_back(idx0); - shape.mesh.indices.push_back(idx1); - shape.mesh.indices.push_back(idx2); + 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); + shape->mesh.num_vertices.push_back(3); + shape->mesh.material_ids.push_back(material_id); } } else { - for (size_t k = 0; k < npolys; k++) { index_t idx; idx.vertex_index = face[k].v_idx; @@ -541,26 +525,25 @@ static bool exportFaceGroupToShape( idx.texcoord_index = face[k].vt_idx; } - shape.mesh.num_vertices.push_back(static_cast(npolys)); - shape.mesh.material_ids.push_back(material_id); // per face + shape->mesh.num_vertices.push_back(static_cast(npolys)); + shape->mesh.material_ids.push_back(material_id); // per face } } - shape.name = name; - shape.mesh.tags.swap(tags); + shape->name = name; + shape->mesh.tags = tags; return true; } void LoadMtl(std::map *material_map, std::vector *materials, std::istream *inStream) { - // Create a default material anyway. material_t material; - InitMaterial(material); + InitMaterial(&material); - size_t maxchars = 8192; // Alloc enough size. - std::vector buf(maxchars); // Alloc enough size. + 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)); @@ -586,11 +569,9 @@ void LoadMtl(std::map *material_map, token += strspn(token, " \t"); assert(token); - if (token[0] == '\0') - continue; // empty line + 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)) && IS_SPACE((token[6]))) { @@ -602,7 +583,7 @@ void LoadMtl(std::map *material_map, } // initial temporary material - InitMaterial(material); + InitMaterial(&material); // set new mtl name char namebuf[TINYOBJ_SSCANF_BUFFER_SIZE]; @@ -620,7 +601,7 @@ void LoadMtl(std::map *material_map, if (token[0] == 'K' && token[1] == 'a' && IS_SPACE((token[2]))) { token += 2; float r, g, b; - parseFloat3(r, g, b, token); + parseFloat3(&r, &g, &b, &token); material.ambient[0] = r; material.ambient[1] = g; material.ambient[2] = b; @@ -631,7 +612,7 @@ void LoadMtl(std::map *material_map, if (token[0] == 'K' && token[1] == 'd' && IS_SPACE((token[2]))) { token += 2; float r, g, b; - parseFloat3(r, g, b, token); + parseFloat3(&r, &g, &b, &token); material.diffuse[0] = r; material.diffuse[1] = g; material.diffuse[2] = b; @@ -642,7 +623,7 @@ void LoadMtl(std::map *material_map, if (token[0] == 'K' && token[1] == 's' && IS_SPACE((token[2]))) { token += 2; float r, g, b; - parseFloat3(r, g, b, token); + parseFloat3(&r, &g, &b, &token); material.specular[0] = r; material.specular[1] = g; material.specular[2] = b; @@ -653,7 +634,7 @@ void LoadMtl(std::map *material_map, if (token[0] == 'K' && token[1] == 't' && IS_SPACE((token[2]))) { token += 2; float r, g, b; - parseFloat3(r, g, b, token); + parseFloat3(&r, &g, &b, &token); material.transmittance[0] = r; material.transmittance[1] = g; material.transmittance[2] = b; @@ -663,7 +644,7 @@ void LoadMtl(std::map *material_map, // ior(index of refraction) if (token[0] == 'N' && token[1] == 'i' && IS_SPACE((token[2]))) { token += 2; - material.ior = parseFloat(token); + material.ior = parseFloat(&token); continue; } @@ -671,7 +652,7 @@ void LoadMtl(std::map *material_map, if (token[0] == 'K' && token[1] == 'e' && IS_SPACE(token[2])) { token += 2; float r, g, b; - parseFloat3(r, g, b, token); + parseFloat3(&r, &g, &b, &token); material.emission[0] = r; material.emission[1] = g; material.emission[2] = b; @@ -681,27 +662,27 @@ void LoadMtl(std::map *material_map, // shininess if (token[0] == 'N' && token[1] == 's' && IS_SPACE(token[2])) { token += 2; - material.shininess = parseFloat(token); + material.shininess = parseFloat(&token); continue; } // illum model if (0 == strncmp(token, "illum", 5) && IS_SPACE(token[5])) { token += 6; - material.illum = parseInt(token); + material.illum = parseInt(&token); continue; } // dissolve if ((token[0] == 'd' && IS_SPACE(token[1]))) { token += 1; - material.dissolve = parseFloat(token); + material.dissolve = parseFloat(&token); continue; } if (token[0] == 'T' && token[1] == 'r' && IS_SPACE(token[2])) { token += 2; // Invert value of Tr(assume Tr is in range [0, 1]) - material.dissolve = 1.0f - parseFloat(token); + material.dissolve = 1.0f - parseFloat(&token); continue; } @@ -805,13 +786,11 @@ bool MaterialFileReader::operator()(const std::string &matId, return true; } -bool LoadObj(attrib_t *attrib, - std::vector *shapes, - std::vector *materials, - 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) { - - attrib->positions.clear(); + attrib->vertices.clear(); attrib->normals.clear(); attrib->texcoords.clear(); shapes->clear(); @@ -833,14 +812,14 @@ bool LoadObj(attrib_t *attrib, } MaterialFileReader matFileReader(basePath); - return LoadObj(attrib, shapes, materials, err, &ifs, &matFileReader, trianglulate); + return LoadObj(attrib, shapes, materials, err, &ifs, &matFileReader, + trianglulate); } -bool LoadObj(attrib_t *attrib, - std::vector *shapes, - std::vector *materials, - 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; @@ -852,13 +831,13 @@ bool LoadObj(attrib_t *attrib, // 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. + int maxchars = 8192; // Alloc enough size. + std::vector buf(static_cast(maxchars)); // Alloc enough size. while (inStream->peek() != -1) { inStream->getline(&buf[0], maxchars); @@ -884,17 +863,15 @@ bool LoadObj(attrib_t *attrib, token += strspn(token, " \t"); assert(token); - if (token[0] == '\0') - continue; // empty line + if (token[0] == '\0') continue; // empty line - if (token[0] == '#') - continue; // comment line + if (token[0] == '#') continue; // comment line // vertex if (token[0] == 'v' && IS_SPACE((token[1]))) { token += 2; float x, y, z; - parseFloat3(x, y, z, token); + parseFloat3(&x, &y, &z, &token); v.push_back(x); v.push_back(y); v.push_back(z); @@ -905,7 +882,7 @@ bool LoadObj(attrib_t *attrib, if (token[0] == 'v' && token[1] == 'n' && IS_SPACE((token[2]))) { token += 3; float x, y, z; - parseFloat3(x, y, z, token); + parseFloat3(&x, &y, &z, &token); vn.push_back(x); vn.push_back(y); vn.push_back(z); @@ -916,7 +893,7 @@ bool LoadObj(attrib_t *attrib, if (token[0] == 'v' && token[1] == 't' && IS_SPACE((token[2]))) { token += 3; float x, y; - parseFloat2(x, y, token); + parseFloat2(&x, &y, &token); vt.push_back(x); vt.push_back(y); continue; @@ -931,7 +908,7 @@ bool LoadObj(attrib_t *attrib, face.reserve(3); while (!IS_NEW_LINE(token[0])) { - vertex_index vi = parseTriple(token, static_cast(v.size() / 3), + vertex_index vi = parseTriple(&token, static_cast(v.size() / 3), static_cast(vn.size() / 3), static_cast(vt.size() / 2)); face.push_back(vi); @@ -948,7 +925,6 @@ bool LoadObj(attrib_t *attrib, // use mtl if ((0 == strncmp(token, "usemtl", 6)) && IS_SPACE((token[6]))) { - char namebuf[TINYOBJ_SSCANF_BUFFER_SIZE]; token += 7; #ifdef _MSC_VER @@ -966,8 +942,8 @@ bool LoadObj(attrib_t *attrib, if (newMaterialId != material) { // Create per-face material - exportFaceGroupToShape(shape, v, vn, vt, faceGroup, tags, - material, name, triangulate); + exportFaceGroupToShape(&shape, faceGroup, tags, material, name, + triangulate); faceGroup.clear(); material = newMaterialId; } @@ -992,7 +968,7 @@ bool LoadObj(attrib_t *attrib, } if (!ok) { - faceGroup.clear(); // for safety + faceGroup.clear(); // for safety return false; } @@ -1001,11 +977,9 @@ bool LoadObj(attrib_t *attrib, // group name if (token[0] == 'g' && IS_SPACE((token[1]))) { - // flush previous face group. - bool ret = - exportFaceGroupToShape(shape, v, vn, vt, faceGroup, tags, - material, name, triangulate); + bool ret = exportFaceGroupToShape(&shape, faceGroup, tags, material, name, + triangulate); if (ret) { shapes->push_back(shape); } @@ -1019,9 +993,9 @@ bool LoadObj(attrib_t *attrib, names.reserve(2); while (!IS_NEW_LINE(token[0])) { - std::string str = parseString(token); + std::string str = parseString(&token); names.push_back(str); - token += strspn(token, " \t\r"); // skip tag + token += strspn(token, " \t\r"); // skip tag } assert(names.size() > 0); @@ -1038,11 +1012,9 @@ bool LoadObj(attrib_t *attrib, // object name if (token[0] == 'o' && IS_SPACE((token[1]))) { - // flush previous face group. - bool ret = - exportFaceGroupToShape(shape, v, vn, vt, faceGroup, tags, - material, name, triangulate); + bool ret = exportFaceGroupToShape(&shape, faceGroup, tags, material, name, + triangulate); if (ret) { shapes->push_back(shape); } @@ -1078,7 +1050,7 @@ bool LoadObj(attrib_t *attrib, token += tag.name.size() + 1; - tag_sizes ts = parseTagTriple(token); + tag_sizes ts = parseTagTriple(&token); tag.intValues.resize(static_cast(ts.num_ints)); @@ -1089,7 +1061,7 @@ bool LoadObj(attrib_t *attrib, tag.floatValues.resize(static_cast(ts.num_floats)); for (size_t i = 0; i < static_cast(ts.num_floats); ++i) { - tag.floatValues[i] = parseFloat(token); + tag.floatValues[i] = parseFloat(&token); token += strcspn(token, "/ \t\r") + 1; } @@ -1098,7 +1070,8 @@ bool LoadObj(attrib_t *attrib, char stringValueBuffer[4096]; #ifdef _MSC_VER - sscanf_s(token, "%s", stringValueBuffer, (unsigned)_countof(stringValueBuffer)); + sscanf_s(token, "%s", stringValueBuffer, + (unsigned)_countof(stringValueBuffer)); #else sscanf(token, "%s", stringValueBuffer); #endif @@ -1112,26 +1085,26 @@ bool LoadObj(attrib_t *attrib, // Ignore unknown command. } - bool ret = exportFaceGroupToShape(shape, v, vn, vt, faceGroup, - tags, material, name, triangulate); + bool ret = exportFaceGroupToShape(&shape, faceGroup, tags, material, name, + triangulate); if (ret) { shapes->push_back(shape); } - faceGroup.clear(); // for safety + faceGroup.clear(); // for safety if (err) { (*err) += errss.str(); } - attrib->positions.swap(v); + attrib->vertices.swap(v); attrib->normals.swap(vn); attrib->texcoords.swap(vt); return true; } -} // namespace +} // namespace tinyobj #endif -#endif // TINY_OBJ_LOADER_H_ +#endif // TINY_OBJ_LOADER_H_ -- cgit v1.2.3 From 72ef6cbb76bea690288f20e9d6fa61241ee3e0be Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Mon, 18 Apr 2016 16:03:24 +0900 Subject: Add initial unit test codes using Catch. Add Kuroga build script. --- build.ninja | 8 +- cornell_box.mtl | 24 - cornell_box.obj | 145 - deps/cpplint.py | 6323 ++++++++++++++++++++++++++++ loader_example.cc | 312 ++ models/cornell_box.mtl | 24 + models/cornell_box.obj | 145 + premake4.lua | 6 +- test.cc | 312 -- tests/Makefile | 13 + tests/README.md | 25 + tests/catch.hpp | 10445 +++++++++++++++++++++++++++++++++++++++++++++++ tests/config-msvc.py | 52 + tests/config-posix.py | 53 + tests/kuroga.py | 312 ++ tests/tester.cc | 324 ++ tests/vcbuild.bat | 3 + tiny_obj_loader.h | 340 +- 18 files changed, 18376 insertions(+), 490 deletions(-) delete mode 100644 cornell_box.mtl delete mode 100644 cornell_box.obj create mode 100755 deps/cpplint.py create mode 100644 loader_example.cc create mode 100644 models/cornell_box.mtl create mode 100644 models/cornell_box.obj delete mode 100644 test.cc create mode 100644 tests/Makefile create mode 100644 tests/README.md create mode 100644 tests/catch.hpp create mode 100644 tests/config-msvc.py create mode 100644 tests/config-posix.py create mode 100755 tests/kuroga.py create mode 100644 tests/tester.cc create mode 100644 tests/vcbuild.bat diff --git a/build.ninja b/build.ninja index 3a26d97..5048ff1 100644 --- a/build.ninja +++ b/build.ninja @@ -3,6 +3,8 @@ cc = clang cxx = clang++ cflags = -Werror -Weverything cxxflags = -Werror -Weverything +#cflags = -O2 +#cxxflags = -O2 rule compile command = $cxx $cxxflags -c $in -o $out @@ -10,7 +12,7 @@ rule compile rule link command = $cxx $in -o $out -build test.o: compile test.cc -build test: link test.o +build loader_example.o: compile loader_example.cc +build loader_example: link loader_example.o -default test +default loader_example diff --git a/cornell_box.mtl b/cornell_box.mtl deleted file mode 100644 index d3a1c7a..0000000 --- a/cornell_box.mtl +++ /dev/null @@ -1,24 +0,0 @@ -newmtl white -Ka 0 0 0 -Kd 1 1 1 -Ks 0 0 0 - -newmtl red -Ka 0 0 0 -Kd 1 0 0 -Ks 0 0 0 - -newmtl green -Ka 0 0 0 -Kd 0 1 0 -Ks 0 0 0 - -newmtl blue -Ka 0 0 0 -Kd 0 0 1 -Ks 0 0 0 - -newmtl light -Ka 20 20 20 -Kd 1 1 1 -Ks 0 0 0 diff --git a/cornell_box.obj b/cornell_box.obj deleted file mode 100644 index 43e021f..0000000 --- a/cornell_box.obj +++ /dev/null @@ -1,145 +0,0 @@ -# cornell_box.obj and cornell_box.mtl are grabbed from Intel's embree project. -# original cornell box data - # comment - -# empty line including some space - - -mtllib cornell_box.mtl - -o floor -usemtl white -v 552.8 0.0 0.0 -v 0.0 0.0 0.0 -v 0.0 0.0 559.2 -v 549.6 0.0 559.2 - -v 130.0 0.0 65.0 -v 82.0 0.0 225.0 -v 240.0 0.0 272.0 -v 290.0 0.0 114.0 - -v 423.0 0.0 247.0 -v 265.0 0.0 296.0 -v 314.0 0.0 456.0 -v 472.0 0.0 406.0 - -f 1 2 3 4 -f 8 7 6 5 -f 12 11 10 9 - -o light -usemtl light -v 343.0 548.0 227.0 -v 343.0 548.0 332.0 -v 213.0 548.0 332.0 -v 213.0 548.0 227.0 -f -4 -3 -2 -1 - -o ceiling -usemtl white -v 556.0 548.8 0.0 -v 556.0 548.8 559.2 -v 0.0 548.8 559.2 -v 0.0 548.8 0.0 -f -4 -3 -2 -1 - -o back_wall -usemtl white -v 549.6 0.0 559.2 -v 0.0 0.0 559.2 -v 0.0 548.8 559.2 -v 556.0 548.8 559.2 -f -4 -3 -2 -1 - -o front_wall -usemtl blue -v 549.6 0.0 0 -v 0.0 0.0 0 -v 0.0 548.8 0 -v 556.0 548.8 0 -#f -1 -2 -3 -4 - -o green_wall -usemtl green -v 0.0 0.0 559.2 -v 0.0 0.0 0.0 -v 0.0 548.8 0.0 -v 0.0 548.8 559.2 -f -4 -3 -2 -1 - -o red_wall -usemtl red -v 552.8 0.0 0.0 -v 549.6 0.0 559.2 -v 556.0 548.8 559.2 -v 556.0 548.8 0.0 -f -4 -3 -2 -1 - -o short_block -usemtl white - -v 130.0 165.0 65.0 -v 82.0 165.0 225.0 -v 240.0 165.0 272.0 -v 290.0 165.0 114.0 -f -4 -3 -2 -1 - -v 290.0 0.0 114.0 -v 290.0 165.0 114.0 -v 240.0 165.0 272.0 -v 240.0 0.0 272.0 -f -4 -3 -2 -1 - -v 130.0 0.0 65.0 -v 130.0 165.0 65.0 -v 290.0 165.0 114.0 -v 290.0 0.0 114.0 -f -4 -3 -2 -1 - -v 82.0 0.0 225.0 -v 82.0 165.0 225.0 -v 130.0 165.0 65.0 -v 130.0 0.0 65.0 -f -4 -3 -2 -1 - -v 240.0 0.0 272.0 -v 240.0 165.0 272.0 -v 82.0 165.0 225.0 -v 82.0 0.0 225.0 -f -4 -3 -2 -1 - -o tall_block -usemtl white - -v 423.0 330.0 247.0 -v 265.0 330.0 296.0 -v 314.0 330.0 456.0 -v 472.0 330.0 406.0 -f -4 -3 -2 -1 - -usemtl white -v 423.0 0.0 247.0 -v 423.0 330.0 247.0 -v 472.0 330.0 406.0 -v 472.0 0.0 406.0 -f -4 -3 -2 -1 - -v 472.0 0.0 406.0 -v 472.0 330.0 406.0 -v 314.0 330.0 456.0 -v 314.0 0.0 456.0 -f -4 -3 -2 -1 - -v 314.0 0.0 456.0 -v 314.0 330.0 456.0 -v 265.0 330.0 296.0 -v 265.0 0.0 296.0 -f -4 -3 -2 -1 - -v 265.0 0.0 296.0 -v 265.0 330.0 296.0 -v 423.0 330.0 247.0 -v 423.0 0.0 247.0 -f -4 -3 -2 -1 - diff --git a/deps/cpplint.py b/deps/cpplint.py new file mode 100755 index 0000000..ccc25d4 --- /dev/null +++ b/deps/cpplint.py @@ -0,0 +1,6323 @@ +#!/usr/bin/env python +# +# Copyright (c) 2009 Google Inc. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +"""Does google-lint on c++ files. + +The goal of this script is to identify places in the code that *may* +be in non-compliance with google style. It does not attempt to fix +up these problems -- the point is to educate. It does also not +attempt to find all problems, or to ensure that everything it does +find is legitimately a problem. + +In particular, we can get very confused by /* and // inside strings! +We do a small hack, which is to ignore //'s with "'s after them on the +same line, but it is far from perfect (in either direction). +""" + +import codecs +import copy +import getopt +import math # for log +import os +import re +import sre_compile +import string +import sys +import unicodedata + + +_USAGE = """ +Syntax: cpplint.py [--verbose=#] [--output=vs7] [--filter=-x,+y,...] + [--counting=total|toplevel|detailed] [--root=subdir] + [--linelength=digits] + [file] ... + + The style guidelines this tries to follow are those in + http://google-styleguide.googlecode.com/svn/trunk/cppguide.xml + + Every problem is given a confidence score from 1-5, with 5 meaning we are + certain of the problem, and 1 meaning it could be a legitimate construct. + This will miss some errors, and is not a substitute for a code review. + + To suppress false-positive errors of a certain category, add a + 'NOLINT(category)' comment to the line. NOLINT or NOLINT(*) + suppresses errors of all categories on that line. + + The files passed in will be linted; at least one file must be provided. + Default linted extensions are .cc, .cpp, .cu, .cuh and .h. Change the + extensions with the --extensions flag. + + Flags: + + output=vs7 + By default, the output is formatted to ease emacs parsing. Visual Studio + compatible output (vs7) may also be used. Other formats are unsupported. + + verbose=# + Specify a number 0-5 to restrict errors to certain verbosity levels. + + filter=-x,+y,... + Specify a comma-separated list of category-filters to apply: only + error messages whose category names pass the filters will be printed. + (Category names are printed with the message and look like + "[whitespace/indent]".) Filters are evaluated left to right. + "-FOO" and "FOO" means "do not print categories that start with FOO". + "+FOO" means "do print categories that start with FOO". + + Examples: --filter=-whitespace,+whitespace/braces + --filter=whitespace,runtime/printf,+runtime/printf_format + --filter=-,+build/include_what_you_use + + To see a list of all the categories used in cpplint, pass no arg: + --filter= + + counting=total|toplevel|detailed + The total number of errors found is always printed. If + 'toplevel' is provided, then the count of errors in each of + the top-level categories like 'build' and 'whitespace' will + also be printed. If 'detailed' is provided, then a count + is provided for each category like 'build/class'. + + root=subdir + The root directory used for deriving header guard CPP variable. + By default, the header guard CPP variable is calculated as the relative + path to the directory that contains .git, .hg, or .svn. When this flag + is specified, the relative path is calculated from the specified + directory. If the specified directory does not exist, this flag is + ignored. + + Examples: + Assuming that src/.git exists, the header guard CPP variables for + src/chrome/browser/ui/browser.h are: + + No flag => CHROME_BROWSER_UI_BROWSER_H_ + --root=chrome => BROWSER_UI_BROWSER_H_ + --root=chrome/browser => UI_BROWSER_H_ + + linelength=digits + This is the allowed line length for the project. The default value is + 80 characters. + + Examples: + --linelength=120 + + extensions=extension,extension,... + The allowed file extensions that cpplint will check + + Examples: + --extensions=hpp,cpp + + cpplint.py supports per-directory configurations specified in CPPLINT.cfg + files. CPPLINT.cfg file can contain a number of key=value pairs. + Currently the following options are supported: + + set noparent + filter=+filter1,-filter2,... + exclude_files=regex + linelength=80 + + "set noparent" option prevents cpplint from traversing directory tree + upwards looking for more .cfg files in parent directories. This option + is usually placed in the top-level project directory. + + The "filter" option is similar in function to --filter flag. It specifies + message filters in addition to the |_DEFAULT_FILTERS| and those specified + through --filter command-line flag. + + "exclude_files" allows to specify a regular expression to be matched against + a file name. If the expression matches, the file is skipped and not run + through liner. + + "linelength" allows to specify the allowed line length for the project. + + CPPLINT.cfg has an effect on files in the same directory and all + sub-directories, unless overridden by a nested configuration file. + + Example file: + filter=-build/include_order,+build/include_alpha + exclude_files=.*\.cc + + The above example disables build/include_order warning and enables + build/include_alpha as well as excludes all .cc from being + processed by linter, in the current directory (where the .cfg + file is located) and all sub-directories. +""" + +# We categorize each error message we print. Here are the categories. +# We want an explicit list so we can list them all in cpplint --filter=. +# If you add a new error message with a new category, add it to the list +# here! cpplint_unittest.py should tell you if you forget to do this. +_ERROR_CATEGORIES = [ + 'build/class', + 'build/c++11', + 'build/deprecated', + 'build/endif_comment', + 'build/explicit_make_pair', + 'build/forward_decl', + 'build/header_guard', + 'build/include', + 'build/include_alpha', + 'build/include_order', + 'build/include_what_you_use', + 'build/namespaces', + 'build/printf_format', + 'build/storage_class', + 'legal/copyright', + 'readability/alt_tokens', + 'readability/braces', + 'readability/casting', + 'readability/check', + 'readability/constructors', + 'readability/fn_size', + 'readability/function', + 'readability/inheritance', + 'readability/multiline_comment', + 'readability/multiline_string', + 'readability/namespace', + 'readability/nolint', + 'readability/nul', + 'readability/strings', + 'readability/todo', + 'readability/utf8', + 'runtime/arrays', + 'runtime/casting', + 'runtime/explicit', + 'runtime/int', + 'runtime/init', + 'runtime/invalid_increment', + 'runtime/member_string_references', + 'runtime/memset', + 'runtime/indentation_namespace', + 'runtime/operator', + 'runtime/printf', + 'runtime/printf_format', + 'runtime/references', + 'runtime/string', + 'runtime/threadsafe_fn', + 'runtime/vlog', + 'whitespace/blank_line', + 'whitespace/braces', + 'whitespace/comma', + 'whitespace/comments', + 'whitespace/empty_conditional_body', + 'whitespace/empty_loop_body', + 'whitespace/end_of_line', + 'whitespace/ending_newline', + 'whitespace/forcolon', + 'whitespace/indent', + 'whitespace/line_length', + 'whitespace/newline', + 'whitespace/operators', + 'whitespace/parens', + 'whitespace/semicolon', + 'whitespace/tab', + 'whitespace/todo', + ] + +# These error categories are no longer enforced by cpplint, but for backwards- +# compatibility they may still appear in NOLINT comments. +_LEGACY_ERROR_CATEGORIES = [ + 'readability/streams', + ] + +# The default state of the category filter. This is overridden by the --filter= +# flag. By default all errors are on, so only add here categories that should be +# off by default (i.e., categories that must be enabled by the --filter= flags). +# All entries here should start with a '-' or '+', as in the --filter= flag. +_DEFAULT_FILTERS = ['-build/include_alpha'] + +# We used to check for high-bit characters, but after much discussion we +# decided those were OK, as long as they were in UTF-8 and didn't represent +# hard-coded international strings, which belong in a separate i18n file. + +# C++ headers +_CPP_HEADERS = frozenset([ + # Legacy + 'algobase.h', + 'algo.h', + 'alloc.h', + 'builtinbuf.h', + 'bvector.h', + 'complex.h', + 'defalloc.h', + 'deque.h', + 'editbuf.h', + 'fstream.h', + 'function.h', + 'hash_map', + 'hash_map.h', + 'hash_set', + 'hash_set.h', + 'hashtable.h', + 'heap.h', + 'indstream.h', + 'iomanip.h', + 'iostream.h', + 'istream.h', + 'iterator.h', + 'list.h', + 'map.h', + 'multimap.h', + 'multiset.h', + 'ostream.h', + 'pair.h', + 'parsestream.h', + 'pfstream.h', + 'procbuf.h', + 'pthread_alloc', + 'pthread_alloc.h', + 'rope', + 'rope.h', + 'ropeimpl.h', + 'set.h', + 'slist', + 'slist.h', + 'stack.h', + 'stdiostream.h', + 'stl_alloc.h', + 'stl_relops.h', + 'streambuf.h', + 'stream.h', + 'strfile.h', + 'strstream.h', + 'tempbuf.h', + 'tree.h', + 'type_traits.h', + 'vector.h', + # 17.6.1.2 C++ library headers + 'algorithm', + 'array', + 'atomic', + 'bitset', + 'chrono', + 'codecvt', + 'complex', + 'condition_variable', + 'deque', + 'exception', + 'forward_list', + 'fstream', + 'functional', + 'future', + 'initializer_list', + 'iomanip', + 'ios', + 'iosfwd', + 'iostream', + 'istream', + 'iterator', + 'limits', + 'list', + 'locale', + 'map', + 'memory', + 'mutex', + 'new', + 'numeric', + 'ostream', + 'queue', + 'random', + 'ratio', + 'regex', + 'set', + 'sstream', + 'stack', + 'stdexcept', + 'streambuf', + 'string', + 'strstream', + 'system_error', + 'thread', + 'tuple', + 'typeindex', + 'typeinfo', + 'type_traits', + 'unordered_map', + 'unordered_set', + 'utility', + 'valarray', + 'vector', + # 17.6.1.2 C++ headers for C library facilities + 'cassert', + 'ccomplex', + 'cctype', + 'cerrno', + 'cfenv', + 'cfloat', + 'cinttypes', + 'ciso646', + 'climits', + 'clocale', + 'cmath', + 'csetjmp', + 'csignal', + 'cstdalign', + 'cstdarg', + 'cstdbool', + 'cstddef', + 'cstdint', + 'cstdio', + 'cstdlib', + 'cstring', + 'ctgmath', + 'ctime', + 'cuchar', + 'cwchar', + 'cwctype', + ]) + + +# These headers are excluded from [build/include] and [build/include_order] +# checks: +# - Anything not following google file name conventions (containing an +# uppercase character, such as Python.h or nsStringAPI.h, for example). +# - Lua headers. +_THIRD_PARTY_HEADERS_PATTERN = re.compile( + r'^(?:[^/]*[A-Z][^/]*\.h|lua\.h|lauxlib\.h|lualib\.h)$') + + +# Assertion macros. These are defined in base/logging.h and +# testing/base/gunit.h. Note that the _M versions need to come first +# for substring matching to work. +_CHECK_MACROS = [ + 'DCHECK', 'CHECK', + 'EXPECT_TRUE_M', 'EXPECT_TRUE', + 'ASSERT_TRUE_M', 'ASSERT_TRUE', + 'EXPECT_FALSE_M', 'EXPECT_FALSE', + 'ASSERT_FALSE_M', 'ASSERT_FALSE', + ] + +# Replacement macros for CHECK/DCHECK/EXPECT_TRUE/EXPECT_FALSE +_CHECK_REPLACEMENT = dict([(m, {}) for m in _CHECK_MACROS]) + +for op, replacement in [('==', 'EQ'), ('!=', 'NE'), + ('>=', 'GE'), ('>', 'GT'), + ('<=', 'LE'), ('<', 'LT')]: + _CHECK_REPLACEMENT['DCHECK'][op] = 'DCHECK_%s' % replacement + _CHECK_REPLACEMENT['CHECK'][op] = 'CHECK_%s' % replacement + _CHECK_REPLACEMENT['EXPECT_TRUE'][op] = 'EXPECT_%s' % replacement + _CHECK_REPLACEMENT['ASSERT_TRUE'][op] = 'ASSERT_%s' % replacement + _CHECK_REPLACEMENT['EXPECT_TRUE_M'][op] = 'EXPECT_%s_M' % replacement + _CHECK_REPLACEMENT['ASSERT_TRUE_M'][op] = 'ASSERT_%s_M' % replacement + +for op, inv_replacement in [('==', 'NE'), ('!=', 'EQ'), + ('>=', 'LT'), ('>', 'LE'), + ('<=', 'GT'), ('<', 'GE')]: + _CHECK_REPLACEMENT['EXPECT_FALSE'][op] = 'EXPECT_%s' % inv_replacement + _CHECK_REPLACEMENT['ASSERT_FALSE'][op] = 'ASSERT_%s' % inv_replacement + _CHECK_REPLACEMENT['EXPECT_FALSE_M'][op] = 'EXPECT_%s_M' % inv_replacement + _CHECK_REPLACEMENT['ASSERT_FALSE_M'][op] = 'ASSERT_%s_M' % inv_replacement + +# Alternative tokens and their replacements. For full list, see section 2.5 +# Alternative tokens [lex.digraph] in the C++ standard. +# +# Digraphs (such as '%:') are not included here since it's a mess to +# match those on a word boundary. +_ALT_TOKEN_REPLACEMENT = { + 'and': '&&', + 'bitor': '|', + 'or': '||', + 'xor': '^', + 'compl': '~', + 'bitand': '&', + 'and_eq': '&=', + 'or_eq': '|=', + 'xor_eq': '^=', + 'not': '!', + 'not_eq': '!=' + } + +# Compile regular expression that matches all the above keywords. The "[ =()]" +# bit is meant to avoid matching these keywords outside of boolean expressions. +# +# False positives include C-style multi-line comments and multi-line strings +# but those have always been troublesome for cpplint. +_ALT_TOKEN_REPLACEMENT_PATTERN = re.compile( + r'[ =()](' + ('|'.join(_ALT_TOKEN_REPLACEMENT.keys())) + r')(?=[ (]|$)') + + +# These constants define types of headers for use with +# _IncludeState.CheckNextIncludeOrder(). +_C_SYS_HEADER = 1 +_CPP_SYS_HEADER = 2 +_LIKELY_MY_HEADER = 3 +_POSSIBLE_MY_HEADER = 4 +_OTHER_HEADER = 5 + +# These constants define the current inline assembly state +_NO_ASM = 0 # Outside of inline assembly block +_INSIDE_ASM = 1 # Inside inline assembly block +_END_ASM = 2 # Last line of inline assembly block +_BLOCK_ASM = 3 # The whole block is an inline assembly block + +# Match start of assembly blocks +_MATCH_ASM = re.compile(r'^\s*(?:asm|_asm|__asm|__asm__)' + r'(?:\s+(volatile|__volatile__))?' + r'\s*[{(]') + + +_regexp_compile_cache = {} + +# {str, set(int)}: a map from error categories to sets of linenumbers +# on which those errors are expected and should be suppressed. +_error_suppressions = {} + +# The root directory used for deriving header guard CPP variable. +# This is set by --root flag. +_root = None + +# The allowed line length of files. +# This is set by --linelength flag. +_line_length = 80 + +# The allowed extensions for file names +# This is set by --extensions flag. +_valid_extensions = set(['cc', 'h', 'cpp', 'cu', 'cuh']) + +def ParseNolintSuppressions(filename, raw_line, linenum, error): + """Updates the global list of error-suppressions. + + Parses any NOLINT comments on the current line, updating the global + error_suppressions store. Reports an error if the NOLINT comment + was malformed. + + Args: + filename: str, the name of the input file. + raw_line: str, the line of input text, with comments. + linenum: int, the number of the current line. + error: function, an error handler. + """ + matched = Search(r'\bNOLINT(NEXTLINE)?\b(\([^)]+\))?', raw_line) + if matched: + if matched.group(1): + suppressed_line = linenum + 1 + else: + suppressed_line = linenum + category = matched.group(2) + if category in (None, '(*)'): # => "suppress all" + _error_suppressions.setdefault(None, set()).add(suppressed_line) + else: + if category.startswith('(') and category.endswith(')'): + category = category[1:-1] + if category in _ERROR_CATEGORIES: + _error_suppressions.setdefault(category, set()).add(suppressed_line) + elif category not in _LEGACY_ERROR_CATEGORIES: + error(filename, linenum, 'readability/nolint', 5, + 'Unknown NOLINT error category: %s' % category) + + +def ResetNolintSuppressions(): + """Resets the set of NOLINT suppressions to empty.""" + _error_suppressions.clear() + + +def IsErrorSuppressedByNolint(category, linenum): + """Returns true if the specified error category is suppressed on this line. + + Consults the global error_suppressions map populated by + ParseNolintSuppressions/ResetNolintSuppressions. + + Args: + category: str, the category of the error. + linenum: int, the current line number. + Returns: + bool, True iff the error should be suppressed due to a NOLINT comment. + """ + return (linenum in _error_suppressions.get(category, set()) or + linenum in _error_suppressions.get(None, set())) + + +def Match(pattern, s): + """Matches the string with the pattern, caching the compiled regexp.""" + # The regexp compilation caching is inlined in both Match and Search for + # performance reasons; factoring it out into a separate function turns out + # to be noticeably expensive. + if pattern not in _regexp_compile_cache: + _regexp_compile_cache[pattern] = sre_compile.compile(pattern) + return _regexp_compile_cache[pattern].match(s) + + +def ReplaceAll(pattern, rep, s): + """Replaces instances of pattern in a string with a replacement. + + The compiled regex is kept in a cache shared by Match and Search. + + Args: + pattern: regex pattern + rep: replacement text + s: search string + + Returns: + string with replacements made (or original string if no replacements) + """ + if pattern not in _regexp_compile_cache: + _regexp_compile_cache[pattern] = sre_compile.compile(pattern) + return _regexp_compile_cache[pattern].sub(rep, s) + + +def Search(pattern, s): + """Searches the string for the pattern, caching the compiled regexp.""" + if pattern not in _regexp_compile_cache: + _regexp_compile_cache[pattern] = sre_compile.compile(pattern) + return _regexp_compile_cache[pattern].search(s) + + +class _IncludeState(object): + """Tracks line numbers for includes, and the order in which includes appear. + + include_list contains list of lists of (header, line number) pairs. + It's a lists of lists rather than just one flat list to make it + easier to update across preprocessor boundaries. + + Call CheckNextIncludeOrder() once for each header in the file, passing + in the type constants defined above. Calls in an illegal order will + raise an _IncludeError with an appropriate error message. + + """ + # self._section will move monotonically through this set. If it ever + # needs to move backwards, CheckNextIncludeOrder will raise an error. + _INITIAL_SECTION = 0 + _MY_H_SECTION = 1 + _C_SECTION = 2 + _CPP_SECTION = 3 + _OTHER_H_SECTION = 4 + + _TYPE_NAMES = { + _C_SYS_HEADER: 'C system header', + _CPP_SYS_HEADER: 'C++ system header', + _LIKELY_MY_HEADER: 'header this file implements', + _POSSIBLE_MY_HEADER: 'header this file may implement', + _OTHER_HEADER: 'other header', + } + _SECTION_NAMES = { + _INITIAL_SECTION: "... nothing. (This can't be an error.)", + _MY_H_SECTION: 'a header this file implements', + _C_SECTION: 'C system header', + _CPP_SECTION: 'C++ system header', + _OTHER_H_SECTION: 'other header', + } + + def __init__(self): + self.include_list = [[]] + self.ResetSection('') + + def FindHeader(self, header): + """Check if a header has already been included. + + Args: + header: header to check. + Returns: + Line number of previous occurrence, or -1 if the header has not + been seen before. + """ + for section_list in self.include_list: + for f in section_list: + if f[0] == header: + return f[1] + return -1 + + def ResetSection(self, directive): + """Reset section checking for preprocessor directive. + + Args: + directive: preprocessor directive (e.g. "if", "else"). + """ + # The name of the current section. + self._section = self._INITIAL_SECTION + # The path of last found header. + self._last_header = '' + + # Update list of includes. Note that we never pop from the + # include list. + if directive in ('if', 'ifdef', 'ifndef'): + self.include_list.append([]) + elif directive in ('else', 'elif'): + self.include_list[-1] = [] + + def SetLastHeader(self, header_path): + self._last_header = header_path + + def CanonicalizeAlphabeticalOrder(self, header_path): + """Returns a path canonicalized for alphabetical comparison. + + - replaces "-" with "_" so they both cmp the same. + - removes '-inl' since we don't require them to be after the main header. + - lowercase everything, just in case. + + Args: + header_path: Path to be canonicalized. + + Returns: + Canonicalized path. + """ + return header_path.replace('-inl.h', '.h').replace('-', '_').lower() + + def IsInAlphabeticalOrder(self, clean_lines, linenum, header_path): + """Check if a header is in alphabetical order with the previous header. + + Args: + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + header_path: Canonicalized header to be checked. + + Returns: + Returns true if the header is in alphabetical order. + """ + # If previous section is different from current section, _last_header will + # be reset to empty string, so it's always less than current header. + # + # If previous line was a blank line, assume that the headers are + # intentionally sorted the way they are. + if (self._last_header > header_path and + Match(r'^\s*#\s*include\b', clean_lines.elided[linenum - 1])): + return False + return True + + def CheckNextIncludeOrder(self, header_type): + """Returns a non-empty error message if the next header is out of order. + + This function also updates the internal state to be ready to check + the next include. + + Args: + header_type: One of the _XXX_HEADER constants defined above. + + Returns: + The empty string if the header is in the right order, or an + error message describing what's wrong. + + """ + error_message = ('Found %s after %s' % + (self._TYPE_NAMES[header_type], + self._SECTION_NAMES[self._section])) + + last_section = self._section + + if header_type == _C_SYS_HEADER: + if self._section <= self._C_SECTION: + self._section = self._C_SECTION + else: + self._last_header = '' + return error_message + elif header_type == _CPP_SYS_HEADER: + if self._section <= self._CPP_SECTION: + self._section = self._CPP_SECTION + else: + self._last_header = '' + return error_message + elif header_type == _LIKELY_MY_HEADER: + if self._section <= self._MY_H_SECTION: + self._section = self._MY_H_SECTION + else: + self._section = self._OTHER_H_SECTION + elif header_type == _POSSIBLE_MY_HEADER: + if self._section <= self._MY_H_SECTION: + self._section = self._MY_H_SECTION + else: + # This will always be the fallback because we're not sure + # enough that the header is associated with this file. + self._section = self._OTHER_H_SECTION + else: + assert header_type == _OTHER_HEADER + self._section = self._OTHER_H_SECTION + + if last_section != self._section: + self._last_header = '' + + return '' + + +class _CppLintState(object): + """Maintains module-wide state..""" + + def __init__(self): + self.verbose_level = 1 # global setting. + self.error_count = 0 # global count of reported errors + # filters to apply when emitting error messages + self.filters = _DEFAULT_FILTERS[:] + # backup of filter list. Used to restore the state after each file. + self._filters_backup = self.filters[:] + self.counting = 'total' # In what way are we counting errors? + self.errors_by_category = {} # string to int dict storing error counts + + # output format: + # "emacs" - format that emacs can parse (default) + # "vs7" - format that Microsoft Visual Studio 7 can parse + self.output_format = 'emacs' + + def SetOutputFormat(self, output_format): + """Sets the output format for errors.""" + self.output_format = output_format + + def SetVerboseLevel(self, level): + """Sets the module's verbosity, and returns the previous setting.""" + last_verbose_level = self.verbose_level + self.verbose_level = level + return last_verbose_level + + def SetCountingStyle(self, counting_style): + """Sets the module's counting options.""" + self.counting = counting_style + + def SetFilters(self, filters): + """Sets the error-message filters. + + These filters are applied when deciding whether to emit a given + error message. + + Args: + filters: A string of comma-separated filters (eg "+whitespace/indent"). + Each filter should start with + or -; else we die. + + Raises: + ValueError: The comma-separated filters did not all start with '+' or '-'. + E.g. "-,+whitespace,-whitespace/indent,whitespace/badfilter" + """ + # Default filters always have less priority than the flag ones. + self.filters = _DEFAULT_FILTERS[:] + self.AddFilters(filters) + + def AddFilters(self, filters): + """ Adds more filters to the existing list of error-message filters. """ + for filt in filters.split(','): + clean_filt = filt.strip() + if clean_filt: + self.filters.append(clean_filt) + for filt in self.filters: + if not (filt.startswith('+') or filt.startswith('-')): + raise ValueError('Every filter in --filters must start with + or -' + ' (%s does not)' % filt) + + def BackupFilters(self): + """ Saves the current filter list to backup storage.""" + self._filters_backup = self.filters[:] + + def RestoreFilters(self): + """ Restores filters previously backed up.""" + self.filters = self._filters_backup[:] + + def ResetErrorCounts(self): + """Sets the module's error statistic back to zero.""" + self.error_count = 0 + self.errors_by_category = {} + + def IncrementErrorCount(self, category): + """Bumps the module's error statistic.""" + self.error_count += 1 + if self.counting in ('toplevel', 'detailed'): + if self.counting != 'detailed': + category = category.split('/')[0] + if category not in self.errors_by_category: + self.errors_by_category[category] = 0 + self.errors_by_category[category] += 1 + + def PrintErrorCounts(self): + """Print a summary of errors by category, and the total.""" + for category, count in self.errors_by_category.iteritems(): + sys.stderr.write('Category \'%s\' errors found: %d\n' % + (category, count)) + sys.stderr.write('Total errors found: %d\n' % self.error_count) + +_cpplint_state = _CppLintState() + + +def _OutputFormat(): + """Gets the module's output format.""" + return _cpplint_state.output_format + + +def _SetOutputFormat(output_format): + """Sets the module's output format.""" + _cpplint_state.SetOutputFormat(output_format) + + +def _VerboseLevel(): + """Returns the module's verbosity setting.""" + return _cpplint_state.verbose_level + + +def _SetVerboseLevel(level): + """Sets the module's verbosity, and returns the previous setting.""" + return _cpplint_state.SetVerboseLevel(level) + + +def _SetCountingStyle(level): + """Sets the module's counting options.""" + _cpplint_state.SetCountingStyle(level) + + +def _Filters(): + """Returns the module's list of output filters, as a list.""" + return _cpplint_state.filters + + +def _SetFilters(filters): + """Sets the module's error-message filters. + + These filters are applied when deciding whether to emit a given + error message. + + Args: + filters: A string of comma-separated filters (eg "whitespace/indent"). + Each filter should start with + or -; else we die. + """ + _cpplint_state.SetFilters(filters) + +def _AddFilters(filters): + """Adds more filter overrides. + + Unlike _SetFilters, this function does not reset the current list of filters + available. + + Args: + filters: A string of comma-separated filters (eg "whitespace/indent"). + Each filter should start with + or -; else we die. + """ + _cpplint_state.AddFilters(filters) + +def _BackupFilters(): + """ Saves the current filter list to backup storage.""" + _cpplint_state.BackupFilters() + +def _RestoreFilters(): + """ Restores filters previously backed up.""" + _cpplint_state.RestoreFilters() + +class _FunctionState(object): + """Tracks current function name and the number of lines in its body.""" + + _NORMAL_TRIGGER = 250 # for --v=0, 500 for --v=1, etc. + _TEST_TRIGGER = 400 # about 50% more than _NORMAL_TRIGGER. + + def __init__(self): + self.in_a_function = False + self.lines_in_function = 0 + self.current_function = '' + + def Begin(self, function_name): + """Start analyzing function body. + + Args: + function_name: The name of the function being tracked. + """ + self.in_a_function = True + self.lines_in_function = 0 + self.current_function = function_name + + def Count(self): + """Count line in current function body.""" + if self.in_a_function: + self.lines_in_function += 1 + + def Check(self, error, filename, linenum): + """Report if too many lines in function body. + + Args: + error: The function to call with any errors found. + filename: The name of the current file. + linenum: The number of the line to check. + """ + if Match(r'T(EST|est)', self.current_function): + base_trigger = self._TEST_TRIGGER + else: + base_trigger = self._NORMAL_TRIGGER + trigger = base_trigger * 2**_VerboseLevel() + + if self.lines_in_function > trigger: + error_level = int(math.log(self.lines_in_function / base_trigger, 2)) + # 50 => 0, 100 => 1, 200 => 2, 400 => 3, 800 => 4, 1600 => 5, ... + if error_level > 5: + error_level = 5 + error(filename, linenum, 'readability/fn_size', error_level, + 'Small and focused functions are preferred:' + ' %s has %d non-comment lines' + ' (error triggered by exceeding %d lines).' % ( + self.current_function, self.lines_in_function, trigger)) + + def End(self): + """Stop analyzing function body.""" + self.in_a_function = False + + +class _IncludeError(Exception): + """Indicates a problem with the include order in a file.""" + pass + + +class FileInfo(object): + """Provides utility functions for filenames. + + FileInfo provides easy access to the components of a file's path + relative to the project root. + """ + + def __init__(self, filename): + self._filename = filename + + def FullName(self): + """Make Windows paths like Unix.""" + return os.path.abspath(self._filename).replace('\\', '/') + + def RepositoryName(self): + """FullName after removing the local path to the repository. + + If we have a real absolute path name here we can try to do something smart: + detecting the root of the checkout and truncating /path/to/checkout from + the name so that we get header guards that don't include things like + "C:\Documents and Settings\..." or "/home/username/..." in them and thus + people on different computers who have checked the source out to different + locations won't see bogus errors. + """ + fullname = self.FullName() + + if os.path.exists(fullname): + project_dir = os.path.dirname(fullname) + + if os.path.exists(os.path.join(project_dir, ".svn")): + # If there's a .svn file in the current directory, we recursively look + # up the directory tree for the top of the SVN checkout + root_dir = project_dir + one_up_dir = os.path.dirname(root_dir) + while os.path.exists(os.path.join(one_up_dir, ".svn")): + root_dir = os.path.dirname(root_dir) + one_up_dir = os.path.dirname(one_up_dir) + + prefix = os.path.commonprefix([root_dir, project_dir]) + return fullname[len(prefix) + 1:] + + # Not SVN <= 1.6? Try to find a git, hg, or svn top level directory by + # searching up from the current path. + root_dir = os.path.dirname(fullname) + while (root_dir != os.path.dirname(root_dir) and + not os.path.exists(os.path.join(root_dir, ".git")) and + not os.path.exists(os.path.join(root_dir, ".hg")) and + not os.path.exists(os.path.join(root_dir, ".svn"))): + root_dir = os.path.dirname(root_dir) + + if (os.path.exists(os.path.join(root_dir, ".git")) or + os.path.exists(os.path.join(root_dir, ".hg")) or + os.path.exists(os.path.join(root_dir, ".svn"))): + prefix = os.path.commonprefix([root_dir, project_dir]) + return fullname[len(prefix) + 1:] + + # Don't know what to do; header guard warnings may be wrong... + return fullname + + def Split(self): + """Splits the file into the directory, basename, and extension. + + For 'chrome/browser/browser.cc', Split() would + return ('chrome/browser', 'browser', '.cc') + + Returns: + A tuple of (directory, basename, extension). + """ + + googlename = self.RepositoryName() + project, rest = os.path.split(googlename) + return (project,) + os.path.splitext(rest) + + def BaseName(self): + """File base name - text after the final slash, before the final period.""" + return self.Split()[1] + + def Extension(self): + """File extension - text following the final period.""" + return self.Split()[2] + + def NoExtension(self): + """File has no source file extension.""" + return '/'.join(self.Split()[0:2]) + + def IsSource(self): + """File has a source file extension.""" + return self.Extension()[1:] in ('c', 'cc', 'cpp', 'cxx') + + +def _ShouldPrintError(category, confidence, linenum): + """If confidence >= verbose, category passes filter and is not suppressed.""" + + # There are three ways we might decide not to print an error message: + # a "NOLINT(category)" comment appears in the source, + # the verbosity level isn't high enough, or the filters filter it out. + if IsErrorSuppressedByNolint(category, linenum): + return False + + if confidence < _cpplint_state.verbose_level: + return False + + is_filtered = False + for one_filter in _Filters(): + if one_filter.startswith('-'): + if category.startswith(one_filter[1:]): + is_filtered = True + elif one_filter.startswith('+'): + if category.startswith(one_filter[1:]): + is_filtered = False + else: + assert False # should have been checked for in SetFilter. + if is_filtered: + return False + + return True + + +def Error(filename, linenum, category, confidence, message): + """Logs the fact we've found a lint error. + + We log where the error was found, and also our confidence in the error, + that is, how certain we are this is a legitimate style regression, and + not a misidentification or a use that's sometimes justified. + + False positives can be suppressed by the use of + "cpplint(category)" comments on the offending line. These are + parsed into _error_suppressions. + + Args: + filename: The name of the file containing the error. + linenum: The number of the line containing the error. + category: A string used to describe the "category" this bug + falls under: "whitespace", say, or "runtime". Categories + may have a hierarchy separated by slashes: "whitespace/indent". + confidence: A number from 1-5 representing a confidence score for + the error, with 5 meaning that we are certain of the problem, + and 1 meaning that it could be a legitimate construct. + message: The error message. + """ + if _ShouldPrintError(category, confidence, linenum): + _cpplint_state.IncrementErrorCount(category) + if _cpplint_state.output_format == 'vs7': + sys.stderr.write('%s(%s): %s [%s] [%d]\n' % ( + filename, linenum, message, category, confidence)) + elif _cpplint_state.output_format == 'eclipse': + sys.stderr.write('%s:%s: warning: %s [%s] [%d]\n' % ( + filename, linenum, message, category, confidence)) + else: + sys.stderr.write('%s:%s: %s [%s] [%d]\n' % ( + filename, linenum, message, category, confidence)) + + +# Matches standard C++ escape sequences per 2.13.2.3 of the C++ standard. +_RE_PATTERN_CLEANSE_LINE_ESCAPES = re.compile( + r'\\([abfnrtv?"\\\']|\d+|x[0-9a-fA-F]+)') +# Match a single C style comment on the same line. +_RE_PATTERN_C_COMMENTS = r'/\*(?:[^*]|\*(?!/))*\*/' +# Matches multi-line C style comments. +# This RE is a little bit more complicated than one might expect, because we +# have to take care of space removals tools so we can handle comments inside +# statements better. +# The current rule is: We only clear spaces from both sides when we're at the +# end of the line. Otherwise, we try to remove spaces from the right side, +# if this doesn't work we try on left side but only if there's a non-character +# on the right. +_RE_PATTERN_CLEANSE_LINE_C_COMMENTS = re.compile( + r'(\s*' + _RE_PATTERN_C_COMMENTS + r'\s*$|' + + _RE_PATTERN_C_COMMENTS + r'\s+|' + + r'\s+' + _RE_PATTERN_C_COMMENTS + r'(?=\W)|' + + _RE_PATTERN_C_COMMENTS + r')') + + +def IsCppString(line): + """Does line terminate so, that the next symbol is in string constant. + + This function does not consider single-line nor multi-line comments. + + Args: + line: is a partial line of code starting from the 0..n. + + Returns: + True, if next character appended to 'line' is inside a + string constant. + """ + + line = line.replace(r'\\', 'XX') # after this, \\" does not match to \" + return ((line.count('"') - line.count(r'\"') - line.count("'\"'")) & 1) == 1 + + +def CleanseRawStrings(raw_lines): + """Removes C++11 raw strings from lines. + + Before: + static const char kData[] = R"( + multi-line string + )"; + + After: + static const char kData[] = "" + (replaced by blank line) + ""; + + Args: + raw_lines: list of raw lines. + + Returns: + list of lines with C++11 raw strings replaced by empty strings. + """ + + delimiter = None + lines_without_raw_strings = [] + for line in raw_lines: + if delimiter: + # Inside a raw string, look for the end + end = line.find(delimiter) + if end >= 0: + # Found the end of the string, match leading space for this + # line and resume copying the original lines, and also insert + # a "" on the last line. + leading_space = Match(r'^(\s*)\S', line) + line = leading_space.group(1) + '""' + line[end + len(delimiter):] + delimiter = None + else: + # Haven't found the end yet, append a blank line. + line = '""' + + # Look for beginning of a raw string, and replace them with + # empty strings. This is done in a loop to handle multiple raw + # strings on the same line. + while delimiter is None: + # Look for beginning of a raw string. + # See 2.14.15 [lex.string] for syntax. + matched = Match(r'^(.*)\b(?:R|u8R|uR|UR|LR)"([^\s\\()]*)\((.*)$', line) + if matched: + delimiter = ')' + matched.group(2) + '"' + + end = matched.group(3).find(delimiter) + if end >= 0: + # Raw string ended on same line + line = (matched.group(1) + '""' + + matched.group(3)[end + len(delimiter):]) + delimiter = None + else: + # Start of a multi-line raw string + line = matched.group(1) + '""' + else: + break + + lines_without_raw_strings.append(line) + + # TODO(unknown): if delimiter is not None here, we might want to + # emit a warning for unterminated string. + return lines_without_raw_strings + + +def FindNextMultiLineCommentStart(lines, lineix): + """Find the beginning marker for a multiline comment.""" + while lineix < len(lines): + if lines[lineix].strip().startswith('/*'): + # Only return this marker if the comment goes beyond this line + if lines[lineix].strip().find('*/', 2) < 0: + return lineix + lineix += 1 + return len(lines) + + +def FindNextMultiLineCommentEnd(lines, lineix): + """We are inside a comment, find the end marker.""" + while lineix < len(lines): + if lines[lineix].strip().endswith('*/'): + return lineix + lineix += 1 + return len(lines) + + +def RemoveMultiLineCommentsFromRange(lines, begin, end): + """Clears a range of lines for multi-line comments.""" + # Having // dummy comments makes the lines non-empty, so we will not get + # unnecessary blank line warnings later in the code. + for i in range(begin, end): + lines[i] = '/**/' + + +def RemoveMultiLineComments(filename, lines, error): + """Removes multiline (c-style) comments from lines.""" + lineix = 0 + while lineix < len(lines): + lineix_begin = FindNextMultiLineCommentStart(lines, lineix) + if lineix_begin >= len(lines): + return + lineix_end = FindNextMultiLineCommentEnd(lines, lineix_begin) + if lineix_end >= len(lines): + error(filename, lineix_begin + 1, 'readability/multiline_comment', 5, + 'Could not find end of multi-line comment') + return + RemoveMultiLineCommentsFromRange(lines, lineix_begin, lineix_end + 1) + lineix = lineix_end + 1 + + +def CleanseComments(line): + """Removes //-comments and single-line C-style /* */ comments. + + Args: + line: A line of C++ source. + + Returns: + The line with single-line comments removed. + """ + commentpos = line.find('//') + if commentpos != -1 and not IsCppString(line[:commentpos]): + line = line[:commentpos].rstrip() + # get rid of /* ... */ + return _RE_PATTERN_CLEANSE_LINE_C_COMMENTS.sub('', line) + + +class CleansedLines(object): + """Holds 4 copies of all lines with different preprocessing applied to them. + + 1) elided member contains lines without strings and comments. + 2) lines member contains lines without comments. + 3) raw_lines member contains all the lines without processing. + 4) lines_without_raw_strings member is same as raw_lines, but with C++11 raw + strings removed. + All these members are of , and of the same length. + """ + + def __init__(self, lines): + self.elided = [] + self.lines = [] + self.raw_lines = lines + self.num_lines = len(lines) + self.lines_without_raw_strings = CleanseRawStrings(lines) + for linenum in range(len(self.lines_without_raw_strings)): + self.lines.append(CleanseComments( + self.lines_without_raw_strings[linenum])) + elided = self._CollapseStrings(self.lines_without_raw_strings[linenum]) + self.elided.append(CleanseComments(elided)) + + def NumLines(self): + """Returns the number of lines represented.""" + return self.num_lines + + @staticmethod + def _CollapseStrings(elided): + """Collapses strings and chars on a line to simple "" or '' blocks. + + We nix strings first so we're not fooled by text like '"http://"' + + Args: + elided: The line being processed. + + Returns: + The line with collapsed strings. + """ + if _RE_PATTERN_INCLUDE.match(elided): + return elided + + # Remove escaped characters first to make quote/single quote collapsing + # basic. Things that look like escaped characters shouldn't occur + # outside of strings and chars. + elided = _RE_PATTERN_CLEANSE_LINE_ESCAPES.sub('', elided) + + # Replace quoted strings and digit separators. Both single quotes + # and double quotes are processed in the same loop, otherwise + # nested quotes wouldn't work. + collapsed = '' + while True: + # Find the first quote character + match = Match(r'^([^\'"]*)([\'"])(.*)$', elided) + if not match: + collapsed += elided + break + head, quote, tail = match.groups() + + if quote == '"': + # Collapse double quoted strings + second_quote = tail.find('"') + if second_quote >= 0: + collapsed += head + '""' + elided = tail[second_quote + 1:] + else: + # Unmatched double quote, don't bother processing the rest + # of the line since this is probably a multiline string. + collapsed += elided + break + else: + # Found single quote, check nearby text to eliminate digit separators. + # + # There is no special handling for floating point here, because + # the integer/fractional/exponent parts would all be parsed + # correctly as long as there are digits on both sides of the + # separator. So we are fine as long as we don't see something + # like "0.'3" (gcc 4.9.0 will not allow this literal). + if Search(r'\b(?:0[bBxX]?|[1-9])[0-9a-fA-F]*$', head): + match_literal = Match(r'^((?:\'?[0-9a-zA-Z_])*)(.*)$', "'" + tail) + collapsed += head + match_literal.group(1).replace("'", '') + elided = match_literal.group(2) + else: + second_quote = tail.find('\'') + if second_quote >= 0: + collapsed += head + "''" + elided = tail[second_quote + 1:] + else: + # Unmatched single quote + collapsed += elided + break + + return collapsed + + +def FindEndOfExpressionInLine(line, startpos, stack): + """Find the position just after the end of current parenthesized expression. + + Args: + line: a CleansedLines line. + startpos: start searching at this position. + stack: nesting stack at startpos. + + Returns: + On finding matching end: (index just after matching end, None) + On finding an unclosed expression: (-1, None) + Otherwise: (-1, new stack at end of this line) + """ + for i in xrange(startpos, len(line)): + char = line[i] + if char in '([{': + # Found start of parenthesized expression, push to expression stack + stack.append(char) + elif char == '<': + # Found potential start of template argument list + if i > 0 and line[i - 1] == '<': + # Left shift operator + if stack and stack[-1] == '<': + stack.pop() + if not stack: + return (-1, None) + elif i > 0 and Search(r'\boperator\s*$', line[0:i]): + # operator<, don't add to stack + continue + else: + # Tentative start of template argument list + stack.append('<') + elif char in ')]}': + # Found end of parenthesized expression. + # + # If we are currently expecting a matching '>', the pending '<' + # must have been an operator. Remove them from expression stack. + while stack and stack[-1] == '<': + stack.pop() + if not stack: + return (-1, None) + if ((stack[-1] == '(' and char == ')') or + (stack[-1] == '[' and char == ']') or + (stack[-1] == '{' and char == '}')): + stack.pop() + if not stack: + return (i + 1, None) + else: + # Mismatched parentheses + return (-1, None) + elif char == '>': + # Found potential end of template argument list. + + # Ignore "->" and operator functions + if (i > 0 and + (line[i - 1] == '-' or Search(r'\boperator\s*$', line[0:i - 1]))): + continue + + # Pop the stack if there is a matching '<'. Otherwise, ignore + # this '>' since it must be an operator. + if stack: + if stack[-1] == '<': + stack.pop() + if not stack: + return (i + 1, None) + elif char == ';': + # Found something that look like end of statements. If we are currently + # expecting a '>', the matching '<' must have been an operator, since + # template argument list should not contain statements. + while stack and stack[-1] == '<': + stack.pop() + if not stack: + return (-1, None) + + # Did not find end of expression or unbalanced parentheses on this line + return (-1, stack) + + +def CloseExpression(clean_lines, linenum, pos): + """If input points to ( or { or [ or <, finds the position that closes it. + + If lines[linenum][pos] points to a '(' or '{' or '[' or '<', finds the + linenum/pos that correspond to the closing of the expression. + + TODO(unknown): cpplint spends a fair bit of time matching parentheses. + Ideally we would want to index all opening and closing parentheses once + and have CloseExpression be just a simple lookup, but due to preprocessor + tricks, this is not so easy. + + Args: + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + pos: A position on the line. + + Returns: + A tuple (line, linenum, pos) pointer *past* the closing brace, or + (line, len(lines), -1) if we never find a close. Note we ignore + strings and comments when matching; and the line we return is the + 'cleansed' line at linenum. + """ + + line = clean_lines.elided[linenum] + if (line[pos] not in '({[<') or Match(r'<[<=]', line[pos:]): + return (line, clean_lines.NumLines(), -1) + + # Check first line + (end_pos, stack) = FindEndOfExpressionInLine(line, pos, []) + if end_pos > -1: + return (line, linenum, end_pos) + + # Continue scanning forward + while stack and linenum < clean_lines.NumLines() - 1: + linenum += 1 + line = clean_lines.elided[linenum] + (end_pos, stack) = FindEndOfExpressionInLine(line, 0, stack) + if end_pos > -1: + return (line, linenum, end_pos) + + # Did not find end of expression before end of file, give up + return (line, clean_lines.NumLines(), -1) + + +def FindStartOfExpressionInLine(line, endpos, stack): + """Find position at the matching start of current expression. + + This is almost the reverse of FindEndOfExpressionInLine, but note + that the input position and returned position differs by 1. + + Args: + line: a CleansedLines line. + endpos: start searching at this position. + stack: nesting stack at endpos. + + Returns: + On finding matching start: (index at matching start, None) + On finding an unclosed expression: (-1, None) + Otherwise: (-1, new stack at beginning of this line) + """ + i = endpos + while i >= 0: + char = line[i] + if char in ')]}': + # Found end of expression, push to expression stack + stack.append(char) + elif char == '>': + # Found potential end of template argument list. + # + # Ignore it if it's a "->" or ">=" or "operator>" + if (i > 0 and + (line[i - 1] == '-' or + Match(r'\s>=\s', line[i - 1:]) or + Search(r'\boperator\s*$', line[0:i]))): + i -= 1 + else: + stack.append('>') + elif char == '<': + # Found potential start of template argument list + if i > 0 and line[i - 1] == '<': + # Left shift operator + i -= 1 + else: + # If there is a matching '>', we can pop the expression stack. + # Otherwise, ignore this '<' since it must be an operator. + if stack and stack[-1] == '>': + stack.pop() + if not stack: + return (i, None) + elif char in '([{': + # Found start of expression. + # + # If there are any unmatched '>' on the stack, they must be + # operators. Remove those. + while stack and stack[-1] == '>': + stack.pop() + if not stack: + return (-1, None) + if ((char == '(' and stack[-1] == ')') or + (char == '[' and stack[-1] == ']') or + (char == '{' and stack[-1] == '}')): + stack.pop() + if not stack: + return (i, None) + else: + # Mismatched parentheses + return (-1, None) + elif char == ';': + # Found something that look like end of statements. If we are currently + # expecting a '<', the matching '>' must have been an operator, since + # template argument list should not contain statements. + while stack and stack[-1] == '>': + stack.pop() + if not stack: + return (-1, None) + + i -= 1 + + return (-1, stack) + + +def ReverseCloseExpression(clean_lines, linenum, pos): + """If input points to ) or } or ] or >, finds the position that opens it. + + If lines[linenum][pos] points to a ')' or '}' or ']' or '>', finds the + linenum/pos that correspond to the opening of the expression. + + Args: + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + pos: A position on the line. + + Returns: + A tuple (line, linenum, pos) pointer *at* the opening brace, or + (line, 0, -1) if we never find the matching opening brace. Note + we ignore strings and comments when matching; and the line we + return is the 'cleansed' line at linenum. + """ + line = clean_lines.elided[linenum] + if line[pos] not in ')}]>': + return (line, 0, -1) + + # Check last line + (start_pos, stack) = FindStartOfExpressionInLine(line, pos, []) + if start_pos > -1: + return (line, linenum, start_pos) + + # Continue scanning backward + while stack and linenum > 0: + linenum -= 1 + line = clean_lines.elided[linenum] + (start_pos, stack) = FindStartOfExpressionInLine(line, len(line) - 1, stack) + if start_pos > -1: + return (line, linenum, start_pos) + + # Did not find start of expression before beginning of file, give up + return (line, 0, -1) + + +def CheckForCopyright(filename, lines, error): + """Logs an error if no Copyright message appears at the top of the file.""" + + # We'll say it should occur by line 10. Don't forget there's a + # dummy line at the front. + for line in xrange(1, min(len(lines), 11)): + if re.search(r'Copyright', lines[line], re.I): break + else: # means no copyright line was found + error(filename, 0, 'legal/copyright', 5, + 'No copyright message found. ' + 'You should have a line: "Copyright [year] "') + + +def GetIndentLevel(line): + """Return the number of leading spaces in line. + + Args: + line: A string to check. + + Returns: + An integer count of leading spaces, possibly zero. + """ + indent = Match(r'^( *)\S', line) + if indent: + return len(indent.group(1)) + else: + return 0 + + +def GetHeaderGuardCPPVariable(filename): + """Returns the CPP variable that should be used as a header guard. + + Args: + filename: The name of a C++ header file. + + Returns: + The CPP variable that should be used as a header guard in the + named file. + + """ + + # Restores original filename in case that cpplint is invoked from Emacs's + # flymake. + filename = re.sub(r'_flymake\.h$', '.h', filename) + filename = re.sub(r'/\.flymake/([^/]*)$', r'/\1', filename) + # Replace 'c++' with 'cpp'. + filename = filename.replace('C++', 'cpp').replace('c++', 'cpp') + + fileinfo = FileInfo(filename) + file_path_from_root = fileinfo.RepositoryName() + if _root: + file_path_from_root = re.sub('^' + _root + os.sep, '', file_path_from_root) + return re.sub(r'[^a-zA-Z0-9]', '_', file_path_from_root).upper() + '_' + + +def CheckForHeaderGuard(filename, clean_lines, error): + """Checks that the file contains a header guard. + + Logs an error if no #ifndef header guard is present. For other + headers, checks that the full pathname is used. + + Args: + filename: The name of the C++ header file. + clean_lines: A CleansedLines instance containing the file. + error: The function to call with any errors found. + """ + + # Don't check for header guards if there are error suppression + # comments somewhere in this file. + # + # Because this is silencing a warning for a nonexistent line, we + # only support the very specific NOLINT(build/header_guard) syntax, + # and not the general NOLINT or NOLINT(*) syntax. + raw_lines = clean_lines.lines_without_raw_strings + for i in raw_lines: + if Search(r'//\s*NOLINT\(build/header_guard\)', i): + return + + cppvar = GetHeaderGuardCPPVariable(filename) + + ifndef = '' + ifndef_linenum = 0 + define = '' + endif = '' + endif_linenum = 0 + for linenum, line in enumerate(raw_lines): + linesplit = line.split() + if len(linesplit) >= 2: + # find the first occurrence of #ifndef and #define, save arg + if not ifndef and linesplit[0] == '#ifndef': + # set ifndef to the header guard presented on the #ifndef line. + ifndef = linesplit[1] + ifndef_linenum = linenum + if not define and linesplit[0] == '#define': + define = linesplit[1] + # find the last occurrence of #endif, save entire line + if line.startswith('#endif'): + endif = line + endif_linenum = linenum + + if not ifndef or not define or ifndef != define: + error(filename, 0, 'build/header_guard', 5, + 'No #ifndef header guard found, suggested CPP variable is: %s' % + cppvar) + return + + # The guard should be PATH_FILE_H_, but we also allow PATH_FILE_H__ + # for backward compatibility. + if ifndef != cppvar: + error_level = 0 + if ifndef != cppvar + '_': + error_level = 5 + + ParseNolintSuppressions(filename, raw_lines[ifndef_linenum], ifndef_linenum, + error) + error(filename, ifndef_linenum, 'build/header_guard', error_level, + '#ifndef header guard has wrong style, please use: %s' % cppvar) + + # Check for "//" comments on endif line. + ParseNolintSuppressions(filename, raw_lines[endif_linenum], endif_linenum, + error) + match = Match(r'#endif\s*//\s*' + cppvar + r'(_)?\b', endif) + if match: + if match.group(1) == '_': + # Issue low severity warning for deprecated double trailing underscore + error(filename, endif_linenum, 'build/header_guard', 0, + '#endif line should be "#endif // %s"' % cppvar) + return + + # Didn't find the corresponding "//" comment. If this file does not + # contain any "//" comments at all, it could be that the compiler + # only wants "/**/" comments, look for those instead. + no_single_line_comments = True + for i in xrange(1, len(raw_lines) - 1): + line = raw_lines[i] + if Match(r'^(?:(?:\'(?:\.|[^\'])*\')|(?:"(?:\.|[^"])*")|[^\'"])*//', line): + no_single_line_comments = False + break + + if no_single_line_comments: + match = Match(r'#endif\s*/\*\s*' + cppvar + r'(_)?\s*\*/', endif) + if match: + if match.group(1) == '_': + # Low severity warning for double trailing underscore + error(filename, endif_linenum, 'build/header_guard', 0, + '#endif line should be "#endif /* %s */"' % cppvar) + return + + # Didn't find anything + error(filename, endif_linenum, 'build/header_guard', 5, + '#endif line should be "#endif // %s"' % cppvar) + + +def CheckHeaderFileIncluded(filename, include_state, error): + """Logs an error if a .cc file does not include its header.""" + + # Do not check test files + if filename.endswith('_test.cc') or filename.endswith('_unittest.cc'): + return + + fileinfo = FileInfo(filename) + headerfile = filename[0:len(filename) - 2] + 'h' + if not os.path.exists(headerfile): + return + headername = FileInfo(headerfile).RepositoryName() + first_include = 0 + for section_list in include_state.include_list: + for f in section_list: + if headername in f[0] or f[0] in headername: + return + if not first_include: + first_include = f[1] + + error(filename, first_include, 'build/include', 5, + '%s should include its header file %s' % (fileinfo.RepositoryName(), + headername)) + + +def CheckForBadCharacters(filename, lines, error): + """Logs an error for each line containing bad characters. + + Two kinds of bad characters: + + 1. Unicode replacement characters: These indicate that either the file + contained invalid UTF-8 (likely) or Unicode replacement characters (which + it shouldn't). Note that it's possible for this to throw off line + numbering if the invalid UTF-8 occurred adjacent to a newline. + + 2. NUL bytes. These are problematic for some tools. + + Args: + filename: The name of the current file. + lines: An array of strings, each representing a line of the file. + error: The function to call with any errors found. + """ + for linenum, line in enumerate(lines): + if u'\ufffd' in line: + error(filename, linenum, 'readability/utf8', 5, + 'Line contains invalid UTF-8 (or Unicode replacement character).') + if '\0' in line: + error(filename, linenum, 'readability/nul', 5, 'Line contains NUL byte.') + + +def CheckForNewlineAtEOF(filename, lines, error): + """Logs an error if there is no newline char at the end of the file. + + Args: + filename: The name of the current file. + lines: An array of strings, each representing a line of the file. + error: The function to call with any errors found. + """ + + # The array lines() was created by adding two newlines to the + # original file (go figure), then splitting on \n. + # To verify that the file ends in \n, we just have to make sure the + # last-but-two element of lines() exists and is empty. + if len(lines) < 3 or lines[-2]: + error(filename, len(lines) - 2, 'whitespace/ending_newline', 5, + 'Could not find a newline character at the end of the file.') + + +def CheckForMultilineCommentsAndStrings(filename, clean_lines, linenum, error): + """Logs an error if we see /* ... */ or "..." that extend past one line. + + /* ... */ comments are legit inside macros, for one line. + Otherwise, we prefer // comments, so it's ok to warn about the + other. Likewise, it's ok for strings to extend across multiple + lines, as long as a line continuation character (backslash) + terminates each line. Although not currently prohibited by the C++ + style guide, it's ugly and unnecessary. We don't do well with either + in this lint program, so we warn about both. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + error: The function to call with any errors found. + """ + line = clean_lines.elided[linenum] + + # Remove all \\ (escaped backslashes) from the line. They are OK, and the + # second (escaped) slash may trigger later \" detection erroneously. + line = line.replace('\\\\', '') + + if line.count('/*') > line.count('*/'): + error(filename, linenum, 'readability/multiline_comment', 5, + 'Complex multi-line /*...*/-style comment found. ' + 'Lint may give bogus warnings. ' + 'Consider replacing these with //-style comments, ' + 'with #if 0...#endif, ' + 'or with more clearly structured multi-line comments.') + + if (line.count('"') - line.count('\\"')) % 2: + error(filename, linenum, 'readability/multiline_string', 5, + 'Multi-line string ("...") found. This lint script doesn\'t ' + 'do well with such strings, and may give bogus warnings. ' + 'Use C++11 raw strings or concatenation instead.') + + +# (non-threadsafe name, thread-safe alternative, validation pattern) +# +# The validation pattern is used to eliminate false positives such as: +# _rand(); // false positive due to substring match. +# ->rand(); // some member function rand(). +# ACMRandom rand(seed); // some variable named rand. +# ISAACRandom rand(); // another variable named rand. +# +# Basically we require the return value of these functions to be used +# in some expression context on the same line by matching on some +# operator before the function name. This eliminates constructors and +# member function calls. +_UNSAFE_FUNC_PREFIX = r'(?:[-+*/=%^&|(<]\s*|>\s+)' +_THREADING_LIST = ( + ('asctime(', 'asctime_r(', _UNSAFE_FUNC_PREFIX + r'asctime\([^)]+\)'), + ('ctime(', 'ctime_r(', _UNSAFE_FUNC_PREFIX + r'ctime\([^)]+\)'), + ('getgrgid(', 'getgrgid_r(', _UNSAFE_FUNC_PREFIX + r'getgrgid\([^)]+\)'), + ('getgrnam(', 'getgrnam_r(', _UNSAFE_FUNC_PREFIX + r'getgrnam\([^)]+\)'), + ('getlogin(', 'getlogin_r(', _UNSAFE_FUNC_PREFIX + r'getlogin\(\)'), + ('getpwnam(', 'getpwnam_r(', _UNSAFE_FUNC_PREFIX + r'getpwnam\([^)]+\)'), + ('getpwuid(', 'getpwuid_r(', _UNSAFE_FUNC_PREFIX + r'getpwuid\([^)]+\)'), + ('gmtime(', 'gmtime_r(', _UNSAFE_FUNC_PREFIX + r'gmtime\([^)]+\)'), + ('localtime(', 'localtime_r(', _UNSAFE_FUNC_PREFIX + r'localtime\([^)]+\)'), + ('rand(', 'rand_r(', _UNSAFE_FUNC_PREFIX + r'rand\(\)'), + ('strtok(', 'strtok_r(', + _UNSAFE_FUNC_PREFIX + r'strtok\([^)]+\)'), + ('ttyname(', 'ttyname_r(', _UNSAFE_FUNC_PREFIX + r'ttyname\([^)]+\)'), + ) + + +def CheckPosixThreading(filename, clean_lines, linenum, error): + """Checks for calls to thread-unsafe functions. + + Much code has been originally written without consideration of + multi-threading. Also, engineers are relying on their old experience; + they have learned posix before threading extensions were added. These + tests guide the engineers to use thread-safe functions (when using + posix directly). + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + error: The function to call with any errors found. + """ + line = clean_lines.elided[linenum] + for single_thread_func, multithread_safe_func, pattern in _THREADING_LIST: + # Additional pattern matching check to confirm that this is the + # function we are looking for + if Search(pattern, line): + error(filename, linenum, 'runtime/threadsafe_fn', 2, + 'Consider using ' + multithread_safe_func + + '...) instead of ' + single_thread_func + + '...) for improved thread safety.') + + +def CheckVlogArguments(filename, clean_lines, linenum, error): + """Checks that VLOG() is only used for defining a logging level. + + For example, VLOG(2) is correct. VLOG(INFO), VLOG(WARNING), VLOG(ERROR), and + VLOG(FATAL) are not. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + error: The function to call with any errors found. + """ + line = clean_lines.elided[linenum] + if Search(r'\bVLOG\((INFO|ERROR|WARNING|DFATAL|FATAL)\)', line): + error(filename, linenum, 'runtime/vlog', 5, + 'VLOG() should be used with numeric verbosity level. ' + 'Use LOG() if you want symbolic severity levels.') + +# Matches invalid increment: *count++, which moves pointer instead of +# incrementing a value. +_RE_PATTERN_INVALID_INCREMENT = re.compile( + r'^\s*\*\w+(\+\+|--);') + + +def CheckInvalidIncrement(filename, clean_lines, linenum, error): + """Checks for invalid increment *count++. + + For example following function: + void increment_counter(int* count) { + *count++; + } + is invalid, because it effectively does count++, moving pointer, and should + be replaced with ++*count, (*count)++ or *count += 1. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + error: The function to call with any errors found. + """ + line = clean_lines.elided[linenum] + if _RE_PATTERN_INVALID_INCREMENT.match(line): + error(filename, linenum, 'runtime/invalid_increment', 5, + 'Changing pointer instead of value (or unused value of operator*).') + + +def IsMacroDefinition(clean_lines, linenum): + if Search(r'^#define', clean_lines[linenum]): + return True + + if linenum > 0 and Search(r'\\$', clean_lines[linenum - 1]): + return True + + return False + + +def IsForwardClassDeclaration(clean_lines, linenum): + return Match(r'^\s*(\btemplate\b)*.*class\s+\w+;\s*$', clean_lines[linenum]) + + +class _BlockInfo(object): + """Stores information about a generic block of code.""" + + def __init__(self, seen_open_brace): + self.seen_open_brace = seen_open_brace + self.open_parentheses = 0 + self.inline_asm = _NO_ASM + self.check_namespace_indentation = False + + def CheckBegin(self, filename, clean_lines, linenum, error): + """Run checks that applies to text up to the opening brace. + + This is mostly for checking the text after the class identifier + and the "{", usually where the base class is specified. For other + blocks, there isn't much to check, so we always pass. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + error: The function to call with any errors found. + """ + pass + + def CheckEnd(self, filename, clean_lines, linenum, error): + """Run checks that applies to text after the closing brace. + + This is mostly used for checking end of namespace comments. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + error: The function to call with any errors found. + """ + pass + + def IsBlockInfo(self): + """Returns true if this block is a _BlockInfo. + + This is convenient for verifying that an object is an instance of + a _BlockInfo, but not an instance of any of the derived classes. + + Returns: + True for this class, False for derived classes. + """ + return self.__class__ == _BlockInfo + + +class _ExternCInfo(_BlockInfo): + """Stores information about an 'extern "C"' block.""" + + def __init__(self): + _BlockInfo.__init__(self, True) + + +class _ClassInfo(_BlockInfo): + """Stores information about a class.""" + + def __init__(self, name, class_or_struct, clean_lines, linenum): + _BlockInfo.__init__(self, False) + self.name = name + self.starting_linenum = linenum + self.is_derived = False + self.check_namespace_indentation = True + if class_or_struct == 'struct': + self.access = 'public' + self.is_struct = True + else: + self.access = 'private' + self.is_struct = False + + # Remember initial indentation level for this class. Using raw_lines here + # instead of elided to account for leading comments. + self.class_indent = GetIndentLevel(clean_lines.raw_lines[linenum]) + + # Try to find the end of the class. This will be confused by things like: + # class A { + # } *x = { ... + # + # But it's still good enough for CheckSectionSpacing. + self.last_line = 0 + depth = 0 + for i in range(linenum, clean_lines.NumLines()): + line = clean_lines.elided[i] + depth += line.count('{') - line.count('}') + if not depth: + self.last_line = i + break + + def CheckBegin(self, filename, clean_lines, linenum, error): + # Look for a bare ':' + if Search('(^|[^:]):($|[^:])', clean_lines.elided[linenum]): + self.is_derived = True + + def CheckEnd(self, filename, clean_lines, linenum, error): + # If there is a DISALLOW macro, it should appear near the end of + # the class. + seen_last_thing_in_class = False + for i in xrange(linenum - 1, self.starting_linenum, -1): + match = Search( + r'\b(DISALLOW_COPY_AND_ASSIGN|DISALLOW_IMPLICIT_CONSTRUCTORS)\(' + + self.name + r'\)', + clean_lines.elided[i]) + if match: + if seen_last_thing_in_class: + error(filename, i, 'readability/constructors', 3, + match.group(1) + ' should be the last thing in the class') + break + + if not Match(r'^\s*$', clean_lines.elided[i]): + seen_last_thing_in_class = True + + # Check that closing brace is aligned with beginning of the class. + # Only do this if the closing brace is indented by only whitespaces. + # This means we will not check single-line class definitions. + indent = Match(r'^( *)\}', clean_lines.elided[linenum]) + if indent and len(indent.group(1)) != self.class_indent: + if self.is_struct: + parent = 'struct ' + self.name + else: + parent = 'class ' + self.name + error(filename, linenum, 'whitespace/indent', 3, + 'Closing brace should be aligned with beginning of %s' % parent) + + +class _NamespaceInfo(_BlockInfo): + """Stores information about a namespace.""" + + def __init__(self, name, linenum): + _BlockInfo.__init__(self, False) + self.name = name or '' + self.starting_linenum = linenum + self.check_namespace_indentation = True + + def CheckEnd(self, filename, clean_lines, linenum, error): + """Check end of namespace comments.""" + line = clean_lines.raw_lines[linenum] + + # Check how many lines is enclosed in this namespace. Don't issue + # warning for missing namespace comments if there aren't enough + # lines. However, do apply checks if there is already an end of + # namespace comment and it's incorrect. + # + # TODO(unknown): We always want to check end of namespace comments + # if a namespace is large, but sometimes we also want to apply the + # check if a short namespace contained nontrivial things (something + # other than forward declarations). There is currently no logic on + # deciding what these nontrivial things are, so this check is + # triggered by namespace size only, which works most of the time. + if (linenum - self.starting_linenum < 10 + and not Match(r'};*\s*(//|/\*).*\bnamespace\b', line)): + return + + # Look for matching comment at end of namespace. + # + # Note that we accept C style "/* */" comments for terminating + # namespaces, so that code that terminate namespaces inside + # preprocessor macros can be cpplint clean. + # + # We also accept stuff like "// end of namespace ." with the + # period at the end. + # + # Besides these, we don't accept anything else, otherwise we might + # get false negatives when existing comment is a substring of the + # expected namespace. + if self.name: + # Named namespace + if not Match((r'};*\s*(//|/\*).*\bnamespace\s+' + re.escape(self.name) + + r'[\*/\.\\\s]*$'), + line): + error(filename, linenum, 'readability/namespace', 5, + 'Namespace should be terminated with "// namespace %s"' % + self.name) + else: + # Anonymous namespace + if not Match(r'};*\s*(//|/\*).*\bnamespace[\*/\.\\\s]*$', line): + # If "// namespace anonymous" or "// anonymous namespace (more text)", + # mention "// anonymous namespace" as an acceptable form + if Match(r'}.*\b(namespace anonymous|anonymous namespace)\b', line): + error(filename, linenum, 'readability/namespace', 5, + 'Anonymous namespace should be terminated with "// namespace"' + ' or "// anonymous namespace"') + else: + error(filename, linenum, 'readability/namespace', 5, + 'Anonymous namespace should be terminated with "// namespace"') + + +class _PreprocessorInfo(object): + """Stores checkpoints of nesting stacks when #if/#else is seen.""" + + def __init__(self, stack_before_if): + # The entire nesting stack before #if + self.stack_before_if = stack_before_if + + # The entire nesting stack up to #else + self.stack_before_else = [] + + # Whether we have already seen #else or #elif + self.seen_else = False + + +class NestingState(object): + """Holds states related to parsing braces.""" + + def __init__(self): + # Stack for tracking all braces. An object is pushed whenever we + # see a "{", and popped when we see a "}". Only 3 types of + # objects are possible: + # - _ClassInfo: a class or struct. + # - _NamespaceInfo: a namespace. + # - _BlockInfo: some other type of block. + self.stack = [] + + # Top of the previous stack before each Update(). + # + # Because the nesting_stack is updated at the end of each line, we + # had to do some convoluted checks to find out what is the current + # scope at the beginning of the line. This check is simplified by + # saving the previous top of nesting stack. + # + # We could save the full stack, but we only need the top. Copying + # the full nesting stack would slow down cpplint by ~10%. + self.previous_stack_top = [] + + # Stack of _PreprocessorInfo objects. + self.pp_stack = [] + + def SeenOpenBrace(self): + """Check if we have seen the opening brace for the innermost block. + + Returns: + True if we have seen the opening brace, False if the innermost + block is still expecting an opening brace. + """ + return (not self.stack) or self.stack[-1].seen_open_brace + + def InNamespaceBody(self): + """Check if we are currently one level inside a namespace body. + + Returns: + True if top of the stack is a namespace block, False otherwise. + """ + return self.stack and isinstance(self.stack[-1], _NamespaceInfo) + + def InExternC(self): + """Check if we are currently one level inside an 'extern "C"' block. + + Returns: + True if top of the stack is an extern block, False otherwise. + """ + return self.stack and isinstance(self.stack[-1], _ExternCInfo) + + def InClassDeclaration(self): + """Check if we are currently one level inside a class or struct declaration. + + Returns: + True if top of the stack is a class/struct, False otherwise. + """ + return self.stack and isinstance(self.stack[-1], _ClassInfo) + + def InAsmBlock(self): + """Check if we are currently one level inside an inline ASM block. + + Returns: + True if the top of the stack is a block containing inline ASM. + """ + return self.stack and self.stack[-1].inline_asm != _NO_ASM + + def InTemplateArgumentList(self, clean_lines, linenum, pos): + """Check if current position is inside template argument list. + + Args: + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + pos: position just after the suspected template argument. + Returns: + True if (linenum, pos) is inside template arguments. + """ + while linenum < clean_lines.NumLines(): + # Find the earliest character that might indicate a template argument + line = clean_lines.elided[linenum] + match = Match(r'^[^{};=\[\]\.<>]*(.)', line[pos:]) + if not match: + linenum += 1 + pos = 0 + continue + token = match.group(1) + pos += len(match.group(0)) + + # These things do not look like template argument list: + # class Suspect { + # class Suspect x; } + if token in ('{', '}', ';'): return False + + # These things look like template argument list: + # template + # template + # template + # template + if token in ('>', '=', '[', ']', '.'): return True + + # Check if token is an unmatched '<'. + # If not, move on to the next character. + if token != '<': + pos += 1 + if pos >= len(line): + linenum += 1 + pos = 0 + continue + + # We can't be sure if we just find a single '<', and need to + # find the matching '>'. + (_, end_line, end_pos) = CloseExpression(clean_lines, linenum, pos - 1) + if end_pos < 0: + # Not sure if template argument list or syntax error in file + return False + linenum = end_line + pos = end_pos + return False + + def UpdatePreprocessor(self, line): + """Update preprocessor stack. + + We need to handle preprocessors due to classes like this: + #ifdef SWIG + struct ResultDetailsPageElementExtensionPoint { + #else + struct ResultDetailsPageElementExtensionPoint : public Extension { + #endif + + We make the following assumptions (good enough for most files): + - Preprocessor condition evaluates to true from #if up to first + #else/#elif/#endif. + + - Preprocessor condition evaluates to false from #else/#elif up + to #endif. We still perform lint checks on these lines, but + these do not affect nesting stack. + + Args: + line: current line to check. + """ + if Match(r'^\s*#\s*(if|ifdef|ifndef)\b', line): + # Beginning of #if block, save the nesting stack here. The saved + # stack will allow us to restore the parsing state in the #else case. + self.pp_stack.append(_PreprocessorInfo(copy.deepcopy(self.stack))) + elif Match(r'^\s*#\s*(else|elif)\b', line): + # Beginning of #else block + if self.pp_stack: + if not self.pp_stack[-1].seen_else: + # This is the first #else or #elif block. Remember the + # whole nesting stack up to this point. This is what we + # keep after the #endif. + self.pp_stack[-1].seen_else = True + self.pp_stack[-1].stack_before_else = copy.deepcopy(self.stack) + + # Restore the stack to how it was before the #if + self.stack = copy.deepcopy(self.pp_stack[-1].stack_before_if) + else: + # TODO(unknown): unexpected #else, issue warning? + pass + elif Match(r'^\s*#\s*endif\b', line): + # End of #if or #else blocks. + if self.pp_stack: + # If we saw an #else, we will need to restore the nesting + # stack to its former state before the #else, otherwise we + # will just continue from where we left off. + if self.pp_stack[-1].seen_else: + # Here we can just use a shallow copy since we are the last + # reference to it. + self.stack = self.pp_stack[-1].stack_before_else + # Drop the corresponding #if + self.pp_stack.pop() + else: + # TODO(unknown): unexpected #endif, issue warning? + pass + + # TODO(unknown): Update() is too long, but we will refactor later. + def Update(self, filename, clean_lines, linenum, error): + """Update nesting state with current line. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + error: The function to call with any errors found. + """ + line = clean_lines.elided[linenum] + + # Remember top of the previous nesting stack. + # + # The stack is always pushed/popped and not modified in place, so + # we can just do a shallow copy instead of copy.deepcopy. Using + # deepcopy would slow down cpplint by ~28%. + if self.stack: + self.previous_stack_top = self.stack[-1] + else: + self.previous_stack_top = None + + # Update pp_stack + self.UpdatePreprocessor(line) + + # Count parentheses. This is to avoid adding struct arguments to + # the nesting stack. + if self.stack: + inner_block = self.stack[-1] + depth_change = line.count('(') - line.count(')') + inner_block.open_parentheses += depth_change + + # Also check if we are starting or ending an inline assembly block. + if inner_block.inline_asm in (_NO_ASM, _END_ASM): + if (depth_change != 0 and + inner_block.open_parentheses == 1 and + _MATCH_ASM.match(line)): + # Enter assembly block + inner_block.inline_asm = _INSIDE_ASM + else: + # Not entering assembly block. If previous line was _END_ASM, + # we will now shift to _NO_ASM state. + inner_block.inline_asm = _NO_ASM + elif (inner_block.inline_asm == _INSIDE_ASM and + inner_block.open_parentheses == 0): + # Exit assembly block + inner_block.inline_asm = _END_ASM + + # Consume namespace declaration at the beginning of the line. Do + # this in a loop so that we catch same line declarations like this: + # namespace proto2 { namespace bridge { class MessageSet; } } + while True: + # Match start of namespace. The "\b\s*" below catches namespace + # declarations even if it weren't followed by a whitespace, this + # is so that we don't confuse our namespace checker. The + # missing spaces will be flagged by CheckSpacing. + namespace_decl_match = Match(r'^\s*namespace\b\s*([:\w]+)?(.*)$', line) + if not namespace_decl_match: + break + + new_namespace = _NamespaceInfo(namespace_decl_match.group(1), linenum) + self.stack.append(new_namespace) + + line = namespace_decl_match.group(2) + if line.find('{') != -1: + new_namespace.seen_open_brace = True + line = line[line.find('{') + 1:] + + # Look for a class declaration in whatever is left of the line + # after parsing namespaces. The regexp accounts for decorated classes + # such as in: + # class LOCKABLE API Object { + # }; + class_decl_match = Match( + r'^(\s*(?:template\s*<[\w\s<>,:]*>\s*)?' + r'(class|struct)\s+(?:[A-Z_]+\s+)*(\w+(?:::\w+)*))' + r'(.*)$', line) + if (class_decl_match and + (not self.stack or self.stack[-1].open_parentheses == 0)): + # We do not want to accept classes that are actually template arguments: + # template , + # template class Ignore3> + # void Function() {}; + # + # To avoid template argument cases, we scan forward and look for + # an unmatched '>'. If we see one, assume we are inside a + # template argument list. + end_declaration = len(class_decl_match.group(1)) + if not self.InTemplateArgumentList(clean_lines, linenum, end_declaration): + self.stack.append(_ClassInfo( + class_decl_match.group(3), class_decl_match.group(2), + clean_lines, linenum)) + line = class_decl_match.group(4) + + # If we have not yet seen the opening brace for the innermost block, + # run checks here. + if not self.SeenOpenBrace(): + self.stack[-1].CheckBegin(filename, clean_lines, linenum, error) + + # Update access control if we are inside a class/struct + if self.stack and isinstance(self.stack[-1], _ClassInfo): + classinfo = self.stack[-1] + access_match = Match( + r'^(.*)\b(public|private|protected|signals)(\s+(?:slots\s*)?)?' + r':(?:[^:]|$)', + line) + if access_match: + classinfo.access = access_match.group(2) + + # Check that access keywords are indented +1 space. Skip this + # check if the keywords are not preceded by whitespaces. + indent = access_match.group(1) + if (len(indent) != classinfo.class_indent + 1 and + Match(r'^\s*$', indent)): + if classinfo.is_struct: + parent = 'struct ' + classinfo.name + else: + parent = 'class ' + classinfo.name + slots = '' + if access_match.group(3): + slots = access_match.group(3) + error(filename, linenum, 'whitespace/indent', 3, + '%s%s: should be indented +1 space inside %s' % ( + access_match.group(2), slots, parent)) + + # Consume braces or semicolons from what's left of the line + while True: + # Match first brace, semicolon, or closed parenthesis. + matched = Match(r'^[^{;)}]*([{;)}])(.*)$', line) + if not matched: + break + + token = matched.group(1) + if token == '{': + # If namespace or class hasn't seen a opening brace yet, mark + # namespace/class head as complete. Push a new block onto the + # stack otherwise. + if not self.SeenOpenBrace(): + self.stack[-1].seen_open_brace = True + elif Match(r'^extern\s*"[^"]*"\s*\{', line): + self.stack.append(_ExternCInfo()) + else: + self.stack.append(_BlockInfo(True)) + if _MATCH_ASM.match(line): + self.stack[-1].inline_asm = _BLOCK_ASM + + elif token == ';' or token == ')': + # If we haven't seen an opening brace yet, but we already saw + # a semicolon, this is probably a forward declaration. Pop + # the stack for these. + # + # Similarly, if we haven't seen an opening brace yet, but we + # already saw a closing parenthesis, then these are probably + # function arguments with extra "class" or "struct" keywords. + # Also pop these stack for these. + if not self.SeenOpenBrace(): + self.stack.pop() + else: # token == '}' + # Perform end of block checks and pop the stack. + if self.stack: + self.stack[-1].CheckEnd(filename, clean_lines, linenum, error) + self.stack.pop() + line = matched.group(2) + + def InnermostClass(self): + """Get class info on the top of the stack. + + Returns: + A _ClassInfo object if we are inside a class, or None otherwise. + """ + for i in range(len(self.stack), 0, -1): + classinfo = self.stack[i - 1] + if isinstance(classinfo, _ClassInfo): + return classinfo + return None + + def CheckCompletedBlocks(self, filename, error): + """Checks that all classes and namespaces have been completely parsed. + + Call this when all lines in a file have been processed. + Args: + filename: The name of the current file. + error: The function to call with any errors found. + """ + # Note: This test can result in false positives if #ifdef constructs + # get in the way of brace matching. See the testBuildClass test in + # cpplint_unittest.py for an example of this. + for obj in self.stack: + if isinstance(obj, _ClassInfo): + error(filename, obj.starting_linenum, 'build/class', 5, + 'Failed to find complete declaration of class %s' % + obj.name) + elif isinstance(obj, _NamespaceInfo): + error(filename, obj.starting_linenum, 'build/namespaces', 5, + 'Failed to find complete declaration of namespace %s' % + obj.name) + + +def CheckForNonStandardConstructs(filename, clean_lines, linenum, + nesting_state, error): + r"""Logs an error if we see certain non-ANSI constructs ignored by gcc-2. + + Complain about several constructs which gcc-2 accepts, but which are + not standard C++. Warning about these in lint is one way to ease the + transition to new compilers. + - put storage class first (e.g. "static const" instead of "const static"). + - "%lld" instead of %qd" in printf-type functions. + - "%1$d" is non-standard in printf-type functions. + - "\%" is an undefined character escape sequence. + - text after #endif is not allowed. + - invalid inner-style forward declaration. + - >? and ?= and )\?=?\s*(\w+|[+-]?\d+)(\.\d*)?', + line): + error(filename, linenum, 'build/deprecated', 3, + '>? and ))?' + # r'\s*const\s*' + type_name + '\s*&\s*\w+\s*;' + error(filename, linenum, 'runtime/member_string_references', 2, + 'const string& members are dangerous. It is much better to use ' + 'alternatives, such as pointers or simple constants.') + + # Everything else in this function operates on class declarations. + # Return early if the top of the nesting stack is not a class, or if + # the class head is not completed yet. + classinfo = nesting_state.InnermostClass() + if not classinfo or not classinfo.seen_open_brace: + return + + # The class may have been declared with namespace or classname qualifiers. + # The constructor and destructor will not have those qualifiers. + base_classname = classinfo.name.split('::')[-1] + + # Look for single-argument constructors that aren't marked explicit. + # Technically a valid construct, but against style. Also look for + # non-single-argument constructors which are also technically valid, but + # strongly suggest something is wrong. + explicit_constructor_match = Match( + r'\s+(?:inline\s+)?(explicit\s+)?(?:inline\s+)?%s\s*' + r'\(((?:[^()]|\([^()]*\))*)\)' + % re.escape(base_classname), + line) + + if explicit_constructor_match: + is_marked_explicit = explicit_constructor_match.group(1) + + if not explicit_constructor_match.group(2): + constructor_args = [] + else: + constructor_args = explicit_constructor_match.group(2).split(',') + + # collapse arguments so that commas in template parameter lists and function + # argument parameter lists don't split arguments in two + i = 0 + while i < len(constructor_args): + constructor_arg = constructor_args[i] + while (constructor_arg.count('<') > constructor_arg.count('>') or + constructor_arg.count('(') > constructor_arg.count(')')): + constructor_arg += ',' + constructor_args[i + 1] + del constructor_args[i + 1] + constructor_args[i] = constructor_arg + i += 1 + + defaulted_args = [arg for arg in constructor_args if '=' in arg] + noarg_constructor = (not constructor_args or # empty arg list + # 'void' arg specifier + (len(constructor_args) == 1 and + constructor_args[0].strip() == 'void')) + onearg_constructor = ((len(constructor_args) == 1 and # exactly one arg + not noarg_constructor) or + # all but at most one arg defaulted + (len(constructor_args) >= 1 and + not noarg_constructor and + len(defaulted_args) >= len(constructor_args) - 1)) + initializer_list_constructor = bool( + onearg_constructor and + Search(r'\bstd\s*::\s*initializer_list\b', constructor_args[0])) + copy_constructor = bool( + onearg_constructor and + Match(r'(const\s+)?%s(\s*<[^>]*>)?(\s+const)?\s*(?:<\w+>\s*)?&' + % re.escape(base_classname), constructor_args[0].strip())) + + if (not is_marked_explicit and + onearg_constructor and + not initializer_list_constructor and + not copy_constructor): + if defaulted_args: + error(filename, linenum, 'runtime/explicit', 5, + 'Constructors callable with one argument ' + 'should be marked explicit.') + else: + error(filename, linenum, 'runtime/explicit', 5, + 'Single-parameter constructors should be marked explicit.') + elif is_marked_explicit and not onearg_constructor: + if noarg_constructor: + error(filename, linenum, 'runtime/explicit', 5, + 'Zero-parameter constructors should not be marked explicit.') + else: + error(filename, linenum, 'runtime/explicit', 0, + 'Constructors that require multiple arguments ' + 'should not be marked explicit.') + + +def CheckSpacingForFunctionCall(filename, clean_lines, linenum, error): + """Checks for the correctness of various spacing around function calls. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + error: The function to call with any errors found. + """ + line = clean_lines.elided[linenum] + + # Since function calls often occur inside if/for/while/switch + # expressions - which have their own, more liberal conventions - we + # first see if we should be looking inside such an expression for a + # function call, to which we can apply more strict standards. + fncall = line # if there's no control flow construct, look at whole line + for pattern in (r'\bif\s*\((.*)\)\s*{', + r'\bfor\s*\((.*)\)\s*{', + r'\bwhile\s*\((.*)\)\s*[{;]', + r'\bswitch\s*\((.*)\)\s*{'): + match = Search(pattern, line) + if match: + fncall = match.group(1) # look inside the parens for function calls + break + + # Except in if/for/while/switch, there should never be space + # immediately inside parens (eg "f( 3, 4 )"). We make an exception + # for nested parens ( (a+b) + c ). Likewise, there should never be + # a space before a ( when it's a function argument. I assume it's a + # function argument when the char before the whitespace is legal in + # a function name (alnum + _) and we're not starting a macro. Also ignore + # pointers and references to arrays and functions coz they're too tricky: + # we use a very simple way to recognize these: + # " (something)(maybe-something)" or + # " (something)(maybe-something," or + # " (something)[something]" + # Note that we assume the contents of [] to be short enough that + # they'll never need to wrap. + if ( # Ignore control structures. + not Search(r'\b(if|for|while|switch|return|new|delete|catch|sizeof)\b', + fncall) and + # Ignore pointers/references to functions. + not Search(r' \([^)]+\)\([^)]*(\)|,$)', fncall) and + # Ignore pointers/references to arrays. + not Search(r' \([^)]+\)\[[^\]]+\]', fncall)): + if Search(r'\w\s*\(\s(?!\s*\\$)', fncall): # a ( used for a fn call + error(filename, linenum, 'whitespace/parens', 4, + 'Extra space after ( in function call') + elif Search(r'\(\s+(?!(\s*\\)|\()', fncall): + error(filename, linenum, 'whitespace/parens', 2, + 'Extra space after (') + if (Search(r'\w\s+\(', fncall) and + not Search(r'#\s*define|typedef|using\s+\w+\s*=', fncall) and + not Search(r'\w\s+\((\w+::)*\*\w+\)\(', fncall) and + not Search(r'\bcase\s+\(', fncall)): + # TODO(unknown): Space after an operator function seem to be a common + # error, silence those for now by restricting them to highest verbosity. + if Search(r'\boperator_*\b', line): + error(filename, linenum, 'whitespace/parens', 0, + 'Extra space before ( in function call') + else: + error(filename, linenum, 'whitespace/parens', 4, + 'Extra space before ( in function call') + # If the ) is followed only by a newline or a { + newline, assume it's + # part of a control statement (if/while/etc), and don't complain + if Search(r'[^)]\s+\)\s*[^{\s]', fncall): + # If the closing parenthesis is preceded by only whitespaces, + # try to give a more descriptive error message. + if Search(r'^\s+\)', fncall): + error(filename, linenum, 'whitespace/parens', 2, + 'Closing ) should be moved to the previous line') + else: + error(filename, linenum, 'whitespace/parens', 2, + 'Extra space before )') + + +def IsBlankLine(line): + """Returns true if the given line is blank. + + We consider a line to be blank if the line is empty or consists of + only white spaces. + + Args: + line: A line of a string. + + Returns: + True, if the given line is blank. + """ + return not line or line.isspace() + + +def CheckForNamespaceIndentation(filename, nesting_state, clean_lines, line, + error): + is_namespace_indent_item = ( + len(nesting_state.stack) > 1 and + nesting_state.stack[-1].check_namespace_indentation and + isinstance(nesting_state.previous_stack_top, _NamespaceInfo) and + nesting_state.previous_stack_top == nesting_state.stack[-2]) + + if ShouldCheckNamespaceIndentation(nesting_state, is_namespace_indent_item, + clean_lines.elided, line): + CheckItemIndentationInNamespace(filename, clean_lines.elided, + line, error) + + +def CheckForFunctionLengths(filename, clean_lines, linenum, + function_state, error): + """Reports for long function bodies. + + For an overview why this is done, see: + http://google-styleguide.googlecode.com/svn/trunk/cppguide.xml#Write_Short_Functions + + Uses a simplistic algorithm assuming other style guidelines + (especially spacing) are followed. + Only checks unindented functions, so class members are unchecked. + Trivial bodies are unchecked, so constructors with huge initializer lists + may be missed. + Blank/comment lines are not counted so as to avoid encouraging the removal + of vertical space and comments just to get through a lint check. + NOLINT *on the last line of a function* disables this check. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + function_state: Current function name and lines in body so far. + error: The function to call with any errors found. + """ + lines = clean_lines.lines + line = lines[linenum] + joined_line = '' + + starting_func = False + regexp = r'(\w(\w|::|\*|\&|\s)*)\(' # decls * & space::name( ... + match_result = Match(regexp, line) + if match_result: + # If the name is all caps and underscores, figure it's a macro and + # ignore it, unless it's TEST or TEST_F. + function_name = match_result.group(1).split()[-1] + if function_name == 'TEST' or function_name == 'TEST_F' or ( + not Match(r'[A-Z_]+$', function_name)): + starting_func = True + + if starting_func: + body_found = False + for start_linenum in xrange(linenum, clean_lines.NumLines()): + start_line = lines[start_linenum] + joined_line += ' ' + start_line.lstrip() + if Search(r'(;|})', start_line): # Declarations and trivial functions + body_found = True + break # ... ignore + elif Search(r'{', start_line): + body_found = True + function = Search(r'((\w|:)*)\(', line).group(1) + if Match(r'TEST', function): # Handle TEST... macros + parameter_regexp = Search(r'(\(.*\))', joined_line) + if parameter_regexp: # Ignore bad syntax + function += parameter_regexp.group(1) + else: + function += '()' + function_state.Begin(function) + break + if not body_found: + # No body for the function (or evidence of a non-function) was found. + error(filename, linenum, 'readability/fn_size', 5, + 'Lint failed to find start of function body.') + elif Match(r'^\}\s*$', line): # function end + function_state.Check(error, filename, linenum) + function_state.End() + elif not Match(r'^\s*$', line): + function_state.Count() # Count non-blank/non-comment lines. + + +_RE_PATTERN_TODO = re.compile(r'^//(\s*)TODO(\(.+?\))?:?(\s|$)?') + + +def CheckComment(line, filename, linenum, next_line_start, error): + """Checks for common mistakes in comments. + + Args: + line: The line in question. + filename: The name of the current file. + linenum: The number of the line to check. + next_line_start: The first non-whitespace column of the next line. + error: The function to call with any errors found. + """ + commentpos = line.find('//') + if commentpos != -1: + # Check if the // may be in quotes. If so, ignore it + # Comparisons made explicit for clarity -- pylint: disable=g-explicit-bool-comparison + if (line.count('"', 0, commentpos) - + line.count('\\"', 0, commentpos)) % 2 == 0: # not in quotes + # Allow one space for new scopes, two spaces otherwise: + if (not (Match(r'^.*{ *//', line) and next_line_start == commentpos) and + ((commentpos >= 1 and + line[commentpos-1] not in string.whitespace) or + (commentpos >= 2 and + line[commentpos-2] not in string.whitespace))): + error(filename, linenum, 'whitespace/comments', 2, + 'At least two spaces is best between code and comments') + + # Checks for common mistakes in TODO comments. + comment = line[commentpos:] + match = _RE_PATTERN_TODO.match(comment) + if match: + # One whitespace is correct; zero whitespace is handled elsewhere. + leading_whitespace = match.group(1) + if len(leading_whitespace) > 1: + error(filename, linenum, 'whitespace/todo', 2, + 'Too many spaces before TODO') + + username = match.group(2) + if not username: + error(filename, linenum, 'readability/todo', 2, + 'Missing username in TODO; it should look like ' + '"// TODO(my_username): Stuff."') + + middle_whitespace = match.group(3) + # Comparisons made explicit for correctness -- pylint: disable=g-explicit-bool-comparison + if middle_whitespace != ' ' and middle_whitespace != '': + error(filename, linenum, 'whitespace/todo', 2, + 'TODO(my_username) should be followed by a space') + + # If the comment contains an alphanumeric character, there + # should be a space somewhere between it and the // unless + # it's a /// or //! Doxygen comment. + if (Match(r'//[^ ]*\w', comment) and + not Match(r'(///|//\!)(\s+|$)', comment)): + error(filename, linenum, 'whitespace/comments', 4, + 'Should have a space between // and comment') + + +def CheckAccess(filename, clean_lines, linenum, nesting_state, error): + """Checks for improper use of DISALLOW* macros. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + nesting_state: A NestingState instance which maintains information about + the current stack of nested blocks being parsed. + error: The function to call with any errors found. + """ + line = clean_lines.elided[linenum] # get rid of comments and strings + + matched = Match((r'\s*(DISALLOW_COPY_AND_ASSIGN|' + r'DISALLOW_IMPLICIT_CONSTRUCTORS)'), line) + if not matched: + return + if nesting_state.stack and isinstance(nesting_state.stack[-1], _ClassInfo): + if nesting_state.stack[-1].access != 'private': + error(filename, linenum, 'readability/constructors', 3, + '%s must be in the private: section' % matched.group(1)) + + else: + # Found DISALLOW* macro outside a class declaration, or perhaps it + # was used inside a function when it should have been part of the + # class declaration. We could issue a warning here, but it + # probably resulted in a compiler error already. + pass + + +def CheckSpacing(filename, clean_lines, linenum, nesting_state, error): + """Checks for the correctness of various spacing issues in the code. + + Things we check for: spaces around operators, spaces after + if/for/while/switch, no spaces around parens in function calls, two + spaces between code and comment, don't start a block with a blank + line, don't end a function with a blank line, don't add a blank line + after public/protected/private, don't have too many blank lines in a row. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + nesting_state: A NestingState instance which maintains information about + the current stack of nested blocks being parsed. + error: The function to call with any errors found. + """ + + # Don't use "elided" lines here, otherwise we can't check commented lines. + # Don't want to use "raw" either, because we don't want to check inside C++11 + # raw strings, + raw = clean_lines.lines_without_raw_strings + line = raw[linenum] + + # Before nixing comments, check if the line is blank for no good + # reason. This includes the first line after a block is opened, and + # blank lines at the end of a function (ie, right before a line like '}' + # + # Skip all the blank line checks if we are immediately inside a + # namespace body. In other words, don't issue blank line warnings + # for this block: + # namespace { + # + # } + # + # A warning about missing end of namespace comments will be issued instead. + # + # Also skip blank line checks for 'extern "C"' blocks, which are formatted + # like namespaces. + if (IsBlankLine(line) and + not nesting_state.InNamespaceBody() and + not nesting_state.InExternC()): + elided = clean_lines.elided + prev_line = elided[linenum - 1] + prevbrace = prev_line.rfind('{') + # TODO(unknown): Don't complain if line before blank line, and line after, + # both start with alnums and are indented the same amount. + # This ignores whitespace at the start of a namespace block + # because those are not usually indented. + if prevbrace != -1 and prev_line[prevbrace:].find('}') == -1: + # OK, we have a blank line at the start of a code block. Before we + # complain, we check if it is an exception to the rule: The previous + # non-empty line has the parameters of a function header that are indented + # 4 spaces (because they did not fit in a 80 column line when placed on + # the same line as the function name). We also check for the case where + # the previous line is indented 6 spaces, which may happen when the + # initializers of a constructor do not fit into a 80 column line. + exception = False + if Match(r' {6}\w', prev_line): # Initializer list? + # We are looking for the opening column of initializer list, which + # should be indented 4 spaces to cause 6 space indentation afterwards. + search_position = linenum-2 + while (search_position >= 0 + and Match(r' {6}\w', elided[search_position])): + search_position -= 1 + exception = (search_position >= 0 + and elided[search_position][:5] == ' :') + else: + # Search for the function arguments or an initializer list. We use a + # simple heuristic here: If the line is indented 4 spaces; and we have a + # closing paren, without the opening paren, followed by an opening brace + # or colon (for initializer lists) we assume that it is the last line of + # a function header. If we have a colon indented 4 spaces, it is an + # initializer list. + exception = (Match(r' {4}\w[^\(]*\)\s*(const\s*)?(\{\s*$|:)', + prev_line) + or Match(r' {4}:', prev_line)) + + if not exception: + error(filename, linenum, 'whitespace/blank_line', 2, + 'Redundant blank line at the start of a code block ' + 'should be deleted.') + # Ignore blank lines at the end of a block in a long if-else + # chain, like this: + # if (condition1) { + # // Something followed by a blank line + # + # } else if (condition2) { + # // Something else + # } + if linenum + 1 < clean_lines.NumLines(): + next_line = raw[linenum + 1] + if (next_line + and Match(r'\s*}', next_line) + and next_line.find('} else ') == -1): + error(filename, linenum, 'whitespace/blank_line', 3, + 'Redundant blank line at the end of a code block ' + 'should be deleted.') + + matched = Match(r'\s*(public|protected|private):', prev_line) + if matched: + error(filename, linenum, 'whitespace/blank_line', 3, + 'Do not leave a blank line after "%s:"' % matched.group(1)) + + # Next, check comments + next_line_start = 0 + if linenum + 1 < clean_lines.NumLines(): + next_line = raw[linenum + 1] + next_line_start = len(next_line) - len(next_line.lstrip()) + CheckComment(line, filename, linenum, next_line_start, error) + + # get rid of comments and strings + line = clean_lines.elided[linenum] + + # You shouldn't have spaces before your brackets, except maybe after + # 'delete []' or 'return []() {};' + if Search(r'\w\s+\[', line) and not Search(r'(?:delete|return)\s+\[', line): + error(filename, linenum, 'whitespace/braces', 5, + 'Extra space before [') + + # In range-based for, we wanted spaces before and after the colon, but + # not around "::" tokens that might appear. + if (Search(r'for *\(.*[^:]:[^: ]', line) or + Search(r'for *\(.*[^: ]:[^:]', line)): + error(filename, linenum, 'whitespace/forcolon', 2, + 'Missing space around colon in range-based for loop') + + +def CheckOperatorSpacing(filename, clean_lines, linenum, error): + """Checks for horizontal spacing around operators. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + error: The function to call with any errors found. + """ + line = clean_lines.elided[linenum] + + # Don't try to do spacing checks for operator methods. Do this by + # replacing the troublesome characters with something else, + # preserving column position for all other characters. + # + # The replacement is done repeatedly to avoid false positives from + # operators that call operators. + while True: + match = Match(r'^(.*\boperator\b)(\S+)(\s*\(.*)$', line) + if match: + line = match.group(1) + ('_' * len(match.group(2))) + match.group(3) + else: + break + + # We allow no-spaces around = within an if: "if ( (a=Foo()) == 0 )". + # Otherwise not. Note we only check for non-spaces on *both* sides; + # sometimes people put non-spaces on one side when aligning ='s among + # many lines (not that this is behavior that I approve of...) + if ((Search(r'[\w.]=', line) or + Search(r'=[\w.]', line)) + and not Search(r'\b(if|while|for) ', line) + # Operators taken from [lex.operators] in C++11 standard. + and not Search(r'(>=|<=|==|!=|&=|\^=|\|=|\+=|\*=|\/=|\%=)', line) + and not Search(r'operator=', line)): + error(filename, linenum, 'whitespace/operators', 4, + 'Missing spaces around =') + + # It's ok not to have spaces around binary operators like + - * /, but if + # there's too little whitespace, we get concerned. It's hard to tell, + # though, so we punt on this one for now. TODO. + + # You should always have whitespace around binary operators. + # + # Check <= and >= first to avoid false positives with < and >, then + # check non-include lines for spacing around < and >. + # + # If the operator is followed by a comma, assume it's be used in a + # macro context and don't do any checks. This avoids false + # positives. + # + # Note that && is not included here. Those are checked separately + # in CheckRValueReference + match = Search(r'[^<>=!\s](==|!=|<=|>=|\|\|)[^<>=!\s,;\)]', line) + if match: + error(filename, linenum, 'whitespace/operators', 3, + 'Missing spaces around %s' % match.group(1)) + elif not Match(r'#.*include', line): + # Look for < that is not surrounded by spaces. This is only + # triggered if both sides are missing spaces, even though + # technically should should flag if at least one side is missing a + # space. This is done to avoid some false positives with shifts. + match = Match(r'^(.*[^\s<])<[^\s=<,]', line) + if match: + (_, _, end_pos) = CloseExpression( + clean_lines, linenum, len(match.group(1))) + if end_pos <= -1: + error(filename, linenum, 'whitespace/operators', 3, + 'Missing spaces around <') + + # Look for > that is not surrounded by spaces. Similar to the + # above, we only trigger if both sides are missing spaces to avoid + # false positives with shifts. + match = Match(r'^(.*[^-\s>])>[^\s=>,]', line) + if match: + (_, _, start_pos) = ReverseCloseExpression( + clean_lines, linenum, len(match.group(1))) + if start_pos <= -1: + error(filename, linenum, 'whitespace/operators', 3, + 'Missing spaces around >') + + # We allow no-spaces around << when used like this: 10<<20, but + # not otherwise (particularly, not when used as streams) + # + # We also allow operators following an opening parenthesis, since + # those tend to be macros that deal with operators. + match = Search(r'(operator|[^\s(<])(?:L|UL|ULL|l|ul|ull)?<<([^\s,=<])', line) + if (match and not (match.group(1).isdigit() and match.group(2).isdigit()) and + not (match.group(1) == 'operator' and match.group(2) == ';')): + error(filename, linenum, 'whitespace/operators', 3, + 'Missing spaces around <<') + + # We allow no-spaces around >> for almost anything. This is because + # C++11 allows ">>" to close nested templates, which accounts for + # most cases when ">>" is not followed by a space. + # + # We still warn on ">>" followed by alpha character, because that is + # likely due to ">>" being used for right shifts, e.g.: + # value >> alpha + # + # When ">>" is used to close templates, the alphanumeric letter that + # follows would be part of an identifier, and there should still be + # a space separating the template type and the identifier. + # type> alpha + match = Search(r'>>[a-zA-Z_]', line) + if match: + error(filename, linenum, 'whitespace/operators', 3, + 'Missing spaces around >>') + + # There shouldn't be space around unary operators + match = Search(r'(!\s|~\s|[\s]--[\s;]|[\s]\+\+[\s;])', line) + if match: + error(filename, linenum, 'whitespace/operators', 4, + 'Extra space for operator %s' % match.group(1)) + + +def CheckParenthesisSpacing(filename, clean_lines, linenum, error): + """Checks for horizontal spacing around parentheses. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + error: The function to call with any errors found. + """ + line = clean_lines.elided[linenum] + + # No spaces after an if, while, switch, or for + match = Search(r' (if\(|for\(|while\(|switch\()', line) + if match: + error(filename, linenum, 'whitespace/parens', 5, + 'Missing space before ( in %s' % match.group(1)) + + # For if/for/while/switch, the left and right parens should be + # consistent about how many spaces are inside the parens, and + # there should either be zero or one spaces inside the parens. + # We don't want: "if ( foo)" or "if ( foo )". + # Exception: "for ( ; foo; bar)" and "for (foo; bar; )" are allowed. + match = Search(r'\b(if|for|while|switch)\s*' + r'\(([ ]*)(.).*[^ ]+([ ]*)\)\s*{\s*$', + line) + if match: + if len(match.group(2)) != len(match.group(4)): + if not (match.group(3) == ';' and + len(match.group(2)) == 1 + len(match.group(4)) or + not match.group(2) and Search(r'\bfor\s*\(.*; \)', line)): + error(filename, linenum, 'whitespace/parens', 5, + 'Mismatching spaces inside () in %s' % match.group(1)) + if len(match.group(2)) not in [0, 1]: + error(filename, linenum, 'whitespace/parens', 5, + 'Should have zero or one spaces inside ( and ) in %s' % + match.group(1)) + + +def CheckCommaSpacing(filename, clean_lines, linenum, error): + """Checks for horizontal spacing near commas and semicolons. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + error: The function to call with any errors found. + """ + raw = clean_lines.lines_without_raw_strings + line = clean_lines.elided[linenum] + + # You should always have a space after a comma (either as fn arg or operator) + # + # This does not apply when the non-space character following the + # comma is another comma, since the only time when that happens is + # for empty macro arguments. + # + # We run this check in two passes: first pass on elided lines to + # verify that lines contain missing whitespaces, second pass on raw + # lines to confirm that those missing whitespaces are not due to + # elided comments. + if (Search(r',[^,\s]', ReplaceAll(r'\boperator\s*,\s*\(', 'F(', line)) and + Search(r',[^,\s]', raw[linenum])): + error(filename, linenum, 'whitespace/comma', 3, + 'Missing space after ,') + + # You should always have a space after a semicolon + # except for few corner cases + # TODO(unknown): clarify if 'if (1) { return 1;}' is requires one more + # space after ; + if Search(r';[^\s};\\)/]', line): + error(filename, linenum, 'whitespace/semicolon', 3, + 'Missing space after ;') + + +def CheckBracesSpacing(filename, clean_lines, linenum, error): + """Checks for horizontal spacing near commas. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + error: The function to call with any errors found. + """ + line = clean_lines.elided[linenum] + + # Except after an opening paren, or after another opening brace (in case of + # an initializer list, for instance), you should have spaces before your + # braces. And since you should never have braces at the beginning of a line, + # this is an easy test. + match = Match(r'^(.*[^ ({>]){', line) + if match: + # Try a bit harder to check for brace initialization. This + # happens in one of the following forms: + # Constructor() : initializer_list_{} { ... } + # Constructor{}.MemberFunction() + # Type variable{}; + # FunctionCall(type{}, ...); + # LastArgument(..., type{}); + # LOG(INFO) << type{} << " ..."; + # map_of_type[{...}] = ...; + # ternary = expr ? new type{} : nullptr; + # OuterTemplate{}> + # + # We check for the character following the closing brace, and + # silence the warning if it's one of those listed above, i.e. + # "{.;,)<>]:". + # + # To account for nested initializer list, we allow any number of + # closing braces up to "{;,)<". We can't simply silence the + # warning on first sight of closing brace, because that would + # cause false negatives for things that are not initializer lists. + # Silence this: But not this: + # Outer{ if (...) { + # Inner{...} if (...){ // Missing space before { + # }; } + # + # There is a false negative with this approach if people inserted + # spurious semicolons, e.g. "if (cond){};", but we will catch the + # spurious semicolon with a separate check. + (endline, endlinenum, endpos) = CloseExpression( + clean_lines, linenum, len(match.group(1))) + trailing_text = '' + if endpos > -1: + trailing_text = endline[endpos:] + for offset in xrange(endlinenum + 1, + min(endlinenum + 3, clean_lines.NumLines() - 1)): + trailing_text += clean_lines.elided[offset] + if not Match(r'^[\s}]*[{.;,)<>\]:]', trailing_text): + error(filename, linenum, 'whitespace/braces', 5, + 'Missing space before {') + + # Make sure '} else {' has spaces. + if Search(r'}else', line): + error(filename, linenum, 'whitespace/braces', 5, + 'Missing space before else') + + # You shouldn't have a space before a semicolon at the end of the line. + # There's a special case for "for" since the style guide allows space before + # the semicolon there. + if Search(r':\s*;\s*$', line): + error(filename, linenum, 'whitespace/semicolon', 5, + 'Semicolon defining empty statement. Use {} instead.') + elif Search(r'^\s*;\s*$', line): + error(filename, linenum, 'whitespace/semicolon', 5, + 'Line contains only semicolon. If this should be an empty statement, ' + 'use {} instead.') + elif (Search(r'\s+;\s*$', line) and + not Search(r'\bfor\b', line)): + error(filename, linenum, 'whitespace/semicolon', 5, + 'Extra space before last semicolon. If this should be an empty ' + 'statement, use {} instead.') + + +def IsDecltype(clean_lines, linenum, column): + """Check if the token ending on (linenum, column) is decltype(). + + Args: + clean_lines: A CleansedLines instance containing the file. + linenum: the number of the line to check. + column: end column of the token to check. + Returns: + True if this token is decltype() expression, False otherwise. + """ + (text, _, start_col) = ReverseCloseExpression(clean_lines, linenum, column) + if start_col < 0: + return False + if Search(r'\bdecltype\s*$', text[0:start_col]): + return True + return False + + +def IsTemplateParameterList(clean_lines, linenum, column): + """Check if the token ending on (linenum, column) is the end of template<>. + + Args: + clean_lines: A CleansedLines instance containing the file. + linenum: the number of the line to check. + column: end column of the token to check. + Returns: + True if this token is end of a template parameter list, False otherwise. + """ + (_, startline, startpos) = ReverseCloseExpression( + clean_lines, linenum, column) + if (startpos > -1 and + Search(r'\btemplate\s*$', clean_lines.elided[startline][0:startpos])): + return True + return False + + +def IsRValueType(typenames, clean_lines, nesting_state, linenum, column): + """Check if the token ending on (linenum, column) is a type. + + Assumes that text to the right of the column is "&&" or a function + name. + + Args: + typenames: set of type names from template-argument-list. + clean_lines: A CleansedLines instance containing the file. + nesting_state: A NestingState instance which maintains information about + the current stack of nested blocks being parsed. + linenum: the number of the line to check. + column: end column of the token to check. + Returns: + True if this token is a type, False if we are not sure. + """ + prefix = clean_lines.elided[linenum][0:column] + + # Get one word to the left. If we failed to do so, this is most + # likely not a type, since it's unlikely that the type name and "&&" + # would be split across multiple lines. + match = Match(r'^(.*)(\b\w+|[>*)&])\s*$', prefix) + if not match: + return False + + # Check text following the token. If it's "&&>" or "&&," or "&&...", it's + # most likely a rvalue reference used inside a template. + suffix = clean_lines.elided[linenum][column:] + if Match(r'&&\s*(?:[>,]|\.\.\.)', suffix): + return True + + # Check for known types and end of templates: + # int&& variable + # vector&& variable + # + # Because this function is called recursively, we also need to + # recognize pointer and reference types: + # int* Function() + # int& Function() + if (match.group(2) in typenames or + match.group(2) in ['char', 'char16_t', 'char32_t', 'wchar_t', 'bool', + 'short', 'int', 'long', 'signed', 'unsigned', + 'float', 'double', 'void', 'auto', '>', '*', '&']): + return True + + # If we see a close parenthesis, look for decltype on the other side. + # decltype would unambiguously identify a type, anything else is + # probably a parenthesized expression and not a type. + if match.group(2) == ')': + return IsDecltype( + clean_lines, linenum, len(match.group(1)) + len(match.group(2)) - 1) + + # Check for casts and cv-qualifiers. + # match.group(1) remainder + # -------------- --------- + # const_cast< type&& + # const type&& + # type const&& + if Search(r'\b(?:const_cast\s*<|static_cast\s*<|dynamic_cast\s*<|' + r'reinterpret_cast\s*<|\w+\s)\s*$', + match.group(1)): + return True + + # Look for a preceding symbol that might help differentiate the context. + # These are the cases that would be ambiguous: + # match.group(1) remainder + # -------------- --------- + # Call ( expression && + # Declaration ( type&& + # sizeof ( type&& + # if ( expression && + # while ( expression && + # for ( type&& + # for( ; expression && + # statement ; type&& + # block { type&& + # constructor { expression && + start = linenum + line = match.group(1) + match_symbol = None + while start >= 0: + # We want to skip over identifiers and commas to get to a symbol. + # Commas are skipped so that we can find the opening parenthesis + # for function parameter lists. + match_symbol = Match(r'^(.*)([^\w\s,])[\w\s,]*$', line) + if match_symbol: + break + start -= 1 + line = clean_lines.elided[start] + + if not match_symbol: + # Probably the first statement in the file is an rvalue reference + return True + + if match_symbol.group(2) == '}': + # Found closing brace, probably an indicate of this: + # block{} type&& + return True + + if match_symbol.group(2) == ';': + # Found semicolon, probably one of these: + # for(; expression && + # statement; type&& + + # Look for the previous 'for(' in the previous lines. + before_text = match_symbol.group(1) + for i in xrange(start - 1, max(start - 6, 0), -1): + before_text = clean_lines.elided[i] + before_text + if Search(r'for\s*\([^{};]*$', before_text): + # This is the condition inside a for-loop + return False + + # Did not find a for-init-statement before this semicolon, so this + # is probably a new statement and not a condition. + return True + + if match_symbol.group(2) == '{': + # Found opening brace, probably one of these: + # block{ type&& = ... ; } + # constructor{ expression && expression } + + # Look for a closing brace or a semicolon. If we see a semicolon + # first, this is probably a rvalue reference. + line = clean_lines.elided[start][0:len(match_symbol.group(1)) + 1] + end = start + depth = 1 + while True: + for ch in line: + if ch == ';': + return True + elif ch == '{': + depth += 1 + elif ch == '}': + depth -= 1 + if depth == 0: + return False + end += 1 + if end >= clean_lines.NumLines(): + break + line = clean_lines.elided[end] + # Incomplete program? + return False + + if match_symbol.group(2) == '(': + # Opening parenthesis. Need to check what's to the left of the + # parenthesis. Look back one extra line for additional context. + before_text = match_symbol.group(1) + if linenum > 1: + before_text = clean_lines.elided[linenum - 1] + before_text + before_text = match_symbol.group(1) + + # Patterns that are likely to be types: + # [](type&& + # for (type&& + # sizeof(type&& + # operator=(type&& + # + if Search(r'(?:\]|\bfor|\bsizeof|\boperator\s*\S+\s*)\s*$', before_text): + return True + + # Patterns that are likely to be expressions: + # if (expression && + # while (expression && + # : initializer(expression && + # , initializer(expression && + # ( FunctionCall(expression && + # + FunctionCall(expression && + # + (expression && + # + # The last '+' represents operators such as '+' and '-'. + if Search(r'(?:\bif|\bwhile|[-+=%^(]*>)?\s*$', + match_symbol.group(1)) + if match_func: + # Check for constructors, which don't have return types. + if Search(r'\b(?:explicit|inline)$', match_func.group(1)): + return True + implicit_constructor = Match(r'\s*(\w+)\((?:const\s+)?(\w+)', prefix) + if (implicit_constructor and + implicit_constructor.group(1) == implicit_constructor.group(2)): + return True + return IsRValueType(typenames, clean_lines, nesting_state, linenum, + len(match_func.group(1))) + + # Nothing before the function name. If this is inside a block scope, + # this is probably a function call. + return not (nesting_state.previous_stack_top and + nesting_state.previous_stack_top.IsBlockInfo()) + + if match_symbol.group(2) == '>': + # Possibly a closing bracket, check that what's on the other side + # looks like the start of a template. + return IsTemplateParameterList( + clean_lines, start, len(match_symbol.group(1))) + + # Some other symbol, usually something like "a=b&&c". This is most + # likely not a type. + return False + + +def IsDeletedOrDefault(clean_lines, linenum): + """Check if current constructor or operator is deleted or default. + + Args: + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + Returns: + True if this is a deleted or default constructor. + """ + open_paren = clean_lines.elided[linenum].find('(') + if open_paren < 0: + return False + (close_line, _, close_paren) = CloseExpression( + clean_lines, linenum, open_paren) + if close_paren < 0: + return False + return Match(r'\s*=\s*(?:delete|default)\b', close_line[close_paren:]) + + +def IsRValueAllowed(clean_lines, linenum, typenames): + """Check if RValue reference is allowed on a particular line. + + Args: + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + typenames: set of type names from template-argument-list. + Returns: + True if line is within the region where RValue references are allowed. + """ + # Allow region marked by PUSH/POP macros + for i in xrange(linenum, 0, -1): + line = clean_lines.elided[i] + if Match(r'GOOGLE_ALLOW_RVALUE_REFERENCES_(?:PUSH|POP)', line): + if not line.endswith('PUSH'): + return False + for j in xrange(linenum, clean_lines.NumLines(), 1): + line = clean_lines.elided[j] + if Match(r'GOOGLE_ALLOW_RVALUE_REFERENCES_(?:PUSH|POP)', line): + return line.endswith('POP') + + # Allow operator= + line = clean_lines.elided[linenum] + if Search(r'\boperator\s*=\s*\(', line): + return IsDeletedOrDefault(clean_lines, linenum) + + # Allow constructors + match = Match(r'\s*(?:[\w<>]+::)*([\w<>]+)\s*::\s*([\w<>]+)\s*\(', line) + if match and match.group(1) == match.group(2): + return IsDeletedOrDefault(clean_lines, linenum) + if Search(r'\b(?:explicit|inline)\s+[\w<>]+\s*\(', line): + return IsDeletedOrDefault(clean_lines, linenum) + + if Match(r'\s*[\w<>]+\s*\(', line): + previous_line = 'ReturnType' + if linenum > 0: + previous_line = clean_lines.elided[linenum - 1] + if Match(r'^\s*$', previous_line) or Search(r'[{}:;]\s*$', previous_line): + return IsDeletedOrDefault(clean_lines, linenum) + + # Reject types not mentioned in template-argument-list + while line: + match = Match(r'^.*?(\w+)\s*&&(.*)$', line) + if not match: + break + if match.group(1) not in typenames: + return False + line = match.group(2) + + # All RValue types that were in template-argument-list should have + # been removed by now. Those were allowed, assuming that they will + # be forwarded. + # + # If there are no remaining RValue types left (i.e. types that were + # not found in template-argument-list), flag those as not allowed. + return line.find('&&') < 0 + + +def GetTemplateArgs(clean_lines, linenum): + """Find list of template arguments associated with this function declaration. + + Args: + clean_lines: A CleansedLines instance containing the file. + linenum: Line number containing the start of the function declaration, + usually one line after the end of the template-argument-list. + Returns: + Set of type names, or empty set if this does not appear to have + any template parameters. + """ + # Find start of function + func_line = linenum + while func_line > 0: + line = clean_lines.elided[func_line] + if Match(r'^\s*$', line): + return set() + if line.find('(') >= 0: + break + func_line -= 1 + if func_line == 0: + return set() + + # Collapse template-argument-list into a single string + argument_list = '' + match = Match(r'^(\s*template\s*)<', clean_lines.elided[func_line]) + if match: + # template-argument-list on the same line as function name + start_col = len(match.group(1)) + _, end_line, end_col = CloseExpression(clean_lines, func_line, start_col) + if end_col > -1 and end_line == func_line: + start_col += 1 # Skip the opening bracket + argument_list = clean_lines.elided[func_line][start_col:end_col] + + elif func_line > 1: + # template-argument-list one line before function name + match = Match(r'^(.*)>\s*$', clean_lines.elided[func_line - 1]) + if match: + end_col = len(match.group(1)) + _, start_line, start_col = ReverseCloseExpression( + clean_lines, func_line - 1, end_col) + if start_col > -1: + start_col += 1 # Skip the opening bracket + while start_line < func_line - 1: + argument_list += clean_lines.elided[start_line][start_col:] + start_col = 0 + start_line += 1 + argument_list += clean_lines.elided[func_line - 1][start_col:end_col] + + if not argument_list: + return set() + + # Extract type names + typenames = set() + while True: + match = Match(r'^[,\s]*(?:typename|class)(?:\.\.\.)?\s+(\w+)(.*)$', + argument_list) + if not match: + break + typenames.add(match.group(1)) + argument_list = match.group(2) + return typenames + + +def CheckRValueReference(filename, clean_lines, linenum, nesting_state, error): + """Check for rvalue references. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + nesting_state: A NestingState instance which maintains information about + the current stack of nested blocks being parsed. + error: The function to call with any errors found. + """ + # Find lines missing spaces around &&. + # TODO(unknown): currently we don't check for rvalue references + # with spaces surrounding the && to avoid false positives with + # boolean expressions. + line = clean_lines.elided[linenum] + match = Match(r'^(.*\S)&&', line) + if not match: + match = Match(r'(.*)&&\S', line) + if (not match) or '(&&)' in line or Search(r'\boperator\s*$', match.group(1)): + return + + # Either poorly formed && or an rvalue reference, check the context + # to get a more accurate error message. Mostly we want to determine + # if what's to the left of "&&" is a type or not. + typenames = GetTemplateArgs(clean_lines, linenum) + and_pos = len(match.group(1)) + if IsRValueType(typenames, clean_lines, nesting_state, linenum, and_pos): + if not IsRValueAllowed(clean_lines, linenum, typenames): + error(filename, linenum, 'build/c++11', 3, + 'RValue references are an unapproved C++ feature.') + else: + error(filename, linenum, 'whitespace/operators', 3, + 'Missing spaces around &&') + + +def CheckSectionSpacing(filename, clean_lines, class_info, linenum, error): + """Checks for additional blank line issues related to sections. + + Currently the only thing checked here is blank line before protected/private. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + class_info: A _ClassInfo objects. + linenum: The number of the line to check. + error: The function to call with any errors found. + """ + # Skip checks if the class is small, where small means 25 lines or less. + # 25 lines seems like a good cutoff since that's the usual height of + # terminals, and any class that can't fit in one screen can't really + # be considered "small". + # + # Also skip checks if we are on the first line. This accounts for + # classes that look like + # class Foo { public: ... }; + # + # If we didn't find the end of the class, last_line would be zero, + # and the check will be skipped by the first condition. + if (class_info.last_line - class_info.starting_linenum <= 24 or + linenum <= class_info.starting_linenum): + return + + matched = Match(r'\s*(public|protected|private):', clean_lines.lines[linenum]) + if matched: + # Issue warning if the line before public/protected/private was + # not a blank line, but don't do this if the previous line contains + # "class" or "struct". This can happen two ways: + # - We are at the beginning of the class. + # - We are forward-declaring an inner class that is semantically + # private, but needed to be public for implementation reasons. + # Also ignores cases where the previous line ends with a backslash as can be + # common when defining classes in C macros. + prev_line = clean_lines.lines[linenum - 1] + if (not IsBlankLine(prev_line) and + not Search(r'\b(class|struct)\b', prev_line) and + not Search(r'\\$', prev_line)): + # Try a bit harder to find the beginning of the class. This is to + # account for multi-line base-specifier lists, e.g.: + # class Derived + # : public Base { + end_class_head = class_info.starting_linenum + for i in range(class_info.starting_linenum, linenum): + if Search(r'\{\s*$', clean_lines.lines[i]): + end_class_head = i + break + if end_class_head < linenum - 1: + error(filename, linenum, 'whitespace/blank_line', 3, + '"%s:" should be preceded by a blank line' % matched.group(1)) + + +def GetPreviousNonBlankLine(clean_lines, linenum): + """Return the most recent non-blank line and its line number. + + Args: + clean_lines: A CleansedLines instance containing the file contents. + linenum: The number of the line to check. + + Returns: + A tuple with two elements. The first element is the contents of the last + non-blank line before the current line, or the empty string if this is the + first non-blank line. The second is the line number of that line, or -1 + if this is the first non-blank line. + """ + + prevlinenum = linenum - 1 + while prevlinenum >= 0: + prevline = clean_lines.elided[prevlinenum] + if not IsBlankLine(prevline): # if not a blank line... + return (prevline, prevlinenum) + prevlinenum -= 1 + return ('', -1) + + +def CheckBraces(filename, clean_lines, linenum, error): + """Looks for misplaced braces (e.g. at the end of line). + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + error: The function to call with any errors found. + """ + + line = clean_lines.elided[linenum] # get rid of comments and strings + + if Match(r'\s*{\s*$', line): + # We allow an open brace to start a line in the case where someone is using + # braces in a block to explicitly create a new scope, which is commonly used + # to control the lifetime of stack-allocated variables. Braces are also + # used for brace initializers inside function calls. We don't detect this + # perfectly: we just don't complain if the last non-whitespace character on + # the previous non-blank line is ',', ';', ':', '(', '{', or '}', or if the + # previous line starts a preprocessor block. + prevline = GetPreviousNonBlankLine(clean_lines, linenum)[0] + if (not Search(r'[,;:}{(]\s*$', prevline) and + not Match(r'\s*#', prevline)): + error(filename, linenum, 'whitespace/braces', 4, + '{ should almost always be at the end of the previous line') + + # An else clause should be on the same line as the preceding closing brace. + if Match(r'\s*else\b\s*(?:if\b|\{|$)', line): + prevline = GetPreviousNonBlankLine(clean_lines, linenum)[0] + if Match(r'\s*}\s*$', prevline): + error(filename, linenum, 'whitespace/newline', 4, + 'An else should appear on the same line as the preceding }') + + # If braces come on one side of an else, they should be on both. + # However, we have to worry about "else if" that spans multiple lines! + if Search(r'else if\s*\(', line): # could be multi-line if + brace_on_left = bool(Search(r'}\s*else if\s*\(', line)) + # find the ( after the if + pos = line.find('else if') + pos = line.find('(', pos) + if pos > 0: + (endline, _, endpos) = CloseExpression(clean_lines, linenum, pos) + brace_on_right = endline[endpos:].find('{') != -1 + if brace_on_left != brace_on_right: # must be brace after if + error(filename, linenum, 'readability/braces', 5, + 'If an else has a brace on one side, it should have it on both') + elif Search(r'}\s*else[^{]*$', line) or Match(r'[^}]*else\s*{', line): + error(filename, linenum, 'readability/braces', 5, + 'If an else has a brace on one side, it should have it on both') + + # Likewise, an else should never have the else clause on the same line + if Search(r'\belse [^\s{]', line) and not Search(r'\belse if\b', line): + error(filename, linenum, 'whitespace/newline', 4, + 'Else clause should never be on same line as else (use 2 lines)') + + # In the same way, a do/while should never be on one line + if Match(r'\s*do [^\s{]', line): + error(filename, linenum, 'whitespace/newline', 4, + 'do/while clauses should not be on a single line') + + # Check single-line if/else bodies. The style guide says 'curly braces are not + # required for single-line statements'. We additionally allow multi-line, + # single statements, but we reject anything with more than one semicolon in + # it. This means that the first semicolon after the if should be at the end of + # its line, and the line after that should have an indent level equal to or + # lower than the if. We also check for ambiguous if/else nesting without + # braces. + if_else_match = Search(r'\b(if\s*\(|else\b)', line) + if if_else_match and not Match(r'\s*#', line): + if_indent = GetIndentLevel(line) + endline, endlinenum, endpos = line, linenum, if_else_match.end() + if_match = Search(r'\bif\s*\(', line) + if if_match: + # This could be a multiline if condition, so find the end first. + pos = if_match.end() - 1 + (endline, endlinenum, endpos) = CloseExpression(clean_lines, linenum, pos) + # Check for an opening brace, either directly after the if or on the next + # line. If found, this isn't a single-statement conditional. + if (not Match(r'\s*{', endline[endpos:]) + and not (Match(r'\s*$', endline[endpos:]) + and endlinenum < (len(clean_lines.elided) - 1) + and Match(r'\s*{', clean_lines.elided[endlinenum + 1]))): + while (endlinenum < len(clean_lines.elided) + and ';' not in clean_lines.elided[endlinenum][endpos:]): + endlinenum += 1 + endpos = 0 + if endlinenum < len(clean_lines.elided): + endline = clean_lines.elided[endlinenum] + # We allow a mix of whitespace and closing braces (e.g. for one-liner + # methods) and a single \ after the semicolon (for macros) + endpos = endline.find(';') + if not Match(r';[\s}]*(\\?)$', endline[endpos:]): + # Semicolon isn't the last character, there's something trailing. + # Output a warning if the semicolon is not contained inside + # a lambda expression. + if not Match(r'^[^{};]*\[[^\[\]]*\][^{}]*\{[^{}]*\}\s*\)*[;,]\s*$', + endline): + error(filename, linenum, 'readability/braces', 4, + 'If/else bodies with multiple statements require braces') + elif endlinenum < len(clean_lines.elided) - 1: + # Make sure the next line is dedented + next_line = clean_lines.elided[endlinenum + 1] + next_indent = GetIndentLevel(next_line) + # With ambiguous nested if statements, this will error out on the + # if that *doesn't* match the else, regardless of whether it's the + # inner one or outer one. + if (if_match and Match(r'\s*else\b', next_line) + and next_indent != if_indent): + error(filename, linenum, 'readability/braces', 4, + 'Else clause should be indented at the same level as if. ' + 'Ambiguous nested if/else chains require braces.') + elif next_indent > if_indent: + error(filename, linenum, 'readability/braces', 4, + 'If/else bodies with multiple statements require braces') + + +def CheckTrailingSemicolon(filename, clean_lines, linenum, error): + """Looks for redundant trailing semicolon. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + error: The function to call with any errors found. + """ + + line = clean_lines.elided[linenum] + + # Block bodies should not be followed by a semicolon. Due to C++11 + # brace initialization, there are more places where semicolons are + # required than not, so we use a whitelist approach to check these + # rather than a blacklist. These are the places where "};" should + # be replaced by just "}": + # 1. Some flavor of block following closing parenthesis: + # for (;;) {}; + # while (...) {}; + # switch (...) {}; + # Function(...) {}; + # if (...) {}; + # if (...) else if (...) {}; + # + # 2. else block: + # if (...) else {}; + # + # 3. const member function: + # Function(...) const {}; + # + # 4. Block following some statement: + # x = 42; + # {}; + # + # 5. Block at the beginning of a function: + # Function(...) { + # {}; + # } + # + # Note that naively checking for the preceding "{" will also match + # braces inside multi-dimensional arrays, but this is fine since + # that expression will not contain semicolons. + # + # 6. Block following another block: + # while (true) {} + # {}; + # + # 7. End of namespaces: + # namespace {}; + # + # These semicolons seems far more common than other kinds of + # redundant semicolons, possibly due to people converting classes + # to namespaces. For now we do not warn for this case. + # + # Try matching case 1 first. + match = Match(r'^(.*\)\s*)\{', line) + if match: + # Matched closing parenthesis (case 1). Check the token before the + # matching opening parenthesis, and don't warn if it looks like a + # macro. This avoids these false positives: + # - macro that defines a base class + # - multi-line macro that defines a base class + # - macro that defines the whole class-head + # + # But we still issue warnings for macros that we know are safe to + # warn, specifically: + # - TEST, TEST_F, TEST_P, MATCHER, MATCHER_P + # - TYPED_TEST + # - INTERFACE_DEF + # - EXCLUSIVE_LOCKS_REQUIRED, SHARED_LOCKS_REQUIRED, LOCKS_EXCLUDED: + # + # We implement a whitelist of safe macros instead of a blacklist of + # unsafe macros, even though the latter appears less frequently in + # google code and would have been easier to implement. This is because + # the downside for getting the whitelist wrong means some extra + # semicolons, while the downside for getting the blacklist wrong + # would result in compile errors. + # + # In addition to macros, we also don't want to warn on + # - Compound literals + # - Lambdas + # - alignas specifier with anonymous structs: + closing_brace_pos = match.group(1).rfind(')') + opening_parenthesis = ReverseCloseExpression( + clean_lines, linenum, closing_brace_pos) + if opening_parenthesis[2] > -1: + line_prefix = opening_parenthesis[0][0:opening_parenthesis[2]] + macro = Search(r'\b([A-Z_]+)\s*$', line_prefix) + func = Match(r'^(.*\])\s*$', line_prefix) + if ((macro and + macro.group(1) not in ( + 'TEST', 'TEST_F', 'MATCHER', 'MATCHER_P', 'TYPED_TEST', + 'EXCLUSIVE_LOCKS_REQUIRED', 'SHARED_LOCKS_REQUIRED', + 'LOCKS_EXCLUDED', 'INTERFACE_DEF')) or + (func and not Search(r'\boperator\s*\[\s*\]', func.group(1))) or + Search(r'\b(?:struct|union)\s+alignas\s*$', line_prefix) or + Search(r'\s+=\s*$', line_prefix)): + match = None + if (match and + opening_parenthesis[1] > 1 and + Search(r'\]\s*$', clean_lines.elided[opening_parenthesis[1] - 1])): + # Multi-line lambda-expression + match = None + + else: + # Try matching cases 2-3. + match = Match(r'^(.*(?:else|\)\s*const)\s*)\{', line) + if not match: + # Try matching cases 4-6. These are always matched on separate lines. + # + # Note that we can't simply concatenate the previous line to the + # current line and do a single match, otherwise we may output + # duplicate warnings for the blank line case: + # if (cond) { + # // blank line + # } + prevline = GetPreviousNonBlankLine(clean_lines, linenum)[0] + if prevline and Search(r'[;{}]\s*$', prevline): + match = Match(r'^(\s*)\{', line) + + # Check matching closing brace + if match: + (endline, endlinenum, endpos) = CloseExpression( + clean_lines, linenum, len(match.group(1))) + if endpos > -1 and Match(r'^\s*;', endline[endpos:]): + # Current {} pair is eligible for semicolon check, and we have found + # the redundant semicolon, output warning here. + # + # Note: because we are scanning forward for opening braces, and + # outputting warnings for the matching closing brace, if there are + # nested blocks with trailing semicolons, we will get the error + # messages in reversed order. + error(filename, endlinenum, 'readability/braces', 4, + "You don't need a ; after a }") + + +def CheckEmptyBlockBody(filename, clean_lines, linenum, error): + """Look for empty loop/conditional body with only a single semicolon. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + error: The function to call with any errors found. + """ + + # Search for loop keywords at the beginning of the line. Because only + # whitespaces are allowed before the keywords, this will also ignore most + # do-while-loops, since those lines should start with closing brace. + # + # We also check "if" blocks here, since an empty conditional block + # is likely an error. + line = clean_lines.elided[linenum] + matched = Match(r'\s*(for|while|if)\s*\(', line) + if matched: + # Find the end of the conditional expression + (end_line, end_linenum, end_pos) = CloseExpression( + clean_lines, linenum, line.find('(')) + + # Output warning if what follows the condition expression is a semicolon. + # No warning for all other cases, including whitespace or newline, since we + # have a separate check for semicolons preceded by whitespace. + if end_pos >= 0 and Match(r';', end_line[end_pos:]): + if matched.group(1) == 'if': + error(filename, end_linenum, 'whitespace/empty_conditional_body', 5, + 'Empty conditional bodies should use {}') + else: + error(filename, end_linenum, 'whitespace/empty_loop_body', 5, + 'Empty loop bodies should use {} or continue') + + +def FindCheckMacro(line): + """Find a replaceable CHECK-like macro. + + Args: + line: line to search on. + Returns: + (macro name, start position), or (None, -1) if no replaceable + macro is found. + """ + for macro in _CHECK_MACROS: + i = line.find(macro) + if i >= 0: + # Find opening parenthesis. Do a regular expression match here + # to make sure that we are matching the expected CHECK macro, as + # opposed to some other macro that happens to contain the CHECK + # substring. + matched = Match(r'^(.*\b' + macro + r'\s*)\(', line) + if not matched: + continue + return (macro, len(matched.group(1))) + return (None, -1) + + +def CheckCheck(filename, clean_lines, linenum, error): + """Checks the use of CHECK and EXPECT macros. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + error: The function to call with any errors found. + """ + + # Decide the set of replacement macros that should be suggested + lines = clean_lines.elided + (check_macro, start_pos) = FindCheckMacro(lines[linenum]) + if not check_macro: + return + + # Find end of the boolean expression by matching parentheses + (last_line, end_line, end_pos) = CloseExpression( + clean_lines, linenum, start_pos) + if end_pos < 0: + return + + # If the check macro is followed by something other than a + # semicolon, assume users will log their own custom error messages + # and don't suggest any replacements. + if not Match(r'\s*;', last_line[end_pos:]): + return + + if linenum == end_line: + expression = lines[linenum][start_pos + 1:end_pos - 1] + else: + expression = lines[linenum][start_pos + 1:] + for i in xrange(linenum + 1, end_line): + expression += lines[i] + expression += last_line[0:end_pos - 1] + + # Parse expression so that we can take parentheses into account. + # This avoids false positives for inputs like "CHECK((a < 4) == b)", + # which is not replaceable by CHECK_LE. + lhs = '' + rhs = '' + operator = None + while expression: + matched = Match(r'^\s*(<<|<<=|>>|>>=|->\*|->|&&|\|\||' + r'==|!=|>=|>|<=|<|\()(.*)$', expression) + if matched: + token = matched.group(1) + if token == '(': + # Parenthesized operand + expression = matched.group(2) + (end, _) = FindEndOfExpressionInLine(expression, 0, ['(']) + if end < 0: + return # Unmatched parenthesis + lhs += '(' + expression[0:end] + expression = expression[end:] + elif token in ('&&', '||'): + # Logical and/or operators. This means the expression + # contains more than one term, for example: + # CHECK(42 < a && a < b); + # + # These are not replaceable with CHECK_LE, so bail out early. + return + elif token in ('<<', '<<=', '>>', '>>=', '->*', '->'): + # Non-relational operator + lhs += token + expression = matched.group(2) + else: + # Relational operator + operator = token + rhs = matched.group(2) + break + else: + # Unparenthesized operand. Instead of appending to lhs one character + # at a time, we do another regular expression match to consume several + # characters at once if possible. Trivial benchmark shows that this + # is more efficient when the operands are longer than a single + # character, which is generally the case. + matched = Match(r'^([^-=!<>()&|]+)(.*)$', expression) + if not matched: + matched = Match(r'^(\s*\S)(.*)$', expression) + if not matched: + break + lhs += matched.group(1) + expression = matched.group(2) + + # Only apply checks if we got all parts of the boolean expression + if not (lhs and operator and rhs): + return + + # Check that rhs do not contain logical operators. We already know + # that lhs is fine since the loop above parses out && and ||. + if rhs.find('&&') > -1 or rhs.find('||') > -1: + return + + # At least one of the operands must be a constant literal. This is + # to avoid suggesting replacements for unprintable things like + # CHECK(variable != iterator) + # + # The following pattern matches decimal, hex integers, strings, and + # characters (in that order). + lhs = lhs.strip() + rhs = rhs.strip() + match_constant = r'^([-+]?(\d+|0[xX][0-9a-fA-F]+)[lLuU]{0,3}|".*"|\'.*\')$' + if Match(match_constant, lhs) or Match(match_constant, rhs): + # Note: since we know both lhs and rhs, we can provide a more + # descriptive error message like: + # Consider using CHECK_EQ(x, 42) instead of CHECK(x == 42) + # Instead of: + # Consider using CHECK_EQ instead of CHECK(a == b) + # + # We are still keeping the less descriptive message because if lhs + # or rhs gets long, the error message might become unreadable. + error(filename, linenum, 'readability/check', 2, + 'Consider using %s instead of %s(a %s b)' % ( + _CHECK_REPLACEMENT[check_macro][operator], + check_macro, operator)) + + +def CheckAltTokens(filename, clean_lines, linenum, error): + """Check alternative keywords being used in boolean expressions. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + error: The function to call with any errors found. + """ + line = clean_lines.elided[linenum] + + # Avoid preprocessor lines + if Match(r'^\s*#', line): + return + + # Last ditch effort to avoid multi-line comments. This will not help + # if the comment started before the current line or ended after the + # current line, but it catches most of the false positives. At least, + # it provides a way to workaround this warning for people who use + # multi-line comments in preprocessor macros. + # + # TODO(unknown): remove this once cpplint has better support for + # multi-line comments. + if line.find('/*') >= 0 or line.find('*/') >= 0: + return + + for match in _ALT_TOKEN_REPLACEMENT_PATTERN.finditer(line): + error(filename, linenum, 'readability/alt_tokens', 2, + 'Use operator %s instead of %s' % ( + _ALT_TOKEN_REPLACEMENT[match.group(1)], match.group(1))) + + +def GetLineWidth(line): + """Determines the width of the line in column positions. + + Args: + line: A string, which may be a Unicode string. + + Returns: + The width of the line in column positions, accounting for Unicode + combining characters and wide characters. + """ + if isinstance(line, unicode): + width = 0 + for uc in unicodedata.normalize('NFC', line): + if unicodedata.east_asian_width(uc) in ('W', 'F'): + width += 2 + elif not unicodedata.combining(uc): + width += 1 + return width + else: + return len(line) + + +def CheckStyle(filename, clean_lines, linenum, file_extension, nesting_state, + error): + """Checks rules from the 'C++ style rules' section of cppguide.html. + + Most of these rules are hard to test (naming, comment style), but we + do what we can. In particular we check for 2-space indents, line lengths, + tab usage, spaces inside code, etc. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + file_extension: The extension (without the dot) of the filename. + nesting_state: A NestingState instance which maintains information about + the current stack of nested blocks being parsed. + error: The function to call with any errors found. + """ + + # Don't use "elided" lines here, otherwise we can't check commented lines. + # Don't want to use "raw" either, because we don't want to check inside C++11 + # raw strings, + raw_lines = clean_lines.lines_without_raw_strings + line = raw_lines[linenum] + + if line.find('\t') != -1: + error(filename, linenum, 'whitespace/tab', 1, + 'Tab found; better to use spaces') + + # One or three blank spaces at the beginning of the line is weird; it's + # hard to reconcile that with 2-space indents. + # NOTE: here are the conditions rob pike used for his tests. Mine aren't + # as sophisticated, but it may be worth becoming so: RLENGTH==initial_spaces + # if(RLENGTH > 20) complain = 0; + # if(match($0, " +(error|private|public|protected):")) complain = 0; + # if(match(prev, "&& *$")) complain = 0; + # if(match(prev, "\\|\\| *$")) complain = 0; + # if(match(prev, "[\",=><] *$")) complain = 0; + # if(match($0, " <<")) complain = 0; + # if(match(prev, " +for \\(")) complain = 0; + # if(prevodd && match(prevprev, " +for \\(")) complain = 0; + scope_or_label_pattern = r'\s*\w+\s*:\s*\\?$' + classinfo = nesting_state.InnermostClass() + initial_spaces = 0 + cleansed_line = clean_lines.elided[linenum] + while initial_spaces < len(line) and line[initial_spaces] == ' ': + initial_spaces += 1 + if line and line[-1].isspace(): + error(filename, linenum, 'whitespace/end_of_line', 4, + 'Line ends in whitespace. Consider deleting these extra spaces.') + # There are certain situations we allow one space, notably for + # section labels, and also lines containing multi-line raw strings. + elif ((initial_spaces == 1 or initial_spaces == 3) and + not Match(scope_or_label_pattern, cleansed_line) and + not (clean_lines.raw_lines[linenum] != line and + Match(r'^\s*""', line))): + error(filename, linenum, 'whitespace/indent', 3, + 'Weird number of spaces at line-start. ' + 'Are you using a 2-space indent?') + + # Check if the line is a header guard. + is_header_guard = False + if file_extension == 'h': + cppvar = GetHeaderGuardCPPVariable(filename) + if (line.startswith('#ifndef %s' % cppvar) or + line.startswith('#define %s' % cppvar) or + line.startswith('#endif // %s' % cppvar)): + is_header_guard = True + # #include lines and header guards can be long, since there's no clean way to + # split them. + # + # URLs can be long too. It's possible to split these, but it makes them + # harder to cut&paste. + # + # The "$Id:...$" comment may also get very long without it being the + # developers fault. + if (not line.startswith('#include') and not is_header_guard and + not Match(r'^\s*//.*http(s?)://\S*$', line) and + not Match(r'^// \$Id:.*#[0-9]+ \$$', line)): + line_width = GetLineWidth(line) + extended_length = int((_line_length * 1.25)) + if line_width > extended_length: + error(filename, linenum, 'whitespace/line_length', 4, + 'Lines should very rarely be longer than %i characters' % + extended_length) + elif line_width > _line_length: + error(filename, linenum, 'whitespace/line_length', 2, + 'Lines should be <= %i characters long' % _line_length) + + if (cleansed_line.count(';') > 1 and + # for loops are allowed two ;'s (and may run over two lines). + cleansed_line.find('for') == -1 and + (GetPreviousNonBlankLine(clean_lines, linenum)[0].find('for') == -1 or + GetPreviousNonBlankLine(clean_lines, linenum)[0].find(';') != -1) and + # It's ok to have many commands in a switch case that fits in 1 line + not ((cleansed_line.find('case ') != -1 or + cleansed_line.find('default:') != -1) and + cleansed_line.find('break;') != -1)): + error(filename, linenum, 'whitespace/newline', 0, + 'More than one command on the same line') + + # Some more style checks + CheckBraces(filename, clean_lines, linenum, error) + CheckTrailingSemicolon(filename, clean_lines, linenum, error) + CheckEmptyBlockBody(filename, clean_lines, linenum, error) + CheckAccess(filename, clean_lines, linenum, nesting_state, error) + CheckSpacing(filename, clean_lines, linenum, nesting_state, error) + CheckOperatorSpacing(filename, clean_lines, linenum, error) + CheckParenthesisSpacing(filename, clean_lines, linenum, error) + CheckCommaSpacing(filename, clean_lines, linenum, error) + CheckBracesSpacing(filename, clean_lines, linenum, error) + CheckSpacingForFunctionCall(filename, clean_lines, linenum, error) + CheckRValueReference(filename, clean_lines, linenum, nesting_state, error) + CheckCheck(filename, clean_lines, linenum, error) + CheckAltTokens(filename, clean_lines, linenum, error) + classinfo = nesting_state.InnermostClass() + if classinfo: + CheckSectionSpacing(filename, clean_lines, classinfo, linenum, error) + + +_RE_PATTERN_INCLUDE = re.compile(r'^\s*#\s*include\s*([<"])([^>"]*)[>"].*$') +# Matches the first component of a filename delimited by -s and _s. That is: +# _RE_FIRST_COMPONENT.match('foo').group(0) == 'foo' +# _RE_FIRST_COMPONENT.match('foo.cc').group(0) == 'foo' +# _RE_FIRST_COMPONENT.match('foo-bar_baz.cc').group(0) == 'foo' +# _RE_FIRST_COMPONENT.match('foo_bar-baz.cc').group(0) == 'foo' +_RE_FIRST_COMPONENT = re.compile(r'^[^-_.]+') + + +def _DropCommonSuffixes(filename): + """Drops common suffixes like _test.cc or -inl.h from filename. + + For example: + >>> _DropCommonSuffixes('foo/foo-inl.h') + 'foo/foo' + >>> _DropCommonSuffixes('foo/bar/foo.cc') + 'foo/bar/foo' + >>> _DropCommonSuffixes('foo/foo_internal.h') + 'foo/foo' + >>> _DropCommonSuffixes('foo/foo_unusualinternal.h') + 'foo/foo_unusualinternal' + + Args: + filename: The input filename. + + Returns: + The filename with the common suffix removed. + """ + for suffix in ('test.cc', 'regtest.cc', 'unittest.cc', + 'inl.h', 'impl.h', 'internal.h'): + if (filename.endswith(suffix) and len(filename) > len(suffix) and + filename[-len(suffix) - 1] in ('-', '_')): + return filename[:-len(suffix) - 1] + return os.path.splitext(filename)[0] + + +def _IsTestFilename(filename): + """Determines if the given filename has a suffix that identifies it as a test. + + Args: + filename: The input filename. + + Returns: + True if 'filename' looks like a test, False otherwise. + """ + if (filename.endswith('_test.cc') or + filename.endswith('_unittest.cc') or + filename.endswith('_regtest.cc')): + return True + else: + return False + + +def _ClassifyInclude(fileinfo, include, is_system): + """Figures out what kind of header 'include' is. + + Args: + fileinfo: The current file cpplint is running over. A FileInfo instance. + include: The path to a #included file. + is_system: True if the #include used <> rather than "". + + Returns: + One of the _XXX_HEADER constants. + + For example: + >>> _ClassifyInclude(FileInfo('foo/foo.cc'), 'stdio.h', True) + _C_SYS_HEADER + >>> _ClassifyInclude(FileInfo('foo/foo.cc'), 'string', True) + _CPP_SYS_HEADER + >>> _ClassifyInclude(FileInfo('foo/foo.cc'), 'foo/foo.h', False) + _LIKELY_MY_HEADER + >>> _ClassifyInclude(FileInfo('foo/foo_unknown_extension.cc'), + ... 'bar/foo_other_ext.h', False) + _POSSIBLE_MY_HEADER + >>> _ClassifyInclude(FileInfo('foo/foo.cc'), 'foo/bar.h', False) + _OTHER_HEADER + """ + # This is a list of all standard c++ header files, except + # those already checked for above. + is_cpp_h = include in _CPP_HEADERS + + if is_system: + if is_cpp_h: + return _CPP_SYS_HEADER + else: + return _C_SYS_HEADER + + # If the target file and the include we're checking share a + # basename when we drop common extensions, and the include + # lives in . , then it's likely to be owned by the target file. + target_dir, target_base = ( + os.path.split(_DropCommonSuffixes(fileinfo.RepositoryName()))) + include_dir, include_base = os.path.split(_DropCommonSuffixes(include)) + if target_base == include_base and ( + include_dir == target_dir or + include_dir == os.path.normpath(target_dir + '/../public')): + return _LIKELY_MY_HEADER + + # If the target and include share some initial basename + # component, it's possible the target is implementing the + # include, so it's allowed to be first, but we'll never + # complain if it's not there. + target_first_component = _RE_FIRST_COMPONENT.match(target_base) + include_first_component = _RE_FIRST_COMPONENT.match(include_base) + if (target_first_component and include_first_component and + target_first_component.group(0) == + include_first_component.group(0)): + return _POSSIBLE_MY_HEADER + + return _OTHER_HEADER + + + +def CheckIncludeLine(filename, clean_lines, linenum, include_state, error): + """Check rules that are applicable to #include lines. + + Strings on #include lines are NOT removed from elided line, to make + certain tasks easier. However, to prevent false positives, checks + applicable to #include lines in CheckLanguage must be put here. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + include_state: An _IncludeState instance in which the headers are inserted. + error: The function to call with any errors found. + """ + fileinfo = FileInfo(filename) + line = clean_lines.lines[linenum] + + # "include" should use the new style "foo/bar.h" instead of just "bar.h" + # Only do this check if the included header follows google naming + # conventions. If not, assume that it's a 3rd party API that + # requires special include conventions. + # + # We also make an exception for Lua headers, which follow google + # naming convention but not the include convention. + match = Match(r'#include\s*"([^/]+\.h)"', line) + if match and not _THIRD_PARTY_HEADERS_PATTERN.match(match.group(1)): + error(filename, linenum, 'build/include', 4, + 'Include the directory when naming .h files') + + # we shouldn't include a file more than once. actually, there are a + # handful of instances where doing so is okay, but in general it's + # not. + match = _RE_PATTERN_INCLUDE.search(line) + if match: + include = match.group(2) + is_system = (match.group(1) == '<') + duplicate_line = include_state.FindHeader(include) + if duplicate_line >= 0: + error(filename, linenum, 'build/include', 4, + '"%s" already included at %s:%s' % + (include, filename, duplicate_line)) + elif (include.endswith('.cc') and + os.path.dirname(fileinfo.RepositoryName()) != os.path.dirname(include)): + error(filename, linenum, 'build/include', 4, + 'Do not include .cc files from other packages') + elif not _THIRD_PARTY_HEADERS_PATTERN.match(include): + include_state.include_list[-1].append((include, linenum)) + + # We want to ensure that headers appear in the right order: + # 1) for foo.cc, foo.h (preferred location) + # 2) c system files + # 3) cpp system files + # 4) for foo.cc, foo.h (deprecated location) + # 5) other google headers + # + # We classify each include statement as one of those 5 types + # using a number of techniques. The include_state object keeps + # track of the highest type seen, and complains if we see a + # lower type after that. + error_message = include_state.CheckNextIncludeOrder( + _ClassifyInclude(fileinfo, include, is_system)) + if error_message: + error(filename, linenum, 'build/include_order', 4, + '%s. Should be: %s.h, c system, c++ system, other.' % + (error_message, fileinfo.BaseName())) + canonical_include = include_state.CanonicalizeAlphabeticalOrder(include) + if not include_state.IsInAlphabeticalOrder( + clean_lines, linenum, canonical_include): + error(filename, linenum, 'build/include_alpha', 4, + 'Include "%s" not in alphabetical order' % include) + include_state.SetLastHeader(canonical_include) + + + +def _GetTextInside(text, start_pattern): + r"""Retrieves all the text between matching open and close parentheses. + + Given a string of lines and a regular expression string, retrieve all the text + following the expression and between opening punctuation symbols like + (, [, or {, and the matching close-punctuation symbol. This properly nested + occurrences of the punctuations, so for the text like + printf(a(), b(c())); + a call to _GetTextInside(text, r'printf\(') will return 'a(), b(c())'. + start_pattern must match string having an open punctuation symbol at the end. + + Args: + text: The lines to extract text. Its comments and strings must be elided. + It can be single line and can span multiple lines. + start_pattern: The regexp string indicating where to start extracting + the text. + Returns: + The extracted text. + None if either the opening string or ending punctuation could not be found. + """ + # TODO(unknown): Audit cpplint.py to see what places could be profitably + # rewritten to use _GetTextInside (and use inferior regexp matching today). + + # Give opening punctuations to get the matching close-punctuations. + matching_punctuation = {'(': ')', '{': '}', '[': ']'} + closing_punctuation = set(matching_punctuation.itervalues()) + + # Find the position to start extracting text. + match = re.search(start_pattern, text, re.M) + if not match: # start_pattern not found in text. + return None + start_position = match.end(0) + + assert start_position > 0, ( + 'start_pattern must ends with an opening punctuation.') + assert text[start_position - 1] in matching_punctuation, ( + 'start_pattern must ends with an opening punctuation.') + # Stack of closing punctuations we expect to have in text after position. + punctuation_stack = [matching_punctuation[text[start_position - 1]]] + position = start_position + while punctuation_stack and position < len(text): + if text[position] == punctuation_stack[-1]: + punctuation_stack.pop() + elif text[position] in closing_punctuation: + # A closing punctuation without matching opening punctuations. + return None + elif text[position] in matching_punctuation: + punctuation_stack.append(matching_punctuation[text[position]]) + position += 1 + if punctuation_stack: + # Opening punctuations left without matching close-punctuations. + return None + # punctuations match. + return text[start_position:position - 1] + + +# Patterns for matching call-by-reference parameters. +# +# Supports nested templates up to 2 levels deep using this messy pattern: +# < (?: < (?: < [^<>]* +# > +# | [^<>] )* +# > +# | [^<>] )* +# > +_RE_PATTERN_IDENT = r'[_a-zA-Z]\w*' # =~ [[:alpha:]][[:alnum:]]* +_RE_PATTERN_TYPE = ( + r'(?:const\s+)?(?:typename\s+|class\s+|struct\s+|union\s+|enum\s+)?' + r'(?:\w|' + r'\s*<(?:<(?:<[^<>]*>|[^<>])*>|[^<>])*>|' + r'::)+') +# A call-by-reference parameter ends with '& identifier'. +_RE_PATTERN_REF_PARAM = re.compile( + r'(' + _RE_PATTERN_TYPE + r'(?:\s*(?:\bconst\b|[*]))*\s*' + r'&\s*' + _RE_PATTERN_IDENT + r')\s*(?:=[^,()]+)?[,)]') +# A call-by-const-reference parameter either ends with 'const& identifier' +# or looks like 'const type& identifier' when 'type' is atomic. +_RE_PATTERN_CONST_REF_PARAM = ( + r'(?:.*\s*\bconst\s*&\s*' + _RE_PATTERN_IDENT + + r'|const\s+' + _RE_PATTERN_TYPE + r'\s*&\s*' + _RE_PATTERN_IDENT + r')') + + +def CheckLanguage(filename, clean_lines, linenum, file_extension, + include_state, nesting_state, error): + """Checks rules from the 'C++ language rules' section of cppguide.html. + + Some of these rules are hard to test (function overloading, using + uint32 inappropriately), but we do the best we can. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + file_extension: The extension (without the dot) of the filename. + include_state: An _IncludeState instance in which the headers are inserted. + nesting_state: A NestingState instance which maintains information about + the current stack of nested blocks being parsed. + error: The function to call with any errors found. + """ + # If the line is empty or consists of entirely a comment, no need to + # check it. + line = clean_lines.elided[linenum] + if not line: + return + + match = _RE_PATTERN_INCLUDE.search(line) + if match: + CheckIncludeLine(filename, clean_lines, linenum, include_state, error) + return + + # Reset include state across preprocessor directives. This is meant + # to silence warnings for conditional includes. + match = Match(r'^\s*#\s*(if|ifdef|ifndef|elif|else|endif)\b', line) + if match: + include_state.ResetSection(match.group(1)) + + # Make Windows paths like Unix. + fullname = os.path.abspath(filename).replace('\\', '/') + + # Perform other checks now that we are sure that this is not an include line + CheckCasts(filename, clean_lines, linenum, error) + CheckGlobalStatic(filename, clean_lines, linenum, error) + CheckPrintf(filename, clean_lines, linenum, error) + + if file_extension == 'h': + # TODO(unknown): check that 1-arg constructors are explicit. + # How to tell it's a constructor? + # (handled in CheckForNonStandardConstructs for now) + # TODO(unknown): check that classes declare or disable copy/assign + # (level 1 error) + pass + + # Check if people are using the verboten C basic types. The only exception + # we regularly allow is "unsigned short port" for port. + if Search(r'\bshort port\b', line): + if not Search(r'\bunsigned short port\b', line): + error(filename, linenum, 'runtime/int', 4, + 'Use "unsigned short" for ports, not "short"') + else: + match = Search(r'\b(short|long(?! +double)|long long)\b', line) + if match: + error(filename, linenum, 'runtime/int', 4, + 'Use int16/int64/etc, rather than the C type %s' % match.group(1)) + + # Check if some verboten operator overloading is going on + # TODO(unknown): catch out-of-line unary operator&: + # class X {}; + # int operator&(const X& x) { return 42; } // unary operator& + # The trick is it's hard to tell apart from binary operator&: + # class Y { int operator&(const Y& x) { return 23; } }; // binary operator& + if Search(r'\boperator\s*&\s*\(\s*\)', line): + error(filename, linenum, 'runtime/operator', 4, + 'Unary operator& is dangerous. Do not use it.') + + # Check for suspicious usage of "if" like + # } if (a == b) { + if Search(r'\}\s*if\s*\(', line): + error(filename, linenum, 'readability/braces', 4, + 'Did you mean "else if"? If not, start a new line for "if".') + + # Check for potential format string bugs like printf(foo). + # We constrain the pattern not to pick things like DocidForPrintf(foo). + # Not perfect but it can catch printf(foo.c_str()) and printf(foo->c_str()) + # TODO(unknown): Catch the following case. Need to change the calling + # convention of the whole function to process multiple line to handle it. + # printf( + # boy_this_is_a_really_long_variable_that_cannot_fit_on_the_prev_line); + printf_args = _GetTextInside(line, r'(?i)\b(string)?printf\s*\(') + if printf_args: + match = Match(r'([\w.\->()]+)$', printf_args) + if match and match.group(1) != '__VA_ARGS__': + function_name = re.search(r'\b((?:string)?printf)\s*\(', + line, re.I).group(1) + error(filename, linenum, 'runtime/printf', 4, + 'Potential format string bug. Do %s("%%s", %s) instead.' + % (function_name, match.group(1))) + + # Check for potential memset bugs like memset(buf, sizeof(buf), 0). + match = Search(r'memset\s*\(([^,]*),\s*([^,]*),\s*0\s*\)', line) + if match and not Match(r"^''|-?[0-9]+|0x[0-9A-Fa-f]$", match.group(2)): + error(filename, linenum, 'runtime/memset', 4, + 'Did you mean "memset(%s, 0, %s)"?' + % (match.group(1), match.group(2))) + + if Search(r'\busing namespace\b', line): + error(filename, linenum, 'build/namespaces', 5, + 'Do not use namespace using-directives. ' + 'Use using-declarations instead.') + + # Detect variable-length arrays. + match = Match(r'\s*(.+::)?(\w+) [a-z]\w*\[(.+)];', line) + if (match and match.group(2) != 'return' and match.group(2) != 'delete' and + match.group(3).find(']') == -1): + # Split the size using space and arithmetic operators as delimiters. + # If any of the resulting tokens are not compile time constants then + # report the error. + tokens = re.split(r'\s|\+|\-|\*|\/|<<|>>]', match.group(3)) + is_const = True + skip_next = False + for tok in tokens: + if skip_next: + skip_next = False + continue + + if Search(r'sizeof\(.+\)', tok): continue + if Search(r'arraysize\(\w+\)', tok): continue + + tok = tok.lstrip('(') + tok = tok.rstrip(')') + if not tok: continue + if Match(r'\d+', tok): continue + if Match(r'0[xX][0-9a-fA-F]+', tok): continue + if Match(r'k[A-Z0-9]\w*', tok): continue + if Match(r'(.+::)?k[A-Z0-9]\w*', tok): continue + if Match(r'(.+::)?[A-Z][A-Z0-9_]*', tok): continue + # A catch all for tricky sizeof cases, including 'sizeof expression', + # 'sizeof(*type)', 'sizeof(const type)', 'sizeof(struct StructName)' + # requires skipping the next token because we split on ' ' and '*'. + if tok.startswith('sizeof'): + skip_next = True + continue + is_const = False + break + if not is_const: + error(filename, linenum, 'runtime/arrays', 1, + 'Do not use variable-length arrays. Use an appropriately named ' + "('k' followed by CamelCase) compile-time constant for the size.") + + # Check for use of unnamed namespaces in header files. Registration + # macros are typically OK, so we allow use of "namespace {" on lines + # that end with backslashes. + if (file_extension == 'h' + and Search(r'\bnamespace\s*{', line) + and line[-1] != '\\'): + error(filename, linenum, 'build/namespaces', 4, + 'Do not use unnamed namespaces in header files. See ' + 'http://google-styleguide.googlecode.com/svn/trunk/cppguide.xml#Namespaces' + ' for more information.') + + +def CheckGlobalStatic(filename, clean_lines, linenum, error): + """Check for unsafe global or static objects. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + error: The function to call with any errors found. + """ + line = clean_lines.elided[linenum] + + # Match two lines at a time to support multiline declarations + if linenum + 1 < clean_lines.NumLines() and not Search(r'[;({]', line): + line += clean_lines.elided[linenum + 1].strip() + + # Check for people declaring static/global STL strings at the top level. + # This is dangerous because the C++ language does not guarantee that + # globals with constructors are initialized before the first access. + match = Match( + r'((?:|static +)(?:|const +))string +([a-zA-Z0-9_:]+)\b(.*)', + line) + + # Remove false positives: + # - String pointers (as opposed to values). + # string *pointer + # const string *pointer + # string const *pointer + # string *const pointer + # + # - Functions and template specializations. + # string Function(... + # string Class::Method(... + # + # - Operators. These are matched separately because operator names + # cross non-word boundaries, and trying to match both operators + # and functions at the same time would decrease accuracy of + # matching identifiers. + # string Class::operator*() + if (match and + not Search(r'\bstring\b(\s+const)?\s*\*\s*(const\s+)?\w', line) and + not Search(r'\boperator\W', line) and + not Match(r'\s*(<.*>)?(::[a-zA-Z0-9_]+)*\s*\(([^"]|$)', match.group(3))): + error(filename, linenum, 'runtime/string', 4, + 'For a static/global string constant, use a C style string instead: ' + '"%schar %s[]".' % + (match.group(1), match.group(2))) + + if Search(r'\b([A-Za-z0-9_]*_)\(\1\)', line): + error(filename, linenum, 'runtime/init', 4, + 'You seem to be initializing a member variable with itself.') + + +def CheckPrintf(filename, clean_lines, linenum, error): + """Check for printf related issues. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + error: The function to call with any errors found. + """ + line = clean_lines.elided[linenum] + + # When snprintf is used, the second argument shouldn't be a literal. + match = Search(r'snprintf\s*\(([^,]*),\s*([0-9]*)\s*,', line) + if match and match.group(2) != '0': + # If 2nd arg is zero, snprintf is used to calculate size. + error(filename, linenum, 'runtime/printf', 3, + 'If you can, use sizeof(%s) instead of %s as the 2nd arg ' + 'to snprintf.' % (match.group(1), match.group(2))) + + # Check if some verboten C functions are being used. + if Search(r'\bsprintf\s*\(', line): + error(filename, linenum, 'runtime/printf', 5, + 'Never use sprintf. Use snprintf instead.') + match = Search(r'\b(strcpy|strcat)\s*\(', line) + if match: + error(filename, linenum, 'runtime/printf', 4, + 'Almost always, snprintf is better than %s' % match.group(1)) + + +def IsDerivedFunction(clean_lines, linenum): + """Check if current line contains an inherited function. + + Args: + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + Returns: + True if current line contains a function with "override" + virt-specifier. + """ + # Scan back a few lines for start of current function + for i in xrange(linenum, max(-1, linenum - 10), -1): + match = Match(r'^([^()]*\w+)\(', clean_lines.elided[i]) + if match: + # Look for "override" after the matching closing parenthesis + line, _, closing_paren = CloseExpression( + clean_lines, i, len(match.group(1))) + return (closing_paren >= 0 and + Search(r'\boverride\b', line[closing_paren:])) + return False + + +def IsOutOfLineMethodDefinition(clean_lines, linenum): + """Check if current line contains an out-of-line method definition. + + Args: + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + Returns: + True if current line contains an out-of-line method definition. + """ + # Scan back a few lines for start of current function + for i in xrange(linenum, max(-1, linenum - 10), -1): + if Match(r'^([^()]*\w+)\(', clean_lines.elided[i]): + return Match(r'^[^()]*\w+::\w+\(', clean_lines.elided[i]) is not None + return False + + +def IsInitializerList(clean_lines, linenum): + """Check if current line is inside constructor initializer list. + + Args: + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + Returns: + True if current line appears to be inside constructor initializer + list, False otherwise. + """ + for i in xrange(linenum, 1, -1): + line = clean_lines.elided[i] + if i == linenum: + remove_function_body = Match(r'^(.*)\{\s*$', line) + if remove_function_body: + line = remove_function_body.group(1) + + if Search(r'\s:\s*\w+[({]', line): + # A lone colon tend to indicate the start of a constructor + # initializer list. It could also be a ternary operator, which + # also tend to appear in constructor initializer lists as + # opposed to parameter lists. + return True + if Search(r'\}\s*,\s*$', line): + # A closing brace followed by a comma is probably the end of a + # brace-initialized member in constructor initializer list. + return True + if Search(r'[{};]\s*$', line): + # Found one of the following: + # - A closing brace or semicolon, probably the end of the previous + # function. + # - An opening brace, probably the start of current class or namespace. + # + # Current line is probably not inside an initializer list since + # we saw one of those things without seeing the starting colon. + return False + + # Got to the beginning of the file without seeing the start of + # constructor initializer list. + return False + + +def CheckForNonConstReference(filename, clean_lines, linenum, + nesting_state, error): + """Check for non-const references. + + Separate from CheckLanguage since it scans backwards from current + line, instead of scanning forward. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + nesting_state: A NestingState instance which maintains information about + the current stack of nested blocks being parsed. + error: The function to call with any errors found. + """ + # Do nothing if there is no '&' on current line. + line = clean_lines.elided[linenum] + if '&' not in line: + return + + # If a function is inherited, current function doesn't have much of + # a choice, so any non-const references should not be blamed on + # derived function. + if IsDerivedFunction(clean_lines, linenum): + return + + # Don't warn on out-of-line method definitions, as we would warn on the + # in-line declaration, if it isn't marked with 'override'. + if IsOutOfLineMethodDefinition(clean_lines, linenum): + return + + # Long type names may be broken across multiple lines, usually in one + # of these forms: + # LongType + # ::LongTypeContinued &identifier + # LongType:: + # LongTypeContinued &identifier + # LongType< + # ...>::LongTypeContinued &identifier + # + # If we detected a type split across two lines, join the previous + # line to current line so that we can match const references + # accordingly. + # + # Note that this only scans back one line, since scanning back + # arbitrary number of lines would be expensive. If you have a type + # that spans more than 2 lines, please use a typedef. + if linenum > 1: + previous = None + if Match(r'\s*::(?:[\w<>]|::)+\s*&\s*\S', line): + # previous_line\n + ::current_line + previous = Search(r'\b((?:const\s*)?(?:[\w<>]|::)+[\w<>])\s*$', + clean_lines.elided[linenum - 1]) + elif Match(r'\s*[a-zA-Z_]([\w<>]|::)+\s*&\s*\S', line): + # previous_line::\n + current_line + previous = Search(r'\b((?:const\s*)?(?:[\w<>]|::)+::)\s*$', + clean_lines.elided[linenum - 1]) + if previous: + line = previous.group(1) + line.lstrip() + else: + # Check for templated parameter that is split across multiple lines + endpos = line.rfind('>') + if endpos > -1: + (_, startline, startpos) = ReverseCloseExpression( + clean_lines, linenum, endpos) + if startpos > -1 and startline < linenum: + # Found the matching < on an earlier line, collect all + # pieces up to current line. + line = '' + for i in xrange(startline, linenum + 1): + line += clean_lines.elided[i].strip() + + # Check for non-const references in function parameters. A single '&' may + # found in the following places: + # inside expression: binary & for bitwise AND + # inside expression: unary & for taking the address of something + # inside declarators: reference parameter + # We will exclude the first two cases by checking that we are not inside a + # function body, including one that was just introduced by a trailing '{'. + # TODO(unknown): Doesn't account for 'catch(Exception& e)' [rare]. + if (nesting_state.previous_stack_top and + not (isinstance(nesting_state.previous_stack_top, _ClassInfo) or + isinstance(nesting_state.previous_stack_top, _NamespaceInfo))): + # Not at toplevel, not within a class, and not within a namespace + return + + # Avoid initializer lists. We only need to scan back from the + # current line for something that starts with ':'. + # + # We don't need to check the current line, since the '&' would + # appear inside the second set of parentheses on the current line as + # opposed to the first set. + if linenum > 0: + for i in xrange(linenum - 1, max(0, linenum - 10), -1): + previous_line = clean_lines.elided[i] + if not Search(r'[),]\s*$', previous_line): + break + if Match(r'^\s*:\s+\S', previous_line): + return + + # Avoid preprocessors + if Search(r'\\\s*$', line): + return + + # Avoid constructor initializer lists + if IsInitializerList(clean_lines, linenum): + return + + # We allow non-const references in a few standard places, like functions + # called "swap()" or iostream operators like "<<" or ">>". Do not check + # those function parameters. + # + # We also accept & in static_assert, which looks like a function but + # it's actually a declaration expression. + whitelisted_functions = (r'(?:[sS]wap(?:<\w:+>)?|' + r'operator\s*[<>][<>]|' + r'static_assert|COMPILE_ASSERT' + r')\s*\(') + if Search(whitelisted_functions, line): + return + elif not Search(r'\S+\([^)]*$', line): + # Don't see a whitelisted function on this line. Actually we + # didn't see any function name on this line, so this is likely a + # multi-line parameter list. Try a bit harder to catch this case. + for i in xrange(2): + if (linenum > i and + Search(whitelisted_functions, clean_lines.elided[linenum - i - 1])): + return + + decls = ReplaceAll(r'{[^}]*}', ' ', line) # exclude function body + for parameter in re.findall(_RE_PATTERN_REF_PARAM, decls): + if not Match(_RE_PATTERN_CONST_REF_PARAM, parameter): + error(filename, linenum, 'runtime/references', 2, + 'Is this a non-const reference? ' + 'If so, make const or use a pointer: ' + + ReplaceAll(' *<', '<', parameter)) + + +def CheckCasts(filename, clean_lines, linenum, error): + """Various cast related checks. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + error: The function to call with any errors found. + """ + line = clean_lines.elided[linenum] + + # Check to see if they're using an conversion function cast. + # I just try to capture the most common basic types, though there are more. + # Parameterless conversion functions, such as bool(), are allowed as they are + # probably a member operator declaration or default constructor. + match = Search( + r'(\bnew\s+|\S<\s*(?:const\s+)?)?\b' + r'(int|float|double|bool|char|int32|uint32|int64|uint64)' + r'(\([^)].*)', line) + expecting_function = ExpectingFunctionArgs(clean_lines, linenum) + if match and not expecting_function: + matched_type = match.group(2) + + # matched_new_or_template is used to silence two false positives: + # - New operators + # - Template arguments with function types + # + # For template arguments, we match on types immediately following + # an opening bracket without any spaces. This is a fast way to + # silence the common case where the function type is the first + # template argument. False negative with less-than comparison is + # avoided because those operators are usually followed by a space. + # + # function // bracket + no space = false positive + # value < double(42) // bracket + space = true positive + matched_new_or_template = match.group(1) + + # Avoid arrays by looking for brackets that come after the closing + # parenthesis. + if Match(r'\([^()]+\)\s*\[', match.group(3)): + return + + # Other things to ignore: + # - Function pointers + # - Casts to pointer types + # - Placement new + # - Alias declarations + matched_funcptr = match.group(3) + if (matched_new_or_template is None and + not (matched_funcptr and + (Match(r'\((?:[^() ]+::\s*\*\s*)?[^() ]+\)\s*\(', + matched_funcptr) or + matched_funcptr.startswith('(*)'))) and + not Match(r'\s*using\s+\S+\s*=\s*' + matched_type, line) and + not Search(r'new\(\S+\)\s*' + matched_type, line)): + error(filename, linenum, 'readability/casting', 4, + 'Using deprecated casting style. ' + 'Use static_cast<%s>(...) instead' % + matched_type) + + if not expecting_function: + CheckCStyleCast(filename, clean_lines, linenum, 'static_cast', + r'\((int|float|double|bool|char|u?int(16|32|64))\)', error) + + # This doesn't catch all cases. Consider (const char * const)"hello". + # + # (char *) "foo" should always be a const_cast (reinterpret_cast won't + # compile). + if CheckCStyleCast(filename, clean_lines, linenum, 'const_cast', + r'\((char\s?\*+\s?)\)\s*"', error): + pass + else: + # Check pointer casts for other than string constants + CheckCStyleCast(filename, clean_lines, linenum, 'reinterpret_cast', + r'\((\w+\s?\*+\s?)\)', error) + + # In addition, we look for people taking the address of a cast. This + # is dangerous -- casts can assign to temporaries, so the pointer doesn't + # point where you think. + # + # Some non-identifier character is required before the '&' for the + # expression to be recognized as a cast. These are casts: + # expression = &static_cast(temporary()); + # function(&(int*)(temporary())); + # + # This is not a cast: + # reference_type&(int* function_param); + match = Search( + r'(?:[^\w]&\(([^)*][^)]*)\)[\w(])|' + r'(?:[^\w]&(static|dynamic|down|reinterpret)_cast\b)', line) + if match: + # Try a better error message when the & is bound to something + # dereferenced by the casted pointer, as opposed to the casted + # pointer itself. + parenthesis_error = False + match = Match(r'^(.*&(?:static|dynamic|down|reinterpret)_cast\b)<', line) + if match: + _, y1, x1 = CloseExpression(clean_lines, linenum, len(match.group(1))) + if x1 >= 0 and clean_lines.elided[y1][x1] == '(': + _, y2, x2 = CloseExpression(clean_lines, y1, x1) + if x2 >= 0: + extended_line = clean_lines.elided[y2][x2:] + if y2 < clean_lines.NumLines() - 1: + extended_line += clean_lines.elided[y2 + 1] + if Match(r'\s*(?:->|\[)', extended_line): + parenthesis_error = True + + if parenthesis_error: + error(filename, linenum, 'readability/casting', 4, + ('Are you taking an address of something dereferenced ' + 'from a cast? Wrapping the dereferenced expression in ' + 'parentheses will make the binding more obvious')) + else: + error(filename, linenum, 'runtime/casting', 4, + ('Are you taking an address of a cast? ' + 'This is dangerous: could be a temp var. ' + 'Take the address before doing the cast, rather than after')) + + +def CheckCStyleCast(filename, clean_lines, linenum, cast_type, pattern, error): + """Checks for a C-style cast by looking for the pattern. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + cast_type: The string for the C++ cast to recommend. This is either + reinterpret_cast, static_cast, or const_cast, depending. + pattern: The regular expression used to find C-style casts. + error: The function to call with any errors found. + + Returns: + True if an error was emitted. + False otherwise. + """ + line = clean_lines.elided[linenum] + match = Search(pattern, line) + if not match: + return False + + # Exclude lines with keywords that tend to look like casts + context = line[0:match.start(1) - 1] + if Match(r'.*\b(?:sizeof|alignof|alignas|[_A-Z][_A-Z0-9]*)\s*$', context): + return False + + # Try expanding current context to see if we one level of + # parentheses inside a macro. + if linenum > 0: + for i in xrange(linenum - 1, max(0, linenum - 5), -1): + context = clean_lines.elided[i] + context + if Match(r'.*\b[_A-Z][_A-Z0-9]*\s*\((?:\([^()]*\)|[^()])*$', context): + return False + + # operator++(int) and operator--(int) + if context.endswith(' operator++') or context.endswith(' operator--'): + return False + + # A single unnamed argument for a function tends to look like old + # style cast. If we see those, don't issue warnings for deprecated + # casts, instead issue warnings for unnamed arguments where + # appropriate. + # + # These are things that we want warnings for, since the style guide + # explicitly require all parameters to be named: + # Function(int); + # Function(int) { + # ConstMember(int) const; + # ConstMember(int) const { + # ExceptionMember(int) throw (...); + # ExceptionMember(int) throw (...) { + # PureVirtual(int) = 0; + # [](int) -> bool { + # + # These are functions of some sort, where the compiler would be fine + # if they had named parameters, but people often omit those + # identifiers to reduce clutter: + # (FunctionPointer)(int); + # (FunctionPointer)(int) = value; + # Function((function_pointer_arg)(int)) + # Function((function_pointer_arg)(int), int param) + # ; + # <(FunctionPointerTemplateArgument)(int)>; + remainder = line[match.end(0):] + if Match(r'^\s*(?:;|const\b|throw\b|final\b|override\b|[=>{),]|->)', + remainder): + # Looks like an unnamed parameter. + + # Don't warn on any kind of template arguments. + if Match(r'^\s*>', remainder): + return False + + # Don't warn on assignments to function pointers, but keep warnings for + # unnamed parameters to pure virtual functions. Note that this pattern + # will also pass on assignments of "0" to function pointers, but the + # preferred values for those would be "nullptr" or "NULL". + matched_zero = Match(r'^\s=\s*(\S+)\s*;', remainder) + if matched_zero and matched_zero.group(1) != '0': + return False + + # Don't warn on function pointer declarations. For this we need + # to check what came before the "(type)" string. + if Match(r'.*\)\s*$', line[0:match.start(0)]): + return False + + # Don't warn if the parameter is named with block comments, e.g.: + # Function(int /*unused_param*/); + raw_line = clean_lines.raw_lines[linenum] + if '/*' in raw_line: + return False + + # Passed all filters, issue warning here. + error(filename, linenum, 'readability/function', 3, + 'All parameters should be named in a function') + return True + + # At this point, all that should be left is actual casts. + error(filename, linenum, 'readability/casting', 4, + 'Using C-style cast. Use %s<%s>(...) instead' % + (cast_type, match.group(1))) + + return True + + +def ExpectingFunctionArgs(clean_lines, linenum): + """Checks whether where function type arguments are expected. + + Args: + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + + Returns: + True if the line at 'linenum' is inside something that expects arguments + of function types. + """ + line = clean_lines.elided[linenum] + return (Match(r'^\s*MOCK_(CONST_)?METHOD\d+(_T)?\(', line) or + (linenum >= 2 and + (Match(r'^\s*MOCK_(?:CONST_)?METHOD\d+(?:_T)?\((?:\S+,)?\s*$', + clean_lines.elided[linenum - 1]) or + Match(r'^\s*MOCK_(?:CONST_)?METHOD\d+(?:_T)?\(\s*$', + clean_lines.elided[linenum - 2]) or + Search(r'\bstd::m?function\s*\<\s*$', + clean_lines.elided[linenum - 1])))) + + +_HEADERS_CONTAINING_TEMPLATES = ( + ('', ('deque',)), + ('', ('unary_function', 'binary_function', + 'plus', 'minus', 'multiplies', 'divides', 'modulus', + 'negate', + 'equal_to', 'not_equal_to', 'greater', 'less', + 'greater_equal', 'less_equal', + 'logical_and', 'logical_or', 'logical_not', + 'unary_negate', 'not1', 'binary_negate', 'not2', + 'bind1st', 'bind2nd', + 'pointer_to_unary_function', + 'pointer_to_binary_function', + 'ptr_fun', + 'mem_fun_t', 'mem_fun', 'mem_fun1_t', 'mem_fun1_ref_t', + 'mem_fun_ref_t', + 'const_mem_fun_t', 'const_mem_fun1_t', + 'const_mem_fun_ref_t', 'const_mem_fun1_ref_t', + 'mem_fun_ref', + )), + ('', ('numeric_limits',)), + ('', ('list',)), + ('', ('map', 'multimap',)), + ('', ('allocator',)), + ('', ('queue', 'priority_queue',)), + ('', ('set', 'multiset',)), + ('', ('stack',)), + ('', ('char_traits', 'basic_string',)), + ('', ('tuple',)), + ('', ('pair',)), + ('', ('vector',)), + + # gcc extensions. + # Note: std::hash is their hash, ::hash is our hash + ('', ('hash_map', 'hash_multimap',)), + ('', ('hash_set', 'hash_multiset',)), + ('', ('slist',)), + ) + +_RE_PATTERN_STRING = re.compile(r'\bstring\b') + +_re_pattern_algorithm_header = [] +for _template in ('copy', 'max', 'min', 'min_element', 'sort', 'swap', + 'transform'): + # Match max(..., ...), max(..., ...), but not foo->max, foo.max or + # type::max(). + _re_pattern_algorithm_header.append( + (re.compile(r'[^>.]\b' + _template + r'(<.*?>)?\([^\)]'), + _template, + '')) + +_re_pattern_templates = [] +for _header, _templates in _HEADERS_CONTAINING_TEMPLATES: + for _template in _templates: + _re_pattern_templates.append( + (re.compile(r'(\<|\b)' + _template + r'\s*\<'), + _template + '<>', + _header)) + + +def FilesBelongToSameModule(filename_cc, filename_h): + """Check if these two filenames belong to the same module. + + The concept of a 'module' here is a as follows: + foo.h, foo-inl.h, foo.cc, foo_test.cc and foo_unittest.cc belong to the + same 'module' if they are in the same directory. + some/path/public/xyzzy and some/path/internal/xyzzy are also considered + to belong to the same module here. + + If the filename_cc contains a longer path than the filename_h, for example, + '/absolute/path/to/base/sysinfo.cc', and this file would include + 'base/sysinfo.h', this function also produces the prefix needed to open the + header. This is used by the caller of this function to more robustly open the + header file. We don't have access to the real include paths in this context, + so we need this guesswork here. + + Known bugs: tools/base/bar.cc and base/bar.h belong to the same module + according to this implementation. Because of this, this function gives + some false positives. This should be sufficiently rare in practice. + + Args: + filename_cc: is the path for the .cc file + filename_h: is the path for the header path + + Returns: + Tuple with a bool and a string: + bool: True if filename_cc and filename_h belong to the same module. + string: the additional prefix needed to open the header file. + """ + + if not filename_cc.endswith('.cc'): + return (False, '') + filename_cc = filename_cc[:-len('.cc')] + if filename_cc.endswith('_unittest'): + filename_cc = filename_cc[:-len('_unittest')] + elif filename_cc.endswith('_test'): + filename_cc = filename_cc[:-len('_test')] + filename_cc = filename_cc.replace('/public/', '/') + filename_cc = filename_cc.replace('/internal/', '/') + + if not filename_h.endswith('.h'): + return (False, '') + filename_h = filename_h[:-len('.h')] + if filename_h.endswith('-inl'): + filename_h = filename_h[:-len('-inl')] + filename_h = filename_h.replace('/public/', '/') + filename_h = filename_h.replace('/internal/', '/') + + files_belong_to_same_module = filename_cc.endswith(filename_h) + common_path = '' + if files_belong_to_same_module: + common_path = filename_cc[:-len(filename_h)] + return files_belong_to_same_module, common_path + + +def UpdateIncludeState(filename, include_dict, io=codecs): + """Fill up the include_dict with new includes found from the file. + + Args: + filename: the name of the header to read. + include_dict: a dictionary in which the headers are inserted. + io: The io factory to use to read the file. Provided for testability. + + Returns: + True if a header was successfully added. False otherwise. + """ + headerfile = None + try: + headerfile = io.open(filename, 'r', 'utf8', 'replace') + except IOError: + return False + linenum = 0 + for line in headerfile: + linenum += 1 + clean_line = CleanseComments(line) + match = _RE_PATTERN_INCLUDE.search(clean_line) + if match: + include = match.group(2) + include_dict.setdefault(include, linenum) + return True + + +def CheckForIncludeWhatYouUse(filename, clean_lines, include_state, error, + io=codecs): + """Reports for missing stl includes. + + This function will output warnings to make sure you are including the headers + necessary for the stl containers and functions that you use. We only give one + reason to include a header. For example, if you use both equal_to<> and + less<> in a .h file, only one (the latter in the file) of these will be + reported as a reason to include the . + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + include_state: An _IncludeState instance. + error: The function to call with any errors found. + io: The IO factory to use to read the header file. Provided for unittest + injection. + """ + required = {} # A map of header name to linenumber and the template entity. + # Example of required: { '': (1219, 'less<>') } + + for linenum in xrange(clean_lines.NumLines()): + line = clean_lines.elided[linenum] + if not line or line[0] == '#': + continue + + # String is special -- it is a non-templatized type in STL. + matched = _RE_PATTERN_STRING.search(line) + if matched: + # Don't warn about strings in non-STL namespaces: + # (We check only the first match per line; good enough.) + prefix = line[:matched.start()] + if prefix.endswith('std::') or not prefix.endswith('::'): + required[''] = (linenum, 'string') + + for pattern, template, header in _re_pattern_algorithm_header: + if pattern.search(line): + required[header] = (linenum, template) + + # The following function is just a speed up, no semantics are changed. + if not '<' in line: # Reduces the cpu time usage by skipping lines. + continue + + for pattern, template, header in _re_pattern_templates: + if pattern.search(line): + required[header] = (linenum, template) + + # The policy is that if you #include something in foo.h you don't need to + # include it again in foo.cc. Here, we will look at possible includes. + # Let's flatten the include_state include_list and copy it into a dictionary. + include_dict = dict([item for sublist in include_state.include_list + for item in sublist]) + + # Did we find the header for this file (if any) and successfully load it? + header_found = False + + # Use the absolute path so that matching works properly. + abs_filename = FileInfo(filename).FullName() + + # For Emacs's flymake. + # If cpplint is invoked from Emacs's flymake, a temporary file is generated + # by flymake and that file name might end with '_flymake.cc'. In that case, + # restore original file name here so that the corresponding header file can be + # found. + # e.g. If the file name is 'foo_flymake.cc', we should search for 'foo.h' + # instead of 'foo_flymake.h' + abs_filename = re.sub(r'_flymake\.cc$', '.cc', abs_filename) + + # include_dict is modified during iteration, so we iterate over a copy of + # the keys. + header_keys = include_dict.keys() + for header in header_keys: + (same_module, common_path) = FilesBelongToSameModule(abs_filename, header) + fullpath = common_path + header + if same_module and UpdateIncludeState(fullpath, include_dict, io): + header_found = True + + # If we can't find the header file for a .cc, assume it's because we don't + # know where to look. In that case we'll give up as we're not sure they + # didn't include it in the .h file. + # TODO(unknown): Do a better job of finding .h files so we are confident that + # not having the .h file means there isn't one. + if filename.endswith('.cc') and not header_found: + return + + # All the lines have been processed, report the errors found. + for required_header_unstripped in required: + template = required[required_header_unstripped][1] + if required_header_unstripped.strip('<>"') not in include_dict: + error(filename, required[required_header_unstripped][0], + 'build/include_what_you_use', 4, + 'Add #include ' + required_header_unstripped + ' for ' + template) + + +_RE_PATTERN_EXPLICIT_MAKEPAIR = re.compile(r'\bmake_pair\s*<') + + +def CheckMakePairUsesDeduction(filename, clean_lines, linenum, error): + """Check that make_pair's template arguments are deduced. + + G++ 4.6 in C++11 mode fails badly if make_pair's template arguments are + specified explicitly, and such use isn't intended in any case. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + error: The function to call with any errors found. + """ + line = clean_lines.elided[linenum] + match = _RE_PATTERN_EXPLICIT_MAKEPAIR.search(line) + if match: + error(filename, linenum, 'build/explicit_make_pair', + 4, # 4 = high confidence + 'For C++11-compatibility, omit template arguments from make_pair' + ' OR use pair directly OR if appropriate, construct a pair directly') + + +def CheckDefaultLambdaCaptures(filename, clean_lines, linenum, error): + """Check that default lambda captures are not used. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + error: The function to call with any errors found. + """ + line = clean_lines.elided[linenum] + + # A lambda introducer specifies a default capture if it starts with "[=" + # or if it starts with "[&" _not_ followed by an identifier. + match = Match(r'^(.*)\[\s*(?:=|&[^\w])', line) + if match: + # Found a potential error, check what comes after the lambda-introducer. + # If it's not open parenthesis (for lambda-declarator) or open brace + # (for compound-statement), it's not a lambda. + line, _, pos = CloseExpression(clean_lines, linenum, len(match.group(1))) + if pos >= 0 and Match(r'^\s*[{(]', line[pos:]): + error(filename, linenum, 'build/c++11', + 4, # 4 = high confidence + 'Default lambda captures are an unapproved C++ feature.') + + +def CheckRedundantVirtual(filename, clean_lines, linenum, error): + """Check if line contains a redundant "virtual" function-specifier. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + error: The function to call with any errors found. + """ + # Look for "virtual" on current line. + line = clean_lines.elided[linenum] + virtual = Match(r'^(.*)(\bvirtual\b)(.*)$', line) + if not virtual: return + + # Ignore "virtual" keywords that are near access-specifiers. These + # are only used in class base-specifier and do not apply to member + # functions. + if (Search(r'\b(public|protected|private)\s+$', virtual.group(1)) or + Match(r'^\s+(public|protected|private)\b', virtual.group(3))): + return + + # Ignore the "virtual" keyword from virtual base classes. Usually + # there is a column on the same line in these cases (virtual base + # classes are rare in google3 because multiple inheritance is rare). + if Match(r'^.*[^:]:[^:].*$', line): return + + # Look for the next opening parenthesis. This is the start of the + # parameter list (possibly on the next line shortly after virtual). + # TODO(unknown): doesn't work if there are virtual functions with + # decltype() or other things that use parentheses, but csearch suggests + # that this is rare. + end_col = -1 + end_line = -1 + start_col = len(virtual.group(2)) + for start_line in xrange(linenum, min(linenum + 3, clean_lines.NumLines())): + line = clean_lines.elided[start_line][start_col:] + parameter_list = Match(r'^([^(]*)\(', line) + if parameter_list: + # Match parentheses to find the end of the parameter list + (_, end_line, end_col) = CloseExpression( + clean_lines, start_line, start_col + len(parameter_list.group(1))) + break + start_col = 0 + + if end_col < 0: + return # Couldn't find end of parameter list, give up + + # Look for "override" or "final" after the parameter list + # (possibly on the next few lines). + for i in xrange(end_line, min(end_line + 3, clean_lines.NumLines())): + line = clean_lines.elided[i][end_col:] + match = Search(r'\b(override|final)\b', line) + if match: + error(filename, linenum, 'readability/inheritance', 4, + ('"virtual" is redundant since function is ' + 'already declared as "%s"' % match.group(1))) + + # Set end_col to check whole lines after we are done with the + # first line. + end_col = 0 + if Search(r'[^\w]\s*$', line): + break + + +def CheckRedundantOverrideOrFinal(filename, clean_lines, linenum, error): + """Check if line contains a redundant "override" or "final" virt-specifier. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + error: The function to call with any errors found. + """ + # Look for closing parenthesis nearby. We need one to confirm where + # the declarator ends and where the virt-specifier starts to avoid + # false positives. + line = clean_lines.elided[linenum] + declarator_end = line.rfind(')') + if declarator_end >= 0: + fragment = line[declarator_end:] + else: + if linenum > 1 and clean_lines.elided[linenum - 1].rfind(')') >= 0: + fragment = line + else: + return + + # Check that at most one of "override" or "final" is present, not both + if Search(r'\boverride\b', fragment) and Search(r'\bfinal\b', fragment): + error(filename, linenum, 'readability/inheritance', 4, + ('"override" is redundant since function is ' + 'already declared as "final"')) + + + + +# Returns true if we are at a new block, and it is directly +# inside of a namespace. +def IsBlockInNameSpace(nesting_state, is_forward_declaration): + """Checks that the new block is directly in a namespace. + + Args: + nesting_state: The _NestingState object that contains info about our state. + is_forward_declaration: If the class is a forward declared class. + Returns: + Whether or not the new block is directly in a namespace. + """ + if is_forward_declaration: + if len(nesting_state.stack) >= 1 and ( + isinstance(nesting_state.stack[-1], _NamespaceInfo)): + return True + else: + return False + + return (len(nesting_state.stack) > 1 and + nesting_state.stack[-1].check_namespace_indentation and + isinstance(nesting_state.stack[-2], _NamespaceInfo)) + + +def ShouldCheckNamespaceIndentation(nesting_state, is_namespace_indent_item, + raw_lines_no_comments, linenum): + """This method determines if we should apply our namespace indentation check. + + Args: + nesting_state: The current nesting state. + is_namespace_indent_item: If we just put a new class on the stack, True. + If the top of the stack is not a class, or we did not recently + add the class, False. + raw_lines_no_comments: The lines without the comments. + linenum: The current line number we are processing. + + Returns: + True if we should apply our namespace indentation check. Currently, it + only works for classes and namespaces inside of a namespace. + """ + + is_forward_declaration = IsForwardClassDeclaration(raw_lines_no_comments, + linenum) + + if not (is_namespace_indent_item or is_forward_declaration): + return False + + # If we are in a macro, we do not want to check the namespace indentation. + if IsMacroDefinition(raw_lines_no_comments, linenum): + return False + + return IsBlockInNameSpace(nesting_state, is_forward_declaration) + + +# Call this method if the line is directly inside of a namespace. +# If the line above is blank (excluding comments) or the start of +# an inner namespace, it cannot be indented. +def CheckItemIndentationInNamespace(filename, raw_lines_no_comments, linenum, + error): + line = raw_lines_no_comments[linenum] + if Match(r'^\s+', line): + error(filename, linenum, 'runtime/indentation_namespace', 4, + 'Do not indent within a namespace') + + +def ProcessLine(filename, file_extension, clean_lines, line, + include_state, function_state, nesting_state, error, + extra_check_functions=[]): + """Processes a single line in the file. + + Args: + filename: Filename of the file that is being processed. + file_extension: The extension (dot not included) of the file. + clean_lines: An array of strings, each representing a line of the file, + with comments stripped. + line: Number of line being processed. + include_state: An _IncludeState instance in which the headers are inserted. + function_state: A _FunctionState instance which counts function lines, etc. + nesting_state: A NestingState instance which maintains information about + the current stack of nested blocks being parsed. + error: A callable to which errors are reported, which takes 4 arguments: + filename, line number, error level, and message + extra_check_functions: An array of additional check functions that will be + run on each source line. Each function takes 4 + arguments: filename, clean_lines, line, error + """ + raw_lines = clean_lines.raw_lines + ParseNolintSuppressions(filename, raw_lines[line], line, error) + nesting_state.Update(filename, clean_lines, line, error) + CheckForNamespaceIndentation(filename, nesting_state, clean_lines, line, + error) + if nesting_state.InAsmBlock(): return + CheckForFunctionLengths(filename, clean_lines, line, function_state, error) + CheckForMultilineCommentsAndStrings(filename, clean_lines, line, error) + CheckStyle(filename, clean_lines, line, file_extension, nesting_state, error) + CheckLanguage(filename, clean_lines, line, file_extension, include_state, + nesting_state, error) + CheckForNonConstReference(filename, clean_lines, line, nesting_state, error) + CheckForNonStandardConstructs(filename, clean_lines, line, + nesting_state, error) + CheckVlogArguments(filename, clean_lines, line, error) + CheckPosixThreading(filename, clean_lines, line, error) + CheckInvalidIncrement(filename, clean_lines, line, error) + CheckMakePairUsesDeduction(filename, clean_lines, line, error) + CheckDefaultLambdaCaptures(filename, clean_lines, line, error) + CheckRedundantVirtual(filename, clean_lines, line, error) + CheckRedundantOverrideOrFinal(filename, clean_lines, line, error) + for check_fn in extra_check_functions: + check_fn(filename, clean_lines, line, error) + +def FlagCxx11Features(filename, clean_lines, linenum, error): + """Flag those c++11 features that we only allow in certain places. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + error: The function to call with any errors found. + """ + line = clean_lines.elided[linenum] + + # Flag unapproved C++11 headers. + include = Match(r'\s*#\s*include\s+[<"]([^<"]+)[">]', line) + if include and include.group(1) in ('cfenv', + 'condition_variable', + 'fenv.h', + 'future', + 'mutex', + 'thread', + 'chrono', + 'ratio', + 'regex', + 'system_error', + ): + error(filename, linenum, 'build/c++11', 5, + ('<%s> is an unapproved C++11 header.') % include.group(1)) + + # The only place where we need to worry about C++11 keywords and library + # features in preprocessor directives is in macro definitions. + if Match(r'\s*#', line) and not Match(r'\s*#\s*define\b', line): return + + # These are classes and free functions. The classes are always + # mentioned as std::*, but we only catch the free functions if + # they're not found by ADL. They're alphabetical by header. + for top_name in ( + # type_traits + 'alignment_of', + 'aligned_union', + ): + if Search(r'\bstd::%s\b' % top_name, line): + error(filename, linenum, 'build/c++11', 5, + ('std::%s is an unapproved C++11 class or function. Send c-style ' + 'an example of where it would make your code more readable, and ' + 'they may let you use it.') % top_name) + + +def ProcessFileData(filename, file_extension, lines, error, + extra_check_functions=[]): + """Performs lint checks and reports any errors to the given error function. + + Args: + filename: Filename of the file that is being processed. + file_extension: The extension (dot not included) of the file. + lines: An array of strings, each representing a line of the file, with the + last element being empty if the file is terminated with a newline. + error: A callable to which errors are reported, which takes 4 arguments: + filename, line number, error level, and message + extra_check_functions: An array of additional check functions that will be + run on each source line. Each function takes 4 + arguments: filename, clean_lines, line, error + """ + lines = (['// marker so line numbers and indices both start at 1'] + lines + + ['// marker so line numbers end in a known way']) + + include_state = _IncludeState() + function_state = _FunctionState() + nesting_state = NestingState() + + ResetNolintSuppressions() + + CheckForCopyright(filename, lines, error) + + RemoveMultiLineComments(filename, lines, error) + clean_lines = CleansedLines(lines) + + if file_extension == 'h': + CheckForHeaderGuard(filename, clean_lines, error) + + for line in xrange(clean_lines.NumLines()): + ProcessLine(filename, file_extension, clean_lines, line, + include_state, function_state, nesting_state, error, + extra_check_functions) + FlagCxx11Features(filename, clean_lines, line, error) + nesting_state.CheckCompletedBlocks(filename, error) + + CheckForIncludeWhatYouUse(filename, clean_lines, include_state, error) + + # Check that the .cc file has included its header if it exists. + if file_extension == 'cc': + CheckHeaderFileIncluded(filename, include_state, error) + + # We check here rather than inside ProcessLine so that we see raw + # lines rather than "cleaned" lines. + CheckForBadCharacters(filename, lines, error) + + CheckForNewlineAtEOF(filename, lines, error) + +def ProcessConfigOverrides(filename): + """ Loads the configuration files and processes the config overrides. + + Args: + filename: The name of the file being processed by the linter. + + Returns: + False if the current |filename| should not be processed further. + """ + + abs_filename = os.path.abspath(filename) + cfg_filters = [] + keep_looking = True + while keep_looking: + abs_path, base_name = os.path.split(abs_filename) + if not base_name: + break # Reached the root directory. + + cfg_file = os.path.join(abs_path, "CPPLINT.cfg") + abs_filename = abs_path + if not os.path.isfile(cfg_file): + continue + + try: + with open(cfg_file) as file_handle: + for line in file_handle: + line, _, _ = line.partition('#') # Remove comments. + if not line.strip(): + continue + + name, _, val = line.partition('=') + name = name.strip() + val = val.strip() + if name == 'set noparent': + keep_looking = False + elif name == 'filter': + cfg_filters.append(val) + elif name == 'exclude_files': + # When matching exclude_files pattern, use the base_name of + # the current file name or the directory name we are processing. + # For example, if we are checking for lint errors in /foo/bar/baz.cc + # and we found the .cfg file at /foo/CPPLINT.cfg, then the config + # file's "exclude_files" filter is meant to be checked against "bar" + # and not "baz" nor "bar/baz.cc". + if base_name: + pattern = re.compile(val) + if pattern.match(base_name): + sys.stderr.write('Ignoring "%s": file excluded by "%s". ' + 'File path component "%s" matches ' + 'pattern "%s"\n' % + (filename, cfg_file, base_name, val)) + return False + elif name == 'linelength': + global _line_length + try: + _line_length = int(val) + except ValueError: + sys.stderr.write('Line length must be numeric.') + else: + sys.stderr.write( + 'Invalid configuration option (%s) in file %s\n' % + (name, cfg_file)) + + except IOError: + sys.stderr.write( + "Skipping config file '%s': Can't open for reading\n" % cfg_file) + keep_looking = False + + # Apply all the accumulated filters in reverse order (top-level directory + # config options having the least priority). + for filter in reversed(cfg_filters): + _AddFilters(filter) + + return True + + +def ProcessFile(filename, vlevel, extra_check_functions=[]): + """Does google-lint on a single file. + + Args: + filename: The name of the file to parse. + + vlevel: The level of errors to report. Every error of confidence + >= verbose_level will be reported. 0 is a good default. + + extra_check_functions: An array of additional check functions that will be + run on each source line. Each function takes 4 + arguments: filename, clean_lines, line, error + """ + + _SetVerboseLevel(vlevel) + _BackupFilters() + + if not ProcessConfigOverrides(filename): + _RestoreFilters() + return + + lf_lines = [] + crlf_lines = [] + try: + # Support the UNIX convention of using "-" for stdin. Note that + # we are not opening the file with universal newline support + # (which codecs doesn't support anyway), so the resulting lines do + # contain trailing '\r' characters if we are reading a file that + # has CRLF endings. + # If after the split a trailing '\r' is present, it is removed + # below. + if filename == '-': + lines = codecs.StreamReaderWriter(sys.stdin, + codecs.getreader('utf8'), + codecs.getwriter('utf8'), + 'replace').read().split('\n') + else: + lines = codecs.open(filename, 'r', 'utf8', 'replace').read().split('\n') + + # Remove trailing '\r'. + # The -1 accounts for the extra trailing blank line we get from split() + for linenum in range(len(lines) - 1): + if lines[linenum].endswith('\r'): + lines[linenum] = lines[linenum].rstrip('\r') + crlf_lines.append(linenum + 1) + else: + lf_lines.append(linenum + 1) + + except IOError: + sys.stderr.write( + "Skipping input '%s': Can't open for reading\n" % filename) + _RestoreFilters() + return + + # Note, if no dot is found, this will give the entire filename as the ext. + file_extension = filename[filename.rfind('.') + 1:] + + # When reading from stdin, the extension is unknown, so no cpplint tests + # should rely on the extension. + if filename != '-' and file_extension not in _valid_extensions: + sys.stderr.write('Ignoring %s; not a valid file name ' + '(%s)\n' % (filename, ', '.join(_valid_extensions))) + else: + ProcessFileData(filename, file_extension, lines, Error, + extra_check_functions) + + # If end-of-line sequences are a mix of LF and CR-LF, issue + # warnings on the lines with CR. + # + # Don't issue any warnings if all lines are uniformly LF or CR-LF, + # since critique can handle these just fine, and the style guide + # doesn't dictate a particular end of line sequence. + # + # We can't depend on os.linesep to determine what the desired + # end-of-line sequence should be, since that will return the + # server-side end-of-line sequence. + if lf_lines and crlf_lines: + # Warn on every line with CR. An alternative approach might be to + # check whether the file is mostly CRLF or just LF, and warn on the + # minority, we bias toward LF here since most tools prefer LF. + for linenum in crlf_lines: + Error(filename, linenum, 'whitespace/newline', 1, + 'Unexpected \\r (^M) found; better to use only \\n') + + sys.stderr.write('Done processing %s\n' % filename) + _RestoreFilters() + + +def PrintUsage(message): + """Prints a brief usage string and exits, optionally with an error message. + + Args: + message: The optional error message. + """ + sys.stderr.write(_USAGE) + if message: + sys.exit('\nFATAL ERROR: ' + message) + else: + sys.exit(1) + + +def PrintCategories(): + """Prints a list of all the error-categories used by error messages. + + These are the categories used to filter messages via --filter. + """ + sys.stderr.write(''.join(' %s\n' % cat for cat in _ERROR_CATEGORIES)) + sys.exit(0) + + +def ParseArguments(args): + """Parses the command line arguments. + + This may set the output format and verbosity level as side-effects. + + Args: + args: The command line arguments: + + Returns: + The list of filenames to lint. + """ + try: + (opts, filenames) = getopt.getopt(args, '', ['help', 'output=', 'verbose=', + 'counting=', + 'filter=', + 'root=', + 'linelength=', + 'extensions=']) + except getopt.GetoptError: + PrintUsage('Invalid arguments.') + + verbosity = _VerboseLevel() + output_format = _OutputFormat() + filters = '' + counting_style = '' + + for (opt, val) in opts: + if opt == '--help': + PrintUsage(None) + elif opt == '--output': + if val not in ('emacs', 'vs7', 'eclipse'): + PrintUsage('The only allowed output formats are emacs, vs7 and eclipse.') + output_format = val + elif opt == '--verbose': + verbosity = int(val) + elif opt == '--filter': + filters = val + if not filters: + PrintCategories() + elif opt == '--counting': + if val not in ('total', 'toplevel', 'detailed'): + PrintUsage('Valid counting options are total, toplevel, and detailed') + counting_style = val + elif opt == '--root': + global _root + _root = val + elif opt == '--linelength': + global _line_length + try: + _line_length = int(val) + except ValueError: + PrintUsage('Line length must be digits.') + elif opt == '--extensions': + global _valid_extensions + try: + _valid_extensions = set(val.split(',')) + except ValueError: + PrintUsage('Extensions must be comma seperated list.') + + if not filenames: + PrintUsage('No files were specified.') + + _SetOutputFormat(output_format) + _SetVerboseLevel(verbosity) + _SetFilters(filters) + _SetCountingStyle(counting_style) + + return filenames + + +def main(): + filenames = ParseArguments(sys.argv[1:]) + + # Change stderr to write with replacement characters so we don't die + # if we try to print something containing non-ASCII characters. + sys.stderr = codecs.StreamReaderWriter(sys.stderr, + codecs.getreader('utf8'), + codecs.getwriter('utf8'), + 'replace') + + _cpplint_state.ResetErrorCounts() + for filename in filenames: + ProcessFile(filename, _cpplint_state.verbose_level) + _cpplint_state.PrintErrorCounts() + + sys.exit(_cpplint_state.error_count > 0) + + +if __name__ == '__main__': + main() diff --git a/loader_example.cc b/loader_example.cc new file mode 100644 index 0000000..d91baaa --- /dev/null +++ b/loader_example.cc @@ -0,0 +1,312 @@ +#define TINYOBJLOADER_IMPLEMENTATION +#include "tiny_obj_loader.h" + +#include +#include +#include +#include +#include +#include + +static void PrintInfo(const tinyobj::attrib_t &attrib, const std::vector& shapes, const std::vector& materials, bool triangulate = true) +{ + std::cout << "# of vertices : " << (attrib.vertices.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.vertices.size() / 3; v++) { + printf(" v[%ld] = (%f, %f, %f)\n", v, + static_cast(attrib.vertices[3*v+0]), + static_cast(attrib.vertices[3*v+1]), + static_cast(attrib.vertices[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()); + + if (triangulate) + { + 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++) { + 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++) { + 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()); + assert(shapes[i].mesh.material_ids.size() == shapes[i].mesh.num_vertices.size()); + for (size_t m = 0; m < shapes[i].mesh.material_ids.size(); m++) { + printf(" material_id[%ld] = %d\n", m, + shapes[i].mesh.material_ids[m]); + } + + } + + printf("shape[%ld].num_faces: %ld\n", i, shapes[i].mesh.num_vertices.size()); + for (size_t v = 0; v < shapes[i].mesh.num_vertices.size(); v++) { + printf(" num_vertices[%ld] = %ld\n", v, + 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, + // 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++) { + printf(" tag[%ld] = %s ", t, shapes[i].mesh.tags[t].name.c_str()); + printf(" ints: ["); + for (size_t j = 0; j < shapes[i].mesh.tags[t].intValues.size(); ++j) + { + printf("%ld", static_cast(shapes[i].mesh.tags[t].intValues[j])); + if (j < (shapes[i].mesh.tags[t].intValues.size()-1)) + { + printf(", "); + } + } + printf("]"); + + printf(" floats: ["); + for (size_t j = 0; j < shapes[i].mesh.tags[t].floatValues.size(); ++j) + { + printf("%f", static_cast(shapes[i].mesh.tags[t].floatValues[j])); + if (j < (shapes[i].mesh.tags[t].floatValues.size()-1)) + { + printf(", "); + } + } + printf("]"); + + printf(" strings: ["); + for (size_t j = 0; j < shapes[i].mesh.tags[t].stringValues.size(); ++j) + { + printf("%s", shapes[i].mesh.tags[t].stringValues[j].c_str()); + if (j < (shapes[i].mesh.tags[t].stringValues.size()-1)) + { + printf(", "); + } + } + printf("]"); + printf("\n"); + } + } + + 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", static_cast(materials[i].ambient[0]), static_cast(materials[i].ambient[1]), static_cast(materials[i].ambient[2])); + printf(" material.Kd = (%f, %f ,%f)\n", static_cast(materials[i].diffuse[0]), static_cast(materials[i].diffuse[1]), static_cast(materials[i].diffuse[2])); + printf(" material.Ks = (%f, %f ,%f)\n", static_cast(materials[i].specular[0]), static_cast(materials[i].specular[1]), static_cast(materials[i].specular[2])); + printf(" material.Tr = (%f, %f ,%f)\n", static_cast(materials[i].transmittance[0]), static_cast(materials[i].transmittance[1]), static_cast(materials[i].transmittance[2])); + printf(" material.Ke = (%f, %f ,%f)\n", static_cast(materials[i].emission[0]), static_cast(materials[i].emission[1]), static_cast(materials[i].emission[2])); + printf(" material.Ns = %f\n", static_cast(materials[i].shininess)); + printf(" material.Ni = %f\n", static_cast(materials[i].ior)); + printf(" material.dissolve = %f\n", static_cast(materials[i].dissolve)); + printf(" material.illum = %d\n", materials[i].illum); + printf(" material.map_Ka = %s\n", materials[i].ambient_texname.c_str()); + printf(" material.map_Kd = %s\n", materials[i].diffuse_texname.c_str()); + printf(" material.map_Ks = %s\n", materials[i].specular_texname.c_str()); + printf(" material.map_Ns = %s\n", materials[i].specular_highlight_texname.c_str()); + printf(" material.map_bump = %s\n", materials[i].bump_texname.c_str()); + printf(" material.map_d = %s\n", materials[i].alpha_texname.c_str()); + printf(" material.disp = %s\n", materials[i].displacement_texname.c_str()); + std::map::const_iterator it(materials[i].unknown_parameter.begin()); + std::map::const_iterator itEnd(materials[i].unknown_parameter.end()); + + for (; it != itEnd; it++) { + printf(" material.%s = %s\n", it->first.c_str(), it->second.c_str()); + } + printf("\n"); + } +} + +static bool +TestLoadObj( + const char* filename, + const char* basepath = NULL, + bool triangulate = true) +{ + std::cout << "Loading " << filename << std::endl; + + tinyobj::attrib_t attrib; + std::vector shapes; + std::vector materials; + + std::string err; + bool ret = tinyobj::LoadObj(&attrib, &shapes, &materials, &err, filename, basepath, triangulate); + + if (!err.empty()) { + std::cerr << err << std::endl; + } + + if (!ret) { + printf("Failed to load/parse .obj.\n"); + return false; + } + + PrintInfo(attrib, shapes, materials, triangulate); + + return true; +} + + +static bool +TestStreamLoadObj() +{ + std::cout << "Stream Loading " << std::endl; + + std::stringstream objStream; + objStream + << "mtllib cube.mtl\n" + "\n" + "v 0.000000 2.000000 2.000000\n" + "v 0.000000 0.000000 2.000000\n" + "v 2.000000 0.000000 2.000000\n" + "v 2.000000 2.000000 2.000000\n" + "v 0.000000 2.000000 0.000000\n" + "v 0.000000 0.000000 0.000000\n" + "v 2.000000 0.000000 0.000000\n" + "v 2.000000 2.000000 0.000000\n" + "# 8 vertices\n" + "\n" + "g front cube\n" + "usemtl white\n" + "f 1 2 3 4\n" + "g back cube\n" + "# expects white material\n" + "f 8 7 6 5\n" + "g right cube\n" + "usemtl red\n" + "f 4 3 7 8\n" + "g top cube\n" + "usemtl white\n" + "f 5 1 4 8\n" + "g left cube\n" + "usemtl green\n" + "f 5 6 2 1\n" + "g bottom cube\n" + "usemtl white\n" + "f 2 6 7 3\n" + "# 6 elements"; + +std::string matStream( + "newmtl white\n" + "Ka 0 0 0\n" + "Kd 1 1 1\n" + "Ks 0 0 0\n" + "\n" + "newmtl red\n" + "Ka 0 0 0\n" + "Kd 1 0 0\n" + "Ks 0 0 0\n" + "\n" + "newmtl green\n" + "Ka 0 0 0\n" + "Kd 0 1 0\n" + "Ks 0 0 0\n" + "\n" + "newmtl blue\n" + "Ka 0 0 0\n" + "Kd 0 0 1\n" + "Ks 0 0 0\n" + "\n" + "newmtl light\n" + "Ka 20 20 20\n" + "Kd 1 1 1\n" + "Ks 0 0 0"); + + using namespace tinyobj; + class MaterialStringStreamReader: + public MaterialReader + { + public: + MaterialStringStreamReader(const std::string& matSStream): m_matSStream(matSStream) {} + virtual ~MaterialStringStreamReader() {} + virtual bool operator() ( + const std::string& matId, + std::vector* materials, + std::map* matMap, + std::string* err) + { + (void)matId; + (void)err; + LoadMtl(matMap, materials, &m_matSStream); + return true; + } + + private: + std::stringstream m_matSStream; + }; + + MaterialStringStreamReader matSSReader(matStream); + tinyobj::attrib_t attrib; + std::vector shapes; + std::vector materials; + std::string err; + bool ret = tinyobj::LoadObj(&attrib, &shapes, &materials, &err, &objStream, &matSSReader); + + if (!err.empty()) { + std::cerr << err << std::endl; + } + + if (!ret) { + return false; + } + + PrintInfo(attrib, shapes, materials); + + return true; +} + +int +main( + int argc, + char **argv) +{ + if (argc > 1) { + const char* basepath = "models/"; + if (argc > 2) { + basepath = argv[2]; + } + assert(true == TestLoadObj(argv[1], basepath)); + } else { + //assert(true == TestLoadObj("cornell_box.obj")); + //assert(true == TestLoadObj("cube.obj")); + assert(true == TestStreamLoadObj()); + assert(true == TestLoadObj("models/catmark_torus_creases0.obj", "models/", false)); + } + + return 0; +} diff --git a/models/cornell_box.mtl b/models/cornell_box.mtl new file mode 100644 index 0000000..d3a1c7a --- /dev/null +++ b/models/cornell_box.mtl @@ -0,0 +1,24 @@ +newmtl white +Ka 0 0 0 +Kd 1 1 1 +Ks 0 0 0 + +newmtl red +Ka 0 0 0 +Kd 1 0 0 +Ks 0 0 0 + +newmtl green +Ka 0 0 0 +Kd 0 1 0 +Ks 0 0 0 + +newmtl blue +Ka 0 0 0 +Kd 0 0 1 +Ks 0 0 0 + +newmtl light +Ka 20 20 20 +Kd 1 1 1 +Ks 0 0 0 diff --git a/models/cornell_box.obj b/models/cornell_box.obj new file mode 100644 index 0000000..43e021f --- /dev/null +++ b/models/cornell_box.obj @@ -0,0 +1,145 @@ +# cornell_box.obj and cornell_box.mtl are grabbed from Intel's embree project. +# original cornell box data + # comment + +# empty line including some space + + +mtllib cornell_box.mtl + +o floor +usemtl white +v 552.8 0.0 0.0 +v 0.0 0.0 0.0 +v 0.0 0.0 559.2 +v 549.6 0.0 559.2 + +v 130.0 0.0 65.0 +v 82.0 0.0 225.0 +v 240.0 0.0 272.0 +v 290.0 0.0 114.0 + +v 423.0 0.0 247.0 +v 265.0 0.0 296.0 +v 314.0 0.0 456.0 +v 472.0 0.0 406.0 + +f 1 2 3 4 +f 8 7 6 5 +f 12 11 10 9 + +o light +usemtl light +v 343.0 548.0 227.0 +v 343.0 548.0 332.0 +v 213.0 548.0 332.0 +v 213.0 548.0 227.0 +f -4 -3 -2 -1 + +o ceiling +usemtl white +v 556.0 548.8 0.0 +v 556.0 548.8 559.2 +v 0.0 548.8 559.2 +v 0.0 548.8 0.0 +f -4 -3 -2 -1 + +o back_wall +usemtl white +v 549.6 0.0 559.2 +v 0.0 0.0 559.2 +v 0.0 548.8 559.2 +v 556.0 548.8 559.2 +f -4 -3 -2 -1 + +o front_wall +usemtl blue +v 549.6 0.0 0 +v 0.0 0.0 0 +v 0.0 548.8 0 +v 556.0 548.8 0 +#f -1 -2 -3 -4 + +o green_wall +usemtl green +v 0.0 0.0 559.2 +v 0.0 0.0 0.0 +v 0.0 548.8 0.0 +v 0.0 548.8 559.2 +f -4 -3 -2 -1 + +o red_wall +usemtl red +v 552.8 0.0 0.0 +v 549.6 0.0 559.2 +v 556.0 548.8 559.2 +v 556.0 548.8 0.0 +f -4 -3 -2 -1 + +o short_block +usemtl white + +v 130.0 165.0 65.0 +v 82.0 165.0 225.0 +v 240.0 165.0 272.0 +v 290.0 165.0 114.0 +f -4 -3 -2 -1 + +v 290.0 0.0 114.0 +v 290.0 165.0 114.0 +v 240.0 165.0 272.0 +v 240.0 0.0 272.0 +f -4 -3 -2 -1 + +v 130.0 0.0 65.0 +v 130.0 165.0 65.0 +v 290.0 165.0 114.0 +v 290.0 0.0 114.0 +f -4 -3 -2 -1 + +v 82.0 0.0 225.0 +v 82.0 165.0 225.0 +v 130.0 165.0 65.0 +v 130.0 0.0 65.0 +f -4 -3 -2 -1 + +v 240.0 0.0 272.0 +v 240.0 165.0 272.0 +v 82.0 165.0 225.0 +v 82.0 0.0 225.0 +f -4 -3 -2 -1 + +o tall_block +usemtl white + +v 423.0 330.0 247.0 +v 265.0 330.0 296.0 +v 314.0 330.0 456.0 +v 472.0 330.0 406.0 +f -4 -3 -2 -1 + +usemtl white +v 423.0 0.0 247.0 +v 423.0 330.0 247.0 +v 472.0 330.0 406.0 +v 472.0 0.0 406.0 +f -4 -3 -2 -1 + +v 472.0 0.0 406.0 +v 472.0 330.0 406.0 +v 314.0 330.0 456.0 +v 314.0 0.0 456.0 +f -4 -3 -2 -1 + +v 314.0 0.0 456.0 +v 314.0 330.0 456.0 +v 265.0 330.0 296.0 +v 265.0 0.0 296.0 +f -4 -3 -2 -1 + +v 265.0 0.0 296.0 +v 265.0 330.0 296.0 +v 423.0 330.0 247.0 +v 423.0 0.0 247.0 +f -4 -3 -2 -1 + diff --git a/premake4.lua b/premake4.lua index cbfdfd9..cd5b295 100644 --- a/premake4.lua +++ b/premake4.lua @@ -1,5 +1,5 @@ sources = { - "test.cc", + "loader_example.cc", } -- premake4.lua @@ -21,9 +21,9 @@ solution "TinyObjLoaderSolution" configuration "Debug" defines { "DEBUG" } -- -DDEBUG flags { "Symbols" } - targetname "test_tinyobjloader_debug" + targetname "loader_example_debug" configuration "Release" -- defines { "NDEBUG" } -- -NDEBUG flags { "Symbols", "Optimize" } - targetname "test_tinyobjloader" + targetname "loader_example" diff --git a/test.cc b/test.cc deleted file mode 100644 index 72b4fa2..0000000 --- a/test.cc +++ /dev/null @@ -1,312 +0,0 @@ -#define TINYOBJLOADER_IMPLEMENTATION -#include "tiny_obj_loader.h" - -#include -#include -#include -#include -#include -#include - -static void PrintInfo(const tinyobj::attrib_t &attrib, const std::vector& shapes, const std::vector& materials, bool triangulate = true) -{ - std::cout << "# of vertices : " << (attrib.vertices.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.vertices.size() / 3; v++) { - printf(" v[%ld] = (%f, %f, %f)\n", v, - static_cast(attrib.vertices[3*v+0]), - static_cast(attrib.vertices[3*v+1]), - static_cast(attrib.vertices[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()); - - if (triangulate) - { - 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++) { - 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++) { - 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()); - assert(shapes[i].mesh.material_ids.size() == shapes[i].mesh.num_vertices.size()); - for (size_t m = 0; m < shapes[i].mesh.material_ids.size(); m++) { - printf(" material_id[%ld] = %d\n", m, - shapes[i].mesh.material_ids[m]); - } - - } - - printf("shape[%ld].num_faces: %ld\n", i, shapes[i].mesh.num_vertices.size()); - for (size_t v = 0; v < shapes[i].mesh.num_vertices.size(); v++) { - printf(" num_vertices[%ld] = %ld\n", v, - 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, - // 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++) { - printf(" tag[%ld] = %s ", t, shapes[i].mesh.tags[t].name.c_str()); - printf(" ints: ["); - for (size_t j = 0; j < shapes[i].mesh.tags[t].intValues.size(); ++j) - { - printf("%ld", static_cast(shapes[i].mesh.tags[t].intValues[j])); - if (j < (shapes[i].mesh.tags[t].intValues.size()-1)) - { - printf(", "); - } - } - printf("]"); - - printf(" floats: ["); - for (size_t j = 0; j < shapes[i].mesh.tags[t].floatValues.size(); ++j) - { - printf("%f", static_cast(shapes[i].mesh.tags[t].floatValues[j])); - if (j < (shapes[i].mesh.tags[t].floatValues.size()-1)) - { - printf(", "); - } - } - printf("]"); - - printf(" strings: ["); - for (size_t j = 0; j < shapes[i].mesh.tags[t].stringValues.size(); ++j) - { - printf("%s", shapes[i].mesh.tags[t].stringValues[j].c_str()); - if (j < (shapes[i].mesh.tags[t].stringValues.size()-1)) - { - printf(", "); - } - } - printf("]"); - printf("\n"); - } - } - - 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", static_cast(materials[i].ambient[0]), static_cast(materials[i].ambient[1]), static_cast(materials[i].ambient[2])); - printf(" material.Kd = (%f, %f ,%f)\n", static_cast(materials[i].diffuse[0]), static_cast(materials[i].diffuse[1]), static_cast(materials[i].diffuse[2])); - printf(" material.Ks = (%f, %f ,%f)\n", static_cast(materials[i].specular[0]), static_cast(materials[i].specular[1]), static_cast(materials[i].specular[2])); - printf(" material.Tr = (%f, %f ,%f)\n", static_cast(materials[i].transmittance[0]), static_cast(materials[i].transmittance[1]), static_cast(materials[i].transmittance[2])); - printf(" material.Ke = (%f, %f ,%f)\n", static_cast(materials[i].emission[0]), static_cast(materials[i].emission[1]), static_cast(materials[i].emission[2])); - printf(" material.Ns = %f\n", static_cast(materials[i].shininess)); - printf(" material.Ni = %f\n", static_cast(materials[i].ior)); - printf(" material.dissolve = %f\n", static_cast(materials[i].dissolve)); - printf(" material.illum = %d\n", materials[i].illum); - printf(" material.map_Ka = %s\n", materials[i].ambient_texname.c_str()); - printf(" material.map_Kd = %s\n", materials[i].diffuse_texname.c_str()); - printf(" material.map_Ks = %s\n", materials[i].specular_texname.c_str()); - printf(" material.map_Ns = %s\n", materials[i].specular_highlight_texname.c_str()); - printf(" material.map_bump = %s\n", materials[i].bump_texname.c_str()); - printf(" material.map_d = %s\n", materials[i].alpha_texname.c_str()); - printf(" material.disp = %s\n", materials[i].displacement_texname.c_str()); - std::map::const_iterator it(materials[i].unknown_parameter.begin()); - std::map::const_iterator itEnd(materials[i].unknown_parameter.end()); - - for (; it != itEnd; it++) { - printf(" material.%s = %s\n", it->first.c_str(), it->second.c_str()); - } - printf("\n"); - } -} - -static bool -TestLoadObj( - const char* filename, - const char* basepath = NULL, - bool triangulate = true) -{ - std::cout << "Loading " << filename << std::endl; - - tinyobj::attrib_t attrib; - std::vector shapes; - std::vector materials; - - std::string err; - bool ret = tinyobj::LoadObj(&attrib, &shapes, &materials, &err, filename, basepath, triangulate); - - if (!err.empty()) { - std::cerr << err << std::endl; - } - - if (!ret) { - printf("Failed to load/parse .obj.\n"); - return false; - } - - PrintInfo(attrib, shapes, materials, triangulate); - - return true; -} - - -static bool -TestStreamLoadObj() -{ - std::cout << "Stream Loading " << std::endl; - - std::stringstream objStream; - objStream - << "mtllib cube.mtl\n" - "\n" - "v 0.000000 2.000000 2.000000\n" - "v 0.000000 0.000000 2.000000\n" - "v 2.000000 0.000000 2.000000\n" - "v 2.000000 2.000000 2.000000\n" - "v 0.000000 2.000000 0.000000\n" - "v 0.000000 0.000000 0.000000\n" - "v 2.000000 0.000000 0.000000\n" - "v 2.000000 2.000000 0.000000\n" - "# 8 vertices\n" - "\n" - "g front cube\n" - "usemtl white\n" - "f 1 2 3 4\n" - "g back cube\n" - "# expects white material\n" - "f 8 7 6 5\n" - "g right cube\n" - "usemtl red\n" - "f 4 3 7 8\n" - "g top cube\n" - "usemtl white\n" - "f 5 1 4 8\n" - "g left cube\n" - "usemtl green\n" - "f 5 6 2 1\n" - "g bottom cube\n" - "usemtl white\n" - "f 2 6 7 3\n" - "# 6 elements"; - -std::string matStream( - "newmtl white\n" - "Ka 0 0 0\n" - "Kd 1 1 1\n" - "Ks 0 0 0\n" - "\n" - "newmtl red\n" - "Ka 0 0 0\n" - "Kd 1 0 0\n" - "Ks 0 0 0\n" - "\n" - "newmtl green\n" - "Ka 0 0 0\n" - "Kd 0 1 0\n" - "Ks 0 0 0\n" - "\n" - "newmtl blue\n" - "Ka 0 0 0\n" - "Kd 0 0 1\n" - "Ks 0 0 0\n" - "\n" - "newmtl light\n" - "Ka 20 20 20\n" - "Kd 1 1 1\n" - "Ks 0 0 0"); - - using namespace tinyobj; - class MaterialStringStreamReader: - public MaterialReader - { - public: - MaterialStringStreamReader(const std::string& matSStream): m_matSStream(matSStream) {} - virtual ~MaterialStringStreamReader() {} - virtual bool operator() ( - const std::string& matId, - std::vector* materials, - std::map* matMap, - std::string* err) - { - (void)matId; - (void)err; - LoadMtl(matMap, materials, &m_matSStream); - return true; - } - - private: - std::stringstream m_matSStream; - }; - - MaterialStringStreamReader matSSReader(matStream); - tinyobj::attrib_t attrib; - std::vector shapes; - std::vector materials; - std::string err; - bool ret = tinyobj::LoadObj(&attrib, &shapes, &materials, &err, &objStream, &matSSReader); - - if (!err.empty()) { - std::cerr << err << std::endl; - } - - if (!ret) { - return false; - } - - PrintInfo(attrib, shapes, materials); - - return true; -} - -int -main( - int argc, - char **argv) -{ - if (argc > 1) { - const char* basepath = NULL; - if (argc > 2) { - basepath = argv[2]; - } - assert(true == TestLoadObj(argv[1], basepath)); - } else { - //assert(true == TestLoadObj("cornell_box.obj")); - //assert(true == TestLoadObj("cube.obj")); - assert(true == TestStreamLoadObj()); - assert(true == TestLoadObj("catmark_torus_creases0.obj", NULL, false)); - } - - return 0; -} diff --git a/tests/Makefile b/tests/Makefile new file mode 100644 index 0000000..1a1434a --- /dev/null +++ b/tests/Makefile @@ -0,0 +1,13 @@ +.PHONY: clean + +tester: tester.cc + g++ -g -O0 -o tester tester.cc + +all: tester + +check: tester + ./tester + +clean: + rm -rf tester + diff --git a/tests/README.md b/tests/README.md new file mode 100644 index 0000000..6ed65ff --- /dev/null +++ b/tests/README.md @@ -0,0 +1,25 @@ +# Build&Test + +## Use makefile + + $ make check + +## Use ninja + kuroga + +Assume + +* ninja 1.4+ +* python 2.6+ + +Are installed. + +### Linux/MacOSX + + $ python kuroga.py config-posix.py + $ ninja + +### Windows + + > python kuroga.py config-msvc.py + > vcbuild.bat + diff --git a/tests/catch.hpp b/tests/catch.hpp new file mode 100644 index 0000000..2a7146a --- /dev/null +++ b/tests/catch.hpp @@ -0,0 +1,10445 @@ +/* + * Catch v1.4.0 + * Generated: 2016-03-15 07:23:12.623111 + * ---------------------------------------------------------- + * This file has been merged from multiple headers. Please don't edit it directly + * Copyright (c) 2012 Two Blue Cubes Ltd. All rights reserved. + * + * Distributed under the Boost Software License, Version 1.0. (See accompanying + * file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + */ +#ifndef TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED +#define TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED + +#define TWOBLUECUBES_CATCH_HPP_INCLUDED + +#ifdef __clang__ +# pragma clang system_header +#elif defined __GNUC__ +# pragma GCC system_header +#endif + +// #included from: internal/catch_suppress_warnings.h + +#ifdef __clang__ +# ifdef __ICC // icpc defines the __clang__ macro +# pragma warning(push) +# pragma warning(disable: 161 1682) +# else // __ICC +# pragma clang diagnostic ignored "-Wglobal-constructors" +# pragma clang diagnostic ignored "-Wvariadic-macros" +# pragma clang diagnostic ignored "-Wc99-extensions" +# pragma clang diagnostic ignored "-Wunused-variable" +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wpadded" +# pragma clang diagnostic ignored "-Wc++98-compat" +# pragma clang diagnostic ignored "-Wc++98-compat-pedantic" +# pragma clang diagnostic ignored "-Wswitch-enum" +# pragma clang diagnostic ignored "-Wcovered-switch-default" +# endif +#elif defined __GNUC__ +# pragma GCC diagnostic ignored "-Wvariadic-macros" +# pragma GCC diagnostic ignored "-Wunused-variable" +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wpadded" +#endif +#if defined(CATCH_CONFIG_MAIN) || defined(CATCH_CONFIG_RUNNER) +# define CATCH_IMPL +#endif + +#ifdef CATCH_IMPL +# ifndef CLARA_CONFIG_MAIN +# define CLARA_CONFIG_MAIN_NOT_DEFINED +# define CLARA_CONFIG_MAIN +# endif +#endif + +// #included from: internal/catch_notimplemented_exception.h +#define TWOBLUECUBES_CATCH_NOTIMPLEMENTED_EXCEPTION_H_INCLUDED + +// #included from: catch_common.h +#define TWOBLUECUBES_CATCH_COMMON_H_INCLUDED + +#define INTERNAL_CATCH_UNIQUE_NAME_LINE2( name, line ) name##line +#define INTERNAL_CATCH_UNIQUE_NAME_LINE( name, line ) INTERNAL_CATCH_UNIQUE_NAME_LINE2( name, line ) +#ifdef CATCH_CONFIG_COUNTER +# define INTERNAL_CATCH_UNIQUE_NAME( name ) INTERNAL_CATCH_UNIQUE_NAME_LINE( name, __COUNTER__ ) +#else +# define INTERNAL_CATCH_UNIQUE_NAME( name ) INTERNAL_CATCH_UNIQUE_NAME_LINE( name, __LINE__ ) +#endif + +#define INTERNAL_CATCH_STRINGIFY2( expr ) #expr +#define INTERNAL_CATCH_STRINGIFY( expr ) INTERNAL_CATCH_STRINGIFY2( expr ) + +#include +#include +#include + +// #included from: catch_compiler_capabilities.h +#define TWOBLUECUBES_CATCH_COMPILER_CAPABILITIES_HPP_INCLUDED + +// Detect a number of compiler features - mostly C++11/14 conformance - by compiler +// The following features are defined: +// +// CATCH_CONFIG_CPP11_NULLPTR : is nullptr supported? +// CATCH_CONFIG_CPP11_NOEXCEPT : is noexcept supported? +// CATCH_CONFIG_CPP11_GENERATED_METHODS : The delete and default keywords for compiler generated methods +// CATCH_CONFIG_CPP11_IS_ENUM : std::is_enum is supported? +// CATCH_CONFIG_CPP11_TUPLE : std::tuple is supported +// CATCH_CONFIG_CPP11_LONG_LONG : is long long supported? +// CATCH_CONFIG_CPP11_OVERRIDE : is override supported? +// CATCH_CONFIG_CPP11_UNIQUE_PTR : is unique_ptr supported (otherwise use auto_ptr) + +// CATCH_CONFIG_CPP11_OR_GREATER : Is C++11 supported? + +// CATCH_CONFIG_VARIADIC_MACROS : are variadic macros supported? +// CATCH_CONFIG_COUNTER : is the __COUNTER__ macro supported? +// **************** +// Note to maintainers: if new toggles are added please document them +// in configuration.md, too +// **************** + +// In general each macro has a _NO_ form +// (e.g. CATCH_CONFIG_CPP11_NO_NULLPTR) which disables the feature. +// Many features, at point of detection, define an _INTERNAL_ macro, so they +// can be combined, en-mass, with the _NO_ forms later. + +// All the C++11 features can be disabled with CATCH_CONFIG_NO_CPP11 + +#if defined(__cplusplus) && __cplusplus >= 201103L +# define CATCH_CPP11_OR_GREATER +#endif + +#ifdef __clang__ + +# if __has_feature(cxx_nullptr) +# define CATCH_INTERNAL_CONFIG_CPP11_NULLPTR +# endif + +# if __has_feature(cxx_noexcept) +# define CATCH_INTERNAL_CONFIG_CPP11_NOEXCEPT +# endif + +# if defined(CATCH_CPP11_OR_GREATER) +# define CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS _Pragma( "clang diagnostic ignored \"-Wparentheses\"" ) +# endif + +#endif // __clang__ + +//////////////////////////////////////////////////////////////////////////////// +// Borland +#ifdef __BORLANDC__ + +#endif // __BORLANDC__ + +//////////////////////////////////////////////////////////////////////////////// +// EDG +#ifdef __EDG_VERSION__ + +#endif // __EDG_VERSION__ + +//////////////////////////////////////////////////////////////////////////////// +// Digital Mars +#ifdef __DMC__ + +#endif // __DMC__ + +//////////////////////////////////////////////////////////////////////////////// +// GCC +#ifdef __GNUC__ + +# if __GNUC__ == 4 && __GNUC_MINOR__ >= 6 && defined(__GXX_EXPERIMENTAL_CXX0X__) +# define CATCH_INTERNAL_CONFIG_CPP11_NULLPTR +# endif + +# if !defined(CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS) && defined(CATCH_CPP11_OR_GREATER) +# define CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS _Pragma( "GCC diagnostic ignored \"-Wparentheses\"" ) +# endif + +// - otherwise more recent versions define __cplusplus >= 201103L +// and will get picked up below + +#endif // __GNUC__ + +//////////////////////////////////////////////////////////////////////////////// +// Visual C++ +#ifdef _MSC_VER + +#if (_MSC_VER >= 1600) +# define CATCH_INTERNAL_CONFIG_CPP11_NULLPTR +# define CATCH_INTERNAL_CONFIG_CPP11_UNIQUE_PTR +#endif + +#if (_MSC_VER >= 1900 ) // (VC++ 13 (VS2015)) +#define CATCH_INTERNAL_CONFIG_CPP11_NOEXCEPT +#define CATCH_INTERNAL_CONFIG_CPP11_GENERATED_METHODS +#endif + +#endif // _MSC_VER + +//////////////////////////////////////////////////////////////////////////////// + +// Use variadic macros if the compiler supports them +#if ( defined _MSC_VER && _MSC_VER > 1400 && !defined __EDGE__) || \ + ( defined __WAVE__ && __WAVE_HAS_VARIADICS ) || \ + ( defined __GNUC__ && __GNUC__ >= 3 ) || \ + ( !defined __cplusplus && __STDC_VERSION__ >= 199901L || __cplusplus >= 201103L ) + +#define CATCH_INTERNAL_CONFIG_VARIADIC_MACROS + +#endif + +// Use __COUNTER__ if the compiler supports it +#if ( defined _MSC_VER && _MSC_VER >= 1300 ) || \ + ( defined __GNUC__ && __GNUC__ >= 4 && __GNUC_MINOR__ >= 3 ) || \ + ( defined __clang__ && __clang_major__ >= 3 ) + +#define CATCH_INTERNAL_CONFIG_COUNTER + +#endif + +//////////////////////////////////////////////////////////////////////////////// +// C++ language feature support + +// catch all support for C++11 +#if defined(CATCH_CPP11_OR_GREATER) + +# if !defined(CATCH_INTERNAL_CONFIG_CPP11_NULLPTR) +# define CATCH_INTERNAL_CONFIG_CPP11_NULLPTR +# endif + +# ifndef CATCH_INTERNAL_CONFIG_CPP11_NOEXCEPT +# define CATCH_INTERNAL_CONFIG_CPP11_NOEXCEPT +# endif + +# ifndef CATCH_INTERNAL_CONFIG_CPP11_GENERATED_METHODS +# define CATCH_INTERNAL_CONFIG_CPP11_GENERATED_METHODS +# endif + +# ifndef CATCH_INTERNAL_CONFIG_CPP11_IS_ENUM +# define CATCH_INTERNAL_CONFIG_CPP11_IS_ENUM +# endif + +# ifndef CATCH_INTERNAL_CONFIG_CPP11_TUPLE +# define CATCH_INTERNAL_CONFIG_CPP11_TUPLE +# endif + +# ifndef CATCH_INTERNAL_CONFIG_VARIADIC_MACROS +# define CATCH_INTERNAL_CONFIG_VARIADIC_MACROS +# endif + +# if !defined(CATCH_INTERNAL_CONFIG_CPP11_LONG_LONG) +# define CATCH_INTERNAL_CONFIG_CPP11_LONG_LONG +# endif + +# if !defined(CATCH_INTERNAL_CONFIG_CPP11_OVERRIDE) +# define CATCH_INTERNAL_CONFIG_CPP11_OVERRIDE +# endif +# if !defined(CATCH_INTERNAL_CONFIG_CPP11_UNIQUE_PTR) +# define CATCH_INTERNAL_CONFIG_CPP11_UNIQUE_PTR +# endif + +#endif // __cplusplus >= 201103L + +// Now set the actual defines based on the above + anything the user has configured +#if defined(CATCH_INTERNAL_CONFIG_CPP11_NULLPTR) && !defined(CATCH_CONFIG_CPP11_NO_NULLPTR) && !defined(CATCH_CONFIG_CPP11_NULLPTR) && !defined(CATCH_CONFIG_NO_CPP11) +# define CATCH_CONFIG_CPP11_NULLPTR +#endif +#if defined(CATCH_INTERNAL_CONFIG_CPP11_NOEXCEPT) && !defined(CATCH_CONFIG_CPP11_NO_NOEXCEPT) && !defined(CATCH_CONFIG_CPP11_NOEXCEPT) && !defined(CATCH_CONFIG_NO_CPP11) +# define CATCH_CONFIG_CPP11_NOEXCEPT +#endif +#if defined(CATCH_INTERNAL_CONFIG_CPP11_GENERATED_METHODS) && !defined(CATCH_CONFIG_CPP11_NO_GENERATED_METHODS) && !defined(CATCH_CONFIG_CPP11_GENERATED_METHODS) && !defined(CATCH_CONFIG_NO_CPP11) +# define CATCH_CONFIG_CPP11_GENERATED_METHODS +#endif +#if defined(CATCH_INTERNAL_CONFIG_CPP11_IS_ENUM) && !defined(CATCH_CONFIG_CPP11_NO_IS_ENUM) && !defined(CATCH_CONFIG_CPP11_IS_ENUM) && !defined(CATCH_CONFIG_NO_CPP11) +# define CATCH_CONFIG_CPP11_IS_ENUM +#endif +#if defined(CATCH_INTERNAL_CONFIG_CPP11_TUPLE) && !defined(CATCH_CONFIG_CPP11_NO_TUPLE) && !defined(CATCH_CONFIG_CPP11_TUPLE) && !defined(CATCH_CONFIG_NO_CPP11) +# define CATCH_CONFIG_CPP11_TUPLE +#endif +#if defined(CATCH_INTERNAL_CONFIG_VARIADIC_MACROS) && !defined(CATCH_CONFIG_NO_VARIADIC_MACROS) && !defined(CATCH_CONFIG_VARIADIC_MACROS) +# define CATCH_CONFIG_VARIADIC_MACROS +#endif +#if defined(CATCH_INTERNAL_CONFIG_CPP11_LONG_LONG) && !defined(CATCH_CONFIG_NO_LONG_LONG) && !defined(CATCH_CONFIG_CPP11_LONG_LONG) && !defined(CATCH_CONFIG_NO_CPP11) +# define CATCH_CONFIG_CPP11_LONG_LONG +#endif +#if defined(CATCH_INTERNAL_CONFIG_CPP11_OVERRIDE) && !defined(CATCH_CONFIG_NO_OVERRIDE) && !defined(CATCH_CONFIG_CPP11_OVERRIDE) && !defined(CATCH_CONFIG_NO_CPP11) +# define CATCH_CONFIG_CPP11_OVERRIDE +#endif +#if defined(CATCH_INTERNAL_CONFIG_CPP11_UNIQUE_PTR) && !defined(CATCH_CONFIG_NO_UNIQUE_PTR) && !defined(CATCH_CONFIG_CPP11_UNIQUE_PTR) && !defined(CATCH_CONFIG_NO_CPP11) +# define CATCH_CONFIG_CPP11_UNIQUE_PTR +#endif +#if defined(CATCH_INTERNAL_CONFIG_COUNTER) && !defined(CATCH_CONFIG_NO_COUNTER) && !defined(CATCH_CONFIG_COUNTER) +# define CATCH_CONFIG_COUNTER +#endif + +#if !defined(CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS) +# define CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS +#endif + +// noexcept support: +#if defined(CATCH_CONFIG_CPP11_NOEXCEPT) && !defined(CATCH_NOEXCEPT) +# define CATCH_NOEXCEPT noexcept +# define CATCH_NOEXCEPT_IS(x) noexcept(x) +#else +# define CATCH_NOEXCEPT throw() +# define CATCH_NOEXCEPT_IS(x) +#endif + +// nullptr support +#ifdef CATCH_CONFIG_CPP11_NULLPTR +# define CATCH_NULL nullptr +#else +# define CATCH_NULL NULL +#endif + +// override support +#ifdef CATCH_CONFIG_CPP11_OVERRIDE +# define CATCH_OVERRIDE override +#else +# define CATCH_OVERRIDE +#endif + +// unique_ptr support +#ifdef CATCH_CONFIG_CPP11_UNIQUE_PTR +# define CATCH_AUTO_PTR( T ) std::unique_ptr +#else +# define CATCH_AUTO_PTR( T ) std::auto_ptr +#endif + +namespace Catch { + + struct IConfig; + + struct CaseSensitive { enum Choice { + Yes, + No + }; }; + + class NonCopyable { +#ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS + NonCopyable( NonCopyable const& ) = delete; + NonCopyable( NonCopyable && ) = delete; + NonCopyable& operator = ( NonCopyable const& ) = delete; + NonCopyable& operator = ( NonCopyable && ) = delete; +#else + NonCopyable( NonCopyable const& info ); + NonCopyable& operator = ( NonCopyable const& ); +#endif + + protected: + NonCopyable() {} + virtual ~NonCopyable(); + }; + + class SafeBool { + public: + typedef void (SafeBool::*type)() const; + + static type makeSafe( bool value ) { + return value ? &SafeBool::trueValue : 0; + } + private: + void trueValue() const {} + }; + + template + inline void deleteAll( ContainerT& container ) { + typename ContainerT::const_iterator it = container.begin(); + typename ContainerT::const_iterator itEnd = container.end(); + for(; it != itEnd; ++it ) + delete *it; + } + template + inline void deleteAllValues( AssociativeContainerT& container ) { + typename AssociativeContainerT::const_iterator it = container.begin(); + typename AssociativeContainerT::const_iterator itEnd = container.end(); + for(; it != itEnd; ++it ) + delete it->second; + } + + bool startsWith( std::string const& s, std::string const& prefix ); + bool endsWith( std::string const& s, std::string const& suffix ); + bool contains( std::string const& s, std::string const& infix ); + void toLowerInPlace( std::string& s ); + std::string toLower( std::string const& s ); + std::string trim( std::string const& str ); + bool replaceInPlace( std::string& str, std::string const& replaceThis, std::string const& withThis ); + + struct pluralise { + pluralise( std::size_t count, std::string const& label ); + + friend std::ostream& operator << ( std::ostream& os, pluralise const& pluraliser ); + + std::size_t m_count; + std::string m_label; + }; + + struct SourceLineInfo { + + SourceLineInfo(); + SourceLineInfo( char const* _file, std::size_t _line ); + SourceLineInfo( SourceLineInfo const& other ); +# ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS + SourceLineInfo( SourceLineInfo && ) = default; + SourceLineInfo& operator = ( SourceLineInfo const& ) = default; + SourceLineInfo& operator = ( SourceLineInfo && ) = default; +# endif + bool empty() const; + bool operator == ( SourceLineInfo const& other ) const; + bool operator < ( SourceLineInfo const& other ) const; + + std::string file; + std::size_t line; + }; + + std::ostream& operator << ( std::ostream& os, SourceLineInfo const& info ); + + // This is just here to avoid compiler warnings with macro constants and boolean literals + inline bool isTrue( bool value ){ return value; } + inline bool alwaysTrue() { return true; } + inline bool alwaysFalse() { return false; } + + void throwLogicError( std::string const& message, SourceLineInfo const& locationInfo ); + + void seedRng( IConfig const& config ); + unsigned int rngSeed(); + + // Use this in variadic streaming macros to allow + // >> +StreamEndStop + // as well as + // >> stuff +StreamEndStop + struct StreamEndStop { + std::string operator+() { + return std::string(); + } + }; + template + T const& operator + ( T const& value, StreamEndStop ) { + return value; + } +} + +#define CATCH_INTERNAL_LINEINFO ::Catch::SourceLineInfo( __FILE__, static_cast( __LINE__ ) ) +#define CATCH_INTERNAL_ERROR( msg ) ::Catch::throwLogicError( msg, CATCH_INTERNAL_LINEINFO ); + +#include + +namespace Catch { + + class NotImplementedException : public std::exception + { + public: + NotImplementedException( SourceLineInfo const& lineInfo ); + NotImplementedException( NotImplementedException const& ) {} + + virtual ~NotImplementedException() CATCH_NOEXCEPT {} + + virtual const char* what() const CATCH_NOEXCEPT; + + private: + std::string m_what; + SourceLineInfo m_lineInfo; + }; + +} // end namespace Catch + +/////////////////////////////////////////////////////////////////////////////// +#define CATCH_NOT_IMPLEMENTED throw Catch::NotImplementedException( CATCH_INTERNAL_LINEINFO ) + +// #included from: internal/catch_context.h +#define TWOBLUECUBES_CATCH_CONTEXT_H_INCLUDED + +// #included from: catch_interfaces_generators.h +#define TWOBLUECUBES_CATCH_INTERFACES_GENERATORS_H_INCLUDED + +#include + +namespace Catch { + + struct IGeneratorInfo { + virtual ~IGeneratorInfo(); + virtual bool moveNext() = 0; + virtual std::size_t getCurrentIndex() const = 0; + }; + + struct IGeneratorsForTest { + virtual ~IGeneratorsForTest(); + + virtual IGeneratorInfo& getGeneratorInfo( std::string const& fileInfo, std::size_t size ) = 0; + virtual bool moveNext() = 0; + }; + + IGeneratorsForTest* createGeneratorsForTest(); + +} // end namespace Catch + +// #included from: catch_ptr.hpp +#define TWOBLUECUBES_CATCH_PTR_HPP_INCLUDED + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wpadded" +#endif + +namespace Catch { + + // An intrusive reference counting smart pointer. + // T must implement addRef() and release() methods + // typically implementing the IShared interface + template + class Ptr { + public: + Ptr() : m_p( CATCH_NULL ){} + Ptr( T* p ) : m_p( p ){ + if( m_p ) + m_p->addRef(); + } + Ptr( Ptr const& other ) : m_p( other.m_p ){ + if( m_p ) + m_p->addRef(); + } + ~Ptr(){ + if( m_p ) + m_p->release(); + } + void reset() { + if( m_p ) + m_p->release(); + m_p = CATCH_NULL; + } + Ptr& operator = ( T* p ){ + Ptr temp( p ); + swap( temp ); + return *this; + } + Ptr& operator = ( Ptr const& other ){ + Ptr temp( other ); + swap( temp ); + return *this; + } + void swap( Ptr& other ) { std::swap( m_p, other.m_p ); } + T* get() const{ return m_p; } + T& operator*() const { return *m_p; } + T* operator->() const { return m_p; } + bool operator !() const { return m_p == CATCH_NULL; } + operator SafeBool::type() const { return SafeBool::makeSafe( m_p != CATCH_NULL ); } + + private: + T* m_p; + }; + + struct IShared : NonCopyable { + virtual ~IShared(); + virtual void addRef() const = 0; + virtual void release() const = 0; + }; + + template + struct SharedImpl : T { + + SharedImpl() : m_rc( 0 ){} + + virtual void addRef() const { + ++m_rc; + } + virtual void release() const { + if( --m_rc == 0 ) + delete this; + } + + mutable unsigned int m_rc; + }; + +} // end namespace Catch + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +#include +#include +#include + +namespace Catch { + + class TestCase; + class Stream; + struct IResultCapture; + struct IRunner; + struct IGeneratorsForTest; + struct IConfig; + + struct IContext + { + virtual ~IContext(); + + virtual IResultCapture* getResultCapture() = 0; + virtual IRunner* getRunner() = 0; + virtual size_t getGeneratorIndex( std::string const& fileInfo, size_t totalSize ) = 0; + virtual bool advanceGeneratorsForCurrentTest() = 0; + virtual Ptr getConfig() const = 0; + }; + + struct IMutableContext : IContext + { + virtual ~IMutableContext(); + virtual void setResultCapture( IResultCapture* resultCapture ) = 0; + virtual void setRunner( IRunner* runner ) = 0; + virtual void setConfig( Ptr const& config ) = 0; + }; + + IContext& getCurrentContext(); + IMutableContext& getCurrentMutableContext(); + void cleanUpContext(); + Stream createStream( std::string const& streamName ); + +} + +// #included from: internal/catch_test_registry.hpp +#define TWOBLUECUBES_CATCH_TEST_REGISTRY_HPP_INCLUDED + +// #included from: catch_interfaces_testcase.h +#define TWOBLUECUBES_CATCH_INTERFACES_TESTCASE_H_INCLUDED + +#include + +namespace Catch { + + class TestSpec; + + struct ITestCase : IShared { + virtual void invoke () const = 0; + protected: + virtual ~ITestCase(); + }; + + class TestCase; + struct IConfig; + + struct ITestCaseRegistry { + virtual ~ITestCaseRegistry(); + virtual std::vector const& getAllTests() const = 0; + virtual std::vector const& getAllTestsSorted( IConfig const& config ) const = 0; + }; + + bool matchTest( TestCase const& testCase, TestSpec const& testSpec, IConfig const& config ); + std::vector filterTests( std::vector const& testCases, TestSpec const& testSpec, IConfig const& config ); + std::vector const& getAllTestCasesSorted( IConfig const& config ); + +} + +namespace Catch { + +template +class MethodTestCase : public SharedImpl { + +public: + MethodTestCase( void (C::*method)() ) : m_method( method ) {} + + virtual void invoke() const { + C obj; + (obj.*m_method)(); + } + +private: + virtual ~MethodTestCase() {} + + void (C::*m_method)(); +}; + +typedef void(*TestFunction)(); + +struct NameAndDesc { + NameAndDesc( const char* _name = "", const char* _description= "" ) + : name( _name ), description( _description ) + {} + + const char* name; + const char* description; +}; + +void registerTestCase + ( ITestCase* testCase, + char const* className, + NameAndDesc const& nameAndDesc, + SourceLineInfo const& lineInfo ); + +struct AutoReg { + + AutoReg + ( TestFunction function, + SourceLineInfo const& lineInfo, + NameAndDesc const& nameAndDesc ); + + template + AutoReg + ( void (C::*method)(), + char const* className, + NameAndDesc const& nameAndDesc, + SourceLineInfo const& lineInfo ) { + + registerTestCase + ( new MethodTestCase( method ), + className, + nameAndDesc, + lineInfo ); + } + + ~AutoReg(); + +private: + AutoReg( AutoReg const& ); + void operator= ( AutoReg const& ); +}; + +void registerTestCaseFunction + ( TestFunction function, + SourceLineInfo const& lineInfo, + NameAndDesc const& nameAndDesc ); + +} // end namespace Catch + +#ifdef CATCH_CONFIG_VARIADIC_MACROS + /////////////////////////////////////////////////////////////////////////////// + #define INTERNAL_CATCH_TESTCASE2( TestName, ... ) \ + static void TestName(); \ + namespace{ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( &TestName, CATCH_INTERNAL_LINEINFO, Catch::NameAndDesc( __VA_ARGS__ ) ); }\ + static void TestName() + #define INTERNAL_CATCH_TESTCASE( ... ) \ + INTERNAL_CATCH_TESTCASE2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ), __VA_ARGS__ ) + + /////////////////////////////////////////////////////////////////////////////// + #define INTERNAL_CATCH_METHOD_AS_TEST_CASE( QualifiedMethod, ... ) \ + namespace{ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( &QualifiedMethod, "&" #QualifiedMethod, Catch::NameAndDesc( __VA_ARGS__ ), CATCH_INTERNAL_LINEINFO ); } + + /////////////////////////////////////////////////////////////////////////////// + #define INTERNAL_CATCH_TEST_CASE_METHOD2( TestName, ClassName, ... )\ + namespace{ \ + struct TestName : ClassName{ \ + void test(); \ + }; \ + Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar ) ( &TestName::test, #ClassName, Catch::NameAndDesc( __VA_ARGS__ ), CATCH_INTERNAL_LINEINFO ); \ + } \ + void TestName::test() + #define INTERNAL_CATCH_TEST_CASE_METHOD( ClassName, ... ) \ + INTERNAL_CATCH_TEST_CASE_METHOD2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ), ClassName, __VA_ARGS__ ) + + /////////////////////////////////////////////////////////////////////////////// + #define INTERNAL_CATCH_REGISTER_TESTCASE( Function, ... ) \ + Catch::AutoReg( Function, CATCH_INTERNAL_LINEINFO, Catch::NameAndDesc( __VA_ARGS__ ) ); + +#else + /////////////////////////////////////////////////////////////////////////////// + #define INTERNAL_CATCH_TESTCASE2( TestName, Name, Desc ) \ + static void TestName(); \ + namespace{ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( &TestName, CATCH_INTERNAL_LINEINFO, Catch::NameAndDesc( Name, Desc ) ); }\ + static void TestName() + #define INTERNAL_CATCH_TESTCASE( Name, Desc ) \ + INTERNAL_CATCH_TESTCASE2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ), Name, Desc ) + + /////////////////////////////////////////////////////////////////////////////// + #define INTERNAL_CATCH_METHOD_AS_TEST_CASE( QualifiedMethod, Name, Desc ) \ + namespace{ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( &QualifiedMethod, "&" #QualifiedMethod, Catch::NameAndDesc( Name, Desc ), CATCH_INTERNAL_LINEINFO ); } + + /////////////////////////////////////////////////////////////////////////////// + #define INTERNAL_CATCH_TEST_CASE_METHOD2( TestCaseName, ClassName, TestName, Desc )\ + namespace{ \ + struct TestCaseName : ClassName{ \ + void test(); \ + }; \ + Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar ) ( &TestCaseName::test, #ClassName, Catch::NameAndDesc( TestName, Desc ), CATCH_INTERNAL_LINEINFO ); \ + } \ + void TestCaseName::test() + #define INTERNAL_CATCH_TEST_CASE_METHOD( ClassName, TestName, Desc )\ + INTERNAL_CATCH_TEST_CASE_METHOD2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ), ClassName, TestName, Desc ) + + /////////////////////////////////////////////////////////////////////////////// + #define INTERNAL_CATCH_REGISTER_TESTCASE( Function, Name, Desc ) \ + Catch::AutoReg( Function, CATCH_INTERNAL_LINEINFO, Catch::NameAndDesc( Name, Desc ) ); +#endif + +// #included from: internal/catch_capture.hpp +#define TWOBLUECUBES_CATCH_CAPTURE_HPP_INCLUDED + +// #included from: catch_result_builder.h +#define TWOBLUECUBES_CATCH_RESULT_BUILDER_H_INCLUDED + +// #included from: catch_result_type.h +#define TWOBLUECUBES_CATCH_RESULT_TYPE_H_INCLUDED + +namespace Catch { + + // ResultWas::OfType enum + struct ResultWas { enum OfType { + Unknown = -1, + Ok = 0, + Info = 1, + Warning = 2, + + FailureBit = 0x10, + + ExpressionFailed = FailureBit | 1, + ExplicitFailure = FailureBit | 2, + + Exception = 0x100 | FailureBit, + + ThrewException = Exception | 1, + DidntThrowException = Exception | 2, + + FatalErrorCondition = 0x200 | FailureBit + + }; }; + + inline bool isOk( ResultWas::OfType resultType ) { + return ( resultType & ResultWas::FailureBit ) == 0; + } + inline bool isJustInfo( int flags ) { + return flags == ResultWas::Info; + } + + // ResultDisposition::Flags enum + struct ResultDisposition { enum Flags { + Normal = 0x01, + + ContinueOnFailure = 0x02, // Failures fail test, but execution continues + FalseTest = 0x04, // Prefix expression with ! + SuppressFail = 0x08 // Failures are reported but do not fail the test + }; }; + + inline ResultDisposition::Flags operator | ( ResultDisposition::Flags lhs, ResultDisposition::Flags rhs ) { + return static_cast( static_cast( lhs ) | static_cast( rhs ) ); + } + + inline bool shouldContinueOnFailure( int flags ) { return ( flags & ResultDisposition::ContinueOnFailure ) != 0; } + inline bool isFalseTest( int flags ) { return ( flags & ResultDisposition::FalseTest ) != 0; } + inline bool shouldSuppressFailure( int flags ) { return ( flags & ResultDisposition::SuppressFail ) != 0; } + +} // end namespace Catch + +// #included from: catch_assertionresult.h +#define TWOBLUECUBES_CATCH_ASSERTIONRESULT_H_INCLUDED + +#include + +namespace Catch { + + struct AssertionInfo + { + AssertionInfo() {} + AssertionInfo( std::string const& _macroName, + SourceLineInfo const& _lineInfo, + std::string const& _capturedExpression, + ResultDisposition::Flags _resultDisposition ); + + std::string macroName; + SourceLineInfo lineInfo; + std::string capturedExpression; + ResultDisposition::Flags resultDisposition; + }; + + struct AssertionResultData + { + AssertionResultData() : resultType( ResultWas::Unknown ) {} + + std::string reconstructedExpression; + std::string message; + ResultWas::OfType resultType; + }; + + class AssertionResult { + public: + AssertionResult(); + AssertionResult( AssertionInfo const& info, AssertionResultData const& data ); + ~AssertionResult(); +# ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS + AssertionResult( AssertionResult const& ) = default; + AssertionResult( AssertionResult && ) = default; + AssertionResult& operator = ( AssertionResult const& ) = default; + AssertionResult& operator = ( AssertionResult && ) = default; +# endif + + bool isOk() const; + bool succeeded() const; + ResultWas::OfType getResultType() const; + bool hasExpression() const; + bool hasMessage() const; + std::string getExpression() const; + std::string getExpressionInMacro() const; + bool hasExpandedExpression() const; + std::string getExpandedExpression() const; + std::string getMessage() const; + SourceLineInfo getSourceInfo() const; + std::string getTestMacroName() const; + + protected: + AssertionInfo m_info; + AssertionResultData m_resultData; + }; + +} // end namespace Catch + +// #included from: catch_matchers.hpp +#define TWOBLUECUBES_CATCH_MATCHERS_HPP_INCLUDED + +namespace Catch { +namespace Matchers { + namespace Impl { + + namespace Generic { + template class AllOf; + template class AnyOf; + template class Not; + } + + template + struct Matcher : SharedImpl + { + typedef ExpressionT ExpressionType; + + virtual ~Matcher() {} + virtual Ptr clone() const = 0; + virtual bool match( ExpressionT const& expr ) const = 0; + virtual std::string toString() const = 0; + + Generic::AllOf operator && ( Matcher const& other ) const; + Generic::AnyOf operator || ( Matcher const& other ) const; + Generic::Not operator ! () const; + }; + + template + struct MatcherImpl : Matcher { + + virtual Ptr > clone() const { + return Ptr >( new DerivedT( static_cast( *this ) ) ); + } + }; + + namespace Generic { + template + class Not : public MatcherImpl, ExpressionT> { + public: + explicit Not( Matcher const& matcher ) : m_matcher(matcher.clone()) {} + Not( Not const& other ) : m_matcher( other.m_matcher ) {} + + virtual bool match( ExpressionT const& expr ) const CATCH_OVERRIDE { + return !m_matcher->match( expr ); + } + + virtual std::string toString() const CATCH_OVERRIDE { + return "not " + m_matcher->toString(); + } + private: + Ptr< Matcher > m_matcher; + }; + + template + class AllOf : public MatcherImpl, ExpressionT> { + public: + + AllOf() {} + AllOf( AllOf const& other ) : m_matchers( other.m_matchers ) {} + + AllOf& add( Matcher const& matcher ) { + m_matchers.push_back( matcher.clone() ); + return *this; + } + virtual bool match( ExpressionT const& expr ) const + { + for( std::size_t i = 0; i < m_matchers.size(); ++i ) + if( !m_matchers[i]->match( expr ) ) + return false; + return true; + } + virtual std::string toString() const { + std::ostringstream oss; + oss << "( "; + for( std::size_t i = 0; i < m_matchers.size(); ++i ) { + if( i != 0 ) + oss << " and "; + oss << m_matchers[i]->toString(); + } + oss << " )"; + return oss.str(); + } + + AllOf operator && ( Matcher const& other ) const { + AllOf allOfExpr( *this ); + allOfExpr.add( other ); + return allOfExpr; + } + + private: + std::vector > > m_matchers; + }; + + template + class AnyOf : public MatcherImpl, ExpressionT> { + public: + + AnyOf() {} + AnyOf( AnyOf const& other ) : m_matchers( other.m_matchers ) {} + + AnyOf& add( Matcher const& matcher ) { + m_matchers.push_back( matcher.clone() ); + return *this; + } + virtual bool match( ExpressionT const& expr ) const + { + for( std::size_t i = 0; i < m_matchers.size(); ++i ) + if( m_matchers[i]->match( expr ) ) + return true; + return false; + } + virtual std::string toString() const { + std::ostringstream oss; + oss << "( "; + for( std::size_t i = 0; i < m_matchers.size(); ++i ) { + if( i != 0 ) + oss << " or "; + oss << m_matchers[i]->toString(); + } + oss << " )"; + return oss.str(); + } + + AnyOf operator || ( Matcher const& other ) const { + AnyOf anyOfExpr( *this ); + anyOfExpr.add( other ); + return anyOfExpr; + } + + private: + std::vector > > m_matchers; + }; + + } // namespace Generic + + template + Generic::AllOf Matcher::operator && ( Matcher const& other ) const { + Generic::AllOf allOfExpr; + allOfExpr.add( *this ); + allOfExpr.add( other ); + return allOfExpr; + } + + template + Generic::AnyOf Matcher::operator || ( Matcher const& other ) const { + Generic::AnyOf anyOfExpr; + anyOfExpr.add( *this ); + anyOfExpr.add( other ); + return anyOfExpr; + } + + template + Generic::Not Matcher::operator ! () const { + return Generic::Not( *this ); + } + + namespace StdString { + + inline std::string makeString( std::string const& str ) { return str; } + inline std::string makeString( const char* str ) { return str ? std::string( str ) : std::string(); } + + struct CasedString + { + CasedString( std::string const& str, CaseSensitive::Choice caseSensitivity ) + : m_caseSensitivity( caseSensitivity ), + m_str( adjustString( str ) ) + {} + std::string adjustString( std::string const& str ) const { + return m_caseSensitivity == CaseSensitive::No + ? toLower( str ) + : str; + + } + std::string toStringSuffix() const + { + return m_caseSensitivity == CaseSensitive::No + ? " (case insensitive)" + : ""; + } + CaseSensitive::Choice m_caseSensitivity; + std::string m_str; + }; + + struct Equals : MatcherImpl { + Equals( std::string const& str, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes ) + : m_data( str, caseSensitivity ) + {} + Equals( Equals const& other ) : m_data( other.m_data ){} + + virtual ~Equals(); + + virtual bool match( std::string const& expr ) const { + return m_data.m_str == m_data.adjustString( expr );; + } + virtual std::string toString() const { + return "equals: \"" + m_data.m_str + "\"" + m_data.toStringSuffix(); + } + + CasedString m_data; + }; + + struct Contains : MatcherImpl { + Contains( std::string const& substr, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes ) + : m_data( substr, caseSensitivity ){} + Contains( Contains const& other ) : m_data( other.m_data ){} + + virtual ~Contains(); + + virtual bool match( std::string const& expr ) const { + return m_data.adjustString( expr ).find( m_data.m_str ) != std::string::npos; + } + virtual std::string toString() const { + return "contains: \"" + m_data.m_str + "\"" + m_data.toStringSuffix(); + } + + CasedString m_data; + }; + + struct StartsWith : MatcherImpl { + StartsWith( std::string const& substr, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes ) + : m_data( substr, caseSensitivity ){} + + StartsWith( StartsWith const& other ) : m_data( other.m_data ){} + + virtual ~StartsWith(); + + virtual bool match( std::string const& expr ) const { + return startsWith( m_data.adjustString( expr ), m_data.m_str ); + } + virtual std::string toString() const { + return "starts with: \"" + m_data.m_str + "\"" + m_data.toStringSuffix(); + } + + CasedString m_data; + }; + + struct EndsWith : MatcherImpl { + EndsWith( std::string const& substr, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes ) + : m_data( substr, caseSensitivity ){} + EndsWith( EndsWith const& other ) : m_data( other.m_data ){} + + virtual ~EndsWith(); + + virtual bool match( std::string const& expr ) const { + return endsWith( m_data.adjustString( expr ), m_data.m_str ); + } + virtual std::string toString() const { + return "ends with: \"" + m_data.m_str + "\"" + m_data.toStringSuffix(); + } + + CasedString m_data; + }; + } // namespace StdString + } // namespace Impl + + // The following functions create the actual matcher objects. + // This allows the types to be inferred + template + inline Impl::Generic::Not Not( Impl::Matcher const& m ) { + return Impl::Generic::Not( m ); + } + + template + inline Impl::Generic::AllOf AllOf( Impl::Matcher const& m1, + Impl::Matcher const& m2 ) { + return Impl::Generic::AllOf().add( m1 ).add( m2 ); + } + template + inline Impl::Generic::AllOf AllOf( Impl::Matcher const& m1, + Impl::Matcher const& m2, + Impl::Matcher const& m3 ) { + return Impl::Generic::AllOf().add( m1 ).add( m2 ).add( m3 ); + } + template + inline Impl::Generic::AnyOf AnyOf( Impl::Matcher const& m1, + Impl::Matcher const& m2 ) { + return Impl::Generic::AnyOf().add( m1 ).add( m2 ); + } + template + inline Impl::Generic::AnyOf AnyOf( Impl::Matcher const& m1, + Impl::Matcher const& m2, + Impl::Matcher const& m3 ) { + return Impl::Generic::AnyOf().add( m1 ).add( m2 ).add( m3 ); + } + + inline Impl::StdString::Equals Equals( std::string const& str, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes ) { + return Impl::StdString::Equals( str, caseSensitivity ); + } + inline Impl::StdString::Equals Equals( const char* str, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes ) { + return Impl::StdString::Equals( Impl::StdString::makeString( str ), caseSensitivity ); + } + inline Impl::StdString::Contains Contains( std::string const& substr, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes ) { + return Impl::StdString::Contains( substr, caseSensitivity ); + } + inline Impl::StdString::Contains Contains( const char* substr, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes ) { + return Impl::StdString::Contains( Impl::StdString::makeString( substr ), caseSensitivity ); + } + inline Impl::StdString::StartsWith StartsWith( std::string const& substr ) { + return Impl::StdString::StartsWith( substr ); + } + inline Impl::StdString::StartsWith StartsWith( const char* substr ) { + return Impl::StdString::StartsWith( Impl::StdString::makeString( substr ) ); + } + inline Impl::StdString::EndsWith EndsWith( std::string const& substr ) { + return Impl::StdString::EndsWith( substr ); + } + inline Impl::StdString::EndsWith EndsWith( const char* substr ) { + return Impl::StdString::EndsWith( Impl::StdString::makeString( substr ) ); + } + +} // namespace Matchers + +using namespace Matchers; + +} // namespace Catch + +namespace Catch { + + struct TestFailureException{}; + + template class ExpressionLhs; + + struct STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison; + + struct CopyableStream { + CopyableStream() {} + CopyableStream( CopyableStream const& other ) { + oss << other.oss.str(); + } + CopyableStream& operator=( CopyableStream const& other ) { + oss.str(""); + oss << other.oss.str(); + return *this; + } + std::ostringstream oss; + }; + + class ResultBuilder { + public: + ResultBuilder( char const* macroName, + SourceLineInfo const& lineInfo, + char const* capturedExpression, + ResultDisposition::Flags resultDisposition, + char const* secondArg = "" ); + + template + ExpressionLhs operator <= ( T const& operand ); + ExpressionLhs operator <= ( bool value ); + + template + ResultBuilder& operator << ( T const& value ) { + m_stream.oss << value; + return *this; + } + + template STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator && ( RhsT const& ); + template STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator || ( RhsT const& ); + + ResultBuilder& setResultType( ResultWas::OfType result ); + ResultBuilder& setResultType( bool result ); + ResultBuilder& setLhs( std::string const& lhs ); + ResultBuilder& setRhs( std::string const& rhs ); + ResultBuilder& setOp( std::string const& op ); + + void endExpression(); + + std::string reconstructExpression() const; + AssertionResult build() const; + + void useActiveException( ResultDisposition::Flags resultDisposition = ResultDisposition::Normal ); + void captureResult( ResultWas::OfType resultType ); + void captureExpression(); + void captureExpectedException( std::string const& expectedMessage ); + void captureExpectedException( Matchers::Impl::Matcher const& matcher ); + void handleResult( AssertionResult const& result ); + void react(); + bool shouldDebugBreak() const; + bool allowThrows() const; + + private: + AssertionInfo m_assertionInfo; + AssertionResultData m_data; + struct ExprComponents { + ExprComponents() : testFalse( false ) {} + bool testFalse; + std::string lhs, rhs, op; + } m_exprComponents; + CopyableStream m_stream; + + bool m_shouldDebugBreak; + bool m_shouldThrow; + }; + +} // namespace Catch + +// Include after due to circular dependency: +// #included from: catch_expression_lhs.hpp +#define TWOBLUECUBES_CATCH_EXPRESSION_LHS_HPP_INCLUDED + +// #included from: catch_evaluate.hpp +#define TWOBLUECUBES_CATCH_EVALUATE_HPP_INCLUDED + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable:4389) // '==' : signed/unsigned mismatch +#endif + +#include + +namespace Catch { +namespace Internal { + + enum Operator { + IsEqualTo, + IsNotEqualTo, + IsLessThan, + IsGreaterThan, + IsLessThanOrEqualTo, + IsGreaterThanOrEqualTo + }; + + template struct OperatorTraits { static const char* getName(){ return "*error*"; } }; + template<> struct OperatorTraits { static const char* getName(){ return "=="; } }; + template<> struct OperatorTraits { static const char* getName(){ return "!="; } }; + template<> struct OperatorTraits { static const char* getName(){ return "<"; } }; + template<> struct OperatorTraits { static const char* getName(){ return ">"; } }; + template<> struct OperatorTraits { static const char* getName(){ return "<="; } }; + template<> struct OperatorTraits{ static const char* getName(){ return ">="; } }; + + template + inline T& opCast(T const& t) { return const_cast(t); } + +// nullptr_t support based on pull request #154 from Konstantin Baumann +#ifdef CATCH_CONFIG_CPP11_NULLPTR + inline std::nullptr_t opCast(std::nullptr_t) { return nullptr; } +#endif // CATCH_CONFIG_CPP11_NULLPTR + + // So the compare overloads can be operator agnostic we convey the operator as a template + // enum, which is used to specialise an Evaluator for doing the comparison. + template + class Evaluator{}; + + template + struct Evaluator { + static bool evaluate( T1 const& lhs, T2 const& rhs) { + return bool( opCast( lhs ) == opCast( rhs ) ); + } + }; + template + struct Evaluator { + static bool evaluate( T1 const& lhs, T2 const& rhs ) { + return bool( opCast( lhs ) != opCast( rhs ) ); + } + }; + template + struct Evaluator { + static bool evaluate( T1 const& lhs, T2 const& rhs ) { + return bool( opCast( lhs ) < opCast( rhs ) ); + } + }; + template + struct Evaluator { + static bool evaluate( T1 const& lhs, T2 const& rhs ) { + return bool( opCast( lhs ) > opCast( rhs ) ); + } + }; + template + struct Evaluator { + static bool evaluate( T1 const& lhs, T2 const& rhs ) { + return bool( opCast( lhs ) >= opCast( rhs ) ); + } + }; + template + struct Evaluator { + static bool evaluate( T1 const& lhs, T2 const& rhs ) { + return bool( opCast( lhs ) <= opCast( rhs ) ); + } + }; + + template + bool applyEvaluator( T1 const& lhs, T2 const& rhs ) { + return Evaluator::evaluate( lhs, rhs ); + } + + // This level of indirection allows us to specialise for integer types + // to avoid signed/ unsigned warnings + + // "base" overload + template + bool compare( T1 const& lhs, T2 const& rhs ) { + return Evaluator::evaluate( lhs, rhs ); + } + + // unsigned X to int + template bool compare( unsigned int lhs, int rhs ) { + return applyEvaluator( lhs, static_cast( rhs ) ); + } + template bool compare( unsigned long lhs, int rhs ) { + return applyEvaluator( lhs, static_cast( rhs ) ); + } + template bool compare( unsigned char lhs, int rhs ) { + return applyEvaluator( lhs, static_cast( rhs ) ); + } + + // unsigned X to long + template bool compare( unsigned int lhs, long rhs ) { + return applyEvaluator( lhs, static_cast( rhs ) ); + } + template bool compare( unsigned long lhs, long rhs ) { + return applyEvaluator( lhs, static_cast( rhs ) ); + } + template bool compare( unsigned char lhs, long rhs ) { + return applyEvaluator( lhs, static_cast( rhs ) ); + } + + // int to unsigned X + template bool compare( int lhs, unsigned int rhs ) { + return applyEvaluator( static_cast( lhs ), rhs ); + } + template bool compare( int lhs, unsigned long rhs ) { + return applyEvaluator( static_cast( lhs ), rhs ); + } + template bool compare( int lhs, unsigned char rhs ) { + return applyEvaluator( static_cast( lhs ), rhs ); + } + + // long to unsigned X + template bool compare( long lhs, unsigned int rhs ) { + return applyEvaluator( static_cast( lhs ), rhs ); + } + template bool compare( long lhs, unsigned long rhs ) { + return applyEvaluator( static_cast( lhs ), rhs ); + } + template bool compare( long lhs, unsigned char rhs ) { + return applyEvaluator( static_cast( lhs ), rhs ); + } + + // pointer to long (when comparing against NULL) + template bool compare( long lhs, T* rhs ) { + return Evaluator::evaluate( reinterpret_cast( lhs ), rhs ); + } + template bool compare( T* lhs, long rhs ) { + return Evaluator::evaluate( lhs, reinterpret_cast( rhs ) ); + } + + // pointer to int (when comparing against NULL) + template bool compare( int lhs, T* rhs ) { + return Evaluator::evaluate( reinterpret_cast( lhs ), rhs ); + } + template bool compare( T* lhs, int rhs ) { + return Evaluator::evaluate( lhs, reinterpret_cast( rhs ) ); + } + +#ifdef CATCH_CONFIG_CPP11_LONG_LONG + // long long to unsigned X + template bool compare( long long lhs, unsigned int rhs ) { + return applyEvaluator( static_cast( lhs ), rhs ); + } + template bool compare( long long lhs, unsigned long rhs ) { + return applyEvaluator( static_cast( lhs ), rhs ); + } + template bool compare( long long lhs, unsigned long long rhs ) { + return applyEvaluator( static_cast( lhs ), rhs ); + } + template bool compare( long long lhs, unsigned char rhs ) { + return applyEvaluator( static_cast( lhs ), rhs ); + } + + // unsigned long long to X + template bool compare( unsigned long long lhs, int rhs ) { + return applyEvaluator( static_cast( lhs ), rhs ); + } + template bool compare( unsigned long long lhs, long rhs ) { + return applyEvaluator( static_cast( lhs ), rhs ); + } + template bool compare( unsigned long long lhs, long long rhs ) { + return applyEvaluator( static_cast( lhs ), rhs ); + } + template bool compare( unsigned long long lhs, char rhs ) { + return applyEvaluator( static_cast( lhs ), rhs ); + } + + // pointer to long long (when comparing against NULL) + template bool compare( long long lhs, T* rhs ) { + return Evaluator::evaluate( reinterpret_cast( lhs ), rhs ); + } + template bool compare( T* lhs, long long rhs ) { + return Evaluator::evaluate( lhs, reinterpret_cast( rhs ) ); + } +#endif // CATCH_CONFIG_CPP11_LONG_LONG + +#ifdef CATCH_CONFIG_CPP11_NULLPTR + // pointer to nullptr_t (when comparing against nullptr) + template bool compare( std::nullptr_t, T* rhs ) { + return Evaluator::evaluate( nullptr, rhs ); + } + template bool compare( T* lhs, std::nullptr_t ) { + return Evaluator::evaluate( lhs, nullptr ); + } +#endif // CATCH_CONFIG_CPP11_NULLPTR + +} // end of namespace Internal +} // end of namespace Catch + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +// #included from: catch_tostring.h +#define TWOBLUECUBES_CATCH_TOSTRING_H_INCLUDED + +#include +#include +#include +#include +#include + +#ifdef __OBJC__ +// #included from: catch_objc_arc.hpp +#define TWOBLUECUBES_CATCH_OBJC_ARC_HPP_INCLUDED + +#import + +#ifdef __has_feature +#define CATCH_ARC_ENABLED __has_feature(objc_arc) +#else +#define CATCH_ARC_ENABLED 0 +#endif + +void arcSafeRelease( NSObject* obj ); +id performOptionalSelector( id obj, SEL sel ); + +#if !CATCH_ARC_ENABLED +inline void arcSafeRelease( NSObject* obj ) { + [obj release]; +} +inline id performOptionalSelector( id obj, SEL sel ) { + if( [obj respondsToSelector: sel] ) + return [obj performSelector: sel]; + return nil; +} +#define CATCH_UNSAFE_UNRETAINED +#define CATCH_ARC_STRONG +#else +inline void arcSafeRelease( NSObject* ){} +inline id performOptionalSelector( id obj, SEL sel ) { +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Warc-performSelector-leaks" +#endif + if( [obj respondsToSelector: sel] ) + return [obj performSelector: sel]; +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + return nil; +} +#define CATCH_UNSAFE_UNRETAINED __unsafe_unretained +#define CATCH_ARC_STRONG __strong +#endif + +#endif + +#ifdef CATCH_CONFIG_CPP11_TUPLE +#include +#endif + +#ifdef CATCH_CONFIG_CPP11_IS_ENUM +#include +#endif + +namespace Catch { + +// Why we're here. +template +std::string toString( T const& value ); + +// Built in overloads + +std::string toString( std::string const& value ); +std::string toString( std::wstring const& value ); +std::string toString( const char* const value ); +std::string toString( char* const value ); +std::string toString( const wchar_t* const value ); +std::string toString( wchar_t* const value ); +std::string toString( int value ); +std::string toString( unsigned long value ); +std::string toString( unsigned int value ); +std::string toString( const double value ); +std::string toString( const float value ); +std::string toString( bool value ); +std::string toString( char value ); +std::string toString( signed char value ); +std::string toString( unsigned char value ); + +#ifdef CATCH_CONFIG_CPP11_LONG_LONG +std::string toString( long long value ); +std::string toString( unsigned long long value ); +#endif + +#ifdef CATCH_CONFIG_CPP11_NULLPTR +std::string toString( std::nullptr_t ); +#endif + +#ifdef __OBJC__ + std::string toString( NSString const * const& nsstring ); + std::string toString( NSString * CATCH_ARC_STRONG const& nsstring ); + std::string toString( NSObject* const& nsObject ); +#endif + +namespace Detail { + + extern const std::string unprintableString; + + struct BorgType { + template BorgType( T const& ); + }; + + struct TrueType { char sizer[1]; }; + struct FalseType { char sizer[2]; }; + + TrueType& testStreamable( std::ostream& ); + FalseType testStreamable( FalseType ); + + FalseType operator<<( std::ostream const&, BorgType const& ); + + template + struct IsStreamInsertable { + static std::ostream &s; + static T const&t; + enum { value = sizeof( testStreamable(s << t) ) == sizeof( TrueType ) }; + }; + +#if defined(CATCH_CONFIG_CPP11_IS_ENUM) + template::value + > + struct EnumStringMaker + { + static std::string convert( T const& ) { return unprintableString; } + }; + + template + struct EnumStringMaker + { + static std::string convert( T const& v ) + { + return ::Catch::toString( + static_cast::type>(v) + ); + } + }; +#endif + template + struct StringMakerBase { +#if defined(CATCH_CONFIG_CPP11_IS_ENUM) + template + static std::string convert( T const& v ) + { + return EnumStringMaker::convert( v ); + } +#else + template + static std::string convert( T const& ) { return unprintableString; } +#endif + }; + + template<> + struct StringMakerBase { + template + static std::string convert( T const& _value ) { + std::ostringstream oss; + oss << _value; + return oss.str(); + } + }; + + std::string rawMemoryToString( const void *object, std::size_t size ); + + template + inline std::string rawMemoryToString( const T& object ) { + return rawMemoryToString( &object, sizeof(object) ); + } + +} // end namespace Detail + +template +struct StringMaker : + Detail::StringMakerBase::value> {}; + +template +struct StringMaker { + template + static std::string convert( U* p ) { + if( !p ) + return "NULL"; + else + return Detail::rawMemoryToString( p ); + } +}; + +template +struct StringMaker { + static std::string convert( R C::* p ) { + if( !p ) + return "NULL"; + else + return Detail::rawMemoryToString( p ); + } +}; + +namespace Detail { + template + std::string rangeToString( InputIterator first, InputIterator last ); +} + +//template +//struct StringMaker > { +// static std::string convert( std::vector const& v ) { +// return Detail::rangeToString( v.begin(), v.end() ); +// } +//}; + +template +std::string toString( std::vector const& v ) { + return Detail::rangeToString( v.begin(), v.end() ); +} + +#ifdef CATCH_CONFIG_CPP11_TUPLE + +// toString for tuples +namespace TupleDetail { + template< + typename Tuple, + std::size_t N = 0, + bool = (N < std::tuple_size::value) + > + struct ElementPrinter { + static void print( const Tuple& tuple, std::ostream& os ) + { + os << ( N ? ", " : " " ) + << Catch::toString(std::get(tuple)); + ElementPrinter::print(tuple,os); + } + }; + + template< + typename Tuple, + std::size_t N + > + struct ElementPrinter { + static void print( const Tuple&, std::ostream& ) {} + }; + +} + +template +struct StringMaker> { + + static std::string convert( const std::tuple& tuple ) + { + std::ostringstream os; + os << '{'; + TupleDetail::ElementPrinter>::print( tuple, os ); + os << " }"; + return os.str(); + } +}; +#endif // CATCH_CONFIG_CPP11_TUPLE + +namespace Detail { + template + std::string makeString( T const& value ) { + return StringMaker::convert( value ); + } +} // end namespace Detail + +/// \brief converts any type to a string +/// +/// The default template forwards on to ostringstream - except when an +/// ostringstream overload does not exist - in which case it attempts to detect +/// that and writes {?}. +/// Overload (not specialise) this template for custom typs that you don't want +/// to provide an ostream overload for. +template +std::string toString( T const& value ) { + return StringMaker::convert( value ); +} + + namespace Detail { + template + std::string rangeToString( InputIterator first, InputIterator last ) { + std::ostringstream oss; + oss << "{ "; + if( first != last ) { + oss << Catch::toString( *first ); + for( ++first ; first != last ; ++first ) + oss << ", " << Catch::toString( *first ); + } + oss << " }"; + return oss.str(); + } +} + +} // end namespace Catch + +namespace Catch { + +// Wraps the LHS of an expression and captures the operator and RHS (if any) - +// wrapping them all in a ResultBuilder object +template +class ExpressionLhs { + ExpressionLhs& operator = ( ExpressionLhs const& ); +# ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS + ExpressionLhs& operator = ( ExpressionLhs && ) = delete; +# endif + +public: + ExpressionLhs( ResultBuilder& rb, T lhs ) : m_rb( rb ), m_lhs( lhs ) {} +# ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS + ExpressionLhs( ExpressionLhs const& ) = default; + ExpressionLhs( ExpressionLhs && ) = default; +# endif + + template + ResultBuilder& operator == ( RhsT const& rhs ) { + return captureExpression( rhs ); + } + + template + ResultBuilder& operator != ( RhsT const& rhs ) { + return captureExpression( rhs ); + } + + template + ResultBuilder& operator < ( RhsT const& rhs ) { + return captureExpression( rhs ); + } + + template + ResultBuilder& operator > ( RhsT const& rhs ) { + return captureExpression( rhs ); + } + + template + ResultBuilder& operator <= ( RhsT const& rhs ) { + return captureExpression( rhs ); + } + + template + ResultBuilder& operator >= ( RhsT const& rhs ) { + return captureExpression( rhs ); + } + + ResultBuilder& operator == ( bool rhs ) { + return captureExpression( rhs ); + } + + ResultBuilder& operator != ( bool rhs ) { + return captureExpression( rhs ); + } + + void endExpression() { + bool value = m_lhs ? true : false; + m_rb + .setLhs( Catch::toString( value ) ) + .setResultType( value ) + .endExpression(); + } + + // Only simple binary expressions are allowed on the LHS. + // If more complex compositions are required then place the sub expression in parentheses + template STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator + ( RhsT const& ); + template STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator - ( RhsT const& ); + template STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator / ( RhsT const& ); + template STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator * ( RhsT const& ); + template STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator && ( RhsT const& ); + template STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator || ( RhsT const& ); + +private: + template + ResultBuilder& captureExpression( RhsT const& rhs ) { + return m_rb + .setResultType( Internal::compare( m_lhs, rhs ) ) + .setLhs( Catch::toString( m_lhs ) ) + .setRhs( Catch::toString( rhs ) ) + .setOp( Internal::OperatorTraits::getName() ); + } + +private: + ResultBuilder& m_rb; + T m_lhs; +}; + +} // end namespace Catch + + +namespace Catch { + + template + inline ExpressionLhs ResultBuilder::operator <= ( T const& operand ) { + return ExpressionLhs( *this, operand ); + } + + inline ExpressionLhs ResultBuilder::operator <= ( bool value ) { + return ExpressionLhs( *this, value ); + } + +} // namespace Catch + +// #included from: catch_message.h +#define TWOBLUECUBES_CATCH_MESSAGE_H_INCLUDED + +#include + +namespace Catch { + + struct MessageInfo { + MessageInfo( std::string const& _macroName, + SourceLineInfo const& _lineInfo, + ResultWas::OfType _type ); + + std::string macroName; + SourceLineInfo lineInfo; + ResultWas::OfType type; + std::string message; + unsigned int sequence; + + bool operator == ( MessageInfo const& other ) const { + return sequence == other.sequence; + } + bool operator < ( MessageInfo const& other ) const { + return sequence < other.sequence; + } + private: + static unsigned int globalCount; + }; + + struct MessageBuilder { + MessageBuilder( std::string const& macroName, + SourceLineInfo const& lineInfo, + ResultWas::OfType type ) + : m_info( macroName, lineInfo, type ) + {} + + template + MessageBuilder& operator << ( T const& value ) { + m_stream << value; + return *this; + } + + MessageInfo m_info; + std::ostringstream m_stream; + }; + + class ScopedMessage { + public: + ScopedMessage( MessageBuilder const& builder ); + ScopedMessage( ScopedMessage const& other ); + ~ScopedMessage(); + + MessageInfo m_info; + }; + +} // end namespace Catch + +// #included from: catch_interfaces_capture.h +#define TWOBLUECUBES_CATCH_INTERFACES_CAPTURE_H_INCLUDED + +#include + +namespace Catch { + + class TestCase; + class AssertionResult; + struct AssertionInfo; + struct SectionInfo; + struct SectionEndInfo; + struct MessageInfo; + class ScopedMessageBuilder; + struct Counts; + + struct IResultCapture { + + virtual ~IResultCapture(); + + virtual void assertionEnded( AssertionResult const& result ) = 0; + virtual bool sectionStarted( SectionInfo const& sectionInfo, + Counts& assertions ) = 0; + virtual void sectionEnded( SectionEndInfo const& endInfo ) = 0; + virtual void sectionEndedEarly( SectionEndInfo const& endInfo ) = 0; + virtual void pushScopedMessage( MessageInfo const& message ) = 0; + virtual void popScopedMessage( MessageInfo const& message ) = 0; + + virtual std::string getCurrentTestName() const = 0; + virtual const AssertionResult* getLastResult() const = 0; + + virtual void handleFatalErrorCondition( std::string const& message ) = 0; + }; + + IResultCapture& getResultCapture(); +} + +// #included from: catch_debugger.h +#define TWOBLUECUBES_CATCH_DEBUGGER_H_INCLUDED + +// #included from: catch_platform.h +#define TWOBLUECUBES_CATCH_PLATFORM_H_INCLUDED + +#if defined(__MAC_OS_X_VERSION_MIN_REQUIRED) +#define CATCH_PLATFORM_MAC +#elif defined(__IPHONE_OS_VERSION_MIN_REQUIRED) +#define CATCH_PLATFORM_IPHONE +#elif defined(WIN32) || defined(__WIN32__) || defined(_WIN32) || defined(_MSC_VER) +#define CATCH_PLATFORM_WINDOWS +#endif + +#include + +namespace Catch{ + + bool isDebuggerActive(); + void writeToDebugConsole( std::string const& text ); +} + +#ifdef CATCH_PLATFORM_MAC + + // The following code snippet based on: + // http://cocoawithlove.com/2008/03/break-into-debugger.html + #ifdef DEBUG + #if defined(__ppc64__) || defined(__ppc__) + #define CATCH_BREAK_INTO_DEBUGGER() \ + if( Catch::isDebuggerActive() ) { \ + __asm__("li r0, 20\nsc\nnop\nli r0, 37\nli r4, 2\nsc\nnop\n" \ + : : : "memory","r0","r3","r4" ); \ + } + #else + #define CATCH_BREAK_INTO_DEBUGGER() if( Catch::isDebuggerActive() ) {__asm__("int $3\n" : : );} + #endif + #endif + +#elif defined(_MSC_VER) + #define CATCH_BREAK_INTO_DEBUGGER() if( Catch::isDebuggerActive() ) { __debugbreak(); } +#elif defined(__MINGW32__) + extern "C" __declspec(dllimport) void __stdcall DebugBreak(); + #define CATCH_BREAK_INTO_DEBUGGER() if( Catch::isDebuggerActive() ) { DebugBreak(); } +#endif + +#ifndef CATCH_BREAK_INTO_DEBUGGER +#define CATCH_BREAK_INTO_DEBUGGER() Catch::alwaysTrue(); +#endif + +// #included from: catch_interfaces_runner.h +#define TWOBLUECUBES_CATCH_INTERFACES_RUNNER_H_INCLUDED + +namespace Catch { + class TestCase; + + struct IRunner { + virtual ~IRunner(); + virtual bool aborting() const = 0; + }; +} + +/////////////////////////////////////////////////////////////////////////////// +// In the event of a failure works out if the debugger needs to be invoked +// and/or an exception thrown and takes appropriate action. +// This needs to be done as a macro so the debugger will stop in the user +// source code rather than in Catch library code +#define INTERNAL_CATCH_REACT( resultBuilder ) \ + if( resultBuilder.shouldDebugBreak() ) CATCH_BREAK_INTO_DEBUGGER(); \ + resultBuilder.react(); + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_TEST( expr, resultDisposition, macroName ) \ + do { \ + Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, #expr, resultDisposition ); \ + try { \ + CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS \ + ( __catchResult <= expr ).endExpression(); \ + } \ + catch( ... ) { \ + __catchResult.useActiveException( Catch::ResultDisposition::Normal ); \ + } \ + INTERNAL_CATCH_REACT( __catchResult ) \ + } while( Catch::isTrue( false && static_cast(expr) ) ) // expr here is never evaluated at runtime but it forces the compiler to give it a look + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_IF( expr, resultDisposition, macroName ) \ + INTERNAL_CATCH_TEST( expr, resultDisposition, macroName ); \ + if( Catch::getResultCapture().getLastResult()->succeeded() ) + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_ELSE( expr, resultDisposition, macroName ) \ + INTERNAL_CATCH_TEST( expr, resultDisposition, macroName ); \ + if( !Catch::getResultCapture().getLastResult()->succeeded() ) + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_NO_THROW( expr, resultDisposition, macroName ) \ + do { \ + Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, #expr, resultDisposition ); \ + try { \ + expr; \ + __catchResult.captureResult( Catch::ResultWas::Ok ); \ + } \ + catch( ... ) { \ + __catchResult.useActiveException( resultDisposition ); \ + } \ + INTERNAL_CATCH_REACT( __catchResult ) \ + } while( Catch::alwaysFalse() ) + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_THROWS( expr, resultDisposition, matcher, macroName ) \ + do { \ + Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, #expr, resultDisposition, #matcher ); \ + if( __catchResult.allowThrows() ) \ + try { \ + expr; \ + __catchResult.captureResult( Catch::ResultWas::DidntThrowException ); \ + } \ + catch( ... ) { \ + __catchResult.captureExpectedException( matcher ); \ + } \ + else \ + __catchResult.captureResult( Catch::ResultWas::Ok ); \ + INTERNAL_CATCH_REACT( __catchResult ) \ + } while( Catch::alwaysFalse() ) + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_THROWS_AS( expr, exceptionType, resultDisposition, macroName ) \ + do { \ + Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, #expr, resultDisposition ); \ + if( __catchResult.allowThrows() ) \ + try { \ + expr; \ + __catchResult.captureResult( Catch::ResultWas::DidntThrowException ); \ + } \ + catch( exceptionType ) { \ + __catchResult.captureResult( Catch::ResultWas::Ok ); \ + } \ + catch( ... ) { \ + __catchResult.useActiveException( resultDisposition ); \ + } \ + else \ + __catchResult.captureResult( Catch::ResultWas::Ok ); \ + INTERNAL_CATCH_REACT( __catchResult ) \ + } while( Catch::alwaysFalse() ) + +/////////////////////////////////////////////////////////////////////////////// +#ifdef CATCH_CONFIG_VARIADIC_MACROS + #define INTERNAL_CATCH_MSG( messageType, resultDisposition, macroName, ... ) \ + do { \ + Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, "", resultDisposition ); \ + __catchResult << __VA_ARGS__ + ::Catch::StreamEndStop(); \ + __catchResult.captureResult( messageType ); \ + INTERNAL_CATCH_REACT( __catchResult ) \ + } while( Catch::alwaysFalse() ) +#else + #define INTERNAL_CATCH_MSG( messageType, resultDisposition, macroName, log ) \ + do { \ + Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, "", resultDisposition ); \ + __catchResult << log + ::Catch::StreamEndStop(); \ + __catchResult.captureResult( messageType ); \ + INTERNAL_CATCH_REACT( __catchResult ) \ + } while( Catch::alwaysFalse() ) +#endif + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_INFO( log, macroName ) \ + Catch::ScopedMessage INTERNAL_CATCH_UNIQUE_NAME( scopedMessage ) = Catch::MessageBuilder( macroName, CATCH_INTERNAL_LINEINFO, Catch::ResultWas::Info ) << log; + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CHECK_THAT( arg, matcher, resultDisposition, macroName ) \ + do { \ + Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, #arg ", " #matcher, resultDisposition ); \ + try { \ + std::string matcherAsString = (matcher).toString(); \ + __catchResult \ + .setLhs( Catch::toString( arg ) ) \ + .setRhs( matcherAsString == Catch::Detail::unprintableString ? #matcher : matcherAsString ) \ + .setOp( "matches" ) \ + .setResultType( (matcher).match( arg ) ); \ + __catchResult.captureExpression(); \ + } catch( ... ) { \ + __catchResult.useActiveException( resultDisposition | Catch::ResultDisposition::ContinueOnFailure ); \ + } \ + INTERNAL_CATCH_REACT( __catchResult ) \ + } while( Catch::alwaysFalse() ) + +// #included from: internal/catch_section.h +#define TWOBLUECUBES_CATCH_SECTION_H_INCLUDED + +// #included from: catch_section_info.h +#define TWOBLUECUBES_CATCH_SECTION_INFO_H_INCLUDED + +// #included from: catch_totals.hpp +#define TWOBLUECUBES_CATCH_TOTALS_HPP_INCLUDED + +#include + +namespace Catch { + + struct Counts { + Counts() : passed( 0 ), failed( 0 ), failedButOk( 0 ) {} + + Counts operator - ( Counts const& other ) const { + Counts diff; + diff.passed = passed - other.passed; + diff.failed = failed - other.failed; + diff.failedButOk = failedButOk - other.failedButOk; + return diff; + } + Counts& operator += ( Counts const& other ) { + passed += other.passed; + failed += other.failed; + failedButOk += other.failedButOk; + return *this; + } + + std::size_t total() const { + return passed + failed + failedButOk; + } + bool allPassed() const { + return failed == 0 && failedButOk == 0; + } + bool allOk() const { + return failed == 0; + } + + std::size_t passed; + std::size_t failed; + std::size_t failedButOk; + }; + + struct Totals { + + Totals operator - ( Totals const& other ) const { + Totals diff; + diff.assertions = assertions - other.assertions; + diff.testCases = testCases - other.testCases; + return diff; + } + + Totals delta( Totals const& prevTotals ) const { + Totals diff = *this - prevTotals; + if( diff.assertions.failed > 0 ) + ++diff.testCases.failed; + else if( diff.assertions.failedButOk > 0 ) + ++diff.testCases.failedButOk; + else + ++diff.testCases.passed; + return diff; + } + + Totals& operator += ( Totals const& other ) { + assertions += other.assertions; + testCases += other.testCases; + return *this; + } + + Counts assertions; + Counts testCases; + }; +} + +namespace Catch { + + struct SectionInfo { + SectionInfo + ( SourceLineInfo const& _lineInfo, + std::string const& _name, + std::string const& _description = std::string() ); + + std::string name; + std::string description; + SourceLineInfo lineInfo; + }; + + struct SectionEndInfo { + SectionEndInfo( SectionInfo const& _sectionInfo, Counts const& _prevAssertions, double _durationInSeconds ) + : sectionInfo( _sectionInfo ), prevAssertions( _prevAssertions ), durationInSeconds( _durationInSeconds ) + {} + + SectionInfo sectionInfo; + Counts prevAssertions; + double durationInSeconds; + }; + +} // end namespace Catch + +// #included from: catch_timer.h +#define TWOBLUECUBES_CATCH_TIMER_H_INCLUDED + +#ifdef CATCH_PLATFORM_WINDOWS +typedef unsigned long long uint64_t; +#else +#include +#endif + +namespace Catch { + + class Timer { + public: + Timer() : m_ticks( 0 ) {} + void start(); + unsigned int getElapsedMicroseconds() const; + unsigned int getElapsedMilliseconds() const; + double getElapsedSeconds() const; + + private: + uint64_t m_ticks; + }; + +} // namespace Catch + +#include + +namespace Catch { + + class Section : NonCopyable { + public: + Section( SectionInfo const& info ); + ~Section(); + + // This indicates whether the section should be executed or not + operator bool() const; + + private: + SectionInfo m_info; + + std::string m_name; + Counts m_assertions; + bool m_sectionIncluded; + Timer m_timer; + }; + +} // end namespace Catch + +#ifdef CATCH_CONFIG_VARIADIC_MACROS + #define INTERNAL_CATCH_SECTION( ... ) \ + if( Catch::Section const& INTERNAL_CATCH_UNIQUE_NAME( catch_internal_Section ) = Catch::SectionInfo( CATCH_INTERNAL_LINEINFO, __VA_ARGS__ ) ) +#else + #define INTERNAL_CATCH_SECTION( name, desc ) \ + if( Catch::Section const& INTERNAL_CATCH_UNIQUE_NAME( catch_internal_Section ) = Catch::SectionInfo( CATCH_INTERNAL_LINEINFO, name, desc ) ) +#endif + +// #included from: internal/catch_generators.hpp +#define TWOBLUECUBES_CATCH_GENERATORS_HPP_INCLUDED + +#include +#include +#include +#include + +namespace Catch { + +template +struct IGenerator { + virtual ~IGenerator() {} + virtual T getValue( std::size_t index ) const = 0; + virtual std::size_t size () const = 0; +}; + +template +class BetweenGenerator : public IGenerator { +public: + BetweenGenerator( T from, T to ) : m_from( from ), m_to( to ){} + + virtual T getValue( std::size_t index ) const { + return m_from+static_cast( index ); + } + + virtual std::size_t size() const { + return static_cast( 1+m_to-m_from ); + } + +private: + + T m_from; + T m_to; +}; + +template +class ValuesGenerator : public IGenerator { +public: + ValuesGenerator(){} + + void add( T value ) { + m_values.push_back( value ); + } + + virtual T getValue( std::size_t index ) const { + return m_values[index]; + } + + virtual std::size_t size() const { + return m_values.size(); + } + +private: + std::vector m_values; +}; + +template +class CompositeGenerator { +public: + CompositeGenerator() : m_totalSize( 0 ) {} + + // *** Move semantics, similar to auto_ptr *** + CompositeGenerator( CompositeGenerator& other ) + : m_fileInfo( other.m_fileInfo ), + m_totalSize( 0 ) + { + move( other ); + } + + CompositeGenerator& setFileInfo( const char* fileInfo ) { + m_fileInfo = fileInfo; + return *this; + } + + ~CompositeGenerator() { + deleteAll( m_composed ); + } + + operator T () const { + size_t overallIndex = getCurrentContext().getGeneratorIndex( m_fileInfo, m_totalSize ); + + typename std::vector*>::const_iterator it = m_composed.begin(); + typename std::vector*>::const_iterator itEnd = m_composed.end(); + for( size_t index = 0; it != itEnd; ++it ) + { + const IGenerator* generator = *it; + if( overallIndex >= index && overallIndex < index + generator->size() ) + { + return generator->getValue( overallIndex-index ); + } + index += generator->size(); + } + CATCH_INTERNAL_ERROR( "Indexed past end of generated range" ); + return T(); // Suppress spurious "not all control paths return a value" warning in Visual Studio - if you know how to fix this please do so + } + + void add( const IGenerator* generator ) { + m_totalSize += generator->size(); + m_composed.push_back( generator ); + } + + CompositeGenerator& then( CompositeGenerator& other ) { + move( other ); + return *this; + } + + CompositeGenerator& then( T value ) { + ValuesGenerator* valuesGen = new ValuesGenerator(); + valuesGen->add( value ); + add( valuesGen ); + return *this; + } + +private: + + void move( CompositeGenerator& other ) { + std::copy( other.m_composed.begin(), other.m_composed.end(), std::back_inserter( m_composed ) ); + m_totalSize += other.m_totalSize; + other.m_composed.clear(); + } + + std::vector*> m_composed; + std::string m_fileInfo; + size_t m_totalSize; +}; + +namespace Generators +{ + template + CompositeGenerator between( T from, T to ) { + CompositeGenerator generators; + generators.add( new BetweenGenerator( from, to ) ); + return generators; + } + + template + CompositeGenerator values( T val1, T val2 ) { + CompositeGenerator generators; + ValuesGenerator* valuesGen = new ValuesGenerator(); + valuesGen->add( val1 ); + valuesGen->add( val2 ); + generators.add( valuesGen ); + return generators; + } + + template + CompositeGenerator values( T val1, T val2, T val3 ){ + CompositeGenerator generators; + ValuesGenerator* valuesGen = new ValuesGenerator(); + valuesGen->add( val1 ); + valuesGen->add( val2 ); + valuesGen->add( val3 ); + generators.add( valuesGen ); + return generators; + } + + template + CompositeGenerator values( T val1, T val2, T val3, T val4 ) { + CompositeGenerator generators; + ValuesGenerator* valuesGen = new ValuesGenerator(); + valuesGen->add( val1 ); + valuesGen->add( val2 ); + valuesGen->add( val3 ); + valuesGen->add( val4 ); + generators.add( valuesGen ); + return generators; + } + +} // end namespace Generators + +using namespace Generators; + +} // end namespace Catch + +#define INTERNAL_CATCH_LINESTR2( line ) #line +#define INTERNAL_CATCH_LINESTR( line ) INTERNAL_CATCH_LINESTR2( line ) + +#define INTERNAL_CATCH_GENERATE( expr ) expr.setFileInfo( __FILE__ "(" INTERNAL_CATCH_LINESTR( __LINE__ ) ")" ) + +// #included from: internal/catch_interfaces_exception.h +#define TWOBLUECUBES_CATCH_INTERFACES_EXCEPTION_H_INCLUDED + +#include +#include + +// #included from: catch_interfaces_registry_hub.h +#define TWOBLUECUBES_CATCH_INTERFACES_REGISTRY_HUB_H_INCLUDED + +#include + +namespace Catch { + + class TestCase; + struct ITestCaseRegistry; + struct IExceptionTranslatorRegistry; + struct IExceptionTranslator; + struct IReporterRegistry; + struct IReporterFactory; + + struct IRegistryHub { + virtual ~IRegistryHub(); + + virtual IReporterRegistry const& getReporterRegistry() const = 0; + virtual ITestCaseRegistry const& getTestCaseRegistry() const = 0; + virtual IExceptionTranslatorRegistry& getExceptionTranslatorRegistry() = 0; + }; + + struct IMutableRegistryHub { + virtual ~IMutableRegistryHub(); + virtual void registerReporter( std::string const& name, Ptr const& factory ) = 0; + virtual void registerListener( Ptr const& factory ) = 0; + virtual void registerTest( TestCase const& testInfo ) = 0; + virtual void registerTranslator( const IExceptionTranslator* translator ) = 0; + }; + + IRegistryHub& getRegistryHub(); + IMutableRegistryHub& getMutableRegistryHub(); + void cleanUp(); + std::string translateActiveException(); + +} + +namespace Catch { + + typedef std::string(*exceptionTranslateFunction)(); + + struct IExceptionTranslator; + typedef std::vector ExceptionTranslators; + + struct IExceptionTranslator { + virtual ~IExceptionTranslator(); + virtual std::string translate( ExceptionTranslators::const_iterator it, ExceptionTranslators::const_iterator itEnd ) const = 0; + }; + + struct IExceptionTranslatorRegistry { + virtual ~IExceptionTranslatorRegistry(); + + virtual std::string translateActiveException() const = 0; + }; + + class ExceptionTranslatorRegistrar { + template + class ExceptionTranslator : public IExceptionTranslator { + public: + + ExceptionTranslator( std::string(*translateFunction)( T& ) ) + : m_translateFunction( translateFunction ) + {} + + virtual std::string translate( ExceptionTranslators::const_iterator it, ExceptionTranslators::const_iterator itEnd ) const CATCH_OVERRIDE { + try { + if( it == itEnd ) + throw; + else + return (*it)->translate( it+1, itEnd ); + } + catch( T& ex ) { + return m_translateFunction( ex ); + } + } + + protected: + std::string(*m_translateFunction)( T& ); + }; + + public: + template + ExceptionTranslatorRegistrar( std::string(*translateFunction)( T& ) ) { + getMutableRegistryHub().registerTranslator + ( new ExceptionTranslator( translateFunction ) ); + } + }; +} + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_TRANSLATE_EXCEPTION2( translatorName, signature ) \ + static std::string translatorName( signature ); \ + namespace{ Catch::ExceptionTranslatorRegistrar INTERNAL_CATCH_UNIQUE_NAME( catch_internal_ExceptionRegistrar )( &translatorName ); }\ + static std::string translatorName( signature ) + +#define INTERNAL_CATCH_TRANSLATE_EXCEPTION( signature ) INTERNAL_CATCH_TRANSLATE_EXCEPTION2( INTERNAL_CATCH_UNIQUE_NAME( catch_internal_ExceptionTranslator ), signature ) + +// #included from: internal/catch_approx.hpp +#define TWOBLUECUBES_CATCH_APPROX_HPP_INCLUDED + +#include +#include + +namespace Catch { +namespace Detail { + + class Approx { + public: + explicit Approx ( double value ) + : m_epsilon( std::numeric_limits::epsilon()*100 ), + m_scale( 1.0 ), + m_value( value ) + {} + + Approx( Approx const& other ) + : m_epsilon( other.m_epsilon ), + m_scale( other.m_scale ), + m_value( other.m_value ) + {} + + static Approx custom() { + return Approx( 0 ); + } + + Approx operator()( double value ) { + Approx approx( value ); + approx.epsilon( m_epsilon ); + approx.scale( m_scale ); + return approx; + } + + friend bool operator == ( double lhs, Approx const& rhs ) { + // Thanks to Richard Harris for his help refining this formula + return fabs( lhs - rhs.m_value ) < rhs.m_epsilon * (rhs.m_scale + (std::max)( fabs(lhs), fabs(rhs.m_value) ) ); + } + + friend bool operator == ( Approx const& lhs, double rhs ) { + return operator==( rhs, lhs ); + } + + friend bool operator != ( double lhs, Approx const& rhs ) { + return !operator==( lhs, rhs ); + } + + friend bool operator != ( Approx const& lhs, double rhs ) { + return !operator==( rhs, lhs ); + } + + Approx& epsilon( double newEpsilon ) { + m_epsilon = newEpsilon; + return *this; + } + + Approx& scale( double newScale ) { + m_scale = newScale; + return *this; + } + + std::string toString() const { + std::ostringstream oss; + oss << "Approx( " << Catch::toString( m_value ) << " )"; + return oss.str(); + } + + private: + double m_epsilon; + double m_scale; + double m_value; + }; +} + +template<> +inline std::string toString( Detail::Approx const& value ) { + return value.toString(); +} + +} // end namespace Catch + +// #included from: internal/catch_interfaces_tag_alias_registry.h +#define TWOBLUECUBES_CATCH_INTERFACES_TAG_ALIAS_REGISTRY_H_INCLUDED + +// #included from: catch_tag_alias.h +#define TWOBLUECUBES_CATCH_TAG_ALIAS_H_INCLUDED + +#include + +namespace Catch { + + struct TagAlias { + TagAlias( std::string _tag, SourceLineInfo _lineInfo ) : tag( _tag ), lineInfo( _lineInfo ) {} + + std::string tag; + SourceLineInfo lineInfo; + }; + + struct RegistrarForTagAliases { + RegistrarForTagAliases( char const* alias, char const* tag, SourceLineInfo const& lineInfo ); + }; + +} // end namespace Catch + +#define CATCH_REGISTER_TAG_ALIAS( alias, spec ) namespace{ Catch::RegistrarForTagAliases INTERNAL_CATCH_UNIQUE_NAME( AutoRegisterTagAlias )( alias, spec, CATCH_INTERNAL_LINEINFO ); } +// #included from: catch_option.hpp +#define TWOBLUECUBES_CATCH_OPTION_HPP_INCLUDED + +namespace Catch { + + // An optional type + template + class Option { + public: + Option() : nullableValue( CATCH_NULL ) {} + Option( T const& _value ) + : nullableValue( new( storage ) T( _value ) ) + {} + Option( Option const& _other ) + : nullableValue( _other ? new( storage ) T( *_other ) : CATCH_NULL ) + {} + + ~Option() { + reset(); + } + + Option& operator= ( Option const& _other ) { + if( &_other != this ) { + reset(); + if( _other ) + nullableValue = new( storage ) T( *_other ); + } + return *this; + } + Option& operator = ( T const& _value ) { + reset(); + nullableValue = new( storage ) T( _value ); + return *this; + } + + void reset() { + if( nullableValue ) + nullableValue->~T(); + nullableValue = CATCH_NULL; + } + + T& operator*() { return *nullableValue; } + T const& operator*() const { return *nullableValue; } + T* operator->() { return nullableValue; } + const T* operator->() const { return nullableValue; } + + T valueOr( T const& defaultValue ) const { + return nullableValue ? *nullableValue : defaultValue; + } + + bool some() const { return nullableValue != CATCH_NULL; } + bool none() const { return nullableValue == CATCH_NULL; } + + bool operator !() const { return nullableValue == CATCH_NULL; } + operator SafeBool::type() const { + return SafeBool::makeSafe( some() ); + } + + private: + T* nullableValue; + char storage[sizeof(T)]; + }; + +} // end namespace Catch + +namespace Catch { + + struct ITagAliasRegistry { + virtual ~ITagAliasRegistry(); + virtual Option find( std::string const& alias ) const = 0; + virtual std::string expandAliases( std::string const& unexpandedTestSpec ) const = 0; + + static ITagAliasRegistry const& get(); + }; + +} // end namespace Catch + +// These files are included here so the single_include script doesn't put them +// in the conditionally compiled sections +// #included from: internal/catch_test_case_info.h +#define TWOBLUECUBES_CATCH_TEST_CASE_INFO_H_INCLUDED + +#include +#include + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wpadded" +#endif + +namespace Catch { + + struct ITestCase; + + struct TestCaseInfo { + enum SpecialProperties{ + None = 0, + IsHidden = 1 << 1, + ShouldFail = 1 << 2, + MayFail = 1 << 3, + Throws = 1 << 4 + }; + + TestCaseInfo( std::string const& _name, + std::string const& _className, + std::string const& _description, + std::set const& _tags, + SourceLineInfo const& _lineInfo ); + + TestCaseInfo( TestCaseInfo const& other ); + + friend void setTags( TestCaseInfo& testCaseInfo, std::set const& tags ); + + bool isHidden() const; + bool throws() const; + bool okToFail() const; + bool expectedToFail() const; + + std::string name; + std::string className; + std::string description; + std::set tags; + std::set lcaseTags; + std::string tagsAsString; + SourceLineInfo lineInfo; + SpecialProperties properties; + }; + + class TestCase : public TestCaseInfo { + public: + + TestCase( ITestCase* testCase, TestCaseInfo const& info ); + TestCase( TestCase const& other ); + + TestCase withName( std::string const& _newName ) const; + + void invoke() const; + + TestCaseInfo const& getTestCaseInfo() const; + + void swap( TestCase& other ); + bool operator == ( TestCase const& other ) const; + bool operator < ( TestCase const& other ) const; + TestCase& operator = ( TestCase const& other ); + + private: + Ptr test; + }; + + TestCase makeTestCase( ITestCase* testCase, + std::string const& className, + std::string const& name, + std::string const& description, + SourceLineInfo const& lineInfo ); +} + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + + +#ifdef __OBJC__ +// #included from: internal/catch_objc.hpp +#define TWOBLUECUBES_CATCH_OBJC_HPP_INCLUDED + +#import + +#include + +// NB. Any general catch headers included here must be included +// in catch.hpp first to make sure they are included by the single +// header for non obj-usage + +/////////////////////////////////////////////////////////////////////////////// +// This protocol is really only here for (self) documenting purposes, since +// all its methods are optional. +@protocol OcFixture + +@optional + +-(void) setUp; +-(void) tearDown; + +@end + +namespace Catch { + + class OcMethod : public SharedImpl { + + public: + OcMethod( Class cls, SEL sel ) : m_cls( cls ), m_sel( sel ) {} + + virtual void invoke() const { + id obj = [[m_cls alloc] init]; + + performOptionalSelector( obj, @selector(setUp) ); + performOptionalSelector( obj, m_sel ); + performOptionalSelector( obj, @selector(tearDown) ); + + arcSafeRelease( obj ); + } + private: + virtual ~OcMethod() {} + + Class m_cls; + SEL m_sel; + }; + + namespace Detail{ + + inline std::string getAnnotation( Class cls, + std::string const& annotationName, + std::string const& testCaseName ) { + NSString* selStr = [[NSString alloc] initWithFormat:@"Catch_%s_%s", annotationName.c_str(), testCaseName.c_str()]; + SEL sel = NSSelectorFromString( selStr ); + arcSafeRelease( selStr ); + id value = performOptionalSelector( cls, sel ); + if( value ) + return [(NSString*)value UTF8String]; + return ""; + } + } + + inline size_t registerTestMethods() { + size_t noTestMethods = 0; + int noClasses = objc_getClassList( CATCH_NULL, 0 ); + + Class* classes = (CATCH_UNSAFE_UNRETAINED Class *)malloc( sizeof(Class) * noClasses); + objc_getClassList( classes, noClasses ); + + for( int c = 0; c < noClasses; c++ ) { + Class cls = classes[c]; + { + u_int count; + Method* methods = class_copyMethodList( cls, &count ); + for( u_int m = 0; m < count ; m++ ) { + SEL selector = method_getName(methods[m]); + std::string methodName = sel_getName(selector); + if( startsWith( methodName, "Catch_TestCase_" ) ) { + std::string testCaseName = methodName.substr( 15 ); + std::string name = Detail::getAnnotation( cls, "Name", testCaseName ); + std::string desc = Detail::getAnnotation( cls, "Description", testCaseName ); + const char* className = class_getName( cls ); + + getMutableRegistryHub().registerTest( makeTestCase( new OcMethod( cls, selector ), className, name.c_str(), desc.c_str(), SourceLineInfo() ) ); + noTestMethods++; + } + } + free(methods); + } + } + return noTestMethods; + } + + namespace Matchers { + namespace Impl { + namespace NSStringMatchers { + + template + struct StringHolder : MatcherImpl{ + StringHolder( NSString* substr ) : m_substr( [substr copy] ){} + StringHolder( StringHolder const& other ) : m_substr( [other.m_substr copy] ){} + StringHolder() { + arcSafeRelease( m_substr ); + } + + NSString* m_substr; + }; + + struct Equals : StringHolder { + Equals( NSString* substr ) : StringHolder( substr ){} + + virtual bool match( ExpressionType const& str ) const { + return (str != nil || m_substr == nil ) && + [str isEqualToString:m_substr]; + } + + virtual std::string toString() const { + return "equals string: " + Catch::toString( m_substr ); + } + }; + + struct Contains : StringHolder { + Contains( NSString* substr ) : StringHolder( substr ){} + + virtual bool match( ExpressionType const& str ) const { + return (str != nil || m_substr == nil ) && + [str rangeOfString:m_substr].location != NSNotFound; + } + + virtual std::string toString() const { + return "contains string: " + Catch::toString( m_substr ); + } + }; + + struct StartsWith : StringHolder { + StartsWith( NSString* substr ) : StringHolder( substr ){} + + virtual bool match( ExpressionType const& str ) const { + return (str != nil || m_substr == nil ) && + [str rangeOfString:m_substr].location == 0; + } + + virtual std::string toString() const { + return "starts with: " + Catch::toString( m_substr ); + } + }; + struct EndsWith : StringHolder { + EndsWith( NSString* substr ) : StringHolder( substr ){} + + virtual bool match( ExpressionType const& str ) const { + return (str != nil || m_substr == nil ) && + [str rangeOfString:m_substr].location == [str length] - [m_substr length]; + } + + virtual std::string toString() const { + return "ends with: " + Catch::toString( m_substr ); + } + }; + + } // namespace NSStringMatchers + } // namespace Impl + + inline Impl::NSStringMatchers::Equals + Equals( NSString* substr ){ return Impl::NSStringMatchers::Equals( substr ); } + + inline Impl::NSStringMatchers::Contains + Contains( NSString* substr ){ return Impl::NSStringMatchers::Contains( substr ); } + + inline Impl::NSStringMatchers::StartsWith + StartsWith( NSString* substr ){ return Impl::NSStringMatchers::StartsWith( substr ); } + + inline Impl::NSStringMatchers::EndsWith + EndsWith( NSString* substr ){ return Impl::NSStringMatchers::EndsWith( substr ); } + + } // namespace Matchers + + using namespace Matchers; + +} // namespace Catch + +/////////////////////////////////////////////////////////////////////////////// +#define OC_TEST_CASE( name, desc )\ ++(NSString*) INTERNAL_CATCH_UNIQUE_NAME( Catch_Name_test ) \ +{\ +return @ name; \ +}\ ++(NSString*) INTERNAL_CATCH_UNIQUE_NAME( Catch_Description_test ) \ +{ \ +return @ desc; \ +} \ +-(void) INTERNAL_CATCH_UNIQUE_NAME( Catch_TestCase_test ) + +#endif + +#ifdef CATCH_IMPL +// #included from: internal/catch_impl.hpp +#define TWOBLUECUBES_CATCH_IMPL_HPP_INCLUDED + +// Collect all the implementation files together here +// These are the equivalent of what would usually be cpp files + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wweak-vtables" +#endif + +// #included from: ../catch_session.hpp +#define TWOBLUECUBES_CATCH_RUNNER_HPP_INCLUDED + +// #included from: internal/catch_commandline.hpp +#define TWOBLUECUBES_CATCH_COMMANDLINE_HPP_INCLUDED + +// #included from: catch_config.hpp +#define TWOBLUECUBES_CATCH_CONFIG_HPP_INCLUDED + +// #included from: catch_test_spec_parser.hpp +#define TWOBLUECUBES_CATCH_TEST_SPEC_PARSER_HPP_INCLUDED + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wpadded" +#endif + +// #included from: catch_test_spec.hpp +#define TWOBLUECUBES_CATCH_TEST_SPEC_HPP_INCLUDED + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wpadded" +#endif + +// #included from: catch_wildcard_pattern.hpp +#define TWOBLUECUBES_CATCH_WILDCARD_PATTERN_HPP_INCLUDED + +namespace Catch +{ + class WildcardPattern { + enum WildcardPosition { + NoWildcard = 0, + WildcardAtStart = 1, + WildcardAtEnd = 2, + WildcardAtBothEnds = WildcardAtStart | WildcardAtEnd + }; + + public: + + WildcardPattern( std::string const& pattern, CaseSensitive::Choice caseSensitivity ) + : m_caseSensitivity( caseSensitivity ), + m_wildcard( NoWildcard ), + m_pattern( adjustCase( pattern ) ) + { + if( startsWith( m_pattern, "*" ) ) { + m_pattern = m_pattern.substr( 1 ); + m_wildcard = WildcardAtStart; + } + if( endsWith( m_pattern, "*" ) ) { + m_pattern = m_pattern.substr( 0, m_pattern.size()-1 ); + m_wildcard = static_cast( m_wildcard | WildcardAtEnd ); + } + } + virtual ~WildcardPattern(); + virtual bool matches( std::string const& str ) const { + switch( m_wildcard ) { + case NoWildcard: + return m_pattern == adjustCase( str ); + case WildcardAtStart: + return endsWith( adjustCase( str ), m_pattern ); + case WildcardAtEnd: + return startsWith( adjustCase( str ), m_pattern ); + case WildcardAtBothEnds: + return contains( adjustCase( str ), m_pattern ); + } + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunreachable-code" +#endif + throw std::logic_error( "Unknown enum" ); +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + } + private: + std::string adjustCase( std::string const& str ) const { + return m_caseSensitivity == CaseSensitive::No ? toLower( str ) : str; + } + CaseSensitive::Choice m_caseSensitivity; + WildcardPosition m_wildcard; + std::string m_pattern; + }; +} + +#include +#include + +namespace Catch { + + class TestSpec { + struct Pattern : SharedImpl<> { + virtual ~Pattern(); + virtual bool matches( TestCaseInfo const& testCase ) const = 0; + }; + class NamePattern : public Pattern { + public: + NamePattern( std::string const& name ) + : m_wildcardPattern( toLower( name ), CaseSensitive::No ) + {} + virtual ~NamePattern(); + virtual bool matches( TestCaseInfo const& testCase ) const { + return m_wildcardPattern.matches( toLower( testCase.name ) ); + } + private: + WildcardPattern m_wildcardPattern; + }; + + class TagPattern : public Pattern { + public: + TagPattern( std::string const& tag ) : m_tag( toLower( tag ) ) {} + virtual ~TagPattern(); + virtual bool matches( TestCaseInfo const& testCase ) const { + return testCase.lcaseTags.find( m_tag ) != testCase.lcaseTags.end(); + } + private: + std::string m_tag; + }; + + class ExcludedPattern : public Pattern { + public: + ExcludedPattern( Ptr const& underlyingPattern ) : m_underlyingPattern( underlyingPattern ) {} + virtual ~ExcludedPattern(); + virtual bool matches( TestCaseInfo const& testCase ) const { return !m_underlyingPattern->matches( testCase ); } + private: + Ptr m_underlyingPattern; + }; + + struct Filter { + std::vector > m_patterns; + + bool matches( TestCaseInfo const& testCase ) const { + // All patterns in a filter must match for the filter to be a match + for( std::vector >::const_iterator it = m_patterns.begin(), itEnd = m_patterns.end(); it != itEnd; ++it ) + if( !(*it)->matches( testCase ) ) + return false; + return true; + } + }; + + public: + bool hasFilters() const { + return !m_filters.empty(); + } + bool matches( TestCaseInfo const& testCase ) const { + // A TestSpec matches if any filter matches + for( std::vector::const_iterator it = m_filters.begin(), itEnd = m_filters.end(); it != itEnd; ++it ) + if( it->matches( testCase ) ) + return true; + return false; + } + + private: + std::vector m_filters; + + friend class TestSpecParser; + }; +} + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +namespace Catch { + + class TestSpecParser { + enum Mode{ None, Name, QuotedName, Tag }; + Mode m_mode; + bool m_exclusion; + std::size_t m_start, m_pos; + std::string m_arg; + TestSpec::Filter m_currentFilter; + TestSpec m_testSpec; + ITagAliasRegistry const* m_tagAliases; + + public: + TestSpecParser( ITagAliasRegistry const& tagAliases ) : m_tagAliases( &tagAliases ) {} + + TestSpecParser& parse( std::string const& arg ) { + m_mode = None; + m_exclusion = false; + m_start = std::string::npos; + m_arg = m_tagAliases->expandAliases( arg ); + for( m_pos = 0; m_pos < m_arg.size(); ++m_pos ) + visitChar( m_arg[m_pos] ); + if( m_mode == Name ) + addPattern(); + return *this; + } + TestSpec testSpec() { + addFilter(); + return m_testSpec; + } + private: + void visitChar( char c ) { + if( m_mode == None ) { + switch( c ) { + case ' ': return; + case '~': m_exclusion = true; return; + case '[': return startNewMode( Tag, ++m_pos ); + case '"': return startNewMode( QuotedName, ++m_pos ); + default: startNewMode( Name, m_pos ); break; + } + } + if( m_mode == Name ) { + if( c == ',' ) { + addPattern(); + addFilter(); + } + else if( c == '[' ) { + if( subString() == "exclude:" ) + m_exclusion = true; + else + addPattern(); + startNewMode( Tag, ++m_pos ); + } + } + else if( m_mode == QuotedName && c == '"' ) + addPattern(); + else if( m_mode == Tag && c == ']' ) + addPattern(); + } + void startNewMode( Mode mode, std::size_t start ) { + m_mode = mode; + m_start = start; + } + std::string subString() const { return m_arg.substr( m_start, m_pos - m_start ); } + template + void addPattern() { + std::string token = subString(); + if( startsWith( token, "exclude:" ) ) { + m_exclusion = true; + token = token.substr( 8 ); + } + if( !token.empty() ) { + Ptr pattern = new T( token ); + if( m_exclusion ) + pattern = new TestSpec::ExcludedPattern( pattern ); + m_currentFilter.m_patterns.push_back( pattern ); + } + m_exclusion = false; + m_mode = None; + } + void addFilter() { + if( !m_currentFilter.m_patterns.empty() ) { + m_testSpec.m_filters.push_back( m_currentFilter ); + m_currentFilter = TestSpec::Filter(); + } + } + }; + inline TestSpec parseTestSpec( std::string const& arg ) { + return TestSpecParser( ITagAliasRegistry::get() ).parse( arg ).testSpec(); + } + +} // namespace Catch + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +// #included from: catch_interfaces_config.h +#define TWOBLUECUBES_CATCH_INTERFACES_CONFIG_H_INCLUDED + +#include +#include +#include + +namespace Catch { + + struct Verbosity { enum Level { + NoOutput = 0, + Quiet, + Normal + }; }; + + struct WarnAbout { enum What { + Nothing = 0x00, + NoAssertions = 0x01 + }; }; + + struct ShowDurations { enum OrNot { + DefaultForReporter, + Always, + Never + }; }; + struct RunTests { enum InWhatOrder { + InDeclarationOrder, + InLexicographicalOrder, + InRandomOrder + }; }; + struct UseColour { enum YesOrNo { + Auto, + Yes, + No + }; }; + + class TestSpec; + + struct IConfig : IShared { + + virtual ~IConfig(); + + virtual bool allowThrows() const = 0; + virtual std::ostream& stream() const = 0; + virtual std::string name() const = 0; + virtual bool includeSuccessfulResults() const = 0; + virtual bool shouldDebugBreak() const = 0; + virtual bool warnAboutMissingAssertions() const = 0; + virtual int abortAfter() const = 0; + virtual bool showInvisibles() const = 0; + virtual ShowDurations::OrNot showDurations() const = 0; + virtual TestSpec const& testSpec() const = 0; + virtual RunTests::InWhatOrder runOrder() const = 0; + virtual unsigned int rngSeed() const = 0; + virtual UseColour::YesOrNo useColour() const = 0; + }; +} + +// #included from: catch_stream.h +#define TWOBLUECUBES_CATCH_STREAM_H_INCLUDED + +// #included from: catch_streambuf.h +#define TWOBLUECUBES_CATCH_STREAMBUF_H_INCLUDED + +#include + +namespace Catch { + + class StreamBufBase : public std::streambuf { + public: + virtual ~StreamBufBase() CATCH_NOEXCEPT; + }; +} + +#include +#include +#include + +namespace Catch { + + std::ostream& cout(); + std::ostream& cerr(); + + struct IStream { + virtual ~IStream() CATCH_NOEXCEPT; + virtual std::ostream& stream() const = 0; + }; + + class FileStream : public IStream { + mutable std::ofstream m_ofs; + public: + FileStream( std::string const& filename ); + virtual ~FileStream() CATCH_NOEXCEPT; + public: // IStream + virtual std::ostream& stream() const CATCH_OVERRIDE; + }; + + class CoutStream : public IStream { + mutable std::ostream m_os; + public: + CoutStream(); + virtual ~CoutStream() CATCH_NOEXCEPT; + + public: // IStream + virtual std::ostream& stream() const CATCH_OVERRIDE; + }; + + class DebugOutStream : public IStream { + std::auto_ptr m_streamBuf; + mutable std::ostream m_os; + public: + DebugOutStream(); + virtual ~DebugOutStream() CATCH_NOEXCEPT; + + public: // IStream + virtual std::ostream& stream() const CATCH_OVERRIDE; + }; +} + +#include +#include +#include +#include +#include + +#ifndef CATCH_CONFIG_CONSOLE_WIDTH +#define CATCH_CONFIG_CONSOLE_WIDTH 80 +#endif + +namespace Catch { + + struct ConfigData { + + ConfigData() + : listTests( false ), + listTags( false ), + listReporters( false ), + listTestNamesOnly( false ), + showSuccessfulTests( false ), + shouldDebugBreak( false ), + noThrow( false ), + showHelp( false ), + showInvisibles( false ), + filenamesAsTags( false ), + abortAfter( -1 ), + rngSeed( 0 ), + verbosity( Verbosity::Normal ), + warnings( WarnAbout::Nothing ), + showDurations( ShowDurations::DefaultForReporter ), + runOrder( RunTests::InDeclarationOrder ), + useColour( UseColour::Auto ) + {} + + bool listTests; + bool listTags; + bool listReporters; + bool listTestNamesOnly; + + bool showSuccessfulTests; + bool shouldDebugBreak; + bool noThrow; + bool showHelp; + bool showInvisibles; + bool filenamesAsTags; + + int abortAfter; + unsigned int rngSeed; + + Verbosity::Level verbosity; + WarnAbout::What warnings; + ShowDurations::OrNot showDurations; + RunTests::InWhatOrder runOrder; + UseColour::YesOrNo useColour; + + std::string outputFilename; + std::string name; + std::string processName; + + std::vector reporterNames; + std::vector testsOrTags; + }; + + class Config : public SharedImpl { + private: + Config( Config const& other ); + Config& operator = ( Config const& other ); + virtual void dummy(); + public: + + Config() + {} + + Config( ConfigData const& data ) + : m_data( data ), + m_stream( openStream() ) + { + if( !data.testsOrTags.empty() ) { + TestSpecParser parser( ITagAliasRegistry::get() ); + for( std::size_t i = 0; i < data.testsOrTags.size(); ++i ) + parser.parse( data.testsOrTags[i] ); + m_testSpec = parser.testSpec(); + } + } + + virtual ~Config() { + } + + std::string const& getFilename() const { + return m_data.outputFilename ; + } + + bool listTests() const { return m_data.listTests; } + bool listTestNamesOnly() const { return m_data.listTestNamesOnly; } + bool listTags() const { return m_data.listTags; } + bool listReporters() const { return m_data.listReporters; } + + std::string getProcessName() const { return m_data.processName; } + + bool shouldDebugBreak() const { return m_data.shouldDebugBreak; } + + std::vector getReporterNames() const { return m_data.reporterNames; } + + int abortAfter() const { return m_data.abortAfter; } + + TestSpec const& testSpec() const { return m_testSpec; } + + bool showHelp() const { return m_data.showHelp; } + bool showInvisibles() const { return m_data.showInvisibles; } + + // IConfig interface + virtual bool allowThrows() const { return !m_data.noThrow; } + virtual std::ostream& stream() const { return m_stream->stream(); } + virtual std::string name() const { return m_data.name.empty() ? m_data.processName : m_data.name; } + virtual bool includeSuccessfulResults() const { return m_data.showSuccessfulTests; } + virtual bool warnAboutMissingAssertions() const { return m_data.warnings & WarnAbout::NoAssertions; } + virtual ShowDurations::OrNot showDurations() const { return m_data.showDurations; } + virtual RunTests::InWhatOrder runOrder() const { return m_data.runOrder; } + virtual unsigned int rngSeed() const { return m_data.rngSeed; } + virtual UseColour::YesOrNo useColour() const { return m_data.useColour; } + + private: + + IStream const* openStream() { + if( m_data.outputFilename.empty() ) + return new CoutStream(); + else if( m_data.outputFilename[0] == '%' ) { + if( m_data.outputFilename == "%debug" ) + return new DebugOutStream(); + else + throw std::domain_error( "Unrecognised stream: " + m_data.outputFilename ); + } + else + return new FileStream( m_data.outputFilename ); + } + ConfigData m_data; + + std::auto_ptr m_stream; + TestSpec m_testSpec; + }; + +} // end namespace Catch + +// #included from: catch_clara.h +#define TWOBLUECUBES_CATCH_CLARA_H_INCLUDED + +// Use Catch's value for console width (store Clara's off to the side, if present) +#ifdef CLARA_CONFIG_CONSOLE_WIDTH +#define CATCH_TEMP_CLARA_CONFIG_CONSOLE_WIDTH CLARA_CONFIG_CONSOLE_WIDTH +#undef CLARA_CONFIG_CONSOLE_WIDTH +#endif +#define CLARA_CONFIG_CONSOLE_WIDTH CATCH_CONFIG_CONSOLE_WIDTH + +// Declare Clara inside the Catch namespace +#define STITCH_CLARA_OPEN_NAMESPACE namespace Catch { +// #included from: ../external/clara.h + +// Version 0.0.1.1 + +// Only use header guard if we are not using an outer namespace +#if !defined(TWOBLUECUBES_CLARA_H_INCLUDED) || defined(STITCH_CLARA_OPEN_NAMESPACE) + +#ifndef STITCH_CLARA_OPEN_NAMESPACE +#define TWOBLUECUBES_CLARA_H_INCLUDED +#define STITCH_CLARA_OPEN_NAMESPACE +#define STITCH_CLARA_CLOSE_NAMESPACE +#else +#define STITCH_CLARA_CLOSE_NAMESPACE } +#endif + +#define STITCH_TBC_TEXT_FORMAT_OPEN_NAMESPACE STITCH_CLARA_OPEN_NAMESPACE + +// ----------- #included from tbc_text_format.h ----------- + +// Only use header guard if we are not using an outer namespace +#if !defined(TBC_TEXT_FORMAT_H_INCLUDED) || defined(STITCH_TBC_TEXT_FORMAT_OUTER_NAMESPACE) +#ifndef STITCH_TBC_TEXT_FORMAT_OUTER_NAMESPACE +#define TBC_TEXT_FORMAT_H_INCLUDED +#endif + +#include +#include +#include +#include + +// Use optional outer namespace +#ifdef STITCH_TBC_TEXT_FORMAT_OUTER_NAMESPACE +namespace STITCH_TBC_TEXT_FORMAT_OUTER_NAMESPACE { +#endif + +namespace Tbc { + +#ifdef TBC_TEXT_FORMAT_CONSOLE_WIDTH + const unsigned int consoleWidth = TBC_TEXT_FORMAT_CONSOLE_WIDTH; +#else + const unsigned int consoleWidth = 80; +#endif + + struct TextAttributes { + TextAttributes() + : initialIndent( std::string::npos ), + indent( 0 ), + width( consoleWidth-1 ), + tabChar( '\t' ) + {} + + TextAttributes& setInitialIndent( std::size_t _value ) { initialIndent = _value; return *this; } + TextAttributes& setIndent( std::size_t _value ) { indent = _value; return *this; } + TextAttributes& setWidth( std::size_t _value ) { width = _value; return *this; } + TextAttributes& setTabChar( char _value ) { tabChar = _value; return *this; } + + std::size_t initialIndent; // indent of first line, or npos + std::size_t indent; // indent of subsequent lines, or all if initialIndent is npos + std::size_t width; // maximum width of text, including indent. Longer text will wrap + char tabChar; // If this char is seen the indent is changed to current pos + }; + + class Text { + public: + Text( std::string const& _str, TextAttributes const& _attr = TextAttributes() ) + : attr( _attr ) + { + std::string wrappableChars = " [({.,/|\\-"; + std::size_t indent = _attr.initialIndent != std::string::npos + ? _attr.initialIndent + : _attr.indent; + std::string remainder = _str; + + while( !remainder.empty() ) { + if( lines.size() >= 1000 ) { + lines.push_back( "... message truncated due to excessive size" ); + return; + } + std::size_t tabPos = std::string::npos; + std::size_t width = (std::min)( remainder.size(), _attr.width - indent ); + std::size_t pos = remainder.find_first_of( '\n' ); + if( pos <= width ) { + width = pos; + } + pos = remainder.find_last_of( _attr.tabChar, width ); + if( pos != std::string::npos ) { + tabPos = pos; + if( remainder[width] == '\n' ) + width--; + remainder = remainder.substr( 0, tabPos ) + remainder.substr( tabPos+1 ); + } + + if( width == remainder.size() ) { + spliceLine( indent, remainder, width ); + } + else if( remainder[width] == '\n' ) { + spliceLine( indent, remainder, width ); + if( width <= 1 || remainder.size() != 1 ) + remainder = remainder.substr( 1 ); + indent = _attr.indent; + } + else { + pos = remainder.find_last_of( wrappableChars, width ); + if( pos != std::string::npos && pos > 0 ) { + spliceLine( indent, remainder, pos ); + if( remainder[0] == ' ' ) + remainder = remainder.substr( 1 ); + } + else { + spliceLine( indent, remainder, width-1 ); + lines.back() += "-"; + } + if( lines.size() == 1 ) + indent = _attr.indent; + if( tabPos != std::string::npos ) + indent += tabPos; + } + } + } + + void spliceLine( std::size_t _indent, std::string& _remainder, std::size_t _pos ) { + lines.push_back( std::string( _indent, ' ' ) + _remainder.substr( 0, _pos ) ); + _remainder = _remainder.substr( _pos ); + } + + typedef std::vector::const_iterator const_iterator; + + const_iterator begin() const { return lines.begin(); } + const_iterator end() const { return lines.end(); } + std::string const& last() const { return lines.back(); } + std::size_t size() const { return lines.size(); } + std::string const& operator[]( std::size_t _index ) const { return lines[_index]; } + std::string toString() const { + std::ostringstream oss; + oss << *this; + return oss.str(); + } + + inline friend std::ostream& operator << ( std::ostream& _stream, Text const& _text ) { + for( Text::const_iterator it = _text.begin(), itEnd = _text.end(); + it != itEnd; ++it ) { + if( it != _text.begin() ) + _stream << "\n"; + _stream << *it; + } + return _stream; + } + + private: + std::string str; + TextAttributes attr; + std::vector lines; + }; + +} // end namespace Tbc + +#ifdef STITCH_TBC_TEXT_FORMAT_OUTER_NAMESPACE +} // end outer namespace +#endif + +#endif // TBC_TEXT_FORMAT_H_INCLUDED + +// ----------- end of #include from tbc_text_format.h ----------- +// ........... back in clara.h + +#undef STITCH_TBC_TEXT_FORMAT_OPEN_NAMESPACE + +// ----------- #included from clara_compilers.h ----------- + +#ifndef TWOBLUECUBES_CLARA_COMPILERS_H_INCLUDED +#define TWOBLUECUBES_CLARA_COMPILERS_H_INCLUDED + +// Detect a number of compiler features - mostly C++11/14 conformance - by compiler +// The following features are defined: +// +// CLARA_CONFIG_CPP11_NULLPTR : is nullptr supported? +// CLARA_CONFIG_CPP11_NOEXCEPT : is noexcept supported? +// CLARA_CONFIG_CPP11_GENERATED_METHODS : The delete and default keywords for compiler generated methods +// CLARA_CONFIG_CPP11_OVERRIDE : is override supported? +// CLARA_CONFIG_CPP11_UNIQUE_PTR : is unique_ptr supported (otherwise use auto_ptr) + +// CLARA_CONFIG_CPP11_OR_GREATER : Is C++11 supported? + +// CLARA_CONFIG_VARIADIC_MACROS : are variadic macros supported? + +// In general each macro has a _NO_ form +// (e.g. CLARA_CONFIG_CPP11_NO_NULLPTR) which disables the feature. +// Many features, at point of detection, define an _INTERNAL_ macro, so they +// can be combined, en-mass, with the _NO_ forms later. + +// All the C++11 features can be disabled with CLARA_CONFIG_NO_CPP11 + +#ifdef __clang__ + +#if __has_feature(cxx_nullptr) +#define CLARA_INTERNAL_CONFIG_CPP11_NULLPTR +#endif + +#if __has_feature(cxx_noexcept) +#define CLARA_INTERNAL_CONFIG_CPP11_NOEXCEPT +#endif + +#endif // __clang__ + +//////////////////////////////////////////////////////////////////////////////// +// GCC +#ifdef __GNUC__ + +#if __GNUC__ == 4 && __GNUC_MINOR__ >= 6 && defined(__GXX_EXPERIMENTAL_CXX0X__) +#define CLARA_INTERNAL_CONFIG_CPP11_NULLPTR +#endif + +// - otherwise more recent versions define __cplusplus >= 201103L +// and will get picked up below + +#endif // __GNUC__ + +//////////////////////////////////////////////////////////////////////////////// +// Visual C++ +#ifdef _MSC_VER + +#if (_MSC_VER >= 1600) +#define CLARA_INTERNAL_CONFIG_CPP11_NULLPTR +#define CLARA_INTERNAL_CONFIG_CPP11_UNIQUE_PTR +#endif + +#if (_MSC_VER >= 1900 ) // (VC++ 13 (VS2015)) +#define CLARA_INTERNAL_CONFIG_CPP11_NOEXCEPT +#define CLARA_INTERNAL_CONFIG_CPP11_GENERATED_METHODS +#endif + +#endif // _MSC_VER + +//////////////////////////////////////////////////////////////////////////////// +// C++ language feature support + +// catch all support for C++11 +#if defined(__cplusplus) && __cplusplus >= 201103L + +#define CLARA_CPP11_OR_GREATER + +#if !defined(CLARA_INTERNAL_CONFIG_CPP11_NULLPTR) +#define CLARA_INTERNAL_CONFIG_CPP11_NULLPTR +#endif + +#ifndef CLARA_INTERNAL_CONFIG_CPP11_NOEXCEPT +#define CLARA_INTERNAL_CONFIG_CPP11_NOEXCEPT +#endif + +#ifndef CLARA_INTERNAL_CONFIG_CPP11_GENERATED_METHODS +#define CLARA_INTERNAL_CONFIG_CPP11_GENERATED_METHODS +#endif + +#if !defined(CLARA_INTERNAL_CONFIG_CPP11_OVERRIDE) +#define CLARA_INTERNAL_CONFIG_CPP11_OVERRIDE +#endif +#if !defined(CLARA_INTERNAL_CONFIG_CPP11_UNIQUE_PTR) +#define CLARA_INTERNAL_CONFIG_CPP11_UNIQUE_PTR +#endif + +#endif // __cplusplus >= 201103L + +// Now set the actual defines based on the above + anything the user has configured +#if defined(CLARA_INTERNAL_CONFIG_CPP11_NULLPTR) && !defined(CLARA_CONFIG_CPP11_NO_NULLPTR) && !defined(CLARA_CONFIG_CPP11_NULLPTR) && !defined(CLARA_CONFIG_NO_CPP11) +#define CLARA_CONFIG_CPP11_NULLPTR +#endif +#if defined(CLARA_INTERNAL_CONFIG_CPP11_NOEXCEPT) && !defined(CLARA_CONFIG_CPP11_NO_NOEXCEPT) && !defined(CLARA_CONFIG_CPP11_NOEXCEPT) && !defined(CLARA_CONFIG_NO_CPP11) +#define CLARA_CONFIG_CPP11_NOEXCEPT +#endif +#if defined(CLARA_INTERNAL_CONFIG_CPP11_GENERATED_METHODS) && !defined(CLARA_CONFIG_CPP11_NO_GENERATED_METHODS) && !defined(CLARA_CONFIG_CPP11_GENERATED_METHODS) && !defined(CLARA_CONFIG_NO_CPP11) +#define CLARA_CONFIG_CPP11_GENERATED_METHODS +#endif +#if defined(CLARA_INTERNAL_CONFIG_CPP11_OVERRIDE) && !defined(CLARA_CONFIG_NO_OVERRIDE) && !defined(CLARA_CONFIG_CPP11_OVERRIDE) && !defined(CLARA_CONFIG_NO_CPP11) +#define CLARA_CONFIG_CPP11_OVERRIDE +#endif +#if defined(CLARA_INTERNAL_CONFIG_CPP11_UNIQUE_PTR) && !defined(CLARA_CONFIG_NO_UNIQUE_PTR) && !defined(CLARA_CONFIG_CPP11_UNIQUE_PTR) && !defined(CLARA_CONFIG_NO_CPP11) +#define CLARA_CONFIG_CPP11_UNIQUE_PTR +#endif + +// noexcept support: +#if defined(CLARA_CONFIG_CPP11_NOEXCEPT) && !defined(CLARA_NOEXCEPT) +#define CLARA_NOEXCEPT noexcept +# define CLARA_NOEXCEPT_IS(x) noexcept(x) +#else +#define CLARA_NOEXCEPT throw() +# define CLARA_NOEXCEPT_IS(x) +#endif + +// nullptr support +#ifdef CLARA_CONFIG_CPP11_NULLPTR +#define CLARA_NULL nullptr +#else +#define CLARA_NULL NULL +#endif + +// override support +#ifdef CLARA_CONFIG_CPP11_OVERRIDE +#define CLARA_OVERRIDE override +#else +#define CLARA_OVERRIDE +#endif + +// unique_ptr support +#ifdef CLARA_CONFIG_CPP11_UNIQUE_PTR +# define CLARA_AUTO_PTR( T ) std::unique_ptr +#else +# define CLARA_AUTO_PTR( T ) std::auto_ptr +#endif + +#endif // TWOBLUECUBES_CLARA_COMPILERS_H_INCLUDED + +// ----------- end of #include from clara_compilers.h ----------- +// ........... back in clara.h + +#include +#include +#include + +// Use optional outer namespace +#ifdef STITCH_CLARA_OPEN_NAMESPACE +STITCH_CLARA_OPEN_NAMESPACE +#endif + +namespace Clara { + + struct UnpositionalTag {}; + + extern UnpositionalTag _; + +#ifdef CLARA_CONFIG_MAIN + UnpositionalTag _; +#endif + + namespace Detail { + +#ifdef CLARA_CONSOLE_WIDTH + const unsigned int consoleWidth = CLARA_CONFIG_CONSOLE_WIDTH; +#else + const unsigned int consoleWidth = 80; +#endif + + // Use this to try and stop compiler from warning about unreachable code + inline bool isTrue( bool value ) { return value; } + + using namespace Tbc; + + inline bool startsWith( std::string const& str, std::string const& prefix ) { + return str.size() >= prefix.size() && str.substr( 0, prefix.size() ) == prefix; + } + + template struct RemoveConstRef{ typedef T type; }; + template struct RemoveConstRef{ typedef T type; }; + template struct RemoveConstRef{ typedef T type; }; + template struct RemoveConstRef{ typedef T type; }; + + template struct IsBool { static const bool value = false; }; + template<> struct IsBool { static const bool value = true; }; + + template + void convertInto( std::string const& _source, T& _dest ) { + std::stringstream ss; + ss << _source; + ss >> _dest; + if( ss.fail() ) + throw std::runtime_error( "Unable to convert " + _source + " to destination type" ); + } + inline void convertInto( std::string const& _source, std::string& _dest ) { + _dest = _source; + } + inline void convertInto( std::string const& _source, bool& _dest ) { + std::string sourceLC = _source; + std::transform( sourceLC.begin(), sourceLC.end(), sourceLC.begin(), ::tolower ); + if( sourceLC == "y" || sourceLC == "1" || sourceLC == "true" || sourceLC == "yes" || sourceLC == "on" ) + _dest = true; + else if( sourceLC == "n" || sourceLC == "0" || sourceLC == "false" || sourceLC == "no" || sourceLC == "off" ) + _dest = false; + else + throw std::runtime_error( "Expected a boolean value but did not recognise:\n '" + _source + "'" ); + } + inline void convertInto( bool _source, bool& _dest ) { + _dest = _source; + } + template + inline void convertInto( bool, T& ) { + if( isTrue( true ) ) + throw std::runtime_error( "Invalid conversion" ); + } + + template + struct IArgFunction { + virtual ~IArgFunction() {} +#ifdef CLARA_CONFIG_CPP11_GENERATED_METHODS + IArgFunction() = default; + IArgFunction( IArgFunction const& ) = default; +#endif + virtual void set( ConfigT& config, std::string const& value ) const = 0; + virtual void setFlag( ConfigT& config ) const = 0; + virtual bool takesArg() const = 0; + virtual IArgFunction* clone() const = 0; + }; + + template + class BoundArgFunction { + public: + BoundArgFunction() : functionObj( CLARA_NULL ) {} + BoundArgFunction( IArgFunction* _functionObj ) : functionObj( _functionObj ) {} + BoundArgFunction( BoundArgFunction const& other ) : functionObj( other.functionObj ? other.functionObj->clone() : CLARA_NULL ) {} + BoundArgFunction& operator = ( BoundArgFunction const& other ) { + IArgFunction* newFunctionObj = other.functionObj ? other.functionObj->clone() : CLARA_NULL; + delete functionObj; + functionObj = newFunctionObj; + return *this; + } + ~BoundArgFunction() { delete functionObj; } + + void set( ConfigT& config, std::string const& value ) const { + functionObj->set( config, value ); + } + void setFlag( ConfigT& config ) const { + functionObj->setFlag( config ); + } + bool takesArg() const { return functionObj->takesArg(); } + + bool isSet() const { + return functionObj != CLARA_NULL; + } + private: + IArgFunction* functionObj; + }; + + template + struct NullBinder : IArgFunction{ + virtual void set( C&, std::string const& ) const {} + virtual void setFlag( C& ) const {} + virtual bool takesArg() const { return true; } + virtual IArgFunction* clone() const { return new NullBinder( *this ); } + }; + + template + struct BoundDataMember : IArgFunction{ + BoundDataMember( M C::* _member ) : member( _member ) {} + virtual void set( C& p, std::string const& stringValue ) const { + convertInto( stringValue, p.*member ); + } + virtual void setFlag( C& p ) const { + convertInto( true, p.*member ); + } + virtual bool takesArg() const { return !IsBool::value; } + virtual IArgFunction* clone() const { return new BoundDataMember( *this ); } + M C::* member; + }; + template + struct BoundUnaryMethod : IArgFunction{ + BoundUnaryMethod( void (C::*_member)( M ) ) : member( _member ) {} + virtual void set( C& p, std::string const& stringValue ) const { + typename RemoveConstRef::type value; + convertInto( stringValue, value ); + (p.*member)( value ); + } + virtual void setFlag( C& p ) const { + typename RemoveConstRef::type value; + convertInto( true, value ); + (p.*member)( value ); + } + virtual bool takesArg() const { return !IsBool::value; } + virtual IArgFunction* clone() const { return new BoundUnaryMethod( *this ); } + void (C::*member)( M ); + }; + template + struct BoundNullaryMethod : IArgFunction{ + BoundNullaryMethod( void (C::*_member)() ) : member( _member ) {} + virtual void set( C& p, std::string const& stringValue ) const { + bool value; + convertInto( stringValue, value ); + if( value ) + (p.*member)(); + } + virtual void setFlag( C& p ) const { + (p.*member)(); + } + virtual bool takesArg() const { return false; } + virtual IArgFunction* clone() const { return new BoundNullaryMethod( *this ); } + void (C::*member)(); + }; + + template + struct BoundUnaryFunction : IArgFunction{ + BoundUnaryFunction( void (*_function)( C& ) ) : function( _function ) {} + virtual void set( C& obj, std::string const& stringValue ) const { + bool value; + convertInto( stringValue, value ); + if( value ) + function( obj ); + } + virtual void setFlag( C& p ) const { + function( p ); + } + virtual bool takesArg() const { return false; } + virtual IArgFunction* clone() const { return new BoundUnaryFunction( *this ); } + void (*function)( C& ); + }; + + template + struct BoundBinaryFunction : IArgFunction{ + BoundBinaryFunction( void (*_function)( C&, T ) ) : function( _function ) {} + virtual void set( C& obj, std::string const& stringValue ) const { + typename RemoveConstRef::type value; + convertInto( stringValue, value ); + function( obj, value ); + } + virtual void setFlag( C& obj ) const { + typename RemoveConstRef::type value; + convertInto( true, value ); + function( obj, value ); + } + virtual bool takesArg() const { return !IsBool::value; } + virtual IArgFunction* clone() const { return new BoundBinaryFunction( *this ); } + void (*function)( C&, T ); + }; + + } // namespace Detail + + struct Parser { + Parser() : separators( " \t=:" ) {} + + struct Token { + enum Type { Positional, ShortOpt, LongOpt }; + Token( Type _type, std::string const& _data ) : type( _type ), data( _data ) {} + Type type; + std::string data; + }; + + void parseIntoTokens( int argc, char const* const argv[], std::vector& tokens ) const { + const std::string doubleDash = "--"; + for( int i = 1; i < argc && argv[i] != doubleDash; ++i ) + parseIntoTokens( argv[i] , tokens); + } + void parseIntoTokens( std::string arg, std::vector& tokens ) const { + while( !arg.empty() ) { + Parser::Token token( Parser::Token::Positional, arg ); + arg = ""; + if( token.data[0] == '-' ) { + if( token.data.size() > 1 && token.data[1] == '-' ) { + token = Parser::Token( Parser::Token::LongOpt, token.data.substr( 2 ) ); + } + else { + token = Parser::Token( Parser::Token::ShortOpt, token.data.substr( 1 ) ); + if( token.data.size() > 1 && separators.find( token.data[1] ) == std::string::npos ) { + arg = "-" + token.data.substr( 1 ); + token.data = token.data.substr( 0, 1 ); + } + } + } + if( token.type != Parser::Token::Positional ) { + std::size_t pos = token.data.find_first_of( separators ); + if( pos != std::string::npos ) { + arg = token.data.substr( pos+1 ); + token.data = token.data.substr( 0, pos ); + } + } + tokens.push_back( token ); + } + } + std::string separators; + }; + + template + struct CommonArgProperties { + CommonArgProperties() {} + CommonArgProperties( Detail::BoundArgFunction const& _boundField ) : boundField( _boundField ) {} + + Detail::BoundArgFunction boundField; + std::string description; + std::string detail; + std::string placeholder; // Only value if boundField takes an arg + + bool takesArg() const { + return !placeholder.empty(); + } + void validate() const { + if( !boundField.isSet() ) + throw std::logic_error( "option not bound" ); + } + }; + struct OptionArgProperties { + std::vector shortNames; + std::string longName; + + bool hasShortName( std::string const& shortName ) const { + return std::find( shortNames.begin(), shortNames.end(), shortName ) != shortNames.end(); + } + bool hasLongName( std::string const& _longName ) const { + return _longName == longName; + } + }; + struct PositionalArgProperties { + PositionalArgProperties() : position( -1 ) {} + int position; // -1 means non-positional (floating) + + bool isFixedPositional() const { + return position != -1; + } + }; + + template + class CommandLine { + + struct Arg : CommonArgProperties, OptionArgProperties, PositionalArgProperties { + Arg() {} + Arg( Detail::BoundArgFunction const& _boundField ) : CommonArgProperties( _boundField ) {} + + using CommonArgProperties::placeholder; // !TBD + + std::string dbgName() const { + if( !longName.empty() ) + return "--" + longName; + if( !shortNames.empty() ) + return "-" + shortNames[0]; + return "positional args"; + } + std::string commands() const { + std::ostringstream oss; + bool first = true; + std::vector::const_iterator it = shortNames.begin(), itEnd = shortNames.end(); + for(; it != itEnd; ++it ) { + if( first ) + first = false; + else + oss << ", "; + oss << "-" << *it; + } + if( !longName.empty() ) { + if( !first ) + oss << ", "; + oss << "--" << longName; + } + if( !placeholder.empty() ) + oss << " <" << placeholder << ">"; + return oss.str(); + } + }; + + typedef CLARA_AUTO_PTR( Arg ) ArgAutoPtr; + + friend void addOptName( Arg& arg, std::string const& optName ) + { + if( optName.empty() ) + return; + if( Detail::startsWith( optName, "--" ) ) { + if( !arg.longName.empty() ) + throw std::logic_error( "Only one long opt may be specified. '" + + arg.longName + + "' already specified, now attempting to add '" + + optName + "'" ); + arg.longName = optName.substr( 2 ); + } + else if( Detail::startsWith( optName, "-" ) ) + arg.shortNames.push_back( optName.substr( 1 ) ); + else + throw std::logic_error( "option must begin with - or --. Option was: '" + optName + "'" ); + } + friend void setPositionalArg( Arg& arg, int position ) + { + arg.position = position; + } + + class ArgBuilder { + public: + ArgBuilder( Arg* arg ) : m_arg( arg ) {} + + // Bind a non-boolean data member (requires placeholder string) + template + void bind( M C::* field, std::string const& placeholder ) { + m_arg->boundField = new Detail::BoundDataMember( field ); + m_arg->placeholder = placeholder; + } + // Bind a boolean data member (no placeholder required) + template + void bind( bool C::* field ) { + m_arg->boundField = new Detail::BoundDataMember( field ); + } + + // Bind a method taking a single, non-boolean argument (requires a placeholder string) + template + void bind( void (C::* unaryMethod)( M ), std::string const& placeholder ) { + m_arg->boundField = new Detail::BoundUnaryMethod( unaryMethod ); + m_arg->placeholder = placeholder; + } + + // Bind a method taking a single, boolean argument (no placeholder string required) + template + void bind( void (C::* unaryMethod)( bool ) ) { + m_arg->boundField = new Detail::BoundUnaryMethod( unaryMethod ); + } + + // Bind a method that takes no arguments (will be called if opt is present) + template + void bind( void (C::* nullaryMethod)() ) { + m_arg->boundField = new Detail::BoundNullaryMethod( nullaryMethod ); + } + + // Bind a free function taking a single argument - the object to operate on (no placeholder string required) + template + void bind( void (* unaryFunction)( C& ) ) { + m_arg->boundField = new Detail::BoundUnaryFunction( unaryFunction ); + } + + // Bind a free function taking a single argument - the object to operate on (requires a placeholder string) + template + void bind( void (* binaryFunction)( C&, T ), std::string const& placeholder ) { + m_arg->boundField = new Detail::BoundBinaryFunction( binaryFunction ); + m_arg->placeholder = placeholder; + } + + ArgBuilder& describe( std::string const& description ) { + m_arg->description = description; + return *this; + } + ArgBuilder& detail( std::string const& detail ) { + m_arg->detail = detail; + return *this; + } + + protected: + Arg* m_arg; + }; + + class OptBuilder : public ArgBuilder { + public: + OptBuilder( Arg* arg ) : ArgBuilder( arg ) {} + OptBuilder( OptBuilder& other ) : ArgBuilder( other ) {} + + OptBuilder& operator[]( std::string const& optName ) { + addOptName( *ArgBuilder::m_arg, optName ); + return *this; + } + }; + + public: + + CommandLine() + : m_boundProcessName( new Detail::NullBinder() ), + m_highestSpecifiedArgPosition( 0 ), + m_throwOnUnrecognisedTokens( false ) + {} + CommandLine( CommandLine const& other ) + : m_boundProcessName( other.m_boundProcessName ), + m_options ( other.m_options ), + m_positionalArgs( other.m_positionalArgs ), + m_highestSpecifiedArgPosition( other.m_highestSpecifiedArgPosition ), + m_throwOnUnrecognisedTokens( other.m_throwOnUnrecognisedTokens ) + { + if( other.m_floatingArg.get() ) + m_floatingArg.reset( new Arg( *other.m_floatingArg ) ); + } + + CommandLine& setThrowOnUnrecognisedTokens( bool shouldThrow = true ) { + m_throwOnUnrecognisedTokens = shouldThrow; + return *this; + } + + OptBuilder operator[]( std::string const& optName ) { + m_options.push_back( Arg() ); + addOptName( m_options.back(), optName ); + OptBuilder builder( &m_options.back() ); + return builder; + } + + ArgBuilder operator[]( int position ) { + m_positionalArgs.insert( std::make_pair( position, Arg() ) ); + if( position > m_highestSpecifiedArgPosition ) + m_highestSpecifiedArgPosition = position; + setPositionalArg( m_positionalArgs[position], position ); + ArgBuilder builder( &m_positionalArgs[position] ); + return builder; + } + + // Invoke this with the _ instance + ArgBuilder operator[]( UnpositionalTag ) { + if( m_floatingArg.get() ) + throw std::logic_error( "Only one unpositional argument can be added" ); + m_floatingArg.reset( new Arg() ); + ArgBuilder builder( m_floatingArg.get() ); + return builder; + } + + template + void bindProcessName( M C::* field ) { + m_boundProcessName = new Detail::BoundDataMember( field ); + } + template + void bindProcessName( void (C::*_unaryMethod)( M ) ) { + m_boundProcessName = new Detail::BoundUnaryMethod( _unaryMethod ); + } + + void optUsage( std::ostream& os, std::size_t indent = 0, std::size_t width = Detail::consoleWidth ) const { + typename std::vector::const_iterator itBegin = m_options.begin(), itEnd = m_options.end(), it; + std::size_t maxWidth = 0; + for( it = itBegin; it != itEnd; ++it ) + maxWidth = (std::max)( maxWidth, it->commands().size() ); + + for( it = itBegin; it != itEnd; ++it ) { + Detail::Text usage( it->commands(), Detail::TextAttributes() + .setWidth( maxWidth+indent ) + .setIndent( indent ) ); + Detail::Text desc( it->description, Detail::TextAttributes() + .setWidth( width - maxWidth - 3 ) ); + + for( std::size_t i = 0; i < (std::max)( usage.size(), desc.size() ); ++i ) { + std::string usageCol = i < usage.size() ? usage[i] : ""; + os << usageCol; + + if( i < desc.size() && !desc[i].empty() ) + os << std::string( indent + 2 + maxWidth - usageCol.size(), ' ' ) + << desc[i]; + os << "\n"; + } + } + } + std::string optUsage() const { + std::ostringstream oss; + optUsage( oss ); + return oss.str(); + } + + void argSynopsis( std::ostream& os ) const { + for( int i = 1; i <= m_highestSpecifiedArgPosition; ++i ) { + if( i > 1 ) + os << " "; + typename std::map::const_iterator it = m_positionalArgs.find( i ); + if( it != m_positionalArgs.end() ) + os << "<" << it->second.placeholder << ">"; + else if( m_floatingArg.get() ) + os << "<" << m_floatingArg->placeholder << ">"; + else + throw std::logic_error( "non consecutive positional arguments with no floating args" ); + } + // !TBD No indication of mandatory args + if( m_floatingArg.get() ) { + if( m_highestSpecifiedArgPosition > 1 ) + os << " "; + os << "[<" << m_floatingArg->placeholder << "> ...]"; + } + } + std::string argSynopsis() const { + std::ostringstream oss; + argSynopsis( oss ); + return oss.str(); + } + + void usage( std::ostream& os, std::string const& procName ) const { + validate(); + os << "usage:\n " << procName << " "; + argSynopsis( os ); + if( !m_options.empty() ) { + os << " [options]\n\nwhere options are: \n"; + optUsage( os, 2 ); + } + os << "\n"; + } + std::string usage( std::string const& procName ) const { + std::ostringstream oss; + usage( oss, procName ); + return oss.str(); + } + + ConfigT parse( int argc, char const* const argv[] ) const { + ConfigT config; + parseInto( argc, argv, config ); + return config; + } + + std::vector parseInto( int argc, char const* argv[], ConfigT& config ) const { + std::string processName = argv[0]; + std::size_t lastSlash = processName.find_last_of( "/\\" ); + if( lastSlash != std::string::npos ) + processName = processName.substr( lastSlash+1 ); + m_boundProcessName.set( config, processName ); + std::vector tokens; + Parser parser; + parser.parseIntoTokens( argc, argv, tokens ); + return populate( tokens, config ); + } + + std::vector populate( std::vector const& tokens, ConfigT& config ) const { + validate(); + std::vector unusedTokens = populateOptions( tokens, config ); + unusedTokens = populateFixedArgs( unusedTokens, config ); + unusedTokens = populateFloatingArgs( unusedTokens, config ); + return unusedTokens; + } + + std::vector populateOptions( std::vector const& tokens, ConfigT& config ) const { + std::vector unusedTokens; + std::vector errors; + for( std::size_t i = 0; i < tokens.size(); ++i ) { + Parser::Token const& token = tokens[i]; + typename std::vector::const_iterator it = m_options.begin(), itEnd = m_options.end(); + for(; it != itEnd; ++it ) { + Arg const& arg = *it; + + try { + if( ( token.type == Parser::Token::ShortOpt && arg.hasShortName( token.data ) ) || + ( token.type == Parser::Token::LongOpt && arg.hasLongName( token.data ) ) ) { + if( arg.takesArg() ) { + if( i == tokens.size()-1 || tokens[i+1].type != Parser::Token::Positional ) + errors.push_back( "Expected argument to option: " + token.data ); + else + arg.boundField.set( config, tokens[++i].data ); + } + else { + arg.boundField.setFlag( config ); + } + break; + } + } + catch( std::exception& ex ) { + errors.push_back( std::string( ex.what() ) + "\n- while parsing: (" + arg.commands() + ")" ); + } + } + if( it == itEnd ) { + if( token.type == Parser::Token::Positional || !m_throwOnUnrecognisedTokens ) + unusedTokens.push_back( token ); + else if( errors.empty() && m_throwOnUnrecognisedTokens ) + errors.push_back( "unrecognised option: " + token.data ); + } + } + if( !errors.empty() ) { + std::ostringstream oss; + for( std::vector::const_iterator it = errors.begin(), itEnd = errors.end(); + it != itEnd; + ++it ) { + if( it != errors.begin() ) + oss << "\n"; + oss << *it; + } + throw std::runtime_error( oss.str() ); + } + return unusedTokens; + } + std::vector populateFixedArgs( std::vector const& tokens, ConfigT& config ) const { + std::vector unusedTokens; + int position = 1; + for( std::size_t i = 0; i < tokens.size(); ++i ) { + Parser::Token const& token = tokens[i]; + typename std::map::const_iterator it = m_positionalArgs.find( position ); + if( it != m_positionalArgs.end() ) + it->second.boundField.set( config, token.data ); + else + unusedTokens.push_back( token ); + if( token.type == Parser::Token::Positional ) + position++; + } + return unusedTokens; + } + std::vector populateFloatingArgs( std::vector const& tokens, ConfigT& config ) const { + if( !m_floatingArg.get() ) + return tokens; + std::vector unusedTokens; + for( std::size_t i = 0; i < tokens.size(); ++i ) { + Parser::Token const& token = tokens[i]; + if( token.type == Parser::Token::Positional ) + m_floatingArg->boundField.set( config, token.data ); + else + unusedTokens.push_back( token ); + } + return unusedTokens; + } + + void validate() const + { + if( m_options.empty() && m_positionalArgs.empty() && !m_floatingArg.get() ) + throw std::logic_error( "No options or arguments specified" ); + + for( typename std::vector::const_iterator it = m_options.begin(), + itEnd = m_options.end(); + it != itEnd; ++it ) + it->validate(); + } + + private: + Detail::BoundArgFunction m_boundProcessName; + std::vector m_options; + std::map m_positionalArgs; + ArgAutoPtr m_floatingArg; + int m_highestSpecifiedArgPosition; + bool m_throwOnUnrecognisedTokens; + }; + +} // end namespace Clara + +STITCH_CLARA_CLOSE_NAMESPACE +#undef STITCH_CLARA_OPEN_NAMESPACE +#undef STITCH_CLARA_CLOSE_NAMESPACE + +#endif // TWOBLUECUBES_CLARA_H_INCLUDED +#undef STITCH_CLARA_OPEN_NAMESPACE + +// Restore Clara's value for console width, if present +#ifdef CATCH_TEMP_CLARA_CONFIG_CONSOLE_WIDTH +#define CLARA_CONFIG_CONSOLE_WIDTH CATCH_TEMP_CLARA_CONFIG_CONSOLE_WIDTH +#undef CATCH_TEMP_CLARA_CONFIG_CONSOLE_WIDTH +#endif + +#include + +namespace Catch { + + inline void abortAfterFirst( ConfigData& config ) { config.abortAfter = 1; } + inline void abortAfterX( ConfigData& config, int x ) { + if( x < 1 ) + throw std::runtime_error( "Value after -x or --abortAfter must be greater than zero" ); + config.abortAfter = x; + } + inline void addTestOrTags( ConfigData& config, std::string const& _testSpec ) { config.testsOrTags.push_back( _testSpec ); } + inline void addReporterName( ConfigData& config, std::string const& _reporterName ) { config.reporterNames.push_back( _reporterName ); } + + inline void addWarning( ConfigData& config, std::string const& _warning ) { + if( _warning == "NoAssertions" ) + config.warnings = static_cast( config.warnings | WarnAbout::NoAssertions ); + else + throw std::runtime_error( "Unrecognised warning: '" + _warning + "'" ); + } + inline void setOrder( ConfigData& config, std::string const& order ) { + if( startsWith( "declared", order ) ) + config.runOrder = RunTests::InDeclarationOrder; + else if( startsWith( "lexical", order ) ) + config.runOrder = RunTests::InLexicographicalOrder; + else if( startsWith( "random", order ) ) + config.runOrder = RunTests::InRandomOrder; + else + throw std::runtime_error( "Unrecognised ordering: '" + order + "'" ); + } + inline void setRngSeed( ConfigData& config, std::string const& seed ) { + if( seed == "time" ) { + config.rngSeed = static_cast( std::time(0) ); + } + else { + std::stringstream ss; + ss << seed; + ss >> config.rngSeed; + if( ss.fail() ) + throw std::runtime_error( "Argment to --rng-seed should be the word 'time' or a number" ); + } + } + inline void setVerbosity( ConfigData& config, int level ) { + // !TBD: accept strings? + config.verbosity = static_cast( level ); + } + inline void setShowDurations( ConfigData& config, bool _showDurations ) { + config.showDurations = _showDurations + ? ShowDurations::Always + : ShowDurations::Never; + } + inline void setUseColour( ConfigData& config, std::string const& value ) { + std::string mode = toLower( value ); + + if( mode == "yes" ) + config.useColour = UseColour::Yes; + else if( mode == "no" ) + config.useColour = UseColour::No; + else if( mode == "auto" ) + config.useColour = UseColour::Auto; + else + throw std::runtime_error( "colour mode must be one of: auto, yes or no" ); + } + inline void forceColour( ConfigData& config ) { + config.useColour = UseColour::Yes; + } + inline void loadTestNamesFromFile( ConfigData& config, std::string const& _filename ) { + std::ifstream f( _filename.c_str() ); + if( !f.is_open() ) + throw std::domain_error( "Unable to load input file: " + _filename ); + + std::string line; + while( std::getline( f, line ) ) { + line = trim(line); + if( !line.empty() && !startsWith( line, "#" ) ) + addTestOrTags( config, "\"" + line + "\"," ); + } + } + + inline Clara::CommandLine makeCommandLineParser() { + + using namespace Clara; + CommandLine cli; + + cli.bindProcessName( &ConfigData::processName ); + + cli["-?"]["-h"]["--help"] + .describe( "display usage information" ) + .bind( &ConfigData::showHelp ); + + cli["-l"]["--list-tests"] + .describe( "list all/matching test cases" ) + .bind( &ConfigData::listTests ); + + cli["-t"]["--list-tags"] + .describe( "list all/matching tags" ) + .bind( &ConfigData::listTags ); + + cli["-s"]["--success"] + .describe( "include successful tests in output" ) + .bind( &ConfigData::showSuccessfulTests ); + + cli["-b"]["--break"] + .describe( "break into debugger on failure" ) + .bind( &ConfigData::shouldDebugBreak ); + + cli["-e"]["--nothrow"] + .describe( "skip exception tests" ) + .bind( &ConfigData::noThrow ); + + cli["-i"]["--invisibles"] + .describe( "show invisibles (tabs, newlines)" ) + .bind( &ConfigData::showInvisibles ); + + cli["-o"]["--out"] + .describe( "output filename" ) + .bind( &ConfigData::outputFilename, "filename" ); + + cli["-r"]["--reporter"] +// .placeholder( "name[:filename]" ) + .describe( "reporter to use (defaults to console)" ) + .bind( &addReporterName, "name" ); + + cli["-n"]["--name"] + .describe( "suite name" ) + .bind( &ConfigData::name, "name" ); + + cli["-a"]["--abort"] + .describe( "abort at first failure" ) + .bind( &abortAfterFirst ); + + cli["-x"]["--abortx"] + .describe( "abort after x failures" ) + .bind( &abortAfterX, "no. failures" ); + + cli["-w"]["--warn"] + .describe( "enable warnings" ) + .bind( &addWarning, "warning name" ); + +// - needs updating if reinstated +// cli.into( &setVerbosity ) +// .describe( "level of verbosity (0=no output)" ) +// .shortOpt( "v") +// .longOpt( "verbosity" ) +// .placeholder( "level" ); + + cli[_] + .describe( "which test or tests to use" ) + .bind( &addTestOrTags, "test name, pattern or tags" ); + + cli["-d"]["--durations"] + .describe( "show test durations" ) + .bind( &setShowDurations, "yes|no" ); + + cli["-f"]["--input-file"] + .describe( "load test names to run from a file" ) + .bind( &loadTestNamesFromFile, "filename" ); + + cli["-#"]["--filenames-as-tags"] + .describe( "adds a tag for the filename" ) + .bind( &ConfigData::filenamesAsTags ); + + // Less common commands which don't have a short form + cli["--list-test-names-only"] + .describe( "list all/matching test cases names only" ) + .bind( &ConfigData::listTestNamesOnly ); + + cli["--list-reporters"] + .describe( "list all reporters" ) + .bind( &ConfigData::listReporters ); + + cli["--order"] + .describe( "test case order (defaults to decl)" ) + .bind( &setOrder, "decl|lex|rand" ); + + cli["--rng-seed"] + .describe( "set a specific seed for random numbers" ) + .bind( &setRngSeed, "'time'|number" ); + + cli["--force-colour"] + .describe( "force colourised output (deprecated)" ) + .bind( &forceColour ); + + cli["--use-colour"] + .describe( "should output be colourised" ) + .bind( &setUseColour, "yes|no" ); + + return cli; + } + +} // end namespace Catch + +// #included from: internal/catch_list.hpp +#define TWOBLUECUBES_CATCH_LIST_HPP_INCLUDED + +// #included from: catch_text.h +#define TWOBLUECUBES_CATCH_TEXT_H_INCLUDED + +#define TBC_TEXT_FORMAT_CONSOLE_WIDTH CATCH_CONFIG_CONSOLE_WIDTH + +#define CLICHE_TBC_TEXT_FORMAT_OUTER_NAMESPACE Catch +// #included from: ../external/tbc_text_format.h +// Only use header guard if we are not using an outer namespace +#ifndef CLICHE_TBC_TEXT_FORMAT_OUTER_NAMESPACE +# ifdef TWOBLUECUBES_TEXT_FORMAT_H_INCLUDED +# ifndef TWOBLUECUBES_TEXT_FORMAT_H_ALREADY_INCLUDED +# define TWOBLUECUBES_TEXT_FORMAT_H_ALREADY_INCLUDED +# endif +# else +# define TWOBLUECUBES_TEXT_FORMAT_H_INCLUDED +# endif +#endif +#ifndef TWOBLUECUBES_TEXT_FORMAT_H_ALREADY_INCLUDED +#include +#include +#include + +// Use optional outer namespace +#ifdef CLICHE_TBC_TEXT_FORMAT_OUTER_NAMESPACE +namespace CLICHE_TBC_TEXT_FORMAT_OUTER_NAMESPACE { +#endif + +namespace Tbc { + +#ifdef TBC_TEXT_FORMAT_CONSOLE_WIDTH + const unsigned int consoleWidth = TBC_TEXT_FORMAT_CONSOLE_WIDTH; +#else + const unsigned int consoleWidth = 80; +#endif + + struct TextAttributes { + TextAttributes() + : initialIndent( std::string::npos ), + indent( 0 ), + width( consoleWidth-1 ), + tabChar( '\t' ) + {} + + TextAttributes& setInitialIndent( std::size_t _value ) { initialIndent = _value; return *this; } + TextAttributes& setIndent( std::size_t _value ) { indent = _value; return *this; } + TextAttributes& setWidth( std::size_t _value ) { width = _value; return *this; } + TextAttributes& setTabChar( char _value ) { tabChar = _value; return *this; } + + std::size_t initialIndent; // indent of first line, or npos + std::size_t indent; // indent of subsequent lines, or all if initialIndent is npos + std::size_t width; // maximum width of text, including indent. Longer text will wrap + char tabChar; // If this char is seen the indent is changed to current pos + }; + + class Text { + public: + Text( std::string const& _str, TextAttributes const& _attr = TextAttributes() ) + : attr( _attr ) + { + std::string wrappableChars = " [({.,/|\\-"; + std::size_t indent = _attr.initialIndent != std::string::npos + ? _attr.initialIndent + : _attr.indent; + std::string remainder = _str; + + while( !remainder.empty() ) { + if( lines.size() >= 1000 ) { + lines.push_back( "... message truncated due to excessive size" ); + return; + } + std::size_t tabPos = std::string::npos; + std::size_t width = (std::min)( remainder.size(), _attr.width - indent ); + std::size_t pos = remainder.find_first_of( '\n' ); + if( pos <= width ) { + width = pos; + } + pos = remainder.find_last_of( _attr.tabChar, width ); + if( pos != std::string::npos ) { + tabPos = pos; + if( remainder[width] == '\n' ) + width--; + remainder = remainder.substr( 0, tabPos ) + remainder.substr( tabPos+1 ); + } + + if( width == remainder.size() ) { + spliceLine( indent, remainder, width ); + } + else if( remainder[width] == '\n' ) { + spliceLine( indent, remainder, width ); + if( width <= 1 || remainder.size() != 1 ) + remainder = remainder.substr( 1 ); + indent = _attr.indent; + } + else { + pos = remainder.find_last_of( wrappableChars, width ); + if( pos != std::string::npos && pos > 0 ) { + spliceLine( indent, remainder, pos ); + if( remainder[0] == ' ' ) + remainder = remainder.substr( 1 ); + } + else { + spliceLine( indent, remainder, width-1 ); + lines.back() += "-"; + } + if( lines.size() == 1 ) + indent = _attr.indent; + if( tabPos != std::string::npos ) + indent += tabPos; + } + } + } + + void spliceLine( std::size_t _indent, std::string& _remainder, std::size_t _pos ) { + lines.push_back( std::string( _indent, ' ' ) + _remainder.substr( 0, _pos ) ); + _remainder = _remainder.substr( _pos ); + } + + typedef std::vector::const_iterator const_iterator; + + const_iterator begin() const { return lines.begin(); } + const_iterator end() const { return lines.end(); } + std::string const& last() const { return lines.back(); } + std::size_t size() const { return lines.size(); } + std::string const& operator[]( std::size_t _index ) const { return lines[_index]; } + std::string toString() const { + std::ostringstream oss; + oss << *this; + return oss.str(); + } + + inline friend std::ostream& operator << ( std::ostream& _stream, Text const& _text ) { + for( Text::const_iterator it = _text.begin(), itEnd = _text.end(); + it != itEnd; ++it ) { + if( it != _text.begin() ) + _stream << "\n"; + _stream << *it; + } + return _stream; + } + + private: + std::string str; + TextAttributes attr; + std::vector lines; + }; + +} // end namespace Tbc + +#ifdef CLICHE_TBC_TEXT_FORMAT_OUTER_NAMESPACE +} // end outer namespace +#endif + +#endif // TWOBLUECUBES_TEXT_FORMAT_H_ALREADY_INCLUDED +#undef CLICHE_TBC_TEXT_FORMAT_OUTER_NAMESPACE + +namespace Catch { + using Tbc::Text; + using Tbc::TextAttributes; +} + +// #included from: catch_console_colour.hpp +#define TWOBLUECUBES_CATCH_CONSOLE_COLOUR_HPP_INCLUDED + +namespace Catch { + + struct Colour { + enum Code { + None = 0, + + White, + Red, + Green, + Blue, + Cyan, + Yellow, + Grey, + + Bright = 0x10, + + BrightRed = Bright | Red, + BrightGreen = Bright | Green, + LightGrey = Bright | Grey, + BrightWhite = Bright | White, + + // By intention + FileName = LightGrey, + Warning = Yellow, + ResultError = BrightRed, + ResultSuccess = BrightGreen, + ResultExpectedFailure = Warning, + + Error = BrightRed, + Success = Green, + + OriginalExpression = Cyan, + ReconstructedExpression = Yellow, + + SecondaryText = LightGrey, + Headers = White + }; + + // Use constructed object for RAII guard + Colour( Code _colourCode ); + Colour( Colour const& other ); + ~Colour(); + + // Use static method for one-shot changes + static void use( Code _colourCode ); + + private: + bool m_moved; + }; + + inline std::ostream& operator << ( std::ostream& os, Colour const& ) { return os; } + +} // end namespace Catch + +// #included from: catch_interfaces_reporter.h +#define TWOBLUECUBES_CATCH_INTERFACES_REPORTER_H_INCLUDED + +#include +#include +#include +#include + +namespace Catch +{ + struct ReporterConfig { + explicit ReporterConfig( Ptr const& _fullConfig ) + : m_stream( &_fullConfig->stream() ), m_fullConfig( _fullConfig ) {} + + ReporterConfig( Ptr const& _fullConfig, std::ostream& _stream ) + : m_stream( &_stream ), m_fullConfig( _fullConfig ) {} + + std::ostream& stream() const { return *m_stream; } + Ptr fullConfig() const { return m_fullConfig; } + + private: + std::ostream* m_stream; + Ptr m_fullConfig; + }; + + struct ReporterPreferences { + ReporterPreferences() + : shouldRedirectStdOut( false ) + {} + + bool shouldRedirectStdOut; + }; + + template + struct LazyStat : Option { + LazyStat() : used( false ) {} + LazyStat& operator=( T const& _value ) { + Option::operator=( _value ); + used = false; + return *this; + } + void reset() { + Option::reset(); + used = false; + } + bool used; + }; + + struct TestRunInfo { + TestRunInfo( std::string const& _name ) : name( _name ) {} + std::string name; + }; + struct GroupInfo { + GroupInfo( std::string const& _name, + std::size_t _groupIndex, + std::size_t _groupsCount ) + : name( _name ), + groupIndex( _groupIndex ), + groupsCounts( _groupsCount ) + {} + + std::string name; + std::size_t groupIndex; + std::size_t groupsCounts; + }; + + struct AssertionStats { + AssertionStats( AssertionResult const& _assertionResult, + std::vector const& _infoMessages, + Totals const& _totals ) + : assertionResult( _assertionResult ), + infoMessages( _infoMessages ), + totals( _totals ) + { + if( assertionResult.hasMessage() ) { + // Copy message into messages list. + // !TBD This should have been done earlier, somewhere + MessageBuilder builder( assertionResult.getTestMacroName(), assertionResult.getSourceInfo(), assertionResult.getResultType() ); + builder << assertionResult.getMessage(); + builder.m_info.message = builder.m_stream.str(); + + infoMessages.push_back( builder.m_info ); + } + } + virtual ~AssertionStats(); + +# ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS + AssertionStats( AssertionStats const& ) = default; + AssertionStats( AssertionStats && ) = default; + AssertionStats& operator = ( AssertionStats const& ) = default; + AssertionStats& operator = ( AssertionStats && ) = default; +# endif + + AssertionResult assertionResult; + std::vector infoMessages; + Totals totals; + }; + + struct SectionStats { + SectionStats( SectionInfo const& _sectionInfo, + Counts const& _assertions, + double _durationInSeconds, + bool _missingAssertions ) + : sectionInfo( _sectionInfo ), + assertions( _assertions ), + durationInSeconds( _durationInSeconds ), + missingAssertions( _missingAssertions ) + {} + virtual ~SectionStats(); +# ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS + SectionStats( SectionStats const& ) = default; + SectionStats( SectionStats && ) = default; + SectionStats& operator = ( SectionStats const& ) = default; + SectionStats& operator = ( SectionStats && ) = default; +# endif + + SectionInfo sectionInfo; + Counts assertions; + double durationInSeconds; + bool missingAssertions; + }; + + struct TestCaseStats { + TestCaseStats( TestCaseInfo const& _testInfo, + Totals const& _totals, + std::string const& _stdOut, + std::string const& _stdErr, + bool _aborting ) + : testInfo( _testInfo ), + totals( _totals ), + stdOut( _stdOut ), + stdErr( _stdErr ), + aborting( _aborting ) + {} + virtual ~TestCaseStats(); + +# ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS + TestCaseStats( TestCaseStats const& ) = default; + TestCaseStats( TestCaseStats && ) = default; + TestCaseStats& operator = ( TestCaseStats const& ) = default; + TestCaseStats& operator = ( TestCaseStats && ) = default; +# endif + + TestCaseInfo testInfo; + Totals totals; + std::string stdOut; + std::string stdErr; + bool aborting; + }; + + struct TestGroupStats { + TestGroupStats( GroupInfo const& _groupInfo, + Totals const& _totals, + bool _aborting ) + : groupInfo( _groupInfo ), + totals( _totals ), + aborting( _aborting ) + {} + TestGroupStats( GroupInfo const& _groupInfo ) + : groupInfo( _groupInfo ), + aborting( false ) + {} + virtual ~TestGroupStats(); + +# ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS + TestGroupStats( TestGroupStats const& ) = default; + TestGroupStats( TestGroupStats && ) = default; + TestGroupStats& operator = ( TestGroupStats const& ) = default; + TestGroupStats& operator = ( TestGroupStats && ) = default; +# endif + + GroupInfo groupInfo; + Totals totals; + bool aborting; + }; + + struct TestRunStats { + TestRunStats( TestRunInfo const& _runInfo, + Totals const& _totals, + bool _aborting ) + : runInfo( _runInfo ), + totals( _totals ), + aborting( _aborting ) + {} + virtual ~TestRunStats(); + +# ifndef CATCH_CONFIG_CPP11_GENERATED_METHODS + TestRunStats( TestRunStats const& _other ) + : runInfo( _other.runInfo ), + totals( _other.totals ), + aborting( _other.aborting ) + {} +# else + TestRunStats( TestRunStats const& ) = default; + TestRunStats( TestRunStats && ) = default; + TestRunStats& operator = ( TestRunStats const& ) = default; + TestRunStats& operator = ( TestRunStats && ) = default; +# endif + + TestRunInfo runInfo; + Totals totals; + bool aborting; + }; + + struct IStreamingReporter : IShared { + virtual ~IStreamingReporter(); + + // Implementing class must also provide the following static method: + // static std::string getDescription(); + + virtual ReporterPreferences getPreferences() const = 0; + + virtual void noMatchingTestCases( std::string const& spec ) = 0; + + virtual void testRunStarting( TestRunInfo const& testRunInfo ) = 0; + virtual void testGroupStarting( GroupInfo const& groupInfo ) = 0; + + virtual void testCaseStarting( TestCaseInfo const& testInfo ) = 0; + virtual void sectionStarting( SectionInfo const& sectionInfo ) = 0; + + virtual void assertionStarting( AssertionInfo const& assertionInfo ) = 0; + + // The return value indicates if the messages buffer should be cleared: + virtual bool assertionEnded( AssertionStats const& assertionStats ) = 0; + + virtual void sectionEnded( SectionStats const& sectionStats ) = 0; + virtual void testCaseEnded( TestCaseStats const& testCaseStats ) = 0; + virtual void testGroupEnded( TestGroupStats const& testGroupStats ) = 0; + virtual void testRunEnded( TestRunStats const& testRunStats ) = 0; + + virtual void skipTest( TestCaseInfo const& testInfo ) = 0; + }; + + struct IReporterFactory : IShared { + virtual ~IReporterFactory(); + virtual IStreamingReporter* create( ReporterConfig const& config ) const = 0; + virtual std::string getDescription() const = 0; + }; + + struct IReporterRegistry { + typedef std::map > FactoryMap; + typedef std::vector > Listeners; + + virtual ~IReporterRegistry(); + virtual IStreamingReporter* create( std::string const& name, Ptr const& config ) const = 0; + virtual FactoryMap const& getFactories() const = 0; + virtual Listeners const& getListeners() const = 0; + }; + + Ptr addReporter( Ptr const& existingReporter, Ptr const& additionalReporter ); + +} + +#include +#include + +namespace Catch { + + inline std::size_t listTests( Config const& config ) { + + TestSpec testSpec = config.testSpec(); + if( config.testSpec().hasFilters() ) + Catch::cout() << "Matching test cases:\n"; + else { + Catch::cout() << "All available test cases:\n"; + testSpec = TestSpecParser( ITagAliasRegistry::get() ).parse( "*" ).testSpec(); + } + + std::size_t matchedTests = 0; + TextAttributes nameAttr, tagsAttr; + nameAttr.setInitialIndent( 2 ).setIndent( 4 ); + tagsAttr.setIndent( 6 ); + + std::vector matchedTestCases = filterTests( getAllTestCasesSorted( config ), testSpec, config ); + for( std::vector::const_iterator it = matchedTestCases.begin(), itEnd = matchedTestCases.end(); + it != itEnd; + ++it ) { + matchedTests++; + TestCaseInfo const& testCaseInfo = it->getTestCaseInfo(); + Colour::Code colour = testCaseInfo.isHidden() + ? Colour::SecondaryText + : Colour::None; + Colour colourGuard( colour ); + + Catch::cout() << Text( testCaseInfo.name, nameAttr ) << std::endl; + if( !testCaseInfo.tags.empty() ) + Catch::cout() << Text( testCaseInfo.tagsAsString, tagsAttr ) << std::endl; + } + + if( !config.testSpec().hasFilters() ) + Catch::cout() << pluralise( matchedTests, "test case" ) << "\n" << std::endl; + else + Catch::cout() << pluralise( matchedTests, "matching test case" ) << "\n" << std::endl; + return matchedTests; + } + + inline std::size_t listTestsNamesOnly( Config const& config ) { + TestSpec testSpec = config.testSpec(); + if( !config.testSpec().hasFilters() ) + testSpec = TestSpecParser( ITagAliasRegistry::get() ).parse( "*" ).testSpec(); + std::size_t matchedTests = 0; + std::vector matchedTestCases = filterTests( getAllTestCasesSorted( config ), testSpec, config ); + for( std::vector::const_iterator it = matchedTestCases.begin(), itEnd = matchedTestCases.end(); + it != itEnd; + ++it ) { + matchedTests++; + TestCaseInfo const& testCaseInfo = it->getTestCaseInfo(); + Catch::cout() << testCaseInfo.name << std::endl; + } + return matchedTests; + } + + struct TagInfo { + TagInfo() : count ( 0 ) {} + void add( std::string const& spelling ) { + ++count; + spellings.insert( spelling ); + } + std::string all() const { + std::string out; + for( std::set::const_iterator it = spellings.begin(), itEnd = spellings.end(); + it != itEnd; + ++it ) + out += "[" + *it + "]"; + return out; + } + std::set spellings; + std::size_t count; + }; + + inline std::size_t listTags( Config const& config ) { + TestSpec testSpec = config.testSpec(); + if( config.testSpec().hasFilters() ) + Catch::cout() << "Tags for matching test cases:\n"; + else { + Catch::cout() << "All available tags:\n"; + testSpec = TestSpecParser( ITagAliasRegistry::get() ).parse( "*" ).testSpec(); + } + + std::map tagCounts; + + std::vector matchedTestCases = filterTests( getAllTestCasesSorted( config ), testSpec, config ); + for( std::vector::const_iterator it = matchedTestCases.begin(), itEnd = matchedTestCases.end(); + it != itEnd; + ++it ) { + for( std::set::const_iterator tagIt = it->getTestCaseInfo().tags.begin(), + tagItEnd = it->getTestCaseInfo().tags.end(); + tagIt != tagItEnd; + ++tagIt ) { + std::string tagName = *tagIt; + std::string lcaseTagName = toLower( tagName ); + std::map::iterator countIt = tagCounts.find( lcaseTagName ); + if( countIt == tagCounts.end() ) + countIt = tagCounts.insert( std::make_pair( lcaseTagName, TagInfo() ) ).first; + countIt->second.add( tagName ); + } + } + + for( std::map::const_iterator countIt = tagCounts.begin(), + countItEnd = tagCounts.end(); + countIt != countItEnd; + ++countIt ) { + std::ostringstream oss; + oss << " " << std::setw(2) << countIt->second.count << " "; + Text wrapper( countIt->second.all(), TextAttributes() + .setInitialIndent( 0 ) + .setIndent( oss.str().size() ) + .setWidth( CATCH_CONFIG_CONSOLE_WIDTH-10 ) ); + Catch::cout() << oss.str() << wrapper << "\n"; + } + Catch::cout() << pluralise( tagCounts.size(), "tag" ) << "\n" << std::endl; + return tagCounts.size(); + } + + inline std::size_t listReporters( Config const& /*config*/ ) { + Catch::cout() << "Available reporters:\n"; + IReporterRegistry::FactoryMap const& factories = getRegistryHub().getReporterRegistry().getFactories(); + IReporterRegistry::FactoryMap::const_iterator itBegin = factories.begin(), itEnd = factories.end(), it; + std::size_t maxNameLen = 0; + for(it = itBegin; it != itEnd; ++it ) + maxNameLen = (std::max)( maxNameLen, it->first.size() ); + + for(it = itBegin; it != itEnd; ++it ) { + Text wrapper( it->second->getDescription(), TextAttributes() + .setInitialIndent( 0 ) + .setIndent( 7+maxNameLen ) + .setWidth( CATCH_CONFIG_CONSOLE_WIDTH - maxNameLen-8 ) ); + Catch::cout() << " " + << it->first + << ":" + << std::string( maxNameLen - it->first.size() + 2, ' ' ) + << wrapper << "\n"; + } + Catch::cout() << std::endl; + return factories.size(); + } + + inline Option list( Config const& config ) { + Option listedCount; + if( config.listTests() ) + listedCount = listedCount.valueOr(0) + listTests( config ); + if( config.listTestNamesOnly() ) + listedCount = listedCount.valueOr(0) + listTestsNamesOnly( config ); + if( config.listTags() ) + listedCount = listedCount.valueOr(0) + listTags( config ); + if( config.listReporters() ) + listedCount = listedCount.valueOr(0) + listReporters( config ); + return listedCount; + } + +} // end namespace Catch + +// #included from: internal/catch_run_context.hpp +#define TWOBLUECUBES_CATCH_RUNNER_IMPL_HPP_INCLUDED + +// #included from: catch_test_case_tracker.hpp +#define TWOBLUECUBES_CATCH_TEST_CASE_TRACKER_HPP_INCLUDED + +#include +#include +#include +#include + +namespace Catch { +namespace TestCaseTracking { + + struct ITracker : SharedImpl<> { + virtual ~ITracker(); + + // static queries + virtual std::string name() const = 0; + + // dynamic queries + virtual bool isComplete() const = 0; // Successfully completed or failed + virtual bool isSuccessfullyCompleted() const = 0; + virtual bool isOpen() const = 0; // Started but not complete + virtual bool hasChildren() const = 0; + + virtual ITracker& parent() = 0; + + // actions + virtual void close() = 0; // Successfully complete + virtual void fail() = 0; + virtual void markAsNeedingAnotherRun() = 0; + + virtual void addChild( Ptr const& child ) = 0; + virtual ITracker* findChild( std::string const& name ) = 0; + virtual void openChild() = 0; + }; + + class TrackerContext { + + enum RunState { + NotStarted, + Executing, + CompletedCycle + }; + + Ptr m_rootTracker; + ITracker* m_currentTracker; + RunState m_runState; + + public: + + static TrackerContext& instance() { + static TrackerContext s_instance; + return s_instance; + } + + TrackerContext() + : m_currentTracker( CATCH_NULL ), + m_runState( NotStarted ) + {} + + ITracker& startRun(); + + void endRun() { + m_rootTracker.reset(); + m_currentTracker = CATCH_NULL; + m_runState = NotStarted; + } + + void startCycle() { + m_currentTracker = m_rootTracker.get(); + m_runState = Executing; + } + void completeCycle() { + m_runState = CompletedCycle; + } + + bool completedCycle() const { + return m_runState == CompletedCycle; + } + ITracker& currentTracker() { + return *m_currentTracker; + } + void setCurrentTracker( ITracker* tracker ) { + m_currentTracker = tracker; + } + }; + + class TrackerBase : public ITracker { + protected: + enum CycleState { + NotStarted, + Executing, + ExecutingChildren, + NeedsAnotherRun, + CompletedSuccessfully, + Failed + }; + class TrackerHasName { + std::string m_name; + public: + TrackerHasName( std::string const& name ) : m_name( name ) {} + bool operator ()( Ptr const& tracker ) { + return tracker->name() == m_name; + } + }; + typedef std::vector > Children; + std::string m_name; + TrackerContext& m_ctx; + ITracker* m_parent; + Children m_children; + CycleState m_runState; + public: + TrackerBase( std::string const& name, TrackerContext& ctx, ITracker* parent ) + : m_name( name ), + m_ctx( ctx ), + m_parent( parent ), + m_runState( NotStarted ) + {} + virtual ~TrackerBase(); + + virtual std::string name() const CATCH_OVERRIDE { + return m_name; + } + virtual bool isComplete() const CATCH_OVERRIDE { + return m_runState == CompletedSuccessfully || m_runState == Failed; + } + virtual bool isSuccessfullyCompleted() const CATCH_OVERRIDE { + return m_runState == CompletedSuccessfully; + } + virtual bool isOpen() const CATCH_OVERRIDE { + return m_runState != NotStarted && !isComplete(); + } + virtual bool hasChildren() const CATCH_OVERRIDE { + return !m_children.empty(); + } + + virtual void addChild( Ptr const& child ) CATCH_OVERRIDE { + m_children.push_back( child ); + } + + virtual ITracker* findChild( std::string const& name ) CATCH_OVERRIDE { + Children::const_iterator it = std::find_if( m_children.begin(), m_children.end(), TrackerHasName( name ) ); + return( it != m_children.end() ) + ? it->get() + : CATCH_NULL; + } + virtual ITracker& parent() CATCH_OVERRIDE { + assert( m_parent ); // Should always be non-null except for root + return *m_parent; + } + + virtual void openChild() CATCH_OVERRIDE { + if( m_runState != ExecutingChildren ) { + m_runState = ExecutingChildren; + if( m_parent ) + m_parent->openChild(); + } + } + void open() { + m_runState = Executing; + moveToThis(); + if( m_parent ) + m_parent->openChild(); + } + + virtual void close() CATCH_OVERRIDE { + + // Close any still open children (e.g. generators) + while( &m_ctx.currentTracker() != this ) + m_ctx.currentTracker().close(); + + switch( m_runState ) { + case NotStarted: + case CompletedSuccessfully: + case Failed: + throw std::logic_error( "Illogical state" ); + + case NeedsAnotherRun: + break;; + + case Executing: + m_runState = CompletedSuccessfully; + break; + case ExecutingChildren: + if( m_children.empty() || m_children.back()->isComplete() ) + m_runState = CompletedSuccessfully; + break; + + default: + throw std::logic_error( "Unexpected state" ); + } + moveToParent(); + m_ctx.completeCycle(); + } + virtual void fail() CATCH_OVERRIDE { + m_runState = Failed; + if( m_parent ) + m_parent->markAsNeedingAnotherRun(); + moveToParent(); + m_ctx.completeCycle(); + } + virtual void markAsNeedingAnotherRun() CATCH_OVERRIDE { + m_runState = NeedsAnotherRun; + } + private: + void moveToParent() { + assert( m_parent ); + m_ctx.setCurrentTracker( m_parent ); + } + void moveToThis() { + m_ctx.setCurrentTracker( this ); + } + }; + + class SectionTracker : public TrackerBase { + public: + SectionTracker( std::string const& name, TrackerContext& ctx, ITracker* parent ) + : TrackerBase( name, ctx, parent ) + {} + virtual ~SectionTracker(); + + static SectionTracker& acquire( TrackerContext& ctx, std::string const& name ) { + SectionTracker* section = CATCH_NULL; + + ITracker& currentTracker = ctx.currentTracker(); + if( ITracker* childTracker = currentTracker.findChild( name ) ) { + section = dynamic_cast( childTracker ); + assert( section ); + } + else { + section = new SectionTracker( name, ctx, ¤tTracker ); + currentTracker.addChild( section ); + } + if( !ctx.completedCycle() && !section->isComplete() ) { + + section->open(); + } + return *section; + } + }; + + class IndexTracker : public TrackerBase { + int m_size; + int m_index; + public: + IndexTracker( std::string const& name, TrackerContext& ctx, ITracker* parent, int size ) + : TrackerBase( name, ctx, parent ), + m_size( size ), + m_index( -1 ) + {} + virtual ~IndexTracker(); + + static IndexTracker& acquire( TrackerContext& ctx, std::string const& name, int size ) { + IndexTracker* tracker = CATCH_NULL; + + ITracker& currentTracker = ctx.currentTracker(); + if( ITracker* childTracker = currentTracker.findChild( name ) ) { + tracker = dynamic_cast( childTracker ); + assert( tracker ); + } + else { + tracker = new IndexTracker( name, ctx, ¤tTracker, size ); + currentTracker.addChild( tracker ); + } + + if( !ctx.completedCycle() && !tracker->isComplete() ) { + if( tracker->m_runState != ExecutingChildren && tracker->m_runState != NeedsAnotherRun ) + tracker->moveNext(); + tracker->open(); + } + + return *tracker; + } + + int index() const { return m_index; } + + void moveNext() { + m_index++; + m_children.clear(); + } + + virtual void close() CATCH_OVERRIDE { + TrackerBase::close(); + if( m_runState == CompletedSuccessfully && m_index < m_size-1 ) + m_runState = Executing; + } + }; + + inline ITracker& TrackerContext::startRun() { + m_rootTracker = new SectionTracker( "{root}", *this, CATCH_NULL ); + m_currentTracker = CATCH_NULL; + m_runState = Executing; + return *m_rootTracker; + } + +} // namespace TestCaseTracking + +using TestCaseTracking::ITracker; +using TestCaseTracking::TrackerContext; +using TestCaseTracking::SectionTracker; +using TestCaseTracking::IndexTracker; + +} // namespace Catch + +// #included from: catch_fatal_condition.hpp +#define TWOBLUECUBES_CATCH_FATAL_CONDITION_H_INCLUDED + +namespace Catch { + + // Report the error condition then exit the process + inline void fatal( std::string const& message, int exitCode ) { + IContext& context = Catch::getCurrentContext(); + IResultCapture* resultCapture = context.getResultCapture(); + resultCapture->handleFatalErrorCondition( message ); + + if( Catch::alwaysTrue() ) // avoids "no return" warnings + exit( exitCode ); + } + +} // namespace Catch + +#if defined ( CATCH_PLATFORM_WINDOWS ) ///////////////////////////////////////// + +namespace Catch { + + struct FatalConditionHandler { + void reset() {} + }; + +} // namespace Catch + +#else // Not Windows - assumed to be POSIX compatible ////////////////////////// + +#include + +namespace Catch { + + struct SignalDefs { int id; const char* name; }; + extern SignalDefs signalDefs[]; + SignalDefs signalDefs[] = { + { SIGINT, "SIGINT - Terminal interrupt signal" }, + { SIGILL, "SIGILL - Illegal instruction signal" }, + { SIGFPE, "SIGFPE - Floating point error signal" }, + { SIGSEGV, "SIGSEGV - Segmentation violation signal" }, + { SIGTERM, "SIGTERM - Termination request signal" }, + { SIGABRT, "SIGABRT - Abort (abnormal termination) signal" } + }; + + struct FatalConditionHandler { + + static void handleSignal( int sig ) { + for( std::size_t i = 0; i < sizeof(signalDefs)/sizeof(SignalDefs); ++i ) + if( sig == signalDefs[i].id ) + fatal( signalDefs[i].name, -sig ); + fatal( "", -sig ); + } + + FatalConditionHandler() : m_isSet( true ) { + for( std::size_t i = 0; i < sizeof(signalDefs)/sizeof(SignalDefs); ++i ) + signal( signalDefs[i].id, handleSignal ); + } + ~FatalConditionHandler() { + reset(); + } + void reset() { + if( m_isSet ) { + for( std::size_t i = 0; i < sizeof(signalDefs)/sizeof(SignalDefs); ++i ) + signal( signalDefs[i].id, SIG_DFL ); + m_isSet = false; + } + } + + bool m_isSet; + }; + +} // namespace Catch + +#endif // not Windows + +#include +#include + +namespace Catch { + + class StreamRedirect { + + public: + StreamRedirect( std::ostream& stream, std::string& targetString ) + : m_stream( stream ), + m_prevBuf( stream.rdbuf() ), + m_targetString( targetString ) + { + stream.rdbuf( m_oss.rdbuf() ); + } + + ~StreamRedirect() { + m_targetString += m_oss.str(); + m_stream.rdbuf( m_prevBuf ); + } + + private: + std::ostream& m_stream; + std::streambuf* m_prevBuf; + std::ostringstream m_oss; + std::string& m_targetString; + }; + + /////////////////////////////////////////////////////////////////////////// + + class RunContext : public IResultCapture, public IRunner { + + RunContext( RunContext const& ); + void operator =( RunContext const& ); + + public: + + explicit RunContext( Ptr const& _config, Ptr const& reporter ) + : m_runInfo( _config->name() ), + m_context( getCurrentMutableContext() ), + m_activeTestCase( CATCH_NULL ), + m_config( _config ), + m_reporter( reporter ) + { + m_context.setRunner( this ); + m_context.setConfig( m_config ); + m_context.setResultCapture( this ); + m_reporter->testRunStarting( m_runInfo ); + } + + virtual ~RunContext() { + m_reporter->testRunEnded( TestRunStats( m_runInfo, m_totals, aborting() ) ); + } + + void testGroupStarting( std::string const& testSpec, std::size_t groupIndex, std::size_t groupsCount ) { + m_reporter->testGroupStarting( GroupInfo( testSpec, groupIndex, groupsCount ) ); + } + void testGroupEnded( std::string const& testSpec, Totals const& totals, std::size_t groupIndex, std::size_t groupsCount ) { + m_reporter->testGroupEnded( TestGroupStats( GroupInfo( testSpec, groupIndex, groupsCount ), totals, aborting() ) ); + } + + Totals runTest( TestCase const& testCase ) { + Totals prevTotals = m_totals; + + std::string redirectedCout; + std::string redirectedCerr; + + TestCaseInfo testInfo = testCase.getTestCaseInfo(); + + m_reporter->testCaseStarting( testInfo ); + + m_activeTestCase = &testCase; + + do { + m_trackerContext.startRun(); + do { + m_trackerContext.startCycle(); + m_testCaseTracker = &SectionTracker::acquire( m_trackerContext, testInfo.name ); + runCurrentTest( redirectedCout, redirectedCerr ); + } + while( !m_testCaseTracker->isSuccessfullyCompleted() && !aborting() ); + } + // !TBD: deprecated - this will be replaced by indexed trackers + while( getCurrentContext().advanceGeneratorsForCurrentTest() && !aborting() ); + + Totals deltaTotals = m_totals.delta( prevTotals ); + if( testInfo.expectedToFail() && deltaTotals.testCases.passed > 0 ) { + deltaTotals.assertions.failed++; + deltaTotals.testCases.passed--; + deltaTotals.testCases.failed++; + } + m_totals.testCases += deltaTotals.testCases; + m_reporter->testCaseEnded( TestCaseStats( testInfo, + deltaTotals, + redirectedCout, + redirectedCerr, + aborting() ) ); + + m_activeTestCase = CATCH_NULL; + m_testCaseTracker = CATCH_NULL; + + return deltaTotals; + } + + Ptr config() const { + return m_config; + } + + private: // IResultCapture + + virtual void assertionEnded( AssertionResult const& result ) { + if( result.getResultType() == ResultWas::Ok ) { + m_totals.assertions.passed++; + } + else if( !result.isOk() ) { + m_totals.assertions.failed++; + } + + if( m_reporter->assertionEnded( AssertionStats( result, m_messages, m_totals ) ) ) + m_messages.clear(); + + // Reset working state + m_lastAssertionInfo = AssertionInfo( "", m_lastAssertionInfo.lineInfo, "{Unknown expression after the reported line}" , m_lastAssertionInfo.resultDisposition ); + m_lastResult = result; + } + + virtual bool sectionStarted ( + SectionInfo const& sectionInfo, + Counts& assertions + ) + { + std::ostringstream oss; + oss << sectionInfo.name << "@" << sectionInfo.lineInfo; + + ITracker& sectionTracker = SectionTracker::acquire( m_trackerContext, oss.str() ); + if( !sectionTracker.isOpen() ) + return false; + m_activeSections.push_back( §ionTracker ); + + m_lastAssertionInfo.lineInfo = sectionInfo.lineInfo; + + m_reporter->sectionStarting( sectionInfo ); + + assertions = m_totals.assertions; + + return true; + } + bool testForMissingAssertions( Counts& assertions ) { + if( assertions.total() != 0 ) + return false; + if( !m_config->warnAboutMissingAssertions() ) + return false; + if( m_trackerContext.currentTracker().hasChildren() ) + return false; + m_totals.assertions.failed++; + assertions.failed++; + return true; + } + + virtual void sectionEnded( SectionEndInfo const& endInfo ) { + Counts assertions = m_totals.assertions - endInfo.prevAssertions; + bool missingAssertions = testForMissingAssertions( assertions ); + + if( !m_activeSections.empty() ) { + m_activeSections.back()->close(); + m_activeSections.pop_back(); + } + + m_reporter->sectionEnded( SectionStats( endInfo.sectionInfo, assertions, endInfo.durationInSeconds, missingAssertions ) ); + m_messages.clear(); + } + + virtual void sectionEndedEarly( SectionEndInfo const& endInfo ) { + if( m_unfinishedSections.empty() ) + m_activeSections.back()->fail(); + else + m_activeSections.back()->close(); + m_activeSections.pop_back(); + + m_unfinishedSections.push_back( endInfo ); + } + + virtual void pushScopedMessage( MessageInfo const& message ) { + m_messages.push_back( message ); + } + + virtual void popScopedMessage( MessageInfo const& message ) { + m_messages.erase( std::remove( m_messages.begin(), m_messages.end(), message ), m_messages.end() ); + } + + virtual std::string getCurrentTestName() const { + return m_activeTestCase + ? m_activeTestCase->getTestCaseInfo().name + : ""; + } + + virtual const AssertionResult* getLastResult() const { + return &m_lastResult; + } + + virtual void handleFatalErrorCondition( std::string const& message ) { + ResultBuilder resultBuilder = makeUnexpectedResultBuilder(); + resultBuilder.setResultType( ResultWas::FatalErrorCondition ); + resultBuilder << message; + resultBuilder.captureExpression(); + + handleUnfinishedSections(); + + // Recreate section for test case (as we will lose the one that was in scope) + TestCaseInfo const& testCaseInfo = m_activeTestCase->getTestCaseInfo(); + SectionInfo testCaseSection( testCaseInfo.lineInfo, testCaseInfo.name, testCaseInfo.description ); + + Counts assertions; + assertions.failed = 1; + SectionStats testCaseSectionStats( testCaseSection, assertions, 0, false ); + m_reporter->sectionEnded( testCaseSectionStats ); + + TestCaseInfo testInfo = m_activeTestCase->getTestCaseInfo(); + + Totals deltaTotals; + deltaTotals.testCases.failed = 1; + m_reporter->testCaseEnded( TestCaseStats( testInfo, + deltaTotals, + "", + "", + false ) ); + m_totals.testCases.failed++; + testGroupEnded( "", m_totals, 1, 1 ); + m_reporter->testRunEnded( TestRunStats( m_runInfo, m_totals, false ) ); + } + + public: + // !TBD We need to do this another way! + bool aborting() const { + return m_totals.assertions.failed == static_cast( m_config->abortAfter() ); + } + + private: + + void runCurrentTest( std::string& redirectedCout, std::string& redirectedCerr ) { + TestCaseInfo const& testCaseInfo = m_activeTestCase->getTestCaseInfo(); + SectionInfo testCaseSection( testCaseInfo.lineInfo, testCaseInfo.name, testCaseInfo.description ); + m_reporter->sectionStarting( testCaseSection ); + Counts prevAssertions = m_totals.assertions; + double duration = 0; + try { + m_lastAssertionInfo = AssertionInfo( "TEST_CASE", testCaseInfo.lineInfo, "", ResultDisposition::Normal ); + + seedRng( *m_config ); + + Timer timer; + timer.start(); + if( m_reporter->getPreferences().shouldRedirectStdOut ) { + StreamRedirect coutRedir( Catch::cout(), redirectedCout ); + StreamRedirect cerrRedir( Catch::cerr(), redirectedCerr ); + invokeActiveTestCase(); + } + else { + invokeActiveTestCase(); + } + duration = timer.getElapsedSeconds(); + } + catch( TestFailureException& ) { + // This just means the test was aborted due to failure + } + catch(...) { + makeUnexpectedResultBuilder().useActiveException(); + } + m_testCaseTracker->close(); + handleUnfinishedSections(); + m_messages.clear(); + + Counts assertions = m_totals.assertions - prevAssertions; + bool missingAssertions = testForMissingAssertions( assertions ); + + if( testCaseInfo.okToFail() ) { + std::swap( assertions.failedButOk, assertions.failed ); + m_totals.assertions.failed -= assertions.failedButOk; + m_totals.assertions.failedButOk += assertions.failedButOk; + } + + SectionStats testCaseSectionStats( testCaseSection, assertions, duration, missingAssertions ); + m_reporter->sectionEnded( testCaseSectionStats ); + } + + void invokeActiveTestCase() { + FatalConditionHandler fatalConditionHandler; // Handle signals + m_activeTestCase->invoke(); + fatalConditionHandler.reset(); + } + + private: + + ResultBuilder makeUnexpectedResultBuilder() const { + return ResultBuilder( m_lastAssertionInfo.macroName.c_str(), + m_lastAssertionInfo.lineInfo, + m_lastAssertionInfo.capturedExpression.c_str(), + m_lastAssertionInfo.resultDisposition ); + } + + void handleUnfinishedSections() { + // If sections ended prematurely due to an exception we stored their + // infos here so we can tear them down outside the unwind process. + for( std::vector::const_reverse_iterator it = m_unfinishedSections.rbegin(), + itEnd = m_unfinishedSections.rend(); + it != itEnd; + ++it ) + sectionEnded( *it ); + m_unfinishedSections.clear(); + } + + TestRunInfo m_runInfo; + IMutableContext& m_context; + TestCase const* m_activeTestCase; + ITracker* m_testCaseTracker; + ITracker* m_currentSectionTracker; + AssertionResult m_lastResult; + + Ptr m_config; + Totals m_totals; + Ptr m_reporter; + std::vector m_messages; + AssertionInfo m_lastAssertionInfo; + std::vector m_unfinishedSections; + std::vector m_activeSections; + TrackerContext m_trackerContext; + }; + + IResultCapture& getResultCapture() { + if( IResultCapture* capture = getCurrentContext().getResultCapture() ) + return *capture; + else + throw std::logic_error( "No result capture instance" ); + } + +} // end namespace Catch + +// #included from: internal/catch_version.h +#define TWOBLUECUBES_CATCH_VERSION_H_INCLUDED + +namespace Catch { + + // Versioning information + struct Version { + Version( unsigned int _majorVersion, + unsigned int _minorVersion, + unsigned int _patchNumber, + std::string const& _branchName, + unsigned int _buildNumber ); + + unsigned int const majorVersion; + unsigned int const minorVersion; + unsigned int const patchNumber; + + // buildNumber is only used if branchName is not null + std::string const branchName; + unsigned int const buildNumber; + + friend std::ostream& operator << ( std::ostream& os, Version const& version ); + + private: + void operator=( Version const& ); + }; + + extern Version libraryVersion; +} + +#include +#include +#include + +namespace Catch { + + Ptr createReporter( std::string const& reporterName, Ptr const& config ) { + Ptr reporter = getRegistryHub().getReporterRegistry().create( reporterName, config.get() ); + if( !reporter ) { + std::ostringstream oss; + oss << "No reporter registered with name: '" << reporterName << "'"; + throw std::domain_error( oss.str() ); + } + return reporter; + } + + Ptr makeReporter( Ptr const& config ) { + std::vector reporters = config->getReporterNames(); + if( reporters.empty() ) + reporters.push_back( "console" ); + + Ptr reporter; + for( std::vector::const_iterator it = reporters.begin(), itEnd = reporters.end(); + it != itEnd; + ++it ) + reporter = addReporter( reporter, createReporter( *it, config ) ); + return reporter; + } + Ptr addListeners( Ptr const& config, Ptr reporters ) { + IReporterRegistry::Listeners listeners = getRegistryHub().getReporterRegistry().getListeners(); + for( IReporterRegistry::Listeners::const_iterator it = listeners.begin(), itEnd = listeners.end(); + it != itEnd; + ++it ) + reporters = addReporter(reporters, (*it)->create( ReporterConfig( config ) ) ); + return reporters; + } + + Totals runTests( Ptr const& config ) { + + Ptr iconfig = config.get(); + + Ptr reporter = makeReporter( config ); + reporter = addListeners( iconfig, reporter ); + + RunContext context( iconfig, reporter ); + + Totals totals; + + context.testGroupStarting( config->name(), 1, 1 ); + + TestSpec testSpec = config->testSpec(); + if( !testSpec.hasFilters() ) + testSpec = TestSpecParser( ITagAliasRegistry::get() ).parse( "~[.]" ).testSpec(); // All not hidden tests + + std::vector const& allTestCases = getAllTestCasesSorted( *iconfig ); + for( std::vector::const_iterator it = allTestCases.begin(), itEnd = allTestCases.end(); + it != itEnd; + ++it ) { + if( !context.aborting() && matchTest( *it, testSpec, *iconfig ) ) + totals += context.runTest( *it ); + else + reporter->skipTest( *it ); + } + + context.testGroupEnded( iconfig->name(), totals, 1, 1 ); + return totals; + } + + void applyFilenamesAsTags( IConfig const& config ) { + std::vector const& tests = getAllTestCasesSorted( config ); + for(std::size_t i = 0; i < tests.size(); ++i ) { + TestCase& test = const_cast( tests[i] ); + std::set tags = test.tags; + + std::string filename = test.lineInfo.file; + std::string::size_type lastSlash = filename.find_last_of( "\\/" ); + if( lastSlash != std::string::npos ) + filename = filename.substr( lastSlash+1 ); + + std::string::size_type lastDot = filename.find_last_of( "." ); + if( lastDot != std::string::npos ) + filename = filename.substr( 0, lastDot ); + + tags.insert( "#" + filename ); + setTags( test, tags ); + } + } + + class Session : NonCopyable { + static bool alreadyInstantiated; + + public: + + struct OnUnusedOptions { enum DoWhat { Ignore, Fail }; }; + + Session() + : m_cli( makeCommandLineParser() ) { + if( alreadyInstantiated ) { + std::string msg = "Only one instance of Catch::Session can ever be used"; + Catch::cerr() << msg << std::endl; + throw std::logic_error( msg ); + } + alreadyInstantiated = true; + } + ~Session() { + Catch::cleanUp(); + } + + void showHelp( std::string const& processName ) { + Catch::cout() << "\nCatch v" << libraryVersion << "\n"; + + m_cli.usage( Catch::cout(), processName ); + Catch::cout() << "For more detail usage please see the project docs\n" << std::endl; + } + + int applyCommandLine( int argc, char const* argv[], OnUnusedOptions::DoWhat unusedOptionBehaviour = OnUnusedOptions::Fail ) { + try { + m_cli.setThrowOnUnrecognisedTokens( unusedOptionBehaviour == OnUnusedOptions::Fail ); + m_unusedTokens = m_cli.parseInto( argc, argv, m_configData ); + if( m_configData.showHelp ) + showHelp( m_configData.processName ); + m_config.reset(); + } + catch( std::exception& ex ) { + { + Colour colourGuard( Colour::Red ); + Catch::cerr() + << "\nError(s) in input:\n" + << Text( ex.what(), TextAttributes().setIndent(2) ) + << "\n\n"; + } + m_cli.usage( Catch::cout(), m_configData.processName ); + return (std::numeric_limits::max)(); + } + return 0; + } + + void useConfigData( ConfigData const& _configData ) { + m_configData = _configData; + m_config.reset(); + } + + int run( int argc, char const* argv[] ) { + + int returnCode = applyCommandLine( argc, argv ); + if( returnCode == 0 ) + returnCode = run(); + return returnCode; + } + int run( int argc, char* argv[] ) { + return run( argc, const_cast( argv ) ); + } + + int run() { + if( m_configData.showHelp ) + return 0; + + try + { + config(); // Force config to be constructed + + seedRng( *m_config ); + + if( m_configData.filenamesAsTags ) + applyFilenamesAsTags( *m_config ); + + // Handle list request + if( Option listed = list( config() ) ) + return static_cast( *listed ); + + return static_cast( runTests( m_config ).assertions.failed ); + } + catch( std::exception& ex ) { + Catch::cerr() << ex.what() << std::endl; + return (std::numeric_limits::max)(); + } + } + + Clara::CommandLine const& cli() const { + return m_cli; + } + std::vector const& unusedTokens() const { + return m_unusedTokens; + } + ConfigData& configData() { + return m_configData; + } + Config& config() { + if( !m_config ) + m_config = new Config( m_configData ); + return *m_config; + } + private: + Clara::CommandLine m_cli; + std::vector m_unusedTokens; + ConfigData m_configData; + Ptr m_config; + }; + + bool Session::alreadyInstantiated = false; + +} // end namespace Catch + +// #included from: catch_registry_hub.hpp +#define TWOBLUECUBES_CATCH_REGISTRY_HUB_HPP_INCLUDED + +// #included from: catch_test_case_registry_impl.hpp +#define TWOBLUECUBES_CATCH_TEST_CASE_REGISTRY_IMPL_HPP_INCLUDED + +#include +#include +#include +#include +#include + +namespace Catch { + + struct LexSort { + bool operator() (TestCase i,TestCase j) const { return (i sortTests( IConfig const& config, std::vector const& unsortedTestCases ) { + + std::vector sorted = unsortedTestCases; + + switch( config.runOrder() ) { + case RunTests::InLexicographicalOrder: + std::sort( sorted.begin(), sorted.end(), LexSort() ); + break; + case RunTests::InRandomOrder: + { + seedRng( config ); + + RandomNumberGenerator rng; + std::random_shuffle( sorted.begin(), sorted.end(), rng ); + } + break; + case RunTests::InDeclarationOrder: + // already in declaration order + break; + } + return sorted; + } + bool matchTest( TestCase const& testCase, TestSpec const& testSpec, IConfig const& config ) { + return testSpec.matches( testCase ) && ( config.allowThrows() || !testCase.throws() ); + } + + void enforceNoDuplicateTestCases( std::vector const& functions ) { + std::set seenFunctions; + for( std::vector::const_iterator it = functions.begin(), itEnd = functions.end(); + it != itEnd; + ++it ) { + std::pair::const_iterator, bool> prev = seenFunctions.insert( *it ); + if( !prev.second ){ + Catch::cerr() + << Colour( Colour::Red ) + << "error: TEST_CASE( \"" << it->name << "\" ) already defined.\n" + << "\tFirst seen at " << prev.first->getTestCaseInfo().lineInfo << "\n" + << "\tRedefined at " << it->getTestCaseInfo().lineInfo << std::endl; + exit(1); + } + } + } + + std::vector filterTests( std::vector const& testCases, TestSpec const& testSpec, IConfig const& config ) { + std::vector filtered; + filtered.reserve( testCases.size() ); + for( std::vector::const_iterator it = testCases.begin(), itEnd = testCases.end(); + it != itEnd; + ++it ) + if( matchTest( *it, testSpec, config ) ) + filtered.push_back( *it ); + return filtered; + } + std::vector const& getAllTestCasesSorted( IConfig const& config ) { + return getRegistryHub().getTestCaseRegistry().getAllTestsSorted( config ); + } + + class TestRegistry : public ITestCaseRegistry { + public: + TestRegistry() + : m_currentSortOrder( RunTests::InDeclarationOrder ), + m_unnamedCount( 0 ) + {} + virtual ~TestRegistry(); + + virtual void registerTest( TestCase const& testCase ) { + std::string name = testCase.getTestCaseInfo().name; + if( name == "" ) { + std::ostringstream oss; + oss << "Anonymous test case " << ++m_unnamedCount; + return registerTest( testCase.withName( oss.str() ) ); + } + m_functions.push_back( testCase ); + } + + virtual std::vector const& getAllTests() const { + return m_functions; + } + virtual std::vector const& getAllTestsSorted( IConfig const& config ) const { + if( m_sortedFunctions.empty() ) + enforceNoDuplicateTestCases( m_functions ); + + if( m_currentSortOrder != config.runOrder() || m_sortedFunctions.empty() ) { + m_sortedFunctions = sortTests( config, m_functions ); + m_currentSortOrder = config.runOrder(); + } + return m_sortedFunctions; + } + + private: + std::vector m_functions; + mutable RunTests::InWhatOrder m_currentSortOrder; + mutable std::vector m_sortedFunctions; + size_t m_unnamedCount; + std::ios_base::Init m_ostreamInit; // Forces cout/ cerr to be initialised + }; + + /////////////////////////////////////////////////////////////////////////// + + class FreeFunctionTestCase : public SharedImpl { + public: + + FreeFunctionTestCase( TestFunction fun ) : m_fun( fun ) {} + + virtual void invoke() const { + m_fun(); + } + + private: + virtual ~FreeFunctionTestCase(); + + TestFunction m_fun; + }; + + inline std::string extractClassName( std::string const& classOrQualifiedMethodName ) { + std::string className = classOrQualifiedMethodName; + if( startsWith( className, "&" ) ) + { + std::size_t lastColons = className.rfind( "::" ); + std::size_t penultimateColons = className.rfind( "::", lastColons-1 ); + if( penultimateColons == std::string::npos ) + penultimateColons = 1; + className = className.substr( penultimateColons, lastColons-penultimateColons ); + } + return className; + } + + void registerTestCase + ( ITestCase* testCase, + char const* classOrQualifiedMethodName, + NameAndDesc const& nameAndDesc, + SourceLineInfo const& lineInfo ) { + + getMutableRegistryHub().registerTest + ( makeTestCase + ( testCase, + extractClassName( classOrQualifiedMethodName ), + nameAndDesc.name, + nameAndDesc.description, + lineInfo ) ); + } + void registerTestCaseFunction + ( TestFunction function, + SourceLineInfo const& lineInfo, + NameAndDesc const& nameAndDesc ) { + registerTestCase( new FreeFunctionTestCase( function ), "", nameAndDesc, lineInfo ); + } + + /////////////////////////////////////////////////////////////////////////// + + AutoReg::AutoReg + ( TestFunction function, + SourceLineInfo const& lineInfo, + NameAndDesc const& nameAndDesc ) { + registerTestCaseFunction( function, lineInfo, nameAndDesc ); + } + + AutoReg::~AutoReg() {} + +} // end namespace Catch + +// #included from: catch_reporter_registry.hpp +#define TWOBLUECUBES_CATCH_REPORTER_REGISTRY_HPP_INCLUDED + +#include + +namespace Catch { + + class ReporterRegistry : public IReporterRegistry { + + public: + + virtual ~ReporterRegistry() CATCH_OVERRIDE {} + + virtual IStreamingReporter* create( std::string const& name, Ptr const& config ) const CATCH_OVERRIDE { + FactoryMap::const_iterator it = m_factories.find( name ); + if( it == m_factories.end() ) + return CATCH_NULL; + return it->second->create( ReporterConfig( config ) ); + } + + void registerReporter( std::string const& name, Ptr const& factory ) { + m_factories.insert( std::make_pair( name, factory ) ); + } + void registerListener( Ptr const& factory ) { + m_listeners.push_back( factory ); + } + + virtual FactoryMap const& getFactories() const CATCH_OVERRIDE { + return m_factories; + } + virtual Listeners const& getListeners() const CATCH_OVERRIDE { + return m_listeners; + } + + private: + FactoryMap m_factories; + Listeners m_listeners; + }; +} + +// #included from: catch_exception_translator_registry.hpp +#define TWOBLUECUBES_CATCH_EXCEPTION_TRANSLATOR_REGISTRY_HPP_INCLUDED + +#ifdef __OBJC__ +#import "Foundation/Foundation.h" +#endif + +namespace Catch { + + class ExceptionTranslatorRegistry : public IExceptionTranslatorRegistry { + public: + ~ExceptionTranslatorRegistry() { + deleteAll( m_translators ); + } + + virtual void registerTranslator( const IExceptionTranslator* translator ) { + m_translators.push_back( translator ); + } + + virtual std::string translateActiveException() const { + try { +#ifdef __OBJC__ + // In Objective-C try objective-c exceptions first + @try { + return tryTranslators(); + } + @catch (NSException *exception) { + return Catch::toString( [exception description] ); + } +#else + return tryTranslators(); +#endif + } + catch( TestFailureException& ) { + throw; + } + catch( std::exception& ex ) { + return ex.what(); + } + catch( std::string& msg ) { + return msg; + } + catch( const char* msg ) { + return msg; + } + catch(...) { + return "Unknown exception"; + } + } + + std::string tryTranslators() const { + if( m_translators.empty() ) + throw; + else + return m_translators[0]->translate( m_translators.begin()+1, m_translators.end() ); + } + + private: + std::vector m_translators; + }; +} + +namespace Catch { + + namespace { + + class RegistryHub : public IRegistryHub, public IMutableRegistryHub { + + RegistryHub( RegistryHub const& ); + void operator=( RegistryHub const& ); + + public: // IRegistryHub + RegistryHub() { + } + virtual IReporterRegistry const& getReporterRegistry() const CATCH_OVERRIDE { + return m_reporterRegistry; + } + virtual ITestCaseRegistry const& getTestCaseRegistry() const CATCH_OVERRIDE { + return m_testCaseRegistry; + } + virtual IExceptionTranslatorRegistry& getExceptionTranslatorRegistry() CATCH_OVERRIDE { + return m_exceptionTranslatorRegistry; + } + + public: // IMutableRegistryHub + virtual void registerReporter( std::string const& name, Ptr const& factory ) CATCH_OVERRIDE { + m_reporterRegistry.registerReporter( name, factory ); + } + virtual void registerListener( Ptr const& factory ) CATCH_OVERRIDE { + m_reporterRegistry.registerListener( factory ); + } + virtual void registerTest( TestCase const& testInfo ) CATCH_OVERRIDE { + m_testCaseRegistry.registerTest( testInfo ); + } + virtual void registerTranslator( const IExceptionTranslator* translator ) CATCH_OVERRIDE { + m_exceptionTranslatorRegistry.registerTranslator( translator ); + } + + private: + TestRegistry m_testCaseRegistry; + ReporterRegistry m_reporterRegistry; + ExceptionTranslatorRegistry m_exceptionTranslatorRegistry; + }; + + // Single, global, instance + inline RegistryHub*& getTheRegistryHub() { + static RegistryHub* theRegistryHub = CATCH_NULL; + if( !theRegistryHub ) + theRegistryHub = new RegistryHub(); + return theRegistryHub; + } + } + + IRegistryHub& getRegistryHub() { + return *getTheRegistryHub(); + } + IMutableRegistryHub& getMutableRegistryHub() { + return *getTheRegistryHub(); + } + void cleanUp() { + delete getTheRegistryHub(); + getTheRegistryHub() = CATCH_NULL; + cleanUpContext(); + } + std::string translateActiveException() { + return getRegistryHub().getExceptionTranslatorRegistry().translateActiveException(); + } + +} // end namespace Catch + +// #included from: catch_notimplemented_exception.hpp +#define TWOBLUECUBES_CATCH_NOTIMPLEMENTED_EXCEPTION_HPP_INCLUDED + +#include + +namespace Catch { + + NotImplementedException::NotImplementedException( SourceLineInfo const& lineInfo ) + : m_lineInfo( lineInfo ) { + std::ostringstream oss; + oss << lineInfo << ": function "; + oss << "not implemented"; + m_what = oss.str(); + } + + const char* NotImplementedException::what() const CATCH_NOEXCEPT { + return m_what.c_str(); + } + +} // end namespace Catch + +// #included from: catch_context_impl.hpp +#define TWOBLUECUBES_CATCH_CONTEXT_IMPL_HPP_INCLUDED + +// #included from: catch_stream.hpp +#define TWOBLUECUBES_CATCH_STREAM_HPP_INCLUDED + +#include +#include +#include + +namespace Catch { + + template + class StreamBufImpl : public StreamBufBase { + char data[bufferSize]; + WriterF m_writer; + + public: + StreamBufImpl() { + setp( data, data + sizeof(data) ); + } + + ~StreamBufImpl() CATCH_NOEXCEPT { + sync(); + } + + private: + int overflow( int c ) { + sync(); + + if( c != EOF ) { + if( pbase() == epptr() ) + m_writer( std::string( 1, static_cast( c ) ) ); + else + sputc( static_cast( c ) ); + } + return 0; + } + + int sync() { + if( pbase() != pptr() ) { + m_writer( std::string( pbase(), static_cast( pptr() - pbase() ) ) ); + setp( pbase(), epptr() ); + } + return 0; + } + }; + + /////////////////////////////////////////////////////////////////////////// + + FileStream::FileStream( std::string const& filename ) { + m_ofs.open( filename.c_str() ); + if( m_ofs.fail() ) { + std::ostringstream oss; + oss << "Unable to open file: '" << filename << "'"; + throw std::domain_error( oss.str() ); + } + } + + std::ostream& FileStream::stream() const { + return m_ofs; + } + + struct OutputDebugWriter { + + void operator()( std::string const&str ) { + writeToDebugConsole( str ); + } + }; + + DebugOutStream::DebugOutStream() + : m_streamBuf( new StreamBufImpl() ), + m_os( m_streamBuf.get() ) + {} + + std::ostream& DebugOutStream::stream() const { + return m_os; + } + + // Store the streambuf from cout up-front because + // cout may get redirected when running tests + CoutStream::CoutStream() + : m_os( Catch::cout().rdbuf() ) + {} + + std::ostream& CoutStream::stream() const { + return m_os; + } + +#ifndef CATCH_CONFIG_NOSTDOUT // If you #define this you must implement these functions + std::ostream& cout() { + return std::cout; + } + std::ostream& cerr() { + return std::cerr; + } +#endif +} + +namespace Catch { + + class Context : public IMutableContext { + + Context() : m_config( CATCH_NULL ), m_runner( CATCH_NULL ), m_resultCapture( CATCH_NULL ) {} + Context( Context const& ); + void operator=( Context const& ); + + public: // IContext + virtual IResultCapture* getResultCapture() { + return m_resultCapture; + } + virtual IRunner* getRunner() { + return m_runner; + } + virtual size_t getGeneratorIndex( std::string const& fileInfo, size_t totalSize ) { + return getGeneratorsForCurrentTest() + .getGeneratorInfo( fileInfo, totalSize ) + .getCurrentIndex(); + } + virtual bool advanceGeneratorsForCurrentTest() { + IGeneratorsForTest* generators = findGeneratorsForCurrentTest(); + return generators && generators->moveNext(); + } + + virtual Ptr getConfig() const { + return m_config; + } + + public: // IMutableContext + virtual void setResultCapture( IResultCapture* resultCapture ) { + m_resultCapture = resultCapture; + } + virtual void setRunner( IRunner* runner ) { + m_runner = runner; + } + virtual void setConfig( Ptr const& config ) { + m_config = config; + } + + friend IMutableContext& getCurrentMutableContext(); + + private: + IGeneratorsForTest* findGeneratorsForCurrentTest() { + std::string testName = getResultCapture()->getCurrentTestName(); + + std::map::const_iterator it = + m_generatorsByTestName.find( testName ); + return it != m_generatorsByTestName.end() + ? it->second + : CATCH_NULL; + } + + IGeneratorsForTest& getGeneratorsForCurrentTest() { + IGeneratorsForTest* generators = findGeneratorsForCurrentTest(); + if( !generators ) { + std::string testName = getResultCapture()->getCurrentTestName(); + generators = createGeneratorsForTest(); + m_generatorsByTestName.insert( std::make_pair( testName, generators ) ); + } + return *generators; + } + + private: + Ptr m_config; + IRunner* m_runner; + IResultCapture* m_resultCapture; + std::map m_generatorsByTestName; + }; + + namespace { + Context* currentContext = CATCH_NULL; + } + IMutableContext& getCurrentMutableContext() { + if( !currentContext ) + currentContext = new Context(); + return *currentContext; + } + IContext& getCurrentContext() { + return getCurrentMutableContext(); + } + + void cleanUpContext() { + delete currentContext; + currentContext = CATCH_NULL; + } +} + +// #included from: catch_console_colour_impl.hpp +#define TWOBLUECUBES_CATCH_CONSOLE_COLOUR_IMPL_HPP_INCLUDED + +namespace Catch { + namespace { + + struct IColourImpl { + virtual ~IColourImpl() {} + virtual void use( Colour::Code _colourCode ) = 0; + }; + + struct NoColourImpl : IColourImpl { + void use( Colour::Code ) {} + + static IColourImpl* instance() { + static NoColourImpl s_instance; + return &s_instance; + } + }; + + } // anon namespace +} // namespace Catch + +#if !defined( CATCH_CONFIG_COLOUR_NONE ) && !defined( CATCH_CONFIG_COLOUR_WINDOWS ) && !defined( CATCH_CONFIG_COLOUR_ANSI ) +# ifdef CATCH_PLATFORM_WINDOWS +# define CATCH_CONFIG_COLOUR_WINDOWS +# else +# define CATCH_CONFIG_COLOUR_ANSI +# endif +#endif + +#if defined ( CATCH_CONFIG_COLOUR_WINDOWS ) ///////////////////////////////////////// + +#ifndef NOMINMAX +#define NOMINMAX +#endif + +#ifdef __AFXDLL +#include +#else +#include +#endif + +namespace Catch { +namespace { + + class Win32ColourImpl : public IColourImpl { + public: + Win32ColourImpl() : stdoutHandle( GetStdHandle(STD_OUTPUT_HANDLE) ) + { + CONSOLE_SCREEN_BUFFER_INFO csbiInfo; + GetConsoleScreenBufferInfo( stdoutHandle, &csbiInfo ); + originalForegroundAttributes = csbiInfo.wAttributes & ~( BACKGROUND_GREEN | BACKGROUND_RED | BACKGROUND_BLUE | BACKGROUND_INTENSITY ); + originalBackgroundAttributes = csbiInfo.wAttributes & ~( FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE | FOREGROUND_INTENSITY ); + } + + virtual void use( Colour::Code _colourCode ) { + switch( _colourCode ) { + case Colour::None: return setTextAttribute( originalForegroundAttributes ); + case Colour::White: return setTextAttribute( FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE ); + case Colour::Red: return setTextAttribute( FOREGROUND_RED ); + case Colour::Green: return setTextAttribute( FOREGROUND_GREEN ); + case Colour::Blue: return setTextAttribute( FOREGROUND_BLUE ); + case Colour::Cyan: return setTextAttribute( FOREGROUND_BLUE | FOREGROUND_GREEN ); + case Colour::Yellow: return setTextAttribute( FOREGROUND_RED | FOREGROUND_GREEN ); + case Colour::Grey: return setTextAttribute( 0 ); + + case Colour::LightGrey: return setTextAttribute( FOREGROUND_INTENSITY ); + case Colour::BrightRed: return setTextAttribute( FOREGROUND_INTENSITY | FOREGROUND_RED ); + case Colour::BrightGreen: return setTextAttribute( FOREGROUND_INTENSITY | FOREGROUND_GREEN ); + case Colour::BrightWhite: return setTextAttribute( FOREGROUND_INTENSITY | FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE ); + + case Colour::Bright: throw std::logic_error( "not a colour" ); + } + } + + private: + void setTextAttribute( WORD _textAttribute ) { + SetConsoleTextAttribute( stdoutHandle, _textAttribute | originalBackgroundAttributes ); + } + HANDLE stdoutHandle; + WORD originalForegroundAttributes; + WORD originalBackgroundAttributes; + }; + + IColourImpl* platformColourInstance() { + static Win32ColourImpl s_instance; + + Ptr config = getCurrentContext().getConfig(); + UseColour::YesOrNo colourMode = config + ? config->useColour() + : UseColour::Auto; + if( colourMode == UseColour::Auto ) + colourMode = !isDebuggerActive() + ? UseColour::Yes + : UseColour::No; + return colourMode == UseColour::Yes + ? &s_instance + : NoColourImpl::instance(); + } + +} // end anon namespace +} // end namespace Catch + +#elif defined( CATCH_CONFIG_COLOUR_ANSI ) ////////////////////////////////////// + +#include + +namespace Catch { +namespace { + + // use POSIX/ ANSI console terminal codes + // Thanks to Adam Strzelecki for original contribution + // (http://github.com/nanoant) + // https://github.com/philsquared/Catch/pull/131 + class PosixColourImpl : public IColourImpl { + public: + virtual void use( Colour::Code _colourCode ) { + switch( _colourCode ) { + case Colour::None: + case Colour::White: return setColour( "[0m" ); + case Colour::Red: return setColour( "[0;31m" ); + case Colour::Green: return setColour( "[0;32m" ); + case Colour::Blue: return setColour( "[0:34m" ); + case Colour::Cyan: return setColour( "[0;36m" ); + case Colour::Yellow: return setColour( "[0;33m" ); + case Colour::Grey: return setColour( "[1;30m" ); + + case Colour::LightGrey: return setColour( "[0;37m" ); + case Colour::BrightRed: return setColour( "[1;31m" ); + case Colour::BrightGreen: return setColour( "[1;32m" ); + case Colour::BrightWhite: return setColour( "[1;37m" ); + + case Colour::Bright: throw std::logic_error( "not a colour" ); + } + } + static IColourImpl* instance() { + static PosixColourImpl s_instance; + return &s_instance; + } + + private: + void setColour( const char* _escapeCode ) { + Catch::cout() << '\033' << _escapeCode; + } + }; + + IColourImpl* platformColourInstance() { + Ptr config = getCurrentContext().getConfig(); + UseColour::YesOrNo colourMode = config + ? config->useColour() + : UseColour::Auto; + if( colourMode == UseColour::Auto ) + colourMode = (!isDebuggerActive() && isatty(STDOUT_FILENO) ) + ? UseColour::Yes + : UseColour::No; + return colourMode == UseColour::Yes + ? PosixColourImpl::instance() + : NoColourImpl::instance(); + } + +} // end anon namespace +} // end namespace Catch + +#else // not Windows or ANSI /////////////////////////////////////////////// + +namespace Catch { + + static IColourImpl* platformColourInstance() { return NoColourImpl::instance(); } + +} // end namespace Catch + +#endif // Windows/ ANSI/ None + +namespace Catch { + + Colour::Colour( Code _colourCode ) : m_moved( false ) { use( _colourCode ); } + Colour::Colour( Colour const& _other ) : m_moved( false ) { const_cast( _other ).m_moved = true; } + Colour::~Colour(){ if( !m_moved ) use( None ); } + + void Colour::use( Code _colourCode ) { + static IColourImpl* impl = platformColourInstance(); + impl->use( _colourCode ); + } + +} // end namespace Catch + +// #included from: catch_generators_impl.hpp +#define TWOBLUECUBES_CATCH_GENERATORS_IMPL_HPP_INCLUDED + +#include +#include +#include + +namespace Catch { + + struct GeneratorInfo : IGeneratorInfo { + + GeneratorInfo( std::size_t size ) + : m_size( size ), + m_currentIndex( 0 ) + {} + + bool moveNext() { + if( ++m_currentIndex == m_size ) { + m_currentIndex = 0; + return false; + } + return true; + } + + std::size_t getCurrentIndex() const { + return m_currentIndex; + } + + std::size_t m_size; + std::size_t m_currentIndex; + }; + + /////////////////////////////////////////////////////////////////////////// + + class GeneratorsForTest : public IGeneratorsForTest { + + public: + ~GeneratorsForTest() { + deleteAll( m_generatorsInOrder ); + } + + IGeneratorInfo& getGeneratorInfo( std::string const& fileInfo, std::size_t size ) { + std::map::const_iterator it = m_generatorsByName.find( fileInfo ); + if( it == m_generatorsByName.end() ) { + IGeneratorInfo* info = new GeneratorInfo( size ); + m_generatorsByName.insert( std::make_pair( fileInfo, info ) ); + m_generatorsInOrder.push_back( info ); + return *info; + } + return *it->second; + } + + bool moveNext() { + std::vector::const_iterator it = m_generatorsInOrder.begin(); + std::vector::const_iterator itEnd = m_generatorsInOrder.end(); + for(; it != itEnd; ++it ) { + if( (*it)->moveNext() ) + return true; + } + return false; + } + + private: + std::map m_generatorsByName; + std::vector m_generatorsInOrder; + }; + + IGeneratorsForTest* createGeneratorsForTest() + { + return new GeneratorsForTest(); + } + +} // end namespace Catch + +// #included from: catch_assertionresult.hpp +#define TWOBLUECUBES_CATCH_ASSERTIONRESULT_HPP_INCLUDED + +namespace Catch { + + AssertionInfo::AssertionInfo( std::string const& _macroName, + SourceLineInfo const& _lineInfo, + std::string const& _capturedExpression, + ResultDisposition::Flags _resultDisposition ) + : macroName( _macroName ), + lineInfo( _lineInfo ), + capturedExpression( _capturedExpression ), + resultDisposition( _resultDisposition ) + {} + + AssertionResult::AssertionResult() {} + + AssertionResult::AssertionResult( AssertionInfo const& info, AssertionResultData const& data ) + : m_info( info ), + m_resultData( data ) + {} + + AssertionResult::~AssertionResult() {} + + // Result was a success + bool AssertionResult::succeeded() const { + return Catch::isOk( m_resultData.resultType ); + } + + // Result was a success, or failure is suppressed + bool AssertionResult::isOk() const { + return Catch::isOk( m_resultData.resultType ) || shouldSuppressFailure( m_info.resultDisposition ); + } + + ResultWas::OfType AssertionResult::getResultType() const { + return m_resultData.resultType; + } + + bool AssertionResult::hasExpression() const { + return !m_info.capturedExpression.empty(); + } + + bool AssertionResult::hasMessage() const { + return !m_resultData.message.empty(); + } + + std::string AssertionResult::getExpression() const { + if( isFalseTest( m_info.resultDisposition ) ) + return "!" + m_info.capturedExpression; + else + return m_info.capturedExpression; + } + std::string AssertionResult::getExpressionInMacro() const { + if( m_info.macroName.empty() ) + return m_info.capturedExpression; + else + return m_info.macroName + "( " + m_info.capturedExpression + " )"; + } + + bool AssertionResult::hasExpandedExpression() const { + return hasExpression() && getExpandedExpression() != getExpression(); + } + + std::string AssertionResult::getExpandedExpression() const { + return m_resultData.reconstructedExpression; + } + + std::string AssertionResult::getMessage() const { + return m_resultData.message; + } + SourceLineInfo AssertionResult::getSourceInfo() const { + return m_info.lineInfo; + } + + std::string AssertionResult::getTestMacroName() const { + return m_info.macroName; + } + +} // end namespace Catch + +// #included from: catch_test_case_info.hpp +#define TWOBLUECUBES_CATCH_TEST_CASE_INFO_HPP_INCLUDED + +namespace Catch { + + inline TestCaseInfo::SpecialProperties parseSpecialTag( std::string const& tag ) { + if( startsWith( tag, "." ) || + tag == "hide" || + tag == "!hide" ) + return TestCaseInfo::IsHidden; + else if( tag == "!throws" ) + return TestCaseInfo::Throws; + else if( tag == "!shouldfail" ) + return TestCaseInfo::ShouldFail; + else if( tag == "!mayfail" ) + return TestCaseInfo::MayFail; + else + return TestCaseInfo::None; + } + inline bool isReservedTag( std::string const& tag ) { + return parseSpecialTag( tag ) == TestCaseInfo::None && tag.size() > 0 && !isalnum( tag[0] ); + } + inline void enforceNotReservedTag( std::string const& tag, SourceLineInfo const& _lineInfo ) { + if( isReservedTag( tag ) ) { + { + Colour colourGuard( Colour::Red ); + Catch::cerr() + << "Tag name [" << tag << "] not allowed.\n" + << "Tag names starting with non alpha-numeric characters are reserved\n"; + } + { + Colour colourGuard( Colour::FileName ); + Catch::cerr() << _lineInfo << std::endl; + } + exit(1); + } + } + + TestCase makeTestCase( ITestCase* _testCase, + std::string const& _className, + std::string const& _name, + std::string const& _descOrTags, + SourceLineInfo const& _lineInfo ) + { + bool isHidden( startsWith( _name, "./" ) ); // Legacy support + + // Parse out tags + std::set tags; + std::string desc, tag; + bool inTag = false; + for( std::size_t i = 0; i < _descOrTags.size(); ++i ) { + char c = _descOrTags[i]; + if( !inTag ) { + if( c == '[' ) + inTag = true; + else + desc += c; + } + else { + if( c == ']' ) { + TestCaseInfo::SpecialProperties prop = parseSpecialTag( tag ); + if( prop == TestCaseInfo::IsHidden ) + isHidden = true; + else if( prop == TestCaseInfo::None ) + enforceNotReservedTag( tag, _lineInfo ); + + tags.insert( tag ); + tag.clear(); + inTag = false; + } + else + tag += c; + } + } + if( isHidden ) { + tags.insert( "hide" ); + tags.insert( "." ); + } + + TestCaseInfo info( _name, _className, desc, tags, _lineInfo ); + return TestCase( _testCase, info ); + } + + void setTags( TestCaseInfo& testCaseInfo, std::set const& tags ) + { + testCaseInfo.tags = tags; + testCaseInfo.lcaseTags.clear(); + + std::ostringstream oss; + for( std::set::const_iterator it = tags.begin(), itEnd = tags.end(); it != itEnd; ++it ) { + oss << "[" << *it << "]"; + std::string lcaseTag = toLower( *it ); + testCaseInfo.properties = static_cast( testCaseInfo.properties | parseSpecialTag( lcaseTag ) ); + testCaseInfo.lcaseTags.insert( lcaseTag ); + } + testCaseInfo.tagsAsString = oss.str(); + } + + TestCaseInfo::TestCaseInfo( std::string const& _name, + std::string const& _className, + std::string const& _description, + std::set const& _tags, + SourceLineInfo const& _lineInfo ) + : name( _name ), + className( _className ), + description( _description ), + lineInfo( _lineInfo ), + properties( None ) + { + setTags( *this, _tags ); + } + + TestCaseInfo::TestCaseInfo( TestCaseInfo const& other ) + : name( other.name ), + className( other.className ), + description( other.description ), + tags( other.tags ), + lcaseTags( other.lcaseTags ), + tagsAsString( other.tagsAsString ), + lineInfo( other.lineInfo ), + properties( other.properties ) + {} + + bool TestCaseInfo::isHidden() const { + return ( properties & IsHidden ) != 0; + } + bool TestCaseInfo::throws() const { + return ( properties & Throws ) != 0; + } + bool TestCaseInfo::okToFail() const { + return ( properties & (ShouldFail | MayFail ) ) != 0; + } + bool TestCaseInfo::expectedToFail() const { + return ( properties & (ShouldFail ) ) != 0; + } + + TestCase::TestCase( ITestCase* testCase, TestCaseInfo const& info ) : TestCaseInfo( info ), test( testCase ) {} + + TestCase::TestCase( TestCase const& other ) + : TestCaseInfo( other ), + test( other.test ) + {} + + TestCase TestCase::withName( std::string const& _newName ) const { + TestCase other( *this ); + other.name = _newName; + return other; + } + + void TestCase::swap( TestCase& other ) { + test.swap( other.test ); + name.swap( other.name ); + className.swap( other.className ); + description.swap( other.description ); + tags.swap( other.tags ); + lcaseTags.swap( other.lcaseTags ); + tagsAsString.swap( other.tagsAsString ); + std::swap( TestCaseInfo::properties, static_cast( other ).properties ); + std::swap( lineInfo, other.lineInfo ); + } + + void TestCase::invoke() const { + test->invoke(); + } + + bool TestCase::operator == ( TestCase const& other ) const { + return test.get() == other.test.get() && + name == other.name && + className == other.className; + } + + bool TestCase::operator < ( TestCase const& other ) const { + return name < other.name; + } + TestCase& TestCase::operator = ( TestCase const& other ) { + TestCase temp( other ); + swap( temp ); + return *this; + } + + TestCaseInfo const& TestCase::getTestCaseInfo() const + { + return *this; + } + +} // end namespace Catch + +// #included from: catch_version.hpp +#define TWOBLUECUBES_CATCH_VERSION_HPP_INCLUDED + +namespace Catch { + + Version::Version + ( unsigned int _majorVersion, + unsigned int _minorVersion, + unsigned int _patchNumber, + std::string const& _branchName, + unsigned int _buildNumber ) + : majorVersion( _majorVersion ), + minorVersion( _minorVersion ), + patchNumber( _patchNumber ), + branchName( _branchName ), + buildNumber( _buildNumber ) + {} + + std::ostream& operator << ( std::ostream& os, Version const& version ) { + os << version.majorVersion << "." + << version.minorVersion << "." + << version.patchNumber; + + if( !version.branchName.empty() ) { + os << "-" << version.branchName + << "." << version.buildNumber; + } + return os; + } + + Version libraryVersion( 1, 4, 0, "", 0 ); + +} + +// #included from: catch_message.hpp +#define TWOBLUECUBES_CATCH_MESSAGE_HPP_INCLUDED + +namespace Catch { + + MessageInfo::MessageInfo( std::string const& _macroName, + SourceLineInfo const& _lineInfo, + ResultWas::OfType _type ) + : macroName( _macroName ), + lineInfo( _lineInfo ), + type( _type ), + sequence( ++globalCount ) + {} + + // This may need protecting if threading support is added + unsigned int MessageInfo::globalCount = 0; + + //////////////////////////////////////////////////////////////////////////// + + ScopedMessage::ScopedMessage( MessageBuilder const& builder ) + : m_info( builder.m_info ) + { + m_info.message = builder.m_stream.str(); + getResultCapture().pushScopedMessage( m_info ); + } + ScopedMessage::ScopedMessage( ScopedMessage const& other ) + : m_info( other.m_info ) + {} + + ScopedMessage::~ScopedMessage() { + getResultCapture().popScopedMessage( m_info ); + } + +} // end namespace Catch + +// #included from: catch_legacy_reporter_adapter.hpp +#define TWOBLUECUBES_CATCH_LEGACY_REPORTER_ADAPTER_HPP_INCLUDED + +// #included from: catch_legacy_reporter_adapter.h +#define TWOBLUECUBES_CATCH_LEGACY_REPORTER_ADAPTER_H_INCLUDED + +namespace Catch +{ + // Deprecated + struct IReporter : IShared { + virtual ~IReporter(); + + virtual bool shouldRedirectStdout() const = 0; + + virtual void StartTesting() = 0; + virtual void EndTesting( Totals const& totals ) = 0; + virtual void StartGroup( std::string const& groupName ) = 0; + virtual void EndGroup( std::string const& groupName, Totals const& totals ) = 0; + virtual void StartTestCase( TestCaseInfo const& testInfo ) = 0; + virtual void EndTestCase( TestCaseInfo const& testInfo, Totals const& totals, std::string const& stdOut, std::string const& stdErr ) = 0; + virtual void StartSection( std::string const& sectionName, std::string const& description ) = 0; + virtual void EndSection( std::string const& sectionName, Counts const& assertions ) = 0; + virtual void NoAssertionsInSection( std::string const& sectionName ) = 0; + virtual void NoAssertionsInTestCase( std::string const& testName ) = 0; + virtual void Aborted() = 0; + virtual void Result( AssertionResult const& result ) = 0; + }; + + class LegacyReporterAdapter : public SharedImpl + { + public: + LegacyReporterAdapter( Ptr const& legacyReporter ); + virtual ~LegacyReporterAdapter(); + + virtual ReporterPreferences getPreferences() const; + virtual void noMatchingTestCases( std::string const& ); + virtual void testRunStarting( TestRunInfo const& ); + virtual void testGroupStarting( GroupInfo const& groupInfo ); + virtual void testCaseStarting( TestCaseInfo const& testInfo ); + virtual void sectionStarting( SectionInfo const& sectionInfo ); + virtual void assertionStarting( AssertionInfo const& ); + virtual bool assertionEnded( AssertionStats const& assertionStats ); + virtual void sectionEnded( SectionStats const& sectionStats ); + virtual void testCaseEnded( TestCaseStats const& testCaseStats ); + virtual void testGroupEnded( TestGroupStats const& testGroupStats ); + virtual void testRunEnded( TestRunStats const& testRunStats ); + virtual void skipTest( TestCaseInfo const& ); + + private: + Ptr m_legacyReporter; + }; +} + +namespace Catch +{ + LegacyReporterAdapter::LegacyReporterAdapter( Ptr const& legacyReporter ) + : m_legacyReporter( legacyReporter ) + {} + LegacyReporterAdapter::~LegacyReporterAdapter() {} + + ReporterPreferences LegacyReporterAdapter::getPreferences() const { + ReporterPreferences prefs; + prefs.shouldRedirectStdOut = m_legacyReporter->shouldRedirectStdout(); + return prefs; + } + + void LegacyReporterAdapter::noMatchingTestCases( std::string const& ) {} + void LegacyReporterAdapter::testRunStarting( TestRunInfo const& ) { + m_legacyReporter->StartTesting(); + } + void LegacyReporterAdapter::testGroupStarting( GroupInfo const& groupInfo ) { + m_legacyReporter->StartGroup( groupInfo.name ); + } + void LegacyReporterAdapter::testCaseStarting( TestCaseInfo const& testInfo ) { + m_legacyReporter->StartTestCase( testInfo ); + } + void LegacyReporterAdapter::sectionStarting( SectionInfo const& sectionInfo ) { + m_legacyReporter->StartSection( sectionInfo.name, sectionInfo.description ); + } + void LegacyReporterAdapter::assertionStarting( AssertionInfo const& ) { + // Not on legacy interface + } + + bool LegacyReporterAdapter::assertionEnded( AssertionStats const& assertionStats ) { + if( assertionStats.assertionResult.getResultType() != ResultWas::Ok ) { + for( std::vector::const_iterator it = assertionStats.infoMessages.begin(), itEnd = assertionStats.infoMessages.end(); + it != itEnd; + ++it ) { + if( it->type == ResultWas::Info ) { + ResultBuilder rb( it->macroName.c_str(), it->lineInfo, "", ResultDisposition::Normal ); + rb << it->message; + rb.setResultType( ResultWas::Info ); + AssertionResult result = rb.build(); + m_legacyReporter->Result( result ); + } + } + } + m_legacyReporter->Result( assertionStats.assertionResult ); + return true; + } + void LegacyReporterAdapter::sectionEnded( SectionStats const& sectionStats ) { + if( sectionStats.missingAssertions ) + m_legacyReporter->NoAssertionsInSection( sectionStats.sectionInfo.name ); + m_legacyReporter->EndSection( sectionStats.sectionInfo.name, sectionStats.assertions ); + } + void LegacyReporterAdapter::testCaseEnded( TestCaseStats const& testCaseStats ) { + m_legacyReporter->EndTestCase + ( testCaseStats.testInfo, + testCaseStats.totals, + testCaseStats.stdOut, + testCaseStats.stdErr ); + } + void LegacyReporterAdapter::testGroupEnded( TestGroupStats const& testGroupStats ) { + if( testGroupStats.aborting ) + m_legacyReporter->Aborted(); + m_legacyReporter->EndGroup( testGroupStats.groupInfo.name, testGroupStats.totals ); + } + void LegacyReporterAdapter::testRunEnded( TestRunStats const& testRunStats ) { + m_legacyReporter->EndTesting( testRunStats.totals ); + } + void LegacyReporterAdapter::skipTest( TestCaseInfo const& ) { + } +} + +// #included from: catch_timer.hpp + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wc++11-long-long" +#endif + +#ifdef CATCH_PLATFORM_WINDOWS +#include +#else +#include +#endif + +namespace Catch { + + namespace { +#ifdef CATCH_PLATFORM_WINDOWS + uint64_t getCurrentTicks() { + static uint64_t hz=0, hzo=0; + if (!hz) { + QueryPerformanceFrequency( reinterpret_cast( &hz ) ); + QueryPerformanceCounter( reinterpret_cast( &hzo ) ); + } + uint64_t t; + QueryPerformanceCounter( reinterpret_cast( &t ) ); + return ((t-hzo)*1000000)/hz; + } +#else + uint64_t getCurrentTicks() { + timeval t; + gettimeofday(&t,CATCH_NULL); + return static_cast( t.tv_sec ) * 1000000ull + static_cast( t.tv_usec ); + } +#endif + } + + void Timer::start() { + m_ticks = getCurrentTicks(); + } + unsigned int Timer::getElapsedMicroseconds() const { + return static_cast(getCurrentTicks() - m_ticks); + } + unsigned int Timer::getElapsedMilliseconds() const { + return static_cast(getElapsedMicroseconds()/1000); + } + double Timer::getElapsedSeconds() const { + return getElapsedMicroseconds()/1000000.0; + } + +} // namespace Catch + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif +// #included from: catch_common.hpp +#define TWOBLUECUBES_CATCH_COMMON_HPP_INCLUDED + +namespace Catch { + + bool startsWith( std::string const& s, std::string const& prefix ) { + return s.size() >= prefix.size() && s.substr( 0, prefix.size() ) == prefix; + } + bool endsWith( std::string const& s, std::string const& suffix ) { + return s.size() >= suffix.size() && s.substr( s.size()-suffix.size(), suffix.size() ) == suffix; + } + bool contains( std::string const& s, std::string const& infix ) { + return s.find( infix ) != std::string::npos; + } + void toLowerInPlace( std::string& s ) { + std::transform( s.begin(), s.end(), s.begin(), ::tolower ); + } + std::string toLower( std::string const& s ) { + std::string lc = s; + toLowerInPlace( lc ); + return lc; + } + std::string trim( std::string const& str ) { + static char const* whitespaceChars = "\n\r\t "; + std::string::size_type start = str.find_first_not_of( whitespaceChars ); + std::string::size_type end = str.find_last_not_of( whitespaceChars ); + + return start != std::string::npos ? str.substr( start, 1+end-start ) : ""; + } + + bool replaceInPlace( std::string& str, std::string const& replaceThis, std::string const& withThis ) { + bool replaced = false; + std::size_t i = str.find( replaceThis ); + while( i != std::string::npos ) { + replaced = true; + str = str.substr( 0, i ) + withThis + str.substr( i+replaceThis.size() ); + if( i < str.size()-withThis.size() ) + i = str.find( replaceThis, i+withThis.size() ); + else + i = std::string::npos; + } + return replaced; + } + + pluralise::pluralise( std::size_t count, std::string const& label ) + : m_count( count ), + m_label( label ) + {} + + std::ostream& operator << ( std::ostream& os, pluralise const& pluraliser ) { + os << pluraliser.m_count << " " << pluraliser.m_label; + if( pluraliser.m_count != 1 ) + os << "s"; + return os; + } + + SourceLineInfo::SourceLineInfo() : line( 0 ){} + SourceLineInfo::SourceLineInfo( char const* _file, std::size_t _line ) + : file( _file ), + line( _line ) + {} + SourceLineInfo::SourceLineInfo( SourceLineInfo const& other ) + : file( other.file ), + line( other.line ) + {} + bool SourceLineInfo::empty() const { + return file.empty(); + } + bool SourceLineInfo::operator == ( SourceLineInfo const& other ) const { + return line == other.line && file == other.file; + } + bool SourceLineInfo::operator < ( SourceLineInfo const& other ) const { + return line < other.line || ( line == other.line && file < other.file ); + } + + void seedRng( IConfig const& config ) { + if( config.rngSeed() != 0 ) + std::srand( config.rngSeed() ); + } + unsigned int rngSeed() { + return getCurrentContext().getConfig()->rngSeed(); + } + + std::ostream& operator << ( std::ostream& os, SourceLineInfo const& info ) { +#ifndef __GNUG__ + os << info.file << "(" << info.line << ")"; +#else + os << info.file << ":" << info.line; +#endif + return os; + } + + void throwLogicError( std::string const& message, SourceLineInfo const& locationInfo ) { + std::ostringstream oss; + oss << locationInfo << ": Internal Catch error: '" << message << "'"; + if( alwaysTrue() ) + throw std::logic_error( oss.str() ); + } +} + +// #included from: catch_section.hpp +#define TWOBLUECUBES_CATCH_SECTION_HPP_INCLUDED + +namespace Catch { + + SectionInfo::SectionInfo + ( SourceLineInfo const& _lineInfo, + std::string const& _name, + std::string const& _description ) + : name( _name ), + description( _description ), + lineInfo( _lineInfo ) + {} + + Section::Section( SectionInfo const& info ) + : m_info( info ), + m_sectionIncluded( getResultCapture().sectionStarted( m_info, m_assertions ) ) + { + m_timer.start(); + } + + Section::~Section() { + if( m_sectionIncluded ) { + SectionEndInfo endInfo( m_info, m_assertions, m_timer.getElapsedSeconds() ); + if( std::uncaught_exception() ) + getResultCapture().sectionEndedEarly( endInfo ); + else + getResultCapture().sectionEnded( endInfo ); + } + } + + // This indicates whether the section should be executed or not + Section::operator bool() const { + return m_sectionIncluded; + } + +} // end namespace Catch + +// #included from: catch_debugger.hpp +#define TWOBLUECUBES_CATCH_DEBUGGER_HPP_INCLUDED + +#include + +#ifdef CATCH_PLATFORM_MAC + + #include + #include + #include + #include + #include + + namespace Catch{ + + // The following function is taken directly from the following technical note: + // http://developer.apple.com/library/mac/#qa/qa2004/qa1361.html + + // Returns true if the current process is being debugged (either + // running under the debugger or has a debugger attached post facto). + bool isDebuggerActive(){ + + int mib[4]; + struct kinfo_proc info; + size_t size; + + // Initialize the flags so that, if sysctl fails for some bizarre + // reason, we get a predictable result. + + info.kp_proc.p_flag = 0; + + // Initialize mib, which tells sysctl the info we want, in this case + // we're looking for information about a specific process ID. + + mib[0] = CTL_KERN; + mib[1] = KERN_PROC; + mib[2] = KERN_PROC_PID; + mib[3] = getpid(); + + // Call sysctl. + + size = sizeof(info); + if( sysctl(mib, sizeof(mib) / sizeof(*mib), &info, &size, CATCH_NULL, 0) != 0 ) { + Catch::cerr() << "\n** Call to sysctl failed - unable to determine if debugger is active **\n" << std::endl; + return false; + } + + // We're being debugged if the P_TRACED flag is set. + + return ( (info.kp_proc.p_flag & P_TRACED) != 0 ); + } + } // namespace Catch + +#elif defined(_MSC_VER) + extern "C" __declspec(dllimport) int __stdcall IsDebuggerPresent(); + namespace Catch { + bool isDebuggerActive() { + return IsDebuggerPresent() != 0; + } + } +#elif defined(__MINGW32__) + extern "C" __declspec(dllimport) int __stdcall IsDebuggerPresent(); + namespace Catch { + bool isDebuggerActive() { + return IsDebuggerPresent() != 0; + } + } +#else + namespace Catch { + inline bool isDebuggerActive() { return false; } + } +#endif // Platform + +#ifdef CATCH_PLATFORM_WINDOWS + extern "C" __declspec(dllimport) void __stdcall OutputDebugStringA( const char* ); + namespace Catch { + void writeToDebugConsole( std::string const& text ) { + ::OutputDebugStringA( text.c_str() ); + } + } +#else + namespace Catch { + void writeToDebugConsole( std::string const& text ) { + // !TBD: Need a version for Mac/ XCode and other IDEs + Catch::cout() << text; + } + } +#endif // Platform + +// #included from: catch_tostring.hpp +#define TWOBLUECUBES_CATCH_TOSTRING_HPP_INCLUDED + +namespace Catch { + +namespace Detail { + + const std::string unprintableString = "{?}"; + + namespace { + const int hexThreshold = 255; + + struct Endianness { + enum Arch { Big, Little }; + + static Arch which() { + union _{ + int asInt; + char asChar[sizeof (int)]; + } u; + + u.asInt = 1; + return ( u.asChar[sizeof(int)-1] == 1 ) ? Big : Little; + } + }; + } + + std::string rawMemoryToString( const void *object, std::size_t size ) + { + // Reverse order for little endian architectures + int i = 0, end = static_cast( size ), inc = 1; + if( Endianness::which() == Endianness::Little ) { + i = end-1; + end = inc = -1; + } + + unsigned char const *bytes = static_cast(object); + std::ostringstream os; + os << "0x" << std::setfill('0') << std::hex; + for( ; i != end; i += inc ) + os << std::setw(2) << static_cast(bytes[i]); + return os.str(); + } +} + +std::string toString( std::string const& value ) { + std::string s = value; + if( getCurrentContext().getConfig()->showInvisibles() ) { + for(size_t i = 0; i < s.size(); ++i ) { + std::string subs; + switch( s[i] ) { + case '\n': subs = "\\n"; break; + case '\t': subs = "\\t"; break; + default: break; + } + if( !subs.empty() ) { + s = s.substr( 0, i ) + subs + s.substr( i+1 ); + ++i; + } + } + } + return "\"" + s + "\""; +} +std::string toString( std::wstring const& value ) { + + std::string s; + s.reserve( value.size() ); + for(size_t i = 0; i < value.size(); ++i ) + s += value[i] <= 0xff ? static_cast( value[i] ) : '?'; + return Catch::toString( s ); +} + +std::string toString( const char* const value ) { + return value ? Catch::toString( std::string( value ) ) : std::string( "{null string}" ); +} + +std::string toString( char* const value ) { + return Catch::toString( static_cast( value ) ); +} + +std::string toString( const wchar_t* const value ) +{ + return value ? Catch::toString( std::wstring(value) ) : std::string( "{null string}" ); +} + +std::string toString( wchar_t* const value ) +{ + return Catch::toString( static_cast( value ) ); +} + +std::string toString( int value ) { + std::ostringstream oss; + oss << value; + if( value > Detail::hexThreshold ) + oss << " (0x" << std::hex << value << ")"; + return oss.str(); +} + +std::string toString( unsigned long value ) { + std::ostringstream oss; + oss << value; + if( value > Detail::hexThreshold ) + oss << " (0x" << std::hex << value << ")"; + return oss.str(); +} + +std::string toString( unsigned int value ) { + return Catch::toString( static_cast( value ) ); +} + +template +std::string fpToString( T value, int precision ) { + std::ostringstream oss; + oss << std::setprecision( precision ) + << std::fixed + << value; + std::string d = oss.str(); + std::size_t i = d.find_last_not_of( '0' ); + if( i != std::string::npos && i != d.size()-1 ) { + if( d[i] == '.' ) + i++; + d = d.substr( 0, i+1 ); + } + return d; +} + +std::string toString( const double value ) { + return fpToString( value, 10 ); +} +std::string toString( const float value ) { + return fpToString( value, 5 ) + "f"; +} + +std::string toString( bool value ) { + return value ? "true" : "false"; +} + +std::string toString( char value ) { + return value < ' ' + ? toString( static_cast( value ) ) + : Detail::makeString( value ); +} + +std::string toString( signed char value ) { + return toString( static_cast( value ) ); +} + +std::string toString( unsigned char value ) { + return toString( static_cast( value ) ); +} + +#ifdef CATCH_CONFIG_CPP11_LONG_LONG +std::string toString( long long value ) { + std::ostringstream oss; + oss << value; + if( value > Detail::hexThreshold ) + oss << " (0x" << std::hex << value << ")"; + return oss.str(); +} +std::string toString( unsigned long long value ) { + std::ostringstream oss; + oss << value; + if( value > Detail::hexThreshold ) + oss << " (0x" << std::hex << value << ")"; + return oss.str(); +} +#endif + +#ifdef CATCH_CONFIG_CPP11_NULLPTR +std::string toString( std::nullptr_t ) { + return "nullptr"; +} +#endif + +#ifdef __OBJC__ + std::string toString( NSString const * const& nsstring ) { + if( !nsstring ) + return "nil"; + return "@" + toString([nsstring UTF8String]); + } + std::string toString( NSString * CATCH_ARC_STRONG const& nsstring ) { + if( !nsstring ) + return "nil"; + return "@" + toString([nsstring UTF8String]); + } + std::string toString( NSObject* const& nsObject ) { + return toString( [nsObject description] ); + } +#endif + +} // end namespace Catch + +// #included from: catch_result_builder.hpp +#define TWOBLUECUBES_CATCH_RESULT_BUILDER_HPP_INCLUDED + +namespace Catch { + + std::string capturedExpressionWithSecondArgument( std::string const& capturedExpression, std::string const& secondArg ) { + return secondArg.empty() || secondArg == "\"\"" + ? capturedExpression + : capturedExpression + ", " + secondArg; + } + ResultBuilder::ResultBuilder( char const* macroName, + SourceLineInfo const& lineInfo, + char const* capturedExpression, + ResultDisposition::Flags resultDisposition, + char const* secondArg ) + : m_assertionInfo( macroName, lineInfo, capturedExpressionWithSecondArgument( capturedExpression, secondArg ), resultDisposition ), + m_shouldDebugBreak( false ), + m_shouldThrow( false ) + {} + + ResultBuilder& ResultBuilder::setResultType( ResultWas::OfType result ) { + m_data.resultType = result; + return *this; + } + ResultBuilder& ResultBuilder::setResultType( bool result ) { + m_data.resultType = result ? ResultWas::Ok : ResultWas::ExpressionFailed; + return *this; + } + ResultBuilder& ResultBuilder::setLhs( std::string const& lhs ) { + m_exprComponents.lhs = lhs; + return *this; + } + ResultBuilder& ResultBuilder::setRhs( std::string const& rhs ) { + m_exprComponents.rhs = rhs; + return *this; + } + ResultBuilder& ResultBuilder::setOp( std::string const& op ) { + m_exprComponents.op = op; + return *this; + } + + void ResultBuilder::endExpression() { + m_exprComponents.testFalse = isFalseTest( m_assertionInfo.resultDisposition ); + captureExpression(); + } + + void ResultBuilder::useActiveException( ResultDisposition::Flags resultDisposition ) { + m_assertionInfo.resultDisposition = resultDisposition; + m_stream.oss << Catch::translateActiveException(); + captureResult( ResultWas::ThrewException ); + } + + void ResultBuilder::captureResult( ResultWas::OfType resultType ) { + setResultType( resultType ); + captureExpression(); + } + void ResultBuilder::captureExpectedException( std::string const& expectedMessage ) { + if( expectedMessage.empty() ) + captureExpectedException( Matchers::Impl::Generic::AllOf() ); + else + captureExpectedException( Matchers::Equals( expectedMessage ) ); + } + + void ResultBuilder::captureExpectedException( Matchers::Impl::Matcher const& matcher ) { + + assert( m_exprComponents.testFalse == false ); + AssertionResultData data = m_data; + data.resultType = ResultWas::Ok; + data.reconstructedExpression = m_assertionInfo.capturedExpression; + + std::string actualMessage = Catch::translateActiveException(); + if( !matcher.match( actualMessage ) ) { + data.resultType = ResultWas::ExpressionFailed; + data.reconstructedExpression = actualMessage; + } + AssertionResult result( m_assertionInfo, data ); + handleResult( result ); + } + + void ResultBuilder::captureExpression() { + AssertionResult result = build(); + handleResult( result ); + } + void ResultBuilder::handleResult( AssertionResult const& result ) + { + getResultCapture().assertionEnded( result ); + + if( !result.isOk() ) { + if( getCurrentContext().getConfig()->shouldDebugBreak() ) + m_shouldDebugBreak = true; + if( getCurrentContext().getRunner()->aborting() || (m_assertionInfo.resultDisposition & ResultDisposition::Normal) ) + m_shouldThrow = true; + } + } + void ResultBuilder::react() { + if( m_shouldThrow ) + throw Catch::TestFailureException(); + } + + bool ResultBuilder::shouldDebugBreak() const { return m_shouldDebugBreak; } + bool ResultBuilder::allowThrows() const { return getCurrentContext().getConfig()->allowThrows(); } + + AssertionResult ResultBuilder::build() const + { + assert( m_data.resultType != ResultWas::Unknown ); + + AssertionResultData data = m_data; + + // Flip bool results if testFalse is set + if( m_exprComponents.testFalse ) { + if( data.resultType == ResultWas::Ok ) + data.resultType = ResultWas::ExpressionFailed; + else if( data.resultType == ResultWas::ExpressionFailed ) + data.resultType = ResultWas::Ok; + } + + data.message = m_stream.oss.str(); + data.reconstructedExpression = reconstructExpression(); + if( m_exprComponents.testFalse ) { + if( m_exprComponents.op == "" ) + data.reconstructedExpression = "!" + data.reconstructedExpression; + else + data.reconstructedExpression = "!(" + data.reconstructedExpression + ")"; + } + return AssertionResult( m_assertionInfo, data ); + } + std::string ResultBuilder::reconstructExpression() const { + if( m_exprComponents.op == "" ) + return m_exprComponents.lhs.empty() ? m_assertionInfo.capturedExpression : m_exprComponents.op + m_exprComponents.lhs; + else if( m_exprComponents.op == "matches" ) + return m_exprComponents.lhs + " " + m_exprComponents.rhs; + else if( m_exprComponents.op != "!" ) { + if( m_exprComponents.lhs.size() + m_exprComponents.rhs.size() < 40 && + m_exprComponents.lhs.find("\n") == std::string::npos && + m_exprComponents.rhs.find("\n") == std::string::npos ) + return m_exprComponents.lhs + " " + m_exprComponents.op + " " + m_exprComponents.rhs; + else + return m_exprComponents.lhs + "\n" + m_exprComponents.op + "\n" + m_exprComponents.rhs; + } + else + return "{can't expand - use " + m_assertionInfo.macroName + "_FALSE( " + m_assertionInfo.capturedExpression.substr(1) + " ) instead of " + m_assertionInfo.macroName + "( " + m_assertionInfo.capturedExpression + " ) for better diagnostics}"; + } + +} // end namespace Catch + +// #included from: catch_tag_alias_registry.hpp +#define TWOBLUECUBES_CATCH_TAG_ALIAS_REGISTRY_HPP_INCLUDED + +// #included from: catch_tag_alias_registry.h +#define TWOBLUECUBES_CATCH_TAG_ALIAS_REGISTRY_H_INCLUDED + +#include + +namespace Catch { + + class TagAliasRegistry : public ITagAliasRegistry { + public: + virtual ~TagAliasRegistry(); + virtual Option find( std::string const& alias ) const; + virtual std::string expandAliases( std::string const& unexpandedTestSpec ) const; + void add( char const* alias, char const* tag, SourceLineInfo const& lineInfo ); + static TagAliasRegistry& get(); + + private: + std::map m_registry; + }; + +} // end namespace Catch + +#include +#include + +namespace Catch { + + TagAliasRegistry::~TagAliasRegistry() {} + + Option TagAliasRegistry::find( std::string const& alias ) const { + std::map::const_iterator it = m_registry.find( alias ); + if( it != m_registry.end() ) + return it->second; + else + return Option(); + } + + std::string TagAliasRegistry::expandAliases( std::string const& unexpandedTestSpec ) const { + std::string expandedTestSpec = unexpandedTestSpec; + for( std::map::const_iterator it = m_registry.begin(), itEnd = m_registry.end(); + it != itEnd; + ++it ) { + std::size_t pos = expandedTestSpec.find( it->first ); + if( pos != std::string::npos ) { + expandedTestSpec = expandedTestSpec.substr( 0, pos ) + + it->second.tag + + expandedTestSpec.substr( pos + it->first.size() ); + } + } + return expandedTestSpec; + } + + void TagAliasRegistry::add( char const* alias, char const* tag, SourceLineInfo const& lineInfo ) { + + if( !startsWith( alias, "[@" ) || !endsWith( alias, "]" ) ) { + std::ostringstream oss; + oss << "error: tag alias, \"" << alias << "\" is not of the form [@alias name].\n" << lineInfo; + throw std::domain_error( oss.str().c_str() ); + } + if( !m_registry.insert( std::make_pair( alias, TagAlias( tag, lineInfo ) ) ).second ) { + std::ostringstream oss; + oss << "error: tag alias, \"" << alias << "\" already registered.\n" + << "\tFirst seen at " << find(alias)->lineInfo << "\n" + << "\tRedefined at " << lineInfo; + throw std::domain_error( oss.str().c_str() ); + } + } + + TagAliasRegistry& TagAliasRegistry::get() { + static TagAliasRegistry instance; + return instance; + + } + + ITagAliasRegistry::~ITagAliasRegistry() {} + ITagAliasRegistry const& ITagAliasRegistry::get() { return TagAliasRegistry::get(); } + + RegistrarForTagAliases::RegistrarForTagAliases( char const* alias, char const* tag, SourceLineInfo const& lineInfo ) { + try { + TagAliasRegistry::get().add( alias, tag, lineInfo ); + } + catch( std::exception& ex ) { + Colour colourGuard( Colour::Red ); + Catch::cerr() << ex.what() << std::endl; + exit(1); + } + } + +} // end namespace Catch + +// #included from: ../reporters/catch_reporter_multi.hpp +#define TWOBLUECUBES_CATCH_REPORTER_MULTI_HPP_INCLUDED + +namespace Catch { + +class MultipleReporters : public SharedImpl { + typedef std::vector > Reporters; + Reporters m_reporters; + +public: + void add( Ptr const& reporter ) { + m_reporters.push_back( reporter ); + } + +public: // IStreamingReporter + + virtual ReporterPreferences getPreferences() const CATCH_OVERRIDE { + return m_reporters[0]->getPreferences(); + } + + virtual void noMatchingTestCases( std::string const& spec ) CATCH_OVERRIDE { + for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end(); + it != itEnd; + ++it ) + (*it)->noMatchingTestCases( spec ); + } + + virtual void testRunStarting( TestRunInfo const& testRunInfo ) CATCH_OVERRIDE { + for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end(); + it != itEnd; + ++it ) + (*it)->testRunStarting( testRunInfo ); + } + + virtual void testGroupStarting( GroupInfo const& groupInfo ) CATCH_OVERRIDE { + for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end(); + it != itEnd; + ++it ) + (*it)->testGroupStarting( groupInfo ); + } + + virtual void testCaseStarting( TestCaseInfo const& testInfo ) CATCH_OVERRIDE { + for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end(); + it != itEnd; + ++it ) + (*it)->testCaseStarting( testInfo ); + } + + virtual void sectionStarting( SectionInfo const& sectionInfo ) CATCH_OVERRIDE { + for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end(); + it != itEnd; + ++it ) + (*it)->sectionStarting( sectionInfo ); + } + + virtual void assertionStarting( AssertionInfo const& assertionInfo ) CATCH_OVERRIDE { + for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end(); + it != itEnd; + ++it ) + (*it)->assertionStarting( assertionInfo ); + } + + // The return value indicates if the messages buffer should be cleared: + virtual bool assertionEnded( AssertionStats const& assertionStats ) CATCH_OVERRIDE { + bool clearBuffer = false; + for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end(); + it != itEnd; + ++it ) + clearBuffer |= (*it)->assertionEnded( assertionStats ); + return clearBuffer; + } + + virtual void sectionEnded( SectionStats const& sectionStats ) CATCH_OVERRIDE { + for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end(); + it != itEnd; + ++it ) + (*it)->sectionEnded( sectionStats ); + } + + virtual void testCaseEnded( TestCaseStats const& testCaseStats ) CATCH_OVERRIDE { + for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end(); + it != itEnd; + ++it ) + (*it)->testCaseEnded( testCaseStats ); + } + + virtual void testGroupEnded( TestGroupStats const& testGroupStats ) CATCH_OVERRIDE { + for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end(); + it != itEnd; + ++it ) + (*it)->testGroupEnded( testGroupStats ); + } + + virtual void testRunEnded( TestRunStats const& testRunStats ) CATCH_OVERRIDE { + for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end(); + it != itEnd; + ++it ) + (*it)->testRunEnded( testRunStats ); + } + + virtual void skipTest( TestCaseInfo const& testInfo ) CATCH_OVERRIDE { + for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end(); + it != itEnd; + ++it ) + (*it)->skipTest( testInfo ); + } +}; + +Ptr addReporter( Ptr const& existingReporter, Ptr const& additionalReporter ) { + Ptr resultingReporter; + + if( existingReporter ) { + MultipleReporters* multi = dynamic_cast( existingReporter.get() ); + if( !multi ) { + multi = new MultipleReporters; + resultingReporter = Ptr( multi ); + if( existingReporter ) + multi->add( existingReporter ); + } + else + resultingReporter = existingReporter; + multi->add( additionalReporter ); + } + else + resultingReporter = additionalReporter; + + return resultingReporter; +} + +} // end namespace Catch + +// #included from: ../reporters/catch_reporter_xml.hpp +#define TWOBLUECUBES_CATCH_REPORTER_XML_HPP_INCLUDED + +// #included from: catch_reporter_bases.hpp +#define TWOBLUECUBES_CATCH_REPORTER_BASES_HPP_INCLUDED + +#include + +namespace Catch { + + struct StreamingReporterBase : SharedImpl { + + StreamingReporterBase( ReporterConfig const& _config ) + : m_config( _config.fullConfig() ), + stream( _config.stream() ) + { + m_reporterPrefs.shouldRedirectStdOut = false; + } + + virtual ReporterPreferences getPreferences() const CATCH_OVERRIDE { + return m_reporterPrefs; + } + + virtual ~StreamingReporterBase() CATCH_OVERRIDE; + + virtual void noMatchingTestCases( std::string const& ) CATCH_OVERRIDE {} + + virtual void testRunStarting( TestRunInfo const& _testRunInfo ) CATCH_OVERRIDE { + currentTestRunInfo = _testRunInfo; + } + virtual void testGroupStarting( GroupInfo const& _groupInfo ) CATCH_OVERRIDE { + currentGroupInfo = _groupInfo; + } + + virtual void testCaseStarting( TestCaseInfo const& _testInfo ) CATCH_OVERRIDE { + currentTestCaseInfo = _testInfo; + } + virtual void sectionStarting( SectionInfo const& _sectionInfo ) CATCH_OVERRIDE { + m_sectionStack.push_back( _sectionInfo ); + } + + virtual void sectionEnded( SectionStats const& /* _sectionStats */ ) CATCH_OVERRIDE { + m_sectionStack.pop_back(); + } + virtual void testCaseEnded( TestCaseStats const& /* _testCaseStats */ ) CATCH_OVERRIDE { + currentTestCaseInfo.reset(); + } + virtual void testGroupEnded( TestGroupStats const& /* _testGroupStats */ ) CATCH_OVERRIDE { + currentGroupInfo.reset(); + } + virtual void testRunEnded( TestRunStats const& /* _testRunStats */ ) CATCH_OVERRIDE { + currentTestCaseInfo.reset(); + currentGroupInfo.reset(); + currentTestRunInfo.reset(); + } + + virtual void skipTest( TestCaseInfo const& ) CATCH_OVERRIDE { + // Don't do anything with this by default. + // It can optionally be overridden in the derived class. + } + + Ptr m_config; + std::ostream& stream; + + LazyStat currentTestRunInfo; + LazyStat currentGroupInfo; + LazyStat currentTestCaseInfo; + + std::vector m_sectionStack; + ReporterPreferences m_reporterPrefs; + }; + + struct CumulativeReporterBase : SharedImpl { + template + struct Node : SharedImpl<> { + explicit Node( T const& _value ) : value( _value ) {} + virtual ~Node() {} + + typedef std::vector > ChildNodes; + T value; + ChildNodes children; + }; + struct SectionNode : SharedImpl<> { + explicit SectionNode( SectionStats const& _stats ) : stats( _stats ) {} + virtual ~SectionNode(); + + bool operator == ( SectionNode const& other ) const { + return stats.sectionInfo.lineInfo == other.stats.sectionInfo.lineInfo; + } + bool operator == ( Ptr const& other ) const { + return operator==( *other ); + } + + SectionStats stats; + typedef std::vector > ChildSections; + typedef std::vector Assertions; + ChildSections childSections; + Assertions assertions; + std::string stdOut; + std::string stdErr; + }; + + struct BySectionInfo { + BySectionInfo( SectionInfo const& other ) : m_other( other ) {} + BySectionInfo( BySectionInfo const& other ) : m_other( other.m_other ) {} + bool operator() ( Ptr const& node ) const { + return node->stats.sectionInfo.lineInfo == m_other.lineInfo; + } + private: + void operator=( BySectionInfo const& ); + SectionInfo const& m_other; + }; + + typedef Node TestCaseNode; + typedef Node TestGroupNode; + typedef Node TestRunNode; + + CumulativeReporterBase( ReporterConfig const& _config ) + : m_config( _config.fullConfig() ), + stream( _config.stream() ) + { + m_reporterPrefs.shouldRedirectStdOut = false; + } + ~CumulativeReporterBase(); + + virtual ReporterPreferences getPreferences() const CATCH_OVERRIDE { + return m_reporterPrefs; + } + + virtual void testRunStarting( TestRunInfo const& ) CATCH_OVERRIDE {} + virtual void testGroupStarting( GroupInfo const& ) CATCH_OVERRIDE {} + + virtual void testCaseStarting( TestCaseInfo const& ) CATCH_OVERRIDE {} + + virtual void sectionStarting( SectionInfo const& sectionInfo ) CATCH_OVERRIDE { + SectionStats incompleteStats( sectionInfo, Counts(), 0, false ); + Ptr node; + if( m_sectionStack.empty() ) { + if( !m_rootSection ) + m_rootSection = new SectionNode( incompleteStats ); + node = m_rootSection; + } + else { + SectionNode& parentNode = *m_sectionStack.back(); + SectionNode::ChildSections::const_iterator it = + std::find_if( parentNode.childSections.begin(), + parentNode.childSections.end(), + BySectionInfo( sectionInfo ) ); + if( it == parentNode.childSections.end() ) { + node = new SectionNode( incompleteStats ); + parentNode.childSections.push_back( node ); + } + else + node = *it; + } + m_sectionStack.push_back( node ); + m_deepestSection = node; + } + + virtual void assertionStarting( AssertionInfo const& ) CATCH_OVERRIDE {} + + virtual bool assertionEnded( AssertionStats const& assertionStats ) { + assert( !m_sectionStack.empty() ); + SectionNode& sectionNode = *m_sectionStack.back(); + sectionNode.assertions.push_back( assertionStats ); + return true; + } + virtual void sectionEnded( SectionStats const& sectionStats ) CATCH_OVERRIDE { + assert( !m_sectionStack.empty() ); + SectionNode& node = *m_sectionStack.back(); + node.stats = sectionStats; + m_sectionStack.pop_back(); + } + virtual void testCaseEnded( TestCaseStats const& testCaseStats ) CATCH_OVERRIDE { + Ptr node = new TestCaseNode( testCaseStats ); + assert( m_sectionStack.size() == 0 ); + node->children.push_back( m_rootSection ); + m_testCases.push_back( node ); + m_rootSection.reset(); + + assert( m_deepestSection ); + m_deepestSection->stdOut = testCaseStats.stdOut; + m_deepestSection->stdErr = testCaseStats.stdErr; + } + virtual void testGroupEnded( TestGroupStats const& testGroupStats ) CATCH_OVERRIDE { + Ptr node = new TestGroupNode( testGroupStats ); + node->children.swap( m_testCases ); + m_testGroups.push_back( node ); + } + virtual void testRunEnded( TestRunStats const& testRunStats ) CATCH_OVERRIDE { + Ptr node = new TestRunNode( testRunStats ); + node->children.swap( m_testGroups ); + m_testRuns.push_back( node ); + testRunEndedCumulative(); + } + virtual void testRunEndedCumulative() = 0; + + virtual void skipTest( TestCaseInfo const& ) CATCH_OVERRIDE {} + + Ptr m_config; + std::ostream& stream; + std::vector m_assertions; + std::vector > > m_sections; + std::vector > m_testCases; + std::vector > m_testGroups; + + std::vector > m_testRuns; + + Ptr m_rootSection; + Ptr m_deepestSection; + std::vector > m_sectionStack; + ReporterPreferences m_reporterPrefs; + + }; + + template + char const* getLineOfChars() { + static char line[CATCH_CONFIG_CONSOLE_WIDTH] = {0}; + if( !*line ) { + memset( line, C, CATCH_CONFIG_CONSOLE_WIDTH-1 ); + line[CATCH_CONFIG_CONSOLE_WIDTH-1] = 0; + } + return line; + } + + struct TestEventListenerBase : StreamingReporterBase { + TestEventListenerBase( ReporterConfig const& _config ) + : StreamingReporterBase( _config ) + {} + + virtual void assertionStarting( AssertionInfo const& ) CATCH_OVERRIDE {} + virtual bool assertionEnded( AssertionStats const& ) CATCH_OVERRIDE { + return false; + } + }; + +} // end namespace Catch + +// #included from: ../internal/catch_reporter_registrars.hpp +#define TWOBLUECUBES_CATCH_REPORTER_REGISTRARS_HPP_INCLUDED + +namespace Catch { + + template + class LegacyReporterRegistrar { + + class ReporterFactory : public IReporterFactory { + virtual IStreamingReporter* create( ReporterConfig const& config ) const { + return new LegacyReporterAdapter( new T( config ) ); + } + + virtual std::string getDescription() const { + return T::getDescription(); + } + }; + + public: + + LegacyReporterRegistrar( std::string const& name ) { + getMutableRegistryHub().registerReporter( name, new ReporterFactory() ); + } + }; + + template + class ReporterRegistrar { + + class ReporterFactory : public SharedImpl { + + // *** Please Note ***: + // - If you end up here looking at a compiler error because it's trying to register + // your custom reporter class be aware that the native reporter interface has changed + // to IStreamingReporter. The "legacy" interface, IReporter, is still supported via + // an adapter. Just use REGISTER_LEGACY_REPORTER to take advantage of the adapter. + // However please consider updating to the new interface as the old one is now + // deprecated and will probably be removed quite soon! + // Please contact me via github if you have any questions at all about this. + // In fact, ideally, please contact me anyway to let me know you've hit this - as I have + // no idea who is actually using custom reporters at all (possibly no-one!). + // The new interface is designed to minimise exposure to interface changes in the future. + virtual IStreamingReporter* create( ReporterConfig const& config ) const { + return new T( config ); + } + + virtual std::string getDescription() const { + return T::getDescription(); + } + }; + + public: + + ReporterRegistrar( std::string const& name ) { + getMutableRegistryHub().registerReporter( name, new ReporterFactory() ); + } + }; + + template + class ListenerRegistrar { + + class ListenerFactory : public SharedImpl { + + virtual IStreamingReporter* create( ReporterConfig const& config ) const { + return new T( config ); + } + virtual std::string getDescription() const { + return ""; + } + }; + + public: + + ListenerRegistrar() { + getMutableRegistryHub().registerListener( new ListenerFactory() ); + } + }; +} + +#define INTERNAL_CATCH_REGISTER_LEGACY_REPORTER( name, reporterType ) \ + namespace{ Catch::LegacyReporterRegistrar catch_internal_RegistrarFor##reporterType( name ); } + +#define INTERNAL_CATCH_REGISTER_REPORTER( name, reporterType ) \ + namespace{ Catch::ReporterRegistrar catch_internal_RegistrarFor##reporterType( name ); } + +#define INTERNAL_CATCH_REGISTER_LISTENER( listenerType ) \ + namespace{ Catch::ListenerRegistrar catch_internal_RegistrarFor##listenerType; } + +// #included from: ../internal/catch_xmlwriter.hpp +#define TWOBLUECUBES_CATCH_XMLWRITER_HPP_INCLUDED + +#include +#include +#include +#include + +namespace Catch { + + class XmlEncode { + public: + enum ForWhat { ForTextNodes, ForAttributes }; + + XmlEncode( std::string const& str, ForWhat forWhat = ForTextNodes ) + : m_str( str ), + m_forWhat( forWhat ) + {} + + void encodeTo( std::ostream& os ) const { + + // Apostrophe escaping not necessary if we always use " to write attributes + // (see: http://www.w3.org/TR/xml/#syntax) + + for( std::size_t i = 0; i < m_str.size(); ++ i ) { + char c = m_str[i]; + switch( c ) { + case '<': os << "<"; break; + case '&': os << "&"; break; + + case '>': + // See: http://www.w3.org/TR/xml/#syntax + if( i > 2 && m_str[i-1] == ']' && m_str[i-2] == ']' ) + os << ">"; + else + os << c; + break; + + case '\"': + if( m_forWhat == ForAttributes ) + os << """; + else + os << c; + break; + + default: + // Escape control chars - based on contribution by @espenalb in PR #465 + if ( ( c < '\x09' ) || ( c > '\x0D' && c < '\x20') || c=='\x7F' ) + os << "&#x" << std::uppercase << std::hex << static_cast( c ); + else + os << c; + } + } + } + + friend std::ostream& operator << ( std::ostream& os, XmlEncode const& xmlEncode ) { + xmlEncode.encodeTo( os ); + return os; + } + + private: + std::string m_str; + ForWhat m_forWhat; + }; + + class XmlWriter { + public: + + class ScopedElement { + public: + ScopedElement( XmlWriter* writer ) + : m_writer( writer ) + {} + + ScopedElement( ScopedElement const& other ) + : m_writer( other.m_writer ){ + other.m_writer = CATCH_NULL; + } + + ~ScopedElement() { + if( m_writer ) + m_writer->endElement(); + } + + ScopedElement& writeText( std::string const& text, bool indent = true ) { + m_writer->writeText( text, indent ); + return *this; + } + + template + ScopedElement& writeAttribute( std::string const& name, T const& attribute ) { + m_writer->writeAttribute( name, attribute ); + return *this; + } + + private: + mutable XmlWriter* m_writer; + }; + + XmlWriter() + : m_tagIsOpen( false ), + m_needsNewline( false ), + m_os( &Catch::cout() ) + {} + + XmlWriter( std::ostream& os ) + : m_tagIsOpen( false ), + m_needsNewline( false ), + m_os( &os ) + {} + + ~XmlWriter() { + while( !m_tags.empty() ) + endElement(); + } + + XmlWriter& startElement( std::string const& name ) { + ensureTagClosed(); + newlineIfNecessary(); + stream() << m_indent << "<" << name; + m_tags.push_back( name ); + m_indent += " "; + m_tagIsOpen = true; + return *this; + } + + ScopedElement scopedElement( std::string const& name ) { + ScopedElement scoped( this ); + startElement( name ); + return scoped; + } + + XmlWriter& endElement() { + newlineIfNecessary(); + m_indent = m_indent.substr( 0, m_indent.size()-2 ); + if( m_tagIsOpen ) { + stream() << "/>\n"; + m_tagIsOpen = false; + } + else { + stream() << m_indent << "\n"; + } + m_tags.pop_back(); + return *this; + } + + XmlWriter& writeAttribute( std::string const& name, std::string const& attribute ) { + if( !name.empty() && !attribute.empty() ) + stream() << " " << name << "=\"" << XmlEncode( attribute, XmlEncode::ForAttributes ) << "\""; + return *this; + } + + XmlWriter& writeAttribute( std::string const& name, bool attribute ) { + stream() << " " << name << "=\"" << ( attribute ? "true" : "false" ) << "\""; + return *this; + } + + template + XmlWriter& writeAttribute( std::string const& name, T const& attribute ) { + std::ostringstream oss; + oss << attribute; + return writeAttribute( name, oss.str() ); + } + + XmlWriter& writeText( std::string const& text, bool indent = true ) { + if( !text.empty() ){ + bool tagWasOpen = m_tagIsOpen; + ensureTagClosed(); + if( tagWasOpen && indent ) + stream() << m_indent; + stream() << XmlEncode( text ); + m_needsNewline = true; + } + return *this; + } + + XmlWriter& writeComment( std::string const& text ) { + ensureTagClosed(); + stream() << m_indent << ""; + m_needsNewline = true; + return *this; + } + + XmlWriter& writeBlankLine() { + ensureTagClosed(); + stream() << "\n"; + return *this; + } + + void setStream( std::ostream& os ) { + m_os = &os; + } + + private: + XmlWriter( XmlWriter const& ); + void operator=( XmlWriter const& ); + + std::ostream& stream() { + return *m_os; + } + + void ensureTagClosed() { + if( m_tagIsOpen ) { + stream() << ">\n"; + m_tagIsOpen = false; + } + } + + void newlineIfNecessary() { + if( m_needsNewline ) { + stream() << "\n"; + m_needsNewline = false; + } + } + + bool m_tagIsOpen; + bool m_needsNewline; + std::vector m_tags; + std::string m_indent; + std::ostream* m_os; + }; + +} +// #included from: catch_reenable_warnings.h + +#define TWOBLUECUBES_CATCH_REENABLE_WARNINGS_H_INCLUDED + +#ifdef __clang__ +# ifdef __ICC // icpc defines the __clang__ macro +# pragma warning(pop) +# else +# pragma clang diagnostic pop +# endif +#elif defined __GNUC__ +# pragma GCC diagnostic pop +#endif + + +namespace Catch { + class XmlReporter : public StreamingReporterBase { + public: + XmlReporter( ReporterConfig const& _config ) + : StreamingReporterBase( _config ), + m_sectionDepth( 0 ) + { + m_reporterPrefs.shouldRedirectStdOut = true; + } + + virtual ~XmlReporter() CATCH_OVERRIDE; + + static std::string getDescription() { + return "Reports test results as an XML document"; + } + + public: // StreamingReporterBase + + virtual void noMatchingTestCases( std::string const& s ) CATCH_OVERRIDE { + StreamingReporterBase::noMatchingTestCases( s ); + } + + virtual void testRunStarting( TestRunInfo const& testInfo ) CATCH_OVERRIDE { + StreamingReporterBase::testRunStarting( testInfo ); + m_xml.setStream( stream ); + m_xml.startElement( "Catch" ); + if( !m_config->name().empty() ) + m_xml.writeAttribute( "name", m_config->name() ); + } + + virtual void testGroupStarting( GroupInfo const& groupInfo ) CATCH_OVERRIDE { + StreamingReporterBase::testGroupStarting( groupInfo ); + m_xml.startElement( "Group" ) + .writeAttribute( "name", groupInfo.name ); + } + + virtual void testCaseStarting( TestCaseInfo const& testInfo ) CATCH_OVERRIDE { + StreamingReporterBase::testCaseStarting(testInfo); + m_xml.startElement( "TestCase" ).writeAttribute( "name", trim( testInfo.name ) ); + + if ( m_config->showDurations() == ShowDurations::Always ) + m_testCaseTimer.start(); + } + + virtual void sectionStarting( SectionInfo const& sectionInfo ) CATCH_OVERRIDE { + StreamingReporterBase::sectionStarting( sectionInfo ); + if( m_sectionDepth++ > 0 ) { + m_xml.startElement( "Section" ) + .writeAttribute( "name", trim( sectionInfo.name ) ) + .writeAttribute( "description", sectionInfo.description ); + } + } + + virtual void assertionStarting( AssertionInfo const& ) CATCH_OVERRIDE { } + + virtual bool assertionEnded( AssertionStats const& assertionStats ) CATCH_OVERRIDE { + const AssertionResult& assertionResult = assertionStats.assertionResult; + + // Print any info messages in tags. + if( assertionStats.assertionResult.getResultType() != ResultWas::Ok ) { + for( std::vector::const_iterator it = assertionStats.infoMessages.begin(), itEnd = assertionStats.infoMessages.end(); + it != itEnd; + ++it ) { + if( it->type == ResultWas::Info ) { + m_xml.scopedElement( "Info" ) + .writeText( it->message ); + } else if ( it->type == ResultWas::Warning ) { + m_xml.scopedElement( "Warning" ) + .writeText( it->message ); + } + } + } + + // Drop out if result was successful but we're not printing them. + if( !m_config->includeSuccessfulResults() && isOk(assertionResult.getResultType()) ) + return true; + + // Print the expression if there is one. + if( assertionResult.hasExpression() ) { + m_xml.startElement( "Expression" ) + .writeAttribute( "success", assertionResult.succeeded() ) + .writeAttribute( "type", assertionResult.getTestMacroName() ) + .writeAttribute( "filename", assertionResult.getSourceInfo().file ) + .writeAttribute( "line", assertionResult.getSourceInfo().line ); + + m_xml.scopedElement( "Original" ) + .writeText( assertionResult.getExpression() ); + m_xml.scopedElement( "Expanded" ) + .writeText( assertionResult.getExpandedExpression() ); + } + + // And... Print a result applicable to each result type. + switch( assertionResult.getResultType() ) { + case ResultWas::ThrewException: + m_xml.scopedElement( "Exception" ) + .writeAttribute( "filename", assertionResult.getSourceInfo().file ) + .writeAttribute( "line", assertionResult.getSourceInfo().line ) + .writeText( assertionResult.getMessage() ); + break; + case ResultWas::FatalErrorCondition: + m_xml.scopedElement( "Fatal Error Condition" ) + .writeAttribute( "filename", assertionResult.getSourceInfo().file ) + .writeAttribute( "line", assertionResult.getSourceInfo().line ) + .writeText( assertionResult.getMessage() ); + break; + case ResultWas::Info: + m_xml.scopedElement( "Info" ) + .writeText( assertionResult.getMessage() ); + break; + case ResultWas::Warning: + // Warning will already have been written + break; + case ResultWas::ExplicitFailure: + m_xml.scopedElement( "Failure" ) + .writeText( assertionResult.getMessage() ); + break; + default: + break; + } + + if( assertionResult.hasExpression() ) + m_xml.endElement(); + + return true; + } + + virtual void sectionEnded( SectionStats const& sectionStats ) CATCH_OVERRIDE { + StreamingReporterBase::sectionEnded( sectionStats ); + if( --m_sectionDepth > 0 ) { + XmlWriter::ScopedElement e = m_xml.scopedElement( "OverallResults" ); + e.writeAttribute( "successes", sectionStats.assertions.passed ); + e.writeAttribute( "failures", sectionStats.assertions.failed ); + e.writeAttribute( "expectedFailures", sectionStats.assertions.failedButOk ); + + if ( m_config->showDurations() == ShowDurations::Always ) + e.writeAttribute( "durationInSeconds", sectionStats.durationInSeconds ); + + m_xml.endElement(); + } + } + + virtual void testCaseEnded( TestCaseStats const& testCaseStats ) CATCH_OVERRIDE { + StreamingReporterBase::testCaseEnded( testCaseStats ); + XmlWriter::ScopedElement e = m_xml.scopedElement( "OverallResult" ); + e.writeAttribute( "success", testCaseStats.totals.assertions.allOk() ); + + if ( m_config->showDurations() == ShowDurations::Always ) + e.writeAttribute( "durationInSeconds", m_testCaseTimer.getElapsedSeconds() ); + + m_xml.endElement(); + } + + virtual void testGroupEnded( TestGroupStats const& testGroupStats ) CATCH_OVERRIDE { + StreamingReporterBase::testGroupEnded( testGroupStats ); + // TODO: Check testGroupStats.aborting and act accordingly. + m_xml.scopedElement( "OverallResults" ) + .writeAttribute( "successes", testGroupStats.totals.assertions.passed ) + .writeAttribute( "failures", testGroupStats.totals.assertions.failed ) + .writeAttribute( "expectedFailures", testGroupStats.totals.assertions.failedButOk ); + m_xml.endElement(); + } + + virtual void testRunEnded( TestRunStats const& testRunStats ) CATCH_OVERRIDE { + StreamingReporterBase::testRunEnded( testRunStats ); + m_xml.scopedElement( "OverallResults" ) + .writeAttribute( "successes", testRunStats.totals.assertions.passed ) + .writeAttribute( "failures", testRunStats.totals.assertions.failed ) + .writeAttribute( "expectedFailures", testRunStats.totals.assertions.failedButOk ); + m_xml.endElement(); + } + + private: + Timer m_testCaseTimer; + XmlWriter m_xml; + int m_sectionDepth; + }; + + INTERNAL_CATCH_REGISTER_REPORTER( "xml", XmlReporter ) + +} // end namespace Catch + +// #included from: ../reporters/catch_reporter_junit.hpp +#define TWOBLUECUBES_CATCH_REPORTER_JUNIT_HPP_INCLUDED + +#include + +namespace Catch { + + class JunitReporter : public CumulativeReporterBase { + public: + JunitReporter( ReporterConfig const& _config ) + : CumulativeReporterBase( _config ), + xml( _config.stream() ) + { + m_reporterPrefs.shouldRedirectStdOut = true; + } + + virtual ~JunitReporter() CATCH_OVERRIDE; + + static std::string getDescription() { + return "Reports test results in an XML format that looks like Ant's junitreport target"; + } + + virtual void noMatchingTestCases( std::string const& /*spec*/ ) CATCH_OVERRIDE {} + + virtual void testRunStarting( TestRunInfo const& runInfo ) CATCH_OVERRIDE { + CumulativeReporterBase::testRunStarting( runInfo ); + xml.startElement( "testsuites" ); + } + + virtual void testGroupStarting( GroupInfo const& groupInfo ) CATCH_OVERRIDE { + suiteTimer.start(); + stdOutForSuite.str(""); + stdErrForSuite.str(""); + unexpectedExceptions = 0; + CumulativeReporterBase::testGroupStarting( groupInfo ); + } + + virtual bool assertionEnded( AssertionStats const& assertionStats ) CATCH_OVERRIDE { + if( assertionStats.assertionResult.getResultType() == ResultWas::ThrewException ) + unexpectedExceptions++; + return CumulativeReporterBase::assertionEnded( assertionStats ); + } + + virtual void testCaseEnded( TestCaseStats const& testCaseStats ) CATCH_OVERRIDE { + stdOutForSuite << testCaseStats.stdOut; + stdErrForSuite << testCaseStats.stdErr; + CumulativeReporterBase::testCaseEnded( testCaseStats ); + } + + virtual void testGroupEnded( TestGroupStats const& testGroupStats ) CATCH_OVERRIDE { + double suiteTime = suiteTimer.getElapsedSeconds(); + CumulativeReporterBase::testGroupEnded( testGroupStats ); + writeGroup( *m_testGroups.back(), suiteTime ); + } + + virtual void testRunEndedCumulative() CATCH_OVERRIDE { + xml.endElement(); + } + + void writeGroup( TestGroupNode const& groupNode, double suiteTime ) { + XmlWriter::ScopedElement e = xml.scopedElement( "testsuite" ); + TestGroupStats const& stats = groupNode.value; + xml.writeAttribute( "name", stats.groupInfo.name ); + xml.writeAttribute( "errors", unexpectedExceptions ); + xml.writeAttribute( "failures", stats.totals.assertions.failed-unexpectedExceptions ); + xml.writeAttribute( "tests", stats.totals.assertions.total() ); + xml.writeAttribute( "hostname", "tbd" ); // !TBD + if( m_config->showDurations() == ShowDurations::Never ) + xml.writeAttribute( "time", "" ); + else + xml.writeAttribute( "time", suiteTime ); + xml.writeAttribute( "timestamp", "tbd" ); // !TBD + + // Write test cases + for( TestGroupNode::ChildNodes::const_iterator + it = groupNode.children.begin(), itEnd = groupNode.children.end(); + it != itEnd; + ++it ) + writeTestCase( **it ); + + xml.scopedElement( "system-out" ).writeText( trim( stdOutForSuite.str() ), false ); + xml.scopedElement( "system-err" ).writeText( trim( stdErrForSuite.str() ), false ); + } + + void writeTestCase( TestCaseNode const& testCaseNode ) { + TestCaseStats const& stats = testCaseNode.value; + + // All test cases have exactly one section - which represents the + // test case itself. That section may have 0-n nested sections + assert( testCaseNode.children.size() == 1 ); + SectionNode const& rootSection = *testCaseNode.children.front(); + + std::string className = stats.testInfo.className; + + if( className.empty() ) { + if( rootSection.childSections.empty() ) + className = "global"; + } + writeSection( className, "", rootSection ); + } + + void writeSection( std::string const& className, + std::string const& rootName, + SectionNode const& sectionNode ) { + std::string name = trim( sectionNode.stats.sectionInfo.name ); + if( !rootName.empty() ) + name = rootName + "/" + name; + + if( !sectionNode.assertions.empty() || + !sectionNode.stdOut.empty() || + !sectionNode.stdErr.empty() ) { + XmlWriter::ScopedElement e = xml.scopedElement( "testcase" ); + if( className.empty() ) { + xml.writeAttribute( "classname", name ); + xml.writeAttribute( "name", "root" ); + } + else { + xml.writeAttribute( "classname", className ); + xml.writeAttribute( "name", name ); + } + xml.writeAttribute( "time", Catch::toString( sectionNode.stats.durationInSeconds ) ); + + writeAssertions( sectionNode ); + + if( !sectionNode.stdOut.empty() ) + xml.scopedElement( "system-out" ).writeText( trim( sectionNode.stdOut ), false ); + if( !sectionNode.stdErr.empty() ) + xml.scopedElement( "system-err" ).writeText( trim( sectionNode.stdErr ), false ); + } + for( SectionNode::ChildSections::const_iterator + it = sectionNode.childSections.begin(), + itEnd = sectionNode.childSections.end(); + it != itEnd; + ++it ) + if( className.empty() ) + writeSection( name, "", **it ); + else + writeSection( className, name, **it ); + } + + void writeAssertions( SectionNode const& sectionNode ) { + for( SectionNode::Assertions::const_iterator + it = sectionNode.assertions.begin(), itEnd = sectionNode.assertions.end(); + it != itEnd; + ++it ) + writeAssertion( *it ); + } + void writeAssertion( AssertionStats const& stats ) { + AssertionResult const& result = stats.assertionResult; + if( !result.isOk() ) { + std::string elementName; + switch( result.getResultType() ) { + case ResultWas::ThrewException: + case ResultWas::FatalErrorCondition: + elementName = "error"; + break; + case ResultWas::ExplicitFailure: + elementName = "failure"; + break; + case ResultWas::ExpressionFailed: + elementName = "failure"; + break; + case ResultWas::DidntThrowException: + elementName = "failure"; + break; + + // We should never see these here: + case ResultWas::Info: + case ResultWas::Warning: + case ResultWas::Ok: + case ResultWas::Unknown: + case ResultWas::FailureBit: + case ResultWas::Exception: + elementName = "internalError"; + break; + } + + XmlWriter::ScopedElement e = xml.scopedElement( elementName ); + + xml.writeAttribute( "message", result.getExpandedExpression() ); + xml.writeAttribute( "type", result.getTestMacroName() ); + + std::ostringstream oss; + if( !result.getMessage().empty() ) + oss << result.getMessage() << "\n"; + for( std::vector::const_iterator + it = stats.infoMessages.begin(), + itEnd = stats.infoMessages.end(); + it != itEnd; + ++it ) + if( it->type == ResultWas::Info ) + oss << it->message << "\n"; + + oss << "at " << result.getSourceInfo(); + xml.writeText( oss.str(), false ); + } + } + + XmlWriter xml; + Timer suiteTimer; + std::ostringstream stdOutForSuite; + std::ostringstream stdErrForSuite; + unsigned int unexpectedExceptions; + }; + + INTERNAL_CATCH_REGISTER_REPORTER( "junit", JunitReporter ) + +} // end namespace Catch + +// #included from: ../reporters/catch_reporter_console.hpp +#define TWOBLUECUBES_CATCH_REPORTER_CONSOLE_HPP_INCLUDED + +namespace Catch { + + struct ConsoleReporter : StreamingReporterBase { + ConsoleReporter( ReporterConfig const& _config ) + : StreamingReporterBase( _config ), + m_headerPrinted( false ) + {} + + virtual ~ConsoleReporter() CATCH_OVERRIDE; + static std::string getDescription() { + return "Reports test results as plain lines of text"; + } + + virtual void noMatchingTestCases( std::string const& spec ) CATCH_OVERRIDE { + stream << "No test cases matched '" << spec << "'" << std::endl; + } + + virtual void assertionStarting( AssertionInfo const& ) CATCH_OVERRIDE { + } + + virtual bool assertionEnded( AssertionStats const& _assertionStats ) CATCH_OVERRIDE { + AssertionResult const& result = _assertionStats.assertionResult; + + bool printInfoMessages = true; + + // Drop out if result was successful and we're not printing those + if( !m_config->includeSuccessfulResults() && result.isOk() ) { + if( result.getResultType() != ResultWas::Warning ) + return false; + printInfoMessages = false; + } + + lazyPrint(); + + AssertionPrinter printer( stream, _assertionStats, printInfoMessages ); + printer.print(); + stream << std::endl; + return true; + } + + virtual void sectionStarting( SectionInfo const& _sectionInfo ) CATCH_OVERRIDE { + m_headerPrinted = false; + StreamingReporterBase::sectionStarting( _sectionInfo ); + } + virtual void sectionEnded( SectionStats const& _sectionStats ) CATCH_OVERRIDE { + if( _sectionStats.missingAssertions ) { + lazyPrint(); + Colour colour( Colour::ResultError ); + if( m_sectionStack.size() > 1 ) + stream << "\nNo assertions in section"; + else + stream << "\nNo assertions in test case"; + stream << " '" << _sectionStats.sectionInfo.name << "'\n" << std::endl; + } + if( m_headerPrinted ) { + if( m_config->showDurations() == ShowDurations::Always ) + stream << "Completed in " << _sectionStats.durationInSeconds << "s" << std::endl; + m_headerPrinted = false; + } + else { + if( m_config->showDurations() == ShowDurations::Always ) + stream << _sectionStats.sectionInfo.name << " completed in " << _sectionStats.durationInSeconds << "s" << std::endl; + } + StreamingReporterBase::sectionEnded( _sectionStats ); + } + + virtual void testCaseEnded( TestCaseStats const& _testCaseStats ) CATCH_OVERRIDE { + StreamingReporterBase::testCaseEnded( _testCaseStats ); + m_headerPrinted = false; + } + virtual void testGroupEnded( TestGroupStats const& _testGroupStats ) CATCH_OVERRIDE { + if( currentGroupInfo.used ) { + printSummaryDivider(); + stream << "Summary for group '" << _testGroupStats.groupInfo.name << "':\n"; + printTotals( _testGroupStats.totals ); + stream << "\n" << std::endl; + } + StreamingReporterBase::testGroupEnded( _testGroupStats ); + } + virtual void testRunEnded( TestRunStats const& _testRunStats ) CATCH_OVERRIDE { + printTotalsDivider( _testRunStats.totals ); + printTotals( _testRunStats.totals ); + stream << std::endl; + StreamingReporterBase::testRunEnded( _testRunStats ); + } + + private: + + class AssertionPrinter { + void operator= ( AssertionPrinter const& ); + public: + AssertionPrinter( std::ostream& _stream, AssertionStats const& _stats, bool _printInfoMessages ) + : stream( _stream ), + stats( _stats ), + result( _stats.assertionResult ), + colour( Colour::None ), + message( result.getMessage() ), + messages( _stats.infoMessages ), + printInfoMessages( _printInfoMessages ) + { + switch( result.getResultType() ) { + case ResultWas::Ok: + colour = Colour::Success; + passOrFail = "PASSED"; + //if( result.hasMessage() ) + if( _stats.infoMessages.size() == 1 ) + messageLabel = "with message"; + if( _stats.infoMessages.size() > 1 ) + messageLabel = "with messages"; + break; + case ResultWas::ExpressionFailed: + if( result.isOk() ) { + colour = Colour::Success; + passOrFail = "FAILED - but was ok"; + } + else { + colour = Colour::Error; + passOrFail = "FAILED"; + } + if( _stats.infoMessages.size() == 1 ) + messageLabel = "with message"; + if( _stats.infoMessages.size() > 1 ) + messageLabel = "with messages"; + break; + case ResultWas::ThrewException: + colour = Colour::Error; + passOrFail = "FAILED"; + messageLabel = "due to unexpected exception with message"; + break; + case ResultWas::FatalErrorCondition: + colour = Colour::Error; + passOrFail = "FAILED"; + messageLabel = "due to a fatal error condition"; + break; + case ResultWas::DidntThrowException: + colour = Colour::Error; + passOrFail = "FAILED"; + messageLabel = "because no exception was thrown where one was expected"; + break; + case ResultWas::Info: + messageLabel = "info"; + break; + case ResultWas::Warning: + messageLabel = "warning"; + break; + case ResultWas::ExplicitFailure: + passOrFail = "FAILED"; + colour = Colour::Error; + if( _stats.infoMessages.size() == 1 ) + messageLabel = "explicitly with message"; + if( _stats.infoMessages.size() > 1 ) + messageLabel = "explicitly with messages"; + break; + // These cases are here to prevent compiler warnings + case ResultWas::Unknown: + case ResultWas::FailureBit: + case ResultWas::Exception: + passOrFail = "** internal error **"; + colour = Colour::Error; + break; + } + } + + void print() const { + printSourceInfo(); + if( stats.totals.assertions.total() > 0 ) { + if( result.isOk() ) + stream << "\n"; + printResultType(); + printOriginalExpression(); + printReconstructedExpression(); + } + else { + stream << "\n"; + } + printMessage(); + } + + private: + void printResultType() const { + if( !passOrFail.empty() ) { + Colour colourGuard( colour ); + stream << passOrFail << ":\n"; + } + } + void printOriginalExpression() const { + if( result.hasExpression() ) { + Colour colourGuard( Colour::OriginalExpression ); + stream << " "; + stream << result.getExpressionInMacro(); + stream << "\n"; + } + } + void printReconstructedExpression() const { + if( result.hasExpandedExpression() ) { + stream << "with expansion:\n"; + Colour colourGuard( Colour::ReconstructedExpression ); + stream << Text( result.getExpandedExpression(), TextAttributes().setIndent(2) ) << "\n"; + } + } + void printMessage() const { + if( !messageLabel.empty() ) + stream << messageLabel << ":" << "\n"; + for( std::vector::const_iterator it = messages.begin(), itEnd = messages.end(); + it != itEnd; + ++it ) { + // If this assertion is a warning ignore any INFO messages + if( printInfoMessages || it->type != ResultWas::Info ) + stream << Text( it->message, TextAttributes().setIndent(2) ) << "\n"; + } + } + void printSourceInfo() const { + Colour colourGuard( Colour::FileName ); + stream << result.getSourceInfo() << ": "; + } + + std::ostream& stream; + AssertionStats const& stats; + AssertionResult const& result; + Colour::Code colour; + std::string passOrFail; + std::string messageLabel; + std::string message; + std::vector messages; + bool printInfoMessages; + }; + + void lazyPrint() { + + if( !currentTestRunInfo.used ) + lazyPrintRunInfo(); + if( !currentGroupInfo.used ) + lazyPrintGroupInfo(); + + if( !m_headerPrinted ) { + printTestCaseAndSectionHeader(); + m_headerPrinted = true; + } + } + void lazyPrintRunInfo() { + stream << "\n" << getLineOfChars<'~'>() << "\n"; + Colour colour( Colour::SecondaryText ); + stream << currentTestRunInfo->name + << " is a Catch v" << libraryVersion << " host application.\n" + << "Run with -? for options\n\n"; + + if( m_config->rngSeed() != 0 ) + stream << "Randomness seeded to: " << m_config->rngSeed() << "\n\n"; + + currentTestRunInfo.used = true; + } + void lazyPrintGroupInfo() { + if( !currentGroupInfo->name.empty() && currentGroupInfo->groupsCounts > 1 ) { + printClosedHeader( "Group: " + currentGroupInfo->name ); + currentGroupInfo.used = true; + } + } + void printTestCaseAndSectionHeader() { + assert( !m_sectionStack.empty() ); + printOpenHeader( currentTestCaseInfo->name ); + + if( m_sectionStack.size() > 1 ) { + Colour colourGuard( Colour::Headers ); + + std::vector::const_iterator + it = m_sectionStack.begin()+1, // Skip first section (test case) + itEnd = m_sectionStack.end(); + for( ; it != itEnd; ++it ) + printHeaderString( it->name, 2 ); + } + + SourceLineInfo lineInfo = m_sectionStack.front().lineInfo; + + if( !lineInfo.empty() ){ + stream << getLineOfChars<'-'>() << "\n"; + Colour colourGuard( Colour::FileName ); + stream << lineInfo << "\n"; + } + stream << getLineOfChars<'.'>() << "\n" << std::endl; + } + + void printClosedHeader( std::string const& _name ) { + printOpenHeader( _name ); + stream << getLineOfChars<'.'>() << "\n"; + } + void printOpenHeader( std::string const& _name ) { + stream << getLineOfChars<'-'>() << "\n"; + { + Colour colourGuard( Colour::Headers ); + printHeaderString( _name ); + } + } + + // if string has a : in first line will set indent to follow it on + // subsequent lines + void printHeaderString( std::string const& _string, std::size_t indent = 0 ) { + std::size_t i = _string.find( ": " ); + if( i != std::string::npos ) + i+=2; + else + i = 0; + stream << Text( _string, TextAttributes() + .setIndent( indent+i) + .setInitialIndent( indent ) ) << "\n"; + } + + struct SummaryColumn { + + SummaryColumn( std::string const& _label, Colour::Code _colour ) + : label( _label ), + colour( _colour ) + {} + SummaryColumn addRow( std::size_t count ) { + std::ostringstream oss; + oss << count; + std::string row = oss.str(); + for( std::vector::iterator it = rows.begin(); it != rows.end(); ++it ) { + while( it->size() < row.size() ) + *it = " " + *it; + while( it->size() > row.size() ) + row = " " + row; + } + rows.push_back( row ); + return *this; + } + + std::string label; + Colour::Code colour; + std::vector rows; + + }; + + void printTotals( Totals const& totals ) { + if( totals.testCases.total() == 0 ) { + stream << Colour( Colour::Warning ) << "No tests ran\n"; + } + else if( totals.assertions.total() > 0 && totals.testCases.allPassed() ) { + stream << Colour( Colour::ResultSuccess ) << "All tests passed"; + stream << " (" + << pluralise( totals.assertions.passed, "assertion" ) << " in " + << pluralise( totals.testCases.passed, "test case" ) << ")" + << "\n"; + } + else { + + std::vector columns; + columns.push_back( SummaryColumn( "", Colour::None ) + .addRow( totals.testCases.total() ) + .addRow( totals.assertions.total() ) ); + columns.push_back( SummaryColumn( "passed", Colour::Success ) + .addRow( totals.testCases.passed ) + .addRow( totals.assertions.passed ) ); + columns.push_back( SummaryColumn( "failed", Colour::ResultError ) + .addRow( totals.testCases.failed ) + .addRow( totals.assertions.failed ) ); + columns.push_back( SummaryColumn( "failed as expected", Colour::ResultExpectedFailure ) + .addRow( totals.testCases.failedButOk ) + .addRow( totals.assertions.failedButOk ) ); + + printSummaryRow( "test cases", columns, 0 ); + printSummaryRow( "assertions", columns, 1 ); + } + } + void printSummaryRow( std::string const& label, std::vector const& cols, std::size_t row ) { + for( std::vector::const_iterator it = cols.begin(); it != cols.end(); ++it ) { + std::string value = it->rows[row]; + if( it->label.empty() ) { + stream << label << ": "; + if( value != "0" ) + stream << value; + else + stream << Colour( Colour::Warning ) << "- none -"; + } + else if( value != "0" ) { + stream << Colour( Colour::LightGrey ) << " | "; + stream << Colour( it->colour ) + << value << " " << it->label; + } + } + stream << "\n"; + } + + static std::size_t makeRatio( std::size_t number, std::size_t total ) { + std::size_t ratio = total > 0 ? CATCH_CONFIG_CONSOLE_WIDTH * number/ total : 0; + return ( ratio == 0 && number > 0 ) ? 1 : ratio; + } + static std::size_t& findMax( std::size_t& i, std::size_t& j, std::size_t& k ) { + if( i > j && i > k ) + return i; + else if( j > k ) + return j; + else + return k; + } + + void printTotalsDivider( Totals const& totals ) { + if( totals.testCases.total() > 0 ) { + std::size_t failedRatio = makeRatio( totals.testCases.failed, totals.testCases.total() ); + std::size_t failedButOkRatio = makeRatio( totals.testCases.failedButOk, totals.testCases.total() ); + std::size_t passedRatio = makeRatio( totals.testCases.passed, totals.testCases.total() ); + while( failedRatio + failedButOkRatio + passedRatio < CATCH_CONFIG_CONSOLE_WIDTH-1 ) + findMax( failedRatio, failedButOkRatio, passedRatio )++; + while( failedRatio + failedButOkRatio + passedRatio > CATCH_CONFIG_CONSOLE_WIDTH-1 ) + findMax( failedRatio, failedButOkRatio, passedRatio )--; + + stream << Colour( Colour::Error ) << std::string( failedRatio, '=' ); + stream << Colour( Colour::ResultExpectedFailure ) << std::string( failedButOkRatio, '=' ); + if( totals.testCases.allPassed() ) + stream << Colour( Colour::ResultSuccess ) << std::string( passedRatio, '=' ); + else + stream << Colour( Colour::Success ) << std::string( passedRatio, '=' ); + } + else { + stream << Colour( Colour::Warning ) << std::string( CATCH_CONFIG_CONSOLE_WIDTH-1, '=' ); + } + stream << "\n"; + } + void printSummaryDivider() { + stream << getLineOfChars<'-'>() << "\n"; + } + + private: + bool m_headerPrinted; + }; + + INTERNAL_CATCH_REGISTER_REPORTER( "console", ConsoleReporter ) + +} // end namespace Catch + +// #included from: ../reporters/catch_reporter_compact.hpp +#define TWOBLUECUBES_CATCH_REPORTER_COMPACT_HPP_INCLUDED + +namespace Catch { + + struct CompactReporter : StreamingReporterBase { + + CompactReporter( ReporterConfig const& _config ) + : StreamingReporterBase( _config ) + {} + + virtual ~CompactReporter(); + + static std::string getDescription() { + return "Reports test results on a single line, suitable for IDEs"; + } + + virtual ReporterPreferences getPreferences() const { + ReporterPreferences prefs; + prefs.shouldRedirectStdOut = false; + return prefs; + } + + virtual void noMatchingTestCases( std::string const& spec ) { + stream << "No test cases matched '" << spec << "'" << std::endl; + } + + virtual void assertionStarting( AssertionInfo const& ) { + } + + virtual bool assertionEnded( AssertionStats const& _assertionStats ) { + AssertionResult const& result = _assertionStats.assertionResult; + + bool printInfoMessages = true; + + // Drop out if result was successful and we're not printing those + if( !m_config->includeSuccessfulResults() && result.isOk() ) { + if( result.getResultType() != ResultWas::Warning ) + return false; + printInfoMessages = false; + } + + AssertionPrinter printer( stream, _assertionStats, printInfoMessages ); + printer.print(); + + stream << std::endl; + return true; + } + + virtual void testRunEnded( TestRunStats const& _testRunStats ) { + printTotals( _testRunStats.totals ); + stream << "\n" << std::endl; + StreamingReporterBase::testRunEnded( _testRunStats ); + } + + private: + class AssertionPrinter { + void operator= ( AssertionPrinter const& ); + public: + AssertionPrinter( std::ostream& _stream, AssertionStats const& _stats, bool _printInfoMessages ) + : stream( _stream ) + , stats( _stats ) + , result( _stats.assertionResult ) + , messages( _stats.infoMessages ) + , itMessage( _stats.infoMessages.begin() ) + , printInfoMessages( _printInfoMessages ) + {} + + void print() { + printSourceInfo(); + + itMessage = messages.begin(); + + switch( result.getResultType() ) { + case ResultWas::Ok: + printResultType( Colour::ResultSuccess, passedString() ); + printOriginalExpression(); + printReconstructedExpression(); + if ( ! result.hasExpression() ) + printRemainingMessages( Colour::None ); + else + printRemainingMessages(); + break; + case ResultWas::ExpressionFailed: + if( result.isOk() ) + printResultType( Colour::ResultSuccess, failedString() + std::string( " - but was ok" ) ); + else + printResultType( Colour::Error, failedString() ); + printOriginalExpression(); + printReconstructedExpression(); + printRemainingMessages(); + break; + case ResultWas::ThrewException: + printResultType( Colour::Error, failedString() ); + printIssue( "unexpected exception with message:" ); + printMessage(); + printExpressionWas(); + printRemainingMessages(); + break; + case ResultWas::FatalErrorCondition: + printResultType( Colour::Error, failedString() ); + printIssue( "fatal error condition with message:" ); + printMessage(); + printExpressionWas(); + printRemainingMessages(); + break; + case ResultWas::DidntThrowException: + printResultType( Colour::Error, failedString() ); + printIssue( "expected exception, got none" ); + printExpressionWas(); + printRemainingMessages(); + break; + case ResultWas::Info: + printResultType( Colour::None, "info" ); + printMessage(); + printRemainingMessages(); + break; + case ResultWas::Warning: + printResultType( Colour::None, "warning" ); + printMessage(); + printRemainingMessages(); + break; + case ResultWas::ExplicitFailure: + printResultType( Colour::Error, failedString() ); + printIssue( "explicitly" ); + printRemainingMessages( Colour::None ); + break; + // These cases are here to prevent compiler warnings + case ResultWas::Unknown: + case ResultWas::FailureBit: + case ResultWas::Exception: + printResultType( Colour::Error, "** internal error **" ); + break; + } + } + + private: + // Colour::LightGrey + + static Colour::Code dimColour() { return Colour::FileName; } + +#ifdef CATCH_PLATFORM_MAC + static const char* failedString() { return "FAILED"; } + static const char* passedString() { return "PASSED"; } +#else + static const char* failedString() { return "failed"; } + static const char* passedString() { return "passed"; } +#endif + + void printSourceInfo() const { + Colour colourGuard( Colour::FileName ); + stream << result.getSourceInfo() << ":"; + } + + void printResultType( Colour::Code colour, std::string passOrFail ) const { + if( !passOrFail.empty() ) { + { + Colour colourGuard( colour ); + stream << " " << passOrFail; + } + stream << ":"; + } + } + + void printIssue( std::string issue ) const { + stream << " " << issue; + } + + void printExpressionWas() { + if( result.hasExpression() ) { + stream << ";"; + { + Colour colour( dimColour() ); + stream << " expression was:"; + } + printOriginalExpression(); + } + } + + void printOriginalExpression() const { + if( result.hasExpression() ) { + stream << " " << result.getExpression(); + } + } + + void printReconstructedExpression() const { + if( result.hasExpandedExpression() ) { + { + Colour colour( dimColour() ); + stream << " for: "; + } + stream << result.getExpandedExpression(); + } + } + + void printMessage() { + if ( itMessage != messages.end() ) { + stream << " '" << itMessage->message << "'"; + ++itMessage; + } + } + + void printRemainingMessages( Colour::Code colour = dimColour() ) { + if ( itMessage == messages.end() ) + return; + + // using messages.end() directly yields compilation error: + std::vector::const_iterator itEnd = messages.end(); + const std::size_t N = static_cast( std::distance( itMessage, itEnd ) ); + + { + Colour colourGuard( colour ); + stream << " with " << pluralise( N, "message" ) << ":"; + } + + for(; itMessage != itEnd; ) { + // If this assertion is a warning ignore any INFO messages + if( printInfoMessages || itMessage->type != ResultWas::Info ) { + stream << " '" << itMessage->message << "'"; + if ( ++itMessage != itEnd ) { + Colour colourGuard( dimColour() ); + stream << " and"; + } + } + } + } + + private: + std::ostream& stream; + AssertionStats const& stats; + AssertionResult const& result; + std::vector messages; + std::vector::const_iterator itMessage; + bool printInfoMessages; + }; + + // Colour, message variants: + // - white: No tests ran. + // - red: Failed [both/all] N test cases, failed [both/all] M assertions. + // - white: Passed [both/all] N test cases (no assertions). + // - red: Failed N tests cases, failed M assertions. + // - green: Passed [both/all] N tests cases with M assertions. + + std::string bothOrAll( std::size_t count ) const { + return count == 1 ? "" : count == 2 ? "both " : "all " ; + } + + void printTotals( const Totals& totals ) const { + if( totals.testCases.total() == 0 ) { + stream << "No tests ran."; + } + else if( totals.testCases.failed == totals.testCases.total() ) { + Colour colour( Colour::ResultError ); + const std::string qualify_assertions_failed = + totals.assertions.failed == totals.assertions.total() ? + bothOrAll( totals.assertions.failed ) : ""; + stream << + "Failed " << bothOrAll( totals.testCases.failed ) + << pluralise( totals.testCases.failed, "test case" ) << ", " + "failed " << qualify_assertions_failed << + pluralise( totals.assertions.failed, "assertion" ) << "."; + } + else if( totals.assertions.total() == 0 ) { + stream << + "Passed " << bothOrAll( totals.testCases.total() ) + << pluralise( totals.testCases.total(), "test case" ) + << " (no assertions)."; + } + else if( totals.assertions.failed ) { + Colour colour( Colour::ResultError ); + stream << + "Failed " << pluralise( totals.testCases.failed, "test case" ) << ", " + "failed " << pluralise( totals.assertions.failed, "assertion" ) << "."; + } + else { + Colour colour( Colour::ResultSuccess ); + stream << + "Passed " << bothOrAll( totals.testCases.passed ) + << pluralise( totals.testCases.passed, "test case" ) << + " with " << pluralise( totals.assertions.passed, "assertion" ) << "."; + } + } + }; + + INTERNAL_CATCH_REGISTER_REPORTER( "compact", CompactReporter ) + +} // end namespace Catch + +namespace Catch { + // These are all here to avoid warnings about not having any out of line + // virtual methods + NonCopyable::~NonCopyable() {} + IShared::~IShared() {} + IStream::~IStream() CATCH_NOEXCEPT {} + FileStream::~FileStream() CATCH_NOEXCEPT {} + CoutStream::~CoutStream() CATCH_NOEXCEPT {} + DebugOutStream::~DebugOutStream() CATCH_NOEXCEPT {} + StreamBufBase::~StreamBufBase() CATCH_NOEXCEPT {} + IContext::~IContext() {} + IResultCapture::~IResultCapture() {} + ITestCase::~ITestCase() {} + ITestCaseRegistry::~ITestCaseRegistry() {} + IRegistryHub::~IRegistryHub() {} + IMutableRegistryHub::~IMutableRegistryHub() {} + IExceptionTranslator::~IExceptionTranslator() {} + IExceptionTranslatorRegistry::~IExceptionTranslatorRegistry() {} + IReporter::~IReporter() {} + IReporterFactory::~IReporterFactory() {} + IReporterRegistry::~IReporterRegistry() {} + IStreamingReporter::~IStreamingReporter() {} + AssertionStats::~AssertionStats() {} + SectionStats::~SectionStats() {} + TestCaseStats::~TestCaseStats() {} + TestGroupStats::~TestGroupStats() {} + TestRunStats::~TestRunStats() {} + CumulativeReporterBase::SectionNode::~SectionNode() {} + CumulativeReporterBase::~CumulativeReporterBase() {} + + StreamingReporterBase::~StreamingReporterBase() {} + ConsoleReporter::~ConsoleReporter() {} + CompactReporter::~CompactReporter() {} + IRunner::~IRunner() {} + IMutableContext::~IMutableContext() {} + IConfig::~IConfig() {} + XmlReporter::~XmlReporter() {} + JunitReporter::~JunitReporter() {} + TestRegistry::~TestRegistry() {} + FreeFunctionTestCase::~FreeFunctionTestCase() {} + IGeneratorInfo::~IGeneratorInfo() {} + IGeneratorsForTest::~IGeneratorsForTest() {} + WildcardPattern::~WildcardPattern() {} + TestSpec::Pattern::~Pattern() {} + TestSpec::NamePattern::~NamePattern() {} + TestSpec::TagPattern::~TagPattern() {} + TestSpec::ExcludedPattern::~ExcludedPattern() {} + + Matchers::Impl::StdString::Equals::~Equals() {} + Matchers::Impl::StdString::Contains::~Contains() {} + Matchers::Impl::StdString::StartsWith::~StartsWith() {} + Matchers::Impl::StdString::EndsWith::~EndsWith() {} + + void Config::dummy() {} + + namespace TestCaseTracking { + ITracker::~ITracker() {} + TrackerBase::~TrackerBase() {} + SectionTracker::~SectionTracker() {} + IndexTracker::~IndexTracker() {} + } +} + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +#endif + +#ifdef CATCH_CONFIG_MAIN +// #included from: internal/catch_default_main.hpp +#define TWOBLUECUBES_CATCH_DEFAULT_MAIN_HPP_INCLUDED + +#ifndef __OBJC__ + +// Standard C/C++ main entry point +int main (int argc, char * argv[]) { + return Catch::Session().run( argc, argv ); +} + +#else // __OBJC__ + +// Objective-C entry point +int main (int argc, char * const argv[]) { +#if !CATCH_ARC_ENABLED + NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; +#endif + + Catch::registerTestMethods(); + int result = Catch::Session().run( argc, (char* const*)argv ); + +#if !CATCH_ARC_ENABLED + [pool drain]; +#endif + + return result; +} + +#endif // __OBJC__ + +#endif + +#ifdef CLARA_CONFIG_MAIN_NOT_DEFINED +# undef CLARA_CONFIG_MAIN +#endif + +////// + +// If this config identifier is defined then all CATCH macros are prefixed with CATCH_ +#ifdef CATCH_CONFIG_PREFIX_ALL + +#define CATCH_REQUIRE( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::Normal, "CATCH_REQUIRE" ) +#define CATCH_REQUIRE_FALSE( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::Normal | Catch::ResultDisposition::FalseTest, "CATCH_REQUIRE_FALSE" ) + +#define CATCH_REQUIRE_THROWS( expr ) INTERNAL_CATCH_THROWS( expr, Catch::ResultDisposition::Normal, "", "CATCH_REQUIRE_THROWS" ) +#define CATCH_REQUIRE_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( expr, exceptionType, Catch::ResultDisposition::Normal, "CATCH_REQUIRE_THROWS_AS" ) +#define CATCH_REQUIRE_THROWS_WITH( expr, matcher ) INTERNAL_CATCH_THROWS( expr, Catch::ResultDisposition::Normal, matcher, "CATCH_REQUIRE_THROWS_WITH" ) +#define CATCH_REQUIRE_NOTHROW( expr ) INTERNAL_CATCH_NO_THROW( expr, Catch::ResultDisposition::Normal, "CATCH_REQUIRE_NOTHROW" ) + +#define CATCH_CHECK( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::ContinueOnFailure, "CATCH_CHECK" ) +#define CATCH_CHECK_FALSE( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::FalseTest, "CATCH_CHECK_FALSE" ) +#define CATCH_CHECKED_IF( expr ) INTERNAL_CATCH_IF( expr, Catch::ResultDisposition::ContinueOnFailure, "CATCH_CHECKED_IF" ) +#define CATCH_CHECKED_ELSE( expr ) INTERNAL_CATCH_ELSE( expr, Catch::ResultDisposition::ContinueOnFailure, "CATCH_CHECKED_ELSE" ) +#define CATCH_CHECK_NOFAIL( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::SuppressFail, "CATCH_CHECK_NOFAIL" ) + +#define CATCH_CHECK_THROWS( expr ) INTERNAL_CATCH_THROWS( expr, Catch::ResultDisposition::ContinueOnFailure, "CATCH_CHECK_THROWS" ) +#define CATCH_CHECK_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( expr, exceptionType, Catch::ResultDisposition::ContinueOnFailure, "CATCH_CHECK_THROWS_AS" ) +#define CATCH_CHECK_THROWS_WITH( expr, matcher ) INTERNAL_CATCH_THROWS( expr, Catch::ResultDisposition::ContinueOnFailure, matcher, "CATCH_CHECK_THROWS_WITH" ) +#define CATCH_CHECK_NOTHROW( expr ) INTERNAL_CATCH_NO_THROW( expr, Catch::ResultDisposition::ContinueOnFailure, "CATCH_CHECK_NOTHROW" ) + +#define CHECK_THAT( arg, matcher ) INTERNAL_CHECK_THAT( arg, matcher, Catch::ResultDisposition::ContinueOnFailure, "CATCH_CHECK_THAT" ) +#define CATCH_REQUIRE_THAT( arg, matcher ) INTERNAL_CHECK_THAT( arg, matcher, Catch::ResultDisposition::Normal, "CATCH_REQUIRE_THAT" ) + +#define CATCH_INFO( msg ) INTERNAL_CATCH_INFO( msg, "CATCH_INFO" ) +#define CATCH_WARN( msg ) INTERNAL_CATCH_MSG( Catch::ResultWas::Warning, Catch::ResultDisposition::ContinueOnFailure, "CATCH_WARN", msg ) +#define CATCH_SCOPED_INFO( msg ) INTERNAL_CATCH_INFO( msg, "CATCH_INFO" ) +#define CATCH_CAPTURE( msg ) INTERNAL_CATCH_INFO( #msg " := " << msg, "CATCH_CAPTURE" ) +#define CATCH_SCOPED_CAPTURE( msg ) INTERNAL_CATCH_INFO( #msg " := " << msg, "CATCH_CAPTURE" ) + +#ifdef CATCH_CONFIG_VARIADIC_MACROS + #define CATCH_TEST_CASE( ... ) INTERNAL_CATCH_TESTCASE( __VA_ARGS__ ) + #define CATCH_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, __VA_ARGS__ ) + #define CATCH_METHOD_AS_TEST_CASE( method, ... ) INTERNAL_CATCH_METHOD_AS_TEST_CASE( method, __VA_ARGS__ ) + #define CATCH_REGISTER_TEST_CASE( Function, ... ) INTERNAL_CATCH_REGISTER_TESTCASE( Function, __VA_ARGS__ ) + #define CATCH_SECTION( ... ) INTERNAL_CATCH_SECTION( __VA_ARGS__ ) + #define CATCH_FAIL( ... ) INTERNAL_CATCH_MSG( Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, "CATCH_FAIL", __VA_ARGS__ ) + #define CATCH_SUCCEED( ... ) INTERNAL_CATCH_MSG( Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, "CATCH_SUCCEED", __VA_ARGS__ ) +#else + #define CATCH_TEST_CASE( name, description ) INTERNAL_CATCH_TESTCASE( name, description ) + #define CATCH_TEST_CASE_METHOD( className, name, description ) INTERNAL_CATCH_TEST_CASE_METHOD( className, name, description ) + #define CATCH_METHOD_AS_TEST_CASE( method, name, description ) INTERNAL_CATCH_METHOD_AS_TEST_CASE( method, name, description ) + #define CATCH_REGISTER_TEST_CASE( function, name, description ) INTERNAL_CATCH_REGISTER_TESTCASE( function, name, description ) + #define CATCH_SECTION( name, description ) INTERNAL_CATCH_SECTION( name, description ) + #define CATCH_FAIL( msg ) INTERNAL_CATCH_MSG( Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, "CATCH_FAIL", msg ) + #define CATCH_SUCCEED( msg ) INTERNAL_CATCH_MSG( Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, "CATCH_SUCCEED", msg ) +#endif +#define CATCH_ANON_TEST_CASE() INTERNAL_CATCH_TESTCASE( "", "" ) + +#define CATCH_REGISTER_REPORTER( name, reporterType ) INTERNAL_CATCH_REGISTER_REPORTER( name, reporterType ) +#define CATCH_REGISTER_LEGACY_REPORTER( name, reporterType ) INTERNAL_CATCH_REGISTER_LEGACY_REPORTER( name, reporterType ) + +#define CATCH_GENERATE( expr) INTERNAL_CATCH_GENERATE( expr ) + +// "BDD-style" convenience wrappers +#ifdef CATCH_CONFIG_VARIADIC_MACROS +#define CATCH_SCENARIO( ... ) CATCH_TEST_CASE( "Scenario: " __VA_ARGS__ ) +#define CATCH_SCENARIO_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, "Scenario: " __VA_ARGS__ ) +#else +#define CATCH_SCENARIO( name, tags ) CATCH_TEST_CASE( "Scenario: " name, tags ) +#define CATCH_SCENARIO_METHOD( className, name, tags ) INTERNAL_CATCH_TEST_CASE_METHOD( className, "Scenario: " name, tags ) +#endif +#define CATCH_GIVEN( desc ) CATCH_SECTION( std::string( "Given: ") + desc, "" ) +#define CATCH_WHEN( desc ) CATCH_SECTION( std::string( " When: ") + desc, "" ) +#define CATCH_AND_WHEN( desc ) CATCH_SECTION( std::string( " And: ") + desc, "" ) +#define CATCH_THEN( desc ) CATCH_SECTION( std::string( " Then: ") + desc, "" ) +#define CATCH_AND_THEN( desc ) CATCH_SECTION( std::string( " And: ") + desc, "" ) + +// If CATCH_CONFIG_PREFIX_ALL is not defined then the CATCH_ prefix is not required +#else + +#define REQUIRE( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::Normal, "REQUIRE" ) +#define REQUIRE_FALSE( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::Normal | Catch::ResultDisposition::FalseTest, "REQUIRE_FALSE" ) + +#define REQUIRE_THROWS( expr ) INTERNAL_CATCH_THROWS( expr, Catch::ResultDisposition::Normal, "", "REQUIRE_THROWS" ) +#define REQUIRE_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( expr, exceptionType, Catch::ResultDisposition::Normal, "REQUIRE_THROWS_AS" ) +#define REQUIRE_THROWS_WITH( expr, matcher ) INTERNAL_CATCH_THROWS( expr, Catch::ResultDisposition::Normal, matcher, "REQUIRE_THROWS_WITH" ) +#define REQUIRE_NOTHROW( expr ) INTERNAL_CATCH_NO_THROW( expr, Catch::ResultDisposition::Normal, "REQUIRE_NOTHROW" ) + +#define CHECK( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::ContinueOnFailure, "CHECK" ) +#define CHECK_FALSE( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::FalseTest, "CHECK_FALSE" ) +#define CHECKED_IF( expr ) INTERNAL_CATCH_IF( expr, Catch::ResultDisposition::ContinueOnFailure, "CHECKED_IF" ) +#define CHECKED_ELSE( expr ) INTERNAL_CATCH_ELSE( expr, Catch::ResultDisposition::ContinueOnFailure, "CHECKED_ELSE" ) +#define CHECK_NOFAIL( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::SuppressFail, "CHECK_NOFAIL" ) + +#define CHECK_THROWS( expr ) INTERNAL_CATCH_THROWS( expr, Catch::ResultDisposition::ContinueOnFailure, "", "CHECK_THROWS" ) +#define CHECK_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( expr, exceptionType, Catch::ResultDisposition::ContinueOnFailure, "CHECK_THROWS_AS" ) +#define CHECK_THROWS_WITH( expr, matcher ) INTERNAL_CATCH_THROWS( expr, Catch::ResultDisposition::ContinueOnFailure, matcher, "CHECK_THROWS_WITH" ) +#define CHECK_NOTHROW( expr ) INTERNAL_CATCH_NO_THROW( expr, Catch::ResultDisposition::ContinueOnFailure, "CHECK_NOTHROW" ) + +#define CHECK_THAT( arg, matcher ) INTERNAL_CHECK_THAT( arg, matcher, Catch::ResultDisposition::ContinueOnFailure, "CHECK_THAT" ) +#define REQUIRE_THAT( arg, matcher ) INTERNAL_CHECK_THAT( arg, matcher, Catch::ResultDisposition::Normal, "REQUIRE_THAT" ) + +#define INFO( msg ) INTERNAL_CATCH_INFO( msg, "INFO" ) +#define WARN( msg ) INTERNAL_CATCH_MSG( Catch::ResultWas::Warning, Catch::ResultDisposition::ContinueOnFailure, "WARN", msg ) +#define SCOPED_INFO( msg ) INTERNAL_CATCH_INFO( msg, "INFO" ) +#define CAPTURE( msg ) INTERNAL_CATCH_INFO( #msg " := " << msg, "CAPTURE" ) +#define SCOPED_CAPTURE( msg ) INTERNAL_CATCH_INFO( #msg " := " << msg, "CAPTURE" ) + +#ifdef CATCH_CONFIG_VARIADIC_MACROS + #define TEST_CASE( ... ) INTERNAL_CATCH_TESTCASE( __VA_ARGS__ ) + #define TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, __VA_ARGS__ ) + #define METHOD_AS_TEST_CASE( method, ... ) INTERNAL_CATCH_METHOD_AS_TEST_CASE( method, __VA_ARGS__ ) + #define REGISTER_TEST_CASE( Function, ... ) INTERNAL_CATCH_REGISTER_TESTCASE( Function, __VA_ARGS__ ) + #define SECTION( ... ) INTERNAL_CATCH_SECTION( __VA_ARGS__ ) + #define FAIL( ... ) INTERNAL_CATCH_MSG( Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, "FAIL", __VA_ARGS__ ) + #define SUCCEED( ... ) INTERNAL_CATCH_MSG( Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, "SUCCEED", __VA_ARGS__ ) +#else + #define TEST_CASE( name, description ) INTERNAL_CATCH_TESTCASE( name, description ) + #define TEST_CASE_METHOD( className, name, description ) INTERNAL_CATCH_TEST_CASE_METHOD( className, name, description ) + #define METHOD_AS_TEST_CASE( method, name, description ) INTERNAL_CATCH_METHOD_AS_TEST_CASE( method, name, description ) + #define REGISTER_TEST_CASE( method, name, description ) INTERNAL_CATCH_REGISTER_TESTCASE( method, name, description ) + #define SECTION( name, description ) INTERNAL_CATCH_SECTION( name, description ) + #define FAIL( msg ) INTERNAL_CATCH_MSG( Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, "FAIL", msg ) + #define SUCCEED( msg ) INTERNAL_CATCH_MSG( Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, "SUCCEED", msg ) +#endif +#define ANON_TEST_CASE() INTERNAL_CATCH_TESTCASE( "", "" ) + +#define REGISTER_REPORTER( name, reporterType ) INTERNAL_CATCH_REGISTER_REPORTER( name, reporterType ) +#define REGISTER_LEGACY_REPORTER( name, reporterType ) INTERNAL_CATCH_REGISTER_LEGACY_REPORTER( name, reporterType ) + +#define GENERATE( expr) INTERNAL_CATCH_GENERATE( expr ) + +#endif + +#define CATCH_TRANSLATE_EXCEPTION( signature ) INTERNAL_CATCH_TRANSLATE_EXCEPTION( signature ) + +// "BDD-style" convenience wrappers +#ifdef CATCH_CONFIG_VARIADIC_MACROS +#define SCENARIO( ... ) TEST_CASE( "Scenario: " __VA_ARGS__ ) +#define SCENARIO_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, "Scenario: " __VA_ARGS__ ) +#else +#define SCENARIO( name, tags ) TEST_CASE( "Scenario: " name, tags ) +#define SCENARIO_METHOD( className, name, tags ) INTERNAL_CATCH_TEST_CASE_METHOD( className, "Scenario: " name, tags ) +#endif +#define GIVEN( desc ) SECTION( std::string(" Given: ") + desc, "" ) +#define WHEN( desc ) SECTION( std::string(" When: ") + desc, "" ) +#define AND_WHEN( desc ) SECTION( std::string("And when: ") + desc, "" ) +#define THEN( desc ) SECTION( std::string(" Then: ") + desc, "" ) +#define AND_THEN( desc ) SECTION( std::string(" And: ") + desc, "" ) + +using Catch::Detail::Approx; + +#endif // TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED + diff --git a/tests/config-msvc.py b/tests/config-msvc.py new file mode 100644 index 0000000..06fae62 --- /dev/null +++ b/tests/config-msvc.py @@ -0,0 +1,52 @@ +exe = "tester.exe" + +toolchain = "msvc" + +# optional +link_pool_depth = 1 + +# optional +builddir = { + "gnu" : "build" + , "msvc" : "build" + , "clang" : "build" + } + +includes = { + "gnu" : [ "-I." ] + , "msvc" : [ "/I." ] + , "clang" : [ "-I." ] + } + +defines = { + "gnu" : [ "-DEXAMPLE=1" ] + , "msvc" : [ "/DEXAMPLE=1" ] + , "clang" : [ "-DEXAMPLE=1" ] + } + +cflags = { + "gnu" : [ "-O2", "-g" ] + , "msvc" : [ "/O2" ] + , "clang" : [ "-O2", "-g" ] + } + +cxxflags = { + "gnu" : [ "-O2", "-g" ] + , "msvc" : [ "/O2" ] + , "clang" : [ "-O2", "-g", "-fsanitize=address" ] + } + +ldflags = { + "gnu" : [ ] + , "msvc" : [ ] + , "clang" : [ "-fsanitize=address" ] + } + +# optionsl +cxx_files = [ "tester.cc" ] +c_files = [ ] + +# You can register your own toolchain through register_toolchain function +def register_toolchain(ninja): + pass + diff --git a/tests/config-posix.py b/tests/config-posix.py new file mode 100644 index 0000000..29cc4d5 --- /dev/null +++ b/tests/config-posix.py @@ -0,0 +1,53 @@ +exe = "tester" + +# "gnu" or "clang" +toolchain = "gnu" + +# optional +link_pool_depth = 1 + +# optional +builddir = { + "gnu" : "build" + , "msvc" : "build" + , "clang" : "build" + } + +includes = { + "gnu" : [ "-I." ] + , "msvc" : [ "/I." ] + , "clang" : [ "-I." ] + } + +defines = { + "gnu" : [ ] + , "msvc" : [ ] + , "clang" : [ ] + } + +cflags = { + "gnu" : [ "-O2", "-g" ] + , "msvc" : [ "/O2" ] + , "clang" : [ "-O2", "-g" ] + } + +# Warn as much as possible: http://qiita.com/MitsutakaTakeda/items/6b9966f890cc9b944d75 +cxxflags = { + "gnu" : [ "-O2", "-g", "-pedantic -Wall -Wextra -Wcast-align -Wcast-qual -Wctor-dtor-privacy -Wdisabled-optimization -Wformat=2 -Winit-self -Wmissing-declarations -Wmissing-include-dirs -Wold-style-cast -Woverloaded-virtual -Wredundant-decls -Wshadow -Wsign-conversion -Wsign-promo -Wstrict-overflow=5 -Wswitch-default -Wundef -Werror -Wno-unused", "-fsanitize=address" ] + , "msvc" : [ "/O2", "/W4" ] + , "clang" : [ "-O2", "-g", "-Werror -Weverything -Wno-c++98-compat -Wno-c++98-compat-pedantic", "-fsanitize=address" ] + } + +ldflags = { + "gnu" : [ "-fsanitize=address" ] + , "msvc" : [ ] + , "clang" : [ "-fsanitize=address" ] + } + +cxx_files = [ "tester.cc" ] +c_files = [ ] + +# You can register your own toolchain through register_toolchain function +def register_toolchain(ninja): + pass + diff --git a/tests/kuroga.py b/tests/kuroga.py new file mode 100755 index 0000000..56d3f86 --- /dev/null +++ b/tests/kuroga.py @@ -0,0 +1,312 @@ +#!/usr/bin/env python + +# +# Kuroga, single python file meta-build system for ninja +# https://github.com/lighttransport/kuroga +# +# Requirements: python 2.6 or 2.7 +# +# Usage: $ python kuroga.py input.py +# + +import imp +import re +import textwrap +import glob +import os +import sys + +# gcc preset +def add_gnu_rule(ninja): + ninja.rule('gnucxx', description='CXX $out', + command='$gnucxx -MMD -MF $out.d $gnudefines $gnuincludes $gnucxxflags -c $in -o $out', + depfile='$out.d', deps='gcc') + ninja.rule('gnucc', description='CC $out', + command='$gnucc -MMD -MF $out.d $gnudefines $gnuincludes $gnucflags -c $in -o $out', + depfile='$out.d', deps='gcc') + ninja.rule('gnulink', description='LINK $out', pool='link_pool', + command='$gnuld -o $out $in $libs $gnuldflags') + ninja.rule('gnuar', description='AR $out', pool='link_pool', + command='$gnuar rsc $out $in') + ninja.rule('gnustamp', description='STAMP $out', command='touch $out') + ninja.newline() + + ninja.variable('gnucxx', 'g++') + ninja.variable('gnucc', 'gcc') + ninja.variable('gnuld', '$gnucxx') + ninja.variable('gnuar', 'ar') + ninja.newline() + +# clang preset +def add_clang_rule(ninja): + ninja.rule('clangcxx', description='CXX $out', + command='$clangcxx -MMD -MF $out.d $clangdefines $clangincludes $clangcxxflags -c $in -o $out', + depfile='$out.d', deps='gcc') + ninja.rule('clangcc', description='CC $out', + command='$clangcc -MMD -MF $out.d $clangdefines $clangincludes $clangcflags -c $in -o $out', + depfile='$out.d', deps='gcc') + ninja.rule('clanglink', description='LINK $out', pool='link_pool', + command='$clangld -o $out $in $libs $clangldflags') + ninja.rule('clangar', description='AR $out', pool='link_pool', + command='$clangar rsc $out $in') + ninja.rule('clangstamp', description='STAMP $out', command='touch $out') + ninja.newline() + + ninja.variable('clangcxx', 'clang++') + ninja.variable('clangcc', 'clang') + ninja.variable('clangld', '$clangcxx') + ninja.variable('clangar', 'ar') + ninja.newline() + +# msvc preset +def add_msvc_rule(ninja): + ninja.rule('msvccxx', description='CXX $out', + command='$msvccxx /TP /showIncludes $msvcdefines $msvcincludes $msvccxxflags -c $in /Fo$out', + depfile='$out.d', deps='msvc') + ninja.rule('msvccc', description='CC $out', + command='$msvccc /TC /showIncludes $msvcdefines $msvcincludes $msvccflags -c $in /Fo$out', + depfile='$out.d', deps='msvc') + ninja.rule('msvclink', description='LINK $out', pool='link_pool', + command='$msvcld $msvcldflags $in $libs /OUT:$out') + ninja.rule('msvcar', description='AR $out', pool='link_pool', + command='$msvcar $in /OUT:$out') + #ninja.rule('msvcstamp', description='STAMP $out', command='touch $out') + ninja.newline() + + ninja.variable('msvccxx', 'cl.exe') + ninja.variable('msvccc', 'cl.exe') + ninja.variable('msvcld', 'link.exe') + ninja.variable('msvcar', 'lib.exe') + ninja.newline() + +# -- from ninja_syntax.py -- +def escape_path(word): + return word.replace('$ ', '$$ ').replace(' ', '$ ').replace(':', '$:') + +class Writer(object): + def __init__(self, output, width=78): + self.output = output + self.width = width + + def newline(self): + self.output.write('\n') + + def comment(self, text, has_path=False): + for line in textwrap.wrap(text, self.width - 2, break_long_words=False, + break_on_hyphens=False): + self.output.write('# ' + line + '\n') + + def variable(self, key, value, indent=0): + if value is None: + return + if isinstance(value, list): + value = ' '.join(filter(None, value)) # Filter out empty strings. + self._line('%s = %s' % (key, value), indent) + + def pool(self, name, depth): + self._line('pool %s' % name) + self.variable('depth', depth, indent=1) + + def rule(self, name, command, description=None, depfile=None, + generator=False, pool=None, restat=False, rspfile=None, + rspfile_content=None, deps=None): + self._line('rule %s' % name) + self.variable('command', command, indent=1) + if description: + self.variable('description', description, indent=1) + if depfile: + self.variable('depfile', depfile, indent=1) + if generator: + self.variable('generator', '1', indent=1) + if pool: + self.variable('pool', pool, indent=1) + if restat: + self.variable('restat', '1', indent=1) + if rspfile: + self.variable('rspfile', rspfile, indent=1) + if rspfile_content: + self.variable('rspfile_content', rspfile_content, indent=1) + if deps: + self.variable('deps', deps, indent=1) + + def build(self, outputs, rule, inputs=None, implicit=None, order_only=None, + variables=None): + outputs = as_list(outputs) + out_outputs = [escape_path(x) for x in outputs] + all_inputs = [escape_path(x) for x in as_list(inputs)] + + if implicit: + implicit = [escape_path(x) for x in as_list(implicit)] + all_inputs.append('|') + all_inputs.extend(implicit) + if order_only: + order_only = [escape_path(x) for x in as_list(order_only)] + all_inputs.append('||') + all_inputs.extend(order_only) + + self._line('build %s: %s' % (' '.join(out_outputs), + ' '.join([rule] + all_inputs))) + + if variables: + if isinstance(variables, dict): + iterator = iter(variables.items()) + else: + iterator = iter(variables) + + for key, val in iterator: + self.variable(key, val, indent=1) + + return outputs + + def include(self, path): + self._line('include %s' % path) + + def subninja(self, path): + self._line('subninja %s' % path) + + def default(self, paths): + self._line('default %s' % ' '.join(as_list(paths))) + + def _count_dollars_before_index(self, s, i): + """Returns the number of '$' characters right in front of s[i].""" + dollar_count = 0 + dollar_index = i - 1 + while dollar_index > 0 and s[dollar_index] == '$': + dollar_count += 1 + dollar_index -= 1 + return dollar_count + + def _line(self, text, indent=0): + """Write 'text' word-wrapped at self.width characters.""" + leading_space = ' ' * indent + while len(leading_space) + len(text) > self.width: + # The text is too wide; wrap if possible. + + # Find the rightmost space that would obey our width constraint and + # that's not an escaped space. + available_space = self.width - len(leading_space) - len(' $') + space = available_space + while True: + space = text.rfind(' ', 0, space) + if (space < 0 or + self._count_dollars_before_index(text, space) % 2 == 0): + break + + if space < 0: + # No such space; just use the first unescaped space we can find. + space = available_space - 1 + while True: + space = text.find(' ', space + 1) + if (space < 0 or + self._count_dollars_before_index(text, space) % 2 == 0): + break + if space < 0: + # Give up on breaking. + break + + self.output.write(leading_space + text[0:space] + ' $\n') + text = text[space+1:] + + # Subsequent lines are continuations, so indent them. + leading_space = ' ' * (indent+2) + + self.output.write(leading_space + text + '\n') + + def close(self): + self.output.close() + + +def as_list(input): + if input is None: + return [] + if isinstance(input, list): + return input + return [input] + +# -- end from ninja_syntax.py -- + +def gen(ninja, toolchain, config): + + ninja.variable('ninja_required_version', '1.4') + ninja.newline() + + if hasattr(config, "builddir"): + builddir = config.builddir[toolchain] + ninja.variable(toolchain + 'builddir', builddir) + else: + builddir = '' + + ninja.variable(toolchain + 'defines', config.defines[toolchain] or []) + ninja.variable(toolchain + 'includes', config.includes[toolchain] or []) + ninja.variable(toolchain + 'cflags', config.cflags[toolchain] or []) + ninja.variable(toolchain + 'cxxflags', config.cxxflags[toolchain] or []) + ninja.variable(toolchain + 'ldflags', config.ldflags[toolchain] or []) + ninja.newline() + + if hasattr(config, "link_pool_depth"): + ninja.pool('link_pool', depth=config.link_pool_depth) + else: + ninja.pool('link_pool', depth=4) + ninja.newline() + + # Add default toolchain(gnu, clang and msvc) + add_gnu_rule(ninja) + add_clang_rule(ninja) + add_msvc_rule(ninja) + + obj_files = [] + + cc = toolchain + 'cc' + cxx = toolchain + 'cxx' + link = toolchain + 'link' + ar = toolchain + 'ar' + + if hasattr(config, "cxx_files"): + for src in config.cxx_files: + srcfile = src + obj = os.path.splitext(srcfile)[0] + '.o' + obj = os.path.join(builddir, obj); + obj_files.append(obj) + ninja.build(obj, cxx, srcfile) + ninja.newline() + + if hasattr(config, "c_files"): + for src in config.c_files: + srcfile = src + obj = os.path.splitext(srcfile)[0] + '.o' + obj = os.path.join(builddir, obj); + obj_files.append(obj) + ninja.build(obj, cc, srcfile) + ninja.newline() + + targetlist = [] + if hasattr(config, "exe"): + ninja.build(config.exe, link, obj_files) + targetlist.append(config.exe) + + if hasattr(config, "staticlib"): + ninja.build(config.staticlib, ar, obj_files) + targetlist.append(config.staticlib) + + ninja.build('all', 'phony', targetlist) + ninja.newline() + + ninja.default('all') + +def main(): + if len(sys.argv) < 2: + print("Usage: python kuroga.py config.py") + sys.exit(1) + + config = imp.load_source("config", sys.argv[1]) + + f = open('build.ninja', 'w') + ninja = Writer(f) + + if hasattr(config, "register_toolchain"): + config.register_toolchain(ninja) + + gen(ninja, config.toolchain, config) + f.close() + +main() diff --git a/tests/tester.cc b/tests/tester.cc new file mode 100644 index 0000000..edb92ba --- /dev/null +++ b/tests/tester.cc @@ -0,0 +1,324 @@ +#define TINYOBJLOADER_IMPLEMENTATION +#include "../tiny_obj_loader.h" + +#define CATCH_CONFIG_MAIN // This tells Catch to provide a main() - only do this in one cpp file +#include "catch.hpp" + +#include +#include +#include +#include +#include +#include + +static void PrintInfo(const tinyobj::attrib_t &attrib, const std::vector& shapes, const std::vector& materials, bool triangulate = true) +{ + std::cout << "# of vertices : " << (attrib.vertices.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.vertices.size() / 3; v++) { + printf(" v[%ld] = (%f, %f, %f)\n", v, + static_cast(attrib.vertices[3*v+0]), + static_cast(attrib.vertices[3*v+1]), + static_cast(attrib.vertices[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()); + + if (triangulate) + { + 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++) { + 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++) { + 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()); + assert(shapes[i].mesh.material_ids.size() == shapes[i].mesh.num_vertices.size()); + for (size_t m = 0; m < shapes[i].mesh.material_ids.size(); m++) { + printf(" material_id[%ld] = %d\n", m, + shapes[i].mesh.material_ids[m]); + } + + } + + printf("shape[%ld].num_faces: %ld\n", i, shapes[i].mesh.num_vertices.size()); + for (size_t v = 0; v < shapes[i].mesh.num_vertices.size(); v++) { + printf(" num_vertices[%ld] = %ld\n", v, + 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, + // 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++) { + printf(" tag[%ld] = %s ", t, shapes[i].mesh.tags[t].name.c_str()); + printf(" ints: ["); + for (size_t j = 0; j < shapes[i].mesh.tags[t].intValues.size(); ++j) + { + printf("%ld", static_cast(shapes[i].mesh.tags[t].intValues[j])); + if (j < (shapes[i].mesh.tags[t].intValues.size()-1)) + { + printf(", "); + } + } + printf("]"); + + printf(" floats: ["); + for (size_t j = 0; j < shapes[i].mesh.tags[t].floatValues.size(); ++j) + { + printf("%f", static_cast(shapes[i].mesh.tags[t].floatValues[j])); + if (j < (shapes[i].mesh.tags[t].floatValues.size()-1)) + { + printf(", "); + } + } + printf("]"); + + printf(" strings: ["); + for (size_t j = 0; j < shapes[i].mesh.tags[t].stringValues.size(); ++j) + { + printf("%s", shapes[i].mesh.tags[t].stringValues[j].c_str()); + if (j < (shapes[i].mesh.tags[t].stringValues.size()-1)) + { + printf(", "); + } + } + printf("]"); + printf("\n"); + } + } + + 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", static_cast(materials[i].ambient[0]), static_cast(materials[i].ambient[1]), static_cast(materials[i].ambient[2])); + printf(" material.Kd = (%f, %f ,%f)\n", static_cast(materials[i].diffuse[0]), static_cast(materials[i].diffuse[1]), static_cast(materials[i].diffuse[2])); + printf(" material.Ks = (%f, %f ,%f)\n", static_cast(materials[i].specular[0]), static_cast(materials[i].specular[1]), static_cast(materials[i].specular[2])); + printf(" material.Tr = (%f, %f ,%f)\n", static_cast(materials[i].transmittance[0]), static_cast(materials[i].transmittance[1]), static_cast(materials[i].transmittance[2])); + printf(" material.Ke = (%f, %f ,%f)\n", static_cast(materials[i].emission[0]), static_cast(materials[i].emission[1]), static_cast(materials[i].emission[2])); + printf(" material.Ns = %f\n", static_cast(materials[i].shininess)); + printf(" material.Ni = %f\n", static_cast(materials[i].ior)); + printf(" material.dissolve = %f\n", static_cast(materials[i].dissolve)); + printf(" material.illum = %d\n", materials[i].illum); + printf(" material.map_Ka = %s\n", materials[i].ambient_texname.c_str()); + printf(" material.map_Kd = %s\n", materials[i].diffuse_texname.c_str()); + printf(" material.map_Ks = %s\n", materials[i].specular_texname.c_str()); + printf(" material.map_Ns = %s\n", materials[i].specular_highlight_texname.c_str()); + printf(" material.map_bump = %s\n", materials[i].bump_texname.c_str()); + printf(" material.map_d = %s\n", materials[i].alpha_texname.c_str()); + printf(" material.disp = %s\n", materials[i].displacement_texname.c_str()); + std::map::const_iterator it(materials[i].unknown_parameter.begin()); + std::map::const_iterator itEnd(materials[i].unknown_parameter.end()); + + for (; it != itEnd; it++) { + printf(" material.%s = %s\n", it->first.c_str(), it->second.c_str()); + } + printf("\n"); + } +} + +static bool +TestLoadObj( + const char* filename, + const char* basepath = NULL, + bool triangulate = true) +{ + std::cout << "Loading " << filename << std::endl; + + tinyobj::attrib_t attrib; + std::vector shapes; + std::vector materials; + + std::string err; + bool ret = tinyobj::LoadObj(&attrib, &shapes, &materials, &err, filename, basepath, triangulate); + + if (!err.empty()) { + std::cerr << err << std::endl; + } + + if (!ret) { + printf("Failed to load/parse .obj.\n"); + return false; + } + + PrintInfo(attrib, shapes, materials, triangulate); + + return true; +} + + +static bool +TestStreamLoadObj() +{ + std::cout << "Stream Loading " << std::endl; + + std::stringstream objStream; + objStream + << "mtllib cube.mtl\n" + "\n" + "v 0.000000 2.000000 2.000000\n" + "v 0.000000 0.000000 2.000000\n" + "v 2.000000 0.000000 2.000000\n" + "v 2.000000 2.000000 2.000000\n" + "v 0.000000 2.000000 0.000000\n" + "v 0.000000 0.000000 0.000000\n" + "v 2.000000 0.000000 0.000000\n" + "v 2.000000 2.000000 0.000000\n" + "# 8 vertices\n" + "\n" + "g front cube\n" + "usemtl white\n" + "f 1 2 3 4\n" + "g back cube\n" + "# expects white material\n" + "f 8 7 6 5\n" + "g right cube\n" + "usemtl red\n" + "f 4 3 7 8\n" + "g top cube\n" + "usemtl white\n" + "f 5 1 4 8\n" + "g left cube\n" + "usemtl green\n" + "f 5 6 2 1\n" + "g bottom cube\n" + "usemtl white\n" + "f 2 6 7 3\n" + "# 6 elements"; + +std::string matStream( + "newmtl white\n" + "Ka 0 0 0\n" + "Kd 1 1 1\n" + "Ks 0 0 0\n" + "\n" + "newmtl red\n" + "Ka 0 0 0\n" + "Kd 1 0 0\n" + "Ks 0 0 0\n" + "\n" + "newmtl green\n" + "Ka 0 0 0\n" + "Kd 0 1 0\n" + "Ks 0 0 0\n" + "\n" + "newmtl blue\n" + "Ka 0 0 0\n" + "Kd 0 0 1\n" + "Ks 0 0 0\n" + "\n" + "newmtl light\n" + "Ka 20 20 20\n" + "Kd 1 1 1\n" + "Ks 0 0 0"); + + using namespace tinyobj; + class MaterialStringStreamReader: + public MaterialReader + { + public: + MaterialStringStreamReader(const std::string& matSStream): m_matSStream(matSStream) {} + virtual ~MaterialStringStreamReader() {} + virtual bool operator() ( + const std::string& matId, + std::vector* materials, + std::map* matMap, + std::string* err) + { + (void)matId; + (void)err; + LoadMtl(matMap, materials, &m_matSStream); + return true; + } + + private: + std::stringstream m_matSStream; + }; + + MaterialStringStreamReader matSSReader(matStream); + tinyobj::attrib_t attrib; + std::vector shapes; + std::vector materials; + std::string err; + bool ret = tinyobj::LoadObj(&attrib, &shapes, &materials, &err, &objStream, &matSSReader); + + if (!err.empty()) { + std::cerr << err << std::endl; + } + + if (!ret) { + return false; + } + + PrintInfo(attrib, shapes, materials); + + return true; +} + +const char* gMtlBasePath = "../models"; + +TEST_CASE("cornell_box", "[Loader]") { + + REQUIRE(true == TestLoadObj("../models/cornell_box.obj", gMtlBasePath)); +} + +#if 0 +int +main( + int argc, + char **argv) +{ + if (argc > 1) { + const char* basepath = NULL; + if (argc > 2) { + basepath = argv[2]; + } + assert(true == TestLoadObj(argv[1], basepath)); + } else { + //assert(true == TestLoadObj("cornell_box.obj")); + //assert(true == TestLoadObj("cube.obj")); + assert(true == TestStreamLoadObj()); + assert(true == TestLoadObj("catmark_torus_creases0.obj", NULL, false)); + } + + return 0; +} +#endif diff --git a/tests/vcbuild.bat b/tests/vcbuild.bat new file mode 100644 index 0000000..3dbc6c1 --- /dev/null +++ b/tests/vcbuild.bat @@ -0,0 +1,3 @@ +chcp 437 +call "C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\vcvarsall.bat" x86_amd64 +ninja diff --git a/tiny_obj_loader.h b/tiny_obj_loader.h index e67bed3..9a03489 100644 --- a/tiny_obj_loader.h +++ b/tiny_obj_loader.h @@ -111,6 +111,33 @@ typedef struct { std::vector texcoords; // 'vt' } attrib_t; +typedef struct callback_t_ { + void (*vertex_cb)(void *user_data, float x, float y, float z); + void (*normal_cb)(void *user_data, float x, float y, float z); + void (*texcoord_cb)(void *user_data, float x, float y); + // -2147483648 will be passed for undefined index + void (*index_cb)(void *user_data, int v_idx, int vn_idx, int vt_idx); + // Index to material + void (*usemtl_cb)(void *user_data, int material_idx); + void (*mtllib_cb)(void *user_data, const material_t *materials, + int num_materials); + // There may be multiple group names + void (*group_cb)(void *user_data, const char **names, int num_names); + void (*object_cb)(void *user_data, const char *name); + + callback_t_() : + vertex_cb(NULL), + normal_cb(NULL), + texcoord_cb(NULL), + index_cb(NULL), + usemtl_cb(NULL), + mtllib_cb(NULL), + group_cb(NULL), + object_cb(NULL) { + } + +} callback_t; + class MaterialReader { public: MaterialReader() {} @@ -138,7 +165,6 @@ class MaterialFileReader : public MaterialReader { /// 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. /// Returns warning and error message into `err` /// 'mtl_basepath' is optional, and used for base path for .mtl file. @@ -149,6 +175,18 @@ bool LoadObj(attrib_t *attrib, std::vector *shapes, const char *filename, const char *mtl_basepath = NULL, bool triangulate = true); +/// Loads .obj from a file with custom user callback. +/// .mtl is loaded as usual and parsed material_t data will be passed to +/// `callback.mtllib_cb`. +/// Returns true when loading .obj/.mtl 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. +bool LoadObjWithCallback(void *user_data, const callback_t &callback, + std::string *err, std::istream *inStream, + MaterialReader *readMatFn); + /// Loads object from a std::istream, uses GetMtlIStreamFn to retrieve /// std::istream for materials. /// Returns true when loading .obj become success. @@ -418,7 +456,7 @@ static tag_sizes parseTagTriple(const char **token) { return ts; } -// Parse triples: i, i/j/k, i//k, i/j +// Parse triples with index offsets: 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); @@ -452,6 +490,39 @@ static vertex_index parseTriple(const char **token, int vsize, int vnsize, return vi; } +// Parse raw triples: i, i/j/k, i//k, i/j +static vertex_index parseRawTriple(const char **token) { + vertex_index vi(0x8000000); // -2147483648 = invalid + + vi.v_idx = atoi((*token)); + (*token) += strcspn((*token), "/ \t\r"); + if ((*token)[0] != '/') { + return vi; + } + (*token)++; + + // i//k + if ((*token)[0] == '/') { + (*token)++; + vi.vn_idx = atoi((*token)); + (*token) += strcspn((*token), "/ \t\r"); + return vi; + } + + // i/j/k or i/j + vi.vt_idx = atoi((*token)); + (*token) += strcspn((*token), "/ \t\r"); + if ((*token)[0] != '/') { + return vi; + } + + // i/j/k + (*token)++; // skip '/' + vi.vn_idx = atoi((*token)); + (*token) += strcspn((*token), "/ \t\r"); + return vi; +} + static void InitMaterial(material_t *material) { material->name = ""; material->ambient_texname = ""; @@ -831,7 +902,6 @@ bool LoadObj(attrib_t *attrib, std::vector *shapes, // material std::map material_map; - // std::map vertexCache; int material = -1; shape_t shape; @@ -1103,6 +1173,270 @@ bool LoadObj(attrib_t *attrib, std::vector *shapes, return true; } +bool LoadObjWithCallback(void *user_data, const callback_t &callback, + std::string *err, std::istream *inStream, + MaterialReader *readMatFn) { + std::stringstream errss; + + std::string name; + + // material + std::map material_map; + 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); + + 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); + } + + // Skip if empty line. + if (linebuf.empty()) { + continue; + } + + // Skip leading space. + const char *token = linebuf.c_str(); + token += strspn(token, " \t"); + + assert(token); + if (token[0] == '\0') continue; // empty line + + if (token[0] == '#') continue; // comment line + + // vertex + if (token[0] == 'v' && IS_SPACE((token[1]))) { + token += 2; + float x, y, z; + parseFloat3(&x, &y, &z, &token); + if (callback.vertex_cb) { + callback.vertex_cb(user_data, x, y, z); + } + continue; + } + + // normal + if (token[0] == 'v' && token[1] == 'n' && IS_SPACE((token[2]))) { + token += 3; + float x, y, z; + parseFloat3(&x, &y, &z, &token); + if (callback.normal_cb) { + callback.normal_cb(user_data, x, y, z); + } + continue; + } + + // texcoord + if (token[0] == 'v' && token[1] == 't' && IS_SPACE((token[2]))) { + token += 3; + float x, y; + parseFloat2(&x, &y, &token); + if (callback.texcoord_cb) { + callback.texcoord_cb(user_data, x, y); + } + continue; + } + + // face + if (token[0] == 'f' && IS_SPACE((token[1]))) { + token += 2; + token += strspn(token, " \t"); + + while (!IS_NEW_LINE(token[0])) { + vertex_index vi = parseRawTriple(&token); + if (callback.index_cb) { + callback.index_cb(user_data, vi.v_idx, vi.vn_idx, vi.vt_idx); + } + size_t n = strspn(token, " \t\r"); + token += n; + } + + continue; + } + + // use mtl + if ((0 == strncmp(token, "usemtl", 6)) && IS_SPACE((token[6]))) { + char namebuf[TINYOBJ_SSCANF_BUFFER_SIZE]; + token += 7; +#ifdef _MSC_VER + sscanf_s(token, "%s", namebuf, (unsigned)_countof(namebuf)); +#else + sscanf(token, "%s", namebuf); +#endif + + int newMaterialId = -1; + if (material_map.find(namebuf) != material_map.end()) { + newMaterialId = material_map[namebuf]; + } else { + // { error!! material not found } + } + + if (newMaterialId != material) { + material = newMaterialId; + } + + if (callback.usemtl_cb) { + callback.usemtl_cb(user_data, material); + } + + continue; + } + + // load mtl + if ((0 == strncmp(token, "mtllib", 6)) && IS_SPACE((token[6]))) { + char namebuf[TINYOBJ_SSCANF_BUFFER_SIZE]; + token += 7; +#ifdef _MSC_VER + sscanf_s(token, "%s", namebuf, (unsigned)_countof(namebuf)); +#else + sscanf(token, "%s", namebuf); +#endif + + std::string err_mtl; + std::vector materials; + bool ok = (*readMatFn)(namebuf, &materials, &material_map, &err_mtl); + if (err) { + (*err) += err_mtl; + } + + if (!ok) { + return false; + } + + if (callback.mtllib_cb) { + callback.mtllib_cb(user_data, &materials.at(0), + static_cast(materials.size())); + } + + continue; + } + + // group name + if (token[0] == 'g' && IS_SPACE((token[1]))) { + std::vector names; + names.reserve(2); + + while (!IS_NEW_LINE(token[0])) { + std::string str = parseString(&token); + names.push_back(str); + token += strspn(token, " \t\r"); // skip tag + } + + assert(names.size() > 0); + + // names[0] must be 'g', so skip the 0th element. + if (names.size() > 1) { + name = names[1]; + } else { + name = ""; + } + + if (callback.group_cb) { + if (names.size() > 1) { + // create const char* array. + std::vector tmp(names.size() - 1); + for (size_t j = 0; j < tmp.size(); j++) { + tmp[j] = names[j + 1].c_str(); + } + callback.group_cb(user_data, &tmp.at(0), + static_cast(tmp.size())); + + } else { + callback.group_cb(user_data, NULL, 0); + } + + continue; + } + + // object name + if (token[0] == 'o' && IS_SPACE((token[1]))) { + // @todo { multiple object name? } + char namebuf[TINYOBJ_SSCANF_BUFFER_SIZE]; + token += 2; +#ifdef _MSC_VER + sscanf_s(token, "%s", namebuf, (unsigned)_countof(namebuf)); +#else + sscanf(token, "%s", namebuf); +#endif + name = std::string(namebuf); + + if (callback.object_cb) { + callback.object_cb(user_data, name.c_str()); + } + + continue; + } + +#if 0 // @todo + if (token[0] == 't' && IS_SPACE(token[1])) { + tag_t tag; + + char namebuf[4096]; + token += 2; +#ifdef _MSC_VER + sscanf_s(token, "%s", namebuf, (unsigned)_countof(namebuf)); +#else + sscanf(token, "%s", namebuf); +#endif + tag.name = std::string(namebuf); + + token += tag.name.size() + 1; + + tag_sizes ts = parseTagTriple(&token); + + tag.intValues.resize(static_cast(ts.num_ints)); + + for (size_t i = 0; i < static_cast(ts.num_ints); ++i) { + tag.intValues[i] = atoi(token); + token += strcspn(token, "/ \t\r") + 1; + } + + tag.floatValues.resize(static_cast(ts.num_floats)); + for (size_t i = 0; i < static_cast(ts.num_floats); ++i) { + tag.floatValues[i] = parseFloat(&token); + token += strcspn(token, "/ \t\r") + 1; + } + + tag.stringValues.resize(static_cast(ts.num_strings)); + for (size_t i = 0; i < static_cast(ts.num_strings); ++i) { + char stringValueBuffer[4096]; + +#ifdef _MSC_VER + sscanf_s(token, "%s", stringValueBuffer, + (unsigned)_countof(stringValueBuffer)); +#else + sscanf(token, "%s", stringValueBuffer); +#endif + tag.stringValues[i] = stringValueBuffer; + token += tag.stringValues[i].size() + 1; + } + + tags.push_back(tag); +#endif + } + + // Ignore unknown command. + } + + if (err) { + (*err) += errss.str(); + } + + return true; +} } // namespace tinyobj #endif -- cgit v1.2.3 From 93acf631571ded19556a4f4684541f5ecb3c8f10 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Mon, 18 Apr 2016 16:48:37 +0900 Subject: Change API for usemtl callback. --- tiny_obj_loader.h | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/tiny_obj_loader.h b/tiny_obj_loader.h index 9a03489..d46c704 100644 --- a/tiny_obj_loader.h +++ b/tiny_obj_loader.h @@ -117,8 +117,9 @@ typedef struct callback_t_ { void (*texcoord_cb)(void *user_data, float x, float y); // -2147483648 will be passed for undefined index void (*index_cb)(void *user_data, int v_idx, int vn_idx, int vt_idx); - // Index to material - void (*usemtl_cb)(void *user_data, int material_idx); + // `name` material name, `materialId` = the array index of material_t[]. -1 if a material not found in .mtl + void (*usemtl_cb)(void *user_data, const char* name, int materialId); + // `materials` = parsed material data. void (*mtllib_cb)(void *user_data, const material_t *materials, int num_materials); // There may be multiple group names @@ -1178,13 +1179,9 @@ bool LoadObjWithCallback(void *user_data, const callback_t &callback, MaterialReader *readMatFn) { std::stringstream errss; - std::string name; - // material std::map material_map; - int material = -1; - - shape_t shape; + int materialId = -1; // -1 = invalid int maxchars = 8192; // Alloc enough size. std::vector buf(static_cast(maxchars)); // Alloc enough size. @@ -1284,12 +1281,12 @@ bool LoadObjWithCallback(void *user_data, const callback_t &callback, // { error!! material not found } } - if (newMaterialId != material) { - material = newMaterialId; + if (newMaterialId != materialId) { + materialId = newMaterialId; } if (callback.usemtl_cb) { - callback.usemtl_cb(user_data, material); + callback.usemtl_cb(user_data, namebuf, materialId); } continue; @@ -1337,6 +1334,7 @@ bool LoadObjWithCallback(void *user_data, const callback_t &callback, assert(names.size() > 0); + std::string name; // names[0] must be 'g', so skip the 0th element. if (names.size() > 1) { name = names[1]; -- cgit v1.2.3 From 1e663342bf8d3ea41378e536abd26a609cb45ca0 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Mon, 18 Apr 2016 17:52:04 +0900 Subject: Add callback API example. Fix a sentinel value for the vertex index. --- README.md | 5 ++ examples/callback_api/Makefile | 2 + examples/callback_api/main.cc | 155 +++++++++++++++++++++++++++++++++++++++++ tiny_obj_loader.h | 2 +- 4 files changed, 163 insertions(+), 1 deletion(-) create mode 100644 examples/callback_api/Makefile create mode 100644 examples/callback_api/main.cc diff --git a/README.md b/README.md index 056f7c3..a180d70 100644 --- a/README.md +++ b/README.md @@ -193,3 +193,8 @@ for (size_t i = 0; i < shapes.size(); i++) { } ``` + +Tests +----- + +Unit tests are provided. see `tests` directory for details. diff --git a/examples/callback_api/Makefile b/examples/callback_api/Makefile new file mode 100644 index 0000000..45d60d8 --- /dev/null +++ b/examples/callback_api/Makefile @@ -0,0 +1,2 @@ +all: + clang++ -I../../ -Wall -g -o example main.cc diff --git a/examples/callback_api/main.cc b/examples/callback_api/main.cc new file mode 100644 index 0000000..4f492d0 --- /dev/null +++ b/examples/callback_api/main.cc @@ -0,0 +1,155 @@ +#define TINYOBJLOADER_IMPLEMENTATION +#include "tiny_obj_loader.h" + +#include +#include +#include +#include +#include +#include + +typedef struct +{ + std::vector vertices; + std::vector normals; + std::vector texcoords; + std::vector v_indices; + std::vector vn_indices; + std::vector vt_indices; + + std::vector materials; + +} MyMesh; + +void vertex_cb(void *user_data, float x, float y, float z) +{ + MyMesh *mesh = reinterpret_cast(user_data); + printf("v[%ld] = %f, %f, %f\n", mesh->vertices.size() / 3, x, y, z); + + mesh->vertices.push_back(x); + mesh->vertices.push_back(y); + mesh->vertices.push_back(z); +} + +void normal_cb(void *user_data, float x, float y, float z) +{ + MyMesh *mesh = reinterpret_cast(user_data); + printf("vn[%ld] = %f, %f, %f\n", mesh->normals.size() / 3, x, y, z); + + mesh->normals.push_back(x); + mesh->normals.push_back(y); + mesh->normals.push_back(z); +} + +void texcoord_cb(void *user_data, float x, float y) +{ + MyMesh *mesh = reinterpret_cast(user_data); + printf("vt[%ld] = %f, %f\n", mesh->texcoords.size() / 2, x, y); + + mesh->texcoords.push_back(x); + mesh->texcoords.push_back(y); +} + +void index_cb(void *user_data, int v_idx, int vn_idx, int vt_idx) +{ + // NOTE: the value of each index is raw value. + // For example, the application must manually adjust the index with offset + // (e.g. v_indices.size()) when the value is negative(relative index). + // See fixIndex() function in tiny_obj_loader.h for details. + // Also, -2147483648(0x80000000) is set for the index value which does not exist in .obj + MyMesh *mesh = reinterpret_cast(user_data); + printf("idx[%ld] = %d, %d, %d\n", mesh->v_indices.size(), v_idx, vn_idx, vt_idx); + + if (v_idx != 0x80000000) { + mesh->v_indices.push_back(v_idx); + } + if (vn_idx != 0x80000000) { + mesh->vn_indices.push_back(vn_idx); + } + if (vt_idx != 0x80000000) { + mesh->vt_indices.push_back(vt_idx); + } +} + +void usemtl_cb(void *user_data, const char* name, int material_idx) +{ + MyMesh *mesh = reinterpret_cast(user_data); + if ((material_idx > -1) && (material_idx < mesh->materials.size())) { + printf("usemtl. material id = %d(name = %s)\n", material_idx, mesh->materials[material_idx].name.c_str()); + } else { + printf("usemtl. name = %s\n", name); + } +} + +void mtllib_cb(void *user_data, const tinyobj::material_t *materials, int num_materials) +{ + MyMesh *mesh = reinterpret_cast(user_data); + printf("mtllib. # of materials = %d\n", num_materials); + + for (int i = 0; i < num_materials; i++) { + mesh->materials.push_back(materials[i]); + } +} + +void group_cb(void *user_data, const char **names, int num_names) +{ + //MyMesh *mesh = reinterpret_cast(user_data); + printf("group : name = \n"); + + for (int i = 0; i < num_names; i++) { + printf(" %s\n", names[i]); + } +} + +void object_cb(void *user_data, const char *name) +{ + //MyMesh *mesh = reinterpret_cast(user_data); + printf("object : name = %s\n", name); + +} + +int +main(int argc, char** argv) +{ + tinyobj::callback_t cb; + cb.vertex_cb = vertex_cb; + cb.normal_cb = normal_cb; + cb.texcoord_cb = texcoord_cb; + cb.index_cb = index_cb; + cb.usemtl_cb = usemtl_cb; + cb.mtllib_cb = mtllib_cb; + cb.group_cb = group_cb; + cb.object_cb = object_cb; + + MyMesh mesh; + std::string err; + std::ifstream ifs("../../models/cornell_box.obj"); + + if (ifs.fail()) { + std::cerr << "file not found." << std::endl; + return EXIT_FAILURE; + } + + tinyobj::MaterialFileReader mtlReader("../../models/"); + + bool ret = tinyobj::LoadObjWithCallback(&mesh, cb, &err, &ifs, &mtlReader); + + if (!err.empty()) { + std::cerr << err << std::endl; + } + + if (!ret) { + std::cerr << "Failed to parse .obj" << std::endl; + return EXIT_FAILURE; + } + + printf("# of vertices = %ld\n", mesh.vertices.size() / 3); + printf("# of normals = %ld\n", mesh.normals.size() / 3); + printf("# of texcoords = %ld\n", mesh.texcoords.size() / 2); + printf("# of vertex indices = %ld\n", mesh.v_indices.size()); + printf("# of normal indices = %ld\n", mesh.vn_indices.size()); + printf("# of texcoord indices = %ld\n", mesh.vt_indices.size()); + printf("# of materials = %ld\n", mesh.materials.size()); + + return EXIT_SUCCESS; +} diff --git a/tiny_obj_loader.h b/tiny_obj_loader.h index d46c704..d2aae38 100644 --- a/tiny_obj_loader.h +++ b/tiny_obj_loader.h @@ -493,7 +493,7 @@ static vertex_index parseTriple(const char **token, int vsize, int vnsize, // Parse raw triples: i, i/j/k, i//k, i/j static vertex_index parseRawTriple(const char **token) { - vertex_index vi(0x8000000); // -2147483648 = invalid + vertex_index vi(0x80000000); // 0x80000000 = -2147483648 = invalid vi.v_idx = atoi((*token)); (*token) += strcspn((*token), "/ \t\r"); -- cgit v1.2.3 From 90d33c33c02359b3a545664cd60eae84d04788a2 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Mon, 18 Apr 2016 17:59:17 +0900 Subject: Fix CI scripts. --- .travis.yml | 11 ++--------- tests/Makefile | 5 ++++- wercker.yml | 6 ++---- 3 files changed, 8 insertions(+), 14 deletions(-) diff --git a/.travis.yml b/.travis.yml index bde52f6..3ec9e45 100644 --- a/.travis.yml +++ b/.travis.yml @@ -42,15 +42,8 @@ before_install: - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew upgrade; fi - if [ -n "$REPORT_COVERAGE" ]; then pip install --user cpp-coveralls; fi script: -- mkdir build && cd build -- export CC="${CC}-${COMPILER_VERSION}" -- export CXX="${CXX}-${COMPILER_VERSION}" -- ${CC} -v -- cmake --version -- cmake -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DTINYOBJLOADER_BUILD_TEST_LOADER=On -G Ninja - .. -- ninja -- ./test_loader ../cornell_box.obj +- cd tests +- make check - if [ -n "$REPORT_COVERAGE" ]; then coveralls -b . -r .. -e examples -e tools -e jni -e python -e images -E ".*CompilerId.*" -E ".*feature_tests.*" ; fi - cd .. diff --git a/tests/Makefile b/tests/Makefile index 1a1434a..4a18c71 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -1,7 +1,10 @@ .PHONY: clean +CXX ?= g++ +CXXFLAGS ?= -g -O2 + tester: tester.cc - g++ -g -O0 -o tester tester.cc + $(CXX) $(CXXFLAGS) -o tester tester.cc all: tester diff --git a/wercker.yml b/wercker.yml index 3c1583c..1d1aa26 100644 --- a/wercker.yml +++ b/wercker.yml @@ -6,7 +6,5 @@ build: name: build code: | git clone https://github.com/syoyo/orebuildenv.git - chmod +x ./orebuildenv/build/linux/bin/premake4 - ./orebuildenv/build/linux/bin/premake4 gmake - make - ./test_tinyobjloader + cd tests + make check -- cgit v1.2.3 From 153de2b3f04a44245ae6acd09eeaf0fff2e3efe0 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Mon, 18 Apr 2016 19:29:59 +0900 Subject: Update Android build. --- README.md | 90 +++------------------------------------------------------- jni/Android.mk | 4 +-- 2 files changed, 6 insertions(+), 88 deletions(-) diff --git a/README.md b/README.md index a180d70..35dfd1e 100644 --- a/README.md +++ b/README.md @@ -73,12 +73,13 @@ Features * Material * Unknown material attributes are returned as key-value(value is string) map. * Crease tag('t'). This is OpenSubdiv specific(not in wavefront .obj specification) +* Callback API for custom loading. TODO ---- -* [ ] Support different indices for vertex/normal/texcoord +* [ ] Read .obj/.mtl from memory License ------- @@ -88,7 +89,6 @@ Licensed under 2 clause BSD. Usage ----- -TinyObjLoader triangulate input .obj by default. ```c++ #define TINYOBJLOADER_IMPLEMENTATION // define this in only *one* .cc #include "tiny_obj_loader.h" @@ -108,93 +108,11 @@ if (!ret) { exit(1); } -std::cout << "# of shapes : " << shapes.size() << std::endl; -std::cout << "# of materials : " << materials.size() << std::endl; - -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()); - 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]); - } - - 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]); - } -} - -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]); - printf(" material.Kd = (%f, %f ,%f)\n", materials[i].diffuse[0], materials[i].diffuse[1], materials[i].diffuse[2]); - printf(" material.Ks = (%f, %f ,%f)\n", materials[i].specular[0], materials[i].specular[1], materials[i].specular[2]); - printf(" material.Tr = (%f, %f ,%f)\n", materials[i].transmittance[0], materials[i].transmittance[1], materials[i].transmittance[2]); - printf(" material.Ke = (%f, %f ,%f)\n", materials[i].emission[0], materials[i].emission[1], materials[i].emission[2]); - printf(" material.Ns = %f\n", materials[i].shininess); - printf(" material.Ni = %f\n", materials[i].ior); - printf(" material.dissolve = %f\n", materials[i].dissolve); - printf(" material.illum = %d\n", materials[i].illum); - printf(" material.map_Ka = %s\n", materials[i].ambient_texname.c_str()); - printf(" material.map_Kd = %s\n", materials[i].diffuse_texname.c_str()); - printf(" material.map_Ks = %s\n", materials[i].specular_texname.c_str()); - printf(" material.map_Ns = %s\n", materials[i].specular_highlight_texname.c_str()); - std::map::const_iterator it(materials[i].unknown_parameter.begin()); - std::map::const_iterator itEnd(materials[i].unknown_parameter.end()); - for (; it != itEnd; it++) { - printf(" material.%s = %s\n", it->first.c_str(), it->second.c_str()); - } - printf("\n"); -} +// See loader_example.cc for more details. ``` -Reading .obj without triangulation. Use `num_vertices[i]` to iterate over faces(indices). `num_vertices[i]` stores the number of vertices for ith face. -```c++ -#define TINYOBJLOADER_IMPLEMENTATION // define this in only *one* .cc -#include "tiny_obj_loader.h" - -std::string inputfile = "cornell_box.obj"; -std::vector shapes; -std::vector materials; - -std::string err; -bool triangulate = false; -bool ret = tinyobj::LoadObj(shapes, materials, err, inputfile.c_str(), triangulate); - -if (!err.empty()) { // `err` may contain warning message. - std::cerr << err << std::endl; -} - -if (!ret) { - exit(1); -} - -for (size_t i = 0; i < shapes.size(); i++) { - - size_t indexOffset = 0; - for (size_t n = 0; n < shapes[i].mesh.num_vertices.size(); n++) { - int ngon = shapes[i].mesh.num_vertices[n]; - for (size_t f = 0; f < ngon; f++) { - unsigned int v = shapes[i].mesh.indices[indexOffset + f]; - printf(" face[%ld] v[%ld] = (%f, %f, %f)\n", n, - shapes[i].mesh.positions[3*v+0], - shapes[i].mesh.positions[3*v+1], - shapes[i].mesh.positions[3*v+2]); - - } - indexOffset += ngon; - } - -} -``` Tests ----- -Unit tests are provided. see `tests` directory for details. +Unit tests are provided in `tests` directory. See `tests/README.md` for details. diff --git a/jni/Android.mk b/jni/Android.mk index eaed1b7..bc81f8f 100644 --- a/jni/Android.mk +++ b/jni/Android.mk @@ -5,8 +5,8 @@ LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE := tinyobjloader -LOCAL_SRC_FILES := ../tiny_obj_loader.cc ../test.cc +LOCAL_SRC_FILES := ../tiny_obj_loader.cc LOCAL_C_INCLUDES := ../ -include $(BUILD_EXECUTABLE) +include $(BUILD_STATIC_LIBRARY) -- cgit v1.2.3 From 1703ab087db0e90fc807ddd176a1ea426b7cf7a5 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Mon, 18 Apr 2016 20:07:04 +0900 Subject: Add simple OpenGL viewer example. --- examples/viewer/Makefile | 8 + examples/viewer/README.md | 1 + examples/viewer/trackball.cc | 292 +++++++++++++++++++++++++++++++ examples/viewer/trackball.h | 75 ++++++++ examples/viewer/viewer.cc | 403 +++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 779 insertions(+) create mode 100644 examples/viewer/Makefile create mode 100644 examples/viewer/README.md create mode 100644 examples/viewer/trackball.cc create mode 100644 examples/viewer/trackball.h create mode 100644 examples/viewer/viewer.cc diff --git a/examples/viewer/Makefile b/examples/viewer/Makefile new file mode 100644 index 0000000..e5204fc --- /dev/null +++ b/examples/viewer/Makefile @@ -0,0 +1,8 @@ +GLFW_INC=-I/usr/local/include +GLFW_LIBS=-L/usr/local/lib -lglfw3 -lglew +GL_LIBS=-framework OpenGL + +CXX_FLAGS=-Wno-deprecated-declarations + +all: + g++ -fsanitize=address -O0 -g -o objviewer $(CXX_FLAGS) viewer.cc ../../tiny_obj_loader.cc trackball.cc $(GLFW_INC) $(GL_LIBS) $(GLFW_LIBS) diff --git a/examples/viewer/README.md b/examples/viewer/README.md new file mode 100644 index 0000000..4e0e087 --- /dev/null +++ b/examples/viewer/README.md @@ -0,0 +1 @@ +Simple .obj viewer with glew + glfw3 + OpenGL diff --git a/examples/viewer/trackball.cc b/examples/viewer/trackball.cc new file mode 100644 index 0000000..86ff3b3 --- /dev/null +++ b/examples/viewer/trackball.cc @@ -0,0 +1,292 @@ +/* + * (c) Copyright 1993, 1994, Silicon Graphics, Inc. + * ALL RIGHTS RESERVED + * Permission to use, copy, modify, and distribute this software for + * any purpose and without fee is hereby granted, provided that the above + * copyright notice appear in all copies and that both the copyright notice + * and this permission notice appear in supporting documentation, and that + * the name of Silicon Graphics, Inc. not be used in advertising + * or publicity pertaining to distribution of the software without specific, + * written prior permission. + * + * THE MATERIAL EMBODIED ON THIS SOFTWARE IS PROVIDED TO YOU "AS-IS" + * AND WITHOUT WARRANTY OF ANY KIND, EXPRESS, IMPLIED OR OTHERWISE, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTY OF MERCHANTABILITY OR + * FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT SHALL SILICON + * GRAPHICS, INC. BE LIABLE TO YOU OR ANYONE ELSE FOR ANY DIRECT, + * SPECIAL, INCIDENTAL, INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY + * KIND, OR ANY DAMAGES WHATSOEVER, INCLUDING WITHOUT LIMITATION, + * LOSS OF PROFIT, LOSS OF USE, SAVINGS OR REVENUE, OR THE CLAIMS OF + * THIRD PARTIES, WHETHER OR NOT SILICON GRAPHICS, INC. HAS BEEN + * ADVISED OF THE POSSIBILITY OF SUCH LOSS, HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, ARISING OUT OF OR IN CONNECTION WITH THE + * POSSESSION, USE OR PERFORMANCE OF THIS SOFTWARE. + * + * US Government Users Restricted Rights + * Use, duplication, or disclosure by the Government is subject to + * restrictions set forth in FAR 52.227.19(c)(2) or subparagraph + * (c)(1)(ii) of the Rights in Technical Data and Computer Software + * clause at DFARS 252.227-7013 and/or in similar or successor + * clauses in the FAR or the DOD or NASA FAR Supplement. + * Unpublished-- rights reserved under the copyright laws of the + * United States. Contractor/manufacturer is Silicon Graphics, + * Inc., 2011 N. Shoreline Blvd., Mountain View, CA 94039-7311. + * + * OpenGL(TM) is a trademark of Silicon Graphics, Inc. + */ +/* + * Trackball code: + * + * Implementation of a virtual trackball. + * Implemented by Gavin Bell, lots of ideas from Thant Tessman and + * the August '88 issue of Siggraph's "Computer Graphics," pp. 121-129. + * + * Vector manip code: + * + * Original code from: + * David M. Ciemiewicz, Mark Grossman, Henry Moreton, and Paul Haeberli + * + * Much mucking with by: + * Gavin Bell + */ +#include +#include "trackball.h" + +/* + * This size should really be based on the distance from the center of + * rotation to the point on the object underneath the mouse. That + * point would then track the mouse as closely as possible. This is a + * simple example, though, so that is left as an Exercise for the + * Programmer. + */ +#define TRACKBALLSIZE (0.8) + +/* + * Local function prototypes (not defined in trackball.h) + */ +static float tb_project_to_sphere(float, float, float); +static void normalize_quat(float[4]); + +static void vzero(float *v) { + v[0] = 0.0; + v[1] = 0.0; + v[2] = 0.0; +} + +static void vset(float *v, float x, float y, float z) { + v[0] = x; + v[1] = y; + v[2] = z; +} + +static void vsub(const float *src1, const float *src2, float *dst) { + dst[0] = src1[0] - src2[0]; + dst[1] = src1[1] - src2[1]; + dst[2] = src1[2] - src2[2]; +} + +static void vcopy(const float *v1, float *v2) { + register int i; + for (i = 0; i < 3; i++) + v2[i] = v1[i]; +} + +static void vcross(const float *v1, const float *v2, float *cross) { + float temp[3]; + + temp[0] = (v1[1] * v2[2]) - (v1[2] * v2[1]); + temp[1] = (v1[2] * v2[0]) - (v1[0] * v2[2]); + temp[2] = (v1[0] * v2[1]) - (v1[1] * v2[0]); + vcopy(temp, cross); +} + +static float vlength(const float *v) { + return sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]); +} + +static void vscale(float *v, float div) { + v[0] *= div; + v[1] *= div; + v[2] *= div; +} + +static void vnormal(float *v) { vscale(v, 1.0 / vlength(v)); } + +static float vdot(const float *v1, const float *v2) { + return v1[0] * v2[0] + v1[1] * v2[1] + v1[2] * v2[2]; +} + +static void vadd(const float *src1, const float *src2, float *dst) { + dst[0] = src1[0] + src2[0]; + dst[1] = src1[1] + src2[1]; + dst[2] = src1[2] + src2[2]; +} + +/* + * Ok, simulate a track-ball. Project the points onto the virtual + * trackball, then figure out the axis of rotation, which is the cross + * product of P1 P2 and O P1 (O is the center of the ball, 0,0,0) + * Note: This is a deformed trackball-- is a trackball in the center, + * but is deformed into a hyperbolic sheet of rotation away from the + * center. This particular function was chosen after trying out + * several variations. + * + * It is assumed that the arguments to this routine are in the range + * (-1.0 ... 1.0) + */ +void trackball(float q[4], float p1x, float p1y, float p2x, float p2y) { + float a[3]; /* Axis of rotation */ + float phi; /* how much to rotate about axis */ + float p1[3], p2[3], d[3]; + float t; + + if (p1x == p2x && p1y == p2y) { + /* Zero rotation */ + vzero(q); + q[3] = 1.0; + return; + } + + /* + * First, figure out z-coordinates for projection of P1 and P2 to + * deformed sphere + */ + vset(p1, p1x, p1y, tb_project_to_sphere(TRACKBALLSIZE, p1x, p1y)); + vset(p2, p2x, p2y, tb_project_to_sphere(TRACKBALLSIZE, p2x, p2y)); + + /* + * Now, we want the cross product of P1 and P2 + */ + vcross(p2, p1, a); + + /* + * Figure out how much to rotate around that axis. + */ + vsub(p1, p2, d); + t = vlength(d) / (2.0 * TRACKBALLSIZE); + + /* + * Avoid problems with out-of-control values... + */ + if (t > 1.0) + t = 1.0; + if (t < -1.0) + t = -1.0; + phi = 2.0 * asin(t); + + axis_to_quat(a, phi, q); +} + +/* + * Given an axis and angle, compute quaternion. + */ +void axis_to_quat(float a[3], float phi, float q[4]) { + vnormal(a); + vcopy(a, q); + vscale(q, sin(phi / 2.0)); + q[3] = cos(phi / 2.0); +} + +/* + * Project an x,y pair onto a sphere of radius r OR a hyperbolic sheet + * if we are away from the center of the sphere. + */ +static float tb_project_to_sphere(float r, float x, float y) { + float d, t, z; + + d = sqrt(x * x + y * y); + if (d < r * 0.70710678118654752440) { /* Inside sphere */ + z = sqrt(r * r - d * d); + } else { /* On hyperbola */ + t = r / 1.41421356237309504880; + z = t * t / d; + } + return z; +} + +/* + * Given two rotations, e1 and e2, expressed as quaternion rotations, + * figure out the equivalent single rotation and stuff it into dest. + * + * This routine also normalizes the result every RENORMCOUNT times it is + * called, to keep error from creeping in. + * + * NOTE: This routine is written so that q1 or q2 may be the same + * as dest (or each other). + */ + +#define RENORMCOUNT 97 + +void add_quats(float q1[4], float q2[4], float dest[4]) { + static int count = 0; + float t1[4], t2[4], t3[4]; + float tf[4]; + + vcopy(q1, t1); + vscale(t1, q2[3]); + + vcopy(q2, t2); + vscale(t2, q1[3]); + + vcross(q2, q1, t3); + vadd(t1, t2, tf); + vadd(t3, tf, tf); + tf[3] = q1[3] * q2[3] - vdot(q1, q2); + + dest[0] = tf[0]; + dest[1] = tf[1]; + dest[2] = tf[2]; + dest[3] = tf[3]; + + if (++count > RENORMCOUNT) { + count = 0; + normalize_quat(dest); + } +} + +/* + * Quaternions always obey: a^2 + b^2 + c^2 + d^2 = 1.0 + * If they don't add up to 1.0, dividing by their magnitued will + * renormalize them. + * + * Note: See the following for more information on quaternions: + * + * - Shoemake, K., Animating rotation with quaternion curves, Computer + * Graphics 19, No 3 (Proc. SIGGRAPH'85), 245-254, 1985. + * - Pletinckx, D., Quaternion calculus as a basic tool in computer + * graphics, The Visual Computer 5, 2-13, 1989. + */ +static void normalize_quat(float q[4]) { + int i; + float mag; + + mag = (q[0] * q[0] + q[1] * q[1] + q[2] * q[2] + q[3] * q[3]); + for (i = 0; i < 4; i++) + q[i] /= mag; +} + +/* + * Build a rotation matrix, given a quaternion rotation. + * + */ +void build_rotmatrix(float m[4][4], const float q[4]) { + m[0][0] = 1.0 - 2.0 * (q[1] * q[1] + q[2] * q[2]); + m[0][1] = 2.0 * (q[0] * q[1] - q[2] * q[3]); + m[0][2] = 2.0 * (q[2] * q[0] + q[1] * q[3]); + m[0][3] = 0.0; + + m[1][0] = 2.0 * (q[0] * q[1] + q[2] * q[3]); + m[1][1] = 1.0 - 2.0 * (q[2] * q[2] + q[0] * q[0]); + m[1][2] = 2.0 * (q[1] * q[2] - q[0] * q[3]); + m[1][3] = 0.0; + + m[2][0] = 2.0 * (q[2] * q[0] - q[1] * q[3]); + m[2][1] = 2.0 * (q[1] * q[2] + q[0] * q[3]); + m[2][2] = 1.0 - 2.0 * (q[1] * q[1] + q[0] * q[0]); + m[2][3] = 0.0; + + m[3][0] = 0.0; + m[3][1] = 0.0; + m[3][2] = 0.0; + m[3][3] = 1.0; +} diff --git a/examples/viewer/trackball.h b/examples/viewer/trackball.h new file mode 100644 index 0000000..b1f9437 --- /dev/null +++ b/examples/viewer/trackball.h @@ -0,0 +1,75 @@ +/* + * (c) Copyright 1993, 1994, Silicon Graphics, Inc. + * ALL RIGHTS RESERVED + * Permission to use, copy, modify, and distribute this software for + * any purpose and without fee is hereby granted, provided that the above + * copyright notice appear in all copies and that both the copyright notice + * and this permission notice appear in supporting documentation, and that + * the name of Silicon Graphics, Inc. not be used in advertising + * or publicity pertaining to distribution of the software without specific, + * written prior permission. + * + * THE MATERIAL EMBODIED ON THIS SOFTWARE IS PROVIDED TO YOU "AS-IS" + * AND WITHOUT WARRANTY OF ANY KIND, EXPRESS, IMPLIED OR OTHERWISE, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTY OF MERCHANTABILITY OR + * FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT SHALL SILICON + * GRAPHICS, INC. BE LIABLE TO YOU OR ANYONE ELSE FOR ANY DIRECT, + * SPECIAL, INCIDENTAL, INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY + * KIND, OR ANY DAMAGES WHATSOEVER, INCLUDING WITHOUT LIMITATION, + * LOSS OF PROFIT, LOSS OF USE, SAVINGS OR REVENUE, OR THE CLAIMS OF + * THIRD PARTIES, WHETHER OR NOT SILICON GRAPHICS, INC. HAS BEEN + * ADVISED OF THE POSSIBILITY OF SUCH LOSS, HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, ARISING OUT OF OR IN CONNECTION WITH THE + * POSSESSION, USE OR PERFORMANCE OF THIS SOFTWARE. + * + * US Government Users Restricted Rights + * Use, duplication, or disclosure by the Government is subject to + * restrictions set forth in FAR 52.227.19(c)(2) or subparagraph + * (c)(1)(ii) of the Rights in Technical Data and Computer Software + * clause at DFARS 252.227-7013 and/or in similar or successor + * clauses in the FAR or the DOD or NASA FAR Supplement. + * Unpublished-- rights reserved under the copyright laws of the + * United States. Contractor/manufacturer is Silicon Graphics, + * Inc., 2011 N. Shoreline Blvd., Mountain View, CA 94039-7311. + * + * OpenGL(TM) is a trademark of Silicon Graphics, Inc. + */ +/* + * trackball.h + * A virtual trackball implementation + * Written by Gavin Bell for Silicon Graphics, November 1988. + */ + +/* + * Pass the x and y coordinates of the last and current positions of + * the mouse, scaled so they are from (-1.0 ... 1.0). + * + * The resulting rotation is returned as a quaternion rotation in the + * first paramater. + */ +void trackball(float q[4], float p1x, float p1y, float p2x, float p2y); + +void negate_quat(float *q, float *qn); + +/* + * Given two quaternions, add them together to get a third quaternion. + * Adding quaternions to get a compound rotation is analagous to adding + * translations to get a compound translation. When incrementally + * adding rotations, the first argument here should be the new + * rotation, the second and third the total rotation (which will be + * over-written with the resulting new total rotation). + */ +void add_quats(float *q1, float *q2, float *dest); + +/* + * A useful function, builds a rotation matrix in Matrix based on + * given quaternion. + */ +void build_rotmatrix(float m[4][4], const float q[4]); + +/* + * This function computes a quaternion based on an axis (defined by + * the given vector) and an angle about which to rotate. The angle is + * expressed in radians. The result is put into the third argument. + */ +void axis_to_quat(float a[3], float phi, float q[4]); diff --git a/examples/viewer/viewer.cc b/examples/viewer/viewer.cc new file mode 100644 index 0000000..d89fadc --- /dev/null +++ b/examples/viewer/viewer.cc @@ -0,0 +1,403 @@ +// +// Simple .obj viewer(vertex only) +// +#include +#include +#include +#include +#include +#include + +#include + +#ifdef __APPLE__ +#include +#else +#include +#endif + +#include + +#include "../../tiny_obj_loader.h" + +#include "trackball.h" + +typedef struct { + GLuint vb; // vertex buffer + int numTriangles; +} DrawObject; + +std::vector gDrawObjects; + +int width = 512; +int height = 512; + +double prevMouseX, prevMouseY; +bool mouseLeftPressed; +bool mouseMiddlePressed; +bool mouseRightPressed; +float curr_quat[4]; +float prev_quat[4]; +float eye[3], lookat[3], up[3]; + +GLFWwindow* window; + +void CheckErrors(std::string desc) { + GLenum e = glGetError(); + if (e != GL_NO_ERROR) { + fprintf(stderr, "OpenGL error in \"%s\": %d (%d)\n", desc.c_str(), e, e); + exit(20); + } +} + +void CalcNormal(float N[3], float v0[3], float v1[3], float v2[3]) { + float v10[3]; + v10[0] = v1[0] - v0[0]; + v10[1] = v1[1] - v0[1]; + v10[2] = v1[2] - v0[2]; + + float v20[3]; + v20[0] = v2[0] - v0[0]; + v20[1] = v2[1] - v0[1]; + v20[2] = v2[2] - v0[2]; + + N[0] = v20[1] * v10[2] - v20[2] * v10[1]; + N[1] = v20[2] * v10[0] - v20[0] * v10[2]; + N[2] = v20[0] * v10[1] - v20[1] * v10[0]; + + float len2 = N[0] * N[0] + N[1] * N[1] + N[2] * N[2]; + if (len2 > 0.0f) { + float len = sqrtf(len2); + + N[0] /= len; + N[1] /= len; + } +} + +bool LoadObjAndConvert(float bmin[3], float bmax[3], std::vector& drawObjects, const char* filename) +{ + tinyobj::attrib_t attrib; + std::vector shapes; + std::vector materials; + + std::string err; + bool ret = tinyobj::LoadObj(&attrib, &shapes, &materials, &err, filename, NULL); + if (!err.empty()) { + std::cerr << err << std::endl; + } + + if (!ret) { + std::cerr << "Failed to load " << filename << std::endl; + return false; + } + + printf("# of materials = %d\n", (int)materials.size()); + printf("# of shapes = %d\n", (int)shapes.size()); + + bmin[0] = bmin[1] = bmin[2] = std::numeric_limits::max(); + bmax[0] = bmax[1] = bmax[2] = -std::numeric_limits::max(); + + { + for (size_t s = 0; s < shapes.size(); s++) { + DrawObject o; + std::vector vb; // pos(3float), normal(3float) + for (size_t f = 0; f < shapes[s].mesh.indices.size()/3; f++) { + + tinyobj::index_t idx0 = shapes[s].mesh.indices[3*f+0]; + tinyobj::index_t idx1 = shapes[s].mesh.indices[3*f+1]; + tinyobj::index_t idx2 = shapes[s].mesh.indices[3*f+2]; + + float v[3][3]; + for (int k = 0; k < 3; k++) { + int f0 = idx0.vertex_index; + int f1 = idx1.vertex_index; + int f2 = idx2.vertex_index; + + v[0][k] = attrib.vertices[3*f0+k]; + v[1][k] = attrib.vertices[3*f1+k]; + v[2][k] = attrib.vertices[3*f2+k]; + bmin[k] = std::min(v[0][k], bmin[k]); + bmin[k] = std::min(v[1][k], bmin[k]); + bmin[k] = std::min(v[2][k], bmin[k]); + bmax[k] = std::max(v[0][k], bmax[k]); + bmax[k] = std::max(v[1][k], bmax[k]); + bmax[k] = std::max(v[2][k], bmax[k]); + } + + float n[3][3]; + + if (attrib.normals.size() > 0) { + int f0 = idx0.normal_index; + int f1 = idx1.normal_index; + int f2 = idx2.normal_index; + for (int k = 0; k < 3; k++) { + n[0][k] = attrib.normals[3*f0+k]; + n[1][k] = attrib.normals[3*f1+k]; + n[2][k] = attrib.normals[3*f2+k]; + } + } else { + // compute geometric normal + CalcNormal(n[0], v[0], v[1], v[2]); + n[1][0] = n[0][0]; n[1][1] = n[0][1]; n[1][2] = n[0][2]; + n[2][0] = n[0][0]; n[2][1] = n[0][1]; n[2][2] = n[0][2]; + } + + for (int k = 0; k < 3; k++) { + vb.push_back(v[k][0]); + vb.push_back(v[k][1]); + vb.push_back(v[k][2]); + vb.push_back(n[k][0]); + vb.push_back(n[k][1]); + vb.push_back(n[k][2]); + } + + } + + o.vb = 0; + o.numTriangles = 0; + if (vb.size() > 0) { + glGenBuffers(1, &o.vb); + glBindBuffer(GL_ARRAY_BUFFER, o.vb); + glBufferData(GL_ARRAY_BUFFER, vb.size() * sizeof(float), &vb.at(0), GL_STATIC_DRAW); + o.numTriangles = vb.size() / 6 / 3; + } + + gDrawObjects.push_back(o); + } + } + + printf("bmin = %f, %f, %f\n", bmin[0], bmin[1], bmin[2]); + printf("bmax = %f, %f, %f\n", bmax[0], bmax[1], bmax[2]); + + return true; +} + +void reshapeFunc(GLFWwindow* window, int w, int h) +{ + printf("reshape\n"); + glViewport(0, 0, w, h); + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + gluPerspective(45.0, (float)w / (float)h, 0.1f, 1000.0f); + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + + width = w; + height = h; +} + +void keyboardFunc(GLFWwindow *window, int key, int scancode, int action, int mods) { + if(action == GLFW_PRESS || action == GLFW_REPEAT){ + // Move camera + float mv_x = 0, mv_y = 0, mv_z = 0; + if(key == GLFW_KEY_K) mv_x += 1; + else if(key == GLFW_KEY_J) mv_x += -1; + else if(key == GLFW_KEY_L) mv_y += 1; + else if(key == GLFW_KEY_H) mv_y += -1; + else if(key == GLFW_KEY_P) mv_z += 1; + else if(key == GLFW_KEY_N) mv_z += -1; + //camera.move(mv_x * 0.05, mv_y * 0.05, mv_z * 0.05); + // Close window + if(key == GLFW_KEY_Q || key == GLFW_KEY_ESCAPE) glfwSetWindowShouldClose(window, GL_TRUE); + + //init_frame = true; + } +} + +void clickFunc(GLFWwindow* window, int button, int action, int mods){ + if(button == GLFW_MOUSE_BUTTON_LEFT){ + if(action == GLFW_PRESS){ + mouseLeftPressed = true; + trackball(prev_quat, 0.0, 0.0, 0.0, 0.0); + } else if(action == GLFW_RELEASE){ + mouseLeftPressed = false; + } + } + if(button == GLFW_MOUSE_BUTTON_RIGHT){ + if(action == GLFW_PRESS){ + mouseRightPressed = true; + } else if(action == GLFW_RELEASE){ + mouseRightPressed = false; + } + } + if(button == GLFW_MOUSE_BUTTON_MIDDLE){ + if(action == GLFW_PRESS){ + mouseMiddlePressed = true; + } else if(action == GLFW_RELEASE){ + mouseMiddlePressed = false; + } + } +} + +void motionFunc(GLFWwindow* window, double mouse_x, double mouse_y){ + float rotScale = 1.0f; + float transScale = 2.0f; + + if(mouseLeftPressed){ + trackball(prev_quat, + rotScale * (2.0f * prevMouseX - width) / (float)width, + rotScale * (height - 2.0f * prevMouseY) / (float)height, + rotScale * (2.0f * mouse_x - width) / (float)width, + rotScale * (height - 2.0f * mouse_y) / (float)height); + + add_quats(prev_quat, curr_quat, curr_quat); + } else if (mouseMiddlePressed) { + eye[0] += transScale * (mouse_x - prevMouseX) / (float)width; + lookat[0] += transScale * (mouse_x - prevMouseX) / (float)width; + eye[1] += transScale * (mouse_y - prevMouseY) / (float)height; + lookat[1] += transScale * (mouse_y - prevMouseY) / (float)height; + } else if (mouseRightPressed) { + eye[2] += transScale * (mouse_y - prevMouseY) / (float)height; + lookat[2] += transScale * (mouse_y - prevMouseY) / (float)height; + } + + // Update mouse point + prevMouseX = mouse_x; + prevMouseY = mouse_y; +} + +void Draw(const std::vector& drawObjects) +{ + glPolygonMode(GL_FRONT, GL_FILL); + glPolygonMode(GL_BACK, GL_FILL); + + glEnable(GL_POLYGON_OFFSET_FILL); + glPolygonOffset(1.0, 1.0); + glColor3f(1.0f, 1.0f, 1.0f); + for (size_t i = 0; i < drawObjects.size(); i++) { + DrawObject o = drawObjects[i]; + if (o.vb < 1) { + continue; + } + + glBindBuffer(GL_ARRAY_BUFFER, o.vb); + glEnableClientState(GL_VERTEX_ARRAY); + glEnableClientState(GL_NORMAL_ARRAY); + glVertexPointer(3, GL_FLOAT, 24, (const void*)0); + glNormalPointer(GL_FLOAT, 24, (const void*)(sizeof(float)*3)); + + glDrawArrays(GL_TRIANGLES, 0, 3 * o.numTriangles); + CheckErrors("drawarrays"); + } + + // draw wireframe + glDisable(GL_POLYGON_OFFSET_FILL); + glPolygonMode(GL_FRONT, GL_LINE); + glPolygonMode(GL_BACK, GL_LINE); + + glColor3f(0.0f, 0.0f, 0.4f); + for (size_t i = 0; i < drawObjects.size(); i++) { + DrawObject o = drawObjects[i]; + if (o.vb < 1) { + continue; + } + + glBindBuffer(GL_ARRAY_BUFFER, o.vb); + glEnableClientState(GL_VERTEX_ARRAY); + glEnableClientState(GL_NORMAL_ARRAY); + glVertexPointer(3, GL_FLOAT, 24, (const void*)0); + glNormalPointer(GL_FLOAT, 24, (const void*)(sizeof(float)*3)); + + glDrawArrays(GL_TRIANGLES, 0, 3 * o.numTriangles); + CheckErrors("drawarrays"); + } +} + +static void Init() { + trackball(curr_quat, 0, 0, 0, 0); + + eye[0] = 0.0f; + eye[1] = 0.0f; + eye[2] = 3.0f; + + lookat[0] = 0.0f; + lookat[1] = 0.0f; + lookat[2] = 0.0f; + + up[0] = 0.0f; + up[1] = 1.0f; + up[2] = 0.0f; +} + + +int main(int argc, char **argv) +{ + if (argc < 2) { + std::cout << "Needs input.obj\n" << std::endl; + return 0; + } + + Init(); + + + if(!glfwInit()){ + std::cerr << "Failed to initialize GLFW." << std::endl; + return -1; + } + + + + window = glfwCreateWindow(width, height, "Obj viewer", NULL, NULL); + if(window == NULL){ + std::cerr << "Failed to open GLFW window. " << std::endl; + glfwTerminate(); + return 1; + } + + glfwMakeContextCurrent(window); + + // Callback + glfwSetWindowSizeCallback(window, reshapeFunc); + glfwSetKeyCallback(window, keyboardFunc); + glfwSetMouseButtonCallback(window, clickFunc); + glfwSetCursorPosCallback(window, motionFunc); + + glewExperimental = true; + if (glewInit() != GLEW_OK) { + std::cerr << "Failed to initialize GLEW." << std::endl; + return -1; + } + + reshapeFunc(window, width, height); + + float bmin[3], bmax[3]; + if (false == LoadObjAndConvert(bmin, bmax, gDrawObjects, argv[1])) { + return -1; + } + + float maxExtent = 0.5f * (bmax[0] - bmin[0]); + if (maxExtent < 0.5f * (bmax[1] - bmin[1])) { + maxExtent = 0.5f * (bmax[1] - bmin[1]); + } + if (maxExtent < 0.5f * (bmax[2] - bmin[2])) { + maxExtent = 0.5f * (bmax[2] - bmin[2]); + } + + while(glfwWindowShouldClose(window) == GL_FALSE) { + glfwPollEvents(); + glClearColor(0.1f, 0.2f, 0.3f, 1.0f); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + glEnable(GL_DEPTH_TEST); + + // camera & rotate + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + GLfloat mat[4][4]; + gluLookAt(eye[0], eye[1], eye[2], lookat[0], lookat[1], lookat[2], up[0], up[1], up[2]); + build_rotmatrix(mat, curr_quat); + glMultMatrixf(&mat[0][0]); + + // Fit to -1, 1 + glScalef(1.0f / maxExtent, 1.0f / maxExtent, 1.0f / maxExtent); + + // Centerize object. + glTranslatef(-0.5*(bmax[0] + bmin[0]), -0.5*(bmax[1] + bmin[1]), -0.5*(bmax[2] + bmin[2])); + + Draw(gDrawObjects); + + glfwSwapBuffers(window); + } + + glfwTerminate(); +} -- cgit v1.2.3 From 00ed158a8eed7c50549c3f294759f5e55664ce58 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Tue, 19 Apr 2016 12:32:12 +0900 Subject: Update CMake. --- CMakeLists.txt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 4db707c..f4f497a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -12,8 +12,8 @@ set(tinyobjloader-Source ${CMAKE_CURRENT_SOURCE_DIR}/tiny_obj_loader.cc ) -set(tinyobjloader-Test-Source - ${CMAKE_CURRENT_SOURCE_DIR}/test.cc +set(tinyobjloader-Example-Source + ${CMAKE_CURRENT_SOURCE_DIR}/loader_example.cc ) set(tinyobjloader-examples-objsticher @@ -26,10 +26,10 @@ add_library(tinyobjloader ${tinyobjloader-Source} ) -option(TINYOBJLOADER_BUILD_TEST_LOADER "Build Test Loader Application" OFF) +option(TINYOBJLOADER_BUILD_TEST_LOADER "Build Example Loader Application" OFF) if(TINYOBJLOADER_BUILD_TEST_LOADER) - add_executable(test_loader ${tinyobjloader-Test-Source}) + add_executable(test_loader ${tinyobjloader-Example-Source}) target_link_libraries(test_loader tinyobjloader) endif() -- cgit v1.2.3 From f4695de408516f45652b161a34bb62fbe6edec07 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Tue, 19 Apr 2016 12:50:41 +0900 Subject: Suppress compiler warning. --- tiny_obj_loader.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tiny_obj_loader.h b/tiny_obj_loader.h index d2aae38..af395ff 100644 --- a/tiny_obj_loader.h +++ b/tiny_obj_loader.h @@ -493,7 +493,7 @@ static vertex_index parseTriple(const char **token, int vsize, int vnsize, // Parse raw triples: i, i/j/k, i//k, i/j static vertex_index parseRawTriple(const char **token) { - vertex_index vi(0x80000000); // 0x80000000 = -2147483648 = invalid + vertex_index vi(-2147483648); // 0x80000000 = -2147483648 = invalid vi.v_idx = atoi((*token)); (*token) += strcspn((*token), "/ \t\r"); -- cgit v1.2.3 From a55247574c2c2436989a9a0654c51a62cc7193ba Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Tue, 19 Apr 2016 13:11:55 +0900 Subject: Suppress VC2013 warnings. Update AppVeyor script. --- appveyor.yml | 11 +++-------- build.ninja | 18 ------------------ tests/config-msvc.py | 2 +- tests/tester.cc | 23 +++++++++++++++++++++++ tests/vcbuild.bat | 1 + tiny_obj_loader.h | 26 ++++++++++++-------------- 6 files changed, 40 insertions(+), 41 deletions(-) delete mode 100644 build.ninja diff --git a/appveyor.yml b/appveyor.yml index 38eaf15..b52a666 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,12 +1,7 @@ version: 0.9.{build} -# scripts that runs after repo cloning. -install: - - vcsetup.bat - platform: x64 -configuration: Release -build: - parallel: true - project: TinyObjLoaderSolution.sln +build_script: + - cd tests + - vcbuild.bat diff --git a/build.ninja b/build.ninja deleted file mode 100644 index 5048ff1..0000000 --- a/build.ninja +++ /dev/null @@ -1,18 +0,0 @@ -# build.ninja -cc = clang -cxx = clang++ -cflags = -Werror -Weverything -cxxflags = -Werror -Weverything -#cflags = -O2 -#cxxflags = -O2 - -rule compile - command = $cxx $cxxflags -c $in -o $out - -rule link - command = $cxx $in -o $out - -build loader_example.o: compile loader_example.cc -build loader_example: link loader_example.o - -default loader_example diff --git a/tests/config-msvc.py b/tests/config-msvc.py index 06fae62..a7771de 100644 --- a/tests/config-msvc.py +++ b/tests/config-msvc.py @@ -32,7 +32,7 @@ cflags = { cxxflags = { "gnu" : [ "-O2", "-g" ] - , "msvc" : [ "/O2" ] + , "msvc" : [ "/O2", "/W4", "/EHsc"] , "clang" : [ "-O2", "-g", "-fsanitize=address" ] } diff --git a/tests/tester.cc b/tests/tester.cc index edb92ba..f16eeb0 100644 --- a/tests/tester.cc +++ b/tests/tester.cc @@ -300,6 +300,29 @@ TEST_CASE("cornell_box", "[Loader]") { REQUIRE(true == TestLoadObj("../models/cornell_box.obj", gMtlBasePath)); } +TEST_CASE("catmark_torus_creases0", "[Loader]") { + + tinyobj::attrib_t attrib; + std::vector shapes; + std::vector materials; + + std::string err; + bool ret = tinyobj::LoadObj(&attrib, &shapes, &materials, &err, "../models/catmark_torus_creases0.obj", gMtlBasePath, /*triangulate*/false); + + if (!err.empty()) { + std::cerr << err << std::endl; + } + + REQUIRE(true == ret); + + REQUIRE(1 == shapes.size()); + REQUIRE(8 == shapes[0].mesh.tags.size()); +} + +TEST_CASE("stream_load", "[Stream]") { + REQUIRE(true == TestStreamLoadObj()); +} + #if 0 int main( diff --git a/tests/vcbuild.bat b/tests/vcbuild.bat index 3dbc6c1..e864673 100644 --- a/tests/vcbuild.bat +++ b/tests/vcbuild.bat @@ -1,3 +1,4 @@ chcp 437 +python kuroga.py config-msvc.py call "C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\vcvarsall.bat" x86_amd64 ninja diff --git a/tiny_obj_loader.h b/tiny_obj_loader.h index af395ff..5db2bb8 100644 --- a/tiny_obj_loader.h +++ b/tiny_obj_loader.h @@ -236,15 +236,6 @@ struct tag_sizes { int num_strings; }; -// 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); - - return false; -} - struct obj_shape { std::vector v; std::vector vn; @@ -346,11 +337,13 @@ static bool tryParseDouble(const char *s, const char *s_end, double *result) { } // Read the integer part. - while ((end_not_reached = (curr != s_end)) && IS_DIGIT(*curr)) { + end_not_reached = (curr != s_end); + while (end_not_reached && IS_DIGIT(*curr)) { mantissa *= 10; mantissa += static_cast(*curr - 0x30); curr++; read++; + end_not_reached = (curr != s_end); } // We must make sure we actually got something. @@ -362,11 +355,13 @@ static bool tryParseDouble(const char *s, const char *s_end, double *result) { if (*curr == '.') { curr++; read = 1; - while ((end_not_reached = (curr != s_end)) && IS_DIGIT(*curr)) { + end_not_reached = (curr != s_end); + while (end_not_reached && IS_DIGIT(*curr)) { // NOTE: Don't use powf here, it will absolutely murder precision. mantissa += static_cast(*curr - 0x30) * pow(10.0, -read); read++; curr++; + end_not_reached = (curr != s_end); } } else if (*curr == 'e' || *curr == 'E') { } else { @@ -379,7 +374,8 @@ static bool tryParseDouble(const char *s, const char *s_end, double *result) { 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 == '-')) { + end_not_reached = (curr != s_end); + if (end_not_reached && (*curr == '+' || *curr == '-')) { exp_sign = *curr; curr++; } else if (IS_DIGIT(*curr)) { /* Pass through. */ @@ -389,11 +385,13 @@ static bool tryParseDouble(const char *s, const char *s_end, double *result) { } read = 0; - while ((end_not_reached = (curr != s_end)) && IS_DIGIT(*curr)) { + end_not_reached = (curr != s_end); + while (end_not_reached && IS_DIGIT(*curr)) { exponent *= 10; exponent += static_cast(*curr - 0x30); curr++; read++; + end_not_reached = (curr != s_end); } exponent *= (exp_sign == '+' ? 1 : -1); if (read == 0) goto fail; @@ -493,7 +491,7 @@ static vertex_index parseTriple(const char **token, int vsize, int vnsize, // Parse raw triples: i, i/j/k, i//k, i/j static vertex_index parseRawTriple(const char **token) { - vertex_index vi(-2147483648); // 0x80000000 = -2147483648 = invalid + vertex_index vi(static_cast(0x80000000)); // 0x80000000 = -2147483648 = invalid vi.v_idx = atoi((*token)); (*token) += strcspn((*token), "/ \t\r"); -- cgit v1.2.3 From 6b41e68bbc589cd2439845b5041e71ee5f60dd61 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Tue, 19 Apr 2016 13:25:57 +0900 Subject: Install ninja in appveyor build. --- appveyor.yml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/appveyor.yml b/appveyor.yml index b52a666..22e3d7c 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -2,6 +2,22 @@ version: 0.9.{build} platform: x64 +install: + ####################################################################################### + # All external dependencies are installed in C:\projects\deps + ####################################################################################### + - mkdir C:\projects\deps + - cd C:\projects\deps + + ####################################################################################### + # Install Ninja + ####################################################################################### + - set NINJA_URL="https://github.com/ninja-build/ninja/releases/download/v1.6.0/ninja-win.zip" + - appveyor DownloadFile %NINJA_URL% -FileName ninja.zip + - 7z x ninja.zip -oC:\projects\deps\ninja > nul + - set PATH=C:\projects\deps\ninja;%PATH% + - ninja --version + build_script: - cd tests - vcbuild.bat -- cgit v1.2.3 From 9eecb4634d98cfe0fa09b1caac01f602f13ec9cb Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Tue, 19 Apr 2016 13:27:44 +0900 Subject: Update Travis script. --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 3ec9e45..0b1926d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -40,6 +40,7 @@ matrix: --coverage" REPORT_COVERAGE=1 before_install: - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew upgrade; fi +- if [ -n "$REPORT_COVERAGE" ]; then pip install --user requests[security]; fi - if [ -n "$REPORT_COVERAGE" ]; then pip install --user cpp-coveralls; fi script: - cd tests -- cgit v1.2.3 From 28d1bb55218467b935436bf4e651deed33801d5b Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Tue, 19 Apr 2016 13:32:03 +0900 Subject: Update Travis. --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 0b1926d..c9403fd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -40,6 +40,7 @@ matrix: --coverage" REPORT_COVERAGE=1 before_install: - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew upgrade; fi +- if [ -n "$REPORT_COVERAGE" ]; then pip install --upgrade pip; fi - if [ -n "$REPORT_COVERAGE" ]; then pip install --user requests[security]; fi - if [ -n "$REPORT_COVERAGE" ]; then pip install --user cpp-coveralls; fi script: -- cgit v1.2.3 From 016362234b0c28d010f9dfb70699bfb94eeb9e7d Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Tue, 19 Apr 2016 13:36:46 +0900 Subject: Update Travis script. --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index c9403fd..da93e43 100644 --- a/.travis.yml +++ b/.travis.yml @@ -40,7 +40,7 @@ matrix: --coverage" REPORT_COVERAGE=1 before_install: - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew upgrade; fi -- if [ -n "$REPORT_COVERAGE" ]; then pip install --upgrade pip; fi +- if [ -n "$REPORT_COVERAGE" ]; then sudo pip install --upgrade pip; fi - if [ -n "$REPORT_COVERAGE" ]; then pip install --user requests[security]; fi - if [ -n "$REPORT_COVERAGE" ]; then pip install --user cpp-coveralls; fi script: -- cgit v1.2.3 From 170cb86870fe71224bc4c17c81a2e97f045150ac Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Tue, 19 Apr 2016 13:56:27 +0900 Subject: Change to sudo required. --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index da93e43..78bbd0b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,5 @@ language: cpp -sudo: false +sudo: required matrix: include: - addons: &1 -- cgit v1.2.3 From a66eab0f75dcc4c8f81c6adfc4c942114fafb221 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Tue, 19 Apr 2016 14:03:43 +0900 Subject: More Travis fix. --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 78bbd0b..74ab531 100644 --- a/.travis.yml +++ b/.travis.yml @@ -40,6 +40,7 @@ matrix: --coverage" REPORT_COVERAGE=1 before_install: - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew upgrade; fi +- if [ -n "$REPORT_COVERAGE" ]; then sudo apt-get install python-dev libffi-dev libssl-dev; fi - if [ -n "$REPORT_COVERAGE" ]; then sudo pip install --upgrade pip; fi - if [ -n "$REPORT_COVERAGE" ]; then pip install --user requests[security]; fi - if [ -n "$REPORT_COVERAGE" ]; then pip install --user cpp-coveralls; fi -- cgit v1.2.3 From 2e6cccbfe415814f6c788dc7db3998d3c3a129e9 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Tue, 19 Apr 2016 14:32:10 +0900 Subject: Force use g++ for pip install. --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 74ab531..a2a4428 100644 --- a/.travis.yml +++ b/.travis.yml @@ -42,8 +42,8 @@ before_install: - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew upgrade; fi - if [ -n "$REPORT_COVERAGE" ]; then sudo apt-get install python-dev libffi-dev libssl-dev; fi - if [ -n "$REPORT_COVERAGE" ]; then sudo pip install --upgrade pip; fi -- if [ -n "$REPORT_COVERAGE" ]; then pip install --user requests[security]; fi -- if [ -n "$REPORT_COVERAGE" ]; then pip install --user cpp-coveralls; fi +- if [ -n "$REPORT_COVERAGE" ]; then CXX=g++ pip install --user requests[security]; fi +- if [ -n "$REPORT_COVERAGE" ]; then CXX=g++ pip install --user cpp-coveralls; fi script: - cd tests - make check -- cgit v1.2.3 From a608c3b5b1f867044ea99d1f7bc4847ea44ab01d Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Tue, 19 Apr 2016 14:37:57 +0900 Subject: Update python. --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index a2a4428..cc79cf5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -40,6 +40,7 @@ matrix: --coverage" REPORT_COVERAGE=1 before_install: - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew upgrade; fi +- if [ -n "$REPORT_COVERAGE" ]; then sudo apt-get update python; fi - if [ -n "$REPORT_COVERAGE" ]; then sudo apt-get install python-dev libffi-dev libssl-dev; fi - if [ -n "$REPORT_COVERAGE" ]; then sudo pip install --upgrade pip; fi - if [ -n "$REPORT_COVERAGE" ]; then CXX=g++ pip install --user requests[security]; fi -- cgit v1.2.3 From 28fc3523d46470a9c5806635277730ba3b243879 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Tue, 19 Apr 2016 14:46:23 +0900 Subject: Disable coveralls due to Travis + python bug? --- .travis.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index cc79cf5..06b2d75 100644 --- a/.travis.yml +++ b/.travis.yml @@ -36,8 +36,7 @@ matrix: env: COMPILER_VERSION=4.9 BUILD_TYPE=Release - addons: *1 compiler: clang - env: COMPILER_VERSION=3.7 BUILD_TYPE=Debug CFLAGS="-O0 --coverage" CXXFLAGS="-O0 - --coverage" REPORT_COVERAGE=1 + env: COMPILER_VERSION=3.7 BUILD_TYPE=Debug before_install: - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew upgrade; fi - if [ -n "$REPORT_COVERAGE" ]; then sudo apt-get update python; fi -- cgit v1.2.3 From 7399aedfdd2097d8906dfc1760f8110ca257bd2e Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Tue, 19 Apr 2016 20:34:51 +0900 Subject: Initial support of linux for viewer example. --- examples/viewer/Makefile | 10 ++++++++-- examples/viewer/viewer.cc | 2 ++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/examples/viewer/Makefile b/examples/viewer/Makefile index e5204fc..9c1f621 100644 --- a/examples/viewer/Makefile +++ b/examples/viewer/Makefile @@ -1,6 +1,12 @@ GLFW_INC=-I/usr/local/include -GLFW_LIBS=-L/usr/local/lib -lglfw3 -lglew -GL_LIBS=-framework OpenGL + +# OSX +#GLFW_LIBS=-L/usr/local/lib -lglfw3 -lGLEW +#GL_LIBS=-framework OpenGL + +# Linux +GLFW_LIBS=-L/usr/local/lib -lglfw3 -lGLEW +GL_LIBS=-lGL -lGLU -lX11 -lXrandr -lXi -lXxf86vm -lXcursor -lXinerama -ldl -pthread CXX_FLAGS=-Wno-deprecated-declarations diff --git a/examples/viewer/viewer.cc b/examples/viewer/viewer.cc index d89fadc..2022168 100644 --- a/examples/viewer/viewer.cc +++ b/examples/viewer/viewer.cc @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -345,6 +346,7 @@ int main(int argc, char **argv) } glfwMakeContextCurrent(window); + glfwSwapInterval(1); // Callback glfwSetWindowSizeCallback(window, reshapeFunc); -- cgit v1.2.3 From 58fa260605fe380387ac08e59bdbff87add72857 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Wed, 20 Apr 2016 16:00:45 +0900 Subject: Show normal vector in viewer example. --- README.md | 8 +++--- examples/viewer/Makefile | 12 ++++++--- examples/viewer/viewer.cc | 42 +++++++++++++++++++++++--------- loader_example.cc | 3 +++ tiny_obj_loader.h | 62 +++++++++++++++++++++++++++++++---------------- 5 files changed, 88 insertions(+), 39 deletions(-) diff --git a/README.md b/README.md index 35dfd1e..838890f 100644 --- a/README.md +++ b/README.md @@ -79,12 +79,13 @@ Features TODO ---- -* [ ] Read .obj/.mtl from memory +* [ ] Read .obj/.mtl from memory. +* [ ] Fix Python binding. License ------- -Licensed under 2 clause BSD. +Licensed under MIT license. Usage ----- @@ -94,11 +95,12 @@ Usage #include "tiny_obj_loader.h" std::string inputfile = "cornell_box.obj"; +tinyobj::attrib_t attrib; std::vector shapes; std::vector materials; std::string err; -bool ret = tinyobj::LoadObj(shapes, materials, err, inputfile.c_str()); +bool ret = tinyobj::LoadObj(&attrib, &shapes, &materials, &err, inputfile.c_str()); if (!err.empty()) { // `err` may contain warning message. std::cerr << err << std::endl; diff --git a/examples/viewer/Makefile b/examples/viewer/Makefile index 9c1f621..f06e3d0 100644 --- a/examples/viewer/Makefile +++ b/examples/viewer/Makefile @@ -1,12 +1,16 @@ GLFW_INC=-I/usr/local/include -# OSX -#GLFW_LIBS=-L/usr/local/lib -lglfw3 -lGLEW -#GL_LIBS=-framework OpenGL +UNAME=$(shell uname -s) -# Linux +ifeq ($(UNAME),Darwin) +# OSX +GLFW_LIBS=-L/usr/local/lib -lglfw3 -lGLEW +GL_LIBS=-framework OpenGL +else +# Assume Linux GLFW_LIBS=-L/usr/local/lib -lglfw3 -lGLEW GL_LIBS=-lGL -lGLU -lX11 -lXrandr -lXi -lXxf86vm -lXcursor -lXinerama -ldl -pthread +endif CXX_FLAGS=-Wno-deprecated-declarations diff --git a/examples/viewer/viewer.cc b/examples/viewer/viewer.cc index 2022168..a86c853 100644 --- a/examples/viewer/viewer.cc +++ b/examples/viewer/viewer.cc @@ -30,8 +30,8 @@ typedef struct { std::vector gDrawObjects; -int width = 512; -int height = 512; +int width = 768; +int height = 768; double prevMouseX, prevMouseY; bool mouseLeftPressed; @@ -92,8 +92,11 @@ bool LoadObjAndConvert(float bmin[3], float bmax[3], std::vector& dr return false; } + printf("# of vertices = %d\n", (int)(attrib.vertices.size()) / 3); + printf("# of normals = %d\n", (int)(attrib.normals.size()) / 3); + printf("# of texcoords = %d\n", (int)(attrib.texcoords.size()) / 2); printf("# of materials = %d\n", (int)materials.size()); - printf("# of shapes = %d\n", (int)shapes.size()); + printf("# of shapes = %d\n", (int)shapes.size()); bmin[0] = bmin[1] = bmin[2] = std::numeric_limits::max(); bmax[0] = bmax[1] = bmax[2] = -std::numeric_limits::max(); @@ -101,7 +104,7 @@ bool LoadObjAndConvert(float bmin[3], float bmax[3], std::vector& dr { for (size_t s = 0; s < shapes.size(); s++) { DrawObject o; - std::vector vb; // pos(3float), normal(3float) + std::vector vb; // pos(3float), normal(3float), color(3float) for (size_t f = 0; f < shapes[s].mesh.indices.size()/3; f++) { tinyobj::index_t idx0 = shapes[s].mesh.indices[3*f+0]; @@ -150,6 +153,19 @@ bool LoadObjAndConvert(float bmin[3], float bmax[3], std::vector& dr vb.push_back(n[k][0]); vb.push_back(n[k][1]); vb.push_back(n[k][2]); + // Use normal as color. + float c[3] = {n[k][0], n[k][1], n[k][2]}; + float len2 = c[0] * c[0] + c[1] * c[1] + c[2] * c[2]; + if (len2 > 0.0f) { + float len = sqrtf(len2); + + c[0] /= len; + c[1] /= len; + c[2] /= len; + } + vb.push_back(c[0] * 0.5 + 0.5); + vb.push_back(c[1] * 0.5 + 0.5); + vb.push_back(c[2] * 0.5 + 0.5); } } @@ -160,7 +176,8 @@ bool LoadObjAndConvert(float bmin[3], float bmax[3], std::vector& dr glGenBuffers(1, &o.vb); glBindBuffer(GL_ARRAY_BUFFER, o.vb); glBufferData(GL_ARRAY_BUFFER, vb.size() * sizeof(float), &vb.at(0), GL_STATIC_DRAW); - o.numTriangles = vb.size() / 6 / 3; + o.numTriangles = vb.size() / 9 / 3; + printf("shape[%d] # of triangles = %d\n", static_cast(s), o.numTriangles); } gDrawObjects.push_back(o); @@ -243,8 +260,8 @@ void motionFunc(GLFWwindow* window, double mouse_x, double mouse_y){ add_quats(prev_quat, curr_quat, curr_quat); } else if (mouseMiddlePressed) { - eye[0] += transScale * (mouse_x - prevMouseX) / (float)width; - lookat[0] += transScale * (mouse_x - prevMouseX) / (float)width; + eye[0] -= transScale * (mouse_x - prevMouseX) / (float)width; + lookat[0] -= transScale * (mouse_x - prevMouseX) / (float)width; eye[1] += transScale * (mouse_y - prevMouseY) / (float)height; lookat[1] += transScale * (mouse_y - prevMouseY) / (float)height; } else if (mouseRightPressed) { @@ -274,8 +291,10 @@ void Draw(const std::vector& drawObjects) glBindBuffer(GL_ARRAY_BUFFER, o.vb); glEnableClientState(GL_VERTEX_ARRAY); glEnableClientState(GL_NORMAL_ARRAY); - glVertexPointer(3, GL_FLOAT, 24, (const void*)0); - glNormalPointer(GL_FLOAT, 24, (const void*)(sizeof(float)*3)); + glEnableClientState(GL_COLOR_ARRAY); + glVertexPointer(3, GL_FLOAT, 36, (const void*)0); + glNormalPointer(GL_FLOAT, 36, (const void*)(sizeof(float)*3)); + glColorPointer(3, GL_FLOAT, 36, (const void*)(sizeof(float)*6)); glDrawArrays(GL_TRIANGLES, 0, 3 * o.numTriangles); CheckErrors("drawarrays"); @@ -296,8 +315,9 @@ void Draw(const std::vector& drawObjects) glBindBuffer(GL_ARRAY_BUFFER, o.vb); glEnableClientState(GL_VERTEX_ARRAY); glEnableClientState(GL_NORMAL_ARRAY); - glVertexPointer(3, GL_FLOAT, 24, (const void*)0); - glNormalPointer(GL_FLOAT, 24, (const void*)(sizeof(float)*3)); + glDisableClientState(GL_COLOR_ARRAY); + glVertexPointer(3, GL_FLOAT, 36, (const void*)0); + glNormalPointer(GL_FLOAT, 36, (const void*)(sizeof(float)*3)); glDrawArrays(GL_TRIANGLES, 0, 3 * o.numTriangles); CheckErrors("drawarrays"); diff --git a/loader_example.cc b/loader_example.cc index d91baaa..435684e 100644 --- a/loader_example.cc +++ b/loader_example.cc @@ -1,3 +1,6 @@ +// +// g++ loader_example.cc +// #define TINYOBJLOADER_IMPLEMENTATION #include "tiny_obj_loader.h" diff --git a/tiny_obj_loader.h b/tiny_obj_loader.h index 5db2bb8..8500fc9 100644 --- a/tiny_obj_loader.h +++ b/tiny_obj_loader.h @@ -1,11 +1,30 @@ -// -// Copyright 2012-2016, Syoyo Fujita. -// -// Licensed under 2-clause BSD license. -// +/* +The MIT License (MIT) + +Copyright (c) 2012-2016 Syoyo Fujita and many contributors. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ // -// version devel : Change data structure. Support different index for +// version 1.0.0 : Change data structure. Change license from BSD to MIT. +// 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) @@ -117,8 +136,9 @@ typedef struct callback_t_ { void (*texcoord_cb)(void *user_data, float x, float y); // -2147483648 will be passed for undefined index void (*index_cb)(void *user_data, int v_idx, int vn_idx, int vt_idx); - // `name` material name, `materialId` = the array index of material_t[]. -1 if a material not found in .mtl - void (*usemtl_cb)(void *user_data, const char* name, int materialId); + // `name` material name, `materialId` = the array index of material_t[]. -1 if + // a material not found in .mtl + void (*usemtl_cb)(void *user_data, const char *name, int materialId); // `materials` = parsed material data. void (*mtllib_cb)(void *user_data, const material_t *materials, int num_materials); @@ -126,17 +146,16 @@ typedef struct callback_t_ { void (*group_cb)(void *user_data, const char **names, int num_names); void (*object_cb)(void *user_data, const char *name); - callback_t_() : - vertex_cb(NULL), - normal_cb(NULL), - texcoord_cb(NULL), - index_cb(NULL), - usemtl_cb(NULL), - mtllib_cb(NULL), - group_cb(NULL), - object_cb(NULL) { - } - + callback_t_() + : vertex_cb(NULL), + normal_cb(NULL), + texcoord_cb(NULL), + index_cb(NULL), + usemtl_cb(NULL), + mtllib_cb(NULL), + group_cb(NULL), + object_cb(NULL) {} + } callback_t; class MaterialReader { @@ -491,7 +510,8 @@ static vertex_index parseTriple(const char **token, int vsize, int vnsize, // Parse raw triples: i, i/j/k, i//k, i/j static vertex_index parseRawTriple(const char **token) { - vertex_index vi(static_cast(0x80000000)); // 0x80000000 = -2147483648 = invalid + vertex_index vi( + static_cast(0x80000000)); // 0x80000000 = -2147483648 = invalid vi.v_idx = atoi((*token)); (*token) += strcspn((*token), "/ \t\r"); @@ -1179,7 +1199,7 @@ bool LoadObjWithCallback(void *user_data, const callback_t &callback, // material std::map material_map; - int materialId = -1; // -1 = invalid + int materialId = -1; // -1 = invalid int maxchars = 8192; // Alloc enough size. std::vector buf(static_cast(maxchars)); // Alloc enough size. -- cgit v1.2.3 From 9d6b58b90e879a87e4455f79ef9ae585f9bfed10 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Sun, 24 Apr 2016 00:34:05 +0900 Subject: Add new build.ninja. --- README.md | 1 + build.ninja | 53 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+) create mode 100644 build.ninja diff --git a/README.md b/README.md index 838890f..f24bc8d 100644 --- a/README.md +++ b/README.md @@ -81,6 +81,7 @@ TODO * [ ] Read .obj/.mtl from memory. * [ ] Fix Python binding. +* [ ] Unit test codes. License ------- diff --git a/build.ninja b/build.ninja new file mode 100644 index 0000000..77801bb --- /dev/null +++ b/build.ninja @@ -0,0 +1,53 @@ +ninja_required_version = 1.4 + +gnubuilddir = build +gnudefines = +gnuincludes = -I. +gnucflags = -O2 -g +gnucxxflags = -O2 -g -pedantic -Wall -Wextra -Wcast-align -Wcast-qual $ + -Wctor-dtor-privacy -Wdisabled-optimization -Wformat=2 -Winit-self $ + -Wmissing-declarations -Wmissing-include-dirs -Wold-style-cast $ + -Woverloaded-virtual -Wredundant-decls -Wshadow -Wsign-conversion $ + -Wsign-promo -Wstrict-overflow=5 -Wswitch-default -Wundef -Werror $ + -Wno-unused -fsanitize=address +gnuldflags = -fsanitize=address + +pool link_pool + depth = 1 + +rule gnucxx + command = $gnucxx -MMD -MF $out.d $gnudefines $gnuincludes $gnucxxflags $ + -c $in -o $out + description = CXX $out + depfile = $out.d + deps = gcc +rule gnucc + command = $gnucc -MMD -MF $out.d $gnudefines $gnuincludes $gnucflags -c $ + $in -o $out + description = CC $out + depfile = $out.d + deps = gcc +rule gnulink + command = $gnuld -o $out $in $libs $gnuldflags + description = LINK $out + pool = link_pool +rule gnuar + command = $gnuar rsc $out $in + description = AR $out + pool = link_pool +rule gnustamp + command = touch $out + description = STAMP $out + +gnucxx = g++ +gnucc = gcc +gnuld = $gnucxx +gnuar = ar + +build loader_example.o: gnucxx loader_example.cc + + +build loader_example: gnulink loader_example.o +build all: phony loader_example + +default all -- cgit v1.2.3 From 7dc1418e2b1826a7859393d4c0f08d2ece7808d5 Mon Sep 17 00:00:00 2001 From: unknown Date: Tue, 26 Apr 2016 19:42:42 +0200 Subject: viewer: Cleaned up Makefile and added a semblance of a Mingw64-target for Windows. --- examples/viewer/Makefile | 64 ++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 56 insertions(+), 8 deletions(-) diff --git a/examples/viewer/Makefile b/examples/viewer/Makefile index f06e3d0..d42278c 100644 --- a/examples/viewer/Makefile +++ b/examples/viewer/Makefile @@ -1,18 +1,66 @@ -GLFW_INC=-I/usr/local/include +CXX_FLAGS=-O0 -g -Wno-deprecated-declarations +ifndef PLATFORM UNAME=$(shell uname -s) +# OSX TEST ifeq ($(UNAME),Darwin) -# OSX -GLFW_LIBS=-L/usr/local/lib -lglfw3 -lGLEW +PLATFORM=OSX +else +# WINDOWS MINGW TEST +ifeq ($(findstring MINGW, $(UNAME)), MINGW) + PLATFORM=WINDOWS_MINGW +else +# ASSUME LINUX + PLATFORM=LINUX +endif +endif + +endif +$(info PLATFORM SELECTED: $(PLATFORM)) + +ifeq ($(PLATFORM),OSX) +# OSX CONFIGURATION------------------------------------------------------------- +CXX_FLAGS+= -fsanitize=address +GLFW_INC=-I/usr/local/include +GLFW_LIB_DIR=/usr/local/lib +GLFW_LIBS=-L$(GLFW_LIB_DIR) -lglfw3 -lGLEW GL_LIBS=-framework OpenGL +# /OSX CONFIGURATION------------------------------------------------------------ else -# Assume Linux -GLFW_LIBS=-L/usr/local/lib -lglfw3 -lGLEW +ifeq ($(PLATFORM),LINUX) +# LINUX CONFIGURATION----------------------------------------------------------- +CXX_FLAGS+= -fsanitize=address +GLFW_INC=-I/usr/local/include +GLFW_LIB_DIR=/usr/local/lib +GLFW_LIBS=-L$(GLFW_LIB_DIR) -lglfw3 -lGLEW GL_LIBS=-lGL -lGLU -lX11 -lXrandr -lXi -lXxf86vm -lXcursor -lXinerama -ldl -pthread +# /LINUX CONFIGURATION---------------------------------------------------------- +else +ifeq ($(PLATFORM),WINDOWS_MINGW) +# WINDOWS MINGW CONFIGURATION--------------------------------------------------- +# Since Windows unfortunately doesn't have any defacto directories for third +# party libraries, we'll have to force the user to give us them. So we check +# that the user passed in GLFW-dirs in some way, via flag or environ. +ifndef GLFW_DIR +$(error NO GLFW_DIR SPECIFIED. PASS GLFW_DIR= TO MAKE) +endif +ifndef GLEW_DIR +$(error NO GLEW_DIR SPECIFIED. PASS GLEW_DIR= TO MAKE) endif - -CXX_FLAGS=-Wno-deprecated-declarations +GLFW_INC=-I$(GLFW_DIR)/include +GLFW_LIB_DIR=$(GLFW_DIR)/lib-mingw-w64 +GLFW_LIBS= -lglfw3 -L$(GLFW_LIB_DIR) +GL_LIBS= -lglew32 -lopengl32 -lglu32 -lgdi32 -I$(GLEW_DIR)/include -L$(GLEW_DIR)/bin/Release/x64 +# /WINDOWS CONFIGURATION-------------------------------------------------------- +else +$(error UNKNOWN PLATFORM "$(PLATFORM)") +endif #ifeq ($(PLATFORM),WINDOWS_MINGW) +endif #ifeq ($(PLATFORM),LINUX) +endif #ifeq ($(PLATFORM),OSX) all: - g++ -fsanitize=address -O0 -g -o objviewer $(CXX_FLAGS) viewer.cc ../../tiny_obj_loader.cc trackball.cc $(GLFW_INC) $(GL_LIBS) $(GLFW_LIBS) + g++ -o objviewer $(CXX_FLAGS) viewer.cc ../../tiny_obj_loader.cc trackball.cc $(GLFW_INC) $(GLFW_LIBS) $(GL_LIBS) +ifeq ($(PLATFORM), WINDOWS_MINGW) + cp $(GLEW_DIR)/bin/Release/x64/glew32.dll . +endif -- cgit v1.2.3 From 8d60b4963ae7a7cccd41d2bd22f2b2e17f1e0e9d Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Sun, 1 May 2016 21:30:50 +0900 Subject: Rename varialble for better understanding. Write some API usage in README.md. --- README.md | 36 +++++++++++++++++++++++++++++++++--- loader_example.cc | 10 +++++----- tests/tester.cc | 8 ++++---- tiny_obj_loader.h | 6 +++--- 4 files changed, 45 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index f24bc8d..74bc8f8 100644 --- a/README.md +++ b/README.md @@ -79,9 +79,9 @@ Features TODO ---- -* [ ] Read .obj/.mtl from memory. * [ ] Fix Python binding. -* [ ] Unit test codes. +* [ ] Fix obj_sticker example. +* [ ] More unit test codes. License ------- @@ -91,6 +91,10 @@ Licensed under MIT license. Usage ----- +`attrib_t` contains single and linear array of vertex data(position, normal and texcoord). +Each `shape_t` does not contain vertex data but contains array index to `attrib_t`. +See `loader_example.cc` for more details. + ```c++ #define TINYOBJLOADER_IMPLEMENTATION // define this in only *one* .cc #include "tiny_obj_loader.h" @@ -111,7 +115,33 @@ if (!ret) { exit(1); } -// See loader_example.cc for more details. +// Loop over shapes +for (size_t s = 0; s < shapes.size(); s++) { + // Loop over faces(polygon) + size_t index_offset = 0; + for (size_t f = 0; f < shapes[i].mesh.num_face_vertices; f++) { + int fv = shapes[i].mesh.num_face_vertices[f]; + + // Loop over vertices in the face. + for (size_t v = 0; v < fv; f++) { + // access to vertex + tinyobj::index_t idx = shapes[i].mesh.indices[index_offset + v]; + float vx = attrib.positions[3*idx.vertex_index+0]; + float vy = attrib.positions[3*idx.vertex_index+1]; + float vz = attrib.positions[3*idx.vertex_index+2]; + float nx = attrib.normals[3*idx.normal_index+0]; + float ny = attrib.normals[3*idx.normal_index+1]; + float nz = attrib.normals[3*idx.normal_index+2]; + float tx = attrib.texcoords[2*idx.texcoord_index+0]; + float ty = attrib.texcoords[2*idx.texcoord_index+1]; + } + index_offset += fv; + + // per-face material + shapes[i].mesh.material_ids[f]; + } +} + ``` diff --git a/loader_example.cc b/loader_example.cc index 435684e..e592e95 100644 --- a/loader_example.cc +++ b/loader_example.cc @@ -65,7 +65,7 @@ static void PrintInfo(const tinyobj::attrib_t &attrib, const std::vector(shapes[i].mesh.num_vertices[v])); + printf("shape[%ld].num_faces: %ld\n", i, shapes[i].mesh.num_face_vertices.size()); + for (size_t v = 0; v < shapes[i].mesh.num_face_vertices.size(); v++) { + printf(" num_face_vertices[%ld] = %ld\n", v, + static_cast(shapes[i].mesh.num_face_vertices[v])); } //printf("shape[%ld].vertices: %ld\n", i, shapes[i].mesh.positions.size()); diff --git a/tests/tester.cc b/tests/tester.cc index f16eeb0..e8e3b42 100644 --- a/tests/tester.cc +++ b/tests/tester.cc @@ -65,7 +65,7 @@ static void PrintInfo(const tinyobj::attrib_t &attrib, const std::vector(shapes[i].mesh.num_vertices[v])); + static_cast(shapes[i].mesh.num_face_vertices[v])); } //printf("shape[%ld].vertices: %ld\n", i, shapes[i].mesh.positions.size()); diff --git a/tiny_obj_loader.h b/tiny_obj_loader.h index 8500fc9..01ff721 100644 --- a/tiny_obj_loader.h +++ b/tiny_obj_loader.h @@ -113,7 +113,7 @@ typedef struct { typedef struct { std::vector indices; std::vector - num_vertices; // The number of vertices per face. Up to 255. + num_face_vertices; // The number of vertices per face. 3 = polygon, 4 = quad, ... Up to 255. std::vector material_ids; // per-face material ID std::vector tags; // SubD tag } mesh_t; @@ -604,7 +604,7 @@ static bool exportFaceGroupToShape( shape->mesh.indices.push_back(idx1); shape->mesh.indices.push_back(idx2); - shape->mesh.num_vertices.push_back(3); + shape->mesh.num_face_vertices.push_back(3); shape->mesh.material_ids.push_back(material_id); } } else { @@ -615,7 +615,7 @@ static bool exportFaceGroupToShape( idx.texcoord_index = face[k].vt_idx; } - shape->mesh.num_vertices.push_back(static_cast(npolys)); + shape->mesh.num_face_vertices.push_back(static_cast(npolys)); shape->mesh.material_ids.push_back(material_id); // per face } } -- cgit v1.2.3 From 368312cb4bd95e2806da09a2d2f43d04de73cb41 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Mon, 2 May 2016 01:41:37 +0900 Subject: Fix index buffer was not filled when !triangulate case. Suppress VS2015 warnings. Update premake5.exe. --- loader_example.cc | 76 ++++++++++++++++----------------------------- tiny_obj_loader.h | 1 + tools/windows/premake5.exe | Bin 514560 -> 912896 bytes 3 files changed, 28 insertions(+), 49 deletions(-) diff --git a/loader_example.cc b/loader_example.cc index e592e95..c44e0e7 100644 --- a/loader_example.cc +++ b/loader_example.cc @@ -11,7 +11,7 @@ #include #include -static void PrintInfo(const tinyobj::attrib_t &attrib, 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) { std::cout << "# of vertices : " << (attrib.vertices.size() / 3) << std::endl; std::cout << "# of normals : " << (attrib.normals.size() / 3) << std::endl; @@ -21,76 +21,54 @@ static void PrintInfo(const tinyobj::attrib_t &attrib, const std::vector(v), static_cast(attrib.vertices[3*v+0]), static_cast(attrib.vertices[3*v+1]), static_cast(attrib.vertices[3*v+2])); } for (size_t v = 0; v < attrib.normals.size() / 3; v++) { - printf(" n[%ld] = (%f, %f, %f)\n", v, + printf(" n[%ld] = (%f, %f, %f)\n", static_cast(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, + printf(" uv[%ld] = (%f, %f)\n", static_cast(v), static_cast(attrib.texcoords[2*v+0]), static_cast(attrib.texcoords[2*v+1])); } + // For each shape 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()); + printf("shape[%ld].name = %s\n", static_cast(i), shapes[i].name.c_str()); + printf("Size of shape[%ld].indices: %lu\n", static_cast(i), static_cast(shapes[i].mesh.indices.size())); - if (triangulate) - { - 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++) { - 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++) { - 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); - } + size_t index_offset = 0; - printf("Size of shape[%ld].material_ids: %ld\n", i, shapes[i].mesh.material_ids.size()); - assert(shapes[i].mesh.material_ids.size() == shapes[i].mesh.num_face_vertices.size()); - for (size_t m = 0; m < shapes[i].mesh.material_ids.size(); m++) { - printf(" material_id[%ld] = %d\n", m, - shapes[i].mesh.material_ids[m]); - } + assert(shapes[i].mesh.num_face_vertices.size() == shapes[i].mesh.material_ids.size()); - } + printf("shape[%ld].num_faces: %lu\n", static_cast(i), static_cast(shapes[i].mesh.num_face_vertices.size())); - printf("shape[%ld].num_faces: %ld\n", i, shapes[i].mesh.num_face_vertices.size()); - for (size_t v = 0; v < shapes[i].mesh.num_face_vertices.size(); v++) { - printf(" num_face_vertices[%ld] = %ld\n", v, - static_cast(shapes[i].mesh.num_face_vertices[v])); - } + // For each face + for (size_t f = 0; f < shapes[i].mesh.num_face_vertices.size(); f++) { + size_t fnum = shapes[i].mesh.num_face_vertices[f]; - //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])); - //} + // For each vertex in the face + for (size_t v = 0; v < fnum; v++) { + tinyobj::index_t idx = shapes[i].mesh.indices[index_offset + v]; + printf(" face[%ld].v[%ld].idx = %d/%d/%d\n", static_cast(f), static_cast(v), idx.vertex_index, idx.normal_index, idx.texcoord_index); + } - printf("shape[%ld].num_tags: %ld\n", i, shapes[i].mesh.tags.size()); + printf(" face[%ld].material_id = %d\n", static_cast(f), shapes[i].mesh.material_ids[f]); + + index_offset += fnum; + } + + printf("shape[%ld].num_tags: %lu\n", static_cast(i), static_cast(shapes[i].mesh.tags.size())); for (size_t t = 0; t < shapes[i].mesh.tags.size(); t++) { - printf(" tag[%ld] = %s ", t, shapes[i].mesh.tags[t].name.c_str()); + printf(" tag[%ld] = %s ", static_cast(t), shapes[i].mesh.tags[t].name.c_str()); printf(" ints: ["); for (size_t j = 0; j < shapes[i].mesh.tags[t].intValues.size(); ++j) { @@ -128,7 +106,7 @@ static void PrintInfo(const tinyobj::attrib_t &attrib, const std::vector(i), materials[i].name.c_str()); printf(" material.Ka = (%f, %f ,%f)\n", static_cast(materials[i].ambient[0]), static_cast(materials[i].ambient[1]), static_cast(materials[i].ambient[2])); printf(" material.Kd = (%f, %f ,%f)\n", static_cast(materials[i].diffuse[0]), static_cast(materials[i].diffuse[1]), static_cast(materials[i].diffuse[2])); printf(" material.Ks = (%f, %f ,%f)\n", static_cast(materials[i].specular[0]), static_cast(materials[i].specular[1]), static_cast(materials[i].specular[2])); @@ -179,7 +157,7 @@ TestLoadObj( return false; } - PrintInfo(attrib, shapes, materials, triangulate); + PrintInfo(attrib, shapes, materials); return true; } diff --git a/tiny_obj_loader.h b/tiny_obj_loader.h index 01ff721..2a93119 100644 --- a/tiny_obj_loader.h +++ b/tiny_obj_loader.h @@ -613,6 +613,7 @@ static bool exportFaceGroupToShape( idx.vertex_index = face[k].v_idx; idx.normal_index = face[k].vn_idx; idx.texcoord_index = face[k].vt_idx; + shape->mesh.indices.push_back(idx); } shape->mesh.num_face_vertices.push_back(static_cast(npolys)); diff --git a/tools/windows/premake5.exe b/tools/windows/premake5.exe index c0bf928..51c05a8 100644 Binary files a/tools/windows/premake5.exe and b/tools/windows/premake5.exe differ -- cgit v1.2.3 From b90f767367a6ccb4afaea0e8af924141b3eb3620 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Mon, 2 May 2016 02:03:51 +0900 Subject: Cosmetics. --- loader_example.cc | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/loader_example.cc b/loader_example.cc index c44e0e7..0aec2d1 100644 --- a/loader_example.cc +++ b/loader_example.cc @@ -55,10 +55,12 @@ static void PrintInfo(const tinyobj::attrib_t &attrib, const std::vector(f), static_cast(fnum)); + // For each vertex in the face for (size_t v = 0; v < fnum; v++) { tinyobj::index_t idx = shapes[i].mesh.indices[index_offset + v]; - printf(" face[%ld].v[%ld].idx = %d/%d/%d\n", static_cast(f), static_cast(v), idx.vertex_index, idx.normal_index, idx.texcoord_index); + printf(" face[%ld].v[%ld].idx = %d/%d/%d\n", static_cast(f), static_cast(v), idx.vertex_index, idx.normal_index, idx.texcoord_index); } printf(" face[%ld].material_id = %d\n", static_cast(f), shapes[i].mesh.material_ids[f]); -- cgit v1.2.3 From e9a7c76c235da94916cdec478552251f8fe8957e Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Wed, 11 May 2016 23:38:36 +0900 Subject: Add build script for Windows. Fix build on Windows. --- examples/viewer/README.md | 17 ++++++++++++++++- examples/viewer/premake4.lua | 43 +++++++++++++++++++++++++++++++++++++++++++ examples/viewer/viewer.cc | 2 ++ 3 files changed, 61 insertions(+), 1 deletion(-) create mode 100644 examples/viewer/premake4.lua diff --git a/examples/viewer/README.md b/examples/viewer/README.md index 4e0e087..8cb41c1 100644 --- a/examples/viewer/README.md +++ b/examples/viewer/README.md @@ -1 +1,16 @@ -Simple .obj viewer with glew + glfw3 + OpenGL +# Simple .obj viewer with glew + glfw3 + OpenGL + +## Build on Windows. + +### Requirements + +* premake5 +* Visual Studio 2013 +* Windows 64bit + * 32bit may work. + +Put glfw3 and glew library somewhere and replace include and lib path in `premake4.lua` + +Then, + + > premake5.exe vs2013 diff --git a/examples/viewer/premake4.lua b/examples/viewer/premake4.lua new file mode 100644 index 0000000..66da6f9 --- /dev/null +++ b/examples/viewer/premake4.lua @@ -0,0 +1,43 @@ +solution "objview" + -- location ( "build" ) + configurations { "Debug", "Release" } + platforms {"native", "x64", "x32"} + + project "objview" + + kind "ConsoleApp" + language "C++" + files { "viewer.cc", "trackball.cc" } + includedirs { "./" } + includedirs { "../../" } + + configuration { "linux" } + linkoptions { "`pkg-config --libs glfw3`" } + links { "GL", "GLU", "m", "GLEW", "X11", "Xrandr", "Xinerama", "Xi", "Xxf86vm", "Xcursor", "dl" } + + configuration { "windows" } + -- Path to GLFW3 + includedirs { '../../../../local/glfw-3.1.2.bin.WIN64/include' } + libdirs { '../../../../local/glfw-3.1.2.bin.WIN64/lib-vc2013' } + -- Path to GLEW + includedirs { '../../../../local/glew-1.13.0/include' } + libdirs { '../../../../local/glew-1.13.0/lib/Release/x64' } + + links { "glfw3", "glew32", "gdi32", "winmm", "user32", "glu32","opengl32", "kernel32" } + defines { "_CRT_SECURE_NO_WARNINGS" } + + configuration { "macosx" } + includedirs { "/usr/local/include" } + buildoptions { "-Wno-deprecated-declarations" } + libdirs { "/usr/local/lib" } + links { "glfw3", "GLEW" } + linkoptions { "-framework OpenGL", "-framework Cocoa", "-framework IOKit", "-framework CoreVideo" } + + configuration "Debug" + defines { "DEBUG" } + flags { "Symbols", "ExtraWarnings"} + + configuration "Release" + defines { "NDEBUG" } + flags { "Optimize", "ExtraWarnings"} + diff --git a/examples/viewer/viewer.cc b/examples/viewer/viewer.cc index a86c853..3160643 100644 --- a/examples/viewer/viewer.cc +++ b/examples/viewer/viewer.cc @@ -8,6 +8,7 @@ #include #include #include +#include #include @@ -19,6 +20,7 @@ #include +#define TINYOBJLOADER_IMPLEMENTATION #include "../../tiny_obj_loader.h" #include "trackball.h" -- cgit v1.2.3 From 48839e3b07f754cf3c6350746e55d31b900f7219 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Thu, 12 May 2016 01:13:47 +0900 Subject: Add screenshot of glviewer. --- images/sanmugel.png | Bin 0 -> 192538 bytes tests/README.md | 12 ++++++++++++ 2 files changed, 12 insertions(+) create mode 100644 images/sanmugel.png diff --git a/images/sanmugel.png b/images/sanmugel.png new file mode 100644 index 0000000..32ea150 Binary files /dev/null and b/images/sanmugel.png differ diff --git a/tests/README.md b/tests/README.md index 6ed65ff..1b0b43d 100644 --- a/tests/README.md +++ b/tests/README.md @@ -20,6 +20,18 @@ Are installed. ### Windows +Visual Studio 2013 is required to build tester. + +On Windows console. + > python kuroga.py config-msvc.py > vcbuild.bat + +Or on msys2 bash, + + $ python kuroga.py config-msvc.py + $ cmd //c vcbuild.bat + + + -- cgit v1.2.3 From bbb6aeff6a865340e1108f0d3457799f0f383417 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Thu, 12 May 2016 02:57:41 +0900 Subject: Adjust near/far clipping value. --- README.md | 6 +++++- examples/viewer/viewer.cc | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 74bc8f8..3e6663b 100644 --- a/README.md +++ b/README.md @@ -40,11 +40,15 @@ What's new Example ------- -![Rungholt](https://github.com/syoyo/tinyobjloader/blob/master/images/rungholt.jpg?raw=true) +![Rungholt](images/rungholt.jpg) tinyobjloader can successfully load 6M triangles Rungholt scene. http://graphics.cs.williams.edu/data/meshes.xml +![](images/sanmugel.png) + +* [examples/viewer/](examples/viewer) OpenGL .obj viewer + Use case -------- diff --git a/examples/viewer/viewer.cc b/examples/viewer/viewer.cc index 3160643..94153c8 100644 --- a/examples/viewer/viewer.cc +++ b/examples/viewer/viewer.cc @@ -198,7 +198,7 @@ void reshapeFunc(GLFWwindow* window, int w, int h) glViewport(0, 0, w, h); glMatrixMode(GL_PROJECTION); glLoadIdentity(); - gluPerspective(45.0, (float)w / (float)h, 0.1f, 1000.0f); + gluPerspective(45.0, (float)w / (float)h, 0.01f, 100.0f); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); -- cgit v1.2.3 From 73af05bc23dd4398ba0372902649ca0639d51c27 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Thu, 12 May 2016 19:33:22 +0900 Subject: Add assertion check. --- examples/viewer/viewer.cc | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/examples/viewer/viewer.cc b/examples/viewer/viewer.cc index a86c853..44eac0e 100644 --- a/examples/viewer/viewer.cc +++ b/examples/viewer/viewer.cc @@ -8,6 +8,7 @@ #include #include #include +#include #include @@ -116,6 +117,9 @@ bool LoadObjAndConvert(float bmin[3], float bmax[3], std::vector& dr int f0 = idx0.vertex_index; int f1 = idx1.vertex_index; int f2 = idx2.vertex_index; + assert(f0 >= 0); + assert(f1 >= 0); + assert(f2 >= 0); v[0][k] = attrib.vertices[3*f0+k]; v[1][k] = attrib.vertices[3*f1+k]; @@ -134,6 +138,9 @@ bool LoadObjAndConvert(float bmin[3], float bmax[3], std::vector& dr int f0 = idx0.normal_index; int f1 = idx1.normal_index; int f2 = idx2.normal_index; + assert(f0 >= 0); + assert(f1 >= 0); + assert(f2 >= 0); for (int k = 0; k < 3; k++) { n[0][k] = attrib.normals[3*f0+k]; n[1][k] = attrib.normals[3*f1+k]; -- cgit v1.2.3 From 0a3d47fdadc4160d39b53ed83c58106bbc2123f4 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Thu, 12 May 2016 19:38:29 +0900 Subject: Update README. --- examples/viewer/Makefile | 66 ----------------------------------------------- examples/viewer/README.md | 27 ++++++++++++++++--- 2 files changed, 24 insertions(+), 69 deletions(-) delete mode 100644 examples/viewer/Makefile diff --git a/examples/viewer/Makefile b/examples/viewer/Makefile deleted file mode 100644 index d42278c..0000000 --- a/examples/viewer/Makefile +++ /dev/null @@ -1,66 +0,0 @@ -CXX_FLAGS=-O0 -g -Wno-deprecated-declarations - -ifndef PLATFORM -UNAME=$(shell uname -s) - -# OSX TEST -ifeq ($(UNAME),Darwin) -PLATFORM=OSX -else -# WINDOWS MINGW TEST -ifeq ($(findstring MINGW, $(UNAME)), MINGW) - PLATFORM=WINDOWS_MINGW -else -# ASSUME LINUX - PLATFORM=LINUX -endif -endif - -endif -$(info PLATFORM SELECTED: $(PLATFORM)) - -ifeq ($(PLATFORM),OSX) -# OSX CONFIGURATION------------------------------------------------------------- -CXX_FLAGS+= -fsanitize=address -GLFW_INC=-I/usr/local/include -GLFW_LIB_DIR=/usr/local/lib -GLFW_LIBS=-L$(GLFW_LIB_DIR) -lglfw3 -lGLEW -GL_LIBS=-framework OpenGL -# /OSX CONFIGURATION------------------------------------------------------------ -else -ifeq ($(PLATFORM),LINUX) -# LINUX CONFIGURATION----------------------------------------------------------- -CXX_FLAGS+= -fsanitize=address -GLFW_INC=-I/usr/local/include -GLFW_LIB_DIR=/usr/local/lib -GLFW_LIBS=-L$(GLFW_LIB_DIR) -lglfw3 -lGLEW -GL_LIBS=-lGL -lGLU -lX11 -lXrandr -lXi -lXxf86vm -lXcursor -lXinerama -ldl -pthread -# /LINUX CONFIGURATION---------------------------------------------------------- -else -ifeq ($(PLATFORM),WINDOWS_MINGW) -# WINDOWS MINGW CONFIGURATION--------------------------------------------------- -# Since Windows unfortunately doesn't have any defacto directories for third -# party libraries, we'll have to force the user to give us them. So we check -# that the user passed in GLFW-dirs in some way, via flag or environ. -ifndef GLFW_DIR -$(error NO GLFW_DIR SPECIFIED. PASS GLFW_DIR= TO MAKE) -endif -ifndef GLEW_DIR -$(error NO GLEW_DIR SPECIFIED. PASS GLEW_DIR= TO MAKE) -endif -GLFW_INC=-I$(GLFW_DIR)/include -GLFW_LIB_DIR=$(GLFW_DIR)/lib-mingw-w64 -GLFW_LIBS= -lglfw3 -L$(GLFW_LIB_DIR) -GL_LIBS= -lglew32 -lopengl32 -lglu32 -lgdi32 -I$(GLEW_DIR)/include -L$(GLEW_DIR)/bin/Release/x64 -# /WINDOWS CONFIGURATION-------------------------------------------------------- -else -$(error UNKNOWN PLATFORM "$(PLATFORM)") -endif #ifeq ($(PLATFORM),WINDOWS_MINGW) -endif #ifeq ($(PLATFORM),LINUX) -endif #ifeq ($(PLATFORM),OSX) - -all: - g++ -o objviewer $(CXX_FLAGS) viewer.cc ../../tiny_obj_loader.cc trackball.cc $(GLFW_INC) $(GLFW_LIBS) $(GL_LIBS) -ifeq ($(PLATFORM), WINDOWS_MINGW) - cp $(GLEW_DIR)/bin/Release/x64/glew32.dll . -endif diff --git a/examples/viewer/README.md b/examples/viewer/README.md index 8cb41c1..79e544e 100644 --- a/examples/viewer/README.md +++ b/examples/viewer/README.md @@ -1,10 +1,31 @@ # Simple .obj viewer with glew + glfw3 + OpenGL -## Build on Windows. - -### Requirements +## Requirements * premake5 +* glfw3 +* glew + + +## Build on MaCOSX + +Install glfw3 and glew using brew. +Then, + + $ premake5 gmake + $ make + +## Build on Linux + +Set `PKG_CONFIG_PATH` or Edit path to glfw3 and glew in `premake4.lua` + +Then, + + $ premake5 gmake + $ make + +## Build on Windows. + * Visual Studio 2013 * Windows 64bit * 32bit may work. -- cgit v1.2.3 From 93c495eca8eda060205d536e15cb259932ad926b Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Fri, 13 May 2016 15:56:12 +0900 Subject: Fix 'o' callback. --- tiny_obj_loader.h | 32 +++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/tiny_obj_loader.h b/tiny_obj_loader.h index 2a93119..d130f8b 100644 --- a/tiny_obj_loader.h +++ b/tiny_obj_loader.h @@ -1375,27 +1375,29 @@ bool LoadObjWithCallback(void *user_data, const callback_t &callback, callback.group_cb(user_data, NULL, 0); } - continue; } - // object name - if (token[0] == 'o' && IS_SPACE((token[1]))) { - // @todo { multiple object name? } - char namebuf[TINYOBJ_SSCANF_BUFFER_SIZE]; - token += 2; + continue; + } + + // object name + if (token[0] == 'o' && IS_SPACE((token[1]))) { + // @todo { multiple object name? } + char namebuf[TINYOBJ_SSCANF_BUFFER_SIZE]; + token += 2; #ifdef _MSC_VER - sscanf_s(token, "%s", namebuf, (unsigned)_countof(namebuf)); + sscanf_s(token, "%s", namebuf, (unsigned)_countof(namebuf)); #else - sscanf(token, "%s", namebuf); + sscanf(token, "%s", namebuf); #endif - name = std::string(namebuf); + std::string name = std::string(namebuf); - if (callback.object_cb) { - callback.object_cb(user_data, name.c_str()); - } + if (callback.object_cb) { + callback.object_cb(user_data, name.c_str()); + } - continue; - } + continue; + } #if 0 // @todo if (token[0] == 't' && IS_SPACE(token[1])) { @@ -1442,8 +1444,8 @@ bool LoadObjWithCallback(void *user_data, const callback_t &callback, } tags.push_back(tag); -#endif } +#endif // Ignore unknown command. } -- cgit v1.2.3 From 2d16510c15b9bced3187beeadbfb61c94963cae1 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Fri, 13 May 2016 16:33:40 +0900 Subject: Measure .obj parsing time. --- examples/viewer/premake4.lua | 1 + examples/viewer/viewer.cc | 91 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 92 insertions(+) diff --git a/examples/viewer/premake4.lua b/examples/viewer/premake4.lua index 66da6f9..3b6ca1f 100644 --- a/examples/viewer/premake4.lua +++ b/examples/viewer/premake4.lua @@ -14,6 +14,7 @@ solution "objview" configuration { "linux" } linkoptions { "`pkg-config --libs glfw3`" } links { "GL", "GLU", "m", "GLEW", "X11", "Xrandr", "Xinerama", "Xi", "Xxf86vm", "Xcursor", "dl" } + linkoptions { "-pthread" } configuration { "windows" } -- Path to GLFW3 diff --git a/examples/viewer/viewer.cc b/examples/viewer/viewer.cc index 4f1c211..2b1bdf4 100644 --- a/examples/viewer/viewer.cc +++ b/examples/viewer/viewer.cc @@ -26,6 +26,89 @@ #include "trackball.h" +#ifdef _WIN32 +#ifdef __cplusplus +extern "C" { +#endif +#include +#include +#ifdef __cplusplus +} +#endif +#pragma comment(lib, "winmm.lib") +#else +#if defined(__unix__) || defined(__APPLE__) +#include +#else +#include +#endif +#endif + +class timerutil { +public: +#ifdef _WIN32 + typedef DWORD time_t; + + timerutil() { ::timeBeginPeriod(1); } + ~timerutil() { ::timeEndPeriod(1); } + + void start() { t_[0] = ::timeGetTime(); } + void end() { t_[1] = ::timeGetTime(); } + + time_t sec() { return (time_t)((t_[1] - t_[0]) / 1000); } + time_t msec() { return (time_t)((t_[1] - t_[0])); } + time_t usec() { return (time_t)((t_[1] - t_[0]) * 1000); } + time_t current() { return ::timeGetTime(); } + +#else +#if defined(__unix__) || defined(__APPLE__) + typedef unsigned long int time_t; + + void start() { gettimeofday(tv + 0, &tz); } + void end() { gettimeofday(tv + 1, &tz); } + + time_t sec() { return (time_t)(tv[1].tv_sec - tv[0].tv_sec); } + time_t msec() { + return this->sec() * 1000 + + (time_t)((tv[1].tv_usec - tv[0].tv_usec) / 1000); + } + time_t usec() { + return this->sec() * 1000000 + (time_t)(tv[1].tv_usec - tv[0].tv_usec); + } + time_t current() { + struct timeval t; + gettimeofday(&t, NULL); + return (time_t)(t.tv_sec * 1000 + t.tv_usec); + } + +#else // C timer + // using namespace std; + typedef clock_t time_t; + + void start() { t_[0] = clock(); } + void end() { t_[1] = clock(); } + + time_t sec() { return (time_t)((t_[1] - t_[0]) / CLOCKS_PER_SEC); } + time_t msec() { return (time_t)((t_[1] - t_[0]) * 1000 / CLOCKS_PER_SEC); } + time_t usec() { return (time_t)((t_[1] - t_[0]) * 1000000 / CLOCKS_PER_SEC); } + time_t current() { return (time_t)clock(); } + +#endif +#endif + +private: +#ifdef _WIN32 + DWORD t_[2]; +#else +#if defined(__unix__) || defined(__APPLE__) + struct timeval tv[2]; + struct timezone tz; +#else + time_t t_[2]; +#endif +#endif +}; + typedef struct { GLuint vb; // vertex buffer int numTriangles; @@ -83,6 +166,10 @@ bool LoadObjAndConvert(float bmin[3], float bmax[3], std::vector& dr tinyobj::attrib_t attrib; std::vector shapes; std::vector materials; + + timerutil tm; + + tm.start(); std::string err; bool ret = tinyobj::LoadObj(&attrib, &shapes, &materials, &err, filename, NULL); @@ -90,11 +177,15 @@ bool LoadObjAndConvert(float bmin[3], float bmax[3], std::vector& dr std::cerr << err << std::endl; } + tm.end(); + if (!ret) { std::cerr << "Failed to load " << filename << std::endl; return false; } + printf("Parsing time: %d [ms]\n", tm.msec()); + printf("# of vertices = %d\n", (int)(attrib.vertices.size()) / 3); printf("# of normals = %d\n", (int)(attrib.normals.size()) / 3); printf("# of texcoords = %d\n", (int)(attrib.texcoords.size()) / 2); -- cgit v1.2.3 From e528741a8b163ebd7f110e25ef2969ce7da674c7 Mon Sep 17 00:00:00 2001 From: Vazquinhos Date: Fri, 13 May 2016 12:25:48 +0200 Subject: 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; } -- cgit v1.2.3 From 33d5e9aa07625db7d24bafab39e3bb55ddf02a5b Mon Sep 17 00:00:00 2001 From: Vazquinhos Date: Fri, 13 May 2016 12:28:10 +0200 Subject: 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; } -- cgit v1.2.3 From 0dcc72239d9f593b7347ed45ce2d71feefff3f6b Mon Sep 17 00:00:00 2001 From: Vazquinhos Date: Fri, 13 May 2016 12:29:42 +0200 Subject: 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 -- cgit v1.2.3 From 9aee576b99e38f05cc2e3af804650484358fe3b8 Mon Sep 17 00:00:00 2001 From: Vazquinhos Date: Fri, 13 May 2016 12:37:24 +0200 Subject: 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); -- cgit v1.2.3 From 73d823ba77b07695927d80e77cdf2482a9137625 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Fri, 13 May 2016 19:49:54 +0900 Subject: Add code for parallel optimized .obj parsing(not working yet). --- sandbox/Makefile | 2 + sandbox/optimized-parse.cc | 870 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 872 insertions(+) create mode 100644 sandbox/Makefile create mode 100644 sandbox/optimized-parse.cc diff --git a/sandbox/Makefile b/sandbox/Makefile new file mode 100644 index 0000000..f72d56e --- /dev/null +++ b/sandbox/Makefile @@ -0,0 +1,2 @@ +all: + g++ -O2 -g -pthread -std=c++11 optimized-parse.cc diff --git a/sandbox/optimized-parse.cc b/sandbox/optimized-parse.cc new file mode 100644 index 0000000..686ff11 --- /dev/null +++ b/sandbox/optimized-parse.cc @@ -0,0 +1,870 @@ +#ifdef _WIN64 +#define atoll(S) _atoi64(S) +#include +#else +#include +#include +#include +#include +#include +#include +#endif + +#include +#include +#include +#include +#include +#include +#include + +#include // C++11 +#include // C++11 +#include // C++11 + +// ---------------------------------------------------------------------------- +// Small vector class useful for multi-threaded environment. +// +// stack_container.h +// +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This allocator can be used with STL containers to provide a stack buffer +// from which to allocate memory and overflows onto the heap. This stack buffer +// would be allocated on the stack and allows us to avoid heap operations in +// some situations. +// +// STL likes to make copies of allocators, so the allocator itself can't hold +// the data. Instead, we make the creator responsible for creating a +// StackAllocator::Source which contains the data. Copying the allocator +// merely copies the pointer to this shared source, so all allocators created +// based on our allocator will share the same stack buffer. +// +// This stack buffer implementation is very simple. The first allocation that +// fits in the stack buffer will use the stack buffer. Any subsequent +// allocations will not use the stack buffer, even if there is unused room. +// This makes it appropriate for array-like containers, but the caller should +// be sure to reserve() in the container up to the stack buffer size. Otherwise +// the container will allocate a small array which will "use up" the stack +// buffer. +template +class StackAllocator : public std::allocator { + public: + typedef typename std::allocator::pointer pointer; + typedef typename std::allocator::size_type size_type; + + // Backing store for the allocator. The container owner is responsible for + // maintaining this for as long as any containers using this allocator are + // live. + struct Source { + Source() : used_stack_buffer_(false) {} + + // Casts the buffer in its right type. + T *stack_buffer() { return reinterpret_cast(stack_buffer_); } + const T *stack_buffer() const { + return reinterpret_cast(stack_buffer_); + } + + // + // IMPORTANT: Take care to ensure that stack_buffer_ is aligned + // since it is used to mimic an array of T. + // Be careful while declaring any unaligned types (like bool) + // before stack_buffer_. + // + + // The buffer itself. It is not of type T because we don't want the + // constructors and destructors to be automatically called. Define a POD + // buffer of the right size instead. + char stack_buffer_[sizeof(T[stack_capacity])]; + + // Set when the stack buffer is used for an allocation. We do not track + // how much of the buffer is used, only that somebody is using it. + bool used_stack_buffer_; + }; + + // Used by containers when they want to refer to an allocator of type U. + template + struct rebind { + typedef StackAllocator other; + }; + + // For the straight up copy c-tor, we can share storage. + StackAllocator(const StackAllocator &rhs) + : source_(rhs.source_) {} + + // ISO C++ requires the following constructor to be defined, + // and std::vector in VC++2008SP1 Release fails with an error + // in the class _Container_base_aux_alloc_real (from ) + // if the constructor does not exist. + // For this constructor, we cannot share storage; there's + // no guarantee that the Source buffer of Ts is large enough + // for Us. + // TODO(Google): If we were fancy pants, perhaps we could share storage + // iff sizeof(T) == sizeof(U). + template + StackAllocator(const StackAllocator &other) + : source_(NULL) { + (void)other; + } + + explicit StackAllocator(Source *source) : source_(source) {} + + // Actually do the allocation. Use the stack buffer if nobody has used it yet + // and the size requested fits. Otherwise, fall through to the standard + // allocator. + pointer allocate(size_type n, void *hint = 0) { + if (source_ != NULL && !source_->used_stack_buffer_ && + n <= stack_capacity) { + source_->used_stack_buffer_ = true; + return source_->stack_buffer(); + } else { + return std::allocator::allocate(n, hint); + } + } + + // Free: when trying to free the stack buffer, just mark it as free. For + // non-stack-buffer pointers, just fall though to the standard allocator. + void deallocate(pointer p, size_type n) { + if (source_ != NULL && p == source_->stack_buffer()) + source_->used_stack_buffer_ = false; + else + std::allocator::deallocate(p, n); + } + + private: + Source *source_; +}; + +// A wrapper around STL containers that maintains a stack-sized buffer that the +// initial capacity of the vector is based on. Growing the container beyond the +// stack capacity will transparently overflow onto the heap. The container must +// support reserve(). +// +// WATCH OUT: the ContainerType MUST use the proper StackAllocator for this +// type. This object is really intended to be used only internally. You'll want +// to use the wrappers below for different types. +template +class StackContainer { + public: + typedef TContainerType ContainerType; + typedef typename ContainerType::value_type ContainedType; + typedef StackAllocator Allocator; + + // Allocator must be constructed before the container! + StackContainer() : allocator_(&stack_data_), container_(allocator_) { + // Make the container use the stack allocation by reserving our buffer size + // before doing anything else. + container_.reserve(stack_capacity); + } + + // Getters for the actual container. + // + // Danger: any copies of this made using the copy constructor must have + // shorter lifetimes than the source. The copy will share the same allocator + // and therefore the same stack buffer as the original. Use std::copy to + // copy into a "real" container for longer-lived objects. + ContainerType &container() { return container_; } + const ContainerType &container() const { return container_; } + + // Support operator-> to get to the container. This allows nicer syntax like: + // StackContainer<...> foo; + // std::sort(foo->begin(), foo->end()); + ContainerType *operator->() { return &container_; } + const ContainerType *operator->() const { return &container_; } + +#ifdef UNIT_TEST + // Retrieves the stack source so that that unit tests can verify that the + // buffer is being used properly. + const typename Allocator::Source &stack_data() const { return stack_data_; } +#endif + + protected: + typename Allocator::Source stack_data_; + unsigned char pad_[7]; + Allocator allocator_; + ContainerType container_; + + // DISALLOW_EVIL_CONSTRUCTORS(StackContainer); + StackContainer(const StackContainer &); + void operator=(const StackContainer &); +}; + +// StackVector +// +// Example: +// StackVector foo; +// foo->push_back(22); // we have overloaded operator-> +// foo[0] = 10; // as well as operator[] +template +class StackVector + : public StackContainer >, + stack_capacity> { + public: + StackVector() + : StackContainer >, + stack_capacity>() {} + + // We need to put this in STL containers sometimes, which requires a copy + // constructor. We can't call the regular copy constructor because that will + // take the stack buffer from the original. Here, we create an empty object + // and make a stack buffer of its own. + StackVector(const StackVector &other) + : StackContainer >, + stack_capacity>() { + this->container().assign(other->begin(), other->end()); + } + + StackVector &operator=( + const StackVector &other) { + this->container().assign(other->begin(), other->end()); + return *this; + } + + // Vectors are commonly indexed, which isn't very convenient even with + // operator-> (using "->at()" does exception stuff we don't want). + T &operator[](size_t i) { return this->container().operator[](i); } + const T &operator[](size_t i) const { + return this->container().operator[](i); + } +}; + +// ---------------------------------------------------------------------------- + +typedef StackVector ShortString; + +#define IS_SPACE(x) (((x) == ' ') || ((x) == '\t')) +#define IS_DIGIT(x) \ + (static_cast((x) - '0') < static_cast(10)) +#define IS_NEW_LINE(x) (((x) == '\r') || ((x) == '\n') || ((x) == '\0')) + +struct vertex_index { + int v_idx, vt_idx, vn_idx; + vertex_index() : v_idx(-1), vt_idx(-1), vn_idx(-1) {} + explicit 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) {} +}; + +// 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 +} + +// Parse triples with index offsets: 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); + + 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/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; +} + +// Parse raw triples: i, i/j/k, i//k, i/j +static vertex_index parseRawTriple(const char **token) { + vertex_index vi( + static_cast(0x80000000)); // 0x80000000 = -2147483648 = invalid + + vi.v_idx = atoi((*token)); + (*token) += strcspn((*token), "/ \t\r"); + if ((*token)[0] != '/') { + return vi; + } + (*token)++; + + // i//k + if ((*token)[0] == '/') { + (*token)++; + vi.vn_idx = atoi((*token)); + (*token) += strcspn((*token), "/ \t\r"); + return vi; + } + + // i/j/k or i/j + vi.vt_idx = atoi((*token)); + (*token) += strcspn((*token), "/ \t\r"); + if ((*token)[0] != '/') { + return vi; + } + + // i/j/k + (*token)++; // skip '/' + vi.vn_idx = atoi((*token)); + (*token) += strcspn((*token), "/ \t\r"); + return vi; +} + +static inline bool parseString(ShortString *s, const char **token) { + (*token) += strspn((*token), " \t"); + size_t e = strcspn((*token), " \t\r"); + (*s)->insert((*s)->end(), (*token), (*token) + e); + (*token) += e; + return true; +} + +static inline int parseInt(const char **token) { + (*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 +// stop. For example at the end of the string, to prevent buffer overflows. +// +// Parses the following EBNF grammar: +// sign = "+" | "-" ; +// END = ? anything not in digit ? +// digit = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" ; +// integer = [sign] , digit , {digit} ; +// decimal = integer , ["." , integer] ; +// float = ( decimal , END ) | ( decimal , ("E" | "e") , integer , END ) ; +// +// 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 +// is returned. +// +// The function is greedy and will parse until any of the following happens: +// - a non-conforming character is encountered. +// - s_end is reached. +// +// 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) { + 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 + // 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 + // exponent to be in base 2. + int exponent = 0; + + // NOTE: THESE MUST BE DECLARED HERE SINCE WE ARE NOT ALLOWED + // TO JUMP OVER DEFINITIONS. + char sign = '+'; + char exp_sign = '+'; + char const *curr = s; + + // 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; + + /* + BEGIN PARSING. + */ + + // Find out what sign we've got. + if (*curr == '+' || *curr == '-') { + sign = *curr; + curr++; + } else if (IS_DIGIT(*curr)) { /* Pass through. */ + } else { + goto fail; + } + + // Read the integer part. + end_not_reached = (curr != s_end); + while (end_not_reached && IS_DIGIT(*curr)) { + mantissa *= 10; + mantissa += static_cast(*curr - 0x30); + curr++; + read++; + end_not_reached = (curr != s_end); + } + + // We must make sure we actually got something. + if (read == 0) goto fail; + // We allow numbers of form "#", "###" etc. + if (!end_not_reached) goto assemble; + + // Read the decimal part. + if (*curr == '.') { + curr++; + read = 1; + end_not_reached = (curr != s_end); + while (end_not_reached && IS_DIGIT(*curr)) { + // NOTE: Don't use powf here, it will absolutely murder precision. + mantissa += static_cast(*curr - 0x30) * pow(10.0, -read); + read++; + curr++; + end_not_reached = (curr != s_end); + } + } else if (*curr == 'e' || *curr == 'E') { + } else { + goto assemble; + } + + if (!end_not_reached) goto assemble; + + // Read the exponent part. + if (*curr == 'e' || *curr == 'E') { + curr++; + // Figure out if a sign is present and if it is. + end_not_reached = (curr != s_end); + if (end_not_reached && (*curr == '+' || *curr == '-')) { + exp_sign = *curr; + curr++; + } else if (IS_DIGIT(*curr)) { /* Pass through. */ + } else { + // Empty E is not allowed. + goto fail; + } + + read = 0; + end_not_reached = (curr != s_end); + while (end_not_reached && IS_DIGIT(*curr)) { + exponent *= 10; + exponent += static_cast(*curr - 0x30); + curr++; + read++; + end_not_reached = (curr != s_end); + } + exponent *= (exp_sign == '+' ? 1 : -1); + if (read == 0) goto fail; + } + +assemble: + *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"); +#ifdef TINY_OBJ_LOADER_OLD_FLOAT_PARSER + float f = static_cast(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; +#endif + return f; +} + +static inline void parseFloat2(float *x, float *y, const char **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); +} + +typedef enum +{ + COMMAND_EMPTY, + COMMAND_V, + COMMAND_VN, + COMMAND_VT, + COMMAND_F, + COMMAND_G, + COMMAND_O, + COMMAND_USEMTL, + +} CommandType; + +typedef struct +{ + float vx, vy, vz; + float nx, ny, nz; + float tx, ty; + + StackVector f; + ShortString group_name; + ShortString object_name; + ShortString material_name; + + CommandType type; +} Command; + +bool test(Command *command, const char *p, size_t p_len) +{ + std::vector linebuf(p_len + 1, '\0'); + memcpy(&linebuf.at(0), p, p_len); + + const char *token = &linebuf.at(0); + + // Skip leading space. + token += strspn(token, " \t"); + + assert(token); + if (token[0] == '\0') { // empty line + return false; + } + + if (token[0] == '#') { // comment line + return false; + } + + // vertex + if (token[0] == 'v' && IS_SPACE((token[1]))) { + token += 2; + float x, y, z; + parseFloat3(&x, &y, &z, &token); + command->vx = x; + command->vy = y; + command->vz = z; + command->type = COMMAND_V; + return true; + } + + // normal + if (token[0] == 'v' && token[1] == 'n' && IS_SPACE((token[2]))) { + token += 3; + float x, y, z; + parseFloat3(&x, &y, &z, &token); + command->nx = x; + command->ny = y; + command->nz = z; + command->type = COMMAND_VN; + return true; + } + + // texcoord + if (token[0] == 'v' && token[1] == 't' && IS_SPACE((token[2]))) { + token += 3; + float x, y; + parseFloat2(&x, &y, &token); + command->tx = x; + command->ty = y; + command->type = COMMAND_VT; + return true; + } + + // face + if (token[0] == 'f' && IS_SPACE((token[1]))) { + token += 2; + token += strspn(token, " \t"); + + while (!IS_NEW_LINE(token[0])) { + vertex_index vi = parseRawTriple(&token); + //if (callback.index_cb) { + // callback.index_cb(user_data, vi.v_idx, vi.vn_idx, vi.vt_idx); + //} + size_t n = strspn(token, " \t\r"); + token += n; + + command->f->push_back(vi); + } + + command->type = COMMAND_F; + + return true; + } + + // use mtl + if ((0 == strncmp(token, "usemtl", 6)) && IS_SPACE((token[6]))) { + char namebuf[8192]; + token += 7; +#ifdef _MSC_VER + sscanf_s(token, "%s", namebuf, (unsigned)_countof(namebuf)); +#else + sscanf(token, "%s", namebuf); +#endif + + //int newMaterialId = -1; + //if (material_map.find(namebuf) != material_map.end()) { + // newMaterialId = material_map[namebuf]; + //} else { + // // { error!! material not found } + //} + + //if (newMaterialId != materialId) { + // materialId = newMaterialId; + //} + + command->material_name->insert(command->material_name->end(), namebuf, namebuf + strlen(namebuf)); + command->material_name->push_back('\0'); + command->type = COMMAND_USEMTL; + + return true; + } + +#if 0 + // load mtl + if ((0 == strncmp(token, "mtllib", 6)) && IS_SPACE((token[6]))) { + char namebuf[TINYOBJ_SSCANF_BUFFER_SIZE]; + token += 7; +#ifdef _MSC_VER + sscanf_s(token, "%s", namebuf, (unsigned)_countof(namebuf)); +#else + sscanf(token, "%s", namebuf); +#endif + + std::string err_mtl; + std::vector materials; + bool ok = (*readMatFn)(namebuf, &materials, &material_map, &err_mtl); + if (err) { + (*err) += err_mtl; + } + + if (!ok) { + return false; + } + + if (callback.mtllib_cb) { + callback.mtllib_cb(user_data, &materials.at(0), + static_cast(materials.size())); + } + + continue; + } +#endif + + // group name + if (token[0] == 'g' && IS_SPACE((token[1]))) { + ShortString names[16]; + int num_names = 0; + + while (!IS_NEW_LINE(token[0])) { + bool ret = parseString(&(names[num_names]), &token); + assert(ret); + token += strspn(token, " \t\r"); // skip tag + num_names++; + } + + assert(num_names > 0); + + int name_idx = 0; + // names[0] must be 'g', so skip the 0th element. + if (num_names > 1) { + name_idx = 1; + } + + //command->group_name->assign(names[name_idx]); + command->type = COMMAND_G; + + //if (callback.group_cb) { + // if (names.size() > 1) { + // // create const char* array. + // std::vector tmp(names.size() - 1); + // for (size_t j = 0; j < tmp.size(); j++) { + // tmp[j] = names[j + 1].c_str(); + // } + // callback.group_cb(user_data, &tmp.at(0), + // static_cast(tmp.size())); + + // } else { + // callback.group_cb(user_data, NULL, 0); + // } + + //} + return true; + } + + // object name + if (token[0] == 'o' && IS_SPACE((token[1]))) { + // @todo { multiple object name? } + char namebuf[8192]; + token += 2; +#ifdef _MSC_VER + sscanf_s(token, "%s", namebuf, (unsigned)_countof(namebuf)); +#else + sscanf(token, "%s", namebuf); +#endif + + //if (callback.object_cb) { + // callback.object_cb(user_data, name.c_str()); + //} + + command->object_name->insert(command->object_name->end(), namebuf, namebuf + strlen(namebuf)); + command->object_name->push_back('\0'); + command->type = COMMAND_O; + + return true; + } + + + return false; +} + +typedef struct +{ + size_t pos; + size_t len; +} LineInfo; + +// Idea come from https://github.com/antonmks/nvParse +// 1. mmap file +// 2. find newline(\n) and list of line data. +// 3. Do parallel parsing for each line. +// 4. Reconstruct final mesh data structure. + +void parse(const char* buf, size_t len) +{ + std::vector newline_marker(len, 0); + + auto num_threads = std::thread::hardware_concurrency(); + std::cout << "# of threads = " << num_threads << std::endl; + + auto t1 = std::chrono::high_resolution_clock::now(); + + std::atomic newline_counter(0); + std::vector workers; + + std::vector line_infos; + line_infos.reserve(len / 1024); // len / 1024 = heuristics + + // @todo { parallelize lineinfo construction ? } + size_t prev_pos = 0; + for (size_t i = 0; i < len - 1; i++) { + if (buf[i] == '\n') { + LineInfo info; + info.pos = prev_pos; + info.len = i - prev_pos; + + //printf("p = %d, len = %d\n", prev_pos, info.len); + + if (info.len > 0) { + line_infos.push_back(info); + } + + prev_pos = i+1; + } + } + + auto chunk = line_infos.size() / num_threads; + + for (auto t = 0; t < num_threads; t++) { + workers.push_back(std::thread([&, t]() { + auto start_idx = (t + 0) * chunk; + auto end_idx = std::min((t + 1) * chunk, line_infos.size()); + + for (auto i = start_idx; i < end_idx; i++) { + Command cmd; + bool ret = test(&cmd, &buf[line_infos[i].pos], line_infos[i].len); + if (ret) { + } + } + + })); + } + + for (auto &t : workers) { + t.join(); + } + + auto t2 = std::chrono::high_resolution_clock::now(); + + std::chrono::duration ms = t2 - t1; + + std::cout << ms.count() << " ms\n"; + + std::cout << "# of newlines = " << newline_counter << std::endl; + std::cout << "# of lines = " << line_infos.size() << std::endl; +} + +int +main(int argc, char **argv) +{ + + if (argc < 2) { + printf("Needs .obj\n"); + return EXIT_FAILURE; + } + +#ifdef _WIN64 + HANDLE file = CreateFileA("lineitem.tbl", GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, NULL); + assert(file != INVALID_HANDLE_VALUE); + + HANDLE fileMapping = CreateFileMapping(file, NULL, PAGE_READONLY, 0, 0, NULL); + assert(fileMapping != INVALID_HANDLE_VALUE); + + LPVOID fileMapView = MapViewOfFile(fileMapping, FILE_MAP_READ, 0, 0, 0); + auto fileMapViewChar = (const char*)fileMapView; + assert(fileMapView != NULL); +#else + + FILE* f = fopen(argv[1], "r" ); + fseek(f, 0, SEEK_END); + long fileSize = ftell(f); + fclose(f); + + struct stat sb; + char *p; + int fd; + + fd = open (argv[1], O_RDONLY); + if (fd == -1) { + perror ("open"); + return 1; + } + + if (fstat (fd, &sb) == -1) { + perror ("fstat"); + return 1; + } + + if (!S_ISREG (sb.st_mode)) { + fprintf (stderr, "%s is not a file\n", "lineitem.tbl"); + return 1; + } + + p = (char*)mmap (0, fileSize, PROT_READ, MAP_SHARED, fd, 0); + + if (p == MAP_FAILED) { + perror ("mmap"); + return 1; + } + + if (close (fd) == -1) { + perror ("close"); + return 1; + } + + printf("fsize: %lu\n", fileSize); +#endif + + parse(p, fileSize); + + return EXIT_SUCCESS; +} -- cgit v1.2.3 From a20e4ede852de1d4cca132fb89db371d42e00fc0 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Fri, 13 May 2016 20:09:49 +0900 Subject: 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 -- cgit v1.2.3 From 41f46c7fd73dc2fd55702a7aa4cdc55b9ec58328 Mon Sep 17 00:00:00 2001 From: Vazquinhos Date: Fri, 13 May 2016 13:54:27 +0200 Subject: Error fixed with no triangulation. Removed warnings and modified coding brace style --- tiny_obj_loader.h | 61 ++++++++++++++++++++++++++----------------------------- 1 file changed, 29 insertions(+), 32 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)); - - 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(); - } - } + 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; -- cgit v1.2.3 From 109090e5b87779a7ad5b5fd5bb3168ff0b026bba Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Fri, 13 May 2016 20:59:42 +0900 Subject: Optimized parser more(not working yet, though) --- sandbox/optimized-parse.cc | 99 +++++++++++++++++++++++++++++++++++----------- 1 file changed, 75 insertions(+), 24 deletions(-) diff --git a/sandbox/optimized-parse.cc b/sandbox/optimized-parse.cc index 686ff11..ed7c758 100644 --- a/sandbox/optimized-parse.cc +++ b/sandbox/optimized-parse.cc @@ -239,6 +239,29 @@ typedef StackVector ShortString; (static_cast((x) - '0') < static_cast(10)) #define IS_NEW_LINE(x) (((x) == '\r') || ((x) == '\n') || ((x) == '\0')) +static inline void skip_space(const char **token) +{ + while ((*token)[0] == ' ' || (*token)[0] == '\t') { + (*token)++; + } +} + +// http://stackoverflow.com/questions/5710091/how-does-atoi-function-in-c-work +int my_atoi( const char *c ) { + int value = 0; + int sign = 1; + if( *c == '+' || *c == '-' ) { + if( *c == '-' ) sign = -1; + c++; + } + while ( isdigit( *c ) ) { + value *= 10; + value += (int) (*c-'0'); + c++; + } + return value * sign; +} + struct vertex_index { int v_idx, vt_idx, vn_idx; vertex_index() : v_idx(-1), vt_idx(-1), vn_idx(-1) {} @@ -259,8 +282,12 @@ static vertex_index parseTriple(const char **token, int vsize, int vnsize, int vtsize) { vertex_index vi(-1); - vi.v_idx = fixIndex(atoi((*token)), vsize); - (*token) += strcspn((*token), "/ \t\r"); + vi.v_idx = fixIndex(my_atoi((*token)), vsize); + + //(*token) += strcspn((*token), "/ \t\r"); + while ((*token)[0] != '\0' && (*token)[0] != '/' && (*token)[0] != ' ' && (*token)[0] != '\t' && (*token)[0] != '\r') { + (*token)++; + } if ((*token)[0] != '/') { return vi; } @@ -269,22 +296,31 @@ static vertex_index parseTriple(const char **token, int vsize, int vnsize, // i//k if ((*token)[0] == '/') { (*token)++; - vi.vn_idx = fixIndex(atoi((*token)), vnsize); - (*token) += strcspn((*token), "/ \t\r"); + vi.vn_idx = fixIndex(my_atoi((*token)), vnsize); + //(*token) += strcspn((*token), "/ \t\r"); + while ((*token)[0] != '\0' && (*token)[0] != '/' && (*token)[0] != ' ' && (*token)[0] != '\t' && (*token)[0] != '\r') { + (*token)++; + } return vi; } // i/j/k or i/j - vi.vt_idx = fixIndex(atoi((*token)), vtsize); - (*token) += strcspn((*token), "/ \t\r"); + vi.vt_idx = fixIndex(my_atoi((*token)), vtsize); + //(*token) += strcspn((*token), "/ \t\r"); + while ((*token)[0] != '\0' && (*token)[0] != '/' && (*token)[0] != ' ' && (*token)[0] != '\t' && (*token)[0] != '\r') { + (*token)++; + } if ((*token)[0] != '/') { return vi; } // i/j/k (*token)++; // skip '/' - vi.vn_idx = fixIndex(atoi((*token)), vnsize); - (*token) += strcspn((*token), "/ \t\r"); + vi.vn_idx = fixIndex(my_atoi((*token)), vnsize); + //(*token) += strcspn((*token), "/ \t\r"); + while ((*token)[0] != '\0' && (*token)[0] != '/' && (*token)[0] != ' ' && (*token)[0] != '\t' && (*token)[0] != '\r') { + (*token)++; + } return vi; } @@ -293,8 +329,11 @@ static vertex_index parseRawTriple(const char **token) { vertex_index vi( static_cast(0x80000000)); // 0x80000000 = -2147483648 = invalid - vi.v_idx = atoi((*token)); - (*token) += strcspn((*token), "/ \t\r"); + vi.v_idx = my_atoi((*token)); + //(*token) += strcspn((*token), "/ \t\r"); + while ((*token)[0] != '\0' && (*token)[0] != '/' && (*token)[0] != ' ' && (*token)[0] != '\t' && (*token)[0] != '\r') { + (*token)++; + } if ((*token)[0] != '/') { return vi; } @@ -303,27 +342,36 @@ static vertex_index parseRawTriple(const char **token) { // i//k if ((*token)[0] == '/') { (*token)++; - vi.vn_idx = atoi((*token)); - (*token) += strcspn((*token), "/ \t\r"); + vi.vn_idx = my_atoi((*token)); + //(*token) += strcspn((*token), "/ \t\r"); + while ((*token)[0] != '\0' && (*token)[0] != '/' && (*token)[0] != ' ' && (*token)[0] != '\t' && (*token)[0] != '\r') { + (*token)++; + } return vi; } // i/j/k or i/j - vi.vt_idx = atoi((*token)); - (*token) += strcspn((*token), "/ \t\r"); + vi.vt_idx = my_atoi((*token)); + //(*token) += strcspn((*token), "/ \t\r"); + while ((*token)[0] != '\0' && (*token)[0] != '/' && (*token)[0] != ' ' && (*token)[0] != '\t' && (*token)[0] != '\r') { + (*token)++; + } if ((*token)[0] != '/') { return vi; } // i/j/k (*token)++; // skip '/' - vi.vn_idx = atoi((*token)); - (*token) += strcspn((*token), "/ \t\r"); + vi.vn_idx = my_atoi((*token)); + //(*token) += strcspn((*token), "/ \t\r"); + while ((*token)[0] != '\0' && (*token)[0] != '/' && (*token)[0] != ' ' && (*token)[0] != '\t' && (*token)[0] != '\r') { + (*token)++; + } return vi; } static inline bool parseString(ShortString *s, const char **token) { - (*token) += strspn((*token), " \t"); + skip_space(token); //(*token) += strspn((*token), " \t"); size_t e = strcspn((*token), " \t\r"); (*s)->insert((*s)->end(), (*token), (*token) + e); (*token) += e; @@ -331,8 +379,8 @@ static inline bool parseString(ShortString *s, const char **token) { } static inline int parseInt(const char **token) { - (*token) += strspn((*token), " \t"); - int i = atoi((*token)); + skip_space(token); //(*token) += strspn((*token), " \t"); + int i = my_atoi((*token)); (*token) += strcspn((*token), " \t\r"); return i; } @@ -473,7 +521,7 @@ fail: } static inline float parseFloat(const char **token) { - (*token) += strspn((*token), " \t"); + skip_space(token); //(*token) += strspn((*token), " \t"); #ifdef TINY_OBJ_LOADER_OLD_FLOAT_PARSER float f = static_cast(atof(*token)); (*token) += strcspn((*token), " \t\r"); @@ -528,13 +576,16 @@ typedef struct bool test(Command *command, const char *p, size_t p_len) { - std::vector linebuf(p_len + 1, '\0'); - memcpy(&linebuf.at(0), p, p_len); + StackVector linebuf; + linebuf->resize(p_len + 1); + memcpy(&linebuf->at(0), p, p_len); + linebuf[p_len] = '\0'; - const char *token = &linebuf.at(0); + const char *token = &linebuf->at(0); // Skip leading space. - token += strspn(token, " \t"); + //token += strspn(token, " \t"); + skip_space(&token); //(*token) += strspn((*token), " \t"); assert(token); if (token[0] == '\0') { // empty line -- cgit v1.2.3 From db62284ceff98d0799b66cb6fb41227f53f4afc1 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Sat, 14 May 2016 12:33:12 +0900 Subject: Optimized more. --- experimental/Makefile | 2 + experimental/optimized-parse.cc | 1014 +++++++++++++++++++++++++++++++++++++++ sandbox/Makefile | 2 - sandbox/optimized-parse.cc | 921 ----------------------------------- 4 files changed, 1016 insertions(+), 923 deletions(-) create mode 100644 experimental/Makefile create mode 100644 experimental/optimized-parse.cc delete mode 100644 sandbox/Makefile delete mode 100644 sandbox/optimized-parse.cc diff --git a/experimental/Makefile b/experimental/Makefile new file mode 100644 index 0000000..f72d56e --- /dev/null +++ b/experimental/Makefile @@ -0,0 +1,2 @@ +all: + g++ -O2 -g -pthread -std=c++11 optimized-parse.cc diff --git a/experimental/optimized-parse.cc b/experimental/optimized-parse.cc new file mode 100644 index 0000000..90f268d --- /dev/null +++ b/experimental/optimized-parse.cc @@ -0,0 +1,1014 @@ +#ifdef _WIN64 +#define atoll(S) _atoi64(S) +#include +#else +#include +#include +#include +#include +#include +#include +#endif + +#include +#include +#include +#include +#include +#include +#include + +#include // C++11 +#include // C++11 +#include // C++11 + +// ---------------------------------------------------------------------------- +// Small vector class useful for multi-threaded environment. +// +// stack_container.h +// +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This allocator can be used with STL containers to provide a stack buffer +// from which to allocate memory and overflows onto the heap. This stack buffer +// would be allocated on the stack and allows us to avoid heap operations in +// some situations. +// +// STL likes to make copies of allocators, so the allocator itself can't hold +// the data. Instead, we make the creator responsible for creating a +// StackAllocator::Source which contains the data. Copying the allocator +// merely copies the pointer to this shared source, so all allocators created +// based on our allocator will share the same stack buffer. +// +// This stack buffer implementation is very simple. The first allocation that +// fits in the stack buffer will use the stack buffer. Any subsequent +// allocations will not use the stack buffer, even if there is unused room. +// This makes it appropriate for array-like containers, but the caller should +// be sure to reserve() in the container up to the stack buffer size. Otherwise +// the container will allocate a small array which will "use up" the stack +// buffer. +template +class StackAllocator : public std::allocator { + public: + typedef typename std::allocator::pointer pointer; + typedef typename std::allocator::size_type size_type; + + // Backing store for the allocator. The container owner is responsible for + // maintaining this for as long as any containers using this allocator are + // live. + struct Source { + Source() : used_stack_buffer_(false) {} + + // Casts the buffer in its right type. + T *stack_buffer() { return reinterpret_cast(stack_buffer_); } + const T *stack_buffer() const { + return reinterpret_cast(stack_buffer_); + } + + // + // IMPORTANT: Take care to ensure that stack_buffer_ is aligned + // since it is used to mimic an array of T. + // Be careful while declaring any unaligned types (like bool) + // before stack_buffer_. + // + + // The buffer itself. It is not of type T because we don't want the + // constructors and destructors to be automatically called. Define a POD + // buffer of the right size instead. + char stack_buffer_[sizeof(T[stack_capacity])]; + + // Set when the stack buffer is used for an allocation. We do not track + // how much of the buffer is used, only that somebody is using it. + bool used_stack_buffer_; + }; + + // Used by containers when they want to refer to an allocator of type U. + template + struct rebind { + typedef StackAllocator other; + }; + + // For the straight up copy c-tor, we can share storage. + StackAllocator(const StackAllocator &rhs) + : source_(rhs.source_) {} + + // ISO C++ requires the following constructor to be defined, + // and std::vector in VC++2008SP1 Release fails with an error + // in the class _Container_base_aux_alloc_real (from ) + // if the constructor does not exist. + // For this constructor, we cannot share storage; there's + // no guarantee that the Source buffer of Ts is large enough + // for Us. + // TODO(Google): If we were fancy pants, perhaps we could share storage + // iff sizeof(T) == sizeof(U). + template + StackAllocator(const StackAllocator &other) + : source_(NULL) { + (void)other; + } + + explicit StackAllocator(Source *source) : source_(source) {} + + // Actually do the allocation. Use the stack buffer if nobody has used it yet + // and the size requested fits. Otherwise, fall through to the standard + // allocator. + pointer allocate(size_type n, void *hint = 0) { + if (source_ != NULL && !source_->used_stack_buffer_ && + n <= stack_capacity) { + source_->used_stack_buffer_ = true; + return source_->stack_buffer(); + } else { + return std::allocator::allocate(n, hint); + } + } + + // Free: when trying to free the stack buffer, just mark it as free. For + // non-stack-buffer pointers, just fall though to the standard allocator. + void deallocate(pointer p, size_type n) { + if (source_ != NULL && p == source_->stack_buffer()) + source_->used_stack_buffer_ = false; + else + std::allocator::deallocate(p, n); + } + + private: + Source *source_; +}; + +// A wrapper around STL containers that maintains a stack-sized buffer that the +// initial capacity of the vector is based on. Growing the container beyond the +// stack capacity will transparently overflow onto the heap. The container must +// support reserve(). +// +// WATCH OUT: the ContainerType MUST use the proper StackAllocator for this +// type. This object is really intended to be used only internally. You'll want +// to use the wrappers below for different types. +template +class StackContainer { + public: + typedef TContainerType ContainerType; + typedef typename ContainerType::value_type ContainedType; + typedef StackAllocator Allocator; + + // Allocator must be constructed before the container! + StackContainer() : allocator_(&stack_data_), container_(allocator_) { + // Make the container use the stack allocation by reserving our buffer size + // before doing anything else. + container_.reserve(stack_capacity); + } + + // Getters for the actual container. + // + // Danger: any copies of this made using the copy constructor must have + // shorter lifetimes than the source. The copy will share the same allocator + // and therefore the same stack buffer as the original. Use std::copy to + // copy into a "real" container for longer-lived objects. + ContainerType &container() { return container_; } + const ContainerType &container() const { return container_; } + + // Support operator-> to get to the container. This allows nicer syntax like: + // StackContainer<...> foo; + // std::sort(foo->begin(), foo->end()); + ContainerType *operator->() { return &container_; } + const ContainerType *operator->() const { return &container_; } + +#ifdef UNIT_TEST + // Retrieves the stack source so that that unit tests can verify that the + // buffer is being used properly. + const typename Allocator::Source &stack_data() const { return stack_data_; } +#endif + + protected: + typename Allocator::Source stack_data_; + unsigned char pad_[7]; + Allocator allocator_; + ContainerType container_; + + // DISALLOW_EVIL_CONSTRUCTORS(StackContainer); + StackContainer(const StackContainer &); + void operator=(const StackContainer &); +}; + +// StackVector +// +// Example: +// StackVector foo; +// foo->push_back(22); // we have overloaded operator-> +// foo[0] = 10; // as well as operator[] +template +class StackVector + : public StackContainer >, + stack_capacity> { + public: + StackVector() + : StackContainer >, + stack_capacity>() {} + + // We need to put this in STL containers sometimes, which requires a copy + // constructor. We can't call the regular copy constructor because that will + // take the stack buffer from the original. Here, we create an empty object + // and make a stack buffer of its own. + StackVector(const StackVector &other) + : StackContainer >, + stack_capacity>() { + this->container().assign(other->begin(), other->end()); + } + + StackVector &operator=( + const StackVector &other) { + this->container().assign(other->begin(), other->end()); + return *this; + } + + // Vectors are commonly indexed, which isn't very convenient even with + // operator-> (using "->at()" does exception stuff we don't want). + T &operator[](size_t i) { return this->container().operator[](i); } + const T &operator[](size_t i) const { + return this->container().operator[](i); + } +}; + +// ---------------------------------------------------------------------------- + +typedef StackVector ShortString; + +#define IS_SPACE(x) (((x) == ' ') || ((x) == '\t')) +#define IS_DIGIT(x) \ + (static_cast((x) - '0') < static_cast(10)) +#define IS_NEW_LINE(x) (((x) == '\r') || ((x) == '\n') || ((x) == '\0')) + +static inline void skip_space(const char **token) +{ + while ((*token)[0] == ' ' || (*token)[0] == '\t') { + (*token)++; + } +} + +static inline void skip_space_and_cr(const char **token) +{ + while ((*token)[0] == ' ' || (*token)[0] == '\t' || (*token)[0] == '\r') { + (*token)++; + } +} + +static inline int until_space(const char *token) +{ + const char *p = token; + while (p[0] != '\0' && p[0] != ' ' && p[0] != '\t' && p[0] != '\r') { + p++; + } + + return p - token; +} + +// http://stackoverflow.com/questions/5710091/how-does-atoi-function-in-c-work +static inline int my_atoi( const char *c ) { + int value = 0; + int sign = 1; + if( *c == '+' || *c == '-' ) { + if( *c == '-' ) sign = -1; + c++; + } + while ( isdigit( *c ) ) { + value *= 10; + value += (int) (*c-'0'); + c++; + } + return value * sign; +} + +struct vertex_index { + int v_idx, vt_idx, vn_idx; + vertex_index() : v_idx(-1), vt_idx(-1), vn_idx(-1) {} + explicit 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) {} +}; + +// 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 +} + +// Parse triples with index offsets: 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); + + vi.v_idx = fixIndex(my_atoi((*token)), vsize); + + //(*token) += strcspn((*token), "/ \t\r"); + while ((*token)[0] != '\0' && (*token)[0] != '/' && (*token)[0] != ' ' && (*token)[0] != '\t' && (*token)[0] != '\r') { + (*token)++; + } + if ((*token)[0] != '/') { + return vi; + } + (*token)++; + + // i//k + if ((*token)[0] == '/') { + (*token)++; + vi.vn_idx = fixIndex(my_atoi((*token)), vnsize); + //(*token) += strcspn((*token), "/ \t\r"); + while ((*token)[0] != '\0' && (*token)[0] != '/' && (*token)[0] != ' ' && (*token)[0] != '\t' && (*token)[0] != '\r') { + (*token)++; + } + return vi; + } + + // i/j/k or i/j + vi.vt_idx = fixIndex(my_atoi((*token)), vtsize); + //(*token) += strcspn((*token), "/ \t\r"); + while ((*token)[0] != '\0' && (*token)[0] != '/' && (*token)[0] != ' ' && (*token)[0] != '\t' && (*token)[0] != '\r') { + (*token)++; + } + if ((*token)[0] != '/') { + return vi; + } + + // i/j/k + (*token)++; // skip '/' + vi.vn_idx = fixIndex(my_atoi((*token)), vnsize); + //(*token) += strcspn((*token), "/ \t\r"); + while ((*token)[0] != '\0' && (*token)[0] != '/' && (*token)[0] != ' ' && (*token)[0] != '\t' && (*token)[0] != '\r') { + (*token)++; + } + return vi; +} + +// Parse raw triples: i, i/j/k, i//k, i/j +static vertex_index parseRawTriple(const char **token) { + vertex_index vi( + static_cast(0x80000000)); // 0x80000000 = -2147483648 = invalid + + vi.v_idx = my_atoi((*token)); + //(*token) += strcspn((*token), "/ \t\r"); + while ((*token)[0] != '\0' && (*token)[0] != '/' && (*token)[0] != ' ' && (*token)[0] != '\t' && (*token)[0] != '\r') { + (*token)++; + } + if ((*token)[0] != '/') { + return vi; + } + (*token)++; + + // i//k + if ((*token)[0] == '/') { + (*token)++; + vi.vn_idx = my_atoi((*token)); + //(*token) += strcspn((*token), "/ \t\r"); + while ((*token)[0] != '\0' && (*token)[0] != '/' && (*token)[0] != ' ' && (*token)[0] != '\t' && (*token)[0] != '\r') { + (*token)++; + } + return vi; + } + + // i/j/k or i/j + vi.vt_idx = my_atoi((*token)); + //(*token) += strcspn((*token), "/ \t\r"); + while ((*token)[0] != '\0' && (*token)[0] != '/' && (*token)[0] != ' ' && (*token)[0] != '\t' && (*token)[0] != '\r') { + (*token)++; + } + if ((*token)[0] != '/') { + return vi; + } + + // i/j/k + (*token)++; // skip '/' + vi.vn_idx = my_atoi((*token)); + //(*token) += strcspn((*token), "/ \t\r"); + while ((*token)[0] != '\0' && (*token)[0] != '/' && (*token)[0] != ' ' && (*token)[0] != '\t' && (*token)[0] != '\r') { + (*token)++; + } + return vi; +} + +static inline bool parseString(ShortString *s, const char **token) { + skip_space(token); //(*token) += strspn((*token), " \t"); + size_t e = until_space((*token)); //strcspn((*token), " \t\r"); + (*s)->insert((*s)->end(), (*token), (*token) + e); + (*token) += e; + return true; +} + +static inline int parseInt(const char **token) { + skip_space(token); //(*token) += strspn((*token), " \t"); + int i = my_atoi((*token)); + (*token) += until_space((*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 +// stop. For example at the end of the string, to prevent buffer overflows. +// +// Parses the following EBNF grammar: +// sign = "+" | "-" ; +// END = ? anything not in digit ? +// digit = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" ; +// integer = [sign] , digit , {digit} ; +// decimal = integer , ["." , integer] ; +// float = ( decimal , END ) | ( decimal , ("E" | "e") , integer , END ) ; +// +// 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 +// is returned. +// +// The function is greedy and will parse until any of the following happens: +// - a non-conforming character is encountered. +// - s_end is reached. +// +// 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) { + 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 + // 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 + // exponent to be in base 2. + int exponent = 0; + + // NOTE: THESE MUST BE DECLARED HERE SINCE WE ARE NOT ALLOWED + // TO JUMP OVER DEFINITIONS. + char sign = '+'; + char exp_sign = '+'; + char const *curr = s; + + // 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; + + /* + BEGIN PARSING. + */ + + // Find out what sign we've got. + if (*curr == '+' || *curr == '-') { + sign = *curr; + curr++; + } else if (IS_DIGIT(*curr)) { /* Pass through. */ + } else { + goto fail; + } + + // Read the integer part. + end_not_reached = (curr != s_end); + while (end_not_reached && IS_DIGIT(*curr)) { + mantissa *= 10; + mantissa += static_cast(*curr - 0x30); + curr++; + read++; + end_not_reached = (curr != s_end); + } + + // We must make sure we actually got something. + if (read == 0) goto fail; + // We allow numbers of form "#", "###" etc. + if (!end_not_reached) goto assemble; + + // Read the decimal part. + if (*curr == '.') { + curr++; + read = 1; + end_not_reached = (curr != s_end); + while (end_not_reached && IS_DIGIT(*curr)) { + // NOTE: Don't use powf here, it will absolutely murder precision. + mantissa += static_cast(*curr - 0x30) * pow(10.0, -read); + read++; + curr++; + end_not_reached = (curr != s_end); + } + } else if (*curr == 'e' || *curr == 'E') { + } else { + goto assemble; + } + + if (!end_not_reached) goto assemble; + + // Read the exponent part. + if (*curr == 'e' || *curr == 'E') { + curr++; + // Figure out if a sign is present and if it is. + end_not_reached = (curr != s_end); + if (end_not_reached && (*curr == '+' || *curr == '-')) { + exp_sign = *curr; + curr++; + } else if (IS_DIGIT(*curr)) { /* Pass through. */ + } else { + // Empty E is not allowed. + goto fail; + } + + read = 0; + end_not_reached = (curr != s_end); + while (end_not_reached && IS_DIGIT(*curr)) { + exponent *= 10; + exponent += static_cast(*curr - 0x30); + curr++; + read++; + end_not_reached = (curr != s_end); + } + exponent *= (exp_sign == '+' ? 1 : -1); + if (read == 0) goto fail; + } + +assemble: + *result = + (sign == '+' ? 1 : -1) * ldexp(mantissa * pow(5.0, exponent), exponent); + return true; +fail: + return false; +} + +static inline float parseFloat(const char **token) { + skip_space(token); //(*token) += strspn((*token), " \t"); +#ifdef TINY_OBJ_LOADER_OLD_FLOAT_PARSER + float f = static_cast(atof(*token)); + (*token) += strcspn((*token), " \t\r"); +#else + const char *end = (*token) + until_space((*token)); //strcspn((*token), " \t\r"); + double val = 0.0; + tryParseDouble((*token), end, &val); + float f = static_cast(val); + (*token) = end; +#endif + return f; +} + +static inline void parseFloat2(float *x, float *y, const char **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); +} + +typedef enum +{ + COMMAND_EMPTY, + COMMAND_V, + COMMAND_VN, + COMMAND_VT, + COMMAND_F, + COMMAND_G, + COMMAND_O, + COMMAND_USEMTL, + +} CommandType; + +typedef struct +{ + float vx, vy, vz; + float nx, ny, nz; + float tx, ty; + + std::vector f; + const char* group_name; + const char* object_name; + const char* material_name; + + CommandType type; +} Command; + +bool parseLine(Command *command, const char *p, size_t p_len) +{ + StackVector linebuf; + linebuf->resize(p_len + 1); + memcpy(&linebuf->at(0), p, p_len); + linebuf[p_len] = '\0'; + + const char *token = &linebuf->at(0); + + command->type = COMMAND_EMPTY; + + // Skip leading space. + //token += strspn(token, " \t"); + skip_space(&token); //(*token) += strspn((*token), " \t"); + + assert(token); + if (token[0] == '\0') { // empty line + return false; + } + + if (token[0] == '#') { // comment line + return false; + } + + // vertex + if (token[0] == 'v' && IS_SPACE((token[1]))) { + token += 2; + float x, y, z; + parseFloat3(&x, &y, &z, &token); + command->vx = x; + command->vy = y; + command->vz = z; + command->type = COMMAND_V; + return true; + } + + // normal + if (token[0] == 'v' && token[1] == 'n' && IS_SPACE((token[2]))) { + token += 3; + float x, y, z; + parseFloat3(&x, &y, &z, &token); + command->nx = x; + command->ny = y; + command->nz = z; + command->type = COMMAND_VN; + return true; + } + + // texcoord + if (token[0] == 'v' && token[1] == 't' && IS_SPACE((token[2]))) { + token += 3; + float x, y; + parseFloat2(&x, &y, &token); + command->tx = x; + command->ty = y; + command->type = COMMAND_VT; + return true; + } + + // face + if (token[0] == 'f' && IS_SPACE((token[1]))) { + token += 2; + //token += strspn(token, " \t"); + skip_space(&token); + + while (!IS_NEW_LINE(token[0])) { + vertex_index vi = parseRawTriple(&token); + //printf("v = %d, %d, %d\n", vi.v_idx, vi.vn_idx, vi.vt_idx); + //if (callback.index_cb) { + // callback.index_cb(user_data, vi.v_idx, vi.vn_idx, vi.vt_idx); + //} + //size_t n = strspn(token, " \t\r"); + //token += n; + skip_space_and_cr(&token); + + command->f.push_back(vi); + } + + command->type = COMMAND_F; + + return true; + } + + // use mtl + if ((0 == strncmp(token, "usemtl", 6)) && IS_SPACE((token[6]))) { + char namebuf[8192]; + token += 7; +#ifdef _MSC_VER + sscanf_s(token, "%s", namebuf, (unsigned)_countof(namebuf)); +#else + sscanf(token, "%s", namebuf); +#endif + + //int newMaterialId = -1; + //if (material_map.find(namebuf) != material_map.end()) { + // newMaterialId = material_map[namebuf]; + //} else { + // // { error!! material not found } + //} + + //if (newMaterialId != materialId) { + // materialId = newMaterialId; + //} + + //command->material_name = .insert(command->material_name->end(), namebuf, namebuf + strlen(namebuf)); + //command->material_name->push_back('\0'); + command->type = COMMAND_USEMTL; + + return true; + } + +#if 0 + // load mtl + if ((0 == strncmp(token, "mtllib", 6)) && IS_SPACE((token[6]))) { + char namebuf[TINYOBJ_SSCANF_BUFFER_SIZE]; + token += 7; +#ifdef _MSC_VER + sscanf_s(token, "%s", namebuf, (unsigned)_countof(namebuf)); +#else + sscanf(token, "%s", namebuf); +#endif + + std::string err_mtl; + std::vector materials; + bool ok = (*readMatFn)(namebuf, &materials, &material_map, &err_mtl); + if (err) { + (*err) += err_mtl; + } + + if (!ok) { + return false; + } + + if (callback.mtllib_cb) { + callback.mtllib_cb(user_data, &materials.at(0), + static_cast(materials.size())); + } + + continue; + } +#endif + + // group name + if (token[0] == 'g' && IS_SPACE((token[1]))) { + ShortString names[16]; + int num_names = 0; + + while (!IS_NEW_LINE(token[0])) { + bool ret = parseString(&(names[num_names]), &token); + assert(ret); + token += strspn(token, " \t\r"); // skip tag + num_names++; + } + + assert(num_names > 0); + + int name_idx = 0; + // names[0] must be 'g', so skip the 0th element. + if (num_names > 1) { + name_idx = 1; + } + + //command->group_name->assign(names[name_idx]); + command->type = COMMAND_G; + + //if (callback.group_cb) { + // if (names.size() > 1) { + // // create const char* array. + // std::vector tmp(names.size() - 1); + // for (size_t j = 0; j < tmp.size(); j++) { + // tmp[j] = names[j + 1].c_str(); + // } + // callback.group_cb(user_data, &tmp.at(0), + // static_cast(tmp.size())); + + // } else { + // callback.group_cb(user_data, NULL, 0); + // } + + //} + return true; + } + + // object name + if (token[0] == 'o' && IS_SPACE((token[1]))) { + // @todo { multiple object name? } + char namebuf[8192]; + token += 2; +#ifdef _MSC_VER + sscanf_s(token, "%s", namebuf, (unsigned)_countof(namebuf)); +#else + sscanf(token, "%s", namebuf); +#endif + + //if (callback.object_cb) { + // callback.object_cb(user_data, name.c_str()); + //} + + //command->object_name->insert(command->object_name->end(), namebuf, namebuf + strlen(namebuf)); + //command->object_name->push_back('\0'); + command->type = COMMAND_O; + + return true; + } + + + return false; +} + +typedef struct +{ + size_t pos; + size_t len; +} LineInfo; + +// Idea come from https://github.com/antonmks/nvParse +// 1. mmap file +// 2. find newline(\n) and list of line data. +// 3. Do parallel parsing for each line. +// 4. Reconstruct final mesh data structure. + +void parse(const char* buf, size_t len) +{ + std::vector newline_marker(len, 0); + + auto num_threads = std::thread::hardware_concurrency(); + std::cout << "# of threads = " << num_threads << std::endl; + + auto t1 = std::chrono::high_resolution_clock::now(); + + std::atomic newline_counter(0); + std::vector workers; + + std::vector line_infos; + line_infos.reserve(len / 1024); // len / 1024 = heuristics + + // 1. Find '\n' and create line data. + // @todo { parallelize lineinfo construction ? } + size_t prev_pos = 0; + for (size_t i = 0; i < len - 1; i++) { + if (buf[i] == '\n') { + LineInfo info; + info.pos = prev_pos; + info.len = i - prev_pos; + + if (info.len > 0) { + line_infos.push_back(info); + } + + prev_pos = i+1; + } + } + + { + auto t = std::chrono::high_resolution_clock::now(); + std::chrono::duration ms = t - t1; + std::cout << "line split: " << ms.count() << " ms\n"; + } + + std::vector commands(line_infos.size()); + + auto t2 = std::chrono::high_resolution_clock::now(); + std::chrono::duration ms1 = t2 - t1; + + std::cout << ms1.count() << " ms\n"; + + // 2. parse each line in parallel. + auto chunk = line_infos.size() / num_threads; + + { + auto t_start = std::chrono::high_resolution_clock::now(); + + for (auto t = 0; t < num_threads; t++) { + workers.push_back(std::thread([&, t]() { + auto start_idx = (t + 0) * chunk; + auto end_idx = std::min((t + 1) * chunk, line_infos.size()); + + for (auto i = start_idx; i < end_idx; i++) { + bool ret = parseLine(&commands[i], &buf[line_infos[i].pos], line_infos[i].len); + if (ret) { + } + } + + })); + } + + for (auto &t : workers) { + t.join(); + } + + auto t_end = std::chrono::high_resolution_clock::now(); + + std::chrono::duration ms = t_end - t_start; + std::cout << "parse:" << ms.count() << " ms\n"; + } + + auto t3 = std::chrono::high_resolution_clock::now(); + std::chrono::duration ms2 = t3 - t1; + + std::cout << ms2.count() << " ms\n"; + + std::vector vertices; + std::vector normals; + std::vector texcoords; + std::vector faces; + + { + auto t_start = std::chrono::high_resolution_clock::now(); + + for (size_t i = 0; i < commands.size(); i++) { + if (commands[i].type == COMMAND_EMPTY) { + continue; + } else if (commands[i].type == COMMAND_V) { + vertices.push_back(commands[i].vx); + vertices.push_back(commands[i].vy); + vertices.push_back(commands[i].vz); + } else if (commands[i].type == COMMAND_VN) { + normals.push_back(commands[i].nx); + normals.push_back(commands[i].ny); + normals.push_back(commands[i].nz); + } else if (commands[i].type == COMMAND_VT) { + texcoords.push_back(commands[i].tx); + texcoords.push_back(commands[i].ty); + } else if (commands[i].type == COMMAND_F) { + int v_size = vertices.size() / 3; + int vn_size = normals.size() / 3; + int vt_size = texcoords.size() / 2; + for (size_t k = 0; k < commands[i].f.size(); k++) { + int v_idx = fixIndex(commands[i].f[k].v_idx, v_size); + int vn_idx = fixIndex(commands[i].f[k].vn_idx, v_size); + int vt_idx = fixIndex(commands[i].f[k].vt_idx, v_size); + faces.push_back(vertex_index(v_idx, vn_idx, vt_idx)); + } + } + } + + auto t_end = std::chrono::high_resolution_clock::now(); + std::chrono::duration ms = t_end - t_start; + std::cout << "merge:" << ms.count() << " ms\n"; + } + + auto t4 = std::chrono::high_resolution_clock::now(); + + std::chrono::duration ms_total = t4 - t1; + std::cout << "total: " << ms_total.count() << " ms\n"; + + std::cout << "# of newlines = " << newline_counter << std::endl; + std::cout << "# of lines = " << line_infos.size() << std::endl; + + std::cout << "# of vertices = " << vertices.size() << std::endl; + std::cout << "# of normals = " << normals.size() << std::endl; + std::cout << "# of texcoords = " << texcoords.size() << std::endl; + std::cout << "# of faces = " << faces.size() << std::endl; +} + +int +main(int argc, char **argv) +{ + + if (argc < 2) { + printf("Needs .obj\n"); + return EXIT_FAILURE; + } + +#ifdef _WIN64 + HANDLE file = CreateFileA("lineitem.tbl", GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, NULL); + assert(file != INVALID_HANDLE_VALUE); + + HANDLE fileMapping = CreateFileMapping(file, NULL, PAGE_READONLY, 0, 0, NULL); + assert(fileMapping != INVALID_HANDLE_VALUE); + + LPVOID fileMapView = MapViewOfFile(fileMapping, FILE_MAP_READ, 0, 0, 0); + auto fileMapViewChar = (const char*)fileMapView; + assert(fileMapView != NULL); +#else + + FILE* f = fopen(argv[1], "r" ); + fseek(f, 0, SEEK_END); + long fileSize = ftell(f); + fclose(f); + + struct stat sb; + char *p; + int fd; + + fd = open (argv[1], O_RDONLY); + if (fd == -1) { + perror ("open"); + return 1; + } + + if (fstat (fd, &sb) == -1) { + perror ("fstat"); + return 1; + } + + if (!S_ISREG (sb.st_mode)) { + fprintf (stderr, "%s is not a file\n", "lineitem.tbl"); + return 1; + } + + p = (char*)mmap (0, fileSize, PROT_READ, MAP_SHARED, fd, 0); + + if (p == MAP_FAILED) { + perror ("mmap"); + return 1; + } + + if (close (fd) == -1) { + perror ("close"); + return 1; + } + + printf("fsize: %lu\n", fileSize); +#endif + + parse(p, fileSize); + + return EXIT_SUCCESS; +} diff --git a/sandbox/Makefile b/sandbox/Makefile deleted file mode 100644 index f72d56e..0000000 --- a/sandbox/Makefile +++ /dev/null @@ -1,2 +0,0 @@ -all: - g++ -O2 -g -pthread -std=c++11 optimized-parse.cc diff --git a/sandbox/optimized-parse.cc b/sandbox/optimized-parse.cc deleted file mode 100644 index ed7c758..0000000 --- a/sandbox/optimized-parse.cc +++ /dev/null @@ -1,921 +0,0 @@ -#ifdef _WIN64 -#define atoll(S) _atoi64(S) -#include -#else -#include -#include -#include -#include -#include -#include -#endif - -#include -#include -#include -#include -#include -#include -#include - -#include // C++11 -#include // C++11 -#include // C++11 - -// ---------------------------------------------------------------------------- -// Small vector class useful for multi-threaded environment. -// -// stack_container.h -// -// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -// This allocator can be used with STL containers to provide a stack buffer -// from which to allocate memory and overflows onto the heap. This stack buffer -// would be allocated on the stack and allows us to avoid heap operations in -// some situations. -// -// STL likes to make copies of allocators, so the allocator itself can't hold -// the data. Instead, we make the creator responsible for creating a -// StackAllocator::Source which contains the data. Copying the allocator -// merely copies the pointer to this shared source, so all allocators created -// based on our allocator will share the same stack buffer. -// -// This stack buffer implementation is very simple. The first allocation that -// fits in the stack buffer will use the stack buffer. Any subsequent -// allocations will not use the stack buffer, even if there is unused room. -// This makes it appropriate for array-like containers, but the caller should -// be sure to reserve() in the container up to the stack buffer size. Otherwise -// the container will allocate a small array which will "use up" the stack -// buffer. -template -class StackAllocator : public std::allocator { - public: - typedef typename std::allocator::pointer pointer; - typedef typename std::allocator::size_type size_type; - - // Backing store for the allocator. The container owner is responsible for - // maintaining this for as long as any containers using this allocator are - // live. - struct Source { - Source() : used_stack_buffer_(false) {} - - // Casts the buffer in its right type. - T *stack_buffer() { return reinterpret_cast(stack_buffer_); } - const T *stack_buffer() const { - return reinterpret_cast(stack_buffer_); - } - - // - // IMPORTANT: Take care to ensure that stack_buffer_ is aligned - // since it is used to mimic an array of T. - // Be careful while declaring any unaligned types (like bool) - // before stack_buffer_. - // - - // The buffer itself. It is not of type T because we don't want the - // constructors and destructors to be automatically called. Define a POD - // buffer of the right size instead. - char stack_buffer_[sizeof(T[stack_capacity])]; - - // Set when the stack buffer is used for an allocation. We do not track - // how much of the buffer is used, only that somebody is using it. - bool used_stack_buffer_; - }; - - // Used by containers when they want to refer to an allocator of type U. - template - struct rebind { - typedef StackAllocator other; - }; - - // For the straight up copy c-tor, we can share storage. - StackAllocator(const StackAllocator &rhs) - : source_(rhs.source_) {} - - // ISO C++ requires the following constructor to be defined, - // and std::vector in VC++2008SP1 Release fails with an error - // in the class _Container_base_aux_alloc_real (from ) - // if the constructor does not exist. - // For this constructor, we cannot share storage; there's - // no guarantee that the Source buffer of Ts is large enough - // for Us. - // TODO(Google): If we were fancy pants, perhaps we could share storage - // iff sizeof(T) == sizeof(U). - template - StackAllocator(const StackAllocator &other) - : source_(NULL) { - (void)other; - } - - explicit StackAllocator(Source *source) : source_(source) {} - - // Actually do the allocation. Use the stack buffer if nobody has used it yet - // and the size requested fits. Otherwise, fall through to the standard - // allocator. - pointer allocate(size_type n, void *hint = 0) { - if (source_ != NULL && !source_->used_stack_buffer_ && - n <= stack_capacity) { - source_->used_stack_buffer_ = true; - return source_->stack_buffer(); - } else { - return std::allocator::allocate(n, hint); - } - } - - // Free: when trying to free the stack buffer, just mark it as free. For - // non-stack-buffer pointers, just fall though to the standard allocator. - void deallocate(pointer p, size_type n) { - if (source_ != NULL && p == source_->stack_buffer()) - source_->used_stack_buffer_ = false; - else - std::allocator::deallocate(p, n); - } - - private: - Source *source_; -}; - -// A wrapper around STL containers that maintains a stack-sized buffer that the -// initial capacity of the vector is based on. Growing the container beyond the -// stack capacity will transparently overflow onto the heap. The container must -// support reserve(). -// -// WATCH OUT: the ContainerType MUST use the proper StackAllocator for this -// type. This object is really intended to be used only internally. You'll want -// to use the wrappers below for different types. -template -class StackContainer { - public: - typedef TContainerType ContainerType; - typedef typename ContainerType::value_type ContainedType; - typedef StackAllocator Allocator; - - // Allocator must be constructed before the container! - StackContainer() : allocator_(&stack_data_), container_(allocator_) { - // Make the container use the stack allocation by reserving our buffer size - // before doing anything else. - container_.reserve(stack_capacity); - } - - // Getters for the actual container. - // - // Danger: any copies of this made using the copy constructor must have - // shorter lifetimes than the source. The copy will share the same allocator - // and therefore the same stack buffer as the original. Use std::copy to - // copy into a "real" container for longer-lived objects. - ContainerType &container() { return container_; } - const ContainerType &container() const { return container_; } - - // Support operator-> to get to the container. This allows nicer syntax like: - // StackContainer<...> foo; - // std::sort(foo->begin(), foo->end()); - ContainerType *operator->() { return &container_; } - const ContainerType *operator->() const { return &container_; } - -#ifdef UNIT_TEST - // Retrieves the stack source so that that unit tests can verify that the - // buffer is being used properly. - const typename Allocator::Source &stack_data() const { return stack_data_; } -#endif - - protected: - typename Allocator::Source stack_data_; - unsigned char pad_[7]; - Allocator allocator_; - ContainerType container_; - - // DISALLOW_EVIL_CONSTRUCTORS(StackContainer); - StackContainer(const StackContainer &); - void operator=(const StackContainer &); -}; - -// StackVector -// -// Example: -// StackVector foo; -// foo->push_back(22); // we have overloaded operator-> -// foo[0] = 10; // as well as operator[] -template -class StackVector - : public StackContainer >, - stack_capacity> { - public: - StackVector() - : StackContainer >, - stack_capacity>() {} - - // We need to put this in STL containers sometimes, which requires a copy - // constructor. We can't call the regular copy constructor because that will - // take the stack buffer from the original. Here, we create an empty object - // and make a stack buffer of its own. - StackVector(const StackVector &other) - : StackContainer >, - stack_capacity>() { - this->container().assign(other->begin(), other->end()); - } - - StackVector &operator=( - const StackVector &other) { - this->container().assign(other->begin(), other->end()); - return *this; - } - - // Vectors are commonly indexed, which isn't very convenient even with - // operator-> (using "->at()" does exception stuff we don't want). - T &operator[](size_t i) { return this->container().operator[](i); } - const T &operator[](size_t i) const { - return this->container().operator[](i); - } -}; - -// ---------------------------------------------------------------------------- - -typedef StackVector ShortString; - -#define IS_SPACE(x) (((x) == ' ') || ((x) == '\t')) -#define IS_DIGIT(x) \ - (static_cast((x) - '0') < static_cast(10)) -#define IS_NEW_LINE(x) (((x) == '\r') || ((x) == '\n') || ((x) == '\0')) - -static inline void skip_space(const char **token) -{ - while ((*token)[0] == ' ' || (*token)[0] == '\t') { - (*token)++; - } -} - -// http://stackoverflow.com/questions/5710091/how-does-atoi-function-in-c-work -int my_atoi( const char *c ) { - int value = 0; - int sign = 1; - if( *c == '+' || *c == '-' ) { - if( *c == '-' ) sign = -1; - c++; - } - while ( isdigit( *c ) ) { - value *= 10; - value += (int) (*c-'0'); - c++; - } - return value * sign; -} - -struct vertex_index { - int v_idx, vt_idx, vn_idx; - vertex_index() : v_idx(-1), vt_idx(-1), vn_idx(-1) {} - explicit 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) {} -}; - -// 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 -} - -// Parse triples with index offsets: 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); - - vi.v_idx = fixIndex(my_atoi((*token)), vsize); - - //(*token) += strcspn((*token), "/ \t\r"); - while ((*token)[0] != '\0' && (*token)[0] != '/' && (*token)[0] != ' ' && (*token)[0] != '\t' && (*token)[0] != '\r') { - (*token)++; - } - if ((*token)[0] != '/') { - return vi; - } - (*token)++; - - // i//k - if ((*token)[0] == '/') { - (*token)++; - vi.vn_idx = fixIndex(my_atoi((*token)), vnsize); - //(*token) += strcspn((*token), "/ \t\r"); - while ((*token)[0] != '\0' && (*token)[0] != '/' && (*token)[0] != ' ' && (*token)[0] != '\t' && (*token)[0] != '\r') { - (*token)++; - } - return vi; - } - - // i/j/k or i/j - vi.vt_idx = fixIndex(my_atoi((*token)), vtsize); - //(*token) += strcspn((*token), "/ \t\r"); - while ((*token)[0] != '\0' && (*token)[0] != '/' && (*token)[0] != ' ' && (*token)[0] != '\t' && (*token)[0] != '\r') { - (*token)++; - } - if ((*token)[0] != '/') { - return vi; - } - - // i/j/k - (*token)++; // skip '/' - vi.vn_idx = fixIndex(my_atoi((*token)), vnsize); - //(*token) += strcspn((*token), "/ \t\r"); - while ((*token)[0] != '\0' && (*token)[0] != '/' && (*token)[0] != ' ' && (*token)[0] != '\t' && (*token)[0] != '\r') { - (*token)++; - } - return vi; -} - -// Parse raw triples: i, i/j/k, i//k, i/j -static vertex_index parseRawTriple(const char **token) { - vertex_index vi( - static_cast(0x80000000)); // 0x80000000 = -2147483648 = invalid - - vi.v_idx = my_atoi((*token)); - //(*token) += strcspn((*token), "/ \t\r"); - while ((*token)[0] != '\0' && (*token)[0] != '/' && (*token)[0] != ' ' && (*token)[0] != '\t' && (*token)[0] != '\r') { - (*token)++; - } - if ((*token)[0] != '/') { - return vi; - } - (*token)++; - - // i//k - if ((*token)[0] == '/') { - (*token)++; - vi.vn_idx = my_atoi((*token)); - //(*token) += strcspn((*token), "/ \t\r"); - while ((*token)[0] != '\0' && (*token)[0] != '/' && (*token)[0] != ' ' && (*token)[0] != '\t' && (*token)[0] != '\r') { - (*token)++; - } - return vi; - } - - // i/j/k or i/j - vi.vt_idx = my_atoi((*token)); - //(*token) += strcspn((*token), "/ \t\r"); - while ((*token)[0] != '\0' && (*token)[0] != '/' && (*token)[0] != ' ' && (*token)[0] != '\t' && (*token)[0] != '\r') { - (*token)++; - } - if ((*token)[0] != '/') { - return vi; - } - - // i/j/k - (*token)++; // skip '/' - vi.vn_idx = my_atoi((*token)); - //(*token) += strcspn((*token), "/ \t\r"); - while ((*token)[0] != '\0' && (*token)[0] != '/' && (*token)[0] != ' ' && (*token)[0] != '\t' && (*token)[0] != '\r') { - (*token)++; - } - return vi; -} - -static inline bool parseString(ShortString *s, const char **token) { - skip_space(token); //(*token) += strspn((*token), " \t"); - size_t e = strcspn((*token), " \t\r"); - (*s)->insert((*s)->end(), (*token), (*token) + e); - (*token) += e; - return true; -} - -static inline int parseInt(const char **token) { - skip_space(token); //(*token) += strspn((*token), " \t"); - int i = my_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 -// stop. For example at the end of the string, to prevent buffer overflows. -// -// Parses the following EBNF grammar: -// sign = "+" | "-" ; -// END = ? anything not in digit ? -// digit = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" ; -// integer = [sign] , digit , {digit} ; -// decimal = integer , ["." , integer] ; -// float = ( decimal , END ) | ( decimal , ("E" | "e") , integer , END ) ; -// -// 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 -// is returned. -// -// The function is greedy and will parse until any of the following happens: -// - a non-conforming character is encountered. -// - s_end is reached. -// -// 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) { - 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 - // 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 - // exponent to be in base 2. - int exponent = 0; - - // NOTE: THESE MUST BE DECLARED HERE SINCE WE ARE NOT ALLOWED - // TO JUMP OVER DEFINITIONS. - char sign = '+'; - char exp_sign = '+'; - char const *curr = s; - - // 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; - - /* - BEGIN PARSING. - */ - - // Find out what sign we've got. - if (*curr == '+' || *curr == '-') { - sign = *curr; - curr++; - } else if (IS_DIGIT(*curr)) { /* Pass through. */ - } else { - goto fail; - } - - // Read the integer part. - end_not_reached = (curr != s_end); - while (end_not_reached && IS_DIGIT(*curr)) { - mantissa *= 10; - mantissa += static_cast(*curr - 0x30); - curr++; - read++; - end_not_reached = (curr != s_end); - } - - // We must make sure we actually got something. - if (read == 0) goto fail; - // We allow numbers of form "#", "###" etc. - if (!end_not_reached) goto assemble; - - // Read the decimal part. - if (*curr == '.') { - curr++; - read = 1; - end_not_reached = (curr != s_end); - while (end_not_reached && IS_DIGIT(*curr)) { - // NOTE: Don't use powf here, it will absolutely murder precision. - mantissa += static_cast(*curr - 0x30) * pow(10.0, -read); - read++; - curr++; - end_not_reached = (curr != s_end); - } - } else if (*curr == 'e' || *curr == 'E') { - } else { - goto assemble; - } - - if (!end_not_reached) goto assemble; - - // Read the exponent part. - if (*curr == 'e' || *curr == 'E') { - curr++; - // Figure out if a sign is present and if it is. - end_not_reached = (curr != s_end); - if (end_not_reached && (*curr == '+' || *curr == '-')) { - exp_sign = *curr; - curr++; - } else if (IS_DIGIT(*curr)) { /* Pass through. */ - } else { - // Empty E is not allowed. - goto fail; - } - - read = 0; - end_not_reached = (curr != s_end); - while (end_not_reached && IS_DIGIT(*curr)) { - exponent *= 10; - exponent += static_cast(*curr - 0x30); - curr++; - read++; - end_not_reached = (curr != s_end); - } - exponent *= (exp_sign == '+' ? 1 : -1); - if (read == 0) goto fail; - } - -assemble: - *result = - (sign == '+' ? 1 : -1) * ldexp(mantissa * pow(5.0, exponent), exponent); - return true; -fail: - return false; -} - -static inline float parseFloat(const char **token) { - skip_space(token); //(*token) += strspn((*token), " \t"); -#ifdef TINY_OBJ_LOADER_OLD_FLOAT_PARSER - float f = static_cast(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; -#endif - return f; -} - -static inline void parseFloat2(float *x, float *y, const char **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); -} - -typedef enum -{ - COMMAND_EMPTY, - COMMAND_V, - COMMAND_VN, - COMMAND_VT, - COMMAND_F, - COMMAND_G, - COMMAND_O, - COMMAND_USEMTL, - -} CommandType; - -typedef struct -{ - float vx, vy, vz; - float nx, ny, nz; - float tx, ty; - - StackVector f; - ShortString group_name; - ShortString object_name; - ShortString material_name; - - CommandType type; -} Command; - -bool test(Command *command, const char *p, size_t p_len) -{ - StackVector linebuf; - linebuf->resize(p_len + 1); - memcpy(&linebuf->at(0), p, p_len); - linebuf[p_len] = '\0'; - - const char *token = &linebuf->at(0); - - // Skip leading space. - //token += strspn(token, " \t"); - skip_space(&token); //(*token) += strspn((*token), " \t"); - - assert(token); - if (token[0] == '\0') { // empty line - return false; - } - - if (token[0] == '#') { // comment line - return false; - } - - // vertex - if (token[0] == 'v' && IS_SPACE((token[1]))) { - token += 2; - float x, y, z; - parseFloat3(&x, &y, &z, &token); - command->vx = x; - command->vy = y; - command->vz = z; - command->type = COMMAND_V; - return true; - } - - // normal - if (token[0] == 'v' && token[1] == 'n' && IS_SPACE((token[2]))) { - token += 3; - float x, y, z; - parseFloat3(&x, &y, &z, &token); - command->nx = x; - command->ny = y; - command->nz = z; - command->type = COMMAND_VN; - return true; - } - - // texcoord - if (token[0] == 'v' && token[1] == 't' && IS_SPACE((token[2]))) { - token += 3; - float x, y; - parseFloat2(&x, &y, &token); - command->tx = x; - command->ty = y; - command->type = COMMAND_VT; - return true; - } - - // face - if (token[0] == 'f' && IS_SPACE((token[1]))) { - token += 2; - token += strspn(token, " \t"); - - while (!IS_NEW_LINE(token[0])) { - vertex_index vi = parseRawTriple(&token); - //if (callback.index_cb) { - // callback.index_cb(user_data, vi.v_idx, vi.vn_idx, vi.vt_idx); - //} - size_t n = strspn(token, " \t\r"); - token += n; - - command->f->push_back(vi); - } - - command->type = COMMAND_F; - - return true; - } - - // use mtl - if ((0 == strncmp(token, "usemtl", 6)) && IS_SPACE((token[6]))) { - char namebuf[8192]; - token += 7; -#ifdef _MSC_VER - sscanf_s(token, "%s", namebuf, (unsigned)_countof(namebuf)); -#else - sscanf(token, "%s", namebuf); -#endif - - //int newMaterialId = -1; - //if (material_map.find(namebuf) != material_map.end()) { - // newMaterialId = material_map[namebuf]; - //} else { - // // { error!! material not found } - //} - - //if (newMaterialId != materialId) { - // materialId = newMaterialId; - //} - - command->material_name->insert(command->material_name->end(), namebuf, namebuf + strlen(namebuf)); - command->material_name->push_back('\0'); - command->type = COMMAND_USEMTL; - - return true; - } - -#if 0 - // load mtl - if ((0 == strncmp(token, "mtllib", 6)) && IS_SPACE((token[6]))) { - char namebuf[TINYOBJ_SSCANF_BUFFER_SIZE]; - token += 7; -#ifdef _MSC_VER - sscanf_s(token, "%s", namebuf, (unsigned)_countof(namebuf)); -#else - sscanf(token, "%s", namebuf); -#endif - - std::string err_mtl; - std::vector materials; - bool ok = (*readMatFn)(namebuf, &materials, &material_map, &err_mtl); - if (err) { - (*err) += err_mtl; - } - - if (!ok) { - return false; - } - - if (callback.mtllib_cb) { - callback.mtllib_cb(user_data, &materials.at(0), - static_cast(materials.size())); - } - - continue; - } -#endif - - // group name - if (token[0] == 'g' && IS_SPACE((token[1]))) { - ShortString names[16]; - int num_names = 0; - - while (!IS_NEW_LINE(token[0])) { - bool ret = parseString(&(names[num_names]), &token); - assert(ret); - token += strspn(token, " \t\r"); // skip tag - num_names++; - } - - assert(num_names > 0); - - int name_idx = 0; - // names[0] must be 'g', so skip the 0th element. - if (num_names > 1) { - name_idx = 1; - } - - //command->group_name->assign(names[name_idx]); - command->type = COMMAND_G; - - //if (callback.group_cb) { - // if (names.size() > 1) { - // // create const char* array. - // std::vector tmp(names.size() - 1); - // for (size_t j = 0; j < tmp.size(); j++) { - // tmp[j] = names[j + 1].c_str(); - // } - // callback.group_cb(user_data, &tmp.at(0), - // static_cast(tmp.size())); - - // } else { - // callback.group_cb(user_data, NULL, 0); - // } - - //} - return true; - } - - // object name - if (token[0] == 'o' && IS_SPACE((token[1]))) { - // @todo { multiple object name? } - char namebuf[8192]; - token += 2; -#ifdef _MSC_VER - sscanf_s(token, "%s", namebuf, (unsigned)_countof(namebuf)); -#else - sscanf(token, "%s", namebuf); -#endif - - //if (callback.object_cb) { - // callback.object_cb(user_data, name.c_str()); - //} - - command->object_name->insert(command->object_name->end(), namebuf, namebuf + strlen(namebuf)); - command->object_name->push_back('\0'); - command->type = COMMAND_O; - - return true; - } - - - return false; -} - -typedef struct -{ - size_t pos; - size_t len; -} LineInfo; - -// Idea come from https://github.com/antonmks/nvParse -// 1. mmap file -// 2. find newline(\n) and list of line data. -// 3. Do parallel parsing for each line. -// 4. Reconstruct final mesh data structure. - -void parse(const char* buf, size_t len) -{ - std::vector newline_marker(len, 0); - - auto num_threads = std::thread::hardware_concurrency(); - std::cout << "# of threads = " << num_threads << std::endl; - - auto t1 = std::chrono::high_resolution_clock::now(); - - std::atomic newline_counter(0); - std::vector workers; - - std::vector line_infos; - line_infos.reserve(len / 1024); // len / 1024 = heuristics - - // @todo { parallelize lineinfo construction ? } - size_t prev_pos = 0; - for (size_t i = 0; i < len - 1; i++) { - if (buf[i] == '\n') { - LineInfo info; - info.pos = prev_pos; - info.len = i - prev_pos; - - //printf("p = %d, len = %d\n", prev_pos, info.len); - - if (info.len > 0) { - line_infos.push_back(info); - } - - prev_pos = i+1; - } - } - - auto chunk = line_infos.size() / num_threads; - - for (auto t = 0; t < num_threads; t++) { - workers.push_back(std::thread([&, t]() { - auto start_idx = (t + 0) * chunk; - auto end_idx = std::min((t + 1) * chunk, line_infos.size()); - - for (auto i = start_idx; i < end_idx; i++) { - Command cmd; - bool ret = test(&cmd, &buf[line_infos[i].pos], line_infos[i].len); - if (ret) { - } - } - - })); - } - - for (auto &t : workers) { - t.join(); - } - - auto t2 = std::chrono::high_resolution_clock::now(); - - std::chrono::duration ms = t2 - t1; - - std::cout << ms.count() << " ms\n"; - - std::cout << "# of newlines = " << newline_counter << std::endl; - std::cout << "# of lines = " << line_infos.size() << std::endl; -} - -int -main(int argc, char **argv) -{ - - if (argc < 2) { - printf("Needs .obj\n"); - return EXIT_FAILURE; - } - -#ifdef _WIN64 - HANDLE file = CreateFileA("lineitem.tbl", GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, NULL); - assert(file != INVALID_HANDLE_VALUE); - - HANDLE fileMapping = CreateFileMapping(file, NULL, PAGE_READONLY, 0, 0, NULL); - assert(fileMapping != INVALID_HANDLE_VALUE); - - LPVOID fileMapView = MapViewOfFile(fileMapping, FILE_MAP_READ, 0, 0, 0); - auto fileMapViewChar = (const char*)fileMapView; - assert(fileMapView != NULL); -#else - - FILE* f = fopen(argv[1], "r" ); - fseek(f, 0, SEEK_END); - long fileSize = ftell(f); - fclose(f); - - struct stat sb; - char *p; - int fd; - - fd = open (argv[1], O_RDONLY); - if (fd == -1) { - perror ("open"); - return 1; - } - - if (fstat (fd, &sb) == -1) { - perror ("fstat"); - return 1; - } - - if (!S_ISREG (sb.st_mode)) { - fprintf (stderr, "%s is not a file\n", "lineitem.tbl"); - return 1; - } - - p = (char*)mmap (0, fileSize, PROT_READ, MAP_SHARED, fd, 0); - - if (p == MAP_FAILED) { - perror ("mmap"); - return 1; - } - - if (close (fd) == -1) { - perror ("close"); - return 1; - } - - printf("fsize: %lu\n", fileSize); -#endif - - parse(p, fileSize); - - return EXIT_SUCCESS; -} -- cgit v1.2.3 From bf626b5809ef00c98fb9e916fe8f982061262c5e Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Sat, 14 May 2016 16:53:55 +0900 Subject: Mostly finished parallelized parsing of .obj. Still work in progress. --- experimental/optimized-parse.cc | 186 +++++++++++++++++++++++++++------------- 1 file changed, 125 insertions(+), 61 deletions(-) diff --git a/experimental/optimized-parse.cc b/experimental/optimized-parse.cc index 90f268d..372ccd8 100644 --- a/experimental/optimized-parse.cc +++ b/experimental/optimized-parse.cc @@ -584,7 +584,9 @@ typedef struct float nx, ny, nz; float tx, ty; + // for f std::vector f; + const char* group_name; const char* object_name; const char* material_name; @@ -657,6 +659,7 @@ bool parseLine(Command *command, const char *p, size_t p_len) //token += strspn(token, " \t"); skip_space(&token); + int num_verts = 0; while (!IS_NEW_LINE(token[0])) { vertex_index vi = parseRawTriple(&token); //printf("v = %d, %d, %d\n", vi.v_idx, vi.vn_idx, vi.vt_idx); @@ -813,45 +816,104 @@ typedef struct // 3. Do parallel parsing for each line. // 4. Reconstruct final mesh data structure. +#define kMaxThreads (32) + void parse(const char* buf, size_t len) { + std::vector newline_marker(len, 0); - auto num_threads = std::thread::hardware_concurrency(); + auto num_threads = std::max(1, std::min(static_cast(std::thread::hardware_concurrency()), kMaxThreads)); std::cout << "# of threads = " << num_threads << std::endl; auto t1 = std::chrono::high_resolution_clock::now(); std::atomic newline_counter(0); - std::vector workers; - std::vector line_infos; - line_infos.reserve(len / 1024); // len / 1024 = heuristics + std::vector line_infos[kMaxThreads]; + for (auto t = 0; t < num_threads; t++) { + // Pre allocate enough memory. len / 1024 / num_threads is just a heuristic value. + line_infos[t].reserve(len / 1024 / num_threads); + } // 1. Find '\n' and create line data. - // @todo { parallelize lineinfo construction ? } - size_t prev_pos = 0; - for (size_t i = 0; i < len - 1; i++) { - if (buf[i] == '\n') { - LineInfo info; - info.pos = prev_pos; - info.len = i - prev_pos; - - if (info.len > 0) { - line_infos.push_back(info); - } + { + std::vector workers; + + auto start_time = std::chrono::high_resolution_clock::now(); + auto chunk_size = len / num_threads; + + for (auto t = 0; t < num_threads; t++) { + workers.push_back(std::thread([&, t]() { + auto start_idx = (t + 0) * chunk_size; + auto end_idx = std::min((t + 1) * chunk_size, len - 1); + if (t == (num_threads - 1)) { + end_idx = len - 1; + } - prev_pos = i+1; + size_t prev_pos = start_idx; + for (size_t i = start_idx; i < end_idx; i++) { + if (buf[i] == '\n') { + if ((t > 0) && (prev_pos == start_idx) && (buf[start_idx-1] != '\n')) { + // first linebreak found in (chunk > 0), and a line before this linebreak belongs to previous chunk, so skip it. + prev_pos = i + 1; + continue; + } else { + LineInfo info; + info.pos = prev_pos; + info.len = i - prev_pos; + + if (info.len > 0) { + line_infos[t].push_back(info); + } + + prev_pos = i+1; + } + } + } + + // Find extra line which spand across chunk boundary. + if ((t < num_threads) && (buf[end_idx-1] != '\n')) { + auto extra_span_idx = std::min(end_idx-1+chunk_size, len - 1); + for (size_t i = end_idx; i < extra_span_idx; i++) { + if (buf[i] == '\n') { + LineInfo info; + info.pos = prev_pos; + info.len = i - prev_pos; + + if (info.len > 0) { + line_infos[t].push_back(info); + } + + break; + } + } + } + })); } + + for (auto &t : workers) { + t.join(); + } + + auto end_time = std::chrono::high_resolution_clock::now(); + + std::chrono::duration ms = end_time - start_time; + std::cout << "line detection:" << ms.count() << " ms\n"; } - { - auto t = std::chrono::high_resolution_clock::now(); - std::chrono::duration ms = t - t1; - std::cout << "line split: " << ms.count() << " ms\n"; + auto line_sum = 0; + for (auto t = 0; t < num_threads; t++) { + std::cout << t << ": # of lines = " << line_infos[t].size() << std::endl; + line_sum += line_infos[t].size(); } + std::cout << "# of lines = " << line_sum << std::endl; + + std::vector commands[kMaxThreads]; - std::vector commands(line_infos.size()); + for (size_t t = 0; t < num_threads; t++) { + commands[t].reserve(line_infos[t].size()); + } auto t2 = std::chrono::high_resolution_clock::now(); std::chrono::duration ms1 = t2 - t1; @@ -859,19 +921,18 @@ void parse(const char* buf, size_t len) std::cout << ms1.count() << " ms\n"; // 2. parse each line in parallel. - auto chunk = line_infos.size() / num_threads; - { + std::vector workers; auto t_start = std::chrono::high_resolution_clock::now(); for (auto t = 0; t < num_threads; t++) { workers.push_back(std::thread([&, t]() { - auto start_idx = (t + 0) * chunk; - auto end_idx = std::min((t + 1) * chunk, line_infos.size()); - for (auto i = start_idx; i < end_idx; i++) { - bool ret = parseLine(&commands[i], &buf[line_infos[i].pos], line_infos[i].len); + for (auto i = 0; i < line_infos[t].size(); i++) { + Command command; + bool ret = parseLine(&command, &buf[line_infos[t][i].pos], line_infos[t][i].len); if (ret) { + commands[t].push_back(command); } } @@ -888,42 +949,47 @@ void parse(const char* buf, size_t len) std::cout << "parse:" << ms.count() << " ms\n"; } - auto t3 = std::chrono::high_resolution_clock::now(); - std::chrono::duration ms2 = t3 - t1; - - std::cout << ms2.count() << " ms\n"; + auto command_sum = 0; + for (auto t = 0; t < num_threads; t++) { + //std::cout << t << ": # of commands = " << commands[t].size() << std::endl; + command_sum += commands[t].size(); + } + //std::cout << "# of commands = " << command_sum << std::endl; std::vector vertices; std::vector normals; std::vector texcoords; std::vector faces; + // merge { auto t_start = std::chrono::high_resolution_clock::now(); - for (size_t i = 0; i < commands.size(); i++) { - if (commands[i].type == COMMAND_EMPTY) { - continue; - } else if (commands[i].type == COMMAND_V) { - vertices.push_back(commands[i].vx); - vertices.push_back(commands[i].vy); - vertices.push_back(commands[i].vz); - } else if (commands[i].type == COMMAND_VN) { - normals.push_back(commands[i].nx); - normals.push_back(commands[i].ny); - normals.push_back(commands[i].nz); - } else if (commands[i].type == COMMAND_VT) { - texcoords.push_back(commands[i].tx); - texcoords.push_back(commands[i].ty); - } else if (commands[i].type == COMMAND_F) { - int v_size = vertices.size() / 3; - int vn_size = normals.size() / 3; - int vt_size = texcoords.size() / 2; - for (size_t k = 0; k < commands[i].f.size(); k++) { - int v_idx = fixIndex(commands[i].f[k].v_idx, v_size); - int vn_idx = fixIndex(commands[i].f[k].vn_idx, v_size); - int vt_idx = fixIndex(commands[i].f[k].vt_idx, v_size); - faces.push_back(vertex_index(v_idx, vn_idx, vt_idx)); + for (size_t t = 0; t < num_threads; t++) { + for (size_t i = 0; i < commands[t].size(); i++) { + if (commands[t][i].type == COMMAND_EMPTY) { + continue; + } else if (commands[t][i].type == COMMAND_V) { + vertices.push_back(commands[t][i].vx); + vertices.push_back(commands[t][i].vy); + vertices.push_back(commands[t][i].vz); + } else if (commands[t][i].type == COMMAND_VN) { + normals.push_back(commands[t][i].nx); + normals.push_back(commands[t][i].ny); + normals.push_back(commands[t][i].nz); + } else if (commands[t][i].type == COMMAND_VT) { + texcoords.push_back(commands[t][i].tx); + texcoords.push_back(commands[t][i].ty); + } else if (commands[t][i].type == COMMAND_F) { + int v_size = vertices.size() / 3; + int vn_size = normals.size() / 3; + int vt_size = texcoords.size() / 2; + for (size_t k = 0; k < commands[t][i].f.size(); k++) { + int v_idx = fixIndex(commands[t][i].f[k].v_idx, v_size); + int vn_idx = fixIndex(commands[t][i].f[k].vn_idx, v_size); + int vt_idx = fixIndex(commands[t][i].f[k].vt_idx, v_size); + faces.push_back(vertex_index(v_idx, vn_idx, vt_idx)); + } } } } @@ -933,18 +999,16 @@ void parse(const char* buf, size_t len) std::cout << "merge:" << ms.count() << " ms\n"; } + std::cout << "# of vertices = " << vertices.size() << std::endl; + std::cout << "# of normals = " << normals.size() << std::endl; + std::cout << "# of texcoords = " << texcoords.size() << std::endl; + std::cout << "# of faces = " << faces.size() << std::endl; + auto t4 = std::chrono::high_resolution_clock::now(); std::chrono::duration ms_total = t4 - t1; std::cout << "total: " << ms_total.count() << " ms\n"; - std::cout << "# of newlines = " << newline_counter << std::endl; - std::cout << "# of lines = " << line_infos.size() << std::endl; - - std::cout << "# of vertices = " << vertices.size() << std::endl; - std::cout << "# of normals = " << normals.size() << std::endl; - std::cout << "# of texcoords = " << texcoords.size() << std::endl; - std::cout << "# of faces = " << faces.size() << std::endl; } int -- cgit v1.2.3 From 654e686079a88b0dcde31db4adbd0a8d5fd67575 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Sat, 14 May 2016 17:25:36 +0900 Subject: Optimized a bit. --- experimental/optimized-parse.cc | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/experimental/optimized-parse.cc b/experimental/optimized-parse.cc index 372ccd8..bfbe9e7 100644 --- a/experimental/optimized-parse.cc +++ b/experimental/optimized-parse.cc @@ -271,7 +271,7 @@ static inline int my_atoi( const char *c ) { if( *c == '-' ) sign = -1; c++; } - while ( isdigit( *c ) ) { + while ( ((*c) >= '0') && ((*c) <= '9') ) { // isdigit(*c) value *= 10; value += (int) (*c-'0'); c++; @@ -596,12 +596,14 @@ typedef struct bool parseLine(Command *command, const char *p, size_t p_len) { - StackVector linebuf; - linebuf->resize(p_len + 1); - memcpy(&linebuf->at(0), p, p_len); + char linebuf[4096]; + assert(p_len < 4095); + //StackVector linebuf; + //linebuf->resize(p_len + 1); + memcpy(&linebuf, p, p_len); linebuf[p_len] = '\0'; - const char *token = &linebuf->at(0); + const char *token = linebuf; command->type = COMMAND_EMPTY; -- cgit v1.2.3 From 566d259df22e96f4f3a551994ffd142db86892a8 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Sun, 15 May 2016 16:08:16 +0900 Subject: Specify # of threads. --- experimental/optimized-parse.cc | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/experimental/optimized-parse.cc b/experimental/optimized-parse.cc index bfbe9e7..2f57754 100644 --- a/experimental/optimized-parse.cc +++ b/experimental/optimized-parse.cc @@ -491,7 +491,13 @@ static bool tryParseDouble(const char *s, const char *s_end, double *result) { end_not_reached = (curr != s_end); while (end_not_reached && IS_DIGIT(*curr)) { // NOTE: Don't use powf here, it will absolutely murder precision. - mantissa += static_cast(*curr - 0x30) * pow(10.0, -read); + + // pow(10.0, -read) + double frac_value = 10.0; + for (int f = 0; f < read; f++) { + frac_value *= 0.1; + } + mantissa += static_cast(*curr - 0x30) * frac_value; read++; curr++; end_not_reached = (curr != s_end); @@ -820,12 +826,13 @@ typedef struct #define kMaxThreads (32) -void parse(const char* buf, size_t len) +void parse(const char* buf, size_t len, int req_num_threads) { std::vector newline_marker(len, 0); - auto num_threads = std::max(1, std::min(static_cast(std::thread::hardware_concurrency()), kMaxThreads)); + auto num_threads = (req_num_threads < 0) ? std::thread::hardware_concurrency() : req_num_threads; + num_threads = std::max(1, std::min(static_cast(num_threads), kMaxThreads)); std::cout << "# of threads = " << num_threads << std::endl; auto t1 = std::chrono::high_resolution_clock::now(); @@ -1022,6 +1029,11 @@ main(int argc, char **argv) return EXIT_FAILURE; } + int req_num_threads = -1; + if (argc > 2) { + req_num_threads = atoi(argv[2]); + } + #ifdef _WIN64 HANDLE file = CreateFileA("lineitem.tbl", GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, NULL); assert(file != INVALID_HANDLE_VALUE); @@ -1074,7 +1086,7 @@ main(int argc, char **argv) printf("fsize: %lu\n", fileSize); #endif - parse(p, fileSize); + parse(p, fileSize, req_num_threads); return EXIT_SUCCESS; } -- cgit v1.2.3 From 7ecb0b2f373dea25605b84c89a244adce8128dd1 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Sun, 15 May 2016 16:19:57 +0900 Subject: 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) { -- cgit v1.2.3 From 88fe2421d90447b6e9e1f935cdd730550db6173f Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Sun, 15 May 2016 17:46:59 +0900 Subject: Add gl view for testing. --- experimental/optimized-parse.cc | 124 +++++++--- experimental/premake4.lua | 48 ++++ experimental/trackball.cc | 292 ++++++++++++++++++++++++ experimental/trackball.h | 75 +++++++ experimental/viewer.cc | 487 ++++++++++++++++++++++++++++++++++++++++ 5 files changed, 992 insertions(+), 34 deletions(-) create mode 100644 experimental/premake4.lua create mode 100644 experimental/trackball.cc create mode 100644 experimental/trackball.h create mode 100644 experimental/viewer.cc diff --git a/experimental/optimized-parse.cc b/experimental/optimized-parse.cc index 2f57754..c27ceae 100644 --- a/experimental/optimized-parse.cc +++ b/experimental/optimized-parse.cc @@ -294,6 +294,7 @@ static inline int fixIndex(int idx, int n) { return n + idx; // negative value = relative } +#if 0 // Parse triples with index offsets: i, i/j/k, i//k, i/j static vertex_index parseTriple(const char **token, int vsize, int vnsize, int vtsize) { @@ -340,6 +341,7 @@ static vertex_index parseTriple(const char **token, int vsize, int vnsize, } return vi; } +#endif // Parse raw triples: i, i/j/k, i//k, i/j static vertex_index parseRawTriple(const char **token) { @@ -600,7 +602,21 @@ typedef struct CommandType type; } Command; -bool parseLine(Command *command, const char *p, size_t p_len) +struct CommandCount +{ + size_t num_v; + size_t num_vn; + size_t num_vt; + size_t num_f; + CommandCount() { + num_v = 0; + num_vn = 0; + num_vt = 0; + num_f = 0; + } +}; + +static bool parseLine(Command *command, const char *p, size_t p_len) { char linebuf[4096]; assert(p_len < 4095); @@ -667,7 +683,6 @@ bool parseLine(Command *command, const char *p, size_t p_len) //token += strspn(token, " \t"); skip_space(&token); - int num_verts = 0; while (!IS_NEW_LINE(token[0])) { vertex_index vi = parseRawTriple(&token); //printf("v = %d, %d, %d\n", vi.v_idx, vi.vn_idx, vi.vt_idx); @@ -747,7 +762,8 @@ bool parseLine(Command *command, const char *p, size_t p_len) // group name if (token[0] == 'g' && IS_SPACE((token[1]))) { - ShortString names[16]; + std::vector names; + int num_names = 0; while (!IS_NEW_LINE(token[0])) { @@ -759,10 +775,10 @@ bool parseLine(Command *command, const char *p, size_t p_len) assert(num_names > 0); - int name_idx = 0; + //int name_idx = 0; // names[0] must be 'g', so skip the 0th element. if (num_names > 1) { - name_idx = 1; + //name_idx = 1; } //command->group_name->assign(names[name_idx]); @@ -820,13 +836,25 @@ typedef struct // Idea come from https://github.com/antonmks/nvParse // 1. mmap file -// 2. find newline(\n) and list of line data. +// 2. find newline(\n, \r\n, \r) and list of line data. // 3. Do parallel parsing for each line. // 4. Reconstruct final mesh data structure. #define kMaxThreads (32) -void parse(const char* buf, size_t len, int req_num_threads) +static inline bool is_line_ending(const char* p, size_t i, size_t end_i) +{ + if (p[i] == '\0') return true; + if (p[i] == '\n') return true; // this includes \r\n + if (p[i] == '\r') { + if (((i+1) < end_i) && (p[i+1] != '\n')) { // detect only \r case + return true; + } + } + return false; +} + +void parse(std::vector &vertices, std::vector &normals, std::vector &texcoords, std::vector &faces, const char* buf, size_t len, int req_num_threads) { std::vector newline_marker(len, 0); @@ -840,7 +868,7 @@ void parse(const char* buf, size_t len, int req_num_threads) std::atomic newline_counter(0); std::vector line_infos[kMaxThreads]; - for (auto t = 0; t < num_threads; t++) { + for (size_t t = 0; t < static_cast(num_threads); t++) { // Pre allocate enough memory. len / 1024 / num_threads is just a heuristic value. line_infos[t].reserve(len / 1024 / num_threads); } @@ -852,18 +880,18 @@ void parse(const char* buf, size_t len, int req_num_threads) auto start_time = std::chrono::high_resolution_clock::now(); auto chunk_size = len / num_threads; - for (auto t = 0; t < num_threads; t++) { + for (size_t t = 0; t < static_cast(num_threads); t++) { workers.push_back(std::thread([&, t]() { auto start_idx = (t + 0) * chunk_size; auto end_idx = std::min((t + 1) * chunk_size, len - 1); - if (t == (num_threads - 1)) { + if (t == static_cast((num_threads - 1))) { end_idx = len - 1; } size_t prev_pos = start_idx; for (size_t i = start_idx; i < end_idx; i++) { - if (buf[i] == '\n') { - if ((t > 0) && (prev_pos == start_idx) && (buf[start_idx-1] != '\n')) { + if (is_line_ending(buf, i, end_idx)) { + if ((t > 0) && (prev_pos == start_idx) && (!is_line_ending(buf, start_idx-1, end_idx))) { // first linebreak found in (chunk > 0), and a line before this linebreak belongs to previous chunk, so skip it. prev_pos = i + 1; continue; @@ -885,7 +913,7 @@ void parse(const char* buf, size_t len, int req_num_threads) if ((t < num_threads) && (buf[end_idx-1] != '\n')) { auto extra_span_idx = std::min(end_idx-1+chunk_size, len - 1); for (size_t i = end_idx; i < extra_span_idx; i++) { - if (buf[i] == '\n') { + if (is_line_ending(buf, i, extra_span_idx)) { LineInfo info; info.pos = prev_pos; info.len = i - prev_pos; @@ -912,7 +940,7 @@ void parse(const char* buf, size_t len, int req_num_threads) } auto line_sum = 0; - for (auto t = 0; t < num_threads; t++) { + for (size_t t = 0; t < num_threads; t++) { std::cout << t << ": # of lines = " << line_infos[t].size() << std::endl; line_sum += line_infos[t].size(); } @@ -929,19 +957,30 @@ void parse(const char* buf, size_t len, int req_num_threads) std::cout << ms1.count() << " ms\n"; + CommandCount command_count[kMaxThreads]; + // 2. parse each line in parallel. { std::vector workers; auto t_start = std::chrono::high_resolution_clock::now(); - for (auto t = 0; t < num_threads; t++) { + for (size_t t = 0; t < num_threads; t++) { workers.push_back(std::thread([&, t]() { - for (auto i = 0; i < line_infos[t].size(); i++) { + for (size_t i = 0; i < line_infos[t].size(); i++) { Command command; bool ret = parseLine(&command, &buf[line_infos[t][i].pos], line_infos[t][i].len); if (ret) { - commands[t].push_back(command); + if (command.type == COMMAND_V) { + command_count[t].num_v++; + } else if (command.type == COMMAND_VN) { + command_count[t].num_vn++; + } else if (command.type == COMMAND_VT) { + command_count[t].num_vt++; + } else if (command.type == COMMAND_F) { + command_count[t].num_f++; + } + commands[t].emplace_back(std::move(command)); } } @@ -959,16 +998,31 @@ void parse(const char* buf, size_t len, int req_num_threads) } auto command_sum = 0; - for (auto t = 0; t < num_threads; t++) { + for (size_t t = 0; t < num_threads; t++) { //std::cout << t << ": # of commands = " << commands[t].size() << std::endl; command_sum += commands[t].size(); } //std::cout << "# of commands = " << command_sum << std::endl; - std::vector vertices; - std::vector normals; - std::vector texcoords; - std::vector faces; + size_t num_v = 0; + size_t num_vn = 0; + size_t num_vt = 0; + size_t num_f = 0; + for (size_t t = 0; t < num_threads; t++) { + num_v += command_count[t].num_v; + num_vn += command_count[t].num_vn; + num_vt += command_count[t].num_vt; + num_f += command_count[t].num_f; + } + std::cout << "# v " << num_v << std::endl; + std::cout << "# vn " << num_vn << std::endl; + std::cout << "# vt " << num_vt << std::endl; + std::cout << "# f " << num_f << std::endl; + + vertices.reserve(num_v * 3); + normals.reserve(num_vn * 3); + texcoords.reserve(num_vt * 2); + faces.reserve(num_f); // merge { @@ -979,25 +1033,25 @@ void parse(const char* buf, size_t len, int req_num_threads) if (commands[t][i].type == COMMAND_EMPTY) { continue; } else if (commands[t][i].type == COMMAND_V) { - vertices.push_back(commands[t][i].vx); - vertices.push_back(commands[t][i].vy); - vertices.push_back(commands[t][i].vz); + vertices.emplace_back(commands[t][i].vx); + vertices.emplace_back(commands[t][i].vy); + vertices.emplace_back(commands[t][i].vz); } else if (commands[t][i].type == COMMAND_VN) { - normals.push_back(commands[t][i].nx); - normals.push_back(commands[t][i].ny); - normals.push_back(commands[t][i].nz); + normals.emplace_back(commands[t][i].nx); + normals.emplace_back(commands[t][i].ny); + normals.emplace_back(commands[t][i].nz); } else if (commands[t][i].type == COMMAND_VT) { - texcoords.push_back(commands[t][i].tx); - texcoords.push_back(commands[t][i].ty); + texcoords.emplace_back(commands[t][i].tx); + texcoords.emplace_back(commands[t][i].ty); } else if (commands[t][i].type == COMMAND_F) { int v_size = vertices.size() / 3; int vn_size = normals.size() / 3; int vt_size = texcoords.size() / 2; for (size_t k = 0; k < commands[t][i].f.size(); k++) { int v_idx = fixIndex(commands[t][i].f[k].v_idx, v_size); - int vn_idx = fixIndex(commands[t][i].f[k].vn_idx, v_size); - int vt_idx = fixIndex(commands[t][i].f[k].vt_idx, v_size); - faces.push_back(vertex_index(v_idx, vn_idx, vt_idx)); + int vn_idx = fixIndex(commands[t][i].f[k].vn_idx, vn_size); + int vt_idx = fixIndex(commands[t][i].f[k].vt_idx, vt_size); + faces.emplace_back(std::move(vertex_index(v_idx, vn_idx, vt_idx))); } } } @@ -1020,6 +1074,7 @@ void parse(const char* buf, size_t len, int req_num_threads) } +#ifdef CONSOLE_TEST int main(int argc, char **argv) { @@ -1035,7 +1090,7 @@ main(int argc, char **argv) } #ifdef _WIN64 - HANDLE file = CreateFileA("lineitem.tbl", GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, NULL); + HANDLE file = CreateFileA(argv[1], GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, NULL); assert(file != INVALID_HANDLE_VALUE); HANDLE fileMapping = CreateFileMapping(file, NULL, PAGE_READONLY, 0, 0, NULL); @@ -1090,3 +1145,4 @@ main(int argc, char **argv) return EXIT_SUCCESS; } +#endif diff --git a/experimental/premake4.lua b/experimental/premake4.lua new file mode 100644 index 0000000..e302e91 --- /dev/null +++ b/experimental/premake4.lua @@ -0,0 +1,48 @@ +solution "objview" + -- location ( "build" ) + configurations { "Debug", "Release" } + platforms {"native", "x64", "x32"} + + project "objview" + + kind "ConsoleApp" + language "C++" + files { "viewer.cc", "trackball.cc" } + includedirs { "./" } + includedirs { "../../" } + + buildoptions { "-std=c++11" } + buildoptions { "-fsanitize=address" } + linkoptions { "-fsanitize=address" } + + configuration { "linux" } + linkoptions { "`pkg-config --libs glfw3`" } + links { "GL", "GLU", "m", "GLEW", "X11", "Xrandr", "Xinerama", "Xi", "Xxf86vm", "Xcursor", "dl" } + linkoptions { "-pthread" } + + configuration { "windows" } + -- Path to GLFW3 + includedirs { '../../../../local/glfw-3.1.2.bin.WIN64/include' } + libdirs { '../../../../local/glfw-3.1.2.bin.WIN64/lib-vc2013' } + -- Path to GLEW + includedirs { '../../../../local/glew-1.13.0/include' } + libdirs { '../../../../local/glew-1.13.0/lib/Release/x64' } + + links { "glfw3", "glew32", "gdi32", "winmm", "user32", "glu32","opengl32", "kernel32" } + defines { "_CRT_SECURE_NO_WARNINGS" } + + configuration { "macosx" } + includedirs { "/usr/local/include" } + buildoptions { "-Wno-deprecated-declarations" } + libdirs { "/usr/local/lib" } + links { "glfw3", "GLEW" } + linkoptions { "-framework OpenGL", "-framework Cocoa", "-framework IOKit", "-framework CoreVideo" } + + configuration "Debug" + defines { "DEBUG" } + flags { "Symbols", "ExtraWarnings"} + + configuration "Release" + defines { "NDEBUG" } + flags { "Optimize", "ExtraWarnings"} + diff --git a/experimental/trackball.cc b/experimental/trackball.cc new file mode 100644 index 0000000..86ff3b3 --- /dev/null +++ b/experimental/trackball.cc @@ -0,0 +1,292 @@ +/* + * (c) Copyright 1993, 1994, Silicon Graphics, Inc. + * ALL RIGHTS RESERVED + * Permission to use, copy, modify, and distribute this software for + * any purpose and without fee is hereby granted, provided that the above + * copyright notice appear in all copies and that both the copyright notice + * and this permission notice appear in supporting documentation, and that + * the name of Silicon Graphics, Inc. not be used in advertising + * or publicity pertaining to distribution of the software without specific, + * written prior permission. + * + * THE MATERIAL EMBODIED ON THIS SOFTWARE IS PROVIDED TO YOU "AS-IS" + * AND WITHOUT WARRANTY OF ANY KIND, EXPRESS, IMPLIED OR OTHERWISE, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTY OF MERCHANTABILITY OR + * FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT SHALL SILICON + * GRAPHICS, INC. BE LIABLE TO YOU OR ANYONE ELSE FOR ANY DIRECT, + * SPECIAL, INCIDENTAL, INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY + * KIND, OR ANY DAMAGES WHATSOEVER, INCLUDING WITHOUT LIMITATION, + * LOSS OF PROFIT, LOSS OF USE, SAVINGS OR REVENUE, OR THE CLAIMS OF + * THIRD PARTIES, WHETHER OR NOT SILICON GRAPHICS, INC. HAS BEEN + * ADVISED OF THE POSSIBILITY OF SUCH LOSS, HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, ARISING OUT OF OR IN CONNECTION WITH THE + * POSSESSION, USE OR PERFORMANCE OF THIS SOFTWARE. + * + * US Government Users Restricted Rights + * Use, duplication, or disclosure by the Government is subject to + * restrictions set forth in FAR 52.227.19(c)(2) or subparagraph + * (c)(1)(ii) of the Rights in Technical Data and Computer Software + * clause at DFARS 252.227-7013 and/or in similar or successor + * clauses in the FAR or the DOD or NASA FAR Supplement. + * Unpublished-- rights reserved under the copyright laws of the + * United States. Contractor/manufacturer is Silicon Graphics, + * Inc., 2011 N. Shoreline Blvd., Mountain View, CA 94039-7311. + * + * OpenGL(TM) is a trademark of Silicon Graphics, Inc. + */ +/* + * Trackball code: + * + * Implementation of a virtual trackball. + * Implemented by Gavin Bell, lots of ideas from Thant Tessman and + * the August '88 issue of Siggraph's "Computer Graphics," pp. 121-129. + * + * Vector manip code: + * + * Original code from: + * David M. Ciemiewicz, Mark Grossman, Henry Moreton, and Paul Haeberli + * + * Much mucking with by: + * Gavin Bell + */ +#include +#include "trackball.h" + +/* + * This size should really be based on the distance from the center of + * rotation to the point on the object underneath the mouse. That + * point would then track the mouse as closely as possible. This is a + * simple example, though, so that is left as an Exercise for the + * Programmer. + */ +#define TRACKBALLSIZE (0.8) + +/* + * Local function prototypes (not defined in trackball.h) + */ +static float tb_project_to_sphere(float, float, float); +static void normalize_quat(float[4]); + +static void vzero(float *v) { + v[0] = 0.0; + v[1] = 0.0; + v[2] = 0.0; +} + +static void vset(float *v, float x, float y, float z) { + v[0] = x; + v[1] = y; + v[2] = z; +} + +static void vsub(const float *src1, const float *src2, float *dst) { + dst[0] = src1[0] - src2[0]; + dst[1] = src1[1] - src2[1]; + dst[2] = src1[2] - src2[2]; +} + +static void vcopy(const float *v1, float *v2) { + register int i; + for (i = 0; i < 3; i++) + v2[i] = v1[i]; +} + +static void vcross(const float *v1, const float *v2, float *cross) { + float temp[3]; + + temp[0] = (v1[1] * v2[2]) - (v1[2] * v2[1]); + temp[1] = (v1[2] * v2[0]) - (v1[0] * v2[2]); + temp[2] = (v1[0] * v2[1]) - (v1[1] * v2[0]); + vcopy(temp, cross); +} + +static float vlength(const float *v) { + return sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]); +} + +static void vscale(float *v, float div) { + v[0] *= div; + v[1] *= div; + v[2] *= div; +} + +static void vnormal(float *v) { vscale(v, 1.0 / vlength(v)); } + +static float vdot(const float *v1, const float *v2) { + return v1[0] * v2[0] + v1[1] * v2[1] + v1[2] * v2[2]; +} + +static void vadd(const float *src1, const float *src2, float *dst) { + dst[0] = src1[0] + src2[0]; + dst[1] = src1[1] + src2[1]; + dst[2] = src1[2] + src2[2]; +} + +/* + * Ok, simulate a track-ball. Project the points onto the virtual + * trackball, then figure out the axis of rotation, which is the cross + * product of P1 P2 and O P1 (O is the center of the ball, 0,0,0) + * Note: This is a deformed trackball-- is a trackball in the center, + * but is deformed into a hyperbolic sheet of rotation away from the + * center. This particular function was chosen after trying out + * several variations. + * + * It is assumed that the arguments to this routine are in the range + * (-1.0 ... 1.0) + */ +void trackball(float q[4], float p1x, float p1y, float p2x, float p2y) { + float a[3]; /* Axis of rotation */ + float phi; /* how much to rotate about axis */ + float p1[3], p2[3], d[3]; + float t; + + if (p1x == p2x && p1y == p2y) { + /* Zero rotation */ + vzero(q); + q[3] = 1.0; + return; + } + + /* + * First, figure out z-coordinates for projection of P1 and P2 to + * deformed sphere + */ + vset(p1, p1x, p1y, tb_project_to_sphere(TRACKBALLSIZE, p1x, p1y)); + vset(p2, p2x, p2y, tb_project_to_sphere(TRACKBALLSIZE, p2x, p2y)); + + /* + * Now, we want the cross product of P1 and P2 + */ + vcross(p2, p1, a); + + /* + * Figure out how much to rotate around that axis. + */ + vsub(p1, p2, d); + t = vlength(d) / (2.0 * TRACKBALLSIZE); + + /* + * Avoid problems with out-of-control values... + */ + if (t > 1.0) + t = 1.0; + if (t < -1.0) + t = -1.0; + phi = 2.0 * asin(t); + + axis_to_quat(a, phi, q); +} + +/* + * Given an axis and angle, compute quaternion. + */ +void axis_to_quat(float a[3], float phi, float q[4]) { + vnormal(a); + vcopy(a, q); + vscale(q, sin(phi / 2.0)); + q[3] = cos(phi / 2.0); +} + +/* + * Project an x,y pair onto a sphere of radius r OR a hyperbolic sheet + * if we are away from the center of the sphere. + */ +static float tb_project_to_sphere(float r, float x, float y) { + float d, t, z; + + d = sqrt(x * x + y * y); + if (d < r * 0.70710678118654752440) { /* Inside sphere */ + z = sqrt(r * r - d * d); + } else { /* On hyperbola */ + t = r / 1.41421356237309504880; + z = t * t / d; + } + return z; +} + +/* + * Given two rotations, e1 and e2, expressed as quaternion rotations, + * figure out the equivalent single rotation and stuff it into dest. + * + * This routine also normalizes the result every RENORMCOUNT times it is + * called, to keep error from creeping in. + * + * NOTE: This routine is written so that q1 or q2 may be the same + * as dest (or each other). + */ + +#define RENORMCOUNT 97 + +void add_quats(float q1[4], float q2[4], float dest[4]) { + static int count = 0; + float t1[4], t2[4], t3[4]; + float tf[4]; + + vcopy(q1, t1); + vscale(t1, q2[3]); + + vcopy(q2, t2); + vscale(t2, q1[3]); + + vcross(q2, q1, t3); + vadd(t1, t2, tf); + vadd(t3, tf, tf); + tf[3] = q1[3] * q2[3] - vdot(q1, q2); + + dest[0] = tf[0]; + dest[1] = tf[1]; + dest[2] = tf[2]; + dest[3] = tf[3]; + + if (++count > RENORMCOUNT) { + count = 0; + normalize_quat(dest); + } +} + +/* + * Quaternions always obey: a^2 + b^2 + c^2 + d^2 = 1.0 + * If they don't add up to 1.0, dividing by their magnitued will + * renormalize them. + * + * Note: See the following for more information on quaternions: + * + * - Shoemake, K., Animating rotation with quaternion curves, Computer + * Graphics 19, No 3 (Proc. SIGGRAPH'85), 245-254, 1985. + * - Pletinckx, D., Quaternion calculus as a basic tool in computer + * graphics, The Visual Computer 5, 2-13, 1989. + */ +static void normalize_quat(float q[4]) { + int i; + float mag; + + mag = (q[0] * q[0] + q[1] * q[1] + q[2] * q[2] + q[3] * q[3]); + for (i = 0; i < 4; i++) + q[i] /= mag; +} + +/* + * Build a rotation matrix, given a quaternion rotation. + * + */ +void build_rotmatrix(float m[4][4], const float q[4]) { + m[0][0] = 1.0 - 2.0 * (q[1] * q[1] + q[2] * q[2]); + m[0][1] = 2.0 * (q[0] * q[1] - q[2] * q[3]); + m[0][2] = 2.0 * (q[2] * q[0] + q[1] * q[3]); + m[0][3] = 0.0; + + m[1][0] = 2.0 * (q[0] * q[1] + q[2] * q[3]); + m[1][1] = 1.0 - 2.0 * (q[2] * q[2] + q[0] * q[0]); + m[1][2] = 2.0 * (q[1] * q[2] - q[0] * q[3]); + m[1][3] = 0.0; + + m[2][0] = 2.0 * (q[2] * q[0] - q[1] * q[3]); + m[2][1] = 2.0 * (q[1] * q[2] + q[0] * q[3]); + m[2][2] = 1.0 - 2.0 * (q[1] * q[1] + q[0] * q[0]); + m[2][3] = 0.0; + + m[3][0] = 0.0; + m[3][1] = 0.0; + m[3][2] = 0.0; + m[3][3] = 1.0; +} diff --git a/experimental/trackball.h b/experimental/trackball.h new file mode 100644 index 0000000..b1f9437 --- /dev/null +++ b/experimental/trackball.h @@ -0,0 +1,75 @@ +/* + * (c) Copyright 1993, 1994, Silicon Graphics, Inc. + * ALL RIGHTS RESERVED + * Permission to use, copy, modify, and distribute this software for + * any purpose and without fee is hereby granted, provided that the above + * copyright notice appear in all copies and that both the copyright notice + * and this permission notice appear in supporting documentation, and that + * the name of Silicon Graphics, Inc. not be used in advertising + * or publicity pertaining to distribution of the software without specific, + * written prior permission. + * + * THE MATERIAL EMBODIED ON THIS SOFTWARE IS PROVIDED TO YOU "AS-IS" + * AND WITHOUT WARRANTY OF ANY KIND, EXPRESS, IMPLIED OR OTHERWISE, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTY OF MERCHANTABILITY OR + * FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT SHALL SILICON + * GRAPHICS, INC. BE LIABLE TO YOU OR ANYONE ELSE FOR ANY DIRECT, + * SPECIAL, INCIDENTAL, INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY + * KIND, OR ANY DAMAGES WHATSOEVER, INCLUDING WITHOUT LIMITATION, + * LOSS OF PROFIT, LOSS OF USE, SAVINGS OR REVENUE, OR THE CLAIMS OF + * THIRD PARTIES, WHETHER OR NOT SILICON GRAPHICS, INC. HAS BEEN + * ADVISED OF THE POSSIBILITY OF SUCH LOSS, HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, ARISING OUT OF OR IN CONNECTION WITH THE + * POSSESSION, USE OR PERFORMANCE OF THIS SOFTWARE. + * + * US Government Users Restricted Rights + * Use, duplication, or disclosure by the Government is subject to + * restrictions set forth in FAR 52.227.19(c)(2) or subparagraph + * (c)(1)(ii) of the Rights in Technical Data and Computer Software + * clause at DFARS 252.227-7013 and/or in similar or successor + * clauses in the FAR or the DOD or NASA FAR Supplement. + * Unpublished-- rights reserved under the copyright laws of the + * United States. Contractor/manufacturer is Silicon Graphics, + * Inc., 2011 N. Shoreline Blvd., Mountain View, CA 94039-7311. + * + * OpenGL(TM) is a trademark of Silicon Graphics, Inc. + */ +/* + * trackball.h + * A virtual trackball implementation + * Written by Gavin Bell for Silicon Graphics, November 1988. + */ + +/* + * Pass the x and y coordinates of the last and current positions of + * the mouse, scaled so they are from (-1.0 ... 1.0). + * + * The resulting rotation is returned as a quaternion rotation in the + * first paramater. + */ +void trackball(float q[4], float p1x, float p1y, float p2x, float p2y); + +void negate_quat(float *q, float *qn); + +/* + * Given two quaternions, add them together to get a third quaternion. + * Adding quaternions to get a compound rotation is analagous to adding + * translations to get a compound translation. When incrementally + * adding rotations, the first argument here should be the new + * rotation, the second and third the total rotation (which will be + * over-written with the resulting new total rotation). + */ +void add_quats(float *q1, float *q2, float *dest); + +/* + * A useful function, builds a rotation matrix in Matrix based on + * given quaternion. + */ +void build_rotmatrix(float m[4][4], const float q[4]); + +/* + * This function computes a quaternion based on an axis (defined by + * the given vector) and an angle about which to rotate. The angle is + * expressed in radians. The result is put into the third argument. + */ +void axis_to_quat(float a[3], float phi, float q[4]); diff --git a/experimental/viewer.cc b/experimental/viewer.cc new file mode 100644 index 0000000..0d58d0b --- /dev/null +++ b/experimental/viewer.cc @@ -0,0 +1,487 @@ +// +// Simple .obj viewer(vertex only) +// +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#ifdef __APPLE__ +#include +#else +#include +#endif + +#include + +#include "trackball.h" +#include "optimized-parse.cc" + +typedef struct { + GLuint vb; // vertex buffer + int numTriangles; +} DrawObject; + +std::vector gDrawObjects; + +int width = 768; +int height = 768; + +double prevMouseX, prevMouseY; +bool mouseLeftPressed; +bool mouseMiddlePressed; +bool mouseRightPressed; +float curr_quat[4]; +float prev_quat[4]; +float eye[3], lookat[3], up[3]; + +GLFWwindow* window; + +void CheckErrors(std::string desc) { + GLenum e = glGetError(); + if (e != GL_NO_ERROR) { + fprintf(stderr, "OpenGL error in \"%s\": %d (%d)\n", desc.c_str(), e, e); + exit(20); + } +} + +void CalcNormal(float N[3], float v0[3], float v1[3], float v2[3]) { + float v10[3]; + v10[0] = v1[0] - v0[0]; + v10[1] = v1[1] - v0[1]; + v10[2] = v1[2] - v0[2]; + + float v20[3]; + v20[0] = v2[0] - v0[0]; + v20[1] = v2[1] - v0[1]; + v20[2] = v2[2] - v0[2]; + + N[0] = v20[1] * v10[2] - v20[2] * v10[1]; + N[1] = v20[2] * v10[0] - v20[0] * v10[2]; + N[2] = v20[0] * v10[1] - v20[1] * v10[0]; + + float len2 = N[0] * N[0] + N[1] * N[1] + N[2] * N[2]; + if (len2 > 0.0f) { + float len = sqrtf(len2); + + N[0] /= len; + N[1] /= len; + } +} + +const char *mmap_file(size_t *len, const char* filename) +{ +#ifdef _WIN64 + HANDLE file = CreateFileA(filename, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, NULL); + assert(file != INVALID_HANDLE_VALUE); + + HANDLE fileMapping = CreateFileMapping(file, NULL, PAGE_READONLY, 0, 0, NULL); + assert(fileMapping != INVALID_HANDLE_VALUE); + + LPVOID fileMapView = MapViewOfFile(fileMapping, FILE_MAP_READ, 0, 0, 0); + auto fileMapViewChar = (const char*)fileMapView; + assert(fileMapView != NULL); +#else + + FILE* f = fopen(filename, "r" ); + fseek(f, 0, SEEK_END); + long fileSize = ftell(f); + fclose(f); + + struct stat sb; + char *p; + int fd; + + fd = open (filename, O_RDONLY); + if (fd == -1) { + perror ("open"); + return NULL; + } + + if (fstat (fd, &sb) == -1) { + perror ("fstat"); + return NULL; + } + + if (!S_ISREG (sb.st_mode)) { + fprintf (stderr, "%s is not a file\n", "lineitem.tbl"); + return NULL; + } + + p = (char*)mmap (0, fileSize, PROT_READ, MAP_SHARED, fd, 0); + + if (p == MAP_FAILED) { + perror ("mmap"); + return NULL; + } + + if (close (fd) == -1) { + perror ("close"); + return NULL; + } + + return p; + + (*len) = fileSize; +#endif +} + + +bool LoadObjAndConvert(float bmin[3], float bmax[3], const char* filename) +{ + std::vector vertices; + std::vector normals; + std::vector texcoords; + std::vector faces; + + size_t data_len = 0; + const char* data = nullptr; + data = mmap_file(&data_len, filename); + if (data == nullptr) { + exit(-1); + return false; + } + parse(vertices, normals, texcoords, faces, data, data_len, 1); + + bmin[0] = bmin[1] = bmin[2] = std::numeric_limits::max(); + bmax[0] = bmax[1] = bmax[2] = -std::numeric_limits::max(); + + { + DrawObject o; + std::vector vb; // pos(3float), normal(3float), color(3float) + for (size_t f = 0; f < faces.size()/3; f++) { + + vertex_index idx0 = faces[3*f+0]; + vertex_index idx1 = faces[3*f+1]; + vertex_index idx2 = faces[3*f+2]; + + float v[3][3]; + for (int k = 0; k < 3; k++) { + int f0 = idx0.v_idx; + int f1 = idx1.v_idx; + int f2 = idx2.v_idx; + assert(f0 >= 0); + assert(f1 >= 0); + assert(f2 >= 0); + + v[0][k] = vertices[3*f0+k]; + v[1][k] = vertices[3*f1+k]; + v[2][k] = vertices[3*f2+k]; + bmin[k] = std::min(v[0][k], bmin[k]); + bmin[k] = std::min(v[1][k], bmin[k]); + bmin[k] = std::min(v[2][k], bmin[k]); + bmax[k] = std::max(v[0][k], bmax[k]); + bmax[k] = std::max(v[1][k], bmax[k]); + bmax[k] = std::max(v[2][k], bmax[k]); + } + + float n[3][3]; + + if (normals.size() > 0) { + int f0 = idx0.vn_idx; + int f1 = idx1.vn_idx; + int f2 = idx2.vn_idx; + assert(f0 >= 0); + assert(f1 >= 0); + assert(f2 >= 0); + for (int k = 0; k < 3; k++) { + n[0][k] = normals[3*f0+k]; + n[1][k] = normals[3*f1+k]; + n[2][k] = normals[3*f2+k]; + } + } else { + // compute geometric normal + CalcNormal(n[0], v[0], v[1], v[2]); + n[1][0] = n[0][0]; n[1][1] = n[0][1]; n[1][2] = n[0][2]; + n[2][0] = n[0][0]; n[2][1] = n[0][1]; n[2][2] = n[0][2]; + } + + for (int k = 0; k < 3; k++) { + vb.push_back(v[k][0]); + vb.push_back(v[k][1]); + vb.push_back(v[k][2]); + vb.push_back(n[k][0]); + vb.push_back(n[k][1]); + vb.push_back(n[k][2]); + // Use normal as color. + float c[3] = {n[k][0], n[k][1], n[k][2]}; + float len2 = c[0] * c[0] + c[1] * c[1] + c[2] * c[2]; + if (len2 > 0.0f) { + float len = sqrtf(len2); + + c[0] /= len; + c[1] /= len; + c[2] /= len; + } + vb.push_back(c[0] * 0.5 + 0.5); + vb.push_back(c[1] * 0.5 + 0.5); + vb.push_back(c[2] * 0.5 + 0.5); + } + + } + + o.vb = 0; + o.numTriangles = 0; + if (vb.size() > 0) { + glGenBuffers(1, &o.vb); + glBindBuffer(GL_ARRAY_BUFFER, o.vb); + glBufferData(GL_ARRAY_BUFFER, vb.size() * sizeof(float), &vb.at(0), GL_STATIC_DRAW); + o.numTriangles = vb.size() / 9 / 3; + } + + gDrawObjects.push_back(o); + } + + printf("bmin = %f, %f, %f\n", bmin[0], bmin[1], bmin[2]); + printf("bmax = %f, %f, %f\n", bmax[0], bmax[1], bmax[2]); + + return true; +} + +void reshapeFunc(GLFWwindow* window, int w, int h) +{ + (void)window; + printf("reshape\n"); + glViewport(0, 0, w, h); + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + gluPerspective(45.0, (float)w / (float)h, 0.01f, 100.0f); + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + + width = w; + height = h; +} + +void keyboardFunc(GLFWwindow *window, int key, int scancode, int action, int mods) { + (void)window; + (void)scancode; + (void)mods; + if(action == GLFW_PRESS || action == GLFW_REPEAT){ + // Move camera + float mv_x = 0, mv_y = 0, mv_z = 0; + if(key == GLFW_KEY_K) mv_x += 1; + else if(key == GLFW_KEY_J) mv_x += -1; + else if(key == GLFW_KEY_L) mv_y += 1; + else if(key == GLFW_KEY_H) mv_y += -1; + else if(key == GLFW_KEY_P) mv_z += 1; + else if(key == GLFW_KEY_N) mv_z += -1; + //camera.move(mv_x * 0.05, mv_y * 0.05, mv_z * 0.05); + // Close window + if(key == GLFW_KEY_Q || key == GLFW_KEY_ESCAPE) glfwSetWindowShouldClose(window, GL_TRUE); + + //init_frame = true; + } +} + +void clickFunc(GLFWwindow* window, int button, int action, int mods){ + (void)window; + (void)mods; + if(button == GLFW_MOUSE_BUTTON_LEFT){ + if(action == GLFW_PRESS){ + mouseLeftPressed = true; + trackball(prev_quat, 0.0, 0.0, 0.0, 0.0); + } else if(action == GLFW_RELEASE){ + mouseLeftPressed = false; + } + } + if(button == GLFW_MOUSE_BUTTON_RIGHT){ + if(action == GLFW_PRESS){ + mouseRightPressed = true; + } else if(action == GLFW_RELEASE){ + mouseRightPressed = false; + } + } + if(button == GLFW_MOUSE_BUTTON_MIDDLE){ + if(action == GLFW_PRESS){ + mouseMiddlePressed = true; + } else if(action == GLFW_RELEASE){ + mouseMiddlePressed = false; + } + } +} + +void motionFunc(GLFWwindow* window, double mouse_x, double mouse_y){ + (void)window; + float rotScale = 1.0f; + float transScale = 2.0f; + + if(mouseLeftPressed){ + trackball(prev_quat, + rotScale * (2.0f * prevMouseX - width) / (float)width, + rotScale * (height - 2.0f * prevMouseY) / (float)height, + rotScale * (2.0f * mouse_x - width) / (float)width, + rotScale * (height - 2.0f * mouse_y) / (float)height); + + add_quats(prev_quat, curr_quat, curr_quat); + } else if (mouseMiddlePressed) { + eye[0] -= transScale * (mouse_x - prevMouseX) / (float)width; + lookat[0] -= transScale * (mouse_x - prevMouseX) / (float)width; + eye[1] += transScale * (mouse_y - prevMouseY) / (float)height; + lookat[1] += transScale * (mouse_y - prevMouseY) / (float)height; + } else if (mouseRightPressed) { + eye[2] += transScale * (mouse_y - prevMouseY) / (float)height; + lookat[2] += transScale * (mouse_y - prevMouseY) / (float)height; + } + + // Update mouse point + prevMouseX = mouse_x; + prevMouseY = mouse_y; +} + +void Draw(const std::vector& drawObjects) +{ + glPolygonMode(GL_FRONT, GL_FILL); + glPolygonMode(GL_BACK, GL_FILL); + + glEnable(GL_POLYGON_OFFSET_FILL); + glPolygonOffset(1.0, 1.0); + glColor3f(1.0f, 1.0f, 1.0f); + for (size_t i = 0; i < drawObjects.size(); i++) { + DrawObject o = drawObjects[i]; + if (o.vb < 1) { + continue; + } + + glBindBuffer(GL_ARRAY_BUFFER, o.vb); + glEnableClientState(GL_VERTEX_ARRAY); + glEnableClientState(GL_NORMAL_ARRAY); + glEnableClientState(GL_COLOR_ARRAY); + glVertexPointer(3, GL_FLOAT, 36, (const void*)0); + glNormalPointer(GL_FLOAT, 36, (const void*)(sizeof(float)*3)); + glColorPointer(3, GL_FLOAT, 36, (const void*)(sizeof(float)*6)); + + glDrawArrays(GL_TRIANGLES, 0, 3 * o.numTriangles); + CheckErrors("drawarrays"); + } + + // draw wireframe + glDisable(GL_POLYGON_OFFSET_FILL); + glPolygonMode(GL_FRONT, GL_LINE); + glPolygonMode(GL_BACK, GL_LINE); + + glColor3f(0.0f, 0.0f, 0.4f); + for (size_t i = 0; i < drawObjects.size(); i++) { + DrawObject o = drawObjects[i]; + if (o.vb < 1) { + continue; + } + + glBindBuffer(GL_ARRAY_BUFFER, o.vb); + glEnableClientState(GL_VERTEX_ARRAY); + glEnableClientState(GL_NORMAL_ARRAY); + glDisableClientState(GL_COLOR_ARRAY); + glVertexPointer(3, GL_FLOAT, 36, (const void*)0); + glNormalPointer(GL_FLOAT, 36, (const void*)(sizeof(float)*3)); + + glDrawArrays(GL_TRIANGLES, 0, 3 * o.numTriangles); + CheckErrors("drawarrays"); + } +} + +static void Init() { + trackball(curr_quat, 0, 0, 0, 0); + + eye[0] = 0.0f; + eye[1] = 0.0f; + eye[2] = 3.0f; + + lookat[0] = 0.0f; + lookat[1] = 0.0f; + lookat[2] = 0.0f; + + up[0] = 0.0f; + up[1] = 1.0f; + up[2] = 0.0f; +} + + +int main(int argc, char **argv) +{ + if (argc < 2) { + std::cout << "Needs input.obj\n" << std::endl; + return 0; + } + + Init(); + + + if(!glfwInit()){ + std::cerr << "Failed to initialize GLFW." << std::endl; + return -1; + } + + + + window = glfwCreateWindow(width, height, "Obj viewer", NULL, NULL); + if(window == NULL){ + std::cerr << "Failed to open GLFW window. " << std::endl; + glfwTerminate(); + return 1; + } + + glfwMakeContextCurrent(window); + glfwSwapInterval(1); + + // Callback + glfwSetWindowSizeCallback(window, reshapeFunc); + glfwSetKeyCallback(window, keyboardFunc); + glfwSetMouseButtonCallback(window, clickFunc); + glfwSetCursorPosCallback(window, motionFunc); + + glewExperimental = true; + if (glewInit() != GLEW_OK) { + std::cerr << "Failed to initialize GLEW." << std::endl; + return -1; + } + + reshapeFunc(window, width, height); + + float bmin[3], bmax[3]; + if (false == LoadObjAndConvert(bmin, bmax, argv[1])) { + return -1; + } + + float maxExtent = 0.5f * (bmax[0] - bmin[0]); + if (maxExtent < 0.5f * (bmax[1] - bmin[1])) { + maxExtent = 0.5f * (bmax[1] - bmin[1]); + } + if (maxExtent < 0.5f * (bmax[2] - bmin[2])) { + maxExtent = 0.5f * (bmax[2] - bmin[2]); + } + + while(glfwWindowShouldClose(window) == GL_FALSE) { + glfwPollEvents(); + glClearColor(0.1f, 0.2f, 0.3f, 1.0f); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + glEnable(GL_DEPTH_TEST); + + // camera & rotate + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + GLfloat mat[4][4]; + gluLookAt(eye[0], eye[1], eye[2], lookat[0], lookat[1], lookat[2], up[0], up[1], up[2]); + build_rotmatrix(mat, curr_quat); + glMultMatrixf(&mat[0][0]); + + // Fit to -1, 1 + glScalef(1.0f / maxExtent, 1.0f / maxExtent, 1.0f / maxExtent); + + // Centerize object. + glTranslatef(-0.5*(bmax[0] + bmin[0]), -0.5*(bmax[1] + bmin[1]), -0.5*(bmax[2] + bmin[2])); + + Draw(gDrawObjects); + + glfwSwapBuffers(window); + } + + glfwTerminate(); +} -- cgit v1.2.3 From d392282f02c001cfab961d9d916ca464679d42cf Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Sun, 15 May 2016 18:49:06 +0900 Subject: Fix parser. --- experimental/optimized-parse.cc | 39 ++++++++++++++++++++++++++++++++------- experimental/viewer.cc | 16 +++++++++++----- 2 files changed, 43 insertions(+), 12 deletions(-) diff --git a/experimental/optimized-parse.cc b/experimental/optimized-parse.cc index c27ceae..6894184 100644 --- a/experimental/optimized-parse.cc +++ b/experimental/optimized-parse.cc @@ -495,7 +495,7 @@ static bool tryParseDouble(const char *s, const char *s_end, double *result) { // NOTE: Don't use powf here, it will absolutely murder precision. // pow(10.0, -read) - double frac_value = 10.0; + double frac_value = 1.0; for (int f = 0; f < read; f++) { frac_value *= 0.1; } @@ -762,7 +762,7 @@ static bool parseLine(Command *command, const char *p, size_t p_len) // group name if (token[0] == 'g' && IS_SPACE((token[1]))) { - std::vector names; + ShortString names[16]; int num_names = 0; @@ -857,6 +857,13 @@ static inline bool is_line_ending(const char* p, size_t i, size_t end_i) void parse(std::vector &vertices, std::vector &normals, std::vector &texcoords, std::vector &faces, const char* buf, size_t len, int req_num_threads) { + vertices.clear(); + normals.clear(); + texcoords.clear(); + faces.clear(); + + if (len < 1) return; + std::vector newline_marker(len, 0); auto num_threads = (req_num_threads < 0) ? std::thread::hardware_concurrency() : req_num_threads; @@ -1047,11 +1054,29 @@ void parse(std::vector &vertices, std::vector &normals, std::vecto int v_size = vertices.size() / 3; int vn_size = normals.size() / 3; int vt_size = texcoords.size() / 2; - for (size_t k = 0; k < commands[t][i].f.size(); k++) { - int v_idx = fixIndex(commands[t][i].f[k].v_idx, v_size); - int vn_idx = fixIndex(commands[t][i].f[k].vn_idx, vn_size); - int vt_idx = fixIndex(commands[t][i].f[k].vt_idx, vt_size); - faces.emplace_back(std::move(vertex_index(v_idx, vn_idx, vt_idx))); + + // triangulate. + { + vertex_index i0 = commands[t][i].f[0]; + vertex_index i1(-1); + vertex_index i2 = commands[t][i].f[1]; + + for (size_t k = 2; k < commands[t][i].f.size(); k++) { + i1 = i2; + i2 = commands[t][i].f[k]; + int v_idx = fixIndex(i0.v_idx, v_size); + int vn_idx = fixIndex(i0.vn_idx, vn_size); + int vt_idx = fixIndex(i0.vt_idx, vt_size); + faces.emplace_back(vertex_index(v_idx, vn_idx, vt_idx)); + v_idx = fixIndex(i1.v_idx, v_size); + vn_idx = fixIndex(i1.vn_idx, vn_size); + vt_idx = fixIndex(i1.vt_idx, vt_size); + faces.emplace_back(vertex_index(v_idx, vn_idx, vt_idx)); + v_idx = fixIndex(i2.v_idx, v_size); + vn_idx = fixIndex(i2.vn_idx, vn_size); + vt_idx = fixIndex(i2.vt_idx, vt_size); + faces.emplace_back(vertex_index(v_idx, vn_idx, vt_idx)); + } } } } diff --git a/experimental/viewer.cc b/experimental/viewer.cc index 0d58d0b..2691fcb 100644 --- a/experimental/viewer.cc +++ b/experimental/viewer.cc @@ -78,6 +78,7 @@ void CalcNormal(float N[3], float v0[3], float v1[3], float v2[3]) { const char *mmap_file(size_t *len, const char* filename) { + (*len) = 0; #ifdef _WIN64 HANDLE file = CreateFileA(filename, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, NULL); assert(file != INVALID_HANDLE_VALUE); @@ -127,9 +128,10 @@ const char *mmap_file(size_t *len, const char* filename) return NULL; } - return p; - (*len) = fileSize; + + return p; + #endif } @@ -148,7 +150,8 @@ bool LoadObjAndConvert(float bmin[3], float bmax[3], const char* filename) exit(-1); return false; } - parse(vertices, normals, texcoords, faces, data, data_len, 1); + printf("filesize: %d\n", (int)data_len); + parse(vertices, normals, texcoords, faces, data, data_len, /* num_threads */-1); bmin[0] = bmin[1] = bmin[2] = std::numeric_limits::max(); bmax[0] = bmax[1] = bmax[2] = -std::numeric_limits::max(); @@ -248,8 +251,11 @@ bool LoadObjAndConvert(float bmin[3], float bmax[3], const char* filename) void reshapeFunc(GLFWwindow* window, int w, int h) { (void)window; - printf("reshape\n"); - glViewport(0, 0, w, h); + // for retinal display. + int fb_w, fb_h; + glfwGetFramebufferSize(window, &fb_w, &fb_h); + + glViewport(0, 0, fb_w, fb_h); glMatrixMode(GL_PROJECTION); glLoadIdentity(); gluPerspective(45.0, (float)w / (float)h, 0.01f, 100.0f); -- cgit v1.2.3 From 54c28bd05f8de8876d05ab930d4df5f1a3f7bac4 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Sun, 15 May 2016 19:18:21 +0900 Subject: Fix vertex_index creation. --- experimental/optimized-parse.cc | 38 +++++++++++++++++++++----------------- experimental/premake4.lua | 10 +++++----- experimental/viewer.cc | 3 +++ 3 files changed, 29 insertions(+), 22 deletions(-) diff --git a/experimental/optimized-parse.cc b/experimental/optimized-parse.cc index 6894184..ef6b4fb 100644 --- a/experimental/optimized-parse.cc +++ b/experimental/optimized-parse.cc @@ -1,3 +1,7 @@ +// +//@todo { parse material. assign material id } +//@todo { support object&group } +// #ifdef _WIN64 #define atoll(S) _atoi64(S) #include @@ -943,15 +947,15 @@ void parse(std::vector &vertices, std::vector &normals, std::vecto auto end_time = std::chrono::high_resolution_clock::now(); std::chrono::duration ms = end_time - start_time; - std::cout << "line detection:" << ms.count() << " ms\n"; + //std::cout << "line detection:" << ms.count() << " ms\n"; } auto line_sum = 0; for (size_t t = 0; t < num_threads; t++) { - std::cout << t << ": # of lines = " << line_infos[t].size() << std::endl; + //std::cout << t << ": # of lines = " << line_infos[t].size() << std::endl; line_sum += line_infos[t].size(); } - std::cout << "# of lines = " << line_sum << std::endl; + //std::cout << "# of lines = " << line_sum << std::endl; std::vector commands[kMaxThreads]; @@ -962,7 +966,7 @@ void parse(std::vector &vertices, std::vector &normals, std::vecto auto t2 = std::chrono::high_resolution_clock::now(); std::chrono::duration ms1 = t2 - t1; - std::cout << ms1.count() << " ms\n"; + //std::cout << ms1.count() << " ms\n"; CommandCount command_count[kMaxThreads]; @@ -1001,7 +1005,7 @@ void parse(std::vector &vertices, std::vector &normals, std::vecto auto t_end = std::chrono::high_resolution_clock::now(); std::chrono::duration ms = t_end - t_start; - std::cout << "parse:" << ms.count() << " ms\n"; + //std::cout << "parse:" << ms.count() << " ms\n"; } auto command_sum = 0; @@ -1021,10 +1025,10 @@ void parse(std::vector &vertices, std::vector &normals, std::vecto num_vt += command_count[t].num_vt; num_f += command_count[t].num_f; } - std::cout << "# v " << num_v << std::endl; - std::cout << "# vn " << num_vn << std::endl; - std::cout << "# vt " << num_vt << std::endl; - std::cout << "# f " << num_f << std::endl; + //std::cout << "# v " << num_v << std::endl; + //std::cout << "# vn " << num_vn << std::endl; + //std::cout << "# vt " << num_vt << std::endl; + //std::cout << "# f " << num_f << std::endl; vertices.reserve(num_v * 3); normals.reserve(num_vn * 3); @@ -1067,15 +1071,15 @@ void parse(std::vector &vertices, std::vector &normals, std::vecto int v_idx = fixIndex(i0.v_idx, v_size); int vn_idx = fixIndex(i0.vn_idx, vn_size); int vt_idx = fixIndex(i0.vt_idx, vt_size); - faces.emplace_back(vertex_index(v_idx, vn_idx, vt_idx)); + faces.emplace_back(vertex_index(v_idx, vt_idx, vn_idx)); v_idx = fixIndex(i1.v_idx, v_size); vn_idx = fixIndex(i1.vn_idx, vn_size); vt_idx = fixIndex(i1.vt_idx, vt_size); - faces.emplace_back(vertex_index(v_idx, vn_idx, vt_idx)); + faces.emplace_back(vertex_index(v_idx, vt_idx, vn_idx)); v_idx = fixIndex(i2.v_idx, v_size); vn_idx = fixIndex(i2.vn_idx, vn_size); vt_idx = fixIndex(i2.vt_idx, vt_size); - faces.emplace_back(vertex_index(v_idx, vn_idx, vt_idx)); + faces.emplace_back(vertex_index(v_idx, vt_idx, vn_idx)); } } } @@ -1084,18 +1088,18 @@ void parse(std::vector &vertices, std::vector &normals, std::vecto auto t_end = std::chrono::high_resolution_clock::now(); std::chrono::duration ms = t_end - t_start; - std::cout << "merge:" << ms.count() << " ms\n"; + //std::cout << "merge:" << ms.count() << " ms\n"; } + auto t4 = std::chrono::high_resolution_clock::now(); + + std::chrono::duration ms_total = t4 - t1; + std::cout << "total parsing time: " << ms_total.count() << " ms\n"; std::cout << "# of vertices = " << vertices.size() << std::endl; std::cout << "# of normals = " << normals.size() << std::endl; std::cout << "# of texcoords = " << texcoords.size() << std::endl; std::cout << "# of faces = " << faces.size() << std::endl; - auto t4 = std::chrono::high_resolution_clock::now(); - - std::chrono::duration ms_total = t4 - t1; - std::cout << "total: " << ms_total.count() << " ms\n"; } diff --git a/experimental/premake4.lua b/experimental/premake4.lua index e302e91..6df8335 100644 --- a/experimental/premake4.lua +++ b/experimental/premake4.lua @@ -1,6 +1,6 @@ solution "objview" -- location ( "build" ) - configurations { "Debug", "Release" } + configurations { "Release", "Debug" } platforms {"native", "x64", "x32"} project "objview" @@ -12,8 +12,8 @@ solution "objview" includedirs { "../../" } buildoptions { "-std=c++11" } - buildoptions { "-fsanitize=address" } - linkoptions { "-fsanitize=address" } + --buildoptions { "-fsanitize=address" } + --linkoptions { "-fsanitize=address" } configuration { "linux" } linkoptions { "`pkg-config --libs glfw3`" } @@ -40,9 +40,9 @@ solution "objview" configuration "Debug" defines { "DEBUG" } - flags { "Symbols", "ExtraWarnings"} + flags { "Symbols"} configuration "Release" defines { "NDEBUG" } - flags { "Optimize", "ExtraWarnings"} + flags { "Optimize"} diff --git a/experimental/viewer.cc b/experimental/viewer.cc index 2691fcb..1990342 100644 --- a/experimental/viewer.cc +++ b/experimental/viewer.cc @@ -194,6 +194,9 @@ bool LoadObjAndConvert(float bmin[3], float bmax[3], const char* filename) assert(f0 >= 0); assert(f1 >= 0); assert(f2 >= 0); + assert(3*f0+2 < normals.size()); + assert(3*f1+2 < normals.size()); + assert(3*f2+2 < normals.size()); for (int k = 0; k < 3; k++) { n[0][k] = normals[3*f0+k]; n[1][k] = normals[3*f1+k]; -- cgit v1.2.3 From 00251e9a5b675f7cef126c82cc4993c85704a1c7 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Sun, 15 May 2016 19:19:41 +0900 Subject: Remove Makefile. --- experimental/Makefile | 2 -- 1 file changed, 2 deletions(-) delete mode 100644 experimental/Makefile diff --git a/experimental/Makefile b/experimental/Makefile deleted file mode 100644 index f72d56e..0000000 --- a/experimental/Makefile +++ /dev/null @@ -1,2 +0,0 @@ -all: - g++ -O2 -g -pthread -std=c++11 optimized-parse.cc -- cgit v1.2.3 From 660cc22b74a1bad7a97619dca56eb78a9f23e180 Mon Sep 17 00:00:00 2001 From: filipwasil Date: Sun, 15 May 2016 15:47:55 +0200 Subject: Enable building as shared library --- CMakeLists.txt | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index f4f497a..e43ea57 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -22,11 +22,14 @@ set(tinyobjloader-examples-objsticher ${TINYOBJLOADEREXAMPLES_DIR}/obj_sticher/obj_sticher.cc ) -add_library(tinyobjloader - ${tinyobjloader-Source} - ) - option(TINYOBJLOADER_BUILD_TEST_LOADER "Build Example Loader Application" OFF) +option(TINYOBJLOADER_COMPILATION_SHARED "Build as shared library" OFF) + +if (TINYOBJLOADER_COMPILATION_SHARED) + add_library(tinyobjloader SHARED ${tinyobjloader-Source}) +else() + add_library(tinyobjloader STATIC ${tinyobjloader-Source}) +endif() if(TINYOBJLOADER_BUILD_TEST_LOADER) add_executable(test_loader ${tinyobjloader-Example-Source}) -- cgit v1.2.3 From 820506792828c8662b7fdac2fad432993be859d4 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Mon, 16 May 2016 01:24:52 +0900 Subject: Support loading .obj from gzip compression. --- experimental/premake4.lua | 92 ++++++++++++++++++++++++++--------------------- experimental/viewer.cc | 80 +++++++++++++++++++++++++++++++++++++++-- 2 files changed, 130 insertions(+), 42 deletions(-) diff --git a/experimental/premake4.lua b/experimental/premake4.lua index 6df8335..4571f6a 100644 --- a/experimental/premake4.lua +++ b/experimental/premake4.lua @@ -1,3 +1,8 @@ +newoption { + trigger = "with-zlib", + description = "Build with zlib." +} + solution "objview" -- location ( "build" ) configurations { "Release", "Debug" } @@ -5,44 +10,51 @@ solution "objview" project "objview" - kind "ConsoleApp" - language "C++" - files { "viewer.cc", "trackball.cc" } - includedirs { "./" } - includedirs { "../../" } - - buildoptions { "-std=c++11" } - --buildoptions { "-fsanitize=address" } - --linkoptions { "-fsanitize=address" } - - configuration { "linux" } - linkoptions { "`pkg-config --libs glfw3`" } - links { "GL", "GLU", "m", "GLEW", "X11", "Xrandr", "Xinerama", "Xi", "Xxf86vm", "Xcursor", "dl" } - linkoptions { "-pthread" } - - configuration { "windows" } - -- Path to GLFW3 - includedirs { '../../../../local/glfw-3.1.2.bin.WIN64/include' } - libdirs { '../../../../local/glfw-3.1.2.bin.WIN64/lib-vc2013' } - -- Path to GLEW - includedirs { '../../../../local/glew-1.13.0/include' } - libdirs { '../../../../local/glew-1.13.0/lib/Release/x64' } - - links { "glfw3", "glew32", "gdi32", "winmm", "user32", "glu32","opengl32", "kernel32" } - defines { "_CRT_SECURE_NO_WARNINGS" } - - configuration { "macosx" } - includedirs { "/usr/local/include" } - buildoptions { "-Wno-deprecated-declarations" } - libdirs { "/usr/local/lib" } - links { "glfw3", "GLEW" } - linkoptions { "-framework OpenGL", "-framework Cocoa", "-framework IOKit", "-framework CoreVideo" } - - configuration "Debug" - defines { "DEBUG" } - flags { "Symbols"} - - configuration "Release" - defines { "NDEBUG" } - flags { "Optimize"} + kind "ConsoleApp" + language "C++" + files { "viewer.cc", "trackball.cc" } + includedirs { "./" } + includedirs { "../../" } + + buildoptions { "-std=c++11" } + + if _OPTIONS['with-zlib'] then + defines { 'ENABLE_ZLIB' } + links { 'z' } + end + + -- Uncomment if you want address sanitizer(gcc/clang only) + --buildoptions { "-fsanitize=address" } + --linkoptions { "-fsanitize=address" } + + configuration { "linux" } + linkoptions { "`pkg-config --libs glfw3`" } + links { "GL", "GLU", "m", "GLEW", "X11", "Xrandr", "Xinerama", "Xi", "Xxf86vm", "Xcursor", "dl" } + linkoptions { "-pthread" } + + configuration { "windows" } + -- Path to GLFW3 + includedirs { '../../../../local/glfw-3.1.2.bin.WIN64/include' } + libdirs { '../../../../local/glfw-3.1.2.bin.WIN64/lib-vc2013' } + -- Path to GLEW + includedirs { '../../../../local/glew-1.13.0/include' } + libdirs { '../../../../local/glew-1.13.0/lib/Release/x64' } + + links { "glfw3", "glew32", "gdi32", "winmm", "user32", "glu32","opengl32", "kernel32" } + defines { "_CRT_SECURE_NO_WARNINGS" } + + configuration { "macosx" } + includedirs { "/usr/local/include" } + buildoptions { "-Wno-deprecated-declarations" } + libdirs { "/usr/local/lib" } + links { "glfw3", "GLEW" } + linkoptions { "-framework OpenGL", "-framework Cocoa", "-framework IOKit", "-framework CoreVideo" } + + configuration "Debug" + defines { "DEBUG" } + flags { "Symbols"} + + configuration "Release" + defines { "NDEBUG" } + flags { "Optimize"} diff --git a/experimental/viewer.cc b/experimental/viewer.cc index 1990342..fa44320 100644 --- a/experimental/viewer.cc +++ b/experimental/viewer.cc @@ -9,8 +9,13 @@ #include #include #include +#include #include +#if defined(ENABLE_ZLIB) +#include +#endif + #include #ifdef __APPLE__ @@ -135,6 +140,78 @@ const char *mmap_file(size_t *len, const char* filename) #endif } +bool gz_load(std::vector* buf, const char* filename) +{ + gzFile file; + file = gzopen (filename, "r"); + if (! file) { + fprintf (stderr, "gzopen of '%s' failed: %s.\n", filename, + strerror (errno)); + exit (EXIT_FAILURE); + return false; + } + while (1) { + int err; + int bytes_read; + unsigned char buffer[1024]; + bytes_read = gzread (file, buffer, 1024); + buf->insert(buf->end(), buffer, buffer + 1024); + //printf ("%s", buffer); + if (bytes_read < 1024) { + if (gzeof (file)) { + break; + } + else { + const char * error_string; + error_string = gzerror (file, & err); + if (err) { + fprintf (stderr, "Error: %s.\n", error_string); + exit (EXIT_FAILURE); + return false; + } + } + } + } + gzclose (file); + return true; +} + +const char* get_file_data(size_t *len, const char* filename) +{ + + char *ext = strrchr(filename, '.'); + + size_t data_len = 0; + const char* data = nullptr; + +#if defined(ENABLE_ZLIB) + if (strcmp(ext, ".gz") == 0) { + // gzipped data. + printf("compressed\n"); + + std::vector buf; + bool ret = gz_load(&buf, filename); + + if (ret) { + char *p = static_cast(malloc(buf.size() + 1)); // @fixme { implement deleter } + memcpy(p, &buf.at(0), buf.size()); + p[buf.size()] = '\0'; + data = p; + data_len = buf.size(); + } + + } else { +#else + { +#endif + + data = mmap_file(&data_len, filename); + } + + (*len) = data_len; + return data; +} + bool LoadObjAndConvert(float bmin[3], float bmax[3], const char* filename) { @@ -144,8 +221,7 @@ bool LoadObjAndConvert(float bmin[3], float bmax[3], const char* filename) std::vector faces; size_t data_len = 0; - const char* data = nullptr; - data = mmap_file(&data_len, filename); + const char* data = get_file_data(&data_len, filename); if (data == nullptr) { exit(-1); return false; -- cgit v1.2.3 From 64164b3a8255d54b96ad86375f0f8270ef5a752d Mon Sep 17 00:00:00 2001 From: filipwasil Date: Sun, 15 May 2016 22:26:29 +0200 Subject: missing size() in radme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3e6663b..e710b69 100644 --- a/README.md +++ b/README.md @@ -123,7 +123,7 @@ if (!ret) { for (size_t s = 0; s < shapes.size(); s++) { // Loop over faces(polygon) size_t index_offset = 0; - for (size_t f = 0; f < shapes[i].mesh.num_face_vertices; f++) { + for (size_t f = 0; f < shapes[i].mesh.num_face_vertices.size(); f++) { int fv = shapes[i].mesh.num_face_vertices[f]; // Loop over vertices in the face. -- cgit v1.2.3 From 8c03771aac600dc9775c1ca0df0c78c23727f2cf Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Thu, 19 May 2016 15:05:57 +0900 Subject: Add ltalloc. --- experimental/LICENSE.ltalloc | 26 ++ experimental/ltalloc.cc | 973 ++++++++++++++++++++++++++++++++++++++++ experimental/ltalloc.h | 14 + experimental/ltalloc.hpp | 59 +++ experimental/optimized-parse.cc | 86 ++-- experimental/premake4.lua | 2 +- experimental/viewer.cc | 54 ++- 7 files changed, 1164 insertions(+), 50 deletions(-) create mode 100644 experimental/LICENSE.ltalloc create mode 100644 experimental/ltalloc.cc create mode 100644 experimental/ltalloc.h create mode 100644 experimental/ltalloc.hpp diff --git a/experimental/LICENSE.ltalloc b/experimental/LICENSE.ltalloc new file mode 100644 index 0000000..4a084d1 --- /dev/null +++ b/experimental/LICENSE.ltalloc @@ -0,0 +1,26 @@ +Copyright (c) 2013, Alexander Tretyak +Copyright (c) 2015, r-lyeh +All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of the author nor the names of its +contributors may be used to endorse or promote products derived +from this software without specific prior written permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/experimental/ltalloc.cc b/experimental/ltalloc.cc new file mode 100644 index 0000000..1d32b44 --- /dev/null +++ b/experimental/ltalloc.cc @@ -0,0 +1,973 @@ +/* +Copyright (c) 2013, Alexander Tretyak +Copyright (c) 2015, r-lyeh +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of the author nor the names of its +contributors may be used to endorse or promote products derived +from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + + Note: The latest version of this memory allocator obtainable at + http://ltalloc.googlecode.com/hg/ltalloc.cc + + Project URL: http://code.google.com/p/ltalloc +*/ + +#include "ltalloc.h" + +#define LTALLOC_VERSION "2.0.0" /* (2015/06/16) - ltcalloc(), ltmsize(), ltrealloc(), ltmemalign(), LTALLOC_AUTO_GC_INTERVAL +#define LTALLOC_VERSION "1.0.0" /* (2015/06/16) - standard STL allocator provided [see ltalloc.hpp file](ltalloc.hpp) +#define LTALLOC_VERSION "0.0.0" /* (2013/xx/xx) - fork from public repository */ + +//Customizable constants +//#define LTALLOC_DISABLE_OPERATOR_NEW_OVERRIDE +//#define LTALLOC_AUTO_GC_INTERVAL 3.0 +#ifndef LTALLOC_SIZE_CLASSES_SUBPOWER_OF_TWO +#define LTALLOC_SIZE_CLASSES_SUBPOWER_OF_TWO 2//determines how accurately size classes are spaced (i.e. when = 0, allocation requests are rounded up to the nearest power of two (2^n), when = 1, rounded to 2^n, or (2^n)*1.5, when = 2, rounded to 2^n, (2^n)*1.25, (2^n)*1.5, or (2^n)*1.75, and so on); this parameter have direct influence on memory fragmentation - bigger values lead to reducing internal fragmentation (which can be approximately estimated as pow(0.5, VALUE)*100%), but at the same time increasing external fragmentation +#endif +#define CHUNK_SIZE (64*1024)//size of chunk (basic allocation unit for all allocations of size <= MAX_BLOCK_SIZE); must be a power of two (as well as all following parameters), also should not be less than allocation granularity on Windows (which is always 64K by design of NT kernel) +#define CACHE_LINE_SIZE 64 +#define MAX_NUM_OF_BLOCKS_IN_BATCH 256//maximum number of blocks to move between a thread cache and a central cache in one shot +static const unsigned int MAX_BATCH_SIZE = 64*1024;//maximum total size of blocks to move between a thread cache and a central cache in one shot (corresponds to half size of thread cache of each size class) +static const unsigned int MAX_BLOCK_SIZE = CHUNK_SIZE;//requesting memory of any size greater than this value will lead to direct call of system virtual memory allocation routine + +//Platform-specific stuff + +#ifdef __cplusplus +#define CPPCODE(code) code +#include +#else +#define CPPCODE(code) +#endif + +#ifdef LTALLOC_AUTO_GC_INTERVAL +#include +# if LTALLOC_AUTO_GC_INTERVAL <= 0 +# undef LTALLOC_AUTO_GC_INTERVAL +# define LTALLOC_AUTO_GC_INTERVAL 3.00 +# endif +#endif + +#ifdef __GNUC__ + +#define __STDC_LIMIT_MACROS +#include //for SIZE_MAX +#include //for UINT_MAX +#define alignas(a) __attribute__((aligned(a))) +#define thread_local __thread +#define NOINLINE __attribute__((noinline)) +#define CAS_LOCK(lock) __sync_lock_test_and_set(lock, 1) +#define SPINLOCK_RELEASE(lock) __sync_lock_release(lock) +#define PAUSE __asm__ __volatile__("pause" ::: "memory") +#define BSR(r, v) r = CODE3264(__builtin_clz(v) ^ 31, __builtin_clzll(v) ^ 63)//x ^ 31 = 31 - x, but gcc does not optimize 31 - __builtin_clz(x) to bsr(x), but generates 31 - (bsr(x) ^ 31) + +#elif _MSC_VER + +#define _ALLOW_KEYWORD_MACROS +#include //for SIZE_MAX and UINT_MAX +#define alignas(a) __declspec(align(a)) +#define thread_local __declspec(thread) +#define NOINLINE __declspec(noinline) +#define CAS_LOCK(lock) _InterlockedExchange((long*)lock, 1) +#define SPINLOCK_RELEASE(lock) _InterlockedExchange((long*)lock, 0) +#define PAUSE _mm_pause() +#define BSR(r, v) CODE3264(_BitScanReverse, _BitScanReverse64)((unsigned long*)&r, v) +CPPCODE(extern "C") long _InterlockedExchange(long volatile *, long); +CPPCODE(extern "C") void _mm_pause(); +#pragma warning(disable: 4127 4201 4324 4290)//"conditional expression is constant", "nonstandard extension used : nameless struct/union", and "structure was padded due to __declspec(align())" + +#else +#error Unsupported compiler +#endif + +#if __GNUC__ || __INTEL_COMPILER +#define likely(x) __builtin_expect(!!(x), 1) +#define unlikely(x) __builtin_expect(!!(x), 0) +#else +#define likely(x) (x) +#define unlikely(x) (x) +#endif + +static void SPINLOCK_ACQUIRE(volatile int *lock) {if (CAS_LOCK(lock)) while (*lock || CAS_LOCK(lock)) PAUSE;} + +#include +#include //for memset + +#if SIZE_MAX == UINT_MAX +#define CODE3264(c32, c64) c32 +#else +#define CODE3264(c32, c64) c64 +#endif +typedef char CODE3264_check[sizeof(void*) == CODE3264(4, 8) ? 1 : -1]; + +#ifdef _WIN32 +#define WIN32_LEAN_AND_MEAN +#include + +#define VMALLOC(size) VirtualAlloc(NULL, size, MEM_RESERVE|MEM_COMMIT, PAGE_READWRITE) +#define VMFREE(p, size) VirtualFree(p, 0, MEM_RELEASE) + +#else + +#include +#include + +#define VMALLOC(size) (void*)(((uintptr_t)mmap(NULL, size, PROT_READ|PROT_WRITE, MAP_ANON|MAP_PRIVATE, -1, 0)+1)&~1)//with the conversion of MAP_FAILED to 0 +#define VMFREE(p, size) munmap(p, size) + +static size_t page_size() +{ + assert((uintptr_t)MAP_FAILED+1 == 0);//have to use dynamic check somewhere, because some gcc versions (e.g. 4.4.5) won't compile typedef char MAP_FAILED_value_static_check[(uintptr_t)MAP_FAILED+1 == 0 ? 1 : -1]; + static size_t pagesize = 0; + if (!pagesize) pagesize = sysconf(_SC_PAGE_SIZE);//assuming that writing of size_t value is atomic, so this can be done safely in different simultaneously running threads + return pagesize; +} + +typedef struct PTrieNode // Compressed radix tree (patricia trie) for storing sizes of system allocations +{ // This implementation have some specific properties: + uintptr_t keys[2]; // 1. There are no separate leaf nodes (with both null children), as leaf's value is stored directly in place of corresponding child node pointer. Least significant bit of that pointer used to determine its meaning (i.e., is it a value, or child node pointer). + struct PTrieNode *childNodes[2];// 2. Inserting a new element (key/value) into this tree require to create always an exactly one new node (and similarly for remove key/node operation). +} PTrieNode; // 3. Tree always contains just one node with null child (i.e. all but one nodes in any possible tree are always have two children). +#define PTRIE_NULL_NODE (PTrieNode*)(uintptr_t)1 +static PTrieNode *ptrieRoot = PTRIE_NULL_NODE, *ptrieFreeNodesList = NULL, *ptrieNewAllocatedPage = NULL; +static volatile int ptrieLock = 0; + +static uintptr_t ptrie_lookup(uintptr_t key) +{ + PTrieNode *node = ptrieRoot; + uintptr_t *lastKey = NULL; + while (!((uintptr_t)node & 1)) + { + int branch = (key >> (node->keys[0] & 0xFF)) & 1; + lastKey = &node->keys[branch]; + node = node->childNodes[branch]; + } + assert(lastKey && (*lastKey & ~0xFF) == key); + return (uintptr_t)node & ~1; +} + +static void ptrie_insert(uintptr_t key, uintptr_t value, PTrieNode *newNode/* = (PTrieNode*)malloc(sizeof(PTrieNode))*/) +{ + PTrieNode **node = &ptrieRoot, *n; + uintptr_t *prevKey = NULL, x, pkey; + unsigned int index, b; + assert(!((value & 1) | (key & 0xFF)));//check constraints for key/value + for (;;) + { + n = *node; + if (!((uintptr_t)n & 1))//not a leaf + { + int prefixEnd = n->keys[0] & 0xFF; + x = key ^ n->keys[0];// & ~0xFF; + if (!(x & (~(uintptr_t)1 << prefixEnd))) {//prefix matches, so go on + int branch = (key >> prefixEnd) & 1; + node = &n->childNodes[branch]; + prevKey = &n->keys[branch]; + } else {//insert a new node before current + pkey = n->keys[0] & ~0xFF; + break; + } + } else {//leaf + if (*node == PTRIE_NULL_NODE) { + *node = newNode; + newNode->keys[0] = key;//left prefixEnd = 0, so all following insertions will be before this node + newNode->childNodes[0] = (PTrieNode*)(value | 1); + newNode->childNodes[1] = PTRIE_NULL_NODE; + return; + } else { + pkey = *prevKey & ~0xFF; + x = key ^ pkey; + assert(x/*key != pkey*/ && "key already inserted"); + break; + } + } + } + BSR(index, x); + b = (key >> index) & 1; + newNode->keys[b] = key; + newNode->keys[b^1] = pkey; + newNode->keys[0] |= index; + newNode->childNodes[b] = (PTrieNode*)(value | 1); + newNode->childNodes[b^1] = n; + *node = newNode; +} + +static uintptr_t ptrie_remove(uintptr_t key) +{ + PTrieNode **node = &ptrieRoot; + uintptr_t *pkey = NULL; + assert(ptrieRoot != PTRIE_NULL_NODE && "trie is empty!"); + for (;;) + { + PTrieNode *n = *node; + int branch = (key >> (n->keys[0] & 0xFF)) & 1; + PTrieNode *cn = n->childNodes[branch];//current child node + if ((uintptr_t)cn & 1)//leaf + { + PTrieNode *other = n->childNodes[branch^1]; + assert((n->keys[branch] & ~0xFF) == key); + assert(cn != PTRIE_NULL_NODE && "node's key is probably broken"); + // if (other == PTRIE_NULL_NODE) *node = PTRIE_NULL_NODE; else//special handling for null child nodes is not necessary + if (((uintptr_t)other & 1) && other != PTRIE_NULL_NODE)//if other node is not a pointer + *pkey = (n->keys[branch^1] & ~0xFF) | ((*pkey) & 0xFF); + *node = other; + *(PTrieNode**)n = ptrieFreeNodesList; ptrieFreeNodesList = n;//free(n); + return (uintptr_t)cn & ~1; + } + pkey = &n->keys[branch]; + node = &n->childNodes[branch]; + } +} +#endif + +static void *sys_aligned_alloc(size_t alignment, size_t size) +{ + void *p = VMALLOC(size);//optimistically try mapping precisely the right amount before falling back to the slow method + assert(!(alignment & (alignment-1)) && "alignment must be a power of two"); + if ((uintptr_t)p & (alignment-1)/* && p != MAP_FAILED*/) + { + VMFREE(p, size); +#ifdef _WIN32 + {static DWORD allocationGranularity = 0; + if (!allocationGranularity) { + SYSTEM_INFO si; + GetSystemInfo(&si); + allocationGranularity = si.dwAllocationGranularity; + } + if ((uintptr_t)p < 16*1024*1024)//fill "bubbles" (reserve unaligned regions) at the beginning of virtual address space, otherwise there will be always falling back to the slow method + VirtualAlloc(p, alignment - ((uintptr_t)p & (alignment-1)), MEM_RESERVE, PAGE_NOACCESS); + do + { + p = VirtualAlloc(NULL, size + alignment - allocationGranularity, MEM_RESERVE, PAGE_NOACCESS); + if (p == NULL) return NULL; + VirtualFree(p, 0, MEM_RELEASE);//unfortunately, WinAPI doesn't support release a part of allocated region, so release a whole region + p = VirtualAlloc((void*)(((uintptr_t)p + (alignment-1)) & ~(alignment-1)), size, MEM_RESERVE|MEM_COMMIT, PAGE_READWRITE); + } while (p == NULL);} +#else + p = VMALLOC(size + alignment - page_size()); + if (p/* != MAP_FAILED*/) + { + uintptr_t ap = ((uintptr_t)p + (alignment-1)) & ~(alignment-1); + uintptr_t diff = ap - (uintptr_t)p; + if (diff) VMFREE(p, diff); + diff = alignment - page_size() - diff; + assert((intptr_t)diff >= 0); + if (diff) VMFREE((void*)(ap + size), diff); + return (void*)ap; + } +#endif + } + //if (p == 0) p = sys_aligned_alloc(alignment, size);//just in case (because 0 pointer is handled specially elsewhere) + //if (p == MAP_FAILED) p = NULL; + return p; +} + +static NOINLINE void sys_free(void *p) +{ + if (p == NULL) return; +#ifdef _WIN32 + VirtualFree(p, 0, MEM_RELEASE); +#else + SPINLOCK_ACQUIRE(&ptrieLock); + size_t size = ptrie_remove((uintptr_t)p); + SPINLOCK_RELEASE(&ptrieLock); + munmap(p, size); +#endif +} + +static void release_thread_cache(void*); +#ifdef __GNUC__ +#include +#pragma weak pthread_once +#pragma weak pthread_key_create +#pragma weak pthread_setspecific +static pthread_key_t pthread_key; +static pthread_once_t init_once = PTHREAD_ONCE_INIT; +static void init_pthread_key() { pthread_key_create(&pthread_key, release_thread_cache); } +static thread_local int thread_initialized = 0; +static void init_pthread_destructor()//must be called only when some block placed into a thread cache's free list +{ + if (unlikely(!thread_initialized)) + { + thread_initialized = 1; + if (pthread_once) + { + pthread_once(&init_once, init_pthread_key); + pthread_setspecific(pthread_key, (void*)1);//set nonzero value to force calling of release_thread_cache() on thread terminate + } + } +} +#else +static void NTAPI on_tls_callback(PVOID h, DWORD reason, PVOID pv) { h; pv; if (reason == DLL_THREAD_DETACH) release_thread_cache(0); } +#pragma comment(linker, "/INCLUDE:" CODE3264("_","") "p_thread_callback_ltalloc") +#pragma const_seg(".CRT$XLL") +extern CPPCODE("C") const PIMAGE_TLS_CALLBACK p_thread_callback_ltalloc = on_tls_callback; +#pragma const_seg() +#define init_pthread_destructor() +#endif + +//End of platform-specific stuff + +#define MAX_BLOCK_SIZE (MAX_BLOCK_SIZE < CHUNK_SIZE - (CHUNK_SIZE >> (1 + LTALLOC_SIZE_CLASSES_SUBPOWER_OF_TWO)) ? \ + MAX_BLOCK_SIZE : CHUNK_SIZE - (CHUNK_SIZE >> (1 + LTALLOC_SIZE_CLASSES_SUBPOWER_OF_TWO))) +#define NUMBER_OF_SIZE_CLASSES ((sizeof(void*)*8 + 1) << LTALLOC_SIZE_CLASSES_SUBPOWER_OF_TWO) + +typedef struct FreeBlock +{ + struct FreeBlock *next, + *nextBatch;//in the central cache blocks are organized into batches to allow fast moving blocks from thread cache and back +} FreeBlock; + +typedef struct alignas(CACHE_LINE_SIZE) ChunkBase//force sizeof(Chunk) = cache line size to avoid false sharing +{ + unsigned int sizeClass; +} Chunk; + +typedef struct alignas(CACHE_LINE_SIZE) ChunkSm//chunk of smallest blocks of size = sizeof(void*) +{ + unsigned int sizeClass;//struct ChunkBase chunk; + struct ChunkSm *prev, *next; + int numBatches; +#define NUM_OF_BATCHES_IN_CHUNK_SM CHUNK_SIZE/(sizeof(void*)*MAX_NUM_OF_BLOCKS_IN_BATCH) + FreeBlock *batches[NUM_OF_BATCHES_IN_CHUNK_SM];//batches of blocks inside ChunkSm have to be stored separately (as smallest blocks of size = sizeof(void*) do not have enough space to store second pointer for the batch) +} ChunkSm; + +typedef struct alignas(CACHE_LINE_SIZE)//align needed to prevent cache line sharing between adjacent classes accessed from different threads +{ + volatile int lock; + unsigned int freeBlocksInLastChunk; + char *lastChunk;//Chunk or ChunkSm + union { + FreeBlock *firstBatch; + ChunkSm *chunkWithFreeBatches; + }; + FreeBlock *freeList;//short list of free blocks that for some reason are not organized into batches + unsigned int freeListSize;//should be less than batch size + uintptr_t minChunkAddr, maxChunkAddr; +} CentralCache; +static CentralCache centralCache[NUMBER_OF_SIZE_CLASSES];// = {{0}}; + +typedef struct +{ + FreeBlock *freeList; + FreeBlock *tempList;//intermediate list providing a hysteresis in order to avoid a corner case of too frequent moving free blocks to the central cache and back from + int counter;//number of blocks in freeList (used to determine when to move free blocks list to the central cache) +} ThreadCache; +static thread_local ThreadCache threadCache[NUMBER_OF_SIZE_CLASSES];// = {{0}}; + +static struct +{ + volatile int lock; + void *freeChunk; + size_t size; +} pad = {0, NULL, 0}; + +static CPPCODE(inline) unsigned int get_size_class(size_t size) +{ + unsigned int index; +#if _MSC_VER && LTALLOC_SIZE_CLASSES_SUBPOWER_OF_TWO == 2 + static const unsigned char small_size_classes[256] = {//have to use a const array here, because MS compiler unfortunately does not evaluate _BitScanReverse with a constant argument at compile time (as gcc does for __builtin_clz) +#if CODE3264(1, 0) + 131, 4, 15, 17, 19, 20, 21, 22, 23, 24, 24, 25, 25, 26, 26, 27, 27, 28, 28, 28, 28, 29, 29, 29, 29, 30, 30, 30, 30, 31, 31, 31, 31, 32, 32, 32, 32, 32, 32, 32, 32, 33, 33, 33, 33, 33, 33, 33, 33, 34, 34, 34, 34, 34, 34, 34, 34, 35, 35, 35, 35, 35, 35, 35, 35, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43 +#else + 131, 15, 19, 21, 23, 24, 25, 26, 27, 28, 28, 29, 29, 30, 30, 31, 31, 32, 32, 32, 32, 33, 33, 33, 33, 34, 34, 34, 34, 35, 35, 35, 35, 36, 36, 36, 36, 36, 36, 36, 36, 37, 37, 37, 37, 37, 37, 37, 37, 38, 38, 38, 38, 38, 38, 38, 38, 39, 39, 39, 39, 39, 39, 39, 39, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47 +#endif + }; + if (size < 256 * sizeof(void*) - (sizeof(void*)-1)) + return small_size_classes[(size + (sizeof(void*)-1)) / sizeof(void*)]; +#endif + + size = (size + (sizeof(void*)-1)) & ~(sizeof(void*)-1);//minimum block size is sizeof(void*), doing this is better than just "size = max(size, sizeof(void*))" + + BSR(index, (size-1)|1);//"|1" needed because result of BSR is undefined for zero input +#if LTALLOC_SIZE_CLASSES_SUBPOWER_OF_TWO == 0 + return index; +#else + return (index<> (index-LTALLOC_SIZE_CLASSES_SUBPOWER_OF_TWO)); +#endif +} + +static unsigned int class_to_size(unsigned int c) +{ +#if LTALLOC_SIZE_CLASSES_SUBPOWER_OF_TWO == 0 + return 2 << c; +#else +#if LTALLOC_SIZE_CLASSES_SUBPOWER_OF_TWO >= CODE3264(2, 3) + if (unlikely(c < (LTALLOC_SIZE_CLASSES_SUBPOWER_OF_TWO<>LTALLOC_SIZE_CLASSES_SUBPOWER_OF_TWO); + else +#endif + { + c -= (1<>LTALLOC_SIZE_CLASSES_SUBPOWER_OF_TWO)-LTALLOC_SIZE_CLASSES_SUBPOWER_OF_TWO); + } +#endif +} + +static unsigned int batch_size(unsigned int sizeClass)//calculates a number of blocks to move between a thread cache and a central cache in one shot +{ + return ((MAX_BATCH_SIZE-1) >> (sizeClass >> LTALLOC_SIZE_CLASSES_SUBPOWER_OF_TWO)) & (MAX_NUM_OF_BLOCKS_IN_BATCH-1); +} + +CPPCODE(template static) void *ltmalloc(size_t size); + +CPPCODE(template ) static void *fetch_from_central_cache(size_t size, ThreadCache *tc, unsigned int sizeClass) +{ + void *p; + if (likely(size-1u <= MAX_BLOCK_SIZE-1u))//<=> if (size <= MAX_BLOCK_SIZE && size != 0) + { + FreeBlock *fb = tc->tempList; + if (fb) + { + assert(tc->counter == (int)batch_size(sizeClass)+1); + tc->counter = 1; + tc->freeList = fb->next; + tc->tempList = NULL; + return fb; + } + + assert(tc->counter == 0 || tc->counter == (int)batch_size(sizeClass)+1); + tc->counter = 1; + + {CentralCache *cc = ¢ralCache[sizeClass]; + SPINLOCK_ACQUIRE(&cc->lock); + if (unlikely(!cc->firstBatch))//no free batch + {no_free_batch:{ + unsigned int batchSize = batch_size(sizeClass)+1; + + if (cc->freeList) + { + assert(cc->freeListSize); + if (likely(cc->freeListSize <= batchSize + 1)) + { + tc->counter = batchSize - cc->freeListSize + 1; + // batchSize = cc->freeListSize; + cc->freeListSize = 0; + fb = cc->freeList; + cc->freeList = NULL; + } + else + { + cc->freeListSize -= batchSize; + fb = cc->freeList; + {FreeBlock *b = cc->freeList; + while (--batchSize) b = b->next; + cc->freeList = b->next; + b->next = NULL;} + } + SPINLOCK_RELEASE(&cc->lock); + tc->freeList = fb->next; + init_pthread_destructor();//this call must be placed carefully to allow recursive memory allocation from pthread_key_create (in case when ltalloc replaces the system malloc) + return fb; + } + + {unsigned int blockSize = class_to_size(sizeClass); + if (cc->freeBlocksInLastChunk) + { + char *firstFree = cc->lastChunk; + assert(cc->lastChunk && cc->freeBlocksInLastChunk == (CHUNK_SIZE - ((uintptr_t)cc->lastChunk & (CHUNK_SIZE-1)))/blockSize); + if (cc->freeBlocksInLastChunk < batchSize) { + tc->counter = batchSize - cc->freeBlocksInLastChunk + 1; + batchSize = cc->freeBlocksInLastChunk; + } + cc->freeBlocksInLastChunk -= batchSize; + cc->lastChunk += blockSize * batchSize; + if (cc->freeBlocksInLastChunk == 0) { + assert(((uintptr_t)cc->lastChunk & (CHUNK_SIZE-1)) == 0); + cc->lastChunk = ((char**)cc->lastChunk)[-1]; + if (cc->lastChunk) + cc->freeBlocksInLastChunk = (CHUNK_SIZE - ((uintptr_t)cc->lastChunk & (CHUNK_SIZE-1)))/blockSize; + } + SPINLOCK_RELEASE(&cc->lock); + fb = (FreeBlock*)firstFree; + while (--batchSize) + firstFree = (char*)(((FreeBlock*)firstFree)->next = (FreeBlock*)(firstFree + blockSize)); + ((FreeBlock*)firstFree)->next = NULL; + tc->freeList = fb->next; + init_pthread_destructor(); + return fb; + } + + //Allocate new chunk + SPINLOCK_RELEASE(&cc->lock);//release lock for a while + + SPINLOCK_ACQUIRE(&pad.lock); + if (pad.freeChunk) + { + p = pad.freeChunk; + pad.freeChunk = *(void**)p; + pad.size -= CHUNK_SIZE; + SPINLOCK_RELEASE(&pad.lock); + ((char**)((char*)p + CHUNK_SIZE))[-1] = 0; + } else { + SPINLOCK_RELEASE(&pad.lock); + p = sys_aligned_alloc(CHUNK_SIZE, CHUNK_SIZE); + if (unlikely(!p)) { CPPCODE(if (throw_) throw std::bad_alloc(); else) return NULL; } + } + +#define CHUNK_IS_SMALL unlikely(sizeClass < get_size_class(2*sizeof(void*))) + {unsigned int numBlocksInChunk = (CHUNK_SIZE - (CHUNK_IS_SMALL ? sizeof(ChunkSm) : sizeof(Chunk)))/blockSize; +#ifndef _WIN32 + //intptr_t sz = ((CHUNK_SIZE - numBlocksInChunk*blockSize) & ~(page_size()-1)) - page_size(); + //if (sz > 0) mprotect((char*)p + page_size(), sz, PROT_NONE);//munmap((char*)p + page_size(), sz);//to make possible unmapping, we need to be more careful when returning memory to the system, not simply VMFREE(firstFreeChunk, CHUNK_SIZE), so let there be just mprotect +#endif + assert(((char**)((char*)p + CHUNK_SIZE))[-1] == 0);//assume that allocated memory is always zero filled (on first access); it is better not to zero it explicitly because it will lead to allocation of physical page which may never needed otherwise + if (numBlocksInChunk < batchSize) { + tc->counter = batchSize - numBlocksInChunk + 1; + batchSize = numBlocksInChunk; + } + + //Prepare chunk + ((Chunk*)p)->sizeClass = sizeClass; + {char *firstFree = (char*)p + CHUNK_SIZE - numBlocksInChunk*blockSize;//blocks in chunk are located in such way to achieve a maximum possible alignment + fb = (FreeBlock*)firstFree; + {int n = batchSize; while (--n) + firstFree = (char*)(((FreeBlock*)firstFree)->next = (FreeBlock*)(firstFree + blockSize));} + ((FreeBlock*)firstFree)->next = NULL; + firstFree += blockSize; + + SPINLOCK_ACQUIRE(&cc->lock); + if ((uintptr_t)p < cc->minChunkAddr || !cc->minChunkAddr) cc->minChunkAddr = (uintptr_t)p; + if ((uintptr_t)p > cc->maxChunkAddr ) cc->maxChunkAddr = (uintptr_t)p; + + if (CHUNK_IS_SMALL)//special handling for smallest blocks of size = sizeof(void*) + { + ChunkSm *cs = (ChunkSm*)p; + cs->numBatches = 0; + //Insert new chunk right after chunkWithFreeBatches + cs->prev = cc->chunkWithFreeBatches; + if (cc->chunkWithFreeBatches) { + cs->next = cc->chunkWithFreeBatches->next; + if (cc->chunkWithFreeBatches->next) cc->chunkWithFreeBatches->next->prev = cs; + cc->chunkWithFreeBatches->next = cs; + } else { + cs->next = NULL; + cc->chunkWithFreeBatches = cs; + } + } + + if (unlikely(cc->freeBlocksInLastChunk))//so happened that other thread have already allocated chunk for the same size class while the lock was released + { + //Hook pointer to the current lastChunk at the end of new chunk (another way is just put all blocks to cc->freeList which is much less effecient) + ((char**)(((uintptr_t)firstFree & ~(CHUNK_SIZE-1)) + CHUNK_SIZE))[-1] = cc->lastChunk; + } + cc->freeBlocksInLastChunk = numBlocksInChunk - batchSize; + cc->lastChunk = firstFree; + }}}}} + else { + if (!CHUNK_IS_SMALL)//smallest blocks of size = sizeof(void*) are handled specially + { + fb = cc->firstBatch; + cc->firstBatch = fb->nextBatch; + } + else//size of block = sizeof(void*) + { + ChunkSm *cs = cc->chunkWithFreeBatches; + if (unlikely(cs->numBatches == 0)) + { + if (unlikely(cs->prev == NULL)) goto no_free_batch; + cs = cc->chunkWithFreeBatches = cs->prev; + assert(cs->numBatches == NUM_OF_BATCHES_IN_CHUNK_SM); + } + fb = cs->batches[--cs->numBatches]; + } + } + SPINLOCK_RELEASE(&cc->lock);} + tc->freeList = fb->next; + init_pthread_destructor(); + return fb; + } + else//allocate block directly from the system + { + if (unlikely(size == 0)) return ltmalloc CPPCODE()(1);//return NULL;//doing this check here is better than on the top level + + size = (size + CHUNK_SIZE-1) & ~(CHUNK_SIZE-1); + p = sys_aligned_alloc(CHUNK_SIZE, size); +#ifndef _WIN32 + if (p) { + SPINLOCK_ACQUIRE(&ptrieLock); + PTrieNode *newNode; + if (ptrieFreeNodesList) + ptrieFreeNodesList = *(PTrieNode**)(newNode = ptrieFreeNodesList); + else if (ptrieNewAllocatedPage) { + newNode = ptrieNewAllocatedPage; + if (!((uintptr_t)++ptrieNewAllocatedPage & (page_size()-1))) + ptrieNewAllocatedPage = ((PTrieNode**)ptrieNewAllocatedPage)[-1]; + } else { + SPINLOCK_RELEASE(&ptrieLock); + newNode = (PTrieNode*)VMALLOC(page_size()); + if (unlikely(!newNode)) { CPPCODE(if (throw_) throw std::bad_alloc(); else) return NULL; } + assert(((char**)((char*)newNode + page_size()))[-1] == 0); + SPINLOCK_ACQUIRE(&ptrieLock); + ((PTrieNode**)((char*)newNode + page_size()))[-1] = ptrieNewAllocatedPage;//in case if other thread also have just allocated a new page + ptrieNewAllocatedPage = newNode + 1; + } + ptrie_insert((uintptr_t)p, size, newNode); + SPINLOCK_RELEASE(&ptrieLock); + } +#endif + CPPCODE(if (throw_) if (unlikely(!p)) throw std::bad_alloc();) + return p; + } +} + +CPPCODE(template static) void *ltmalloc(size_t size) +{ + unsigned int sizeClass = get_size_class(size); + ThreadCache *tc = &threadCache[sizeClass]; + FreeBlock *fb = tc->freeList; + if (likely(fb)) + { + tc->freeList = fb->next; + tc->counter++; + return fb; + } + else + return fetch_from_central_cache CPPCODE()(size, tc, sizeClass); +} +CPPCODE(void *ltmalloc(size_t size) {return ltmalloc(size);})//for possible external usage + +static void add_batch_to_central_cache(CentralCache *cc, unsigned int sizeClass, FreeBlock *batch) +{ + if (!CHUNK_IS_SMALL) + { + batch->nextBatch = cc->firstBatch; + cc->firstBatch = batch; + } + else + { + ChunkSm *cs = cc->chunkWithFreeBatches; + if (unlikely(cs->numBatches == NUM_OF_BATCHES_IN_CHUNK_SM)) + { + cs = cc->chunkWithFreeBatches = cc->chunkWithFreeBatches->next; + assert(cs && cs->numBatches == 0); + } + cs->batches[cs->numBatches++] = batch; + } +} + +static NOINLINE void move_to_central_cache(ThreadCache *tc, unsigned int sizeClass) +{ + init_pthread_destructor();//needed for cases when freed memory was allocated in the other thread and no alloc was called in this thread till its termination + + tc->counter = batch_size(sizeClass); + if (tc->tempList)//move temp list to the central cache + { + CentralCache *cc = ¢ralCache[sizeClass]; + SPINLOCK_ACQUIRE(&cc->lock); + add_batch_to_central_cache(cc, sizeClass, tc->tempList); + SPINLOCK_RELEASE(&cc->lock); + } +// else if (unlikely(!tc->freeList))//this is a first call (i.e. when counter = 0) - just initialization of counter needed +// { +// tc->counter--; +// return; +// } + + tc->tempList = tc->freeList; + tc->freeList = NULL; +} + +void ltfree(void *p) +{ + if (likely((uintptr_t)p & (CHUNK_SIZE-1))) + { + unsigned int sizeClass = ((Chunk*)((uintptr_t)p & ~(CHUNK_SIZE-1)))->sizeClass; + ThreadCache *tc = &threadCache[sizeClass]; + + if (unlikely(--tc->counter < 0)) + move_to_central_cache(tc, sizeClass); + + ((FreeBlock*)p)->next = tc->freeList; + tc->freeList = (FreeBlock*)p; + } + else + sys_free(p); +} + +size_t ltmsize(void *p) +{ + if (likely((uintptr_t)p & (CHUNK_SIZE-1))) + { + return class_to_size(((Chunk*)((uintptr_t)p & ~(CHUNK_SIZE-1)))->sizeClass); + } + else + { + if (p == NULL) return 0; +#ifdef _WIN32 + {MEMORY_BASIC_INFORMATION mi; + VirtualQuery(p, &mi, sizeof(mi)); + return mi.RegionSize;} +#else + SPINLOCK_ACQUIRE(&ptrieLock); + size_t size = ptrie_lookup((uintptr_t)p); + SPINLOCK_RELEASE(&ptrieLock); + return size; +#endif + } +} + +static void release_thread_cache(void *p) +{ + unsigned int sizeClass = 0; (void)p; + for (;sizeClass < NUMBER_OF_SIZE_CLASSES; sizeClass++) + { + ThreadCache *tc = &threadCache[sizeClass]; + if (tc->freeList || tc->tempList) + { + FreeBlock *tail = tc->freeList; + unsigned int freeListSize = 1; + CentralCache *cc = ¢ralCache[sizeClass]; + + if (tail) + while (tail->next)//search for end of list + tail = tail->next, freeListSize++; + + SPINLOCK_ACQUIRE(&cc->lock); + if (tc->tempList) + add_batch_to_central_cache(cc, sizeClass, tc->tempList); + if (tc->freeList) {//append tc->freeList to cc->freeList + tail->next = cc->freeList; + cc->freeList = tc->freeList; + assert(freeListSize == batch_size(sizeClass)+1 - tc->counter); + cc->freeListSize += freeListSize; + } + SPINLOCK_RELEASE(&cc->lock); + } + } +} + +void ltsqueeze(size_t padsz) +{ + unsigned int sizeClass = get_size_class(2*sizeof(void*));//skip small chunks because corresponding batches can not be efficiently detached from the central cache (if that becomes relevant, may be it worths to reimplement batches for small chunks from array to linked lists) + for (;sizeClass < NUMBER_OF_SIZE_CLASSES; sizeClass++) + { + CentralCache *cc = ¢ralCache[sizeClass]; + if (cc->maxChunkAddr - cc->minChunkAddr <= CHUNK_SIZE)//preliminary check without lock (assume that writing to minChunkAddr/maxChunkAddr is atomic) + continue; + + SPINLOCK_ACQUIRE(&cc->lock); + if (cc->maxChunkAddr - cc->minChunkAddr <= CHUNK_SIZE) {//quick check for theoretical possibility that at least one chunk is totally free + SPINLOCK_RELEASE(&cc->lock); + continue; + } + {uintptr_t minChunkAddr = cc->minChunkAddr; + size_t bufferSize = ((cc->maxChunkAddr - minChunkAddr) / CHUNK_SIZE + 1) * sizeof(short); + //Quickly detach all batches of the current size class from the central cache + unsigned int freeListSize = cc->freeListSize; + FreeBlock *firstBatch = cc->firstBatch, *freeList = cc->freeList; + cc->firstBatch = NULL; + cc->freeList = NULL; + cc->freeListSize = 0; + SPINLOCK_RELEASE(&cc->lock); + + //1. Find out chunks with only free blocks via a simple counting the number of free blocks in each chunk + {char buffer[32*1024];//enough for 1GB address space + unsigned short *inChunkFreeBlocks = (unsigned short*)(bufferSize <= sizeof(buffer) ? memset(buffer, 0, bufferSize) : VMALLOC(bufferSize)); + unsigned int numBlocksInChunk = (CHUNK_SIZE - (/*CHUNK_IS_SMALL ? sizeof(ChunkSm) : */sizeof(Chunk)))/class_to_size(sizeClass); + FreeBlock **pbatch, *block, **pblock; + Chunk *firstFreeChunk = NULL; + assert(numBlocksInChunk < (1U<<(sizeof(short)*8)));//in case if CHUNK_SIZE is too big that total count of blocks in it doesn't fit at short type (...may be use static_assert instead?) + if (inChunkFreeBlocks)//consider VMALLOC can fail + { + for (pbatch = &firstBatch; *pbatch; pbatch = &(*pbatch)->nextBatch) + for (block = *pbatch; block; block = block->next) +#define FREE_BLOCK(block) \ + if (++inChunkFreeBlocks[((uintptr_t)block - minChunkAddr) / CHUNK_SIZE] == numBlocksInChunk)/*chunk is totally free*/\ + {\ + Chunk *chunk = (Chunk*)((uintptr_t)block & ~(CHUNK_SIZE-1));\ + assert(chunk->sizeClass == sizeClass);/*just in case check before overwriting this info*/\ + *(Chunk**)chunk = firstFreeChunk;/*put nextFreeChunk pointer right at the beginning of Chunk as there are always must be a space for one pointer before first memory block*/\ + firstFreeChunk = chunk;\ + } + FREE_BLOCK(block) + for (pblock = &freeList; *pblock; pblock = &(*pblock)->next) + FREE_BLOCK(*pblock) +#undef FREE_BLOCK + } + else { + for (pbatch = &firstBatch; *pbatch; pbatch = &(*pbatch)->nextBatch); + for (pblock = &freeList; *pblock; pblock = &(*pblock)->next); + } + + if (firstFreeChunk)//is anything to release + { + //2. Unlink all matching blocks from the corresponding free lists + FreeBlock *additionalBatchesList = NULL, *additionalBlocksList = NULL, **abatch = &additionalBatchesList, **ablock = &additionalBlocksList; + unsigned int additionalBlocksListSize = 0, batchSize = batch_size(sizeClass)+1; + for (pbatch = &firstBatch; *pbatch;) + { + for (block = *pbatch; block; block = block->next) + if (inChunkFreeBlocks[((uintptr_t)block - minChunkAddr) / CHUNK_SIZE] == numBlocksInChunk)//if at least one block belongs to a releasable chunk, then this batch should be handled specially + { + FreeBlock *nextBatch = (*pbatch)->nextBatch; + for (block = *pbatch; block;)//re-add blocks of not-for-release chunks and organize them into another batches' list (to join it with the main later) + if (inChunkFreeBlocks[((uintptr_t)block - minChunkAddr) / CHUNK_SIZE] != numBlocksInChunk)//skip matching-for-release blocks + { + *ablock = block; + do//this loop needed only to minimize memory write operations, otherwise a simpler approach could be used (like in the next loop below) + { + ablock = &block->next; + block = block->next; + if (++additionalBlocksListSize == batchSize) + { + abatch = &(*abatch = additionalBlocksList)->nextBatch; + *abatch = NULL; + *ablock = NULL; + ablock = &additionalBlocksList; + additionalBlocksList = NULL; + additionalBlocksListSize = 0; + break;//to force *ablock = block; for starting a new batch + } + } while (block && inChunkFreeBlocks[((uintptr_t)block - minChunkAddr) / CHUNK_SIZE] != numBlocksInChunk); + } + else + block = block->next; + *ablock = NULL; + *pbatch = nextBatch;//unlink batch + goto continue_; + } + pbatch = &(*pbatch)->nextBatch; +continue_:; + } + for (block = freeList; block;) + if (inChunkFreeBlocks[((uintptr_t)block - minChunkAddr) / CHUNK_SIZE] != numBlocksInChunk) + { + //*pblock = (*pblock)->next, freeListSize--;//unlink block + ablock = &(*ablock = block)->next; + block = block->next; + *ablock = NULL; + if (++additionalBlocksListSize == batchSize) + { + abatch = &(*abatch = additionalBlocksList)->nextBatch; + *abatch = NULL; + ablock = &additionalBlocksList; + additionalBlocksList = NULL; + additionalBlocksListSize = 0; + } + } + else + block = block->next; + //Add additional lists + *abatch = *pbatch; + *pbatch = additionalBatchesList; + pblock = ablock; + freeList = additionalBlocksList; + freeListSize = additionalBlocksListSize; + + //Return back all left not-for-release blocks to the central cache as quickly as possible (as other threads may want to allocate a new memory) +#define GIVE_LISTS_BACK_TO_CC \ + SPINLOCK_ACQUIRE(&cc->lock);\ + *pbatch = cc->firstBatch;\ + cc->firstBatch = firstBatch;\ + *pblock = cc->freeList;\ + cc->freeList = freeList;\ + cc->freeListSize += freeListSize;\ + SPINLOCK_RELEASE(&cc->lock);\ + if (bufferSize > sizeof(buffer)) VMFREE(inChunkFreeBlocks, bufferSize);//this better to do before 3. as kernel is likely optimized for release of just allocated range + GIVE_LISTS_BACK_TO_CC + + if (padsz) + { + SPINLOCK_ACQUIRE(&pad.lock); + if (pad.size < padsz) + { + Chunk *first = firstFreeChunk, **c; + do//put off free chunks up to a specified pad size + { + c = (Chunk**)firstFreeChunk; + firstFreeChunk = *c; + pad.size += CHUNK_SIZE; + } while (pad.size < padsz && firstFreeChunk); + *c = (Chunk*)pad.freeChunk; + pad.freeChunk = first; + } + SPINLOCK_RELEASE(&pad.lock); + } + + //3. Return memory to the system + while (firstFreeChunk) + { + Chunk *nextFreeChunk = *(Chunk**)firstFreeChunk; + VMFREE(firstFreeChunk, CHUNK_SIZE); + firstFreeChunk = nextFreeChunk; + } + } + else//nothing to release - just return batches back to the central cache + { + GIVE_LISTS_BACK_TO_CC +#undef GIVE_LISTS_BACK_TO_CC + }}} + } +} + +#if defined(__cplusplus) && !defined(LTALLOC_DISABLE_OPERATOR_NEW_OVERRIDE) +void *operator new (size_t size) throw(std::bad_alloc) {return ltmalloc (size);} +void *operator new (size_t size, const std::nothrow_t&) throw() {return ltmalloc(size);} +void *operator new[](size_t size) throw(std::bad_alloc) {return ltmalloc (size);} +void *operator new[](size_t size, const std::nothrow_t&) throw() {return ltmalloc(size);} + +void operator delete (void* p) throw() {ltfree(p);} +void operator delete (void* p, const std::nothrow_t&) throw() {ltfree(p);} +void operator delete[](void* p) throw() {ltfree(p);} +void operator delete[](void* p, const std::nothrow_t&) throw() {ltfree(p);} +#endif + +/* @r-lyeh's { */ +#include +void *ltcalloc(size_t elems, size_t size) { + size *= elems; + return memset( ltmalloc( size ), 0, size ); +} +void *ltmemalign( size_t align, size_t size ) { + return --align, ltmalloc( (size+align)&~align ); +} +void *ltrealloc( void *ptr, size_t sz ) { + if( !ptr ) return ltmalloc( sz ); + if( !sz ) return ltfree( ptr ), (void *)0; + size_t osz = ltmsize( ptr ); + if( sz <= osz ) { + return ptr; + } + void *nptr = memcpy( ltmalloc(sz), ptr, osz ); + ltfree( ptr ); + +#ifdef LTALLOC_AUTO_GC_INTERVAL + /* this is kind of compromise; the following timer is to guarantee + that memory gets wiped out at least every given seconds between consecutive + ltrealloc() calls (I am assuming frequency usage for ltrealloc() is smaller + than ltmalloc() or ltfree() too) - @r-lyeh */ + clock_t now = clock(); + static clock_t then = now; + if( ( double(now - then) / CLOCKS_PER_SEC ) > LTALLOC_AUTO_GC_INTERVAL ) { + ltsqueeze(0); + } + then = now; +#endif + + return nptr; +} +/* } */ diff --git a/experimental/ltalloc.h b/experimental/ltalloc.h new file mode 100644 index 0000000..22d6f2e --- /dev/null +++ b/experimental/ltalloc.h @@ -0,0 +1,14 @@ +#include /*a more portable size_t definition than stddef.h itself*/ +#ifdef __cplusplus +extern "C" { +#endif +void* ltmalloc(size_t); +void ltfree(void*); +void* ltrealloc( void *, size_t ); +void* ltcalloc( size_t, size_t ); +void* ltmemalign( size_t, size_t ); +void ltsqueeze(size_t pad); /*return memory to system (see README.md)*/ +size_t ltmsize(void*); +#ifdef __cplusplus +} +#endif diff --git a/experimental/ltalloc.hpp b/experimental/ltalloc.hpp new file mode 100644 index 0000000..cd6aee1 --- /dev/null +++ b/experimental/ltalloc.hpp @@ -0,0 +1,59 @@ +// based on code by Jerry Coffin (most likely Public Domain) +// - rlyeh + +#pragma once + +#include +#include +#include + +#include "ltalloc.h" + +namespace lt { +template +struct allocator { + typedef size_t size_type; + typedef ptrdiff_t difference_type; + typedef T* pointer; + typedef const T* const_pointer; + typedef T& reference; + typedef const T& const_reference; + typedef T value_type; + + template struct rebind { typedef allocator other; }; + allocator() throw() {} + allocator(const allocator&) throw() {} + + template allocator(const allocator&) throw(){} + + ~allocator() throw() {} + + pointer address(reference x) const { return &x; } + const_pointer address(const_reference x) const { return &x; } + + pointer allocate(size_type s, void const * = 0) { + if (0 == s) + return NULL; + pointer temp = (pointer)ltmalloc(s * sizeof(T)); + if (temp == NULL) + throw std::bad_alloc(); + return temp; + } + + void deallocate(pointer p, size_type) { + ltfree(p); + } + + size_type max_size() const throw() { + return std::numeric_limits::max() / sizeof(T); + } + + void construct(pointer p, const T& val) { + new((void *)p) T(val); + } + + void destroy(pointer p) { + p->~T(); + } +}; +} diff --git a/experimental/optimized-parse.cc b/experimental/optimized-parse.cc index ef6b4fb..4e1a8eb 100644 --- a/experimental/optimized-parse.cc +++ b/experimental/optimized-parse.cc @@ -26,6 +26,8 @@ #include // C++11 #include // C++11 +#include "ltalloc.hpp" + // ---------------------------------------------------------------------------- // Small vector class useful for multi-threaded environment. // @@ -597,7 +599,8 @@ typedef struct float tx, ty; // for f - std::vector f; + std::vector > f; + //std::vector f; const char* group_name; const char* object_name; @@ -858,7 +861,7 @@ static inline bool is_line_ending(const char* p, size_t i, size_t end_i) return false; } -void parse(std::vector &vertices, std::vector &normals, std::vector &texcoords, std::vector &faces, const char* buf, size_t len, int req_num_threads) +bool parse(std::vector> &vertices, std::vector> &normals, std::vector> &texcoords, std::vector> &faces, const char* buf, size_t len, int req_num_threads) { vertices.clear(); @@ -866,9 +869,9 @@ void parse(std::vector &vertices, std::vector &normals, std::vecto texcoords.clear(); faces.clear(); - if (len < 1) return; + if (len < 1) return false; - std::vector newline_marker(len, 0); + std::cout << "parse" << std::endl; auto num_threads = (req_num_threads < 0) ? std::thread::hardware_concurrency() : req_num_threads; num_threads = std::max(1, std::min(static_cast(num_threads), kMaxThreads)); @@ -878,21 +881,27 @@ void parse(std::vector &vertices, std::vector &normals, std::vecto std::atomic newline_counter(0); - std::vector line_infos[kMaxThreads]; + std::vector> line_infos[kMaxThreads]; for (size_t t = 0; t < static_cast(num_threads); t++) { // Pre allocate enough memory. len / 1024 / num_threads is just a heuristic value. line_infos[t].reserve(len / 1024 / num_threads); } + std::chrono::duration ms_linedetection; + std::chrono::duration ms_alloc; + std::chrono::duration ms_parse; + std::chrono::duration ms_merge; + + // 1. Find '\n' and create line data. { - std::vector workers; + StackVector workers; auto start_time = std::chrono::high_resolution_clock::now(); auto chunk_size = len / num_threads; for (size_t t = 0; t < static_cast(num_threads); t++) { - workers.push_back(std::thread([&, t]() { + workers->push_back(std::thread([&, t]() { auto start_idx = (t + 0) * chunk_size; auto end_idx = std::min((t + 1) * chunk_size, len - 1); if (t == static_cast((num_threads - 1))) { @@ -940,14 +949,13 @@ void parse(std::vector &vertices, std::vector &normals, std::vecto })); } - for (auto &t : workers) { - t.join(); + for (size_t t = 0; t < workers->size(); t++) { + workers[t].join(); } auto end_time = std::chrono::high_resolution_clock::now(); - std::chrono::duration ms = end_time - start_time; - //std::cout << "line detection:" << ms.count() << " ms\n"; + ms_linedetection = end_time - start_time; } auto line_sum = 0; @@ -957,26 +965,30 @@ void parse(std::vector &vertices, std::vector &normals, std::vecto } //std::cout << "# of lines = " << line_sum << std::endl; - std::vector commands[kMaxThreads]; + //std::vector commands[kMaxThreads]; + std::vector > commands[kMaxThreads]; - for (size_t t = 0; t < num_threads; t++) { - commands[t].reserve(line_infos[t].size()); - } + // 2. allocate buffer + auto t_alloc_start = std::chrono::high_resolution_clock::now(); + { - auto t2 = std::chrono::high_resolution_clock::now(); - std::chrono::duration ms1 = t2 - t1; + for (size_t t = 0; t < num_threads; t++) { + commands[t].reserve(line_infos[t].size()); + } - //std::cout << ms1.count() << " ms\n"; + } CommandCount command_count[kMaxThreads]; + ms_alloc = std::chrono::high_resolution_clock::now() - t_alloc_start; + // 2. parse each line in parallel. { - std::vector workers; + StackVector workers; auto t_start = std::chrono::high_resolution_clock::now(); for (size_t t = 0; t < num_threads; t++) { - workers.push_back(std::thread([&, t]() { + workers->push_back(std::thread([&, t]() { for (size_t i = 0; i < line_infos[t].size(); i++) { Command command; @@ -998,14 +1010,13 @@ void parse(std::vector &vertices, std::vector &normals, std::vecto })); } - for (auto &t : workers) { - t.join(); + for (size_t t = 0; t < workers->size(); t++) { + workers[t].join(); } auto t_end = std::chrono::high_resolution_clock::now(); - std::chrono::duration ms = t_end - t_start; - //std::cout << "parse:" << ms.count() << " ms\n"; + ms_parse = t_end - t_start; } auto command_sum = 0; @@ -1030,15 +1041,16 @@ void parse(std::vector &vertices, std::vector &normals, std::vecto //std::cout << "# vt " << num_vt << std::endl; //std::cout << "# f " << num_f << std::endl; - vertices.reserve(num_v * 3); - normals.reserve(num_vn * 3); - texcoords.reserve(num_vt * 2); - faces.reserve(num_f); - - // merge + // 4. merge + // @todo { parallelize merge. } { auto t_start = std::chrono::high_resolution_clock::now(); + vertices.reserve(num_v * 3); + normals.reserve(num_vn * 3); + texcoords.reserve(num_vt * 2); + faces.reserve(num_f); + for (size_t t = 0; t < num_threads; t++) { for (size_t i = 0; i < commands[t].size(); i++) { if (commands[t][i].type == COMMAND_EMPTY) { @@ -1071,15 +1083,15 @@ void parse(std::vector &vertices, std::vector &normals, std::vecto int v_idx = fixIndex(i0.v_idx, v_size); int vn_idx = fixIndex(i0.vn_idx, vn_size); int vt_idx = fixIndex(i0.vt_idx, vt_size); - faces.emplace_back(vertex_index(v_idx, vt_idx, vn_idx)); + faces.emplace_back(std::move(vertex_index(v_idx, vt_idx, vn_idx))); v_idx = fixIndex(i1.v_idx, v_size); vn_idx = fixIndex(i1.vn_idx, vn_size); vt_idx = fixIndex(i1.vt_idx, vt_size); - faces.emplace_back(vertex_index(v_idx, vt_idx, vn_idx)); + faces.emplace_back(std::move(vertex_index(v_idx, vt_idx, vn_idx))); v_idx = fixIndex(i2.v_idx, v_size); vn_idx = fixIndex(i2.vn_idx, vn_size); vt_idx = fixIndex(i2.vt_idx, vt_size); - faces.emplace_back(vertex_index(v_idx, vt_idx, vn_idx)); + faces.emplace_back(std::move(vertex_index(v_idx, vt_idx, vn_idx))); } } } @@ -1087,20 +1099,24 @@ void parse(std::vector &vertices, std::vector &normals, std::vecto } auto t_end = std::chrono::high_resolution_clock::now(); - std::chrono::duration ms = t_end - t_start; - //std::cout << "merge:" << ms.count() << " ms\n"; + ms_merge = t_end - t_start; } auto t4 = std::chrono::high_resolution_clock::now(); std::chrono::duration ms_total = t4 - t1; std::cout << "total parsing time: " << ms_total.count() << " ms\n"; + std::cout << " line detection :" << ms_linedetection.count() << " ms\n"; + std::cout << " alloc buf :" << ms_alloc.count() << " ms\n"; + std::cout << " parse :" << ms_parse.count() << " ms\n"; + std::cout << " merge :" << ms_merge.count() << " ms\n"; std::cout << "# of vertices = " << vertices.size() << std::endl; std::cout << "# of normals = " << normals.size() << std::endl; std::cout << "# of texcoords = " << texcoords.size() << std::endl; std::cout << "# of faces = " << faces.size() << std::endl; + return true; } #ifdef CONSOLE_TEST diff --git a/experimental/premake4.lua b/experimental/premake4.lua index 4571f6a..321ab19 100644 --- a/experimental/premake4.lua +++ b/experimental/premake4.lua @@ -12,7 +12,7 @@ solution "objview" kind "ConsoleApp" language "C++" - files { "viewer.cc", "trackball.cc" } + files { "viewer.cc", "trackball.cc", "ltalloc.cc" } includedirs { "./" } includedirs { "../../" } diff --git a/experimental/viewer.cc b/experimental/viewer.cc index fa44320..5a26518 100644 --- a/experimental/viewer.cc +++ b/experimental/viewer.cc @@ -142,6 +142,7 @@ const char *mmap_file(size_t *len, const char* filename) bool gz_load(std::vector* buf, const char* filename) { +#ifdef ENABLE_ZLIB gzFile file; file = gzopen (filename, "r"); if (! file) { @@ -174,6 +175,9 @@ bool gz_load(std::vector* buf, const char* filename) } gzclose (file); return true; +#else + return false; +#endif } const char* get_file_data(size_t *len, const char* filename) @@ -184,10 +188,8 @@ const char* get_file_data(size_t *len, const char* filename) size_t data_len = 0; const char* data = nullptr; -#if defined(ENABLE_ZLIB) if (strcmp(ext, ".gz") == 0) { // gzipped data. - printf("compressed\n"); std::vector buf; bool ret = gz_load(&buf, filename); @@ -201,9 +203,6 @@ const char* get_file_data(size_t *len, const char* filename) } } else { -#else - { -#endif data = mmap_file(&data_len, filename); } @@ -213,12 +212,12 @@ const char* get_file_data(size_t *len, const char* filename) } -bool LoadObjAndConvert(float bmin[3], float bmax[3], const char* filename) +bool LoadObjAndConvert(float bmin[3], float bmax[3], const char* filename, int num_threads) { - std::vector vertices; - std::vector normals; - std::vector texcoords; - std::vector faces; + std::vector> vertices; + std::vector> normals; + std::vector> texcoords; + std::vector> faces; size_t data_len = 0; const char* data = get_file_data(&data_len, filename); @@ -227,7 +226,7 @@ bool LoadObjAndConvert(float bmin[3], float bmax[3], const char* filename) return false; } printf("filesize: %d\n", (int)data_len); - parse(vertices, normals, texcoords, faces, data, data_len, /* num_threads */-1); + bool ret = parse(vertices, normals, texcoords, faces, data, data_len, num_threads); bmin[0] = bmin[1] = bmin[2] = std::numeric_limits::max(); bmax[0] = bmax[1] = bmax[2] = -std::numeric_limits::max(); @@ -495,15 +494,42 @@ int main(int argc, char **argv) return 0; } + bool benchmark_only = false; + int num_threads = -1; + if (argc > 2) { + num_threads = atoi(argv[2]); + } + + if (argc > 3) { + benchmark_only = true; + } + + if (benchmark_only) { + + std::vector> vertices; + std::vector> normals; + std::vector> texcoords; + std::vector> faces; + + size_t data_len = 0; + const char* data = get_file_data(&data_len, argv[1]); + if (data == nullptr) { + exit(-1); + return false; + } + printf("filesize: %d\n", (int)data_len); + bool ret = parse(vertices, normals, texcoords, faces, data, data_len, num_threads); + + return ret; + } + Init(); - if(!glfwInit()){ std::cerr << "Failed to initialize GLFW." << std::endl; return -1; } - window = glfwCreateWindow(width, height, "Obj viewer", NULL, NULL); if(window == NULL){ @@ -530,7 +556,7 @@ int main(int argc, char **argv) reshapeFunc(window, width, height); float bmin[3], bmax[3]; - if (false == LoadObjAndConvert(bmin, bmax, argv[1])) { + if (false == LoadObjAndConvert(bmin, bmax, argv[1], num_threads)) { return -1; } -- cgit v1.2.3 From 8880438c361ce70e0ff1fe869f3225dfbb0b794d Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Tue, 24 May 2016 12:58:31 +0900 Subject: Optimzie face generation. --- experimental/optimized-parse.cc | 75 +++++++++++++++++++++++++++++++---------- 1 file changed, 58 insertions(+), 17 deletions(-) diff --git a/experimental/optimized-parse.cc b/experimental/optimized-parse.cc index 4e1a8eb..c5c8d04 100644 --- a/experimental/optimized-parse.cc +++ b/experimental/optimized-parse.cc @@ -623,7 +623,7 @@ struct CommandCount } }; -static bool parseLine(Command *command, const char *p, size_t p_len) +static bool parseLine(Command *command, const char *p, size_t p_len, bool triangulate = true) { char linebuf[4096]; assert(p_len < 4095); @@ -690,6 +690,8 @@ static bool parseLine(Command *command, const char *p, size_t p_len) //token += strspn(token, " \t"); skip_space(&token); + StackVector f; + while (!IS_NEW_LINE(token[0])) { vertex_index vi = parseRawTriple(&token); //printf("v = %d, %d, %d\n", vi.v_idx, vi.vn_idx, vi.vt_idx); @@ -700,11 +702,31 @@ static bool parseLine(Command *command, const char *p, size_t p_len) //token += n; skip_space_and_cr(&token); - command->f.push_back(vi); + f->push_back(vi); } command->type = COMMAND_F; + if (triangulate) { + vertex_index i0 = f[0]; + vertex_index i1(-1); + vertex_index i2 = f[1]; + + for (size_t k = 2; k < f->size(); k++) { + i1 = i2; + i2 = f[k]; + command->f.emplace_back(i0); + command->f.emplace_back(i1); + command->f.emplace_back(i2); + } + + } else { + + for (size_t k = 0; k < f->size(); k++) { + command->f.emplace_back(f[k]); + } + } + return true; } @@ -965,8 +987,8 @@ bool parse(std::vector> &vertices, std::vectormaterial_name = .insert(command->material_name->end(), namebuf, namebuf + strlen(namebuf)); //command->material_name->push_back('\0'); + skip_space(&token); + command->material_name = token; + command->material_name_len = length_until_newline(token, p_len - (token - p)); command->type = COMMAND_USEMTL; return true; } -#if 0 // load mtl if ((0 == strncmp(token, "mtllib", 6)) && IS_SPACE((token[6]))) { - char namebuf[TINYOBJ_SSCANF_BUFFER_SIZE]; + // By specification, `mtllib` should be appaar only once in .obj + char namebuf[8192]; token += 7; #ifdef _MSC_VER sscanf_s(token, "%s", namebuf, (unsigned)_countof(namebuf)); @@ -747,28 +764,28 @@ static bool parseLine(Command *command, const char *p, size_t p_len) sscanf(token, "%s", namebuf); #endif - std::string err_mtl; - std::vector materials; - bool ok = (*readMatFn)(namebuf, &materials, &material_map, &err_mtl); - if (err) { - (*err) += err_mtl; - } + //std::string err_mtl; + //std::vector materials; + //bool ok = (*readMatFn)(namebuf, &materials, &material_map, &err_mtl); + //if (err) { + // (*err) += err_mtl; + //} - if (!ok) { - return false; - } + //if (!ok) { + // return false; + //} - if (callback.mtllib_cb) { - callback.mtllib_cb(user_data, &materials.at(0), - static_cast(materials.size())); - } + //if (callback.mtllib_cb) { + // callback.mtllib_cb(user_data, &materials.at(0), + // static_cast(materials.size())); + //} - continue; + return true; } -#endif // group name if (token[0] == 'g' && IS_SPACE((token[1]))) { +#if 0 ShortString names[16]; int num_names = 0; @@ -782,35 +799,24 @@ static bool parseLine(Command *command, const char *p, size_t p_len) assert(num_names > 0); - //int name_idx = 0; + int name_idx = 0; // names[0] must be 'g', so skip the 0th element. if (num_names > 1) { - //name_idx = 1; + name_idx = 1; } +#endif + token += 2; - //command->group_name->assign(names[name_idx]); + command->group_name = token; + command->group_name_len = length_until_newline(token, p_len - (token - p)); command->type = COMMAND_G; - //if (callback.group_cb) { - // if (names.size() > 1) { - // // create const char* array. - // std::vector tmp(names.size() - 1); - // for (size_t j = 0; j < tmp.size(); j++) { - // tmp[j] = names[j + 1].c_str(); - // } - // callback.group_cb(user_data, &tmp.at(0), - // static_cast(tmp.size())); - - // } else { - // callback.group_cb(user_data, NULL, 0); - // } - - //} return true; } // object name if (token[0] == 'o' && IS_SPACE((token[1]))) { +#if 0 // @todo { multiple object name? } char namebuf[8192]; token += 2; @@ -820,12 +826,12 @@ static bool parseLine(Command *command, const char *p, size_t p_len) sscanf(token, "%s", namebuf); #endif - //if (callback.object_cb) { - // callback.object_cb(user_data, name.c_str()); - //} +#endif + + token += 2; - //command->object_name->insert(command->object_name->end(), namebuf, namebuf + strlen(namebuf)); - //command->object_name->push_back('\0'); + command->object_name = token; + command->object_name_len = length_until_newline(token, p_len - (token - p)); command->type = COMMAND_O; return true; @@ -879,12 +885,10 @@ bool parse(std::vector> &vertices, std::vector newline_counter(0); - std::vector> line_infos[kMaxThreads]; for (size_t t = 0; t < static_cast(num_threads); t++) { - // Pre allocate enough memory. len / 1024 / num_threads is just a heuristic value. - line_infos[t].reserve(len / 1024 / num_threads); + // Pre allocate enough memory. len / 128 / num_threads is just a heuristic value. + line_infos[t].reserve(len / 128 / num_threads); } std::chrono::duration ms_linedetection; @@ -1049,7 +1053,7 @@ bool parse(std::vector> &vertices, std::vector> vertices; std::vector> normals; std::vector> texcoords; - std::vector> faces; + std::vector> faces; size_t data_len = 0; const char* data = get_file_data(&data_len, filename); @@ -324,6 +325,9 @@ bool LoadObjAndConvert(float bmin[3], float bmax[3], const char* filename, int n printf("bmax = %f, %f, %f\n", bmax[0], bmax[1], bmax[2]); return true; +#else + return false; +#endif } void reshapeFunc(GLFWwindow* window, int w, int h) -- cgit v1.2.3 From 5f4a557d693b7b6cc9eb4afd737e3625a2112580 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Wed, 25 May 2016 19:48:25 +0900 Subject: Fix memory bug. --- experimental/optimized-parse.cc | 2 -- experimental/viewer.cc | 9 +++++++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/experimental/optimized-parse.cc b/experimental/optimized-parse.cc index 01df737..5f7ae93 100644 --- a/experimental/optimized-parse.cc +++ b/experimental/optimized-parse.cc @@ -273,8 +273,6 @@ static inline int length_until_newline(const char *token, int n) { int len = 0; - assert(n < 4095); - // Assume token[n-1] = '\0' for (len = 0; len < n -1; len++) { if (token[len] == '\n') { diff --git a/experimental/viewer.cc b/experimental/viewer.cc index e9402be..36d9ee9 100644 --- a/experimental/viewer.cc +++ b/experimental/viewer.cc @@ -214,11 +214,11 @@ const char* get_file_data(size_t *len, const char* filename) bool LoadObjAndConvert(float bmin[3], float bmax[3], const char* filename, int num_threads) { -#if 0 +#if 1 std::vector> vertices; std::vector> normals; std::vector> texcoords; - std::vector> faces; + std::vector> faces; size_t data_len = 0; const char* data = get_file_data(&data_len, filename); @@ -529,11 +529,15 @@ int main(int argc, char **argv) Init(); + std::cout << "Initialize GLFW..." << std::endl; + if(!glfwInit()){ std::cerr << "Failed to initialize GLFW." << std::endl; return -1; } + std::cout << "GLFW OK." << std::endl; + window = glfwCreateWindow(width, height, "Obj viewer", NULL, NULL); if(window == NULL){ @@ -561,6 +565,7 @@ int main(int argc, char **argv) float bmin[3], bmax[3]; if (false == LoadObjAndConvert(bmin, bmax, argv[1], num_threads)) { + printf("failed to load & conv\n"); return -1; } -- cgit v1.2.3 From 96ba498d7023041385e51a34103e466c0c8c87e8 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Sun, 29 May 2016 13:09:00 +0900 Subject: Add voxelizer. --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 8bc365a..34bd08f 100644 --- a/README.md +++ b/README.md @@ -60,6 +60,7 @@ TinyObjLoader is successfully used in ... * pbrt-v3 https://github.com/mmp/pbrt-v3 * cocos2d-x https://github.com/cocos2d/cocos2d-x/ * Android Vulkan demo https://github.com/SaschaWillems/Vulkan +* voxelizer https://github.com/karimnaaji/voxelizer * Your project here! Features -- cgit v1.2.3 From e2103793353982467f4f69dfc8368ef9f498181c Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Sun, 29 May 2016 14:08:27 +0900 Subject: Add Probulator --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 3dc8e56..e72645c 100644 --- a/README.md +++ b/README.md @@ -62,6 +62,7 @@ TinyObjLoader is successfully used in ... * cocos2d-x https://github.com/cocos2d/cocos2d-x/ * Android Vulkan demo https://github.com/SaschaWillems/Vulkan * voxelizer https://github.com/karimnaaji/voxelizer +* Probulator https://github.com/kayru/Probulator * Your project here! Features -- cgit v1.2.3 From 629f1825c566750e416b8d8370528733a480bb76 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Sun, 29 May 2016 16:30:54 +0900 Subject: Support retina resolution. --- examples/viewer/premake4.lua | 2 +- examples/viewer/viewer.cc | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/examples/viewer/premake4.lua b/examples/viewer/premake4.lua index 3b6ca1f..4e1e54f 100644 --- a/examples/viewer/premake4.lua +++ b/examples/viewer/premake4.lua @@ -1,6 +1,6 @@ solution "objview" -- location ( "build" ) - configurations { "Debug", "Release" } + configurations { "Release", "Debug" } platforms {"native", "x64", "x32"} project "objview" diff --git a/examples/viewer/viewer.cc b/examples/viewer/viewer.cc index 2b1bdf4..6af24ba 100644 --- a/examples/viewer/viewer.cc +++ b/examples/viewer/viewer.cc @@ -292,8 +292,11 @@ bool LoadObjAndConvert(float bmin[3], float bmax[3], std::vector& dr void reshapeFunc(GLFWwindow* window, int w, int h) { - printf("reshape\n"); - glViewport(0, 0, w, h); + int fb_w, fb_h; + // Get actual framebuffer size. + glfwGetFramebufferSize(window, &fb_w, &fb_h); + + glViewport(0, 0, fb_w, fb_h); glMatrixMode(GL_PROJECTION); glLoadIdentity(); gluPerspective(45.0, (float)w / (float)h, 0.01f, 100.0f); -- cgit v1.2.3 From b69d2a2c5543026ba66ba7985447ee1726dcd700 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Sun, 29 May 2016 16:37:30 +0900 Subject: Add more links. --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index e72645c..d8d9241 100644 --- a/README.md +++ b/README.md @@ -63,6 +63,10 @@ TinyObjLoader is successfully used in ... * Android Vulkan demo https://github.com/SaschaWillems/Vulkan * voxelizer https://github.com/karimnaaji/voxelizer * Probulator https://github.com/kayru/Probulator +* OptiX Prime baking https://github.com/nvpro-samples/optix_prime_baking +* FireRays SDK https://github.com/GPUOpen-LibrariesAndSDKs/FireRays_SDK +* parg, tiny C library of various graphics utilities and GL demos https://github.com/prideout/parg +* Opengl unit of ChronoEngine https://github.com/projectchrono/chrono-opengl * Your project here! Features -- cgit v1.2.3 From 4b9ef527c67f06abd308939fad18d8a5da9c3d73 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Wed, 1 Jun 2016 01:07:14 +0900 Subject: Parallelize merge. --- experimental/optimized-parse.cc | 136 +++++++++++++++++++++------------------- 1 file changed, 73 insertions(+), 63 deletions(-) diff --git a/experimental/optimized-parse.cc b/experimental/optimized-parse.cc index 5f7ae93..696c7a8 100644 --- a/experimental/optimized-parse.cc +++ b/experimental/optimized-parse.cc @@ -515,7 +515,6 @@ static bool tryParseDouble(const char *s, const char *s_end, double *result) { read = 1; end_not_reached = (curr != s_end); while (end_not_reached && IS_DIGIT(*curr)) { - // NOTE: Don't use powf here, it will absolutely murder precision. // pow(10.0, -read) double frac_value = 1.0; @@ -562,8 +561,18 @@ static bool tryParseDouble(const char *s, const char *s_end, double *result) { } assemble: - *result = - (sign == '+' ? 1 : -1) * ldexp(mantissa * pow(5.0, exponent), exponent); + + { + // = pow(5.0, exponent); + double a = 5.0; + for (int i = 0; i < exponent; i++) { + a = a * a; + } + *result = + //(sign == '+' ? 1 : -1) * ldexp(mantissa * pow(5.0, exponent), exponent); + (sign == '+' ? 1 : -1) * (mantissa * a) * static_cast(1ULL << exponent); // 5.0^exponent * 2^exponent + } + return true; fail: return false; @@ -618,6 +627,7 @@ typedef struct // for f std::vector > f; //std::vector f; + std::vector > f_num_verts; const char* group_name; unsigned int group_name_len; @@ -738,6 +748,8 @@ static bool parseLine(Command *command, const char *p, size_t p_len, bool triang command->f.emplace_back(i0); command->f.emplace_back(i1); command->f.emplace_back(i2); + + command->f_num_verts.emplace_back(3); } } else { @@ -745,6 +757,8 @@ static bool parseLine(Command *command, const char *p, size_t p_len, bool triang for (size_t k = 0; k < f->size(); k++) { command->f.emplace_back(f[k]); } + + command->f_num_verts.emplace_back(f->size()); } return true; @@ -1077,70 +1091,66 @@ bool parse(std::vector> &vertices, std::vector workers; + for (size_t t = 0; t < num_threads; t++) { - for (size_t i = 0; i < commands[t].size(); i++) { - if (commands[t][i].type == COMMAND_EMPTY) { - continue; - } else if (commands[t][i].type == COMMAND_V) { - vertices[3*v_count+0] = commands[t][i].vx; - vertices[3*v_count+1] = commands[t][i].vy; - vertices[3*v_count+2] = commands[t][i].vz; - v_count++; - } else if (commands[t][i].type == COMMAND_VN) { - normals[3*n_count+0] = commands[t][i].nx; - normals[3*n_count+1] = commands[t][i].ny; - normals[3*n_count+2] = commands[t][i].nz; - n_count++; - } else if (commands[t][i].type == COMMAND_VT) { - texcoords[2*t_count+0] = commands[t][i].tx; - texcoords[2*t_count+1] = commands[t][i].ty; - t_count++; - } else if (commands[t][i].type == COMMAND_F) { -#if 0 - int v_size = vertices.size() / 3; - int vn_size = normals.size() / 3; - int vt_size = texcoords.size() / 2; - - // triangulate. - { - vertex_index i0 = commands[t][i].f[0]; - vertex_index i1(-1); - vertex_index i2 = commands[t][i].f[1]; - - for (size_t k = 2; k < commands[t][i].f.size(); k++) { - i1 = i2; - i2 = commands[t][i].f[k]; - int v_idx = fixIndex(i0.v_idx, v_size); - int vn_idx = fixIndex(i0.vn_idx, vn_size); - int vt_idx = fixIndex(i0.vt_idx, vt_size); - faces.emplace_back(std::move(vertex_index(v_idx, vt_idx, vn_idx))); - v_idx = fixIndex(i1.v_idx, v_size); - vn_idx = fixIndex(i1.vn_idx, vn_size); - vt_idx = fixIndex(i1.vt_idx, vt_size); - faces.emplace_back(std::move(vertex_index(v_idx, vt_idx, vn_idx))); - v_idx = fixIndex(i2.v_idx, v_size); - vn_idx = fixIndex(i2.vn_idx, vn_size); - vt_idx = fixIndex(i2.vt_idx, vt_size); - faces.emplace_back(std::move(vertex_index(v_idx, vt_idx, vn_idx))); + workers->push_back(std::thread([&, t]() { + size_t v_count = v_offsets[t]; + size_t n_count = n_offsets[t]; + size_t t_count = t_offsets[t]; + size_t f_count = f_offsets[t]; + + for (size_t i = 0; i < commands[t].size(); i++) { + if (commands[t][i].type == COMMAND_EMPTY) { + continue; + } else if (commands[t][i].type == COMMAND_V) { + vertices[3*v_count+0] = commands[t][i].vx; + vertices[3*v_count+1] = commands[t][i].vy; + vertices[3*v_count+2] = commands[t][i].vz; + v_count++; + } else if (commands[t][i].type == COMMAND_VN) { + normals[3*n_count+0] = commands[t][i].nx; + normals[3*n_count+1] = commands[t][i].ny; + normals[3*n_count+2] = commands[t][i].nz; + n_count++; + } else if (commands[t][i].type == COMMAND_VT) { + texcoords[2*t_count+0] = commands[t][i].tx; + texcoords[2*t_count+1] = commands[t][i].ty; + t_count++; + } else if (commands[t][i].type == COMMAND_F) { + for (size_t k = 0; k < commands[t][i].f.size(); k++) { + vertex_index &vi = commands[t][i].f[k]; + int v_idx = fixIndex(vi.v_idx, v_count); + int vn_idx = fixIndex(vi.vn_idx, n_count); + int vt_idx = fixIndex(vi.vt_idx, t_count); + faces[f_count + k] = vertex_index(v_idx, vn_idx, vt_idx); } - } -#else - for (size_t k = 0; k < commands[t][i].f.size(); k++) { - vertex_index &vi = commands[t][i].f[k]; - int v_idx = fixIndex(vi.v_idx, v_count); - int vn_idx = fixIndex(vi.vn_idx, n_count); - int vt_idx = fixIndex(vi.vt_idx, t_count); - faces[f_count + k] = vertex_index(v_idx, vn_idx, vt_idx); - } - f_count += commands[t][i].f.size(); -#endif + f_count += commands[t][i].f.size(); + } } - } + })); + } + + for (size_t t = 0; t < workers->size(); t++) { + workers[t].join(); } auto t_end = std::chrono::high_resolution_clock::now(); -- cgit v1.2.3 From 831a1a4b8d4b61ff2c80c9c9a409387cc52b8d18 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Mon, 6 Jun 2016 21:22:38 +0900 Subject: Initial support of shape data structure and material ids. --- experimental/optimized-parse.cc | 500 +++++++++++++++++++++++++++++++++++----- experimental/viewer.cc | 8 +- 2 files changed, 445 insertions(+), 63 deletions(-) diff --git a/experimental/optimized-parse.cc b/experimental/optimized-parse.cc index 696c7a8..2d359e9 100644 --- a/experimental/optimized-parse.cc +++ b/experimental/optimized-parse.cc @@ -21,6 +21,8 @@ #include #include #include +#include +#include #include // C++11 #include // C++11 @@ -238,6 +240,38 @@ class StackVector // ---------------------------------------------------------------------------- +typedef struct { + 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; + + int dummy; // Suppress padding warning. + + std::string ambient_texname; // map_Ka + std::string diffuse_texname; // map_Kd + std::string specular_texname; // map_Ks + std::string specular_highlight_texname; // map_Ns + std::string bump_texname; // map_bump, bump + std::string displacement_texname; // disp + std::string alpha_texname; // map_d + std::map unknown_parameter; +} material_t; + +typedef struct { + std::string name; // group name or object name. + unsigned int face_offset; + unsigned int length; +} shape_t; + typedef StackVector ShortString; #define IS_SPACE(x) (((x) == ' ') || ((x) == '\t')) @@ -605,6 +639,254 @@ static inline void parseFloat3(float *x, float *y, float *z, (*z) = parseFloat(token); } +static void InitMaterial(material_t *material) { + material->name = ""; + material->ambient_texname = ""; + material->diffuse_texname = ""; + material->specular_texname = ""; + material->specular_highlight_texname = ""; + material->bump_texname = ""; + material->displacement_texname = ""; + material->alpha_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 void LoadMtl(std::map *material_map, + std::vector *materials, std::istream *inStream) { + // Create a default material anyway. + 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]); + + // 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 leading space. + const char *token = linebuf.c_str(); + token += strspn(token, " \t"); + + assert(token); + if (token[0] == '\0') continue; // empty line + + if (token[0] == '#') continue; // comment line + + // new mtl + 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); + } + + // initial temporary material + InitMaterial(&material); + + // set new mtl name + char namebuf[4096]; + token += 7; +#ifdef _MSC_VER + sscanf_s(token, "%s", namebuf, (unsigned)_countof(namebuf)); +#else + sscanf(token, "%s", namebuf); +#endif + material.name = namebuf; + continue; + } + + // ambient + if (token[0] == 'K' && token[1] == 'a' && IS_SPACE((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' && IS_SPACE((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' && IS_SPACE((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' && IS_SPACE((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' && IS_SPACE((token[2]))) { + token += 2; + material.ior = parseFloat(&token); + continue; + } + + // emission + if (token[0] == 'K' && token[1] == 'e' && IS_SPACE(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' && IS_SPACE(token[2])) { + token += 2; + material.shininess = parseFloat(&token); + continue; + } + + // illum model + if (0 == strncmp(token, "illum", 5) && IS_SPACE(token[5])) { + token += 6; + material.illum = parseInt(&token); + continue; + } + + // dissolve + if ((token[0] == 'd' && IS_SPACE(token[1]))) { + token += 1; + material.dissolve = parseFloat(&token); + continue; + } + if (token[0] == 'T' && token[1] == 'r' && IS_SPACE(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)) && IS_SPACE(token[6])) { + token += 7; + material.ambient_texname = token; + continue; + } + + // diffuse texture + if ((0 == strncmp(token, "map_Kd", 6)) && IS_SPACE(token[6])) { + token += 7; + material.diffuse_texname = token; + continue; + } + + // specular texture + if ((0 == strncmp(token, "map_Ks", 6)) && IS_SPACE(token[6])) { + token += 7; + material.specular_texname = token; + continue; + } + + // specular highlight texture + if ((0 == strncmp(token, "map_Ns", 6)) && IS_SPACE(token[6])) { + token += 7; + material.specular_highlight_texname = token; + continue; + } + + // bump texture + if ((0 == strncmp(token, "map_bump", 8)) && IS_SPACE(token[8])) { + token += 9; + material.bump_texname = token; + continue; + } + + // alpha texture + if ((0 == strncmp(token, "map_d", 5)) && IS_SPACE(token[5])) { + token += 6; + material.alpha_texname = token; + continue; + } + + // bump texture + if ((0 == strncmp(token, "bump", 4)) && IS_SPACE(token[4])) { + token += 5; + material.bump_texname = token; + continue; + } + + // displacement texture + if ((0 == strncmp(token, "disp", 4)) && IS_SPACE(token[4])) { + token += 5; + material.displacement_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, static_cast(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); +} + typedef enum { COMMAND_EMPTY, @@ -615,6 +897,7 @@ typedef enum COMMAND_G, COMMAND_O, COMMAND_USEMTL, + COMMAND_MTLLIB, } CommandType; @@ -636,6 +919,9 @@ typedef struct const char* material_name; unsigned int material_name_len; + const char* mtllib_name; + unsigned int mtllib_name_len; + CommandType type; } Command; @@ -645,11 +931,13 @@ struct CommandCount size_t num_vn; size_t num_vt; size_t num_f; + size_t num_faces; CommandCount() { num_v = 0; num_vn = 0; num_vt = 0; num_f = 0; + num_faces = 0; } }; @@ -782,8 +1070,8 @@ static bool parseLine(Command *command, const char *p, size_t p_len, bool triang //command->material_name = .insert(command->material_name->end(), namebuf, namebuf + strlen(namebuf)); //command->material_name->push_back('\0'); skip_space(&token); - command->material_name = token; - command->material_name_len = length_until_newline(token, p_len - (token - linebuf)); + command->material_name = p + (token - linebuf); + command->material_name_len = length_until_newline(token, p_len - (token - linebuf)) + 1; command->type = COMMAND_USEMTL; return true; @@ -791,60 +1079,24 @@ static bool parseLine(Command *command, const char *p, size_t p_len, bool triang // load mtl if ((0 == strncmp(token, "mtllib", 6)) && IS_SPACE((token[6]))) { - // By specification, `mtllib` should be appaar only once in .obj - char namebuf[8192]; + // By specification, `mtllib` should be appear only once in .obj token += 7; -#ifdef _MSC_VER - sscanf_s(token, "%s", namebuf, (unsigned)_countof(namebuf)); -#else - sscanf(token, "%s", namebuf); -#endif - //std::string err_mtl; - //std::vector materials; - //bool ok = (*readMatFn)(namebuf, &materials, &material_map, &err_mtl); - //if (err) { - // (*err) += err_mtl; - //} - - //if (!ok) { - // return false; - //} - - //if (callback.mtllib_cb) { - // callback.mtllib_cb(user_data, &materials.at(0), - // static_cast(materials.size())); - //} + skip_space(&token); + command->mtllib_name = p + (token - linebuf); + command->mtllib_name_len = length_until_newline(token, p_len - (token - linebuf)) + 1; + command->type = COMMAND_MTLLIB; return true; } // group name if (token[0] == 'g' && IS_SPACE((token[1]))) { -#if 0 - ShortString names[16]; - - int num_names = 0; - - while (!IS_NEW_LINE(token[0])) { - bool ret = parseString(&(names[num_names]), &token); - assert(ret); - token += strspn(token, " \t\r"); // skip tag - num_names++; - } - - assert(num_names > 0); - - int name_idx = 0; - // names[0] must be 'g', so skip the 0th element. - if (num_names > 1) { - name_idx = 1; - } -#endif + // @todo { multiple group name. } token += 2; - command->group_name = token; - command->group_name_len = length_until_newline(token, p_len - (token - linebuf)); + command->group_name = p + (token - linebuf); + command->group_name_len = length_until_newline(token, p_len - (token - linebuf)) + 1; command->type = COMMAND_G; return true; @@ -852,22 +1104,11 @@ static bool parseLine(Command *command, const char *p, size_t p_len, bool triang // object name if (token[0] == 'o' && IS_SPACE((token[1]))) { -#if 0 // @todo { multiple object name? } - char namebuf[8192]; - token += 2; -#ifdef _MSC_VER - sscanf_s(token, "%s", namebuf, (unsigned)_countof(namebuf)); -#else - sscanf(token, "%s", namebuf); -#endif - -#endif - token += 2; - command->object_name = token; - command->object_name_len = length_until_newline(token, p_len - (token - linebuf)); + command->object_name = p + (token - linebuf); + command->object_name_len = length_until_newline(token, p_len - (token - linebuf)) + 1; command->type = COMMAND_O; return true; @@ -903,13 +1144,15 @@ static inline bool is_line_ending(const char* p, size_t i, size_t end_i) return false; } -bool parse(std::vector> &vertices, std::vector> &normals, std::vector> &texcoords, std::vector> &faces, const char* buf, size_t len, int req_num_threads) +bool parse(std::vector> &vertices, std::vector> &normals, std::vector> &texcoords, std::vector> &faces, std::vector > &material_ids, std::vector& shapes, const char* buf, size_t len, int req_num_threads) { vertices.clear(); normals.clear(); texcoords.clear(); faces.clear(); + material_ids.clear(); + shapes.clear(); if (len < 1) return false; @@ -930,7 +1173,9 @@ bool parse(std::vector> &vertices, std::vector ms_linedetection; std::chrono::duration ms_alloc; std::chrono::duration ms_parse; + std::chrono::duration ms_load_mtl; std::chrono::duration ms_merge; + std::chrono::duration ms_construct; // 1. Find '\n' and create line data. @@ -1019,6 +1264,9 @@ bool parse(std::vector> &vertices, std::vector> &vertices, std::vectorsize(); + } + commands[t].emplace_back(std::move(command)); + } } @@ -1057,6 +1313,31 @@ bool parse(std::vector> &vertices, std::vector material_map; + std::vector materials; + + // Load material(if exits) + if (mtllib_i_index >= 0 && mtllib_t_index >= 0 && commands[mtllib_t_index][mtllib_i_index].mtllib_name && commands[mtllib_t_index][mtllib_i_index].mtllib_name_len > 0) { + std::string material_filename = std::string(commands[mtllib_t_index][mtllib_i_index].mtllib_name, commands[mtllib_t_index][mtllib_i_index].mtllib_name_len); + std::cout << "mtllib :" << material_filename << std::endl; + + auto t1 = std::chrono::high_resolution_clock::now(); + + std::ifstream ifs(material_filename); + if (ifs.good()) { + LoadMtl(&material_map, &materials, &ifs); + + std::cout << "maetrials = " << materials.size() << std::endl; + + ifs.close(); + } + + auto t2 = std::chrono::high_resolution_clock::now(); + + ms_load_mtl = t2 - t1; } auto command_sum = 0; @@ -1070,11 +1351,13 @@ bool parse(std::vector> &vertices, std::vector> &vertices, std::vector workers; for (size_t t = 0; t < num_threads; t++) { + int material_id = -1; // -1 = default unknown material. workers->push_back(std::thread([&, t]() { size_t v_count = v_offsets[t]; size_t n_count = n_offsets[t]; size_t t_count = t_offsets[t]; size_t f_count = f_offsets[t]; + size_t face_count = face_offsets[t]; for (size_t i = 0; i < commands[t].size(); i++) { if (commands[t][i].type == COMMAND_EMPTY) { continue; + } else if (commands[t][i].type == COMMAND_USEMTL) { + if (commands[t][i].material_name && commands[t][i].material_name_len > 0) { + std::string material_name(commands[t][i].material_name, commands[t][i].material_name_len); + + if (material_map.find(material_name) != material_map.end()) { + material_id = material_map[material_name]; + } else { + // Assign invalid material ID + material_id = -1; + } + } } else if (commands[t][i].type == COMMAND_V) { vertices[3*v_count+0] = commands[t][i].vx; vertices[3*v_count+1] = commands[t][i].vy; @@ -1142,8 +1442,10 @@ bool parse(std::vector> &vertices, std::vector> &vertices, std::vector 0) { + // push previous shape + shape.length = face_count - face_prev_offset; + shapes.push_back(shape); + face_prev_offset = face_count; + } + } + + // redefine shape. + shape.name = name; + shape.face_offset = face_count; + shape.length = 0; + } + } else if (commands[t][i].type == COMMAND_G) { + std::cout << "g" << std::endl; + } + if (commands[t][i].type == COMMAND_F) { + face_count++; + } + } + } + + if ((face_count - face_prev_offset) > 0) { + shape.length = face_count - shape.face_offset; + if (shape.length > 0) { + shapes.push_back(shape); + } + } else { + // Guess no 'v' line occurrence after 'o' or 'g', so discards current shape information. + std::cout << "remainder " << shape.name << ", " << shape.face_offset << ", " << face_prev_offset << ", " << face_count << std::endl; + } + + auto t_end = std::chrono::high_resolution_clock::now(); + + ms_construct = t_end - t_start; + } std::chrono::duration ms_total = t4 - t1; std::cout << "total parsing time: " << ms_total.count() << " ms\n"; @@ -1165,10 +1539,14 @@ bool parse(std::vector> &vertices, std::vector::max(); bmax[0] = bmax[1] = bmax[2] = -std::numeric_limits::max(); @@ -514,6 +516,8 @@ int main(int argc, char **argv) std::vector> normals; std::vector> texcoords; std::vector> faces; + std::vector> material_ids; + std::vector shapes; size_t data_len = 0; const char* data = get_file_data(&data_len, argv[1]); @@ -522,7 +526,7 @@ int main(int argc, char **argv) return false; } printf("filesize: %d\n", (int)data_len); - bool ret = parse(vertices, normals, texcoords, faces, data, data_len, num_threads); + bool ret = parse(vertices, normals, texcoords, faces, material_ids, shapes, data, data_len, num_threads); return ret; } -- cgit v1.2.3 From 5fc9842e977896e403b588ecd5f98026a7ff2018 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Tue, 7 Jun 2016 18:58:08 +0900 Subject: Refactor source code of optimized .obj parser. --- experimental/optimized-parse.cc | 1626 ------------------------------------- experimental/tinyobj_loader_opt.h | 1562 +++++++++++++++++++++++++++++++++++ experimental/viewer.cc | 165 ++-- 3 files changed, 1643 insertions(+), 1710 deletions(-) delete mode 100644 experimental/optimized-parse.cc create mode 100644 experimental/tinyobj_loader_opt.h diff --git a/experimental/optimized-parse.cc b/experimental/optimized-parse.cc deleted file mode 100644 index 2d359e9..0000000 --- a/experimental/optimized-parse.cc +++ /dev/null @@ -1,1626 +0,0 @@ -// -//@todo { parse material. assign material id } -//@todo { support object&group } -// -#ifdef _WIN64 -#define atoll(S) _atoi64(S) -#include -#else -#include -#include -#include -#include -#include -#include -#endif - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include // C++11 -#include // C++11 -#include // C++11 - -#include "ltalloc.hpp" - -// ---------------------------------------------------------------------------- -// Small vector class useful for multi-threaded environment. -// -// stack_container.h -// -// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -// This allocator can be used with STL containers to provide a stack buffer -// from which to allocate memory and overflows onto the heap. This stack buffer -// would be allocated on the stack and allows us to avoid heap operations in -// some situations. -// -// STL likes to make copies of allocators, so the allocator itself can't hold -// the data. Instead, we make the creator responsible for creating a -// StackAllocator::Source which contains the data. Copying the allocator -// merely copies the pointer to this shared source, so all allocators created -// based on our allocator will share the same stack buffer. -// -// This stack buffer implementation is very simple. The first allocation that -// fits in the stack buffer will use the stack buffer. Any subsequent -// allocations will not use the stack buffer, even if there is unused room. -// This makes it appropriate for array-like containers, but the caller should -// be sure to reserve() in the container up to the stack buffer size. Otherwise -// the container will allocate a small array which will "use up" the stack -// buffer. -template -class StackAllocator : public std::allocator { - public: - typedef typename std::allocator::pointer pointer; - typedef typename std::allocator::size_type size_type; - - // Backing store for the allocator. The container owner is responsible for - // maintaining this for as long as any containers using this allocator are - // live. - struct Source { - Source() : used_stack_buffer_(false) {} - - // Casts the buffer in its right type. - T *stack_buffer() { return reinterpret_cast(stack_buffer_); } - const T *stack_buffer() const { - return reinterpret_cast(stack_buffer_); - } - - // - // IMPORTANT: Take care to ensure that stack_buffer_ is aligned - // since it is used to mimic an array of T. - // Be careful while declaring any unaligned types (like bool) - // before stack_buffer_. - // - - // The buffer itself. It is not of type T because we don't want the - // constructors and destructors to be automatically called. Define a POD - // buffer of the right size instead. - char stack_buffer_[sizeof(T[stack_capacity])]; - - // Set when the stack buffer is used for an allocation. We do not track - // how much of the buffer is used, only that somebody is using it. - bool used_stack_buffer_; - }; - - // Used by containers when they want to refer to an allocator of type U. - template - struct rebind { - typedef StackAllocator other; - }; - - // For the straight up copy c-tor, we can share storage. - StackAllocator(const StackAllocator &rhs) - : source_(rhs.source_) {} - - // ISO C++ requires the following constructor to be defined, - // and std::vector in VC++2008SP1 Release fails with an error - // in the class _Container_base_aux_alloc_real (from ) - // if the constructor does not exist. - // For this constructor, we cannot share storage; there's - // no guarantee that the Source buffer of Ts is large enough - // for Us. - // TODO(Google): If we were fancy pants, perhaps we could share storage - // iff sizeof(T) == sizeof(U). - template - StackAllocator(const StackAllocator &other) - : source_(NULL) { - (void)other; - } - - explicit StackAllocator(Source *source) : source_(source) {} - - // Actually do the allocation. Use the stack buffer if nobody has used it yet - // and the size requested fits. Otherwise, fall through to the standard - // allocator. - pointer allocate(size_type n, void *hint = 0) { - if (source_ != NULL && !source_->used_stack_buffer_ && - n <= stack_capacity) { - source_->used_stack_buffer_ = true; - return source_->stack_buffer(); - } else { - return std::allocator::allocate(n, hint); - } - } - - // Free: when trying to free the stack buffer, just mark it as free. For - // non-stack-buffer pointers, just fall though to the standard allocator. - void deallocate(pointer p, size_type n) { - if (source_ != NULL && p == source_->stack_buffer()) - source_->used_stack_buffer_ = false; - else - std::allocator::deallocate(p, n); - } - - private: - Source *source_; -}; - -// A wrapper around STL containers that maintains a stack-sized buffer that the -// initial capacity of the vector is based on. Growing the container beyond the -// stack capacity will transparently overflow onto the heap. The container must -// support reserve(). -// -// WATCH OUT: the ContainerType MUST use the proper StackAllocator for this -// type. This object is really intended to be used only internally. You'll want -// to use the wrappers below for different types. -template -class StackContainer { - public: - typedef TContainerType ContainerType; - typedef typename ContainerType::value_type ContainedType; - typedef StackAllocator Allocator; - - // Allocator must be constructed before the container! - StackContainer() : allocator_(&stack_data_), container_(allocator_) { - // Make the container use the stack allocation by reserving our buffer size - // before doing anything else. - container_.reserve(stack_capacity); - } - - // Getters for the actual container. - // - // Danger: any copies of this made using the copy constructor must have - // shorter lifetimes than the source. The copy will share the same allocator - // and therefore the same stack buffer as the original. Use std::copy to - // copy into a "real" container for longer-lived objects. - ContainerType &container() { return container_; } - const ContainerType &container() const { return container_; } - - // Support operator-> to get to the container. This allows nicer syntax like: - // StackContainer<...> foo; - // std::sort(foo->begin(), foo->end()); - ContainerType *operator->() { return &container_; } - const ContainerType *operator->() const { return &container_; } - -#ifdef UNIT_TEST - // Retrieves the stack source so that that unit tests can verify that the - // buffer is being used properly. - const typename Allocator::Source &stack_data() const { return stack_data_; } -#endif - - protected: - typename Allocator::Source stack_data_; - unsigned char pad_[7]; - Allocator allocator_; - ContainerType container_; - - // DISALLOW_EVIL_CONSTRUCTORS(StackContainer); - StackContainer(const StackContainer &); - void operator=(const StackContainer &); -}; - -// StackVector -// -// Example: -// StackVector foo; -// foo->push_back(22); // we have overloaded operator-> -// foo[0] = 10; // as well as operator[] -template -class StackVector - : public StackContainer >, - stack_capacity> { - public: - StackVector() - : StackContainer >, - stack_capacity>() {} - - // We need to put this in STL containers sometimes, which requires a copy - // constructor. We can't call the regular copy constructor because that will - // take the stack buffer from the original. Here, we create an empty object - // and make a stack buffer of its own. - StackVector(const StackVector &other) - : StackContainer >, - stack_capacity>() { - this->container().assign(other->begin(), other->end()); - } - - StackVector &operator=( - const StackVector &other) { - this->container().assign(other->begin(), other->end()); - return *this; - } - - // Vectors are commonly indexed, which isn't very convenient even with - // operator-> (using "->at()" does exception stuff we don't want). - T &operator[](size_t i) { return this->container().operator[](i); } - const T &operator[](size_t i) const { - return this->container().operator[](i); - } -}; - -// ---------------------------------------------------------------------------- - -typedef struct { - 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; - - int dummy; // Suppress padding warning. - - std::string ambient_texname; // map_Ka - std::string diffuse_texname; // map_Kd - std::string specular_texname; // map_Ks - std::string specular_highlight_texname; // map_Ns - std::string bump_texname; // map_bump, bump - std::string displacement_texname; // disp - std::string alpha_texname; // map_d - std::map unknown_parameter; -} material_t; - -typedef struct { - std::string name; // group name or object name. - unsigned int face_offset; - unsigned int length; -} shape_t; - -typedef StackVector ShortString; - -#define IS_SPACE(x) (((x) == ' ') || ((x) == '\t')) -#define IS_DIGIT(x) \ - (static_cast((x) - '0') < static_cast(10)) -#define IS_NEW_LINE(x) (((x) == '\r') || ((x) == '\n') || ((x) == '\0')) - -static inline void skip_space(const char **token) -{ - while ((*token)[0] == ' ' || (*token)[0] == '\t') { - (*token)++; - } -} - -static inline void skip_space_and_cr(const char **token) -{ - while ((*token)[0] == ' ' || (*token)[0] == '\t' || (*token)[0] == '\r') { - (*token)++; - } -} - -static inline int until_space(const char *token) -{ - const char *p = token; - while (p[0] != '\0' && p[0] != ' ' && p[0] != '\t' && p[0] != '\r') { - p++; - } - - return p - token; -} - -static inline int length_until_newline(const char *token, int n) -{ - int len = 0; - - // Assume token[n-1] = '\0' - for (len = 0; len < n -1; len++) { - if (token[len] == '\n') { - break; - } - if ((token[len] == '\r') && ((len < (n-2)) && (token[len+1] != '\n'))) { - break; - } - } - - return len; -} - -// http://stackoverflow.com/questions/5710091/how-does-atoi-function-in-c-work -static inline int my_atoi( const char *c ) { - int value = 0; - int sign = 1; - if( *c == '+' || *c == '-' ) { - if( *c == '-' ) sign = -1; - c++; - } - while ( ((*c) >= '0') && ((*c) <= '9') ) { // isdigit(*c) - value *= 10; - value += (int) (*c-'0'); - c++; - } - return value * sign; -} - -struct vertex_index { - int v_idx, vt_idx, vn_idx; - vertex_index() : v_idx(-1), vt_idx(-1), vn_idx(-1) {} - explicit 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) {} -}; - -// 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 0 -// Parse triples with index offsets: 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); - - vi.v_idx = fixIndex(my_atoi((*token)), vsize); - - //(*token) += strcspn((*token), "/ \t\r"); - while ((*token)[0] != '\0' && (*token)[0] != '/' && (*token)[0] != ' ' && (*token)[0] != '\t' && (*token)[0] != '\r') { - (*token)++; - } - if ((*token)[0] != '/') { - return vi; - } - (*token)++; - - // i//k - if ((*token)[0] == '/') { - (*token)++; - vi.vn_idx = fixIndex(my_atoi((*token)), vnsize); - //(*token) += strcspn((*token), "/ \t\r"); - while ((*token)[0] != '\0' && (*token)[0] != '/' && (*token)[0] != ' ' && (*token)[0] != '\t' && (*token)[0] != '\r') { - (*token)++; - } - return vi; - } - - // i/j/k or i/j - vi.vt_idx = fixIndex(my_atoi((*token)), vtsize); - //(*token) += strcspn((*token), "/ \t\r"); - while ((*token)[0] != '\0' && (*token)[0] != '/' && (*token)[0] != ' ' && (*token)[0] != '\t' && (*token)[0] != '\r') { - (*token)++; - } - if ((*token)[0] != '/') { - return vi; - } - - // i/j/k - (*token)++; // skip '/' - vi.vn_idx = fixIndex(my_atoi((*token)), vnsize); - //(*token) += strcspn((*token), "/ \t\r"); - while ((*token)[0] != '\0' && (*token)[0] != '/' && (*token)[0] != ' ' && (*token)[0] != '\t' && (*token)[0] != '\r') { - (*token)++; - } - return vi; -} -#endif - -// Parse raw triples: i, i/j/k, i//k, i/j -static vertex_index parseRawTriple(const char **token) { - vertex_index vi( - static_cast(0x80000000)); // 0x80000000 = -2147483648 = invalid - - vi.v_idx = my_atoi((*token)); - //(*token) += strcspn((*token), "/ \t\r"); - while ((*token)[0] != '\0' && (*token)[0] != '/' && (*token)[0] != ' ' && (*token)[0] != '\t' && (*token)[0] != '\r') { - (*token)++; - } - if ((*token)[0] != '/') { - return vi; - } - (*token)++; - - // i//k - if ((*token)[0] == '/') { - (*token)++; - vi.vn_idx = my_atoi((*token)); - //(*token) += strcspn((*token), "/ \t\r"); - while ((*token)[0] != '\0' && (*token)[0] != '/' && (*token)[0] != ' ' && (*token)[0] != '\t' && (*token)[0] != '\r') { - (*token)++; - } - return vi; - } - - // i/j/k or i/j - vi.vt_idx = my_atoi((*token)); - //(*token) += strcspn((*token), "/ \t\r"); - while ((*token)[0] != '\0' && (*token)[0] != '/' && (*token)[0] != ' ' && (*token)[0] != '\t' && (*token)[0] != '\r') { - (*token)++; - } - if ((*token)[0] != '/') { - return vi; - } - - // i/j/k - (*token)++; // skip '/' - vi.vn_idx = my_atoi((*token)); - //(*token) += strcspn((*token), "/ \t\r"); - while ((*token)[0] != '\0' && (*token)[0] != '/' && (*token)[0] != ' ' && (*token)[0] != '\t' && (*token)[0] != '\r') { - (*token)++; - } - return vi; -} - -static inline bool parseString(ShortString *s, const char **token) { - skip_space(token); //(*token) += strspn((*token), " \t"); - size_t e = until_space((*token)); //strcspn((*token), " \t\r"); - (*s)->insert((*s)->end(), (*token), (*token) + e); - (*token) += e; - return true; -} - -static inline int parseInt(const char **token) { - skip_space(token); //(*token) += strspn((*token), " \t"); - int i = my_atoi((*token)); - (*token) += until_space((*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 -// stop. For example at the end of the string, to prevent buffer overflows. -// -// Parses the following EBNF grammar: -// sign = "+" | "-" ; -// END = ? anything not in digit ? -// digit = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" ; -// integer = [sign] , digit , {digit} ; -// decimal = integer , ["." , integer] ; -// float = ( decimal , END ) | ( decimal , ("E" | "e") , integer , END ) ; -// -// 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 -// is returned. -// -// The function is greedy and will parse until any of the following happens: -// - a non-conforming character is encountered. -// - s_end is reached. -// -// 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) { - 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 - // 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 - // exponent to be in base 2. - int exponent = 0; - - // NOTE: THESE MUST BE DECLARED HERE SINCE WE ARE NOT ALLOWED - // TO JUMP OVER DEFINITIONS. - char sign = '+'; - char exp_sign = '+'; - char const *curr = s; - - // 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; - - /* - BEGIN PARSING. - */ - - // Find out what sign we've got. - if (*curr == '+' || *curr == '-') { - sign = *curr; - curr++; - } else if (IS_DIGIT(*curr)) { /* Pass through. */ - } else { - goto fail; - } - - // Read the integer part. - end_not_reached = (curr != s_end); - while (end_not_reached && IS_DIGIT(*curr)) { - mantissa *= 10; - mantissa += static_cast(*curr - 0x30); - curr++; - read++; - end_not_reached = (curr != s_end); - } - - // We must make sure we actually got something. - if (read == 0) goto fail; - // We allow numbers of form "#", "###" etc. - if (!end_not_reached) goto assemble; - - // Read the decimal part. - if (*curr == '.') { - curr++; - read = 1; - end_not_reached = (curr != s_end); - while (end_not_reached && IS_DIGIT(*curr)) { - - // pow(10.0, -read) - double frac_value = 1.0; - for (int f = 0; f < read; f++) { - frac_value *= 0.1; - } - mantissa += static_cast(*curr - 0x30) * frac_value; - read++; - curr++; - end_not_reached = (curr != s_end); - } - } else if (*curr == 'e' || *curr == 'E') { - } else { - goto assemble; - } - - if (!end_not_reached) goto assemble; - - // Read the exponent part. - if (*curr == 'e' || *curr == 'E') { - curr++; - // Figure out if a sign is present and if it is. - end_not_reached = (curr != s_end); - if (end_not_reached && (*curr == '+' || *curr == '-')) { - exp_sign = *curr; - curr++; - } else if (IS_DIGIT(*curr)) { /* Pass through. */ - } else { - // Empty E is not allowed. - goto fail; - } - - read = 0; - end_not_reached = (curr != s_end); - while (end_not_reached && IS_DIGIT(*curr)) { - exponent *= 10; - exponent += static_cast(*curr - 0x30); - curr++; - read++; - end_not_reached = (curr != s_end); - } - exponent *= (exp_sign == '+' ? 1 : -1); - if (read == 0) goto fail; - } - -assemble: - - { - // = pow(5.0, exponent); - double a = 5.0; - for (int i = 0; i < exponent; i++) { - a = a * a; - } - *result = - //(sign == '+' ? 1 : -1) * ldexp(mantissa * pow(5.0, exponent), exponent); - (sign == '+' ? 1 : -1) * (mantissa * a) * static_cast(1ULL << exponent); // 5.0^exponent * 2^exponent - } - - return true; -fail: - return false; -} - -static inline float parseFloat(const char **token) { - skip_space(token); //(*token) += strspn((*token), " \t"); -#ifdef TINY_OBJ_LOADER_OLD_FLOAT_PARSER - float f = static_cast(atof(*token)); - (*token) += strcspn((*token), " \t\r"); -#else - const char *end = (*token) + until_space((*token)); //strcspn((*token), " \t\r"); - double val = 0.0; - tryParseDouble((*token), end, &val); - float f = static_cast(val); - (*token) = end; -#endif - return f; -} - -static inline void parseFloat2(float *x, float *y, const char **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); -} - -static void InitMaterial(material_t *material) { - material->name = ""; - material->ambient_texname = ""; - material->diffuse_texname = ""; - material->specular_texname = ""; - material->specular_highlight_texname = ""; - material->bump_texname = ""; - material->displacement_texname = ""; - material->alpha_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 void LoadMtl(std::map *material_map, - std::vector *materials, std::istream *inStream) { - // Create a default material anyway. - 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]); - - // 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 leading space. - const char *token = linebuf.c_str(); - token += strspn(token, " \t"); - - assert(token); - if (token[0] == '\0') continue; // empty line - - if (token[0] == '#') continue; // comment line - - // new mtl - 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); - } - - // initial temporary material - InitMaterial(&material); - - // set new mtl name - char namebuf[4096]; - token += 7; -#ifdef _MSC_VER - sscanf_s(token, "%s", namebuf, (unsigned)_countof(namebuf)); -#else - sscanf(token, "%s", namebuf); -#endif - material.name = namebuf; - continue; - } - - // ambient - if (token[0] == 'K' && token[1] == 'a' && IS_SPACE((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' && IS_SPACE((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' && IS_SPACE((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' && IS_SPACE((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' && IS_SPACE((token[2]))) { - token += 2; - material.ior = parseFloat(&token); - continue; - } - - // emission - if (token[0] == 'K' && token[1] == 'e' && IS_SPACE(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' && IS_SPACE(token[2])) { - token += 2; - material.shininess = parseFloat(&token); - continue; - } - - // illum model - if (0 == strncmp(token, "illum", 5) && IS_SPACE(token[5])) { - token += 6; - material.illum = parseInt(&token); - continue; - } - - // dissolve - if ((token[0] == 'd' && IS_SPACE(token[1]))) { - token += 1; - material.dissolve = parseFloat(&token); - continue; - } - if (token[0] == 'T' && token[1] == 'r' && IS_SPACE(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)) && IS_SPACE(token[6])) { - token += 7; - material.ambient_texname = token; - continue; - } - - // diffuse texture - if ((0 == strncmp(token, "map_Kd", 6)) && IS_SPACE(token[6])) { - token += 7; - material.diffuse_texname = token; - continue; - } - - // specular texture - if ((0 == strncmp(token, "map_Ks", 6)) && IS_SPACE(token[6])) { - token += 7; - material.specular_texname = token; - continue; - } - - // specular highlight texture - if ((0 == strncmp(token, "map_Ns", 6)) && IS_SPACE(token[6])) { - token += 7; - material.specular_highlight_texname = token; - continue; - } - - // bump texture - if ((0 == strncmp(token, "map_bump", 8)) && IS_SPACE(token[8])) { - token += 9; - material.bump_texname = token; - continue; - } - - // alpha texture - if ((0 == strncmp(token, "map_d", 5)) && IS_SPACE(token[5])) { - token += 6; - material.alpha_texname = token; - continue; - } - - // bump texture - if ((0 == strncmp(token, "bump", 4)) && IS_SPACE(token[4])) { - token += 5; - material.bump_texname = token; - continue; - } - - // displacement texture - if ((0 == strncmp(token, "disp", 4)) && IS_SPACE(token[4])) { - token += 5; - material.displacement_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, static_cast(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); -} - -typedef enum -{ - COMMAND_EMPTY, - COMMAND_V, - COMMAND_VN, - COMMAND_VT, - COMMAND_F, - COMMAND_G, - COMMAND_O, - COMMAND_USEMTL, - COMMAND_MTLLIB, - -} CommandType; - -typedef struct -{ - float vx, vy, vz; - float nx, ny, nz; - float tx, ty; - - // for f - std::vector > f; - //std::vector f; - std::vector > f_num_verts; - - const char* group_name; - unsigned int group_name_len; - const char* object_name; - unsigned int object_name_len; - const char* material_name; - unsigned int material_name_len; - - const char* mtllib_name; - unsigned int mtllib_name_len; - - CommandType type; -} Command; - -struct CommandCount -{ - size_t num_v; - size_t num_vn; - size_t num_vt; - size_t num_f; - size_t num_faces; - CommandCount() { - num_v = 0; - num_vn = 0; - num_vt = 0; - num_f = 0; - num_faces = 0; - } -}; - -static bool parseLine(Command *command, const char *p, size_t p_len, bool triangulate = true) -{ - char linebuf[4096]; - assert(p_len < 4095); - //StackVector linebuf; - //linebuf->resize(p_len + 1); - memcpy(&linebuf, p, p_len); - linebuf[p_len] = '\0'; - - const char *token = linebuf; - - command->type = COMMAND_EMPTY; - - // Skip leading space. - //token += strspn(token, " \t"); - skip_space(&token); //(*token) += strspn((*token), " \t"); - - assert(token); - if (token[0] == '\0') { // empty line - return false; - } - - if (token[0] == '#') { // comment line - return false; - } - - // vertex - if (token[0] == 'v' && IS_SPACE((token[1]))) { - token += 2; - float x, y, z; - parseFloat3(&x, &y, &z, &token); - command->vx = x; - command->vy = y; - command->vz = z; - command->type = COMMAND_V; - return true; - } - - // normal - if (token[0] == 'v' && token[1] == 'n' && IS_SPACE((token[2]))) { - token += 3; - float x, y, z; - parseFloat3(&x, &y, &z, &token); - command->nx = x; - command->ny = y; - command->nz = z; - command->type = COMMAND_VN; - return true; - } - - // texcoord - if (token[0] == 'v' && token[1] == 't' && IS_SPACE((token[2]))) { - token += 3; - float x, y; - parseFloat2(&x, &y, &token); - command->tx = x; - command->ty = y; - command->type = COMMAND_VT; - return true; - } - - // face - if (token[0] == 'f' && IS_SPACE((token[1]))) { - token += 2; - //token += strspn(token, " \t"); - skip_space(&token); - - StackVector f; - - while (!IS_NEW_LINE(token[0])) { - vertex_index vi = parseRawTriple(&token); - //printf("v = %d, %d, %d\n", vi.v_idx, vi.vn_idx, vi.vt_idx); - //if (callback.index_cb) { - // callback.index_cb(user_data, vi.v_idx, vi.vn_idx, vi.vt_idx); - //} - //size_t n = strspn(token, " \t\r"); - //token += n; - skip_space_and_cr(&token); - - f->push_back(vi); - } - - command->type = COMMAND_F; - - if (triangulate) { - vertex_index i0 = f[0]; - vertex_index i1(-1); - vertex_index i2 = f[1]; - - for (size_t k = 2; k < f->size(); k++) { - i1 = i2; - i2 = f[k]; - command->f.emplace_back(i0); - command->f.emplace_back(i1); - command->f.emplace_back(i2); - - command->f_num_verts.emplace_back(3); - } - - } else { - - for (size_t k = 0; k < f->size(); k++) { - command->f.emplace_back(f[k]); - } - - command->f_num_verts.emplace_back(f->size()); - } - - return true; - } - - // use mtl - if ((0 == strncmp(token, "usemtl", 6)) && IS_SPACE((token[6]))) { - token += 7; - - //int newMaterialId = -1; - //if (material_map.find(namebuf) != material_map.end()) { - // newMaterialId = material_map[namebuf]; - //} else { - // // { error!! material not found } - //} - - //if (newMaterialId != materialId) { - // materialId = newMaterialId; - //} - - //command->material_name = .insert(command->material_name->end(), namebuf, namebuf + strlen(namebuf)); - //command->material_name->push_back('\0'); - skip_space(&token); - command->material_name = p + (token - linebuf); - command->material_name_len = length_until_newline(token, p_len - (token - linebuf)) + 1; - command->type = COMMAND_USEMTL; - - return true; - } - - // load mtl - if ((0 == strncmp(token, "mtllib", 6)) && IS_SPACE((token[6]))) { - // By specification, `mtllib` should be appear only once in .obj - token += 7; - - skip_space(&token); - command->mtllib_name = p + (token - linebuf); - command->mtllib_name_len = length_until_newline(token, p_len - (token - linebuf)) + 1; - command->type = COMMAND_MTLLIB; - - return true; - } - - // group name - if (token[0] == 'g' && IS_SPACE((token[1]))) { - // @todo { multiple group name. } - token += 2; - - command->group_name = p + (token - linebuf); - command->group_name_len = length_until_newline(token, p_len - (token - linebuf)) + 1; - command->type = COMMAND_G; - - return true; - } - - // object name - if (token[0] == 'o' && IS_SPACE((token[1]))) { - // @todo { multiple object name? } - token += 2; - - command->object_name = p + (token - linebuf); - command->object_name_len = length_until_newline(token, p_len - (token - linebuf)) + 1; - command->type = COMMAND_O; - - return true; - } - - - return false; -} - -typedef struct -{ - size_t pos; - size_t len; -} LineInfo; - -// Idea come from https://github.com/antonmks/nvParse -// 1. mmap file -// 2. find newline(\n, \r\n, \r) and list of line data. -// 3. Do parallel parsing for each line. -// 4. Reconstruct final mesh data structure. - -#define kMaxThreads (32) - -static inline bool is_line_ending(const char* p, size_t i, size_t end_i) -{ - if (p[i] == '\0') return true; - if (p[i] == '\n') return true; // this includes \r\n - if (p[i] == '\r') { - if (((i+1) < end_i) && (p[i+1] != '\n')) { // detect only \r case - return true; - } - } - return false; -} - -bool parse(std::vector> &vertices, std::vector> &normals, std::vector> &texcoords, std::vector> &faces, std::vector > &material_ids, std::vector& shapes, const char* buf, size_t len, int req_num_threads) -{ - - vertices.clear(); - normals.clear(); - texcoords.clear(); - faces.clear(); - material_ids.clear(); - shapes.clear(); - - if (len < 1) return false; - - std::cout << "parse" << std::endl; - - auto num_threads = (req_num_threads < 0) ? std::thread::hardware_concurrency() : req_num_threads; - num_threads = std::max(1, std::min(static_cast(num_threads), kMaxThreads)); - std::cout << "# of threads = " << num_threads << std::endl; - - auto t1 = std::chrono::high_resolution_clock::now(); - - std::vector> line_infos[kMaxThreads]; - for (size_t t = 0; t < static_cast(num_threads); t++) { - // Pre allocate enough memory. len / 128 / num_threads is just a heuristic value. - line_infos[t].reserve(len / 128 / num_threads); - } - - std::chrono::duration ms_linedetection; - std::chrono::duration ms_alloc; - std::chrono::duration ms_parse; - std::chrono::duration ms_load_mtl; - std::chrono::duration ms_merge; - std::chrono::duration ms_construct; - - - // 1. Find '\n' and create line data. - { - StackVector workers; - - auto start_time = std::chrono::high_resolution_clock::now(); - auto chunk_size = len / num_threads; - - for (size_t t = 0; t < static_cast(num_threads); t++) { - workers->push_back(std::thread([&, t]() { - auto start_idx = (t + 0) * chunk_size; - auto end_idx = std::min((t + 1) * chunk_size, len - 1); - if (t == static_cast((num_threads - 1))) { - end_idx = len - 1; - } - - size_t prev_pos = start_idx; - for (size_t i = start_idx; i < end_idx; i++) { - if (is_line_ending(buf, i, end_idx)) { - if ((t > 0) && (prev_pos == start_idx) && (!is_line_ending(buf, start_idx-1, end_idx))) { - // first linebreak found in (chunk > 0), and a line before this linebreak belongs to previous chunk, so skip it. - prev_pos = i + 1; - continue; - } else { - LineInfo info; - info.pos = prev_pos; - info.len = i - prev_pos; - - if (info.len > 0) { - line_infos[t].push_back(info); - } - - prev_pos = i+1; - } - } - } - - // Find extra line which spand across chunk boundary. - if ((t < num_threads) && (buf[end_idx-1] != '\n')) { - auto extra_span_idx = std::min(end_idx-1+chunk_size, len - 1); - for (size_t i = end_idx; i < extra_span_idx; i++) { - if (is_line_ending(buf, i, extra_span_idx)) { - LineInfo info; - info.pos = prev_pos; - info.len = i - prev_pos; - - if (info.len > 0) { - line_infos[t].push_back(info); - } - - break; - } - } - } - })); - } - - for (size_t t = 0; t < workers->size(); t++) { - workers[t].join(); - } - - auto end_time = std::chrono::high_resolution_clock::now(); - - ms_linedetection = end_time - start_time; - } - - auto line_sum = 0; - for (size_t t = 0; t < num_threads; t++) { - //std::cout << t << ": # of lines = " << line_infos[t].size() << std::endl; - line_sum += line_infos[t].size(); - } - //std::cout << "# of lines = " << line_sum << std::endl; - - std::vector commands[kMaxThreads]; - //thread_local std::vector > commands; - - // 2. allocate buffer - auto t_alloc_start = std::chrono::high_resolution_clock::now(); - { - - for (size_t t = 0; t < num_threads; t++) { - commands[t].reserve(line_infos[t].size()); - } - - } - - CommandCount command_count[kMaxThreads]; - // Array index to `mtllib` line. According to wavefront .obj spec, `mtllib' should appear only once in .obj. - int mtllib_t_index = -1; - int mtllib_i_index = -1; - - ms_alloc = std::chrono::high_resolution_clock::now() - t_alloc_start; - - // 2. parse each line in parallel. - { - StackVector workers; - auto t_start = std::chrono::high_resolution_clock::now(); - - for (size_t t = 0; t < num_threads; t++) { - workers->push_back(std::thread([&, t]() { - - for (size_t i = 0; i < line_infos[t].size(); i++) { - Command command; - bool ret = parseLine(&command, &buf[line_infos[t][i].pos], line_infos[t][i].len); - if (ret) { - if (command.type == COMMAND_V) { - command_count[t].num_v++; - } else if (command.type == COMMAND_VN) { - command_count[t].num_vn++; - } else if (command.type == COMMAND_VT) { - command_count[t].num_vt++; - } else if (command.type == COMMAND_F) { - command_count[t].num_f += command.f.size(); - command_count[t].num_faces++; - } - - if (command.type == COMMAND_MTLLIB) { - mtllib_t_index = t; - mtllib_i_index = commands->size(); - } - - commands[t].emplace_back(std::move(command)); - - } - } - - })); - } - - for (size_t t = 0; t < workers->size(); t++) { - workers[t].join(); - } - - auto t_end = std::chrono::high_resolution_clock::now(); - - ms_parse = t_end - t_start; - - } - - std::map material_map; - std::vector materials; - - // Load material(if exits) - if (mtllib_i_index >= 0 && mtllib_t_index >= 0 && commands[mtllib_t_index][mtllib_i_index].mtllib_name && commands[mtllib_t_index][mtllib_i_index].mtllib_name_len > 0) { - std::string material_filename = std::string(commands[mtllib_t_index][mtllib_i_index].mtllib_name, commands[mtllib_t_index][mtllib_i_index].mtllib_name_len); - std::cout << "mtllib :" << material_filename << std::endl; - - auto t1 = std::chrono::high_resolution_clock::now(); - - std::ifstream ifs(material_filename); - if (ifs.good()) { - LoadMtl(&material_map, &materials, &ifs); - - std::cout << "maetrials = " << materials.size() << std::endl; - - ifs.close(); - } - - auto t2 = std::chrono::high_resolution_clock::now(); - - ms_load_mtl = t2 - t1; - } - - auto command_sum = 0; - for (size_t t = 0; t < num_threads; t++) { - //std::cout << t << ": # of commands = " << commands[t].size() << std::endl; - command_sum += commands[t].size(); - } - //std::cout << "# of commands = " << command_sum << std::endl; - - size_t num_v = 0; - size_t num_vn = 0; - size_t num_vt = 0; - size_t num_f = 0; - size_t num_faces = 0; - for (size_t t = 0; t < num_threads; t++) { - num_v += command_count[t].num_v; - num_vn += command_count[t].num_vn; - num_vt += command_count[t].num_vt; - num_f += command_count[t].num_f; - num_faces += command_count[t].num_faces; - } - //std::cout << "# v " << num_v << std::endl; - //std::cout << "# vn " << num_vn << std::endl; - //std::cout << "# vt " << num_vt << std::endl; - //std::cout << "# f " << num_f << std::endl; - - // 4. merge - // @todo { parallelize merge. } - { - auto t_start = std::chrono::high_resolution_clock::now(); - - vertices.resize(num_v * 3); - normals.resize(num_vn * 3); - texcoords.resize(num_vt * 2); - faces.resize(num_f); - material_ids.resize(num_faces); - - size_t v_offsets[kMaxThreads]; - size_t n_offsets[kMaxThreads]; - size_t t_offsets[kMaxThreads]; - size_t f_offsets[kMaxThreads]; - size_t face_offsets[kMaxThreads]; - - v_offsets[0] = 0; - n_offsets[0] = 0; - t_offsets[0] = 0; - f_offsets[0] = 0; - face_offsets[0] = 0; - - for (size_t t = 1; t < num_threads; t++) { - v_offsets[t] = v_offsets[t-1] + command_count[t-1].num_v; - n_offsets[t] = n_offsets[t-1] + command_count[t-1].num_vn; - t_offsets[t] = t_offsets[t-1] + command_count[t-1].num_vt; - f_offsets[t] = f_offsets[t-1] + command_count[t-1].num_f; - face_offsets[t] = face_offsets[t-1] + command_count[t-1].num_faces; - } - - StackVector workers; - - for (size_t t = 0; t < num_threads; t++) { - int material_id = -1; // -1 = default unknown material. - workers->push_back(std::thread([&, t]() { - size_t v_count = v_offsets[t]; - size_t n_count = n_offsets[t]; - size_t t_count = t_offsets[t]; - size_t f_count = f_offsets[t]; - size_t face_count = face_offsets[t]; - - for (size_t i = 0; i < commands[t].size(); i++) { - if (commands[t][i].type == COMMAND_EMPTY) { - continue; - } else if (commands[t][i].type == COMMAND_USEMTL) { - if (commands[t][i].material_name && commands[t][i].material_name_len > 0) { - std::string material_name(commands[t][i].material_name, commands[t][i].material_name_len); - - if (material_map.find(material_name) != material_map.end()) { - material_id = material_map[material_name]; - } else { - // Assign invalid material ID - material_id = -1; - } - } - } else if (commands[t][i].type == COMMAND_V) { - vertices[3*v_count+0] = commands[t][i].vx; - vertices[3*v_count+1] = commands[t][i].vy; - vertices[3*v_count+2] = commands[t][i].vz; - v_count++; - } else if (commands[t][i].type == COMMAND_VN) { - normals[3*n_count+0] = commands[t][i].nx; - normals[3*n_count+1] = commands[t][i].ny; - normals[3*n_count+2] = commands[t][i].nz; - n_count++; - } else if (commands[t][i].type == COMMAND_VT) { - texcoords[2*t_count+0] = commands[t][i].tx; - texcoords[2*t_count+1] = commands[t][i].ty; - t_count++; - } else if (commands[t][i].type == COMMAND_F) { - for (size_t k = 0; k < commands[t][i].f.size(); k++) { - vertex_index &vi = commands[t][i].f[k]; - int v_idx = fixIndex(vi.v_idx, v_count); - int vn_idx = fixIndex(vi.vn_idx, n_count); - int vt_idx = fixIndex(vi.vt_idx, t_count); - faces[f_count + k] = vertex_index(v_idx, vn_idx, vt_idx); - } - material_ids[face_count] = material_id; - - f_count += commands[t][i].f.size(); - face_count++; - } - } - })); - } - - for (size_t t = 0; t < workers->size(); t++) { - workers[t].join(); - } - - auto t_end = std::chrono::high_resolution_clock::now(); - ms_merge = t_end - t_start; - } - - auto t4 = std::chrono::high_resolution_clock::now(); - - // 5. Construct shape information. - { - auto t_start = std::chrono::high_resolution_clock::now(); - - // @todo { Can we boost the performance by multi-threaded execution? } - int face_count = 0; - shape_t shape; - shape.face_offset = 0; - shape.length = 0; - int face_prev_offset = 0; - for (size_t t = 0; t < num_threads; t++) { - for (size_t i = 0; i < commands[t].size(); i++) { - if (commands[t][i].type == COMMAND_O || commands[t][i].type == COMMAND_G) { - std::string name; - if (commands[t][i].type == COMMAND_O) { - name = std::string(commands[t][i].object_name, commands[t][i].object_name_len); - } else { - name = std::string(commands[t][i].group_name, commands[t][i].group_name_len); - } - - if (face_count == 0) { - // 'o' or 'g' appears before any 'f' - shape.name = name; - shape.face_offset = face_count; - face_prev_offset = face_count; - } else { - if (shapes.size() == 0) { - // 'o' or 'g' after some 'v' lines. - // create a shape with null name - shape.length = face_count - face_prev_offset; - face_prev_offset = face_count; - - shapes.push_back(shape); - - } else { - if ((face_count - face_prev_offset) > 0) { - // push previous shape - shape.length = face_count - face_prev_offset; - shapes.push_back(shape); - face_prev_offset = face_count; - } - } - - // redefine shape. - shape.name = name; - shape.face_offset = face_count; - shape.length = 0; - } - } else if (commands[t][i].type == COMMAND_G) { - std::cout << "g" << std::endl; - } - if (commands[t][i].type == COMMAND_F) { - face_count++; - } - } - } - - if ((face_count - face_prev_offset) > 0) { - shape.length = face_count - shape.face_offset; - if (shape.length > 0) { - shapes.push_back(shape); - } - } else { - // Guess no 'v' line occurrence after 'o' or 'g', so discards current shape information. - std::cout << "remainder " << shape.name << ", " << shape.face_offset << ", " << face_prev_offset << ", " << face_count << std::endl; - } - - auto t_end = std::chrono::high_resolution_clock::now(); - - ms_construct = t_end - t_start; - } - - std::chrono::duration ms_total = t4 - t1; - std::cout << "total parsing time: " << ms_total.count() << " ms\n"; - std::cout << " line detection :" << ms_linedetection.count() << " ms\n"; - std::cout << " alloc buf :" << ms_alloc.count() << " ms\n"; - std::cout << " parse :" << ms_parse.count() << " ms\n"; - std::cout << " merge :" << ms_merge.count() << " ms\n"; - std::cout << " construct :" << ms_construct.count() << " ms\n"; - std::cout << " mtl load :" << ms_load_mtl.count() << " ms\n"; - std::cout << "# of vertices = " << vertices.size() << std::endl; - std::cout << "# of normals = " << normals.size() << std::endl; - std::cout << "# of texcoords = " << texcoords.size() << std::endl; - std::cout << "# of face indices = " << faces.size() << std::endl; - std::cout << "# of faces = " << material_ids.size() << std::endl; - std::cout << "# of shapes = " << shapes.size() << std::endl; - - - return true; -} - -#ifdef CONSOLE_TEST -int -main(int argc, char **argv) -{ - - if (argc < 2) { - printf("Needs .obj\n"); - return EXIT_FAILURE; - } - - int req_num_threads = -1; - if (argc > 2) { - req_num_threads = atoi(argv[2]); - } - -#ifdef _WIN64 - HANDLE file = CreateFileA(argv[1], GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, NULL); - assert(file != INVALID_HANDLE_VALUE); - - HANDLE fileMapping = CreateFileMapping(file, NULL, PAGE_READONLY, 0, 0, NULL); - assert(fileMapping != INVALID_HANDLE_VALUE); - - LPVOID fileMapView = MapViewOfFile(fileMapping, FILE_MAP_READ, 0, 0, 0); - auto fileMapViewChar = (const char*)fileMapView; - assert(fileMapView != NULL); -#else - - FILE* f = fopen(argv[1], "r" ); - fseek(f, 0, SEEK_END); - long fileSize = ftell(f); - fclose(f); - - struct stat sb; - char *p; - int fd; - - fd = open (argv[1], O_RDONLY); - if (fd == -1) { - perror ("open"); - return 1; - } - - if (fstat (fd, &sb) == -1) { - perror ("fstat"); - return 1; - } - - if (!S_ISREG (sb.st_mode)) { - fprintf (stderr, "%s is not a file\n", "lineitem.tbl"); - return 1; - } - - p = (char*)mmap (0, fileSize, PROT_READ, MAP_SHARED, fd, 0); - - if (p == MAP_FAILED) { - perror ("mmap"); - return 1; - } - - if (close (fd) == -1) { - perror ("close"); - return 1; - } - - printf("fsize: %lu\n", fileSize); -#endif - - parse(p, fileSize, req_num_threads); - - return EXIT_SUCCESS; -} -#endif diff --git a/experimental/tinyobj_loader_opt.h b/experimental/tinyobj_loader_opt.h new file mode 100644 index 0000000..bfb550c --- /dev/null +++ b/experimental/tinyobj_loader_opt.h @@ -0,0 +1,1562 @@ +// +// Optimized wavefront .obj loader. +// Requires ltalloc and C++11 +// + +/* +The MIT License (MIT) + +Copyright (c) 2012-2016 Syoyo Fujita and many contributors. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ +#ifndef TINOBJ_LOADER_OPT_H_ +#define TINOBJ_LOADER_OPT_H_ + +#ifdef _WIN64 +#define atoll(S) _atoi64(S) +#include +#else +#include +#include +#include +#include +#include +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include // C++11 +#include // C++11 +#include // C++11 + +#include "ltalloc.hpp" + +namespace tinyobj_opt { + +// ---------------------------------------------------------------------------- +// Small vector class useful for multi-threaded environment. +// +// stack_container.h +// +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This allocator can be used with STL containers to provide a stack buffer +// from which to allocate memory and overflows onto the heap. This stack buffer +// would be allocated on the stack and allows us to avoid heap operations in +// some situations. +// +// STL likes to make copies of allocators, so the allocator itself can't hold +// the data. Instead, we make the creator responsible for creating a +// StackAllocator::Source which contains the data. Copying the allocator +// merely copies the pointer to this shared source, so all allocators created +// based on our allocator will share the same stack buffer. +// +// This stack buffer implementation is very simple. The first allocation that +// fits in the stack buffer will use the stack buffer. Any subsequent +// allocations will not use the stack buffer, even if there is unused room. +// This makes it appropriate for array-like containers, but the caller should +// be sure to reserve() in the container up to the stack buffer size. Otherwise +// the container will allocate a small array which will "use up" the stack +// buffer. +template +class StackAllocator : public std::allocator { + public: + typedef typename std::allocator::pointer pointer; + typedef typename std::allocator::size_type size_type; + + // Backing store for the allocator. The container owner is responsible for + // maintaining this for as long as any containers using this allocator are + // live. + struct Source { + Source() : used_stack_buffer_(false) {} + + // Casts the buffer in its right type. + T *stack_buffer() { return reinterpret_cast(stack_buffer_); } + const T *stack_buffer() const { + return reinterpret_cast(stack_buffer_); + } + + // + // IMPORTANT: Take care to ensure that stack_buffer_ is aligned + // since it is used to mimic an array of T. + // Be careful while declaring any unaligned types (like bool) + // before stack_buffer_. + // + + // The buffer itself. It is not of type T because we don't want the + // constructors and destructors to be automatically called. Define a POD + // buffer of the right size instead. + char stack_buffer_[sizeof(T[stack_capacity])]; + + // Set when the stack buffer is used for an allocation. We do not track + // how much of the buffer is used, only that somebody is using it. + bool used_stack_buffer_; + }; + + // Used by containers when they want to refer to an allocator of type U. + template + struct rebind { + typedef StackAllocator other; + }; + + // For the straight up copy c-tor, we can share storage. + StackAllocator(const StackAllocator &rhs) + : source_(rhs.source_) {} + + // ISO C++ requires the following constructor to be defined, + // and std::vector in VC++2008SP1 Release fails with an error + // in the class _Container_base_aux_alloc_real (from ) + // if the constructor does not exist. + // For this constructor, we cannot share storage; there's + // no guarantee that the Source buffer of Ts is large enough + // for Us. + // TODO(Google): If we were fancy pants, perhaps we could share storage + // iff sizeof(T) == sizeof(U). + template + StackAllocator(const StackAllocator &other) + : source_(NULL) { + (void)other; + } + + explicit StackAllocator(Source *source) : source_(source) {} + + // Actually do the allocation. Use the stack buffer if nobody has used it yet + // and the size requested fits. Otherwise, fall through to the standard + // allocator. + pointer allocate(size_type n, void *hint = 0) { + if (source_ != NULL && !source_->used_stack_buffer_ && + n <= stack_capacity) { + source_->used_stack_buffer_ = true; + return source_->stack_buffer(); + } else { + return std::allocator::allocate(n, hint); + } + } + + // Free: when trying to free the stack buffer, just mark it as free. For + // non-stack-buffer pointers, just fall though to the standard allocator. + void deallocate(pointer p, size_type n) { + if (source_ != NULL && p == source_->stack_buffer()) + source_->used_stack_buffer_ = false; + else + std::allocator::deallocate(p, n); + } + + private: + Source *source_; +}; + +// A wrapper around STL containers that maintains a stack-sized buffer that the +// initial capacity of the vector is based on. Growing the container beyond the +// stack capacity will transparently overflow onto the heap. The container must +// support reserve(). +// +// WATCH OUT: the ContainerType MUST use the proper StackAllocator for this +// type. This object is really intended to be used only internally. You'll want +// to use the wrappers below for different types. +template +class StackContainer { + public: + typedef TContainerType ContainerType; + typedef typename ContainerType::value_type ContainedType; + typedef StackAllocator Allocator; + + // Allocator must be constructed before the container! + StackContainer() : allocator_(&stack_data_), container_(allocator_) { + // Make the container use the stack allocation by reserving our buffer size + // before doing anything else. + container_.reserve(stack_capacity); + } + + // Getters for the actual container. + // + // Danger: any copies of this made using the copy constructor must have + // shorter lifetimes than the source. The copy will share the same allocator + // and therefore the same stack buffer as the original. Use std::copy to + // copy into a "real" container for longer-lived objects. + ContainerType &container() { return container_; } + const ContainerType &container() const { return container_; } + + // Support operator-> to get to the container. This allows nicer syntax like: + // StackContainer<...> foo; + // std::sort(foo->begin(), foo->end()); + ContainerType *operator->() { return &container_; } + const ContainerType *operator->() const { return &container_; } + +#ifdef UNIT_TEST + // Retrieves the stack source so that that unit tests can verify that the + // buffer is being used properly. + const typename Allocator::Source &stack_data() const { return stack_data_; } +#endif + + protected: + typename Allocator::Source stack_data_; + unsigned char pad_[7]; + Allocator allocator_; + ContainerType container_; + + // DISALLOW_EVIL_CONSTRUCTORS(StackContainer); + StackContainer(const StackContainer &); + void operator=(const StackContainer &); +}; + +// StackVector +// +// Example: +// StackVector foo; +// foo->push_back(22); // we have overloaded operator-> +// foo[0] = 10; // as well as operator[] +template +class StackVector + : public StackContainer >, + stack_capacity> { + public: + StackVector() + : StackContainer >, + stack_capacity>() {} + + // We need to put this in STL containers sometimes, which requires a copy + // constructor. We can't call the regular copy constructor because that will + // take the stack buffer from the original. Here, we create an empty object + // and make a stack buffer of its own. + StackVector(const StackVector &other) + : StackContainer >, + stack_capacity>() { + this->container().assign(other->begin(), other->end()); + } + + StackVector &operator=( + const StackVector &other) { + this->container().assign(other->begin(), other->end()); + return *this; + } + + // Vectors are commonly indexed, which isn't very convenient even with + // operator-> (using "->at()" does exception stuff we don't want). + T &operator[](size_t i) { return this->container().operator[](i); } + const T &operator[](size_t i) const { + return this->container().operator[](i); + } +}; + +// ---------------------------------------------------------------------------- + +typedef struct { + 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; + + int dummy; // Suppress padding warning. + + std::string ambient_texname; // map_Ka + std::string diffuse_texname; // map_Kd + std::string specular_texname; // map_Ks + std::string specular_highlight_texname; // map_Ns + std::string bump_texname; // map_bump, bump + std::string displacement_texname; // disp + std::string alpha_texname; // map_d + std::map unknown_parameter; +} material_t; + +typedef struct { + std::string name; // group name or object name. + unsigned int face_offset; + unsigned int length; +} shape_t; + +struct vertex_index { + int v_idx, vt_idx, vn_idx; + vertex_index() : v_idx(-1), vt_idx(-1), vn_idx(-1) {} + explicit 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) {} +}; + +typedef struct { + std::vector > vertices; + std::vector > normals; + std::vector > texcoords; + std::vector > faces; + std::vector > face_num_verts; + std::vector > material_ids; +} attrib_t; + +typedef StackVector ShortString; + +#define IS_SPACE(x) (((x) == ' ') || ((x) == '\t')) +#define IS_DIGIT(x) \ + (static_cast((x) - '0') < static_cast(10)) +#define IS_NEW_LINE(x) (((x) == '\r') || ((x) == '\n') || ((x) == '\0')) + +static inline void skip_space(const char **token) { + while ((*token)[0] == ' ' || (*token)[0] == '\t') { + (*token)++; + } +} + +static inline void skip_space_and_cr(const char **token) { + while ((*token)[0] == ' ' || (*token)[0] == '\t' || (*token)[0] == '\r') { + (*token)++; + } +} + +static inline int until_space(const char *token) { + const char *p = token; + while (p[0] != '\0' && p[0] != ' ' && p[0] != '\t' && p[0] != '\r') { + p++; + } + + return p - token; +} + +static inline int length_until_newline(const char *token, int n) { + int len = 0; + + // Assume token[n-1] = '\0' + for (len = 0; len < n - 1; len++) { + if (token[len] == '\n') { + break; + } + if ((token[len] == '\r') && ((len < (n - 2)) && (token[len + 1] != '\n'))) { + break; + } + } + + return len; +} + +// http://stackoverflow.com/questions/5710091/how-does-atoi-function-in-c-work +static inline int my_atoi(const char *c) { + int value = 0; + int sign = 1; + if (*c == '+' || *c == '-') { + if (*c == '-') sign = -1; + c++; + } + while (((*c) >= '0') && ((*c) <= '9')) { // isdigit(*c) + value *= 10; + value += (int)(*c - '0'); + c++; + } + return value * sign; +} + +// 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 +} + +// Parse raw triples: i, i/j/k, i//k, i/j +static vertex_index parseRawTriple(const char **token) { + vertex_index vi( + static_cast(0x80000000)); // 0x80000000 = -2147483648 = invalid + + vi.v_idx = my_atoi((*token)); + //(*token) += strcspn((*token), "/ \t\r"); + while ((*token)[0] != '\0' && (*token)[0] != '/' && (*token)[0] != ' ' && + (*token)[0] != '\t' && (*token)[0] != '\r') { + (*token)++; + } + if ((*token)[0] != '/') { + return vi; + } + (*token)++; + + // i//k + if ((*token)[0] == '/') { + (*token)++; + vi.vn_idx = my_atoi((*token)); + //(*token) += strcspn((*token), "/ \t\r"); + while ((*token)[0] != '\0' && (*token)[0] != '/' && (*token)[0] != ' ' && + (*token)[0] != '\t' && (*token)[0] != '\r') { + (*token)++; + } + return vi; + } + + // i/j/k or i/j + vi.vt_idx = my_atoi((*token)); + //(*token) += strcspn((*token), "/ \t\r"); + while ((*token)[0] != '\0' && (*token)[0] != '/' && (*token)[0] != ' ' && + (*token)[0] != '\t' && (*token)[0] != '\r') { + (*token)++; + } + if ((*token)[0] != '/') { + return vi; + } + + // i/j/k + (*token)++; // skip '/' + vi.vn_idx = my_atoi((*token)); + //(*token) += strcspn((*token), "/ \t\r"); + while ((*token)[0] != '\0' && (*token)[0] != '/' && (*token)[0] != ' ' && + (*token)[0] != '\t' && (*token)[0] != '\r') { + (*token)++; + } + return vi; +} + +static inline bool parseString(ShortString *s, const char **token) { + skip_space(token); //(*token) += strspn((*token), " \t"); + size_t e = until_space((*token)); // strcspn((*token), " \t\r"); + (*s)->insert((*s)->end(), (*token), (*token) + e); + (*token) += e; + return true; +} + +static inline int parseInt(const char **token) { + skip_space(token); //(*token) += strspn((*token), " \t"); + int i = my_atoi((*token)); + (*token) += until_space((*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 +// stop. For example at the end of the string, to prevent buffer overflows. +// +// Parses the following EBNF grammar: +// sign = "+" | "-" ; +// END = ? anything not in digit ? +// digit = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" ; +// integer = [sign] , digit , {digit} ; +// decimal = integer , ["." , integer] ; +// float = ( decimal , END ) | ( decimal , ("E" | "e") , integer , END ) ; +// +// 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 +// is returned. +// +// The function is greedy and will parse until any of the following happens: +// - a non-conforming character is encountered. +// - s_end is reached. +// +// 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) { + 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 + // 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 + // exponent to be in base 2. + int exponent = 0; + + // NOTE: THESE MUST BE DECLARED HERE SINCE WE ARE NOT ALLOWED + // TO JUMP OVER DEFINITIONS. + char sign = '+'; + char exp_sign = '+'; + char const *curr = s; + + // 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; + + /* + BEGIN PARSING. + */ + + // Find out what sign we've got. + if (*curr == '+' || *curr == '-') { + sign = *curr; + curr++; + } else if (IS_DIGIT(*curr)) { /* Pass through. */ + } else { + goto fail; + } + + // Read the integer part. + end_not_reached = (curr != s_end); + while (end_not_reached && IS_DIGIT(*curr)) { + mantissa *= 10; + mantissa += static_cast(*curr - 0x30); + curr++; + read++; + end_not_reached = (curr != s_end); + } + + // We must make sure we actually got something. + if (read == 0) goto fail; + // We allow numbers of form "#", "###" etc. + if (!end_not_reached) goto assemble; + + // Read the decimal part. + if (*curr == '.') { + curr++; + read = 1; + end_not_reached = (curr != s_end); + while (end_not_reached && IS_DIGIT(*curr)) { + // pow(10.0, -read) + double frac_value = 1.0; + for (int f = 0; f < read; f++) { + frac_value *= 0.1; + } + mantissa += static_cast(*curr - 0x30) * frac_value; + read++; + curr++; + end_not_reached = (curr != s_end); + } + } else if (*curr == 'e' || *curr == 'E') { + } else { + goto assemble; + } + + if (!end_not_reached) goto assemble; + + // Read the exponent part. + if (*curr == 'e' || *curr == 'E') { + curr++; + // Figure out if a sign is present and if it is. + end_not_reached = (curr != s_end); + if (end_not_reached && (*curr == '+' || *curr == '-')) { + exp_sign = *curr; + curr++; + } else if (IS_DIGIT(*curr)) { /* Pass through. */ + } else { + // Empty E is not allowed. + goto fail; + } + + read = 0; + end_not_reached = (curr != s_end); + while (end_not_reached && IS_DIGIT(*curr)) { + exponent *= 10; + exponent += static_cast(*curr - 0x30); + curr++; + read++; + end_not_reached = (curr != s_end); + } + exponent *= (exp_sign == '+' ? 1 : -1); + if (read == 0) goto fail; + } + +assemble : + +{ + // = pow(5.0, exponent); + double a = 5.0; + for (int i = 0; i < exponent; i++) { + a = a * a; + } + *result = + //(sign == '+' ? 1 : -1) * ldexp(mantissa * pow(5.0, exponent), exponent); + (sign == '+' ? 1 : -1) * (mantissa * a) * + static_cast(1ULL << exponent); // 5.0^exponent * 2^exponent +} + + return true; +fail: + return false; +} + +static inline float parseFloat(const char **token) { + skip_space(token); //(*token) += strspn((*token), " \t"); +#ifdef TINY_OBJ_LOADER_OLD_FLOAT_PARSER + float f = static_cast(atof(*token)); + (*token) += strcspn((*token), " \t\r"); +#else + const char *end = + (*token) + until_space((*token)); // strcspn((*token), " \t\r"); + double val = 0.0; + tryParseDouble((*token), end, &val); + float f = static_cast(val); + (*token) = end; +#endif + return f; +} + +static inline void parseFloat2(float *x, float *y, const char **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); +} + +static void InitMaterial(material_t *material) { + material->name = ""; + material->ambient_texname = ""; + material->diffuse_texname = ""; + material->specular_texname = ""; + material->specular_highlight_texname = ""; + material->bump_texname = ""; + material->displacement_texname = ""; + material->alpha_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 void LoadMtl(std::map *material_map, + std::vector *materials, + std::istream *inStream) { + // Create a default material anyway. + 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]); + + // 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 leading space. + const char *token = linebuf.c_str(); + token += strspn(token, " \t"); + + assert(token); + if (token[0] == '\0') continue; // empty line + + if (token[0] == '#') continue; // comment line + + // new mtl + 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); + } + + // initial temporary material + InitMaterial(&material); + + // set new mtl name + char namebuf[4096]; + token += 7; +#ifdef _MSC_VER + sscanf_s(token, "%s", namebuf, (unsigned)_countof(namebuf)); +#else + sscanf(token, "%s", namebuf); +#endif + material.name = namebuf; + continue; + } + + // ambient + if (token[0] == 'K' && token[1] == 'a' && IS_SPACE((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' && IS_SPACE((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' && IS_SPACE((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' && IS_SPACE((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' && IS_SPACE((token[2]))) { + token += 2; + material.ior = parseFloat(&token); + continue; + } + + // emission + if (token[0] == 'K' && token[1] == 'e' && IS_SPACE(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' && IS_SPACE(token[2])) { + token += 2; + material.shininess = parseFloat(&token); + continue; + } + + // illum model + if (0 == strncmp(token, "illum", 5) && IS_SPACE(token[5])) { + token += 6; + material.illum = parseInt(&token); + continue; + } + + // dissolve + if ((token[0] == 'd' && IS_SPACE(token[1]))) { + token += 1; + material.dissolve = parseFloat(&token); + continue; + } + if (token[0] == 'T' && token[1] == 'r' && IS_SPACE(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)) && IS_SPACE(token[6])) { + token += 7; + material.ambient_texname = token; + continue; + } + + // diffuse texture + if ((0 == strncmp(token, "map_Kd", 6)) && IS_SPACE(token[6])) { + token += 7; + material.diffuse_texname = token; + continue; + } + + // specular texture + if ((0 == strncmp(token, "map_Ks", 6)) && IS_SPACE(token[6])) { + token += 7; + material.specular_texname = token; + continue; + } + + // specular highlight texture + if ((0 == strncmp(token, "map_Ns", 6)) && IS_SPACE(token[6])) { + token += 7; + material.specular_highlight_texname = token; + continue; + } + + // bump texture + if ((0 == strncmp(token, "map_bump", 8)) && IS_SPACE(token[8])) { + token += 9; + material.bump_texname = token; + continue; + } + + // alpha texture + if ((0 == strncmp(token, "map_d", 5)) && IS_SPACE(token[5])) { + token += 6; + material.alpha_texname = token; + continue; + } + + // bump texture + if ((0 == strncmp(token, "bump", 4)) && IS_SPACE(token[4])) { + token += 5; + material.bump_texname = token; + continue; + } + + // displacement texture + if ((0 == strncmp(token, "disp", 4)) && IS_SPACE(token[4])) { + token += 5; + material.displacement_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, static_cast(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); +} + +typedef enum { + COMMAND_EMPTY, + COMMAND_V, + COMMAND_VN, + COMMAND_VT, + COMMAND_F, + COMMAND_G, + COMMAND_O, + COMMAND_USEMTL, + COMMAND_MTLLIB, + +} CommandType; + +typedef struct { + float vx, vy, vz; + float nx, ny, nz; + float tx, ty; + + // for f + std::vector > f; + // std::vector f; + std::vector > f_num_verts; + + const char *group_name; + unsigned int group_name_len; + const char *object_name; + unsigned int object_name_len; + const char *material_name; + unsigned int material_name_len; + + const char *mtllib_name; + unsigned int mtllib_name_len; + + CommandType type; +} Command; + +struct CommandCount { + size_t num_v; + size_t num_vn; + size_t num_vt; + size_t num_f; + size_t num_faces; + CommandCount() { + num_v = 0; + num_vn = 0; + num_vt = 0; + num_f = 0; + num_faces = 0; + } +}; + +/// Parse wavefront .obj(.obj string data is expanded to linear char array +/// `buf') +/// -1 to req_num_threads use the number of HW threads in the running system. +bool parseObj(attrib_t *attrib, std::vector *shapes, const char *buf, + size_t len, int req_num_threads = -1, bool triangulate = true); + +#ifdef TINYOBJ_LOADER_OPT_IMPLEMENTATION + +static bool parseLine(Command *command, const char *p, size_t p_len, + bool triangulate = true) { + char linebuf[4096]; + assert(p_len < 4095); + // StackVector linebuf; + // linebuf->resize(p_len + 1); + memcpy(&linebuf, p, p_len); + linebuf[p_len] = '\0'; + + const char *token = linebuf; + + command->type = COMMAND_EMPTY; + + // Skip leading space. + // token += strspn(token, " \t"); + skip_space(&token); //(*token) += strspn((*token), " \t"); + + assert(token); + if (token[0] == '\0') { // empty line + return false; + } + + if (token[0] == '#') { // comment line + return false; + } + + // vertex + if (token[0] == 'v' && IS_SPACE((token[1]))) { + token += 2; + float x, y, z; + parseFloat3(&x, &y, &z, &token); + command->vx = x; + command->vy = y; + command->vz = z; + command->type = COMMAND_V; + return true; + } + + // normal + if (token[0] == 'v' && token[1] == 'n' && IS_SPACE((token[2]))) { + token += 3; + float x, y, z; + parseFloat3(&x, &y, &z, &token); + command->nx = x; + command->ny = y; + command->nz = z; + command->type = COMMAND_VN; + return true; + } + + // texcoord + if (token[0] == 'v' && token[1] == 't' && IS_SPACE((token[2]))) { + token += 3; + float x, y; + parseFloat2(&x, &y, &token); + command->tx = x; + command->ty = y; + command->type = COMMAND_VT; + return true; + } + + // face + if (token[0] == 'f' && IS_SPACE((token[1]))) { + token += 2; + // token += strspn(token, " \t"); + skip_space(&token); + + StackVector f; + + while (!IS_NEW_LINE(token[0])) { + vertex_index vi = parseRawTriple(&token); + // printf("v = %d, %d, %d\n", vi.v_idx, vi.vn_idx, vi.vt_idx); + // if (callback.index_cb) { + // callback.index_cb(user_data, vi.v_idx, vi.vn_idx, vi.vt_idx); + //} + // size_t n = strspn(token, " \t\r"); + // token += n; + skip_space_and_cr(&token); + + f->push_back(vi); + } + + command->type = COMMAND_F; + + if (triangulate) { + vertex_index i0 = f[0]; + vertex_index i1(-1); + vertex_index i2 = f[1]; + + for (size_t k = 2; k < f->size(); k++) { + i1 = i2; + i2 = f[k]; + command->f.emplace_back(i0); + command->f.emplace_back(i1); + command->f.emplace_back(i2); + + command->f_num_verts.emplace_back(3); + } + + } else { + for (size_t k = 0; k < f->size(); k++) { + command->f.emplace_back(f[k]); + } + + command->f_num_verts.emplace_back(f->size()); + } + + return true; + } + + // use mtl + if ((0 == strncmp(token, "usemtl", 6)) && IS_SPACE((token[6]))) { + token += 7; + + // int newMaterialId = -1; + // if (material_map.find(namebuf) != material_map.end()) { + // newMaterialId = material_map[namebuf]; + //} else { + // // { error!! material not found } + //} + + // if (newMaterialId != materialId) { + // materialId = newMaterialId; + //} + + // command->material_name = .insert(command->material_name->end(), namebuf, + // namebuf + strlen(namebuf)); + // command->material_name->push_back('\0'); + skip_space(&token); + command->material_name = p + (token - linebuf); + command->material_name_len = + length_until_newline(token, p_len - (token - linebuf)) + 1; + command->type = COMMAND_USEMTL; + + return true; + } + + // load mtl + if ((0 == strncmp(token, "mtllib", 6)) && IS_SPACE((token[6]))) { + // By specification, `mtllib` should be appear only once in .obj + token += 7; + + skip_space(&token); + command->mtllib_name = p + (token - linebuf); + command->mtllib_name_len = + length_until_newline(token, p_len - (token - linebuf)) + 1; + command->type = COMMAND_MTLLIB; + + return true; + } + + // group name + if (token[0] == 'g' && IS_SPACE((token[1]))) { + // @todo { multiple group name. } + token += 2; + + command->group_name = p + (token - linebuf); + command->group_name_len = + length_until_newline(token, p_len - (token - linebuf)) + 1; + command->type = COMMAND_G; + + return true; + } + + // object name + if (token[0] == 'o' && IS_SPACE((token[1]))) { + // @todo { multiple object name? } + token += 2; + + command->object_name = p + (token - linebuf); + command->object_name_len = + length_until_newline(token, p_len - (token - linebuf)) + 1; + command->type = COMMAND_O; + + return true; + } + + return false; +} + +typedef struct { + size_t pos; + size_t len; +} LineInfo; + +// Idea come from https://github.com/antonmks/nvParse +// 1. mmap file +// 2. find newline(\n, \r\n, \r) and list of line data. +// 3. Do parallel parsing for each line. +// 4. Reconstruct final mesh data structure. + +#define kMaxThreads (32) + +static inline bool is_line_ending(const char *p, size_t i, size_t end_i) { + if (p[i] == '\0') return true; + if (p[i] == '\n') return true; // this includes \r\n + if (p[i] == '\r') { + if (((i + 1) < end_i) && (p[i + 1] != '\n')) { // detect only \r case + return true; + } + } + return false; +} + +bool parseObj(attrib_t *attrib, std::vector *shapes, const char *buf, + size_t len, int req_num_threads, bool triangulate) { + attrib->vertices.clear(); + attrib->normals.clear(); + attrib->texcoords.clear(); + attrib->faces.clear(); + attrib->face_num_verts.clear(); + attrib->material_ids.clear(); + shapes->clear(); + + if (len < 1) return false; + + auto num_threads = (req_num_threads < 0) ? std::thread::hardware_concurrency() + : req_num_threads; + num_threads = + std::max(1, std::min(static_cast(num_threads), kMaxThreads)); + std::cout << "# of threads = " << num_threads << std::endl; + + auto t1 = std::chrono::high_resolution_clock::now(); + + std::vector > line_infos[kMaxThreads]; + for (size_t t = 0; t < static_cast(num_threads); t++) { + // Pre allocate enough memory. len / 128 / num_threads is just a heuristic + // value. + line_infos[t].reserve(len / 128 / num_threads); + } + + std::chrono::duration ms_linedetection; + std::chrono::duration ms_alloc; + std::chrono::duration ms_parse; + std::chrono::duration ms_load_mtl; + std::chrono::duration ms_merge; + std::chrono::duration ms_construct; + + // 1. Find '\n' and create line data. + { + StackVector workers; + + auto start_time = std::chrono::high_resolution_clock::now(); + auto chunk_size = len / num_threads; + + for (size_t t = 0; t < static_cast(num_threads); t++) { + workers->push_back(std::thread([&, t]() { + auto start_idx = (t + 0) * chunk_size; + auto end_idx = std::min((t + 1) * chunk_size, len - 1); + if (t == static_cast((num_threads - 1))) { + end_idx = len - 1; + } + + size_t prev_pos = start_idx; + for (size_t i = start_idx; i < end_idx; i++) { + if (is_line_ending(buf, i, end_idx)) { + if ((t > 0) && (prev_pos == start_idx) && + (!is_line_ending(buf, start_idx - 1, end_idx))) { + // first linebreak found in (chunk > 0), and a line before this + // linebreak belongs to previous chunk, so skip it. + prev_pos = i + 1; + continue; + } else { + LineInfo info; + info.pos = prev_pos; + info.len = i - prev_pos; + + if (info.len > 0) { + line_infos[t].push_back(info); + } + + prev_pos = i + 1; + } + } + } + + // Find extra line which spand across chunk boundary. + if ((t < num_threads) && (buf[end_idx - 1] != '\n')) { + auto extra_span_idx = std::min(end_idx - 1 + chunk_size, len - 1); + for (size_t i = end_idx; i < extra_span_idx; i++) { + if (is_line_ending(buf, i, extra_span_idx)) { + LineInfo info; + info.pos = prev_pos; + info.len = i - prev_pos; + + if (info.len > 0) { + line_infos[t].push_back(info); + } + + break; + } + } + } + })); + } + + for (size_t t = 0; t < workers->size(); t++) { + workers[t].join(); + } + + auto end_time = std::chrono::high_resolution_clock::now(); + + ms_linedetection = end_time - start_time; + } + + auto line_sum = 0; + for (size_t t = 0; t < num_threads; t++) { + // std::cout << t << ": # of lines = " << line_infos[t].size() << std::endl; + line_sum += line_infos[t].size(); + } + // std::cout << "# of lines = " << line_sum << std::endl; + + std::vector commands[kMaxThreads]; + + // 2. allocate buffer + auto t_alloc_start = std::chrono::high_resolution_clock::now(); + { + for (size_t t = 0; t < num_threads; t++) { + commands[t].reserve(line_infos[t].size()); + } + } + + CommandCount command_count[kMaxThreads]; + // Array index to `mtllib` line. According to wavefront .obj spec, `mtllib' + // should appear only once in .obj. + int mtllib_t_index = -1; + int mtllib_i_index = -1; + + ms_alloc = std::chrono::high_resolution_clock::now() - t_alloc_start; + + // 2. parse each line in parallel. + { + StackVector workers; + auto t_start = std::chrono::high_resolution_clock::now(); + + for (size_t t = 0; t < num_threads; t++) { + workers->push_back(std::thread([&, t]() { + + for (size_t i = 0; i < line_infos[t].size(); i++) { + Command command; + bool ret = parseLine(&command, &buf[line_infos[t][i].pos], + line_infos[t][i].len, triangulate); + if (ret) { + if (command.type == COMMAND_V) { + command_count[t].num_v++; + } else if (command.type == COMMAND_VN) { + command_count[t].num_vn++; + } else if (command.type == COMMAND_VT) { + command_count[t].num_vt++; + } else if (command.type == COMMAND_F) { + command_count[t].num_f += command.f.size(); + command_count[t].num_faces++; + } + + if (command.type == COMMAND_MTLLIB) { + mtllib_t_index = t; + mtllib_i_index = commands->size(); + } + + commands[t].emplace_back(std::move(command)); + } + } + + })); + } + + for (size_t t = 0; t < workers->size(); t++) { + workers[t].join(); + } + + auto t_end = std::chrono::high_resolution_clock::now(); + + ms_parse = t_end - t_start; + } + + std::map material_map; + std::vector materials; + + // Load material(if exits) + if (mtllib_i_index >= 0 && mtllib_t_index >= 0 && + commands[mtllib_t_index][mtllib_i_index].mtllib_name && + commands[mtllib_t_index][mtllib_i_index].mtllib_name_len > 0) { + std::string material_filename = + std::string(commands[mtllib_t_index][mtllib_i_index].mtllib_name, + commands[mtllib_t_index][mtllib_i_index].mtllib_name_len); + // std::cout << "mtllib :" << material_filename << std::endl; + + auto t1 = std::chrono::high_resolution_clock::now(); + + std::ifstream ifs(material_filename); + if (ifs.good()) { + LoadMtl(&material_map, &materials, &ifs); + + // std::cout << "maetrials = " << materials.size() << std::endl; + + ifs.close(); + } + + auto t2 = std::chrono::high_resolution_clock::now(); + + ms_load_mtl = t2 - t1; + } + + auto command_sum = 0; + for (size_t t = 0; t < num_threads; t++) { + // std::cout << t << ": # of commands = " << commands[t].size() << + // std::endl; + command_sum += commands[t].size(); + } + // std::cout << "# of commands = " << command_sum << std::endl; + + size_t num_v = 0; + size_t num_vn = 0; + size_t num_vt = 0; + size_t num_f = 0; + size_t num_faces = 0; + for (size_t t = 0; t < num_threads; t++) { + num_v += command_count[t].num_v; + num_vn += command_count[t].num_vn; + num_vt += command_count[t].num_vt; + num_f += command_count[t].num_f; + num_faces += command_count[t].num_faces; + } + // std::cout << "# v " << num_v << std::endl; + // std::cout << "# vn " << num_vn << std::endl; + // std::cout << "# vt " << num_vt << std::endl; + // std::cout << "# f " << num_f << std::endl; + + // 4. merge + // @todo { parallelize merge. } + { + auto t_start = std::chrono::high_resolution_clock::now(); + + attrib->vertices.resize(num_v * 3); + attrib->normals.resize(num_vn * 3); + attrib->texcoords.resize(num_vt * 2); + attrib->faces.resize(num_f); + attrib->face_num_verts.resize(num_faces); + attrib->material_ids.resize(num_faces); + + size_t v_offsets[kMaxThreads]; + size_t n_offsets[kMaxThreads]; + size_t t_offsets[kMaxThreads]; + size_t f_offsets[kMaxThreads]; + size_t face_offsets[kMaxThreads]; + + v_offsets[0] = 0; + n_offsets[0] = 0; + t_offsets[0] = 0; + f_offsets[0] = 0; + face_offsets[0] = 0; + + for (size_t t = 1; t < num_threads; t++) { + v_offsets[t] = v_offsets[t - 1] + command_count[t - 1].num_v; + n_offsets[t] = n_offsets[t - 1] + command_count[t - 1].num_vn; + t_offsets[t] = t_offsets[t - 1] + command_count[t - 1].num_vt; + f_offsets[t] = f_offsets[t - 1] + command_count[t - 1].num_f; + face_offsets[t] = face_offsets[t - 1] + command_count[t - 1].num_faces; + } + + StackVector workers; + + for (size_t t = 0; t < num_threads; t++) { + int material_id = -1; // -1 = default unknown material. + workers->push_back(std::thread([&, t]() { + size_t v_count = v_offsets[t]; + size_t n_count = n_offsets[t]; + size_t t_count = t_offsets[t]; + size_t f_count = f_offsets[t]; + size_t face_count = face_offsets[t]; + + for (size_t i = 0; i < commands[t].size(); i++) { + if (commands[t][i].type == COMMAND_EMPTY) { + continue; + } else if (commands[t][i].type == COMMAND_USEMTL) { + if (commands[t][i].material_name && + commands[t][i].material_name_len > 0) { + std::string material_name(commands[t][i].material_name, + commands[t][i].material_name_len); + + if (material_map.find(material_name) != material_map.end()) { + material_id = material_map[material_name]; + } else { + // Assign invalid material ID + material_id = -1; + } + } + } else if (commands[t][i].type == COMMAND_V) { + attrib->vertices[3 * v_count + 0] = commands[t][i].vx; + attrib->vertices[3 * v_count + 1] = commands[t][i].vy; + attrib->vertices[3 * v_count + 2] = commands[t][i].vz; + v_count++; + } else if (commands[t][i].type == COMMAND_VN) { + attrib->normals[3 * n_count + 0] = commands[t][i].nx; + attrib->normals[3 * n_count + 1] = commands[t][i].ny; + attrib->normals[3 * n_count + 2] = commands[t][i].nz; + n_count++; + } else if (commands[t][i].type == COMMAND_VT) { + attrib->texcoords[2 * t_count + 0] = commands[t][i].tx; + attrib->texcoords[2 * t_count + 1] = commands[t][i].ty; + t_count++; + } else if (commands[t][i].type == COMMAND_F) { + for (size_t k = 0; k < commands[t][i].f.size(); k++) { + vertex_index &vi = commands[t][i].f[k]; + int v_idx = fixIndex(vi.v_idx, v_count); + int vn_idx = fixIndex(vi.vn_idx, n_count); + int vt_idx = fixIndex(vi.vt_idx, t_count); + attrib->faces[f_count + k] = vertex_index(v_idx, vn_idx, vt_idx); + } + attrib->material_ids[face_count] = material_id; + attrib->face_num_verts[face_count] = commands[t][i].f.size(); + + f_count += commands[t][i].f.size(); + face_count++; + } + } + })); + } + + for (size_t t = 0; t < workers->size(); t++) { + workers[t].join(); + } + + auto t_end = std::chrono::high_resolution_clock::now(); + ms_merge = t_end - t_start; + } + + auto t4 = std::chrono::high_resolution_clock::now(); + + // 5. Construct shape information. + { + auto t_start = std::chrono::high_resolution_clock::now(); + + // @todo { Can we boost the performance by multi-threaded execution? } + int face_count = 0; + shape_t shape; + shape.face_offset = 0; + shape.length = 0; + int face_prev_offset = 0; + for (size_t t = 0; t < num_threads; t++) { + for (size_t i = 0; i < commands[t].size(); i++) { + if (commands[t][i].type == COMMAND_O || + commands[t][i].type == COMMAND_G) { + std::string name; + if (commands[t][i].type == COMMAND_O) { + name = std::string(commands[t][i].object_name, + commands[t][i].object_name_len); + } else { + name = std::string(commands[t][i].group_name, + commands[t][i].group_name_len); + } + + if (face_count == 0) { + // 'o' or 'g' appears before any 'f' + shape.name = name; + shape.face_offset = face_count; + face_prev_offset = face_count; + } else { + if (shapes->size() == 0) { + // 'o' or 'g' after some 'v' lines. + // create a shape with null name + shape.length = face_count - face_prev_offset; + face_prev_offset = face_count; + + shapes->push_back(shape); + + } else { + if ((face_count - face_prev_offset) > 0) { + // push previous shape + shape.length = face_count - face_prev_offset; + shapes->push_back(shape); + face_prev_offset = face_count; + } + } + + // redefine shape. + shape.name = name; + shape.face_offset = face_count; + shape.length = 0; + } + } + if (commands[t][i].type == COMMAND_F) { + face_count++; + } + } + } + + if ((face_count - face_prev_offset) > 0) { + shape.length = face_count - shape.face_offset; + if (shape.length > 0) { + shapes->push_back(shape); + } + } else { + // Guess no 'v' line occurrence after 'o' or 'g', so discards current + // shape information. + } + + auto t_end = std::chrono::high_resolution_clock::now(); + + ms_construct = t_end - t_start; + } + + std::chrono::duration ms_total = t4 - t1; + std::cout << "total parsing time: " << ms_total.count() << " ms\n"; + std::cout << " line detection : " << ms_linedetection.count() << " ms\n"; + std::cout << " alloc buf : " << ms_alloc.count() << " ms\n"; + std::cout << " parse : " << ms_parse.count() << " ms\n"; + std::cout << " merge : " << ms_merge.count() << " ms\n"; + std::cout << " construct : " << ms_construct.count() << " ms\n"; + std::cout << " mtl load : " << ms_load_mtl.count() << " ms\n"; + std::cout << "# of vertices = " << attrib->vertices.size() << std::endl; + std::cout << "# of normals = " << attrib->normals.size() << std::endl; + std::cout << "# of texcoords = " << attrib->texcoords.size() << std::endl; + std::cout << "# of face indices = " << attrib->faces.size() << std::endl; + std::cout << "# of faces = " << attrib->material_ids.size() << std::endl; + std::cout << "# of shapes = " << shapes->size() << std::endl; + + return true; +} +#endif // TINYOBJ_LOADER_OPT_IMPLEMENTATION + +} // namespace tinyobj_opt + +#endif // TINOBJ_LOADER_OPT_H_ diff --git a/experimental/viewer.cc b/experimental/viewer.cc index 8d420b3..6fa1e37 100644 --- a/experimental/viewer.cc +++ b/experimental/viewer.cc @@ -27,7 +27,9 @@ #include #include "trackball.h" -#include "optimized-parse.cc" + +#define TINYOBJ_LOADER_OPT_IMPLEMENTATION +#include "tinyobj_loader_opt.h" typedef struct { GLuint vb; // vertex buffer @@ -215,12 +217,8 @@ const char* get_file_data(size_t *len, const char* filename) bool LoadObjAndConvert(float bmin[3], float bmax[3], const char* filename, int num_threads) { #if 1 - std::vector> vertices; - std::vector> normals; - std::vector> texcoords; - std::vector> faces; - std::vector> material_ids; - std::vector shapes; + tinyobj_opt::attrib_t attrib; + std::vector shapes; size_t data_len = 0; const char* data = get_file_data(&data_len, filename); @@ -229,7 +227,7 @@ bool LoadObjAndConvert(float bmin[3], float bmax[3], const char* filename, int n return false; } printf("filesize: %d\n", (int)data_len); - bool ret = parse(vertices, normals, texcoords, faces, material_ids, shapes, data, data_len, num_threads); + bool ret = parseObj(&attrib, &shapes, data, data_len, num_threads); bmin[0] = bmin[1] = bmin[2] = std::numeric_limits::max(); bmax[0] = bmax[1] = bmax[2] = -std::numeric_limits::max(); @@ -237,78 +235,81 @@ bool LoadObjAndConvert(float bmin[3], float bmax[3], const char* filename, int n { DrawObject o; std::vector vb; // pos(3float), normal(3float), color(3float) - for (size_t f = 0; f < faces.size()/3; f++) { - - vertex_index idx0 = faces[3*f+0]; - vertex_index idx1 = faces[3*f+1]; - vertex_index idx2 = faces[3*f+2]; - - float v[3][3]; - for (int k = 0; k < 3; k++) { - int f0 = idx0.v_idx; - int f1 = idx1.v_idx; - int f2 = idx2.v_idx; - assert(f0 >= 0); - assert(f1 >= 0); - assert(f2 >= 0); - - v[0][k] = vertices[3*f0+k]; - v[1][k] = vertices[3*f1+k]; - v[2][k] = vertices[3*f2+k]; - bmin[k] = std::min(v[0][k], bmin[k]); - bmin[k] = std::min(v[1][k], bmin[k]); - bmin[k] = std::min(v[2][k], bmin[k]); - bmax[k] = std::max(v[0][k], bmax[k]); - bmax[k] = std::max(v[1][k], bmax[k]); - bmax[k] = std::max(v[2][k], bmax[k]); - } - - float n[3][3]; - - if (normals.size() > 0) { - int f0 = idx0.vn_idx; - int f1 = idx1.vn_idx; - int f2 = idx2.vn_idx; - assert(f0 >= 0); - assert(f1 >= 0); - assert(f2 >= 0); - assert(3*f0+2 < normals.size()); - assert(3*f1+2 < normals.size()); - assert(3*f2+2 < normals.size()); + size_t face_offset = 0; + for (size_t v = 0; v < attrib.face_num_verts.size(); v++) { + assert(attrib.face_num_verts[v] % 3 == 0); // assume all triangle face. + for (size_t f = 0; f < attrib.face_num_verts[v] / 3; f++) { + tinyobj_opt::vertex_index idx0 = attrib.faces[face_offset+3*f+0]; + tinyobj_opt::vertex_index idx1 = attrib.faces[face_offset+3*f+1]; + tinyobj_opt::vertex_index idx2 = attrib.faces[face_offset+3*f+2]; + + float v[3][3]; for (int k = 0; k < 3; k++) { - n[0][k] = normals[3*f0+k]; - n[1][k] = normals[3*f1+k]; - n[2][k] = normals[3*f2+k]; + int f0 = idx0.v_idx; + int f1 = idx1.v_idx; + int f2 = idx2.v_idx; + assert(f0 >= 0); + assert(f1 >= 0); + assert(f2 >= 0); + + v[0][k] = attrib.vertices[3*f0+k]; + v[1][k] = attrib.vertices[3*f1+k]; + v[2][k] = attrib.vertices[3*f2+k]; + bmin[k] = std::min(v[0][k], bmin[k]); + bmin[k] = std::min(v[1][k], bmin[k]); + bmin[k] = std::min(v[2][k], bmin[k]); + bmax[k] = std::max(v[0][k], bmax[k]); + bmax[k] = std::max(v[1][k], bmax[k]); + bmax[k] = std::max(v[2][k], bmax[k]); + } + + float n[3][3]; + + if (attrib.normals.size() > 0) { + int f0 = idx0.vn_idx; + int f1 = idx1.vn_idx; + int f2 = idx2.vn_idx; + assert(f0 >= 0); + assert(f1 >= 0); + assert(f2 >= 0); + assert(3*f0+2 < attrib.normals.size()); + assert(3*f1+2 < attrib.normals.size()); + assert(3*f2+2 < attrib.normals.size()); + for (int k = 0; k < 3; k++) { + n[0][k] = attrib.normals[3*f0+k]; + n[1][k] = attrib.normals[3*f1+k]; + n[2][k] = attrib.normals[3*f2+k]; + } + } else { + // compute geometric normal + CalcNormal(n[0], v[0], v[1], v[2]); + n[1][0] = n[0][0]; n[1][1] = n[0][1]; n[1][2] = n[0][2]; + n[2][0] = n[0][0]; n[2][1] = n[0][1]; n[2][2] = n[0][2]; } - } else { - // compute geometric normal - CalcNormal(n[0], v[0], v[1], v[2]); - n[1][0] = n[0][0]; n[1][1] = n[0][1]; n[1][2] = n[0][2]; - n[2][0] = n[0][0]; n[2][1] = n[0][1]; n[2][2] = n[0][2]; - } - - for (int k = 0; k < 3; k++) { - vb.push_back(v[k][0]); - vb.push_back(v[k][1]); - vb.push_back(v[k][2]); - vb.push_back(n[k][0]); - vb.push_back(n[k][1]); - vb.push_back(n[k][2]); - // Use normal as color. - float c[3] = {n[k][0], n[k][1], n[k][2]}; - float len2 = c[0] * c[0] + c[1] * c[1] + c[2] * c[2]; - if (len2 > 0.0f) { - float len = sqrtf(len2); - - c[0] /= len; - c[1] /= len; - c[2] /= len; + + for (int k = 0; k < 3; k++) { + vb.push_back(v[k][0]); + vb.push_back(v[k][1]); + vb.push_back(v[k][2]); + vb.push_back(n[k][0]); + vb.push_back(n[k][1]); + vb.push_back(n[k][2]); + // Use normal as color. + float c[3] = {n[k][0], n[k][1], n[k][2]}; + float len2 = c[0] * c[0] + c[1] * c[1] + c[2] * c[2]; + if (len2 > 0.0f) { + float len = sqrtf(len2); + + c[0] /= len; + c[1] /= len; + c[2] /= len; + } + vb.push_back(c[0] * 0.5 + 0.5); + vb.push_back(c[1] * 0.5 + 0.5); + vb.push_back(c[2] * 0.5 + 0.5); } - vb.push_back(c[0] * 0.5 + 0.5); - vb.push_back(c[1] * 0.5 + 0.5); - vb.push_back(c[2] * 0.5 + 0.5); - } - + } + face_offset += attrib.face_num_verts[v]; } o.vb = 0; @@ -512,12 +513,8 @@ int main(int argc, char **argv) if (benchmark_only) { - std::vector> vertices; - std::vector> normals; - std::vector> texcoords; - std::vector> faces; - std::vector> material_ids; - std::vector shapes; + tinyobj_opt::attrib_t attrib; + std::vector shapes; size_t data_len = 0; const char* data = get_file_data(&data_len, argv[1]); @@ -526,7 +523,7 @@ int main(int argc, char **argv) return false; } printf("filesize: %d\n", (int)data_len); - bool ret = parse(vertices, normals, texcoords, faces, material_ids, shapes, data, data_len, num_threads); + bool ret = parseObj(&attrib, &shapes, data, data_len, num_threads); return ret; } -- cgit v1.2.3 From 164dcb8121098c730facb8f10b40544313b2cd56 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Sat, 11 Jun 2016 19:34:12 +0900 Subject: Refactor. --- experimental/tinyobj_loader_opt.h | 57 ++++++++++++++++++++++++++------------- experimental/viewer.cc | 8 ++++-- 2 files changed, 44 insertions(+), 21 deletions(-) diff --git a/experimental/tinyobj_loader_opt.h b/experimental/tinyobj_loader_opt.h index bfb550c..680514d 100644 --- a/experimental/tinyobj_loader_opt.h +++ b/experimental/tinyobj_loader_opt.h @@ -928,11 +928,24 @@ struct CommandCount { } }; +class +LoadOption +{ + public: + LoadOption() : req_num_threads(-1), triangulate(true), verbose(false) {} + + int req_num_threads; + bool triangulate; + bool verbose; + +}; + + /// Parse wavefront .obj(.obj string data is expanded to linear char array /// `buf') /// -1 to req_num_threads use the number of HW threads in the running system. bool parseObj(attrib_t *attrib, std::vector *shapes, const char *buf, - size_t len, int req_num_threads = -1, bool triangulate = true); + size_t len, const LoadOption& option); #ifdef TINYOBJ_LOADER_OPT_IMPLEMENTATION @@ -1141,7 +1154,8 @@ static inline bool is_line_ending(const char *p, size_t i, size_t end_i) { } bool parseObj(attrib_t *attrib, std::vector *shapes, const char *buf, - size_t len, int req_num_threads, bool triangulate) { + size_t len, const LoadOption& option) +{ attrib->vertices.clear(); attrib->normals.clear(); attrib->texcoords.clear(); @@ -1152,11 +1166,14 @@ bool parseObj(attrib_t *attrib, std::vector *shapes, const char *buf, if (len < 1) return false; - auto num_threads = (req_num_threads < 0) ? std::thread::hardware_concurrency() - : req_num_threads; + auto num_threads = (option.req_num_threads < 0) ? std::thread::hardware_concurrency() + : option.req_num_threads; num_threads = std::max(1, std::min(static_cast(num_threads), kMaxThreads)); - std::cout << "# of threads = " << num_threads << std::endl; + + if (option.verbose) { + std::cout << "# of threads = " << num_threads << std::endl; + } auto t1 = std::chrono::high_resolution_clock::now(); @@ -1277,7 +1294,7 @@ bool parseObj(attrib_t *attrib, std::vector *shapes, const char *buf, for (size_t i = 0; i < line_infos[t].size(); i++) { Command command; bool ret = parseLine(&command, &buf[line_infos[t][i].pos], - line_infos[t][i].len, triangulate); + line_infos[t][i].len, option.triangulate); if (ret) { if (command.type == COMMAND_V) { command_count[t].num_v++; @@ -1539,19 +1556,21 @@ bool parseObj(attrib_t *attrib, std::vector *shapes, const char *buf, } std::chrono::duration ms_total = t4 - t1; - std::cout << "total parsing time: " << ms_total.count() << " ms\n"; - std::cout << " line detection : " << ms_linedetection.count() << " ms\n"; - std::cout << " alloc buf : " << ms_alloc.count() << " ms\n"; - std::cout << " parse : " << ms_parse.count() << " ms\n"; - std::cout << " merge : " << ms_merge.count() << " ms\n"; - std::cout << " construct : " << ms_construct.count() << " ms\n"; - std::cout << " mtl load : " << ms_load_mtl.count() << " ms\n"; - std::cout << "# of vertices = " << attrib->vertices.size() << std::endl; - std::cout << "# of normals = " << attrib->normals.size() << std::endl; - std::cout << "# of texcoords = " << attrib->texcoords.size() << std::endl; - std::cout << "# of face indices = " << attrib->faces.size() << std::endl; - std::cout << "# of faces = " << attrib->material_ids.size() << std::endl; - std::cout << "# of shapes = " << shapes->size() << std::endl; + if (option.verbose) { + std::cout << "total parsing time: " << ms_total.count() << " ms\n"; + std::cout << " line detection : " << ms_linedetection.count() << " ms\n"; + std::cout << " alloc buf : " << ms_alloc.count() << " ms\n"; + std::cout << " parse : " << ms_parse.count() << " ms\n"; + std::cout << " merge : " << ms_merge.count() << " ms\n"; + std::cout << " construct : " << ms_construct.count() << " ms\n"; + std::cout << " mtl load : " << ms_load_mtl.count() << " ms\n"; + std::cout << "# of vertices = " << attrib->vertices.size() << std::endl; + std::cout << "# of normals = " << attrib->normals.size() << std::endl; + std::cout << "# of texcoords = " << attrib->texcoords.size() << std::endl; + std::cout << "# of face indices = " << attrib->faces.size() << std::endl; + std::cout << "# of faces = " << attrib->material_ids.size() << std::endl; + std::cout << "# of shapes = " << shapes->size() << std::endl; + } return true; } diff --git a/experimental/viewer.cc b/experimental/viewer.cc index 6fa1e37..3c32404 100644 --- a/experimental/viewer.cc +++ b/experimental/viewer.cc @@ -227,7 +227,9 @@ bool LoadObjAndConvert(float bmin[3], float bmax[3], const char* filename, int n return false; } printf("filesize: %d\n", (int)data_len); - bool ret = parseObj(&attrib, &shapes, data, data_len, num_threads); + tinyobj_opt::LoadOption option; + option.req_num_threads = num_threads; + bool ret = parseObj(&attrib, &shapes, data, data_len, option); bmin[0] = bmin[1] = bmin[2] = std::numeric_limits::max(); bmax[0] = bmax[1] = bmax[2] = -std::numeric_limits::max(); @@ -523,7 +525,9 @@ int main(int argc, char **argv) return false; } printf("filesize: %d\n", (int)data_len); - bool ret = parseObj(&attrib, &shapes, data, data_len, num_threads); + tinyobj_opt::LoadOption option; + option.req_num_threads = num_threads; + bool ret = parseObj(&attrib, &shapes, data, data_len, option); return ret; } -- cgit v1.2.3 From e85155b2dd99e65282e94f15faa77edd30a774bf Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Tue, 28 Jun 2016 00:39:36 +0900 Subject: Add README on optimized obj loader. --- README.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/README.md b/README.md index e710b69..e1b19d8 100644 --- a/README.md +++ b/README.md @@ -148,6 +148,19 @@ for (size_t s = 0; s < shapes.size(); s++) { ``` +Optimized loader +---------------- + +Optimized multi-threaded .obj loader is available at `experimental/` directory. +If you want absolute performance to load .obj data, this optimized loader will fit your purpose. +Note that the optimized loader uses C++11 thread and it does less error checks but may work most .obj data. + +Here is some benchmark result. Time are measured on MacBook 12(Early 2016, Core m5 1.2GHz). + +* Rungholt scene(6M triangles) + * baseline: 6800 msecs + * optimised: 1500 msecs(4.5x faster) + Tests ----- -- cgit v1.2.3 From 0389e238472f15d17426849837389993a64556de Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Fri, 1 Jul 2016 23:35:39 +0900 Subject: Show parsing time. --- loader_example.cc | 89 ++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 88 insertions(+), 1 deletion(-) diff --git a/loader_example.cc b/loader_example.cc index 0aec2d1..340b071 100644 --- a/loader_example.cc +++ b/loader_example.cc @@ -11,6 +11,89 @@ #include #include +#ifdef _WIN32 +#ifdef __cplusplus +extern "C" { +#endif +#include +#include +#ifdef __cplusplus +} +#endif +#pragma comment(lib, "winmm.lib") +#else +#if defined(__unix__) || defined(__APPLE__) +#include +#else +#include +#endif +#endif + +class timerutil { +public: +#ifdef _WIN32 + typedef DWORD time_t; + + timerutil() { ::timeBeginPeriod(1); } + ~timerutil() { ::timeEndPeriod(1); } + + void start() { t_[0] = ::timeGetTime(); } + void end() { t_[1] = ::timeGetTime(); } + + time_t sec() { return (time_t)((t_[1] - t_[0]) / 1000); } + time_t msec() { return (time_t)((t_[1] - t_[0])); } + time_t usec() { return (time_t)((t_[1] - t_[0]) * 1000); } + time_t current() { return ::timeGetTime(); } + +#else +#if defined(__unix__) || defined(__APPLE__) + typedef unsigned long int time_t; + + void start() { gettimeofday(tv + 0, &tz); } + void end() { gettimeofday(tv + 1, &tz); } + + time_t sec() { return static_cast(tv[1].tv_sec - tv[0].tv_sec); } + time_t msec() { + return this->sec() * 1000 + + static_cast((tv[1].tv_usec - tv[0].tv_usec) / 1000); + } + time_t usec() { + return this->sec() * 1000000 + static_cast(tv[1].tv_usec - tv[0].tv_usec); + } + time_t current() { + struct timeval t; + gettimeofday(&t, NULL); + return static_cast(t.tv_sec * 1000 + t.tv_usec); + } + +#else // C timer + // using namespace std; + typedef clock_t time_t; + + void start() { t_[0] = clock(); } + void end() { t_[1] = clock(); } + + time_t sec() { return (time_t)((t_[1] - t_[0]) / CLOCKS_PER_SEC); } + time_t msec() { return (time_t)((t_[1] - t_[0]) * 1000 / CLOCKS_PER_SEC); } + time_t usec() { return (time_t)((t_[1] - t_[0]) * 1000000 / CLOCKS_PER_SEC); } + time_t current() { return (time_t)clock(); } + +#endif +#endif + +private: +#ifdef _WIN32 + DWORD t_[2]; +#else +#if defined(__unix__) || defined(__APPLE__) + struct timeval tv[2]; + struct timezone tz; +#else + time_t t_[2]; +#endif +#endif +}; + static void PrintInfo(const tinyobj::attrib_t &attrib, const std::vector& shapes, const std::vector& materials) { std::cout << "# of vertices : " << (attrib.vertices.size() / 3) << std::endl; @@ -55,7 +138,7 @@ static void PrintInfo(const tinyobj::attrib_t &attrib, const std::vector(f), static_cast(fnum)); + printf(" face[%ld].fnum = %ld\n", static_cast(f), static_cast(fnum)); // For each vertex in the face for (size_t v = 0; v < fnum; v++) { @@ -147,8 +230,12 @@ TestLoadObj( std::vector shapes; std::vector materials; + timerutil t; + t.start(); std::string err; bool ret = tinyobj::LoadObj(&attrib, &shapes, &materials, &err, filename, basepath, triangulate); + t.end(); + printf("Parsing time: %lu [msecs]\n", t.msec()); if (!err.empty()) { std::cerr << err << std::endl; -- cgit v1.2.3 From b8c33156de96592903f06acc619ac921adc251c3 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Fri, 1 Jul 2016 23:39:36 +0900 Subject: Update README. --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index e1b19d8..c6095a6 100644 --- a/README.md +++ b/README.md @@ -130,9 +130,9 @@ for (size_t s = 0; s < shapes.size(); s++) { for (size_t v = 0; v < fv; f++) { // access to vertex tinyobj::index_t idx = shapes[i].mesh.indices[index_offset + v]; - float vx = attrib.positions[3*idx.vertex_index+0]; - float vy = attrib.positions[3*idx.vertex_index+1]; - float vz = attrib.positions[3*idx.vertex_index+2]; + float vx = attrib.vertices[3*idx.vertex_index+0]; + float vy = attrib.vertices[3*idx.vertex_index+1]; + float vz = attrib.vertices[3*idx.vertex_index+2]; float nx = attrib.normals[3*idx.normal_index+0]; float ny = attrib.normals[3*idx.normal_index+1]; float nz = attrib.normals[3*idx.normal_index+2]; -- cgit v1.2.3 From 16ed0ac129ebf7ed476147468e9bb173587d8f92 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Wed, 6 Jul 2016 01:36:16 +0900 Subject: Update README. --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index d8d9241..dbf3749 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,12 @@ Tiny but powerful single file wavefront obj loader written in C++. No dependency `tinyobjloader` is good for embedding .obj loader to your (global illumination) renderer ;-) +Notice! +------- + +`master` branch will be replaced with `develop` branch in the near future: https://github.com/syoyo/tinyobjloader/tree/develop +`develop` branch has more better support and clean API interface for loading .obj and also it has optimized multi-threaded parser(probably 10x faster than `master`). If you are new to use `TinyObjLoader`, I highly recommend to use `develop` branch. + What's new ---------- -- cgit v1.2.3 From d5c722125a7e7efe265f232c6abaa0ff50c9a719 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Sat, 9 Jul 2016 00:52:27 +0900 Subject: Show parsing time. --- test.cc | 89 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 89 insertions(+) diff --git a/test.cc b/test.cc index e13b172..7b675b7 100644 --- a/test.cc +++ b/test.cc @@ -8,6 +8,90 @@ #include #include +#ifdef _WIN32 +#ifdef __cplusplus +extern "C" { +#endif +#include +#include +#ifdef __cplusplus +} +#endif +#pragma comment(lib, "winmm.lib") +#else +#if defined(__unix__) || defined(__APPLE__) +#include +#else +#include +#endif +#endif + +// not thread-safe +class timerutil { +public: +#ifdef _WIN32 + typedef DWORD time_t; + + timerutil() { ::timeBeginPeriod(1); } + ~timerutil() { ::timeEndPeriod(1); } + + void start() { t_[0] = ::timeGetTime(); } + void end() { t_[1] = ::timeGetTime(); } + + time_t sec() { return (time_t)((t_[1] - t_[0]) / 1000); } + time_t msec() { return (time_t)((t_[1] - t_[0])); } + time_t usec() { return (time_t)((t_[1] - t_[0]) * 1000); } + time_t current() { return ::timeGetTime(); } + +#else +#if defined(__unix__) || defined(__APPLE__) + typedef unsigned long int time_t; + + void start() { gettimeofday(tv + 0, &tz); } + void end() { gettimeofday(tv + 1, &tz); } + + time_t sec() { return (time_t)(tv[1].tv_sec - tv[0].tv_sec); } + time_t msec() { + return this->sec() * 1000 + + (time_t)((tv[1].tv_usec - tv[0].tv_usec) / 1000); + } + time_t usec() { + return this->sec() * 1000000 + (time_t)(tv[1].tv_usec - tv[0].tv_usec); + } + time_t current() { + struct timeval t; + gettimeofday(&t, NULL); + return (time_t)(t.tv_sec * 1000 + t.tv_usec); + } + +#else // C timer + // using namespace std; + typedef clock_t time_t; + + void start() { t_[0] = clock(); } + void end() { t_[1] = clock(); } + + time_t sec() { return (time_t)((t_[1] - t_[0]) / CLOCKS_PER_SEC); } + time_t msec() { return (time_t)((t_[1] - t_[0]) * 1000 / CLOCKS_PER_SEC); } + time_t usec() { return (time_t)((t_[1] - t_[0]) * 1000000 / CLOCKS_PER_SEC); } + time_t current() { return (time_t)clock(); } + +#endif +#endif + +private: +#ifdef _WIN32 + DWORD t_[2]; +#else +#if defined(__unix__) || defined(__APPLE__) + struct timeval tv[2]; + struct timezone tz; +#else + time_t t_[2]; +#endif +#endif +}; + static void PrintInfo(const std::vector& shapes, const std::vector& materials, bool triangulate = true) { std::cout << "# of shapes : " << shapes.size() << std::endl; @@ -131,8 +215,11 @@ TestLoadObj( std::vector shapes; std::vector materials; + timerutil t; + t.start(); std::string err; bool ret = tinyobj::LoadObj(shapes, materials, err, filename, basepath, flags); + t.end(); if (!err.empty()) { std::cerr << err << std::endl; @@ -143,6 +230,8 @@ TestLoadObj( return false; } + printf("Parse time: %lu [msecs]\n", t.msec()); + bool triangulate( ( flags & tinyobj::triangulation ) == tinyobj::triangulation ); PrintInfo(shapes, materials, triangulate ); -- cgit v1.2.3 From d2793bf454ff6ae8b1920b12a2a9f92c5328bc83 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Sat, 9 Jul 2016 01:31:13 +0900 Subject: Update performance note. --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index c6095a6..1bfcb90 100644 --- a/README.md +++ b/README.md @@ -158,8 +158,9 @@ Note that the optimized loader uses C++11 thread and it does less error checks b Here is some benchmark result. Time are measured on MacBook 12(Early 2016, Core m5 1.2GHz). * Rungholt scene(6M triangles) - * baseline: 6800 msecs - * optimised: 1500 msecs(4.5x faster) + * old version(v0.9.x): 15500 msecs. + * baseline(v1.0.x): 6800 msecs(2.3x faster than old version) + * optimised: 1500 msecs(10x faster than old version, 4.5x faster than basedline) Tests -- cgit v1.2.3 From d4a7eefc5462392dc1be520210f7c03f1b8515a9 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Sun, 10 Jul 2016 00:11:37 +0900 Subject: Fix typo in usage. --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 1bfcb90..b33c6a3 100644 --- a/README.md +++ b/README.md @@ -123,13 +123,13 @@ if (!ret) { for (size_t s = 0; s < shapes.size(); s++) { // Loop over faces(polygon) size_t index_offset = 0; - for (size_t f = 0; f < shapes[i].mesh.num_face_vertices.size(); f++) { - int fv = shapes[i].mesh.num_face_vertices[f]; + for (size_t f = 0; f < shapes[s].mesh.num_face_vertices.size(); f++) { + int fv = shapes[s].mesh.num_face_vertices[f]; // Loop over vertices in the face. - for (size_t v = 0; v < fv; f++) { + for (size_t v = 0; v < fv; v++) { // access to vertex - tinyobj::index_t idx = shapes[i].mesh.indices[index_offset + v]; + tinyobj::index_t idx = shapes[s].mesh.indices[index_offset + v]; float vx = attrib.vertices[3*idx.vertex_index+0]; float vy = attrib.vertices[3*idx.vertex_index+1]; float vz = attrib.vertices[3*idx.vertex_index+2]; @@ -142,7 +142,7 @@ for (size_t s = 0; s < shapes.size(); s++) { index_offset += fv; // per-face material - shapes[i].mesh.material_ids[f]; + shapes[s].mesh.material_ids[f]; } } -- cgit v1.2.3 From 7d6318c3ad0e54e6e2290440b7dbc095f7700b78 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Fri, 15 Jul 2016 01:38:39 +0900 Subject: Remove redundant & buggy memcpy. Add verbose option. --- experimental/tinyobj_loader_opt.h | 29 ++++++++++++++--------------- experimental/viewer.cc | 19 ++++++++++++------- 2 files changed, 26 insertions(+), 22 deletions(-) diff --git a/experimental/tinyobj_loader_opt.h b/experimental/tinyobj_loader_opt.h index 680514d..db9319e 100644 --- a/experimental/tinyobj_loader_opt.h +++ b/experimental/tinyobj_loader_opt.h @@ -951,14 +951,12 @@ bool parseObj(attrib_t *attrib, std::vector *shapes, const char *buf, static bool parseLine(Command *command, const char *p, size_t p_len, bool triangulate = true) { - char linebuf[4096]; - assert(p_len < 4095); - // StackVector linebuf; - // linebuf->resize(p_len + 1); - memcpy(&linebuf, p, p_len); - linebuf[p_len] = '\0'; + //char linebuf[4096]; + //assert(p_len < 4095); + //memcpy(linebuf, p, p_len); + //linebuf[p_len] = '\0'; - const char *token = linebuf; + const char *token = p; command->type = COMMAND_EMPTY; @@ -1078,9 +1076,10 @@ static bool parseLine(Command *command, const char *p, size_t p_len, // namebuf + strlen(namebuf)); // command->material_name->push_back('\0'); skip_space(&token); - command->material_name = p + (token - linebuf); + command->material_name = token; // p + (token - linebuf); command->material_name_len = - length_until_newline(token, p_len - (token - linebuf)) + 1; + length_until_newline(token, p_len - (token - p)) + 1; + //length_until_newline(token, p_len - (token - linebuf)) + 1; command->type = COMMAND_USEMTL; return true; @@ -1092,9 +1091,9 @@ static bool parseLine(Command *command, const char *p, size_t p_len, token += 7; skip_space(&token); - command->mtllib_name = p + (token - linebuf); + command->mtllib_name = token; //p + (token - linebuf); command->mtllib_name_len = - length_until_newline(token, p_len - (token - linebuf)) + 1; + length_until_newline(token, p_len - (token - p)) + 1; command->type = COMMAND_MTLLIB; return true; @@ -1105,9 +1104,9 @@ static bool parseLine(Command *command, const char *p, size_t p_len, // @todo { multiple group name. } token += 2; - command->group_name = p + (token - linebuf); + command->group_name = token; command->group_name_len = - length_until_newline(token, p_len - (token - linebuf)) + 1; + length_until_newline(token, p_len - (token - p)) + 1; command->type = COMMAND_G; return true; @@ -1118,9 +1117,9 @@ static bool parseLine(Command *command, const char *p, size_t p_len, // @todo { multiple object name? } token += 2; - command->object_name = p + (token - linebuf); + command->object_name = token; command->object_name_len = - length_until_newline(token, p_len - (token - linebuf)) + 1; + length_until_newline(token, p_len - (token - p)) + 1; command->type = COMMAND_O; return true; diff --git a/experimental/viewer.cc b/experimental/viewer.cc index 3c32404..d4c9d29 100644 --- a/experimental/viewer.cc +++ b/experimental/viewer.cc @@ -214,9 +214,8 @@ const char* get_file_data(size_t *len, const char* filename) } -bool LoadObjAndConvert(float bmin[3], float bmax[3], const char* filename, int num_threads) +bool LoadObjAndConvert(float bmin[3], float bmax[3], const char* filename, int num_threads, bool verbose) { -#if 1 tinyobj_opt::attrib_t attrib; std::vector shapes; @@ -229,6 +228,7 @@ bool LoadObjAndConvert(float bmin[3], float bmax[3], const char* filename, int n printf("filesize: %d\n", (int)data_len); tinyobj_opt::LoadOption option; option.req_num_threads = num_threads; + option.verbose = verbose; bool ret = parseObj(&attrib, &shapes, data, data_len, option); bmin[0] = bmin[1] = bmin[2] = std::numeric_limits::max(); @@ -330,9 +330,6 @@ bool LoadObjAndConvert(float bmin[3], float bmax[3], const char* filename, int n printf("bmax = %f, %f, %f\n", bmax[0], bmax[1], bmax[2]); return true; -#else - return false; -#endif } void reshapeFunc(GLFWwindow* window, int w, int h) @@ -499,12 +496,14 @@ static void Init() { int main(int argc, char **argv) { if (argc < 2) { - std::cout << "Needs input.obj\n" << std::endl; + std::cout << "view input.obj " << std::endl; return 0; } bool benchmark_only = false; int num_threads = -1; + bool verbose = false; + if (argc > 2) { num_threads = atoi(argv[2]); } @@ -513,6 +512,10 @@ int main(int argc, char **argv) benchmark_only = true; } + if (argc > 4) { + verbose = true; + } + if (benchmark_only) { tinyobj_opt::attrib_t attrib; @@ -527,6 +530,8 @@ int main(int argc, char **argv) printf("filesize: %d\n", (int)data_len); tinyobj_opt::LoadOption option; option.req_num_threads = num_threads; + option.verbose = true; + bool ret = parseObj(&attrib, &shapes, data, data_len, option); return ret; @@ -569,7 +574,7 @@ int main(int argc, char **argv) reshapeFunc(window, width, height); float bmin[3], bmax[3]; - if (false == LoadObjAndConvert(bmin, bmax, argv[1], num_threads)) { + if (false == LoadObjAndConvert(bmin, bmax, argv[1], num_threads, verbose)) { printf("failed to load & conv\n"); return -1; } -- cgit v1.2.3 From 333bb55d84da7bc18170c25b41adb5463f0de0b2 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Fri, 15 Jul 2016 02:01:50 +0900 Subject: Revert memcpy'ing input string into local buffer. --- experimental/tinyobj_loader_opt.h | 54 +++++++++++++++------------------------ 1 file changed, 21 insertions(+), 33 deletions(-) diff --git a/experimental/tinyobj_loader_opt.h b/experimental/tinyobj_loader_opt.h index db9319e..3b29f9e 100644 --- a/experimental/tinyobj_loader_opt.h +++ b/experimental/tinyobj_loader_opt.h @@ -391,7 +391,6 @@ static vertex_index parseRawTriple(const char **token) { static_cast(0x80000000)); // 0x80000000 = -2147483648 = invalid vi.v_idx = my_atoi((*token)); - //(*token) += strcspn((*token), "/ \t\r"); while ((*token)[0] != '\0' && (*token)[0] != '/' && (*token)[0] != ' ' && (*token)[0] != '\t' && (*token)[0] != '\r') { (*token)++; @@ -405,7 +404,6 @@ static vertex_index parseRawTriple(const char **token) { if ((*token)[0] == '/') { (*token)++; vi.vn_idx = my_atoi((*token)); - //(*token) += strcspn((*token), "/ \t\r"); while ((*token)[0] != '\0' && (*token)[0] != '/' && (*token)[0] != ' ' && (*token)[0] != '\t' && (*token)[0] != '\r') { (*token)++; @@ -415,7 +413,6 @@ static vertex_index parseRawTriple(const char **token) { // i/j/k or i/j vi.vt_idx = my_atoi((*token)); - //(*token) += strcspn((*token), "/ \t\r"); while ((*token)[0] != '\0' && (*token)[0] != '/' && (*token)[0] != ' ' && (*token)[0] != '\t' && (*token)[0] != '\r') { (*token)++; @@ -427,7 +424,6 @@ static vertex_index parseRawTriple(const char **token) { // i/j/k (*token)++; // skip '/' vi.vn_idx = my_atoi((*token)); - //(*token) += strcspn((*token), "/ \t\r"); while ((*token)[0] != '\0' && (*token)[0] != '/' && (*token)[0] != ' ' && (*token)[0] != '\t' && (*token)[0] != '\r') { (*token)++; @@ -436,17 +432,17 @@ static vertex_index parseRawTriple(const char **token) { } static inline bool parseString(ShortString *s, const char **token) { - skip_space(token); //(*token) += strspn((*token), " \t"); - size_t e = until_space((*token)); // strcspn((*token), " \t\r"); + skip_space(token); + size_t e = until_space((*token)); (*s)->insert((*s)->end(), (*token), (*token) + e); (*token) += e; return true; } static inline int parseInt(const char **token) { - skip_space(token); //(*token) += strspn((*token), " \t"); + skip_space(token); int i = my_atoi((*token)); - (*token) += until_space((*token)); // strcspn((*token), " \t\r"); + (*token) += until_space((*token)); return i; } @@ -601,13 +597,13 @@ fail: } static inline float parseFloat(const char **token) { - skip_space(token); //(*token) += strspn((*token), " \t"); + skip_space(token); #ifdef TINY_OBJ_LOADER_OLD_FLOAT_PARSER float f = static_cast(atof(*token)); (*token) += strcspn((*token), " \t\r"); #else const char *end = - (*token) + until_space((*token)); // strcspn((*token), " \t\r"); + (*token) + until_space((*token)); double val = 0.0; tryParseDouble((*token), end, &val); float f = static_cast(val); @@ -951,18 +947,18 @@ bool parseObj(attrib_t *attrib, std::vector *shapes, const char *buf, static bool parseLine(Command *command, const char *p, size_t p_len, bool triangulate = true) { - //char linebuf[4096]; - //assert(p_len < 4095); - //memcpy(linebuf, p, p_len); - //linebuf[p_len] = '\0'; + // @todo { operate directly on pointer `p'. to do that, add range check for string operatoion against `p', since `p' is not null-terminated at p[p_len] } + char linebuf[4096]; + assert(p_len < 4095); + memcpy(linebuf, p, p_len); + linebuf[p_len] = '\0'; - const char *token = p; + const char *token = linebuf; command->type = COMMAND_EMPTY; // Skip leading space. - // token += strspn(token, " \t"); - skip_space(&token); //(*token) += strspn((*token), " \t"); + skip_space(&token); assert(token); if (token[0] == '\0') { // empty line @@ -1011,19 +1007,12 @@ static bool parseLine(Command *command, const char *p, size_t p_len, // face if (token[0] == 'f' && IS_SPACE((token[1]))) { token += 2; - // token += strspn(token, " \t"); skip_space(&token); StackVector f; while (!IS_NEW_LINE(token[0])) { vertex_index vi = parseRawTriple(&token); - // printf("v = %d, %d, %d\n", vi.v_idx, vi.vn_idx, vi.vt_idx); - // if (callback.index_cb) { - // callback.index_cb(user_data, vi.v_idx, vi.vn_idx, vi.vt_idx); - //} - // size_t n = strspn(token, " \t\r"); - // token += n; skip_space_and_cr(&token); f->push_back(vi); @@ -1076,10 +1065,9 @@ static bool parseLine(Command *command, const char *p, size_t p_len, // namebuf + strlen(namebuf)); // command->material_name->push_back('\0'); skip_space(&token); - command->material_name = token; // p + (token - linebuf); + command->material_name = p + (token - linebuf); command->material_name_len = - length_until_newline(token, p_len - (token - p)) + 1; - //length_until_newline(token, p_len - (token - linebuf)) + 1; + length_until_newline(token, p_len - (token - linebuf)) + 1; command->type = COMMAND_USEMTL; return true; @@ -1091,9 +1079,9 @@ static bool parseLine(Command *command, const char *p, size_t p_len, token += 7; skip_space(&token); - command->mtllib_name = token; //p + (token - linebuf); + command->mtllib_name = p + (token - linebuf); command->mtllib_name_len = - length_until_newline(token, p_len - (token - p)) + 1; + length_until_newline(token, p_len - (token - linebuf)) + 1; command->type = COMMAND_MTLLIB; return true; @@ -1104,9 +1092,9 @@ static bool parseLine(Command *command, const char *p, size_t p_len, // @todo { multiple group name. } token += 2; - command->group_name = token; + command->group_name = p + (token - linebuf); command->group_name_len = - length_until_newline(token, p_len - (token - p)) + 1; + length_until_newline(token, p_len - (token - linebuf)) + 1; command->type = COMMAND_G; return true; @@ -1117,9 +1105,9 @@ static bool parseLine(Command *command, const char *p, size_t p_len, // @todo { multiple object name? } token += 2; - command->object_name = token; + command->object_name = p + (token - linebuf); command->object_name_len = - length_until_newline(token, p_len - (token - p)) + 1; + length_until_newline(token, p_len - (token - linebuf)) + 1; command->type = COMMAND_O; return true; -- cgit v1.2.3 From 5ef400882afd3a7f99aee54f81adcbb281f66464 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Fri, 15 Jul 2016 13:25:44 +0900 Subject: Fix pow computation. --- experimental/tinyobj_loader_opt.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/experimental/tinyobj_loader_opt.h b/experimental/tinyobj_loader_opt.h index 3b29f9e..7b92323 100644 --- a/experimental/tinyobj_loader_opt.h +++ b/experimental/tinyobj_loader_opt.h @@ -581,9 +581,9 @@ assemble : { // = pow(5.0, exponent); - double a = 5.0; + double a = 1.0; for (int i = 0; i < exponent; i++) { - a = a * a; + a = a * 5.0; } *result = //(sign == '+' ? 1 : -1) * ldexp(mantissa * pow(5.0, exponent), exponent); -- cgit v1.2.3 From e3a56816d6736d96dbeb5b126c9a921bf216861e Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Sun, 24 Jul 2016 15:53:05 +0900 Subject: Use std::getline() to read arbitrary size of line data. --- tiny_obj_loader.h | 56 +++++++++++++++++++++++++++---------------------------- 1 file changed, 27 insertions(+), 29 deletions(-) diff --git a/tiny_obj_loader.h b/tiny_obj_loader.h index d130f8b..3534b91 100644 --- a/tiny_obj_loader.h +++ b/tiny_obj_loader.h @@ -62,9 +62,9 @@ THE SOFTWARE. #ifndef TINY_OBJ_LOADER_H_ #define TINY_OBJ_LOADER_H_ +#include #include #include -#include namespace tinyobj { @@ -112,10 +112,11 @@ typedef struct { typedef struct { std::vector indices; - std::vector - num_face_vertices; // The number of vertices per face. 3 = polygon, 4 = quad, ... Up to 255. - std::vector material_ids; // per-face material ID - std::vector tags; // SubD tag + std::vector num_face_vertices; // The number of vertices per + // face. 3 = polygon, 4 = quad, + // ... Up to 255. + std::vector material_ids; // per-face material ID + std::vector tags; // SubD tag } mesh_t; typedef struct { @@ -223,12 +224,12 @@ void LoadMtl(std::map *material_map, } // namespace tinyobj #ifdef TINYOBJLOADER_IMPLEMENTATION -#include -#include #include +#include #include #include -#include +#include +#include #include #include @@ -616,7 +617,8 @@ static bool exportFaceGroupToShape( shape->mesh.indices.push_back(idx); } - shape->mesh.num_face_vertices.push_back(static_cast(npolys)); + shape->mesh.num_face_vertices.push_back( + static_cast(npolys)); shape->mesh.material_ids.push_back(material_id); // per face } } @@ -926,12 +928,9 @@ bool LoadObj(attrib_t *attrib, std::vector *shapes, 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; + std::getline((*inStream), linebuf); // Trim newline '\r\n' or '\n' if (linebuf.size() > 0) { @@ -1374,30 +1373,29 @@ bool LoadObjWithCallback(void *user_data, const callback_t &callback, } else { callback.group_cb(user_data, NULL, 0); } - } continue; - } + } - // object name - if (token[0] == 'o' && IS_SPACE((token[1]))) { - // @todo { multiple object name? } - char namebuf[TINYOBJ_SSCANF_BUFFER_SIZE]; - token += 2; + // object name + if (token[0] == 'o' && IS_SPACE((token[1]))) { + // @todo { multiple object name? } + char namebuf[TINYOBJ_SSCANF_BUFFER_SIZE]; + token += 2; #ifdef _MSC_VER - sscanf_s(token, "%s", namebuf, (unsigned)_countof(namebuf)); + sscanf_s(token, "%s", namebuf, (unsigned)_countof(namebuf)); #else - sscanf(token, "%s", namebuf); + sscanf(token, "%s", namebuf); #endif - std::string name = std::string(namebuf); + std::string name = std::string(namebuf); - if (callback.object_cb) { - callback.object_cb(user_data, name.c_str()); - } + if (callback.object_cb) { + callback.object_cb(user_data, name.c_str()); + } - continue; - } + continue; + } #if 0 // @todo if (token[0] == 't' && IS_SPACE(token[1])) { -- cgit v1.2.3 From ef6560298ee8d59203dffccf656114ffb3bb79cb Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Mon, 25 Jul 2016 19:02:51 +0900 Subject: Add Vulkan Tutorial to the list. --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index b33c6a3..90fa5b2 100644 --- a/README.md +++ b/README.md @@ -65,6 +65,7 @@ TinyObjLoader is successfully used in ... * pbrt-v3 https://github.com/mmp/pbrt-v3 * cocos2d-x https://github.com/cocos2d/cocos2d-x/ * Android Vulkan demo https://github.com/SaschaWillems/Vulkan +* Loading models in Vulkan Tutorial https://vulkan-tutorial.com/Loading_models * Your project here! Features -- cgit v1.2.3 From 22883def8db9ef1f3ffb9b404318e7dd25fdbb51 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Mon, 25 Jul 2016 22:46:30 +0900 Subject: Support PBR extension for MTL which is proposed in http://exocortex.com/blog/extending_wavefront_mtl_to_support_pbr --- loader_example.cc | 372 ++++++++++++++++++++++++++----------------------- models/pbr-mat-ext.mtl | 19 +++ models/pbr-mat-ext.obj | 10 ++ tests/tester.cc | 30 +++- tiny_obj_loader.h | 115 ++++++++++++++- 5 files changed, 373 insertions(+), 173 deletions(-) create mode 100644 models/pbr-mat-ext.mtl create mode 100644 models/pbr-mat-ext.obj diff --git a/loader_example.cc b/loader_example.cc index 340b071..076ab3d 100644 --- a/loader_example.cc +++ b/loader_example.cc @@ -4,19 +4,19 @@ #define TINYOBJLOADER_IMPLEMENTATION #include "tiny_obj_loader.h" +#include #include #include -#include +#include #include #include -#include #ifdef _WIN32 #ifdef __cplusplus extern "C" { #endif -#include #include +#include #ifdef __cplusplus } #endif @@ -30,7 +30,7 @@ extern "C" { #endif class timerutil { -public: + public: #ifdef _WIN32 typedef DWORD time_t; @@ -58,7 +58,8 @@ public: static_cast((tv[1].tv_usec - tv[0].tv_usec) / 1000); } time_t usec() { - return this->sec() * 1000000 + static_cast(tv[1].tv_usec - tv[0].tv_usec); + return this->sec() * 1000000 + + static_cast(tv[1].tv_usec - tv[0].tv_usec); } time_t current() { struct timeval t; @@ -66,7 +67,7 @@ public: return static_cast(t.tv_sec * 1000 + t.tv_usec); } -#else // C timer +#else // C timer // using namespace std; typedef clock_t time_t; @@ -81,7 +82,7 @@ public: #endif #endif -private: + private: #ifdef _WIN32 DWORD t_[2]; #else @@ -94,96 +95,103 @@ private: #endif }; -static void PrintInfo(const tinyobj::attrib_t &attrib, const std::vector& shapes, const std::vector& materials) -{ +static void PrintInfo(const tinyobj::attrib_t& attrib, + const std::vector& shapes, + const std::vector& materials) { std::cout << "# of vertices : " << (attrib.vertices.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 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.vertices.size() / 3; v++) { printf(" v[%ld] = (%f, %f, %f)\n", static_cast(v), - static_cast(attrib.vertices[3*v+0]), - static_cast(attrib.vertices[3*v+1]), - static_cast(attrib.vertices[3*v+2])); + static_cast(attrib.vertices[3 * v + 0]), + static_cast(attrib.vertices[3 * v + 1]), + static_cast(attrib.vertices[3 * v + 2])); } for (size_t v = 0; v < attrib.normals.size() / 3; v++) { printf(" n[%ld] = (%f, %f, %f)\n", static_cast(v), - static_cast(attrib.normals[3*v+0]), - static_cast(attrib.normals[3*v+1]), - static_cast(attrib.normals[3*v+2])); + 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", static_cast(v), - static_cast(attrib.texcoords[2*v+0]), - static_cast(attrib.texcoords[2*v+1])); + static_cast(attrib.texcoords[2 * v + 0]), + static_cast(attrib.texcoords[2 * v + 1])); } // For each shape for (size_t i = 0; i < shapes.size(); i++) { - printf("shape[%ld].name = %s\n", static_cast(i), shapes[i].name.c_str()); - printf("Size of shape[%ld].indices: %lu\n", static_cast(i), static_cast(shapes[i].mesh.indices.size())); + printf("shape[%ld].name = %s\n", static_cast(i), + shapes[i].name.c_str()); + printf("Size of shape[%ld].indices: %lu\n", static_cast(i), + static_cast(shapes[i].mesh.indices.size())); - size_t index_offset = 0; + size_t index_offset = 0; - assert(shapes[i].mesh.num_face_vertices.size() == shapes[i].mesh.material_ids.size()); + assert(shapes[i].mesh.num_face_vertices.size() == + shapes[i].mesh.material_ids.size()); - printf("shape[%ld].num_faces: %lu\n", static_cast(i), static_cast(shapes[i].mesh.num_face_vertices.size())); + printf("shape[%ld].num_faces: %lu\n", static_cast(i), + static_cast(shapes[i].mesh.num_face_vertices.size())); - // For each face + // For each face for (size_t f = 0; f < shapes[i].mesh.num_face_vertices.size(); f++) { - size_t fnum = shapes[i].mesh.num_face_vertices[f]; + size_t fnum = shapes[i].mesh.num_face_vertices[f]; - printf(" face[%ld].fnum = %ld\n", static_cast(f), static_cast(fnum)); + printf(" face[%ld].fnum = %ld\n", static_cast(f), + static_cast(fnum)); - // For each vertex in the face - for (size_t v = 0; v < fnum; v++) { - tinyobj::index_t idx = shapes[i].mesh.indices[index_offset + v]; - printf(" face[%ld].v[%ld].idx = %d/%d/%d\n", static_cast(f), static_cast(v), idx.vertex_index, idx.normal_index, idx.texcoord_index); - } + // For each vertex in the face + for (size_t v = 0; v < fnum; v++) { + tinyobj::index_t idx = shapes[i].mesh.indices[index_offset + v]; + printf(" face[%ld].v[%ld].idx = %d/%d/%d\n", static_cast(f), + static_cast(v), idx.vertex_index, idx.normal_index, + idx.texcoord_index); + } - printf(" face[%ld].material_id = %d\n", static_cast(f), shapes[i].mesh.material_ids[f]); + printf(" face[%ld].material_id = %d\n", static_cast(f), + shapes[i].mesh.material_ids[f]); - index_offset += fnum; + index_offset += fnum; } - printf("shape[%ld].num_tags: %lu\n", static_cast(i), static_cast(shapes[i].mesh.tags.size())); + printf("shape[%ld].num_tags: %lu\n", static_cast(i), + static_cast(shapes[i].mesh.tags.size())); for (size_t t = 0; t < shapes[i].mesh.tags.size(); t++) { - printf(" tag[%ld] = %s ", static_cast(t), shapes[i].mesh.tags[t].name.c_str()); + printf(" tag[%ld] = %s ", static_cast(t), + shapes[i].mesh.tags[t].name.c_str()); printf(" ints: ["); - for (size_t j = 0; j < shapes[i].mesh.tags[t].intValues.size(); ++j) - { - printf("%ld", static_cast(shapes[i].mesh.tags[t].intValues[j])); - if (j < (shapes[i].mesh.tags[t].intValues.size()-1)) - { - printf(", "); - } + for (size_t j = 0; j < shapes[i].mesh.tags[t].intValues.size(); ++j) { + printf("%ld", static_cast(shapes[i].mesh.tags[t].intValues[j])); + if (j < (shapes[i].mesh.tags[t].intValues.size() - 1)) { + printf(", "); + } } printf("]"); printf(" floats: ["); - for (size_t j = 0; j < shapes[i].mesh.tags[t].floatValues.size(); ++j) - { - printf("%f", static_cast(shapes[i].mesh.tags[t].floatValues[j])); - if (j < (shapes[i].mesh.tags[t].floatValues.size()-1)) - { - printf(", "); - } + for (size_t j = 0; j < shapes[i].mesh.tags[t].floatValues.size(); ++j) { + printf("%f", static_cast( + shapes[i].mesh.tags[t].floatValues[j])); + if (j < (shapes[i].mesh.tags[t].floatValues.size() - 1)) { + printf(", "); + } } printf("]"); printf(" strings: ["); - for (size_t j = 0; j < shapes[i].mesh.tags[t].stringValues.size(); ++j) - { - printf("%s", shapes[i].mesh.tags[t].stringValues[j].c_str()); - if (j < (shapes[i].mesh.tags[t].stringValues.size()-1)) - { - printf(", "); - } + for (size_t j = 0; j < shapes[i].mesh.tags[t].stringValues.size(); ++j) { + printf("%s", shapes[i].mesh.tags[t].stringValues[j].c_str()); + if (j < (shapes[i].mesh.tags[t].stringValues.size() - 1)) { + printf(", "); + } } printf("]"); printf("\n"); @@ -191,25 +199,59 @@ static void PrintInfo(const tinyobj::attrib_t &attrib, const std::vector(i), materials[i].name.c_str()); - printf(" material.Ka = (%f, %f ,%f)\n", static_cast(materials[i].ambient[0]), static_cast(materials[i].ambient[1]), static_cast(materials[i].ambient[2])); - printf(" material.Kd = (%f, %f ,%f)\n", static_cast(materials[i].diffuse[0]), static_cast(materials[i].diffuse[1]), static_cast(materials[i].diffuse[2])); - printf(" material.Ks = (%f, %f ,%f)\n", static_cast(materials[i].specular[0]), static_cast(materials[i].specular[1]), static_cast(materials[i].specular[2])); - printf(" material.Tr = (%f, %f ,%f)\n", static_cast(materials[i].transmittance[0]), static_cast(materials[i].transmittance[1]), static_cast(materials[i].transmittance[2])); - printf(" material.Ke = (%f, %f ,%f)\n", static_cast(materials[i].emission[0]), static_cast(materials[i].emission[1]), static_cast(materials[i].emission[2])); - printf(" material.Ns = %f\n", static_cast(materials[i].shininess)); + printf("material[%ld].name = %s\n", static_cast(i), + materials[i].name.c_str()); + printf(" material.Ka = (%f, %f ,%f)\n", + static_cast(materials[i].ambient[0]), + static_cast(materials[i].ambient[1]), + static_cast(materials[i].ambient[2])); + printf(" material.Kd = (%f, %f ,%f)\n", + static_cast(materials[i].diffuse[0]), + static_cast(materials[i].diffuse[1]), + static_cast(materials[i].diffuse[2])); + printf(" material.Ks = (%f, %f ,%f)\n", + static_cast(materials[i].specular[0]), + static_cast(materials[i].specular[1]), + static_cast(materials[i].specular[2])); + printf(" material.Tr = (%f, %f ,%f)\n", + static_cast(materials[i].transmittance[0]), + static_cast(materials[i].transmittance[1]), + static_cast(materials[i].transmittance[2])); + printf(" material.Ke = (%f, %f ,%f)\n", + static_cast(materials[i].emission[0]), + static_cast(materials[i].emission[1]), + static_cast(materials[i].emission[2])); + printf(" material.Ns = %f\n", + static_cast(materials[i].shininess)); printf(" material.Ni = %f\n", static_cast(materials[i].ior)); - printf(" material.dissolve = %f\n", static_cast(materials[i].dissolve)); - printf(" material.illum = %d\n", materials[i].illum); + printf(" material.dissolve = %f\n", + static_cast(materials[i].dissolve)); + printf(" material.illum = %d\n", materials[i].illum); printf(" material.map_Ka = %s\n", materials[i].ambient_texname.c_str()); printf(" material.map_Kd = %s\n", materials[i].diffuse_texname.c_str()); printf(" material.map_Ks = %s\n", materials[i].specular_texname.c_str()); - printf(" material.map_Ns = %s\n", materials[i].specular_highlight_texname.c_str()); + printf(" material.map_Ns = %s\n", + materials[i].specular_highlight_texname.c_str()); printf(" material.map_bump = %s\n", materials[i].bump_texname.c_str()); printf(" material.map_d = %s\n", materials[i].alpha_texname.c_str()); printf(" material.disp = %s\n", materials[i].displacement_texname.c_str()); - std::map::const_iterator it(materials[i].unknown_parameter.begin()); - std::map::const_iterator itEnd(materials[i].unknown_parameter.end()); + printf(" <>\n"); + printf(" material.Pr = %f\n", materials[i].roughness); + printf(" material.Pm = %f\n", materials[i].metallic); + printf(" material.Ps = %f\n", materials[i].sheen); + printf(" material.Pc = %f\n", materials[i].clearcoat_thickness); + printf(" material.Pcr = %f\n", materials[i].clearcoat_thickness); + printf(" material.aniso = %f\n", materials[i].anisotropy); + printf(" material.anisor = %f\n", materials[i].anisotropy_rotation); + printf(" material.map_Ke = %s\n", materials[i].emissive_texname.c_str()); + printf(" material.map_Pr = %s\n", materials[i].roughness_texname.c_str()); + printf(" material.map_Pm = %s\n", materials[i].metallic_texname.c_str()); + printf(" material.map_Ps = %s\n", materials[i].sheen_texname.c_str()); + printf(" material.norm = %s\n", materials[i].normal_texname.c_str()); + std::map::const_iterator it( + materials[i].unknown_parameter.begin()); + std::map::const_iterator itEnd( + materials[i].unknown_parameter.end()); for (; it != itEnd; it++) { printf(" material.%s = %s\n", it->first.c_str(), it->second.c_str()); @@ -218,12 +260,8 @@ static void PrintInfo(const tinyobj::attrib_t &attrib, const std::vector* materials, - std::map* matMap, - std::string* err) - { - (void)matId; - (void)err; - LoadMtl(matMap, materials, &m_matSStream); - return true; - } - - private: - std::stringstream m_matSStream; - }; + objStream << "mtllib cube.mtl\n" + "\n" + "v 0.000000 2.000000 2.000000\n" + "v 0.000000 0.000000 2.000000\n" + "v 2.000000 0.000000 2.000000\n" + "v 2.000000 2.000000 2.000000\n" + "v 0.000000 2.000000 0.000000\n" + "v 0.000000 0.000000 0.000000\n" + "v 2.000000 0.000000 0.000000\n" + "v 2.000000 2.000000 0.000000\n" + "# 8 vertices\n" + "\n" + "g front cube\n" + "usemtl white\n" + "f 1 2 3 4\n" + "g back cube\n" + "# expects white material\n" + "f 8 7 6 5\n" + "g right cube\n" + "usemtl red\n" + "f 4 3 7 8\n" + "g top cube\n" + "usemtl white\n" + "f 5 1 4 8\n" + "g left cube\n" + "usemtl green\n" + "f 5 6 2 1\n" + "g bottom cube\n" + "usemtl white\n" + "f 2 6 7 3\n" + "# 6 elements"; + + std::string matStream( + "newmtl white\n" + "Ka 0 0 0\n" + "Kd 1 1 1\n" + "Ks 0 0 0\n" + "\n" + "newmtl red\n" + "Ka 0 0 0\n" + "Kd 1 0 0\n" + "Ks 0 0 0\n" + "\n" + "newmtl green\n" + "Ka 0 0 0\n" + "Kd 0 1 0\n" + "Ks 0 0 0\n" + "\n" + "newmtl blue\n" + "Ka 0 0 0\n" + "Kd 0 0 1\n" + "Ks 0 0 0\n" + "\n" + "newmtl light\n" + "Ka 20 20 20\n" + "Kd 1 1 1\n" + "Ks 0 0 0"); + + using namespace tinyobj; + class MaterialStringStreamReader : public MaterialReader { + public: + MaterialStringStreamReader(const std::string& matSStream) + : m_matSStream(matSStream) {} + virtual ~MaterialStringStreamReader() {} + virtual bool operator()(const std::string& matId, + std::vector* materials, + std::map* matMap, + std::string* err) { + (void)matId; + (void)err; + LoadMtl(matMap, materials, &m_matSStream); + return true; + } + + private: + std::stringstream m_matSStream; + }; MaterialStringStreamReader matSSReader(matStream); tinyobj::attrib_t attrib; std::vector shapes; std::vector materials; std::string err; - bool ret = tinyobj::LoadObj(&attrib, &shapes, &materials, &err, &objStream, &matSSReader); - + bool ret = tinyobj::LoadObj(&attrib, &shapes, &materials, &err, &objStream, + &matSSReader); + if (!err.empty()) { std::cerr << err << std::endl; } @@ -356,15 +389,11 @@ std::string matStream( } PrintInfo(attrib, shapes, materials); - + return true; } -int -main( - int argc, - char **argv) -{ +int main(int argc, char** argv) { if (argc > 1) { const char* basepath = "models/"; if (argc > 2) { @@ -372,10 +401,11 @@ main( } assert(true == TestLoadObj(argv[1], basepath)); } else { - //assert(true == TestLoadObj("cornell_box.obj")); - //assert(true == TestLoadObj("cube.obj")); + // assert(true == TestLoadObj("cornell_box.obj")); + // assert(true == TestLoadObj("cube.obj")); assert(true == TestStreamLoadObj()); - assert(true == TestLoadObj("models/catmark_torus_creases0.obj", "models/", false)); + assert(true == + TestLoadObj("models/catmark_torus_creases0.obj", "models/", false)); } return 0; diff --git a/models/pbr-mat-ext.mtl b/models/pbr-mat-ext.mtl new file mode 100644 index 0000000..bed905d --- /dev/null +++ b/models/pbr-mat-ext.mtl @@ -0,0 +1,19 @@ +# .MTL with PBR extension. +newmtl pbr +Ka 0 0 0 +Kd 1 1 1 +Ks 0 0 0 +Ke 0.1 0.1 0.1 +Pr 0.2 +Pm 0.3 +Ps 0.4 +Pc 0.5 +Pcr 0.6 +aniso 0.7 +anisor 0.8 +map_Pr roughness.tex +map_Pm metallic.tex +map_Ps sheen.tex +map_Ke emissive.tex +norm normalmap.tex + diff --git a/models/pbr-mat-ext.obj b/models/pbr-mat-ext.obj new file mode 100644 index 0000000..bb3e371 --- /dev/null +++ b/models/pbr-mat-ext.obj @@ -0,0 +1,10 @@ +mtllib pbr-mat-ext.mtl + +o floor +usemtl pbr +v 552.8 0.0 0.0 +v 0.0 0.0 0.0 +v 0.0 0.0 559.2 +v 549.6 0.0 559.2 + +f 1 2 3 4 diff --git a/tests/tester.cc b/tests/tester.cc index e8e3b42..dbfb939 100644 --- a/tests/tester.cc +++ b/tests/tester.cc @@ -293,7 +293,7 @@ std::string matStream( return true; } -const char* gMtlBasePath = "../models"; +const char* gMtlBasePath = "../models/"; TEST_CASE("cornell_box", "[Loader]") { @@ -319,6 +319,34 @@ TEST_CASE("catmark_torus_creases0", "[Loader]") { REQUIRE(8 == shapes[0].mesh.tags.size()); } +TEST_CASE("pbr", "[Loader]") { + + tinyobj::attrib_t attrib; + std::vector shapes; + std::vector materials; + + std::string err; + bool ret = tinyobj::LoadObj(&attrib, &shapes, &materials, &err, "../models/pbr-mat-ext.obj", gMtlBasePath, /*triangulate*/false); + + if (!err.empty()) { + std::cerr << err << std::endl; + } + REQUIRE(true == ret); + REQUIRE(1 == materials.size()); + REQUIRE(0.2 == Approx(materials[0].roughness)); + REQUIRE(0.3 == Approx(materials[0].metallic)); + REQUIRE(0.4 == Approx(materials[0].sheen)); + REQUIRE(0.5 == Approx(materials[0].clearcoat_thickness)); + REQUIRE(0.6 == Approx(materials[0].clearcoat_roughness)); + REQUIRE(0.7 == Approx(materials[0].anisotropy)); + REQUIRE(0.8 == Approx(materials[0].anisotropy_rotation)); + REQUIRE(0 == materials[0].roughness_texname.compare("roughness.tex")); + REQUIRE(0 == materials[0].metallic_texname.compare("metallic.tex")); + REQUIRE(0 == materials[0].sheen_texname.compare("sheen.tex")); + REQUIRE(0 == materials[0].emissive_texname.compare("emissive.tex")); + REQUIRE(0 == materials[0].normal_texname.compare("normalmap.tex")); +} + TEST_CASE("stream_load", "[Stream]") { REQUIRE(true == TestStreamLoadObj()); } diff --git a/tiny_obj_loader.h b/tiny_obj_loader.h index 3534b91..777ca70 100644 --- a/tiny_obj_loader.h +++ b/tiny_obj_loader.h @@ -91,6 +91,22 @@ typedef struct { std::string bump_texname; // map_bump, bump std::string displacement_texname; // disp std::string alpha_texname; // map_d + + // PBR extension + // http://exocortex.com/blog/extending_wavefront_mtl_to_support_pbr + float roughness; // [0, 1] default 0 + float metallic; // [0, 1] default 0 + float sheen; // [0, 1] default 0 + float clearcoat_thickness; // [0, 1] default 0 + float clearcoat_roughness; // [0, 1] default 0 + float anisotropy; // aniso. [0, 1] default 0 + float anisotropy_rotation; // anisor. [0, 1] default 0 + std::string roughness_texname; // map_Pr + std::string metallic_texname; // map_Pm + std::string sheen_texname; // map_Ps + std::string emissive_texname; // map_Ke + std::string normal_texname; // norm. For normal mapping. + std::map unknown_parameter; } material_t; @@ -156,7 +172,6 @@ typedef struct callback_t_ { mtllib_cb(NULL), group_cb(NULL), object_cb(NULL) {} - } callback_t; class MaterialReader { @@ -563,6 +578,20 @@ static void InitMaterial(material_t *material) { material->dissolve = 1.f; material->shininess = 1.f; material->ior = 1.f; + + material->roughness = 0.f; + material->metallic = 0.f; + material->sheen = 0.f; + material->clearcoat_thickness = 0.f; + material->clearcoat_roughness = 0.f; + material->anisotropy_rotation = 0.f; + material->anisotropy = 0.f; + material->roughness_texname = ""; + material->metallic_texname = ""; + material->sheen_texname = ""; + material->emissive_texname = ""; + material->normal_texname = ""; + material->unknown_parameter.clear(); } @@ -779,6 +808,55 @@ void LoadMtl(std::map *material_map, continue; } + // PBR: roughness + if (token[0] == 'P' && token[1] == 'r' && IS_SPACE(token[2])) { + token += 2; + material.roughness = parseFloat(&token); + continue; + } + + // PBR: metallic + if (token[0] == 'P' && token[1] == 'm' && IS_SPACE(token[2])) { + token += 2; + material.metallic = parseFloat(&token); + continue; + } + + // PBR: sheen + if (token[0] == 'P' && token[1] == 's' && IS_SPACE(token[2])) { + token += 2; + material.sheen = parseFloat(&token); + continue; + } + + // PBR: clearcoat thickness + if (token[0] == 'P' && token[1] == 'c' && IS_SPACE(token[2])) { + token += 2; + material.clearcoat_thickness = parseFloat(&token); + continue; + } + + // PBR: clearcoat roughness + if ((0 == strncmp(token, "Pcr", 3)) && IS_SPACE(token[3])) { + token += 4; + material.clearcoat_roughness = parseFloat(&token); + continue; + } + + // PBR: anisotropy + if ((0 == strncmp(token, "aniso", 5)) && IS_SPACE(token[5])) { + token += 6; + material.anisotropy = parseFloat(&token); + continue; + } + + // PBR: anisotropy rotation + if ((0 == strncmp(token, "anisor", 6)) && IS_SPACE(token[6])) { + token += 7; + material.anisotropy_rotation = parseFloat(&token); + continue; + } + // ambient texture if ((0 == strncmp(token, "map_Ka", 6)) && IS_SPACE(token[6])) { token += 7; @@ -835,6 +913,41 @@ void LoadMtl(std::map *material_map, continue; } + // PBR: roughness texture + if ((0 == strncmp(token, "map_Pr", 6)) && IS_SPACE(token[6])) { + token += 7; + material.roughness_texname = token; + continue; + } + + // PBR: metallic texture + if ((0 == strncmp(token, "map_Pm", 6)) && IS_SPACE(token[6])) { + token += 7; + material.metallic_texname = token; + continue; + } + + // PBR: sheen texture + if ((0 == strncmp(token, "map_Ps", 6)) && IS_SPACE(token[6])) { + token += 7; + material.sheen_texname = token; + continue; + } + + // PBR: emissive texture + if ((0 == strncmp(token, "map_Ke", 6)) && IS_SPACE(token[6])) { + token += 7; + material.emissive_texname = token; + continue; + } + + // PBR: normal map texture + if ((0 == strncmp(token, "norm", 4)) && IS_SPACE(token[4])) { + token += 5; + material.normal_texname = token; + continue; + } + // unknown parameter const char *_space = strchr(token, ' '); if (!_space) { -- cgit v1.2.3 From d496d8eab6fceac4b095c528ca07848e0ac9928a Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Wed, 27 Jul 2016 00:02:53 +0900 Subject: Remove some invalid comments. Fix calling `index_cb` per `f` line. Fixes #87. --- examples/callback_api/main.cc | 105 ++++++++++++++++++++++-------------------- tiny_obj_loader.h | 47 +++++++++++-------- 2 files changed, 83 insertions(+), 69 deletions(-) diff --git a/examples/callback_api/main.cc b/examples/callback_api/main.cc index 4f492d0..44ec62f 100644 --- a/examples/callback_api/main.cc +++ b/examples/callback_api/main.cc @@ -1,29 +1,34 @@ +// +// An example of how to use callback API. +// This example is minimum and incomplete. Just showing the usage of callback +// API. +// You need to implement your own Mesh data struct constrution based on this +// example in practical. +// #define TINYOBJLOADER_IMPLEMENTATION #include "tiny_obj_loader.h" +#include #include #include -#include +#include #include #include -#include -typedef struct -{ +typedef struct { std::vector vertices; std::vector normals; std::vector texcoords; - std::vector v_indices; - std::vector vn_indices; - std::vector vt_indices; + std::vector v_indices; + std::vector vn_indices; + std::vector vt_indices; std::vector materials; } MyMesh; -void vertex_cb(void *user_data, float x, float y, float z) -{ - MyMesh *mesh = reinterpret_cast(user_data); +void vertex_cb(void *user_data, float x, float y, float z) { + MyMesh *mesh = reinterpret_cast(user_data); printf("v[%ld] = %f, %f, %f\n", mesh->vertices.size() / 3, x, y, z); mesh->vertices.push_back(x); @@ -31,9 +36,8 @@ void vertex_cb(void *user_data, float x, float y, float z) mesh->vertices.push_back(z); } -void normal_cb(void *user_data, float x, float y, float z) -{ - MyMesh *mesh = reinterpret_cast(user_data); +void normal_cb(void *user_data, float x, float y, float z) { + MyMesh *mesh = reinterpret_cast(user_data); printf("vn[%ld] = %f, %f, %f\n", mesh->normals.size() / 3, x, y, z); mesh->normals.push_back(x); @@ -41,49 +45,55 @@ void normal_cb(void *user_data, float x, float y, float z) mesh->normals.push_back(z); } -void texcoord_cb(void *user_data, float x, float y) -{ - MyMesh *mesh = reinterpret_cast(user_data); +void texcoord_cb(void *user_data, float x, float y) { + MyMesh *mesh = reinterpret_cast(user_data); printf("vt[%ld] = %f, %f\n", mesh->texcoords.size() / 2, x, y); mesh->texcoords.push_back(x); mesh->texcoords.push_back(y); } -void index_cb(void *user_data, int v_idx, int vn_idx, int vt_idx) -{ - // NOTE: the value of each index is raw value. +void index_cb(void *user_data, tinyobj::index_t *indices, int num_indices) { + // NOTE: the value of each index is raw value. // For example, the application must manually adjust the index with offset - // (e.g. v_indices.size()) when the value is negative(relative index). + // (e.g. v_indices.size()) when the value is negative(whic means relative + // index). + // Also, the first index starts with 1, not 0. // See fixIndex() function in tiny_obj_loader.h for details. - // Also, -2147483648(0x80000000) is set for the index value which does not exist in .obj - MyMesh *mesh = reinterpret_cast(user_data); - printf("idx[%ld] = %d, %d, %d\n", mesh->v_indices.size(), v_idx, vn_idx, vt_idx); - - if (v_idx != 0x80000000) { - mesh->v_indices.push_back(v_idx); - } - if (vn_idx != 0x80000000) { - mesh->vn_indices.push_back(vn_idx); - } - if (vt_idx != 0x80000000) { - mesh->vt_indices.push_back(vt_idx); + // Also, -2147483648(0x80000000 = -INT_MAX) is set for the index value which + // does not exist in .obj + MyMesh *mesh = reinterpret_cast(user_data); + + for (int i = 0; i < num_indices; i++) { + tinyobj::index_t idx = indices[i]; + printf("idx[%ld] = %d, %d, %d\n", mesh->v_indices.size(), idx.vertex_index, + idx.normal_index, idx.texcoord_index); + + if (idx.vertex_index != 0x80000000) { + mesh->v_indices.push_back(idx.vertex_index); + } + if (idx.normal_index != 0x80000000) { + mesh->vn_indices.push_back(idx.normal_index); + } + if (idx.texcoord_index != 0x80000000) { + mesh->vt_indices.push_back(idx.texcoord_index); + } } } -void usemtl_cb(void *user_data, const char* name, int material_idx) -{ - MyMesh *mesh = reinterpret_cast(user_data); +void usemtl_cb(void *user_data, const char *name, int material_idx) { + MyMesh *mesh = reinterpret_cast(user_data); if ((material_idx > -1) && (material_idx < mesh->materials.size())) { - printf("usemtl. material id = %d(name = %s)\n", material_idx, mesh->materials[material_idx].name.c_str()); + printf("usemtl. material id = %d(name = %s)\n", material_idx, + mesh->materials[material_idx].name.c_str()); } else { printf("usemtl. name = %s\n", name); } } -void mtllib_cb(void *user_data, const tinyobj::material_t *materials, int num_materials) -{ - MyMesh *mesh = reinterpret_cast(user_data); +void mtllib_cb(void *user_data, const tinyobj::material_t *materials, + int num_materials) { + MyMesh *mesh = reinterpret_cast(user_data); printf("mtllib. # of materials = %d\n", num_materials); for (int i = 0; i < num_materials; i++) { @@ -91,9 +101,8 @@ void mtllib_cb(void *user_data, const tinyobj::material_t *materials, int num_ma } } -void group_cb(void *user_data, const char **names, int num_names) -{ - //MyMesh *mesh = reinterpret_cast(user_data); +void group_cb(void *user_data, const char **names, int num_names) { + // MyMesh *mesh = reinterpret_cast(user_data); printf("group : name = \n"); for (int i = 0; i < num_names; i++) { @@ -101,16 +110,12 @@ void group_cb(void *user_data, const char **names, int num_names) } } -void object_cb(void *user_data, const char *name) -{ - //MyMesh *mesh = reinterpret_cast(user_data); +void object_cb(void *user_data, const char *name) { + // MyMesh *mesh = reinterpret_cast(user_data); printf("object : name = %s\n", name); - } -int -main(int argc, char** argv) -{ +int main(int argc, char **argv) { tinyobj::callback_t cb; cb.vertex_cb = vertex_cb; cb.normal_cb = normal_cb; @@ -131,7 +136,7 @@ main(int argc, char** argv) } tinyobj::MaterialFileReader mtlReader("../../models/"); - + bool ret = tinyobj::LoadObjWithCallback(&mesh, cb, &err, &ifs, &mtlReader); if (!err.empty()) { diff --git a/tiny_obj_loader.h b/tiny_obj_loader.h index 777ca70..4f79f42 100644 --- a/tiny_obj_loader.h +++ b/tiny_obj_loader.h @@ -151,11 +151,16 @@ typedef struct callback_t_ { void (*vertex_cb)(void *user_data, float x, float y, float z); void (*normal_cb)(void *user_data, float x, float y, float z); void (*texcoord_cb)(void *user_data, float x, float y); - // -2147483648 will be passed for undefined index - void (*index_cb)(void *user_data, int v_idx, int vn_idx, int vt_idx); - // `name` material name, `materialId` = the array index of material_t[]. -1 if + + // called per 'f' line. num_indices is the number of face indices(e.g. 3 for + // triangle, 4 for quad) + // -2147483648(-INT_MAX) will be passed for undefined index in index_t + // members. + void (*index_cb)(void *user_data, index_t *indices, int num_indices); + // `name` material name, `material_id` = the array index of material_t[]. -1 + // if // a material not found in .mtl - void (*usemtl_cb)(void *user_data, const char *name, int materialId); + void (*usemtl_cb)(void *user_data, const char *name, int material_id); // `materials` = parsed material data. void (*mtllib_cb)(void *user_data, const material_t *materials, int num_materials); @@ -216,9 +221,7 @@ bool LoadObj(attrib_t *attrib, std::vector *shapes, /// `callback.mtllib_cb`. /// Returns true when loading .obj/.mtl 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. +/// See `examples/callback_api/` for how to use this function. bool LoadObjWithCallback(void *user_data, const callback_t &callback, std::string *err, std::istream *inStream, MaterialReader *readMatFn); @@ -1312,14 +1315,11 @@ bool LoadObjWithCallback(void *user_data, const callback_t &callback, // material std::map material_map; - int materialId = -1; // -1 = invalid + int material_id = -1; // -1 = invalid - 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; + std::getline(*inStream, linebuf); // Trim newline '\r\n' or '\n' if (linebuf.size() > 0) { @@ -1383,15 +1383,24 @@ bool LoadObjWithCallback(void *user_data, const callback_t &callback, token += 2; token += strspn(token, " \t"); + std::vector indices; while (!IS_NEW_LINE(token[0])) { vertex_index vi = parseRawTriple(&token); - if (callback.index_cb) { - callback.index_cb(user_data, vi.v_idx, vi.vn_idx, vi.vt_idx); - } + + index_t idx; + idx.vertex_index = vi.v_idx; + idx.normal_index = vi.vn_idx; + idx.texcoord_index = vi.vt_idx; + + indices.push_back(idx); size_t n = strspn(token, " \t\r"); token += n; } + if (callback.index_cb && indices.size() > 0) { + callback.index_cb(user_data, &indices.at(0), indices.size()); + } + continue; } @@ -1412,12 +1421,12 @@ bool LoadObjWithCallback(void *user_data, const callback_t &callback, // { error!! material not found } } - if (newMaterialId != materialId) { - materialId = newMaterialId; + if (newMaterialId != material_id) { + material_id = newMaterialId; } if (callback.usemtl_cb) { - callback.usemtl_cb(user_data, namebuf, materialId); + callback.usemtl_cb(user_data, namebuf, material_id); } continue; -- cgit v1.2.3 From 0b0bf60137ba7e87842d5213b90b9144d34ea131 Mon Sep 17 00:00:00 2001 From: "Adi Shavit @ MacBookPro" Date: Wed, 27 Jul 2016 10:00:13 +0300 Subject: Move heap-based objects like vectors and strings outside the parse loop in LoadObjWithCallback(). This avoids reallocation on every use making the code faster. --- tiny_obj_loader.h | 61 ++++++++++++++++++++++++++++++------------------------- 1 file changed, 33 insertions(+), 28 deletions(-) diff --git a/tiny_obj_loader.h b/tiny_obj_loader.h index 4f79f42..68f0d7d 100644 --- a/tiny_obj_loader.h +++ b/tiny_obj_loader.h @@ -1317,8 +1317,15 @@ bool LoadObjWithCallback(void *user_data, const callback_t &callback, std::map material_map; int material_id = -1; // -1 = invalid + std::vector indices; + std::vector materials; + std::vector names; + names.reserve(2); + std::string name; + std::vector names_out; + + std::string linebuf; while (inStream->peek() != -1) { - std::string linebuf; std::getline(*inStream, linebuf); // Trim newline '\r\n' or '\n' @@ -1383,7 +1390,7 @@ bool LoadObjWithCallback(void *user_data, const callback_t &callback, token += 2; token += strspn(token, " \t"); - std::vector indices; + indices.clear(); while (!IS_NEW_LINE(token[0])) { vertex_index vi = parseRawTriple(&token); @@ -1434,37 +1441,36 @@ bool LoadObjWithCallback(void *user_data, const callback_t &callback, // load mtl if ((0 == strncmp(token, "mtllib", 6)) && IS_SPACE((token[6]))) { - char namebuf[TINYOBJ_SSCANF_BUFFER_SIZE]; - token += 7; + char namebuf[TINYOBJ_SSCANF_BUFFER_SIZE]; + token += 7; #ifdef _MSC_VER - sscanf_s(token, "%s", namebuf, (unsigned)_countof(namebuf)); + sscanf_s(token, "%s", namebuf, (unsigned)_countof(namebuf)); #else - sscanf(token, "%s", namebuf); + sscanf(token, "%s", namebuf); #endif - std::string err_mtl; - std::vector materials; - bool ok = (*readMatFn)(namebuf, &materials, &material_map, &err_mtl); - if (err) { - (*err) += err_mtl; - } + std::string err_mtl; + materials.clear(); + bool ok = (*readMatFn)(namebuf, &materials, &material_map, &err_mtl); + if (err) { + (*err) += err_mtl; + } - if (!ok) { - return false; - } + if (!ok) { + return false; + } - if (callback.mtllib_cb) { - callback.mtllib_cb(user_data, &materials.at(0), - static_cast(materials.size())); - } + if (callback.mtllib_cb) { + callback.mtllib_cb(user_data, &materials.at(0), + static_cast(materials.size())); + } continue; } // group name if (token[0] == 'g' && IS_SPACE((token[1]))) { - std::vector names; - names.reserve(2); + names.clear(); while (!IS_NEW_LINE(token[0])) { std::string str = parseString(&token); @@ -1474,23 +1480,22 @@ bool LoadObjWithCallback(void *user_data, const callback_t &callback, assert(names.size() > 0); - std::string name; // names[0] must be 'g', so skip the 0th element. if (names.size() > 1) { name = names[1]; } else { - name = ""; + name.clear(); } if (callback.group_cb) { if (names.size() > 1) { // create const char* array. - std::vector tmp(names.size() - 1); - for (size_t j = 0; j < tmp.size(); j++) { - tmp[j] = names[j + 1].c_str(); + names_out.resize(names.size() - 1); + for (size_t j = 0; j < names_out.size(); j++) { + names_out[j] = names[j + 1].c_str(); } - callback.group_cb(user_data, &tmp.at(0), - static_cast(tmp.size())); + callback.group_cb(user_data, &names_out.at(0), + static_cast(names_out.size())); } else { callback.group_cb(user_data, NULL, 0); -- cgit v1.2.3 From 3736a5791f63881b3ebe14fbb593099a7dfe7bc1 Mon Sep 17 00:00:00 2001 From: "Adi Shavit @ MacBookPro" Date: Wed, 27 Jul 2016 10:04:22 +0300 Subject: Fix a crash bug in `LoadObjWithCallback()` when passing a nullptr as `MaterialReader *readMatFn`. - This preserves the existing API. If `nullptr` is passed then the material file will be ignored. This is useful when the user is not interested in the materials. - Note that if `nullptr` is not a valid option for this function, then the API needs to be changed to take `MaterialReader&`. --- tiny_obj_loader.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tiny_obj_loader.h b/tiny_obj_loader.h index 68f0d7d..0e85632 100644 --- a/tiny_obj_loader.h +++ b/tiny_obj_loader.h @@ -1441,6 +1441,7 @@ bool LoadObjWithCallback(void *user_data, const callback_t &callback, // load mtl if ((0 == strncmp(token, "mtllib", 6)) && IS_SPACE((token[6]))) { + if (readMatFn) { char namebuf[TINYOBJ_SSCANF_BUFFER_SIZE]; token += 7; #ifdef _MSC_VER @@ -1464,6 +1465,7 @@ bool LoadObjWithCallback(void *user_data, const callback_t &callback, callback.mtllib_cb(user_data, &materials.at(0), static_cast(materials.size())); } + } continue; } -- cgit v1.2.3 From 75e64cd47a1217f4200bfcb2c65c5c1ccc196dbf Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Wed, 27 Jul 2016 16:16:51 +0900 Subject: Support parsing `w` element of vertex coordinate, and also third element of texture coordinates. Fixes #88. --- examples/callback_api/main.cc | 16 +++++++++++----- tiny_obj_loader.h | 36 +++++++++++++++++++++--------------- 2 files changed, 32 insertions(+), 20 deletions(-) diff --git a/examples/callback_api/main.cc b/examples/callback_api/main.cc index 44ec62f..97b8867 100644 --- a/examples/callback_api/main.cc +++ b/examples/callback_api/main.cc @@ -27,13 +27,14 @@ typedef struct { } MyMesh; -void vertex_cb(void *user_data, float x, float y, float z) { +void vertex_cb(void *user_data, float x, float y, float z, float w) { MyMesh *mesh = reinterpret_cast(user_data); - printf("v[%ld] = %f, %f, %f\n", mesh->vertices.size() / 3, x, y, z); + printf("v[%ld] = %f, %f, %f (w %f)\n", mesh->vertices.size() / 3, x, y, z, w); mesh->vertices.push_back(x); mesh->vertices.push_back(y); mesh->vertices.push_back(z); + // Discard w } void normal_cb(void *user_data, float x, float y, float z) { @@ -45,12 +46,13 @@ void normal_cb(void *user_data, float x, float y, float z) { mesh->normals.push_back(z); } -void texcoord_cb(void *user_data, float x, float y) { +void texcoord_cb(void *user_data, float x, float y, float z) { MyMesh *mesh = reinterpret_cast(user_data); - printf("vt[%ld] = %f, %f\n", mesh->texcoords.size() / 2, x, y); + printf("vt[%ld] = %f, %f, %f\n", mesh->texcoords.size() / 3, x, y, z); mesh->texcoords.push_back(x); mesh->texcoords.push_back(y); + mesh->texcoords.push_back(z); } void index_cb(void *user_data, tinyobj::index_t *indices, int num_indices) { @@ -128,7 +130,11 @@ int main(int argc, char **argv) { MyMesh mesh; std::string err; - std::ifstream ifs("../../models/cornell_box.obj"); + std::string filename = "../../models/cornell_box.obj"; + if (argc > 1) { + filename = std::string(argv[1]); + } + std::ifstream ifs(filename.c_str()); if (ifs.fail()) { std::cerr << "file not found." << std::endl; diff --git a/tiny_obj_loader.h b/tiny_obj_loader.h index 4f79f42..d9b8148 100644 --- a/tiny_obj_loader.h +++ b/tiny_obj_loader.h @@ -148,9 +148,12 @@ typedef struct { } attrib_t; typedef struct callback_t_ { - void (*vertex_cb)(void *user_data, float x, float y, float z); + // W is optional and set to 1 if there is no `w` item in `v` line + void (*vertex_cb)(void *user_data, float x, float y, float z, float w); void (*normal_cb)(void *user_data, float x, float y, float z); - void (*texcoord_cb)(void *user_data, float x, float y); + + // y and z are optional and set to 0 if there is no `y` and/or `z` item(s) in `vt` line. + void (*texcoord_cb)(void *user_data, float x, float y, float z); // called per 'f' line. num_indices is the number of face indices(e.g. 3 for // triangle, 4 for quad) @@ -443,18 +446,13 @@ fail: return false; } -static inline float parseFloat(const char **token) { +static inline float parseFloat(const char **token, double default_value = 0.0) { (*token) += strspn((*token), " \t"); -#ifdef TINY_OBJ_LOADER_OLD_FLOAT_PARSER - float f = static_cast(atof(*token)); - (*token) += strcspn((*token), " \t\r"); -#else const char *end = (*token) + strcspn((*token), " \t\r"); - double val = 0.0; + double val = default_value; tryParseDouble((*token), end, &val); float f = static_cast(val); (*token) = end; -#endif return f; } @@ -470,6 +468,14 @@ static inline void parseFloat3(float *x, float *y, float *z, (*z) = parseFloat(token); } +static inline void parseV(float *x, float *y, float *z, float *w, + const char **token) { + (*x) = parseFloat(token); + (*y) = parseFloat(token); + (*z) = parseFloat(token); + (*w) = parseFloat(token, 1.0); +} + static tag_sizes parseTagTriple(const char **token) { tag_sizes ts; @@ -1348,10 +1354,10 @@ bool LoadObjWithCallback(void *user_data, const callback_t &callback, // vertex if (token[0] == 'v' && IS_SPACE((token[1]))) { token += 2; - float x, y, z; - parseFloat3(&x, &y, &z, &token); + float x, y, z, w; // w is optional. default = 1.0 + parseV(&x, &y, &z, &w, &token); if (callback.vertex_cb) { - callback.vertex_cb(user_data, x, y, z); + callback.vertex_cb(user_data, x, y, z, w); } continue; } @@ -1370,10 +1376,10 @@ bool LoadObjWithCallback(void *user_data, const callback_t &callback, // texcoord if (token[0] == 'v' && token[1] == 't' && IS_SPACE((token[2]))) { token += 3; - float x, y; - parseFloat2(&x, &y, &token); + float x, y, z; // y and z are optional. default = 0.0 + parseFloat3(&x, &y, &z, &token); if (callback.texcoord_cb) { - callback.texcoord_cb(user_data, x, y); + callback.texcoord_cb(user_data, x, y, z); } continue; } -- cgit v1.2.3 From 951833812ae17fa4a4b748de1dab807c1a9fcfc0 Mon Sep 17 00:00:00 2001 From: "Adi Shavit @ MacBookPro" Date: Wed, 27 Jul 2016 11:38:25 +0300 Subject: Changes the LoadObjWithCallback() API to accept `std::istream&` instead of pointed since this is a required argument. - Also changing the argument order to allow for defaults for optional arguments. --- tiny_obj_loader.h | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/tiny_obj_loader.h b/tiny_obj_loader.h index 5aaea19..df42093 100644 --- a/tiny_obj_loader.h +++ b/tiny_obj_loader.h @@ -225,9 +225,8 @@ bool LoadObj(attrib_t *attrib, std::vector *shapes, /// Returns true when loading .obj/.mtl become success. /// Returns warning and error message into `err` /// See `examples/callback_api/` for how to use this function. -bool LoadObjWithCallback(void *user_data, const callback_t &callback, - std::string *err, std::istream *inStream, - MaterialReader *readMatFn); +bool LoadObjWithCallback(std::istream &inStream, const callback_t &callback, void *user_data = NULL, + MaterialReader *readMatFn = NULL, std::string *err = NULL); /// Loads object from a std::istream, uses GetMtlIStreamFn to retrieve /// std::istream for materials. @@ -1314,9 +1313,9 @@ bool LoadObj(attrib_t *attrib, std::vector *shapes, return true; } -bool LoadObjWithCallback(void *user_data, const callback_t &callback, - std::string *err, std::istream *inStream, - MaterialReader *readMatFn) { +bool LoadObjWithCallback(std::istream &inStream, const callback_t &callback, void *user_data /*= NULL*/, + MaterialReader *readMatFn /*= NULL*/, + std::string *err /*= NULL*/) { std::stringstream errss; // material @@ -1331,8 +1330,8 @@ bool LoadObjWithCallback(void *user_data, const callback_t &callback, std::vector names_out; std::string linebuf; - while (inStream->peek() != -1) { - std::getline(*inStream, linebuf); + while (inStream.peek() != -1) { + std::getline(inStream, linebuf); // Trim newline '\r\n' or '\n' if (linebuf.size() > 0) { @@ -1411,7 +1410,7 @@ bool LoadObjWithCallback(void *user_data, const callback_t &callback, } if (callback.index_cb && indices.size() > 0) { - callback.index_cb(user_data, &indices.at(0), indices.size()); + callback.index_cb(user_data, &indices.at(0), (int)indices.size()); } continue; -- cgit v1.2.3 From a7ea651bef861158c6f536506112bc220b0babc0 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Thu, 28 Jul 2016 00:30:52 +0900 Subject: Suppress clang warnings. --- tiny_obj_loader.h | 68 +++++++++++++++++++++++++++++-------------------------- 1 file changed, 36 insertions(+), 32 deletions(-) diff --git a/tiny_obj_loader.h b/tiny_obj_loader.h index df42093..a033224 100644 --- a/tiny_obj_loader.h +++ b/tiny_obj_loader.h @@ -24,8 +24,6 @@ THE SOFTWARE. // // version 1.0.0 : Change data structure. Change license from BSD to MIT. -// 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 @@ -152,7 +150,8 @@ typedef struct callback_t_ { void (*vertex_cb)(void *user_data, float x, float y, float z, float w); void (*normal_cb)(void *user_data, float x, float y, float z); - // y and z are optional and set to 0 if there is no `y` and/or `z` item(s) in `vt` line. + // y and z are optional and set to 0 if there is no `y` and/or `z` item(s) in + // `vt` line. void (*texcoord_cb)(void *user_data, float x, float y, float z); // called per 'f' line. num_indices is the number of face indices(e.g. 3 for @@ -225,8 +224,10 @@ bool LoadObj(attrib_t *attrib, std::vector *shapes, /// Returns true when loading .obj/.mtl become success. /// Returns warning and error message into `err` /// See `examples/callback_api/` for how to use this function. -bool LoadObjWithCallback(std::istream &inStream, const callback_t &callback, void *user_data = NULL, - MaterialReader *readMatFn = NULL, std::string *err = NULL); +bool LoadObjWithCallback(std::istream &inStream, const callback_t &callback, + void *user_data = NULL, + MaterialReader *readMatFn = NULL, + std::string *err = NULL); /// Loads object from a std::istream, uses GetMtlIStreamFn to retrieve /// std::istream for materials. @@ -1313,7 +1314,8 @@ bool LoadObj(attrib_t *attrib, std::vector *shapes, return true; } -bool LoadObjWithCallback(std::istream &inStream, const callback_t &callback, void *user_data /*= NULL*/, +bool LoadObjWithCallback(std::istream &inStream, const callback_t &callback, + void *user_data /*= NULL*/, MaterialReader *readMatFn /*= NULL*/, std::string *err /*= NULL*/) { std::stringstream errss; @@ -1360,7 +1362,7 @@ bool LoadObjWithCallback(std::istream &inStream, const callback_t &callback, voi // vertex if (token[0] == 'v' && IS_SPACE((token[1]))) { token += 2; - float x, y, z, w; // w is optional. default = 1.0 + float x, y, z, w; // w is optional. default = 1.0 parseV(&x, &y, &z, &w, &token); if (callback.vertex_cb) { callback.vertex_cb(user_data, x, y, z, w); @@ -1382,7 +1384,7 @@ bool LoadObjWithCallback(std::istream &inStream, const callback_t &callback, voi // texcoord if (token[0] == 'v' && token[1] == 't' && IS_SPACE((token[2]))) { token += 3; - float x, y, z; // y and z are optional. default = 0.0 + float x, y, z; // y and z are optional. default = 0.0 parseFloat3(&x, &y, &z, &token); if (callback.texcoord_cb) { callback.texcoord_cb(user_data, x, y, z); @@ -1410,7 +1412,8 @@ bool LoadObjWithCallback(std::istream &inStream, const callback_t &callback, voi } if (callback.index_cb && indices.size() > 0) { - callback.index_cb(user_data, &indices.at(0), (int)indices.size()); + callback.index_cb(user_data, &indices.at(0), + static_cast(indices.size())); } continue; @@ -1421,7 +1424,8 @@ bool LoadObjWithCallback(std::istream &inStream, const callback_t &callback, voi char namebuf[TINYOBJ_SSCANF_BUFFER_SIZE]; token += 7; #ifdef _MSC_VER - sscanf_s(token, "%s", namebuf, (unsigned)_countof(namebuf)); + sscanf_s(token, "%s", namebuf, + static_cast(_countof(namebuf))); #else sscanf(token, "%s", namebuf); #endif @@ -1446,32 +1450,32 @@ bool LoadObjWithCallback(std::istream &inStream, const callback_t &callback, voi // load mtl if ((0 == strncmp(token, "mtllib", 6)) && IS_SPACE((token[6]))) { - if (readMatFn) { - char namebuf[TINYOBJ_SSCANF_BUFFER_SIZE]; - token += 7; + if (readMatFn) { + char namebuf[TINYOBJ_SSCANF_BUFFER_SIZE]; + token += 7; #ifdef _MSC_VER - sscanf_s(token, "%s", namebuf, (unsigned)_countof(namebuf)); + sscanf_s(token, "%s", namebuf, (unsigned)_countof(namebuf)); #else - sscanf(token, "%s", namebuf); + sscanf(token, "%s", namebuf); #endif - std::string err_mtl; - materials.clear(); - bool ok = (*readMatFn)(namebuf, &materials, &material_map, &err_mtl); - if (err) { - (*err) += err_mtl; - } - - if (!ok) { - return false; - } - - if (callback.mtllib_cb) { - callback.mtllib_cb(user_data, &materials.at(0), - static_cast(materials.size())); - } + std::string err_mtl; + materials.clear(); + bool ok = (*readMatFn)(namebuf, &materials, &material_map, &err_mtl); + if (err) { + (*err) += err_mtl; + } + + if (!ok) { + return false; } + if (callback.mtllib_cb) { + callback.mtllib_cb(user_data, &materials.at(0), + static_cast(materials.size())); + } + } + continue; } @@ -1522,10 +1526,10 @@ bool LoadObjWithCallback(std::istream &inStream, const callback_t &callback, voi #else sscanf(token, "%s", namebuf); #endif - std::string name = std::string(namebuf); + std::string object_name = std::string(namebuf); if (callback.object_cb) { - callback.object_cb(user_data, name.c_str()); + callback.object_cb(user_data, object_name.c_str()); } continue; -- cgit v1.2.3 From 4dee4cc673c319d5fabc9b87989d7ac53dac3840 Mon Sep 17 00:00:00 2001 From: "Adi Shavit @ MacBookPro" Date: Thu, 28 Jul 2016 10:12:16 +0300 Subject: Changes the value indicating non-existent index to 0. OBJ indices are 1 (or -1) based, so 0 is an invalid value. --- tiny_obj_loader.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tiny_obj_loader.h b/tiny_obj_loader.h index df42093..0961225 100644 --- a/tiny_obj_loader.h +++ b/tiny_obj_loader.h @@ -535,7 +535,7 @@ static vertex_index parseTriple(const char **token, int vsize, int vnsize, // Parse raw triples: i, i/j/k, i//k, i/j static vertex_index parseRawTriple(const char **token) { vertex_index vi( - static_cast(0x80000000)); // 0x80000000 = -2147483648 = invalid + static_cast(0)); // 0 is an invalid index in OBJ vi.v_idx = atoi((*token)); (*token) += strcspn((*token), "/ \t\r"); -- cgit v1.2.3 From 1983e889dcaf96fdf5945bd2b2f7e029c3036600 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Thu, 28 Jul 2016 16:21:48 +0900 Subject: Update callback API example. Update API document. --- examples/callback_api/main.cc | 10 +++++----- tiny_obj_loader.h | 3 +-- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/examples/callback_api/main.cc b/examples/callback_api/main.cc index 97b8867..a7a7422 100644 --- a/examples/callback_api/main.cc +++ b/examples/callback_api/main.cc @@ -62,7 +62,7 @@ void index_cb(void *user_data, tinyobj::index_t *indices, int num_indices) { // index). // Also, the first index starts with 1, not 0. // See fixIndex() function in tiny_obj_loader.h for details. - // Also, -2147483648(0x80000000 = -INT_MAX) is set for the index value which + // Also, 0 is set for the index value which // does not exist in .obj MyMesh *mesh = reinterpret_cast(user_data); @@ -71,13 +71,13 @@ void index_cb(void *user_data, tinyobj::index_t *indices, int num_indices) { printf("idx[%ld] = %d, %d, %d\n", mesh->v_indices.size(), idx.vertex_index, idx.normal_index, idx.texcoord_index); - if (idx.vertex_index != 0x80000000) { + if (idx.vertex_index != 0) { mesh->v_indices.push_back(idx.vertex_index); } - if (idx.normal_index != 0x80000000) { + if (idx.normal_index != 0) { mesh->vn_indices.push_back(idx.normal_index); } - if (idx.texcoord_index != 0x80000000) { + if (idx.texcoord_index != 0) { mesh->vt_indices.push_back(idx.texcoord_index); } } @@ -143,7 +143,7 @@ int main(int argc, char **argv) { tinyobj::MaterialFileReader mtlReader("../../models/"); - bool ret = tinyobj::LoadObjWithCallback(&mesh, cb, &err, &ifs, &mtlReader); + bool ret = tinyobj::LoadObjWithCallback(ifs, cb, &mesh, &mtlReader, &err); if (!err.empty()) { std::cerr << err << std::endl; diff --git a/tiny_obj_loader.h b/tiny_obj_loader.h index 799d22a..a1f4645 100644 --- a/tiny_obj_loader.h +++ b/tiny_obj_loader.h @@ -156,8 +156,7 @@ typedef struct callback_t_ { // called per 'f' line. num_indices is the number of face indices(e.g. 3 for // triangle, 4 for quad) - // -2147483648(-INT_MAX) will be passed for undefined index in index_t - // members. + // 0 will be passed for undefined index in index_t members. void (*index_cb)(void *user_data, index_t *indices, int num_indices); // `name` material name, `material_id` = the array index of material_t[]. -1 // if -- cgit v1.2.3 From 51d13700d880d2d5fdaf5bd3547100bafcae7932 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Tue, 2 Aug 2016 16:55:50 +0900 Subject: Skip trailing whitespace in mtl. Fixes #92. --- test.cc | 10 ++- tiny_obj_loader.h | 219 +++++++++++++++++++++++++++--------------------------- 2 files changed, 118 insertions(+), 111 deletions(-) diff --git a/test.cc b/test.cc index 7b675b7..9d53076 100644 --- a/test.cc +++ b/test.cc @@ -302,7 +302,8 @@ std::string matStream( "newmtl light\n" "Ka 20 20 20\n" "Kd 1 1 1\n" - "Ks 0 0 0"); + "Ks 0 0 0\n" + "map_Kd tmp.png \n"); // #92(whitespace after filename) using namespace tinyobj; class MaterialStringStreamReader: @@ -342,6 +343,13 @@ std::string matStream( } PrintInfo(shapes, materials); + + // #92 + for (size_t i = 0; i < materials.size(); i++) { + if (materials[i].name.compare("light") == 0) { + assert(materials[i].diffuse_texname.compare("tmp.png") == 0); + } + } return true; } diff --git a/tiny_obj_loader.h b/tiny_obj_loader.h index 4fafda1..d0a7886 100644 --- a/tiny_obj_loader.h +++ b/tiny_obj_loader.h @@ -42,10 +42,10 @@ #ifndef TINY_OBJ_LOADER_H_ #define TINY_OBJ_LOADER_H_ +#include +#include #include #include -#include -#include namespace tinyobj { @@ -99,52 +99,37 @@ 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 +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 -{ +class float3 { public: - float3() - : x( 0.0f ) - , y( 0.0f ) - , z( 0.0f ) - { - } + 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 ) - { - } + : x(coord_x), y(coord_y), z(coord_z) {} - float3(const float3& from, const float3& to) - { + 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 ); + 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 ) - { + 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); @@ -152,12 +137,10 @@ public: } private: - union - { + union { float coord[3]; - struct - { - float x,y,z; + struct { + float x, y, z; }; }; }; @@ -197,7 +180,7 @@ bool LoadObj(std::vector &shapes, // [output] std::vector &materials, // [output] std::string &err, // [output] const char *filename, const char *mtl_basepath = NULL, - unsigned int flags = 1 ); + unsigned int flags = 1); /// Loads object from a std::istream, uses GetMtlIStreamFn to retrieve /// std::istream for materials. @@ -216,12 +199,12 @@ void LoadMtl(std::map &material_map, // [output] } #ifdef TINYOBJLOADER_IMPLEMENTATION -#include -#include #include +#include #include #include -#include +#include +#include #include #include @@ -267,43 +250,43 @@ 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; - } +// 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') ) +#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')) // Make index zero-base, and also support relative index. static inline int fixIndex(int idx, int n) { @@ -614,13 +597,13 @@ 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, unsigned int flags, std::string& err ) { + 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 ); + 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++) { @@ -673,31 +656,41 @@ 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(); - } + 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; @@ -720,6 +713,11 @@ void LoadMtl(std::map &material_map, std::string linebuf; safeGetline(inStream, linebuf); + // Trim trailing whitespace + if (linebuf.size() > 0) { + linebuf = linebuf.substr(0, linebuf.find_last_not_of(" \t") + 1); + } + // Trim newline '\r\n' or '\n' if (linebuf.size() > 0) { if (linebuf[linebuf.size() - 1] == '\n') @@ -1110,7 +1108,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, flags, err ); + material, name, true, flags, err); faceGroup.clear(); material = newMaterialId; } @@ -1146,7 +1144,7 @@ bool LoadObj(std::vector &shapes, // [output] // flush previous face group. bool ret = exportFaceGroupToShape(shape, vertexCache, v, vn, vt, faceGroup, tags, - material, name, true, flags, err ); + material, name, true, flags, err); if (ret) { shapes.push_back(shape); } @@ -1183,7 +1181,7 @@ bool LoadObj(std::vector &shapes, // [output] // flush previous face group. bool ret = exportFaceGroupToShape(shape, vertexCache, v, vn, vt, faceGroup, tags, - material, name, true, flags, err ); + material, name, true, flags, err); if (ret) { shapes.push_back(shape); } @@ -1239,7 +1237,8 @@ bool LoadObj(std::vector &shapes, // [output] char stringValueBuffer[4096]; #ifdef _MSC_VER - sscanf_s(token, "%s", stringValueBuffer, (unsigned)_countof(stringValueBuffer)); + sscanf_s(token, "%s", stringValueBuffer, + (unsigned)_countof(stringValueBuffer)); #else sscanf(token, "%s", stringValueBuffer); #endif @@ -1254,7 +1253,7 @@ bool LoadObj(std::vector &shapes, // [output] } bool ret = exportFaceGroupToShape(shape, vertexCache, v, vn, vt, faceGroup, - tags, material, name, true, flags, err ); + tags, material, name, true, flags, err); if (ret) { shapes.push_back(shape); } -- cgit v1.2.3 From 0a8594576713b736ba71b2813aeb6f21adf201dc Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Tue, 2 Aug 2016 17:04:00 +0900 Subject: Skip trailing whitespace in mtl. Fixes #92. --- models/issue-92.mtl | 6 ++++++ models/issue-92.obj | 7 +++++++ tests/tester.cc | 16 ++++++++++++++++ tiny_obj_loader.h | 8 ++++++-- 4 files changed, 35 insertions(+), 2 deletions(-) create mode 100644 models/issue-92.mtl create mode 100644 models/issue-92.obj diff --git a/models/issue-92.mtl b/models/issue-92.mtl new file mode 100644 index 0000000..5ebd668 --- /dev/null +++ b/models/issue-92.mtl @@ -0,0 +1,6 @@ +newmtl default +Ka 0 0 0 +Kd 0 0 0 +Ks 0 0 0 +map_Kd tmp.png + diff --git a/models/issue-92.obj b/models/issue-92.obj new file mode 100644 index 0000000..f7be3b6 --- /dev/null +++ b/models/issue-92.obj @@ -0,0 +1,7 @@ +mtllib issue-92.mtl +o Test +v 1.864151 -1.219172 -5.532511 +v 0.575869 -0.666304 5.896140 +v 0.940448 1.000000 -1.971128 +usemtl default +f 1 2 3 diff --git a/tests/tester.cc b/tests/tester.cc index dbfb939..8ca12de 100644 --- a/tests/tester.cc +++ b/tests/tester.cc @@ -351,6 +351,22 @@ TEST_CASE("stream_load", "[Stream]") { REQUIRE(true == TestStreamLoadObj()); } +TEST_CASE("trailing_whitespace_in_mtl", "[Issue92]") { + tinyobj::attrib_t attrib; + std::vector shapes; + std::vector materials; + + std::string err; + bool ret = tinyobj::LoadObj(&attrib, &shapes, &materials, &err, "../models/issue-92.obj", gMtlBasePath); + + if (!err.empty()) { + std::cerr << err << std::endl; + } + REQUIRE(true == ret); + REQUIRE(1 == materials.size()); + REQUIRE(0 == materials[0].diffuse_texname.compare("tmp.png")); +} + #if 0 int main( diff --git a/tiny_obj_loader.h b/tiny_obj_loader.h index a1f4645..0bdbdf8 100644 --- a/tiny_obj_loader.h +++ b/tiny_obj_loader.h @@ -534,8 +534,7 @@ static vertex_index parseTriple(const char **token, int vsize, int vnsize, // Parse raw triples: i, i/j/k, i//k, i/j static vertex_index parseRawTriple(const char **token) { - vertex_index vi( - static_cast(0)); // 0 is an invalid index in OBJ + vertex_index vi(static_cast(0)); // 0 is an invalid index in OBJ vi.v_idx = atoi((*token)); (*token) += strcspn((*token), "/ \t\r"); @@ -679,6 +678,11 @@ void LoadMtl(std::map *material_map, std::string linebuf(&buf[0]); + // Trim trailing whitespace. + if (linebuf.size() > 0) { + linebuf = linebuf.substr(0, linebuf.find_last_not_of(" \t") + 1); + } + // Trim newline '\r\n' or '\n' if (linebuf.size() > 0) { if (linebuf[linebuf.size() - 1] == '\n') -- cgit v1.2.3 From 8ca2bc0de7047dd6188ee68f2cee1fef449389d5 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Tue, 2 Aug 2016 17:14:46 +0900 Subject: Fix appveyor script. --- appveyor.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index 22e3d7c..4df02f3 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -7,7 +7,7 @@ install: # All external dependencies are installed in C:\projects\deps ####################################################################################### - mkdir C:\projects\deps - - cd C:\projects\deps + - pushd C:\projects\deps ####################################################################################### # Install Ninja @@ -17,6 +17,7 @@ install: - 7z x ninja.zip -oC:\projects\deps\ninja > nul - set PATH=C:\projects\deps\ninja;%PATH% - ninja --version + - popd build_script: - cd tests -- cgit v1.2.3 From c7da23795d07b054910ee053bdfe7dd89775cf8f Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Fri, 5 Aug 2016 19:16:46 +0900 Subject: Add static keyword to safeGetline(). --- tiny_obj_loader.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tiny_obj_loader.h b/tiny_obj_loader.h index d0a7886..c77ffc8 100644 --- a/tiny_obj_loader.h +++ b/tiny_obj_loader.h @@ -252,7 +252,7 @@ struct obj_shape { // See // http://stackoverflow.com/questions/6089231/getting-std-ifstream-to-handle-lf-cr-and-crlf -std::istream &safeGetline(std::istream &is, std::string &t) { +static 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. -- cgit v1.2.3 From 2ed3222bbe16a128145251aaae5478392272e013 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Fri, 12 Aug 2016 18:58:34 +0900 Subject: Compute geometric normal where vertex normal is not available. --- experimental/viewer.cc | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/experimental/viewer.cc b/experimental/viewer.cc index d4c9d29..5c9cc49 100644 --- a/experimental/viewer.cc +++ b/experimental/viewer.cc @@ -271,16 +271,21 @@ bool LoadObjAndConvert(float bmin[3], float bmax[3], const char* filename, int n int f0 = idx0.vn_idx; int f1 = idx1.vn_idx; int f2 = idx2.vn_idx; - assert(f0 >= 0); - assert(f1 >= 0); - assert(f2 >= 0); - assert(3*f0+2 < attrib.normals.size()); - assert(3*f1+2 < attrib.normals.size()); - assert(3*f2+2 < attrib.normals.size()); - for (int k = 0; k < 3; k++) { - n[0][k] = attrib.normals[3*f0+k]; - n[1][k] = attrib.normals[3*f1+k]; - n[2][k] = attrib.normals[3*f2+k]; + + if (f0 >= 0 && f1 >= 0 && f2 >= 0) { + assert(3*f0+2 < attrib.normals.size()); + assert(3*f1+2 < attrib.normals.size()); + assert(3*f2+2 < attrib.normals.size()); + for (int k = 0; k < 3; k++) { + n[0][k] = attrib.normals[3*f0+k]; + n[1][k] = attrib.normals[3*f1+k]; + n[2][k] = attrib.normals[3*f2+k]; + } + } else { + // compute geometric normal + CalcNormal(n[0], v[0], v[1], v[2]); + n[1][0] = n[0][0]; n[1][1] = n[0][1]; n[1][2] = n[0][2]; + n[2][0] = n[0][0]; n[2][1] = n[0][1]; n[2][2] = n[0][2]; } } else { // compute geometric normal -- cgit v1.2.3 From b56fc1a0cc1c86a62d2825a67156ec7f024e6649 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Fri, 12 Aug 2016 19:01:51 +0900 Subject: Fix appveyor script. --- appveyor.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index 4df02f3..9430b49 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -7,7 +7,6 @@ install: # All external dependencies are installed in C:\projects\deps ####################################################################################### - mkdir C:\projects\deps - - pushd C:\projects\deps ####################################################################################### # Install Ninja @@ -17,7 +16,6 @@ install: - 7z x ninja.zip -oC:\projects\deps\ninja > nul - set PATH=C:\projects\deps\ninja;%PATH% - ninja --version - - popd build_script: - cd tests -- cgit v1.2.3 From 673501749f89d93a1ba9ac8dc0d31d5d6cd0d955 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Fri, 12 Aug 2016 20:39:42 +0900 Subject: Apply clang-format. --- experimental/tinyobj_loader_opt.h | 73 +++++++++++++++++++-------------------- experimental/trackball.cc | 2 +- 2 files changed, 36 insertions(+), 39 deletions(-) diff --git a/experimental/tinyobj_loader_opt.h b/experimental/tinyobj_loader_opt.h index 7b92323..9fa91e1 100644 --- a/experimental/tinyobj_loader_opt.h +++ b/experimental/tinyobj_loader_opt.h @@ -432,8 +432,8 @@ static vertex_index parseRawTriple(const char **token) { } static inline bool parseString(ShortString *s, const char **token) { - skip_space(token); - size_t e = until_space((*token)); + skip_space(token); + size_t e = until_space((*token)); (*s)->insert((*s)->end(), (*token), (*token) + e); (*token) += e; return true; @@ -597,13 +597,12 @@ fail: } static inline float parseFloat(const char **token) { - skip_space(token); + skip_space(token); #ifdef TINY_OBJ_LOADER_OLD_FLOAT_PARSER float f = static_cast(atof(*token)); (*token) += strcspn((*token), " \t\r"); #else - const char *end = - (*token) + until_space((*token)); + const char *end = (*token) + until_space((*token)); double val = 0.0; tryParseDouble((*token), end, &val); float f = static_cast(val); @@ -924,30 +923,28 @@ struct CommandCount { } }; -class -LoadOption -{ +class LoadOption { public: - LoadOption() : req_num_threads(-1), triangulate(true), verbose(false) {} - - int req_num_threads; - bool triangulate; - bool verbose; + LoadOption() : req_num_threads(-1), triangulate(true), verbose(false) {} + int req_num_threads; + bool triangulate; + bool verbose; }; - /// Parse wavefront .obj(.obj string data is expanded to linear char array /// `buf') /// -1 to req_num_threads use the number of HW threads in the running system. bool parseObj(attrib_t *attrib, std::vector *shapes, const char *buf, - size_t len, const LoadOption& option); + size_t len, const LoadOption &option); #ifdef TINYOBJ_LOADER_OPT_IMPLEMENTATION static bool parseLine(Command *command, const char *p, size_t p_len, bool triangulate = true) { - // @todo { operate directly on pointer `p'. to do that, add range check for string operatoion against `p', since `p' is not null-terminated at p[p_len] } + // @todo { operate directly on pointer `p'. to do that, add range check for + // string operatoion against `p', since `p' is not null-terminated at p[p_len] + // } char linebuf[4096]; assert(p_len < 4095); memcpy(linebuf, p, p_len); @@ -1141,8 +1138,7 @@ static inline bool is_line_ending(const char *p, size_t i, size_t end_i) { } bool parseObj(attrib_t *attrib, std::vector *shapes, const char *buf, - size_t len, const LoadOption& option) -{ + size_t len, const LoadOption &option) { attrib->vertices.clear(); attrib->normals.clear(); attrib->texcoords.clear(); @@ -1153,14 +1149,15 @@ bool parseObj(attrib_t *attrib, std::vector *shapes, const char *buf, if (len < 1) return false; - auto num_threads = (option.req_num_threads < 0) ? std::thread::hardware_concurrency() - : option.req_num_threads; + auto num_threads = (option.req_num_threads < 0) + ? std::thread::hardware_concurrency() + : option.req_num_threads; num_threads = std::max(1, std::min(static_cast(num_threads), kMaxThreads)); - if (option.verbose) { - std::cout << "# of threads = " << num_threads << std::endl; - } + if (option.verbose) { + std::cout << "# of threads = " << num_threads << std::endl; + } auto t1 = std::chrono::high_resolution_clock::now(); @@ -1543,21 +1540,21 @@ bool parseObj(attrib_t *attrib, std::vector *shapes, const char *buf, } std::chrono::duration ms_total = t4 - t1; - if (option.verbose) { - std::cout << "total parsing time: " << ms_total.count() << " ms\n"; - std::cout << " line detection : " << ms_linedetection.count() << " ms\n"; - std::cout << " alloc buf : " << ms_alloc.count() << " ms\n"; - std::cout << " parse : " << ms_parse.count() << " ms\n"; - std::cout << " merge : " << ms_merge.count() << " ms\n"; - std::cout << " construct : " << ms_construct.count() << " ms\n"; - std::cout << " mtl load : " << ms_load_mtl.count() << " ms\n"; - std::cout << "# of vertices = " << attrib->vertices.size() << std::endl; - std::cout << "# of normals = " << attrib->normals.size() << std::endl; - std::cout << "# of texcoords = " << attrib->texcoords.size() << std::endl; - std::cout << "# of face indices = " << attrib->faces.size() << std::endl; - std::cout << "# of faces = " << attrib->material_ids.size() << std::endl; - std::cout << "# of shapes = " << shapes->size() << std::endl; - } + if (option.verbose) { + std::cout << "total parsing time: " << ms_total.count() << " ms\n"; + std::cout << " line detection : " << ms_linedetection.count() << " ms\n"; + std::cout << " alloc buf : " << ms_alloc.count() << " ms\n"; + std::cout << " parse : " << ms_parse.count() << " ms\n"; + std::cout << " merge : " << ms_merge.count() << " ms\n"; + std::cout << " construct : " << ms_construct.count() << " ms\n"; + std::cout << " mtl load : " << ms_load_mtl.count() << " ms\n"; + std::cout << "# of vertices = " << attrib->vertices.size() << std::endl; + std::cout << "# of normals = " << attrib->normals.size() << std::endl; + std::cout << "# of texcoords = " << attrib->texcoords.size() << std::endl; + std::cout << "# of face indices = " << attrib->faces.size() << std::endl; + std::cout << "# of faces = " << attrib->material_ids.size() << std::endl; + std::cout << "# of shapes = " << shapes->size() << std::endl; + } return true; } diff --git a/experimental/trackball.cc b/experimental/trackball.cc index 86ff3b3..27642e8 100644 --- a/experimental/trackball.cc +++ b/experimental/trackball.cc @@ -86,7 +86,7 @@ static void vsub(const float *src1, const float *src2, float *dst) { } static void vcopy(const float *v1, float *v2) { - register int i; + int i; for (i = 0; i < 3; i++) v2[i] = v1[i]; } -- cgit v1.2.3 From 07852e206d46d359a383c8b5aea9b4af986f747b Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Fri, 12 Aug 2016 23:12:46 +0900 Subject: Apply clang-format. --- examples/viewer/viewer.cc | 364 ++++++++++++++++++++++++---------------------- 1 file changed, 189 insertions(+), 175 deletions(-) diff --git a/examples/viewer/viewer.cc b/examples/viewer/viewer.cc index 6af24ba..9ebbe40 100644 --- a/examples/viewer/viewer.cc +++ b/examples/viewer/viewer.cc @@ -1,15 +1,15 @@ // // Simple .obj viewer(vertex only) // -#include -#include +#include +#include +#include #include #include #include #include -#include -#include -#include +#include +#include #include @@ -30,8 +30,8 @@ #ifdef __cplusplus extern "C" { #endif -#include #include +#include #ifdef __cplusplus } #endif @@ -45,7 +45,7 @@ extern "C" { #endif class timerutil { -public: + public: #ifdef _WIN32 typedef DWORD time_t; @@ -81,7 +81,7 @@ public: return (time_t)(t.tv_sec * 1000 + t.tv_usec); } -#else // C timer +#else // C timer // using namespace std; typedef clock_t time_t; @@ -96,7 +96,7 @@ public: #endif #endif -private: + private: #ifdef _WIN32 DWORD t_[2]; #else @@ -110,7 +110,7 @@ private: }; typedef struct { - GLuint vb; // vertex buffer + GLuint vb; // vertex buffer int numTriangles; } DrawObject; @@ -155,14 +155,15 @@ void CalcNormal(float N[3], float v0[3], float v1[3], float v2[3]) { float len2 = N[0] * N[0] + N[1] * N[1] + N[2] * N[2]; if (len2 > 0.0f) { float len = sqrtf(len2); - + N[0] /= len; N[1] /= len; } } -bool LoadObjAndConvert(float bmin[3], float bmax[3], std::vector& drawObjects, const char* filename) -{ +bool LoadObjAndConvert(float bmin[3], float bmax[3], + std::vector* drawObjects, + const char* filename) { tinyobj::attrib_t attrib; std::vector shapes; std::vector materials; @@ -170,21 +171,22 @@ bool LoadObjAndConvert(float bmin[3], float bmax[3], std::vector& dr timerutil tm; tm.start(); - + std::string err; - bool ret = tinyobj::LoadObj(&attrib, &shapes, &materials, &err, filename, NULL); + bool ret = + tinyobj::LoadObj(&attrib, &shapes, &materials, &err, filename, NULL); if (!err.empty()) { std::cerr << err << std::endl; } tm.end(); - + if (!ret) { std::cerr << "Failed to load " << filename << std::endl; return false; } - printf("Parsing time: %d [ms]\n", tm.msec()); + printf("Parsing time: %d [ms]\n", (int)tm.msec()); printf("# of vertices = %d\n", (int)(attrib.vertices.size()) / 3); printf("# of normals = %d\n", (int)(attrib.normals.size()) / 3); @@ -196,102 +198,105 @@ bool LoadObjAndConvert(float bmin[3], float bmax[3], std::vector& dr bmax[0] = bmax[1] = bmax[2] = -std::numeric_limits::max(); { - for (size_t s = 0; s < shapes.size(); s++) { - DrawObject o; - std::vector vb; // pos(3float), normal(3float), color(3float) - for (size_t f = 0; f < shapes[s].mesh.indices.size()/3; f++) { - - tinyobj::index_t idx0 = shapes[s].mesh.indices[3*f+0]; - tinyobj::index_t idx1 = shapes[s].mesh.indices[3*f+1]; - tinyobj::index_t idx2 = shapes[s].mesh.indices[3*f+2]; - - float v[3][3]; - for (int k = 0; k < 3; k++) { - int f0 = idx0.vertex_index; - int f1 = idx1.vertex_index; - int f2 = idx2.vertex_index; - assert(f0 >= 0); - assert(f1 >= 0); - assert(f2 >= 0); - - v[0][k] = attrib.vertices[3*f0+k]; - v[1][k] = attrib.vertices[3*f1+k]; - v[2][k] = attrib.vertices[3*f2+k]; - bmin[k] = std::min(v[0][k], bmin[k]); - bmin[k] = std::min(v[1][k], bmin[k]); - bmin[k] = std::min(v[2][k], bmin[k]); - bmax[k] = std::max(v[0][k], bmax[k]); - bmax[k] = std::max(v[1][k], bmax[k]); - bmax[k] = std::max(v[2][k], bmax[k]); - } + for (size_t s = 0; s < shapes.size(); s++) { + DrawObject o; + std::vector vb; // pos(3float), normal(3float), color(3float) + for (size_t f = 0; f < shapes[s].mesh.indices.size() / 3; f++) { + tinyobj::index_t idx0 = shapes[s].mesh.indices[3 * f + 0]; + tinyobj::index_t idx1 = shapes[s].mesh.indices[3 * f + 1]; + tinyobj::index_t idx2 = shapes[s].mesh.indices[3 * f + 2]; + + float v[3][3]; + for (int k = 0; k < 3; k++) { + int f0 = idx0.vertex_index; + int f1 = idx1.vertex_index; + int f2 = idx2.vertex_index; + assert(f0 >= 0); + assert(f1 >= 0); + assert(f2 >= 0); + + v[0][k] = attrib.vertices[3 * f0 + k]; + v[1][k] = attrib.vertices[3 * f1 + k]; + v[2][k] = attrib.vertices[3 * f2 + k]; + bmin[k] = std::min(v[0][k], bmin[k]); + bmin[k] = std::min(v[1][k], bmin[k]); + bmin[k] = std::min(v[2][k], bmin[k]); + bmax[k] = std::max(v[0][k], bmax[k]); + bmax[k] = std::max(v[1][k], bmax[k]); + bmax[k] = std::max(v[2][k], bmax[k]); + } - float n[3][3]; - - if (attrib.normals.size() > 0) { - int f0 = idx0.normal_index; - int f1 = idx1.normal_index; - int f2 = idx2.normal_index; - assert(f0 >= 0); - assert(f1 >= 0); - assert(f2 >= 0); - for (int k = 0; k < 3; k++) { - n[0][k] = attrib.normals[3*f0+k]; - n[1][k] = attrib.normals[3*f1+k]; - n[2][k] = attrib.normals[3*f2+k]; - } - } else { - // compute geometric normal - CalcNormal(n[0], v[0], v[1], v[2]); - n[1][0] = n[0][0]; n[1][1] = n[0][1]; n[1][2] = n[0][2]; - n[2][0] = n[0][0]; n[2][1] = n[0][1]; n[2][2] = n[0][2]; - } + float n[3][3]; + if (attrib.normals.size() > 0) { + int f0 = idx0.normal_index; + int f1 = idx1.normal_index; + int f2 = idx2.normal_index; + assert(f0 >= 0); + assert(f1 >= 0); + assert(f2 >= 0); for (int k = 0; k < 3; k++) { - vb.push_back(v[k][0]); - vb.push_back(v[k][1]); - vb.push_back(v[k][2]); - vb.push_back(n[k][0]); - vb.push_back(n[k][1]); - vb.push_back(n[k][2]); - // Use normal as color. - float c[3] = {n[k][0], n[k][1], n[k][2]}; - float len2 = c[0] * c[0] + c[1] * c[1] + c[2] * c[2]; - if (len2 > 0.0f) { - float len = sqrtf(len2); - - c[0] /= len; - c[1] /= len; - c[2] /= len; - } - vb.push_back(c[0] * 0.5 + 0.5); - vb.push_back(c[1] * 0.5 + 0.5); - vb.push_back(c[2] * 0.5 + 0.5); + n[0][k] = attrib.normals[3 * f0 + k]; + n[1][k] = attrib.normals[3 * f1 + k]; + n[2][k] = attrib.normals[3 * f2 + k]; } - + } else { + // compute geometric normal + CalcNormal(n[0], v[0], v[1], v[2]); + n[1][0] = n[0][0]; + n[1][1] = n[0][1]; + n[1][2] = n[0][2]; + n[2][0] = n[0][0]; + n[2][1] = n[0][1]; + n[2][2] = n[0][2]; } - o.vb = 0; - o.numTriangles = 0; - if (vb.size() > 0) { - glGenBuffers(1, &o.vb); - glBindBuffer(GL_ARRAY_BUFFER, o.vb); - glBufferData(GL_ARRAY_BUFFER, vb.size() * sizeof(float), &vb.at(0), GL_STATIC_DRAW); - o.numTriangles = vb.size() / 9 / 3; - printf("shape[%d] # of triangles = %d\n", static_cast(s), o.numTriangles); + for (int k = 0; k < 3; k++) { + vb.push_back(v[k][0]); + vb.push_back(v[k][1]); + vb.push_back(v[k][2]); + vb.push_back(n[k][0]); + vb.push_back(n[k][1]); + vb.push_back(n[k][2]); + // Use normal as color. + float c[3] = {n[k][0], n[k][1], n[k][2]}; + float len2 = c[0] * c[0] + c[1] * c[1] + c[2] * c[2]; + if (len2 > 0.0f) { + float len = sqrtf(len2); + + c[0] /= len; + c[1] /= len; + c[2] /= len; + } + vb.push_back(c[0] * 0.5 + 0.5); + vb.push_back(c[1] * 0.5 + 0.5); + vb.push_back(c[2] * 0.5 + 0.5); } - - gDrawObjects.push_back(o); + } + + o.vb = 0; + o.numTriangles = 0; + if (vb.size() > 0) { + glGenBuffers(1, &o.vb); + glBindBuffer(GL_ARRAY_BUFFER, o.vb); + glBufferData(GL_ARRAY_BUFFER, vb.size() * sizeof(float), &vb.at(0), + GL_STATIC_DRAW); + o.numTriangles = vb.size() / 9 / 3; + printf("shape[%d] # of triangles = %d\n", static_cast(s), + o.numTriangles); + } + + drawObjects->push_back(o); } } - + printf("bmin = %f, %f, %f\n", bmin[0], bmin[1], bmin[2]); printf("bmax = %f, %f, %f\n", bmax[0], bmax[1], bmax[2]); return true; } -void reshapeFunc(GLFWwindow* window, int w, int h) -{ +void reshapeFunc(GLFWwindow* window, int w, int h) { int fb_w, fb_h; // Get actual framebuffer size. glfwGetFramebufferSize(window, &fb_w, &fb_h); @@ -307,78 +312,90 @@ void reshapeFunc(GLFWwindow* window, int w, int h) height = h; } -void keyboardFunc(GLFWwindow *window, int key, int scancode, int action, int mods) { - if(action == GLFW_PRESS || action == GLFW_REPEAT){ - // Move camera - float mv_x = 0, mv_y = 0, mv_z = 0; - if(key == GLFW_KEY_K) mv_x += 1; - else if(key == GLFW_KEY_J) mv_x += -1; - else if(key == GLFW_KEY_L) mv_y += 1; - else if(key == GLFW_KEY_H) mv_y += -1; - else if(key == GLFW_KEY_P) mv_z += 1; - else if(key == GLFW_KEY_N) mv_z += -1; - //camera.move(mv_x * 0.05, mv_y * 0.05, mv_z * 0.05); - // Close window - if(key == GLFW_KEY_Q || key == GLFW_KEY_ESCAPE) glfwSetWindowShouldClose(window, GL_TRUE); - - //init_frame = true; - } +void keyboardFunc(GLFWwindow* window, int key, int scancode, int action, + int mods) { + (void)window; + (void)scancode; + (void)mods; + if (action == GLFW_PRESS || action == GLFW_REPEAT) { + // Move camera + float mv_x = 0, mv_y = 0, mv_z = 0; + if (key == GLFW_KEY_K) + mv_x += 1; + else if (key == GLFW_KEY_J) + mv_x += -1; + else if (key == GLFW_KEY_L) + mv_y += 1; + else if (key == GLFW_KEY_H) + mv_y += -1; + else if (key == GLFW_KEY_P) + mv_z += 1; + else if (key == GLFW_KEY_N) + mv_z += -1; + // camera.move(mv_x * 0.05, mv_y * 0.05, mv_z * 0.05); + // Close window + if (key == GLFW_KEY_Q || key == GLFW_KEY_ESCAPE) + glfwSetWindowShouldClose(window, GL_TRUE); + + // init_frame = true; + } } -void clickFunc(GLFWwindow* window, int button, int action, int mods){ - if(button == GLFW_MOUSE_BUTTON_LEFT){ - if(action == GLFW_PRESS){ - mouseLeftPressed = true; - trackball(prev_quat, 0.0, 0.0, 0.0, 0.0); - } else if(action == GLFW_RELEASE){ - mouseLeftPressed = false; - } +void clickFunc(GLFWwindow* window, int button, int action, int mods) { + (void)window; + (void)mods; + if (button == GLFW_MOUSE_BUTTON_LEFT) { + if (action == GLFW_PRESS) { + mouseLeftPressed = true; + trackball(prev_quat, 0.0, 0.0, 0.0, 0.0); + } else if (action == GLFW_RELEASE) { + mouseLeftPressed = false; } - if(button == GLFW_MOUSE_BUTTON_RIGHT){ - if(action == GLFW_PRESS){ - mouseRightPressed = true; - } else if(action == GLFW_RELEASE){ - mouseRightPressed = false; - } + } + if (button == GLFW_MOUSE_BUTTON_RIGHT) { + if (action == GLFW_PRESS) { + mouseRightPressed = true; + } else if (action == GLFW_RELEASE) { + mouseRightPressed = false; } - if(button == GLFW_MOUSE_BUTTON_MIDDLE){ - if(action == GLFW_PRESS){ - mouseMiddlePressed = true; - } else if(action == GLFW_RELEASE){ - mouseMiddlePressed = false; - } + } + if (button == GLFW_MOUSE_BUTTON_MIDDLE) { + if (action == GLFW_PRESS) { + mouseMiddlePressed = true; + } else if (action == GLFW_RELEASE) { + mouseMiddlePressed = false; } + } } -void motionFunc(GLFWwindow* window, double mouse_x, double mouse_y){ +void motionFunc(GLFWwindow* window, double mouse_x, double mouse_y) { + (void)window; float rotScale = 1.0f; float transScale = 2.0f; - if(mouseLeftPressed){ - trackball(prev_quat, - rotScale * (2.0f * prevMouseX - width) / (float)width, - rotScale * (height - 2.0f * prevMouseY) / (float)height, - rotScale * (2.0f * mouse_x - width) / (float)width, - rotScale * (height - 2.0f * mouse_y) / (float)height); - - add_quats(prev_quat, curr_quat, curr_quat); - } else if (mouseMiddlePressed) { - eye[0] -= transScale * (mouse_x - prevMouseX) / (float)width; - lookat[0] -= transScale * (mouse_x - prevMouseX) / (float)width; - eye[1] += transScale * (mouse_y - prevMouseY) / (float)height; - lookat[1] += transScale * (mouse_y - prevMouseY) / (float)height; - } else if (mouseRightPressed) { - eye[2] += transScale * (mouse_y - prevMouseY) / (float)height; - lookat[2] += transScale * (mouse_y - prevMouseY) / (float)height; - } + if (mouseLeftPressed) { + trackball(prev_quat, rotScale * (2.0f * prevMouseX - width) / (float)width, + rotScale * (height - 2.0f * prevMouseY) / (float)height, + rotScale * (2.0f * mouse_x - width) / (float)width, + rotScale * (height - 2.0f * mouse_y) / (float)height); + + add_quats(prev_quat, curr_quat, curr_quat); + } else if (mouseMiddlePressed) { + eye[0] -= transScale * (mouse_x - prevMouseX) / (float)width; + lookat[0] -= transScale * (mouse_x - prevMouseX) / (float)width; + eye[1] += transScale * (mouse_y - prevMouseY) / (float)height; + lookat[1] += transScale * (mouse_y - prevMouseY) / (float)height; + } else if (mouseRightPressed) { + eye[2] += transScale * (mouse_y - prevMouseY) / (float)height; + lookat[2] += transScale * (mouse_y - prevMouseY) / (float)height; + } - // Update mouse point - prevMouseX = mouse_x; - prevMouseY = mouse_y; + // Update mouse point + prevMouseX = mouse_x; + prevMouseY = mouse_y; } -void Draw(const std::vector& drawObjects) -{ +void Draw(const std::vector& drawObjects) { glPolygonMode(GL_FRONT, GL_FILL); glPolygonMode(GL_BACK, GL_FILL); @@ -390,14 +407,14 @@ void Draw(const std::vector& drawObjects) if (o.vb < 1) { continue; } - + glBindBuffer(GL_ARRAY_BUFFER, o.vb); glEnableClientState(GL_VERTEX_ARRAY); glEnableClientState(GL_NORMAL_ARRAY); glEnableClientState(GL_COLOR_ARRAY); glVertexPointer(3, GL_FLOAT, 36, (const void*)0); - glNormalPointer(GL_FLOAT, 36, (const void*)(sizeof(float)*3)); - glColorPointer(3, GL_FLOAT, 36, (const void*)(sizeof(float)*6)); + glNormalPointer(GL_FLOAT, 36, (const void*)(sizeof(float) * 3)); + glColorPointer(3, GL_FLOAT, 36, (const void*)(sizeof(float) * 6)); glDrawArrays(GL_TRIANGLES, 0, 3 * o.numTriangles); CheckErrors("drawarrays"); @@ -414,13 +431,13 @@ void Draw(const std::vector& drawObjects) if (o.vb < 1) { continue; } - + glBindBuffer(GL_ARRAY_BUFFER, o.vb); glEnableClientState(GL_VERTEX_ARRAY); glEnableClientState(GL_NORMAL_ARRAY); glDisableClientState(GL_COLOR_ARRAY); glVertexPointer(3, GL_FLOAT, 36, (const void*)0); - glNormalPointer(GL_FLOAT, 36, (const void*)(sizeof(float)*3)); + glNormalPointer(GL_FLOAT, 36, (const void*)(sizeof(float) * 3)); glDrawArrays(GL_TRIANGLES, 0, 3 * o.numTriangles); CheckErrors("drawarrays"); @@ -443,26 +460,21 @@ static void Init() { up[2] = 0.0f; } - -int main(int argc, char **argv) -{ +int main(int argc, char** argv) { if (argc < 2) { std::cout << "Needs input.obj\n" << std::endl; return 0; } Init(); - - if(!glfwInit()){ + if (!glfwInit()) { std::cerr << "Failed to initialize GLFW." << std::endl; return -1; } - - window = glfwCreateWindow(width, height, "Obj viewer", NULL, NULL); - if(window == NULL){ + if (window == NULL) { std::cerr << "Failed to open GLFW window. " << std::endl; glfwTerminate(); return 1; @@ -486,7 +498,7 @@ int main(int argc, char **argv) reshapeFunc(window, width, height); float bmin[3], bmax[3]; - if (false == LoadObjAndConvert(bmin, bmax, gDrawObjects, argv[1])) { + if (false == LoadObjAndConvert(bmin, bmax, &gDrawObjects, argv[1])) { return -1; } @@ -498,7 +510,7 @@ int main(int argc, char **argv) maxExtent = 0.5f * (bmax[2] - bmin[2]); } - while(glfwWindowShouldClose(window) == GL_FALSE) { + while (glfwWindowShouldClose(window) == GL_FALSE) { glfwPollEvents(); glClearColor(0.1f, 0.2f, 0.3f, 1.0f); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); @@ -509,7 +521,8 @@ int main(int argc, char **argv) glMatrixMode(GL_MODELVIEW); glLoadIdentity(); GLfloat mat[4][4]; - gluLookAt(eye[0], eye[1], eye[2], lookat[0], lookat[1], lookat[2], up[0], up[1], up[2]); + gluLookAt(eye[0], eye[1], eye[2], lookat[0], lookat[1], lookat[2], up[0], + up[1], up[2]); build_rotmatrix(mat, curr_quat); glMultMatrixf(&mat[0][0]); @@ -517,8 +530,9 @@ int main(int argc, char **argv) glScalef(1.0f / maxExtent, 1.0f / maxExtent, 1.0f / maxExtent); // Centerize object. - glTranslatef(-0.5*(bmax[0] + bmin[0]), -0.5*(bmax[1] + bmin[1]), -0.5*(bmax[2] + bmin[2])); - + glTranslatef(-0.5 * (bmax[0] + bmin[0]), -0.5 * (bmax[1] + bmin[1]), + -0.5 * (bmax[2] + bmin[2])); + Draw(gDrawObjects); glfwSwapBuffers(window); -- cgit v1.2.3 From 42f04024d43dbea0f51df53c6cf054142d0e230e Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Fri, 12 Aug 2016 23:13:05 +0900 Subject: Remove old version log. --- tiny_obj_loader.h | 25 ------------------------- 1 file changed, 25 deletions(-) diff --git a/tiny_obj_loader.h b/tiny_obj_loader.h index 0bdbdf8..34375e9 100644 --- a/tiny_obj_loader.h +++ b/tiny_obj_loader.h @@ -24,31 +24,6 @@ THE SOFTWARE. // // version 1.0.0 : Change data structure. Change license from BSD to MIT. -// 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 -// version 0.9.15: Change API to handle no mtl file case correctly(#58) -// version 0.9.14: Support specular highlight, bump, displacement and alpha -// map(#53) -// 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.11: Invert `Tr` parameter(#43) -// version 0.9.10: Fix seg fault on windows. -// version 0.9.9 : Replace atof() with custom parser. -// version 0.9.8 : Fix multi-materials(per-face material ID). -// version 0.9.7 : Support multi-materials(per-face material ID) per -// object/group. -// 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. -// version 0.9.4 : Initial support of group tag(g) -// version 0.9.3 : Fix parsing triple 'x/y/z' -// version 0.9.2 : Add more .mtl load support -// version 0.9.1 : Add initial .mtl load support -// version 0.9.0 : Initial // // -- cgit v1.2.3 From 5a832b470a0899e1d6b79d6dc8005dbd3ca5e217 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Fri, 12 Aug 2016 23:13:31 +0900 Subject: Update README. --- README.md | 58 ++++++++++++++++++++++------------------------------------ 1 file changed, 22 insertions(+), 36 deletions(-) diff --git a/README.md b/README.md index 90fa5b2..3cfbbbb 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,4 @@ -tinyobjloader -============= +# tinyobjloader [![Join the chat at https://gitter.im/syoyo/tinyobjloader](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/syoyo/tinyobjloader?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) @@ -18,27 +17,16 @@ Tiny but powerful single file wavefront obj loader written in C++. No dependency `tinyobjloader` is good for embedding .obj loader to your (global illumination) renderer ;-) -What's new ----------- +## 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. -* Jun 23, 2015 : Various fixes and added more projects using tinyobjloader. Thanks many contributors! -* Mar 03, 2015 : Replace atof() with hand-written parser for robust reading of numeric value. Thanks skurmedel! -* Feb 06, 2015 : Fix parsing multi-material object -* Sep 14, 2014 : Add support for multi-material per object/group. Thanks Mykhailo! -* Mar 17, 2014 : Fixed trim newline bugs. Thanks ardneran! -* Apr 29, 2014 : Add API to read .obj from std::istream. Good for reading compressed .obj or connecting to procedural primitive generator. Thanks burnse! -* Apr 21, 2014 : Define default material if no material definition exists in .obj. Thanks YarmUI! -* Apr 10, 2014 : Add support for parsing 'illum' and 'd'/'Tr' statements. Thanks mmp! -* Jan 27, 2014 : Added CMake project. Thanks bradc6! -* Nov 26, 2013 : Performance optimization by NeuralSandwich. 9% improvement in his project, thanks! -* Sep 12, 2013 : Added multiple .obj sticher example. - -Example -------- + +### Old version + +Previous old version is avaiable as `v0.9` branch. + + +## Example ![Rungholt](images/rungholt.jpg) @@ -49,11 +37,16 @@ http://graphics.cs.williams.edu/data/meshes.xml * [examples/viewer/](examples/viewer) OpenGL .obj viewer -Use case --------- +## Use case TinyObjLoader is successfully used in ... +### New version + +* Your project here! + +### Old version + * bullet3 https://github.com/erwincoumans/bullet3 * pbrt-v2 https://github.com/mmp/pbrt-v2 * OpenGL game engine development http://swarminglogic.com/jotting/2013_10_gamedev01 @@ -66,10 +59,8 @@ TinyObjLoader is successfully used in ... * cocos2d-x https://github.com/cocos2d/cocos2d-x/ * Android Vulkan demo https://github.com/SaschaWillems/Vulkan * Loading models in Vulkan Tutorial https://vulkan-tutorial.com/Loading_models -* Your project here! -Features --------- +## Features * Group(parse multiple group name) * Vertex @@ -81,20 +72,17 @@ Features * Callback API for custom loading. -TODO ----- +## TODO * [ ] Fix Python binding. * [ ] Fix obj_sticker example. * [ ] More unit test codes. -License -------- +## License Licensed under MIT license. -Usage ------ +## Usage `attrib_t` contains single and linear array of vertex data(position, normal and texcoord). Each `shape_t` does not contain vertex data but contains array index to `attrib_t`. @@ -149,8 +137,7 @@ for (size_t s = 0; s < shapes.size(); s++) { ``` -Optimized loader ----------------- +## Optimized loader Optimized multi-threaded .obj loader is available at `experimental/` directory. If you want absolute performance to load .obj data, this optimized loader will fit your purpose. @@ -164,7 +151,6 @@ Here is some benchmark result. Time are measured on MacBook 12(Early 2016, Core * optimised: 1500 msecs(10x faster than old version, 4.5x faster than basedline) -Tests ------ +## Tests Unit tests are provided in `tests` directory. See `tests/README.md` for details. -- cgit v1.2.3 From 2cb73fa85d8a8cfba3b858c2f4fc32c8835bee2a Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Sat, 13 Aug 2016 02:52:36 +0900 Subject: Fix the ordefing of a constructor call of `vertex_index`. --- experimental/tinyobj_loader_opt.h | 19 ++++++++++--------- experimental/viewer.cc | 27 +++++++++++++++------------ 2 files changed, 25 insertions(+), 21 deletions(-) diff --git a/experimental/tinyobj_loader_opt.h b/experimental/tinyobj_loader_opt.h index 9fa91e1..2088a6e 100644 --- a/experimental/tinyobj_loader_opt.h +++ b/experimental/tinyobj_loader_opt.h @@ -969,7 +969,7 @@ static bool parseLine(Command *command, const char *p, size_t p_len, // vertex if (token[0] == 'v' && IS_SPACE((token[1]))) { token += 2; - float x, y, z; + float x = 0.0f, y = 0.0f, z = 0.0f; parseFloat3(&x, &y, &z, &token); command->vx = x; command->vy = y; @@ -981,7 +981,7 @@ static bool parseLine(Command *command, const char *p, size_t p_len, // normal if (token[0] == 'v' && token[1] == 'n' && IS_SPACE((token[2]))) { token += 3; - float x, y, z; + float x = 0.0f, y = 0.0f, z = 0.0f; parseFloat3(&x, &y, &z, &token); command->nx = x; command->ny = y; @@ -993,7 +993,7 @@ static bool parseLine(Command *command, const char *p, size_t p_len, // texcoord if (token[0] == 'v' && token[1] == 't' && IS_SPACE((token[2]))) { token += 3; - float x, y; + float x = 0.0f, y = 0.0f; parseFloat2(&x, &y, &token); command->tx = x; command->ty = y; @@ -1360,10 +1360,11 @@ bool parseObj(attrib_t *attrib, std::vector *shapes, const char *buf, num_f += command_count[t].num_f; num_faces += command_count[t].num_faces; } - // std::cout << "# v " << num_v << std::endl; - // std::cout << "# vn " << num_vn << std::endl; - // std::cout << "# vt " << num_vt << std::endl; - // std::cout << "# f " << num_f << std::endl; + + //std::cout << "# v " << num_v << std::endl; + //std::cout << "# vn " << num_vn << std::endl; + //std::cout << "# vt " << num_vt << std::endl; + //std::cout << "# f " << num_f << std::endl; // 4. merge // @todo { parallelize merge. } @@ -1442,9 +1443,9 @@ bool parseObj(attrib_t *attrib, std::vector *shapes, const char *buf, for (size_t k = 0; k < commands[t][i].f.size(); k++) { vertex_index &vi = commands[t][i].f[k]; int v_idx = fixIndex(vi.v_idx, v_count); - int vn_idx = fixIndex(vi.vn_idx, n_count); int vt_idx = fixIndex(vi.vt_idx, t_count); - attrib->faces[f_count + k] = vertex_index(v_idx, vn_idx, vt_idx); + int vn_idx = fixIndex(vi.vn_idx, n_count); + attrib->faces[f_count + k] = vertex_index(v_idx, vt_idx, vn_idx); } attrib->material_ids[face_count] = material_id; attrib->face_num_verts[face_count] = commands[t][i].f.size(); diff --git a/experimental/viewer.cc b/experimental/viewer.cc index 5c9cc49..e64f31d 100644 --- a/experimental/viewer.cc +++ b/experimental/viewer.cc @@ -234,6 +234,9 @@ bool LoadObjAndConvert(float bmin[3], float bmax[3], const char* filename, int n bmin[0] = bmin[1] = bmin[2] = std::numeric_limits::max(); bmax[0] = bmax[1] = bmax[2] = -std::numeric_limits::max(); + //std::cout << "vertices.size() = " << attrib.vertices.size() << std::endl; + //std::cout << "normals.size() = " << attrib.normals.size() << std::endl; + { DrawObject o; std::vector vb; // pos(3float), normal(3float), color(3float) @@ -268,18 +271,18 @@ bool LoadObjAndConvert(float bmin[3], float bmax[3], const char* filename, int n float n[3][3]; if (attrib.normals.size() > 0) { - int f0 = idx0.vn_idx; - int f1 = idx1.vn_idx; - int f2 = idx2.vn_idx; - - if (f0 >= 0 && f1 >= 0 && f2 >= 0) { - assert(3*f0+2 < attrib.normals.size()); - assert(3*f1+2 < attrib.normals.size()); - assert(3*f2+2 < attrib.normals.size()); + int nf0 = idx0.vn_idx; + int nf1 = idx1.vn_idx; + int nf2 = idx2.vn_idx; + + if (nf0 >= 0 && nf1 >= 0 && nf2 >= 0) { + assert(3*nf0+2 < attrib.normals.size()); + assert(3*nf1+2 < attrib.normals.size()); + assert(3*nf2+2 < attrib.normals.size()); for (int k = 0; k < 3; k++) { - n[0][k] = attrib.normals[3*f0+k]; - n[1][k] = attrib.normals[3*f1+k]; - n[2][k] = attrib.normals[3*f2+k]; + n[0][k] = attrib.normals[3*nf0+k]; + n[1][k] = attrib.normals[3*nf1+k]; + n[2][k] = attrib.normals[3*nf2+k]; } } else { // compute geometric normal @@ -304,7 +307,7 @@ bool LoadObjAndConvert(float bmin[3], float bmax[3], const char* filename, int n // Use normal as color. float c[3] = {n[k][0], n[k][1], n[k][2]}; float len2 = c[0] * c[0] + c[1] * c[1] + c[2] * c[2]; - if (len2 > 0.0f) { + if (len2 > 1.0e-6f) { float len = sqrtf(len2); c[0] /= len; -- cgit v1.2.3 From 319746d3db5483490b3ea5b8cda9854033c7c84e Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Sat, 13 Aug 2016 15:09:58 +0900 Subject: Fix parsing benchmark flag. --- experimental/viewer.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/experimental/viewer.cc b/experimental/viewer.cc index e64f31d..dc62e2c 100644 --- a/experimental/viewer.cc +++ b/experimental/viewer.cc @@ -517,7 +517,7 @@ int main(int argc, char **argv) } if (argc > 3) { - benchmark_only = true; + benchmark_only = (atoi(argv[3]) > 0) ? true : false; } if (argc > 4) { -- cgit v1.2.3 From 110b49a0c899031d54c054ee4d9ce32b9f0e2939 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Sat, 13 Aug 2016 15:27:35 +0900 Subject: Rename vertex_index struct. --- experimental/tinyobj_loader_opt.h | 74 +++++++++++++++++++-------------------- experimental/viewer.cc | 18 +++++----- 2 files changed, 46 insertions(+), 46 deletions(-) diff --git a/experimental/tinyobj_loader_opt.h b/experimental/tinyobj_loader_opt.h index 2088a6e..8c745d3 100644 --- a/experimental/tinyobj_loader_opt.h +++ b/experimental/tinyobj_loader_opt.h @@ -301,19 +301,19 @@ typedef struct { unsigned int length; } shape_t; -struct vertex_index { - int v_idx, vt_idx, vn_idx; - vertex_index() : v_idx(-1), vt_idx(-1), vn_idx(-1) {} - explicit 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) {} +struct index_t { + int vertex_index, texcoord_index, normal_index; + index_t() : vertex_index(-1), texcoord_index(-1), normal_index(-1) {} + explicit index_t(int idx) : vertex_index(idx), texcoord_index(idx), normal_index(idx) {} + index_t(int vidx, int vtidx, int vnidx) + : vertex_index(vidx), texcoord_index(vtidx), normal_index(vnidx) {} }; typedef struct { std::vector > vertices; std::vector > normals; std::vector > texcoords; - std::vector > faces; + std::vector > indices; std::vector > face_num_verts; std::vector > material_ids; } attrib_t; @@ -386,11 +386,11 @@ static inline int fixIndex(int idx, int n) { } // Parse raw triples: i, i/j/k, i//k, i/j -static vertex_index parseRawTriple(const char **token) { - vertex_index vi( +static index_t parseRawTriple(const char **token) { + index_t vi( static_cast(0x80000000)); // 0x80000000 = -2147483648 = invalid - vi.v_idx = my_atoi((*token)); + vi.vertex_index = my_atoi((*token)); while ((*token)[0] != '\0' && (*token)[0] != '/' && (*token)[0] != ' ' && (*token)[0] != '\t' && (*token)[0] != '\r') { (*token)++; @@ -403,7 +403,7 @@ static vertex_index parseRawTriple(const char **token) { // i//k if ((*token)[0] == '/') { (*token)++; - vi.vn_idx = my_atoi((*token)); + vi.normal_index = my_atoi((*token)); while ((*token)[0] != '\0' && (*token)[0] != '/' && (*token)[0] != ' ' && (*token)[0] != '\t' && (*token)[0] != '\r') { (*token)++; @@ -412,7 +412,7 @@ static vertex_index parseRawTriple(const char **token) { } // i/j/k or i/j - vi.vt_idx = my_atoi((*token)); + vi.texcoord_index = my_atoi((*token)); while ((*token)[0] != '\0' && (*token)[0] != '/' && (*token)[0] != ' ' && (*token)[0] != '\t' && (*token)[0] != '\r') { (*token)++; @@ -423,7 +423,7 @@ static vertex_index parseRawTriple(const char **token) { // i/j/k (*token)++; // skip '/' - vi.vn_idx = my_atoi((*token)); + vi.normal_index = my_atoi((*token)); while ((*token)[0] != '\0' && (*token)[0] != '/' && (*token)[0] != ' ' && (*token)[0] != '\t' && (*token)[0] != '\r') { (*token)++; @@ -891,8 +891,8 @@ typedef struct { float tx, ty; // for f - std::vector > f; - // std::vector f; + std::vector > f; + // std::vector f; std::vector > f_num_verts; const char *group_name; @@ -913,13 +913,13 @@ struct CommandCount { size_t num_vn; size_t num_vt; size_t num_f; - size_t num_faces; + size_t num_indices; CommandCount() { num_v = 0; num_vn = 0; num_vt = 0; num_f = 0; - num_faces = 0; + num_indices = 0; } }; @@ -1006,10 +1006,10 @@ static bool parseLine(Command *command, const char *p, size_t p_len, token += 2; skip_space(&token); - StackVector f; + StackVector f; while (!IS_NEW_LINE(token[0])) { - vertex_index vi = parseRawTriple(&token); + index_t vi = parseRawTriple(&token); skip_space_and_cr(&token); f->push_back(vi); @@ -1018,9 +1018,9 @@ static bool parseLine(Command *command, const char *p, size_t p_len, command->type = COMMAND_F; if (triangulate) { - vertex_index i0 = f[0]; - vertex_index i1(-1); - vertex_index i2 = f[1]; + index_t i0 = f[0]; + index_t i1(-1); + index_t i2 = f[1]; for (size_t k = 2; k < f->size(); k++) { i1 = i2; @@ -1142,7 +1142,7 @@ bool parseObj(attrib_t *attrib, std::vector *shapes, const char *buf, attrib->vertices.clear(); attrib->normals.clear(); attrib->texcoords.clear(); - attrib->faces.clear(); + attrib->indices.clear(); attrib->face_num_verts.clear(); attrib->material_ids.clear(); shapes->clear(); @@ -1288,7 +1288,7 @@ bool parseObj(attrib_t *attrib, std::vector *shapes, const char *buf, command_count[t].num_vt++; } else if (command.type == COMMAND_F) { command_count[t].num_f += command.f.size(); - command_count[t].num_faces++; + command_count[t].num_indices++; } if (command.type == COMMAND_MTLLIB) { @@ -1352,13 +1352,13 @@ bool parseObj(attrib_t *attrib, std::vector *shapes, const char *buf, size_t num_vn = 0; size_t num_vt = 0; size_t num_f = 0; - size_t num_faces = 0; + size_t num_indices = 0; for (size_t t = 0; t < num_threads; t++) { num_v += command_count[t].num_v; num_vn += command_count[t].num_vn; num_vt += command_count[t].num_vt; num_f += command_count[t].num_f; - num_faces += command_count[t].num_faces; + num_indices += command_count[t].num_indices; } //std::cout << "# v " << num_v << std::endl; @@ -1374,9 +1374,9 @@ bool parseObj(attrib_t *attrib, std::vector *shapes, const char *buf, attrib->vertices.resize(num_v * 3); attrib->normals.resize(num_vn * 3); attrib->texcoords.resize(num_vt * 2); - attrib->faces.resize(num_f); - attrib->face_num_verts.resize(num_faces); - attrib->material_ids.resize(num_faces); + attrib->indices.resize(num_f); + attrib->face_num_verts.resize(num_indices); + attrib->material_ids.resize(num_indices); size_t v_offsets[kMaxThreads]; size_t n_offsets[kMaxThreads]; @@ -1395,7 +1395,7 @@ bool parseObj(attrib_t *attrib, std::vector *shapes, const char *buf, n_offsets[t] = n_offsets[t - 1] + command_count[t - 1].num_vn; t_offsets[t] = t_offsets[t - 1] + command_count[t - 1].num_vt; f_offsets[t] = f_offsets[t - 1] + command_count[t - 1].num_f; - face_offsets[t] = face_offsets[t - 1] + command_count[t - 1].num_faces; + face_offsets[t] = face_offsets[t - 1] + command_count[t - 1].num_indices; } StackVector workers; @@ -1441,11 +1441,11 @@ bool parseObj(attrib_t *attrib, std::vector *shapes, const char *buf, t_count++; } else if (commands[t][i].type == COMMAND_F) { for (size_t k = 0; k < commands[t][i].f.size(); k++) { - vertex_index &vi = commands[t][i].f[k]; - int v_idx = fixIndex(vi.v_idx, v_count); - int vt_idx = fixIndex(vi.vt_idx, t_count); - int vn_idx = fixIndex(vi.vn_idx, n_count); - attrib->faces[f_count + k] = vertex_index(v_idx, vt_idx, vn_idx); + index_t &vi = commands[t][i].f[k]; + int vertex_index = fixIndex(vi.vertex_index, v_count); + int texcoord_index = fixIndex(vi.texcoord_index, t_count); + int normal_index = fixIndex(vi.normal_index, n_count); + attrib->indices[f_count + k] = index_t(vertex_index, texcoord_index, normal_index); } attrib->material_ids[face_count] = material_id; attrib->face_num_verts[face_count] = commands[t][i].f.size(); @@ -1552,8 +1552,8 @@ bool parseObj(attrib_t *attrib, std::vector *shapes, const char *buf, std::cout << "# of vertices = " << attrib->vertices.size() << std::endl; std::cout << "# of normals = " << attrib->normals.size() << std::endl; std::cout << "# of texcoords = " << attrib->texcoords.size() << std::endl; - std::cout << "# of face indices = " << attrib->faces.size() << std::endl; - std::cout << "# of faces = " << attrib->material_ids.size() << std::endl; + std::cout << "# of face indices = " << attrib->indices.size() << std::endl; + std::cout << "# of indices = " << attrib->material_ids.size() << std::endl; std::cout << "# of shapes = " << shapes->size() << std::endl; } diff --git a/experimental/viewer.cc b/experimental/viewer.cc index dc62e2c..f4ab5ed 100644 --- a/experimental/viewer.cc +++ b/experimental/viewer.cc @@ -244,15 +244,15 @@ bool LoadObjAndConvert(float bmin[3], float bmax[3], const char* filename, int n for (size_t v = 0; v < attrib.face_num_verts.size(); v++) { assert(attrib.face_num_verts[v] % 3 == 0); // assume all triangle face. for (size_t f = 0; f < attrib.face_num_verts[v] / 3; f++) { - tinyobj_opt::vertex_index idx0 = attrib.faces[face_offset+3*f+0]; - tinyobj_opt::vertex_index idx1 = attrib.faces[face_offset+3*f+1]; - tinyobj_opt::vertex_index idx2 = attrib.faces[face_offset+3*f+2]; + tinyobj_opt::index_t idx0 = attrib.indices[face_offset+3*f+0]; + tinyobj_opt::index_t idx1 = attrib.indices[face_offset+3*f+1]; + tinyobj_opt::index_t idx2 = attrib.indices[face_offset+3*f+2]; float v[3][3]; for (int k = 0; k < 3; k++) { - int f0 = idx0.v_idx; - int f1 = idx1.v_idx; - int f2 = idx2.v_idx; + int f0 = idx0.vertex_index; + int f1 = idx1.vertex_index; + int f2 = idx2.vertex_index; assert(f0 >= 0); assert(f1 >= 0); assert(f2 >= 0); @@ -271,9 +271,9 @@ bool LoadObjAndConvert(float bmin[3], float bmax[3], const char* filename, int n float n[3][3]; if (attrib.normals.size() > 0) { - int nf0 = idx0.vn_idx; - int nf1 = idx1.vn_idx; - int nf2 = idx2.vn_idx; + int nf0 = idx0.normal_index; + int nf1 = idx1.normal_index; + int nf2 = idx2.normal_index; if (nf0 >= 0 && nf1 >= 0 && nf2 >= 0) { assert(3*nf0+2 < attrib.normals.size()); -- cgit v1.2.3 From 2b50b31657a65dcdaeb47bd7ab3ae645cf58223a Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Sat, 13 Aug 2016 18:38:45 +0900 Subject: Return parsed material information in `parseObj` API.. Parse PBR extension in MTL --- experimental/tinyobj_loader_opt.h | 133 ++++++++++++++++++++++++++++++++++---- experimental/viewer.cc | 6 +- 2 files changed, 125 insertions(+), 14 deletions(-) diff --git a/experimental/tinyobj_loader_opt.h b/experimental/tinyobj_loader_opt.h index 8c745d3..91902fb 100644 --- a/experimental/tinyobj_loader_opt.h +++ b/experimental/tinyobj_loader_opt.h @@ -292,6 +292,22 @@ typedef struct { std::string bump_texname; // map_bump, bump std::string displacement_texname; // disp std::string alpha_texname; // map_d + + // PBR extension + // http://exocortex.com/blog/extending_wavefront_mtl_to_support_pbr + float roughness; // [0, 1] default 0 + float metallic; // [0, 1] default 0 + float sheen; // [0, 1] default 0 + float clearcoat_thickness; // [0, 1] default 0 + float clearcoat_roughness; // [0, 1] default 0 + float anisotropy; // aniso. [0, 1] default 0 + float anisotropy_rotation; // anisor. [0, 1] default 0 + std::string roughness_texname; // map_Pr + std::string metallic_texname; // map_Pm + std::string sheen_texname; // map_Ps + std::string emissive_texname; // map_Ke + std::string normal_texname; // norm. For normal mapping. + std::map unknown_parameter; } material_t; @@ -304,7 +320,8 @@ typedef struct { struct index_t { int vertex_index, texcoord_index, normal_index; index_t() : vertex_index(-1), texcoord_index(-1), normal_index(-1) {} - explicit index_t(int idx) : vertex_index(idx), texcoord_index(idx), normal_index(idx) {} + explicit index_t(int idx) + : vertex_index(idx), texcoord_index(idx), normal_index(idx) {} index_t(int vidx, int vtidx, int vnidx) : vertex_index(vidx), texcoord_index(vtidx), normal_index(vnidx) {} }; @@ -660,6 +677,11 @@ static void LoadMtl(std::map *material_map, std::string linebuf(&buf[0]); + // Trim trailing whitespace. + if (linebuf.size() > 0) { + linebuf = linebuf.substr(0, linebuf.find_last_not_of(" \t") + 1); + } + // Trim newline '\r\n' or '\n' if (linebuf.size() > 0) { if (linebuf[linebuf.size() - 1] == '\n') @@ -790,6 +812,7 @@ static void LoadMtl(std::map *material_map, material.dissolve = parseFloat(&token); continue; } + if (token[0] == 'T' && token[1] == 'r' && IS_SPACE(token[2])) { token += 2; // Invert value of Tr(assume Tr is in range [0, 1]) @@ -797,6 +820,55 @@ static void LoadMtl(std::map *material_map, continue; } + // PBR: roughness + if (token[0] == 'P' && token[1] == 'r' && IS_SPACE(token[2])) { + token += 2; + material.roughness = parseFloat(&token); + continue; + } + + // PBR: metallic + if (token[0] == 'P' && token[1] == 'm' && IS_SPACE(token[2])) { + token += 2; + material.metallic = parseFloat(&token); + continue; + } + + // PBR: sheen + if (token[0] == 'P' && token[1] == 's' && IS_SPACE(token[2])) { + token += 2; + material.sheen = parseFloat(&token); + continue; + } + + // PBR: clearcoat thickness + if (token[0] == 'P' && token[1] == 'c' && IS_SPACE(token[2])) { + token += 2; + material.clearcoat_thickness = parseFloat(&token); + continue; + } + + // PBR: clearcoat roughness + if ((0 == strncmp(token, "Pcr", 3)) && IS_SPACE(token[3])) { + token += 4; + material.clearcoat_roughness = parseFloat(&token); + continue; + } + + // PBR: anisotropy + if ((0 == strncmp(token, "aniso", 5)) && IS_SPACE(token[5])) { + token += 6; + material.anisotropy = parseFloat(&token); + continue; + } + + // PBR: anisotropy rotation + if ((0 == strncmp(token, "anisor", 6)) && IS_SPACE(token[6])) { + token += 7; + material.anisotropy_rotation = parseFloat(&token); + continue; + } + // ambient texture if ((0 == strncmp(token, "map_Ka", 6)) && IS_SPACE(token[6])) { token += 7; @@ -853,6 +925,41 @@ static void LoadMtl(std::map *material_map, continue; } + // PBR: roughness texture + if ((0 == strncmp(token, "map_Pr", 6)) && IS_SPACE(token[6])) { + token += 7; + material.roughness_texname = token; + continue; + } + + // PBR: metallic texture + if ((0 == strncmp(token, "map_Pm", 6)) && IS_SPACE(token[6])) { + token += 7; + material.metallic_texname = token; + continue; + } + + // PBR: sheen texture + if ((0 == strncmp(token, "map_Ps", 6)) && IS_SPACE(token[6])) { + token += 7; + material.sheen_texname = token; + continue; + } + + // PBR: emissive texture + if ((0 == strncmp(token, "map_Ke", 6)) && IS_SPACE(token[6])) { + token += 7; + material.emissive_texname = token; + continue; + } + + // PBR: normal map texture + if ((0 == strncmp(token, "norm", 4)) && IS_SPACE(token[4])) { + token += 5; + material.normal_texname = token; + continue; + } + // unknown parameter const char *_space = strchr(token, ' '); if (!_space) { @@ -935,8 +1042,9 @@ class LoadOption { /// Parse wavefront .obj(.obj string data is expanded to linear char array /// `buf') /// -1 to req_num_threads use the number of HW threads in the running system. -bool parseObj(attrib_t *attrib, std::vector *shapes, const char *buf, - size_t len, const LoadOption &option); +bool parseObj(attrib_t *attrib, std::vector *shapes, + std::vector *materials, const char *buf, size_t len, + const LoadOption &option); #ifdef TINYOBJ_LOADER_OPT_IMPLEMENTATION @@ -1137,8 +1245,9 @@ static inline bool is_line_ending(const char *p, size_t i, size_t end_i) { return false; } -bool parseObj(attrib_t *attrib, std::vector *shapes, const char *buf, - size_t len, const LoadOption &option) { +bool parseObj(attrib_t *attrib, std::vector *shapes, + std::vector *materials, const char *buf, size_t len, + const LoadOption &option) { attrib->vertices.clear(); attrib->normals.clear(); attrib->texcoords.clear(); @@ -1313,7 +1422,6 @@ bool parseObj(attrib_t *attrib, std::vector *shapes, const char *buf, } std::map material_map; - std::vector materials; // Load material(if exits) if (mtllib_i_index >= 0 && mtllib_t_index >= 0 && @@ -1328,7 +1436,7 @@ bool parseObj(attrib_t *attrib, std::vector *shapes, const char *buf, std::ifstream ifs(material_filename); if (ifs.good()) { - LoadMtl(&material_map, &materials, &ifs); + LoadMtl(&material_map, materials, &ifs); // std::cout << "maetrials = " << materials.size() << std::endl; @@ -1361,10 +1469,10 @@ bool parseObj(attrib_t *attrib, std::vector *shapes, const char *buf, num_indices += command_count[t].num_indices; } - //std::cout << "# v " << num_v << std::endl; - //std::cout << "# vn " << num_vn << std::endl; - //std::cout << "# vt " << num_vt << std::endl; - //std::cout << "# f " << num_f << std::endl; + // std::cout << "# v " << num_v << std::endl; + // std::cout << "# vn " << num_vn << std::endl; + // std::cout << "# vt " << num_vt << std::endl; + // std::cout << "# f " << num_f << std::endl; // 4. merge // @todo { parallelize merge. } @@ -1445,7 +1553,8 @@ bool parseObj(attrib_t *attrib, std::vector *shapes, const char *buf, int vertex_index = fixIndex(vi.vertex_index, v_count); int texcoord_index = fixIndex(vi.texcoord_index, t_count); int normal_index = fixIndex(vi.normal_index, n_count); - attrib->indices[f_count + k] = index_t(vertex_index, texcoord_index, normal_index); + attrib->indices[f_count + k] = + index_t(vertex_index, texcoord_index, normal_index); } attrib->material_ids[face_count] = material_id; attrib->face_num_verts[face_count] = commands[t][i].f.size(); diff --git a/experimental/viewer.cc b/experimental/viewer.cc index f4ab5ed..be17c7a 100644 --- a/experimental/viewer.cc +++ b/experimental/viewer.cc @@ -218,6 +218,7 @@ bool LoadObjAndConvert(float bmin[3], float bmax[3], const char* filename, int n { tinyobj_opt::attrib_t attrib; std::vector shapes; + std::vector materials; size_t data_len = 0; const char* data = get_file_data(&data_len, filename); @@ -229,7 +230,7 @@ bool LoadObjAndConvert(float bmin[3], float bmax[3], const char* filename, int n tinyobj_opt::LoadOption option; option.req_num_threads = num_threads; option.verbose = verbose; - bool ret = parseObj(&attrib, &shapes, data, data_len, option); + bool ret = parseObj(&attrib, &shapes, &materials, data, data_len, option); bmin[0] = bmin[1] = bmin[2] = std::numeric_limits::max(); bmax[0] = bmax[1] = bmax[2] = -std::numeric_limits::max(); @@ -528,6 +529,7 @@ int main(int argc, char **argv) tinyobj_opt::attrib_t attrib; std::vector shapes; + std::vector materials; size_t data_len = 0; const char* data = get_file_data(&data_len, argv[1]); @@ -540,7 +542,7 @@ int main(int argc, char **argv) option.req_num_threads = num_threads; option.verbose = true; - bool ret = parseObj(&attrib, &shapes, data, data_len, option); + bool ret = parseObj(&attrib, &shapes, &materials, data, data_len, option); return ret; } -- cgit v1.2.3 From dea325cdcbed8644e50efca7bfec274286da3518 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Sat, 13 Aug 2016 19:27:13 +0900 Subject: Fix face num calculation. --- experimental/tinyobj_loader_opt.h | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/experimental/tinyobj_loader_opt.h b/experimental/tinyobj_loader_opt.h index 91902fb..98fe73e 100644 --- a/experimental/tinyobj_loader_opt.h +++ b/experimental/tinyobj_loader_opt.h @@ -1397,7 +1397,7 @@ bool parseObj(attrib_t *attrib, std::vector *shapes, command_count[t].num_vt++; } else if (command.type == COMMAND_F) { command_count[t].num_f += command.f.size(); - command_count[t].num_indices++; + command_count[t].num_indices += command.f_num_verts.size(); } if (command.type == COMMAND_MTLLIB) { @@ -1556,11 +1556,13 @@ bool parseObj(attrib_t *attrib, std::vector *shapes, attrib->indices[f_count + k] = index_t(vertex_index, texcoord_index, normal_index); } - attrib->material_ids[face_count] = material_id; - attrib->face_num_verts[face_count] = commands[t][i].f.size(); + for (size_t k = 0; k < commands[t][i].f_num_verts.size(); k++) { + attrib->material_ids[face_count + k] = material_id; + attrib->face_num_verts[face_count + k] = commands[t][i].f_num_verts[k]; + } f_count += commands[t][i].f.size(); - face_count++; + face_count += commands[t][i].f_num_verts.size(); } } })); -- cgit v1.2.3 From d3d6932efdaac3dca798ac295723b7187154e0e7 Mon Sep 17 00:00:00 2001 From: Grayson Lang Date: Wed, 17 Aug 2016 13:30:25 -0700 Subject: Fix MTL "transmission filter" token The "transmission filter" is currently set to Kt, which is undocumented. Adding support for the specified token of "Tf". --- tiny_obj_loader.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tiny_obj_loader.h b/tiny_obj_loader.h index c77ffc8..02ef7b0 100644 --- a/tiny_obj_loader.h +++ b/tiny_obj_loader.h @@ -802,7 +802,8 @@ void LoadMtl(std::map &material_map, } // transmittance - if (token[0] == 'K' && token[1] == 't' && IS_SPACE((token[2]))) { + if ((token[0] == 'K' && token[1] == 't' && IS_SPACE((token[2]))) + || (token[0] == 'T' && token[1] == 'F' && IS_SPACE((token[2])))) { token += 2; float r, g, b; parseFloat3(r, g, b, token); -- cgit v1.2.3 From 8e53519a2773e29f8e0540f00982660852385568 Mon Sep 17 00:00:00 2001 From: Grayson Lang Date: Wed, 17 Aug 2016 13:34:18 -0700 Subject: Ooops, meant "Tf" not "TF". --- tiny_obj_loader.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tiny_obj_loader.h b/tiny_obj_loader.h index 02ef7b0..dd8f061 100644 --- a/tiny_obj_loader.h +++ b/tiny_obj_loader.h @@ -803,7 +803,7 @@ void LoadMtl(std::map &material_map, // transmittance if ((token[0] == 'K' && token[1] == 't' && IS_SPACE((token[2]))) - || (token[0] == 'T' && token[1] == 'F' && IS_SPACE((token[2])))) { + || (token[0] == 'T' && token[1] == 'f' && IS_SPACE((token[2])))) { token += 2; float r, g, b; parseFloat3(r, g, b, token); -- cgit v1.2.3 From 646f1312f138898d81ccb38c2d2b8b8f4bb6ffa8 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Thu, 18 Aug 2016 16:13:27 +0900 Subject: Parse 'Tf' field in MTL. --- experimental/tinyobj_loader_opt.h | 3 ++- models/issue-95.mtl | 5 +++++ models/issue-95.obj | 7 +++++++ tests/tester.cc | 18 ++++++++++++++++++ 4 files changed, 32 insertions(+), 1 deletion(-) create mode 100644 models/issue-95.mtl create mode 100644 models/issue-95.obj diff --git a/experimental/tinyobj_loader_opt.h b/experimental/tinyobj_loader_opt.h index 98fe73e..d4e9eaf 100644 --- a/experimental/tinyobj_loader_opt.h +++ b/experimental/tinyobj_loader_opt.h @@ -764,7 +764,8 @@ static void LoadMtl(std::map *material_map, } // transmittance - if (token[0] == 'K' && token[1] == 't' && IS_SPACE((token[2]))) { + if ((token[0] == 'K' && token[1] == 't' && IS_SPACE((token[2]))) || + (token[0] == 'T' && token[1] == 'f' && IS_SPACE((token[2])))) { token += 2; float r, g, b; parseFloat3(&r, &g, &b, &token); diff --git a/models/issue-95.mtl b/models/issue-95.mtl new file mode 100644 index 0000000..68d484c --- /dev/null +++ b/models/issue-95.mtl @@ -0,0 +1,5 @@ +newmtl default +Ka 0 0 0 +Kd 0 0 0 +Ks 0 0 0 +Tf 0.1 0.2 0.3 diff --git a/models/issue-95.obj b/models/issue-95.obj new file mode 100644 index 0000000..f7be3b6 --- /dev/null +++ b/models/issue-95.obj @@ -0,0 +1,7 @@ +mtllib issue-92.mtl +o Test +v 1.864151 -1.219172 -5.532511 +v 0.575869 -0.666304 5.896140 +v 0.940448 1.000000 -1.971128 +usemtl default +f 1 2 3 diff --git a/tests/tester.cc b/tests/tester.cc index 8ca12de..8667888 100644 --- a/tests/tester.cc +++ b/tests/tester.cc @@ -367,6 +367,24 @@ TEST_CASE("trailing_whitespace_in_mtl", "[Issue92]") { REQUIRE(0 == materials[0].diffuse_texname.compare("tmp.png")); } +TEST_CASE("transmittance_filter", "[Issue95]") { + tinyobj::attrib_t attrib; + std::vector shapes; + std::vector materials; + + std::string err; + bool ret = tinyobj::LoadObj(&attrib, &shapes, &materials, &err, "../models/issue-95.obj", gMtlBasePath); + + if (!err.empty()) { + std::cerr << err << std::endl; + } + REQUIRE(true == ret); + REQUIRE(1 == materials.size()); + REQUIRE(0.1 == Approx(materials[0].transmittance[0])); + REQUIRE(0.2 == Approx(materials[0].transmittance[1])); + REQUIRE(0.3 == Approx(materials[0].transmittance[2])); +} + #if 0 int main( -- cgit v1.2.3 From d192402800906ff4a5e70eb07d8e888483fa0043 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Fri, 19 Aug 2016 20:19:28 +0900 Subject: Support `Tf` in MTL. --- models/issue-95-2.mtl | 5 +++++ models/issue-95-2.obj | 7 +++++++ models/issue-95.mtl | 2 +- models/issue-95.obj | 2 +- tests/tester.cc | 35 +++++++++++++++++++++++++++++++++++ tiny_obj_loader.h | 3 ++- 6 files changed, 51 insertions(+), 3 deletions(-) create mode 100644 models/issue-95-2.mtl create mode 100644 models/issue-95-2.obj diff --git a/models/issue-95-2.mtl b/models/issue-95-2.mtl new file mode 100644 index 0000000..68d484c --- /dev/null +++ b/models/issue-95-2.mtl @@ -0,0 +1,5 @@ +newmtl default +Ka 0 0 0 +Kd 0 0 0 +Ks 0 0 0 +Tf 0.1 0.2 0.3 diff --git a/models/issue-95-2.obj b/models/issue-95-2.obj new file mode 100644 index 0000000..456f854 --- /dev/null +++ b/models/issue-95-2.obj @@ -0,0 +1,7 @@ +mtllib issue-95-2.mtl +o Test +v 1.864151 -1.219172 -5.532511 +v 0.575869 -0.666304 5.896140 +v 0.940448 1.000000 -1.971128 +usemtl default +f 1 2 3 diff --git a/models/issue-95.mtl b/models/issue-95.mtl index 68d484c..1d29fee 100644 --- a/models/issue-95.mtl +++ b/models/issue-95.mtl @@ -2,4 +2,4 @@ newmtl default Ka 0 0 0 Kd 0 0 0 Ks 0 0 0 -Tf 0.1 0.2 0.3 +Kt 0.1 0.2 0.3 diff --git a/models/issue-95.obj b/models/issue-95.obj index f7be3b6..8ee267e 100644 --- a/models/issue-95.obj +++ b/models/issue-95.obj @@ -1,4 +1,4 @@ -mtllib issue-92.mtl +mtllib issue-95.mtl o Test v 1.864151 -1.219172 -5.532511 v 0.575869 -0.666304 5.896140 diff --git a/tests/tester.cc b/tests/tester.cc index 8667888..5573bb6 100644 --- a/tests/tester.cc +++ b/tests/tester.cc @@ -385,6 +385,41 @@ TEST_CASE("transmittance_filter", "[Issue95]") { REQUIRE(0.3 == Approx(materials[0].transmittance[2])); } +TEST_CASE("transmittance_filter_Tf", "[Issue95-Tf]") { + tinyobj::attrib_t attrib; + std::vector shapes; + std::vector materials; + + std::string err; + bool ret = tinyobj::LoadObj(&attrib, &shapes, &materials, &err, "../models/issue-95-2.obj", gMtlBasePath); + + if (!err.empty()) { + std::cerr << err << std::endl; + } + REQUIRE(true == ret); + REQUIRE(1 == materials.size()); + REQUIRE(0.1 == Approx(materials[0].transmittance[0])); + REQUIRE(0.2 == Approx(materials[0].transmittance[1])); + REQUIRE(0.3 == Approx(materials[0].transmittance[2])); +} +TEST_CASE("transmittance_filter_Kt", "[Issue95-Kt]") { + tinyobj::attrib_t attrib; + std::vector shapes; + std::vector materials; + + std::string err; + bool ret = tinyobj::LoadObj(&attrib, &shapes, &materials, &err, "../models/issue-95.obj", gMtlBasePath); + + if (!err.empty()) { + std::cerr << err << std::endl; + } + REQUIRE(true == ret); + REQUIRE(1 == materials.size()); + REQUIRE(0.1 == Approx(materials[0].transmittance[0])); + REQUIRE(0.2 == Approx(materials[0].transmittance[1])); + REQUIRE(0.3 == Approx(materials[0].transmittance[2])); +} + #if 0 int main( diff --git a/tiny_obj_loader.h b/tiny_obj_loader.h index 34375e9..c2b49b3 100644 --- a/tiny_obj_loader.h +++ b/tiny_obj_loader.h @@ -740,7 +740,8 @@ void LoadMtl(std::map *material_map, } // transmittance - if (token[0] == 'K' && token[1] == 't' && IS_SPACE((token[2]))) { + if ((token[0] == 'K' && token[1] == 't' && IS_SPACE((token[2]))) || + (token[0] == 'T' && token[1] == 'f' && IS_SPACE((token[2])))) { token += 2; float r, g, b; parseFloat3(&r, &g, &b, &token); -- cgit v1.2.3 From 49988672f417c5d7eebd819f4aeaf48d56c796b4 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Fri, 19 Aug 2016 20:22:26 +0900 Subject: Add link to tinyobjloader-c. --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index dbf3749..6f0de8e 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,8 @@ Tiny but powerful single file wavefront obj loader written in C++. No dependency `tinyobjloader` is good for embedding .obj loader to your (global illumination) renderer ;-) +If you are looking for C89 version, please see https://github.com/syoyo/tinyobjloader-c . + Notice! ------- -- cgit v1.2.3 From 5eef2b0914316786ce820964495663d809c6bf88 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Sat, 20 Aug 2016 16:23:20 +0900 Subject: Fix python binding. --- python/TODO.md | 3 + python/main.cpp | 269 +++++++++++++++++++++++++++++++------------------------- 2 files changed, 153 insertions(+), 119 deletions(-) create mode 100644 python/TODO.md diff --git a/python/TODO.md b/python/TODO.md new file mode 100644 index 0000000..d7aff07 --- /dev/null +++ b/python/TODO.md @@ -0,0 +1,3 @@ +* PBR material +* Define index_t struct +* Python 2.7 binding diff --git a/python/main.cpp b/python/main.cpp index f772793..6c794fa 100644 --- a/python/main.cpp +++ b/python/main.cpp @@ -1,160 +1,191 @@ -//python3 module for tinyobjloader +// python3 module for tinyobjloader // -//usage: +// usage: // import tinyobjloader as tol // model = tol.LoadObj(name) // print(model["shapes"]) // print(model["materials"] +// note: +// `shape.mesh.index_t` is represented as flattened array: (vertex_index, normal_index, texcoord_index) * num_faces #include #include #include "../tiny_obj_loader.h" typedef std::vector vectd; +typedef std::vector vecti; -PyObject* -pyTupleFromfloat3 (float array[3]) -{ - int i; - PyObject* tuple = PyTuple_New(3); +PyObject* pyTupleFromfloat3(float array[3]) { + int i; + PyObject* tuple = PyTuple_New(3); - for(i=0; i<=2 ; i++){ - PyTuple_SetItem(tuple, i, PyFloat_FromDouble(array[i])); - } + for (i = 0; i <= 2; i++) { + PyTuple_SetItem(tuple, i, PyFloat_FromDouble(array[i])); + } - return tuple; + return tuple; } -extern "C" -{ +extern "C" { + +static PyObject* pyLoadObj(PyObject* self, PyObject* args) { + PyObject *rtndict, *pyshapes, *pymaterials, *attribobj, *current, *meshobj; + + char const* current_name; + char const* filename; + vectd vect; + std::vector indices; + std::vector face_verts; -static PyObject* -pyLoadObj(PyObject* self, PyObject* args) -{ - PyObject *rtndict, *pyshapes, *pymaterials, - *current, *meshobj; + tinyobj::attrib_t attrib; + std::vector shapes; + std::vector materials; - char const* filename; - char *current_name; - vectd vect; + if (!PyArg_ParseTuple(args, "s", &filename)) return NULL; - std::vector shapes; - std::vector materials; + std::string err; + tinyobj::LoadObj(&attrib, &shapes, &materials, &err, filename); - if(!PyArg_ParseTuple(args, "s", &filename)) - return NULL; + pyshapes = PyDict_New(); + pymaterials = PyDict_New(); + rtndict = PyDict_New(); - std::string err; - tinyobj::LoadObj(shapes, materials, err, filename); + attribobj = PyDict_New(); - pyshapes = PyDict_New(); - pymaterials = PyDict_New(); - rtndict = PyDict_New(); + for (int i = 0; i <= 2; i++) { + current = PyList_New(0); + + switch (i) { + case 0: + current_name = "vertices"; + vect = vectd(attrib.vertices.begin(), attrib.vertices.end()); + break; + case 1: + current_name = "normals"; + vect = vectd(attrib.normals.begin(), attrib.normals.end()); + break; + case 2: + current_name = "texcoords"; + vect = vectd(attrib.texcoords.begin(), attrib.texcoords.end()); + break; + } + + for (vectd::iterator it = vect.begin(); it != vect.end(); it++) { + PyList_Insert(current, it - vect.begin(), PyFloat_FromDouble(*it)); + } + + PyDict_SetItemString(attribobj, current_name, current); + } + + for (std::vector::iterator shape = shapes.begin(); + shape != shapes.end(); shape++) { + meshobj = PyDict_New(); + tinyobj::mesh_t cm = (*shape).mesh; - for (std::vector::iterator shape = shapes.begin() ; - shape != shapes.end(); shape++) { - meshobj = PyDict_New(); - tinyobj::mesh_t cm = (*shape).mesh; - - for (int i = 0; i <= 4; i++ ) - { - current = PyList_New(0); - - switch(i) { - - case 0: - current_name = "positions"; - vect = vectd(cm.positions.begin(), cm.positions.end()); break; - case 1: - current_name = "normals"; - vect = vectd(cm.normals.begin(), cm.normals.end()); break; - case 2: - current_name = "texcoords"; - vect = vectd(cm.texcoords.begin(), cm.texcoords.end()); break; - case 3: - current_name = "indicies"; - vect = vectd(cm.indices.begin(), cm.indices.end()); break; - case 4: - current_name = "material_ids"; - vect = vectd(cm.material_ids.begin(), cm.material_ids.end()); break; - - } - - for (vectd::iterator it = vect.begin() ; - it != vect.end(); it++) - { - PyList_Insert(current, it - vect.begin(), PyFloat_FromDouble(*it)); - } - - PyDict_SetItemString(meshobj, current_name, current); - - } - - PyDict_SetItemString(pyshapes, (*shape).name.c_str(), meshobj); + current = PyList_New(0); + + for (size_t i = 0; i < cm.indices.size(); i++) { + // Flatten index array: v_idx, vn_idx, vt_idx, v_idx, vn_idx, vt_idx, + // ... + PyList_Insert(current, 3 * i + 0, + PyLong_FromLong(cm.indices[i].vertex_index)); + PyList_Insert(current, 3 * i + 1, + PyLong_FromLong(cm.indices[i].normal_index)); + PyList_Insert(current, 3 * i + 2, + PyLong_FromLong(cm.indices[i].texcoord_index)); + } + + PyDict_SetItemString(meshobj, "indices", current); } - for (std::vector::iterator mat = materials.begin() ; - mat != materials.end(); mat++) { - PyObject *matobj = PyDict_New(); - PyObject *unknown_parameter = PyDict_New(); - - for (std::map::iterator p = (*mat).unknown_parameter.begin() ; - p != (*mat).unknown_parameter.end(); ++p) - { - PyDict_SetItemString(unknown_parameter, p->first.c_str(), PyUnicode_FromString(p->second.c_str())); - } - - PyDict_SetItemString(matobj, "shininess", PyFloat_FromDouble((*mat).shininess)); - PyDict_SetItemString(matobj, "ior", PyFloat_FromDouble((*mat).ior)); - PyDict_SetItemString(matobj, "dissolve", PyFloat_FromDouble((*mat).dissolve)); - PyDict_SetItemString(matobj, "illum", PyLong_FromLong((*mat).illum)); - PyDict_SetItemString(matobj, "ambient_texname", PyUnicode_FromString((*mat).ambient_texname.c_str())); - PyDict_SetItemString(matobj, "diffuse_texname", PyUnicode_FromString((*mat).diffuse_texname.c_str())); - PyDict_SetItemString(matobj, "specular_texname", PyUnicode_FromString((*mat).specular_texname.c_str())); - PyDict_SetItemString(matobj, "specular_highlight_texname", PyUnicode_FromString((*mat).specular_highlight_texname.c_str())); - PyDict_SetItemString(matobj, "bump_texname", PyUnicode_FromString((*mat).bump_texname.c_str())); - PyDict_SetItemString(matobj, "displacement_texname", PyUnicode_FromString((*mat).displacement_texname.c_str())); - PyDict_SetItemString(matobj, "alpha_texname", PyUnicode_FromString((*mat).alpha_texname.c_str())); - PyDict_SetItemString(matobj, "ambient", pyTupleFromfloat3((*mat).ambient)); - PyDict_SetItemString(matobj, "diffuse", pyTupleFromfloat3((*mat).diffuse)); - PyDict_SetItemString(matobj, "specular", pyTupleFromfloat3((*mat).specular)); - PyDict_SetItemString(matobj, "transmittance", pyTupleFromfloat3((*mat).transmittance)); - PyDict_SetItemString(matobj, "emission", pyTupleFromfloat3((*mat).emission)); - PyDict_SetItemString(matobj, "unknown_parameter", unknown_parameter); - - PyDict_SetItemString(pymaterials, (*mat).name.c_str(), matobj); + current = PyList_New(0); + + for (size_t i = 0; i < cm.num_face_vertices.size(); i++) { + // Widen data type to long. + PyList_Insert(current, i, PyLong_FromLong(cm.num_face_vertices[i])); + } + + PyDict_SetItemString(meshobj, "num_face_vertices", current); } - PyDict_SetItemString(rtndict, "shapes", pyshapes); - PyDict_SetItemString(rtndict, "materials", pymaterials); + { + current = PyList_New(0); - return rtndict; -} + for (size_t i = 0; i < cm.material_ids.size(); i++) { + PyList_Insert(current, i, PyLong_FromLong(cm.material_ids[i])); + } + PyDict_SetItemString(meshobj, "material_ids", current); + } -static PyMethodDef mMethods[] = { + PyDict_SetItemString(pyshapes, (*shape).name.c_str(), meshobj); + } -{"LoadObj", pyLoadObj, METH_VARARGS}, -{NULL, NULL, 0, NULL} + for (std::vector::iterator mat = materials.begin(); + mat != materials.end(); mat++) { + PyObject* matobj = PyDict_New(); + PyObject* unknown_parameter = PyDict_New(); -}; + for (std::map::iterator p = + (*mat).unknown_parameter.begin(); + p != (*mat).unknown_parameter.end(); ++p) { + PyDict_SetItemString(unknown_parameter, p->first.c_str(), + PyUnicode_FromString(p->second.c_str())); + } + PyDict_SetItemString(matobj, "shininess", + PyFloat_FromDouble((*mat).shininess)); + PyDict_SetItemString(matobj, "ior", PyFloat_FromDouble((*mat).ior)); + PyDict_SetItemString(matobj, "dissolve", + PyFloat_FromDouble((*mat).dissolve)); + PyDict_SetItemString(matobj, "illum", PyLong_FromLong((*mat).illum)); + PyDict_SetItemString(matobj, "ambient_texname", + PyUnicode_FromString((*mat).ambient_texname.c_str())); + PyDict_SetItemString(matobj, "diffuse_texname", + PyUnicode_FromString((*mat).diffuse_texname.c_str())); + PyDict_SetItemString(matobj, "specular_texname", + PyUnicode_FromString((*mat).specular_texname.c_str())); + PyDict_SetItemString( + matobj, "specular_highlight_texname", + PyUnicode_FromString((*mat).specular_highlight_texname.c_str())); + PyDict_SetItemString(matobj, "bump_texname", + PyUnicode_FromString((*mat).bump_texname.c_str())); + PyDict_SetItemString( + matobj, "displacement_texname", + PyUnicode_FromString((*mat).displacement_texname.c_str())); + PyDict_SetItemString(matobj, "alpha_texname", + PyUnicode_FromString((*mat).alpha_texname.c_str())); + PyDict_SetItemString(matobj, "ambient", pyTupleFromfloat3((*mat).ambient)); + PyDict_SetItemString(matobj, "diffuse", pyTupleFromfloat3((*mat).diffuse)); + PyDict_SetItemString(matobj, "specular", + pyTupleFromfloat3((*mat).specular)); + PyDict_SetItemString(matobj, "transmittance", + pyTupleFromfloat3((*mat).transmittance)); + PyDict_SetItemString(matobj, "emission", + pyTupleFromfloat3((*mat).emission)); + PyDict_SetItemString(matobj, "unknown_parameter", unknown_parameter); + + PyDict_SetItemString(pymaterials, (*mat).name.c_str(), matobj); + } + + PyDict_SetItemString(rtndict, "shapes", pyshapes); + PyDict_SetItemString(rtndict, "materials", pymaterials); + + return rtndict; +} + +static PyMethodDef mMethods[] = { + + {"LoadObj", pyLoadObj, METH_VARARGS}, {NULL, NULL, 0, NULL} -static struct PyModuleDef moduledef = { - PyModuleDef_HEAD_INIT, - "tinyobjloader", - NULL, - -1, - mMethods }; +static struct PyModuleDef moduledef = {PyModuleDef_HEAD_INIT, "tinyobjloader", + NULL, -1, mMethods}; -PyMODINIT_FUNC -PyInit_tinyobjloader(void) -{ - return PyModule_Create(&moduledef); +PyMODINIT_FUNC PyInit_tinyobjloader(void) { + return PyModule_Create(&moduledef); } - } -- cgit v1.2.3 From f06e814d7c31c15767b81114da4702093b2b7b1d Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Sat, 20 Aug 2016 16:23:41 +0900 Subject: Update README. --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 3cfbbbb..cd9dbc3 100644 --- a/README.md +++ b/README.md @@ -74,7 +74,6 @@ TinyObjLoader is successfully used in ... ## TODO -* [ ] Fix Python binding. * [ ] Fix obj_sticker example. * [ ] More unit test codes. -- cgit v1.2.3 From 25c194bf718c34494c1e487b50790d98c18252e6 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Sat, 20 Aug 2016 17:53:07 +0900 Subject: Update README. --- README.md | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 6c5b3e1..36b34b2 100644 --- a/README.md +++ b/README.md @@ -19,9 +19,8 @@ Tiny but powerful single file wavefront obj loader written in C++. No dependency Notice! ------- -`master` branch will be replaced with `develop` branch in the near future: https://github.com/syoyo/tinyobjloader/tree/develop -`develop` branch has more better support and clean API interface for loading .obj and also it has optimized multi-threaded parser(probably 10x faster than `master`). If you are new to use `TinyObjLoader`, I highly recommend to use `develop` branch. - +We have released new version v1.0.0 on 20 Aug, 2016. +Old version is available `v0.9.x` branch https://github.com/syoyo/tinyobjloader/tree/v0.9.x ## What's new @@ -29,8 +28,7 @@ Notice! ### Old version -Previous old version is avaiable as `v0.9` branch. - +Previous old version is avaiable in `v0.9.x` branch. ## Example -- cgit v1.2.3 From 9d81c249341eeed15137bf0a1afadbe9da27405a Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Sat, 20 Aug 2016 17:54:24 +0900 Subject: Update AppVeyor script. --- appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index 9430b49..89fd100 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,4 +1,4 @@ -version: 0.9.{build} +version: 1.0.{build} platform: x64 -- cgit v1.2.3 From 64149943cc0108f86ada3dd37fd69c44d5a6b12c Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Sat, 20 Aug 2016 18:01:27 +0900 Subject: Add link to examples. --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index ed13cf2..6e2b7ce 100644 --- a/README.md +++ b/README.md @@ -42,6 +42,8 @@ http://graphics.cs.williams.edu/data/meshes.xml ![](images/sanmugel.png) * [examples/viewer/](examples/viewer) OpenGL .obj viewer +* [examples/callback_api/](examples/callback_api/) Callback API example +* [examples/voxelize/](examples/voxelize/) Voxelizer example ## Use case -- cgit v1.2.3 From ad79762212969e8e7b7f2ba67ef3d4d8b27f4def Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Sat, 20 Aug 2016 18:03:18 +0900 Subject: Update README. --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 6e2b7ce..a218930 100644 --- a/README.md +++ b/README.md @@ -49,12 +49,12 @@ http://graphics.cs.williams.edu/data/meshes.xml TinyObjLoader is successfully used in ... -### New version +### New version(v1.0.x) * Loading models in Vulkan Tutorial https://vulkan-tutorial.com/Loading_models * Your project here! -### Old version +### Old version(v0.9.x) * bullet3 https://github.com/erwincoumans/bullet3 * pbrt-v2 https://github.com/mmp/pbrt-v2 -- cgit v1.2.3 From 5b9f4315124b85fcb1e2efd3c43389803904b1e1 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Sat, 20 Aug 2016 18:16:50 +0900 Subject: Update TODO list. --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index a218930..8613748 100644 --- a/README.md +++ b/README.md @@ -90,6 +90,9 @@ TinyObjLoader is successfully used in ... * [ ] Fix obj_sticker example. * [ ] More unit test codes. +* [ ] Texture options +* [ ] Normal vector generation + * [ ] Support smoothing groups ## License -- cgit v1.2.3 From 69240e18b49469762d1322ac78a6af69202fe61d Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Tue, 23 Aug 2016 02:29:41 +0900 Subject: Add link to PBGI. --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 8613748..68318d4 100644 --- a/README.md +++ b/README.md @@ -73,6 +73,7 @@ TinyObjLoader is successfully used in ... * FireRays SDK https://github.com/GPUOpen-LibrariesAndSDKs/FireRays_SDK * parg, tiny C library of various graphics utilities and GL demos https://github.com/prideout/parg * Opengl unit of ChronoEngine https://github.com/projectchrono/chrono-opengl +* Point Based Global Illumination on modern GPU https://pbgi.wordpress.com/code-source/ ## Features -- cgit v1.2.3 From 6bf145d7cf057775f3db138b03f68392af21d1eb Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Tue, 23 Aug 2016 02:35:31 +0900 Subject: Add link to "Fast OBJ file importing and parsing in CUDA". --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 68318d4..6cf5ac4 100644 --- a/README.md +++ b/README.md @@ -74,6 +74,8 @@ TinyObjLoader is successfully used in ... * parg, tiny C library of various graphics utilities and GL demos https://github.com/prideout/parg * Opengl unit of ChronoEngine https://github.com/projectchrono/chrono-opengl * Point Based Global Illumination on modern GPU https://pbgi.wordpress.com/code-source/ +* Fast OBJ file importing and parsing in CUDA http://researchonline.jcu.edu.au/42515/1/2015.CVM.OBJCUDA.pdf + ## Features -- cgit v1.2.3 From 25cf039953dafe6ce4ba82e562672c8fc303f9cd Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Tue, 23 Aug 2016 02:37:47 +0900 Subject: Add more link. --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 6cf5ac4..9e95eed 100644 --- a/README.md +++ b/README.md @@ -75,6 +75,7 @@ TinyObjLoader is successfully used in ... * Opengl unit of ChronoEngine https://github.com/projectchrono/chrono-opengl * Point Based Global Illumination on modern GPU https://pbgi.wordpress.com/code-source/ * Fast OBJ file importing and parsing in CUDA http://researchonline.jcu.edu.au/42515/1/2015.CVM.OBJCUDA.pdf +* Sorted Shading for Uni-Directional Pathtracing by Joshua Bainbridge https://nccastaff.bournemouth.ac.uk/jmacey/MastersProjects/MSc15/02Josh/joshua_bainbridge_thesis.pdf ## Features -- cgit v1.2.3 From 9f92ba34a6923997b7589982e924eed5fcdb031e Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Tue, 23 Aug 2016 02:44:32 +0900 Subject: Update `Feature` list. --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 9e95eed..09eb2f6 100644 --- a/README.md +++ b/README.md @@ -87,6 +87,7 @@ TinyObjLoader is successfully used in ... * Material * Unknown material attributes are returned as key-value(value is string) map. * Crease tag('t'). This is OpenSubdiv specific(not in wavefront .obj specification) +* PBR material extension for .MTL. Its proposed here: http://exocortex.com/blog/extending_wavefront_mtl_to_support_pbr * Callback API for custom loading. -- cgit v1.2.3 From a7a16db9080ed3c5212639e79c4f1f1cfb3b4c2c Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Tue, 23 Aug 2016 02:48:06 +0900 Subject: Add a link to GeeXLab. --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 09eb2f6..90a3ae9 100644 --- a/README.md +++ b/README.md @@ -76,6 +76,7 @@ TinyObjLoader is successfully used in ... * Point Based Global Illumination on modern GPU https://pbgi.wordpress.com/code-source/ * Fast OBJ file importing and parsing in CUDA http://researchonline.jcu.edu.au/42515/1/2015.CVM.OBJCUDA.pdf * Sorted Shading for Uni-Directional Pathtracing by Joshua Bainbridge https://nccastaff.bournemouth.ac.uk/jmacey/MastersProjects/MSc15/02Josh/joshua_bainbridge_thesis.pdf +* GeeXLab http://www.geeks3d.com/hacklab/20160531/geexlab-0-12-0-0-released-for-windows/ ## Features -- cgit v1.2.3 From b3feefafdfddc670bcaebf2662a6c8f2c779dec9 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Sat, 3 Sep 2016 21:43:00 +0900 Subject: Add link to MetalExamples. --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 90a3ae9..fab4db2 100644 --- a/README.md +++ b/README.md @@ -52,6 +52,7 @@ TinyObjLoader is successfully used in ... ### New version(v1.0.x) * Loading models in Vulkan Tutorial https://vulkan-tutorial.com/Loading_models +* .obj viewer with Metal https://github.com/middlefeng/MetalExamples/tree/master/objc/ModelViewer * Your project here! ### Old version(v0.9.x) -- cgit v1.2.3 From 1b88a1e3c72a43aae0c42146555ac0ee3f5c9117 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Sun, 4 Sep 2016 19:34:05 +0900 Subject: Support reading .obj from ZStd compressed format. --- experimental/premake4.lua | 10 ++++++ experimental/viewer.cc | 82 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 92 insertions(+) diff --git a/experimental/premake4.lua b/experimental/premake4.lua index 321ab19..211e1ea 100644 --- a/experimental/premake4.lua +++ b/experimental/premake4.lua @@ -3,6 +3,11 @@ newoption { description = "Build with zlib." } +newoption { + trigger = "with-zstd", + description = "Build with ZStandard compression." +} + solution "objview" -- location ( "build" ) configurations { "Release", "Debug" } @@ -23,6 +28,11 @@ solution "objview" links { 'z' } end + if _OPTIONS['with-zstd'] then + defines { 'ENABLE_ZSTD' } + links { 'zstd' } + end + -- Uncomment if you want address sanitizer(gcc/clang only) --buildoptions { "-fsanitize=address" } --linkoptions { "-fsanitize=address" } diff --git a/experimental/viewer.cc b/experimental/viewer.cc index be17c7a..b79fe46 100644 --- a/experimental/viewer.cc +++ b/experimental/viewer.cc @@ -16,6 +16,10 @@ #include #endif +#if defined(ENABLE_ZSTD) +#include +#endif + #include #ifdef __APPLE__ @@ -182,6 +186,71 @@ bool gz_load(std::vector* buf, const char* filename) #endif } +#ifdef ENABLE_ZSTD +static off_t fsize_X(const char *filename) +{ + struct stat st; + if (stat(filename, &st) == 0) return st.st_size; + /* error */ + printf("stat: %s : %s \n", filename, strerror(errno)); + exit(1); +} + +static FILE* fopen_X(const char *filename, const char *instruction) +{ + FILE* const inFile = fopen(filename, instruction); + if (inFile) return inFile; + /* error */ + printf("fopen: %s : %s \n", filename, strerror(errno)); + exit(2); +} + +static void* malloc_X(size_t size) +{ + void* const buff = malloc(size); + if (buff) return buff; + /* error */ + printf("malloc: %s \n", strerror(errno)); + exit(3); +} +#endif + +bool zstd_load(std::vector* buf, const char* filename) +{ +#ifdef ENABLE_ZSTD + off_t const buffSize = fsize_X(filename); + FILE* const inFile = fopen_X(filename, "rb"); + void* const buffer = malloc_X(buffSize); + size_t const readSize = fread(buffer, 1, buffSize, inFile); + if (readSize != (size_t)buffSize) { + printf("fread: %s : %s \n", filename, strerror(errno)); + exit(4); + } + fclose(inFile); + + unsigned long long const rSize = ZSTD_getDecompressedSize(buffer, buffSize); + if (rSize==0) { + printf("%s : original size unknown \n", filename); + exit(5); + } + + buf->resize(rSize); + + size_t const dSize = ZSTD_decompress(buf->data(), rSize, buffer, buffSize); + + if (dSize != rSize) { + printf("error decoding %s : %s \n", filename, ZSTD_getErrorName(dSize)); + exit(7); + } + + free(buffer); + + return true; +#else + return false; +#endif +} + const char* get_file_data(size_t *len, const char* filename) { @@ -204,6 +273,19 @@ const char* get_file_data(size_t *len, const char* filename) data_len = buf.size(); } + } else if (strcmp(ext, ".zst") == 0) { + // gzipped data. + + std::vector buf; + bool ret = zstd_load(&buf, filename); + + if (ret) { + char *p = static_cast(malloc(buf.size() + 1)); // @fixme { implement deleter } + memcpy(p, &buf.at(0), buf.size()); + p[buf.size()] = '\0'; + data = p; + data_len = buf.size(); + } } else { data = mmap_file(&data_len, filename); -- cgit v1.2.3 From 4a18e241d9294ea76c33d759f3d7fcedb50d56ad Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Tue, 6 Sep 2016 02:11:23 +0900 Subject: Measure zstd decompress time. --- experimental/premake4.lua | 4 ++++ experimental/viewer.cc | 15 +++++++++++++-- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/experimental/premake4.lua b/experimental/premake4.lua index 211e1ea..d5f433f 100644 --- a/experimental/premake4.lua +++ b/experimental/premake4.lua @@ -29,7 +29,11 @@ solution "objview" end if _OPTIONS['with-zstd'] then + print("with-zstd") defines { 'ENABLE_ZSTD' } + -- Set path to zstd installed dir. + includedirs { '$$HOME/local/include' } + libdirs { '$$HOME/local/lib' } links { 'zstd' } end diff --git a/experimental/viewer.cc b/experimental/viewer.cc index b79fe46..87db22b 100644 --- a/experimental/viewer.cc +++ b/experimental/viewer.cc @@ -274,7 +274,8 @@ const char* get_file_data(size_t *len, const char* filename) } } else if (strcmp(ext, ".zst") == 0) { - // gzipped data. + printf("zstd\n"); + // Zstandard data. std::vector buf; bool ret = zstd_load(&buf, filename); @@ -302,13 +303,22 @@ bool LoadObjAndConvert(float bmin[3], float bmax[3], const char* filename, int n std::vector shapes; std::vector materials; + auto load_t_begin = std::chrono::high_resolution_clock::now(); size_t data_len = 0; const char* data = get_file_data(&data_len, filename); if (data == nullptr) { + printf("failed to load file\n"); exit(-1); return false; } - printf("filesize: %d\n", (int)data_len); + auto load_t_end = std::chrono::high_resolution_clock::now(); + std::chrono::duration load_ms = load_t_end - load_t_begin; + if (verbose) { + std::cout << "filesize: " << data_len << std::endl; + std::cout << "load time: " << load_ms.count() << " [msecs]" << std::endl; + } + + tinyobj_opt::LoadOption option; option.req_num_threads = num_threads; option.verbose = verbose; @@ -616,6 +626,7 @@ int main(int argc, char **argv) size_t data_len = 0; const char* data = get_file_data(&data_len, argv[1]); if (data == nullptr) { + printf("failed to load file\n"); exit(-1); return false; } -- cgit v1.2.3 From 60acd05a0304d44e65893bc615138dc68dc23a9d Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Tue, 6 Sep 2016 02:18:24 +0900 Subject: Add brief README. --- experimental/README.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 experimental/README.md diff --git a/experimental/README.md b/experimental/README.md new file mode 100644 index 0000000..9a88abb --- /dev/null +++ b/experimental/README.md @@ -0,0 +1,5 @@ +Experimental code for .obj loader. + +* Multi-threaded optimized parser : tinyobj_loader_opt.h +* zstd compressed .obj support. `--with-zstd` premake option. +* gzip compressed .obj support. `--with-zlib` premake option. -- cgit v1.2.3 From 92805d30881478f999a0f6f279c0e001b1454cee Mon Sep 17 00:00:00 2001 From: middleware Date: Wed, 7 Sep 2016 06:31:08 -0700 Subject: Metal viewer URL update. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index fab4db2..00ada0e 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,7 @@ TinyObjLoader is successfully used in ... ### New version(v1.0.x) * Loading models in Vulkan Tutorial https://vulkan-tutorial.com/Loading_models -* .obj viewer with Metal https://github.com/middlefeng/MetalExamples/tree/master/objc/ModelViewer +* .obj viewer with Metal https://github.com/middlefeng/NuoModelViewer/tree/master * Your project here! ### Old version(v0.9.x) -- cgit v1.2.3 From 71cc967f422c9547c4a1eff97aa19b8d10c86a69 Mon Sep 17 00:00:00 2001 From: Merlyn Morgan-Graham Date: Sun, 2 Oct 2016 00:52:19 -0700 Subject: Allow skipping material reads on LoadObj istream overload --- tiny_obj_loader.h | 31 +++++++++++++++++-------------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/tiny_obj_loader.h b/tiny_obj_loader.h index 0200ff5..a7ed6ab 100644 --- a/tiny_obj_loader.h +++ b/tiny_obj_loader.h @@ -209,7 +209,7 @@ bool LoadObjWithCallback(std::istream &inStream, const callback_t &callback, /// Returns warning and error message into `err` bool LoadObj(attrib_t *attrib, std::vector *shapes, std::vector *materials, std::string *err, - std::istream *inStream, MaterialReader *readMatFn, + std::istream *inStream, MaterialReader *readMatFn = NULL, bool triangulate = true); /// Loads materials into std::map @@ -1042,7 +1042,8 @@ bool LoadObj(attrib_t *attrib, std::vector *shapes, bool LoadObj(attrib_t *attrib, std::vector *shapes, std::vector *materials, std::string *err, - std::istream *inStream, MaterialReader *readMatFn, + std::istream *inStream, + MaterialReader *readMatFn /*= NULL*/, bool triangulate) { std::stringstream errss; @@ -1173,23 +1174,25 @@ bool LoadObj(attrib_t *attrib, std::vector *shapes, // load mtl if ((0 == strncmp(token, "mtllib", 6)) && IS_SPACE((token[6]))) { - char namebuf[TINYOBJ_SSCANF_BUFFER_SIZE]; - token += 7; + if (readMatFn) { + char namebuf[TINYOBJ_SSCANF_BUFFER_SIZE]; + token += 7; #ifdef _MSC_VER - sscanf_s(token, "%s", namebuf, (unsigned)_countof(namebuf)); + sscanf_s(token, "%s", namebuf, (unsigned)_countof(namebuf)); #else - sscanf(token, "%s", namebuf); + sscanf(token, "%s", namebuf); #endif - std::string err_mtl; - bool ok = (*readMatFn)(namebuf, materials, &material_map, &err_mtl); - if (err) { - (*err) += err_mtl; - } + std::string err_mtl; + bool ok = (*readMatFn)(namebuf, materials, &material_map, &err_mtl); + if (err) { + (*err) += err_mtl; + } - if (!ok) { - faceGroup.clear(); // for safety - return false; + if (!ok) { + faceGroup.clear(); // for safety + return false; + } } continue; -- cgit v1.2.3 From 7fc9b0fe97ab1b814c0ac0b0f937652f13b188f2 Mon Sep 17 00:00:00 2001 From: Merlyn Morgan-Graham Date: Sun, 2 Oct 2016 00:52:45 -0700 Subject: Add stream based material reader implementation --- tiny_obj_loader.h | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/tiny_obj_loader.h b/tiny_obj_loader.h index a7ed6ab..228e819 100644 --- a/tiny_obj_loader.h +++ b/tiny_obj_loader.h @@ -179,6 +179,19 @@ class MaterialFileReader : public MaterialReader { std::string m_mtlBasePath; }; +class MaterialStreamReader : public MaterialReader { + public: + explicit MaterialStreamReader(std::istream &inStream) + : m_inStream(inStream) {} + virtual ~MaterialStreamReader() {} + virtual bool operator()(const std::string &matId, + std::vector *materials, + std::map *matMap, std::string *err); + + private: + std::istream &m_inStream; +}; + /// Loads .obj from a file. /// 'attrib', 'shapes' and 'materials' will be filled with parsed shape data /// 'shapes' will be filled with parsed shape data @@ -1010,6 +1023,22 @@ bool MaterialFileReader::operator()(const std::string &matId, return true; } +bool MaterialStreamReader::operator()(const std::string &matId, + std::vector *materials, + std::map *matMap, + std::string *err) { + LoadMtl(matMap, materials, &m_inStream); + if (!m_inStream) { + std::stringstream ss; + ss << "WARN: Material stream in error state." + << " Created a default material."; + if (err) { + (*err) += ss.str(); + } + } + return true; +} + bool LoadObj(attrib_t *attrib, std::vector *shapes, std::vector *materials, std::string *err, const char *filename, const char *mtl_basepath, -- cgit v1.2.3 From 398e358826035929795171740433e96fe2061e5a Mon Sep 17 00:00:00 2001 From: Merlyn Morgan-Graham Date: Sun, 2 Oct 2016 01:25:07 -0700 Subject: Add unit tests for reading from existing file stream --- tests/tester.cc | 52 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/tests/tester.cc b/tests/tester.cc index 5573bb6..00b4eb0 100644 --- a/tests/tester.cc +++ b/tests/tester.cc @@ -184,6 +184,48 @@ TestLoadObj( return true; } +static bool +TestLoadObjFromPreopenedFile( + const char* filename, + const char* basepath = NULL, + bool readMaterials = true, + bool triangulate = true) +{ + std::string fullFilename = std::string(basepath) + filename; + std::cout << "Loading " << fullFilename << std::endl; + + std::ifstream fileStream(fullFilename); + + if (!fileStream) { + std::cerr << "Could not find specified file: " << fullFilename << std::endl; + return false; + } + + tinyobj::MaterialStreamReader materialStreamReader(fileStream); + tinyobj::MaterialStreamReader* materialReader = readMaterials + ? &materialStreamReader + : NULL; + + tinyobj::attrib_t attrib; + std::vector shapes; + std::vector materials; + + std::string err; + bool ret = tinyobj::LoadObj(&attrib, &shapes, &materials, &err, &fileStream, materialReader); + + if (!err.empty()) { + std::cerr << err << std::endl; + } + + if (!ret) { + printf("Failed to load/parse .obj.\n"); + return false; + } + + std::cout << "Loaded material count: " << materials.size() << "\n"; + + return true; +} static bool TestStreamLoadObj() @@ -351,6 +393,16 @@ TEST_CASE("stream_load", "[Stream]") { REQUIRE(true == TestStreamLoadObj()); } +TEST_CASE("stream_load_from_file_skipping_materials", "[Stream]") { + REQUIRE(true == TestLoadObjFromPreopenedFile( + "../models/pbr-mat-ext.obj", gMtlBasePath, /*readMaterials*/false, /*triangulate*/false)); +} + +TEST_CASE("stream_load_from_file_with_materials", "[Stream]") { + REQUIRE(true == TestLoadObjFromPreopenedFile( + "../models/pbr-mat-ext.obj", gMtlBasePath, /*readMaterials*/true, /*triangulate*/false)); +} + TEST_CASE("trailing_whitespace_in_mtl", "[Issue92]") { tinyobj::attrib_t attrib; std::vector shapes; -- cgit v1.2.3 From 79513077f3649c789e84fa94cd452fffce10dd6b Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Sun, 2 Oct 2016 17:37:02 +0900 Subject: Fix compilation on pre-C++11 compiler. --- tests/tester.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/tester.cc b/tests/tester.cc index 00b4eb0..9ac49e6 100644 --- a/tests/tester.cc +++ b/tests/tester.cc @@ -194,7 +194,7 @@ TestLoadObjFromPreopenedFile( std::string fullFilename = std::string(basepath) + filename; std::cout << "Loading " << fullFilename << std::endl; - std::ifstream fileStream(fullFilename); + std::ifstream fileStream(fullFilename.c_str()); if (!fileStream) { std::cerr << "Could not find specified file: " << fullFilename << std::endl; -- cgit v1.2.3 From e4598ba84afb121edab56782dda11634abe72d8c Mon Sep 17 00:00:00 2001 From: Alexander Lingtorp Date: Thu, 6 Oct 2016 19:30:21 +0200 Subject: Minor spelling fix --- tiny_obj_loader.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tiny_obj_loader.h b/tiny_obj_loader.h index 228e819..e4d4a19 100644 --- a/tiny_obj_loader.h +++ b/tiny_obj_loader.h @@ -91,7 +91,7 @@ typedef struct { std::vector stringValues; } tag_t; -// Index struct to support differnt indices for vtx/normal/texcoord. +// Index struct to support different indices for vtx/normal/texcoord. // -1 means not used. typedef struct { int vertex_index; -- cgit v1.2.3 From e81ac971b0d24efcdadbc07c42286c92ccbdb395 Mon Sep 17 00:00:00 2001 From: Chris Liebert Date: Fri, 7 Oct 2016 20:58:04 -0700 Subject: Issue #74: Added basic diffuse texture support to examples/viewer using stb_image. The previously used color based on the normal is combined with the diffuse color from the material definition. --- examples/viewer/stb_image.h | 6755 +++++++++++++++++++++++++++++++++++++++++++ examples/viewer/viewer.cc | 134 +- 2 files changed, 6874 insertions(+), 15 deletions(-) create mode 100644 examples/viewer/stb_image.h diff --git a/examples/viewer/stb_image.h b/examples/viewer/stb_image.h new file mode 100644 index 0000000..a3c1129 --- /dev/null +++ b/examples/viewer/stb_image.h @@ -0,0 +1,6755 @@ +/* stb_image - v2.12 - public domain image loader - http://nothings.org/stb_image.h + no warranty implied; use at your own risk + + Do this: + #define STB_IMAGE_IMPLEMENTATION + before you include this file in *one* C or C++ file to create the implementation. + + // i.e. it should look like this: + #include ... + #include ... + #include ... + #define STB_IMAGE_IMPLEMENTATION + #include "stb_image.h" + + You can #define STBI_ASSERT(x) before the #include to avoid using assert.h. + And #define STBI_MALLOC, STBI_REALLOC, and STBI_FREE to avoid using malloc,realloc,free + + + QUICK NOTES: + Primarily of interest to game developers and other people who can + avoid problematic images and only need the trivial interface + + JPEG baseline & progressive (12 bpc/arithmetic not supported, same as stock IJG lib) + PNG 1/2/4/8-bit-per-channel (16 bpc not supported) + + TGA (not sure what subset, if a subset) + BMP non-1bpp, non-RLE + PSD (composited view only, no extra channels, 8/16 bit-per-channel) + + GIF (*comp always reports as 4-channel) + HDR (radiance rgbE format) + PIC (Softimage PIC) + PNM (PPM and PGM binary only) + + Animated GIF still needs a proper API, but here's one way to do it: + http://gist.github.com/urraka/685d9a6340b26b830d49 + + - decode from memory or through FILE (define STBI_NO_STDIO to remove code) + - decode from arbitrary I/O callbacks + - SIMD acceleration on x86/x64 (SSE2) and ARM (NEON) + + Full documentation under "DOCUMENTATION" below. + + + Revision 2.00 release notes: + + - Progressive JPEG is now supported. + + - PPM and PGM binary formats are now supported, thanks to Ken Miller. + + - x86 platforms now make use of SSE2 SIMD instructions for + JPEG decoding, and ARM platforms can use NEON SIMD if requested. + This work was done by Fabian "ryg" Giesen. SSE2 is used by + default, but NEON must be enabled explicitly; see docs. + + With other JPEG optimizations included in this version, we see + 2x speedup on a JPEG on an x86 machine, and a 1.5x speedup + on a JPEG on an ARM machine, relative to previous versions of this + library. The same results will not obtain for all JPGs and for all + x86/ARM machines. (Note that progressive JPEGs are significantly + slower to decode than regular JPEGs.) This doesn't mean that this + is the fastest JPEG decoder in the land; rather, it brings it + closer to parity with standard libraries. If you want the fastest + decode, look elsewhere. (See "Philosophy" section of docs below.) + + See final bullet items below for more info on SIMD. + + - Added STBI_MALLOC, STBI_REALLOC, and STBI_FREE macros for replacing + the memory allocator. Unlike other STBI libraries, these macros don't + support a context parameter, so if you need to pass a context in to + the allocator, you'll have to store it in a global or a thread-local + variable. + + - Split existing STBI_NO_HDR flag into two flags, STBI_NO_HDR and + STBI_NO_LINEAR. + STBI_NO_HDR: suppress implementation of .hdr reader format + STBI_NO_LINEAR: suppress high-dynamic-range light-linear float API + + - You can suppress implementation of any of the decoders to reduce + your code footprint by #defining one or more of the following + symbols before creating the implementation. + + STBI_NO_JPEG + STBI_NO_PNG + STBI_NO_BMP + STBI_NO_PSD + STBI_NO_TGA + STBI_NO_GIF + STBI_NO_HDR + STBI_NO_PIC + STBI_NO_PNM (.ppm and .pgm) + + - You can request *only* certain decoders and suppress all other ones + (this will be more forward-compatible, as addition of new decoders + doesn't require you to disable them explicitly): + + STBI_ONLY_JPEG + STBI_ONLY_PNG + STBI_ONLY_BMP + STBI_ONLY_PSD + STBI_ONLY_TGA + STBI_ONLY_GIF + STBI_ONLY_HDR + STBI_ONLY_PIC + STBI_ONLY_PNM (.ppm and .pgm) + + Note that you can define multiples of these, and you will get all + of them ("only x" and "only y" is interpreted to mean "only x&y"). + + - If you use STBI_NO_PNG (or _ONLY_ without PNG), and you still + want the zlib decoder to be available, #define STBI_SUPPORT_ZLIB + + - Compilation of all SIMD code can be suppressed with + #define STBI_NO_SIMD + It should not be necessary to disable SIMD unless you have issues + compiling (e.g. using an x86 compiler which doesn't support SSE + intrinsics or that doesn't support the method used to detect + SSE2 support at run-time), and even those can be reported as + bugs so I can refine the built-in compile-time checking to be + smarter. + + - The old STBI_SIMD system which allowed installing a user-defined + IDCT etc. has been removed. If you need this, don't upgrade. My + assumption is that almost nobody was doing this, and those who + were will find the built-in SIMD more satisfactory anyway. + + - RGB values computed for JPEG images are slightly different from + previous versions of stb_image. (This is due to using less + integer precision in SIMD.) The C code has been adjusted so + that the same RGB values will be computed regardless of whether + SIMD support is available, so your app should always produce + consistent results. But these results are slightly different from + previous versions. (Specifically, about 3% of available YCbCr values + will compute different RGB results from pre-1.49 versions by +-1; + most of the deviating values are one smaller in the G channel.) + + - If you must produce consistent results with previous versions of + stb_image, #define STBI_JPEG_OLD and you will get the same results + you used to; however, you will not get the SIMD speedups for + the YCbCr-to-RGB conversion step (although you should still see + significant JPEG speedup from the other changes). + + Please note that STBI_JPEG_OLD is a temporary feature; it will be + removed in future versions of the library. It is only intended for + near-term back-compatibility use. + + + Latest revision history: + 2.12 (2016-04-02) fix typo in 2.11 PSD fix that caused crashes + 2.11 (2016-04-02) 16-bit PNGS; enable SSE2 in non-gcc x64 + RGB-format JPEG; remove white matting in PSD; + allocate large structures on the stack; + correct channel count for PNG & BMP + 2.10 (2016-01-22) avoid warning introduced in 2.09 + 2.09 (2016-01-16) 16-bit TGA; comments in PNM files; STBI_REALLOC_SIZED + 2.08 (2015-09-13) fix to 2.07 cleanup, reading RGB PSD as RGBA + 2.07 (2015-09-13) partial animated GIF support + limited 16-bit PSD support + minor bugs, code cleanup, and compiler warnings + 2.06 (2015-04-19) fix bug where PSD returns wrong '*comp' value + 2.05 (2015-04-19) fix bug in progressive JPEG handling, fix warning + 2.04 (2015-04-15) try to re-enable SIMD on MinGW 64-bit + 2.03 (2015-04-12) additional corruption checking + stbi_set_flip_vertically_on_load + fix NEON support; fix mingw support + 2.02 (2015-01-19) fix incorrect assert, fix warning + 2.01 (2015-01-17) fix various warnings + 2.00b (2014-12-25) fix STBI_MALLOC in progressive JPEG + 2.00 (2014-12-25) optimize JPEG, including x86 SSE2 & ARM NEON SIMD + progressive JPEG + PGM/PPM support + STBI_MALLOC,STBI_REALLOC,STBI_FREE + STBI_NO_*, STBI_ONLY_* + GIF bugfix + + See end of file for full revision history. + + + ============================ Contributors ========================= + + Image formats Extensions, features + Sean Barrett (jpeg, png, bmp) Jetro Lauha (stbi_info) + Nicolas Schulz (hdr, psd) Martin "SpartanJ" Golini (stbi_info) + Jonathan Dummer (tga) James "moose2000" Brown (iPhone PNG) + Jean-Marc Lienher (gif) Ben "Disch" Wenger (io callbacks) + Tom Seddon (pic) Omar Cornut (1/2/4-bit PNG) + Thatcher Ulrich (psd) Nicolas Guillemot (vertical flip) + Ken Miller (pgm, ppm) Richard Mitton (16-bit PSD) + urraka@github (animated gif) Junggon Kim (PNM comments) + Daniel Gibson (16-bit TGA) + + Optimizations & bugfixes + Fabian "ryg" Giesen + Arseny Kapoulkine + + Bug & warning fixes + Marc LeBlanc David Woo Guillaume George Martins Mozeiko + Christpher Lloyd Martin Golini Jerry Jansson Joseph Thomson + Dave Moore Roy Eltham Hayaki Saito Phil Jordan + Won Chun Luke Graham Johan Duparc Nathan Reed + the Horde3D community Thomas Ruf Ronny Chevalier Nick Verigakis + Janez Zemva John Bartholomew Michal Cichon svdijk@github + Jonathan Blow Ken Hamada Tero Hanninen Baldur Karlsson + Laurent Gomila Cort Stratton Sergio Gonzalez romigrou@github + Aruelien Pocheville Thibault Reuille Cass Everitt Matthew Gregan + Ryamond Barbiero Paul Du Bois Engin Manap snagar@github + Michaelangel007@github Oriol Ferrer Mesia socks-the-fox + Blazej Dariusz Roszkowski + + +LICENSE + +This software is dual-licensed to the public domain and under the following +license: you are granted a perpetual, irrevocable license to copy, modify, +publish, and distribute this file as you see fit. + +*/ + +#ifndef STBI_INCLUDE_STB_IMAGE_H +#define STBI_INCLUDE_STB_IMAGE_H + +// DOCUMENTATION +// +// Limitations: +// - no 16-bit-per-channel PNG +// - no 12-bit-per-channel JPEG +// - no JPEGs with arithmetic coding +// - no 1-bit BMP +// - GIF always returns *comp=4 +// +// Basic usage (see HDR discussion below for HDR usage): +// int x,y,n; +// unsigned char *data = stbi_load(filename, &x, &y, &n, 0); +// // ... process data if not NULL ... +// // ... x = width, y = height, n = # 8-bit components per pixel ... +// // ... replace '0' with '1'..'4' to force that many components per pixel +// // ... but 'n' will always be the number that it would have been if you said 0 +// stbi_image_free(data) +// +// Standard parameters: +// int *x -- outputs image width in pixels +// int *y -- outputs image height in pixels +// int *comp -- outputs # of image components in image file +// int req_comp -- if non-zero, # of image components requested in result +// +// The return value from an image loader is an 'unsigned char *' which points +// to the pixel data, or NULL on an allocation failure or if the image is +// corrupt or invalid. The pixel data consists of *y scanlines of *x pixels, +// with each pixel consisting of N interleaved 8-bit components; the first +// pixel pointed to is top-left-most in the image. There is no padding between +// image scanlines or between pixels, regardless of format. The number of +// components N is 'req_comp' if req_comp is non-zero, or *comp otherwise. +// If req_comp is non-zero, *comp has the number of components that _would_ +// have been output otherwise. E.g. if you set req_comp to 4, you will always +// get RGBA output, but you can check *comp to see if it's trivially opaque +// because e.g. there were only 3 channels in the source image. +// +// An output image with N components has the following components interleaved +// in this order in each pixel: +// +// N=#comp components +// 1 grey +// 2 grey, alpha +// 3 red, green, blue +// 4 red, green, blue, alpha +// +// If image loading fails for any reason, the return value will be NULL, +// and *x, *y, *comp will be unchanged. The function stbi_failure_reason() +// can be queried for an extremely brief, end-user unfriendly explanation +// of why the load failed. Define STBI_NO_FAILURE_STRINGS to avoid +// compiling these strings at all, and STBI_FAILURE_USERMSG to get slightly +// more user-friendly ones. +// +// Paletted PNG, BMP, GIF, and PIC images are automatically depalettized. +// +// =========================================================================== +// +// Philosophy +// +// stb libraries are designed with the following priorities: +// +// 1. easy to use +// 2. easy to maintain +// 3. good performance +// +// Sometimes I let "good performance" creep up in priority over "easy to maintain", +// and for best performance I may provide less-easy-to-use APIs that give higher +// performance, in addition to the easy to use ones. Nevertheless, it's important +// to keep in mind that from the standpoint of you, a client of this library, +// all you care about is #1 and #3, and stb libraries do not emphasize #3 above all. +// +// Some secondary priorities arise directly from the first two, some of which +// make more explicit reasons why performance can't be emphasized. +// +// - Portable ("ease of use") +// - Small footprint ("easy to maintain") +// - No dependencies ("ease of use") +// +// =========================================================================== +// +// I/O callbacks +// +// I/O callbacks allow you to read from arbitrary sources, like packaged +// files or some other source. Data read from callbacks are processed +// through a small internal buffer (currently 128 bytes) to try to reduce +// overhead. +// +// The three functions you must define are "read" (reads some bytes of data), +// "skip" (skips some bytes of data), "eof" (reports if the stream is at the end). +// +// =========================================================================== +// +// SIMD support +// +// The JPEG decoder will try to automatically use SIMD kernels on x86 when +// supported by the compiler. For ARM Neon support, you must explicitly +// request it. +// +// (The old do-it-yourself SIMD API is no longer supported in the current +// code.) +// +// On x86, SSE2 will automatically be used when available based on a run-time +// test; if not, the generic C versions are used as a fall-back. On ARM targets, +// the typical path is to have separate builds for NEON and non-NEON devices +// (at least this is true for iOS and Android). Therefore, the NEON support is +// toggled by a build flag: define STBI_NEON to get NEON loops. +// +// The output of the JPEG decoder is slightly different from versions where +// SIMD support was introduced (that is, for versions before 1.49). The +// difference is only +-1 in the 8-bit RGB channels, and only on a small +// fraction of pixels. You can force the pre-1.49 behavior by defining +// STBI_JPEG_OLD, but this will disable some of the SIMD decoding path +// and hence cost some performance. +// +// If for some reason you do not want to use any of SIMD code, or if +// you have issues compiling it, you can disable it entirely by +// defining STBI_NO_SIMD. +// +// =========================================================================== +// +// HDR image support (disable by defining STBI_NO_HDR) +// +// stb_image now supports loading HDR images in general, and currently +// the Radiance .HDR file format, although the support is provided +// generically. You can still load any file through the existing interface; +// if you attempt to load an HDR file, it will be automatically remapped to +// LDR, assuming gamma 2.2 and an arbitrary scale factor defaulting to 1; +// both of these constants can be reconfigured through this interface: +// +// stbi_hdr_to_ldr_gamma(2.2f); +// stbi_hdr_to_ldr_scale(1.0f); +// +// (note, do not use _inverse_ constants; stbi_image will invert them +// appropriately). +// +// Additionally, there is a new, parallel interface for loading files as +// (linear) floats to preserve the full dynamic range: +// +// float *data = stbi_loadf(filename, &x, &y, &n, 0); +// +// If you load LDR images through this interface, those images will +// be promoted to floating point values, run through the inverse of +// constants corresponding to the above: +// +// stbi_ldr_to_hdr_scale(1.0f); +// stbi_ldr_to_hdr_gamma(2.2f); +// +// Finally, given a filename (or an open file or memory block--see header +// file for details) containing image data, you can query for the "most +// appropriate" interface to use (that is, whether the image is HDR or +// not), using: +// +// stbi_is_hdr(char *filename); +// +// =========================================================================== +// +// iPhone PNG support: +// +// By default we convert iphone-formatted PNGs back to RGB, even though +// they are internally encoded differently. You can disable this conversion +// by by calling stbi_convert_iphone_png_to_rgb(0), in which case +// you will always just get the native iphone "format" through (which +// is BGR stored in RGB). +// +// Call stbi_set_unpremultiply_on_load(1) as well to force a divide per +// pixel to remove any premultiplied alpha *only* if the image file explicitly +// says there's premultiplied data (currently only happens in iPhone images, +// and only if iPhone convert-to-rgb processing is on). +// + + +#ifndef STBI_NO_STDIO +#include +#endif // STBI_NO_STDIO + +#define STBI_VERSION 1 + +enum +{ + STBI_default = 0, // only used for req_comp + + STBI_grey = 1, + STBI_grey_alpha = 2, + STBI_rgb = 3, + STBI_rgb_alpha = 4 +}; + +typedef unsigned char stbi_uc; + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef STB_IMAGE_STATIC +#define STBIDEF static +#else +#define STBIDEF extern +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// PRIMARY API - works on images of any type +// + +// +// load image by filename, open file, or memory buffer +// + +typedef struct +{ + int (*read) (void *user,char *data,int size); // fill 'data' with 'size' bytes. return number of bytes actually read + void (*skip) (void *user,int n); // skip the next 'n' bytes, or 'unget' the last -n bytes if negative + int (*eof) (void *user); // returns nonzero if we are at end of file/data +} stbi_io_callbacks; + +STBIDEF stbi_uc *stbi_load (char const *filename, int *x, int *y, int *comp, int req_comp); +STBIDEF stbi_uc *stbi_load_from_memory (stbi_uc const *buffer, int len , int *x, int *y, int *comp, int req_comp); +STBIDEF stbi_uc *stbi_load_from_callbacks(stbi_io_callbacks const *clbk , void *user, int *x, int *y, int *comp, int req_comp); + +#ifndef STBI_NO_STDIO +STBIDEF stbi_uc *stbi_load_from_file (FILE *f, int *x, int *y, int *comp, int req_comp); +// for stbi_load_from_file, file pointer is left pointing immediately after image +#endif + +#ifndef STBI_NO_LINEAR + STBIDEF float *stbi_loadf (char const *filename, int *x, int *y, int *comp, int req_comp); + STBIDEF float *stbi_loadf_from_memory (stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp); + STBIDEF float *stbi_loadf_from_callbacks (stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp, int req_comp); + + #ifndef STBI_NO_STDIO + STBIDEF float *stbi_loadf_from_file (FILE *f, int *x, int *y, int *comp, int req_comp); + #endif +#endif + +#ifndef STBI_NO_HDR + STBIDEF void stbi_hdr_to_ldr_gamma(float gamma); + STBIDEF void stbi_hdr_to_ldr_scale(float scale); +#endif // STBI_NO_HDR + +#ifndef STBI_NO_LINEAR + STBIDEF void stbi_ldr_to_hdr_gamma(float gamma); + STBIDEF void stbi_ldr_to_hdr_scale(float scale); +#endif // STBI_NO_LINEAR + +// stbi_is_hdr is always defined, but always returns false if STBI_NO_HDR +STBIDEF int stbi_is_hdr_from_callbacks(stbi_io_callbacks const *clbk, void *user); +STBIDEF int stbi_is_hdr_from_memory(stbi_uc const *buffer, int len); +#ifndef STBI_NO_STDIO +STBIDEF int stbi_is_hdr (char const *filename); +STBIDEF int stbi_is_hdr_from_file(FILE *f); +#endif // STBI_NO_STDIO + + +// get a VERY brief reason for failure +// NOT THREADSAFE +STBIDEF const char *stbi_failure_reason (void); + +// free the loaded image -- this is just free() +STBIDEF void stbi_image_free (void *retval_from_stbi_load); + +// get image dimensions & components without fully decoding +STBIDEF int stbi_info_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp); +STBIDEF int stbi_info_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp); + +#ifndef STBI_NO_STDIO +STBIDEF int stbi_info (char const *filename, int *x, int *y, int *comp); +STBIDEF int stbi_info_from_file (FILE *f, int *x, int *y, int *comp); + +#endif + + + +// for image formats that explicitly notate that they have premultiplied alpha, +// we just return the colors as stored in the file. set this flag to force +// unpremultiplication. results are undefined if the unpremultiply overflow. +STBIDEF void stbi_set_unpremultiply_on_load(int flag_true_if_should_unpremultiply); + +// indicate whether we should process iphone images back to canonical format, +// or just pass them through "as-is" +STBIDEF void stbi_convert_iphone_png_to_rgb(int flag_true_if_should_convert); + +// flip the image vertically, so the first pixel in the output array is the bottom left +STBIDEF void stbi_set_flip_vertically_on_load(int flag_true_if_should_flip); + +// ZLIB client - used by PNG, available for other purposes + +STBIDEF char *stbi_zlib_decode_malloc_guesssize(const char *buffer, int len, int initial_size, int *outlen); +STBIDEF char *stbi_zlib_decode_malloc_guesssize_headerflag(const char *buffer, int len, int initial_size, int *outlen, int parse_header); +STBIDEF char *stbi_zlib_decode_malloc(const char *buffer, int len, int *outlen); +STBIDEF int stbi_zlib_decode_buffer(char *obuffer, int olen, const char *ibuffer, int ilen); + +STBIDEF char *stbi_zlib_decode_noheader_malloc(const char *buffer, int len, int *outlen); +STBIDEF int stbi_zlib_decode_noheader_buffer(char *obuffer, int olen, const char *ibuffer, int ilen); + + +#ifdef __cplusplus +} +#endif + +// +// +//// end header file ///////////////////////////////////////////////////// +#endif // STBI_INCLUDE_STB_IMAGE_H + +#ifdef STB_IMAGE_IMPLEMENTATION + +#if defined(STBI_ONLY_JPEG) || defined(STBI_ONLY_PNG) || defined(STBI_ONLY_BMP) \ + || defined(STBI_ONLY_TGA) || defined(STBI_ONLY_GIF) || defined(STBI_ONLY_PSD) \ + || defined(STBI_ONLY_HDR) || defined(STBI_ONLY_PIC) || defined(STBI_ONLY_PNM) \ + || defined(STBI_ONLY_ZLIB) + #ifndef STBI_ONLY_JPEG + #define STBI_NO_JPEG + #endif + #ifndef STBI_ONLY_PNG + #define STBI_NO_PNG + #endif + #ifndef STBI_ONLY_BMP + #define STBI_NO_BMP + #endif + #ifndef STBI_ONLY_PSD + #define STBI_NO_PSD + #endif + #ifndef STBI_ONLY_TGA + #define STBI_NO_TGA + #endif + #ifndef STBI_ONLY_GIF + #define STBI_NO_GIF + #endif + #ifndef STBI_ONLY_HDR + #define STBI_NO_HDR + #endif + #ifndef STBI_ONLY_PIC + #define STBI_NO_PIC + #endif + #ifndef STBI_ONLY_PNM + #define STBI_NO_PNM + #endif +#endif + +#if defined(STBI_NO_PNG) && !defined(STBI_SUPPORT_ZLIB) && !defined(STBI_NO_ZLIB) +#define STBI_NO_ZLIB +#endif + + +#include +#include // ptrdiff_t on osx +#include +#include + +#if !defined(STBI_NO_LINEAR) || !defined(STBI_NO_HDR) +#include // ldexp +#endif + +#ifndef STBI_NO_STDIO +#include +#endif + +#ifndef STBI_ASSERT +#include +#define STBI_ASSERT(x) assert(x) +#endif + + +#ifndef _MSC_VER + #ifdef __cplusplus + #define stbi_inline inline + #else + #define stbi_inline + #endif +#else + #define stbi_inline __forceinline +#endif + + +#ifdef _MSC_VER +typedef unsigned short stbi__uint16; +typedef signed short stbi__int16; +typedef unsigned int stbi__uint32; +typedef signed int stbi__int32; +#else +#include +typedef uint16_t stbi__uint16; +typedef int16_t stbi__int16; +typedef uint32_t stbi__uint32; +typedef int32_t stbi__int32; +#endif + +// should produce compiler error if size is wrong +typedef unsigned char validate_uint32[sizeof(stbi__uint32)==4 ? 1 : -1]; + +#ifdef _MSC_VER +#define STBI_NOTUSED(v) (void)(v) +#else +#define STBI_NOTUSED(v) (void)sizeof(v) +#endif + +#ifdef _MSC_VER +#define STBI_HAS_LROTL +#endif + +#ifdef STBI_HAS_LROTL + #define stbi_lrot(x,y) _lrotl(x,y) +#else + #define stbi_lrot(x,y) (((x) << (y)) | ((x) >> (32 - (y)))) +#endif + +#if defined(STBI_MALLOC) && defined(STBI_FREE) && (defined(STBI_REALLOC) || defined(STBI_REALLOC_SIZED)) +// ok +#elif !defined(STBI_MALLOC) && !defined(STBI_FREE) && !defined(STBI_REALLOC) && !defined(STBI_REALLOC_SIZED) +// ok +#else +#error "Must define all or none of STBI_MALLOC, STBI_FREE, and STBI_REALLOC (or STBI_REALLOC_SIZED)." +#endif + +#ifndef STBI_MALLOC +#define STBI_MALLOC(sz) malloc(sz) +#define STBI_REALLOC(p,newsz) realloc(p,newsz) +#define STBI_FREE(p) free(p) +#endif + +#ifndef STBI_REALLOC_SIZED +#define STBI_REALLOC_SIZED(p,oldsz,newsz) STBI_REALLOC(p,newsz) +#endif + +// x86/x64 detection +#if defined(__x86_64__) || defined(_M_X64) +#define STBI__X64_TARGET +#elif defined(__i386) || defined(_M_IX86) +#define STBI__X86_TARGET +#endif + +#if defined(__GNUC__) && (defined(STBI__X86_TARGET) || defined(STBI__X64_TARGET)) && !defined(__SSE2__) && !defined(STBI_NO_SIMD) +// NOTE: not clear do we actually need this for the 64-bit path? +// gcc doesn't support sse2 intrinsics unless you compile with -msse2, +// (but compiling with -msse2 allows the compiler to use SSE2 everywhere; +// this is just broken and gcc are jerks for not fixing it properly +// http://www.virtualdub.org/blog/pivot/entry.php?id=363 ) +#define STBI_NO_SIMD +#endif + +#if defined(__MINGW32__) && defined(STBI__X86_TARGET) && !defined(STBI_MINGW_ENABLE_SSE2) && !defined(STBI_NO_SIMD) +// Note that __MINGW32__ doesn't actually mean 32-bit, so we have to avoid STBI__X64_TARGET +// +// 32-bit MinGW wants ESP to be 16-byte aligned, but this is not in the +// Windows ABI and VC++ as well as Windows DLLs don't maintain that invariant. +// As a result, enabling SSE2 on 32-bit MinGW is dangerous when not +// simultaneously enabling "-mstackrealign". +// +// See https://github.com/nothings/stb/issues/81 for more information. +// +// So default to no SSE2 on 32-bit MinGW. If you've read this far and added +// -mstackrealign to your build settings, feel free to #define STBI_MINGW_ENABLE_SSE2. +#define STBI_NO_SIMD +#endif + +#if !defined(STBI_NO_SIMD) && (defined(STBI__X86_TARGET) || defined(STBI__X64_TARGET)) +#define STBI_SSE2 +#include + +#ifdef _MSC_VER + +#if _MSC_VER >= 1400 // not VC6 +#include // __cpuid +static int stbi__cpuid3(void) +{ + int info[4]; + __cpuid(info,1); + return info[3]; +} +#else +static int stbi__cpuid3(void) +{ + int res; + __asm { + mov eax,1 + cpuid + mov res,edx + } + return res; +} +#endif + +#define STBI_SIMD_ALIGN(type, name) __declspec(align(16)) type name + +static int stbi__sse2_available() +{ + int info3 = stbi__cpuid3(); + return ((info3 >> 26) & 1) != 0; +} +#else // assume GCC-style if not VC++ +#define STBI_SIMD_ALIGN(type, name) type name __attribute__((aligned(16))) + +static int stbi__sse2_available() +{ +#if defined(__GNUC__) && (__GNUC__ * 100 + __GNUC_MINOR__) >= 408 // GCC 4.8 or later + // GCC 4.8+ has a nice way to do this + return __builtin_cpu_supports("sse2"); +#else + // portable way to do this, preferably without using GCC inline ASM? + // just bail for now. + return 0; +#endif +} +#endif +#endif + +// ARM NEON +#if defined(STBI_NO_SIMD) && defined(STBI_NEON) +#undef STBI_NEON +#endif + +#ifdef STBI_NEON +#include +// assume GCC or Clang on ARM targets +#define STBI_SIMD_ALIGN(type, name) type name __attribute__((aligned(16))) +#endif + +#ifndef STBI_SIMD_ALIGN +#define STBI_SIMD_ALIGN(type, name) type name +#endif + +/////////////////////////////////////////////// +// +// stbi__context struct and start_xxx functions + +// stbi__context structure is our basic context used by all images, so it +// contains all the IO context, plus some basic image information +typedef struct +{ + stbi__uint32 img_x, img_y; + int img_n, img_out_n; + + stbi_io_callbacks io; + void *io_user_data; + + int read_from_callbacks; + int buflen; + stbi_uc buffer_start[128]; + + stbi_uc *img_buffer, *img_buffer_end; + stbi_uc *img_buffer_original, *img_buffer_original_end; +} stbi__context; + + +static void stbi__refill_buffer(stbi__context *s); + +// initialize a memory-decode context +static void stbi__start_mem(stbi__context *s, stbi_uc const *buffer, int len) +{ + s->io.read = NULL; + s->read_from_callbacks = 0; + s->img_buffer = s->img_buffer_original = (stbi_uc *) buffer; + s->img_buffer_end = s->img_buffer_original_end = (stbi_uc *) buffer+len; +} + +// initialize a callback-based context +static void stbi__start_callbacks(stbi__context *s, stbi_io_callbacks *c, void *user) +{ + s->io = *c; + s->io_user_data = user; + s->buflen = sizeof(s->buffer_start); + s->read_from_callbacks = 1; + s->img_buffer_original = s->buffer_start; + stbi__refill_buffer(s); + s->img_buffer_original_end = s->img_buffer_end; +} + +#ifndef STBI_NO_STDIO + +static int stbi__stdio_read(void *user, char *data, int size) +{ + return (int) fread(data,1,size,(FILE*) user); +} + +static void stbi__stdio_skip(void *user, int n) +{ + fseek((FILE*) user, n, SEEK_CUR); +} + +static int stbi__stdio_eof(void *user) +{ + return feof((FILE*) user); +} + +static stbi_io_callbacks stbi__stdio_callbacks = +{ + stbi__stdio_read, + stbi__stdio_skip, + stbi__stdio_eof, +}; + +static void stbi__start_file(stbi__context *s, FILE *f) +{ + stbi__start_callbacks(s, &stbi__stdio_callbacks, (void *) f); +} + +//static void stop_file(stbi__context *s) { } + +#endif // !STBI_NO_STDIO + +static void stbi__rewind(stbi__context *s) +{ + // conceptually rewind SHOULD rewind to the beginning of the stream, + // but we just rewind to the beginning of the initial buffer, because + // we only use it after doing 'test', which only ever looks at at most 92 bytes + s->img_buffer = s->img_buffer_original; + s->img_buffer_end = s->img_buffer_original_end; +} + +#ifndef STBI_NO_JPEG +static int stbi__jpeg_test(stbi__context *s); +static stbi_uc *stbi__jpeg_load(stbi__context *s, int *x, int *y, int *comp, int req_comp); +static int stbi__jpeg_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_PNG +static int stbi__png_test(stbi__context *s); +static stbi_uc *stbi__png_load(stbi__context *s, int *x, int *y, int *comp, int req_comp); +static int stbi__png_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_BMP +static int stbi__bmp_test(stbi__context *s); +static stbi_uc *stbi__bmp_load(stbi__context *s, int *x, int *y, int *comp, int req_comp); +static int stbi__bmp_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_TGA +static int stbi__tga_test(stbi__context *s); +static stbi_uc *stbi__tga_load(stbi__context *s, int *x, int *y, int *comp, int req_comp); +static int stbi__tga_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_PSD +static int stbi__psd_test(stbi__context *s); +static stbi_uc *stbi__psd_load(stbi__context *s, int *x, int *y, int *comp, int req_comp); +static int stbi__psd_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_HDR +static int stbi__hdr_test(stbi__context *s); +static float *stbi__hdr_load(stbi__context *s, int *x, int *y, int *comp, int req_comp); +static int stbi__hdr_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_PIC +static int stbi__pic_test(stbi__context *s); +static stbi_uc *stbi__pic_load(stbi__context *s, int *x, int *y, int *comp, int req_comp); +static int stbi__pic_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_GIF +static int stbi__gif_test(stbi__context *s); +static stbi_uc *stbi__gif_load(stbi__context *s, int *x, int *y, int *comp, int req_comp); +static int stbi__gif_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_PNM +static int stbi__pnm_test(stbi__context *s); +static stbi_uc *stbi__pnm_load(stbi__context *s, int *x, int *y, int *comp, int req_comp); +static int stbi__pnm_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +// this is not threadsafe +static const char *stbi__g_failure_reason; + +STBIDEF const char *stbi_failure_reason(void) +{ + return stbi__g_failure_reason; +} + +static int stbi__err(const char *str) +{ + stbi__g_failure_reason = str; + return 0; +} + +static void *stbi__malloc(size_t size) +{ + return STBI_MALLOC(size); +} + +// stbi__err - error +// stbi__errpf - error returning pointer to float +// stbi__errpuc - error returning pointer to unsigned char + +#ifdef STBI_NO_FAILURE_STRINGS + #define stbi__err(x,y) 0 +#elif defined(STBI_FAILURE_USERMSG) + #define stbi__err(x,y) stbi__err(y) +#else + #define stbi__err(x,y) stbi__err(x) +#endif + +#define stbi__errpf(x,y) ((float *)(size_t) (stbi__err(x,y)?NULL:NULL)) +#define stbi__errpuc(x,y) ((unsigned char *)(size_t) (stbi__err(x,y)?NULL:NULL)) + +STBIDEF void stbi_image_free(void *retval_from_stbi_load) +{ + STBI_FREE(retval_from_stbi_load); +} + +#ifndef STBI_NO_LINEAR +static float *stbi__ldr_to_hdr(stbi_uc *data, int x, int y, int comp); +#endif + +#ifndef STBI_NO_HDR +static stbi_uc *stbi__hdr_to_ldr(float *data, int x, int y, int comp); +#endif + +static int stbi__vertically_flip_on_load = 0; + +STBIDEF void stbi_set_flip_vertically_on_load(int flag_true_if_should_flip) +{ + stbi__vertically_flip_on_load = flag_true_if_should_flip; +} + +static unsigned char *stbi__load_main(stbi__context *s, int *x, int *y, int *comp, int req_comp) +{ + #ifndef STBI_NO_JPEG + if (stbi__jpeg_test(s)) return stbi__jpeg_load(s,x,y,comp,req_comp); + #endif + #ifndef STBI_NO_PNG + if (stbi__png_test(s)) return stbi__png_load(s,x,y,comp,req_comp); + #endif + #ifndef STBI_NO_BMP + if (stbi__bmp_test(s)) return stbi__bmp_load(s,x,y,comp,req_comp); + #endif + #ifndef STBI_NO_GIF + if (stbi__gif_test(s)) return stbi__gif_load(s,x,y,comp,req_comp); + #endif + #ifndef STBI_NO_PSD + if (stbi__psd_test(s)) return stbi__psd_load(s,x,y,comp,req_comp); + #endif + #ifndef STBI_NO_PIC + if (stbi__pic_test(s)) return stbi__pic_load(s,x,y,comp,req_comp); + #endif + #ifndef STBI_NO_PNM + if (stbi__pnm_test(s)) return stbi__pnm_load(s,x,y,comp,req_comp); + #endif + + #ifndef STBI_NO_HDR + if (stbi__hdr_test(s)) { + float *hdr = stbi__hdr_load(s, x,y,comp,req_comp); + return stbi__hdr_to_ldr(hdr, *x, *y, req_comp ? req_comp : *comp); + } + #endif + + #ifndef STBI_NO_TGA + // test tga last because it's a crappy test! + if (stbi__tga_test(s)) + return stbi__tga_load(s,x,y,comp,req_comp); + #endif + + return stbi__errpuc("unknown image type", "Image not of any known type, or corrupt"); +} + +static unsigned char *stbi__load_flip(stbi__context *s, int *x, int *y, int *comp, int req_comp) +{ + unsigned char *result = stbi__load_main(s, x, y, comp, req_comp); + + if (stbi__vertically_flip_on_load && result != NULL) { + int w = *x, h = *y; + int depth = req_comp ? req_comp : *comp; + int row,col,z; + stbi_uc temp; + + // @OPTIMIZE: use a bigger temp buffer and memcpy multiple pixels at once + for (row = 0; row < (h>>1); row++) { + for (col = 0; col < w; col++) { + for (z = 0; z < depth; z++) { + temp = result[(row * w + col) * depth + z]; + result[(row * w + col) * depth + z] = result[((h - row - 1) * w + col) * depth + z]; + result[((h - row - 1) * w + col) * depth + z] = temp; + } + } + } + } + + return result; +} + +#ifndef STBI_NO_HDR +static void stbi__float_postprocess(float *result, int *x, int *y, int *comp, int req_comp) +{ + if (stbi__vertically_flip_on_load && result != NULL) { + int w = *x, h = *y; + int depth = req_comp ? req_comp : *comp; + int row,col,z; + float temp; + + // @OPTIMIZE: use a bigger temp buffer and memcpy multiple pixels at once + for (row = 0; row < (h>>1); row++) { + for (col = 0; col < w; col++) { + for (z = 0; z < depth; z++) { + temp = result[(row * w + col) * depth + z]; + result[(row * w + col) * depth + z] = result[((h - row - 1) * w + col) * depth + z]; + result[((h - row - 1) * w + col) * depth + z] = temp; + } + } + } + } +} +#endif + +#ifndef STBI_NO_STDIO + +static FILE *stbi__fopen(char const *filename, char const *mode) +{ + FILE *f; +#if defined(_MSC_VER) && _MSC_VER >= 1400 + if (0 != fopen_s(&f, filename, mode)) + f=0; +#else + f = fopen(filename, mode); +#endif + return f; +} + + +STBIDEF stbi_uc *stbi_load(char const *filename, int *x, int *y, int *comp, int req_comp) +{ + FILE *f = stbi__fopen(filename, "rb"); + unsigned char *result; + if (!f) return stbi__errpuc("can't fopen", "Unable to open file"); + result = stbi_load_from_file(f,x,y,comp,req_comp); + fclose(f); + return result; +} + +STBIDEF stbi_uc *stbi_load_from_file(FILE *f, int *x, int *y, int *comp, int req_comp) +{ + unsigned char *result; + stbi__context s; + stbi__start_file(&s,f); + result = stbi__load_flip(&s,x,y,comp,req_comp); + if (result) { + // need to 'unget' all the characters in the IO buffer + fseek(f, - (int) (s.img_buffer_end - s.img_buffer), SEEK_CUR); + } + return result; +} +#endif //!STBI_NO_STDIO + +STBIDEF stbi_uc *stbi_load_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp) +{ + stbi__context s; + stbi__start_mem(&s,buffer,len); + return stbi__load_flip(&s,x,y,comp,req_comp); +} + +STBIDEF stbi_uc *stbi_load_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp, int req_comp) +{ + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user); + return stbi__load_flip(&s,x,y,comp,req_comp); +} + +#ifndef STBI_NO_LINEAR +static float *stbi__loadf_main(stbi__context *s, int *x, int *y, int *comp, int req_comp) +{ + unsigned char *data; + #ifndef STBI_NO_HDR + if (stbi__hdr_test(s)) { + float *hdr_data = stbi__hdr_load(s,x,y,comp,req_comp); + if (hdr_data) + stbi__float_postprocess(hdr_data,x,y,comp,req_comp); + return hdr_data; + } + #endif + data = stbi__load_flip(s, x, y, comp, req_comp); + if (data) + return stbi__ldr_to_hdr(data, *x, *y, req_comp ? req_comp : *comp); + return stbi__errpf("unknown image type", "Image not of any known type, or corrupt"); +} + +STBIDEF float *stbi_loadf_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp) +{ + stbi__context s; + stbi__start_mem(&s,buffer,len); + return stbi__loadf_main(&s,x,y,comp,req_comp); +} + +STBIDEF float *stbi_loadf_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp, int req_comp) +{ + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user); + return stbi__loadf_main(&s,x,y,comp,req_comp); +} + +#ifndef STBI_NO_STDIO +STBIDEF float *stbi_loadf(char const *filename, int *x, int *y, int *comp, int req_comp) +{ + float *result; + FILE *f = stbi__fopen(filename, "rb"); + if (!f) return stbi__errpf("can't fopen", "Unable to open file"); + result = stbi_loadf_from_file(f,x,y,comp,req_comp); + fclose(f); + return result; +} + +STBIDEF float *stbi_loadf_from_file(FILE *f, int *x, int *y, int *comp, int req_comp) +{ + stbi__context s; + stbi__start_file(&s,f); + return stbi__loadf_main(&s,x,y,comp,req_comp); +} +#endif // !STBI_NO_STDIO + +#endif // !STBI_NO_LINEAR + +// these is-hdr-or-not is defined independent of whether STBI_NO_LINEAR is +// defined, for API simplicity; if STBI_NO_LINEAR is defined, it always +// reports false! + +STBIDEF int stbi_is_hdr_from_memory(stbi_uc const *buffer, int len) +{ + #ifndef STBI_NO_HDR + stbi__context s; + stbi__start_mem(&s,buffer,len); + return stbi__hdr_test(&s); + #else + STBI_NOTUSED(buffer); + STBI_NOTUSED(len); + return 0; + #endif +} + +#ifndef STBI_NO_STDIO +STBIDEF int stbi_is_hdr (char const *filename) +{ + FILE *f = stbi__fopen(filename, "rb"); + int result=0; + if (f) { + result = stbi_is_hdr_from_file(f); + fclose(f); + } + return result; +} + +STBIDEF int stbi_is_hdr_from_file(FILE *f) +{ + #ifndef STBI_NO_HDR + stbi__context s; + stbi__start_file(&s,f); + return stbi__hdr_test(&s); + #else + STBI_NOTUSED(f); + return 0; + #endif +} +#endif // !STBI_NO_STDIO + +STBIDEF int stbi_is_hdr_from_callbacks(stbi_io_callbacks const *clbk, void *user) +{ + #ifndef STBI_NO_HDR + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user); + return stbi__hdr_test(&s); + #else + STBI_NOTUSED(clbk); + STBI_NOTUSED(user); + return 0; + #endif +} + +#ifndef STBI_NO_LINEAR +static float stbi__l2h_gamma=2.2f, stbi__l2h_scale=1.0f; + +STBIDEF void stbi_ldr_to_hdr_gamma(float gamma) { stbi__l2h_gamma = gamma; } +STBIDEF void stbi_ldr_to_hdr_scale(float scale) { stbi__l2h_scale = scale; } +#endif + +static float stbi__h2l_gamma_i=1.0f/2.2f, stbi__h2l_scale_i=1.0f; + +STBIDEF void stbi_hdr_to_ldr_gamma(float gamma) { stbi__h2l_gamma_i = 1/gamma; } +STBIDEF void stbi_hdr_to_ldr_scale(float scale) { stbi__h2l_scale_i = 1/scale; } + + +////////////////////////////////////////////////////////////////////////////// +// +// Common code used by all image loaders +// + +enum +{ + STBI__SCAN_load=0, + STBI__SCAN_type, + STBI__SCAN_header +}; + +static void stbi__refill_buffer(stbi__context *s) +{ + int n = (s->io.read)(s->io_user_data,(char*)s->buffer_start,s->buflen); + if (n == 0) { + // at end of file, treat same as if from memory, but need to handle case + // where s->img_buffer isn't pointing to safe memory, e.g. 0-byte file + s->read_from_callbacks = 0; + s->img_buffer = s->buffer_start; + s->img_buffer_end = s->buffer_start+1; + *s->img_buffer = 0; + } else { + s->img_buffer = s->buffer_start; + s->img_buffer_end = s->buffer_start + n; + } +} + +stbi_inline static stbi_uc stbi__get8(stbi__context *s) +{ + if (s->img_buffer < s->img_buffer_end) + return *s->img_buffer++; + if (s->read_from_callbacks) { + stbi__refill_buffer(s); + return *s->img_buffer++; + } + return 0; +} + +stbi_inline static int stbi__at_eof(stbi__context *s) +{ + if (s->io.read) { + if (!(s->io.eof)(s->io_user_data)) return 0; + // if feof() is true, check if buffer = end + // special case: we've only got the special 0 character at the end + if (s->read_from_callbacks == 0) return 1; + } + + return s->img_buffer >= s->img_buffer_end; +} + +static void stbi__skip(stbi__context *s, int n) +{ + if (n < 0) { + s->img_buffer = s->img_buffer_end; + return; + } + if (s->io.read) { + int blen = (int) (s->img_buffer_end - s->img_buffer); + if (blen < n) { + s->img_buffer = s->img_buffer_end; + (s->io.skip)(s->io_user_data, n - blen); + return; + } + } + s->img_buffer += n; +} + +static int stbi__getn(stbi__context *s, stbi_uc *buffer, int n) +{ + if (s->io.read) { + int blen = (int) (s->img_buffer_end - s->img_buffer); + if (blen < n) { + int res, count; + + memcpy(buffer, s->img_buffer, blen); + + count = (s->io.read)(s->io_user_data, (char*) buffer + blen, n - blen); + res = (count == (n-blen)); + s->img_buffer = s->img_buffer_end; + return res; + } + } + + if (s->img_buffer+n <= s->img_buffer_end) { + memcpy(buffer, s->img_buffer, n); + s->img_buffer += n; + return 1; + } else + return 0; +} + +static int stbi__get16be(stbi__context *s) +{ + int z = stbi__get8(s); + return (z << 8) + stbi__get8(s); +} + +static stbi__uint32 stbi__get32be(stbi__context *s) +{ + stbi__uint32 z = stbi__get16be(s); + return (z << 16) + stbi__get16be(s); +} + +#if defined(STBI_NO_BMP) && defined(STBI_NO_TGA) && defined(STBI_NO_GIF) +// nothing +#else +static int stbi__get16le(stbi__context *s) +{ + int z = stbi__get8(s); + return z + (stbi__get8(s) << 8); +} +#endif + +#ifndef STBI_NO_BMP +static stbi__uint32 stbi__get32le(stbi__context *s) +{ + stbi__uint32 z = stbi__get16le(s); + return z + (stbi__get16le(s) << 16); +} +#endif + +#define STBI__BYTECAST(x) ((stbi_uc) ((x) & 255)) // truncate int to byte without warnings + + +////////////////////////////////////////////////////////////////////////////// +// +// generic converter from built-in img_n to req_comp +// individual types do this automatically as much as possible (e.g. jpeg +// does all cases internally since it needs to colorspace convert anyway, +// and it never has alpha, so very few cases ). png can automatically +// interleave an alpha=255 channel, but falls back to this for other cases +// +// assume data buffer is malloced, so malloc a new one and free that one +// only failure mode is malloc failing + +static stbi_uc stbi__compute_y(int r, int g, int b) +{ + return (stbi_uc) (((r*77) + (g*150) + (29*b)) >> 8); +} + +static unsigned char *stbi__convert_format(unsigned char *data, int img_n, int req_comp, unsigned int x, unsigned int y) +{ + int i,j; + unsigned char *good; + + if (req_comp == img_n) return data; + STBI_ASSERT(req_comp >= 1 && req_comp <= 4); + + good = (unsigned char *) stbi__malloc(req_comp * x * y); + if (good == NULL) { + STBI_FREE(data); + return stbi__errpuc("outofmem", "Out of memory"); + } + + for (j=0; j < (int) y; ++j) { + unsigned char *src = data + j * x * img_n ; + unsigned char *dest = good + j * x * req_comp; + + #define COMBO(a,b) ((a)*8+(b)) + #define CASE(a,b) case COMBO(a,b): for(i=x-1; i >= 0; --i, src += a, dest += b) + // convert source image with img_n components to one with req_comp components; + // avoid switch per pixel, so use switch per scanline and massive macros + switch (COMBO(img_n, req_comp)) { + CASE(1,2) dest[0]=src[0], dest[1]=255; break; + CASE(1,3) dest[0]=dest[1]=dest[2]=src[0]; break; + CASE(1,4) dest[0]=dest[1]=dest[2]=src[0], dest[3]=255; break; + CASE(2,1) dest[0]=src[0]; break; + CASE(2,3) dest[0]=dest[1]=dest[2]=src[0]; break; + CASE(2,4) dest[0]=dest[1]=dest[2]=src[0], dest[3]=src[1]; break; + CASE(3,4) dest[0]=src[0],dest[1]=src[1],dest[2]=src[2],dest[3]=255; break; + CASE(3,1) dest[0]=stbi__compute_y(src[0],src[1],src[2]); break; + CASE(3,2) dest[0]=stbi__compute_y(src[0],src[1],src[2]), dest[1] = 255; break; + CASE(4,1) dest[0]=stbi__compute_y(src[0],src[1],src[2]); break; + CASE(4,2) dest[0]=stbi__compute_y(src[0],src[1],src[2]), dest[1] = src[3]; break; + CASE(4,3) dest[0]=src[0],dest[1]=src[1],dest[2]=src[2]; break; + default: STBI_ASSERT(0); + } + #undef CASE + } + + STBI_FREE(data); + return good; +} + +#ifndef STBI_NO_LINEAR +static float *stbi__ldr_to_hdr(stbi_uc *data, int x, int y, int comp) +{ + int i,k,n; + float *output = (float *) stbi__malloc(x * y * comp * sizeof(float)); + if (output == NULL) { STBI_FREE(data); return stbi__errpf("outofmem", "Out of memory"); } + // compute number of non-alpha components + if (comp & 1) n = comp; else n = comp-1; + for (i=0; i < x*y; ++i) { + for (k=0; k < n; ++k) { + output[i*comp + k] = (float) (pow(data[i*comp+k]/255.0f, stbi__l2h_gamma) * stbi__l2h_scale); + } + if (k < comp) output[i*comp + k] = data[i*comp+k]/255.0f; + } + STBI_FREE(data); + return output; +} +#endif + +#ifndef STBI_NO_HDR +#define stbi__float2int(x) ((int) (x)) +static stbi_uc *stbi__hdr_to_ldr(float *data, int x, int y, int comp) +{ + int i,k,n; + stbi_uc *output = (stbi_uc *) stbi__malloc(x * y * comp); + if (output == NULL) { STBI_FREE(data); return stbi__errpuc("outofmem", "Out of memory"); } + // compute number of non-alpha components + if (comp & 1) n = comp; else n = comp-1; + for (i=0; i < x*y; ++i) { + for (k=0; k < n; ++k) { + float z = (float) pow(data[i*comp+k]*stbi__h2l_scale_i, stbi__h2l_gamma_i) * 255 + 0.5f; + if (z < 0) z = 0; + if (z > 255) z = 255; + output[i*comp + k] = (stbi_uc) stbi__float2int(z); + } + if (k < comp) { + float z = data[i*comp+k] * 255 + 0.5f; + if (z < 0) z = 0; + if (z > 255) z = 255; + output[i*comp + k] = (stbi_uc) stbi__float2int(z); + } + } + STBI_FREE(data); + return output; +} +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// "baseline" JPEG/JFIF decoder +// +// simple implementation +// - doesn't support delayed output of y-dimension +// - simple interface (only one output format: 8-bit interleaved RGB) +// - doesn't try to recover corrupt jpegs +// - doesn't allow partial loading, loading multiple at once +// - still fast on x86 (copying globals into locals doesn't help x86) +// - allocates lots of intermediate memory (full size of all components) +// - non-interleaved case requires this anyway +// - allows good upsampling (see next) +// high-quality +// - upsampled channels are bilinearly interpolated, even across blocks +// - quality integer IDCT derived from IJG's 'slow' +// performance +// - fast huffman; reasonable integer IDCT +// - some SIMD kernels for common paths on targets with SSE2/NEON +// - uses a lot of intermediate memory, could cache poorly + +#ifndef STBI_NO_JPEG + +// huffman decoding acceleration +#define FAST_BITS 9 // larger handles more cases; smaller stomps less cache + +typedef struct +{ + stbi_uc fast[1 << FAST_BITS]; + // weirdly, repacking this into AoS is a 10% speed loss, instead of a win + stbi__uint16 code[256]; + stbi_uc values[256]; + stbi_uc size[257]; + unsigned int maxcode[18]; + int delta[17]; // old 'firstsymbol' - old 'firstcode' +} stbi__huffman; + +typedef struct +{ + stbi__context *s; + stbi__huffman huff_dc[4]; + stbi__huffman huff_ac[4]; + stbi_uc dequant[4][64]; + stbi__int16 fast_ac[4][1 << FAST_BITS]; + +// sizes for components, interleaved MCUs + int img_h_max, img_v_max; + int img_mcu_x, img_mcu_y; + int img_mcu_w, img_mcu_h; + +// definition of jpeg image component + struct + { + int id; + int h,v; + int tq; + int hd,ha; + int dc_pred; + + int x,y,w2,h2; + stbi_uc *data; + void *raw_data, *raw_coeff; + stbi_uc *linebuf; + short *coeff; // progressive only + int coeff_w, coeff_h; // number of 8x8 coefficient blocks + } img_comp[4]; + + stbi__uint32 code_buffer; // jpeg entropy-coded buffer + int code_bits; // number of valid bits + unsigned char marker; // marker seen while filling entropy buffer + int nomore; // flag if we saw a marker so must stop + + int progressive; + int spec_start; + int spec_end; + int succ_high; + int succ_low; + int eob_run; + int rgb; + + int scan_n, order[4]; + int restart_interval, todo; + +// kernels + void (*idct_block_kernel)(stbi_uc *out, int out_stride, short data[64]); + void (*YCbCr_to_RGB_kernel)(stbi_uc *out, const stbi_uc *y, const stbi_uc *pcb, const stbi_uc *pcr, int count, int step); + stbi_uc *(*resample_row_hv_2_kernel)(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs); +} stbi__jpeg; + +static int stbi__build_huffman(stbi__huffman *h, int *count) +{ + int i,j,k=0,code; + // build size list for each symbol (from JPEG spec) + for (i=0; i < 16; ++i) + for (j=0; j < count[i]; ++j) + h->size[k++] = (stbi_uc) (i+1); + h->size[k] = 0; + + // compute actual symbols (from jpeg spec) + code = 0; + k = 0; + for(j=1; j <= 16; ++j) { + // compute delta to add to code to compute symbol id + h->delta[j] = k - code; + if (h->size[k] == j) { + while (h->size[k] == j) + h->code[k++] = (stbi__uint16) (code++); + if (code-1 >= (1 << j)) return stbi__err("bad code lengths","Corrupt JPEG"); + } + // compute largest code + 1 for this size, preshifted as needed later + h->maxcode[j] = code << (16-j); + code <<= 1; + } + h->maxcode[j] = 0xffffffff; + + // build non-spec acceleration table; 255 is flag for not-accelerated + memset(h->fast, 255, 1 << FAST_BITS); + for (i=0; i < k; ++i) { + int s = h->size[i]; + if (s <= FAST_BITS) { + int c = h->code[i] << (FAST_BITS-s); + int m = 1 << (FAST_BITS-s); + for (j=0; j < m; ++j) { + h->fast[c+j] = (stbi_uc) i; + } + } + } + return 1; +} + +// build a table that decodes both magnitude and value of small ACs in +// one go. +static void stbi__build_fast_ac(stbi__int16 *fast_ac, stbi__huffman *h) +{ + int i; + for (i=0; i < (1 << FAST_BITS); ++i) { + stbi_uc fast = h->fast[i]; + fast_ac[i] = 0; + if (fast < 255) { + int rs = h->values[fast]; + int run = (rs >> 4) & 15; + int magbits = rs & 15; + int len = h->size[fast]; + + if (magbits && len + magbits <= FAST_BITS) { + // magnitude code followed by receive_extend code + int k = ((i << len) & ((1 << FAST_BITS) - 1)) >> (FAST_BITS - magbits); + int m = 1 << (magbits - 1); + if (k < m) k += (-1 << magbits) + 1; + // if the result is small enough, we can fit it in fast_ac table + if (k >= -128 && k <= 127) + fast_ac[i] = (stbi__int16) ((k << 8) + (run << 4) + (len + magbits)); + } + } + } +} + +static void stbi__grow_buffer_unsafe(stbi__jpeg *j) +{ + do { + int b = j->nomore ? 0 : stbi__get8(j->s); + if (b == 0xff) { + int c = stbi__get8(j->s); + if (c != 0) { + j->marker = (unsigned char) c; + j->nomore = 1; + return; + } + } + j->code_buffer |= b << (24 - j->code_bits); + j->code_bits += 8; + } while (j->code_bits <= 24); +} + +// (1 << n) - 1 +static stbi__uint32 stbi__bmask[17]={0,1,3,7,15,31,63,127,255,511,1023,2047,4095,8191,16383,32767,65535}; + +// decode a jpeg huffman value from the bitstream +stbi_inline static int stbi__jpeg_huff_decode(stbi__jpeg *j, stbi__huffman *h) +{ + unsigned int temp; + int c,k; + + if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); + + // look at the top FAST_BITS and determine what symbol ID it is, + // if the code is <= FAST_BITS + c = (j->code_buffer >> (32 - FAST_BITS)) & ((1 << FAST_BITS)-1); + k = h->fast[c]; + if (k < 255) { + int s = h->size[k]; + if (s > j->code_bits) + return -1; + j->code_buffer <<= s; + j->code_bits -= s; + return h->values[k]; + } + + // naive test is to shift the code_buffer down so k bits are + // valid, then test against maxcode. To speed this up, we've + // preshifted maxcode left so that it has (16-k) 0s at the + // end; in other words, regardless of the number of bits, it + // wants to be compared against something shifted to have 16; + // that way we don't need to shift inside the loop. + temp = j->code_buffer >> 16; + for (k=FAST_BITS+1 ; ; ++k) + if (temp < h->maxcode[k]) + break; + if (k == 17) { + // error! code not found + j->code_bits -= 16; + return -1; + } + + if (k > j->code_bits) + return -1; + + // convert the huffman code to the symbol id + c = ((j->code_buffer >> (32 - k)) & stbi__bmask[k]) + h->delta[k]; + STBI_ASSERT((((j->code_buffer) >> (32 - h->size[c])) & stbi__bmask[h->size[c]]) == h->code[c]); + + // convert the id to a symbol + j->code_bits -= k; + j->code_buffer <<= k; + return h->values[c]; +} + +// bias[n] = (-1<code_bits < n) stbi__grow_buffer_unsafe(j); + + sgn = (stbi__int32)j->code_buffer >> 31; // sign bit is always in MSB + k = stbi_lrot(j->code_buffer, n); + STBI_ASSERT(n >= 0 && n < (int) (sizeof(stbi__bmask)/sizeof(*stbi__bmask))); + j->code_buffer = k & ~stbi__bmask[n]; + k &= stbi__bmask[n]; + j->code_bits -= n; + return k + (stbi__jbias[n] & ~sgn); +} + +// get some unsigned bits +stbi_inline static int stbi__jpeg_get_bits(stbi__jpeg *j, int n) +{ + unsigned int k; + if (j->code_bits < n) stbi__grow_buffer_unsafe(j); + k = stbi_lrot(j->code_buffer, n); + j->code_buffer = k & ~stbi__bmask[n]; + k &= stbi__bmask[n]; + j->code_bits -= n; + return k; +} + +stbi_inline static int stbi__jpeg_get_bit(stbi__jpeg *j) +{ + unsigned int k; + if (j->code_bits < 1) stbi__grow_buffer_unsafe(j); + k = j->code_buffer; + j->code_buffer <<= 1; + --j->code_bits; + return k & 0x80000000; +} + +// given a value that's at position X in the zigzag stream, +// where does it appear in the 8x8 matrix coded as row-major? +static stbi_uc stbi__jpeg_dezigzag[64+15] = +{ + 0, 1, 8, 16, 9, 2, 3, 10, + 17, 24, 32, 25, 18, 11, 4, 5, + 12, 19, 26, 33, 40, 48, 41, 34, + 27, 20, 13, 6, 7, 14, 21, 28, + 35, 42, 49, 56, 57, 50, 43, 36, + 29, 22, 15, 23, 30, 37, 44, 51, + 58, 59, 52, 45, 38, 31, 39, 46, + 53, 60, 61, 54, 47, 55, 62, 63, + // let corrupt input sample past end + 63, 63, 63, 63, 63, 63, 63, 63, + 63, 63, 63, 63, 63, 63, 63 +}; + +// decode one 64-entry block-- +static int stbi__jpeg_decode_block(stbi__jpeg *j, short data[64], stbi__huffman *hdc, stbi__huffman *hac, stbi__int16 *fac, int b, stbi_uc *dequant) +{ + int diff,dc,k; + int t; + + if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); + t = stbi__jpeg_huff_decode(j, hdc); + if (t < 0) return stbi__err("bad huffman code","Corrupt JPEG"); + + // 0 all the ac values now so we can do it 32-bits at a time + memset(data,0,64*sizeof(data[0])); + + diff = t ? stbi__extend_receive(j, t) : 0; + dc = j->img_comp[b].dc_pred + diff; + j->img_comp[b].dc_pred = dc; + data[0] = (short) (dc * dequant[0]); + + // decode AC components, see JPEG spec + k = 1; + do { + unsigned int zig; + int c,r,s; + if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); + c = (j->code_buffer >> (32 - FAST_BITS)) & ((1 << FAST_BITS)-1); + r = fac[c]; + if (r) { // fast-AC path + k += (r >> 4) & 15; // run + s = r & 15; // combined length + j->code_buffer <<= s; + j->code_bits -= s; + // decode into unzigzag'd location + zig = stbi__jpeg_dezigzag[k++]; + data[zig] = (short) ((r >> 8) * dequant[zig]); + } else { + int rs = stbi__jpeg_huff_decode(j, hac); + if (rs < 0) return stbi__err("bad huffman code","Corrupt JPEG"); + s = rs & 15; + r = rs >> 4; + if (s == 0) { + if (rs != 0xf0) break; // end block + k += 16; + } else { + k += r; + // decode into unzigzag'd location + zig = stbi__jpeg_dezigzag[k++]; + data[zig] = (short) (stbi__extend_receive(j,s) * dequant[zig]); + } + } + } while (k < 64); + return 1; +} + +static int stbi__jpeg_decode_block_prog_dc(stbi__jpeg *j, short data[64], stbi__huffman *hdc, int b) +{ + int diff,dc; + int t; + if (j->spec_end != 0) return stbi__err("can't merge dc and ac", "Corrupt JPEG"); + + if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); + + if (j->succ_high == 0) { + // first scan for DC coefficient, must be first + memset(data,0,64*sizeof(data[0])); // 0 all the ac values now + t = stbi__jpeg_huff_decode(j, hdc); + diff = t ? stbi__extend_receive(j, t) : 0; + + dc = j->img_comp[b].dc_pred + diff; + j->img_comp[b].dc_pred = dc; + data[0] = (short) (dc << j->succ_low); + } else { + // refinement scan for DC coefficient + if (stbi__jpeg_get_bit(j)) + data[0] += (short) (1 << j->succ_low); + } + return 1; +} + +// @OPTIMIZE: store non-zigzagged during the decode passes, +// and only de-zigzag when dequantizing +static int stbi__jpeg_decode_block_prog_ac(stbi__jpeg *j, short data[64], stbi__huffman *hac, stbi__int16 *fac) +{ + int k; + if (j->spec_start == 0) return stbi__err("can't merge dc and ac", "Corrupt JPEG"); + + if (j->succ_high == 0) { + int shift = j->succ_low; + + if (j->eob_run) { + --j->eob_run; + return 1; + } + + k = j->spec_start; + do { + unsigned int zig; + int c,r,s; + if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); + c = (j->code_buffer >> (32 - FAST_BITS)) & ((1 << FAST_BITS)-1); + r = fac[c]; + if (r) { // fast-AC path + k += (r >> 4) & 15; // run + s = r & 15; // combined length + j->code_buffer <<= s; + j->code_bits -= s; + zig = stbi__jpeg_dezigzag[k++]; + data[zig] = (short) ((r >> 8) << shift); + } else { + int rs = stbi__jpeg_huff_decode(j, hac); + if (rs < 0) return stbi__err("bad huffman code","Corrupt JPEG"); + s = rs & 15; + r = rs >> 4; + if (s == 0) { + if (r < 15) { + j->eob_run = (1 << r); + if (r) + j->eob_run += stbi__jpeg_get_bits(j, r); + --j->eob_run; + break; + } + k += 16; + } else { + k += r; + zig = stbi__jpeg_dezigzag[k++]; + data[zig] = (short) (stbi__extend_receive(j,s) << shift); + } + } + } while (k <= j->spec_end); + } else { + // refinement scan for these AC coefficients + + short bit = (short) (1 << j->succ_low); + + if (j->eob_run) { + --j->eob_run; + for (k = j->spec_start; k <= j->spec_end; ++k) { + short *p = &data[stbi__jpeg_dezigzag[k]]; + if (*p != 0) + if (stbi__jpeg_get_bit(j)) + if ((*p & bit)==0) { + if (*p > 0) + *p += bit; + else + *p -= bit; + } + } + } else { + k = j->spec_start; + do { + int r,s; + int rs = stbi__jpeg_huff_decode(j, hac); // @OPTIMIZE see if we can use the fast path here, advance-by-r is so slow, eh + if (rs < 0) return stbi__err("bad huffman code","Corrupt JPEG"); + s = rs & 15; + r = rs >> 4; + if (s == 0) { + if (r < 15) { + j->eob_run = (1 << r) - 1; + if (r) + j->eob_run += stbi__jpeg_get_bits(j, r); + r = 64; // force end of block + } else { + // r=15 s=0 should write 16 0s, so we just do + // a run of 15 0s and then write s (which is 0), + // so we don't have to do anything special here + } + } else { + if (s != 1) return stbi__err("bad huffman code", "Corrupt JPEG"); + // sign bit + if (stbi__jpeg_get_bit(j)) + s = bit; + else + s = -bit; + } + + // advance by r + while (k <= j->spec_end) { + short *p = &data[stbi__jpeg_dezigzag[k++]]; + if (*p != 0) { + if (stbi__jpeg_get_bit(j)) + if ((*p & bit)==0) { + if (*p > 0) + *p += bit; + else + *p -= bit; + } + } else { + if (r == 0) { + *p = (short) s; + break; + } + --r; + } + } + } while (k <= j->spec_end); + } + } + return 1; +} + +// take a -128..127 value and stbi__clamp it and convert to 0..255 +stbi_inline static stbi_uc stbi__clamp(int x) +{ + // trick to use a single test to catch both cases + if ((unsigned int) x > 255) { + if (x < 0) return 0; + if (x > 255) return 255; + } + return (stbi_uc) x; +} + +#define stbi__f2f(x) ((int) (((x) * 4096 + 0.5))) +#define stbi__fsh(x) ((x) << 12) + +// derived from jidctint -- DCT_ISLOW +#define STBI__IDCT_1D(s0,s1,s2,s3,s4,s5,s6,s7) \ + int t0,t1,t2,t3,p1,p2,p3,p4,p5,x0,x1,x2,x3; \ + p2 = s2; \ + p3 = s6; \ + p1 = (p2+p3) * stbi__f2f(0.5411961f); \ + t2 = p1 + p3*stbi__f2f(-1.847759065f); \ + t3 = p1 + p2*stbi__f2f( 0.765366865f); \ + p2 = s0; \ + p3 = s4; \ + t0 = stbi__fsh(p2+p3); \ + t1 = stbi__fsh(p2-p3); \ + x0 = t0+t3; \ + x3 = t0-t3; \ + x1 = t1+t2; \ + x2 = t1-t2; \ + t0 = s7; \ + t1 = s5; \ + t2 = s3; \ + t3 = s1; \ + p3 = t0+t2; \ + p4 = t1+t3; \ + p1 = t0+t3; \ + p2 = t1+t2; \ + p5 = (p3+p4)*stbi__f2f( 1.175875602f); \ + t0 = t0*stbi__f2f( 0.298631336f); \ + t1 = t1*stbi__f2f( 2.053119869f); \ + t2 = t2*stbi__f2f( 3.072711026f); \ + t3 = t3*stbi__f2f( 1.501321110f); \ + p1 = p5 + p1*stbi__f2f(-0.899976223f); \ + p2 = p5 + p2*stbi__f2f(-2.562915447f); \ + p3 = p3*stbi__f2f(-1.961570560f); \ + p4 = p4*stbi__f2f(-0.390180644f); \ + t3 += p1+p4; \ + t2 += p2+p3; \ + t1 += p2+p4; \ + t0 += p1+p3; + +static void stbi__idct_block(stbi_uc *out, int out_stride, short data[64]) +{ + int i,val[64],*v=val; + stbi_uc *o; + short *d = data; + + // columns + for (i=0; i < 8; ++i,++d, ++v) { + // if all zeroes, shortcut -- this avoids dequantizing 0s and IDCTing + if (d[ 8]==0 && d[16]==0 && d[24]==0 && d[32]==0 + && d[40]==0 && d[48]==0 && d[56]==0) { + // no shortcut 0 seconds + // (1|2|3|4|5|6|7)==0 0 seconds + // all separate -0.047 seconds + // 1 && 2|3 && 4|5 && 6|7: -0.047 seconds + int dcterm = d[0] << 2; + v[0] = v[8] = v[16] = v[24] = v[32] = v[40] = v[48] = v[56] = dcterm; + } else { + STBI__IDCT_1D(d[ 0],d[ 8],d[16],d[24],d[32],d[40],d[48],d[56]) + // constants scaled things up by 1<<12; let's bring them back + // down, but keep 2 extra bits of precision + x0 += 512; x1 += 512; x2 += 512; x3 += 512; + v[ 0] = (x0+t3) >> 10; + v[56] = (x0-t3) >> 10; + v[ 8] = (x1+t2) >> 10; + v[48] = (x1-t2) >> 10; + v[16] = (x2+t1) >> 10; + v[40] = (x2-t1) >> 10; + v[24] = (x3+t0) >> 10; + v[32] = (x3-t0) >> 10; + } + } + + for (i=0, v=val, o=out; i < 8; ++i,v+=8,o+=out_stride) { + // no fast case since the first 1D IDCT spread components out + STBI__IDCT_1D(v[0],v[1],v[2],v[3],v[4],v[5],v[6],v[7]) + // constants scaled things up by 1<<12, plus we had 1<<2 from first + // loop, plus horizontal and vertical each scale by sqrt(8) so together + // we've got an extra 1<<3, so 1<<17 total we need to remove. + // so we want to round that, which means adding 0.5 * 1<<17, + // aka 65536. Also, we'll end up with -128 to 127 that we want + // to encode as 0..255 by adding 128, so we'll add that before the shift + x0 += 65536 + (128<<17); + x1 += 65536 + (128<<17); + x2 += 65536 + (128<<17); + x3 += 65536 + (128<<17); + // tried computing the shifts into temps, or'ing the temps to see + // if any were out of range, but that was slower + o[0] = stbi__clamp((x0+t3) >> 17); + o[7] = stbi__clamp((x0-t3) >> 17); + o[1] = stbi__clamp((x1+t2) >> 17); + o[6] = stbi__clamp((x1-t2) >> 17); + o[2] = stbi__clamp((x2+t1) >> 17); + o[5] = stbi__clamp((x2-t1) >> 17); + o[3] = stbi__clamp((x3+t0) >> 17); + o[4] = stbi__clamp((x3-t0) >> 17); + } +} + +#ifdef STBI_SSE2 +// sse2 integer IDCT. not the fastest possible implementation but it +// produces bit-identical results to the generic C version so it's +// fully "transparent". +static void stbi__idct_simd(stbi_uc *out, int out_stride, short data[64]) +{ + // This is constructed to match our regular (generic) integer IDCT exactly. + __m128i row0, row1, row2, row3, row4, row5, row6, row7; + __m128i tmp; + + // dot product constant: even elems=x, odd elems=y + #define dct_const(x,y) _mm_setr_epi16((x),(y),(x),(y),(x),(y),(x),(y)) + + // out(0) = c0[even]*x + c0[odd]*y (c0, x, y 16-bit, out 32-bit) + // out(1) = c1[even]*x + c1[odd]*y + #define dct_rot(out0,out1, x,y,c0,c1) \ + __m128i c0##lo = _mm_unpacklo_epi16((x),(y)); \ + __m128i c0##hi = _mm_unpackhi_epi16((x),(y)); \ + __m128i out0##_l = _mm_madd_epi16(c0##lo, c0); \ + __m128i out0##_h = _mm_madd_epi16(c0##hi, c0); \ + __m128i out1##_l = _mm_madd_epi16(c0##lo, c1); \ + __m128i out1##_h = _mm_madd_epi16(c0##hi, c1) + + // out = in << 12 (in 16-bit, out 32-bit) + #define dct_widen(out, in) \ + __m128i out##_l = _mm_srai_epi32(_mm_unpacklo_epi16(_mm_setzero_si128(), (in)), 4); \ + __m128i out##_h = _mm_srai_epi32(_mm_unpackhi_epi16(_mm_setzero_si128(), (in)), 4) + + // wide add + #define dct_wadd(out, a, b) \ + __m128i out##_l = _mm_add_epi32(a##_l, b##_l); \ + __m128i out##_h = _mm_add_epi32(a##_h, b##_h) + + // wide sub + #define dct_wsub(out, a, b) \ + __m128i out##_l = _mm_sub_epi32(a##_l, b##_l); \ + __m128i out##_h = _mm_sub_epi32(a##_h, b##_h) + + // butterfly a/b, add bias, then shift by "s" and pack + #define dct_bfly32o(out0, out1, a,b,bias,s) \ + { \ + __m128i abiased_l = _mm_add_epi32(a##_l, bias); \ + __m128i abiased_h = _mm_add_epi32(a##_h, bias); \ + dct_wadd(sum, abiased, b); \ + dct_wsub(dif, abiased, b); \ + out0 = _mm_packs_epi32(_mm_srai_epi32(sum_l, s), _mm_srai_epi32(sum_h, s)); \ + out1 = _mm_packs_epi32(_mm_srai_epi32(dif_l, s), _mm_srai_epi32(dif_h, s)); \ + } + + // 8-bit interleave step (for transposes) + #define dct_interleave8(a, b) \ + tmp = a; \ + a = _mm_unpacklo_epi8(a, b); \ + b = _mm_unpackhi_epi8(tmp, b) + + // 16-bit interleave step (for transposes) + #define dct_interleave16(a, b) \ + tmp = a; \ + a = _mm_unpacklo_epi16(a, b); \ + b = _mm_unpackhi_epi16(tmp, b) + + #define dct_pass(bias,shift) \ + { \ + /* even part */ \ + dct_rot(t2e,t3e, row2,row6, rot0_0,rot0_1); \ + __m128i sum04 = _mm_add_epi16(row0, row4); \ + __m128i dif04 = _mm_sub_epi16(row0, row4); \ + dct_widen(t0e, sum04); \ + dct_widen(t1e, dif04); \ + dct_wadd(x0, t0e, t3e); \ + dct_wsub(x3, t0e, t3e); \ + dct_wadd(x1, t1e, t2e); \ + dct_wsub(x2, t1e, t2e); \ + /* odd part */ \ + dct_rot(y0o,y2o, row7,row3, rot2_0,rot2_1); \ + dct_rot(y1o,y3o, row5,row1, rot3_0,rot3_1); \ + __m128i sum17 = _mm_add_epi16(row1, row7); \ + __m128i sum35 = _mm_add_epi16(row3, row5); \ + dct_rot(y4o,y5o, sum17,sum35, rot1_0,rot1_1); \ + dct_wadd(x4, y0o, y4o); \ + dct_wadd(x5, y1o, y5o); \ + dct_wadd(x6, y2o, y5o); \ + dct_wadd(x7, y3o, y4o); \ + dct_bfly32o(row0,row7, x0,x7,bias,shift); \ + dct_bfly32o(row1,row6, x1,x6,bias,shift); \ + dct_bfly32o(row2,row5, x2,x5,bias,shift); \ + dct_bfly32o(row3,row4, x3,x4,bias,shift); \ + } + + __m128i rot0_0 = dct_const(stbi__f2f(0.5411961f), stbi__f2f(0.5411961f) + stbi__f2f(-1.847759065f)); + __m128i rot0_1 = dct_const(stbi__f2f(0.5411961f) + stbi__f2f( 0.765366865f), stbi__f2f(0.5411961f)); + __m128i rot1_0 = dct_const(stbi__f2f(1.175875602f) + stbi__f2f(-0.899976223f), stbi__f2f(1.175875602f)); + __m128i rot1_1 = dct_const(stbi__f2f(1.175875602f), stbi__f2f(1.175875602f) + stbi__f2f(-2.562915447f)); + __m128i rot2_0 = dct_const(stbi__f2f(-1.961570560f) + stbi__f2f( 0.298631336f), stbi__f2f(-1.961570560f)); + __m128i rot2_1 = dct_const(stbi__f2f(-1.961570560f), stbi__f2f(-1.961570560f) + stbi__f2f( 3.072711026f)); + __m128i rot3_0 = dct_const(stbi__f2f(-0.390180644f) + stbi__f2f( 2.053119869f), stbi__f2f(-0.390180644f)); + __m128i rot3_1 = dct_const(stbi__f2f(-0.390180644f), stbi__f2f(-0.390180644f) + stbi__f2f( 1.501321110f)); + + // rounding biases in column/row passes, see stbi__idct_block for explanation. + __m128i bias_0 = _mm_set1_epi32(512); + __m128i bias_1 = _mm_set1_epi32(65536 + (128<<17)); + + // load + row0 = _mm_load_si128((const __m128i *) (data + 0*8)); + row1 = _mm_load_si128((const __m128i *) (data + 1*8)); + row2 = _mm_load_si128((const __m128i *) (data + 2*8)); + row3 = _mm_load_si128((const __m128i *) (data + 3*8)); + row4 = _mm_load_si128((const __m128i *) (data + 4*8)); + row5 = _mm_load_si128((const __m128i *) (data + 5*8)); + row6 = _mm_load_si128((const __m128i *) (data + 6*8)); + row7 = _mm_load_si128((const __m128i *) (data + 7*8)); + + // column pass + dct_pass(bias_0, 10); + + { + // 16bit 8x8 transpose pass 1 + dct_interleave16(row0, row4); + dct_interleave16(row1, row5); + dct_interleave16(row2, row6); + dct_interleave16(row3, row7); + + // transpose pass 2 + dct_interleave16(row0, row2); + dct_interleave16(row1, row3); + dct_interleave16(row4, row6); + dct_interleave16(row5, row7); + + // transpose pass 3 + dct_interleave16(row0, row1); + dct_interleave16(row2, row3); + dct_interleave16(row4, row5); + dct_interleave16(row6, row7); + } + + // row pass + dct_pass(bias_1, 17); + + { + // pack + __m128i p0 = _mm_packus_epi16(row0, row1); // a0a1a2a3...a7b0b1b2b3...b7 + __m128i p1 = _mm_packus_epi16(row2, row3); + __m128i p2 = _mm_packus_epi16(row4, row5); + __m128i p3 = _mm_packus_epi16(row6, row7); + + // 8bit 8x8 transpose pass 1 + dct_interleave8(p0, p2); // a0e0a1e1... + dct_interleave8(p1, p3); // c0g0c1g1... + + // transpose pass 2 + dct_interleave8(p0, p1); // a0c0e0g0... + dct_interleave8(p2, p3); // b0d0f0h0... + + // transpose pass 3 + dct_interleave8(p0, p2); // a0b0c0d0... + dct_interleave8(p1, p3); // a4b4c4d4... + + // store + _mm_storel_epi64((__m128i *) out, p0); out += out_stride; + _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p0, 0x4e)); out += out_stride; + _mm_storel_epi64((__m128i *) out, p2); out += out_stride; + _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p2, 0x4e)); out += out_stride; + _mm_storel_epi64((__m128i *) out, p1); out += out_stride; + _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p1, 0x4e)); out += out_stride; + _mm_storel_epi64((__m128i *) out, p3); out += out_stride; + _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p3, 0x4e)); + } + +#undef dct_const +#undef dct_rot +#undef dct_widen +#undef dct_wadd +#undef dct_wsub +#undef dct_bfly32o +#undef dct_interleave8 +#undef dct_interleave16 +#undef dct_pass +} + +#endif // STBI_SSE2 + +#ifdef STBI_NEON + +// NEON integer IDCT. should produce bit-identical +// results to the generic C version. +static void stbi__idct_simd(stbi_uc *out, int out_stride, short data[64]) +{ + int16x8_t row0, row1, row2, row3, row4, row5, row6, row7; + + int16x4_t rot0_0 = vdup_n_s16(stbi__f2f(0.5411961f)); + int16x4_t rot0_1 = vdup_n_s16(stbi__f2f(-1.847759065f)); + int16x4_t rot0_2 = vdup_n_s16(stbi__f2f( 0.765366865f)); + int16x4_t rot1_0 = vdup_n_s16(stbi__f2f( 1.175875602f)); + int16x4_t rot1_1 = vdup_n_s16(stbi__f2f(-0.899976223f)); + int16x4_t rot1_2 = vdup_n_s16(stbi__f2f(-2.562915447f)); + int16x4_t rot2_0 = vdup_n_s16(stbi__f2f(-1.961570560f)); + int16x4_t rot2_1 = vdup_n_s16(stbi__f2f(-0.390180644f)); + int16x4_t rot3_0 = vdup_n_s16(stbi__f2f( 0.298631336f)); + int16x4_t rot3_1 = vdup_n_s16(stbi__f2f( 2.053119869f)); + int16x4_t rot3_2 = vdup_n_s16(stbi__f2f( 3.072711026f)); + int16x4_t rot3_3 = vdup_n_s16(stbi__f2f( 1.501321110f)); + +#define dct_long_mul(out, inq, coeff) \ + int32x4_t out##_l = vmull_s16(vget_low_s16(inq), coeff); \ + int32x4_t out##_h = vmull_s16(vget_high_s16(inq), coeff) + +#define dct_long_mac(out, acc, inq, coeff) \ + int32x4_t out##_l = vmlal_s16(acc##_l, vget_low_s16(inq), coeff); \ + int32x4_t out##_h = vmlal_s16(acc##_h, vget_high_s16(inq), coeff) + +#define dct_widen(out, inq) \ + int32x4_t out##_l = vshll_n_s16(vget_low_s16(inq), 12); \ + int32x4_t out##_h = vshll_n_s16(vget_high_s16(inq), 12) + +// wide add +#define dct_wadd(out, a, b) \ + int32x4_t out##_l = vaddq_s32(a##_l, b##_l); \ + int32x4_t out##_h = vaddq_s32(a##_h, b##_h) + +// wide sub +#define dct_wsub(out, a, b) \ + int32x4_t out##_l = vsubq_s32(a##_l, b##_l); \ + int32x4_t out##_h = vsubq_s32(a##_h, b##_h) + +// butterfly a/b, then shift using "shiftop" by "s" and pack +#define dct_bfly32o(out0,out1, a,b,shiftop,s) \ + { \ + dct_wadd(sum, a, b); \ + dct_wsub(dif, a, b); \ + out0 = vcombine_s16(shiftop(sum_l, s), shiftop(sum_h, s)); \ + out1 = vcombine_s16(shiftop(dif_l, s), shiftop(dif_h, s)); \ + } + +#define dct_pass(shiftop, shift) \ + { \ + /* even part */ \ + int16x8_t sum26 = vaddq_s16(row2, row6); \ + dct_long_mul(p1e, sum26, rot0_0); \ + dct_long_mac(t2e, p1e, row6, rot0_1); \ + dct_long_mac(t3e, p1e, row2, rot0_2); \ + int16x8_t sum04 = vaddq_s16(row0, row4); \ + int16x8_t dif04 = vsubq_s16(row0, row4); \ + dct_widen(t0e, sum04); \ + dct_widen(t1e, dif04); \ + dct_wadd(x0, t0e, t3e); \ + dct_wsub(x3, t0e, t3e); \ + dct_wadd(x1, t1e, t2e); \ + dct_wsub(x2, t1e, t2e); \ + /* odd part */ \ + int16x8_t sum15 = vaddq_s16(row1, row5); \ + int16x8_t sum17 = vaddq_s16(row1, row7); \ + int16x8_t sum35 = vaddq_s16(row3, row5); \ + int16x8_t sum37 = vaddq_s16(row3, row7); \ + int16x8_t sumodd = vaddq_s16(sum17, sum35); \ + dct_long_mul(p5o, sumodd, rot1_0); \ + dct_long_mac(p1o, p5o, sum17, rot1_1); \ + dct_long_mac(p2o, p5o, sum35, rot1_2); \ + dct_long_mul(p3o, sum37, rot2_0); \ + dct_long_mul(p4o, sum15, rot2_1); \ + dct_wadd(sump13o, p1o, p3o); \ + dct_wadd(sump24o, p2o, p4o); \ + dct_wadd(sump23o, p2o, p3o); \ + dct_wadd(sump14o, p1o, p4o); \ + dct_long_mac(x4, sump13o, row7, rot3_0); \ + dct_long_mac(x5, sump24o, row5, rot3_1); \ + dct_long_mac(x6, sump23o, row3, rot3_2); \ + dct_long_mac(x7, sump14o, row1, rot3_3); \ + dct_bfly32o(row0,row7, x0,x7,shiftop,shift); \ + dct_bfly32o(row1,row6, x1,x6,shiftop,shift); \ + dct_bfly32o(row2,row5, x2,x5,shiftop,shift); \ + dct_bfly32o(row3,row4, x3,x4,shiftop,shift); \ + } + + // load + row0 = vld1q_s16(data + 0*8); + row1 = vld1q_s16(data + 1*8); + row2 = vld1q_s16(data + 2*8); + row3 = vld1q_s16(data + 3*8); + row4 = vld1q_s16(data + 4*8); + row5 = vld1q_s16(data + 5*8); + row6 = vld1q_s16(data + 6*8); + row7 = vld1q_s16(data + 7*8); + + // add DC bias + row0 = vaddq_s16(row0, vsetq_lane_s16(1024, vdupq_n_s16(0), 0)); + + // column pass + dct_pass(vrshrn_n_s32, 10); + + // 16bit 8x8 transpose + { +// these three map to a single VTRN.16, VTRN.32, and VSWP, respectively. +// whether compilers actually get this is another story, sadly. +#define dct_trn16(x, y) { int16x8x2_t t = vtrnq_s16(x, y); x = t.val[0]; y = t.val[1]; } +#define dct_trn32(x, y) { int32x4x2_t t = vtrnq_s32(vreinterpretq_s32_s16(x), vreinterpretq_s32_s16(y)); x = vreinterpretq_s16_s32(t.val[0]); y = vreinterpretq_s16_s32(t.val[1]); } +#define dct_trn64(x, y) { int16x8_t x0 = x; int16x8_t y0 = y; x = vcombine_s16(vget_low_s16(x0), vget_low_s16(y0)); y = vcombine_s16(vget_high_s16(x0), vget_high_s16(y0)); } + + // pass 1 + dct_trn16(row0, row1); // a0b0a2b2a4b4a6b6 + dct_trn16(row2, row3); + dct_trn16(row4, row5); + dct_trn16(row6, row7); + + // pass 2 + dct_trn32(row0, row2); // a0b0c0d0a4b4c4d4 + dct_trn32(row1, row3); + dct_trn32(row4, row6); + dct_trn32(row5, row7); + + // pass 3 + dct_trn64(row0, row4); // a0b0c0d0e0f0g0h0 + dct_trn64(row1, row5); + dct_trn64(row2, row6); + dct_trn64(row3, row7); + +#undef dct_trn16 +#undef dct_trn32 +#undef dct_trn64 + } + + // row pass + // vrshrn_n_s32 only supports shifts up to 16, we need + // 17. so do a non-rounding shift of 16 first then follow + // up with a rounding shift by 1. + dct_pass(vshrn_n_s32, 16); + + { + // pack and round + uint8x8_t p0 = vqrshrun_n_s16(row0, 1); + uint8x8_t p1 = vqrshrun_n_s16(row1, 1); + uint8x8_t p2 = vqrshrun_n_s16(row2, 1); + uint8x8_t p3 = vqrshrun_n_s16(row3, 1); + uint8x8_t p4 = vqrshrun_n_s16(row4, 1); + uint8x8_t p5 = vqrshrun_n_s16(row5, 1); + uint8x8_t p6 = vqrshrun_n_s16(row6, 1); + uint8x8_t p7 = vqrshrun_n_s16(row7, 1); + + // again, these can translate into one instruction, but often don't. +#define dct_trn8_8(x, y) { uint8x8x2_t t = vtrn_u8(x, y); x = t.val[0]; y = t.val[1]; } +#define dct_trn8_16(x, y) { uint16x4x2_t t = vtrn_u16(vreinterpret_u16_u8(x), vreinterpret_u16_u8(y)); x = vreinterpret_u8_u16(t.val[0]); y = vreinterpret_u8_u16(t.val[1]); } +#define dct_trn8_32(x, y) { uint32x2x2_t t = vtrn_u32(vreinterpret_u32_u8(x), vreinterpret_u32_u8(y)); x = vreinterpret_u8_u32(t.val[0]); y = vreinterpret_u8_u32(t.val[1]); } + + // sadly can't use interleaved stores here since we only write + // 8 bytes to each scan line! + + // 8x8 8-bit transpose pass 1 + dct_trn8_8(p0, p1); + dct_trn8_8(p2, p3); + dct_trn8_8(p4, p5); + dct_trn8_8(p6, p7); + + // pass 2 + dct_trn8_16(p0, p2); + dct_trn8_16(p1, p3); + dct_trn8_16(p4, p6); + dct_trn8_16(p5, p7); + + // pass 3 + dct_trn8_32(p0, p4); + dct_trn8_32(p1, p5); + dct_trn8_32(p2, p6); + dct_trn8_32(p3, p7); + + // store + vst1_u8(out, p0); out += out_stride; + vst1_u8(out, p1); out += out_stride; + vst1_u8(out, p2); out += out_stride; + vst1_u8(out, p3); out += out_stride; + vst1_u8(out, p4); out += out_stride; + vst1_u8(out, p5); out += out_stride; + vst1_u8(out, p6); out += out_stride; + vst1_u8(out, p7); + +#undef dct_trn8_8 +#undef dct_trn8_16 +#undef dct_trn8_32 + } + +#undef dct_long_mul +#undef dct_long_mac +#undef dct_widen +#undef dct_wadd +#undef dct_wsub +#undef dct_bfly32o +#undef dct_pass +} + +#endif // STBI_NEON + +#define STBI__MARKER_none 0xff +// if there's a pending marker from the entropy stream, return that +// otherwise, fetch from the stream and get a marker. if there's no +// marker, return 0xff, which is never a valid marker value +static stbi_uc stbi__get_marker(stbi__jpeg *j) +{ + stbi_uc x; + if (j->marker != STBI__MARKER_none) { x = j->marker; j->marker = STBI__MARKER_none; return x; } + x = stbi__get8(j->s); + if (x != 0xff) return STBI__MARKER_none; + while (x == 0xff) + x = stbi__get8(j->s); + return x; +} + +// in each scan, we'll have scan_n components, and the order +// of the components is specified by order[] +#define STBI__RESTART(x) ((x) >= 0xd0 && (x) <= 0xd7) + +// after a restart interval, stbi__jpeg_reset the entropy decoder and +// the dc prediction +static void stbi__jpeg_reset(stbi__jpeg *j) +{ + j->code_bits = 0; + j->code_buffer = 0; + j->nomore = 0; + j->img_comp[0].dc_pred = j->img_comp[1].dc_pred = j->img_comp[2].dc_pred = 0; + j->marker = STBI__MARKER_none; + j->todo = j->restart_interval ? j->restart_interval : 0x7fffffff; + j->eob_run = 0; + // no more than 1<<31 MCUs if no restart_interal? that's plenty safe, + // since we don't even allow 1<<30 pixels +} + +static int stbi__parse_entropy_coded_data(stbi__jpeg *z) +{ + stbi__jpeg_reset(z); + if (!z->progressive) { + if (z->scan_n == 1) { + int i,j; + STBI_SIMD_ALIGN(short, data[64]); + int n = z->order[0]; + // non-interleaved data, we just need to process one block at a time, + // in trivial scanline order + // number of blocks to do just depends on how many actual "pixels" this + // component has, independent of interleaved MCU blocking and such + int w = (z->img_comp[n].x+7) >> 3; + int h = (z->img_comp[n].y+7) >> 3; + for (j=0; j < h; ++j) { + for (i=0; i < w; ++i) { + int ha = z->img_comp[n].ha; + if (!stbi__jpeg_decode_block(z, data, z->huff_dc+z->img_comp[n].hd, z->huff_ac+ha, z->fast_ac[ha], n, z->dequant[z->img_comp[n].tq])) return 0; + z->idct_block_kernel(z->img_comp[n].data+z->img_comp[n].w2*j*8+i*8, z->img_comp[n].w2, data); + // every data block is an MCU, so countdown the restart interval + if (--z->todo <= 0) { + if (z->code_bits < 24) stbi__grow_buffer_unsafe(z); + // if it's NOT a restart, then just bail, so we get corrupt data + // rather than no data + if (!STBI__RESTART(z->marker)) return 1; + stbi__jpeg_reset(z); + } + } + } + return 1; + } else { // interleaved + int i,j,k,x,y; + STBI_SIMD_ALIGN(short, data[64]); + for (j=0; j < z->img_mcu_y; ++j) { + for (i=0; i < z->img_mcu_x; ++i) { + // scan an interleaved mcu... process scan_n components in order + for (k=0; k < z->scan_n; ++k) { + int n = z->order[k]; + // scan out an mcu's worth of this component; that's just determined + // by the basic H and V specified for the component + for (y=0; y < z->img_comp[n].v; ++y) { + for (x=0; x < z->img_comp[n].h; ++x) { + int x2 = (i*z->img_comp[n].h + x)*8; + int y2 = (j*z->img_comp[n].v + y)*8; + int ha = z->img_comp[n].ha; + if (!stbi__jpeg_decode_block(z, data, z->huff_dc+z->img_comp[n].hd, z->huff_ac+ha, z->fast_ac[ha], n, z->dequant[z->img_comp[n].tq])) return 0; + z->idct_block_kernel(z->img_comp[n].data+z->img_comp[n].w2*y2+x2, z->img_comp[n].w2, data); + } + } + } + // after all interleaved components, that's an interleaved MCU, + // so now count down the restart interval + if (--z->todo <= 0) { + if (z->code_bits < 24) stbi__grow_buffer_unsafe(z); + if (!STBI__RESTART(z->marker)) return 1; + stbi__jpeg_reset(z); + } + } + } + return 1; + } + } else { + if (z->scan_n == 1) { + int i,j; + int n = z->order[0]; + // non-interleaved data, we just need to process one block at a time, + // in trivial scanline order + // number of blocks to do just depends on how many actual "pixels" this + // component has, independent of interleaved MCU blocking and such + int w = (z->img_comp[n].x+7) >> 3; + int h = (z->img_comp[n].y+7) >> 3; + for (j=0; j < h; ++j) { + for (i=0; i < w; ++i) { + short *data = z->img_comp[n].coeff + 64 * (i + j * z->img_comp[n].coeff_w); + if (z->spec_start == 0) { + if (!stbi__jpeg_decode_block_prog_dc(z, data, &z->huff_dc[z->img_comp[n].hd], n)) + return 0; + } else { + int ha = z->img_comp[n].ha; + if (!stbi__jpeg_decode_block_prog_ac(z, data, &z->huff_ac[ha], z->fast_ac[ha])) + return 0; + } + // every data block is an MCU, so countdown the restart interval + if (--z->todo <= 0) { + if (z->code_bits < 24) stbi__grow_buffer_unsafe(z); + if (!STBI__RESTART(z->marker)) return 1; + stbi__jpeg_reset(z); + } + } + } + return 1; + } else { // interleaved + int i,j,k,x,y; + for (j=0; j < z->img_mcu_y; ++j) { + for (i=0; i < z->img_mcu_x; ++i) { + // scan an interleaved mcu... process scan_n components in order + for (k=0; k < z->scan_n; ++k) { + int n = z->order[k]; + // scan out an mcu's worth of this component; that's just determined + // by the basic H and V specified for the component + for (y=0; y < z->img_comp[n].v; ++y) { + for (x=0; x < z->img_comp[n].h; ++x) { + int x2 = (i*z->img_comp[n].h + x); + int y2 = (j*z->img_comp[n].v + y); + short *data = z->img_comp[n].coeff + 64 * (x2 + y2 * z->img_comp[n].coeff_w); + if (!stbi__jpeg_decode_block_prog_dc(z, data, &z->huff_dc[z->img_comp[n].hd], n)) + return 0; + } + } + } + // after all interleaved components, that's an interleaved MCU, + // so now count down the restart interval + if (--z->todo <= 0) { + if (z->code_bits < 24) stbi__grow_buffer_unsafe(z); + if (!STBI__RESTART(z->marker)) return 1; + stbi__jpeg_reset(z); + } + } + } + return 1; + } + } +} + +static void stbi__jpeg_dequantize(short *data, stbi_uc *dequant) +{ + int i; + for (i=0; i < 64; ++i) + data[i] *= dequant[i]; +} + +static void stbi__jpeg_finish(stbi__jpeg *z) +{ + if (z->progressive) { + // dequantize and idct the data + int i,j,n; + for (n=0; n < z->s->img_n; ++n) { + int w = (z->img_comp[n].x+7) >> 3; + int h = (z->img_comp[n].y+7) >> 3; + for (j=0; j < h; ++j) { + for (i=0; i < w; ++i) { + short *data = z->img_comp[n].coeff + 64 * (i + j * z->img_comp[n].coeff_w); + stbi__jpeg_dequantize(data, z->dequant[z->img_comp[n].tq]); + z->idct_block_kernel(z->img_comp[n].data+z->img_comp[n].w2*j*8+i*8, z->img_comp[n].w2, data); + } + } + } + } +} + +static int stbi__process_marker(stbi__jpeg *z, int m) +{ + int L; + switch (m) { + case STBI__MARKER_none: // no marker found + return stbi__err("expected marker","Corrupt JPEG"); + + case 0xDD: // DRI - specify restart interval + if (stbi__get16be(z->s) != 4) return stbi__err("bad DRI len","Corrupt JPEG"); + z->restart_interval = stbi__get16be(z->s); + return 1; + + case 0xDB: // DQT - define quantization table + L = stbi__get16be(z->s)-2; + while (L > 0) { + int q = stbi__get8(z->s); + int p = q >> 4; + int t = q & 15,i; + if (p != 0) return stbi__err("bad DQT type","Corrupt JPEG"); + if (t > 3) return stbi__err("bad DQT table","Corrupt JPEG"); + for (i=0; i < 64; ++i) + z->dequant[t][stbi__jpeg_dezigzag[i]] = stbi__get8(z->s); + L -= 65; + } + return L==0; + + case 0xC4: // DHT - define huffman table + L = stbi__get16be(z->s)-2; + while (L > 0) { + stbi_uc *v; + int sizes[16],i,n=0; + int q = stbi__get8(z->s); + int tc = q >> 4; + int th = q & 15; + if (tc > 1 || th > 3) return stbi__err("bad DHT header","Corrupt JPEG"); + for (i=0; i < 16; ++i) { + sizes[i] = stbi__get8(z->s); + n += sizes[i]; + } + L -= 17; + if (tc == 0) { + if (!stbi__build_huffman(z->huff_dc+th, sizes)) return 0; + v = z->huff_dc[th].values; + } else { + if (!stbi__build_huffman(z->huff_ac+th, sizes)) return 0; + v = z->huff_ac[th].values; + } + for (i=0; i < n; ++i) + v[i] = stbi__get8(z->s); + if (tc != 0) + stbi__build_fast_ac(z->fast_ac[th], z->huff_ac + th); + L -= n; + } + return L==0; + } + // check for comment block or APP blocks + if ((m >= 0xE0 && m <= 0xEF) || m == 0xFE) { + stbi__skip(z->s, stbi__get16be(z->s)-2); + return 1; + } + return 0; +} + +// after we see SOS +static int stbi__process_scan_header(stbi__jpeg *z) +{ + int i; + int Ls = stbi__get16be(z->s); + z->scan_n = stbi__get8(z->s); + if (z->scan_n < 1 || z->scan_n > 4 || z->scan_n > (int) z->s->img_n) return stbi__err("bad SOS component count","Corrupt JPEG"); + if (Ls != 6+2*z->scan_n) return stbi__err("bad SOS len","Corrupt JPEG"); + for (i=0; i < z->scan_n; ++i) { + int id = stbi__get8(z->s), which; + int q = stbi__get8(z->s); + for (which = 0; which < z->s->img_n; ++which) + if (z->img_comp[which].id == id) + break; + if (which == z->s->img_n) return 0; // no match + z->img_comp[which].hd = q >> 4; if (z->img_comp[which].hd > 3) return stbi__err("bad DC huff","Corrupt JPEG"); + z->img_comp[which].ha = q & 15; if (z->img_comp[which].ha > 3) return stbi__err("bad AC huff","Corrupt JPEG"); + z->order[i] = which; + } + + { + int aa; + z->spec_start = stbi__get8(z->s); + z->spec_end = stbi__get8(z->s); // should be 63, but might be 0 + aa = stbi__get8(z->s); + z->succ_high = (aa >> 4); + z->succ_low = (aa & 15); + if (z->progressive) { + if (z->spec_start > 63 || z->spec_end > 63 || z->spec_start > z->spec_end || z->succ_high > 13 || z->succ_low > 13) + return stbi__err("bad SOS", "Corrupt JPEG"); + } else { + if (z->spec_start != 0) return stbi__err("bad SOS","Corrupt JPEG"); + if (z->succ_high != 0 || z->succ_low != 0) return stbi__err("bad SOS","Corrupt JPEG"); + z->spec_end = 63; + } + } + + return 1; +} + +static int stbi__process_frame_header(stbi__jpeg *z, int scan) +{ + stbi__context *s = z->s; + int Lf,p,i,q, h_max=1,v_max=1,c; + Lf = stbi__get16be(s); if (Lf < 11) return stbi__err("bad SOF len","Corrupt JPEG"); // JPEG + p = stbi__get8(s); if (p != 8) return stbi__err("only 8-bit","JPEG format not supported: 8-bit only"); // JPEG baseline + s->img_y = stbi__get16be(s); if (s->img_y == 0) return stbi__err("no header height", "JPEG format not supported: delayed height"); // Legal, but we don't handle it--but neither does IJG + s->img_x = stbi__get16be(s); if (s->img_x == 0) return stbi__err("0 width","Corrupt JPEG"); // JPEG requires + c = stbi__get8(s); + if (c != 3 && c != 1) return stbi__err("bad component count","Corrupt JPEG"); // JFIF requires + s->img_n = c; + for (i=0; i < c; ++i) { + z->img_comp[i].data = NULL; + z->img_comp[i].linebuf = NULL; + } + + if (Lf != 8+3*s->img_n) return stbi__err("bad SOF len","Corrupt JPEG"); + + z->rgb = 0; + for (i=0; i < s->img_n; ++i) { + static unsigned char rgb[3] = { 'R', 'G', 'B' }; + z->img_comp[i].id = stbi__get8(s); + if (z->img_comp[i].id != i+1) // JFIF requires + if (z->img_comp[i].id != i) { // some version of jpegtran outputs non-JFIF-compliant files! + // somethings output this (see http://fileformats.archiveteam.org/wiki/JPEG#Color_format) + if (z->img_comp[i].id != rgb[i]) + return stbi__err("bad component ID","Corrupt JPEG"); + ++z->rgb; + } + q = stbi__get8(s); + z->img_comp[i].h = (q >> 4); if (!z->img_comp[i].h || z->img_comp[i].h > 4) return stbi__err("bad H","Corrupt JPEG"); + z->img_comp[i].v = q & 15; if (!z->img_comp[i].v || z->img_comp[i].v > 4) return stbi__err("bad V","Corrupt JPEG"); + z->img_comp[i].tq = stbi__get8(s); if (z->img_comp[i].tq > 3) return stbi__err("bad TQ","Corrupt JPEG"); + } + + if (scan != STBI__SCAN_load) return 1; + + if ((1 << 30) / s->img_x / s->img_n < s->img_y) return stbi__err("too large", "Image too large to decode"); + + for (i=0; i < s->img_n; ++i) { + if (z->img_comp[i].h > h_max) h_max = z->img_comp[i].h; + if (z->img_comp[i].v > v_max) v_max = z->img_comp[i].v; + } + + // compute interleaved mcu info + z->img_h_max = h_max; + z->img_v_max = v_max; + z->img_mcu_w = h_max * 8; + z->img_mcu_h = v_max * 8; + z->img_mcu_x = (s->img_x + z->img_mcu_w-1) / z->img_mcu_w; + z->img_mcu_y = (s->img_y + z->img_mcu_h-1) / z->img_mcu_h; + + for (i=0; i < s->img_n; ++i) { + // number of effective pixels (e.g. for non-interleaved MCU) + z->img_comp[i].x = (s->img_x * z->img_comp[i].h + h_max-1) / h_max; + z->img_comp[i].y = (s->img_y * z->img_comp[i].v + v_max-1) / v_max; + // to simplify generation, we'll allocate enough memory to decode + // the bogus oversized data from using interleaved MCUs and their + // big blocks (e.g. a 16x16 iMCU on an image of width 33); we won't + // discard the extra data until colorspace conversion + z->img_comp[i].w2 = z->img_mcu_x * z->img_comp[i].h * 8; + z->img_comp[i].h2 = z->img_mcu_y * z->img_comp[i].v * 8; + z->img_comp[i].raw_data = stbi__malloc(z->img_comp[i].w2 * z->img_comp[i].h2+15); + + if (z->img_comp[i].raw_data == NULL) { + for(--i; i >= 0; --i) { + STBI_FREE(z->img_comp[i].raw_data); + z->img_comp[i].raw_data = NULL; + } + return stbi__err("outofmem", "Out of memory"); + } + // align blocks for idct using mmx/sse + z->img_comp[i].data = (stbi_uc*) (((size_t) z->img_comp[i].raw_data + 15) & ~15); + z->img_comp[i].linebuf = NULL; + if (z->progressive) { + z->img_comp[i].coeff_w = (z->img_comp[i].w2 + 7) >> 3; + z->img_comp[i].coeff_h = (z->img_comp[i].h2 + 7) >> 3; + z->img_comp[i].raw_coeff = STBI_MALLOC(z->img_comp[i].coeff_w * z->img_comp[i].coeff_h * 64 * sizeof(short) + 15); + z->img_comp[i].coeff = (short*) (((size_t) z->img_comp[i].raw_coeff + 15) & ~15); + } else { + z->img_comp[i].coeff = 0; + z->img_comp[i].raw_coeff = 0; + } + } + + return 1; +} + +// use comparisons since in some cases we handle more than one case (e.g. SOF) +#define stbi__DNL(x) ((x) == 0xdc) +#define stbi__SOI(x) ((x) == 0xd8) +#define stbi__EOI(x) ((x) == 0xd9) +#define stbi__SOF(x) ((x) == 0xc0 || (x) == 0xc1 || (x) == 0xc2) +#define stbi__SOS(x) ((x) == 0xda) + +#define stbi__SOF_progressive(x) ((x) == 0xc2) + +static int stbi__decode_jpeg_header(stbi__jpeg *z, int scan) +{ + int m; + z->marker = STBI__MARKER_none; // initialize cached marker to empty + m = stbi__get_marker(z); + if (!stbi__SOI(m)) return stbi__err("no SOI","Corrupt JPEG"); + if (scan == STBI__SCAN_type) return 1; + m = stbi__get_marker(z); + while (!stbi__SOF(m)) { + if (!stbi__process_marker(z,m)) return 0; + m = stbi__get_marker(z); + while (m == STBI__MARKER_none) { + // some files have extra padding after their blocks, so ok, we'll scan + if (stbi__at_eof(z->s)) return stbi__err("no SOF", "Corrupt JPEG"); + m = stbi__get_marker(z); + } + } + z->progressive = stbi__SOF_progressive(m); + if (!stbi__process_frame_header(z, scan)) return 0; + return 1; +} + +// decode image to YCbCr format +static int stbi__decode_jpeg_image(stbi__jpeg *j) +{ + int m; + for (m = 0; m < 4; m++) { + j->img_comp[m].raw_data = NULL; + j->img_comp[m].raw_coeff = NULL; + } + j->restart_interval = 0; + if (!stbi__decode_jpeg_header(j, STBI__SCAN_load)) return 0; + m = stbi__get_marker(j); + while (!stbi__EOI(m)) { + if (stbi__SOS(m)) { + if (!stbi__process_scan_header(j)) return 0; + if (!stbi__parse_entropy_coded_data(j)) return 0; + if (j->marker == STBI__MARKER_none ) { + // handle 0s at the end of image data from IP Kamera 9060 + while (!stbi__at_eof(j->s)) { + int x = stbi__get8(j->s); + if (x == 255) { + j->marker = stbi__get8(j->s); + break; + } else if (x != 0) { + return stbi__err("junk before marker", "Corrupt JPEG"); + } + } + // if we reach eof without hitting a marker, stbi__get_marker() below will fail and we'll eventually return 0 + } + } else { + if (!stbi__process_marker(j, m)) return 0; + } + m = stbi__get_marker(j); + } + if (j->progressive) + stbi__jpeg_finish(j); + return 1; +} + +// static jfif-centered resampling (across block boundaries) + +typedef stbi_uc *(*resample_row_func)(stbi_uc *out, stbi_uc *in0, stbi_uc *in1, + int w, int hs); + +#define stbi__div4(x) ((stbi_uc) ((x) >> 2)) + +static stbi_uc *resample_row_1(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + STBI_NOTUSED(out); + STBI_NOTUSED(in_far); + STBI_NOTUSED(w); + STBI_NOTUSED(hs); + return in_near; +} + +static stbi_uc* stbi__resample_row_v_2(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + // need to generate two samples vertically for every one in input + int i; + STBI_NOTUSED(hs); + for (i=0; i < w; ++i) + out[i] = stbi__div4(3*in_near[i] + in_far[i] + 2); + return out; +} + +static stbi_uc* stbi__resample_row_h_2(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + // need to generate two samples horizontally for every one in input + int i; + stbi_uc *input = in_near; + + if (w == 1) { + // if only one sample, can't do any interpolation + out[0] = out[1] = input[0]; + return out; + } + + out[0] = input[0]; + out[1] = stbi__div4(input[0]*3 + input[1] + 2); + for (i=1; i < w-1; ++i) { + int n = 3*input[i]+2; + out[i*2+0] = stbi__div4(n+input[i-1]); + out[i*2+1] = stbi__div4(n+input[i+1]); + } + out[i*2+0] = stbi__div4(input[w-2]*3 + input[w-1] + 2); + out[i*2+1] = input[w-1]; + + STBI_NOTUSED(in_far); + STBI_NOTUSED(hs); + + return out; +} + +#define stbi__div16(x) ((stbi_uc) ((x) >> 4)) + +static stbi_uc *stbi__resample_row_hv_2(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + // need to generate 2x2 samples for every one in input + int i,t0,t1; + if (w == 1) { + out[0] = out[1] = stbi__div4(3*in_near[0] + in_far[0] + 2); + return out; + } + + t1 = 3*in_near[0] + in_far[0]; + out[0] = stbi__div4(t1+2); + for (i=1; i < w; ++i) { + t0 = t1; + t1 = 3*in_near[i]+in_far[i]; + out[i*2-1] = stbi__div16(3*t0 + t1 + 8); + out[i*2 ] = stbi__div16(3*t1 + t0 + 8); + } + out[w*2-1] = stbi__div4(t1+2); + + STBI_NOTUSED(hs); + + return out; +} + +#if defined(STBI_SSE2) || defined(STBI_NEON) +static stbi_uc *stbi__resample_row_hv_2_simd(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + // need to generate 2x2 samples for every one in input + int i=0,t0,t1; + + if (w == 1) { + out[0] = out[1] = stbi__div4(3*in_near[0] + in_far[0] + 2); + return out; + } + + t1 = 3*in_near[0] + in_far[0]; + // process groups of 8 pixels for as long as we can. + // note we can't handle the last pixel in a row in this loop + // because we need to handle the filter boundary conditions. + for (; i < ((w-1) & ~7); i += 8) { +#if defined(STBI_SSE2) + // load and perform the vertical filtering pass + // this uses 3*x + y = 4*x + (y - x) + __m128i zero = _mm_setzero_si128(); + __m128i farb = _mm_loadl_epi64((__m128i *) (in_far + i)); + __m128i nearb = _mm_loadl_epi64((__m128i *) (in_near + i)); + __m128i farw = _mm_unpacklo_epi8(farb, zero); + __m128i nearw = _mm_unpacklo_epi8(nearb, zero); + __m128i diff = _mm_sub_epi16(farw, nearw); + __m128i nears = _mm_slli_epi16(nearw, 2); + __m128i curr = _mm_add_epi16(nears, diff); // current row + + // horizontal filter works the same based on shifted vers of current + // row. "prev" is current row shifted right by 1 pixel; we need to + // insert the previous pixel value (from t1). + // "next" is current row shifted left by 1 pixel, with first pixel + // of next block of 8 pixels added in. + __m128i prv0 = _mm_slli_si128(curr, 2); + __m128i nxt0 = _mm_srli_si128(curr, 2); + __m128i prev = _mm_insert_epi16(prv0, t1, 0); + __m128i next = _mm_insert_epi16(nxt0, 3*in_near[i+8] + in_far[i+8], 7); + + // horizontal filter, polyphase implementation since it's convenient: + // even pixels = 3*cur + prev = cur*4 + (prev - cur) + // odd pixels = 3*cur + next = cur*4 + (next - cur) + // note the shared term. + __m128i bias = _mm_set1_epi16(8); + __m128i curs = _mm_slli_epi16(curr, 2); + __m128i prvd = _mm_sub_epi16(prev, curr); + __m128i nxtd = _mm_sub_epi16(next, curr); + __m128i curb = _mm_add_epi16(curs, bias); + __m128i even = _mm_add_epi16(prvd, curb); + __m128i odd = _mm_add_epi16(nxtd, curb); + + // interleave even and odd pixels, then undo scaling. + __m128i int0 = _mm_unpacklo_epi16(even, odd); + __m128i int1 = _mm_unpackhi_epi16(even, odd); + __m128i de0 = _mm_srli_epi16(int0, 4); + __m128i de1 = _mm_srli_epi16(int1, 4); + + // pack and write output + __m128i outv = _mm_packus_epi16(de0, de1); + _mm_storeu_si128((__m128i *) (out + i*2), outv); +#elif defined(STBI_NEON) + // load and perform the vertical filtering pass + // this uses 3*x + y = 4*x + (y - x) + uint8x8_t farb = vld1_u8(in_far + i); + uint8x8_t nearb = vld1_u8(in_near + i); + int16x8_t diff = vreinterpretq_s16_u16(vsubl_u8(farb, nearb)); + int16x8_t nears = vreinterpretq_s16_u16(vshll_n_u8(nearb, 2)); + int16x8_t curr = vaddq_s16(nears, diff); // current row + + // horizontal filter works the same based on shifted vers of current + // row. "prev" is current row shifted right by 1 pixel; we need to + // insert the previous pixel value (from t1). + // "next" is current row shifted left by 1 pixel, with first pixel + // of next block of 8 pixels added in. + int16x8_t prv0 = vextq_s16(curr, curr, 7); + int16x8_t nxt0 = vextq_s16(curr, curr, 1); + int16x8_t prev = vsetq_lane_s16(t1, prv0, 0); + int16x8_t next = vsetq_lane_s16(3*in_near[i+8] + in_far[i+8], nxt0, 7); + + // horizontal filter, polyphase implementation since it's convenient: + // even pixels = 3*cur + prev = cur*4 + (prev - cur) + // odd pixels = 3*cur + next = cur*4 + (next - cur) + // note the shared term. + int16x8_t curs = vshlq_n_s16(curr, 2); + int16x8_t prvd = vsubq_s16(prev, curr); + int16x8_t nxtd = vsubq_s16(next, curr); + int16x8_t even = vaddq_s16(curs, prvd); + int16x8_t odd = vaddq_s16(curs, nxtd); + + // undo scaling and round, then store with even/odd phases interleaved + uint8x8x2_t o; + o.val[0] = vqrshrun_n_s16(even, 4); + o.val[1] = vqrshrun_n_s16(odd, 4); + vst2_u8(out + i*2, o); +#endif + + // "previous" value for next iter + t1 = 3*in_near[i+7] + in_far[i+7]; + } + + t0 = t1; + t1 = 3*in_near[i] + in_far[i]; + out[i*2] = stbi__div16(3*t1 + t0 + 8); + + for (++i; i < w; ++i) { + t0 = t1; + t1 = 3*in_near[i]+in_far[i]; + out[i*2-1] = stbi__div16(3*t0 + t1 + 8); + out[i*2 ] = stbi__div16(3*t1 + t0 + 8); + } + out[w*2-1] = stbi__div4(t1+2); + + STBI_NOTUSED(hs); + + return out; +} +#endif + +static stbi_uc *stbi__resample_row_generic(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + // resample with nearest-neighbor + int i,j; + STBI_NOTUSED(in_far); + for (i=0; i < w; ++i) + for (j=0; j < hs; ++j) + out[i*hs+j] = in_near[i]; + return out; +} + +#ifdef STBI_JPEG_OLD +// this is the same YCbCr-to-RGB calculation that stb_image has used +// historically before the algorithm changes in 1.49 +#define float2fixed(x) ((int) ((x) * 65536 + 0.5)) +static void stbi__YCbCr_to_RGB_row(stbi_uc *out, const stbi_uc *y, const stbi_uc *pcb, const stbi_uc *pcr, int count, int step) +{ + int i; + for (i=0; i < count; ++i) { + int y_fixed = (y[i] << 16) + 32768; // rounding + int r,g,b; + int cr = pcr[i] - 128; + int cb = pcb[i] - 128; + r = y_fixed + cr*float2fixed(1.40200f); + g = y_fixed - cr*float2fixed(0.71414f) - cb*float2fixed(0.34414f); + b = y_fixed + cb*float2fixed(1.77200f); + r >>= 16; + g >>= 16; + b >>= 16; + if ((unsigned) r > 255) { if (r < 0) r = 0; else r = 255; } + if ((unsigned) g > 255) { if (g < 0) g = 0; else g = 255; } + if ((unsigned) b > 255) { if (b < 0) b = 0; else b = 255; } + out[0] = (stbi_uc)r; + out[1] = (stbi_uc)g; + out[2] = (stbi_uc)b; + out[3] = 255; + out += step; + } +} +#else +// this is a reduced-precision calculation of YCbCr-to-RGB introduced +// to make sure the code produces the same results in both SIMD and scalar +#define float2fixed(x) (((int) ((x) * 4096.0f + 0.5f)) << 8) +static void stbi__YCbCr_to_RGB_row(stbi_uc *out, const stbi_uc *y, const stbi_uc *pcb, const stbi_uc *pcr, int count, int step) +{ + int i; + for (i=0; i < count; ++i) { + int y_fixed = (y[i] << 20) + (1<<19); // rounding + int r,g,b; + int cr = pcr[i] - 128; + int cb = pcb[i] - 128; + r = y_fixed + cr* float2fixed(1.40200f); + g = y_fixed + (cr*-float2fixed(0.71414f)) + ((cb*-float2fixed(0.34414f)) & 0xffff0000); + b = y_fixed + cb* float2fixed(1.77200f); + r >>= 20; + g >>= 20; + b >>= 20; + if ((unsigned) r > 255) { if (r < 0) r = 0; else r = 255; } + if ((unsigned) g > 255) { if (g < 0) g = 0; else g = 255; } + if ((unsigned) b > 255) { if (b < 0) b = 0; else b = 255; } + out[0] = (stbi_uc)r; + out[1] = (stbi_uc)g; + out[2] = (stbi_uc)b; + out[3] = 255; + out += step; + } +} +#endif + +#if defined(STBI_SSE2) || defined(STBI_NEON) +static void stbi__YCbCr_to_RGB_simd(stbi_uc *out, stbi_uc const *y, stbi_uc const *pcb, stbi_uc const *pcr, int count, int step) +{ + int i = 0; + +#ifdef STBI_SSE2 + // step == 3 is pretty ugly on the final interleave, and i'm not convinced + // it's useful in practice (you wouldn't use it for textures, for example). + // so just accelerate step == 4 case. + if (step == 4) { + // this is a fairly straightforward implementation and not super-optimized. + __m128i signflip = _mm_set1_epi8(-0x80); + __m128i cr_const0 = _mm_set1_epi16( (short) ( 1.40200f*4096.0f+0.5f)); + __m128i cr_const1 = _mm_set1_epi16( - (short) ( 0.71414f*4096.0f+0.5f)); + __m128i cb_const0 = _mm_set1_epi16( - (short) ( 0.34414f*4096.0f+0.5f)); + __m128i cb_const1 = _mm_set1_epi16( (short) ( 1.77200f*4096.0f+0.5f)); + __m128i y_bias = _mm_set1_epi8((char) (unsigned char) 128); + __m128i xw = _mm_set1_epi16(255); // alpha channel + + for (; i+7 < count; i += 8) { + // load + __m128i y_bytes = _mm_loadl_epi64((__m128i *) (y+i)); + __m128i cr_bytes = _mm_loadl_epi64((__m128i *) (pcr+i)); + __m128i cb_bytes = _mm_loadl_epi64((__m128i *) (pcb+i)); + __m128i cr_biased = _mm_xor_si128(cr_bytes, signflip); // -128 + __m128i cb_biased = _mm_xor_si128(cb_bytes, signflip); // -128 + + // unpack to short (and left-shift cr, cb by 8) + __m128i yw = _mm_unpacklo_epi8(y_bias, y_bytes); + __m128i crw = _mm_unpacklo_epi8(_mm_setzero_si128(), cr_biased); + __m128i cbw = _mm_unpacklo_epi8(_mm_setzero_si128(), cb_biased); + + // color transform + __m128i yws = _mm_srli_epi16(yw, 4); + __m128i cr0 = _mm_mulhi_epi16(cr_const0, crw); + __m128i cb0 = _mm_mulhi_epi16(cb_const0, cbw); + __m128i cb1 = _mm_mulhi_epi16(cbw, cb_const1); + __m128i cr1 = _mm_mulhi_epi16(crw, cr_const1); + __m128i rws = _mm_add_epi16(cr0, yws); + __m128i gwt = _mm_add_epi16(cb0, yws); + __m128i bws = _mm_add_epi16(yws, cb1); + __m128i gws = _mm_add_epi16(gwt, cr1); + + // descale + __m128i rw = _mm_srai_epi16(rws, 4); + __m128i bw = _mm_srai_epi16(bws, 4); + __m128i gw = _mm_srai_epi16(gws, 4); + + // back to byte, set up for transpose + __m128i brb = _mm_packus_epi16(rw, bw); + __m128i gxb = _mm_packus_epi16(gw, xw); + + // transpose to interleave channels + __m128i t0 = _mm_unpacklo_epi8(brb, gxb); + __m128i t1 = _mm_unpackhi_epi8(brb, gxb); + __m128i o0 = _mm_unpacklo_epi16(t0, t1); + __m128i o1 = _mm_unpackhi_epi16(t0, t1); + + // store + _mm_storeu_si128((__m128i *) (out + 0), o0); + _mm_storeu_si128((__m128i *) (out + 16), o1); + out += 32; + } + } +#endif + +#ifdef STBI_NEON + // in this version, step=3 support would be easy to add. but is there demand? + if (step == 4) { + // this is a fairly straightforward implementation and not super-optimized. + uint8x8_t signflip = vdup_n_u8(0x80); + int16x8_t cr_const0 = vdupq_n_s16( (short) ( 1.40200f*4096.0f+0.5f)); + int16x8_t cr_const1 = vdupq_n_s16( - (short) ( 0.71414f*4096.0f+0.5f)); + int16x8_t cb_const0 = vdupq_n_s16( - (short) ( 0.34414f*4096.0f+0.5f)); + int16x8_t cb_const1 = vdupq_n_s16( (short) ( 1.77200f*4096.0f+0.5f)); + + for (; i+7 < count; i += 8) { + // load + uint8x8_t y_bytes = vld1_u8(y + i); + uint8x8_t cr_bytes = vld1_u8(pcr + i); + uint8x8_t cb_bytes = vld1_u8(pcb + i); + int8x8_t cr_biased = vreinterpret_s8_u8(vsub_u8(cr_bytes, signflip)); + int8x8_t cb_biased = vreinterpret_s8_u8(vsub_u8(cb_bytes, signflip)); + + // expand to s16 + int16x8_t yws = vreinterpretq_s16_u16(vshll_n_u8(y_bytes, 4)); + int16x8_t crw = vshll_n_s8(cr_biased, 7); + int16x8_t cbw = vshll_n_s8(cb_biased, 7); + + // color transform + int16x8_t cr0 = vqdmulhq_s16(crw, cr_const0); + int16x8_t cb0 = vqdmulhq_s16(cbw, cb_const0); + int16x8_t cr1 = vqdmulhq_s16(crw, cr_const1); + int16x8_t cb1 = vqdmulhq_s16(cbw, cb_const1); + int16x8_t rws = vaddq_s16(yws, cr0); + int16x8_t gws = vaddq_s16(vaddq_s16(yws, cb0), cr1); + int16x8_t bws = vaddq_s16(yws, cb1); + + // undo scaling, round, convert to byte + uint8x8x4_t o; + o.val[0] = vqrshrun_n_s16(rws, 4); + o.val[1] = vqrshrun_n_s16(gws, 4); + o.val[2] = vqrshrun_n_s16(bws, 4); + o.val[3] = vdup_n_u8(255); + + // store, interleaving r/g/b/a + vst4_u8(out, o); + out += 8*4; + } + } +#endif + + for (; i < count; ++i) { + int y_fixed = (y[i] << 20) + (1<<19); // rounding + int r,g,b; + int cr = pcr[i] - 128; + int cb = pcb[i] - 128; + r = y_fixed + cr* float2fixed(1.40200f); + g = y_fixed + cr*-float2fixed(0.71414f) + ((cb*-float2fixed(0.34414f)) & 0xffff0000); + b = y_fixed + cb* float2fixed(1.77200f); + r >>= 20; + g >>= 20; + b >>= 20; + if ((unsigned) r > 255) { if (r < 0) r = 0; else r = 255; } + if ((unsigned) g > 255) { if (g < 0) g = 0; else g = 255; } + if ((unsigned) b > 255) { if (b < 0) b = 0; else b = 255; } + out[0] = (stbi_uc)r; + out[1] = (stbi_uc)g; + out[2] = (stbi_uc)b; + out[3] = 255; + out += step; + } +} +#endif + +// set up the kernels +static void stbi__setup_jpeg(stbi__jpeg *j) +{ + j->idct_block_kernel = stbi__idct_block; + j->YCbCr_to_RGB_kernel = stbi__YCbCr_to_RGB_row; + j->resample_row_hv_2_kernel = stbi__resample_row_hv_2; + +#ifdef STBI_SSE2 + if (stbi__sse2_available()) { + j->idct_block_kernel = stbi__idct_simd; + #ifndef STBI_JPEG_OLD + j->YCbCr_to_RGB_kernel = stbi__YCbCr_to_RGB_simd; + #endif + j->resample_row_hv_2_kernel = stbi__resample_row_hv_2_simd; + } +#endif + +#ifdef STBI_NEON + j->idct_block_kernel = stbi__idct_simd; + #ifndef STBI_JPEG_OLD + j->YCbCr_to_RGB_kernel = stbi__YCbCr_to_RGB_simd; + #endif + j->resample_row_hv_2_kernel = stbi__resample_row_hv_2_simd; +#endif +} + +// clean up the temporary component buffers +static void stbi__cleanup_jpeg(stbi__jpeg *j) +{ + int i; + for (i=0; i < j->s->img_n; ++i) { + if (j->img_comp[i].raw_data) { + STBI_FREE(j->img_comp[i].raw_data); + j->img_comp[i].raw_data = NULL; + j->img_comp[i].data = NULL; + } + if (j->img_comp[i].raw_coeff) { + STBI_FREE(j->img_comp[i].raw_coeff); + j->img_comp[i].raw_coeff = 0; + j->img_comp[i].coeff = 0; + } + if (j->img_comp[i].linebuf) { + STBI_FREE(j->img_comp[i].linebuf); + j->img_comp[i].linebuf = NULL; + } + } +} + +typedef struct +{ + resample_row_func resample; + stbi_uc *line0,*line1; + int hs,vs; // expansion factor in each axis + int w_lores; // horizontal pixels pre-expansion + int ystep; // how far through vertical expansion we are + int ypos; // which pre-expansion row we're on +} stbi__resample; + +static stbi_uc *load_jpeg_image(stbi__jpeg *z, int *out_x, int *out_y, int *comp, int req_comp) +{ + int n, decode_n; + z->s->img_n = 0; // make stbi__cleanup_jpeg safe + + // validate req_comp + if (req_comp < 0 || req_comp > 4) return stbi__errpuc("bad req_comp", "Internal error"); + + // load a jpeg image from whichever source, but leave in YCbCr format + if (!stbi__decode_jpeg_image(z)) { stbi__cleanup_jpeg(z); return NULL; } + + // determine actual number of components to generate + n = req_comp ? req_comp : z->s->img_n; + + if (z->s->img_n == 3 && n < 3) + decode_n = 1; + else + decode_n = z->s->img_n; + + // resample and color-convert + { + int k; + unsigned int i,j; + stbi_uc *output; + stbi_uc *coutput[4]; + + stbi__resample res_comp[4]; + + for (k=0; k < decode_n; ++k) { + stbi__resample *r = &res_comp[k]; + + // allocate line buffer big enough for upsampling off the edges + // with upsample factor of 4 + z->img_comp[k].linebuf = (stbi_uc *) stbi__malloc(z->s->img_x + 3); + if (!z->img_comp[k].linebuf) { stbi__cleanup_jpeg(z); return stbi__errpuc("outofmem", "Out of memory"); } + + r->hs = z->img_h_max / z->img_comp[k].h; + r->vs = z->img_v_max / z->img_comp[k].v; + r->ystep = r->vs >> 1; + r->w_lores = (z->s->img_x + r->hs-1) / r->hs; + r->ypos = 0; + r->line0 = r->line1 = z->img_comp[k].data; + + if (r->hs == 1 && r->vs == 1) r->resample = resample_row_1; + else if (r->hs == 1 && r->vs == 2) r->resample = stbi__resample_row_v_2; + else if (r->hs == 2 && r->vs == 1) r->resample = stbi__resample_row_h_2; + else if (r->hs == 2 && r->vs == 2) r->resample = z->resample_row_hv_2_kernel; + else r->resample = stbi__resample_row_generic; + } + + // can't error after this so, this is safe + output = (stbi_uc *) stbi__malloc(n * z->s->img_x * z->s->img_y + 1); + if (!output) { stbi__cleanup_jpeg(z); return stbi__errpuc("outofmem", "Out of memory"); } + + // now go ahead and resample + for (j=0; j < z->s->img_y; ++j) { + stbi_uc *out = output + n * z->s->img_x * j; + for (k=0; k < decode_n; ++k) { + stbi__resample *r = &res_comp[k]; + int y_bot = r->ystep >= (r->vs >> 1); + coutput[k] = r->resample(z->img_comp[k].linebuf, + y_bot ? r->line1 : r->line0, + y_bot ? r->line0 : r->line1, + r->w_lores, r->hs); + if (++r->ystep >= r->vs) { + r->ystep = 0; + r->line0 = r->line1; + if (++r->ypos < z->img_comp[k].y) + r->line1 += z->img_comp[k].w2; + } + } + if (n >= 3) { + stbi_uc *y = coutput[0]; + if (z->s->img_n == 3) { + if (z->rgb == 3) { + for (i=0; i < z->s->img_x; ++i) { + out[0] = y[i]; + out[1] = coutput[1][i]; + out[2] = coutput[2][i]; + out[3] = 255; + out += n; + } + } else { + z->YCbCr_to_RGB_kernel(out, y, coutput[1], coutput[2], z->s->img_x, n); + } + } else + for (i=0; i < z->s->img_x; ++i) { + out[0] = out[1] = out[2] = y[i]; + out[3] = 255; // not used if n==3 + out += n; + } + } else { + stbi_uc *y = coutput[0]; + if (n == 1) + for (i=0; i < z->s->img_x; ++i) out[i] = y[i]; + else + for (i=0; i < z->s->img_x; ++i) *out++ = y[i], *out++ = 255; + } + } + stbi__cleanup_jpeg(z); + *out_x = z->s->img_x; + *out_y = z->s->img_y; + if (comp) *comp = z->s->img_n; // report original components, not output + return output; + } +} + +static unsigned char *stbi__jpeg_load(stbi__context *s, int *x, int *y, int *comp, int req_comp) +{ + unsigned char* result; + stbi__jpeg* j = (stbi__jpeg*) stbi__malloc(sizeof(stbi__jpeg)); + j->s = s; + stbi__setup_jpeg(j); + result = load_jpeg_image(j, x,y,comp,req_comp); + STBI_FREE(j); + return result; +} + +static int stbi__jpeg_test(stbi__context *s) +{ + int r; + stbi__jpeg j; + j.s = s; + stbi__setup_jpeg(&j); + r = stbi__decode_jpeg_header(&j, STBI__SCAN_type); + stbi__rewind(s); + return r; +} + +static int stbi__jpeg_info_raw(stbi__jpeg *j, int *x, int *y, int *comp) +{ + if (!stbi__decode_jpeg_header(j, STBI__SCAN_header)) { + stbi__rewind( j->s ); + return 0; + } + if (x) *x = j->s->img_x; + if (y) *y = j->s->img_y; + if (comp) *comp = j->s->img_n; + return 1; +} + +static int stbi__jpeg_info(stbi__context *s, int *x, int *y, int *comp) +{ + int result; + stbi__jpeg* j = (stbi__jpeg*) (stbi__malloc(sizeof(stbi__jpeg))); + j->s = s; + result = stbi__jpeg_info_raw(j, x, y, comp); + STBI_FREE(j); + return result; +} +#endif + +// public domain zlib decode v0.2 Sean Barrett 2006-11-18 +// simple implementation +// - all input must be provided in an upfront buffer +// - all output is written to a single output buffer (can malloc/realloc) +// performance +// - fast huffman + +#ifndef STBI_NO_ZLIB + +// fast-way is faster to check than jpeg huffman, but slow way is slower +#define STBI__ZFAST_BITS 9 // accelerate all cases in default tables +#define STBI__ZFAST_MASK ((1 << STBI__ZFAST_BITS) - 1) + +// zlib-style huffman encoding +// (jpegs packs from left, zlib from right, so can't share code) +typedef struct +{ + stbi__uint16 fast[1 << STBI__ZFAST_BITS]; + stbi__uint16 firstcode[16]; + int maxcode[17]; + stbi__uint16 firstsymbol[16]; + stbi_uc size[288]; + stbi__uint16 value[288]; +} stbi__zhuffman; + +stbi_inline static int stbi__bitreverse16(int n) +{ + n = ((n & 0xAAAA) >> 1) | ((n & 0x5555) << 1); + n = ((n & 0xCCCC) >> 2) | ((n & 0x3333) << 2); + n = ((n & 0xF0F0) >> 4) | ((n & 0x0F0F) << 4); + n = ((n & 0xFF00) >> 8) | ((n & 0x00FF) << 8); + return n; +} + +stbi_inline static int stbi__bit_reverse(int v, int bits) +{ + STBI_ASSERT(bits <= 16); + // to bit reverse n bits, reverse 16 and shift + // e.g. 11 bits, bit reverse and shift away 5 + return stbi__bitreverse16(v) >> (16-bits); +} + +static int stbi__zbuild_huffman(stbi__zhuffman *z, stbi_uc *sizelist, int num) +{ + int i,k=0; + int code, next_code[16], sizes[17]; + + // DEFLATE spec for generating codes + memset(sizes, 0, sizeof(sizes)); + memset(z->fast, 0, sizeof(z->fast)); + for (i=0; i < num; ++i) + ++sizes[sizelist[i]]; + sizes[0] = 0; + for (i=1; i < 16; ++i) + if (sizes[i] > (1 << i)) + return stbi__err("bad sizes", "Corrupt PNG"); + code = 0; + for (i=1; i < 16; ++i) { + next_code[i] = code; + z->firstcode[i] = (stbi__uint16) code; + z->firstsymbol[i] = (stbi__uint16) k; + code = (code + sizes[i]); + if (sizes[i]) + if (code-1 >= (1 << i)) return stbi__err("bad codelengths","Corrupt PNG"); + z->maxcode[i] = code << (16-i); // preshift for inner loop + code <<= 1; + k += sizes[i]; + } + z->maxcode[16] = 0x10000; // sentinel + for (i=0; i < num; ++i) { + int s = sizelist[i]; + if (s) { + int c = next_code[s] - z->firstcode[s] + z->firstsymbol[s]; + stbi__uint16 fastv = (stbi__uint16) ((s << 9) | i); + z->size [c] = (stbi_uc ) s; + z->value[c] = (stbi__uint16) i; + if (s <= STBI__ZFAST_BITS) { + int j = stbi__bit_reverse(next_code[s],s); + while (j < (1 << STBI__ZFAST_BITS)) { + z->fast[j] = fastv; + j += (1 << s); + } + } + ++next_code[s]; + } + } + return 1; +} + +// zlib-from-memory implementation for PNG reading +// because PNG allows splitting the zlib stream arbitrarily, +// and it's annoying structurally to have PNG call ZLIB call PNG, +// we require PNG read all the IDATs and combine them into a single +// memory buffer + +typedef struct +{ + stbi_uc *zbuffer, *zbuffer_end; + int num_bits; + stbi__uint32 code_buffer; + + char *zout; + char *zout_start; + char *zout_end; + int z_expandable; + + stbi__zhuffman z_length, z_distance; +} stbi__zbuf; + +stbi_inline static stbi_uc stbi__zget8(stbi__zbuf *z) +{ + if (z->zbuffer >= z->zbuffer_end) return 0; + return *z->zbuffer++; +} + +static void stbi__fill_bits(stbi__zbuf *z) +{ + do { + STBI_ASSERT(z->code_buffer < (1U << z->num_bits)); + z->code_buffer |= (unsigned int) stbi__zget8(z) << z->num_bits; + z->num_bits += 8; + } while (z->num_bits <= 24); +} + +stbi_inline static unsigned int stbi__zreceive(stbi__zbuf *z, int n) +{ + unsigned int k; + if (z->num_bits < n) stbi__fill_bits(z); + k = z->code_buffer & ((1 << n) - 1); + z->code_buffer >>= n; + z->num_bits -= n; + return k; +} + +static int stbi__zhuffman_decode_slowpath(stbi__zbuf *a, stbi__zhuffman *z) +{ + int b,s,k; + // not resolved by fast table, so compute it the slow way + // use jpeg approach, which requires MSbits at top + k = stbi__bit_reverse(a->code_buffer, 16); + for (s=STBI__ZFAST_BITS+1; ; ++s) + if (k < z->maxcode[s]) + break; + if (s == 16) return -1; // invalid code! + // code size is s, so: + b = (k >> (16-s)) - z->firstcode[s] + z->firstsymbol[s]; + STBI_ASSERT(z->size[b] == s); + a->code_buffer >>= s; + a->num_bits -= s; + return z->value[b]; +} + +stbi_inline static int stbi__zhuffman_decode(stbi__zbuf *a, stbi__zhuffman *z) +{ + int b,s; + if (a->num_bits < 16) stbi__fill_bits(a); + b = z->fast[a->code_buffer & STBI__ZFAST_MASK]; + if (b) { + s = b >> 9; + a->code_buffer >>= s; + a->num_bits -= s; + return b & 511; + } + return stbi__zhuffman_decode_slowpath(a, z); +} + +static int stbi__zexpand(stbi__zbuf *z, char *zout, int n) // need to make room for n bytes +{ + char *q; + int cur, limit, old_limit; + z->zout = zout; + if (!z->z_expandable) return stbi__err("output buffer limit","Corrupt PNG"); + cur = (int) (z->zout - z->zout_start); + limit = old_limit = (int) (z->zout_end - z->zout_start); + while (cur + n > limit) + limit *= 2; + q = (char *) STBI_REALLOC_SIZED(z->zout_start, old_limit, limit); + STBI_NOTUSED(old_limit); + if (q == NULL) return stbi__err("outofmem", "Out of memory"); + z->zout_start = q; + z->zout = q + cur; + z->zout_end = q + limit; + return 1; +} + +static int stbi__zlength_base[31] = { + 3,4,5,6,7,8,9,10,11,13, + 15,17,19,23,27,31,35,43,51,59, + 67,83,99,115,131,163,195,227,258,0,0 }; + +static int stbi__zlength_extra[31]= +{ 0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0,0,0 }; + +static int stbi__zdist_base[32] = { 1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193, +257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577,0,0}; + +static int stbi__zdist_extra[32] = +{ 0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13}; + +static int stbi__parse_huffman_block(stbi__zbuf *a) +{ + char *zout = a->zout; + for(;;) { + int z = stbi__zhuffman_decode(a, &a->z_length); + if (z < 256) { + if (z < 0) return stbi__err("bad huffman code","Corrupt PNG"); // error in huffman codes + if (zout >= a->zout_end) { + if (!stbi__zexpand(a, zout, 1)) return 0; + zout = a->zout; + } + *zout++ = (char) z; + } else { + stbi_uc *p; + int len,dist; + if (z == 256) { + a->zout = zout; + return 1; + } + z -= 257; + len = stbi__zlength_base[z]; + if (stbi__zlength_extra[z]) len += stbi__zreceive(a, stbi__zlength_extra[z]); + z = stbi__zhuffman_decode(a, &a->z_distance); + if (z < 0) return stbi__err("bad huffman code","Corrupt PNG"); + dist = stbi__zdist_base[z]; + if (stbi__zdist_extra[z]) dist += stbi__zreceive(a, stbi__zdist_extra[z]); + if (zout - a->zout_start < dist) return stbi__err("bad dist","Corrupt PNG"); + if (zout + len > a->zout_end) { + if (!stbi__zexpand(a, zout, len)) return 0; + zout = a->zout; + } + p = (stbi_uc *) (zout - dist); + if (dist == 1) { // run of one byte; common in images. + stbi_uc v = *p; + if (len) { do *zout++ = v; while (--len); } + } else { + if (len) { do *zout++ = *p++; while (--len); } + } + } + } +} + +static int stbi__compute_huffman_codes(stbi__zbuf *a) +{ + static stbi_uc length_dezigzag[19] = { 16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15 }; + stbi__zhuffman z_codelength; + stbi_uc lencodes[286+32+137];//padding for maximum single op + stbi_uc codelength_sizes[19]; + int i,n; + + int hlit = stbi__zreceive(a,5) + 257; + int hdist = stbi__zreceive(a,5) + 1; + int hclen = stbi__zreceive(a,4) + 4; + + memset(codelength_sizes, 0, sizeof(codelength_sizes)); + for (i=0; i < hclen; ++i) { + int s = stbi__zreceive(a,3); + codelength_sizes[length_dezigzag[i]] = (stbi_uc) s; + } + if (!stbi__zbuild_huffman(&z_codelength, codelength_sizes, 19)) return 0; + + n = 0; + while (n < hlit + hdist) { + int c = stbi__zhuffman_decode(a, &z_codelength); + if (c < 0 || c >= 19) return stbi__err("bad codelengths", "Corrupt PNG"); + if (c < 16) + lencodes[n++] = (stbi_uc) c; + else if (c == 16) { + c = stbi__zreceive(a,2)+3; + memset(lencodes+n, lencodes[n-1], c); + n += c; + } else if (c == 17) { + c = stbi__zreceive(a,3)+3; + memset(lencodes+n, 0, c); + n += c; + } else { + STBI_ASSERT(c == 18); + c = stbi__zreceive(a,7)+11; + memset(lencodes+n, 0, c); + n += c; + } + } + if (n != hlit+hdist) return stbi__err("bad codelengths","Corrupt PNG"); + if (!stbi__zbuild_huffman(&a->z_length, lencodes, hlit)) return 0; + if (!stbi__zbuild_huffman(&a->z_distance, lencodes+hlit, hdist)) return 0; + return 1; +} + +static int stbi__parse_uncompressed_block(stbi__zbuf *a) +{ + stbi_uc header[4]; + int len,nlen,k; + if (a->num_bits & 7) + stbi__zreceive(a, a->num_bits & 7); // discard + // drain the bit-packed data into header + k = 0; + while (a->num_bits > 0) { + header[k++] = (stbi_uc) (a->code_buffer & 255); // suppress MSVC run-time check + a->code_buffer >>= 8; + a->num_bits -= 8; + } + STBI_ASSERT(a->num_bits == 0); + // now fill header the normal way + while (k < 4) + header[k++] = stbi__zget8(a); + len = header[1] * 256 + header[0]; + nlen = header[3] * 256 + header[2]; + if (nlen != (len ^ 0xffff)) return stbi__err("zlib corrupt","Corrupt PNG"); + if (a->zbuffer + len > a->zbuffer_end) return stbi__err("read past buffer","Corrupt PNG"); + if (a->zout + len > a->zout_end) + if (!stbi__zexpand(a, a->zout, len)) return 0; + memcpy(a->zout, a->zbuffer, len); + a->zbuffer += len; + a->zout += len; + return 1; +} + +static int stbi__parse_zlib_header(stbi__zbuf *a) +{ + int cmf = stbi__zget8(a); + int cm = cmf & 15; + /* int cinfo = cmf >> 4; */ + int flg = stbi__zget8(a); + if ((cmf*256+flg) % 31 != 0) return stbi__err("bad zlib header","Corrupt PNG"); // zlib spec + if (flg & 32) return stbi__err("no preset dict","Corrupt PNG"); // preset dictionary not allowed in png + if (cm != 8) return stbi__err("bad compression","Corrupt PNG"); // DEFLATE required for png + // window = 1 << (8 + cinfo)... but who cares, we fully buffer output + return 1; +} + +// @TODO: should statically initialize these for optimal thread safety +static stbi_uc stbi__zdefault_length[288], stbi__zdefault_distance[32]; +static void stbi__init_zdefaults(void) +{ + int i; // use <= to match clearly with spec + for (i=0; i <= 143; ++i) stbi__zdefault_length[i] = 8; + for ( ; i <= 255; ++i) stbi__zdefault_length[i] = 9; + for ( ; i <= 279; ++i) stbi__zdefault_length[i] = 7; + for ( ; i <= 287; ++i) stbi__zdefault_length[i] = 8; + + for (i=0; i <= 31; ++i) stbi__zdefault_distance[i] = 5; +} + +static int stbi__parse_zlib(stbi__zbuf *a, int parse_header) +{ + int final, type; + if (parse_header) + if (!stbi__parse_zlib_header(a)) return 0; + a->num_bits = 0; + a->code_buffer = 0; + do { + final = stbi__zreceive(a,1); + type = stbi__zreceive(a,2); + if (type == 0) { + if (!stbi__parse_uncompressed_block(a)) return 0; + } else if (type == 3) { + return 0; + } else { + if (type == 1) { + // use fixed code lengths + if (!stbi__zdefault_distance[31]) stbi__init_zdefaults(); + if (!stbi__zbuild_huffman(&a->z_length , stbi__zdefault_length , 288)) return 0; + if (!stbi__zbuild_huffman(&a->z_distance, stbi__zdefault_distance, 32)) return 0; + } else { + if (!stbi__compute_huffman_codes(a)) return 0; + } + if (!stbi__parse_huffman_block(a)) return 0; + } + } while (!final); + return 1; +} + +static int stbi__do_zlib(stbi__zbuf *a, char *obuf, int olen, int exp, int parse_header) +{ + a->zout_start = obuf; + a->zout = obuf; + a->zout_end = obuf + olen; + a->z_expandable = exp; + + return stbi__parse_zlib(a, parse_header); +} + +STBIDEF char *stbi_zlib_decode_malloc_guesssize(const char *buffer, int len, int initial_size, int *outlen) +{ + stbi__zbuf a; + char *p = (char *) stbi__malloc(initial_size); + if (p == NULL) return NULL; + a.zbuffer = (stbi_uc *) buffer; + a.zbuffer_end = (stbi_uc *) buffer + len; + if (stbi__do_zlib(&a, p, initial_size, 1, 1)) { + if (outlen) *outlen = (int) (a.zout - a.zout_start); + return a.zout_start; + } else { + STBI_FREE(a.zout_start); + return NULL; + } +} + +STBIDEF char *stbi_zlib_decode_malloc(char const *buffer, int len, int *outlen) +{ + return stbi_zlib_decode_malloc_guesssize(buffer, len, 16384, outlen); +} + +STBIDEF char *stbi_zlib_decode_malloc_guesssize_headerflag(const char *buffer, int len, int initial_size, int *outlen, int parse_header) +{ + stbi__zbuf a; + char *p = (char *) stbi__malloc(initial_size); + if (p == NULL) return NULL; + a.zbuffer = (stbi_uc *) buffer; + a.zbuffer_end = (stbi_uc *) buffer + len; + if (stbi__do_zlib(&a, p, initial_size, 1, parse_header)) { + if (outlen) *outlen = (int) (a.zout - a.zout_start); + return a.zout_start; + } else { + STBI_FREE(a.zout_start); + return NULL; + } +} + +STBIDEF int stbi_zlib_decode_buffer(char *obuffer, int olen, char const *ibuffer, int ilen) +{ + stbi__zbuf a; + a.zbuffer = (stbi_uc *) ibuffer; + a.zbuffer_end = (stbi_uc *) ibuffer + ilen; + if (stbi__do_zlib(&a, obuffer, olen, 0, 1)) + return (int) (a.zout - a.zout_start); + else + return -1; +} + +STBIDEF char *stbi_zlib_decode_noheader_malloc(char const *buffer, int len, int *outlen) +{ + stbi__zbuf a; + char *p = (char *) stbi__malloc(16384); + if (p == NULL) return NULL; + a.zbuffer = (stbi_uc *) buffer; + a.zbuffer_end = (stbi_uc *) buffer+len; + if (stbi__do_zlib(&a, p, 16384, 1, 0)) { + if (outlen) *outlen = (int) (a.zout - a.zout_start); + return a.zout_start; + } else { + STBI_FREE(a.zout_start); + return NULL; + } +} + +STBIDEF int stbi_zlib_decode_noheader_buffer(char *obuffer, int olen, const char *ibuffer, int ilen) +{ + stbi__zbuf a; + a.zbuffer = (stbi_uc *) ibuffer; + a.zbuffer_end = (stbi_uc *) ibuffer + ilen; + if (stbi__do_zlib(&a, obuffer, olen, 0, 0)) + return (int) (a.zout - a.zout_start); + else + return -1; +} +#endif + +// public domain "baseline" PNG decoder v0.10 Sean Barrett 2006-11-18 +// simple implementation +// - only 8-bit samples +// - no CRC checking +// - allocates lots of intermediate memory +// - avoids problem of streaming data between subsystems +// - avoids explicit window management +// performance +// - uses stb_zlib, a PD zlib implementation with fast huffman decoding + +#ifndef STBI_NO_PNG +typedef struct +{ + stbi__uint32 length; + stbi__uint32 type; +} stbi__pngchunk; + +static stbi__pngchunk stbi__get_chunk_header(stbi__context *s) +{ + stbi__pngchunk c; + c.length = stbi__get32be(s); + c.type = stbi__get32be(s); + return c; +} + +static int stbi__check_png_header(stbi__context *s) +{ + static stbi_uc png_sig[8] = { 137,80,78,71,13,10,26,10 }; + int i; + for (i=0; i < 8; ++i) + if (stbi__get8(s) != png_sig[i]) return stbi__err("bad png sig","Not a PNG"); + return 1; +} + +typedef struct +{ + stbi__context *s; + stbi_uc *idata, *expanded, *out; + int depth; +} stbi__png; + + +enum { + STBI__F_none=0, + STBI__F_sub=1, + STBI__F_up=2, + STBI__F_avg=3, + STBI__F_paeth=4, + // synthetic filters used for first scanline to avoid needing a dummy row of 0s + STBI__F_avg_first, + STBI__F_paeth_first +}; + +static stbi_uc first_row_filter[5] = +{ + STBI__F_none, + STBI__F_sub, + STBI__F_none, + STBI__F_avg_first, + STBI__F_paeth_first +}; + +static int stbi__paeth(int a, int b, int c) +{ + int p = a + b - c; + int pa = abs(p-a); + int pb = abs(p-b); + int pc = abs(p-c); + if (pa <= pb && pa <= pc) return a; + if (pb <= pc) return b; + return c; +} + +static stbi_uc stbi__depth_scale_table[9] = { 0, 0xff, 0x55, 0, 0x11, 0,0,0, 0x01 }; + +// create the png data from post-deflated data +static int stbi__create_png_image_raw(stbi__png *a, stbi_uc *raw, stbi__uint32 raw_len, int out_n, stbi__uint32 x, stbi__uint32 y, int depth, int color) +{ + int bytes = (depth == 16? 2 : 1); + stbi__context *s = a->s; + stbi__uint32 i,j,stride = x*out_n*bytes; + stbi__uint32 img_len, img_width_bytes; + int k; + int img_n = s->img_n; // copy it into a local for later + + int output_bytes = out_n*bytes; + int filter_bytes = img_n*bytes; + int width = x; + + STBI_ASSERT(out_n == s->img_n || out_n == s->img_n+1); + a->out = (stbi_uc *) stbi__malloc(x * y * output_bytes); // extra bytes to write off the end into + if (!a->out) return stbi__err("outofmem", "Out of memory"); + + img_width_bytes = (((img_n * x * depth) + 7) >> 3); + img_len = (img_width_bytes + 1) * y; + if (s->img_x == x && s->img_y == y) { + if (raw_len != img_len) return stbi__err("not enough pixels","Corrupt PNG"); + } else { // interlaced: + if (raw_len < img_len) return stbi__err("not enough pixels","Corrupt PNG"); + } + + for (j=0; j < y; ++j) { + stbi_uc *cur = a->out + stride*j; + stbi_uc *prior = cur - stride; + int filter = *raw++; + + if (filter > 4) + return stbi__err("invalid filter","Corrupt PNG"); + + if (depth < 8) { + STBI_ASSERT(img_width_bytes <= x); + cur += x*out_n - img_width_bytes; // store output to the rightmost img_len bytes, so we can decode in place + filter_bytes = 1; + width = img_width_bytes; + } + + // if first row, use special filter that doesn't sample previous row + if (j == 0) filter = first_row_filter[filter]; + + // handle first byte explicitly + for (k=0; k < filter_bytes; ++k) { + switch (filter) { + case STBI__F_none : cur[k] = raw[k]; break; + case STBI__F_sub : cur[k] = raw[k]; break; + case STBI__F_up : cur[k] = STBI__BYTECAST(raw[k] + prior[k]); break; + case STBI__F_avg : cur[k] = STBI__BYTECAST(raw[k] + (prior[k]>>1)); break; + case STBI__F_paeth : cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(0,prior[k],0)); break; + case STBI__F_avg_first : cur[k] = raw[k]; break; + case STBI__F_paeth_first: cur[k] = raw[k]; break; + } + } + + if (depth == 8) { + if (img_n != out_n) + cur[img_n] = 255; // first pixel + raw += img_n; + cur += out_n; + prior += out_n; + } else if (depth == 16) { + if (img_n != out_n) { + cur[filter_bytes] = 255; // first pixel top byte + cur[filter_bytes+1] = 255; // first pixel bottom byte + } + raw += filter_bytes; + cur += output_bytes; + prior += output_bytes; + } else { + raw += 1; + cur += 1; + prior += 1; + } + + // this is a little gross, so that we don't switch per-pixel or per-component + if (depth < 8 || img_n == out_n) { + int nk = (width - 1)*filter_bytes; + #define CASE(f) \ + case f: \ + for (k=0; k < nk; ++k) + switch (filter) { + // "none" filter turns into a memcpy here; make that explicit. + case STBI__F_none: memcpy(cur, raw, nk); break; + CASE(STBI__F_sub) cur[k] = STBI__BYTECAST(raw[k] + cur[k-filter_bytes]); break; + CASE(STBI__F_up) cur[k] = STBI__BYTECAST(raw[k] + prior[k]); break; + CASE(STBI__F_avg) cur[k] = STBI__BYTECAST(raw[k] + ((prior[k] + cur[k-filter_bytes])>>1)); break; + CASE(STBI__F_paeth) cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k-filter_bytes],prior[k],prior[k-filter_bytes])); break; + CASE(STBI__F_avg_first) cur[k] = STBI__BYTECAST(raw[k] + (cur[k-filter_bytes] >> 1)); break; + CASE(STBI__F_paeth_first) cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k-filter_bytes],0,0)); break; + } + #undef CASE + raw += nk; + } else { + STBI_ASSERT(img_n+1 == out_n); + #define CASE(f) \ + case f: \ + for (i=x-1; i >= 1; --i, cur[filter_bytes]=255,raw+=filter_bytes,cur+=output_bytes,prior+=output_bytes) \ + for (k=0; k < filter_bytes; ++k) + switch (filter) { + CASE(STBI__F_none) cur[k] = raw[k]; break; + CASE(STBI__F_sub) cur[k] = STBI__BYTECAST(raw[k] + cur[k- output_bytes]); break; + CASE(STBI__F_up) cur[k] = STBI__BYTECAST(raw[k] + prior[k]); break; + CASE(STBI__F_avg) cur[k] = STBI__BYTECAST(raw[k] + ((prior[k] + cur[k- output_bytes])>>1)); break; + CASE(STBI__F_paeth) cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k- output_bytes],prior[k],prior[k- output_bytes])); break; + CASE(STBI__F_avg_first) cur[k] = STBI__BYTECAST(raw[k] + (cur[k- output_bytes] >> 1)); break; + CASE(STBI__F_paeth_first) cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k- output_bytes],0,0)); break; + } + #undef CASE + + // the loop above sets the high byte of the pixels' alpha, but for + // 16 bit png files we also need the low byte set. we'll do that here. + if (depth == 16) { + cur = a->out + stride*j; // start at the beginning of the row again + for (i=0; i < x; ++i,cur+=output_bytes) { + cur[filter_bytes+1] = 255; + } + } + } + } + + // we make a separate pass to expand bits to pixels; for performance, + // this could run two scanlines behind the above code, so it won't + // intefere with filtering but will still be in the cache. + if (depth < 8) { + for (j=0; j < y; ++j) { + stbi_uc *cur = a->out + stride*j; + stbi_uc *in = a->out + stride*j + x*out_n - img_width_bytes; + // unpack 1/2/4-bit into a 8-bit buffer. allows us to keep the common 8-bit path optimal at minimal cost for 1/2/4-bit + // png guarante byte alignment, if width is not multiple of 8/4/2 we'll decode dummy trailing data that will be skipped in the later loop + stbi_uc scale = (color == 0) ? stbi__depth_scale_table[depth] : 1; // scale grayscale values to 0..255 range + + // note that the final byte might overshoot and write more data than desired. + // we can allocate enough data that this never writes out of memory, but it + // could also overwrite the next scanline. can it overwrite non-empty data + // on the next scanline? yes, consider 1-pixel-wide scanlines with 1-bit-per-pixel. + // so we need to explicitly clamp the final ones + + if (depth == 4) { + for (k=x*img_n; k >= 2; k-=2, ++in) { + *cur++ = scale * ((*in >> 4) ); + *cur++ = scale * ((*in ) & 0x0f); + } + if (k > 0) *cur++ = scale * ((*in >> 4) ); + } else if (depth == 2) { + for (k=x*img_n; k >= 4; k-=4, ++in) { + *cur++ = scale * ((*in >> 6) ); + *cur++ = scale * ((*in >> 4) & 0x03); + *cur++ = scale * ((*in >> 2) & 0x03); + *cur++ = scale * ((*in ) & 0x03); + } + if (k > 0) *cur++ = scale * ((*in >> 6) ); + if (k > 1) *cur++ = scale * ((*in >> 4) & 0x03); + if (k > 2) *cur++ = scale * ((*in >> 2) & 0x03); + } else if (depth == 1) { + for (k=x*img_n; k >= 8; k-=8, ++in) { + *cur++ = scale * ((*in >> 7) ); + *cur++ = scale * ((*in >> 6) & 0x01); + *cur++ = scale * ((*in >> 5) & 0x01); + *cur++ = scale * ((*in >> 4) & 0x01); + *cur++ = scale * ((*in >> 3) & 0x01); + *cur++ = scale * ((*in >> 2) & 0x01); + *cur++ = scale * ((*in >> 1) & 0x01); + *cur++ = scale * ((*in ) & 0x01); + } + if (k > 0) *cur++ = scale * ((*in >> 7) ); + if (k > 1) *cur++ = scale * ((*in >> 6) & 0x01); + if (k > 2) *cur++ = scale * ((*in >> 5) & 0x01); + if (k > 3) *cur++ = scale * ((*in >> 4) & 0x01); + if (k > 4) *cur++ = scale * ((*in >> 3) & 0x01); + if (k > 5) *cur++ = scale * ((*in >> 2) & 0x01); + if (k > 6) *cur++ = scale * ((*in >> 1) & 0x01); + } + if (img_n != out_n) { + int q; + // insert alpha = 255 + cur = a->out + stride*j; + if (img_n == 1) { + for (q=x-1; q >= 0; --q) { + cur[q*2+1] = 255; + cur[q*2+0] = cur[q]; + } + } else { + STBI_ASSERT(img_n == 3); + for (q=x-1; q >= 0; --q) { + cur[q*4+3] = 255; + cur[q*4+2] = cur[q*3+2]; + cur[q*4+1] = cur[q*3+1]; + cur[q*4+0] = cur[q*3+0]; + } + } + } + } + } else if (depth == 16) { + // force the image data from big-endian to platform-native. + // this is done in a separate pass due to the decoding relying + // on the data being untouched, but could probably be done + // per-line during decode if care is taken. + stbi_uc *cur = a->out; + stbi__uint16 *cur16 = (stbi__uint16*)cur; + + for(i=0; i < x*y*out_n; ++i,cur16++,cur+=2) { + *cur16 = (cur[0] << 8) | cur[1]; + } + } + + return 1; +} + +static int stbi__create_png_image(stbi__png *a, stbi_uc *image_data, stbi__uint32 image_data_len, int out_n, int depth, int color, int interlaced) +{ + stbi_uc *final; + int p; + if (!interlaced) + return stbi__create_png_image_raw(a, image_data, image_data_len, out_n, a->s->img_x, a->s->img_y, depth, color); + + // de-interlacing + final = (stbi_uc *) stbi__malloc(a->s->img_x * a->s->img_y * out_n); + for (p=0; p < 7; ++p) { + int xorig[] = { 0,4,0,2,0,1,0 }; + int yorig[] = { 0,0,4,0,2,0,1 }; + int xspc[] = { 8,8,4,4,2,2,1 }; + int yspc[] = { 8,8,8,4,4,2,2 }; + int i,j,x,y; + // pass1_x[4] = 0, pass1_x[5] = 1, pass1_x[12] = 1 + x = (a->s->img_x - xorig[p] + xspc[p]-1) / xspc[p]; + y = (a->s->img_y - yorig[p] + yspc[p]-1) / yspc[p]; + if (x && y) { + stbi__uint32 img_len = ((((a->s->img_n * x * depth) + 7) >> 3) + 1) * y; + if (!stbi__create_png_image_raw(a, image_data, image_data_len, out_n, x, y, depth, color)) { + STBI_FREE(final); + return 0; + } + for (j=0; j < y; ++j) { + for (i=0; i < x; ++i) { + int out_y = j*yspc[p]+yorig[p]; + int out_x = i*xspc[p]+xorig[p]; + memcpy(final + out_y*a->s->img_x*out_n + out_x*out_n, + a->out + (j*x+i)*out_n, out_n); + } + } + STBI_FREE(a->out); + image_data += img_len; + image_data_len -= img_len; + } + } + a->out = final; + + return 1; +} + +static int stbi__compute_transparency(stbi__png *z, stbi_uc tc[3], int out_n) +{ + stbi__context *s = z->s; + stbi__uint32 i, pixel_count = s->img_x * s->img_y; + stbi_uc *p = z->out; + + // compute color-based transparency, assuming we've + // already got 255 as the alpha value in the output + STBI_ASSERT(out_n == 2 || out_n == 4); + + if (out_n == 2) { + for (i=0; i < pixel_count; ++i) { + p[1] = (p[0] == tc[0] ? 0 : 255); + p += 2; + } + } else { + for (i=0; i < pixel_count; ++i) { + if (p[0] == tc[0] && p[1] == tc[1] && p[2] == tc[2]) + p[3] = 0; + p += 4; + } + } + return 1; +} + +static int stbi__compute_transparency16(stbi__png *z, stbi__uint16 tc[3], int out_n) +{ + stbi__context *s = z->s; + stbi__uint32 i, pixel_count = s->img_x * s->img_y; + stbi__uint16 *p = (stbi__uint16*) z->out; + + // compute color-based transparency, assuming we've + // already got 65535 as the alpha value in the output + STBI_ASSERT(out_n == 2 || out_n == 4); + + if (out_n == 2) { + for (i = 0; i < pixel_count; ++i) { + p[1] = (p[0] == tc[0] ? 0 : 65535); + p += 2; + } + } else { + for (i = 0; i < pixel_count; ++i) { + if (p[0] == tc[0] && p[1] == tc[1] && p[2] == tc[2]) + p[3] = 0; + p += 4; + } + } + return 1; +} + +static int stbi__expand_png_palette(stbi__png *a, stbi_uc *palette, int len, int pal_img_n) +{ + stbi__uint32 i, pixel_count = a->s->img_x * a->s->img_y; + stbi_uc *p, *temp_out, *orig = a->out; + + p = (stbi_uc *) stbi__malloc(pixel_count * pal_img_n); + if (p == NULL) return stbi__err("outofmem", "Out of memory"); + + // between here and free(out) below, exitting would leak + temp_out = p; + + if (pal_img_n == 3) { + for (i=0; i < pixel_count; ++i) { + int n = orig[i]*4; + p[0] = palette[n ]; + p[1] = palette[n+1]; + p[2] = palette[n+2]; + p += 3; + } + } else { + for (i=0; i < pixel_count; ++i) { + int n = orig[i]*4; + p[0] = palette[n ]; + p[1] = palette[n+1]; + p[2] = palette[n+2]; + p[3] = palette[n+3]; + p += 4; + } + } + STBI_FREE(a->out); + a->out = temp_out; + + STBI_NOTUSED(len); + + return 1; +} + +static int stbi__reduce_png(stbi__png *p) +{ + int i; + int img_len = p->s->img_x * p->s->img_y * p->s->img_out_n; + stbi_uc *reduced; + stbi__uint16 *orig = (stbi__uint16*)p->out; + + if (p->depth != 16) return 1; // don't need to do anything if not 16-bit data + + reduced = (stbi_uc *)stbi__malloc(img_len); + if (p == NULL) return stbi__err("outofmem", "Out of memory"); + + for (i = 0; i < img_len; ++i) reduced[i] = (stbi_uc)((orig[i] >> 8) & 0xFF); // top half of each byte is a decent approx of 16->8 bit scaling + + p->out = reduced; + STBI_FREE(orig); + + return 1; +} + +static int stbi__unpremultiply_on_load = 0; +static int stbi__de_iphone_flag = 0; + +STBIDEF void stbi_set_unpremultiply_on_load(int flag_true_if_should_unpremultiply) +{ + stbi__unpremultiply_on_load = flag_true_if_should_unpremultiply; +} + +STBIDEF void stbi_convert_iphone_png_to_rgb(int flag_true_if_should_convert) +{ + stbi__de_iphone_flag = flag_true_if_should_convert; +} + +static void stbi__de_iphone(stbi__png *z) +{ + stbi__context *s = z->s; + stbi__uint32 i, pixel_count = s->img_x * s->img_y; + stbi_uc *p = z->out; + + if (s->img_out_n == 3) { // convert bgr to rgb + for (i=0; i < pixel_count; ++i) { + stbi_uc t = p[0]; + p[0] = p[2]; + p[2] = t; + p += 3; + } + } else { + STBI_ASSERT(s->img_out_n == 4); + if (stbi__unpremultiply_on_load) { + // convert bgr to rgb and unpremultiply + for (i=0; i < pixel_count; ++i) { + stbi_uc a = p[3]; + stbi_uc t = p[0]; + if (a) { + p[0] = p[2] * 255 / a; + p[1] = p[1] * 255 / a; + p[2] = t * 255 / a; + } else { + p[0] = p[2]; + p[2] = t; + } + p += 4; + } + } else { + // convert bgr to rgb + for (i=0; i < pixel_count; ++i) { + stbi_uc t = p[0]; + p[0] = p[2]; + p[2] = t; + p += 4; + } + } + } +} + +#define STBI__PNG_TYPE(a,b,c,d) (((a) << 24) + ((b) << 16) + ((c) << 8) + (d)) + +static int stbi__parse_png_file(stbi__png *z, int scan, int req_comp) +{ + stbi_uc palette[1024], pal_img_n=0; + stbi_uc has_trans=0, tc[3]; + stbi__uint16 tc16[3]; + stbi__uint32 ioff=0, idata_limit=0, i, pal_len=0; + int first=1,k,interlace=0, color=0, is_iphone=0; + stbi__context *s = z->s; + + z->expanded = NULL; + z->idata = NULL; + z->out = NULL; + + if (!stbi__check_png_header(s)) return 0; + + if (scan == STBI__SCAN_type) return 1; + + for (;;) { + stbi__pngchunk c = stbi__get_chunk_header(s); + switch (c.type) { + case STBI__PNG_TYPE('C','g','B','I'): + is_iphone = 1; + stbi__skip(s, c.length); + break; + case STBI__PNG_TYPE('I','H','D','R'): { + int comp,filter; + if (!first) return stbi__err("multiple IHDR","Corrupt PNG"); + first = 0; + if (c.length != 13) return stbi__err("bad IHDR len","Corrupt PNG"); + s->img_x = stbi__get32be(s); if (s->img_x > (1 << 24)) return stbi__err("too large","Very large image (corrupt?)"); + s->img_y = stbi__get32be(s); if (s->img_y > (1 << 24)) return stbi__err("too large","Very large image (corrupt?)"); + z->depth = stbi__get8(s); if (z->depth != 1 && z->depth != 2 && z->depth != 4 && z->depth != 8 && z->depth != 16) return stbi__err("1/2/4/8/16-bit only","PNG not supported: 1/2/4/8/16-bit only"); + color = stbi__get8(s); if (color > 6) return stbi__err("bad ctype","Corrupt PNG"); + if (color == 3 && z->depth == 16) return stbi__err("bad ctype","Corrupt PNG"); + if (color == 3) pal_img_n = 3; else if (color & 1) return stbi__err("bad ctype","Corrupt PNG"); + comp = stbi__get8(s); if (comp) return stbi__err("bad comp method","Corrupt PNG"); + filter= stbi__get8(s); if (filter) return stbi__err("bad filter method","Corrupt PNG"); + interlace = stbi__get8(s); if (interlace>1) return stbi__err("bad interlace method","Corrupt PNG"); + if (!s->img_x || !s->img_y) return stbi__err("0-pixel image","Corrupt PNG"); + if (!pal_img_n) { + s->img_n = (color & 2 ? 3 : 1) + (color & 4 ? 1 : 0); + if ((1 << 30) / s->img_x / s->img_n < s->img_y) return stbi__err("too large", "Image too large to decode"); + if (scan == STBI__SCAN_header) return 1; + } else { + // if paletted, then pal_n is our final components, and + // img_n is # components to decompress/filter. + s->img_n = 1; + if ((1 << 30) / s->img_x / 4 < s->img_y) return stbi__err("too large","Corrupt PNG"); + // if SCAN_header, have to scan to see if we have a tRNS + } + break; + } + + case STBI__PNG_TYPE('P','L','T','E'): { + if (first) return stbi__err("first not IHDR", "Corrupt PNG"); + if (c.length > 256*3) return stbi__err("invalid PLTE","Corrupt PNG"); + pal_len = c.length / 3; + if (pal_len * 3 != c.length) return stbi__err("invalid PLTE","Corrupt PNG"); + for (i=0; i < pal_len; ++i) { + palette[i*4+0] = stbi__get8(s); + palette[i*4+1] = stbi__get8(s); + palette[i*4+2] = stbi__get8(s); + palette[i*4+3] = 255; + } + break; + } + + case STBI__PNG_TYPE('t','R','N','S'): { + if (first) return stbi__err("first not IHDR", "Corrupt PNG"); + if (z->idata) return stbi__err("tRNS after IDAT","Corrupt PNG"); + if (pal_img_n) { + if (scan == STBI__SCAN_header) { s->img_n = 4; return 1; } + if (pal_len == 0) return stbi__err("tRNS before PLTE","Corrupt PNG"); + if (c.length > pal_len) return stbi__err("bad tRNS len","Corrupt PNG"); + pal_img_n = 4; + for (i=0; i < c.length; ++i) + palette[i*4+3] = stbi__get8(s); + } else { + if (!(s->img_n & 1)) return stbi__err("tRNS with alpha","Corrupt PNG"); + if (c.length != (stbi__uint32) s->img_n*2) return stbi__err("bad tRNS len","Corrupt PNG"); + has_trans = 1; + if (z->depth == 16) { + for (k = 0; k < s->img_n; ++k) tc16[k] = stbi__get16be(s); // copy the values as-is + } else { + for (k = 0; k < s->img_n; ++k) tc[k] = (stbi_uc)(stbi__get16be(s) & 255) * stbi__depth_scale_table[z->depth]; // non 8-bit images will be larger + } + } + break; + } + + case STBI__PNG_TYPE('I','D','A','T'): { + if (first) return stbi__err("first not IHDR", "Corrupt PNG"); + if (pal_img_n && !pal_len) return stbi__err("no PLTE","Corrupt PNG"); + if (scan == STBI__SCAN_header) { s->img_n = pal_img_n; return 1; } + if ((int)(ioff + c.length) < (int)ioff) return 0; + if (ioff + c.length > idata_limit) { + stbi__uint32 idata_limit_old = idata_limit; + stbi_uc *p; + if (idata_limit == 0) idata_limit = c.length > 4096 ? c.length : 4096; + while (ioff + c.length > idata_limit) + idata_limit *= 2; + STBI_NOTUSED(idata_limit_old); + p = (stbi_uc *) STBI_REALLOC_SIZED(z->idata, idata_limit_old, idata_limit); if (p == NULL) return stbi__err("outofmem", "Out of memory"); + z->idata = p; + } + if (!stbi__getn(s, z->idata+ioff,c.length)) return stbi__err("outofdata","Corrupt PNG"); + ioff += c.length; + break; + } + + case STBI__PNG_TYPE('I','E','N','D'): { + stbi__uint32 raw_len, bpl; + if (first) return stbi__err("first not IHDR", "Corrupt PNG"); + if (scan != STBI__SCAN_load) return 1; + if (z->idata == NULL) return stbi__err("no IDAT","Corrupt PNG"); + // initial guess for decoded data size to avoid unnecessary reallocs + bpl = (s->img_x * z->depth + 7) / 8; // bytes per line, per component + raw_len = bpl * s->img_y * s->img_n /* pixels */ + s->img_y /* filter mode per row */; + z->expanded = (stbi_uc *) stbi_zlib_decode_malloc_guesssize_headerflag((char *) z->idata, ioff, raw_len, (int *) &raw_len, !is_iphone); + if (z->expanded == NULL) return 0; // zlib should set error + STBI_FREE(z->idata); z->idata = NULL; + if ((req_comp == s->img_n+1 && req_comp != 3 && !pal_img_n) || has_trans) + s->img_out_n = s->img_n+1; + else + s->img_out_n = s->img_n; + if (!stbi__create_png_image(z, z->expanded, raw_len, s->img_out_n, z->depth, color, interlace)) return 0; + if (has_trans) { + if (z->depth == 16) { + if (!stbi__compute_transparency16(z, tc16, s->img_out_n)) return 0; + } else { + if (!stbi__compute_transparency(z, tc, s->img_out_n)) return 0; + } + } + if (is_iphone && stbi__de_iphone_flag && s->img_out_n > 2) + stbi__de_iphone(z); + if (pal_img_n) { + // pal_img_n == 3 or 4 + s->img_n = pal_img_n; // record the actual colors we had + s->img_out_n = pal_img_n; + if (req_comp >= 3) s->img_out_n = req_comp; + if (!stbi__expand_png_palette(z, palette, pal_len, s->img_out_n)) + return 0; + } + STBI_FREE(z->expanded); z->expanded = NULL; + return 1; + } + + default: + // if critical, fail + if (first) return stbi__err("first not IHDR", "Corrupt PNG"); + if ((c.type & (1 << 29)) == 0) { + #ifndef STBI_NO_FAILURE_STRINGS + // not threadsafe + static char invalid_chunk[] = "XXXX PNG chunk not known"; + invalid_chunk[0] = STBI__BYTECAST(c.type >> 24); + invalid_chunk[1] = STBI__BYTECAST(c.type >> 16); + invalid_chunk[2] = STBI__BYTECAST(c.type >> 8); + invalid_chunk[3] = STBI__BYTECAST(c.type >> 0); + #endif + return stbi__err(invalid_chunk, "PNG not supported: unknown PNG chunk type"); + } + stbi__skip(s, c.length); + break; + } + // end of PNG chunk, read and skip CRC + stbi__get32be(s); + } +} + +static unsigned char *stbi__do_png(stbi__png *p, int *x, int *y, int *n, int req_comp) +{ + unsigned char *result=NULL; + if (req_comp < 0 || req_comp > 4) return stbi__errpuc("bad req_comp", "Internal error"); + if (stbi__parse_png_file(p, STBI__SCAN_load, req_comp)) { + if (p->depth == 16) { + if (!stbi__reduce_png(p)) { + return result; + } + } + result = p->out; + p->out = NULL; + if (req_comp && req_comp != p->s->img_out_n) { + result = stbi__convert_format(result, p->s->img_out_n, req_comp, p->s->img_x, p->s->img_y); + p->s->img_out_n = req_comp; + if (result == NULL) return result; + } + *x = p->s->img_x; + *y = p->s->img_y; + if (n) *n = p->s->img_n; + } + STBI_FREE(p->out); p->out = NULL; + STBI_FREE(p->expanded); p->expanded = NULL; + STBI_FREE(p->idata); p->idata = NULL; + + return result; +} + +static unsigned char *stbi__png_load(stbi__context *s, int *x, int *y, int *comp, int req_comp) +{ + stbi__png p; + p.s = s; + return stbi__do_png(&p, x,y,comp,req_comp); +} + +static int stbi__png_test(stbi__context *s) +{ + int r; + r = stbi__check_png_header(s); + stbi__rewind(s); + return r; +} + +static int stbi__png_info_raw(stbi__png *p, int *x, int *y, int *comp) +{ + if (!stbi__parse_png_file(p, STBI__SCAN_header, 0)) { + stbi__rewind( p->s ); + return 0; + } + if (x) *x = p->s->img_x; + if (y) *y = p->s->img_y; + if (comp) *comp = p->s->img_n; + return 1; +} + +static int stbi__png_info(stbi__context *s, int *x, int *y, int *comp) +{ + stbi__png p; + p.s = s; + return stbi__png_info_raw(&p, x, y, comp); +} +#endif + +// Microsoft/Windows BMP image + +#ifndef STBI_NO_BMP +static int stbi__bmp_test_raw(stbi__context *s) +{ + int r; + int sz; + if (stbi__get8(s) != 'B') return 0; + if (stbi__get8(s) != 'M') return 0; + stbi__get32le(s); // discard filesize + stbi__get16le(s); // discard reserved + stbi__get16le(s); // discard reserved + stbi__get32le(s); // discard data offset + sz = stbi__get32le(s); + r = (sz == 12 || sz == 40 || sz == 56 || sz == 108 || sz == 124); + return r; +} + +static int stbi__bmp_test(stbi__context *s) +{ + int r = stbi__bmp_test_raw(s); + stbi__rewind(s); + return r; +} + + +// returns 0..31 for the highest set bit +static int stbi__high_bit(unsigned int z) +{ + int n=0; + if (z == 0) return -1; + if (z >= 0x10000) n += 16, z >>= 16; + if (z >= 0x00100) n += 8, z >>= 8; + if (z >= 0x00010) n += 4, z >>= 4; + if (z >= 0x00004) n += 2, z >>= 2; + if (z >= 0x00002) n += 1, z >>= 1; + return n; +} + +static int stbi__bitcount(unsigned int a) +{ + a = (a & 0x55555555) + ((a >> 1) & 0x55555555); // max 2 + a = (a & 0x33333333) + ((a >> 2) & 0x33333333); // max 4 + a = (a + (a >> 4)) & 0x0f0f0f0f; // max 8 per 4, now 8 bits + a = (a + (a >> 8)); // max 16 per 8 bits + a = (a + (a >> 16)); // max 32 per 8 bits + return a & 0xff; +} + +static int stbi__shiftsigned(int v, int shift, int bits) +{ + int result; + int z=0; + + if (shift < 0) v <<= -shift; + else v >>= shift; + result = v; + + z = bits; + while (z < 8) { + result += v >> z; + z += bits; + } + return result; +} + +typedef struct +{ + int bpp, offset, hsz; + unsigned int mr,mg,mb,ma, all_a; +} stbi__bmp_data; + +static void *stbi__bmp_parse_header(stbi__context *s, stbi__bmp_data *info) +{ + int hsz; + if (stbi__get8(s) != 'B' || stbi__get8(s) != 'M') return stbi__errpuc("not BMP", "Corrupt BMP"); + stbi__get32le(s); // discard filesize + stbi__get16le(s); // discard reserved + stbi__get16le(s); // discard reserved + info->offset = stbi__get32le(s); + info->hsz = hsz = stbi__get32le(s); + info->mr = info->mg = info->mb = info->ma = 0; + + if (hsz != 12 && hsz != 40 && hsz != 56 && hsz != 108 && hsz != 124) return stbi__errpuc("unknown BMP", "BMP type not supported: unknown"); + if (hsz == 12) { + s->img_x = stbi__get16le(s); + s->img_y = stbi__get16le(s); + } else { + s->img_x = stbi__get32le(s); + s->img_y = stbi__get32le(s); + } + if (stbi__get16le(s) != 1) return stbi__errpuc("bad BMP", "bad BMP"); + info->bpp = stbi__get16le(s); + if (info->bpp == 1) return stbi__errpuc("monochrome", "BMP type not supported: 1-bit"); + if (hsz != 12) { + int compress = stbi__get32le(s); + if (compress == 1 || compress == 2) return stbi__errpuc("BMP RLE", "BMP type not supported: RLE"); + stbi__get32le(s); // discard sizeof + stbi__get32le(s); // discard hres + stbi__get32le(s); // discard vres + stbi__get32le(s); // discard colorsused + stbi__get32le(s); // discard max important + if (hsz == 40 || hsz == 56) { + if (hsz == 56) { + stbi__get32le(s); + stbi__get32le(s); + stbi__get32le(s); + stbi__get32le(s); + } + if (info->bpp == 16 || info->bpp == 32) { + if (compress == 0) { + if (info->bpp == 32) { + info->mr = 0xffu << 16; + info->mg = 0xffu << 8; + info->mb = 0xffu << 0; + info->ma = 0xffu << 24; + info->all_a = 0; // if all_a is 0 at end, then we loaded alpha channel but it was all 0 + } else { + info->mr = 31u << 10; + info->mg = 31u << 5; + info->mb = 31u << 0; + } + } else if (compress == 3) { + info->mr = stbi__get32le(s); + info->mg = stbi__get32le(s); + info->mb = stbi__get32le(s); + // not documented, but generated by photoshop and handled by mspaint + if (info->mr == info->mg && info->mg == info->mb) { + // ?!?!? + return stbi__errpuc("bad BMP", "bad BMP"); + } + } else + return stbi__errpuc("bad BMP", "bad BMP"); + } + } else { + int i; + if (hsz != 108 && hsz != 124) + return stbi__errpuc("bad BMP", "bad BMP"); + info->mr = stbi__get32le(s); + info->mg = stbi__get32le(s); + info->mb = stbi__get32le(s); + info->ma = stbi__get32le(s); + stbi__get32le(s); // discard color space + for (i=0; i < 12; ++i) + stbi__get32le(s); // discard color space parameters + if (hsz == 124) { + stbi__get32le(s); // discard rendering intent + stbi__get32le(s); // discard offset of profile data + stbi__get32le(s); // discard size of profile data + stbi__get32le(s); // discard reserved + } + } + } + return (void *) 1; +} + + +static stbi_uc *stbi__bmp_load(stbi__context *s, int *x, int *y, int *comp, int req_comp) +{ + stbi_uc *out; + unsigned int mr=0,mg=0,mb=0,ma=0, all_a; + stbi_uc pal[256][4]; + int psize=0,i,j,width; + int flip_vertically, pad, target; + stbi__bmp_data info; + + info.all_a = 255; + if (stbi__bmp_parse_header(s, &info) == NULL) + return NULL; // error code already set + + flip_vertically = ((int) s->img_y) > 0; + s->img_y = abs((int) s->img_y); + + mr = info.mr; + mg = info.mg; + mb = info.mb; + ma = info.ma; + all_a = info.all_a; + + if (info.hsz == 12) { + if (info.bpp < 24) + psize = (info.offset - 14 - 24) / 3; + } else { + if (info.bpp < 16) + psize = (info.offset - 14 - info.hsz) >> 2; + } + + s->img_n = ma ? 4 : 3; + if (req_comp && req_comp >= 3) // we can directly decode 3 or 4 + target = req_comp; + else + target = s->img_n; // if they want monochrome, we'll post-convert + + out = (stbi_uc *) stbi__malloc(target * s->img_x * s->img_y); + if (!out) return stbi__errpuc("outofmem", "Out of memory"); + if (info.bpp < 16) { + int z=0; + if (psize == 0 || psize > 256) { STBI_FREE(out); return stbi__errpuc("invalid", "Corrupt BMP"); } + for (i=0; i < psize; ++i) { + pal[i][2] = stbi__get8(s); + pal[i][1] = stbi__get8(s); + pal[i][0] = stbi__get8(s); + if (info.hsz != 12) stbi__get8(s); + pal[i][3] = 255; + } + stbi__skip(s, info.offset - 14 - info.hsz - psize * (info.hsz == 12 ? 3 : 4)); + if (info.bpp == 4) width = (s->img_x + 1) >> 1; + else if (info.bpp == 8) width = s->img_x; + else { STBI_FREE(out); return stbi__errpuc("bad bpp", "Corrupt BMP"); } + pad = (-width)&3; + for (j=0; j < (int) s->img_y; ++j) { + for (i=0; i < (int) s->img_x; i += 2) { + int v=stbi__get8(s),v2=0; + if (info.bpp == 4) { + v2 = v & 15; + v >>= 4; + } + out[z++] = pal[v][0]; + out[z++] = pal[v][1]; + out[z++] = pal[v][2]; + if (target == 4) out[z++] = 255; + if (i+1 == (int) s->img_x) break; + v = (info.bpp == 8) ? stbi__get8(s) : v2; + out[z++] = pal[v][0]; + out[z++] = pal[v][1]; + out[z++] = pal[v][2]; + if (target == 4) out[z++] = 255; + } + stbi__skip(s, pad); + } + } else { + int rshift=0,gshift=0,bshift=0,ashift=0,rcount=0,gcount=0,bcount=0,acount=0; + int z = 0; + int easy=0; + stbi__skip(s, info.offset - 14 - info.hsz); + if (info.bpp == 24) width = 3 * s->img_x; + else if (info.bpp == 16) width = 2*s->img_x; + else /* bpp = 32 and pad = 0 */ width=0; + pad = (-width) & 3; + if (info.bpp == 24) { + easy = 1; + } else if (info.bpp == 32) { + if (mb == 0xff && mg == 0xff00 && mr == 0x00ff0000 && ma == 0xff000000) + easy = 2; + } + if (!easy) { + if (!mr || !mg || !mb) { STBI_FREE(out); return stbi__errpuc("bad masks", "Corrupt BMP"); } + // right shift amt to put high bit in position #7 + rshift = stbi__high_bit(mr)-7; rcount = stbi__bitcount(mr); + gshift = stbi__high_bit(mg)-7; gcount = stbi__bitcount(mg); + bshift = stbi__high_bit(mb)-7; bcount = stbi__bitcount(mb); + ashift = stbi__high_bit(ma)-7; acount = stbi__bitcount(ma); + } + for (j=0; j < (int) s->img_y; ++j) { + if (easy) { + for (i=0; i < (int) s->img_x; ++i) { + unsigned char a; + out[z+2] = stbi__get8(s); + out[z+1] = stbi__get8(s); + out[z+0] = stbi__get8(s); + z += 3; + a = (easy == 2 ? stbi__get8(s) : 255); + all_a |= a; + if (target == 4) out[z++] = a; + } + } else { + int bpp = info.bpp; + for (i=0; i < (int) s->img_x; ++i) { + stbi__uint32 v = (bpp == 16 ? (stbi__uint32) stbi__get16le(s) : stbi__get32le(s)); + int a; + out[z++] = STBI__BYTECAST(stbi__shiftsigned(v & mr, rshift, rcount)); + out[z++] = STBI__BYTECAST(stbi__shiftsigned(v & mg, gshift, gcount)); + out[z++] = STBI__BYTECAST(stbi__shiftsigned(v & mb, bshift, bcount)); + a = (ma ? stbi__shiftsigned(v & ma, ashift, acount) : 255); + all_a |= a; + if (target == 4) out[z++] = STBI__BYTECAST(a); + } + } + stbi__skip(s, pad); + } + } + + // if alpha channel is all 0s, replace with all 255s + if (target == 4 && all_a == 0) + for (i=4*s->img_x*s->img_y-1; i >= 0; i -= 4) + out[i] = 255; + + if (flip_vertically) { + stbi_uc t; + for (j=0; j < (int) s->img_y>>1; ++j) { + stbi_uc *p1 = out + j *s->img_x*target; + stbi_uc *p2 = out + (s->img_y-1-j)*s->img_x*target; + for (i=0; i < (int) s->img_x*target; ++i) { + t = p1[i], p1[i] = p2[i], p2[i] = t; + } + } + } + + if (req_comp && req_comp != target) { + out = stbi__convert_format(out, target, req_comp, s->img_x, s->img_y); + if (out == NULL) return out; // stbi__convert_format frees input on failure + } + + *x = s->img_x; + *y = s->img_y; + if (comp) *comp = s->img_n; + return out; +} +#endif + +// Targa Truevision - TGA +// by Jonathan Dummer +#ifndef STBI_NO_TGA +// returns STBI_rgb or whatever, 0 on error +static int stbi__tga_get_comp(int bits_per_pixel, int is_grey, int* is_rgb16) +{ + // only RGB or RGBA (incl. 16bit) or grey allowed + if(is_rgb16) *is_rgb16 = 0; + switch(bits_per_pixel) { + case 8: return STBI_grey; + case 16: if(is_grey) return STBI_grey_alpha; + // else: fall-through + case 15: if(is_rgb16) *is_rgb16 = 1; + return STBI_rgb; + case 24: // fall-through + case 32: return bits_per_pixel/8; + default: return 0; + } +} + +static int stbi__tga_info(stbi__context *s, int *x, int *y, int *comp) +{ + int tga_w, tga_h, tga_comp, tga_image_type, tga_bits_per_pixel, tga_colormap_bpp; + int sz, tga_colormap_type; + stbi__get8(s); // discard Offset + tga_colormap_type = stbi__get8(s); // colormap type + if( tga_colormap_type > 1 ) { + stbi__rewind(s); + return 0; // only RGB or indexed allowed + } + tga_image_type = stbi__get8(s); // image type + if ( tga_colormap_type == 1 ) { // colormapped (paletted) image + if (tga_image_type != 1 && tga_image_type != 9) { + stbi__rewind(s); + return 0; + } + stbi__skip(s,4); // skip index of first colormap entry and number of entries + sz = stbi__get8(s); // check bits per palette color entry + if ( (sz != 8) && (sz != 15) && (sz != 16) && (sz != 24) && (sz != 32) ) { + stbi__rewind(s); + return 0; + } + stbi__skip(s,4); // skip image x and y origin + tga_colormap_bpp = sz; + } else { // "normal" image w/o colormap - only RGB or grey allowed, +/- RLE + if ( (tga_image_type != 2) && (tga_image_type != 3) && (tga_image_type != 10) && (tga_image_type != 11) ) { + stbi__rewind(s); + return 0; // only RGB or grey allowed, +/- RLE + } + stbi__skip(s,9); // skip colormap specification and image x/y origin + tga_colormap_bpp = 0; + } + tga_w = stbi__get16le(s); + if( tga_w < 1 ) { + stbi__rewind(s); + return 0; // test width + } + tga_h = stbi__get16le(s); + if( tga_h < 1 ) { + stbi__rewind(s); + return 0; // test height + } + tga_bits_per_pixel = stbi__get8(s); // bits per pixel + stbi__get8(s); // ignore alpha bits + if (tga_colormap_bpp != 0) { + if((tga_bits_per_pixel != 8) && (tga_bits_per_pixel != 16)) { + // when using a colormap, tga_bits_per_pixel is the size of the indexes + // I don't think anything but 8 or 16bit indexes makes sense + stbi__rewind(s); + return 0; + } + tga_comp = stbi__tga_get_comp(tga_colormap_bpp, 0, NULL); + } else { + tga_comp = stbi__tga_get_comp(tga_bits_per_pixel, (tga_image_type == 3) || (tga_image_type == 11), NULL); + } + if(!tga_comp) { + stbi__rewind(s); + return 0; + } + if (x) *x = tga_w; + if (y) *y = tga_h; + if (comp) *comp = tga_comp; + return 1; // seems to have passed everything +} + +static int stbi__tga_test(stbi__context *s) +{ + int res = 0; + int sz, tga_color_type; + stbi__get8(s); // discard Offset + tga_color_type = stbi__get8(s); // color type + if ( tga_color_type > 1 ) goto errorEnd; // only RGB or indexed allowed + sz = stbi__get8(s); // image type + if ( tga_color_type == 1 ) { // colormapped (paletted) image + if (sz != 1 && sz != 9) goto errorEnd; // colortype 1 demands image type 1 or 9 + stbi__skip(s,4); // skip index of first colormap entry and number of entries + sz = stbi__get8(s); // check bits per palette color entry + if ( (sz != 8) && (sz != 15) && (sz != 16) && (sz != 24) && (sz != 32) ) goto errorEnd; + stbi__skip(s,4); // skip image x and y origin + } else { // "normal" image w/o colormap + if ( (sz != 2) && (sz != 3) && (sz != 10) && (sz != 11) ) goto errorEnd; // only RGB or grey allowed, +/- RLE + stbi__skip(s,9); // skip colormap specification and image x/y origin + } + if ( stbi__get16le(s) < 1 ) goto errorEnd; // test width + if ( stbi__get16le(s) < 1 ) goto errorEnd; // test height + sz = stbi__get8(s); // bits per pixel + if ( (tga_color_type == 1) && (sz != 8) && (sz != 16) ) goto errorEnd; // for colormapped images, bpp is size of an index + if ( (sz != 8) && (sz != 15) && (sz != 16) && (sz != 24) && (sz != 32) ) goto errorEnd; + + res = 1; // if we got this far, everything's good and we can return 1 instead of 0 + +errorEnd: + stbi__rewind(s); + return res; +} + +// read 16bit value and convert to 24bit RGB +void stbi__tga_read_rgb16(stbi__context *s, stbi_uc* out) +{ + stbi__uint16 px = stbi__get16le(s); + stbi__uint16 fiveBitMask = 31; + // we have 3 channels with 5bits each + int r = (px >> 10) & fiveBitMask; + int g = (px >> 5) & fiveBitMask; + int b = px & fiveBitMask; + // Note that this saves the data in RGB(A) order, so it doesn't need to be swapped later + out[0] = (r * 255)/31; + out[1] = (g * 255)/31; + out[2] = (b * 255)/31; + + // some people claim that the most significant bit might be used for alpha + // (possibly if an alpha-bit is set in the "image descriptor byte") + // but that only made 16bit test images completely translucent.. + // so let's treat all 15 and 16bit TGAs as RGB with no alpha. +} + +static stbi_uc *stbi__tga_load(stbi__context *s, int *x, int *y, int *comp, int req_comp) +{ + // read in the TGA header stuff + int tga_offset = stbi__get8(s); + int tga_indexed = stbi__get8(s); + int tga_image_type = stbi__get8(s); + int tga_is_RLE = 0; + int tga_palette_start = stbi__get16le(s); + int tga_palette_len = stbi__get16le(s); + int tga_palette_bits = stbi__get8(s); + int tga_x_origin = stbi__get16le(s); + int tga_y_origin = stbi__get16le(s); + int tga_width = stbi__get16le(s); + int tga_height = stbi__get16le(s); + int tga_bits_per_pixel = stbi__get8(s); + int tga_comp, tga_rgb16=0; + int tga_inverted = stbi__get8(s); + // int tga_alpha_bits = tga_inverted & 15; // the 4 lowest bits - unused (useless?) + // image data + unsigned char *tga_data; + unsigned char *tga_palette = NULL; + int i, j; + unsigned char raw_data[4]; + int RLE_count = 0; + int RLE_repeating = 0; + int read_next_pixel = 1; + + // do a tiny bit of precessing + if ( tga_image_type >= 8 ) + { + tga_image_type -= 8; + tga_is_RLE = 1; + } + tga_inverted = 1 - ((tga_inverted >> 5) & 1); + + // If I'm paletted, then I'll use the number of bits from the palette + if ( tga_indexed ) tga_comp = stbi__tga_get_comp(tga_palette_bits, 0, &tga_rgb16); + else tga_comp = stbi__tga_get_comp(tga_bits_per_pixel, (tga_image_type == 3), &tga_rgb16); + + if(!tga_comp) // shouldn't really happen, stbi__tga_test() should have ensured basic consistency + return stbi__errpuc("bad format", "Can't find out TGA pixelformat"); + + // tga info + *x = tga_width; + *y = tga_height; + if (comp) *comp = tga_comp; + + tga_data = (unsigned char*)stbi__malloc( (size_t)tga_width * tga_height * tga_comp ); + if (!tga_data) return stbi__errpuc("outofmem", "Out of memory"); + + // skip to the data's starting position (offset usually = 0) + stbi__skip(s, tga_offset ); + + if ( !tga_indexed && !tga_is_RLE && !tga_rgb16 ) { + for (i=0; i < tga_height; ++i) { + int row = tga_inverted ? tga_height -i - 1 : i; + stbi_uc *tga_row = tga_data + row*tga_width*tga_comp; + stbi__getn(s, tga_row, tga_width * tga_comp); + } + } else { + // do I need to load a palette? + if ( tga_indexed) + { + // any data to skip? (offset usually = 0) + stbi__skip(s, tga_palette_start ); + // load the palette + tga_palette = (unsigned char*)stbi__malloc( tga_palette_len * tga_comp ); + if (!tga_palette) { + STBI_FREE(tga_data); + return stbi__errpuc("outofmem", "Out of memory"); + } + if (tga_rgb16) { + stbi_uc *pal_entry = tga_palette; + STBI_ASSERT(tga_comp == STBI_rgb); + for (i=0; i < tga_palette_len; ++i) { + stbi__tga_read_rgb16(s, pal_entry); + pal_entry += tga_comp; + } + } else if (!stbi__getn(s, tga_palette, tga_palette_len * tga_comp)) { + STBI_FREE(tga_data); + STBI_FREE(tga_palette); + return stbi__errpuc("bad palette", "Corrupt TGA"); + } + } + // load the data + for (i=0; i < tga_width * tga_height; ++i) + { + // if I'm in RLE mode, do I need to get a RLE stbi__pngchunk? + if ( tga_is_RLE ) + { + if ( RLE_count == 0 ) + { + // yep, get the next byte as a RLE command + int RLE_cmd = stbi__get8(s); + RLE_count = 1 + (RLE_cmd & 127); + RLE_repeating = RLE_cmd >> 7; + read_next_pixel = 1; + } else if ( !RLE_repeating ) + { + read_next_pixel = 1; + } + } else + { + read_next_pixel = 1; + } + // OK, if I need to read a pixel, do it now + if ( read_next_pixel ) + { + // load however much data we did have + if ( tga_indexed ) + { + // read in index, then perform the lookup + int pal_idx = (tga_bits_per_pixel == 8) ? stbi__get8(s) : stbi__get16le(s); + if ( pal_idx >= tga_palette_len ) { + // invalid index + pal_idx = 0; + } + pal_idx *= tga_comp; + for (j = 0; j < tga_comp; ++j) { + raw_data[j] = tga_palette[pal_idx+j]; + } + } else if(tga_rgb16) { + STBI_ASSERT(tga_comp == STBI_rgb); + stbi__tga_read_rgb16(s, raw_data); + } else { + // read in the data raw + for (j = 0; j < tga_comp; ++j) { + raw_data[j] = stbi__get8(s); + } + } + // clear the reading flag for the next pixel + read_next_pixel = 0; + } // end of reading a pixel + + // copy data + for (j = 0; j < tga_comp; ++j) + tga_data[i*tga_comp+j] = raw_data[j]; + + // in case we're in RLE mode, keep counting down + --RLE_count; + } + // do I need to invert the image? + if ( tga_inverted ) + { + for (j = 0; j*2 < tga_height; ++j) + { + int index1 = j * tga_width * tga_comp; + int index2 = (tga_height - 1 - j) * tga_width * tga_comp; + for (i = tga_width * tga_comp; i > 0; --i) + { + unsigned char temp = tga_data[index1]; + tga_data[index1] = tga_data[index2]; + tga_data[index2] = temp; + ++index1; + ++index2; + } + } + } + // clear my palette, if I had one + if ( tga_palette != NULL ) + { + STBI_FREE( tga_palette ); + } + } + + // swap RGB - if the source data was RGB16, it already is in the right order + if (tga_comp >= 3 && !tga_rgb16) + { + unsigned char* tga_pixel = tga_data; + for (i=0; i < tga_width * tga_height; ++i) + { + unsigned char temp = tga_pixel[0]; + tga_pixel[0] = tga_pixel[2]; + tga_pixel[2] = temp; + tga_pixel += tga_comp; + } + } + + // convert to target component count + if (req_comp && req_comp != tga_comp) + tga_data = stbi__convert_format(tga_data, tga_comp, req_comp, tga_width, tga_height); + + // the things I do to get rid of an error message, and yet keep + // Microsoft's C compilers happy... [8^( + tga_palette_start = tga_palette_len = tga_palette_bits = + tga_x_origin = tga_y_origin = 0; + // OK, done + return tga_data; +} +#endif + +// ************************************************************************************************* +// Photoshop PSD loader -- PD by Thatcher Ulrich, integration by Nicolas Schulz, tweaked by STB + +#ifndef STBI_NO_PSD +static int stbi__psd_test(stbi__context *s) +{ + int r = (stbi__get32be(s) == 0x38425053); + stbi__rewind(s); + return r; +} + +static stbi_uc *stbi__psd_load(stbi__context *s, int *x, int *y, int *comp, int req_comp) +{ + int pixelCount; + int channelCount, compression; + int channel, i, count, len; + int bitdepth; + int w,h; + stbi_uc *out; + + // Check identifier + if (stbi__get32be(s) != 0x38425053) // "8BPS" + return stbi__errpuc("not PSD", "Corrupt PSD image"); + + // Check file type version. + if (stbi__get16be(s) != 1) + return stbi__errpuc("wrong version", "Unsupported version of PSD image"); + + // Skip 6 reserved bytes. + stbi__skip(s, 6 ); + + // Read the number of channels (R, G, B, A, etc). + channelCount = stbi__get16be(s); + if (channelCount < 0 || channelCount > 16) + return stbi__errpuc("wrong channel count", "Unsupported number of channels in PSD image"); + + // Read the rows and columns of the image. + h = stbi__get32be(s); + w = stbi__get32be(s); + + // Make sure the depth is 8 bits. + bitdepth = stbi__get16be(s); + if (bitdepth != 8 && bitdepth != 16) + return stbi__errpuc("unsupported bit depth", "PSD bit depth is not 8 or 16 bit"); + + // Make sure the color mode is RGB. + // Valid options are: + // 0: Bitmap + // 1: Grayscale + // 2: Indexed color + // 3: RGB color + // 4: CMYK color + // 7: Multichannel + // 8: Duotone + // 9: Lab color + if (stbi__get16be(s) != 3) + return stbi__errpuc("wrong color format", "PSD is not in RGB color format"); + + // Skip the Mode Data. (It's the palette for indexed color; other info for other modes.) + stbi__skip(s,stbi__get32be(s) ); + + // Skip the image resources. (resolution, pen tool paths, etc) + stbi__skip(s, stbi__get32be(s) ); + + // Skip the reserved data. + stbi__skip(s, stbi__get32be(s) ); + + // Find out if the data is compressed. + // Known values: + // 0: no compression + // 1: RLE compressed + compression = stbi__get16be(s); + if (compression > 1) + return stbi__errpuc("bad compression", "PSD has an unknown compression format"); + + // Create the destination image. + out = (stbi_uc *) stbi__malloc(4 * w*h); + if (!out) return stbi__errpuc("outofmem", "Out of memory"); + pixelCount = w*h; + + // Initialize the data to zero. + //memset( out, 0, pixelCount * 4 ); + + // Finally, the image data. + if (compression) { + // RLE as used by .PSD and .TIFF + // Loop until you get the number of unpacked bytes you are expecting: + // Read the next source byte into n. + // If n is between 0 and 127 inclusive, copy the next n+1 bytes literally. + // Else if n is between -127 and -1 inclusive, copy the next byte -n+1 times. + // Else if n is 128, noop. + // Endloop + + // The RLE-compressed data is preceeded by a 2-byte data count for each row in the data, + // which we're going to just skip. + stbi__skip(s, h * channelCount * 2 ); + + // Read the RLE data by channel. + for (channel = 0; channel < 4; channel++) { + stbi_uc *p; + + p = out+channel; + if (channel >= channelCount) { + // Fill this channel with default data. + for (i = 0; i < pixelCount; i++, p += 4) + *p = (channel == 3 ? 255 : 0); + } else { + // Read the RLE data. + count = 0; + while (count < pixelCount) { + len = stbi__get8(s); + if (len == 128) { + // No-op. + } else if (len < 128) { + // Copy next len+1 bytes literally. + len++; + count += len; + while (len) { + *p = stbi__get8(s); + p += 4; + len--; + } + } else if (len > 128) { + stbi_uc val; + // Next -len+1 bytes in the dest are replicated from next source byte. + // (Interpret len as a negative 8-bit int.) + len ^= 0x0FF; + len += 2; + val = stbi__get8(s); + count += len; + while (len) { + *p = val; + p += 4; + len--; + } + } + } + } + } + + } else { + // We're at the raw image data. It's each channel in order (Red, Green, Blue, Alpha, ...) + // where each channel consists of an 8-bit value for each pixel in the image. + + // Read the data by channel. + for (channel = 0; channel < 4; channel++) { + stbi_uc *p; + + p = out + channel; + if (channel >= channelCount) { + // Fill this channel with default data. + stbi_uc val = channel == 3 ? 255 : 0; + for (i = 0; i < pixelCount; i++, p += 4) + *p = val; + } else { + // Read the data. + if (bitdepth == 16) { + for (i = 0; i < pixelCount; i++, p += 4) + *p = (stbi_uc) (stbi__get16be(s) >> 8); + } else { + for (i = 0; i < pixelCount; i++, p += 4) + *p = stbi__get8(s); + } + } + } + } + + if (channelCount >= 4) { + for (i=0; i < w*h; ++i) { + unsigned char *pixel = out + 4*i; + if (pixel[3] != 0 && pixel[3] != 255) { + // remove weird white matte from PSD + float a = pixel[3] / 255.0f; + float ra = 1.0f / a; + float inv_a = 255.0f * (1 - ra); + pixel[0] = (unsigned char) (pixel[0]*ra + inv_a); + pixel[1] = (unsigned char) (pixel[1]*ra + inv_a); + pixel[2] = (unsigned char) (pixel[2]*ra + inv_a); + } + } + } + + if (req_comp && req_comp != 4) { + out = stbi__convert_format(out, 4, req_comp, w, h); + if (out == NULL) return out; // stbi__convert_format frees input on failure + } + + if (comp) *comp = 4; + *y = h; + *x = w; + + return out; +} +#endif + +// ************************************************************************************************* +// Softimage PIC loader +// by Tom Seddon +// +// See http://softimage.wiki.softimage.com/index.php/INFO:_PIC_file_format +// See http://ozviz.wasp.uwa.edu.au/~pbourke/dataformats/softimagepic/ + +#ifndef STBI_NO_PIC +static int stbi__pic_is4(stbi__context *s,const char *str) +{ + int i; + for (i=0; i<4; ++i) + if (stbi__get8(s) != (stbi_uc)str[i]) + return 0; + + return 1; +} + +static int stbi__pic_test_core(stbi__context *s) +{ + int i; + + if (!stbi__pic_is4(s,"\x53\x80\xF6\x34")) + return 0; + + for(i=0;i<84;++i) + stbi__get8(s); + + if (!stbi__pic_is4(s,"PICT")) + return 0; + + return 1; +} + +typedef struct +{ + stbi_uc size,type,channel; +} stbi__pic_packet; + +static stbi_uc *stbi__readval(stbi__context *s, int channel, stbi_uc *dest) +{ + int mask=0x80, i; + + for (i=0; i<4; ++i, mask>>=1) { + if (channel & mask) { + if (stbi__at_eof(s)) return stbi__errpuc("bad file","PIC file too short"); + dest[i]=stbi__get8(s); + } + } + + return dest; +} + +static void stbi__copyval(int channel,stbi_uc *dest,const stbi_uc *src) +{ + int mask=0x80,i; + + for (i=0;i<4; ++i, mask>>=1) + if (channel&mask) + dest[i]=src[i]; +} + +static stbi_uc *stbi__pic_load_core(stbi__context *s,int width,int height,int *comp, stbi_uc *result) +{ + int act_comp=0,num_packets=0,y,chained; + stbi__pic_packet packets[10]; + + // this will (should...) cater for even some bizarre stuff like having data + // for the same channel in multiple packets. + do { + stbi__pic_packet *packet; + + if (num_packets==sizeof(packets)/sizeof(packets[0])) + return stbi__errpuc("bad format","too many packets"); + + packet = &packets[num_packets++]; + + chained = stbi__get8(s); + packet->size = stbi__get8(s); + packet->type = stbi__get8(s); + packet->channel = stbi__get8(s); + + act_comp |= packet->channel; + + if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (reading packets)"); + if (packet->size != 8) return stbi__errpuc("bad format","packet isn't 8bpp"); + } while (chained); + + *comp = (act_comp & 0x10 ? 4 : 3); // has alpha channel? + + for(y=0; ytype) { + default: + return stbi__errpuc("bad format","packet has bad compression type"); + + case 0: {//uncompressed + int x; + + for(x=0;xchannel,dest)) + return 0; + break; + } + + case 1://Pure RLE + { + int left=width, i; + + while (left>0) { + stbi_uc count,value[4]; + + count=stbi__get8(s); + if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (pure read count)"); + + if (count > left) + count = (stbi_uc) left; + + if (!stbi__readval(s,packet->channel,value)) return 0; + + for(i=0; ichannel,dest,value); + left -= count; + } + } + break; + + case 2: {//Mixed RLE + int left=width; + while (left>0) { + int count = stbi__get8(s), i; + if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (mixed read count)"); + + if (count >= 128) { // Repeated + stbi_uc value[4]; + + if (count==128) + count = stbi__get16be(s); + else + count -= 127; + if (count > left) + return stbi__errpuc("bad file","scanline overrun"); + + if (!stbi__readval(s,packet->channel,value)) + return 0; + + for(i=0;ichannel,dest,value); + } else { // Raw + ++count; + if (count>left) return stbi__errpuc("bad file","scanline overrun"); + + for(i=0;ichannel,dest)) + return 0; + } + left-=count; + } + break; + } + } + } + } + + return result; +} + +static stbi_uc *stbi__pic_load(stbi__context *s,int *px,int *py,int *comp,int req_comp) +{ + stbi_uc *result; + int i, x,y; + + for (i=0; i<92; ++i) + stbi__get8(s); + + x = stbi__get16be(s); + y = stbi__get16be(s); + if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (pic header)"); + if ((1 << 28) / x < y) return stbi__errpuc("too large", "Image too large to decode"); + + stbi__get32be(s); //skip `ratio' + stbi__get16be(s); //skip `fields' + stbi__get16be(s); //skip `pad' + + // intermediate buffer is RGBA + result = (stbi_uc *) stbi__malloc(x*y*4); + memset(result, 0xff, x*y*4); + + if (!stbi__pic_load_core(s,x,y,comp, result)) { + STBI_FREE(result); + result=0; + } + *px = x; + *py = y; + if (req_comp == 0) req_comp = *comp; + result=stbi__convert_format(result,4,req_comp,x,y); + + return result; +} + +static int stbi__pic_test(stbi__context *s) +{ + int r = stbi__pic_test_core(s); + stbi__rewind(s); + return r; +} +#endif + +// ************************************************************************************************* +// GIF loader -- public domain by Jean-Marc Lienher -- simplified/shrunk by stb + +#ifndef STBI_NO_GIF +typedef struct +{ + stbi__int16 prefix; + stbi_uc first; + stbi_uc suffix; +} stbi__gif_lzw; + +typedef struct +{ + int w,h; + stbi_uc *out, *old_out; // output buffer (always 4 components) + int flags, bgindex, ratio, transparent, eflags, delay; + stbi_uc pal[256][4]; + stbi_uc lpal[256][4]; + stbi__gif_lzw codes[4096]; + stbi_uc *color_table; + int parse, step; + int lflags; + int start_x, start_y; + int max_x, max_y; + int cur_x, cur_y; + int line_size; +} stbi__gif; + +static int stbi__gif_test_raw(stbi__context *s) +{ + int sz; + if (stbi__get8(s) != 'G' || stbi__get8(s) != 'I' || stbi__get8(s) != 'F' || stbi__get8(s) != '8') return 0; + sz = stbi__get8(s); + if (sz != '9' && sz != '7') return 0; + if (stbi__get8(s) != 'a') return 0; + return 1; +} + +static int stbi__gif_test(stbi__context *s) +{ + int r = stbi__gif_test_raw(s); + stbi__rewind(s); + return r; +} + +static void stbi__gif_parse_colortable(stbi__context *s, stbi_uc pal[256][4], int num_entries, int transp) +{ + int i; + for (i=0; i < num_entries; ++i) { + pal[i][2] = stbi__get8(s); + pal[i][1] = stbi__get8(s); + pal[i][0] = stbi__get8(s); + pal[i][3] = transp == i ? 0 : 255; + } +} + +static int stbi__gif_header(stbi__context *s, stbi__gif *g, int *comp, int is_info) +{ + stbi_uc version; + if (stbi__get8(s) != 'G' || stbi__get8(s) != 'I' || stbi__get8(s) != 'F' || stbi__get8(s) != '8') + return stbi__err("not GIF", "Corrupt GIF"); + + version = stbi__get8(s); + if (version != '7' && version != '9') return stbi__err("not GIF", "Corrupt GIF"); + if (stbi__get8(s) != 'a') return stbi__err("not GIF", "Corrupt GIF"); + + stbi__g_failure_reason = ""; + g->w = stbi__get16le(s); + g->h = stbi__get16le(s); + g->flags = stbi__get8(s); + g->bgindex = stbi__get8(s); + g->ratio = stbi__get8(s); + g->transparent = -1; + + if (comp != 0) *comp = 4; // can't actually tell whether it's 3 or 4 until we parse the comments + + if (is_info) return 1; + + if (g->flags & 0x80) + stbi__gif_parse_colortable(s,g->pal, 2 << (g->flags & 7), -1); + + return 1; +} + +static int stbi__gif_info_raw(stbi__context *s, int *x, int *y, int *comp) +{ + stbi__gif* g = (stbi__gif*) stbi__malloc(sizeof(stbi__gif)); + if (!stbi__gif_header(s, g, comp, 1)) { + STBI_FREE(g); + stbi__rewind( s ); + return 0; + } + if (x) *x = g->w; + if (y) *y = g->h; + STBI_FREE(g); + return 1; +} + +static void stbi__out_gif_code(stbi__gif *g, stbi__uint16 code) +{ + stbi_uc *p, *c; + + // recurse to decode the prefixes, since the linked-list is backwards, + // and working backwards through an interleaved image would be nasty + if (g->codes[code].prefix >= 0) + stbi__out_gif_code(g, g->codes[code].prefix); + + if (g->cur_y >= g->max_y) return; + + p = &g->out[g->cur_x + g->cur_y]; + c = &g->color_table[g->codes[code].suffix * 4]; + + if (c[3] >= 128) { + p[0] = c[2]; + p[1] = c[1]; + p[2] = c[0]; + p[3] = c[3]; + } + g->cur_x += 4; + + if (g->cur_x >= g->max_x) { + g->cur_x = g->start_x; + g->cur_y += g->step; + + while (g->cur_y >= g->max_y && g->parse > 0) { + g->step = (1 << g->parse) * g->line_size; + g->cur_y = g->start_y + (g->step >> 1); + --g->parse; + } + } +} + +static stbi_uc *stbi__process_gif_raster(stbi__context *s, stbi__gif *g) +{ + stbi_uc lzw_cs; + stbi__int32 len, init_code; + stbi__uint32 first; + stbi__int32 codesize, codemask, avail, oldcode, bits, valid_bits, clear; + stbi__gif_lzw *p; + + lzw_cs = stbi__get8(s); + if (lzw_cs > 12) return NULL; + clear = 1 << lzw_cs; + first = 1; + codesize = lzw_cs + 1; + codemask = (1 << codesize) - 1; + bits = 0; + valid_bits = 0; + for (init_code = 0; init_code < clear; init_code++) { + g->codes[init_code].prefix = -1; + g->codes[init_code].first = (stbi_uc) init_code; + g->codes[init_code].suffix = (stbi_uc) init_code; + } + + // support no starting clear code + avail = clear+2; + oldcode = -1; + + len = 0; + for(;;) { + if (valid_bits < codesize) { + if (len == 0) { + len = stbi__get8(s); // start new block + if (len == 0) + return g->out; + } + --len; + bits |= (stbi__int32) stbi__get8(s) << valid_bits; + valid_bits += 8; + } else { + stbi__int32 code = bits & codemask; + bits >>= codesize; + valid_bits -= codesize; + // @OPTIMIZE: is there some way we can accelerate the non-clear path? + if (code == clear) { // clear code + codesize = lzw_cs + 1; + codemask = (1 << codesize) - 1; + avail = clear + 2; + oldcode = -1; + first = 0; + } else if (code == clear + 1) { // end of stream code + stbi__skip(s, len); + while ((len = stbi__get8(s)) > 0) + stbi__skip(s,len); + return g->out; + } else if (code <= avail) { + if (first) return stbi__errpuc("no clear code", "Corrupt GIF"); + + if (oldcode >= 0) { + p = &g->codes[avail++]; + if (avail > 4096) return stbi__errpuc("too many codes", "Corrupt GIF"); + p->prefix = (stbi__int16) oldcode; + p->first = g->codes[oldcode].first; + p->suffix = (code == avail) ? p->first : g->codes[code].first; + } else if (code == avail) + return stbi__errpuc("illegal code in raster", "Corrupt GIF"); + + stbi__out_gif_code(g, (stbi__uint16) code); + + if ((avail & codemask) == 0 && avail <= 0x0FFF) { + codesize++; + codemask = (1 << codesize) - 1; + } + + oldcode = code; + } else { + return stbi__errpuc("illegal code in raster", "Corrupt GIF"); + } + } + } +} + +static void stbi__fill_gif_background(stbi__gif *g, int x0, int y0, int x1, int y1) +{ + int x, y; + stbi_uc *c = g->pal[g->bgindex]; + for (y = y0; y < y1; y += 4 * g->w) { + for (x = x0; x < x1; x += 4) { + stbi_uc *p = &g->out[y + x]; + p[0] = c[2]; + p[1] = c[1]; + p[2] = c[0]; + p[3] = 0; + } + } +} + +// this function is designed to support animated gifs, although stb_image doesn't support it +static stbi_uc *stbi__gif_load_next(stbi__context *s, stbi__gif *g, int *comp, int req_comp) +{ + int i; + stbi_uc *prev_out = 0; + + if (g->out == 0 && !stbi__gif_header(s, g, comp,0)) + return 0; // stbi__g_failure_reason set by stbi__gif_header + + prev_out = g->out; + g->out = (stbi_uc *) stbi__malloc(4 * g->w * g->h); + if (g->out == 0) return stbi__errpuc("outofmem", "Out of memory"); + + switch ((g->eflags & 0x1C) >> 2) { + case 0: // unspecified (also always used on 1st frame) + stbi__fill_gif_background(g, 0, 0, 4 * g->w, 4 * g->w * g->h); + break; + case 1: // do not dispose + if (prev_out) memcpy(g->out, prev_out, 4 * g->w * g->h); + g->old_out = prev_out; + break; + case 2: // dispose to background + if (prev_out) memcpy(g->out, prev_out, 4 * g->w * g->h); + stbi__fill_gif_background(g, g->start_x, g->start_y, g->max_x, g->max_y); + break; + case 3: // dispose to previous + if (g->old_out) { + for (i = g->start_y; i < g->max_y; i += 4 * g->w) + memcpy(&g->out[i + g->start_x], &g->old_out[i + g->start_x], g->max_x - g->start_x); + } + break; + } + + for (;;) { + switch (stbi__get8(s)) { + case 0x2C: /* Image Descriptor */ + { + int prev_trans = -1; + stbi__int32 x, y, w, h; + stbi_uc *o; + + x = stbi__get16le(s); + y = stbi__get16le(s); + w = stbi__get16le(s); + h = stbi__get16le(s); + if (((x + w) > (g->w)) || ((y + h) > (g->h))) + return stbi__errpuc("bad Image Descriptor", "Corrupt GIF"); + + g->line_size = g->w * 4; + g->start_x = x * 4; + g->start_y = y * g->line_size; + g->max_x = g->start_x + w * 4; + g->max_y = g->start_y + h * g->line_size; + g->cur_x = g->start_x; + g->cur_y = g->start_y; + + g->lflags = stbi__get8(s); + + if (g->lflags & 0x40) { + g->step = 8 * g->line_size; // first interlaced spacing + g->parse = 3; + } else { + g->step = g->line_size; + g->parse = 0; + } + + if (g->lflags & 0x80) { + stbi__gif_parse_colortable(s,g->lpal, 2 << (g->lflags & 7), g->eflags & 0x01 ? g->transparent : -1); + g->color_table = (stbi_uc *) g->lpal; + } else if (g->flags & 0x80) { + if (g->transparent >= 0 && (g->eflags & 0x01)) { + prev_trans = g->pal[g->transparent][3]; + g->pal[g->transparent][3] = 0; + } + g->color_table = (stbi_uc *) g->pal; + } else + return stbi__errpuc("missing color table", "Corrupt GIF"); + + o = stbi__process_gif_raster(s, g); + if (o == NULL) return NULL; + + if (prev_trans != -1) + g->pal[g->transparent][3] = (stbi_uc) prev_trans; + + return o; + } + + case 0x21: // Comment Extension. + { + int len; + if (stbi__get8(s) == 0xF9) { // Graphic Control Extension. + len = stbi__get8(s); + if (len == 4) { + g->eflags = stbi__get8(s); + g->delay = stbi__get16le(s); + g->transparent = stbi__get8(s); + } else { + stbi__skip(s, len); + break; + } + } + while ((len = stbi__get8(s)) != 0) + stbi__skip(s, len); + break; + } + + case 0x3B: // gif stream termination code + return (stbi_uc *) s; // using '1' causes warning on some compilers + + default: + return stbi__errpuc("unknown code", "Corrupt GIF"); + } + } + + STBI_NOTUSED(req_comp); +} + +static stbi_uc *stbi__gif_load(stbi__context *s, int *x, int *y, int *comp, int req_comp) +{ + stbi_uc *u = 0; + stbi__gif* g = (stbi__gif*) stbi__malloc(sizeof(stbi__gif)); + memset(g, 0, sizeof(*g)); + + u = stbi__gif_load_next(s, g, comp, req_comp); + if (u == (stbi_uc *) s) u = 0; // end of animated gif marker + if (u) { + *x = g->w; + *y = g->h; + if (req_comp && req_comp != 4) + u = stbi__convert_format(u, 4, req_comp, g->w, g->h); + } + else if (g->out) + STBI_FREE(g->out); + STBI_FREE(g); + return u; +} + +static int stbi__gif_info(stbi__context *s, int *x, int *y, int *comp) +{ + return stbi__gif_info_raw(s,x,y,comp); +} +#endif + +// ************************************************************************************************* +// Radiance RGBE HDR loader +// originally by Nicolas Schulz +#ifndef STBI_NO_HDR +static int stbi__hdr_test_core(stbi__context *s) +{ + const char *signature = "#?RADIANCE\n"; + int i; + for (i=0; signature[i]; ++i) + if (stbi__get8(s) != signature[i]) + return 0; + return 1; +} + +static int stbi__hdr_test(stbi__context* s) +{ + int r = stbi__hdr_test_core(s); + stbi__rewind(s); + return r; +} + +#define STBI__HDR_BUFLEN 1024 +static char *stbi__hdr_gettoken(stbi__context *z, char *buffer) +{ + int len=0; + char c = '\0'; + + c = (char) stbi__get8(z); + + while (!stbi__at_eof(z) && c != '\n') { + buffer[len++] = c; + if (len == STBI__HDR_BUFLEN-1) { + // flush to end of line + while (!stbi__at_eof(z) && stbi__get8(z) != '\n') + ; + break; + } + c = (char) stbi__get8(z); + } + + buffer[len] = 0; + return buffer; +} + +static void stbi__hdr_convert(float *output, stbi_uc *input, int req_comp) +{ + if ( input[3] != 0 ) { + float f1; + // Exponent + f1 = (float) ldexp(1.0f, input[3] - (int)(128 + 8)); + if (req_comp <= 2) + output[0] = (input[0] + input[1] + input[2]) * f1 / 3; + else { + output[0] = input[0] * f1; + output[1] = input[1] * f1; + output[2] = input[2] * f1; + } + if (req_comp == 2) output[1] = 1; + if (req_comp == 4) output[3] = 1; + } else { + switch (req_comp) { + case 4: output[3] = 1; /* fallthrough */ + case 3: output[0] = output[1] = output[2] = 0; + break; + case 2: output[1] = 1; /* fallthrough */ + case 1: output[0] = 0; + break; + } + } +} + +static float *stbi__hdr_load(stbi__context *s, int *x, int *y, int *comp, int req_comp) +{ + char buffer[STBI__HDR_BUFLEN]; + char *token; + int valid = 0; + int width, height; + stbi_uc *scanline; + float *hdr_data; + int len; + unsigned char count, value; + int i, j, k, c1,c2, z; + + + // Check identifier + if (strcmp(stbi__hdr_gettoken(s,buffer), "#?RADIANCE") != 0) + return stbi__errpf("not HDR", "Corrupt HDR image"); + + // Parse header + for(;;) { + token = stbi__hdr_gettoken(s,buffer); + if (token[0] == 0) break; + if (strcmp(token, "FORMAT=32-bit_rle_rgbe") == 0) valid = 1; + } + + if (!valid) return stbi__errpf("unsupported format", "Unsupported HDR format"); + + // Parse width and height + // can't use sscanf() if we're not using stdio! + token = stbi__hdr_gettoken(s,buffer); + if (strncmp(token, "-Y ", 3)) return stbi__errpf("unsupported data layout", "Unsupported HDR format"); + token += 3; + height = (int) strtol(token, &token, 10); + while (*token == ' ') ++token; + if (strncmp(token, "+X ", 3)) return stbi__errpf("unsupported data layout", "Unsupported HDR format"); + token += 3; + width = (int) strtol(token, NULL, 10); + + *x = width; + *y = height; + + if (comp) *comp = 3; + if (req_comp == 0) req_comp = 3; + + // Read data + hdr_data = (float *) stbi__malloc(height * width * req_comp * sizeof(float)); + + // Load image data + // image data is stored as some number of sca + if ( width < 8 || width >= 32768) { + // Read flat data + for (j=0; j < height; ++j) { + for (i=0; i < width; ++i) { + stbi_uc rgbe[4]; + main_decode_loop: + stbi__getn(s, rgbe, 4); + stbi__hdr_convert(hdr_data + j * width * req_comp + i * req_comp, rgbe, req_comp); + } + } + } else { + // Read RLE-encoded data + scanline = NULL; + + for (j = 0; j < height; ++j) { + c1 = stbi__get8(s); + c2 = stbi__get8(s); + len = stbi__get8(s); + if (c1 != 2 || c2 != 2 || (len & 0x80)) { + // not run-length encoded, so we have to actually use THIS data as a decoded + // pixel (note this can't be a valid pixel--one of RGB must be >= 128) + stbi_uc rgbe[4]; + rgbe[0] = (stbi_uc) c1; + rgbe[1] = (stbi_uc) c2; + rgbe[2] = (stbi_uc) len; + rgbe[3] = (stbi_uc) stbi__get8(s); + stbi__hdr_convert(hdr_data, rgbe, req_comp); + i = 1; + j = 0; + STBI_FREE(scanline); + goto main_decode_loop; // yes, this makes no sense + } + len <<= 8; + len |= stbi__get8(s); + if (len != width) { STBI_FREE(hdr_data); STBI_FREE(scanline); return stbi__errpf("invalid decoded scanline length", "corrupt HDR"); } + if (scanline == NULL) scanline = (stbi_uc *) stbi__malloc(width * 4); + + for (k = 0; k < 4; ++k) { + i = 0; + while (i < width) { + count = stbi__get8(s); + if (count > 128) { + // Run + value = stbi__get8(s); + count -= 128; + for (z = 0; z < count; ++z) + scanline[i++ * 4 + k] = value; + } else { + // Dump + for (z = 0; z < count; ++z) + scanline[i++ * 4 + k] = stbi__get8(s); + } + } + } + for (i=0; i < width; ++i) + stbi__hdr_convert(hdr_data+(j*width + i)*req_comp, scanline + i*4, req_comp); + } + STBI_FREE(scanline); + } + + return hdr_data; +} + +static int stbi__hdr_info(stbi__context *s, int *x, int *y, int *comp) +{ + char buffer[STBI__HDR_BUFLEN]; + char *token; + int valid = 0; + + if (stbi__hdr_test(s) == 0) { + stbi__rewind( s ); + return 0; + } + + for(;;) { + token = stbi__hdr_gettoken(s,buffer); + if (token[0] == 0) break; + if (strcmp(token, "FORMAT=32-bit_rle_rgbe") == 0) valid = 1; + } + + if (!valid) { + stbi__rewind( s ); + return 0; + } + token = stbi__hdr_gettoken(s,buffer); + if (strncmp(token, "-Y ", 3)) { + stbi__rewind( s ); + return 0; + } + token += 3; + *y = (int) strtol(token, &token, 10); + while (*token == ' ') ++token; + if (strncmp(token, "+X ", 3)) { + stbi__rewind( s ); + return 0; + } + token += 3; + *x = (int) strtol(token, NULL, 10); + *comp = 3; + return 1; +} +#endif // STBI_NO_HDR + +#ifndef STBI_NO_BMP +static int stbi__bmp_info(stbi__context *s, int *x, int *y, int *comp) +{ + void *p; + stbi__bmp_data info; + + info.all_a = 255; + p = stbi__bmp_parse_header(s, &info); + stbi__rewind( s ); + if (p == NULL) + return 0; + *x = s->img_x; + *y = s->img_y; + *comp = info.ma ? 4 : 3; + return 1; +} +#endif + +#ifndef STBI_NO_PSD +static int stbi__psd_info(stbi__context *s, int *x, int *y, int *comp) +{ + int channelCount; + if (stbi__get32be(s) != 0x38425053) { + stbi__rewind( s ); + return 0; + } + if (stbi__get16be(s) != 1) { + stbi__rewind( s ); + return 0; + } + stbi__skip(s, 6); + channelCount = stbi__get16be(s); + if (channelCount < 0 || channelCount > 16) { + stbi__rewind( s ); + return 0; + } + *y = stbi__get32be(s); + *x = stbi__get32be(s); + if (stbi__get16be(s) != 8) { + stbi__rewind( s ); + return 0; + } + if (stbi__get16be(s) != 3) { + stbi__rewind( s ); + return 0; + } + *comp = 4; + return 1; +} +#endif + +#ifndef STBI_NO_PIC +static int stbi__pic_info(stbi__context *s, int *x, int *y, int *comp) +{ + int act_comp=0,num_packets=0,chained; + stbi__pic_packet packets[10]; + + if (!stbi__pic_is4(s,"\x53\x80\xF6\x34")) { + stbi__rewind(s); + return 0; + } + + stbi__skip(s, 88); + + *x = stbi__get16be(s); + *y = stbi__get16be(s); + if (stbi__at_eof(s)) { + stbi__rewind( s); + return 0; + } + if ( (*x) != 0 && (1 << 28) / (*x) < (*y)) { + stbi__rewind( s ); + return 0; + } + + stbi__skip(s, 8); + + do { + stbi__pic_packet *packet; + + if (num_packets==sizeof(packets)/sizeof(packets[0])) + return 0; + + packet = &packets[num_packets++]; + chained = stbi__get8(s); + packet->size = stbi__get8(s); + packet->type = stbi__get8(s); + packet->channel = stbi__get8(s); + act_comp |= packet->channel; + + if (stbi__at_eof(s)) { + stbi__rewind( s ); + return 0; + } + if (packet->size != 8) { + stbi__rewind( s ); + return 0; + } + } while (chained); + + *comp = (act_comp & 0x10 ? 4 : 3); + + return 1; +} +#endif + +// ************************************************************************************************* +// Portable Gray Map and Portable Pixel Map loader +// by Ken Miller +// +// PGM: http://netpbm.sourceforge.net/doc/pgm.html +// PPM: http://netpbm.sourceforge.net/doc/ppm.html +// +// Known limitations: +// Does not support comments in the header section +// Does not support ASCII image data (formats P2 and P3) +// Does not support 16-bit-per-channel + +#ifndef STBI_NO_PNM + +static int stbi__pnm_test(stbi__context *s) +{ + char p, t; + p = (char) stbi__get8(s); + t = (char) stbi__get8(s); + if (p != 'P' || (t != '5' && t != '6')) { + stbi__rewind( s ); + return 0; + } + return 1; +} + +static stbi_uc *stbi__pnm_load(stbi__context *s, int *x, int *y, int *comp, int req_comp) +{ + stbi_uc *out; + if (!stbi__pnm_info(s, (int *)&s->img_x, (int *)&s->img_y, (int *)&s->img_n)) + return 0; + *x = s->img_x; + *y = s->img_y; + *comp = s->img_n; + + out = (stbi_uc *) stbi__malloc(s->img_n * s->img_x * s->img_y); + if (!out) return stbi__errpuc("outofmem", "Out of memory"); + stbi__getn(s, out, s->img_n * s->img_x * s->img_y); + + if (req_comp && req_comp != s->img_n) { + out = stbi__convert_format(out, s->img_n, req_comp, s->img_x, s->img_y); + if (out == NULL) return out; // stbi__convert_format frees input on failure + } + return out; +} + +static int stbi__pnm_isspace(char c) +{ + return c == ' ' || c == '\t' || c == '\n' || c == '\v' || c == '\f' || c == '\r'; +} + +static void stbi__pnm_skip_whitespace(stbi__context *s, char *c) +{ + for (;;) { + while (!stbi__at_eof(s) && stbi__pnm_isspace(*c)) + *c = (char) stbi__get8(s); + + if (stbi__at_eof(s) || *c != '#') + break; + + while (!stbi__at_eof(s) && *c != '\n' && *c != '\r' ) + *c = (char) stbi__get8(s); + } +} + +static int stbi__pnm_isdigit(char c) +{ + return c >= '0' && c <= '9'; +} + +static int stbi__pnm_getinteger(stbi__context *s, char *c) +{ + int value = 0; + + while (!stbi__at_eof(s) && stbi__pnm_isdigit(*c)) { + value = value*10 + (*c - '0'); + *c = (char) stbi__get8(s); + } + + return value; +} + +static int stbi__pnm_info(stbi__context *s, int *x, int *y, int *comp) +{ + int maxv; + char c, p, t; + + stbi__rewind( s ); + + // Get identifier + p = (char) stbi__get8(s); + t = (char) stbi__get8(s); + if (p != 'P' || (t != '5' && t != '6')) { + stbi__rewind( s ); + return 0; + } + + *comp = (t == '6') ? 3 : 1; // '5' is 1-component .pgm; '6' is 3-component .ppm + + c = (char) stbi__get8(s); + stbi__pnm_skip_whitespace(s, &c); + + *x = stbi__pnm_getinteger(s, &c); // read width + stbi__pnm_skip_whitespace(s, &c); + + *y = stbi__pnm_getinteger(s, &c); // read height + stbi__pnm_skip_whitespace(s, &c); + + maxv = stbi__pnm_getinteger(s, &c); // read max value + + if (maxv > 255) + return stbi__err("max value > 255", "PPM image not 8-bit"); + else + return 1; +} +#endif + +static int stbi__info_main(stbi__context *s, int *x, int *y, int *comp) +{ + #ifndef STBI_NO_JPEG + if (stbi__jpeg_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_PNG + if (stbi__png_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_GIF + if (stbi__gif_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_BMP + if (stbi__bmp_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_PSD + if (stbi__psd_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_PIC + if (stbi__pic_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_PNM + if (stbi__pnm_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_HDR + if (stbi__hdr_info(s, x, y, comp)) return 1; + #endif + + // test tga last because it's a crappy test! + #ifndef STBI_NO_TGA + if (stbi__tga_info(s, x, y, comp)) + return 1; + #endif + return stbi__err("unknown image type", "Image not of any known type, or corrupt"); +} + +#ifndef STBI_NO_STDIO +STBIDEF int stbi_info(char const *filename, int *x, int *y, int *comp) +{ + FILE *f = stbi__fopen(filename, "rb"); + int result; + if (!f) return stbi__err("can't fopen", "Unable to open file"); + result = stbi_info_from_file(f, x, y, comp); + fclose(f); + return result; +} + +STBIDEF int stbi_info_from_file(FILE *f, int *x, int *y, int *comp) +{ + int r; + stbi__context s; + long pos = ftell(f); + stbi__start_file(&s, f); + r = stbi__info_main(&s,x,y,comp); + fseek(f,pos,SEEK_SET); + return r; +} +#endif // !STBI_NO_STDIO + +STBIDEF int stbi_info_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp) +{ + stbi__context s; + stbi__start_mem(&s,buffer,len); + return stbi__info_main(&s,x,y,comp); +} + +STBIDEF int stbi_info_from_callbacks(stbi_io_callbacks const *c, void *user, int *x, int *y, int *comp) +{ + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *) c, user); + return stbi__info_main(&s,x,y,comp); +} + +#endif // STB_IMAGE_IMPLEMENTATION + +/* + revision history: + 2.12 (2016-04-02) fix typo in 2.11 PSD fix that caused crashes + 2.11 (2016-04-02) allocate large structures on the stack + remove white matting for transparent PSD + fix reported channel count for PNG & BMP + re-enable SSE2 in non-gcc 64-bit + support RGB-formatted JPEG + read 16-bit PNGs (only as 8-bit) + 2.10 (2016-01-22) avoid warning introduced in 2.09 by STBI_REALLOC_SIZED + 2.09 (2016-01-16) allow comments in PNM files + 16-bit-per-pixel TGA (not bit-per-component) + info() for TGA could break due to .hdr handling + info() for BMP to shares code instead of sloppy parse + can use STBI_REALLOC_SIZED if allocator doesn't support realloc + code cleanup + 2.08 (2015-09-13) fix to 2.07 cleanup, reading RGB PSD as RGBA + 2.07 (2015-09-13) fix compiler warnings + partial animated GIF support + limited 16-bpc PSD support + #ifdef unused functions + bug with < 92 byte PIC,PNM,HDR,TGA + 2.06 (2015-04-19) fix bug where PSD returns wrong '*comp' value + 2.05 (2015-04-19) fix bug in progressive JPEG handling, fix warning + 2.04 (2015-04-15) try to re-enable SIMD on MinGW 64-bit + 2.03 (2015-04-12) extra corruption checking (mmozeiko) + stbi_set_flip_vertically_on_load (nguillemot) + fix NEON support; fix mingw support + 2.02 (2015-01-19) fix incorrect assert, fix warning + 2.01 (2015-01-17) fix various warnings; suppress SIMD on gcc 32-bit without -msse2 + 2.00b (2014-12-25) fix STBI_MALLOC in progressive JPEG + 2.00 (2014-12-25) optimize JPG, including x86 SSE2 & NEON SIMD (ryg) + progressive JPEG (stb) + PGM/PPM support (Ken Miller) + STBI_MALLOC,STBI_REALLOC,STBI_FREE + GIF bugfix -- seemingly never worked + STBI_NO_*, STBI_ONLY_* + 1.48 (2014-12-14) fix incorrectly-named assert() + 1.47 (2014-12-14) 1/2/4-bit PNG support, both direct and paletted (Omar Cornut & stb) + optimize PNG (ryg) + fix bug in interlaced PNG with user-specified channel count (stb) + 1.46 (2014-08-26) + fix broken tRNS chunk (colorkey-style transparency) in non-paletted PNG + 1.45 (2014-08-16) + fix MSVC-ARM internal compiler error by wrapping malloc + 1.44 (2014-08-07) + various warning fixes from Ronny Chevalier + 1.43 (2014-07-15) + fix MSVC-only compiler problem in code changed in 1.42 + 1.42 (2014-07-09) + don't define _CRT_SECURE_NO_WARNINGS (affects user code) + fixes to stbi__cleanup_jpeg path + added STBI_ASSERT to avoid requiring assert.h + 1.41 (2014-06-25) + fix search&replace from 1.36 that messed up comments/error messages + 1.40 (2014-06-22) + fix gcc struct-initialization warning + 1.39 (2014-06-15) + fix to TGA optimization when req_comp != number of components in TGA; + fix to GIF loading because BMP wasn't rewinding (whoops, no GIFs in my test suite) + add support for BMP version 5 (more ignored fields) + 1.38 (2014-06-06) + suppress MSVC warnings on integer casts truncating values + fix accidental rename of 'skip' field of I/O + 1.37 (2014-06-04) + remove duplicate typedef + 1.36 (2014-06-03) + convert to header file single-file library + if de-iphone isn't set, load iphone images color-swapped instead of returning NULL + 1.35 (2014-05-27) + various warnings + fix broken STBI_SIMD path + fix bug where stbi_load_from_file no longer left file pointer in correct place + fix broken non-easy path for 32-bit BMP (possibly never used) + TGA optimization by Arseny Kapoulkine + 1.34 (unknown) + use STBI_NOTUSED in stbi__resample_row_generic(), fix one more leak in tga failure case + 1.33 (2011-07-14) + make stbi_is_hdr work in STBI_NO_HDR (as specified), minor compiler-friendly improvements + 1.32 (2011-07-13) + support for "info" function for all supported filetypes (SpartanJ) + 1.31 (2011-06-20) + a few more leak fixes, bug in PNG handling (SpartanJ) + 1.30 (2011-06-11) + added ability to load files via callbacks to accomidate custom input streams (Ben Wenger) + removed deprecated format-specific test/load functions + removed support for installable file formats (stbi_loader) -- would have been broken for IO callbacks anyway + error cases in bmp and tga give messages and don't leak (Raymond Barbiero, grisha) + fix inefficiency in decoding 32-bit BMP (David Woo) + 1.29 (2010-08-16) + various warning fixes from Aurelien Pocheville + 1.28 (2010-08-01) + fix bug in GIF palette transparency (SpartanJ) + 1.27 (2010-08-01) + cast-to-stbi_uc to fix warnings + 1.26 (2010-07-24) + fix bug in file buffering for PNG reported by SpartanJ + 1.25 (2010-07-17) + refix trans_data warning (Won Chun) + 1.24 (2010-07-12) + perf improvements reading from files on platforms with lock-heavy fgetc() + minor perf improvements for jpeg + deprecated type-specific functions so we'll get feedback if they're needed + attempt to fix trans_data warning (Won Chun) + 1.23 fixed bug in iPhone support + 1.22 (2010-07-10) + removed image *writing* support + stbi_info support from Jetro Lauha + GIF support from Jean-Marc Lienher + iPhone PNG-extensions from James Brown + warning-fixes from Nicolas Schulz and Janez Zemva (i.stbi__err. Janez (U+017D)emva) + 1.21 fix use of 'stbi_uc' in header (reported by jon blow) + 1.20 added support for Softimage PIC, by Tom Seddon + 1.19 bug in interlaced PNG corruption check (found by ryg) + 1.18 (2008-08-02) + fix a threading bug (local mutable static) + 1.17 support interlaced PNG + 1.16 major bugfix - stbi__convert_format converted one too many pixels + 1.15 initialize some fields for thread safety + 1.14 fix threadsafe conversion bug + header-file-only version (#define STBI_HEADER_FILE_ONLY before including) + 1.13 threadsafe + 1.12 const qualifiers in the API + 1.11 Support installable IDCT, colorspace conversion routines + 1.10 Fixes for 64-bit (don't use "unsigned long") + optimized upsampling by Fabian "ryg" Giesen + 1.09 Fix format-conversion for PSD code (bad global variables!) + 1.08 Thatcher Ulrich's PSD code integrated by Nicolas Schulz + 1.07 attempt to fix C++ warning/errors again + 1.06 attempt to fix C++ warning/errors again + 1.05 fix TGA loading to return correct *comp and use good luminance calc + 1.04 default float alpha is 1, not 255; use 'void *' for stbi_image_free + 1.03 bugfixes to STBI_NO_STDIO, STBI_NO_HDR + 1.02 support for (subset of) HDR files, float interface for preferred access to them + 1.01 fix bug: possible bug in handling right-side up bmps... not sure + fix bug: the stbi__bmp_load() and stbi__tga_load() functions didn't work at all + 1.00 interface to zlib that skips zlib header + 0.99 correct handling of alpha in palette + 0.98 TGA loader by lonesock; dynamically add loaders (untested) + 0.97 jpeg errors on too large a file; also catch another malloc failure + 0.96 fix detection of invalid v value - particleman@mollyrocket forum + 0.95 during header scan, seek to markers in case of padding + 0.94 STBI_NO_STDIO to disable stdio usage; rename all #defines the same + 0.93 handle jpegtran output; verbose errors + 0.92 read 4,8,16,24,32-bit BMP files of several formats + 0.91 output 24-bit Windows 3.0 BMP files + 0.90 fix a few more warnings; bump version number to approach 1.0 + 0.61 bugfixes due to Marc LeBlanc, Christopher Lloyd + 0.60 fix compiling as c++ + 0.59 fix warnings: merge Dave Moore's -Wall fixes + 0.58 fix bug: zlib uncompressed mode len/nlen was wrong endian + 0.57 fix bug: jpg last huffman symbol before marker was >9 bits but less than 16 available + 0.56 fix bug: zlib uncompressed mode len vs. nlen + 0.55 fix bug: restart_interval not initialized to 0 + 0.54 allow NULL for 'int *comp' + 0.53 fix bug in png 3->4; speedup png decoding + 0.52 png handles req_comp=3,4 directly; minor cleanup; jpeg comments + 0.51 obey req_comp requests, 1-component jpegs return as 1-component, + on 'test' only check type, not whether we support this variant + 0.50 (2006-11-19) + first released version +*/ diff --git a/examples/viewer/viewer.cc b/examples/viewer/viewer.cc index 9ebbe40..3bce085 100644 --- a/examples/viewer/viewer.cc +++ b/examples/viewer/viewer.cc @@ -8,6 +8,7 @@ #include #include #include +#include #include #include @@ -26,12 +27,24 @@ #include "trackball.h" +#define STB_IMAGE_IMPLEMENTATION +#include "stb_image.h" + #ifdef _WIN32 #ifdef __cplusplus extern "C" { #endif -#include #include + +#ifdef max + #undef max +#endif + +#ifdef min + #undef min +#endif + +#include #ifdef __cplusplus } #endif @@ -112,6 +125,7 @@ class timerutil { typedef struct { GLuint vb; // vertex buffer int numTriangles; + size_t material_id; } DrawObject; std::vector gDrawObjects; @@ -163,10 +177,11 @@ void CalcNormal(float N[3], float v0[3], float v1[3], float v2[3]) { bool LoadObjAndConvert(float bmin[3], float bmax[3], std::vector* drawObjects, + std::vector& materials, + std::map& textures, const char* filename) { tinyobj::attrib_t attrib; std::vector shapes; - std::vector materials; timerutil tm; @@ -194,17 +209,83 @@ bool LoadObjAndConvert(float bmin[3], float bmax[3], printf("# of materials = %d\n", (int)materials.size()); printf("# of shapes = %d\n", (int)shapes.size()); + // Load diffuse textures + { + for (size_t m = 0; m < materials.size(); m++) { + tinyobj::material_t* mp = &materials[m]; + + if (mp->diffuse_texname.length() > 0) { + // Only load the texture if it is not already loaded + if (textures.find(mp->diffuse_texname) == textures.end()) { + GLuint texture_id; + int w, h; + int comp; + unsigned char* image = stbi_load(mp->diffuse_texname.c_str(), &w, &h, &comp, STBI_default); + if (image == nullptr) { + std::cerr << "Unable to load texture: " << mp->diffuse_texname << std::endl; + exit(1); + } + glGenTextures(1, &texture_id); + glBindTexture(GL_TEXTURE_2D, texture_id); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + if (comp == 3) { + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, w, h, 0, GL_RGB, GL_UNSIGNED_BYTE, image); + } + else if (comp == 4) { + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, image); + } + glBindTexture(GL_TEXTURE_2D, 0); + stbi_image_free(image); + textures.insert(std::make_pair(mp->diffuse_texname, texture_id)); + } + } + } + } + bmin[0] = bmin[1] = bmin[2] = std::numeric_limits::max(); bmax[0] = bmax[1] = bmax[2] = -std::numeric_limits::max(); { for (size_t s = 0; s < shapes.size(); s++) { + size_t current_material_id = 0; + if (shapes[s].mesh.material_ids.size() > 0) { + // Base case + current_material_id = shapes[s].mesh.material_ids[s]; + std::cerr << "Setting base case: " << current_material_id << std::endl; + } DrawObject o; std::vector vb; // pos(3float), normal(3float), color(3float) for (size_t f = 0; f < shapes[s].mesh.indices.size() / 3; f++) { tinyobj::index_t idx0 = shapes[s].mesh.indices[3 * f + 0]; tinyobj::index_t idx1 = shapes[s].mesh.indices[3 * f + 1]; tinyobj::index_t idx2 = shapes[s].mesh.indices[3 * f + 2]; + + current_material_id = shapes[s].mesh.material_ids[f]; + + if (current_material_id >= materials.size()) { + std::cerr << "Invalid material index: " << current_material_id << std::endl; + } + + float diffuse[3]; + for (size_t i = 0; i < 3; i++) { + diffuse[i] = materials[current_material_id].diffuse[i]; + } + float tc[3][2]; + if (attrib.texcoords.size() > 0) { + assert(attrib.texcoords.size() > 2 * idx0.texcoord_index + 1); + assert(attrib.texcoords.size() > 2 * idx1.texcoord_index + 1); + assert(attrib.texcoords.size() > 2 * idx2.texcoord_index + 1); + tc[0][0] = attrib.texcoords[2 * idx0.texcoord_index]; + tc[0][1] = 1.0f - attrib.texcoords[2 * idx0.texcoord_index + 1]; + tc[1][0] = attrib.texcoords[2 * idx1.texcoord_index]; + tc[1][1] = 1.0f - attrib.texcoords[2 * idx1.texcoord_index + 1]; + tc[2][0] = attrib.texcoords[2 * idx2.texcoord_index]; + tc[2][1] = 1.0f - attrib.texcoords[2 * idx2.texcoord_index + 1]; + } else { + std::cerr << "Texcoordinates are not defined" << std::endl; + exit(2); + } float v[3][3]; for (int k = 0; k < 3; k++) { @@ -227,7 +308,6 @@ bool LoadObjAndConvert(float bmin[3], float bmax[3], } float n[3][3]; - if (attrib.normals.size() > 0) { int f0 = idx0.normal_index; int f1 = idx1.normal_index; @@ -258,8 +338,14 @@ bool LoadObjAndConvert(float bmin[3], float bmax[3], vb.push_back(n[k][0]); vb.push_back(n[k][1]); vb.push_back(n[k][2]); - // Use normal as color. - float c[3] = {n[k][0], n[k][1], n[k][2]}; + // Combine normal and diffuse to get color. + float normal_factor = 0.2; + float diffuse_factor = 1 - normal_factor; + float c[3] = { + n[k][0] * normal_factor + diffuse[0] * diffuse_factor, + n[k][1] * normal_factor + diffuse[1] * diffuse_factor, + n[k][2] * normal_factor + diffuse[2] * diffuse_factor + }; float len2 = c[0] * c[0] + c[1] * c[1] + c[2] * c[2]; if (len2 > 0.0f) { float len = sqrtf(len2); @@ -271,17 +357,21 @@ bool LoadObjAndConvert(float bmin[3], float bmax[3], vb.push_back(c[0] * 0.5 + 0.5); vb.push_back(c[1] * 0.5 + 0.5); vb.push_back(c[2] * 0.5 + 0.5); + + vb.push_back(tc[k][0]); + vb.push_back(tc[k][1]); } } o.vb = 0; o.numTriangles = 0; + o.material_id = current_material_id; if (vb.size() > 0) { glGenBuffers(1, &o.vb); glBindBuffer(GL_ARRAY_BUFFER, o.vb); glBufferData(GL_ARRAY_BUFFER, vb.size() * sizeof(float), &vb.at(0), GL_STATIC_DRAW); - o.numTriangles = vb.size() / 9 / 3; + o.numTriangles = vb.size() / (3 + 3 + 3 + 2) * 3; printf("shape[%d] # of triangles = %d\n", static_cast(s), o.numTriangles); } @@ -395,13 +485,13 @@ void motionFunc(GLFWwindow* window, double mouse_x, double mouse_y) { prevMouseY = mouse_y; } -void Draw(const std::vector& drawObjects) { +void Draw(const std::vector& drawObjects, std::vector& materials, std::map& textures) { glPolygonMode(GL_FRONT, GL_FILL); glPolygonMode(GL_BACK, GL_FILL); glEnable(GL_POLYGON_OFFSET_FILL); glPolygonOffset(1.0, 1.0); - glColor3f(1.0f, 1.0f, 1.0f); + GLsizei stride = (3 + 3 + 3 + 2) * sizeof(float); for (size_t i = 0; i < drawObjects.size(); i++) { DrawObject o = drawObjects[i]; if (o.vb < 1) { @@ -412,12 +502,20 @@ void Draw(const std::vector& drawObjects) { glEnableClientState(GL_VERTEX_ARRAY); glEnableClientState(GL_NORMAL_ARRAY); glEnableClientState(GL_COLOR_ARRAY); - glVertexPointer(3, GL_FLOAT, 36, (const void*)0); - glNormalPointer(GL_FLOAT, 36, (const void*)(sizeof(float) * 3)); - glColorPointer(3, GL_FLOAT, 36, (const void*)(sizeof(float) * 6)); + glEnableClientState(GL_TEXTURE_COORD_ARRAY); + + std::string diffuse_texname = materials[o.material_id].diffuse_texname; + if (diffuse_texname.length() > 0) { + glBindTexture(GL_TEXTURE_2D, textures[diffuse_texname]); + } + glVertexPointer(3, GL_FLOAT, stride, (const void*)0); + glNormalPointer(GL_FLOAT, stride, (const void*)(sizeof(float) * 3)); + glColorPointer(3, GL_FLOAT, stride, (const void*)(sizeof(float) * 6)); + glTexCoordPointer(2, GL_FLOAT, stride, (const void*)(sizeof(float) * 9)); glDrawArrays(GL_TRIANGLES, 0, 3 * o.numTriangles); CheckErrors("drawarrays"); + glBindTexture(GL_TEXTURE_2D, 0); } // draw wireframe @@ -436,8 +534,11 @@ void Draw(const std::vector& drawObjects) { glEnableClientState(GL_VERTEX_ARRAY); glEnableClientState(GL_NORMAL_ARRAY); glDisableClientState(GL_COLOR_ARRAY); - glVertexPointer(3, GL_FLOAT, 36, (const void*)0); - glNormalPointer(GL_FLOAT, 36, (const void*)(sizeof(float) * 3)); + glDisableClientState(GL_TEXTURE_COORD_ARRAY); + glVertexPointer(3, GL_FLOAT, stride, (const void*)0); + glNormalPointer(GL_FLOAT, stride, (const void*)(sizeof(float) * 3)); + glColorPointer(3, GL_FLOAT, stride, (const void*)(sizeof(float) * 6)); + glTexCoordPointer(2, GL_FLOAT, stride, (const void*)(sizeof(float) * 9)); glDrawArrays(GL_TRIANGLES, 0, 3 * o.numTriangles); CheckErrors("drawarrays"); @@ -498,7 +599,9 @@ int main(int argc, char** argv) { reshapeFunc(window, width, height); float bmin[3], bmax[3]; - if (false == LoadObjAndConvert(bmin, bmax, &gDrawObjects, argv[1])) { + std::vector materials; + std::map textures; + if (false == LoadObjAndConvert(bmin, bmax, &gDrawObjects, materials, textures, argv[1])) { return -1; } @@ -516,6 +619,7 @@ int main(int argc, char** argv) { glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glEnable(GL_DEPTH_TEST); + glEnable(GL_TEXTURE_2D); // camera & rotate glMatrixMode(GL_MODELVIEW); @@ -533,7 +637,7 @@ int main(int argc, char** argv) { glTranslatef(-0.5 * (bmax[0] + bmin[0]), -0.5 * (bmax[1] + bmin[1]), -0.5 * (bmax[2] + bmin[2])); - Draw(gDrawObjects); + Draw(gDrawObjects, materials, textures); glfwSwapBuffers(window); } -- cgit v1.2.3 From 35889026d6027b4df785cf478f4b3a96980eed95 Mon Sep 17 00:00:00 2001 From: Chris Liebert Date: Fri, 7 Oct 2016 21:21:33 -0700 Subject: added additional bounds checking --- examples/viewer/viewer.cc | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/examples/viewer/viewer.cc b/examples/viewer/viewer.cc index 3bce085..323faac 100644 --- a/examples/viewer/viewer.cc +++ b/examples/viewer/viewer.cc @@ -249,10 +249,9 @@ bool LoadObjAndConvert(float bmin[3], float bmax[3], { for (size_t s = 0; s < shapes.size(); s++) { size_t current_material_id = 0; - if (shapes[s].mesh.material_ids.size() > 0) { + if (shapes[s].mesh.material_ids.size() > 0 && shapes[s].mesh.material_ids.size() > s) { // Base case current_material_id = shapes[s].mesh.material_ids[s]; - std::cerr << "Setting base case: " << current_material_id << std::endl; } DrawObject o; std::vector vb; // pos(3float), normal(3float), color(3float) -- cgit v1.2.3 From 039d4a6c54f7650a55b53a2fd68c38765867ad80 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Tue, 18 Oct 2016 17:33:28 +0900 Subject: Fix a shape is lost if obj ends with a 'usemtl'. Fixes #104 --- models/usemtl-issue-104.obj | 30 ++++++++++++++++++++++++++++++ tests/tester.cc | 16 ++++++++++++++++ tiny_obj_loader.h | 11 +++++++++-- 3 files changed, 55 insertions(+), 2 deletions(-) create mode 100644 models/usemtl-issue-104.obj diff --git a/models/usemtl-issue-104.obj b/models/usemtl-issue-104.obj new file mode 100644 index 0000000..5183676 --- /dev/null +++ b/models/usemtl-issue-104.obj @@ -0,0 +1,30 @@ +# cornell_box.obj and cornell_box.mtl are grabbed from Intel's embree project. +# original cornell box data + # comment + +# empty line including some space + + +mtllib cornell_box.mtl + +o floor +v 552.8 0.0 0.0 +v 0.0 0.0 0.0 +v 0.0 0.0 559.2 +v 549.6 0.0 559.2 + +v 130.0 0.0 65.0 +v 82.0 0.0 225.0 +v 240.0 0.0 272.0 +v 290.0 0.0 114.0 + +v 423.0 0.0 247.0 +v 265.0 0.0 296.0 +v 314.0 0.0 456.0 +v 472.0 0.0 406.0 + +f 1 2 3 4 +f 8 7 6 5 +f 12 11 10 9 + +usemtl white diff --git a/tests/tester.cc b/tests/tester.cc index 5573bb6..f38238d 100644 --- a/tests/tester.cc +++ b/tests/tester.cc @@ -402,6 +402,7 @@ TEST_CASE("transmittance_filter_Tf", "[Issue95-Tf]") { REQUIRE(0.2 == Approx(materials[0].transmittance[1])); REQUIRE(0.3 == Approx(materials[0].transmittance[2])); } + TEST_CASE("transmittance_filter_Kt", "[Issue95-Kt]") { tinyobj::attrib_t attrib; std::vector shapes; @@ -420,6 +421,21 @@ TEST_CASE("transmittance_filter_Kt", "[Issue95-Kt]") { REQUIRE(0.3 == Approx(materials[0].transmittance[2])); } +TEST_CASE("usemtl_at_last_line", "[Issue104]") { + tinyobj::attrib_t attrib; + std::vector shapes; + std::vector materials; + + std::string err; + bool ret = tinyobj::LoadObj(&attrib, &shapes, &materials, &err, "../models/usemtl-issue-104.obj", gMtlBasePath); + + if (!err.empty()) { + std::cerr << err << std::endl; + } + REQUIRE(true == ret); + REQUIRE(1 == shapes.size()); +} + #if 0 int main( diff --git a/tiny_obj_loader.h b/tiny_obj_loader.h index 0200ff5..6ee59c1 100644 --- a/tiny_obj_loader.h +++ b/tiny_obj_loader.h @@ -23,6 +23,7 @@ THE SOFTWARE. */ // +// version 1.0.1 : Fixes a shape is lost if obj ends with a 'usemtl'(#104) // version 1.0.0 : Change data structure. Change license from BSD to MIT. // @@ -1161,7 +1162,9 @@ bool LoadObj(attrib_t *attrib, std::vector *shapes, } if (newMaterialId != material) { - // Create per-face material + // Create per-face material. Thus we don't add `shape` to `shapes` at + // this time. + // just clear `faceGroup` after `exportFaceGroupToShape()` call. exportFaceGroupToShape(&shape, faceGroup, tags, material, name, triangulate); faceGroup.clear(); @@ -1307,7 +1310,11 @@ bool LoadObj(attrib_t *attrib, std::vector *shapes, bool ret = exportFaceGroupToShape(&shape, faceGroup, tags, material, name, triangulate); - if (ret) { + // exportFaceGroupToShape return false when `usemtl` is called in the last + // line. + // we also add `shape` to `shapes` when `shape.mesh` has already some + // faces(indices) + if (ret || shape.mesh.indices.size()) { shapes->push_back(shape); } faceGroup.clear(); // for safety -- cgit v1.2.3 From ebdbd8a231501903831f8d1602be542ff7a717b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Steinbrink?= Date: Mon, 24 Oct 2016 11:28:33 +0200 Subject: Avoid unnecessary reallocations of the "linebuf" string safeGetline() already clears the string buffer before writing to it, so the same buffer can be used multiple times, but the loops calling safeGetline() have the string scoped within the loop, so its constructed and destructed in each loop iteration, causing lots of unnecessary allocations. Parse times for some large .obj files (without asan): File A File B File C Before 2743ms 589ms 615ms After 2500ms 573ms 545ms --- tiny_obj_loader.h | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tiny_obj_loader.h b/tiny_obj_loader.h index 2276adc..b39cb89 100644 --- a/tiny_obj_loader.h +++ b/tiny_obj_loader.h @@ -692,9 +692,8 @@ void LoadMtl(std::map *material_map, material_t material; InitMaterial(&material); + std::string linebuf; while (inStream->peek() != -1) { - std::string linebuf; - safeGetline(*inStream, linebuf); // Trim trailing whitespace. @@ -1090,8 +1089,8 @@ bool LoadObj(attrib_t *attrib, std::vector *shapes, shape_t shape; + std::string linebuf; while (inStream->peek() != -1) { - std::string linebuf; safeGetline(*inStream, linebuf); // Trim newline '\r\n' or '\n' -- cgit v1.2.3 From aa670fe91eee32072b360d802a6d6db86bc4eb0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Steinbrink?= Date: Mon, 24 Oct 2016 11:53:00 +0200 Subject: Use a lookup table to speed up float parsing The pow() function is pretty expensive, so creating a small lookup table for the first few negative powers of ten provides a big speedup. Parse times for some large .obj files (without asan): File A File B File C Before 2500ms 573ms 545ms After 1239ms 294ms 271ms --- tiny_obj_loader.h | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/tiny_obj_loader.h b/tiny_obj_loader.h index b39cb89..f043628 100644 --- a/tiny_obj_loader.h +++ b/tiny_obj_loader.h @@ -418,8 +418,21 @@ static bool tryParseDouble(const char *s, const char *s_end, double *result) { read = 1; end_not_reached = (curr != s_end); while (end_not_reached && IS_DIGIT(*curr)) { + static const double pow_lut[] = { + 1.0, + 0.1, + 0.01, + 0.001, + 0.0001, + 0.00001, + 0.000001, + 0.0000001, + }; + const int lut_entries = sizeof pow_lut / sizeof pow_lut[0]; + // NOTE: Don't use powf here, it will absolutely murder precision. - mantissa += static_cast(*curr - 0x30) * pow(10.0, -read); + mantissa += static_cast(*curr - 0x30) * + (read < lut_entries ? pow_lut[read] : pow(10.0, -read)); read++; curr++; end_not_reached = (curr != s_end); -- cgit v1.2.3 From d6eeb14216f77cda6d089f737b2b21dcb30e326c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Steinbrink?= Date: Mon, 24 Oct 2016 14:45:40 +0200 Subject: Avoid unnecessary ldexp() and pow() calls Parse times for some large .obj files (without asan): File A File B File C Before 1239ms 294ms 271ms After 1037ms 203ms 190ms --- tiny_obj_loader.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tiny_obj_loader.h b/tiny_obj_loader.h index f043628..b62321b 100644 --- a/tiny_obj_loader.h +++ b/tiny_obj_loader.h @@ -472,8 +472,8 @@ static bool tryParseDouble(const char *s, const char *s_end, double *result) { } assemble: - *result = - (sign == '+' ? 1 : -1) * ldexp(mantissa * pow(5.0, exponent), exponent); + *result = (sign == '+' ? 1 : -1) * + (exponent ? ldexp(mantissa * pow(5.0, exponent), exponent) : mantissa); return true; fail: return false; -- cgit v1.2.3 From 4d6649cc6d350e32d142876149490deec2348ecb Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Mon, 24 Oct 2016 23:54:20 +0900 Subject: Suppress compiler warnings. --- tiny_obj_loader.h | 1 + 1 file changed, 1 insertion(+) diff --git a/tiny_obj_loader.h b/tiny_obj_loader.h index b62321b..a46b590 100644 --- a/tiny_obj_loader.h +++ b/tiny_obj_loader.h @@ -1040,6 +1040,7 @@ bool MaterialStreamReader::operator()(const std::string &matId, std::vector *materials, std::map *matMap, std::string *err) { + (void)matId; LoadMtl(matMap, materials, &m_inStream); if (!m_inStream) { std::stringstream ss; -- cgit v1.2.3 From 6c6390f034f3bdef854f9f0321833493224c48cf Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Mon, 24 Oct 2016 23:58:23 +0900 Subject: Fix typo. --- examples/voxelize/main.cc | 74 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 examples/voxelize/main.cc diff --git a/examples/voxelize/main.cc b/examples/voxelize/main.cc new file mode 100644 index 0000000..035e3a1 --- /dev/null +++ b/examples/voxelize/main.cc @@ -0,0 +1,74 @@ +#define VOXELIZER_IMPLEMENTATION +#include "voxelizer.h" + +#define TINYOBJLOADER_IMPLEMENTATION +#include "../../tiny_obj_loader.h" + +bool Voxelize(const char* filename, float voxelsizex, float voxelsizey, float voxelsizez, float precision) +{ + tinyobj::attrib_t attrib; + std::vector shapes; + std::vector materials; + std::string err; + bool ret = tinyobj::LoadObj(&attrib, &shapes, &materials, &err, filename); + + if (!err.empty()) { + printf("err: %s\n", err.c_str()); + } + + if (!ret) { + printf("failed to load : %s\n", filename); + return false; + } + + if (shapes.size() == 0) { + printf("err: # of shapes are zero.\n"); + return false; + } + + // Only use first shape. + { + vx_mesh_t* mesh; + vx_mesh_t* result; + + mesh = vx_mesh_alloc(attrib.vertices.size(), shapes[0].mesh.indices.size()); + + for (size_t f = 0; f < shapes[0].mesh.indices.size(); f++) { + mesh->indices[f] = shapes[0].mesh.indices[f].vertex_index; + } + + for (size_t v = 0; v < attrib.vertices.size() / 3; v++) { + mesh->vertices[v].x = attrib.vertices[3*v+0]; + mesh->vertices[v].y = attrib.vertices[3*v+1]; + mesh->vertices[v].z = attrib.vertices[3*v+2]; + } + + result = vx_voxelize(mesh, voxelsizex, voxelsizey, voxelsizez, precision); + + printf("Number of vertices: %ld\n", result->nvertices); + printf("Number of indices: %ld\n", result->nindices); + } + return true; +} + + +int +main( + int argc, + char** argv) +{ + if (argc < 4) { + printf("Usage: voxelize input.obj voxelsizex voxelsizey voxelsizez precision\n"); + exit(-1); + } + + const char* filename = argv[1]; + float voxelsizex = atof(argv[2]); + float voxelsizey = atof(argv[3]); + float voxelsizez = atof(argv[4]); + float prec = atof(argv[5]); + bool ret = Voxelize(filename, voxelsizex, voxelsizey, voxelsizez, prec); + + return ret ? EXIT_SUCCESS : EXIT_FAILURE; +} + -- cgit v1.2.3 From c2474e27ab05a6db067dfa5dafbc62397cb4ea50 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Mon, 24 Oct 2016 23:58:33 +0900 Subject: Fix seg fault when no material assigned to object in viewer example. Bump version v1.0.2. --- examples/viewer/viewer.cc | 30 ++++++++++++++++++++---------- loader_example.cc | 14 +++++++------- tiny_obj_loader.h | 2 ++ 3 files changed, 29 insertions(+), 17 deletions(-) diff --git a/examples/viewer/viewer.cc b/examples/viewer/viewer.cc index 323faac..09fcb67 100644 --- a/examples/viewer/viewer.cc +++ b/examples/viewer/viewer.cc @@ -209,6 +209,9 @@ bool LoadObjAndConvert(float bmin[3], float bmax[3], printf("# of materials = %d\n", (int)materials.size()); printf("# of shapes = %d\n", (int)shapes.size()); + // Append `default` material + materials.push_back(tinyobj::material_t()); + // Load diffuse textures { for (size_t m = 0; m < materials.size(); m++) { @@ -248,11 +251,6 @@ bool LoadObjAndConvert(float bmin[3], float bmax[3], { for (size_t s = 0; s < shapes.size(); s++) { - size_t current_material_id = 0; - if (shapes[s].mesh.material_ids.size() > 0 && shapes[s].mesh.material_ids.size() > s) { - // Base case - current_material_id = shapes[s].mesh.material_ids[s]; - } DrawObject o; std::vector vb; // pos(3float), normal(3float), color(3float) for (size_t f = 0; f < shapes[s].mesh.indices.size() / 3; f++) { @@ -260,12 +258,16 @@ bool LoadObjAndConvert(float bmin[3], float bmax[3], tinyobj::index_t idx1 = shapes[s].mesh.indices[3 * f + 1]; tinyobj::index_t idx2 = shapes[s].mesh.indices[3 * f + 2]; - current_material_id = shapes[s].mesh.material_ids[f]; + int current_material_id = shapes[s].mesh.material_ids[f]; - if (current_material_id >= materials.size()) { - std::cerr << "Invalid material index: " << current_material_id << std::endl; + if ((current_material_id < 0) || (current_material_id >= static_cast(materials.size()))) { + // Invaid material ID. Use default material. + current_material_id = materials.size() - 1; // Default material is added to the last item in `materials`. } - + //if (current_material_id >= materials.size()) { + // std::cerr << "Invalid material index: " << current_material_id << std::endl; + //} + // float diffuse[3]; for (size_t i = 0; i < 3; i++) { diffuse[i] = materials[current_material_id].diffuse[i]; @@ -364,7 +366,15 @@ bool LoadObjAndConvert(float bmin[3], float bmax[3], o.vb = 0; o.numTriangles = 0; - o.material_id = current_material_id; + + // OpenGL viewer does not support texturing with per-face material. + if (shapes[s].mesh.material_ids.size() > 0 && shapes[s].mesh.material_ids.size() > s) { + // Base case + o.material_id = shapes[s].mesh.material_ids[s]; + } else { + o.material_id = materials.size() - 1; // = ID for default material. + } + if (vb.size() > 0) { glGenBuffers(1, &o.vb); glBindBuffer(GL_ARRAY_BUFFER, o.vb); diff --git a/loader_example.cc b/loader_example.cc index 076ab3d..df74a6c 100644 --- a/loader_example.cc +++ b/loader_example.cc @@ -236,13 +236,13 @@ static void PrintInfo(const tinyobj::attrib_t& attrib, printf(" material.map_d = %s\n", materials[i].alpha_texname.c_str()); printf(" material.disp = %s\n", materials[i].displacement_texname.c_str()); printf(" <>\n"); - printf(" material.Pr = %f\n", materials[i].roughness); - printf(" material.Pm = %f\n", materials[i].metallic); - printf(" material.Ps = %f\n", materials[i].sheen); - printf(" material.Pc = %f\n", materials[i].clearcoat_thickness); - printf(" material.Pcr = %f\n", materials[i].clearcoat_thickness); - printf(" material.aniso = %f\n", materials[i].anisotropy); - printf(" material.anisor = %f\n", materials[i].anisotropy_rotation); + printf(" material.Pr = %f\n", static_cast(materials[i].roughness)); + printf(" material.Pm = %f\n", static_cast(materials[i].metallic)); + printf(" material.Ps = %f\n", static_cast(materials[i].sheen)); + printf(" material.Pc = %f\n", static_cast(materials[i].clearcoat_thickness)); + printf(" material.Pcr = %f\n", static_cast(materials[i].clearcoat_thickness)); + printf(" material.aniso = %f\n", static_cast(materials[i].anisotropy)); + printf(" material.anisor = %f\n", static_cast(materials[i].anisotropy_rotation)); printf(" material.map_Ke = %s\n", materials[i].emissive_texname.c_str()); printf(" material.map_Pr = %s\n", materials[i].roughness_texname.c_str()); printf(" material.map_Pm = %s\n", materials[i].metallic_texname.c_str()); diff --git a/tiny_obj_loader.h b/tiny_obj_loader.h index a46b590..9c9f77d 100644 --- a/tiny_obj_loader.h +++ b/tiny_obj_loader.h @@ -23,6 +23,7 @@ THE SOFTWARE. */ // +// version 1.0.2 : Improve parsing speed by about a factor of 2 for large files(#105) // version 1.0.1 : Fixes a shape is lost if obj ends with a 'usemtl'(#104) // version 1.0.0 : Change data structure. Change license from BSD to MIT. // @@ -75,6 +76,7 @@ typedef struct { float clearcoat_roughness; // [0, 1] default 0 float anisotropy; // aniso. [0, 1] default 0 float anisotropy_rotation; // anisor. [0, 1] default 0 + float pad0; std::string roughness_texname; // map_Pr std::string metallic_texname; // map_Pm std::string sheen_texname; // map_Ps -- cgit v1.2.3 From 582eb2b818ee5454aec73822d43c8c5e95cfc404 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Wed, 2 Nov 2016 02:11:17 +0900 Subject: Initial support of texture options. --- loader_example.cc | 1 + models/texture-options-issue-85.mtl | 20 ++++ models/texture-options-issue-85.obj | 7 ++ tiny_obj_loader.h | 226 +++++++++++++++++++++++++++++++++--- 4 files changed, 237 insertions(+), 17 deletions(-) create mode 100644 models/texture-options-issue-85.mtl create mode 100644 models/texture-options-issue-85.obj diff --git a/loader_example.cc b/loader_example.cc index df74a6c..6a12a21 100644 --- a/loader_example.cc +++ b/loader_example.cc @@ -233,6 +233,7 @@ static void PrintInfo(const tinyobj::attrib_t& attrib, printf(" material.map_Ns = %s\n", materials[i].specular_highlight_texname.c_str()); printf(" material.map_bump = %s\n", materials[i].bump_texname.c_str()); + printf(" bump_multiplier = %f\n", static_cast(materials[i].bump_texopt.bump_multiplier)); printf(" material.map_d = %s\n", materials[i].alpha_texname.c_str()); printf(" material.disp = %s\n", materials[i].displacement_texname.c_str()); printf(" <>\n"); diff --git a/models/texture-options-issue-85.mtl b/models/texture-options-issue-85.mtl new file mode 100644 index 0000000..49879cd --- /dev/null +++ b/models/texture-options-issue-85.mtl @@ -0,0 +1,20 @@ +newmtl default +Ka 0 0 0 +Kd 0 0 0 +Ks 0 0 0 +Kt 0.1 0.2 0.3 +map_Kd -o 0.1 diffuse.jpg +map_Ks -o 0.1 0.2 specular.jpg +map_Ns -o 0.1 0.2 0.3 specular_highlight.jpg +map_bump -bm 3 bumpmap.jpg + +newmtl bm2 +Ka 0 0 0 +Kd 0 0 0 +Ks 0 0 0 +Kt 0.1 0.2 0.3 +# blendu +map_Kd -blendu on diffuse.jpg +map_Ks -blendv off specular.jpg +# -bm after filename +map_bump bumpmap2.jpg -bm 1.5 diff --git a/models/texture-options-issue-85.obj b/models/texture-options-issue-85.obj new file mode 100644 index 0000000..f7abb0c --- /dev/null +++ b/models/texture-options-issue-85.obj @@ -0,0 +1,7 @@ +mtllib texture-options-issue-85.mtl +o Test +v 1.864151 -1.219172 -5.532511 +v 0.575869 -0.666304 5.896140 +v 0.940448 1.000000 -1.971128 +usemtl default +f 1 2 3 diff --git a/tiny_obj_loader.h b/tiny_obj_loader.h index 9c9f77d..794baa4 100644 --- a/tiny_obj_loader.h +++ b/tiny_obj_loader.h @@ -43,6 +43,64 @@ THE SOFTWARE. namespace tinyobj { +// https://en.wikipedia.org/wiki/Wavefront_.obj_file says ... +// +// -blendu on | off # set horizontal texture blending (default on) +// -blendv on | off # set vertical texture blending (default on) +// -boost float_value # boost mip-map sharpness +// -mm base_value gain_value # modify texture map values (default 0 1) +// # base_value = brightness, gain_value = contrast +// -o u [v [w]] # Origin offset (default 0 0 0) +// -s u [v [w]] # Scale (default 1 1 1) +// -t u [v [w]] # Turbulence (default 0 0 0) +// -texres resolution # texture resolution to create +// -clamp on | off # only render texels in the clamped 0-1 range (default off) +// # When unclamped, textures are repeated across a surface, +// # when clamped, only texels which fall within the 0-1 +// # range are rendered. +// -bm mult_value # bump multiplier (for bump maps only) +// +// -imfchan r | g | b | m | l | z # specifies which channel of the file is used to +// # create a scalar or bump texture. r:red, g:green, +// # b:blue, m:matte, l:luminance, z:z-depth.. +// # (the default for bump is 'l' and for decal is 'm') +// bump -imfchan r bumpmap.tga # says to use the red channel of bumpmap.tga as the bumpmap +// +// For reflection maps... +// +// -type sphere # specifies a sphere for a "refl" reflection map +// -type cube_top | cube_bottom | # when using a cube map, the texture file for each +// cube_front | cube_back | # side of the cube is specified separately +// cube_left | cube_right + + +typedef enum { + TEXTURE_TYPE_NONE, // default + TEXTURE_TYPE_SPHERE, + TEXTURE_TYPE_CUBE_TOP, + TEXTURE_TYPE_CUBE_BOTTOM, + TEXTURE_TYPE_CUBE_FRONT, + TEXTURE_TYPE_CUBE_BACK, + TEXTURE_TYPE_CUBE_LEFT, + TEXTURE_TYPE_CUBE_RIGHT +} texture_type_t; + +typedef struct { + texture_type_t type; // -type (default TEXTURE_TYPE_NONE) + float sharpness; // -boost (default 1.0?) + float brightness; // base_value in -mm option (default 0) + float contrast; // gain_value in -mm option (default 1) + float origin_offset[3]; // -o u [v [w]] (default 0 0 0) + float scale[3]; // -s u [v [w]] (default 1 1 1) + float turbulence[3]; // -t u [v [w]] (default 0 0 0) + //int texture_resolution; // -texres resolution (default = ?) TODO + bool clamp; // -clamp (default false) + char imfchan; // -imfchan (the default for bump is 'l' and for decal is 'm') + bool blendu; // -blendu (default on) + bool blendv; // -blendv (default on) + float bump_multiplier; // -bm (for bump maps only, default 1.0) +} texture_option_t; + typedef struct { std::string name; @@ -67,6 +125,14 @@ typedef struct { std::string displacement_texname; // disp std::string alpha_texname; // map_d + texture_option_t ambient_texopt; + texture_option_t diffuse_texopt; + texture_option_t specular_texopt; + texture_option_t specular_highlight_texopt; + texture_option_t bump_texopt; + texture_option_t displacement_texopt; + texture_option_t alpha_texopt; + // PBR extension // http://exocortex.com/blog/extending_wavefront_mtl_to_support_pbr float roughness; // [0, 1] default 0 @@ -77,12 +143,21 @@ typedef struct { float anisotropy; // aniso. [0, 1] default 0 float anisotropy_rotation; // anisor. [0, 1] default 0 float pad0; + float pad1; std::string roughness_texname; // map_Pr std::string metallic_texname; // map_Pm std::string sheen_texname; // map_Ps std::string emissive_texname; // map_Ke std::string normal_texname; // norm. For normal mapping. + texture_option_t roughness_texopt; + texture_option_t metallic_texopt; + texture_option_t sheen_texopt; + texture_option_t emissive_texopt; + texture_option_t normal_texopt; + + int pad2; + std::map unknown_parameter; } material_t; @@ -511,6 +586,23 @@ static inline void parseV(float *x, float *y, float *z, float *w, (*w) = parseFloat(token, 1.0); } +static inline bool parseOnOff(const char **token, bool default_value = true) { + (*token) += strspn((*token), " \t"); + const char *end = (*token) + strcspn((*token), " \t\r"); + + printf("token = %s\n", (*token)); + bool ret = default_value; + if ((0 == strncmp((*token), "on", 2))) { + ret = true; + } else if ((0 == strncmp((*token), "off", 3))) { + printf("off\n"); + ret = false; + } + + (*token) = end; + return ret; +} + static tag_sizes parseTagTriple(const char **token) { tag_sizes ts; @@ -601,6 +693,105 @@ static vertex_index parseRawTriple(const char **token) { return vi; } +static bool ParseTextureNameAndOption(std::string* texname, texture_option_t *texopt, const char* linebuf, const bool is_bump) +{ + // @todo { write more robust lexer and parser. } + bool found_texname = false; + std::string texture_name; + + // Fill with default value for texopt. + if (is_bump) { + texopt->imfchan = 'l'; + } else { + texopt->imfchan = 'm'; + } + texopt->bump_multiplier = 1.0f; + texopt->clamp = false; + texopt->blendu = true; + texopt->blendv = true; + texopt->sharpness = 1.0f; + texopt->brightness = 0.0f; + texopt->contrast = 1.0f; + texopt->origin_offset[0] = 0.0f; + texopt->origin_offset[1] = 0.0f; + texopt->origin_offset[2] = 0.0f; + texopt->scale[0] = 1.0f; + texopt->scale[1] = 1.0f; + texopt->scale[2] = 1.0f; + texopt->turbulence[0] = 0.0f; + texopt->turbulence[1] = 0.0f; + texopt->turbulence[2] = 0.0f; + texopt->type = TEXTURE_TYPE_NONE; + + const char *token = linebuf; // Assume line ends with NULL + + // @todo { -mm, -type, -imfchan } + while (!IS_NEW_LINE((*token))) { + if ((0 == strncmp(token, "-blendu", 7)) && IS_SPACE((token[7]))) { + token += 8; + texopt->blendu = parseOnOff(&token, /* default */true); + printf("blendu = %d\n", texopt->blendu); + } else if ((0 == strncmp(token, "-blendv", 7)) && IS_SPACE((token[7]))) { + token += 8; + texopt->blendv = parseOnOff(&token, /* default */true); + printf("blendv = %d\n", texopt->blendv); + } else if ((0 == strncmp(token, "-clamp", 6)) && IS_SPACE((token[6]))) { + token += 7; + texopt->clamp = parseOnOff(&token, /* default */true); + printf("clamp= %d\n", texopt->clamp); + } else if ((0 == strncmp(token, "-boost", 6)) && IS_SPACE((token[6]))) { + token += 7; + texopt->sharpness = parseFloat(&token, 1.0); + printf("boost = %f\n", static_cast(texopt->sharpness)); + } else if ((0 == strncmp(token, "-bm", 3)) && IS_SPACE((token[3]))) { + token += 4; + texopt->bump_multiplier = parseFloat(&token, 1.0); + printf("bm %f\n", static_cast(texopt->bump_multiplier)); + } else if ((0 == strncmp(token, "-o", 2)) && IS_SPACE((token[2]))) { + token += 3; + parseFloat3(&(texopt->origin_offset[0]), &(texopt->origin_offset[1]), &(texopt->origin_offset[2]), &token); + printf("o %f, %f, %f\n", + static_cast(texopt->origin_offset[0]), + static_cast(texopt->origin_offset[1]), + static_cast(texopt->origin_offset[2])); + } else if ((0 == strncmp(token, "-s", 2)) && IS_SPACE((token[2]))) { + token += 3; + parseFloat3(&(texopt->scale[0]), &(texopt->scale[1]), &(texopt->scale[2]), &token); + printf("s %f, %f, %f\n", + static_cast(texopt->scale[0]), + static_cast(texopt->scale[1]), + static_cast(texopt->scale[2])); + } else if ((0 == strncmp(token, "-t", 2)) && IS_SPACE((token[2]))) { + token += 3; + parseFloat3(&(texopt->turbulence[0]), &(texopt->turbulence[1]), &(texopt->turbulence[2]), &token); + printf("t %f, %f, %f\n", + static_cast(texopt->turbulence[0]), + static_cast(texopt->turbulence[1]), + static_cast(texopt->turbulence[2])); + } else { + // Assume texture filename + token += strspn(token, " \t"); // skip space + size_t len = strcspn(token, " \t\r"); // untile next space + texture_name = std::string(token, token + len); + printf("texname = [%s]\n", texture_name.c_str()); + token += len; + + token += strspn(token, " \t"); // skip space + + found_texname = true; + } + } + + if (found_texname) { + (*texname) = texture_name; + return true; + } else { + return false; + } +} + + + static void InitMaterial(material_t *material) { material->name = ""; material->ambient_texname = ""; @@ -913,28 +1104,35 @@ void LoadMtl(std::map *material_map, // diffuse texture if ((0 == strncmp(token, "map_Kd", 6)) && IS_SPACE(token[6])) { token += 7; - material.diffuse_texname = token; + ParseTextureNameAndOption(&(material.diffuse_texname), &(material.diffuse_texopt), token, /* is_bump */false); continue; } // specular texture if ((0 == strncmp(token, "map_Ks", 6)) && IS_SPACE(token[6])) { token += 7; - material.specular_texname = token; + ParseTextureNameAndOption(&(material.specular_texname), &(material.specular_texopt), token, /* is_bump */false); continue; } // specular highlight texture if ((0 == strncmp(token, "map_Ns", 6)) && IS_SPACE(token[6])) { token += 7; - material.specular_highlight_texname = token; + ParseTextureNameAndOption(&(material.specular_highlight_texname), &(material.specular_highlight_texopt), token, /* is_bump */false); continue; } // bump texture if ((0 == strncmp(token, "map_bump", 8)) && IS_SPACE(token[8])) { token += 9; - material.bump_texname = token; + ParseTextureNameAndOption(&(material.bump_texname), &(material.bump_texopt), token, /* is_bump */true); + continue; + } + + // bump texture + if ((0 == strncmp(token, "bump", 4)) && IS_SPACE(token[4])) { + token += 5; + ParseTextureNameAndOption(&(material.bump_texname), &(material.bump_texopt), token, /* is_bump */true); continue; } @@ -942,55 +1140,49 @@ void LoadMtl(std::map *material_map, if ((0 == strncmp(token, "map_d", 5)) && IS_SPACE(token[5])) { token += 6; material.alpha_texname = token; - continue; - } - - // bump texture - if ((0 == strncmp(token, "bump", 4)) && IS_SPACE(token[4])) { - token += 5; - material.bump_texname = token; + ParseTextureNameAndOption(&(material.alpha_texname), &(material.alpha_texopt), token, /* is_bump */false); continue; } // displacement texture if ((0 == strncmp(token, "disp", 4)) && IS_SPACE(token[4])) { token += 5; - material.displacement_texname = token; + ParseTextureNameAndOption(&(material.displacement_texname), &(material.displacement_texopt), token, /* is_bump */false); continue; } // PBR: roughness texture if ((0 == strncmp(token, "map_Pr", 6)) && IS_SPACE(token[6])) { token += 7; - material.roughness_texname = token; + ParseTextureNameAndOption(&(material.roughness_texname), &(material.roughness_texopt), token, /* is_bump */false); continue; } // PBR: metallic texture if ((0 == strncmp(token, "map_Pm", 6)) && IS_SPACE(token[6])) { token += 7; - material.metallic_texname = token; + ParseTextureNameAndOption(&(material.metallic_texname), &(material.metallic_texopt), token, /* is_bump */false); continue; } // PBR: sheen texture if ((0 == strncmp(token, "map_Ps", 6)) && IS_SPACE(token[6])) { token += 7; - material.sheen_texname = token; + ParseTextureNameAndOption(&(material.sheen_texname), &(material.sheen_texopt), token, /* is_bump */false); continue; } // PBR: emissive texture if ((0 == strncmp(token, "map_Ke", 6)) && IS_SPACE(token[6])) { token += 7; - material.emissive_texname = token; + ParseTextureNameAndOption(&(material.emissive_texname), &(material.emissive_texopt), token, /* is_bump */false); continue; } // PBR: normal map texture if ((0 == strncmp(token, "norm", 4)) && IS_SPACE(token[4])) { token += 5; - material.normal_texname = token; + ParseTextureNameAndOption(&(material.normal_texname), &(material.normal_texopt), token, /* is_bump */false); // @fixme { is_bump will be true? } continue; } -- cgit v1.2.3 From 0948ca0417455dd82891547699e97ba142021a7b Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Thu, 3 Nov 2016 02:58:44 +0900 Subject: Add more support for parsing texture options. Add more unit testing for texture options. --- models/texture-options-issue-85.mtl | 22 ++- tests/tester.cc | 42 +++++ tiny_obj_loader.h | 317 ++++++++++++++++++++++-------------- 3 files changed, 253 insertions(+), 128 deletions(-) diff --git a/models/texture-options-issue-85.mtl b/models/texture-options-issue-85.mtl index 49879cd..d4d62ad 100644 --- a/models/texture-options-issue-85.mtl +++ b/models/texture-options-issue-85.mtl @@ -3,9 +3,10 @@ Ka 0 0 0 Kd 0 0 0 Ks 0 0 0 Kt 0.1 0.2 0.3 +map_Ka -clamp on ambient.jpg map_Kd -o 0.1 diffuse.jpg -map_Ks -o 0.1 0.2 specular.jpg -map_Ns -o 0.1 0.2 0.3 specular_highlight.jpg +map_Ks -s 0.1 0.2 specular.jpg +map_Ns -t 0.1 0.2 0.3 specular_highlight.jpg map_bump -bm 3 bumpmap.jpg newmtl bm2 @@ -16,5 +17,20 @@ Kt 0.1 0.2 0.3 # blendu map_Kd -blendu on diffuse.jpg map_Ks -blendv off specular.jpg +map_Ns -mm 0.1 0.3 specular_highlight.jpg # -bm after filename -map_bump bumpmap2.jpg -bm 1.5 +map_bump -imfchan r bumpmap2.jpg -bm 1.5 + +newmtl bm3 +Ka 0 0 0 +Kd 0 0 0 +Ks 0 0 0 +Kt 0.1 0.2 0.3 +# type +map_Kd -type sphere diffuse.jpg +map_Ks -type cube_top specular.jpg +map_Ns -type cube_bottom specular_highlight.jpg +map_Ka -type cube_left ambient.jpg +map_d -type cube_right alpha.jpg +map_bump -type cube_front bump.jpg +disp -type cube_back displacement.jpg diff --git a/tests/tester.cc b/tests/tester.cc index 53955a8..b5ac70f 100644 --- a/tests/tester.cc +++ b/tests/tester.cc @@ -488,6 +488,48 @@ TEST_CASE("usemtl_at_last_line", "[Issue104]") { REQUIRE(1 == shapes.size()); } +TEST_CASE("texture_opts", "[Issue85]") { + tinyobj::attrib_t attrib; + std::vector shapes; + std::vector materials; + + std::string err; + bool ret = tinyobj::LoadObj(&attrib, &shapes, &materials, &err, "../models/texture-options-issue-85.obj", gMtlBasePath); + + if (!err.empty()) { + std::cerr << err << std::endl; + } + REQUIRE(true == ret); + REQUIRE(1 == shapes.size()); + REQUIRE(3 == materials.size()); + REQUIRE(0 == materials[0].name.compare("default")); + REQUIRE(0 == materials[1].name.compare("bm2")); + REQUIRE(0 == materials[2].name.compare("bm3")); + REQUIRE(true == materials[0].ambient_texopt.clamp); + REQUIRE(0.1 == Approx(materials[0].diffuse_texopt.origin_offset[0])); + REQUIRE(0.0 == Approx(materials[0].diffuse_texopt.origin_offset[1])); + REQUIRE(0.0 == Approx(materials[0].diffuse_texopt.origin_offset[2])); + REQUIRE(0.1 == Approx(materials[0].specular_texopt.scale[0])); + REQUIRE(0.2 == Approx(materials[0].specular_texopt.scale[1])); + REQUIRE(1.0 == Approx(materials[0].specular_texopt.scale[2])); + REQUIRE(0.1 == Approx(materials[0].specular_highlight_texopt.turbulence[0])); + REQUIRE(0.2 == Approx(materials[0].specular_highlight_texopt.turbulence[1])); + REQUIRE(0.3 == Approx(materials[0].specular_highlight_texopt.turbulence[2])); + REQUIRE(3.0 == Approx(materials[0].bump_texopt.bump_multiplier)); + + REQUIRE(0.1 == Approx(materials[1].specular_highlight_texopt.brightness)); + REQUIRE(0.3 == Approx(materials[1].specular_highlight_texopt.contrast)); + REQUIRE('r' == materials[1].bump_texopt.imfchan); + + REQUIRE(tinyobj::TEXTURE_TYPE_SPHERE == materials[2].diffuse_texopt.type); + REQUIRE(tinyobj::TEXTURE_TYPE_CUBE_TOP == materials[2].specular_texopt.type); + REQUIRE(tinyobj::TEXTURE_TYPE_CUBE_BOTTOM == materials[2].specular_highlight_texopt.type); + REQUIRE(tinyobj::TEXTURE_TYPE_CUBE_LEFT == materials[2].ambient_texopt.type); + REQUIRE(tinyobj::TEXTURE_TYPE_CUBE_RIGHT == materials[2].alpha_texopt.type); + REQUIRE(tinyobj::TEXTURE_TYPE_CUBE_FRONT == materials[2].bump_texopt.type); + REQUIRE(tinyobj::TEXTURE_TYPE_CUBE_BACK == materials[2].displacement_texopt.type); +} + #if 0 int main( diff --git a/tiny_obj_loader.h b/tiny_obj_loader.h index 794baa4..198b196 100644 --- a/tiny_obj_loader.h +++ b/tiny_obj_loader.h @@ -23,7 +23,9 @@ THE SOFTWARE. */ // -// version 1.0.2 : Improve parsing speed by about a factor of 2 for large files(#105) +// version 1.0.3 : Support parsing texture options(#85) +// version 1.0.2 : Improve parsing speed by about a factor of 2 for large +// files(#105) // version 1.0.1 : Fixes a shape is lost if obj ends with a 'usemtl'(#104) // version 1.0.0 : Change data structure. Change license from BSD to MIT. // @@ -44,38 +46,56 @@ THE SOFTWARE. namespace tinyobj { // https://en.wikipedia.org/wiki/Wavefront_.obj_file says ... -// -// -blendu on | off # set horizontal texture blending (default on) -// -blendv on | off # set vertical texture blending (default on) +// +// -blendu on | off # set horizontal texture blending +// (default on) +// -blendv on | off # set vertical texture blending +// (default on) // -boost float_value # boost mip-map sharpness -// -mm base_value gain_value # modify texture map values (default 0 1) -// # base_value = brightness, gain_value = contrast -// -o u [v [w]] # Origin offset (default 0 0 0) -// -s u [v [w]] # Scale (default 1 1 1) -// -t u [v [w]] # Turbulence (default 0 0 0) +// -mm base_value gain_value # modify texture map values (default +// 0 1) +// # base_value = brightness, +// gain_value = contrast +// -o u [v [w]] # Origin offset (default +// 0 0 0) +// -s u [v [w]] # Scale (default +// 1 1 1) +// -t u [v [w]] # Turbulence (default +// 0 0 0) // -texres resolution # texture resolution to create -// -clamp on | off # only render texels in the clamped 0-1 range (default off) -// # When unclamped, textures are repeated across a surface, -// # when clamped, only texels which fall within the 0-1 +// -clamp on | off # only render texels in the clamped +// 0-1 range (default off) +// # When unclamped, textures are +// repeated across a surface, +// # when clamped, only texels which +// fall within the 0-1 // # range are rendered. -// -bm mult_value # bump multiplier (for bump maps only) -// -// -imfchan r | g | b | m | l | z # specifies which channel of the file is used to -// # create a scalar or bump texture. r:red, g:green, -// # b:blue, m:matte, l:luminance, z:z-depth.. -// # (the default for bump is 'l' and for decal is 'm') -// bump -imfchan r bumpmap.tga # says to use the red channel of bumpmap.tga as the bumpmap +// -bm mult_value # bump multiplier (for bump maps +// only) +// +// -imfchan r | g | b | m | l | z # specifies which channel of the file +// is used to +// # create a scalar or bump texture. +// r:red, g:green, +// # b:blue, m:matte, l:luminance, +// z:z-depth.. +// # (the default for bump is 'l' and +// for decal is 'm') +// bump -imfchan r bumpmap.tga # says to use the red channel of +// bumpmap.tga as the bumpmap // // For reflection maps... // -// -type sphere # specifies a sphere for a "refl" reflection map -// -type cube_top | cube_bottom | # when using a cube map, the texture file for each -// cube_front | cube_back | # side of the cube is specified separately +// -type sphere # specifies a sphere for a "refl" +// reflection map +// -type cube_top | cube_bottom | # when using a cube map, the texture +// file for each +// cube_front | cube_back | # side of the cube is specified +// separately // cube_left | cube_right - typedef enum { - TEXTURE_TYPE_NONE, // default + TEXTURE_TYPE_NONE, // default TEXTURE_TYPE_SPHERE, TEXTURE_TYPE_CUBE_TOP, TEXTURE_TYPE_CUBE_BOTTOM, @@ -84,21 +104,21 @@ typedef enum { TEXTURE_TYPE_CUBE_LEFT, TEXTURE_TYPE_CUBE_RIGHT } texture_type_t; - + typedef struct { - texture_type_t type; // -type (default TEXTURE_TYPE_NONE) - float sharpness; // -boost (default 1.0?) - float brightness; // base_value in -mm option (default 0) - float contrast; // gain_value in -mm option (default 1) - float origin_offset[3]; // -o u [v [w]] (default 0 0 0) - float scale[3]; // -s u [v [w]] (default 1 1 1) - float turbulence[3]; // -t u [v [w]] (default 0 0 0) - //int texture_resolution; // -texres resolution (default = ?) TODO - bool clamp; // -clamp (default false) - char imfchan; // -imfchan (the default for bump is 'l' and for decal is 'm') - bool blendu; // -blendu (default on) - bool blendv; // -blendv (default on) - float bump_multiplier; // -bm (for bump maps only, default 1.0) + texture_type_t type; // -type (default TEXTURE_TYPE_NONE) + float sharpness; // -boost (default 1.0?) + float brightness; // base_value in -mm option (default 0) + float contrast; // gain_value in -mm option (default 1) + float origin_offset[3]; // -o u [v [w]] (default 0 0 0) + float scale[3]; // -s u [v [w]] (default 1 1 1) + float turbulence[3]; // -t u [v [w]] (default 0 0 0) + // int texture_resolution; // -texres resolution (default = ?) TODO + bool clamp; // -clamp (default false) + char imfchan; // -imfchan (the default for bump is 'l' and for decal is 'm') + bool blendu; // -blendu (default on) + bool blendv; // -blendv (default on) + float bump_multiplier; // -bm (for bump maps only, default 1.0) } texture_option_t; typedef struct { @@ -135,13 +155,13 @@ typedef struct { // PBR extension // http://exocortex.com/blog/extending_wavefront_mtl_to_support_pbr - float roughness; // [0, 1] default 0 - float metallic; // [0, 1] default 0 - float sheen; // [0, 1] default 0 - float clearcoat_thickness; // [0, 1] default 0 - float clearcoat_roughness; // [0, 1] default 0 - float anisotropy; // aniso. [0, 1] default 0 - float anisotropy_rotation; // anisor. [0, 1] default 0 + float roughness; // [0, 1] default 0 + float metallic; // [0, 1] default 0 + float sheen; // [0, 1] default 0 + float clearcoat_thickness; // [0, 1] default 0 + float clearcoat_roughness; // [0, 1] default 0 + float anisotropy; // aniso. [0, 1] default 0 + float anisotropy_rotation; // anisor. [0, 1] default 0 float pad0; float pad1; std::string roughness_texname; // map_Pr @@ -496,20 +516,13 @@ static bool tryParseDouble(const char *s, const char *s_end, double *result) { end_not_reached = (curr != s_end); while (end_not_reached && IS_DIGIT(*curr)) { static const double pow_lut[] = { - 1.0, - 0.1, - 0.01, - 0.001, - 0.0001, - 0.00001, - 0.000001, - 0.0000001, + 1.0, 0.1, 0.01, 0.001, 0.0001, 0.00001, 0.000001, 0.0000001, }; const int lut_entries = sizeof pow_lut / sizeof pow_lut[0]; // NOTE: Don't use powf here, it will absolutely murder precision. mantissa += static_cast(*curr - 0x30) * - (read < lut_entries ? pow_lut[read] : pow(10.0, -read)); + (read < lut_entries ? pow_lut[read] : pow(10.0, -read)); read++; curr++; end_not_reached = (curr != s_end); @@ -549,8 +562,9 @@ static bool tryParseDouble(const char *s, const char *s_end, double *result) { } assemble: - *result = (sign == '+' ? 1 : -1) * - (exponent ? ldexp(mantissa * pow(5.0, exponent), exponent) : mantissa); + *result = + (sign == '+' ? 1 : -1) * + (exponent ? ldexp(mantissa * pow(5.0, exponent), exponent) : mantissa); return true; fail: return false; @@ -566,43 +580,74 @@ static inline float parseFloat(const char **token, double default_value = 0.0) { return f; } -static inline void parseFloat2(float *x, float *y, const char **token) { - (*x) = parseFloat(token); - (*y) = parseFloat(token); +static inline void parseFloat2(float *x, float *y, const char **token, + const double default_x = 0.0, + const double default_y = 0.0) { + (*x) = parseFloat(token, default_x); + (*y) = parseFloat(token, default_y); } -static inline void parseFloat3(float *x, float *y, float *z, - const char **token) { - (*x) = parseFloat(token); - (*y) = parseFloat(token); - (*z) = parseFloat(token); +static inline void parseFloat3(float *x, float *y, float *z, const char **token, + const double default_x = 0.0, + const double default_y = 0.0, + const double default_z = 0.0) { + (*x) = parseFloat(token, default_x); + (*y) = parseFloat(token, default_y); + (*z) = parseFloat(token, default_z); } static inline void parseV(float *x, float *y, float *z, float *w, - const char **token) { - (*x) = parseFloat(token); - (*y) = parseFloat(token); - (*z) = parseFloat(token); - (*w) = parseFloat(token, 1.0); + const char **token, const double default_x = 0.0, + const double default_y = 0.0, + const double default_z = 0.0, + const double default_w = 1.0) { + (*x) = parseFloat(token, default_x); + (*y) = parseFloat(token, default_y); + (*z) = parseFloat(token, default_z); + (*w) = parseFloat(token, default_w); } static inline bool parseOnOff(const char **token, bool default_value = true) { (*token) += strspn((*token), " \t"); const char *end = (*token) + strcspn((*token), " \t\r"); - printf("token = %s\n", (*token)); bool ret = default_value; if ((0 == strncmp((*token), "on", 2))) { ret = true; } else if ((0 == strncmp((*token), "off", 3))) { - printf("off\n"); ret = false; } - + (*token) = end; return ret; } +static inline texture_type_t parseTextureType( + const char **token, texture_type_t default_value = TEXTURE_TYPE_NONE) { + (*token) += strspn((*token), " \t"); + const char *end = (*token) + strcspn((*token), " \t\r"); + texture_type_t ty = default_value; + + if ((0 == strncmp((*token), "cube_top", strlen("cube_top")))) { + ty = TEXTURE_TYPE_CUBE_TOP; + } else if ((0 == strncmp((*token), "cube_bottom", strlen("cube_bottom")))) { + ty = TEXTURE_TYPE_CUBE_BOTTOM; + } else if ((0 == strncmp((*token), "cube_left", strlen("cube_left")))) { + ty = TEXTURE_TYPE_CUBE_LEFT; + } else if ((0 == strncmp((*token), "cube_right", strlen("cube_right")))) { + ty = TEXTURE_TYPE_CUBE_RIGHT; + } else if ((0 == strncmp((*token), "cube_front", strlen("cube_front")))) { + ty = TEXTURE_TYPE_CUBE_FRONT; + } else if ((0 == strncmp((*token), "cube_back", strlen("cube_back")))) { + ty = TEXTURE_TYPE_CUBE_BACK; + } else if ((0 == strncmp((*token), "sphere", strlen("sphere")))) { + ty = TEXTURE_TYPE_SPHERE; + } + + (*token) = end; + return ty; +} + static tag_sizes parseTagTriple(const char **token) { tag_sizes ts; @@ -693,8 +738,9 @@ static vertex_index parseRawTriple(const char **token) { return vi; } -static bool ParseTextureNameAndOption(std::string* texname, texture_option_t *texopt, const char* linebuf, const bool is_bump) -{ +static bool ParseTextureNameAndOption(std::string *texname, + texture_option_t *texopt, + const char *linebuf, const bool is_bump) { // @todo { write more robust lexer and parser. } bool found_texname = false; std::string texture_name; @@ -723,60 +769,58 @@ static bool ParseTextureNameAndOption(std::string* texname, texture_option_t *te texopt->turbulence[2] = 0.0f; texopt->type = TEXTURE_TYPE_NONE; - const char *token = linebuf; // Assume line ends with NULL - - // @todo { -mm, -type, -imfchan } + const char *token = linebuf; // Assume line ends with NULL + while (!IS_NEW_LINE((*token))) { if ((0 == strncmp(token, "-blendu", 7)) && IS_SPACE((token[7]))) { token += 8; - texopt->blendu = parseOnOff(&token, /* default */true); - printf("blendu = %d\n", texopt->blendu); + texopt->blendu = parseOnOff(&token, /* default */ true); } else if ((0 == strncmp(token, "-blendv", 7)) && IS_SPACE((token[7]))) { token += 8; - texopt->blendv = parseOnOff(&token, /* default */true); - printf("blendv = %d\n", texopt->blendv); + texopt->blendv = parseOnOff(&token, /* default */ true); } else if ((0 == strncmp(token, "-clamp", 6)) && IS_SPACE((token[6]))) { token += 7; - texopt->clamp = parseOnOff(&token, /* default */true); - printf("clamp= %d\n", texopt->clamp); + texopt->clamp = parseOnOff(&token, /* default */ true); } else if ((0 == strncmp(token, "-boost", 6)) && IS_SPACE((token[6]))) { token += 7; texopt->sharpness = parseFloat(&token, 1.0); - printf("boost = %f\n", static_cast(texopt->sharpness)); } else if ((0 == strncmp(token, "-bm", 3)) && IS_SPACE((token[3]))) { - token += 4; + token += 4; texopt->bump_multiplier = parseFloat(&token, 1.0); - printf("bm %f\n", static_cast(texopt->bump_multiplier)); } else if ((0 == strncmp(token, "-o", 2)) && IS_SPACE((token[2]))) { - token += 3; - parseFloat3(&(texopt->origin_offset[0]), &(texopt->origin_offset[1]), &(texopt->origin_offset[2]), &token); - printf("o %f, %f, %f\n", - static_cast(texopt->origin_offset[0]), - static_cast(texopt->origin_offset[1]), - static_cast(texopt->origin_offset[2])); + token += 3; + parseFloat3(&(texopt->origin_offset[0]), &(texopt->origin_offset[1]), + &(texopt->origin_offset[2]), &token); } else if ((0 == strncmp(token, "-s", 2)) && IS_SPACE((token[2]))) { - token += 3; - parseFloat3(&(texopt->scale[0]), &(texopt->scale[1]), &(texopt->scale[2]), &token); - printf("s %f, %f, %f\n", - static_cast(texopt->scale[0]), - static_cast(texopt->scale[1]), - static_cast(texopt->scale[2])); + token += 3; + parseFloat3(&(texopt->scale[0]), &(texopt->scale[1]), &(texopt->scale[2]), + &token, 1.0, 1.0, 1.0); } else if ((0 == strncmp(token, "-t", 2)) && IS_SPACE((token[2]))) { - token += 3; - parseFloat3(&(texopt->turbulence[0]), &(texopt->turbulence[1]), &(texopt->turbulence[2]), &token); - printf("t %f, %f, %f\n", - static_cast(texopt->turbulence[0]), - static_cast(texopt->turbulence[1]), - static_cast(texopt->turbulence[2])); + token += 3; + parseFloat3(&(texopt->turbulence[0]), &(texopt->turbulence[1]), + &(texopt->turbulence[2]), &token); + } else if ((0 == strncmp(token, "-type", 5)) && IS_SPACE((token[5]))) { + token += 5; + texopt->type = parseTextureType((&token), TEXTURE_TYPE_NONE); + } else if ((0 == strncmp(token, "-imfchan", 8)) && IS_SPACE((token[8]))) { + token += 9; + token += strspn(token, " \t"); + const char *end = token + strcspn(token, " \t\r"); + if ((end - token) == 1) { // Assume one char for -imfchan + texopt->imfchan = (*token); + } + token = end; + } else if ((0 == strncmp(token, "-mm", 3)) && IS_SPACE((token[3]))) { + token += 4; + parseFloat2(&(texopt->brightness), &(texopt->contrast), &token, 0.0, 1.0); } else { // Assume texture filename - token += strspn(token, " \t"); // skip space - size_t len = strcspn(token, " \t\r"); // untile next space + token += strspn(token, " \t"); // skip space + size_t len = strcspn(token, " \t\r"); // untile next space texture_name = std::string(token, token + len); - printf("texname = [%s]\n", texture_name.c_str()); token += len; - token += strspn(token, " \t"); // skip space + token += strspn(token, " \t"); // skip space found_texname = true; } @@ -790,8 +834,6 @@ static bool ParseTextureNameAndOption(std::string* texname, texture_option_t *te } } - - static void InitMaterial(material_t *material) { material->name = ""; material->ambient_texname = ""; @@ -1097,42 +1139,54 @@ void LoadMtl(std::map *material_map, // ambient texture if ((0 == strncmp(token, "map_Ka", 6)) && IS_SPACE(token[6])) { token += 7; - material.ambient_texname = token; + ParseTextureNameAndOption(&(material.ambient_texname), + &(material.ambient_texopt), token, + /* is_bump */ false); continue; } // diffuse texture if ((0 == strncmp(token, "map_Kd", 6)) && IS_SPACE(token[6])) { token += 7; - ParseTextureNameAndOption(&(material.diffuse_texname), &(material.diffuse_texopt), token, /* is_bump */false); + ParseTextureNameAndOption(&(material.diffuse_texname), + &(material.diffuse_texopt), token, + /* is_bump */ false); continue; } // specular texture if ((0 == strncmp(token, "map_Ks", 6)) && IS_SPACE(token[6])) { token += 7; - ParseTextureNameAndOption(&(material.specular_texname), &(material.specular_texopt), token, /* is_bump */false); + ParseTextureNameAndOption(&(material.specular_texname), + &(material.specular_texopt), token, + /* is_bump */ false); continue; } // specular highlight texture if ((0 == strncmp(token, "map_Ns", 6)) && IS_SPACE(token[6])) { token += 7; - ParseTextureNameAndOption(&(material.specular_highlight_texname), &(material.specular_highlight_texopt), token, /* is_bump */false); + ParseTextureNameAndOption(&(material.specular_highlight_texname), + &(material.specular_highlight_texopt), token, + /* is_bump */ false); continue; } // bump texture if ((0 == strncmp(token, "map_bump", 8)) && IS_SPACE(token[8])) { token += 9; - ParseTextureNameAndOption(&(material.bump_texname), &(material.bump_texopt), token, /* is_bump */true); + ParseTextureNameAndOption(&(material.bump_texname), + &(material.bump_texopt), token, + /* is_bump */ true); continue; } // bump texture if ((0 == strncmp(token, "bump", 4)) && IS_SPACE(token[4])) { token += 5; - ParseTextureNameAndOption(&(material.bump_texname), &(material.bump_texopt), token, /* is_bump */true); + ParseTextureNameAndOption(&(material.bump_texname), + &(material.bump_texopt), token, + /* is_bump */ true); continue; } @@ -1140,49 +1194,63 @@ void LoadMtl(std::map *material_map, if ((0 == strncmp(token, "map_d", 5)) && IS_SPACE(token[5])) { token += 6; material.alpha_texname = token; - ParseTextureNameAndOption(&(material.alpha_texname), &(material.alpha_texopt), token, /* is_bump */false); + ParseTextureNameAndOption(&(material.alpha_texname), + &(material.alpha_texopt), token, + /* is_bump */ false); continue; } // displacement texture if ((0 == strncmp(token, "disp", 4)) && IS_SPACE(token[4])) { token += 5; - ParseTextureNameAndOption(&(material.displacement_texname), &(material.displacement_texopt), token, /* is_bump */false); + ParseTextureNameAndOption(&(material.displacement_texname), + &(material.displacement_texopt), token, + /* is_bump */ false); continue; } // PBR: roughness texture if ((0 == strncmp(token, "map_Pr", 6)) && IS_SPACE(token[6])) { token += 7; - ParseTextureNameAndOption(&(material.roughness_texname), &(material.roughness_texopt), token, /* is_bump */false); + ParseTextureNameAndOption(&(material.roughness_texname), + &(material.roughness_texopt), token, + /* is_bump */ false); continue; } // PBR: metallic texture if ((0 == strncmp(token, "map_Pm", 6)) && IS_SPACE(token[6])) { token += 7; - ParseTextureNameAndOption(&(material.metallic_texname), &(material.metallic_texopt), token, /* is_bump */false); + ParseTextureNameAndOption(&(material.metallic_texname), + &(material.metallic_texopt), token, + /* is_bump */ false); continue; } // PBR: sheen texture if ((0 == strncmp(token, "map_Ps", 6)) && IS_SPACE(token[6])) { token += 7; - ParseTextureNameAndOption(&(material.sheen_texname), &(material.sheen_texopt), token, /* is_bump */false); + ParseTextureNameAndOption(&(material.sheen_texname), + &(material.sheen_texopt), token, + /* is_bump */ false); continue; } // PBR: emissive texture if ((0 == strncmp(token, "map_Ke", 6)) && IS_SPACE(token[6])) { token += 7; - ParseTextureNameAndOption(&(material.emissive_texname), &(material.emissive_texopt), token, /* is_bump */false); + ParseTextureNameAndOption(&(material.emissive_texname), + &(material.emissive_texopt), token, + /* is_bump */ false); continue; } // PBR: normal map texture if ((0 == strncmp(token, "norm", 4)) && IS_SPACE(token[4])) { token += 5; - ParseTextureNameAndOption(&(material.normal_texname), &(material.normal_texopt), token, /* is_bump */false); // @fixme { is_bump will be true? } + ParseTextureNameAndOption( + &(material.normal_texname), &(material.normal_texopt), token, + /* is_bump */ false); // @fixme { is_bump will be true? } continue; } @@ -1279,8 +1347,7 @@ bool LoadObj(attrib_t *attrib, std::vector *shapes, bool LoadObj(attrib_t *attrib, std::vector *shapes, std::vector *materials, std::string *err, - std::istream *inStream, - MaterialReader *readMatFn /*= NULL*/, + std::istream *inStream, MaterialReader *readMatFn /*= NULL*/, bool triangulate) { std::stringstream errss; -- cgit v1.2.3 From 7d5699118e7e5543546133289ce925fa8e2babd7 Mon Sep 17 00:00:00 2001 From: Nikita Krupitskas Date: Thu, 3 Nov 2016 11:23:21 +0300 Subject: Little intuitive improvements --- tiny_obj_loader.h | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/tiny_obj_loader.h b/tiny_obj_loader.h index 198b196..9ac0977 100644 --- a/tiny_obj_loader.h +++ b/tiny_obj_loader.h @@ -266,15 +266,15 @@ class MaterialReader { class MaterialFileReader : public MaterialReader { public: - explicit MaterialFileReader(const std::string &mtl_basepath) - : m_mtlBasePath(mtl_basepath) {} + explicit MaterialFileReader(const std::string &mtl_basedir) + : m_mtlBaseDir(mtl_basedir) {} virtual ~MaterialFileReader() {} virtual bool operator()(const std::string &matId, std::vector *materials, std::map *matMap, std::string *err); private: - std::string m_mtlBasePath; + std::string m_mtlBaseDir; }; class MaterialStreamReader : public MaterialReader { @@ -295,12 +295,12 @@ class MaterialStreamReader : public MaterialReader { /// 'shapes' will be filled with parsed shape data /// 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. +/// 'mtl_basedir' is optional, and used for base directory for .mtl file. /// 'triangulate' is optional, and used whether triangulate polygon face in .obj /// or not. bool LoadObj(attrib_t *attrib, std::vector *shapes, std::vector *materials, std::string *err, - const char *filename, const char *mtl_basepath = NULL, + const char *filename, const char *mtl_basedir = NULL, bool triangulate = true); /// Loads .obj from a file with custom user callback. @@ -1279,8 +1279,8 @@ bool MaterialFileReader::operator()(const std::string &matId, std::string *err) { std::string filepath; - if (!m_mtlBasePath.empty()) { - filepath = std::string(m_mtlBasePath) + matId; + if (!m_mtlBaseDir.empty()) { + filepath = std::string(m_mtlBaseDir) + matId; } else { filepath = matId; } @@ -1317,7 +1317,7 @@ bool MaterialStreamReader::operator()(const std::string &matId, bool LoadObj(attrib_t *attrib, std::vector *shapes, std::vector *materials, std::string *err, - const char *filename, const char *mtl_basepath, + const char *filename, const char *mtl_basedir, bool trianglulate) { attrib->vertices.clear(); attrib->normals.clear(); @@ -1335,11 +1335,11 @@ bool LoadObj(attrib_t *attrib, std::vector *shapes, return false; } - std::string basePath; - if (mtl_basepath) { - basePath = mtl_basepath; + std::string baseDir; + if (mtl_basedir) { + baseDir = mtl_basedir; } - MaterialFileReader matFileReader(basePath); + MaterialFileReader matFileReader(baseDir); return LoadObj(attrib, shapes, materials, err, &ifs, &matFileReader, trianglulate); -- cgit v1.2.3 From 9868630d0e15d967f2134fbf7f97a71d378de656 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Fri, 4 Nov 2016 12:36:15 +0900 Subject: Fix windows build of loader_example. --- loader_example.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/loader_example.cc b/loader_example.cc index 6a12a21..c4e2b0b 100644 --- a/loader_example.cc +++ b/loader_example.cc @@ -15,8 +15,8 @@ #ifdef __cplusplus extern "C" { #endif -#include #include +#include #ifdef __cplusplus } #endif -- cgit v1.2.3 From aa4dabe64f76640469926465562bd38da3620d5e Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Wed, 23 Nov 2016 17:17:35 +0900 Subject: Describe default behavior of `mtl_basedir`. --- tiny_obj_loader.h | 1 + 1 file changed, 1 insertion(+) diff --git a/tiny_obj_loader.h b/tiny_obj_loader.h index 9ac0977..b975601 100644 --- a/tiny_obj_loader.h +++ b/tiny_obj_loader.h @@ -296,6 +296,7 @@ class MaterialStreamReader : public MaterialReader { /// Returns true when loading .obj become success. /// Returns warning and error message into `err` /// 'mtl_basedir' is optional, and used for base directory for .mtl file. +/// In default(`NULL'), .mtl file is searched from an application's working directory. /// 'triangulate' is optional, and used whether triangulate polygon face in .obj /// or not. bool LoadObj(attrib_t *attrib, std::vector *shapes, -- cgit v1.2.3 From 41dd7c806ebfd2e6da0a850c8aaa8a9e00e82f33 Mon Sep 17 00:00:00 2001 From: Jonathan L Long Date: Mon, 5 Dec 2016 15:12:28 -0800 Subject: (Re-)expose attribs to Python. --- python/main.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/python/main.cpp b/python/main.cpp index 6c794fa..1b5af7f 100644 --- a/python/main.cpp +++ b/python/main.cpp @@ -172,6 +172,7 @@ static PyObject* pyLoadObj(PyObject* self, PyObject* args) { PyDict_SetItemString(rtndict, "shapes", pyshapes); PyDict_SetItemString(rtndict, "materials", pymaterials); + PyDict_SetItemString(rtndict, "attribs", attribobj); return rtndict; } -- cgit v1.2.3 From c207ff3561ba10ea1d9f46f82cccbb9e91a50bdb Mon Sep 17 00:00:00 2001 From: Jonathan L Long Date: Mon, 5 Dec 2016 15:22:32 -0800 Subject: Support Python 2.7. --- python/TODO.md | 1 - python/main.cpp | 13 ++++++++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/python/TODO.md b/python/TODO.md index d7aff07..621e79f 100644 --- a/python/TODO.md +++ b/python/TODO.md @@ -1,3 +1,2 @@ * PBR material * Define index_t struct -* Python 2.7 binding diff --git a/python/main.cpp b/python/main.cpp index 6c794fa..aea5804 100644 --- a/python/main.cpp +++ b/python/main.cpp @@ -1,4 +1,4 @@ -// python3 module for tinyobjloader +// python2/3 module for tinyobjloader // // usage: // import tinyobjloader as tol @@ -182,10 +182,21 @@ static PyMethodDef mMethods[] = { }; +#if PY_MAJOR_VERSION >= 3 + static struct PyModuleDef moduledef = {PyModuleDef_HEAD_INIT, "tinyobjloader", NULL, -1, mMethods}; PyMODINIT_FUNC PyInit_tinyobjloader(void) { return PyModule_Create(&moduledef); } + +#else + +PyMODINIT_FUNC inittinyobjloader(void) { + Py_InitModule3("tinyobjloader", mMethods, NULL); +} + +#endif // PY_MAJOR_VERSION >= 3 + } -- cgit v1.2.3 From a6a134a60e7f0b0a08c2f6d16afde05429b2d554 Mon Sep 17 00:00:00 2001 From: Reinier Maas Date: Tue, 6 Dec 2016 15:38:26 +0100 Subject: Update README.md Basedline -> baseline --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 00ada0e..545b0f8 100644 --- a/README.md +++ b/README.md @@ -171,7 +171,7 @@ Here is some benchmark result. Time are measured on MacBook 12(Early 2016, Core * Rungholt scene(6M triangles) * old version(v0.9.x): 15500 msecs. * baseline(v1.0.x): 6800 msecs(2.3x faster than old version) - * optimised: 1500 msecs(10x faster than old version, 4.5x faster than basedline) + * optimised: 1500 msecs(10x faster than old version, 4.5x faster than baseline) ## Tests -- cgit v1.2.3 From c2ff3f12fceafd99eda191091eadcac0a30132a1 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Tue, 6 Dec 2016 23:38:21 +0900 Subject: Support multiple filenames for mtllib line. Fixes #112. --- models/mtllib-multiple-files-issue-112.mtl | 6 ++ models/mtllib-multiple-files-issue-112.obj | 7 ++ tests/tester.cc | 15 ++++ tiny_obj_loader.h | 120 ++++++++++++++++++++--------- 4 files changed, 111 insertions(+), 37 deletions(-) create mode 100644 models/mtllib-multiple-files-issue-112.mtl create mode 100644 models/mtllib-multiple-files-issue-112.obj diff --git a/models/mtllib-multiple-files-issue-112.mtl b/models/mtllib-multiple-files-issue-112.mtl new file mode 100644 index 0000000..5ebd668 --- /dev/null +++ b/models/mtllib-multiple-files-issue-112.mtl @@ -0,0 +1,6 @@ +newmtl default +Ka 0 0 0 +Kd 0 0 0 +Ks 0 0 0 +map_Kd tmp.png + diff --git a/models/mtllib-multiple-files-issue-112.obj b/models/mtllib-multiple-files-issue-112.obj new file mode 100644 index 0000000..9966dfb --- /dev/null +++ b/models/mtllib-multiple-files-issue-112.obj @@ -0,0 +1,7 @@ +mtllib invalid-file-aaa.mtl invalid-file-bbb.mtl mtllib-multiple-files-issue-112.mtl +o Test +v 1.864151 -1.219172 -5.532511 +v 0.575869 -0.666304 5.896140 +v 0.940448 1.000000 -1.971128 +usemtl default +f 1 2 3 diff --git a/tests/tester.cc b/tests/tester.cc index b5ac70f..28d3871 100644 --- a/tests/tester.cc +++ b/tests/tester.cc @@ -530,6 +530,21 @@ TEST_CASE("texture_opts", "[Issue85]") { REQUIRE(tinyobj::TEXTURE_TYPE_CUBE_BACK == materials[2].displacement_texopt.type); } +TEST_CASE("mtllib_multiple_filenames", "[Issue112]") { + tinyobj::attrib_t attrib; + std::vector shapes; + std::vector materials; + + std::string err; + bool ret = tinyobj::LoadObj(&attrib, &shapes, &materials, &err, "../models/mtllib-multiple-files-issue-112.obj", gMtlBasePath); + + if (!err.empty()) { + std::cerr << err << std::endl; + } + REQUIRE(true == ret); + REQUIRE(1 == materials.size()); +} + #if 0 int main( diff --git a/tiny_obj_loader.h b/tiny_obj_loader.h index b975601..6947bbd 100644 --- a/tiny_obj_loader.h +++ b/tiny_obj_loader.h @@ -23,6 +23,7 @@ THE SOFTWARE. */ // +// version 1.0.4 : Support multiple filenames for 'mtllib'(#112) // version 1.0.3 : Support parsing texture options(#85) // version 1.0.2 : Improve parsing speed by about a factor of 2 for large // files(#105) @@ -935,6 +936,18 @@ static bool exportFaceGroupToShape( return true; } +// Split a string with specified delimiter character. +// http://stackoverflow.com/questions/236129/split-a-string-in-c +static void SplitString(const std::string &s, char delim, std::vector &elems) +{ + std::stringstream ss; + ss.str(s); + std::string item; + while (std::getline(ss, item, delim)) { + elems.push_back(item); + } +} + void LoadMtl(std::map *material_map, std::vector *materials, std::istream *inStream) { // Create a default material anyway. @@ -1287,15 +1300,17 @@ bool MaterialFileReader::operator()(const std::string &matId, } std::ifstream matIStream(filepath.c_str()); - LoadMtl(matMap, materials, &matIStream); if (!matIStream) { std::stringstream ss; ss << "WARN: Material file [ " << filepath - << " ] not found. Created a default material."; + << " ] not found." << std::endl; if (err) { (*err) += ss.str(); } + return false; } + + LoadMtl(matMap, materials, &matIStream); return true; } @@ -1304,15 +1319,16 @@ bool MaterialStreamReader::operator()(const std::string &matId, std::map *matMap, std::string *err) { (void)matId; - LoadMtl(matMap, materials, &m_inStream); if (!m_inStream) { std::stringstream ss; - ss << "WARN: Material stream in error state." - << " Created a default material."; + ss << "WARN: Material stream in error state. " << std::endl; if (err) { (*err) += ss.str(); } + return false; } + + LoadMtl(matMap, materials, &m_inStream); return true; } @@ -1482,23 +1498,37 @@ bool LoadObj(attrib_t *attrib, std::vector *shapes, // load mtl if ((0 == strncmp(token, "mtllib", 6)) && IS_SPACE((token[6]))) { if (readMatFn) { - char namebuf[TINYOBJ_SSCANF_BUFFER_SIZE]; token += 7; -#ifdef _MSC_VER - sscanf_s(token, "%s", namebuf, (unsigned)_countof(namebuf)); -#else - sscanf(token, "%s", namebuf); -#endif - std::string err_mtl; - bool ok = (*readMatFn)(namebuf, materials, &material_map, &err_mtl); - if (err) { - (*err) += err_mtl; - } + std::vector filenames; + SplitString(std::string(token), ' ', filenames); + + if (filenames.empty()) { + if (err) { + (*err) += "WARN: Looks like empty filename for mtllib. Use default material. \n"; + } + } else { - if (!ok) { - faceGroup.clear(); // for safety - return false; + bool found = false; + for (size_t s = 0; s < filenames.size(); s++) { + + std::string err_mtl; + bool ok = (*readMatFn)(filenames[s].c_str(), materials, &material_map, &err_mtl); + if (err && (!err_mtl.empty())) { + (*err) += err_mtl; // This should be warn message. + } + + if (ok) { + found = true; + break; + } + } + + if (!found) { + if (err) { + (*err) += "WARN: Failed to load material file(s). Use default material.\n"; + } + } } } @@ -1774,28 +1804,44 @@ bool LoadObjWithCallback(std::istream &inStream, const callback_t &callback, // load mtl if ((0 == strncmp(token, "mtllib", 6)) && IS_SPACE((token[6]))) { if (readMatFn) { - char namebuf[TINYOBJ_SSCANF_BUFFER_SIZE]; token += 7; -#ifdef _MSC_VER - sscanf_s(token, "%s", namebuf, (unsigned)_countof(namebuf)); -#else - sscanf(token, "%s", namebuf); -#endif + token += 7; - std::string err_mtl; - materials.clear(); - bool ok = (*readMatFn)(namebuf, &materials, &material_map, &err_mtl); - if (err) { - (*err) += err_mtl; - } + std::vector filenames; + SplitString(std::string(token), ' ', filenames); - if (!ok) { - return false; - } + if (filenames.empty()) { + if (err) { + (*err) += "WARN: Looks like empty filename for mtllib. Use default material. \n"; + } + } else { + + bool found = false; + for (size_t s = 0; s < filenames.size(); s++) { + + std::string err_mtl; + bool ok = (*readMatFn)(filenames[s].c_str(), &materials, &material_map, &err_mtl); + if (err && (!err_mtl.empty())) { + (*err) += err_mtl; // This should be warn message. + } - if (callback.mtllib_cb) { - callback.mtllib_cb(user_data, &materials.at(0), - static_cast(materials.size())); + if (ok) { + found = true; + break; + } + } + + if (!found) { + if (err) { + (*err) += "WARN: Failed to load material file(s). Use default material.\n"; + } + } else { + + if (callback.mtllib_cb) { + callback.mtllib_cb(user_data, &materials.at(0), + static_cast(materials.size())); + } + } } } -- cgit v1.2.3 From 2daec8be537d3a16eb33894fa11d16ae8f82cfa0 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Wed, 7 Dec 2016 00:54:12 +0900 Subject: Search .mtl and texture from .obj's base dir. Fix bad memory access. --- examples/viewer/viewer.cc | 67 +++++++++++++++++++++++++++++++++++++---------- 1 file changed, 53 insertions(+), 14 deletions(-) diff --git a/examples/viewer/viewer.cc b/examples/viewer/viewer.cc index 09fcb67..f3c753a 100644 --- a/examples/viewer/viewer.cc +++ b/examples/viewer/viewer.cc @@ -143,7 +143,26 @@ float eye[3], lookat[3], up[3]; GLFWwindow* window; -void CheckErrors(std::string desc) { +static std::string GetBaseDir(const std::string &filepath) { + if (filepath.find_last_of("/\\") != std::string::npos) + return filepath.substr(0, filepath.find_last_of("/\\")); + return ""; +} + +static bool FileExists(const std::string &abs_filename) { + bool ret; + FILE *fp = fopen(abs_filename.c_str(), "rb"); + if (fp) { + ret = true; + fclose(fp); + } else { + ret = false; + } + + return ret; +} + +static void CheckErrors(std::string desc) { GLenum e = glGetError(); if (e != GL_NO_ERROR) { fprintf(stderr, "OpenGL error in \"%s\": %d (%d)\n", desc.c_str(), e, e); @@ -151,7 +170,7 @@ void CheckErrors(std::string desc) { } } -void CalcNormal(float N[3], float v0[3], float v1[3], float v2[3]) { +static void CalcNormal(float N[3], float v0[3], float v1[3], float v2[3]) { float v10[3]; v10[0] = v1[0] - v0[0]; v10[1] = v1[1] - v0[1]; @@ -175,7 +194,7 @@ void CalcNormal(float N[3], float v0[3], float v1[3], float v2[3]) { } } -bool LoadObjAndConvert(float bmin[3], float bmax[3], +static bool LoadObjAndConvert(float bmin[3], float bmax[3], std::vector* drawObjects, std::vector& materials, std::map& textures, @@ -187,9 +206,16 @@ bool LoadObjAndConvert(float bmin[3], float bmax[3], tm.start(); + std::string base_dir = GetBaseDir(filename); +#ifdef _WIN32 + base_dir += "\\"; +#else + base_dir += "/"; +#endif + std::string err; bool ret = - tinyobj::LoadObj(&attrib, &shapes, &materials, &err, filename, NULL); + tinyobj::LoadObj(&attrib, &shapes, &materials, &err, filename, base_dir.c_str()); if (!err.empty()) { std::cerr << err << std::endl; } @@ -223,9 +249,20 @@ bool LoadObjAndConvert(float bmin[3], float bmax[3], GLuint texture_id; int w, h; int comp; - unsigned char* image = stbi_load(mp->diffuse_texname.c_str(), &w, &h, &comp, STBI_default); + + std::string texture_filename = mp->diffuse_texname; + if (!FileExists(texture_filename)) { + // Append base dir. + texture_filename = base_dir + mp->diffuse_texname; + if (!FileExists(texture_filename)) { + std::cerr << "Unable to find file: " << mp->diffuse_texname << std::endl; + exit(1); + } + } + + unsigned char* image = stbi_load(texture_filename.c_str(), &w, &h, &comp, STBI_default); if (image == nullptr) { - std::cerr << "Unable to load texture: " << mp->diffuse_texname << std::endl; + std::cerr << "Unable to load texture: " << texture_filename << std::endl; exit(1); } glGenTextures(1, &texture_id); @@ -395,7 +432,7 @@ bool LoadObjAndConvert(float bmin[3], float bmax[3], return true; } -void reshapeFunc(GLFWwindow* window, int w, int h) { +static void reshapeFunc(GLFWwindow* window, int w, int h) { int fb_w, fb_h; // Get actual framebuffer size. glfwGetFramebufferSize(window, &fb_w, &fb_h); @@ -411,7 +448,7 @@ void reshapeFunc(GLFWwindow* window, int w, int h) { height = h; } -void keyboardFunc(GLFWwindow* window, int key, int scancode, int action, +static void keyboardFunc(GLFWwindow* window, int key, int scancode, int action, int mods) { (void)window; (void)scancode; @@ -440,7 +477,7 @@ void keyboardFunc(GLFWwindow* window, int key, int scancode, int action, } } -void clickFunc(GLFWwindow* window, int button, int action, int mods) { +static void clickFunc(GLFWwindow* window, int button, int action, int mods) { (void)window; (void)mods; if (button == GLFW_MOUSE_BUTTON_LEFT) { @@ -467,7 +504,7 @@ void clickFunc(GLFWwindow* window, int button, int action, int mods) { } } -void motionFunc(GLFWwindow* window, double mouse_x, double mouse_y) { +static void motionFunc(GLFWwindow* window, double mouse_x, double mouse_y) { (void)window; float rotScale = 1.0f; float transScale = 2.0f; @@ -494,7 +531,7 @@ void motionFunc(GLFWwindow* window, double mouse_x, double mouse_y) { prevMouseY = mouse_y; } -void Draw(const std::vector& drawObjects, std::vector& materials, std::map& textures) { +static void Draw(const std::vector& drawObjects, std::vector& materials, std::map& textures) { glPolygonMode(GL_FRONT, GL_FILL); glPolygonMode(GL_BACK, GL_FILL); @@ -513,9 +550,11 @@ void Draw(const std::vector& drawObjects, std::vector 0) { - glBindTexture(GL_TEXTURE_2D, textures[diffuse_texname]); + if ((o.material_id < materials.size())) { + std::string diffuse_texname = materials[o.material_id].diffuse_texname; + if (textures.find(diffuse_texname) != textures.end()) { + glBindTexture(GL_TEXTURE_2D, textures[diffuse_texname]); + } } glVertexPointer(3, GL_FLOAT, stride, (const void*)0); glNormalPointer(GL_FLOAT, stride, (const void*)(sizeof(float) * 3)); -- cgit v1.2.3 From 60ffb3ca9a2243ea5736ee9861f143d55312b4f7 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Fri, 9 Dec 2016 01:51:03 +0900 Subject: Add missing files. Fixes #115. --- examples/voxelize/Makefile | 2 + examples/voxelize/README.md | 5 + examples/voxelize/voxelizer.h | 764 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 771 insertions(+) create mode 100644 examples/voxelize/Makefile create mode 100644 examples/voxelize/README.md create mode 100644 examples/voxelize/voxelizer.h diff --git a/examples/voxelize/Makefile b/examples/voxelize/Makefile new file mode 100644 index 0000000..98189d9 --- /dev/null +++ b/examples/voxelize/Makefile @@ -0,0 +1,2 @@ +all: + g++ -o voxelizer main.cc diff --git a/examples/voxelize/README.md b/examples/voxelize/README.md new file mode 100644 index 0000000..8a113b6 --- /dev/null +++ b/examples/voxelize/README.md @@ -0,0 +1,5 @@ +# Voxelize .obj and export it as also in .obj + +## Third party library + +This example uses https://github.com/karimnaaji/voxelizer, which is licensed under MIT Liense. diff --git a/examples/voxelize/voxelizer.h b/examples/voxelize/voxelizer.h new file mode 100644 index 0000000..4958695 --- /dev/null +++ b/examples/voxelize/voxelizer.h @@ -0,0 +1,764 @@ +// +// LICENCE: +// The MIT License (MIT) +// +// Copyright (c) 2016 Karim Naaji, karim.naaji@gmail.com +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE +// +// REFERENCES: +// http://matthias-mueller-fischer.ch/publications/tetraederCollision.pdf +// http://fileadmin.cs.lth.se/cs/Personal/Tomas_Akenine-Moller/code/tribox2.txt +// +// HOWTO: +// #define VOXELIZER_IMPLEMENTATION +// #define VOXELIZER_DEBUG // Only if assertions need to be checked +// #include "voxelizer.h" +// +// HISTORY: +// - version 0.9.0: Initial +// +// TODO: +// - Triangle face merging +// - Add colors from input mesh +// - Potential issue with voxel bigger than triangle +// + +#ifndef VOXELIZER_H +#define VOXELIZER_H + +// ------------------------------------------------------------------------------------------------ +// VOXELIZER PUBLIC API +// + +#ifndef VOXELIZER_HELPERS +#include // malloc, calloc, free +#endif + +typedef struct vx_vertex { + union { + float v[3]; + struct { + float x; + float y; + float z; + }; + }; +} vx_vertex_t; + +typedef struct vx_mesh { + vx_vertex_t* vertices; // Contiguous mesh vertices + vx_vertex_t* normals; // Contiguous mesh normals + unsigned int* indices; // Mesh indices + unsigned int* normalindices; // Mesh normal indices + size_t nindices; // The number of normal indices + size_t nvertices; // The number of vertices + size_t nnormals; // The number of normals +} vx_mesh_t; + +vx_mesh_t* vx_voxelize(vx_mesh_t* _mesh, // The input mesh + float voxelsizex, // Voxel size on X-axis + float voxelsizey, // Voxel size on Y-axis + float voxelsizez, // Voxel size on Z-axis + float precision); // A precision factor that reduces "holes" artifact + // usually a precision = voxelsize / 10. works ok. + +void vx_mesh_free(vx_mesh_t* _mesh); +vx_mesh_t* vx_mesh_alloc(int nindices, int nvertices); + +// Voxelizer Helpers, define your own if needed +#ifndef VOXELIZER_HELPERS +#define VOXELIZER_HELPERS 1 +#define VX_MIN(a, b) (a > b ? b : a) +#define VX_MAX(a, b) (a > b ? a : b) +#define VX_FINDMINMAX(x0, x1, x2, min, max) \ + min = max = x0; \ + if (x1 < min) min = x1; \ + if (x1 > max) max = x1; \ + if (x2 < min) min = x2; \ + if (x2 > max) max = x2; +#define VX_CLAMP(v, lo, hi) VX_MAX(lo, VX_MIN(hi, v)) +#define VX_MALLOC(T, N) ((T*) malloc(N * sizeof(T))) +#define VX_FREE(T) free(T) +#define VX_CALLOC(T, N) ((T*) calloc(N * sizeof(T), 1)) +#define VX_SWAP(T, A, B) { T tmp = B; B = A; A = tmp; } +#ifdef VOXELIZER_DEBUG +#define VX_ASSERT(STMT) if (!(STMT)) { *(int *)0 = 0; } +#else +#define VX_ASSERT(STMT) +#endif // VOXELIZER_DEBUG +#endif // VOXELIZER_HELPERS + +// +// END VOXELIZER PUBLIC API +// ------------------------------------------------------------------------------------------------ + +#endif // VOXELIZER_H + +#ifdef VOXELIZER_IMPLEMENTATION + +#include // ceil, fabs & al. +#include // hughh +#include // memcpy + +#define VOXELIZER_EPSILON (0.0000001) +#define VOXELIZER_NORMAL_INDICES_SIZE (6) +#define VOXELIZER_INDICES_SIZE (36) +#define VOXELIZER_HASH_TABLE_SIZE (4096) + +unsigned int vx_voxel_indices[VOXELIZER_INDICES_SIZE] = { + 0, 1, 2, + 0, 2, 3, + 3, 2, 6, + 3, 6, 7, + 0, 7, 4, + 0, 3, 7, + 4, 7, 5, + 7, 6, 5, + 0, 4, 5, + 0, 5, 1, + 1, 5, 6, + 1, 6, 2, +}; + +float vx_normals[18] = { + 0.0, -1.0, 0.0, + 0.0, 1.0, 0.0, + 1.0, 0.0, 0.0, + 0.0, 0.0, 1.0, + -1.0, 0.0, 0.0, + 0.0, 0.0, -1.0, +}; + +unsigned int vx_normal_indices[VOXELIZER_NORMAL_INDICES_SIZE] = { + 3, 2, 1, 5, 4, 0, +}; + +typedef struct vx_aabb { + vx_vertex_t min; + vx_vertex_t max; +} vx_aabb_t; + +typedef struct vx_edge { + vx_vertex_t p1; + vx_vertex_t p2; +} vx_edge_t; + +typedef struct vx_triangle { + vx_vertex_t p1; + vx_vertex_t p2; + vx_vertex_t p3; +} vx_triangle_t; + +typedef struct vx_hash_table_node { + struct vx_hash_table_node* next; + struct vx_hash_table_node* prev; + void* data; +} vx_hash_table_node_t; + +typedef struct vx_hash_table { + vx_hash_table_node_t** elements; + size_t size; +} vx_hash_table_t; + +vx_hash_table_t* vx__hash_table_alloc(size_t size) +{ + vx_hash_table_t* table = VX_MALLOC(vx_hash_table_t, 1); + if (!table) { return NULL; } + table->size = size; + table->elements = VX_MALLOC(vx_hash_table_node_t*, size); + if (!table->elements) { return NULL; } + for (size_t i = 0; i < table->size; ++i) { + table->elements[i] = NULL; + } + return table; +} + +void vx__hash_table_free(vx_hash_table_t* table, bool freedata) +{ + for (size_t i = 0; i < table->size; ++i) { + vx_hash_table_node_t* node = table->elements[i]; + + if (node) { + if (node->next) { + while (node->next) { + node = node->next; + if (freedata) { + VX_FREE(node->prev->data); + } + VX_FREE(node->prev); + } + VX_FREE(node); + } else { + VX_FREE(node->data); + VX_FREE(node); + } + } + } + + VX_FREE(table->elements); + VX_FREE(table); +} + +bool vx__hash_table_insert(vx_hash_table_t* table, + size_t hash, + void* data, + bool (*compfunc)(void* d1, void* d2)) +{ + if (!table->elements[hash]) { + table->elements[hash] = VX_MALLOC(vx_hash_table_node_t, 1); + table->elements[hash]->prev = NULL; + table->elements[hash]->next = NULL; + table->elements[hash]->data = data; + } else { + vx_hash_table_node_t* node = table->elements[hash]; + + if (compfunc && compfunc(node->data, data)) { + return false; + } + + while (node->next) { + node = node->next; + if (compfunc && compfunc(node->data, data)) { + return false; + } + } + + vx_hash_table_node_t* nnode = VX_MALLOC(vx_hash_table_node_t, 1); + + nnode->prev = node; + nnode->next = NULL; + nnode->data = data; + node->next = nnode; + } + return true; +} + +void vx_mesh_free(vx_mesh_t* m) +{ + VX_FREE(m->vertices); + m->vertices = NULL; + m->nvertices = 0; + VX_FREE(m->indices); + m->indices = NULL; + m->nindices = 0; + if (m->normals) { VX_FREE(m->normals); } + VX_FREE(m); +} + +vx_mesh_t* vx_mesh_alloc(int nvertices, int nindices) +{ + vx_mesh_t* m = VX_MALLOC(vx_mesh_t, 1); + if (!m) { return NULL; } + m->indices = VX_CALLOC(unsigned int, nindices); + if (!m->indices) { return NULL; } + m->vertices = VX_CALLOC(vx_vertex_t, nvertices); + if (!m->vertices) { return NULL; } + m->normals = NULL; + m->nindices = nindices; + m->nvertices = nvertices; + return m; +} + +float vx__map_to_voxel(float position, float voxelSize, bool min) +{ + float vox = (position + (position < 0.f ? -1.f : 1.f) * voxelSize * 0.5f) / voxelSize; + return (min ? floor(vox) : ceil(vox)) * voxelSize; +} + +vx_vertex_t vx__vertex_cross(vx_vertex_t* v1, vx_vertex_t* v2) +{ + vx_vertex_t cross; + cross.x = v1->y * v2->z - v1->z * v2->y; + cross.y = v1->z * v2->x - v1->x * v2->z; + cross.z = v1->x * v2->y - v1->y * v2->x; + return cross; +} + +bool vx__vertex_equals(vx_vertex_t* v1, vx_vertex_t* v2) { + return fabs(v1->x - v2->x) < VOXELIZER_EPSILON && + fabs(v1->y - v2->y) < VOXELIZER_EPSILON && + fabs(v1->z - v2->z) < VOXELIZER_EPSILON; +} + +bool vx__vertex_comp_func(void* a, void* b) +{ + return vx__vertex_equals((vx_vertex_t*) a, (vx_vertex_t*) b); +} + +void vx__vertex_sub(vx_vertex_t* a, vx_vertex_t* b) +{ + a->x -= b->x; + a->y -= b->y; + a->z -= b->z; +} + +void vx__vertex_add(vx_vertex_t* a, vx_vertex_t* b) +{ + a->x += b->x; + a->y += b->y; + a->z += b->z; +} + +void vx__vertex_multiply(vx_vertex_t* a, float v) +{ + a->x *= v; + a->y *= v; + a->z *= v; +} + +float vx__vertex_dot(vx_vertex_t* v1, vx_vertex_t* v2) +{ + return v1->x * v2->x + v1->y * v2->y + v1->z * v2->z; +} + +int vx__plane_box_overlap(vx_vertex_t* normal, + float d, + vx_vertex_t* halfboxsize) +{ + vx_vertex_t vmin, vmax; + + for (int dim = 0; dim <= 2; dim++) { + if (normal->v[dim] > 0.0f) { + vmin.v[dim] = -halfboxsize->v[dim]; + vmax.v[dim] = halfboxsize->v[dim]; + } else { + vmin.v[dim] = halfboxsize->v[dim]; + vmax.v[dim] = -halfboxsize->v[dim]; + } + } + + if (vx__vertex_dot(normal, &vmin) + d > 0.0f) { + return false; + } + + if (vx__vertex_dot(normal, &vmax) + d >= 0.0f) { + return true; + } + + return false; +} + +#define AXISTEST_X01(a, b, fa, fb) \ + p1 = a * v1.y - b * v1.z; \ + p3 = a * v3.y - b * v3.z; \ + if (p1 < p3) { \ + min = p1; max = p3; \ + } else { \ + min = p3; max = p1; \ + } \ + rad = fa * halfboxsize.y + fb * halfboxsize.z; \ + if (min > rad || max < -rad) { \ + return false; \ + } \ + +#define AXISTEST_X2(a, b, fa, fb) \ + p1 = a * v1.y - b * v1.z; \ + p2 = a * v2.y - b * v2.z; \ + if (p1 < p2) { \ + min = p1; max = p2; \ + } else { \ + min = p2; max = p1; \ + } \ + rad = fa * halfboxsize.y + fb * halfboxsize.z; \ + if (min > rad || max < -rad) { \ + return false; \ + } \ + +#define AXISTEST_Y02(a, b, fa, fb) \ + p1 = -a * v1.x + b * v1.z; \ + p3 = -a * v3.x + b * v3.z; \ + if (p1 < p3) { \ + min = p1; max = p3; \ + } else { \ + min = p3; max = p1; \ + } \ + rad = fa * halfboxsize.x + fb * halfboxsize.z; \ + if (min > rad || max < -rad) { \ + return false; \ + } \ + +#define AXISTEST_Y1(a, b, fa, fb) \ + p1 = -a * v1.x + b * v1.z; \ + p2 = -a * v2.x + b * v2.z; \ + if (p1 < p2) { \ + min = p1; max = p2; \ + } else { \ + min = p2; max = p1; \ + } \ + rad = fa * halfboxsize.x + fb * halfboxsize.z; \ + if (min > rad || max < -rad) { \ + return false; \ + } + +#define AXISTEST_Z12(a, b, fa, fb) \ + p2 = a * v2.x - b * v2.y; \ + p3 = a * v3.x - b * v3.y; \ + if (p3 < p2) { \ + min = p3; max = p2; \ + } else { \ + min = p2; max = p3; \ + } \ + rad = fa * halfboxsize.x + fb * halfboxsize.y; \ + if (min > rad || max < -rad) { \ + return false; \ + } + +#define AXISTEST_Z0(a, b, fa, fb) \ + p1 = a * v1.x - b * v1.y; \ + p2 = a * v2.x - b * v2.y; \ + if (p1 < p2) { \ + min = p1; max = p2; \ + } else { \ + min = p2; max = p1; \ + } \ + rad = fa * halfboxsize.x + fb * halfboxsize.y; \ + if (min > rad || max < -rad) { \ + return false; \ + } + +int vx__triangle_box_overlap(vx_vertex_t boxcenter, + vx_vertex_t halfboxsize, + vx_triangle_t triangle) +{ + vx_vertex_t v1, v2, v3, normal, e1, e2, e3; + float min, max, d, p1, p2, p3, rad, fex, fey, fez; + + v1 = triangle.p1; + v2 = triangle.p2; + v3 = triangle.p3; + + vx__vertex_sub(&v1, &boxcenter); + vx__vertex_sub(&v2, &boxcenter); + vx__vertex_sub(&v3, &boxcenter); + + e1 = v2; + e2 = v3; + e3 = v1; + + vx__vertex_sub(&e1, &v1); + vx__vertex_sub(&e2, &v2); + vx__vertex_sub(&e3, &v3); + + fex = fabs(e1.x); + fey = fabs(e1.y); + fez = fabs(e1.z); + + AXISTEST_X01(e1.z, e1.y, fez, fey); + AXISTEST_Y02(e1.z, e1.x, fez, fex); + AXISTEST_Z12(e1.y, e1.x, fey, fex); + + fex = fabs(e2.x); + fey = fabs(e2.y); + fez = fabs(e2.z); + + AXISTEST_X01(e2.z, e2.y, fez, fey); + AXISTEST_Y02(e2.z, e2.x, fez, fex); + AXISTEST_Z0(e2.y, e2.x, fey, fex); + + fex = fabs(e3.x); + fey = fabs(e3.y); + fez = fabs(e3.z); + + AXISTEST_X2(e3.z, e3.y, fez, fey); + AXISTEST_Y1(e3.z, e3.x, fez, fex); + AXISTEST_Z12(e3.y, e3.x, fey, fex); + + VX_FINDMINMAX(v1.x, v2.x, v3.x, min, max); + if (min > halfboxsize.x || max < -halfboxsize.x) { + return false; + } + + VX_FINDMINMAX(v1.y, v2.y, v3.y, min, max); + if (min > halfboxsize.y || max < -halfboxsize.y) { + return false; + } + + VX_FINDMINMAX(v1.z, v2.z, v3.z, min, max); + if (min > halfboxsize.z || max < -halfboxsize.z) { + return false; + } + + normal = vx__vertex_cross(&e1, &e2); + d = -vx__vertex_dot(&normal, &v1); + + if (!vx__plane_box_overlap(&normal, d, &halfboxsize)) { + return false; + } + + return true; +} + +#undef AXISTEST_X2 +#undef AXISTEST_X01 +#undef AXISTEST_Y1 +#undef AXISTEST_Y02 +#undef AXISTEST_Z0 +#undef AXISTEST_Z12 + +float vx__triangle_area(vx_triangle_t* triangle) { + vx_vertex_t ab = triangle->p2; + vx_vertex_t ac = triangle->p3; + + vx__vertex_sub(&ab, &triangle->p1); + vx__vertex_sub(&ac, &triangle->p1); + + float a0 = ab.y * ac.z - ab.z * ac.y; + float a1 = ab.z * ac.x - ab.x * ac.z; + float a2 = ab.x * ac.y - ab.y * ac.x; + + return sqrtf(powf(a0, 2.f) + powf(a1, 2.f) + powf(a2, 2.f)) * 0.5f; +} + +void vx__aabb_init(vx_aabb_t* aabb) +{ + aabb->max.x = aabb->max.y = aabb->max.z = -INFINITY; + aabb->min.x = aabb->min.y = aabb->min.z = INFINITY; +} + +vx_aabb_t vx__triangle_aabb(vx_triangle_t* triangle) +{ + vx_aabb_t aabb; + + vx__aabb_init(&aabb); + + aabb.max.x = VX_MAX(aabb.max.x, triangle->p1.x); aabb.max.x = VX_MAX(aabb.max.x, + triangle->p2.x); aabb.max.x = VX_MAX(aabb.max.x, triangle->p3.x); + aabb.max.y = VX_MAX(aabb.max.y, triangle->p1.y); aabb.max.y = VX_MAX(aabb.max.y, + triangle->p2.y); aabb.max.y = VX_MAX(aabb.max.y, triangle->p3.y); + aabb.max.z = VX_MAX(aabb.max.z, triangle->p1.z); aabb.max.z = VX_MAX(aabb.max.z, + triangle->p2.z); aabb.max.z = VX_MAX(aabb.max.z, triangle->p3.z); + + aabb.min.x = VX_MIN(aabb.min.x, triangle->p1.x); aabb.min.x = VX_MIN(aabb.min.x, + triangle->p2.x); aabb.min.x = VX_MIN(aabb.min.x, triangle->p3.x); + aabb.min.y = VX_MIN(aabb.min.y, triangle->p1.y); aabb.min.y = VX_MIN(aabb.min.y, + triangle->p2.y); aabb.min.y = VX_MIN(aabb.min.y, triangle->p3.y); + aabb.min.z = VX_MIN(aabb.min.z, triangle->p1.z); aabb.min.z = VX_MIN(aabb.min.z, + triangle->p2.z); aabb.min.z = VX_MIN(aabb.min.z, triangle->p3.z); + + return aabb; +} + +vx_vertex_t vx__aabb_center(vx_aabb_t* a) +{ + vx_vertex_t boxcenter = a->min; + vx__vertex_add(&boxcenter, &a->max); + vx__vertex_multiply(&boxcenter, 0.5f); + + return boxcenter; +} + +vx_vertex_t vx__aabb_half_size(vx_aabb_t* a) +{ + vx_vertex_t size; + + size.x = fabs(a->max.x - a->min.x) * 0.5f; + size.y = fabs(a->max.y - a->min.y) * 0.5f; + size.z = fabs(a->max.z - a->min.z) * 0.5f; + + return size; +} + +vx_aabb_t vx__aabb_merge(vx_aabb_t* a, vx_aabb_t* b) +{ + vx_aabb_t merge; + + merge.min.x = VX_MIN(a->min.x, b->min.x); + merge.min.y = VX_MIN(a->min.y, b->min.y); + merge.min.z = VX_MIN(a->min.z, b->min.z); + + merge.max.x = VX_MAX(a->max.x, b->max.x); + merge.max.y = VX_MAX(a->max.y, b->max.y); + merge.max.z = VX_MAX(a->max.z, b->max.z); + + return merge; +} + +size_t vx__vertex_hash(vx_vertex_t pos, size_t n) +{ + size_t a = (size_t)(pos.x * 73856093); + size_t b = (size_t)(pos.y * 19349663); + size_t c = (size_t)(pos.z * 83492791); + + return (a ^ b ^ c) % n; +} + +void vx__add_voxel(vx_mesh_t* mesh, + vx_vertex_t* pos, + float* vertices) +{ + for (size_t i = 0; i < 8; ++i) { + size_t index = i+mesh->nvertices; + + mesh->vertices[index].x = vertices[i*3+0] + pos->x; + mesh->vertices[index].y = vertices[i*3+1] + pos->y; + mesh->vertices[index].z = vertices[i*3+2] + pos->z; + } + + int j = -1; + for (size_t i = 0; i < VOXELIZER_INDICES_SIZE; ++i) { + if (i % 6 == 0) { + j++; + } + mesh->normalindices[i+mesh->nindices] = vx_normal_indices[j]; + } + + for (size_t i = 0; i < VOXELIZER_INDICES_SIZE; ++i) { + mesh->indices[i+mesh->nindices] = vx_voxel_indices[i] + mesh->nvertices; + } + + mesh->nindices += VOXELIZER_INDICES_SIZE; + mesh->nvertices += 8; +} + +vx_mesh_t* vx_voxelize(vx_mesh_t* m, + float voxelsizex, + float voxelsizey, + float voxelsizez, + float precision) +{ + vx_mesh_t* outmesh = NULL; + vx_hash_table_t* table = NULL; + size_t voxels = 0; + + float halfsizex = voxelsizex * 0.5f; + float halfsizey = voxelsizey * 0.5f; + float halfsizez = voxelsizez * 0.5f; + + table = vx__hash_table_alloc(VOXELIZER_HASH_TABLE_SIZE); + + for (int i = 0; i < m->nindices; i += 3) { + vx_triangle_t triangle; + + VX_ASSERT(m->indices[i+0] < m->nvertices); + VX_ASSERT(m->indices[i+1] < m->nvertices); + VX_ASSERT(m->indices[i+2] < m->nvertices); + + triangle.p1 = m->vertices[m->indices[i+0]]; + triangle.p2 = m->vertices[m->indices[i+1]]; + triangle.p3 = m->vertices[m->indices[i+2]]; + + if (vx__triangle_area(&triangle) < VOXELIZER_EPSILON) { + // triangle with 0 area + continue; + } + + vx_aabb_t aabb = vx__triangle_aabb(&triangle); + + aabb.min.x = vx__map_to_voxel(aabb.min.x, voxelsizex, true); + aabb.min.y = vx__map_to_voxel(aabb.min.y, voxelsizey, true); + aabb.min.z = vx__map_to_voxel(aabb.min.z, voxelsizez, true); + + aabb.max.x = vx__map_to_voxel(aabb.max.x, voxelsizex, false); + aabb.max.y = vx__map_to_voxel(aabb.max.y, voxelsizey, false); + aabb.max.z = vx__map_to_voxel(aabb.max.z, voxelsizez, false); + + for (float x = aabb.min.x; x < aabb.max.x; x += voxelsizex) { + for (float y = aabb.min.y; y < aabb.max.y; y += voxelsizey) { + for (float z = aabb.min.z; z < aabb.max.z; z += voxelsizez) { + vx_aabb_t saabb; + + saabb.min.x = x - halfsizex; + saabb.min.y = y - halfsizey; + saabb.min.z = z - halfsizez; + saabb.max.x = x + halfsizex; + saabb.max.y = y + halfsizey; + saabb.max.z = z + halfsizez; + + vx_vertex_t boxcenter = vx__aabb_center(&saabb); + vx_vertex_t halfsize = vx__aabb_half_size(&saabb); + + // HACK: some holes might appear, this + // precision factor reduces the artifact + halfsize.x += precision; + halfsize.y += precision; + halfsize.z += precision; + + if (vx__triangle_box_overlap(boxcenter, halfsize, triangle)) { + vx_vertex_t* nodedata = VX_MALLOC(vx_vertex_t, 1); + *nodedata = boxcenter; + + size_t hash = vx__vertex_hash(boxcenter, VOXELIZER_HASH_TABLE_SIZE); + + bool insert = vx__hash_table_insert(table, + hash, + nodedata, + vx__vertex_comp_func); + + if (insert) { + voxels++; + } + } + } + } + } + } + + outmesh = VX_MALLOC(vx_mesh_t, 1); + size_t nvertices = voxels * 8; + size_t nindices = voxels * VOXELIZER_INDICES_SIZE; + outmesh->nnormals = VOXELIZER_NORMAL_INDICES_SIZE; + outmesh->vertices = VX_CALLOC(vx_vertex_t, nvertices); + outmesh->normals = VX_CALLOC(vx_vertex_t, 6); + outmesh->indices = VX_CALLOC(unsigned int, nindices); + outmesh->normalindices = VX_CALLOC(unsigned int, nindices); + outmesh->nindices = 0; + outmesh->nvertices = 0; + + memcpy(outmesh->normals, vx_normals, 18 * sizeof(float)); + + float vertices[24] = { + -halfsizex, halfsizey, halfsizez, + -halfsizex, -halfsizey, halfsizez, + halfsizex, -halfsizey, halfsizez, + halfsizex, halfsizey, halfsizez, + -halfsizex, halfsizey, -halfsizez, + -halfsizex, -halfsizey, -halfsizez, + halfsizex, -halfsizey, -halfsizez, + halfsizex, halfsizey, -halfsizez, + }; + + for (size_t i = 0; i < table->size; ++i) { + if (table->elements[i] != NULL) { + vx_hash_table_node_t* node = table->elements[i]; + + if (!node) { + continue; + } + + vx_vertex_t* p = (vx_vertex_t*) node->data; + vx__add_voxel(outmesh, p, vertices); + + while (node->next) { + node = node->next; + p = (vx_vertex_t*) node->data; + vx__add_voxel(outmesh, p, vertices); + } + } + } + + vx__hash_table_free(table, true); + + return outmesh; +} + +#undef VOXELIZER_EPSILON +#undef VOXELIZER_INDICES_SIZE +#undef VOXELIZER_HASH_TABLE_SIZE + +#endif // VX_VOXELIZER_IMPLEMENTATION -- cgit v1.2.3 From edabf194611dffcf59738b613b715e0ec027c940 Mon Sep 17 00:00:00 2001 From: Jamie Snape Date: Mon, 19 Dec 2016 11:44:39 -0500 Subject: Write and install CMake package config files This allows find_package(tinyobjloader) to be called. --- CMakeLists.txt | 60 +++++++++++++++++++++++++++++++++++++++++-- tinyobjloader-config.cmake.in | 9 +++++++ 2 files changed, 67 insertions(+), 2 deletions(-) create mode 100644 tinyobjloader-config.cmake.in diff --git a/CMakeLists.txt b/CMakeLists.txt index e43ea57..956c0c2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,6 +3,8 @@ #on the platform and configuration it is set to build in. project(tinyobjloader) cmake_minimum_required(VERSION 2.8.6) +set(TINYOBJLOADER_SOVERSION 1) +set(TINYOBJLOADER_VERSION 1.0.4) #Folder Shortcuts set(TINYOBJLOADEREXAMPLES_DIR ${CMAKE_CURRENT_SOURCE_DIR}/examples) @@ -22,15 +24,32 @@ set(tinyobjloader-examples-objsticher ${TINYOBJLOADEREXAMPLES_DIR}/obj_sticher/obj_sticher.cc ) +#Install destinations +set(TINYOBJLOADER_CMAKE_DIR lib/cmake) +set(TINYOBJLOADER_INCLUDE_DIR include) +set(TINYOBJLOADER_LIBRARY_DIR lib) + option(TINYOBJLOADER_BUILD_TEST_LOADER "Build Example Loader Application" OFF) option(TINYOBJLOADER_COMPILATION_SHARED "Build as shared library" OFF) if (TINYOBJLOADER_COMPILATION_SHARED) add_library(tinyobjloader SHARED ${tinyobjloader-Source}) + set_target_properties(tinyobjloader PROPERTIES + SOVERSION ${TINYOBJLOADER_SOVERSION} + ) else() add_library(tinyobjloader STATIC ${tinyobjloader-Source}) endif() +set_target_properties(tinyobjloader PROPERTIES VERSION ${TINYOBJLOADER_VERSION}) + +target_include_directories(tinyobjloader INTERFACE + $ + $ + ) + +export(TARGETS tinyobjloader FILE ${PROJECT_NAME}-targets.cmake) + if(TINYOBJLOADER_BUILD_TEST_LOADER) add_executable(test_loader ${tinyobjloader-Example-Source}) target_link_libraries(test_loader tinyobjloader) @@ -48,14 +67,51 @@ if (TINYOBJLOADER_BUILD_OBJ_STICHER) ) endif() +#Write CMake package config files +include(CMakePackageConfigHelpers) + +configure_package_config_file( + tinyobjloader-config.cmake.in + tinyobjloader-config.cmake + INSTALL_DESTINATION + ${TINYOBJLOADER_CMAKE_DIR} + PATH_VARS + TINYOBJLOADER_INCLUDE_DIR + TINYOBJLOADER_LIBRARY_DIR + NO_CHECK_REQUIRED_COMPONENTS_MACRO + ) + +write_basic_package_version_file(tinyobjloader-config-version.cmake + VERSION + ${TINYOBJLOADER_VERSION} + COMPATIBILITY + SameMajorVersion + ) + #Installation install ( TARGETS tinyobjloader + EXPORT ${PROJECT_NAME}-targets + DESTINATION + ${TINYOBJLOADER_LIBRARY_DIR} + PUBLIC_HEADER DESTINATION + ${TINYOBJLOADER_INCLUDE_DIR} + RUNTIME DESTINATION + bin + ) +install ( EXPORT + ${PROJECT_NAME}-targets DESTINATION - lib + ${TINYOBJLOADER_LIBRARY_DIR} ) install ( FILES tiny_obj_loader.h DESTINATION - include + ${TINYOBJLOADER_INCLUDE_DIR} + ) +install ( FILES + "${CMAKE_CURRENT_BINARY_DIR}/tinyobjloader-config.cmake" + "${CMAKE_CURRENT_BINARY_DIR}/tinyobjloader-config-version.cmake" + DESTINATION + ${TINYOBJLOADER_CMAKE_DIR} ) diff --git a/tinyobjloader-config.cmake.in b/tinyobjloader-config.cmake.in new file mode 100644 index 0000000..2d6b3e3 --- /dev/null +++ b/tinyobjloader-config.cmake.in @@ -0,0 +1,9 @@ +@PACKAGE_INIT@ + +set(TINYOBJLOADER_VERSION "@TINYOBJLOADER_VERSION@") + +set_and_check(TINYOBJLOADER_INCLUDE_DIRS "@PACKAGE_TINYOBJLOADER_INCLUDE_DIR@") +set_and_check(TINYOBJLOADER_LIBRARY_DIRS "@PACKAGE_TINYOBJLOADER_LIBRARY_DIR@") +set(TINYOBJLOADER_LIBRARIES tinyobjloader) + +include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@-targets.cmake") -- cgit v1.2.3 From e88016c63f814ceaa84127273cfd1002a1922209 Mon Sep 17 00:00:00 2001 From: Jamie Snape Date: Mon, 19 Dec 2016 11:45:00 -0500 Subject: Write and install pkg-config file --- CMakeLists.txt | 9 +++++++++ tinyobjloader.pc.in | 15 +++++++++++++++ 2 files changed, 24 insertions(+) create mode 100644 tinyobjloader.pc.in diff --git a/CMakeLists.txt b/CMakeLists.txt index 956c0c2..0ebd0af 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -28,6 +28,7 @@ set(tinyobjloader-examples-objsticher set(TINYOBJLOADER_CMAKE_DIR lib/cmake) set(TINYOBJLOADER_INCLUDE_DIR include) set(TINYOBJLOADER_LIBRARY_DIR lib) +set(TINYOBJLOADER_PKGCONFIG_DIR lib/pkgconfig) option(TINYOBJLOADER_BUILD_TEST_LOADER "Build Example Loader Application" OFF) option(TINYOBJLOADER_COMPILATION_SHARED "Build as shared library" OFF) @@ -88,6 +89,9 @@ write_basic_package_version_file(tinyobjloader-config-version.cmake SameMajorVersion ) +#pkg-config file +configure_file(tinyobjloader.pc.in tinyobjloader.pc @ONLY) + #Installation install ( TARGETS tinyobjloader @@ -115,3 +119,8 @@ install ( FILES DESTINATION ${TINYOBJLOADER_CMAKE_DIR} ) +install ( FILES + "${CMAKE_CURRENT_BINARY_DIR}/tinyobjloader.pc" + DESTINATION + ${TINYOBJLOADER_PKGCONFIG_DIR} + ) diff --git a/tinyobjloader.pc.in b/tinyobjloader.pc.in new file mode 100644 index 0000000..7585fee --- /dev/null +++ b/tinyobjloader.pc.in @@ -0,0 +1,15 @@ +# Generated by CMake @CMAKE_VERSION@ for @PROJECT_NAME@. Any changes to this +# file will be overwritten by the next CMake run. The input file was +# tinyobjloader.pc.in. + +prefix=@CMAKE_INSTALL_PREFIX@ +exec_prefix=${prefix} +libdir=${prefix}/@TINYOBJLOADER_LIBRARY_DIR@ +includedir=${prefix}/@TINYOBJLOADER_INCLUDE_DIR@ + +Name: @PROJECT_NAME@ +Description: Tiny but powerful single file wavefront obj loader +URL: https://syoyo.github.io/tinyobjloader/ +Version: @TINYOBJLOADER_VERSION@ +Libs: -L${libdir} -ltinyobjloader +Cflags: -I${includedir} -- cgit v1.2.3 From d7f83f29f07348be19aa124f259a5280f716857d Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Tue, 20 Dec 2016 15:42:15 +0900 Subject: Fix for Windows platform. --- experimental/premake4.lua | 12 +++++++----- experimental/tinyobj_loader_opt.h | 8 ++++---- experimental/viewer.cc | 26 +++++++++++++++++++------- 3 files changed, 30 insertions(+), 16 deletions(-) diff --git a/experimental/premake4.lua b/experimental/premake4.lua index d5f433f..0273ed2 100644 --- a/experimental/premake4.lua +++ b/experimental/premake4.lua @@ -21,7 +21,8 @@ solution "objview" includedirs { "./" } includedirs { "../../" } - buildoptions { "-std=c++11" } + flags { "c++11" } + --buildoptions { "-std=c++11" } if _OPTIONS['with-zlib'] then defines { 'ENABLE_ZLIB' } @@ -48,14 +49,15 @@ solution "objview" configuration { "windows" } -- Path to GLFW3 - includedirs { '../../../../local/glfw-3.1.2.bin.WIN64/include' } - libdirs { '../../../../local/glfw-3.1.2.bin.WIN64/lib-vc2013' } + includedirs { '../../../local/glfw-3.2.bin.WIN64/include' } + libdirs { '../../../local/glfw-3.2.bin.WIN64/lib-vc2015' } -- Path to GLEW - includedirs { '../../../../local/glew-1.13.0/include' } - libdirs { '../../../../local/glew-1.13.0/lib/Release/x64' } + includedirs { '../../../local/glew-1.13.0/include' } + libdirs { '../../../local/glew-1.13.0/lib/Release/x64' } links { "glfw3", "glew32", "gdi32", "winmm", "user32", "glu32","opengl32", "kernel32" } defines { "_CRT_SECURE_NO_WARNINGS" } + defines { "NOMINMAX" } configuration { "macosx" } includedirs { "/usr/local/include" } diff --git a/experimental/tinyobj_loader_opt.h b/experimental/tinyobj_loader_opt.h index d4e9eaf..3bee218 100644 --- a/experimental/tinyobj_loader_opt.h +++ b/experimental/tinyobj_loader_opt.h @@ -29,7 +29,7 @@ THE SOFTWARE. #ifndef TINOBJ_LOADER_OPT_H_ #define TINOBJ_LOADER_OPT_H_ -#ifdef _WIN64 +#ifdef _WIN32 #define atoll(S) _atoi64(S) #include #else @@ -1263,7 +1263,7 @@ bool parseObj(attrib_t *attrib, std::vector *shapes, ? std::thread::hardware_concurrency() : option.req_num_threads; num_threads = - std::max(1, std::min(static_cast(num_threads), kMaxThreads)); + (std::max)(1, (std::min)(static_cast(num_threads), kMaxThreads)); if (option.verbose) { std::cout << "# of threads = " << num_threads << std::endl; @@ -1295,7 +1295,7 @@ bool parseObj(attrib_t *attrib, std::vector *shapes, for (size_t t = 0; t < static_cast(num_threads); t++) { workers->push_back(std::thread([&, t]() { auto start_idx = (t + 0) * chunk_size; - auto end_idx = std::min((t + 1) * chunk_size, len - 1); + auto end_idx = (std::min)((t + 1) * chunk_size, len - 1); if (t == static_cast((num_threads - 1))) { end_idx = len - 1; } @@ -1325,7 +1325,7 @@ bool parseObj(attrib_t *attrib, std::vector *shapes, // Find extra line which spand across chunk boundary. if ((t < num_threads) && (buf[end_idx - 1] != '\n')) { - auto extra_span_idx = std::min(end_idx - 1 + chunk_size, len - 1); + auto extra_span_idx = (std::min)(end_idx - 1 + chunk_size, len - 1); for (size_t i = end_idx; i < extra_span_idx; i++) { if (is_line_ending(buf, i, extra_span_idx)) { LineInfo info; diff --git a/experimental/viewer.cc b/experimental/viewer.cc index 87db22b..3555b54 100644 --- a/experimental/viewer.cc +++ b/experimental/viewer.cc @@ -90,16 +90,24 @@ void CalcNormal(float N[3], float v0[3], float v1[3], float v2[3]) { const char *mmap_file(size_t *len, const char* filename) { (*len) = 0; -#ifdef _WIN64 +#ifdef _WIN32 HANDLE file = CreateFileA(filename, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, NULL); - assert(file != INVALID_HANDLE_VALUE); + assert(file != INVALID_HANDLE_VALUE); - HANDLE fileMapping = CreateFileMapping(file, NULL, PAGE_READONLY, 0, 0, NULL); - assert(fileMapping != INVALID_HANDLE_VALUE); + HANDLE fileMapping = CreateFileMapping(file, NULL, PAGE_READONLY, 0, 0, NULL); + assert(fileMapping != INVALID_HANDLE_VALUE); - LPVOID fileMapView = MapViewOfFile(fileMapping, FILE_MAP_READ, 0, 0, 0); - auto fileMapViewChar = (const char*)fileMapView; - assert(fileMapView != NULL); + LPVOID fileMapView = MapViewOfFile(fileMapping, FILE_MAP_READ, 0, 0, 0); + auto fileMapViewChar = (const char*)fileMapView; + assert(fileMapView != NULL); + + LARGE_INTEGER fileSize; + fileSize.QuadPart = 0; + GetFileSizeEx(file, &fileSize); + + (*len) = static_cast(fileSize.QuadPart); + return fileMapViewChar; + #else FILE* f = fopen(filename, "r" ); @@ -324,6 +332,10 @@ bool LoadObjAndConvert(float bmin[3], float bmax[3], const char* filename, int n option.verbose = verbose; bool ret = parseObj(&attrib, &shapes, &materials, data, data_len, option); + if (!ret) { + std::cerr << "Failed to parse .obj" << std::endl; + return false; + } bmin[0] = bmin[1] = bmin[2] = std::numeric_limits::max(); bmax[0] = bmax[1] = bmax[2] = -std::numeric_limits::max(); -- cgit v1.2.3 From ad96ff0769930f7be7989fda7aa62c0c87e17155 Mon Sep 17 00:00:00 2001 From: Jamie Snape Date: Tue, 20 Dec 2016 09:32:41 -0500 Subject: Consistently use spaces instead of tabs in CMake files --- CMakeLists.txt | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 0ebd0af..93d62e3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,19 +10,19 @@ set(TINYOBJLOADER_VERSION 1.0.4) set(TINYOBJLOADEREXAMPLES_DIR ${CMAKE_CURRENT_SOURCE_DIR}/examples) set(tinyobjloader-Source - ${CMAKE_CURRENT_SOURCE_DIR}/tiny_obj_loader.h - ${CMAKE_CURRENT_SOURCE_DIR}/tiny_obj_loader.cc - ) + ${CMAKE_CURRENT_SOURCE_DIR}/tiny_obj_loader.h + ${CMAKE_CURRENT_SOURCE_DIR}/tiny_obj_loader.cc + ) set(tinyobjloader-Example-Source - ${CMAKE_CURRENT_SOURCE_DIR}/loader_example.cc - ) + ${CMAKE_CURRENT_SOURCE_DIR}/loader_example.cc + ) set(tinyobjloader-examples-objsticher - ${TINYOBJLOADEREXAMPLES_DIR}/obj_sticher/obj_writer.h - ${TINYOBJLOADEREXAMPLES_DIR}/obj_sticher/obj_writer.cc - ${TINYOBJLOADEREXAMPLES_DIR}/obj_sticher/obj_sticher.cc - ) + ${TINYOBJLOADEREXAMPLES_DIR}/obj_sticher/obj_writer.h + ${TINYOBJLOADEREXAMPLES_DIR}/obj_sticher/obj_writer.cc + ${TINYOBJLOADEREXAMPLES_DIR}/obj_sticher/obj_sticher.cc + ) #Install destinations set(TINYOBJLOADER_CMAKE_DIR lib/cmake) @@ -34,20 +34,20 @@ option(TINYOBJLOADER_BUILD_TEST_LOADER "Build Example Loader Application" OFF) option(TINYOBJLOADER_COMPILATION_SHARED "Build as shared library" OFF) if (TINYOBJLOADER_COMPILATION_SHARED) - add_library(tinyobjloader SHARED ${tinyobjloader-Source}) - set_target_properties(tinyobjloader PROPERTIES - SOVERSION ${TINYOBJLOADER_SOVERSION} - ) + add_library(tinyobjloader SHARED ${tinyobjloader-Source}) + set_target_properties(tinyobjloader PROPERTIES + SOVERSION ${TINYOBJLOADER_SOVERSION} + ) else() - add_library(tinyobjloader STATIC ${tinyobjloader-Source}) + add_library(tinyobjloader STATIC ${tinyobjloader-Source}) endif() set_target_properties(tinyobjloader PROPERTIES VERSION ${TINYOBJLOADER_VERSION}) target_include_directories(tinyobjloader INTERFACE - $ - $ - ) + $ + $ + ) export(TARGETS tinyobjloader FILE ${PROJECT_NAME}-targets.cmake) -- cgit v1.2.3 From 2019ace3b7e731936b122d8376d9f420b35e9ec7 Mon Sep 17 00:00:00 2001 From: Jamie Snape Date: Tue, 20 Dec 2016 09:32:41 -0500 Subject: Remove excess padding around some parentheses in CMake files --- CMakeLists.txt | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 93d62e3..e702ce7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -33,7 +33,7 @@ set(TINYOBJLOADER_PKGCONFIG_DIR lib/pkgconfig) option(TINYOBJLOADER_BUILD_TEST_LOADER "Build Example Loader Application" OFF) option(TINYOBJLOADER_COMPILATION_SHARED "Build as shared library" OFF) -if (TINYOBJLOADER_COMPILATION_SHARED) +if(TINYOBJLOADER_COMPILATION_SHARED) add_library(tinyobjloader SHARED ${tinyobjloader-Source}) set_target_properties(tinyobjloader PROPERTIES SOVERSION ${TINYOBJLOADER_SOVERSION} @@ -57,11 +57,11 @@ if(TINYOBJLOADER_BUILD_TEST_LOADER) endif() option(TINYOBJLOADER_BUILD_OBJ_STICHER "Build OBJ Sticher Application" OFF) -if (TINYOBJLOADER_BUILD_OBJ_STICHER) +if(TINYOBJLOADER_BUILD_OBJ_STICHER) add_executable(obj_sticher ${tinyobjloader-examples-objsticher}) target_link_libraries(obj_sticher tinyobjloader) - install ( TARGETS + install(TARGETS obj_sticher DESTINATION bin @@ -93,7 +93,7 @@ write_basic_package_version_file(tinyobjloader-config-version.cmake configure_file(tinyobjloader.pc.in tinyobjloader.pc @ONLY) #Installation -install ( TARGETS +install(TARGETS tinyobjloader EXPORT ${PROJECT_NAME}-targets DESTINATION @@ -103,23 +103,23 @@ install ( TARGETS RUNTIME DESTINATION bin ) -install ( EXPORT +install(EXPORT ${PROJECT_NAME}-targets DESTINATION ${TINYOBJLOADER_LIBRARY_DIR} ) -install ( FILES +install(FILES tiny_obj_loader.h DESTINATION ${TINYOBJLOADER_INCLUDE_DIR} ) -install ( FILES +install(FILES "${CMAKE_CURRENT_BINARY_DIR}/tinyobjloader-config.cmake" "${CMAKE_CURRENT_BINARY_DIR}/tinyobjloader-config-version.cmake" DESTINATION ${TINYOBJLOADER_CMAKE_DIR} ) -install ( FILES +install(FILES "${CMAKE_CURRENT_BINARY_DIR}/tinyobjloader.pc" DESTINATION ${TINYOBJLOADER_PKGCONFIG_DIR} -- cgit v1.2.3 From 38c07d34c45f789461e9fc746a9ae3e51df86091 Mon Sep 17 00:00:00 2001 From: Jamie Snape Date: Tue, 20 Dec 2016 09:44:26 -0500 Subject: Fix typo in destination of CMake export file --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index e702ce7..e9ead11 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -106,7 +106,7 @@ install(TARGETS install(EXPORT ${PROJECT_NAME}-targets DESTINATION - ${TINYOBJLOADER_LIBRARY_DIR} + ${TINYOBJLOADER_CMAKE_DIR} ) install(FILES tiny_obj_loader.h -- cgit v1.2.3 From bbcfe59c0f38362985fb8aec9288a94d34410392 Mon Sep 17 00:00:00 2001 From: Jamie Snape Date: Tue, 20 Dec 2016 09:44:47 -0500 Subject: Update minimum required version of CMake --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index e9ead11..d8cbea5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,7 +2,7 @@ #This configures the Cmake system with multiple properties, depending #on the platform and configuration it is set to build in. project(tinyobjloader) -cmake_minimum_required(VERSION 2.8.6) +cmake_minimum_required(VERSION 2.8.11) set(TINYOBJLOADER_SOVERSION 1) set(TINYOBJLOADER_VERSION 1.0.4) -- cgit v1.2.3 From 0fe1bb96c2a12141d0b53db0a45a2e5b95b416ec Mon Sep 17 00:00:00 2001 From: Jamie Snape Date: Wed, 28 Dec 2016 11:10:07 -0500 Subject: Add MIT license file --- LICENSE | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..707594d --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2012-2016 Syoyo Fujita and many contributors. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. -- cgit v1.2.3 From 7212ee47eba3b6affe271f25fdf0602d3e57dff1 Mon Sep 17 00:00:00 2001 From: Jamie Snape Date: Wed, 28 Dec 2016 11:11:59 -0500 Subject: Use GNUInstallDirs module and fix cmake directory location --- CMakeLists.txt | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index d8cbea5..2b4e06a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -25,10 +25,14 @@ set(tinyobjloader-examples-objsticher ) #Install destinations -set(TINYOBJLOADER_CMAKE_DIR lib/cmake) -set(TINYOBJLOADER_INCLUDE_DIR include) -set(TINYOBJLOADER_LIBRARY_DIR lib) -set(TINYOBJLOADER_PKGCONFIG_DIR lib/pkgconfig) +include(GNUInstallDirs) + +set(TINYOBJLOADER_CMAKE_DIR ${CMAKE_INSTALL_LIBDIR}/${PROJECT_NAME}/cmake) +set(TINYOBJLOADER_DOC_DIR ${CMAKE_INSTALL_DOCDIR}) +set(TINYOBJLOADER_INCLUDE_DIR ${CMAKE_INSTALL_INCLUDEDIR}) +set(TINYOBJLOADER_LIBRARY_DIR ${CMAKE_INSTALL_LIBDIR}) +set(TINYOBJLOADER_PKGCONFIG_DIR ${CMAKE_INSTALL_LIBDIR}/pkgconfig) +set(TINYOBJLOADER_RUNTIME_DIR ${CMAKE_INSTALL_BINDIR}) option(TINYOBJLOADER_BUILD_TEST_LOADER "Build Example Loader Application" OFF) option(TINYOBJLOADER_COMPILATION_SHARED "Build as shared library" OFF) @@ -64,7 +68,7 @@ if(TINYOBJLOADER_BUILD_OBJ_STICHER) install(TARGETS obj_sticher DESTINATION - bin + ${TINYOBJLOADER_RUNTIME_DIR} ) endif() @@ -101,7 +105,7 @@ install(TARGETS PUBLIC_HEADER DESTINATION ${TINYOBJLOADER_INCLUDE_DIR} RUNTIME DESTINATION - bin + ${TINYOBJLOADER_RUNTIME_DIR} ) install(EXPORT ${PROJECT_NAME}-targets @@ -113,6 +117,11 @@ install(FILES DESTINATION ${TINYOBJLOADER_INCLUDE_DIR} ) +install(FILES + LICENSE + DESTINATION + ${TINYOBJLOADER_DOC_DIR} + ) install(FILES "${CMAKE_CURRENT_BINARY_DIR}/tinyobjloader-config.cmake" "${CMAKE_CURRENT_BINARY_DIR}/tinyobjloader-config-version.cmake" -- cgit v1.2.3 From 91c727e204060d7fdfaea65e8e2937ee09a391ca Mon Sep 17 00:00:00 2001 From: dPavelDev Date: Thu, 29 Dec 2016 15:45:23 +0300 Subject: Fixed error in getting material name in LoadObjWithCallback --- tiny_obj_loader.h | 1 - 1 file changed, 1 deletion(-) diff --git a/tiny_obj_loader.h b/tiny_obj_loader.h index 6947bbd..ae744a3 100644 --- a/tiny_obj_loader.h +++ b/tiny_obj_loader.h @@ -1805,7 +1805,6 @@ bool LoadObjWithCallback(std::istream &inStream, const callback_t &callback, if ((0 == strncmp(token, "mtllib", 6)) && IS_SPACE((token[6]))) { if (readMatFn) { token += 7; - token += 7; std::vector filenames; SplitString(std::string(token), ' ', filenames); -- cgit v1.2.3 From 7c3206f9193ca259b73fec6556cdcab45c1f5723 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Sat, 31 Dec 2016 21:05:37 +0900 Subject: Ignore `Tr` parameter when `d` exists in MTL(#43) --- loader_example.cc | 10 +- models/tr-and-d-issue-43.mtl | 13 + models/tr-and-d-issue-43.obj | 817 +++++++++++++++++++++++++++++++++++++++++++ tests/tester.cc | 21 +- tiny_obj_loader.h | 127 +++++-- 5 files changed, 948 insertions(+), 40 deletions(-) create mode 100644 models/tr-and-d-issue-43.mtl create mode 100644 models/tr-and-d-issue-43.obj diff --git a/loader_example.cc b/loader_example.cc index c4e2b0b..203fbf8 100644 --- a/loader_example.cc +++ b/loader_example.cc @@ -364,8 +364,14 @@ static bool TestStreamLoadObj() { std::map* matMap, std::string* err) { (void)matId; - (void)err; - LoadMtl(matMap, materials, &m_matSStream); + std::string warning; + LoadMtl(matMap, materials, &m_matSStream, &warning); + + if (!warning.empty()) { + if (err) { + (*err) += warning; + } + } return true; } diff --git a/models/tr-and-d-issue-43.mtl b/models/tr-and-d-issue-43.mtl new file mode 100644 index 0000000..44fd7ac --- /dev/null +++ b/models/tr-and-d-issue-43.mtl @@ -0,0 +1,13 @@ +newmtl Material.001 +Ka 0 0 0 +Kd 0 0 0 +Ks 0 0 0 +d 0.75 +Tr 0.5 + +newmtl Material.002 +Ka 0 0 0 +Kd 0 0 0 +Ks 0 0 0 +Tr 0.5 +d 0.75 diff --git a/models/tr-and-d-issue-43.obj b/models/tr-and-d-issue-43.obj new file mode 100644 index 0000000..d86c3cf --- /dev/null +++ b/models/tr-and-d-issue-43.obj @@ -0,0 +1,817 @@ +# https://github.com/syoyo/tinyobjloader/issues/68 +# Blender v2.73 (sub 0) OBJ File: 'enemy.blend' +# www.blender.org +mtllib tr-and-d-issue-43.mtl +o Cube +v 1.864151 -1.219172 -5.532511 +v 0.575869 -0.666304 5.896140 +v 0.940448 1.000000 -1.971128 +v 1.620345 1.000000 -5.815706 +v 1.864152 1.000000 -6.334323 +v 0.575869 -0.129842 5.896143 +v 5.440438 -1.462153 -5.818601 +v 4.896782 -1.462153 -2.744413 +v 1.000825 -0.677484 1.899605 +v 5.440438 -1.246362 -5.818600 +v 1.000825 0.852342 1.899608 +v 4.896782 -1.246362 -2.744412 +v 1.160660 -0.450871 -2.356325 +v 1.704316 -0.450871 -5.430513 +v 1.000825 -0.351920 -1.293797 +v 1.000825 1.000000 -1.293794 +v 1.160660 -0.877888 -2.356326 +v 1.704316 -0.877888 -5.430514 +v 1.000825 -1.219172 -1.452514 +v 1.000825 1.000000 -1.452511 +v 1.000825 -0.351920 1.759410 +v 1.000825 1.000000 1.759413 +v 9.097919 1.221145 -6.212147 +v 8.356775 1.221145 -2.021231 +v 1.864151 -0.109586 -6.334325 +v 0.575869 -0.398073 5.896141 +v 9.097919 0.943958 -6.212148 +v 8.356775 0.943958 -2.021233 +v 1.061916 0.113661 -1.797961 +v 1.000825 0.161258 1.899606 +v 1.000825 0.324040 -1.293795 +v 1.803060 0.113661 -5.988876 +v 1.000825 -0.109586 -1.452513 +v 1.061916 0.776753 -1.797960 +v 1.803061 0.776753 -5.988875 +v 1.000825 0.324040 1.759412 +v 0.000825 -1.219172 -5.532512 +v 0.000825 -0.666304 5.896139 +v 0.000826 1.000000 -6.334325 +v 0.000825 -0.129842 5.896140 +v 0.000825 0.852342 1.899606 +v 0.000825 -0.677484 1.899604 +v 0.000825 -0.351920 -1.293797 +v 0.000825 1.000000 -1.293796 +v 0.000825 1.000000 -1.452513 +v 0.000825 -1.219172 -1.452515 +v 0.000825 -0.351920 1.759409 +v 0.000825 1.000000 1.759411 +v 0.000826 -0.109586 -6.334326 +v 0.000825 -0.398073 5.896140 +v 0.152918 1.000000 -5.815708 +v 0.152917 1.000000 -1.971130 +v 0.940448 1.168419 -1.971128 +v 1.620345 1.168419 -5.815706 +v 0.152918 1.168419 -5.815708 +v 0.152917 1.168419 -1.971130 +v 0.921118 1.091883 -1.050430 +v 0.921118 1.091883 1.516050 +v 0.080533 1.091883 -1.050432 +v 0.080533 1.091883 1.516048 +v 0.613003 -0.553430 5.546911 +v 0.963691 -0.559956 2.248834 +v 0.613003 -0.396857 5.546912 +v 0.963691 -0.070362 2.248835 +v 1.499370 -0.994317 3.966028 +v 1.850058 -0.997914 0.667950 +v 1.499370 -0.908021 3.966029 +v 1.850058 -0.728071 0.667951 +v 1.601022 0.760960 -6.334324 +v 1.601021 0.129454 -6.334325 +v 0.263955 0.760960 -6.334325 +v 0.263955 0.129454 -6.334325 +v 1.334809 0.760960 -7.515329 +v 1.334809 0.129455 -7.515330 +v 0.530168 0.760960 -7.515330 +v 0.530168 0.129455 -7.515330 +v 1.192720 0.649445 -7.515329 +v 1.192720 0.240971 -7.515330 +v 0.672258 0.649445 -7.515330 +v 0.672258 0.240971 -7.515330 +v 1.192719 0.649444 -6.524630 +v 1.192719 0.240970 -6.524631 +v 0.672257 0.649444 -6.524631 +v 0.672257 0.240970 -6.524631 +v 3.851026 0.431116 -1.883326 +v 3.851026 0.946662 -1.883325 +v 4.592170 0.946662 -6.074241 +v 4.592169 0.431116 -6.074242 +v 4.995714 0.561404 -1.918362 +v 4.995714 1.016394 -1.918360 +v 5.736857 1.016394 -6.109276 +v 5.736857 0.561404 -6.109277 +v 3.975454 0.471731 -2.162156 +v 3.975454 0.919244 -2.162155 +v 4.618796 0.919244 -5.800034 +v 4.618795 0.471730 -5.800035 +v 4.969088 0.584825 -2.192568 +v 4.969088 0.979775 -2.192567 +v 5.612430 0.979775 -5.830446 +v 5.612429 0.584825 -5.830447 +v 0.864214 -0.673890 3.184381 +v 0.864213 0.489129 3.184384 +v 0.864213 -0.018552 3.184383 +v 0.000825 0.489129 3.184382 +v 0.000825 -0.673890 3.184381 +v 0.850955 -0.557858 3.309075 +v 0.850955 -0.175321 3.309076 +v 1.737321 -0.996758 1.728192 +v 1.737321 -0.785920 1.728193 +v -1.864151 -1.219172 -5.532511 +v -0.575869 -0.666304 5.896140 +v -0.940448 1.000000 -1.971128 +v -1.620345 1.000000 -5.815706 +v -1.864152 1.000000 -6.334323 +v -0.575869 -0.129842 5.896143 +v -5.440438 -1.462153 -5.818601 +v -4.896782 -1.462153 -2.744413 +v -1.000825 -0.677484 1.899605 +v -5.440438 -1.246362 -5.818600 +v -1.000825 0.852342 1.899608 +v -4.896782 -1.246362 -2.744412 +v -1.160660 -0.450871 -2.356325 +v -1.704316 -0.450871 -5.430513 +v -1.000825 -0.351920 -1.293797 +v -1.000825 1.000000 -1.293794 +v -1.160660 -0.877888 -2.356326 +v -1.704316 -0.877888 -5.430514 +v -1.000825 -1.219172 -1.452514 +v -1.000825 1.000000 -1.452511 +v -1.000825 -0.351920 1.759410 +v -1.000825 1.000000 1.759413 +v -9.097919 1.221145 -6.212147 +v -8.356775 1.221145 -2.021231 +v -1.864151 -0.109586 -6.334325 +v -0.575869 -0.398073 5.896141 +v -9.097919 0.943958 -6.212148 +v -8.356775 0.943958 -2.021233 +v -1.061916 0.113661 -1.797961 +v -1.000825 0.161258 1.899606 +v -1.000825 0.324040 -1.293795 +v -1.803060 0.113661 -5.988876 +v -1.000825 -0.109586 -1.452513 +v -1.061916 0.776753 -1.797960 +v -1.803061 0.776753 -5.988875 +v -1.000825 0.324040 1.759412 +v -0.000825 -1.219172 -5.532512 +v -0.000825 -0.666304 5.896139 +v -0.000826 1.000000 -6.334325 +v -0.000825 -0.129842 5.896140 +v -0.000825 0.852342 1.899606 +v -0.000825 -0.677484 1.899604 +v -0.000825 -0.351920 -1.293797 +v -0.000825 1.000000 -1.293796 +v -0.000825 1.000000 -1.452513 +v -0.000825 -1.219172 -1.452515 +v -0.000825 -0.351920 1.759409 +v -0.000825 1.000000 1.759411 +v -0.000826 -0.109586 -6.334326 +v -0.000825 -0.398073 5.896140 +v -0.152918 1.000000 -5.815708 +v -0.152917 1.000000 -1.971130 +v -0.940448 1.168419 -1.971128 +v -1.620345 1.168419 -5.815706 +v -0.152918 1.168419 -5.815708 +v -0.152917 1.168419 -1.971130 +v -0.921118 1.091883 -1.050430 +v -0.921118 1.091883 1.516050 +v -0.080533 1.091883 -1.050432 +v -0.080533 1.091883 1.516048 +v -0.613003 -0.553430 5.546911 +v -0.963691 -0.559956 2.248834 +v -0.613003 -0.396857 5.546912 +v -0.963691 -0.070362 2.248835 +v -1.499370 -0.994317 3.966028 +v -1.850058 -0.997914 0.667950 +v -1.499370 -0.908021 3.966029 +v -1.850058 -0.728071 0.667951 +v -1.601022 0.760960 -6.334324 +v -1.601021 0.129454 -6.334325 +v -0.263955 0.760960 -6.334325 +v -0.263955 0.129454 -6.334325 +v -1.334809 0.760960 -7.515329 +v -1.334809 0.129455 -7.515330 +v -0.530168 0.760960 -7.515330 +v -0.530168 0.129455 -7.515330 +v -1.192720 0.649445 -7.515329 +v -1.192720 0.240971 -7.515330 +v -0.672258 0.649445 -7.515330 +v -0.672258 0.240971 -7.515330 +v -1.192719 0.649444 -6.524630 +v -1.192719 0.240970 -6.524631 +v -0.672257 0.649444 -6.524631 +v -0.672257 0.240970 -6.524631 +v -3.851026 0.431116 -1.883326 +v -3.851026 0.946662 -1.883325 +v -4.592170 0.946662 -6.074241 +v -4.592169 0.431116 -6.074242 +v -4.995714 0.561404 -1.918362 +v -4.995714 1.016394 -1.918360 +v -5.736857 1.016394 -6.109276 +v -5.736857 0.561404 -6.109277 +v -3.975454 0.471731 -2.162156 +v -3.975454 0.919244 -2.162155 +v -4.618796 0.919244 -5.800034 +v -4.618795 0.471730 -5.800035 +v -4.969088 0.584825 -2.192568 +v -4.969088 0.979775 -2.192567 +v -5.612430 0.979775 -5.830446 +v -5.612429 0.584825 -5.830447 +v -0.864214 -0.673890 3.184381 +v -0.864213 0.489129 3.184384 +v -0.864213 -0.018552 3.184383 +v -0.000825 0.489129 3.184382 +v -0.000825 -0.673890 3.184381 +v -0.850955 -0.557858 3.309075 +v -0.850955 -0.175321 3.309076 +v -1.737321 -0.996758 1.728192 +v -1.737321 -0.785920 1.728193 +vt 0.135351 -0.558072 +vt 0.003035 -0.363507 +vt 0.092282 -0.976844 +vt -0.081322 0.947351 +vt 0.100058 1.958891 +vt 0.050091 1.852185 +vt -0.092752 1.055565 +vt -0.251711 1.059474 +vt 0.075587 0.041384 +vt -0.086008 0.279003 +vt -0.086212 0.249830 +vt -0.276044 1.968137 +vt -0.246101 1.859467 +vt 0.009828 1.911388 +vt -0.133014 1.114769 +vt 0.413322 1.261595 +vt 0.299103 0.624605 +vt 1.243955 0.407183 +vt 0.515404 1.111487 +vt 1.358173 1.044173 +vt -0.081553 0.914324 +vt 0.080042 0.676706 +vt 0.401185 0.474498 +vt 1.295541 0.331328 +vt 0.365315 1.568841 +vt 0.299111 1.575740 +vt 0.143401 0.707357 +vt 0.629403 1.011947 +vt 0.449192 0.167251 +vt 1.409760 0.968317 +vt 0.986264 1.738667 +vt 1.573373 1.877873 +vt 1.417663 1.009490 +vt 0.237182 -0.196235 +vt 0.721785 1.030226 +vt 0.830554 0.870285 +vt 0.877494 1.898608 +vt 1.351399 1.106930 +vt 0.183935 0.557301 +vt 1.507109 1.975312 +vt 0.241636 0.439088 +vt 0.114297 -0.045011 +vt 0.140593 1.808834 +vt -0.015118 0.940452 +vt 0.156405 -1.071134 +vt 0.164119 -0.998223 +vt 0.040336 -1.068281 +vt 0.104459 -1.162571 +vt -0.165787 1.882802 +vt -0.014821 1.660811 +vt -0.287852 0.283965 +vt -0.293374 0.366508 +vt -0.289630 0.900550 +vt 0.035337 -0.191272 +vt 0.247348 0.172213 +vt 0.253300 1.021193 +vt -0.283166 0.952313 +vt -0.283398 0.919286 +vt 0.039792 0.444050 +vt 0.314806 -0.339851 +vt 0.112962 -0.334889 +vt -0.288056 0.254793 +vt -0.023788 -0.973990 +vt -0.155922 -0.359599 +vt 0.220528 -1.165425 +vt 0.108710 -0.748730 +vt -0.286364 1.918670 +vt -0.291973 1.118678 +vt -0.119962 0.896379 +vt -0.123707 0.362337 +vt 0.162891 -0.598569 +vt 0.467532 -0.853353 +vt 0.201549 -1.053262 +vt 0.161663 -0.198915 +vt 0.267667 -0.752638 +vt 0.278705 -0.371021 +vt 0.526390 -0.542053 +vt 0.483821 -0.479457 +vt 0.488162 -0.883689 +vt 0.500110 -0.105561 +vt 0.564618 -0.200418 +vt -0.110331 2.127229 +vt 0.040636 1.905238 +vt -0.010786 1.578087 +vt 0.104092 1.876168 +vt 0.255058 1.654176 +vt -0.054992 2.087323 +vt 0.203048 1.901245 +vt 0.052081 2.123235 +vt 0.042658 1.943733 +vt -0.056437 1.881175 +vt 0.147710 1.941151 +vt 0.050060 2.084741 +vt 0.146264 1.735002 +vt 0.041212 1.737584 +vt 0.048615 1.878591 +vt 0.663065 1.872485 +vt 0.786311 1.691257 +vt 0.507355 1.004102 +vt 0.630601 0.822874 +vt 0.955144 1.689498 +vt 0.860727 1.828333 +vt 0.725565 1.074543 +vt 0.819981 0.935708 +vt 0.674594 1.805657 +vt 0.539432 1.051867 +vt 0.646413 0.894554 +vt 0.781576 1.648344 +vt 0.240127 -0.712141 +vn 0.994400 0.000000 0.105700 +vn 0.000000 1.000000 0.000000 +vn 1.000000 0.000000 0.000000 +vn 0.984700 0.000000 0.174100 +vn 0.211800 0.976600 0.037500 +vn -0.103300 0.000000 -0.994600 +vn 0.103300 -0.000000 0.994600 +vn 0.911400 0.378700 0.161200 +vn -0.157300 -0.987200 -0.027800 +vn 0.113700 -0.993300 0.020100 +vn 0.030600 -0.000000 0.999500 +vn -0.061100 0.998100 -0.010800 +vn -0.030600 0.000000 -0.999500 +vn -0.000000 -0.000000 1.000000 +vn 0.000000 0.000000 -1.000000 +vn -0.755400 0.655300 0.000000 +vn 0.000000 -1.000000 0.000000 +vn -0.000000 -0.180000 0.983700 +vn 0.000000 -0.395500 -0.918500 +vn -0.000000 0.688500 0.725200 +vn 0.000000 -0.585700 -0.810500 +vn -0.000000 0.974900 0.222500 +vn -0.000000 -1.000000 0.002800 +vn -1.000000 0.000000 -0.000000 +vn -0.000000 0.935500 0.353200 +vn 0.755400 0.655300 0.000000 +vn 0.000000 0.935500 -0.353200 +vn 0.673800 0.724900 0.143400 +vn 0.872300 -0.000000 0.489100 +vn -0.872300 0.000000 -0.489100 +vn -0.518300 -0.853500 -0.054200 +vn -0.975500 0.000000 -0.219900 +vn 0.975500 0.000000 -0.219900 +vn -0.913200 0.000000 -0.407500 +vn -0.436900 0.896200 -0.077300 +vn -0.995300 -0.000000 0.096600 +vn -0.297300 -0.953400 -0.052600 +vn 0.473900 -0.876600 0.083800 +vn 0.913200 0.000000 0.407500 +vn 0.342200 0.937700 0.060500 +vn 0.995300 -0.000000 -0.096600 +vn -0.519200 -0.853000 -0.054300 +vn 0.722400 0.676400 0.143800 +vn -0.994400 0.000000 0.105700 +vn -0.984700 0.000000 0.174100 +vn -0.211800 0.976600 0.037500 +vn 0.103300 0.000000 -0.994600 +vn -0.103300 -0.000000 0.994600 +vn -0.911400 0.378700 0.161200 +vn 0.157300 -0.987200 -0.027800 +vn -0.113700 -0.993300 0.020100 +vn -0.030600 -0.000000 0.999500 +vn 0.061100 0.998100 -0.010800 +vn 0.030600 0.000000 -0.999500 +vn -0.691900 0.713200 0.112500 +vn -0.872300 -0.000000 0.489100 +vn 0.872300 0.000000 -0.489100 +vn 0.518300 -0.853500 -0.054200 +vn 0.913200 0.000000 -0.407500 +vn 0.436900 0.896200 -0.077300 +vn 0.995300 0.000000 0.096600 +vn 0.297300 -0.953300 -0.052600 +vn -0.473900 -0.876600 0.083800 +vn -0.913200 -0.000000 0.407500 +vn -0.342200 0.937700 0.060500 +vn -0.995300 -0.000000 -0.096600 +vn 0.519200 -0.853000 -0.054300 +vn -0.714800 0.690100 0.113700 +vn 0.974400 0.089700 0.206200 +vn 0.870400 0.288400 0.399100 +vn 0.691900 0.713200 0.112500 +vn -0.518000 -0.853700 -0.053400 +vn -0.519700 -0.852700 -0.053600 +vn 0.714800 0.690100 0.113700 +vn -0.974400 0.089700 0.206200 +vn -0.870400 0.288400 0.399100 +vn -0.673800 0.724900 0.143400 +vn 0.518000 -0.853700 -0.053400 +vn 0.297300 -0.953400 -0.052600 +vn 0.519700 -0.852700 -0.053600 +vn -0.722400 0.676400 0.143800 +vn -0.000000 0.962300 0.272000 +usemtl Material.001 +s off +f 103/1/1 102/2/1 6/3/1 +f 20/4/2 5/5/2 4/6/2 +f 20/4/2 3/7/2 52/8/2 +f 36/9/3 22/10/3 11/11/3 +f 39/12/2 51/13/2 4/6/2 +f 4/6/4 54/14/4 53/15/4 +f 14/16/5 13/17/5 12/18/5 +f 18/19/6 14/16/6 10/20/6 +f 20/4/3 16/21/3 31/22/3 +f 17/23/7 8/24/7 12/18/7 +f 25/25/4 32/26/4 29/27/4 +f 10/20/4 12/18/4 8/24/4 +f 1/28/8 18/19/8 17/23/8 +f 19/29/4 17/23/4 13/17/4 +f 25/25/4 14/16/4 18/19/4 +f 18/19/9 7/30/9 8/24/9 +f 92/31/10 27/32/10 28/33/10 +f 16/21/3 22/10/3 36/9/3 +f 31/22/3 36/9/3 21/34/3 +f 90/35/11 89/36/11 28/33/11 +f 91/37/12 90/35/12 24/38/12 +f 33/39/4 13/17/4 14/16/4 +f 23/40/4 24/38/4 28/33/4 +f 33/39/3 31/22/3 15/41/3 +f 21/34/3 36/9/3 30/42/3 +f 5/5/4 35/43/4 32/26/4 +f 5/5/4 20/4/4 34/44/4 +f 33/39/4 29/27/4 34/44/4 +f 91/37/13 23/40/13 27/32/13 +f 103/1/1 26/45/1 63/46/1 +f 26/45/14 50/47/14 38/48/14 +f 39/12/15 71/49/15 72/50/15 +f 48/51/16 60/52/16 59/53/16 +f 15/41/17 21/34/17 47/54/17 +f 19/29/17 46/55/17 37/56/17 +f 39/12/2 45/57/2 52/8/2 +f 20/4/2 45/57/2 44/58/2 +f 19/29/18 15/41/18 43/59/18 +f 9/60/19 42/61/19 47/54/19 +f 22/10/20 48/51/20 41/62/20 +f 25/25/21 1/28/21 37/56/21 +f 6/3/14 40/63/14 50/47/14 +f 104/64/22 40/63/22 6/3/22 +f 2/65/23 38/48/23 105/66/23 +f 55/67/2 56/68/2 53/15/2 +f 3/7/14 53/15/14 56/68/14 +f 51/13/15 55/67/15 54/14/15 +f 52/8/24 56/68/24 55/67/24 +f 57/69/2 59/53/2 60/52/2 +f 48/51/25 22/10/25 58/70/25 +f 16/21/26 57/69/26 58/70/26 +f 16/21/27 44/58/27 59/53/27 +f 107/71/28 63/46/28 67/72/28 +f 26/45/1 2/65/1 61/73/1 +f 9/60/1 30/42/1 64/74/1 +f 101/75/1 9/60/1 62/76/1 +f 108/77/1 109/78/1 67/72/1 +f 61/73/29 65/79/29 67/72/29 +f 62/76/30 64/74/30 68/80/30 +f 62/76/31 66/81/31 108/77/31 +f 71/49/32 75/82/32 76/83/32 +f 25/25/15 49/84/15 72/50/15 +f 5/5/15 69/85/15 71/49/15 +f 25/25/15 70/86/15 69/85/15 +f 76/83/15 75/82/15 79/87/15 +f 72/50/17 76/83/17 74/88/17 +f 71/49/2 69/85/2 73/89/2 +f 70/86/33 74/88/33 73/89/33 +f 80/90/3 79/87/3 83/91/3 +f 76/83/15 80/90/15 78/92/15 +f 75/82/15 73/89/15 77/93/15 +f 74/88/15 78/92/15 77/93/15 +f 82/94/15 84/95/15 83/91/15 +f 80/90/2 84/95/2 82/94/2 +f 77/93/17 81/96/17 83/91/17 +f 77/93/24 78/92/24 82/94/24 +f 35/43/13 87/97/13 88/98/13 +f 35/43/12 34/44/12 86/99/12 +f 34/44/11 29/27/11 85/100/11 +f 32/26/10 88/98/10 85/100/10 +f 92/31/34 100/101/34 99/102/34 +f 90/35/35 91/37/35 99/102/35 +f 89/36/36 90/35/36 98/103/36 +f 89/36/37 97/104/37 100/101/37 +f 95/105/13 99/102/13 100/101/13 +f 95/105/12 94/106/12 98/103/12 +f 94/106/11 93/107/11 97/104/11 +f 96/108/10 100/101/10 97/104/10 +f 88/98/38 96/108/38 93/107/38 +f 86/99/39 85/100/39 93/107/39 +f 87/97/40 86/99/40 94/106/40 +f 87/97/41 95/105/41 96/108/41 +f 106/109/42 108/77/42 65/79/42 +f 66/81/1 68/80/1 109/78/1 +f 101/75/1 106/109/1 61/73/1 +f 64/74/43 107/71/43 109/78/43 +f 101/75/23 105/66/23 42/61/23 +f 103/1/1 107/71/1 64/74/1 +f 30/42/1 11/11/1 102/2/1 +f 212/1/44 135/45/44 115/3/44 +f 129/4/2 112/7/2 113/6/2 +f 161/8/2 112/7/2 129/4/2 +f 145/9/24 139/42/24 120/11/24 +f 113/6/2 160/13/2 148/12/2 +f 162/15/45 163/14/45 113/6/45 +f 123/16/46 119/20/46 121/18/46 +f 127/19/47 116/30/47 119/20/47 +f 140/22/24 125/21/24 129/4/24 +f 121/18/48 117/24/48 126/23/48 +f 138/27/45 141/26/45 134/25/45 +f 117/24/45 121/18/45 119/20/45 +f 126/23/49 127/19/49 110/28/49 +f 122/17/45 126/23/45 128/29/45 +f 127/19/45 123/16/45 134/25/45 +f 117/24/50 116/30/50 127/19/50 +f 137/33/51 136/32/51 201/31/51 +f 145/9/24 131/10/24 125/21/24 +f 130/34/24 145/9/24 140/22/24 +f 199/35/52 133/38/52 137/33/52 +f 200/37/53 132/40/53 133/38/53 +f 123/16/45 122/17/45 142/39/45 +f 137/33/45 133/38/45 132/40/45 +f 124/41/24 140/22/24 142/39/24 +f 130/34/24 118/60/24 139/42/24 +f 141/26/45 144/43/45 114/5/45 +f 114/5/45 144/43/45 143/44/45 +f 143/44/45 138/27/45 142/39/45 +f 136/32/54 132/40/54 200/37/54 +f 212/1/44 216/71/44 172/46/44 +f 147/48/14 159/47/14 135/45/14 +f 181/50/15 180/49/15 148/12/15 +f 168/53/26 169/52/26 157/51/26 +f 124/41/17 152/59/17 156/54/17 +f 146/56/17 155/55/17 128/29/17 +f 148/12/2 160/13/2 161/8/2 +f 129/4/2 125/21/2 153/58/2 +f 155/55/18 152/59/18 124/41/18 +f 130/34/19 156/54/19 151/61/19 +f 131/10/20 120/11/20 150/62/20 +f 134/25/21 158/84/21 146/56/21 +f 159/47/14 149/63/14 115/3/14 +f 115/3/22 149/63/22 213/64/22 +f 214/66/23 147/48/23 111/65/23 +f 162/15/2 165/68/2 164/67/2 +f 165/68/14 162/15/14 112/7/14 +f 163/14/15 164/67/15 160/13/15 +f 164/67/3 165/68/3 161/8/3 +f 166/69/2 167/70/2 169/52/2 +f 157/51/25 169/52/25 167/70/25 +f 167/70/16 166/69/16 125/21/16 +f 125/21/27 166/69/27 168/53/27 +f 216/71/55 218/78/55 176/72/55 +f 135/45/44 172/46/44 170/73/44 +f 118/60/44 171/76/44 173/74/44 +f 210/75/44 215/109/44 171/76/44 +f 217/77/44 174/79/44 176/72/44 +f 176/72/56 174/79/56 170/73/56 +f 171/76/57 175/81/57 177/80/57 +f 217/77/58 175/81/58 171/76/58 +f 185/83/33 184/82/33 180/49/33 +f 134/25/15 179/86/15 181/50/15 +f 180/49/15 178/85/15 114/5/15 +f 178/85/15 179/86/15 134/25/15 +f 189/90/15 188/87/15 184/82/15 +f 183/88/17 185/83/17 181/50/17 +f 180/49/2 184/82/2 182/89/2 +f 182/89/32 183/88/32 179/86/32 +f 189/90/24 193/95/24 192/91/24 +f 187/92/15 189/90/15 185/83/15 +f 184/82/15 188/87/15 186/93/15 +f 186/93/15 187/92/15 183/88/15 +f 192/91/15 193/95/15 191/94/15 +f 191/94/2 193/95/2 189/90/2 +f 192/91/17 190/96/17 186/93/17 +f 186/93/3 190/96/3 191/94/3 +f 197/98/54 196/97/54 144/43/54 +f 144/43/53 196/97/53 195/99/53 +f 143/44/52 195/99/52 194/100/52 +f 194/100/51 197/98/51 141/26/51 +f 208/102/59 209/101/59 201/31/59 +f 199/35/60 207/103/60 208/102/60 +f 198/36/61 206/104/61 207/103/61 +f 209/101/62 206/104/62 198/36/62 +f 209/101/54 208/102/54 204/105/54 +f 204/105/53 208/102/53 207/103/53 +f 203/106/52 207/103/52 206/104/52 +f 206/104/51 209/101/51 205/108/51 +f 202/107/63 205/108/63 197/98/63 +f 195/99/64 203/106/64 202/107/64 +f 196/97/65 204/105/65 203/106/65 +f 205/108/66 204/105/66 196/97/66 +f 174/79/67 217/77/67 215/109/67 +f 175/81/44 217/77/44 218/78/44 +f 170/73/44 215/109/44 210/75/44 +f 173/74/68 177/80/68 218/78/68 +f 151/61/23 214/66/23 210/75/23 +f 173/74/44 216/71/44 212/1/44 +f 139/42/44 212/1/44 211/2/44 +f 26/45/1 103/1/1 6/3/1 +f 3/7/2 20/4/2 4/6/2 +f 45/57/2 20/4/2 52/8/2 +f 30/42/3 36/9/3 11/11/3 +f 5/5/2 39/12/2 4/6/2 +f 3/7/4 4/6/4 53/15/4 +f 10/20/5 14/16/5 12/18/5 +f 7/30/6 18/19/6 10/20/6 +f 33/39/3 20/4/3 31/22/3 +f 13/17/7 17/23/7 12/18/7 +f 33/39/4 25/25/4 29/27/4 +f 7/30/4 10/20/4 8/24/4 +f 19/29/69 1/28/69 17/23/69 +f 33/39/4 19/29/4 13/17/4 +f 1/28/70 25/25/70 18/19/70 +f 17/23/9 18/19/9 8/24/9 +f 89/36/10 92/31/10 28/33/10 +f 31/22/3 16/21/3 36/9/3 +f 15/41/3 31/22/3 21/34/3 +f 24/38/11 90/35/11 28/33/11 +f 23/40/12 91/37/12 24/38/12 +f 25/25/4 33/39/4 14/16/4 +f 27/32/4 23/40/4 28/33/4 +f 19/29/3 33/39/3 15/41/3 +f 9/60/3 21/34/3 30/42/3 +f 25/25/4 5/5/4 32/26/4 +f 35/43/4 5/5/4 34/44/4 +f 20/4/4 33/39/4 34/44/4 +f 92/31/13 91/37/13 27/32/13 +f 107/71/1 103/1/1 63/46/1 +f 2/65/14 26/45/14 38/48/14 +f 49/84/15 39/12/15 72/50/15 +f 44/58/16 48/51/16 59/53/16 +f 43/59/17 15/41/17 47/54/17 +f 1/28/17 19/29/17 37/56/17 +f 51/13/2 39/12/2 52/8/2 +f 16/21/2 20/4/2 44/58/2 +f 46/55/18 19/29/18 43/59/18 +f 21/34/19 9/60/19 47/54/19 +f 11/11/20 22/10/20 41/62/20 +f 49/84/21 25/25/21 37/56/21 +f 26/45/14 6/3/14 50/47/14 +f 102/2/22 104/64/22 6/3/22 +f 101/75/23 2/65/23 105/66/23 +f 54/14/2 55/67/2 53/15/2 +f 52/8/14 3/7/14 56/68/14 +f 4/6/15 51/13/15 54/14/15 +f 51/13/24 52/8/24 55/67/24 +f 58/70/2 57/69/2 60/52/2 +f 60/52/25 48/51/25 58/70/25 +f 22/10/26 16/21/26 58/70/26 +f 57/69/27 16/21/27 59/53/27 +f 109/78/71 107/71/71 67/72/71 +f 63/46/1 26/45/1 61/73/1 +f 62/76/1 9/60/1 64/74/1 +f 106/109/1 101/75/1 62/76/1 +f 65/79/1 108/77/1 67/72/1 +f 63/46/29 61/73/29 67/72/29 +f 66/81/30 62/76/30 68/80/30 +f 106/109/72 62/76/72 108/77/72 +f 72/50/32 71/49/32 76/83/32 +f 70/86/15 25/25/15 72/50/15 +f 39/12/15 5/5/15 71/49/15 +f 5/5/15 25/25/15 69/85/15 +f 80/90/15 76/83/15 79/87/15 +f 70/86/17 72/50/17 74/88/17 +f 75/82/2 71/49/2 73/89/2 +f 69/85/33 70/86/33 73/89/33 +f 84/95/3 80/90/3 83/91/3 +f 74/88/15 76/83/15 78/92/15 +f 79/87/15 75/82/15 77/93/15 +f 73/89/15 74/88/15 77/93/15 +f 81/96/15 82/94/15 83/91/15 +f 78/92/2 80/90/2 82/94/2 +f 79/87/17 77/93/17 83/91/17 +f 81/96/24 77/93/24 82/94/24 +f 32/26/13 35/43/13 88/98/13 +f 87/97/12 35/43/12 86/99/12 +f 86/99/11 34/44/11 85/100/11 +f 29/27/10 32/26/10 85/100/10 +f 91/37/34 92/31/34 99/102/34 +f 98/103/35 90/35/35 99/102/35 +f 97/104/36 89/36/36 98/103/36 +f 92/31/37 89/36/37 100/101/37 +f 96/108/13 95/105/13 100/101/13 +f 99/102/12 95/105/12 98/103/12 +f 98/103/11 94/106/11 97/104/11 +f 93/107/10 96/108/10 97/104/10 +f 85/100/38 88/98/38 93/107/38 +f 94/106/39 86/99/39 93/107/39 +f 95/105/40 87/97/40 94/106/40 +f 88/98/41 87/97/41 96/108/41 +f 61/73/73 106/109/73 65/79/73 +f 108/77/1 66/81/1 109/78/1 +f 2/65/1 101/75/1 61/73/1 +f 68/80/74 64/74/74 109/78/74 +f 9/60/23 101/75/23 42/61/23 +f 30/42/1 103/1/1 64/74/1 +f 103/1/1 30/42/1 102/2/1 +f 211/2/44 212/1/44 115/3/44 +f 114/5/2 129/4/2 113/6/2 +f 154/57/2 161/8/2 129/4/2 +f 131/10/24 145/9/24 120/11/24 +f 114/5/2 113/6/2 148/12/2 +f 112/7/45 162/15/45 113/6/45 +f 122/17/46 123/16/46 121/18/46 +f 123/16/47 127/19/47 119/20/47 +f 142/39/24 140/22/24 129/4/24 +f 122/17/48 121/18/48 126/23/48 +f 142/39/45 138/27/45 134/25/45 +f 116/30/45 117/24/45 119/20/45 +f 128/29/75 126/23/75 110/28/75 +f 142/39/45 122/17/45 128/29/45 +f 110/28/76 127/19/76 134/25/76 +f 126/23/50 117/24/50 127/19/50 +f 198/36/51 137/33/51 201/31/51 +f 140/22/24 145/9/24 125/21/24 +f 124/41/24 130/34/24 140/22/24 +f 198/36/52 199/35/52 137/33/52 +f 199/35/53 200/37/53 133/38/53 +f 134/25/45 123/16/45 142/39/45 +f 136/32/45 137/33/45 132/40/45 +f 128/29/24 124/41/24 142/39/24 +f 145/9/24 130/34/24 139/42/24 +f 134/25/45 141/26/45 114/5/45 +f 129/4/45 114/5/45 143/44/45 +f 129/4/45 143/44/45 142/39/45 +f 201/31/54 136/32/54 200/37/54 +f 135/45/44 212/1/44 172/46/44 +f 111/65/14 147/48/14 135/45/14 +f 158/84/15 181/50/15 148/12/15 +f 153/58/26 168/53/26 157/51/26 +f 130/34/17 124/41/17 156/54/17 +f 110/28/17 146/56/17 128/29/17 +f 154/57/2 148/12/2 161/8/2 +f 154/57/2 129/4/2 153/58/2 +f 128/29/18 155/55/18 124/41/18 +f 118/60/19 130/34/19 151/61/19 +f 157/51/20 131/10/20 150/62/20 +f 110/28/21 134/25/21 146/56/21 +f 135/45/14 159/47/14 115/3/14 +f 211/2/22 115/3/22 213/64/22 +f 210/75/23 214/66/23 111/65/23 +f 163/14/2 162/15/2 164/67/2 +f 161/8/14 165/68/14 112/7/14 +f 113/6/15 163/14/15 160/13/15 +f 160/13/3 164/67/3 161/8/3 +f 168/53/2 166/69/2 169/52/2 +f 131/10/25 157/51/25 167/70/25 +f 131/10/16 167/70/16 125/21/16 +f 153/58/27 125/21/27 168/53/27 +f 172/46/77 216/71/77 176/72/77 +f 111/65/44 135/45/44 170/73/44 +f 139/42/44 118/60/44 173/74/44 +f 118/60/44 210/75/44 171/76/44 +f 218/78/44 217/77/44 176/72/44 +f 172/46/56 176/72/56 170/73/56 +f 173/74/57 171/76/57 177/80/57 +f 215/109/78 217/77/78 171/76/78 +f 181/50/33 185/83/33 180/49/33 +f 158/84/15 134/25/15 181/50/15 +f 148/12/15 180/49/15 114/5/15 +f 114/5/15 178/85/15 134/25/15 +f 185/83/15 189/90/15 184/82/15 +f 179/86/17 183/88/17 181/50/17 +f 178/85/2 180/49/2 182/89/2 +f 178/85/32 182/89/32 179/86/32 +f 188/87/24 189/90/24 192/91/24 +f 183/88/15 187/92/15 185/83/15 +f 182/89/15 184/82/15 186/93/15 +f 182/89/15 186/93/15 183/88/15 +f 190/96/15 192/91/15 191/94/15 +f 187/92/2 191/94/2 189/90/2 +f 188/87/17 192/91/17 186/93/17 +f 187/92/3 186/93/3 191/94/3 +f 141/26/54 197/98/54 144/43/54 +f 143/44/53 144/43/53 195/99/53 +f 138/27/52 143/44/52 194/100/52 +f 138/27/51 194/100/51 141/26/51 +f 200/37/59 208/102/59 201/31/59 +f 200/37/60 199/35/60 208/102/60 +f 199/35/61 198/36/61 207/103/61 +f 201/31/79 209/101/79 198/36/79 +f 205/108/54 209/101/54 204/105/54 +f 203/106/53 204/105/53 207/103/53 +f 202/107/52 203/106/52 206/104/52 +f 202/107/51 206/104/51 205/108/51 +f 194/100/63 202/107/63 197/98/63 +f 194/100/64 195/99/64 202/107/64 +f 195/99/65 196/97/65 203/106/65 +f 197/98/66 205/108/66 196/97/66 +f 170/73/80 174/79/80 215/109/80 +f 177/80/44 175/81/44 218/78/44 +f 111/65/44 170/73/44 210/75/44 +f 216/71/81 173/74/81 218/78/81 +f 118/60/23 151/61/23 210/75/23 +f 139/42/44 173/74/44 212/1/44 +f 120/11/44 139/42/44 211/2/44 +usemtl Material.002 +f 41/62/82 104/64/82 102/2/82 +f 211/2/82 213/64/82 150/62/82 +f 11/11/82 41/62/82 102/2/82 +f 120/11/82 211/2/82 150/62/82 diff --git a/tests/tester.cc b/tests/tester.cc index 28d3871..80736eb 100644 --- a/tests/tester.cc +++ b/tests/tester.cc @@ -307,7 +307,8 @@ std::string matStream( { (void)matId; (void)err; - LoadMtl(matMap, materials, &m_matSStream); + std::string warning; + LoadMtl(matMap, materials, &m_matSStream, &warning); return true; } @@ -545,6 +546,24 @@ TEST_CASE("mtllib_multiple_filenames", "[Issue112]") { REQUIRE(1 == materials.size()); } +TEST_CASE("tr_and_d", "[Issue43]") { + tinyobj::attrib_t attrib; + std::vector shapes; + std::vector materials; + + std::string err; + bool ret = tinyobj::LoadObj(&attrib, &shapes, &materials, &err, "../models/tr-and-d-issue-43.obj", gMtlBasePath); + + if (!err.empty()) { + std::cerr << err << std::endl; + } + REQUIRE(true == ret); + REQUIRE(2 == materials.size()); + + REQUIRE(0.75 == Approx(materials[0].dissolve)); + REQUIRE(0.75 == Approx(materials[1].dissolve)); +} + #if 0 int main( diff --git a/tiny_obj_loader.h b/tiny_obj_loader.h index ae744a3..cef0dd7 100644 --- a/tiny_obj_loader.h +++ b/tiny_obj_loader.h @@ -23,6 +23,7 @@ THE SOFTWARE. */ // +// version 1.0.5 : Ignore `Tr` when `d` exists in MTL(#43) // version 1.0.4 : Support multiple filenames for 'mtllib'(#112) // version 1.0.3 : Support parsing texture options(#85) // version 1.0.2 : Improve parsing speed by about a factor of 2 for large @@ -268,7 +269,7 @@ class MaterialReader { class MaterialFileReader : public MaterialReader { public: explicit MaterialFileReader(const std::string &mtl_basedir) - : m_mtlBaseDir(mtl_basedir) {} + : m_mtlBaseDir(mtl_basedir) {} virtual ~MaterialFileReader() {} virtual bool operator()(const std::string &matId, std::vector *materials, @@ -297,7 +298,8 @@ class MaterialStreamReader : public MaterialReader { /// Returns true when loading .obj become success. /// Returns warning and error message into `err` /// 'mtl_basedir' is optional, and used for base directory for .mtl file. -/// In default(`NULL'), .mtl file is searched from an application's working directory. +/// In default(`NULL'), .mtl file is searched from an application's working +/// directory. /// 'triangulate' is optional, and used whether triangulate polygon face in .obj /// or not. bool LoadObj(attrib_t *attrib, std::vector *shapes, @@ -327,7 +329,8 @@ bool LoadObj(attrib_t *attrib, std::vector *shapes, /// Loads materials into std::map void LoadMtl(std::map *material_map, - std::vector *materials, std::istream *inStream); + std::vector *materials, std::istream *inStream, + std::string *warning); } // namespace tinyobj @@ -938,22 +941,29 @@ static bool exportFaceGroupToShape( // Split a string with specified delimiter character. // http://stackoverflow.com/questions/236129/split-a-string-in-c -static void SplitString(const std::string &s, char delim, std::vector &elems) -{ - std::stringstream ss; - ss.str(s); - std::string item; - while (std::getline(ss, item, delim)) { - elems.push_back(item); - } +static void SplitString(const std::string &s, char delim, + std::vector &elems) { + std::stringstream ss; + ss.str(s); + std::string item; + while (std::getline(ss, item, delim)) { + elems.push_back(item); + } } void LoadMtl(std::map *material_map, - std::vector *materials, std::istream *inStream) { + std::vector *materials, std::istream *inStream, + std::string *warning) { // Create a default material anyway. material_t material; InitMaterial(&material); + // Issue 43. `d` wins against `Tr` since `Tr` is not in the MTL specification. + bool has_d = false; + bool has_tr = false; + + std::stringstream ss; + std::string linebuf; while (inStream->peek() != -1) { safeGetline(*inStream, linebuf); @@ -999,6 +1009,9 @@ void LoadMtl(std::map *material_map, // initial temporary material InitMaterial(&material); + has_d = false; + has_tr = false; + // set new mtl name char namebuf[TINYOBJ_SSCANF_BUFFER_SIZE]; token += 7; @@ -1092,12 +1105,29 @@ void LoadMtl(std::map *material_map, if ((token[0] == 'd' && IS_SPACE(token[1]))) { token += 1; material.dissolve = parseFloat(&token); + + if (has_tr) { + ss << "WARN: Both `d` and `Tr` parameters defined for \"" + << material.name << "\". Use the value of `d` for dissolve." + << std::endl; + } + has_d = true; continue; } if (token[0] == 'T' && token[1] == 'r' && IS_SPACE(token[2])) { token += 2; - // Invert value of Tr(assume Tr is in range [0, 1]) - material.dissolve = 1.0f - parseFloat(&token); + if (has_d) { + // `d` wins. Ignore `Tr` value. + ss << "WARN: Both `d` and `Tr` parameters defined for \"" + << material.name << "\". Use the value of `d` for dissolve." + << std::endl; + } else { + // We invert value of Tr(assume Tr is in range [0, 1]) + // NOTE: Interpretation of Tr is application(exporter) dependent. For + // some application(e.g. 3ds max obj exporter), Tr = d(Issue 43) + material.dissolve = 1.0f - parseFloat(&token); + } + has_tr = true; continue; } @@ -1285,6 +1315,10 @@ void LoadMtl(std::map *material_map, material_map->insert(std::pair( material.name, static_cast(materials->size()))); materials->push_back(material); + + if (warning) { + (*warning) = ss.str(); + } } bool MaterialFileReader::operator()(const std::string &matId, @@ -1302,15 +1336,22 @@ bool MaterialFileReader::operator()(const std::string &matId, std::ifstream matIStream(filepath.c_str()); if (!matIStream) { std::stringstream ss; - ss << "WARN: Material file [ " << filepath - << " ] not found." << std::endl; + ss << "WARN: Material file [ " << filepath << " ] not found." << std::endl; if (err) { (*err) += ss.str(); } return false; } - LoadMtl(matMap, materials, &matIStream); + std::string warning; + LoadMtl(matMap, materials, &matIStream, &warning); + + if (!warning.empty()) { + if (err) { + (*err) += warning; + } + } + return true; } @@ -1328,14 +1369,21 @@ bool MaterialStreamReader::operator()(const std::string &matId, return false; } - LoadMtl(matMap, materials, &m_inStream); + std::string warning; + LoadMtl(matMap, materials, &m_inStream, &warning); + + if (!warning.empty()) { + if (err) { + (*err) += warning; + } + } + return true; } bool LoadObj(attrib_t *attrib, std::vector *shapes, std::vector *materials, std::string *err, - const char *filename, const char *mtl_basedir, - bool trianglulate) { + const char *filename, const char *mtl_basedir, bool trianglulate) { attrib->vertices.clear(); attrib->normals.clear(); attrib->texcoords.clear(); @@ -1505,17 +1553,18 @@ bool LoadObj(attrib_t *attrib, std::vector *shapes, if (filenames.empty()) { if (err) { - (*err) += "WARN: Looks like empty filename for mtllib. Use default material. \n"; + (*err) += + "WARN: Looks like empty filename for mtllib. Use default " + "material. \n"; } } else { - bool found = false; for (size_t s = 0; s < filenames.size(); s++) { - std::string err_mtl; - bool ok = (*readMatFn)(filenames[s].c_str(), materials, &material_map, &err_mtl); - if (err && (!err_mtl.empty())) { - (*err) += err_mtl; // This should be warn message. + bool ok = (*readMatFn)(filenames[s].c_str(), materials, + &material_map, &err_mtl); + if (err && (!err_mtl.empty())) { + (*err) += err_mtl; // This should be warn message. } if (ok) { @@ -1525,8 +1574,10 @@ bool LoadObj(attrib_t *attrib, std::vector *shapes, } if (!found) { - if (err) { - (*err) += "WARN: Failed to load material file(s). Use default material.\n"; + if (err) { + (*err) += + "WARN: Failed to load material file(s). Use default " + "material.\n"; } } } @@ -1811,17 +1862,18 @@ bool LoadObjWithCallback(std::istream &inStream, const callback_t &callback, if (filenames.empty()) { if (err) { - (*err) += "WARN: Looks like empty filename for mtllib. Use default material. \n"; + (*err) += + "WARN: Looks like empty filename for mtllib. Use default " + "material. \n"; } } else { - bool found = false; for (size_t s = 0; s < filenames.size(); s++) { - std::string err_mtl; - bool ok = (*readMatFn)(filenames[s].c_str(), &materials, &material_map, &err_mtl); - if (err && (!err_mtl.empty())) { - (*err) += err_mtl; // This should be warn message. + bool ok = (*readMatFn)(filenames[s].c_str(), &materials, + &material_map, &err_mtl); + if (err && (!err_mtl.empty())) { + (*err) += err_mtl; // This should be warn message. } if (ok) { @@ -1831,11 +1883,12 @@ bool LoadObjWithCallback(std::istream &inStream, const callback_t &callback, } if (!found) { - if (err) { - (*err) += "WARN: Failed to load material file(s). Use default material.\n"; + if (err) { + (*err) += + "WARN: Failed to load material file(s). Use default " + "material.\n"; } } else { - if (callback.mtllib_cb) { callback.mtllib_cb(user_data, &materials.at(0), static_cast(materials.size())); -- cgit v1.2.3 From da5942d65285cfcdb541a3024a069900f3dcd40e Mon Sep 17 00:00:00 2001 From: Ashley Date: Wed, 11 Jan 2017 01:10:52 +0100 Subject: Grammar? --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 545b0f8..aedde6e 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ http://syoyo.github.io/tinyobjloader/ -Tiny but powerful single file wavefront obj loader written in C++. No dependency except for C++ STL. It can parse 10M over polygons with moderate memory and time. +Tiny but powerful single file wavefront obj loader written in C++. No dependency except for C++ STL. It can parse over 10M polygons with moderate memory and time. `tinyobjloader` is good for embedding .obj loader to your (global illumination) renderer ;-) -- cgit v1.2.3 From 156b709556d4055471d493606ca31e5f6603d35e Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Thu, 2 Feb 2017 14:11:13 +0900 Subject: Move TINYOBJLOADER_IMPLEMENTATION outside of TINY_OBJ_LOADER_H_ ifdef guard. Fixes #122. --- tiny_obj_loader.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tiny_obj_loader.h b/tiny_obj_loader.h index cef0dd7..6d5a557 100644 --- a/tiny_obj_loader.h +++ b/tiny_obj_loader.h @@ -334,6 +334,8 @@ void LoadMtl(std::map *material_map, } // namespace tinyobj +#endif // TINY_OBJ_LOADER_H_ + #ifdef TINYOBJLOADER_IMPLEMENTATION #include #include @@ -2016,5 +2018,3 @@ bool LoadObjWithCallback(std::istream &inStream, const callback_t &callback, } // namespace tinyobj #endif - -#endif // TINY_OBJ_LOADER_H_ -- cgit v1.2.3 From 4b29502c82848c20906275acb9d598738286cb15 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Wed, 8 Feb 2017 13:57:25 +0900 Subject: Add data layout description to README. --- README.md | 68 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 67 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index aedde6e..7fd6dbd 100644 --- a/README.md +++ b/README.md @@ -97,7 +97,7 @@ TinyObjLoader is successfully used in ... * [ ] Fix obj_sticker example. * [ ] More unit test codes. -* [ ] Texture options +* [x] Texture options * [ ] Normal vector generation * [ ] Support smoothing groups @@ -107,10 +107,76 @@ Licensed under MIT license. ## Usage +### Data format + `attrib_t` contains single and linear array of vertex data(position, normal and texcoord). + +``` +attrib_t::vertices => 3 floats per vertex + + v[0] v[1] v[2] v[3] v[n-1] + +-----------+-----------+-----------+-----------+ +-----------+ + | x | y | z | x | y | z | x | y | z | x | y | z | .... | x | y | z | + +-----------+-----------+-----------+-----------+ +-----------+ + +attrib_t::normals => 3 floats per vertex + + n[0] n[1] n[2] n[3] n[n-1] + +-----------+-----------+-----------+-----------+ +-----------+ + | x | y | z | x | y | z | x | y | z | x | y | z | .... | x | y | z | + +-----------+-----------+-----------+-----------+ +-----------+ + +attrib_t::texcoords => 2 floats per vertex + + t[0] t[1] t[2] t[3] t[n-1] + +-----------+-----------+-----------+-----------+ +-----------+ + | u | v | u | v | u | v | u | v | .... | u | v | + +-----------+-----------+-----------+-----------+ +-----------+ + +``` + Each `shape_t` does not contain vertex data but contains array index to `attrib_t`. See `loader_example.cc` for more details. + +``` + +mesh_t::indices => array of vertex indices. + + +----+----+----+----+----+----+----+----+----+----+ +--------+ + | i0 | i1 | i2 | i3 | i4 | i5 | i6 | i7 | i8 | i9 | ... | i(n-1) | + +----+----+----+----+----+----+----+----+----+----+ +--------+ + +Each index has an array index to attrib_t::vertices, attrib_t::normals and attrib_t::texcoords. + +mesh_t::num_face_vertices => array of the number of vertices per face(e.g. 3 = triangle, 4 = quad , 5 or more = N-gons). + + + +---+---+---+ +---+ + | 3 | 4 | 3 | ...... | 3 | + +---+---+---+ +---+ + | | | | + | | | +-----------------------------------------+ + | | | | + | | +------------------------------+ | + | | | | + | +------------------+ | | + | | | | + |/ |/ |/ |/ + + mesh_t::indices + + | face[0] | face[1] | face[2] | | face[n-1] | + +----+----+----+----+----+----+----+----+----+----+ +--------+--------+--------+ + | i0 | i1 | i2 | i3 | i4 | i5 | i6 | i7 | i8 | i9 | ... | i(n-3) | i(n-2) | i(n-1) | + +----+----+----+----+----+----+----+----+----+----+ +--------+--------+--------+ + +``` + +Note that when `triangulate` flas is true in `tinyobj::LoadObj()` argument, `num_face_vertices` are all filled with 3(triangle). + +#### Example code + ```c++ #define TINYOBJLOADER_IMPLEMENTATION // define this in only *one* .cc #include "tiny_obj_loader.h" -- cgit v1.2.3 From 9613108cef5070d7eac672f6a4cdfa8a6f72336b Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Wed, 8 Feb 2017 13:58:40 +0900 Subject: Cosmetics. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7fd6dbd..56bcfb1 100644 --- a/README.md +++ b/README.md @@ -135,7 +135,7 @@ attrib_t::texcoords => 2 floats per vertex ``` -Each `shape_t` does not contain vertex data but contains array index to `attrib_t`. +Each `shape_t::mesh_t` does not contain vertex data but contains array index to `attrib_t`. See `loader_example.cc` for more details. -- cgit v1.2.3 From e43580bd9a5d38127b0e9b5b9769bb3182a69ffe Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Thu, 9 Feb 2017 16:10:19 +0900 Subject: Fix build with pre-C++11 compiler --- examples/viewer/viewer.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/viewer/viewer.cc b/examples/viewer/viewer.cc index f3c753a..19ffd5c 100644 --- a/examples/viewer/viewer.cc +++ b/examples/viewer/viewer.cc @@ -261,7 +261,7 @@ static bool LoadObjAndConvert(float bmin[3], float bmax[3], } unsigned char* image = stbi_load(texture_filename.c_str(), &w, &h, &comp, STBI_default); - if (image == nullptr) { + if (!image) { std::cerr << "Unable to load texture: " << texture_filename << std::endl; exit(1); } -- cgit v1.2.3 From cc9897316fd68556d8b7c96fb55392260d2a029a Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Thu, 9 Feb 2017 20:13:27 +0900 Subject: Do not exit program when there is no texture coordinate in .obj. --- examples/viewer/viewer.cc | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/examples/viewer/viewer.cc b/examples/viewer/viewer.cc index 19ffd5c..3df5361 100644 --- a/examples/viewer/viewer.cc +++ b/examples/viewer/viewer.cc @@ -321,8 +321,12 @@ static bool LoadObjAndConvert(float bmin[3], float bmax[3], tc[2][0] = attrib.texcoords[2 * idx2.texcoord_index]; tc[2][1] = 1.0f - attrib.texcoords[2 * idx2.texcoord_index + 1]; } else { - std::cerr << "Texcoordinates are not defined" << std::endl; - exit(2); + tc[0][0] = 0.0f; + tc[0][1] = 0.0f; + tc[1][0] = 0.0f; + tc[1][1] = 0.0f; + tc[2][0] = 0.0f; + tc[2][1] = 0.0f; } float v[3][3]; -- cgit v1.2.3 From 9134c1d3951fc2b3a83dd0691cb79b4245c94523 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Fri, 10 Feb 2017 17:19:28 +0900 Subject: Add file size check. --- experimental/premake4.lua | 76 ------------------------------------- experimental/premake5.lua | 95 +++++++++++++++++++++++++++++++++++++++++++++++ experimental/viewer.cc | 28 +++++++++++--- 3 files changed, 117 insertions(+), 82 deletions(-) delete mode 100644 experimental/premake4.lua create mode 100644 experimental/premake5.lua diff --git a/experimental/premake4.lua b/experimental/premake4.lua deleted file mode 100644 index 0273ed2..0000000 --- a/experimental/premake4.lua +++ /dev/null @@ -1,76 +0,0 @@ -newoption { - trigger = "with-zlib", - description = "Build with zlib." -} - -newoption { - trigger = "with-zstd", - description = "Build with ZStandard compression." -} - -solution "objview" - -- location ( "build" ) - configurations { "Release", "Debug" } - platforms {"native", "x64", "x32"} - - project "objview" - - kind "ConsoleApp" - language "C++" - files { "viewer.cc", "trackball.cc", "ltalloc.cc" } - includedirs { "./" } - includedirs { "../../" } - - flags { "c++11" } - --buildoptions { "-std=c++11" } - - if _OPTIONS['with-zlib'] then - defines { 'ENABLE_ZLIB' } - links { 'z' } - end - - if _OPTIONS['with-zstd'] then - print("with-zstd") - defines { 'ENABLE_ZSTD' } - -- Set path to zstd installed dir. - includedirs { '$$HOME/local/include' } - libdirs { '$$HOME/local/lib' } - links { 'zstd' } - end - - -- Uncomment if you want address sanitizer(gcc/clang only) - --buildoptions { "-fsanitize=address" } - --linkoptions { "-fsanitize=address" } - - configuration { "linux" } - linkoptions { "`pkg-config --libs glfw3`" } - links { "GL", "GLU", "m", "GLEW", "X11", "Xrandr", "Xinerama", "Xi", "Xxf86vm", "Xcursor", "dl" } - linkoptions { "-pthread" } - - configuration { "windows" } - -- Path to GLFW3 - includedirs { '../../../local/glfw-3.2.bin.WIN64/include' } - libdirs { '../../../local/glfw-3.2.bin.WIN64/lib-vc2015' } - -- Path to GLEW - includedirs { '../../../local/glew-1.13.0/include' } - libdirs { '../../../local/glew-1.13.0/lib/Release/x64' } - - links { "glfw3", "glew32", "gdi32", "winmm", "user32", "glu32","opengl32", "kernel32" } - defines { "_CRT_SECURE_NO_WARNINGS" } - defines { "NOMINMAX" } - - configuration { "macosx" } - includedirs { "/usr/local/include" } - buildoptions { "-Wno-deprecated-declarations" } - libdirs { "/usr/local/lib" } - links { "glfw3", "GLEW" } - linkoptions { "-framework OpenGL", "-framework Cocoa", "-framework IOKit", "-framework CoreVideo" } - - configuration "Debug" - defines { "DEBUG" } - flags { "Symbols"} - - configuration "Release" - defines { "NDEBUG" } - flags { "Optimize"} - diff --git a/experimental/premake5.lua b/experimental/premake5.lua new file mode 100644 index 0000000..4a94af9 --- /dev/null +++ b/experimental/premake5.lua @@ -0,0 +1,95 @@ +newoption { + trigger = "with-zlib", + description = "Build with zlib." +} + +newoption { + trigger = "with-zstd", + description = "Build with ZStandard compression." +} + +newoption { + trigger = "clang", + description = "Use clang compiler." +} + +newoption { + trigger = "asan", + description = "Enable AddressSanitizer(gcc or clang only)." +} + +solution "objview" + -- location ( "build" ) + configurations { "Release", "Debug" } + platforms {"native", "x64", "x32"} + + project "objview" + + kind "ConsoleApp" + language "C++" + files { "viewer.cc", "trackball.cc", "ltalloc.cc" } + includedirs { "./" } + includedirs { "../../" } + + flags { "c++11" } + --buildoptions { "-std=c++11" } + + if _OPTIONS['clang'] then + toolset "clang" + end + + if _OPTIONS['with-zlib'] then + defines { 'ENABLE_ZLIB' } + links { 'z' } + end + + if _OPTIONS['asan'] then + buildoptions { '-fsanitize=address' } + linkoptions { '-fsanitize=address' } + end + + if _OPTIONS['with-zstd'] then + print("with-zstd") + defines { 'ENABLE_ZSTD' } + -- Set path to zstd installed dir. + includedirs { '$$HOME/local/include' } + libdirs { '$$HOME/local/lib' } + links { 'zstd' } + end + + -- Uncomment if you want address sanitizer(gcc/clang only) + --buildoptions { "-fsanitize=address" } + --linkoptions { "-fsanitize=address" } + + configuration { "linux" } + linkoptions { "`pkg-config --libs glfw3`" } + links { "GL", "GLU", "m", "GLEW", "X11", "Xrandr", "Xinerama", "Xi", "Xxf86vm", "Xcursor", "dl" } + linkoptions { "-pthread" } + + configuration { "windows" } + -- Path to GLFW3 + includedirs { '../../../local/glfw-3.2.bin.WIN64/include' } + libdirs { '../../../local/glfw-3.2.bin.WIN64/lib-vc2015' } + -- Path to GLEW + includedirs { '../../../local/glew-1.13.0/include' } + libdirs { '../../../local/glew-1.13.0/lib/Release/x64' } + + links { "glfw3", "glew32", "gdi32", "winmm", "user32", "glu32","opengl32", "kernel32" } + defines { "_CRT_SECURE_NO_WARNINGS" } + defines { "NOMINMAX" } + + configuration { "macosx" } + includedirs { "/usr/local/include" } + buildoptions { "-Wno-deprecated-declarations" } + libdirs { "/usr/local/lib" } + links { "glfw3", "GLEW" } + linkoptions { "-framework OpenGL", "-framework Cocoa", "-framework IOKit", "-framework CoreVideo" } + + configuration "Debug" + defines { "DEBUG" } + flags { "Symbols"} + + configuration "Release" + defines { "NDEBUG" } + flags { "Optimize"} + diff --git a/experimental/viewer.cc b/experimental/viewer.cc index 3555b54..be5053f 100644 --- a/experimental/viewer.cc +++ b/experimental/viewer.cc @@ -110,11 +110,20 @@ const char *mmap_file(size_t *len, const char* filename) #else - FILE* f = fopen(filename, "r" ); + FILE* f = fopen(filename, "rb" ); + if (!f) { + fprintf(stderr, "Failed to open file : %s\n", filename); + return nullptr; + } fseek(f, 0, SEEK_END); long fileSize = ftell(f); fclose(f); + if (fileSize < 16) { + fprintf(stderr, "Empty or invalid .obj : %s\n", filename); + return nullptr; + } + struct stat sb; char *p; int fd; @@ -122,29 +131,29 @@ const char *mmap_file(size_t *len, const char* filename) fd = open (filename, O_RDONLY); if (fd == -1) { perror ("open"); - return NULL; + return nullptr; } if (fstat (fd, &sb) == -1) { perror ("fstat"); - return NULL; + return nullptr; } if (!S_ISREG (sb.st_mode)) { fprintf (stderr, "%s is not a file\n", "lineitem.tbl"); - return NULL; + return nullptr; } p = (char*)mmap (0, fileSize, PROT_READ, MAP_SHARED, fd, 0); if (p == MAP_FAILED) { perror ("mmap"); - return NULL; + return nullptr; } if (close (fd) == -1) { perror ("close"); - return NULL; + return nullptr; } (*len) = fileSize; @@ -298,6 +307,7 @@ const char* get_file_data(size_t *len, const char* filename) } else { data = mmap_file(&data_len, filename); + } (*len) = data_len; @@ -642,6 +652,12 @@ int main(int argc, char **argv) exit(-1); return false; } + + if (data_len < 4) { + printf("Empty file\n"); + exit(-1); + return false; + } printf("filesize: %d\n", (int)data_len); tinyobj_opt::LoadOption option; option.req_num_threads = num_threads; -- cgit v1.2.3 From 345560040b4bfaa2f60619f435e1e314d3647e14 Mon Sep 17 00:00:00 2001 From: nyatsenk Date: Wed, 22 Feb 2017 11:34:39 +0100 Subject: Add std namespace for pow, ldexp, sscanf function calls. --- tiny_obj_loader.h | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/tiny_obj_loader.h b/tiny_obj_loader.h index 6d5a557..7f704d8 100644 --- a/tiny_obj_loader.h +++ b/tiny_obj_loader.h @@ -529,7 +529,7 @@ static bool tryParseDouble(const char *s, const char *s_end, double *result) { // NOTE: Don't use powf here, it will absolutely murder precision. mantissa += static_cast(*curr - 0x30) * - (read < lut_entries ? pow_lut[read] : pow(10.0, -read)); + (read < lut_entries ? pow_lut[read] : std::pow(10.0, -read)); read++; curr++; end_not_reached = (curr != s_end); @@ -571,7 +571,7 @@ static bool tryParseDouble(const char *s, const char *s_end, double *result) { assemble: *result = (sign == '+' ? 1 : -1) * - (exponent ? ldexp(mantissa * pow(5.0, exponent), exponent) : mantissa); + (exponent ? std::ldexp(mantissa * std::pow(5.0, exponent), exponent) : mantissa); return true; fail: return false; @@ -1020,7 +1020,7 @@ void LoadMtl(std::map *material_map, #ifdef _MSC_VER sscanf_s(token, "%s", namebuf, (unsigned)_countof(namebuf)); #else - sscanf(token, "%s", namebuf); + std::sscanf(token, "%s", namebuf); #endif material.name = namebuf; continue; @@ -1522,7 +1522,7 @@ bool LoadObj(attrib_t *attrib, std::vector *shapes, #ifdef _MSC_VER sscanf_s(token, "%s", namebuf, (unsigned)_countof(namebuf)); #else - sscanf(token, "%s", namebuf); + std::sscanf(token, "%s", namebuf); #endif int newMaterialId = -1; @@ -1642,7 +1642,7 @@ bool LoadObj(attrib_t *attrib, std::vector *shapes, #ifdef _MSC_VER sscanf_s(token, "%s", namebuf, (unsigned)_countof(namebuf)); #else - sscanf(token, "%s", namebuf); + std::sscanf(token, "%s", namebuf); #endif name = std::string(namebuf); @@ -1657,7 +1657,7 @@ bool LoadObj(attrib_t *attrib, std::vector *shapes, #ifdef _MSC_VER sscanf_s(token, "%s", namebuf, (unsigned)_countof(namebuf)); #else - sscanf(token, "%s", namebuf); + std::sscanf(token, "%s", namebuf); #endif tag.name = std::string(namebuf); @@ -1686,7 +1686,7 @@ bool LoadObj(attrib_t *attrib, std::vector *shapes, sscanf_s(token, "%s", stringValueBuffer, (unsigned)_countof(stringValueBuffer)); #else - sscanf(token, "%s", stringValueBuffer); + std::sscanf(token, "%s", stringValueBuffer); #endif tag.stringValues[i] = stringValueBuffer; token += tag.stringValues[i].size() + 1; @@ -1833,7 +1833,7 @@ bool LoadObjWithCallback(std::istream &inStream, const callback_t &callback, sscanf_s(token, "%s", namebuf, static_cast(_countof(namebuf))); #else - sscanf(token, "%s", namebuf); + std::sscanf(token, "%s", namebuf); #endif int newMaterialId = -1; @@ -1947,7 +1947,7 @@ bool LoadObjWithCallback(std::istream &inStream, const callback_t &callback, #ifdef _MSC_VER sscanf_s(token, "%s", namebuf, (unsigned)_countof(namebuf)); #else - sscanf(token, "%s", namebuf); + std::sscanf(token, "%s", namebuf); #endif std::string object_name = std::string(namebuf); @@ -1967,7 +1967,7 @@ bool LoadObjWithCallback(std::istream &inStream, const callback_t &callback, #ifdef _MSC_VER sscanf_s(token, "%s", namebuf, (unsigned)_countof(namebuf)); #else - sscanf(token, "%s", namebuf); + std::sscanf(token, "%s", namebuf); #endif tag.name = std::string(namebuf); @@ -1996,7 +1996,7 @@ bool LoadObjWithCallback(std::istream &inStream, const callback_t &callback, sscanf_s(token, "%s", stringValueBuffer, (unsigned)_countof(stringValueBuffer)); #else - sscanf(token, "%s", stringValueBuffer); + std::sscanf(token, "%s", stringValueBuffer); #endif tag.stringValues[i] = stringValueBuffer; token += tag.stringValues[i].size() + 1; -- cgit v1.2.3 From 9912bc5023e7416fa5e065b90b4eaecb2d06299c Mon Sep 17 00:00:00 2001 From: noma Date: Mon, 24 Apr 2017 17:20:20 +0200 Subject: Added double variant of lib to CMakeLists.txt --- CMakeLists.txt | 43 +++++++++++++++++++++++++++---------------- 1 file changed, 27 insertions(+), 16 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 2b4e06a..acfcd3a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,6 +6,17 @@ cmake_minimum_required(VERSION 2.8.11) set(TINYOBJLOADER_SOVERSION 1) set(TINYOBJLOADER_VERSION 1.0.4) +#optional double precision support +option(TINYOBJLOADER_USE_DOUBLE "Build library with double precision instead of single (float)" OFF) + +if(TINYOBJLOADER_USE_DOUBLE) + add_definitions(-DTINYOBJLOADER_USE_DOUBLE) + set(LIBRARY_NAME ${PROJECT_NAME}_double) +else() + set(LIBRARY_NAME ${PROJECT_NAME}) +endif() + + #Folder Shortcuts set(TINYOBJLOADEREXAMPLES_DIR ${CMAKE_CURRENT_SOURCE_DIR}/examples) @@ -38,32 +49,32 @@ option(TINYOBJLOADER_BUILD_TEST_LOADER "Build Example Loader Application" OFF) option(TINYOBJLOADER_COMPILATION_SHARED "Build as shared library" OFF) if(TINYOBJLOADER_COMPILATION_SHARED) - add_library(tinyobjloader SHARED ${tinyobjloader-Source}) - set_target_properties(tinyobjloader PROPERTIES + add_library(${LIBRARY_NAME} SHARED ${tinyobjloader-Source}) + set_target_properties(${LIBRARY_NAME} PROPERTIES SOVERSION ${TINYOBJLOADER_SOVERSION} ) else() - add_library(tinyobjloader STATIC ${tinyobjloader-Source}) + add_library(${LIBRARY_NAME} STATIC ${tinyobjloader-Source}) endif() -set_target_properties(tinyobjloader PROPERTIES VERSION ${TINYOBJLOADER_VERSION}) +set_target_properties(${LIBRARY_NAME} PROPERTIES VERSION ${TINYOBJLOADER_VERSION}) -target_include_directories(tinyobjloader INTERFACE +target_include_directories(${LIBRARY_NAME} INTERFACE $ $ ) -export(TARGETS tinyobjloader FILE ${PROJECT_NAME}-targets.cmake) +export(TARGETS ${LIBRARY_NAME} FILE ${PROJECT_NAME}-targets.cmake) if(TINYOBJLOADER_BUILD_TEST_LOADER) add_executable(test_loader ${tinyobjloader-Example-Source}) - target_link_libraries(test_loader tinyobjloader) + target_link_libraries(test_loader ${LIBRARY_NAME}) endif() option(TINYOBJLOADER_BUILD_OBJ_STICHER "Build OBJ Sticher Application" OFF) if(TINYOBJLOADER_BUILD_OBJ_STICHER) add_executable(obj_sticher ${tinyobjloader-examples-objsticher}) - target_link_libraries(obj_sticher tinyobjloader) + target_link_libraries(obj_sticher ${LIBRARY_NAME}) install(TARGETS obj_sticher @@ -76,8 +87,8 @@ endif() include(CMakePackageConfigHelpers) configure_package_config_file( - tinyobjloader-config.cmake.in - tinyobjloader-config.cmake + ${PROJECT_NAME}-config.cmake.in + ${LIBRARY_NAME}-config.cmake INSTALL_DESTINATION ${TINYOBJLOADER_CMAKE_DIR} PATH_VARS @@ -86,7 +97,7 @@ configure_package_config_file( NO_CHECK_REQUIRED_COMPONENTS_MACRO ) -write_basic_package_version_file(tinyobjloader-config-version.cmake +write_basic_package_version_file(${LIBRARY_NAME}-config-version.cmake VERSION ${TINYOBJLOADER_VERSION} COMPATIBILITY @@ -94,11 +105,11 @@ write_basic_package_version_file(tinyobjloader-config-version.cmake ) #pkg-config file -configure_file(tinyobjloader.pc.in tinyobjloader.pc @ONLY) +configure_file(${PROJECT_NAME}.pc.in ${LIBRARY_NAME}.pc @ONLY) #Installation install(TARGETS - tinyobjloader + ${LIBRARY_NAME} EXPORT ${PROJECT_NAME}-targets DESTINATION ${TINYOBJLOADER_LIBRARY_DIR} @@ -123,13 +134,13 @@ install(FILES ${TINYOBJLOADER_DOC_DIR} ) install(FILES - "${CMAKE_CURRENT_BINARY_DIR}/tinyobjloader-config.cmake" - "${CMAKE_CURRENT_BINARY_DIR}/tinyobjloader-config-version.cmake" + "${CMAKE_CURRENT_BINARY_DIR}/${LIBRARY_NAME}-config.cmake" + "${CMAKE_CURRENT_BINARY_DIR}/${LIBRARY_NAME}-config-version.cmake" DESTINATION ${TINYOBJLOADER_CMAKE_DIR} ) install(FILES - "${CMAKE_CURRENT_BINARY_DIR}/tinyobjloader.pc" + "${CMAKE_CURRENT_BINARY_DIR}/${LIBRARY_NAME}.pc" DESTINATION ${TINYOBJLOADER_PKGCONFIG_DIR} ) -- cgit v1.2.3 From 13412b0898ce5ef00dcc02be8772a66febbc49cf Mon Sep 17 00:00:00 2001 From: noma Date: Mon, 24 Apr 2017 17:25:21 +0200 Subject: Added real_t, defined to float or double depending on CMake option TINYOBJLOADER_USE_DOUBLE --- tiny_obj_loader.h | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tiny_obj_loader.h b/tiny_obj_loader.h index 7f704d8..f59d21f 100644 --- a/tiny_obj_loader.h +++ b/tiny_obj_loader.h @@ -96,6 +96,14 @@ namespace tinyobj { // separately // cube_left | cube_right +#ifdef TINYOBJLOADER_USE_DOUBLE + //#pragma message "using double" + typedef double real_t; +#else + //#pragma message "using float" + typedef float real_t; +#endif + typedef enum { TEXTURE_TYPE_NONE, // default TEXTURE_TYPE_SPHERE, -- cgit v1.2.3 From 69e56db1244db2686c555e0368b44660759e1981 Mon Sep 17 00:00:00 2001 From: noma Date: Mon, 24 Apr 2017 17:31:42 +0200 Subject: Replaced all float types by real_t ones. Also adapted names accordingly. --- tiny_obj_loader.h | 198 +++++++++++++++++++++++++++--------------------------- 1 file changed, 99 insertions(+), 99 deletions(-) diff --git a/tiny_obj_loader.h b/tiny_obj_loader.h index f59d21f..097da8d 100644 --- a/tiny_obj_loader.h +++ b/tiny_obj_loader.h @@ -53,7 +53,7 @@ namespace tinyobj { // (default on) // -blendv on | off # set vertical texture blending // (default on) -// -boost float_value # boost mip-map sharpness +// -boost real_value # boost mip-map sharpness // -mm base_value gain_value # modify texture map values (default // 0 1) // # base_value = brightness, @@ -117,31 +117,31 @@ typedef enum { typedef struct { texture_type_t type; // -type (default TEXTURE_TYPE_NONE) - float sharpness; // -boost (default 1.0?) - float brightness; // base_value in -mm option (default 0) - float contrast; // gain_value in -mm option (default 1) - float origin_offset[3]; // -o u [v [w]] (default 0 0 0) - float scale[3]; // -s u [v [w]] (default 1 1 1) - float turbulence[3]; // -t u [v [w]] (default 0 0 0) + real_t sharpness; // -boost (default 1.0?) + real_t brightness; // base_value in -mm option (default 0) + real_t contrast; // gain_value in -mm option (default 1) + real_t origin_offset[3]; // -o u [v [w]] (default 0 0 0) + real_t scale[3]; // -s u [v [w]] (default 1 1 1) + real_t turbulence[3]; // -t u [v [w]] (default 0 0 0) // int texture_resolution; // -texres resolution (default = ?) TODO bool clamp; // -clamp (default false) char imfchan; // -imfchan (the default for bump is 'l' and for decal is 'm') bool blendu; // -blendu (default on) bool blendv; // -blendv (default on) - float bump_multiplier; // -bm (for bump maps only, default 1.0) + real_t bump_multiplier; // -bm (for bump maps only, default 1.0) } texture_option_t; typedef struct { 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 + real_t ambient[3]; + real_t diffuse[3]; + real_t specular[3]; + real_t transmittance[3]; + real_t emission[3]; + real_t shininess; + real_t ior; // index of refraction + real_t dissolve; // 1 == opaque; 0 == fully transparent // illumination model (see http://www.fileformat.info/format/material/) int illum; @@ -165,15 +165,15 @@ typedef struct { // PBR extension // http://exocortex.com/blog/extending_wavefront_mtl_to_support_pbr - float roughness; // [0, 1] default 0 - float metallic; // [0, 1] default 0 - float sheen; // [0, 1] default 0 - float clearcoat_thickness; // [0, 1] default 0 - float clearcoat_roughness; // [0, 1] default 0 - float anisotropy; // aniso. [0, 1] default 0 - float anisotropy_rotation; // anisor. [0, 1] default 0 - float pad0; - float pad1; + real_t roughness; // [0, 1] default 0 + real_t metallic; // [0, 1] default 0 + real_t sheen; // [0, 1] default 0 + real_t clearcoat_thickness; // [0, 1] default 0 + real_t clearcoat_roughness; // [0, 1] default 0 + real_t anisotropy; // aniso. [0, 1] default 0 + real_t anisotropy_rotation; // anisor. [0, 1] default 0 + real_t pad0; + real_t pad1; std::string roughness_texname; // map_Pr std::string metallic_texname; // map_Pm std::string sheen_texname; // map_Ps @@ -195,7 +195,7 @@ typedef struct { std::string name; std::vector intValues; - std::vector floatValues; + std::vector realValues; std::vector stringValues; } tag_t; @@ -223,19 +223,19 @@ typedef struct { // Vertex attributes typedef struct { - std::vector vertices; // 'v' - std::vector normals; // 'vn' - std::vector texcoords; // 'vt' + std::vector vertices; // 'v' + std::vector normals; // 'vn' + std::vector texcoords; // 'vt' } attrib_t; typedef struct callback_t_ { // W is optional and set to 1 if there is no `w` item in `v` line - void (*vertex_cb)(void *user_data, float x, float y, float z, float w); - void (*normal_cb)(void *user_data, float x, float y, float z); + void (*vertex_cb)(void *user_data, real_t x, real_t y, real_t z, real_t w); + void (*normal_cb)(void *user_data, real_t x, real_t y, real_t z); // y and z are optional and set to 0 if there is no `y` and/or `z` item(s) in // `vt` line. - void (*texcoord_cb)(void *user_data, float x, float y, float z); + void (*texcoord_cb)(void *user_data, real_t x, real_t y, real_t z); // called per 'f' line. num_indices is the number of face indices(e.g. 3 for // triangle, 4 for quad) @@ -371,16 +371,16 @@ struct vertex_index { }; struct tag_sizes { - tag_sizes() : num_ints(0), num_floats(0), num_strings(0) {} + tag_sizes() : num_ints(0), num_reals(0), num_strings(0) {} int num_ints; - int num_floats; + int num_reals; int num_strings; }; struct obj_shape { - std::vector v; - std::vector vn; - std::vector vt; + std::vector v; + std::vector vn; + std::vector vt; }; // See @@ -585,41 +585,41 @@ fail: return false; } -static inline float parseFloat(const char **token, double default_value = 0.0) { +static inline real_t parseReal(const char **token, double default_value = 0.0) { (*token) += strspn((*token), " \t"); const char *end = (*token) + strcspn((*token), " \t\r"); double val = default_value; tryParseDouble((*token), end, &val); - float f = static_cast(val); + real_t f = static_cast(val); (*token) = end; return f; } -static inline void parseFloat2(float *x, float *y, const char **token, +static inline void parseReal2(real_t *x, real_t *y, const char **token, const double default_x = 0.0, const double default_y = 0.0) { - (*x) = parseFloat(token, default_x); - (*y) = parseFloat(token, default_y); + (*x) = parseReal(token, default_x); + (*y) = parseReal(token, default_y); } -static inline void parseFloat3(float *x, float *y, float *z, const char **token, +static inline void parseReal3(real_t *x, real_t *y, real_t *z, const char **token, const double default_x = 0.0, const double default_y = 0.0, const double default_z = 0.0) { - (*x) = parseFloat(token, default_x); - (*y) = parseFloat(token, default_y); - (*z) = parseFloat(token, default_z); + (*x) = parseReal(token, default_x); + (*y) = parseReal(token, default_y); + (*z) = parseReal(token, default_z); } -static inline void parseV(float *x, float *y, float *z, float *w, +static inline void parseV(real_t *x, real_t *y, real_t *z, real_t *w, const char **token, const double default_x = 0.0, const double default_y = 0.0, const double default_z = 0.0, const double default_w = 1.0) { - (*x) = parseFloat(token, default_x); - (*y) = parseFloat(token, default_y); - (*z) = parseFloat(token, default_z); - (*w) = parseFloat(token, default_w); + (*x) = parseReal(token, default_x); + (*y) = parseReal(token, default_y); + (*z) = parseReal(token, default_z); + (*w) = parseReal(token, default_w); } static inline bool parseOnOff(const char **token, bool default_value = true) { @@ -673,7 +673,7 @@ static tag_sizes parseTagTriple(const char **token) { } (*token)++; - ts.num_floats = atoi((*token)); + ts.num_reals = atoi((*token)); (*token) += strcspn((*token), "/ \t\r"); if ((*token)[0] != '/') { return ts; @@ -798,21 +798,21 @@ static bool ParseTextureNameAndOption(std::string *texname, texopt->clamp = parseOnOff(&token, /* default */ true); } else if ((0 == strncmp(token, "-boost", 6)) && IS_SPACE((token[6]))) { token += 7; - texopt->sharpness = parseFloat(&token, 1.0); + texopt->sharpness = parseReal(&token, 1.0); } else if ((0 == strncmp(token, "-bm", 3)) && IS_SPACE((token[3]))) { token += 4; - texopt->bump_multiplier = parseFloat(&token, 1.0); + texopt->bump_multiplier = parseReal(&token, 1.0); } else if ((0 == strncmp(token, "-o", 2)) && IS_SPACE((token[2]))) { token += 3; - parseFloat3(&(texopt->origin_offset[0]), &(texopt->origin_offset[1]), + parseReal3(&(texopt->origin_offset[0]), &(texopt->origin_offset[1]), &(texopt->origin_offset[2]), &token); } else if ((0 == strncmp(token, "-s", 2)) && IS_SPACE((token[2]))) { token += 3; - parseFloat3(&(texopt->scale[0]), &(texopt->scale[1]), &(texopt->scale[2]), + parseReal3(&(texopt->scale[0]), &(texopt->scale[1]), &(texopt->scale[2]), &token, 1.0, 1.0, 1.0); } else if ((0 == strncmp(token, "-t", 2)) && IS_SPACE((token[2]))) { token += 3; - parseFloat3(&(texopt->turbulence[0]), &(texopt->turbulence[1]), + parseReal3(&(texopt->turbulence[0]), &(texopt->turbulence[1]), &(texopt->turbulence[2]), &token); } else if ((0 == strncmp(token, "-type", 5)) && IS_SPACE((token[5]))) { token += 5; @@ -827,7 +827,7 @@ static bool ParseTextureNameAndOption(std::string *texname, token = end; } else if ((0 == strncmp(token, "-mm", 3)) && IS_SPACE((token[3]))) { token += 4; - parseFloat2(&(texopt->brightness), &(texopt->contrast), &token, 0.0, 1.0); + parseReal2(&(texopt->brightness), &(texopt->contrast), &token, 0.0, 1.0); } else { // Assume texture filename token += strspn(token, " \t"); // skip space @@ -1037,8 +1037,8 @@ void LoadMtl(std::map *material_map, // ambient if (token[0] == 'K' && token[1] == 'a' && IS_SPACE((token[2]))) { token += 2; - float r, g, b; - parseFloat3(&r, &g, &b, &token); + real_t r, g, b; + parseReal3(&r, &g, &b, &token); material.ambient[0] = r; material.ambient[1] = g; material.ambient[2] = b; @@ -1048,8 +1048,8 @@ void LoadMtl(std::map *material_map, // diffuse if (token[0] == 'K' && token[1] == 'd' && IS_SPACE((token[2]))) { token += 2; - float r, g, b; - parseFloat3(&r, &g, &b, &token); + real_t r, g, b; + parseReal3(&r, &g, &b, &token); material.diffuse[0] = r; material.diffuse[1] = g; material.diffuse[2] = b; @@ -1059,8 +1059,8 @@ void LoadMtl(std::map *material_map, // specular if (token[0] == 'K' && token[1] == 's' && IS_SPACE((token[2]))) { token += 2; - float r, g, b; - parseFloat3(&r, &g, &b, &token); + real_t r, g, b; + parseReal3(&r, &g, &b, &token); material.specular[0] = r; material.specular[1] = g; material.specular[2] = b; @@ -1071,8 +1071,8 @@ void LoadMtl(std::map *material_map, if ((token[0] == 'K' && token[1] == 't' && IS_SPACE((token[2]))) || (token[0] == 'T' && token[1] == 'f' && IS_SPACE((token[2])))) { token += 2; - float r, g, b; - parseFloat3(&r, &g, &b, &token); + real_t r, g, b; + parseReal3(&r, &g, &b, &token); material.transmittance[0] = r; material.transmittance[1] = g; material.transmittance[2] = b; @@ -1082,15 +1082,15 @@ void LoadMtl(std::map *material_map, // ior(index of refraction) if (token[0] == 'N' && token[1] == 'i' && IS_SPACE((token[2]))) { token += 2; - material.ior = parseFloat(&token); + material.ior = parseReal(&token); continue; } // emission if (token[0] == 'K' && token[1] == 'e' && IS_SPACE(token[2])) { token += 2; - float r, g, b; - parseFloat3(&r, &g, &b, &token); + real_t r, g, b; + parseReal3(&r, &g, &b, &token); material.emission[0] = r; material.emission[1] = g; material.emission[2] = b; @@ -1100,7 +1100,7 @@ void LoadMtl(std::map *material_map, // shininess if (token[0] == 'N' && token[1] == 's' && IS_SPACE(token[2])) { token += 2; - material.shininess = parseFloat(&token); + material.shininess = parseReal(&token); continue; } @@ -1114,7 +1114,7 @@ void LoadMtl(std::map *material_map, // dissolve if ((token[0] == 'd' && IS_SPACE(token[1]))) { token += 1; - material.dissolve = parseFloat(&token); + material.dissolve = parseReal(&token); if (has_tr) { ss << "WARN: Both `d` and `Tr` parameters defined for \"" @@ -1135,7 +1135,7 @@ void LoadMtl(std::map *material_map, // We invert value of Tr(assume Tr is in range [0, 1]) // NOTE: Interpretation of Tr is application(exporter) dependent. For // some application(e.g. 3ds max obj exporter), Tr = d(Issue 43) - material.dissolve = 1.0f - parseFloat(&token); + material.dissolve = 1.0f - parseReal(&token); } has_tr = true; continue; @@ -1144,49 +1144,49 @@ void LoadMtl(std::map *material_map, // PBR: roughness if (token[0] == 'P' && token[1] == 'r' && IS_SPACE(token[2])) { token += 2; - material.roughness = parseFloat(&token); + material.roughness = parseReal(&token); continue; } // PBR: metallic if (token[0] == 'P' && token[1] == 'm' && IS_SPACE(token[2])) { token += 2; - material.metallic = parseFloat(&token); + material.metallic = parseReal(&token); continue; } // PBR: sheen if (token[0] == 'P' && token[1] == 's' && IS_SPACE(token[2])) { token += 2; - material.sheen = parseFloat(&token); + material.sheen = parseReal(&token); continue; } // PBR: clearcoat thickness if (token[0] == 'P' && token[1] == 'c' && IS_SPACE(token[2])) { token += 2; - material.clearcoat_thickness = parseFloat(&token); + material.clearcoat_thickness = parseReal(&token); continue; } // PBR: clearcoat roughness if ((0 == strncmp(token, "Pcr", 3)) && IS_SPACE(token[3])) { token += 4; - material.clearcoat_roughness = parseFloat(&token); + material.clearcoat_roughness = parseReal(&token); continue; } // PBR: anisotropy if ((0 == strncmp(token, "aniso", 5)) && IS_SPACE(token[5])) { token += 6; - material.anisotropy = parseFloat(&token); + material.anisotropy = parseReal(&token); continue; } // PBR: anisotropy rotation if ((0 == strncmp(token, "anisor", 6)) && IS_SPACE(token[6])) { token += 7; - material.anisotropy_rotation = parseFloat(&token); + material.anisotropy_rotation = parseReal(&token); continue; } @@ -1426,9 +1426,9 @@ bool LoadObj(attrib_t *attrib, std::vector *shapes, bool triangulate) { std::stringstream errss; - std::vector v; - std::vector vn; - std::vector vt; + std::vector v; + std::vector vn; + std::vector vt; std::vector tags; std::vector > faceGroup; std::string name; @@ -1470,8 +1470,8 @@ bool LoadObj(attrib_t *attrib, std::vector *shapes, // vertex if (token[0] == 'v' && IS_SPACE((token[1]))) { token += 2; - float x, y, z; - parseFloat3(&x, &y, &z, &token); + real_t x, y, z; + parseReal3(&x, &y, &z, &token); v.push_back(x); v.push_back(y); v.push_back(z); @@ -1481,8 +1481,8 @@ bool LoadObj(attrib_t *attrib, std::vector *shapes, // normal if (token[0] == 'v' && token[1] == 'n' && IS_SPACE((token[2]))) { token += 3; - float x, y, z; - parseFloat3(&x, &y, &z, &token); + real_t x, y, z; + parseReal3(&x, &y, &z, &token); vn.push_back(x); vn.push_back(y); vn.push_back(z); @@ -1492,8 +1492,8 @@ bool LoadObj(attrib_t *attrib, std::vector *shapes, // texcoord if (token[0] == 'v' && token[1] == 't' && IS_SPACE((token[2]))) { token += 3; - float x, y; - parseFloat2(&x, &y, &token); + real_t x, y; + parseReal2(&x, &y, &token); vt.push_back(x); vt.push_back(y); continue; @@ -1680,9 +1680,9 @@ bool LoadObj(attrib_t *attrib, std::vector *shapes, token += strcspn(token, "/ \t\r") + 1; } - tag.floatValues.resize(static_cast(ts.num_floats)); - for (size_t i = 0; i < static_cast(ts.num_floats); ++i) { - tag.floatValues[i] = parseFloat(&token); + tag.realValues.resize(static_cast(ts.num_reals)); + for (size_t i = 0; i < static_cast(ts.num_reals); ++i) { + tag.realValues[i] = parseReal(&token); token += strcspn(token, "/ \t\r") + 1; } @@ -1776,7 +1776,7 @@ bool LoadObjWithCallback(std::istream &inStream, const callback_t &callback, // vertex if (token[0] == 'v' && IS_SPACE((token[1]))) { token += 2; - float x, y, z, w; // w is optional. default = 1.0 + real_t x, y, z, w; // w is optional. default = 1.0 parseV(&x, &y, &z, &w, &token); if (callback.vertex_cb) { callback.vertex_cb(user_data, x, y, z, w); @@ -1787,8 +1787,8 @@ bool LoadObjWithCallback(std::istream &inStream, const callback_t &callback, // normal if (token[0] == 'v' && token[1] == 'n' && IS_SPACE((token[2]))) { token += 3; - float x, y, z; - parseFloat3(&x, &y, &z, &token); + real_t x, y, z; + parseReal3(&x, &y, &z, &token); if (callback.normal_cb) { callback.normal_cb(user_data, x, y, z); } @@ -1798,8 +1798,8 @@ bool LoadObjWithCallback(std::istream &inStream, const callback_t &callback, // texcoord if (token[0] == 'v' && token[1] == 't' && IS_SPACE((token[2]))) { token += 3; - float x, y, z; // y and z are optional. default = 0.0 - parseFloat3(&x, &y, &z, &token); + real_t x, y, z; // y and z are optional. default = 0.0 + parseReal3(&x, &y, &z, &token); if (callback.texcoord_cb) { callback.texcoord_cb(user_data, x, y, z); } @@ -1990,9 +1990,9 @@ bool LoadObjWithCallback(std::istream &inStream, const callback_t &callback, token += strcspn(token, "/ \t\r") + 1; } - tag.floatValues.resize(static_cast(ts.num_floats)); - for (size_t i = 0; i < static_cast(ts.num_floats); ++i) { - tag.floatValues[i] = parseFloat(&token); + tag.realValues.resize(static_cast(ts.num_reals)); + for (size_t i = 0; i < static_cast(ts.num_reals); ++i) { + tag.realValues[i] = parseReal(&token); token += strcspn(token, "/ \t\r") + 1; } -- cgit v1.2.3 From b1ac3a6c7e12d0c6b5909147aee1aca7d1777154 Mon Sep 17 00:00:00 2001 From: noma Date: Mon, 24 Apr 2017 17:41:22 +0200 Subject: Fixed package config and cmake config templates for double variant of the library. --- tinyobjloader-config.cmake.in | 2 +- tinyobjloader.pc.in | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tinyobjloader-config.cmake.in b/tinyobjloader-config.cmake.in index 2d6b3e3..91f01b0 100644 --- a/tinyobjloader-config.cmake.in +++ b/tinyobjloader-config.cmake.in @@ -4,6 +4,6 @@ set(TINYOBJLOADER_VERSION "@TINYOBJLOADER_VERSION@") set_and_check(TINYOBJLOADER_INCLUDE_DIRS "@PACKAGE_TINYOBJLOADER_INCLUDE_DIR@") set_and_check(TINYOBJLOADER_LIBRARY_DIRS "@PACKAGE_TINYOBJLOADER_LIBRARY_DIR@") -set(TINYOBJLOADER_LIBRARIES tinyobjloader) +set(TINYOBJLOADER_LIBRARIES @LIBRARY_NAME@) include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@-targets.cmake") diff --git a/tinyobjloader.pc.in b/tinyobjloader.pc.in index 7585fee..048a287 100644 --- a/tinyobjloader.pc.in +++ b/tinyobjloader.pc.in @@ -11,5 +11,5 @@ Name: @PROJECT_NAME@ Description: Tiny but powerful single file wavefront obj loader URL: https://syoyo.github.io/tinyobjloader/ Version: @TINYOBJLOADER_VERSION@ -Libs: -L${libdir} -ltinyobjloader +Libs: -L${libdir} -l@LIBRARY_NAME@ Cflags: -I${includedir} -- cgit v1.2.3 From d5ca25881728ed6591257b530bff973e0c394e7f Mon Sep 17 00:00:00 2001 From: noma Date: Mon, 24 Apr 2017 17:49:39 +0200 Subject: Renamed realValues back to flaotValues, as it is part of the external interface and renaming would break existing code. --- tiny_obj_loader.h | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tiny_obj_loader.h b/tiny_obj_loader.h index 097da8d..a9bfb4f 100644 --- a/tiny_obj_loader.h +++ b/tiny_obj_loader.h @@ -195,7 +195,7 @@ typedef struct { std::string name; std::vector intValues; - std::vector realValues; + std::vector floatValues; std::vector stringValues; } tag_t; @@ -1680,9 +1680,9 @@ bool LoadObj(attrib_t *attrib, std::vector *shapes, token += strcspn(token, "/ \t\r") + 1; } - tag.realValues.resize(static_cast(ts.num_reals)); + tag.floatValues.resize(static_cast(ts.num_reals)); for (size_t i = 0; i < static_cast(ts.num_reals); ++i) { - tag.realValues[i] = parseReal(&token); + tag.floatValues[i] = parseReal(&token); token += strcspn(token, "/ \t\r") + 1; } @@ -1990,9 +1990,9 @@ bool LoadObjWithCallback(std::istream &inStream, const callback_t &callback, token += strcspn(token, "/ \t\r") + 1; } - tag.realValues.resize(static_cast(ts.num_reals)); + tag.floatValues.resize(static_cast(ts.num_reals)); for (size_t i = 0; i < static_cast(ts.num_reals); ++i) { - tag.realValues[i] = parseReal(&token); + tag.floatValues[i] = parseReal(&token); token += strcspn(token, "/ \t\r") + 1; } -- cgit v1.2.3 From e60d33385e2e4f7fa891513150f2532b5bbcb093 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Tue, 25 Apr 2017 04:34:45 +0900 Subject: Bump version 1.0.6 --- tiny_obj_loader.h | 1 + 1 file changed, 1 insertion(+) diff --git a/tiny_obj_loader.h b/tiny_obj_loader.h index a9bfb4f..ee44076 100644 --- a/tiny_obj_loader.h +++ b/tiny_obj_loader.h @@ -23,6 +23,7 @@ THE SOFTWARE. */ // +// version 1.0.6 : Add TINYOBJLOADER_USE_DOUBLE option(#124) // version 1.0.5 : Ignore `Tr` when `d` exists in MTL(#43) // version 1.0.4 : Support multiple filenames for 'mtllib'(#112) // version 1.0.3 : Support parsing texture options(#85) -- cgit v1.2.3 From 3e146c376cc85a7b7f1d7e92919db279257f6efc Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Tue, 25 Apr 2017 15:50:42 +0900 Subject: Update README. --- README.md | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 56bcfb1..99bf9e9 100644 --- a/README.md +++ b/README.md @@ -51,6 +51,7 @@ TinyObjLoader is successfully used in ... ### New version(v1.0.x) +* Double precision support through `TINYOBJLOADER_USE_DOUBLE` thanks to noma * Loading models in Vulkan Tutorial https://vulkan-tutorial.com/Loading_models * .obj viewer with Metal https://github.com/middlefeng/NuoModelViewer/tree/master * Your project here! @@ -91,6 +92,7 @@ TinyObjLoader is successfully used in ... * Crease tag('t'). This is OpenSubdiv specific(not in wavefront .obj specification) * PBR material extension for .MTL. Its proposed here: http://exocortex.com/blog/extending_wavefront_mtl_to_support_pbr * Callback API for custom loading. +* Double precision support(for HPC application). ## TODO @@ -175,6 +177,12 @@ mesh_t::num_face_vertices => array of the number of vertices per face(e.g. 3 = t Note that when `triangulate` flas is true in `tinyobj::LoadObj()` argument, `num_face_vertices` are all filled with 3(triangle). +### float data type + +TinyObjLoader now use `real_t` for floating point data type. +Default is `float(32bit)`. +You can enable `double(64bit)` precision by using `TINYOBJLOADER_USE_DOUBLE` define. + #### Example code ```c++ @@ -208,14 +216,14 @@ for (size_t s = 0; s < shapes.size(); s++) { for (size_t v = 0; v < fv; v++) { // access to vertex tinyobj::index_t idx = shapes[s].mesh.indices[index_offset + v]; - float vx = attrib.vertices[3*idx.vertex_index+0]; - float vy = attrib.vertices[3*idx.vertex_index+1]; - float vz = attrib.vertices[3*idx.vertex_index+2]; - float nx = attrib.normals[3*idx.normal_index+0]; - float ny = attrib.normals[3*idx.normal_index+1]; - float nz = attrib.normals[3*idx.normal_index+2]; - float tx = attrib.texcoords[2*idx.texcoord_index+0]; - float ty = attrib.texcoords[2*idx.texcoord_index+1]; + tinyobj::real_t vx = attrib.vertices[3*idx.vertex_index+0]; + tinyobj::real_t vy = attrib.vertices[3*idx.vertex_index+1]; + tinyobj::real_t vz = attrib.vertices[3*idx.vertex_index+2]; + tinyobj::real_t nx = attrib.normals[3*idx.normal_index+0]; + tinyobj::real_t ny = attrib.normals[3*idx.normal_index+1]; + tinyobj::real_t nz = attrib.normals[3*idx.normal_index+2]; + tinyobj::real_t tx = attrib.texcoords[2*idx.texcoord_index+0]; + tinyobj::real_t ty = attrib.texcoords[2*idx.texcoord_index+1]; } index_offset += fv; -- cgit v1.2.3 From 889b2187c1baa2e918069ae7fb7b14348ba4574b Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Tue, 9 May 2017 01:23:09 +0900 Subject: Add link to Vulkan Cookbook. --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 99bf9e9..417e628 100644 --- a/README.md +++ b/README.md @@ -54,6 +54,7 @@ TinyObjLoader is successfully used in ... * Double precision support through `TINYOBJLOADER_USE_DOUBLE` thanks to noma * Loading models in Vulkan Tutorial https://vulkan-tutorial.com/Loading_models * .obj viewer with Metal https://github.com/middlefeng/NuoModelViewer/tree/master +* Vulkan Cookbook https://github.com/PacktPublishing/Vulkan-Cookbook * Your project here! ### Old version(v0.9.x) -- cgit v1.2.3 From cc948e4c443aac525ca3822c602ae871dc35c9a6 Mon Sep 17 00:00:00 2001 From: alangfel Date: Fri, 12 May 2017 08:34:05 +0200 Subject: Deal with more than one texture option After processing one textureoption like "-s u v w" the next option " -o u v w" has a beginning whitespace. Due to this it does not match to the option "-o" and it is skipped. --- tiny_obj_loader.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tiny_obj_loader.h b/tiny_obj_loader.h index ee44076..a666f33 100644 --- a/tiny_obj_loader.h +++ b/tiny_obj_loader.h @@ -788,6 +788,7 @@ static bool ParseTextureNameAndOption(std::string *texname, const char *token = linebuf; // Assume line ends with NULL while (!IS_NEW_LINE((*token))) { + token += strspn(token, " \t"); // skip space if ((0 == strncmp(token, "-blendu", 7)) && IS_SPACE((token[7]))) { token += 8; texopt->blendu = parseOnOff(&token, /* default */ true); @@ -831,7 +832,6 @@ static bool ParseTextureNameAndOption(std::string *texname, parseReal2(&(texopt->brightness), &(texopt->contrast), &token, 0.0, 1.0); } else { // Assume texture filename - token += strspn(token, " \t"); // skip space size_t len = strcspn(token, " \t\r"); // untile next space texture_name = std::string(token, token + len); token += len; -- cgit v1.2.3 From 47989b591faa70d7e1f1a05c68d11d5fdce30b6e Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Wed, 24 May 2017 17:22:46 +0900 Subject: Update copyright year and version. --- tiny_obj_loader.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tiny_obj_loader.h b/tiny_obj_loader.h index a666f33..bc69ad9 100644 --- a/tiny_obj_loader.h +++ b/tiny_obj_loader.h @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2012-2016 Syoyo Fujita and many contributors. +Copyright (c) 2012-2017 Syoyo Fujita and many contributors. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -23,6 +23,7 @@ THE SOFTWARE. */ // +// version 1.0.7 : Support multiple tex options(#126) // version 1.0.6 : Add TINYOBJLOADER_USE_DOUBLE option(#124) // version 1.0.5 : Ignore `Tr` when `d` exists in MTL(#43) // version 1.0.4 : Support multiple filenames for 'mtllib'(#112) -- cgit v1.2.3 From 44bff466e566454086fa462db98f6f846375f64d Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Wed, 24 May 2017 17:43:45 +0900 Subject: Initial support of reflection map(`refl`). --- models/refl.mtl | 25 +++++++++++++++++++++++++ models/refl.obj | 32 ++++++++++++++++++++++++++++++++ tests/tester.cc | 21 +++++++++++++++++++++ tiny_obj_loader.h | 12 ++++++++++++ 4 files changed, 90 insertions(+) create mode 100644 models/refl.mtl create mode 100644 models/refl.obj diff --git a/models/refl.mtl b/models/refl.mtl new file mode 100644 index 0000000..7e04f28 --- /dev/null +++ b/models/refl.mtl @@ -0,0 +1,25 @@ +newmtl white +Ka 0 0 0 +Kd 1 1 1 +Ks 0 0 0 +refl reflection.tga + +newmtl red +Ka 0 0 0 +Kd 1 0 0 +Ks 0 0 0 + +newmtl green +Ka 0 0 0 +Kd 0 1 0 +Ks 0 0 0 + +newmtl blue +Ka 0 0 0 +Kd 0 0 1 +Ks 0 0 0 + +newmtl light +Ka 20 20 20 +Kd 1 1 1 +Ks 0 0 0 diff --git a/models/refl.obj b/models/refl.obj new file mode 100644 index 0000000..e9715f5 --- /dev/null +++ b/models/refl.obj @@ -0,0 +1,32 @@ +# Test for `refl` material parameter +mtllib refl.mtl + +v 0.000000 2.000000 2.000000 +v 0.000000 0.000000 2.000000 +v 2.000000 0.000000 2.000000 +v 2.000000 2.000000 2.000000 +v 0.000000 2.000000 0.000000 +v 0.000000 0.000000 0.000000 +v 2.000000 0.000000 0.000000 +v 2.000000 2.000000 0.000000 +# 8 vertices + +g front cube +usemtl white +f 1 2 3 4 +g back cube +# expects white material +f 8 7 6 5 +g right cube +usemtl red +f 4 3 7 8 +g top cube +usemtl white +f 5 1 4 8 +g left cube +usemtl green +f 5 6 2 1 +g bottom cube +usemtl white +f 2 6 7 3 +# 6 elements diff --git a/tests/tester.cc b/tests/tester.cc index 80736eb..649451d 100644 --- a/tests/tester.cc +++ b/tests/tester.cc @@ -145,6 +145,7 @@ static void PrintInfo(const tinyobj::attrib_t &attrib, const std::vector::const_iterator it(materials[i].unknown_parameter.begin()); std::map::const_iterator itEnd(materials[i].unknown_parameter.end()); @@ -564,6 +565,26 @@ TEST_CASE("tr_and_d", "[Issue43]") { REQUIRE(0.75 == Approx(materials[1].dissolve)); } +TEST_CASE("refl", "[refl]") { + tinyobj::attrib_t attrib; + std::vector shapes; + std::vector materials; + + std::string err; + bool ret = tinyobj::LoadObj(&attrib, &shapes, &materials, &err, "../models/refl.obj", gMtlBasePath); + + if (!err.empty()) { + std::cerr << err << std::endl; + } + + PrintInfo(attrib, shapes, materials); + + REQUIRE(true == ret); + REQUIRE(5 == materials.size()); + + REQUIRE(materials[0].reflection_texname.compare("reflection.tga") == 0); +} + #if 0 int main( diff --git a/tiny_obj_loader.h b/tiny_obj_loader.h index bc69ad9..3c80145 100644 --- a/tiny_obj_loader.h +++ b/tiny_obj_loader.h @@ -156,6 +156,7 @@ typedef struct { std::string bump_texname; // map_bump, bump std::string displacement_texname; // disp std::string alpha_texname; // map_d + std::string reflection_texname; // refl texture_option_t ambient_texopt; texture_option_t diffuse_texopt; @@ -164,6 +165,7 @@ typedef struct { texture_option_t bump_texopt; texture_option_t displacement_texopt; texture_option_t alpha_texopt; + texture_option_t reflection_texopt; // PBR extension // http://exocortex.com/blog/extending_wavefront_mtl_to_support_pbr @@ -859,6 +861,7 @@ static void InitMaterial(material_t *material) { material->specular_highlight_texname = ""; material->bump_texname = ""; material->displacement_texname = ""; + material->reflection_texname = ""; material->alpha_texname = ""; for (int i = 0; i < 3; i++) { material->ambient[i] = 0.f; @@ -1265,6 +1268,15 @@ void LoadMtl(std::map *material_map, continue; } + // reflection map + if ((0 == strncmp(token, "refl", 4)) && IS_SPACE(token[4])) { + token += 5; + ParseTextureNameAndOption(&(material.reflection_texname), + &(material.reflection_texopt), token, + /* is_bump */ false); + continue; + } + // PBR: roughness texture if ((0 == strncmp(token, "map_Pr", 6)) && IS_SPACE(token[6])) { token += 7; -- cgit v1.2.3 From cdb5c2d37558a65cd144201b4fd8534e027f94f4 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Fri, 2 Jun 2017 03:04:42 +0900 Subject: Fix the number of triangle calculation. Fixes #127 --- examples/viewer/viewer.cc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/examples/viewer/viewer.cc b/examples/viewer/viewer.cc index 3df5361..c660de9 100644 --- a/examples/viewer/viewer.cc +++ b/examples/viewer/viewer.cc @@ -421,7 +421,8 @@ static bool LoadObjAndConvert(float bmin[3], float bmax[3], glBindBuffer(GL_ARRAY_BUFFER, o.vb); glBufferData(GL_ARRAY_BUFFER, vb.size() * sizeof(float), &vb.at(0), GL_STATIC_DRAW); - o.numTriangles = vb.size() / (3 + 3 + 3 + 2) * 3; + o.numTriangles = vb.size() / (3 + 3 + 3 + 2) / 3; // 3:vtx, 3:normal, 3:col, 2:texcoord + printf("shape[%d] # of triangles = %d\n", static_cast(s), o.numTriangles); } -- cgit v1.2.3 From fb80e0421275403a929743cad86b71de7ffd646e Mon Sep 17 00:00:00 2001 From: Shane Loretz Date: Thu, 1 Jun 2017 14:05:03 -0700 Subject: Check sentry to remove unused variable warning --- tiny_obj_loader.h | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/tiny_obj_loader.h b/tiny_obj_loader.h index 3c80145..1006070 100644 --- a/tiny_obj_loader.h +++ b/tiny_obj_loader.h @@ -401,20 +401,22 @@ static std::istream &safeGetline(std::istream &is, std::string &t) { 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 += static_cast(c); + if (se) { + 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 += static_cast(c); + } } } } -- cgit v1.2.3 From 0f4a955e0164e14aa108539f41eba38dd54a0402 Mon Sep 17 00:00:00 2001 From: Shane Loretz Date: Thu, 1 Jun 2017 14:09:15 -0700 Subject: 4096 -> TINYOBJ_SSCANF_BUFFER_SIZE --- tiny_obj_loader.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tiny_obj_loader.h b/tiny_obj_loader.h index 1006070..07a876c 100644 --- a/tiny_obj_loader.h +++ b/tiny_obj_loader.h @@ -1676,7 +1676,7 @@ bool LoadObj(attrib_t *attrib, std::vector *shapes, if (token[0] == 't' && IS_SPACE(token[1])) { tag_t tag; - char namebuf[4096]; + char namebuf[TINYOBJ_SSCANF_BUFFER_SIZE]; token += 2; #ifdef _MSC_VER sscanf_s(token, "%s", namebuf, (unsigned)_countof(namebuf)); @@ -1704,7 +1704,7 @@ bool LoadObj(attrib_t *attrib, std::vector *shapes, tag.stringValues.resize(static_cast(ts.num_strings)); for (size_t i = 0; i < static_cast(ts.num_strings); ++i) { - char stringValueBuffer[4096]; + char stringValueBuffer[TINYOBJ_SSCANF_BUFFER_SIZE]; #ifdef _MSC_VER sscanf_s(token, "%s", stringValueBuffer, @@ -1986,7 +1986,7 @@ bool LoadObjWithCallback(std::istream &inStream, const callback_t &callback, if (token[0] == 't' && IS_SPACE(token[1])) { tag_t tag; - char namebuf[4096]; + char namebuf[TINYOBJ_SSCANF_BUFFER_SIZE]; token += 2; #ifdef _MSC_VER sscanf_s(token, "%s", namebuf, (unsigned)_countof(namebuf)); @@ -2014,7 +2014,7 @@ bool LoadObjWithCallback(std::istream &inStream, const callback_t &callback, tag.stringValues.resize(static_cast(ts.num_strings)); for (size_t i = 0; i < static_cast(ts.num_strings); ++i) { - char stringValueBuffer[4096]; + char stringValueBuffer[TINYOBJ_SSCANF_BUFFER_SIZE]; #ifdef _MSC_VER sscanf_s(token, "%s", stringValueBuffer, -- cgit v1.2.3 From 56fa047ba9eada1bf72cd6f716f04515d3825c11 Mon Sep 17 00:00:00 2001 From: Shane Loretz Date: Thu, 1 Jun 2017 14:14:52 -0700 Subject: Replace sscanf with snprintf --- tiny_obj_loader.h | 57 +++++++++---------------------------------------------- 1 file changed, 9 insertions(+), 48 deletions(-) diff --git a/tiny_obj_loader.h b/tiny_obj_loader.h index 07a876c..b70f30d 100644 --- a/tiny_obj_loader.h +++ b/tiny_obj_loader.h @@ -1032,11 +1032,7 @@ void LoadMtl(std::map *material_map, // set new mtl name char namebuf[TINYOBJ_SSCANF_BUFFER_SIZE]; token += 7; -#ifdef _MSC_VER - sscanf_s(token, "%s", namebuf, (unsigned)_countof(namebuf)); -#else - std::sscanf(token, "%s", namebuf); -#endif + std::snprintf(namebuf, TINYOBJ_SSCANF_BUFFER_SIZE, "%s", token); material.name = namebuf; continue; } @@ -1543,11 +1539,7 @@ bool LoadObj(attrib_t *attrib, std::vector *shapes, if ((0 == strncmp(token, "usemtl", 6)) && IS_SPACE((token[6]))) { char namebuf[TINYOBJ_SSCANF_BUFFER_SIZE]; token += 7; -#ifdef _MSC_VER - sscanf_s(token, "%s", namebuf, (unsigned)_countof(namebuf)); -#else - std::sscanf(token, "%s", namebuf); -#endif + std::snprintf(namebuf, TINYOBJ_SSCANF_BUFFER_SIZE, "%s", token); int newMaterialId = -1; if (material_map.find(namebuf) != material_map.end()) { @@ -1663,11 +1655,7 @@ bool LoadObj(attrib_t *attrib, std::vector *shapes, // @todo { multiple object name? } char namebuf[TINYOBJ_SSCANF_BUFFER_SIZE]; token += 2; -#ifdef _MSC_VER - sscanf_s(token, "%s", namebuf, (unsigned)_countof(namebuf)); -#else - std::sscanf(token, "%s", namebuf); -#endif + std::snprintf(namebuf, TINYOBJ_SSCANF_BUFFER_SIZE, "%s", token); name = std::string(namebuf); continue; @@ -1678,11 +1666,7 @@ bool LoadObj(attrib_t *attrib, std::vector *shapes, char namebuf[TINYOBJ_SSCANF_BUFFER_SIZE]; token += 2; -#ifdef _MSC_VER - sscanf_s(token, "%s", namebuf, (unsigned)_countof(namebuf)); -#else - std::sscanf(token, "%s", namebuf); -#endif + std::snprintf(namebuf, TINYOBJ_SSCANF_BUFFER_SIZE, "%s", token); tag.name = std::string(namebuf); token += tag.name.size() + 1; @@ -1706,12 +1690,7 @@ bool LoadObj(attrib_t *attrib, std::vector *shapes, for (size_t i = 0; i < static_cast(ts.num_strings); ++i) { char stringValueBuffer[TINYOBJ_SSCANF_BUFFER_SIZE]; -#ifdef _MSC_VER - sscanf_s(token, "%s", stringValueBuffer, - (unsigned)_countof(stringValueBuffer)); -#else - std::sscanf(token, "%s", stringValueBuffer); -#endif + std::snprintf(stringValueBuffer, TINYOBJ_SSCANF_BUFFER_SIZE, "%s", token); tag.stringValues[i] = stringValueBuffer; token += tag.stringValues[i].size() + 1; } @@ -1853,12 +1832,7 @@ bool LoadObjWithCallback(std::istream &inStream, const callback_t &callback, if ((0 == strncmp(token, "usemtl", 6)) && IS_SPACE((token[6]))) { char namebuf[TINYOBJ_SSCANF_BUFFER_SIZE]; token += 7; -#ifdef _MSC_VER - sscanf_s(token, "%s", namebuf, - static_cast(_countof(namebuf))); -#else - std::sscanf(token, "%s", namebuf); -#endif + std::snprintf(namebuf, TINYOBJ_SSCANF_BUFFER_SIZE, "%s", token); int newMaterialId = -1; if (material_map.find(namebuf) != material_map.end()) { @@ -1968,11 +1942,7 @@ bool LoadObjWithCallback(std::istream &inStream, const callback_t &callback, // @todo { multiple object name? } char namebuf[TINYOBJ_SSCANF_BUFFER_SIZE]; token += 2; -#ifdef _MSC_VER - sscanf_s(token, "%s", namebuf, (unsigned)_countof(namebuf)); -#else - std::sscanf(token, "%s", namebuf); -#endif + std::snprintf(namebuf, TINYOBJ_SSCANF_BUFFER_SIZE, "%s", token); std::string object_name = std::string(namebuf); if (callback.object_cb) { @@ -1988,11 +1958,7 @@ bool LoadObjWithCallback(std::istream &inStream, const callback_t &callback, char namebuf[TINYOBJ_SSCANF_BUFFER_SIZE]; token += 2; -#ifdef _MSC_VER - sscanf_s(token, "%s", namebuf, (unsigned)_countof(namebuf)); -#else - std::sscanf(token, "%s", namebuf); -#endif + std::snprintf(namebuf, TINYOBJ_SSCANF_BUFFER_SIZE, "%s", token); tag.name = std::string(namebuf); token += tag.name.size() + 1; @@ -2016,12 +1982,7 @@ bool LoadObjWithCallback(std::istream &inStream, const callback_t &callback, for (size_t i = 0; i < static_cast(ts.num_strings); ++i) { char stringValueBuffer[TINYOBJ_SSCANF_BUFFER_SIZE]; -#ifdef _MSC_VER - sscanf_s(token, "%s", stringValueBuffer, - (unsigned)_countof(stringValueBuffer)); -#else - std::sscanf(token, "%s", stringValueBuffer); -#endif + std::snprintf(stringValueBuffer, TINYOBJ_SSCANF_BUFFER_SIZE, "%s", token); tag.stringValues[i] = stringValueBuffer; token += tag.stringValues[i].size() + 1; } -- cgit v1.2.3 From a1324f17fd186c22bda0fa44ad8eda50dec26e87 Mon Sep 17 00:00:00 2001 From: Shane Loretz Date: Fri, 2 Jun 2017 07:28:20 -0700 Subject: Replaced snprintf with stringstream Deleted now unused TINYOBJ_SSCANF_BUFFER_SIZE --- tiny_obj_loader.h | 59 ++++++++++++++++++++++++++++--------------------------- 1 file changed, 30 insertions(+), 29 deletions(-) diff --git a/tiny_obj_loader.h b/tiny_obj_loader.h index b70f30d..1a506da 100644 --- a/tiny_obj_loader.h +++ b/tiny_obj_loader.h @@ -44,6 +44,7 @@ THE SOFTWARE. #define TINY_OBJ_LOADER_H_ #include +#include #include #include @@ -364,7 +365,6 @@ namespace tinyobj { MaterialReader::~MaterialReader() {} -#define TINYOBJ_SSCANF_BUFFER_SIZE (4096) struct vertex_index { int v_idx, vt_idx, vn_idx; @@ -1030,10 +1030,10 @@ void LoadMtl(std::map *material_map, has_tr = false; // set new mtl name - char namebuf[TINYOBJ_SSCANF_BUFFER_SIZE]; token += 7; - std::snprintf(namebuf, TINYOBJ_SSCANF_BUFFER_SIZE, "%s", token); - material.name = namebuf; + std::stringstream ss; + ss << token; + material.name = ss.str(); continue; } @@ -1537,9 +1537,10 @@ bool LoadObj(attrib_t *attrib, std::vector *shapes, // use mtl if ((0 == strncmp(token, "usemtl", 6)) && IS_SPACE((token[6]))) { - char namebuf[TINYOBJ_SSCANF_BUFFER_SIZE]; token += 7; - std::snprintf(namebuf, TINYOBJ_SSCANF_BUFFER_SIZE, "%s", token); + std::stringstream ss; + ss << token; + std::string namebuf = ss.str(); int newMaterialId = -1; if (material_map.find(namebuf) != material_map.end()) { @@ -1653,10 +1654,10 @@ bool LoadObj(attrib_t *attrib, std::vector *shapes, shape = shape_t(); // @todo { multiple object name? } - char namebuf[TINYOBJ_SSCANF_BUFFER_SIZE]; token += 2; - std::snprintf(namebuf, TINYOBJ_SSCANF_BUFFER_SIZE, "%s", token); - name = std::string(namebuf); + std::stringstream ss; + ss << token; + name = ss.str(); continue; } @@ -1664,10 +1665,10 @@ bool LoadObj(attrib_t *attrib, std::vector *shapes, if (token[0] == 't' && IS_SPACE(token[1])) { tag_t tag; - char namebuf[TINYOBJ_SSCANF_BUFFER_SIZE]; token += 2; - std::snprintf(namebuf, TINYOBJ_SSCANF_BUFFER_SIZE, "%s", token); - tag.name = std::string(namebuf); + std::stringstream ss; + ss << token; + tag.name = ss.str(); token += tag.name.size() + 1; @@ -1688,10 +1689,9 @@ bool LoadObj(attrib_t *attrib, std::vector *shapes, tag.stringValues.resize(static_cast(ts.num_strings)); for (size_t i = 0; i < static_cast(ts.num_strings); ++i) { - char stringValueBuffer[TINYOBJ_SSCANF_BUFFER_SIZE]; - - std::snprintf(stringValueBuffer, TINYOBJ_SSCANF_BUFFER_SIZE, "%s", token); - tag.stringValues[i] = stringValueBuffer; + std::stringstream ss; + ss << token; + tag.stringValues[i] = ss.str(); token += tag.stringValues[i].size() + 1; } @@ -1830,9 +1830,10 @@ bool LoadObjWithCallback(std::istream &inStream, const callback_t &callback, // use mtl if ((0 == strncmp(token, "usemtl", 6)) && IS_SPACE((token[6]))) { - char namebuf[TINYOBJ_SSCANF_BUFFER_SIZE]; token += 7; - std::snprintf(namebuf, TINYOBJ_SSCANF_BUFFER_SIZE, "%s", token); + std::stringstream ss; + ss << token; + std::string namebuf = ss.str(); int newMaterialId = -1; if (material_map.find(namebuf) != material_map.end()) { @@ -1846,7 +1847,7 @@ bool LoadObjWithCallback(std::istream &inStream, const callback_t &callback, } if (callback.usemtl_cb) { - callback.usemtl_cb(user_data, namebuf, material_id); + callback.usemtl_cb(user_data, namebuf.c_str(), material_id); } continue; @@ -1940,10 +1941,11 @@ bool LoadObjWithCallback(std::istream &inStream, const callback_t &callback, // object name if (token[0] == 'o' && IS_SPACE((token[1]))) { // @todo { multiple object name? } - char namebuf[TINYOBJ_SSCANF_BUFFER_SIZE]; token += 2; - std::snprintf(namebuf, TINYOBJ_SSCANF_BUFFER_SIZE, "%s", token); - std::string object_name = std::string(namebuf); + + std::stringstream ss; + ss << token; + std::string object_name = ss.str(); if (callback.object_cb) { callback.object_cb(user_data, object_name.c_str()); @@ -1956,10 +1958,10 @@ bool LoadObjWithCallback(std::istream &inStream, const callback_t &callback, if (token[0] == 't' && IS_SPACE(token[1])) { tag_t tag; - char namebuf[TINYOBJ_SSCANF_BUFFER_SIZE]; token += 2; - std::snprintf(namebuf, TINYOBJ_SSCANF_BUFFER_SIZE, "%s", token); - tag.name = std::string(namebuf); + std::stringstream ss; + ss << token; + tag.name = ss.str(); token += tag.name.size() + 1; @@ -1980,10 +1982,9 @@ bool LoadObjWithCallback(std::istream &inStream, const callback_t &callback, tag.stringValues.resize(static_cast(ts.num_strings)); for (size_t i = 0; i < static_cast(ts.num_strings); ++i) { - char stringValueBuffer[TINYOBJ_SSCANF_BUFFER_SIZE]; - - std::snprintf(stringValueBuffer, TINYOBJ_SSCANF_BUFFER_SIZE, "%s", token); - tag.stringValues[i] = stringValueBuffer; + std::stringstream ss; + ss << token; + tag.stringValues[i] = ss.str(); token += tag.stringValues[i].size() + 1; } -- cgit v1.2.3 From 6eca09f2bfd910556855ca8af816c504cb2c2401 Mon Sep 17 00:00:00 2001 From: Shane Loretz Date: Fri, 2 Jun 2017 07:31:19 -0700 Subject: Removed double import of sstream --- tiny_obj_loader.h | 1 - 1 file changed, 1 deletion(-) diff --git a/tiny_obj_loader.h b/tiny_obj_loader.h index 1a506da..4420831 100644 --- a/tiny_obj_loader.h +++ b/tiny_obj_loader.h @@ -44,7 +44,6 @@ THE SOFTWARE. #define TINY_OBJ_LOADER_H_ #include -#include #include #include -- cgit v1.2.3 From 99518b6d3e7c624d97e5e4791efcc96755c65d44 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Mon, 5 Jun 2017 02:00:32 +0900 Subject: Suppress some clang warnings. --- tiny_obj_loader.h | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/tiny_obj_loader.h b/tiny_obj_loader.h index 4420831..3273e9d 100644 --- a/tiny_obj_loader.h +++ b/tiny_obj_loader.h @@ -177,7 +177,6 @@ typedef struct { real_t anisotropy; // aniso. [0, 1] default 0 real_t anisotropy_rotation; // anisor. [0, 1] default 0 real_t pad0; - real_t pad1; std::string roughness_texname; // map_Pr std::string metallic_texname; // map_Pm std::string sheen_texname; // map_Ps @@ -418,6 +417,8 @@ static std::istream &safeGetline(std::istream &is, std::string &t) { } } } + + return is; } #define IS_SPACE(x) (((x) == ' ') || ((x) == '\t')) @@ -1030,9 +1031,11 @@ void LoadMtl(std::map *material_map, // set new mtl name token += 7; - std::stringstream ss; - ss << token; - material.name = ss.str(); + { + std::stringstream sstr; + sstr << token; + material.name = sstr.str(); + } continue; } @@ -1688,9 +1691,9 @@ bool LoadObj(attrib_t *attrib, std::vector *shapes, tag.stringValues.resize(static_cast(ts.num_strings)); for (size_t i = 0; i < static_cast(ts.num_strings); ++i) { - std::stringstream ss; - ss << token; - tag.stringValues[i] = ss.str(); + std::stringstream sstr; + sstr << token; + tag.stringValues[i] = sstr.str(); token += tag.stringValues[i].size() + 1; } -- cgit v1.2.3 From 95fba2ab323d69aab94aee01e0594d2a899fe722 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Wed, 5 Jul 2017 16:32:40 +0900 Subject: Changed to use lfpAlloc from ltalloc for experimental multi-threaded .obj parser since ltalloc is not a porable(e.g. it does not support ARM archtecture). --- experimental/README.md | 13 +- experimental/lfpAlloc/Allocator.hpp | 89 +++ experimental/lfpAlloc/ChunkList.hpp | 116 ++++ experimental/lfpAlloc/LICENSE | 21 + experimental/lfpAlloc/Pool.hpp | 48 ++ experimental/lfpAlloc/PoolDispatcher.hpp | 79 +++ experimental/lfpAlloc/Utils.hpp | 20 + experimental/ltalloc.cc | 973 ------------------------------- experimental/ltalloc.h | 14 - experimental/ltalloc.hpp | 59 -- experimental/premake5.lua | 3 +- experimental/tinyobj_loader_opt.h | 31 +- 12 files changed, 402 insertions(+), 1064 deletions(-) create mode 100644 experimental/lfpAlloc/Allocator.hpp create mode 100644 experimental/lfpAlloc/ChunkList.hpp create mode 100644 experimental/lfpAlloc/LICENSE create mode 100644 experimental/lfpAlloc/Pool.hpp create mode 100644 experimental/lfpAlloc/PoolDispatcher.hpp create mode 100644 experimental/lfpAlloc/Utils.hpp delete mode 100644 experimental/ltalloc.cc delete mode 100644 experimental/ltalloc.h delete mode 100644 experimental/ltalloc.hpp diff --git a/experimental/README.md b/experimental/README.md index 9a88abb..99378ce 100644 --- a/experimental/README.md +++ b/experimental/README.md @@ -1,5 +1,16 @@ -Experimental code for .obj loader. +# Experimental code for .obj loader. * Multi-threaded optimized parser : tinyobj_loader_opt.h + +## Requirements + +* C++-11 compiler + +## Compile options + * zstd compressed .obj support. `--with-zstd` premake option. * gzip compressed .obj support. `--with-zlib` premake option. + +## Licenses + +* lfpAlloc : MIT license. diff --git a/experimental/lfpAlloc/Allocator.hpp b/experimental/lfpAlloc/Allocator.hpp new file mode 100644 index 0000000..4dddaab --- /dev/null +++ b/experimental/lfpAlloc/Allocator.hpp @@ -0,0 +1,89 @@ +#ifndef LF_POOL_ALLOCATOR +#define LF_POOL_ALLOCATOR + +#include +#include +#include + +namespace lfpAlloc { +template +class lfpAllocator { +public: + using value_type = T; + using pointer = T*; + using const_pointer = const T*; + using reference = T&; + using const_reference = T const&; + + template + struct rebind { + typedef lfpAllocator other; + }; + + lfpAllocator() {} + + template + lfpAllocator(lfpAllocator&&) noexcept {} + + template + lfpAllocator(const lfpAllocator&) noexcept {} + + T* allocate(std::size_t count) { + if (sizeof(T) * count <= + alignof(std::max_align_t) * NumPools - sizeof(void*)) { + return reinterpret_cast( + dispatcher_.allocate(sizeof(T) * count)); + } else { + return new T[count]; + } + } + + void deallocate(T* p, std::size_t count) noexcept { + if (sizeof(T) * count <= + alignof(std::max_align_t) * NumPools - sizeof(void*)) { + dispatcher_.deallocate(p, sizeof(T) * count); + } else { + delete[] p; + } + } + + // Should not be required, but allocator_traits is not complete in + // gcc 4.9.1 + template + void destroy(U* p) { + p->~U(); + } + + template + void construct(U* p, Args&&... args) { + new (p) U(std::forward(args)...); + } + + template + friend bool operator==(const lfpAllocator&, + const lfpAllocator&) noexcept; + + template + friend class lfpAllocator; + +private: + static PoolDispatcher dispatcher_; +}; + +template +PoolDispatcher lfpAllocator::dispatcher_; + +template +inline bool operator==(const lfpAllocator&, + const lfpAllocator&) noexcept { + return N == M; +} + +template +inline bool operator!=(const lfpAllocator& left, + const lfpAllocator& right) noexcept { + return !(left == right); +} +} + +#endif diff --git a/experimental/lfpAlloc/ChunkList.hpp b/experimental/lfpAlloc/ChunkList.hpp new file mode 100644 index 0000000..760c670 --- /dev/null +++ b/experimental/lfpAlloc/ChunkList.hpp @@ -0,0 +1,116 @@ +#ifndef LF_POOL_ALLOC_CHUNK_LIST +#define LF_POOL_ALLOC_CHUNK_LIST + +#include +#include +#include + +#ifndef LFP_ALLOW_BLOCKING +static_assert(ATOMIC_POINTER_LOCK_FREE == 2, + "Atomic pointer is not lock-free."); +#endif + +namespace lfpAlloc { + +template +struct Cell { + uint8_t val_[Size]; + Cell* next_ = this + 1; +}; + +// For small types (less than the size of void*), no additional +// space is needed, so union val_ with next_ to avoid overhead. +template <> +struct Cell<0> { + Cell() : next_{this + 1} {} + union { + uint8_t val_[sizeof(Cell*)]; + Cell* next_; + }; +}; + +template +struct Chunk { + Chunk() noexcept { + auto& last = memBlock_[AllocationsPerChunk - 1]; + last.next_ = nullptr; + } + Cell memBlock_[AllocationsPerChunk]; +}; + +template +struct Node { + Node() : val_(), next_(nullptr) {} + Node(const T& val) : val_(val), next_(nullptr) {} + T val_; + std::atomic*> next_; +}; + +template +class ChunkList { + static constexpr auto CellSize = + (Size > sizeof(void*)) ? Size - sizeof(void*) : 0; + using Chunk_t = Chunk; + using Cell_t = Cell; + + using ChunkNode = Node; + using CellNode = Node; + +public: + static ChunkList& getInstance() { + static ChunkList c; + return c; + } + + Cell_t* allocateChain() { + CellNode* recentHead = head_.load(); + CellNode* currentNext = nullptr; + do { + // If there are no available chains, allocate a new chunk + if (!recentHead) { + ChunkNode* currentHandle; + + // Make a new node + auto newChunk = new ChunkNode(); + + // Add the chunk to the chain + do { + currentHandle = handle_.load(); + newChunk->next_ = currentHandle; + } while ( + !handle_.compare_exchange_weak(currentHandle, newChunk)); + return &newChunk->val_.memBlock_[0]; + } + + currentNext = recentHead->next_; + } while (!head_.compare_exchange_weak(recentHead, currentNext)); + + auto retnValue = recentHead->val_; + delete recentHead; + return retnValue; + } + + void deallocateChain(Cell_t* newCell) { + if (!newCell) { + return; + } + CellNode* currentHead = head_.load(); + + // Construct a new node to be added to the linked list + CellNode* newHead = new CellNode(newCell); + + // Add the chain to the linked list + do { + newHead->next_.store(currentHead, std::memory_order_release); + } while (!head_.compare_exchange_weak(currentHead, newHead)); + } + +private: + ChunkList() : handle_(nullptr), head_(nullptr) {} + + std::atomic handle_; + std::atomic head_; +}; +} + +#endif diff --git a/experimental/lfpAlloc/LICENSE b/experimental/lfpAlloc/LICENSE new file mode 100644 index 0000000..b9e2c10 --- /dev/null +++ b/experimental/lfpAlloc/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2014 Adam Schwalm + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. \ No newline at end of file diff --git a/experimental/lfpAlloc/Pool.hpp b/experimental/lfpAlloc/Pool.hpp new file mode 100644 index 0000000..370dab7 --- /dev/null +++ b/experimental/lfpAlloc/Pool.hpp @@ -0,0 +1,48 @@ +#ifndef LF_POOL_ALLOC_POOL +#define LF_POOL_ALLOC_POOL + +#include +#include + +namespace lfpAlloc { +template +class Pool { + using ChunkList_t = ChunkList; + +public: + static constexpr auto CellSize = + (Size > sizeof(void*)) ? Size - sizeof(void*) : 0; + using Cell_t = Cell; + + Pool() : head_(nullptr) {} + + ~Pool() { ChunkList_t::getInstance().deallocateChain(head_); } + + void* allocate() { + // Head loaded from head_ + Cell_t* currentHead = head_; + Cell_t* next; + + // Out of cells to allocate + if (!currentHead) { + currentHead = ChunkList_t::getInstance().allocateChain(); + } + + next = currentHead->next_; + head_ = next; + return ¤tHead->val_; + } + + void deallocate(void* p) noexcept { + auto newHead = reinterpret_cast(p); + Cell_t* currentHead = head_; + newHead->next_ = currentHead; + head_ = newHead; + } + +private: + Cell_t* head_; +}; +} + +#endif diff --git a/experimental/lfpAlloc/PoolDispatcher.hpp b/experimental/lfpAlloc/PoolDispatcher.hpp new file mode 100644 index 0000000..e4d1427 --- /dev/null +++ b/experimental/lfpAlloc/PoolDispatcher.hpp @@ -0,0 +1,79 @@ +#ifndef LF_POOL_DISPATCHER +#define LF_POOL_DISPATCHER + +#include +#include +#include +#include + +#ifndef LFP_ALLOCATIONS_PER_CHUNK +#define LFP_ALLOCATIONS_PER_CHUNK 64 * 100 +#endif + +namespace lfpAlloc { +namespace detail { + +template +struct Pools : Pools {}; + +template +struct Pools<0, Size...> { + using type = std::tuple...>; +}; +} + +template +class PoolDispatcher { +public: + void* allocate(std::size_t size) { return dispatchAllocate<0>(size); } + + void deallocate(void* p, std::size_t size) noexcept { + dispatchDeallocate<0>(p, size); + } + +private: + thread_local static typename detail::Pools::type pools_; + static_assert(NumPools > 0, "Invalid number of pools"); + + template + typename std::enable_if < + Index::type + dispatchAllocate(std::size_t const& requestSize) { + if (requestSize <= std::get(pools_).CellSize) { + return std::get(pools_).allocate(); + } else { + return dispatchAllocate(requestSize); + } + } + + template + typename std::enable_if::type + dispatchAllocate(std::size_t const&) { + assert(false && "Invalid allocation size."); + return nullptr; + } + + template + typename std::enable_if < + Index::type + dispatchDeallocate(void* p, std::size_t const& requestSize) noexcept { + if (requestSize <= std::get(pools_).CellSize) { + std::get(pools_).deallocate(p); + } else { + dispatchDeallocate(p, requestSize); + } + } + + template + typename std::enable_if::type + dispatchDeallocate(void*, std::size_t const&) noexcept { + assert(false && "Invalid deallocation size."); + } +}; + +template +thread_local typename detail::Pools::type + PoolDispatcher::pools_; +} + +#endif diff --git a/experimental/lfpAlloc/Utils.hpp b/experimental/lfpAlloc/Utils.hpp new file mode 100644 index 0000000..8740a79 --- /dev/null +++ b/experimental/lfpAlloc/Utils.hpp @@ -0,0 +1,20 @@ +#include + +namespace lfpAlloc { +namespace detail { +template +struct Log { + enum { value = 1 + Log::value }; +}; + +template +struct Log<1, base> { + enum { value = 0 }; +}; + +template +struct Log<0, base> { + enum { value = 0 }; +}; +} +} diff --git a/experimental/ltalloc.cc b/experimental/ltalloc.cc deleted file mode 100644 index 1d32b44..0000000 --- a/experimental/ltalloc.cc +++ /dev/null @@ -1,973 +0,0 @@ -/* -Copyright (c) 2013, Alexander Tretyak -Copyright (c) 2015, r-lyeh -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: - - * Redistributions of source code must retain the above copyright -notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above -copyright notice, this list of conditions and the following disclaimer -in the documentation and/or other materials provided with the -distribution. - * Neither the name of the author nor the names of its -contributors may be used to endorse or promote products derived -from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - - Note: The latest version of this memory allocator obtainable at - http://ltalloc.googlecode.com/hg/ltalloc.cc - - Project URL: http://code.google.com/p/ltalloc -*/ - -#include "ltalloc.h" - -#define LTALLOC_VERSION "2.0.0" /* (2015/06/16) - ltcalloc(), ltmsize(), ltrealloc(), ltmemalign(), LTALLOC_AUTO_GC_INTERVAL -#define LTALLOC_VERSION "1.0.0" /* (2015/06/16) - standard STL allocator provided [see ltalloc.hpp file](ltalloc.hpp) -#define LTALLOC_VERSION "0.0.0" /* (2013/xx/xx) - fork from public repository */ - -//Customizable constants -//#define LTALLOC_DISABLE_OPERATOR_NEW_OVERRIDE -//#define LTALLOC_AUTO_GC_INTERVAL 3.0 -#ifndef LTALLOC_SIZE_CLASSES_SUBPOWER_OF_TWO -#define LTALLOC_SIZE_CLASSES_SUBPOWER_OF_TWO 2//determines how accurately size classes are spaced (i.e. when = 0, allocation requests are rounded up to the nearest power of two (2^n), when = 1, rounded to 2^n, or (2^n)*1.5, when = 2, rounded to 2^n, (2^n)*1.25, (2^n)*1.5, or (2^n)*1.75, and so on); this parameter have direct influence on memory fragmentation - bigger values lead to reducing internal fragmentation (which can be approximately estimated as pow(0.5, VALUE)*100%), but at the same time increasing external fragmentation -#endif -#define CHUNK_SIZE (64*1024)//size of chunk (basic allocation unit for all allocations of size <= MAX_BLOCK_SIZE); must be a power of two (as well as all following parameters), also should not be less than allocation granularity on Windows (which is always 64K by design of NT kernel) -#define CACHE_LINE_SIZE 64 -#define MAX_NUM_OF_BLOCKS_IN_BATCH 256//maximum number of blocks to move between a thread cache and a central cache in one shot -static const unsigned int MAX_BATCH_SIZE = 64*1024;//maximum total size of blocks to move between a thread cache and a central cache in one shot (corresponds to half size of thread cache of each size class) -static const unsigned int MAX_BLOCK_SIZE = CHUNK_SIZE;//requesting memory of any size greater than this value will lead to direct call of system virtual memory allocation routine - -//Platform-specific stuff - -#ifdef __cplusplus -#define CPPCODE(code) code -#include -#else -#define CPPCODE(code) -#endif - -#ifdef LTALLOC_AUTO_GC_INTERVAL -#include -# if LTALLOC_AUTO_GC_INTERVAL <= 0 -# undef LTALLOC_AUTO_GC_INTERVAL -# define LTALLOC_AUTO_GC_INTERVAL 3.00 -# endif -#endif - -#ifdef __GNUC__ - -#define __STDC_LIMIT_MACROS -#include //for SIZE_MAX -#include //for UINT_MAX -#define alignas(a) __attribute__((aligned(a))) -#define thread_local __thread -#define NOINLINE __attribute__((noinline)) -#define CAS_LOCK(lock) __sync_lock_test_and_set(lock, 1) -#define SPINLOCK_RELEASE(lock) __sync_lock_release(lock) -#define PAUSE __asm__ __volatile__("pause" ::: "memory") -#define BSR(r, v) r = CODE3264(__builtin_clz(v) ^ 31, __builtin_clzll(v) ^ 63)//x ^ 31 = 31 - x, but gcc does not optimize 31 - __builtin_clz(x) to bsr(x), but generates 31 - (bsr(x) ^ 31) - -#elif _MSC_VER - -#define _ALLOW_KEYWORD_MACROS -#include //for SIZE_MAX and UINT_MAX -#define alignas(a) __declspec(align(a)) -#define thread_local __declspec(thread) -#define NOINLINE __declspec(noinline) -#define CAS_LOCK(lock) _InterlockedExchange((long*)lock, 1) -#define SPINLOCK_RELEASE(lock) _InterlockedExchange((long*)lock, 0) -#define PAUSE _mm_pause() -#define BSR(r, v) CODE3264(_BitScanReverse, _BitScanReverse64)((unsigned long*)&r, v) -CPPCODE(extern "C") long _InterlockedExchange(long volatile *, long); -CPPCODE(extern "C") void _mm_pause(); -#pragma warning(disable: 4127 4201 4324 4290)//"conditional expression is constant", "nonstandard extension used : nameless struct/union", and "structure was padded due to __declspec(align())" - -#else -#error Unsupported compiler -#endif - -#if __GNUC__ || __INTEL_COMPILER -#define likely(x) __builtin_expect(!!(x), 1) -#define unlikely(x) __builtin_expect(!!(x), 0) -#else -#define likely(x) (x) -#define unlikely(x) (x) -#endif - -static void SPINLOCK_ACQUIRE(volatile int *lock) {if (CAS_LOCK(lock)) while (*lock || CAS_LOCK(lock)) PAUSE;} - -#include -#include //for memset - -#if SIZE_MAX == UINT_MAX -#define CODE3264(c32, c64) c32 -#else -#define CODE3264(c32, c64) c64 -#endif -typedef char CODE3264_check[sizeof(void*) == CODE3264(4, 8) ? 1 : -1]; - -#ifdef _WIN32 -#define WIN32_LEAN_AND_MEAN -#include - -#define VMALLOC(size) VirtualAlloc(NULL, size, MEM_RESERVE|MEM_COMMIT, PAGE_READWRITE) -#define VMFREE(p, size) VirtualFree(p, 0, MEM_RELEASE) - -#else - -#include -#include - -#define VMALLOC(size) (void*)(((uintptr_t)mmap(NULL, size, PROT_READ|PROT_WRITE, MAP_ANON|MAP_PRIVATE, -1, 0)+1)&~1)//with the conversion of MAP_FAILED to 0 -#define VMFREE(p, size) munmap(p, size) - -static size_t page_size() -{ - assert((uintptr_t)MAP_FAILED+1 == 0);//have to use dynamic check somewhere, because some gcc versions (e.g. 4.4.5) won't compile typedef char MAP_FAILED_value_static_check[(uintptr_t)MAP_FAILED+1 == 0 ? 1 : -1]; - static size_t pagesize = 0; - if (!pagesize) pagesize = sysconf(_SC_PAGE_SIZE);//assuming that writing of size_t value is atomic, so this can be done safely in different simultaneously running threads - return pagesize; -} - -typedef struct PTrieNode // Compressed radix tree (patricia trie) for storing sizes of system allocations -{ // This implementation have some specific properties: - uintptr_t keys[2]; // 1. There are no separate leaf nodes (with both null children), as leaf's value is stored directly in place of corresponding child node pointer. Least significant bit of that pointer used to determine its meaning (i.e., is it a value, or child node pointer). - struct PTrieNode *childNodes[2];// 2. Inserting a new element (key/value) into this tree require to create always an exactly one new node (and similarly for remove key/node operation). -} PTrieNode; // 3. Tree always contains just one node with null child (i.e. all but one nodes in any possible tree are always have two children). -#define PTRIE_NULL_NODE (PTrieNode*)(uintptr_t)1 -static PTrieNode *ptrieRoot = PTRIE_NULL_NODE, *ptrieFreeNodesList = NULL, *ptrieNewAllocatedPage = NULL; -static volatile int ptrieLock = 0; - -static uintptr_t ptrie_lookup(uintptr_t key) -{ - PTrieNode *node = ptrieRoot; - uintptr_t *lastKey = NULL; - while (!((uintptr_t)node & 1)) - { - int branch = (key >> (node->keys[0] & 0xFF)) & 1; - lastKey = &node->keys[branch]; - node = node->childNodes[branch]; - } - assert(lastKey && (*lastKey & ~0xFF) == key); - return (uintptr_t)node & ~1; -} - -static void ptrie_insert(uintptr_t key, uintptr_t value, PTrieNode *newNode/* = (PTrieNode*)malloc(sizeof(PTrieNode))*/) -{ - PTrieNode **node = &ptrieRoot, *n; - uintptr_t *prevKey = NULL, x, pkey; - unsigned int index, b; - assert(!((value & 1) | (key & 0xFF)));//check constraints for key/value - for (;;) - { - n = *node; - if (!((uintptr_t)n & 1))//not a leaf - { - int prefixEnd = n->keys[0] & 0xFF; - x = key ^ n->keys[0];// & ~0xFF; - if (!(x & (~(uintptr_t)1 << prefixEnd))) {//prefix matches, so go on - int branch = (key >> prefixEnd) & 1; - node = &n->childNodes[branch]; - prevKey = &n->keys[branch]; - } else {//insert a new node before current - pkey = n->keys[0] & ~0xFF; - break; - } - } else {//leaf - if (*node == PTRIE_NULL_NODE) { - *node = newNode; - newNode->keys[0] = key;//left prefixEnd = 0, so all following insertions will be before this node - newNode->childNodes[0] = (PTrieNode*)(value | 1); - newNode->childNodes[1] = PTRIE_NULL_NODE; - return; - } else { - pkey = *prevKey & ~0xFF; - x = key ^ pkey; - assert(x/*key != pkey*/ && "key already inserted"); - break; - } - } - } - BSR(index, x); - b = (key >> index) & 1; - newNode->keys[b] = key; - newNode->keys[b^1] = pkey; - newNode->keys[0] |= index; - newNode->childNodes[b] = (PTrieNode*)(value | 1); - newNode->childNodes[b^1] = n; - *node = newNode; -} - -static uintptr_t ptrie_remove(uintptr_t key) -{ - PTrieNode **node = &ptrieRoot; - uintptr_t *pkey = NULL; - assert(ptrieRoot != PTRIE_NULL_NODE && "trie is empty!"); - for (;;) - { - PTrieNode *n = *node; - int branch = (key >> (n->keys[0] & 0xFF)) & 1; - PTrieNode *cn = n->childNodes[branch];//current child node - if ((uintptr_t)cn & 1)//leaf - { - PTrieNode *other = n->childNodes[branch^1]; - assert((n->keys[branch] & ~0xFF) == key); - assert(cn != PTRIE_NULL_NODE && "node's key is probably broken"); - // if (other == PTRIE_NULL_NODE) *node = PTRIE_NULL_NODE; else//special handling for null child nodes is not necessary - if (((uintptr_t)other & 1) && other != PTRIE_NULL_NODE)//if other node is not a pointer - *pkey = (n->keys[branch^1] & ~0xFF) | ((*pkey) & 0xFF); - *node = other; - *(PTrieNode**)n = ptrieFreeNodesList; ptrieFreeNodesList = n;//free(n); - return (uintptr_t)cn & ~1; - } - pkey = &n->keys[branch]; - node = &n->childNodes[branch]; - } -} -#endif - -static void *sys_aligned_alloc(size_t alignment, size_t size) -{ - void *p = VMALLOC(size);//optimistically try mapping precisely the right amount before falling back to the slow method - assert(!(alignment & (alignment-1)) && "alignment must be a power of two"); - if ((uintptr_t)p & (alignment-1)/* && p != MAP_FAILED*/) - { - VMFREE(p, size); -#ifdef _WIN32 - {static DWORD allocationGranularity = 0; - if (!allocationGranularity) { - SYSTEM_INFO si; - GetSystemInfo(&si); - allocationGranularity = si.dwAllocationGranularity; - } - if ((uintptr_t)p < 16*1024*1024)//fill "bubbles" (reserve unaligned regions) at the beginning of virtual address space, otherwise there will be always falling back to the slow method - VirtualAlloc(p, alignment - ((uintptr_t)p & (alignment-1)), MEM_RESERVE, PAGE_NOACCESS); - do - { - p = VirtualAlloc(NULL, size + alignment - allocationGranularity, MEM_RESERVE, PAGE_NOACCESS); - if (p == NULL) return NULL; - VirtualFree(p, 0, MEM_RELEASE);//unfortunately, WinAPI doesn't support release a part of allocated region, so release a whole region - p = VirtualAlloc((void*)(((uintptr_t)p + (alignment-1)) & ~(alignment-1)), size, MEM_RESERVE|MEM_COMMIT, PAGE_READWRITE); - } while (p == NULL);} -#else - p = VMALLOC(size + alignment - page_size()); - if (p/* != MAP_FAILED*/) - { - uintptr_t ap = ((uintptr_t)p + (alignment-1)) & ~(alignment-1); - uintptr_t diff = ap - (uintptr_t)p; - if (diff) VMFREE(p, diff); - diff = alignment - page_size() - diff; - assert((intptr_t)diff >= 0); - if (diff) VMFREE((void*)(ap + size), diff); - return (void*)ap; - } -#endif - } - //if (p == 0) p = sys_aligned_alloc(alignment, size);//just in case (because 0 pointer is handled specially elsewhere) - //if (p == MAP_FAILED) p = NULL; - return p; -} - -static NOINLINE void sys_free(void *p) -{ - if (p == NULL) return; -#ifdef _WIN32 - VirtualFree(p, 0, MEM_RELEASE); -#else - SPINLOCK_ACQUIRE(&ptrieLock); - size_t size = ptrie_remove((uintptr_t)p); - SPINLOCK_RELEASE(&ptrieLock); - munmap(p, size); -#endif -} - -static void release_thread_cache(void*); -#ifdef __GNUC__ -#include -#pragma weak pthread_once -#pragma weak pthread_key_create -#pragma weak pthread_setspecific -static pthread_key_t pthread_key; -static pthread_once_t init_once = PTHREAD_ONCE_INIT; -static void init_pthread_key() { pthread_key_create(&pthread_key, release_thread_cache); } -static thread_local int thread_initialized = 0; -static void init_pthread_destructor()//must be called only when some block placed into a thread cache's free list -{ - if (unlikely(!thread_initialized)) - { - thread_initialized = 1; - if (pthread_once) - { - pthread_once(&init_once, init_pthread_key); - pthread_setspecific(pthread_key, (void*)1);//set nonzero value to force calling of release_thread_cache() on thread terminate - } - } -} -#else -static void NTAPI on_tls_callback(PVOID h, DWORD reason, PVOID pv) { h; pv; if (reason == DLL_THREAD_DETACH) release_thread_cache(0); } -#pragma comment(linker, "/INCLUDE:" CODE3264("_","") "p_thread_callback_ltalloc") -#pragma const_seg(".CRT$XLL") -extern CPPCODE("C") const PIMAGE_TLS_CALLBACK p_thread_callback_ltalloc = on_tls_callback; -#pragma const_seg() -#define init_pthread_destructor() -#endif - -//End of platform-specific stuff - -#define MAX_BLOCK_SIZE (MAX_BLOCK_SIZE < CHUNK_SIZE - (CHUNK_SIZE >> (1 + LTALLOC_SIZE_CLASSES_SUBPOWER_OF_TWO)) ? \ - MAX_BLOCK_SIZE : CHUNK_SIZE - (CHUNK_SIZE >> (1 + LTALLOC_SIZE_CLASSES_SUBPOWER_OF_TWO))) -#define NUMBER_OF_SIZE_CLASSES ((sizeof(void*)*8 + 1) << LTALLOC_SIZE_CLASSES_SUBPOWER_OF_TWO) - -typedef struct FreeBlock -{ - struct FreeBlock *next, - *nextBatch;//in the central cache blocks are organized into batches to allow fast moving blocks from thread cache and back -} FreeBlock; - -typedef struct alignas(CACHE_LINE_SIZE) ChunkBase//force sizeof(Chunk) = cache line size to avoid false sharing -{ - unsigned int sizeClass; -} Chunk; - -typedef struct alignas(CACHE_LINE_SIZE) ChunkSm//chunk of smallest blocks of size = sizeof(void*) -{ - unsigned int sizeClass;//struct ChunkBase chunk; - struct ChunkSm *prev, *next; - int numBatches; -#define NUM_OF_BATCHES_IN_CHUNK_SM CHUNK_SIZE/(sizeof(void*)*MAX_NUM_OF_BLOCKS_IN_BATCH) - FreeBlock *batches[NUM_OF_BATCHES_IN_CHUNK_SM];//batches of blocks inside ChunkSm have to be stored separately (as smallest blocks of size = sizeof(void*) do not have enough space to store second pointer for the batch) -} ChunkSm; - -typedef struct alignas(CACHE_LINE_SIZE)//align needed to prevent cache line sharing between adjacent classes accessed from different threads -{ - volatile int lock; - unsigned int freeBlocksInLastChunk; - char *lastChunk;//Chunk or ChunkSm - union { - FreeBlock *firstBatch; - ChunkSm *chunkWithFreeBatches; - }; - FreeBlock *freeList;//short list of free blocks that for some reason are not organized into batches - unsigned int freeListSize;//should be less than batch size - uintptr_t minChunkAddr, maxChunkAddr; -} CentralCache; -static CentralCache centralCache[NUMBER_OF_SIZE_CLASSES];// = {{0}}; - -typedef struct -{ - FreeBlock *freeList; - FreeBlock *tempList;//intermediate list providing a hysteresis in order to avoid a corner case of too frequent moving free blocks to the central cache and back from - int counter;//number of blocks in freeList (used to determine when to move free blocks list to the central cache) -} ThreadCache; -static thread_local ThreadCache threadCache[NUMBER_OF_SIZE_CLASSES];// = {{0}}; - -static struct -{ - volatile int lock; - void *freeChunk; - size_t size; -} pad = {0, NULL, 0}; - -static CPPCODE(inline) unsigned int get_size_class(size_t size) -{ - unsigned int index; -#if _MSC_VER && LTALLOC_SIZE_CLASSES_SUBPOWER_OF_TWO == 2 - static const unsigned char small_size_classes[256] = {//have to use a const array here, because MS compiler unfortunately does not evaluate _BitScanReverse with a constant argument at compile time (as gcc does for __builtin_clz) -#if CODE3264(1, 0) - 131, 4, 15, 17, 19, 20, 21, 22, 23, 24, 24, 25, 25, 26, 26, 27, 27, 28, 28, 28, 28, 29, 29, 29, 29, 30, 30, 30, 30, 31, 31, 31, 31, 32, 32, 32, 32, 32, 32, 32, 32, 33, 33, 33, 33, 33, 33, 33, 33, 34, 34, 34, 34, 34, 34, 34, 34, 35, 35, 35, 35, 35, 35, 35, 35, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43 -#else - 131, 15, 19, 21, 23, 24, 25, 26, 27, 28, 28, 29, 29, 30, 30, 31, 31, 32, 32, 32, 32, 33, 33, 33, 33, 34, 34, 34, 34, 35, 35, 35, 35, 36, 36, 36, 36, 36, 36, 36, 36, 37, 37, 37, 37, 37, 37, 37, 37, 38, 38, 38, 38, 38, 38, 38, 38, 39, 39, 39, 39, 39, 39, 39, 39, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47 -#endif - }; - if (size < 256 * sizeof(void*) - (sizeof(void*)-1)) - return small_size_classes[(size + (sizeof(void*)-1)) / sizeof(void*)]; -#endif - - size = (size + (sizeof(void*)-1)) & ~(sizeof(void*)-1);//minimum block size is sizeof(void*), doing this is better than just "size = max(size, sizeof(void*))" - - BSR(index, (size-1)|1);//"|1" needed because result of BSR is undefined for zero input -#if LTALLOC_SIZE_CLASSES_SUBPOWER_OF_TWO == 0 - return index; -#else - return (index<> (index-LTALLOC_SIZE_CLASSES_SUBPOWER_OF_TWO)); -#endif -} - -static unsigned int class_to_size(unsigned int c) -{ -#if LTALLOC_SIZE_CLASSES_SUBPOWER_OF_TWO == 0 - return 2 << c; -#else -#if LTALLOC_SIZE_CLASSES_SUBPOWER_OF_TWO >= CODE3264(2, 3) - if (unlikely(c < (LTALLOC_SIZE_CLASSES_SUBPOWER_OF_TWO<>LTALLOC_SIZE_CLASSES_SUBPOWER_OF_TWO); - else -#endif - { - c -= (1<>LTALLOC_SIZE_CLASSES_SUBPOWER_OF_TWO)-LTALLOC_SIZE_CLASSES_SUBPOWER_OF_TWO); - } -#endif -} - -static unsigned int batch_size(unsigned int sizeClass)//calculates a number of blocks to move between a thread cache and a central cache in one shot -{ - return ((MAX_BATCH_SIZE-1) >> (sizeClass >> LTALLOC_SIZE_CLASSES_SUBPOWER_OF_TWO)) & (MAX_NUM_OF_BLOCKS_IN_BATCH-1); -} - -CPPCODE(template static) void *ltmalloc(size_t size); - -CPPCODE(template ) static void *fetch_from_central_cache(size_t size, ThreadCache *tc, unsigned int sizeClass) -{ - void *p; - if (likely(size-1u <= MAX_BLOCK_SIZE-1u))//<=> if (size <= MAX_BLOCK_SIZE && size != 0) - { - FreeBlock *fb = tc->tempList; - if (fb) - { - assert(tc->counter == (int)batch_size(sizeClass)+1); - tc->counter = 1; - tc->freeList = fb->next; - tc->tempList = NULL; - return fb; - } - - assert(tc->counter == 0 || tc->counter == (int)batch_size(sizeClass)+1); - tc->counter = 1; - - {CentralCache *cc = ¢ralCache[sizeClass]; - SPINLOCK_ACQUIRE(&cc->lock); - if (unlikely(!cc->firstBatch))//no free batch - {no_free_batch:{ - unsigned int batchSize = batch_size(sizeClass)+1; - - if (cc->freeList) - { - assert(cc->freeListSize); - if (likely(cc->freeListSize <= batchSize + 1)) - { - tc->counter = batchSize - cc->freeListSize + 1; - // batchSize = cc->freeListSize; - cc->freeListSize = 0; - fb = cc->freeList; - cc->freeList = NULL; - } - else - { - cc->freeListSize -= batchSize; - fb = cc->freeList; - {FreeBlock *b = cc->freeList; - while (--batchSize) b = b->next; - cc->freeList = b->next; - b->next = NULL;} - } - SPINLOCK_RELEASE(&cc->lock); - tc->freeList = fb->next; - init_pthread_destructor();//this call must be placed carefully to allow recursive memory allocation from pthread_key_create (in case when ltalloc replaces the system malloc) - return fb; - } - - {unsigned int blockSize = class_to_size(sizeClass); - if (cc->freeBlocksInLastChunk) - { - char *firstFree = cc->lastChunk; - assert(cc->lastChunk && cc->freeBlocksInLastChunk == (CHUNK_SIZE - ((uintptr_t)cc->lastChunk & (CHUNK_SIZE-1)))/blockSize); - if (cc->freeBlocksInLastChunk < batchSize) { - tc->counter = batchSize - cc->freeBlocksInLastChunk + 1; - batchSize = cc->freeBlocksInLastChunk; - } - cc->freeBlocksInLastChunk -= batchSize; - cc->lastChunk += blockSize * batchSize; - if (cc->freeBlocksInLastChunk == 0) { - assert(((uintptr_t)cc->lastChunk & (CHUNK_SIZE-1)) == 0); - cc->lastChunk = ((char**)cc->lastChunk)[-1]; - if (cc->lastChunk) - cc->freeBlocksInLastChunk = (CHUNK_SIZE - ((uintptr_t)cc->lastChunk & (CHUNK_SIZE-1)))/blockSize; - } - SPINLOCK_RELEASE(&cc->lock); - fb = (FreeBlock*)firstFree; - while (--batchSize) - firstFree = (char*)(((FreeBlock*)firstFree)->next = (FreeBlock*)(firstFree + blockSize)); - ((FreeBlock*)firstFree)->next = NULL; - tc->freeList = fb->next; - init_pthread_destructor(); - return fb; - } - - //Allocate new chunk - SPINLOCK_RELEASE(&cc->lock);//release lock for a while - - SPINLOCK_ACQUIRE(&pad.lock); - if (pad.freeChunk) - { - p = pad.freeChunk; - pad.freeChunk = *(void**)p; - pad.size -= CHUNK_SIZE; - SPINLOCK_RELEASE(&pad.lock); - ((char**)((char*)p + CHUNK_SIZE))[-1] = 0; - } else { - SPINLOCK_RELEASE(&pad.lock); - p = sys_aligned_alloc(CHUNK_SIZE, CHUNK_SIZE); - if (unlikely(!p)) { CPPCODE(if (throw_) throw std::bad_alloc(); else) return NULL; } - } - -#define CHUNK_IS_SMALL unlikely(sizeClass < get_size_class(2*sizeof(void*))) - {unsigned int numBlocksInChunk = (CHUNK_SIZE - (CHUNK_IS_SMALL ? sizeof(ChunkSm) : sizeof(Chunk)))/blockSize; -#ifndef _WIN32 - //intptr_t sz = ((CHUNK_SIZE - numBlocksInChunk*blockSize) & ~(page_size()-1)) - page_size(); - //if (sz > 0) mprotect((char*)p + page_size(), sz, PROT_NONE);//munmap((char*)p + page_size(), sz);//to make possible unmapping, we need to be more careful when returning memory to the system, not simply VMFREE(firstFreeChunk, CHUNK_SIZE), so let there be just mprotect -#endif - assert(((char**)((char*)p + CHUNK_SIZE))[-1] == 0);//assume that allocated memory is always zero filled (on first access); it is better not to zero it explicitly because it will lead to allocation of physical page which may never needed otherwise - if (numBlocksInChunk < batchSize) { - tc->counter = batchSize - numBlocksInChunk + 1; - batchSize = numBlocksInChunk; - } - - //Prepare chunk - ((Chunk*)p)->sizeClass = sizeClass; - {char *firstFree = (char*)p + CHUNK_SIZE - numBlocksInChunk*blockSize;//blocks in chunk are located in such way to achieve a maximum possible alignment - fb = (FreeBlock*)firstFree; - {int n = batchSize; while (--n) - firstFree = (char*)(((FreeBlock*)firstFree)->next = (FreeBlock*)(firstFree + blockSize));} - ((FreeBlock*)firstFree)->next = NULL; - firstFree += blockSize; - - SPINLOCK_ACQUIRE(&cc->lock); - if ((uintptr_t)p < cc->minChunkAddr || !cc->minChunkAddr) cc->minChunkAddr = (uintptr_t)p; - if ((uintptr_t)p > cc->maxChunkAddr ) cc->maxChunkAddr = (uintptr_t)p; - - if (CHUNK_IS_SMALL)//special handling for smallest blocks of size = sizeof(void*) - { - ChunkSm *cs = (ChunkSm*)p; - cs->numBatches = 0; - //Insert new chunk right after chunkWithFreeBatches - cs->prev = cc->chunkWithFreeBatches; - if (cc->chunkWithFreeBatches) { - cs->next = cc->chunkWithFreeBatches->next; - if (cc->chunkWithFreeBatches->next) cc->chunkWithFreeBatches->next->prev = cs; - cc->chunkWithFreeBatches->next = cs; - } else { - cs->next = NULL; - cc->chunkWithFreeBatches = cs; - } - } - - if (unlikely(cc->freeBlocksInLastChunk))//so happened that other thread have already allocated chunk for the same size class while the lock was released - { - //Hook pointer to the current lastChunk at the end of new chunk (another way is just put all blocks to cc->freeList which is much less effecient) - ((char**)(((uintptr_t)firstFree & ~(CHUNK_SIZE-1)) + CHUNK_SIZE))[-1] = cc->lastChunk; - } - cc->freeBlocksInLastChunk = numBlocksInChunk - batchSize; - cc->lastChunk = firstFree; - }}}}} - else { - if (!CHUNK_IS_SMALL)//smallest blocks of size = sizeof(void*) are handled specially - { - fb = cc->firstBatch; - cc->firstBatch = fb->nextBatch; - } - else//size of block = sizeof(void*) - { - ChunkSm *cs = cc->chunkWithFreeBatches; - if (unlikely(cs->numBatches == 0)) - { - if (unlikely(cs->prev == NULL)) goto no_free_batch; - cs = cc->chunkWithFreeBatches = cs->prev; - assert(cs->numBatches == NUM_OF_BATCHES_IN_CHUNK_SM); - } - fb = cs->batches[--cs->numBatches]; - } - } - SPINLOCK_RELEASE(&cc->lock);} - tc->freeList = fb->next; - init_pthread_destructor(); - return fb; - } - else//allocate block directly from the system - { - if (unlikely(size == 0)) return ltmalloc CPPCODE()(1);//return NULL;//doing this check here is better than on the top level - - size = (size + CHUNK_SIZE-1) & ~(CHUNK_SIZE-1); - p = sys_aligned_alloc(CHUNK_SIZE, size); -#ifndef _WIN32 - if (p) { - SPINLOCK_ACQUIRE(&ptrieLock); - PTrieNode *newNode; - if (ptrieFreeNodesList) - ptrieFreeNodesList = *(PTrieNode**)(newNode = ptrieFreeNodesList); - else if (ptrieNewAllocatedPage) { - newNode = ptrieNewAllocatedPage; - if (!((uintptr_t)++ptrieNewAllocatedPage & (page_size()-1))) - ptrieNewAllocatedPage = ((PTrieNode**)ptrieNewAllocatedPage)[-1]; - } else { - SPINLOCK_RELEASE(&ptrieLock); - newNode = (PTrieNode*)VMALLOC(page_size()); - if (unlikely(!newNode)) { CPPCODE(if (throw_) throw std::bad_alloc(); else) return NULL; } - assert(((char**)((char*)newNode + page_size()))[-1] == 0); - SPINLOCK_ACQUIRE(&ptrieLock); - ((PTrieNode**)((char*)newNode + page_size()))[-1] = ptrieNewAllocatedPage;//in case if other thread also have just allocated a new page - ptrieNewAllocatedPage = newNode + 1; - } - ptrie_insert((uintptr_t)p, size, newNode); - SPINLOCK_RELEASE(&ptrieLock); - } -#endif - CPPCODE(if (throw_) if (unlikely(!p)) throw std::bad_alloc();) - return p; - } -} - -CPPCODE(template static) void *ltmalloc(size_t size) -{ - unsigned int sizeClass = get_size_class(size); - ThreadCache *tc = &threadCache[sizeClass]; - FreeBlock *fb = tc->freeList; - if (likely(fb)) - { - tc->freeList = fb->next; - tc->counter++; - return fb; - } - else - return fetch_from_central_cache CPPCODE()(size, tc, sizeClass); -} -CPPCODE(void *ltmalloc(size_t size) {return ltmalloc(size);})//for possible external usage - -static void add_batch_to_central_cache(CentralCache *cc, unsigned int sizeClass, FreeBlock *batch) -{ - if (!CHUNK_IS_SMALL) - { - batch->nextBatch = cc->firstBatch; - cc->firstBatch = batch; - } - else - { - ChunkSm *cs = cc->chunkWithFreeBatches; - if (unlikely(cs->numBatches == NUM_OF_BATCHES_IN_CHUNK_SM)) - { - cs = cc->chunkWithFreeBatches = cc->chunkWithFreeBatches->next; - assert(cs && cs->numBatches == 0); - } - cs->batches[cs->numBatches++] = batch; - } -} - -static NOINLINE void move_to_central_cache(ThreadCache *tc, unsigned int sizeClass) -{ - init_pthread_destructor();//needed for cases when freed memory was allocated in the other thread and no alloc was called in this thread till its termination - - tc->counter = batch_size(sizeClass); - if (tc->tempList)//move temp list to the central cache - { - CentralCache *cc = ¢ralCache[sizeClass]; - SPINLOCK_ACQUIRE(&cc->lock); - add_batch_to_central_cache(cc, sizeClass, tc->tempList); - SPINLOCK_RELEASE(&cc->lock); - } -// else if (unlikely(!tc->freeList))//this is a first call (i.e. when counter = 0) - just initialization of counter needed -// { -// tc->counter--; -// return; -// } - - tc->tempList = tc->freeList; - tc->freeList = NULL; -} - -void ltfree(void *p) -{ - if (likely((uintptr_t)p & (CHUNK_SIZE-1))) - { - unsigned int sizeClass = ((Chunk*)((uintptr_t)p & ~(CHUNK_SIZE-1)))->sizeClass; - ThreadCache *tc = &threadCache[sizeClass]; - - if (unlikely(--tc->counter < 0)) - move_to_central_cache(tc, sizeClass); - - ((FreeBlock*)p)->next = tc->freeList; - tc->freeList = (FreeBlock*)p; - } - else - sys_free(p); -} - -size_t ltmsize(void *p) -{ - if (likely((uintptr_t)p & (CHUNK_SIZE-1))) - { - return class_to_size(((Chunk*)((uintptr_t)p & ~(CHUNK_SIZE-1)))->sizeClass); - } - else - { - if (p == NULL) return 0; -#ifdef _WIN32 - {MEMORY_BASIC_INFORMATION mi; - VirtualQuery(p, &mi, sizeof(mi)); - return mi.RegionSize;} -#else - SPINLOCK_ACQUIRE(&ptrieLock); - size_t size = ptrie_lookup((uintptr_t)p); - SPINLOCK_RELEASE(&ptrieLock); - return size; -#endif - } -} - -static void release_thread_cache(void *p) -{ - unsigned int sizeClass = 0; (void)p; - for (;sizeClass < NUMBER_OF_SIZE_CLASSES; sizeClass++) - { - ThreadCache *tc = &threadCache[sizeClass]; - if (tc->freeList || tc->tempList) - { - FreeBlock *tail = tc->freeList; - unsigned int freeListSize = 1; - CentralCache *cc = ¢ralCache[sizeClass]; - - if (tail) - while (tail->next)//search for end of list - tail = tail->next, freeListSize++; - - SPINLOCK_ACQUIRE(&cc->lock); - if (tc->tempList) - add_batch_to_central_cache(cc, sizeClass, tc->tempList); - if (tc->freeList) {//append tc->freeList to cc->freeList - tail->next = cc->freeList; - cc->freeList = tc->freeList; - assert(freeListSize == batch_size(sizeClass)+1 - tc->counter); - cc->freeListSize += freeListSize; - } - SPINLOCK_RELEASE(&cc->lock); - } - } -} - -void ltsqueeze(size_t padsz) -{ - unsigned int sizeClass = get_size_class(2*sizeof(void*));//skip small chunks because corresponding batches can not be efficiently detached from the central cache (if that becomes relevant, may be it worths to reimplement batches for small chunks from array to linked lists) - for (;sizeClass < NUMBER_OF_SIZE_CLASSES; sizeClass++) - { - CentralCache *cc = ¢ralCache[sizeClass]; - if (cc->maxChunkAddr - cc->minChunkAddr <= CHUNK_SIZE)//preliminary check without lock (assume that writing to minChunkAddr/maxChunkAddr is atomic) - continue; - - SPINLOCK_ACQUIRE(&cc->lock); - if (cc->maxChunkAddr - cc->minChunkAddr <= CHUNK_SIZE) {//quick check for theoretical possibility that at least one chunk is totally free - SPINLOCK_RELEASE(&cc->lock); - continue; - } - {uintptr_t minChunkAddr = cc->minChunkAddr; - size_t bufferSize = ((cc->maxChunkAddr - minChunkAddr) / CHUNK_SIZE + 1) * sizeof(short); - //Quickly detach all batches of the current size class from the central cache - unsigned int freeListSize = cc->freeListSize; - FreeBlock *firstBatch = cc->firstBatch, *freeList = cc->freeList; - cc->firstBatch = NULL; - cc->freeList = NULL; - cc->freeListSize = 0; - SPINLOCK_RELEASE(&cc->lock); - - //1. Find out chunks with only free blocks via a simple counting the number of free blocks in each chunk - {char buffer[32*1024];//enough for 1GB address space - unsigned short *inChunkFreeBlocks = (unsigned short*)(bufferSize <= sizeof(buffer) ? memset(buffer, 0, bufferSize) : VMALLOC(bufferSize)); - unsigned int numBlocksInChunk = (CHUNK_SIZE - (/*CHUNK_IS_SMALL ? sizeof(ChunkSm) : */sizeof(Chunk)))/class_to_size(sizeClass); - FreeBlock **pbatch, *block, **pblock; - Chunk *firstFreeChunk = NULL; - assert(numBlocksInChunk < (1U<<(sizeof(short)*8)));//in case if CHUNK_SIZE is too big that total count of blocks in it doesn't fit at short type (...may be use static_assert instead?) - if (inChunkFreeBlocks)//consider VMALLOC can fail - { - for (pbatch = &firstBatch; *pbatch; pbatch = &(*pbatch)->nextBatch) - for (block = *pbatch; block; block = block->next) -#define FREE_BLOCK(block) \ - if (++inChunkFreeBlocks[((uintptr_t)block - minChunkAddr) / CHUNK_SIZE] == numBlocksInChunk)/*chunk is totally free*/\ - {\ - Chunk *chunk = (Chunk*)((uintptr_t)block & ~(CHUNK_SIZE-1));\ - assert(chunk->sizeClass == sizeClass);/*just in case check before overwriting this info*/\ - *(Chunk**)chunk = firstFreeChunk;/*put nextFreeChunk pointer right at the beginning of Chunk as there are always must be a space for one pointer before first memory block*/\ - firstFreeChunk = chunk;\ - } - FREE_BLOCK(block) - for (pblock = &freeList; *pblock; pblock = &(*pblock)->next) - FREE_BLOCK(*pblock) -#undef FREE_BLOCK - } - else { - for (pbatch = &firstBatch; *pbatch; pbatch = &(*pbatch)->nextBatch); - for (pblock = &freeList; *pblock; pblock = &(*pblock)->next); - } - - if (firstFreeChunk)//is anything to release - { - //2. Unlink all matching blocks from the corresponding free lists - FreeBlock *additionalBatchesList = NULL, *additionalBlocksList = NULL, **abatch = &additionalBatchesList, **ablock = &additionalBlocksList; - unsigned int additionalBlocksListSize = 0, batchSize = batch_size(sizeClass)+1; - for (pbatch = &firstBatch; *pbatch;) - { - for (block = *pbatch; block; block = block->next) - if (inChunkFreeBlocks[((uintptr_t)block - minChunkAddr) / CHUNK_SIZE] == numBlocksInChunk)//if at least one block belongs to a releasable chunk, then this batch should be handled specially - { - FreeBlock *nextBatch = (*pbatch)->nextBatch; - for (block = *pbatch; block;)//re-add blocks of not-for-release chunks and organize them into another batches' list (to join it with the main later) - if (inChunkFreeBlocks[((uintptr_t)block - minChunkAddr) / CHUNK_SIZE] != numBlocksInChunk)//skip matching-for-release blocks - { - *ablock = block; - do//this loop needed only to minimize memory write operations, otherwise a simpler approach could be used (like in the next loop below) - { - ablock = &block->next; - block = block->next; - if (++additionalBlocksListSize == batchSize) - { - abatch = &(*abatch = additionalBlocksList)->nextBatch; - *abatch = NULL; - *ablock = NULL; - ablock = &additionalBlocksList; - additionalBlocksList = NULL; - additionalBlocksListSize = 0; - break;//to force *ablock = block; for starting a new batch - } - } while (block && inChunkFreeBlocks[((uintptr_t)block - minChunkAddr) / CHUNK_SIZE] != numBlocksInChunk); - } - else - block = block->next; - *ablock = NULL; - *pbatch = nextBatch;//unlink batch - goto continue_; - } - pbatch = &(*pbatch)->nextBatch; -continue_:; - } - for (block = freeList; block;) - if (inChunkFreeBlocks[((uintptr_t)block - minChunkAddr) / CHUNK_SIZE] != numBlocksInChunk) - { - //*pblock = (*pblock)->next, freeListSize--;//unlink block - ablock = &(*ablock = block)->next; - block = block->next; - *ablock = NULL; - if (++additionalBlocksListSize == batchSize) - { - abatch = &(*abatch = additionalBlocksList)->nextBatch; - *abatch = NULL; - ablock = &additionalBlocksList; - additionalBlocksList = NULL; - additionalBlocksListSize = 0; - } - } - else - block = block->next; - //Add additional lists - *abatch = *pbatch; - *pbatch = additionalBatchesList; - pblock = ablock; - freeList = additionalBlocksList; - freeListSize = additionalBlocksListSize; - - //Return back all left not-for-release blocks to the central cache as quickly as possible (as other threads may want to allocate a new memory) -#define GIVE_LISTS_BACK_TO_CC \ - SPINLOCK_ACQUIRE(&cc->lock);\ - *pbatch = cc->firstBatch;\ - cc->firstBatch = firstBatch;\ - *pblock = cc->freeList;\ - cc->freeList = freeList;\ - cc->freeListSize += freeListSize;\ - SPINLOCK_RELEASE(&cc->lock);\ - if (bufferSize > sizeof(buffer)) VMFREE(inChunkFreeBlocks, bufferSize);//this better to do before 3. as kernel is likely optimized for release of just allocated range - GIVE_LISTS_BACK_TO_CC - - if (padsz) - { - SPINLOCK_ACQUIRE(&pad.lock); - if (pad.size < padsz) - { - Chunk *first = firstFreeChunk, **c; - do//put off free chunks up to a specified pad size - { - c = (Chunk**)firstFreeChunk; - firstFreeChunk = *c; - pad.size += CHUNK_SIZE; - } while (pad.size < padsz && firstFreeChunk); - *c = (Chunk*)pad.freeChunk; - pad.freeChunk = first; - } - SPINLOCK_RELEASE(&pad.lock); - } - - //3. Return memory to the system - while (firstFreeChunk) - { - Chunk *nextFreeChunk = *(Chunk**)firstFreeChunk; - VMFREE(firstFreeChunk, CHUNK_SIZE); - firstFreeChunk = nextFreeChunk; - } - } - else//nothing to release - just return batches back to the central cache - { - GIVE_LISTS_BACK_TO_CC -#undef GIVE_LISTS_BACK_TO_CC - }}} - } -} - -#if defined(__cplusplus) && !defined(LTALLOC_DISABLE_OPERATOR_NEW_OVERRIDE) -void *operator new (size_t size) throw(std::bad_alloc) {return ltmalloc (size);} -void *operator new (size_t size, const std::nothrow_t&) throw() {return ltmalloc(size);} -void *operator new[](size_t size) throw(std::bad_alloc) {return ltmalloc (size);} -void *operator new[](size_t size, const std::nothrow_t&) throw() {return ltmalloc(size);} - -void operator delete (void* p) throw() {ltfree(p);} -void operator delete (void* p, const std::nothrow_t&) throw() {ltfree(p);} -void operator delete[](void* p) throw() {ltfree(p);} -void operator delete[](void* p, const std::nothrow_t&) throw() {ltfree(p);} -#endif - -/* @r-lyeh's { */ -#include -void *ltcalloc(size_t elems, size_t size) { - size *= elems; - return memset( ltmalloc( size ), 0, size ); -} -void *ltmemalign( size_t align, size_t size ) { - return --align, ltmalloc( (size+align)&~align ); -} -void *ltrealloc( void *ptr, size_t sz ) { - if( !ptr ) return ltmalloc( sz ); - if( !sz ) return ltfree( ptr ), (void *)0; - size_t osz = ltmsize( ptr ); - if( sz <= osz ) { - return ptr; - } - void *nptr = memcpy( ltmalloc(sz), ptr, osz ); - ltfree( ptr ); - -#ifdef LTALLOC_AUTO_GC_INTERVAL - /* this is kind of compromise; the following timer is to guarantee - that memory gets wiped out at least every given seconds between consecutive - ltrealloc() calls (I am assuming frequency usage for ltrealloc() is smaller - than ltmalloc() or ltfree() too) - @r-lyeh */ - clock_t now = clock(); - static clock_t then = now; - if( ( double(now - then) / CLOCKS_PER_SEC ) > LTALLOC_AUTO_GC_INTERVAL ) { - ltsqueeze(0); - } - then = now; -#endif - - return nptr; -} -/* } */ diff --git a/experimental/ltalloc.h b/experimental/ltalloc.h deleted file mode 100644 index 22d6f2e..0000000 --- a/experimental/ltalloc.h +++ /dev/null @@ -1,14 +0,0 @@ -#include /*a more portable size_t definition than stddef.h itself*/ -#ifdef __cplusplus -extern "C" { -#endif -void* ltmalloc(size_t); -void ltfree(void*); -void* ltrealloc( void *, size_t ); -void* ltcalloc( size_t, size_t ); -void* ltmemalign( size_t, size_t ); -void ltsqueeze(size_t pad); /*return memory to system (see README.md)*/ -size_t ltmsize(void*); -#ifdef __cplusplus -} -#endif diff --git a/experimental/ltalloc.hpp b/experimental/ltalloc.hpp deleted file mode 100644 index cd6aee1..0000000 --- a/experimental/ltalloc.hpp +++ /dev/null @@ -1,59 +0,0 @@ -// based on code by Jerry Coffin (most likely Public Domain) -// - rlyeh - -#pragma once - -#include -#include -#include - -#include "ltalloc.h" - -namespace lt { -template -struct allocator { - typedef size_t size_type; - typedef ptrdiff_t difference_type; - typedef T* pointer; - typedef const T* const_pointer; - typedef T& reference; - typedef const T& const_reference; - typedef T value_type; - - template struct rebind { typedef allocator other; }; - allocator() throw() {} - allocator(const allocator&) throw() {} - - template allocator(const allocator&) throw(){} - - ~allocator() throw() {} - - pointer address(reference x) const { return &x; } - const_pointer address(const_reference x) const { return &x; } - - pointer allocate(size_type s, void const * = 0) { - if (0 == s) - return NULL; - pointer temp = (pointer)ltmalloc(s * sizeof(T)); - if (temp == NULL) - throw std::bad_alloc(); - return temp; - } - - void deallocate(pointer p, size_type) { - ltfree(p); - } - - size_type max_size() const throw() { - return std::numeric_limits::max() / sizeof(T); - } - - void construct(pointer p, const T& val) { - new((void *)p) T(val); - } - - void destroy(pointer p) { - p->~T(); - } -}; -} diff --git a/experimental/premake5.lua b/experimental/premake5.lua index 4a94af9..29511e1 100644 --- a/experimental/premake5.lua +++ b/experimental/premake5.lua @@ -27,12 +27,11 @@ solution "objview" kind "ConsoleApp" language "C++" - files { "viewer.cc", "trackball.cc", "ltalloc.cc" } + files { "viewer.cc", "trackball.cc" } includedirs { "./" } includedirs { "../../" } flags { "c++11" } - --buildoptions { "-std=c++11" } if _OPTIONS['clang'] then toolset "clang" diff --git a/experimental/tinyobj_loader_opt.h b/experimental/tinyobj_loader_opt.h index 3bee218..bdb343c 100644 --- a/experimental/tinyobj_loader_opt.h +++ b/experimental/tinyobj_loader_opt.h @@ -1,12 +1,12 @@ // // Optimized wavefront .obj loader. -// Requires ltalloc and C++11 +// Requires lfpAlloc and C++11 // /* The MIT License (MIT) -Copyright (c) 2012-2016 Syoyo Fujita and many contributors. +Copyright (c) 2012-2017 Syoyo Fujita and many contributors. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -38,7 +38,6 @@ THE SOFTWARE. #include #include #include -#include #endif #include @@ -55,7 +54,7 @@ THE SOFTWARE. #include // C++11 #include // C++11 -#include "ltalloc.hpp" +#include "lfpAlloc/Allocator.hpp" namespace tinyobj_opt { @@ -327,12 +326,12 @@ struct index_t { }; typedef struct { - std::vector > vertices; - std::vector > normals; - std::vector > texcoords; - std::vector > indices; - std::vector > face_num_verts; - std::vector > material_ids; + std::vector > vertices; + std::vector > normals; + std::vector > texcoords; + std::vector > indices; + std::vector > face_num_verts; + std::vector > material_ids; } attrib_t; typedef StackVector ShortString; @@ -999,9 +998,9 @@ typedef struct { float tx, ty; // for f - std::vector > f; + std::vector > f; // std::vector f; - std::vector > f_num_verts; + std::vector > f_num_verts; const char *group_name; unsigned int group_name_len; @@ -1271,7 +1270,8 @@ bool parseObj(attrib_t *attrib, std::vector *shapes, auto t1 = std::chrono::high_resolution_clock::now(); - std::vector > line_infos[kMaxThreads]; + std::vector > + line_infos[kMaxThreads]; for (size_t t = 0; t < static_cast(num_threads); t++) { // Pre allocate enough memory. len / 128 / num_threads is just a heuristic // value. @@ -1558,8 +1558,9 @@ bool parseObj(attrib_t *attrib, std::vector *shapes, index_t(vertex_index, texcoord_index, normal_index); } for (size_t k = 0; k < commands[t][i].f_num_verts.size(); k++) { - attrib->material_ids[face_count + k] = material_id; - attrib->face_num_verts[face_count + k] = commands[t][i].f_num_verts[k]; + attrib->material_ids[face_count + k] = material_id; + attrib->face_num_verts[face_count + k] = + commands[t][i].f_num_verts[k]; } f_count += commands[t][i].f.size(); -- cgit v1.2.3 From 2409832b244abd6fc1047e0eeb426ce8ecc17737 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Wed, 5 Jul 2017 16:32:00 +0900 Subject: Remove unused file. --- experimental/LICENSE.ltalloc | 26 -------------------------- 1 file changed, 26 deletions(-) delete mode 100644 experimental/LICENSE.ltalloc diff --git a/experimental/LICENSE.ltalloc b/experimental/LICENSE.ltalloc deleted file mode 100644 index 4a084d1..0000000 --- a/experimental/LICENSE.ltalloc +++ /dev/null @@ -1,26 +0,0 @@ -Copyright (c) 2013, Alexander Tretyak -Copyright (c) 2015, r-lyeh -All rights reserved. -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: - * Redistributions of source code must retain the above copyright -notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above -copyright notice, this list of conditions and the following disclaimer -in the documentation and/or other materials provided with the -distribution. - * Neither the name of the author nor the names of its -contributors may be used to endorse or promote products derived -from this software without specific prior written permission. -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file -- cgit v1.2.3 From 6cde18eb555696b909ae53ab3d7d4e29fe2dd397 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Wed, 5 Jul 2017 16:50:12 +0900 Subject: Fix ifdef guard. --- experimental/tinyobj_loader_opt.h | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/experimental/tinyobj_loader_opt.h b/experimental/tinyobj_loader_opt.h index bdb343c..ed44b47 100644 --- a/experimental/tinyobj_loader_opt.h +++ b/experimental/tinyobj_loader_opt.h @@ -1046,8 +1046,14 @@ bool parseObj(attrib_t *attrib, std::vector *shapes, std::vector *materials, const char *buf, size_t len, const LoadOption &option); +} // namespace tinyobj_opt + +#endif // TINOBJ_LOADER_OPT_H_ + #ifdef TINYOBJ_LOADER_OPT_IMPLEMENTATION +namespace tinyobj_opt { + static bool parseLine(Command *command, const char *p, size_t p_len, bool triangulate = true) { // @todo { operate directly on pointer `p'. to do that, add range check for @@ -1672,8 +1678,7 @@ bool parseObj(attrib_t *attrib, std::vector *shapes, return true; } -#endif // TINYOBJ_LOADER_OPT_IMPLEMENTATION } // namespace tinyobj_opt -#endif // TINOBJ_LOADER_OPT_H_ +#endif // TINYOBJ_LOADER_OPT_IMPLEMENTATION -- cgit v1.2.3 From 8e7da8285275d41ebca3f9e747d5129a12b22d88 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Mon, 10 Jul 2017 01:41:39 +0900 Subject: Fix index calculation. --- experimental/tinyobj_loader_opt.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/experimental/tinyobj_loader_opt.h b/experimental/tinyobj_loader_opt.h index ed44b47..f86b482 100644 --- a/experimental/tinyobj_loader_opt.h +++ b/experimental/tinyobj_loader_opt.h @@ -1331,7 +1331,7 @@ bool parseObj(attrib_t *attrib, std::vector *shapes, // Find extra line which spand across chunk boundary. if ((t < num_threads) && (buf[end_idx - 1] != '\n')) { - auto extra_span_idx = (std::min)(end_idx - 1 + chunk_size, len - 1); + auto extra_span_idx = (std::min)(end_idx - 1 + chunk_size, len); for (size_t i = end_idx; i < extra_span_idx; i++) { if (is_line_ending(buf, i, extra_span_idx)) { LineInfo info; -- cgit v1.2.3 From e3508c3ca3f3d02f0718df890d9aa3486d5ca8c0 Mon Sep 17 00:00:00 2001 From: chrisliebert Date: Mon, 17 Jul 2017 01:07:40 -0700 Subject: Added mapping from material index to to material name (#131) to Python wrapper. --- python/main.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/python/main.cpp b/python/main.cpp index 82ecd25..9e266c1 100644 --- a/python/main.cpp +++ b/python/main.cpp @@ -29,7 +29,7 @@ PyObject* pyTupleFromfloat3(float array[3]) { extern "C" { static PyObject* pyLoadObj(PyObject* self, PyObject* args) { - PyObject *rtndict, *pyshapes, *pymaterials, *attribobj, *current, *meshobj; + PyObject *rtndict, *pyshapes, *pymaterials, *pymaterial_indices, *attribobj, *current, *meshobj; char const* current_name; char const* filename; @@ -48,6 +48,7 @@ static PyObject* pyLoadObj(PyObject* self, PyObject* args) { pyshapes = PyDict_New(); pymaterials = PyDict_New(); + pymaterial_indices = PyDict_New(); rtndict = PyDict_New(); attribobj = PyDict_New(); @@ -123,6 +124,7 @@ static PyObject* pyLoadObj(PyObject* self, PyObject* args) { PyDict_SetItemString(pyshapes, (*shape).name.c_str(), meshobj); } + long material_index = 0; for (std::vector::iterator mat = materials.begin(); mat != materials.end(); mat++) { PyObject* matobj = PyDict_New(); @@ -168,10 +170,12 @@ static PyObject* pyLoadObj(PyObject* self, PyObject* args) { PyDict_SetItemString(matobj, "unknown_parameter", unknown_parameter); PyDict_SetItemString(pymaterials, (*mat).name.c_str(), matobj); + PyDict_SetItemString(pymaterial_indices, PyLong_FromLong(material_index++), (*mat).name.c_str()); } PyDict_SetItemString(rtndict, "shapes", pyshapes); PyDict_SetItemString(rtndict, "materials", pymaterials); + PyDict_SetItemString(rtndict, "material_indices", pymaterial_indices); PyDict_SetItemString(rtndict, "attribs", attribobj); return rtndict; -- cgit v1.2.3 From 1dfd117ccd712ae24195537c3f47c60d3fd706e3 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Mon, 17 Jul 2017 18:40:42 +0900 Subject: Add project URLs using tinyobjloader 1.0.x. --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 417e628..7c95235 100644 --- a/README.md +++ b/README.md @@ -55,6 +55,8 @@ TinyObjLoader is successfully used in ... * Loading models in Vulkan Tutorial https://vulkan-tutorial.com/Loading_models * .obj viewer with Metal https://github.com/middlefeng/NuoModelViewer/tree/master * Vulkan Cookbook https://github.com/PacktPublishing/Vulkan-Cookbook +* cudabox: CUDA Solid Voxelizer Engine https://github.com/gaspardzoss/cudavox +* Drake: A planning, control, and analysis toolbox for nonlinear dynamical systems https://github.com/RobotLocomotion/drake * Your project here! ### Old version(v0.9.x) -- cgit v1.2.3 From f59f93d7dcefba21d7ffe078ec99a8258b605565 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Mon, 17 Jul 2017 18:48:04 +0900 Subject: Rename variables to avoid confusion. Fixes #108 . --- examples/viewer/viewer.cc | 46 +++++++++++++++++++++++----------------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/examples/viewer/viewer.cc b/examples/viewer/viewer.cc index c660de9..def61ca 100644 --- a/examples/viewer/viewer.cc +++ b/examples/viewer/viewer.cc @@ -123,7 +123,7 @@ class timerutil { }; typedef struct { - GLuint vb; // vertex buffer + GLuint vb_id; // vertex buffer id int numTriangles; size_t material_id; } DrawObject; @@ -289,7 +289,7 @@ static bool LoadObjAndConvert(float bmin[3], float bmax[3], { for (size_t s = 0; s < shapes.size(); s++) { DrawObject o; - std::vector vb; // pos(3float), normal(3float), color(3float) + std::vector buffer; // pos(3float), normal(3float), color(3float) for (size_t f = 0; f < shapes[s].mesh.indices.size() / 3; f++) { tinyobj::index_t idx0 = shapes[s].mesh.indices[3 * f + 0]; tinyobj::index_t idx1 = shapes[s].mesh.indices[3 * f + 1]; @@ -374,12 +374,12 @@ static bool LoadObjAndConvert(float bmin[3], float bmax[3], } for (int k = 0; k < 3; k++) { - vb.push_back(v[k][0]); - vb.push_back(v[k][1]); - vb.push_back(v[k][2]); - vb.push_back(n[k][0]); - vb.push_back(n[k][1]); - vb.push_back(n[k][2]); + buffer.push_back(v[k][0]); + buffer.push_back(v[k][1]); + buffer.push_back(v[k][2]); + buffer.push_back(n[k][0]); + buffer.push_back(n[k][1]); + buffer.push_back(n[k][2]); // Combine normal and diffuse to get color. float normal_factor = 0.2; float diffuse_factor = 1 - normal_factor; @@ -396,16 +396,16 @@ static bool LoadObjAndConvert(float bmin[3], float bmax[3], c[1] /= len; c[2] /= len; } - vb.push_back(c[0] * 0.5 + 0.5); - vb.push_back(c[1] * 0.5 + 0.5); - vb.push_back(c[2] * 0.5 + 0.5); + buffer.push_back(c[0] * 0.5 + 0.5); + buffer.push_back(c[1] * 0.5 + 0.5); + buffer.push_back(c[2] * 0.5 + 0.5); - vb.push_back(tc[k][0]); - vb.push_back(tc[k][1]); + buffer.push_back(tc[k][0]); + buffer.push_back(tc[k][1]); } } - o.vb = 0; + o.vb_id = 0; o.numTriangles = 0; // OpenGL viewer does not support texturing with per-face material. @@ -416,12 +416,12 @@ static bool LoadObjAndConvert(float bmin[3], float bmax[3], o.material_id = materials.size() - 1; // = ID for default material. } - if (vb.size() > 0) { - glGenBuffers(1, &o.vb); - glBindBuffer(GL_ARRAY_BUFFER, o.vb); - glBufferData(GL_ARRAY_BUFFER, vb.size() * sizeof(float), &vb.at(0), + if (buffer.size() > 0) { + glGenBuffers(1, &o.vb_id); + glBindBuffer(GL_ARRAY_BUFFER, o.vb_id); + glBufferData(GL_ARRAY_BUFFER, buffer.size() * sizeof(float), &buffer.at(0), GL_STATIC_DRAW); - o.numTriangles = vb.size() / (3 + 3 + 3 + 2) / 3; // 3:vtx, 3:normal, 3:col, 2:texcoord + o.numTriangles = buffer.size() / (3 + 3 + 3 + 2) / 3; // 3:vtx, 3:normal, 3:col, 2:texcoord printf("shape[%d] # of triangles = %d\n", static_cast(s), o.numTriangles); @@ -545,11 +545,11 @@ static void Draw(const std::vector& drawObjects, std::vector& drawObjects, std::vector Date: Fri, 4 Aug 2017 12:11:36 +0200 Subject: Update README.md Fix typo. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7c95235..7a3e86b 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ Old version is available `v0.9.x` branch https://github.com/syoyo/tinyobjloader/ ## What's new -* 20 Aug, 2016 : Bump version v1.0.0. New data strcutre and API! +* 20 Aug, 2016 : Bump version v1.0.0. New data structure and API! ### Old version -- cgit v1.2.3 From be46318a52701f9e849ed97c7c6b0ac887303a19 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Tue, 8 Aug 2017 16:02:12 +0900 Subject: print some material infos. --- examples/viewer/README.md | 7 ++++++- examples/viewer/viewer.cc | 20 +++++++++++++++++--- 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/examples/viewer/README.md b/examples/viewer/README.md index 79e544e..9cb032c 100644 --- a/examples/viewer/README.md +++ b/examples/viewer/README.md @@ -6,7 +6,6 @@ * glfw3 * glew - ## Build on MaCOSX Install glfw3 and glew using brew. @@ -35,3 +34,9 @@ Put glfw3 and glew library somewhere and replace include and lib path in `premak Then, > premake5.exe vs2013 + +## TODO + +* [ ] Support per-face material. +* [ ] Use shader-based GL rendering. +* [ ] PBR shader support. diff --git a/examples/viewer/viewer.cc b/examples/viewer/viewer.cc index def61ca..4747232 100644 --- a/examples/viewer/viewer.cc +++ b/examples/viewer/viewer.cc @@ -207,6 +207,9 @@ static bool LoadObjAndConvert(float bmin[3], float bmax[3], tm.start(); std::string base_dir = GetBaseDir(filename); + if (base_dir.empty()) { + base_dir = "."; + } #ifdef _WIN32 base_dir += "\\"; #else @@ -238,6 +241,10 @@ static bool LoadObjAndConvert(float bmin[3], float bmax[3], // Append `default` material materials.push_back(tinyobj::material_t()); + for (size_t i = 0; i < materials.size(); i++) { + printf("material[%d].diffuse_texname = %s\n", int(i), materials[i].diffuse_texname.c_str()); + } + // Load diffuse textures { for (size_t m = 0; m < materials.size(); m++) { @@ -265,15 +272,19 @@ static bool LoadObjAndConvert(float bmin[3], float bmax[3], std::cerr << "Unable to load texture: " << texture_filename << std::endl; exit(1); } + std::cout << "Loaded texture: " << texture_filename << ", w = " << w << ", h = " << h << ", comp = " << comp << std::endl; + glGenTextures(1, &texture_id); glBindTexture(GL_TEXTURE_2D, texture_id); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); if (comp == 3) { glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, w, h, 0, GL_RGB, GL_UNSIGNED_BYTE, image); } else if (comp == 4) { glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, image); + } else { + assert(0); // TODO } glBindTexture(GL_TEXTURE_2D, 0); stbi_image_free(image); @@ -314,6 +325,8 @@ static bool LoadObjAndConvert(float bmin[3], float bmax[3], assert(attrib.texcoords.size() > 2 * idx0.texcoord_index + 1); assert(attrib.texcoords.size() > 2 * idx1.texcoord_index + 1); assert(attrib.texcoords.size() > 2 * idx2.texcoord_index + 1); + + // Flip Y coord. tc[0][0] = attrib.texcoords[2 * idx0.texcoord_index]; tc[0][1] = 1.0f - attrib.texcoords[2 * idx0.texcoord_index + 1]; tc[1][0] = attrib.texcoords[2 * idx1.texcoord_index]; @@ -410,11 +423,11 @@ static bool LoadObjAndConvert(float bmin[3], float bmax[3], // OpenGL viewer does not support texturing with per-face material. if (shapes[s].mesh.material_ids.size() > 0 && shapes[s].mesh.material_ids.size() > s) { - // Base case - o.material_id = shapes[s].mesh.material_ids[s]; + o.material_id = shapes[s].mesh.material_ids[0]; // use the material ID of the first face. } else { o.material_id = materials.size() - 1; // = ID for default material. } + printf("shape[%d] material_id %d\n", int(s), int(o.material_id)); if (buffer.size() > 0) { glGenBuffers(1, &o.vb_id); @@ -555,6 +568,7 @@ static void Draw(const std::vector& drawObjects, std::vector Date: Sun, 20 Aug 2017 18:30:54 +0900 Subject: Add support for parsing `map_Bump` in mtl. --- models/map-bump.mtl | 10 + models/map-bump.obj | 817 ++++++++++++++++++++++++++++++++++++++++++++++++++++ tests/tester.cc | 20 ++ tiny_obj_loader.h | 11 +- 4 files changed, 857 insertions(+), 1 deletion(-) create mode 100644 models/map-bump.mtl create mode 100644 models/map-bump.obj diff --git a/models/map-bump.mtl b/models/map-bump.mtl new file mode 100644 index 0000000..6fb1291 --- /dev/null +++ b/models/map-bump.mtl @@ -0,0 +1,10 @@ +newmtl Material.001 +Ka 0 0 0 +Kd 0 0 0 +Ks 0 0 0 +map_Bump bump.jpg + +newmtl Material.003 +Ka 0 0 0 +Kd 1 1 1 +Ks 0 0 0 diff --git a/models/map-bump.obj b/models/map-bump.obj new file mode 100644 index 0000000..03071f1 --- /dev/null +++ b/models/map-bump.obj @@ -0,0 +1,817 @@ +# https://github.com/syoyo/tinyobjloader/issues/68 +# Blender v2.73 (sub 0) OBJ File: 'enemy.blend' +# www.blender.org +mtllib map-bump.mtl +o Cube +v 1.864151 -1.219172 -5.532511 +v 0.575869 -0.666304 5.896140 +v 0.940448 1.000000 -1.971128 +v 1.620345 1.000000 -5.815706 +v 1.864152 1.000000 -6.334323 +v 0.575869 -0.129842 5.896143 +v 5.440438 -1.462153 -5.818601 +v 4.896782 -1.462153 -2.744413 +v 1.000825 -0.677484 1.899605 +v 5.440438 -1.246362 -5.818600 +v 1.000825 0.852342 1.899608 +v 4.896782 -1.246362 -2.744412 +v 1.160660 -0.450871 -2.356325 +v 1.704316 -0.450871 -5.430513 +v 1.000825 -0.351920 -1.293797 +v 1.000825 1.000000 -1.293794 +v 1.160660 -0.877888 -2.356326 +v 1.704316 -0.877888 -5.430514 +v 1.000825 -1.219172 -1.452514 +v 1.000825 1.000000 -1.452511 +v 1.000825 -0.351920 1.759410 +v 1.000825 1.000000 1.759413 +v 9.097919 1.221145 -6.212147 +v 8.356775 1.221145 -2.021231 +v 1.864151 -0.109586 -6.334325 +v 0.575869 -0.398073 5.896141 +v 9.097919 0.943958 -6.212148 +v 8.356775 0.943958 -2.021233 +v 1.061916 0.113661 -1.797961 +v 1.000825 0.161258 1.899606 +v 1.000825 0.324040 -1.293795 +v 1.803060 0.113661 -5.988876 +v 1.000825 -0.109586 -1.452513 +v 1.061916 0.776753 -1.797960 +v 1.803061 0.776753 -5.988875 +v 1.000825 0.324040 1.759412 +v 0.000825 -1.219172 -5.532512 +v 0.000825 -0.666304 5.896139 +v 0.000826 1.000000 -6.334325 +v 0.000825 -0.129842 5.896140 +v 0.000825 0.852342 1.899606 +v 0.000825 -0.677484 1.899604 +v 0.000825 -0.351920 -1.293797 +v 0.000825 1.000000 -1.293796 +v 0.000825 1.000000 -1.452513 +v 0.000825 -1.219172 -1.452515 +v 0.000825 -0.351920 1.759409 +v 0.000825 1.000000 1.759411 +v 0.000826 -0.109586 -6.334326 +v 0.000825 -0.398073 5.896140 +v 0.152918 1.000000 -5.815708 +v 0.152917 1.000000 -1.971130 +v 0.940448 1.168419 -1.971128 +v 1.620345 1.168419 -5.815706 +v 0.152918 1.168419 -5.815708 +v 0.152917 1.168419 -1.971130 +v 0.921118 1.091883 -1.050430 +v 0.921118 1.091883 1.516050 +v 0.080533 1.091883 -1.050432 +v 0.080533 1.091883 1.516048 +v 0.613003 -0.553430 5.546911 +v 0.963691 -0.559956 2.248834 +v 0.613003 -0.396857 5.546912 +v 0.963691 -0.070362 2.248835 +v 1.499370 -0.994317 3.966028 +v 1.850058 -0.997914 0.667950 +v 1.499370 -0.908021 3.966029 +v 1.850058 -0.728071 0.667951 +v 1.601022 0.760960 -6.334324 +v 1.601021 0.129454 -6.334325 +v 0.263955 0.760960 -6.334325 +v 0.263955 0.129454 -6.334325 +v 1.334809 0.760960 -7.515329 +v 1.334809 0.129455 -7.515330 +v 0.530168 0.760960 -7.515330 +v 0.530168 0.129455 -7.515330 +v 1.192720 0.649445 -7.515329 +v 1.192720 0.240971 -7.515330 +v 0.672258 0.649445 -7.515330 +v 0.672258 0.240971 -7.515330 +v 1.192719 0.649444 -6.524630 +v 1.192719 0.240970 -6.524631 +v 0.672257 0.649444 -6.524631 +v 0.672257 0.240970 -6.524631 +v 3.851026 0.431116 -1.883326 +v 3.851026 0.946662 -1.883325 +v 4.592170 0.946662 -6.074241 +v 4.592169 0.431116 -6.074242 +v 4.995714 0.561404 -1.918362 +v 4.995714 1.016394 -1.918360 +v 5.736857 1.016394 -6.109276 +v 5.736857 0.561404 -6.109277 +v 3.975454 0.471731 -2.162156 +v 3.975454 0.919244 -2.162155 +v 4.618796 0.919244 -5.800034 +v 4.618795 0.471730 -5.800035 +v 4.969088 0.584825 -2.192568 +v 4.969088 0.979775 -2.192567 +v 5.612430 0.979775 -5.830446 +v 5.612429 0.584825 -5.830447 +v 0.864214 -0.673890 3.184381 +v 0.864213 0.489129 3.184384 +v 0.864213 -0.018552 3.184383 +v 0.000825 0.489129 3.184382 +v 0.000825 -0.673890 3.184381 +v 0.850955 -0.557858 3.309075 +v 0.850955 -0.175321 3.309076 +v 1.737321 -0.996758 1.728192 +v 1.737321 -0.785920 1.728193 +v -1.864151 -1.219172 -5.532511 +v -0.575869 -0.666304 5.896140 +v -0.940448 1.000000 -1.971128 +v -1.620345 1.000000 -5.815706 +v -1.864152 1.000000 -6.334323 +v -0.575869 -0.129842 5.896143 +v -5.440438 -1.462153 -5.818601 +v -4.896782 -1.462153 -2.744413 +v -1.000825 -0.677484 1.899605 +v -5.440438 -1.246362 -5.818600 +v -1.000825 0.852342 1.899608 +v -4.896782 -1.246362 -2.744412 +v -1.160660 -0.450871 -2.356325 +v -1.704316 -0.450871 -5.430513 +v -1.000825 -0.351920 -1.293797 +v -1.000825 1.000000 -1.293794 +v -1.160660 -0.877888 -2.356326 +v -1.704316 -0.877888 -5.430514 +v -1.000825 -1.219172 -1.452514 +v -1.000825 1.000000 -1.452511 +v -1.000825 -0.351920 1.759410 +v -1.000825 1.000000 1.759413 +v -9.097919 1.221145 -6.212147 +v -8.356775 1.221145 -2.021231 +v -1.864151 -0.109586 -6.334325 +v -0.575869 -0.398073 5.896141 +v -9.097919 0.943958 -6.212148 +v -8.356775 0.943958 -2.021233 +v -1.061916 0.113661 -1.797961 +v -1.000825 0.161258 1.899606 +v -1.000825 0.324040 -1.293795 +v -1.803060 0.113661 -5.988876 +v -1.000825 -0.109586 -1.452513 +v -1.061916 0.776753 -1.797960 +v -1.803061 0.776753 -5.988875 +v -1.000825 0.324040 1.759412 +v -0.000825 -1.219172 -5.532512 +v -0.000825 -0.666304 5.896139 +v -0.000826 1.000000 -6.334325 +v -0.000825 -0.129842 5.896140 +v -0.000825 0.852342 1.899606 +v -0.000825 -0.677484 1.899604 +v -0.000825 -0.351920 -1.293797 +v -0.000825 1.000000 -1.293796 +v -0.000825 1.000000 -1.452513 +v -0.000825 -1.219172 -1.452515 +v -0.000825 -0.351920 1.759409 +v -0.000825 1.000000 1.759411 +v -0.000826 -0.109586 -6.334326 +v -0.000825 -0.398073 5.896140 +v -0.152918 1.000000 -5.815708 +v -0.152917 1.000000 -1.971130 +v -0.940448 1.168419 -1.971128 +v -1.620345 1.168419 -5.815706 +v -0.152918 1.168419 -5.815708 +v -0.152917 1.168419 -1.971130 +v -0.921118 1.091883 -1.050430 +v -0.921118 1.091883 1.516050 +v -0.080533 1.091883 -1.050432 +v -0.080533 1.091883 1.516048 +v -0.613003 -0.553430 5.546911 +v -0.963691 -0.559956 2.248834 +v -0.613003 -0.396857 5.546912 +v -0.963691 -0.070362 2.248835 +v -1.499370 -0.994317 3.966028 +v -1.850058 -0.997914 0.667950 +v -1.499370 -0.908021 3.966029 +v -1.850058 -0.728071 0.667951 +v -1.601022 0.760960 -6.334324 +v -1.601021 0.129454 -6.334325 +v -0.263955 0.760960 -6.334325 +v -0.263955 0.129454 -6.334325 +v -1.334809 0.760960 -7.515329 +v -1.334809 0.129455 -7.515330 +v -0.530168 0.760960 -7.515330 +v -0.530168 0.129455 -7.515330 +v -1.192720 0.649445 -7.515329 +v -1.192720 0.240971 -7.515330 +v -0.672258 0.649445 -7.515330 +v -0.672258 0.240971 -7.515330 +v -1.192719 0.649444 -6.524630 +v -1.192719 0.240970 -6.524631 +v -0.672257 0.649444 -6.524631 +v -0.672257 0.240970 -6.524631 +v -3.851026 0.431116 -1.883326 +v -3.851026 0.946662 -1.883325 +v -4.592170 0.946662 -6.074241 +v -4.592169 0.431116 -6.074242 +v -4.995714 0.561404 -1.918362 +v -4.995714 1.016394 -1.918360 +v -5.736857 1.016394 -6.109276 +v -5.736857 0.561404 -6.109277 +v -3.975454 0.471731 -2.162156 +v -3.975454 0.919244 -2.162155 +v -4.618796 0.919244 -5.800034 +v -4.618795 0.471730 -5.800035 +v -4.969088 0.584825 -2.192568 +v -4.969088 0.979775 -2.192567 +v -5.612430 0.979775 -5.830446 +v -5.612429 0.584825 -5.830447 +v -0.864214 -0.673890 3.184381 +v -0.864213 0.489129 3.184384 +v -0.864213 -0.018552 3.184383 +v -0.000825 0.489129 3.184382 +v -0.000825 -0.673890 3.184381 +v -0.850955 -0.557858 3.309075 +v -0.850955 -0.175321 3.309076 +v -1.737321 -0.996758 1.728192 +v -1.737321 -0.785920 1.728193 +vt 0.135351 -0.558072 +vt 0.003035 -0.363507 +vt 0.092282 -0.976844 +vt -0.081322 0.947351 +vt 0.100058 1.958891 +vt 0.050091 1.852185 +vt -0.092752 1.055565 +vt -0.251711 1.059474 +vt 0.075587 0.041384 +vt -0.086008 0.279003 +vt -0.086212 0.249830 +vt -0.276044 1.968137 +vt -0.246101 1.859467 +vt 0.009828 1.911388 +vt -0.133014 1.114769 +vt 0.413322 1.261595 +vt 0.299103 0.624605 +vt 1.243955 0.407183 +vt 0.515404 1.111487 +vt 1.358173 1.044173 +vt -0.081553 0.914324 +vt 0.080042 0.676706 +vt 0.401185 0.474498 +vt 1.295541 0.331328 +vt 0.365315 1.568841 +vt 0.299111 1.575740 +vt 0.143401 0.707357 +vt 0.629403 1.011947 +vt 0.449192 0.167251 +vt 1.409760 0.968317 +vt 0.986264 1.738667 +vt 1.573373 1.877873 +vt 1.417663 1.009490 +vt 0.237182 -0.196235 +vt 0.721785 1.030226 +vt 0.830554 0.870285 +vt 0.877494 1.898608 +vt 1.351399 1.106930 +vt 0.183935 0.557301 +vt 1.507109 1.975312 +vt 0.241636 0.439088 +vt 0.114297 -0.045011 +vt 0.140593 1.808834 +vt -0.015118 0.940452 +vt 0.156405 -1.071134 +vt 0.164119 -0.998223 +vt 0.040336 -1.068281 +vt 0.104459 -1.162571 +vt -0.165787 1.882802 +vt -0.014821 1.660811 +vt -0.287852 0.283965 +vt -0.293374 0.366508 +vt -0.289630 0.900550 +vt 0.035337 -0.191272 +vt 0.247348 0.172213 +vt 0.253300 1.021193 +vt -0.283166 0.952313 +vt -0.283398 0.919286 +vt 0.039792 0.444050 +vt 0.314806 -0.339851 +vt 0.112962 -0.334889 +vt -0.288056 0.254793 +vt -0.023788 -0.973990 +vt -0.155922 -0.359599 +vt 0.220528 -1.165425 +vt 0.108710 -0.748730 +vt -0.286364 1.918670 +vt -0.291973 1.118678 +vt -0.119962 0.896379 +vt -0.123707 0.362337 +vt 0.162891 -0.598569 +vt 0.467532 -0.853353 +vt 0.201549 -1.053262 +vt 0.161663 -0.198915 +vt 0.267667 -0.752638 +vt 0.278705 -0.371021 +vt 0.526390 -0.542053 +vt 0.483821 -0.479457 +vt 0.488162 -0.883689 +vt 0.500110 -0.105561 +vt 0.564618 -0.200418 +vt -0.110331 2.127229 +vt 0.040636 1.905238 +vt -0.010786 1.578087 +vt 0.104092 1.876168 +vt 0.255058 1.654176 +vt -0.054992 2.087323 +vt 0.203048 1.901245 +vt 0.052081 2.123235 +vt 0.042658 1.943733 +vt -0.056437 1.881175 +vt 0.147710 1.941151 +vt 0.050060 2.084741 +vt 0.146264 1.735002 +vt 0.041212 1.737584 +vt 0.048615 1.878591 +vt 0.663065 1.872485 +vt 0.786311 1.691257 +vt 0.507355 1.004102 +vt 0.630601 0.822874 +vt 0.955144 1.689498 +vt 0.860727 1.828333 +vt 0.725565 1.074543 +vt 0.819981 0.935708 +vt 0.674594 1.805657 +vt 0.539432 1.051867 +vt 0.646413 0.894554 +vt 0.781576 1.648344 +vt 0.240127 -0.712141 +vn 0.994400 0.000000 0.105700 +vn 0.000000 1.000000 0.000000 +vn 1.000000 0.000000 0.000000 +vn 0.984700 0.000000 0.174100 +vn 0.211800 0.976600 0.037500 +vn -0.103300 0.000000 -0.994600 +vn 0.103300 -0.000000 0.994600 +vn 0.911400 0.378700 0.161200 +vn -0.157300 -0.987200 -0.027800 +vn 0.113700 -0.993300 0.020100 +vn 0.030600 -0.000000 0.999500 +vn -0.061100 0.998100 -0.010800 +vn -0.030600 0.000000 -0.999500 +vn -0.000000 -0.000000 1.000000 +vn 0.000000 0.000000 -1.000000 +vn -0.755400 0.655300 0.000000 +vn 0.000000 -1.000000 0.000000 +vn -0.000000 -0.180000 0.983700 +vn 0.000000 -0.395500 -0.918500 +vn -0.000000 0.688500 0.725200 +vn 0.000000 -0.585700 -0.810500 +vn -0.000000 0.974900 0.222500 +vn -0.000000 -1.000000 0.002800 +vn -1.000000 0.000000 -0.000000 +vn -0.000000 0.935500 0.353200 +vn 0.755400 0.655300 0.000000 +vn 0.000000 0.935500 -0.353200 +vn 0.673800 0.724900 0.143400 +vn 0.872300 -0.000000 0.489100 +vn -0.872300 0.000000 -0.489100 +vn -0.518300 -0.853500 -0.054200 +vn -0.975500 0.000000 -0.219900 +vn 0.975500 0.000000 -0.219900 +vn -0.913200 0.000000 -0.407500 +vn -0.436900 0.896200 -0.077300 +vn -0.995300 -0.000000 0.096600 +vn -0.297300 -0.953400 -0.052600 +vn 0.473900 -0.876600 0.083800 +vn 0.913200 0.000000 0.407500 +vn 0.342200 0.937700 0.060500 +vn 0.995300 -0.000000 -0.096600 +vn -0.519200 -0.853000 -0.054300 +vn 0.722400 0.676400 0.143800 +vn -0.994400 0.000000 0.105700 +vn -0.984700 0.000000 0.174100 +vn -0.211800 0.976600 0.037500 +vn 0.103300 0.000000 -0.994600 +vn -0.103300 -0.000000 0.994600 +vn -0.911400 0.378700 0.161200 +vn 0.157300 -0.987200 -0.027800 +vn -0.113700 -0.993300 0.020100 +vn -0.030600 -0.000000 0.999500 +vn 0.061100 0.998100 -0.010800 +vn 0.030600 0.000000 -0.999500 +vn -0.691900 0.713200 0.112500 +vn -0.872300 -0.000000 0.489100 +vn 0.872300 0.000000 -0.489100 +vn 0.518300 -0.853500 -0.054200 +vn 0.913200 0.000000 -0.407500 +vn 0.436900 0.896200 -0.077300 +vn 0.995300 0.000000 0.096600 +vn 0.297300 -0.953300 -0.052600 +vn -0.473900 -0.876600 0.083800 +vn -0.913200 -0.000000 0.407500 +vn -0.342200 0.937700 0.060500 +vn -0.995300 -0.000000 -0.096600 +vn 0.519200 -0.853000 -0.054300 +vn -0.714800 0.690100 0.113700 +vn 0.974400 0.089700 0.206200 +vn 0.870400 0.288400 0.399100 +vn 0.691900 0.713200 0.112500 +vn -0.518000 -0.853700 -0.053400 +vn -0.519700 -0.852700 -0.053600 +vn 0.714800 0.690100 0.113700 +vn -0.974400 0.089700 0.206200 +vn -0.870400 0.288400 0.399100 +vn -0.673800 0.724900 0.143400 +vn 0.518000 -0.853700 -0.053400 +vn 0.297300 -0.953400 -0.052600 +vn 0.519700 -0.852700 -0.053600 +vn -0.722400 0.676400 0.143800 +vn -0.000000 0.962300 0.272000 +usemtl Material.001 +s off +f 103/1/1 102/2/1 6/3/1 +f 20/4/2 5/5/2 4/6/2 +f 20/4/2 3/7/2 52/8/2 +f 36/9/3 22/10/3 11/11/3 +f 39/12/2 51/13/2 4/6/2 +f 4/6/4 54/14/4 53/15/4 +f 14/16/5 13/17/5 12/18/5 +f 18/19/6 14/16/6 10/20/6 +f 20/4/3 16/21/3 31/22/3 +f 17/23/7 8/24/7 12/18/7 +f 25/25/4 32/26/4 29/27/4 +f 10/20/4 12/18/4 8/24/4 +f 1/28/8 18/19/8 17/23/8 +f 19/29/4 17/23/4 13/17/4 +f 25/25/4 14/16/4 18/19/4 +f 18/19/9 7/30/9 8/24/9 +f 92/31/10 27/32/10 28/33/10 +f 16/21/3 22/10/3 36/9/3 +f 31/22/3 36/9/3 21/34/3 +f 90/35/11 89/36/11 28/33/11 +f 91/37/12 90/35/12 24/38/12 +f 33/39/4 13/17/4 14/16/4 +f 23/40/4 24/38/4 28/33/4 +f 33/39/3 31/22/3 15/41/3 +f 21/34/3 36/9/3 30/42/3 +f 5/5/4 35/43/4 32/26/4 +f 5/5/4 20/4/4 34/44/4 +f 33/39/4 29/27/4 34/44/4 +f 91/37/13 23/40/13 27/32/13 +f 103/1/1 26/45/1 63/46/1 +f 26/45/14 50/47/14 38/48/14 +f 39/12/15 71/49/15 72/50/15 +f 48/51/16 60/52/16 59/53/16 +f 15/41/17 21/34/17 47/54/17 +f 19/29/17 46/55/17 37/56/17 +f 39/12/2 45/57/2 52/8/2 +f 20/4/2 45/57/2 44/58/2 +f 19/29/18 15/41/18 43/59/18 +f 9/60/19 42/61/19 47/54/19 +f 22/10/20 48/51/20 41/62/20 +f 25/25/21 1/28/21 37/56/21 +f 6/3/14 40/63/14 50/47/14 +f 104/64/22 40/63/22 6/3/22 +f 2/65/23 38/48/23 105/66/23 +f 55/67/2 56/68/2 53/15/2 +f 3/7/14 53/15/14 56/68/14 +f 51/13/15 55/67/15 54/14/15 +f 52/8/24 56/68/24 55/67/24 +f 57/69/2 59/53/2 60/52/2 +f 48/51/25 22/10/25 58/70/25 +f 16/21/26 57/69/26 58/70/26 +f 16/21/27 44/58/27 59/53/27 +f 107/71/28 63/46/28 67/72/28 +f 26/45/1 2/65/1 61/73/1 +f 9/60/1 30/42/1 64/74/1 +f 101/75/1 9/60/1 62/76/1 +f 108/77/1 109/78/1 67/72/1 +f 61/73/29 65/79/29 67/72/29 +f 62/76/30 64/74/30 68/80/30 +f 62/76/31 66/81/31 108/77/31 +f 71/49/32 75/82/32 76/83/32 +f 25/25/15 49/84/15 72/50/15 +f 5/5/15 69/85/15 71/49/15 +f 25/25/15 70/86/15 69/85/15 +f 76/83/15 75/82/15 79/87/15 +f 72/50/17 76/83/17 74/88/17 +f 71/49/2 69/85/2 73/89/2 +f 70/86/33 74/88/33 73/89/33 +f 80/90/3 79/87/3 83/91/3 +f 76/83/15 80/90/15 78/92/15 +f 75/82/15 73/89/15 77/93/15 +f 74/88/15 78/92/15 77/93/15 +f 82/94/15 84/95/15 83/91/15 +f 80/90/2 84/95/2 82/94/2 +f 77/93/17 81/96/17 83/91/17 +f 77/93/24 78/92/24 82/94/24 +f 35/43/13 87/97/13 88/98/13 +f 35/43/12 34/44/12 86/99/12 +f 34/44/11 29/27/11 85/100/11 +f 32/26/10 88/98/10 85/100/10 +f 92/31/34 100/101/34 99/102/34 +f 90/35/35 91/37/35 99/102/35 +f 89/36/36 90/35/36 98/103/36 +f 89/36/37 97/104/37 100/101/37 +f 95/105/13 99/102/13 100/101/13 +f 95/105/12 94/106/12 98/103/12 +f 94/106/11 93/107/11 97/104/11 +f 96/108/10 100/101/10 97/104/10 +f 88/98/38 96/108/38 93/107/38 +f 86/99/39 85/100/39 93/107/39 +f 87/97/40 86/99/40 94/106/40 +f 87/97/41 95/105/41 96/108/41 +f 106/109/42 108/77/42 65/79/42 +f 66/81/1 68/80/1 109/78/1 +f 101/75/1 106/109/1 61/73/1 +f 64/74/43 107/71/43 109/78/43 +f 101/75/23 105/66/23 42/61/23 +f 103/1/1 107/71/1 64/74/1 +f 30/42/1 11/11/1 102/2/1 +f 212/1/44 135/45/44 115/3/44 +f 129/4/2 112/7/2 113/6/2 +f 161/8/2 112/7/2 129/4/2 +f 145/9/24 139/42/24 120/11/24 +f 113/6/2 160/13/2 148/12/2 +f 162/15/45 163/14/45 113/6/45 +f 123/16/46 119/20/46 121/18/46 +f 127/19/47 116/30/47 119/20/47 +f 140/22/24 125/21/24 129/4/24 +f 121/18/48 117/24/48 126/23/48 +f 138/27/45 141/26/45 134/25/45 +f 117/24/45 121/18/45 119/20/45 +f 126/23/49 127/19/49 110/28/49 +f 122/17/45 126/23/45 128/29/45 +f 127/19/45 123/16/45 134/25/45 +f 117/24/50 116/30/50 127/19/50 +f 137/33/51 136/32/51 201/31/51 +f 145/9/24 131/10/24 125/21/24 +f 130/34/24 145/9/24 140/22/24 +f 199/35/52 133/38/52 137/33/52 +f 200/37/53 132/40/53 133/38/53 +f 123/16/45 122/17/45 142/39/45 +f 137/33/45 133/38/45 132/40/45 +f 124/41/24 140/22/24 142/39/24 +f 130/34/24 118/60/24 139/42/24 +f 141/26/45 144/43/45 114/5/45 +f 114/5/45 144/43/45 143/44/45 +f 143/44/45 138/27/45 142/39/45 +f 136/32/54 132/40/54 200/37/54 +f 212/1/44 216/71/44 172/46/44 +f 147/48/14 159/47/14 135/45/14 +f 181/50/15 180/49/15 148/12/15 +f 168/53/26 169/52/26 157/51/26 +f 124/41/17 152/59/17 156/54/17 +f 146/56/17 155/55/17 128/29/17 +f 148/12/2 160/13/2 161/8/2 +f 129/4/2 125/21/2 153/58/2 +f 155/55/18 152/59/18 124/41/18 +f 130/34/19 156/54/19 151/61/19 +f 131/10/20 120/11/20 150/62/20 +f 134/25/21 158/84/21 146/56/21 +f 159/47/14 149/63/14 115/3/14 +f 115/3/22 149/63/22 213/64/22 +f 214/66/23 147/48/23 111/65/23 +f 162/15/2 165/68/2 164/67/2 +f 165/68/14 162/15/14 112/7/14 +f 163/14/15 164/67/15 160/13/15 +f 164/67/3 165/68/3 161/8/3 +f 166/69/2 167/70/2 169/52/2 +f 157/51/25 169/52/25 167/70/25 +f 167/70/16 166/69/16 125/21/16 +f 125/21/27 166/69/27 168/53/27 +f 216/71/55 218/78/55 176/72/55 +f 135/45/44 172/46/44 170/73/44 +f 118/60/44 171/76/44 173/74/44 +f 210/75/44 215/109/44 171/76/44 +f 217/77/44 174/79/44 176/72/44 +f 176/72/56 174/79/56 170/73/56 +f 171/76/57 175/81/57 177/80/57 +f 217/77/58 175/81/58 171/76/58 +f 185/83/33 184/82/33 180/49/33 +f 134/25/15 179/86/15 181/50/15 +f 180/49/15 178/85/15 114/5/15 +f 178/85/15 179/86/15 134/25/15 +f 189/90/15 188/87/15 184/82/15 +f 183/88/17 185/83/17 181/50/17 +f 180/49/2 184/82/2 182/89/2 +f 182/89/32 183/88/32 179/86/32 +f 189/90/24 193/95/24 192/91/24 +f 187/92/15 189/90/15 185/83/15 +f 184/82/15 188/87/15 186/93/15 +f 186/93/15 187/92/15 183/88/15 +f 192/91/15 193/95/15 191/94/15 +f 191/94/2 193/95/2 189/90/2 +f 192/91/17 190/96/17 186/93/17 +f 186/93/3 190/96/3 191/94/3 +f 197/98/54 196/97/54 144/43/54 +f 144/43/53 196/97/53 195/99/53 +f 143/44/52 195/99/52 194/100/52 +f 194/100/51 197/98/51 141/26/51 +f 208/102/59 209/101/59 201/31/59 +f 199/35/60 207/103/60 208/102/60 +f 198/36/61 206/104/61 207/103/61 +f 209/101/62 206/104/62 198/36/62 +f 209/101/54 208/102/54 204/105/54 +f 204/105/53 208/102/53 207/103/53 +f 203/106/52 207/103/52 206/104/52 +f 206/104/51 209/101/51 205/108/51 +f 202/107/63 205/108/63 197/98/63 +f 195/99/64 203/106/64 202/107/64 +f 196/97/65 204/105/65 203/106/65 +f 205/108/66 204/105/66 196/97/66 +f 174/79/67 217/77/67 215/109/67 +f 175/81/44 217/77/44 218/78/44 +f 170/73/44 215/109/44 210/75/44 +f 173/74/68 177/80/68 218/78/68 +f 151/61/23 214/66/23 210/75/23 +f 173/74/44 216/71/44 212/1/44 +f 139/42/44 212/1/44 211/2/44 +f 26/45/1 103/1/1 6/3/1 +f 3/7/2 20/4/2 4/6/2 +f 45/57/2 20/4/2 52/8/2 +f 30/42/3 36/9/3 11/11/3 +f 5/5/2 39/12/2 4/6/2 +f 3/7/4 4/6/4 53/15/4 +f 10/20/5 14/16/5 12/18/5 +f 7/30/6 18/19/6 10/20/6 +f 33/39/3 20/4/3 31/22/3 +f 13/17/7 17/23/7 12/18/7 +f 33/39/4 25/25/4 29/27/4 +f 7/30/4 10/20/4 8/24/4 +f 19/29/69 1/28/69 17/23/69 +f 33/39/4 19/29/4 13/17/4 +f 1/28/70 25/25/70 18/19/70 +f 17/23/9 18/19/9 8/24/9 +f 89/36/10 92/31/10 28/33/10 +f 31/22/3 16/21/3 36/9/3 +f 15/41/3 31/22/3 21/34/3 +f 24/38/11 90/35/11 28/33/11 +f 23/40/12 91/37/12 24/38/12 +f 25/25/4 33/39/4 14/16/4 +f 27/32/4 23/40/4 28/33/4 +f 19/29/3 33/39/3 15/41/3 +f 9/60/3 21/34/3 30/42/3 +f 25/25/4 5/5/4 32/26/4 +f 35/43/4 5/5/4 34/44/4 +f 20/4/4 33/39/4 34/44/4 +f 92/31/13 91/37/13 27/32/13 +f 107/71/1 103/1/1 63/46/1 +f 2/65/14 26/45/14 38/48/14 +f 49/84/15 39/12/15 72/50/15 +f 44/58/16 48/51/16 59/53/16 +f 43/59/17 15/41/17 47/54/17 +f 1/28/17 19/29/17 37/56/17 +f 51/13/2 39/12/2 52/8/2 +f 16/21/2 20/4/2 44/58/2 +f 46/55/18 19/29/18 43/59/18 +f 21/34/19 9/60/19 47/54/19 +f 11/11/20 22/10/20 41/62/20 +f 49/84/21 25/25/21 37/56/21 +f 26/45/14 6/3/14 50/47/14 +f 102/2/22 104/64/22 6/3/22 +f 101/75/23 2/65/23 105/66/23 +f 54/14/2 55/67/2 53/15/2 +f 52/8/14 3/7/14 56/68/14 +f 4/6/15 51/13/15 54/14/15 +f 51/13/24 52/8/24 55/67/24 +f 58/70/2 57/69/2 60/52/2 +f 60/52/25 48/51/25 58/70/25 +f 22/10/26 16/21/26 58/70/26 +f 57/69/27 16/21/27 59/53/27 +f 109/78/71 107/71/71 67/72/71 +f 63/46/1 26/45/1 61/73/1 +f 62/76/1 9/60/1 64/74/1 +f 106/109/1 101/75/1 62/76/1 +f 65/79/1 108/77/1 67/72/1 +f 63/46/29 61/73/29 67/72/29 +f 66/81/30 62/76/30 68/80/30 +f 106/109/72 62/76/72 108/77/72 +f 72/50/32 71/49/32 76/83/32 +f 70/86/15 25/25/15 72/50/15 +f 39/12/15 5/5/15 71/49/15 +f 5/5/15 25/25/15 69/85/15 +f 80/90/15 76/83/15 79/87/15 +f 70/86/17 72/50/17 74/88/17 +f 75/82/2 71/49/2 73/89/2 +f 69/85/33 70/86/33 73/89/33 +f 84/95/3 80/90/3 83/91/3 +f 74/88/15 76/83/15 78/92/15 +f 79/87/15 75/82/15 77/93/15 +f 73/89/15 74/88/15 77/93/15 +f 81/96/15 82/94/15 83/91/15 +f 78/92/2 80/90/2 82/94/2 +f 79/87/17 77/93/17 83/91/17 +f 81/96/24 77/93/24 82/94/24 +f 32/26/13 35/43/13 88/98/13 +f 87/97/12 35/43/12 86/99/12 +f 86/99/11 34/44/11 85/100/11 +f 29/27/10 32/26/10 85/100/10 +f 91/37/34 92/31/34 99/102/34 +f 98/103/35 90/35/35 99/102/35 +f 97/104/36 89/36/36 98/103/36 +f 92/31/37 89/36/37 100/101/37 +f 96/108/13 95/105/13 100/101/13 +f 99/102/12 95/105/12 98/103/12 +f 98/103/11 94/106/11 97/104/11 +f 93/107/10 96/108/10 97/104/10 +f 85/100/38 88/98/38 93/107/38 +f 94/106/39 86/99/39 93/107/39 +f 95/105/40 87/97/40 94/106/40 +f 88/98/41 87/97/41 96/108/41 +f 61/73/73 106/109/73 65/79/73 +f 108/77/1 66/81/1 109/78/1 +f 2/65/1 101/75/1 61/73/1 +f 68/80/74 64/74/74 109/78/74 +f 9/60/23 101/75/23 42/61/23 +f 30/42/1 103/1/1 64/74/1 +f 103/1/1 30/42/1 102/2/1 +f 211/2/44 212/1/44 115/3/44 +f 114/5/2 129/4/2 113/6/2 +f 154/57/2 161/8/2 129/4/2 +f 131/10/24 145/9/24 120/11/24 +f 114/5/2 113/6/2 148/12/2 +f 112/7/45 162/15/45 113/6/45 +f 122/17/46 123/16/46 121/18/46 +f 123/16/47 127/19/47 119/20/47 +f 142/39/24 140/22/24 129/4/24 +f 122/17/48 121/18/48 126/23/48 +f 142/39/45 138/27/45 134/25/45 +f 116/30/45 117/24/45 119/20/45 +f 128/29/75 126/23/75 110/28/75 +f 142/39/45 122/17/45 128/29/45 +f 110/28/76 127/19/76 134/25/76 +f 126/23/50 117/24/50 127/19/50 +f 198/36/51 137/33/51 201/31/51 +f 140/22/24 145/9/24 125/21/24 +f 124/41/24 130/34/24 140/22/24 +f 198/36/52 199/35/52 137/33/52 +f 199/35/53 200/37/53 133/38/53 +f 134/25/45 123/16/45 142/39/45 +f 136/32/45 137/33/45 132/40/45 +f 128/29/24 124/41/24 142/39/24 +f 145/9/24 130/34/24 139/42/24 +f 134/25/45 141/26/45 114/5/45 +f 129/4/45 114/5/45 143/44/45 +f 129/4/45 143/44/45 142/39/45 +f 201/31/54 136/32/54 200/37/54 +f 135/45/44 212/1/44 172/46/44 +f 111/65/14 147/48/14 135/45/14 +f 158/84/15 181/50/15 148/12/15 +f 153/58/26 168/53/26 157/51/26 +f 130/34/17 124/41/17 156/54/17 +f 110/28/17 146/56/17 128/29/17 +f 154/57/2 148/12/2 161/8/2 +f 154/57/2 129/4/2 153/58/2 +f 128/29/18 155/55/18 124/41/18 +f 118/60/19 130/34/19 151/61/19 +f 157/51/20 131/10/20 150/62/20 +f 110/28/21 134/25/21 146/56/21 +f 135/45/14 159/47/14 115/3/14 +f 211/2/22 115/3/22 213/64/22 +f 210/75/23 214/66/23 111/65/23 +f 163/14/2 162/15/2 164/67/2 +f 161/8/14 165/68/14 112/7/14 +f 113/6/15 163/14/15 160/13/15 +f 160/13/3 164/67/3 161/8/3 +f 168/53/2 166/69/2 169/52/2 +f 131/10/25 157/51/25 167/70/25 +f 131/10/16 167/70/16 125/21/16 +f 153/58/27 125/21/27 168/53/27 +f 172/46/77 216/71/77 176/72/77 +f 111/65/44 135/45/44 170/73/44 +f 139/42/44 118/60/44 173/74/44 +f 118/60/44 210/75/44 171/76/44 +f 218/78/44 217/77/44 176/72/44 +f 172/46/56 176/72/56 170/73/56 +f 173/74/57 171/76/57 177/80/57 +f 215/109/78 217/77/78 171/76/78 +f 181/50/33 185/83/33 180/49/33 +f 158/84/15 134/25/15 181/50/15 +f 148/12/15 180/49/15 114/5/15 +f 114/5/15 178/85/15 134/25/15 +f 185/83/15 189/90/15 184/82/15 +f 179/86/17 183/88/17 181/50/17 +f 178/85/2 180/49/2 182/89/2 +f 178/85/32 182/89/32 179/86/32 +f 188/87/24 189/90/24 192/91/24 +f 183/88/15 187/92/15 185/83/15 +f 182/89/15 184/82/15 186/93/15 +f 182/89/15 186/93/15 183/88/15 +f 190/96/15 192/91/15 191/94/15 +f 187/92/2 191/94/2 189/90/2 +f 188/87/17 192/91/17 186/93/17 +f 187/92/3 186/93/3 191/94/3 +f 141/26/54 197/98/54 144/43/54 +f 143/44/53 144/43/53 195/99/53 +f 138/27/52 143/44/52 194/100/52 +f 138/27/51 194/100/51 141/26/51 +f 200/37/59 208/102/59 201/31/59 +f 200/37/60 199/35/60 208/102/60 +f 199/35/61 198/36/61 207/103/61 +f 201/31/79 209/101/79 198/36/79 +f 205/108/54 209/101/54 204/105/54 +f 203/106/53 204/105/53 207/103/53 +f 202/107/52 203/106/52 206/104/52 +f 202/107/51 206/104/51 205/108/51 +f 194/100/63 202/107/63 197/98/63 +f 194/100/64 195/99/64 202/107/64 +f 195/99/65 196/97/65 203/106/65 +f 197/98/66 205/108/66 196/97/66 +f 170/73/80 174/79/80 215/109/80 +f 177/80/44 175/81/44 218/78/44 +f 111/65/44 170/73/44 210/75/44 +f 216/71/81 173/74/81 218/78/81 +f 118/60/23 151/61/23 210/75/23 +f 139/42/44 173/74/44 212/1/44 +f 120/11/44 139/42/44 211/2/44 +usemtl Material.003 +f 41/62/82 104/64/82 102/2/82 +f 211/2/82 213/64/82 150/62/82 +f 11/11/82 41/62/82 102/2/82 +f 120/11/82 211/2/82 150/62/82 diff --git a/tests/tester.cc b/tests/tester.cc index 649451d..a76359a 100644 --- a/tests/tester.cc +++ b/tests/tester.cc @@ -585,6 +585,26 @@ TEST_CASE("refl", "[refl]") { REQUIRE(materials[0].reflection_texname.compare("reflection.tga") == 0); } +TEST_CASE("map_Bump", "[bump]") { + tinyobj::attrib_t attrib; + std::vector shapes; + std::vector materials; + + std::string err; + bool ret = tinyobj::LoadObj(&attrib, &shapes, &materials, &err, "../models/map-bump.obj", gMtlBasePath); + + if (!err.empty()) { + std::cerr << err << std::endl; + } + + PrintInfo(attrib, shapes, materials); + + REQUIRE(true == ret); + REQUIRE(2 == materials.size()); + + REQUIRE(materials[0].bump_texname.compare("bump.jpg") == 0); +} + #if 0 int main( diff --git a/tiny_obj_loader.h b/tiny_obj_loader.h index 3273e9d..34cd85e 100644 --- a/tiny_obj_loader.h +++ b/tiny_obj_loader.h @@ -153,7 +153,7 @@ typedef struct { std::string diffuse_texname; // map_Kd std::string specular_texname; // map_Ks std::string specular_highlight_texname; // map_Ns - std::string bump_texname; // map_bump, bump + std::string bump_texname; // map_bump, map_Bump, bump std::string displacement_texname; // disp std::string alpha_texname; // map_d std::string reflection_texname; // refl @@ -1240,6 +1240,15 @@ void LoadMtl(std::map *material_map, continue; } + // bump texture + if ((0 == strncmp(token, "map_Bump", 8)) && IS_SPACE(token[8])) { + token += 9; + ParseTextureNameAndOption(&(material.bump_texname), + &(material.bump_texopt), token, + /* is_bump */ true); + continue; + } + // bump texture if ((0 == strncmp(token, "bump", 4)) && IS_SPACE(token[4])) { token += 5; -- cgit v1.2.3 From dcbc8d51aa2e2facb1eb2a884b31dd280880c7a5 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Sun, 3 Sep 2017 12:45:57 +0900 Subject: Update URL for Rungholt scene. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7a3e86b..2acea1f 100644 --- a/README.md +++ b/README.md @@ -37,7 +37,7 @@ Previous old version is avaiable in `v0.9.x` branch. ![Rungholt](images/rungholt.jpg) tinyobjloader can successfully load 6M triangles Rungholt scene. -http://graphics.cs.williams.edu/data/meshes.xml +http://casual-effects.com/data/index.html ![](images/sanmugel.png) -- cgit v1.2.3 From 981f7c5f99053e6010410f0b60a9377795c3a660 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Mon, 11 Sep 2017 14:58:40 +0900 Subject: Add VFPR URL. --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 7c95235..582e6db 100644 --- a/README.md +++ b/README.md @@ -57,6 +57,7 @@ TinyObjLoader is successfully used in ... * Vulkan Cookbook https://github.com/PacktPublishing/Vulkan-Cookbook * cudabox: CUDA Solid Voxelizer Engine https://github.com/gaspardzoss/cudavox * Drake: A planning, control, and analysis toolbox for nonlinear dynamical systems https://github.com/RobotLocomotion/drake +* VFPR - a Vulkan Forward Plus Renderer : https://github.com/WindyDarian/Vulkan-Forward-Plus-Renderer * Your project here! ### Old version(v0.9.x) -- cgit v1.2.3 From 1065d7cfb26e11f84eb20b283991f8549e0a412b Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Tue, 12 Sep 2017 02:18:27 +0900 Subject: Change to add a shape when shape.mesh.indices.size() > 0 once `g` tag appears. Fixes #138. --- tests/tester.cc | 20 ++++++++++++++++++++ tiny_obj_loader.h | 5 ++++- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/tests/tester.cc b/tests/tester.cc index a76359a..d4070c9 100644 --- a/tests/tester.cc +++ b/tests/tester.cc @@ -605,6 +605,26 @@ TEST_CASE("map_Bump", "[bump]") { REQUIRE(materials[0].bump_texname.compare("bump.jpg") == 0); } +TEST_CASE("g_ignored", "[Issue138]") { + tinyobj::attrib_t attrib; + std::vector shapes; + std::vector materials; + + std::string err; + bool ret = tinyobj::LoadObj(&attrib, &shapes, &materials, &err, "../models/issue-138.obj", gMtlBasePath); + + if (!err.empty()) { + std::cerr << err << std::endl; + } + + PrintInfo(attrib, shapes, materials); + + REQUIRE(true == ret); + REQUIRE(2 == shapes.size()); + REQUIRE(2 == materials.size()); + +} + #if 0 int main( diff --git a/tiny_obj_loader.h b/tiny_obj_loader.h index 34cd85e..4e2f6b5 100644 --- a/tiny_obj_loader.h +++ b/tiny_obj_loader.h @@ -23,6 +23,7 @@ THE SOFTWARE. */ // +// version 1.0.8 : Fix parsing `g` tag just after `usemtl`(#138) // version 1.0.7 : Support multiple tex options(#126) // version 1.0.6 : Add TINYOBJLOADER_USE_DOUBLE option(#124) // version 1.0.5 : Ignore `Tr` when `d` exists in MTL(#43) @@ -1621,7 +1622,9 @@ bool LoadObj(attrib_t *attrib, std::vector *shapes, // flush previous face group. bool ret = exportFaceGroupToShape(&shape, faceGroup, tags, material, name, triangulate); - if (ret) { + (void)ret; // return value not used. + + if (shape.mesh.indices.size() > 0) { shapes->push_back(shape); } -- cgit v1.2.3 From 3a9483ca6f06cfbf8215753981c8af2965d3afaa Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Tue, 12 Sep 2017 02:21:11 +0900 Subject: Add regression test for issue 138. --- models/issue-138.mtl | 23 +++++++++++++++++++++++ models/issue-138.obj | 51 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 74 insertions(+) create mode 100644 models/issue-138.mtl create mode 100644 models/issue-138.obj diff --git a/models/issue-138.mtl b/models/issue-138.mtl new file mode 100644 index 0000000..8894d7e --- /dev/null +++ b/models/issue-138.mtl @@ -0,0 +1,23 @@ +newmtl test1 + Ns 10.0000 + Ni 1.5000 + d 1.0000 + Tr 0.0000 + Tf 1.0000 1.0000 1.0000 + illum 2 + Ka 0.0000 0.0000 0.0000 + Kd 0.5 0.2 0.2 + Ks 0.0000 0.0000 0.0000 + Ke 0.0000 0.0000 0.0000 + + newmtl test2 + Ns 10.0000 + Ni 1.5000 + d 1.0000 + Tr 0.0000 + Tf 1.0000 1.0000 1.0000 + illum 2 + Ka 0.0000 0.0000 0.0000 + Kd 0.2 0.5 0.2 + Ks 0.0000 0.0000 0.0000 + Ke 0.0000 0.0000 0.0000 diff --git a/models/issue-138.obj b/models/issue-138.obj new file mode 100644 index 0000000..2465920 --- /dev/null +++ b/models/issue-138.obj @@ -0,0 +1,51 @@ + +# cube.obj +# + +mtllib issue-138.mtl + +v -0.500000 -0.500000 0.500000 +v 0.500000 -0.500000 0.500000 +v -0.500000 0.500000 0.500000 +v 0.500000 0.500000 0.500000 +v -0.500000 0.500000 -0.500000 +v 0.500000 0.500000 -0.500000 +v -0.500000 -0.500000 -0.500000 +v 0.500000 -0.500000 -0.500000 + +vt 0.000000 0.000000 +vt 1.000000 0.000000 +vt 0.000000 1.000000 +vt 1.000000 1.000000 + +vn 0.000000 0.000000 1.000000 +vn 0.000000 1.000000 0.000000 +vn 0.000000 0.000000 -1.000000 +vn 0.000000 -1.000000 0.000000 +vn 1.000000 0.000000 0.000000 +vn -1.000000 0.000000 0.000000 + +usemtl test1 +g test1 +s 1 +f 1/1/1 2/2/1 3/3/1 +f 3/3/1 2/2/1 4/4/1 + +usemtl test2 +g test2 + +s 2 +f 3/1/2 4/2/2 5/3/2 +f 5/3/2 4/2/2 6/4/2 +s 3 +f 5/4/3 6/3/3 7/2/3 +f 7/2/3 6/3/3 8/1/3 +s 4 +f 7/1/4 8/2/4 1/3/4 +f 1/3/4 8/2/4 2/4/4 +s 5 +f 2/1/5 8/2/5 4/3/5 +f 4/3/5 8/2/5 6/4/5 +s 6 +f 7/1/6 1/2/6 5/3/6 +f 5/3/6 1/2/6 3/4/6 -- cgit v1.2.3 From 7c7335c907907d04bb22c56d1af59e5c88e943f6 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Fri, 15 Sep 2017 16:34:31 +0900 Subject: Add test for parsing bump_multipler for normal map. --- models/norm-texopt.mtl | 7 +++++++ models/norm-texopt.obj | 7 +++++++ tests/tester.cc | 18 ++++++++++++++++++ 3 files changed, 32 insertions(+) create mode 100644 models/norm-texopt.mtl create mode 100644 models/norm-texopt.obj diff --git a/models/norm-texopt.mtl b/models/norm-texopt.mtl new file mode 100644 index 0000000..e2d4a2c --- /dev/null +++ b/models/norm-texopt.mtl @@ -0,0 +1,7 @@ +newmtl default +Ka 0 0 0 +Kd 0 0 0 +Ks 0 0 0 +Kt 0.1 0.2 0.3 +norm -bm 3 normalmap.jpg + diff --git a/models/norm-texopt.obj b/models/norm-texopt.obj new file mode 100644 index 0000000..babe94d --- /dev/null +++ b/models/norm-texopt.obj @@ -0,0 +1,7 @@ +mtllib norm-texopt.mtl +o Test +v 1.864151 -1.219172 -5.532511 +v 0.575869 -0.666304 5.896140 +v 0.940448 1.000000 -1.971128 +usemtl default +f 1 2 3 diff --git a/tests/tester.cc b/tests/tester.cc index d4070c9..5c31cc9 100644 --- a/tests/tester.cc +++ b/tests/tester.cc @@ -625,6 +625,24 @@ TEST_CASE("g_ignored", "[Issue138]") { } +TEST_CASE("norm_texopts", "[norm]") { + tinyobj::attrib_t attrib; + std::vector shapes; + std::vector materials; + + std::string err; + bool ret = tinyobj::LoadObj(&attrib, &shapes, &materials, &err, "../models/norm-texopt.obj", gMtlBasePath); + + if (!err.empty()) { + std::cerr << err << std::endl; + } + REQUIRE(true == ret); + REQUIRE(1 == shapes.size()); + REQUIRE(1 == materials.size()); + REQUIRE(3.0 == Approx(materials[0].normal_texopt.bump_multiplier)); + +} + #if 0 int main( -- cgit v1.2.3 From 75a4bd1d35f489808a3e4dc52d0f9121823fe5f4 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Mon, 25 Sep 2017 02:30:24 +0900 Subject: Add zero-value check when parsing `f' line. Fixes #140. --- models/issue-140-zero-face-idx.mtl | 2 + models/issue-140-zero-face-idx.obj | 17 ++++++ tests/tester.cc | 17 ++++++ tiny_obj_loader.h | 115 +++++++++++++++++++++++++------------ 4 files changed, 115 insertions(+), 36 deletions(-) create mode 100644 models/issue-140-zero-face-idx.mtl create mode 100644 models/issue-140-zero-face-idx.obj diff --git a/models/issue-140-zero-face-idx.mtl b/models/issue-140-zero-face-idx.mtl new file mode 100644 index 0000000..990a345 --- /dev/null +++ b/models/issue-140-zero-face-idx.mtl @@ -0,0 +1,2 @@ +newmtl main +Kd 1 1 1 diff --git a/models/issue-140-zero-face-idx.obj b/models/issue-140-zero-face-idx.obj new file mode 100644 index 0000000..21a6060 --- /dev/null +++ b/models/issue-140-zero-face-idx.obj @@ -0,0 +1,17 @@ +mtllib issue-140-zero-face-idx.mtl + +v -0.5 -0.5 0 +v 0.5 -0.5 0 +v 0.5 0.5 0 +v -0.5 0.5 0 + +vt 0 0 0 +vt 1 0 0 +vt 1 1 0 +vt 0 1 0 + +vn 0 0 -1 + +usemtl main +f 0/0/0 1/1/0 3/3/0 +f 1/1/0 3/3/0 2/2/0 diff --git a/tests/tester.cc b/tests/tester.cc index 5c31cc9..4a97f86 100644 --- a/tests/tester.cc +++ b/tests/tester.cc @@ -643,6 +643,23 @@ TEST_CASE("norm_texopts", "[norm]") { } +TEST_CASE("zero-face-idx-value", "[Issue140]") { + tinyobj::attrib_t attrib; + std::vector shapes; + std::vector materials; + + std::string err; + bool ret = tinyobj::LoadObj(&attrib, &shapes, &materials, &err, "../models/issue-140-zero-face-idx.obj", gMtlBasePath); + + + if (!err.empty()) { + std::cerr << err << std::endl; + } + REQUIRE(false == ret); + REQUIRE(!err.empty()); + +} + #if 0 int main( diff --git a/tiny_obj_loader.h b/tiny_obj_loader.h index 4e2f6b5..6f0515b 100644 --- a/tiny_obj_loader.h +++ b/tiny_obj_loader.h @@ -100,11 +100,11 @@ namespace tinyobj { // cube_left | cube_right #ifdef TINYOBJLOADER_USE_DOUBLE - //#pragma message "using double" - typedef double real_t; +//#pragma message "using double" +typedef double real_t; #else - //#pragma message "using float" - typedef float real_t; +//#pragma message "using float" +typedef float real_t; #endif typedef enum { @@ -119,7 +119,7 @@ typedef enum { } texture_type_t; typedef struct { - texture_type_t type; // -type (default TEXTURE_TYPE_NONE) + texture_type_t type; // -type (default TEXTURE_TYPE_NONE) real_t sharpness; // -boost (default 1.0?) real_t brightness; // base_value in -mm option (default 0) real_t contrast; // gain_value in -mm option (default 1) @@ -364,7 +364,6 @@ namespace tinyobj { MaterialReader::~MaterialReader() {} - struct vertex_index { int v_idx, vt_idx, vn_idx; vertex_index() : v_idx(-1), vt_idx(-1), vn_idx(-1) {} @@ -428,10 +427,27 @@ static std::istream &safeGetline(std::istream &is, std::string &t) { #define IS_NEW_LINE(x) (((x) == '\r') || ((x) == '\n') || ((x) == '\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 +static inline bool fixIndex(int idx, int n, int *ret) { + if (!ret) { + return false; + } + + if (idx > 0) { + (*ret) = idx - 1; + return true; + } + + if (idx == 0) { + // zero is not allowed according to the spec. + return false; + } + + if (idx < 0) { + (*ret) = n + idx; // negative value = relative + return true; + } + + return false; // never reach here. } static inline std::string parseString(const char **token) { @@ -584,9 +600,9 @@ static bool tryParseDouble(const char *s, const char *s_end, double *result) { } assemble: - *result = - (sign == '+' ? 1 : -1) * - (exponent ? std::ldexp(mantissa * std::pow(5.0, exponent), exponent) : mantissa); + *result = (sign == '+' ? 1 : -1) * + (exponent ? std::ldexp(mantissa * std::pow(5.0, exponent), exponent) + : mantissa); return true; fail: return false; @@ -603,16 +619,16 @@ static inline real_t parseReal(const char **token, double default_value = 0.0) { } static inline void parseReal2(real_t *x, real_t *y, const char **token, - const double default_x = 0.0, - const double default_y = 0.0) { + const double default_x = 0.0, + const double default_y = 0.0) { (*x) = parseReal(token, default_x); (*y) = parseReal(token, default_y); } -static inline void parseReal3(real_t *x, real_t *y, real_t *z, const char **token, - const double default_x = 0.0, - const double default_y = 0.0, - const double default_z = 0.0) { +static inline void parseReal3(real_t *x, real_t *y, real_t *z, + const char **token, const double default_x = 0.0, + const double default_y = 0.0, + const double default_z = 0.0) { (*x) = parseReal(token, default_x); (*y) = parseReal(token, default_y); (*z) = parseReal(token, default_z); @@ -694,37 +710,57 @@ static tag_sizes parseTagTriple(const char **token) { } // Parse triples with index offsets: i, i/j/k, i//k, i/j -static vertex_index parseTriple(const char **token, int vsize, int vnsize, - int vtsize) { +static bool parseTriple(const char **token, int vsize, int vnsize, int vtsize, + vertex_index *ret) { + if (!ret) { + return false; + } + vertex_index vi(-1); - vi.v_idx = fixIndex(atoi((*token)), vsize); + if (!fixIndex(atoi((*token)), vsize, &(vi.v_idx))) { + return false; + } + (*token) += strcspn((*token), "/ \t\r"); if ((*token)[0] != '/') { - return vi; + (*ret) = vi; + return true; } (*token)++; // i//k if ((*token)[0] == '/') { (*token)++; - vi.vn_idx = fixIndex(atoi((*token)), vnsize); + if (!fixIndex(atoi((*token)), vnsize, &(vi.vn_idx))) { + return false; + } (*token) += strcspn((*token), "/ \t\r"); - return vi; + (*ret) = vi; + return true; } // i/j/k or i/j - vi.vt_idx = fixIndex(atoi((*token)), vtsize); + if (!fixIndex(atoi((*token)), vtsize, &(vi.vt_idx))) { + return false; + } + (*token) += strcspn((*token), "/ \t\r"); if ((*token)[0] != '/') { - return vi; + (*ret) = vi; + return true; } // i/j/k (*token)++; // skip '/' - vi.vn_idx = fixIndex(atoi((*token)), vnsize); + if (!fixIndex(atoi((*token)), vnsize, &(vi.vn_idx))) { + return false; + } (*token) += strcspn((*token), "/ \t\r"); - return vi; + + (*ret) = vi; + + return true; } // Parse raw triples: i, i/j/k, i//k, i/j @@ -813,15 +849,15 @@ static bool ParseTextureNameAndOption(std::string *texname, } else if ((0 == strncmp(token, "-o", 2)) && IS_SPACE((token[2]))) { token += 3; parseReal3(&(texopt->origin_offset[0]), &(texopt->origin_offset[1]), - &(texopt->origin_offset[2]), &token); + &(texopt->origin_offset[2]), &token); } else if ((0 == strncmp(token, "-s", 2)) && IS_SPACE((token[2]))) { token += 3; parseReal3(&(texopt->scale[0]), &(texopt->scale[1]), &(texopt->scale[2]), - &token, 1.0, 1.0, 1.0); + &token, 1.0, 1.0, 1.0); } else if ((0 == strncmp(token, "-t", 2)) && IS_SPACE((token[2]))) { token += 3; parseReal3(&(texopt->turbulence[0]), &(texopt->turbulence[1]), - &(texopt->turbulence[2]), &token); + &(texopt->turbulence[2]), &token); } else if ((0 == strncmp(token, "-type", 5)) && IS_SPACE((token[5]))) { token += 5; texopt->type = parseTextureType((&token), TEXTURE_TYPE_NONE); @@ -1532,9 +1568,16 @@ bool LoadObj(attrib_t *attrib, std::vector *shapes, face.reserve(3); while (!IS_NEW_LINE(token[0])) { - vertex_index vi = parseTriple(&token, static_cast(v.size() / 3), - static_cast(vn.size() / 3), - static_cast(vt.size() / 2)); + vertex_index vi; + if (!parseTriple(&token, static_cast(v.size() / 3), + static_cast(vn.size() / 3), + static_cast(vt.size() / 2), &vi)) { + if (err) { + (*err) = "Failed parse `f' line(e.g. zero value for face index).\n"; + } + return false; + } + face.push_back(vi); size_t n = strspn(token, " \t\r"); token += n; @@ -1622,7 +1665,7 @@ bool LoadObj(attrib_t *attrib, std::vector *shapes, // flush previous face group. bool ret = exportFaceGroupToShape(&shape, faceGroup, tags, material, name, triangulate); - (void)ret; // return value not used. + (void)ret; // return value not used. if (shape.mesh.indices.size() > 0) { shapes->push_back(shape); -- cgit v1.2.3 From 27bdd547f004f71fce96be60553891a014660e67 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Mon, 25 Sep 2017 02:36:15 +0900 Subject: If you use tinyobjloader, please let us know via github issue!. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2737f08..9b87713 100644 --- a/README.md +++ b/README.md @@ -58,7 +58,7 @@ TinyObjLoader is successfully used in ... * cudabox: CUDA Solid Voxelizer Engine https://github.com/gaspardzoss/cudavox * Drake: A planning, control, and analysis toolbox for nonlinear dynamical systems https://github.com/RobotLocomotion/drake * VFPR - a Vulkan Forward Plus Renderer : https://github.com/WindyDarian/Vulkan-Forward-Plus-Renderer -* Your project here! +* Your project here! (Letting us know via github issue is welcome!) ### Old version(v0.9.x) -- cgit v1.2.3 From eb1f395101468a12a968eab1ac2a32b61f852c6c Mon Sep 17 00:00:00 2001 From: Ododo Date: Sun, 8 Oct 2017 04:00:14 +0200 Subject: Fix mapping for #131 and compilation error #139 #132 (related) --- python/main.cpp | 43 +++++++++++++++++++++---------------------- 1 file changed, 21 insertions(+), 22 deletions(-) diff --git a/python/main.cpp b/python/main.cpp index 9e266c1..4f1d0e0 100644 --- a/python/main.cpp +++ b/python/main.cpp @@ -48,7 +48,7 @@ static PyObject* pyLoadObj(PyObject* self, PyObject* args) { pyshapes = PyDict_New(); pymaterials = PyDict_New(); - pymaterial_indices = PyDict_New(); + pymaterial_indices = PyList_New(0); rtndict = PyDict_New(); attribobj = PyDict_New(); @@ -124,53 +124,52 @@ static PyObject* pyLoadObj(PyObject* self, PyObject* args) { PyDict_SetItemString(pyshapes, (*shape).name.c_str(), meshobj); } - long material_index = 0; for (std::vector::iterator mat = materials.begin(); mat != materials.end(); mat++) { PyObject* matobj = PyDict_New(); PyObject* unknown_parameter = PyDict_New(); for (std::map::iterator p = - (*mat).unknown_parameter.begin(); - p != (*mat).unknown_parameter.end(); ++p) { + mat->unknown_parameter.begin(); + p != mat->unknown_parameter.end(); ++p) { PyDict_SetItemString(unknown_parameter, p->first.c_str(), PyUnicode_FromString(p->second.c_str())); } PyDict_SetItemString(matobj, "shininess", - PyFloat_FromDouble((*mat).shininess)); - PyDict_SetItemString(matobj, "ior", PyFloat_FromDouble((*mat).ior)); + PyFloat_FromDouble(mat->shininess)); + PyDict_SetItemString(matobj, "ior", PyFloat_FromDouble(mat->ior)); PyDict_SetItemString(matobj, "dissolve", - PyFloat_FromDouble((*mat).dissolve)); - PyDict_SetItemString(matobj, "illum", PyLong_FromLong((*mat).illum)); + PyFloat_FromDouble(mat->dissolve)); + PyDict_SetItemString(matobj, "illum", PyLong_FromLong(mat->illum)); PyDict_SetItemString(matobj, "ambient_texname", - PyUnicode_FromString((*mat).ambient_texname.c_str())); + PyUnicode_FromString(mat->ambient_texname.c_str())); PyDict_SetItemString(matobj, "diffuse_texname", - PyUnicode_FromString((*mat).diffuse_texname.c_str())); + PyUnicode_FromString(mat->diffuse_texname.c_str())); PyDict_SetItemString(matobj, "specular_texname", - PyUnicode_FromString((*mat).specular_texname.c_str())); + PyUnicode_FromString(mat->specular_texname.c_str())); PyDict_SetItemString( matobj, "specular_highlight_texname", - PyUnicode_FromString((*mat).specular_highlight_texname.c_str())); + PyUnicode_FromString(mat->specular_highlight_texname.c_str())); PyDict_SetItemString(matobj, "bump_texname", - PyUnicode_FromString((*mat).bump_texname.c_str())); + PyUnicode_FromString(mat->bump_texname.c_str())); PyDict_SetItemString( matobj, "displacement_texname", - PyUnicode_FromString((*mat).displacement_texname.c_str())); + PyUnicode_FromString(mat->displacement_texname.c_str())); PyDict_SetItemString(matobj, "alpha_texname", - PyUnicode_FromString((*mat).alpha_texname.c_str())); - PyDict_SetItemString(matobj, "ambient", pyTupleFromfloat3((*mat).ambient)); - PyDict_SetItemString(matobj, "diffuse", pyTupleFromfloat3((*mat).diffuse)); + PyUnicode_FromString(mat->alpha_texname.c_str())); + PyDict_SetItemString(matobj, "ambient", pyTupleFromfloat3(mat->ambient)); + PyDict_SetItemString(matobj, "diffuse", pyTupleFromfloat3(mat->diffuse)); PyDict_SetItemString(matobj, "specular", - pyTupleFromfloat3((*mat).specular)); + pyTupleFromfloat3(mat->specular)); PyDict_SetItemString(matobj, "transmittance", - pyTupleFromfloat3((*mat).transmittance)); + pyTupleFromfloat3(mat->transmittance)); PyDict_SetItemString(matobj, "emission", - pyTupleFromfloat3((*mat).emission)); + pyTupleFromfloat3(mat->emission)); PyDict_SetItemString(matobj, "unknown_parameter", unknown_parameter); - PyDict_SetItemString(pymaterials, (*mat).name.c_str(), matobj); - PyDict_SetItemString(pymaterial_indices, PyLong_FromLong(material_index++), (*mat).name.c_str()); + PyDict_SetItemString(pymaterials, mat->name.c_str(), matobj); + PyList_Append(pymaterial_indices, PyUnicode_FromString(mat->name.c_str())); } PyDict_SetItemString(rtndict, "shapes", pyshapes); -- cgit v1.2.3 From 88ad575f6247a215bda2a8938bc0f6e41d113ea4 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Thu, 12 Oct 2017 19:04:38 +0900 Subject: Initial support of parsing vertex color(extension format) --- README.md | 12 ++++++++++++ models/cube-vertexcol.obj | 31 +++++++++++++++++++++++++++++++ tests/tester.cc | 33 +++++++++++++++++++++++++++++++++ tiny_obj_loader.h | 43 ++++++++++++++++++++++++++++++++++++++++++- 4 files changed, 118 insertions(+), 1 deletion(-) create mode 100644 models/cube-vertexcol.obj diff --git a/README.md b/README.md index 2737f08..8608c3b 100644 --- a/README.md +++ b/README.md @@ -89,6 +89,7 @@ TinyObjLoader is successfully used in ... * Group(parse multiple group name) * Vertex + * Vertex color(as an extension: https://blender.stackexchange.com/questions/31997/how-can-i-get-vertex-painted-obj-files-to-import-into-blender) * Texcoord * Normal * Material @@ -139,6 +140,13 @@ attrib_t::texcoords => 2 floats per vertex | u | v | u | v | u | v | u | v | .... | u | v | +-----------+-----------+-----------+-----------+ +-----------+ +attrib_t::colors => 3 floats per vertex(vertex color. optional) + + c[0] c[1] c[2] c[3] c[n-1] + +-----------+-----------+-----------+-----------+ +-----------+ + | x | y | z | x | y | z | x | y | z | x | y | z | .... | x | y | z | + +-----------+-----------+-----------+-----------+ +-----------+ + ``` Each `shape_t::mesh_t` does not contain vertex data but contains array index to `attrib_t`. @@ -228,6 +236,10 @@ for (size_t s = 0; s < shapes.size(); s++) { tinyobj::real_t nz = attrib.normals[3*idx.normal_index+2]; tinyobj::real_t tx = attrib.texcoords[2*idx.texcoord_index+0]; tinyobj::real_t ty = attrib.texcoords[2*idx.texcoord_index+1]; + // Optional: vertex colors + // tinyobj::real_t red = attrib.colors[3*idx.vertex_index+0]; + // tinyobj::real_t green = attrib.colors[3*idx.vertex_index+1]; + // tinyobj::real_t blue = attrib.colors[3*idx.vertex_index+2]; } index_offset += fv; diff --git a/models/cube-vertexcol.obj b/models/cube-vertexcol.obj new file mode 100644 index 0000000..494ce21 --- /dev/null +++ b/models/cube-vertexcol.obj @@ -0,0 +1,31 @@ +mtllib cube.mtl + +v 0.000000 2.000000 2.000000 0 0 0 +v 0.000000 0.000000 2.000000 0 0 1 +v 2.000000 0.000000 2.000000 0 1 0 +v 2.000000 2.000000 2.000000 0 1 1 +v 0.000000 2.000000 0.000000 1 0 0 +v 0.000000 0.000000 0.000000 1 0 1 +v 2.000000 0.000000 0.000000 1 1 0 +v 2.000000 2.000000 0.000000 1 1 1 +# 8 vertices + +g front cube +usemtl white +f 1 2 3 4 +g back cube +# expects white material +f 8 7 6 5 +g right cube +usemtl red +f 4 3 7 8 +g top cube +usemtl white +f 5 1 4 8 +g left cube +usemtl green +f 5 6 2 1 +g bottom cube +usemtl white +f 2 6 7 3 +# 6 elements diff --git a/tests/tester.cc b/tests/tester.cc index d4070c9..83b1df5 100644 --- a/tests/tester.cc +++ b/tests/tester.cc @@ -625,6 +625,39 @@ TEST_CASE("g_ignored", "[Issue138]") { } +TEST_CASE("vertex-col-ext", "[Issue144]") { + tinyobj::attrib_t attrib; + std::vector shapes; + std::vector materials; + + std::string err; + bool ret = tinyobj::LoadObj(&attrib, &shapes, &materials, &err, "../models/cube-vertexcol.obj", gMtlBasePath); + + if (!err.empty()) { + std::cerr << err << std::endl; + } + + PrintInfo(attrib, shapes, materials); + + REQUIRE(true == ret); + REQUIRE((8 * 3) == attrib.colors.size()); + + REQUIRE(0 == Approx(attrib.colors[3 * 0 + 0])); + REQUIRE(0 == Approx(attrib.colors[3 * 0 + 1])); + REQUIRE(0 == Approx(attrib.colors[3 * 0 + 2])); + + REQUIRE(0 == Approx(attrib.colors[3 * 1 + 0])); + REQUIRE(0 == Approx(attrib.colors[3 * 1 + 1])); + REQUIRE(1 == Approx(attrib.colors[3 * 1 + 2])); + + REQUIRE(1 == Approx(attrib.colors[3 * 4 + 0])); + + REQUIRE(1 == Approx(attrib.colors[3 * 7 + 0])); + REQUIRE(1 == Approx(attrib.colors[3 * 7 + 1])); + REQUIRE(1 == Approx(attrib.colors[3 * 7 + 2])); + +} + #if 0 int main( diff --git a/tiny_obj_loader.h b/tiny_obj_loader.h index 4e2f6b5..3d02e8d 100644 --- a/tiny_obj_loader.h +++ b/tiny_obj_loader.h @@ -23,6 +23,7 @@ THE SOFTWARE. */ // +// version 1.1.0 : Support parsing vertex color(#144) // version 1.0.8 : Fix parsing `g` tag just after `usemtl`(#138) // version 1.0.7 : Support multiple tex options(#126) // version 1.0.6 : Add TINYOBJLOADER_USE_DOUBLE option(#124) @@ -230,6 +231,7 @@ typedef struct { std::vector vertices; // 'v' std::vector normals; // 'vn' std::vector texcoords; // 'vt' + std::vector colors; // extension: vertex colors } attrib_t; typedef struct callback_t_ { @@ -602,6 +604,19 @@ static inline real_t parseReal(const char **token, double default_value = 0.0) { return f; } +static inline bool parseReal(const char **token, real_t *out) { + (*token) += strspn((*token), " \t"); + const char *end = (*token) + strcspn((*token), " \t\r"); + double val; + bool ret = tryParseDouble((*token), end, &val); + if (ret) { + real_t f = static_cast(val); + (*out) = f; + } + (*token) = end; + return ret; +} + static inline void parseReal2(real_t *x, real_t *y, const char **token, const double default_x = 0.0, const double default_y = 0.0) { @@ -629,6 +644,23 @@ static inline void parseV(real_t *x, real_t *y, real_t *z, real_t *w, (*w) = parseReal(token, default_w); } +// Extension: parse vertex with colors(6 items) +static inline bool parseVertexWithColor(real_t *x, real_t *y, real_t *z, real_t *r, + real_t *g, real_t *b, + const char **token, const double default_x = 0.0, + const double default_y = 0.0, + const double default_z = 0.0) { + (*x) = parseReal(token, default_x); + (*y) = parseReal(token, default_y); + (*z) = parseReal(token, default_z); + + (*r) = parseReal(token, 1.0); + (*g) = parseReal(token, 1.0); + (*b) = parseReal(token, 1.0); + + return true; +} + static inline bool parseOnOff(const char **token, bool default_value = true) { (*token) += strspn((*token), " \t"); const char *end = (*token) + strcspn((*token), " \t\r"); @@ -1421,6 +1453,7 @@ bool LoadObj(attrib_t *attrib, std::vector *shapes, attrib->vertices.clear(); attrib->normals.clear(); attrib->texcoords.clear(); + attrib->colors.clear(); shapes->clear(); std::stringstream errss; @@ -1453,6 +1486,7 @@ bool LoadObj(attrib_t *attrib, std::vector *shapes, std::vector v; std::vector vn; std::vector vt; + std::vector vc; std::vector tags; std::vector > faceGroup; std::string name; @@ -1495,10 +1529,15 @@ bool LoadObj(attrib_t *attrib, std::vector *shapes, if (token[0] == 'v' && IS_SPACE((token[1]))) { token += 2; real_t x, y, z; - parseReal3(&x, &y, &z, &token); + real_t r, g, b; + parseVertexWithColor(&x, &y, &z, &r, &g, &b, &token); v.push_back(x); v.push_back(y); v.push_back(z); + + vc.push_back(r); + vc.push_back(g); + vc.push_back(b); continue; } @@ -1733,6 +1772,7 @@ bool LoadObj(attrib_t *attrib, std::vector *shapes, attrib->vertices.swap(v); attrib->normals.swap(vn); attrib->texcoords.swap(vt); + attrib->colors.swap(vc); return true; } @@ -1785,6 +1825,7 @@ bool LoadObjWithCallback(std::istream &inStream, const callback_t &callback, // vertex if (token[0] == 'v' && IS_SPACE((token[1]))) { token += 2; + // TODO(syoyo): Support parsing vertex color extension. real_t x, y, z, w; // w is optional. default = 1.0 parseV(&x, &y, &z, &w, &token); if (callback.vertex_cb) { -- cgit v1.2.3 From 94fc413466581c53c13015cf3b959d744abf5932 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Fri, 13 Oct 2017 18:13:36 +0900 Subject: Fix parsing of crease tags(`t`) Support parsing texture filename containing whitespace. --- models/texture-filename-with-whitespace.mtl | 28 ++++++++++++++++++++++++++ models/texture-filename-with-whitespace.obj | 31 +++++++++++++++++++++++++++++ tests/tester.cc | 23 +++++++++++++++++++++ tiny_obj_loader.h | 31 +++++++++++++++-------------- 4 files changed, 98 insertions(+), 15 deletions(-) create mode 100644 models/texture-filename-with-whitespace.mtl create mode 100644 models/texture-filename-with-whitespace.obj diff --git a/models/texture-filename-with-whitespace.mtl b/models/texture-filename-with-whitespace.mtl new file mode 100644 index 0000000..70b1a4e --- /dev/null +++ b/models/texture-filename-with-whitespace.mtl @@ -0,0 +1,28 @@ +newmtl white +Ka 0 0 0 +Kd 1 1 1 +Ks 0 0 0 +# filename with white space. +map_Kd texture 01.png + +newmtl red +Ka 0 0 0 +Kd 1 0 0 +Ks 0 0 0 +# texture option + filename with white space. +bump -bm 2 bump 01.png + +newmtl green +Ka 0 0 0 +Kd 0 1 0 +Ks 0 0 0 + +newmtl blue +Ka 0 0 0 +Kd 0 0 1 +Ks 0 0 0 + +newmtl light +Ka 20 20 20 +Kd 1 1 1 +Ks 0 0 0 diff --git a/models/texture-filename-with-whitespace.obj b/models/texture-filename-with-whitespace.obj new file mode 100644 index 0000000..46e61e7 --- /dev/null +++ b/models/texture-filename-with-whitespace.obj @@ -0,0 +1,31 @@ +mtllib texture-filename-with-whitespace.mtl + +v 0.000000 2.000000 2.000000 +v 0.000000 0.000000 2.000000 +v 2.000000 0.000000 2.000000 +v 2.000000 2.000000 2.000000 +v 0.000000 2.000000 0.000000 +v 0.000000 0.000000 0.000000 +v 2.000000 0.000000 0.000000 +v 2.000000 2.000000 0.000000 +# 8 vertices + +g front cube +usemtl white +f 1 2 3 4 +g back cube +# expects white material +f 8 7 6 5 +g right cube +usemtl red +f 4 3 7 8 +g top cube +usemtl white +f 5 1 4 8 +g left cube +usemtl green +f 5 6 2 1 +g bottom cube +usemtl white +f 2 6 7 3 +# 6 elements diff --git a/tests/tester.cc b/tests/tester.cc index 811dfbf..cd972f8 100644 --- a/tests/tester.cc +++ b/tests/tester.cc @@ -694,6 +694,29 @@ TEST_CASE("zero-face-idx-value", "[Issue140]") { } +TEST_CASE("texture-name-whitespace", "[Issue145]") { + tinyobj::attrib_t attrib; + std::vector shapes; + std::vector materials; + + std::string err; + bool ret = tinyobj::LoadObj(&attrib, &shapes, &materials, &err, "../models/texture-filename-with-whitespace.obj", gMtlBasePath); + + + if (!err.empty()) { + std::cerr << "[Issue145] " << err << std::endl; + } + + REQUIRE(true == ret); + REQUIRE(err.empty()); + REQUIRE(2 < materials.size()); + + REQUIRE(0 == materials[0].diffuse_texname.compare("texture 01.png")); + REQUIRE(0 == materials[1].bump_texname.compare("bump 01.png")); + REQUIRE(2 == Approx(materials[1].bump_texopt.bump_multiplier)); + +} + #if 0 int main( diff --git a/tiny_obj_loader.h b/tiny_obj_loader.h index 591d236..65a9d62 100644 --- a/tiny_obj_loader.h +++ b/tiny_obj_loader.h @@ -721,22 +721,24 @@ static inline texture_type_t parseTextureType( static tag_sizes parseTagTriple(const char **token) { tag_sizes ts; + (*token) += strspn((*token), " \t"); ts.num_ints = atoi((*token)); (*token) += strcspn((*token), "/ \t\r"); if ((*token)[0] != '/') { return ts; } - (*token)++; + (*token)++; // Skip '/' + + (*token) += strspn((*token), " \t"); ts.num_reals = atoi((*token)); (*token) += strcspn((*token), "/ \t\r"); if ((*token)[0] != '/') { return ts; } - (*token)++; + (*token)++; // Skip '/' - ts.num_strings = atoi((*token)); - (*token) += strcspn((*token), "/ \t\r") + 1; + ts.num_strings = parseInt(token); return ts; } @@ -906,11 +908,18 @@ static bool ParseTextureNameAndOption(std::string *texname, parseReal2(&(texopt->brightness), &(texopt->contrast), &token, 0.0, 1.0); } else { // Assume texture filename +#if 0 size_t len = strcspn(token, " \t\r"); // untile next space texture_name = std::string(token, token + len); token += len; token += strspn(token, " \t"); // skip space +#else + // Read filename until line end to parse filename containing whitespace + // TODO(syoyo): Support parsing texture option flag after the filename. + texture_name = std::string(token); + token += texture_name.length(); +#endif found_texname = true; } @@ -1762,33 +1771,25 @@ bool LoadObj(attrib_t *attrib, std::vector *shapes, tag_t tag; token += 2; - std::stringstream ss; - ss << token; - tag.name = ss.str(); - token += tag.name.size() + 1; + tag.name = parseString(&token); tag_sizes ts = parseTagTriple(&token); tag.intValues.resize(static_cast(ts.num_ints)); for (size_t i = 0; i < static_cast(ts.num_ints); ++i) { - tag.intValues[i] = atoi(token); - token += strcspn(token, "/ \t\r") + 1; + tag.intValues[i] = parseInt(&token); } tag.floatValues.resize(static_cast(ts.num_reals)); for (size_t i = 0; i < static_cast(ts.num_reals); ++i) { tag.floatValues[i] = parseReal(&token); - token += strcspn(token, "/ \t\r") + 1; } tag.stringValues.resize(static_cast(ts.num_strings)); for (size_t i = 0; i < static_cast(ts.num_strings); ++i) { - std::stringstream sstr; - sstr << token; - tag.stringValues[i] = sstr.str(); - token += tag.stringValues[i].size() + 1; + tag.stringValues[i] = parseString(&token); } tags.push_back(tag); -- cgit v1.2.3 From b434c2497fcb52aa1497b84aa8aeb12bb590492d Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Mon, 16 Oct 2017 17:48:45 +0900 Subject: Update master URL. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e1e4bfc..47477f8 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ [![Coverage Status](https://coveralls.io/repos/github/syoyo/tinyobjloader/badge.svg?branch=master)](https://coveralls.io/github/syoyo/tinyobjloader?branch=master) -http://syoyo.github.io/tinyobjloader/ +[https://github.com/syoyo/tinyobjloader](https://github.com/syoyo/tinyobjloader) Tiny but powerful single file wavefront obj loader written in C++. No dependency except for C++ STL. It can parse over 10M polygons with moderate memory and time. -- cgit v1.2.3