#include "viz/contour.h" #include namespace { // Devuelve la coordenada interpolada [0..1] entre v1 y v2 donde se cruza level. float interp(float v1, float v2, float level) { float d = v2 - v1; if (std::fabs(d) < 1e-9f) return 0.5f; float t = (level - v1) / d; if (t < 0.0f) t = 0.0f; if (t > 1.0f) t = 1.0f; return t; } inline float at(const float* g, int nx, int x, int y) { return g[y * nx + x]; } } // namespace std::vector contour_compute(const float* grid, int nx, int ny, const float* levels, int n_levels) { std::vector out; if (!grid || nx < 2 || ny < 2 || !levels || n_levels <= 0) return out; out.resize(n_levels); for (int li = 0; li < n_levels; li++) out[li].level = levels[li]; for (int li = 0; li < n_levels; li++) { float L = levels[li]; auto& line = out[li]; for (int y = 0; y < ny - 1; y++) { for (int x = 0; x < nx - 1; x++) { float v00 = at(grid, nx, x, y); float v10 = at(grid, nx, x + 1, y); float v11 = at(grid, nx, x + 1, y + 1); float v01 = at(grid, nx, x, y + 1); int code = 0; if (v00 >= L) code |= 1; if (v10 >= L) code |= 2; if (v11 >= L) code |= 4; if (v01 >= L) code |= 8; if (code == 0 || code == 15) continue; // Puntos en cada arista (top=between v00 y v10, etc.) ImVec2 pT(x + interp(v00, v10, L), (float)y); ImVec2 pR((float)(x + 1), y + interp(v10, v11, L)); ImVec2 pB(x + interp(v01, v11, L), (float)(y + 1)); ImVec2 pL((float)x, y + interp(v00, v01, L)); auto seg = [&](ImVec2 a, ImVec2 b) { line.pts.push_back(a); line.pts.push_back(b); }; switch (code) { case 1: case 14: seg(pL, pT); break; case 2: case 13: seg(pT, pR); break; case 4: case 11: seg(pR, pB); break; case 8: case 7: seg(pB, pL); break; case 3: case 12: seg(pL, pR); break; case 6: case 9: seg(pT, pB); break; // ambiguos: 5 y 10 — partir en dos segmentos. case 5: seg(pL, pT); seg(pR, pB); break; case 10: seg(pT, pR); seg(pB, pL); break; default: break; } } } } return out; } void contour(const char* id, const float* grid, int nx, int ny, const float* levels, int n_levels, 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 (!grid || nx < 2 || ny < 2 || n_levels <= 0) { ImGui::PopID(); return; } auto lines = contour_compute(grid, nx, ny, levels, n_levels); ImDrawList* dl = ImGui::GetWindowDrawList(); // Borde dl->AddRect(origin, ImVec2(origin.x + W, origin.y + H), IM_COL32(80, 80, 90, 200), 0.0f, 0, 1.0f); float sx = W / (float)(nx - 1); float sy = H / (float)(ny - 1); // Color por nivel: gradiente azul -> amarillo auto level_color = [&](int idx) { float t = (n_levels > 1) ? (float)idx / (float)(n_levels - 1) : 0.5f; // (76,140,230) -> (250,200,90) int r = (int)(76 + (250 - 76) * t); int g = (int)(140 + (200 - 140) * t); int b = (int)(230 + (90 - 230) * t); return IM_COL32(r, g, b, 230); }; for (int li = 0; li < (int)lines.size(); li++) { ImU32 col = level_color(li); const auto& seg = lines[li].pts; for (size_t k = 0; k + 1 < seg.size(); k += 2) { ImVec2 a(origin.x + seg[k].x * sx, origin.y + seg[k].y * sy); ImVec2 b(origin.x + seg[k + 1].x * sx, origin.y + seg[k + 1].y * sy); dl->AddLine(a, b, col, 1.5f); } } ImGui::PopID(); }