Skip to content

Commit d986c2d

Browse files
authored
Merge pull request #43 from mavlink/fix/release-build
fix: add dummy texcoords to OBJ models to prevent Release crash
2 parents af8cbec + 5c1630b commit d986c2d

File tree

9 files changed

+21673
-21504
lines changed

9 files changed

+21673
-21504
lines changed

models/fpv_hexarotor.obj

Lines changed: 2941 additions & 2940 deletions
Large diffs are not rendered by default.

models/fpv_quadrotor.obj

Lines changed: 2069 additions & 2068 deletions
Large diffs are not rendered by default.

models/px4_hexarotor.obj

Lines changed: 3229 additions & 3228 deletions
Large diffs are not rendered by default.

models/px4_quadrotor.obj

Lines changed: 2357 additions & 2356 deletions
Large diffs are not rendered by default.

models/rover_4.obj

Lines changed: 4957 additions & 4956 deletions
Large diffs are not rendered by default.

models/vtol_wing.obj

Lines changed: 5957 additions & 5956 deletions
Large diffs are not rendered by default.

scripts/fix_obj_texcoords.py

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
#!/usr/bin/env python3
2+
"""Add dummy texture coordinates to OBJ files that lack them.
3+
4+
Raylib 5.5's OBJ loader crashes in Release builds when models use the
5+
"f v//vn" face format (no texture coordinate index). This script adds a
6+
single "vt 0.0 0.0" line after the last vertex normal and rewrites all
7+
faces from "f v//vn" to "f v/1/vn".
8+
9+
Usage:
10+
python3 scripts/fix_obj_texcoords.py models/my_model.obj
11+
python3 scripts/fix_obj_texcoords.py models/*.obj
12+
"""
13+
import re
14+
import sys
15+
16+
def fix_obj(path):
17+
with open(path, 'r') as f:
18+
lines = f.readlines()
19+
20+
has_vt = any(l.startswith('vt ') for l in lines)
21+
has_bare = any('//' in l for l in lines if l.startswith('f '))
22+
23+
if has_vt and not has_bare:
24+
print(f" SKIP {path} (already has texcoords)")
25+
return False
26+
27+
# Find last vn line and insert vt after it
28+
last_vn = -1
29+
for i, line in enumerate(lines):
30+
if line.startswith('vn '):
31+
last_vn = i
32+
33+
if last_vn == -1:
34+
print(f" SKIP {path} (no vertex normals)")
35+
return False
36+
37+
if not has_vt:
38+
lines.insert(last_vn + 1, 'vt 0.0 0.0\n')
39+
40+
# Replace f v//vn with f v/1/vn
41+
fixed = 0
42+
for i, line in enumerate(lines):
43+
if line.startswith('f ') and '//' in line:
44+
lines[i] = re.sub(r'(\d+)//(\d+)', r'\1/1/\2', line)
45+
fixed += 1
46+
47+
with open(path, 'w') as f:
48+
f.writelines(lines)
49+
50+
print(f" FIXED {path} ({fixed} faces updated)")
51+
return True
52+
53+
54+
if __name__ == '__main__':
55+
if len(sys.argv) < 2:
56+
print(__doc__)
57+
sys.exit(1)
58+
59+
count = 0
60+
for path in sys.argv[1:]:
61+
if fix_obj(path):
62+
count += 1
63+
64+
print(f"\n{count} file(s) fixed." if count else "\nNo files needed fixing.")

tests/CMakeLists.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,8 @@ add_executable(test_data_source test_data_source.c)
1717
target_link_libraries(test_data_source PRIVATE mavsim-core)
1818
target_compile_definitions(test_data_source PRIVATE FIXTURES_DIR="${FIXTURES_DIR}")
1919
add_test(NAME data_source COMMAND test_data_source)
20+
21+
# OBJ model validation (catches missing texcoords that crash Raylib)
22+
add_executable(test_obj_models test_obj_models.c)
23+
target_compile_definitions(test_obj_models PRIVATE MODELS_DIR="${CMAKE_SOURCE_DIR}/models")
24+
add_test(NAME obj_models COMMAND test_obj_models)

tests/test_obj_models.c

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
#include <assert.h>
2+
#include <dirent.h>
3+
#include <stdio.h>
4+
#include <string.h>
5+
#include <stdlib.h>
6+
7+
#ifndef MODELS_DIR
8+
#define MODELS_DIR "models"
9+
#endif
10+
11+
// Raylib 5.5's OBJ loader crashes when a model has no texture coordinates.
12+
// The face format "f v//vn" (skipping texcoord index) causes an out-of-bounds
13+
// read in tinyobj_loader_c. This only manifests in Release builds.
14+
//
15+
// To fix an OBJ exported without texcoords:
16+
// 1. Add a single "vt 0.0 0.0" line after the last "vn" line
17+
// 2. Change all faces from "f v//vn" to "f v/1/vn"
18+
//
19+
// Example sed one-liner (backup first!):
20+
// sed -i 's|//|/1/|g' model.obj
21+
// sed -i '/^vn /a vt 0.0 0.0' model.obj (add after first vn block)
22+
23+
static int failures = 0;
24+
25+
static void check_obj(const char *path) {
26+
FILE *f = fopen(path, "r");
27+
if (!f) {
28+
printf(" SKIP %s (not found)\n", path);
29+
return;
30+
}
31+
32+
int has_faces = 0;
33+
int has_vt = 0;
34+
int bare_face_line = 0;
35+
int line_num = 0;
36+
char line[512];
37+
38+
while (fgets(line, sizeof(line), f)) {
39+
line_num++;
40+
if (line[0] == 'f' && line[1] == ' ') {
41+
has_faces = 1;
42+
if (!bare_face_line && strstr(line, "//"))
43+
bare_face_line = line_num;
44+
}
45+
if (line[0] == 'v' && line[1] == 't' && line[2] == ' ')
46+
has_vt = 1;
47+
}
48+
fclose(f);
49+
50+
if (!has_faces) {
51+
printf(" PASS %s (no faces)\n", path);
52+
return;
53+
}
54+
55+
if (!has_vt || bare_face_line) {
56+
printf("\n FAIL %s\n", path);
57+
if (!has_vt)
58+
printf(" Missing texture coordinates (no 'vt' lines).\n");
59+
if (bare_face_line)
60+
printf(" Uses 'f v//vn' format at line %d (missing texcoord index).\n", bare_face_line);
61+
printf(" Raylib 5.5 will segfault in Release builds.\n");
62+
printf(" Run: python3 scripts/fix_obj_texcoords.py %s\n\n", path);
63+
failures++;
64+
return;
65+
}
66+
67+
printf(" PASS %s\n", path);
68+
}
69+
70+
int main(void) {
71+
printf("test_obj_models:\n");
72+
73+
DIR *dir = opendir(MODELS_DIR);
74+
assert(dir && "Cannot open models directory");
75+
76+
struct dirent *entry;
77+
while ((entry = readdir(dir)) != NULL) {
78+
size_t len = strlen(entry->d_name);
79+
if (len > 4 && strcmp(entry->d_name + len - 4, ".obj") == 0) {
80+
char path[512];
81+
snprintf(path, sizeof(path), "%s/%s", MODELS_DIR, entry->d_name);
82+
check_obj(path);
83+
}
84+
}
85+
closedir(dir);
86+
87+
if (failures > 0) {
88+
printf("\n%d model(s) failed validation.\n", failures);
89+
return 1;
90+
}
91+
92+
printf("All OBJ models passed.\n");
93+
return 0;
94+
}

0 commit comments

Comments
 (0)