

uniform float wetness;

uniform sampler2D colortex6; // data0
uniform sampler2D colortex4; // data0

vec3 rayTraceDiffuse(vec3 dir, vec3 position, float dither)
{
    const int quality = 6;
    float stepSizeAdjust = clamp(dither, 0.25, 1.0);

    vec3 clipPosition = toClipSpace3(position);
    vec3 direction = normalize(toClipSpace3(position + dir) - clipPosition);
    vec3 maxLengths = ((step(0.0, direction) - clipPosition) / direction) * dither;
    float minLength = min(min(maxLengths.x, maxLengths.y), maxLengths.z);
    vec3 stepv = (direction * (minLength * stepSizeAdjust)) / float(32 * dither);

    float tolerance = max(abs(stepv.z) * 10.0, 0.02 / (position.z * position.z));

    for (int i = 0; i <= quality; ++i)
    {
        vec3 spos = clipPosition + stepv * (float(i) + blueNoise4(gl_FragCoord.xy).rgr);
        float depthSample = texelFetch(depthtex0, ivec2(spos.xy / texelSize), 0).r;

        if (depthSample < spos.z)
        {
            if (abs(spos.z - depthSample) < tolerance)
            {
                return vec3(spos.xy, depthSample);
            }
        }
    }


    return vec3(1.1); 
}

// Adapted from "Sampling the GGX Distribution of Visible Normals",
// http://jcgt.org/published/0007/04/01/
vec3 SampleGGXVNDF(vec3 Ve, float alpha_x, float alpha_y, float U1, float U2)
{
    // Transforming the view direction to the hemisphere configuration
    vec3 Vh = normalize(vec3(alpha_x * Ve.x, alpha_y * Ve.y, Ve.z));

    // Orthonormal basis
    vec3 T1 = (Vh.z < 1.0f) ? normalize(cross(vec3(0, 0, 1), Vh)) : vec3(1, 0, 0);
    vec3 T2 = cross(Vh, T1);

    // Parameterization of the projected area
    float r = sqrt(U1);
    float phi = 2.0f * PI * U2;
    float t1 = r * cos(phi);
    float t2 = r * sin(phi);
    float s = 0.5f * (1.0f + Vh.z);
    t2 = (1.0f - s) * sqrt(1.0f - t1 * t1) + s * t2;

    // Reprojection onto hemisphere
    vec3 Nh = t1 * T1 + t2 * T2 + sqrt(max(0.0f, 1.0f - t1 * t1 - t2 * t2)) * Vh;

    // Transform the normal back to the ellipsoid configuration
    vec3 Ne = normalize(vec3(alpha_x * Nh.x, alpha_y * Nh.y, max(0.0f, Nh.z)));

    return Ne;
}

float vdc(uint bits)
{
    bits = (bits << 16u) | (bits >> 16u);
    bits = ((bits & 0x55555555u) << 1u) | ((bits & 0xAAAAAAAAu) >> 1u);
    bits = ((bits & 0x33333333u) << 2u) | ((bits & 0xCCCCCCCCu) >> 2u);
    bits = ((bits & 0x0F0F0F0Fu) << 4u) | ((bits & 0xF0F0F0F0u) >> 4u);
    bits = ((bits & 0x00FF00FFu) << 8u) | ((bits & 0xFF00FF00u) >> 8u);
    return float(bits) * 2.3283064365386963e-10;
}

// Hammersley point set
vec2 hs(uint i, uint N)
{
    return vec2(float(i) / float(N), vdc(i));
}

