diff --git a/dev/issues/0025-cpp-text-editor-file-watcher.md b/dev/issues/0025-cpp-text-editor-file-watcher.md new file mode 100644 index 00000000..8a04d276 --- /dev/null +++ b/dev/issues/0025-cpp-text-editor-file-watcher.md @@ -0,0 +1,150 @@ +# 0025 — C++ text_editor + file_watcher + +## APP Metadata + +| Campo | Valor | +|-------|-------| +| **ID** | 0025 | +| **Estado** | pendiente | +| **Prioridad** | alta | +| **Tipo** | feature — C++ devx (cpp/functions/core) | + +## Dependencias + +Ninguna. Se apoya en lo que ya existe (`tokens`, `app_base`, ImGui vendoreado). + +**Desbloquea:** ciclo de edicion completo dentro de `shaders_lab` (editar GLSL + reload automatico al guardar) sin alt-tab a un editor externo. Tambien util para un futuro `sql_workbench` (issue 0032). + +--- + +## Objetivo + +Añadir dos primitivos al registry C++: + +1. **`text_editor_cpp_core`** — editor de codigo embebido en ImGui con syntax highlighting (GLSL, SQL, generic). Wrapper de [ImGuiColorTextEdit](https://github.com/BalazsJako/ImGuiColorTextEdit) (header + cpp, MIT) vendoreado en `cpp/vendor/imgui_text_edit/`. +2. **`file_watcher_cpp_core`** — watcher de archivos cross-platform (inotify Linux / ReadDirectoryChangesW Win) puro en su API: registra paths + callback, expone `poll()` no bloqueante. + +Mostrar ambos en `primitives_gallery` con una demo combinada: editor que carga un .glsl, file_watcher detecta cambios externos y el editor recarga. + +## Contexto + +`shaders_lab` actualmente compila un `gl_shader` desde texto en RAM. No hay editor in-app: el usuario edita en VSCode y copia/pega. Tampoco hay file_watcher: para iterar sobre `.glsl` externos hay que recompilar la app. + +Otros usos del editor: +- `sql_workbench` (0032) — editor SQL + ejecucion sobre `registry.db`/`operations.db`. +- Edicion de prompts/configs en apps futuras. + +## Arquitectura + +``` +cpp/ +├── vendor/imgui_text_edit/ # NEW — vendor (MIT, 1 .h + 1 .cpp) +│ ├── TextEditor.h +│ └── TextEditor.cpp +├── functions/core/ +│ ├── text_editor.h # NEW — wrapper en namespace fn +│ ├── text_editor.cpp # NEW +│ ├── text_editor.md # NEW +│ ├── file_watcher.h # NEW +│ ├── file_watcher.cpp # NEW (impure) +│ └── file_watcher.md # NEW +└── apps/primitives_gallery/ + ├── demos_text_editor.cpp # NEW — demo combinada (editor + watcher) + ├── demos.h # MOD — declarar demo_text_editor() + ├── main.cpp # MOD — registrar entrada en sidebar + └── CMakeLists.txt # MOD — añadir fuentes +cpp/CMakeLists.txt # MOD — añadir vendor/imgui_text_edit a sources +``` + +### Pure core / impure shell + +- **`text_editor_cpp_core`**: API es **pura** en el sentido de "sin I/O propio" — todo el estado vive en una struct `TextEditorState`. La `TextEditor::Render()` hace draw calls de ImGui (igual que el resto de componentes). Marcar como `purity: pure`, `kind: component`. Misma categoria que `button_cpp_core`. +- **`file_watcher_cpp_core`**: hace I/O del sistema de ficheros. `purity: impure`, `kind: function`, `error_type: error_go_core` (usar el equivalente C++ — ver `tokens` para errores existentes; si no, retornar `bool` con `last_error()`). + +### API propuesta + +```cpp +namespace fn { + +// text_editor.h +struct TextEditorState; // forward (PIMPL hacia TextEditor de vendor) + +enum class CodeLang { Generic, GLSL, SQL, Cpp }; + +TextEditorState* text_editor_create(CodeLang lang = CodeLang::Generic); +void text_editor_destroy(TextEditorState*); +void text_editor_set_text(TextEditorState*, const char* text); +const char* text_editor_get_text(TextEditorState*); // valido hasta el siguiente call +bool text_editor_render(TextEditorState*, const char* label, ImVec2 size); // true si cambio +bool text_editor_is_dirty(const TextEditorState*); +void text_editor_clear_dirty(TextEditorState*); + +// file_watcher.h +struct FileWatcher; + +FileWatcher* file_watcher_create(); +void file_watcher_destroy(FileWatcher*); +bool file_watcher_add(FileWatcher*, const char* path); // file or dir +struct FileEvent { std::string path; enum { Modified, Created, Deleted } kind; }; +std::vector file_watcher_poll(FileWatcher*); // non-blocking, drain +} +``` + +## Tareas + +### Fase 1 — Vendor + +- 1.1 Descargar [ImGuiColorTextEdit](https://github.com/BalazsJako/ImGuiColorTextEdit) (commit estable, MIT) a `cpp/vendor/imgui_text_edit/`. +- 1.2 Añadirlo al `cpp/CMakeLists.txt` global como source list reusable. + +### Fase 2 — text_editor + +- 2.1 Implementar `text_editor.h/.cpp` con la API de arriba (PIMPL). Aplicar `tokens` para colores de fondo y border consistentes. +- 2.2 Configurar lenguajes: `LanguageDefinition::GLSL()` y `LanguageDefinition::SQL()` (vendor ya las trae). +- 2.3 `text_editor.md` con frontmatter completo (`kind: component`, `purity: pure`, `params`, `output`, ejemplo). + +### Fase 3 — file_watcher + +- 3.1 Implementar `file_watcher.h/.cpp`. Linux: inotify. Windows: ReadDirectoryChangesW (solo dir-level, filtrar por path). Macros condicionales. +- 3.2 `file_watcher.md` (`kind: function`, `purity: impure`). + +### Fase 4 — Gallery demo + +- 4.1 `demos_text_editor.cpp` con `demo_text_editor()`: split horizontal — izquierda editor con un GLSL de ejemplo, derecha info (dirty flag, eventos del watcher). Botón "Save to /tmp/demo.glsl" + watcher activo sobre `/tmp/demo.glsl` que muestra los eventos. +- 4.2 Registrar en `demos.h`, `main.cpp`, `CMakeLists.txt`. + +### Fase 5 — Integracion en shaders_lab (opcional) + +- 5.1 (Stretch) Añadir flag `--watch ` a `shaders_lab` que abre el archivo con text_editor + file_watcher y recompila automaticamente. + +### Fase 6 — Tests + docs + +- 6.1 Tests para `file_watcher` (Linux): crear tmpfile, modify, verificar evento. Compilable solo en Linux con guard. +- 6.2 `./fn index` y `./fn show text_editor_cpp_core` / `file_watcher_cpp_core` verifica frontmatter. + +## Ejemplo de uso + +```cpp +auto* ed = fn::text_editor_create(fn::CodeLang::GLSL); +auto* fw = fn::file_watcher_create(); +fn::file_watcher_add(fw, "/tmp/demo.glsl"); + +fn::run_app("editor demo", [&]{ + if (fn::text_editor_render(ed, "##ed", {600, 400})) + std::printf("changed\n"); + for (auto& ev : fn::file_watcher_poll(fw)) + std::printf("FS: %s\n", ev.path.c_str()); +}); +``` + +## Decisiones de diseño + +- **PIMPL** evita exponer `TextEditor` del vendor en headers publicos. +- **Vendor in-tree** sigue la convencion de ImGui/ImPlot ya presentes en `cpp/vendor/`. +- **Sin tokens custom** — heredamos los del color theme global. + +## Riesgos + +- **API del vendor cambia entre commits**: pinear a un commit concreto en un README en `cpp/vendor/imgui_text_edit/`. +- **inotify watch limit en Linux**: documentar `fs.inotify.max_user_watches` en el .md. +- **Windows ReadDirectoryChangesW** solo emite a nivel de directorio: filtrar el path en el poll. diff --git a/dev/issues/0026-cpp-gl-texture-load.md b/dev/issues/0026-cpp-gl-texture-load.md new file mode 100644 index 00000000..b69e1106 --- /dev/null +++ b/dev/issues/0026-cpp-gl-texture-load.md @@ -0,0 +1,130 @@ +# 0026 — C++ gl_texture_load + +## APP Metadata + +| Campo | Valor | +|-------|-------| +| **ID** | 0026 | +| **Estado** | pendiente | +| **Prioridad** | alta | +| **Tipo** | feature — C++ gfx (cpp/functions/gfx) | + +## Dependencias + +Ninguna. Se compone con `gl_loader_cpp_gfx`, `gl_shader_cpp_gfx`, `shader_canvas_cpp_gfx`. + +**Desbloquea:** shaders que toman imagenes externas (textura de ruido, foto, lookup tables). Base para `webcam_texture` (0036) y para nodos DAG futuros que usen sampler2D. + +--- + +## Objetivo + +Añadir una funcion impura al registry C++ que carga PNG/JPG/HDR desde disco y devuelve un `GLuint` listo para `glBindTexture` + `glUniform1i`. Vendorea **stb_image** (header-only, dominio publico) en `cpp/vendor/stb/`. + +Mostrar el resultado en `primitives_gallery` como demo: un shader fullscreen que samplea una imagen cargada, con sliders para tint y zoom UV. + +## Contexto + +Actualmente `gl_shader_cpp_gfx` solo expone uniforms `u_resolution`, `u_time`, `u_mouse`, y `shaders_lab` puede pasar `u_params[16]` via `dag_uniforms`. No hay forma de meter una imagen al pipeline. Esto bloquea casos comunes: +- Texturas de ruido (perlin/bluenoise) precomputadas. +- Lookup tables para color grading. +- Imagenes de referencia para post-process. + +## Arquitectura + +``` +cpp/ +├── vendor/stb/ +│ └── stb_image.h # NEW (~10k LOC, public domain) +├── functions/gfx/ +│ ├── gl_texture_load.h # NEW +│ ├── gl_texture_load.cpp # NEW +│ └── gl_texture_load.md # NEW +└── apps/primitives_gallery/ + ├── demos_gl_texture.cpp # NEW + ├── demos.h # MOD + ├── main.cpp # MOD + └── CMakeLists.txt # MOD +cpp/CMakeLists.txt # MOD +``` + +### Pure core / impure shell + +Funcion **impura** (`purity: impure`, `kind: function`): hace I/O de disco + crea recurso GPU. + +### API propuesta + +```cpp +namespace fn { + +struct GlTexture { + GLuint id = 0; + int w = 0; + int h = 0; + int channels = 0; + bool ok() const { return id != 0; } +}; + +// Carga desde disco (PNG/JPG/BMP/TGA via stb_image, HDR como float opcional). +// flip_y por defecto true para coincidir con UV de OpenGL. +// Devuelve GlTexture con id=0 si falla; usar gl_texture_last_error() para detalle. +GlTexture gl_texture_load(const char* path, bool flip_y = true, bool srgb = false); +GlTexture gl_texture_load_from_memory(const unsigned char* data, int size, bool flip_y = true, bool srgb = false); + +void gl_texture_destroy(GlTexture& tex); +const char* gl_texture_last_error(); + +// Helper: bind a una texture unit y subir uniform sampler. +void gl_texture_bind_uniform(GLuint program, const char* name, const GlTexture& tex, int unit); +} +``` + +## Tareas + +### Fase 1 — Vendor stb_image + +- 1.1 Descargar `stb_image.h` (commit estable) a `cpp/vendor/stb/`. Crear `cpp/vendor/stb/stb_image_impl.cpp` con `#define STB_IMAGE_IMPLEMENTATION` antes del include, para evitar emitir el cuerpo en multiples TUs. +- 1.2 Añadirlo a `cpp/CMakeLists.txt` global. + +### Fase 2 — gl_texture_load + +- 2.1 Implementar `gl_texture_load.h/.cpp`. Usar `glGenTextures` + `glTexImage2D`. Filtros LINEAR_MIPMAP_LINEAR + LINEAR. Generar mipmaps con `glGenerateMipmap`. +- 2.2 Soporte para `srgb=true` (`GL_SRGB8_ALPHA8` como internal format). +- 2.3 Soporte HDR: si extension es `.hdr`, usar `stbi_loadf` y `GL_RGBA16F`. +- 2.4 `gl_texture_load.md` con frontmatter (`kind: function`, `purity: impure`, `error_type`, `uses_functions: [gl_loader_cpp_gfx]`). + +### Fase 3 — Gallery demo + +- 3.1 `demos_gl_texture.cpp` con `demo_gl_texture_load()`: carga `assets/sample.png` (un PNG de prueba, ej. patron damero). Renderiza un fullscreen quad con un shader que samplea la textura y aplica tint via uniforms. ImGui::Image con thumbnail al lado y info (w, h, channels). +- 3.2 Añadir `assets/sample.png` (256x256, ~10KB). +- 3.3 Registrar en `demos.h`, `main.cpp`, `CMakeLists.txt`. + +### Fase 4 — Tests + docs + +- 4.1 Test que carga un PNG embebido (in-memory) y verifica `tex.ok() && tex.w == ...`. +- 4.2 `./fn index` y verificar via `fn show gl_texture_load_cpp_gfx`. + +## Ejemplo de uso + +```cpp +auto tex = fn::gl_texture_load("assets/noise.png"); +if (!tex.ok()) { + std::fprintf(stderr, "error: %s\n", fn::gl_texture_last_error()); + return 1; +} +glUseProgram(prog); +fn::gl_texture_bind_uniform(prog, "u_noise", tex, 0); // unit 0 +glDrawArrays(GL_TRIANGLES, 0, 6); +``` + +## Decisiones de diseño + +- **Vendoreado en `cpp/vendor/stb/`** sigue el patron actual de ImGui/ImPlot. +- **`flip_y=true` por defecto** porque la convencion en GLSL es V hacia arriba. +- **`srgb` opcional** — para texturas LDR de color, `GL_SRGB8_ALPHA8` da un gamma correcto sin tener que `pow(color, 2.2)` en el shader. + +## Riesgos + +- **Tamaño de imagenes**: cargar PNGs de 4K consume RAM/VRAM. Documentar limite practico (<= 8192 lado). +- **Driver no soporta sRGB**: documentar fallback a `GL_RGBA8`. +- **Codigo de stb_image lento**: aceptable para uso interactivo (no es hot path de cada frame). diff --git a/dev/issues/0027-cpp-gl-compute-pingpong.md b/dev/issues/0027-cpp-gl-compute-pingpong.md new file mode 100644 index 00000000..d493b72f --- /dev/null +++ b/dev/issues/0027-cpp-gl-compute-pingpong.md @@ -0,0 +1,171 @@ +# 0027 — C++ gl_compute_shader + gl_pingpong_fbo + DAG node Compute + +## APP Metadata + +| Campo | Valor | +|-------|-------| +| **ID** | 0027 | +| **Estado** | pendiente | +| **Prioridad** | alta | +| **Tipo** | feature — C++ gfx (cpp/functions/gfx) | + +## Dependencias + +Recomendado leer primero `gl_loader_cpp_gfx`, `gl_shader_cpp_gfx`, `gl_framebuffer_cpp_gfx`, `dag_catalog_cpp_gfx`, `dag_compile_cpp_gfx`. Sin bloqueos. + +**Desbloquea:** simulaciones GPGPU (particulas, fluidos), feedback loops (reaction-diffusion, blur multipass, bloom), cualquier shader que necesite estado entre frames. Triplica el alcance del DAG visual de `shaders_lab`. + +--- + +## Objetivo + +Tres adiciones complementarias: + +1. **`gl_compute_shader_cpp_gfx`** — carga + dispatch de compute shaders (GLSL `#version 430 core` + `layout(local_size_*) in`). API parejo a `gl_shader`. +2. **`gl_pingpong_fbo_cpp_gfx`** — wrapper de **dos** `gl_framebuffer` con swap A↔B en cada paso. Permite usar el output del frame N-1 como input del frame N (feedback). +3. **DAG kind `Compute`** — nuevo `DagNodeKind::Compute` en `dag_catalog`/`dag_compile`. Nodo que ejecuta un compute shader sobre la imagen actual, con N iteraciones configurable. + +Demo en `primitives_gallery`: reaction-diffusion (Gray-Scott) usando ping-pong FBO + compute shader. + +## Contexto + +`gl_shader` solo soporta fragment shaders. El DAG actual es estrictamente fragment-pipeline (gen → op → blend). No hay forma de: +- Persistir estado entre frames (necesario para feedback effects). +- Hacer GPGPU verdadero (compute con buffers / images). +- Implementar efectos como fluid sim, particulas o blur multipass eficientes. + +OpenGL compute shaders requieren `#version 430 core` (= GL 4.3). El proyecto ya pide GL 3.3+; subir el minimo solo para los nodos compute es aceptable (graceful degrade si la GPU no lo soporta). + +## Arquitectura + +``` +cpp/functions/gfx/ +├── gl_compute_shader.h # NEW +├── gl_compute_shader.cpp # NEW +├── gl_compute_shader.md # NEW +├── gl_pingpong_fbo.h # NEW +├── gl_pingpong_fbo.cpp # NEW +├── gl_pingpong_fbo.md # NEW +├── dag_types.h # MOD: añadir DagNodeKind::Compute +├── dag_catalog.cpp/.h # MOD: registrar nodos compute (ej: ReactionDiffusion) +├── dag_compile.cpp/.h # MOD: emitir paso compute en pipeline +└── dag_uniforms.cpp/.h # MOD si los compute tienen params +cpp/apps/primitives_gallery/ +├── demos_compute.cpp # NEW +├── demos.h # MOD +├── main.cpp # MOD +└── CMakeLists.txt # MOD +``` + +### Pure core / impure shell + +- `gl_compute_shader` y `gl_pingpong_fbo`: ambos `purity: impure`, `kind: function`. +- `dag_catalog`/`dag_compile`: ya son `pure`, mantener. +- `dag_node_editor` / `dag_panel`: ya son `impure`, no se tocan en este issue (los nodos compute aparecen automaticamente al añadirse al catalog). + +### API propuesta + +```cpp +namespace fn { + +// gl_compute_shader.h +struct GlComputeProgram { + GLuint program = 0; + int local_x = 1, local_y = 1, local_z = 1; + bool ok() const { return program != 0; } +}; + +GlComputeProgram gl_compute_compile(const char* glsl_body); // body sin version +void gl_compute_destroy(GlComputeProgram&); + +// Dispatch: bind images, set uniforms (callback opcional), glDispatchCompute, barrier. +void gl_compute_dispatch(const GlComputeProgram&, int groups_x, int groups_y, int groups_z = 1); +void gl_compute_bind_image(int unit, GLuint texture, GLenum access /*GL_READ_ONLY|WRITE|READ_WRITE*/, GLenum format = GL_RGBA8); + +const char* gl_compute_last_error(); + +// gl_pingpong_fbo.h — dos FBO RGBA8 del mismo tamaño con A/B swap. +struct PingpongFbo { + Framebuffer a, b; + bool a_is_front = true; // front = ultimo escrito +}; + +PingpongFbo pingpong_create(int w, int h); +void pingpong_destroy(PingpongFbo&); +void pingpong_resize(PingpongFbo&, int w, int h); +void pingpong_swap(PingpongFbo&); +const Framebuffer& pingpong_front(const PingpongFbo&); // ultima lectura +const Framebuffer& pingpong_back (const PingpongFbo&); // siguiente write target +} +``` + +### DAG: nuevo kind Compute + +`dag_catalog` registra al menos un nodo de ejemplo: `compute_blur_separable` o `compute_reaction_diffusion`. Tras este issue queda abierto añadir mas (cada uno = un nuevo `DagNode`). + +`dag_compile`: cuando emite un paso `Compute`, decide bindings (image read = ping-front, image write = pong-back) y devuelve metadatos para que el host haga `dispatch + swap` correctamente. + +## Tareas + +### Fase 1 — gl_compute_shader + +- 1.1 Implementar wrapper minimo. Detectar version GL >= 4.3 al inicializar; si no, retornar `program == 0` y guardar error "compute shaders require OpenGL 4.3+". +- 1.2 Helper `gl_compute_bind_image` con `glBindImageTexture`. +- 1.3 `.md` con frontmatter (`purity: impure`, `kind: function`, `error_type`). + +### Fase 2 — gl_pingpong_fbo + +- 2.1 Implementar wrapper que reusa el `gl_framebuffer` existente. Resize propaga a los dos. +- 2.2 `.md` con frontmatter. + +### Fase 3 — DAG Compute kind + +- 3.1 Añadir `DagNodeKind::Compute` en `dag_types.h` + serializacion (json IO). +- 3.2 `dag_catalog`: registrar nodo de ejemplo `compute_blur_2pass` (separable Gaussian blur, 2 dispatches via flag uniform `direction`). +- 3.3 `dag_compile`: emitir step compute con suficiente metadata para que el host (un nuevo helper) haga el dispatch. +- 3.4 Helper `dag_compute_run_step()` — recibe `DagStep` compute + `PingpongFbo` + uniforms y hace `dispatch + swap`. + +### Fase 4 — Gallery demo + +- 4.1 `demos_compute.cpp` con `demo_compute_reaction_diffusion()`: `PingpongFbo` 512×512 + compute shader Gray-Scott con sliders (feed, kill, dt). Usa `ImGui::Image` para mostrar el front buffer cada frame. Boton "reset" rellena con seed. +- 4.2 Registrar en gallery. + +### Fase 5 — Tests + docs + +- 5.1 Test (Linux + GPU disponible, opcional via env var `FN_GPU_TESTS=1`): compila un compute simple que escribe `vec4(1,0,0,1)` y verifica via `glReadPixels`. +- 5.2 Test puro de `dag_compile` con un pipeline que mezcla nodos `Gen` + `Compute`. +- 5.3 `./fn index` + `./fn show *_cpp_gfx` para los nuevos. + +## Ejemplo de uso + +```cpp +auto cs = fn::gl_compute_compile(R"glsl( + layout(local_size_x=8, local_size_y=8) in; + layout(rgba8, binding=0) uniform image2D src; + layout(rgba8, binding=1) uniform image2D dst; + void main() { + ivec2 p = ivec2(gl_GlobalInvocationID.xy); + vec4 c = imageLoad(src, p); + imageStore(dst, p, vec4(1.0 - c.rgb, 1.0)); + } +)glsl"); + +auto pp = fn::pingpong_create(512, 512); +glUseProgram(cs.program); +fn::gl_compute_bind_image(0, fn::pingpong_front(pp).color_tex, GL_READ_ONLY); +fn::gl_compute_bind_image(1, fn::pingpong_back (pp).color_tex, GL_WRITE_ONLY); +fn::gl_compute_dispatch(cs, 64, 64); +fn::pingpong_swap(pp); +``` + +## Decisiones de diseño + +- **Subir minimo a GL 4.3 solo para nodos Compute** — el resto del pipeline sigue compilando con 3.3. `gl_compute_compile` falla con error claro en GPUs viejas. +- **Ping-pong fuera de FBO** — wrapper aparte, no fusionado con `gl_framebuffer` (mantenemos primitivos atomicos). +- **Catalog del DAG abierto** — un solo nodo compute en este issue. Otros (fluid, particles) se añaden en issues posteriores reusando los wrappers. + +## Riesgos + +- **macOS no soporta compute shaders en GL 4.1** — documentar limitacion. En macOS el path Compute queda inactivo. +- **Memory barriers**: olvidar `glMemoryBarrier(GL_SHADER_IMAGE_ACCESS_BARRIER_BIT)` produce datos basura. Encapsular en `gl_compute_dispatch`. +- **Dispatch grande bloquea UI**: documentar que dispatches > 1M pixels deben dividirse o bajar local_size. diff --git a/dev/issues/0028-cpp-implot3d-3d-viz.md b/dev/issues/0028-cpp-implot3d-3d-viz.md new file mode 100644 index 00000000..75df4b42 --- /dev/null +++ b/dev/issues/0028-cpp-implot3d-3d-viz.md @@ -0,0 +1,138 @@ +# 0028 — C++ ImPlot3D + surface_plot_3d real + scatter_3d + +## APP Metadata + +| Campo | Valor | +|-------|-------| +| **ID** | 0028 | +| **Estado** | pendiente | +| **Prioridad** | media | +| **Tipo** | feature — C++ viz (cpp/functions/viz) | + +## Dependencias + +`surface_plot_3d_cpp_viz` ya existe como **STUB**. Este issue quita el stub. + +**Desbloquea:** dashboards 3D (terreno, FFT 2D, height maps, scatter 3D para PCA/clustering visualizations). + +--- + +## Objetivo + +1. Vendorear [ImPlot3D](https://github.com/brenocq/implot3d) (MIT, header + cpp) en `cpp/vendor/implot3d/`. +2. Reescribir **`surface_plot_3d_cpp_viz`** (hoy STUB) con implementacion real. +3. Añadir **`scatter_3d_cpp_viz`** — scatter 3D con color/size por punto. +4. Demos en `primitives_gallery`. + +## Contexto + +`surface_plot_3d_cpp_viz` aparece en el registry pero su `code` es `[STUB] requires ImPlot3D not vendored yet`. Bloquea apps de visualizacion cientifica (heightfield, terreno, superficies parametricas). + +ImPlot ya esta vendoreado y se usa para 2D. ImPlot3D es del mismo autor (brenocq), API parejo a ImPlot, con tres-axis y rotacion drag-to-orbit. + +## Arquitectura + +``` +cpp/ +├── vendor/implot3d/ # NEW +│ ├── implot3d.h +│ ├── implot3d.cpp +│ ├── implot3d_internal.h +│ ├── implot3d_items.cpp +│ └── implot3d_demo.cpp # opcional, util como referencia +├── functions/viz/ +│ ├── surface_plot_3d.cpp/.h # MOD — quitar STUB +│ ├── surface_plot_3d.md # MOD — actualizar tags/notes/code +│ ├── scatter_3d.h # NEW +│ ├── scatter_3d.cpp # NEW +│ └── scatter_3d.md # NEW +└── apps/primitives_gallery/ + ├── demos_3d.cpp # NEW + ├── demos.h # MOD + ├── main.cpp # MOD + └── CMakeLists.txt # MOD +cpp/CMakeLists.txt # MOD — añadir sources de implot3d +``` + +### API propuesta + +```cpp +namespace fn { + +struct SurfacePlot3DConfig { + const float* z; // size = nx * ny, row-major + int nx, ny; + float x_min = 0.f, x_max = 1.f; + float y_min = 0.f, y_max = 1.f; + const char* x_label = "x"; + const char* y_label = "y"; + const char* z_label = "z"; + ImVec2 size = {-1, 400}; + bool show_colormap = true; +}; +void surface_plot_3d(const char* title, const SurfacePlot3DConfig& cfg); + +struct Scatter3DConfig { + const float* xs; // size = n + const float* ys; + const float* zs; + const float* sizes = nullptr; // opcional: size = n + const ImU32* colors = nullptr; // opcional: size = n + int n; + ImVec2 size = {-1, 400}; +}; +void scatter_3d(const char* title, const Scatter3DConfig& cfg); +} +``` + +## Tareas + +### Fase 1 — Vendor + +- 1.1 Clonar ImPlot3D, copiar fuentes a `cpp/vendor/implot3d/`. Pinear a tag/commit conocido. +- 1.2 Inicializar contexto en `app_base` (similar a como ya inicializa ImPlot): `ImPlot3D::CreateContext()` / `DestroyContext()`. +- 1.3 Añadir sources al `cpp/CMakeLists.txt`. + +### Fase 2 — surface_plot_3d real + +- 2.1 Reescribir `surface_plot_3d.cpp/.h` con la API de arriba usando `ImPlot3D::PlotSurface`. +- 2.2 Actualizar `surface_plot_3d.md`: quitar tag `stub`, actualizar `code`, añadir `uses_functions: []` y `tags: ["3d", "viz"]`. + +### Fase 3 — scatter_3d + +- 3.1 Implementar `scatter_3d.cpp/.h` con `ImPlot3D::PlotScatter`. Soportar size/color por punto. +- 3.2 `scatter_3d.md` con frontmatter (`kind: component`, `purity: pure`). + +### Fase 4 — Gallery demos + +- 4.1 `demos_3d.cpp` con dos demos: + - `demo_surface_plot_3d()`: malla 64×64 con `z = sin(x)*cos(y)`. Sliders para frecuencia. + - `demo_scatter_3d()`: 500 puntos con clusters sinteticos (3 clusters), colores por cluster. +- 4.2 Registrar en gallery. + +### Fase 5 — Tests + docs + +- 5.1 Test puro de generacion de datos (la rendering depende del contexto GL, asumir que el smoke-test es la propia gallery). +- 5.2 `./fn index` + verificar `surface_plot_3d_cpp_viz` ya no es STUB. + +## Ejemplo de uso + +```cpp +std::vector z(64*64); +for (int j=0;j<64;j++) for (int i=0;i<64;i++) + z[j*64+i] = std::sin(0.2f*i) * std::cos(0.2f*j); + +fn::SurfacePlot3DConfig s{}; +s.z = z.data(); s.nx = 64; s.ny = 64; +fn::surface_plot_3d("terreno", s); +``` + +## Decisiones de diseño + +- **ImPlot3D vs Plotly export**: ImPlot3D mantiene la coherencia con ImPlot 2D que ya usamos. Sin dependencias extra. +- **Render-only por ahora**: pan/zoom/orbit las aporta ImPlot3D nativo. + +## Riesgos + +- **ImPlot3D inestable**: pinear commit; documentar version exacta en `cpp/vendor/implot3d/README.md`. +- **Performance con malla grande**: documentar limite practico (~256×256 antes de notar drops). diff --git a/dev/issues/0029-cpp-mesh-viewer.md b/dev/issues/0029-cpp-mesh-viewer.md new file mode 100644 index 00000000..e278ec78 --- /dev/null +++ b/dev/issues/0029-cpp-mesh-viewer.md @@ -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 positions; // x,y,z stride=3 + std::vector normals; // optional, stride=3 + std::vector 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. diff --git a/dev/issues/0030-cpp-audio-reactive.md b/dev/issues/0030-cpp-audio-reactive.md new file mode 100644 index 00000000..35c7149b --- /dev/null +++ b/dev/issues/0030-cpp-audio-reactive.md @@ -0,0 +1,170 @@ +# 0030 — C++ audio reactivo (capture + FFT + uniform feed + viz) + +## APP Metadata + +| Campo | Valor | +|-------|-------| +| **ID** | 0030 | +| **Estado** | pendiente | +| **Prioridad** | media | +| **Tipo** | feature — C++ multi-domain (core, gfx, viz) | + +## Dependencias + +`gl_loader_cpp_gfx`, `time_series_buffer_cpp_core`. Independiente de los demas issues. + +**Desbloquea:** shaders audio-reactivos en `shaders_lab`, dashboards en vivo con waveform/spectrum, debugging visual de pipelines de audio. + +--- + +## Objetivo + +Cinco primitivos coherentes: + +1. **`audio_capture_cpp_core`** — input mic/loopback con [miniaudio](https://github.com/mackron/miniaudio) (single-header, dominio publico). Lock-free ring buffer. +2. **`audio_fft_cpp_core`** — FFT real → magnitudes + smoothing (puro). Tamaño configurable (256/512/1024/2048). +3. **`audio_uniform_feed_cpp_gfx`** — sube espectro como `sampler1D` o `texture2D 1×N` al shader activo. +4. **`waveform_view_cpp_viz`** — dibuja muestras crudas en un line plot ImPlot. +5. **`spectrum_view_cpp_viz`** — dibuja magnitudes FFT en bar plot logaritmico. + +Demo en `primitives_gallery`: input live → fft → vis dual + opcion de bind a un shader fullscreen reactivo. + +## Contexto + +`shaders_lab` no tiene input externo dinamico. Los visuals reaccionan a `u_time` y `u_mouse` solamente. Audio reactivo es uno de los casos mas comunes de uso creativo de shaders y abre demos espectaculares con muy poco codigo añadido. + +## Arquitectura + +``` +cpp/ +├── vendor/miniaudio/ +│ └── miniaudio.h # NEW (~80k LOC, public domain) +├── functions/core/ +│ ├── audio_capture.h/.cpp/.md # NEW (impure) +│ └── audio_fft.h/.cpp/.md # NEW (pure) +├── functions/gfx/ +│ └── audio_uniform_feed.h/.cpp/.md # NEW (impure) +└── functions/viz/ + ├── waveform_view.h/.cpp/.md # NEW (pure component) + └── spectrum_view.h/.cpp/.md # NEW (pure component) +cpp/apps/primitives_gallery/ +├── demos_audio.cpp # NEW +├── demos.h # MOD +├── main.cpp # MOD +└── CMakeLists.txt # MOD +cpp/CMakeLists.txt # MOD +``` + +### API propuesta + +```cpp +namespace fn { + +// audio_capture (impure) +struct AudioCapture; +struct AudioCaptureConfig { + int sample_rate = 48000; + int channels = 1; // mixdown a mono + int buffer_samples = 8192; // ring buffer + bool loopback = false; // Win/Linux pulse: capturar output del sistema +}; +AudioCapture* audio_capture_start(const AudioCaptureConfig&); +void audio_capture_stop(AudioCapture*); +// Drena hasta `out_capacity` muestras float [-1,1]. Retorna count efectivo. +int audio_capture_read(AudioCapture*, float* out, int out_capacity); +const char* audio_capture_last_error(); + +// audio_fft (pure) +struct FftResult { + std::vector magnitudes; // size = fft_size/2 + float dc = 0.f; + float peak_freq_hz = 0.f; +}; +FftResult audio_fft_compute(const float* samples, int n, int sample_rate, float smoothing = 0.7f, FftResult* prev = nullptr); + +// audio_uniform_feed (impure) +struct AudioTexture { GLuint tex = 0; int n = 0; }; +AudioTexture audio_texture_create(int n); +void audio_texture_destroy(AudioTexture&); +void audio_texture_upload(AudioTexture&, const float* magnitudes, int n); +void audio_texture_bind_uniform(GLuint program, const char* name, const AudioTexture&, int unit); + +// waveform_view (pure component) +void waveform_view(const char* id, const float* samples, int n, ImVec2 size = {-1, 100}); +// spectrum_view (pure component) +void spectrum_view(const char* id, const float* magnitudes, int n, int sample_rate, ImVec2 size = {-1, 150}); +} +``` + +## Tareas + +### Fase 1 — Vendor + +- 1.1 `cpp/vendor/miniaudio/miniaudio.h` (pin commit). Crear `cpp/vendor/miniaudio/miniaudio_impl.cpp` con `#define MINIAUDIO_IMPLEMENTATION`. +- 1.2 Añadir source al CMakeLists. + +### Fase 2 — audio_capture + +- 2.1 Implementar wrapper. Backend default por OS (alsa/pulse en Linux, wasapi en Win). Lock-free ring buffer (single-producer/single-consumer) accumulando muestras del callback. +- 2.2 `loopback`: Linux pulse loopback monitor source; Windows wasapi loopback. Documentar limitaciones. +- 2.3 `.md` (`kind: function`, `purity: impure`, `error_type`). + +### Fase 3 — audio_fft (puro) + +- 3.1 Usar implementacion FFT minima (Cooley-Tukey radix-2) o vendorear [pffft](https://github.com/marton78/pffft) (BSD). Decidir y documentar. +- 3.2 Aplicar Hann window antes de FFT. +- 3.3 Smoothing exponencial frame-a-frame con `prev`. +- 3.4 Tests con seno puro a frecuencia conocida; `peak_freq_hz` debe coincidir. +- 3.5 `.md`. + +### Fase 4 — audio_uniform_feed + +- 4.1 Crear textura 1D (o 2D 1×N) `GL_R32F`. Subida con `glTexSubImage1D`/`2D`. +- 4.2 `.md`. + +### Fase 5 — waveform_view + spectrum_view + +- 5.1 `waveform_view`: linea con `ImPlot::PlotLine`. Eje y fijado [-1, 1]. Eje x = sample index. +- 5.2 `spectrum_view`: bar plot con eje x logaritmico (Hz), eje y dB (20*log10(magnitude+eps)). +- 5.3 `.md` para cada uno. + +### Fase 6 — Gallery demo + +- 6.1 `demos_audio.cpp` con `demo_audio_reactive()`: start capture, fft cada frame, dos vistas (waveform + spectrum), shader fullscreen 256×256 al lado que samplea la `audio_texture` y desplaza colores en funcion del bin medio. +- 6.2 Boton start/stop. Mostrar device en uso. + +### Fase 7 — Tests + docs + +- 7.1 Test FFT con seno conocido. +- 7.2 Test ring buffer (productor escribe N, consumidor lee, sin perdidas). +- 7.3 Smoke test: capture 100ms, asserts `read > 0`. +- 7.4 `./fn index` + `./fn show` de los 5. + +## Ejemplo de uso + +```cpp +auto* cap = fn::audio_capture_start({}); +fn::FftResult prev{}; + +fn::run_app("audio", [&]{ + float buf[2048]; + int n = fn::audio_capture_read(cap, buf, 2048); + if (n >= 1024) { + prev = fn::audio_fft_compute(buf, 1024, 48000, 0.7f, &prev); + fn::waveform_view("##wav", buf, n); + fn::spectrum_view("##spec", prev.magnitudes.data(), prev.magnitudes.size(), 48000); + } +}); +``` + +## Decisiones de diseño + +- **miniaudio** vs portaudio: miniaudio es header-only, sin build extra; gana por simplicidad de integracion. +- **FFT minimo propio o pffft**: empezar con propio (50 LOC) — performance suficiente para 1024 samples a 60Hz. Si hace falta velocidad, swap por pffft. +- **`spectrum_view` y `waveform_view` puros**: no tocan el audio, solo dibujan. + +## Riesgos + +- **Permisos mic en macOS**: documentar Info.plist NSMicrophoneUsageDescription si se distribuye. +- **Loopback no estandar entre OSes**: documentar plataformas soportadas y degradar grácilmente si no esta disponible. +- **Latencia**: ring buffer vs callback puede acumular. Documentar tamaño y politica de drop. diff --git a/dev/issues/0031-cpp-animation-curves.md b/dev/issues/0031-cpp-animation-curves.md new file mode 100644 index 00000000..727842bf --- /dev/null +++ b/dev/issues/0031-cpp-animation-curves.md @@ -0,0 +1,162 @@ +# 0031 — C++ animation curves (timeline + bezier_editor + tween_curves) + +## APP Metadata + +| Campo | Valor | +|-------|-------| +| **ID** | 0031 | +| **Estado** | pendiente | +| **Prioridad** | media | +| **Tipo** | feature — C++ core (cpp/functions/core) | + +## Dependencias + +`tokens_cpp_core`. Independiente de los demas issues. + +**Desbloquea:** animar uniforms en `shaders_lab` con keyframes; easing reusable en transiciones de UI; control fino sobre animaciones procedurales. + +--- + +## Objetivo + +Tres primitivos: + +1. **`tween_curves_cpp_core`** — set de funciones de easing puras (Penner): linear, ease_in/out_quad/cubic/expo, elastic, bounce. Header-only. +2. **`bezier_editor_cpp_core`** — editor visual de una curva cubica Bezier (4 puntos de control), evaluacion `f(t)` para t∈[0,1]. Estado puro + render en ImGui canvas. +3. **`timeline_cpp_core`** — widget tipo DAW: tracks horizontales con keyframes draggable, scrub, play/pause, evaluacion `track_value_at(time)` con interp lineal o curve por keyframe. + +Demo en `primitives_gallery` con un slider animado por timeline + curva bezier para ease. + +## Contexto + +`shaders_lab` tiene sliders manuales para uniforms. No hay forma de: +- Animar un uniform con curva temporal. +- Disenar transiciones suaves reusables. + +Las funciones de easing son utiles tambien fuera de animacion (interpolacion de colores, rampas). + +## Arquitectura + +``` +cpp/functions/core/ +├── tween_curves.h # NEW (header-only ok) +├── tween_curves.cpp # NEW (si hace falta .cpp) +├── tween_curves.md # NEW +├── bezier_editor.h # NEW +├── bezier_editor.cpp # NEW +├── bezier_editor.md # NEW +├── timeline.h # NEW +├── timeline.cpp # NEW +└── timeline.md # NEW +cpp/apps/primitives_gallery/ +├── demos_animation.cpp # NEW +├── demos.h # MOD +├── main.cpp # MOD +└── CMakeLists.txt # MOD +``` + +### API propuesta + +```cpp +namespace fn { + +// --- tween_curves (puro, header-only ok) --- +namespace tween { + inline float linear(float t) { return t; } + inline float in_quad(float t) { return t*t; } + inline float out_quad(float t) { return 1 - (1-t)*(1-t); } + inline float in_out_cubic(float t) { /* Penner */ } + inline float in_expo(float t); + inline float out_expo(float t); + inline float in_elastic(float t); + inline float out_elastic(float t); + inline float out_bounce(float t); + // ... ~15 easing funcs + + enum class Ease { Linear, InQuad, OutQuad, InOutQuad, InCubic, OutCubic, /*...*/ }; + float apply(Ease e, float t); +} + +// --- bezier_editor (puro estado) --- +struct BezierCurve { ImVec2 p0{0,0}, p1{0.25f,0.0f}, p2{0.75f,1.0f}, p3{1,1}; }; +float bezier_eval(const BezierCurve&, float t); // y at x=t +bool bezier_editor(const char* id, BezierCurve&, ImVec2 size = {200, 200}); // returns true if changed + +// --- timeline --- +struct Keyframe { float time; float value; tween::Ease ease = tween::Ease::Linear; }; +struct Track { std::string name; std::vector keys; }; +struct TimelineState { + std::vector tracks; + float current_time = 0.0f; + float duration = 5.0f; + bool playing = false; +}; +float track_value_at(const Track&, float t); // interp puro +void timeline_update(TimelineState&, float dt); // avanza si playing +bool timeline_widget(const char* id, TimelineState&, ImVec2 size = {-1, 200}); // returns true if changed +} +``` + +## Tareas + +### Fase 1 — tween_curves + +- 1.1 Implementar las funciones Penner (referencia: easings.net). Header-only inline para que el compilador inline en hot paths. +- 1.2 `tween::apply(Ease, t)` con switch. +- 1.3 Tests: cada curva en t=0 y t=1 da 0 y 1 respectivamente (excepto elastic/bounce). +- 1.4 `.md`. + +### Fase 2 — bezier_editor + +- 2.1 `bezier_eval`: De Casteljau o forma polinomial. Implementacion puramente algebraica. +- 2.2 `bezier_editor`: canvas ImGui con 4 puntos draggable (p0/p3 fijos en {0,0}/{1,1} para easing). Dibuja la curva con `AddBezierCubic`. +- 2.3 Tests para `bezier_eval`. +- 2.4 `.md`. + +### Fase 3 — timeline + +- 3.1 `track_value_at`: encuentra el segmento (k_i, k_{i+1}), normaliza t, aplica `tween::apply(ease)`. +- 3.2 `timeline_update`: si `playing`, avanza `current_time += dt`; loop al llegar a duration. +- 3.3 `timeline_widget`: barra con tracks horizontales, keyframes como diamantes draggable, scrub time con click en barra superior, botones play/pause. Estilo basado en `tokens`. +- 3.4 Tests para `track_value_at` (linear). +- 3.5 `.md`. + +### Fase 4 — Gallery demo + +- 4.1 `demos_animation.cpp`: + - `demo_tween()`: dropdown de Ease + curva animandose contra el tiempo. + - `demo_bezier_editor()`: editor + plot de la curva resultante. + - `demo_timeline()`: timeline con 2 tracks ("hue", "amp") + slider que muestra `track_value_at(now)` para cada uno. +- 4.2 Registrar en gallery (3 entradas). + +### Fase 5 — Tests + docs + +- 5.1 Tests de easing y track_value_at. +- 5.2 `./fn index` + `./fn show` de los 3. + +## Ejemplo de uso + +```cpp +fn::TimelineState tl{}; +tl.tracks.push_back({"hue", {{0, 0}, {2, 1}, {4, 0}}}); +tl.duration = 4.0f; +tl.playing = true; + +fn::run_app("anim", [&](float dt){ + fn::timeline_update(tl, dt); + float h = fn::track_value_at(tl.tracks[0], tl.current_time); + ImGui::Text("hue = %.3f", h); + fn::timeline_widget("##tl", tl); +}); +``` + +## Decisiones de diseño + +- **Tween header-only**: codigo pequeño, alto uso → inline. +- **Bezier solo para easing (p0=0, p3=1)** en MVP. Generalizable si hace falta. +- **Timeline simple, no jerarquico**: tracks planos. Si hace falta layering en el futuro, otro issue. + +## Riesgos + +- **UX del bezier_editor**: precision de drag con mouse en canvas pequeño. Documentar tamaño minimo recomendado (180+). +- **Timeline drag race**: keyframe drag debe respetar orden temporal. Reordenar al soltar. diff --git a/dev/issues/0032-cpp-sql-workbench.md b/dev/issues/0032-cpp-sql-workbench.md new file mode 100644 index 00000000..7e60b7b4 --- /dev/null +++ b/dev/issues/0032-cpp-sql-workbench.md @@ -0,0 +1,130 @@ +# 0032 — C++ sql_workbench + +## APP Metadata + +| Campo | Valor | +|-------|-------| +| **ID** | 0032 | +| **Estado** | pendiente | +| **Prioridad** | media | +| **Tipo** | feature — C++ core (cpp/functions/core) | + +## Dependencias + +`text_editor_cpp_core` (issue 0025) — recomendado pero no bloqueante: si no esta listo, fallback a `ImGui::InputTextMultiline`. `table_view_cpp_viz`, `tokens_cpp_core`. SQLite ya esta en uso por `layout_storage_sqlite`. + +**Desbloquea:** explorar `registry.db` y `operations.db` desde una app C++ sin salir al CLI; debugging visual de assertions/executions. + +--- + +## Objetivo + +Componente ImGui que combina: +- Editor SQL (con highlight si `text_editor` esta disponible). +- Botón "Run" que ejecuta sobre una `sqlite3*` proporcionada por el caller. +- Tabla de resultados (`table_view`) con sorting y scrolling. +- Lista de tablas/views del schema (sidebar) clickable que insertan `SELECT * FROM LIMIT 100;`. +- Historial de queries en memoria + boton "Save" a un archivo `.sql.history`. + +Demo en `primitives_gallery` apuntando a `registry.db` (read-only). + +## Contexto + +Manualmente uso `sqlite3` CLI o DBeaver para inspeccionar `registry.db` mientras desarrollo. Tener un workbench in-app abre la posibilidad de apps C++ que sean dashboards-cum-SQL-explorers contra `operations.db` (entities, executions, assertions). + +## Arquitectura + +``` +cpp/functions/core/ +├── sql_workbench.h # NEW +├── sql_workbench.cpp # NEW +└── sql_workbench.md # NEW +cpp/apps/primitives_gallery/ +├── demos_sql.cpp # NEW +├── demos.h # MOD +├── main.cpp # MOD +└── CMakeLists.txt # MOD +``` + +### Pure core / impure shell + +`sql_workbench_cpp_core`: `kind: component`, `purity: impure` (ejecuta SQL contra una DB). + +### API propuesta + +```cpp +namespace fn { + +struct SqlWorkbenchState { + std::string query = "SELECT name FROM sqlite_master WHERE type='table';"; + std::string last_error; + std::vector columns; + std::vector> rows; + std::vector history; // queries ejecutadas + bool readonly = false; +}; + +// db: caller-owned sqlite3*. NO se cierra desde el componente. +void sql_workbench(const char* id, sqlite3* db, SqlWorkbenchState& state, ImVec2 size = {-1, -1}); +} +``` + +## Tareas + +### Fase 1 — Backend de ejecucion + +- 1.1 Implementar `sql_workbench_run_query(sqlite3*, const char* sql, SqlWorkbenchState&)`: prepara, step, lee columnas y filas como strings. Limpia errores anteriores. +- 1.2 Si `state.readonly=true`, rechazar comandos que no sean `SELECT`/`PRAGMA`/`EXPLAIN`. +- 1.3 Cap rows a 10000 para no congelar UI; mostrar warning si se trunca. + +### Fase 2 — Schema sidebar + +- 2.1 `sql_workbench_list_schema(sqlite3*) -> {tables: [], views: [], indices: []}`. Query a `sqlite_master`. +- 2.2 Sidebar collapsable con tablas. Click → insertar `SELECT * FROM LIMIT 100;` en el editor. + +### Fase 3 — UI + +- 3.1 Layout: panel izquierdo schema (200px), panel derecho dividido vertical (editor arriba, tabla resultado abajo). +- 3.2 Boton Run + atajo Ctrl+Enter. +- 3.3 Barra inferior: status (`5 rows in 12.3 ms`), error en rojo si hubo. +- 3.4 Editor: usar `text_editor` con `CodeLang::SQL` si disponible (compile-time `#ifdef FN_HAS_TEXT_EDITOR` o detectar via include guard). +- 3.5 Resultado: usar `table_view_cpp_viz` con sorting por columnas. + +### Fase 4 — Historial + +- 4.1 Cada Run exitoso aparea la query a `state.history`. +- 4.2 Popup "History" con las ultimas 50; click → recargar al editor. + +### Fase 5 — Gallery demo + +- 5.1 `demos_sql.cpp` con `demo_sql_workbench()`: abre `registry.db` (path resuelto via env var `FN_REGISTRY_ROOT` o cwd), modo readonly. La sidebar muestra `functions`, `types`, etc. +- 5.2 Registrar. + +### Fase 6 — Tests + docs + +- 6.1 Test `sql_workbench_run_query` con DB en memoria: crea tabla, insert, select, verifica columns/rows. +- 6.2 Test rechazo de DML en modo readonly. +- 6.3 `./fn index` + `./fn show sql_workbench_cpp_core`. + +## Ejemplo de uso + +```cpp +sqlite3* db; sqlite3_open_v2("registry.db", &db, SQLITE_OPEN_READONLY, nullptr); +fn::SqlWorkbenchState st; st.readonly = true; + +fn::run_app("sql", [&]{ + fn::sql_workbench("##sql", db, st); +}); +sqlite3_close(db); +``` + +## Decisiones de diseño + +- **Caller-owned DB**: el workbench no abre/cierra. Permite reusar conexiones del host (un app que ya tiene `operations.db` abierto la pasa). +- **Rows como strings**: SQLite ya las da convertibles. La `table_view` espera strings, sin conversion extra. +- **Readonly opt-in**: por defecto permite escribir; el caller decide. + +## Riesgos + +- **Queries que cuelgan UI**: documentar; en MVP, ejecucion sincrona en main thread. Si bloquea, considerar `std::thread` (ya disponible via `process_runner_cpp_core` patrón). +- **Memoria con resultados grandes**: cap a 10000 filas. Documentar. diff --git a/dev/issues/0033-cpp-http-ws-inspector.md b/dev/issues/0033-cpp-http-ws-inspector.md new file mode 100644 index 00000000..395c95ab --- /dev/null +++ b/dev/issues/0033-cpp-http-ws-inspector.md @@ -0,0 +1,144 @@ +# 0033 — C++ http_inspector + websocket_client + +## APP Metadata + +| Campo | Valor | +|-------|-------| +| **ID** | 0033 | +| **Estado** | pendiente | +| **Prioridad** | baja | +| **Tipo** | feature — C++ core (cpp/functions/core) | + +## Dependencias + +`text_editor_cpp_core` (issue 0025) recomendado para editar bodies; fallback a `InputTextMultiline`. `time_series_buffer_cpp_core`. + +**Desbloquea:** debugging visual de APIs internas y feeds en tiempo real desde C++ apps. Util para apps de monitoring que se conectan a servicios propios. + +--- + +## Objetivo + +Dos componentes complementarios: + +1. **`http_inspector_cpp_core`** — panel ImGui tipo Postman minimo: input URL, dropdown method (GET/POST/PUT/DELETE/PATCH), headers (key/value list), body, boton Send. Muestra status, headers de respuesta, body con pretty-print JSON. +2. **`websocket_client_cpp_core`** — panel WebSocket: input ws://… , botones Connect/Disconnect, area de mensajes (timeline), input para enviar texto, contador msg/s y plot rate. + +Vendorea [cpp-httplib](https://github.com/yhirose/cpp-httplib) (MIT, header-only) para HTTP. Para WS usa la API client de cpp-httplib (>=0.20 trae soporte ws basico) o vendorea [websocketpp](https://github.com/zaphoyd/websocketpp). Decidir en Fase 1. + +Demo en `primitives_gallery` con un endpoint HTTP de prueba (httpbin.org) y un WS echo (`wss://echo.websocket.events`). + +## Contexto + +Apps C++ que consumen APIs internas (registry_api, deploy_server) suelen requerir scripts curl/Postman aparte para debugging. Tener un inspector embebido reduce friccion. + +## Arquitectura + +``` +cpp/ +├── vendor/cpp-httplib/ +│ └── httplib.h # NEW (header-only, MIT) +├── functions/core/ +│ ├── http_inspector.h/.cpp/.md # NEW (impure component) +│ └── websocket_client.h/.cpp/.md # NEW (impure component) +└── apps/primitives_gallery/ + ├── demos_net.cpp # NEW + ├── demos.h # MOD + ├── main.cpp # MOD + └── CMakeLists.txt # MOD +cpp/CMakeLists.txt # MOD +``` + +### API propuesta + +```cpp +namespace fn { + +struct HttpHeader { std::string key, value; }; +struct HttpInspectorState { + std::string url = "https://httpbin.org/get"; + std::string method = "GET"; + std::vector headers; + std::string body; + int last_status = 0; + std::vector last_headers; + std::string last_body; + double last_ms = 0.0; + bool in_flight = false; // request en background + std::string error; +}; +void http_inspector(const char* id, HttpInspectorState&, ImVec2 size = {-1, -1}); + +struct WsMessage { double t; bool incoming; std::string text; }; +struct WebSocketClientState { + std::string url = "wss://echo.websocket.events"; + bool connected = false; + std::string send_buf; + std::vector messages; // capped at 500 + float rate_msgs_s = 0.f; + std::string error; +}; +void websocket_client(const char* id, WebSocketClientState&, ImVec2 size = {-1, -1}); +} +``` + +## Tareas + +### Fase 1 — Vendor + decision WS + +- 1.1 Vendorear `httplib.h` (pinear version reciente con TLS opcional). +- 1.2 Decidir cliente WS: + - Opcion A: cpp-httplib `WebSocketClient` si la version vendoreada lo trae. + - Opcion B: vendorear `websocketpp` (header-only-ish, depende de Asio header-only). +- 1.3 Documentar la decision en `cpp/vendor//README.md`. + +### Fase 2 — http_inspector + +- 2.1 Implementar el componente. Request en background con `std::thread` (ver `process_runner_cpp_core` como patrón). +- 2.2 Pretty-print: si `Content-Type: application/json`, formatear con un mini parser/printer JSON (o copiar uno minimal; no necesitamos validacion exhaustiva). +- 2.3 `.md` con frontmatter (`kind: component`, `purity: impure`, `error_type`). + +### Fase 3 — websocket_client + +- 3.1 Implementar `connect/disconnect` en thread; lockear cola de mensajes con mutex; copiar a `state.messages` en `websocket_client()` (main thread). +- 3.2 Calcular `rate_msgs_s` como contador por segundo con ventana deslizante (puede usar `time_series_buffer_cpp_core`). +- 3.3 `.md`. + +### Fase 4 — Gallery demo + +- 4.1 `demos_net.cpp` con `demo_http_inspector()` y `demo_websocket_client()`. +- 4.2 Registrar. + +### Fase 5 — Tests + docs + +- 5.1 Test pretty-print JSON sobre payload conocido. +- 5.2 Test ratelimit del rate_msgs_s. +- 5.3 Smoke test (opcional, requires net): GET a 127.0.0.1 contra un servidor levantado en el test. +- 5.4 `./fn index` + `./fn show`. + +## Ejemplo de uso + +```cpp +fn::HttpInspectorState http; +fn::WebSocketClientState ws; + +fn::run_app("net", [&]{ + if (ImGui::BeginTabBar("net")) { + if (ImGui::BeginTabItem("HTTP")) { fn::http_inspector("##h", http); ImGui::EndTabItem(); } + if (ImGui::BeginTabItem("WS")) { fn::websocket_client("##w", ws); ImGui::EndTabItem(); } + ImGui::EndTabBar(); + } +}); +``` + +## Decisiones de diseño + +- **cpp-httplib** vs libcurl: header-only gana en complejidad de build. TLS via OpenSSL si esta disponible; degradar a HTTP si no. +- **Mensajes WS capped**: 500 maximo en buffer para no crecer indefinidamente; descartar oldest. +- **Sin authentication helpers**: el header `Authorization` se mete a mano. UX simple. + +## Riesgos + +- **TLS**: cpp-httplib requiere OpenSSL para `https://`. Documentar en CMake como opt-in. +- **WS reconnect**: no implementar auto-reconnect en MVP. El usuario reconecta manualmente. +- **Threading**: cuidado con marshalling de strings entre thread y UI. Mutex obligatorio en mensajes. diff --git a/dev/issues/0034-cpp-scientific-viz.md b/dev/issues/0034-cpp-scientific-viz.md new file mode 100644 index 00000000..c5136e6e --- /dev/null +++ b/dev/issues/0034-cpp-scientific-viz.md @@ -0,0 +1,156 @@ +# 0034 — C++ scientific viz (treemap, sankey, chord, contour, voronoi) + +## APP Metadata + +| Campo | Valor | +|-------|-------| +| **ID** | 0034 | +| **Estado** | pendiente | +| **Prioridad** | media | +| **Tipo** | feature — C++ viz (cpp/functions/viz) | + +## Dependencias + +`tokens_cpp_core`, `plot_theme_cpp_core`. ImPlot ya vendoreado. Sin bloqueos por otros issues. + +**Desbloquea:** dashboards con tipos de visualizacion mas alla de bar/line/scatter — utiles para jerarquias, flujos, relaciones, distribuciones 2D continuas. + +--- + +## Objetivo + +Cinco componentes nuevos en `cpp/functions/viz/`, cada uno con su demo en gallery: + +1. **`treemap_cpp_viz`** — squarified treemap (Bruls, Huijbrechts, van Wijk) para jerarquias planas con valores. Click hace zoom (opcional MVP: solo render). +2. **`sankey_cpp_viz`** — sankey diagram para flujos `source → target` con magnitudes. +3. **`chord_cpp_viz`** — chord diagram para matrices de relaciones N×N (ej: flujos entre N entidades). +4. **`contour_cpp_viz`** — contour plot de un grid 2D escalar (marching squares con N niveles). +5. **`voronoi_cpp_viz`** — diagrama de Voronoi (Fortune o aproximacion via ImGui draw): regiones coloreadas a partir de N seeds. + +Todos los layouts/algoritmos deben ser **puros**; el render usa primitivas ImGui DrawList (`AddTriangleFilled`, `AddBezierQuadratic`, etc.). + +## Contexto + +`cpp/functions/viz/` cubre charts standard (bar/line/scatter/heatmap/histogram/pie/candlestick/sparkline + table + graph). Faltan las visualizaciones "narrativas" tipicas de dashboards mas avanzados. + +Implementar 5 a la vez es viable porque comparten la misma estructura: `compute_layout` puro + `render` con DrawList. + +## Arquitectura + +``` +cpp/functions/viz/ +├── treemap.h/.cpp/.md # NEW +├── sankey.h/.cpp/.md # NEW +├── chord.h/.cpp/.md # NEW +├── contour.h/.cpp/.md # NEW +└── voronoi.h/.cpp/.md # NEW +cpp/apps/primitives_gallery/ +├── demos_scientific.cpp # NEW (5 demos en un archivo) +├── demos.h # MOD +├── main.cpp # MOD +└── CMakeLists.txt # MOD +``` + +### API propuesta + +```cpp +namespace fn { + +// --- treemap --- +struct TreemapItem { std::string label; float value; ImU32 color; }; +struct TreemapRect { ImVec2 min, max; const TreemapItem* item; }; +std::vector treemap_layout(const std::vector&, ImVec2 region); // pure +void treemap(const char* id, const std::vector&, ImVec2 size = {-1, 300}); + +// --- sankey --- +struct SankeyNode { std::string label; }; +struct SankeyLink { int src, dst; float value; }; +void sankey(const char* id, + const std::vector&, const std::vector&, + ImVec2 size = {-1, 400}); + +// --- chord --- +void chord(const char* id, + const float* matrix, int n, const char* const* labels, + ImVec2 size = {400, 400}); + +// --- contour --- +struct ContourLine { std::vector pts; float level; }; +std::vector contour_compute(const float* grid, int nx, int ny, const float* levels, int n_levels); // pure (marching squares) +void contour(const char* id, const float* grid, int nx, int ny, const float* levels, int n_levels, + ImVec2 size = {-1, 300}); + +// --- voronoi --- +struct VoronoiCell { std::vector polygon; ImVec2 seed; ImU32 color; }; +std::vector voronoi_layout(const ImVec2* seeds, int n, ImVec2 region); // pure +void voronoi(const char* id, const ImVec2* seeds, int n, const ImU32* colors, ImVec2 size = {-1, 300}); +} +``` + +## Tareas + +### Fase 1 — treemap + +- 1.1 Implementar squarified algorithm (recursivo, O(n log n)). +- 1.2 Render con `AddRectFilled` + `AddText` (label si cabe). +- 1.3 Tests con 5 items conocidos. +- 1.4 `.md`. + +### Fase 2 — sankey + +- 2.1 Asignar nodos a columnas via topological levels (BFS desde sources). +- 2.2 Stack vertical de cada columna por valor; calcular Y offset por link. +- 2.3 Render: rectangulos para nodos + bezier cubic para links. +- 2.4 `.md`. + +### Fase 3 — chord + +- 3.1 Calcular angulos por nodo proporcionales a `sum(matrix[i,*])`. +- 3.2 Arcos en el borde del circulo + bezier interno entre arcos. +- 3.3 `.md`. + +### Fase 4 — contour + +- 4.1 `contour_compute` con marching squares clasico (16 casos), interp lineal entre celdas. +- 4.2 Render: `AddPolyline` por cada level, color por level (gradiente). +- 4.3 Tests con grid conocido (gaussiana → contornos circulares). +- 4.4 `.md`. + +### Fase 5 — voronoi + +- 5.1 MVP: implementacion brute-force (para cada pixel, encontrar seed mas cercana). Para N pequeño (<200) y region <500², es < 1ms. +- 5.2 Para layout poligonal: usar half-plane intersections (mas complejo). MVP puede renderizar como raster a una textura cacheada. +- 5.3 `.md`. + +### Fase 6 — Gallery demos + +- 6.1 `demos_scientific.cpp` con 5 funciones, una por chart, datos sinteticos coherentes (treemap: gastos por categoria; sankey: flujos cliente→producto; chord: relaciones entre paises; contour: gaussiana mezcla; voronoi: 30 seeds aleatorios). +- 6.2 Registrar en gallery. + +### Fase 7 — Tests + docs + +- 7.1 Tests por componente (algorithm puro, no render). +- 7.2 `./fn index` + `./fn show` para los 5. + +## Ejemplo de uso + +```cpp +std::vector items = { + {"comida", 320, IM_COL32(120,180,200,255)}, + {"vivienda", 950, IM_COL32(180,120,200,255)}, + {"transporte", 180, IM_COL32(200,180,120,255)}, +}; +fn::treemap("##gastos", items); +``` + +## Decisiones de diseño + +- **Render con DrawList** (no ImPlot) para chart types que ImPlot no soporta nativo. +- **Layout puro separado del render**: facilita tests y reuso (treemap → exportar a SVG futuro). +- **Voronoi raster en MVP**: simple, suficiente. Si hace falta poligonos, otro issue. + +## Riesgos + +- **Tamaño del issue**: 5 charts es mucho. Mitigacion: comparten patron y un archivo `demos_scientific.cpp`. Si un agente no termina, sub-issue por chart. +- **Voronoi performance**: documentar limite practico N <= 200. +- **Sankey con grafo ciclico**: assumir DAG; documentar. diff --git a/dev/issues/0035-cpp-map-tiles.md b/dev/issues/0035-cpp-map-tiles.md new file mode 100644 index 00000000..f7419b1e --- /dev/null +++ b/dev/issues/0035-cpp-map-tiles.md @@ -0,0 +1,122 @@ +# 0035 — C++ map_tiles (slippy map OSM) + +## APP Metadata + +| Campo | Valor | +|-------|-------| +| **ID** | 0035 | +| **Estado** | pendiente | +| **Prioridad** | baja | +| **Tipo** | feature — C++ viz (cpp/functions/viz) | + +## Dependencias + +`gl_texture_load_cpp_gfx` (issue 0026), `gl_loader_cpp_gfx`. Vendor: `cpp-httplib` (si ya esta vendoreado por 0033) o reusar. + +**Desbloquea:** apps GIS minimas, dashboards con localizacion (entities con lat/lon), debugging de scrapers geograficos. + +--- + +## Objetivo + +Componente ImGui que renderiza un slippy map estilo Leaflet sobre OpenStreetMap tiles, con pan/zoom y soporte para markers + polylines. + +## Contexto + +Hay datos geograficos en varios proyectos (registry de proveedores, viajes, infrastructura). No hay forma de visualizarlos en C++; se exporta a una notebook con folium o se monta un frontend React. + +## Arquitectura + +``` +cpp/functions/viz/ +├── map_tiles.h # NEW +├── map_tiles.cpp # NEW +└── map_tiles.md # NEW (impure) +cpp/apps/primitives_gallery/ +├── demos_map.cpp # NEW +├── demos.h # MOD +├── main.cpp # MOD +└── CMakeLists.txt # MOD +``` + +### API propuesta + +```cpp +namespace fn { + +struct MapMarker { double lat, lon; ImU32 color; const char* label; }; +struct MapPolyline { std::vector latlon; ImU32 color; float thickness = 2.f; }; // ImVec2 = (lat, lon) +struct MapState { + double lat = 40.4168; // Madrid default + double lon = -3.7038; + int zoom = 6; // 0..18 + std::string tile_url = "https://tile.openstreetmap.org/{z}/{x}/{y}.png"; + std::string user_agent = "fn-registry-map/0.1 (egutierrez@aurgi.com)"; + bool show_attribution = true; // OSM atribucion obligatoria +}; + +void map_tiles(const char* id, MapState&, + const MapMarker* markers, int n_markers, + const MapPolyline* polylines, int n_polylines, + ImVec2 size = {-1, -1}); +} +``` + +## Tareas + +### Fase 1 — Tile cache + +- 1.1 Cache en disco bajo `~/.cache/fn-registry/tiles/{z}/{x}/{y}.png`. Crear directorio si no existe. +- 1.2 Cache en RAM (LRU) de `GlTexture` ya subidas, max 256 entradas. +- 1.3 Funcion `tile_get_or_fetch(z, x, y)`: si en RAM → return; si en disco → load (gl_texture_load); si no → spawn thread que descarga via cpp-httplib y guarda en disco. Mientras tanto, devolver placeholder. + +### Fase 2 — Proyeccion + interaction + +- 2.1 Funciones puras `lonlat_to_xy(lat, lon, zoom) -> (px, py)` y `xy_to_lonlat`. +- 2.2 `map_tiles_handle_input`: drag pan, wheel zoom (clamp 0-18), mantener punto bajo cursor durante zoom. + +### Fase 3 — Render + +- 3.1 Calcular tiles visibles dado el viewport y zoom. +- 3.2 Para cada tile visible: `ImGui::Image(texture, screen_rect)` o `AddImage` en el DrawList. +- 3.3 Markers: `AddCircleFilled` + texto. +- 3.4 Polylines: convertir lat/lon a screen y `AddPolyline`. +- 3.5 Atribucion en esquina inferior derecha: `"© OpenStreetMap contributors"`. + +### Fase 4 — User-Agent + ToS + +- 4.1 Enviar `User-Agent` custom (OSM lo exige). Documentar en `.md` que el usuario debe poner su contacto. +- 4.2 Cap a 2 requests/segundo simultaneos para no abusar del tile server. + +### Fase 5 — Gallery demo + +- 5.1 `demos_map.cpp` con `demo_map_tiles()`: mapa centrado en Espana, 5 markers en ciudades, polyline conectandolas. +- 5.2 Registrar. + +### Fase 6 — Tests + docs + +- 6.1 Tests puros de proyeccion (Madrid → conocidos pixels a zoom 10). +- 6.2 `./fn index` + `./fn show map_tiles_cpp_viz`. + +## Ejemplo de uso + +```cpp +fn::MapState m; +fn::MapMarker markers[] = { + {40.4168, -3.7038, IM_COL32(255,80,80,255), "Madrid"}, + {41.3851, 2.1734, IM_COL32(80,150,255,255), "Barcelona"}, +}; +fn::map_tiles("##m", m, markers, 2, nullptr, 0); +``` + +## Decisiones de diseño + +- **OSM tile server por defecto**: gratis, sin API key. Documentar limite y alternativa (carto, stamen) cambiando `tile_url`. +- **Cache en `~/.cache/fn-registry/tiles/`**: respeta XDG; persistente entre runs. +- **Sin GeoJSON parsing en MVP**: solo markers/polylines via API. GeoJSON en issue futuro. + +## Riesgos + +- **Bloqueo de IP por OSM**: documentar terminos de uso (max 1 req/s recomendado, User-Agent unico). +- **Tiles a alta latitud**: documentar que la proyeccion Web Mercator distorsiona >85°. +- **Threading de descargas**: cuidado con marshalling de texturas GL (deben crearse en hilo con contexto). Patron: descargar PNG bytes en thread, crear GL texture en main thread. diff --git a/dev/issues/0036-cpp-image-canvas-webcam.md b/dev/issues/0036-cpp-image-canvas-webcam.md new file mode 100644 index 00000000..12e1374f --- /dev/null +++ b/dev/issues/0036-cpp-image-canvas-webcam.md @@ -0,0 +1,144 @@ +# 0036 — C++ image_canvas + webcam_texture + +## APP Metadata + +| Campo | Valor | +|-------|-------| +| **ID** | 0036 | +| **Estado** | pendiente | +| **Prioridad** | baja | +| **Tipo** | feature — C++ core/gfx (cpp/functions/core, cpp/functions/gfx) | + +## Dependencias + +`gl_texture_load_cpp_gfx` (issue 0026) — recomendado. `tokens_cpp_core`. Independiente de los demas. + +**Desbloquea:** UIs para etiquetado/anotacion de imagenes (CV, OCR debugging) y entrada de webcam para `shaders_lab` reactivo a video. + +--- + +## Objetivo + +Dos primitivos: + +1. **`image_canvas_cpp_core`** — viewer de imagen con pan/zoom + capa de anotaciones (rectangulos, puntos, polilineas, texto). Drag para crear nuevas anotaciones; click para seleccionar; tecla Del para borrar. +2. **`webcam_texture_cpp_gfx`** — captura de webcam (V4L2 en Linux, Media Foundation en Windows) → GL texture actualizada cada frame. API parejo a `audio_capture`. + +## Contexto + +Casos de uso identificados: +- Inspeccionar resultados de un detector (rectangulos overlay). +- Etiquetar manualmente datasets pequeños in-app. +- Pasar webcam a shaders en `shaders_lab` (filtros video en tiempo real). + +Ningun primitivo del registry actual cubre estos casos. + +## Arquitectura + +``` +cpp/functions/core/ +├── image_canvas.h # NEW +├── image_canvas.cpp # NEW +└── image_canvas.md # NEW (impure component) +cpp/functions/gfx/ +├── webcam_texture.h # NEW +├── webcam_texture.cpp # NEW +└── webcam_texture.md # NEW (impure) +cpp/apps/primitives_gallery/ +├── demos_image_webcam.cpp # NEW +├── demos.h # MOD +├── main.cpp # MOD +└── CMakeLists.txt # MOD +``` + +### API propuesta + +```cpp +namespace fn { + +// --- image_canvas --- +struct Annotation { + enum Kind { Rect, Point, Polyline, Text } kind; + std::vector points; // image-space coords (no screen) + ImU32 color = IM_COL32(255, 80, 80, 255); + std::string label; +}; +struct ImageCanvasState { + GLuint texture = 0; // caller-owned + int img_w = 0, img_h = 0; + float zoom = 1.f; + ImVec2 pan = {0, 0}; + std::vector annotations; + int selected = -1; + enum Tool { ToolNone, ToolRect, ToolPoint, ToolPolyline } tool = ToolNone; +}; +void image_canvas(const char* id, ImageCanvasState&, ImVec2 size = {-1, -1}); + +// --- webcam_texture --- +struct WebcamTextureConfig { int width = 640; int height = 480; int fps = 30; int device = 0; }; +struct WebcamTexture { GLuint id = 0; int w = 0; int h = 0; bool ok() const { return id != 0; } }; +WebcamTexture webcam_texture_start(const WebcamTextureConfig&); +void webcam_texture_stop(WebcamTexture&); +bool webcam_texture_update(WebcamTexture&); // copia ultimo frame a GPU; true si hubo nuevo +const char* webcam_texture_last_error(); +} +``` + +## Tareas + +### Fase 1 — image_canvas + +- 1.1 Render: `ImGui::Image(texture, ...)` con transform por pan/zoom. Drag con MMB para pan, wheel para zoom. +- 1.2 Tool == Rect: drag con LMB en image-space crea anotacion `Rect` con 2 puntos. +- 1.3 Tool == Point: click LMB anade un Point. +- 1.4 Tool == Polyline: click anade puntos; doble-click cierra. +- 1.5 Hit-test para selection; Del key elimina la seleccionada. +- 1.6 `.md`. + +### Fase 2 — webcam_texture (Linux V4L2) + +- 2.1 Abrir `/dev/video`, MMAP buffers, VIDIOC_STREAMON. +- 2.2 En `update`, dequeue buffer, convert YUYV → RGBA (CPU), upload a GL texture, requeue. +- 2.3 `.md` (`kind: function`, `purity: impure`, `error_type`). + +### Fase 3 — webcam_texture (Windows Media Foundation, opcional) + +- 3.1 Si `_WIN32`, usar `IMFSourceReader`. Convert NV12 → RGBA. +- 3.2 Documentar que macOS no se soporta en MVP. + +### Fase 4 — Gallery demo + +- 4.1 `demos_image_webcam.cpp`: + - `demo_image_canvas()`: carga `assets/sample.png` (si existe — fallback a rendered placeholder), permite pintar rectangulos. + - `demo_webcam_texture()`: start cam, mostrar preview + sliders RGB sobre un shader fullscreen que samplea la cam. +- 4.2 Registrar. + +### Fase 5 — Tests + docs + +- 5.1 Tests puros de transform image-space ↔ screen-space. +- 5.2 Smoke test webcam (skip si no hay /dev/video0). +- 5.3 `./fn index`. + +## Ejemplo de uso + +```cpp +auto cam = fn::webcam_texture_start({}); +if (!cam.ok()) std::fprintf(stderr, "%s\n", fn::webcam_texture_last_error()); + +fn::run_app("cam", [&]{ + fn::webcam_texture_update(cam); + ImGui::Image((ImTextureID)(intptr_t)cam.id, {cam.w*1.f, cam.h*1.f}); +}); +``` + +## Decisiones de diseño + +- **Anotaciones en image-space**: pan/zoom no afectan coordenadas guardadas. Permite serializar a JSON (issue futuro). +- **YUYV → RGBA en CPU**: simple, ~5ms/frame a 640×480. Si hace falta, hacer un compute shader (issue 0027). +- **macOS sin webcam por ahora**: AVFoundation requiere ObjC++; no compensa en MVP. + +## Riesgos + +- **Permisos webcam**: documentar troubleshooting (`/dev/video0` permisos en Linux). +- **Driver V4L2 raro**: algunos formatos de pixel no son YUYV. Documentar fallback a MJPEG (con stb_image decode) o error claro. +- **`ImTextureID` semantics** entre backends: documentar. diff --git a/dev/issues/README.md b/dev/issues/README.md index 808325ab..5bd6304c 100644 --- a/dev/issues/README.md +++ b/dev/issues/README.md @@ -30,3 +30,15 @@ | [0022](completed/0022-init-pipelines.md) | Init Pipelines (scaffolding) | completado | alta | feature | — | | [0023](completed/0023-testing-utils.md) | Testing Utilities Go | completado | media | feature | — | | [0024](completed/0024-dashboard-yaml-split-por-tab.md) | auto_metabase: split dashboard YAMLs por tab | completado | alta | mejora | — | +| [0025](0025-cpp-text-editor-file-watcher.md) | C++ text_editor + file_watcher | pendiente | alta | feature | — | +| [0026](0026-cpp-gl-texture-load.md) | C++ gl_texture_load (stb_image → sampler2D) | pendiente | alta | feature | 0035, 0036 | +| [0027](0027-cpp-gl-compute-pingpong.md) | C++ gl_compute_shader + gl_pingpong_fbo + DAG Compute | pendiente | alta | feature | — | +| [0028](0028-cpp-implot3d-3d-viz.md) | C++ ImPlot3D + surface_plot_3d + scatter_3d | pendiente | media | feature | — | +| [0029](0029-cpp-mesh-viewer.md) | C++ mesh_viewer + obj loader + orbit_camera | pendiente | media | feature | — | +| [0030](0030-cpp-audio-reactive.md) | C++ audio reactivo (capture + fft + uniform feed + viz) | pendiente | media | feature | — | +| [0031](0031-cpp-animation-curves.md) | C++ animation curves (timeline + bezier_editor + tween) | pendiente | media | feature | — | +| [0032](0032-cpp-sql-workbench.md) | C++ sql_workbench | pendiente | media | feature | — | +| [0033](0033-cpp-http-ws-inspector.md) | C++ http_inspector + websocket_client | pendiente | baja | feature | — | +| [0034](0034-cpp-scientific-viz.md) | C++ scientific viz (treemap, sankey, chord, contour, voronoi) | pendiente | media | feature | — | +| [0035](0035-cpp-map-tiles.md) | C++ map_tiles (slippy map OSM) | pendiente | baja | feature | — | +| [0036](0036-cpp-image-canvas-webcam.md) | C++ image_canvas + webcam_texture | pendiente | baja | feature | — |