← 返回首页
Add pure C11 loader (tiny_obj_c) with robust tessellation library by syoyo · Pull Request #430 · tinyobjloader/tinyobjloader · GitHub
Skip to content

Navigation Menu

Toggle navigation
Sign in
Appearance settings
Search or jump to...

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Include my email address so I can be contacted

Saved searches

Use saved searches to filter your results more quickly

Appearance settings
Resetting focus
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension .c  (6) .h  (2) .inc  (4) .txt  (1) No extension  (1) All 5 file types selected Viewed files
Conversations
Failed to load comments. Retry
Loading
Jump to
Jump to file
Failed to load files. Retry
Loading
Diff view
Unified
Split
Hide whitespace
Apply and reload
Show whitespace
Diff view
Unified
Split
Hide whitespace
Apply and reload
70 changes: 70 additions & 0 deletions CMakeLists.txt
Show comments View file Edit file Delete file Open in desktop
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,76 @@ if (TINYOBJLOADER_WITH_PYTHON)

endif()

# ---------------------------------------------------------------------------
# Pure C11 loader (tiny_obj_c + tobj_tess)
# ---------------------------------------------------------------------------
option(TINYOBJLOADER_BUILD_C_LIBRARY "Build the pure-C11 loader (tiny_obj_c)" ON)
option(TINYOBJLOADER_C_ENABLE_FILE_IO "tiny_obj_c: file loading helpers" ON)
option(TINYOBJLOADER_C_ENABLE_MMAP "tiny_obj_c: mmap-based file loading" OFF)
option(TINYOBJLOADER_C_ENABLE_SIMD "tiny_obj_c: SIMD newline scan" OFF)
option(TINYOBJLOADER_C_ENABLE_MULTITHREADING "tiny_obj_c: multithreaded parse" OFF)
option(TINYOBJLOADER_C_BUILD_TESTS "tiny_obj_c: build C test drivers" OFF)

if(TINYOBJLOADER_BUILD_C_LIBRARY)
enable_language(C)
set(CMAKE_C_STANDARD 11)
set(CMAKE_C_STANDARD_REQUIRED ON)
set(CMAKE_C_EXTENSIONS OFF)

add_library(tinyobjloader_c
${CMAKE_CURRENT_SOURCE_DIR}/tiny_obj_c.c
${CMAKE_CURRENT_SOURCE_DIR}/tobj_tess.c)
add_sanitizers(tinyobjloader_c)
target_include_directories(tinyobjloader_c PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>
$<INSTALL_INTERFACE:${TINYOBJLOADER_INCLUDE_DIR}>)

if(TINYOBJLOADER_C_ENABLE_FILE_IO)
target_compile_definitions(tinyobjloader_c PUBLIC TOBJ_ENABLE_FILE_IO)
endif()
if(TINYOBJLOADER_C_ENABLE_MMAP)
target_compile_definitions(tinyobjloader_c PUBLIC TOBJ_ENABLE_MMAP)
endif()
if(TINYOBJLOADER_C_ENABLE_SIMD)
target_compile_definitions(tinyobjloader_c PUBLIC TOBJ_ENABLE_SIMD)
endif()
if(TINYOBJLOADER_C_ENABLE_MULTITHREADING)
find_package(Threads REQUIRED)
target_compile_definitions(tinyobjloader_c PUBLIC TOBJ_ENABLE_MULTITHREADING)
target_link_libraries(tinyobjloader_c PUBLIC Threads::Threads)
endif()

install(TARGETS tinyobjloader_c
EXPORT ${PROJECT_NAME}-targets
DESTINATION ${TINYOBJLOADER_LIBRARY_DIR})
install(FILES
tiny_obj_c.h tiny_obj_c_pub.inc tiny_obj_c_impl.inc
tobj_tess.h tobj_tess_pub.inc tobj_tess_impl.inc
DESTINATION ${TINYOBJLOADER_INCLUDE_DIR})

