townengine/third-party/fast_obj/fast_obj.h

1520 lines
33 KiB
C
Raw Permalink Normal View History

2025-02-07 07:19:36 +00:00
/*
* fast_obj
*
* Version 1.3
*
* MIT License
*
* Copyright (c) 2018-2021 Richard Knight
*
* 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 FAST_OBJ_HDR
#define FAST_OBJ_HDR
#define FAST_OBJ_VERSION_MAJOR 1
#define FAST_OBJ_VERSION_MINOR 3
#define FAST_OBJ_VERSION ((FAST_OBJ_VERSION_MAJOR << 8) | FAST_OBJ_VERSION_MINOR)
#include <stdlib.h>
typedef struct
{
/* Texture name from .mtl file */
char* name;
/* Resolved path to texture */
char* path;
} fastObjTexture;
typedef struct
{
/* Material name */
char* name;
/* Parameters */
float Ka[3]; /* Ambient */
float Kd[3]; /* Diffuse */
float Ks[3]; /* Specular */
float Ke[3]; /* Emission */
float Kt[3]; /* Transmittance */
float Ns; /* Shininess */
float Ni; /* Index of refraction */
float Tf[3]; /* Transmission filter */
float d; /* Disolve (alpha) */
int illum; /* Illumination model */
/* Set for materials that don't come from the associated mtllib */
int fallback;
/* Texture map indices in fastObjMesh textures array */
unsigned int map_Ka;
unsigned int map_Kd;
unsigned int map_Ks;
unsigned int map_Ke;
unsigned int map_Kt;
unsigned int map_Ns;
unsigned int map_Ni;
unsigned int map_d;
unsigned int map_bump;
} fastObjMaterial;
/* Allows user override to bigger indexable array */
#ifndef FAST_OBJ_UINT_TYPE
#define FAST_OBJ_UINT_TYPE unsigned int
#endif
typedef FAST_OBJ_UINT_TYPE fastObjUInt;
typedef struct
{
fastObjUInt p;
fastObjUInt t;
fastObjUInt n;
} fastObjIndex;
typedef struct
{
/* Group name */
char* name;
/* Number of faces */
unsigned int face_count;
/* First face in fastObjMesh face_* arrays */
unsigned int face_offset;
/* First index in fastObjMesh indices array */
unsigned int index_offset;
} fastObjGroup;
/* Note: a dummy zero-initialized value is added to the first index
of the positions, texcoords, normals and textures arrays. Hence,
valid indices into these arrays start from 1, with an index of 0
indicating that the attribute is not present. */
typedef struct
{
/* Vertex data */
unsigned int position_count;
float* positions;
unsigned int texcoord_count;
float* texcoords;
unsigned int normal_count;
float* normals;
unsigned int color_count;
float* colors;
/* Face data: one element for each face */
unsigned int face_count;
unsigned int* face_vertices;
unsigned int* face_materials;
/* Index data: one element for each face vertex */
unsigned int index_count;
fastObjIndex* indices;
/* Materials */
unsigned int material_count;
fastObjMaterial* materials;
/* Texture maps */
unsigned int texture_count;
fastObjTexture* textures;
/* Mesh objects ('o' tag in .obj file) */
unsigned int object_count;
fastObjGroup* objects;
/* Mesh groups ('g' tag in .obj file) */
unsigned int group_count;
fastObjGroup* groups;
} fastObjMesh;
typedef struct
{
void* (*file_open)(const char* path, void* user_data);
void (*file_close)(void* file, void* user_data);
size_t (*file_read)(void* file, void* dst, size_t bytes, void* user_data);
unsigned long (*file_size)(void* file, void* user_data);
} fastObjCallbacks;
#ifdef __cplusplus
extern "C" {
#endif
fastObjMesh* fast_obj_read(const char* path);
fastObjMesh* fast_obj_read_with_callbacks(const char* path, const fastObjCallbacks* callbacks, void* user_data);
void fast_obj_destroy(fastObjMesh* mesh);
#ifdef __cplusplus
}
#endif
#endif
#ifdef FAST_OBJ_IMPLEMENTATION
#include <stdio.h>
#include <string.h>
#ifndef FAST_OBJ_REALLOC
#define FAST_OBJ_REALLOC realloc
#endif
#ifndef FAST_OBJ_FREE
#define FAST_OBJ_FREE free
#endif
#ifdef _WIN32
#define FAST_OBJ_SEPARATOR '\\'
#define FAST_OBJ_OTHER_SEP '/'
#else
#define FAST_OBJ_SEPARATOR '/'
#define FAST_OBJ_OTHER_SEP '\\'
#endif
/* Size of buffer to read into */
#define BUFFER_SIZE 65536
/* Max supported power when parsing float */
#define MAX_POWER 20
typedef struct
{
/* Final mesh */
fastObjMesh* mesh;
/* Current object/group */
fastObjGroup object;
fastObjGroup group;
/* Current material index */
unsigned int material;
/* Current line in file */
unsigned int line;
/* Base path for materials/textures */
char* base;
} fastObjData;
static const
double POWER_10_POS[MAX_POWER] =
{
1.0e0, 1.0e1, 1.0e2, 1.0e3, 1.0e4, 1.0e5, 1.0e6, 1.0e7, 1.0e8, 1.0e9,
1.0e10, 1.0e11, 1.0e12, 1.0e13, 1.0e14, 1.0e15, 1.0e16, 1.0e17, 1.0e18, 1.0e19,
};
static const
double POWER_10_NEG[MAX_POWER] =
{
1.0e0, 1.0e-1, 1.0e-2, 1.0e-3, 1.0e-4, 1.0e-5, 1.0e-6, 1.0e-7, 1.0e-8, 1.0e-9,
1.0e-10, 1.0e-11, 1.0e-12, 1.0e-13, 1.0e-14, 1.0e-15, 1.0e-16, 1.0e-17, 1.0e-18, 1.0e-19,
};
static void* memory_realloc(void* ptr, size_t bytes)
{
return FAST_OBJ_REALLOC(ptr, bytes);
}
static
void memory_dealloc(void* ptr)
{
FAST_OBJ_FREE(ptr);
}
#define array_clean(_arr) ((_arr) ? memory_dealloc(_array_header(_arr)), 0 : 0)
#define array_push(_arr, _val) (_array_mgrow(_arr, 1) ? ((_arr)[_array_size(_arr)++] = (_val), _array_size(_arr) - 1) : 0)
#define array_size(_arr) ((_arr) ? _array_size(_arr) : 0)
#define array_capacity(_arr) ((_arr) ? _array_capacity(_arr) : 0)
#define array_empty(_arr) (array_size(_arr) == 0)
#define _array_header(_arr) ((fastObjUInt*)(_arr)-2)
#define _array_size(_arr) (_array_header(_arr)[0])
#define _array_capacity(_arr) (_array_header(_arr)[1])
#define _array_ngrow(_arr, _n) ((_arr) == 0 || (_array_size(_arr) + (_n) >= _array_capacity(_arr)))
#define _array_mgrow(_arr, _n) (_array_ngrow(_arr, _n) ? (_array_grow(_arr, _n) != 0) : 1)
#define _array_grow(_arr, _n) (*((void**)&(_arr)) = array_realloc(_arr, _n, sizeof(*(_arr))))
static void* array_realloc(void* ptr, fastObjUInt n, fastObjUInt b)
{
fastObjUInt sz = array_size(ptr);
fastObjUInt nsz = sz + n;
fastObjUInt cap = array_capacity(ptr);
fastObjUInt ncap = cap + cap / 2;
fastObjUInt* r;
if (ncap < nsz)
ncap = nsz;
ncap = (ncap + 15) & ~15u;
r = (fastObjUInt*)(memory_realloc(ptr ? _array_header(ptr) : 0, (size_t)b * ncap + 2 * sizeof(fastObjUInt)));
if (!r)
return 0;
r[0] = sz;
r[1] = ncap;
return (r + 2);
}
static
char* string_copy(const char* s, const char* e)
{
size_t n;
char* p;
n = (size_t)(e - s);
p = (char*)(memory_realloc(0, n + 1));
if (p)
{
memcpy(p, s, n);
p[n] = '\0';
}
return p;
}
static
char* string_substr(const char* s, size_t a, size_t b)
{
return string_copy(s + a, s + b);
}
static
char* string_concat(const char* a, const char* s, const char* e)
{
size_t an;
size_t sn;
char* p;
an = a ? strlen(a) : 0;
sn = (size_t)(e - s);
p = (char*)(memory_realloc(0, an + sn + 1));
if (p)
{
if (a)
memcpy(p, a, an);
memcpy(p + an, s, sn);
p[an + sn] = '\0';
}
return p;
}
static
int string_equal(const char* a, const char* s, const char* e)
{
size_t an = strlen(a);
size_t sn = (size_t)(e - s);
return an == sn && memcmp(a, s, an) == 0;
}
static
void string_fix_separators(char* s)
{
while (*s)
{
if (*s == FAST_OBJ_OTHER_SEP)
*s = FAST_OBJ_SEPARATOR;
s++;
}
}
static
int is_whitespace(char c)
{
return (c == ' ' || c == '\t' || c == '\r');
}
static
int is_newline(char c)
{
return (c == '\n');
}
static
int is_digit(char c)
{
return (c >= '0' && c <= '9');
}
static
int is_exponent(char c)
{
return (c == 'e' || c == 'E');
}
static
const char* skip_name(const char* ptr)
{
const char* s = ptr;
while (!is_newline(*ptr))
ptr++;
while (ptr > s && is_whitespace(*(ptr - 1)))
ptr--;
return ptr;
}
static
const char* skip_whitespace(const char* ptr)
{
while (is_whitespace(*ptr))
ptr++;
return ptr;
}
static
const char* skip_line(const char* ptr)
{
while (!is_newline(*ptr++))
;
return ptr;
}
static
fastObjGroup object_default(void)
{
fastObjGroup object;
object.name = 0;
object.face_count = 0;
object.face_offset = 0;
object.index_offset = 0;
return object;
}
static
void object_clean(fastObjGroup* object)
{
memory_dealloc(object->name);
}
static
void flush_object(fastObjData* data)
{
/* Add object if not empty */
if (data->object.face_count > 0)
array_push(data->mesh->objects, data->object);
else
object_clean(&data->object);
/* Reset for more data */
data->object = object_default();
data->object.face_offset = array_size(data->mesh->face_vertices);
data->object.index_offset = array_size(data->mesh->indices);
}
static
fastObjGroup group_default(void)
{
fastObjGroup group;
group.name = 0;
group.face_count = 0;
group.face_offset = 0;
group.index_offset = 0;
return group;
}
static
void group_clean(fastObjGroup* group)
{
memory_dealloc(group->name);
}
static
void flush_group(fastObjData* data)
{
/* Add group if not empty */
if (data->group.face_count > 0)
array_push(data->mesh->groups, data->group);
else
group_clean(&data->group);
/* Reset for more data */
data->group = group_default();
data->group.face_offset = array_size(data->mesh->face_vertices);
data->group.index_offset = array_size(data->mesh->indices);
}
static
const char* parse_int(const char* ptr, int* val)
{
int sign;
int num;
if (*ptr == '-')
{
sign = -1;
ptr++;
}
else
{
sign = +1;
}
num = 0;
while (is_digit(*ptr))
num = 10 * num + (*ptr++ - '0');
*val = sign * num;
return ptr;
}
static
const char* parse_float(const char* ptr, float* val)
{
double sign;
double num;
double fra;
double div;
unsigned int eval;
const double* powers;
ptr = skip_whitespace(ptr);
switch (*ptr)
{
case '+':
sign = 1.0;
ptr++;
break;
case '-':
sign = -1.0;
ptr++;
break;
default:
sign = 1.0;
break;
}
num = 0.0;
while (is_digit(*ptr))
num = 10.0 * num + (double)(*ptr++ - '0');
if (*ptr == '.')
ptr++;
fra = 0.0;
div = 1.0;
while (is_digit(*ptr))
{
fra = 10.0 * fra + (double)(*ptr++ - '0');
div *= 10.0;
}
num += fra / div;
if (is_exponent(*ptr))
{
ptr++;
switch (*ptr)
{
case '+':
powers = POWER_10_POS;
ptr++;
break;
case '-':
powers = POWER_10_NEG;
ptr++;
break;
default:
powers = POWER_10_POS;
break;
}
eval = 0;
while (is_digit(*ptr))
eval = 10 * eval + (*ptr++ - '0');
num *= (eval >= MAX_POWER) ? 0.0 : powers[eval];
}
*val = (float)(sign * num);
return ptr;
}
static
const char* parse_vertex(fastObjData* data, const char* ptr)
{
unsigned int ii;
float v;
for (ii = 0; ii < 3; ii++)
{
ptr = parse_float(ptr, &v);
array_push(data->mesh->positions, v);
}
ptr = skip_whitespace(ptr);
if (!is_newline(*ptr))
{
/* Fill the colors array until it matches the size of the positions array */
for (ii = array_size(data->mesh->colors); ii < array_size(data->mesh->positions) - 3; ++ii)
{
array_push(data->mesh->colors, 1.0f);
}
for (ii = 0; ii < 3; ++ii)
{
ptr = parse_float(ptr, &v);
array_push(data->mesh->colors, v);
}
}
return ptr;
}
static
const char* parse_texcoord(fastObjData* data, const char* ptr)
{
unsigned int ii;
float v;
for (ii = 0; ii < 2; ii++)
{
ptr = parse_float(ptr, &v);
array_push(data->mesh->texcoords, v);
}
return ptr;
}
static
const char* parse_normal(fastObjData* data, const char* ptr)
{
unsigned int ii;
float v;
for (ii = 0; ii < 3; ii++)
{
ptr = parse_float(ptr, &v);
array_push(data->mesh->normals, v);
}
return ptr;
}
static
const char* parse_face(fastObjData* data, const char* ptr)
{
unsigned int count;
fastObjIndex vn;
int v;
int t;
int n;
ptr = skip_whitespace(ptr);
count = 0;
while (!is_newline(*ptr))
{
v = 0;
t = 0;
n = 0;
ptr = parse_int(ptr, &v);
if (*ptr == '/')
{
ptr++;
if (*ptr != '/')
ptr = parse_int(ptr, &t);
if (*ptr == '/')
{
ptr++;
ptr = parse_int(ptr, &n);
}
}
if (v < 0)
vn.p = (array_size(data->mesh->positions) / 3) - (fastObjUInt)(-v);
else if (v > 0)
vn.p = (fastObjUInt)(v);
else
return ptr; /* Skip lines with no valid vertex index */
if (t < 0)
vn.t = (array_size(data->mesh->texcoords) / 2) - (fastObjUInt)(-t);
else if (t > 0)
vn.t = (fastObjUInt)(t);
else
vn.t = 0;
if (n < 0)
vn.n = (array_size(data->mesh->normals) / 3) - (fastObjUInt)(-n);
else if (n > 0)
vn.n = (fastObjUInt)(n);
else
vn.n = 0;
array_push(data->mesh->indices, vn);
count++;
ptr = skip_whitespace(ptr);
}
array_push(data->mesh->face_vertices, count);
array_push(data->mesh->face_materials, data->material);
data->group.face_count++;
data->object.face_count++;
return ptr;
}
static
const char* parse_object(fastObjData* data, const char* ptr)
{
const char* s;
const char* e;
ptr = skip_whitespace(ptr);
s = ptr;
ptr = skip_name(ptr);
e = ptr;
flush_object(data);
data->object.name = string_copy(s, e);
return ptr;
}
static
const char* parse_group(fastObjData* data, const char* ptr)
{
const char* s;
const char* e;
ptr = skip_whitespace(ptr);
s = ptr;
ptr = skip_name(ptr);
e = ptr;
flush_group(data);
data->group.name = string_copy(s, e);
return ptr;
}
static
fastObjTexture map_default(void)
{
fastObjTexture map;
map.name = 0;
map.path = 0;
return map;
}
static
fastObjMaterial mtl_default(void)
{
fastObjMaterial mtl;
mtl.name = 0;
mtl.Ka[0] = 0.0;
mtl.Ka[1] = 0.0;
mtl.Ka[2] = 0.0;
mtl.Kd[0] = 1.0;
mtl.Kd[1] = 1.0;
mtl.Kd[2] = 1.0;
mtl.Ks[0] = 0.0;
mtl.Ks[1] = 0.0;
mtl.Ks[2] = 0.0;
mtl.Ke[0] = 0.0;
mtl.Ke[1] = 0.0;
mtl.Ke[2] = 0.0;
mtl.Kt[0] = 0.0;
mtl.Kt[1] = 0.0;
mtl.Kt[2] = 0.0;
mtl.Ns = 1.0;
mtl.Ni = 1.0;
mtl.Tf[0] = 1.0;
mtl.Tf[1] = 1.0;
mtl.Tf[2] = 1.0;
mtl.d = 1.0;
mtl.illum = 1;
mtl.fallback = 0;
mtl.map_Ka = 0;
mtl.map_Kd = 0;
mtl.map_Ks = 0;
mtl.map_Ke = 0;
mtl.map_Kt = 0;
mtl.map_Ns = 0;
mtl.map_Ni = 0;
mtl.map_d = 0;
mtl.map_bump = 0;
return mtl;
}
static
const char* parse_usemtl(fastObjData* data, const char* ptr)
{
const char* s;
const char* e;
unsigned int idx;
fastObjMaterial* mtl;
ptr = skip_whitespace(ptr);
/* Parse the material name */
s = ptr;
ptr = skip_name(ptr);
e = ptr;
/* Find an existing material with the same name */
idx = 0;
while (idx < array_size(data->mesh->materials))
{
mtl = &data->mesh->materials[idx];
if (mtl->name && string_equal(mtl->name, s, e))
break;
idx++;
}
/* If doesn't exist, create a default one with this name
Note: this case happens when OBJ doesn't have its MTL */
if (idx == array_size(data->mesh->materials))
{
fastObjMaterial new_mtl = mtl_default();
new_mtl.name = string_copy(s, e);
new_mtl.fallback = 1;
array_push(data->mesh->materials, new_mtl);
}
data->material = idx;
return ptr;
}
static
void map_clean(fastObjTexture* map)
{
memory_dealloc(map->name);
memory_dealloc(map->path);
}
static
void mtl_clean(fastObjMaterial* mtl)
{
memory_dealloc(mtl->name);
}
static
const char* read_mtl_int(const char* p, int* v)
{
return parse_int(p, v);
}
static
const char* read_mtl_single(const char* p, float* v)
{
return parse_float(p, v);
}
static
const char* read_mtl_triple(const char* p, float v[3])
{
p = read_mtl_single(p, &v[0]);
p = read_mtl_single(p, &v[1]);
p = read_mtl_single(p, &v[2]);
return p;
}
static
const char* read_map(fastObjData* data, const char* ptr, unsigned int* idx)
{
const char* s;
const char* e;
fastObjTexture* map;
ptr = skip_whitespace(ptr);
/* Don't support options at present */
if (*ptr == '-')
return ptr;
/* Read name */
s = ptr;
ptr = skip_name(ptr);
e = ptr;
/* Try to find an existing texture map with the same name */
*idx = 1; /* skip dummy at index 0 */
while (*idx < array_size(data->mesh->textures))
{
map = &data->mesh->textures[*idx];
if (map->name && string_equal(map->name, s, e))
break;
(*idx)++;
}
/* Add it to the texture array if it didn't already exist */
if (*idx == array_size(data->mesh->textures))
{
fastObjTexture new_map = map_default();
new_map.name = string_copy(s, e);
new_map.path = string_concat(data->base, s, e);
string_fix_separators(new_map.path);
array_push(data->mesh->textures, new_map);
}
return e;
}
static
int read_mtllib(fastObjData* data, void* file, const fastObjCallbacks* callbacks, void* user_data)
{
unsigned long n;
const char* s;
char* contents;
size_t l;
const char* p;
const char* e;
int found_d;
fastObjMaterial mtl;
/* Read entire file */
n = callbacks->file_size(file, user_data);
contents = (char*)(memory_realloc(0, n + 1));
if (!contents)
return 0;
l = callbacks->file_read(file, contents, n, user_data);
contents[l] = '\n';
mtl = mtl_default();
found_d = 0;
p = contents;
e = contents + l;
while (p < e)
{
p = skip_whitespace(p);
switch (*p)
{
case 'n':
p++;
if (p[0] == 'e' &&
p[1] == 'w' &&
p[2] == 'm' &&
p[3] == 't' &&
p[4] == 'l' &&
is_whitespace(p[5]))
{
/* Push previous material (if there is one) */
if (mtl.name)
{
array_push(data->mesh->materials, mtl);
mtl = mtl_default();
}
/* Read name */
p += 5;
while (is_whitespace(*p))
p++;
s = p;
p = skip_name(p);
mtl.name = string_copy(s, p);
}
break;
case 'K':
if (p[1] == 'a')
p = read_mtl_triple(p + 2, mtl.Ka);
else if (p[1] == 'd')
p = read_mtl_triple(p + 2, mtl.Kd);
else if (p[1] == 's')
p = read_mtl_triple(p + 2, mtl.Ks);
else if (p[1] == 'e')
p = read_mtl_triple(p + 2, mtl.Ke);
else if (p[1] == 't')
p = read_mtl_triple(p + 2, mtl.Kt);
break;
case 'N':
if (p[1] == 's')
p = read_mtl_single(p + 2, &mtl.Ns);
else if (p[1] == 'i')
p = read_mtl_single(p + 2, &mtl.Ni);
break;
case 'T':
if (p[1] == 'r')
{
float Tr;
p = read_mtl_single(p + 2, &Tr);
if (!found_d)
{
/* Ignore Tr if we've already read d */
mtl.d = 1.0f - Tr;
}
}
else if (p[1] == 'f')
p = read_mtl_triple(p + 2, mtl.Tf);
break;
case 'd':
if (is_whitespace(p[1]))
{
p = read_mtl_single(p + 1, &mtl.d);
found_d = 1;
}
break;
case 'i':
p++;
if (p[0] == 'l' &&
p[1] == 'l' &&
p[2] == 'u' &&
p[3] == 'm' &&
is_whitespace(p[4]))
{
p = read_mtl_int(p + 4, &mtl.illum);
}
break;
case 'm':
p++;
if (p[0] == 'a' &&
p[1] == 'p' &&
p[2] == '_')
{
p += 3;
if (*p == 'K')
{
p++;
if (is_whitespace(p[1]))
{
if (*p == 'a')
p = read_map(data, p + 1, &mtl.map_Ka);
else if (*p == 'd')
p = read_map(data, p + 1, &mtl.map_Kd);
else if (*p == 's')
p = read_map(data, p + 1, &mtl.map_Ks);
else if (*p == 'e')
p = read_map(data, p + 1, &mtl.map_Ke);
else if (*p == 't')
p = read_map(data, p + 1, &mtl.map_Kt);
}
}
else if (*p == 'N')
{
p++;
if (is_whitespace(p[1]))
{
if (*p == 's')
p = read_map(data, p + 1, &mtl.map_Ns);
else if (*p == 'i')
p = read_map(data, p + 1, &mtl.map_Ni);
}
}
else if (*p == 'd')
{
p++;
if (is_whitespace(*p))
p = read_map(data, p, &mtl.map_d);
}
else if ((p[0] == 'b' || p[0] == 'B') &&
p[1] == 'u' &&
p[2] == 'm' &&
p[3] == 'p' &&
is_whitespace(p[4]))
{
p = read_map(data, p + 4, &mtl.map_bump);
}
}
break;
case '#':
break;
}
p = skip_line(p);
}
/* Push final material */
if (mtl.name)
array_push(data->mesh->materials, mtl);
memory_dealloc(contents);
return 1;
}
static
const char* parse_mtllib(fastObjData* data, const char* ptr, const fastObjCallbacks* callbacks, void* user_data)
{
const char* s;
const char* e;
char* lib;
void* file;
ptr = skip_whitespace(ptr);
s = ptr;
ptr = skip_name(ptr);
e = ptr;
lib = string_concat(data->base, s, e);
if (lib)
{
string_fix_separators(lib);
file = callbacks->file_open(lib, user_data);
if (file)
{
read_mtllib(data, file, callbacks, user_data);
callbacks->file_close(file, user_data);
}
memory_dealloc(lib);
}
return ptr;
}
static
void parse_buffer(fastObjData* data, const char* ptr, const char* end, const fastObjCallbacks* callbacks, void* user_data)
{
const char* p;
p = ptr;
while (p != end)
{
p = skip_whitespace(p);
switch (*p)
{
case 'v':
p++;
switch (*p++)
{
case ' ':
case '\t':
p = parse_vertex(data, p);
break;
case 't':
p = parse_texcoord(data, p);
break;
case 'n':
p = parse_normal(data, p);
break;
default:
p--; /* roll p++ back in case *p was a newline */
}
break;
case 'f':
p++;
switch (*p++)
{
case ' ':
case '\t':
p = parse_face(data, p);
break;
default:
p--; /* roll p++ back in case *p was a newline */
}
break;
case 'o':
p++;
switch (*p++)
{
case ' ':
case '\t':
p = parse_object(data, p);
break;
default:
p--; /* roll p++ back in case *p was a newline */
}
break;
case 'g':
p++;
switch (*p++)
{
case ' ':
case '\t':
p = parse_group(data, p);
break;
default:
p--; /* roll p++ back in case *p was a newline */
}
break;
case 'm':
p++;
if (p[0] == 't' &&
p[1] == 'l' &&
p[2] == 'l' &&
p[3] == 'i' &&
p[4] == 'b' &&
is_whitespace(p[5]))
p = parse_mtllib(data, p + 5, callbacks, user_data);
break;
case 'u':
p++;
if (p[0] == 's' &&
p[1] == 'e' &&
p[2] == 'm' &&
p[3] == 't' &&
p[4] == 'l' &&
is_whitespace(p[5]))
p = parse_usemtl(data, p + 5);
break;
case '#':
break;
}
p = skip_line(p);
data->line++;
}
if (array_size(data->mesh->colors) > 0)
{
/* Fill the remaining slots in the colors array */
unsigned int ii;
for (ii = array_size(data->mesh->colors); ii < array_size(data->mesh->positions); ++ii)
{
array_push(data->mesh->colors, 1.0f);
}
}
}
void fast_obj_destroy(fastObjMesh* m)
{
unsigned int ii;
for (ii = 0; ii < array_size(m->objects); ii++)
object_clean(&m->objects[ii]);
for (ii = 0; ii < array_size(m->groups); ii++)
group_clean(&m->groups[ii]);
for (ii = 0; ii < array_size(m->materials); ii++)
mtl_clean(&m->materials[ii]);
for (ii = 0; ii < array_size(m->textures); ii++)
map_clean(&m->textures[ii]);
array_clean(m->positions);
array_clean(m->texcoords);
array_clean(m->normals);
array_clean(m->colors);
array_clean(m->face_vertices);
array_clean(m->face_materials);
array_clean(m->indices);
array_clean(m->objects);
array_clean(m->groups);
array_clean(m->materials);
array_clean(m->textures);
memory_dealloc(m);
}
fastObjMesh* fast_obj_read_with_callbacks(const char* path, const fastObjCallbacks* callbacks, void* user_data)
{
fastObjData data;
fastObjMesh* m;
void* file;
char* buffer;
char* start;
char* end;
char* last;
fastObjUInt read;
fastObjUInt bytes;
/* Check if callbacks are valid */
if(!callbacks)
return 0;
/* Open file */
file = callbacks->file_open(path, user_data);
if (!file)
return 0;
/* Empty mesh */
m = (fastObjMesh*)(memory_realloc(0, sizeof(fastObjMesh)));
if (!m)
return 0;
m->positions = 0;
m->texcoords = 0;
m->normals = 0;
m->colors = 0;
m->face_vertices = 0;
m->face_materials = 0;
m->indices = 0;
m->materials = 0;
m->textures = 0;
m->objects = 0;
m->groups = 0;
/* Add dummy position/texcoord/normal/texture */
array_push(m->positions, 0.0f);
array_push(m->positions, 0.0f);
array_push(m->positions, 0.0f);
array_push(m->texcoords, 0.0f);
array_push(m->texcoords, 0.0f);
array_push(m->normals, 0.0f);
array_push(m->normals, 0.0f);
array_push(m->normals, 1.0f);
array_push(m->textures, map_default());
/* Data needed during parsing */
data.mesh = m;
data.object = object_default();
data.group = group_default();
data.material = 0;
data.line = 1;
data.base = 0;
/* Find base path for materials/textures */
{
const char* sep1 = strrchr(path, FAST_OBJ_SEPARATOR);
const char* sep2 = strrchr(path, FAST_OBJ_OTHER_SEP);
/* Use the last separator in the path */
const char* sep = sep2 && (!sep1 || sep1 < sep2) ? sep2 : sep1;
if (sep)
data.base = string_substr(path, 0, sep - path + 1);
}
/* Create buffer for reading file */
buffer = (char*)(memory_realloc(0, 2 * BUFFER_SIZE * sizeof(char)));
if (!buffer)
return 0;
start = buffer;
for (;;)
{
/* Read another buffer's worth from file */
read = (fastObjUInt)(callbacks->file_read(file, start, BUFFER_SIZE, user_data));
if (read == 0 && start == buffer)
break;
/* Ensure buffer ends in a newline */
if (read < BUFFER_SIZE)
{
if (read == 0 || start[read - 1] != '\n')
start[read++] = '\n';
}
end = start + read;
if (end == buffer)
break;
/* Find last new line */
last = end;
while (last > buffer)
{
last--;
if (*last == '\n')
break;
}
/* Check there actually is a new line */
if (*last != '\n')
break;
last++;
/* Process buffer */
parse_buffer(&data, buffer, last, callbacks, user_data);
/* Copy overflow for next buffer */
bytes = (fastObjUInt)(end - last);
memmove(buffer, last, bytes);
start = buffer + bytes;
}
/* Flush final object/group */
flush_object(&data);
object_clean(&data.object);
flush_group(&data);
group_clean(&data.group);
m->position_count = array_size(m->positions) / 3;
m->texcoord_count = array_size(m->texcoords) / 2;
m->normal_count = array_size(m->normals) / 3;
m->color_count = array_size(m->colors) / 3;
m->face_count = array_size(m->face_vertices);
m->index_count = array_size(m->indices);
m->material_count = array_size(m->materials);
m->texture_count = array_size(m->textures);
m->object_count = array_size(m->objects);
m->group_count = array_size(m->groups);
/* Clean up */
memory_dealloc(buffer);
memory_dealloc(data.base);
callbacks->file_close(file, user_data);
return m;
}
#endif