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);
}
}
}