Gama C Library
Gama C API Documentation
gltf.h
Go to the documentation of this file.
1/**
2 * @file gltf.h
3 * @brief Implements a glTF 3D model file loader.
4 *
5 * This file provides functionality to parse glTF (.gltf, .glb) files and load their
6 * geometric and material data into a `gm3Mesh` structure. It uses the `cgltf`
7 * library for core glTF parsing.
8 */
9#pragma once
10
11#include <stdio.h>
12#include <string.h>
13
14// -- UTILS --
15/**
16 * @def GM_DYN_ARRAY_INIT_CAP
17 * @internal
18 * @brief Initial capacity for dynamic arrays used in glTF loading.
19 */
20#define GM_DYN_ARRAY_INIT_CAP 256
21/**
22 * @def GM_DYN_ARRAY_APPEND
23 * @internal
24 * @brief Macro to append an item to a dynamically resizing array.
25 *
26 * This macro automatically reallocates the array if its capacity is exceeded.
27 *
28 * @param arr The array pointer (e.g., `gm3Pos*`).
29 * @param count The current number of elements in the array.
30 * @param capacity The current allocated capacity of the array.
31 * @param item The item to append to the array.
32 */
33#define GM_DYN_ARRAY_APPEND(arr, count, capacity, item) \
34 do { \
35 if ((count) >= (capacity)) { \
36 (capacity) = (capacity) == 0 ? GM_DYN_ARRAY_INIT_CAP : (capacity)*2; \
37 (arr) = realloc((arr), (capacity) * sizeof(*(arr))); \
38 } \
39 (arr)[(count)++] = (item); \
40 } while (0)
41
42// -- HEADERS --
43#include "mesh.h"
44#include "position.h"
45
46// Forward declarations
47/**
48 * @brief Loads a glTF 3D model from a file.
49 * @param mesh A pointer to the `gm3Mesh` structure to populate.
50 * @param path The file path to the glTF model (.gltf or .glb).
51 * @return 0 on success, -1 on failure.
52 */
53int gm3_gltf_load(gm3Mesh *mesh, const char *path);
54/**
55 * @brief Prints a diagnostic overview of a glTF file to the console.
56 * @param path The file path to the glTF model (.gltf or .glb).
57 * @return 0 on success, -1 on failure.
58 */
59int gmd_gltf_print(const char *path);
60
61// -- IMPLEMENTATION --
62
63#define CGLTF_IMPLEMENTATION
64#include "../../cgltf.h"
65
66/**
67 * @internal
68 * @brief Recursively prints information about a glTF node and its children.
69 * @param node A pointer to the `cgltf_node` to print.
70 * @param level The current recursion depth for indentation.
71 */
72static void gmd_gltf_print_node(cgltf_node *node, int level) {
73 for (int i = 0; i < level; ++i)
74 printf(" ");
75 printf("Node: %s\n", node->name ? node->name : "(unnamed)");
76
77 if (node->mesh) {
78 for (int i = 0; i < level + 1; ++i)
79 printf(" ");
80 printf("Mesh: %s (%zu primitives)\n",
81 node->mesh->name ? node->mesh->name : "(unnamed)",
82 node->mesh->primitives_count);
83 }
84
85 for (size_t i = 0; i < node->children_count; ++i) {
86 gmd_gltf_print_node(node->children[i], level + 1);
87 }
88}
89
90/**
91 * @brief Prints a diagnostic overview of a glTF file to the console.
92 * @param path The file path to the glTF model (.gltf or .glb).
93 * @return 0 on success, -1 on failure.
94 */
95int gmd_gltf_print(const char *path) {
96 cgltf_options options = {0};
97 cgltf_data *data = NULL;
98 cgltf_result result = cgltf_parse_file(&options, path, &data);
99 if (result != cgltf_result_success) {
100 printf("gmd_gltf_print: Failed to parse GLTF file: %s\n", path);
101 return -1;
102 }
103
104 printf("--- GLTF Inspector: %s ---\n", path);
105 printf("Scenes: %zu\n", data->scenes_count);
106 if (data->scene) {
107 printf("Default Scene: '%s' (%zu root nodes)\n",
108 data->scene->name ? data->scene->name : "unnamed",
109 data->scene->nodes_count);
110 for (size_t i = 0; i < data->scene->nodes_count; ++i) {
111 gmd_gltf_print_node(data->scene->nodes[i], 1);
112 }
113 }
114 printf("Meshes: %zu\n", data->meshes_count);
115 printf("Materials: %zu\n", data->materials_count);
116 printf("Textures: %zu\n", data->textures_count);
117 printf("Images: %zu\n", data->images_count);
118 printf("---------------------------------\n");
119
120 cgltf_free(data);
121 return 0;
122}
123
124/**
125 * @internal
126 * @brief Transforms a 3D position vector by a 4x4 matrix.
127 * @param dst A pointer to the `gm3Pos` to store the transformed result.
128 * @param src A pointer to the source `gm3Pos` vector.
129 * @param m A pointer to the 4x4 transformation matrix (column-major).
130 */
131static void transform_pos(gm3Pos *dst, const gm3Pos *src,
132 const cgltf_float *m) {
133 dst->x = src->x * m[0] + src->y * m[4] + src->z * m[8] + m[12];
134 dst->y = src->x * m[1] + src->y * m[5] + src->z * m[9] + m[13];
135 dst->z = src->x * m[2] + src->y * m[6] + src->z * m[10] + m[14];
136}
137
138/**
139 * @internal
140 * @brief Transforms a 3D normal vector by a 4x4 matrix (applying only rotation).
141 * @param dst A pointer to the `gm3Pos` to store the transformed normal.
142 * @param src A pointer to the source `gm3Pos` normal vector.
143 * @param m A pointer to the 4x4 transformation matrix (column-major).
144 */
145static void transform_normal(gm3Pos *dst, const gm3Pos *src,
146 const cgltf_float *m) {
147 // Apply the 3x3 rotation part of the matrix
148 dst->x = src->x * m[0] + src->y * m[4] + src->z * m[8];
149 dst->y = src->x * m[1] + src->y * m[5] + src->z * m[9];
150 dst->z = src->x * m[2] + src->y * m[6] + src->z * m[10];
151 gm3_pos_normalize(dst);
152}
153
154/**
155 * @brief Loads a glTF 3D model from a file into a `gm3Mesh` structure.
156 *
157 * This function parses the glTF file, including geometric data (vertices,
158 * normals, texture coordinates), material properties, and textures. It
159 * dynamically allocates memory for the mesh components as it encounters them.
160 *
161 * @param mesh A pointer to the `gm3Mesh` structure to populate.
162 * @param path The file path to the glTF model (.gltf or .glb).
163 * @return 0 on success, -1 on file parsing or loading failure.
164 */
165int gm3_gltf_load(gm3Mesh *mesh, const char *path) {
166 memset(mesh, 0, sizeof(gm3Mesh));
167
168 cgltf_options options = {0};
169 cgltf_data *data = NULL;
170 if (cgltf_parse_file(&options, path, &data) != cgltf_result_success)
171 return -1;
172 if (cgltf_load_buffers(&options, data, path) != cgltf_result_success) {
173 cgltf_free(data);
174 return -1;
175 }
176
177 // Use dynamic arrays to avoid miscalculating buffer sizes
178 size_t vertices_cap = 0, normals_cap = 0, texs_cap = 0, faces_cap = 0;
179
180 // Create a single material library for this glTF file
181 mesh->n_mtllibs = 1;
182 mesh->mtllibs = calloc(1, sizeof(gm3MtlLib));
183 gm3MtlLib *mtllib = &mesh->mtllibs[0];
184 strcpy(mtllib->name, "gltf_materials");
185
186 // Pre-load all images into our material lib's textures
187 mtllib->n_textures = data->images_count;
188 if (data->images_count > 0) {
189 mtllib->textures = calloc(mtllib->n_textures, sizeof(gm3Texture));
190 for (size_t i = 0; i < data->images_count; ++i) {
191 cgltf_image *gimg = &data->images[i];
192 gm3Texture *dtex = &mtllib->textures[i];
193 if (gimg->buffer_view) {
194 snprintf(dtex->path, sizeof(dtex->path), "gltf_embedded_image_%zu", i);
196 &dtex->data,
197 (unsigned char *)gimg->buffer_view->buffer->data +
198 gimg->buffer_view->offset,
199 gimg->buffer_view->size);
200 } else if (gimg->uri) {
201 char dir[256] = ".";
202 const char *slash = strrchr(path, '/');
203 if (slash) {
204 size_t dir_len = slash - path;
205 // Ensure null-termination and prevent buffer overflow
206 if (dir_len < sizeof(dir)) {
207 memcpy(dir, path, dir_len);
208 dir[dir_len] = '\0';
209 } else {
210 // Handle case where path is too long for dir buffer
211 strncpy(dir, path, sizeof(dir) - 1);
212 dir[sizeof(dir) - 1] = '\0';
213 }
214 }
215 snprintf(dtex->path, sizeof(dtex->path), "%s/%s", dir, gimg->uri);
216 gm_image_data_load(&dtex->data, dtex->path);
217 }
218 }
219 }
220
221 // Process materials, linking to pre-loaded textures
222 mtllib->n_materials = data->materials_count;
223 if (data->materials_count > 0) {
224 mtllib->materials = calloc(mtllib->n_materials, sizeof(gm3Material));
225 for (size_t i = 0; i < data->materials_count; ++i) {
226 cgltf_material *gmat = &data->materials[i];
227 gm3Material *dmat = &mtllib->materials[i];
228 *dmat = (gm3Material){.tex_diffuse = -1,
229 .tex_emissive = -1,
230 .tex_specular = -1,
231 .tex_alpha = -1};
232 snprintf(dmat->name, sizeof(dmat->name), "%s",
233 gmat->name ? gmat->name : "default");
234
235 if (gmat->has_pbr_metallic_roughness) {
236 cgltf_pbr_metallic_roughness *pbr = &gmat->pbr_metallic_roughness;
237 dmat->alpha = pbr->base_color_factor[3];
238
239 if (pbr->base_color_texture.texture &&
240 pbr->base_color_texture.texture->image) {
241 dmat->tex_diffuse =
242 pbr->base_color_texture.texture->image - data->images;
243 // New: Calculate average color from texture
244 gm3Texture *tex = &mtllib->textures[dmat->tex_diffuse];
246
247 } else {
248 // Fallback to base color factor if no texture
249 dmat->diffuse =
250 gm_rgb(pbr->base_color_factor[0] * 255,
251 pbr->base_color_factor[1] * 255,
252 pbr->base_color_factor[2] * 255);
253 }
254 }
255 }
256 }
257
258 // Process nodes and primitives
259 for (size_t i = 0; i < data->nodes_count; ++i) {
260 cgltf_node *node = &data->nodes[i];
261 if (!node->mesh)
262 continue;
263
264 cgltf_float matrix[16];
265 cgltf_node_transform_world(node, matrix);
266
267 for (size_t j = 0; j < node->mesh->primitives_count; ++j) {
268 cgltf_primitive *prim = &node->mesh->primitives[j];
269 if (prim->type != cgltf_primitive_type_triangles || !prim->indices)
270 continue;
271
272 size_t v_base_idx = mesh->n_vertices;
273 size_t t_base_idx = mesh->n_texs;
274
275 cgltf_accessor *pos_acc = NULL, *nrm_acc = NULL, *uv_acc = NULL;
276 for (size_t k = 0; k < prim->attributes_count; k++) {
277 if (prim->attributes[k].type == cgltf_attribute_type_position)
278 pos_acc = prim->attributes[k].data;
279 else if (prim->attributes[k].type == cgltf_attribute_type_normal)
280 nrm_acc = prim->attributes[k].data;
281 else if (prim->attributes[k].type == cgltf_attribute_type_texcoord)
282 uv_acc = prim->attributes[k].data;
283 }
284 if (!pos_acc)
285 continue;
286
287 for (size_t k = 0; k < pos_acc->count; k++) {
288 float p_float[3], n_float[3], t_float[2];
289 gm3Pos p, n;
290 gm3Tex t;
291
292 cgltf_accessor_read_float(pos_acc, k, p_float, 3);
293 p.x = p_float[0];
294 p.y = p_float[1];
295 p.z = p_float[2];
296 transform_pos(&p, &p, matrix);
297 GM_DYN_ARRAY_APPEND(mesh->vertices, mesh->n_vertices, vertices_cap, p);
298
299 if (nrm_acc) {
300 cgltf_accessor_read_float(nrm_acc, k, n_float, 3);
301 n.x = n_float[0];
302 n.y = n_float[1];
303 n.z = n_float[2];
304 transform_normal(&n, &n, matrix);
305 GM_DYN_ARRAY_APPEND(mesh->normals, mesh->n_normals, normals_cap, n);
306 }
307 if (uv_acc) {
308 cgltf_accessor_read_float(uv_acc, k, t_float, 2);
309 t.u = t_float[0];
310 t.v = t_float[1];
311 GM_DYN_ARRAY_APPEND(mesh->texs, mesh->n_texs, texs_cap, t);
312 }
313 }
314
315 for (size_t k = 0; k < prim->indices->count; k += 3) {
316 gm3MeshFace face = {0};
317 face.vertices[0] =
318 v_base_idx + cgltf_accessor_read_index(prim->indices, k + 0);
319 face.vertices[1] =
320 v_base_idx + cgltf_accessor_read_index(prim->indices, k + 1);
321 face.vertices[2] =
322 v_base_idx + cgltf_accessor_read_index(prim->indices, k + 2);
323 if (uv_acc) {
324 face.uvs[0] =
325 t_base_idx + cgltf_accessor_read_index(prim->indices, k + 0);
326 face.uvs[1] =
327 t_base_idx + cgltf_accessor_read_index(prim->indices, k + 1);
328 face.uvs[2] =
329 t_base_idx + cgltf_accessor_read_index(prim->indices, k + 2);
330 }
331 face.material =
332 prim->material ? (int)(prim->material - data->materials) : -1;
333 face.material_file = 0;
334
335 gm3Pos p0 = mesh->vertices[face.vertices[0]];
336 gm3Pos p1 = mesh->vertices[face.vertices[1]];
337 gm3Pos p2 = mesh->vertices[face.vertices[2]];
338 gm3Pos e1 = p1;
339 gm3_pos_substract(&e1, &p0);
340 gm3Pos e2 = p2;
341 gm3_pos_substract(&e2, &p0);
342 face.normal = gm3_pos_cross(e1, e2);
343 gm3_pos_normalize(&face.normal);
344 GM_DYN_ARRAY_APPEND(mesh->faces, mesh->n_faces, faces_cap, face);
345 }
346 }
347 }
348
349 gm3_mesh_center(mesh);
350 cgltf_free(data);
351 return 0;
352}
#define gm3_pos_cross(a, b)
Calculates the cross product of two gm3Pos vectors (a x b).
Definition position.h:146
int gmd_gltf_print(const char *path)
Prints a diagnostic overview of a glTF file to the console.
Definition gltf.h:95
int gm3_gltf_load(gm3Mesh *mesh, const char *path)
Loads a glTF 3D model from a file.
Definition gltf.h:165
#define GM_DYN_ARRAY_APPEND(arr, count, capacity, item)
Definition gltf.h:33
int32_t gm_image_data_load_from_memory(gmImageData *data, const unsigned char *buffer, int len)
Loads image data from an in-memory buffer.
Definition image.h:42
gmColor gm_image_data_average_color(const gmImageData *data)
Calculates the average color of an image's raw pixel data.
Definition image.h:67
int32_t gm_image_data_load(gmImageData *data, const char *path)
Loads image file from disk into a gmImageData struct.
Definition image.h:29
void * calloc(size_t count, size_t size)
Custom implementation of calloc using a static memory pool.
Definition malloc.h:215
int gm3_mesh_center(gm3Mesh *m)
Centers the mesh geometry around the origin (0,0,0).
Definition mesh.h:86
Represents a single 3D material with various rendering properties.
Definition mtl.h:26
long tex_diffuse
Definition mtl.h:34
double alpha
Definition mtl.h:31
gmColor diffuse
Definition mtl.h:28
char name[64]
Definition mtl.h:27
Represents a single face (triangle) in a 3D mesh.
Definition mesh.h:10
int material
Definition mesh.h:13
int material_file
Definition mesh.h:14
size_t vertices[3]
Definition mesh.h:11
long uvs[3]
Definition mesh.h:12
gm3Pos normal
Definition mesh.h:15
Represents a 3D mesh composed of vertices, faces, normals, and texture coordinates.
Definition mesh.h:30
size_t n_mtllibs
Definition mesh.h:44
size_t n_normals
Definition mesh.h:38
gm3Tex * texs
Definition mesh.h:40
size_t n_texs
Definition mesh.h:41
gm3MeshFace * faces
Definition mesh.h:34
size_t n_vertices
Definition mesh.h:32
gm3Pos * vertices
Definition mesh.h:31
gm3MtlLib * mtllibs
Definition mesh.h:43
gm3Pos * normals
Definition mesh.h:37
size_t n_faces
Definition mesh.h:35
Represents a material library, typically loaded from an .mtl file.
Definition mtl.h:53
size_t n_textures
Definition mtl.h:60
gm3Material * materials
Definition mtl.h:56
char name[256]
Definition mtl.h:54
size_t n_materials
Definition mtl.h:57
gm3Texture * textures
Definition mtl.h:59
Represents a 3D position or vector.
Definition position.h:11
double y
Definition position.h:12
double z
Definition position.h:12
double x
Definition position.h:12
Represents a 2D texture coordinate.
Definition mesh.h:21
double v
Definition mesh.h:22
double u
Definition mesh.h:22
Represents a 3D texture, including its raw image data and file path.
Definition mtl.h:43
gmImageData data
Definition mtl.h:44
char path[256]
Definition mtl.h:45
unsigned char * data
Definition image.h:20