/********************************************************
    © 2020 Continuum Graphics LLC. All Rights Reserved
 ********************************************************/

#extension GL_EXT_gpu_shader4 : enable

#include "/../ContinuumLib/Syntax.glsl"

#define DynamicShaderStage ShaderStage


varying mat3 tbn;

varying vec4 color;

varying vec3 tangent;
varying vec3 surfaceNormal;

varying vec3 viewSpacePosition;
varying vec3 tangentSpaceViewVector;
varying vec3 worldSpacePosition;

varying vec2 texcoord;
varying vec2 lightmaps;
varying vec2 midcoord;
varying vec2 tileSize;
varying vec2 atlasSize;

varying float parallaxDepth;
flat varying int blockID;


/***********************************************************************/
#if defined vsh

#include "/../ContinuumLib/Debug.glsl"

attribute vec4 at_tangent;
attribute vec3 mc_Entity;
attribute vec2 mc_midTexCoord;

uniform sampler2D texture;

uniform vec3 cameraPosition;

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

uniform vec2 taaJitter;

uniform float frameTimeCounter;

#include "/../ContinuumLib/Utilities.glsl"

#include "/../ContinuumLib/Uniform/ProjectionMatrices.glsl"

#include "/../InternalLib/Vertex/VertexDisplacement.vsh"

vec4 ProjectViewSpace(vec3 viewSpacePosition) {
    #if !defined hand
        return vec4(projMAD(projMatrix, viewSpacePosition), viewSpacePosition.z * projMatrix[2].w);
    #else
        return vec4(projMAD(gl_ProjectionMatrix, viewSpacePosition), viewSpacePosition.z * gl_ProjectionMatrix[2].w);
    #endif
}

void main() {
    surfaceNormal = gl_Normal;
    tangent = at_tangent.xyz / at_tangent.w;
    
    atlasSize = textureSize2D(texture, 0);

    color     = gl_Color;
    blockID   = int(mc_Entity.x);
    midcoord  = mc_midTexCoord.xy;
    texcoord  = gl_MultiTexCoord0.xy;
    lightmaps = gl_MultiTexCoord1.xy / 255.0;

    // Lit block fix
    if (blockID == 89
     || blockID == 169
     || blockID == 124
     || blockID == 51
     || blockID == 10
     || blockID == 11
     || blockID == 138
     || blockID == 198)
        { lightmaps.x = 1.0; }
    
    #if (defined terrain || defined hand || defined translucent)
        tileSize = abs(gl_MultiTexCoord0.xy - mc_midTexCoord.xy) * 2.0;
        const float pomDepthMultiplier = TERRAIN_PARALLAX_DEPTH * 0.0625;
        parallaxDepth = length(tileSize) * pomDepthMultiplier;
    #endif

    #if defined terrain || defined translucent
        tbn = mat3(tangent, cross(tangent, surfaceNormal), surfaceNormal);
    #endif

    mat3 tbnMod = mat3(tangent * atlasSize.y / atlasSize.x, tbn[1], tbn[2]);

    CalculateProjectionMatrices();

    viewSpacePosition = transMAD(gl_ModelViewMatrix, gl_Vertex.xyz);

    worldSpacePosition = transMAD(gbufferModelViewInverse, viewSpacePosition);

    #if defined terrain
        calculateDisplacement(worldSpacePosition, lightmaps.y, blockID);
    #endif

    viewSpacePosition = transMAD(gbufferModelView, worldSpacePosition);
    tangentSpaceViewVector = (viewSpacePosition * gl_NormalMatrix) * tbnMod;

    vec4 position = ProjectViewSpace(viewSpacePosition);

    #ifdef TAA
		position.xy = taaJitter * position.w + position.xy;
	#endif

    gl_Position = position;
}

#endif
/***********************************************************************/



/***********************************************************************/
#if defined fsh

#include "/../ContinuumLib/Debug.glsl"

uniform sampler2D tex;
uniform sampler2D normals;
uniform sampler2D specular;
uniform sampler2D noisetex;

uniform mat4 gbufferModelView;
uniform mat4 gbufferModelViewInverse;

uniform vec4 entityColor;

uniform vec3 shadowLightPosition;
uniform vec3 cameraPosition;

uniform float frameTimeCounter;
uniform int frameCounter;

uniform int isEyeInWater;

#include "/ShaderConstants.glsl"

#if defined weather
/* DRAWBUFFERS:15 */
layout (location = 0) out vec4 Buffer1; //colortex1 - RGBA8
layout (location = 1) out vec4 Buffer5; //colortex5 - R8
#else
/* DRAWBUFFERS:125 */
layout (location = 0) out vec4 Buffer1; //colortex1 - RGBA8
layout (location = 1) out vec4 Buffer2; //colortex2 - RGBA16
layout (location = 2) out vec4 Buffer5; //colortex2 - RGBA16
#endif

#define LAYOUT_0 Buffer1
#include "/../ContinuumLib/Exit.glsl"

#include "../ContinuumLib/Utilities.glsl"

#include "/../ContinuumLib/Uniform/ProjectionMatrices.glsl"
#include "/../InternalLib/Fragment/TerrainParallax.fsh"
#include "/../InternalLib/Fragment/LightmapShading.fsh"
#include "/../InternalLib/Fragment/WaterWaves.fsh"

#include "/../ContinuumLib/Common/MatID.glsl"

vec2 calculateParallaxWaterCoord(vec3 position, vec3 tangentVec, float roughnessDistanceEstimate){
    #ifndef WATER_PARALLAX
        return position.xz;
    #endif
    
    const int steps = WATER_PARALLAX_SAMPLES;
    const float rSteps = inversesqrt(steps);

    const float maxHeight = WATER_PARALLAX_DEPTH;

    vec3 direction = normalize(worldSpacePosition - gbufferModelViewInverse[3].xyz);

    mat3 tbnFix = tbn;

    if (isEyeInWater == 1) {
        tbnFix = transpose(tbn);
    }

    direction = direction * tbnFix;

    vec3 increment = rSteps * direction / -direction.z;
    float height = getwaves(position.xz, roughnessDistanceEstimate);
    vec3 offset = -height * increment;
    height = getwaves(position.xz + offset.xy, roughnessDistanceEstimate) * maxHeight;

    for (int i = 1; i < steps - 1 && height < offset.z; ++i) {
    	offset = (offset.z - height) * increment + offset;
    	height = getwaves(position.xz + offset.xy, roughnessDistanceEstimate) * maxHeight;
    }

    if (steps > 1) {
    	offset.xy = (offset.z - height) * increment.xz + offset.xy;
    }

    position.xz += offset.xy;

    return position.xz;
}

float calculateRainPuddles(vec2 p) {
    vec2 pos = p * 0.005;

    float weight = 1.0;
    float totWeight = 0.0;
    float freq = 1.0;
    float totalNoise = 0.0;

    for (int i = 0; i < 8; i++) {
        totalNoise += texture(noisetex, pos * freq).x * weight;
        weight = mix(weight, 0.0, 0.5);
        totWeight += weight;

        freq *= 2.0;
    }

    return totalNoise / totWeight;
}

float calculateSplashHeightMap(vec2 position, float puddles) {
    if ((1.0 - puddles) <= 0.0) return 0.0;

    position *= 4.0;

    const float scale = 16.0;
    const float speed = 1.5;
    const float halflife = 2.0;

    float totalWave = 0.0;

    for (int i = -1; i <= 1; i++) {
        for (int j = -1; j <= 1; j++) {
            vec2 g = vec2(i, j);
            vec2 newP = position + g;

            vec2 fl = floor(newP);
            vec2 fr = fract(newP) - 0.5 - g;

            float randTime = hash12(fl) * 32452.0;
            float f = (frameTimeCounter * speed + randTime) / halflife;
            float rand = hash12(fl + floor(f) * halflife);
            float mask = step(rand, 0.25) * (1.0 - fract(f));

            float dis = length(fr) * scale;
            float atten = 1.0 / (dis * dis + 1.0);
            float k = dis - fract(f) * halflife * scale;
            float wave = exp(-abs(k)) * sin(k);

            totalWave += wave * atten * mask;
        }
    }

    return totalWave * puddles;
}

