merge: issue/0029 — mesh_viewer + obj loader + orbit_camera
# Conflicts: # cpp/apps/primitives_gallery/demos.h # cpp/apps/primitives_gallery/main.cpp
This commit is contained in:
@@ -0,0 +1,178 @@
|
||||
# 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.
|
||||
Reference in New Issue
Block a user