/*
   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. unsigned int? } ---> RAY: DONE. */
//#include <stddef.h>

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)

/* Parse wavefront .obj(.obj string data is expanded to linear char array `buf')
 * flags are combination of TINYOBJ_FLAG_***
 * Returns TINYOBJ_SUCCESS if things goes well.
 * Returns TINYOBJ_ERR_*** when there is an error.
 */
extern int tinyobj_parse_obj(tinyobj_attrib_t *attrib, tinyobj_shape_t **shapes,
                             unsigned int *num_shapes, tinyobj_material_t **materials,
                             unsigned int *num_materials, const char *buf, unsigned int len,
                             unsigned int flags);
extern int tinyobj_parse_mtl_file(tinyobj_material_t **materials_out,
                                  unsigned int *num_materials_out,
                                  const char *filename);

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, unsigned int num_shapes);
extern void tinyobj_materials_free(tinyobj_material_t *materials,
                                   unsigned int num_materials);

#ifdef TINYOBJ_LOADER_C_IMPLEMENTATION
#include <stdio.h>
#include <assert.h>
#include <string.h>
#include <errno.h>

#if defined(TINYOBJ_MALLOC) && defined(TINYOBJ_REALLOC) && defined(TINYOBJ_CALLOC) && defined(TINYOBJ_FREE)
/* ok */
#elif !defined(TINYOBJ_MALLOC) && !defined(TINYOBJ_REALLOC) && !defined(TINYOBJ_CALLOC) && !defined(TINYOBJ_FREE)
/* ok */
#else
#error "Must define all or none of TINYOBJ_MALLOC, TINYOBJ_REALLOC, TINYOBJ_CALLOC and TINYOBJ_FREE."
#endif

#ifndef TINYOBJ_MALLOC
#include <stdlib.h>
#define TINYOBJ_MALLOC malloc
#define TINYOBJ_REALLOC realloc
#define TINYOBJ_CALLOC calloc
#define TINYOBJ_FREE free
#endif

#define TINYOBJ_MAX_FACES_PER_F_LINE (16)

#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 unsigned int length_until_newline(const char *token, unsigned int n) {
  unsigned 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;
}

static unsigned int length_until_line_feed(const char *token, unsigned int n) {
  unsigned int 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, unsigned int 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 unsigned int my_strnlen(const char *s, unsigned int n) {
    const char *p = memchr(s, 0, n);
    return p ? (unsigned int)(p - s) : n;
}

static char *my_strdup(const char *s, unsigned int max_length) {
  char *d;
  unsigned int 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, (unsigned int)(len));
  d[len] = '\0';

  return d;
}

