From a206e73a7ce254a0f9dcc2e258b43438629225f9 Mon Sep 17 00:00:00 2001 From: Valerio Santinelli Date: Tue, 28 Nov 2023 16:03:08 +0100 Subject: [PATCH] Updated 3D model loading --- src/binocle/CMakeLists.txt | 1 + src/binocle/core/binocle_fs.c | 45 ++ src/binocle/core/binocle_fs.h | 18 + src/binocle/core/binocle_model.c | 76 ++- src/binocle/core/binocle_model.h | 12 +- src/deps/cute_path/cute_path.h | 545 +++++++++++++++++++ src/deps/tiniobj_loader_c/tinyobj_loader_c.h | 373 +++++++++---- 7 files changed, 954 insertions(+), 116 deletions(-) create mode 100644 src/deps/cute_path/cute_path.h diff --git a/src/binocle/CMakeLists.txt b/src/binocle/CMakeLists.txt index ef0429ae..0ebfe8ef 100644 --- a/src/binocle/CMakeLists.txt +++ b/src/binocle/CMakeLists.txt @@ -1,5 +1,6 @@ include_directories(${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_SOURCE_DIR}/src/deps + ${CMAKE_SOURCE_DIR}/src/deps/cute_path ${CMAKE_SOURCE_DIR}/src/deps/glew ${CMAKE_SOURCE_DIR}/src/deps/freetype ${CMAKE_SOURCE_DIR}/src/deps/miniaudio diff --git a/src/binocle/core/binocle_fs.c b/src/binocle/core/binocle_fs.c index 2c3d27d5..83eb5d98 100644 --- a/src/binocle/core/binocle_fs.c +++ b/src/binocle/core/binocle_fs.c @@ -9,6 +9,9 @@ #include #include +#define CUTE_PATH_IMPLEMENTATION +#include + binocle_fs binocle_fs_new() { binocle_fs res = {0}; return res; @@ -175,4 +178,46 @@ bool binocle_fs_load_text_file(const char *filename, char **buffer, size_t *size binocle_fs_close(file); return true; +} + +void binocle_fs_get_directory(const char *filename, char *path, int *length) { + *length = path_pop(filename, path, NULL); +} + +void binocle_fs_path_without_extension(char* myStr, char extSep, char pathSep, char *retStr) { + char *lastExt, *lastPath; + + // Error checks and allocate string. + + if (myStr == NULL) return; + if (retStr == NULL) return; + + // Make a copy and find the relevant characters. + + strcpy (retStr, myStr); + lastExt = strrchr (retStr, extSep); + lastPath = (pathSep == 0) ? NULL : strrchr (retStr, pathSep); + + // If it has an extension separator. + + if (lastExt != NULL) { + // and it's to the right of the path separator. + + if (lastPath != NULL) { + if (lastPath < lastExt) { + // then remove it. + + *lastExt = '\0'; + } + } else { + // Has extension separator with no path separator. + + *lastExt = '\0'; + } + } +} + +void binocle_fs_get_filename(const char *full_path, char *filename, int *length) { + char path[CUTE_PATH_MAX_PATH]; + *length = path_pop(full_path, path, filename); } \ No newline at end of file diff --git a/src/binocle/core/binocle_fs.h b/src/binocle/core/binocle_fs.h index 4c257b2f..62551462 100644 --- a/src/binocle/core/binocle_fs.h +++ b/src/binocle/core/binocle_fs.h @@ -38,5 +38,23 @@ void binocle_fs_close(binocle_fs_file file); bool binocle_fs_read(binocle_fs_file file, void **buffer, size_t *size); bool binocle_fs_load_binary_file(const char *filename, void **buffer, size_t *size); bool binocle_fs_load_text_file(const char *filename, char **buffer, size_t *size); +/// \brief Gets the directory part of the filename+path given as input +/// \param filename the full path including the filename +/// \param path the path without the filename and with no trailing slash +/// \param length the length of the returned path +void binocle_fs_get_directory(const char *filename, char *path, int *length); + +/// \brief Return the file name given a full path +/// \param full_path the full path +/// \param filename the filename extracted from the path +/// \param length the length of the filename; +void binocle_fs_get_filename(const char *full_path, char *filename, int *length); + +/// \brief Returns the filename without the extension +/// \param myStr the full filename (it can include the path) +/// \param extSep the separator character, e.g. "." +/// \param pathSep the path separator, e.g. "/" +/// \param retStr the filename without extension (including path if provided) +void binocle_fs_path_without_extension(char* myStr, char extSep, char pathSep, char *retStr); #endif //BINOCLE_FS_H diff --git a/src/binocle/core/binocle_model.c b/src/binocle/core/binocle_model.c index 3098d31b..34ce9cbe 100644 --- a/src/binocle/core/binocle_model.c +++ b/src/binocle/core/binocle_model.c @@ -13,12 +13,35 @@ #include "backend/binocle_material.h" #include "binocle_image.h" #include "backend/binocle_vpct.h" +#include "cute_path/cute_path.h" -binocle_model binocle_model_load_obj(char *filename, char *mtl_filename) { +void binocle_model_file_reader_callback(void *ctx, const char *filename, int is_mtl, const char *obj_filename, char **buf, size_t *len) { + binocle_model_buffers *buffers = (binocle_model_buffers *)ctx; + if (is_mtl) { + *buf = buffers->mtl.buffer; + *len = buffers->mtl.buffer_length; + } else { + *buf = buffers->obj.buffer; + *len = buffers->obj.buffer_length; + } + if (!binocle_sdl_load_binary_file(filename, buf, len)) { + *len = 0; + binocle_log_error("OBJ loader: cannot open file %s", filename); + return; + } +} + +void binocle_model_get_mtl_filename(const char *filename, char *mtl_filename, int *length) { + char name[CUTE_PATH_MAX_PATH]; + + binocle_fs_path_without_extension(filename, '.', '/', name); + sprintf(mtl_filename, "%s.mtl", name); +} + +binocle_model binocle_model_load_obj(char *filename) { binocle_model model = {0}; - char *buffer = NULL; - size_t buffer_length = 0; + binocle_model_buffers buffers = {0}; tinyobj_attrib_t attrib; tinyobj_shape_t *meshes = NULL; size_t mesh_count = 0; @@ -29,12 +52,7 @@ binocle_model binocle_model_load_obj(char *filename, char *mtl_filename) { smooth_vertex_normals = kh_init(spatial_binocle_smooth_vertex_t); - if (!binocle_sdl_load_binary_file(filename, &buffer, &buffer_length)) { - binocle_log_error("Cannot open OBJ file %s", filename); - return model; - } - - int res = tinyobj_parse_obj(&attrib, &meshes, &mesh_count, &materials, &material_count, buffer, buffer_length, flags); + int res = tinyobj_parse_obj(&attrib, &meshes, &mesh_count, &materials, &material_count, filename, binocle_model_file_reader_callback, &buffers, flags); if (res != TINYOBJ_SUCCESS) { binocle_log_warning("Cannot load model data for %s", filename); @@ -42,7 +60,12 @@ binocle_model binocle_model_load_obj(char *filename, char *mtl_filename) { binocle_log_info("Loaded model data for %s: %i meshes and %i materials", filename, mesh_count, material_count); } - res = tinyobj_parse_mtl_file(&materials, &material_count, mtl_filename); + char mtl_filename[CUTE_PATH_MAX_PATH]; + int mtl_filename_length = 0; + + binocle_model_get_mtl_filename(filename, (char *) &mtl_filename, &mtl_filename_length); + + res = tinyobj_parse_mtl_file(&materials, &material_count, mtl_filename, filename, binocle_model_file_reader_callback, &buffers); if (res != TINYOBJ_SUCCESS) { binocle_log_warning("Cannot load material data for %s", mtl_filename); } else { @@ -53,7 +76,7 @@ binocle_model binocle_model_load_obj(char *filename, char *mtl_filename) { binocle_log_info("# of normals = %d", attrib.num_normals); binocle_log_info("# of texcoords = %d", attrib.num_texcoords); - model.mesh_count = 1; //mesh_count; + model.mesh_count = mesh_count; model.meshes = malloc(model.mesh_count * sizeof(binocle_mesh)); memset(model.meshes, 0, model.mesh_count * sizeof(binocle_model)); model.material_count = material_count; @@ -283,18 +306,43 @@ binocle_model binocle_model_load_obj(char *filename, char *mtl_filename) { for (int i = 0 ; i < material_count ; i++) { binocle_material *mat = binocle_material_new(); model.materials[i] = mat; - // We use diffuse only atm if (materials[i].diffuse_texname != NULL) { sg_image image = binocle_image_load(materials[i].diffuse_texname); model.materials[i]->albedo_texture = image; - // TODO: fix this now that we no longer have default shaders - //model.materials[i]->shader = &binocle_shader_defaults[BINOCLE_SHADER_DEFAULT_FLAT]; } + + if (materials[i].bump_texname != NULL) { + sg_image image = binocle_image_load(materials[i].bump_texname); + model.materials[i]->normal_texture = image; + } + + if (materials[i].ambient_texname != NULL) { + sg_image image = binocle_image_load(materials[i].ambient_texname); + model.materials[i]->ao_texture = image; + } + + if (materials[i].metallic_texname != NULL) { + sg_image image = binocle_image_load(materials[i].metallic_texname); + model.materials[i]->metallic_texture = image; + } + + if (materials[i].specular_highlight_texname != NULL) { + sg_image image = binocle_image_load(materials[i].specular_highlight_texname); + model.materials[i]->roughness_texture = image; + } + } + + // Fix up the materials of each mesh. + // TODO: check that we are assigning the right material and not just the first one. + for (int i = 0 ; i < mesh_count ; i++) { + model.meshes[i].material = model.materials[model.mesh_materials[i]]; } tinyobj_attrib_free(&attrib); tinyobj_shapes_free(meshes, mesh_count); tinyobj_materials_free(materials, material_count); + SDL_free(buffers.obj.buffer); + SDL_free(buffers.mtl.buffer); binocle_log_info("Model %s loaded successfully", filename); diff --git a/src/binocle/core/binocle_model.h b/src/binocle/core/binocle_model.h index 80798d18..03e1133a 100644 --- a/src/binocle/core/binocle_model.h +++ b/src/binocle/core/binocle_model.h @@ -36,12 +36,22 @@ typedef struct binocle_model { uint64_t *mesh_materials; // mesh materials } binocle_model; +typedef struct binocle_model_buffer { + char *buffer; + size_t buffer_length; +} binocle_model_buffer; + +typedef struct binocle_model_buffers { + binocle_model_buffer obj; + binocle_model_buffer mtl; +} binocle_model_buffers; + typedef uint64_t binocle_model_smooth_vertex_key_t; #define binocle_model_smooth_vertex_hash_func(key) (binocle_model_smooth_vertex_key_t)(key) #define binocle_model_smooth_vertex_equal(a, b) ((a) == (b)) KHASH_INIT(spatial_binocle_smooth_vertex_t, binocle_model_smooth_vertex_key_t, kmVec3, 1, binocle_model_smooth_vertex_hash_func, binocle_model_smooth_vertex_equal) -binocle_model binocle_model_load_obj(char *filename, char *mtl_filename); +binocle_model binocle_model_load_obj(char *filename); void binocle_model_compute_normal(float N[3], float v0[3], float v1[3], float v2[3]); void binocle_model_compute_smoothing_normals(tinyobj_attrib_t *attrib, tinyobj_shape_t *shape, khash_t(spatial_binocle_smooth_vertex_t) *smooth_vertex_normals); diff --git a/src/deps/cute_path/cute_path.h b/src/deps/cute_path/cute_path.h new file mode 100644 index 00000000..013649d9 --- /dev/null +++ b/src/deps/cute_path/cute_path.h @@ -0,0 +1,545 @@ +/* + ------------------------------------------------------------------------------ + Licensing information can be found at the end of the file. + ------------------------------------------------------------------------------ + + cute_path.h - v1.01 + + To create implementation (the function definitions) + #define CUTE_PATH_IMPLEMENTATION + in *one* C/CPP file (translation unit) that includes this file + + SUMMARY: + + Collection of c-string manipulation functions for dealing with common file-path + operations. More or less a less-featuerd replacement for Shlwapi.h path functions + on Windows. + + Performs no dynamic memory management and has no external dependencies (other than + some crt funcs). + + Revision history: + 1.0 (11/01/2017) initial release + 1.01 (11/10/2017) path_compact, path_pop bugfixes +*/ + +/* + Contributors: + sro5h 1.01 - path_compact, path_pop bugfixes +*/ + +#if !defined(CUTE_PATH_H) + +#define CUTE_PATH_MAX_PATH 1024 +#define CUTE_PATH_MAX_EXT 32 + +// Copies path to out, but not the extension. Places a nul terminator in out. +// Returns the length of the string in out, excluding the nul byte. +// Length of copied output can be up to CUTE_PATH_MAX_PATH. Can also copy the file +// extension into ext, up to CUTE_PATH_MAX_EXT. +#if !defined(__cplusplus) +int path_pop_ext(const char* path, char* out, char* ext); +#else +int path_pop_ext(const char* path, char* out = 0, char* ext = 0); +#endif + +// Copies path to out, but excludes the final file or folder from the output. +// If the final file or folder contains a period, the file or folder will +// still be appropriately popped. If the path contains only one file or folder, +// the output will contain a period representing the current directory. All +// outputs are nul terminated. +// Returns the length of the string in out, excluding the nul byte. +// Length of copied output can be up to CUTE_PATH_MAX_PATH. +// Optionally stores the popped filename in pop. pop can be NULL. +// out can also be NULL. +#if !defined(__cplusplus) +int path_pop(const char* path, char* out, char* pop); +#else +int path_pop(const char* path, char* out = 0, char* pop = 0); +#endif + +// Concatenates path_b onto the end of path_a. Will not write beyond max_buffer_length. +// Places a single '/' character between path_a and path_b. Does no other "intelligent" +// manipulation of path_a and path_b; it's a basic strcat kind of function. +void path_concat(const char* path_a, const char* path_b, char* out, int max_buffer_length); + +// Copies the name of the folder the file sits in (but not the entire path) to out. Will +// not write beyond max_buffer_length. Length of copied output can be up to CUTE_PATH_MAX_PATH. +// path contains the full path to the file in question. +// Returns 0 for inputs of "", "." or ".." as the path, 1 otherwise (success). +int path_name_of_folder_im_in(const char* path, char* out); + +// Shrinks the path to the desired length n, the out buffer will never be bigger than +// n + 1. Places three '.' between the last part of the path and the first part that +// will be shortened to fit. If the last part is too long to fit in a string of length n, +// the last part will be shortened to fit and three '.' will be added in front & back. +int path_compact(const char* path, char* out, int n); + +// Some useful (but not yet implemented) functions +/* + int path_root(const char* path, char* out); +*/ + +#define CUTE_PATH_UNIT_TESTS 1 +void path_do_unit_tests(); + +#define CUTE_PATH_H +#endif + +#ifdef CUTE_PATH_IMPLEMENTATION +#ifndef CUTE_PATH_IMPLEMENTATION_ONCE +#define CUTE_PATH_IMPLEMENTATION_ONCE + +#ifdef _WIN32 + + #if !defined(_CRT_SECURE_NO_WARNINGS) + #define _CRT_SECURE_NO_WARNINGS + #endif + +#endif + +#include // strncpy, strncat, strlen +#define CUTE_PATH_STRNCPY strncpy +#define CUTE_PATH_STRNCAT strncat +#define CUTE_PATH_STRLEN strlen + +int path_is_slash(char c) +{ + return (c == '/') | (c == '\\'); +} + +int path_pop_ext(const char* path, char* out, char* ext) +{ + int initial_skipped_periods = 0; + while (*path == '.') + { + ++path; + ++initial_skipped_periods; + } + + const char* period = path; + char c; + while ((c = *period++)) if (c == '.') break; + + int has_period = c == '.'; + int len = (int)(period - path) - 1 + initial_skipped_periods; + if (len > CUTE_PATH_MAX_PATH - 1) len = CUTE_PATH_MAX_PATH - 1; + + if (out) + { + CUTE_PATH_STRNCPY(out, path - initial_skipped_periods, len); + out[len] = 0; + } + + if (ext) + { + if (has_period) + { + CUTE_PATH_STRNCPY(ext, path - initial_skipped_periods + len + 1, CUTE_PATH_MAX_EXT); + } + else + { + ext[0] = 0; + } + } + return len; +} + +int path_pop(const char* path, char* out, char* pop) +{ + const char* original = path; + int total_len = 0; + while (*path) + { + ++total_len; + ++path; + } + + // ignore trailing slash from input path + if (path_is_slash(*(path - 1))) + { + --path; + total_len -= 1; + } + + int pop_len = 0; // length of substring to be popped + while (!path_is_slash(*--path) && pop_len != total_len) + ++pop_len; + int len = total_len - pop_len; // length to copy + + // don't ignore trailing slash if it is the first character + if (len > 1) + { + len -= 1; + } + + if (len > 0) + { + if (out) + { + CUTE_PATH_STRNCPY(out, original, len); + out[len] = 0; + } + + if (pop) + { + CUTE_PATH_STRNCPY(pop, path + 1, pop_len); + pop[pop_len] = 0; + } + + return len; + } + + else + { + if (out) + { + out[0] = '.'; + out[1] = 0; + } + if (pop) *pop = 0; + return 1; + } +} + +static int path_strncpy(char* dst, const char* src, int n, int max) +{ + int c; + + do + { + if (n >= max - 1) + { + dst[max - 1] = 0; + break; + } + c = *src++; + dst[n] = c; + ++n; + } while (c); + + return n; +} + +void path_concat(const char* path_a, const char* path_b, char* out, int max_buffer_length) +{ + int n = path_strncpy(out, path_a, 0, max_buffer_length); + n = path_strncpy(out, "/", n - 1, max_buffer_length); + path_strncpy(out, path_b, n - 1, max_buffer_length); +} + +int path_name_of_folder_im_in(const char* path, char* out) +{ + // return failure for empty strings and "." or ".." + if (!*path || (*path == '.' && CUTE_PATH_STRLEN(path) < 3)) return 0; + int len = path_pop(path, out, NULL); + int has_slash = 0; + for (int i = 0; out[i]; ++i) + { + if (path_is_slash(out[i])) + { + has_slash = 1; + break; + } + } + + if (has_slash) + { + int n = path_pop(out, NULL, NULL) + 1; + len -= n; + CUTE_PATH_STRNCPY(out, path + n, len); + } + + else CUTE_PATH_STRNCPY(out, path, len); + out[len] = 0; + return 1; +} + +int path_compact(const char* path, char* out, int n) +{ + if (n <= 6) return 0; + + const char* sep = "..."; + const int seplen = strlen(sep); + + int pathlen = strlen(path); + out[0] = 0; + + if (pathlen <= n) + { + CUTE_PATH_STRNCPY(out, path, pathlen); + out[pathlen] = 0; + return pathlen; + } + + // Find last path separator + // Ignores the last character as it could be a path separator + int i = pathlen - 1; + do + { + --i; + } while (!path_is_slash(path[i]) && i > 0); + + const char* back = path + i; + int backlen = strlen(back); + + // No path separator was found or the first character was one + if (pathlen == backlen) + { + CUTE_PATH_STRNCPY(out, path, n - seplen); + out[n - seplen] = 0; + CUTE_PATH_STRNCAT(out, sep, seplen + 1); + return n; + } + + // Last path part with separators in front equals n + if (backlen == n - seplen) + { + CUTE_PATH_STRNCPY(out, sep, seplen + 1); + CUTE_PATH_STRNCAT(out, back, backlen); + return n; + } + + // Last path part with separators in front is too long + if (backlen > n - seplen) + { + CUTE_PATH_STRNCPY(out, sep, seplen + 1); + CUTE_PATH_STRNCAT(out, back, n - (2 * seplen)); + CUTE_PATH_STRNCAT(out, sep, seplen); + return n; + } + + int remaining = n - backlen - seplen; + + CUTE_PATH_STRNCPY(out, path, remaining); + out[remaining] = 0; + CUTE_PATH_STRNCAT(out, sep, seplen); + CUTE_PATH_STRNCAT(out, back, backlen); + + return n; +} + +#if CUTE_PATH_UNIT_TESTS + + #include + + #define CUTE_PATH_STRCMP strcmp + #define CUTE_PATH_EXPECT(X) do { if (!(X)) printf("Failed tinyfiles.h unit test at line %d of file %s.\n", __LINE__, __FILE__); } while (0) + + void path_do_unit_tests() + { + char out[CUTE_PATH_MAX_PATH]; + char pop[CUTE_PATH_MAX_PATH]; + char ext[CUTE_PATH_MAX_PATH]; + int n; + + const char* path = "../root/file.ext"; + path_pop_ext(path, out, ext); + CUTE_PATH_EXPECT(!CUTE_PATH_STRCMP(out, "../root/file")); + CUTE_PATH_EXPECT(!CUTE_PATH_STRCMP(ext, "ext")); + + path_pop(path, out, pop); + CUTE_PATH_EXPECT(!CUTE_PATH_STRCMP(out, "../root")); + CUTE_PATH_EXPECT(!CUTE_PATH_STRCMP(pop, "file.ext")); + + path = "../root/file"; + path_pop_ext(path, out, ext); + CUTE_PATH_EXPECT(!CUTE_PATH_STRCMP(out, "../root/file")); + CUTE_PATH_EXPECT(!CUTE_PATH_STRCMP(ext, "")); + + path_pop(path, out, pop); + CUTE_PATH_EXPECT(!CUTE_PATH_STRCMP(out, "../root")); + CUTE_PATH_EXPECT(!CUTE_PATH_STRCMP(pop, "file")); + + path = "../root/"; + path_pop_ext(path, out, ext); + CUTE_PATH_EXPECT(!CUTE_PATH_STRCMP(out, "../root/")); + CUTE_PATH_EXPECT(!CUTE_PATH_STRCMP(ext, "")); + + path_pop(path, out, pop); + CUTE_PATH_EXPECT(!CUTE_PATH_STRCMP(out, "..")); + CUTE_PATH_EXPECT(!CUTE_PATH_STRCMP(pop, "root")); + + path = "../root"; + path_pop_ext(path, out, ext); + CUTE_PATH_EXPECT(!CUTE_PATH_STRCMP(out, "../root")); + CUTE_PATH_EXPECT(!CUTE_PATH_STRCMP(ext, "")); + + path_pop(path, out, pop); + CUTE_PATH_EXPECT(!CUTE_PATH_STRCMP(out, "..")); + CUTE_PATH_EXPECT(!CUTE_PATH_STRCMP(pop, "root")); + + path = "/file"; + path_pop_ext(path, out, ext); + CUTE_PATH_EXPECT(!CUTE_PATH_STRCMP(out, "/file")); + CUTE_PATH_EXPECT(!CUTE_PATH_STRCMP(ext, "")); + + path_pop(path, out, pop); + CUTE_PATH_EXPECT(!CUTE_PATH_STRCMP(out, "/")); + CUTE_PATH_EXPECT(!CUTE_PATH_STRCMP(pop, "file")); + + path = "../"; + path_pop_ext(path, out, ext); + CUTE_PATH_EXPECT(!CUTE_PATH_STRCMP(out, "../")); + CUTE_PATH_EXPECT(!CUTE_PATH_STRCMP(ext, "")); + + path_pop(path, out, pop); + CUTE_PATH_EXPECT(!CUTE_PATH_STRCMP(out, ".")); + CUTE_PATH_EXPECT(!CUTE_PATH_STRCMP(pop, "")); + + path = ".."; + path_pop_ext(path, out, ext); + CUTE_PATH_EXPECT(!CUTE_PATH_STRCMP(out, "..")); + CUTE_PATH_EXPECT(!CUTE_PATH_STRCMP(ext, "")); + + path_pop(path, out, pop); + CUTE_PATH_EXPECT(!CUTE_PATH_STRCMP(out, ".")); + CUTE_PATH_EXPECT(!CUTE_PATH_STRCMP(pop, "")); + + path = "."; + path_pop_ext(path, out, ext); + CUTE_PATH_EXPECT(!CUTE_PATH_STRCMP(out, ".")); + CUTE_PATH_EXPECT(!CUTE_PATH_STRCMP(ext, "")); + + path_pop(path, out, pop); + CUTE_PATH_EXPECT(!CUTE_PATH_STRCMP(out, ".")); + CUTE_PATH_EXPECT(!CUTE_PATH_STRCMP(pop, "")); + + path = ""; + path_pop_ext(path, out, ext); + CUTE_PATH_EXPECT(!CUTE_PATH_STRCMP(out, "")); + CUTE_PATH_EXPECT(!CUTE_PATH_STRCMP(ext, "")); + + path_pop(path, out, pop); + CUTE_PATH_EXPECT(!CUTE_PATH_STRCMP(out, ".")); + CUTE_PATH_EXPECT(!CUTE_PATH_STRCMP(pop, "")); + + path = "asdf/file.ext"; + path_name_of_folder_im_in(path, out); + CUTE_PATH_EXPECT(!CUTE_PATH_STRCMP(out, "asdf")); + + path = "asdf/lkjh/file.ext"; + path_name_of_folder_im_in(path, out); + CUTE_PATH_EXPECT(!CUTE_PATH_STRCMP(out, "lkjh")); + + path = "poiu/asdf/lkjh/file.ext"; + path_name_of_folder_im_in(path, out); + CUTE_PATH_EXPECT(!CUTE_PATH_STRCMP(out, "lkjh")); + + path = "poiu/asdf/lkjhqwer/file.ext"; + path_name_of_folder_im_in(path, out); + CUTE_PATH_EXPECT(!CUTE_PATH_STRCMP(out, "lkjhqwer")); + + path = "../file.ext"; + path_name_of_folder_im_in(path, out); + CUTE_PATH_EXPECT(!CUTE_PATH_STRCMP(out, "..")); + + path = "./file.ext"; + path_name_of_folder_im_in(path, out); + CUTE_PATH_EXPECT(!CUTE_PATH_STRCMP(out, ".")); + + path = ".."; + CUTE_PATH_EXPECT(!path_name_of_folder_im_in(path, out)); + + path = "."; + CUTE_PATH_EXPECT(!path_name_of_folder_im_in(path, out)); + + path = ""; + CUTE_PATH_EXPECT(!path_name_of_folder_im_in(path, out)); + + const char* path_a = "asdf"; + const char* path_b = "qwerzxcv"; + path_concat(path_a, path_b, out, CUTE_PATH_MAX_PATH); + CUTE_PATH_EXPECT(!CUTE_PATH_STRCMP(out, "asdf/qwerzxcv")); + + path_a = "path/owoasf.as.f.q.e.a"; + path_b = ".."; + path_concat(path_a, path_b, out, CUTE_PATH_MAX_PATH); + CUTE_PATH_EXPECT(!CUTE_PATH_STRCMP(out, "path/owoasf.as.f.q.e.a/..")); + + path_a = "a/b/c"; + path_b = "d/e/f/g/h/i"; + path_concat(path_a, path_b, out, CUTE_PATH_MAX_PATH); + CUTE_PATH_EXPECT(!CUTE_PATH_STRCMP(out, "a/b/c/d/e/f/g/h/i")); + + path = "/path/to/file.vim"; + n = path_compact(path, out, 17); + CUTE_PATH_EXPECT(!CUTE_PATH_STRCMP(out, "/path/to/file.vim")); + CUTE_PATH_EXPECT(n == CUTE_PATH_STRLEN(out)); + + path = "/path/to/file.vim"; + n = path_compact(path, out, 16); + CUTE_PATH_EXPECT(!CUTE_PATH_STRCMP(out, "/pat.../file.vim")); + CUTE_PATH_EXPECT(n == CUTE_PATH_STRLEN(out)); + + path = "/path/to/file.vim"; + n = path_compact(path, out, 12); + CUTE_PATH_EXPECT(!CUTE_PATH_STRCMP(out, ".../file.vim")); + CUTE_PATH_EXPECT(n == CUTE_PATH_STRLEN(out)); + + path = "/path/to/file.vim"; + n = path_compact(path, out, 11); + CUTE_PATH_EXPECT(!CUTE_PATH_STRCMP(out, ".../file...")); + CUTE_PATH_EXPECT(n == CUTE_PATH_STRLEN(out)); + + path = "longfile.vim"; + n = path_compact(path, out, 12); + CUTE_PATH_EXPECT(!CUTE_PATH_STRCMP(out, "longfile.vim")); + CUTE_PATH_EXPECT(n == CUTE_PATH_STRLEN(out)); + + path = "longfile.vim"; + n = path_compact(path, out, 11); + CUTE_PATH_EXPECT(!CUTE_PATH_STRCMP(out, "longfile...")); + CUTE_PATH_EXPECT(n == CUTE_PATH_STRLEN(out)); + } + +#else + + void path_do_unit_tests() + { + } + +#endif // CUTE_PATH_UNIT_TESTS + +#endif // CUTE_PATH_IMPLEMENTATION_ONCE +#endif // CUTE_PATH_IMPLEMENTATION + +/* + ------------------------------------------------------------------------------ + This software is available under 2 licenses - you may choose the one you like. + ------------------------------------------------------------------------------ + ALTERNATIVE A - zlib license + Copyright (c) 2017 Randy Gaul http://www.randygaul.net + This software is provided 'as-is', without any express or implied warranty. + In no event will the authors be held liable for any damages arising from + the use of this software. + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not + be misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + ------------------------------------------------------------------------------ + ALTERNATIVE B - Public Domain (www.unlicense.org) + This is free and unencumbered software released into the public domain. + Anyone is free to copy, modify, publish, use, compile, sell, or distribute this + software, either in source code form or as a compiled binary, for any purpose, + commercial or non-commercial, and by any means. + In jurisdictions that recognize copyright laws, the author or authors of this + software dedicate any and all copyright interest in the software to the public + domain. We make this dedication for the benefit of the public at large and to + the detriment of our heirs and successors. We intend this dedication to be an + overt act of relinquishment in perpetuity of all present and future rights to + this software under copyright law. + 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 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. + ------------------------------------------------------------------------------ +*/ \ No newline at end of file diff --git a/src/deps/tiniobj_loader_c/tinyobj_loader_c.h b/src/deps/tiniobj_loader_c/tinyobj_loader_c.h index d2897fd1..10dd7a43 100644 --- a/src/deps/tiniobj_loader_c/tinyobj_loader_c.h +++ b/src/deps/tiniobj_loader_c/tinyobj_loader_c.h @@ -27,6 +27,10 @@ /* @todo { Remove stddef dependency. size_t? } */ #include +#ifdef __cplusplus +extern "C" { +#endif + typedef struct { char *name; @@ -50,6 +54,7 @@ typedef struct { char *bump_texname; /* map_bump, bump */ char *displacement_texname; /* disp */ char *alpha_texname; /* map_d */ + char *metallic_texname; /* map_refl */ } tinyobj_material_t; typedef struct { @@ -87,18 +92,55 @@ typedef struct { #define TINYOBJ_ERROR_INVALID_PARAMETER (-2) #define TINYOBJ_ERROR_FILE_OPERATION (-3) -/* Parse wavefront .obj(.obj string data is expanded to linear char array `buf') - * flags are combination of TINYOBJ_FLAG_*** +/* 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] ctx User provided context. + * @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)(void *ctx, const char *filename, int is_mtl, const char *obj_filename, char **buf, size_t *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] ctx Context pointer passed to the file_reader_callback. + * @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, size_t *num_shapes, tinyobj_material_t **materials, - size_t *num_materials, const char *buf, size_t len, - unsigned int flags); + size_t *num_materials, const char *file_name, file_reader_callback file_reader, + void *ctx, 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 + * @param[in[ ctx Context pointer passed to the file_reader callack. + + * 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, size_t *num_materials_out, - const char *filename); + const char *filename, const char *obj_filename, file_reader_callback file_reader, + void *ctx); extern void tinyobj_attrib_init(tinyobj_attrib_t *attrib); extern void tinyobj_attrib_free(tinyobj_attrib_t *attrib); @@ -106,18 +148,22 @@ extern void tinyobj_shapes_free(tinyobj_shape_t *shapes, size_t num_shapes); extern void tinyobj_materials_free(tinyobj_material_t *materials, size_t num_materials); +#ifdef __cplusplus +} +#endif + #ifdef TINYOBJ_LOADER_C_IMPLEMENTATION #include #include #include #include -#if defined(TINYOBJ_MALLOC) && defined(TINYOBJ_REALLOC) && defined(TINYOBJ_CALLOC) && defined(TINYOBJ_FREE) +#if defined(TINYOBJ_MALLOC) && defined(TINYOBJ_CALLOC) && defined(TINYOBJ_FREE) && (defined(TINYOBJ_REALLOC) || defined(TINYOBJ_REALLOC_SIZED)) /* ok */ -#elif !defined(TINYOBJ_MALLOC) && !defined(TINYOBJ_REALLOC) && !defined(TINYOBJ_CALLOC) && !defined(TINYOBJ_FREE) +#elif !defined(TINYOBJ_MALLOC) && !defined(TINYOBJ_CALLOC) && !defined(TINYOBJ_FREE) && !defined(TINYOBJ_REALLOC) && !defined(TINYOBJ_REALLOC_SIZED) /* ok */ #else -#error "Must define all or none of TINYOBJ_MALLOC, TINYOBJ_REALLOC, TINYOBJ_CALLOC and TINYOBJ_FREE." +#error "Must define all or none of TINYOBJ_MALLOC, TINYOBJ_CALLOC, TINYOBJ_FREE, and TINYOBJ_REALLOC (or TINYOBJ_REALLOC_SIZED)." #endif #ifndef TINYOBJ_MALLOC @@ -128,7 +174,12 @@ extern void tinyobj_materials_free(tinyobj_material_t *materials, #define TINYOBJ_FREE free #endif -#define TINYOBJ_MAX_FACES_PER_F_LINE (100) +#ifndef TINYOBJ_REALLOC_SIZED +#define TINYOBJ_REALLOC_SIZED(p,oldsz,newsz) TINYOBJ_REALLOC(p,newsz) +#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)) @@ -454,7 +505,7 @@ static void parseFloat3(float *x, float *y, float *z, const char **token) { } static size_t my_strnlen(const char *s, size_t n) { - const char *p = memchr(s, 0, n); + const char *p = (char *)memchr(s, 0, n); return p ? (size_t)(p - s) : n; } @@ -510,7 +561,7 @@ char *dynamic_fgets(char **buf, size_t *size, FILE *file) { do { old_size = *size; *size *= 2; - *buf = (char*)TINYOBJ_REALLOC(*buf, *size); + *buf = (char*)TINYOBJ_REALLOC_SIZED(*buf, old_size, *size); offset = &((*buf)[old_size - 1]); ret = fgets(offset, (int)(old_size + 1), file); @@ -529,6 +580,7 @@ static void initMaterial(tinyobj_material_t *material) { material->bump_texname = NULL; material->displacement_texname = NULL; material->alpha_texname = NULL; + material->metallic_texname = NULL; for (i = 0; i < 3; i++) { material->ambient[i] = 0.f; material->diffuse[i] = 0.f; @@ -544,7 +596,7 @@ static void initMaterial(tinyobj_material_t *material) { /* Implementation of string to int hashtable */ -#define HASH_TABLE_ERROR 1 +#define HASH_TABLE_ERROR 1 #define HASH_TABLE_SUCCESS 0 #define HASH_TABLE_DEFAULT_SIZE 10 @@ -609,7 +661,7 @@ static int hash_table_insert_value(unsigned long hash, long value, hash_table_t* { if (i >= hash_table->capacity) return HASH_TABLE_ERROR; - index = (start_index + (i * i)) % hash_table->capacity; + index = (start_index + (i * i)) % hash_table->capacity; } entry = hash_table->entries + index; @@ -662,7 +714,8 @@ static void hash_table_maybe_grow(size_t new_n, hash_table_t* hash_table) } 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.hashes = hash_table->hashes = (unsigned long*) TINYOBJ_REALLOC_SIZED( + (void*) hash_table->hashes, sizeof(unsigned long) * hash_table->capacity, 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; @@ -715,25 +768,89 @@ static tinyobj_material_t *tinyobj_material_add(tinyobj_material_t *prev, size_t 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)); + size_t num_bytes = sizeof(tinyobj_material_t) * num_materials; + dst = (tinyobj_material_t *)TINYOBJ_REALLOC_SIZED( + prev, num_bytes, num_bytes + sizeof(tinyobj_material_t)); dst[num_materials] = (*new_mat); /* Just copy pointer for char* members */ return dst; } +static int is_line_ending(const char *p, size_t i, size_t 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 { + size_t pos; + size_t len; +} LineInfo; + +/* Find '\n' and create line data. */ +static int get_line_infos(const char *buf, size_t buf_len, LineInfo **line_infos, size_t *num_lines) +{ + size_t i = 0; + size_t end_idx = buf_len; + size_t prev_pos = 0; + size_t line_no = 0; + size_t 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, size_t *num_materials_out, - const char *filename, + const char *mtl_filename, const char *obj_filename, file_reader_callback file_reader, void *ctx, hash_table_t* material_table) { tinyobj_material_t material; - size_t buffer_size = 128; - char *linebuf; - FILE *fp; size_t num_materials = 0; tinyobj_material_t *materials = NULL; int has_previous_material = 0; const char *line_end = NULL; + size_t num_lines = 0; + LineInfo *line_infos = NULL; + size_t i = 0; + char *buf = NULL; + size_t len = 0; if (materials_out == NULL) { return TINYOBJ_ERROR_INVALID_PARAMETER; @@ -746,20 +863,31 @@ static int tinyobj_parse_and_index_mtl_file(tinyobj_material_t **materials_out, (*materials_out) = NULL; (*num_materials_out) = 0; - fp = fopen(filename, "r"); - if (!fp) { - fprintf(stderr, "TINYOBJ: Error reading file '%s': %s (%d)\n", filename, strerror(errno), errno); - return TINYOBJ_ERROR_FILE_OPERATION; + file_reader(ctx, 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) { + TINYOBJ_FREE(line_infos); + return TINYOBJ_ERROR_EMPTY; } /* Create a default material */ initMaterial(&material); - linebuf = (char*)TINYOBJ_MALLOC(buffer_size); - while (NULL != dynamic_fgets(&linebuf, &buffer_size, fp)) { - const char *token = linebuf; + for (i = 0; i < num_lines; i++) { + const char *p = &buf[line_infos[i].pos]; + size_t p_len = line_infos[i].len; - line_end = token + strlen(token); + 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"); @@ -917,6 +1045,13 @@ static int tinyobj_parse_and_index_mtl_file(tinyobj_material_t **materials_out, continue; } + /* metallic texture */ + if ((0 == strncmp(token, "map_refl", 8)) && IS_SPACE(token[8])) { + token += 9; + material.metallic_texname = my_strdup(token, (size_t) (line_end - token)); + continue; + } + /* bump texture */ if ((0 == strncmp(token, "map_bump", 8)) && IS_SPACE(token[8])) { token += 9; @@ -924,6 +1059,13 @@ static int tinyobj_parse_and_index_mtl_file(tinyobj_material_t **materials_out, continue; } + /* bump texture */ + if ((0 == strncmp(token, "map_Bump", 8)) && IS_SPACE(token[8])) { + token += 9; + material.bump_texname = my_strdup(token, (size_t) (line_end - token)); + continue; + } + /* alpha texture */ if ((0 == strncmp(token, "map_d", 5)) && IS_SPACE(token[5])) { token += 6; @@ -948,6 +1090,8 @@ static int tinyobj_parse_and_index_mtl_file(tinyobj_material_t **materials_out, /* @todo { unknown parameter } */ } + TINYOBJ_FREE(line_infos); + if (material.name) { /* Flush last material element */ materials = tinyobj_material_add(materials, num_materials, &material); @@ -957,18 +1101,15 @@ static int tinyobj_parse_and_index_mtl_file(tinyobj_material_t **materials_out, (*num_materials_out) = num_materials; (*materials_out) = materials; - if (linebuf) { - TINYOBJ_FREE(linebuf); - } - return TINYOBJ_SUCCESS; } int tinyobj_parse_mtl_file(tinyobj_material_t **materials_out, size_t *num_materials_out, - const char *filename) { - return tinyobj_parse_and_index_mtl_file(materials_out, num_materials_out, filename, NULL); -} + const char *mtl_filename, const char *obj_filename, file_reader_callback file_reader, + void *ctx) { + return tinyobj_parse_and_index_mtl_file(materials_out, num_materials_out, mtl_filename, obj_filename, file_reader, ctx, NULL); +} typedef enum { @@ -1189,25 +1330,68 @@ static int parseLine(Command *command, const char *p, size_t p_len, return 0; } -typedef struct { - size_t pos; - size_t len; -} LineInfo; - -static int is_line_ending(const char *p, size_t i, size_t 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; +static size_t basename_len(const char *filename, size_t filename_length) { + /* Count includes NUL terminator. */ + const char *p = &filename[filename_length - 1]; + size_t count = 1; + + /* On Windows, the directory delimiter is '\' and both it and '/' is + * reserved by the filesystem. On *nix platforms, only the '/' character + * is reserved, so account for the two cases separately. */ + #if _WIN32 + while (p[-1] != '/' && p[-1] != '\\') { + if (p == filename) { + count = filename_length; + return count; + } + count++; + p--; } - } - return 0; + p++; + return count; + #else + while (*(--p) != '/') { + if (p == filename) { + count = filename_length; + return count; + } + count++; + } + return count; + #endif +} + +static char *generate_mtl_filename(const char *obj_filename, + size_t obj_filename_length, + const char *mtllib_name, + size_t mtllib_name_length) { + /* Create a dynamically-allocated material filename. This allows the material + * and obj files to be separated, however the mtllib name in the OBJ file + * must be a relative path to the material file from the OBJ's directory. + * This does not support the matllib name as an absolute address. */ + char *mtl_filename; + char *p; + size_t mtl_filename_length; + size_t obj_basename_length; + + /* Calculate required size of mtl_filename and allocate */ + obj_basename_length = basename_len(obj_filename, obj_filename_length); + mtl_filename_length = (obj_filename_length - obj_basename_length) + mtllib_name_length; + mtl_filename = (char *)TINYOBJ_MALLOC(mtl_filename_length); + + /* Copy over the obj's path */ + memcpy(mtl_filename, obj_filename, (obj_filename_length - obj_basename_length)); + + /* Overwrite the obj basename with the mtllib name, filling the string */ + p = &mtl_filename[mtl_filename_length - mtllib_name_length]; + strcpy(p, mtllib_name); + return mtl_filename; } int tinyobj_parse_obj(tinyobj_attrib_t *attrib, tinyobj_shape_t **shapes, size_t *num_shapes, tinyobj_material_t **materials_out, - size_t *num_materials_out, const char *buf, size_t len, + size_t *num_materials_out, const char *obj_filename, + file_reader_callback file_reader, void *ctx, unsigned int flags) { LineInfo *line_infos = NULL; Command *commands = NULL; @@ -1226,6 +1410,10 @@ int tinyobj_parse_obj(tinyobj_attrib_t *attrib, tinyobj_shape_t **shapes, hash_table_t material_table; + char *buf = NULL; + size_t len = 0; + file_reader(ctx, 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; @@ -1235,49 +1423,13 @@ int tinyobj_parse_obj(tinyobj_attrib_t *attrib, tinyobj_shape_t **shapes, if (num_materials_out == NULL) return TINYOBJ_ERROR_INVALID_PARAMETER; tinyobj_attrib_init(attrib); - /* 1. Find '\n' and create line data. */ - { - size_t i; - size_t end_idx = len; - size_t prev_pos = 0; - size_t line_no = 0; - size_t 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; - } + /* 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); + commands = (Command *)TINYOBJ_MALLOC(sizeof(Command) * num_lines); create_hash_table(HASH_TABLE_DEFAULT_SIZE, &material_table); @@ -1311,21 +1463,38 @@ int tinyobj_parse_obj(tinyobj_attrib_t *attrib, tinyobj_shape_t **shapes, TINYOBJ_FREE(line_infos); } - /* Load material(if exits) */ + /* Load material (if it exists) */ if (mtllib_line_index >= 0 && commands[mtllib_line_index].mtllib_name && commands[mtllib_line_index].mtllib_name_len > 0) { - char *filename = my_strndup(commands[mtllib_line_index].mtllib_name, - commands[mtllib_line_index].mtllib_name_len); + /* Maximum length allowed by Linux - higher than Windows and macOS */ + size_t obj_filename_len = my_strnlen(obj_filename, 4096 + 255) + 1; + char *mtl_filename; + char *mtllib_name; + size_t mtllib_name_len = 0; + int ret; - int ret = tinyobj_parse_and_index_mtl_file(&materials, &num_materials, filename, &material_table); + mtllib_name_len = length_until_line_feed(commands[mtllib_line_index].mtllib_name, + commands[mtllib_line_index].mtllib_name_len); + + mtllib_name = my_strndup(commands[mtllib_line_index].mtllib_name, + mtllib_name_len); + + /* allow for NUL terminator */ + mtllib_name_len++; + mtl_filename = generate_mtl_filename(obj_filename, obj_filename_len, + mtllib_name, mtllib_name_len); + + ret = tinyobj_parse_and_index_mtl_file(&materials, &num_materials, + mtl_filename, obj_filename, + file_reader, ctx, + &material_table); if (ret != TINYOBJ_SUCCESS) { /* warning. */ - fprintf(stderr, "TINYOBJ: Failed to parse material file '%s': %d\n", filename, ret); + fprintf(stderr, "TINYOBJ: Failed to parse material file '%s': %d\n", mtl_filename, ret); } - - TINYOBJ_FREE(filename); - + TINYOBJ_FREE(mtl_filename); + TINYOBJ_FREE(mtllib_name); } /* Construct attributes */ @@ -1371,12 +1540,12 @@ int tinyobj_parse_obj(tinyobj_attrib_t *attrib, tinyobj_shape_t **shapes, } */ if (commands[i].material_name && - commands[i].material_name_len >0) + 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 - 1] = 0; + 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); @@ -1520,7 +1689,7 @@ int tinyobj_parse_obj(tinyobj_attrib_t *attrib, tinyobj_shape_t **shapes, } destroy_hash_table(&material_table); - + (*materials_out) = materials; (*num_materials_out) = num_materials; @@ -1577,10 +1746,12 @@ void tinyobj_materials_free(tinyobj_material_t *materials, if (materials[i].displacement_texname) TINYOBJ_FREE(materials[i].displacement_texname); if (materials[i].alpha_texname) TINYOBJ_FREE(materials[i].alpha_texname); + if (materials[i].metallic_texname) + TINYOBJ_FREE(materials[i].metallic_texname); } TINYOBJ_FREE(materials); } #endif /* TINYOBJ_LOADER_C_IMPLEMENTATION */ -#endif /* TINOBJ_LOADER_C_H_ */ +#endif /* TINOBJ_LOADER_C_H_ */ \ No newline at end of file