---
id: "0029"
title: "C++ mesh_viewer + obj loader + orbit_camera"
status: completado
type: feature
domain:
  - cpp-stack
scope: multi-app
priority: media
depends: []
blocks: []
related: []
created: 2026-05-17
updated: 2026-05-17
tags: []
---
# 0029 — C++ mesh_viewer + obj loader + orbit_camera

## APP Metadata

| Campo | Valor |
|-------|-------|
| **ID** | 0029 |
| **Estado** | pendiente |
| **Prioridad** | media |
| **Tipo** | feature — C++ viz/gfx (cpp/functions/viz, cpp/functions/gfx) |

## Dependencias

`gl_loader_cpp_gfx`, `gl_shader_cpp_gfx`, `gl_framebuffer_cpp_gfx`. Independiente de los demas issues.

**Desbloquea:** visualizacion 3D real (no solo plots): inspeccionar modelos, point clouds, debugging de geometria.

---

## Objetivo

Tres primitivos:

1. **`mesh_obj_load_cpp_gfx`** — parser minimo de Wavefront `.obj` (vertices + normales + indices). Sin materiales ni texturas en este issue.
2. **`orbit_camera_cpp_core`** — camara orbital con drag (azimuth/elevation/distance), uniforms `view`/`proj`. Estado puro + helpers.
3. **`mesh_viewer_cpp_viz`** — componente ImGui que rendea una malla `MeshGpu` con orbit camera dentro de un FBO + `ImGui::Image`.

Demo en `primitives_gallery` con un cubo procedural y opcion de cargar un `.obj` desde disco.

## Contexto

El stack actual hace 2D plotting (ImPlot) y 2D fragment shaders (`shader_canvas`). No hay forma de visualizar geometria 3D. ImPlot3D (issue 0028) cubre plots cientificos pero no meshes generales. Este issue añade el camino "raster 3D" autonomo.

## Arquitectura

```
cpp/functions/gfx/
├── mesh_obj_load.h              # NEW
├── mesh_obj_load.cpp            # NEW (parser puro)
├── mesh_obj_load.md             # NEW (kind: function, purity: pure)
├── mesh_gpu.h                   # NEW (VAO/VBO/IBO de un Mesh)
├── mesh_gpu.cpp                 # NEW
└── mesh_gpu.md                  # NEW (kind: function, purity: impure)
cpp/functions/core/
├── orbit_camera.h               # NEW
├── orbit_camera.cpp             # NEW
└── orbit_camera.md              # NEW (kind: function, purity: pure)
cpp/functions/viz/
├── mesh_viewer.h                # NEW
├── mesh_viewer.cpp              # NEW
└── mesh_viewer.md               # NEW (kind: component, purity: impure)
cpp/apps/primitives_gallery/
├── demos_mesh.cpp               # NEW
├── demos.h                      # MOD
├── main.cpp                     # MOD
└── CMakeLists.txt               # MOD
cpp/CMakeLists.txt               # MOD
```

### API propuesta

```cpp
namespace fn {

// --- mesh_obj_load (puro) ---
struct Mesh {
    std::vector<float>   positions;   // x,y,z stride=3
    std::vector<float>   normals;     // optional, stride=3
    std::vector<uint32_t> indices;
};
Mesh mesh_obj_parse(const char* obj_text, size_t len);   // pure
Mesh mesh_obj_load(const char* path);                     // impure (lee fichero) — vive en mesh_gpu.cpp o aparte

// --- mesh_gpu (impure) ---
struct MeshGpu {
    GLuint vao = 0, vbo = 0, ebo = 0;
    int    index_count = 0;
    bool   ok() const { return vao != 0; }
};
MeshGpu mesh_gpu_upload(const Mesh&);
void    mesh_gpu_destroy(MeshGpu&);

// --- orbit_camera (puro) ---
struct OrbitCamera {
    float azimuth = 0.7f;     // rad
    float elevation = 0.4f;   // rad
    float distance = 3.0f;
    float fov = 45.0f;        // deg
    float aspect = 1.0f;
    float near_plane = 0.05f;
    float far_plane  = 100.0f;
};
struct CameraMatrices { float view[16]; float proj[16]; };
CameraMatrices orbit_camera_matrices(const OrbitCamera&);
void           orbit_camera_handle_drag(OrbitCamera&, ImVec2 drag_delta, float wheel);

// --- mesh_viewer (impure) ---
struct MeshViewerConfig {
    const MeshGpu* mesh;
    OrbitCamera*   cam;        // se modifica con drag
    ImVec2         size = {-1, 400};
    ImU32          color = IM_COL32(180,180,200,255);
    bool           wireframe = false;
};
void mesh_viewer(const char* id, const MeshViewerConfig&);
}
```