static char *my_strndup(const char *s, unsigned int len) {
  char *d;
  unsigned int 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, unsigned int *size, FILE *file) {
  char *offset;
  char *ret;
  unsigned int 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(*buf, *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;
  unsigned int capacity;
  unsigned int 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(unsigned int 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 */
  unsigned int start_index = hash % hash_table->capacity;
  unsigned int index = start_index;
  hash_table_entry_t* start_entry = hash_table->entries + start_index;
  unsigned int 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_maybe_grow(unsigned int new_n, hash_table_t* hash_table)
{
  unsigned int new_capacity;
  hash_table_t new_hash_table;
  unsigned int i;

  if (new_n <= hash_table->capacity) {
    return;
  }
  new_capacity = 2 * ((2 * hash_table->capacity) > new_n ? hash_table->capacity : new_n);
  /* 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((void*) hash_table->hashes, 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_find(hash_table->hashes[i], hash_table);
    hash_table_insert_value(hash_table->hashes[i], 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, unsigned int 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
   */
  do
  {
    hash_table_maybe_grow(hash_table->n + 1, hash_table);
  }
  while (hash_table_insert(hash, (long)val, hash_table) != HASH_TABLE_SUCCESS);
}

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,
                                                unsigned int num_materials,
                                                tinyobj_material_t *new_mat) {
  tinyobj_material_t *dst;
  dst = (tinyobj_material_t *)TINYOBJ_REALLOC(
                                      prev, sizeof(tinyobj_material_t) * (num_materials + 1));

  dst[num_materials] = (*new_mat); /* Just copy pointer for char* members */
  return dst;
}

static int tinyobj_parse_and_index_mtl_file(tinyobj_material_t **materials_out,
                                            unsigned int *num_materials_out,
                                            const char *filename,
                                            hash_table_t* material_table) {
  tinyobj_material_t material;
  unsigned int buffer_size = 128;
  char *linebuf;
  FILE *fp;
  unsigned int num_materials = 0;
  tinyobj_material_t *materials = NULL;
  int has_previous_material = 0;
  const char *line_end = NULL;

  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;

  fp = fopen(filename, "rt");
  if (!fp) {
    fprintf(stderr, "TINYOBJ: Error reading file '%s': %s (%d)\n", filename, strerror(errno), errno);
    return TINYOBJ_ERROR_FILE_OPERATION;
  }

  /* Create a default material */
  initMaterial(&material);

  linebuf = (char*)TINYOBJ_MALLOC(buffer_size);
  while (NULL != dynamic_fgets(&linebuf, &buffer_size, fp)) {
    const char *token = linebuf;

    line_end = token + strlen(token);

    /* 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, (unsigned int) (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, (unsigned int) (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, (unsigned int) (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, (unsigned int) (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, (unsigned int) (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, (unsigned int) (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, (unsigned int) (line_end - token));
      continue;
    }

    /* bump texture */
    if ((0 == strncmp(token, "bump", 4)) && IS_SPACE(token[4])) {
      token += 5;
      material.bump_texname = my_strdup(token, (unsigned int) (line_end - token));
      continue;
    }

    /* displacement texture */
    if ((0 == strncmp(token, "disp", 4)) && IS_SPACE(token[4])) {
      token += 5;
      material.displacement_texname = my_strdup(token, (unsigned int) (line_end - token));
      continue;
    }

    /* @todo { unknown parameter } */
  }

  fclose(fp);

  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;

  if (linebuf) {
    TINYOBJ_FREE(linebuf);
  }

  return TINYOBJ_SUCCESS;
}

int tinyobj_parse_mtl_file(tinyobj_material_t **materials_out,
                           unsigned int *num_materials_out,
                           const char *filename) {
  return tinyobj_parse_and_index_mtl_file(materials_out, num_materials_out, filename, 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];
  unsigned int num_f;

  int f_num_verts[TINYOBJ_MAX_FACES_PER_F_LINE];
  unsigned int 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, unsigned int 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]))) {
    unsigned int 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) {
      unsigned int k;
      unsigned int 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 {
      unsigned int 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 - (unsigned int)(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 - (unsigned int)(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 - (unsigned int)(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 - (unsigned int)(token - linebuf)) +
      1;
    command->type = COMMAND_O;

    return 1;
  }

  return 0;
}

typedef struct {
  unsigned int pos;
  unsigned int len;
} LineInfo;

static int is_line_ending(const char *p, unsigned int i, unsigned int 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;
}

int tinyobj_parse_obj(tinyobj_attrib_t *attrib, tinyobj_shape_t **shapes,
                      unsigned int *num_shapes, tinyobj_material_t **materials_out,
                      unsigned int *num_materials_out, const char *buf, unsigned int len,
                      unsigned int flags) {
  LineInfo *line_infos = NULL;
  Command *commands = NULL;
  unsigned int num_lines = 0;

  unsigned int num_v = 0;
  unsigned int num_vn = 0;
  unsigned int num_vt = 0;
  unsigned int num_f = 0;
  unsigned int num_faces = 0;

  int mtllib_line_index = -1;

  tinyobj_material_t *materials = NULL;
  unsigned int num_materials = 0;

  hash_table_t material_table;

  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. Find '\n' and create line data. */
  {
    unsigned int i;
    unsigned int end_idx = len;
    unsigned int prev_pos = 0;
    unsigned int line_no = 0;
    unsigned int 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 > 0) {
        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;
         
// ---- QUICK BUG FIX : https://github.com/raysan5/raylib/issues/3473
        if ( i > 0 && buf[i-1] == '\r' ) line_infos[line_no].len--;
// --------

        prev_pos = i + 1;
        line_no++;
      }
    }
    if (end_idx - last_line_ending > 0) {
      line_infos[line_no].pos = prev_pos;
      line_infos[line_no].len = end_idx - 1 - last_line_ending;
    }
  }

  commands = (Command *)TINYOBJ_MALLOC(sizeof(Command) * num_lines); 

  create_hash_table(HASH_TABLE_DEFAULT_SIZE, &material_table);

  /* 2. parse each line */
  {
    unsigned int 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 exits) */
  if (mtllib_line_index >= 0 && commands[mtllib_line_index].mtllib_name &&
      commands[mtllib_line_index].mtllib_name_len > 0) {
    char *filename = my_strndup(commands[mtllib_line_index].mtllib_name,
                                commands[mtllib_line_index].mtllib_name_len);

    int ret = tinyobj_parse_and_index_mtl_file(&materials, &num_materials, filename, &material_table);

    if (ret != TINYOBJ_SUCCESS) {
      /* warning. */
      fprintf(stderr, "TINYOBJ: Failed to parse material file '%s': %d\n", filename, ret);
    }

    TINYOBJ_FREE(filename);

  }

  /* Construct attributes */

  {
    unsigned int v_count = 0;
    unsigned int n_count = 0;
    unsigned int t_count = 0;
    unsigned int f_count = 0;
    unsigned int face_count = 0;
    int material_id = -1; /* -1 = default unknown material. */
    unsigned int 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->face_num_verts = (int *)TINYOBJ_MALLOC(sizeof(int) * num_faces);
    
    attrib->num_faces = (unsigned int)num_faces;
    attrib->num_face_num_verts = (unsigned int)num_f;
    
    attrib->material_ids = (int *)TINYOBJ_MALLOC(sizeof(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) {
        unsigned int 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;
    unsigned int i = 0;
    unsigned int n = 0;
    unsigned int 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) {
      unsigned int 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, unsigned int num_shapes) {
  unsigned int 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,
                            unsigned int num_materials) {
  unsigned int 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 */

#endif /* TINOBJ_LOADER_C_H_ */