if(TINYOBJLOADER_C_BUILD_TESTS)
add_executable(tester_c tests/tester_c.c)
target_link_libraries(tester_c tinyobjloader_c)
target_compile_definitions(tester_c PRIVATE TOBJ_ENABLE_FILE_IO)
add_executable(tess_tester tests/tess_tester.c
${CMAKE_CURRENT_SOURCE_DIR}/tobj_tess.c)
target_include_directories(tess_tester PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
if(UNIX)
target_link_libraries(tester_c m)
target_link_libraries(tess_tester m)
endif()
enable_testing()
add_test(NAME tester_c COMMAND tester_c
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/tests)
add_test(NAME tess_tester COMMAND tess_tester)
endif()

if(DEFINED ENV{LIB_FUZZING_ENGINE})
add_executable(fuzz_tobj_c fuzzer/fuzz_tobj_c.c)
target_link_libraries(fuzz_tobj_c tinyobjloader_c $ENV{LIB_FUZZING_ENGINE})
endif()
endif()

#Write CMake package config files
include(CMakePackageConfigHelpers)

Expand Down
16 changes: 16 additions & 0 deletions fuzzer/fuzz_tobj_c.c
Show comments View file Edit file Delete file Open in desktop
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/*
* fuzz_tobj_c.c -- libFuzzer/AFL entry for the pure C11 loader.
*
* Build (libFuzzer):
* clang -std=c11 -g -O1 -fsanitize=address,undefined,fuzzer \
* -DTOBJ_ENABLE_FILE_IO -I.. fuzz_tobj_c.c ../tiny_obj_c.c ../tobj_tess.c \
* -o fuzz_tobj_c
*
* tobj_fuzz_one parses the input from memory and frees the scene; the harness
* asserts only that it returns (no crash / hang / leak / UB).
*/
#include "tiny_obj_c.h"

int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
return tobj_fuzz_one(data, size);
}
56 changes: 52 additions & 4 deletions tests/Makefile
Show comments View file Edit file Delete file Open in desktop
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
@@ -1,10 +1,19 @@
.PHONY: clean check check_default check_features check_nofastfloat all \
obj-fuzz llvm-fuzz
obj-fuzz llvm-fuzz \
check_c check_c_default check_c_features check_tess check_c_freestanding

CXX ?= clang++
CXXFLAGS ?= -g -O1
EXTRA_CXXFLAGS ?= -std=c++11 -fsanitize=address

# ---- pure C11 loader (tiny_obj_c) -------------------------------------------
CC ?= clang
CFLAGS ?= -g -O1
EXTRA_CFLAGS ?= -std=c11 -Wall -Wextra -fsanitize=address,undefined
C_SRCS = ../tiny_obj_c.c ../tobj_tess.c
C_DEPS = ../tiny_obj_c.h ../tiny_obj_c_pub.inc ../tiny_obj_c_impl.inc \
../tobj_tess.h ../tobj_tess_pub.inc ../tobj_tess_impl.inc acutest.h

# Optional feature macros exercised by the `tester_features` build.
FEATURE_FLAGS = -DTINYOBJLOADER_USE_MULTITHREADING \
-DTINYOBJLOADER_USE_SIMD \
Expand Down Expand Up @@ -33,7 +42,30 @@ tester_nofastfloat: $(DEPS)
$(CXX) $(CXXFLAGS) $(EXTRA_CXXFLAGS) -DTINYOBJLOADER_DISABLE_FAST_FLOAT \
-pthread -I.. -o tester_nofastfloat $(SRCS)

all: tester tester_features tester_nofastfloat
all: tester tester_features tester_nofastfloat tester_c tess_tester \
tester_c_features

# Default C build: scalar, single-threaded, file I/O + mmap, both precisions.
tester_c: tester_c.c $(C_SRCS) $(C_DEPS)
$(CC) $(CFLAGS) $(EXTRA_CFLAGS) -DTOBJ_ENABLE_FILE_IO -DTOBJ_ENABLE_MMAP \
-I.. -o tester_c tester_c.c $(C_SRCS) -lm

# Feature C build: exercise SIMD + multithreading paths.
tester_c_features: tester_c.c $(C_SRCS) $(C_DEPS)
$(CC) $(CFLAGS) $(EXTRA_CFLAGS) -DTOBJ_ENABLE_FILE_IO -DTOBJ_ENABLE_MMAP \
-DTOBJ_ENABLE_SIMD -DTOBJ_ENABLE_MULTITHREADING -pthread \
-I.. -o tester_c_features tester_c.c $(C_SRCS) -lm

# Tessellator unit/property tests.
tess_tester: tess_tester.c ../tobj_tess.c ../tobj_tess.h ../tobj_tess_pub.inc \
../tobj_tess_impl.inc acutest.h
$(CC) $(CFLAGS) $(EXTRA_CFLAGS) -I.. -o tess_tester tess_tester.c \
../tobj_tess.c -lm

# Freestanding compile/link check: no libc, caller-supplied allocator.
tester_c_freestanding: tester_c_freestanding.c $(C_SRCS) $(C_DEPS)
$(CC) $(CFLAGS) -std=c11 -Wall -Wextra -ffreestanding -DTOBJ_NO_LIBC \
-I.. -o tester_c_freestanding tester_c_freestanding.c $(C_SRCS)

obj-fuzz:
$(MAKE) -C obj-fuzz
Expand All @@ -43,7 +75,7 @@ llvm-fuzz:

# Run all configurations so CI covers the default, feature-enabled, and
# fast_float-disabled code paths.
check: check_default check_features check_nofastfloat
check: check_default check_features check_nofastfloat check_c

