6.1 KiB
6.1 KiB
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:
mesh_obj_load_cpp_gfx— parser minimo de Wavefront.obj(vertices + normales + indices). Sin materiales ni texturas en este issue.orbit_camera_cpp_core— camara orbital con drag (azimuth/elevation/distance), uniformsview/proj. Estado puro + helpers.mesh_viewer_cpp_viz— componente ImGui que rendea una mallaMeshGpucon 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:
- Compila/cachea un shader de Lambert minimo (vertex + fragment).
- Tiene un
Framebufferpropio (cache porid+ tamaño). - Cada frame: bind FBO, draw mesh,
ImGui::Image(framebuffer.color_tex). - 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). Ignoravt,mtllib, materiales en este issue. - 1.2 Generar normales por face si faltan.
- 1.3 Tests unitarios con .obj inline (cubo).
- 1.4
.mdcon frontmatter.
Fase 2 — mesh_gpu (impuro)
- 2.1
mesh_gpu_upload: crea VAO + VBO interleaved (pos+normal) + EBO. - 2.2
.mdcon 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 showpara 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
idestable.