/* * 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 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 #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 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