diff --git a/cpp/functions/viz/voronoi.cpp b/cpp/functions/viz/voronoi.cpp new file mode 100644 index 00000000..bc787dcc --- /dev/null +++ b/cpp/functions/viz/voronoi.cpp @@ -0,0 +1,83 @@ +#include "viz/voronoi.h" + +#include + +namespace { + +inline int nearest_seed(float px, float py, const ImVec2* seeds, int n) { + int best = 0; + float bd2 = 1e30f; + for (int i = 0; i < n; i++) { + float dx = px - seeds[i].x; + float dy = py - seeds[i].y; + float d2 = dx * dx + dy * dy; + if (d2 < bd2) { bd2 = d2; best = i; } + } + return best; +} + +} // namespace + +std::vector voronoi_layout(const ImVec2* seeds, int n, ImVec2 /*region*/) { + std::vector out; + if (!seeds || n <= 0) return out; + out.resize(n); + for (int i = 0; i < n; i++) { + out[i].seed = seeds[i]; + out[i].color = IM_COL32(120, 144, 252, 230); + out[i].polygon.clear(); + } + return out; +} + +void voronoi(const char* id, + const ImVec2* seeds, + int n, + const ImU32* colors, + ImVec2 size) { + ImGui::PushID(id); + + ImVec2 avail = ImGui::GetContentRegionAvail(); + float W = (size.x > 0.0f) ? size.x : avail.x; + float H = (size.y > 0.0f) ? size.y : 200.0f; + + ImVec2 origin = ImGui::GetCursorScreenPos(); + ImGui::Dummy(ImVec2(W, H)); + + if (!seeds || n <= 0) { ImGui::PopID(); return; } + + ImDrawList* dl = ImGui::GetWindowDrawList(); + + // Raster: tile de 4x4 pixels. Region <= ~600x400 con N<=200 -> ~150*100 = 15000 calls, + // dentro de presupuesto frame. + const float tile = 4.0f; + int cols = (int)std::ceil(W / tile); + int rows = (int)std::ceil(H / tile); + + for (int ry = 0; ry < rows; ry++) { + float py = (ry + 0.5f) * tile; + if (py > H) py = H; + for (int rx = 0; rx < cols; rx++) { + float px = (rx + 0.5f) * tile; + if (px > W) px = W; + int idx = nearest_seed(px, py, seeds, n); + ImU32 col = colors ? colors[idx] : IM_COL32(120, 144, 252, 230); + + ImVec2 a(origin.x + rx * tile, origin.y + ry * tile); + ImVec2 b(origin.x + std::min((rx + 1) * tile, W), + origin.y + std::min((ry + 1) * tile, H)); + dl->AddRectFilled(a, b, col); + } + } + + // Borde y seeds + dl->AddRect(origin, ImVec2(origin.x + W, origin.y + H), + IM_COL32(80, 80, 90, 220), 0.0f, 0, 1.0f); + for (int i = 0; i < n; i++) { + ImVec2 c(origin.x + seeds[i].x, origin.y + seeds[i].y); + dl->AddCircleFilled(c, 2.5f, IM_COL32(20, 22, 28, 255)); + dl->AddCircleFilled(c, 1.5f, IM_COL32(245, 246, 250, 255)); + } + + ImGui::PopID(); +} diff --git a/cpp/functions/viz/voronoi.h b/cpp/functions/viz/voronoi.h new file mode 100644 index 00000000..27042226 --- /dev/null +++ b/cpp/functions/viz/voronoi.h @@ -0,0 +1,26 @@ +#pragma once + +// Diagrama de Voronoi via raster brute-force (MVP). +// Para cada pixel, encontrar el seed mas cercano (Euclidea). Suficiente para +// N <= 200 seeds y region <= 500x500. + +#include "imgui.h" +#include + +struct VoronoiCell { + std::vector polygon; // En MVP raster queda vacio. Lo dejamos por API future-proof. + ImVec2 seed; + ImU32 color; +}; + +// Layout puro. En MVP raster no devuelve poligonos analiticos: el polygon +// queda vacio y solo se rellenan seed/color. El render usa raster directo. +std::vector voronoi_layout(const ImVec2* seeds, int n, ImVec2 region); + +// Render brute-force: muestrea region en celdas de px_size pixeles, para cada +// celda escribe un AddRectFilled con el color del seed mas cercano. +void voronoi(const char* id, + const ImVec2* seeds, + int n, + const ImU32* colors, + ImVec2 size = ImVec2(-1.0f, 300.0f)); diff --git a/cpp/functions/viz/voronoi.md b/cpp/functions/viz/voronoi.md new file mode 100644 index 00000000..5585c7db --- /dev/null +++ b/cpp/functions/viz/voronoi.md @@ -0,0 +1,56 @@ +--- +name: voronoi +kind: component +lang: cpp +domain: viz +version: "1.0.0" +purity: pure +signature: "void voronoi(const char* id, const ImVec2* seeds, int n, const ImU32* colors, ImVec2 size)" +description: "Diagrama de Voronoi via raster brute-force (MVP). Para cada tile 4x4 px encontrar seed mas cercano y rellenar con su color." +tags: [imgui, drawlist, chart, visualization, voronoi, raster] +uses_functions: [] +uses_types: [] +returns: [] +returns_optional: false +error_type: "" +imports: [imgui] +tested: false +tests: [] +test_file_path: "" +file_path: "cpp/functions/viz/voronoi.cpp" +framework: imgui +params: + - name: id + desc: "Identificador unico para PushID" + - name: seeds + desc: "Array de N posiciones (en coords locales del rect, [0..W]x[0..H])" + - name: n + desc: "Numero de seeds" + - name: colors + desc: "Array de N colores ImU32 (uno por seed). Si nullptr, usa color default" + - name: size + desc: "Tamano del area Voronoi. x <= 0 usa el ancho disponible" +output: "Renderiza la teselacion de Voronoi como mosaico de tiles 4x4 px coloreados + seeds visibles como circulos blancos sobre negros" +--- + +# voronoi + +Voronoi diagram MVP via raster brute-force. Para cada tile de 4x4 pixeles del rect de render, calcula el seed mas cercano por distancia Euclidea y rellena el tile con el color de ese seed. + +## Limitaciones MVP + +- **No genera poligonos analiticos** — `voronoi_layout` deja `polygon` vacio. Para extraer poligonos seria necesario half-plane intersections (Fortune's algorithm) — ver issue futuro. +- **Tile 4x4 px**: las fronteras son escalonadas. Si se requiere precision sub-pixel, reducir el tile (cuesta O(1/tile^2) en tiempo). +- **Performance**: O(W * H * N / tile^2). Para N <= 200 y region 500x500 es < 1ms. + +## Ejemplo + +```cpp +ImVec2 seeds[30]; +ImU32 colors[30]; +for (int i = 0; i < 30; i++) { + seeds[i] = ImVec2(rand_float() * W, rand_float() * H); + colors[i] = IM_COL32(rand() & 255, rand() & 255, rand() & 255, 230); +} +voronoi("##v", seeds, 30, colors, ImVec2(-1, 300)); +```