#version 440

layout(location=0) in vec3 v_nearPoint;
layout(location=1) in vec3 v_farPoint;

layout(location=0) out vec4 fragColor;

layout(std140, binding=0) uniform buf {
    mat4 viewProj; // viewProj
    mat4 invViewProj; // invViewProj
    float near;
    float far;
    float scale;
    float yFactor;
    uint gridFlags;
} u_buf;

// gridFlag enum values:

const uint drawAxisFlag = 1;

vec4 grid(vec2 fragPos2D, float scale, bool drawAxis, float lineAlpha) {
    vec2 coord = fragPos2D * scale; // use the scale variable to set the distance between the lines
    vec2 derivative = fwidth(coord); // derivative is pixel spacing in grid coords (1/derivative is distance between grid lines in pixels)
    vec2 grid = abs(fract(coord - 0.5) - 0.5) / derivative; // distance from grid in pixels

    float line = min(grid.x, grid.y);
    float alpha = smoothstep(0.0, 1, 1 - line) * lineAlpha;

    vec4 color = vec4(0.5, 0.5, 0.5, alpha); // NOT premultipled

    float minimumx = min(derivative.x, 1.0);
    float minimumz = min(derivative.y, 1.0);

    if (drawAxis) {
        vec2 axisDistance = abs(coord/derivative);
        if (axisDistance.y < 1) // distance to first axis
            color = vec4(1.0, 0.0, 0.0, alpha);
        if (axisDistance.x < 1) // distance to second axis
            color = vec4(0.0, 0.0, 1.0, alpha);
    }

    // Fade the lines when they get too close together
    const float fadeLimit = 0.1; // Start fading at grid spacing 1/fadeLimit
    const float fadeGradient = 1.5; // Fade out completely at length(derivative) == fadeLimit + 1/fadeGradient

    float d2 = length(derivative);
    if (d2 > fadeLimit)
        color.a *= max(0.0, 1 - fadeGradient * (d2 - fadeLimit));

    return color;
}

float computeLinearDepth(float fragDepth, float near, float far) {
    if (near <= 0.0)
        return fragDepth;
    float linearDepth = (2.0 * near * far) / (near + far - fragDepth * (far - near));
    return linearDepth / far;
}

void main(void)
{
    const float nearFloor = v_nearPoint.y;
    const float farFloor = v_farPoint.y;
    // Find where the sight line intersects the floor
    const float t = -nearFloor / (farFloor - nearFloor);

    // Fragment posision in 3D world space
    const vec3 fragPos3D = v_nearPoint + t * (v_farPoint - v_nearPoint);

    const vec2 fragPos2D = fragPos3D.xz;

    // Getting the fragment position in clip space
    const vec4 clipSpacePos = u_buf.viewProj * vec4(fragPos3D, 1.0);

    // The out color is a combination of a larger grid and a smaller grid.
    // The grid is only shown at y=0.

    const bool drawAxis = (u_buf.gridFlags & drawAxisFlag) != 0;
    const vec4 outColor = max(grid(fragPos2D, 10.0*u_buf.scale, drawAxis, 0.5), grid(fragPos2D, 1.0*u_buf.scale, drawAxis, 1.0));

    // Fade at far clip: Calculating the depth of the fragment, the linear
    // depth and using the linear depth to calculate the fading constant
    // Do a hard cutoff at near clip, since that is less common, and we don't have an
    // instinct to expect a near fade anyway
    const float fragDepth = clipSpacePos.z / clipSpacePos.w;
    const float linearDepth = computeLinearDepth(fragDepth, u_buf.near, u_buf.far);
    const float fading = float(fragDepth > -1 && fragDepth < 1) * (1-max((linearDepth - 0.7)/0.3, 0.0));

    fragColor = vec4(outColor.rgb, outColor.a * fading);
    gl_FragDepth = 0.5 * fragDepth + 0.5;
}
