Add-Type -AssemblyName System.Drawing
$ErrorActionPreference = 'Stop'

# Generate a 64^3 periodic Perlin-Worley volume packed as an 8x8 slice atlas.
# C# keeps the offline Worley search fast; the shader only pays filtered
# texture reads at runtime.
$source = @'
using System;
using System.Drawing;
using System.Drawing.Imaging;

public static class ModernPsxCloudNoise {
    const int Size = 64;

    static uint Hash(int x, int y, int z, uint seed) {
        uint h = (uint)x * 73856093u ^ (uint)y * 19349663u ^
                 (uint)z * 83492791u ^ seed;
        h ^= h >> 13;
        h *= 1274126177u;
        h ^= h >> 16;
        return h;
    }

    static float Random01(int x, int y, int z, uint seed) {
        return Hash(x, y, z, seed) / 4294967295.0f;
    }

    static int Wrap(int value, int period) {
        int wrapped = value % period;
        return wrapped < 0 ? wrapped + period : wrapped;
    }

    static float Smooth(float value) {
        return value * value * value *
               (value * (value * 6.0f - 15.0f) + 10.0f);
    }

    static float Lerp(float a, float b, float amount) {
        return a + (b - a) * amount;
    }

    static float ValueNoise(float x, float y, float z, int frequency,
                            uint seed) {
        float px = x * frequency;
        float py = y * frequency;
        float pz = z * frequency;
        int x0 = (int)Math.Floor(px);
        int y0 = (int)Math.Floor(py);
        int z0 = (int)Math.Floor(pz);
        float fx = Smooth(px - x0);
        float fy = Smooth(py - y0);
        float fz = Smooth(pz - z0);
        int x1 = Wrap(x0 + 1, frequency);
        int y1 = Wrap(y0 + 1, frequency);
        int z1 = Wrap(z0 + 1, frequency);
        x0 = Wrap(x0, frequency);
        y0 = Wrap(y0, frequency);
        z0 = Wrap(z0, frequency);

        float n000 = Random01(x0, y0, z0, seed);
        float n100 = Random01(x1, y0, z0, seed);
        float n010 = Random01(x0, y1, z0, seed);
        float n110 = Random01(x1, y1, z0, seed);
        float n001 = Random01(x0, y0, z1, seed);
        float n101 = Random01(x1, y0, z1, seed);
        float n011 = Random01(x0, y1, z1, seed);
        float n111 = Random01(x1, y1, z1, seed);
        float xy0 = Lerp(Lerp(n000, n100, fx), Lerp(n010, n110, fx), fy);
        float xy1 = Lerp(Lerp(n001, n101, fx), Lerp(n011, n111, fx), fy);
        return Lerp(xy0, xy1, fz);
    }

    static float Cellular(float x, float y, float z, int frequency,
                          uint seed) {
        float px = x * frequency;
        float py = y * frequency;
        float pz = z * frequency;
        int bx = (int)Math.Floor(px);
        int by = (int)Math.Floor(py);
        int bz = (int)Math.Floor(pz);
        float best = 4.0f;
        for (int oz = -1; oz <= 1; ++oz)
        for (int oy = -1; oy <= 1; ++oy)
        for (int ox = -1; ox <= 1; ++ox) {
            int cx = bx + ox;
            int cy = by + oy;
            int cz = bz + oz;
            int hx = Wrap(cx, frequency);
            int hy = Wrap(cy, frequency);
            int hz = Wrap(cz, frequency);
            float fx = cx + Random01(hx, hy, hz, seed + 0u);
            float fy = cy + Random01(hx, hy, hz, seed + 1u);
            float fz = cz + Random01(hx, hy, hz, seed + 2u);
            float dx = px - fx;
            float dy = py - fy;
            float dz = pz - fz;
            best = Math.Min(best, dx * dx + dy * dy + dz * dz);
        }
        // White in the centres of rounded cells, dark at their boundaries.
        return Math.Max(0.0f, 1.0f - Math.Min(1.0f,
            (float)Math.Sqrt(best) / 1.05f));
    }

    static int Byte(float value) {
        return (int)Math.Round(Math.Max(0.0f, Math.Min(1.0f, value)) * 255.0f);
    }

    public static void Generate(string path) {
        using (var bitmap = new Bitmap(Size * 8, Size * 8,
                                       PixelFormat.Format32bppArgb)) {
            for (int z = 0; z < Size; ++z) {
                int tileX = z & 7;
                int tileY = z >> 3;
                for (int y = 0; y < Size; ++y)
                for (int x = 0; x < Size; ++x) {
                    float nx = x / (float)Size;
                    float ny = y / (float)Size;
                    float nz = z / (float)Size;
                    float baseFbm =
                        0.55f * ValueNoise(nx, ny, nz, 2, 17041u) +
                        0.30f * ValueNoise(nx, ny, nz, 4, 91127u) +
                        0.15f * ValueNoise(nx, ny, nz, 8, 43103u);
                    float broadCells = Cellular(nx, ny, nz, 4, 29173u);
                    float fineCells = Cellular(nx, ny, nz, 8, 77351u);
                    // Red is the coherent Perlin-Worley base; green and blue
                    // are independent erosion scales.
                    float perlinWorley = baseFbm * 0.68f + broadCells * 0.32f;
                    Color color = Color.FromArgb(
                        255,
                        Byte(perlinWorley),
                        Byte(broadCells),
                        Byte(fineCells)
                    );
                    bitmap.SetPixel(tileX * Size + x, tileY * Size + y, color);
                }
            }
            bitmap.Save(path, ImageFormat.Png);
        }
    }
}
'@

$drawingAssembly = [System.Drawing.Bitmap].Assembly.Location
$drawingPrimitivesAssembly = [System.Drawing.Color].Assembly.Location
$gdiAssembly = Join-Path $PSHOME 'System.Private.Windows.GdiPlus.dll'
$windowsCoreAssembly = Join-Path $PSHOME 'System.Private.Windows.Core.dll'
Add-Type -TypeDefinition $source -Language CSharp `
    -ReferencedAssemblies @(
        $drawingAssembly,
        $drawingPrimitivesAssembly,
        $gdiAssembly,
        $windowsCoreAssembly
    )
$outputPath = Join-Path $PSScriptRoot '..\shaders\textures\cloud_noise.png'
[ModernPsxCloudNoise]::Generate($outputPath)
Write-Output "Generated $outputPath"
