// // Optimized wavefront .obj loader. // Requires lfpAlloc and C++11 // /* The MIT License (MIT) 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 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 _WIN32 #define atoll(S) _atoi64(S) #include #else #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 "lfpAlloc/Allocator.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 // 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; typedef struct { std::string name; // group name or object name. unsigned int face_offset; unsigned int length; } shape_t; 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 > indices; 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 index_t parseRawTriple(const char **token) { index_t vi( static_cast(0x80000000)); // 0x80000000 = -2147483648 = invalid vi.vertex_index = my_atoi((*token)); 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.normal_index = my_atoi((*token)); while ((*token)[0] != '\0' && (*token)[0] != '/' && (*token)[0] != ' ' && (*token)[0] != '\t' && (*token)[0] != '\r') { (*token)++; } return vi; } // i/j/k or i/j vi.texcoord_index = my_atoi((*token)); 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.normal_index = my_atoi((*token)); 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); 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); int i = my_atoi((*token)); (*token) += until_space((*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 (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 = 1.0; for (int i = 0; i < exponent; i++) { a = a * 5.0; } *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); #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)); 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 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') 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[0] == 'T' && token[1] == 'f' && 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; } // 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; 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; } // 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) { _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_indices; CommandCount() { num_v = 0; num_vn = 0; num_vt = 0; num_f = 0; num_indices = 0; } }; 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, 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 // 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 = linebuf; command->type = COMMAND_EMPTY; // Skip leading space. skip_space(&token); 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 = 0.0f, y = 0.0f, z = 0.0f; 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 = 0.0f, y = 0.0f, z = 0.0f; 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 = 0.0f, y = 0.0f; 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; skip_space(&token); StackVector f; while (!IS_NEW_LINE(token[0])) { index_t vi = parseRawTriple(&token); skip_space_and_cr(&token); f->push_back(vi); } command->type = COMMAND_F; if (triangulate) { 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; 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, std::vector *materials, const char *buf, size_t len, const LoadOption &option) { attrib->vertices.clear(); attrib->normals.clear(); attrib->texcoords.clear(); attrib->indices.clear(); attrib->face_num_verts.clear(); attrib->material_ids.clear(); shapes->clear(); if (len < 1) return false; 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; } 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); 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, option.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_indices += command.f_num_verts.size(); } 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; // 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_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_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; // 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->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]; 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_indices; } 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++) { 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); } 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 += commands[t][i].f_num_verts.size(); } } })); } 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; 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->indices.size() << std::endl; std::cout << "# of indices = " << attrib->material_ids.size() << std::endl; std::cout << "# of shapes = " << shapes->size() << std::endl; } return true; } } // namespace tinyobj_opt #endif // TINYOBJ_LOADER_OPT_IMPLEMENTATION