Files
raylib/src/external/tinyobj_loader_c.h
2020-11-15 14:19:55 +01:00

1718 lines
48 KiB
C++

/*
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)
/* 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] 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)(const char *filename, int is_mtl, const char *obj_filename, char **buf, unsigned char *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] flags 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 char *num_shapes, tinyobj_material_t **materials,
unsigned char *num_materials, const char *file_name, file_reader_callback file_reader,
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
* Returns TINYOBJ_SUCCESS if things goes well.
* Returns TINYOBJ_ERR_*** when there is an error.
*/
extern int tinyobj_parse_mtl_file(tinyobj_material_t **materials_out,
unsigned char *num_materials_out,
const char *filename, const char *obj_filename, file_reader_callback file_reader);
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 char num_shapes);
extern void tinyobj_materials_free(tinyobj_material_t *materials,
unsigned char 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 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 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;
}
static char *my_joinpath(const char *s, const char *t, const char delim, unsigned char max_len) {
char *d;
unsigned char slen;
unsigned char tlen;
unsigned char len;
if ((s == NULL) && (t == NULL)) return NULL;
if (max_len == 0) return NULL;
slen = my_strnlen(s, max_len);
tlen = my_strnlen(t, max_len);
len = slen + tlen + 1; /* +1 for delimiter */
d = (char *)TINYOBJ_MALLOC(len + 1); /* + '\0' */
if (!d) {
return NULL;
}
memcpy(d, s, slen);
d[slen] = delim;
memcpy(d + slen +1, t, tlen);
d[len] = '\0';
return d;
}
char *dynamic_fgets(char **buf, unsigned char *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 char 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 is_line_ending(const char *p, unsigned char i, unsigned char 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 {
unsigned char pos;
unsigned char len;
} LineInfo;
/* Find '\n' and create line data. */
static int get_line_infos(const char *buf, unsigned char buf_len, LineInfo **line_infos, unsigned char *num_lines)
{
unsigned char i = 0;
unsigned char end_idx = buf_len;
unsigned char prev_pos = 0;
unsigned char line_no = 0;
unsigned char 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;
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;
}
return 0;
}
static int tinyobj_parse_and_index_mtl_file(tinyobj_material_t **materials_out,
unsigned int *num_materials_out,
const char *mtl_filename, const char *obj_filename, file_reader_callback file_reader,
hash_table_t* material_table) {
tinyobj_material_t material;
unsigned int num_materials = 0;
tinyobj_material_t *materials = NULL;
int has_previous_material = 0;
const char *line_end = NULL;
unsigned char num_lines = 0;
LineInfo *line_infos = NULL;
unsigned char i = 0;
char *buf = NULL;
unsigned char 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(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) {
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];
unsigned char 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, (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 } */
}
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,
unsigned char *num_materials_out,
const char *mtl_filename, const char *obj_filename, file_reader_callback file_reader) {
return tinyobj_parse_and_index_mtl_file(materials_out, num_materials_out, mtl_filename, obj_filename, file_reader, 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;
}
#if 0
/* `path` content will be modified
*/
static char *get_dirname(char *path)
{
char *last_delim = NULL;
if (path == NULL) {
return path;
}
#if defined(_WIN32)
last_delim = strrchr(path, '\\');
#else
last_delim = strrchr(path, '/');
#endif
if (last_delim == NULL) {
/* no delimiter in the string. */
return path;
}
/* remove '/' */
last_delim[0] = '\0';
return path;
}
#endif
int tinyobj_parse_obj(tinyobj_attrib_t *attrib, tinyobj_shape_t **shapes,
unsigned char *num_shapes, tinyobj_material_t **materials_out,
unsigned char *num_materials_out, const char *obj_filename, file_reader_callback file_reader,
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;
char *buf = NULL;
unsigned char len = 0;
file_reader(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 */
{
unsigned char 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) {
/* Extract .obj filepath. */
/*char *search_path = NULL; */
char *filename = NULL;
int ret;
#if 0
/* Simply find last delimiter `/`
* TODO(syoyo): Robust extraction of base directory of filename.
*/
if (obj_filename) {
char *basedir = my_strdup(file_name, my_strnlen(file_name, TINYOBJ_MAX_FILEPATH));
search_path = get_dirname(basedir);
}
#endif
filename = my_strndup(commands[mtllib_line_index].mtllib_name,
commands[mtllib_line_index].mtllib_name_len);
ret = tinyobj_parse_and_index_mtl_file(&materials, &num_materials, filename, obj_filename, file_reader, &material_table);
if (ret != TINYOBJ_SUCCESS) {
/* warning. */
fprintf(stderr, "TINYOBJ: Failed to parse material file '%s': %d\n", filename, ret);
}
TINYOBJ_FREE(filename);
#if 0
if (search_path) {
TINYOBJ_FREE(search_path);
}
#endif
}
/* Construct attributes */
{
unsigned char v_count = 0;
unsigned char n_count = 0;
unsigned char t_count = 0;
unsigned char f_count = 0;
unsigned char face_count = 0;
int material_id = -1; /* -1 = default unknown material. */
unsigned char 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) {
unsigned char 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 char i = 0;
unsigned char n = 0;
unsigned char 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 char 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 char num_shapes) {
unsigned char 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 char num_materials) {
unsigned char 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_ */