Skip to content

Add pure C11 loader (tiny_obj_c) with robust tessellation library#430

Merged
syoyo merged 2 commits into
releasefrom
pure-c11-loader
Jun 19, 2026
Merged

Add pure C11 loader (tiny_obj_c) with robust tessellation library#430
syoyo merged 2 commits into
releasefrom
pure-c11-loader

Conversation

@syoyo

@syoyo syoyo commented Jun 19, 2026

Copy link
Copy Markdown
Collaborator

Summary

A from-scratch pure C11 reimplementation of tinyobjloader: secure, portable, freestanding-capable, dependency-free, fast, and robust. Not header-only — tiny_obj_c.c + tiny_obj_c.h, plus a separate reusable tessellation library tobj_tess.{c,h}. Carries the C++ feature set over to C11 and adds line/point/free-form-patch primitives.

Highlights

  • Dual precision — float (_f) and double (_d) families coexist in one build, generated from a single template via token-pasting.
  • Freestanding / no-dependency — the core links zero libc symbols (verified with nm under -ffreestanding -DTOBJ_NO_LIBC): no malloc/strtod/fopen/memcpy/sqrt/pthread. Injectable allocator, bundled ASCII→float parser, no math.h in the core or default tessellator.
  • Robust tessellation (tobj_tess) — Newell projection with bbox fallback, magnitude-scaled epsilons (fixes the C++ absolute-epsilon area threshold that is meaningless at the ~8000–14000 coordinate magnitudes in sandbox/zh2-ill.obj, sandbox/ill-tri.obj, models/issue-295-trianguation-failure.obj), a convex fan fast path, and a force-clip backstop that always emits n−2 triangles and never crashes or loops on degenerate, concave, collinear, coincident, non-planar, or self-intersecting input.
  • Primitives — faces (triangulated), lines (l), points (p), and free-form patches (cstype/curv/curv2/surf/vp/parm/trim/hole/end) parsed and retained losslessly (no NURBS evaluation).
  • Full material support — Phong + PBR + 13 texture channels with options, unknown-parameter map, map_Kd-without-Kd default, standalone .mtl API, material-resolver callback (replaces the virtual MaterialReader), and a streaming callback API.
  • Optional perf behind macros (scalar/single-threaded/libc default) — multithreaded two-pass parse, SIMD newline scan (AVX2/SSE2/NEON), mmap + file I/O, arena allocator, fast float.
  • Security — bounds-checked indices, overflow-safe size arithmetic, configurable caps, defined teardown.

Validation (all under ASan + UBSan)

  • 84-file corpus (models/ + sandbox/) loads with 0 failures, 1.45M triangles, consistent index counts.
  • acutest drivers: tests/tess_tester.c (15 unit/property tests incl. pathological classes, area preservation, determinism) and tests/tester_c.c (12 loader tests: corpus, both precisions, triangulation, BOM/CRLF, vertex colors, lines/points, MTL, garbage) — all pass.
  • Freestanding driver runs with a custom bump allocator and no libc.
  • Multithreaded output is byte-identical to single-threaded on a 20k-vertex varied OBJ.
  • libFuzzer harness finds no crashes/leaks.
  • Strict -Wall -Wextra -Wpedantic -Wshadow clean (default and all-features).

Build / test

cd tests && make check_c          # tester_c, tester_c_features (SIMD+MT), tess_tester, freestanding
# or via CMake:
cmake -S . -B build -DTINYOBJLOADER_C_BUILD_TESTS=ON && cmake --build build && ctest --test-dir build

Added options in CMakeLists.txt: TINYOBJLOADER_BUILD_C_LIBRARY (ON), TINYOBJLOADER_C_ENABLE_FILE_IO/MMAP/SIMD/MULTITHREADING, TINYOBJLOADER_C_BUILD_TESTS, plus a fuzz_tobj_c target under LIB_FUZZING_ENGINE.

Known limitation

In the multithreaded path, curv2vp index resolution can differ from single-threaded only when vp lines have inconsistent component counts (a pathological free-form case); it does not affect the corpus or normal use. A reserved tobj_mathops hook is currently unused (the default tessellator needs no sqrt).

🤖 Generated with Claude Code

syoyo and others added 2 commits June 19, 2026 17:31
A from-scratch C11 reimplementation of tinyobjloader: secure, portable,
freestanding-capable, dependency-free, fast, and robust. Not header-only
(tiny_obj_c.c + tiny_obj_c.h, plus a separate tobj_tess tessellation library).

Highlights:
- Dual precision: float (_f) and double (_d) families coexist in one build,
  generated from a single template via token-pasting.
- Freestanding / no-dependency: the core links zero libc symbols (verified with
  nm under -ffreestanding -DTOBJ_NO_LIBC). Injectable allocator, bundled
  ASCII->float parser, no math.h in the core or default tessellator.
- Robust tessellation (tobj_tess): Newell projection with bbox fallback,
  magnitude-scaled epsilons, convex fan fast path, and a force-clip backstop
  that always emits n-2 triangles and never crashes/loops on degenerate,
  concave, collinear, coincident, non-planar, or self-intersecting input.
- Primitives: faces (triangulated), lines, points, and free-form patches
  (cstype/curv/curv2/surf/vp/parm/trim/hole/end) parsed and retained losslessly.
- Full material support: Phong + PBR + 13 texture channels with options,
  unknown-parameter map, map_Kd-without-Kd default, standalone .mtl API,
  material resolver callback, and a streaming callback API.
- Optional perf behind macros (scalar/single-threaded default): multithreaded
  two-pass parse (byte-identical to single-threaded), SIMD newline scan
  (AVX2/SSE2/NEON), mmap + file I/O, arena allocator, fast float.
- Security: bounds-checked indices, overflow-safe sizing, configurable caps.

Validation (all under ASan + UBSan): 84-file corpus loads with 0 failures;
acutest drivers tester_c (12) and tess_tester (15) pass; freestanding driver
runs with a custom allocator; MT output is byte-identical to single-threaded;
libFuzzer runs find no crashes/leaks. CMake and tests/Makefile integration with
a fuzzer entry point.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
The corpus_loads test referenced sandbox/*.obj, which are local scratch files
not tracked in the repo, so they were absent in the clean CI checkout and
failed with TOBJ_ERR_IO. Replace them with tracked models/*.obj files. The
pathological-geometry triangulation cases remain covered directly by
tests/tess_tester.c.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
@syoyo syoyo merged commit 68aa0d6 into release Jun 19, 2026
30 checks passed
@syoyo syoyo deleted the pure-c11-loader branch June 19, 2026 09:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant