#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"

in vec2 coord;

flat in mat2x3 lightColor;

uniform sampler2D colortex0;
uniform usampler2D colortex1;
uniform sampler2D colortex2;
uniform sampler2D colortex3;
uniform sampler2D colortex6;

uniform sampler2D depthtex0;
uniform sampler2D depthtex1;

uniform sampler2D noisetex;

uniform int frameCounter;
uniform int isEyeInWater;

uniform float eyeAltitude;
uniform float far;
uniform float frameTimeCounter;

uniform vec2 taaOffset;
uniform vec2 viewSize;

uniform vec3 cameraPosition;

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


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

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

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

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

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


/* ------ functions ------ */
vec3 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;
    float smokeGlowing = 0.0;

    if (smokeFade > 1e-10) {
        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    += value3D(pos * 8.0 - wind) - 0.5;

        pos.x *= 0.7;

        float noise     = value3D(pos * 4.0 + wind);
            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 - noise;
        float glowProximity = sqrt(sstep(length(rPos), 4.0, 24.0));
        smokeGlowing = saturate((glowProximity * (0.3 + cube(smokeFade) * 0.15) - noise) * pi);
        //smoke      -= 0.1;

        smoke       = cubeSmooth(max0(smoke)) * (smokeFade * 0.5 + 0.5);
        smokeGlowing *= smokeFade;
    }

    return vec3(1.0, smoke, smokeGlowing);
}

flat in vec3 fogCol;
flat in vec3 fogCol2;

const vec3 hazeCoeff    = vec3(2e-3);
const vec3 smokeCoeff   = vec3(3e-2);

mat2x3 volumetricSmoke(vec3 scenePos, vec3 sceneDir, float dither) {
    vec3 startPos   = gbufferModelViewInverse[3].xyz;
    vec3 endPos     = scenePos;
        if (length(endPos) > 192.0) endPos = sceneDir * 192.0;

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

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

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

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

    const mat2x3 scatterMat   = mat2x3(hazeCoeff, smokeCoeff);
    const mat2x3 extinctMat   = mat2x3(hazeCoeff * 1.1, smokeCoeff);

    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;

        vec3 density    = smokeDensity(rPos, altitude);

        vec2 stepRho    = density.xy * rLength;
        vec3 od         = extinctMat * stepRho;

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

        vec3 a          = visScatter * transmittance;

        mat2x3 stepScatter = mat2x3(scatterMat[0] * stepRho.x * a, scatterMat[1] * stepRho.y * a);

        #ifdef netherSmokeEmission
            vec3 smokeEmission = density.z * mix(vec3(1.0, 0.1, 0.0), lightColor[1] * 0.5, density.z) * stepRho.y * a;

            scattering     += mat3x3(stepScatter[0], stepScatter[1], smokeEmission);
        #else
            scattering     += mat3x3(stepScatter[0], stepScatter[1], vec3(0.0));
        #endif

        transmittance  *= stepT;
    }

    //vec3 smokeCol       = mix(fogCol, lightColor[1], saturate(scattering[1]));

    vec3 color          = scattering[0] * fogCol2 + scattering[1] * fogCol + scattering[2] * 0.6 * netherSmokeEmissionMult;

    return mat2x3(color, transmittance);     
}

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


/* ------ simple fog ------ */

vec3 simpleFog(vec3 scenecolor, float d, vec3 color) {
    //float dist      = max(0.0, d-16.0);
    float density   = (linStep(d, far * 0.4, far * 0.99));

    return mix(scenecolor, color, density);
}

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/labPBR.glsl"

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

/*
These two functions used for rough reflections are based on zombye's spectrum shaders
https://github.com/zombye/spectrum
*/

mat3 getRotationMat(vec3 x, vec3 y) {
	float cosine = dot(x, y);
	vec3 axis = cross(y, x);

	float tmp = 1.0 / dot(axis, axis);
	      tmp = tmp - tmp * cosine;
	vec3 tmpv = axis * tmp;

	return mat3(
		axis.x * tmpv.x + cosine, axis.x * tmpv.y - axis.z, axis.x * tmpv.z + axis.y,
		axis.y * tmpv.x + axis.z, axis.y * tmpv.y + cosine, axis.y * tmpv.z - axis.x,
		axis.z * tmpv.x - axis.y, axis.z * tmpv.y + axis.x, axis.z * tmpv.z + cosine
	);
}
vec3 ggxFacetDist(vec3 viewDir, float roughness, vec2 xy) {
	/*
    GGX VNDF sampling
	http://www.jcgt.org/published/0007/04/01/
    */

    viewDir     = normalize(vec3(roughness * viewDir.xy, viewDir.z));

    float clsq  = dot(viewDir.xy, viewDir.xy);
    vec3 T1     = vec3(clsq > 0.0 ? vec2(-viewDir.y, viewDir.x) * inversesqrt(clsq) : vec2(1.0, 0.0), 0.0);
    vec3 T2     = vec3(-T1.y * viewDir.z, viewDir.z * T1.x, viewDir.x * T1.y - T1.x * viewDir.y);

	float r     = sqrt(xy.x);
	float phi   = tau * xy.y;
	float t1    = r * cos(phi);
	float a     = saturate(1.0 - t1 * t1);
	float t2    = mix(sqrt(a), r * sin(phi), 0.5 + 0.5 * viewDir.z);

	vec3 normalH = t1 * T1 + t2 * T2 + sqrt(saturate(a - t2 * t2)) * viewDir;

	return normalize(vec3(roughness * normalH.xy, normalH.z));
}

vec2 unprojectSphere(vec3 dir) {
    vec2 lonlat     = vec2(atan(-dir.x, dir.z), acos(dir.y));
    return lonlat * vec2(rcp(tau), rcp(pi)) + vec2(0.5, 0.0);
}

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

    uvec4 tex1      = stex(colortex1);
        vec2 d1     = unpack2x16(tex1.y);
        vec2 d3     = unpack2x16(tex1.w);
    vec2 sceneLmap  = decode2x8(d1.x);

    vec3 sceneNormal = decodeNormal(unpack2x16(tex1.x));
    
    int matID       = decodeMatID16(d1.y);

    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);

    vec3 translucentAlbedo = vec3(1.0);

    bool water      = false;

    vec4 specularData = unpack4x8(tex1.z);

    #ifdef resourcepackReflectionsEnabled
        materialProperties material = labToGeneric(decodeSpecularTexture(specularData));
    #else
        materialProperties material = materialProperties(0.0001, 0.02, false, false, mat2x3(1.0));
    #endif

    if (translucent) {
        sceneNormal = decodeNormal(tex2.xy);
        vec2 d1     = decode2x8(tex2.z);
        matID       = decodeMatID8(d1.y);
        sceneLmap.y = d1.x;

        water       = matID == 102;

        vec3 tex6   = stex(colortex6).rgb;
        translucentAlbedo = decode3x8(tex6.x);

        if (water) material = materialProperties(0.0001, 0.02, false, false, mat2x3(1.0));
    }

    vec3 viewNormal = normalize(mat3(gbufferModelView) * sceneNormal);

    float bluenoise     = ditherBluenoise();

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

    /* ------ reflections ------ */
    
    #ifdef resourcepackReflectionsEnabled

    if (water || material.roughness < 0.95){
        vec3 viewDir    = normalize(viewPos0);

        vec4 reflection = vec4(0.0);

        vec3 metalAlbedo = sqr(decode3x8(unpack2x16(tex1.w).x));

        vec3 fresnel    = vec3(0.0);

        #ifdef roughReflectionsEnabled

        if (material.roughness < 0.0002 || water) {
            vec3 reflectDir = reflect(viewDir, viewNormal);

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

            vec3 viewNormalSSR  = viewNormal;

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

            reflection          = screenRT(viewPos0, viewNormalSSR, bluenoise, translucent);

            if (reflection.a < 0.5) {
                reflection.rgb      = lightColor[0] / 21.0;
                reflection.a        = 0.5;
            }

            fresnel   = BRDFfresnel(-viewDir, viewNormal, material, metalAlbedo);
        } else {
            float ssrAlpha  = 1.0 - sstep(material.roughness, roughSSR_maxR - 0.05, roughSSR_maxR);
            
            mat3 rot    = getRotationMat(vec3(0, 0, 1), viewNormal);
            vec3 tangentV = viewDir * rot;
            float noise = bluenoise;
            float dither = ditherGradNoiseTemporal();

            const uint steps    = roughReflectionSamples;
            const float rSteps  = 1.0 / float(steps);

            for (uint i = 0; i < steps; ++i) {
                vec2 xy         = vec2(fract((i + noise) * sqr(32.0) * phi), (i + noise) * rSteps);
                vec3 roughNrm   = rot * ggxFacetDist(-tangentV, material.roughness, xy);

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

                vec3 reflectDir = reflect(viewDir, roughNrm);

                vec3 viewNormalSSR  = roughNrm;

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

                vec4 currReflection     = vec4(0.0);

                if (ssrAlpha > 0.0) currReflection = screenRT_Fast(viewPos0, viewNormalSSR, dither, translucent);

                if (currReflection.a < 0.5) {
                    currReflection.rgb = lightColor[0] / 21.0;
                    currReflection.a   = 0.5;
                }
    
                reflection += currReflection;
                fresnel    += BRDFfresnel(-viewDir, roughNrm, material, metalAlbedo);
            }
            reflection *= rSteps;
            fresnel    *= rSteps;
            reflection.a *= 1.0 - linStep(material.roughness, roughReflectionsThreshold * 0.66, roughReflectionsThreshold);
        }

        #else

            vec3 reflectDir = reflect(viewDir, viewNormal);

            vec3 viewNormalSSR  = viewNormal;

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

            reflection          = screenRT(viewPos0, viewNormalSSR, bluenoise, translucent);

            if (reflection.a < 0.5) {
                reflection.rgb      = lightColor[0] / 21.0;
                reflection.a        = 0.5;
            }

            reflection.a *= 1.0 - linStep(material.roughness, roughReflectionsThreshold * 0.66, roughReflectionsThreshold);

            fresnel   = BRDFfresnel(-viewDir, viewNormal, material, metalAlbedo);

        #endif
        
        //vec3 fresnel   = BRDFfresnel(-viewDir, viewNormal, material, metalAlbedo);

        if (material.conductor && material.conductorComplex) reflection.rgb *= sqrt(metalAlbedo);
        
        if (material.conductor) sceneColor.rgb = mix(sceneColor.rgb, reflection.rgb * fresnel, reflection.a);
        else sceneColor.rgb = mix(sceneColor.rgb, reflection.rgb, vec3(reflection.a) * fresnel);

        #if DEBUG_VIEW == 6
            sceneColor.rgb = reflection.rgb;
        #endif
    }

    #else

    if (water){
        vec3 viewDir    = normalize(viewPos0);

        vec4 reflection = vec4(0.0);
        float skyOcclusion  = cubeSmooth(sqr(linStep(sceneLmap.y, 0.7, 0.9)));
        vec3 reflectDir = reflect(viewDir, viewNormal);

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

        reflection          = screenRT(viewPos0, viewNormal, bluenoise, translucent);
        
        vec3 fresnel   = BRDFfresnel(-viewDir, viewNormal, material);
        
        sceneColor.rgb = mix(sceneColor.rgb, reflection.rgb, vec3(reflection.a) * fresnel);

        #if DEBUG_VIEW == 6
            sceneColor.rgb = reflection.rgb;
        #endif
    }

    #endif

    sceneColor.rgb      = simpleFog(sceneColor.rgb, length(scenePos0), fogCol2);

    #ifdef netherSmokeEnabled
        mat2x3 fogData      = volumetricSmoke(scenePos0, normalize(scenePos0), ditherBluenoise());

        applyFogData(sceneColor.rgb, fogData);
    #endif

    if (isEyeInWater == 2) {
        sceneColor.rgb = lavaFog(sceneColor.rgb, length(viewPos0));
    }

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