Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 10 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -413,6 +413,11 @@ Properties:
(no `malloc` / `stdio` / `strtod` / `math.h`): supply your own allocator and a
byte buffer. Define `TOBJ_NO_LIBC` (a.k.a. `TOBJ_FREESTANDING`) for that mode,
or enable libc-backed conveniences with `TOBJ_ENABLE_FILE_IO`.
* **Pluggable allocation and I/O.** `tobj_allocator` carries bounded
size/alignment requests (`max_alloc_size` is optional) and supports custom
`alloc`/`calloc`/`free` backends; libc builds default to malloc/calloc/free.
`tobj_load_obj_from_io_*` reads through caller-supplied byte callbacks for
freestanding streams.
* **Dual precision.** `float` (`_f`) and `double` (`_d`) data structures and
entry points coexist in the same build (e.g. `tobj_scene_f` / `tobj_scene_d`).
* **Robust tessellation.** Survives degenerate / concave / collinear /
Expand Down Expand Up @@ -456,10 +461,11 @@ tobj_diag_free(&diag, NULL);
```

Use the `_d` variants for double precision, `tobj_load_obj_from_memory_f` for
the freestanding / in-memory path, and `tobj_load_obj_with_callbacks_f` for
streaming. Build with CMake (`-DTINYOBJLOADER_BUILD_C_LIBRARY=ON`, the default;
options `TINYOBJLOADER_C_ENABLE_FILE_IO` / `MMAP` / `SIMD` / `MULTITHREADING`)
or simply compile `tiny_obj_c.c` and `tobj_tess.c`. C tests live under `tests/`
the freestanding / in-memory path, `tobj_load_obj_from_io_f` for custom byte
input, and `tobj_load_obj_with_callbacks_f` for parse-event callbacks. Build
with CMake (`-DTINYOBJLOADER_BUILD_C_LIBRARY=ON`, the default; options
`TINYOBJLOADER_C_ENABLE_FILE_IO` / `MMAP` / `SIMD` / `MULTITHREADING`) or simply
compile `tiny_obj_c.c` and `tobj_tess.c`. C tests live under `tests/`
(`make check_c`).

## Python binding
Expand Down
186 changes: 185 additions & 1 deletion tests/tester_c.c
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,48 @@ static const char *load_string_f(const char *obj, tobj_scene_f *sc,
return tobj_result_string(r);
}

typedef struct mem_io {
const uint8_t *data;
size_t len;
size_t pos;
size_t chunk;
int closed;
} mem_io;

static tobj_result mem_io_read(void *ud, uint8_t *dst, size_t dst_size,
size_t *bytes_read) {
mem_io *m = (mem_io *)ud;
size_t left = m->len - m->pos;
size_t n = left < dst_size ? left : dst_size;
if (m->chunk && n > m->chunk) n = m->chunk;
if (n) memcpy(dst, m->data + m->pos, n);
m->pos += n;
*bytes_read = n;
return TOBJ_OK;
}

static void mem_io_close(void *ud) { ((mem_io *)ud)->closed = 1; }

static tobj_result io_overreport_read(void *ud, uint8_t *dst, size_t dst_size,
size_t *bytes_read) {
(void)ud;
if (dst_size) dst[0] = 'x';
*bytes_read = dst_size + 1;
return TOBJ_OK;
}

static tobj_result io_error_read(void *ud, uint8_t *dst, size_t dst_size,
size_t *bytes_read) {
(void)ud;
(void)dst;
(void)dst_size;
*bytes_read = 0;
return TOBJ_ERR_IO;
}

/* ---- corpus ------------------------------------------------------------ */

/* Only files committed to the repo (CI checks out a clean tree; sandbox/*.obj
/* Only files committed to the repo (CI checks out a clean tree; sandbox files
* are local scratch files and are intentionally not used here). The
* pathological-geometry triangulation cases are covered directly by
* tests/tess_tester.c. */
Expand Down Expand Up @@ -205,6 +244,143 @@ static void test_mtl_standalone(void) {
tobj_material_list_free_f(&ml);
}

static void test_io_callbacks(void) {
const char *obj = "v 0 0 0\nv 1 0 0\nv 0 1 0\nf 1 2 3\n";
mem_io mio;
mio.data = (const uint8_t *)obj;
mio.len = strlen(obj);
mio.pos = 0;
mio.chunk = 5;
mio.closed = 0;
tobj_io_callbacks io;
io.read = mem_io_read;
io.close = mem_io_close;
io.user_data = &mio;
tobj_load_config cfg = tobj_default_config();
tobj_scene_f sc;
tobj_result r = tobj_load_obj_from_io_f(&sc, &io, &cfg, NULL);
TEST_CHECK(r == TOBJ_OK);
TEST_CHECK(mio.closed == 1);
TEST_CHECK(sc.num_shapes == 1);
TEST_CHECK(sc.shapes[0].mesh.num_indices == 3);
tobj_scene_free_f(&sc);
}

static void test_allocator_limit_failure(void) {
const char *obj = "v 0 0 0\nv 1 0 0\nv 0 1 0\nf 1 2 3\n";
tobj_load_config cfg = tobj_default_config();
cfg.allocator = tobj_default_allocator();
cfg.allocator.max_alloc_size = 16;
tobj_scene_f sc;
tobj_result r =
tobj_load_obj_from_memory_f(&sc, (const uint8_t *)obj, strlen(obj), &cfg,
NULL);
TEST_CHECK(r == TOBJ_ERR_ALLOC);
TEST_CHECK(sc.num_shapes == 0);
}

static void test_input_limit_failure(void) {
const char *obj = "v 0 0 0\n";
tobj_load_config cfg = tobj_default_config();
cfg.max_input_bytes = 4;
tobj_scene_f sc;
tobj_result r =
tobj_load_obj_from_memory_f(&sc, (const uint8_t *)obj, strlen(obj), &cfg,
NULL);
TEST_CHECK(r == TOBJ_ERR_LIMIT_EXCEEDED);
}

static void test_io_input_limit_exact_and_over(void) {
const char *obj = "v 0 0 0\n";
tobj_load_config cfg = tobj_default_config();
cfg.max_input_bytes = strlen(obj);

mem_io exact;
exact.data = (const uint8_t *)obj;
exact.len = strlen(obj);
exact.pos = 0;
exact.chunk = 3;
exact.closed = 0;
tobj_io_callbacks io;
io.read = mem_io_read;
io.close = mem_io_close;
io.user_data = &exact;
tobj_scene_f sc;
tobj_result r = tobj_load_obj_from_io_f(&sc, &io, &cfg, NULL);
TEST_CHECK(r == TOBJ_OK);
TEST_CHECK(exact.closed == 1);
tobj_scene_free_f(&sc);

const char *too_big = "v 0 0 0\n# extra\n";
mem_io over;
over.data = (const uint8_t *)too_big;
over.len = strlen(too_big);
over.pos = 0;
over.chunk = 0;
over.closed = 0;
io.user_data = &over;
r = tobj_load_obj_from_io_f(&sc, &io, &cfg, NULL);
TEST_CHECK(r == TOBJ_ERR_LIMIT_EXCEEDED);
TEST_CHECK(over.closed == 1);
TEST_CHECK(sc.num_shapes == 0);
}

static void test_io_callback_errors(void) {
tobj_io_callbacks io;
io.close = mem_io_close;

mem_io state;
state.data = NULL;
state.len = 0;
state.pos = 0;
state.chunk = 0;
state.closed = 0;
io.user_data = &state;
io.read = io_overreport_read;
tobj_scene_f sc;
tobj_result r = tobj_load_obj_from_io_f(&sc, &io, NULL, NULL);
TEST_CHECK(r == TOBJ_ERR_IO);
TEST_CHECK(state.closed == 1);

state.closed = 0;
io.read = io_error_read;
r = tobj_load_obj_from_io_f(&sc, &io, NULL, NULL);
TEST_CHECK(r == TOBJ_ERR_IO);
TEST_CHECK(state.closed == 1);
}

static void test_invalid_allocator_rejected(void) {
const char *obj = "v 0 0 0\n";
tobj_load_config cfg = tobj_default_config();
cfg.allocator = tobj_default_allocator();
cfg.allocator.free = NULL;
tobj_scene_f sc;
tobj_result r =
tobj_load_obj_from_memory_f(&sc, (const uint8_t *)obj, strlen(obj), &cfg,
NULL);
TEST_CHECK(r == TOBJ_ERR_INVALID_ARG);
TEST_CHECK(sc.num_shapes == 0);
}

static void test_allocator_without_calloc_or_realloc(void) {
const char *obj =
"v 0 0 0\nv 1 0 0\nv 2 0 0\nv 3 0 0\nv 4 0 0\n"
"v 5 0 0\nv 6 0 0\nv 7 0 0\nv 8 0 0\n"
"f 1 2 3\nf 4 5 6\nf 7 8 9\n";
tobj_load_config cfg = tobj_default_config();
cfg.allocator = tobj_default_allocator();
cfg.allocator.calloc = NULL;
cfg.allocator.realloc = NULL;
tobj_scene_f sc;
tobj_result r =
tobj_load_obj_from_memory_f(&sc, (const uint8_t *)obj, strlen(obj), &cfg,
NULL);
TEST_CHECK(r == TOBJ_OK);
TEST_CHECK(sc.attrib.vertices.count == 27);
TEST_CHECK(sc.shapes[0].mesh.num_indices == 9);
tobj_scene_free_f(&sc);
}

TEST_LIST = {
{"corpus_loads", test_corpus_loads},
{"both_precisions", test_both_precisions},
Expand All @@ -218,4 +394,12 @@ TEST_LIST = {
{"degenerate_face_skipped", test_degenerate_face_skipped},
{"empty_and_garbage", test_empty_and_garbage},
{"mtl_standalone", test_mtl_standalone},
{"io_callbacks", test_io_callbacks},
{"allocator_limit_failure", test_allocator_limit_failure},
{"input_limit_failure", test_input_limit_failure},
{"io_input_limit_exact_and_over", test_io_input_limit_exact_and_over},
{"io_callback_errors", test_io_callback_errors},
{"invalid_allocator_rejected", test_invalid_allocator_rejected},
{"allocator_without_calloc_or_realloc",
test_allocator_without_calloc_or_realloc},
{NULL, NULL}};
2 changes: 2 additions & 0 deletions tests/tester_c_freestanding.c
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,10 @@ static const char g_obj[] =
int main(void) {
tobj_allocator a;
a.alloc = fs_alloc;
a.calloc = 0;
a.realloc = fs_realloc;
a.free = fs_free;
a.max_alloc_size = 0;
a.user_data = (void *)0;

tobj_load_config cfg = tobj_default_config();
Expand Down
Loading
Loading