

vec4 ld4(vec4 dist)
{
    return (2.0 * dhNearPlane) / (dhFarPlane + dhNearPlane - dist * (dhFarPlane - dhNearPlane));
}
float lddh(float dist)
{
    return (2.0 * dhNearPlane) / (dhFarPlane + dhNearPlane - dist * (dhFarPlane - dhNearPlane));
}

float AmbientOcclusion2(float dither, float depth)
{

    float ao = 0.0;

    depth = lddh(depth);

    vec2 scale = dither * gl_FragCoord.xy * 0.01;
    float mult = 0.5 * (dhFarPlane - dhNearPlane);

    const vec4 steps = 0.4 + vec4(0.0, 0.2, 0.4, 0.6);

    ivec2 texelcoord = ivec2(gl_FragCoord.xy);

    ivec2 texelCoords0 = ivec2(texelcoord + vec2(0.321630, 0.237497) * scale);
    ivec2 texelCoords1 = ivec2(texelcoord + vec2(0.170197, 0.575310) * scale);
    ivec2 texelCoords2 = ivec2(texelcoord + vec2(0.226930, 0.767481) * scale);
    ivec2 texelCoords3 = ivec2(texelcoord + vec2(1.000000, 0.000000) * scale);

    ivec2 texelCoordsx = ivec2(texelcoord - vec2(0.321630, 0.237497) * scale);
    ivec2 texelCoordsy = ivec2(texelcoord - vec2(0.170197, 0.575310) * scale);
    ivec2 texelCoordsz = ivec2(texelcoord - vec2(0.226930, 0.767481) * scale);
    ivec2 texelCoordsw = ivec2(texelcoord - vec2(1.000000, 0.000000) * scale);

    vec4 depthgather0 =
        ld4(vec4(texelFetch(dhDepthTex0, texelCoords0, 0).r, texelFetch(dhDepthTex0, texelCoords1, 0).r,
                 texelFetch(dhDepthTex0, texelCoords2, 0).r, texelFetch(dhDepthTex0, texelCoords3, 0).r));
    vec4 depthgather1 =
        ld4(vec4(texelFetch(dhDepthTex0, texelCoordsx, 0).r, texelFetch(dhDepthTex0, texelCoordsy, 0).r,
                 texelFetch(dhDepthTex0, texelCoordsz, 0).r, texelFetch(dhDepthTex0, texelCoordsw, 0).r));

    vec4 sample0 = (depth - (depthgather0)) * mult / steps;
    vec4 sample1 = (depth - (depthgather1)) * mult / steps;

    vec4 angle;
    angle += max(0.5 - sample0, 0.0);
    angle += max(0.5 - sample1, 0.0);

    vec4 dist;
    dist += max(0.25 * sample0 - 1.0, 0.0);
    dist += max(0.25 * sample1 - 1.0, 0.0);

    vec4 ao2 = clamp(angle + dist, 0.0, 1.0);
    ao = dot(ao2, vec4(1.0)) * 0.25;

    return min(ao + 0.0, 1.0);
}

vec2 offsetDist(float x, int s)
{
    float n = fract(x * 1.414) * PI;
    return vec2(cos(n), sin(n)) * x / s;
}

float dbao(float dither)
{
    float ao = 0.0;
    int samples = 4;

    float d = lddh(texture2D(dhDepthTex0, texcoord.xy).r);
    vec2 scale =
        3 * dither * vec2(1.0 / aspectRatio, 1.0) * dhProjection[1][1] / (2.74747742 * max(dhFarPlane * d, 6.0));

    for (int i = 1; i <= samples; i++)
    {
        vec2 offset = offsetDist(i + dither, samples) * scale;

        float sd1 = lddh(texture2D(dhDepthTex0, texcoord.xy + offset).r);
        float sample1 = dhFarPlane * (d - sd1) * 10.0 * dither;

        float angle1 = clamp(0.5 - sample1, 0.0, 1.0);
        float dist1 = clamp(0.25 * sample1 - 1.0, 0.0, 1.0);

        float sd2 = lddh(texture2D(dhDepthTex0, texcoord.xy - offset).r);
        float sample2 = dhFarPlane * (d - sd2) * 10.0 * dither;

        float angle2 = clamp(0.5 - sample2, 0.0, 1.0);
        float dist2 = clamp(0.25 * sample2 - 1.0, 0.0, 1.0);

        ao += clamp(angle1 + dist1 + angle2 + dist2, 0.0, 1.0);
    }
    ao /= samples;

    return ao;
}

