diff options
Diffstat (limited to 'vendor/tiny_obj_loader_c.h')
| -rw-r--r-- | vendor/tiny_obj_loader_c.h | 1793 | 
1 files changed, 1793 insertions, 0 deletions
diff --git a/vendor/tiny_obj_loader_c.h b/vendor/tiny_obj_loader_c.h new file mode 100644 index 0000000..09087fe --- /dev/null +++ b/vendor/tiny_obj_loader_c.h @@ -0,0 +1,1793 @@ +/* +   The MIT License (MIT) + +   Copyright (c) 2016 - 2019 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_C_H_ +#define TINOBJ_LOADER_C_H_ + +/* @todo { Remove stddef dependency. size_t? } */ +#include <stddef.h> + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct { +  char *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 pad0; + +  char *ambient_texname;            /* map_Ka */ +  char *diffuse_texname;            /* map_Kd */ +  char *specular_texname;           /* map_Ks */ +  char *specular_highlight_texname; /* map_Ns */ +  char *bump_texname;               /* map_bump, bump */ +  char *displacement_texname;       /* disp */ +  char *alpha_texname;              /* map_d */ +} tinyobj_material_t; + +typedef struct { +  char *name; /* group name or object name. */ +  unsigned int face_offset; +  unsigned int length; +} tinyobj_shape_t; + +typedef struct { +  int v_idx, vt_idx, vn_idx; +} tinyobj_vertex_index_t; + +typedef struct { +  unsigned int num_vertices; +  unsigned int num_normals; +  unsigned int num_texcoords; +  unsigned int num_faces; +  unsigned int num_face_num_verts; + +  int pad0; + +  float *vertices; +  float *normals; +  float *texcoords; +  tinyobj_vertex_index_t *faces; +  int *face_num_verts; +  int *material_ids; +} tinyobj_attrib_t; + +#define TINYOBJ_FLAG_TRIANGULATE (1 << 0) + +#define TINYOBJ_INVALID_INDEX (0x80000000) + +#define TINYOBJ_SUCCESS (0) +#define TINYOBJ_ERROR_EMPTY (-1) +#define TINYOBJ_ERROR_INVALID_PARAMETER (-2) +#define TINYOBJ_ERROR_FILE_OPERATION (-3) + +/* Provide a callback that can read text file without any parsing or + * modification. The obj and mtl parser is going to read all the necessary data: + * tinyobj_parse_obj + * tinyobj_parse_mtl_file + * + * @param[in] ctx User provided context. + * @param[in] filename Filename to be loaded. + * @param[in] is_mtl 1 when the callback is invoked for loading .mtl. 0 for .obj + * @param[in] obj_filename .obj filename. Useful when you load .mtl from same + * location of .obj. When the callback is called to load .obj, `filename` and + * `obj_filename` are same. + * @param[out] buf Content of loaded file + * @param[out] len Size of content(file) + */ +typedef void (*file_reader_callback)(void *ctx, const char *filename, +                                     int is_mtl, const char *obj_filename, +                                     char **buf, size_t *len); + +/* Parse wavefront .obj + * @param[out] attrib Attibutes + * @param[out] shapes Array of parsed shapes + * @param[out] num_shapes Array length of `shapes` + * @param[out] materials Array of parsed materials + * @param[out] num_materials Array length of `materials` + * @param[in] file_name File name of .obj + * @param[in] file_reader File reader callback function(to read .obj and .mtl). + * @param[in] ctx Context pointer passed to the file_reader_callback. + * @param[in] flags combination of TINYOBJ_FLAG_*** + * + * Returns TINYOBJ_SUCCESS if things goes well. + * Returns TINYOBJ_ERROR_*** when there is an error. + */ +extern int tinyobj_parse_obj(tinyobj_attrib_t *attrib, tinyobj_shape_t **shapes, +                             size_t *num_shapes, tinyobj_material_t **materials, +                             size_t *num_materials, const char *file_name, +                             file_reader_callback file_reader, void *ctx, +                             unsigned int flags); + +/* Parse wavefront .mtl + * + * @param[out] materials_out + * @param[out] num_materials_out + * @param[in] filename .mtl filename + * @param[in] filename of .obj filename. could be NULL if you just want to parse + .mtl file. + * @param[in] file_reader File reader callback + * @param[in[ ctx Context pointer passed to the file_reader callack. + + * Returns TINYOBJ_SUCCESS if things goes well. + * Returns TINYOBJ_ERROR_*** when there is an error. + */ +extern int tinyobj_parse_mtl_file(tinyobj_material_t **materials_out, +                                  size_t *num_materials_out, +                                  const char *filename, +                                  const char *obj_filename, +                                  file_reader_callback file_reader, void *ctx); + +extern void tinyobj_attrib_init(tinyobj_attrib_t *attrib); +extern void tinyobj_attrib_free(tinyobj_attrib_t *attrib); +extern void tinyobj_shapes_free(tinyobj_shape_t *shapes, size_t num_shapes); +extern void tinyobj_materials_free(tinyobj_material_t *materials, +                                   size_t num_materials); + +#ifdef __cplusplus +} +#endif +#endif /* TINOBJ_LOADER_C_H_ */ + +#ifdef TINYOBJ_LOADER_C_IMPLEMENTATION +#include <assert.h> +#include <errno.h> +#include <stdio.h> +#include <string.h> + +#if defined(TINYOBJ_MALLOC) && defined(TINYOBJ_CALLOC) &&                      \ +    defined(TINYOBJ_FREE) &&                                                   \ +    (defined(TINYOBJ_REALLOC) || defined(TINYOBJ_REALLOC_SIZED)) +/* ok */ +#elif !defined(TINYOBJ_MALLOC) && !defined(TINYOBJ_CALLOC) &&                  \ +    !defined(TINYOBJ_FREE) && !defined(TINYOBJ_REALLOC) &&                     \ +    !defined(TINYOBJ_REALLOC_SIZED) +/* ok */ +#else +#error                                                                         \ +    "Must define all or none of TINYOBJ_MALLOC, TINYOBJ_CALLOC, TINYOBJ_FREE, and TINYOBJ_REALLOC (or TINYOBJ_REALLOC_SIZED)." +#endif + +#ifndef TINYOBJ_MALLOC +#include <stdlib.h> +#define TINYOBJ_MALLOC malloc +#define TINYOBJ_REALLOC realloc +#define TINYOBJ_CALLOC calloc +#define TINYOBJ_FREE free +#endif + +#ifndef TINYOBJ_REALLOC_SIZED +#define TINYOBJ_REALLOC_SIZED(p, oldsz, newsz) TINYOBJ_REALLOC(p, newsz) +#endif + +#define TINYOBJ_MAX_FACES_PER_F_LINE (16) +#define TINYOBJ_MAX_FILEPATH (8192) + +#define IS_SPACE(x) (((x) == ' ') || ((x) == '\t')) +#define IS_DIGIT(x) ((unsigned int)((x) - '0') < (unsigned int)(10)) +#define IS_NEW_LINE(x) (((x) == '\r') || ((x) == '\n') || ((x) == '\0')) + +static void skip_space(const char **token) { +  while ((*token)[0] == ' ' || (*token)[0] == '\t') { +    (*token)++; +  } +} + +static void skip_space_and_cr(const char **token) { +  while ((*token)[0] == ' ' || (*token)[0] == '\t' || (*token)[0] == '\r') { +    (*token)++; +  } +} + +static int until_space(const char *token) { +  const char *p = token; +  while (p[0] != '\0' && p[0] != ' ' && p[0] != '\t' && p[0] != '\r') { +    p++; +  } + +  return (int)(p - token); +} + +static size_t length_until_newline(const char *token, size_t n) { +  size_t 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; +} + +static size_t length_until_line_feed(const char *token, size_t n) { +  size_t len = 0; + +  /* Assume token[n-1] = '\0' */ +  for (len = 0; len < n; len++) { +    if ((token[len] == '\n') || (token[len] == '\r')) { +      break; +    } +  } + +  return len; +} + +/* http://stackoverflow.com/questions/5710091/how-does-atoi-function-in-c-work + */ +static 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 int fixIndex(int idx, size_t n) { +  if (idx > 0) +    return idx - 1; +  if (idx == 0) +    return 0; +  return (int)n + idx; /* negative value = relative */ +} + +/* Parse raw triples: i, i/j/k, i//k, i/j */ +static tinyobj_vertex_index_t parseRawTriple(const char **token) { +  tinyobj_vertex_index_t vi; +  /* 0x80000000 = -2147483648 = invalid */ +  vi.v_idx = (int)(0x80000000); +  vi.vn_idx = (int)(0x80000000); +  vi.vt_idx = (int)(0x80000000); + +  vi.v_idx = 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.vn_idx = 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.vt_idx = 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.vn_idx = my_atoi((*token)); +  while ((*token)[0] != '\0' && (*token)[0] != '/' && (*token)[0] != ' ' && +         (*token)[0] != '\t' && (*token)[0] != '\r') { +    (*token)++; +  } +  return vi; +} + +static int parseInt(const char **token) { +  int i = 0; +  skip_space(token); +  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 int tryParseDouble(const char *s, const char *s_end, double *result) { +  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. */ +  int end_not_reached = 0; + +  /* +     BEGIN PARSING. +     */ + +  if (s >= s_end) { +    return 0; /* fail */ +  } + +  /* 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 += (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; +      int f; +      for (f = 0; f < read; f++) { +        frac_value *= 0.1; +      } +      mantissa += (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 += (int)(*curr - 0x30); +      curr++; +      read++; +      end_not_reached = (curr != s_end); +    } +    if (read == 0) +      goto fail; +  } + +assemble: + +{ +  double a = 1.0; /* = pow(5.0, exponent); */ +  double b = 1.0; /* = 2.0^exponent */ +  int i; +  for (i = 0; i < exponent; i++) { +    a = a * 5.0; +  } + +  for (i = 0; i < exponent; i++) { +    b = b * 2.0; +  } + +  if (exp_sign == '-') { +    a = 1.0 / a; +    b = 1.0 / b; +  } + +  *result = +      /* (sign == '+' ? 1 : -1) * ldexp(mantissa * pow(5.0, exponent), +         exponent); */ +      (sign == '+' ? 1 : -1) * (mantissa * a * b); +} + +  return 1; +fail: +  return 0; +} + +static float parseFloat(const char **token) { +  const char *end; +  double val = 0.0; +  float f = 0.0f; +  skip_space(token); +  end = (*token) + until_space((*token)); +  val = 0.0; +  tryParseDouble((*token), end, &val); +  f = (float)(val); +  (*token) = end; +  return f; +} + +static void parseFloat2(float *x, float *y, const char **token) { +  (*x) = parseFloat(token); +  (*y) = parseFloat(token); +} + +static void parseFloat3(float *x, float *y, float *z, const char **token) { +  (*x) = parseFloat(token); +  (*y) = parseFloat(token); +  (*z) = parseFloat(token); +} + +static size_t my_strnlen(const char *s, size_t n) { +  const char *p = (char *)memchr(s, 0, n); +  return p ? (size_t)(p - s) : n; +} + +static char *my_strdup(const char *s, size_t max_length) { +  char *d; +  size_t len; + +  if (s == NULL) +    return NULL; + +  /* Do not consider CRLF line ending(#19) */ +  len = length_until_line_feed(s, max_length); +  /* len = strlen(s); */ + +  /* trim line ending and append '\0' */ +  d = (char *)TINYOBJ_MALLOC(len + 1); /* + '\0' */ +  memcpy(d, s, (size_t)(len)); +  d[len] = '\0'; + +  return d; +} + +static char *my_strndup(const char *s, size_t len) { +  char *d; +  size_t slen; + +  if (s == NULL) +    return NULL; +  if (len == 0) +    return NULL; + +  slen = my_strnlen(s, len); +  d = (char *)TINYOBJ_MALLOC(slen + 1); /* + '\0' */ +  if (!d) { +    return NULL; +  } +  memcpy(d, s, slen); +  d[slen] = '\0'; + +  return d; +} + +char *dynamic_fgets(char **buf, size_t *size, FILE *file) { +  char *offset; +  char *ret; +  size_t old_size; + +  if (!(ret = fgets(*buf, (int)*size, file))) { +    return ret; +  } + +  if (NULL != strchr(*buf, '\n')) { +    return ret; +  } + +  do { +    old_size = *size; +    *size *= 2; +    *buf = (char *)TINYOBJ_REALLOC_SIZED(*buf, old_size, *size); +    offset = &((*buf)[old_size - 1]); + +    ret = fgets(offset, (int)(old_size + 1), file); +  } while (ret && (NULL == strchr(*buf, '\n'))); + +  return ret; +} + +static void initMaterial(tinyobj_material_t *material) { +  int i; +  material->name = NULL; +  material->ambient_texname = NULL; +  material->diffuse_texname = NULL; +  material->specular_texname = NULL; +  material->specular_highlight_texname = NULL; +  material->bump_texname = NULL; +  material->displacement_texname = NULL; +  material->alpha_texname = NULL; +  for (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; +} + +/* Implementation of string to int hashtable */ + +#define HASH_TABLE_ERROR 1 +#define HASH_TABLE_SUCCESS 0 + +#define HASH_TABLE_DEFAULT_SIZE 10 + +typedef struct hash_table_entry_t { +  unsigned long hash; +  int filled; +  int pad0; +  long value; + +  struct hash_table_entry_t *next; +} hash_table_entry_t; + +typedef struct { +  unsigned long *hashes; +  hash_table_entry_t *entries; +  size_t capacity; +  size_t n; +} hash_table_t; + +static unsigned long hash_djb2(const unsigned char *str) { +  unsigned long hash = 5381; +  int c; + +  while ((c = *str++)) { +    hash = ((hash << 5) + hash) + (unsigned long)(c); +  } + +  return hash; +} + +static void create_hash_table(size_t start_capacity, hash_table_t *hash_table) { +  if (start_capacity < 1) +    start_capacity = HASH_TABLE_DEFAULT_SIZE; +  hash_table->hashes = +      (unsigned long *)TINYOBJ_MALLOC(start_capacity * sizeof(unsigned long)); +  hash_table->entries = (hash_table_entry_t *)TINYOBJ_CALLOC( +      start_capacity, sizeof(hash_table_entry_t)); +  hash_table->capacity = start_capacity; +  hash_table->n = 0; +} + +static void destroy_hash_table(hash_table_t *hash_table) { +  TINYOBJ_FREE(hash_table->entries); +  TINYOBJ_FREE(hash_table->hashes); +} + +/* Insert with quadratic probing */ +static int hash_table_insert_value(unsigned long hash, long value, +                                   hash_table_t *hash_table) { +  /* Insert value */ +  size_t start_index = hash % hash_table->capacity; +  size_t index = start_index; +  hash_table_entry_t *start_entry = hash_table->entries + start_index; +  size_t i; +  hash_table_entry_t *entry; + +  for (i = 1; hash_table->entries[index].filled; i++) { +    if (i >= hash_table->capacity) +      return HASH_TABLE_ERROR; +    index = (start_index + (i * i)) % hash_table->capacity; +  } + +  entry = hash_table->entries + index; +  entry->hash = hash; +  entry->filled = 1; +  entry->value = value; + +  if (index != start_index) { +    /* This is a new entry, but not the start entry, hence we need to add a next +     * pointer to our entry */ +    entry->next = start_entry->next; +    start_entry->next = entry; +  } + +  return HASH_TABLE_SUCCESS; +} + +static int hash_table_insert(unsigned long hash, long value, +                             hash_table_t *hash_table) { +  int ret = hash_table_insert_value(hash, value, hash_table); +  if (ret == HASH_TABLE_SUCCESS) { +    hash_table->hashes[hash_table->n] = hash; +    hash_table->n++; +  } +  return ret; +} + +static hash_table_entry_t *hash_table_find(unsigned long hash, +                                           hash_table_t *hash_table) { +  hash_table_entry_t *entry = +      hash_table->entries + (hash % hash_table->capacity); +  while (entry) { +    if (entry->hash == hash && entry->filled) { +      return entry; +    } +    entry = entry->next; +  } +  return NULL; +} + +static void hash_table_grow(hash_table_t *hash_table) { +  size_t new_capacity; +  hash_table_t new_hash_table; +  size_t i; + +  new_capacity = 2 * hash_table->capacity; +  /* Create a new hash table. We're not calling create_hash_table because we +   * want to realloc the hash array */ +  new_hash_table.hashes = hash_table->hashes = +      (unsigned long *)TINYOBJ_REALLOC_SIZED( +          (void *)hash_table->hashes, +          sizeof(unsigned long) * hash_table->capacity, +          sizeof(unsigned long) * new_capacity); +  new_hash_table.entries = (hash_table_entry_t *)TINYOBJ_CALLOC( +      new_capacity, sizeof(hash_table_entry_t)); +  new_hash_table.capacity = new_capacity; +  new_hash_table.n = hash_table->n; + +  /* Rehash */ +  for (i = 0; i < hash_table->capacity; i++) { +    hash_table_entry_t *entry = &hash_table->entries[i]; +    if (entry->filled) { +      hash_table_insert_value(entry->hash, entry->value, &new_hash_table); +    } +  } + +  TINYOBJ_FREE(hash_table->entries); +  (*hash_table) = new_hash_table; +} + +static int hash_table_exists(const char *name, hash_table_t *hash_table) { +  return hash_table_find(hash_djb2((const unsigned char *)name), hash_table) != +         NULL; +} + +static void hash_table_set(const char *name, size_t val, +                           hash_table_t *hash_table) { +  /* Hash name */ +  unsigned long hash = hash_djb2((const unsigned char *)name); + +  hash_table_entry_t *entry = hash_table_find(hash, hash_table); +  if (entry) { +    entry->value = (long)val; +    return; +  } + +  /* Expand if necessary +   * Grow until the element has been added +   */ +  while (hash_table_insert(hash, (long)val, hash_table) != HASH_TABLE_SUCCESS) { +    hash_table_grow(hash_table); +  } +} + +static long hash_table_get(const char *name, hash_table_t *hash_table) { +  hash_table_entry_t *ret = +      hash_table_find(hash_djb2((const unsigned char *)(name)), hash_table); +  return ret->value; +} + +static tinyobj_material_t *tinyobj_material_add(tinyobj_material_t *prev, +                                                size_t num_materials, +                                                tinyobj_material_t *new_mat) { +  tinyobj_material_t *dst; +  size_t num_bytes = sizeof(tinyobj_material_t) * num_materials; +  dst = (tinyobj_material_t *)TINYOBJ_REALLOC_SIZED( +      prev, num_bytes, num_bytes + sizeof(tinyobj_material_t)); + +  dst[num_materials] = (*new_mat); /* Just copy pointer for char* members */ +  return dst; +} + +static int is_line_ending(const char *p, size_t i, size_t end_i) { +  if (p[i] == '\0') +    return 1; +  if (p[i] == '\n') +    return 1; /* this includes \r\n */ +  if (p[i] == '\r') { +    if (((i + 1) < end_i) && (p[i + 1] != '\n')) { /* detect only \r case */ +      return 1; +    } +  } +  return 0; +} + +typedef struct { +  size_t pos; +  size_t len; +} LineInfo; + +/* Find '\n' and create line data. */ +static int get_line_infos(const char *buf, size_t buf_len, +                          LineInfo **line_infos, size_t *num_lines) { +  size_t i = 0; +  size_t end_idx = buf_len; +  size_t prev_pos = 0; +  size_t line_no = 0; +  size_t last_line_ending = 0; + +  /* Count # of lines. */ +  for (i = 0; i < end_idx; i++) { +    if (is_line_ending(buf, i, end_idx)) { +      (*num_lines)++; +      last_line_ending = i; +    } +  } +  /* The last char from the input may not be a line +   * ending character so add an extra line if there +   * are more characters after the last line ending +   * that was found. */ +  if (end_idx - last_line_ending > 1) { +    (*num_lines)++; +  } + +  if (*num_lines == 0) +    return TINYOBJ_ERROR_EMPTY; + +  *line_infos = (LineInfo *)TINYOBJ_MALLOC(sizeof(LineInfo) * (*num_lines)); + +  /* Fill line infos. */ +  for (i = 0; i < end_idx; i++) { +    if (is_line_ending(buf, i, end_idx)) { +      (*line_infos)[line_no].pos = prev_pos; +      (*line_infos)[line_no].len = i - prev_pos; +      prev_pos = i + 1; +      line_no++; +    } +  } +  if (end_idx - last_line_ending > 1) { +    (*line_infos)[line_no].pos = prev_pos; +    (*line_infos)[line_no].len = end_idx - 1 - last_line_ending; +  } + +  return 0; +} + +static int tinyobj_parse_and_index_mtl_file( +    tinyobj_material_t **materials_out, size_t *num_materials_out, +    const char *mtl_filename, const char *obj_filename, +    file_reader_callback file_reader, void *ctx, hash_table_t *material_table) { +  tinyobj_material_t material; +  size_t num_materials = 0; +  tinyobj_material_t *materials = NULL; +  int has_previous_material = 0; +  const char *line_end = NULL; +  size_t num_lines = 0; +  LineInfo *line_infos = NULL; +  size_t i = 0; +  char *buf = NULL; +  size_t len = 0; + +  if (materials_out == NULL) { +    return TINYOBJ_ERROR_INVALID_PARAMETER; +  } + +  if (num_materials_out == NULL) { +    return TINYOBJ_ERROR_INVALID_PARAMETER; +  } + +  (*materials_out) = NULL; +  (*num_materials_out) = 0; + +  file_reader(ctx, mtl_filename, 1, obj_filename, &buf, &len); +  if (len < 1) +    return TINYOBJ_ERROR_INVALID_PARAMETER; +  if (buf == NULL) +    return TINYOBJ_ERROR_INVALID_PARAMETER; + +  if (get_line_infos(buf, len, &line_infos, &num_lines) != 0) { +    TINYOBJ_FREE(line_infos); +    return TINYOBJ_ERROR_EMPTY; +  } + +  /* Create a default material */ +  initMaterial(&material); + +  for (i = 0; i < num_lines; i++) { +    const char *p = &buf[line_infos[i].pos]; +    size_t p_len = line_infos[i].len; + +    char linebuf[4096]; +    const char *token; +    assert(p_len < 4095); + +    memcpy(linebuf, p, p_len); +    linebuf[p_len] = '\0'; + +    token = linebuf; +    line_end = token + p_len; + +    /* Skip leading space. */ +    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]))) { +      char namebuf[4096]; + +      /* flush previous material. */ +      if (has_previous_material) { +        materials = tinyobj_material_add(materials, num_materials, &material); +        num_materials++; +      } else { +        has_previous_material = 1; +      } + +      /* initial temporary material */ +      initMaterial(&material); + +      /* set new mtl name */ +      token += 7; +#ifdef _MSC_VER +      sscanf_s(token, "%s", namebuf, (unsigned)_countof(namebuf)); +#else +      sscanf(token, "%s", namebuf); +#endif +      material.name = my_strdup(namebuf, (size_t)(line_end - token)); + +      /* Add material to material table */ +      if (material_table) +        hash_table_set(material.name, num_materials, material_table); + +      continue; +    } + +    /* ambient */ +    if (token[0] == 'K' && token[1] == 'a' && IS_SPACE((token[2]))) { +      float r, g, b; +      token += 2; +      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]))) { +      float r, g, b; +      token += 2; +      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]))) { +      float r, g, b; +      token += 2; +      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]))) { +      float r, g, b; +      token += 2; +      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])) { +      float r, g, b; +      token += 2; +      parseFloat3(&r, &g, &b, &token); +      material.emission[0] = r; +      material.emission[1] = g; +      material.emission[2] = b; +      continue; +    } + +    /* shininess */ +    if (token[0] == 'N' && token[1] == 's' && IS_SPACE(token[2])) { +      token += 2; +      material.shininess = parseFloat(&token); +      continue; +    } + +    /* illum model */ +    if (0 == strncmp(token, "illum", 5) && IS_SPACE(token[5])) { +      token += 6; +      material.illum = parseInt(&token); +      continue; +    } + +    /* dissolve */ +    if ((token[0] == 'd' && IS_SPACE(token[1]))) { +      token += 1; +      material.dissolve = parseFloat(&token); +      continue; +    } +    if (token[0] == 'T' && token[1] == 'r' && IS_SPACE(token[2])) { +      token += 2; +      /* Invert value of Tr(assume Tr is in range [0, 1]) */ +      material.dissolve = 1.0f - parseFloat(&token); +      continue; +    } + +    /* ambient texture */ +    if ((0 == strncmp(token, "map_Ka", 6)) && IS_SPACE(token[6])) { +      token += 7; +      material.ambient_texname = my_strdup(token, (size_t)(line_end - token)); +      continue; +    } + +    /* diffuse texture */ +    if ((0 == strncmp(token, "map_Kd", 6)) && IS_SPACE(token[6])) { +      token += 7; +      material.diffuse_texname = my_strdup(token, (size_t)(line_end - token)); +      continue; +    } + +    /* specular texture */ +    if ((0 == strncmp(token, "map_Ks", 6)) && IS_SPACE(token[6])) { +      token += 7; +      material.specular_texname = my_strdup(token, (size_t)(line_end - token)); +      continue; +    } + +    /* specular highlight texture */ +    if ((0 == strncmp(token, "map_Ns", 6)) && IS_SPACE(token[6])) { +      token += 7; +      material.specular_highlight_texname = +          my_strdup(token, (size_t)(line_end - token)); +      continue; +    } + +    /* bump texture */ +    if ((0 == strncmp(token, "map_bump", 8)) && IS_SPACE(token[8])) { +      token += 9; +      material.bump_texname = my_strdup(token, (size_t)(line_end - token)); +      continue; +    } + +    /* alpha texture */ +    if ((0 == strncmp(token, "map_d", 5)) && IS_SPACE(token[5])) { +      token += 6; +      material.alpha_texname = my_strdup(token, (size_t)(line_end - token)); +      continue; +    } + +    /* bump texture */ +    if ((0 == strncmp(token, "bump", 4)) && IS_SPACE(token[4])) { +      token += 5; +      material.bump_texname = my_strdup(token, (size_t)(line_end - token)); +      continue; +    } + +    /* displacement texture */ +    if ((0 == strncmp(token, "disp", 4)) && IS_SPACE(token[4])) { +      token += 5; +      material.displacement_texname = +          my_strdup(token, (size_t)(line_end - token)); +      continue; +    } + +    /* @todo { unknown parameter } */ +  } + +  TINYOBJ_FREE(line_infos); + +  if (material.name) { +    /* Flush last material element */ +    materials = tinyobj_material_add(materials, num_materials, &material); +    num_materials++; +  } + +  (*num_materials_out) = num_materials; +  (*materials_out) = materials; + +  return TINYOBJ_SUCCESS; +} + +int tinyobj_parse_mtl_file(tinyobj_material_t **materials_out, +                           size_t *num_materials_out, const char *mtl_filename, +                           const char *obj_filename, +                           file_reader_callback file_reader, void *ctx) { +  return tinyobj_parse_and_index_mtl_file(materials_out, num_materials_out, +                                          mtl_filename, obj_filename, +                                          file_reader, ctx, NULL); +} + +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; + +  /* @todo { Use dynamic array } */ +  tinyobj_vertex_index_t f[TINYOBJ_MAX_FACES_PER_F_LINE]; +  size_t num_f; + +  int f_num_verts[TINYOBJ_MAX_FACES_PER_F_LINE]; +  size_t num_f_num_verts; + +  const char *group_name; +  unsigned int group_name_len; +  int pad0; + +  const char *object_name; +  unsigned int object_name_len; +  int pad1; + +  const char *material_name; +  unsigned int material_name_len; +  int pad2; + +  const char *mtllib_name; +  unsigned int mtllib_name_len; + +  CommandType type; +} Command; + +static int parseLine(Command *command, const char *p, size_t p_len, +                     int triangulate) { +  char linebuf[4096]; +  const char *token; +  assert(p_len < 4095); + +  memcpy(linebuf, p, p_len); +  linebuf[p_len] = '\0'; + +  token = linebuf; + +  command->type = COMMAND_EMPTY; + +  /* Skip leading space. */ +  skip_space(&token); + +  assert(token); +  if (token[0] == '\0') { /* empty line */ +    return 0; +  } + +  if (token[0] == '#') { /* comment line */ +    return 0; +  } + +  /* vertex */ +  if (token[0] == 'v' && IS_SPACE((token[1]))) { +    float x, y, z; +    token += 2; +    parseFloat3(&x, &y, &z, &token); +    command->vx = x; +    command->vy = y; +    command->vz = z; +    command->type = COMMAND_V; +    return 1; +  } + +  /* normal */ +  if (token[0] == 'v' && token[1] == 'n' && IS_SPACE((token[2]))) { +    float x, y, z; +    token += 3; +    parseFloat3(&x, &y, &z, &token); +    command->nx = x; +    command->ny = y; +    command->nz = z; +    command->type = COMMAND_VN; +    return 1; +  } + +  /* texcoord */ +  if (token[0] == 'v' && token[1] == 't' && IS_SPACE((token[2]))) { +    float x, y; +    token += 3; +    parseFloat2(&x, &y, &token); +    command->tx = x; +    command->ty = y; +    command->type = COMMAND_VT; +    return 1; +  } + +  /* face */ +  if (token[0] == 'f' && IS_SPACE((token[1]))) { +    size_t num_f = 0; + +    tinyobj_vertex_index_t f[TINYOBJ_MAX_FACES_PER_F_LINE]; +    token += 2; +    skip_space(&token); + +    while (!IS_NEW_LINE(token[0])) { +      tinyobj_vertex_index_t vi = parseRawTriple(&token); +      skip_space_and_cr(&token); + +      f[num_f] = vi; +      num_f++; +    } + +    command->type = COMMAND_F; + +    if (triangulate) { +      size_t k; +      size_t n = 0; + +      tinyobj_vertex_index_t i0 = f[0]; +      tinyobj_vertex_index_t i1; +      tinyobj_vertex_index_t i2 = f[1]; + +      assert(3 * num_f < TINYOBJ_MAX_FACES_PER_F_LINE); + +      for (k = 2; k < num_f; k++) { +        i1 = i2; +        i2 = f[k]; +        command->f[3 * n + 0] = i0; +        command->f[3 * n + 1] = i1; +        command->f[3 * n + 2] = i2; + +        command->f_num_verts[n] = 3; +        n++; +      } +      command->num_f = 3 * n; +      command->num_f_num_verts = n; + +    } else { +      size_t k = 0; +      assert(num_f < TINYOBJ_MAX_FACES_PER_F_LINE); +      for (k = 0; k < num_f; k++) { +        command->f[k] = f[k]; +      } + +      command->num_f = num_f; +      command->f_num_verts[0] = (int)num_f; +      command->num_f_num_verts = 1; +    } + +    return 1; +  } + +  /* use mtl */ +  if ((0 == strncmp(token, "usemtl", 6)) && IS_SPACE((token[6]))) { +    token += 7; + +    skip_space(&token); +    command->material_name = p + (token - linebuf); +    command->material_name_len = (unsigned int)length_until_newline( +        token, (p_len - (size_t)(token - linebuf)) + 1); +    command->type = COMMAND_USEMTL; + +    return 1; +  } + +  /* 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 = (unsigned int)length_until_newline( +                                   token, p_len - (size_t)(token - linebuf)) + +                               1; +    command->type = COMMAND_MTLLIB; + +    return 1; +  } + +  /* 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 = (unsigned int)length_until_newline( +                                  token, p_len - (size_t)(token - linebuf)) + +                              1; +    command->type = COMMAND_G; + +    return 1; +  } + +  /* 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 = (unsigned int)length_until_newline( +                                   token, p_len - (size_t)(token - linebuf)) + +                               1; +    command->type = COMMAND_O; + +    return 1; +  } + +  return 0; +} + +static size_t basename_len(const char *filename, size_t filename_length) { +  /* Count includes NUL terminator. */ +  const char *p = &filename[filename_length - 1]; +  size_t count = 1; + +/* On Windows, the directory delimiter is '\' and both it and '/' is + * reserved by the filesystem. On *nix platforms, only the '/' character + * is reserved, so account for the two cases separately. */ +#if _WIN32 +  while (p[-1] != '/' && p[-1] != '\\') { +    if (p == filename) { +      count = filename_length; +      return count; +    } +    count++; +    p--; +  } +  p++; +  return count; +#else +  while (*(--p) != '/') { +    if (p == filename) { +      count = filename_length; +      return count; +    } +    count++; +  } +  return count; +#endif +} + +static char *generate_mtl_filename(const char *obj_filename, +                                   size_t obj_filename_length, +                                   const char *mtllib_name, +                                   size_t mtllib_name_length) { +  /* Create a dynamically-allocated material filename. This allows the material +   * and obj files to be separated, however the mtllib name in the OBJ file +   * must be a relative path to the material file from the OBJ's directory. +   * This does not support the matllib name as an absolute address. */ +  char *mtl_filename; +  char *p; +  size_t mtl_filename_length; +  size_t obj_basename_length; + +  /* Calculate required size of mtl_filename and allocate */ +  obj_basename_length = basename_len(obj_filename, obj_filename_length); +  mtl_filename_length = +      (obj_filename_length - obj_basename_length) + mtllib_name_length; +  mtl_filename = (char *)TINYOBJ_MALLOC(mtl_filename_length); + +  /* Copy over the obj's path */ +  memcpy(mtl_filename, obj_filename, +         (obj_filename_length - obj_basename_length)); + +  /* Overwrite the obj basename with the mtllib name, filling the string */ +  p = &mtl_filename[mtl_filename_length - mtllib_name_length]; +  strcpy(p, mtllib_name); +  return mtl_filename; +} + +int tinyobj_parse_obj(tinyobj_attrib_t *attrib, tinyobj_shape_t **shapes, +                      size_t *num_shapes, tinyobj_material_t **materials_out, +                      size_t *num_materials_out, const char *obj_filename, +                      file_reader_callback file_reader, void *ctx, +                      unsigned int flags) { +  LineInfo *line_infos = NULL; +  Command *commands = NULL; +  size_t num_lines = 0; + +  size_t num_v = 0; +  size_t num_vn = 0; +  size_t num_vt = 0; +  size_t num_f = 0; +  size_t num_faces = 0; + +  int mtllib_line_index = -1; + +  tinyobj_material_t *materials = NULL; +  size_t num_materials = 0; + +  hash_table_t material_table; + +  char *buf = NULL; +  size_t len = 0; +  file_reader(ctx, obj_filename, /* is_mtl */ 0, obj_filename, &buf, &len); + +  if (len < 1) +    return TINYOBJ_ERROR_INVALID_PARAMETER; +  if (attrib == NULL) +    return TINYOBJ_ERROR_INVALID_PARAMETER; +  if (shapes == NULL) +    return TINYOBJ_ERROR_INVALID_PARAMETER; +  if (num_shapes == NULL) +    return TINYOBJ_ERROR_INVALID_PARAMETER; +  if (buf == NULL) +    return TINYOBJ_ERROR_INVALID_PARAMETER; +  if (materials_out == NULL) +    return TINYOBJ_ERROR_INVALID_PARAMETER; +  if (num_materials_out == NULL) +    return TINYOBJ_ERROR_INVALID_PARAMETER; + +  tinyobj_attrib_init(attrib); + +  /* 1. create line data */ +  if (get_line_infos(buf, len, &line_infos, &num_lines) != 0) { +    return TINYOBJ_ERROR_EMPTY; +  } + +  commands = (Command *)TINYOBJ_MALLOC(sizeof(Command) * num_lines); + +  create_hash_table(HASH_TABLE_DEFAULT_SIZE, &material_table); + +  /* 2. parse each line */ +  { +    size_t i = 0; +    for (i = 0; i < num_lines; i++) { +      int ret = parseLine(&commands[i], &buf[line_infos[i].pos], +                          line_infos[i].len, flags & TINYOBJ_FLAG_TRIANGULATE); +      if (ret) { +        if (commands[i].type == COMMAND_V) { +          num_v++; +        } else if (commands[i].type == COMMAND_VN) { +          num_vn++; +        } else if (commands[i].type == COMMAND_VT) { +          num_vt++; +        } else if (commands[i].type == COMMAND_F) { +          num_f += commands[i].num_f; +          num_faces += commands[i].num_f_num_verts; +        } + +        if (commands[i].type == COMMAND_MTLLIB) { +          mtllib_line_index = (int)i; +        } +      } +    } +  } + +  /* line_infos are not used anymore. Release memory. */ +  if (line_infos) { +    TINYOBJ_FREE(line_infos); +  } + +  /* Load material (if it exists) */ +  if (mtllib_line_index >= 0 && commands[mtllib_line_index].mtllib_name && +      commands[mtllib_line_index].mtllib_name_len > 0) { +    /* Maximum length allowed by Linux - higher than Windows and macOS */ +    size_t obj_filename_len = my_strnlen(obj_filename, 4096 + 255) + 1; +    char *mtl_filename; +    char *mtllib_name; +    size_t mtllib_name_len = 0; +    int ret; + +    mtllib_name_len = +        length_until_line_feed(commands[mtllib_line_index].mtllib_name, +                               commands[mtllib_line_index].mtllib_name_len); + +    mtllib_name = +        my_strndup(commands[mtllib_line_index].mtllib_name, mtllib_name_len); + +    /* allow for NUL terminator */ +    mtllib_name_len++; +    mtl_filename = generate_mtl_filename(obj_filename, obj_filename_len, +                                         mtllib_name, mtllib_name_len); + +    ret = tinyobj_parse_and_index_mtl_file(&materials, &num_materials, +                                           mtl_filename, obj_filename, +                                           file_reader, ctx, &material_table); + +    if (ret != TINYOBJ_SUCCESS) { +      /* warning. */ +      fprintf(stderr, "TINYOBJ: Failed to parse material file '%s': %d\n", +              mtl_filename, ret); +    } +    TINYOBJ_FREE(mtl_filename); +    TINYOBJ_FREE(mtllib_name); +  } + +  /* Construct attributes */ + +  { +    size_t v_count = 0; +    size_t n_count = 0; +    size_t t_count = 0; +    size_t f_count = 0; +    size_t face_count = 0; +    int material_id = -1; /* -1 = default unknown material. */ +    size_t i = 0; + +    attrib->vertices = (float *)TINYOBJ_MALLOC(sizeof(float) * num_v * 3); +    attrib->num_vertices = (unsigned int)num_v; +    attrib->normals = (float *)TINYOBJ_MALLOC(sizeof(float) * num_vn * 3); +    attrib->num_normals = (unsigned int)num_vn; +    attrib->texcoords = (float *)TINYOBJ_MALLOC(sizeof(float) * num_vt * 2); +    attrib->num_texcoords = (unsigned int)num_vt; +    attrib->faces = (tinyobj_vertex_index_t *)TINYOBJ_MALLOC( +        sizeof(tinyobj_vertex_index_t) * num_f); +    attrib->num_faces = (unsigned int)num_f; +    attrib->face_num_verts = (int *)TINYOBJ_MALLOC(sizeof(int) * num_faces); +    attrib->material_ids = (int *)TINYOBJ_MALLOC(sizeof(int) * num_faces); +    attrib->num_face_num_verts = (unsigned int)num_faces; + +    for (i = 0; i < num_lines; i++) { +      if (commands[i].type == COMMAND_EMPTY) { +        continue; +      } else if (commands[i].type == COMMAND_USEMTL) { +        /* @todo +           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; +        } +        } +        */ +        if (commands[i].material_name && commands[i].material_name_len > 0) { +          /* Create a null terminated string */ +          char *material_name_null_term = +              (char *)TINYOBJ_MALLOC(commands[i].material_name_len + 1); +          memcpy((void *)material_name_null_term, +                 (const void *)commands[i].material_name, +                 commands[i].material_name_len); +          material_name_null_term[commands[i].material_name_len] = 0; + +          if (hash_table_exists(material_name_null_term, &material_table)) +            material_id = +                (int)hash_table_get(material_name_null_term, &material_table); +          else +            material_id = -1; + +          TINYOBJ_FREE(material_name_null_term); +        } +      } else if (commands[i].type == COMMAND_V) { +        attrib->vertices[3 * v_count + 0] = commands[i].vx; +        attrib->vertices[3 * v_count + 1] = commands[i].vy; +        attrib->vertices[3 * v_count + 2] = commands[i].vz; +        v_count++; +      } else if (commands[i].type == COMMAND_VN) { +        attrib->normals[3 * n_count + 0] = commands[i].nx; +        attrib->normals[3 * n_count + 1] = commands[i].ny; +        attrib->normals[3 * n_count + 2] = commands[i].nz; +        n_count++; +      } else if (commands[i].type == COMMAND_VT) { +        attrib->texcoords[2 * t_count + 0] = commands[i].tx; +        attrib->texcoords[2 * t_count + 1] = commands[i].ty; +        t_count++; +      } else if (commands[i].type == COMMAND_F) { +        size_t k = 0; +        for (k = 0; k < commands[i].num_f; k++) { +          tinyobj_vertex_index_t vi = commands[i].f[k]; +          int v_idx = fixIndex(vi.v_idx, v_count); +          int vn_idx = fixIndex(vi.vn_idx, n_count); +          int vt_idx = fixIndex(vi.vt_idx, t_count); +          attrib->faces[f_count + k].v_idx = v_idx; +          attrib->faces[f_count + k].vn_idx = vn_idx; +          attrib->faces[f_count + k].vt_idx = vt_idx; +        } + +        for (k = 0; k < commands[i].num_f_num_verts; k++) { +          attrib->material_ids[face_count + k] = material_id; +          attrib->face_num_verts[face_count + k] = commands[i].f_num_verts[k]; +        } + +        f_count += commands[i].num_f; +        face_count += commands[i].num_f_num_verts; +      } +    } +  } + +  /* 5. Construct shape information. */ +  { +    unsigned int face_count = 0; +    size_t i = 0; +    size_t n = 0; +    size_t shape_idx = 0; + +    const char *shape_name = NULL; +    unsigned int shape_name_len = 0; +    const char *prev_shape_name = NULL; +    unsigned int prev_shape_name_len = 0; +    unsigned int prev_shape_face_offset = 0; +    unsigned int prev_face_offset = 0; +    tinyobj_shape_t prev_shape = {NULL, 0, 0}; + +    /* Find the number of shapes in .obj */ +    for (i = 0; i < num_lines; i++) { +      if (commands[i].type == COMMAND_O || commands[i].type == COMMAND_G) { +        n++; +      } +    } + +    /* Allocate array of shapes with maximum possible size(+1 for unnamed +     * group/object). +     * Actual # of shapes found in .obj is determined in the later */ +    (*shapes) = +        (tinyobj_shape_t *)TINYOBJ_MALLOC(sizeof(tinyobj_shape_t) * (n + 1)); + +    for (i = 0; i < num_lines; i++) { +      if (commands[i].type == COMMAND_O || commands[i].type == COMMAND_G) { +        if (commands[i].type == COMMAND_O) { +          shape_name = commands[i].object_name; +          shape_name_len = commands[i].object_name_len; +        } else { +          shape_name = commands[i].group_name; +          shape_name_len = commands[i].group_name_len; +        } + +        if (face_count == 0) { +          /* 'o' or 'g' appears before any 'f' */ +          prev_shape_name = shape_name; +          prev_shape_name_len = shape_name_len; +          prev_shape_face_offset = face_count; +          prev_face_offset = face_count; +        } else { +          if (shape_idx == 0) { +            /* 'o' or 'g' after some 'v' lines. */ +            (*shapes)[shape_idx].name = my_strndup( +                prev_shape_name, prev_shape_name_len); /* may be NULL */ +            (*shapes)[shape_idx].face_offset = prev_shape.face_offset; +            (*shapes)[shape_idx].length = face_count - prev_face_offset; +            shape_idx++; + +            prev_face_offset = face_count; + +          } else { +            if ((face_count - prev_face_offset) > 0) { +              (*shapes)[shape_idx].name = +                  my_strndup(prev_shape_name, prev_shape_name_len); +              (*shapes)[shape_idx].face_offset = prev_face_offset; +              (*shapes)[shape_idx].length = face_count - prev_face_offset; +              shape_idx++; +              prev_face_offset = face_count; +            } +          } + +          /* Record shape info for succeeding 'o' or 'g' command. */ +          prev_shape_name = shape_name; +          prev_shape_name_len = shape_name_len; +          prev_shape_face_offset = face_count; +        } +      } +      if (commands[i].type == COMMAND_F) { +        face_count++; +      } +    } + +    if ((face_count - prev_face_offset) > 0) { +      size_t length = face_count - prev_shape_face_offset; +      if (length > 0) { +        (*shapes)[shape_idx].name = +            my_strndup(prev_shape_name, prev_shape_name_len); +        (*shapes)[shape_idx].face_offset = prev_face_offset; +        (*shapes)[shape_idx].length = face_count - prev_face_offset; +        shape_idx++; +      } +    } else { +      /* Guess no 'v' line occurrence after 'o' or 'g', so discards current +       * shape information. */ +    } + +    (*num_shapes) = shape_idx; +  } + +  if (commands) { +    TINYOBJ_FREE(commands); +  } + +  destroy_hash_table(&material_table); + +  (*materials_out) = materials; +  (*num_materials_out) = num_materials; + +  return TINYOBJ_SUCCESS; +} + +void tinyobj_attrib_init(tinyobj_attrib_t *attrib) { +  attrib->vertices = NULL; +  attrib->num_vertices = 0; +  attrib->normals = NULL; +  attrib->num_normals = 0; +  attrib->texcoords = NULL; +  attrib->num_texcoords = 0; +  attrib->faces = NULL; +  attrib->num_faces = 0; +  attrib->face_num_verts = NULL; +  attrib->num_face_num_verts = 0; +  attrib->material_ids = NULL; +} + +void tinyobj_attrib_free(tinyobj_attrib_t *attrib) { +  if (attrib->vertices) +    TINYOBJ_FREE(attrib->vertices); +  if (attrib->normals) +    TINYOBJ_FREE(attrib->normals); +  if (attrib->texcoords) +    TINYOBJ_FREE(attrib->texcoords); +  if (attrib->faces) +    TINYOBJ_FREE(attrib->faces); +  if (attrib->face_num_verts) +    TINYOBJ_FREE(attrib->face_num_verts); +  if (attrib->material_ids) +    TINYOBJ_FREE(attrib->material_ids); +} + +void tinyobj_shapes_free(tinyobj_shape_t *shapes, size_t num_shapes) { +  size_t i; +  if (shapes == NULL) +    return; + +  for (i = 0; i < num_shapes; i++) { +    if (shapes[i].name) +      TINYOBJ_FREE(shapes[i].name); +  } + +  TINYOBJ_FREE(shapes); +} + +void tinyobj_materials_free(tinyobj_material_t *materials, +                            size_t num_materials) { +  size_t i; +  if (materials == NULL) +    return; + +  for (i = 0; i < num_materials; i++) { +    if (materials[i].name) +      TINYOBJ_FREE(materials[i].name); +    if (materials[i].ambient_texname) +      TINYOBJ_FREE(materials[i].ambient_texname); +    if (materials[i].diffuse_texname) +      TINYOBJ_FREE(materials[i].diffuse_texname); +    if (materials[i].specular_texname) +      TINYOBJ_FREE(materials[i].specular_texname); +    if (materials[i].specular_highlight_texname) +      TINYOBJ_FREE(materials[i].specular_highlight_texname); +    if (materials[i].bump_texname) +      TINYOBJ_FREE(materials[i].bump_texname); +    if (materials[i].displacement_texname) +      TINYOBJ_FREE(materials[i].displacement_texname); +    if (materials[i].alpha_texname) +      TINYOBJ_FREE(materials[i].alpha_texname); +  } + +  TINYOBJ_FREE(materials); +} +#endif /* TINYOBJ_LOADER_C_IMPLEMENTATION */  | 