vec3 calculateSplashNormal(vec2 position, float puddles) {
    const vec2 delta = vec2(0.01, 0.0);

    float c = calculateSplashHeightMap(position, puddles);
    float cx = calculateSplashHeightMap(position + delta.xy, puddles);
    float cy = calculateSplashHeightMap(position + delta.yx, puddles);

    float dx = (c - cx) / delta.x;
    float dy = (c - cy) / delta.x;

    return normalize(vec3(dx, dy, 1.0));
}

vec4 sampleTexture(sampler2D tex, vec2 uv, mat2 texD) {
    #ifdef BILINEAR_TEXTURES
        return texBilinear(tex, uv, texD);
    #else
        return texture2DGrad(tex, uv, texD[0], texD[1]);
    #endif
}

void main() {
    //This will be seperated into multiple sections, one for opauqe one for transparents.
    //Transparents have 2 modes of rendering. Forward Lit and Front Lit.
    //Front lit lighting and blending is done in the composite, however lighting will not go through the layers.
    //Forward Lit is lit in the gbuffer, it handles shading and reflections on every layer and is correctly ordered, this is slow.
    //In forward lit We take in the lit buffer and blend lit verticies with vl and other effect applied over or between surfaces over the buffer.

    //Out data will be Albedo(RGB), Normal(XY regen Z), Roughness, Reflectivity(f0), Emissive(Chroma), Lightmaps(XY), Material Flags.

    #if defined weather
        vec4 albedo = texture2D(tex, texcoord) * color;
        if (albedo.a < 0.1000003) { discard; }
        Buffer5 = vec4(albedo.a, 1.0, 1.0, albedo.a);
        return;
    #else
    
    #if defined WORLD0 || defined WORLD1
    bool isWater = blockID == 8 || blockID == 9;
    if (isWater && !gl_FrontFacing) discard;
    #endif
    
    mat2 texD = mat2(dFdx(texcoord), dFdy(texcoord));
    
    float pomShadow = 1.0;
    vec2  pomCoord  = TerrainPOM(texcoord, texD, pomShadow);
    
    vec4 albedo = texture2DGrad(tex, pomCoord, texD[0], texD[1]) * color;

    #if defined entities
        albedo.rgb = mix(albedo.rgb, albedo.rgb * entityColor.rgb, sqrt(entityColor.a));
    #endif
    
    #if defined basic
        albedo = color;
    #endif
	
    #if defined WORLD0 || defined WORLD1
    if (isWater) albedo = vec4(0.0);
    #endif
    
    #if !defined translucent
        if (albedo.a < 0.1000003) { discard; }
    #endif

    vec4 normalTexture     = sampleTexture(normals, pomCoord, texD);

    float ao = normalTexture.z * float(normalTexture.z > 0.0) + float(normalTexture.z <= 0.0);

    bool hasNormalInformation = normalTexture.z > 0.0;

    normalTexture.xy  = normalTexture.xy * 2.0 - 1.0;
    normalTexture.z = sqrt(max0(1.0 - dot(normalTexture.xy, normalTexture.xy)));

    vec3 normal = normalTexture.xyz;
    if (!hasNormalInformation) {
        normal = vec3(0.0, 0.0, 1.0);
    }
    
    vec4  materialInfo = sampleTexture(specular, pomCoord, texD);
    float porosity     = 1.0 - materialInfo.z;
    float reflectivity = materialInfo.y;
    float roughness    = 1.0 - materialInfo.x;
    float emissivity   = 1.0 - materialInfo.a;
    float materialID   = PackMaterialID(reflectivity, blockID);

    /*
    #if defined terrain
        vec3 worldPos = worldSpacePosition + cameraPosition;
        float puddles = calculateRainPuddles(worldPos.xz);
        puddles = smoothstep(0.75, 1.0, puddles);
        puddles = mix(1.0, puddles, smoothstep(0.7, 1.0, lightmaps.y) *  max0(dot(tbn[2], vec3(0.0, 1.0, 0.0))));
        roughness = puddles;

        vec3 splashNormal = calculateSplashNormal(worldPos.xz, (1.0 - puddles) * clamp01((lightmaps.y - 0.9) * 100.0));
    #endif
    */

    #if defined translucent
        #if defined WORLD0 || defined WORLD1
        if (isWater) {
            vec3 worldVector = -normalize(worldSpacePosition - gbufferModelViewInverse[3].xyz);
            float NoV = dot(surfaceNormal, worldVector);

            float angleEstimate = abs(NoV);

            float roughnessDistanceEstimate = sqrt(dot(viewSpacePosition, viewSpacePosition) / angleEstimate);
                  roughnessDistanceEstimate = 1.0 - exp2(-roughnessDistanceEstimate * 0.01);

            roughness = roughnessDistanceEstimate * 0.1 + 0.01570796;
            materialID = 5.0;

            vec3 wavePosition;
            wavePosition    = worldSpacePosition + cameraPosition;
            wavePosition.xz = calculateParallaxWaterCoord(wavePosition, tangentSpaceViewVector, roughnessDistanceEstimate);

            normal = calculateWaterNormal(wavePosition, roughnessDistanceEstimate);
        } else { // Non-water translucent, Ex: glass
        #endif
            roughness  = (roughness >= 1.0) ? 0.025 : roughness;
            materialID = 4.0;
        #if defined WORLD0 || defined WORLD1
        }
        #endif
        
        if (tangentSpaceViewVector.z > 0.0)
            { normal = -normal; }
    #endif

    #if defined terrain || defined translucent
        normal = tbn * normal;
    #else
        vec3 fixedWorldPosition = worldSpacePosition - gbufferModelViewInverse[3].xyz;
        vec3 calculatednormal = normalize(cross(dFdx(fixedWorldPosition), dFdy(fixedWorldPosition)));
             calculatednormal = clamp(calculatednormal, -1.0, 1.0);

        /*
        vec2 pos = texcoord * atlasSize;
	    texD = mat2(dFdx(pos), dFdy(pos));

        vec3 dp2perp = cross(dFdy(fixedWorldPosition), calculatednormal);
        vec3 dp1perp = cross(calculatednormal, dFdx(fixedWorldPosition));
        vec3 newTangent = dp2perp * texD[0].x + dp1perp * texD[1].x;
        vec3 newBinormal = dp2perp * texD[0].y + dp1perp * texD[1].y;

        float invmax = inversesqrt(max(dot(newTangent, newTangent), dot(newBinormal, newBinormal)));
        mat3 newTbn = mat3(newTangent * invmax, newBinormal * invmax, calculatednormal);
        */

        vec3 binormal = cross(tangent, calculatednormal);
        mat3 newTbn = mat3(tangent, binormal, calculatednormal);
        
        normal = newTbn * normal;
    #endif

    /*
    #if defined terrain
        mat3 normalTbn = GenerateTBN(normal);
        normal = normalTbn * splashNormal;
    #endif
    */

    // Welcome to Continuums Material System, I'm going to open source this pipeline in the future.
    // To start we have a smoothness input, a f0 input, an emmissivity/subsurface input, and porosity/smoothnessY.
    // You probably notice that that's more than 4 things. thats because the f0 input can be used as a control for what kind of material it is.
    // If the f0 is in the range of 0.0 - 0.45 the surface is a dielectric, assuming it's not a plant we take in porosity, f0, roughness, and emissivity.
    // If the f0 is above 0.45 the BRDF becomes anisotropic, smoothness becomes smoothnessX and porosity becomes smoothnessY.
    // If the block is a sub surface block IE Snow, Grass, Tall Grass, Leaves, vines, etc, emissivity becomes SubSurface or Light Translucency.

    #ifdef WHITE_WORLD
        albedo.rgb = vec3(1.0);
    #endif

    Buffer1 = albedo;

    float dither = bayer64(gl_FragCoord.xy);

    vec4 materialData; //MatFlags
    materialData.x = EncodeNormal(normal);
    materialData.y = Encode2x8(clamp(roughness, 0.01570796, 1.0), materialID * 0.03125);
    #if !defined basic
        materialData.z = Encode2x8((dither / 255.0 + vec2(CalculateBlockLight(normal, lightmaps.x), lightmaps.y * sqrt(ao))));
    #else
        materialData.z = 0.0;
    #endif
    materialData.w = Encode2x8(pomShadow, 1.0);
    
    Buffer2 = materialData;
    Buffer5 = vec4(0.0, 0.0, 0.0, 1.0);
	
	exit();
    #endif
}

#endif
/***********************************************************************/
