Editor/UI/MaterialAssets.SpecularGlossiness.cs
using SkiaSharp;
namespace HumanoidRigger.Editor;
using Vector3=System.Numerics.Vector3;
using Vector4=System.Numerics.Vector4;

internal static partial class MaterialAssets
{
    static SourceMaterial ConvertSpecularGlossiness(SourceMaterial material,string directory,int index)
    {
        if(material.SpecularGlossiness is not {} source)return material;
        var result=material with{SpecularGlossiness=null,AuthoredPbr=true};
        var constant=source.Evaluate(Vector4.One,Vector4.One);
        result.ColorFactor=constant.Color;result.OpacityFactor=constant.Opacity;result.MetallicFactor=constant.Metallic;result.RoughnessFactor=constant.Roughness;
        if(source.DiffuseTexture is null&&source.SpecularGlossinessTexture is null)return result;
        SKBitmap Decode(string path)=>path is null?null:SKBitmap.Decode(Path.Combine(directory,path))??throw new FormatException($"Cannot decode texture '{path}'.");
        using var diffuse=Decode(source.DiffuseTexture);using var specular=Decode(source.SpecularGlossinessTexture);
        int width=Math.Max(diffuse?.Width??1,specular?.Width??1),height=Math.Max(diffuse?.Height??1,specular?.Height??1);
        using var color=new SKBitmap(width,height,SKColorType.Rgba8888,SKAlphaType.Unpremul);
        using var packed=new SKBitmap(width,height,SKColorType.Rgba8888,SKAlphaType.Opaque);
        static float Linear(byte channel){float v=channel/255f;return v<=.04045f?v/12.92f:MathF.Pow((v+.055f)/1.055f,2.4f);}
        static byte Byte(float v)=>(byte)Math.Clamp((int)MathF.Round(v*255),0,255);
        static byte Srgb(float v)=>Byte(v<=.0031308f?v*12.92f:1.055f*MathF.Pow(v,1/2.4f)-.055f);
        Vector4 Sample(SKBitmap bitmap,int x,int y)
        {
            if(bitmap is null)return Vector4.One;
            var c=bitmap.GetPixel(Math.Min(bitmap.Width-1,x*bitmap.Width/width),Math.Min(bitmap.Height-1,y*bitmap.Height/height));
            return new(Linear(c.Red),Linear(c.Green),Linear(c.Blue),c.Alpha/255f);
        }
        for(int y=0;y<height;y++)for(int x=0;x<width;x++)
        {
            var pixel=source.Evaluate(Sample(diffuse,x,y),Sample(specular,x,y));
            color.SetPixel(x,y,new(Srgb(pixel.Color.X),Srgb(pixel.Color.Y),Srgb(pixel.Color.Z),Byte(pixel.Opacity)));
            packed.SetPixel(x,y,new(255,Byte(pixel.Roughness),Byte(pixel.Metallic)));
        }
        string Save(SKBitmap bitmap,string suffix)
        {
            string relative=$"textures/pbr_{index}_sg_{suffix}.png",path=Path.Combine(directory,relative);
            Directory.CreateDirectory(Path.GetDirectoryName(path));
            using var image=SKImage.FromBitmap(bitmap);using var data=image.Encode(SKEncodedImageFormat.Png,100);using var stream=File.Create(path);data.SaveTo(stream);
            return relative;
        }
        result.ColorTexture=Save(color,"base");result.MetallicRoughnessTexture=Save(packed,"metal_rough");
        result.ColorFactor=Vector3.One;result.OpacityFactor=1;result.MetallicFactor=1;result.RoughnessFactor=1;
        return result;
    }
}