blob: f86b48257fbebf00e38391d649949613fb5c17a9 [file] [log] [blame] [edit]
//
// 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