#version 400 compatibility

/*
====================================================================================================

    Copyright (C) 2020 RRe36

    All Rights Reserved unless otherwise explicitly stated.


    By downloading this you have agreed to the license and terms of use.
    These can be found inside the included license-file
    or here: https://rre36.com/copyright-license

    Violating these terms may be penalized with actions according to the Digital Millennium
    Copyright Act (DMCA), the Information Society Directive and/or similar laws
    depending on your country.

====================================================================================================
*/

#include "/lib/head.glsl"
#include "/lib/util/encoders.glsl"
#include "/lib/util/colorspace.glsl"

const int shadowMapResolution   = 2048;     //[512 1024 1536 2048 2560 3072 3584 4096 6144 8192 16384]
const float shadowDistance      = 128.0;

const bool shadowHardwareFiltering = true;

in vec2 coord;

flat in mat3x3 lightColor;

uniform sampler2D colortex0;
uniform sampler2D colortex1;
uniform sampler2D colortex2;
uniform sampler2D colortex3;
uniform sampler2D colortex5;

uniform sampler2D depthtex0;
uniform sampler2D depthtex1;

uniform sampler2D noisetex;

uniform sampler2DShadow shadowtex0;
uniform sampler2DShadow shadowtex1;
uniform sampler2D shadowcolor0;

uniform int frameCounter;
uniform int isEyeInWater;

uniform float eyeAltitude;
uniform float far, near;
uniform float frameTimeCounter;

uniform vec2 taaOffset;
uniform vec2 viewSize, pixelSize;

uniform vec3 cameraPosition;

uniform mat4 gbufferModelView, gbufferModelViewInverse;
uniform mat4 gbufferProjection, gbufferProjectionInverse;
uniform mat4 shadowModelView, shadowProjection;


/* ------ includes ------ */
#define FUTIL_TBLEND
#define FUTIL_MAT16
#define FUTIL_ROT2
#include "/lib/fUtil.glsl"

#include "/lib/frag/bluenoise.glsl"

#include "/lib/util/transforms.glsl"

#include "/lib/light/warp.glsl"

#include "/lib/frag/noise.glsl"

#include "/lib/atmos/waterConst.glsl"

vec3 lightvec     = normalize(vec3(0.0, 1.0, 0.0));
vec3 lightvecView = vec3(0.0);

vec3 getShadowmapPos(vec3 scenePos, const float bias) {  //shadow 2d
    vec3 pos    = scenePos + vec3(bias) * lightvec;
    float a     = length(pos);
        pos     = viewMAD(shadowModelView, pos);
        pos     = projMAD(shadowProjection, pos);
        pos.z  *= 0.2;
        pos.z  -= 0.0012*(saturate(a/256.0));

        pos.xy  = shadowmapWarp(pos.xy);

    return pos*0.5+0.5;
}


/* ------ functions ------ */
float smokeDensity(vec3 rPos, float altitude) {
    //float smokeFade = sqr(1.0 - linStep(altitude, 128.0, 256.0));
    //    smokeFade  *= expf(-max0((altitude - 32.0) * 0.03)) * 0.6 + 0.4;

    float smoke     = 0.0;

    vec3 center     = (rPos + cameraPosition) + vec3(0.0, -64.0, 0.0);
    
    float smokeFade = expf(-max0(length(center * vec3(1.0, 1.3, 1.0)) - 192.0) * 0.005) * 0.5 + 0.5;

    vec3 wind   = frameTimeCounter * 0.6 * vec3(1.0, -0.9, 0.1);

    vec3 pos    = (rPos + cameraPosition) * 0.03;
        pos    += value3D(pos * 4.0 + wind * 0.5) * 1.5 - 0.75;

        pos.xz  = rotatePos(pos.xz, pi / 4.5);

        pos    += value3D(pos.zxy * 8.0 - wind) - 0.5;

    pos.x *= 0.7;
    pos.y *= 0.4;

    float noise     = value3D(pos * 4.0 + wind);

        pos.xz  = rotatePos(pos.xz, pi / 4.5);

        noise      += value3D(pos * 8.0 - wind * 2 + noise * 0.5) * 0.5;
        noise      += value3D(pos * 16.0 + wind * 4 + noise * 0.5) * 0.25;
        noise      /= 1.5;

    smoke       = (smokeFade * 0.65 + 0.35) - noise - 0.3 * smokeFade;
    //smoke      /= smokeFade;

    smoke       = cubeSmooth(max0(smoke));

    return smoke;
}

const vec3 smokeCoeff   = vec3(1e-2);

