Files
fn_registry/dev/issues/0029-cpp-mesh-viewer.md
T
egutierrez a11a58dab0 docs(issues): añadir 0025-0036 — features C++ para registry y primitives_gallery
12 issues nuevos para implementacion paralela: text_editor, file_watcher,
gl_texture_load, gl_compute+pingpong+DAG Compute, ImPlot3D, mesh_viewer,
audio reactivo, animation curves, sql_workbench, http+ws inspector,
scientific viz (5 charts), map_tiles, image_canvas + webcam_texture.

Cada issue añade funciones al registry y un demo propio en
primitives_gallery/demos_<feature>.cpp para minimizar conflictos en paralelo.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 20:48:18 +02:00

6.1 KiB
Raw Blame History

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.