check_default: tester
./tester
Expand All @@ -54,7 +86,23 @@ check_features: tester_features
check_nofastfloat: tester_nofastfloat
./tester_nofastfloat

# Pure C11 test suite.
check_c: check_c_default check_c_features check_tess check_c_freestanding

check_c_default: tester_c
./tester_c

check_c_features: tester_c_features
./tester_c_features

check_tess: tess_tester
./tess_tester

check_c_freestanding: tester_c_freestanding
./tester_c_freestanding

clean:
rm -rf tester tester_features tester_nofastfloat
rm -rf tester tester_features tester_nofastfloat \
tester_c tester_c_features tess_tester tester_c_freestanding
$(MAKE) -C obj-fuzz clean
$(MAKE) -C llvm-fuzz clean
217 changes: 217 additions & 0 deletions tests/tess_tester.c
Show comments View file Edit file Delete file Open in desktop
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
@@ -0,0 +1,217 @@
/*
* tess_tester.c -- unit + property tests for tobj_tess (pure C11).
* Build via tests/Makefile (`tess_tester`) with ASan+UBSan.
*/
#include "acutest.h"
#include "tobj_tess.h"

#include <math.h>
#include <string.h>

#define MAXN 4096
static uint32_t g_out[3 * MAXN];
static unsigned char g_scratch[1 << 20];

/* Run the double tessellator on an xyz ring; returns the result. */
static tobj_tess_result run_d(const double *pts, uint32_t n) {
tobj_tess_desc_d d;
memset(&d, 0, sizeof d);
d.points = pts;
d.n = n;
d.out_indices = g_out;
d.out_cap = 3 * MAXN;
d.scratch = g_scratch;
d.scratch_size = sizeof g_scratch;
tobj_tess_result r;
tobj_tess_polygon_d(&d, &r);
return r;
}

/* Shared invariant checks: count == n-2, all indices in [0,n), no repeats in a
* triangle, status not OOM/INVALID for n>=3. */
static void check_invariants(const double *pts, uint32_t n) {
tobj_tess_result r = run_d(pts, n);
TEST_CHECK_(r.status != TOBJ_TESS_OOM, "n=%u not OOM", n);
TEST_CHECK_(r.status != TOBJ_TESS_INVALID, "n=%u not INVALID", n);
TEST_CHECK_(r.num_triangles == n - 2, "n=%u tris=%u expected %u", n,
r.num_triangles, n - 2);
for (uint32_t i = 0; i < r.num_triangles * 3; i++)
TEST_CHECK_(g_out[i] < n, "idx %u in range (<%u)", g_out[i], n);
for (uint32_t t = 0; t < r.num_triangles; t++) {
uint32_t a = g_out[3 * t], b = g_out[3 * t + 1], c = g_out[3 * t + 2];
TEST_CHECK_(a != b && b != c && a != c, "tri %u has distinct corners", t);
}
}

static void test_n_too_small(void) {
double p[] = {0, 0, 0, 1, 1, 1};
tobj_tess_result r = run_d(p, 2);
TEST_CHECK(r.status == TOBJ_TESS_INVALID);
TEST_CHECK(r.num_triangles == 0);
}

static void test_triangle(void) {
double p[] = {0, 0, 0, 1, 0, 0, 0, 1, 0};
check_invariants(p, 3);
}

static void test_square(void) {
double p[] = {0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0};
check_invariants(p, 4);
}

static void test_concave_L(void) {
double p[] = {0, 0, 0, 2, 0, 0, 2, 1, 0, 1, 1, 0, 1, 2, 0, 0, 2, 0};
check_invariants(p, 6);
}

static void test_collinear_spike(void) {
double p[] = {0, 0, 0, 1, 0, 0, 2, 0, 0, 2, 2, 0, 0, 2, 0};
check_invariants(p, 5);
}

static void test_bowtie(void) {
double p[] = {0, 0, 0, 2, 2, 0, 2, 0, 0, 0, 2, 0};
tobj_tess_result r = run_d(p, 4);
TEST_CHECK(r.num_triangles == 2);
TEST_CHECK(r.status == TOBJ_TESS_DEGENERATE_BESTEFFORT);
}

static void test_big_coord_sliver(void) {
/* regression for the absolute-epsilon bug: tiny sliver at magnitude ~14000 */
double p[] = {14000, 14000, 0, 14000.001, 14000, 0,
14000.0005, 14000.0008, 0, 14000, 14000.001, 0};
check_invariants(p, 4);
}

static void test_coincident(void) {
double p[] = {5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5};
check_invariants(p, 5);
}

static void test_nonplanar(void) {
double p[] = {0, 0, 0, 2, 0, 0.3, 3, 1, -0.2,
2, 2, 0.1, 0, 2, 0.4, -1, 1, -0.3};
check_invariants(p, 6);
}

