docs(issues): añadir 0025-0036 — features C++ para registry y primitives_gallery

12 issues nuevos para implementacion paralela: text_editor, file_watcher,
gl_texture_load, gl_compute+pingpong+DAG Compute, ImPlot3D, mesh_viewer,
audio reactivo, animation curves, sql_workbench, http+ws inspector,
scientific viz (5 charts), map_tiles, image_canvas + webcam_texture.

Cada issue añade funciones al registry y un demo propio en
primitives_gallery/demos_<feature>.cpp para minimizar conflictos en paralelo.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-25 20:48:18 +02:00
parent 7d598c7345
commit a11a58dab0
13 changed files with 1807 additions and 0 deletions
@@ -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<FileEvent> 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 <archivo.glsl>` 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.
+130
View File
@@ -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).
+171
View File
@@ -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.
+138
View File
@@ -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<float> 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).
+178
View File
@@ -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.
+170
View File
@@ -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<float> 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.
+162
View File
@@ -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<Keyframe> keys; };
struct TimelineState {
std::vector<Track> 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.
+130
View File
@@ -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 <name> 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<std::string> columns;
std::vector<std::vector<std::string>> rows;
std::vector<std::string> 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 <t> 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.
+144
View File
@@ -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<HttpHeader> headers;
std::string body;
int last_status = 0;
std::vector<HttpHeader> 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<WsMessage> 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/<lib>/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.
+156
View File
@@ -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<TreemapRect> treemap_layout(const std::vector<TreemapItem>&, ImVec2 region); // pure
void treemap(const char* id, const std::vector<TreemapItem>&, 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<SankeyNode>&, const std::vector<SankeyLink>&,
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<ImVec2> pts; float level; };
std::vector<ContourLine> 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<ImVec2> polygon; ImVec2 seed; ImU32 color; };
std::vector<VoronoiCell> 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<fn::TreemapItem> 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.
+122
View File
@@ -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<ImVec2> 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.
+144
View File
@@ -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<ImVec2> 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<Annotation> 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<device>`, 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.
+12
View File
@@ -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 | — |