using System; using System.Collections.Generic; using Godot; using IslaApocalypse.Core; namespace IslaApocalypse.Tools { /// /// THE DRAINAGE MAPS (chat2/12) — presentation only, for eyeballing that the flow is sane: /// /// • the LOG-SCALED ACCUMULATION map — drainage spans orders of magnitude, so log(1+acc) over land; /// the dendritic uplands and the trunks read as bright channels on dark hillslopes; the ocean is a /// flat dark blue and enclosed (non-ocean) water a dark teal, so the ocean identity is visible too; /// • the PROMOTED-CANDIDATES overlay — a faint grey terrain, the sea-reaching trunks in cyan (outlet /// square, mountain-exit white ring, lean tributaries thin), the endorheic giants in orange (pooling /// terminal disc, lean tributaries thin), the lean endorheic terminals as red rings. Provisional /// routes are NOT drawn (routing is a later task). Nothing here touches data. /// public static class DrainageRenderer { private static readonly Color Ocean = new(0.055f, 0.110f, 0.235f); private static readonly Color Enclosed = new(0.060f, 0.220f, 0.230f); private static readonly Color Trunk = new(0.250f, 0.900f, 1.000f); private static readonly Color Giant = new(1.000f, 0.600f, 0.150f); private static readonly Color Endo = new(1.000f, 0.250f, 0.250f); private static readonly Color Exit = new(1.000f, 1.000f, 1.000f); private static readonly Color Ink = new(0.941f, 0.949f, 0.961f); /// log(1 + acc) / log(1 + max) over land; ocean / enclosed water flat. public static Image Accumulation(int[] acc, bool[] isOcean, float[,] render, int n, float sea) { long max = 1; for (int i = 0; i < acc.Length; i++) if (acc[i] > max) max = acc[i]; double lmax = Math.Log(1.0 + max); var img = Image.CreateEmpty(n, n, false, Image.Format.Rgb8); for (int x = 0; x < n; x++) for (int y = 0; y < n; y++) { int i = x * n + y; if (isOcean[i]) { img.SetPixel(x, y, Ocean); continue; } if (render[x, y] < sea) { img.SetPixel(x, y, Enclosed); continue; } float v = (float)(Math.Log(1.0 + acc[i]) / lmax); // a dark-to-bright ramp with a cool tint in the channels float r = 0.06f + 0.94f * v * v, g = 0.08f + 0.92f * v, b = 0.12f + 0.88f * MathF.Sqrt(v); img.SetPixel(x, y, new Color(MathF.Min(1f, r), MathF.Min(1f, g), MathF.Min(1f, b))); } return img; } /// The candidates over a faint terrain. public static Image Candidates(DrainageAnalysis.Plan plan, bool[] isOcean, float[,] render, int n, float sea, float hMax, string title) { var img = Image.CreateEmpty(n, n, false, Image.Format.Rgb8); float span = MathF.Max(1e-6f, hMax - sea); for (int x = 0; x < n; x++) for (int y = 0; y < n; y++) { int i = x * n + y; if (isOcean[i]) { img.SetPixel(x, y, Ocean); continue; } if (render[x, y] < sea) { img.SetPixel(x, y, Enclosed); continue; } float t = MathF.Min(1f, (render[x, y] - sea) / span); float g = 0.30f + 0.45f * MathF.Sqrt(t); img.SetPixel(x, y, new Color(g, g, g * 0.96f)); } int thick = n >= 4096 ? 5 : 3, thin = n >= 4096 ? 3 : 2, mark = n >= 4096 ? 18 : 10; foreach (var g in plan.Giants) { foreach (var tr in g.Tributaries) Polyline(img, tr.Course, n, Giant, thin); Polyline(img, g.Course, n, Giant, thick); Disc(img, (int)g.Terminal.x, (int)g.Terminal.y, mark, n, Giant); Ring(img, (int)g.Terminal.x, (int)g.Terminal.y, mark + 8, n, Ink, 3); if (g.ExitFound) Ring(img, (int)g.MountainExit.x, (int)g.MountainExit.y, mark, n, Exit, 4); } foreach (var t in plan.Trunks) { foreach (var tr in t.Tributaries) Polyline(img, tr.Course, n, Trunk, thin); Polyline(img, t.Course, n, Trunk, thick); Square(img, (int)t.Outlet.x, (int)t.Outlet.y, mark, n, Trunk); if (t.ExitFound) Ring(img, (int)t.MountainExit.x, (int)t.MountainExit.y, mark, n, Exit, 4); } foreach (var e in plan.Endorheics) Ring(img, (int)e.Terminal.x, (int)e.Terminal.y, mark + 4, n, Endo, 4); int s = n >= 4096 ? 4 : 3; int lh = TinyFont.Height(s) + 6; TinyFont.Draw(img, title, 12, 12, s, Ink); TinyFont.Draw(img, $"CYAN: SEA-REACHING TRUNKS ({plan.Trunks.Count}) - SQUARE = OUTLET WHITE RING = MOUNTAIN EXIT", 12, 12 + lh, s, Ink); TinyFont.Draw(img, $"ORANGE: ENDORHEIC GIANTS ({plan.Giants.Count}) - DISC = POOLING TERMINAL (EXPECTED, NOT AN ERROR)", 12, 12 + lh * 2, s, Ink); TinyFont.Draw(img, $"RED RING: LEAN ENDORHEIC TERMINALS ({plan.Endorheics.Count}) THIN LINES: LEAN TRIBUTARIES NOTHING CARVED - ANALYSIS ONLY", 12, 12 + lh * 3, s, Ink); return img; } private static void Polyline(Image img, List<(float x, float y)> pts, int n, Color c, int thick) { for (int i = 1; i < pts.Count; i++) Line(img, (int)pts[i - 1].x, (int)pts[i - 1].y, (int)pts[i].x, (int)pts[i].y, n, c, thick); } private static void Line(Image img, int x0, int y0, int x1, int y1, int n, Color c, int thick) { int dx = Math.Abs(x1 - x0), sx = x0 < x1 ? 1 : -1; int dy = -Math.Abs(y1 - y0), sy = y0 < y1 ? 1 : -1; int err = dx + dy; int r = thick / 2; int guard = 0; while (true) { for (int ox = -r; ox <= r; ox++) for (int oy = -r; oy <= r; oy++) { int px = x0 + ox, py = y0 + oy; if (px >= 0 && py >= 0 && px < n && py < n) img.SetPixel(px, py, c); } if (x0 == x1 && y0 == y1) break; if (++guard > 4 * n) break; int e2 = 2 * err; if (e2 >= dy) { err += dy; x0 += sx; } if (e2 <= dx) { err += dx; y0 += sy; } } } private static void Disc(Image img, int cx, int cy, int r, int n, Color c) { for (int ox = -r; ox <= r; ox++) for (int oy = -r; oy <= r; oy++) { if (ox * ox + oy * oy > r * r) continue; int px = cx + ox, py = cy + oy; if (px >= 0 && py >= 0 && px < n && py < n) img.SetPixel(px, py, c); } } private static void Ring(Image img, int cx, int cy, int r, int n, Color c, int w) { for (int ox = -r; ox <= r; ox++) for (int oy = -r; oy <= r; oy++) { int d2 = ox * ox + oy * oy; if (d2 > r * r || d2 < (r - w) * (r - w)) continue; int px = cx + ox, py = cy + oy; if (px >= 0 && py >= 0 && px < n && py < n) img.SetPixel(px, py, c); } } private static void Square(Image img, int cx, int cy, int r, int n, Color c) { for (int ox = -r; ox <= r; ox++) for (int oy = -r; oy <= r; oy++) { int px = cx + ox, py = cy + oy; if (px >= 0 && py >= 0 && px < n && py < n) img.SetPixel(px, py, c); } } } }