using Godot; namespace IslaApocalypse.Tools { /// /// A plain grayscale render: normalize a float field to its own [min, max] and map to /// [black, white]. No colour, no hillshade, no interpretation. /// /// ═══ WHY THIS EXISTS ═══ /// /// Because you cannot judge noise through a palette. A hypsometric ramp and a hillshade are both /// opinionated transforms — one bends the value distribution onto chosen stops, the other adds /// shape the data does not contain. Both are the right thing for a map and the wrong thing for /// asking "is the noise itself good?". This mode answers that question and no other. /// /// ⚠ THIS IS THE ONE PLACE PER-IMAGE NORMALIZATION IS CORRECT. Everywhere else the palette /// anchors are fixed so images can be compared. Here the point is to see the full structure of /// ONE field at full contrast, so it is normalized to its own range — and the range used is /// printed and written into the INDEX, so a shade reads back to a height. /// public static class GrayscaleRenderer { /// Render to grayscale. Returns the (min, max) used for the normalization. public static (float min, float max) SavePng(float[,] field, int mapSize, string absolutePath) { float min = float.MaxValue, max = float.MinValue; for (int x = 0; x < mapSize; x++) for (int y = 0; y < mapSize; y++) { float v = field[x, y]; if (v < min) min = v; if (v > max) max = v; } float span = max - min; float inv = span > 1e-9f ? 1f / span : 0f; var img = Image.CreateEmpty(mapSize, mapSize, false, Image.Format.Rgb8); for (int x = 0; x < mapSize; x++) for (int y = 0; y < mapSize; y++) { float g = (field[x, y] - min) * inv; img.SetPixel(x, y, new Color(g, g, g)); } Error err = img.SavePng(absolutePath); if (err != Error.Ok) GD.PrintErr($"[GrayscaleRenderer] SavePng failed ({err}) for {absolutePath}"); return (min, max); } } }