mat2x3 volumetricSmoke(vec3 scenePos, vec3 sceneDir, float dither, bool isSky) {
    vec3 startPos   = gbufferModelViewInverse[3].xyz;
    vec3 endPos     = scenePos;
        if (isSky) endPos = sceneDir * 256.0;

    float baseStep  = length(endPos - startPos);
    float stepCoeff = saturate(baseStep * rcp(clamp(far, 128.0, 192.0)));

    uint steps      = 12 + uint(stepCoeff * 16.0);

    vec3 rStep      = (endPos - startPos) / float(steps);
    vec3 rPos       = startPos + rStep * dither;
    float rLength   = length(rStep);

    vec3 scattering = vec3(0.0);
    vec3 transmittance = vec3(1.0);

    for (uint i = 0; i < steps; ++i, rPos += rStep) {
        if (max3(transmittance) < 0.01) break;

        float altitude  = rPos.y + eyeAltitude;

        //if (altitude > 256.0) continue;

        float density   = smokeDensity(rPos, altitude);

        float stepRho   = density * rLength;
        vec3 od         = smokeCoeff * stepRho;

        vec3 stepT      = expf(-od);
        vec3 scatterInt = saturate((stepT - 1.0) * rcp(-max(od, 1e-16)));
        vec3 visScatter = transmittance * scatterInt;

        vec3 stepScatter = vec3(smokeCoeff * stepRho) * visScatter;

        scattering     += stepScatter * transmittance;

        transmittance  *= stepT;
    }

    vec3 color          = scattering * lightColor[0];

    return mat2x3(color, transmittance);     
}

void applyFogData(inout vec3 color, in mat2x3 data) {
    color = color * data[1] + data[0];
}


vec3 simpleFog(vec3 scenecolor, float d, vec3 color) {
    float dist      = max(0.0, d-32.0);
    float density   = 1.0-exp(-dist*1e-3);

    return mix(scenecolor, vec3(0.0), density);
}

vec3 waterFog(vec3 scene, float d, vec3 color) {
    float density   = max(0.0, d);

    vec3 transmittance = expf(-waterAttenCoeff * density);

    const vec3 scatterCoeff = vec3(5e-2, 1e-1, 2e-1);

    vec3 scatter    = 1.0-exp(-density * scatterCoeff);
        scatter    *= max(expf(-waterAttenCoeff * density), expf(-waterAttenCoeff * pi));

    return scene * transmittance + scatter * color * rcp(pi);
}

vec3 lavaFog(vec3 scene, float d) {
    float density   = max(0.0, d);

    float transmittance = expf(-1.0 * density);

    return mix(vec3(1.0, 0.3, 0.02), scene, transmittance);
}


/* ------ reflections ------ */

#include "/lib/light/brdf.glsl"

#include "/lib/frag/ssr.glsl"

vec3 fresnelTinted(vec3 tint, float VoH) {
    float f0    = v3avg(tint);
        f0      = min(sqrt(f0), 0.99999);
        f0      = (1.0 + f0) * rcp(1.0 - f0);

    float sinThetaI     = sqrt(saturate(1.0 - sqr(VoH)));
    float sinThetaT     = sinThetaI * rcp(f0);
    float cosThetaT     = sqrt(1.0 - sqr(sinThetaT));

    float Rs    = sqr((VoH - (f0 * cosThetaT)) * rcp(VoH + (f0 * cosThetaT)));
    float Rp    = sqr((cosThetaT - (f0 * VoH)) * rcp(cosThetaT + (f0 * VoH)));

    return saturate((Rs + Rp) * 0.5) * tint;
}

/* ------ refraction ------ */

vec3 refract2(vec3 I, vec3 N, vec3 NF, float eta) {     //from spectrum by zombye
    float NoI = dot(N, I);
    float k = 1.0 - eta * eta * (1.0 - NoI * NoI);
    if (k < 0.0) {
        return vec3(0.0); // Total Internal Reflection
    } else {
        float sqrtk = sqrt(k);
        vec3 R = (eta * dot(NF, I) + sqrtk) * NF - (eta * NoI + sqrtk) * N;
        return normalize(R * sqrt(abs(NoI)) + eta * I);
    }
}

