--- name: gltf_load_mesh kind: function lang: cpp domain: gfx version: "1.0.0" purity: impure signature: "Mesh gltf_load_mesh_from_file(const char* path); Mesh gltf_load_mesh_from_memory(const unsigned char* data, size_t size); const char* gltf_load_last_error()" description: "Parser GLB 2.0 (glTF binario): carga el primer mesh/primitive a CPU como fn::gfx::Mesh. Soporta POSITION+NORMAL (vec3 float), indices ubyte/ushort/uint, node transform TRS/matrix. Genera normales smooth area-weighted si faltan. Sin dependencias externas — BIN chunk + nlohmann JSON vendored." tags: [mesh, gltf, glb, 3d, loader, geometry, gfx, mesh-3d] uses_functions: [] uses_types: [] returns: [] returns_optional: false error_type: "error_go_core" imports: [gfx/mesh_obj_load.h, nlohmann/json.hpp, fstream, cstring, cmath] tested: true tests: - "invalid magic -> empty Mesh + last_error set" - "too-small buffer -> empty Mesh + last_error set" - "triangle without NORMAL -> normals generated, correct count" - "quad (2 triangles) -> positions.size()==12, indices.size()==6" - "explicit normals -> passed through unchanged" - "nonexistent file -> empty Mesh + last_error set" test_file_path: "cpp/tests/test_gltf_load_mesh.cpp" file_path: "cpp/functions/gfx/gltf_load_mesh.cpp" framework: opengl params: - name: path desc: "Ruta al archivo .glb. Solo GLB binario — .gltf+.bin separado y data-URI base64 no soportados." - name: data desc: "Puntero al buffer GLB en memoria. Debe vivir mientras dure la llamada." - name: size desc: "Longitud del buffer en bytes." output: "fn::gfx::Mesh con positions/normals (stride 3, mismo length) y indices uint32 (tri-list). Mesh vacio (positions.empty()==true) si parse falla. gltf_load_last_error() devuelve descripcion del error." notes: | Usa fn::gfx::Mesh de mesh_obj_load.h — mismo struct que consume mesh_gpu_upload(). nlohmann vendored en cpp/vendor/nlohmann/json.hpp. El parser no aloca heap mas alla del Mesh de salida + JSON temporal. gltf_load_last_error() usa thread_local — seguro en multihilo siempre que cada hilo llame sus propias funciones. --- # gltf_load_mesh Loader GLB 2.0 minimal para el registry. Parsea el contenedor GLB binario a mano (header 12 bytes + chunks JSON + BIN) usando nlohmann para el JSON. KISS: sin tinygltf ni dependencias extra. ## Ejemplo ```cpp // Cargar .glb generado por TripoSR/trimesh y subir a GPU: #include "gfx/gltf_load_mesh.h" #include "gfx/mesh_gpu.h" auto cpu = fn::gfx::gltf_load_mesh_from_file("model.glb"); if (cpu.positions.empty()) { fprintf(stderr, "gltf load failed: %s\n", fn::gfx::gltf_load_last_error()); return; } // Subir a GPU (requiere contexto GL activo): auto gpu = fn::gfx::mesh_gpu_upload(cpu); if (!gpu.ok()) { /* fallo de upload GL */ return; } glUseProgram(prog); glBindVertexArray(gpu.vao); glDrawElements(GL_TRIANGLES, gpu.index_count, GL_UNSIGNED_INT, 0); glBindVertexArray(0); fn::gfx::mesh_gpu_destroy(gpu); ``` ```cpp // Desde memoria (ej. respuesta HTTP o embedding): std::vector glb_buf = download_glb(...); auto cpu = fn::gfx::gltf_load_mesh_from_memory(glb_buf.data(), glb_buf.size()); ``` ## Cuando usarla Cuando recibes un `.glb` (binario glTF 2.0) de un backend Python (TripoSR, trimesh, open3d) y necesitas renderizarlo en una app ImGui via `mesh_gpu_upload`. Tambien util para inspeccionar geometria en CPU sin subir a GPU. ## Limitaciones - **Solo GLB binario**. `.gltf + .bin` separado: no soportado. Data URIs base64: no soportados. - **Primer mesh, primera primitive**. Archivos con multiples meshes o materiales: solo se carga el primero. - **Sin texturas ni materiales**. El Mesh solo contiene geometria (posicion + normal). El shader del viewer usa color uniforme. - **Buffer unico embebido** (chunk BIN). Referencias a buffers externos: no soportadas. - **Modo solo triangulos** (`"mode": 4`, default). Puntos, lineas, triangle-strip: no soportados. ## Gotchas - `gltf_load_last_error()` es `thread_local`. Si usas multihilo, cada hilo tiene su propio error buffer — no compartas el puntero entre hilos. - El puntero que devuelve `gltf_load_last_error()` se sobreescribe en la siguiente llamada a `gltf_load_mesh_from_*`. Copia el string si lo necesitas despues. - Un `Mesh` retornado con `positions.empty() == true` es la senal de fallo — **no** lanzamos excepciones. - Para archivos grandes (>50 MB) la lectura es un `std::vector` completo en memoria. Para streaming, usa `gltf_load_mesh_from_memory` con tu propio buffer. - El parser no valida que `indices` sean menores que `nv` en cada vertice — indices fuera de rango se saltan silenciosamente durante la generacion de normales pero pueden producir geometria incorrecta.