

uniform float wetness;

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

vec3 rayTrace(vec3 dir, vec3 position, float dither, float rayQuality)
{
    float quality = 32;

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

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

    for (int i = 0; i <= int(rayQuality); ++i)
    {

        vec3 spos = clipPosition + stepv * (float(i) + dither);
        float depthSample = texelFetch2D(depthtex0, ivec2(spos.xy / texelSize), 0).r;
        if (depthSample < spos.z && abs(spos.z - depthSample) < tolerance)
        {
            return vec3(spos.xy, depthSample);
        }
    }

    return vec3(1.1); // Indicates no intersection found
}
vec3 rayTraceDiffuse(vec3 dir, vec3 position, float dither, float roughness)
{
    float quality = 32;

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

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

    for (int i = 0; i <= int(quality); ++i)
    {

        vec3 spos = clipPosition + stepv * (float(i) + dither);
        float depthSample = texelFetch2D(depthtex0, ivec2(spos.xy / texelSize), 0).r;
        if (depthSample < spos.z && abs(spos.z - depthSample) < tolerance)
        {
            return vec3(spos.xy, depthSample);
        }
    }

    return vec3(1.1); // Indicates no intersection found
}

// 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;
}
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(max(0.0, 1.0 - u1));
    return vec3(x, y, z);
}
vec3 diffuseFunc(vec3 direct, vec3 fresnel, float sunSpec, float skyLight, vec3 normal, vec3 fragpos, float smoothness,
                 vec3 f0, vec3 albedo)
{
    vec3 p3 = mat3(gbufferModelViewInverse) * fragpos + gbufferModelViewInverse[3].xyz;

    vec3 diffuseColor = vec3(0.0);
    vec4 tempDiffuse = vec4(0.0);

    mat3 basis = CoordBase(normal);
    vec3 normSpaceView = -normalize(p3) * basis;

    // Number of samples for diffuse lighting
    int nDiffuseSamples = 4;
    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);

        // Sample directions over the hemisphere oriented around the normal
        vec3 H = sampleHemisphere(ij.x, ij.y);
        vec3 L = basis * H;

        float NdotL = max(dot(normal, L), 0.0);
        if (NdotL > 0.0)
        {
            vec3 rayContrib = albedo * NdotL; // Diffuse contribution

            if (luma(rayContrib) > 0.0)
            {
                float rayQuality = 35.0 * sqrt(luma(rayContrib));
                if (rayQuality > 0.0)
                {
                    vec3 rtPos =
                        rayTraceDiffuse(mat3(gbufferModelView) * L, fragpos.xyz, noise_standard(gl_FragCoord.xy), 1.0);

                    vec3 previousPosition = reproject(
                        mat3(gbufferModelViewInverse) * toScreenSpace(rtPos) + gbufferModelViewInverse[3].xyz, false);
                    if (all(greaterThanEqual(previousPosition.xy, vec2(0.0))) && rtPos.z < 1.0)
                    {
                        tempDiffuse += vec4(texture(colortex4, previousPosition.xy).rgb + albedo, 1);
                    }
                    else
                    {
                        occlusion += 1.0;
                        // v	occlusion += 1.0;ec3 wrefl = mat3(gbufferModelViewInverse) * reflectedVector;
                        float bounceAmount = float(L.y > 0.0) + clamp(-L.y * 0.1 + 0.1, 0.0, 1.0);
                        // vec3 sky_c = skyCloudsFromTex(rayDir,colortex4).rgb * bounceAmount;
                        //     sky = skyFromTex2(L, colortex6);
                        // tempDiffuse.rgb += skyFromTex2(L, colortex6).rgb * bounceAmount;
                        tempDiffuse.rgb += skyFromTex2(L, colortex6).rgb * 1024 * bounceAmount;
                    }
                }
            }
        }
    }

    // return tempDiffuse.rgb / nDiffuseSamples;
    return (tempDiffuse.rgb / nDiffuseSamples) * (1.0 - occlusion / nDiffuseSamples);
}
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);
}