`mesh_viewer` internamente:
1. Compila/cachea un shader de Lambert minimo (vertex + fragment).
2. Tiene un `Framebuffer` propio (cache por `id` + tamaño).
3. Cada frame: bind FBO, draw mesh, `ImGui::Image(framebuffer.color_tex)`.
4. Si `IsItemActive()` y mouse drag → `orbit_camera_handle_drag`.

## Tareas

### Fase 1 — mesh_obj_load (puro)

- 1.1 Implementar parser que cubre `v`, `vn`, `f` (tris y quads). Ignora `vt`, `mtllib`, materiales en este issue.
- 1.2 Generar normales por face si faltan.
- 1.3 Tests unitarios con .obj inline (cubo).
- 1.4 `.md` con frontmatter.

### Fase 2 — mesh_gpu (impuro)

- 2.1 `mesh_gpu_upload`: crea VAO + VBO interleaved (pos+normal) + EBO.
- 2.2 `.md` con frontmatter.

### Fase 3 — orbit_camera (puro)

- 3.1 Calcular `view = lookAt(eye, target=0, up=Y)` + `proj = perspective`.
- 3.2 `handle_drag`: drag.x → azimuth, drag.y → elevation (clamp ±π/2 - eps), wheel → distance (clamp >0).
- 3.3 Tests unitarios para matrices (idempotencia drag=0, rango de elevation).
- 3.4 `.md`.

### Fase 4 — mesh_viewer

- 4.1 Implementar el componente con FBO interno cacheado por `id`.
- 4.2 Shader de iluminacion Lambert con luz fija desde la camara.
- 4.3 `.md`.

### Fase 5 — Gallery demo

- 5.1 `demos_mesh.cpp`: dos sub-demos: cubo procedural (generado in-line) + boton "Load .obj…" con path absoluto en text input.
- 5.2 Registrar en gallery.

### Fase 6 — Tests + docs

- 6.1 Test parser obj (cubo: 8 vertices, 12 tris).
- 6.2 Test orbit_camera (matrices conocidas).
- 6.3 `./fn index` + `./fn show` para los nuevos.

## Ejemplo de uso

```cpp
auto mesh = fn::mesh_obj_load("assets/teapot.obj");
auto gpu  = fn::mesh_gpu_upload(mesh);
fn::OrbitCamera cam;
cam.aspect = 4.0f/3.0f;

fn::run_app("mesh demo", [&]{
    fn::MeshViewerConfig cfg{};
    cfg.mesh = &gpu; cfg.cam = &cam;
    fn::mesh_viewer("##mv", cfg);
});
```

## Decisiones de diseño

- **Sin glm**: matrices a mano (4×4 row-major). Evita dependencia extra; el codigo es ~50 LOC.
- **Sin gltf por ahora**: .obj cubre el 80% de casos de inspeccion rapida. gltf en issue futuro si se necesita.
- **Iluminacion fija**: Lambert con luz=camara (headlight). Suficiente para inspeccion de geometria.

## Riesgos

- **Obj con quads o n-gons**: cubrir tris y quads (tris-fan), advertir en .md.
- **Modelos enormes**: limite practico 1M tris. Documentar.
- **Cache de FBO por id**: si la app cambia el id dinamicamente, fugas. Documentar para reusar `id` estable.
