feat(viz): renderer shapes/iconos/flechas/edge-styles (issue 0049f)

graph_renderer 1.5.0:
- 6 shapes SDF (circle, square, diamond, hex, triangle, rounded square)
  con dispatch en fragment shader y AA via fwidth.
- Atlas opcional de iconos Tabler bakeado por graph_icons; el shader
  compone overlay desde un uniform vec4 u_icon_uvs[256]. Setter publico
  graph_renderer_set_icon_atlas(r, tex, uv_table, count).
- Aristas direccionales: 6 vertices por arista (line + chevron de la
  flecha) en una sola draw call; segmento principal acortado por el
  radio del nodo target.
- Edge styles solid/dashed/dotted via descarte por arc_length en el
  fragment shader; las lineas del chevron son siempre solidas.

graph_icons 1.0.0 (nuevo):
- Atlas RGBA8 512x512 = grid 16x16 (256 iconos max) bakeado con
  stb_truetype desde tabler-icons.ttf.
- API: graph_icons_build/texture/region/uv_table/destroy. icon_id es
  1-based; 0 reservado para "sin icono".
- Hook FN_GRAPH_ICONS_SKIP_GL=1 para tests sin contexto GL.

Demo demos_graph_styles en primitives_gallery: 6 EntityTypes (uno por
shape) con icono Tabler representativo + 3 RelationTypes (knows/uses/
owns) con flechas direccionales y los 3 estilos.

test_graph_icons: 6 casos cubriendo bake, regiones 1-indexed, uv_table
consistente, layout en grid 16x16, validacion de count fuera de rango,
y verificacion de alpha != 0 en las celdas tras bake.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-29 23:01:49 +02:00
parent 34a3addc56
commit c967c2edfd
14 changed files with 1180 additions and 174 deletions
+62
View File
@@ -0,0 +1,62 @@
#pragma once
#include <cstdint>
// Atlas de iconos Tabler para `graph_renderer`. Bakea N codepoints (0xE000-
// 0xFCFF) en una textura RGBA 512×512 organizada como grid de 16×16 celdas
// de 32 px cada una. Cada icono se rasteriza con `stb_truetype` desde
// `tabler-icons.ttf`. La textura se sube a GPU como `GL_RGBA8` con filtrado
// linear.
//
// Convencion de IDs: `icon_id = 0` significa "sin icono". Las regiones
// devueltas por `graph_icons_build` tienen `id = i + 1`, donde `i` es la
// posicion del codepoint en el array de entrada. De esta forma un nodo o un
// EntityType con `icon_id == 0` (default tras `graph_node`/`entity_type`) se
// pinta sin icono superpuesto.
struct IconAtlas;
struct IconRegion {
uint16_t id; // 1-based; 0 reservado para "sin icono"
uint16_t codepoint; // codepoint Unicode original (debug)
float u0, v0, u1, v1; // UVs en [0,1] dentro del atlas
};
// Construye el atlas. `count` puede ser 1..256 (limite del grid 16×16).
// `icon_px` controla el tamano de rasterizacion; tipicamente 32 para grid de
// 32 px sin re-escalado.
//
// Devuelve `nullptr` si no encuentra `tabler-icons.ttf` o si `count` esta
// fuera de rango. El TTF se busca en (en orden):
// 1. `./tabler-icons.ttf`
// 2. `./assets/tabler-icons.ttf`
// 3. `$FN_ASSETS_DIR/tabler-icons.ttf`
// 4. `${FN_CPP_ROOT}/vendor/tabler-icons/tabler-icons.ttf`
//
// Requiere un contexto OpenGL valido en el hilo actual (sube la textura).
IconAtlas* graph_icons_build(const uint16_t* codepoints, int count, int icon_px = 32);
// GL texture id (RGBA8) — lo consume `graph_renderer` como `samplerBuffer u_icon_atlas`.
unsigned int graph_icons_texture(const IconAtlas*);
// Devuelve la region por icon_id (1-based). nullptr si fuera de rango.
const IconRegion* graph_icons_region(const IconAtlas*, uint16_t icon_id);
// Numero de iconos cargados.
int graph_icons_count(const IconAtlas*);
// Dimensiones del atlas en pixels (siempre 512×512 actualmente).
int graph_icons_width(const IconAtlas*);
int graph_icons_height(const IconAtlas*);
// Acceso al bitmap RGBA en CPU (para tests / debug). Layout: row-major,
// `width * height * 4` bytes. NULL si el atlas se construyo sin retener
// pixels (por defecto se retienen para tests).
const unsigned char* graph_icons_pixels(const IconAtlas*);
// Tabla plana de UVs lista para subir como uniform array al shader. Layout:
// `count * 4` floats consecutivos (u0, v0, u1, v1) en el orden de
// codepoints pasado a `_build` (0-indexed: el icono con `icon_id == k`
// vive en `uv_table[(k-1)*4 .. (k-1)*4 + 4]`).
const float* graph_icons_uv_table(const IconAtlas*);
void graph_icons_destroy(IconAtlas*);