out vec3 sceneData;

/* DRAWBUFFERS:4 */

#include "/lib/Head/Common.inc"
#include "/lib/Head/Uniforms.inc"

// ======================================================================
// DOF CONFIG
// ======================================================================   
#define DOF_CHROMA_DISPERSION
#define DOF_SAMPLES 48 // [0.0 1.0 1.5 2.0 2.5 3.0 3.5 4.0 4.5 5.0 5.5 6.0 6.5 7.0 7.5 8.0 8.5 9.0 9.5 10.0 11.0 12.0 13.0 14.0 15.0 16.0 17.0 18.0 19.0 20.0 21.0 22.0 23.0 24.0 25.0 26.0 27.0 28.0 29.0 30.0 35.0 40.0 48.0 50.0]
#define DOF_INTENSITY 0.02 // [0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1.0]
#define DOF_ANAMORPHIC_RATIO 1.0 // [0.0 1.0 1.5 2.0 2.5 3.0 3.5 4.0 4.5 5.0 5.5 6.0 6.5 7.0 7.5 8.0 8.5 9.0 9.5 10.0 11.0 12.0 13.0 14.0 15.0 16.0 17.0 18.0 19.0 20.0 21.0 22.0 23.0 24.0 25.0 26.0 27.0 28.0 29.0 30.0]
#define FOCUS_MODE 0
#define MANUAL_FOCUS 5.0 // [0.0 1.0 1.5 2.0 2.5 3.0 3.5 4.0 4.5 5.0 5.5 6.0 6.5 7.0 7.5 8.0 8.5 9.0 9.5 10.0 11.0 12.0 13.0 14.0 15.0 16.0 17.0 18.0 19.0 20.0 21.0 22.0 23.0 24.0 25.0 26.0 27.0 28.0 29.0 30.0]
#define CAMERA_APERTURE 2.8 // [0.0 1.0 1.5 2.0 2.5 3.0 3.5 4.0 4.5 5.0 5.5 6.0 6.5 7.0 7.5 8.0 8.5 9.0 9.5 10.0 11.0 12.0 13.0 14.0 15.0 16.0 17.0 18.0 19.0 20.0 21.0 22.0 23.0 24.0 25.0 26.0 27.0 28.0 29.0 30.0]
#define DOF_APERTURE_SHAPE 2 // [0 1 2]
// 0 = circle
// 1 = hexagon
// 2 = octagon
#define DOF_CAT_EYE
#define CAT_EYE_STRENGTH 0.6 // [0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1.0]
#define MAX_COC_RADIUS 25.0 // [0.0 1.0 1.5 2.0 2.5 3.0 3.5 4.0 4.5 5.0 5.5 6.0 6.5 7.0 7.5 8.0 8.5 9.0 9.5 10.0 11.0 12.0 13.0 14.0 15.0 16.0 17.0 18.0 19.0 20.0 21.0 22.0 23.0 24.0 25.0 26.0 27.0 28.0 29.0 30.0]
#define COC_SMOOTH_START 0.6 // [0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1.0]

//----// FUNCTIONS //-----------------------------------------------------------------------------//

#include "/lib/Head/Functions.inc"

// Physical CoC
float CalculateCoC(float p, float z, float a, float f) {
    return oneMinus(p / z) * a * f * rcp(p - f);
}

// UE soft clamp
float ClampCoC(float coc) {
    float a = abs(coc);
    float c = mix(a, MAX_COC_RADIUS,
        smoothstep(MAX_COC_RADIUS * COC_SMOOTH_START, MAX_COC_RADIUS, a));
    return sign(coc) * c;
}

// ======================================================================
// APERTURE SHAPES
// ======================================================================

float PolygonMask(vec2 p, int sides) {
    float a = atan(p.y, p.x);
    float r = length(p);
    float k = TAU / float(sides);
    float d = cos(floor(0.5 + a / k) * k - a) * r;
    return smoothstep(1.0, 0.98, d);
}

// ======================================================================
// CAT-EYE
// ======================================================================

vec2 ApplyCatEye(vec2 offset, vec2 uv) {
    vec2 fromCenter = uv - 0.5;
    float vignette = length(fromCenter) * 2.0;
    float squeeze = mix(1.0, 1.0 - CAT_EYE_STRENGTH, vignette);
    return vec2(offset.x * squeeze, offset.y);
}

//----// MAIN //----------------------------------------------------------------------------------//

void main() {
    ivec2 texel = ivec2(gl_FragCoord.xy);
    vec2 screenCoord = gl_FragCoord.xy * screenPixelSize;

    float depth = texelFetch(depthtex1, texel, 0).x;
    if (depth < 0.56) {
        sceneData = texelFetch(colortex4, texel, 0).rgb;
        return;
    }

    // ===== FOCUS =====
    #if FOCUS_MODE == 0
        float focusDist = texelFetch(colortex5, ivec2(1), 0).a;
    #else
        float focus = far * (MANUAL_FOCUS - near) / ((far - near) * MANUAL_FOCUS);
        float focusDist = GetDepthLinear(focus);
    #endif

    // ===== CAMERA =====
    const float focalLength = 0.5 * 0.035 * gbufferProjection[1][1];
    const float aperture = focalLength / CAMERA_APERTURE;

    float dist = GetDepthLinear(depth);

    float coc = CalculateCoC(focusDist, dist, aperture, focalLength);
    coc = ClampCoC(coc);

    vec2 CoC = vec2(coc * DOF_ANAMORPHIC_RATIO, coc * aspectRatio);
    CoC *= screenSize * DOF_INTENSITY * 1e3;

    // ===== SAMPLING =====
    const float goldenAngle = TAU / (PHI1 + 1.0);
    const mat2 goldenRot = mat2(
        cos(goldenAngle), -sin(goldenAngle),
        sin(goldenAngle),  cos(goldenAngle)
    );

    float noise = BlueNoiseTemporal();
    vec2 rot = cossin(noise * TAU) * CoC;

    sceneData = vec3(0.0);
    float weightSum = 0.0;

    const float rSteps = 1.0 / DOF_SAMPLES;

    #ifdef DOF_CHROMA_DISPERSION
        ivec2 chromaOffset = ivec2(normalize(screenCoord - 0.5) * 0.5 * CoC);
    #endif

    for (uint i = 0u; i < DOF_SAMPLES; ++i, rot *= goldenRot) {
        float r = sqrt((noise + float(i)) * rSteps);
        vec2 offset = rot * r;

        #ifdef DOF_CAT_EYE
            offset = ApplyCatEye(offset, screenCoord);
        #endif

        float shapeMask = 1.0;

        #if DOF_APERTURE_SHAPE == 1
            shapeMask = PolygonMask(offset / CoC, 6);
        #elif DOF_APERTURE_SHAPE == 2
            shapeMask = PolygonMask(offset / CoC, 8);
        #endif

        float w = shapeMask;
        weightSum += w;

        #ifdef DOF_CHROMA_DISPERSION
            ivec2 sc = texel + ivec2(offset);
            sceneData.r += texelFetch(colortex4, sc + chromaOffset, 0).r * w;
            sceneData.g += texelFetch(colortex4, sc, 0).g * w;
            sceneData.b += texelFetch(colortex4, sc - chromaOffset, 0).b * w;
        #else
            sceneData += texelFetch(colortex4, texel + ivec2(offset), 0).rgb * w;
        #endif
    }

    sceneData /= max(weightSum, 1e-4);
}
