aboutsummaryrefslogtreecommitdiff
path: root/experimental/tinyobj_loader_opt.h
diff options
context:
space:
mode:
Diffstat (limited to 'experimental/tinyobj_loader_opt.h')
-rw-r--r--experimental/tinyobj_loader_opt.h1684
1 files changed, 1684 insertions, 0 deletions
diff --git a/experimental/tinyobj_loader_opt.h b/experimental/tinyobj_loader_opt.h
new file mode 100644
index 0000000..f86b482
--- /dev/null
+++ b/experimental/tinyobj_loader_opt.h
@@ -0,0 +1,1684 @@
+//
+// 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 <windows.h>
+#else
+#include <fcntl.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+#endif
+
+#include <cassert>
+#include <cmath>
+#include <cstdio>
+#include <cstdlib>
+#include <cstring>
+#include <fstream>
+#include <iostream>
+#include <map>
+#include <vector>
+
+#include <atomic> // C++11
+#include <chrono> // C++11
+#include <thread> // 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 <typename T, size_t stack_capacity>
+class StackAllocator : public std::allocator<T> {
+ public:
+ typedef typename std::allocator<T>::pointer pointer;
+ typedef typename std::allocator<T>::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<T *>(stack_buffer_); }
+ const T *stack_buffer() const {
+ return reinterpret_cast<const T *>(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 <typename U>
+ struct rebind {
+ typedef StackAllocator<U, stack_capacity> other;
+ };
+
+ // For the straight up copy c-tor, we can share storage.
+ StackAllocator(const StackAllocator<T, stack_capacity> &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 <xutility>)
+ // 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 <typename U, size_t other_capacity>
+ StackAllocator(const StackAllocator<U, other_capacity> &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<T>::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<T>::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 <typename TContainerType, int stack_capacity>
+class StackContainer {
+ public:
+ typedef TContainerType ContainerType;
+ typedef typename ContainerType::value_type ContainedType;
+ typedef StackAllocator<ContainedType, stack_capacity> 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<int, 16> foo;
+// foo->push_back(22); // we have overloaded operator->
+// foo[0] = 10; // as well as operator[]
+template <typename T, size_t stack_capacity>
+class StackVector
+ : public StackContainer<std::vector<T, StackAllocator<T, stack_capacity> >,
+ stack_capacity> {
+ public:
+ StackVector()
+ : StackContainer<std::vector<T, StackAllocator<T, stack_capacity> >,
+ 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<T, stack_capacity> &other)
+ : StackContainer<std::vector<T, StackAllocator<T, stack_capacity> >,
+ stack_capacity>() {
+ this->container().assign(other->begin(), other->end());
+ }
+
+ StackVector<T, stack_capacity> &operator=(
+ const StackVector<T, stack_capacity> &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<std::string, std::string> 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<float, lfpAlloc::lfpAllocator<float> > vertices;
+ std::vector<float, lfpAlloc::lfpAllocator<float> > normals;
+ std::vector<float, lfpAlloc::lfpAllocator<float> > texcoords;
+ std::vector<index_t, lfpAlloc::lfpAllocator<index_t> > indices;
+ std::vector<int, lfpAlloc::lfpAllocator<int> > face_num_verts;
+ std::vector<int, lfpAlloc::lfpAllocator<int> > material_ids;
+} attrib_t;
+
+typedef StackVector<char, 256> ShortString;
+
+#define IS_SPACE(x) (((x) == ' ') || ((x) == '\t'))
+#define IS_DIGIT(x) \
+ (static_cast<unsigned int>((x) - '0') < static_cast<unsigned int>(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<int>(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<int>(*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<int>(*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<int>(*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<double>(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<float>(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<float>(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<std::string, int> *material_map,
+ std::vector<material_t> *materials,
+ std::istream *inStream) {
+ // Create a default material anyway.
+ material_t material;
+ InitMaterial(&material);
+
+ size_t maxchars = 8192; // Alloc enough size.
+ std::vector<char> buf(maxchars); // Alloc enough size.
+ while (inStream->peek() != -1) {
+ inStream->getline(&buf[0], static_cast<std::streamsize>(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<std::string, int>(
+ material.name, static_cast<int>(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<size_t>(len));
+ std::string value = _space + 1;
+ material.unknown_parameter.insert(
+ std::pair<std::string, std::string>(key, value));
+ }
+ }
+ // flush last material.
+ material_map->insert(std::pair<std::string, int>(
+ material.name, static_cast<int>(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<index_t, lfpAlloc::lfpAllocator<index_t> > f;
+ // std::vector<index_t> f;
+ std::vector<int, lfpAlloc::lfpAllocator<int> > 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<shape_t> *shapes,
+ std::vector<material_t> *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<index_t, 8> 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<shape_t> *shapes,
+ std::vector<material_t> *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<int>(num_threads), kMaxThreads));
+
+ if (option.verbose) {
+ std::cout << "# of threads = " << num_threads << std::endl;
+ }
+
+ auto t1 = std::chrono::high_resolution_clock::now();
+
+ std::vector<LineInfo, lfpAlloc::lfpAllocator<LineInfo> >
+ line_infos[kMaxThreads];
+ for (size_t t = 0; t < static_cast<size_t>(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<double, std::milli> ms_linedetection;
+ std::chrono::duration<double, std::milli> ms_alloc;
+ std::chrono::duration<double, std::milli> ms_parse;
+ std::chrono::duration<double, std::milli> ms_load_mtl;
+ std::chrono::duration<double, std::milli> ms_merge;
+ std::chrono::duration<double, std::milli> ms_construct;
+
+ // 1. Find '\n' and create line data.
+ {
+ StackVector<std::thread, 16> workers;
+
+ auto start_time = std::chrono::high_resolution_clock::now();
+ auto chunk_size = len / num_threads;
+
+ for (size_t t = 0; t < static_cast<size_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 == static_cast<size_t>((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<Command> 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<std::thread, 16> 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<std::string, int> 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<std::thread, 16> 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<double, std::milli> 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