static void test_newell_zero(void) {
/* fully collinear: degenerate but must not crash, still n-2 tris */
double p[] = {0, 0, 0, 1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4};
tobj_tess_result r = run_d(p, 5);
TEST_CHECK(r.num_triangles == 3);
for (uint32_t i = 0; i < 9; i++) TEST_CHECK(g_out[i] < 5);
}

static void test_big_convex_fan(void) {
static double p[3 * 1000];
for (int i = 0; i < 1000; i++) {
double a = 2.0 * 3.14159265358979323846 * i / 1000.0;
p[3 * i] = cos(a);
p[3 * i + 1] = sin(a);
p[3 * i + 2] = 0;
}
check_invariants(p, 1000);
}

static void test_star_concave(void) {
static double p[3 * 12];
for (int i = 0; i < 12; i++) {
double a = 2.0 * 3.14159265358979323846 * i / 12.0;
double rr = (i & 1) ? 0.4 : 1.0;
p[3 * i] = rr * cos(a);
p[3 * i + 1] = rr * sin(a);
p[3 * i + 2] = 0;
}
check_invariants(p, 12);
}

/* Property: total triangle area ~= polygon area for a simple convex polygon. */
static double tri_area(const double *a, const double *b, const double *c) {
return 0.5 * fabs((b[0] - a[0]) * (c[1] - a[1]) -
(b[1] - a[1]) * (c[0] - a[0]));
}
static void test_area_preservation(void) {
/* planar (z=0) regular heptagon */
uint32_t n = 7;
static double p[3 * 7];
for (uint32_t i = 0; i < n; i++) {
double a = 2.0 * 3.14159265358979323846 * i / n;
p[3 * i] = 3.0 * cos(a);
p[3 * i + 1] = 3.0 * sin(a);
p[3 * i + 2] = 0;
}
/* shoelace area */
double poly = 0;
for (uint32_t i = 0; i < n; i++) {
uint32_t j = (i + 1) % n;
poly += p[3 * i] * p[3 * j + 1] - p[3 * j] * p[3 * i + 1];
}
poly = fabs(poly) * 0.5;
tobj_tess_result r = run_d(p, n);
double sum = 0;
for (uint32_t t = 0; t < r.num_triangles; t++)
sum += tri_area(&p[3 * g_out[3 * t]], &p[3 * g_out[3 * t + 1]],
&p[3 * g_out[3 * t + 2]]);
TEST_CHECK_(fabs(sum - poly) < 1e-6 * poly, "tri area %.6f vs poly %.6f", sum,
poly);
}

/* Property: deterministic across two independent scratch buffers. */
static void test_determinism(void) {
double p[] = {0, 0, 0, 2, 0, 0, 2, 1, 0, 1, 1, 0, 1, 2, 0, 0, 2, 0};
uint32_t n = 6;
uint32_t out1[3 * 6], out2[3 * 6];
unsigned char s1[4096], s2[4096];
tobj_tess_desc_d d;
memset(&d, 0, sizeof d);
d.points = p;
d.n = n;
tobj_tess_result r1, r2;
d.out_indices = out1;
d.out_cap = 18;
d.scratch = s1;
d.scratch_size = sizeof s1;
tobj_tess_polygon_d(&d, &r1);
d.out_indices = out2;
d.scratch = s2;
d.scratch_size = sizeof s2;
tobj_tess_polygon_d(&d, &r2);
TEST_CHECK(r1.num_triangles == r2.num_triangles);
for (uint32_t i = 0; i < r1.num_triangles * 3; i++)
TEST_CHECK(out1[i] == out2[i]);
}

/* float variant smoke: must match counts. */
static void test_float_variant(void) {
float p[] = {0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0.5f, 1.5f, 0};
tobj_tess_desc_f d;
memset(&d, 0, sizeof d);
d.points = p;
d.n = 5;
d.out_indices = g_out;
d.out_cap = 3 * MAXN;
d.scratch = g_scratch;
d.scratch_size = sizeof g_scratch;
tobj_tess_result r;
tobj_tess_polygon_f(&d, &r);
TEST_CHECK(r.num_triangles == 3);
}

TEST_LIST = {
{"n_too_small", test_n_too_small},
{"triangle", test_triangle},
{"square", test_square},
{"concave_L", test_concave_L},
{"collinear_spike", test_collinear_spike},
{"bowtie", test_bowtie},
{"big_coord_sliver", test_big_coord_sliver},
{"coincident", test_coincident},
{"nonplanar", test_nonplanar},
{"newell_zero", test_newell_zero},
{"big_convex_fan", test_big_convex_fan},
{"star_concave", test_star_concave},
{"area_preservation", test_area_preservation},
{"determinism", test_determinism},
{"float_variant", test_float_variant},
{NULL, NULL}};
Loading
Loading
Toggle all file notes Toggle all file annotations

Footer

© 2026 GitHub, Inc.