vec3 reflectionFunc(float skyLight, vec3 normal, vec3 fragpos, float smoothness, vec3 f0, float rainMult)
{
    float roughness = clamp(pow(1.0 - smoothness, 2.0), 0.0001, 0.9999) * 1;
    roughness = mix(roughness, 0.01, rainMult);

    vec3 p3 = mat3(gbufferModelViewInverse) * fragpos;

    vec3 reflection = vec3(0.0);
    vec4 tempreflection = vec4(0.0);
    vec3 rtPos = vec3(0.0, 0.0, 0.0);
    f0 = mix(f0, vec3(0.02), rainMult);

    mat3 basis = CoordBase(normal);
    vec3 normSpaceView = -normalize(p3) * basis;
    // int nSpecularSamples = int(mix(2, 2, smoothness));
    int nSpecularSamples = REFLECTION_SAMPLES;
    if (roughness <= 1.0)
    {
        for (int i = 0; i < nSpecularSamples; i++)
        {

            vec2 ij = fract(R2_samples(frameCounter * int(nSpecularSamples) + i) + blueNoise3(gl_FragCoord.xy).rg);
            vec3 H1 = SampleGGXVNDF(normSpaceView, roughness, roughness, ij.x, ij.y);
            vec3 H2 = importanceSampleGGX(ij, vec3(0.0, 0.0, 1.0), roughness, 0.0);
            vec3 H = mix(H1, H2, 0.5);
            //  vec3 H = sampleGGX(N, roughness, ij.x, ij.y);

            vec3 Ln = reflect(-normSpaceView, H);
            vec3 L = basis * Ln;
            float g1 = g(clamp(dot(normal, L), 0.0, 1.0), roughness);
            vec3 F = f0 + (1.0 - f0) * pow(clamp(1.0 + dot(-Ln, H), 0.0, 1.0), 5.0);
            vec3 rayContrib = F * g1;
            if (luma(rayContrib) > 0.02)
            {
                float rayQuality = 35 * sqrt(luma(rayContrib));
                if (rayQuality > 5.0)
                {
                    rtPos =
                        rayTrace(mat3(gbufferModelView) * L, fragpos.xyz, noise_standard(gl_FragCoord.xy), rayQuality);

                    vec3 previousPosition = reproject(
                        mat3(gbufferModelViewInverse) * toScreenSpace(rtPos) + gbufferModelViewInverse[3].xyz, false);
                    if (rtPos.z < 1.0)
                    {

                        tempreflection += vec4(texture(colortex4, previousPosition.xy, 0.0).rgb, 1);
                    }
                    else
                    {
                        // vec3 wrefl = mat3(gbufferModelViewInverse) * reflectedVector;

                        tempreflection.rgb += skyFromTex2(L, colortex6).rgb * 0.05 * skyLight;
                    }
                }
            }
            tempreflection.rgb *= rayContrib;
        }

        tempreflection /= nSpecularSamples;
        reflection = mix(reflection.rgb, tempreflection.rgb, 1);
    }
    return reflection;
}

////

vec3 GetN(int idx)
{
    switch (idx)
    {
    case 230:
        return vec3(2.9114, 2.9497, 2.5845);
    case 231:
        return vec3(0.18299, 0.42108, 1.3734);
    case 232:
        return vec3(1.3456, 0.96521, 0.61722);
    case 233:
        return vec3(3.1071, 3.1812, 2.3230);
    case 234:
        return vec3(0.27105, 0.67693, 1.3164);
    case 235:
        return vec3(1.9100, 1.8300, 1.4400);
    case 236:
        return vec3(2.3757, 2.0847, 1.8453);
    case 237:
        return vec3(0.15943, 0.14512, 0.13547);
    default:
        return vec3(0.0);
    }
}

