From 21edd2596ff657fc2de61e8848e74cf3c5c9ef01 Mon Sep 17 00:00:00 2001 From: grm Date: Fri, 13 Mar 2026 22:05:58 +0200 Subject: not sure --- lib/fast_obj.h | 1564 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1564 insertions(+) create mode 100644 lib/fast_obj.h (limited to 'lib/fast_obj.h') diff --git a/lib/fast_obj.h b/lib/fast_obj.h new file mode 100644 index 0000000..b077a5c --- /dev/null +++ b/lib/fast_obj.h @@ -0,0 +1,1564 @@ +/* + * fast_obj + * + * Version 1.2 + * + * 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 2 +#define FAST_OBJ_VERSION ((FAST_OBJ_VERSION_MAJOR << 8) | FAST_OBJ_VERSION_MINOR) + +#include + + +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 maps */ + fastObjTexture map_Ka; + fastObjTexture map_Kd; + fastObjTexture map_Ks; + fastObjTexture map_Ke; + fastObjTexture map_Kt; + fastObjTexture map_Ns; + fastObjTexture map_Ni; + fastObjTexture map_d; + fastObjTexture 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; + + +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; + + /* 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 +#include + +#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 +void* file_open(const char* path, void* user_data) +{ + (void)(user_data); + return fopen(path, "rb"); +} + + +static +void file_close(void* file, void* user_data) +{ + FILE* f; + (void)(user_data); + + f = (FILE*)(file); + fclose(f); +} + + +static +size_t file_read(void* file, void* dst, size_t bytes, void* user_data) +{ + FILE* f; + (void)(user_data); + + f = (FILE*)(file); + return fread(dst, 1, bytes, f); +} + + +static +unsigned long file_size(void* file, void* user_data) +{ + FILE* f; + long p; + long n; + (void)(user_data); + + f = (FILE*)(file); + + p = ftell(f); + fseek(f, 0, SEEK_END); + n = ftell(f); + fseek(f, p, SEEK_SET); + + if (n > 0) + return (unsigned long)(n); + else + return 0; +} + + +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 = map_default(); + mtl.map_Kd = map_default(); + mtl.map_Ks = map_default(); + mtl.map_Ke = map_default(); + mtl.map_Kt = map_default(); + mtl.map_Ns = map_default(); + mtl.map_Ni = map_default(); + mtl.map_d = map_default(); + mtl.map_bump = map_default(); + + 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 exists, 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) +{ + map_clean(&mtl->map_Ka); + map_clean(&mtl->map_Kd); + map_clean(&mtl->map_Ks); + map_clean(&mtl->map_Ke); + map_clean(&mtl->map_Kt); + map_clean(&mtl->map_Ns); + map_clean(&mtl->map_Ni); + map_clean(&mtl->map_d); + map_clean(&mtl->map_bump); + + 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, fastObjTexture* map) +{ + const char* s; + const char* e; + char* name; + char* path; + + ptr = skip_whitespace(ptr); + + /* Don't support options at present */ + if (*ptr == '-') + return ptr; + + + /* Read name */ + s = ptr; + ptr = skip_name(ptr); + e = ptr; + + name = string_copy(s, e); + + path = string_concat(data->base, s, e); + string_fix_separators(path); + + map->name = name; + map->path = path; + + 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]); + + 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); + + memory_dealloc(m); +} + + +fastObjMesh* fast_obj_read(const char* path) +{ + fastObjCallbacks callbacks; + callbacks.file_open = file_open; + callbacks.file_close = file_close; + callbacks.file_read = file_read; + callbacks.file_size = file_size; + + return fast_obj_read_with_callbacks(path, &callbacks, 0); +} + + +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->objects = 0; + m->groups = 0; + + + /* Add dummy position/texcoord/normal */ + 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); + + + /* 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->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 -- cgit v1.2.3