//

vec2 tapLocation(int sampleNumber, float spinAngle, float r0)
{
    float alpha = (float(sampleNumber + r0) * 0.142857);
    float angle = alpha * 552.64 + spinAngle * 6.28;

    float sin_v, cos_v;

    sin_v = sin(angle);
    cos_v = cos(angle);

    return vec2(cos_v, sin_v) * alpha;
}

float ssao(vec3 fragpos, float dither, vec3 normal)
{
    float mulfov2 = dhProjection[1][1] / 2.742192;
    float maxR2 = fragpos.z * fragpos.z * mulfov2 * 0.05648;
    float rd = mulfov2 * 0.04; // pre-rotate direction
    float occlusion = 1.0;
    int samples = 6;
    vec2 texelOffset = texelSize * 0.5;
    vec2 jitter = fract(vec2(dither) + float(frameCounter % 10000) * vec2(0.75487765, 0.56984026));

    for (int j = 0; j < samples; j++)
    {
        vec2 sp = tapLocation(j, jitter.x, jitter.y);
        vec2 sampleOffset = sp * rd;
        ivec2 offset = ivec2(gl_FragCoord.xy + sampleOffset * vec2(viewWidth, viewHeight * aspectRatio));

        if (offset.x >= 0 && offset.y >= 0 && offset.x < viewWidth && offset.y < viewHeight)
        {
            vec3 t0 = toScreenSpaceDH(vec3(offset * texelSize + texelOffset, texelFetch2D(dhDepthTex0, offset, 0).x));
            vec3 vector = t0 - fragpos;
            float dsquared = dot(vector, vector);

            if (dsquared > 1e-5 && dsquared < maxR2)
            {
                float NdotV = max(dot(normalize(vector), normalize(normal)), 0.0);
                occlusion += NdotV * (1.0 - dsquared / maxR2);
            }
        }
    }

    return clamp((1.0 - occlusion / float(samples) * 1.6) * 1.6, 0.5, 1.0);
}
mat3 CoordBase0(vec3 n)
{
    vec3 x, y;

    if (n.z < -0.999999)
    {
        x = vec3(0., -1, 0);
        y = vec3(-1, 0, 0);
    }
    else
    {
        float a = 1. / (1. + n.z);
        float b = -n.x * n.y * a;
        x = vec3(1. - n.x * n.x * a, b, -n.x);
        y = vec3(b, 1. - n.y * n.y * a, -n.y);
    }

    return mat3(x, y, n);
}

float s_rtao(vec3 normal, vec3 fragpos)
{
    // Pre-compute invariant values
    mat3 basis = CoordBase0(normal);
    vec4 bnoise = blueNoise4(gl_FragCoord.xy);
    vec3 clipPosition = toClipSpace3DH(fragpos);
    int fc4 = frameCounter * 4;
    float occlusion = 0.0;
    float fragposSq = fragpos.z * fragpos.z;
    for (int i = 0; i < 4; i++) // Unroll loop since samples is fixed at 4
    {
        int seed = fc4 + i;
        vec2 ij = fract(fract(seed * vec2(0.5, 0.75)) + bnoise.rg);

        // Sample directions over the hemisphere oriented around the normal
        float r = sqrt(ij.x);
        float theta = 6.283185 * ij.y;
        vec3 H = vec3(r * cos(theta), r * sin(theta), sqrt(1.0 - ij.x));
        vec3 L = basis * H;

        // Perform ray tracing directly within the loop
        vec3 direction = normalize(toClipSpace3DH(fragpos + mat3(gbufferModelView) * L) - clipPosition);
        vec3 maxLengths = ((step(0.0, direction) - clipPosition) / direction);
        float minLength = min(min(maxLengths.x, maxLengths.y), maxLengths.z);
        vec3 stepv = direction * (minLength * ij.yxy); // 1/16 = 0.0625

        float tolerance = max(abs(stepv.z) * 16.0, 0.05 / fragposSq);

        vec3 spos = clipPosition + stepv * ij.xyx;
        float depthSample = texelFetch(dhDepthTex0, ivec2(spos.xy / texelSize), 0).r;

        if (!(depthSample < spos.z && abs(spos.z - depthSample) < tolerance))
        {
            occlusion += 0.5;
        }
    }

    return clamp(occlusion, 0, 1); // Use multiplication by inverse to avoid division
}