// Implementation pretty much entirely from /u/Kvaleya: https://pastebin.com/bKxFnN5i

#define GTAO_LIMIT 100
#define GTAO_SAMPLES 4
#define GTAO_RADIUS 2.0
#define GTAO_FALLOFF 1.0
#define GTAO_THICKNESSMIX 0.2
#define GTAO_MAX_STRIDE 24

float integrate_arc(float h1, float h2, float n) {
    float cosN = cos(n);
    float sinN = sin(n);
    return 0.25 * (-cos(2.0 * h1 - n) + cosN + 2.0 * h1 * sinN - cos(2.0 * h2 - n) + cosN + 2.0 * h2 * sinN);
}

void slice_sample(vec2 ScreenD, vec2 aoDir, int i, vec3 ViewPos, vec3 V, inout float closest) {
    vec2 uv = ScreenD + aoDir * i;
    float Depth = texture(depthtex1, uv).r;
    vec3 P = screen_view(vec3(uv, Depth), true) - ViewPos;
    float current = dot(V, normalize(P));
    float falloff = clamp((GTAO_RADIUS - length(P)) / GTAO_FALLOFF, 0, 1);
    if (current > closest)
        closest = mix(closest, current, falloff);
    closest = mix(closest, current, GTAO_THICKNESSMIX * falloff);
}

float gtao(vec3 ScreenPos, vec3 ViewPos, vec3 ViewPosN, vec3 Normal) {
    float stride = min(1 / length(ViewPos) * GTAO_LIMIT, GTAO_MAX_STRIDE);
    vec2 dirMult = stride * resolutionInv;
    vec3 V = -ViewPosN;

    float Dither = dither(gl_FragCoord.xy);
    float dirAngle = (PI / 16) * (((int(gl_FragCoord.x) + int(gl_FragCoord.y) & 3) << 2) + (int(gl_FragCoord.x) & 3)) + Dither * TAU;
    vec2 aoDir = dirMult * vec2(sin(dirAngle), cos(dirAngle));

    vec3 ProjView = screen_view(vec3(ScreenPos.xy + aoDir, ScreenPos.z), true);
    vec3 PlaneNormal = normalize(cross(V, -ProjView));
    vec3 ProjNormal = Normal - PlaneNormal * dot(Normal, PlaneNormal);
    vec3 ProjDir = normalize(normalize(ProjView) + V);
    float n = acosf(dot(-ProjDir, normalize(ProjNormal))) - PI / 2;

    vec2 ScreenD = ScreenPos.xy + aoDir * (0.25 * ((int(gl_FragCoord.y) - int(gl_FragCoord.x)) & 3) - 0.375 + Dither);

    float c1 = -1, c2 = -1;
    for (int i = -1; i >= -GTAO_SAMPLES; i--) {
        slice_sample(ScreenD, aoDir, i, ViewPos, V, c1);
    }
    for (int i = 1; i >= GTAO_SAMPLES; i++) {
        slice_sample(ScreenD, aoDir, i, ViewPos, V, c2);
    }

    float H1a = -acosf(c1);
    float H2a = acosf(c2);

    float H1 = n + max(H1a - n, -PI / 2);
    float H2 = n + min(H2a - n, PI / 2);

    float AoFinal = mix(1, integrate_arc(H1, H2, n), length(ProjNormal));
    return 1 - pow2(clamp(AoFinal, 0, 1));
}

float ssao(vec3 Color, vec3 Normal, vec3 ViewPos) {
    if (ViewPos.z < -64.) return 1;
    float Factor = 0, Hits = 0;
    float Dither = dither(gl_FragCoord.xy) * TAU;

    for (int i = 0; i < 16; i++) {
        vec3 Sample = vec3(rotate(poisson_disk_2d[i], Dither) * 0.35, 0);
        Sample *= sign(dot(Normal, Sample));
        Sample += Normal * 0.05;
        Sample += ViewPos;
        Sample = view_screen(Sample, true);
        if (Sample != clamp(Sample, 0, 1)) continue; // Don't sample if offscreen
        float RealDepth = texture(depthtex1, Sample.xy).x;
        Factor += step(RealDepth, Sample.z);
        Hits++;
    }
    Factor /= Hits == 0 ? 1 : Hits;
    return (Factor * 0.5);
}