vec2 StratifiedSample(int i, int N)
{
    int sqrtN = int(sqrt(float(N)));
    int x = i % sqrtN;
    int y = i / sqrtN;
    return (vec2(x, y) + vec2(fract(sin(float(i)) * 43758.5453123), fract(cos(float(i)) * 43758.5453123))) /
           float(sqrtN);
}
vec3 importanceSampleGGX(vec2 Xi, vec3 N, float roughness, float rot)
{
    float a = roughness * roughness;

    float phi = 2.0 * PI * fract(Xi.x + rot);
    float cosTheta = sqrt((1.0 - Xi.y) / (1.0 + (a * a - 1.0) * Xi.y));
    float sinTheta = sqrt(1.0 - cosTheta * cosTheta);

    // From spherical coordinates to cartesian coordinates
    vec3 H;
    H.x = cos(phi) * sinTheta;
    H.y = sin(phi) * sinTheta;
    H.z = cosTheta;

    // From tangent-space vector to world-space sample vector
    vec3 up = abs(N.z) < 0.5 ? vec3(0.0, 0.0, 1.0) : vec3(1.0, 0.0, 0.0);
    vec3 tangent = normalize(cross(up, N));
    vec3 bitangent = cross(N, tangent);

    vec3 sampleVec = tangent * H.x + bitangent * H.y + N * H.z;

    return normalize(sampleVec);
}

void frisvad(in vec3 n, out vec3 f, out vec3 r)
{
    if (n.z < -0.999999)
    {
        f = vec3(0., -1, 0);
        r = vec3(-1, 0, 0);
    }
    else
    {
        float a = 1. / (1. + n.z);
        float b = -n.x * n.y * a;
        f = vec3(1. - n.x * n.x * a, b, -n.x);
        r = vec3(b, 1. - n.y * n.y * a, -n.y);
    }
}
mat3 CoordBase(vec3 n)
{
    vec3 x, y;
    frisvad(n, x, y);
    return mat3(x, y, n);
}
vec2 R2_samples(int n)
{
    vec2 alpha = vec2(0.75487765, 0.56984026);
    return fract(alpha * n);
}
float square(float x)
{
    return x * x;
}

// GGX distribution function G1
float g(float NdotL, float roughness)
{
    float k = (roughness * roughness) / 2.0;
    return NdotL / (NdotL * (1.0 - k) + k);
}
// const bool colortex4MipmapEnabled = true;

vec3 sampleHemisphere(float u1, float u2)
{
    float r = sqrt(u1);
    float theta = 2.0 * PI * u2;
    float x = r * cos(theta);
    float y = r * sin(theta);
    float z = sqrt(1.0 - u1);
    return vec3(x, y, z);
}
vec3 diffuseFunc(float skyLight, vec3 normal, vec3 fragpos, vec3 light)
{
    vec3 p3 = mat3(gbufferModelViewInverse) * fragpos + gbufferModelViewInverse[3].xyz;

    vec3 tempDiffuse = vec3(0.0);

    mat3 basis = CoordBase(normal);

    // Number of samples for diffuse lighting
    int nDiffuseSamples = RTAO_SAMPLES;
    float occlusion = 0.0;
    for (int i = 0; i < nDiffuseSamples; i++)
    {
        int seed = frameCounter * int(nDiffuseSamples) + i;
        vec2 ij = fract(R2_samples(seed) + blueNoise4(gl_FragCoord.xy).rg);
        vec3 H = sampleHemisphere(ij.x, ij.y);
        vec3 L = basis * H;

        float NdotL = max(dot(normal, L), 0.0);
        if (NdotL > 0.0)
        {

            vec3 rtPos =
                rayTraceDiffuse(mat3(gbufferModelView) * L, fragpos.xyz, noise_standard(gl_FragCoord.xy));

            vec3 previousPosition =
                reproject(mat3(gbufferModelViewInverse) * toScreenSpace(rtPos) + gbufferModelViewInverse[3].xyz, false);
            if (all(greaterThanEqual(previousPosition.xy, vec2(0.0))) && rtPos.z < 1.0)
            {
                tempDiffuse += texture(colortex4, previousPosition.xy).rgb;
            }
            else
            {
                occlusion += 1.0;
                float bounceAmount = float(L.y > 0.0) + clamp(-L.y * 0.1 + 0.1, 0.0, 1.0);
                tempDiffuse.rgb += skyFromTex2(L, colortex6).rgb * 0.25 * bounceAmount * skyLight;
            }

            // tempDiffuse.rgb *= clamp((rayContrib) * 100.0, 0.0, 1.0);
        }
    }

    return tempDiffuse.rgb / nDiffuseSamples;
    // return (tempDiffuse.rgb / nDiffuseSamples) * (occlusion / nDiffuseSamples);
}