// FIXME: Unable to cast object of type 'ReplacePalette' to type 'Godot.CollisionObject3D'. public partial class ReplacePalette : Node3D { readonly struct TextureInfo(float min, float max) { public float MinBrightness { get; } = min; public float MaxBrightness { get; } = max; } static Shader _paletteShader; static Dictionary _textureCache = []; [Export] public Texture2D Palette { get; set; } public override void _Ready() { _paletteShader ??= Load("res://assets/shaders/palette_swap.gdshader"); ReplaceShaderRecursive(this, Palette); } static void ReplaceShaderRecursive(Node node, Texture2D palette) { if (node is MeshInstance3D mesh) { var oldMaterial = (StandardMaterial3D)mesh.Mesh.SurfaceGetMaterial(0); var texture = oldMaterial.AlbedoTexture; var info = GetOrCreateTextureInfo(texture); var material = new ShaderMaterial { Shader = _paletteShader }; material.SetShaderParameter("palette", palette); material.SetShaderParameter("albedo_texture", texture); material.SetShaderParameter("min_brightness", info.MinBrightness); material.SetShaderParameter("max_brightness", info.MaxBrightness); mesh.MaterialOverride = material; } foreach (var child in node.GetChildren()) ReplaceShaderRecursive(child, palette); } static TextureInfo GetOrCreateTextureInfo(Texture2D texture) { if (!_textureCache.TryGetValue(texture, out var info)) { var minBrightness = 1.0f; var maxBrightness = 0.0f; var image = texture.GetImage(); image.Convert(Image.Format.Rgba8); var data = image.GetData().AsSpan(); for (var i = 0; i < data.Length; i += 4) { var rgba = data.Slice(i, 4); if (rgba[3] <= byte.MaxValue / 2) continue; var brightness = Max(rgba[0], Max(rgba[1], rgba[2])) / (float)byte.MaxValue; minBrightness = Min(minBrightness, brightness); maxBrightness = Max(maxBrightness, brightness); } // for (var x = 0; x < image.GetWidth(); x++) // for (var y = 0; y < image.GetHeight(); y++) { // var color = image.GetPixel(x, y); // // var brightness = Max(color.R, Max(color.G, color.B)); // color.ToHsv(out _, out _, out var brightness); // minBrightness = Min(minBrightness, brightness); // maxBrightness = Max(maxBrightness, brightness); // } info = new(minBrightness, maxBrightness); _textureCache.Add(texture, info); } return info; } }