void main() {
    vec4 sceneColor   = stex(colortex0);
    //vec4 tex1       = stex(colortex1);
    vec4 tex2       = stex(colortex2);
    vec4 tex3       = stex(colortex3);

    vec4 tex1       = stex(colortex1);
    vec2 sceneLmap  = decode2x8(tex1.z);
    vec3 sceneNormal = decodeNormal(tex1.xy);
    vec3 viewNormal = normalize(mat3(gbufferModelView) * sceneNormal);
    
    int matID      = decodeMatID16(tex2.x);

    float sceneDepth0   = stex(depthtex0).x;
    vec3 viewPos0       = screenToViewSpace(vec3(coord, sceneDepth0));
    vec3 scenePos0      = viewToSceneSpace(viewPos0);

    float sceneDepth1   = stex(depthtex1).x;
    vec3 viewPos1       = screenToViewSpace(vec3(coord, sceneDepth1));
    vec3 scenePos1      = viewToSceneSpace(viewPos1);

    bool translucent = (sceneDepth0<sceneDepth1);

    bool water      = matID == 102;

    vec3 translucentAlbedo = sqr(decode3x8(tex2.g));
    
    float dist0     = distance(scenePos0, gbufferModelViewInverse[3].xyz);
    float dist1     = distance(scenePos1, gbufferModelViewInverse[3].xyz);

    float bluenoise     = ditherBluenoise();

    if (water){
        vec3 flatNormal     = normalize(cross(dFdx(scenePos0), dFdy(scenePos0)));
        vec3 flatViewNormal = normalize(mat3(gbufferModelView) * flatNormal);

        vec3 normalCorrected = dot(viewNormal, normalize(viewPos1)) > 0.0 ? -viewNormal : viewNormal;

        vec3 refractedDir   = refract2(normalize(viewPos1), normalCorrected, flatViewNormal, rcp(1.33));
        //vec3 refractedDir   = refract(normalize(viewPos1), normalCorrected, rcp(1.33));

        float refractedDist = distance(viewPos0, viewPos1);

        vec3 refractedPos   = viewPos1 + refractedDir * refractedDist;

        vec3 screenPos      = viewToScreenSpace(refractedPos);

        float distToEdge    = max2(abs(screenPos.xy * 2.0 - 1.0));
            distToEdge      = sqr(sstep(distToEdge, 0.7, 1.0));

        screenPos.xy    = mix(screenPos.xy, coord, distToEdge);

        //vec2 refractionDelta = coord - screenPos.xy;

        float sceneDepth    = texture(depthtex1, screenPos.xy).x;

        if (sceneDepth > sceneDepth0) {
            sceneDepth1 = sceneDepth;
            viewPos1    = screenToViewSpace(vec3(screenPos.xy, sceneDepth1));
            scenePos1   = viewToSceneSpace(viewPos1);

            sceneColor.rgb  = texture(colortex0, screenPos.xy).rgb;
        }
    }

    if (translucent && isEyeInWater==0){
        //if (landMask(sceneDepth1) && !water) sceneColor.rgb = simpleFog(sceneColor.rgb, dist1-dist0, lightColor[1]);
        if (water) sceneColor.rgb = waterFog(sceneColor.rgb, dist1-dist0, lightColor[1]);

        sceneColor.rgb    = blendTranslucencies(sceneColor.rgb, tex3, translucentAlbedo);
    }

    if (water){
        vec3 viewDir    = normalize(viewPos0);
        vec3 reflectDir = reflect(viewDir, viewNormal);

        if (dot(viewDir, viewNormal) > 0.0) viewNormal = -viewNormal;

        vec4 reflection     = screenRT(viewPos0, viewNormal, bluenoise, translucent);

        materialProperties mat  = materialProperties(0.0001, 0.02, false, false, mat2x3(1.0));
        
        vec3 fresnel   = BRDFfresnel(-viewDir, viewNormal, mat);
        //fresnel     = fresnelTinted(translucentAlbedo, dot(-viewDir, viewNormal));

        lightvec.yz     = rotatePos(lightvec.yz, radians(sunPathRotation));
        lightvec        = normalize(lightvec);

        lightvecView    = mat3(gbufferModelView) * lightvec;

        vec3 shadowPos  = getShadowmapPos(scenePos0, 0.08);
        float shadow    = texture(shadowtex1, shadowPos);
        
        sceneColor.rgb      = mix(sceneColor.rgb, reflection.rgb, vec3(reflection.a) * fresnel);
        if (shadow > 0.01) {
            vec3 directCol  = lightColor[0];
            sceneColor.rgb += BRDF_Beckmann(-viewDir, lightvecView, viewNormal, mat) * directCol;
        }

        //sceneColor.rgb = reflection.rgb;
    }

    #ifdef endSmokeEnabled
        mat2x3 fogData      = volumetricSmoke(scenePos0, normalize(scenePos0), ditherBluenoise(), !landMask(sceneDepth0));

        applyFogData(sceneColor.rgb, fogData);
    #endif

    //if (landMask(sceneDepth1) && isEyeInWater==1 && translucent) sceneColor.rgb = simpleFog(sceneColor.rgb, dist1-dist0, lightColor[1]);

    //if (landMask(sceneDepth0) && isEyeInWater==0) sceneColor.rgb = simpleFog(sceneColor.rgb, dist0, lightColor[1]);

    if (isEyeInWater==1) sceneColor.rgb = waterFog(sceneColor.rgb, dist0, lightColor[1]);

    /*DRAWBUFFERS:0*/
    gl_FragData[0]  = makeDrawbuffer(sceneColor);
}