Skip to content

Commit c8f2aca

Browse files
committed
feat: add asset path resolution for installed builds
Add asset_path module that resolves asset paths with fallback: 1. User data dir (~/Library/Application Support/ on macOS, XDG_DATA_HOME on Linux) 2. System install dir (compile-time MAVSIM_INSTALL_DATADIR) 3. Relative path (dev/build fallback) Update all asset load sites in scene, hud, and vehicle to use asset_path(). Add CMake install() rules for binary and assets.
1 parent 76ecd2f commit c8f2aca

File tree

7 files changed

+192
-8
lines changed

7 files changed

+192
-8
lines changed

CMakeLists.txt

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ if(NOT BUILD_TESTING_ONLY)
5050
src/hud.c
5151
src/debug_panel.c
5252
src/ortho_panel.c
53+
src/asset_path.c
5354
)
5455

5556
target_include_directories(mavsim-viewer PRIVATE
@@ -85,4 +86,21 @@ if(NOT BUILD_TESTING_ONLY)
8586
COMMAND ${CMAKE_COMMAND} -E copy_directory
8687
${CMAKE_SOURCE_DIR}/textures $<TARGET_FILE_DIR:mavsim-viewer>/textures
8788
)
89+
90+
# Install rules
91+
include(GNUInstallDirs)
92+
93+
target_compile_definitions(mavsim-viewer PRIVATE
94+
MAVSIM_INSTALL_DATADIR="${CMAKE_INSTALL_FULL_DATADIR}/mavsim-viewer")
95+
96+
install(TARGETS mavsim-viewer RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
97+
install(DIRECTORY models/ DESTINATION ${CMAKE_INSTALL_DATADIR}/mavsim-viewer/models
98+
FILES_MATCHING PATTERN "*.obj" PATTERN "*.mtl")
99+
install(DIRECTORY shaders/ DESTINATION ${CMAKE_INSTALL_DATADIR}/mavsim-viewer/shaders
100+
FILES_MATCHING PATTERN "*.vs" PATTERN "*.fs")
101+
install(DIRECTORY fonts/ DESTINATION ${CMAKE_INSTALL_DATADIR}/mavsim-viewer/fonts
102+
FILES_MATCHING PATTERN "*.ttf")
103+
install(DIRECTORY textures/ DESTINATION ${CMAKE_INSTALL_DATADIR}/mavsim-viewer/textures
104+
FILES_MATCHING PATTERN "*.png")
105+
install(FILES fonts/OFL.txt DESTINATION ${CMAKE_INSTALL_DATADIR}/mavsim-viewer/fonts)
88106
endif()

src/asset_path.c

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
#include "asset_path.h"
2+
3+
#include <stdio.h>
4+
#include <string.h>
5+
#include <stdlib.h>
6+
#include <sys/stat.h>
7+
8+
#ifdef _WIN32
9+
#include <direct.h>
10+
#define mkdir_p(path) _mkdir(path)
11+
#else
12+
#include <unistd.h>
13+
#define mkdir_p(path) mkdir(path, 0755)
14+
#endif
15+
16+
// Cached base directories (set once in asset_path_init)
17+
static char s_user_data[512]; // user-writable data dir
18+
static char s_install_data[512]; // system install dir (compile-time)
19+
20+
static int file_exists(const char *path) {
21+
#ifdef _WIN32
22+
struct _stat st;
23+
return _stat(path, &st) == 0;
24+
#else
25+
return access(path, R_OK) == 0;
26+
#endif
27+
}
28+
29+
// Recursively create directories for a file path
30+
static void mkdirs_for_file(const char *filepath) {
31+
char tmp[512];
32+
snprintf(tmp, sizeof(tmp), "%s", filepath);
33+
34+
// Find last separator
35+
char *last_sep = strrchr(tmp, '/');
36+
#ifdef _WIN32
37+
char *last_bsep = strrchr(tmp, '\\');
38+
if (last_bsep > last_sep) last_sep = last_bsep;
39+
#endif
40+
if (!last_sep) return;
41+
*last_sep = '\0';
42+
43+
// Create each directory component
44+
for (char *p = tmp + 1; *p; p++) {
45+
if (*p == '/'
46+
#ifdef _WIN32
47+
|| *p == '\\'
48+
#endif
49+
) {
50+
*p = '\0';
51+
mkdir_p(tmp);
52+
*p = '/';
53+
}
54+
}
55+
mkdir_p(tmp);
56+
}
57+
58+
void asset_path_init(void) {
59+
s_user_data[0] = '\0';
60+
s_install_data[0] = '\0';
61+
62+
#ifdef __APPLE__
63+
const char *home = getenv("HOME");
64+
if (home) {
65+
snprintf(s_user_data, sizeof(s_user_data),
66+
"%s/Library/Application Support/mavsim-viewer", home);
67+
}
68+
#elif !defined(_WIN32)
69+
// Linux: XDG_DATA_HOME or ~/.local/share
70+
const char *xdg = getenv("XDG_DATA_HOME");
71+
if (xdg && xdg[0]) {
72+
snprintf(s_user_data, sizeof(s_user_data),
73+
"%s/mavsim-viewer", xdg);
74+
} else {
75+
const char *home = getenv("HOME");
76+
if (home) {
77+
snprintf(s_user_data, sizeof(s_user_data),
78+
"%s/.local/share/mavsim-viewer", home);
79+
}
80+
}
81+
#endif
82+
83+
// Compile-time install prefix (set by CMake)
84+
#ifdef MAVSIM_INSTALL_DATADIR
85+
snprintf(s_install_data, sizeof(s_install_data),
86+
"%s", MAVSIM_INSTALL_DATADIR);
87+
#endif
88+
}
89+
90+
void asset_path(const char *subpath, char *out, size_t out_size) {
91+
char candidate[512];
92+
93+
// 1. User data directory
94+
if (s_user_data[0]) {
95+
snprintf(candidate, sizeof(candidate), "%s/%s", s_user_data, subpath);
96+
if (file_exists(candidate)) {
97+
snprintf(out, out_size, "%s", candidate);
98+
return;
99+
}
100+
}
101+
102+
// 2. System install directory
103+
if (s_install_data[0]) {
104+
snprintf(candidate, sizeof(candidate), "%s/%s", s_install_data, subpath);
105+
if (file_exists(candidate)) {
106+
snprintf(out, out_size, "%s", candidate);
107+
return;
108+
}
109+
}
110+
111+
// 3. Relative path (dev/build fallback)
112+
snprintf(out, out_size, "%s", subpath);
113+
}
114+
115+
void asset_write_path(const char *subpath, char *out, size_t out_size) {
116+
if (s_user_data[0]) {
117+
snprintf(out, out_size, "%s/%s", s_user_data, subpath);
118+
mkdirs_for_file(out);
119+
} else {
120+
// Fallback to relative path
121+
snprintf(out, out_size, "%s", subpath);
122+
}
123+
}

src/asset_path.h

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
#ifndef ASSET_PATH_H
2+
#define ASSET_PATH_H
3+
4+
#include <stddef.h>
5+
6+
// Initialize asset path resolution. Call once at startup.
7+
void asset_path_init(void);
8+
9+
// Resolve an asset subpath (e.g. "models/px4_quadrotor.obj") to a full path.
10+
// Writes into caller-provided buffer. Search order:
11+
// macOS: ~/Library/Application Support/mavsim-viewer/<subpath>
12+
// Linux: $XDG_DATA_HOME/mavsim-viewer/<subpath>
13+
// Then: MAVSIM_INSTALL_DATADIR/<subpath> (compile-time install prefix)
14+
// Then: ./<subpath> (dev/build fallback)
15+
void asset_path(const char *subpath, char *out, size_t out_size);
16+
17+
// Get a writable path for an asset (for generated files like terrain texture).
18+
// Always returns the user data directory path, creating directories as needed.
19+
// macOS: ~/Library/Application Support/mavsim-viewer/<subpath>
20+
// Linux: $XDG_DATA_HOME/mavsim-viewer/<subpath>
21+
void asset_write_path(const char *subpath, char *out, size_t out_size);
22+
23+
#endif

src/hud.c

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
#include "hud.h"
2+
#include "asset_path.h"
23
#include "raylib.h"
34
#include "raymath.h"
45

@@ -44,8 +45,11 @@ void hud_init(hud_t *h) {
4445
for (int i = 0; i < HUD_MAX_PINNED; i++)
4546
h->pinned[i] = -1;
4647

47-
h->font_value = LoadFontEx("fonts/JetBrainsMono-Medium.ttf", 64, NULL, 0);
48-
h->font_label = LoadFontEx("fonts/Inter-Medium.ttf", 48, NULL, 0);
48+
char font_path[512];
49+
asset_path("fonts/JetBrainsMono-Medium.ttf", font_path, sizeof(font_path));
50+
h->font_value = LoadFontEx(font_path, 64, NULL, 0);
51+
asset_path("fonts/Inter-Medium.ttf", font_path, sizeof(font_path));
52+
h->font_label = LoadFontEx(font_path, 48, NULL, 0);
4953

5054
SetTextureFilter(h->font_value.texture, TEXTURE_FILTER_BILINEAR);
5155
SetTextureFilter(h->font_label.texture, TEXTURE_FILTER_BILINEAR);

src/main.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
#include "hud.h"
1616
#include "debug_panel.h"
1717
#include "ortho_panel.h"
18+
#include "asset_path.h"
1819

1920
#define MAX_VEHICLES 16
2021

@@ -96,6 +97,8 @@ int main(int argc, char *argv[]) {
9697
}
9798
}
9899

100+
asset_path_init();
101+
99102
// Init Raylib
100103
SetConfigFlags(FLAG_MSAA_4X_HINT | FLAG_WINDOW_RESIZABLE);
101104
InitWindow(win_w, win_h, "MAVSim Viewer");

src/scene.c

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
#include "scene.h"
2+
#include "asset_path.h"
23
#include "raymath.h"
34
#include "rlgl.h"
45
#include <stdlib.h>
@@ -241,7 +242,10 @@ void scene_init(scene_t *s) {
241242
};
242243

243244
// Grid shader and plane (100x100 subdivisions for fwidth() precision)
244-
s->grid_shader = LoadShader("shaders/grid.vs", "shaders/grid.fs");
245+
char vs_path[512], fs_path[512];
246+
asset_path("shaders/grid.vs", vs_path, sizeof(vs_path));
247+
asset_path("shaders/grid.fs", fs_path, sizeof(fs_path));
248+
s->grid_shader = LoadShader(vs_path, fs_path);
245249
s->loc_colGround = GetShaderLocation(s->grid_shader, "colGround");
246250
s->loc_colMinor = GetShaderLocation(s->grid_shader, "colMinor");
247251
s->loc_colMajor = GetShaderLocation(s->grid_shader, "colMajor");
@@ -259,13 +263,17 @@ void scene_init(scene_t *s) {
259263

260264
// Load terrain texture from pre-baked PNG
261265
double t0 = GetTime();
262-
Image terrain_img = LoadImage("textures/terrain.png");
266+
char terrain_path[512];
267+
asset_path("textures/terrain.png", terrain_path, sizeof(terrain_path));
268+
Image terrain_img = LoadImage(terrain_path);
263269
if (terrain_img.data == NULL) {
264-
// Fallback: generate procedurally and export
270+
// Fallback: generate procedurally and export to user data dir
265271
printf("Generating terrain texture...\n");
266272
s->ground_tex = gen_ground_texture();
267273
Image export_img = LoadImageFromTexture(s->ground_tex);
268-
ExportImage(export_img, "textures/terrain.png");
274+
char terrain_write[512];
275+
asset_write_path("textures/terrain.png", terrain_write, sizeof(terrain_write));
276+
ExportImage(export_img, terrain_write);
269277
UnloadImage(export_img);
270278
printf("Terrain texture: %dx%d (%d tiles) exported to textures/terrain.png\n",
271279
GTEX_ATLAS_W, GTEX_ATLAS_H, GTEX_VARIANTS);
@@ -285,7 +293,9 @@ void scene_init(scene_t *s) {
285293
s->grid_plane.materials[0].shader = s->grid_shader;
286294

287295
// Vehicle lighting shader — single directional light
288-
s->lighting_shader = LoadShader("shaders/lighting.vs", "shaders/lighting.fs");
296+
asset_path("shaders/lighting.vs", vs_path, sizeof(vs_path));
297+
asset_path("shaders/lighting.fs", fs_path, sizeof(fs_path));
298+
s->lighting_shader = LoadShader(vs_path, fs_path);
289299
s->loc_lightDir = GetShaderLocation(s->lighting_shader, "lightDir");
290300
s->loc_ambient = GetShaderLocation(s->lighting_shader, "ambient");
291301
s->loc_matNormal = GetShaderLocation(s->lighting_shader, "matNormal");

src/vehicle.c

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
#include "vehicle.h"
2+
#include "asset_path.h"
23
#include "raymath.h"
34
#include "rlgl.h"
45
#ifdef _MSC_VER
@@ -74,7 +75,9 @@ void vehicle_load_model(vehicle_t *v, int model_idx) {
7475
v->pitch_offset_deg = info->pitch_offset_deg;
7576
v->yaw_offset_deg = info->yaw_offset_deg;
7677

77-
v->model = LoadModel(info->path);
78+
char model_path[512];
79+
asset_path(info->path, model_path, sizeof(model_path));
80+
v->model = LoadModel(model_path);
7881
if (v->model.meshCount == 0)
7982
printf("Warning: failed to load model %s\n", info->path);
8083

0 commit comments

Comments
 (0)