
#include <metal_stdlib>
using namespace metal;

// Uniforms passed to the shader
struct CloudySky_Uniforms {
    float2 resolution;
    float  time;

    float  cloudscale;
    float  speed;
    float  clouddark;
    float  cloudlight;
    float  cloudcover;
    float  cloudalpha;
    float  skytint;
    float3 skycolour1;
    float3 skycolour2;
};

// Vertex output struct; fragCoord should carry the pixel coordinates
struct CloudySky_VertexOut {
    float4 position [[position]];
    float2 uv;
};

// 2x2 rotation/scaling matrix
float2x2 cloudy_sky_rot_scale() {
    return float2x2( 1.6,  1.2, -1.2,  1.6 );
}

// Hash function
float2 cloudy_sky_hash2(float2 p) {
    float2 dp = float2(dot(p, float2(127.1,311.7)), dot(p, float2(269.5,183.3)));
    return -1.0 + 2.0 * fract(sin(dp) * 43758.5453123);
}

// 2D simplex noise
float cloudy_sky_noise2d(float2 p) {
    const float K1 = 0.366025404; // (sqrt(3)-1)/2
    const float K2 = 0.211324865; // (3-sqrt(3))/6
    float2 i = floor(p + (p.x + p.y) * K1);
    float2 a = p - i + (i.x + i.y) * K2;
    float2 o = (a.x > a.y) ? float2(1.0, 0.0) : float2(0.0, 1.0);
    float2 b = a - o + K2;
    float2 c = a - 1.0 + 2.0 * K2;
    float3 h = max(float3(0.5 - dot(a,a), 0.5 - dot(b,b), 0.5 - dot(c,c)), 0.0);
    float3 n = h * h * h * h * float3(dot(a, cloudy_sky_hash2(i)), dot(b, cloudy_sky_hash2(i + o)), dot(c, cloudy_sky_hash2(i + 1.0)));
    return dot(n, float3(70.0));
}

// Fractal Brownian motion
float cloudy_sky_fbm(float2 p, constant CloudySky_Uniforms &u) {
    float total = 0.0;
    float amplitude = 0.1;
    float2x2 m = cloudy_sky_rot_scale();
    for (int i = 0; i < 7; i++) {
        total += cloudy_sky_noise2d(p) * amplitude;
        p = m * p;
        amplitude *= 0.4;
    }
    return total;
}

// Main fragment function
fragment float4 clouds_fragment(CloudySky_VertexOut in [[stage_in]],
                                constant CloudySky_Uniforms &u [[buffer(0)]]) {
    
    float2 p = in.uv;

    // aspect‐corrected uv:
    float  aspect = u.resolution.x / u.resolution.y;
    float2 uv     = float2(p.x * aspect, p.y);
    
    float2x2 m = cloudy_sky_rot_scale();
    float  t = u.time * u.speed;

    // base fbm
    float q = cloudy_sky_fbm(uv * u.cloudscale * 0.5, u);

    // ridged noise
    float r = 0.0;
    uv *= u.cloudscale;
    uv -= (q - t);
    float weight = 0.8;
    for (int i = 0; i < 8; i++) {
        r += abs(weight * cloudy_sky_noise2d(uv));
        uv = m * uv + t;
        weight *= 0.7;
    }

    // noise shape f
    float f = 0.0;
    uv = p * float2(u.resolution.x/u.resolution.y, 1.0);
    uv *= u.cloudscale;
    uv -= (q - t);
    weight = 0.7;
    for (int i = 0; i < 8; i++) {
        f += weight * cloudy_sky_noise2d(uv);
        uv = m * uv + t;
        weight *= 0.6;
    }
    f *= (r + f);

    // colour noise c
    float c = 0.0;
    t = u.time * u.speed * 2.0;
    uv = p * float2(u.resolution.x/u.resolution.y, 1.0);
    uv *= u.cloudscale * 2.0;
    uv -= (q - t);
    weight = 0.4;
    for (int i = 0; i < 7; i++) {
        c += weight * cloudy_sky_noise2d(uv);
        uv = m * uv + t;
        weight *= 0.6;
    }

    // colour ridge c1
    float c1 = 0.0;
    t = u.time * u.speed * 3.0;
    uv = p * float2(u.resolution.x/u.resolution.y, 1.0);
    uv *= u.cloudscale * 3.0;
    uv -= (q - t);
    weight = 0.4;
    for (int i = 0; i < 7; i++) {
        c1 += abs(weight * cloudy_sky_noise2d(uv));
        uv = m * uv + t;
        weight *= 0.6;
    }
    c += c1;

    // mix sky and cloud colours
    float3 skyCol   = mix(u.skycolour2, u.skycolour1, p.y);
    float3 cloudCol = float3(1.1, 1.1, 0.9) * clamp(u.clouddark + u.cloudlight * c, 0.0, 1.0);
    f = u.cloudcover + u.cloudalpha * f * r;
    float3 result = mix(skyCol,
                        clamp(u.skytint * skyCol + cloudCol, 0.0, 1.0),
                        clamp(f + c, 0.0, 1.0));

    return float4(result, 1.0);
}

