179 lines
6.1 KiB
Markdown
179 lines
6.1 KiB
Markdown
# 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.
|