feat(viz): voronoi diagram via raster brute-force (MVP)
Para cada tile 4x4 px del rect de render: encontrar seed mas cercano (distancia Euclidea) y rellenar con su color. Suficiente para N<=200 seeds en region <=600x400. voronoi_layout deja polygon vacio en MVP — solo rellena seed/color. Para extraer poligonos analiticos seria necesario half-plane intersections (Fortune) — diferido a otro issue.
This commit is contained in:
@@ -0,0 +1,83 @@
|
||||
#include "viz/voronoi.h"
|
||||
|
||||
#include <cmath>
|
||||
|
||||
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<VoronoiCell> voronoi_layout(const ImVec2* seeds, int n, ImVec2 /*region*/) {
|
||||
std::vector<VoronoiCell> 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();
|
||||
}
|
||||
@@ -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 <vector>
|
||||
|
||||
struct VoronoiCell {
|
||||
std::vector<ImVec2> 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<VoronoiCell> 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));
|
||||
@@ -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));
|
||||
```
|
||||
Reference in New Issue
Block a user