vec4 blueNoise2(vec2 coord) {
    return texelFetch2D(colortex14, ivec2(coord) % 512, 0);
}

float linZ(float depth) {
    return (2.0 * near) / (far + near - depth * (far - near));
}
float RT20(vec3 dir, vec3 position, float noise) {

    float ssptbias = 0.025;
    float stepSize = 25.0;
    vec3 clipPosition = toClipSpace3(position);

    float rayLength = ((position.z + dir.z * sqrt(3.0) * far) > -sqrt(3.0) * near) ? (-sqrt(3.0) * near - position.z) / dir.z : sqrt(3.0) * far;

    vec3 end = toClipSpace3(position + dir * rayLength);
    vec3 direction = end - clipPosition; // convert to clip space

    float len = max(abs(direction.x) / texelSize.x, abs(direction.y) / texelSize.y) / stepSize;

    vec3 stepv = direction / len;

    vec3 spos = clipPosition + stepv / stepSize;

    spos += stepv * noise;

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

        float sp = linZ(texelFetch2D(depthtex0, ivec2(spos.xy / texelSize), 0).r);
        float currZ = linZ(spos.z);
        if (sp < currZ) {
            float dist = abs(sp - currZ) / currZ;
            if (dist <= ssptbias)
                return 1.0;
        }

        spos += stepv;

    }

    return 0.0;
}

float RTAO2(vec3 normal, vec3 fragpos) {

    int nrays = 3;

    float occlusion = 0.0;
    vec4 noise3 = blueNoise2(gl_oord.xy);
    for (int i = 0; i < nrays; i++) {
        int seed = (frameCounter % 40000) * nrays + i;

        vec2 ij = fract(R2_samples2(seed) + noise3.rg);
        vec3 rayDir = normalize(cosineHemisphereSample2(ij));

        rayDir = TangentToWorld2(normal, rayDir);
        rayDir = mat3(gbufferModelView) * rayDir;

        float rayHit = RT20(rayDir, fragpos, fract(seed / 1.6180339887 + noise3.b));
        occlusion += rayHit;

    }

    return (1.0 - occlusion / nrays);
}



#if defined MC_GL_VENDOR_NVIDIA && defined MC_OS_WINDOWS
vec4 blueNoise(vec2 coord) {
    return texelFetch2D(noisetex, ivec2(coord) % 512, 0);
}

#endif

float rayTraceShadow(vec3 dir, vec3 position, float dither) {
    // Set the number of samples to take along the ray
    const float quality = 6;

    // Calculate the clip-space position of the current fragment
    vec3 clipPosition = toClipSpace3(position);

    // Calculate the length of the ray in world space
    float precalc = far * 1.73205080757;
    float rayLength = ((dir.z * precalc + position.z) > -near) ? dir.z / (-near - position.z) : precalc;

    // Convert the ray direction to clip space
    vec3 direction = toClipSpace3(position + dir * rayLength) - clipPosition;

    // Scale the ray direction by the texel size to ensure fixed step size
    direction.xyz = direction.xyz / max(abs(direction.x) / texelSize.x, abs(direction.y) / texelSize.y);

    // Calculate the initial step vector based on the ray direction and dither value
    vec3 stepv = direction * 32 * clamp(dither, 0.25, 1);

    // Calculate the initial position of the ray based on the clip-space position, texel size, and dither value
    vec3 spos = clipPosition;

    float tolerance = max(abs(stepv.z) * 1.0, 0.0011 / (position.z * position.z));

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

        float sp = texelFetch2D(depthtex0, ivec2(spos.xy / texelSize), 0).r;
        if (sp < spos.z && abs(tolerance - (spos.z - sp)) < tolerance) {
            return 0.0;
        }

    }

    // Return 1 if the ray did not hit any objects
    return 1.0;
}
