From c1a3d72e59a5aa2ce424350a27d1cb268e38800e Mon Sep 17 00:00:00 2001 From: Egutierrez Date: Sat, 25 Apr 2026 21:51:27 +0200 Subject: [PATCH] feat(primitives_gallery): demo de mesh_viewer (cubo procedural + .obj loader) Genera cubo procedural in-line (mesh_obj_parse de string), permite cargar .obj desde un text input absoluto. Botones: Reload cube, Wireframe toggle, Load .obj. Status line con tris count y instrucciones (drag to orbit, wheel to zoom). issue 0029 --- cpp/apps/primitives_gallery/CMakeLists.txt | 6 ++ cpp/apps/primitives_gallery/demos.h | 1 + cpp/apps/primitives_gallery/demos_mesh.cpp | 108 +++++++++++++++++++++ cpp/apps/primitives_gallery/main.cpp | 1 + 4 files changed, 116 insertions(+) create mode 100644 cpp/apps/primitives_gallery/demos_mesh.cpp diff --git a/cpp/apps/primitives_gallery/CMakeLists.txt b/cpp/apps/primitives_gallery/CMakeLists.txt index 6812bf9f..ebd5cd0c 100644 --- a/cpp/apps/primitives_gallery/CMakeLists.txt +++ b/cpp/apps/primitives_gallery/CMakeLists.txt @@ -8,6 +8,7 @@ add_imgui_app(primitives_gallery demos_text_editor.cpp demos_gl_texture.cpp demos_extras.cpp + demos_mesh.cpp # text_editor + file_watcher (issue 0025) ${CMAKE_SOURCE_DIR}/functions/core/text_editor.cpp ${CMAKE_SOURCE_DIR}/functions/core/file_watcher.cpp @@ -55,6 +56,11 @@ add_imgui_app(primitives_gallery # gl_texture_load (issue 0026) + stb_image ${CMAKE_SOURCE_DIR}/functions/gfx/gl_texture_load.cpp ${CMAKE_SOURCE_DIR}/vendor/stb/stb_image_impl.cpp + # mesh_viewer stack (issue 0029) + ${CMAKE_SOURCE_DIR}/functions/gfx/mesh_obj_load.cpp + ${CMAKE_SOURCE_DIR}/functions/gfx/mesh_gpu.cpp + ${CMAKE_SOURCE_DIR}/functions/core/orbit_camera.cpp + ${CMAKE_SOURCE_DIR}/functions/viz/mesh_viewer.cpp ) target_include_directories(primitives_gallery PRIVATE ${CMAKE_SOURCE_DIR}/vendor/imgui_text_edit diff --git a/cpp/apps/primitives_gallery/demos.h b/cpp/apps/primitives_gallery/demos.h index b7f22c32..c4168d42 100644 --- a/cpp/apps/primitives_gallery/demos.h +++ b/cpp/apps/primitives_gallery/demos.h @@ -34,6 +34,7 @@ void demo_candlestick(); void demo_gauge(); void demo_heatmap(); void demo_table_view(); +void demo_mesh_viewer(); // issue 0029 // --- Gfx --- void demo_shader_canvas(); diff --git a/cpp/apps/primitives_gallery/demos_mesh.cpp b/cpp/apps/primitives_gallery/demos_mesh.cpp new file mode 100644 index 00000000..5626f35e --- /dev/null +++ b/cpp/apps/primitives_gallery/demos_mesh.cpp @@ -0,0 +1,108 @@ +// Demo del primitivo viz/mesh_viewer. +// Genera un cubo procedural in-line, lo sube al GPU, y permite cargar un +// .obj desde un path ingresado en un text input. + +#include "demos.h" +#include "demo.h" + +#include "viz/mesh_viewer.h" +#include "gfx/mesh_obj_load.h" +#include "gfx/mesh_gpu.h" +#include "core/orbit_camera.h" + +#include +#include +#include + +namespace gallery { + +namespace { + +const char* kCubeObj = + "v -1 -1 -1\nv 1 -1 -1\nv 1 1 -1\nv -1 1 -1\n" + "v -1 -1 1\nv 1 -1 1\nv 1 1 1\nv -1 1 1\n" + "f 4 3 2 1\n" // back (-Z) — winding for outward normal + "f 5 6 7 8\n" // front (+Z) + "f 1 2 6 5\n" // bottom (-Y) + "f 8 7 3 4\n" // top (+Y) + "f 5 8 4 1\n" // left (-X) + "f 2 3 7 6\n"; // right (+X) + +struct State { + fn::gfx::MeshGpu mesh{}; + fn::core::OrbitCamera cam{}; + char path[512] = ""; + std::string status; + bool wireframe = false; + bool initialized = false; +}; + +State& state() { + static State s; + return s; +} + +void load_cube() { + auto& s = state(); + if (s.mesh.ok()) fn::gfx::mesh_gpu_destroy(s.mesh); + auto cpu = fn::gfx::mesh_obj_parse(kCubeObj, std::strlen(kCubeObj)); + s.mesh = fn::gfx::mesh_gpu_upload(cpu); + s.status = s.mesh.ok() + ? ("loaded cube: " + std::to_string(s.mesh.index_count / 3) + " tris") + : "cube upload failed"; +} + +void load_from_path() { + auto& s = state(); + if (!s.path[0]) { s.status = "path is empty"; return; } + auto cpu = fn::gfx::mesh_obj_load(s.path); + if (cpu.positions.empty()) { s.status = "parse/read failed"; return; } + if (s.mesh.ok()) fn::gfx::mesh_gpu_destroy(s.mesh); + s.mesh = fn::gfx::mesh_gpu_upload(cpu); + s.status = s.mesh.ok() + ? ("loaded: " + std::to_string(s.mesh.index_count / 3) + " tris") + : "upload failed"; +} + +} // namespace + +void demo_mesh_viewer() { + demo_header("mesh_viewer", "v1.0.0", + "Visualizador 3D para inspeccion de geometria. Composicion de " + "mesh_obj_load (parser .obj puro) + mesh_gpu (upload VAO/VBO/EBO) + " + "orbit_camera (drag/wheel) + mesh_viewer (FBO + ImGui::Image + Lambert)."); + + auto& s = state(); + if (!s.initialized) { + load_cube(); + s.initialized = true; + } + + // Controls row. + if (ImGui::Button("Reload cube")) load_cube(); + ImGui::SameLine(); + ImGui::Checkbox("Wireframe", &s.wireframe); + ImGui::SameLine(); + ImGui::TextDisabled("|"); + ImGui::SameLine(); + ImGui::SetNextItemWidth(360); + ImGui::InputTextWithHint("##obj_path", "absolute path to .obj", s.path, sizeof(s.path)); + ImGui::SameLine(); + if (ImGui::Button("Load .obj")) load_from_path(); + + ImGui::TextDisabled("status: %s | tris: %d | drag to orbit, wheel to zoom", + s.status.c_str(), + s.mesh.ok() ? s.mesh.index_count / 3 : 0); + + ImGui::Separator(); + + fn::viz::MeshViewerConfig cfg{}; + cfg.mesh = &s.mesh; + cfg.cam = &s.cam; + cfg.size = ImVec2(-1.0f, 480.0f); + cfg.color = IM_COL32(160, 200, 255, 255); + cfg.wireframe = s.wireframe; + fn::viz::mesh_viewer("##gallery_mesh_viewer", cfg); +} + +} // namespace gallery diff --git a/cpp/apps/primitives_gallery/main.cpp b/cpp/apps/primitives_gallery/main.cpp index 269bfe6b..18f656df 100644 --- a/cpp/apps/primitives_gallery/main.cpp +++ b/cpp/apps/primitives_gallery/main.cpp @@ -61,6 +61,7 @@ static const DemoEntry k_demos[] = { {"gauge", "gauge", "Viz", &gallery::demo_gauge}, {"heatmap", "heatmap", "Viz", &gallery::demo_heatmap}, {"table_view", "table_view", "Viz", &gallery::demo_table_view}, + {"mesh_viewer", "mesh_viewer", "Viz", &gallery::demo_mesh_viewer}, // Gfx (shaders_lab core) {"shader_canvas", "shader_canvas", "Gfx", &gallery::demo_shader_canvas}, {"gl_texture", "gl_texture_load", "Gfx", &gallery::demo_gl_texture}, // wave 1