feat(gfx): mesh_obj_load — minimal Wavefront .obj parser

mesh_obj_parse (pure) + mesh_obj_load (impure file helper).
Soporta v / vn / f (tris y quads). Genera normales per-face si
faltan (flat shading). Quads se parten en 2 tris; n-gons (>4) se
descartan silenciosamente. Indices 1-based positivos y negativos.

issue 0029
This commit is contained in:
2026-04-25 21:51:05 +02:00
parent 6d7b802199
commit 10ac4c74db
3 changed files with 345 additions and 0 deletions
+75
View File
@@ -0,0 +1,75 @@
---
name: mesh_obj_load
kind: function
lang: cpp
domain: gfx
version: "1.0.0"
purity: pure
signature: "Mesh mesh_obj_parse(const char* obj_text, size_t len); Mesh mesh_obj_load(const char* path)"
description: "Parser minimal de Wavefront .obj — soporta v, vn, f (tris y quads). Genera normales por face si faltan. mesh_obj_parse es puro; mesh_obj_load es helper impuro que lee fichero y delega."
tags: [obj, mesh, parser, wavefront, loader, geometry, 3d]
uses_functions: []
uses_types: []
returns: []
returns_optional: false
error_type: ""
imports: [fstream, sstream, cmath]
tested: false
tests: []
test_file_path: ""
file_path: "cpp/functions/gfx/mesh_obj_load.cpp"
framework: opengl
params:
- name: obj_text
desc: "Buffer de texto .obj. Lineas reconocidas: v, vn, f. Comentarios con #. UTF-8 plano."
- name: len
desc: "Longitud del buffer en bytes"
- name: path
desc: "Ruta absoluta o relativa del .obj a leer (mesh_obj_load impuro)"
output: "Mesh con positions/normals (stride 3, mismo length) y indices (tri-list, multiplo de 3). Si no hay vn, normales por face (flat shading) y vertices duplicados por face. Mesh vacio si parse falla."
---
# mesh_obj_load
Parser pequeno de Wavefront `.obj` para inspeccion de geometria. KISS: cubre el subconjunto que la mayoria de exporters genera (Blender default, MeshLab, etc).
## Soporta
- `v x y z` — vertice (xyz; w opcional ignorado)
- `vn x y z` — normal
- `f a b c` y `f a b c d` — tris y quads (los quads se dividen en 2 tris)
- Indices `v`, `v/t`, `v//n`, `v/t/n` (vt se ignora; t no afecta)
- Indices 1-based positivos y negativos (relative-to-end, conforme spec)
- Lineas en blanco y comentarios con `#`
## NO soporta (en este issue)
- N-gons con mas de 4 vertices → silenciosamente descartados
- `vt` (coordenadas de textura) — el indice se parsea pero el dato se ignora
- `mtllib`, `usemtl`, `o`, `g`, `s` — se saltan
- Lineas de polilineas (`l`)
- Curvas, superficies, `vp`
## Generacion de normales
Si el `.obj` no tiene `vn` (o si alguna face no las referencia), se generan normales por face (flat shading). Esto duplica vertices entre faces que comparten posicion pero no normal — es el comportamiento esperado para inspeccion.
Cuando todas las faces tienen normales explicitas, se hace dedup `(v_idx, n_idx)` con un linear-scan simple. Para meshes inspeccion (<<100k verts unicos) es perf-OK; meshes mas grandes se beneficiarian de un hashmap.
## Errores
Si el archivo no tiene vertices o no tiene faces, devuelve `Mesh{}` (positions/indices vacios). El caller puede chequear con `mesh.positions.empty()`.
## Test inline (cubo)
```cpp
const char* obj =
"v -1 -1 -1\nv 1 -1 -1\nv 1 1 -1\nv -1 1 -1\n"
"v -1 -1 1\nv 1 -1 1\nv 1 1 1\nv -1 1 1\n"
"f 1 2 3 4\nf 5 6 7 8\nf 1 2 6 5\n"
"f 4 3 7 8\nf 1 4 8 5\nf 2 3 7 6\n";
Mesh m = mesh_obj_parse(obj, std::strlen(obj));
// 6 quads -> 12 tris -> 36 indices.
// Sin vn -> face normals -> 36 verts emitidos (duplicados por face).
assert(m.indices.size() == 36);
```