Files
fn_registry/dev/issues/completed/0029-cpp-mesh-viewer.md

6.4 KiB
Raw Permalink Blame History

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

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

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.
  • 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

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.