vec3 GetK(int idx)
{
    switch (idx)
    {
    case 230:
        return vec3(3.0893, 2.9318, 2.7670);
    case 231:
        return vec3(3.4242, 2.3459, 1.7704);
    case 232:
        return vec3(7.4746, 6.3995, 5.3031);
    case 233:
        return vec3(3.3314, 3.3291, 3.1350);
    case 234:
        return vec3(3.6092, 2.6248, 2.2921);
    case 235:
        return vec3(3.5100, 3.4000, 3.1800);
    case 236:
        return vec3(4.2655, 3.7153, 3.1365);
    case 237:
        return vec3(3.9291, 3.1900, 2.3808);
    default:
        return vec3(1.0);
    }
}

float fresnel0(vec3 l, vec3 n, float F0)
{
    // Assume eta1 is 1.0 (air)
    float eta1 = (-1.0 - sqrt(F0)) / (sqrt(F0) - 1.0);

    // Compute eta2 from F0
    float eta2 = 1;

    float cosThetaI = abs(dot(l, n)); // cos(theta_i) - angle of incidence
    float eta = eta2 / eta1;          // relative index of refraction
    float sin2ThetaI = 1.0 - cosThetaI * cosThetaI;
    float sin2ThetaT = eta * eta * sin2ThetaI; // sin^2(theta_t) using Snell's law

    float cosThetaT = sqrt(max(0.0, 1.0 - sin2ThetaT)); // cos(theta_t)

    // Reflectance for s-polarized light
    float Rs = (eta1 * cosThetaI - eta2 * cosThetaT) / (eta1 * cosThetaI + eta2 * cosThetaT);
    Rs = Rs * Rs;

    // Reflectance for p-polarized light
    float Rp = (eta2 * cosThetaI - eta1 * cosThetaT) / (eta2 * cosThetaI + eta1 * cosThetaT);
    Rp = Rp * Rp;

    // Average of the s and p polarization reflectances
    return 0.5 * (Rs + Rp);
}
vec3 ComplexFresnel(vec3 fragpos, vec3 normal, float f0)
{
    float fresnel = fresnel0(fragpos, normal, f0);
    if (f0 > 229.5 * recip)
    {
        int metalidx = int(f0 * 255.0);
        vec3 k = GetK(metalidx);
        vec3 n = GetN(metalidx);
        float f = 1.0 - fresnel;

        vec3 k2 = k * k;
        vec3 n2 = n * n;
        float f2 = f * f;

        vec3 rs_num = n2 + k2 - 2 * n * f + f2;
        vec3 rs_den = n2 + k2 + 2 * n * f + f2;
        vec3 rs = rs_num / rs_den;

        vec3 rp_num = (n2 + k2) * f2 - 2 * n * f + 1;
        vec3 rp_den = (n2 + k2) * f2 + 2 * n * f + 1;
        vec3 rp = rp_num / rp_den;

        vec3 fresnel3 = clamp(0.5 * (rs + rp), vec3(0.0), vec3(1.0));
        fresnel3 *= fresnel3;
        return fresnel3;
    }
    else
    {
        return vec3(fresnel);
    }
}

vec3 computeReflections(float lightmapY, float smoothness, vec3 f0, vec3 normal, vec3 fragpos, float materialsX)
{
    vec3 reflections;
    float f0_orig = f0.y;
    float porosity = (materialsX * 255 <= 64.5) ? step(0.0, materialsX) : 0.0;
    float rainMult = sqrt(lightmapY) * wetness * (1.0 - (porosity * porosity));

    // f0 = mix(f0, vec3(0.02), rainMult);

    vec3 fresnel = ComplexFresnel(normalize(fragpos), worldToView(normal), f0_orig);

    reflections = reflectionFunc(lightmapY, (normal), fragpos, smoothness, f0, rainMult) * fresnel;

    return reflections;
}
